Skip to content

Commit a95e708

Browse files
fix: rebuild prop-origin-transform accessory for thumbnail socket proof
Replace emissive orange/blue blob materials with a shared metal fitting and a deep shadowed mount well so TRAP displacement reads at card scale. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 34131b3 commit a95e708

6 files changed

Lines changed: 99 additions & 92 deletions

File tree

-914 KB
Loading
-17.5 KB
Loading

‎docs/gallery/prop-origin-transform/index.html‎

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ <h1>prop-origin-transform</h1>
190190
<ul><li><strong>Stale <code>matrix_world</code>.</strong> After a location edit, <code>matrix_world</code> is unchanged until <code>view_layer.update()</code> (same half of the orrery contract).</li><li><strong>Scale apply via data API.</strong> Non-uniform scale baked into verts; <code>obj.scale == (1,1,1)</code> exactly afterward.</li><li><strong>Origin at base center.</strong> Local bbox <code>min.z == 0</code>, XY centered; world AABB delta across the bake is <strong>0</strong> (gate <code>1e-5</code>).</li><li><strong>Bare-parent trap.</strong> Parenting the accessory without MPI jumps it (~0.43 m measured); setting <code>matrix_parent_inverse</code> restores world location (err ~3e-8).</li></ul>
191191
<p><strong>What each check catches on failure:</strong> skipping scale apply leaves non-identity scale; skipping origin bake leaves <code>min.z ≈ -0.756</code>; skipping MPI leaves the accessory teleported.</p>
192192
<p><strong>Version witness:</strong> byte-identical numbers on Blender 4.5.11 LTS and 5.1.2.</p>
193-
<p>The render is a dual panel: left <strong>TRAP</strong> (bare parent — flanged conduit teleports off an empty mount socket, hazard material) vs right <strong>MPI KEEP</strong> (flange seated in the socket), with emissive origin markers at each pedestal&#x27;s base. Check closed forms are unchanged; the flange/socket are render form only.</p>
193+
<p>The render is a dual panel: left <strong>TRAP</strong> (bare parent — flanged conduit teleports off a deep shadowed mount socket) vs right <strong>MPI KEEP</strong> (same metal fitting seated in the socket), with emissive origin markers at each pedestal&#x27;s base. Accessory and pedestal stay in one material family so displacement — not color — carries the proof. Check closed forms are unchanged.</p>
194194
<h2>Run</h2>
195195
<pre><code>blender --background --python prop_origin_transform.py --
196196
blender --background --python prop_origin_transform.py -- --output origin.png
@@ -315,60 +315,65 @@ <h2>Source</h2>
315315

316316

