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