Skip to content

Commit 573c7f8

Browse files
TMHSDigitalclaude
andcommitted
fix(examples): raise the lightmap cart posts into its canopy
The four 1.35 m posts stopped 0.33 m under the canopy arc, so the canopy floated. Each post now rises into the arc's underside at its own station, its top sheared to the arc for a uniform 12 mm bite. Every part keeps its UV1 island count, zero overlap and minimum island distance; exit codes are unchanged on all three binaries. Also refreshes the gallery alts and index for both examples. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
1 parent 6ca8abd commit 573c7f8

8 files changed

Lines changed: 52 additions & 17 deletions

File tree

6.83 KB
Loading
54.5 KB
Loading

‎docs/gallery/index.html‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -816,7 +816,7 @@ <h2><a href="modular-kit-snap/">modular-kit-snap</a></h2>
816816
</article>
817817
<article class="card" data-tags="uv game-pipeline rendering">
818818
<a class="card-media" href="lightmap-uv-channel/" tabindex="-1" aria-hidden="true">
819-
<img src="assets/lightmap-uv-channel-hero.webp" alt="A wooden covered cart beside a board showing its packed lightmap UV atlas as coloured islands." loading="lazy" decoding="async" />
819+
<img src="assets/lightmap-uv-channel-hero.webp" alt="A wooden covered cart, its four posts carrying an arched canvas canopy, beside a board showing its packed lightmap UV atlas as coloured islands." loading="lazy" decoding="async" />
820820
</a>
821821
<div class="card-body">
822822
<h2><a href="lightmap-uv-channel/">lightmap-uv-channel</a></h2>
@@ -882,7 +882,7 @@ <h2><a href="gn-zone-iterate/">gn-zone-iterate</a></h2>
882882
</article>
883883
<article class="card" data-tags="mesh">
884884
<a class="card-media" href="bake-normal-high-to-low/" tabindex="-1" aria-hidden="true">
885-
<img src="assets/bake-normal-high-to-low-hero.webp" alt="A baked tangent-space normal map shown as a lilac card beside a bronze plate wearing it, with a radial embossed pattern." loading="lazy" decoding="async" />
885+
<img src="assets/bake-normal-high-to-low-hero.webp" alt="A baked tangent-space normal map shown as a lilac card beside a bronze plate wearing it, with a radial embossed pattern, both standing in low display plinths." loading="lazy" decoding="async" />
886886
</a>
887887
<div class="card-body">
888888
<h2><a href="bake-normal-high-to-low/">bake-normal-high-to-low</a></h2>

