Files
gig-line/Tools/ue_vehicle_materials.py
T
zwitschi 046e2c8b9e docs(review): the Blender-vs-in-game comparison lives in Screenshots/README.md
Merged COMPARISON.md into the review README as a generated section between
<!-- blender-comparison:begin/end --> markers, so there is one place to look and one place
to sign off. Tools/build_comparison_md.py rewrites only that block - the hand-written
sections around it are untouched - and formats the file with prettier afterwards, because a
generated table is never byte-identical to prettier's output and the repo gates on
--check.

Run it after every capture or Blender render; it is the routine that keeps the page current
with new images rather than a one-off export.
2026-11-09 16:10:47 +01:00

106 lines
4.3 KiB
Python

# Rebuild the vehicle's materials from the Blender colours (0.3.0).
#
# blender -b "Art/blender/vehicle/hot hatch.blend" \
# --python Tools/vehicle_from_template.py -- --out /tmp/gigline/hatch
# <engine>/Engine/Binaries/Linux/UnrealEditor-Cmd <uproject> -run=pythonscript \
# -script=<repo>/Tools/ue_vehicle_materials.py -nullrhi -unattended -nopause -stdout
#
# Why this exists: the template's car paint is a **legacy node graph**, so its colour never
# reached the exported FBX and the car arrived in Unreal near-black while Blender showed it
# red. `Tools/vehicle_from_template.py` now reads those graphs and records the colours; this
# replaces the imported material assets in place with plain constant-colour materials, so
# the mesh slots pick them up by path without anything being re-imported.
#
# Result file: /tmp/gigline/vehicle-materials.txt
import json
import os
import unreal
DEST = "/Game/Hollywood/Vehicle"
_REPO = os.path.abspath(os.path.join(unreal.Paths.project_dir(), "..", ".."))
REPORT = os.path.join(_REPO, "..", "..", "..", "tmp", "gigline", "hatch", "report.json")
REPORT = "/tmp/gigline/hatch/report.json"
RESULT = "/tmp/gigline/vehicle-materials.txt"
lines = []
def log(msg):
lines.append(str(msg))
unreal.log(str(msg))
def existing_material_name(name):
"""Find the asset the importer actually created for a Blender material name.
Blender names may contain spaces ("car paint"), which are not valid asset names, so the
importer sanitises them. Guessing the sanitised form is fragile; this searches the
folder instead and reports when nothing matches.
"""
registry = unreal.AssetRegistryHelpers.get_asset_registry()
wanted = name.lower().replace(" ", "").replace("_", "")
for asset in registry.get_assets_by_path(DEST, recursive=False):
candidate = str(asset.asset_name).lower().replace(" ", "").replace("_", "")
if candidate == wanted:
return str(asset.asset_name)
return None
def replace_material(name, spec):
resolved = existing_material_name(name)
if resolved is None:
log(f"{name}: no imported asset matches - skipped")
return None
name = resolved
full = f"{DEST}/{name}"
if unreal.EditorAssetLibrary.does_asset_exist(full):
unreal.EditorAssetLibrary.delete_asset(full)
tools = unreal.AssetToolsHelpers.get_asset_tools()
material = tools.create_asset(name, DEST, unreal.Material, unreal.MaterialFactoryNew())
if material is None:
log(f"{name}: could not create")
return None
lib = unreal.MaterialEditingLibrary
colour = spec["base_color"]
base = lib.create_material_expression(material, unreal.MaterialExpressionConstant3Vector, -400, 0)
base.set_editor_property("constant", unreal.LinearColor(colour[0], colour[1], colour[2], 1.0))
lib.connect_material_property(base, "", unreal.MaterialProperty.MP_BASE_COLOR)
rough = lib.create_material_expression(material, unreal.MaterialExpressionConstant, -400, 180)
rough.set_editor_property("r", max(0.05, float(spec.get("roughness", 0.5))))
lib.connect_material_property(rough, "", unreal.MaterialProperty.MP_ROUGHNESS)
metallic = lib.create_material_expression(material, unreal.MaterialExpressionConstant, -400, 260)
metallic.set_editor_property("r", float(spec.get("metallic", 0.0)))
lib.connect_material_property(metallic, "", unreal.MaterialProperty.MP_METALLIC)
lib.recompile_material(material)
log(f"{name}: base {tuple(round(c, 3) for c in colour)} "
f"roughness {spec.get('roughness')} metallic {spec.get('metallic')} "
f"({spec.get('source')})")
return material
def main():
with open(REPORT) as handle:
report = json.load(handle)
materials = report.get("materials", {})
log(f"{len(materials)} materials in the Blender report")
made = 0
for name, spec in materials.items():
if replace_material(name, spec):
made += 1
unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True)
lines.append(f"VERDICT: {'PASS' if made == len(materials) else 'FAIL'}")
log(f"{made}/{len(materials)} materials replaced")
try:
main()
except Exception as exc: # noqa: BLE001
import traceback
log(f"FAILED: {type(exc).__name__}: {exc}")
log(traceback.format_exc())
lines.append("VERDICT: FAIL")
with open(RESULT, "w") as f:
f.write("\n".join(lines) + "\n")