Resources Numerical accuracy Calculation policy

Beginner shapes, align and drop to ground

Builds the 20 mm starter shapes (box, cylinder, cone, sphere, roof) and moves placements so their boxes line up or stand on the ground.

How the precision is kept

Every shape is built from the constant SIZE = 0.02 m and R = SIZE ÷ 2 in 64-bit numbers. Round shapes are polygons with a fixed number of sides (32 round the cylinder and cone, 24 by 12 on the sphere). Align and drop to ground read each placement's bounding box and add one difference to the translation part of its matrix; moves smaller than 1e-9 model units are skipped.

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.

beginner.js ModelCraft 0.8.1 64-bit 6 calculations Reviewed

Tests tools/modelcraft-test/print07.mjs

Box and cylinder

64-bit

A 20 mm cube and a 20 mm cylinder, standing on z = 0 and centered on the origin.

S = 0.02; R = S ÷ 2; box: S × S square pulled up by S; cylinder: 32-sided polygon of radius R pulled up by S

S = 0.02 is the nearest 64-bit number to 0.02 and R = S ÷ 2 is exact from it. The cylinder is a 32-sided prism, so its footprint is a polygon and not a circle.

  • beginner.js
  • lines 38 to 39
  • ModelCraft 0.8.1
box: (m, name) => { const d = m.newDef('group', name); prism(d, rectPts([-R, -R, 0], [1, 0, 0], [0, 1, 0], S, S), S); return d; },
cylinder: (m, name) => { const d = m.newDef('group', name); prism(d, ngonPts([0, 0, 0], [0, 0, 1], [1, 0, 0], R, 32), S); return d; },

Cone

64-bit

A cone with a 32-sided base of radius R and its apex at height S.

The base corners come from ngonPts and the apex is the exact point [0, 0, S]; each side is one triangle between two neighboring base corners and the apex. No tolerance is used here.

  • beginner.js
  • lines 40 to 45
  • ModelCraft 0.8.1
cone: (m, name) => {
  const n = 32, base = ngonPts([0, 0, 0], [0, 0, 1], [1, 0, 0], R, n), apex = [0, 0, S];
  const polys = [{ loops: [base.slice().reverse()] }];
  for (let i = 0; i < n; i++) polys.push({ loops: [[base[i], base[(i + 1) % n], apex]] });
  return meshDef(m, polys, name, 'group');
},

Sphere

64-bit

A sphere of radius R resting on z = 0, made of 12 rings of 24 faces.

θ = π·i ÷ 12; φ = 2π·j ÷ 24; p = [R·sin θ·cos φ, R·sin θ·sin φ, R − R·cos θ]

Each corner is one 64-bit expression. The bottom pole is exactly [0, 0, 0]; at the top pole sin θ evaluates to about 1.2 × 10⁻¹⁶ instead of 0, so the points computed for it differ by about 10⁻¹⁸ m, and the seam column j = 24 is computed with φ = 2π rather than reused from j = 0 and differs from it by up to about 2.4 × 10⁻¹⁸ m. Both are passed to meshDef as computed.

  • beginner.js
  • lines 46 to 57
  • ModelCraft 0.8.1
sphere: (m, name) => {
  const seg = 24, rings = 12, polys = [];
  const p = (i, j) => { const th = Math.PI * i / rings, ph = 2 * Math.PI * j / seg; return [R * Math.sin(th) * Math.cos(ph), R * Math.sin(th) * Math.sin(ph), R - R * Math.cos(th)]; };
  for (let i = 0; i < rings; i++) for (let j = 0; j < seg; j++) {
    const a = p(i, j), b = p(i + 1, j), c = p(i + 1, j + 1), dd = p(i, j + 1);
    // wound outward (counter-clockwise seen from outside); the poles' quads lose their zero-length side
    if (i === 0) polys.push({ loops: [[a, c, b]] });
    else if (i === rings - 1) polys.push({ loops: [[a, dd, b]] });
    else polys.push({ loops: [[a, dd, c, b]] });
  }
  return meshDef(m, polys, name, 'group');
},

Roof

64-bit

A triangular prism: a triangle across x pulled along y from −R to +R.

The triangle is drawn at y = −R and pulled by S. If the lowest y afterwards is more than 1e-9 below −R the pull went the other way and every point is shifted by +S; the 1e-9 is absolute, in model units.

  • beginner.js
  • lines 58 to 67
  • ModelCraft 0.8.1
roof: (m, name) => {
  // a triangle across red (x), pulled along green (y) from −R to +R
  const d = m.newDef('group', name);
  addPolygon(d, [[-R, -R, 0], [R, -R, 0], [0, -R, S]]);
  pushPull(d, [...d.faces.keys()][0], S);
  let lo = Infinity;
  for (const p of d.verts.values()) lo = Math.min(lo, p[1]);
  if (lo < -R - 1e-9) for (const [id, p] of d.verts) d.verts.set(id, [p[0], p[1] + S, p[2]]);   // it went the other way
  return d;
},

Align

64-bit

Moves each selected placement so the minimum, middle or maximum of its box on one axis meets that of the whole selection.

target = lo, hi or (lo + hi) ÷ 2; move = target − own value; m[12 + axis] += move

The target and each placement's own value are read from bounding boxes and subtracted once; the difference is added to the translation entry of the matrix. A move with |d| < 1e-9 (absolute, model units) is skipped, so placements already within that distance are left as they are.

  • beginner.js
  • lines 115 to 131
  • ModelCraft 0.8.1
export function align(m, ctxDef, iids, axis, where) {
  const boxes = iids.map((iid) => [iid, placementBox(m, ctxDef, iid)]).filter(([, b]) => b);
  if (boxes.length < 2) return 0;
  let lo = Infinity, hi = -Infinity;
  for (const [, b] of boxes) { lo = Math.min(lo, b.min[axis]); hi = Math.max(hi, b.max[axis]); }
  const target = where === 'min' ? lo : where === 'max' ? hi : (lo + hi) / 2;
  let n = 0;
  for (const [iid, b] of boxes) {
    const at = where === 'min' ? b.min[axis] : where === 'max' ? b.max[axis] : (b.min[axis] + b.max[axis]) / 2;
    const d = target - at;
    if (Math.abs(d) < 1e-9) continue;
    const v = [0, 0, 0]; v[axis] = d;
    moveBy(ctxDef.insts.get(iid), v[0], v[1], v[2]);
    n++;
  }
  return n;
}

Drop to ground

64-bit

Moves each selected placement up or down so the bottom of its box is at z = 0.

m[14] += −box.min.z

One subtraction per placement, from the box's lowest z. A placement whose lowest z is within 1e-9 of zero (absolute) is not moved.

  • beginner.js
  • lines 133 to 142
  • ModelCraft 0.8.1
export function dropToGround(m, ctxDef, iids) {
  let n = 0;
  for (const iid of iids) {
    const b = placementBox(m, ctxDef, iid);
    if (!b || Math.abs(b.min[2]) < 1e-9) continue;
    moveBy(ctxDef.insts.get(iid), 0, 0, -b.min[2]);
    n++;
  }
  return n;
}

In detail

Method
Corner coordinates from sine and cosine of evenly spaced angles; align moves each box's minimum, middle or maximum to that of the whole selection.
Number types
64-bit numbers throughout; matrices are plain arrays of 16 numbers.
Tolerances
1e-9 model units, absolute: a move smaller than this is skipped in align and dropToGround, and the roof is shifted back only if it went more than 1e-9 past −R.
Limits
The round shapes are faceted, so their volume and surface are those of the polygons, not of a true cylinder, cone or sphere.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach