brepjs-bim 0.17.1 → 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.
@@ -27624,7 +27624,8 @@ function assignNum(target, key, value) {
27624
27624
  * fill-role void (Door/Window family) maps onto addDoor/addWindow, which cut
27625
27625
  * the wall and wire IfcRelVoidsElement + IfcRelFillsElement; the opening and
27626
27626
  * filler GlobalIds derive from the synthesized key paths. Anonymous (non-fill)
27627
- * 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.
27628
27629
  */
27629
27630
  var SPEC_DEFAULTS = {
27630
27631
  origin: [
@@ -27765,18 +27766,43 @@ function specInput(el) {
27765
27766
  ...collectSpecProps(el)
27766
27767
  };
27767
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
+ };
27768
27779
  function collectSpecProps(el) {
27769
- const psets = el.attributes["psets"];
27770
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"];
27771
27784
  if (psets && typeof psets === "object") {
27772
- const common = psets["Pset_WallCommon"];
27773
- if (common && typeof common === "object") {
27774
- const c = common;
27775
- if (typeof c["IsExternal"] === "boolean") out["isExternal"] = c["IsExternal"];
27776
- 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;
27777
27804
  }
27778
27805
  }
27779
- delete out["psets"];
27780
27806
  return out;
27781
27807
  }
27782
27808
  function addFill(model, fill, input, identity) {
@@ -27817,6 +27843,7 @@ function addOpenings(model, host, wallId, containerId, idByKeyPath) {
27817
27843
  const added = addFill(model, fill, {
27818
27844
  materialName: SPEC_DEFAULTS.materialName,
27819
27845
  ...stripGeometryProps(fill.props),
27846
+ ...collectSpecProps(fill),
27820
27847
  wallLocalId: wallId,
27821
27848
  offsetAlongWall: delta[0] * axisX[0] + delta[1] * axisX[1] + delta[2] * axisX[2],
27822
27849
  offsetFromFloor: delta[2]
@@ -27880,6 +27907,11 @@ function familiesToBim(root, options) {
27880
27907
  } else if (route !== void 0) {
27881
27908
  const keyed = requireKeyed(el);
27882
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
+ }
27883
27915
  const parsed = route.parse(("input" in route ? route.input : specInput)(el));
27884
27916
  if (!parsed.ok) return parsed;
27885
27917
  const added = route.add(model, parsed.value, el.keyPath);
@@ -27601,7 +27601,8 @@ function assignNum(target, key, value) {
27601
27601
  * fill-role void (Door/Window family) maps onto addDoor/addWindow, which cut
27602
27602
  * the wall and wire IfcRelVoidsElement + IfcRelFillsElement; the opening and
27603
27603
  * filler GlobalIds derive from the synthesized key paths. Anonymous (non-fill)
27604
- * 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.
27605
27606
  */
27606
27607
  var SPEC_DEFAULTS = {
27607
27608
  origin: [
@@ -27742,18 +27743,43 @@ function specInput(el) {
27742
27743
  ...collectSpecProps(el)
27743
27744
  };
27744
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
+ };
27745
27756
  function collectSpecProps(el) {
27746
- const psets = el.attributes["psets"];
27747
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"];
27748
27761
  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"];
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;
27754
27781
  }
27755
27782
  }
27756
- delete out["psets"];
27757
27783
  return out;
27758
27784
  }
27759
27785
  function addFill(model, fill, input, identity) {
@@ -27794,6 +27820,7 @@ function addOpenings(model, host, wallId, containerId, idByKeyPath) {
27794
27820
  const added = addFill(model, fill, {
27795
27821
  materialName: SPEC_DEFAULTS.materialName,
27796
27822
  ...stripGeometryProps(fill.props),
27823
+ ...collectSpecProps(fill),
27797
27824
  wallLocalId: wallId,
27798
27825
  offsetAlongWall: delta[0] * axisX[0] + delta[1] * axisX[1] + delta[2] * axisX[2],
27799
27826
  offsetFromFloor: delta[2]
@@ -27857,6 +27884,11 @@ function familiesToBim(root, options) {
27857
27884
  } else if (route !== void 0) {
27858
27885
  const keyed = requireKeyed(el);
27859
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
+ }
27860
27892
  const parsed = route.parse(("input" in route ? route.input : specInput)(el));
27861
27893
  if (!parsed.ok) return parsed;
27862
27894
  const added = route.add(model, parsed.value, el.keyPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brepjs-bim",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
4
4
  "description": "BIM layer for brepjs — IFC4-aligned parametric building elements",
5
5
  "keywords": [
6
6
  "bim",