brepjs-bim 0.24.2 → 0.24.3
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/README.md +6 -0
- package/dist/brepjs-bim.cjs +184 -32
- package/dist/brepjs-bim.js +185 -33
- package/dist/productBodyCleanup.d.ts +33 -0
- package/dist/productBodyTestHooks.d.ts +19 -0
- package/dist/serialize/exactWallQuantities.d.ts +4 -8
- package/dist/types/productBody.d.ts +17 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -192,3 +192,9 @@ npm run lint --workspace=brepjs-bim
|
|
|
192
192
|
npm run build --workspace=brepjs-bim
|
|
193
193
|
npm run test --workspace=brepjs-bim
|
|
194
194
|
```
|
|
195
|
+
|
|
196
|
+
### Body material measurement
|
|
197
|
+
|
|
198
|
+
Exact Wall quantities measure occupied material across all retained items. Overlapping solids count once. Bounds include every item. Measurement and temporary cleanup failures make these optional measurements unavailable and preserve the borrowed inputs. Cleanup never retries an uncertain native release.
|
|
199
|
+
|
|
200
|
+
The BIM package requires `brepjs >=19.0.5` for native cleanup-error reporting.
|
package/dist/brepjs-bim.cjs
CHANGED
|
@@ -7621,6 +7621,71 @@ function coveringToSolid(spec) {
|
|
|
7621
7621
|
}
|
|
7622
7622
|
}
|
|
7623
7623
|
//#endregion
|
|
7624
|
+
//#region src/productBodyCleanup.ts
|
|
7625
|
+
var COMPLETE_CLEANUP = Object.freeze({ kind: "COMPLETE" });
|
|
7626
|
+
function cleanupReport(diagnostics) {
|
|
7627
|
+
const [first, ...rest] = diagnostics;
|
|
7628
|
+
if (first === void 0) return COMPLETE_CLEANUP;
|
|
7629
|
+
const snapshot = Object.freeze([first, ...rest]);
|
|
7630
|
+
return Object.freeze({
|
|
7631
|
+
kind: "FAILED",
|
|
7632
|
+
diagnostics: snapshot
|
|
7633
|
+
});
|
|
7634
|
+
}
|
|
7635
|
+
/** Read known cleanup failures through Result, AggregateError and `using` error chains.
|
|
7636
|
+
* This reports prior attempts only; it never takes ownership or retries a release.
|
|
7637
|
+
*/
|
|
7638
|
+
function nestedCleanupDiagnostics(error, context) {
|
|
7639
|
+
const diagnostics = [];
|
|
7640
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7641
|
+
const visit = (value) => {
|
|
7642
|
+
if (typeof value !== "object" || value === null || seen.has(value)) return;
|
|
7643
|
+
seen.add(value);
|
|
7644
|
+
if (value instanceof brepjs.GeometryCleanupError) {
|
|
7645
|
+
diagnostics.push(Object.freeze({
|
|
7646
|
+
...context,
|
|
7647
|
+
resourceKind: value.resourceKind,
|
|
7648
|
+
cause: value.cause
|
|
7649
|
+
}));
|
|
7650
|
+
return;
|
|
7651
|
+
}
|
|
7652
|
+
if (value instanceof AggregateError) value.errors.forEach(visit);
|
|
7653
|
+
if ("cause" in value) visit(value.cause);
|
|
7654
|
+
if ("error" in value && "suppressed" in value) {
|
|
7655
|
+
visit(value.error);
|
|
7656
|
+
visit(value.suppressed);
|
|
7657
|
+
}
|
|
7658
|
+
};
|
|
7659
|
+
visit(error);
|
|
7660
|
+
return diagnostics;
|
|
7661
|
+
}
|
|
7662
|
+
/** Owner-only, attempt-all cleanup. Never retry a release with an uncertain outcome. */
|
|
7663
|
+
function cleanupOwnedResources(items, context) {
|
|
7664
|
+
const attempted = /* @__PURE__ */ new Set();
|
|
7665
|
+
const diagnostics = [];
|
|
7666
|
+
for (const { resource, itemIndex, resourceKind = "SHAPE" } of items) {
|
|
7667
|
+
if (attempted.has(resource)) continue;
|
|
7668
|
+
attempted.add(resource);
|
|
7669
|
+
try {
|
|
7670
|
+
resource[Symbol.dispose]();
|
|
7671
|
+
} catch (cause) {
|
|
7672
|
+
diagnostics.push(Object.freeze({
|
|
7673
|
+
...context,
|
|
7674
|
+
itemIndex,
|
|
7675
|
+
resourceKind,
|
|
7676
|
+
cause
|
|
7677
|
+
}));
|
|
7678
|
+
}
|
|
7679
|
+
}
|
|
7680
|
+
return cleanupReport(diagnostics);
|
|
7681
|
+
}
|
|
7682
|
+
//#endregion
|
|
7683
|
+
//#region src/productBodyTestHooks.ts
|
|
7684
|
+
var hooks = null;
|
|
7685
|
+
function productBodyTestHooks() {
|
|
7686
|
+
return hooks;
|
|
7687
|
+
}
|
|
7688
|
+
//#endregion
|
|
7624
7689
|
//#region src/types/productBody.ts
|
|
7625
7690
|
/** Returns borrowed Product-local solids. The model retains ownership. */
|
|
7626
7691
|
function bodySolids(body) {
|
|
@@ -7633,6 +7698,113 @@ function bodySolids(body) {
|
|
|
7633
7698
|
function disposeProductBody(body) {
|
|
7634
7699
|
for (const solid of bodySolids(body)) solid[Symbol.dispose]();
|
|
7635
7700
|
}
|
|
7701
|
+
function bodyError(operation, code, message, cause, itemIndex, cleanup = COMPLETE_CLEANUP) {
|
|
7702
|
+
return {
|
|
7703
|
+
kind: "BIM_GEOMETRY",
|
|
7704
|
+
operation,
|
|
7705
|
+
code,
|
|
7706
|
+
message,
|
|
7707
|
+
cause,
|
|
7708
|
+
cleanup,
|
|
7709
|
+
...itemIndex === void 0 ? {} : { itemIndex }
|
|
7710
|
+
};
|
|
7711
|
+
}
|
|
7712
|
+
/** @internal Exact resource object identity only, without native topology queries. */
|
|
7713
|
+
function wrappedResourceObject(solid) {
|
|
7714
|
+
if (typeof solid !== "object" || solid === null || !("wrapped" in solid)) return void 0;
|
|
7715
|
+
if ("disposed" in solid && solid.disposed) return void 0;
|
|
7716
|
+
const wrapped = solid.wrapped;
|
|
7717
|
+
return typeof wrapped === "object" && wrapped !== null ? wrapped : void 0;
|
|
7718
|
+
}
|
|
7719
|
+
/** Validate the public handle contract and native solid topology before narrowing opaque input. */
|
|
7720
|
+
function isLiveValidSolid(value) {
|
|
7721
|
+
return typeof value === "object" && value !== null && "disposed" in value && value.disposed === false && "wrapped" in value && value.wrapped !== null && value.wrapped !== void 0 && "delete" in value && typeof value.delete === "function" && "onDispose" in value && typeof value.onDispose === "function" && Symbol.dispose in value && typeof value[Symbol.dispose] === "function" && (0, brepjs.getKernel)().shapeType(value.wrapped) === "solid" && (0, brepjs.getKernel)().isValid(value.wrapped);
|
|
7722
|
+
}
|
|
7723
|
+
function validateItems(input, operation) {
|
|
7724
|
+
if (!Array.isArray(input) || input.length === 0) return (0, brepjs.err)(bodyError(operation, "BODY_EMPTY_ITEMS", "Expected a nonempty solids array"));
|
|
7725
|
+
const identities = /* @__PURE__ */ new Set();
|
|
7726
|
+
const resources = /* @__PURE__ */ new Set();
|
|
7727
|
+
const solids = [];
|
|
7728
|
+
for (let itemIndex = 0; itemIndex < input.length; itemIndex++) try {
|
|
7729
|
+
const injected = productBodyTestHooks()?.before?.({
|
|
7730
|
+
step: "validate",
|
|
7731
|
+
itemIndex
|
|
7732
|
+
});
|
|
7733
|
+
if (injected && !injected.ok) return (0, brepjs.err)(bodyError(operation, "BODY_VALIDATION_FAILED", "Item validation failed", injected.error, itemIndex));
|
|
7734
|
+
const item = input[itemIndex];
|
|
7735
|
+
if (!isLiveValidSolid(item)) return (0, brepjs.err)(bodyError(operation, "BODY_INVALID_ITEM", "Expected a live valid solid handle", void 0, itemIndex));
|
|
7736
|
+
const resource = wrappedResourceObject(item);
|
|
7737
|
+
if (identities.has(item) || resource !== void 0 && resources.has(resource)) return (0, brepjs.err)(bodyError(operation, "BODY_DUPLICATE_ITEM", "Item duplicates an earlier handle or its wrapped resource object", void 0, itemIndex));
|
|
7738
|
+
identities.add(item);
|
|
7739
|
+
if (resource !== void 0) resources.add(resource);
|
|
7740
|
+
solids.push(item);
|
|
7741
|
+
} catch (cause) {
|
|
7742
|
+
return (0, brepjs.err)(bodyError(operation, "BODY_VALIDATION_FAILED", "Item validation threw", cause, itemIndex));
|
|
7743
|
+
}
|
|
7744
|
+
const [first, ...rest] = solids;
|
|
7745
|
+
if (first === void 0) return (0, brepjs.err)(bodyError(operation, "BODY_EMPTY_ITEMS", "Expected a nonempty solids array"));
|
|
7746
|
+
return (0, brepjs.ok)(Object.freeze([first, ...rest]));
|
|
7747
|
+
}
|
|
7748
|
+
function diagnostics(report) {
|
|
7749
|
+
return report.kind === "FAILED" ? report.diagnostics : [];
|
|
7750
|
+
}
|
|
7751
|
+
function ownedOperation(operation, work) {
|
|
7752
|
+
const scope = {
|
|
7753
|
+
itemIndex: 0,
|
|
7754
|
+
temporaries: []
|
|
7755
|
+
};
|
|
7756
|
+
let result;
|
|
7757
|
+
try {
|
|
7758
|
+
result = work(scope);
|
|
7759
|
+
} catch (cause) {
|
|
7760
|
+
result = (0, brepjs.err)(bodyError(operation, "BODY_OPERATION_FAILED", "Native Body operation threw", cause, scope.itemIndex));
|
|
7761
|
+
}
|
|
7762
|
+
const retired = cleanupOwnedResources(scope.temporaries, { operation });
|
|
7763
|
+
if (result.ok && retired.kind === "COMPLETE") return result;
|
|
7764
|
+
const cleanup = cleanupReport([...result.ok ? [] : nestedCleanupDiagnostics(result.error, {
|
|
7765
|
+
operation,
|
|
7766
|
+
itemIndex: scope.itemIndex
|
|
7767
|
+
}), ...diagnostics(retired)]);
|
|
7768
|
+
if (result.ok) return (0, brepjs.err)(bodyError(operation, "BODY_CLEANUP_FAILED", "Could not retire operation temporaries", void 0, void 0, cleanup));
|
|
7769
|
+
return (0, brepjs.err)(bodyError(operation, result.error.code, result.error.message, result.error, scope.itemIndex, cleanup));
|
|
7770
|
+
}
|
|
7771
|
+
function before(step, itemIndex) {
|
|
7772
|
+
return productBodyTestHooks()?.before?.({
|
|
7773
|
+
step,
|
|
7774
|
+
itemIndex
|
|
7775
|
+
}) ?? (0, brepjs.ok)(void 0);
|
|
7776
|
+
}
|
|
7777
|
+
/** Occupied material in mm³. Borrowed imported items need no authored Body descriptor. */
|
|
7778
|
+
function measureProductBodyMaterial(solids) {
|
|
7779
|
+
const checked = validateItems(solids, "measureProductBodyMaterial");
|
|
7780
|
+
if (!checked.ok) return checked;
|
|
7781
|
+
return ownedOperation("measureProductBodyMaterial", (scope) => {
|
|
7782
|
+
let material = checked.value[0];
|
|
7783
|
+
if (checked.value.length > 1) {
|
|
7784
|
+
const ready = before("union", 0);
|
|
7785
|
+
if (!ready.ok) return ready;
|
|
7786
|
+
const fused = (0, brepjs.fuseAll)([...checked.value], { trackEvolution: false });
|
|
7787
|
+
if (!fused.ok) return (0, brepjs.err)(fromBrepError(fused.error, "BODY_UNION_FAILED", "Could not union occupied material"));
|
|
7788
|
+
material = fused.value;
|
|
7789
|
+
scope.temporaries.push({
|
|
7790
|
+
resource: material,
|
|
7791
|
+
itemIndex: 0
|
|
7792
|
+
});
|
|
7793
|
+
const injected = productBodyTestHooks()?.afterAllocate?.({
|
|
7794
|
+
step: "union",
|
|
7795
|
+
itemIndex: 0,
|
|
7796
|
+
solid: material
|
|
7797
|
+
});
|
|
7798
|
+
if (injected && !injected.ok) return injected;
|
|
7799
|
+
}
|
|
7800
|
+
const ready = before("measure", 0);
|
|
7801
|
+
if (!ready.ok) return ready;
|
|
7802
|
+
const measured = (productBodyTestHooks()?.measure ?? brepjs.measureVolume)(material);
|
|
7803
|
+
if (!measured.ok) return (0, brepjs.err)(fromBrepError(measured.error, "BODY_MEASUREMENT_FAILED", "Could not measure occupied material"));
|
|
7804
|
+
if (!Number.isFinite(measured.value) || measured.value <= 0) return (0, brepjs.err)(bodyError("measureProductBodyMaterial", "BODY_INVALID_VOLUME", "Expected a positive finite occupied volume in mm³", measured.value));
|
|
7805
|
+
return measured;
|
|
7806
|
+
});
|
|
7807
|
+
}
|
|
7636
7808
|
//#endregion
|
|
7637
7809
|
//#region src/model/bimModel.ts
|
|
7638
7810
|
function exactWallBodyImmutable() {
|
|
@@ -15018,41 +15190,21 @@ function preflightExactBody(input) {
|
|
|
15018
15190
|
//#endregion
|
|
15019
15191
|
//#region src/serialize/exactWallQuantities.ts
|
|
15020
15192
|
function deriveExactWallQuantities(input) {
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15193
|
+
try {
|
|
15194
|
+
const measured = measureProductBodyMaterial(input.solids);
|
|
15195
|
+
if (!measured.ok) return wallVolumeError(measured.error);
|
|
15196
|
+
return (0, brepjs.ok)({
|
|
15197
|
+
lengthM: toIfcLengthM(input.spec.length),
|
|
15198
|
+
widthM: toIfcLengthM(input.spec.thickness),
|
|
15199
|
+
heightM: toIfcLengthM(input.spec.height),
|
|
15200
|
+
netVolumeM3: measured.value / 1e9
|
|
15201
|
+
});
|
|
15027
15202
|
} catch (cause) {
|
|
15028
|
-
return
|
|
15203
|
+
return wallVolumeError(cause);
|
|
15029
15204
|
}
|
|
15030
|
-
else {
|
|
15031
|
-
const fuse = input.dependencies?.fuse ?? brepjs.fuseAll;
|
|
15032
|
-
let union = null;
|
|
15033
|
-
try {
|
|
15034
|
-
const fused = fuse([...input.solids]);
|
|
15035
|
-
if (!fused.ok) return exactVolumeError(fused.error);
|
|
15036
|
-
union = fused.value;
|
|
15037
|
-
const measured = measure(union);
|
|
15038
|
-
if (!measured.ok) return exactVolumeError(measured.error);
|
|
15039
|
-
volumeMm3 = measured.value;
|
|
15040
|
-
} catch (cause) {
|
|
15041
|
-
return exactVolumeError(cause);
|
|
15042
|
-
} finally {
|
|
15043
|
-
union?.[Symbol.dispose]();
|
|
15044
|
-
}
|
|
15045
|
-
}
|
|
15046
|
-
if (!Number.isFinite(volumeMm3) || volumeMm3 <= 0) return exactVolumeError(/* @__PURE__ */ new Error(`Measured exact wall volume was ${volumeMm3}`));
|
|
15047
|
-
return (0, brepjs.ok)({
|
|
15048
|
-
lengthM: toIfcLengthM(input.spec.length),
|
|
15049
|
-
widthM: toIfcLengthM(input.spec.thickness),
|
|
15050
|
-
heightM: toIfcLengthM(input.spec.height),
|
|
15051
|
-
netVolumeM3: volumeMm3 / 1e9
|
|
15052
|
-
});
|
|
15053
15205
|
}
|
|
15054
|
-
function
|
|
15055
|
-
return (0, brepjs.err)(ifcError("IFC_EXACT_WALL_QUANTITY_DERIVATION_FAILED", "Failed to derive a positive finite NetVolume for
|
|
15206
|
+
function wallVolumeError(cause) {
|
|
15207
|
+
return (0, brepjs.err)(ifcError("IFC_EXACT_WALL_QUANTITY_DERIVATION_FAILED", "Failed to derive a positive finite NetVolume for the retained Wall Body", cause));
|
|
15056
15208
|
}
|
|
15057
15209
|
//#endregion
|
|
15058
15210
|
//#region src/serialize/toIfc.ts
|
package/dist/brepjs-bim.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addHoles, applyMatrix, autoHeal, box, castShape, clone, convexHull, cut, err, extrude, fuse, fuseAll, getBounds, getKernel, getSolids, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, locate, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, translate, validSolid } from "brepjs";
|
|
1
|
+
import { GeometryCleanupError, addHoles, applyMatrix, autoHeal, box, castShape, clone, convexHull, cut, err, extrude, fuse, fuseAll, getBounds, getKernel, getSolids, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, locate, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, translate, validSolid } from "brepjs";
|
|
2
2
|
import * as WebIFC from "web-ifc";
|
|
3
3
|
import { Handle, IFCROOT, IfcAPI } from "web-ifc";
|
|
4
4
|
//#region src/identity/ifcGuid.ts
|
|
@@ -7598,6 +7598,71 @@ function coveringToSolid(spec) {
|
|
|
7598
7598
|
}
|
|
7599
7599
|
}
|
|
7600
7600
|
//#endregion
|
|
7601
|
+
//#region src/productBodyCleanup.ts
|
|
7602
|
+
var COMPLETE_CLEANUP = Object.freeze({ kind: "COMPLETE" });
|
|
7603
|
+
function cleanupReport(diagnostics) {
|
|
7604
|
+
const [first, ...rest] = diagnostics;
|
|
7605
|
+
if (first === void 0) return COMPLETE_CLEANUP;
|
|
7606
|
+
const snapshot = Object.freeze([first, ...rest]);
|
|
7607
|
+
return Object.freeze({
|
|
7608
|
+
kind: "FAILED",
|
|
7609
|
+
diagnostics: snapshot
|
|
7610
|
+
});
|
|
7611
|
+
}
|
|
7612
|
+
/** Read known cleanup failures through Result, AggregateError and `using` error chains.
|
|
7613
|
+
* This reports prior attempts only; it never takes ownership or retries a release.
|
|
7614
|
+
*/
|
|
7615
|
+
function nestedCleanupDiagnostics(error, context) {
|
|
7616
|
+
const diagnostics = [];
|
|
7617
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7618
|
+
const visit = (value) => {
|
|
7619
|
+
if (typeof value !== "object" || value === null || seen.has(value)) return;
|
|
7620
|
+
seen.add(value);
|
|
7621
|
+
if (value instanceof GeometryCleanupError) {
|
|
7622
|
+
diagnostics.push(Object.freeze({
|
|
7623
|
+
...context,
|
|
7624
|
+
resourceKind: value.resourceKind,
|
|
7625
|
+
cause: value.cause
|
|
7626
|
+
}));
|
|
7627
|
+
return;
|
|
7628
|
+
}
|
|
7629
|
+
if (value instanceof AggregateError) value.errors.forEach(visit);
|
|
7630
|
+
if ("cause" in value) visit(value.cause);
|
|
7631
|
+
if ("error" in value && "suppressed" in value) {
|
|
7632
|
+
visit(value.error);
|
|
7633
|
+
visit(value.suppressed);
|
|
7634
|
+
}
|
|
7635
|
+
};
|
|
7636
|
+
visit(error);
|
|
7637
|
+
return diagnostics;
|
|
7638
|
+
}
|
|
7639
|
+
/** Owner-only, attempt-all cleanup. Never retry a release with an uncertain outcome. */
|
|
7640
|
+
function cleanupOwnedResources(items, context) {
|
|
7641
|
+
const attempted = /* @__PURE__ */ new Set();
|
|
7642
|
+
const diagnostics = [];
|
|
7643
|
+
for (const { resource, itemIndex, resourceKind = "SHAPE" } of items) {
|
|
7644
|
+
if (attempted.has(resource)) continue;
|
|
7645
|
+
attempted.add(resource);
|
|
7646
|
+
try {
|
|
7647
|
+
resource[Symbol.dispose]();
|
|
7648
|
+
} catch (cause) {
|
|
7649
|
+
diagnostics.push(Object.freeze({
|
|
7650
|
+
...context,
|
|
7651
|
+
itemIndex,
|
|
7652
|
+
resourceKind,
|
|
7653
|
+
cause
|
|
7654
|
+
}));
|
|
7655
|
+
}
|
|
7656
|
+
}
|
|
7657
|
+
return cleanupReport(diagnostics);
|
|
7658
|
+
}
|
|
7659
|
+
//#endregion
|
|
7660
|
+
//#region src/productBodyTestHooks.ts
|
|
7661
|
+
var hooks = null;
|
|
7662
|
+
function productBodyTestHooks() {
|
|
7663
|
+
return hooks;
|
|
7664
|
+
}
|
|
7665
|
+
//#endregion
|
|
7601
7666
|
//#region src/types/productBody.ts
|
|
7602
7667
|
/** Returns borrowed Product-local solids. The model retains ownership. */
|
|
7603
7668
|
function bodySolids(body) {
|
|
@@ -7610,6 +7675,113 @@ function bodySolids(body) {
|
|
|
7610
7675
|
function disposeProductBody(body) {
|
|
7611
7676
|
for (const solid of bodySolids(body)) solid[Symbol.dispose]();
|
|
7612
7677
|
}
|
|
7678
|
+
function bodyError(operation, code, message, cause, itemIndex, cleanup = COMPLETE_CLEANUP) {
|
|
7679
|
+
return {
|
|
7680
|
+
kind: "BIM_GEOMETRY",
|
|
7681
|
+
operation,
|
|
7682
|
+
code,
|
|
7683
|
+
message,
|
|
7684
|
+
cause,
|
|
7685
|
+
cleanup,
|
|
7686
|
+
...itemIndex === void 0 ? {} : { itemIndex }
|
|
7687
|
+
};
|
|
7688
|
+
}
|
|
7689
|
+
/** @internal Exact resource object identity only, without native topology queries. */
|
|
7690
|
+
function wrappedResourceObject(solid) {
|
|
7691
|
+
if (typeof solid !== "object" || solid === null || !("wrapped" in solid)) return void 0;
|
|
7692
|
+
if ("disposed" in solid && solid.disposed) return void 0;
|
|
7693
|
+
const wrapped = solid.wrapped;
|
|
7694
|
+
return typeof wrapped === "object" && wrapped !== null ? wrapped : void 0;
|
|
7695
|
+
}
|
|
7696
|
+
/** Validate the public handle contract and native solid topology before narrowing opaque input. */
|
|
7697
|
+
function isLiveValidSolid(value) {
|
|
7698
|
+
return typeof value === "object" && value !== null && "disposed" in value && value.disposed === false && "wrapped" in value && value.wrapped !== null && value.wrapped !== void 0 && "delete" in value && typeof value.delete === "function" && "onDispose" in value && typeof value.onDispose === "function" && Symbol.dispose in value && typeof value[Symbol.dispose] === "function" && getKernel().shapeType(value.wrapped) === "solid" && getKernel().isValid(value.wrapped);
|
|
7699
|
+
}
|
|
7700
|
+
function validateItems(input, operation) {
|
|
7701
|
+
if (!Array.isArray(input) || input.length === 0) return err(bodyError(operation, "BODY_EMPTY_ITEMS", "Expected a nonempty solids array"));
|
|
7702
|
+
const identities = /* @__PURE__ */ new Set();
|
|
7703
|
+
const resources = /* @__PURE__ */ new Set();
|
|
7704
|
+
const solids = [];
|
|
7705
|
+
for (let itemIndex = 0; itemIndex < input.length; itemIndex++) try {
|
|
7706
|
+
const injected = productBodyTestHooks()?.before?.({
|
|
7707
|
+
step: "validate",
|
|
7708
|
+
itemIndex
|
|
7709
|
+
});
|
|
7710
|
+
if (injected && !injected.ok) return err(bodyError(operation, "BODY_VALIDATION_FAILED", "Item validation failed", injected.error, itemIndex));
|
|
7711
|
+
const item = input[itemIndex];
|
|
7712
|
+
if (!isLiveValidSolid(item)) return err(bodyError(operation, "BODY_INVALID_ITEM", "Expected a live valid solid handle", void 0, itemIndex));
|
|
7713
|
+
const resource = wrappedResourceObject(item);
|
|
7714
|
+
if (identities.has(item) || resource !== void 0 && resources.has(resource)) return err(bodyError(operation, "BODY_DUPLICATE_ITEM", "Item duplicates an earlier handle or its wrapped resource object", void 0, itemIndex));
|
|
7715
|
+
identities.add(item);
|
|
7716
|
+
if (resource !== void 0) resources.add(resource);
|
|
7717
|
+
solids.push(item);
|
|
7718
|
+
} catch (cause) {
|
|
7719
|
+
return err(bodyError(operation, "BODY_VALIDATION_FAILED", "Item validation threw", cause, itemIndex));
|
|
7720
|
+
}
|
|
7721
|
+
const [first, ...rest] = solids;
|
|
7722
|
+
if (first === void 0) return err(bodyError(operation, "BODY_EMPTY_ITEMS", "Expected a nonempty solids array"));
|
|
7723
|
+
return ok(Object.freeze([first, ...rest]));
|
|
7724
|
+
}
|
|
7725
|
+
function diagnostics(report) {
|
|
7726
|
+
return report.kind === "FAILED" ? report.diagnostics : [];
|
|
7727
|
+
}
|
|
7728
|
+
function ownedOperation(operation, work) {
|
|
7729
|
+
const scope = {
|
|
7730
|
+
itemIndex: 0,
|
|
7731
|
+
temporaries: []
|
|
7732
|
+
};
|
|
7733
|
+
let result;
|
|
7734
|
+
try {
|
|
7735
|
+
result = work(scope);
|
|
7736
|
+
} catch (cause) {
|
|
7737
|
+
result = err(bodyError(operation, "BODY_OPERATION_FAILED", "Native Body operation threw", cause, scope.itemIndex));
|
|
7738
|
+
}
|
|
7739
|
+
const retired = cleanupOwnedResources(scope.temporaries, { operation });
|
|
7740
|
+
if (result.ok && retired.kind === "COMPLETE") return result;
|
|
7741
|
+
const cleanup = cleanupReport([...result.ok ? [] : nestedCleanupDiagnostics(result.error, {
|
|
7742
|
+
operation,
|
|
7743
|
+
itemIndex: scope.itemIndex
|
|
7744
|
+
}), ...diagnostics(retired)]);
|
|
7745
|
+
if (result.ok) return err(bodyError(operation, "BODY_CLEANUP_FAILED", "Could not retire operation temporaries", void 0, void 0, cleanup));
|
|
7746
|
+
return err(bodyError(operation, result.error.code, result.error.message, result.error, scope.itemIndex, cleanup));
|
|
7747
|
+
}
|
|
7748
|
+
function before(step, itemIndex) {
|
|
7749
|
+
return productBodyTestHooks()?.before?.({
|
|
7750
|
+
step,
|
|
7751
|
+
itemIndex
|
|
7752
|
+
}) ?? ok(void 0);
|
|
7753
|
+
}
|
|
7754
|
+
/** Occupied material in mm³. Borrowed imported items need no authored Body descriptor. */
|
|
7755
|
+
function measureProductBodyMaterial(solids) {
|
|
7756
|
+
const checked = validateItems(solids, "measureProductBodyMaterial");
|
|
7757
|
+
if (!checked.ok) return checked;
|
|
7758
|
+
return ownedOperation("measureProductBodyMaterial", (scope) => {
|
|
7759
|
+
let material = checked.value[0];
|
|
7760
|
+
if (checked.value.length > 1) {
|
|
7761
|
+
const ready = before("union", 0);
|
|
7762
|
+
if (!ready.ok) return ready;
|
|
7763
|
+
const fused = fuseAll([...checked.value], { trackEvolution: false });
|
|
7764
|
+
if (!fused.ok) return err(fromBrepError(fused.error, "BODY_UNION_FAILED", "Could not union occupied material"));
|
|
7765
|
+
material = fused.value;
|
|
7766
|
+
scope.temporaries.push({
|
|
7767
|
+
resource: material,
|
|
7768
|
+
itemIndex: 0
|
|
7769
|
+
});
|
|
7770
|
+
const injected = productBodyTestHooks()?.afterAllocate?.({
|
|
7771
|
+
step: "union",
|
|
7772
|
+
itemIndex: 0,
|
|
7773
|
+
solid: material
|
|
7774
|
+
});
|
|
7775
|
+
if (injected && !injected.ok) return injected;
|
|
7776
|
+
}
|
|
7777
|
+
const ready = before("measure", 0);
|
|
7778
|
+
if (!ready.ok) return ready;
|
|
7779
|
+
const measured = (productBodyTestHooks()?.measure ?? measureVolume)(material);
|
|
7780
|
+
if (!measured.ok) return err(fromBrepError(measured.error, "BODY_MEASUREMENT_FAILED", "Could not measure occupied material"));
|
|
7781
|
+
if (!Number.isFinite(measured.value) || measured.value <= 0) return err(bodyError("measureProductBodyMaterial", "BODY_INVALID_VOLUME", "Expected a positive finite occupied volume in mm³", measured.value));
|
|
7782
|
+
return measured;
|
|
7783
|
+
});
|
|
7784
|
+
}
|
|
7613
7785
|
//#endregion
|
|
7614
7786
|
//#region src/model/bimModel.ts
|
|
7615
7787
|
function exactWallBodyImmutable() {
|
|
@@ -14995,41 +15167,21 @@ function preflightExactBody(input) {
|
|
|
14995
15167
|
//#endregion
|
|
14996
15168
|
//#region src/serialize/exactWallQuantities.ts
|
|
14997
15169
|
function deriveExactWallQuantities(input) {
|
|
14998
|
-
|
|
14999
|
-
|
|
15000
|
-
|
|
15001
|
-
|
|
15002
|
-
|
|
15003
|
-
|
|
15170
|
+
try {
|
|
15171
|
+
const measured = measureProductBodyMaterial(input.solids);
|
|
15172
|
+
if (!measured.ok) return wallVolumeError(measured.error);
|
|
15173
|
+
return ok({
|
|
15174
|
+
lengthM: toIfcLengthM(input.spec.length),
|
|
15175
|
+
widthM: toIfcLengthM(input.spec.thickness),
|
|
15176
|
+
heightM: toIfcLengthM(input.spec.height),
|
|
15177
|
+
netVolumeM3: measured.value / 1e9
|
|
15178
|
+
});
|
|
15004
15179
|
} catch (cause) {
|
|
15005
|
-
return
|
|
15180
|
+
return wallVolumeError(cause);
|
|
15006
15181
|
}
|
|
15007
|
-
else {
|
|
15008
|
-
const fuse = input.dependencies?.fuse ?? fuseAll;
|
|
15009
|
-
let union = null;
|
|
15010
|
-
try {
|
|
15011
|
-
const fused = fuse([...input.solids]);
|
|
15012
|
-
if (!fused.ok) return exactVolumeError(fused.error);
|
|
15013
|
-
union = fused.value;
|
|
15014
|
-
const measured = measure(union);
|
|
15015
|
-
if (!measured.ok) return exactVolumeError(measured.error);
|
|
15016
|
-
volumeMm3 = measured.value;
|
|
15017
|
-
} catch (cause) {
|
|
15018
|
-
return exactVolumeError(cause);
|
|
15019
|
-
} finally {
|
|
15020
|
-
union?.[Symbol.dispose]();
|
|
15021
|
-
}
|
|
15022
|
-
}
|
|
15023
|
-
if (!Number.isFinite(volumeMm3) || volumeMm3 <= 0) return exactVolumeError(/* @__PURE__ */ new Error(`Measured exact wall volume was ${volumeMm3}`));
|
|
15024
|
-
return ok({
|
|
15025
|
-
lengthM: toIfcLengthM(input.spec.length),
|
|
15026
|
-
widthM: toIfcLengthM(input.spec.thickness),
|
|
15027
|
-
heightM: toIfcLengthM(input.spec.height),
|
|
15028
|
-
netVolumeM3: volumeMm3 / 1e9
|
|
15029
|
-
});
|
|
15030
15182
|
}
|
|
15031
|
-
function
|
|
15032
|
-
return err(ifcError("IFC_EXACT_WALL_QUANTITY_DERIVATION_FAILED", "Failed to derive a positive finite NetVolume for
|
|
15183
|
+
function wallVolumeError(cause) {
|
|
15184
|
+
return err(ifcError("IFC_EXACT_WALL_QUANTITY_DERIVATION_FAILED", "Failed to derive a positive finite NetVolume for the retained Wall Body", cause));
|
|
15033
15185
|
}
|
|
15034
15186
|
//#endregion
|
|
15035
15187
|
//#region src/serialize/toIfc.ts
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { NonEmpty } from './types/productBody.js';
|
|
2
|
+
export interface GeometryCleanupDiagnostic {
|
|
3
|
+
readonly operation: string;
|
|
4
|
+
readonly itemIndex: number;
|
|
5
|
+
readonly resourceKind: 'SHAPE' | 'TRANSFORM';
|
|
6
|
+
readonly localId?: number;
|
|
7
|
+
readonly cause: unknown;
|
|
8
|
+
}
|
|
9
|
+
export type CleanupReport = {
|
|
10
|
+
readonly kind: 'COMPLETE';
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: 'FAILED';
|
|
13
|
+
readonly diagnostics: NonEmpty<GeometryCleanupDiagnostic>;
|
|
14
|
+
};
|
|
15
|
+
export interface OwnedBodyResource {
|
|
16
|
+
readonly resource: Disposable;
|
|
17
|
+
readonly itemIndex: number;
|
|
18
|
+
readonly resourceKind?: 'SHAPE' | 'TRANSFORM';
|
|
19
|
+
}
|
|
20
|
+
export declare const COMPLETE_CLEANUP: CleanupReport;
|
|
21
|
+
export declare function cleanupReport(diagnostics: readonly GeometryCleanupDiagnostic[]): CleanupReport;
|
|
22
|
+
/** Read known cleanup failures through Result, AggregateError and `using` error chains.
|
|
23
|
+
* This reports prior attempts only; it never takes ownership or retries a release.
|
|
24
|
+
*/
|
|
25
|
+
export declare function nestedCleanupDiagnostics(error: unknown, context: {
|
|
26
|
+
readonly operation: string;
|
|
27
|
+
readonly itemIndex: number;
|
|
28
|
+
}): readonly GeometryCleanupDiagnostic[];
|
|
29
|
+
/** Owner-only, attempt-all cleanup. Never retry a release with an uncertain outcome. */
|
|
30
|
+
export declare function cleanupOwnedResources(items: readonly OwnedBodyResource[], context: {
|
|
31
|
+
readonly operation: string;
|
|
32
|
+
readonly localId?: number;
|
|
33
|
+
}): CleanupReport;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BimError } from './errors/bimError.js';
|
|
2
|
+
import { Result, ValidSolid, measureVolume, getBounds } from 'brepjs';
|
|
3
|
+
export type BodyNativeStep = 'validate' | 'union' | 'measure' | 'bounds';
|
|
4
|
+
export interface BodyNativeEvent {
|
|
5
|
+
readonly step: BodyNativeStep;
|
|
6
|
+
readonly itemIndex: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ProductBodyTestHooks {
|
|
9
|
+
readonly before?: (event: BodyNativeEvent) => Result<void, BimError> | void;
|
|
10
|
+
/** Called only after the new handle has been registered for failure cleanup. */
|
|
11
|
+
readonly afterAllocate?: (event: BodyNativeEvent & {
|
|
12
|
+
readonly solid: ValidSolid;
|
|
13
|
+
}) => Result<void, BimError> | void;
|
|
14
|
+
readonly measure?: typeof measureVolume;
|
|
15
|
+
readonly bounds?: typeof getBounds;
|
|
16
|
+
}
|
|
17
|
+
/** Package-internal seam. Tests keep native geometry real and reset after each case. */
|
|
18
|
+
export declare function setProductBodyTestHooksForTesting(value: ProductBodyTestHooks | null): void;
|
|
19
|
+
export declare function productBodyTestHooks(): ProductBodyTestHooks | null;
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Result, ValidSolid } from 'brepjs';
|
|
2
2
|
import { BimError } from '../errors/bimError.js';
|
|
3
3
|
import { WallSpec } from '../specs/wallSpec.js';
|
|
4
4
|
import { NonEmpty } from '../types/productBody.js';
|
|
5
|
-
|
|
5
|
+
interface ExactWallQuantityValues {
|
|
6
6
|
readonly lengthM: number;
|
|
7
7
|
readonly widthM: number;
|
|
8
8
|
readonly heightM: number;
|
|
9
9
|
readonly netVolumeM3: number;
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
readonly fuse?: typeof fuseAll | undefined;
|
|
13
|
-
readonly measure?: typeof measureVolume | undefined;
|
|
14
|
-
}
|
|
15
|
-
export interface ExactWallQuantityInput {
|
|
11
|
+
interface ExactWallQuantityInput {
|
|
16
12
|
readonly spec: WallSpec;
|
|
17
13
|
readonly solids: NonEmpty<ValidSolid>;
|
|
18
|
-
readonly dependencies?: ExactWallQuantityDependencies | undefined;
|
|
19
14
|
}
|
|
20
15
|
export declare function deriveExactWallQuantities(input: ExactWallQuantityInput): Result<ExactWallQuantityValues, BimError>;
|
|
16
|
+
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { ValidSolid } from 'brepjs';
|
|
1
|
+
import { Bounds3D, Result, ValidSolid } from 'brepjs';
|
|
2
|
+
import { BimError } from '../errors/bimError.js';
|
|
3
|
+
import { CleanupReport } from '../productBodyCleanup.js';
|
|
2
4
|
export type NonEmpty<T> = readonly [T, ...T[]];
|
|
3
5
|
export type ProductBody = {
|
|
4
6
|
readonly kind: 'PARAMETRIC';
|
|
@@ -11,3 +13,17 @@ export type ProductBody = {
|
|
|
11
13
|
export declare function bodySolids(body: ProductBody): NonEmpty<ValidSolid>;
|
|
12
14
|
/** Model-owner cleanup. Borrowers must use {@link bodySolids} without disposing its items. */
|
|
13
15
|
export declare function disposeProductBody(body: ProductBody): void;
|
|
16
|
+
export type ProductBodyOperation = 'productBodyBounds' | 'measureProductBodyMaterial';
|
|
17
|
+
export interface ProductBodyError extends BimError {
|
|
18
|
+
readonly operation: ProductBodyOperation;
|
|
19
|
+
readonly itemIndex?: number;
|
|
20
|
+
readonly cleanup: CleanupReport;
|
|
21
|
+
}
|
|
22
|
+
/** @internal Exact resource object identity only, without native topology queries. */
|
|
23
|
+
export declare function wrappedResourceObject(solid: unknown): object | undefined;
|
|
24
|
+
/** Occupied material in mm³. Borrowed imported items need no authored Body descriptor. */
|
|
25
|
+
export declare function measureProductBodyMaterial(solids: NonEmpty<ValidSolid>): Result<number, ProductBodyError>;
|
|
26
|
+
/** Borrow all items and query their tight bounds in their existing coordinates. */
|
|
27
|
+
export declare function productBodyBounds(solids: NonEmpty<ValidSolid>): Result<{
|
|
28
|
+
readonly bounds: Readonly<Bounds3D>;
|
|
29
|
+
}, ProductBodyError>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brepjs-bim",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.3",
|
|
4
4
|
"description": "BIM layer for brepjs — IFC4-aligned parametric building elements",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bim",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"lint": "eslint src tests"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"brepjs": ">=
|
|
50
|
+
"brepjs": ">=19.0.5",
|
|
51
51
|
"web-ifc": ">=0.0.50"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|