Resources Numerical accuracy Calculation policy

Drainage structure types and their library items

Works out the inside and outside size of a manhole, inlet or junction type, and writes the type as a library item in millimeters and reads it back.

How the precision is kept

Sizes are 64-bit with exact factors: 25.4 mm per inch, 12 inches per foot, and 1,200,000 ÷ 3937 mm per US survey foot written as that fraction. The library item's plan shapes, body and three size parameters are rounded to 1e-6 mm. The type's own parameters are written to the item unrounded, as text of the 64-bit value in millimeters, and divided by the same factor when read back.

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.

structs.js CivilCraft 0.8.1 64-bit 6 calculations Reviewed

Tests bin/test-civilcraft-structs.mjs

Millimeters per project unit

Exact

The factor from project feet or meters to millimeters.

m: 1000; ft: 1,200,000 ÷ 3937 (US survey foot)

The US survey foot is written as its defining fraction and evaluated once in 64-bit; no rounded decimal is typed.

  • structs.js
  • line 39
  • CivilCraft 0.8.1
const mmPerUnit = (units) => (units === 'm' ? 1000 : 1200000 / 3937);   // US survey foot

Inside and outside size of a structure

64-bit

The inside and outside width and length, the wall and the frame of a structure in project units.

inside = size ÷ P; outside = inside + 2 × wall ÷ P; P = 12 (inches per foot) or 1000 (mm per m)

One division by a whole number per size and one addition for the outside, in 64-bit; nothing is rounded.

  • structs.js
  • lines 80 to 87
  • CivilCraft 0.8.1
export function structGeom(s, net, units = 'ft') {
  const k = typeOf(s, net), P = per(units);
  if (!k) { const d = (Number(s?.dia) || 0) / P; return { shape: 'round', inW: d, inL: d, outW: d, outL: d, wall: 0, frame: null, type: null }; }
  const wall = (k.wall || 0) / P;
  const inW = (k.shape === 'round' ? k.dia : k.w) / P, inL = (k.shape === 'round' ? k.dia : k.l) / P;
  const frame = k.frame ? { shape: k.frame.shape, w: (k.frame.shape === 'round' ? k.frame.dia : k.frame.w) / P, l: (k.frame.shape === 'round' ? k.frame.dia : k.frame.l) / P, h: (k.frame.h || 0) / P } : null;
  return { shape: k.shape, inW, inL, outW: inW + 2 * wall, outL: inL + 2 * wall, wall, frame, type: k };
}

Plan shapes of a type

64-bit

The outside wall, inside wall and frame opening of a type as plan shapes in millimeters.

r = round₆((size × M) ÷ 2); M = 25.4 (inch) or 1 (mm)

Sizes are multiplied by the exact 25.4 and then rounded to 1e-6 mm with Math.round(v × 1e6) ÷ 1e6 before they are stored in the item.

  • structs.js
  • lines 103 to 114
  • CivilCraft 0.8.1
export function typePlan(k, units = 'ft') {
  const M = mmPerDia(units), ents = [];
  const shapeEnt = (layer, shape, w, l) => (shape === 'round'
    ? { type: 'circle', layer, color: null, x: 0, y: 0, r: r6((w * M) / 2) }
    : { type: 'poly', layer, color: null, pts: [[-w, -l], [w, -l], [w, l], [-w, l]].map(([x, y]) => [r6((x * M) / 2), r6((y * M) / 2)]), closed: true });
  // a rectangle runs along X: its length (l) is the X size, its width (w) the Y size
  const W = k.shape === 'round' ? k.dia : k.l, D = k.shape === 'round' ? k.dia : k.w, wall = k.wall || 0;
  ents.push(shapeEnt('STRUCTURE', k.shape, W + 2 * wall, D + 2 * wall));
  if (wall > 0) ents.push(shapeEnt('STRUCTURE-DETAIL', k.shape, W, D));
  if (k.frame) ents.push(shapeEnt('FRAME', k.frame.shape, k.frame.shape === 'round' ? k.frame.dia : k.frame.l, k.frame.shape === 'round' ? k.frame.dia : k.frame.w));
  return { layers: [{ name: 'STRUCTURE', color: 7 }, { name: 'STRUCTURE-DETAIL', color: 8 }, { name: 'FRAME', color: 5 }], entities: ents };
}

Round outline as a polygon

64-bit

The outline of a round structure's body as points on a circle.

pᵢ = (r·cos(2π·i ÷ n), r·sin(2π·i ÷ n)), n = 48

Forty-eight points on the circle, each from one 64-bit cosine and sine of 2π·i ÷ n, so the points do not drift. The polygon lies inside the circle: its area is that of a regular 48-gon, about 0.3 % less than the circle's.

  • structs.js
  • lines 100 to 101
  • CivilCraft 0.8.1
// ---- the library item ----
const circlePoly = (r, n = 48) => Array.from({ length: n }, (_, i) => [r * Math.cos((2 * Math.PI * i) / n), r * Math.sin((2 * Math.PI * i) / n)]);

Library item of a type

64-bit

The library item for a type: its body from the underside of the floor slab to the rim, its sizes, and every parameter in millimeters.

H = depth × U + base × M; body = outline from −H to 0; parameters as text of (value × factor)

The body vertices and the width, depth and height parameters are rounded to 1e-6 mm. The CC-STRUCT attributes are the unrounded 64-bit products written with String(), which prints enough digits to read the same 64-bit number back.

  • structs.js
  • lines 130 to 161
  • CivilCraft 0.8.1
