# Blender Tools

Small tools for moving textured assets between Blender and Substance Painter.

Contributor documentation starts at [AGENTS.md](AGENTS.md) and the
[documentation index](docs/README.md). The full guides are:

- [Building Blender extensions](docs/blender-extension-guide.md)
- [Blender Python API notes](docs/blender-api-notes.md)
- [Testing and releasing extensions](docs/testing-and-releasing.md)

```text
AGENTS.md                     Contributor rules and documentation map
blender/
  material_id_baker/        Blender 5.x extension source
  scripts/                  Build, install/update, and test helpers
  tests/                    Headless Blender smoke test
docs/                         Extension authoring and API references
tools/
  combine_substance_textures.py
  requirements.txt
  tests/
dist/                       Built extension packages (generated)
```

Build every Blender extension in the repository at once with:

```bash
./build-all.sh
```

Set `BLENDER_BIN=/path/to/blender` when Blender is not available as `blender`.

Build, install, and enable every extension headlessly for the current Blender user with:

```bash
./install-all.sh
```

The installer targets Blender's `user_default` extension repository. Override it with `BLENDER_EXTENSION_REPO=repository_id` when needed. Close running Blender instances before installing so they do not retain an older loaded copy.

On Windows, use the equivalent batch file from Command Prompt:

```bat
install-all.bat
```

If Blender is not on `PATH`, configure it without embedding extra quotes:

```bat
set "BLENDER_BIN=C:\Program Files\Blender Foundation\Blender 5.2\blender.exe"
set "BLENDER_EXTENSION_REPO=user_default"
install-all.bat
```

## Material ID Baker extension

The Blender extension bakes the active mesh's material-slot assignments to a flat-color texture using its active UV map. It works on a temporary mesh copy, so the source mesh and materials are not modified.

Features include 256px–8K output, a configurable island margin defaulting to 1 px, optional evaluated modifiers, and three palette modes: deterministic distinct colors, material viewport colors, and exact slot-index encoding. It can write a PNG plus a JSON legend used by the standalone merger. The optional **Create Export Copy** setting leaves behind a selected duplicate with one `baked ids` material connected to the baked image. Its dependent **Quick Export GLB** option immediately writes that one-material copy to a chosen `.glb` path for Substance Painter.

### Install or update

Close Blender, set `BLENDER_BIN` if Blender 5.2 is not available as `blender`, and run:

```bash
BLENDER_BIN=/path/to/blender-5.2 ./blender/scripts/install.sh
```

This builds `dist/material_id_baker-<version>.zip` and installs or reinstalls the stable `material_id_baker` extension ID in Blender's `user_default` repository. For a release, bump `version` in `blender/material_id_baker/blender_manifest.toml` first.

You can instead run `./blender/scripts/build.sh` and install the resulting ZIP through **Edit > Preferences > Extensions > Install from Disk**.

### Bake an ID map

1. Select one mesh in Object Mode.
2. Ensure it has an active UV map and the intended per-face material assignments.
3. Open the 3D Viewport sidebar and choose the **Material ID** tab.
4. Select the resolution, margin, and color mode.
5. Optionally enable **Create Export Copy** to create a one-material duplicate for export.
6. Optionally enable **Quick Export GLB** and choose a `.glb` path for immediate Substance Painter export.
7. Enable **Save PNG** and **Save JSON Legend**, then click **Bake Material ID**.

Overlapping UV islands with different materials are ambiguous. Use a non-overlapping UV layout for recovery/compositing.

## Combine separate Substance Painter texture sets

Use [tools/combine_substance_textures.py](tools/combine_substance_textures.py) when an asset was imported into Substance with one texture set per Blender material and now needs one texture per channel.

Ordinary alpha compositing is unreliable here because Substance exports may have opaque backgrounds and padding. The merger uses the Material ID PNG and JSON legend as exact masks, so padding in each separate texture set cannot overwrite another material.

Install its one dependency in your preferred Python environment:

```bash
python3 -m venv .venv
.venv/bin/pip install -r tools/requirements.txt
```

Then run:

```bash
.venv/bin/python tools/combine_substance_textures.py \
  --id-map recovery/Material_ID.png \
  --legend recovery/Material_ID.json \
  --input-dir substance-export \
  --output-dir combined
```

Suppose the legend contains materials named `Body` and `Trim`, and Substance exported:

```text
Robot_Body_BaseColor.png
Robot_Trim_BaseColor.png
Robot_Body_Normal.png
Robot_Trim_Normal.png
```

The tool discovers the material token and writes:

```text
combined/Robot_BaseColor.png
combined/Robot_Normal.png
```

The ID map and exported textures must have the same dimensions. The merger first uses exact ID bytes, with an automatic one-byte fallback only when a material's exact color is absent (this handles Blender's float-to-PNG rounding). `--tolerance 0` forces strict matching; larger explicit tolerances are available for external ID maps, but the tool rejects values that make material masks overlap.

If a Substance texture-set token differs from its Blender material name, add aliases:

```bash
.venv/bin/python tools/combine_substance_textures.py \
  --id-map recovery/Material_ID.png \
  --legend recovery/Material_ID.json \
  --input-dir substance-export \
  --output-dir combined \
  --material-alias 'Body Material=Body' \
  --material-alias 'Trim Material=Trim'
```

Useful options:

- `--dry-run` shows discovered channel groups without writing images.
- `--recursive` searches subdirectories and can group layouts such as `Body/BaseColor.png` plus `Trim/BaseColor.png`; shared directory prefixes are preserved.
- `--overwrite` replaces existing combined textures.
- `--material-alias 'LEGEND NAME=FILENAME NAME'` may be repeated.

The merger supports common Pillow-readable image types including PNG, TGA, TIFF, and JPEG. Prefer lossless 8-bit PNG/TGA exports for data maps; JPEG compression can alter texture values.

## Tests

Run the extension bake test with Blender:

```bash
./blender/scripts/test.sh
```

Run the standalone merger tests after installing Pillow:

```bash
./tools/test.sh
```
