opentakeoff-mcp 0.9.46 → 0.9.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server-core.js +61 -32
- package/package.json +1 -1
package/dist/server-core.js
CHANGED
|
@@ -618,6 +618,7 @@ var ARC_CUSP_SPAN_MULT = 8;
|
|
|
618
618
|
var MASK_NODOOR_BIT = 8;
|
|
619
619
|
var DOOR_R_MIN_FT = 1.5;
|
|
620
620
|
var DOOR_R_MAX_FT = 4.5;
|
|
621
|
+
var DOOR_R_LEAF_MAX_FT = 7;
|
|
621
622
|
var CLUSTER_FIT_TOL_FRAC = 0.05;
|
|
622
623
|
var CLUSTER_FIT_TOL_PX = 1.5;
|
|
623
624
|
var HATCH_ANGLE_TOL = 2;
|
|
@@ -2065,9 +2066,10 @@ function wedgeRimPx(maskPxPerFt) {
|
|
|
2065
2066
|
if (!Number.isFinite(maskPxPerFt) || maskPxPerFt <= 0) return 3;
|
|
2066
2067
|
return Math.max(3, Math.round(WEDGE_RIM_FT * maskPxPerFt));
|
|
2067
2068
|
}
|
|
2068
|
-
function wedgeAllowance(fit, mppf, wedgeCapPx) {
|
|
2069
|
+
function wedgeAllowance(fit, mppf, wedgeCapPx, leafFound = false) {
|
|
2069
2070
|
if (fit.noDoorFrac > 0.5 && !fit.good) return 0;
|
|
2070
|
-
|
|
2071
|
+
const rMax = leafFound ? DOOR_R_LEAF_MAX_FT : DOOR_R_MAX_FT;
|
|
2072
|
+
if (fit.good && mppf > 0 && fit.r / mppf > rMax) return 0;
|
|
2071
2073
|
const rim = wedgeRimPx(mppf);
|
|
2072
2074
|
const bu = fit.good ? fit.buH : fit.bu, bn = fit.good ? fit.bnH : fit.bn;
|
|
2073
2075
|
let area = bu * bn;
|
|
@@ -2075,12 +2077,13 @@ function wedgeAllowance(fit, mppf, wedgeCapPx) {
|
|
|
2075
2077
|
area += 2 * rim * (bu + bn) + 4 * rim * rim;
|
|
2076
2078
|
return Math.min(2 * wedgeCapPx, Math.round(area * WEDGE_SLACK));
|
|
2077
2079
|
}
|
|
2078
|
-
function doorLikeness(fit, mppf) {
|
|
2080
|
+
function doorLikeness(fit, mppf, leafFound = false) {
|
|
2079
2081
|
let s = 1 - fit.noDoorFrac;
|
|
2080
2082
|
const swDeg = fit.sweep * 180 / Math.PI;
|
|
2081
2083
|
if (fit.good) {
|
|
2082
2084
|
s += 1;
|
|
2083
|
-
|
|
2085
|
+
const rMax = leafFound ? DOOR_R_LEAF_MAX_FT : DOOR_R_MAX_FT;
|
|
2086
|
+
if (mppf > 0 && fit.r / mppf >= DOOR_R_MIN_FT && fit.r / mppf <= rMax) s += 4;
|
|
2084
2087
|
if (swDeg >= 45 && swDeg <= 190) s += 2;
|
|
2085
2088
|
}
|
|
2086
2089
|
return s;
|
|
@@ -2211,19 +2214,21 @@ function floodRegionSealedInner(mo, ix, iy, sensitivity, radii, wedgeCapPx, minP
|
|
|
2211
2214
|
const { mw, mh } = mo;
|
|
2212
2215
|
let region = null;
|
|
2213
2216
|
let count = r1.count;
|
|
2214
|
-
let wedges = 0, ringWedges = 0;
|
|
2217
|
+
let wedges = 0, ringWedges = 0, refused = 0;
|
|
2215
2218
|
let hatchFiltered = !!r1.hatchFiltered;
|
|
2216
2219
|
let hatchTier = r1.hatchTier;
|
|
2217
2220
|
let sealedPx = r1.sealedPx, virtualFrac = r1.virtualFrac;
|
|
2218
2221
|
let minPassPxOut = r1.minPassPx, minPassDelta = r1.minPassDelta;
|
|
2219
|
-
const entry = (cl, pass, fit) => {
|
|
2222
|
+
const entry = (cl, pass, fit, leafFound = false) => {
|
|
2220
2223
|
const f = fit ?? arcClusterFit(cl, mw, mo.mask);
|
|
2221
2224
|
const ideal = 0.5 * f.r * f.r * f.sweep;
|
|
2225
|
+
const allow = wedgeAllowance(f, mo.mppf || 0, wedgeCapPx, leafFound);
|
|
2226
|
+
if (!allow && f.good && (mo.mppf || 0) > 0 && f.r / mo.mppf > (leafFound ? DOOR_R_LEAF_MAX_FT : DOOR_R_MAX_FT)) refused++;
|
|
2222
2227
|
return {
|
|
2223
2228
|
cl,
|
|
2224
2229
|
fit: f,
|
|
2225
|
-
allow
|
|
2226
|
-
rank: doorLikeness(f, mo.mppf || 0),
|
|
2230
|
+
allow,
|
|
2231
|
+
rank: doorLikeness(f, mo.mppf || 0, leafFound),
|
|
2227
2232
|
at: cl[0],
|
|
2228
2233
|
pass,
|
|
2229
2234
|
minGrow: pass === 2 ? Math.max(1, Math.round(LEAF_MIN_SECTOR_FRAC * ideal)) : 0
|
|
@@ -2232,15 +2237,16 @@ function floodRegionSealedInner(mo, ix, iy, sensitivity, radii, wedgeCapPx, minP
|
|
|
2232
2237
|
const ranked = [];
|
|
2233
2238
|
for (const cl of clusters) {
|
|
2234
2239
|
const fit = arcClusterFit(cl, mw, mo.mask);
|
|
2235
|
-
ranked.push(entry(cl, 0, fit));
|
|
2236
2240
|
const parts = splitMergedArcs(cl, mw, mo.mask, mo.mppf || 0);
|
|
2237
2241
|
const pieces = parts.length > 1 ? parts : [cl];
|
|
2238
|
-
|
|
2239
|
-
for (const p of pieces) {
|
|
2242
|
+
const leaves = pieces.map((p) => {
|
|
2240
2243
|
const pf = p === cl ? fit : arcClusterFit(p, mw, mo.mask);
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
+
return { p, pf, leaf: doorLeafCells(pf, p, mw, mh, mo.mask, mo.mppf || 0) };
|
|
2245
|
+
});
|
|
2246
|
+
const anyLeaf = leaves.some((l) => !!l.leaf);
|
|
2247
|
+
ranked.push(entry(cl, 0, fit, anyLeaf));
|
|
2248
|
+
if (parts.length > 1) for (const { p, pf, leaf } of leaves) ranked.push(entry(p, 1, pf, !!leaf));
|
|
2249
|
+
for (const { pf, leaf } of leaves) if (leaf) ranked.push({ ...entry(leaf, 2, pf, true), cl: leaf });
|
|
2244
2250
|
}
|
|
2245
2251
|
ranked.splice(0, ranked.length, ...ranked.filter((c) => c.allow >= 1).sort((a, b) => a.pass - b.pass || b.rank - a.rank || a.at - b.at));
|
|
2246
2252
|
let m2mask = null, m2dt = null;
|
|
@@ -2317,7 +2323,10 @@ function floodRegionSealedInner(mo, ix, iy, sensitivity, radii, wedgeCapPx, minP
|
|
|
2317
2323
|
if (r2.minPassPx && (!minPassPxOut || r2.minPassPx > minPassPxOut)) minPassPxOut = r2.minPassPx;
|
|
2318
2324
|
if (r2.minPassDelta != null && (minPassDelta == null || r2.minPassDelta > minPassDelta)) minPassDelta = r2.minPassDelta;
|
|
2319
2325
|
}
|
|
2320
|
-
if (!wedges || !region)
|
|
2326
|
+
if (!wedges || !region) {
|
|
2327
|
+
if (refused) r1.wedgesRefused = refused;
|
|
2328
|
+
return r1;
|
|
2329
|
+
}
|
|
2321
2330
|
const out = {
|
|
2322
2331
|
status: "ok",
|
|
2323
2332
|
region,
|
|
@@ -2334,7 +2343,8 @@ function floodRegionSealedInner(mo, ix, iy, sensitivity, radii, wedgeCapPx, minP
|
|
|
2334
2343
|
virtualFrac,
|
|
2335
2344
|
minPassPx: minPassPxOut,
|
|
2336
2345
|
minPassDelta,
|
|
2337
|
-
...ringWedges ? { ringWedges } : {}
|
|
2346
|
+
...ringWedges ? { ringWedges } : {},
|
|
2347
|
+
...refused ? { wedgesRefused: refused } : {}
|
|
2338
2348
|
};
|
|
2339
2349
|
const reg = region, reg1 = r1.region, mask = mo.mask;
|
|
2340
2350
|
const isDelta = (i) => reg[i] && !reg1[i];
|
|
@@ -2678,11 +2688,13 @@ function clusterRows(spans) {
|
|
|
2678
2688
|
}
|
|
2679
2689
|
var rowY = (r) => r.reduce((s, t) => s + t.y, 0) / r.length;
|
|
2680
2690
|
var SURFACE_WORDS = /* @__PURE__ */ new Set(["FLOOR", "BASE", "WALL", "WALLS", "CEILING", "NORTH", "SOUTH", "EAST", "WEST", "WAINSCOT"]);
|
|
2681
|
-
var ROOM_HEADERS = ["ROOM", "NO", "NUMBER", "NAME", "MARK", "LOCATION", "FLOOR", "BASE", "WALL", "WALLS", "NORTH", "SOUTH", "EAST", "WEST", "CEILING", "WAINSCOT", "REMARKS", "CLG", "HT", "HEIGHT", "FINISH", "BLDG", "BUILDING"];
|
|
2691
|
+
var ROOM_HEADERS = ["ROOM", "NO", "NUMBER", "NAME", "MARK", "LOCATION", "FLOOR", "BASE", "WALL", "WALLS", "NORTH", "SOUTH", "EAST", "WEST", "CEILING", "WAINSCOT", "REMARKS", "CLG", "HT", "HEIGHT", "FINISH", "CASEWORK", "CABINET", "COUNTER", "COUNTERTOP", "BLDG", "BUILDING"];
|
|
2682
2692
|
var FINISH_HEADERS = ["CODE", "MARK", "SYMBOL", "MATERIAL", "MANUFACTURER", "PRODUCT", "STYLE", "COLOR", "SIZE", "REMARKS", "DESCRIPTION", "PATTERN", "COMMENTS"];
|
|
2683
|
-
var headerLabel = (s, vocab) =>
|
|
2684
|
-
|
|
2685
|
-
|
|
2693
|
+
var headerLabel = (s, vocab) => headerLabels(s, vocab)[0] ?? null;
|
|
2694
|
+
var headerLabels = (s, vocab) => {
|
|
2695
|
+
const out = [];
|
|
2696
|
+
for (const w of norm(s).split(/[^A-Z]+/)) if (w && vocab.includes(w) && !out.includes(w)) out.push(w);
|
|
2697
|
+
return out;
|
|
2686
2698
|
};
|
|
2687
2699
|
function headerHits(row, vocab) {
|
|
2688
2700
|
const out = [];
|
|
@@ -2728,6 +2740,10 @@ function findHeaderRow(rows, vocab, required, minHits) {
|
|
|
2728
2740
|
const parent = parentLabelOver(rows, idx, i, h.span.x, hi, vocab);
|
|
2729
2741
|
if (parent && parent !== h.label) label = `${parent} ${h.label}`;
|
|
2730
2742
|
}
|
|
2743
|
+
if (used.has(label)) {
|
|
2744
|
+
const alt = headerLabels(h.span.str, vocab).find((w) => !used.has(w));
|
|
2745
|
+
if (alt) label = alt;
|
|
2746
|
+
}
|
|
2731
2747
|
if (used.has(label)) continue;
|
|
2732
2748
|
used.add(label);
|
|
2733
2749
|
anchors.push({ label, x: h.span.x + (h.span.w || 0) / 2 });
|
|
@@ -2877,14 +2893,15 @@ function rowKeyOf(raw, kind, buildings) {
|
|
|
2877
2893
|
}
|
|
2878
2894
|
var numOf = (key) => key.match(QUALIFIED_KEY_RE)?.[2] ?? key;
|
|
2879
2895
|
var centerX = (t) => t.x + (t.w || 0) / 2;
|
|
2880
|
-
function
|
|
2896
|
+
function columnMapFor(rows, anchors, cfg, x0, x1, coord) {
|
|
2897
|
+
const at = (t) => coord === "left" ? t.x : t.x + (t.w || 0) / 2;
|
|
2881
2898
|
const xs = [];
|
|
2882
2899
|
const hs = [];
|
|
2883
2900
|
for (let i = Math.max(cfg.fromIdx, 0); i < rows.length; i++) {
|
|
2884
2901
|
if (rowY(rows[i]) <= cfg.belowY) continue;
|
|
2885
2902
|
for (const t of rows[i]) {
|
|
2886
2903
|
if (t.x < x0 || t.x > x1 || revisionOf(t.str) != null) continue;
|
|
2887
|
-
xs.push(t
|
|
2904
|
+
xs.push(at(t));
|
|
2888
2905
|
hs.push(t.h || 8);
|
|
2889
2906
|
}
|
|
2890
2907
|
}
|
|
@@ -2912,23 +2929,35 @@ function columnStarts(rows, anchors, cfg, x0, x1) {
|
|
|
2912
2929
|
if (cur == null || c.start < cur) byLabel.set(own.label, c.start);
|
|
2913
2930
|
}
|
|
2914
2931
|
if (byLabel.size !== anchors.length) return null;
|
|
2915
|
-
const
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2932
|
+
const cols = [...byLabel.entries()].map(([label, start]) => ({ label, start })).sort((a, b) => a.start - b.start);
|
|
2933
|
+
if (cols.map((c) => c.label).join("|") !== anchors.map((a) => a.label).join("|")) return null;
|
|
2934
|
+
const starts = kept.map((c) => c.start);
|
|
2935
|
+
let on = 0;
|
|
2936
|
+
for (const x of xs) if (starts.some((st) => Math.abs(x - st) <= tol)) on++;
|
|
2937
|
+
return { coord, cols, score: on / xs.length };
|
|
2938
|
+
}
|
|
2939
|
+
function columnStarts(rows, anchors, cfg, x0, x1) {
|
|
2940
|
+
const FIT_FLOOR = 0.7;
|
|
2941
|
+
const fits = (m) => m && m.score >= FIT_FLOOR ? m : null;
|
|
2942
|
+
const left = fits(columnMapFor(rows, anchors, cfg, x0, x1, "left"));
|
|
2943
|
+
const center = fits(columnMapFor(rows, anchors, cfg, x0, x1, "center"));
|
|
2944
|
+
if (!left) return center;
|
|
2945
|
+
if (!center) return left;
|
|
2946
|
+
return center.score > left.score + 0.05 ? center : left;
|
|
2919
2947
|
}
|
|
2920
2948
|
function bandDataRows(rows, anchors, kind, sheetKey, buildings, cfg) {
|
|
2921
2949
|
const { x0, x1, medGap } = bandLimits(anchors);
|
|
2922
2950
|
const cols = columnStarts(rows, anchors, cfg, x0, x1);
|
|
2923
|
-
const keyTol = cols && cols.length > 1 ? Math.max(8, (cols[1].start - cols[0].start) * 0.5) : 40;
|
|
2951
|
+
const keyTol = cols && cols.cols.length > 1 ? Math.max(8, (cols.cols[1].start - cols.cols[0].start) * 0.5) : 40;
|
|
2924
2952
|
const out = [];
|
|
2925
2953
|
const outY = [];
|
|
2926
2954
|
let region = null;
|
|
2927
2955
|
const columnOf = (t) => {
|
|
2928
2956
|
if (!cols) return nearestAnchor(centerX(t), anchors);
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2957
|
+
const at = cols.coord === "left" ? t.x : centerX(t);
|
|
2958
|
+
let label = cols.cols[0].label;
|
|
2959
|
+
for (const c of cols.cols) {
|
|
2960
|
+
if (at + 1 >= c.start) label = c.label;
|
|
2932
2961
|
else break;
|
|
2933
2962
|
}
|
|
2934
2963
|
return label;
|
|
@@ -2962,7 +2991,7 @@ function bandDataRows(rows, anchors, kind, sheetKey, buildings, cfg) {
|
|
|
2962
2991
|
orphans.push({ toks: banded, y: rowY(rows[i]) });
|
|
2963
2992
|
continue;
|
|
2964
2993
|
}
|
|
2965
|
-
if (cols && Math.abs(banded[0].x - cols[0].start) > keyTol) continue;
|
|
2994
|
+
if (cols && Math.abs((cols.coord === "left" ? banded[0].x : centerX(banded[0])) - cols.cols[0].start) > keyTol) continue;
|
|
2966
2995
|
if (cfg.keyAlign && Math.abs(centerX(banded[0]) - cfg.keyAlign.x) > cfg.keyAlign.tol) continue;
|
|
2967
2996
|
const row = { key: keyed.key, sheet: sheetKey, cells: {} };
|
|
2968
2997
|
if (keyed.building) row.building = keyed.building;
|
|
@@ -11100,7 +11129,7 @@ function applyStagedTools(server, registered) {
|
|
|
11100
11129
|
// package.json
|
|
11101
11130
|
var package_default = {
|
|
11102
11131
|
name: "opentakeoff-mcp",
|
|
11103
|
-
version: "0.9.
|
|
11132
|
+
version: "0.9.48",
|
|
11104
11133
|
mcpName: "io.github.Kentucky-ai/opentakeoff",
|
|
11105
11134
|
type: "module",
|
|
11106
11135
|
description: "OpenTakeoff MCP server \u2014 drive the takeoff engine from your MCP client over stdio.",
|
package/package.json
CHANGED