Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/gallery/assets/gltf-export-roundtrip-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/gallery/assets/vertex-weight-limit-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/gallery/assets/vse-cut-list-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 7 additions & 4 deletions docs/gallery/gltf-export-roundtrip/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ <h1>gltf-export-roundtrip</h1>
</div>
<section class="detail-section md">
<p>A runnable example that builds a sci-fi supply crate — 35 beveled box shells with three material slots and box-mapped UVs — exports it with <code>bpy.ops.export_scene.gltf</code>, parses the file on disk, re-imports it, and verifies the whole round-trip against the depsgraph-evaluated mesh, following <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/skills/depsgraph-and-evaluated-data/SKILL.md"><code>depsgraph-and-evaluated-data</code></a> and <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/skills/headless-batch-scripting/SKILL.md"><code>headless-batch-scripting</code></a>.</p>
<p><strong>Pipeline arc:</strong> modeling/LOD in <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/lod-decimate-chain"><code>lod-decimate-chain</code></a>, weighting in <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/vertex-weight-limit"><code>vertex-weight-limit</code></a>, export here.</p>
<p><strong>What it witnesses:</strong> the interchange contracts AI-generated export code most often gets silently wrong.</p>
<p>1. <strong>The +Y-up convention is baked into vertex data.</strong> glTF is +Y-up, Blender is +Z-up, and <code>export_yup=True</code> (the default) writes <code>(x, y, z) -&gt; (x, z, -y)</code> directly into the POSITION buffer — the node carries <strong>no</strong> rotation or scale. The check parses the <code>.gltf</code> JSON and asserts the accessor bounds equal the axis-converted evaluated bounding box, and that the node transform is absent. Exporting with <code>export_yup=False</code> ships raw Z-up data every engine displays lying on its back. 2. <strong><code>export_apply=True</code> ships the evaluated mesh, not the base cage.</strong> The crate&#x27;s bevel modifier lives only in the depsgraph; with flat shading and UV seams the exporter splits exactly one vertex per evaluated loop (7,560), so the on-disk POSITION count is an exact witness. <code>export_apply=False</code> silently writes the 624-vertex cage. 3. <strong>The round-trip is faithful.</strong> Re-imported positions (bit-exact here), loop normals (≤2e-4), box-mapped UVs (≤3e-5), and per-triangle material bindings all match the evaluated mesh. UVs are V-flipped on disk (glTF texture origin is top-left) and flipped back on import — both flips are proven by reading the <code>.bin</code> buffer directly.</p>
<p><strong>Version witness (probed on Blender 4.5.11 LTS and 5.1.2):</strong> the operator signatures are byte-identical — 109 exporter properties, 20 importer properties, same defaults — and the exported JSON differs only in <code>asset.generator</code> (&quot;Khronos glTF Blender I/O v4.5.51&quot; vs &quot;v5.1.20&quot;). The example therefore runs identical kwargs on both versions and guards forward drift explicitly: every kwarg it passes must still exist in the operator&#x27;s RNA, so a future rename fails loudly instead of drifting silently. One genuine 5.x removal surfaced during authoring: <code>Mesh.calc_normals()</code> is gone (loop normals auto-compute on read) — calling it is itself a cross-version hazard, noted in the code.</p>
Expand Down Expand Up @@ -680,15 +681,17 @@ <h2>Source</h2>
pm = bpy.data.materials.new(<span class="s">&quot;PlaqueMetal&quot;</span>)
pm.use_nodes = <span class="k">True</span>
pb = pm.node_tree.nodes[<span class="s">&quot;Principled BSDF&quot;</span>]
pb.inputs[<span class="s">&quot;Base Color&quot;</span>].default_value = (<span class="n">0.16</span>, <span class="n">0.17</span>, <span class="n">0.19</span>, <span class="n">1.0</span>)
pb.inputs[<span class="s">&quot;Metallic&quot;</span>].default_value = <span class="n">0.9</span>
pb.inputs[<span class="s">&quot;Roughness&quot;</span>].default_value = <span class="n">0.3</span>
<span class="c"># diffuse light-grey, not metal: the AUTHORED/ROUND-TRIP labels are the</span>
<span class="c"># render&#x27;s argument and must survive thumbnail scale on the dark floor</span>
pb.inputs[<span class="s">&quot;Base Color&quot;</span>].default_value = (<span class="n">0.42</span>, <span class="n">0.44</span>, <span class="n">0.48</span>, <span class="n">1.0</span>)
pb.inputs[<span class="s">&quot;Metallic&quot;</span>].default_value = <span class="n">0.2</span>
pb.inputs[<span class="s">&quot;Roughness&quot;</span>].default_value = <span class="n">0.6</span>

