brepjs-bim 0.17.2 → 0.18.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 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
@@ -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). */
@@ -27890,7 +27900,8 @@ function familiesToBim(root, options) {
27890
27900
  if (project !== null) model.aggregate(project.localId, siteResult.value);
27891
27901
  model.aggregate(siteResult.value, buildingId);
27892
27902
  const idByKeyPath = /* @__PURE__ */ new Map();
27893
- const walk = (el, storeyId) => {
27903
+ const walk = (el, storeyId, rotated) => {
27904
+ const rotatedHere = rotated || hasRotateOp(el);
27894
27905
  let containerId = storeyId;
27895
27906
  const route = specRoute(el.type);
27896
27907
  if (el.type === "Storey") {
@@ -27907,6 +27918,7 @@ function familiesToBim(root, options) {
27907
27918
  } else if (route !== void 0) {
27908
27919
  const keyed = requireKeyed(el);
27909
27920
  if (!keyed.ok) return keyed;
27921
+ 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
27922
  const voids = el.props["voids"];
27911
27923
  if (Array.isArray(voids)) {
27912
27924
  const openings = el.children.filter((c) => c.type === "Opening").length;
@@ -27927,12 +27939,12 @@ function familiesToBim(root, options) {
27927
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}'`));
27928
27940
  for (const child of el.children) {
27929
27941
  if (el.type === "Wall" && child.type === "Opening") continue;
27930
- const r = walk(child, containerId);
27942
+ const r = walk(child, containerId, rotatedHere);
27931
27943
  if (!r.ok) return r;
27932
27944
  }
27933
27945
  return (0, brepjs.ok)(void 0);
27934
27946
  };
27935
- const walked = walk(root, null);
27947
+ const walked = walk(root, null, false);
27936
27948
  if (!walked.ok) {
27937
27949
  model[Symbol.dispose]();
27938
27950
  return walked;
@@ -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). */
@@ -27867,7 +27877,8 @@ function familiesToBim(root, options) {
27867
27877
  if (project !== null) model.aggregate(project.localId, siteResult.value);
27868
27878
  model.aggregate(siteResult.value, buildingId);
27869
27879
  const idByKeyPath = /* @__PURE__ */ new Map();
27870
- const walk = (el, storeyId) => {
27880
+ const walk = (el, storeyId, rotated) => {
27881
+ const rotatedHere = rotated || hasRotateOp(el);
27871
27882
  let containerId = storeyId;
27872
27883
  const route = specRoute(el.type);
27873
27884
  if (el.type === "Storey") {
@@ -27884,6 +27895,7 @@ function familiesToBim(root, options) {
27884
27895
  } else if (route !== void 0) {
27885
27896
  const keyed = requireKeyed(el);
27886
27897
  if (!keyed.ok) return keyed;
27898
+ 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
27899
  const voids = el.props["voids"];
27888
27900
  if (Array.isArray(voids)) {
27889
27901
  const openings = el.children.filter((c) => c.type === "Opening").length;
@@ -27904,12 +27916,12 @@ function familiesToBim(root, options) {
27904
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}'`));
27905
27917
  for (const child of el.children) {
27906
27918
  if (el.type === "Wall" && child.type === "Opening") continue;
27907
- const r = walk(child, containerId);
27919
+ const r = walk(child, containerId, rotatedHere);
27908
27920
  if (!r.ok) return r;
27909
27921
  }
27910
27922
  return ok(void 0);
27911
27923
  };
27912
- const walked = walk(root, null);
27924
+ const walked = walk(root, null, false);
27913
27925
  if (!walked.ok) {
27914
27926
  model[Symbol.dispose]();
27915
27927
  return walked;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brepjs-bim",
3
- "version": "0.17.2",
3
+ "version": "0.18.0",
4
4
  "description": "BIM layer for brepjs — IFC4-aligned parametric building elements",
5
5
  "keywords": [
6
6
  "bim",