Skip to content

Commit 38e6f1d

Browse files
TMHSDigitalclaude
andauthored
fix(examples): re-review of the 20 judged-fine examples; seat the gear, buckler and crystal (#247)
* fix(examples): seat the triangulate-tangents buckler on the floor The display stand hung 0.49 m above the floor with nothing under it, and nothing held the buckler's 20-degree backward lean. The stand now rests on the floor, the rim's lowest evaluated vertex bites 1 cm into its top, and a kickstand strut runs from the buckler's back (found by a local-space ray) to the floor. The camera and aim ride with the derived lift. Render path only; exit codes are unchanged on 4.5.11, 5.1.2 and 5.2.1. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> * fix(examples): lean the bmesh-gear on a display wedge The gear leaned 46 degrees back with nothing behind it, and its lowest tooth was sunk 4.5 cm into the floor. The gear is now seated from its lowest evaluated vertex (3 mm bite) and carried by a matte inclined wedge whose slope lies in the back-cap plane. The camera rides with the seat height. bmesh-gear is a pinned calibration reference: luma 0.305 -> 0.302, stage 0.237 -> 0.236, warmth +0.370 -> +0.374, saturation 0.195 -> 0.195. Render path only; exit codes are unchanged on all three binaries. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> * fix(examples): build gp-lineart-contour's crystal as a bipyramid, and mount it build_crystal duplicated an open cone and flipped it over the original. The two crossed into an hourglass with open hexagonal ends and an inner spike, which the old gallery alt described as an hourglass-shaped solid. It is now the hexagonal bipyramid the docstring intended. The crystal also hovered 0.13 m above the floor; a two-tier hex mount, built after the check from the lowest evaluated vertex, now holds its lower tip. The still dollies along the check camera's bearing so the mount stays in frame. The corrected mesh changes the measured Line Art counts from 10 strokes / 34 points to 2 / 12, identical on 4.5.11, 5.1.2 and 5.2.1. The gates are lower bounds (>=1, >=4) and are unchanged, as are every assertion and exit code (default 0, --no-contour 4, --output 0). Shared gallery.json and gallery index updates for all three examples ride here. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> --------- Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent b9e6aa7 commit 38e6f1d

20 files changed

Lines changed: 417 additions & 111 deletions
10.3 KB
Loading
1.32 KB
Loading
5.44 KB
Loading

‎docs/gallery/bmesh-gear/index.html‎

Lines changed: 61 additions & 7 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/bmesh-gear-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 gold fourteen-tooth gear lying at an angle, its face covered in concentric grooves." />
19+
<meta property="og:image:alt" content="A gold fourteen-tooth gear leaning back on a dark display wedge, its face covered in concentric grooves." />
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="bmesh-gear — Examples — Blender Developer Tools" />
2323
<meta name="twitter:description" content="A 14-tooth gear built entirely with bmesh — profile ring, face, extrude — with bm.free() in a try/finally, exactly as the ownership contract demands." />
2424
<meta name="twitter:image" content="https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/assets/bmesh-gear-hero.webp" />
25-
<meta name="twitter:image:alt" content="A gold fourteen-tooth gear lying at an angle, its face covered in concentric grooves." />
25+
<meta name="twitter:image:alt" content="A gold fourteen-tooth gear leaning back on a dark display wedge, its face covered in concentric grooves." />
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>bmesh-gear</h1>
268268
<p>A 14-tooth gear built entirely with bmesh — profile ring, face, extrude — with bm.free() in a try/finally, exactly as the ownership contract demands.</p>
269269
</header>
270270
<main id="main">
271-
<button class="detail-hero" id="heroZoom" type="button" aria-label="View full size: A gold fourteen-tooth gear lying at an angle, its face covered in concentric grooves.">
272-
<img src="../assets/bmesh-gear-hero.webp" alt="A gold fourteen-tooth gear lying at an angle, its face covered in concentric grooves." width="1280" height="720" />
271+
<button class="detail-hero" id="heroZoom" type="button" aria-label="View full size: A gold fourteen-tooth gear leaning back on a dark display wedge, its face covered in concentric grooves.">
272+
<img src="../assets/bmesh-gear-hero.webp" alt="A gold fourteen-tooth gear leaning back on a dark display wedge, its face covered in concentric grooves." 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> Parametric bmesh topology is exactly predictable: verts, edges, and faces match their closed forms, and every edge borders exactly two faces (watertight).</div>
@@ -280,6 +280,7 @@ <h1>bmesh-gear</h1>
280280
<section class="detail-section md">
281281
<p>A runnable example that builds a 14-tooth gear entirely with bmesh — profile ring, face, <code>extrude_face_region</code>, translate — following the ownership contract from <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/skills/mesh-editing-and-bmesh/SKILL.md"><code>mesh-editing-and-bmesh</code></a> and the <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/rules/always-free-bmesh.mdc"><code>always-free-bmesh</code></a> rule: every <code>bmesh.new()</code> is paired with <code>bm.free()</code> in a <code>try</code>/<code>finally</code>.</p>
282282
<p><strong>What it witnesses:</strong> parametric bmesh construction has exactly predictable topology. The check asserts the closed-form counts — verts = 2 × (4 × teeth), faces = sides + 2 caps, edges = 3 × profile — and that the result is watertight (every edge borders exactly two faces). If an op leaks geometry or a face fails to close, the math catches it.</p>
283+
<p>The still leans the gear back on a matte inclined display wedge. Both contacts are derived from the posed mesh: the lowest back-cap vertex sits 3 mm into the floor, and the wedge&#x27;s slope lies in the back-cap plane, so the lean is carried rather than held in the air.</p>
283284
<h2>Run</h2>
284285
<pre><code># Cheap correctness check (no render) — the CI check:
285286
blender --background --python bmesh_gear.py --
@@ -322,6 +323,7 @@ <h2>Source</h2>
322323
blender --background --python bmesh_gear.py -- --output g.png # + render
323324
&quot;&quot;&quot;</span>
324325
<span class="k">import</span> bpy, bmesh, sys, os, math, argparse
326+
<span class="k">from</span> mathutils <span class="k">import</span> Vector
325327

326328
TEETH = <span class="n">14</span>
327329
R_ROOT = <span class="n">1.0</span>
@@ -330,6 +332,11 @@ <h2>Source</h2>
330332
<span class="c"># fraction of a tooth period spent at the tip vs the root</span>
331333
TOOTH_DUTY = <span class="n">0.45</span>
332334

335+
<span class="c"># render staging only (not part of the check)</span>
336+
STAGE_BITE = <span class="n">0.003</span> <span class="c"># contact depth into the floor and into the gear&#x27;s back</span>
337+
WEDGE_SLOPE = <span class="n">1.55</span> <span class="c"># display wedge slope length, up the gear&#x27;s back face</span>
338+
WEDGE_WIDTH = <span class="n">1.8</span> <span class="c"># display wedge width across the gear</span>
339+
333340

334341
<span class="k">def</span> gear_profile():
335342
<span class="s">&quot;&quot;&quot;Vertex ring for the gear silhouette: 4 verts per tooth (root-root-tip-tip).&quot;&quot;&quot;</span>
@@ -425,8 +432,53 @@ <h2>Source</h2>
425432
nt.links.new(rings.outputs[<span class="s">&quot;Fac&quot;</span>], rough.inputs[<span class="s">&quot;Value&quot;</span>])
426433
nt.links.new(rough.outputs[<span class="s">&quot;Result&quot;</span>], bsdf.inputs[<span class="s">&quot;Roughness&quot;</span>])
427434
obj.data.materials.append(mat)
428-
obj.location = (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">0.85</span>)
435+
<span class="c"># the gear leans back on an inclined display wedge. Both contacts are</span>
436+
<span class="c"># derived from the posed mesh: the lowest back-cap vertex bites into</span>
437+
<span class="c"># the floor, and the wedge&#x27;s slope lies in the back-cap plane, sunk into</span>
438+
<span class="c"># the gear, so the lean is carried rather than hung in the air</span>
429439
obj.rotation_euler = (math.radians(<span class="n">46</span>), <span class="n">0.0</span>, math.radians(<span class="n">22</span>))
440+
obj.location = (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">0.0</span>)
441+
bpy.context.view_layer.update()
442+
obj.location.z = -min((obj.matrix_world @ v.co).z <span class="k">for</span> v <span class="k">in</span> obj.data.vertices) - STAGE_BITE
443+
bpy.context.view_layer.update()
444+
mw = obj.matrix_world
445+
rot = mw.to_3x3()
446+
side = (rot @ Vector((<span class="n">1.0</span>, <span class="n">0.0</span>, <span class="n">0.0</span>))).normalized()
447+
up = (rot @ Vector((<span class="n">0.0</span>, <span class="n">1.0</span>, <span class="n">0.0</span>))).normalized()
448+
into = (rot @ Vector((<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">1.0</span>))).normalized()
449+
low = min((mw @ v.co <span class="k">for</span> v <span class="k">in</span> obj.data.vertices), key=<span class="k">lambda</span> p: p.z)
450+
centre = mw @ Vector((<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">0.0</span>))
451+
foot = low + side * (centre - low).dot(side) + into * STAGE_BITE
452+
top = foot + up * WEDGE_SLOPE
453+
back = Vector((top.x, top.y, -STAGE_BITE))
454+
front = Vector((foot.x, foot.y, -STAGE_BITE))
455+
wedge_me = bpy.data.meshes.new(<span class="s">&quot;Wedge&quot;</span>)
456+
bm = bmesh.new()
457+
<span class="k">try</span>:
458+
half = side * (WEDGE_WIDTH / <span class="n">2</span>)
459+
ring = [[bm.verts.new(p + sgn * half) <span class="k">for</span> p <span class="k">in</span> (front, foot, top, back)]
460+
<span class="k">for</span> sgn <span class="k">in</span> (-<span class="n">1.0</span>, <span class="n">1.0</span>)]
461+
a, b = ring
462+
bm.faces.new(a)
463+
bm.faces.new(b[::-<span class="n">1</span>])
464+
<span class="k">for</span> i <span class="k">in</span> range(<span class="n">4</span>):
465+
j = (i + <span class="n">1</span>) % <span class="n">4</span>
466+
bm.faces.new((a[i], a[j], b[j], b[i]))
467+
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
468+
bm.to_mesh(wedge_me)
469+
<span class="k">finally</span>:
470+
bm.free()
471+
wmat = bpy.data.materials.new(<span class="s">&quot;DisplayStand&quot;</span>)
472+
wmat.use_nodes = <span class="k">True</span>
473+
wb = wmat.node_tree.nodes[<span class="s">&quot;Principled BSDF&quot;</span>]
474+
<span class="c"># matte and dark: the slope faces the key square-on, and at the floor&#x27;s</span>
475+
<span class="c"># finish its specular sheen alone (~45/255, measured) reads as paper</span>
476+
wb.inputs[<span class="s">&quot;Base Color&quot;</span>].default_value = (<span class="n">0.02</span>, <span class="n">0.021</span>, <span class="n">0.025</span>, <span class="n">1.0</span>)
477+
wb.inputs[<span class="s">&quot;Roughness&quot;</span>].default_value = <span class="n">0.9</span>
478+
spec = wb.inputs.get(<span class="s">&quot;Specular IOR Level&quot;</span>) <span class="k">or</span> wb.inputs.get(<span class="s">&quot;Specular&quot;</span>)
479+
spec.default_value = <span class="n">0.1</span>
480+
wedge_me.materials.append(wmat)
481+
scene.collection.objects.link(bpy.data.objects.new(<span class="s">&quot;Wedge&quot;</span>, wedge_me))
430482

431483
floor_me = bpy.data.meshes.new(<span class="s">&quot;Floor&quot;</span>)
432484
bm = bmesh.new()
@@ -473,7 +525,9 @@ <h2>Source</h2>
473525
cam_data = bpy.data.cameras.new(<span class="s">&quot;Cam&quot;</span>)
474526
cam_data.lens = <span class="n">55.0</span>
475527
cam = bpy.data.objects.new(<span class="s">&quot;Cam&quot;</span>, cam_data)
476-
cam.location = (<span class="n">0.0</span>, -<span class="n">7.6</span>, <span class="n">4.2</span>)
528+
<span class="c"># the camera rides with the gear&#x27;s derived seat height (it was posed for</span>
529+
<span class="c"># a gear hung at z=0.85), so the composition is unchanged</span>
530+
cam.location = (<span class="n">0.0</span>, -<span class="n">7.6</span>, <span class="n">4.2</span> + obj.location.z - <span class="n">0.85</span>)
477531
cam.rotation_euler = (math.radians(<span class="n">66</span>), <span class="n">0.0</span>, <span class="n">0.0</span>)
478532
scene.collection.objects.link(cam)
479533
scene.camera = cam
@@ -531,7 +585,7 @@ <h2>Source</h2>
531585
</main>
532586
<dialog class="lightbox" id="lightbox" aria-label="Full-size render">
533587
<button class="lightbox-close" type="button" autofocus>Close</button>
534-
<img src="../assets/bmesh-gear-hero.webp" alt="A gold fourteen-tooth gear lying at an angle, its face covered in concentric grooves." />
588+
<img src="../assets/bmesh-gear-hero.webp" alt="A gold fourteen-tooth gear leaning back on a dark display wedge, its face covered in concentric grooves." />
535589
</dialog>
536590
<footer>
537591
<div class="statusbar">
-523 KB
Loading
14 KB
Loading
15.8 KB
Loading

0 commit comments

Comments
 (0)