<span class="k">def</span> plaque(text, x):
cu = bpy.data.curves.new(<span class="s">&quot;Plaque&quot;</span>, <span class="s">&#x27;FONT&#x27;</span>)
cu.body = text
cu.align_x = <span class="s">&#x27;CENTER&#x27;</span>
cu.size = <span class="n">0.24</span>
cu.size = <span class="n">0.30</span>
cu.extrude = <span class="n">0.008</span>
ob = bpy.data.objects.new(<span class="s">&quot;Plaque&quot;</span>, cu)
ob.location = (x, -<span class="n">1.55</span>, <span class="n">0.01</span>)
Expand Down
1 change: 1 addition & 0 deletions docs/gallery/lod-decimate-chain/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ <h1>lod-decimate-chain</h1>
</div>
<section class="detail-section md">
<p>A runnable example that builds a retro toy rocket — a lathed body with an ogive nose, three swept fin plates, a porthole, every dimension a closed form — and evaluates it at LOD0/1/2 through the Decimate modifier via the depsgraph, following <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/skills/depsgraph-and-evaluated-data/SKILL.md"><code>depsgraph-and-evaluated-data</code></a> and <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>.</p>
<p><strong>Pipeline arc:</strong> modeling/LOD here, weighting in <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/vertex-weight-limit"><code>vertex-weight-limit</code></a>, export in <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/gltf-export-roundtrip"><code>gltf-export-roundtrip</code></a>.</p>
<p><strong>What it witnesses:</strong> the modifier-based LOD contract that game asset pipelines rely on.</p>
<p>1. <strong>Decimate is non-destructive and lives in the depsgraph.</strong> The evaluated mesh carries the reduction; the original datablock is byte-identical before and after evaluation. The check proves both — evaluated counts drop while <code>obj.data</code> keeps the closed-form counts (1,147 verts / 2,260 tris) — and every evaluated reference is released with <code>to_mesh_clear()</code>. An LOD chain that bakes the reduction into <code>obj.data</code> destroys the asset&#x27;s LOD0. 2. <strong>The COLLAPSE ratio is a target, not a guarantee.</strong> Each LOD&#x27;s evaluated triangle count must land within 5% of <code>ratio x base_tris</code> — measured 0.00% at ratio 0.5 (1,130 tris) and 0.44% at ratio 0.18 (405 tris). A stacked second Decimate halves the effective ratio (50% excursion, caught); a dropped modifier leaves evaluated == original (caught). 3. <strong>LODs preserve silhouette-critical dimensions.</strong> Height (3.12) and fin span are the rocket&#x27;s readability; the evaluated bbox holds the base bbox within 1e-3 at every level (measured 7.7e-6). Decimate has no vertex pinning, so this is a real risk, not a formality: at an aggressive ratio 0.02 the nose tip collapses (bbox drift 0.0206, caught).</p>
<p>The Decimate modifier API (<code>decimate_type=&#x27;COLLAPSE&#x27;</code>, <code>ratio</code>) is stable between Blender 4.5 LTS and 5.1 — the example runs identically on both, which is itself the version witness (measured values match to the digit).</p>
Expand Down
11 changes: 7 additions & 4 deletions docs/gallery/vertex-weight-limit/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,11 @@ <h1>vertex-weight-limit</h1>
</div>
<section class="detail-section md">
<p>A runnable example that rigs a mech arm — pedestal mount and pauldron, orange armor shells, ribbed flex boots, wrist, three claw prongs — with deliberately rich five-bone weight bumps in the boots, then enforces the game-engine <strong>maximum of four bone influences per vertex</strong> through the data API, following <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 building on the linear-blend-skinning precedent of <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/armature-bend"><code>armature-bend</code></a>.</p>
<p><strong>Pipeline arc:</strong> modeling/LOD in <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/lod-decimate-chain"><code>lod-decimate-chain</code></a>, weighting here, export in <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/gltf-export-roundtrip"><code>gltf-export-roundtrip</code></a>.</p>
<p><strong>What it witnesses:</strong> the skinning constraint every game engine enforces and AI-generated rigging code most often violates silently.</p>
<p>1. <strong>The limit is a data-API operation, not a context operator.</strong> Instead of <code>bpy.ops.object.vertex_group_limit_total</code>, the example reads each vertex&#x27;s groups, keeps the top four by weight, <code>VertexGroup.remove</code>s the rest, and renormalizes the survivors. Dropping without renormalizing leaves sums at 0.986 — a mesh that shrinks toward the origin under load (the check&#x27;s measured failure, 1.438e-02 off unit sum). 2. <strong>The armature modifier is still exactly linear blend skinning</strong> after the limit: every depsgraph-evaluated vertex equals <code>Σ wᵢ · (pose.matrix @ bone.matrix_local.inverted()) @ rest</code>, with the weights <strong>read back from the mesh&#x27;s own deform layer</strong> (<code>v.groups</code>) — the weights on the mesh are the contract, not the weights you meant to write. Measured <code>lbs_err = 3.0e-07</code>. 3. <strong>Pruning must not damage the pose.</strong> Evaluated positions before and after the limit are held within 0.05 (measured 3.0e-03), the pedestal mount stays exactly pinned (Root is unposed), and the pre-limit authoring really carries five influences in the boots — otherwise the witness would be vacuous.</p>
<p>The vertex-group API (<code>v.groups</code>, <code>VertexGroup.add</code>/<code>remove</code>) is stable between Blender 4.5 LTS and 5.1 — the example runs identically on both, which is itself the version witness (measured values match to the digit).</p>
<p>The render shows the pruned arm mid-pose: the elbow bellows and wrist boot flex through the bend, the teal accent ring rides the forearm — proof that the limited weights still deform as authored.</p>
<p>The render shows the pruned arm mid-pose: the flex boots carry the teal accent — the five-influence zones the limit prunes glow at the elbow and wrist, sealed by the bright ring on the elbow boot — proof that the limited weights still deform as authored.</p>
<h2>Run</h2>
<pre><code># Cheap correctness check (no render) — the CI check:
blender --background --python vertex_weight_limit.py --
Expand Down Expand Up @@ -315,8 +316,9 @@ <h2>Source</h2>
(<span class="n">1.82</span>, <span class="n">0.26</span>), (<span class="n">2.00</span>, <span class="n">0.24</span>), (<span class="n">2.10</span>, <span class="n">0.27</span>),
(<span class="n">2.16</span>, <span class="n">0.27</span>), (<span class="n">2.22</span>, <span class="n">0.24</span>), (<span class="n">2.45</span>, <span class="n">0.22</span>)],
ORANGE), <span class="s">&quot;Elbow&quot;</span>)
<span class="c"># teal accent ring inset on the forearm</span>
tag(lathe_part(bm, [(<span class="n">2.10</span>, <span class="n">0.275</span>), (<span class="n">2.16</span>, <span class="n">0.275</span>)], ACCENT), <span class="s">&quot;Elbow&quot;</span>)
<span class="c"># teal accent ring sealing the elbow boot — the primary</span>
<span class="c"># five-influence zone the limit prunes</span>
tag(lathe_part(bm, [(<span class="n">1.33</span>, <span class="n">0.245</span>), (<span class="n">1.39</span>, <span class="n">0.245</span>)], ACCENT), <span class="s">&quot;Elbow&quot;</span>)
<span class="c"># wrist flex boot + ball (second &gt;4-influence region)</span>
tag(lathe_part(bm, [(<span class="n">2.45</span>, <span class="n">0.21</span>), (<span class="n">2.52</span>, <span class="n">0.17</span>), (<span class="n">2.60</span>, <span class="n">0.20</span>),
(<span class="n">2.68</span>, <span class="n">0.16</span>), (<span class="n">2.76</span>, <span class="n">0.19</span>), (<span class="n">2.84</span>, <span class="n">0.16</span>), (<span class="n">2.95</span>, <span class="n">0.15</span>)], RUBBER), <span class="s">&quot;FLEX&quot;</span>)
Expand Down Expand Up @@ -518,7 +520,8 @@ <h2>Source</h2>
<span class="k">return</span> [
pbr(<span class="s">&quot;Gunmetal&quot;</span>, (<span class="n">0.11</span>, <span class="n">0.12</span>, <span class="n">0.14</span>), <span class="n">0.9</span>, <span class="n">0.32</span>),
pbr(<span class="s">&quot;HazardOrange&quot;</span>, (<span class="n">0.82</span>, <span class="n">0.30</span>, <span class="n">0.08</span>), <span class="n">0.10</span>, <span class="n">0.45</span>),
pbr(<span class="s">&quot;FlexRubber&quot;</span>, (<span class="n">0.05</span>, <span class="n">0.05</span>, <span class="n">0.06</span>), <span class="n">0.0</span>, <span class="n">0.85</span>),
pbr(<span class="s">&quot;FlexRubber&quot;</span>, (<span class="n">0.04</span>, <span class="n">0.13</span>, <span class="n">0.17</span>), <span class="n">0.0</span>, <span class="n">0.80</span>,
emission=(<span class="n">0.06</span>, <span class="n">0.30</span>, <span class="n">0.34</span>), strength=<span class="n">0.38</span>),
pbr(<span class="s">&quot;TealAccent&quot;</span>, (<span class="n">0.03</span>, <span class="n">0.22</span>, <span class="n">0.26</span>), <span class="n">0.0</span>, <span class="n">0.35</span>,
emission=(<span class="n">0.10</span>, <span class="n">0.65</span>, <span class="n">0.72</span>), strength=<span class="n">2.2</span>),
]
Expand Down
Loading
Loading