Skip to content

Commit 6f6df42

Browse files
TMHSDigitalclaude
andcommitted
fix(examples): gn-socket-rename renders its Random Value axis
Presentation only; the contract, assertions and exit codes are unchanged. The column shader now draws gauge graduations from the evaluated POINT gauge_h attribute, so an unwired Random Value renders a blank column where before the still looked identical either way. Brushed copper with engraved ticks from each corner, a steel plinth that no longer mirrors the dark stage into a void, the rim light cut so its floor reflection stops lifting the stage, and an 18 degree presentation turn so two faces show. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
1 parent b86086f commit 6f6df42

8 files changed

Lines changed: 318 additions & 5 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ passthrough.
592592
</tr>
593593
<tr>
594594
<td width="46%" valign="middle">
595-
<a href="examples/gn-socket-rename/"><img src="examples/gn-socket-rename/preview.webp" alt="GN socket rename: a copper jo-block column on a steel plinth in a dark studio, the column switched in by Compare and tagged by Random Value, warm wedge on the back wall" /></a>
595+
<a href="examples/gn-socket-rename/"><img src="examples/gn-socket-rename/preview.webp" alt="GN socket rename: a brushed copper gauge column on a steel plinth in a dark studio, graduated every 0.1 m from its stored gauge_h attribute, warm wedge on the back wall" /></a>
596596
</td>
597597
<td valign="middle">
598598

4.93 KB
Loading
10.2 KB
Loading

‎docs/gallery/gn-socket-rename/index.html‎

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ <h1>gn-socket-rename</h1>
253253
<img src="../assets/gn-socket-rename-hero.webp" alt="gn-socket-rename render" width="1280" height="720" />
254254
</button>
255255
<p class="zoom-hint">Rendered headless by the example itself — click to zoom.</p>
256-
<div class="callout"><span class="tag">witnesses</span> A_INT and Min_001 present on 4.5.11 and 5.1.2, absent on 5.2.1. --legacy-ids exits 5 only on 5.2. Evaluated 16 verts and gauge_h 1.80 on eight column verts</div>
256+
<div class="callout"><span class="tag">witnesses</span> A_INT and Min_001 present on 4.5.11 and 5.1.2, absent on 5.2.1. --legacy-ids exits 5 only on 5.2. Evaluated 16 verts and gauge_h 1.80 on eight column verts; the still draws the column graduations from gauge_h, so an unwired Random Value renders a blank column</div>
257257
<div class="runline">
258258
<pre id="runCmd">blender --background --python examples/gn-socket-rename/gn_socket_rename.py --</pre>
259259
<button class="copy-btn" id="copyRun" type="button">Copy</button>
@@ -272,6 +272,8 @@ <h2>Run</h2>
272272
blender --background --python gn_socket_rename.py -- --legacy-ids
273273
blender --background --python gn_socket_rename.py -- --output gauge.png</code></pre>
274274
<p>The <code>--output</code> render path measures framing against the Layer 1 band via <code>examples/gallery_framing.py</code> (exit 10 on violation) before writing the still.</p>
275+
<h2>Render</h2>
276+
<p>The still witnesses both axes, not only the Compare one. The column&#x27;s graduations — a tick every 0.1 m in from each vertical corner, a heavier one every 0.5 m — are drawn by the column shader from the evaluated POINT <code>gauge_h</code> attribute, and only up to its stored height. With Compare broken the column is missing; with Random Value unwired the Store default leaves <code>gauge_h</code> at 0 and the column renders blank copper. Both were rendered to confirm it. The shader is render-path only and nothing in it feeds back into <code>check()</code>; the column is also turned to 18° for the still so two faces show, which <code>check()</code> cannot see because it reads object-space vertices.</p>
275277
<h2>Exit codes</h2>
276278
<p>Per-script sequential checks. <code>9</code> is a valid check code; there is no rule against it. <code>10</code> is the shared framing helper.</p>
277279
<p>| Code | Meaning | | --- | --- | | 0 | Success | | 1 | Uncaught exception (FATAL wrapper) | | 2 | argparse / usage | | 4 | Identifier inventory wrong for this Blender version | | 5 | Pre-5.2 identifier missing (<code>--legacy-ids</code> on 5.2) | | 6 | Enabled-name socket lookup failed | | 7 | Evaluated vert count off (Compare did not switch the column in) | | 8 | Evaluated zmax off closed form | | 9 | POINT <code>gauge_h</code> not 1.80 on eight column verts | | 10 | Gallery framing violation | | 12 | <code>--output</code> produced no file |</p>
@@ -564,8 +566,154 @@ <h2>Source</h2>
564566
<span class="k">return</span> <span class="s">&quot;BLENDER_EEVEE&quot;</span> <span class="k">if</span> bpy.app.version &gt;= (<span class="n">5</span>, <span class="n">0</span>, <span class="n">0</span>) <span class="k">else</span> <span class="s">&quot;BLENDER_EEVEE_NEXT&quot;</span>
565567

