4646sys .path .insert (0 , os .path .join (os .path .dirname (os .path .abspath (__file__ )), os .pardir ))
4747sys .dont_write_bytecode = True # keep examples/__pycache__ out of the repo tree
4848import gallery_framing
49+ import gallery_asset_quality
4950
5051TILE = 4.0 # tile length along X, metres
5152WIDTH = 3.0 # corridor width along Y ( +/- 1.5 )
@@ -141,26 +142,56 @@ def detail_layout():
141142 ("Rib.Beam" , (0.24 , 2 * inner_y - 0.30 , 0.34 ), (0.18 , 0.0 , HEIGHT - WALL - 0.17 ), 0.02 ),
142143 # walkway floor plate
143144 ("FloorPlate" , (3.4 , 1.8 , 0.05 ), (TILE / 2 , 0.0 , WALL + 0.025 ), 0.015 ),
144- # wall panels, two per side, recessed-look slabs on the bore face
145- ("Panel.L.1" , (1.15 , 0.05 , 1.15 ), (1.2 , inner_y - 0.025 , 1.55 ), 0.02 ),
146- ("Panel.L.2" , (1.15 , 0.05 , 1.15 ), (2.8 , inner_y - 0.025 , 1.55 ), 0.02 ),
147- ("Panel.R.1" , (1.15 , 0.05 , 1.15 ), (1.2 , - (inner_y - 0.025 ), 1.55 ), 0.02 ),
148- ("Panel.R.2" , (1.15 , 0.05 , 1.15 ), (2.8 , - (inner_y - 0.025 ), 1.55 ), 0.02 ),
149145 # orange trim rails — the lines that jog visibly when a joint steps
150146 ("Trim.L" , (3.7 , 0.06 , 0.09 ), (TILE / 2 , inner_y - 0.03 , 0.78 ), 0.015 ),
151147 ("Trim.R" , (3.7 , 0.06 , 0.09 ), (TILE / 2 , - (inner_y - 0.03 ), 0.78 ), 0.015 ),
152148 # emissive light strips along the wall/ceiling join
153149 ("Light.L" , (3.0 , 0.05 , 0.07 ), (TILE / 2 , inner_y - 0.025 , HEIGHT - WALL - 0.09 ), 0.01 ),
154150 ("Light.R" , (3.0 , 0.05 , 0.07 ), (TILE / 2 , - (inner_y - 0.025 ), HEIGHT - WALL - 0.09 ), 0.01 ),
155151 ]
152+ # wall panel assemblies: backing plate proud of the bore face, a lighter
153+ # inset panel — depth, not flat grey rectangles. Bolt heads are spheres,
154+ # built by _bolts_mesh in build_kit_meshes rather than as boxes.
155+ for side , sy in (("L" , 1.0 ), ("R" , - 1.0 )):
156+ for idx , px in ((1 , 1.2 ), (2 , 2.8 )):
157+ parts .append ((f"PanelBack.{ side } .{ idx } " , (1.35 , 0.04 , 1.35 ),
158+ (px , sy * (inner_y - 0.02 ), 1.55 ), 0.015 ))
159+ parts .append ((f"Panel.{ side } .{ idx } " , (1.15 , 0.05 , 1.15 ),
160+ (px , sy * (inner_y - 0.045 ), 1.55 ), 0.02 ))
156161 return parts
157162
158163
164+ def _bolts_mesh (name , positions , radius ):
165+ """Bolt head spheres at positions — one mesh, disconnected islands."""
166+ me = bpy .data .meshes .new (name )
167+ bm = bmesh .new ()
168+ try :
169+ for px , py , pz in positions :
170+ before_v = set (bm .verts )
171+ bmesh .ops .create_uvsphere (bm , u_segments = 8 , v_segments = 6 , radius = radius )
172+ for v in set (bm .verts ) - before_v :
173+ v .co .x += px
174+ v .co .y += py
175+ v .co .z += pz
176+ bm .to_mesh (me )
177+ finally :
178+ bm .free ()
179+ return me
180+
181+
159182def build_kit_meshes (skew = 0.0 , nudge = 0.0 ):
160183 """All kit meshes, keyed by part suffix. Skew/nudge touch the shell only."""
161184 meshes = {"Shell" : build_shell ("Kit.CorridorSeg.Shell" , skew , nudge )}
162185 for suffix , dims , center , bevel in detail_layout ():
163186 meshes [suffix ] = build_detail (f"Kit.CorridorSeg.{ suffix } " , dims , center , bevel )
187+ # panel fixings: four bolt heads per panel, proud of the inset panel
188+ inner_y = WIDTH / 2 - WALL
189+ for side , sy in (("L" , 1.0 ), ("R" , - 1.0 )):
190+ for idx , px in ((1 , 1.2 ), (2 , 2.8 )):
191+ positions = [(px + dx , sy * (inner_y - 0.07 ), 1.55 + dz )
192+ for dx in (- 0.5 , 0.5 ) for dz in (- 0.5 , 0.5 )]
193+ meshes [f"PanelBolts.{ side } .{ idx } " ] = _bolts_mesh (
194+ f"Kit.CorridorSeg.PanelBolts.{ side } .{ idx } " , positions , 0.022 )
164195 return meshes
165196
166197
@@ -368,19 +399,28 @@ def make_material(name, rgb, rough=0.45, metallic=0.6, emit=None, estr=0.0):
368399
369400
370401SEGMENT_MATS = {
371- "Shell" : ("Shell" , (0.10 , 0.11 , 0.13 ), 0.5 , 0.8 , None , 0.0 ),
402+ "Shell" : ("Shell" , (0.075 , 0.095 , 0.115 ), 0.5 , 0.8 , None , 0.0 ),
372403 "Rib" : ("Rib" , (0.05 , 0.05 , 0.06 ), 0.45 , 0.85 , None , 0.0 ),
373- "FloorPlate" : ("FloorPlate" , (0.07 , 0.08 , 0.09 ), 0.6 , 0.7 , None , 0.0 ),
374- "Panel" : ("Panel" , (0.19 , 0.25 , 0.33 ), 0.4 , 0.6 , None , 0.0 ),
404+ "FloorPlate" : ("FloorPlate" , (0.06 , 0.075 , 0.09 ), 0.6 , 0.7 , None , 0.0 ),
405+ "Panel" : ("Panel" , (0.10 , 0.21 , 0.26 ), 0.42 , 0.6 , None , 0.0 ),
406+ "PanelBack" : ("PanelBack" , (0.06 , 0.10 , 0.13 ), 0.5 , 0.75 , None , 0.0 ),
407+ "PanelBolts" : ("PanelBolts" , (0.35 , 0.38 , 0.42 ), 0.3 , 0.9 , None , 0.0 ),
375408 "Trim" : ("Trim" , (0.85 , 0.35 , 0.08 ), 0.35 , 0.3 , None , 0.0 ),
376409 "Light" : ("LightStrip" , (0.30 , 0.25 , 0.18 ), 0.5 , 0.2 , (1.0 , 0.75 , 0.4 ), 8.0 ),
377410}
378411
379412
413+ _mat_cache = {}
414+
415+
380416def mat_for (suffix ):
417+ """One material per design key, shared across segments (an earlier
418+ revision instantiated a duplicate material per part per segment)."""
381419 key = suffix .split ("." )[0 ]
382- name , rgb , rough , metal , emit , estr = SEGMENT_MATS [key ]
383- return make_material (name , rgb , rough , metal , emit , estr )
420+ if key not in _mat_cache :
421+ name , rgb , rough , metal , emit , estr = SEGMENT_MATS [key ]
422+ _mat_cache [key ] = make_material (name , rgb , rough , metal , emit , estr )
423+ return _mat_cache [key ]
384424
385425
386426def build_kit_objects (sc , x_offset , skew = 0.0 , nudge = 0.0 , y_jog = 0.0 , z_jog = 0.0 ):
@@ -443,7 +483,7 @@ def light(name, loc, energy, size, col, rot):
443483 # the kit's own lighting language, designed with the asset
444484 for i in range (4 ):
445485 ld = bpy .data .lights .new (f"Fixture{ i } " , "AREA" )
446- ld .energy = 130 .0
486+ ld .energy = 85 .0
447487 ld .size = 3.2
448488 ld .color = (1.0 , 0.8 , 0.55 )
449489 ob = bpy .data .objects .new (f"Fixture{ i } " , ld )
@@ -458,7 +498,7 @@ def build_bulkhead(sc):
458498 Not part of the kit — the corridor's destination for this still."""
459499 frame_mat = make_material ("Bulkhead" , (0.06 , 0.06 , 0.07 ), rough = 0.4 , metallic = 0.85 )
460500 door_mat = make_material ("BulkheadDoor" , (0.30 , 0.16 , 0.07 ), rough = 0.5 ,
461- metallic = 0.3 , emit = (1.0 , 0.42 , 0.12 ), estr = 0.6 )
501+ metallic = 0.3 , emit = (1.0 , 0.42 , 0.12 ), estr = 0.5 )
462502 inner_y = WIDTH / 2 - WALL
463503 end_x = TILE * 4
464504 parts = []
@@ -479,7 +519,7 @@ def build_bulkhead(sc):
479519 parts .append (door_ob )
480520 # warm spill into the corridor, toward the camera
481521 ld = bpy .data .lights .new ("BulkheadSpill" , "AREA" )
482- ld .energy = 140 .0
522+ ld .energy = 90 .0
483523 ld .size = 1.8
484524 ld .color = (1.0 , 0.62 , 0.3 )
485525 ob = bpy .data .objects .new ("BulkheadSpill" , ld )
@@ -489,7 +529,7 @@ def build_bulkhead(sc):
489529 # cool rim from the mouth behind the camera: lifts the near right wall and
490530 # the ceiling edge out of dead black without touching the warm pools
491531 rd = bpy .data .lights .new ("MouthRim" , "AREA" )
492- rd .energy = 65 .0
532+ rd .energy = 45 .0
493533 rd .size = 2.0
494534 rd .color = (0.62 , 0.76 , 1.0 )
495535 rob = bpy .data .objects .new ("MouthRim" , rd )
@@ -508,6 +548,7 @@ def render_still(path, engine, falsify=False):
508548 sc = bpy .context .scene
509549
510550 all_parts = []
551+ aq_hero = None
511552 for i in range (4 ):
512553 if falsify :
513554 obs = build_kit_objects (
@@ -516,6 +557,8 @@ def render_still(path, engine, falsify=False):
516557 z_jog = (0.04 if i % 2 else 0.0 ))
517558 else :
518559 obs = build_kit_objects (sc , TILE * i )
560+ if aq_hero is None :
561+ aq_hero = obs [1 :] # the asset-quality gate scores one segment
519562 all_parts .extend (obs [1 :]) # mesh parts only, not the root empties
520563 floor , wall = build_studio (sc )
521564 bulkhead = build_bulkhead (sc )
@@ -563,6 +606,10 @@ def render_still(path, engine, falsify=False):
563606 )
564607 if fcode :
565608 return fcode
609+ aqcode = gallery_asset_quality .check_asset_quality (
610+ sc , cam , hero = aq_hero , stage = [floor , wall ])
611+ if aqcode :
612+ return aqcode
566613 bpy .ops .render .render (write_still = True )
567614 if not (os .path .exists (path ) and os .path .getsize (path ) > 0 ):
568615 print ("ERROR: render produced no file" , file = sys .stderr )
0 commit comments