brepjs-bim 0.24.0 → 0.24.1
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 -3
- package/dist/brepjs-bim.cjs +223 -46
- package/dist/brepjs-bim.js +214 -37
- package/dist/familiesAdapter.d.ts +15 -3
- package/dist/familiesProductBody.d.ts +40 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,9 +95,16 @@ otherwise supplied. Existing non-semantic Families archetypes continue to use th
|
|
|
95
95
|
registry beneath Bridge Parts. Bridge, Bridge Part, and Earthworks Fill require IFC4X3; `fromIfc`
|
|
96
96
|
reconstructs their civil spatial hierarchy, direct containment, and typed Earthworks inventory.
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
Civil-semantic wall and railing routes require `bodyEvaluator` (or the
|
|
99
|
+
`proxyEvaluator` fallback) so the adapter can verify the authored Product Body. Missing the
|
|
100
|
+
evaluator is `FAMILIES_PRODUCT_BODY_EVALUATOR_REQUIRED` (element path and mapped category); the
|
|
101
|
+
adapter does not silently keep a parametric envelope. Conventional archetype walls and railings
|
|
102
|
+
stay specification-authoritative and do not need an evaluator. When the evaluator is present, the
|
|
103
|
+
adapter applies registered wall openings to the parametric candidate, then compares that candidate
|
|
104
|
+
with the evaluated source in Product-local coordinates. Coincident bodies retain editable
|
|
105
|
+
parametric IFC; compound, voided, or otherwise different bodies retain their typed Wall or Railing
|
|
106
|
+
classification and export every authoritative item as tessellation. The evaluator's source handles
|
|
107
|
+
remain borrowed. Other typed civil routes continue to use their semantic envelope dimensions.
|
|
101
108
|
|
|
102
109
|
This is deliberately not a claim of complete IFC infrastructure coverage or unchanged parity with
|
|
103
110
|
the full scratch prototype. Member and Sign remain outside the profile: without `proxyEvaluator`
|
|
@@ -172,6 +179,8 @@ warnings travel inside the payload rather than throwing.
|
|
|
172
179
|
Each `add*` call parses and validates its spec and stores a typed `BimElement` keyed by a `LocalId`.
|
|
173
180
|
Parametric physical elements build an analytical brepjs solid; civil spatial elements are body-less,
|
|
174
181
|
and arbitrary-body products such as Earthworks Fill take ownership of a validated authored solid.
|
|
182
|
+
Families-projected civil walls and railings retain evaluated authored solids when their Bodies do
|
|
183
|
+
not coincide with the post-opening parametric candidate.
|
|
175
184
|
The IFC writer walks the model, applies placement, and emits schema-correct IFC entities; the
|
|
176
185
|
importer is the inverse. No kernel/WASM changes are required.
|
|
177
186
|
|
package/dist/brepjs-bim.cjs
CHANGED
|
@@ -536,7 +536,7 @@ var propertyKeyTypes = /* @__PURE__*/ new Set([
|
|
|
536
536
|
function escapeRegex(str) {
|
|
537
537
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
538
538
|
}
|
|
539
|
-
function clone$
|
|
539
|
+
function clone$3(inst, def, params) {
|
|
540
540
|
const cl = new inst._zod.constr(def ?? inst._zod.def);
|
|
541
541
|
if (!def || params?.parent) cl._zod.parent = inst;
|
|
542
542
|
return cl;
|
|
@@ -572,7 +572,7 @@ function pick(schema, mask) {
|
|
|
572
572
|
const currDef = schema._zod.def;
|
|
573
573
|
const checks = currDef.checks;
|
|
574
574
|
if (checks && checks.length > 0) throw new Error(".pick() cannot be used on object schemas containing refinements");
|
|
575
|
-
return clone$
|
|
575
|
+
return clone$3(schema, mergeDefs(schema._zod.def, {
|
|
576
576
|
get shape() {
|
|
577
577
|
const newShape = {};
|
|
578
578
|
for (const key in mask) {
|
|
@@ -590,7 +590,7 @@ function omit(schema, mask) {
|
|
|
590
590
|
const currDef = schema._zod.def;
|
|
591
591
|
const checks = currDef.checks;
|
|
592
592
|
if (checks && checks.length > 0) throw new Error(".omit() cannot be used on object schemas containing refinements");
|
|
593
|
-
return clone$
|
|
593
|
+
return clone$3(schema, mergeDefs(schema._zod.def, {
|
|
594
594
|
get shape() {
|
|
595
595
|
const newShape = { ...schema._zod.def.shape };
|
|
596
596
|
for (const key in mask) {
|
|
@@ -611,7 +611,7 @@ function extend(schema, shape) {
|
|
|
611
611
|
const existingShape = schema._zod.def.shape;
|
|
612
612
|
for (const key in shape) if (Object.getOwnPropertyDescriptor(existingShape, key) !== void 0) throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.");
|
|
613
613
|
}
|
|
614
|
-
return clone$
|
|
614
|
+
return clone$3(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
615
615
|
const _shape = {
|
|
616
616
|
...schema._zod.def.shape,
|
|
617
617
|
...shape
|
|
@@ -622,7 +622,7 @@ function extend(schema, shape) {
|
|
|
622
622
|
}
|
|
623
623
|
function safeExtend(schema, shape) {
|
|
624
624
|
if (!isPlainObject(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
|
|
625
|
-
return clone$
|
|
625
|
+
return clone$3(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
626
626
|
const _shape = {
|
|
627
627
|
...schema._zod.def.shape,
|
|
628
628
|
...shape
|
|
@@ -633,7 +633,7 @@ function safeExtend(schema, shape) {
|
|
|
633
633
|
}
|
|
634
634
|
function merge(a, b) {
|
|
635
635
|
if (a._zod.def.checks?.length) throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.");
|
|
636
|
-
return clone$
|
|
636
|
+
return clone$3(a, mergeDefs(a._zod.def, {
|
|
637
637
|
get shape() {
|
|
638
638
|
const _shape = {
|
|
639
639
|
...a._zod.def.shape,
|
|
@@ -651,7 +651,7 @@ function merge(a, b) {
|
|
|
651
651
|
function partial(Class, schema, mask) {
|
|
652
652
|
const checks = schema._zod.def.checks;
|
|
653
653
|
if (checks && checks.length > 0) throw new Error(".partial() cannot be used on object schemas containing refinements");
|
|
654
|
-
return clone$
|
|
654
|
+
return clone$3(schema, mergeDefs(schema._zod.def, {
|
|
655
655
|
get shape() {
|
|
656
656
|
const oldShape = schema._zod.def.shape;
|
|
657
657
|
const shape = { ...oldShape };
|
|
@@ -674,7 +674,7 @@ function partial(Class, schema, mask) {
|
|
|
674
674
|
}));
|
|
675
675
|
}
|
|
676
676
|
function required(Class, schema, mask) {
|
|
677
|
-
return clone$
|
|
677
|
+
return clone$3(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
678
678
|
const oldShape = schema._zod.def.shape;
|
|
679
679
|
const shape = { ...oldShape };
|
|
680
680
|
if (mask) for (const key in mask) {
|
|
@@ -4043,7 +4043,7 @@ var ZodType = /*@__PURE__*/ $constructor("ZodType", (inst, def) => {
|
|
|
4043
4043
|
return this.check(...chks);
|
|
4044
4044
|
},
|
|
4045
4045
|
clone(def, params) {
|
|
4046
|
-
return clone$
|
|
4046
|
+
return clone$3(this, def, params);
|
|
4047
4047
|
},
|
|
4048
4048
|
brand() {
|
|
4049
4049
|
return this;
|
|
@@ -7561,19 +7561,19 @@ function rotationOp(matrix) {
|
|
|
7561
7561
|
}
|
|
7562
7562
|
//#endregion
|
|
7563
7563
|
//#region src/elementFns/placedGeometry.ts
|
|
7564
|
-
var testHooks$
|
|
7564
|
+
var testHooks$5 = null;
|
|
7565
7565
|
function place(solid, frame) {
|
|
7566
7566
|
let placed = null;
|
|
7567
7567
|
try {
|
|
7568
7568
|
placed = locateShapeInFrame(solid, frame);
|
|
7569
|
-
testHooks$
|
|
7569
|
+
testHooks$5?.afterPlaced?.(placed);
|
|
7570
7570
|
return (0, brepjs.ok)(placed);
|
|
7571
7571
|
} catch (cause) {
|
|
7572
7572
|
placed?.[Symbol.dispose]();
|
|
7573
7573
|
return (0, brepjs.err)(geometryError("PLACED_GEOMETRY_FAILED", "Element placement threw while transforming geometry", cause));
|
|
7574
7574
|
}
|
|
7575
7575
|
}
|
|
7576
|
-
function disposeAll(solids) {
|
|
7576
|
+
function disposeAll$1(solids) {
|
|
7577
7577
|
for (const s of solids) s[Symbol.dispose]();
|
|
7578
7578
|
}
|
|
7579
7579
|
function placeWithinParent(solid, localFrame, parentFrame) {
|
|
@@ -7615,7 +7615,7 @@ function placedSolids(el, options = {}) {
|
|
|
7615
7615
|
for (const solid of bodySolids(el.geometry)) {
|
|
7616
7616
|
const placed = placeWithinParent(solid, el.spec, parentFrame);
|
|
7617
7617
|
if (!placed.ok) {
|
|
7618
|
-
disposeAll(out);
|
|
7618
|
+
disposeAll$1(out);
|
|
7619
7619
|
return placed;
|
|
7620
7620
|
}
|
|
7621
7621
|
out.push(placed.value);
|
|
@@ -7640,12 +7640,12 @@ function placedSolids(el, options = {}) {
|
|
|
7640
7640
|
var _usingCtx3 = _usingCtx();
|
|
7641
7641
|
const built = stairFlightToSolid(flight);
|
|
7642
7642
|
if (!built.ok) {
|
|
7643
|
-
disposeAll(out);
|
|
7643
|
+
disposeAll$1(out);
|
|
7644
7644
|
return (0, brepjs.err)(built.error);
|
|
7645
7645
|
}
|
|
7646
7646
|
const placed = placeWithinParent(_usingCtx3.u(built.value.solid), flight, parentFrame);
|
|
7647
7647
|
if (!placed.ok) {
|
|
7648
|
-
disposeAll(out);
|
|
7648
|
+
disposeAll$1(out);
|
|
7649
7649
|
return placed;
|
|
7650
7650
|
}
|
|
7651
7651
|
out.push(placed.value);
|
|
@@ -7662,12 +7662,12 @@ function placedSolids(el, options = {}) {
|
|
|
7662
7662
|
var _usingCtx4 = _usingCtx();
|
|
7663
7663
|
const built = rampFlightToSolid(flight);
|
|
7664
7664
|
if (!built.ok) {
|
|
7665
|
-
disposeAll(out);
|
|
7665
|
+
disposeAll$1(out);
|
|
7666
7666
|
return (0, brepjs.err)(built.error);
|
|
7667
7667
|
}
|
|
7668
7668
|
const placed = placeWithinParent(_usingCtx4.u(built.value.solid), flight, parentFrame);
|
|
7669
7669
|
if (!placed.ok) {
|
|
7670
|
-
disposeAll(out);
|
|
7670
|
+
disposeAll$1(out);
|
|
7671
7671
|
return placed;
|
|
7672
7672
|
}
|
|
7673
7673
|
out.push(placed.value);
|
|
@@ -7718,12 +7718,12 @@ function placedSolids(el, options = {}) {
|
|
|
7718
7718
|
]
|
|
7719
7719
|
});
|
|
7720
7720
|
if (!componentLocal.ok) {
|
|
7721
|
-
disposeAll(out);
|
|
7721
|
+
disposeAll$1(out);
|
|
7722
7722
|
return componentLocal;
|
|
7723
7723
|
}
|
|
7724
7724
|
const placed = placeWithinParent(_usingCtx5.u(componentLocal.value), el.spec, parentFrame);
|
|
7725
7725
|
if (!placed.ok) {
|
|
7726
|
-
disposeAll(out);
|
|
7726
|
+
disposeAll$1(out);
|
|
7727
7727
|
return placed;
|
|
7728
7728
|
}
|
|
7729
7729
|
out.push(placed.value);
|
|
@@ -7828,7 +7828,7 @@ async function initIfcApi(api) {
|
|
|
7828
7828
|
}
|
|
7829
7829
|
//#endregion
|
|
7830
7830
|
//#region src/ifc-writer/ifcWriter.ts
|
|
7831
|
-
var testHooks$
|
|
7831
|
+
var testHooks$4 = null;
|
|
7832
7832
|
/** Default MVD ViewDefinition declared in the STEP FILE_DESCRIPTION header. */
|
|
7833
7833
|
var DEFAULT_MVD_VIEW_DEFINITION = "ReferenceView_v1.2";
|
|
7834
7834
|
var VIEW_DEFINITION_RE = /ViewDefinition \[[^\]]*\]/;
|
|
@@ -7890,7 +7890,7 @@ var IfcWriter = class IfcWriter {
|
|
|
7890
7890
|
}
|
|
7891
7891
|
writeLine(entity) {
|
|
7892
7892
|
this.#api.WriteLine(this.#modelId, entity);
|
|
7893
|
-
testHooks$
|
|
7893
|
+
testHooks$4?.afterWriteLine?.();
|
|
7894
7894
|
return entity.expressID;
|
|
7895
7895
|
}
|
|
7896
7896
|
ref(id) {
|
|
@@ -7903,7 +7903,7 @@ var IfcWriter = class IfcWriter {
|
|
|
7903
7903
|
if (this.#closed) return;
|
|
7904
7904
|
this.#closed = true;
|
|
7905
7905
|
this.#api.CloseModel(this.#modelId);
|
|
7906
|
-
testHooks$
|
|
7906
|
+
testHooks$4?.afterClose?.();
|
|
7907
7907
|
}
|
|
7908
7908
|
[Symbol.dispose]() {
|
|
7909
7909
|
this.close();
|
|
@@ -14439,7 +14439,7 @@ function buildElementContainmentMap(reader) {
|
|
|
14439
14439
|
//#endregion
|
|
14440
14440
|
//#region src/import/geometryRead.ts
|
|
14441
14441
|
var NONE = { kind: "NONE" };
|
|
14442
|
-
var testHooks$
|
|
14442
|
+
var testHooks$3 = null;
|
|
14443
14443
|
/**
|
|
14444
14444
|
* Reconstructs the `Body` (SweptSolid) representation of a product into a brepjs
|
|
14445
14445
|
* solid, falling back to tessellated mesh import when no parametric path exists.
|
|
@@ -14683,7 +14683,7 @@ function reconstructTessellatedMesh(mesh, scaleToMm, itemExpressId, diagnostics)
|
|
|
14683
14683
|
}
|
|
14684
14684
|
if (solid !== null) {
|
|
14685
14685
|
try {
|
|
14686
|
-
testHooks$
|
|
14686
|
+
testHooks$3?.afterItemSolid?.(itemExpressId, solid);
|
|
14687
14687
|
} catch (cause) {
|
|
14688
14688
|
solid[Symbol.dispose]();
|
|
14689
14689
|
throw cause;
|
|
@@ -15644,7 +15644,7 @@ function readOwnerHistory(reader, ownerHistoryExpressId) {
|
|
|
15644
15644
|
}
|
|
15645
15645
|
//#endregion
|
|
15646
15646
|
//#region src/import/fromIfc.ts
|
|
15647
|
-
var testHooks = null;
|
|
15647
|
+
var testHooks$2 = null;
|
|
15648
15648
|
/**
|
|
15649
15649
|
* Each enumerated physical-element IFC type and the {@link ImportedElementCategory}
|
|
15650
15650
|
* it maps to. Curtain-wall sub-components (IfcPlate/IfcMember) and stair/ramp
|
|
@@ -15708,7 +15708,7 @@ async function fromIfc(bytes, options = {}) {
|
|
|
15708
15708
|
if (element === null) continue;
|
|
15709
15709
|
elements.push(element);
|
|
15710
15710
|
byExpressId.set(element.expressId, element);
|
|
15711
|
-
testHooks?.afterElement?.(element, elements.length);
|
|
15711
|
+
testHooks$2?.afterElement?.(element, elements.length);
|
|
15712
15712
|
}
|
|
15713
15713
|
const report = appendIssues(emptyReport(), diagnostics);
|
|
15714
15714
|
const applicationName = readApplicationName(reader);
|
|
@@ -15754,7 +15754,7 @@ function readElement(reader, expressId, category, scale, containment, typeEnums,
|
|
|
15754
15754
|
bounds: null,
|
|
15755
15755
|
volumeMm3: null
|
|
15756
15756
|
} : reconstructGeometry(reader, expressId, scale, voidedBy, diagnostics);
|
|
15757
|
-
testHooks?.afterGeometry?.(expressId, geometry);
|
|
15757
|
+
testHooks$2?.afterGeometry?.(expressId, geometry);
|
|
15758
15758
|
const psets = readPsets(reader, expressId).map(toImportedPset);
|
|
15759
15759
|
const material = readMaterial(reader, expressId, scale);
|
|
15760
15760
|
const classification = readClassification(reader, expressId);
|
|
@@ -28895,13 +28895,151 @@ function isPureTranslation(f, eps = 1e-9) {
|
|
|
28895
28895
|
return Math.abs((f[0] ?? 1) - 1) < eps && Math.abs((f[5] ?? 1) - 1) < eps && Math.abs((f[10] ?? 1) - 1) < eps && Math.abs(f[1] ?? 0) < eps && Math.abs(f[2] ?? 0) < eps && Math.abs(f[4] ?? 0) < eps && Math.abs(f[6] ?? 0) < eps && Math.abs(f[8] ?? 0) < eps && Math.abs(f[9] ?? 0) < eps;
|
|
28896
28896
|
}
|
|
28897
28897
|
//#endregion
|
|
28898
|
+
//#region src/familiesProductBody.ts
|
|
28899
|
+
var RELATIVE_VOLUME_TOLERANCE = 1e-6;
|
|
28900
|
+
var testHooks$1 = null;
|
|
28901
|
+
/**
|
|
28902
|
+
* Evaluates a civil Product Body, clones and localizes every borrowed source,
|
|
28903
|
+
* then proves whether the registered-opening parametric Body is coincident.
|
|
28904
|
+
* An EXACT result is caller-owned until takeExactProductBody() succeeds.
|
|
28905
|
+
*/
|
|
28906
|
+
function selectCivilProductBody(input) {
|
|
28907
|
+
const evaluated = evaluateBody(input);
|
|
28908
|
+
if (!evaluated.ok) return evaluated;
|
|
28909
|
+
const sources = evaluated.value;
|
|
28910
|
+
if (sources.length === 0) return (0, brepjs.err)(productBodyError(input, "FAMILIES_PRODUCT_BODY_EMPTY", `evaluated to no solid Body items`));
|
|
28911
|
+
const localized = [];
|
|
28912
|
+
const inverse = decomposeFrame(frameInverse(input.productWorldFrame));
|
|
28913
|
+
for (const [itemIndex, source] of sources.entries()) {
|
|
28914
|
+
const copied = (0, brepjs.clone)(source);
|
|
28915
|
+
if (!copied.ok) {
|
|
28916
|
+
disposeAll(localized);
|
|
28917
|
+
return (0, brepjs.err)(productBodyError(input, "FAMILIES_PRODUCT_BODY_COPY_FAILED", `Body item ${itemIndex} could not be copied`, copied.error, itemIndex));
|
|
28918
|
+
}
|
|
28919
|
+
try {
|
|
28920
|
+
testHooks$1?.afterCopy?.(itemIndex, copied.value, source);
|
|
28921
|
+
} catch (cause) {
|
|
28922
|
+
copied.value[Symbol.dispose]();
|
|
28923
|
+
disposeAll(localized);
|
|
28924
|
+
return (0, brepjs.err)(productBodyError(input, "FAMILIES_PRODUCT_BODY_COPY_FAILED", `Body item ${itemIndex} could not be copied`, cause, itemIndex));
|
|
28925
|
+
}
|
|
28926
|
+
let valid;
|
|
28927
|
+
try {
|
|
28928
|
+
valid = (0, brepjs.validSolid)(copied.value);
|
|
28929
|
+
} catch (cause) {
|
|
28930
|
+
copied.value[Symbol.dispose]();
|
|
28931
|
+
disposeAll(localized);
|
|
28932
|
+
return (0, brepjs.err)(productBodyError(input, "FAMILIES_PRODUCT_BODY_INVALID", `Body item ${itemIndex} could not be validated as a solid`, cause, itemIndex));
|
|
28933
|
+
}
|
|
28934
|
+
if (!valid.ok) {
|
|
28935
|
+
copied.value[Symbol.dispose]();
|
|
28936
|
+
disposeAll(localized);
|
|
28937
|
+
return (0, brepjs.err)(productBodyError(input, "FAMILIES_PRODUCT_BODY_INVALID", `Body item ${itemIndex} is not a valid solid: ${valid.error}`, void 0, itemIndex));
|
|
28938
|
+
}
|
|
28939
|
+
let local = null;
|
|
28940
|
+
try {
|
|
28941
|
+
testHooks$1?.beforeLocalize?.(itemIndex, valid.value);
|
|
28942
|
+
local = locateShapeInFrame(valid.value, inverse);
|
|
28943
|
+
testHooks$1?.afterLocalized?.(itemIndex, local);
|
|
28944
|
+
localized.push(local);
|
|
28945
|
+
} catch (cause) {
|
|
28946
|
+
local?.[Symbol.dispose]();
|
|
28947
|
+
valid.value[Symbol.dispose]();
|
|
28948
|
+
disposeAll(localized);
|
|
28949
|
+
return (0, brepjs.err)(productBodyError(input, "FAMILIES_PRODUCT_BODY_LOCALIZE_FAILED", `Body item ${itemIndex} could not be moved into the Product-local frame`, cause, itemIndex));
|
|
28950
|
+
}
|
|
28951
|
+
valid.value[Symbol.dispose]();
|
|
28952
|
+
}
|
|
28953
|
+
const exactBody = {
|
|
28954
|
+
kind: "EXACT",
|
|
28955
|
+
solids: asNonEmpty(localized)
|
|
28956
|
+
};
|
|
28957
|
+
try {
|
|
28958
|
+
testHooks$1?.beforeCoincidence?.(exactBody, input.parametricBody);
|
|
28959
|
+
} catch (cause) {
|
|
28960
|
+
disposeAll(exactBody.solids);
|
|
28961
|
+
return (0, brepjs.err)(productBodyError(input, "FAMILIES_PRODUCT_BODY_COMPARISON_FAILED", `authored and parametric Bodies could not be compared`, cause));
|
|
28962
|
+
}
|
|
28963
|
+
if (!bodiesCoincident(exactBody, input.parametricBody)) return (0, brepjs.ok)({
|
|
28964
|
+
kind: "EXACT",
|
|
28965
|
+
body: exactBody
|
|
28966
|
+
});
|
|
28967
|
+
disposeAll(exactBody.solids);
|
|
28968
|
+
return (0, brepjs.ok)({ kind: "PARAMETRIC" });
|
|
28969
|
+
}
|
|
28970
|
+
function evaluateBody(input) {
|
|
28971
|
+
try {
|
|
28972
|
+
const evaluated = input.evaluator.evaluate(input.element.geometry);
|
|
28973
|
+
if (!evaluated.ok) return (0, brepjs.err)(productBodyError(input, "FAMILIES_PRODUCT_BODY_EVALUATION_FAILED", `authored Body evaluation failed: ${evaluated.error.message}`, evaluated.error));
|
|
28974
|
+
return (0, brepjs.ok)((0, brepjs.isSolid)(evaluated.value) ? [evaluated.value] : (0, brepjs.getSolids)(evaluated.value));
|
|
28975
|
+
} catch (cause) {
|
|
28976
|
+
return (0, brepjs.err)(productBodyError(input, "FAMILIES_PRODUCT_BODY_EVALUATION_FAILED", `authored Body evaluation threw`, cause));
|
|
28977
|
+
}
|
|
28978
|
+
}
|
|
28979
|
+
function bodiesCoincident(exact, parametric) {
|
|
28980
|
+
const exactVolume = measureBody(exact);
|
|
28981
|
+
const parametricVolume = measureBody(parametric);
|
|
28982
|
+
if (exactVolume === null || parametricVolume === null || !volumesClose(exactVolume, parametricVolume)) return false;
|
|
28983
|
+
let union = null;
|
|
28984
|
+
try {
|
|
28985
|
+
const fused = (0, brepjs.fuseAll)([...bodySolids(exact), ...bodySolids(parametric)], {
|
|
28986
|
+
optimisation: "sameFace",
|
|
28987
|
+
simplify: true,
|
|
28988
|
+
strategy: "pairwise",
|
|
28989
|
+
trackEvolution: false
|
|
28990
|
+
});
|
|
28991
|
+
if (!fused.ok) return false;
|
|
28992
|
+
union = fused.value;
|
|
28993
|
+
const measured = (0, brepjs.measureVolume)(union);
|
|
28994
|
+
return measured.ok && volumesClose(measured.value, exactVolume) && volumesClose(measured.value, parametricVolume);
|
|
28995
|
+
} catch {
|
|
28996
|
+
return false;
|
|
28997
|
+
} finally {
|
|
28998
|
+
union?.[Symbol.dispose]();
|
|
28999
|
+
}
|
|
29000
|
+
}
|
|
29001
|
+
function measureBody(body) {
|
|
29002
|
+
let total = 0;
|
|
29003
|
+
try {
|
|
29004
|
+
for (const solid of bodySolids(body)) {
|
|
29005
|
+
const measured = (0, brepjs.measureVolume)(solid);
|
|
29006
|
+
if (!measured.ok || !Number.isFinite(measured.value) || measured.value <= 0) return null;
|
|
29007
|
+
total += measured.value;
|
|
29008
|
+
}
|
|
29009
|
+
return total;
|
|
29010
|
+
} catch {
|
|
29011
|
+
return null;
|
|
29012
|
+
}
|
|
29013
|
+
}
|
|
29014
|
+
function volumesClose(a, b) {
|
|
29015
|
+
return Math.abs(a - b) <= RELATIVE_VOLUME_TOLERANCE * Math.max(Math.abs(a), Math.abs(b), Number.EPSILON);
|
|
29016
|
+
}
|
|
29017
|
+
function asNonEmpty(solids) {
|
|
29018
|
+
const first = solids[0];
|
|
29019
|
+
if (first === void 0) throw new Error("Expected a non-empty exact Product Body");
|
|
29020
|
+
return [first, ...solids.slice(1)];
|
|
29021
|
+
}
|
|
29022
|
+
function disposeAll(solids) {
|
|
29023
|
+
for (const solid of solids) solid[Symbol.dispose]();
|
|
29024
|
+
}
|
|
29025
|
+
function productBodyError(input, code, detail, cause, itemIndex) {
|
|
29026
|
+
return {
|
|
29027
|
+
...specError(code, `familiesToBim: '${input.element.keyPath}' (${input.category}) ${detail}`, cause),
|
|
29028
|
+
metadata: {
|
|
29029
|
+
keyPath: input.element.keyPath,
|
|
29030
|
+
category: input.category,
|
|
29031
|
+
...itemIndex !== void 0 ? { itemIndex } : {}
|
|
29032
|
+
}
|
|
29033
|
+
};
|
|
29034
|
+
}
|
|
29035
|
+
//#endregion
|
|
28898
29036
|
//#region src/familiesAdapter.ts
|
|
28899
29037
|
/**
|
|
28900
29038
|
* brepjs-families -> BimModel adapter. Consumes a resolved element tree and
|
|
28901
|
-
* feeds each element's PRE-DESUGARED props into
|
|
28902
|
-
*
|
|
28903
|
-
*
|
|
28904
|
-
* families key paths (stable under reordering), not insertion order.
|
|
29039
|
+
* feeds each element's PRE-DESUGARED props into parametric specs. Civil wall
|
|
29040
|
+
* and railing Products additionally compare the evaluated authored Body with
|
|
29041
|
+
* that spec Body and retain an exact Body when they diverge. GlobalIds derive
|
|
29042
|
+
* from families key paths (stable under reordering), not insertion order.
|
|
28905
29043
|
*
|
|
28906
29044
|
* Scope: building Storey containers; civil Site/Bridge/recursive Bridge Part
|
|
28907
29045
|
* structure and Earthworks Fill bodies; Wall/Slab/Column/Beam/Roof/Stair,
|
|
@@ -28913,6 +29051,7 @@ function isPureTranslation(f, eps = 1e-9) {
|
|
|
28913
29051
|
* voids are rejected: they cut only the IR/viewport geometry, and exporting
|
|
28914
29052
|
* the uncut spec body would silently diverge from what the user sees.
|
|
28915
29053
|
*/
|
|
29054
|
+
var testHooks = null;
|
|
28916
29055
|
var SPEC_DEFAULTS = {
|
|
28917
29056
|
origin: [
|
|
28918
29057
|
0,
|
|
@@ -29093,6 +29232,12 @@ function civilProductArchetype(el) {
|
|
|
29093
29232
|
if (definition === void 0 || !definition.roles.includes(el.semantics.role)) return void 0;
|
|
29094
29233
|
return definition.archetype;
|
|
29095
29234
|
}
|
|
29235
|
+
function civilProductBodyCategory(el, archetype) {
|
|
29236
|
+
if (el.semantics?.kind !== "product") return null;
|
|
29237
|
+
if (archetype === "wall") return "WALL";
|
|
29238
|
+
if (archetype === "railing") return "RAILING";
|
|
29239
|
+
return null;
|
|
29240
|
+
}
|
|
29096
29241
|
function semanticDimension(el, ...names) {
|
|
29097
29242
|
if (el.semantics?.kind !== "product") return void 0;
|
|
29098
29243
|
for (const name of names) {
|
|
@@ -29798,33 +29943,55 @@ function addEarthworksFillElement(model, el, evaluator, spatialFrame) {
|
|
|
29798
29943
|
if (!added.ok) localized.value[Symbol.dispose]();
|
|
29799
29944
|
return added;
|
|
29800
29945
|
}
|
|
29946
|
+
function installCivilProductBody(model, el, localId, category, evaluator, productWorldFrame) {
|
|
29947
|
+
const target = model.getElement(localId);
|
|
29948
|
+
if (target === null || target.category !== category || target.geometry.kind !== "PARAMETRIC") return (0, brepjs.err)(specError("FAMILIES_PRODUCT_BODY_TARGET_INVALID", `familiesToBim: '${el.keyPath}' could not find its parametric ${category} Body after projection`));
|
|
29949
|
+
const selected = selectCivilProductBody({
|
|
29950
|
+
element: el,
|
|
29951
|
+
category,
|
|
29952
|
+
evaluator,
|
|
29953
|
+
productWorldFrame,
|
|
29954
|
+
parametricBody: target.geometry
|
|
29955
|
+
});
|
|
29956
|
+
if (!selected.ok) return selected;
|
|
29957
|
+
if (selected.value.kind === "PARAMETRIC") return (0, brepjs.ok)(void 0);
|
|
29958
|
+
const takeover = model.takeExactProductBody(localId, selected.value.body);
|
|
29959
|
+
if (!takeover.ok) disposeProductBody(selected.value.body);
|
|
29960
|
+
return takeover;
|
|
29961
|
+
}
|
|
29801
29962
|
/**
|
|
29802
29963
|
* Project a resolved families tree into an eager BimModel. The caller owns
|
|
29803
29964
|
* the returned model (`using`); families stays domain-neutral — this adapter
|
|
29804
29965
|
* is where families types meet the IFC vocabulary.
|
|
29805
29966
|
*/
|
|
29806
29967
|
function familiesToBim(root, options) {
|
|
29968
|
+
const model = new BimModel();
|
|
29969
|
+
let transferred = false;
|
|
29970
|
+
try {
|
|
29971
|
+
const projected = projectFamiliesToBim(root, options, model);
|
|
29972
|
+
transferred = projected.ok;
|
|
29973
|
+
return projected;
|
|
29974
|
+
} catch (cause) {
|
|
29975
|
+
return (0, brepjs.err)(specError("FAMILIES_PROJECTION_FAILED", `familiesToBim: unexpected projection failure at '${root.keyPath}'`, cause));
|
|
29976
|
+
} finally {
|
|
29977
|
+
if (!transferred) model[Symbol.dispose]();
|
|
29978
|
+
}
|
|
29979
|
+
}
|
|
29980
|
+
function projectFamiliesToBim(root, options, model) {
|
|
29807
29981
|
const usesAuthoredCivilHierarchy = hasCivilSpatialIntent(root);
|
|
29808
29982
|
if (usesAuthoredCivilHierarchy) {
|
|
29809
29983
|
const keyed = requireKeyed(root);
|
|
29810
29984
|
if (!keyed.ok) return keyed;
|
|
29811
29985
|
}
|
|
29812
|
-
const model = new BimModel();
|
|
29813
29986
|
const initResult = model.init(options.project, usesAuthoredCivilHierarchy ? { stableKey: civilSpatialKind(root) !== void 0 ? `${root.keyPath}#project` : root.keyPath } : void 0);
|
|
29814
29987
|
if (!initResult.ok) return initResult;
|
|
29815
29988
|
const projectId = initResult.value;
|
|
29816
29989
|
let buildingId = null;
|
|
29817
29990
|
if (!usesAuthoredCivilHierarchy) {
|
|
29818
29991
|
const siteResult = model.addSite({ name: options.siteName ?? "Site" });
|
|
29819
|
-
if (!siteResult.ok)
|
|
29820
|
-
model[Symbol.dispose]();
|
|
29821
|
-
return siteResult;
|
|
29822
|
-
}
|
|
29992
|
+
if (!siteResult.ok) return siteResult;
|
|
29823
29993
|
const buildingResult = model.addBuilding({ name: options.buildingName ?? "Building" });
|
|
29824
|
-
if (!buildingResult.ok)
|
|
29825
|
-
model[Symbol.dispose]();
|
|
29826
|
-
return buildingResult;
|
|
29827
|
-
}
|
|
29994
|
+
if (!buildingResult.ok) return buildingResult;
|
|
29828
29995
|
buildingId = buildingResult.value;
|
|
29829
29996
|
model.aggregate(projectId, siteResult.value);
|
|
29830
29997
|
model.aggregate(siteResult.value, buildingId);
|
|
@@ -29854,8 +30021,7 @@ function familiesToBim(root, options) {
|
|
|
29854
30021
|
if (el.geometry.kind !== "Empty") return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: civil spatial element '${el.keyPath}' carries its own geometry — Site/Bridge/Bridge Part export no body, so author it as a child Product (e.g. Earthworks Fill)`));
|
|
29855
30022
|
const nodeFrame = civilNodeFrame(el, cumulativeFrameHere);
|
|
29856
30023
|
const rotatedFrame = rotatedHere || hasRotatedAxes(el);
|
|
29857
|
-
const
|
|
29858
|
-
const added = addCivilSpatialOccurrence(model, el, civilKind, input);
|
|
30024
|
+
const added = addCivilSpatialOccurrence(model, el, civilKind, rotatedFrame ? civilSpatialFrameInput(el, nodeFrame, state.spatialFrame) : civilSpatialInput(el, subtractTranslation(cumulativeTranslationHere, state.projectedSpatialTranslation)));
|
|
29859
30025
|
if (!added.ok) return added;
|
|
29860
30026
|
model.aggregate(state.spatialStructureId, added.value);
|
|
29861
30027
|
idByKeyPath.set(el.keyPath, added.value);
|
|
@@ -29891,6 +30057,15 @@ function familiesToBim(root, options) {
|
|
|
29891
30057
|
} else if (route !== void 0) {
|
|
29892
30058
|
const keyed = requireKeyed(el);
|
|
29893
30059
|
if (!keyed.ok) return keyed;
|
|
30060
|
+
const productBodyCategory = civilProductBodyCategory(el, effectiveArchetype);
|
|
30061
|
+
const productBodyEvaluator = productBodyCategory === null ? void 0 : options.bodyEvaluator ?? options.proxyEvaluator;
|
|
30062
|
+
if (productBodyCategory !== null && productBodyEvaluator === void 0) return (0, brepjs.err)({
|
|
30063
|
+
...specError("FAMILIES_PRODUCT_BODY_EVALUATOR_REQUIRED", `familiesToBim: civil Product '${el.keyPath}' mapped to ${productBodyCategory} needs bodyEvaluator or proxyEvaluator to verify its authoritative Body`),
|
|
30064
|
+
metadata: {
|
|
30065
|
+
keyPath: el.keyPath,
|
|
30066
|
+
category: productBodyCategory
|
|
30067
|
+
}
|
|
30068
|
+
});
|
|
29894
30069
|
const voids = el.props["voids"];
|
|
29895
30070
|
if (Array.isArray(voids)) {
|
|
29896
30071
|
const openings = el.children.filter((c) => c.type === "Opening").length;
|
|
@@ -29910,6 +30085,11 @@ function familiesToBim(root, options) {
|
|
|
29910
30085
|
const opened = addOpenings(model, el, added.value, nextSpatialStructureId, idByKeyPath);
|
|
29911
30086
|
if (!opened.ok) return opened;
|
|
29912
30087
|
}
|
|
30088
|
+
if (productBodyCategory !== null && productBodyEvaluator !== void 0) {
|
|
30089
|
+
const installed = installCivilProductBody(model, el, added.value, productBodyCategory, productBodyEvaluator, elementBodyFrame(el, cumulativeFrameHere, occurrenceTransformDepthHere));
|
|
30090
|
+
if (!installed.ok) return installed;
|
|
30091
|
+
testHooks?.afterCivilProductBody?.(model, added.value, el);
|
|
30092
|
+
}
|
|
29913
30093
|
} else if (el.type === "Opening") return (0, brepjs.err)(specError("FAMILIES_OPENING_OUTSIDE_WALL", `familiesToBim: opening '${el.keyPath}' is not hosted by a Wall — only wall openings are mapped`));
|
|
29914
30094
|
else if (el.type !== "Group" && el.geometry.kind !== "Empty") {
|
|
29915
30095
|
if (options.proxyEvaluator === void 0) return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_TYPE", `familiesToBim: no supported spec mapping for element type '${el.type}' at '${el.keyPath}' (archetype: ${el.archetype ?? "none"}) — declare a recognized archetype or civil Product category/role, add a spec route, or pass proxyEvaluator to export it as an IfcBuildingElementProxy`));
|
|
@@ -29953,10 +30133,7 @@ function familiesToBim(root, options) {
|
|
|
29953
30133
|
cumulativeFrame: IDENTITY_FRAME,
|
|
29954
30134
|
spatialFrame: IDENTITY_FRAME
|
|
29955
30135
|
});
|
|
29956
|
-
if (!walked.ok)
|
|
29957
|
-
model[Symbol.dispose]();
|
|
29958
|
-
return walked;
|
|
29959
|
-
}
|
|
30136
|
+
if (!walked.ok) return walked;
|
|
29960
30137
|
return (0, brepjs.ok)({
|
|
29961
30138
|
model,
|
|
29962
30139
|
idByKeyPath,
|
package/dist/brepjs-bim.js
CHANGED
|
@@ -7538,19 +7538,19 @@ function rotationOp(matrix) {
|
|
|
7538
7538
|
}
|
|
7539
7539
|
//#endregion
|
|
7540
7540
|
//#region src/elementFns/placedGeometry.ts
|
|
7541
|
-
var testHooks$
|
|
7541
|
+
var testHooks$5 = null;
|
|
7542
7542
|
function place(solid, frame) {
|
|
7543
7543
|
let placed = null;
|
|
7544
7544
|
try {
|
|
7545
7545
|
placed = locateShapeInFrame(solid, frame);
|
|
7546
|
-
testHooks$
|
|
7546
|
+
testHooks$5?.afterPlaced?.(placed);
|
|
7547
7547
|
return ok(placed);
|
|
7548
7548
|
} catch (cause) {
|
|
7549
7549
|
placed?.[Symbol.dispose]();
|
|
7550
7550
|
return err(geometryError("PLACED_GEOMETRY_FAILED", "Element placement threw while transforming geometry", cause));
|
|
7551
7551
|
}
|
|
7552
7552
|
}
|
|
7553
|
-
function disposeAll(solids) {
|
|
7553
|
+
function disposeAll$1(solids) {
|
|
7554
7554
|
for (const s of solids) s[Symbol.dispose]();
|
|
7555
7555
|
}
|
|
7556
7556
|
function placeWithinParent(solid, localFrame, parentFrame) {
|
|
@@ -7592,7 +7592,7 @@ function placedSolids(el, options = {}) {
|
|
|
7592
7592
|
for (const solid of bodySolids(el.geometry)) {
|
|
7593
7593
|
const placed = placeWithinParent(solid, el.spec, parentFrame);
|
|
7594
7594
|
if (!placed.ok) {
|
|
7595
|
-
disposeAll(out);
|
|
7595
|
+
disposeAll$1(out);
|
|
7596
7596
|
return placed;
|
|
7597
7597
|
}
|
|
7598
7598
|
out.push(placed.value);
|
|
@@ -7617,12 +7617,12 @@ function placedSolids(el, options = {}) {
|
|
|
7617
7617
|
var _usingCtx3 = _usingCtx();
|
|
7618
7618
|
const built = stairFlightToSolid(flight);
|
|
7619
7619
|
if (!built.ok) {
|
|
7620
|
-
disposeAll(out);
|
|
7620
|
+
disposeAll$1(out);
|
|
7621
7621
|
return err(built.error);
|
|
7622
7622
|
}
|
|
7623
7623
|
const placed = placeWithinParent(_usingCtx3.u(built.value.solid), flight, parentFrame);
|
|
7624
7624
|
if (!placed.ok) {
|
|
7625
|
-
disposeAll(out);
|
|
7625
|
+
disposeAll$1(out);
|
|
7626
7626
|
return placed;
|
|
7627
7627
|
}
|
|
7628
7628
|
out.push(placed.value);
|
|
@@ -7639,12 +7639,12 @@ function placedSolids(el, options = {}) {
|
|
|
7639
7639
|
var _usingCtx4 = _usingCtx();
|
|
7640
7640
|
const built = rampFlightToSolid(flight);
|
|
7641
7641
|
if (!built.ok) {
|
|
7642
|
-
disposeAll(out);
|
|
7642
|
+
disposeAll$1(out);
|
|
7643
7643
|
return err(built.error);
|
|
7644
7644
|
}
|
|
7645
7645
|
const placed = placeWithinParent(_usingCtx4.u(built.value.solid), flight, parentFrame);
|
|
7646
7646
|
if (!placed.ok) {
|
|
7647
|
-
disposeAll(out);
|
|
7647
|
+
disposeAll$1(out);
|
|
7648
7648
|
return placed;
|
|
7649
7649
|
}
|
|
7650
7650
|
out.push(placed.value);
|
|
@@ -7695,12 +7695,12 @@ function placedSolids(el, options = {}) {
|
|
|
7695
7695
|
]
|
|
7696
7696
|
});
|
|
7697
7697
|
if (!componentLocal.ok) {
|
|
7698
|
-
disposeAll(out);
|
|
7698
|
+
disposeAll$1(out);
|
|
7699
7699
|
return componentLocal;
|
|
7700
7700
|
}
|
|
7701
7701
|
const placed = placeWithinParent(_usingCtx5.u(componentLocal.value), el.spec, parentFrame);
|
|
7702
7702
|
if (!placed.ok) {
|
|
7703
|
-
disposeAll(out);
|
|
7703
|
+
disposeAll$1(out);
|
|
7704
7704
|
return placed;
|
|
7705
7705
|
}
|
|
7706
7706
|
out.push(placed.value);
|
|
@@ -7805,7 +7805,7 @@ async function initIfcApi(api) {
|
|
|
7805
7805
|
}
|
|
7806
7806
|
//#endregion
|
|
7807
7807
|
//#region src/ifc-writer/ifcWriter.ts
|
|
7808
|
-
var testHooks$
|
|
7808
|
+
var testHooks$4 = null;
|
|
7809
7809
|
/** Default MVD ViewDefinition declared in the STEP FILE_DESCRIPTION header. */
|
|
7810
7810
|
var DEFAULT_MVD_VIEW_DEFINITION = "ReferenceView_v1.2";
|
|
7811
7811
|
var VIEW_DEFINITION_RE = /ViewDefinition \[[^\]]*\]/;
|
|
@@ -7867,7 +7867,7 @@ var IfcWriter = class IfcWriter {
|
|
|
7867
7867
|
}
|
|
7868
7868
|
writeLine(entity) {
|
|
7869
7869
|
this.#api.WriteLine(this.#modelId, entity);
|
|
7870
|
-
testHooks$
|
|
7870
|
+
testHooks$4?.afterWriteLine?.();
|
|
7871
7871
|
return entity.expressID;
|
|
7872
7872
|
}
|
|
7873
7873
|
ref(id) {
|
|
@@ -7880,7 +7880,7 @@ var IfcWriter = class IfcWriter {
|
|
|
7880
7880
|
if (this.#closed) return;
|
|
7881
7881
|
this.#closed = true;
|
|
7882
7882
|
this.#api.CloseModel(this.#modelId);
|
|
7883
|
-
testHooks$
|
|
7883
|
+
testHooks$4?.afterClose?.();
|
|
7884
7884
|
}
|
|
7885
7885
|
[Symbol.dispose]() {
|
|
7886
7886
|
this.close();
|
|
@@ -14416,7 +14416,7 @@ function buildElementContainmentMap(reader) {
|
|
|
14416
14416
|
//#endregion
|
|
14417
14417
|
//#region src/import/geometryRead.ts
|
|
14418
14418
|
var NONE = { kind: "NONE" };
|
|
14419
|
-
var testHooks$
|
|
14419
|
+
var testHooks$3 = null;
|
|
14420
14420
|
/**
|
|
14421
14421
|
* Reconstructs the `Body` (SweptSolid) representation of a product into a brepjs
|
|
14422
14422
|
* solid, falling back to tessellated mesh import when no parametric path exists.
|
|
@@ -14660,7 +14660,7 @@ function reconstructTessellatedMesh(mesh, scaleToMm, itemExpressId, diagnostics)
|
|
|
14660
14660
|
}
|
|
14661
14661
|
if (solid !== null) {
|
|
14662
14662
|
try {
|
|
14663
|
-
testHooks$
|
|
14663
|
+
testHooks$3?.afterItemSolid?.(itemExpressId, solid);
|
|
14664
14664
|
} catch (cause) {
|
|
14665
14665
|
solid[Symbol.dispose]();
|
|
14666
14666
|
throw cause;
|
|
@@ -15621,7 +15621,7 @@ function readOwnerHistory(reader, ownerHistoryExpressId) {
|
|
|
15621
15621
|
}
|
|
15622
15622
|
//#endregion
|
|
15623
15623
|
//#region src/import/fromIfc.ts
|
|
15624
|
-
var testHooks = null;
|
|
15624
|
+
var testHooks$2 = null;
|
|
15625
15625
|
/**
|
|
15626
15626
|
* Each enumerated physical-element IFC type and the {@link ImportedElementCategory}
|
|
15627
15627
|
* it maps to. Curtain-wall sub-components (IfcPlate/IfcMember) and stair/ramp
|
|
@@ -15685,7 +15685,7 @@ async function fromIfc(bytes, options = {}) {
|
|
|
15685
15685
|
if (element === null) continue;
|
|
15686
15686
|
elements.push(element);
|
|
15687
15687
|
byExpressId.set(element.expressId, element);
|
|
15688
|
-
testHooks?.afterElement?.(element, elements.length);
|
|
15688
|
+
testHooks$2?.afterElement?.(element, elements.length);
|
|
15689
15689
|
}
|
|
15690
15690
|
const report = appendIssues(emptyReport(), diagnostics);
|
|
15691
15691
|
const applicationName = readApplicationName(reader);
|
|
@@ -15731,7 +15731,7 @@ function readElement(reader, expressId, category, scale, containment, typeEnums,
|
|
|
15731
15731
|
bounds: null,
|
|
15732
15732
|
volumeMm3: null
|
|
15733
15733
|
} : reconstructGeometry(reader, expressId, scale, voidedBy, diagnostics);
|
|
15734
|
-
testHooks?.afterGeometry?.(expressId, geometry);
|
|
15734
|
+
testHooks$2?.afterGeometry?.(expressId, geometry);
|
|
15735
15735
|
const psets = readPsets(reader, expressId).map(toImportedPset);
|
|
15736
15736
|
const material = readMaterial(reader, expressId, scale);
|
|
15737
15737
|
const classification = readClassification(reader, expressId);
|
|
@@ -28872,13 +28872,151 @@ function isPureTranslation(f, eps = 1e-9) {
|
|
|
28872
28872
|
return Math.abs((f[0] ?? 1) - 1) < eps && Math.abs((f[5] ?? 1) - 1) < eps && Math.abs((f[10] ?? 1) - 1) < eps && Math.abs(f[1] ?? 0) < eps && Math.abs(f[2] ?? 0) < eps && Math.abs(f[4] ?? 0) < eps && Math.abs(f[6] ?? 0) < eps && Math.abs(f[8] ?? 0) < eps && Math.abs(f[9] ?? 0) < eps;
|
|
28873
28873
|
}
|
|
28874
28874
|
//#endregion
|
|
28875
|
+
//#region src/familiesProductBody.ts
|
|
28876
|
+
var RELATIVE_VOLUME_TOLERANCE = 1e-6;
|
|
28877
|
+
var testHooks$1 = null;
|
|
28878
|
+
/**
|
|
28879
|
+
* Evaluates a civil Product Body, clones and localizes every borrowed source,
|
|
28880
|
+
* then proves whether the registered-opening parametric Body is coincident.
|
|
28881
|
+
* An EXACT result is caller-owned until takeExactProductBody() succeeds.
|
|
28882
|
+
*/
|
|
28883
|
+
function selectCivilProductBody(input) {
|
|
28884
|
+
const evaluated = evaluateBody(input);
|
|
28885
|
+
if (!evaluated.ok) return evaluated;
|
|
28886
|
+
const sources = evaluated.value;
|
|
28887
|
+
if (sources.length === 0) return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_EMPTY", `evaluated to no solid Body items`));
|
|
28888
|
+
const localized = [];
|
|
28889
|
+
const inverse = decomposeFrame(frameInverse(input.productWorldFrame));
|
|
28890
|
+
for (const [itemIndex, source] of sources.entries()) {
|
|
28891
|
+
const copied = clone(source);
|
|
28892
|
+
if (!copied.ok) {
|
|
28893
|
+
disposeAll(localized);
|
|
28894
|
+
return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_COPY_FAILED", `Body item ${itemIndex} could not be copied`, copied.error, itemIndex));
|
|
28895
|
+
}
|
|
28896
|
+
try {
|
|
28897
|
+
testHooks$1?.afterCopy?.(itemIndex, copied.value, source);
|
|
28898
|
+
} catch (cause) {
|
|
28899
|
+
copied.value[Symbol.dispose]();
|
|
28900
|
+
disposeAll(localized);
|
|
28901
|
+
return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_COPY_FAILED", `Body item ${itemIndex} could not be copied`, cause, itemIndex));
|
|
28902
|
+
}
|
|
28903
|
+
let valid;
|
|
28904
|
+
try {
|
|
28905
|
+
valid = validSolid(copied.value);
|
|
28906
|
+
} catch (cause) {
|
|
28907
|
+
copied.value[Symbol.dispose]();
|
|
28908
|
+
disposeAll(localized);
|
|
28909
|
+
return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_INVALID", `Body item ${itemIndex} could not be validated as a solid`, cause, itemIndex));
|
|
28910
|
+
}
|
|
28911
|
+
if (!valid.ok) {
|
|
28912
|
+
copied.value[Symbol.dispose]();
|
|
28913
|
+
disposeAll(localized);
|
|
28914
|
+
return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_INVALID", `Body item ${itemIndex} is not a valid solid: ${valid.error}`, void 0, itemIndex));
|
|
28915
|
+
}
|
|
28916
|
+
let local = null;
|
|
28917
|
+
try {
|
|
28918
|
+
testHooks$1?.beforeLocalize?.(itemIndex, valid.value);
|
|
28919
|
+
local = locateShapeInFrame(valid.value, inverse);
|
|
28920
|
+
testHooks$1?.afterLocalized?.(itemIndex, local);
|
|
28921
|
+
localized.push(local);
|
|
28922
|
+
} catch (cause) {
|
|
28923
|
+
local?.[Symbol.dispose]();
|
|
28924
|
+
valid.value[Symbol.dispose]();
|
|
28925
|
+
disposeAll(localized);
|
|
28926
|
+
return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_LOCALIZE_FAILED", `Body item ${itemIndex} could not be moved into the Product-local frame`, cause, itemIndex));
|
|
28927
|
+
}
|
|
28928
|
+
valid.value[Symbol.dispose]();
|
|
28929
|
+
}
|
|
28930
|
+
const exactBody = {
|
|
28931
|
+
kind: "EXACT",
|
|
28932
|
+
solids: asNonEmpty(localized)
|
|
28933
|
+
};
|
|
28934
|
+
try {
|
|
28935
|
+
testHooks$1?.beforeCoincidence?.(exactBody, input.parametricBody);
|
|
28936
|
+
} catch (cause) {
|
|
28937
|
+
disposeAll(exactBody.solids);
|
|
28938
|
+
return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_COMPARISON_FAILED", `authored and parametric Bodies could not be compared`, cause));
|
|
28939
|
+
}
|
|
28940
|
+
if (!bodiesCoincident(exactBody, input.parametricBody)) return ok({
|
|
28941
|
+
kind: "EXACT",
|
|
28942
|
+
body: exactBody
|
|
28943
|
+
});
|
|
28944
|
+
disposeAll(exactBody.solids);
|
|
28945
|
+
return ok({ kind: "PARAMETRIC" });
|
|
28946
|
+
}
|
|
28947
|
+
function evaluateBody(input) {
|
|
28948
|
+
try {
|
|
28949
|
+
const evaluated = input.evaluator.evaluate(input.element.geometry);
|
|
28950
|
+
if (!evaluated.ok) return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_EVALUATION_FAILED", `authored Body evaluation failed: ${evaluated.error.message}`, evaluated.error));
|
|
28951
|
+
return ok(isSolid(evaluated.value) ? [evaluated.value] : getSolids(evaluated.value));
|
|
28952
|
+
} catch (cause) {
|
|
28953
|
+
return err(productBodyError(input, "FAMILIES_PRODUCT_BODY_EVALUATION_FAILED", `authored Body evaluation threw`, cause));
|
|
28954
|
+
}
|
|
28955
|
+
}
|
|
28956
|
+
function bodiesCoincident(exact, parametric) {
|
|
28957
|
+
const exactVolume = measureBody(exact);
|
|
28958
|
+
const parametricVolume = measureBody(parametric);
|
|
28959
|
+
if (exactVolume === null || parametricVolume === null || !volumesClose(exactVolume, parametricVolume)) return false;
|
|
28960
|
+
let union = null;
|
|
28961
|
+
try {
|
|
28962
|
+
const fused = fuseAll([...bodySolids(exact), ...bodySolids(parametric)], {
|
|
28963
|
+
optimisation: "sameFace",
|
|
28964
|
+
simplify: true,
|
|
28965
|
+
strategy: "pairwise",
|
|
28966
|
+
trackEvolution: false
|
|
28967
|
+
});
|
|
28968
|
+
if (!fused.ok) return false;
|
|
28969
|
+
union = fused.value;
|
|
28970
|
+
const measured = measureVolume(union);
|
|
28971
|
+
return measured.ok && volumesClose(measured.value, exactVolume) && volumesClose(measured.value, parametricVolume);
|
|
28972
|
+
} catch {
|
|
28973
|
+
return false;
|
|
28974
|
+
} finally {
|
|
28975
|
+
union?.[Symbol.dispose]();
|
|
28976
|
+
}
|
|
28977
|
+
}
|
|
28978
|
+
function measureBody(body) {
|
|
28979
|
+
let total = 0;
|
|
28980
|
+
try {
|
|
28981
|
+
for (const solid of bodySolids(body)) {
|
|
28982
|
+
const measured = measureVolume(solid);
|
|
28983
|
+
if (!measured.ok || !Number.isFinite(measured.value) || measured.value <= 0) return null;
|
|
28984
|
+
total += measured.value;
|
|
28985
|
+
}
|
|
28986
|
+
return total;
|
|
28987
|
+
} catch {
|
|
28988
|
+
return null;
|
|
28989
|
+
}
|
|
28990
|
+
}
|
|
28991
|
+
function volumesClose(a, b) {
|
|
28992
|
+
return Math.abs(a - b) <= RELATIVE_VOLUME_TOLERANCE * Math.max(Math.abs(a), Math.abs(b), Number.EPSILON);
|
|
28993
|
+
}
|
|
28994
|
+
function asNonEmpty(solids) {
|
|
28995
|
+
const first = solids[0];
|
|
28996
|
+
if (first === void 0) throw new Error("Expected a non-empty exact Product Body");
|
|
28997
|
+
return [first, ...solids.slice(1)];
|
|
28998
|
+
}
|
|
28999
|
+
function disposeAll(solids) {
|
|
29000
|
+
for (const solid of solids) solid[Symbol.dispose]();
|
|
29001
|
+
}
|
|
29002
|
+
function productBodyError(input, code, detail, cause, itemIndex) {
|
|
29003
|
+
return {
|
|
29004
|
+
...specError(code, `familiesToBim: '${input.element.keyPath}' (${input.category}) ${detail}`, cause),
|
|
29005
|
+
metadata: {
|
|
29006
|
+
keyPath: input.element.keyPath,
|
|
29007
|
+
category: input.category,
|
|
29008
|
+
...itemIndex !== void 0 ? { itemIndex } : {}
|
|
29009
|
+
}
|
|
29010
|
+
};
|
|
29011
|
+
}
|
|
29012
|
+
//#endregion
|
|
28875
29013
|
//#region src/familiesAdapter.ts
|
|
28876
29014
|
/**
|
|
28877
29015
|
* brepjs-families -> BimModel adapter. Consumes a resolved element tree and
|
|
28878
|
-
* feeds each element's PRE-DESUGARED props into
|
|
28879
|
-
*
|
|
28880
|
-
*
|
|
28881
|
-
* families key paths (stable under reordering), not insertion order.
|
|
29016
|
+
* feeds each element's PRE-DESUGARED props into parametric specs. Civil wall
|
|
29017
|
+
* and railing Products additionally compare the evaluated authored Body with
|
|
29018
|
+
* that spec Body and retain an exact Body when they diverge. GlobalIds derive
|
|
29019
|
+
* from families key paths (stable under reordering), not insertion order.
|
|
28882
29020
|
*
|
|
28883
29021
|
* Scope: building Storey containers; civil Site/Bridge/recursive Bridge Part
|
|
28884
29022
|
* structure and Earthworks Fill bodies; Wall/Slab/Column/Beam/Roof/Stair,
|
|
@@ -28890,6 +29028,7 @@ function isPureTranslation(f, eps = 1e-9) {
|
|
|
28890
29028
|
* voids are rejected: they cut only the IR/viewport geometry, and exporting
|
|
28891
29029
|
* the uncut spec body would silently diverge from what the user sees.
|
|
28892
29030
|
*/
|
|
29031
|
+
var testHooks = null;
|
|
28893
29032
|
var SPEC_DEFAULTS = {
|
|
28894
29033
|
origin: [
|
|
28895
29034
|
0,
|
|
@@ -29070,6 +29209,12 @@ function civilProductArchetype(el) {
|
|
|
29070
29209
|
if (definition === void 0 || !definition.roles.includes(el.semantics.role)) return void 0;
|
|
29071
29210
|
return definition.archetype;
|
|
29072
29211
|
}
|
|
29212
|
+
function civilProductBodyCategory(el, archetype) {
|
|
29213
|
+
if (el.semantics?.kind !== "product") return null;
|
|
29214
|
+
if (archetype === "wall") return "WALL";
|
|
29215
|
+
if (archetype === "railing") return "RAILING";
|
|
29216
|
+
return null;
|
|
29217
|
+
}
|
|
29073
29218
|
function semanticDimension(el, ...names) {
|
|
29074
29219
|
if (el.semantics?.kind !== "product") return void 0;
|
|
29075
29220
|
for (const name of names) {
|
|
@@ -29775,33 +29920,55 @@ function addEarthworksFillElement(model, el, evaluator, spatialFrame) {
|
|
|
29775
29920
|
if (!added.ok) localized.value[Symbol.dispose]();
|
|
29776
29921
|
return added;
|
|
29777
29922
|
}
|
|
29923
|
+
function installCivilProductBody(model, el, localId, category, evaluator, productWorldFrame) {
|
|
29924
|
+
const target = model.getElement(localId);
|
|
29925
|
+
if (target === null || target.category !== category || target.geometry.kind !== "PARAMETRIC") return err(specError("FAMILIES_PRODUCT_BODY_TARGET_INVALID", `familiesToBim: '${el.keyPath}' could not find its parametric ${category} Body after projection`));
|
|
29926
|
+
const selected = selectCivilProductBody({
|
|
29927
|
+
element: el,
|
|
29928
|
+
category,
|
|
29929
|
+
evaluator,
|
|
29930
|
+
productWorldFrame,
|
|
29931
|
+
parametricBody: target.geometry
|
|
29932
|
+
});
|
|
29933
|
+
if (!selected.ok) return selected;
|
|
29934
|
+
if (selected.value.kind === "PARAMETRIC") return ok(void 0);
|
|
29935
|
+
const takeover = model.takeExactProductBody(localId, selected.value.body);
|
|
29936
|
+
if (!takeover.ok) disposeProductBody(selected.value.body);
|
|
29937
|
+
return takeover;
|
|
29938
|
+
}
|
|
29778
29939
|
/**
|
|
29779
29940
|
* Project a resolved families tree into an eager BimModel. The caller owns
|
|
29780
29941
|
* the returned model (`using`); families stays domain-neutral — this adapter
|
|
29781
29942
|
* is where families types meet the IFC vocabulary.
|
|
29782
29943
|
*/
|
|
29783
29944
|
function familiesToBim(root, options) {
|
|
29945
|
+
const model = new BimModel();
|
|
29946
|
+
let transferred = false;
|
|
29947
|
+
try {
|
|
29948
|
+
const projected = projectFamiliesToBim(root, options, model);
|
|
29949
|
+
transferred = projected.ok;
|
|
29950
|
+
return projected;
|
|
29951
|
+
} catch (cause) {
|
|
29952
|
+
return err(specError("FAMILIES_PROJECTION_FAILED", `familiesToBim: unexpected projection failure at '${root.keyPath}'`, cause));
|
|
29953
|
+
} finally {
|
|
29954
|
+
if (!transferred) model[Symbol.dispose]();
|
|
29955
|
+
}
|
|
29956
|
+
}
|
|
29957
|
+
function projectFamiliesToBim(root, options, model) {
|
|
29784
29958
|
const usesAuthoredCivilHierarchy = hasCivilSpatialIntent(root);
|
|
29785
29959
|
if (usesAuthoredCivilHierarchy) {
|
|
29786
29960
|
const keyed = requireKeyed(root);
|
|
29787
29961
|
if (!keyed.ok) return keyed;
|
|
29788
29962
|
}
|
|
29789
|
-
const model = new BimModel();
|
|
29790
29963
|
const initResult = model.init(options.project, usesAuthoredCivilHierarchy ? { stableKey: civilSpatialKind(root) !== void 0 ? `${root.keyPath}#project` : root.keyPath } : void 0);
|
|
29791
29964
|
if (!initResult.ok) return initResult;
|
|
29792
29965
|
const projectId = initResult.value;
|
|
29793
29966
|
let buildingId = null;
|
|
29794
29967
|
if (!usesAuthoredCivilHierarchy) {
|
|
29795
29968
|
const siteResult = model.addSite({ name: options.siteName ?? "Site" });
|
|
29796
|
-
if (!siteResult.ok)
|
|
29797
|
-
model[Symbol.dispose]();
|
|
29798
|
-
return siteResult;
|
|
29799
|
-
}
|
|
29969
|
+
if (!siteResult.ok) return siteResult;
|
|
29800
29970
|
const buildingResult = model.addBuilding({ name: options.buildingName ?? "Building" });
|
|
29801
|
-
if (!buildingResult.ok)
|
|
29802
|
-
model[Symbol.dispose]();
|
|
29803
|
-
return buildingResult;
|
|
29804
|
-
}
|
|
29971
|
+
if (!buildingResult.ok) return buildingResult;
|
|
29805
29972
|
buildingId = buildingResult.value;
|
|
29806
29973
|
model.aggregate(projectId, siteResult.value);
|
|
29807
29974
|
model.aggregate(siteResult.value, buildingId);
|
|
@@ -29831,8 +29998,7 @@ function familiesToBim(root, options) {
|
|
|
29831
29998
|
if (el.geometry.kind !== "Empty") return err(specError("FAMILIES_UNSUPPORTED_CIVIL_SEMANTICS", `familiesToBim: civil spatial element '${el.keyPath}' carries its own geometry — Site/Bridge/Bridge Part export no body, so author it as a child Product (e.g. Earthworks Fill)`));
|
|
29832
29999
|
const nodeFrame = civilNodeFrame(el, cumulativeFrameHere);
|
|
29833
30000
|
const rotatedFrame = rotatedHere || hasRotatedAxes(el);
|
|
29834
|
-
const
|
|
29835
|
-
const added = addCivilSpatialOccurrence(model, el, civilKind, input);
|
|
30001
|
+
const added = addCivilSpatialOccurrence(model, el, civilKind, rotatedFrame ? civilSpatialFrameInput(el, nodeFrame, state.spatialFrame) : civilSpatialInput(el, subtractTranslation(cumulativeTranslationHere, state.projectedSpatialTranslation)));
|
|
29836
30002
|
if (!added.ok) return added;
|
|
29837
30003
|
model.aggregate(state.spatialStructureId, added.value);
|
|
29838
30004
|
idByKeyPath.set(el.keyPath, added.value);
|
|
@@ -29868,6 +30034,15 @@ function familiesToBim(root, options) {
|
|
|
29868
30034
|
} else if (route !== void 0) {
|
|
29869
30035
|
const keyed = requireKeyed(el);
|
|
29870
30036
|
if (!keyed.ok) return keyed;
|
|
30037
|
+
const productBodyCategory = civilProductBodyCategory(el, effectiveArchetype);
|
|
30038
|
+
const productBodyEvaluator = productBodyCategory === null ? void 0 : options.bodyEvaluator ?? options.proxyEvaluator;
|
|
30039
|
+
if (productBodyCategory !== null && productBodyEvaluator === void 0) return err({
|
|
30040
|
+
...specError("FAMILIES_PRODUCT_BODY_EVALUATOR_REQUIRED", `familiesToBim: civil Product '${el.keyPath}' mapped to ${productBodyCategory} needs bodyEvaluator or proxyEvaluator to verify its authoritative Body`),
|
|
30041
|
+
metadata: {
|
|
30042
|
+
keyPath: el.keyPath,
|
|
30043
|
+
category: productBodyCategory
|
|
30044
|
+
}
|
|
30045
|
+
});
|
|
29871
30046
|
const voids = el.props["voids"];
|
|
29872
30047
|
if (Array.isArray(voids)) {
|
|
29873
30048
|
const openings = el.children.filter((c) => c.type === "Opening").length;
|
|
@@ -29887,6 +30062,11 @@ function familiesToBim(root, options) {
|
|
|
29887
30062
|
const opened = addOpenings(model, el, added.value, nextSpatialStructureId, idByKeyPath);
|
|
29888
30063
|
if (!opened.ok) return opened;
|
|
29889
30064
|
}
|
|
30065
|
+
if (productBodyCategory !== null && productBodyEvaluator !== void 0) {
|
|
30066
|
+
const installed = installCivilProductBody(model, el, added.value, productBodyCategory, productBodyEvaluator, elementBodyFrame(el, cumulativeFrameHere, occurrenceTransformDepthHere));
|
|
30067
|
+
if (!installed.ok) return installed;
|
|
30068
|
+
testHooks?.afterCivilProductBody?.(model, added.value, el);
|
|
30069
|
+
}
|
|
29890
30070
|
} else if (el.type === "Opening") return err(specError("FAMILIES_OPENING_OUTSIDE_WALL", `familiesToBim: opening '${el.keyPath}' is not hosted by a Wall — only wall openings are mapped`));
|
|
29891
30071
|
else if (el.type !== "Group" && el.geometry.kind !== "Empty") {
|
|
29892
30072
|
if (options.proxyEvaluator === void 0) return err(specError("FAMILIES_UNSUPPORTED_TYPE", `familiesToBim: no supported spec mapping for element type '${el.type}' at '${el.keyPath}' (archetype: ${el.archetype ?? "none"}) — declare a recognized archetype or civil Product category/role, add a spec route, or pass proxyEvaluator to export it as an IfcBuildingElementProxy`));
|
|
@@ -29930,10 +30110,7 @@ function familiesToBim(root, options) {
|
|
|
29930
30110
|
cumulativeFrame: IDENTITY_FRAME,
|
|
29931
30111
|
spatialFrame: IDENTITY_FRAME
|
|
29932
30112
|
});
|
|
29933
|
-
if (!walked.ok)
|
|
29934
|
-
model[Symbol.dispose]();
|
|
29935
|
-
return walked;
|
|
29936
|
-
}
|
|
30113
|
+
if (!walked.ok) return walked;
|
|
29937
30114
|
return ok({
|
|
29938
30115
|
model,
|
|
29939
30116
|
idByKeyPath,
|
|
@@ -9,9 +9,16 @@ export interface FamiliesToBimOptions {
|
|
|
9
9
|
readonly siteName?: string | undefined;
|
|
10
10
|
readonly buildingName?: string | undefined;
|
|
11
11
|
/**
|
|
12
|
-
* Materializes exact evaluated Product Bodies for supported typed routes
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* Materializes exact evaluated Product Bodies for supported typed routes.
|
|
13
|
+
* Earthworks Fill always retains that Body. Civil-semantic walls and railings
|
|
14
|
+
* require this evaluator (or `proxyEvaluator`) before projection: absence
|
|
15
|
+
* returns FAMILIES_PRODUCT_BODY_EVALUATOR_REQUIRED with the element path and
|
|
16
|
+
* mapped category, and does not fall back to a parametric envelope.
|
|
17
|
+
* Conventional archetype walls and railings stay specification-authoritative
|
|
18
|
+
* and do not require an evaluator. When present, civil walls and railings
|
|
19
|
+
* compare the evaluated Body with their post-opening parametric Body and
|
|
20
|
+
* retain the authored Body when they differ. Supplying this option does not
|
|
21
|
+
* opt unsupported products into the proxy fallback.
|
|
15
22
|
*/
|
|
16
23
|
readonly bodyEvaluator?: csg.Evaluator | undefined;
|
|
17
24
|
/**
|
|
@@ -44,6 +51,11 @@ export interface FamiliesBimResult {
|
|
|
44
51
|
*/
|
|
45
52
|
readonly proxied: readonly ProxiedElement[];
|
|
46
53
|
}
|
|
54
|
+
export interface FamiliesAdapterTestHooks {
|
|
55
|
+
readonly afterCivilProductBody?: ((model: BimModel, localId: LocalId, element: ResolvedElement) => void) | undefined;
|
|
56
|
+
}
|
|
57
|
+
/** Package-internal deterministic failure seam for projection ownership tests. */
|
|
58
|
+
export declare function setFamiliesAdapterTestHooksForTesting(hooks: FamiliesAdapterTestHooks | null): void;
|
|
47
59
|
/**
|
|
48
60
|
* Project a resolved families tree into an eager BimModel. The caller owns
|
|
49
61
|
* the returned model (`using`); families stays domain-neutral — this adapter
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Result, Solid, ValidSolid, csg } from 'brepjs';
|
|
2
|
+
import { ResolvedElement } from 'brepjs-families';
|
|
3
|
+
import { BimError } from './errors/bimError.js';
|
|
4
|
+
import { ProductBody } from './types/productBody.js';
|
|
5
|
+
import { Frame } from './placementFrame.js';
|
|
6
|
+
type ExactProductBody = Extract<ProductBody, {
|
|
7
|
+
readonly kind: 'EXACT';
|
|
8
|
+
}>;
|
|
9
|
+
type ParametricProductBody = Extract<ProductBody, {
|
|
10
|
+
readonly kind: 'PARAMETRIC';
|
|
11
|
+
}>;
|
|
12
|
+
export type CivilProductBodySelection = {
|
|
13
|
+
readonly kind: 'PARAMETRIC';
|
|
14
|
+
} | {
|
|
15
|
+
readonly kind: 'EXACT';
|
|
16
|
+
readonly body: ExactProductBody;
|
|
17
|
+
};
|
|
18
|
+
export interface CivilProductBodyInput {
|
|
19
|
+
readonly element: ResolvedElement;
|
|
20
|
+
readonly category: 'WALL' | 'RAILING';
|
|
21
|
+
readonly evaluator: csg.Evaluator;
|
|
22
|
+
readonly productWorldFrame: Frame;
|
|
23
|
+
/** Borrowed model-owned comparison candidate after registered openings. */
|
|
24
|
+
readonly parametricBody: ParametricProductBody;
|
|
25
|
+
}
|
|
26
|
+
export interface FamiliesProductBodyTestHooks {
|
|
27
|
+
readonly afterCopy?: ((itemIndex: number, solid: Solid, source: Solid) => void) | undefined;
|
|
28
|
+
readonly beforeLocalize?: ((itemIndex: number, solid: ValidSolid) => void) | undefined;
|
|
29
|
+
readonly afterLocalized?: ((itemIndex: number, solid: ValidSolid) => void) | undefined;
|
|
30
|
+
readonly beforeCoincidence?: ((exact: ExactProductBody, parametric: ParametricProductBody) => void) | undefined;
|
|
31
|
+
}
|
|
32
|
+
/** Package-internal deterministic failure seams for exact ownership tests. */
|
|
33
|
+
export declare function setFamiliesProductBodyTestHooksForTesting(hooks: FamiliesProductBodyTestHooks | null): void;
|
|
34
|
+
/**
|
|
35
|
+
* Evaluates a civil Product Body, clones and localizes every borrowed source,
|
|
36
|
+
* then proves whether the registered-opening parametric Body is coincident.
|
|
37
|
+
* An EXACT result is caller-owned until takeExactProductBody() succeeds.
|
|
38
|
+
*/
|
|
39
|
+
export declare function selectCivilProductBody(input: CivilProductBodyInput): Result<CivilProductBodySelection, BimError>;
|
|
40
|
+
export {};
|