566568

569+
<span class="c"># Render path only. Graduations are engraved every TICK_MINOR up the column</span>
570+
<span class="c"># and heavier every TICK_MAJOR, measured from the plinth top.</span>
571+
TICK_MINOR = <span class="n">0.1</span>
572+
TICK_MAJOR = <span class="n">0.5</span>
573+
TICK_MINOR_HALF = <span class="n">0.004</span>
574+
TICK_MAJOR_HALF = <span class="n">0.009</span>
575+
<span class="c"># Ticks run in from each vertical corner, like the graduations on a gauge</span>
576+
<span class="c"># column. Full-width lines stacked the column into planks.</span>
577+
TICK_MINOR_LEN = <span class="n">0.07</span>
578+
TICK_MAJOR_LEN = <span class="n">0.16</span>
579+
580+
581+
<span class="k">def</span> shader_math(nt, op, *inputs):
582+
node = nt.nodes.new(<span class="s">&quot;ShaderNodeMath&quot;</span>)
583+
node.operation = op
584+
<span class="k">for</span> i, value <span class="k">in</span> enumerate(inputs):
585+
<span class="k">if</span> isinstance(value, (int, float)):
586+
node.inputs[i].default_value = value
587+
<span class="k">else</span>:
588+
nt.links.new(value, node.inputs[i])
589+
<span class="k">return</span> node.outputs[<span class="n">0</span>]
590+
591+
592+
<span class="k">def</span> tick_mask(nt, height, spacing, half_width):
593+
<span class="s">&quot;&quot;&quot;1 within half_width of a multiple of spacing, else 0.&quot;&quot;&quot;</span>
594+
frac = shader_math(nt, <span class="s">&quot;FRACT&quot;</span>, shader_math(nt, <span class="s">&quot;DIVIDE&quot;</span>, height, spacing))
595+
near = shader_math(nt, <span class="s">&quot;MINIMUM&quot;</span>, frac, shader_math(nt, <span class="s">&quot;SUBTRACT&quot;</span>, <span class="n">1.0</span>, frac))
596+
<span class="k">return</span> shader_math(
597+
nt, <span class="s">&quot;LESS_THAN&quot;</span>, shader_math(nt, <span class="s">&quot;MULTIPLY&quot;</span>, near, spacing), half_width
598+
)
599+
600+
601+
<span class="k">def</span> dress_gauge(copper, steel):
602+
<span class="s">&quot;&quot;&quot;Turn the jo-block into a height gauge, drawn from the witness.
603+
604+
The graduations read the evaluated POINT ``gauge_h`` attribute, so the
605+
render witnesses the Random Value axis as well as the Compare axis: with
606+
Random unwired, the Store default leaves ``gauge_h`` at 0 on the column
607+
and the column renders blank. Nothing here feeds back into ``check``.
608+
&quot;&quot;&quot;</span>
609+
nt = copper.node_tree
610+
bsdf = nt.nodes[<span class="s">&quot;Principled BSDF&quot;</span>]
611+
attr = nt.nodes.new(<span class="s">&quot;ShaderNodeAttribute&quot;</span>)
612+
attr.attribute_type = <span class="s">&quot;GEOMETRY&quot;</span>
613+
attr.attribute_name = ATTR_NAME
614+
coord = nt.nodes.new(<span class="s">&quot;ShaderNodeTexCoord&quot;</span>)
615+
sep = nt.nodes.new(<span class="s">&quot;ShaderNodeSeparateXYZ&quot;</span>)
616+
nt.links.new(coord.outputs[<span class="s">&quot;Object&quot;</span>], sep.inputs[<span class="s">&quot;Vector&quot;</span>])
617+
618+
height = shader_math(nt, <span class="s">&quot;SUBTRACT&quot;</span>, sep.outputs[<span class="s">&quot;Z&quot;</span>], PLINTH_SIZE[<span class="n">2</span>])
619+
<span class="c"># Distance in from the nearest vertical corner, from the object-space</span>
620+
<span class="c"># normal: on a side face one of |nx|, |ny| is 1 and the other 0.</span>
621+
onrm = nt.nodes.new(<span class="s">&quot;ShaderNodeSeparateXYZ&quot;</span>)
622+
nt.links.new(coord.outputs[<span class="s">&quot;Normal&quot;</span>], onrm.inputs[<span class="s">&quot;Vector&quot;</span>])
623+
across = shader_math(
624+
nt,
625+
<span class="s">&quot;ADD&quot;</span>,
626+
shader_math(
627+
nt,
628+
<span class="s">&quot;MULTIPLY&quot;</span>,
629+
shader_math(nt, <span class="s">&quot;ABSOLUTE&quot;</span>, sep.outputs[<span class="s">&quot;X&quot;</span>]),
630+
shader_math(nt, <span class="s">&quot;ABSOLUTE&quot;</span>, onrm.outputs[<span class="s">&quot;Y&quot;</span>]),
631+
),
632+
shader_math(
633+
nt,
634+
<span class="s">&quot;MULTIPLY&quot;</span>,
635+
shader_math(nt, <span class="s">&quot;ABSOLUTE&quot;</span>, sep.outputs[<span class="s">&quot;Y&quot;</span>]),
636+
shader_math(nt, <span class="s">&quot;ABSOLUTE&quot;</span>, onrm.outputs[<span class="s">&quot;X&quot;</span>]),
637+
),
638+
)
639+
inset = shader_math(nt, <span class="s">&quot;SUBTRACT&quot;</span>, COLUMN_XY / <span class="n">2.0</span>, across)
640+
lines = shader_math(
641+
nt,
642+
<span class="s">&quot;MAXIMUM&quot;</span>,
643+
shader_math(
644+
nt,
645+
<span class="s">&quot;MULTIPLY&quot;</span>,
646+
tick_mask(nt, height, TICK_MINOR, TICK_MINOR_HALF),
647+
shader_math(nt, <span class="s">&quot;LESS_THAN&quot;</span>, inset, TICK_MINOR_LEN),
648+
),
649+
shader_math(
650+
nt,
651+
<span class="s">&quot;MULTIPLY&quot;</span>,
652+
tick_mask(nt, height, TICK_MAJOR, TICK_MAJOR_HALF),
653+
shader_math(nt, <span class="s">&quot;LESS_THAN&quot;</span>, inset, TICK_MAJOR_LEN),
654+
),
655+
)
656+
<span class="c"># Side faces only (the top face sits on a tick by construction), and</span>
657+
<span class="c"># only up to the stored gauge height.</span>
658+
side = shader_math(
659+
nt, <span class="s">&quot;LESS_THAN&quot;</span>, shader_math(nt, <span class="s">&quot;ABSOLUTE&quot;</span>, onrm.outputs[<span class="s">&quot;Z&quot;</span>]), <span class="n">0.5</span>
660+
)
661+
stored = attr.outputs[<span class="s">&quot;Fac&quot;</span>]
662+
within = shader_math(
663+
nt, <span class="s">&quot;LESS_THAN&quot;</span>, height, shader_math(nt, <span class="s">&quot;ADD&quot;</span>, stored, <span class="n">0.001</span>)
664+
)
665+
engraved = shader_math(
666+
nt,
667+
<span class="s">&quot;MULTIPLY&quot;</span>,
668+
shader_math(nt, <span class="s">&quot;MULTIPLY&quot;</span>, lines, side),
669+
shader_math(nt, <span class="s">&quot;MULTIPLY&quot;</span>, within, shader_math(nt, <span class="s">&quot;GREATER_THAN&quot;</span>, stored, <span class="n">0.5</span>)),
670+
)
671+
672+
<span class="c"># Brushed along the column: noise squeezed across it, stretched up it.</span>
673+
mapping = nt.nodes.new(<span class="s">&quot;ShaderNodeMapping&quot;</span>)
674+
mapping.inputs[<span class="s">&quot;Scale&quot;</span>].default_value = (<span class="n">60.0</span>, <span class="n">60.0</span>, <span class="n">1.5</span>)
675+
nt.links.new(coord.outputs[<span class="s">&quot;Object&quot;</span>], mapping.inputs[<span class="s">&quot;Vector&quot;</span>])
676+
brush = nt.nodes.new(<span class="s">&quot;ShaderNodeTexNoise&quot;</span>)
677+
brush.inputs[<span class="s">&quot;Scale&quot;</span>].default_value = <span class="n">4.0</span>
678+
brush.inputs[<span class="s">&quot;Detail&quot;</span>].default_value = <span class="n">6.0</span>
679+
nt.links.new(mapping.outputs[<span class="s">&quot;Vector&quot;</span>], brush.inputs[<span class="s">&quot;Vector&quot;</span>])
680+
rough = shader_math(
681+
nt,
682+
<span class="s">&quot;MULTIPLY_ADD&quot;</span>,
683+
engraved,
684+
<span class="n">0.45</span>,
685+
shader_math(nt, <span class="s">&quot;MULTIPLY_ADD&quot;</span>, brush.outputs[<span class="s">&quot;Fac&quot;</span>], <span class="n">0.10</span>, <span class="n">0.20</span>),
686+
)
687+
nt.links.new(rough, bsdf.inputs[<span class="s">&quot;Roughness&quot;</span>])
688+
689+
base = nt.nodes.new(<span class="s">&quot;ShaderNodeMix&quot;</span>)
690+
base.data_type = <span class="s">&quot;RGBA&quot;</span>
691+
nt.links.new(engraved, enabled_sock(base, <span class="s">&quot;inputs&quot;</span>, <span class="s">&quot;Factor&quot;</span>))
692+
enabled_sock(base, <span class="s">&quot;inputs&quot;</span>, <span class="s">&quot;A&quot;</span>).default_value = (<span class="n">0.93</span>, <span class="n">0.42</span>, <span class="n">0.08</span>, <span class="n">1.0</span>)
693+
enabled_sock(base, <span class="s">&quot;inputs&quot;</span>, <span class="s">&quot;B&quot;</span>).default_value = (<span class="n">0.05</span>, <span class="n">0.018</span>, <span class="n">0.006</span>, <span class="n">1.0</span>)
694+
nt.links.new(enabled_sock(base, <span class="s">&quot;outputs&quot;</span>, <span class="s">&quot;Result&quot;</span>), bsdf.inputs[<span class="s">&quot;Base Color&quot;</span>])
695+
emit = bsdf.inputs.get(<span class="s">&quot;Emission Color&quot;</span>) <span class="k">or</span> bsdf.inputs[<span class="s">&quot;Emission&quot;</span>]
696+
nt.links.new(enabled_sock(base, <span class="s">&quot;outputs&quot;</span>, <span class="s">&quot;Result&quot;</span>), emit)
697+
<span class="c"># Brushed, the copper reflects less of the key and went brown at</span>
698+
<span class="c"># thumbnail size; a little more of its own colour keeps it copper.</span>
699+
bsdf.inputs[<span class="s">&quot;Emission Strength&quot;</span>].default_value = <span class="n">0.24</span>
700+
701+
<span class="c"># Ground steel: lift it off the stage and give it a machined finish.</span>
702+
sb = steel.node_tree.nodes[<span class="s">&quot;Principled BSDF&quot;</span>]
703+
<span class="c"># Fully metallic at 0.32 it mirrored the dark studio and read as a void.</span>
704+
sb.inputs[<span class="s">&quot;Base Color&quot;</span>].default_value = (<span class="n">0.27</span>, <span class="n">0.28</span>, <span class="n">0.31</span>, <span class="n">1.0</span>)
705+
sb.inputs[<span class="s">&quot;Metallic&quot;</span>].default_value = <span class="n">0.8</span>
706+
sb.inputs[<span class="s">&quot;Roughness&quot;</span>].default_value = <span class="n">0.42</span>
707+
708+
567709
<span class="k">def</span> render_still(obj, path, engine):
568710
scene = bpy.context.scene
711+
dress_gauge(bpy.data.materials[<span class="s">&quot;ColumnCopper&quot;</span>], bpy.data.materials[<span class="s">&quot;PlinthSteel&quot;</span>])
712+
<span class="c"># Presentation turn. At 38 degrees one column face sat square to the</span>
713+
<span class="c"># camera and the gauge read as a flat card; at 18 two faces show and the</span>
714+
<span class="c"># corner graduations wrap the edge. check() reads object-space vertices,</span>
715+
<span class="c"># so the turn cannot reach an assertion.</span>
716+
obj.rotation_euler = (<span class="n">0.0</span>, <span class="n">0.0</span>, math.radians(<span class="n">18</span>))
569717

