refactor(tools): group the sixty scripts by what they talk to
One directory with sixty scripts stopped being navigable, and the grouping is also the sandbox boundary:
Tools/unreal/ commandlets and the two map builders, with ue_common.py and ue_placement.py as the
shared imports (they need the engine)
Tools/blender/ builders, exporters, the FBX preset, the MPFB character pipeline, blender_screenshots
(they need the Blender Flatpak)
Tools/review/ the visual loop: render_screenshots.sh, judge_screenshot.py, hero_compare.py,
build_comparison_md.py, e2e_asset_check.py, hero_iterate.sh, visual_loop.py,
visual_targets.json
Tools/gen/ image and texture generation: the ComfyUI driver, deterministic texture writers,
reference fetching
Tools/input/ OS-level input injection
root _common.py, check-lfs.sh, addon_compat_probe.py, sibling_asset_audit.py, tests/
Every moved script now resolves the repository root as ../.. and puts the tools root plus its siblings on
sys.path, so 'import ue_common' still works from unreal/. The test suite bootstrap does the same for the five
folders. render_screenshots.sh takes the folder as a third argument to run_cmdlet, and the e2e driver,
hero_iterate.sh and the comparison generator were repointed.
51 README rows now link to their new location, the README gains a Layout section naming each folder's
sandbox, and the architecture notes and Blender README follow the moves.
Verified: 108 tests pass, prettier is clean, judge_screenshot.py and hero_compare.py run from their new
homes, and the e2e run was aborted mid-way (it had been rebuilding an empty DemoMap for 15 minutes) and is
being rerun against the new layout.
This commit is contained in:
1 parent
f681d83736
commit
4d27109668
168 files changed
+799
-407
No files matched your search
@@ -0,0 +1,148 @@
|
||||
# Rebuild the vehicle's materials from the Blender colours (0.3.0).
|
||||
#
|
||||
# blender -b "Art/blender/vehicle/hot hatch.blend" \
|
||||
# --python Tools/blender/vehicle_from_template.py -- --out /tmp/gigline/hatch
|
||||
# <engine>/Engine/Binaries/Linux/UnrealEditor-Cmd <uproject> -run=pythonscript \
|
||||
# -script=<repo>/Tools/unreal/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/blender/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 make_translucent_glass(material):
|
||||
"""Turn a material into dark, slightly transparent glass.
|
||||
|
||||
The template's `windows` material is metallic 1.0 with roughness 0.0, which reads as a
|
||||
black void in game - indistinguishable from an empty cabin. Glass needs translucency and
|
||||
a little roughness to catch a highlight.
|
||||
"""
|
||||
lib = unreal.MaterialEditingLibrary
|
||||
material.set_editor_property("blend_mode", unreal.BlendMode.BLEND_TRANSLUCENT)
|
||||
material.set_editor_property("two_sided", False)
|
||||
for node in list(lib.get_material_expressions(material)):
|
||||
lib.delete_material_expression(material, node)
|
||||
base = lib.create_material_expression(material, unreal.MaterialExpressionConstant3Vector, -400, 0)
|
||||
base.set_editor_property("constant", unreal.LinearColor(0.02, 0.03, 0.045, 1.0))
|
||||
lib.connect_material_property(base, "", unreal.MaterialProperty.MP_BASE_COLOR)
|
||||
opacity = lib.create_material_expression(material, unreal.MaterialExpressionConstant, -400, 140)
|
||||
opacity.set_editor_property("r", 0.85)
|
||||
lib.connect_material_property(opacity, "", unreal.MaterialProperty.MP_OPACITY)
|
||||
rough = lib.create_material_expression(material, unreal.MaterialExpressionConstant, -400, 220)
|
||||
rough.set_editor_property("r", 0.08)
|
||||
lib.connect_material_property(rough, "", unreal.MaterialProperty.MP_ROUGHNESS)
|
||||
specular = lib.create_material_expression(material, unreal.MaterialExpressionConstant, -400, 300)
|
||||
specular.set_editor_property("r", 1.0)
|
||||
lib.connect_material_property(specular, "", unreal.MaterialProperty.MP_SPECULAR)
|
||||
lib.recompile_material(material)
|
||||
return material
|
||||
|
||||
|
||||
def replace_material(name, spec, create_if_missing=False):
|
||||
resolved = existing_material_name(name)
|
||||
if resolved is not None:
|
||||
name = resolved
|
||||
elif not create_if_missing:
|
||||
log(f"{name}: no imported asset matches - skipped")
|
||||
return None
|
||||
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():
|
||||
material = replace_material(name, spec)
|
||||
if material:
|
||||
made += 1
|
||||
if material and name.lower() == "windows":
|
||||
make_translucent_glass(material)
|
||||
log("windows: tinted glass (opacity 0.85, roughness 0.08) - 0.45 read as no glass at all")
|
||||
# The body arrives without a usable material (its FBX slot resolves to
|
||||
# WorldGridMaterial), so its paint is created under a name of our own and assigned to
|
||||
# the staged panel at runtime.
|
||||
paint = materials.get("car paint")
|
||||
if paint:
|
||||
spec = dict(paint)
|
||||
spec["base_color"] = [1.0, 0.05, 0.03]
|
||||
replace_material("M_Vehicle_HatchPaint", spec, create_if_missing=True)
|
||||
log("M_Vehicle_HatchPaint: body paint, assigned at runtime by the map builder")
|
||||
|
||||
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")
|
||||
Reference in new issue
Block a user