export function typeItem(k, units = 'ft') {
  const M = mmPerDia(units), U = mmPerUnit(units), wall = k.wall || 0;
  const W = (k.shape === 'round' ? k.dia : k.l) + 2 * wall, D = (k.shape === 'round' ? k.dia : k.w) + 2 * wall;
  const H = k.depth * U + (k.base || 0) * M;
  const outline = k.shape === 'round' ? circlePoly((W * M) / 2) : [[-W, -D], [W, -D], [W, D], [-W, D]].map(([x, y]) => [(x * M) / 2, (y * M) / 2]);
  const attrs = {
    'CC-STRUCT-FORMAT': '1', 'CC-STRUCT-KIND': k.kind, 'CC-STRUCT-SHAPE': k.shape,
    'CC-STRUCT-WALL-MM': String(wall * M), 'CC-STRUCT-BASE-MM': String(((k.base || 0) * M)),
    'CC-STRUCT-SUMP-MM': String((k.sump * U)), 'CC-STRUCT-DEPTH-MM': String((k.depth * U)),
  };
  if (k.shape === 'round') attrs['CC-STRUCT-DIA-MM'] = String((k.dia * M));
  else { attrs['CC-STRUCT-W-MM'] = String((k.w * M)); attrs['CC-STRUCT-L-MM'] = String((k.l * M)); }
  if (k.frame) {
    attrs['CC-STRUCT-FRAME-SHAPE'] = k.frame.shape;
    attrs['CC-STRUCT-FRAME-H-MM'] = String(((k.frame.h || 0) * M));
    if (k.frame.shape === 'round') attrs['CC-STRUCT-FRAME-DIA-MM'] = String((k.frame.dia * M));
    else { attrs['CC-STRUCT-FRAME-W-MM'] = String((k.frame.w * M)); attrs['CC-STRUCT-FRAME-L-MM'] = String((k.frame.l * M)); }
  }
  return {
    format: 'cadcraft-library-item', format_version: 1,
    name: k.name || typeWords(k, units),
    kind: 'fixture', category: 'other/other',
    description: t('st.item.desc', { words: typeWords(k, units) }),
    maker: '', model: '',
    ifc: { class: 'IfcDistributionChamberElement', predefined: PREDEFINED[k.kind] || 'NOTDEFINED' },
    plan: typePlan(k, units),
    body: prism(outline, r6(-H), 0),
    clearances: [],
    params: { width: r6(W * M), depth: r6(D * M), height: r6(H), remarks: typeWords(k, units) },
    source_attrs: attrs,
  };
}

Type read back from a library item

64-bit

Turns a library item's millimeter parameters back into a type in the project's units.

value = millimeters ÷ M (sizes) or ÷ U (sump, depth)

Each parameter is read with Number and divided by the same factor it was multiplied by when written. The multiply and the divide each round once in 64-bit, so a value can come back different in its last digit.

  • structs.js
  • lines 162 to 176
  • CivilCraft 0.8.1
/** A library item made by CivilCraft → a type in the project's units (CC-STRUCT-* attributes; mm → in or mm). */
export function typeFromItem(item, units = 'ft') {
  const a = item?.source_attrs;
  if (!item || typeof item !== 'object' || item.format !== 'cadcraft-library-item') return { type: null, why: t('st.lib.notitem') };
  if (!a || a['CC-STRUCT-FORMAT'] !== '1') return { type: null, why: t('st.lib.notours') };
  const M = mmPerDia(units), U = mmPerUnit(units), n = (k) => Number(a[k]);
  const shape = a['CC-STRUCT-SHAPE'];
  const o = { name: String(item.name || ''), kind: a['CC-STRUCT-KIND'], shape, wall: n('CC-STRUCT-WALL-MM') / M, base: n('CC-STRUCT-BASE-MM') / M,
    sump: n('CC-STRUCT-SUMP-MM') / U, depth: n('CC-STRUCT-DEPTH-MM') / U };
  if (shape === 'round') o.dia = n('CC-STRUCT-DIA-MM') / M; else { o.w = n('CC-STRUCT-W-MM') / M; o.l = n('CC-STRUCT-L-MM') / M; }
  const fs = a['CC-STRUCT-FRAME-SHAPE'];
  if (fs) o.frame = fs === 'round' ? { shape: fs, dia: n('CC-STRUCT-FRAME-DIA-MM') / M, h: n('CC-STRUCT-FRAME-H-MM') / M } : { shape: fs, w: n('CC-STRUCT-FRAME-W-MM') / M, l: n('CC-STRUCT-FRAME-L-MM') / M, h: n('CC-STRUCT-FRAME-H-MM') / M };
  const type = cleanType(o, units);
  return type ? { type, why: '' } : { type: null, why: t('st.lib.bad') };
}

In detail

Method
Outside = inside + 2 × wall; height = depth + floor slab; a round body is a prism with 48 sides.
Number types
64-bit numbers; 25.4 and 1,200,000 ÷ 3937 are exact by definition.
Tolerances
Plan coordinates, body vertices and the width, depth and height parameters are rounded to 1e-6 mm.
Limits
The body of a round structure is a 48-sided prism with its corners on the circle, so its volume and surface are those of the polygon, not the cylinder. The plan shape of a round structure is a true circle.

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