570718
floor_me = bpy.data.meshes.new(<span class="s">&quot;Floor&quot;</span>)
571719
bm = bmesh.new()
@@ -610,7 +758,10 @@ <h2>Source</h2>
610758

611759
light(<span class="s">&quot;Key&quot;</span>, (-<span class="n">3.4</span>, -<span class="n">4.6</span>, <span class="n">5.4</span>), <span class="n">680.0</span>, <span class="n">4.2</span>, (<span class="n">1.0</span>, <span class="n">0.96</span>, <span class="n">0.9</span>))
612760
light(<span class="s">&quot;Fill&quot;</span>, (<span class="n">4.8</span>, -<span class="n">3.0</span>, <span class="n">2.2</span>), <span class="n">140.0</span>, <span class="n">8.0</span>, (<span class="n">0.75</span>, <span class="n">0.85</span>, <span class="n">1.0</span>))
613-
light(<span class="s">&quot;Rim&quot;</span>, (<span class="n">0.2</span>, <span class="n">5.8</span>, <span class="n">3.4</span>), <span class="n">300.0</span>, <span class="n">3.2</span>, (<span class="n">0.6</span>, <span class="n">0.78</span>, <span class="n">1.0</span>))
761+
<span class="c"># At 300 W the rim&#x27;s glossy reflection off the floor lifted the stage</span>
762+
<span class="c"># right of the column to mid grey-blue (patch mean 104 against 62 with</span>
763+
<span class="c"># the rim off); the column edge still separates at this level.</span>
764+
light(<span class="s">&quot;Rim&quot;</span>, (<span class="n">0.2</span>, <span class="n">5.8</span>, <span class="n">3.4</span>), <span class="n">120.0</span>, <span class="n">3.2</span>, (<span class="n">0.6</span>, <span class="n">0.78</span>, <span class="n">1.0</span>))
614765
light(<span class="s">&quot;Glint&quot;</span>, (<span class="n">1.8</span>, -<span class="n">4.8</span>, <span class="n">5.6</span>), <span class="n">900.0</span>, <span class="n">0.85</span>, (<span class="n">1.0</span>, <span class="n">0.90</span>, <span class="n">0.70</span>))
615766
wedge = bpy.data.lights.new(<span class="s">&quot;Wedge&quot;</span>, <span class="s">&quot;AREA&quot;</span>)
616767
wedge.energy = <span class="n">480.0</span>

