EnfusionTexKit

EnfusionTexKit

enables creation of Enfusion/Arma Reforger PBR materials — generates maps, packs Enfusion channels (BCR/NMO), writes .emat files, and stages them atomatically into addons via MCP or CLI.

Category
访问服务器

README

EnfusionTexKit

Headless PBR-Texture-Adapter für Arma Reforger / Enfusion — von einer Quelltextur zum einsatzbereiten .emat im Addon, per MCP oder CLI.

TexKit portiert die Map-Erzeugungs-Algorithmen von Materialize (GPL-3.0) in einen schlanken Python-Kern und ergänzt genau das, was Materialize für Enfusion prinzipbedingt nicht liefern kann: das korrekte Enfusion-Kanal-Packing, die .emat-Generierung und die atomare Ablage im Ziel-Addon.


Warum das Ganze

Materialize erzeugt gute PBR-Einzelmaps, aber sein Output passt nicht auf Enfusion:

  • Die Property-Map packt nur R/G/B — Enfusion braucht Occlusion in NMO.a und Roughness in BCR.a.
  • Materialize liefert Smoothness, Enfusion will Roughness (R = 1 − S).
  • .emat-Erzeugung und Addon-Ablage fehlen komplett.

TexKit schließt diese Lücke. Es ist bewusst ein Adapter, kein Materialize-Ersatz: kein GUI, keine Echtzeit-Vorschau, kein GPU-Pfad. Quelltextur (oder fertige Materialize-Exporte) rein → fertiges Enfusion-Material im Addon raus. Materialize bleibt für die Handarbeit (Tiling, Alignment) im Werkzeugkasten.


Was es kann

  • End-to-end Map-Generierung aus einer einzigen Diffuse-Quelltextur: Licht/Schatten-Entfernung → Height → Normal (DirectX) → Metallic/Roughness → AO → optional Edge-Wear.
  • Enfusion-Kanal-Packing nach Vanilla-Konvention (BCR / NMO / Opacity).
  • Material-Presets als Erkennungslogik, nicht als Flachwerte — eine Uniform bleibt matt, ihre Schnallen glänzen trotzdem (siehe Presets).
  • Zwei-Phasen-.emat-Flow, der echte .edds-GUIDs aus dem Workbench-Import verdrahtet.
  • Ingest-Brücke für bereits in Materialize erzeugte Einzelmaps (Smoothness→Roughness, Normal-Y-Konvention).
  • Kanal-Kontaktblatt pro Lauf als visuelle Kontrolle + tex_inspect für Zahlen ohne Viewer-Fehldeutung.
  • Läuft als MCP-Server (für Claude) und als CLI über demselben Kern.

Nicht-Ziele (v1)

Kein GUI/Vorschau, kein GPU-Pfad, kein eigener BCn/EDDS-Encoder (die Workbench kompiliert PNG→.edds), kein Tiling-Maker, kein Perspektiv-Alignment, keine Modell-/Prefab-Zuweisung (das macht enfusion-mcp).


Installation & Start

Runtime: Python ≥ 3.12, Abhängigkeiten numpy, pillow, scipy.

Als MCP-Server (registriert in claude_desktop_config.json und ~/.claude.json)

uv run --no-project --with mcp --with numpy --with pillow --with scipy server.py

--with mcp allein würde eine isolierte Umgebung ohne den Bildstack erzeugen. Das mcp-Paket bleibt bewusst nicht global installiert.

Als CLI

python cli.py <command> ...

Der Zwei-Phasen-Workflow

Feldbefund aus Vanilla und Phillips Addons: .emat referenziert ausschließlich .edds, nie PNG. Die .edds (+ .edds.meta mit GUID) entsteht erst durch den Workbench-Import. Deshalb ist der Ablauf zwingend zweistufig:

┌─ Phase 1 (TexKit) ──────────────┐   ┌─ Dazwischen ────────┐   ┌─ Phase 2 (TexKit) ─────────────┐
│ tex_material / material         │   │ Workbench-Import     │   │ tex_emat / material-finalize    │
│ Quelle → Maps → Packing →       │──▶│ PNG → .edds +        │──▶│ liest GUIDs aus .edds.meta,     │
│ PNGs atomar ins Addon staged    │   │ .edds.meta (WB-GUID) │   │ schreibt fertiges .emat         │
└─────────────────────────────────┘   └─────────────────────┘   └─────────────────────────────────┘

Fehlen in Phase 2 die .edds.meta, bricht TexKit mit einer klaren Import-Aufforderung ab, statt ein kaputtes Material zu schreiben. Der Workbench-Import selbst läuft außerhalb von TexKit (headless via enfusion-mcp orchestrierbar).


