fix(hero): seat the façade detail on the wall, so it renders at all

The three hero captures showed a plain block with six columns because every applied detail sat BEHIND the
wall's front face, inside the solid mass:

  Hero_Banner_1   y -1.08..-1.02   (55 cm inside)
  Hero_Portal_1   y -2.35..-1.15   (2 m inside)
  Hero_Window_1_1 y -0.60..-0.52   (2 cm inside)

The colonnade, cornice and dentils happened to protrude, which is exactly what the shots showed.

The depth is now stated once, in hero_facade_build.py: FRONT_Y is the wall's front face and
seat_on_front() moves every applied part until its street-facing face sits at FRONT_Y + offset - 8 cm for
banners and jambs, 5 cm for entablature and cornice, 3 cm for window panes, 4 cm for mullions, 10 cm for
portal signs. It reports what it moved, 99 parts.

After re-export, re-import and re-render the front elevation now shows the three red banners, the two
window bands, the dentil course and the portals. What is still missing against the reference: rusticated
banding on the stone, lettering on the motto band, grilles in the windows, a balustrade on the parapet, and
the blank lintel band across the portals that the reference does not have.

Rows 4, 5 and 6 are back to pending for review because the images changed.
This commit is contained in:
zwitschi committed 2026-11-10 07:59:51 +01:00
1 parent 01e49ff889
commit 7b9a7a3867
110 files changed
+183 -137

No files matched your search

+46
View File
@@ -147,6 +147,14 @@ def build():
# 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
# The wall's front face, and the only Y that matters for what is visible.
# +Y is the street side: anything with y < FRONT_Y is inside the solid mass and never renders.
# Hand-typed negative Y values throughout this file put the banners 55 cm, the portals 2 m and
# the windows 6 cm behind this plane, which is why the captures showed a plain block with six
# columns. `seat_on_front()` below re-seats every applied detail relative to FRONT_Y, so the
# depth is stated once instead of guessed at each call site.
global FRONT_Y
FRONT_Y = -BODY_D / 2.0 - 0.5 + BODY_D / 2.0 # = -0.5 with the current BODY_D
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")
@@ -350,9 +358,47 @@ def build():
cube(f"Hero_Window_{rank}_{i}", wx, WINDOW_Y, z, 2.0, 0.08, 1.9, glass)
note("Hero_Window_x10", (2.0, 0.08, 1.9), "photographs: window bands behind the colonnade")
# --- seat the applied detail on the wall ---------------------------------
# Depth is a property of the façade, not of each call site: state it once here. Parts whose
# front face is behind the wall plane are moved until it sits at FRONT_Y + offset.
seat_on_front((
("Hero_Architrave", 0.05), ("Hero_Cornice", 0.05), ("Hero_Dentil", 0.05),
("Hero_Entablature", 0.03), ("Hero_MottoBand", 0.06), ("Hero_MottoSurround", 0.06),
("Hero_Medallion", 0.08), ("Hero_Banner", 0.08), ("Hero_Window", 0.03),
("Hero_Portal", 0.05), ("Hero_PortalJamb", 0.08), ("Hero_PortalLintel", 0.08),
("Hero_PortalMullion", 0.04), ("Hero_PortalSign", 0.10),
))
return parts, lines
def seat_on_front(table):
"""Move each named part so its street-facing face sits `offset` metres proud of FRONT_Y.
`table` is (name_prefix, offset_m) pairs. Parts that name no prefix - the mass, the colonnade,
the steps and the stylobate - are left where the geometry put them, because they define the
plane rather than hang on it.
"""
moved = 0
for obj in bpy.data.objects:
if obj.type != "MESH":
continue
for prefix, offset in table:
if not obj.name.startswith(prefix):
continue
world_ys = [(obj.matrix_world @ v.co).y for v in obj.data.vertices]
front = max(world_ys)
delta = (FRONT_Y + offset) - front
if abs(delta) < 1e-4:
moved += 1
else:
obj.location.y += delta
print(f"[hero] seat {obj.name:22} y {front:+.2f} -> {front + delta:+.2f} (offset {offset:+.2f})")
moved += 1
break
print(f"[hero] seated {moved} parts on the wall plane at Y {FRONT_Y:+.2f}")
def export_fbx(path):
# Preset lives in Tools/fbx_export_preset.py (ue_metric_v1).
os.makedirs(os.path.dirname(path), exist_ok=True)