Lets a script build implicit shapes, sample them, turn them into a body, measure its volume and check it for manufacturing.
How the precision is kept
The builtins pass 64-bit script values to the field library. Two steps narrow to 32 bits: the contour mesh that to_body and field_volume use comes back from field/contour.js as a Float32Array, and a density map given to field_density is stored in a Float32Array. Bodies are contoured from a grid of spacing h, which also limits the accuracy of the shape, its volume and its area.
Open item. Keep the contour positions and the density map in 64-bit: field/contour.js builds its positions in a Float64Array and converts them to Float32Array when it returns.
Calculations under this policy
6 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.
engineer/cs-lib-field.jsPartCraft 0.8.1 32-bit in part6 calculationsReviewed
TestsNone found by the review
Body, volume and area from a field
32-bit in part
The mesh that to_body returns, with its volume, area and triangle count.
FD.contour returns its positions as a Float32Array (field/contour.js). Float64Array.from here widens those 32-bit values without restoring digits, and the volume and area are computed from the same 32-bit positions. The body's nodes and the reported volume and area therefore carry about 7 significant digits.
The field is baked on a grid of spacing h, contoured, and the contour's volume is returned. The contour positions are 32-bit, and the grid spacing sets how closely the contour follows the shape.
Turns a 0 to 1 density array into a profile that drives a cell size or wall thickness.
d = clamp(D, 0, 1), stored in 32 bits; size = v₀ + (v₁ − v₀) × d
Each density is clamped to 0…1 and written into a Float32Array, so it keeps about 7 significant digits. FD.density in field/field.js keeps it as 32-bit as well. Rows are flipped so that row 1 of the array is the top.
engineer/cs-lib-field.js
lines 90 to 101
PartCraft 0.8.1
field_density: [wrap((a) => {
const D = asTensor(a[0], 'density'), shape = D.shape.length === 3 ? D.shape : [D.shape[0], D.shape[1], 1];
const [ny, nx, nz] = shape; // rows are y from the top (image order), columns x, pages z
const box = Array.from(asTensor(a[1], 'box').data);
if (box.length !== 6) throw err(t('script.field.bbox6'));
const v0 = N(a[2], 'v0'), v1 = N(a[3], 'v1'), data = new Float32Array(nx * ny * nz);
for (let k = 0; k < nz; k++) for (let j = 0; j < ny; j++) for (let i = 0; i < nx; i++) {
const dv = D.data[j + ny * (i + nx * k)]; // column-major tensor: (row j, column i, page k)
data[i + nx * ((ny - 1 - j) + ny * k)] = dv < 0 ? 0 : dv > 1 ? 1 : dv; // row 1 is the top: y runs up, so flip
}
return { t: 'sdf', kind: 'dens', scalar: true, spec: FD.density(data, [nx, ny, nz], box, v0, v1) };
}), 'p = field_density(D, box, v0, v1)'],
Distance at points
64-bit
The signed distance of a field at each row of a points matrix.
Calls the compiled field function once per point with 64-bit coordinates and stores the results in a Float64Array.
engineer/cs-lib-field.js
lines 138 to 144
PartCraft 0.8.1
sample: [(a) => {
const f = FD.compile(fieldArg(a[0])).f, P = asTensor(a[1], 'points'), [n, c] = P.shape;
if (c !== 3) throw err(t('script.field.points_shape'));
const d = new Float64Array(n);
for (let i = 0; i < n; i++) d[i] = f(P.data[i], P.data[i + n], P.data[i + 2 * n]);
return mat(n, 1, d);
}, 'd = sample(f, points)'],
Inside or outside on a box grid
64-bit
Marks each cell of a box as inside (−1) or outside (0) the field.
nᵢ = round((maxᵢ − minᵢ) ÷ h); sample at min + (i + ½) × h
The cell counts are rounded to whole numbers, so the grid covers the box exactly only when its sides are multiples of h. Each cell center is tested with the 64-bit field function. At most 2.5 × 10⁷ cells.
engineer/cs-lib-field.js
lines 156 to 164
PartCraft 0.8.1
field_kind: [wrap((a, ctx) => {
const f = FD.compile(fieldArg(a[0])).f, hh = posArg(a[1], 'h'), b = Array.from(asTensor(a[2], 'box').data);
if (b.length !== 6) throw err(t('script.field.bbox6'));
const n = [0, 1, 2].map((i) => Math.round((b[i + 3] - b[i]) / hh));
if (n.some((x) => x < 1) || n[0] * n[1] * n[2] > 2.5e7) throw err(t('script.field.too_big', { n: n[0] * n[1] * n[2] }));
const d = new Float64Array(n[0] * n[1] * n[2]), check = checker(ctx);
for (let k = 0; k < n[2]; k++) { check(); for (let j = 0; j < n[1]; j++) for (let i = 0; i < n[0]; i++) d[i + n[0] * (j + n[1] * k)] = f(b[0] + (i + 0.5) * hh, b[1] + (j + 0.5) * hh, b[2] + (k + 0.5) * hh) < 0 ? -1 : 0; }
return tensor(n, d);
}), 'kind = field_kind(f, h, box)'],
Overhang reported by the manufacturing check
64-bit
The overhang problem mfg_check adds, with its area and locations.
reported when area > 4 × h²
The area is rounded to 2 decimals for the found value and to 1 decimal for the message, the share to 0.1 %, and each location to 3 decimals, before they are stored in the result the script receives. The mesh the check runs on is the 32-bit contour.
Shapes are signed-distance fields; a body is made by baking the field on a grid of spacing h and contouring it (field/contour.js, dual contouring).
Number types
64-bit numbers and Float64Array for points and samples; Float32Array for the contour mesh positions and for the density map.
Limits
Body nodes, volume and area come from 32-bit positions. The overhang area and positions reported by mfg_check are rounded to 2 and 3 decimals before they are stored in the result.
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.