MCP-Werkzeuge

Alle Tools sind zustandslos — jeder Aufruf ist vollständig über seine Argumente beschrieben.

Tool Phase Zweck
tex_material(source, preset, addon_dir, name, sub_path?, overrides?, overwrite?) 1 End-to-end: Quelle → alle Maps → Packing → atomar ins Addon staged, mit Kontaktblatt. Schreibt bewusst noch kein .emat.
tex_generate(source, preset, out_dir, overrides?) Nur Maps + Kontaktblatt in einen freien Ordner (kein Addon-Schreiben). Für Experimente/Sichtprüfung.
tex_emat(addon_dir, name, family, sub_path?, overwrite?) 2 Schreibt das .emat, nachdem die Workbench die PNGs zu .edds importiert hat. Liest GUIDs aus .edds.meta.
tex_pack(maps_dir, out_dir, name, normal_y?) Ingest: Materialize-Einzelmaps → BCR/NMO. Invertiert Smoothness zu Roughness. normal_y: directx (Default) / opengl.
tex_params(preset?) Alle Presets mit sämtlichen Parametern + Defaults als JSON. Die Feldnamen sind zugleich die gültigen Override-Pfade.
tex_inspect(image) Kanalstatistiken (min/max/mean je R/G/B/A) + Neutralitäts-Hinweise — prüft BCR/NMO/Masken ohne Viewer.

CLI

Spiegelt die MCP-Tools; Fehler → error: ... auf stderr + Exit 1.

# Nur Maps + Kontaktblatt in einen Ordner
python cli.py generate <source> --preset metall --out <dir> [--overrides '{"roughness.base_rough":0.5}']

# Phase 1: Maps ins Addon stagen
python cli.py material <source> --preset stoff --addon <addon_dir> --name MyMat [--sub-path Assets/TexKit] [--overwrite]

# Phase 2: .emat aus .edds.meta-GUIDs (nach Workbench-Import)
python cli.py material-finalize --addon <addon_dir> --name MyMat --family stoff [--overwrite]

# Ingest: Materialize-Einzelmaps packen
python cli.py pack <maps_dir> --out <dir> --name MyMat [--normal-y directx|opengl]

# Presets + Parameter als JSON listen
python cli.py params

Material-Presets

Presets sind Erkennungslogik, keine Flachwerte. Reale Assets mischen Materialien in einer Textur — jede Familie definiert daher neben den Basiswerten eine Detail-Erkennung (Sättigungs-/Luminanz-Heuristik), die abweichende Bereiche findet und dort Metalness/Roughness lokal umsteuert.

Preset Basis Erkennung tut Cutout
metall metallisch, glänzend senkt Metal/Glanz auf Dreck, Rost, Gummi-/Stoffanteilen (auch für Fahrzeuge/lackiertes Metall) nein
stoff matt, Metal 0 (base_rough=0.92) gibt nur an Metall-Hardware und Glanzleder Glanz frei (strengere Schwellen: sat_gain=4.0, lum_floor=0.35) nein
foliage dielektrisch matt (base_rough=0.75) Metal hart 0 (detect_gain=0, metal_cap=0) — Alphakanal Pflicht, erzeugt _Opacity ja

Overrides per flachem Punktpfad-Dict — der Pfad ist entweder ein Top-Level-Preset-Feld oder ein sub_params.feld:

{ "roughness.base_rough": 0.5, "edge_wear": 0.2, "metallic.sat_gain": 3.0 }

tex_params listet jeden gültigen Pfad. Ein unbekannter Pfad wirft ValueError mit Nennung des Pfads.


Kanal-Konventionen

Datei Packung
<Name>_BCR BaseColor (RGB) + Roughness in A
<Name>_NMO Normal.RG (DirectX, Y-down/Grün invertiert) + Metal in B + Occlusion/AO in A
<Name>_Opacity / _Mask Graustufe in RGB, A = 255 (nur bei Cutout-Presets)

⚠️ Viewer deuten Daten-Alpha als Transparenz fehl. Beurteile gepackte Maps deshalb nie am RGBA-Bild direkt, sondern über das Kontaktblatt (<Name>_contact.png, pro Lauf erzeugt) oder tex_inspect.

Die Silhouetten-Maske wird immer genutzt, wenn die Quelle echte Transparenz trägt (alpha.min() < 1.0, Schwelle alpha ≥ 8/255) — sonst prägen sich Hintergrund-Halos in Height/Normal ein. cutout-Presets verlangen diese Maske zusätzlich und brechen ohne Alpha mit ValueError ab.


