① One-shot authoring
model claude-fable-5 · 106 tool calls · 25m07s · cost $17.210686
👁INSPECT #1
I'll start by reading Room.md and Room.py to learn the helper API, then look at the reference stitches.
scene · scene_frame_00005
scene · scene_frame_00030
scene · scene_frame_00047
scene · scene_frame_00050
scene · scene_frame_00052
scene · scene_frame_00055⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen🔧 ToolSearch select:mcp__cap__render,mcp__cap__fetch_📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen⌘ Bash⌘ Bash⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen⌘ Bash⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen🖼 render room
✏️EDIT #2
As my memory notes, the MCP render needs an explicit scan — I'll use the render engine CLI directly.
⌘ Bash⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen⌘ Bash📄 Read /tmp/r26.png📄 Read /tmp/r28.png📄 Read /tmp/r30.png⌘ Bash📄 Read /tmp/r36.png📄 Read /tmp/r46.png📄 Read /tmp/r50.png📄 Read /tmp/r55.png📄 Read /tmp/r57.png⌘ Bash⌘ Bash📄 Read /tmp/c57b.png📄 Read /tmp/c55b.png⌘ Bash📄 Read /tmp/c48rad.png📄 Read /tmp/r47.png⌘ Bash📄 Read /tmp/c56bot.png📄 Read /tmp/c55bot.png🎨 fetch_material blue office carpet low pile⌘ Bash⌘ Bash📄 Read /tmp/c57bot.png📄 Read /tmp/c55mid.png✏️ Edit /scratch2/code/LiteReality_to_share/agen✏️ Edit /scratch2/code/LiteReality_to_share/agen⌘ Bash⌘ Bash⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen🎨 fetch_material blue office carpet low pile⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen⌘ Bash📄 Read /tmp/rh57.png📄 Read /tmp/rh55.png⚖️ critic✏️ Edit /scratch2/code/LiteReality_to_share/agen✏️ Edit /scratch2/code/LiteReality_to_share/agen✏️ Edit /scratch2/code/LiteReality_to_share/agen✏️ Edit /scratch2/code/LiteReality_to_share/agen✏️ Edit /scratch2/code/LiteReality_to_share/agen✏️ Edit /scratch2/code/LiteReality_to_share/agen⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen
✏️EDIT #3
Whiteboards now read bright white, the pinboard is muted grey-blue, and the AC shows louvre relief. Re-running the critic:
⚖️ critic✏️ Edit /scratch2/code/LiteReality_to_share/agen⌘ Bash⌘ Bash⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen✏️ Write /home/zelda/zh340/.claude/projects/-scra⌘ Bash
•STEP #4
Done — `Room.py` now carries the full harness layer for PSi_2_meeting-Zhening and rebuilds cleanly (every render pass re-executed it in Blender withou
All render-vs-real montages12 more
wall · wall_frame_00005
wall · wall_frame_00012
wall · wall_frame_00018
wall · wall_frame_00032
wall · wall_frame_00044
wall · wall_frame_00048
wall · wall_frame_00052
wall · wall_frame_00056
wall_focus · Wall3_frame_00020
wall_focus · Wall3_frame_00022
wall_focus · Wall3_frame_00024
wall_focus · Wall3_frame_00044Authoring — model summary
Done — `Room.py` now carries the full harness layer for PSi_2_meeting-Zhening and rebuilds cleanly (every render pass re-executed it in Blender without error; the hardlinked output copy stays in sync).
## Job 1 — Shell + materials
- **Structure**: checked against photos — walls, both door openings and heights all match the scan; no SHELL edits needed. (Wall9 is a dangling scan sliver at the pipe corner; left as-is, no fixtures on it.)
- **Materials**: all 13 walls warm cream paint; floor gets a real Poly Haven carpet PBR recoloured to slate-blue (`materials/floor_carpet_*`); new `Ceiling0` mesh built over the 11-vertex boundary in cream plaster. Verified on a bare-ish pass before fixtures went in.
## Job 2 — Fixtures (all multi-part, all grouped via `group_fixture`)
| Wall | Group | Parts |
|---|---|---|
| all round | `Trunking0` | channel + proud lid per free span, breaks at both doors |
| all round | `Skirting0` | proud white boards per span |
| Wall0 | `Conduit0` | ceiling-level pipe + 6 saddle clips + junction box + Wall8 continuation |
| Wall0 | `AC0` | body + recessed panel + 2 dark-backed side louvre columns (9 slats each) + 11-slat bottom grille + 6 top slats + drain pipe/box |
| Wall0 | `FireAlarm0` | red call point + glass face + white sounder |
| Wall0 | `Socket0–3` | trunking-mounted body + 2 raised outlets each |
| Wall0 | `Pipes0` | twin heating pipes + clips, continuing across Wall8 |
| Wall0 | `WhiteboardM0` | freestanding board: face, 4 frame bars, 2 legs, 2 feet, tray, yawed off-wall |
| Wall1 | `Whiteboard1` + `WhiteboardSill0` | 4 frame bars, face, full-width tray + lip, white mounting sill |
| Wall1 | `Conduit1`, `Socket4–8`, `PinBoard0` | conduit + clips + round junction; 5 sockets; blue-felt pinboard leaning at 52° with 4 proud beech frame bars |
| Wall2 | `Whiteboard2` | near-full-wall board + tray + red eraser + marker |
| Wall3 | `FloorLamp0` | base disc, pole, bowl, rim |
| Wall7 | `Panel0`, `Cable0` | panelled services cladding (carcass, inset, 3 frame bars, holder); dropped cable |
| Wall8 | `Pipe0` | vertical boxed riser + 3 brackets |
| Wall12 | `Clock0`, `Socket9–10` | rim, face, two hands; sockets under the TV |
| Ceiling | `Beam0`, `Projector0`, `Light0/1`, `Smoke0` | downstand beam + arris; plate/pole/knuckle/body/lens/vent; two twin-tube battens (body, emissive diffuser, end caps); detector base + dome |
**Render-verified** with side-by-side render|photo pairs on frames 5, 47 (Wall1), 50 (Wall2), 52 (Wall7/12), 55 (Wa
✓ [3/6] one-shot authoring · 25m08s
⊘ [4/6] per-object refinement — skipped (SKIP_REFINE=1)
⊘ [5/6] per-wall fixture refinement — skipped (REFINE_WALLS=0)
▶ [6/6] export three.js viewer (0 stages left)
=== RoomScene.build (blank) ===
shell <- SHELL(compact): 13 walls, 2 openings, 11 objects(无 usdz)
thickened 13 walls outward to ~0.1 m
cut 2/2 openings {'Door0': 'Wall1', 'Door1': 'Wall12'}
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node Top
13:59:01 | INFO: glTF import finished in 0.01s
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node Cube
13:59:01 | INFO: glTF import finished in 0.00s
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node Cube
13:59:01 | INFO: Blender create Mesh node Cube.010
13:59:01 | INFO: Blender create Mesh node Cube.013
13:59:01 | INFO: Blender create Mesh node Cube.007
13:59:01 | INFO: Blender create Mesh node Cube.008
13:59:01 | INFO: Blender create Mesh node Cube.009
13:59:01 | INFO: Blender create Mesh node Cube.006
13:59:01 | INFO: glTF import finished in 0.01s
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node Cube
13:59:01 | INFO: Blender create Mesh node Cube.007
13:59:01 | INFO: glTF import finished in 0.00s
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node geometry_0
13:59:01 | INFO: glTF import finished in 0.01s
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node geometry_0
13:59:01 | INFO: glTF import finished in 0.01s
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node geometry_0
13:59:01 | INFO: glTF import finished in 0.01s
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node geometry_0
13:59:01 | INFO: glTF import finished in 0.01s
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node geometry_0
13:59:01 | INFO: glTF import finished in 0.01s
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node geometry_0
13:59:01 | INFO: glTF import finished in 0.01s
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node geometry_0
13:59:01 | INFO: glTF import finished in 0.01s
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node geometry_0
13:59:01 | INFO: glTF import finished in 0.01s
13:59:01 | INFO: Data are loaded, start creating Blender stuff
13:59:01 | INFO: Blender create Mesh node geometry_0
13:59:01 | INFO: glTF import finished in 0.01s
placed 13 asset instances
built 0 capture cameras
indexed 27 objects, 0 cameras
floor-grounded 0 visible piece(s)
harness stage: materials + ceiling + fixtures built
=== ROOM SCENE (blank) ===
ac_unit : AC0
beam : Beam0
cable : Cable0
cable_trunking: Trunking0
ceiling : Ceiling0
ceiling_light: Light0, Light1
chair : Chair0, Chair1, Chair2, Chair3, Chair4, Chair5, Chair6, Chair7, Chair8
clock : Clock0
conduit : Conduit0, Conduit1
door : Door0, Door1
fire_alarm: FireAlarm0
floor : Floor0
floor_lamp: FloorLamp0
notice_board: PinBoard0
panel : Panel0
pipe : Pipe0, Pipes0
projector: Projector0
sill : WhiteboardSill0
skirting : Skirting0
smoke_detector: Smoke0
socket : Socket0, Socket1, Socket10, Socket2, Socket3, Socket4, Socket5, Socket6, Socket7, Socket8, Socket9
table : Table0
television: Television0
wall : Wall0, Wall1, Wall10, Wall11, Wall12, Wall2, Wall3, Wall4, Wall5, Wall6, Wall7, Wall8, Wall9
whiteboard: Whiteboard1, Whiteboard2, WhiteboardM0
placeable surfaces (top z):
Table0 z=-0.574 center=[-1.5195, 0.7541, -0.9387]
cameras: none
INFO Draco mesh compression is available, use library at /scratch2/zh340/blender-4.5.1-linux-x64/4.5/scripts/addons_core/io_scene_gltf2/libextern_draco.so
13:59:01 | INFO: Starting glTF 2.0 export
13:59:01 | INFO: Extracting primitive: Floor0
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall0
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall1
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall10
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall11
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall12
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall2
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall3
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall4
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall5
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall6
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall7
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall8
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Wall9
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Ceiling0
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: Top
13:59:02 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
13:59:02 | INFO: Primitives created: 2
13:59:02 | INFO: Extracting primitive: Cube
13:59:02 | INFO: Primitives created: 4
13:59:02 | INFO: Extracting primitive: geometry_0
13:59:02 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: geometry_0.001
13:59:02 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: geometry_0.002
13:59:02 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: geometry_0.003
13:59:02 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: geometry_0.004
13:59:02 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
13:59:02 | INFO: Primitives created: 1
13:59:02 | INFO: Extracting primitive: geometry_0.005
13:59:03 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: geometry_0.006
13:59:03 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: geometry_0.007
13:59:03 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: geometry_0.008
13:59:03 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Cube.001
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Cube.010
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Cube.013
13:59:03 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
13:59:03 | INFO: Primitives created: 3
13:59:03 | INFO: Extracting primitive: Cube.007
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Cube.008
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Cube.009
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Cube.006
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Cube.002
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Cube.003
13:59:03 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
13:59:03 | INFO: Primitives created: 3
13:59:03 | INFO: Extracting primitive: Trunking0_Wall0_ch0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall0_lid0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall10_ch0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall10_lid0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall11_ch0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall11_lid0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall12_ch0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall12_ch1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall12_lid0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall12_lid1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall1_ch0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall1_ch1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall1_lid0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall1_lid1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall2_ch0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall2_lid0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall3_ch0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall3_lid0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall4_ch0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall4_lid0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall5_ch0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall5_lid0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall6_ch0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall6_lid0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall7_ch0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall7_lid0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall8_ch0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Trunking0_Wall8_lid0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall0_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall10_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall11_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall12_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall12_1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall1_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall1_1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall2_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall3_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall4_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall5_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall6_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall7_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Skirting0_Wall8_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit0_clip0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit0_clip1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit0_clip2
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit0_clip3
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit0_clip4
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit0_junction
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit0_pipe
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit0_pipe_w8
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: FireAlarm0_box
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: FireAlarm0_glass
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: FireAlarm0_sounder
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_bot_back
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_bot_slat0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_bot_slat1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_bot_slat10
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_bot_slat2
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_bot_slat3
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_bot_slat4
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_bot_slat5
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_bot_slat6
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_bot_slat7
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_bot_slat8
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_bot_slat9
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_drain
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_drain_box
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre0_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre0_1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre0_2
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre0_3
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre0_4
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre0_5
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre0_6
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre0_7
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre0_8
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre1_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre1_1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre1_2
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre1_3
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre1_4
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre1_5
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre1_6
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre1_7
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre1_8
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre_back0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_louvre_back1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_panel
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_top_slat0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_top_slat1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_top_slat2
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_top_slat3
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_top_slat4
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: AC0_top_slat5
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket0_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket0_outlet0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket0_outlet1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket1_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket1_outlet0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket1_outlet1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket2_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket2_outlet0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket2_outlet1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket3_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket3_outlet0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket3_outlet1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Pipes0_clip0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Pipes0_clip1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Pipes0_clip2
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Pipes0_clip3
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Pipes0_run0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Pipes0_run1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Pipes0_w8_0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Pipes0_w8_1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: WhiteboardM0_bar_bot
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: WhiteboardM0_bar_l
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: WhiteboardM0_bar_r
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: WhiteboardM0_bar_top
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: WhiteboardM0_face
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: WhiteboardM0_foot_l
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: WhiteboardM0_foot_r
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: WhiteboardM0_leg_l
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: WhiteboardM0_leg_r
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: WhiteboardM0_tray
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit1_clip0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit1_clip1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit1_clip2
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit1_clip3
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit1_junction
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Conduit1_pipe
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard1_bar_bot
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard1_bar_l
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard1_bar_r
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard1_bar_top
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard1_face
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard1_tray
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard1_tray_lip
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard1_sill
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket4_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket4_outlet0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket4_outlet1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket5_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket5_outlet0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket5_outlet1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket6_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket6_outlet0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket6_outlet1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket7_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket7_outlet0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket7_outlet1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket8_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket8_outlet0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket8_outlet1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: PinBoard0_felt
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: PinBoard0_frame_bot
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: PinBoard0_frame_l
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: PinBoard0_frame_r
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: PinBoard0_frame_top
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard2_bar_bot
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard2_bar_l
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard2_bar_r
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard2_bar_top
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard2_eraser
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard2_face
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard2_marker
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard2_tray
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Whiteboard2_tray_lip
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: FloorLamp0_base
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: FloorLamp0_bowl
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: FloorLamp0_bowl_rim
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: FloorLamp0_pole
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Panel0_carcass
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Panel0_frame_l
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Panel0_frame_r
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Panel0_frame_top
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Panel0_holder
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Panel0_inset
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Cable0_drop
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Cable0_floor
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Pipe0_bracket0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Pipe0_bracket1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Pipe0_bracket2
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Pipe0_riser
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Clock0_face
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Clock0_hand_h
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Clock0_hand_m
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Clock0_rim
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket9_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket9_outlet0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket9_outlet1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket10_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket10_outlet0
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Socket10_outlet1
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Beam0_arris
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Beam0_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Projector0_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Projector0_knuckle
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Projector0_lens
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Projector0_plate
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Projector0_pole
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Projector0_vent
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Light0_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Light0_cap_a
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Light0_cap_b
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Light0_diffuser
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Light1_body
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Light1_cap_a
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Light1_cap_b
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Light1_diffuser
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Smoke0_base
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Extracting primitive: Smoke0_dome
13:59:03 | INFO: Primitives created: 1
13:59:03 | INFO: Finished glTF 2.0 export in 1.9470794200897217 s
EXPORTED -> /scratch2/code/LiteReality_to_share/agentic_litereality/output/PSi_2_meeting-Zhening/scene_stage/_oneshot/room_preview/Room.glb (15272 KB)
LAYOUT -> /scratch2/code/LiteReality_to_share/agentic_litereality/output/PSi_2_meeting-Zhening/scene_stage/_oneshot/room_preview/room_layout.json (61 objects, 0 cameras)
Info: Saved as "Room.blend"
BLEND -> /scratch2/code/LiteReality_to_share/agentic_litereality/output/PSi_2_meeting-Zhening/scene_stage/_oneshot/room_preview/Room.blend (40864 KB)
COPIED -> /scratch2/code/LiteReality_to_share/agentic_litereality/output/PSi_2_meeting-Zhening/scene_stage/_oneshot/room_preview/build_room.py
DONE
Blender 4.5.1 LTS (hash b0a72b245dcf built 2025-07-29 06:24:35)
Blender quit
=== 物化 6 个物体 ===
tex Table0: 重建 3 张贴图
regen Table0 (object.py)
regen Television0 (object.py)
tex Wall12_Door_1: 重建 4 张贴图
regen Wall12_Door_1 (object.py)
tex Wall1_Door_0: 重建 4 张贴图
regen Wall1_Door_0 (object.py)
wrap ChairCluster0 (object.py)
wrap ChairCluster1 (object.py)
=== 装配(无 frames(仅几何),外壳来自 Room.py 的 SHELL)-> Room.glb + Room.blend ===
物化 6/6 个物体
Room_preview -> /scratch2/code/LiteReality_to_share/agentic_litereality/output/PSi_2_meeting-Zhening/scene_stage/_oneshot/room_preview
viewer → /scratch2/code/LiteReality_to_share/agentic_litereality/final_results/PSi_2_meeting-Zhening/PSi_2_meeting-Zhening.html (8.8 MB, self-contained)
final_results → /scratch2/code/LiteReality_to_share/agentic_litereality/final_results/PSi_2_meeting-Zhening
✓ [6/6] export three.js viewer · 9s
╭────────── PSi_2_meeting-Zhening ──────────╮
✓ init (skipped) 0s
✓ stitches (skipped) 0s
✓ one-shot authoring 25m08s
✓ obj refine (skipped) 0s
✓ wall refine (skipped) 0s
✓ export three.js viewer 9s
total 25m17s
viewer /scratch2/code/LiteReality_to_share/agentic_litereality/final_results/PSi_2_meeting-Zhening/PSi_2_meeting-Zhening.html
room /scratch2/code/LiteReality_to_share/agentic_litereality/output/PSi_2_meeting-Zhening/scene_stage/_oneshot/room/Room.py
╰────────────────────────────────────────────────╯
② Code changes
Room.py diffseed (blank shell) → authored
+631 −0 lines · seed → authored Room.py
@@ -1163,4 +1163,6 @@
def main():
scene = RoomScene().build()
+ harness_stage(scene) # materials + ceiling + wall fixtures (defined below)
+ scene.index()
scene.summary()
scene.export_glb()
@@ -2055,4 +2057,633 @@
+# ===========================================================================
+# HARNESS STAGE — materials for every surface, the missing ceiling, and the
+# wall fixtures the photos show. Every fixture is a MULTI-PART assembly
+# bundled into ONE named hide-as-a-unit group via group_fixture().
+#
+# Reference notes (from surface_ref stitches + raw frames):
+# cream painted walls all round; blue carpet tiles; cream plaster ceiling
+# crossed by a boxed DOWNSTAND BEAM parallel to Wall0 (projector hangs off
+# it); white dado trunking at ~0.90 m around the room; white skirting; twin
+# heating pipes along the base of Wall0/Wall8; grey conduit along the tops
+# of Wall0/Wall8/Wall1; wall-hung whiteboards on Wall1 + Wall2; freestanding
+# whiteboard near Wall0; blue pinboard leaning low against Wall1; white AC
+# unit high on Wall0 (west end); clock high on Wall12; two surface-mounted
+# twin fluorescents. NOTE the Wall0 stitch is MIRRORED vs reality — all
+# Wall0 u-coords below are in REAL wall coords (u=0 at the Wall12 corner).
+# ===========================================================================
+MAT_DIR = os.path.join(HERE, "materials")
+FLOOR_Z = SHELL["floor_z"]
+CEIL_Z = SHELL["ceiling_z"]
+WALL_H = CEIL_Z - FLOOR_Z # 2.9702 m
+GRID_YAW = 164.03 # beam/luminaires run parallel to Wall0
+
+
+def _hex(h):
+ h = h.lstrip("#")
+ return tuple(int(h[i:i + 2], 16) / 255.0 for i in (0, 2, 4))
+
+
+def _pbr(name, hexcol, rough=0.8, metal=0.0, emit=0.0):
+ mat = bpy.data.materials.get(name)
+ if mat is not None:
+ return mat
+ mat = bpy.data.materials.new(name)
+ mat.use_nodes = True
+ nt = mat.node_tree
+ nt.nodes.clear()
+ out = nt.nodes.new("ShaderNodeOutputMaterial")
+ b = nt.nodes.new("ShaderNodeBsdfPrincipled")
+ nt.links.new(b.outputs["BSDF"], out.inputs["Surface"])
+ lin = srgb_to_linear(_hex(hexcol))
+ b.inputs["Base Color"].default_value = (*lin, 1.0)
+ b.inputs["Roughness"].default_value = rough
+ if "Metallic" in b.inputs:
+ b.inputs["Metallic"].default_value = metal
+ if emit: # gentle self-emission so bright surfaces read bright in renders
+ ec = "Emission Color" if "Emission Color" in b.inputs else "Emission"
+ b.inputs[ec].default_value = (*lin, 1.0)
+ if "Emission Strength" in b.inputs:
+ b.inputs["Emission Strength"].default_value = emit
+ return mat
+
+
+def _emit(name, hexcol, strength):
+ mat = bpy.data.materials.get(name)
+ if mat is not None:
+ return mat
+ mat = bpy.data.materials.new(name)
+ mat.use_nodes = True
+ nt = mat.node_tree
+ nt.nodes.clear()
+ out = nt.nodes.new("ShaderNodeOutputMaterial")
+ e = nt.nodes.new("ShaderNodeEmission")
+ e.inputs["Color"].default_value = (*srgb_to_linear(_hex(hexcol)), 1.0)
+ e.inputs["Strength"].default_value = strength
+ nt.links.new(e.outputs["Emission"], out.inputs["Surface"])
+ return mat
+
+
+def _tex_pbr(name, base, metres=2.0, rough=0.8):
+ """Image PBR (materials/<base>_{diff,rough,normal}.jpg) box-projected in
+ world metres — shell meshes carry no UVs, so Object coords do the mapping."""
+ mat = bpy.data.materials.get(name)
+ if mat is not None:
+ return mat
+ mat = bpy.data.materials.new(name)
+ mat.use_nodes = True
+ nt = mat.node_tree
+ nt.nodes.clear()
+ out = nt.nodes.new("ShaderNodeOutputMaterial")
+ b = nt.nodes.new("ShaderNodeBsdfPrincipled")
+ nt.links.new(b.outputs["BSDF"], out.inputs["Surface"])
+ tc = nt.nodes.new("ShaderNodeTexCoord")
+ mp = nt.nodes.new("ShaderNodeMapping")
+ mp.inputs["Scale"].default_value = (1.0 / metres,) * 3
+ nt.links.new(tc.outputs["Object"], mp.inputs["Vector"])
+
+ def img(suffix, noncolor):
+ p = os.path.join(MAT_DIR, f"{base}_{suffix}.jpg")
+ if not os.path.exists(p):
+ return None
+ n = nt.nodes.new("ShaderNodeTexImage")
+ n.image = bpy.data.images.load(p, check_existing=True)
+ n.projection = "BOX"
+ n.projection_blend = 0.3
+ if noncolor:
+ n.image.colorspace_settings.name = "Non-Color"
+ nt.links.new(mp.outputs["Vector"], n.inputs["Vector"])
+ return n
+
+ d = img("diff", False)
+ if d:
+ nt.links.new(d.outputs["Color"], b.inputs["Base Color"])
+ r = img("rough", True)
+ if r:
+ nt.links.new(r.outputs["Color"], b.inputs["Roughness"])
+ else:
+ b.inputs["Roughness"].default_value = rough
+ nm = img("normal", True)
+ if nm:
+ nmap = nt.nodes.new("ShaderNodeNormalMap")
+ nt.links.new(nm.outputs["Color"], nmap.inputs["Color"])
+ nt.links.new(nmap.outputs["Normal"], b.inputs["Normal"])
+ return mat
+
+
+def _room_centre2d():
+ pts = [w["start"] for w in SHELL["walls"].values()]
+ return (sum(p[0] for p in pts) / len(pts), sum(p[1] for p in pts) / len(pts))
+
+
+def _wall_frame2(wall):
+ """(start, along-dir, INTERIOR normal, length) of a SHELL wall on the floor plane."""
+ w = SHELL["walls"][wall]
+ s = Vector((w["start"][0], w["start"][1], 0.0))
+ e = Vector((w["end"][0], w["end"][1], 0.0))
+ d = e - s
+ L = d.length
+ d = d / L
+ n = Vector((-d.y, d.x, 0.0))
+ cx, cy = _room_centre2d()
+ if n.dot(Vector((cx, cy, 0.0)) - s) < 0:
+ n = -n
+ return s, d, n, L
+
+
+def _mk_box3(name, center, ax, al, ay, ayl, az, azl, mat=None):
+ """A box with THREE arbitrary (unit) axes ax/ay/az and lengths al/ayl/azl."""
+ me = _unit_cube_mesh(name)
+ ob = bpy.data.objects.new(name, me)
+ cx, cy, cz = center
+ ob.matrix_world = Matrix(
+ (
+ (ax.x * al, ay.x * ayl, az.x * azl, cx),
+ (ax.y * al, ay.y * ayl, az.y * azl, cy),
+ (ax.z * al, ay.z * ayl, az.z * azl, cz),
+ (0.0, 0.0, 0.0, 1.0),
+ )
+ )
+ bpy.context.scene.collection.objects.link(ob)
+ if mat is not None:
+ ob.data.materials.append(mat)
+ return ob
+
+
+_Z = Vector((0.0, 0.0, 1.0))
+
+
+def _mk_box(name, center, xd, xl, yd, yl, zl, mat=None):
+ return _mk_box3(name, center, xd, xl, yd, yl, _Z, zl, mat)
+
+
+def _wall_box(wall, u, z, w, h, depth, mat, proud=0.0, name="part"):
+ """A box mounted ON a wall. u = centre along the wall from its start (m);
+ z = centre height above the FLOOR; w×h face size; depth = thickness off the
+ wall; the back face sits `proud` metres inside the interior wall face."""
+ s, d, n, L = _wall_frame2(wall)
+ c = s + d * u + n * (proud + depth / 2.0)
+ return _mk_box(name, (c.x, c.y, FLOOR_Z + z), d, w, n, depth, h, mat)
+
+
+def _lean_box(wall, u, z_mid, w, h, depth, mat, tilt_deg, gap=0.0, name="part"):
+ """A box LEANING against a wall: tilted `tilt_deg` off vertical about the
+ wall-parallel axis, top toward the wall. z_mid = centre height above floor
+ measured along the tilted axis; gap = clearance of the top edge off the wall."""
+ s, d, n, L = _wall_frame2(wall)
+ t = math.radians(tilt_deg)
+ up = Vector((-n.x * math.sin(t), -n.y * math.sin(t), math.cos(t)))
+ fn = Vector((n.x * math.cos(t), n.y * math.cos(t), math.sin(t)))
+ base = s + d * u + n * (gap + h * math.sin(t) + depth / 2.0)
+ c = Vector((base.x, base.y, FLOOR_Z)) + up * z_mid
+ return _mk_box3(name, (c.x, c.y, c.z), d, w, fn, depth, up, h, mat)
+
+
+def _yaw_dirs(yaw_deg):
+ r = math.radians(yaw_deg)
+ return (Vector((math.cos(r), math.sin(r), 0.0)),
+ Vector((-math.sin(r), math.cos(r), 0.0)))
+
+
+def _free_box(name, cx, cy, z, sx, sy, sz, mat, yaw=GRID_YAW):
+ """Free box at world (cx,cy), z = centre height above the floor, yawed."""
+ xd, yd = _yaw_dirs(yaw)
+ return _mk_box(name, (cx, cy, FLOOR_Z + z), xd, sx, yd, sy, sz, mat)
+
+
+def group_fixture(name, category, parts):
+ """Bundle a fixture's parts into ONE named hide-as-a-unit group: an empty
+ with room_id/category props, every part parented to it, all of it living in
+ a collection named after the fixture (selects/hides/toggles like Table0)."""
+ grp = bpy.data.objects.new(name, None)
+ grp.empty_display_size = 0.1
+ grp["room_id"] = name
+ grp["category"] = category
+ coll = get_or_make_collection(name)
+ coll.objects.link(grp)
+ for p in parts:
+ p.parent = grp
+ p.matrix_parent_inverse = grp.matrix_world.inverted()
+ move_to_collection(p, coll)
+ return grp
+
+
+def _build_ceiling(mat):
+ order = ["Wall0", "Wall8", "Wall1", "Wall6", "Wall4", "Wall2",
+ "Wall5", "Wall7", "Wall10", "Wall3", "Wall12"] # boundary cycle
+ verts = [(SHELL["walls"][w]["start"][0], SHELL["walls"][w]["start"][1], CEIL_Z)
+ for w in order]
+ me = bpy.data.meshes.new("Ceiling0")
+ me.from_pydata(verts, [], [tuple(range(len(verts)))])
+ me.update()
+ ob = bpy.data.objects.new("Ceiling0", me)
+ bpy.context.scene.collection.objects.link(ob)
+ ob.data.materials.append(mat)
+ move_to_collection(ob, get_or_make_collection(COLL_SHELL))
+ return ob
+
+
+# --------------------------------------------------------------------- shared mats
+def _mats():
+ return {
+ "alu": _pbr("mat_alu", "#ced1d4", rough=0.35, metal=0.75),
+ "white_pvc": _pbr("mat_white_pvc", "#eef0eb", rough=0.5, emit=0.08),
+ "board": _pbr("mat_whiteboard", "#f8f9f8", rough=0.25, emit=0.35),
+ "socket": _pbr("mat_socket", "#f0f0ea", rough=0.5),
+ "conduit": _pbr("mat_conduit_grey", "#63666a", rough=0.4, metal=0.6),
+ "pipe": _pbr("mat_pipe_white", "#e6e6df", rough=0.55),
+ "red": _pbr("mat_red", "#b32427", rough=0.5),
+ "black": _pbr("mat_black", "#2a2b2c", rough=0.6),
+ "dark": _pbr("mat_dark_grey", "#55575a", rough=0.55),
+ "wood": _pbr("mat_pin_wood", "#b9884e", rough=0.6),
+ "felt": _pbr("mat_pin_felt", "#5a6b85", rough=0.95),
+ "grille": _pbr("mat_grille", "#cdd0ca", rough=0.6),
+ "grille_dark": _pbr("mat_grille_dark", "#8f938e", rough=0.7),
+ "cream": _pbr("mat_beam_cream", "#f1ebd9", rough=0.9),
+ "clockface": _pbr("mat_clock_face", "#f4f4f0", rough=0.4),
+ "led": _emit("mat_led_tube", "#f5f7f2", 14.0),
+ }
+
+
+M = {}
+
+
+# ------------------------------------------------------- skirting + dado trunking
+def _skirting_and_trunking(scene):
+ """White skirting + white dado trunking (channel + proud lid) around the
+ room, broken at the two doors via the SHELL free spans."""
+ walls = ["Wall0", "Wall1", "Wall2", "Wall3", "Wall4", "Wall5", "Wall6",
+ "Wall7", "Wall8", "Wall10", "Wall11", "Wall12"]
+ trunk, skirt = [], []
+ for wnm in walls:
+ for k, (a, b) in enumerate(scene._wall_free_spans(wnm)):
+ u, w = (a + b) / 2.0, b - a
+ if w < 0.04:
+ continue
+ trunk.append(_wall_box(wnm, u, 0.90, w, 0.10, 0.045, M["white_pvc"],
+ name=f"Trunking0_{wnm}_ch{k}"))
+ trunk.append(_wall_box(wnm, u, 0.90, w, 0.082, 0.016, M["white_pvc"],
+ proud=0.045, name=f"Trunking0_{wnm}_lid{k}"))
+ skirt.append(_wall_box(wnm, u, 0.05, w, 0.10, 0.015, M["white_pvc"],
+ name=f"Skirting0_{wnm}_{k}"))
+ group_fixture("Trunking0", "cable_trunking", trunk)
+ group_fixture("Skirting0", "skirting", skirt)
+
+
+def _socket(idx, wall, u, z=0.90):
+ """Trunking-mounted double socket = box body on the lid + 2 raised outlets."""
+ parts = [_wall_box(wall, u, z, 0.148, 0.088, 0.030, M["socket"], proud=0.061,
+ name=f"Socket{idx}_body")]
+ for k, du in enumerate((-0.034, 0.034)):
+ parts.append(_wall_box(wall, u + du, z, 0.038, 0.044, 0.008, M["socket"],
+ proud=0.091, name=f"Socket{idx}_outlet{k}"))
+ return group_fixture(f"Socket{idx}", "socket", parts)
+
+
+def _whiteboard(idx, wall, u0, u1, z0, z1, eraser_u=None, marker_u=None):
+ """Wall-hung whiteboard = 4 alu frame bars + near-flush face + pen tray
+ (base + lip) + optional eraser/marker sitting on the tray."""
+ parts = []
+ uc, zc = (u0 + u1) / 2, (z0 + z1) / 2
+ w, h = u1 - u0, z1 - z0
+ fb = 0.032
+ parts.append(_wall_box(wall, uc, zc, w - 0.05, h - 0.05, 0.014, M["board"],
+ proud=0.016, name=f"Whiteboard{idx}_face"))
+ parts.append(_wall_box(wall, uc, z1 - fb / 2, w, fb, 0.028, M["alu"], proud=0.012,
+ name=f"Whiteboard{idx}_bar_top"))
+ parts.append(_wall_box(wall, uc, z0 + fb / 2, w, fb, 0.028, M["alu"], proud=0.012,
+ name=f"Whiteboard{idx}_bar_bot"))
+ parts.append(_wall_box(wall, u0 + fb / 2, zc, fb, h - 2 * fb, 0.028, M["alu"],
+ proud=0.012, name=f"Whiteboard{idx}_bar_l"))
+ parts.append(_wall_box(wall, u1 - fb / 2, zc, fb, h - 2 * fb, 0.028, M["alu"],
+ proud=0.012, name=f"Whiteboard{idx}_bar_r"))
+ parts.append(_wall_box(wall, uc, z0 - 0.028, w * 0.9, 0.02, 0.09, M["alu"],
+ proud=0.008, name=f"Whiteboard{idx}_tray"))
+ parts.append(_wall_box(wall, uc, z0 - 0.008, w * 0.9, 0.038, 0.014, M["alu"],
+ proud=0.092, name=f"Whiteboard{idx}_tray_lip"))
+ if eraser_u is not None:
+ parts.append(_wall_box(wall, eraser_u, z0 - 0.012, 0.11, 0.04, 0.055, M["red"],
+ proud=0.022, name=f"Whiteboard{idx}_eraser"))
+ if marker_u is not None:
+ parts.append(_wall_box(wall, marker_u, z0 - 0.03, 0.12, 0.016, 0.016,
+ M["black"], proud=0.03, name=f"Whiteboard{idx}_marker"))
+ return group_fixture(f"Whiteboard{idx}", "whiteboard", parts)
+
+
+# --------------------------------------------------------------------- Wall0
+def _fixtures_wall0():
+ s, d, n, L = _wall_frame2("Wall0")
+ # grey steel conduit near the ceiling, saddle clips + a junction box; the
+ # run continues around the Wall8 chamfer to Wall1 (Conduit1)
+ parts = [_wall_box("Wall0", L / 2, 2.78, L, 0.034, 0.034, M["conduit"],
+ proud=0.012, name="Conduit0_pipe")]
+ u = 0.45
+ k = 0
+ while u < L - 0.2:
+ parts.append(_wall_box("Wall0", u, 2.78, 0.05, 0.048, 0.044, M["conduit"],
+ proud=0.008, name=f"Conduit0_clip{k}"))
+ u += 0.75
+ k += 1
+ parts.append(_wall_box("Wall0", 2.58, 2.78, 0.11, 0.09, 0.055, M["white_pvc"],
+ proud=0.006, name="Conduit0_junction"))
+ s8, d8, n8, L8 = _wall_frame2("Wall8")
+ parts.append(_wall_box("Wall8", L8 / 2, 2.78, L8 + 0.1, 0.034, 0.034,
+ M["conduit"], proud=0.012, name="Conduit0_pipe_w8"))
+ group_fixture("Conduit0", "conduit", parts)
+ # red fire-alarm call point + small white sounder base beside the junction
+ al = [
+ _wall_box("Wall0", 2.58, 2.55, 0.092, 0.092, 0.05, M["red"],
+ name="FireAlarm0_box"),
+ _wall_box("Wall0", 2.58, 2.55, 0.055, 0.05, 0.012, M["clockface"],
+ proud=0.05, name="FireAlarm0_glass"),
+ _wall_box("Wall0", 2.32, 2.46, 0.10, 0.10, 0.045, M["white_pvc"],
+ name="FireAlarm0_sounder"),
+ ]
+ group_fixture("FireAlarm0", "fire_alarm", al)
+ # white AC / vent unit high on the WEST end: body + recessed centre panel +
+ # two side louvre columns + top grille slats + drain kit below-right
+ acu, acz = 2.95, 2.11 # centre u / z ; 0.81 x 0.58 body
+ parts = [_wall_box("Wall0", acu, acz, 0.81, 0.58, 0.20, M["white_pvc"],
+ name="AC0_body"),
+ _wall_box("Wall0", acu, acz + 0.01, 0.40, 0.38, 0.012, M["grille"],
+ proud=0.20, name="AC0_panel")]
+ for side, su in ((0, acu - 0.315), (1, acu + 0.315)):
+ parts.append(_wall_box("Wall0", su, acz, 0.15, 0.50, 0.008, M["grille_dark"],
+ proud=0.198, name=f"AC0_louvre_back{side}"))
+ for i in range(9):
+ parts.append(_wall_box("Wall0", su, acz - 0.22 + i * 0.052, 0.14, 0.022,
+ 0.03, M["grille"], proud=0.202,
+ name=f"AC0_louvre{side}_{i}"))
+ parts.append(_wall_box("Wall0", acu, acz - 0.245, 0.66, 0.09, 0.008,
+ M["grille_dark"], proud=0.198, name="AC0_bot_back"))
+ for i in range(11):
+ parts.append(_wall_box("Wall0", acu - 0.30 + i * 0.06, acz - 0.245, 0.024,
+ 0.09, 0.028, M["grille"], proud=0.202,
+ name=f"AC0_bot_slat{i}"))
+ for i in range(6):
+ parts.append(_wall_box("Wall0", acu - 0.30 + i * 0.12, acz + 0.245, 0.10,
+ 0.05, 0.035, M["grille"], proud=0.195,
+ name=f"AC0_top_slat{i}"))
+ parts.append(_wall_box("Wall0", acu + 0.33, acz - 0.36, 0.035, 0.14, 0.035,
+ M["pipe"], proud=0.02, name="AC0_drain"))
+ parts.append(_wall_box("Wall0", acu + 0.42, acz - 0.47, 0.09, 0.07, 0.05,
+ M["white_pvc"], proud=0.01, name="AC0_drain_box"))
+ group_fixture("AC0", "ac_unit", parts)
+ # trunking-mounted sockets on the east half (mostly behind the mobile board)
+ _socket(0, "Wall0", 1.25)
+ _socket(1, "Wall0", 1.55)
+ _socket(2, "Wall0", 1.85)
+ _socket(3, "Wall0", 2.15)
+ # twin white heating pipes along the base (photo: west half, running to the
+ # Wall8 corner), with fixing clips
+ parts = []
+ for j, pz in enumerate((0.135, 0.205)):
+ parts.append(_wall_box("Wall0", L / 2, pz, L, 0.032, 0.032, M["pipe"],
+ proud=0.02, name=f"Pipes0_run{j}"))
+ parts.append(_wall_box("Wall8", L8 / 2, pz, L8 + 0.08, 0.032, 0.032,
+ M["pipe"], proud=0.02, name=f"Pipes0_w8_{j}"))
+ u = 0.6
+ k = 0
+ while u < L - 0.3:
+ parts.append(_wall_box("Wall0", u, 0.17, 0.045, 0.135, 0.03, M["pipe"],
+ proud=0.015, name=f"Pipes0_clip{k}"))
+ u += 1.1
+ k += 1
+ group_fixture("Pipes0", "pipe", parts)
+ # freestanding whiteboard standing just off the east half of Wall0,
+ # slightly yawed (frames 55/57): legs + frame + face + tray
+ yaw = GRID_YAW + 7.0
+ xd, yd = _yaw_dirs(yaw)
+ c = s + d * 1.90 + n * 0.32
+ cx, cy = c.x, c.y
+
+ def _wb(name, dx, z, sx, sy, sz, mat):
+ return _mk_box(name, (cx + xd.x * dx - yd.x * 0.0, cy + xd.y * dx, FLOOR_Z + z),
+ xd, sx, yd, sy, sz, mat)
+
+ parts = [
+ _wb("WhiteboardM0_face", 0.0, 1.22, 1.32, 0.018, 1.52, M["board"]),
+ _wb("WhiteboardM0_bar_top", 0.0, 1.985, 1.38, 0.03, 0.035, M["alu"]),
+ _wb("WhiteboardM0_bar_bot", 0.0, 0.455, 1.38, 0.03, 0.035, M["alu"]),
+ _wb("WhiteboardM0_bar_l", -0.675, 1.22, 0.035, 0.03, 1.50, M["alu"]),
+ _wb("WhiteboardM0_bar_r", 0.675, 1.22, 0.035, 0.03, 1.50, M["alu"]),
+ _wb("WhiteboardM0_leg_l", -0.62, 0.24, 0.04, 0.05, 0.48, M["dark"]),
+ _wb("WhiteboardM0_leg_r", 0.62, 0.24, 0.04, 0.05, 0.48, M["dark"]),
+ _wb("WhiteboardM0_foot_l", -0.62, 0.02, 0.05, 0.44, 0.04, M["dark"]),
+ _wb("WhiteboardM0_foot_r", 0.62, 0.02, 0.05, 0.44, 0.04, M["dark"]),
+ ]
+ tray = _mk_box("WhiteboardM0_tray",
+ (cx - yd.x * 0.04, cy - yd.y * 0.04, FLOOR_Z + 0.43),
+ xd, 0.80, yd, 0.06, 0.018, M["alu"])
+ parts.append(tray)
+ group_fixture("WhiteboardM0", "whiteboard", parts)
+
+
+# --------------------------------------------------------------------- Wall1
+def _fixtures_wall1():
+ s, d, n, L = _wall_frame2("Wall1")
+ # conduit run near the ceiling + round white junction box at the north end
+ parts = [_wall_box("Wall1", L / 2, 2.72, L, 0.034, 0.034, M["conduit"],
+ proud=0.012, name="Conduit1_pipe")]
+ u = 0.5
+ k = 0
+ while u < L - 0.2:
+ parts.append(_wall_box("Wall1", u, 2.72, 0.05, 0.048, 0.044, M["conduit"],
+ proud=0.008, name=f"Conduit1_clip{k}"))
+ u += 0.75
+ k += 1
+ parts.append(_wall_box("Wall1", 3.05, 2.72, 0.09, 0.09, 0.05, M["white_pvc"],
+ proud=0.006, name="Conduit1_junction"))
+ group_fixture("Conduit1", "conduit", parts)
+ # big wall-hung whiteboard right of the door, on a white mounting sill
+ _whiteboard(1, "Wall1", 1.25, 3.25, 1.22, 2.26)
+ sill = [_wall_box("Wall1", 2.25, 1.16, 2.15, 0.12, 0.055, M["white_pvc"],
+ name="Whiteboard1_sill")]
+ group_fixture("WhiteboardSill0", "sill", sill)
+ # trunking-mounted sockets under the board (photo: a row of 5)
+ for i, u in enumerate((1.55, 1.85, 2.15, 2.45, 2.75)):
+ _socket(4 + i, "Wall1", u)
+ # blue-felt pinboard in a beech frame leaning LOW against the wall
+ parts = []
+ pw, ph, fb = 1.75, 1.15, 0.045
+ uc = 2.40
+ parts.append(_lean_box("Wall1", uc, ph / 2, pw - 2 * fb, ph - 2 * fb, 0.02,
+ M["felt"], 52.0, gap=0.03, name="PinBoard0_felt"))
+ parts.append(_lean_box("Wall1", uc, ph - fb / 2, pw, fb, 0.055, M["wood"],
+ 52.0, gap=0.025, name="PinBoard0_frame_top"))
+ parts.append(_lean_box("Wall1", uc, fb / 2, pw, fb, 0.055, M["wood"],
+ 52.0, gap=0.025, name="PinBoard0_frame_bot"))
+ parts.append(_lean_box("Wall1", uc - pw / 2 + fb / 2, ph / 2, fb, ph - 2 * fb,
+ 0.055, M["wood"], 52.0, gap=0.025,
+ name="PinBoard0_frame_l"))
+ parts.append(_lean_box("Wall1", uc + pw / 2 - fb / 2, ph / 2, fb, ph - 2 * fb,
+ 0.055, M["wood"], 52.0, gap=0.025,
+ name="PinBoard0_frame_r"))
+ group_fixture("PinBoard0", "notice_board", parts)
+
+
+# --------------------------------------------------------------------- Wall2
+def _fixtures_wall2():
+ # near-full-width whiteboard, eraser + marker on the tray (stitch)
+ _whiteboard(2, "Wall2", 0.10, 2.65, 1.20, 2.43, eraser_u=0.55, marker_u=1.05)
+
+
+# --------------------------------------------------------------------- Wall3
+def _fixtures_wall3():
+ # black torchiere floor lamp standing in front of the wall
+ s, d, n, L = _wall_frame2("Wall3")
+ c = s + d * 0.72 + n * 0.28
+ xd, yd = _yaw_dirs(GRID_YAW)
+ parts = [
+ _mk_box("FloorLamp0_base", (c.x, c.y, FLOOR_Z + 0.015), xd, 0.30, yd, 0.30,
+ 0.03, M["black"]),
+ _mk_box("FloorLamp0_pole", (c.x, c.y, FLOOR_Z + 0.92), xd, 0.028, yd, 0.028,
+ 1.78, M["black"]),
+ _mk_box("FloorLamp0_bowl", (c.x, c.y, FLOOR_Z + 1.845), xd, 0.26, yd, 0.26,
+ 0.07, M["black"]),
+ _mk_box("FloorLamp0_bowl_rim", (c.x, c.y, FLOOR_Z + 1.9), xd, 0.36, yd,
+ 0.36, 0.04, M["dark"]),
+ ]
+ group_fixture("FloorLamp0", "floor_lamp", parts)
+
+
+# --------------------------------------------------------------------- Wall7
+def _fixtures_wall7():
+ # white panelled boxed-in services cupboard front on the alcove face:
+ # proud frame border + inset panel + small cylindrical door holder
+ parts = [
+ _wall_box("Wall7", 0.185, 1.075, 0.34, 2.15, 0.035, M["white_pvc"],
+ name="Panel0_carcass"),
+ _wall_box("Wall7", 0.185, 1.075, 0.26, 2.03, 0.012, M["grille"],
+ proud=0.035, name="Panel0_inset"),
+ _wall_box("Wall7", 0.185, 2.13, 0.34, 0.05, 0.048, M["white_pvc"],
+ name="Panel0_frame_top"),
+ _wall_box("Wall7", 0.035, 1.075, 0.05, 2.15, 0.048, M["white_pvc"],
+ name="Panel0_frame_l"),
+ _wall_box("Wall7", 0.335, 1.075, 0.05, 2.15, 0.048, M["white_pvc"],
+ name="Panel0_frame_r"),
+ _wall_box("Wall7", 0.28, 1.32, 0.05, 0.11, 0.055, M["white_pvc"],
+ proud=0.048, name="Panel0_holder"),
+ ]
+ group_fixture("Panel0", "panel", parts)
+ # loose grey cable dropping to the floor at the corner
+ cab = [
+ _wall_box("Wall7", 0.34, 1.45, 0.014, 2.5, 0.014, M["dark"], proud=0.055,
+ name="Cable0_drop"),
+ _wall_box("Wall7", 0.25, 0.06, 0.20, 0.014, 0.014, M["dark"], proud=0.12,
+ name="Cable0_floor"),
+ ]
+ group_fixture("Cable0", "cable", cab)
+
+
+# --------------------------------------------------------------------- Wall8
+def _fixtures_wall8():
+ # boxed vertical white waste pipe running full height at the chamfer corner
+ parts = [_wall_box("Wall8", 0.20, WALL_H / 2, 0.055, WALL_H, 0.055, M["pipe"],
+ proud=0.012, name="Pipe0_riser")]
+ for k, z in enumerate((0.45, 1.55, 2.5)):
+ parts.append(_wall_box("Wall8", 0.20, z, 0.075, 0.04, 0.045, M["pipe"],
+ proud=0.008, name=f"Pipe0_bracket{k}"))
+ group_fixture("Pipe0", "pipe", parts)
+
+
+# --------------------------------------------------------------------- Wall12
+def _fixtures_wall12():
+ # round wall clock under the beam end, left of the door
+ ck = [
+ _wall_box("Wall12", 2.88, 2.40, 0.30, 0.30, 0.035, M["black"],
+ name="Clock0_rim"),
+ _wall_box("Wall12", 2.88, 2.40, 0.255, 0.255, 0.012, M["clockface"],
+ proud=0.035, name="Clock0_face"),
+ _wall_box("Wall12", 2.88, 2.435, 0.018, 0.085, 0.006, M["black"],
+ proud=0.047, name="Clock0_hand_m"),
+ _wall_box("Wall12", 2.905, 2.395, 0.06, 0.016, 0.006, M["black"],
+ proud=0.047, name="Clock0_hand_h"),
+ ]
+ group_fixture("Clock0", "clock", ck)
+ # sockets on the trunking under the television
+ _socket(9, "Wall12", 1.75)
+ _socket(10, "Wall12", 2.05)
+
+
+# --------------------------------------------------------------------- ceiling
+def _fixtures_ceiling():
+ # boxed cream downstand beam parallel to Wall0 (0.9 m into the room),
+ # spanning Wall12 -> Wall1; the projector hangs off it
+ parts = [_free_box("Beam0_body", -1.801, -0.291, 2.82, 4.75, 0.25, 0.30,
+ M["cream"])]
+ parts.append(_free_box("Beam0_arris", -1.801, -0.291, 2.665, 4.75, 0.27, 0.02,
+ M["cream"]))
+ group_fixture("Beam0", "beam", parts)
+ # ceiling projector on a pole mount off the beam, lens facing west (the
+ # Wall1 whiteboard is the projection surface)
+ xd, yd = _yaw_dirs(GRID_YAW)
+ px, py = -0.221, -0.744
+
+ def _pj(name, dx, z, sx, sy, sz, mat):
+ return _mk_box(name, (px + xd.x * dx, py + xd.y * dx, FLOOR_Z + z),
+ xd, sx, yd, sy, sz, mat)
+
+ parts = [
+ _pj("Projector0_plate", 0.0, 2.655, 0.16, 0.16, 0.02, M["black"]),
+ _pj("Projector0_pole", 0.0, 2.53, 0.045, 0.045, 0.25, M["black"]),
+ _pj("Projector0_knuckle", 0.0, 2.395, 0.09, 0.09, 0.05, M["black"]),
+ _pj("Projector0_body", 0.0, 2.31, 0.40, 0.30, 0.12, M["white_pvc"]),
+ _pj("Projector0_lens", 0.17, 2.30, 0.07, 0.09, 0.07, M["black"]),
+ _pj("Projector0_vent", -0.185, 2.31, 0.03, 0.22, 0.08, M["dark"]),
+ ]
+ group_fixture("Projector0", "projector", parts)
+ # two surface-mounted twin-tube fluorescents, long axis parallel to the beam
+ for i, (cx, cy) in enumerate(((-2.2, 0.65), (-0.75, -0.95))):
+ parts = [
+ _free_box(f"Light{i}_body", cx, cy, WALL_H - 0.02, 1.55, 0.30, 0.04,
+ M["white_pvc"]),
+ _free_box(f"Light{i}_diffuser", cx, cy, WALL_H - 0.075, 1.50, 0.28,
+ 0.07, M["led"]),
+ _free_box(f"Light{i}_cap_a", cx + xd.x * 0.75, cy + xd.y * 0.75,
+ WALL_H - 0.055, 0.05, 0.28, 0.09, M["white_pvc"]),
+ _free_box(f"Light{i}_cap_b", cx - xd.x * 0.75, cy - xd.y * 0.75,
+ WALL_H - 0.055, 0.05, 0.28, 0.09, M["white_pvc"]),
+ ]
+ group_fixture(f"Light{i}", "ceiling_light", parts)
+ # smoke detector near the beam
+ sd = [
+ _free_box("Smoke0_base", -0.5, -0.35, WALL_H - 0.015, 0.11, 0.11, 0.03,
+ M["white_pvc"]),
+ _free_box("Smoke0_dome", -0.5, -0.35, WALL_H - 0.045, 0.075, 0.075, 0.035,
+ M["white_pvc"]),
+ ]
+ group_fixture("Smoke0", "smoke_detector", sd)
+
+
+# --------------------------------------------------------------------- stage
+def harness_stage(scene):
+ """Materials on every surface, the missing ceiling, then the wall fixtures."""
+ global M
+ M = _mats()
+ cream = _pbr("mat_wall_cream", "#ede5cf", rough=0.92)
+ for nm in SHELL["walls"]:
+ ob = bpy.data.objects.get(nm)
+ if ob is not None:
+ ob.data.materials.clear()
+ ob.data.materials.append(cream)
+ floor = bpy.data.objects.get("Floor0")
+ if floor is not None:
+ floor.data.materials.clear()
+ floor.data.materials.append(_tex_pbr("mat_floor_carpet", "floor_carpet",
+ metres=1.6))
+ _build_ceiling(_pbr("mat_ceiling_cream", "#f5f0e2", rough=0.95))
+ _skirting_and_trunking(scene)
+ _fixtures_wall0()
+ _fixtures_wall1()
+ _fixtures_wall2()
+ _fixtures_wall3()
+ _fixtures_wall7()
+ _fixtures_wall8()
+ _fixtures_wall12()
+ _fixtures_ceiling()
+ print(" harness stage: materials + ceiling + fixtures built")
+
+
if __name__ in ("__main__", "room_py_exec"): # SHELL is now defined above
ROOM = main()
③ QC pass(es)
qc · qc_20260720_074632.log
•STEP #1
I'll start by reading Room.md and Room.py in full, then check the stitches and references.
⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen
•STEP #2
Wall1 has a solid dark wood door; Wall12 has the TV, clock, and a glazed-looking door at its edge. Now the door/table/TV build code:
⌘ Bash· Grep📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen
👁INSPECT #3
Wall1 door is a flush leaf, articulated, no glazing, no closer modeled. The stitch shows something dark at the top of that door — let me zoom in to ch
⌘ Bash📄 Read /tmp/w1_doortop.png📄 Read /tmp/w12_door.png📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen🔧 ToolSearch select:mcp__cap__render🖼 render room⌘ Bash⌘ Bash⌘ Bash⌘ Bash⌘ Bash📄 Read /tmp/qc_render/scene_frame_00000.png📄 Read /tmp/qc_render/scene_frame_00014.png📄 Read /tmp/qc_render/scene_frame_00028.png📄 Read /tmp/qc_render/scene_frame_00044.png⌘ Bash📄 Read /tmp/qc_ref/Wall1_compare.png📄 Read /tmp/qc_ref/Wall12_compare.png
✏️EDIT #4
Wall1 matches well (door, whiteboard, sockets, conduit). Wall12 fixtures render in place; the TV doesn't appear in the ortho, so let me verify it with
⌘ Bash📄 Read /tmp/qc_tv/Television0_frame_00021.png📄 Read /tmp/qc_table/Table0_frame_00044.png✏️ Edit /scratch2/code/LiteReality_to_share/agen✏️ Edit /scratch2/code/LiteReality_to_share/agen✏️ Edit /scratch2/code/LiteReality_to_share/agen
•STEP #5
Both edits are in. Now re-render to verify the rebuild picks them up — the Wall1 ortho should now show the closer at the door head, and frame 21 confi
⌘ Bash📄 Read /tmp/qc_ref2/Wall1_compare.png
•STEP #6
No closer visible — the door GLB likely wasn't recompiled (cache stamp). Let me check the rebuild logic:
· Grep⌘ Bash⌘ Bash⌘ Bash
•STEP #7
Hardlinked — the edits are visible in both trees. Now force the door GLBs to rebuild:
⌘ Bash📄 Read /tmp/qc_ref3/Wall1_compare.png
•STEP #8
Hard to tell at this scale. Let me check whether the scene actually recompiled and zoom into the rendered door top:
⌘ Bash
•STEP #9
The door GLBs regenerated (Object/ at 07:59) but the assembled Room.blend/glb are older — the renderer reused the stale assembly. Let me see what trig
· Grep⌘ Bash⌘ Bash
•STEP #10
The stamp only hashes Room.py, so object.py edits don't invalidate it. Deleting the stamp forces reassembly:
⌘ Bash⌘ Bash📄 Read /tmp/w1_new_doortop.png📄 Read /tmp/w12_new_door.png⌘ Bash📄 Read /tmp/w1_new_doortop2.png⌘ Bash⌘ Bash⌘ Bash
•STEP #11
Both doors regenerated and the room reassembled. Re-rendering Wall1 to verify the closer:
⌘ Bash📄 Read /tmp/w1_final_doortop.png
•STEP #12
Still not visible. The door front should face the room. Let me check whether the render actually used the new assembly:
⌘ Bash⌘ Bash
✏️EDIT #13
The closer IS in the GLB (leaf depth now extends to 0.10 m proud — the closer body). The renderer must be using a cached scene. Let me check how `ref`
⌘ Bash⌘ Bash⌘ Bash⌘ Bash⌘ Bash⌘ Bash⌘ Bash⌘ Bash⌘ Bash⌘ Bash📄 Read /tmp/w1_v6_doortop.png📄 Read /tmp/w12_v6_door.png⌘ Bash✏️ Write /home/zelda/zh340/.claude/projects/-scra
•STEP #14
You're out of usage credits. Run /usage-credits to keep using Fable 5 or /model to switch models.
opus · qc_opus_20260720_082943.log
•STEP #1
⌘ Bash⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen⌘ Bash⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen⌘ Bash📄 Read /scratch2/code/LiteReality_to_share/agen📄 Read /scratch2/code/LiteReality_to_share/agen
👁INSPECT #2
Both doors match their references well and are already articulated. Now let me render to verify the assembled state.
🔧 ToolSearch select:mcp__cap__render🖼 render room⌘ Bash⌘ Bash⌘ Bash⌘ Bash⌘ Bash📄 Read /tmp/qc_scene/scene_frame_00010.png📄 Read /tmp/qc_scene/scene_frame_00025.png📄 Read /tmp/qc_scene/scene_frame_00040.png⌘ Bash📄 Read /tmp/qc_door0/Door0_frame_00050.png📄 Read /tmp/qc_door1/Door1_frame_00000.png
•STEP #3
Let me verify the door glass materials are actually transmissive in the built GLBs (glTF can drop node setups).
⌘ Bash
•STEP #4
Let me get a proper view of the doors to confirm they read as real open doorways. Let me find door-facing frames.
⌘ Bash📄 Read /tmp/qc_w1/Wall1_frame_00020.png📄 Read /tmp/qc_w12/Wall12_frame_00005.png📄 Read /tmp/qc_w12/Wall12_frame_00045.png📄 Read /tmp/qc_w12/Wall12_frame_00030.png⌘ Bash📄 Read /tmp/qc_scene2/scene_frame_00058.png📄 Read /tmp/qc_scene2/scene_frame_00052.png
•STEP #5
I still want to confirm Door1 (the walnut door with frosted glass panels) reads correctly in-scene. Let me render it as an object across candidate fra
⌘ Bash📄 Read /tmp/qc_d1b/Door1_frame_00058.png📄 Read /tmp/qc_d1b/Door1_frame_00056.png📄 Read /tmp/qc_d1b/Door1_frame_00057.png
•STEP #6
The scene is rendering with very high fidelity to the real photos across every surface. Let me do a final check on the two things most likely to hide
⌘ Bash
✏️EDIT #7
Everything checks out. Let me record the QC outcome in the existing scene memory.
📄 Read /home/zelda/zh340/.claude/projects/-scra✏️ Edit /home/zelda/zh340/.claude/projects/-scra
•STEP #8
## QC pass complete — no edits needed
## QC pass complete — no edits needed
I read `Room.md` and `Room.py` in full, verified against the surface stitches and per-object references, and rendered the assembled scene from multiple frames (10, 40, 57, 58 + wall-focus on Wall1/Wall12 + object views of both doors). Every QC criterion is already satisfied by the existing authoring, so I made **zero code changes** — which is the right call given the instruction to preserve the authoring and not overwrite what's there.
**What I checked, per surface / concern:**
- **Openings (Door0 on Wall1, Door1 on Wall12)** — the only two openings; both are `type: "door"`, no windows anywhere. Both door GLBs are already articulated (revolute leaf, closed→open clip, hinge on the −X edge, opens toward −Y). In-scene they read as real doors set in their frames, not solid boxes. Both match their reference images (Door0 = flush reddish-brown veneer, white frame, right-hand lever, no glazing; Door1 = dark walnut with three stacked frosted vision panels, right bar-pull, cream frame).
- **Glass** — Door1's vision panels use a transmissive frosted material (Transmission 0.80, roughness 0.42, IOR 1.45) — translucent/milky, not opaque, which correctly matches the *frosted* panels in the reference (clear glass would be *less* accurate). Door0 has no glazing, matching its reference. No opaque "glass" anywhere.
- **Door closer** — Door1 has the overhead brushed-metal closer its reference shows, modelled as a static part on the frame; Door0's real door has no closer, and none is modelled. Correct.
- **Windows / curtains / blinds** — none exist in this room, so the articulation/unification/daylight-card items are N/A.
- **Wall fixtures vs openings** — skirting and dado trunking break at both doors via `_wall_free_spans`; sockets are all clear of the door spans. The Wall1 ceiling conduit and the Wall12 clock sit *above* the door heads (z ≈ 2.72 / 2.40 vs door tops 2.05 / 1.93), so they never intersect a swinging leaf. No fixture overlaps an opening.
- **Materials & lighting** — ceiling is materialed (`mat_ceiling_cream`); the only emitters are the two real surface-mounted fluorescents; no invented lights.
- **Objects vs reference** — Table0 (beech top, black round legs) and Television0 (black flatscreen) both match their references at a glance in-scene.
The room already meets all "done when" conditions: all glass transparent, all openings open, no fixture over an opening, doors articulate, ceiling materialed, objects match references. Memory updated to record the clean QC result.
④ Reference images used
Object references (object refs)real crop → reference · 2




Object references (opening refs)real crop → reference · 2




Surface references (per-wall stitches)authoring ground truth · 15
Final — render | photo






















