opentakeoff-mcp 0.9.47 → 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.
Files changed (2) hide show
  1. package/dist/server-core.js +27 -17
  2. package/package.json +1 -1
@@ -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
- if (fit.good && mppf > 0 && fit.r / mppf > DOOR_R_MAX_FT) return 0;
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
- if (mppf > 0 && fit.r / mppf >= DOOR_R_MIN_FT && fit.r / mppf <= DOOR_R_MAX_FT) s += 4;
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: wedgeAllowance(f, mo.mppf || 0, wedgeCapPx),
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
- if (parts.length > 1) for (const p of parts) ranked.push(entry(p, 1));
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
- const leaf = doorLeafCells(pf, p, mw, mh, mo.mask, mo.mppf || 0);
2242
- if (leaf) ranked.push({ ...entry(leaf, 2, pf), cl: leaf });
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) return r1;
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];
@@ -11119,7 +11129,7 @@ function applyStagedTools(server, registered) {
11119
11129
  // package.json
11120
11130
  var package_default = {
11121
11131
  name: "opentakeoff-mcp",
11122
- version: "0.9.47",
11132
+ version: "0.9.48",
11123
11133
  mcpName: "io.github.Kentucky-ai/opentakeoff",
11124
11134
  type: "module",
11125
11135
  description: "OpenTakeoff MCP server \u2014 drive the takeoff engine from your MCP client over stdio.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opentakeoff-mcp",
3
- "version": "0.9.47",
3
+ "version": "0.9.48",
4
4
  "mcpName": "io.github.Kentucky-ai/opentakeoff",
5
5
  "type": "module",
6
6
  "description": "OpenTakeoff MCP server — drive the takeoff engine from your MCP client over stdio.",