Robustheit

  • Atomare Schreibvorgänge: erst temp, dann rename — kein halbes PNG landet im Addon.
  • Staging innerhalb des Addons (gleiche Partition ⇒ echter Rename, kein EXDEV-Copy): der Normalfall ist ein einziger atomarer Verzeichnis-Rename.
  • Überschreibschutz: existierende Zieldateien ⇒ Abbruch mit Kollisionsliste, außer overwrite=True.
  • Rollback mit Rettungsanker: schlägt ein Overwrite mittendrin fehl, wird vollständig zurückgerollt; überschriebene Dateien liegen im _backup und werden nie stillschweigend gelöscht.
  • Kontaktblatt landet neben Data/, nicht darin (unregistrierte PNGs im Asset-Baum erscheinen sonst als 0000-GUID-Transienten im Resource Browser).

Projektstruktur

EnfusionTexKit/
├── texkit/                  # Rechenkern — importiert weder MCP noch CLI
│   ├── ops.py               # Box-Blur, Blur-Pyramide, Push-Pull-Inpaint, Resampling, atomares Speichern
│   ├── diffuse.py           # Edit-Diffuse (Licht/Schatten/Hotspot-Entfernung)
│   ├── height.py            # Height-from-Diffuse (Frequency-EQ)
│   ├── normal.py            # Normal-from-Height (Vorwärtsdifferenzen, DirectX-G)
│   ├── metallic.py          # Metallic-Heuristik (HSL-Distanz)
│   ├── roughness.py         # Roughness (invertierte Smoothness)
│   ├── ao.py                # Multi-Scale AO
│   ├── edge.py              # Curvature/Edge-Map (fließt optional in Roughness)
│   ├── presets.py           # metall/stoff/foliage + Override-Auflösung
│   ├── packer.py            # BCR/NMO/Mask/Opacity-Packing
│   ├── emat.py              # .emat-Generator aus templates/
│   ├── contact.py           # Kanal-Kontaktblatt
│   ├── ingest.py            # Materialize-Export-Erkennung & -Übernahme
│   ├── validate.py          # Name/sub_path/preset-Validierung (von server.py & cli.py geteilt)
│   └── pipeline.py          # Orchestrierung: generate / material / finalize_material
├── templates/               # .emat-Vorlagen je Familie (aus Vanilla destilliert)
├── server.py                # MCP-Server (stdio)
├── cli.py                   # CLI-Zugang
├── tests/                   # Golden- & Invarianten-Tests, Dogtag-Fixture (61 Tests)
├── docs/                    # Spec, Plan, Backlog, Verifikation
└── LICENSE                  # GPL-3.0

Tests

python -m pytest
  • Golden-Tests: Referenztextur → SHA-256 der Maps; Algorithmus-Drift schlägt an.
  • Invarianten-Tests: NMO außerhalb der Silhouette neutral, Roughness-Roundtrip, Kanalbelegung, Masken RGB-identisch mit A=255.
  • Realfall-Fixture: der Dogtag-Durchlauf.

test_server_smoke wird ohne mcp im Test-Interpreter übersprungen; der echte Regressionsschutz für server.py ist der uv-Smoke (uv run --with mcp ... -c "import server").

Status (2026-08-03): v1 auf master, 61 Tests grün, MCP registriert. Engine-Parse live verifiziert (Workbench lädt/parst das erzeugte .emat). Offen: reine In-World-Sichtprüfung (Normal-Richtung/Glanz am Objekt im Licht).


Status & Roadmap

Voller Backlog aus dem Final-Review: docs/backlog.md.

v1.1 (Aufräumen): ungenutzte write_meta/texture.meta.tpl markieren, scipy aus Deklaration streichen (deklariert, nie importiert), Zweierpotenz-Warnung, overlay_blur_frac verdrahten oder entfernen.

v2 (Substanz): .gamemat/SurfaceProperties je Familie differenzieren, voll templategetriebene Slot-Ableitung in finalize_material, Ingest-Cutout-Support, weitere Familien (Leder/Holz/Gummi), Tiling-Maker, Alignment, GPU-Pfad, Workbench-Auto-Import-Verkettung, .mtz-Import.


Lizenz

GPL-3.0. Die Map-Erzeugungs-Algorithmen sind Ports aus Materialize (GPL-3.0), daher steht das gesamte Projekt unter GPL-3.0. Lokale Nutzung ist davon unberührt — die GPL-Pflichten greifen nur bei Weitergabe.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选