‎docs/gallery/lightmap-uv-channel/index.html‎

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
<meta property="og:image" content="https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/assets/lightmap-uv-channel-hero.webp" />
1717
<meta property="og:image:width" content="1280" />
1818
<meta property="og:image:height" content="720" />
19-
<meta property="og:image:alt" content="A wooden covered cart beside a board showing its packed lightmap UV atlas as coloured islands." />
19+
<meta property="og:image:alt" content="A wooden covered cart, its four posts carrying an arched canvas canopy, beside a board showing its packed lightmap UV atlas as coloured islands." />
2020
<meta property="og:site_name" content="Blender Developer Tools" />
2121
<meta name="twitter:card" content="summary_large_image" />
2222
<meta name="twitter:title" content="lightmap-uv-channel — Examples — Blender Developer Tools" />
2323
<meta name="twitter:description" content="A market cart carrying the two-channel UV contract for baked lighting: UV0 untouched, UVLight packed with no overlaps and a respected margin." />
2424
<meta name="twitter:image" content="https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/assets/lightmap-uv-channel-hero.webp" />
25-
<meta name="twitter:image:alt" content="A wooden covered cart beside a board showing its packed lightmap UV atlas as coloured islands." />
25+
<meta name="twitter:image:alt" content="A wooden covered cart, its four posts carrying an arched canvas canopy, beside a board showing its packed lightmap UV atlas as coloured islands." />
2626
<style>
2727
/* fonts are deployed by the landing build (docs/fonts/) */
2828
@font-face { font-family: 'Barlow Condensed'; font-weight: 600; font-display: swap;
@@ -268,8 +268,8 @@ <h1>lightmap-uv-channel</h1>
268268
<p>A market cart carrying the two-channel UV contract for baked lighting: UV0 untouched, UVLight packed with no overlaps and a respected margin.</p>
269269
</header>
270270
<main id="main">
271-
<button class="detail-hero" id="heroZoom" type="button" aria-label="View full size: A wooden covered cart beside a board showing its packed lightmap UV atlas as coloured islands.">
272-
<img src="../assets/lightmap-uv-channel-hero.webp" alt="A wooden covered cart beside a board showing its packed lightmap UV atlas as coloured islands." width="1280" height="720" />
271+
<button class="detail-hero" id="heroZoom" type="button" aria-label="View full size: A wooden covered cart, its four posts carrying an arched canvas canopy, beside a board showing its packed lightmap UV atlas as coloured islands.">
272+
<img src="../assets/lightmap-uv-channel-hero.webp" alt="A wooden covered cart, its four posts carrying an arched canvas canopy, beside a board showing its packed lightmap UV atlas as coloured islands." width="1280" height="720" />
273273
</button>
274274
<p class="zoom-hint">Rendered headless by the example itself — click to zoom.</p>
275275
<div class="callout"><span class="tag">witnesses</span> Per-part UVMap/UVLight with active vs active_render pinned (ops clear both flags), UV0 drift 0.0, 3680 islands with 0 SAT overlaps, min island distance 0.00401.</div>
@@ -278,7 +278,7 @@ <h1>lightmap-uv-channel</h1>
278278
<button class="copy-btn" id="copyRun" type="button">Copy</button>
279279
</div>
280280
<section class="detail-section md">
281-
<p>A runnable example building the second UV layer engines require for baked lighting, on an asset built to be reused: a market cart assembled from 21 named, watertight parts — a plank-grooved bed with side rails, corner brackets, and fasteners, real wheel assemblies (disc + hub + iron band + bolt ring) on a capped axle, four posts, and a ribbed canvas canopy. Ground-level pivot at <code>z=0</code>, identity transforms by construction, <code>Cart.*</code> datablocks. (An earlier revision shipped a soft uniform-tan cart with lumpy polygon wheels and was remodeled under the asset-quality gate; its wheel material was also silently never applied — a name-key lookup bug the exact per-part mapping now replaces.) UV0 is the texture channel (deterministic dominant-axis box projection per face); UVLight is the baked-lighting atlas (<code>smart_project</code> → <code>lightmap_pack</code>).</p>
281+
<p>A runnable example building the second UV layer engines require for baked lighting, on an asset built to be reused: a market cart assembled from 21 named, watertight parts — a plank-grooved bed with side rails, corner brackets, and fasteners, real wheel assemblies (disc + hub + iron band + bolt ring) on a capped axle, four posts, and a ribbed canvas canopy. Each post rises from inside the bed into the canopy&#x27;s underside, its top sheared to the arc at its own station for a uniform 12 mm bite. Fixed 1.35 m posts used to stop 0.33 m short of the arc, so the canopy floated. The UV1 contract is unchanged: every part keeps its island count, zero overlap and minimum island distance. Ground-level pivot at <code>z=0</code>, identity transforms by construction, <code>Cart.*</code> datablocks. (An earlier revision shipped a soft uniform-tan cart with lumpy polygon wheels and was remodeled under the asset-quality gate; its wheel material was also silently never applied — a name-key lookup bug the exact per-part mapping now replaces.) UV0 is the texture channel (deterministic dominant-axis box projection per face); UVLight is the baked-lighting atlas (<code>smart_project</code> → <code>lightmap_pack</code>).</p>
282282
<p><strong>What it witnesses</strong> (all independently derived, nothing trusted from the packer):</p>
283283
<ul><li><strong>The flag trap.</strong> <code>uv_layers.new()</code> does <strong>not</strong> move the active flags — the first layer keeps <code>active</code> + <code>active_render</code>, so a UV op runs against channel 0 unless you move <code>active</code> yourself (probed: unwrap+pack with the flags unmoved destroys channel 0, measured drift <strong>2.068</strong>). Worse, the edit-mode UV ops <strong>clear both flags</strong> — the sequence must re-assert them. The check pins, per part: exactly two layers <code>UVMap</code>/<code>UVLight</code>, <code>active == UVLight</code> (edit target), <code>active_render == UVMap</code> (render target).</li><li><strong>Channel zero untouched.</strong> UV0 loops snapshotted before the UVLight unwrap, compared after: drift <strong>0.0</strong> on every part (tol 1e-6).</li><li><strong>Bounds.</strong> Every UVLight loop inside <code>[0, 1]</code> within 1e-5.</li><li><strong>Non-overlap.</strong> UV1 triangles binned into a 256² spatial hash and tested pairwise with strict SAT (epsilon-separated, so shared edges don&#x27;t count): <strong>0</strong> overlapping pairs.</li><li><strong>Margin.</strong> <code>MARGIN_DIV</code> semantics measured live: nominal margin == <code>MARGIN_DIV × 0.01</code> UV units; the check requires the measured min island-pair distance ≥ half the nominal — measured <strong>0.00401</strong> (≈ 2× the nominal 0.002, the packer&#x27;s per-island margin applied twice).</li><li><strong>Island census + watertight parts.</strong> Connected components counted independently per part; loops conserved; every part manifold.</li></ul>
284284
<p><strong>What each check catches on failure:</strong> wrong layer names/count (exit 3); flags not re-established after the ops (exit 4); channel 0 touched by the UV1 unwrap (exit 5 — probed via the clobber trap, drift 2.068); UV1 loops outside the unit square (exit 6); overlapping islands (exit 7 — probed by translating an island onto a neighbor, 15 SAT hits); margin floor broken (exit 8 — probed with <code>MARGIN_DIV=0</code>, measured 2e-5 &lt; 0.001); non-watertight part (exit 9).</p>
@@ -577,11 +577,26 @@ <h2>Source</h2>
577577
<span class="s">f&quot;</span><span class="s">Cart.Wheel.</span>{tag}<span class="s">.Band</span><span class="s">&quot;</span>, <span class="n">0.47</span>, <span class="n">0.435</span>, <span class="n">0.10</span>, (<span class="n">0.55</span>, y, <span class="n">0.45</span>))
578578
parts[<span class="s">f&quot;</span><span class="s">Wheel.</span>{tag}<span class="s">.Bolts</span><span class="s">&quot;</span>] = _bolt_ring(
579579
<span class="s">f&quot;</span><span class="s">Cart.Wheel.</span>{tag}<span class="s">.Bolts</span><span class="s">&quot;</span>, <span class="n">6</span>, <span class="n">0.09</span>, <span class="n">0.02</span>, (<span class="n">0.55</span>, y + sy * <span class="n">0.085</span>, <span class="n">0.45</span>))
580+
<span class="c"># Each post runs from inside the bed up into the canopy&#x27;s underside at</span>
581+
<span class="c"># its own station, read off the canopy arc. A fixed 1.35 m post stopped</span>
582+
<span class="c"># 0.33 m short of the arc, so the canopy floated over four sticks.</span>
583+
<span class="c"># The top is sheared to follow the arc, so the bite is the same across</span>
584+
<span class="c"># the post&#x27;s width rather than deep on one side and open on the other.</span>
585+
canopy_z0, canopy_r, canopy_t, post_bite = <span class="n">1.9</span>, <span class="n">0.85</span>, <span class="n">0.04</span>, <span class="n">0.012</span>
586+
post_z0 = <span class="n">0.875</span>
587+
r_in = canopy_r - canopy_t
580588
<span class="k">for</span> tag, (px, py) <span class="k">in</span> ((<span class="s">&quot;FL&quot;</span>, (<span class="n">0.95</span>, <span class="n">0.48</span>)), (<span class="s">&quot;FR&quot;</span>, (<span class="n">0.95</span>, -<span class="n">0.48</span>)),
581589
(<span class="s">&quot;RL&quot;</span>, (-<span class="n">0.95</span>, <span class="n">0.48</span>)), (<span class="s">&quot;RR&quot;</span>, (-<span class="n">0.95</span>, -<span class="n">0.48</span>))):
582-
parts[<span class="s">f&quot;</span><span class="s">Post.</span>{tag}<span class="s">&quot;</span>] = _box(<span class="s">f&quot;</span><span class="s">Cart.Post.</span>{tag}<span class="s">&quot;</span>, (<span class="n">0.09</span>, <span class="n">0.09</span>, <span class="n">1.35</span>),
583-
(px, py, <span class="n">1.55</span>), <span class="n">0.015</span>)
584-
parts[<span class="s">&quot;Canopy&quot;</span>] = _canopy(<span class="s">&quot;Cart.Canopy&quot;</span>, <span class="n">2.3</span>, <span class="n">0.85</span>, <span class="n">0.04</span>, <span class="n">1.9</span>)
590+
top = canopy_z0 + math.sqrt(r_in * r_in - py * py) + post_bite
591+
me = _box(<span class="s">f&quot;</span><span class="s">Cart.Post.</span>{tag}<span class="s">&quot;</span>, (<span class="n">0.09</span>, <span class="n">0.09</span>, top - post_z0),
592+
(px, py, <span class="n">0.5</span> * (post_z0 + top)), <span class="n">0.015</span>)
593+
<span class="k">for</span> v <span class="k">in</span> me.vertices:
594+
<span class="k">if</span> v.co.z &gt; top - <span class="n">0.05</span>:
595+
arc = canopy_z0 + math.sqrt(r_in * r_in - v.co.y * v.co.y) + post_bite
596+
v.co.z += arc - top
597+
me.update()
598+
parts[<span class="s">f&quot;</span><span class="s">Post.</span>{tag}<span class="s">&quot;</span>] = me
599+
parts[<span class="s">&quot;Canopy&quot;</span>] = _canopy(<span class="s">&quot;Cart.Canopy&quot;</span>, <span class="n">2.3</span>, canopy_r, canopy_t, canopy_z0)
585600
parts[<span class="s">&quot;Canopy.Ribs&quot;</span>] = _canopy_ribs(<span class="s">&quot;Cart.Canopy.Ribs&quot;</span>, (-<span class="n">0.75</span>, <span class="n">0.0</span>, <span class="n">0.75</span>),
586601
<span class="n">0.07</span>, <span class="n">0.875</span>, <span class="n">0.035</span>, <span class="n">1.9</span>)
587602
<span class="k">return</span> parts
@@ -1145,7 +1160,7 @@ <h2>Source</h2>
11451160
</main>
11461161
<dialog class="lightbox" id="lightbox" aria-label="Full-size render">
11471162
<button class="lightbox-close" type="button" autofocus>Close</button>
1148-
<img src="../assets/lightmap-uv-channel-hero.webp" alt="A wooden covered cart beside a board showing its packed lightmap UV atlas as coloured islands." />
1163+
<img src="../assets/lightmap-uv-channel-hero.webp" alt="A wooden covered cart, its four posts carrying an arched canvas canopy, beside a board showing its packed lightmap UV atlas as coloured islands." />
11491164
</dialog>
11501165
<footer>
11511166
<div class="statusbar">

‎examples/gallery.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@
588588
"name": "lightmap-uv-channel",
589589
"dir": "examples/lightmap-uv-channel",
590590
"teaches": "A market cart carrying the two-channel UV contract for baked lighting: UV0 untouched, UVLight packed with no overlaps and a respected margin.",
591-
"alt": "A wooden covered cart beside a board showing its packed lightmap UV atlas as coloured islands.",
591+
"alt": "A wooden covered cart, its four posts carrying an arched canvas canopy, beside a board showing its packed lightmap UV atlas as coloured islands.",
592592
"witnessesFix": "Per-part UVMap/UVLight with active vs active_render pinned (ops clear both flags), UV0 drift 0.0, 3680 islands with 0 SAT overlaps, min island distance 0.00401.",
593593
"hero": "docs/gallery/assets/lightmap-uv-channel-hero.webp",
594594
"preview": "examples/lightmap-uv-channel/preview.webp",
@@ -670,7 +670,7 @@
670670
"name": "bake-normal-high-to-low",
671671
"dir": "examples/bake-normal-high-to-low",
672672
"teaches": "A collapse-decimated hatch plate receiving a Cycles cage-baked tangent normal map from a ribbed high-poly source",
673-
"alt": "A baked tangent-space normal map shown as a lilac card beside a bronze plate wearing it, with a radial embossed pattern.",
673+
"alt": "A baked tangent-space normal map shown as a lilac card beside a bronze plate wearing it, with a radial embossed pattern, both standing in low display plinths.",
674674
"witnessesFix": "Statistical gates not byte-identity: detail frac 0.7211 / MAD 0.09356 vs flat 0.0000 / 0.00277; --flat-source exits 5; type=NORMAL not bake_type; RNA identical on 4.5.11, 5.1.2, 5.2.1",
675675
"hero": "docs/gallery/assets/bake-normal-high-to-low-hero.webp",
676676
"preview": "examples/bake-normal-high-to-low/preview.webp",

‎examples/lightmap-uv-channel/README.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ A runnable example building the second UV layer engines require for baked
44
lighting, on an asset built to be reused: a market cart assembled from
55
21 named, watertight parts — a plank-grooved bed with side rails, corner
66
brackets, and fasteners, real wheel assemblies (disc + hub + iron band +
7-
bolt ring) on a capped axle, four posts, and a ribbed canvas canopy.
7+
bolt ring) on a capped axle, four posts, and a ribbed canvas canopy. Each
8+
post rises from inside the bed into the canopy's underside, its top
9+
sheared to the arc at its own station for a uniform 12 mm bite. Fixed
10+
1.35 m posts used to stop 0.33 m short of the arc, so the canopy floated.
11+
The UV1 contract is unchanged: every part keeps its island count, zero
12+
overlap and minimum island distance.
813
Ground-level pivot at `z=0`, identity transforms by construction, `Cart.*`
914
datablocks. (An earlier revision shipped a soft uniform-tan cart with
1015
lumpy polygon wheels and was remodeled under the asset-quality gate;

‎examples/lightmap-uv-channel/lightmap_uv_channel.py‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,26 @@ def build_cart_meshes():
273273
f"Cart.Wheel.{tag}.Band", 0.47, 0.435, 0.10, (0.55, y, 0.45))
274274
parts[f"Wheel.{tag}.Bolts"] = _bolt_ring(
275275
f"Cart.Wheel.{tag}.Bolts", 6, 0.09, 0.02, (0.55, y + sy * 0.085, 0.45))
276+
# Each post runs from inside the bed up into the canopy's underside at
277+
# its own station, read off the canopy arc. A fixed 1.35 m post stopped
278+
# 0.33 m short of the arc, so the canopy floated over four sticks.
279+
# The top is sheared to follow the arc, so the bite is the same across
280+
# the post's width rather than deep on one side and open on the other.
281+
canopy_z0, canopy_r, canopy_t, post_bite = 1.9, 0.85, 0.04, 0.012
282+
post_z0 = 0.875
283+
r_in = canopy_r - canopy_t
276284
for tag, (px, py) in (("FL", (0.95, 0.48)), ("FR", (0.95, -0.48)),
277285
("RL", (-0.95, 0.48)), ("RR", (-0.95, -0.48))):
278-
parts[f"Post.{tag}"] = _box(f"Cart.Post.{tag}", (0.09, 0.09, 1.35),
279-
(px, py, 1.55), 0.015)
280-
parts["Canopy"] = _canopy("Cart.Canopy", 2.3, 0.85, 0.04, 1.9)
286+
top = canopy_z0 + math.sqrt(r_in * r_in - py * py) + post_bite
287+
me = _box(f"Cart.Post.{tag}", (0.09, 0.09, top - post_z0),
288+
(px, py, 0.5 * (post_z0 + top)), 0.015)
289+
for v in me.vertices:
290+
if v.co.z > top - 0.05:
291+
arc = canopy_z0 + math.sqrt(r_in * r_in - v.co.y * v.co.y) + post_bite
292+
v.co.z += arc - top
293+
me.update()
294+
parts[f"Post.{tag}"] = me
295+
parts["Canopy"] = _canopy("Cart.Canopy", 2.3, canopy_r, canopy_t, canopy_z0)
281296
parts["Canopy.Ribs"] = _canopy_ribs("Cart.Canopy.Ribs", (-0.75, 0.0, 0.75),
282297
0.07, 0.875, 0.035, 1.9)
283298
return parts
7.19 KB
Loading

0 commit comments

Comments
 (0)