frontage: south block frontage, plus the placement bug it exposed
Adds the five remaining verified south-side buildings to the demo block (6922, 6902, 6838 El Capitan Theatre, 6822 Budget, 6806-6820, 6800 Lee Drug) exported in place from the OSM blockout and staged by the demo builder. Adding neighbours exposed a pre-existing placement bug that could not be seen while the hero stood alone: - UE south is +Y (south sidewalk Y +718..+1447), but the hero facade sat at Y -1450 on the NORTH side and at X -12024..-9420, which is 26 m east of the 6840 footprint (the frontage gap is X -14591..-12082). - The two north-side buildings were on the SOUTH side, because their exporter mirrored Y. Fixes: hero moved to X -13336 / Y +1307, inside its frontage gap; frontage and north buildings export without a Y mirror (the FBX preset already lands Blender -Y south on UE +Y south); player start and demo cameras moved to the north sidewalk looking south; sun raised to pitch -62 so both frontages read instead of the north side going to silhouette. Screenshots re-rendered; the four demo views are marked needs-review because the whole block changed.
This commit is contained in:
1 parent
01ccc61842
commit
8ca403fd08
41 files changed
+330
-82
No files matched your search
@@ -0,0 +1,98 @@
|
||||
# Export the south-side block frontage from the OSM blockout.
|
||||
#
|
||||
# blender -b --factory-startup Art/blender/blockout/blockout-2026-11-06.blend \
|
||||
# --python Tools/export_block_frontage.py -- --out /tmp/gigline/frontage
|
||||
#
|
||||
# The building set is the one verified in
|
||||
# `Geo/verification/2026-11-06-el-capitan-block.md` ("South frontage, west to
|
||||
# east"), minus 6840 which is modelled by hand as SM_HeroFacade. Everything is
|
||||
# already in the shared project frame (metres, X east / Y north / Z up, origin
|
||||
# Hollywood x Highland), so each object exports at its own world position with no
|
||||
# transform: these are south-side buildings and need no Y mirror.
|
||||
#
|
||||
# The block runs from Highland (X 0) to Orange Drive (X -279.6 m). Objects that
|
||||
# are OSM `element` placeholders are named by their verified address instead, so
|
||||
# the Unreal asset names say what the building is.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import bpy
|
||||
|
||||
# blockout object name -> (asset name, note). Addresses from the verification doc.
|
||||
BUILDINGS = {
|
||||
# Way 424024151, 6922, commercial 12 levels - the west end of the frontage.
|
||||
"element.020": ("SM_Hollywood_6922", "6922, west end of the frontage"),
|
||||
# Way 206486872, 6902, retail, height 13.8.
|
||||
"element": ("SM_Hollywood_6902", "6902, retail, H 13.8"),
|
||||
# Way 424024148, 6838, El Capitan Theatre - distinct from 6840.
|
||||
"El Capitan Theatre": ("SM_ElCapitanTheatre", "6838, cinema, 6 levels"),
|
||||
# Way 424024144, 6822, Budget car rental.
|
||||
"Budget": ("SM_Budget_6822", "6822, car rental, H 5.5"),
|
||||
# Way 424024150, 6806-6820, retail, height 12.1.
|
||||
"element.019": ("SM_Hollywood_6806", "6806-6820, retail, H 12.1"),
|
||||
# Way 424024149, 6800, Lee Drug Building.
|
||||
"Lee Drug Building": ("SM_LeeDrug_6800", "6800, Lee Drug Building, H 12.1"),
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--out", default="/tmp/gigline/frontage")
|
||||
args = ap.parse_args(argv)
|
||||
os.makedirs(args.out, exist_ok=True)
|
||||
|
||||
print(f"[frontage] scene objects: {len(bpy.data.objects)}")
|
||||
exported = 0
|
||||
for obj_name, (asset_name, note) in BUILDINGS.items():
|
||||
obj = bpy.data.objects.get(obj_name)
|
||||
if obj is None or obj.type != "MESH":
|
||||
print(f"[frontage] MISSING blockout object {obj_name!r}")
|
||||
continue
|
||||
|
||||
mesh = obj.data
|
||||
if not mesh.materials:
|
||||
mat = bpy.data.materials.new(f"M_{asset_name}")
|
||||
mat.diffuse_color = (0.72, 0.70, 0.66, 1.0)
|
||||
mat.use_nodes = False
|
||||
mesh.materials.append(mat)
|
||||
|
||||
# No Y mirror: the FBX preset lands Blender -Y (south) on Unreal +Y,
|
||||
# which is the south side of the street (south sidewalk Y +718..+1447).
|
||||
new_mesh = obj.data
|
||||
new_obj = obj
|
||||
|
||||
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, asset_name + ".fbx")
|
||||
bpy.ops.export_scene.fbx(
|
||||
filepath=path,
|
||||
use_selection=True,
|
||||
object_types={"MESH"},
|
||||
apply_unit_scale=True,
|
||||
apply_scale_options="FBX_SCALE_NONE",
|
||||
axis_forward="-Y",
|
||||
axis_up="Z",
|
||||
use_space_transform=True,
|
||||
use_mesh_modifiers=True,
|
||||
mesh_smooth_type="OFF",
|
||||
path_mode="AUTO",
|
||||
bake_space_transform=False,
|
||||
)
|
||||
world = new_obj.matrix_world
|
||||
zs = [(world @ v.co).z for v in new_mesh.vertices]
|
||||
ys = [(world @ v.co).y for v in new_mesh.vertices]
|
||||
xs = [(world @ v.co).x for v in new_mesh.vertices]
|
||||
print(f"[frontage] {asset_name:22} {note:34} "
|
||||
f"X {min(xs):7.1f}..{max(xs):6.1f} Y {min(ys):6.1f}..{max(ys):6.1f} "
|
||||
f"H {max(zs):5.1f} verts {len(new_mesh.vertices):5} -> {path}")
|
||||
exported += 1
|
||||
|
||||
print(f"[frontage] exported {exported} of {len(BUILDINGS)} frontage buildings")
|
||||
|
||||
|
||||
main()
|
||||
@@ -40,27 +40,11 @@ def main():
|
||||
mat.use_nodes = False
|
||||
mesh.materials.append(mat)
|
||||
|
||||
# The blockout sits with the hero (6840, south side) at negative Y. The two
|
||||
# in-scope north buildings must land ACROSS Hollywood Blvd at positive Y in
|
||||
# Unreal. Mirror the exported geometry across the road centreline (Y=0) and
|
||||
# reverse winding; the FBX exporter does not do this for us.
|
||||
new_mesh = mesh.copy()
|
||||
new_obj = obj.copy()
|
||||
new_obj.data = new_mesh
|
||||
new_obj.matrix_world = obj.matrix_world.copy()
|
||||
bpy.context.collection.objects.link(new_obj)
|
||||
import mathutils as mu
|
||||
world = new_obj.matrix_world
|
||||
inv = world.inverted()
|
||||
for v in new_mesh.vertices:
|
||||
wp = world @ v.co
|
||||
wp.y = -wp.y
|
||||
v.co = inv @ wp
|
||||
for poly in new_mesh.polygons:
|
||||
verts = list(poly.vertices)
|
||||
verts.reverse()
|
||||
poly.vertices = verts
|
||||
new_mesh.update()
|
||||
# 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
|
||||
@@ -80,6 +64,7 @@ def main():
|
||||
path_mode="AUTO",
|
||||
bake_space_transform=False,
|
||||
)
|
||||
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}")
|
||||
|
||||
@@ -146,7 +146,7 @@ def build():
|
||||
log(f"could not create {MAP}")
|
||||
return
|
||||
|
||||
removed = clear(("Demo_", "PlayerStart", "Hero_Facade"))
|
||||
removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_"))
|
||||
log(f"cleared {removed} previous demo actors")
|
||||
|
||||
# --- lighting: fixed daytime ------------------------------------------
|
||||
@@ -162,7 +162,7 @@ def build():
|
||||
# 3. Ambience. A height fog gives the street depth and hides the terrain
|
||||
# edge that otherwise reads as a black band at the horizon.
|
||||
sun = spawn(unreal.DirectionalLight, unreal.Vector(0, 0, 2000), "Demo_Sun",
|
||||
unreal.Rotator(pitch=-50.0, yaw=120.0, roll=0.0))
|
||||
unreal.Rotator(pitch=-62.0, yaw=120.0, roll=0.0))
|
||||
if sun:
|
||||
comp = sun.get_component_by_class(unreal.DirectionalLightComponent)
|
||||
if comp:
|
||||
@@ -181,7 +181,7 @@ def build():
|
||||
comp.set_mobility(unreal.ComponentMobility.MOVABLE)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
log(f"sun mobility not set: {exc!r}")
|
||||
log("staged: Demo_Sun (pitch -50, yaw 120, intensity 4, warm)")
|
||||
log("staged: Demo_Sun (pitch -62, yaw 120, intensity 4, warm; higher sun so both frontages read)")
|
||||
|
||||
sky = spawn(unreal.SkyLight, unreal.Vector(0, 0, 2000), "Demo_SkyLight")
|
||||
if sky:
|
||||
@@ -246,9 +246,14 @@ def build():
|
||||
log("staged: Demo_PostProcess (fixed exposure, min=max brightness 1.0)")
|
||||
|
||||
# --- hero facade on the measured building line ------------------------
|
||||
# Hero placement, corrected 2026-11-07 when the neighbouring frontage was
|
||||
# added: UE south is +Y (the south sidewalk is Y +718..+1447) and the block
|
||||
# frontage sits at X -24276..-12082 with a gap at -14591..-12082 for 6840.
|
||||
# The old (-10722.5, -1443) put the facade 26 m east of its footprint and on
|
||||
# the NORTH side of the street, which only looked right while it stood alone.
|
||||
hero = unreal.EditorAssetLibrary.load_asset("/Game/Hollywood/ElCapitan/SM_HeroFacade")
|
||||
if hero:
|
||||
actor = spawn(unreal.StaticMeshActor, unreal.Vector(-10722.5, -1443.0, 0.0), "Hero_Facade")
|
||||
actor = spawn(unreal.StaticMeshActor, unreal.Vector(-13336.0, 1307.0, 0.0), "Hero_Facade")
|
||||
if actor:
|
||||
actor.static_mesh_component.set_static_mesh(hero)
|
||||
actor.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE)
|
||||
@@ -323,14 +328,47 @@ def build():
|
||||
assigned += 1
|
||||
log(f"assigned look materials to {assigned} facade/background actors")
|
||||
|
||||
# --- surrounding block frontage ---------------------------------------
|
||||
# The other five south-side buildings verified for the block
|
||||
# (Geo/verification/2026-11-06-el-capitan-block.md), exported in place from
|
||||
# the OSM blockout with a Y mirror so they sit on the south side with the
|
||||
# hero. The 6840 site itself is the hand-modelled SM_HeroFacade above.
|
||||
FRONTAGE = (
|
||||
("SM_Hollywood_6922", "6922"),
|
||||
("SM_Hollywood_6902", "6902"),
|
||||
("SM_ElCapitanTheatre", "6838 El Capitan Theatre"),
|
||||
("SM_Budget_6822", "6822 Budget"),
|
||||
("SM_Hollywood_6806", "6806-6820"),
|
||||
("SM_LeeDrug_6800", "6800 Lee Drug"),
|
||||
)
|
||||
staged_frontage = 0
|
||||
for asset_name, label in FRONTAGE:
|
||||
mesh = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Frontage/{asset_name}")
|
||||
if mesh is None:
|
||||
log(f"MISSING frontage {asset_name} — run Tools/ue_import_block_frontage.py")
|
||||
continue
|
||||
actor = spawn(unreal.StaticMeshActor, unreal.Vector(0, 0, 0), f"Frontage_{asset_name}")
|
||||
if actor:
|
||||
comp = actor.static_mesh_component
|
||||
comp.set_static_mesh(mesh)
|
||||
comp.set_mobility(unreal.ComponentMobility.MOVABLE)
|
||||
# The two theatre buildings read as stone; the retail run as stucco.
|
||||
mat_name = "M_FacadeStone" if "Theatre" in asset_name else "M_Stucco"
|
||||
if mat_name in built:
|
||||
comp.set_material(0, built[mat_name])
|
||||
staged_frontage += 1
|
||||
log(f"staged {staged_frontage} frontage buildings")
|
||||
|
||||
# --- player start: north sidewalk, facing the hero frontage -------------
|
||||
# Signs were measured from the imported meshes rather than assumed: the FBX
|
||||
# preset flips north, so in Unreal the road spans Y -1382..+1047, the NORTH
|
||||
# sidewalk is at Y -1912..-1053 and the SOUTH sidewalk at Y +718..+1447. The
|
||||
# facade stands at Y -1450. Stand on the north sidewalk and look north (-Y).
|
||||
spawn(unreal.PlayerStart, unreal.Vector(-10722.5, -3500.0, 120.0), "Demo_PlayerStart",
|
||||
unreal.Rotator(pitch=0.0, yaw=0.0, roll=0.0))
|
||||
log("staged: Demo_PlayerStart on the north sidewalk facing south")
|
||||
# North sidewalk (Y -1912..-1053), facing south (+Y) across the road at the
|
||||
# hero on the south side. Yaw 90 looks along +Y.
|
||||
spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1700.0, 120.0), "Demo_PlayerStart",
|
||||
unreal.Rotator(pitch=0.0, yaw=90.0, roll=0.0))
|
||||
log("staged: Demo_PlayerStart on the north sidewalk facing south (+Y)")
|
||||
|
||||
# A lockable LFS .umap checks out read-only, which makes save_map silently
|
||||
# return False — chmod u+w first or the staged actors never persist.
|
||||
|
||||
@@ -163,8 +163,10 @@ try:
|
||||
# from the north sidewalk, and the oblique street view from further west.
|
||||
# Hero placement in Unreal centimetres (see Geo/manifests/local-origin.md):
|
||||
# front plane on the building line, body behind it.
|
||||
hero_x = -12024.5 + 1302.0
|
||||
hero_y = -1450.0 + 7.0
|
||||
# Corrected 2026-11-07 with the block frontage: UE south is +Y (south
|
||||
# sidewalk Y +718..+1447) and 6840 occupies the frontage gap X -14591..-12082.
|
||||
hero_x = -13336.0
|
||||
hero_y = 1307.0
|
||||
hero = unreal.EditorAssetLibrary.load_asset("/Game/Hollywood/ElCapitan/SM_HeroFacade")
|
||||
# The imported mesh spans Y -7..+1250 cm in local space (front plane at -7, the
|
||||
# body behind it) and is centred on X, so the actor sits at building line + 7 cm.
|
||||
@@ -274,12 +276,12 @@ try:
|
||||
demo_shots = [
|
||||
# 14 m back so the 14.5 m elevation fits: at 2 m the camera sees only
|
||||
# the colonnade's underside.
|
||||
("demo-spawn-view", unreal.Vector(-10722.5, -3500, 165), unreal.Vector(-10722.5, -1450, 700)),
|
||||
("demo-spawn-view", unreal.Vector(-13336, -3100, 420), unreal.Vector(-13336, 1300, 620)),
|
||||
# Street level, from the north sidewalk: the old camera sat at Y -400,
|
||||
# inside the facade body (which spans Y -1457..-200), so it looked at
|
||||
# the unlit back face. Y -2600 is on the sidewalk in front of the lit
|
||||
# elevation.
|
||||
("demo-street-level", unreal.Vector(-10850, -2600, 170), unreal.Vector(-10722.5, -1450, 400)),
|
||||
("demo-street-level", unreal.Vector(-13420, -1650, 170), unreal.Vector(-13336, 1300, 400)),
|
||||
# The designated mid-block crossing. Street arc length runs from the
|
||||
# west end (s=0 at X -27966 cm), so s=130.1 m is UE X -14956, not the
|
||||
# -24291 the old camera used (that is the s=36.6 crossing). Camera sits
|
||||
@@ -288,7 +290,7 @@ try:
|
||||
("demo-crossing", unreal.Vector(-15250, -2500, 400), unreal.Vector(-14956, 400, 150)),
|
||||
# the two north-side background buildings (TCL, Tussaud's) seen from
|
||||
# the south sidewalk, across Hollywood Blvd: their frontage and mass.
|
||||
("demo-north-buildings", unreal.Vector(-22800, -1000, 400), unreal.Vector(-22800, 3000, 300)),
|
||||
("demo-north-buildings", unreal.Vector(-22800, 2600, 420), unreal.Vector(-22800, -4200, 300)),
|
||||
]
|
||||
seq = spawn(unreal.GigLineShotActor, unreal.Vector(0, 0, 300), "ShotSequencer")
|
||||
if seq:
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# Import the south-side block frontage FBX set into Unreal.
|
||||
#
|
||||
# blender ... --python Tools/export_block_frontage.py -- --out /tmp/gigline/frontage
|
||||
# <engine>/Engine/Binaries/Linux/UnrealEditor-Cmd <uproject> -run=pythonscript \
|
||||
# -script=<repo>/Tools/ue_import_block_frontage.py -nullrhi -unattended -nopause -stdout
|
||||
#
|
||||
# Each mesh already sits at its world position in the project frame (the FBX was
|
||||
# exported without moving the object), so the actor is spawned at the origin and
|
||||
# the geometry carries the offset. Destination: /Game/Hollywood/Frontage/<name>.
|
||||
#
|
||||
# Staging into the DemoMap is done by Tools/ue_build_demo_map.py, which also
|
||||
# assigns the look materials; this script only produces the assets.
|
||||
|
||||
import glob
|
||||
import os
|
||||
|
||||
import unreal
|
||||
|
||||
SRC = "/tmp/gigline/frontage"
|
||||
DEST = "/Game/Hollywood/Frontage"
|
||||
RESULT = "/tmp/gigline/frontage-import.txt"
|
||||
lines = []
|
||||
|
||||
|
||||
def log(msg):
|
||||
lines.append(str(msg))
|
||||
unreal.log(str(msg))
|
||||
|
||||
|
||||
def import_fbx(path, name):
|
||||
task = unreal.AssetImportTask()
|
||||
task.filename = path
|
||||
task.destination_path = DEST
|
||||
task.destination_name = name
|
||||
task.automated = True
|
||||
task.save = True
|
||||
task.replace_existing = True
|
||||
|
||||
ui = unreal.FbxImportUI()
|
||||
ui.import_mesh = True
|
||||
ui.import_materials = True
|
||||
ui.import_textures = False
|
||||
ui.import_as_skeletal = False
|
||||
smd = ui.static_mesh_import_data
|
||||
smd.import_uniform_scale = 1.0
|
||||
smd.combine_meshes = True
|
||||
smd.convert_scene_unit = False
|
||||
smd.force_front_x_axis = False
|
||||
smd.normal_import_method = unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS
|
||||
task.options = ui
|
||||
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
||||
|
||||
mesh = unreal.EditorAssetLibrary.load_asset(f"{DEST}/{name}")
|
||||
if mesh is None:
|
||||
log(f"MISSING after import: {DEST}/{name}")
|
||||
return
|
||||
# Buildings are solids the player can walk into the side of: simple + complex
|
||||
# so the blockout blocks the player without expensive per-triangle queries.
|
||||
try:
|
||||
body = mesh.get_editor_property("body_setup")
|
||||
body.set_editor_property("collision_trace_flag",
|
||||
unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AND_COMPLEX)
|
||||
mesh.set_editor_property("body_setup", body)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
log(f"{name}: collision flag not set ({exc!r})")
|
||||
box = mesh.get_bounding_box()
|
||||
mn, mx = box.min, box.max
|
||||
# World-space placement, in Unreal centimetres, straight from the mesh bounds.
|
||||
log(f"{name:22} X {mn.x:8.0f}..{mx.x:8.0f} Y {mn.y:7.0f}..{mx.y:7.0f} "
|
||||
f"Z {mn.z:5.0f}..{mx.z:5.0f}")
|
||||
|
||||
|
||||
def main():
|
||||
files = sorted(glob.glob(os.path.join(SRC, "*.fbx")))
|
||||
if not files:
|
||||
log(f"no FBX in {SRC}; run Tools/export_block_frontage.py first")
|
||||
return
|
||||
for path in files:
|
||||
name = os.path.splitext(os.path.basename(path))[0]
|
||||
import_fbx(path, name)
|
||||
unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True)
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
import traceback
|
||||
log(f"FAILED: {type(exc).__name__}: {exc}")
|
||||
log(traceback.format_exc())
|
||||
|
||||
with open(RESULT, "w") as f:
|
||||
f.write("\n".join(lines) + "\n")
|
||||
Reference in new issue
Block a user