Standard sections: member solids, mass and parts list
Builds a steel member as a solid from a standard section, shows a section's mass per meter, and totals the placed parts into a parts list.
How the precision is kept
Member geometry is 64-bit: the section's millimeters are divided by 1000 and extruded along the member. Mass per meter is the published figure when the table has one, else the section's area times a steel density of 7850 kg/m³. Masses shown on cards and in the parts list are rounded to two decimals.
Calculations under this policy
4 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 kilograms per meter shown for a steel section.
kg/m = published mass, else area (mm²) × 1e-6 × 7850
STEEL_DENSITY is 7850 kg/m³ in shapes/sections.js, a nominal value for carbon steel. The area comes from exactArea of the same file. The card shows the result rounded to two decimals.
The faces of a straight member along two points, or of a column of a given height.
outline (mm) ÷ 1000; L = |b − a| × k; point = eₓ·u + e_y·(v + lift) + e_z·s
Section coordinates are divided by 1000, a correctly rounded division, and placed on unit axes from the member's direction (hypot). A beam is lifted so the lowest point of its section sits on the start level. A length of 1e-6 m or less is refused.
stdlib.js
lines 102 to 131
CadCraft 0.8.2
/** Faces (meters, relative to the member's start) of a straight member along a → b, bottom on z0; or a column. */
export function memberPolys(sec, a, b, z0, k) {
const loops = memberLoops(sec).map((l) => l.map(([u, v]) => [u / 1000, v / 1000]));
let ex, ey, ez, L;
if (a) {
const dx = (b.x - a.x) * k, dy = (b.y - a.y) * k;
L = Math.hypot(dx, dy);
if (!(L > 1e-6)) throw new Error(t('std.member.zero'));
const dir = [dx / L, dy / L, 0];
ex = [-dir[1], dir[0], 0]; ey = [0, 0, 1]; ez = dir; // u across (to the left), v up, along the member
} else {
L = z0 * k;
if (!(L > 1e-6)) throw new Error(t('std.member.zero'));
ex = [1, 0, 0]; ey = [0, 1, 0]; ez = [0, 0, 1];
}
let vmin = Infinity;
for (const [, v] of loops[0]) vmin = Math.min(vmin, v);
const lift = a ? -vmin : 0; // a beam sits on its start point's level
const at = (u, v, s) => [ex[0] * u + ey[0] * (v + lift) + ez[0] * s, ex[1] * u + ey[1] * (v + lift) + ez[1] * s, ex[2] * u + ey[2] * (v + lift) + ez[2] * s];
const polys = [];
for (const loop of loops) {
for (let i = 0; i < loop.length; i++) {
const p = loop[i], q = loop[(i + 1) % loop.length];
polys.push([at(p[0], p[1], 0), at(q[0], q[1], 0), at(q[0], q[1], L), at(p[0], p[1], L)]);
}
}
polys.push({ loops: loops.map((l) => l.map(([u, v]) => at(u, v, L))) });
polys.push({ loops: loops.map((l) => l.slice().reverse().map(([u, v]) => at(u, v, 0))) });
return { polys, length: L };
}
Parts list quantities
64-bit
Groups placed parts and adds up how many there are of each.
qty = Σ CC-QTY (1 when absent)
Quantity and unit mass are read with parseFloat from the block reference's attribute text, so they carry whatever digits that text has. Quantities are added in drawing order.
stdlib.js
lines 144 to 162
CadCraft 0.8.2
/** Every placed part and section, grouped (references in the model and on sheets). */
export function partsList(doc) {
const map = new Map();
for (const e of doc.everyEntity()) {
if (e.type !== 'insert') continue;
const a = e.attrs || {};
if (!a[PART_TAGS.type] && !a[PART_TAGS.size]) continue;
const row = { type: a[PART_TAGS.type] || '', std: a[PART_TAGS.std] || '', size: a[PART_TAGS.size] || '', length: a[PART_TAGS.length] || '', material: a[PART_TAGS.material] || '' };
const key = [row.type, row.std, row.size, row.length, row.material].join('\u0001');
let r = map.get(key);
if (!r) { r = { ...row, qty: 0, numbers: [], massEach: parseFloat(a['CC-WEIGHT-KG']) > 0 ? parseFloat(a['CC-WEIGHT-KG']) : null }; map.set(key, r); }
r.qty += parseFloat(a['CC-QTY']) > 0 ? parseFloat(a['CC-QTY']) : 1;
const n = String(a['CC-ITEM'] ?? '').trim();
if (n && !r.numbers.includes(n)) r.numbers.push(n);
}
const out = [...map.values()];
for (const r of out) r.numbers.sort((x, y) => x.localeCompare(y, undefined, { numeric: true }));
return out.sort((x, y) => (x.numbers[0] || '~').localeCompare(y.numbers[0] || '~', undefined, { numeric: true }) || x.type.localeCompare(y.type) || x.size.localeCompare(y.size, undefined, { numeric: true }));
}
Total mass in the parts list
64-bit
The mass column of the parts list and its CSV.
mass = round(mass each × qty × 100) ÷ 100
The total is rounded to two decimals, 0.01 kg, before it is written to the table and the CSV.
Section outline extruded along a unit direction; mass = area × density; quantities summed per part.
Number types
64-bit numbers; quantities and unit masses are read from attribute text.
Tolerances
A member shorter than 1e-6 m is refused.
Limits
7850 kg/m³ is a nominal density. The parts list writes total mass to 0.01 kg and takes each part's mass from the text of its weight attribute.
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.