The height, spread, spacing and supplied sizes of each plant species, and the 2D symbol and library item made from them.
How the precision is kept
Every length that enters the species table, from a file, a CSV or the table editor, is range-checked and rounded to 0.001 m. Later values are 64-bit expressions on those rounded numbers and are rounded again to three decimals where they are stored: spacing, symbol coordinates, CSV cells. A library item's width, depth and height are rounded to a whole millimeter.
Calculations under this policy
10 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.
The rounding applied to every stored length, spacing and symbol coordinate.
r3(v) = round(v × 1000) ÷ 1000
Multiplies by 1000, rounds to the nearest whole number and divides by 1000 in 64-bit. The kept value is the nearest 64-bit number to a three-decimal value.
plants/plants.js
line 64
BimCraft 0.8.1
const r3 = (v) => Math.round(v * 1000) / 1000;
Reading a length
64-bit
A height, spread, spacing, volume or girth read from a file, a CSV cell or the editor.
Text is parsed with Number after the first decimal comma is replaced by a point. A value that is not finite, not above the lower limit or above the field's cap becomes null; an accepted value is rounded to three decimals before it is stored.
plants/plants.js
lines 66 to 67
BimCraft 0.8.1
const num = (v) => (typeof v === 'string' && v.trim() !== '' ? Number(v.trim().replace(',', '.')) : v);
const len = (v, max, min = 0) => { const x = num(v); return fin(x) && x > min && x <= max ? r3(x) : null; };
Sizes, hardiness zone and spacing of one species
64-bit
The mature size, planting size, zone range and spacing kept for a species row.
The planting size is capped at the mature size. Zones are kept only as whole numbers from 1 to 13. A spacing of exactly 0 is kept; otherwise it must be above 0.0499 m and is rounded to three decimals.
The planting size, container and spacing filled in when a species row leaves them empty.
spacing = max(q, round(k × spread ÷ q) × q), by plant type
Fixed factors and steps per plant type (for example 0.75 of the spread in steps of 0.05 m for a shrub). Rounding to a step by multiplying back can leave binary noise in the last digits, which the final rounding to three decimals removes. The planting size is capped at the mature size.
Numbers are matched as digits with an optional point or comma and at most one fraction, so exponent notation and negative values are not read. They then pass the same range checks and three-decimal rounding as any other length.
plants/plants.js
lines 276 to 293
BimCraft 0.8.1
export function suppliesFromText(text) {
const out = [];
for (const part of String(text || '').split(';').slice(0, LIMITS.supplies)) {
const w = part.trim().split(/\s+/).filter(Boolean);
if (w.length < 2) continue;
const o = { id: w[0], kind: w[1].toLowerCase() };
for (const tok of w.slice(2)) {
let m;
if ((m = /^(\d+(?:[.,]\d+)?)l$/i.exec(tok))) o.volume = m[1];
else if ((m = /^st(\d+(?:[.,]\d+)?)$/i.exec(tok))) o.stem = m[1];
else if ((m = /^h(\d+(?:[.,]\d+)?)$/i.exec(tok))) o.h = m[1];
else if ((m = /^s(\d+(?:[.,]\d+)?)$/i.exec(tok))) o.s = m[1];
else if ((m = /^g(\d+(?:[.,]\d+)?)(?:-(\d+(?:[.,]\d+)?))?$/i.exec(tok))) o.girth = { min: m[1], max: m[2] ?? m[1] };
}
out.push(o);
}
return cleanSupplies(out.map((o) => ({ ...o, volume: num(o.volume), h: num(o.h), s: num(o.s), stem: num(o.stem), girth: o.girth ? { min: num(o.girth.min), max: num(o.girth.max) } : null })));
}
2D planting symbol
64-bit
The circles, lines, arcs and outlines of a plant's plan symbol, sized to its spread.
R = spread ÷ 2 × scale; point = (r cos a, r sin a)
Every coordinate and radius is rounded to three decimals of the drawing unit (0.001 mm at the default scale of 1000 per meter); arc angles are converted to degrees with 180 ÷ π and rounded to 0.001°. Points meant to lie on a circle are therefore rounded off it by less than a thousandth of a unit.
plants/plants.js
lines 431 to 463
BimCraft 0.8.1
/** A 2D planting symbol centred on (0,0), radius = half the spread, in `scale` units per metre (1000 → mm). Entities in the
* CadCraft Doc subset the library item format uses: circle {x,y,r}, arc {x,y,r,a0,a1}, line, poly {pts, closed}. */
export function symbolPlan(sp, { which = 'mature', scale = 1000, sym = null } = {}) {
const R = (sizeOf(sp, which).s / 2) * scale, kind = sym || symbolOf(sp);
const L = 'PLANT', D = 'PLANT-DETAIL';
const E = [], ln = (x1, y1, x2, y2, layer = D) => E.push({ type: 'line', layer, color: null, x1: r3(x1), y1: r3(y1), x2: r3(x2), y2: r3(y2) });
const circle = (r, layer = L) => E.push({ type: 'circle', layer, color: null, x: 0, y: 0, r: r3(r) });
const P = (a, r) => [r3(r * Math.cos(a)), r3(r * Math.sin(a))];
switch (kind) {
case 'canopy': circle(R); circle(R * 0.06, D); for (let i = 0; i < 6; i++) { const a = (i * Math.PI) / 3; ln(R * 0.12 * Math.cos(a), R * 0.12 * Math.sin(a), R * 0.55 * Math.cos(a), R * 0.55 * Math.sin(a)); } break;
case 'canopy-scallop': { // a scalloped outline: arcs bulging outwards between points on the circle
const n = Math.max(8, Math.min(18, Math.round(R / (0.35 * scale)) + 8)), pts = [];
for (let i = 0; i < n; i++) { const a = (2 * Math.PI * i) / n; pts.push([...P(a, R * 0.92), 0.35]); }
E.push({ type: 'poly', layer: L, color: null, pts, closed: true });
circle(R * 0.06, D);
break;
}
case 'conifer': circle(R); for (let i = 0; i < 12; i++) { const a = (i * Math.PI) / 6; const [x, y] = P(a, R * (i % 2 ? 0.7 : 0.95)); ln(0, 0, x, y); } break;
case 'palm': for (let i = 0; i < 9; i++) { const a = (2 * Math.PI * i) / 9; const [x, y] = P(a, R); const [mx, my] = P(a + 0.12, R * 0.6); E.push({ type: 'poly', layer: L, color: null, pts: [[0, 0], [mx, my], [x, y]], closed: false }); } circle(R * 0.08, D); break;
case 'shrub': {
const n = 7, pts = [];
for (let i = 0; i < n; i++) { const a = (2 * Math.PI * i) / n + 0.2; pts.push([...P(a, R * (i % 2 ? 0.86 : 0.97)), 0.5]); }
E.push({ type: 'poly', layer: L, color: null, pts, closed: true });
break;
}
case 'grass': circle(R, D); for (let i = 0; i < 10; i++) { const a = -Math.PI / 2 + ((i - 4.5) / 4.5) * 1.2; const [x, y] = P(a + Math.PI, R * 0.9); ln(0, -R * 0.2, x, -y, L); } break;
case 'groundcover': circle(R, D); for (let i = 0; i < 5; i++) { const [x, y] = P((2 * Math.PI * i) / 5, R * 0.45); E.push({ type: 'circle', layer: L, color: null, x, y, r: r3(R * 0.12) }); } break;
case 'climber': circle(R * 0.15); for (let i = 0; i < 3; i++) { const a = (2 * Math.PI * i) / 3; const [x, y] = P(a, R); E.push({ type: 'arc', layer: L, color: null, x: r3(x / 2), y: r3(y / 2), r: r3(R / 2), a0: r3((a * 180) / Math.PI), a1: r3((a * 180) / Math.PI + 180) }); } break;
case 'hedge': { const w = R * 0.85, h = R * 0.5; /* a piece of hedge, inside the spread circle */ E.push({ type: 'poly', layer: L, color: null, pts: [[-w, -h, 0.25], [w, -h, 0.25], [w, h, 0.25], [-w, h, 0.25]], closed: true }); break; }
default: circle(R);
}
return { layers: [{ name: L, color: 3 }, { name: D, color: 8 }], entities: E };
}
Library item size
64-bit
The width, depth and height of the library item placed for a plant.
mm = round(m × 1000)
Height and spread in meters are multiplied by 1000 and rounded to a whole millimeter. The size written into the item's attributes is the meter value as text.
plants/plants.js
lines 465 to 478
BimCraft 0.8.1
/** A library item (format 1, partial: no uuid/version/license/provenance) — what BimCraft and ModelCraft place. `body` = an
* optional mesh in mm (the app passes the entourage model scaled to this plant), else null (2D only). */
export function libraryItem(sp, { which = 'mature', body = null, lang = 'en' } = {}) {
const sz = sizeOf(sp, which), mm = (v) => Math.round(v * 1000);
return {
format: 'cadcraft-library-item', format_version: 1,
name: `${displayName(sp, lang)} (${sp.botanical})`.slice(0, 200), kind: 'symbol', category: 'site/landscape',
description: clean(`${sp.type}${sp.foliage ? ', ' + sp.foliage : ''}; ${which === 'plant' ? 'at planting' : 'at maturity'} ${sz.h} m high, ${sz.s} m across`, 500),
maker: '', model: '', ifc: { class: 'IfcGeographicElement', predefined: 'USERDEFINED' },
plan: symbolPlan(sp, { which, scale: 1000 }), body,
params: { width: mm(sz.s), depth: mm(sz.s), height: mm(sz.h), remarks: clean(sp.notes, 500) },
source_attrs: { 'CC-PLANT-KEY': sp.key, 'CC-PLANT-BOTANICAL': sp.botanical, 'CC-PLANT-TYPE': sp.type, 'CC-PLANT-SIZE': which, 'CC-PLANT-HEIGHT-M': String(sz.h), 'CC-PLANT-SPREAD-M': String(sz.s) },
};
}
In detail
Method
Field-by-field checks of untrusted rows. Spacing from density uses the bed pattern's geometry: square 1 ÷ √d, triangular √(2 ÷ (√3 × d)). Symbols are drawn from the spread with fixed proportions.
Number types
64-bit numbers; lengths in meters in the table, millimeters in a library item. Text numbers are parsed with Number after a decimal comma is replaced by a point.
Tolerances
No comparison tolerances. Range limits: height 150 m, spread 60 m, spacing 50 m (above 0.0499 m, or exactly 0), density 0.01 to 400 per m², girth 500 cm, volume 10000 L.
Limits
Values finer than 0.001 m are lost on reading. Default planting sizes, containers and spacings are rules of thumb by plant type, not data from a source.
Approach: 64-bit, stated limit
The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.