Merged PR #28: v0.1.2 — scale calibration, façade detail, export preset Co-authored-by: Zwitschi <zwitschi82@gmail.com>
84 lines
3.3 KiB
Python
84 lines
3.3 KiB
Python
"""Export the site meshes from the assembled site .blend, one FBX per object.
|
|
|
|
blender -b --factory-startup Art/blender/site/site-2026-11-06.blend \
|
|
--python Art/blender/site/export_site_fbx.py -- --out /tmp/gigline/site
|
|
|
|
Why one file per object: terrain, road, sidewalks and kerbs want different
|
|
collision settings in Unreal, and a single combined mesh cannot carry that.
|
|
|
|
Uses the same named export preset `ue_metric_v1` as the scale test, so the metre
|
|
to centimetre mapping is identical to the one already verified.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
|
|
import bpy
|
|
sys.path.insert(0, os.path.join(os.path.join(os.path.dirname(__file__), "..", "..", ".."), "Tools"))
|
|
import fbx_export_preset as fbx_preset # noqa: E402
|
|
|
|
|
|
MESHES = ("Terrain_DEM", "Road_Hollywood", "Sidewalk_South", "Sidewalk_North",
|
|
"Kerb_South", "Kerb_North", "Crossing_Markings")
|
|
|
|
|
|
def main():
|
|
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--out", default="/tmp/gigline/site")
|
|
args = ap.parse_args(argv)
|
|
os.makedirs(args.out, exist_ok=True)
|
|
|
|
print(f"[site] scene objects: {len(bpy.data.objects)}")
|
|
found = []
|
|
for name in MESHES:
|
|
obj = bpy.data.objects.get(name)
|
|
if obj is None or obj.type != "MESH":
|
|
print(f"[site] MISSING {name}")
|
|
continue
|
|
found.append(name)
|
|
|
|
for name in found:
|
|
# export only the object under test: select it, export, deselect
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
obj = bpy.data.objects[name]
|
|
obj.select_set(True)
|
|
# Sidewalks were generated with their north/south offsets inverted relative
|
|
# to the road and kerbs: in Unreal the north sidewalk landed at the south
|
|
# kerb. Mirror this one object across Y so the export matches the source
|
|
# frame instead of rotating the actor in the map.
|
|
mirrored = False # the south sidewalk is correct in the .blend; see street_mesh
|
|
if mirrored:
|
|
obj.scale.y *= -1.0
|
|
bpy.context.view_layer.objects.active = obj
|
|
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
|
# Object mode has no mesh context; flip winding through the mesh data.
|
|
me = obj.data
|
|
for poly in me.polygons:
|
|
verts = list(poly.vertices)
|
|
verts.reverse()
|
|
poly.vertices = verts
|
|
me.update()
|
|
_ = bpy.context.view_layer
|
|
bpy.context.view_layer.objects.active = obj
|
|
path = os.path.join(args.out, name + ".fbx")
|
|
fbx_preset.export_static(path, selection=True)
|
|
if mirrored:
|
|
# The flip is an export correction, not a change to the source: undo it.
|
|
obj.scale.y *= -1.0
|
|
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
|
for poly in obj.data.polygons:
|
|
verts = list(poly.vertices)
|
|
verts.reverse()
|
|
poly.vertices = verts
|
|
obj.data.update()
|
|
zs = [(obj.matrix_world @ v.co).z for v in obj.data.vertices]
|
|
print(f"[site] {name:18} verts {len(obj.data.vertices):6} z {min(zs):+.2f}..{max(zs):+.2f} m "
|
|
f"-> {path}")
|
|
|
|
print(f"[site] exported {len(found)} of {len(MESHES)} meshes")
|
|
|
|
|
|
main()
|