brepjs-bim 0.17.2 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/brepjs-bim.cjs +67 -14
- package/dist/brepjs-bim.js +68 -15
- package/dist/familiesAdapter.d.ts +10 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,13 @@ and serializes the result to a valid **IFC-SPF** file — with a matching import
|
|
|
14
14
|
Pipeline: **author spec → `BimModel` (typed element + brepjs geometry) → spatial structure +
|
|
15
15
|
property sets + classification → export IFC / COBie, validate, round-trip.**
|
|
16
16
|
|
|
17
|
+
The declarative entry point is
|
|
18
|
+
[`brepjs-families`](https://www.npmjs.com/package/brepjs-families): author the building as a tree of
|
|
19
|
+
prop-driven components, then `familiesToBim(tree, { project })` projects it onto a `BimModel` with
|
|
20
|
+
stable GlobalIds derived from component key paths. Hand-authoring specs against `BimModel` remains
|
|
21
|
+
the low-level path (and covers elements the declarative route doesn't yet). See the
|
|
22
|
+
[Declarative Models guide](https://brepjs.dev/families/overview).
|
|
23
|
+
|
|
17
24
|
## Scope
|
|
18
25
|
|
|
19
26
|
Parametric authoring of the common IFC4 building elements plus the data layers that make a model
|
package/dist/brepjs-bim.cjs
CHANGED
|
@@ -535,7 +535,7 @@ var propertyKeyTypes = /* @__PURE__*/ new Set([
|
|
|
535
535
|
function escapeRegex(str) {
|
|
536
536
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
537
537
|
}
|
|
538
|
-
function clone(inst, def, params) {
|
|
538
|
+
function clone$1(inst, def, params) {
|
|
539
539
|
const cl = new inst._zod.constr(def ?? inst._zod.def);
|
|
540
540
|
if (!def || params?.parent) cl._zod.parent = inst;
|
|
541
541
|
return cl;
|
|
@@ -571,7 +571,7 @@ function pick(schema, mask) {
|
|
|
571
571
|
const currDef = schema._zod.def;
|
|
572
572
|
const checks = currDef.checks;
|
|
573
573
|
if (checks && checks.length > 0) throw new Error(".pick() cannot be used on object schemas containing refinements");
|
|
574
|
-
return clone(schema, mergeDefs(schema._zod.def, {
|
|
574
|
+
return clone$1(schema, mergeDefs(schema._zod.def, {
|
|
575
575
|
get shape() {
|
|
576
576
|
const newShape = {};
|
|
577
577
|
for (const key in mask) {
|
|
@@ -589,7 +589,7 @@ function omit(schema, mask) {
|
|
|
589
589
|
const currDef = schema._zod.def;
|
|
590
590
|
const checks = currDef.checks;
|
|
591
591
|
if (checks && checks.length > 0) throw new Error(".omit() cannot be used on object schemas containing refinements");
|
|
592
|
-
return clone(schema, mergeDefs(schema._zod.def, {
|
|
592
|
+
return clone$1(schema, mergeDefs(schema._zod.def, {
|
|
593
593
|
get shape() {
|
|
594
594
|
const newShape = { ...schema._zod.def.shape };
|
|
595
595
|
for (const key in mask) {
|
|
@@ -610,7 +610,7 @@ function extend(schema, shape) {
|
|
|
610
610
|
const existingShape = schema._zod.def.shape;
|
|
611
611
|
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.");
|
|
612
612
|
}
|
|
613
|
-
return clone(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
613
|
+
return clone$1(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
614
614
|
const _shape = {
|
|
615
615
|
...schema._zod.def.shape,
|
|
616
616
|
...shape
|
|
@@ -621,7 +621,7 @@ function extend(schema, shape) {
|
|
|
621
621
|
}
|
|
622
622
|
function safeExtend(schema, shape) {
|
|
623
623
|
if (!isPlainObject(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
|
|
624
|
-
return clone(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
624
|
+
return clone$1(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
625
625
|
const _shape = {
|
|
626
626
|
...schema._zod.def.shape,
|
|
627
627
|
...shape
|
|
@@ -632,7 +632,7 @@ function safeExtend(schema, shape) {
|
|
|
632
632
|
}
|
|
633
633
|
function merge(a, b) {
|
|
634
634
|
if (a._zod.def.checks?.length) throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.");
|
|
635
|
-
return clone(a, mergeDefs(a._zod.def, {
|
|
635
|
+
return clone$1(a, mergeDefs(a._zod.def, {
|
|
636
636
|
get shape() {
|
|
637
637
|
const _shape = {
|
|
638
638
|
...a._zod.def.shape,
|
|
@@ -650,7 +650,7 @@ function merge(a, b) {
|
|
|
650
650
|
function partial(Class, schema, mask) {
|
|
651
651
|
const checks = schema._zod.def.checks;
|
|
652
652
|
if (checks && checks.length > 0) throw new Error(".partial() cannot be used on object schemas containing refinements");
|
|
653
|
-
return clone(schema, mergeDefs(schema._zod.def, {
|
|
653
|
+
return clone$1(schema, mergeDefs(schema._zod.def, {
|
|
654
654
|
get shape() {
|
|
655
655
|
const oldShape = schema._zod.def.shape;
|
|
656
656
|
const shape = { ...oldShape };
|
|
@@ -673,7 +673,7 @@ function partial(Class, schema, mask) {
|
|
|
673
673
|
}));
|
|
674
674
|
}
|
|
675
675
|
function required(Class, schema, mask) {
|
|
676
|
-
return clone(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
676
|
+
return clone$1(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
677
677
|
const oldShape = schema._zod.def.shape;
|
|
678
678
|
const shape = { ...oldShape };
|
|
679
679
|
if (mask) for (const key in mask) {
|
|
@@ -4042,7 +4042,7 @@ var ZodType = /*@__PURE__*/ $constructor("ZodType", (inst, def) => {
|
|
|
4042
4042
|
return this.check(...chks);
|
|
4043
4043
|
},
|
|
4044
4044
|
clone(def, params) {
|
|
4045
|
-
return clone(this, def, params);
|
|
4045
|
+
return clone$1(this, def, params);
|
|
4046
4046
|
},
|
|
4047
4047
|
brand() {
|
|
4048
4048
|
return this;
|
|
@@ -27735,6 +27735,16 @@ function peelTranslates(node) {
|
|
|
27735
27735
|
moved
|
|
27736
27736
|
};
|
|
27737
27737
|
}
|
|
27738
|
+
/** True when the element's transform PROP carries a rotation. The spec path
|
|
27739
|
+
* folds only translations into IfcLocalPlacement (walls orient via `axisX`),
|
|
27740
|
+
* so a tRotate placement would export un-rotated while the viewport shows it
|
|
27741
|
+
* rotated — reject instead of diverging. Rotations a family render bakes
|
|
27742
|
+
* into its own body geometry (e.g. a circular beam oriented along axisX) are
|
|
27743
|
+
* fine: the spec rebuilds that body parametrically from props. */
|
|
27744
|
+
function hasRotateOp(el) {
|
|
27745
|
+
const ops = el.props["transform"];
|
|
27746
|
+
return Array.isArray(ops) && ops.some((op) => typeof op === "object" && op !== null && op.op === "rotate");
|
|
27747
|
+
}
|
|
27738
27748
|
/** Fold the resolved geometry's outer translate chain into the spec placement
|
|
27739
27749
|
* origin, so IfcLocalPlacement matches the IR world frame no matter where the
|
|
27740
27750
|
* transform came from (family-internal or prop-level). */
|
|
@@ -27818,6 +27828,36 @@ function addFill(model, fill, input, identity) {
|
|
|
27818
27828
|
}
|
|
27819
27829
|
return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_FILL", `familiesToBim: no fill mapping for element type '${fill.type}' at '${fill.keyPath}'`));
|
|
27820
27830
|
}
|
|
27831
|
+
/** Materialize an unrouted element's IR and add it as a proxy. The body is
|
|
27832
|
+
* authoritative for a proxy (no parametric spec to diverge from), so baked
|
|
27833
|
+
* transforms — rotations included — are fine here. */
|
|
27834
|
+
function addProxyElement(model, el, evaluator) {
|
|
27835
|
+
const evaluated = evaluator.evaluate(el.geometry);
|
|
27836
|
+
if (!evaluated.ok) return (0, brepjs.err)(specError("FAMILIES_PROXY_EVAL_FAILED", `familiesToBim: '${el.keyPath}' failed to materialize for the proxy route: ${evaluated.error.message}`, evaluated.error));
|
|
27837
|
+
let source = evaluated.value;
|
|
27838
|
+
if (!(0, brepjs.isSolid)(source)) {
|
|
27839
|
+
const solids = (0, brepjs.getSolids)(source);
|
|
27840
|
+
const only = solids.length === 1 ? solids[0] : void 0;
|
|
27841
|
+
if (only === void 0) return (0, brepjs.err)(specError("FAMILIES_PROXY_NOT_SOLID", `familiesToBim: '${el.keyPath}' materialized to ${solids.length} solids — a proxy body must be exactly one`));
|
|
27842
|
+
source = only;
|
|
27843
|
+
}
|
|
27844
|
+
const copy = (0, brepjs.clone)(source);
|
|
27845
|
+
if (!copy.ok) return (0, brepjs.err)(specError("FAMILIES_PROXY_EVAL_FAILED", `familiesToBim: '${el.keyPath}' could not copy the materialized body`, copy.error));
|
|
27846
|
+
const valid = (0, brepjs.validSolid)(copy.value);
|
|
27847
|
+
if (!valid.ok) {
|
|
27848
|
+
copy.value[Symbol.dispose]();
|
|
27849
|
+
return (0, brepjs.err)(specError("FAMILIES_PROXY_INVALID", `familiesToBim: '${el.keyPath}' materialized to an invalid solid: ${valid.error}`));
|
|
27850
|
+
}
|
|
27851
|
+
const nameAttr = el.attributes["name"];
|
|
27852
|
+
const materialProp = el.props["materialName"];
|
|
27853
|
+
const specProps = collectSpecProps(el);
|
|
27854
|
+
return model.addProxy({
|
|
27855
|
+
name: typeof nameAttr === "string" ? nameAttr : el.type,
|
|
27856
|
+
solid: valid.value,
|
|
27857
|
+
materialName: typeof materialProp === "string" ? materialProp : specProps["materialName"],
|
|
27858
|
+
customProperties: specProps["customProperties"]
|
|
27859
|
+
}, { stableKey: el.keyPath });
|
|
27860
|
+
}
|
|
27821
27861
|
/** Map a wall's synthesized Opening children onto addDoor/addWindow. The
|
|
27822
27862
|
* wall-relative offsets come from the void geometry's frame minus the host's:
|
|
27823
27863
|
* exact because both carry the same outer host transform. */
|
|
@@ -27890,7 +27930,9 @@ function familiesToBim(root, options) {
|
|
|
27890
27930
|
if (project !== null) model.aggregate(project.localId, siteResult.value);
|
|
27891
27931
|
model.aggregate(siteResult.value, buildingId);
|
|
27892
27932
|
const idByKeyPath = /* @__PURE__ */ new Map();
|
|
27893
|
-
const walk = (el, storeyId) => {
|
|
27933
|
+
const walk = (el, storeyId, rotated) => {
|
|
27934
|
+
const rotatedHere = rotated || hasRotateOp(el);
|
|
27935
|
+
let proxied = false;
|
|
27894
27936
|
let containerId = storeyId;
|
|
27895
27937
|
const route = specRoute(el.type);
|
|
27896
27938
|
if (el.type === "Storey") {
|
|
@@ -27907,6 +27949,7 @@ function familiesToBim(root, options) {
|
|
|
27907
27949
|
} else if (route !== void 0) {
|
|
27908
27950
|
const keyed = requireKeyed(el);
|
|
27909
27951
|
if (!keyed.ok) return keyed;
|
|
27952
|
+
if (rotatedHere) return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_TRANSFORM", `familiesToBim: '${el.keyPath}' carries a rotated placement — the spec path folds only translations into IfcLocalPlacement; orient walls via axisX instead of tRotate`));
|
|
27910
27953
|
const voids = el.props["voids"];
|
|
27911
27954
|
if (Array.isArray(voids)) {
|
|
27912
27955
|
const openings = el.children.filter((c) => c.type === "Opening").length;
|
|
@@ -27924,15 +27967,25 @@ function familiesToBim(root, options) {
|
|
|
27924
27967
|
if (!opened.ok) return opened;
|
|
27925
27968
|
}
|
|
27926
27969
|
} 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`));
|
|
27927
|
-
else if (el.type !== "Group" && el.geometry.kind !== "Empty")
|
|
27970
|
+
else if (el.type !== "Group" && el.geometry.kind !== "Empty") {
|
|
27971
|
+
if (options.proxyEvaluator === void 0) return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_TYPE", `familiesToBim: no spec mapping for element type '${el.type}' at '${el.keyPath}' — add a spec route, or pass proxyEvaluator to export it as an IfcBuildingElementProxy`));
|
|
27972
|
+
const keyed = requireKeyed(el);
|
|
27973
|
+
if (!keyed.ok) return keyed;
|
|
27974
|
+
if (containerId === null) return (0, brepjs.err)(specError("FAMILIES_NO_STOREY", `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment`));
|
|
27975
|
+
const added = addProxyElement(model, el, options.proxyEvaluator);
|
|
27976
|
+
if (!added.ok) return added;
|
|
27977
|
+
model.placeIn(added.value, containerId);
|
|
27978
|
+
idByKeyPath.set(el.keyPath, added.value);
|
|
27979
|
+
proxied = true;
|
|
27980
|
+
}
|
|
27928
27981
|
for (const child of el.children) {
|
|
27929
|
-
if (el.type === "Wall" && child.type === "Opening") continue;
|
|
27930
|
-
const r = walk(child, containerId);
|
|
27982
|
+
if ((el.type === "Wall" || proxied) && child.type === "Opening") continue;
|
|
27983
|
+
const r = walk(child, containerId, rotatedHere);
|
|
27931
27984
|
if (!r.ok) return r;
|
|
27932
27985
|
}
|
|
27933
27986
|
return (0, brepjs.ok)(void 0);
|
|
27934
27987
|
};
|
|
27935
|
-
const walked = walk(root, null);
|
|
27988
|
+
const walked = walk(root, null, false);
|
|
27936
27989
|
if (!walked.ok) {
|
|
27937
27990
|
model[Symbol.dispose]();
|
|
27938
27991
|
return walked;
|
package/dist/brepjs-bim.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addHoles, applyMatrix, autoHeal, box, castShape, convexHull, cut, err, extrude, fuse, getKernel, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, validSolid } from "brepjs";
|
|
1
|
+
import { addHoles, applyMatrix, autoHeal, box, castShape, clone, convexHull, cut, err, extrude, fuse, getKernel, getSolids, isClosedWire, isOk, isPlanarWire, isSolid, isValid, isValidSolid, measureVolume, mesh, ok, outerWire, polygon, revolve, rotate, validSolid } from "brepjs";
|
|
2
2
|
import * as WebIFC from "web-ifc";
|
|
3
3
|
import { Handle, IfcAPI } from "web-ifc";
|
|
4
4
|
//#region src/identity/ifcGuid.ts
|
|
@@ -512,7 +512,7 @@ var propertyKeyTypes = /* @__PURE__*/ new Set([
|
|
|
512
512
|
function escapeRegex(str) {
|
|
513
513
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
514
514
|
}
|
|
515
|
-
function clone(inst, def, params) {
|
|
515
|
+
function clone$1(inst, def, params) {
|
|
516
516
|
const cl = new inst._zod.constr(def ?? inst._zod.def);
|
|
517
517
|
if (!def || params?.parent) cl._zod.parent = inst;
|
|
518
518
|
return cl;
|
|
@@ -548,7 +548,7 @@ function pick(schema, mask) {
|
|
|
548
548
|
const currDef = schema._zod.def;
|
|
549
549
|
const checks = currDef.checks;
|
|
550
550
|
if (checks && checks.length > 0) throw new Error(".pick() cannot be used on object schemas containing refinements");
|
|
551
|
-
return clone(schema, mergeDefs(schema._zod.def, {
|
|
551
|
+
return clone$1(schema, mergeDefs(schema._zod.def, {
|
|
552
552
|
get shape() {
|
|
553
553
|
const newShape = {};
|
|
554
554
|
for (const key in mask) {
|
|
@@ -566,7 +566,7 @@ function omit(schema, mask) {
|
|
|
566
566
|
const currDef = schema._zod.def;
|
|
567
567
|
const checks = currDef.checks;
|
|
568
568
|
if (checks && checks.length > 0) throw new Error(".omit() cannot be used on object schemas containing refinements");
|
|
569
|
-
return clone(schema, mergeDefs(schema._zod.def, {
|
|
569
|
+
return clone$1(schema, mergeDefs(schema._zod.def, {
|
|
570
570
|
get shape() {
|
|
571
571
|
const newShape = { ...schema._zod.def.shape };
|
|
572
572
|
for (const key in mask) {
|
|
@@ -587,7 +587,7 @@ function extend(schema, shape) {
|
|
|
587
587
|
const existingShape = schema._zod.def.shape;
|
|
588
588
|
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.");
|
|
589
589
|
}
|
|
590
|
-
return clone(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
590
|
+
return clone$1(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
591
591
|
const _shape = {
|
|
592
592
|
...schema._zod.def.shape,
|
|
593
593
|
...shape
|
|
@@ -598,7 +598,7 @@ function extend(schema, shape) {
|
|
|
598
598
|
}
|
|
599
599
|
function safeExtend(schema, shape) {
|
|
600
600
|
if (!isPlainObject(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
|
|
601
|
-
return clone(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
601
|
+
return clone$1(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
602
602
|
const _shape = {
|
|
603
603
|
...schema._zod.def.shape,
|
|
604
604
|
...shape
|
|
@@ -609,7 +609,7 @@ function safeExtend(schema, shape) {
|
|
|
609
609
|
}
|
|
610
610
|
function merge(a, b) {
|
|
611
611
|
if (a._zod.def.checks?.length) throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.");
|
|
612
|
-
return clone(a, mergeDefs(a._zod.def, {
|
|
612
|
+
return clone$1(a, mergeDefs(a._zod.def, {
|
|
613
613
|
get shape() {
|
|
614
614
|
const _shape = {
|
|
615
615
|
...a._zod.def.shape,
|
|
@@ -627,7 +627,7 @@ function merge(a, b) {
|
|
|
627
627
|
function partial(Class, schema, mask) {
|
|
628
628
|
const checks = schema._zod.def.checks;
|
|
629
629
|
if (checks && checks.length > 0) throw new Error(".partial() cannot be used on object schemas containing refinements");
|
|
630
|
-
return clone(schema, mergeDefs(schema._zod.def, {
|
|
630
|
+
return clone$1(schema, mergeDefs(schema._zod.def, {
|
|
631
631
|
get shape() {
|
|
632
632
|
const oldShape = schema._zod.def.shape;
|
|
633
633
|
const shape = { ...oldShape };
|
|
@@ -650,7 +650,7 @@ function partial(Class, schema, mask) {
|
|
|
650
650
|
}));
|
|
651
651
|
}
|
|
652
652
|
function required(Class, schema, mask) {
|
|
653
|
-
return clone(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
653
|
+
return clone$1(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
654
654
|
const oldShape = schema._zod.def.shape;
|
|
655
655
|
const shape = { ...oldShape };
|
|
656
656
|
if (mask) for (const key in mask) {
|
|
@@ -4019,7 +4019,7 @@ var ZodType = /*@__PURE__*/ $constructor("ZodType", (inst, def) => {
|
|
|
4019
4019
|
return this.check(...chks);
|
|
4020
4020
|
},
|
|
4021
4021
|
clone(def, params) {
|
|
4022
|
-
return clone(this, def, params);
|
|
4022
|
+
return clone$1(this, def, params);
|
|
4023
4023
|
},
|
|
4024
4024
|
brand() {
|
|
4025
4025
|
return this;
|
|
@@ -27712,6 +27712,16 @@ function peelTranslates(node) {
|
|
|
27712
27712
|
moved
|
|
27713
27713
|
};
|
|
27714
27714
|
}
|
|
27715
|
+
/** True when the element's transform PROP carries a rotation. The spec path
|
|
27716
|
+
* folds only translations into IfcLocalPlacement (walls orient via `axisX`),
|
|
27717
|
+
* so a tRotate placement would export un-rotated while the viewport shows it
|
|
27718
|
+
* rotated — reject instead of diverging. Rotations a family render bakes
|
|
27719
|
+
* into its own body geometry (e.g. a circular beam oriented along axisX) are
|
|
27720
|
+
* fine: the spec rebuilds that body parametrically from props. */
|
|
27721
|
+
function hasRotateOp(el) {
|
|
27722
|
+
const ops = el.props["transform"];
|
|
27723
|
+
return Array.isArray(ops) && ops.some((op) => typeof op === "object" && op !== null && op.op === "rotate");
|
|
27724
|
+
}
|
|
27715
27725
|
/** Fold the resolved geometry's outer translate chain into the spec placement
|
|
27716
27726
|
* origin, so IfcLocalPlacement matches the IR world frame no matter where the
|
|
27717
27727
|
* transform came from (family-internal or prop-level). */
|
|
@@ -27795,6 +27805,36 @@ function addFill(model, fill, input, identity) {
|
|
|
27795
27805
|
}
|
|
27796
27806
|
return err(specError("FAMILIES_UNSUPPORTED_FILL", `familiesToBim: no fill mapping for element type '${fill.type}' at '${fill.keyPath}'`));
|
|
27797
27807
|
}
|
|
27808
|
+
/** Materialize an unrouted element's IR and add it as a proxy. The body is
|
|
27809
|
+
* authoritative for a proxy (no parametric spec to diverge from), so baked
|
|
27810
|
+
* transforms — rotations included — are fine here. */
|
|
27811
|
+
function addProxyElement(model, el, evaluator) {
|
|
27812
|
+
const evaluated = evaluator.evaluate(el.geometry);
|
|
27813
|
+
if (!evaluated.ok) return err(specError("FAMILIES_PROXY_EVAL_FAILED", `familiesToBim: '${el.keyPath}' failed to materialize for the proxy route: ${evaluated.error.message}`, evaluated.error));
|
|
27814
|
+
let source = evaluated.value;
|
|
27815
|
+
if (!isSolid(source)) {
|
|
27816
|
+
const solids = getSolids(source);
|
|
27817
|
+
const only = solids.length === 1 ? solids[0] : void 0;
|
|
27818
|
+
if (only === void 0) return err(specError("FAMILIES_PROXY_NOT_SOLID", `familiesToBim: '${el.keyPath}' materialized to ${solids.length} solids — a proxy body must be exactly one`));
|
|
27819
|
+
source = only;
|
|
27820
|
+
}
|
|
27821
|
+
const copy = clone(source);
|
|
27822
|
+
if (!copy.ok) return err(specError("FAMILIES_PROXY_EVAL_FAILED", `familiesToBim: '${el.keyPath}' could not copy the materialized body`, copy.error));
|
|
27823
|
+
const valid = validSolid(copy.value);
|
|
27824
|
+
if (!valid.ok) {
|
|
27825
|
+
copy.value[Symbol.dispose]();
|
|
27826
|
+
return err(specError("FAMILIES_PROXY_INVALID", `familiesToBim: '${el.keyPath}' materialized to an invalid solid: ${valid.error}`));
|
|
27827
|
+
}
|
|
27828
|
+
const nameAttr = el.attributes["name"];
|
|
27829
|
+
const materialProp = el.props["materialName"];
|
|
27830
|
+
const specProps = collectSpecProps(el);
|
|
27831
|
+
return model.addProxy({
|
|
27832
|
+
name: typeof nameAttr === "string" ? nameAttr : el.type,
|
|
27833
|
+
solid: valid.value,
|
|
27834
|
+
materialName: typeof materialProp === "string" ? materialProp : specProps["materialName"],
|
|
27835
|
+
customProperties: specProps["customProperties"]
|
|
27836
|
+
}, { stableKey: el.keyPath });
|
|
27837
|
+
}
|
|
27798
27838
|
/** Map a wall's synthesized Opening children onto addDoor/addWindow. The
|
|
27799
27839
|
* wall-relative offsets come from the void geometry's frame minus the host's:
|
|
27800
27840
|
* exact because both carry the same outer host transform. */
|
|
@@ -27867,7 +27907,9 @@ function familiesToBim(root, options) {
|
|
|
27867
27907
|
if (project !== null) model.aggregate(project.localId, siteResult.value);
|
|
27868
27908
|
model.aggregate(siteResult.value, buildingId);
|
|
27869
27909
|
const idByKeyPath = /* @__PURE__ */ new Map();
|
|
27870
|
-
const walk = (el, storeyId) => {
|
|
27910
|
+
const walk = (el, storeyId, rotated) => {
|
|
27911
|
+
const rotatedHere = rotated || hasRotateOp(el);
|
|
27912
|
+
let proxied = false;
|
|
27871
27913
|
let containerId = storeyId;
|
|
27872
27914
|
const route = specRoute(el.type);
|
|
27873
27915
|
if (el.type === "Storey") {
|
|
@@ -27884,6 +27926,7 @@ function familiesToBim(root, options) {
|
|
|
27884
27926
|
} else if (route !== void 0) {
|
|
27885
27927
|
const keyed = requireKeyed(el);
|
|
27886
27928
|
if (!keyed.ok) return keyed;
|
|
27929
|
+
if (rotatedHere) return err(specError("FAMILIES_UNSUPPORTED_TRANSFORM", `familiesToBim: '${el.keyPath}' carries a rotated placement — the spec path folds only translations into IfcLocalPlacement; orient walls via axisX instead of tRotate`));
|
|
27887
27930
|
const voids = el.props["voids"];
|
|
27888
27931
|
if (Array.isArray(voids)) {
|
|
27889
27932
|
const openings = el.children.filter((c) => c.type === "Opening").length;
|
|
@@ -27901,15 +27944,25 @@ function familiesToBim(root, options) {
|
|
|
27901
27944
|
if (!opened.ok) return opened;
|
|
27902
27945
|
}
|
|
27903
27946
|
} 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`));
|
|
27904
|
-
else if (el.type !== "Group" && el.geometry.kind !== "Empty")
|
|
27947
|
+
else if (el.type !== "Group" && el.geometry.kind !== "Empty") {
|
|
27948
|
+
if (options.proxyEvaluator === void 0) return err(specError("FAMILIES_UNSUPPORTED_TYPE", `familiesToBim: no spec mapping for element type '${el.type}' at '${el.keyPath}' — add a spec route, or pass proxyEvaluator to export it as an IfcBuildingElementProxy`));
|
|
27949
|
+
const keyed = requireKeyed(el);
|
|
27950
|
+
if (!keyed.ok) return keyed;
|
|
27951
|
+
if (containerId === null) return err(specError("FAMILIES_NO_STOREY", `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment`));
|
|
27952
|
+
const added = addProxyElement(model, el, options.proxyEvaluator);
|
|
27953
|
+
if (!added.ok) return added;
|
|
27954
|
+
model.placeIn(added.value, containerId);
|
|
27955
|
+
idByKeyPath.set(el.keyPath, added.value);
|
|
27956
|
+
proxied = true;
|
|
27957
|
+
}
|
|
27905
27958
|
for (const child of el.children) {
|
|
27906
|
-
if (el.type === "Wall" && child.type === "Opening") continue;
|
|
27907
|
-
const r = walk(child, containerId);
|
|
27959
|
+
if ((el.type === "Wall" || proxied) && child.type === "Opening") continue;
|
|
27960
|
+
const r = walk(child, containerId, rotatedHere);
|
|
27908
27961
|
if (!r.ok) return r;
|
|
27909
27962
|
}
|
|
27910
27963
|
return ok(void 0);
|
|
27911
27964
|
};
|
|
27912
|
-
const walked = walk(root, null);
|
|
27965
|
+
const walked = walk(root, null, false);
|
|
27913
27966
|
if (!walked.ok) {
|
|
27914
27967
|
model[Symbol.dispose]();
|
|
27915
27968
|
return walked;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Result } from 'brepjs';
|
|
1
|
+
import { Result, csg } from 'brepjs';
|
|
2
2
|
import { ResolvedElement } from 'brepjs-families';
|
|
3
3
|
import { BimModel } from './model/bimModel.js';
|
|
4
4
|
import { LocalId } from './identity/localId.js';
|
|
@@ -8,6 +8,15 @@ export interface FamiliesToBimOptions {
|
|
|
8
8
|
readonly project: ProjectSpec;
|
|
9
9
|
readonly siteName?: string | undefined;
|
|
10
10
|
readonly buildingName?: string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Enables the proxy route: an unrouted geometry-bearing element is
|
|
13
|
+
* materialized through this evaluator and exported as an
|
|
14
|
+
* IfcBuildingElementProxy (tessellated, world-frame body). Without it,
|
|
15
|
+
* unrouted types stay a hard FAMILIES_UNSUPPORTED_TYPE error. The
|
|
16
|
+
* evaluator's handles stay borrowed; the adapter clones what it hands the
|
|
17
|
+
* model.
|
|
18
|
+
*/
|
|
19
|
+
readonly proxyEvaluator?: csg.Evaluator | undefined;
|
|
11
20
|
}
|
|
12
21
|
export interface FamiliesBimResult {
|
|
13
22
|
readonly model: BimModel;
|