<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Trimesh on Anigeek</title><link>https://blog.anigeek.com/tags/trimesh/</link><description>Recent content in Trimesh on Anigeek</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sat, 28 Mar 2026 03:30:00 -0600</lastBuildDate><atom:link href="https://blog.anigeek.com/tags/trimesh/index.xml" rel="self" type="application/rss+xml"/><item><title>Verifying 3D Prints Without Eyeballs: Ray Probing with Trimesh</title><link>https://blog.anigeek.com/posts/trimesh-ray-probing-cad-verification/</link><pubDate>Sat, 28 Mar 2026 03:30:00 -0600</pubDate><guid>https://blog.anigeek.com/posts/trimesh-ray-probing-cad-verification/</guid><description>When your AI generates a 3D enclosure and your vision model can&amp;#39;t reliably read cross-sections, point-in-mesh probing becomes your best friend.</description><content:encoded><![CDATA[<h2 id="the-problem">The Problem</h2>
<p>You&rsquo;ve just generated a parametric 3D enclosure in Python using <a href="https://build123d.readthedocs.io/">build123d</a> or <a href="https://cadquery.readthedocs.io/">CadQuery</a>. It has screw holes through the walls, a cable slot, a barrel jack cutout, and internal standoffs. The STL exports fine. The STEP looks good in your CAD viewer.</p>
<p>But does the cable slot <em>actually</em> go all the way through the wall? Are the screw holes clipping through the top edge? Is there a paper-thin wall where your subtraction was tangent instead of overlapping?</p>
<p>You could slice the model and look at cross-sections. We tried that. The vision model confidently declared features &ldquo;missing&rdquo; that were present and &ldquo;correct&rdquo; that were broken. Cross-section images of 3D geometry are surprisingly hard to interpret — even for humans, let alone AI.</p>
<p>We needed something deterministic.</p>
<h2 id="the-solution-point-in-mesh-probing">The Solution: Point-in-Mesh Probing</h2>
<p><a href="https://trimsh.org/">Trimesh</a> can tell you whether a point is inside or outside a watertight mesh. That&rsquo;s it. That&rsquo;s the whole trick. But it turns out &ldquo;is this point inside the solid?&rdquo; is the only question you need to verify almost any 3D feature.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> trimesh
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> numpy <span style="color:#66d9ef">as</span> np
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>mesh <span style="color:#f92672">=</span> trimesh<span style="color:#f92672">.</span>load(<span style="color:#e6db74">&#34;enclosure_box.stl&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Is this point inside the mesh (solid material)?</span>
</span></span><span style="display:flex;"><span>point <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>array([[<span style="color:#ae81ff">25.0</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">29.25</span>, <span style="color:#ae81ff">37.25</span>]])
</span></span><span style="display:flex;"><span>is_solid <span style="color:#f92672">=</span> mesh<span style="color:#f92672">.</span>contains(point)[<span style="color:#ae81ff">0</span>]
</span></span></code></pre></div><p>If the point is where a hole should be, <code>is_solid</code> should be <code>False</code>. If it&rsquo;s where wall material should be, it should be <code>True</code>. No image interpretation. No ambiguity. Just geometry.</p>
<h2 id="verifying-wall-holes">Verifying Wall Holes</h2>
<p>Say you have screw holes through the Y± walls of a box. The walls run from Y=±28 (inner face) to Y=±30.5 (outer face). A hole at position X=-25 on the Y- wall should make the wall center hollow:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Wall center point at the screw position</span>
</span></span><span style="display:flex;"><span>wall_center <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>array([[<span style="color:#f92672">-</span><span style="color:#ae81ff">25.0</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">29.25</span>, <span style="color:#ae81ff">37.25</span>]])
</span></span><span style="display:flex;"><span>is_solid <span style="color:#f92672">=</span> mesh<span style="color:#f92672">.</span>contains(wall_center)[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> is_solid:
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;BUG: wall is solid — hole didn&#39;t cut through!&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;Hole verified — wall is hollow at screw position&#34;</span>)
</span></span></code></pre></div><p>This is how we caught a rotation bug where <code>align=UP</code> after <code>rot=(90,0,0)</code> in build123d always grows toward +Y. For Y+ wall holes that&rsquo;s outward (correct). For Y- wall holes that&rsquo;s <em>inward</em> (wrong — the cylinder subtracted from the interior, not the wall). The ray-cast verification caught it instantly:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># All four screw positions</span>
</span></span><span style="display:flex;"><span>BOSS_POSITIONS <span style="color:#f92672">=</span> [(<span style="color:#f92672">-</span><span style="color:#ae81ff">25</span>, <span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>), (<span style="color:#f92672">+</span><span style="color:#ae81ff">15</span>, <span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>), (<span style="color:#f92672">-</span><span style="color:#ae81ff">25</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>), (<span style="color:#f92672">+</span><span style="color:#ae81ff">15</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> bx, wall_sign <span style="color:#f92672">in</span> BOSS_POSITIONS:
</span></span><span style="display:flex;"><span>    wall_center_y <span style="color:#f92672">=</span> wall_sign <span style="color:#f92672">*</span> <span style="color:#ae81ff">29.25</span>  <span style="color:#75715e"># midpoint of wall</span>
</span></span><span style="display:flex;"><span>    point <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>array([[bx, wall_center_y, <span style="color:#ae81ff">37.25</span>]])
</span></span><span style="display:flex;"><span>    is_solid <span style="color:#f92672">=</span> mesh<span style="color:#f92672">.</span>contains(point)[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>    wall <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Y+&#34;</span> <span style="color:#66d9ef">if</span> wall_sign <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">else</span> <span style="color:#e6db74">&#34;Y-&#34;</span>
</span></span><span style="display:flex;"><span>    status <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;SOLID (BUG!)&#34;</span> <span style="color:#66d9ef">if</span> is_solid <span style="color:#66d9ef">else</span> <span style="color:#e6db74">&#34;HOLLOW (OK)&#34;</span>
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;X=</span><span style="color:#e6db74">{</span>bx<span style="color:#e6db74">:</span><span style="color:#e6db74">+d</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> </span><span style="color:#e6db74">{</span>wall<span style="color:#e6db74">}</span><span style="color:#e6db74">: </span><span style="color:#e6db74">{</span>status<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span></code></pre></div><h2 id="verifying-slot-penetration">Verifying Slot Penetration</h2>
<p>A cable slot should create a complete void through the wall. Probe multiple points along the wall thickness:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Cable slot at X=33.2 on Y+ wall</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Wall runs from Y=28.0 (inner) to Y=30.5 (outer)</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> y <span style="color:#f92672">in</span> [<span style="color:#ae81ff">28.0</span>, <span style="color:#ae81ff">28.5</span>, <span style="color:#ae81ff">29.0</span>, <span style="color:#ae81ff">29.5</span>, <span style="color:#ae81ff">30.0</span>, <span style="color:#ae81ff">30.5</span>]:
</span></span><span style="display:flex;"><span>    inside <span style="color:#f92672">=</span> mesh<span style="color:#f92672">.</span>contains(np<span style="color:#f92672">.</span>array([[<span style="color:#ae81ff">33.2</span>, y, <span style="color:#ae81ff">13.6</span>]]))[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Y=</span><span style="color:#e6db74">{</span>y<span style="color:#e6db74">}</span><span style="color:#e6db74">: </span><span style="color:#e6db74">{</span><span style="color:#e6db74">&#39;SOLID&#39;</span> <span style="color:#66d9ef">if</span> inside <span style="color:#66d9ef">else</span> <span style="color:#e6db74">&#39;hollow&#39;</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span></code></pre></div><p>This is how we found that a <code>Box()</code> subtract centered at the wall face with depth <code>WALL + 2</code> left a 0.25mm skin on the inner face. The box was 4.5mm deep, centered at Y=30.5, so it spanned Y=28.25 to Y=32.75 — but the inner wall face was at Y=28.0. A quarter-millimeter gap. Invisible in a cross-section image. Obvious in the point probe:</p>
<pre tabindex="0"><code>Y=28.0: SOLID   ← Bug! Should be hollow.
Y=28.5: hollow
Y=29.0: hollow
...
</code></pre><p>Fix: <code>WALL * 3</code> instead of <code>WALL + 2</code>. Generous oversize on subtractions costs nothing and prevents tangent-face artifacts.</p>
<h2 id="why-not-ray-casting">Why Not Ray Casting?</h2>
<p>Trimesh also supports ray casting (<code>mesh.ray.intersects_location</code>), and we used that too. But it has a subtle failure mode: when a ray passes through <em>two</em> aligned holes (both walls of a box), the near-wall hits can disappear entirely. The ray enters the first hole, exits into the interior, enters the second hole, and exits — but trimesh sometimes reports only the far-wall hits, making it look like the near wall is solid.</p>
<p>Point-in-mesh probing doesn&rsquo;t have this problem. You&rsquo;re asking about a specific location, not tracing a path.</p>
<p>We still use ray casting for some checks (verifying hole <em>size</em> by probing the edge), but <code>contains()</code> is the workhorse for existence checks.</p>
<h2 id="checking-feature-dimensions">Checking Feature Dimensions</h2>
<p>Binary search with <code>contains()</code> can measure features to arbitrary precision:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">find_edge</span>(mesh, start, direction, lo, hi, tol<span style="color:#f92672">=</span><span style="color:#ae81ff">0.01</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;Binary search for the boundary between solid and void.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">while</span> hi <span style="color:#f92672">-</span> lo <span style="color:#f92672">&gt;</span> tol:
</span></span><span style="display:flex;"><span>        mid <span style="color:#f92672">=</span> (lo <span style="color:#f92672">+</span> hi) <span style="color:#f92672">/</span> <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>        point <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>array([start <span style="color:#f92672">+</span> direction <span style="color:#f92672">*</span> mid])
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> mesh<span style="color:#f92672">.</span>contains(point)[<span style="color:#ae81ff">0</span>]:
</span></span><span style="display:flex;"><span>            lo <span style="color:#f92672">=</span> mid
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            hi <span style="color:#f92672">=</span> mid
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> (lo <span style="color:#f92672">+</span> hi) <span style="color:#f92672">/</span> <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Find exact wall thickness at a point</span>
</span></span><span style="display:flex;"><span>outer_y <span style="color:#f92672">=</span> find_edge(mesh, 
</span></span><span style="display:flex;"><span>    start<span style="color:#f92672">=</span>np<span style="color:#f92672">.</span>array([<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">25</span>, <span style="color:#ae81ff">20</span>]),    <span style="color:#75715e"># start inside</span>
</span></span><span style="display:flex;"><span>    direction<span style="color:#f92672">=</span>np<span style="color:#f92672">.</span>array([<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">0</span>]),   <span style="color:#75715e"># probe toward +Y</span>
</span></span><span style="display:flex;"><span>    lo<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>, hi<span style="color:#f92672">=</span><span style="color:#ae81ff">10</span>)
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Wall inner face at Y=</span><span style="color:#e6db74">{</span><span style="color:#ae81ff">25</span> <span style="color:#f92672">+</span> outer_y<span style="color:#e6db74">:</span><span style="color:#e6db74">.2f</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span></code></pre></div><h2 id="the-watertight-prerequisite">The Watertight Prerequisite</h2>
<p>All of this requires a watertight mesh. If your STL has non-manifold faces, <code>contains()</code> returns garbage. We check this first:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>mesh <span style="color:#f92672">=</span> trimesh<span style="color:#f92672">.</span>load(<span style="color:#e6db74">&#34;part.stl&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">assert</span> mesh<span style="color:#f92672">.</span>is_watertight, <span style="color:#e6db74">&#34;Mesh not watertight — contains() will be unreliable&#34;</span>
</span></span></code></pre></div><p>One gotcha we hit: OCP&rsquo;s STL tessellation creates non-manifold meshes when you subtract horizontal cylinders from rounded-rectangle prisms. The STEP geometry is valid, but the triangulation fails at the curved intersection. Workaround: use <code>Box()</code> subtracts (square holes) instead of <code>Cylinder()</code> for features in complex geometry, or increase tessellation quality:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>export_stl(part, <span style="color:#e6db74">&#34;output.stl&#34;</span>, tolerance<span style="color:#f92672">=</span><span style="color:#ae81ff">0.01</span>, angular_tolerance<span style="color:#f92672">=</span><span style="color:#ae81ff">0.05</span>)
</span></span></code></pre></div><h2 id="putting-it-together-an-automated-verify-script">Putting It Together: An Automated Verify Script</h2>
<p>Here&rsquo;s the pattern we use — a <code>verify.py</code> that runs after every <code>enclosure.py</code> build:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> trimesh
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> numpy <span style="color:#66d9ef">as</span> np
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> sys
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">check</span>(name, condition, detail<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;&#34;</span>):
</span></span><span style="display:flex;"><span>    status <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;✓&#34;</span> <span style="color:#66d9ef">if</span> condition <span style="color:#66d9ef">else</span> <span style="color:#e6db74">&#34;✗ FAIL:&#34;</span>
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;  </span><span style="color:#e6db74">{</span>status<span style="color:#e6db74">}</span><span style="color:#e6db74"> </span><span style="color:#e6db74">{</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">+</span> (<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34; — </span><span style="color:#e6db74">{</span>detail<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> <span style="color:#66d9ef">if</span> detail <span style="color:#66d9ef">else</span> <span style="color:#e6db74">&#34;&#34;</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> condition
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>mesh <span style="color:#f92672">=</span> trimesh<span style="color:#f92672">.</span>load(<span style="color:#e6db74">&#34;output/enclosure_box.stl&#34;</span>)
</span></span><span style="display:flex;"><span>passed <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>total <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 1. Mesh integrity</span>
</span></span><span style="display:flex;"><span>total <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>passed <span style="color:#f92672">+=</span> check(<span style="color:#e6db74">&#34;Watertight&#34;</span>, mesh<span style="color:#f92672">.</span>is_watertight)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2. Feature existence (point probes)</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> name, point, expect_hollow <span style="color:#f92672">in</span> [
</span></span><span style="display:flex;"><span>    (<span style="color:#e6db74">&#34;Barrel jack hole&#34;</span>,  [<span style="color:#f92672">-</span><span style="color:#ae81ff">29.25</span>, <span style="color:#ae81ff">10.2</span>, <span style="color:#ae81ff">20.5</span>], <span style="color:#66d9ef">True</span>),
</span></span><span style="display:flex;"><span>    (<span style="color:#e6db74">&#34;Cable slot center&#34;</span>, [<span style="color:#ae81ff">33.2</span>, <span style="color:#ae81ff">29.25</span>, <span style="color:#ae81ff">13.6</span>],  <span style="color:#66d9ef">True</span>),
</span></span><span style="display:flex;"><span>    (<span style="color:#e6db74">&#34;Wall solid&#34;</span>,        [<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">29.25</span>, <span style="color:#ae81ff">20.0</span>],      <span style="color:#66d9ef">False</span>),
</span></span><span style="display:flex;"><span>]:
</span></span><span style="display:flex;"><span>    total <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    is_solid <span style="color:#f92672">=</span> mesh<span style="color:#f92672">.</span>contains(np<span style="color:#f92672">.</span>array([point]))[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>    ok <span style="color:#f92672">=</span> (<span style="color:#f92672">not</span> is_solid) <span style="color:#66d9ef">if</span> expect_hollow <span style="color:#66d9ef">else</span> is_solid
</span></span><span style="display:flex;"><span>    passed <span style="color:#f92672">+=</span> check(name, ok, <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;solid=</span><span style="color:#e6db74">{</span>is_solid<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">{</span>passed<span style="color:#e6db74">}</span><span style="color:#e6db74">/</span><span style="color:#e6db74">{</span>total<span style="color:#e6db74">}</span><span style="color:#e6db74"> passed&#34;</span>)
</span></span><span style="display:flex;"><span>sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">0</span> <span style="color:#66d9ef">if</span> passed <span style="color:#f92672">==</span> total <span style="color:#66d9ef">else</span> <span style="color:#ae81ff">1</span>)
</span></span></code></pre></div><p>Run it after every model generation. If any check fails, the build fails. No more &ldquo;it looked fine in the slicer&rdquo; surprises.</p>
<h2 id="lessons-learned">Lessons Learned</h2>
<ol>
<li>
<p><strong>Vision models can&rsquo;t reliably read CAD cross-sections.</strong> They hallucinate features, misidentify positions, and confidently declare broken geometry correct. Don&rsquo;t trust them for verification.</p>
</li>
<li>
<p><strong>Point probes are deterministic.</strong> <code>contains()</code> returns True or False. No interpretation needed.</p>
</li>
<li>
<p><strong>Oversize your subtractions.</strong> <code>WALL * 3</code> costs nothing. Tangent faces create paper-thin walls that break prints and confuse slicers.</p>
</li>
<li>
<p><strong>Process chamfers longest-first.</strong> OCC&rsquo;s chamfer operation modifies topology. Once you chamfer edge A, nearby edge B might become unchamberable. Do the big visible arcs first.</p>
</li>
<li>
<p><strong>build123d rotation + align is tricky.</strong> <code>align=UP</code> after rotation grows in the <em>rotated</em> +Z direction. For a <code>rot=(90,0,0)</code>, that&rsquo;s world +Y — which is outward for Y+ walls but inward for Y- walls. Use <code>wall_sign * 90</code> for the rotation angle.</p>
</li>
<li>
<p><strong>STL watertight ≠ STEP valid.</strong> OCP can produce valid BREP geometry that tessellates into non-manifold triangles. If your verify script reports non-watertight and the STEP opens fine in a CAD viewer, the tessellation is the problem, not the geometry.</p>
</li>
</ol>
<hr>
<p><em>This post was written at 3:30 AM after a day of designing, printing, debugging, and reprinting a custom ESP32 enclosure. The techniques above caught four separate geometry bugs that would have wasted filament and time. Sleep is for people without parametric models to verify.</em></p>
]]></content:encoded></item></channel></rss>