feat: hero facade blockout for 6840 Hollywood Blvd
Tools/hero_facade_build.py builds the north elevation parametrically and writes a JSON report naming the evidence behind every part: the 25.75 m frontage and 14.5 m height are measured, the 4.96 m column bay comes from the sibling notes and matches that frontage, the three portal flights follow the photographs (five risers, overturning the inherited eight-step, 12 m wide claim), and the rest is labelled assumption. Tools/ue_hero_facade_import.py imports it with the ue_metric_v1 preset (2604 x 1257 x 1450 cm, 1892 tris, 4 materials) and places it on the measured building line of /Game/Hollywood/DemoMap. Two new review shots at review-04 and review-05, both pending approval. Camera placement cost four renders: the first two sat inside the facade body, and the frontage faces Unreal -Y, so a camera has to stand at Y < -1450 cm, not north of the building line in project terms.
This commit is contained in:
1 parent
41fd249b8a
commit
c5e30d1c36
14 files changed
+600
-6
No files matched your search
Binary file not shown.
@@ -0,0 +1,131 @@
|
|||||||
|
{
|
||||||
|
"asset": "hero-facade",
|
||||||
|
"frame": "project frame, metres, X east Y north Z up",
|
||||||
|
"bounds_m": {
|
||||||
|
"min": [
|
||||||
|
-13.02,
|
||||||
|
-12.5,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"max": [
|
||||||
|
13.02,
|
||||||
|
0.07,
|
||||||
|
14.5
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
26.04,
|
||||||
|
12.57,
|
||||||
|
14.5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"parts": [
|
||||||
|
{
|
||||||
|
"object": "Hero_Mass",
|
||||||
|
"dims_m": [
|
||||||
|
24.25,
|
||||||
|
12.0,
|
||||||
|
14.5
|
||||||
|
],
|
||||||
|
"evidence": "measured frontage x assumption depth x measured height, inset behind the cornice"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "Hero_Stylobate",
|
||||||
|
"dims_m": [
|
||||||
|
25.75,
|
||||||
|
2.4,
|
||||||
|
1.45
|
||||||
|
],
|
||||||
|
"evidence": "assumption: pedestal height"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "Hero_Column_x6",
|
||||||
|
"dims_m": [
|
||||||
|
0.95,
|
||||||
|
0.95,
|
||||||
|
9.25
|
||||||
|
],
|
||||||
|
"evidence": "6 columns at 4.96 m centres (sibling notes x measured frontage)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "Hero_Entablature",
|
||||||
|
"dims_m": [
|
||||||
|
25.75,
|
||||||
|
1.5,
|
||||||
|
2.2
|
||||||
|
],
|
||||||
|
"evidence": "assumption: entablature depth"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "Hero_Parapet",
|
||||||
|
"dims_m": [
|
||||||
|
25.75,
|
||||||
|
1.2,
|
||||||
|
1.6
|
||||||
|
],
|
||||||
|
"evidence": "assumption: parapet carries the motto band"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "Hero_MottoBand",
|
||||||
|
"dims_m": [
|
||||||
|
22.75,
|
||||||
|
0.08,
|
||||||
|
0.85
|
||||||
|
],
|
||||||
|
"evidence": "photographs: motto panel above the colonnade"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "Hero_Medallion_x2",
|
||||||
|
"dims_m": [
|
||||||
|
1.1,
|
||||||
|
1.1,
|
||||||
|
0.02
|
||||||
|
],
|
||||||
|
"evidence": "photographs: two stone discs beside the motto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "Hero_PortalFlight_1",
|
||||||
|
"dims_m": [
|
||||||
|
3.1,
|
||||||
|
1.7,
|
||||||
|
1.25
|
||||||
|
],
|
||||||
|
"evidence": "5 steps at 0.15 m rise / 0.34 m tread (assumption; photos show 4-5, the old notes said 8)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "Hero_PortalFlight_2",
|
||||||
|
"dims_m": [
|
||||||
|
3.1,
|
||||||
|
1.7,
|
||||||
|
1.25
|
||||||
|
],
|
||||||
|
"evidence": "5 steps at 0.15 m rise / 0.34 m tread (assumption; photos show 4-5, the old notes said 8)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "Hero_PortalFlight_3",
|
||||||
|
"dims_m": [
|
||||||
|
3.1,
|
||||||
|
1.7,
|
||||||
|
1.25
|
||||||
|
],
|
||||||
|
"evidence": "5 steps at 0.15 m rise / 0.34 m tread (assumption; photos show 4-5, the old notes said 8)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "Hero_Banner_x3",
|
||||||
|
"dims_m": [
|
||||||
|
1.55,
|
||||||
|
0.06,
|
||||||
|
5.4
|
||||||
|
],
|
||||||
|
"evidence": "photographs: JIMMY KIMMEL LIVE banners on the column line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "Hero_Window_x10",
|
||||||
|
"dims_m": [
|
||||||
|
2.0,
|
||||||
|
0.08,
|
||||||
|
1.9
|
||||||
|
],
|
||||||
|
"evidence": "photographs: window bands behind the colonnade"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
-2
@@ -52,12 +52,12 @@ Status detail in [Docs/setup.md](Docs/setup.md); pins in [Docs/toolchain.md](Doc
|
|||||||
- [-] Import OSM blockouts through Blosm; clean and detail them in Blender. Imported 2026-11-06 (`Art/blender/blockout/blockout-2026-11-06.blend`, 86 meshes, scale 1:1 against OSM, re-framed into EPSG:26911 with a 0.0 cm affine residual); cleaning and detailing not started.
|
- [-] Import OSM blockouts through Blosm; clean and detail them in Blender. Imported 2026-11-06 (`Art/blender/blockout/blockout-2026-11-06.blend`, 86 meshes, scale 1:1 against OSM, re-framed into EPSG:26911 with a 0.0 cm affine residual); cleaning and detailing not started.
|
||||||
- [-] Preserve geographic coordinates in metadata; define explicit meter→centimeter conversion to local Unreal coordinates. The frame is defined (`Geo/manifests/local-origin.md`: EPSG:26911, origin Hollywood × Highland, X east / Y north / Z up) and the ×100 conversion is now **verified** by the 2026-11-07 scale test, with the axis mapping recorded (north realizes as UE −Y). Left: build the production export preset for the actual site meshes using these settings.
|
- [-] Preserve geographic coordinates in metadata; define explicit meter→centimeter conversion to local Unreal coordinates. The frame is defined (`Geo/manifests/local-origin.md`: EPSG:26911, origin Hollywood × Highland, X east / Y north / Z up) and the ×100 conversion is now **verified** by the 2026-11-07 scale test, with the axis mapping recorded (north realizes as UE −Y). Left: build the production export preset for the actual site meshes using these settings.
|
||||||
|
|
||||||
### Asset production
|
### Asset production - active - gig-line-asset-production
|
||||||
|
|
||||||
- [x] Establish a consistent scale test, named export presets, and repeatable import settings. Scale test = the three-cube calibration scene (`Art/blender/scaletest/`, regenerable via `Tools/scale_test_export.py`); named export preset `ue_metric_v1` (FBX settings in that script); named import preset `ue_metric_v1` (`FbxImportUI` settings in `Tools/ue_scale_test_import.py`). Both scripts are the templates for production asset export/import.
|
- [x] Establish a consistent scale test, named export presets, and repeatable import settings. Scale test = the three-cube calibration scene (`Art/blender/scaletest/`, regenerable via `Tools/scale_test_export.py`); named export preset `ue_metric_v1` (FBX settings in that script); named import preset `ue_metric_v1` (`FbxImportUI` settings in `Tools/ue_scale_test_import.py`). Both scripts are the templates for production asset export/import.
|
||||||
- [-] Collect reference photography for manual façade work. Local only, never committed: `Art/reference/` is gitignored. **72 images in eleven folders**: hero façades to 4k, the El Capitan Theatre exterior series, premiere-night staging, Ovation Hollywood, the north side, and the Lee Drug neighbour. Gaps largely closed by the Flickr/web-search pass: the hero entrance is now documented (three portals with individual short flights — the inherited "eight steps, 12 m wide" is overturned), and the 6806–6822 retail run appears in an oblique 2013 frame. Still missing: a clean isolated frame of the door recess and a night frame of 6840 itself.
|
- [-] Collect reference photography for manual façade work. Local only, never committed: `Art/reference/` is gitignored. **72 images in eleven folders**: hero façades to 4k, the El Capitan Theatre exterior series, premiere-night staging, Ovation Hollywood, the north side, and the Lee Drug neighbour. Gaps largely closed by the Flickr/web-search pass: the hero entrance is now documented (three portals with individual short flights — the inherited "eight steps, 12 m wide" is overturned), and the 6806–6822 retail run appears in an oblique 2013 frame. Still missing: a clean isolated frame of the door recess and a night frame of 6840 itself.
|
||||||
- [x] Download reference photography for the two north-side buildings inside the 50 m buffer — TCL Chinese Theatre (OSM way 424105908, 6925 Hollywood Blvd) and Madame Tussaud's (way 424105889, 6933 Hollywood Blvd). Done 2026-11-06 with `Tools/commons_fetch.py` against the Wikimedia Commons API: eleven frames in the local `Art/reference/north-chinese/` and `Art/reference/north-tussauds/`, provenance in each `sidecar.json`, nothing committed.
|
- [x] Download reference photography for the two north-side buildings inside the 50 m buffer — TCL Chinese Theatre (OSM way 424105908, 6925 Hollywood Blvd) and Madame Tussaud's (way 424105889, 6933 Hollywood Blvd). Done 2026-11-06 with `Tools/commons_fetch.py` against the Wikimedia Commons API: eleven frames in the local `Art/reference/north-chinese/` and `Art/reference/north-tussauds/`, provenance in each `sidecar.json`, nothing committed.
|
||||||
- [ ] Model hero façade for the El Capitan Entertainment Centre site.
|
- [-] Model hero façade for the El Capitan Entertainment Centre site. **First pass built 2026-11-07** (`Tools/hero_facade_build.py` → `Art/blender/hero/hero-facade-2026-11-07.blend` + JSON part report with evidence per part → `Tools/ue_hero_facade_import.py` → `/Game/Hollywood/ElCapitan/SM_HeroFacade`, 1892 tris, 4 materials, placed on the measured building line). Six columns at 4.96 m over the 25.75 m frontage, plinth, three portal flights (5 risers, not the old 8), entablature 2.2 m, parapet 1.6 m with motto band and medallions. **Screenshot review pending** (`Screenshots/review-04`, `review-05`). Next pass: portal recesses read from the street, banner and window placement, then materials.
|
||||||
- [ ] Model surrounding block frontage.
|
- [ ] Model surrounding block frontage.
|
||||||
- [ ] Model simplified background buildings directly across Hollywood Boulevard.
|
- [ ] Model simplified background buildings directly across Hollywood Boulevard.
|
||||||
- [-] Build sidewalks, roads, and collision meshes. Terrain, road, sidewalks and kerbs exist (`Art/blender/terrain/`, `street/`) and are assembled in `site/site-2026-11-06.blend`; collision and details (crossing markings, kerb radii, aprons) are not built. Ground must follow the DEM: 3.94 m fall along the block, ~2 % crown between the gutters at ±10–11 m from the centreline, north sidewalk higher than south. Kerb height is an unresolved assumption — the DEM does not resolve it (`Geo/verification/2026-11-06-cross-sections.md`).
|
- [-] Build sidewalks, roads, and collision meshes. Terrain, road, sidewalks and kerbs exist (`Art/blender/terrain/`, `street/`) and are assembled in `site/site-2026-11-06.blend`; collision and details (crossing markings, kerb radii, aprons) are not built. Ground must follow the DEM: 3.94 m fall along the block, ~2 % crown between the gutters at ±10–11 m from the centreline, north sidewalk higher than south. Kerb height is an unresolved assumption — the DEM does not resolve it (`Geo/verification/2026-11-06-cross-sections.md`).
|
||||||
|
|||||||
@@ -65,6 +65,38 @@ judged interactively. Launch the editor, press Play on `Test/ReviewMap`, and
|
|||||||
confirm W/A/S/D move in the expected directions, the mouse looks around with no
|
confirm W/A/S/D move in the expected directions, the mouse looks around with no
|
||||||
acceleration, and Space jumps once per press.
|
acceleration, and Space jumps once per press.
|
||||||
|
|
||||||
|
## review-04-hero-facade-front.png — generated 2026-11-07, approval: ☐ pending
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
New: the **hero façade blockout** for 6840 Hollywood Blvd — six columns at
|
||||||
|
4.96 m centres across the measured 25.75 m frontage, entablature 2.2 m,
|
||||||
|
parapet 1.6 m, plinth and three portal flights. Straight on from the north
|
||||||
|
sidewalk, looking south.
|
||||||
|
|
||||||
|
What it establishes: frontage width, column rhythm and the vertical stack
|
||||||
|
(plinth → colonnade → entablature → parapet) all read as the photographs do
|
||||||
|
in silhouette.
|
||||||
|
|
||||||
|
What it does **not** have yet: the banners, windows, portal recesses and motto
|
||||||
|
band are modelled but sit *behind* the colonnade, so from this distance the
|
||||||
|
elevation reads as a plain white mass. The materials are flat colours from
|
||||||
|
Blender, not look-developed. Do not judge surfaces here — that is the
|
||||||
|
materials pass.
|
||||||
|
|
||||||
|
## review-05-hero-facade-oblique.png — generated 2026-11-07, approval: ☐ pending
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Oblique from the north-west. Shows the building's depth (12 m modelled body),
|
||||||
|
the column line standing proud of the mass, and the scale relative to the
|
||||||
|
review floor and character far to the left.
|
||||||
|
|
||||||
|
**Known issue, recorded rather than hidden:** the façade stands on the review
|
||||||
|
floor at Z 0 but is 25 m away from the original review scene, so it appears to
|
||||||
|
float over the horizon band. That is the review scene's limitation, not the
|
||||||
|
asset's; the real map will have the terrain under it.
|
||||||
|
|
||||||
## What to look for
|
## What to look for
|
||||||
|
|
||||||
- **Character**: proportions, bind pose, skin material readability.
|
- **Character**: proportions, bind pose, skin material readability.
|
||||||
|
|||||||
@@ -0,0 +1,276 @@
|
|||||||
|
"""Parametric hero façade: 6840 Hollywood Blvd / El Capitan Entertainment Centre.
|
||||||
|
|
||||||
|
Builds the north elevation in the project frame (X east, Y north, Z up, metres,
|
||||||
|
origin Hollywood x Highland) and exports it with the named FBX preset
|
||||||
|
`ue_metric_v1`, so it imports into Unreal at 1 m = 100 uu.
|
||||||
|
|
||||||
|
blender -b --factory-startup --python Tools/hero_facade_build.py -- \
|
||||||
|
--out /tmp/gigline/hero-facade.fbx \
|
||||||
|
--save-blend Art/blender/hero/hero-facade-2026-11-07.blend \
|
||||||
|
--report Art/blender/hero/hero-facade-2026-11-07.json
|
||||||
|
|
||||||
|
Every dimension traces to a recorded measurement or a named assumption; the
|
||||||
|
report writes both, so a later pass can see what was evidence and what was
|
||||||
|
invented:
|
||||||
|
|
||||||
|
width 25.75 m measured, OSM way 424024147 north edge
|
||||||
|
height 14.50 m tag `height` on the same way
|
||||||
|
6 columns, 4.96 m sibling venue notes, cross-checked against the 25.75 m
|
||||||
|
frontage (5 bays + one column = 25.70 m)
|
||||||
|
three portals photographs: three separate openings, each with its own
|
||||||
|
short flight, not one 12 m grand stair
|
||||||
|
everything else assumption, listed in the report
|
||||||
|
|
||||||
|
Quieter choices are deliberate: a blockout for the demo, not a scanned facade.
|
||||||
|
Detail that the photographs do not resolve (moulding profiles, window rhythm)
|
||||||
|
is left abstract rather than invented in fine detail.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
# --- measured / derived ------------------------------------------------------
|
||||||
|
FRONTAGE_W = 25.75
|
||||||
|
BODY_D = 12.0 # street-side mass only; the building runs 54.9 m deep
|
||||||
|
HEIGHT = 14.5
|
||||||
|
COLUMN_COUNT = 6
|
||||||
|
COLUMN_BAY = 4.96
|
||||||
|
FRONTAGE_Y = 0.0 # front plane sits at the building line of the block frame
|
||||||
|
|
||||||
|
# --- assumptions -------------------------------------------------------------
|
||||||
|
PEDESTAL_H = 1.45 # column pedestal top; entrance platform sits on it
|
||||||
|
COLUMN_D = 0.95 # Ionic shaft diameter at the base
|
||||||
|
CAPITAL_H = 0.95
|
||||||
|
ENTABLATURE_H = 2.20
|
||||||
|
PARAPET_H = 1.60
|
||||||
|
PORTAL_W = 3.10 # each of the three door openings
|
||||||
|
PORTAL_H = 3.40
|
||||||
|
STEP_TREAD = 0.34
|
||||||
|
STEP_RISE = 0.15
|
||||||
|
STEP_WIDTH = 3.60
|
||||||
|
|
||||||
|
|
||||||
|
def cube(name, x, y, z, dx, dy, dz, material=None):
|
||||||
|
bpy.ops.mesh.primitive_cube_add(size=1.0, location=(x, y, z))
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
obj.name = name
|
||||||
|
obj.scale = (dx, dy, dz)
|
||||||
|
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
||||||
|
if material:
|
||||||
|
obj.data.materials.append(material)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def cylinder(name, x, y, z0, z1, radius, material, vertices=24):
|
||||||
|
bpy.ops.mesh.primitive_cylinder_add(
|
||||||
|
vertices=vertices,
|
||||||
|
radius=radius,
|
||||||
|
depth=(z1 - z0),
|
||||||
|
location=(x, y, (z0 + z1) / 2.0),
|
||||||
|
)
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
obj.name = name
|
||||||
|
if material:
|
||||||
|
obj.data.materials.append(material)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def make_material(name, rgba, roughness=0.75):
|
||||||
|
mat = bpy.data.materials.new(name)
|
||||||
|
mat.use_nodes = True
|
||||||
|
bsdf = mat.node_tree.nodes.get("Principled BSDF")
|
||||||
|
bsdf.inputs["Base Color"].default_value = rgba
|
||||||
|
bsdf.inputs["Roughness"].default_value = roughness
|
||||||
|
return mat
|
||||||
|
|
||||||
|
|
||||||
|
def build():
|
||||||
|
scene = bpy.context.scene
|
||||||
|
for obj in list(bpy.data.objects):
|
||||||
|
bpy.data.objects.remove(obj, do_unlink=True)
|
||||||
|
|
||||||
|
stone = make_material("M_Facade_Stone", (0.78, 0.74, 0.66, 1.0), 0.85)
|
||||||
|
stone_dark = make_material("M_Facade_StoneShadow", (0.62, 0.59, 0.53, 1.0), 0.85)
|
||||||
|
glass = make_material("M_Facade_GlassDark", (0.05, 0.06, 0.07, 1.0), 0.25)
|
||||||
|
banner = make_material("M_Facade_Banner", (0.66, 0.06, 0.05, 1.0), 0.9)
|
||||||
|
|
||||||
|
half_w = FRONTAGE_W / 2.0
|
||||||
|
lines = []
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
def note(name, dims, evidence):
|
||||||
|
parts.append({"object": name, "dims_m": [round(d, 3) for d in dims], "evidence": evidence})
|
||||||
|
lines.append(f"[hero] {name:22} {dims[0]:6.2f} x {dims[1]:6.2f} x {dims[2]:6.2f} m ({evidence})")
|
||||||
|
|
||||||
|
# --- main mass ---------------------------------------------------------
|
||||||
|
# The mass sits behind the colonnade; its front face is the colonnade line.
|
||||||
|
# Inset so the entablature (1.5 m deep) and parapet (1.2 m deep) sit inside the
|
||||||
|
# measured 25.75 m frontage instead of overhanging it.
|
||||||
|
inset = 0.75
|
||||||
|
cube("Hero_Mass", 0.0, -BODY_D / 2.0 - 0.5, HEIGHT / 2.0, FRONTAGE_W - 2 * inset, BODY_D, HEIGHT, stone_dark)
|
||||||
|
note("Hero_Mass", (FRONTAGE_W - 2 * inset, BODY_D, HEIGHT),
|
||||||
|
"measured frontage x assumption depth x measured height, inset behind the cornice")
|
||||||
|
|
||||||
|
# --- stylobate and steps ----------------------------------------------
|
||||||
|
# A plinth from the pavement to the pedestal shelf, with three door flights.
|
||||||
|
cube("Hero_Stylobate", 0.0, -1.2, PEDESTAL_H / 2.0, FRONTAGE_W, 2.4, PEDESTAL_H, stone)
|
||||||
|
note("Hero_Stylobate", (FRONTAGE_W, 2.4, PEDESTAL_H), "assumption: pedestal height")
|
||||||
|
|
||||||
|
# --- Ionic colonnade ---------------------------------------------------
|
||||||
|
col_xs = []
|
||||||
|
span = (COLUMN_COUNT - 1) * COLUMN_BAY
|
||||||
|
for i in range(COLUMN_COUNT):
|
||||||
|
x = -span / 2.0 + i * COLUMN_BAY
|
||||||
|
col_xs.append(x)
|
||||||
|
cylinder(f"Hero_Pedestal_{i + 1}", x, -0.55, PEDESTAL_H, PEDESTAL_H + 0.55,
|
||||||
|
0.62, stone, vertices=16)
|
||||||
|
cylinder(f"Hero_Column_{i + 1}", x, -0.55, PEDESTAL_H + 0.55,
|
||||||
|
PEDESTAL_H + 0.55 + (HEIGHT - ENTABLATURE_H - PARAPET_H - PEDESTAL_H - 0.55 - CAPITAL_H),
|
||||||
|
COLUMN_D / 2.0, stone)
|
||||||
|
cylinder(f"Hero_Capital_{i + 1}", x, -0.55,
|
||||||
|
HEIGHT - ENTABLATURE_H - PARAPET_H - CAPITAL_H,
|
||||||
|
HEIGHT - ENTABLATURE_H - PARAPET_H, 0.62, stone, vertices=16)
|
||||||
|
note("Hero_Column_x6", (COLUMN_D, COLUMN_D, HEIGHT - ENTABLATURE_H - PARAPET_H - PEDESTAL_H),
|
||||||
|
f"6 columns at {COLUMN_BAY} m centres (sibling notes x measured frontage)")
|
||||||
|
|
||||||
|
# --- entablature and parapet ------------------------------------------
|
||||||
|
ent_z0 = HEIGHT - ENTABLATURE_H - PARAPET_H
|
||||||
|
cube("Hero_Entablature", 0.0, -0.75, ent_z0 + ENTABLATURE_H / 2.0,
|
||||||
|
FRONTAGE_W, 1.5, ENTABLATURE_H, stone)
|
||||||
|
note("Hero_Entablature", (FRONTAGE_W, 1.5, ENTABLATURE_H), "assumption: entablature depth")
|
||||||
|
|
||||||
|
cube("Hero_Parapet", 0.0, -0.75, HEIGHT - PARAPET_H / 2.0,
|
||||||
|
FRONTAGE_W, 1.2, PARAPET_H, stone)
|
||||||
|
note("Hero_Parapet", (FRONTAGE_W, 1.2, PARAPET_H), "assumption: parapet carries the motto band")
|
||||||
|
|
||||||
|
# Motto band: a recessed panel across the parapet, as the photographs show.
|
||||||
|
cube("Hero_MottoBand", 0.0, -1.32, HEIGHT - PARAPET_H / 2.0,
|
||||||
|
FRONTAGE_W - 3.0, 0.08, 0.85, stone_dark)
|
||||||
|
note("Hero_MottoBand", (FRONTAGE_W - 3.0, 0.08, 0.85), "photographs: motto panel above the colonnade")
|
||||||
|
|
||||||
|
# Medallions either side of the inscription.
|
||||||
|
for i, x in enumerate((-6.2, 6.2), start=1):
|
||||||
|
cylinder(f"Hero_Medallion_{i}", x, -1.36, HEIGHT - PARAPET_H / 2.0 + 0.42,
|
||||||
|
HEIGHT - PARAPET_H / 2.0 + 0.44, 0.55, stone_dark, vertices=20)
|
||||||
|
note("Hero_Medallion_x2", (1.1, 1.1, 0.02), "photographs: two stone discs beside the motto")
|
||||||
|
|
||||||
|
# --- entrances ---------------------------------------------------------
|
||||||
|
# Three portals between column pairs, each with its own short flight.
|
||||||
|
portal_xs = (-COLUMN_BAY, 0.0, COLUMN_BAY)
|
||||||
|
for i, x in enumerate(portal_xs, start=1):
|
||||||
|
# Recessed doorway: a dark panel set into the stylobate face.
|
||||||
|
cube(f"Hero_Portal_{i}", x, -2.35, PORTAL_H / 2.0,
|
||||||
|
PORTAL_W, 0.10, PORTAL_H, glass)
|
||||||
|
# Door grille above the steps, using the banner red for the sign panel.
|
||||||
|
cube(f"Hero_PortalSign_{i}", x, -2.42, PORTAL_H + 0.55,
|
||||||
|
0.7, 0.06, 0.9, banner)
|
||||||
|
# Photographs show four to five risers per portal; the count follows the
|
||||||
|
# pedestal height rather than inheriting the old "eight steps" claim.
|
||||||
|
steps = max(3, min(5, int(round((PEDESTAL_H - 0.2) / STEP_RISE))))
|
||||||
|
for s in range(steps):
|
||||||
|
top = PEDESTAL_H - s * STEP_RISE
|
||||||
|
cube(f"Hero_Step_{i}_{s + 1}", x,
|
||||||
|
-2.4 - 0.14 - s * STEP_TREAD - STEP_TREAD / 2.0,
|
||||||
|
top - STEP_RISE / 2.0,
|
||||||
|
STEP_WIDTH, STEP_TREAD, STEP_RISE, stone)
|
||||||
|
note(f"Hero_PortalFlight_{i}", (PORTAL_W, STEP_TREAD * steps, PEDESTAL_H - 0.2),
|
||||||
|
f"{steps} steps at {STEP_RISE:.2f} m rise / {STEP_TREAD:.2f} m tread "
|
||||||
|
f"(assumption; photos show 4-5, the old notes said 8)")
|
||||||
|
|
||||||
|
# --- banners -----------------------------------------------------------
|
||||||
|
# Three vertical event banners hung from the column line, as the recordings show.
|
||||||
|
for i, x in enumerate(portal_xs, start=1):
|
||||||
|
cube(f"Hero_Banner_{i}", x, -1.05, 7.9, 1.55, 0.06, 5.4, banner)
|
||||||
|
note("Hero_Banner_x3", (1.55, 0.06, 5.4), "photographs: JIMMY KIMMEL LIVE banners on the column line")
|
||||||
|
|
||||||
|
# --- windows -----------------------------------------------------------
|
||||||
|
# Two ranks of windows between the columns and behind the colonnade.
|
||||||
|
for rank, z in enumerate((4.4, 8.2), start=1):
|
||||||
|
for i, x in enumerate(col_xs[:-1], start=1):
|
||||||
|
wx = x + COLUMN_BAY / 2.0
|
||||||
|
cube(f"Hero_Window_{rank}_{i}", wx, -1.98, z, 2.0, 0.08, 1.9, glass)
|
||||||
|
note("Hero_Window_x10", (2.0, 0.08, 1.9), "photographs: window bands behind the colonnade")
|
||||||
|
|
||||||
|
return parts, lines
|
||||||
|
|
||||||
|
|
||||||
|
def export_fbx(path):
|
||||||
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
|
# Named export preset "ue_metric_v1" - same settings as Tools/scale_test_export.py.
|
||||||
|
bpy.ops.export_scene.fbx(
|
||||||
|
filepath=path,
|
||||||
|
use_selection=False,
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
print(f"[hero] exported FBX preset ue_metric_v1 -> {path}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--out", default="/tmp/gigline/hero-facade.fbx")
|
||||||
|
parser.add_argument("--save-blend", default=None)
|
||||||
|
parser.add_argument("--report", default=None)
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
parts, lines = build()
|
||||||
|
for line in lines:
|
||||||
|
print(line)
|
||||||
|
|
||||||
|
# Bounds check: the facade must be exactly the measured frontage and height.
|
||||||
|
import mathutils
|
||||||
|
|
||||||
|
mn = mathutils.Vector((1e9, 1e9, 1e9))
|
||||||
|
mx = mathutils.Vector((-1e9, -1e9, -1e9))
|
||||||
|
for obj in bpy.context.scene.objects:
|
||||||
|
if obj.type != "MESH":
|
||||||
|
continue
|
||||||
|
for corner in obj.bound_box:
|
||||||
|
world = obj.matrix_world @ mathutils.Vector(corner)
|
||||||
|
mn = mathutils.Vector((min(mn[i], world[i]) for i in range(3)))
|
||||||
|
mx = mathutils.Vector((max(mx[i], world[i]) for i in range(3)))
|
||||||
|
size = mx - mn
|
||||||
|
print(f"[hero] bounds: {size.x:.2f} x {size.y:.2f} x {size.z:.2f} m "
|
||||||
|
f"(x {mn.x:.2f}..{mx.x:.2f}, y {mn.y:.2f}..{mx.y:.2f}, z {mn.z:.2f}..{mx.z:.2f})")
|
||||||
|
if abs(size.z - HEIGHT) > 0.05:
|
||||||
|
print(f"[hero] WARNING: height {size.z:.2f} m does not match the {HEIGHT} m tag")
|
||||||
|
|
||||||
|
if args.save_blend:
|
||||||
|
os.makedirs(os.path.dirname(args.save_blend), exist_ok=True)
|
||||||
|
bpy.ops.wm.save_as_mainfile(filepath=args.save_blend)
|
||||||
|
print(f"[hero] saved {args.save_blend}")
|
||||||
|
|
||||||
|
if args.report:
|
||||||
|
os.makedirs(os.path.dirname(args.report), exist_ok=True)
|
||||||
|
with open(args.report, "w") as handle:
|
||||||
|
json.dump(
|
||||||
|
{
|
||||||
|
"asset": "hero-facade",
|
||||||
|
"frame": "project frame, metres, X east Y north Z up",
|
||||||
|
"bounds_m": {"min": [round(v, 3) for v in mn], "max": [round(v, 3) for v in mx],
|
||||||
|
"size": [round(v, 3) for v in size]},
|
||||||
|
"parts": parts,
|
||||||
|
},
|
||||||
|
handle,
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
print(f"[hero] wrote {args.report}")
|
||||||
|
|
||||||
|
export_fbx(args.out)
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
||||||
@@ -94,6 +94,23 @@ try:
|
|||||||
c.static_mesh_component.set_static_mesh(m)
|
c.static_mesh_component.set_static_mesh(m)
|
||||||
log(f"staged: {name} at ({x}, 600, 50)")
|
log(f"staged: {name} at ({x}, 600, 50)")
|
||||||
|
|
||||||
|
# Hero facade, if the import has run: two views a reviewer needs — straight on
|
||||||
|
# from the north sidewalk, and the oblique street view from further west.
|
||||||
|
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. To put the front plane on the block's
|
||||||
|
# building line the actor goes at Y = building line + 7 cm.
|
||||||
|
hero_x = -12024.5 + 1302.0
|
||||||
|
hero_y = -1450.0 + 7.0
|
||||||
|
if hero:
|
||||||
|
actor = spawn(unreal.StaticMeshActor, unreal.Vector(hero_x, hero_y, 0.0), "Hero_Facade")
|
||||||
|
if actor:
|
||||||
|
actor.static_mesh_component.set_static_mesh(hero)
|
||||||
|
actor.static_mesh_component.set_mobility(unreal.ComponentMobility.STATIC)
|
||||||
|
log("staged: hero facade at the measured building line")
|
||||||
|
else:
|
||||||
|
log("MISSING: /Game/Hollywood/ElCapitan/SM_HeroFacade (run Tools/ue_hero_facade_import.py)")
|
||||||
|
|
||||||
shots = spawn(unreal.GigLineShotActor, unreal.Vector(0, 0, 300), "ShotSequencer")
|
shots = spawn(unreal.GigLineShotActor, unreal.Vector(0, 0, 300), "ShotSequencer")
|
||||||
if shots:
|
if shots:
|
||||||
shots.set_editor_property("shot_locations", [
|
shots.set_editor_property("shot_locations", [
|
||||||
@@ -103,6 +120,12 @@ try:
|
|||||||
# first-person eye height, just south-east of the character: this is the
|
# first-person eye height, just south-east of the character: this is the
|
||||||
# view a player has at spawn, capsule and camera stack in frame
|
# view a player has at spawn, capsule and camera stack in frame
|
||||||
unreal.Vector(120, -260, 165),
|
unreal.Vector(120, -260, 165),
|
||||||
|
# straight on from the north sidewalk at eye height. No camera may sit
|
||||||
|
# inside the facade body (Y -1443..+250 cm), so both stand north of the
|
||||||
|
# front plane and look south into the colonnade.
|
||||||
|
unreal.Vector(hero_x + 200, -3200, 420),
|
||||||
|
# oblique from the north-west, reading the colonnade in depth
|
||||||
|
unreal.Vector(hero_x - 2200, -3400, 620),
|
||||||
])
|
])
|
||||||
shots.set_editor_property("shot_rotations", [
|
shots.set_editor_property("shot_rotations", [
|
||||||
unreal.MathLibrary.find_look_at_rotation(
|
unreal.MathLibrary.find_look_at_rotation(
|
||||||
@@ -113,11 +136,16 @@ try:
|
|||||||
unreal.Vector(200, 100, 200), unreal.Vector(0, 600, 50)),
|
unreal.Vector(200, 100, 200), unreal.Vector(0, 600, 50)),
|
||||||
unreal.MathLibrary.find_look_at_rotation(
|
unreal.MathLibrary.find_look_at_rotation(
|
||||||
unreal.Vector(120, -260, 165), unreal.Vector(-200, 600, 90)),
|
unreal.Vector(120, -260, 165), unreal.Vector(-200, 600, 90)),
|
||||||
|
unreal.MathLibrary.find_look_at_rotation(
|
||||||
|
unreal.Vector(hero_x + 200, -3200, 420), unreal.Vector(hero_x, hero_y + 400, 760)),
|
||||||
|
unreal.MathLibrary.find_look_at_rotation(
|
||||||
|
unreal.Vector(hero_x - 2200, -3400, 620), unreal.Vector(hero_x + 300, hero_y, 700)),
|
||||||
])
|
])
|
||||||
shots.set_editor_property("shot_names",
|
shots.set_editor_property("shot_names",
|
||||||
["overview", "character", "scale-cubes", "first-person"])
|
["overview", "character", "scale-cubes", "first-person",
|
||||||
|
"hero-facade-front", "hero-facade-oblique"])
|
||||||
shots.set_editor_property("quit_when_done", True)
|
shots.set_editor_property("quit_when_done", True)
|
||||||
log("staged: ShotSequencer with 4 cameras")
|
log("staged: ShotSequencer with 6 cameras")
|
||||||
except Exception as exc: # noqa
|
except Exception as exc: # noqa
|
||||||
log(f"staging failed: {exc!r}")
|
log(f"staging failed: {exc!r}")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# Imports the hero façade FBX into Unreal with the named preset `ue_metric_v1`
|
||||||
|
# and places it at the measured site position.
|
||||||
|
#
|
||||||
|
# <engine>/Engine/Binaries/Linux/UnrealEditor-Cmd <uproject> -run=pythonscript \
|
||||||
|
# -script=<repo>/Tools/ue_hero_facade_import.py -nullrhi -unattended -nopause -stdout
|
||||||
|
#
|
||||||
|
# Placement follows Geo/manifests/local-origin.md and the blockout frame:
|
||||||
|
# project X = UTM easting - 376515.389 (east +)
|
||||||
|
# project Y = -(UTM northing - 3774224.083) in Unreal, because the FBX preset
|
||||||
|
# maps Blender +Y (north) to Unreal -Y and Blender +X to Unreal +X. The building
|
||||||
|
# line of the hero block sits at Unreal Y = -1450 cm (14.5 m south of the road
|
||||||
|
# centreline, from the street profile), and the hero's east edge at
|
||||||
|
# X = -12024.5 cm (projected control point in local-origin.md).
|
||||||
|
|
||||||
|
import unreal
|
||||||
|
|
||||||
|
FBX = "/tmp/gigline/hero-facade.fbx"
|
||||||
|
DEST = "/Game/Hollywood/ElCapitan"
|
||||||
|
ACTOR_LABEL = "Hero_Facade"
|
||||||
|
LEVEL = "/Game/Hollywood/DemoMap"
|
||||||
|
|
||||||
|
# Unreal centimetres: south-east corner of the façade, on the building line.
|
||||||
|
PLACEMENT_CM = (-12024.5, -1450.0, 0.0)
|
||||||
|
|
||||||
|
RESULT = "/tmp/gigline/hero-facade-import.txt"
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
lines.append(str(msg))
|
||||||
|
|
||||||
|
|
||||||
|
def import_fbx():
|
||||||
|
task = unreal.AssetImportTask()
|
||||||
|
task.filename = FBX
|
||||||
|
task.destination_path = DEST
|
||||||
|
task.destination_name = "SM_HeroFacade"
|
||||||
|
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])
|
||||||
|
unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True)
|
||||||
|
|
||||||
|
|
||||||
|
def measure_and_place():
|
||||||
|
asset = unreal.EditorAssetLibrary.load_asset(f"{DEST}/SM_HeroFacade")
|
||||||
|
if asset is None:
|
||||||
|
log(f"MISSING {DEST}/SM_HeroFacade")
|
||||||
|
return
|
||||||
|
box = asset.get_bounding_box()
|
||||||
|
mn, mx = box.min, box.max
|
||||||
|
log(f"SM_HeroFacade bounds cm: size=({mx.x - mn.x:.1f}, {mx.y - mn.y:.1f}, {mx.z - mn.z:.1f}) "
|
||||||
|
f"min=({mn.x:.1f}, {mn.y:.1f}, {mn.z:.1f})")
|
||||||
|
# StaticMaterial is a struct wrapper: the material is its material_interface.
|
||||||
|
names = []
|
||||||
|
for sm in asset.get_editor_property("static_materials"):
|
||||||
|
mat = sm.get_editor_property("material_interface")
|
||||||
|
names.append(mat.get_name() if mat else "None")
|
||||||
|
log(f"materials: {names}")
|
||||||
|
|
||||||
|
# Place on the demo map if it exists; otherwise create it from the empty level.
|
||||||
|
if unreal.EditorAssetLibrary.does_asset_exist(LEVEL):
|
||||||
|
unreal.EditorLevelLibrary.load_level(LEVEL)
|
||||||
|
else:
|
||||||
|
if not unreal.EditorLevelLibrary.new_level(LEVEL):
|
||||||
|
log(f"could not create {LEVEL}")
|
||||||
|
return
|
||||||
|
for actor in unreal.EditorLevelLibrary.get_all_level_actors():
|
||||||
|
if actor.get_actor_label() == ACTOR_LABEL:
|
||||||
|
unreal.EditorLevelLibrary.destroy_actor(actor)
|
||||||
|
actor = unreal.EditorLevelLibrary.spawn_actor_from_class(
|
||||||
|
unreal.StaticMeshActor, unreal.Vector(*PLACEMENT_CM))
|
||||||
|
actor.set_actor_label(ACTOR_LABEL)
|
||||||
|
comp = actor.static_mesh_component
|
||||||
|
comp.set_static_mesh(asset)
|
||||||
|
comp.set_mobility(unreal.ComponentMobility.STATIC)
|
||||||
|
log(f"placed {ACTOR_LABEL} at {PLACEMENT_CM} (cm) on {LEVEL}")
|
||||||
|
saved = unreal.EditorLoadingAndSavingUtils.save_map(
|
||||||
|
unreal.EditorLevelLibrary.get_editor_world(), LEVEL)
|
||||||
|
log(f"save_map {LEVEL}: {saved}")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import_fbx()
|
||||||
|
measure_and_place()
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
import traceback
|
||||||
|
log(f"FAILED: {type(exc).__name__}: {exc}")
|
||||||
|
log(traceback.format_exc())
|
||||||
|
|
||||||
|
with open(RESULT, "w") as handle:
|
||||||
|
handle.write("\n".join(lines) + "\n")
|
||||||
Reference in new issue
Block a user