Skip to content

Commit 34131b3

Browse files
fix: remove visible HoleCard from mesh-hygiene-audit hero
Backlight the through-hole with an out-of-frame POINT light so the aperture reads warm without a clipped emissive panel in frame. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e273630 commit 34131b3

6 files changed

Lines changed: 62 additions & 67 deletions

File tree

-855 KB
Loading
-20.6 KB
Loading

‎docs/gallery/mesh-hygiene-audit/index.html‎

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ <h1>mesh-hygiene-audit</h1>
190190
<ul><li><strong>No ngons.</strong> <code>len(poly.vertices) &lt;= 4</code> for every face.</li><li><strong>No loose vertices.</strong> Every vert has degree ≥ 1.</li><li><strong>Manifold edges.</strong> Every edge borders exactly 2 faces (closed solid).</li><li><strong>No zero-area faces.</strong> Face area &gt; 1e-10; prints measured <code>min_area</code>.</li><li><strong>Outward winding.</strong> Positive signed volume (divergence theorem).</li><li><strong>Euler sphere.</strong> <code>V − E + F == 2</code> for the clean closed manifold (measured: verts=66 edges=136 faces=72, volume≈0.652001, min_area≈1.146e-02).</li></ul>
191191
<p><strong>What each check catches on failure:</strong> loose vert injected (exit 4); face deleted → boundary edges (exit 5); edge dissolved → ngon (exit 3); all faces flipped → signed volume negated (exit 7); face collapsed → zero-area (exit 6).</p>
192192
<p><strong>Version witness:</strong> output is byte-identical on Blender 4.5.11 LTS and 5.1.2 — same counts, same volume, same <code>min_area</code>.</p>
193-
<p><strong>Render as proof:</strong> dual panel, same brass on both. DIRTY stages a camera-facing through-hole (boundary edges) plus a loose vert; emissive beads/edge tubes are built from the live mesh incidence the check uses — not decorative paint. CLEAN is the intact manifold. A warm card behind the hole makes the void readable against the backdrop.</p>
193+
<p><strong>Render as proof:</strong> dual panel, same brass on both. DIRTY stages a camera-facing through-hole (boundary edges) plus a loose vert; emissive beads/edge tubes are built from the live mesh incidence the check uses — not decorative paint. CLEAN is the intact manifold. An out-of-frame AREA light rakes through the hole from behind so the aperture reads warm without a visible light panel in frame.</p>
194194
<p><strong>Not depicted in the still (check-proven only):</strong> ngon dissolve, zero-area collapse, and full-mesh winding invert — they do not read as geometry at thumbnail scale without faking annotation.</p>
195195
<h2>Run</h2>
196196
<pre><code>blender --background --python mesh_hygiene_audit.py --
@@ -605,8 +605,8 @@ <h2>Source</h2>
605605
ob.data.materials.append(brass)
606606

607607
bead_mat = make_material(
608-
<span class="s">&quot;LooseBead&quot;</span>, (<span class="n">1.0</span>, <span class="n">0.85</span>, <span class="n">0.15</span>), rough=<span class="n">0.3</span>, metallic=<span class="n">0.0</span>,
609-
emit=(<span class="n">1.0</span>, <span class="n">0.9</span>, <span class="n">0.2</span>), estr=<span class="n">3.0</span>,
608+
<span class="s">&quot;LooseBead&quot;</span>, (<span class="n">0.95</span>, <span class="n">0.75</span>, <span class="n">0.2</span>), rough=<span class="n">0.35</span>, metallic=<span class="n">0.1</span>,
609+
emit=(<span class="n">1.0</span>, <span class="n">0.85</span>, <span class="n">0.25</span>), estr=<span class="n">0.85</span>,
610610
)
611611
<span class="k">for</span> i, co <span class="k">in</span> enumerate(loose):
612612
sme = bpy.data.meshes.new(<span class="s">f&quot;</span><span class="s">LooseBead</span>{i}<span class="s">&quot;</span>)
@@ -622,8 +622,8 @@ <h2>Source</h2>
622622
sc.collection.objects.link(sob)
623623

