Skip to content

Commit c03b0e5

Browse files
TMHSDigitalclaude
andcommitted
fix(examples): redesign gn-zone-iterate as graded blocks on a plinth and a spindle
The still showed flat brass and cyan cubes hanging in a dark room: four in a row and six floating in a stack. All presentation now runs after check() passes on the raw evaluated zones: - Each zone's blocks are graded along the axis it iterates on, using Object space: amber to red along X for Repeat, teal to mint along Z for For Each. The colour steps count the iterations. - A render-only Bevel after the GN modifier chamfers the cubes. - The Repeat row sits on a walnut plinth. The For Each tower is threaded on a steel spindle with a brass finial over a round walnut base, so the 0.18 m gaps read as spacing. - The camera is more frontal and tighter. The GN trees, the evaluated counts and the checks are unchanged. Evidence (live runs, local): - E:\Blender-Developer-Tools\.scratch\blender-5.2.1-windows-x64\blender.exe (Blender 5.2.1 LTS): check exit 0, repeat 32/24 and foreach 48/36 verts/ faces, x_centers [0, 1.2, 2.4, 3.6], z_centers [0.21 .. 3.21], pairing=ok. --no-offset exits 4 (X-centers [32,0,0,0]). --foreach-main exits 5 (evaluated 6/0 against 48/36). - E:\Blender-Developer-Tools\.scratch\blender-4.5.11-windows-x64\blender.exe (Blender 4.5.11 LTS): check and --output both exit 0 with the same numbers. - Framing: fill x=0.716 y=0.806, margins l=0.084 r=0.134 b=0.033 t=0.033. - Contact sheet: candidate luma 0.263 / stage 0.227 / warmth +0.243 / sat 0.148, against armature-bend 0.243/0.192/-0.144/0.237, damped-track-aim 0.237/0.218/+0.105/0.178, and bmesh-gear 0.236/0.177/+0.303/0.250. All four readings are within the set's spread. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 9530d78 commit c03b0e5

7 files changed

Lines changed: 103 additions & 14 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ turns 1 / 2 / 3 m into 7 / 17 / 27 treads. Asserts readback and evaluated Z-exte
605605
</tr>
606606
<tr>
607607
<td width="46%" valign="middle">
608-
<a href="examples/gn-zone-iterate/"><img src="examples/gn-zone-iterate/preview.webp" alt="GN zone iterate: four brass cubes in a row (Repeat Zone) beside a tower of six teal cubes (For Each Element) on a dark studio floor, proving pair_with_output actually iterates" /></a>
608+
<a href="examples/gn-zone-iterate/"><img src="examples/gn-zone-iterate/preview.webp" alt="GN zone iterate: four amber-to-red blocks on a walnut plinth (Repeat Zone) beside a spindle of six teal-to-mint blocks (For Each Element) on a dark studio floor, proving pair_with_output actually iterates" /></a>
609609
</td>
610610
<td valign="middle">
611611

3.79 KB
Loading
-1.09 KB
Loading

‎examples/gallery.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@
658658
"name": "gn-zone-iterate",
659659
"dir": "examples/gn-zone-iterate",
660660
"teaches": "Repeat Zone and For Each Element only iterate after pair_with_output. Evaluated cube counts follow 8 times (1+N) and 8 times P, not tree structure.",
661-
"alt": "Four brass cubes evenly spaced in a row from a Repeat zone beside a vertical stack of six cyan cubes from a For Each zone.",
661+
"alt": "Four chamfered blocks graded amber to red, evenly spaced on a walnut plinth by a Repeat zone, beside six teal-to-mint blocks threaded on a steel spindle by a For Each zone.",
662662
"witnessesFix": "Repeat 32/24 verts/faces with X-centers at k*1.2; For Each 48/36 with Z-centers at i*0.6+0.21. Unpaired evaluates empty; For Each main Geometry passthrough is 6 verts.",
663663
"hero": "docs/gallery/assets/gn-zone-iterate-hero.webp",
664664
"preview": "examples/gn-zone-iterate/preview.webp",

‎examples/gn-zone-iterate/README.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ blender --background --python gn_zone_iterate.py -- --output zones.png
4141
The `--output` render path measures framing via `examples/gallery_framing.py`
4242
(exit 10 on violation).
4343