317317
<span class="k">def</span> build_accessory_mesh():
318-
<span class="s">&quot;&quot;&quot;Flanged conduit stub — reads as a bolted part, not a stray cylinder.&quot;&quot;&quot;</span>
318+
<span class="s">&quot;&quot;&quot;Flanged conduit stub — short pipe + mounting flange + bolt heads.&quot;&quot;&quot;</span>
319319
me = bpy.data.meshes.new(<span class="s">&quot;Conduit&quot;</span>)
320320
bm = bmesh.new()
321321
<span class="k">try</span>:
322-
<span class="c"># Barrel along +Y (away from prop face when mounted)</span>
323-
bmesh.ops.create_cone(
324-
bm, cap_ends=<span class="k">True</span>, segments=<span class="n">14</span>, radius1=<span class="n">0.08</span>, radius2=<span class="n">0.08</span>, depth=<span class="n">0.42</span>,
322+
<span class="c"># Barrel along +Y (away from prop face when mounted); origin at flange face</span>
323+
barrel = bmesh.ops.create_cone(
324+
bm, cap_ends=<span class="k">True</span>, segments=<span class="n">16</span>, radius1=<span class="n">0.09</span>, radius2=<span class="n">0.08</span>, depth=<span class="n">0.48</span>,
325325
)
326326
bmesh.ops.rotate(
327-
bm, verts=bm.verts, cent=(<span class="n">0</span>, <span class="n">0</span>, <span class="n">0</span>),
327+
bm, verts=barrel[<span class="s">&quot;verts&quot;</span>], cent=(<span class="n">0</span>, <span class="n">0</span>, <span class="n">0</span>),
328328
matrix=Matrix.Rotation(math.radians(<span class="n">90</span>), <span class="n">4</span>, <span class="s">&quot;X&quot;</span>),
329329
)
330-
bmesh.ops.translate(bm, vec=(<span class="n">0.0</span>, -<span class="n">0.21</span>, <span class="n">0.0</span>), verts=bm.verts)
331-
<span class="c"># Mounting flange at the prop-facing end</span>
332-
ret = bmesh.ops.create_cone(
333-
bm, cap_ends=<span class="k">True</span>, segments=<span class="n">16</span>, radius1=<span class="n">0.16</span>, radius2=<span class="n">0.16</span>, depth=<span class="n">0.04</span>,
330+
bmesh.ops.translate(bm, vec=(<span class="n">0.0</span>, -<span class="n">0.24</span>, <span class="n">0.0</span>), verts=barrel[<span class="s">&quot;verts&quot;</span>])
331+
<span class="c"># Thick mounting flange at the prop-facing (+Y local before rotate → y≈0)</span>
332+
flange = bmesh.ops.create_cone(
333+
bm, cap_ends=<span class="k">True</span>, segments=<span class="n">20</span>, radius1=<span class="n">0.20</span>, radius2=<span class="n">0.20</span>, depth=<span class="n">0.06</span>,
334334
)
335335
bmesh.ops.rotate(
336-
bm, verts=ret[<span class="s">&quot;verts&quot;</span>], cent=(<span class="n">0</span>, <span class="n">0</span>, <span class="n">0</span>),
336+
bm, verts=flange[<span class="s">&quot;verts&quot;</span>], cent=(<span class="n">0</span>, <span class="n">0</span>, <span class="n">0</span>),
337337
matrix=Matrix.Rotation(math.radians(<span class="n">90</span>), <span class="n">4</span>, <span class="s">&quot;X&quot;</span>),
338338
)
339-
bmesh.ops.translate(bm, vec=(<span class="n">0.0</span>, <span class="n">0.02</span>, <span class="n">0.0</span>), verts=ret[<span class="s">&quot;verts&quot;</span>])
340-
<span class="c"># Four bolt heads on the flange</span>
339+
bmesh.ops.translate(bm, vec=(<span class="n">0.0</span>, <span class="n">0.03</span>, <span class="n">0.0</span>), verts=flange[<span class="s">&quot;verts&quot;</span>])
340+
<span class="c"># Four bolt heads on the flange face (camera-visible when seated)</span>
341341
<span class="k">for</span> ang <span class="k">in</span> (<span class="n">45</span>, <span class="n">135</span>, <span class="n">225</span>, <span class="n">315</span>):
342342
rad = math.radians(ang)
343-
bx, bz = <span class="n">0.11</span> * math.cos(rad), <span class="n">0.11</span> * math.sin(rad)
343+
bx, bz = <span class="n">0.14</span> * math.cos(rad), <span class="n">0.14</span> * math.sin(rad)
344344
br = bmesh.ops.create_cone(
345-
bm, cap_ends=<span class="k">True</span>, segments=<span class="n">8</span>, radius1=<span class="n">0.025</span>, radius2=<span class="n">0.025</span>, depth=<span class="n">0.03</span>,
345+
bm, cap_ends=<span class="k">True</span>, segments=<span class="n">8</span>, radius1=<span class="n">0.032</span>, radius2=<span class="n">0.032</span>, depth=<span class="n">0.04</span>,
346346
)
347347
bmesh.ops.rotate(
348348
bm, verts=br[<span class="s">&quot;verts&quot;</span>], cent=(<span class="n">0</span>, <span class="n">0</span>, <span class="n">0</span>),
349349
matrix=Matrix.Rotation(math.radians(<span class="n">90</span>), <span class="n">4</span>, <span class="s">&quot;X&quot;</span>),
350350
)
351-
bmesh.ops.translate(bm, vec=(bx, <span class="n">0.05</span>, bz), verts=br[<span class="s">&quot;verts&quot;</span>])
351+
bmesh.ops.translate(bm, vec=(bx, <span class="n">0.07</span>, bz), verts=br[<span class="s">&quot;verts&quot;</span>])
352352
bm.to_mesh(me)
353353
<span class="k">finally</span>:
354354
bm.free()
355355
<span class="k">return</span> me
356356

357357

358358
<span class="k">def</span> build_socket_mesh():
359-
<span class="s">&quot;&quot;&quot;Empty mount collar left on the prop — the seat the accessory departed.&quot;&quot;&quot;</span>
359+
<span class="s">&quot;&quot;&quot;Deep mount well — shadowed interior reads as the vacated seat.&quot;&quot;&quot;</span>
360360
me = bpy.data.meshes.new(<span class="s">&quot;Socket&quot;</span>)
361361
bm = bmesh.new()
362362
<span class="k">try</span>:
363-
<span class="c"># Outer ring</span>
363+
<span class="c"># Outer collar (matches flange OD)</span>
364364
bmesh.ops.create_cone(
365-
bm, cap_ends=<span class="k">True</span>, segments=<span class="n">20</span>, radius1=<span class="n">0.18</span>, radius2=<span class="n">0.18</span>, depth=<span class="n">0.05</span>,
365+
bm, cap_ends=<span class="k">True</span>, segments=<span class="n">24</span>, radius1=<span class="n">0.24</span>, radius2=<span class="n">0.24</span>, depth=<span class="n">0.07</span>,
366366
)
367-
<span class="c"># Punch a visual recess by adding an inset darker disk slightly proud</span>
368-
inner = bmesh.ops.create_cone(
369-
bm, cap_ends=<span class="k">True</span>, segments=<span class="n">16</span>, radius1=<span class="n">0.09</span>, radius2=<span class="n">0.09</span>, depth=<span class="n">0.02</span>,
367+
<span class="c"># Deep dark well — into the prop after rotate</span>
368+
well = bmesh.ops.create_cone(
369+
bm, cap_ends=<span class="k">True</span>, segments=<span class="n">20</span>, radius1=<span class="n">0.13</span>, radius2=<span class="n">0.11</span>, depth=<span class="n">0.18</span>,
370370
)
371-
bmesh.ops.translate(bm, vec=(<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">0.02</span>), verts=inner[<span class="s">&quot;verts&quot;</span>])
371+
bmesh.ops.translate(bm, vec=(<span class="n">0.0</span>, <span class="n">0.0</span>, -<span class="n">0.06</span>), verts=well[<span class="s">&quot;verts&quot;</span>])
372+
<span class="c"># Backstop disk (shadowed pocket floor)</span>
373+
floor = bmesh.ops.create_cone(
374+
bm, cap_ends=<span class="k">True</span>, segments=<span class="n">16</span>, radius1=<span class="n">0.10</span>, radius2=<span class="n">0.10</span>, depth=<span class="n">0.025</span>,
375+
)
376+
bmesh.ops.translate(bm, vec=(<span class="n">0.0</span>, <span class="n">0.0</span>, -<span class="n">0.16</span>), verts=floor[<span class="s">&quot;verts&quot;</span>])
372377
bmesh.ops.rotate(
373378
bm, verts=bm.verts, cent=(<span class="n">0</span>, <span class="n">0</span>, <span class="n">0</span>),
374379
matrix=Matrix.Rotation(math.radians(<span class="n">90</span>), <span class="n">4</span>, <span class="s">&quot;X&quot;</span>),
@@ -656,17 +661,13 @@ <h2>Source</h2>
656661
bpy.ops.wm.read_factory_settings(use_empty=<span class="k">True</span>)
657662
sc = bpy.context.scene
658663

659-
<span class="c"># Teal pedestal — distinct from mesh-hygiene brass valve</span>
664+
<span class="c"># Teal pedestal — accessory shares metal fitting in the same family (no</span>
665+
<span class="c"># primary-color emission blobs). Empty socket is a dark recessed well.</span>
660666
body_m = make_material(<span class="s">&quot;BodyR&quot;</span>, (<span class="n">0.14</span>, <span class="n">0.48</span>, <span class="n">0.42</span>), rough=<span class="n">0.4</span>, metallic=<span class="n">0.55</span>)
661-
acc_m = make_material(<span class="s">&quot;AccR&quot;</span>, (<span class="n">0.62</span>, <span class="n">0.48</span>, <span class="n">0.18</span>), rough=<span class="n">0.35</span>, metallic=<span class="n">0.85</span>)
662-
trap_m = make_material(
663-
<span class="s">&quot;TrapAcc&quot;</span>, (<span class="n">0.95</span>, <span class="n">0.28</span>, <span class="n">0.08</span>), rough=<span class="n">0.4</span>, metallic=<span class="n">0.35</span>,
664-
emit=(<span class="n">1.0</span>, <span class="n">0.3</span>, <span class="n">0.05</span>), estr=<span class="n">1.1</span>,
665-
)
666-
sock_m = make_material(<span class="s">&quot;Socket&quot;</span>, (<span class="n">0.12</span>, <span class="n">0.14</span>, <span class="n">0.15</span>), rough=<span class="n">0.55</span>, metallic=<span class="n">0.4</span>)
667+
acc_m = make_material(<span class="s">&quot;AccR&quot;</span>, (<span class="n">0.42</span>, <span class="n">0.44</span>, <span class="n">0.40</span>), rough=<span class="n">0.32</span>, metallic=<span class="n">0.88</span>)
668+
sock_m = make_material(<span class="s">&quot;Socket&quot;</span>, (<span class="n">0.06</span>, <span class="n">0.07</span>, <span class="n">0.08</span>), rough=<span class="n">0.7</span>, metallic=<span class="n">0.2</span>)
667669
sock_empty_m = make_material(
668-
<span class="s">&quot;SocketEmpty&quot;</span>, (<span class="n">0.35</span>, <span class="n">0.55</span>, <span class="n">0.75</span>), rough=<span class="n">0.45</span>, metallic=<span class="n">0.2</span>,
669-
emit=(<span class="n">0.3</span>, <span class="n">0.5</span>, <span class="n">0.75</span>), estr=<span class="n">0.6</span>,
670+
<span class="s">&quot;SocketEmpty&quot;</span>, (<span class="n">0.015</span>, <span class="n">0.018</span>, <span class="n">0.02</span>), rough=<span class="n">0.95</span>, metallic=<span class="n">0.0</span>,
670671
)
671672

672673
<span class="k">def</span> make_prop(name, loc, rot_z=<span class="n">0.0</span>):
@@ -709,8 +710,8 @@ <h2>Source</h2>
709710
bpy.context.view_layer.update()
710711
<span class="k">return</span> ob
711712

712-
left = make_prop(<span class="s">&quot;TrapProp&quot;</span>, (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">0.0</span>), rot_z=math.radians(<span class="n">25</span>))
713-
right = make_prop(<span class="s">&quot;KeepProp&quot;</span>, (<span class="n">2.0</span>, <span class="n">0.0</span>, <span class="n">0.0</span>), rot_z=math.radians(-<span class="n">8</span>))
713+
left = make_prop(<span class="s">&quot;TrapProp&quot;</span>, (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">0.0</span>), rot_z=math.radians(<span class="n">55</span>))
714+
right = make_prop(<span class="s">&quot;KeepProp&quot;</span>, (<span class="n">2.2</span>, <span class="n">0.0</span>, <span class="n">0.0</span>), rot_z=math.radians(-<span class="n">10</span>))
714715

715716
bpy.context.view_layer.update()
716717
left_front = left.matrix_world @ Vector((<span class="n">0.0</span>, -<span class="n">0.52</span>, <span class="n">0.78</span>))
@@ -719,10 +720,10 @@ <h2>Source</h2>
719720
make_socket(<span class="s">&quot;TrapSocket&quot;</span>, left_front, sock_empty_m, parent=left)
720721
make_socket(<span class="s">&quot;KeepSocket&quot;</span>, right_front, sock_m, parent=right)
721722

722-
trap_acc = make_acc(<span class="s">&quot;TrapAcc&quot;</span>, left_front + Vector((<span class="n">0.0</span>, -<span class="n">0.08</span>, <span class="n">0.0</span>)), trap_m)
723-
keep_acc = make_acc(<span class="s">&quot;KeepAcc&quot;</span>, right_front + Vector((<span class="n">0.0</span>, -<span class="n">0.08</span>, <span class="n">0.0</span>)), acc_m)
724-
trap_acc.scale = (<span class="n">1.35</span>, <span class="n">1.35</span>, <span class="n">1.35</span>)
725-
keep_acc.scale = (<span class="n">1.35</span>, <span class="n">1.35</span>, <span class="n">1.35</span>)
723+
trap_acc = make_acc(<span class="s">&quot;TrapAcc&quot;</span>, left_front + Vector((<span class="n">0.0</span>, -<span class="n">0.05</span>, <span class="n">0.0</span>)), acc_m)
724+
keep_acc = make_acc(<span class="s">&quot;KeepAcc&quot;</span>, right_front + Vector((<span class="n">0.0</span>, -<span class="n">0.05</span>, <span class="n">0.0</span>)), acc_m)
725+
trap_acc.scale = (<span class="n">1.55</span>, <span class="n">1.55</span>, <span class="n">1.55</span>)
726+
keep_acc.scale = (<span class="n">1.55</span>, <span class="n">1.55</span>, <span class="n">1.55</span>)
726727
bpy.context.view_layer.update()
727728

728729
w_before = trap_acc.matrix_world.translation.copy()
@@ -736,28 +737,30 @@ <h2>Source</h2>
736737
bpy.context.view_layer.update()
737738
print(
738739
<span class="s">f&quot;</span><span class="s">render_trap_jump=</span>{jumped:<span class="s">.4f</span>}<span class="s"> </span><span class="s">&quot;</span>
739-
<span class="s">f&quot;</span><span class="s">acc_at=</span>{tuple(round(c, <span class="n">3</span>) <span class="k">for</span> c <span class="k">in</span> trap_acc.matrix_world.translation)}<span class="s">&quot;</span>
740+
<span class="s">f&quot;</span><span class="s">acc_at=</span>{tuple(round(c, <span class="n">3</span>) <span class="k">for</span> c <span class="k">in</span> trap_acc.matrix_world.translation)}<span class="s"> </span><span class="s">&quot;</span>
741+
<span class="s">f&quot;</span><span class="s">sock_at=</span>{tuple(round(c, <span class="n">3</span>) <span class="k">for</span> c <span class="k">in</span> left_front)}<span class="s"> </span><span class="s">&quot;</span>
742+
<span class="s">f&quot;</span><span class="s">sep=</span>{(trap_acc.matrix_world.translation - left_front).length:<span class="s">.4f</span>}<span class="s">&quot;</span>
740743
)
741744

742745
parent_keep_world(keep_acc, right)
743746
bpy.context.view_layer.update()
744747

745-
<span class="c"># Origin markers at base — small, so they don&#x27;t compete with the accessory</span>
746748
origin_marker(sc, Vector(left.matrix_world.translation))
747749
origin_marker(sc, Vector(right.matrix_world.translation))
748750

749-
placard(sc, <span class="s">&quot;TRAP&quot;</span>, (<span class="n">0.0</span>, -<span class="n">1.05</span>, <span class="n">0.02</span>), size=<span class="n">0.16</span>)
750-
placard(sc, <span class="s">&quot;MPI KEEP&quot;</span>, (<span class="n">2.0</span>, -<span class="n">1.05</span>, <span class="n">0.02</span>), size=<span class="n">0.16</span>)
751+
placard(sc, <span class="s">&quot;TRAP&quot;</span>, (<span class="n">0.15</span>, -<span class="n">1.25</span>, <span class="n">0.02</span>), size=<span class="n">0.11</span>)
752+
placard(sc, <span class="s">&quot;MPI KEEP&quot;</span>, (<span class="n">2.1</span>, -<span class="n">1.25</span>, <span class="n">0.02</span>), size=<span class="n">0.11</span>)
751753

752754
build_studio(sc)
753755

754756
cam_data = bpy.data.cameras.new(<span class="s">&quot;Cam&quot;</span>)
755-
cam_data.lens = <span class="n">42.0</span>
757+
cam_data.lens = <span class="n">48.0</span>
756758
cam = bpy.data.objects.new(<span class="s">&quot;Cam&quot;</span>, cam_data)
757-
cam.location = (<span class="n">0.95</span>, -<span class="n">5.6</span>, <span class="n">1.55</span>)
759+
<span class="c"># Closer for fill; placards still clear the bottom</span>
760+
cam.location = (<span class="n">1.15</span>, -<span class="n">5.35</span>, <span class="n">1.45</span>)
758761
sc.collection.objects.link(cam)
759762
aim = bpy.data.objects.new(<span class="s">&quot;Aim&quot;</span>, <span class="k">None</span>)
760-
aim.location = (<span class="n">0.95</span>, <span class="n">0.0</span>, <span class="n">0.72</span>)
763+
aim.location = (<span class="n">1.1</span>, <span class="n">0.0</span>, <span class="n">0.70</span>)
761764
sc.collection.objects.link(aim)
762765
tr = cam.constraints.new(<span class="s">&quot;TRACK_TO&quot;</span>)
763766
tr.target = aim

‎examples/prop-origin-transform/README.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ MPI leaves the accessory teleported.
3737
**Version witness:** byte-identical numbers on Blender 4.5.11 LTS and 5.1.2.
3838

3939
The render is a dual panel: left **TRAP** (bare parent — flanged conduit
40-
teleports off an empty mount socket, hazard material) vs right **MPI KEEP**
41-
(flange seated in the socket), with emissive origin markers at each pedestal's
42-
base. Check closed forms are unchanged; the flange/socket are render form only.
40+
teleports off a deep shadowed mount socket) vs right **MPI KEEP** (same metal
41+
fitting seated in the socket), with emissive origin markers at each pedestal's
42+
base. Accessory and pedestal stay in one material family so displacement — not
43+
color — carries the proof. Check closed forms are unchanged.
4344

4445
## Run
4546

-791 KB
Loading

0 commit comments

Comments
 (0)