Quantity and mass per row
64-bitCounts the placed parts of each row and works out the mass of one and of all.
mass each g = V mm³ × ρ g/cm³ ÷ 1000; total = each × quantity
64-bit throughout. Mass is left empty when the part has no volume or no density. The total is the unrounded unit mass times the count, not a sum of rounded rows.
export function rows(m, meshes, { view = null, scope = 'all', collapse = true } = {}) {
const shown = scope === 'view' ? new Set(shownInstances(m, view).filter((i) => !view?.insts?.[i.id]?.ghost).map((i) => i.id)) : null;
const insts = m.instances.filter((i) => !shown || shown.has(i.id));
const out = [];
const byKey = new Map();
for (const inst of insts) {
const p = m.parts[inst.part];
let r = null;
if (collapse) {
r = byKey.get(inst.part);
if (!r) r = out.find((x) => x.pids[0] !== inst.part && (p.number ? x.number === p.number : !x.number && x.name === p.name) && sameShape(meshes.get(x.pids[0]), meshes.get(inst.part)));
}
if (!r) {
const vol = statsOf(meshes.get(inst.part)).volume;
r = { item: numberOf(m, inst.part), qty: 0, pids: [inst.part], insts: [], number: p.number, name: p.name, description: p.description, material: p.material, density: p.density, volume: vol, massEach: vol !== null && p.density ? vol * p.density / 1000 : null, massTotal: null, props: { ...p.props } };
out.push(r);
} else if (!r.pids.includes(inst.part)) { r.pids.push(inst.part); if (r.item === null) r.item = numberOf(m, inst.part); }
if (collapse) byKey.set(inst.part, r);
r.qty++; r.insts.push(inst.id);
}
for (const r of out) r.massTotal = r.massEach === null ? null : r.massEach * r.qty;
return out.sort((a, b) => (a.item ?? Infinity) - (b.item ?? Infinity) || a.name.localeCompare(b.name));
}