Resources Numerical accuracy Calculation policy

3D view of the part

Draws the part, its edges, highlights and result colors in the 3D view, and finds what is under the pointer.

How the precision is kept

The view copies the part's triangles into Float32Array buffers for the GPU and draws them with three.js. Result colors arrive as 32-bit color arrays. Picking casts a ray against these display buffers. Nothing in this file computes a dimension, a mass property or an analysis result.

Calculations under this policy

3 calculations 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.

view3d.js PartCraft 0.8.1 32-bit, display only 3 calculations Reviewed

Tests None found by the review

Triangle buffer for drawing

32-bit, display only

Copies the part's mesh into the vertex buffer the GPU draws.

Each triangle corner's coordinates are copied into a Float32Array of nine values per triangle. The part's own mesh object is kept unchanged beside it.

  • view3d.js
  • lines 215 to 218
  • PartCraft 0.8.1
const pos = new Float32Array(nt * 9);
for (let t = 0; t < nt; t++) for (let k = 0; k < 3; k++) for (let c = 0; c < 3; c++) pos[t * 9 + k * 3 + c] = P[I[t * 3 + k] * 3 + c];
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));

Result colors on the part

32-bit, display only

Spreads per-vertex or per-corner result colors onto the drawn triangles.

Colors are copied into a Float32Array per triangle corner. Only colors pass through here; the result values behind them are not in this file.

  • view3d.js
  • lines 251 to 270
  • PartCraft 0.8.1
/** Colour the part (a result plot): RGB 0…1 per mesh vertex (length = mesh.positions.length) or per triangle corner
 *  (length = 9 × triangles); null = the plain body colour. setMesh (every rebuild) clears it. */
setFaceColors(c) {
  if (!this.mesh) return;
  const geo = this.mesh.geometry, mat = this.mesh.material;
  if (!c) {
    if (geo.getAttribute('color')) geo.deleteAttribute('color');
    mat.vertexColors = false; mat.color.copy(this.bodyColor); mat.needsUpdate = true; this.render(); return;
  }
  const I = this.data.indices, nt = I.length / 3;
  let col;
  if (c.length === nt * 9) col = Float32Array.from(c);
  else if (c.length === this.data.positions.length) {
    col = new Float32Array(nt * 9);
    for (let t = 0; t < nt; t++) for (let k = 0; k < 3; k++) for (let j = 0; j < 3; j++) col[t * 9 + k * 3 + j] = c[I[t * 3 + k] * 3 + j];
  } else throw new Error(`setFaceColors: ${c.length} values for ${this.data.positions.length / 3} vertices / ${nt} triangles.`);   // i18n-ignore: a programming error (wrong colour array), never shown to a person
  geo.setAttribute('color', new THREE.BufferAttribute(col, 3));
  mat.vertexColors = true; mat.color.set(0xffffff); mat.needsUpdate = true;
  this.render();
}

What is under the pointer

32-bit, display only

Finds the face, edge or plane under the pointer.

A three.js ray is cast against the 32-bit display buffers. The returned hit point, and an edge's end points read from the edge buffer, therefore have 32-bit coordinates. Hits are ordered by distance with fixed offsets so an edge wins over the face it bounds.

  • view3d.js
  • lines 644 to 700
  • PartCraft 0.8.1
pickAt(clientX, clientY, { planes = this.planes.visible, edges = true } = {}) {
  const ray = this.ray(clientX, clientY);
  const hits = [];
  if (this.mesh && this.partVisible) {
    const h = this.keptHit(ray.intersectObject(this.mesh, false));
    if (h) {
      const tri = this.visTri ? this.visTri[h.faceIndex] : h.faceIndex;   // hidden bodies: drawn index → mesh triangle
      const n = h.face.normal.clone();
      const fi = this.data.triFeature[tri];
      const grp = this.planarGroups();
      const g = grp.faceOf[tri];
      const group = grp.groups[g];
      const planar = group.tris.length > 1 || true;
      const face = this.data.triFace ? this.data.triFace[tri] : undefined;
      if (face !== undefined) n.copy(triNormal3(this.data, tri));
      hits.push({ d: h.distance, hit: { kind: 'face', tri, point: h.point.toArray(), normal: n.toArray(), featureIndex: fi, feature: fi >= 0 ? this.data.featIds[fi] : null, tris: group.tris, group: g, planar, face } });
    }
  }
  if (this.instances?.length) {
    const h = this.keptHit(ray.intersectObjects(this.instances.map((o) => o.mesh), false));
    if (h) {
      const it = this.instances.find((o) => o.mesh === h.object);
      const tri = h.faceIndex;
      const inv = new THREE.Matrix4().fromArray(it.matrix).invert();
      const local = h.point.clone().applyMatrix4(inv);
      const g = it.groups.faceOf[tri];
      const fi = it.data.triFeature[tri];
      hits.push({ d: h.distance, hit: { kind: 'face', inst: it.id, tri, point: local.toArray(), normal: h.face.normal.toArray(), worldPoint: h.point.toArray(), featureIndex: fi, feature: fi >= 0 ? it.data.featIds[fi] : null, tris: it.groups.groups[g].tris, group: g, data: it.data, matrix: it.matrix, face: it.data.triFace ? it.data.triFace[tri] : undefined } });
    }
  }
  if (edges && this.edges && this.partVisible) {
    const h = this.keptHit(ray.intersectObject(this.edges, false));
    if (h) {
      const a = h.object.geometry.attributes.position;
      const i = h.index;
      const A = [a.getX(i), a.getY(i), a.getZ(i)], B = [a.getX(i + 1), a.getY(i + 1), a.getZ(i + 1)];
      const edge = this.edgeOfSeg ? this.edgeOfSeg[i / 2] : undefined;
      // an edge wins over the face it bounds when the pointer is right on it
      hits.push({ d: h.distance - (this.camera.far * 1e-4), hit: { kind: 'edge', a: A, b: B, edge } });
    }
  }
  if (this.refs.children.length && !this.sketchFrame) {
    const lines = this.refs.children.filter((o) => o.isLine);
    const hl = ray.intersectObjects(lines, false)[0];
    if (hl) hits.push({ d: hl.distance - (this.camera.far * 2e-4), hit: { kind: 'ref', id: hl.object.userData.ref } });
    const fills = this.refs.children.filter((o) => o.isMesh);
    const hf = ray.intersectObjects(fills, false)[0];
    if (hf) hits.push({ d: hf.distance + (this.mesh ? 1e9 : 0), hit: { kind: 'ref', id: hf.object.userData.ref } });
  }
  if (planes && this.planes.visible && !this.planesFaint) {
    const fills = this.planes.children.filter((g) => g.userData.plane).map((g) => g.children[0]);
    const h = ray.intersectObjects(fills, false)[0];
    if (h) hits.push({ d: h.distance + (this.mesh ? 1e9 : 0), hit: { kind: 'plane', name: h.object.userData.plane } });
  }
  hits.sort((p, q) => p.d - q.d);
  return hits.length ? hits[0].hit : null;
}

In detail

Method
three.js with an orthographic camera; one vertex per triangle corner for flat shading; ray casting for picks.
Number types
Float32Array for positions and colors sent to the GPU.
Limits
pickAt returns the hit point and an edge's two end points as read from the 32-bit display buffers; what callers do with them is outside this file.

Approach: Display only

Computed to draw the screen, often in 32-bit on the graphics processor. No number a person reads, saves or exports comes from it.

Every policy that takes this approach