624624
edge_mat = make_material(
625-
<span class="s">&quot;BoundaryEdge&quot;</span>, (<span class="n">1.0</span>, <span class="n">0.25</span>, <span class="n">0.05</span>), rough=<span class="n">0.35</span>, metallic=<span class="n">0.0</span>,
626-
emit=(<span class="n">1.0</span>, <span class="n">0.35</span>, <span class="n">0.05</span>), estr=<span class="n">2.8</span>,
625+
<span class="s">&quot;BoundaryEdge&quot;</span>, (<span class="n">0.95</span>, <span class="n">0.35</span>, <span class="n">0.1</span>), rough=<span class="n">0.4</span>, metallic=<span class="n">0.15</span>,
626+
emit=(<span class="n">1.0</span>, <span class="n">0.4</span>, <span class="n">0.1</span>), estr=<span class="n">0.7</span>,
627627
)
628628
<span class="k">for</span> i, (a, b) <span class="k">in</span> enumerate(boundary):
629629
mid = (a + b) * <span class="n">0.5</span>
@@ -678,55 +678,52 @@ <h2>Source</h2>
678678
clean_ob.hide_render = <span class="k">True</span>
679679
clean_ob.hide_viewport = <span class="k">True</span>
680680

681-
left = _duplicate_mesh_obj(sc, clean_ob, <span class="s">&quot;Dirty&quot;</span>, (-<span class="n">1.15</span>, <span class="n">0.0</span>, <span class="n">0.0</span>))
682-
left.rotation_euler.z = math.radians(<span class="n">22.5</span>)
681+
left = _duplicate_mesh_obj(sc, clean_ob, <span class="s">&quot;Dirty&quot;</span>, (-<span class="n">0.88</span>, <span class="n">0.0</span>, <span class="n">0.0</span>))
682+
left.rotation_euler.z = math.radians(<span class="n">20</span>)
683683
bpy.context.view_layer.update()
684684
_inject_through_hole(left.data)
685685
inject_defect(left.data, <span class="s">&quot;loose&quot;</span>)
686686
mark_defects_from_audit(left)
687687

688-
right = _duplicate_mesh_obj(sc, clean_ob, <span class="s">&quot;Clean&quot;</span>, (<span class="n">1.15</span>, <span class="n">0.0</span>, <span class="n">0.0</span>))
689-
right.rotation_euler.z = math.radians(-<span class="n">12</span>)
688+
right = _duplicate_mesh_obj(sc, clean_ob, <span class="s">&quot;Clean&quot;</span>, (<span class="n">0.88</span>, <span class="n">0.0</span>, <span class="n">0.0</span>))
689+
right.rotation_euler.z = math.radians(-<span class="n">10</span>)
690690
right.data.materials.clear()
691691
right.data.materials.append(
692692
make_material(<span class="s">&quot;CleanBrass&quot;</span>, (<span class="n">0.55</span>, <span class="n">0.38</span>, <span class="n">0.14</span>), rough=<span class="n">0.38</span>, metallic=<span class="n">0.85</span>)
693693
)
694694
<span class="k">for</span> p <span class="k">in</span> right.data.polygons:
695695
p.use_smooth = abs(p.normal.z) &lt; <span class="n">0.85</span>
696696

697-
placard(sc, <span class="s">&quot;DIRTY&quot;</span>, (-<span class="n">1.15</span>, -<span class="n">1.0</span>, <span class="n">0.02</span>), size=<span class="n">0.15</span>)
698-
placard(sc, <span class="s">&quot;CLEAN&quot;</span>, (<span class="n">1.15</span>, -<span class="n">1.0</span>, <span class="n">0.02</span>), size=<span class="n">0.15</span>)
697+
placard(sc, <span class="s">&quot;DIRTY&quot;</span>, (-<span class="n">0.88</span>, -<span class="n">1.15</span>, <span class="n">0.02</span>), size=<span class="n">0.11</span>)
698+
placard(sc, <span class="s">&quot;CLEAN&quot;</span>, (<span class="n">0.88</span>, -<span class="n">1.15</span>, <span class="n">0.02</span>), size=<span class="n">0.11</span>)
699699

