@floless/app 0.57.0 → 0.59.0
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/floless-server.cjs +87 -21
- package/dist/skills/floless-app-rebake/SKILL.md +7 -5
- package/dist/skills/floless-app-steel-takeoff/SKILL.md +56 -19
- package/dist/web/app.css +15 -0
- package/dist/web/aware.js +165 -14
- package/dist/web/steel-3d-view.js +755 -38
- package/dist/web/steel-editor.html +484 -76
- package/package.json +1 -1
package/dist/floless-server.cjs
CHANGED
|
@@ -53022,7 +53022,7 @@ function appVersion() {
|
|
|
53022
53022
|
return resolveVersion({
|
|
53023
53023
|
isSea: isSea2(),
|
|
53024
53024
|
sqVersionXml: readSqVersionXml(),
|
|
53025
|
-
define: true ? "0.
|
|
53025
|
+
define: true ? "0.59.0" : void 0,
|
|
53026
53026
|
pkgVersion: readPkgVersion()
|
|
53027
53027
|
});
|
|
53028
53028
|
}
|
|
@@ -53032,7 +53032,7 @@ function resolveChannel(s) {
|
|
|
53032
53032
|
return "dev";
|
|
53033
53033
|
}
|
|
53034
53034
|
function appChannel() {
|
|
53035
|
-
return resolveChannel({ isSea: isSea2(), define: true ? "0.
|
|
53035
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.59.0" : void 0 });
|
|
53036
53036
|
}
|
|
53037
53037
|
|
|
53038
53038
|
// workflow-update.ts
|
|
@@ -53325,6 +53325,9 @@ function extractReportHtml(events) {
|
|
|
53325
53325
|
return found;
|
|
53326
53326
|
}
|
|
53327
53327
|
|
|
53328
|
+
// aisc-shapes.json
|
|
53329
|
+
var aisc_shapes_default = { "C10X15.3": 2.6, C10X20: 2.74, C10X25: 2.89, C10X30: 3.03, "C12X20.7": 2.94, C12X25: 3.05, C12X30: 3.17, "C15X33.9": 3.4, C15X40: 3.52, C15X50: 3.72, "C3X3.5": 1.37, "C3X4.1": 1.41, C3X5: 1.5, C3X6: 1.6, "C4X4.5": 1.52, "C4X5.4": 1.58, "C4X6.25": 1.65, "C4X7.25": 1.72, "C5X6.7": 1.75, C5X9: 1.89, "C6X10.5": 2.03, C6X13: 2.16, "C6X8.2": 1.92, "C7X12.25": 2.19, "C7X14.75": 2.3, "C7X9.8": 2.09, "C8X11.5": 2.26, "C8X13.75": 2.34, "C8X18.75": 2.53, "C9X13.4": 2.43, C9X15: 2.49, C9X20: 2.65, HP10X42: 10.1, HP10X57: 10.2, HP12X53: 12, HP12X63: 12.1, HP12X74: 12.2, HP12X84: 12.3, HP12X89: 12.3, HP14X102: 14.8, HP14X117: 14.9, HP14X73: 14.6, HP14X89: 14.7, HP16X101: 15.8, HP16X121: 15.9, HP16X141: 16, HP16X162: 16.1, HP16X183: 16.3, HP16X88: 15.7, HP18X135: 17.8, HP18X157: 17.9, HP18X181: 18, HP18X204: 18.1, HP8X36: 8.16, "M10X7.5": 2.69, M10X8: 2.69, M10X9: 2.69, "M12.5X11.6": 3.5, "M12.5X12.4": 3.75, M12X10: 3.25, "M12X10.8": 3.07, "M12X11.8": 3.07, "M3X2.9": 2.25, "M4X3.2": 2.25, "M4X3.45": 2.25, "M4X4.08": 2.25, M4X6: 3.8, "M5X18.9": 5, "M6X3.7": 2, "M6X4.4": 1.84, "M8X6.2": 2.28, "M8X6.5": 2.28, MC10X22: 3.32, MC10X25: 3.41, "MC10X28.5": 3.95, "MC10X33.6": 4.1, "MC10X41.1": 4.32, "MC10X6.5": 1.17, "MC10X8.4": 1.5, "MC12X10.6": 1.5, "MC12X14.3": 2.12, MC12X31: 3.67, MC12X35: 3.77, MC12X40: 3.89, MC12X45: 4.01, MC12X50: 4.14, "MC13X31.8": 4, MC13X35: 4.07, MC13X40: 4.19, MC13X50: 4.41, "MC18X42.7": 3.95, "MC18X45.8": 4, "MC18X51.9": 4.1, MC18X58: 4.2, "MC3X7.1": 1.94, "MC4X13.8": 2.5, MC6X12: 2.5, "MC6X15.1": 2.94, "MC6X15.3": 3.5, "MC6X16.3": 3, MC6X18: 3.5, "MC6X6.5": 1.85, MC6X7: 1.88, "MC7X19.1": 3.45, "MC7X22.7": 3.6, "MC8X18.7": 2.98, MC8X20: 3.03, "MC8X21.4": 3.45, "MC8X22.8": 3.5, "MC8X8.5": 1.87, "MC9X23.9": 3.45, "MC9X25.4": 3.5, "MT2.5X9.45": 5, MT2X3: 3.8, "MT3X1.85": 2, "MT3X2.2": 1.84, "MT4X3.1": 2.28, "MT4X3.25": 2.28, "MT5X3.75": 2.69, MT5X4: 2.69, "MT5X4.5": 2.69, "MT6.25X5.8": 3.5, "MT6.25X6.2": 3.75, MT6X5: 3.25, "MT6X5.4": 3.07, "MT6X5.9": 3.07, "S10X25.4": 4.66, S10X35: 4.94, "S12X31.8": 5, S12X35: 5.08, "S12X40.8": 5.25, S12X50: 5.48, "S15X42.9": 5.5, S15X50: 5.64, "S18X54.7": 6, S18X70: 6.25, S20X66: 6.26, S20X75: 6.39, S20X86: 7.06, S20X96: 7.2, S24X100: 7.25, S24X106: 7.87, S24X121: 8.05, S24X80: 7, S24X90: 7.13, "S3X5.7": 2.33, "S3X7.5": 2.51, "S4X7.7": 2.66, "S4X9.5": 2.8, S5X10: 3, "S6X12.5": 3.33, "S6X17.25": 3.57, "S8X18.4": 4, S8X23: 4.17, "ST1.5X2.85": 2.33, "ST1.5X3.75": 2.51, ST10X33: 6.26, "ST10X37.5": 6.39, ST10X43: 7.06, ST10X48: 7.2, ST12X40: 7, ST12X45: 7.13, ST12X50: 7.25, ST12X53: 7.87, "ST12X60.5": 8.05, "ST2.5X5": 3, "ST2X3.85": 2.66, "ST2X4.75": 2.8, "ST3X6.25": 3.33, "ST3X8.6": 3.57, "ST4X11.5": 4.17, "ST4X9.2": 4, "ST5X12.7": 4.66, "ST5X17.5": 4.94, "ST6X15.9": 5, "ST6X17.5": 5.08, "ST6X20.4": 5.25, ST6X25: 5.48, "ST7.5X21.45": 5.5, "ST7.5X25": 5.64, "ST9X27.35": 6, ST9X35: 6.25, W10X100: 10.3, W10X112: 10.4, W10X12: 3.96, W10X15: 4, W10X17: 4.01, W10X19: 4.02, W10X22: 5.75, W10X26: 5.77, W10X30: 5.81, W10X33: 7.96, W10X39: 7.99, W10X45: 8.02, W10X49: 10, W10X54: 10, W10X60: 10.1, W10X68: 10.1, W10X77: 10.2, W10X88: 10.3, W12X106: 12.2, W12X120: 12.3, W12X136: 12.4, W12X14: 3.97, W12X152: 12.5, W12X16: 3.99, W12X170: 12.6, W12X19: 4.01, W12X190: 12.7, W12X210: 12.8, W12X22: 4.03, W12X230: 12.9, W12X252: 13, W12X26: 6.49, W12X279: 13.1, W12X30: 6.52, W12X305: 13.2, W12X336: 13.4, W12X35: 6.56, W12X40: 8.01, W12X45: 8.05, W12X50: 8.08, W12X53: 10, W12X58: 10, W12X65: 12, W12X72: 12, W12X79: 12.1, W12X87: 12.1, W12X96: 12.2, W14X109: 14.6, W14X120: 14.7, W14X132: 14.7, W14X145: 15.5, W14X159: 15.6, W14X176: 15.7, W14X193: 15.7, W14X211: 15.8, W14X22: 5, W14X233: 15.9, W14X257: 16, W14X26: 5.03, W14X283: 16.1, W14X30: 6.73, W14X311: 16.2, W14X34: 6.75, W14X342: 16.4, W14X370: 16.5, W14X38: 6.77, W14X398: 16.6, W14X426: 16.7, W14X43: 8, W14X455: 16.8, W14X48: 8.03, W14X500: 17, W14X53: 8.06, W14X550: 17.2, W14X605: 17.4, W14X61: 10, W14X665: 17.7, W14X68: 10, W14X730: 17.9, W14X74: 10.1, W14X808: 18.6, W14X82: 10.1, W14X873: 18.8, W14X90: 14.5, W14X99: 14.6, W16X100: 10.4, W16X26: 5.5, W16X31: 5.53, W16X36: 6.99, W16X40: 7, W16X45: 7.04, W16X50: 7.07, W16X57: 7.12, W16X67: 10.2, W16X77: 10.3, W16X89: 10.4, W18X106: 11.2, W18X119: 11.3, W18X130: 11.2, W18X143: 11.2, W18X158: 11.3, W18X175: 11.4, W18X192: 11.5, W18X211: 11.6, W18X234: 11.7, W18X258: 11.8, W18X283: 11.9, W18X311: 12, W18X35: 6, W18X40: 6.02, W18X46: 6.06, W18X50: 7.5, W18X55: 7.53, W18X60: 7.56, W18X65: 7.59, W18X71: 7.64, W18X76: 11, W18X86: 11.1, W18X97: 11.1, W21X101: 12.3, W21X111: 12.3, W21X122: 12.4, W21X132: 12.4, W21X147: 12.5, W21X166: 12.4, W21X182: 12.5, W21X201: 12.6, W21X223: 12.7, W21X248: 12.8, W21X275: 12.9, W21X44: 6.5, W21X48: 8.14, W21X50: 6.53, W21X55: 8.22, W21X57: 6.56, W21X62: 8.24, W21X68: 8.27, W21X73: 8.3, W21X83: 8.36, W21X93: 8.42, W24X103: 9, W24X104: 12.8, W24X117: 12.8, W24X131: 12.9, W24X146: 12.9, W24X162: 13, W24X176: 12.9, W24X192: 13, W24X207: 13, W24X229: 13.1, W24X250: 13.2, W24X279: 13.3, W24X306: 13.4, W24X335: 13.5, W24X370: 13.7, W24X55: 7.01, W24X62: 7.04, W24X68: 8.97, W24X76: 8.99, W24X84: 9.02, W24X94: 9.07, W27X102: 10, W27X114: 10.1, W27X129: 10, W27X146: 14, W27X161: 14, W27X178: 14.1, W27X194: 14, W27X217: 14.1, W27X235: 14.2, W27X258: 14.3, W27X281: 14.4, W27X307: 14.4, W27X336: 14.6, W27X368: 14.7, W27X539: 15.3, W27X84: 10, W27X94: 10, W30X108: 10.5, W30X116: 10.5, W30X124: 10.5, W30X132: 10.5, W30X148: 10.5, W30X173: 15, W30X191: 15, W30X211: 15.1, W30X235: 15.1, W30X261: 15.2, W30X292: 15.3, W30X326: 15.4, W30X357: 15.5, W30X391: 15.6, W30X90: 10.4, W30X99: 10.5, W33X118: 11.5, W33X130: 11.5, W33X141: 11.5, W33X152: 11.6, W33X169: 11.5, W33X201: 15.7, W33X221: 15.8, W33X241: 15.9, W33X263: 15.8, W33X291: 15.9, W33X318: 16, W33X354: 16.1, W33X387: 16.2, W36X135: 12, W36X150: 12, W36X160: 12, W36X170: 12, W36X182: 12.1, W36X194: 12.1, W36X210: 12.2, W36X231: 16.5, W36X232: 12.1, W36X247: 16.5, W36X256: 12.2, W36X262: 16.6, W36X282: 16.6, W36X302: 16.7, W36X330: 16.6, W36X361: 16.7, W36X395: 16.8, W36X441: 17, W36X487: 17.1, W36X529: 17.2, W36X652: 17.6, W36X723: 17.8, W36X802: 18, W36X853: 18.2, W36X925: 18.6, W40X149: 11.8, W40X167: 11.8, W40X183: 11.8, W40X199: 15.8, W40X211: 11.8, W40X215: 15.8, W40X235: 11.9, W40X249: 15.8, W40X264: 11.9, W40X277: 15.8, W40X278: 12, W40X294: 12, W40X297: 15.8, W40X324: 15.9, W40X327: 12.1, W40X331: 12.2, W40X362: 16, W40X372: 16.1, W40X392: 12.4, W40X397: 16.1, W40X431: 16.2, W40X503: 16.4, W40X593: 16.7, W40X655: 16.9, W44X230: 15.8, W44X262: 15.8, W44X290: 15.8, W44X335: 15.9, W4X13: 4.06, W5X16: 5, W5X19: 5.03, W6X12: 4, W6X15: 5.99, W6X16: 4.03, W6X20: 6.02, W6X25: 6.08, "W6X8.5": 3.94, W6X9: 3.94, W8X10: 3.94, W8X13: 4, W8X15: 4.02, W8X18: 5.25, W8X21: 5.27, W8X24: 6.5, W8X28: 6.54, W8X31: 8, W8X35: 8.02, W8X40: 8.07, W8X48: 8.11, W8X58: 8.22, W8X67: 8.28, "WT10.5X100.5": 12.6, "WT10.5X111.5": 12.7, "WT10.5X124": 12.8, "WT10.5X137.5": 12.9, "WT10.5X22": 6.5, "WT10.5X24": 8.14, "WT10.5X25": 6.53, "WT10.5X27.5": 8.22, "WT10.5X28.5": 6.56, "WT10.5X31": 8.24, "WT10.5X34": 8.27, "WT10.5X36.5": 8.3, "WT10.5X41.5": 8.36, "WT10.5X46.5": 8.42, "WT10.5X50.5": 12.3, "WT10.5X55.5": 12.3, "WT10.5X61": 12.4, "WT10.5X66": 12.4, "WT10.5X73.5": 12.5, "WT10.5X83": 12.4, "WT10.5X91": 12.5, "WT12X103.5": 13, "WT12X114.5": 13.1, WT12X125: 13.2, "WT12X139.5": 13.3, WT12X153: 13.4, "WT12X167.5": 13.5, WT12X185: 13.7, "WT12X27.5": 7.01, WT12X31: 7.04, WT12X34: 8.97, WT12X38: 8.99, WT12X42: 9.02, WT12X47: 9.07, "WT12X51.5": 9, WT12X52: 12.8, "WT12X58.5": 12.8, "WT12X65.5": 12.9, WT12X73: 12.9, WT12X81: 13, WT12X88: 12.9, WT12X96: 13, "WT13.5X108.5": 14.1, "WT13.5X117.5": 14.2, "WT13.5X129": 14.3, "WT13.5X140.5": 14.4, "WT13.5X153.5": 14.4, "WT13.5X168": 14.6, "WT13.5X184": 14.7, "WT13.5X269.5": 15.3, "WT13.5X42": 10, "WT13.5X47": 10, "WT13.5X51": 10, "WT13.5X57": 10.1, "WT13.5X64.5": 10, "WT13.5X73": 14, "WT13.5X80.5": 14, "WT13.5X89": 14.1, "WT13.5X97": 14, "WT15X105.5": 15.1, "WT15X117.5": 15.1, "WT15X130.5": 15.2, WT15X146: 15.3, WT15X163: 15.4, "WT15X178.5": 15.5, "WT15X195.5": 15.6, WT15X45: 10.4, "WT15X49.5": 10.5, WT15X54: 10.5, WT15X58: 10.5, WT15X62: 10.5, WT15X66: 10.5, WT15X74: 10.5, "WT15X86.5": 15, "WT15X95.5": 15, "WT16.5X100.5": 15.7, "WT16.5X110.5": 15.8, "WT16.5X120.5": 15.9, "WT16.5X131.5": 15.8, "WT16.5X145.5": 15.9, "WT16.5X159": 16, "WT16.5X177": 16.1, "WT16.5X193.5": 16.2, "WT16.5X59": 11.5, "WT16.5X65": 11.5, "WT16.5X70.5": 11.5, "WT16.5X76": 11.6, "WT16.5X84.5": 11.5, WT18X105: 12.2, "WT18X115.5": 16.5, WT18X116: 12.1, "WT18X123.5": 16.5, WT18X128: 12.2, WT18X131: 16.6, WT18X141: 16.6, WT18X151: 16.7, WT18X165: 16.6, "WT18X180.5": 16.7, "WT18X197.5": 16.8, "WT18X220.5": 17, "WT18X243.5": 17.1, "WT18X264.5": 17.2, WT18X326: 17.6, "WT18X361.5": 17.8, WT18X401: 18, "WT18X426.5": 18.2, "WT18X462.5": 18.6, "WT18X67.5": 12, WT18X75: 12, WT18X80: 12, WT18X85: 12, WT18X91: 12.1, WT18X97: 12.1, "WT2.5X8": 5, "WT2.5X9.5": 5.03, "WT20X105.5": 11.8, "WT20X107.5": 15.8, "WT20X117.5": 11.9, "WT20X124.5": 15.8, WT20X132: 11.9, "WT20X138.5": 15.8, WT20X139: 12, WT20X147: 12, "WT20X148.5": 15.8, WT20X162: 15.9, "WT20X163.5": 12.1, "WT20X165.5": 12.2, WT20X181: 16, WT20X186: 16.1, WT20X196: 12.4, "WT20X198.5": 16.1, "WT20X215.5": 16.2, "WT20X251.5": 16.4, "WT20X296.5": 16.7, "WT20X327.5": 16.9, "WT20X74.5": 11.8, "WT20X83.5": 11.8, "WT20X91.5": 11.8, "WT20X99.5": 15.8, WT22X115: 15.8, WT22X131: 15.8, WT22X145: 15.8, "WT22X167.5": 15.9, "WT2X6.5": 4.06, WT3X10: 6.02, "WT3X12.5": 6.08, "WT3X4.25": 3.94, "WT3X4.5": 3.94, WT3X6: 4, "WT3X7.5": 5.99, WT3X8: 4.03, "WT4X10.5": 5.27, WT4X12: 6.5, WT4X14: 6.54, "WT4X15.5": 8, "WT4X17.5": 8.02, WT4X20: 8.07, WT4X24: 8.11, WT4X29: 8.22, "WT4X33.5": 8.28, WT4X5: 3.94, "WT4X6.5": 4, "WT4X7.5": 4.02, WT4X9: 5.25, WT5X11: 5.75, WT5X13: 5.77, WT5X15: 5.81, "WT5X16.5": 7.96, "WT5X19.5": 7.99, "WT5X22.5": 8.02, "WT5X24.5": 10, WT5X27: 10, WT5X30: 10.1, WT5X34: 10.1, "WT5X38.5": 10.2, WT5X44: 10.3, WT5X50: 10.3, WT5X56: 10.4, WT5X6: 3.96, "WT5X7.5": 4, "WT5X8.5": 4.01, "WT5X9.5": 4.02, WT6X105: 12.8, WT6X11: 4.03, WT6X115: 12.9, WT6X126: 13, WT6X13: 6.49, "WT6X139.5": 13.1, WT6X15: 6.52, "WT6X152.5": 13.2, WT6X168: 13.4, "WT6X17.5": 6.56, WT6X20: 8.01, "WT6X22.5": 8.05, WT6X25: 8.08, "WT6X26.5": 10, WT6X29: 10, "WT6X32.5": 12, WT6X36: 12, "WT6X39.5": 12.1, "WT6X43.5": 12.1, WT6X48: 12.2, WT6X53: 12.2, WT6X60: 12.3, WT6X68: 12.4, WT6X7: 3.97, WT6X76: 12.5, WT6X8: 3.99, WT6X85: 12.6, "WT6X9.5": 4.01, WT6X95: 12.7, "WT7X105.5": 15.8, WT7X11: 5, "WT7X116.5": 15.9, "WT7X128.5": 16, WT7X13: 5.03, "WT7X141.5": 16.1, WT7X15: 6.73, "WT7X155.5": 16.2, WT7X17: 6.75, WT7X171: 16.4, WT7X185: 16.5, WT7X19: 6.77, WT7X199: 16.6, "WT7X21.5": 8, WT7X213: 16.7, "WT7X227.5": 16.8, WT7X24: 8.03, WT7X250: 17, "WT7X26.5": 8.06, WT7X275: 17.2, "WT7X30.5": 10, "WT7X302.5": 17.4, "WT7X332.5": 17.7, WT7X34: 10, WT7X365: 17.9, WT7X37: 10.1, WT7X404: 18.6, WT7X41: 10.1, "WT7X436.5": 18.8, WT7X45: 14.5, "WT7X49.5": 14.6, "WT7X54.5": 14.6, WT7X60: 14.7, WT7X66: 14.7, "WT7X72.5": 15.5, "WT7X79.5": 15.6, WT7X88: 15.7, "WT7X96.5": 15.7, WT8X13: 5.5, "WT8X15.5": 5.53, WT8X18: 6.99, WT8X20: 7, "WT8X22.5": 7.04, WT8X25: 7.07, "WT8X28.5": 7.12, "WT8X33.5": 10.2, "WT8X38.5": 10.3, "WT8X44.5": 10.4, WT8X50: 10.4, "WT9X105.5": 11.6, WT9X117: 11.7, WT9X129: 11.8, "WT9X141.5": 11.9, "WT9X155.5": 12, "WT9X17.5": 6, WT9X20: 6.02, WT9X23: 6.06, WT9X25: 7.5, "WT9X27.5": 7.53, WT9X30: 7.56, "WT9X32.5": 7.59, "WT9X35.5": 7.64, WT9X38: 11, WT9X43: 11.1, "WT9X48.5": 11.1, WT9X53: 11.2, "WT9X59.5": 11.3, WT9X65: 11.2, "WT9X71.5": 11.2, WT9X79: 11.3, "WT9X87.5": 11.4, WT9X96: 11.5 };
|
|
53330
|
+
|
|
53328
53331
|
// index.ts
|
|
53329
53332
|
var import_node_crypto7 = require("node:crypto");
|
|
53330
53333
|
|
|
@@ -54002,6 +54005,9 @@ function expandBasePlate(joint, col) {
|
|
|
54002
54005
|
const rows = intMin1(p, "boltRows", 2);
|
|
54003
54006
|
const weldLeg = num(p, "weldLeg", 8);
|
|
54004
54007
|
const projBelow = num(p, "projBelow", 75);
|
|
54008
|
+
const embedment = Math.max(0, num(p, "embedment", 0));
|
|
54009
|
+
const grout = Math.max(0, num(p, "grout", 0));
|
|
54010
|
+
const rodBelow = embedment > 0 ? grout + embedment : projBelow;
|
|
54005
54011
|
const nutH = pos(p, "nutHeight", 0.8 * boltDia);
|
|
54006
54012
|
const nutAF = pos(p, "nutAcrossFlats", 1.6 * boltDia);
|
|
54007
54013
|
const wpSide = pos(p, "washerPlateSide", 2.5 * boltDia);
|
|
@@ -54016,14 +54022,16 @@ function expandBasePlate(joint, col) {
|
|
|
54016
54022
|
const vs = spread(rows, Math.max(0, depth / 2 - edge));
|
|
54017
54023
|
const holes = [];
|
|
54018
54024
|
const kit = [];
|
|
54025
|
+
const wpBot = plateTop + SEAT, wpTop = wpBot + wpThick, washerTop = wpTop + washerH, fnTop = washerTop + nutH;
|
|
54026
|
+
const rodTopZ = fnTop + 0.4 * boltDia, rodBotZ = plateBottom - rodBelow;
|
|
54027
|
+
const concreteTopZ = plateBottom - (embedment > 0 ? grout : 0);
|
|
54019
54028
|
let k = 0;
|
|
54020
54029
|
for (const v of vs) {
|
|
54021
54030
|
for (const u of us) {
|
|
54022
54031
|
const ax = bx + u, ay = by + v, jid = joint.id;
|
|
54023
54032
|
holes.push({ u, v, d: boltDia + clearance });
|
|
54024
|
-
const wpBot = plateTop + SEAT, wpTop = wpBot + wpThick, washerTop = wpTop + washerH, fnTop = washerTop + nutH;
|
|
54025
54033
|
kit.push(
|
|
54026
|
-
{ id: `${jid}:rod${k}`, group: "anchor", kind: "rod", from: [ax, ay,
|
|
54034
|
+
{ id: `${jid}:rod${k}`, group: "anchor", kind: "rod", from: [ax, ay, rodBotZ], to: [ax, ay, rodTopZ], d: boltDia, meta: { label: `Anchor rod \u2300${boltDia}` } },
|
|
54027
54035
|
{ id: `${jid}:wp${k}`, group: "washer-plate", kind: "plate", center: [ax, ay, wpBot + wpThick / 2], uDir: [1, 0, 0], vDir: [0, 1, 0], width: wpSide, depth: wpSide, thickness: wpThick, holes: [{ u: 0, v: 0, d: boltDia + 2 }], meta: { label: `Washer plate ${Math.round(wpSide)}\xD7${Math.round(wpSide)}\xD7${Math.round(wpThick)}` } },
|
|
54028
54036
|
{ id: `${jid}:wsh${k}`, group: "washer", kind: "washer", from: [ax, ay, wpTop], to: [ax, ay, washerTop], dOuter: washerOD, dInner: washerID, meta: { label: `Washer \u2300${Math.round(washerOD)}` } },
|
|
54029
54037
|
{ id: `${jid}:fn${k}`, group: "nut", kind: "nut", from: [ax, ay, washerTop], to: [ax, ay, fnTop], acrossFlats: nutAF, meta: { label: `Nut M${boltDia}` } }
|
|
@@ -54044,7 +54052,12 @@ function expandBasePlate(joint, col) {
|
|
|
54044
54052
|
depth,
|
|
54045
54053
|
thickness,
|
|
54046
54054
|
holes,
|
|
54047
|
-
meta: {
|
|
54055
|
+
meta: {
|
|
54056
|
+
label: `Base plate ${Math.round(width)}\xD7${Math.round(depth)}\xD7${Math.round(thickness)}`,
|
|
54057
|
+
// anchor depth for the view's PROJ/GROUT/EMBED overlay + the rod inspector. projection is above B.O.
|
|
54058
|
+
// plate; the grout pad sits B.O. plate → T.O. concrete; embedment is INTO the concrete (T.O. concrete → rod bottom).
|
|
54059
|
+
anchor: { rodTopZ, rodBotZ, plateBotZ: plateBottom, concreteTopZ, projMm: rodTopZ - plateBottom, groutMm: plateBottom - concreteTopZ, embedMm: concreteTopZ - rodBotZ, embedSet: embedment > 0 }
|
|
54060
|
+
}
|
|
54048
54061
|
};
|
|
54049
54062
|
const weld = {
|
|
54050
54063
|
id: `${joint.id}:weld`,
|
|
@@ -54059,6 +54072,33 @@ function expandBasePlate(joint, col) {
|
|
|
54059
54072
|
function webHalfThk(profile, w) {
|
|
54060
54073
|
return profileKind(profile) === "I" ? Math.min(w * 0.5, Math.max(w * 0.1, 5)) / 2 : 0;
|
|
54061
54074
|
}
|
|
54075
|
+
function distPointSeg(p, a, b) {
|
|
54076
|
+
const abx = b[0] - a[0], aby = b[1] - a[1], abz = b[2] - a[2];
|
|
54077
|
+
const apx = p[0] - a[0], apy = p[1] - a[1], apz = p[2] - a[2];
|
|
54078
|
+
const len2 = abx * abx + aby * aby + abz * abz;
|
|
54079
|
+
const t = len2 > 1e-9 ? Math.max(0, Math.min(1, (apx * abx + apy * aby + apz * abz) / len2)) : 0;
|
|
54080
|
+
return Math.hypot(apx - t * abx, apy - t * aby, apz - t * abz);
|
|
54081
|
+
}
|
|
54082
|
+
function unitDir(a, b) {
|
|
54083
|
+
const dx = b[0] - a[0], dy = b[1] - a[1], dz = b[2] - a[2];
|
|
54084
|
+
const L = Math.hypot(dx, dy, dz);
|
|
54085
|
+
return L > 1e-9 ? [dx / L, dy / L, dz / L] : null;
|
|
54086
|
+
}
|
|
54087
|
+
function inferSupport(beam, end, memberGeo) {
|
|
54088
|
+
const bd = unitDir(beam.from, beam.to);
|
|
54089
|
+
let best, bestD = Infinity;
|
|
54090
|
+
for (const m of memberGeo.values()) {
|
|
54091
|
+
if (m.id === beam.id || !finite3(m.from) || !finite3(m.to)) continue;
|
|
54092
|
+
const md = unitDir(m.from, m.to);
|
|
54093
|
+
if (bd && md && Math.abs(bd[0] * md[0] + bd[1] * md[1] + bd[2] * md[2]) > 0.966) continue;
|
|
54094
|
+
const d = distPointSeg(end, m.from, m.to);
|
|
54095
|
+
if (d < bestD) {
|
|
54096
|
+
bestD = d;
|
|
54097
|
+
best = m;
|
|
54098
|
+
}
|
|
54099
|
+
}
|
|
54100
|
+
return best && bestD <= Math.max(best.dims.w, best.dims.d) ? best : void 0;
|
|
54101
|
+
}
|
|
54062
54102
|
function flangeThk(profile, d) {
|
|
54063
54103
|
return profileKind(profile) === "I" ? Math.min(d * 0.5, Math.max(d * 0.1, 6)) : d / 2;
|
|
54064
54104
|
}
|
|
@@ -54135,6 +54175,7 @@ function expandShearPlate(joint, beam, support) {
|
|
|
54135
54175
|
const twHalf = webHalfThk(beam.profile, beam.dims.w);
|
|
54136
54176
|
const webSide = num(p, "webSide", 1) >= 0 ? 1 : -1;
|
|
54137
54177
|
const pOff = webSide * (twHalf + thickness / 2);
|
|
54178
|
+
const u0 = support ? webHalfThk(support.profile, support.dims.w) : 0;
|
|
54138
54179
|
const edgeV = Math.min(edge, height * 0.4);
|
|
54139
54180
|
const pV = rows > 1 ? Math.min(pitch, Math.max(0, height - 2 * edgeV) / (rows - 1)) : 0;
|
|
54140
54181
|
const vOffs = Array.from({ length: rows }, (_, i) => (i - (rows - 1) / 2) * pV);
|
|
@@ -54162,9 +54203,9 @@ function expandShearPlate(joint, beam, support) {
|
|
|
54162
54203
|
holes.push({ u: u - width / 2, v, d: boltDia + holeClr });
|
|
54163
54204
|
bolts.push(
|
|
54164
54205
|
// the shank = the AISC bolt LENGTH, from under the head (plate outer face) into the connection
|
|
54165
|
-
{ id: `${jid}:bolt${k}`, group: "bolt", kind: "rod", from: P(u, v, plateOuterN), to: P(u, v, plateOuterN - webSide * boltLen), d: boltDia, meta: { label: `Bolt ${callout}` } },
|
|
54166
|
-
{ id: `${jid}:head${k}`, group: "nut", kind: "nut", from: P(u, v, plateOuterN), to: P(u, v, headTipN), acrossFlats: nutAF, meta: { label: `Bolt head ${callout}` } },
|
|
54167
|
-
{ id: `${jid}:nut${k}`, group: "nut", kind: "nut", from: P(u, v, webFarN), to: P(u, v, nutTipN), acrossFlats: nutAF, meta: { label: `Bolt nut ${callout}` } }
|
|
54206
|
+
{ id: `${jid}:bolt${k}`, group: "bolt", kind: "rod", from: P(u0 + u, v, plateOuterN), to: P(u0 + u, v, plateOuterN - webSide * boltLen), d: boltDia, meta: { label: `Bolt ${callout}` } },
|
|
54207
|
+
{ id: `${jid}:head${k}`, group: "nut", kind: "nut", from: P(u0 + u, v, plateOuterN), to: P(u0 + u, v, headTipN), acrossFlats: nutAF, meta: { label: `Bolt head ${callout}` } },
|
|
54208
|
+
{ id: `${jid}:nut${k}`, group: "nut", kind: "nut", from: P(u0 + u, v, webFarN), to: P(u0 + u, v, nutTipN), acrossFlats: nutAF, meta: { label: `Bolt nut ${callout}` } }
|
|
54168
54209
|
);
|
|
54169
54210
|
k++;
|
|
54170
54211
|
}
|
|
@@ -54173,26 +54214,33 @@ function expandShearPlate(joint, beam, support) {
|
|
|
54173
54214
|
id: `${joint.id}:plate`,
|
|
54174
54215
|
group: "shear-plate",
|
|
54175
54216
|
kind: "plate",
|
|
54176
|
-
center: P(width / 2, 0, pOff),
|
|
54217
|
+
center: P(u0 + width / 2, 0, pOff),
|
|
54177
54218
|
uDir,
|
|
54178
54219
|
vDir,
|
|
54179
|
-
// welded edge at
|
|
54220
|
+
// welded edge at the support web FACE (u0); lapped on the webSide face; cantilevered +uDir toward the beam
|
|
54180
54221
|
width,
|
|
54181
54222
|
depth: height,
|
|
54182
54223
|
thickness,
|
|
54183
54224
|
holes,
|
|
54184
54225
|
meta: { label: `Shear plate ${Math.round(width)}\xD7${Math.round(height)}\xD7${Math.round(thickness)}` }
|
|
54185
54226
|
};
|
|
54186
|
-
const
|
|
54187
|
-
|
|
54227
|
+
const weldSide = p?.["weldSide"] === "near" || p?.["weldSide"] === "far" ? p["weldSide"] : "both";
|
|
54228
|
+
const nOuter = pOff + webSide * thickness / 2;
|
|
54229
|
+
const nInner = pOff - webSide * thickness / 2;
|
|
54230
|
+
const faceWeld = (suffix, n) => ({
|
|
54231
|
+
id: `${joint.id}:${suffix}`,
|
|
54188
54232
|
group: "weld",
|
|
54189
54233
|
kind: "weld",
|
|
54190
54234
|
closed: false,
|
|
54191
|
-
path: [P(
|
|
54235
|
+
path: [P(u0, -height / 2, n), P(u0, height / 2, n)],
|
|
54192
54236
|
leg: weldLeg,
|
|
54193
|
-
|
|
54194
|
-
|
|
54195
|
-
|
|
54237
|
+
// at the support web FACE
|
|
54238
|
+
meta: { label: `Fillet ${weldLeg} (${weldSide === "both" ? "both sides" : weldSide + " side"})` }
|
|
54239
|
+
});
|
|
54240
|
+
const welds = [];
|
|
54241
|
+
if (weldSide !== "far") welds.push(faceWeld("weld", nOuter));
|
|
54242
|
+
if (weldSide !== "near") welds.push(faceWeld("weld2", nInner));
|
|
54243
|
+
const parts = [plate, ...bolts, ...welds];
|
|
54196
54244
|
if (stiffener) {
|
|
54197
54245
|
if (support && support.role === "beam" && finite3(support.from) && finite3(support.to) && support.dims && support.dims.d > 0 && support.dims.w > 0) {
|
|
54198
54246
|
const supPosOff = typeof support.posOffset === "number" && isFinite(support.posOffset) ? support.posOffset : 0;
|
|
@@ -54207,8 +54255,8 @@ function expandShearPlate(joint, beam, support) {
|
|
|
54207
54255
|
parts.push({ id: `${joint.id}:stiffweld`, group: "weld", kind: "weld", closed: false, path: [[wfx, wfy, girderCentZ - stiffH / 2], [wfx, wfy, girderCentZ + stiffH / 2]], leg: weldLeg, meta: { label: `Fillet ${weldLeg}` } });
|
|
54208
54256
|
} else {
|
|
54209
54257
|
const oppOff = -webSide * (twHalf + thickness / 2);
|
|
54210
|
-
parts.push({ id: `${joint.id}:stiff`, group: "stiffener", kind: "plate", center: P(width / 2, 0, oppOff), uDir, vDir, width, depth: height, thickness, holes: [], meta: { label: `Stiffener ${Math.round(width)}\xD7${Math.round(height)}\xD7${Math.round(thickness)}` } });
|
|
54211
|
-
parts.push({ id: `${joint.id}:stiffweld`, group: "weld", kind: "weld", closed: false, path: [P(
|
|
54258
|
+
parts.push({ id: `${joint.id}:stiff`, group: "stiffener", kind: "plate", center: P(u0 + width / 2, 0, oppOff), uDir, vDir, width, depth: height, thickness, holes: [], meta: { label: `Stiffener ${Math.round(width)}\xD7${Math.round(height)}\xD7${Math.round(thickness)}` } });
|
|
54259
|
+
parts.push({ id: `${joint.id}:stiffweld`, group: "weld", kind: "weld", closed: false, path: [P(u0, -height / 2, oppOff), P(u0, height / 2, oppOff)], leg: weldLeg, meta: { label: `Fillet ${weldLeg}` } });
|
|
54212
54260
|
}
|
|
54213
54261
|
}
|
|
54214
54262
|
const cope = beamCopes(beam, support, joint.at ?? "end0", p);
|
|
@@ -54230,6 +54278,8 @@ function expandShearPlate(joint, beam, support) {
|
|
|
54230
54278
|
depth: dec.depth,
|
|
54231
54279
|
thickness: beam.dims.w + 2,
|
|
54232
54280
|
radius: copeRadius,
|
|
54281
|
+
reentrantV: side === "top" ? -1 : 1,
|
|
54282
|
+
// web side = toward the beam centroid (top cope below, bottom above)
|
|
54233
54283
|
meta: { label: `${side === "top" ? "Top" : "Bottom"} cope ${Math.round(dec.length)}\xD7${Math.round(dec.depth)}` }
|
|
54234
54284
|
});
|
|
54235
54285
|
};
|
|
@@ -54270,7 +54320,8 @@ function expandJoints(joints, memberGeo) {
|
|
|
54270
54320
|
skipped.push(j.id);
|
|
54271
54321
|
return;
|
|
54272
54322
|
}
|
|
54273
|
-
|
|
54323
|
+
let support = j.secondaries && j.secondaries[0] ? memberGeo.get(j.secondaries[0]) : void 0;
|
|
54324
|
+
if (!support) support = inferSupport(beam, j.at === "end1" ? beam.to : beam.from, memberGeo);
|
|
54274
54325
|
const parts = expandShearPlate(j, beam, support);
|
|
54275
54326
|
if (!parts.length) {
|
|
54276
54327
|
skipped.push(j.id);
|
|
@@ -54289,6 +54340,7 @@ function expandJoints(joints, memberGeo) {
|
|
|
54289
54340
|
}
|
|
54290
54341
|
|
|
54291
54342
|
// contract-to-scene.ts
|
|
54343
|
+
var BF_IN = aisc_shapes_default;
|
|
54292
54344
|
var FT_TO_MM = 304.8;
|
|
54293
54345
|
var IN_TO_MM = 25.4;
|
|
54294
54346
|
function depthPosition(explicit, dzMm, horizMm) {
|
|
@@ -54313,6 +54365,8 @@ function profileDims(profile) {
|
|
|
54313
54365
|
}
|
|
54314
54366
|
if (/^(W|S|M|HP|C|MC|WT|ST|MT)$/.test(prefix)) {
|
|
54315
54367
|
const d = nums[0] * IN_TO_MM;
|
|
54368
|
+
const bf = BF_IN[p.replace(/\s+/g, "")];
|
|
54369
|
+
if (bf !== void 0) return { d, w: bf * IN_TO_MM, approx: false };
|
|
54316
54370
|
const ratio = prefix === "C" || prefix === "MC" ? 0.28 : 0.42;
|
|
54317
54371
|
return { d, w: d * ratio, approx: true };
|
|
54318
54372
|
}
|
|
@@ -59140,6 +59194,14 @@ function rollupDetails(details) {
|
|
|
59140
59194
|
}
|
|
59141
59195
|
|
|
59142
59196
|
// contract-score.ts
|
|
59197
|
+
function isFilterMissing(contract) {
|
|
59198
|
+
const f = contract?.filter;
|
|
59199
|
+
if (!f || typeof f !== "object") return true;
|
|
59200
|
+
const sheets = f.sheets;
|
|
59201
|
+
if (Array.isArray(sheets) && sheets.length > 0) return false;
|
|
59202
|
+
if (f.page) return false;
|
|
59203
|
+
return true;
|
|
59204
|
+
}
|
|
59143
59205
|
var ORDER = { rfi: 0, low: 1, med: 2, high: 3, verified: 4 };
|
|
59144
59206
|
var factor = (factors, key, state2) => factors.find((f) => f.key === key && (state2 === void 0 || f.state === state2));
|
|
59145
59207
|
function reasonFor(m) {
|
|
@@ -59177,7 +59239,8 @@ function scoreContract2(contractInput) {
|
|
|
59177
59239
|
members: s.byMember.length,
|
|
59178
59240
|
rfiCount: s.overall.rfiCount,
|
|
59179
59241
|
counts,
|
|
59180
|
-
worklist
|
|
59242
|
+
worklist,
|
|
59243
|
+
filterMissing: isFilterMissing(contractInput)
|
|
59181
59244
|
};
|
|
59182
59245
|
}
|
|
59183
59246
|
|
|
@@ -63396,6 +63459,7 @@ async function startServer() {
|
|
|
63396
63459
|
const apps = await aware.list();
|
|
63397
63460
|
return { ok: true, apps: apps.map((a) => ({ ...a, provider: appProvider(a.id), baked: appBaked(a.id), ...appOrigin(a.id) })) };
|
|
63398
63461
|
});
|
|
63462
|
+
app.get("/api/aisc-shapes", async () => ({ ok: true, shapes: Object.keys(aisc_shapes_default) }));
|
|
63399
63463
|
app.get("/api/workflows/updates", async () => {
|
|
63400
63464
|
const apps = await aware.list();
|
|
63401
63465
|
const installed = [];
|
|
@@ -64445,7 +64509,9 @@ async function startServer() {
|
|
|
64445
64509
|
const REBAKE_INSTRUCTION = "Re-read the attached drawing and re-bake the extracted values into this app's config.yaml (the literals the deterministic run uses), then `aware app compile` the app and tell me to approve the new lock. Do not change node logic \u2014 only the baked config values.";
|
|
64446
64510
|
app.post(
|
|
64447
64511
|
"/api/rebake",
|
|
64448
|
-
|
|
64512
|
+
// A drawing SET is now multiple files (up to 8) — a single 25 MB cap would 413 a legit multi-sheet
|
|
64513
|
+
// rebake. 96 MB fits several large structural PDFs (base64-inflated); localhost, single-user, transient.
|
|
64514
|
+
{ bodyLimit: 96 * 1024 * 1024 },
|
|
64449
64515
|
async (req, reply) => {
|
|
64450
64516
|
const { appId, inputName, instruction, snapshots } = req.body ?? {};
|
|
64451
64517
|
const sourceName = typeof req.body?.sourceName === "string" ? req.body.sourceName : void 0;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: floless-app-rebake
|
|
3
3
|
description: This skill should be used when the user swaps the source drawing of a BAKED floless.app workflow and wants it re-read — the "Re-read & re-bake" (B3) loop for Visual Inputs. Triggers on a pending floless `rebake` request (queued from the app's "Re-read & re-bake ▸" button), or asks like "re-bake screenshot-to-saved-settings with this new schedule", "I swapped the drawing, re-read it", "re-extract and re-bake the config". It teaches the host AI to read the new image, re-extract its values, rewrite ONLY the app's config.yaml baked literals, recompile, and ask the user to approve — all at COMPOSE time (AWARE forbids reading a drawing during a run).
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.2.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Re-read & re-bake a baked Visual Input (B3)
|
|
@@ -31,10 +31,12 @@ up here.
|
|
|
31
31
|
|
|
32
32
|
1. **Find the request.** `GET http://localhost:<port>/api/requests` → find the entry with
|
|
33
33
|
`type: "rebake"`. It carries `appId`, the `inputName` (the baked input, e.g. `drawing`),
|
|
34
|
-
an `instruction`, and `snapshots: ["<abs path
|
|
35
|
-
ask in prose with
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
an `instruction`, and `snapshots: ["<abs path>", …]` — **one OR MORE** new files (the user can
|
|
35
|
+
attach a set). (The user may also just ask in prose with image(s) attached — same procedure, skip
|
|
36
|
+
the request lookup.)
|
|
37
|
+
2. **Read the new drawing(s).** Open **every** file in `snapshots[]` (real PNG/JPEG/WebP/PDF on
|
|
38
|
+
disk) — not just the first. Read them with your own vision as one combined set — this is the
|
|
39
|
+
compose-time extraction.
|
|
38
40
|
3. **Re-extract the same schema the app already bakes.** Open the app source +
|
|
39
41
|
`config.yaml` under `~/.aware/apps/<appId>/` (and the editable copy under `demos/<appId>/`
|
|
40
42
|
if present). Identify the baked literals (e.g. `config.schedule-rows`). Re-extract **the same
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: floless-app-steel-takeoff
|
|
3
3
|
description: Read a structural steel drawing into an editable steel.takeoff/v1 contract in floless.app — the "Steel Takeoff" reader. Triggers on a pending floless `rebake` request for the `steel-model` app (queued from its "Re-read & re-bake ▸" button), or asks like "read this framing plan into a takeoff", "turn this structural drawing into a takeoff", "bake my drawing into steel-model", "extract members from this PDF". Teaches the host AI to READ a steel drawing (PDF/image) with its own vision at COMPOSE time, build a steel.takeoff/v1 contract (members + profiles + elevations + weights from AISC lookup), write it to the contract store, and hand control to the editor — no model in the run path, no API key.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.3.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Steel Takeoff — read a drawing into a steel.takeoff/v1 contract
|
|
@@ -31,15 +31,29 @@ but do not attempt them here.
|
|
|
31
31
|
|
|
32
32
|
## The loop
|
|
33
33
|
|
|
34
|
-
### 1. Find the drawing
|
|
34
|
+
### 1. Find the drawing(s)
|
|
35
35
|
|
|
36
36
|
From a `rebake` request: `GET http://localhost:<port>/api/requests` → the entry with
|
|
37
|
-
`type:"rebake"` and `appId:"steel-model"` carries `snapshots: ["<abs path>"]`
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
`type:"rebake"` and `appId:"steel-model"` carries `snapshots: ["<abs path>", …]` and optionally
|
|
38
|
+
`sourceName` (the original filename of the first file — use it for the `source.name` provenance
|
|
39
|
+
field). **`snapshots` may hold MULTIPLE files** — the user can attach a whole set (e.g. separate
|
|
40
|
+
PDFs for S-201 / S-202 / S-301, or one multi-sheet PDF plus a detail sheet). **Read every file in
|
|
41
|
+
`snapshots[]`, not just the first** — treat them as one combined set: run the sheet index (step 2.1)
|
|
42
|
+
across all of them, and the per-sheet filter pass (1b) and member read span every file. If the user
|
|
43
|
+
just attaches or points at a drawing in prose, skip the lookup and proceed from that path (again,
|
|
44
|
+
handle all the files they gave you).
|
|
45
|
+
|
|
46
|
+
### 1b. Filter step — isolate the steel linework FIRST (the `filter` node) — MANDATORY, DON'T SKIP
|
|
47
|
+
|
|
48
|
+
> 🚦 **This is a hard gate, not an optional pre-stage. You MUST populate the `filter` block and hand
|
|
49
|
+
> the Filter node to the user to Save BEFORE you read any members (step 2).** The member read is
|
|
50
|
+
> *downstream of* the filter — skipping straight to members is the #1 failure of this skill: the run
|
|
51
|
+
> then renders the shipped example filter (`S-ANNO-BEAM-STEL` / `S-ANNO-GRID` / …) as if it were the
|
|
52
|
+
> user's drawing, and the members get bound by a length heuristic instead of the real steel linework.
|
|
53
|
+
> The `/score` endpoint reports `filterMissing: true` until you've done this — treat that as a blocking
|
|
54
|
+
> worklist item (see step 3). Whenever the drawing has vector linework (any PDF `page.get_drawings()`
|
|
55
|
+
> returns paths), this step is required; a pure raster/photo with no vector paths is the only exception,
|
|
56
|
+
> and even then say so explicitly.
|
|
43
57
|
|
|
44
58
|
Before the heavy read, do **one** deterministic PyMuPDF pass **per framing-plan sheet** that enumerates
|
|
45
59
|
*every* drawing element by **layer · thickness · colour** and bakes it into the contract's `filter`
|
|
@@ -54,6 +68,11 @@ sheets (Foundation, each Floor, Roof). **Show the user the list and get an expli
|
|
|
54
68
|
"I found these framing plans: S-201, S-202, S-301 — filter all of these?" — before the pass. (The full
|
|
55
69
|
elevation/detail classification is the broader workflow; here we only need the plan set.)
|
|
56
70
|
|
|
71
|
+
⚠️ **This ONE plan-scope confirm covers both the filter pass AND the member read — they run on the
|
|
72
|
+
same sheet set. Getting the OK does NOT mean "go read members"; the very next thing you do is the
|
|
73
|
+
filter pass below, then hand off the Filter node. Do not let the confirm gate double as permission to
|
|
74
|
+
skip the filter** (that collision is exactly how a real session read 28 members with no filter block).
|
|
75
|
+
|
|
57
76
|
Then, for **each** confirmed plan sheet, one pass in display space (`page.rotation_matrix` — never mix
|
|
58
77
|
native/display coords):
|
|
59
78
|
|
|
@@ -74,17 +93,23 @@ native/display coords):
|
|
|
74
93
|
5. Set `filter.mode: "layer"`, `filter.active: 0`. **PUT** the contract
|
|
75
94
|
(`PUT /api/contract/steel-model`). On a re-read, preserve any existing per-facet `on` flags.
|
|
76
95
|
|
|
77
|
-
Then
|
|
78
|
-
**Save**. The saved `on` flags + `mode` are
|
|
79
|
-
passing `mode` + the global `on` flags).
|
|
96
|
+
**Then STOP and hand off — checkpoint before the member read.** Tell the user to open the **Filter**
|
|
97
|
+
node, flip through the sheets, narrow to the steel, and **Save**. The saved `on` flags + `mode` are
|
|
98
|
+
what step 2 reads — **per sheet** (each sheet's elements passing `mode` + the global `on` flags). Only
|
|
99
|
+
after the filter is baked (confirm with `GET /api/contract/steel-model` → a `filter` with `sheets`, or
|
|
100
|
+
`POST …/score` → `filterMissing: false`) do you proceed to the member read. The member read binds to
|
|
101
|
+
the user's kept steel; running it against a filter-less contract is the gap this checkpoint exists to
|
|
102
|
+
close.
|
|
80
103
|
|
|
81
104
|
### 2. Read it with your own vision — the heavy pipeline
|
|
82
105
|
|
|
83
|
-
> **Candidate geometry comes from the filter (1b)
|
|
84
|
-
>
|
|
85
|
-
>
|
|
86
|
-
>
|
|
87
|
-
>
|
|
106
|
+
> **Candidate geometry comes from the filter (1b) — which you already baked.** By the time you reach
|
|
107
|
+
> here, step 1b has run and the user has Saved the Filter node, so the contract carries a `filter`: the
|
|
108
|
+
> member candidate set **for each sheet** is that sheet's **kept** line elements
|
|
109
|
+
> (`filter.sheets[i].elements` whose layer/thickness/colour pass the GLOBAL `mode` + `on` flags) — bind
|
|
110
|
+
> labels to *those*, not to a length-filtered scan of the whole sheet. The length heuristic below is a
|
|
111
|
+
> **degraded fallback** for the raster-only exception in 1b (no vector paths to enumerate); if you find
|
|
112
|
+
> yourself using it on a vector PDF, you skipped 1b — go back and do it.
|
|
88
113
|
|
|
89
114
|
Follow the methodology condensed here (full detail + every gotcha in
|
|
90
115
|
`docs/superpowers/specs/2026-06-17-steel-takeoff-agent-guidance.md`):
|
|
@@ -185,9 +210,17 @@ Content-Type: application/json
|
|
|
185
210
|
{ "contract": <your draft contract>, "target": <target_confidence, e.g. 70> }
|
|
186
211
|
```
|
|
187
212
|
|
|
188
|
-
Returns `{ score, target, meetsTarget, gap, rfiCount, counts, worklist }` — the SAME
|
|
189
|
-
editor's Confidence chip shows. `worklist` is the rfi/low/med members dragging the score,
|
|
190
|
-
(heaviest first within a band), each with a `reason`. (Omit `contract` to score the stored
|
|
213
|
+
Returns `{ score, target, meetsTarget, gap, rfiCount, counts, worklist, filterMissing }` — the SAME
|
|
214
|
+
aggregate the editor's Confidence chip shows. `worklist` is the rfi/low/med members dragging the score,
|
|
215
|
+
worst-first (heaviest first within a band), each with a `reason`. (Omit `contract` to score the stored
|
|
216
|
+
one.)
|
|
217
|
+
|
|
218
|
+
**`filterMissing` is the completeness gate — independent of the confidence score.** `true` means the
|
|
219
|
+
contract has no usable `filter` block: step 1b never ran, so the members were bound by the length
|
|
220
|
+
heuristic, not the user's steel. A filter-less contract can still hit `meetsTarget` (as one real
|
|
221
|
+
session did at 73%) — so **do not treat `meetsTarget` alone as "done."** If `filterMissing` is `true`,
|
|
222
|
+
go back to step 1b, bake the filter, and hand off the Filter node before continuing. Hand-off (step 6)
|
|
223
|
+
requires `filterMissing: false`.
|
|
191
224
|
|
|
192
225
|
**The loop** — iterate at COMPOSE time until the goal is met:
|
|
193
226
|
|
|
@@ -404,6 +437,10 @@ offending fields, and re-PUT. You can read back the current contract with
|
|
|
404
437
|
|
|
405
438
|
### 6. Hand off to the user
|
|
406
439
|
|
|
440
|
+
**Precondition:** `POST …/score` returns `filterMissing: false` (the Filter node was baked in 1b and the
|
|
441
|
+
member read bound to it). If it's still `true`, you are not done — return to step 1b. Do not present a
|
|
442
|
+
takeoff whose filter was skipped as finished.
|
|
443
|
+
|
|
407
444
|
Tell the user:
|
|
408
445
|
- What you read (e.g. "read 14 beams and 6 columns on sheets S-201/S-202; 3 MF members await
|
|
409
446
|
Frame-Elevation resolution; aggregate confidence 78% — above your 70% target").
|
package/dist/web/app.css
CHANGED
|
@@ -2854,6 +2854,21 @@ body {
|
|
|
2854
2854
|
.fm-file-replace, .fm-file-clear { font-size: 10px; padding: 3px 8px; background: var(--surface); border: 1px solid var(--border-strong); color: var(--text-muted); border-radius: 3px; cursor: pointer; font-family: var(--ui); }
|
|
2855
2855
|
.fm-file-replace:hover { color: var(--text); border-color: var(--accent-dim); }
|
|
2856
2856
|
.fm-file-clear:hover { color: var(--err); border-color: var(--err); }
|
|
2857
|
+
/* ===== Visual Input: MULTI image/PDF file field (e.g. a framing set) ===== */
|
|
2858
|
+
.fm-files { position: relative; }
|
|
2859
|
+
.fm-files .fm-file-input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
|
2860
|
+
.fm-files .fm-drop.at-cap { cursor: not-allowed; border-style: solid; border-color: var(--border-strong); background: var(--surface-2); color: var(--text-dim); outline: none; }
|
|
2861
|
+
.fm-files-list { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
|
|
2862
|
+
.fm-files-list[hidden] { display: none; }
|
|
2863
|
+
.fm-filechip { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; padding: 4px 6px 4px 8px; border: 1px solid var(--border-strong); border-radius: 4px; background: var(--surface-2); }
|
|
2864
|
+
.fm-filechip.is-reading { opacity: 0.6; }
|
|
2865
|
+
.fm-filechip-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); font-size: 11px; color: var(--text); }
|
|
2866
|
+
.fm-filechip-del { flex: none; width: 16px; height: 16px; padding: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 11px; line-height: 1; background: transparent; border: 1px solid var(--border-strong); border-radius: 2px; color: var(--text-muted); cursor: pointer; }
|
|
2867
|
+
.fm-filechip-del:hover, .fm-filechip-del:focus-visible { color: var(--err); border-color: var(--err); }
|
|
2868
|
+
/* Re-bake "currently loaded" context row (inert — not a field) */
|
|
2869
|
+
.rebake-current { display: flex; align-items: center; gap: 8px; padding: 7px 10px; margin-bottom: 12px; background: var(--surface-2); border-radius: 4px; }
|
|
2870
|
+
.rebake-current-label { flex: none; text-transform: uppercase; letter-spacing: 0.08em; font-size: 10px; color: var(--text-muted); }
|
|
2871
|
+
.rebake-current .fm-file-name { flex: 1; }
|
|
2857
2872
|
/* Visual Input chip on the canvas input node */
|
|
2858
2873
|
.agent-card .node-inputs .ni-pair-file { align-items: center; }
|
|
2859
2874
|
.agent-card .node-inputs .ni-file-glyph { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 15px; border: 1px solid var(--accent-dim); border-radius: 3px; background: var(--accent-soft); color: var(--accent); font: 700 8px/1 var(--mono); letter-spacing: 0.04em; text-transform: uppercase; flex-shrink: 0; }
|