44+
**Staging.** Everything presentational runs after `check()` has passed on the raw
45+
evaluated zones. Each zone's blocks are graded along the axis the zone iterates on,
46+
using Object-space coordinates (amber to red along X for the four Repeat blocks, deep
47+
teal to mint along Z for the six For Each blocks), so the colour steps count the
48+
iterations. A render-only Bevel modifier after the GN modifier chamfers the cubes into
49+
machined blocks. The Repeat row sits on a walnut plinth; the For Each tower is threaded
50+
on a steel spindle over a round walnut base, so its 0.18 m gaps read as spacing on a
51+
rod rather than floating cubes.
52+
4453
## Exit codes
4554

4655
Per-script sequential checks. `9` is a valid check code; there is no rule

‎examples/gn-zone-iterate/gn_zone_iterate.py‎

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -356,26 +356,105 @@ def light(name, loc, energy, size, col, rot):
356356
# Rim from behind so the cube silhouettes separate from the dark wall.
357357
light("Rim", (-1.0, 4.5, 4.5), 260.0, 4.0, (0.70, 0.82, 1.0), (-50, 0, 180))
358358

359-
# Presentation only: the check never reads materials. 0.85 metallic in a
360-
# near-black world reflected near-black, and the brass read as mustard
361-
# card. Less metal and a warmer, brighter base let the key shape it.
362-
for ob, rough, metal in ((rpt, 0.42, 0.55), (fee, 0.38, 0.10)):
359+
# Presentation only — everything below runs after check() has passed on
360+
# the raw evaluated zones. Each zone's blocks are graded along the axis the
361+
# zone iterates on (Object-space X for Repeat, Z for For Each), so the
362+
# colour steps count the iterations; a small render-only bevel after the
363+
# GN modifier turns the cubes into machined blocks.
364+
grades = {
365+
rpt: ("X", REPEAT_N * REPEAT_STEP,
366+
((0.95, 0.55, 0.10), (0.62, 0.07, 0.04)), 0.30, 0.0),
367+
fee: ("Z", (FOREACH_P - 1) * FOREACH_STEP + FOREACH_SIZE,
368+
((0.02, 0.30, 0.36), (0.30, 0.85, 0.72)), 0.28, 0.0),
369+
}
370+
for ob, (axis, span, (c0, c1), rough, metal) in grades.items():
363371
for mat in _node_materials(ob):
364-
bsdf = mat.node_tree.nodes.get("Principled BSDF")
365-
if bsdf is not None:
366-
bsdf.inputs["Metallic"].default_value = metal
367-
bsdf.inputs["Roughness"].default_value = rough
372+
nt = mat.node_tree
373+
bsdf = nt.nodes.get("Principled BSDF")
374+
bsdf.inputs["Metallic"].default_value = metal
375+
bsdf.inputs["Roughness"].default_value = rough
376+
if "Coat Weight" in bsdf.inputs:
377+
bsdf.inputs["Coat Weight"].default_value = 0.5
378+
coord = nt.nodes.new("ShaderNodeTexCoord")
379+
sep = nt.nodes.new("ShaderNodeSeparateXYZ")
380+
nt.links.new(coord.outputs["Object"], sep.inputs["Vector"])
381+
norm = nt.nodes.new("ShaderNodeMapRange")
382+
norm.inputs["From Min"].default_value = 0.0
383+
norm.inputs["From Max"].default_value = span
384+
nt.links.new(sep.outputs[axis], norm.inputs["Value"])
385+
ramp = nt.nodes.new("ShaderNodeValToRGB")
386+
ramp.color_ramp.elements[0].color = (*c0, 1.0)
387+
ramp.color_ramp.elements[1].color = (*c1, 1.0)
388+
nt.links.new(norm.outputs["Result"], ramp.inputs["Fac"])
389+
nt.links.new(ramp.outputs["Color"], bsdf.inputs["Base Color"])
390+
bev = ob.modifiers.new("Chamfer", "BEVEL")
391+
bev.width = 0.035
392+
bev.segments = 3
393+
bev.limit_method = "NONE"
394+
395+
walnut = principled("Walnut", (0.19, 0.09, 0.045, 1.0), 0.0, 0.5)
396+
steel = principled("Steel", (0.55, 0.56, 0.58, 1.0), 1.0, 0.3)
397+
brass = principled("Finial", (0.88, 0.62, 0.26, 1.0), 1.0, 0.28)
398+
399+
def solid(name, build, mat, loc):
400+
me = bpy.data.meshes.new(name)
401+
bm = bmesh.new()
402+
try:
403+
build(bm)
404+
for f in bm.faces:
405+
f.smooth = True
406+
bm.to_mesh(me)
407+
finally:
408+
bm.free()
409+
me.materials.append(mat)
410+
ob = bpy.data.objects.new(name, me)
411+
ob.location = loc
412+
scene.collection.objects.link(ob)
413+
return ob
414+
415+
def box(sx, sy, sz):
416+
def b(bm):
417+
bmesh.ops.create_cube(bm, size=1.0)
418+
bmesh.ops.scale(bm, vec=(sx, sy, sz), verts=bm.verts)
419+
bmesh.ops.bevel(bm, geom=list(bm.edges), offset=0.025, segments=3,
420+
profile=0.5, affect="EDGES", clamp_overlap=True)
421+
return b
422+
423+
def cyl(r, h, segs=48):
424+
def b(bm):
425+
bmesh.ops.create_cone(bm, cap_ends=True, segments=segs, radius1=r, radius2=r, depth=h)
426+
return b
427+
428+
# Stands: the blocks are lifted onto them (render only, after the check).
429+
base_h = 0.14
430+
for ob in (rpt, fee):
431+
ob.location.z = base_h
432+
x0 = rpt.location.x - REPEAT_SIZE / 2 - 0.25
433+
x1 = rpt.location.x + REPEAT_N * REPEAT_STEP + REPEAT_SIZE / 2 + 0.25
434+
row_base = solid("RowPlinth", box(x1 - x0, 1.0, base_h), walnut,
435+
((x0 + x1) / 2, 0.0, base_h / 2))
436+
tower_top = base_h + (FOREACH_P - 1) * FOREACH_STEP + FOREACH_SIZE
437+
tower_base = solid("TowerBase", cyl(0.52, base_h), walnut,
438+
(fee.location.x, 0.0, base_h / 2))
439+
# a steel spindle threads the For Each blocks, so the gaps between them
440+
# read as spacing on a rod, not blocks floating
441+
spindle = solid("Spindle", cyl(0.045, tower_top - base_h + 0.18, 24), steel,
442+
(fee.location.x, 0.0, base_h + (tower_top - base_h + 0.18) / 2))
443+
finial = solid("Finial", lambda bm: bmesh.ops.create_uvsphere(
444+
bm, u_segments=24, v_segments=12, radius=0.09), brass,
445+
(fee.location.x, 0.0, tower_top + 0.2))
446+
props = [row_base, tower_base, spindle, finial]
368447