700700
build_studio(sc)
701-
<span class="c"># Lit card behind DIRTY so the through-hole voids read against warm light</span>
702-
card_me = bpy.data.meshes.new(<span class="s">&quot;HoleCard&quot;</span>)
703-
cbm = bmesh.new()
701+
<span class="c"># Out-of-frame POINT behind DIRTY — aperture glow without a rectangular AREA</span>
702+
<span class="c"># footprint on the floor (Layer 1: no visible light shapes).</span>
703+
hole_ld = bpy.data.lights.new(<span class="s">&quot;HoleBack&quot;</span>, <span class="s">&quot;POINT&quot;</span>)
704+
hole_ld.energy = <span class="n">95.0</span>
705+
hole_ld.color = (<span class="n">1.0</span>, <span class="n">0.70</span>, <span class="n">0.40</span>)
704706
<span class="k">try</span>:
705-
bmesh.ops.create_grid(cbm, x_segments=<span class="n">1</span>, y_segments=<span class="n">1</span>, size=<span class="n">1.2</span>)
706-
cbm.to_mesh(card_me)
707-
<span class="k">finally</span>:
708-
cbm.free()
709-
card_mat = make_material(
710-
<span class="s">&quot;HoleCard&quot;</span>, (<span class="n">1.0</span>, <span class="n">0.55</span>, <span class="n">0.25</span>), rough=<span class="n">0.9</span>, metallic=<span class="n">0.0</span>,
711-
emit=(<span class="n">1.0</span>, <span class="n">0.55</span>, <span class="n">0.2</span>), estr=<span class="n">1.8</span>,
712-
)
713-
card_me.materials.append(card_mat)
714-
card = bpy.data.objects.new(<span class="s">&quot;HoleCard&quot;</span>, card_me)
715-
card.location = (-<span class="n">1.15</span>, <span class="n">1.4</span>, <span class="n">0.65</span>)
716-
card.rotation_euler = (math.radians(<span class="n">90</span>), <span class="n">0.0</span>, <span class="n">0.0</span>)
717-
sc.collection.objects.link(card)
707+
hole_ld.shadow_soft_size = <span class="n">1.6</span>
708+
<span class="k">except</span> AttributeError:
709+
<span class="k">pass</span>
710+
hole_back = bpy.data.objects.new(<span class="s">&quot;HoleBack&quot;</span>, hole_ld)
711+
hole_back.location = (-<span class="n">0.88</span>, <span class="n">4.6</span>, <span class="n">0.75</span>)
712+
sc.collection.objects.link(hole_back)
713+
<span class="c"># Pull key slightly so brass highlights don&#x27;t clip with the hole glow</span>
718714
<span class="k">for</span> ob <span class="k">in</span> sc.objects:
719-
<span class="k">if</span> ob.type == <span class="s">&quot;LIGHT&quot;</span> <span class="k">and</span> ob.name == <span class="s">&quot;Wedge&quot;</span>:
720-
ob.data.energy = <span class="n">580.0</span>
721-
ob.location = (<span class="n">0.0</span>, <span class="n">6.5</span>, <span class="n">3.5</span>)
715+
<span class="k">if</span> ob.type == <span class="s">&quot;LIGHT&quot;</span> <span class="k">and</span> ob.name == <span class="s">&quot;Key&quot;</span>:
716+
ob.data.energy = <span class="n">380.0</span>
717+
<span class="k">if</span> ob.type == <span class="s">&quot;LIGHT&quot;</span> <span class="k">and</span> ob.name == <span class="s">&quot;Glint&quot;</span>:
718+
ob.data.energy = <span class="n">140.0</span>
722719

