brepjs-bim 0.18.0 → 0.20.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.
@@ -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;
@@ -27652,27 +27652,27 @@ var GEOMETRY_PROPS = /* @__PURE__ */ new Set([
27652
27652
  "psets"
27653
27653
  ]);
27654
27654
  var SPEC_ROUTES = {
27655
- Wall: {
27655
+ wall: {
27656
27656
  parse: parseWallSpec,
27657
27657
  add: (m, spec, key) => m.addWall(spec, { stableKey: key })
27658
27658
  },
27659
- Slab: {
27659
+ slab: {
27660
27660
  parse: parseSlabSpec,
27661
27661
  add: (m, spec, key) => m.addSlab(spec, { stableKey: key })
27662
27662
  },
27663
- Column: {
27663
+ column: {
27664
27664
  parse: parseColumnSpec,
27665
27665
  add: (m, spec, key) => m.addColumn(spec, { stableKey: key })
27666
27666
  },
27667
- Beam: {
27667
+ beam: {
27668
27668
  parse: parseBeamSpec,
27669
27669
  add: (m, spec, key) => m.addBeam(spec, { stableKey: key })
27670
27670
  },
27671
- Roof: {
27671
+ roof: {
27672
27672
  parse: parseRoofSpec,
27673
27673
  add: (m, spec, key) => m.addRoof(spec, { stableKey: key })
27674
27674
  },
27675
- Stair: {
27675
+ stair: {
27676
27676
  parse: parseStairSpec,
27677
27677
  add: (m, spec, key) => m.addStair(spec, { stableKey: key }),
27678
27678
  input: stairSpecInput
@@ -27707,8 +27707,28 @@ function stairSpecInput(el) {
27707
27707
  })
27708
27708
  };
27709
27709
  }
27710
- function specRoute(type) {
27711
- return Object.hasOwn(SPEC_ROUTES, type) ? SPEC_ROUTES[type] : void 0;
27710
+ /**
27711
+ * Families predating `archetype` are routed by their display name. Keeping
27712
+ * this fallback makes the declaration purely additive, at the cost of the
27713
+ * original trap surviving for undeclared families: rename one and it stops
27714
+ * routing.
27715
+ */
27716
+ var NAME_ARCHETYPES = {
27717
+ Storey: "storey",
27718
+ Wall: "wall",
27719
+ Slab: "slab",
27720
+ Column: "column",
27721
+ Beam: "beam",
27722
+ Roof: "roof",
27723
+ Stair: "stair",
27724
+ Door: "door",
27725
+ Window: "window"
27726
+ };
27727
+ function archetypeFor(el) {
27728
+ return el.archetype ?? NAME_ARCHETYPES[el.type];
27729
+ }
27730
+ function specRoute(archetype) {
27731
+ return archetype !== void 0 && Object.hasOwn(SPEC_ROUTES, archetype) ? SPEC_ROUTES[archetype] : void 0;
27712
27732
  }
27713
27733
  /** Total of the resolved geometry's OUTER literal translate chain. The
27714
27734
  * transform vocabulary is translate-only, so frame differences are exact
@@ -27816,17 +27836,48 @@ function collectSpecProps(el) {
27816
27836
  return out;
27817
27837
  }
27818
27838
  function addFill(model, fill, input, identity) {
27819
- if (fill.type === "Door") {
27839
+ const archetype = archetypeFor(fill);
27840
+ if (archetype === "door") {
27820
27841
  const parsed = parseDoorSpec(input);
27821
27842
  if (!parsed.ok) return parsed;
27822
27843
  return model.addDoor(parsed.value, identity);
27823
27844
  }
27824
- if (fill.type === "Window") {
27845
+ if (archetype === "window") {
27825
27846
  const parsed = parseWindowSpec(input);
27826
27847
  if (!parsed.ok) return parsed;
27827
27848
  return model.addWindow(parsed.value, identity);
27828
27849
  }
27829
- return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_FILL", `familiesToBim: no fill mapping for element type '${fill.type}' at '${fill.keyPath}'`));
27850
+ return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_FILL", `familiesToBim: no fill mapping for element type '${fill.type}' at '${fill.keyPath}' (archetype: ${archetype ?? "none"}) — a filler needs archetype: 'door' or 'window'`));
27851
+ }
27852
+ /** Materialize an unrouted element's IR and add it as a proxy. The body is
27853
+ * authoritative for a proxy (no parametric spec to diverge from), so baked
27854
+ * transforms — rotations included — are fine here. */
27855
+ function addProxyElement(model, el, evaluator) {
27856
+ const evaluated = evaluator.evaluate(el.geometry);
27857
+ 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));
27858
+ let source = evaluated.value;
27859
+ if (!(0, brepjs.isSolid)(source)) {
27860
+ const solids = (0, brepjs.getSolids)(source);
27861
+ const only = solids.length === 1 ? solids[0] : void 0;
27862
+ 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`));
27863
+ source = only;
27864
+ }
27865
+ const copy = (0, brepjs.clone)(source);
27866
+ if (!copy.ok) return (0, brepjs.err)(specError("FAMILIES_PROXY_EVAL_FAILED", `familiesToBim: '${el.keyPath}' could not copy the materialized body`, copy.error));
27867
+ const valid = (0, brepjs.validSolid)(copy.value);
27868
+ if (!valid.ok) {
27869
+ copy.value[Symbol.dispose]();
27870
+ return (0, brepjs.err)(specError("FAMILIES_PROXY_INVALID", `familiesToBim: '${el.keyPath}' materialized to an invalid solid: ${valid.error}`));
27871
+ }
27872
+ const nameAttr = el.attributes["name"];
27873
+ const materialProp = el.props["materialName"];
27874
+ const specProps = collectSpecProps(el);
27875
+ return model.addProxy({
27876
+ name: typeof nameAttr === "string" ? nameAttr : el.type,
27877
+ solid: valid.value,
27878
+ materialName: typeof materialProp === "string" ? materialProp : specProps["materialName"],
27879
+ customProperties: specProps["customProperties"]
27880
+ }, { stableKey: el.keyPath });
27830
27881
  }
27831
27882
  /** Map a wall's synthesized Opening children onto addDoor/addWindow. The
27832
27883
  * wall-relative offsets come from the void geometry's frame minus the host's:
@@ -27900,11 +27951,14 @@ function familiesToBim(root, options) {
27900
27951
  if (project !== null) model.aggregate(project.localId, siteResult.value);
27901
27952
  model.aggregate(siteResult.value, buildingId);
27902
27953
  const idByKeyPath = /* @__PURE__ */ new Map();
27954
+ const proxied = [];
27903
27955
  const walk = (el, storeyId, rotated) => {
27904
27956
  const rotatedHere = rotated || hasRotateOp(el);
27957
+ let proxiedHere = false;
27905
27958
  let containerId = storeyId;
27906
- const route = specRoute(el.type);
27907
- if (el.type === "Storey") {
27959
+ const archetype = archetypeFor(el);
27960
+ const route = specRoute(archetype);
27961
+ if (archetype === "storey") {
27908
27962
  const keyed = requireKeyed(el);
27909
27963
  if (!keyed.ok) return keyed;
27910
27964
  const storeyResult = model.addStorey({
@@ -27929,16 +27983,31 @@ function familiesToBim(root, options) {
27929
27983
  const added = route.add(model, parsed.value, el.keyPath);
27930
27984
  if (!added.ok) return added;
27931
27985
  idByKeyPath.set(el.keyPath, added.value);
27932
- if (containerId === null) return (0, brepjs.err)(specError("FAMILIES_NO_STOREY", `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment`));
27986
+ if (containerId === null) return (0, brepjs.err)(specError("FAMILIES_NO_STOREY", `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment; a container family needs archetype: 'storey' to be recognised under any name`));
27933
27987
  model.placeIn(added.value, containerId);
27934
- if (el.type === "Wall") {
27988
+ if (archetype === "wall") {
27935
27989
  const opened = addOpenings(model, el, added.value, containerId, idByKeyPath);
27936
27990
  if (!opened.ok) return opened;
27937
27991
  }
27938
27992
  } 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`));
27939
- else if (el.type !== "Group" && el.geometry.kind !== "Empty") return (0, brepjs.err)(specError("FAMILIES_UNSUPPORTED_TYPE", `familiesToBim: no spec mapping for element type '${el.type}' at '${el.keyPath}'`));
27993
+ else if (el.type !== "Group" && el.geometry.kind !== "Empty") {
27994
+ 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}' (archetype: ${el.archetype ?? "none"}) — routing is by archetype, so declare one on the family, add a spec route, or pass proxyEvaluator to export it as an IfcBuildingElementProxy`));
27995
+ const keyed = requireKeyed(el);
27996
+ if (!keyed.ok) return keyed;
27997
+ if (containerId === null) return (0, brepjs.err)(specError("FAMILIES_NO_STOREY", `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment; a container family needs archetype: 'storey' to be recognised under any name`));
27998
+ const added = addProxyElement(model, el, options.proxyEvaluator);
27999
+ if (!added.ok) return added;
28000
+ model.placeIn(added.value, containerId);
28001
+ idByKeyPath.set(el.keyPath, added.value);
28002
+ proxied.push({
28003
+ keyPath: el.keyPath,
28004
+ type: el.type,
28005
+ archetype: el.archetype
28006
+ });
28007
+ proxiedHere = true;
28008
+ }
27940
28009
  for (const child of el.children) {
27941
- if (el.type === "Wall" && child.type === "Opening") continue;
28010
+ if ((archetype === "wall" || proxiedHere) && child.type === "Opening") continue;
27942
28011
  const r = walk(child, containerId, rotatedHere);
27943
28012
  if (!r.ok) return r;
27944
28013
  }
@@ -27951,7 +28020,8 @@ function familiesToBim(root, options) {
27951
28020
  }
27952
28021
  return (0, brepjs.ok)({
27953
28022
  model,
27954
- idByKeyPath
28023
+ idByKeyPath,
28024
+ proxied
27955
28025
  });
27956
28026
  }
27957
28027
  //#endregion
@@ -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;
@@ -27629,27 +27629,27 @@ var GEOMETRY_PROPS = /* @__PURE__ */ new Set([
27629
27629
  "psets"
27630
27630
  ]);
27631
27631
  var SPEC_ROUTES = {
27632
- Wall: {
27632
+ wall: {
27633
27633
  parse: parseWallSpec,
27634
27634
  add: (m, spec, key) => m.addWall(spec, { stableKey: key })
27635
27635
  },
27636
- Slab: {
27636
+ slab: {
27637
27637
  parse: parseSlabSpec,
27638
27638
  add: (m, spec, key) => m.addSlab(spec, { stableKey: key })
27639
27639
  },
27640
- Column: {
27640
+ column: {
27641
27641
  parse: parseColumnSpec,
27642
27642
  add: (m, spec, key) => m.addColumn(spec, { stableKey: key })
27643
27643
  },
27644
- Beam: {
27644
+ beam: {
27645
27645
  parse: parseBeamSpec,
27646
27646
  add: (m, spec, key) => m.addBeam(spec, { stableKey: key })
27647
27647
  },
27648
- Roof: {
27648
+ roof: {
27649
27649
  parse: parseRoofSpec,
27650
27650
  add: (m, spec, key) => m.addRoof(spec, { stableKey: key })
27651
27651
  },
27652
- Stair: {
27652
+ stair: {
27653
27653
  parse: parseStairSpec,
27654
27654
  add: (m, spec, key) => m.addStair(spec, { stableKey: key }),
27655
27655
  input: stairSpecInput
@@ -27684,8 +27684,28 @@ function stairSpecInput(el) {
27684
27684
  })
27685
27685
  };
27686
27686
  }
27687
- function specRoute(type) {
27688
- return Object.hasOwn(SPEC_ROUTES, type) ? SPEC_ROUTES[type] : void 0;
27687
+ /**
27688
+ * Families predating `archetype` are routed by their display name. Keeping
27689
+ * this fallback makes the declaration purely additive, at the cost of the
27690
+ * original trap surviving for undeclared families: rename one and it stops
27691
+ * routing.
27692
+ */
27693
+ var NAME_ARCHETYPES = {
27694
+ Storey: "storey",
27695
+ Wall: "wall",
27696
+ Slab: "slab",
27697
+ Column: "column",
27698
+ Beam: "beam",
27699
+ Roof: "roof",
27700
+ Stair: "stair",
27701
+ Door: "door",
27702
+ Window: "window"
27703
+ };
27704
+ function archetypeFor(el) {
27705
+ return el.archetype ?? NAME_ARCHETYPES[el.type];
27706
+ }
27707
+ function specRoute(archetype) {
27708
+ return archetype !== void 0 && Object.hasOwn(SPEC_ROUTES, archetype) ? SPEC_ROUTES[archetype] : void 0;
27689
27709
  }
27690
27710
  /** Total of the resolved geometry's OUTER literal translate chain. The
27691
27711
  * transform vocabulary is translate-only, so frame differences are exact
@@ -27793,17 +27813,48 @@ function collectSpecProps(el) {
27793
27813
  return out;
27794
27814
  }
27795
27815
  function addFill(model, fill, input, identity) {
27796
- if (fill.type === "Door") {
27816
+ const archetype = archetypeFor(fill);
27817
+ if (archetype === "door") {
27797
27818
  const parsed = parseDoorSpec(input);
27798
27819
  if (!parsed.ok) return parsed;
27799
27820
  return model.addDoor(parsed.value, identity);
27800
27821
  }
27801
- if (fill.type === "Window") {
27822
+ if (archetype === "window") {
27802
27823
  const parsed = parseWindowSpec(input);
27803
27824
  if (!parsed.ok) return parsed;
27804
27825
  return model.addWindow(parsed.value, identity);
27805
27826
  }
27806
- return err(specError("FAMILIES_UNSUPPORTED_FILL", `familiesToBim: no fill mapping for element type '${fill.type}' at '${fill.keyPath}'`));
27827
+ return err(specError("FAMILIES_UNSUPPORTED_FILL", `familiesToBim: no fill mapping for element type '${fill.type}' at '${fill.keyPath}' (archetype: ${archetype ?? "none"}) — a filler needs archetype: 'door' or 'window'`));
27828
+ }
27829
+ /** Materialize an unrouted element's IR and add it as a proxy. The body is
27830
+ * authoritative for a proxy (no parametric spec to diverge from), so baked
27831
+ * transforms — rotations included — are fine here. */
27832
+ function addProxyElement(model, el, evaluator) {
27833
+ const evaluated = evaluator.evaluate(el.geometry);
27834
+ 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));
27835
+ let source = evaluated.value;
27836
+ if (!isSolid(source)) {
27837
+ const solids = getSolids(source);
27838
+ const only = solids.length === 1 ? solids[0] : void 0;
27839
+ 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`));
27840
+ source = only;
27841
+ }
27842
+ const copy = clone(source);
27843
+ if (!copy.ok) return err(specError("FAMILIES_PROXY_EVAL_FAILED", `familiesToBim: '${el.keyPath}' could not copy the materialized body`, copy.error));
27844
+ const valid = validSolid(copy.value);
27845
+ if (!valid.ok) {
27846
+ copy.value[Symbol.dispose]();
27847
+ return err(specError("FAMILIES_PROXY_INVALID", `familiesToBim: '${el.keyPath}' materialized to an invalid solid: ${valid.error}`));
27848
+ }
27849
+ const nameAttr = el.attributes["name"];
27850
+ const materialProp = el.props["materialName"];
27851
+ const specProps = collectSpecProps(el);
27852
+ return model.addProxy({
27853
+ name: typeof nameAttr === "string" ? nameAttr : el.type,
27854
+ solid: valid.value,
27855
+ materialName: typeof materialProp === "string" ? materialProp : specProps["materialName"],
27856
+ customProperties: specProps["customProperties"]
27857
+ }, { stableKey: el.keyPath });
27807
27858
  }
27808
27859
  /** Map a wall's synthesized Opening children onto addDoor/addWindow. The
27809
27860
  * wall-relative offsets come from the void geometry's frame minus the host's:
@@ -27877,11 +27928,14 @@ function familiesToBim(root, options) {
27877
27928
  if (project !== null) model.aggregate(project.localId, siteResult.value);
27878
27929
  model.aggregate(siteResult.value, buildingId);
27879
27930
  const idByKeyPath = /* @__PURE__ */ new Map();
27931
+ const proxied = [];
27880
27932
  const walk = (el, storeyId, rotated) => {
27881
27933
  const rotatedHere = rotated || hasRotateOp(el);
27934
+ let proxiedHere = false;
27882
27935
  let containerId = storeyId;
27883
- const route = specRoute(el.type);
27884
- if (el.type === "Storey") {
27936
+ const archetype = archetypeFor(el);
27937
+ const route = specRoute(archetype);
27938
+ if (archetype === "storey") {
27885
27939
  const keyed = requireKeyed(el);
27886
27940
  if (!keyed.ok) return keyed;
27887
27941
  const storeyResult = model.addStorey({
@@ -27906,16 +27960,31 @@ function familiesToBim(root, options) {
27906
27960
  const added = route.add(model, parsed.value, el.keyPath);
27907
27961
  if (!added.ok) return added;
27908
27962
  idByKeyPath.set(el.keyPath, added.value);
27909
- if (containerId === null) return err(specError("FAMILIES_NO_STOREY", `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment`));
27963
+ if (containerId === null) return err(specError("FAMILIES_NO_STOREY", `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment; a container family needs archetype: 'storey' to be recognised under any name`));
27910
27964
  model.placeIn(added.value, containerId);
27911
- if (el.type === "Wall") {
27965
+ if (archetype === "wall") {
27912
27966
  const opened = addOpenings(model, el, added.value, containerId, idByKeyPath);
27913
27967
  if (!opened.ok) return opened;
27914
27968
  }
27915
27969
  } 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`));
27916
- else if (el.type !== "Group" && el.geometry.kind !== "Empty") return err(specError("FAMILIES_UNSUPPORTED_TYPE", `familiesToBim: no spec mapping for element type '${el.type}' at '${el.keyPath}'`));
27970
+ else if (el.type !== "Group" && el.geometry.kind !== "Empty") {
27971
+ if (options.proxyEvaluator === void 0) return err(specError("FAMILIES_UNSUPPORTED_TYPE", `familiesToBim: no spec mapping for element type '${el.type}' at '${el.keyPath}' (archetype: ${el.archetype ?? "none"}) — routing is by archetype, so declare one on the family, 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 err(specError("FAMILIES_NO_STOREY", `familiesToBim: '${el.keyPath}' has no Storey ancestor — IFC elements need spatial containment; a container family needs archetype: 'storey' to be recognised under any name`));
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.push({
27980
+ keyPath: el.keyPath,
27981
+ type: el.type,
27982
+ archetype: el.archetype
27983
+ });
27984
+ proxiedHere = true;
27985
+ }
27917
27986
  for (const child of el.children) {
27918
- if (el.type === "Wall" && child.type === "Opening") continue;
27987
+ if ((archetype === "wall" || proxiedHere) && child.type === "Opening") continue;
27919
27988
  const r = walk(child, containerId, rotatedHere);
27920
27989
  if (!r.ok) return r;
27921
27990
  }
@@ -27928,7 +27997,8 @@ function familiesToBim(root, options) {
27928
27997
  }
27929
27998
  return ok({
27930
27999
  model,
27931
- idByKeyPath
28000
+ idByKeyPath,
28001
+ proxied
27932
28002
  });
27933
28003
  }
27934
28004
  //#endregion
@@ -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,11 +8,34 @@ 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;
20
+ }
21
+ export interface ProxiedElement {
22
+ readonly keyPath: string;
23
+ /** The family's display name, as resolved. */
24
+ readonly type: string;
25
+ readonly archetype: string | undefined;
11
26
  }
12
27
  export interface FamiliesBimResult {
13
28
  readonly model: BimModel;
14
29
  /** LocalId per geometry-bearing families key path. */
15
30
  readonly idByKeyPath: ReadonlyMap<string, LocalId>;
31
+ /**
32
+ * Elements exported as IfcBuildingElementProxy because no spec route
33
+ * matched, in walk order. Only ever non-empty when `proxyEvaluator` is set:
34
+ * without it an unrouted element is a hard error instead. A renamed family
35
+ * that has lost its routing lands here rather than in the file as the type
36
+ * you meant, so check this before trusting an export.
37
+ */
38
+ readonly proxied: readonly ProxiedElement[];
16
39
  }
17
40
  /**
18
41
  * Project a resolved families tree into an eager BimModel. The caller owns
package/dist/index.d.ts CHANGED
@@ -97,4 +97,4 @@ export type { IdsDocument, IdsSpecification, IdsFacet, IdsRestriction, IdsCardin
97
97
  export { serializeBcfFiles, parseBcfFiles } from './bcf/index.js';
98
98
  export type { BcfColoring, BcfComment, BcfComponent, BcfComponents, BcfContainerData, BcfFiles, BcfProject, BcfTopic, BcfVersion, BcfViewpoint, BcfVisibility, } from './bcf/index.js';
99
99
  export { bcfError, idsError } from './errors/bimError.js';
100
- export { familiesToBim, type FamiliesToBimOptions, type FamiliesBimResult, } from './familiesAdapter.js';
100
+ export { familiesToBim, type FamiliesToBimOptions, type FamiliesBimResult, type ProxiedElement, } from './familiesAdapter.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brepjs-bim",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "BIM layer for brepjs — IFC4-aligned parametric building elements",
5
5
  "keywords": [
6
6
  "bim",