369448
# Centred on the pair and more frontal: from (6.6, -9.3) the repeat row
370449
# foreshortened into a diagonal and the tower hugged the right edge.
371450
aim = bpy.data.objects.new("Aim", None)
372-
aim.location = (-0.10, 0.0, 1.62)
451+
aim.location = (-0.10, 0.0, 1.85)
373452
aim.hide_render = True
374453
scene.collection.objects.link(aim)
375454
cam_data = bpy.data.cameras.new("Cam")
376-
cam_data.lens = 50.0
455+
cam_data.lens = 56.0
377456
cam = bpy.data.objects.new("Cam", cam_data)
378-
cam.location = (2.7, -10.6, 3.1)
457+
cam.location = (1.4, -11.8, 3.6)
379458
scene.collection.objects.link(cam)
380459
scene.camera = cam
381460
track = cam.constraints.new("TRACK_TO")
@@ -398,8 +477,9 @@ def light(name, loc, energy, size, col, rot):
398477
scene.view_settings.view_transform = "Standard"
399478

400479
hero = [rpt, fee]
480+
bpy.context.view_layer.update()
401481
fcode = gallery_framing.check_framing(
402-
scene, cam, hero=hero, elements=hero, stage=[floor, wall],
482+
scene, cam, hero=hero, elements=hero + props, stage=[floor, wall],
403483
)
404484
if fcode:
405485
return fcode
3.34 KB
Loading

0 commit comments

Comments
 (0)