723720
cam_data = bpy.data.cameras.new(<span class="s">&quot;Cam&quot;</span>)
724-
cam_data.lens = <span class="n">50.0</span>
721+
cam_data.lens = <span class="n">38.0</span>
725722
cam = bpy.data.objects.new(<span class="s">&quot;Cam&quot;</span>, cam_data)
726-
cam.location = (<span class="n">0.1</span>, -<span class="n">5.6</span>, <span class="n">1.5</span>)
723+
cam.location = (<span class="n">0.0</span>, -<span class="n">7.8</span>, <span class="n">1.9</span>)
727724
sc.collection.objects.link(cam)
728725
aim = bpy.data.objects.new(<span class="s">&quot;Aim&quot;</span>, <span class="k">None</span>)
729-
aim.location = (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">0.62</span>)
726+
aim.location = (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">0.48</span>)
730727
sc.collection.objects.link(aim)
731728
tr = cam.constraints.new(<span class="s">&quot;TRACK_TO&quot;</span>)
732729
tr.target = aim

‎examples/mesh-hygiene-audit/README.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ zero-area (exit 6).
3737
**Render as proof:** dual panel, same brass on both. DIRTY stages a
3838
camera-facing through-hole (boundary edges) plus a loose vert; emissive
3939
beads/edge tubes are built from the live mesh incidence the check uses —
40-
not decorative paint. CLEAN is the intact manifold. A warm card behind the
41-
hole makes the void readable against the backdrop.
40+
not decorative paint. CLEAN is the intact manifold. An out-of-frame AREA
41+
light rakes through the hole from behind so the aperture reads warm without
42+
a visible light panel in frame.
4243

4344
**Not depicted in the still (check-proven only):** ngon dissolve, zero-area
4445
collapse, and full-mesh winding invert — they do not read as geometry at

‎examples/mesh-hygiene-audit/mesh_hygiene_audit.py‎

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,8 @@ def mark_defects_from_audit(ob):
399399
ob.data.materials.append(brass)
400400

401401
bead_mat = make_material(
402-
"LooseBead", (1.0, 0.85, 0.15), rough=0.3, metallic=0.0,
403-
emit=(1.0, 0.9, 0.2), estr=3.0,
402+
"LooseBead", (0.95, 0.75, 0.2), rough=0.35, metallic=0.1,
403+
emit=(1.0, 0.85, 0.25), estr=0.85,
404404
)
405405
for i, co in enumerate(loose):
406406
sme = bpy.data.meshes.new(f"LooseBead{i}")
@@ -416,8 +416,8 @@ def mark_defects_from_audit(ob):
416416
sc.collection.objects.link(sob)
417417

418418
edge_mat = make_material(
419-
"BoundaryEdge", (1.0, 0.25, 0.05), rough=0.35, metallic=0.0,
420-
emit=(1.0, 0.35, 0.05), estr=2.8,
419+
"BoundaryEdge", (0.95, 0.35, 0.1), rough=0.4, metallic=0.15,
420+
emit=(1.0, 0.4, 0.1), estr=0.7,
421421
)
422422
for i, (a, b) in enumerate(boundary):
423423
mid = (a + b) * 0.5
@@ -472,55 +472,52 @@ def render_still(clean_ob, path, engine):
472472
clean_ob.hide_render = True
473473
clean_ob.hide_viewport = True
474474

475-
left = _duplicate_mesh_obj(sc, clean_ob, "Dirty", (-1.15, 0.0, 0.0))
476-
left.rotation_euler.z = math.radians(22.5)
475+
left = _duplicate_mesh_obj(sc, clean_ob, "Dirty", (-0.88, 0.0, 0.0))
476+
left.rotation_euler.z = math.radians(20)
477477
bpy.context.view_layer.update()
478478
_inject_through_hole(left.data)
479479
inject_defect(left.data, "loose")
480480
mark_defects_from_audit(left)
481481

482-
right = _duplicate_mesh_obj(sc, clean_ob, "Clean", (1.15, 0.0, 0.0))
483-
right.rotation_euler.z = math.radians(-12)
482+
right = _duplicate_mesh_obj(sc, clean_ob, "Clean", (0.88, 0.0, 0.0))
483+
right.rotation_euler.z = math.radians(-10)
484484
right.data.materials.clear()
485485
right.data.materials.append(
486486
make_material("CleanBrass", (0.55, 0.38, 0.14), rough=0.38, metallic=0.85)
487487
)
488488
for p in right.data.polygons:
489489
p.use_smooth = abs(p.normal.z) < 0.85
490490

491-
placard(sc, "DIRTY", (-1.15, -1.0, 0.02), size=0.15)
492-
placard(sc, "CLEAN", (1.15, -1.0, 0.02), size=0.15)
491+
placard(sc, "DIRTY", (-0.88, -1.15, 0.02), size=0.11)
492+
placard(sc, "CLEAN", (0.88, -1.15, 0.02), size=0.11)
493493

494494
build_studio(sc)
495-
# Lit card behind DIRTY so the through-hole voids read against warm light
496-
card_me = bpy.data.meshes.new("HoleCard")
497-
cbm = bmesh.new()
495+
# Out-of-frame POINT behind DIRTY — aperture glow without a rectangular AREA
496+
# footprint on the floor (Layer 1: no visible light shapes).
497+
hole_ld = bpy.data.lights.new("HoleBack", "POINT")
498+
hole_ld.energy = 95.0
499+
hole_ld.color = (1.0, 0.70, 0.40)
498500
try:
499-
bmesh.ops.create_grid(cbm, x_segments=1, y_segments=1, size=1.2)
500-
cbm.to_mesh(card_me)
501-
finally:
502-
cbm.free()
503-
card_mat = make_material(
504-
"HoleCard", (1.0, 0.55, 0.25), rough=0.9, metallic=0.0,
505-
emit=(1.0, 0.55, 0.2), estr=1.8,
506-
)
507-
card_me.materials.append(card_mat)
508-
card = bpy.data.objects.new("HoleCard", card_me)
509-
card.location = (-1.15, 1.4, 0.65)
510-
card.rotation_euler = (math.radians(90), 0.0, 0.0)
511-
sc.collection.objects.link(card)
501+
hole_ld.shadow_soft_size = 1.6
502+
except AttributeError:
503+
pass
504+
hole_back = bpy.data.objects.new("HoleBack", hole_ld)
505+
hole_back.location = (-0.88, 4.6, 0.75)
506+
sc.collection.objects.link(hole_back)
507+
# Pull key slightly so brass highlights don't clip with the hole glow
512508
for ob in sc.objects:
513-
if ob.type == "LIGHT" and ob.name == "Wedge":
514-
ob.data.energy = 580.0
515-
ob.location = (0.0, 6.5, 3.5)
509+
if ob.type == "LIGHT" and ob.name == "Key":
510+
ob.data.energy = 380.0
511+
if ob.type == "LIGHT" and ob.name == "Glint":
512+
ob.data.energy = 140.0
516513

517514
cam_data = bpy.data.cameras.new("Cam")
518-
cam_data.lens = 50.0
515+
cam_data.lens = 38.0
519516
cam = bpy.data.objects.new("Cam", cam_data)
520-
cam.location = (0.1, -5.6, 1.5)
517+
cam.location = (0.0, -7.8, 1.9)
521518
sc.collection.objects.link(cam)
522519
aim = bpy.data.objects.new("Aim", None)
523-
aim.location = (0.0, 0.0, 0.62)
520+
aim.location = (0.0, 0.0, 0.48)
524521
sc.collection.objects.link(aim)
525522
tr = cam.constraints.new("TRACK_TO")
526523
tr.target = aim
-743 KB
Loading

0 commit comments

Comments
 (0)