brepjs-bim 0.17.0 → 0.17.2

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.
@@ -7393,8 +7393,10 @@ function disposeAll(solids) {
7393
7393
  *
7394
7394
  * Stairs and ramps carry no element solid (`.geometry` is null), so flight
7395
7395
  * solids are built from `spec.flights` and placed per flight. Curtain walls
7396
- * return placed panels + mullions. Elements with no solid geometry
7397
- * (doors/windows/groups/spatial) return an empty array.
7396
+ * return placed panels + mullions. Proxies are authored in world coordinates
7397
+ * (no placement frame), so their solid is returned as a fresh identity-placed
7398
+ * copy. Elements with no solid geometry (doors/windows/groups/spatial) return
7399
+ * an empty array.
7398
7400
  */
7399
7401
  function placedSolids(el) {
7400
7402
  switch (el.category) {
@@ -7456,6 +7458,27 @@ function placedSolids(el) {
7456
7458
  }
7457
7459
  return (0, brepjs.ok)(out);
7458
7460
  }
7461
+ case "PROXY": {
7462
+ const placed = place(el.geometry, {
7463
+ origin: [
7464
+ 0,
7465
+ 0,
7466
+ 0
7467
+ ],
7468
+ axisX: [
7469
+ 1,
7470
+ 0,
7471
+ 0
7472
+ ],
7473
+ axisZ: [
7474
+ 0,
7475
+ 0,
7476
+ 1
7477
+ ]
7478
+ });
7479
+ if (!placed.ok) return placed;
7480
+ return (0, brepjs.ok)([placed.value]);
7481
+ }
7459
7482
  case "CURTAIN_WALL": {
7460
7483
  const out = [];
7461
7484
  for (const c of [...el.geometry.panels, ...el.geometry.mullions]) try {
@@ -27601,7 +27624,8 @@ function assignNum(target, key, value) {
27601
27624
  * fill-role void (Door/Window family) maps onto addDoor/addWindow, which cut
27602
27625
  * the wall and wire IfcRelVoidsElement + IfcRelFillsElement; the opening and
27603
27626
  * filler GlobalIds derive from the synthesized key paths. Anonymous (non-fill)
27604
- * voids stay IR-only and never reach the spec path.
27627
+ * voids are rejected: they cut only the IR/viewport geometry, and exporting
27628
+ * the uncut spec body would silently diverge from what the user sees.
27605
27629
  */
27606
27630
  var SPEC_DEFAULTS = {
27607
27631
  origin: [
@@ -27742,18 +27766,43 @@ function specInput(el) {
27742
27766
  ...collectSpecProps(el)
27743
27767
  };
27744
27768
  }
27769
+ /** `Pset_<Type>Common` fields the specs model first-class: mapped onto their
27770
+ * spec names so the writer emits them in the element's own common pset. */
27771
+ var COMMON_PSET_FIELDS = {
27772
+ IsExternal: "isExternal",
27773
+ FireRating: "fireRating",
27774
+ AcousticRating: "acousticRating",
27775
+ ThermalTransmittance: "thermalTransmittance",
27776
+ LoadBearing: "loadBearing",
27777
+ Status: "status"
27778
+ };
27745
27779
  function collectSpecProps(el) {
27746
- const psets = el.attributes["psets"];
27747
27780
  const out = {};
27781
+ const material = el.attributes["material"];
27782
+ if (typeof material === "string" && el.props["materialName"] === void 0) out["materialName"] = material;
27783
+ const psets = el.attributes["psets"];
27748
27784
  if (psets && typeof psets === "object") {
27749
- const common = psets["Pset_WallCommon"];
27750
- if (common && typeof common === "object") {
27751
- const c = common;
27752
- if (typeof c["IsExternal"] === "boolean") out["isExternal"] = c["IsExternal"];
27753
- if (typeof c["FireRating"] === "string") out["fireRating"] = c["FireRating"];
27785
+ const custom = {};
27786
+ const ownCommonPset = `Pset_${el.type}Common`;
27787
+ for (const [psetName, fields] of Object.entries(psets)) {
27788
+ if (!fields || typeof fields !== "object") continue;
27789
+ const record = fields;
27790
+ if (psetName === ownCommonPset) {
27791
+ for (const [field, specKey] of Object.entries(COMMON_PSET_FIELDS)) if (record[field] !== void 0) out[specKey] = record[field];
27792
+ } else {
27793
+ const values = {};
27794
+ for (const [field, value] of Object.entries(record)) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") values[field] = value;
27795
+ if (Object.keys(values).length > 0) custom[psetName] = values;
27796
+ }
27797
+ }
27798
+ if (Object.keys(custom).length > 0) {
27799
+ const declared = el.props["customProperties"];
27800
+ out["customProperties"] = declared && typeof declared === "object" ? {
27801
+ ...custom,
27802
+ ...declared
27803
+ } : custom;
27754
27804
  }
27755
27805
  }
27756
- delete out["psets"];
27757
27806
  return out;
27758
27807
  }
27759
27808
  function addFill(model, fill, input, identity) {
@@ -27794,6 +27843,7 @@ function addOpenings(model, host, wallId, containerId, idByKeyPath) {
27794
27843
  const added = addFill(model, fill, {
27795
27844
  materialName: SPEC_DEFAULTS.materialName,
27796
27845
  ...stripGeometryProps(fill.props),
27846
+ ...collectSpecProps(fill),
27797
27847
  wallLocalId: wallId,
27798
27848
  offsetAlongWall: delta[0] * axisX[0] + delta[1] * axisX[1] + delta[2] * axisX[2],
27799
27849
  offsetFromFloor: delta[2]
@@ -27857,6 +27907,11 @@ function familiesToBim(root, options) {
27857
27907
  } else if (route !== void 0) {
27858
27908
  const keyed = requireKeyed(el);
27859
27909
  if (!keyed.ok) return keyed;
27910
+ const voids = el.props["voids"];
27911
+ if (Array.isArray(voids)) {
27912
+ const openings = el.children.filter((c) => c.type === "Opening").length;
27913
+ if (voids.length > openings) return (0, brepjs.err)(specError("FAMILIES_ANONYMOUS_VOID", `familiesToBim: '${el.keyPath}' has ${voids.length - openings} anonymous void(s) the IFC body cannot carry — use a fill-role family (Door/Window) for each void`));
27914
+ }
27860
27915
  const parsed = route.parse(("input" in route ? route.input : specInput)(el));
27861
27916
  if (!parsed.ok) return parsed;
27862
27917
  const added = route.add(model, parsed.value, el.keyPath);
@@ -7370,8 +7370,10 @@ function disposeAll(solids) {
7370
7370
  *
7371
7371
  * Stairs and ramps carry no element solid (`.geometry` is null), so flight
7372
7372
  * solids are built from `spec.flights` and placed per flight. Curtain walls
7373
- * return placed panels + mullions. Elements with no solid geometry
7374
- * (doors/windows/groups/spatial) return an empty array.
7373
+ * return placed panels + mullions. Proxies are authored in world coordinates
7374
+ * (no placement frame), so their solid is returned as a fresh identity-placed
7375
+ * copy. Elements with no solid geometry (doors/windows/groups/spatial) return
7376
+ * an empty array.
7375
7377
  */
7376
7378
  function placedSolids(el) {
7377
7379
  switch (el.category) {
@@ -7433,6 +7435,27 @@ function placedSolids(el) {
7433
7435
  }
7434
7436
  return ok(out);
7435
7437
  }
7438
+ case "PROXY": {
7439
+ const placed = place(el.geometry, {
7440
+ origin: [
7441
+ 0,
7442
+ 0,
7443
+ 0
7444
+ ],
7445
+ axisX: [
7446
+ 1,
7447
+ 0,
7448
+ 0
7449
+ ],
7450
+ axisZ: [
7451
+ 0,
7452
+ 0,
7453
+ 1
7454
+ ]
7455
+ });
7456
+ if (!placed.ok) return placed;
7457
+ return ok([placed.value]);
7458
+ }
7436
7459
  case "CURTAIN_WALL": {
7437
7460
  const out = [];
7438
7461
  for (const c of [...el.geometry.panels, ...el.geometry.mullions]) try {
@@ -27578,7 +27601,8 @@ function assignNum(target, key, value) {
27578
27601
  * fill-role void (Door/Window family) maps onto addDoor/addWindow, which cut
27579
27602
  * the wall and wire IfcRelVoidsElement + IfcRelFillsElement; the opening and
27580
27603
  * filler GlobalIds derive from the synthesized key paths. Anonymous (non-fill)
27581
- * voids stay IR-only and never reach the spec path.
27604
+ * voids are rejected: they cut only the IR/viewport geometry, and exporting
27605
+ * the uncut spec body would silently diverge from what the user sees.
27582
27606
  */
27583
27607
  var SPEC_DEFAULTS = {
27584
27608
  origin: [
@@ -27719,18 +27743,43 @@ function specInput(el) {
27719
27743
  ...collectSpecProps(el)
27720
27744
  };
27721
27745
  }
27746
+ /** `Pset_<Type>Common` fields the specs model first-class: mapped onto their
27747
+ * spec names so the writer emits them in the element's own common pset. */
27748
+ var COMMON_PSET_FIELDS = {
27749
+ IsExternal: "isExternal",
27750
+ FireRating: "fireRating",
27751
+ AcousticRating: "acousticRating",
27752
+ ThermalTransmittance: "thermalTransmittance",
27753
+ LoadBearing: "loadBearing",
27754
+ Status: "status"
27755
+ };
27722
27756
  function collectSpecProps(el) {
27723
- const psets = el.attributes["psets"];
27724
27757
  const out = {};
27758
+ const material = el.attributes["material"];
27759
+ if (typeof material === "string" && el.props["materialName"] === void 0) out["materialName"] = material;
27760
+ const psets = el.attributes["psets"];
27725
27761
  if (psets && typeof psets === "object") {
27726
- const common = psets["Pset_WallCommon"];
27727
- if (common && typeof common === "object") {
27728
- const c = common;
27729
- if (typeof c["IsExternal"] === "boolean") out["isExternal"] = c["IsExternal"];
27730
- if (typeof c["FireRating"] === "string") out["fireRating"] = c["FireRating"];
27762
+ const custom = {};
27763
+ const ownCommonPset = `Pset_${el.type}Common`;
27764
+ for (const [psetName, fields] of Object.entries(psets)) {
27765
+ if (!fields || typeof fields !== "object") continue;
27766
+ const record = fields;
27767
+ if (psetName === ownCommonPset) {
27768
+ for (const [field, specKey] of Object.entries(COMMON_PSET_FIELDS)) if (record[field] !== void 0) out[specKey] = record[field];
27769
+ } else {
27770
+ const values = {};
27771
+ for (const [field, value] of Object.entries(record)) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") values[field] = value;
27772
+ if (Object.keys(values).length > 0) custom[psetName] = values;
27773
+ }
27774
+ }
27775
+ if (Object.keys(custom).length > 0) {
27776
+ const declared = el.props["customProperties"];
27777
+ out["customProperties"] = declared && typeof declared === "object" ? {
27778
+ ...custom,
27779
+ ...declared
27780
+ } : custom;
27731
27781
  }
27732
27782
  }
27733
- delete out["psets"];
27734
27783
  return out;
27735
27784
  }
27736
27785
  function addFill(model, fill, input, identity) {
@@ -27771,6 +27820,7 @@ function addOpenings(model, host, wallId, containerId, idByKeyPath) {
27771
27820
  const added = addFill(model, fill, {
27772
27821
  materialName: SPEC_DEFAULTS.materialName,
27773
27822
  ...stripGeometryProps(fill.props),
27823
+ ...collectSpecProps(fill),
27774
27824
  wallLocalId: wallId,
27775
27825
  offsetAlongWall: delta[0] * axisX[0] + delta[1] * axisX[1] + delta[2] * axisX[2],
27776
27826
  offsetFromFloor: delta[2]
@@ -27834,6 +27884,11 @@ function familiesToBim(root, options) {
27834
27884
  } else if (route !== void 0) {
27835
27885
  const keyed = requireKeyed(el);
27836
27886
  if (!keyed.ok) return keyed;
27887
+ const voids = el.props["voids"];
27888
+ if (Array.isArray(voids)) {
27889
+ const openings = el.children.filter((c) => c.type === "Opening").length;
27890
+ if (voids.length > openings) return err(specError("FAMILIES_ANONYMOUS_VOID", `familiesToBim: '${el.keyPath}' has ${voids.length - openings} anonymous void(s) the IFC body cannot carry — use a fill-role family (Door/Window) for each void`));
27891
+ }
27837
27892
  const parsed = route.parse(("input" in route ? route.input : specInput)(el));
27838
27893
  if (!parsed.ok) return parsed;
27839
27894
  const added = route.add(model, parsed.value, el.keyPath);
@@ -12,7 +12,9 @@ import { BimError } from '../errors/bimError.js';
12
12
  *
13
13
  * Stairs and ramps carry no element solid (`.geometry` is null), so flight
14
14
  * solids are built from `spec.flights` and placed per flight. Curtain walls
15
- * return placed panels + mullions. Elements with no solid geometry
16
- * (doors/windows/groups/spatial) return an empty array.
15
+ * return placed panels + mullions. Proxies are authored in world coordinates
16
+ * (no placement frame), so their solid is returned as a fresh identity-placed
17
+ * copy. Elements with no solid geometry (doors/windows/groups/spatial) return
18
+ * an empty array.
17
19
  */
18
20
  export declare function placedSolids(el: AnyBimElement): Result<readonly ValidSolid[], BimError>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brepjs-bim",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "description": "BIM layer for brepjs — IFC4-aligned parametric building elements",
5
5
  "keywords": [
6
6
  "bim",