English | 简体中文
Conversion Specification: English | 简体中文
Renderer JSON Config Guide: English | 简体中文
Maya toolkit for PBR material conversion, building, and scene management across Arnold / Redshift / V-Ray.
Place the materialConvert folder anywhere, then create a Shelf button in Maya with the following 3 lines (replace the path with your actual path):
Option 1: Copy to Maya scripts directory (recommended)
import sys
sys.path.insert(0, r"C:\Users\<username>\Documents\maya\<version>\scripts\materialConvert")
exec(open(r"C:\Users\<username>\Documents\maya\<version>\scripts\materialConvert\main.py").read())Option 2: Place in any directory
import sys
sys.path.insert(0, r"your_path\materialConvert")
exec(open(r"your_path\materialConvert\main.py").read())Option 3: Use bat file (easiest)
Double-click copy_launch.bat to copy the launch command to clipboard, then paste directly into Maya Script Editor.
main.py does not require __file__: it accepts the executing file's directory only when that directory really is this project, otherwise it falls back to the first sys.path entry containing main.py + core/__init__.py. Both exec(open(...).read()) snippets above and the bat launcher therefore resolve the same project root.
Supported: Maya 2024+
Requires: none (zero external dependencies, pure maya.cmds API)
- Batch convert PBR materials between Arnold / Redshift / V-Ray
- Auto-detect material types, one-click convert all
- Supports bump/normal nodes, color correction nodes, and displacement nodes
- Preserves roughness semantics: source channels stored as glossiness (e.g. V-Ray
reflectionGlossiness) are inverted automatically - Preserves normal-space semantics: object-space normal maps keep their space on targets that expose a switch, and unsupported targets are reported instead of silently downgraded
- Transactional shading-engine commit: a failed SG connection rolls the touched SGs back to their previous wiring and reports it
- Progress bar for batch conversion, single-step undo (Ctrl+Z)
- Supports 6 material types:
aiStandardSurface/aiOpenPBRSurface/RedshiftMaterial/RedshiftOpenPBRMaterial/RedshiftStandardMaterial/VRayMtl
- One-click build complete PBR materials from texture paths
- Supports Color / Roughness / Glossiness (inverted) / Metallic / Normal / Bump / Displacement / Opacity / Transmission / Reflection / Sheen / SSS / Emission channels
- SSS channel support (colorCorrect + layeredTexture)
- Displacement node chain support
- Material type dropdown driven by
config/material/*.json— new materials appear automatically - Optional "Add To Quick Select Set" toggle
- Optional "Use Full Builder Pipeline" toggle (on by default): the Roughness channel only goes through the full chain (colorCorrect + layeredTexture + ramp); Metallic / Opacity always connect directly
- Scan a directory (optionally with
Include Subfolders; each subdirectory is scanned on its own) and auto-parse PBR texture sets by filename (config/texture_channels.json); a channel token shared by most files of a set (e.g. a_METALNESSsuffix on every file) is treated as a set-wide suffix rather than the channel, and a numeric suffix glued to the token (nrm16,disp16) still matches; artwork/preview files whose name carries anignore_keywordstoken (preview,thumb,render, …) are skipped entirely - Group textures into materials and preview which materials will be created (
Materials to Build) - Show parsed channels, unparsed files and every texture conflict in one table with a sortable Status column (
OK/CONFLICT (kept)/CONFLICT/UNPARSED); the first file in sorted order is kept and every rejected candidate stays visible - Optional
Skip Materials With Conflictstoggle (off by default) so an unresolved channel conflict never silently builds with an arbitrary file - Duplicate channel: the file with the higher bit depth / resolution wins (
NRM16overNRM,nrm4kovernrm), then the channelpriority(RoughnessoverGlossiness,NormaloverBump), then the first file in name order; the table marks the kept fileCONFLICT (kept)(amber), each dropped duplicateCONFLICT(red) and unrecognised filesUNPARSED(grey), with a legend below the table - Batch build all / selected materials to any supported renderer
- Toggle full Builder pipeline (colorCorrect + layeredTexture + ramp, Roughness channel only) or simple direct connection
- Supports BaseColor / Roughness / Glossiness (inverted) / Metallic / Normal / Bump / Displacement / Opacity / Transmission / Reflection / Sheen / SSS (Translucency + Scattering) / Emission
Below Build Options there is a collapsed by default panel for session-only (never written to disk) channel-name overrides and ignore tokens. Expanding it shows an Ignore line plus three side-by-side editors (grouped by channel type):
Color BaseColor =
Transmission =
…
Scalar Roughness =
Glossiness =
…
Geometry Normal =
Bump =
Displacement =
Ignore =
Syntax
| Line | Meaning |
|---|---|
BaseColor = d, skin |
Match this channel with only these words (replaces the alias list from texture_channels.json) |
BaseColor = (empty) |
Keep the aliases from texture_channels.json |
Ignore = g, metalness |
These tokens are never read as a channel marker and are stripped from the material name |
# ... |
Comment (whole line or trailing) |
- Channel names are case-insensitive (
basecolor=BaseColor); words are comma-separated, spaces around commas are fine, empty/trailing commas are ignored. - Applied when you click Scan Directory (the scanner is rebuilt); the Colorspace tab shares the same overrides through one
ConfigLoader, so both tabs always agree.
Matching rules (identical to the aliases in texture_channels.json)
- Token equality:
_dmatchesd. - Word + glued number:
d100→d,s050→s,b005→b,nrm16→nrm,rough4k→rough. - Long words (≥4 chars) across underscores:
met_allic→metallic. - Short words (≤3 chars) match whole tokens only, so
schoolnever matchescol.
Key rule: every "letter + number" token needs a home
Every token like d100 / s050 / g025 / b005 / r100 / p050 must land either in a channel line or in the Ignore line. The one you leave out stays in the material name and splits the set into a separate material.
For example (- is a separator, so s050-g025 is two tokens):
concrete-45_d100.jpg -> BaseColor
concrete-45_b010.jpg -> Bump
concrete-45_s050-g025.jpg -> needs both s050 and g025 handled
| Panel line | Material name for concrete-45_s050-g025.jpg |
|---|---|
Glossiness = s |
concrete_45_g025 ❌ leftover |
Glossiness = g |
concrete_45_s050 ❌ leftover |
Glossiness = s, g |
concrete_45 ✅ |
Glossiness = s + Ignore = g |
concrete_45 ✅ |
Why single letters are user-supplied
Single letters (d/b/s/g…) have no built-in table because their meaning differs per library (_r is reflection in one library and roughness in another) and libraries use different suffix shapes (_top / _front / (g000)). Written in the panel they are ordinary aliases:
- ✅ matched anywhere in the name, so
concrete-52_b005_top.jpgandconcrete-05_s100-(g000).jpgwork too; ⚠️ but there is no built-in false-positive guard:g1.pngor a stray_gin a hash name will be misread as a channel — add those toIgnore.
Relation to the automatic rule: besides the panel, the scanner treats an alias token shared by most files of a set (≥60% of ≥3 files, e.g. a _METALNESS suffix) as a set-wide suffix; Ignore is its manual counterpart.
- Dedicated Colorspace tab — the single UI entry point for all file-node color-space operations (Node Tools no longer exposes any color-space UI)
- File Node List: scans all scene
filenodes into a sortable table (File Node / File Path / Colorspace / Prematch Colorspace / Diagnostic); the Diagnostic column carries the match state (e.g.CONFLICT: ...) and sorts by severity, not alphabetically - Refresh: evaluates automatic matching for every file node without modifying the scene
- Automatic Matching driven by two drivers: Name Driver (channel aliases from
config/texture_channels.json, plus the session overrides set in the Batch Builder's Custom Channel Names panel — the matched channel'stypedecides srgb vs raw) and Channel Driver (BFS-tracing all downstream connections againstcommonAttributeRoles+config/material/*.jsonexpanded keywords); final statesMATCHED/CONFLICT/AMBIGUOUS/UNMATCHED/INVALID. An alias shared by most files of the same directory (e.g. a_METALNESSsuffix on a whole set) is treated as a set-wide suffix and not used as evidence for one file, so the channel evidence can decide - Apply Selected / Apply All Matched: apply only
MATCHEDresults tofile.colorSpace(single undo chunk);CONFLICT/AMBIGUOUS/UNMATCHED/INVALIDare never auto-applied - Manual Colorspace Assignment: pick from the actual Maya input spaces and apply to selected rows only — never alters matcher rules or config
- Selecting a row syncs the Maya selection to the corresponding file node(s); utility button sets
ignoreColorSpaceFileRuleson all file nodes
- Select Nodes: Batch select by type (material/file/bump/layeredTexture/CC), excluding default materials
- Rename Shading Engine: Batch rename SG to match material names
- Create File From P2D: create a
filenode from the selectedplace2dTexture, copying the 2D transform attributes and UV/filter connections
- Global structured log viewer with level, source, and text filters
- Config Validation controls for checking all JSON config spelling against actual Maya node types (materials /
bumpNormal.json/colorCorrection.json) - Creates temporary nodes to check
node_typeand every mapped attribute (incl. prerequisites, invert and displacement), then cleans up - Renderers without an installed plugin are auto-loaded when possible, otherwise skipped entirely (never misreported as spelling errors)
Source material → [Source JSON config] → Universal format → [Target JSON config] → Target material
- Config-driven: All renderer mappings defined in JSON files, zero hardcoded attribute names in Python code
- Config-driven CC types: Color-correction node detection reads the node types defined in
config/colorCorrection.json; adding a configured CC type requires no Python type-list update - Easy extension: Adding new renderer support = add JSON file in
config/material/, no code changes needed - Modular converters: 4 independent modules handle attribute transfer, bump/normal, color correction, and displacement
- Unified imports: PySide version detection centralized in
ui/__init__.py - Co-existence-safe startup: the project root is put first on
sys.path, and same-named top-level packages (core/ui/main) owned by another flat-layout tool are released — including their cached submodules — so the last launched tool wins without breaking the other's open window - Logging: Unified structured logger (ERROR/WARN/SKIP/INFO/DEBUG/OK) with ring buffer; the visible Log tab mirrors the buffer via the single-consumer
drain()API, removing rows evicted from the buffer. Log rows that carry nodes can right-clickSelect Node(s)to select them directly. Logger and UI model both retain at mostDEFAULT_MAX_RECORDS(20,000) records
materialConvert/
├── config/ # JSON configuration files
│ ├── material/ # Renderer material attribute mappings
│ │ ├── common.json # Universal PBR parameters and color-weight relationships
│ │ ├── aiStandardSurface.json
│ │ ├── aiOpenPBRSurface.json
│ │ ├── RedshiftMaterial.json
│ │ ├── RedshiftOpenPBRMaterial.json
│ │ ├── RedshiftStandardMaterial.json
│ │ └── VRayMtl.json
│ ├── bumpNormal.json # Bump/normal node mappings
│ ├── colorCorrection.json # Color correction node mappings
│ ├── colorSpace.json # Color space auto-match rules
│ ├── texture_channels.json # Batch Builder filename-to-channel rules
│ └── builder_naming.json # Material Builder naming conventions
├── core/ # Core engine
│ ├── converter.py # MaterialConverter dispatcher
│ ├── results.py # ConversionResult / BuildResult result models
│ ├── converters/ # Business conversion modules
│ │ ├── attribute.py # Attribute collection & transfer
│ │ ├── bump.py # Bump/normal conversion
│ │ ├── cc.py # Color correction conversion
│ │ └── displacement.py # Displacement conversion
│ ├── config_loader.py # JSON config parser
│ ├── filename_match.py # Shared filename -> texture-channel matching rules
│ ├── config_schema.py # Offline JSON structure validation (pure Python)
│ ├── module_reload.py # Path-based purge of this project's modules on reload
│ ├── node_utils.py # Maya node utility functions
│ ├── prerequisites.py # Renderer prerequisite handling
│ ├── logger.py # Unified logging module
│ ├── builder_context.py # Material Builder shared state
│ ├── texture_scanner.py # Directory scanning / filename-to-channel parsing
│ ├── batch_builder.py # Batch build orchestration
│ ├── material_builder.py # Material Builder core logic
│ ├── colorspace.py # Color-space match core (name/channel drivers + resolver + matcher)
│ └── config_validator.py # JSON config validation (Log tab)
├── ui/ # User interface
│ ├── main_window.py # Main window (QMainWindow + QTabWidget)
│ ├── feedback.py # Best-effort operation feedback decorator
│ ├── operation_state.py # Re-entrancy guard for UI operations (busy state)
│ ├── log_panel.py # Embedded global log viewer (polling, filters, QTableView)
│ ├── styles.py # QSS dark theme
│ ├── widgets.py # Shared target-combo population helper
│ └── tabs/ # Six functional tabs
│ ├── converter_tab.py # Material conversion
│ ├── builder_tab.py # Material Builder
│ ├── batch_builder_tab.py # Batch Builder
│ ├── colorspace_tab.py # Colorspace (file-node color-space management)
│ ├── node_tools_tab.py # Node Tools
│ └── log_tab.py # Log (global log viewer + config validation)
├── docs/ # Documentation
│ ├── AGENTS.md # AI Agent development guide
│ ├── CONVERSION_SPEC.md # Full conversion specification
│ ├── CONVERSION_SPEC_zh.md # 中文版转换规格说明
│ ├── CONFIG_GUIDE.md # Renderer JSON config authoring guide
│ ├── CONFIG_GUIDE_zh.md # 渲染器 JSON 配置编写指南(中文版)
│ ├── images/ # README preview screenshots (one per tab)
│ └── README_zh.md # 中文版 README
├── scripts/ # Repository maintenance scripts
│ └── check_no_silent_pass.py # CI guard: no silent except/print in core/ui
├── tests/ # Test suites
│ ├── conftest.py # Stubs maya.cmds for the pure suite
│ ├── support.py # Shared mayapy harness (standalone + plugins)
│ ├── mayapy/ # Maya integration tests (local, not in CI)
│ └── README.md # Test layout and coverage table
├── pytest.ini # pytest config (ignores tests/mayapy)
├── requirements-dev.txt # Dev/CI-only deps (pytest, PySide6)
├── .github/
│ └── workflows/test.yml # CI: pytest + guard script (Python 3.11 + PySide6)
├── main.py # Entry script
├── copy_launch.bat # Double-click to copy launch command
├── LICENSE
├── CHANGELOG.md # Changelog
├── CHANGELOG_zh.md # 中文版更新日志
├── README.md # This file (English)
└── .gitignore # Ignored caches / local tooling dirs
- CONVERSION_SPEC.md — Full conversion specification
- CONVERSION_SPEC_zh.md — 中文版转换规格说明
- CONFIG_GUIDE.md — Renderer JSON config authoring guide
- CONFIG_GUIDE_zh.md — 渲染器 JSON 配置编写指南(中文版)
- Pure-Python tests (no Maya required):
python -m pytest tests/ -v - Maya integration tests (local, requires Maya):
mayapy -m unittest discover -s tests/mayapy -t tests -v(seetests/README.md) - CI (
.github/workflows/test.yml) runs only the pure pytest suite plusscripts/check_no_silent_pass.pyon Python 3.11 + PySide6; the mayapy suite is not part of CI
MIT





