WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m14s

History-free snapshot of the private development repository at 77a82eb.
Code, docs and the public catalogue only: no plant or job knowledge
layers, no client, job or producer names (tools/scrub.py clean against
the widened term list; a second word-level grep clean). The intro deck is
left out because its slide images cannot be checked for job drawings.
This commit is contained in:
2026-09-09 08:35:15 -05:00
commit 25592fa6c2
406 changed files with 223435 additions and 0 deletions
+246
View File
@@ -0,0 +1,246 @@
// Feet-inches formatting and parsing -- the browser twin of
// `panellift/src/panellift/units.py`. Same rules, same rounding, same
// accepted input forms; kept as a byte-for-byte port on purpose so a value
// typed on the page and the same value printed by the CPython report never
// disagree. See units.py's own module docstring for the owner's ruling this
// exists to satisfy (2026-09-05: every length on any front-end or results
// page is feet-inches, rounded to the nearest 1/8", never a decimal).
//
// Plain ES2017, no modules -- loaded with a bare <script src="units.js">
// the way the rest of this page's vanilla JS is (see EDIT_JS in
// tools/panelanalysis.py). Exposes window.Units = {fmtFtIn, fmtIn,
// parseLength, parseLengthIn}.
(function () {
'use strict';
function gcd(a, b) {
a = Math.abs(a);
b = Math.abs(b);
while (b) {
var t = b;
b = a % b;
a = t;
}
return a || 1;
}
// A reduced "n/d" fraction string, mirroring Python's `fractions.Fraction`
// reduction (e.g. 4/8 -> "1/2").
function fracString(num, den) {
var g = gcd(num, den);
return (num / g) + '/' + (den / g);
}
// Decimal feet -> "8'-4 1/2\"", rounded to the nearest 1/precision inch.
// null/undefined/NaN -> '-'. Negative values keep the sign in front of the
// whole string ("-0'-6\""), never inside it. A remainder that rounds up to
// a full inch carries into the inch count, and 12 inches carries into the
// foot count.
function fmtFtIn(feet, precision) {
precision = precision || 8;
if (feet === null || feet === undefined || isNaN(feet)) {
return '-';
}
var sign = feet < 0 ? '-' : '';
var value = Math.abs(feet);
var wholeFt = Math.floor(value);
// work in units of 1/precision inch so the carry logic is exact integer
// arithmetic, never a float comparison against 12.0 or 1.0.
var unitsTotal = Math.round((value - wholeFt) * 12.0 * precision);
var inch = Math.floor(unitsTotal / precision);
var rem = unitsTotal - inch * precision;
if (inch >= 12) {
wholeFt += Math.floor(inch / 12);
inch = inch % 12;
}
var out = wholeFt + "'-" + inch;
if (rem) {
out += ' ' + fracString(rem, precision);
}
return sign + out + '"';
}
// Decimal inches -> "6 3/8\"" -- for thicknesses, depths, offsets,
// diameters and anything else already spoken of in inches, never feet.
// null/undefined/NaN -> '-'.
function fmtIn(inches, precision) {
precision = precision || 8;
if (inches === null || inches === undefined || isNaN(inches)) {
return '-';
}
var sign = inches < 0 ? '-' : '';
var value = Math.abs(inches);
var wholeIn = Math.floor(value);
var units = Math.round((value - wholeIn) * precision);
if (units >= precision) {
wholeIn += 1;
units = 0;
}
var out = '' + wholeIn;
if (units) {
out += ' ' + fracString(units, precision);
}
return sign + out + '"';
}
// ------------------------------------------------------------ parsing
// A leading sign, held separately from the feet/inch markers below because
// a hyphen INSIDE the string (the "8'-4" separator, not a minus) must
// never be read as negation -- only a hyphen before the first digit is a
// sign.
var SIGN_RE = /^-\s*/;
// "8", optionally decimal, followed by a foot mark, followed by whatever
// is left (which may be empty, or an inch remainder with its own quote).
var FEET_RE = /^(\d+(?:\.\d+)?)\s*'\s*([\s\S]*)$/;
// A hyphen used as the visual separator between a whole inch and a
// fraction ("4-1/2") rather than as a sign -- only recognised with a digit
// already on its left, so a leading "-" (the "8'-6\"" separator right
// after the foot mark) is stripped as its own case, not caught here.
var HYPHEN_FRAC_RE = /(\d)\s*-\s*(\d+\s*\/\s*\d+)/g;
var MIXED_RE = /^(\d+)\s+(\d+)\s*\/\s*(\d+)$/;
var FRAC_RE = /^(\d+)\s*\/\s*(\d+)$/;
var DEC_RE = /^(\d+(?:\.\d+)?)$/;
function badLength(orig) {
return new Error('not a length: ' + orig);
}
// num/den, or badLength(orig) for a 0 denominator -- a typed "1/0" is bad
// input, not a licence to silently hand back Infinity/NaN the way plain
// JS division would.
function safeDiv(num, den, orig) {
if (den === 0) {
throw badLength(orig);
}
return num / den;
}
function normalizeQuotes(s) {
return s.replace(/’/g, "'").replace(/‘/g, "'")
.replace(/”/g, '"').replace(/“/g, '"');
}
// Parse what follows a foot mark (or the whole string, for a bare inch
// value): an optional leading '-' separator, a whole number and/or a
// fraction, an optional trailing '"'. Returns inches as a number.
function parseInchRemainder(rest, orig, allowEmpty) {
rest = rest.trim();
if (rest.slice(-1) === '"') {
rest = rest.slice(0, -1).trim();
}
if (rest.slice(0, 1) === '-') {
// the "8'-6" separator, not a sign -- the sign (if any) was already
// consumed before the foot mark was even found.
rest = rest.slice(1).trim();
}
if (rest === '') {
if (allowEmpty) {
return 0.0;
}
throw badLength(orig);
}
rest = rest.replace(HYPHEN_FRAC_RE, '$1 $2');
var m = MIXED_RE.exec(rest);
if (m) {
return parseFloat(m[1]) +
safeDiv(parseFloat(m[2]), parseFloat(m[3]), orig);
}
m = FRAC_RE.exec(rest);
if (m) {
return safeDiv(parseFloat(m[1]), parseFloat(m[2]), orig);
}
m = DEC_RE.exec(rest);
if (m) {
return parseFloat(m[1]);
}
throw badLength(orig);
}
// No `'` and no trailing `"` anywhere -- a plain number, or a bare
// fraction / mixed number, read in `defaultUnit`.
function parseBareNumber(s, orig, defaultUnit) {
var value;
var m = DEC_RE.exec(s);
if (m) {
value = parseFloat(m[1]);
} else {
var s2 = s.replace(HYPHEN_FRAC_RE, '$1 $2');
m = MIXED_RE.exec(s2);
if (m) {
value = parseFloat(m[1]) +
safeDiv(parseFloat(m[2]), parseFloat(m[3]), orig);
} else {
m = FRAC_RE.exec(s2);
if (m) {
value = safeDiv(parseFloat(m[1]), parseFloat(m[2]), orig);
} else {
throw badLength(orig);
}
}
}
return defaultUnit === 'ft' ? value * 12.0 : value;
}
// The shared parser. Returns inches, given how a bare unmarked number
// should be read ('ft' or 'in').
function parseCoreIn(text, defaultUnit) {
if (text === null || text === undefined) {
throw badLength(text);
}
if (typeof text === 'number') {
return defaultUnit === 'ft' ? text * 12.0 : text;
}
var orig = String(text);
var s = orig.trim();
if (s === '') {
throw badLength(orig);
}
s = normalizeQuotes(s);
var negative = false;
var signMatch = SIGN_RE.exec(s);
if (signMatch) {
negative = true;
s = s.slice(signMatch[0].length).trim();
}
if (s === '') {
throw badLength(orig);
}
var totalIn;
var feetMatch = FEET_RE.exec(s);
if (feetMatch) {
var feet = parseFloat(feetMatch[1]);
totalIn = feet * 12.0 + parseInchRemainder(feetMatch[2], orig, true);
} else if (s.slice(-1) === '"') {
totalIn = parseInchRemainder(s, orig, false);
} else {
totalIn = parseBareNumber(s, orig, defaultUnit);
}
return negative ? -totalIn : totalIn;
}
// Parse any of the accepted forms and return inches. A bare number with
// no `'` or `"` anywhere is read in `defaultUnit` ('in' by default here).
function parseLengthIn(text, defaultUnit) {
return parseCoreIn(text, defaultUnit || 'in');
}
// Parse any of the accepted forms and return decimal feet. A bare number
// with no `'` or `"` anywhere is read in `defaultUnit` ('ft' by default,
// per the owner's ruling).
function parseLength(text, defaultUnit) {
return parseCoreIn(text, defaultUnit || 'ft') / 12.0;
}
window.Units = {
fmtFtIn: fmtFtIn,
fmtIn: fmtIn,
parseLength: parseLength,
parseLengthIn: parseLengthIn
};
})();