The bpy.ops.export_scene.fbx argument block was duplicated in six scripts (hero, block frontage, north buildings, site, scale test, MPFB character) and all six carried mesh_smooth_type="OFF", which drops smoothing groups and made every Unreal import warn and every round part faceted. Tools/fbx_export_preset.py now owns the presets: - preset_kwargs(preset, selection, **overrides) is bpy-free and unit-tested; - export_static()/export_character() are thin wrappers that import bpy lazily; - ue_metric_v1 and ue_character_v1 keep the recorded scale contract (FBX_SCALE_NONE, apply_unit_scale, -Y forward / Z up) and use FACE smoothing. Callers switched: hero_facade_build, export_block_frontage, export_north_buildings, scale_test_export, mpfb_character_export, Art/blender/site/export_site_fbx. Verified by re-exporting every asset through the preset and re-importing all of them: 0 smoothing warnings on site, frontage and north buildings; packaging and -qaselftest all PASS (walk, transect 0 holes, wall probe 0/35, facade probe 0/9). 49 tool tests pass (9 new for the preset contract).
66 lines
2.5 KiB
Python
66 lines
2.5 KiB
Python
# Export the two in-scope north-side background buildings from the OSM blockout.
|
|
#
|
|
# blender -b --factory-startup Art/blender/blockout/blockout-2026-11-06.blend \
|
|
# --python Tools/export_north_buildings.py -- --out /tmp/gigline/north
|
|
#
|
|
# The blockout is already in the shared project frame (metres, X east / Y north /
|
|
# Z up, origin Hollywood x Highland), so exporting each object at its own world
|
|
# position preserves the offset from the hero facade. The site export preset
|
|
# (`ue_metric_v1`) is reused verbatim so the metre->centimetre mapping matches.
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
|
|
import bpy
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import fbx_export_preset as fbx_preset # noqa: E402
|
|
|
|
|
|
BUILDINGS = ("TCL Chinese Theatre", "Madame Tussaud's")
|
|
|
|
|
|
def main():
|
|
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--out", default="/tmp/gigline/north")
|
|
args = ap.parse_args(argv)
|
|
os.makedirs(args.out, exist_ok=True)
|
|
|
|
print(f"[north] scene objects: {len(bpy.data.objects)}")
|
|
for name in BUILDINGS:
|
|
obj = bpy.data.objects.get(name)
|
|
if obj is None or obj.type != "MESH":
|
|
print(f"[north] MISSING {name}")
|
|
continue
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
obj.select_set(True)
|
|
bpy.context.view_layer.objects.active = obj
|
|
mesh = obj.data
|
|
if not mesh.materials:
|
|
mat = bpy.data.materials.new(f"M_{name.replace(' ', '_')}")
|
|
mat.diffuse_color = (0.75, 0.72, 0.68, 1.0)
|
|
mat.use_nodes = False
|
|
mesh.materials.append(mat)
|
|
|
|
# No copy and no Y mirror: the FBX preset already lands Blender +Y
|
|
# (north) on Unreal -Y, and UE north is -Y here (north sidewalk
|
|
# Y -1912..-1053). The old mirror put both buildings on the south side.
|
|
new_obj = obj
|
|
new_mesh = mesh
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
new_obj.select_set(True)
|
|
bpy.context.view_layer.objects.active = new_obj
|
|
|
|
path = os.path.join(args.out, name.replace(" ", "_").replace("'", "") + ".fbx")
|
|
fbx_preset.export_static(path, selection=True)
|
|
world = new_obj.matrix_world
|
|
zs = [(world @ v.co).z for v in new_mesh.vertices]
|
|
print(f"[north] {name:22} verts {len(new_mesh.vertices):5} z {min(zs):+.2f}..{max(zs):+.2f} m "
|
|
f"-> {path}")
|
|
bpy.data.objects.remove(new_obj, do_unlink=True)
|
|
|
|
print(f"[north] done")
|
|
|
|
|
|
main() |