‎examples/gallery.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@
632632
"name": "gn-socket-rename",
633633
"dir": "examples/gn-socket-rename",
634634
"teaches": "Compare and Random Value socket identifiers collapsed onto reused names in 5.2; enabled-name lookup wires on 4.5, 5.1, and 5.2",
635-
"witnessesFix": "A_INT and Min_001 present on 4.5.11 and 5.1.2, absent on 5.2.1. --legacy-ids exits 5 only on 5.2. Evaluated 16 verts and gauge_h 1.80 on eight column verts",
635+
"witnessesFix": "A_INT and Min_001 present on 4.5.11 and 5.1.2, absent on 5.2.1. --legacy-ids exits 5 only on 5.2. Evaluated 16 verts and gauge_h 1.80 on eight column verts; the still draws the column graduations from gauge_h, so an unwired Random Value renders a blank column",
636636
"hero": "docs/gallery/assets/gn-socket-rename-hero.webp",
637637
"preview": "examples/gn-socket-rename/preview.webp",
638638
"tags": [

‎examples/gn-socket-rename/README.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ The `--output` render path measures framing against the Layer 1 band via
5252
`examples/gallery_framing.py` (exit 10 on violation) before writing the
5353
still.
5454

55+
## Render
56+
57+
The still witnesses both axes, not only the Compare one. The column's
58+
graduations — a tick every 0.1 m in from each vertical corner, a heavier
59+
one every 0.5 m — are drawn by the column shader from the evaluated
60+
POINT `gauge_h` attribute, and only up to its stored height. With
61+
Compare broken the column is missing; with Random Value unwired the
62+
Store default leaves `gauge_h` at 0 and the column renders blank copper.
63+
Both were rendered to confirm it. The shader is render-path only and
64+
nothing in it feeds back into `check()`; the column is also turned to
65+
18° for the still so two faces show, which `check()` cannot see because
66+
it reads object-space vertices.
67+
5568
## Exit codes
5669

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

0 commit comments

Comments
 (0)