brepjs-bim 0.24.2 → 0.24.4
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 +12 -0
- package/dist/brepjs-bim.cjs +240 -45
- package/dist/brepjs-bim.js +241 -46
- package/dist/ifc-writer/psetWriter.d.ts +2 -2
- package/dist/productBodyCleanup.d.ts +33 -0
- package/dist/productBodyTestHooks.d.ts +19 -0
- package/dist/serialize/wallQuantities.d.ts +16 -0
- package/dist/types/productBody.d.ts +17 -1
- package/package.json +2 -2
- package/dist/serialize/exactWallQuantities.d.ts +0 -20
package/README.md
CHANGED
|
@@ -192,3 +192,15 @@ 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.
|
|
201
|
+
|
|
202
|
+
### Wall quantity failures
|
|
203
|
+
|
|
204
|
+
Wall `NetVolume` measures retained occupied material for both `PARAMETRIC` and `EXACT` geometry. Recipe Walls retain supported area and weight quantities. Exact takeover has no recipe quantity eligibility.
|
|
205
|
+
|
|
206
|
+
If measurement or temporary cleanup fails, serialization omits optional Wall quantities. `toIfcValidated` reports an element-specific `WALL_QUANTITY_OMITTED` warning from that same serialization. `toIfc` still returns a bytes `Result`.
|
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() {
|
|
@@ -12920,7 +13092,7 @@ function pushWeightQuantity(w, qtyIds, volumeM3, densityKgM3) {
|
|
|
12920
13092
|
if (densityKgM3 === void 0) return;
|
|
12921
13093
|
qtyIds.push(writeWeightQuantity(w, "GrossWeight", volumeM3, densityKgM3));
|
|
12922
13094
|
}
|
|
12923
|
-
function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, openings, densityKgM3) {
|
|
13095
|
+
function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, openings, netVolumeM3, densityKgM3) {
|
|
12924
13096
|
const lengthM = toIfcLengthM(spec.length);
|
|
12925
13097
|
const widthM = toIfcLengthM(spec.thickness);
|
|
12926
13098
|
const heightM = toIfcLengthM(spec.height);
|
|
@@ -12936,7 +13108,6 @@ function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, opening
|
|
|
12936
13108
|
if (toIfcLengthM(op.offsetFromFloor) < FLOOR_TOUCH_EPSILON_M) sumFloorTouchingFootprintM2 += opWidthM * widthM;
|
|
12937
13109
|
}
|
|
12938
13110
|
const netSideAreaM2 = grossSideAreaM2 - sumOpeningAreaM2;
|
|
12939
|
-
const netVolumeM3 = grossVolumeM3 - sumOpeningAreaM2 * widthM;
|
|
12940
13111
|
const netFootprintM2 = grossFootprintM2 - sumFloorTouchingFootprintM2;
|
|
12941
13112
|
const qtyIds = [
|
|
12942
13113
|
writeQtyLength(w, "Length", lengthM),
|
|
@@ -12952,7 +13123,7 @@ function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, opening
|
|
|
12952
13123
|
pushWeightQuantity(w, qtyIds, netVolumeM3, densityKgM3);
|
|
12953
13124
|
writeRelDefinesByProperties(w, ownerHistoryId, wallExpressId, writeElementQuantity(w, ownerHistoryId, "Qto_WallBaseQuantities", qtyIds));
|
|
12954
13125
|
}
|
|
12955
|
-
function
|
|
13126
|
+
function writeWallBodyQuantities(w, ownerHistoryId, wallExpressId, values) {
|
|
12956
13127
|
writeRelDefinesByProperties(w, ownerHistoryId, wallExpressId, writeElementQuantity(w, ownerHistoryId, "Qto_WallBaseQuantities", [
|
|
12957
13128
|
writeQtyLength(w, "Length", values.lengthM),
|
|
12958
13129
|
writeQtyLength(w, "Width", values.widthM),
|
|
@@ -15016,43 +15187,54 @@ function preflightExactBody(input) {
|
|
|
15016
15187
|
return (0, brepjs.ok)([first.value, ...remainingPrepared]);
|
|
15017
15188
|
}
|
|
15018
15189
|
//#endregion
|
|
15019
|
-
//#region src/serialize/
|
|
15020
|
-
function
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15190
|
+
//#region src/serialize/wallQuantities.ts
|
|
15191
|
+
function deriveWallQuantities(input) {
|
|
15192
|
+
try {
|
|
15193
|
+
const items = [];
|
|
15194
|
+
for (const retained of input.solids) {
|
|
15195
|
+
if ((0, brepjs.getKernel)().shapeType(retained.wrapped) !== "compound") {
|
|
15196
|
+
items.push(retained);
|
|
15197
|
+
continue;
|
|
15198
|
+
}
|
|
15199
|
+
const cardinality = singleWrapperChild(retained);
|
|
15200
|
+
if (!cardinality.ok) return cardinality;
|
|
15201
|
+
const children = (0, brepjs.getSolids)(retained);
|
|
15202
|
+
const [child] = children;
|
|
15203
|
+
if (children.length !== 1 || child === void 0 || !(0, brepjs.isValidSolid)(child)) return wallVolumeError(/* @__PURE__ */ new Error("Expected one valid solid in a retained recipe wrapper"));
|
|
15204
|
+
items.push(child);
|
|
15205
|
+
}
|
|
15206
|
+
const [first, ...rest] = items;
|
|
15207
|
+
if (first === void 0) return wallVolumeError(/* @__PURE__ */ new Error("Expected retained Wall material"));
|
|
15208
|
+
const measured = measureProductBodyMaterial([first, ...rest]);
|
|
15209
|
+
if (!measured.ok) return wallVolumeError(measured.error);
|
|
15210
|
+
return (0, brepjs.ok)({
|
|
15211
|
+
lengthM: toIfcLengthM(input.spec.length),
|
|
15212
|
+
widthM: toIfcLengthM(input.spec.thickness),
|
|
15213
|
+
heightM: toIfcLengthM(input.spec.height),
|
|
15214
|
+
netVolumeM3: measured.value / 1e9
|
|
15215
|
+
});
|
|
15027
15216
|
} catch (cause) {
|
|
15028
|
-
return
|
|
15217
|
+
return wallVolumeError(cause);
|
|
15029
15218
|
}
|
|
15219
|
+
}
|
|
15220
|
+
/** Count before cached extraction: a failed later cast cannot strand earlier children. */
|
|
15221
|
+
function singleWrapperChild(retained) {
|
|
15222
|
+
const kernel = (0, brepjs.getKernel)();
|
|
15223
|
+
let count;
|
|
15224
|
+
if (kernel.subShapeCount !== void 0) count = kernel.subShapeCount(retained.wrapped, "solid");
|
|
15030
15225
|
else {
|
|
15031
|
-
const
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
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
|
-
}
|
|
15226
|
+
const rawChildren = kernel.iterShapes(retained.wrapped, "solid");
|
|
15227
|
+
const cleanup = cleanupOwnedResources(rawChildren.map((raw, itemIndex) => ({
|
|
15228
|
+
resource: { [Symbol.dispose]: () => kernel.dispose(raw) },
|
|
15229
|
+
itemIndex
|
|
15230
|
+
})), { operation: "wallQuantityWrapperCount" });
|
|
15231
|
+
if (cleanup.kind === "FAILED") return wallVolumeError({ cleanup });
|
|
15232
|
+
count = rawChildren.length;
|
|
15045
15233
|
}
|
|
15046
|
-
|
|
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
|
-
});
|
|
15234
|
+
return count === 1 ? (0, brepjs.ok)(void 0) : wallVolumeError(/* @__PURE__ */ new Error("Expected one valid solid in a retained recipe wrapper"));
|
|
15053
15235
|
}
|
|
15054
|
-
function
|
|
15055
|
-
return (0, brepjs.err)(ifcError("
|
|
15236
|
+
function wallVolumeError(cause) {
|
|
15237
|
+
return (0, brepjs.err)(ifcError("IFC_WALL_QUANTITY_DERIVATION_FAILED", "Failed to derive a positive finite NetVolume for the retained Wall Body", cause));
|
|
15056
15238
|
}
|
|
15057
15239
|
//#endregion
|
|
15058
15240
|
//#region src/serialize/toIfc.ts
|
|
@@ -15083,8 +15265,13 @@ function preflightProductBodies(elements) {
|
|
|
15083
15265
|
return (0, brepjs.ok)(preflighted);
|
|
15084
15266
|
}
|
|
15085
15267
|
async function toIfc(model, meta) {
|
|
15268
|
+
const serialized = await serializeIfc(model, meta);
|
|
15269
|
+
return serialized.ok ? (0, brepjs.ok)(serialized.value.bytes) : serialized;
|
|
15270
|
+
}
|
|
15271
|
+
async function serializeIfc(model, meta) {
|
|
15086
15272
|
try {
|
|
15087
15273
|
var _usingCtx$2 = _usingCtx();
|
|
15274
|
+
const issues = [];
|
|
15088
15275
|
const project = model.getProject();
|
|
15089
15276
|
if (!project) return (0, brepjs.err)(ifcError("NO_PROJECT", "BimModel has no project — call model.init() first"));
|
|
15090
15277
|
if ((model.getBridges().length > 0 || model.getBridgeParts().length > 0 || model.getEarthworksFills().length > 0) && (meta.ifcSchema ?? "IFC4") !== "IFC4X3") return (0, brepjs.err)(ifcError("IFC4X3_REQUIRED", "Bridge, Bridge Part, and Earthworks Fill entities require IFC4X3 serialization"));
|
|
@@ -15202,10 +15389,10 @@ async function toIfc(model, meta) {
|
|
|
15202
15389
|
const wall = preflighted.element;
|
|
15203
15390
|
const containingId = findContainerOf(wall.localId, relationships);
|
|
15204
15391
|
const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
|
|
15205
|
-
const
|
|
15392
|
+
const bodyQuantities = deriveWallQuantities({
|
|
15206
15393
|
spec: wall.spec,
|
|
15207
|
-
solids:
|
|
15208
|
-
})
|
|
15394
|
+
solids: bodySolids(wall.geometry)
|
|
15395
|
+
});
|
|
15209
15396
|
const { localPlacementId, productDefinitionShapeId } = preflighted.kind === "EXACT" ? writeExactBodyGeometry(w, wall.spec, preflighted.items, geomSubContextId, storeyPlacementId) : writeWallGeometry(w, wall.spec, geomSubContextId, storeyPlacementId);
|
|
15210
15397
|
const wallExpressId = writeWallEntity(w, wall.guid, `Wall ${i + 1}`, ownerHistoryId, localPlacementId, productDefinitionShapeId);
|
|
15211
15398
|
idMap.set(wall.localId, wallExpressId);
|
|
@@ -15213,9 +15400,12 @@ async function toIfc(model, meta) {
|
|
|
15213
15400
|
writeWallCommonPset(w, ownerHistoryId, wallExpressId, wall.spec);
|
|
15214
15401
|
writeManufacturerPset(w, ownerHistoryId, wallExpressId, wall.spec);
|
|
15215
15402
|
if (wall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, wallExpressId, wall.spec.customProperties);
|
|
15216
|
-
if (
|
|
15217
|
-
|
|
15218
|
-
|
|
15403
|
+
if (!bodyQuantities.ok) issues.push(issue("warning", "WALL_QUANTITY_OMITTED", "Wall quantities omitted because occupied material volume could not be measured", wall.localId, {
|
|
15404
|
+
guid: wall.guid,
|
|
15405
|
+
cause: bodyQuantities.error
|
|
15406
|
+
}));
|
|
15407
|
+
else if (wall.geometry.kind === "PARAMETRIC") writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, wall.spec, openingsByWall.get(wall.localId) ?? [], bodyQuantities.value.netVolumeM3, densityByElement.get(wall.localId));
|
|
15408
|
+
else writeWallBodyQuantities(w, ownerHistoryId, wallExpressId, bodyQuantities.value);
|
|
15219
15409
|
}
|
|
15220
15410
|
for (const [i, slab] of slabs.entries()) {
|
|
15221
15411
|
const containingId = findContainerOf(slab.localId, relationships);
|
|
@@ -15559,7 +15749,11 @@ async function toIfc(model, meta) {
|
|
|
15559
15749
|
for (const { ref, ids } of classByKey.values()) byClassification.set(ref, ids);
|
|
15560
15750
|
if (byClassification.size > 0) writeClassificationRefs(w, ownerHistoryId, byClassification);
|
|
15561
15751
|
writeTypeLayer(w, ownerHistoryId, model, idMap);
|
|
15562
|
-
|
|
15752
|
+
const saved = w.save();
|
|
15753
|
+
return saved.ok ? (0, brepjs.ok)({
|
|
15754
|
+
bytes: saved.value,
|
|
15755
|
+
issues
|
|
15756
|
+
}) : saved;
|
|
15563
15757
|
} catch (_) {
|
|
15564
15758
|
_usingCtx$2.e = _;
|
|
15565
15759
|
} finally {
|
|
@@ -15679,15 +15873,16 @@ function toOccurrences(elements, predefined) {
|
|
|
15679
15873
|
async function toIfcValidated(model, meta) {
|
|
15680
15874
|
const integrity = checkReferentialIntegrity(model);
|
|
15681
15875
|
if (hasErrors(integrity)) return (0, brepjs.err)(ifcError("INTEGRITY_FAILURE", "Model failed referential integrity check", integrity.issues.filter((i) => i.severity === "error")));
|
|
15682
|
-
const
|
|
15683
|
-
if (!
|
|
15684
|
-
const bytes =
|
|
15876
|
+
const serialized = await serializeIfc(model, meta);
|
|
15877
|
+
if (!serialized.ok) return serialized;
|
|
15878
|
+
const { bytes } = serialized.value;
|
|
15685
15879
|
const geometry = collectGeometryIssues(model);
|
|
15686
15880
|
const schema = await checkSchema(bytes);
|
|
15687
15881
|
const roundTrip = await checkRoundTrip(bytes);
|
|
15688
15882
|
const gherkin = await checkGherkinRules(bytes);
|
|
15689
15883
|
const report = { issues: [
|
|
15690
15884
|
...integrity.issues,
|
|
15885
|
+
...serialized.value.issues,
|
|
15691
15886
|
...geometry.issues,
|
|
15692
15887
|
...schema.issues,
|
|
15693
15888
|
...roundTrip.issues,
|
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() {
|
|
@@ -12897,7 +13069,7 @@ function pushWeightQuantity(w, qtyIds, volumeM3, densityKgM3) {
|
|
|
12897
13069
|
if (densityKgM3 === void 0) return;
|
|
12898
13070
|
qtyIds.push(writeWeightQuantity(w, "GrossWeight", volumeM3, densityKgM3));
|
|
12899
13071
|
}
|
|
12900
|
-
function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, openings, densityKgM3) {
|
|
13072
|
+
function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, openings, netVolumeM3, densityKgM3) {
|
|
12901
13073
|
const lengthM = toIfcLengthM(spec.length);
|
|
12902
13074
|
const widthM = toIfcLengthM(spec.thickness);
|
|
12903
13075
|
const heightM = toIfcLengthM(spec.height);
|
|
@@ -12913,7 +13085,6 @@ function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, opening
|
|
|
12913
13085
|
if (toIfcLengthM(op.offsetFromFloor) < FLOOR_TOUCH_EPSILON_M) sumFloorTouchingFootprintM2 += opWidthM * widthM;
|
|
12914
13086
|
}
|
|
12915
13087
|
const netSideAreaM2 = grossSideAreaM2 - sumOpeningAreaM2;
|
|
12916
|
-
const netVolumeM3 = grossVolumeM3 - sumOpeningAreaM2 * widthM;
|
|
12917
13088
|
const netFootprintM2 = grossFootprintM2 - sumFloorTouchingFootprintM2;
|
|
12918
13089
|
const qtyIds = [
|
|
12919
13090
|
writeQtyLength(w, "Length", lengthM),
|
|
@@ -12929,7 +13100,7 @@ function writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, spec, opening
|
|
|
12929
13100
|
pushWeightQuantity(w, qtyIds, netVolumeM3, densityKgM3);
|
|
12930
13101
|
writeRelDefinesByProperties(w, ownerHistoryId, wallExpressId, writeElementQuantity(w, ownerHistoryId, "Qto_WallBaseQuantities", qtyIds));
|
|
12931
13102
|
}
|
|
12932
|
-
function
|
|
13103
|
+
function writeWallBodyQuantities(w, ownerHistoryId, wallExpressId, values) {
|
|
12933
13104
|
writeRelDefinesByProperties(w, ownerHistoryId, wallExpressId, writeElementQuantity(w, ownerHistoryId, "Qto_WallBaseQuantities", [
|
|
12934
13105
|
writeQtyLength(w, "Length", values.lengthM),
|
|
12935
13106
|
writeQtyLength(w, "Width", values.widthM),
|
|
@@ -14993,43 +15164,54 @@ function preflightExactBody(input) {
|
|
|
14993
15164
|
return ok([first.value, ...remainingPrepared]);
|
|
14994
15165
|
}
|
|
14995
15166
|
//#endregion
|
|
14996
|
-
//#region src/serialize/
|
|
14997
|
-
function
|
|
14998
|
-
|
|
14999
|
-
|
|
15000
|
-
|
|
15001
|
-
|
|
15002
|
-
|
|
15003
|
-
|
|
15167
|
+
//#region src/serialize/wallQuantities.ts
|
|
15168
|
+
function deriveWallQuantities(input) {
|
|
15169
|
+
try {
|
|
15170
|
+
const items = [];
|
|
15171
|
+
for (const retained of input.solids) {
|
|
15172
|
+
if (getKernel().shapeType(retained.wrapped) !== "compound") {
|
|
15173
|
+
items.push(retained);
|
|
15174
|
+
continue;
|
|
15175
|
+
}
|
|
15176
|
+
const cardinality = singleWrapperChild(retained);
|
|
15177
|
+
if (!cardinality.ok) return cardinality;
|
|
15178
|
+
const children = getSolids(retained);
|
|
15179
|
+
const [child] = children;
|
|
15180
|
+
if (children.length !== 1 || child === void 0 || !isValidSolid(child)) return wallVolumeError(/* @__PURE__ */ new Error("Expected one valid solid in a retained recipe wrapper"));
|
|
15181
|
+
items.push(child);
|
|
15182
|
+
}
|
|
15183
|
+
const [first, ...rest] = items;
|
|
15184
|
+
if (first === void 0) return wallVolumeError(/* @__PURE__ */ new Error("Expected retained Wall material"));
|
|
15185
|
+
const measured = measureProductBodyMaterial([first, ...rest]);
|
|
15186
|
+
if (!measured.ok) return wallVolumeError(measured.error);
|
|
15187
|
+
return ok({
|
|
15188
|
+
lengthM: toIfcLengthM(input.spec.length),
|
|
15189
|
+
widthM: toIfcLengthM(input.spec.thickness),
|
|
15190
|
+
heightM: toIfcLengthM(input.spec.height),
|
|
15191
|
+
netVolumeM3: measured.value / 1e9
|
|
15192
|
+
});
|
|
15004
15193
|
} catch (cause) {
|
|
15005
|
-
return
|
|
15194
|
+
return wallVolumeError(cause);
|
|
15006
15195
|
}
|
|
15196
|
+
}
|
|
15197
|
+
/** Count before cached extraction: a failed later cast cannot strand earlier children. */
|
|
15198
|
+
function singleWrapperChild(retained) {
|
|
15199
|
+
const kernel = getKernel();
|
|
15200
|
+
let count;
|
|
15201
|
+
if (kernel.subShapeCount !== void 0) count = kernel.subShapeCount(retained.wrapped, "solid");
|
|
15007
15202
|
else {
|
|
15008
|
-
const
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
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
|
-
}
|
|
15203
|
+
const rawChildren = kernel.iterShapes(retained.wrapped, "solid");
|
|
15204
|
+
const cleanup = cleanupOwnedResources(rawChildren.map((raw, itemIndex) => ({
|
|
15205
|
+
resource: { [Symbol.dispose]: () => kernel.dispose(raw) },
|
|
15206
|
+
itemIndex
|
|
15207
|
+
})), { operation: "wallQuantityWrapperCount" });
|
|
15208
|
+
if (cleanup.kind === "FAILED") return wallVolumeError({ cleanup });
|
|
15209
|
+
count = rawChildren.length;
|
|
15022
15210
|
}
|
|
15023
|
-
|
|
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
|
-
});
|
|
15211
|
+
return count === 1 ? ok(void 0) : wallVolumeError(/* @__PURE__ */ new Error("Expected one valid solid in a retained recipe wrapper"));
|
|
15030
15212
|
}
|
|
15031
|
-
function
|
|
15032
|
-
return err(ifcError("
|
|
15213
|
+
function wallVolumeError(cause) {
|
|
15214
|
+
return err(ifcError("IFC_WALL_QUANTITY_DERIVATION_FAILED", "Failed to derive a positive finite NetVolume for the retained Wall Body", cause));
|
|
15033
15215
|
}
|
|
15034
15216
|
//#endregion
|
|
15035
15217
|
//#region src/serialize/toIfc.ts
|
|
@@ -15060,8 +15242,13 @@ function preflightProductBodies(elements) {
|
|
|
15060
15242
|
return ok(preflighted);
|
|
15061
15243
|
}
|
|
15062
15244
|
async function toIfc(model, meta) {
|
|
15245
|
+
const serialized = await serializeIfc(model, meta);
|
|
15246
|
+
return serialized.ok ? ok(serialized.value.bytes) : serialized;
|
|
15247
|
+
}
|
|
15248
|
+
async function serializeIfc(model, meta) {
|
|
15063
15249
|
try {
|
|
15064
15250
|
var _usingCtx$2 = _usingCtx();
|
|
15251
|
+
const issues = [];
|
|
15065
15252
|
const project = model.getProject();
|
|
15066
15253
|
if (!project) return err(ifcError("NO_PROJECT", "BimModel has no project — call model.init() first"));
|
|
15067
15254
|
if ((model.getBridges().length > 0 || model.getBridgeParts().length > 0 || model.getEarthworksFills().length > 0) && (meta.ifcSchema ?? "IFC4") !== "IFC4X3") return err(ifcError("IFC4X3_REQUIRED", "Bridge, Bridge Part, and Earthworks Fill entities require IFC4X3 serialization"));
|
|
@@ -15179,10 +15366,10 @@ async function toIfc(model, meta) {
|
|
|
15179
15366
|
const wall = preflighted.element;
|
|
15180
15367
|
const containingId = findContainerOf(wall.localId, relationships);
|
|
15181
15368
|
const storeyPlacementId = containingId !== null ? placementMap.get(containingId) ?? null : null;
|
|
15182
|
-
const
|
|
15369
|
+
const bodyQuantities = deriveWallQuantities({
|
|
15183
15370
|
spec: wall.spec,
|
|
15184
|
-
solids:
|
|
15185
|
-
})
|
|
15371
|
+
solids: bodySolids(wall.geometry)
|
|
15372
|
+
});
|
|
15186
15373
|
const { localPlacementId, productDefinitionShapeId } = preflighted.kind === "EXACT" ? writeExactBodyGeometry(w, wall.spec, preflighted.items, geomSubContextId, storeyPlacementId) : writeWallGeometry(w, wall.spec, geomSubContextId, storeyPlacementId);
|
|
15187
15374
|
const wallExpressId = writeWallEntity(w, wall.guid, `Wall ${i + 1}`, ownerHistoryId, localPlacementId, productDefinitionShapeId);
|
|
15188
15375
|
idMap.set(wall.localId, wallExpressId);
|
|
@@ -15190,9 +15377,12 @@ async function toIfc(model, meta) {
|
|
|
15190
15377
|
writeWallCommonPset(w, ownerHistoryId, wallExpressId, wall.spec);
|
|
15191
15378
|
writeManufacturerPset(w, ownerHistoryId, wallExpressId, wall.spec);
|
|
15192
15379
|
if (wall.spec.customProperties !== void 0) writeCustomPsets(w, ownerHistoryId, wallExpressId, wall.spec.customProperties);
|
|
15193
|
-
if (
|
|
15194
|
-
|
|
15195
|
-
|
|
15380
|
+
if (!bodyQuantities.ok) issues.push(issue("warning", "WALL_QUANTITY_OMITTED", "Wall quantities omitted because occupied material volume could not be measured", wall.localId, {
|
|
15381
|
+
guid: wall.guid,
|
|
15382
|
+
cause: bodyQuantities.error
|
|
15383
|
+
}));
|
|
15384
|
+
else if (wall.geometry.kind === "PARAMETRIC") writeWallBaseQuantities(w, ownerHistoryId, wallExpressId, wall.spec, openingsByWall.get(wall.localId) ?? [], bodyQuantities.value.netVolumeM3, densityByElement.get(wall.localId));
|
|
15385
|
+
else writeWallBodyQuantities(w, ownerHistoryId, wallExpressId, bodyQuantities.value);
|
|
15196
15386
|
}
|
|
15197
15387
|
for (const [i, slab] of slabs.entries()) {
|
|
15198
15388
|
const containingId = findContainerOf(slab.localId, relationships);
|
|
@@ -15536,7 +15726,11 @@ async function toIfc(model, meta) {
|
|
|
15536
15726
|
for (const { ref, ids } of classByKey.values()) byClassification.set(ref, ids);
|
|
15537
15727
|
if (byClassification.size > 0) writeClassificationRefs(w, ownerHistoryId, byClassification);
|
|
15538
15728
|
writeTypeLayer(w, ownerHistoryId, model, idMap);
|
|
15539
|
-
|
|
15729
|
+
const saved = w.save();
|
|
15730
|
+
return saved.ok ? ok({
|
|
15731
|
+
bytes: saved.value,
|
|
15732
|
+
issues
|
|
15733
|
+
}) : saved;
|
|
15540
15734
|
} catch (_) {
|
|
15541
15735
|
_usingCtx$2.e = _;
|
|
15542
15736
|
} finally {
|
|
@@ -15656,15 +15850,16 @@ function toOccurrences(elements, predefined) {
|
|
|
15656
15850
|
async function toIfcValidated(model, meta) {
|
|
15657
15851
|
const integrity = checkReferentialIntegrity(model);
|
|
15658
15852
|
if (hasErrors(integrity)) return err(ifcError("INTEGRITY_FAILURE", "Model failed referential integrity check", integrity.issues.filter((i) => i.severity === "error")));
|
|
15659
|
-
const
|
|
15660
|
-
if (!
|
|
15661
|
-
const bytes =
|
|
15853
|
+
const serialized = await serializeIfc(model, meta);
|
|
15854
|
+
if (!serialized.ok) return serialized;
|
|
15855
|
+
const { bytes } = serialized.value;
|
|
15662
15856
|
const geometry = collectGeometryIssues(model);
|
|
15663
15857
|
const schema = await checkSchema(bytes);
|
|
15664
15858
|
const roundTrip = await checkRoundTrip(bytes);
|
|
15665
15859
|
const gherkin = await checkGherkinRules(bytes);
|
|
15666
15860
|
const report = { issues: [
|
|
15667
15861
|
...integrity.issues,
|
|
15862
|
+
...serialized.value.issues,
|
|
15668
15863
|
...geometry.issues,
|
|
15669
15864
|
...schema.issues,
|
|
15670
15865
|
...roundTrip.issues,
|
|
@@ -44,8 +44,8 @@ export declare function writeCustomPsets(w: IfcWriter, ownerHistoryId: number, e
|
|
|
44
44
|
* so the caller can skip emitting a weight quantity.
|
|
45
45
|
*/
|
|
46
46
|
export declare function resolveDensityKgM3(materialName: string | undefined, explicitDensityKgM3: number | undefined): number | undefined;
|
|
47
|
-
export declare function writeWallBaseQuantities(w: IfcWriter, ownerHistoryId: number, wallExpressId: number, spec: WallSpec, openings: readonly WallOpeningSpec[], densityKgM3?: number): void;
|
|
48
|
-
export declare function
|
|
47
|
+
export declare function writeWallBaseQuantities(w: IfcWriter, ownerHistoryId: number, wallExpressId: number, spec: WallSpec, openings: readonly WallOpeningSpec[], netVolumeM3: number, densityKgM3?: number): void;
|
|
48
|
+
export declare function writeWallBodyQuantities(w: IfcWriter, ownerHistoryId: number, wallExpressId: number, values: {
|
|
49
49
|
readonly lengthM: number;
|
|
50
50
|
readonly widthM: number;
|
|
51
51
|
readonly heightM: number;
|
|
@@ -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;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Result, ValidSolid } from 'brepjs';
|
|
2
|
+
import { BimError } from '../errors/bimError.js';
|
|
3
|
+
import { WallSpec } from '../specs/wallSpec.js';
|
|
4
|
+
import { NonEmpty } from '../types/productBody.js';
|
|
5
|
+
interface WallQuantityValues {
|
|
6
|
+
readonly lengthM: number;
|
|
7
|
+
readonly widthM: number;
|
|
8
|
+
readonly heightM: number;
|
|
9
|
+
readonly netVolumeM3: number;
|
|
10
|
+
}
|
|
11
|
+
interface WallQuantityInput {
|
|
12
|
+
readonly spec: WallSpec;
|
|
13
|
+
readonly solids: NonEmpty<ValidSolid>;
|
|
14
|
+
}
|
|
15
|
+
export declare function deriveWallQuantities(input: WallQuantityInput): Result<WallQuantityValues, 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.4",
|
|
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": {
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { fuseAll, measureVolume, Result, ValidSolid } from 'brepjs';
|
|
2
|
-
import { BimError } from '../errors/bimError.js';
|
|
3
|
-
import { WallSpec } from '../specs/wallSpec.js';
|
|
4
|
-
import { NonEmpty } from '../types/productBody.js';
|
|
5
|
-
export interface ExactWallQuantityValues {
|
|
6
|
-
readonly lengthM: number;
|
|
7
|
-
readonly widthM: number;
|
|
8
|
-
readonly heightM: number;
|
|
9
|
-
readonly netVolumeM3: number;
|
|
10
|
-
}
|
|
11
|
-
export interface ExactWallQuantityDependencies {
|
|
12
|
-
readonly fuse?: typeof fuseAll | undefined;
|
|
13
|
-
readonly measure?: typeof measureVolume | undefined;
|
|
14
|
-
}
|
|
15
|
-
export interface ExactWallQuantityInput {
|
|
16
|
-
readonly spec: WallSpec;
|
|
17
|
-
readonly solids: NonEmpty<ValidSolid>;
|
|
18
|
-
readonly dependencies?: ExactWallQuantityDependencies | undefined;
|
|
19
|
-
}
|
|
20
|
-
export declare function deriveExactWallQuantities(input: ExactWallQuantityInput): Result<ExactWallQuantityValues, BimError>;
|