Turns pipe, duct, cable carrier and lighting results into the warnings a person reads, with the required and found values.
How the precision is kept
The results arrive in SI units as 64-bit numbers. This file converts them for the message with exact unit factors and rounds what it prints and what it stores in each warning's required and found fields to one or two decimals. The sizing results themselves are not changed.
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 value is multiplied, rounded to a whole number and divided, so the kept value has one or two decimals. Math.round rounds halves toward plus infinity. Values that are not finite pass through unchanged.
0.003785411784 m³ per US gallon and 0.3048 m per foot are the exact definitions. The division is done before the multiplication by 60, then the result is rounded to one decimal (two for L/s and m/s).
The stored velocity is rounded to two decimals and the pressures to one. The sizing decision was made upstream on the unrounded value; the stored pair is for the report.
mep/problems-mep.js
lines 27 to 46
BimCraft 0.8.1
/** analyzePipes() result → problems. `unit` { len: 'mm' | 'in', ... } is only for words; numbers are SI in the result. */
export function pipeProblems(res, { labelOf = (id) => id, guidOf = (id) => id, imperial = false } = {}) {
const out = networkProblems('pipes', res, labelOf, guidOf);
const gpm = (q) => f1(q / 0.003785411784 * 60), lps = (q) => f2(q * 1000), fl = (q) => (imperial ? gpm(q) + ' gpm' : lps(q) + ' L/s');
const vel = (v) => (imperial ? f1(v / 0.3048) + ' ft/s' : f2(v) + ' m/s');
for (const s of res.segs) {
const about = [run(guidOf(s.run), labelOf(s.run))];
if (s.status === 'small') out.push(P('pipes', 'pipe.small', 'warning', `${labelOf(s.run)} (${s.size}) carries ${fl(s.flow)} at ${vel(s.V)}; the velocity limit of ${vel(res.opts.vmax)}${res.opts.rateMax ? ' or the friction limit' : ''} needs ${s.need}.`,
{ part: s.run + '#' + s.id, about, vars: { run: s.run, flow: s.flow, need: s.need }, req: { op: '<=', value: f2(res.opts.vmax), unit: 'm/s' }, found: { value: f2(s.V), unit: 'm/s' }, fixes: [{ id: 'size', text: `Set the size to ${s.need}`, action: 'run-size', args: { run: s.run, size: s.need } }] }));
else if (s.status === 'toobig') out.push(P('pipes', 'pipe.toobig', 'error', `${labelOf(s.run)} carries ${fl(s.flow)}: no size of its material keeps the velocity under ${vel(res.opts.vmax)}.`, { part: s.run + '#' + s.id, about, req: { op: '<=', value: f2(res.opts.vmax), unit: 'm/s' }, found: { value: f2(s.V), unit: 'm/s' } }));
if (s.beyond) out.push(P('pipes', 'pipe.beyond', 'note', `${labelOf(s.run)}: ${f1(s.fu)} fixture units is beyond the last point of the demand curve; the curve's last slope was continued.`, { part: s.run + '#' + s.id, about }));
}
for (const l of res.loads) {
if (l.reached === false) out.push(P('pipes', 'pipe.noroute', 'warning', `${l.label || l.key} is not joined to the source by pipe.`, { part: 'load:' + l.key, about: [] }));
else if (l.short) out.push(P('pipes', 'pipe.pressure', 'warning', `${l.label || l.key}: ${f1(l.leftKpa)} kPa left at the fixture after ${f1(l.dpKpa)} kPa of loss; ${f1(res.opts.minKpa)} kPa is needed.`,
{ part: 'load:' + l.key, about: l.path.slice(0, 50).map((id) => run(guidOf(res.segs.find((s) => s.id === id)?.run), labelOf(res.segs.find((s) => s.id === id)?.run))), req: { op: '>=', value: f1(res.opts.minKpa), unit: 'kPa' }, found: { value: f1(l.leftKpa), unit: 'kPa' } }));
}
if (res.curve?.example) out.push(P('pipes', 'pipe.curve', 'note', 'The demand comes from the built-in example curve, not from a code table. Type the table of the code in force before relying on these sizes.', { part: 'curve' }));
return out;
}
Duct velocity, friction rate and aspect ratio
64-bit
Writes a duct's flow, velocity, needed round size and aspect ratio into its warning.
0.00508 m/s per foot per minute is exact (0.3048 ÷ 60). The needed equivalent diameter is rounded to a whole millimeter in the text. The required velocity and friction rate are stored unrounded; the found ones are rounded to two decimals.
mep/problems-mep.js
lines 48 to 61
BimCraft 0.8.1
/** analyzeDucts() result → problems. */
export function ductProblems(res, { labelOf = (id) => id, guidOf = (id) => id, imperial = false } = {}) {
const out = networkProblems('ducts', res, labelOf, guidOf);
const vel = (v) => (imperial ? Math.round(v / 0.00508) + ' fpm' : f1(v) + ' m/s');
for (const s of res.segs) {
const about = [run(guidOf(s.run), labelOf(s.run))];
if (s.status === 'small' || s.status === 'fast') out.push(P('ducts', 'duct.' + s.status, 'warning', `${labelOf(s.run)} (${s.size}) carries ${f1(s.flow * 1000)} L/s at ${vel(s.V)}; ${res.opts.method === 'velocity' ? 'the velocity limit' : 'the friction rate'} needs a round equivalent of at least ${Math.round(s.needDeMm)} mm.`,
{ part: s.run + '#' + s.id, about, req: res.opts.method === 'velocity' ? { op: '<=', value: res.opts.vmax, unit: 'm/s' } : { op: '<=', value: res.opts.rate, unit: 'Pa/m' }, found: res.opts.method === 'velocity' ? { value: f2(s.V), unit: 'm/s' } : { value: f2(s.rate), unit: 'Pa/m' } }));
if (s.aspectHigh) out.push(P('ducts', 'duct.aspect', 'note', `${labelOf(s.run)} (${s.size}) is ${f1(s.aspect)}:1; ${res.opts.maxAspect}:1 is the usual limit.`, { part: s.run + '#' + s.id, about, req: { op: '<=', value: res.opts.maxAspect, unit: ':1' }, found: { value: f1(s.aspect), unit: ':1' } }));
}
for (const t of res.terminals) if (t.reached === false) out.push(P('ducts', 'duct.noroute', 'warning', `${t.label || t.key} is not joined to the source by duct.`, { part: 'term:' + t.key }));
out.push(P('ducts', 'duct.defaults', 'note', 'Fitting losses use rounded default coefficients (elbows, tees, transitions), not a fitting database. Replace them with your project table for a final fan selection.', { part: 'defaults' }));
return out;
}
In detail
Method
Unit factors: 1 US gallon = 0.003785411784 m³, 1 ft = 0.3048 m, 1 ft/min = 0.00508 m/s. Rounding by Math.round(x × 10) ÷ 10 and Math.round(x × 100) ÷ 100.
Number types
64-bit numbers.
Limits
A found value stored in a warning is the rounded one, so a value just over a limit can be stored equal to the limit (for example 2.004 m/s against 2.00).
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.