opentakeoff-mcp 0.9.62 → 0.9.63
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 +120 -65
- package/package.json +1 -1
package/dist/server-core.js
CHANGED
|
@@ -4638,6 +4638,64 @@ function sharedRuns(ringA, ringB, opts) {
|
|
|
4638
4638
|
}
|
|
4639
4639
|
return runs;
|
|
4640
4640
|
}
|
|
4641
|
+
var TRANSITION_DEFAULTS = { max_gap_in: 12, min_run_in: 12 };
|
|
4642
|
+
var round22 = (n) => +n.toFixed(2);
|
|
4643
|
+
var round12 = (n) => +n.toFixed(1);
|
|
4644
|
+
function dropCollinear(path5, tol = 1e-6) {
|
|
4645
|
+
if (path5.length < 3) return path5.map((p) => [p[0], p[1]]);
|
|
4646
|
+
const out = [[path5[0][0], path5[0][1]]];
|
|
4647
|
+
for (let i = 1; i < path5.length - 1; i++) {
|
|
4648
|
+
const a = out[out.length - 1], b = path5[i], c = path5[i + 1];
|
|
4649
|
+
const abx = b[0] - a[0], aby = b[1] - a[1];
|
|
4650
|
+
const acx = c[0] - a[0], acy = c[1] - a[1];
|
|
4651
|
+
const len = Math.hypot(acx, acy);
|
|
4652
|
+
const off = len > 0 ? Math.abs(abx * acy - aby * acx) / len : Infinity;
|
|
4653
|
+
if (off > tol) out.push([b[0], b[1]]);
|
|
4654
|
+
}
|
|
4655
|
+
out.push([path5[path5.length - 1][0], path5[path5.length - 1][1]]);
|
|
4656
|
+
return out;
|
|
4657
|
+
}
|
|
4658
|
+
function deriveTransitionRuns(a, b, sheets, opts = {}) {
|
|
4659
|
+
const maxGapIn = opts.max_gap_in ?? TRANSITION_DEFAULTS.max_gap_in;
|
|
4660
|
+
const minRunIn = opts.min_run_in ?? TRANSITION_DEFAULTS.min_run_in;
|
|
4661
|
+
const runs = [], withheld = [];
|
|
4662
|
+
if (!(maxGapIn > 0) || !(minRunIn > 0)) return { runs, withheld };
|
|
4663
|
+
for (const [key, frame] of sheets) {
|
|
4664
|
+
if (!(frame.upp > 0) || !(frame.widthPx > 0) || !(frame.heightPx > 0)) continue;
|
|
4665
|
+
const onA = a.shapes.filter((s) => s.sheet_id === key);
|
|
4666
|
+
const onB = b.shapes.filter((s) => s.sheet_id === key);
|
|
4667
|
+
if (!onA.length || !onB.length) continue;
|
|
4668
|
+
const pxPerFt = 1 / frame.upp;
|
|
4669
|
+
const toPx = (s) => s.verts_norm.map(([x, y]) => [x * frame.widthPx, y * frame.heightPx]);
|
|
4670
|
+
const runOpts = {
|
|
4671
|
+
step_px: Math.max(1, pxPerFt * 0.25),
|
|
4672
|
+
// a quarter-foot walk — finer than any transition matters
|
|
4673
|
+
touch_px: pxPerFt * (1 / 12),
|
|
4674
|
+
// within an inch: one open space, not two rooms
|
|
4675
|
+
max_gap_px: pxPerFt * (maxGapIn / 12),
|
|
4676
|
+
min_len_px: pxPerFt * (minRunIn / 12)
|
|
4677
|
+
};
|
|
4678
|
+
for (const ra of onA) {
|
|
4679
|
+
for (const rb of onB) {
|
|
4680
|
+
if (ra.id === rb.id) continue;
|
|
4681
|
+
for (const r of sharedRuns(toPx(ra), toPx(rb), runOpts)) {
|
|
4682
|
+
const row = {
|
|
4683
|
+
sheet_id: key,
|
|
4684
|
+
between_shape_ids: [ra.id, rb.id],
|
|
4685
|
+
between: [a.tag, b.tag],
|
|
4686
|
+
length_lf: round22(r.length_px * frame.upp),
|
|
4687
|
+
gap_in: round12(r.gap_px * frame.upp * 12),
|
|
4688
|
+
at: [Math.round(r.at[0]), Math.round(r.at[1])],
|
|
4689
|
+
verts_norm: dropCollinear(r.path).map(([x, y]) => [x / frame.widthPx, y / frame.heightPx])
|
|
4690
|
+
};
|
|
4691
|
+
if (r.kind === "wall") withheld.push({ ...row, reason: "wall_separated" });
|
|
4692
|
+
else runs.push(row);
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
return { runs, withheld };
|
|
4698
|
+
}
|
|
4641
4699
|
|
|
4642
4700
|
// ../web/src/lib/cutout.js
|
|
4643
4701
|
import { polygon as turfPolygon, featureCollection } from "@turf/helpers";
|
|
@@ -4950,7 +5008,7 @@ function applyApprovalCommand(approvals, cmd) {
|
|
|
4950
5008
|
}
|
|
4951
5009
|
|
|
4952
5010
|
// ../web/src/lib/num.js
|
|
4953
|
-
var
|
|
5011
|
+
var round23 = (n) => Math.round((n + Number.EPSILON) * 100) / 100;
|
|
4954
5012
|
|
|
4955
5013
|
// ../web/src/lib/conditionColumns.js
|
|
4956
5014
|
var visible = (v) => typeof v === "string" && v.trim() ? v : "";
|
|
@@ -5025,8 +5083,8 @@ function conditionTotals(conditions, shapes, ctx = null) {
|
|
|
5025
5083
|
const per = Math.max(0, Number(m.per) || 0);
|
|
5026
5084
|
const basisVal = m.basis === "linear" ? lf : m.basis === "count" ? ea : m.basis === "seam_lf" ? seam : total;
|
|
5027
5085
|
let qty = per > 0 ? basisVal / per : 0;
|
|
5028
|
-
qty = m.round === false ?
|
|
5029
|
-
return { name: m.name, unit: m.unit || "", per, basis: m.basis || "area", round: m.round !== false, note: m.note || "", basis_qty:
|
|
5086
|
+
qty = m.round === false ? round23(qty) : Math.ceil(qty - 1e-9);
|
|
5087
|
+
return { name: m.name, unit: m.unit || "", per, basis: m.basis || "area", round: m.round !== false, note: m.note || "", basis_qty: round23(basisVal), qty };
|
|
5030
5088
|
});
|
|
5031
5089
|
return {
|
|
5032
5090
|
id: c.id,
|
|
@@ -5037,19 +5095,19 @@ function conditionTotals(conditions, shapes, ctx = null) {
|
|
|
5037
5095
|
multiplier: mult,
|
|
5038
5096
|
waste_pct: waste,
|
|
5039
5097
|
shape_count: cs.length,
|
|
5040
|
-
floor_sf:
|
|
5041
|
-
wall_sf:
|
|
5042
|
-
border_sf:
|
|
5043
|
-
lf:
|
|
5098
|
+
floor_sf: round23(floor),
|
|
5099
|
+
wall_sf: round23(wall),
|
|
5100
|
+
border_sf: round23(border),
|
|
5101
|
+
lf: round23(lf),
|
|
5044
5102
|
ea,
|
|
5045
|
-
total_sf:
|
|
5103
|
+
total_sf: round23(total),
|
|
5046
5104
|
// waste-adjusted (order quantities)
|
|
5047
|
-
floor_sf_net:
|
|
5048
|
-
wall_sf_net:
|
|
5049
|
-
border_sf_net:
|
|
5050
|
-
lf_net:
|
|
5051
|
-
total_sf_net:
|
|
5052
|
-
sy_net:
|
|
5105
|
+
floor_sf_net: round23(floor * w),
|
|
5106
|
+
wall_sf_net: round23(wall * w),
|
|
5107
|
+
border_sf_net: round23(border * w),
|
|
5108
|
+
lf_net: round23(lf * w),
|
|
5109
|
+
total_sf_net: round23(total * w),
|
|
5110
|
+
sy_net: round23(total * w / 9),
|
|
5053
5111
|
materials
|
|
5054
5112
|
};
|
|
5055
5113
|
});
|
|
@@ -5098,11 +5156,11 @@ function hasMultipliers(bySheet) {
|
|
|
5098
5156
|
function roundSheetRow(r) {
|
|
5099
5157
|
return {
|
|
5100
5158
|
...r,
|
|
5101
|
-
floor_sf:
|
|
5102
|
-
wall_sf:
|
|
5103
|
-
border_sf:
|
|
5104
|
-
lf:
|
|
5105
|
-
ea:
|
|
5159
|
+
floor_sf: round23(r.floor_sf),
|
|
5160
|
+
wall_sf: round23(r.wall_sf),
|
|
5161
|
+
border_sf: round23(r.border_sf),
|
|
5162
|
+
lf: round23(r.lf),
|
|
5163
|
+
ea: round23(r.ea)
|
|
5106
5164
|
};
|
|
5107
5165
|
}
|
|
5108
5166
|
function materialsSummary(rows) {
|
|
@@ -5113,17 +5171,17 @@ function materialsSummary(rows) {
|
|
|
5113
5171
|
cur.qty += m.qty;
|
|
5114
5172
|
map.set(key, cur);
|
|
5115
5173
|
}
|
|
5116
|
-
return [...map.values()].map((x) => ({ ...x, qty:
|
|
5174
|
+
return [...map.values()].map((x) => ({ ...x, qty: round23(x.qty) }));
|
|
5117
5175
|
}
|
|
5118
5176
|
function grandTotals(rows) {
|
|
5119
5177
|
const sum = (k) => rows.reduce((n, r) => n + (r[k] || 0), 0);
|
|
5120
5178
|
return {
|
|
5121
|
-
total_sf:
|
|
5122
|
-
total_sf_net:
|
|
5123
|
-
lf:
|
|
5124
|
-
lf_net:
|
|
5179
|
+
total_sf: round23(sum("total_sf")),
|
|
5180
|
+
total_sf_net: round23(sum("total_sf_net")),
|
|
5181
|
+
lf: round23(sum("lf")),
|
|
5182
|
+
lf_net: round23(sum("lf_net")),
|
|
5125
5183
|
ea: sum("ea"),
|
|
5126
|
-
sy_net:
|
|
5184
|
+
sy_net: round23(sum("sy_net"))
|
|
5127
5185
|
};
|
|
5128
5186
|
}
|
|
5129
5187
|
function reportJson({ projectName = "", rows = [], bySheet = [], scaleInfo = [], markups = [], rfis = [], sheetLabel = null, conditionColumns = [], attrsByCond = null, shapeLabels = [], byLabel = [], displayUnits = "imperial", rollGoods = [] }) {
|
|
@@ -5621,7 +5679,7 @@ function computeRollTakeoff(conditions, shapes, dimsFor, uppFor) {
|
|
|
5621
5679
|
orderFt,
|
|
5622
5680
|
rollCount: rollLayoutRollCount(layout.strips),
|
|
5623
5681
|
oversize: layout.strips.some((s) => s.overRoll),
|
|
5624
|
-
qty:
|
|
5682
|
+
qty: round23(rollQtyForUnit(orderFt, config.rollWidthFt, unit)),
|
|
5625
5683
|
unit,
|
|
5626
5684
|
cutCount: layout.strips.length,
|
|
5627
5685
|
seamLf: seamLfForStrips(layout.strips),
|
|
@@ -5674,16 +5732,16 @@ function rollReportRows(rollByCond, rows) {
|
|
|
5674
5732
|
roll_length_ft: ri.config.rollLengthFt,
|
|
5675
5733
|
direction: ri.direction,
|
|
5676
5734
|
cuts: ri.cutCount,
|
|
5677
|
-
order_lf:
|
|
5735
|
+
order_lf: round23(ri.orderFt * mult),
|
|
5678
5736
|
rolls: ri.rollCount * mult,
|
|
5679
|
-
order_qty:
|
|
5737
|
+
order_qty: round23(ri.qty * mult),
|
|
5680
5738
|
order_unit: ri.unit,
|
|
5681
5739
|
oversize: ri.oversize,
|
|
5682
5740
|
// seam_lf APPENDS last (the additive-only convention the roll_goods
|
|
5683
5741
|
// block already follows): the figured weld-rod / seam-tape length, ×N
|
|
5684
5742
|
// like every other reported quantity. 0 is a real answer — a layout
|
|
5685
5743
|
// whose rooms each fit one strip has no seams to weld.
|
|
5686
|
-
seam_lf:
|
|
5744
|
+
seam_lf: round23((ri.seamLf || 0) * mult)
|
|
5687
5745
|
});
|
|
5688
5746
|
}
|
|
5689
5747
|
return out;
|
|
@@ -7153,26 +7211,40 @@ var Session = class _Session {
|
|
|
7153
7211
|
const s = this.sheet(key);
|
|
7154
7212
|
if (s.upp == null) throw new UserError(`${key} has no scale \u2014 a transition is a real length, so set_scale first (${this.scaleGate(s)})`);
|
|
7155
7213
|
}
|
|
7156
|
-
const
|
|
7214
|
+
const frames = /* @__PURE__ */ new Map();
|
|
7157
7215
|
for (const key of sheetsInPlay) {
|
|
7158
7216
|
const s = this.sheet(key);
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7217
|
+
frames.set(key, { widthPx: s.widthPx, heightPx: s.heightPx, upp: s.upp });
|
|
7218
|
+
}
|
|
7219
|
+
const src = (sh) => ({ id: sh.id, sheet_id: sh.sheet_id, verts_norm: sh.verts_norm });
|
|
7220
|
+
const derived = deriveTransitionRuns(
|
|
7221
|
+
{ tag: a.finish_tag, shapes: fa.map(src) },
|
|
7222
|
+
{ tag: b.finish_tag, shapes: fb.map(src) },
|
|
7223
|
+
frames,
|
|
7224
|
+
{ max_gap_in: maxGapIn, min_run_in: minRunIn }
|
|
7225
|
+
);
|
|
7226
|
+
const committed = [], withheld = [];
|
|
7227
|
+
for (const w of derived.withheld) {
|
|
7228
|
+
withheld.push({
|
|
7229
|
+
sheet: w.sheet_id,
|
|
7230
|
+
between_shape_ids: w.between_shape_ids,
|
|
7231
|
+
length_lf: w.length_lf,
|
|
7232
|
+
gap_in: w.gap_in,
|
|
7233
|
+
at: w.at,
|
|
7234
|
+
reason: "wall_separated",
|
|
7235
|
+
detail: `${a.finish_tag} and ${b.finish_tag} run ${w.length_lf} LF apart across ${w.gap_in}" of wall \u2014 adjacent rooms, not a butt joint. If a door opens here the transition is a threshold at the door, which this cannot see.`
|
|
7236
|
+
});
|
|
7237
|
+
}
|
|
7238
|
+
for (const r of derived.runs) {
|
|
7239
|
+
const s = this.sheet(r.sheet_id);
|
|
7240
|
+
const pathPx = r.verts_norm.map(([x, y]) => [x * s.widthPx, y * s.heightPx]);
|
|
7241
|
+
const shape = this.commit(s, opts.condition, "linear", pathPx, { area_sf: 0, perimeter_lf: r.length_lf }, {
|
|
7242
|
+
method: "agent_v1",
|
|
7243
|
+
actor: "agent",
|
|
7244
|
+
reviewed: false,
|
|
7245
|
+
derived: { between_shape_ids: r.between_shape_ids, between: [a.finish_tag, b.finish_tag], case: "butt", gap_in: r.gap_in }
|
|
7246
|
+
});
|
|
7247
|
+
committed.push({ sheet: r.sheet_id, between_shape_ids: r.between_shape_ids, length_lf: r.length_lf, gap_in: r.gap_in, at: r.at, shape_id: shape.id });
|
|
7176
7248
|
}
|
|
7177
7249
|
if (committed.length) this.flushCommits("derive_transitions");
|
|
7178
7250
|
return {
|
|
@@ -7186,23 +7258,6 @@ var Session = class _Session {
|
|
|
7186
7258
|
note: withheld.length ? `${withheld.length} run(s) are adjacency ACROSS A WALL, not a butt joint \u2014 the transition there is a threshold in the doorway, and the trace record does not say where the doorway is. view_sheet each \`at\` and place them with measure_line / place_count.` : "Every run was a butt joint inside one open space. Verify with view_sheet overlay:true before trusting the total."
|
|
7187
7259
|
};
|
|
7188
7260
|
}
|
|
7189
|
-
/** One shared run → committed transition, or a disclosed question. */
|
|
7190
|
-
recordRun(s, r, upp, condition, aId, bId, aTag, bTag, committed, withheld) {
|
|
7191
|
-
const length_lf = round2(r.length_px * upp);
|
|
7192
|
-
const gap_in = round1(r.gap_px * upp * 12);
|
|
7193
|
-
const row = { sheet: s.key, between_shape_ids: [aId, bId], length_lf, gap_in, at: [Math.round(r.at[0]), Math.round(r.at[1])] };
|
|
7194
|
-
if (r.kind === "wall") {
|
|
7195
|
-
withheld.push({ ...row, reason: "wall_separated", detail: `${aTag} and ${bTag} run ${length_lf} LF apart across ${gap_in}" of wall \u2014 adjacent rooms, not a butt joint. If a door opens here the transition is a threshold at the door, which this cannot see.` });
|
|
7196
|
-
return;
|
|
7197
|
-
}
|
|
7198
|
-
const shape = this.commit(s, condition, "linear", r.path, { area_sf: 0, perimeter_lf: length_lf }, {
|
|
7199
|
-
method: "agent_v1",
|
|
7200
|
-
actor: "agent",
|
|
7201
|
-
reviewed: false,
|
|
7202
|
-
derived: { between_shape_ids: [aId, bId], between: [aTag, bTag], case: "butt", gap_in }
|
|
7203
|
-
});
|
|
7204
|
-
committed.push({ ...row, shape_id: shape.id });
|
|
7205
|
-
}
|
|
7206
7261
|
/** Count markers — the canvas's Count tool (commitCount): one point, one EA,
|
|
7207
7262
|
* computed {count: 1}, NO scale required (EA is scale-free; the canvas's
|
|
7208
7263
|
* recompute skips count shapes for the same reason). One shape per point,
|
|
@@ -12156,7 +12211,7 @@ function nameTheStageInRefusals(server) {
|
|
|
12156
12211
|
// package.json
|
|
12157
12212
|
var package_default = {
|
|
12158
12213
|
name: "opentakeoff-mcp",
|
|
12159
|
-
version: "0.9.
|
|
12214
|
+
version: "0.9.63",
|
|
12160
12215
|
mcpName: "io.github.Kentucky-ai/opentakeoff",
|
|
12161
12216
|
type: "module",
|
|
12162
12217
|
description: "OpenTakeoff MCP server \u2014 drive the takeoff engine from your MCP client over stdio.",
|
package/package.json
CHANGED