Resources Numerical accuracy Calculation policy

Drawing section view of a faceted part

Cuts the faceted solid with the section plane and draws what is behind it, with the cut outline.

How the precision is kept

The mesh positions are copied into a Float32Array to build the manifold-3d solid, the solid is trimmed by the plane, and the result's vertices are read back from the kernel's 32-bit array into 64-bit. The section view's lines, cut outline and extents are computed from those 32-bit positions. Facets are taken to lie in the cutting plane when their corners are within 1e-6 of the part's diagonal from it.

Open item. Keep the section's vertices in 64-bit, for example by cutting the 64-bit mesh directly, instead of passing them through the kernel's 32-bit vertex array.

Calculations under this policy

1 calculation in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

drawing.js PartCraft 0.8.1 32-bit in part 1 calculation Reviewed

Tests bin/test-partcraft.mjs

Section of a faceted mesh

32-bit in part

The section view's visible lines, cut outline, extents and the cutting plane's trace.

Float32Array.from(P) rounds every coordinate to 32-bit before the cut, and the cut solid's vertices come back from a 32-bit array. The section's lines and box in the PDF therefore carry about 7 significant digits. The trace of the plane in the neighboring view is computed in 64-bit from the bounding box center and does not pass through the 32-bit data.

  • drawing.js
  • lines 233 to 297
  • PartCraft 0.8.1
/**
 * A section view of a faceted mesh (0.4 K.3, the mesh kernel's own): the solid cut by the plane square to view `sec.of` at
 * `sec.at` along its direction (default: through the middle of the part), the viewer's side taken away (manifold
 * trimByPlane), the rest projected like the other views (visible lines only, as brep-draw.js does); `cut` = the outline of
 * the facets lying in the cutting plane (drawing.js hatches inside it); `trace` = where the plane shows edge-on in the
 * neighbouring view (SECTION_TRACE). The same shape as brep-draw.js's views.section; null when nothing is left behind the plane.
 */
export function meshSection(M, mesh, views, sec) {
  const view = VIEWS[sec.of];
  const P = mesh.positions;
  const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
  for (let i = 0; i < P.length; i += 3) for (let k = 0; k < 3; k++) { min[k] = Math.min(min[k], P[i + k]); max[k] = Math.max(max[k], P[i + k]); }
  const centre = V.mul(V.add(min, max), 0.5);
  const size = Math.max(1e-6, V.len(V.sub(max, min)));
  const at = Number.isFinite(sec.at) ? sec.at : V.dot(centre, view.dir);
  const o = V.add(V.sub(centre, V.mul(view.dir, V.dot(centre, view.dir))), V.mul(view.dir, at));
  const live = [];
  try {
    const gl = new M.Mesh({ numProp: 3, vertProperties: Float32Array.from(P), triVerts: Uint32Array.from(mesh.indices) });
    gl.merge();
    const whole = new M.Manifold(gl); live.push(whole);
    // trimByPlane keeps what lies on the normal's side: keep n·p ≥ −at with n = −dir, i.e. dir·p ≤ at (away from the viewer)
    const rest = whole.trimByPlane(V.mul(view.dir, -1), -at); live.push(rest);
    if (rest.isEmpty()) return null;
    const g = rest.getMesh();
    const np = g.numProp, nv = g.vertProperties.length / np;
    const pos = new Float64Array(nv * 3);
    for (let i = 0; i < nv; i++) for (let k = 0; k < 3; k++) pos[i * 3 + k] = g.vertProperties[i * np + k];
    const cutMesh = { positions: pos, indices: Uint32Array.from(g.triVerts) };
    const v = projectView(cutMesh, meshEdges(cutMesh), view, { hidden: false });
    // facets in the plane facing the viewer: the edges only one of them uses are the cut's outline
    const tol = 1e-6 * size, I = cutMesh.indices, nt = I.length / 3;
    const use = new Map(), runs = new Map();   // undirected key → count, and the edge as its facet runs it
    const pt = (i) => [pos[i * 3], pos[i * 3 + 1], pos[i * 3 + 2]];
    for (let t = 0; t < nt; t++) {
      const a = pt(I[t * 3]), b = pt(I[t * 3 + 1]), c = pt(I[t * 3 + 2]);
      if ([a, b, c].some((q) => Math.abs(V.dot(q, view.dir) - at) > tol)) continue;
      const n = V.cross(V.sub(b, a), V.sub(c, a));
      if (V.dot(n, view.dir) <= 0) continue;
      for (const [p, q] of [[I[t * 3], I[t * 3 + 1]], [I[t * 3 + 1], I[t * 3 + 2]], [I[t * 3 + 2], I[t * 3]]]) {
        const key = p < q ? p + ',' + q : q + ',' + p;
        use.set(key, (use.get(key) || 0) + 1);
        runs.set(key, [p, q]);
      }
    }
    // outline segments keep their facet's direction: counter-clockwise round the cut seen from the viewer (holes clockwise)
    const cut = [];
    for (const [key, n] of use) {
      if (n !== 1) continue;
      const [p, q] = runs.get(key);
      const a = pt(p), b = pt(q);
      cut.push([V.dot(a, view.right), V.dot(a, view.up), V.dot(b, view.right), V.dot(b, view.up)]);
    }
    let u0 = Infinity, v0 = Infinity, u1 = -Infinity, v1 = -Infinity;
    for (const s of v.visible.concat(cut)) { u0 = Math.min(u0, s[0], s[2]); u1 = Math.max(u1, s[0], s[2]); v0 = Math.min(v0, s[1], s[3]); v1 = Math.max(v1, s[1], s[3]); }
    // the cutting plane's trace in the view that sees it edge-on (the same arithmetic as brep-draw.js)
    const traceView = SECTION_TRACE[sec.of], tv = VIEWS[traceView];
    const along = V.norm(V.cross(view.dir, tv.dir));
    const trace = { view: traceView, p: [V.dot(o, tv.right), V.dot(o, tv.up)], d: [V.dot(along, tv.right), V.dot(along, tv.up)],
      sight: [V.dot(V.mul(view.dir, -1), tv.right), V.dot(V.mul(view.dir, -1), tv.up)], box: views[traceView].box };
    return { visible: v.visible, hidden: [], cut, box: u0 === Infinity ? { u0: 0, v0: 0, u1: 1, v1: 1 } : { u0, v0, u1, v1 }, of: sec.of, at, trace };
  } finally {
    for (const m of live) { try { m.delete(); } catch { /* gone */ } }
  }
}

In detail

Method
manifold-3d trimByPlane on the part's mesh, then the same projection as the other views; the cut outline is the set of edges used by exactly one in-plane facet facing the viewer.
Number types
Float32Array into and out of the kernel; Float64Array and 64-bit numbers for the projection.
Tolerances
In-plane test: 1e-6 × the bounding box diagonal, with the diagonal taken as at least 1e-6.

Approach: 32-bit step, flagged

One step stores or exchanges 32-bit values that reach a number a person reads. It is listed here with its open item, and the step is either moved to 64-bit or the app will warn where the number is read.

Every policy that takes this approach