@bitbybit-dev/ifc 0.0.0-stage → 1.5.0-rc.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.
Files changed (242) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +42 -0
  3. package/README.md +92 -2
  4. package/index.d.ts +1 -0
  5. package/index.js +1 -0
  6. package/lib/api/dto-registry.d.ts +8 -0
  7. package/lib/api/dto-registry.js +109 -0
  8. package/lib/api/ifc-service.d.ts +109 -0
  9. package/lib/api/ifc-service.js +55 -0
  10. package/lib/api/inputs/base-inputs.d.ts +5 -0
  11. package/lib/api/inputs/base-inputs.js +5 -0
  12. package/lib/api/inputs/ifc-inputs.d.ts +2727 -0
  13. package/lib/api/inputs/ifc-inputs.js +2498 -0
  14. package/lib/api/inputs/index.d.ts +3 -0
  15. package/lib/api/inputs/index.js +3 -0
  16. package/lib/api/resolved-inputs/index.d.ts +70 -0
  17. package/lib/api/resolved-inputs/index.js +1 -0
  18. package/lib/api/services/assemblies.d.ts +28 -0
  19. package/lib/api/services/assemblies.js +49 -0
  20. package/lib/api/services/beams.d.ts +33 -0
  21. package/lib/api/services/beams.js +59 -0
  22. package/lib/api/services/columns.d.ts +26 -0
  23. package/lib/api/services/columns.js +56 -0
  24. package/lib/api/services/defaults.constants.d.ts +44 -0
  25. package/lib/api/services/defaults.constants.js +44 -0
  26. package/lib/api/services/doors.d.ts +45 -0
  27. package/lib/api/services/doors.js +94 -0
  28. package/lib/api/services/geometry.d.ts +51 -0
  29. package/lib/api/services/geometry.js +87 -0
  30. package/lib/api/services/index.d.ts +17 -0
  31. package/lib/api/services/index.js +17 -0
  32. package/lib/api/services/materials.d.ts +46 -0
  33. package/lib/api/services/materials.js +86 -0
  34. package/lib/api/services/member-support.d.ts +7 -0
  35. package/lib/api/services/member-support.js +29 -0
  36. package/lib/api/services/members.d.ts +24 -0
  37. package/lib/api/services/members.js +51 -0
  38. package/lib/api/services/model.d.ts +244 -0
  39. package/lib/api/services/model.js +409 -0
  40. package/lib/api/services/openings.d.ts +57 -0
  41. package/lib/api/services/openings.js +102 -0
  42. package/lib/api/services/properties.d.ts +89 -0
  43. package/lib/api/services/properties.js +142 -0
  44. package/lib/api/services/quantities.d.ts +38 -0
  45. package/lib/api/services/quantities.js +51 -0
  46. package/lib/api/services/roofs.d.ts +24 -0
  47. package/lib/api/services/roofs.js +52 -0
  48. package/lib/api/services/service-support.d.ts +8 -0
  49. package/lib/api/services/service-support.js +37 -0
  50. package/lib/api/services/service-types.d.ts +3 -0
  51. package/lib/api/services/service-types.js +1 -0
  52. package/lib/api/services/site.d.ts +37 -0
  53. package/lib/api/services/site.js +80 -0
  54. package/lib/api/services/slabs.d.ts +31 -0
  55. package/lib/api/services/slabs.js +59 -0
  56. package/lib/api/services/spaces.d.ts +36 -0
  57. package/lib/api/services/spaces.js +64 -0
  58. package/lib/api/services/spatial.d.ts +54 -0
  59. package/lib/api/services/spatial.js +75 -0
  60. package/lib/api/services/walls.d.ts +140 -0
  61. package/lib/api/services/walls.js +268 -0
  62. package/lib/api/services/windows.d.ts +46 -0
  63. package/lib/api/services/windows.js +76 -0
  64. package/lib/build/assemblies.d.ts +4 -0
  65. package/lib/build/assemblies.js +45 -0
  66. package/lib/build/build-types.d.ts +345 -0
  67. package/lib/build/build-types.js +1 -0
  68. package/lib/build/checks.d.ts +6 -0
  69. package/lib/build/checks.js +32 -0
  70. package/lib/build/constants.d.ts +27 -0
  71. package/lib/build/constants.js +43 -0
  72. package/lib/build/contexts.d.ts +9 -0
  73. package/lib/build/contexts.js +52 -0
  74. package/lib/build/curves.d.ts +3 -0
  75. package/lib/build/curves.js +23 -0
  76. package/lib/build/entity-writer.d.ts +23 -0
  77. package/lib/build/entity-writer.js +97 -0
  78. package/lib/build/fillings.d.ts +7 -0
  79. package/lib/build/fillings.js +182 -0
  80. package/lib/build/georeference.d.ts +5 -0
  81. package/lib/build/georeference.js +44 -0
  82. package/lib/build/materials.d.ts +18 -0
  83. package/lib/build/materials.js +166 -0
  84. package/lib/build/math.d.ts +10 -0
  85. package/lib/build/math.js +47 -0
  86. package/lib/build/measure.d.ts +9 -0
  87. package/lib/build/measure.js +195 -0
  88. package/lib/build/moving.d.ts +4 -0
  89. package/lib/build/moving.js +61 -0
  90. package/lib/build/openings.d.ts +12 -0
  91. package/lib/build/openings.js +156 -0
  92. package/lib/build/outlines.d.ts +4 -0
  93. package/lib/build/outlines.js +40 -0
  94. package/lib/build/placement.d.ts +16 -0
  95. package/lib/build/placement.js +106 -0
  96. package/lib/build/profiles.d.ts +4 -0
  97. package/lib/build/profiles.js +35 -0
  98. package/lib/build/project.d.ts +11 -0
  99. package/lib/build/project.js +134 -0
  100. package/lib/build/properties.d.ts +10 -0
  101. package/lib/build/properties.js +182 -0
  102. package/lib/build/quantities.d.ts +7 -0
  103. package/lib/build/quantities.js +262 -0
  104. package/lib/build/relationships.d.ts +9 -0
  105. package/lib/build/relationships.js +67 -0
  106. package/lib/build/removal.d.ts +3 -0
  107. package/lib/build/removal.js +106 -0
  108. package/lib/build/representations.d.ts +6 -0
  109. package/lib/build/representations.js +46 -0
  110. package/lib/build/roof-clipping.d.ts +5 -0
  111. package/lib/build/roof-clipping.js +79 -0
  112. package/lib/build/roofs.d.ts +4 -0
  113. package/lib/build/roofs.js +131 -0
  114. package/lib/build/site.d.ts +7 -0
  115. package/lib/build/site.js +112 -0
  116. package/lib/build/slab-openings.d.ts +4 -0
  117. package/lib/build/slab-openings.js +71 -0
  118. package/lib/build/slabs-columns-beams.d.ts +8 -0
  119. package/lib/build/slabs-columns-beams.js +73 -0
  120. package/lib/build/solids.d.ts +5 -0
  121. package/lib/build/solids.js +33 -0
  122. package/lib/build/spaces.d.ts +6 -0
  123. package/lib/build/spaces.js +55 -0
  124. package/lib/build/spatial.d.ts +17 -0
  125. package/lib/build/spatial.js +111 -0
  126. package/lib/build/type-objects.d.ts +4 -0
  127. package/lib/build/type-objects.js +9 -0
  128. package/lib/build/wall-edits.d.ts +5 -0
  129. package/lib/build/wall-edits.js +138 -0
  130. package/lib/build/wall-geometry.d.ts +15 -0
  131. package/lib/build/wall-geometry.js +159 -0
  132. package/lib/build/wall-parameters.d.ts +3 -0
  133. package/lib/build/wall-parameters.js +47 -0
  134. package/lib/build/walls.d.ts +12 -0
  135. package/lib/build/walls.js +238 -0
  136. package/lib/geometry/advanced.d.ts +27 -0
  137. package/lib/geometry/advanced.js +292 -0
  138. package/lib/geometry/colours.d.ts +3 -0
  139. package/lib/geometry/colours.js +109 -0
  140. package/lib/geometry/curves.d.ts +32 -0
  141. package/lib/geometry/curves.js +370 -0
  142. package/lib/geometry/edges.constants.d.ts +5 -0
  143. package/lib/geometry/edges.constants.js +5 -0
  144. package/lib/geometry/edges.d.ts +21 -0
  145. package/lib/geometry/edges.js +164 -0
  146. package/lib/geometry/elements.d.ts +3 -0
  147. package/lib/geometry/elements.js +152 -0
  148. package/lib/geometry/errors.d.ts +3 -0
  149. package/lib/geometry/errors.js +6 -0
  150. package/lib/geometry/faceted.d.ts +31 -0
  151. package/lib/geometry/faceted.js +214 -0
  152. package/lib/geometry/geometry-types.d.ts +103 -0
  153. package/lib/geometry/geometry-types.js +1 -0
  154. package/lib/geometry/groups.d.ts +21 -0
  155. package/lib/geometry/groups.js +106 -0
  156. package/lib/geometry/items.d.ts +34 -0
  157. package/lib/geometry/items.js +313 -0
  158. package/lib/geometry/operators.d.ts +3 -0
  159. package/lib/geometry/operators.js +65 -0
  160. package/lib/geometry/profiles.d.ts +4 -0
  161. package/lib/geometry/profiles.js +277 -0
  162. package/lib/geometry/recipe-builder.d.ts +14 -0
  163. package/lib/geometry/recipe-builder.js +48 -0
  164. package/lib/geometry/references.d.ts +4 -0
  165. package/lib/geometry/references.js +17 -0
  166. package/lib/geometry/regions.d.ts +4 -0
  167. package/lib/geometry/regions.js +26 -0
  168. package/lib/geometry/surfaces.constants.d.ts +17 -0
  169. package/lib/geometry/surfaces.constants.js +17 -0
  170. package/lib/geometry/surfaces.d.ts +6 -0
  171. package/lib/geometry/surfaces.js +306 -0
  172. package/lib/geometry/swept-solids.constants.d.ts +7 -0
  173. package/lib/geometry/swept-solids.constants.js +6 -0
  174. package/lib/geometry/swept-solids.d.ts +25 -0
  175. package/lib/geometry/swept-solids.js +243 -0
  176. package/lib/index.d.ts +6 -0
  177. package/lib/index.js +6 -0
  178. package/lib/model/constants.d.ts +30 -0
  179. package/lib/model/constants.js +30 -0
  180. package/lib/model/entity-lookup.d.ts +9 -0
  181. package/lib/model/entity-lookup.js +20 -0
  182. package/lib/model/guid.d.ts +7 -0
  183. package/lib/model/guid.js +60 -0
  184. package/lib/model/id-map.d.ts +10 -0
  185. package/lib/model/id-map.js +80 -0
  186. package/lib/model/index.d.ts +4 -0
  187. package/lib/model/index.js +2 -0
  188. package/lib/model/io.d.ts +6 -0
  189. package/lib/model/io.js +136 -0
  190. package/lib/model/is-model.d.ts +9 -0
  191. package/lib/model/is-model.js +11 -0
  192. package/lib/model/model-types.d.ts +111 -0
  193. package/lib/model/model-types.js +1 -0
  194. package/lib/model/overlay-indexes.d.ts +17 -0
  195. package/lib/model/overlay-indexes.js +69 -0
  196. package/lib/model/references.d.ts +2 -0
  197. package/lib/model/references.js +51 -0
  198. package/lib/model/rows-by-id.d.ts +8 -0
  199. package/lib/model/rows-by-id.js +39 -0
  200. package/lib/model/snapshot.d.ts +36 -0
  201. package/lib/model/snapshot.js +144 -0
  202. package/lib/model/source.d.ts +29 -0
  203. package/lib/model/source.js +172 -0
  204. package/lib/model/transaction.d.ts +39 -0
  205. package/lib/model/transaction.js +250 -0
  206. package/lib/model/upgrade.d.ts +2 -0
  207. package/lib/model/upgrade.js +78 -0
  208. package/lib/schema/constants.d.ts +3 -0
  209. package/lib/schema/constants.js +3 -0
  210. package/lib/schema/generated/ifc4.d.ts +2 -0
  211. package/lib/schema/generated/ifc4.js +1204 -0
  212. package/lib/schema/generated/ifc4x3.d.ts +2 -0
  213. package/lib/schema/generated/ifc4x3.js +1325 -0
  214. package/lib/schema/registry.d.ts +7 -0
  215. package/lib/schema/registry.js +37 -0
  216. package/lib/schema/schema-types.d.ts +45 -0
  217. package/lib/schema/schema-types.js +1 -0
  218. package/lib/schema/schema.d.ts +23 -0
  219. package/lib/schema/schema.js +136 -0
  220. package/lib/step/constants.d.ts +48 -0
  221. package/lib/step/constants.js +48 -0
  222. package/lib/step/encoder.d.ts +21 -0
  223. package/lib/step/encoder.js +321 -0
  224. package/lib/step/errors.d.ts +39 -0
  225. package/lib/step/errors.js +45 -0
  226. package/lib/step/file-index.d.ts +2 -0
  227. package/lib/step/file-index.js +221 -0
  228. package/lib/step/header.d.ts +4 -0
  229. package/lib/step/header.js +52 -0
  230. package/lib/step/numbers.d.ts +3 -0
  231. package/lib/step/numbers.js +73 -0
  232. package/lib/step/reader.d.ts +2 -0
  233. package/lib/step/reader.js +160 -0
  234. package/lib/step/scanner.d.ts +13 -0
  235. package/lib/step/scanner.js +203 -0
  236. package/lib/step/step-types.d.ts +170 -0
  237. package/lib/step/step-types.js +1 -0
  238. package/lib/step/strings.d.ts +2 -0
  239. package/lib/step/strings.js +150 -0
  240. package/lib/step/values.d.ts +13 -0
  241. package/lib/step/values.js +51 -0
  242. package/package.json +51 -5
@@ -0,0 +1,370 @@
1
+ import { arcThroughThreePoints, pointsOnArc } from "@bitbybit-dev/base/lib/api/services/helpers/arcs";
2
+ import { pointToLocal, squareFrame } from "@bitbybit-dev/base/lib/api/services/helpers/frame-axes";
3
+ import { expandedKnots, knotDomain, nurbsCurvePoint } from "@bitbybit-dev/base/lib/api/services/helpers/nurbs";
4
+ import { newellNormal } from "@bitbybit-dev/base/lib/api/services/helpers/triangulation";
5
+ import { add3, dot3, scale3, subtract3 } from "@bitbybit-dev/base/lib/api/services/helpers/vectors";
6
+ import { axisPlacementFrame, directionOf, numbersOf, pointOf } from "../build/placement";
7
+ import { radiansPerUnit } from "../build/project";
8
+ import { isEnumeration, isList, isReference, isTyped } from "../step/values";
9
+ import { UnsupportedGeometryError } from "./errors";
10
+ const FULL_TURN = 2 * Math.PI;
11
+ const SEGMENTS_PER_TURN = 32;
12
+ const MIN_ARC_SEGMENTS = 2;
13
+ const MAX_CURVE_DEPTH = 64;
14
+ const DUPLICATE_RATIO = 1e-9;
15
+ const ANGLE_TOLERANCE = 1e-9;
16
+ const SEGMENT_SLACK = 1e-9;
17
+ const ARC_POINTS = 3;
18
+ const ARC_INDEX = "IFCARCINDEX";
19
+ const PARAMETER_VALUE = "IFCPARAMETERVALUE";
20
+ const PLANE_RATIO = 1e-6;
21
+ const SEGMENTS_PER_SPAN = 8;
22
+ const MAX_SPLINE_SEGMENTS = 512;
23
+ function withoutRepeats(points) {
24
+ const extent = points.reduce((largest, [x, y]) => Math.max(largest, Math.abs(x), Math.abs(y)), 0);
25
+ const tolerance = extent * DUPLICATE_RATIO;
26
+ const same = (a, b) => Math.abs(a[0] - b[0]) <= tolerance && Math.abs(a[1] - b[1]) <= tolerance;
27
+ const kept = points.filter((point, at) => at === 0 || !same(point, points[at - 1]));
28
+ return kept.length > 1 && same(kept[0], kept[kept.length - 1]) ? kept.slice(0, -1) : kept;
29
+ }
30
+ function withoutRepeatedPoints(points) {
31
+ const extent = points.reduce((largest, point) => Math.max(largest, ...point.map(Math.abs)), 0);
32
+ const tolerance = extent * DUPLICATE_RATIO;
33
+ return points.filter((point, at) => at === 0 || point.some((value, axis) => Math.abs(value - points[at - 1][axis]) > tolerance));
34
+ }
35
+ export function arcSegments(sweep) {
36
+ return Math.max(MIN_ARC_SEGMENTS, Math.ceil(Math.abs(sweep) / (FULL_TURN / SEGMENTS_PER_TURN) - SEGMENT_SLACK));
37
+ }
38
+ export function sweepBetween(start, end, counterClockwise) {
39
+ const ahead = (((end - start) % FULL_TURN) + FULL_TURN) % FULL_TURN;
40
+ if (ahead < ANGLE_TOLERANCE || FULL_TURN - ahead < ANGLE_TOLERANCE) {
41
+ return counterClockwise ? FULL_TURN : -FULL_TURN;
42
+ }
43
+ return counterClockwise ? ahead : ahead - FULL_TURN;
44
+ }
45
+ function threePointArc(start, middle, end) {
46
+ const arc = arcThroughThreePoints(start, middle, end);
47
+ return arc ? pointsOnArc(arc, start, end, arcSegments(arc.sweep)) : [start, middle, end];
48
+ }
49
+ function indexList(value) {
50
+ const inner = isTyped(value) ? value.value : value;
51
+ if (!isList(inner) || !inner.every((item) => typeof item === "number" && Number.isSafeInteger(item))) {
52
+ throw new UnsupportedGeometryError("A curve segment is not a list of point indices");
53
+ }
54
+ return inner;
55
+ }
56
+ export class CurveSampler {
57
+ constructor(model, density = 1) {
58
+ this.model = model;
59
+ this.density = density;
60
+ }
61
+ outline(curve, plane) {
62
+ return withoutRepeats(plane === undefined ? this.sampled(curve, 0).map(([x, y]) => [x, y]) : this.polylineIn(curve, plane));
63
+ }
64
+ path(curve) {
65
+ return withoutRepeatedPoints(this.sampled(curve, 0));
66
+ }
67
+ endTangents(curve, depth = 0) {
68
+ var _a, _b;
69
+ if (depth > MAX_CURVE_DEPTH) {
70
+ return [undefined, undefined];
71
+ }
72
+ const type = this.model.entity(curve).type;
73
+ if (type === "IfcCircle" || type === "IfcEllipse") {
74
+ const tangent = this.conicTangent(curve, 0, 1);
75
+ return [tangent, tangent];
76
+ }
77
+ if (type === "IfcCompositeCurve" || type === "IfcCompositeCurveOnSurface") {
78
+ const segments = this.model.attribute(curve, "Segments");
79
+ const list = (isList(segments) ? segments : []).filter(isReference);
80
+ const ends = (segment, last) => {
81
+ const parent = segment === undefined ? null : this.model.attribute(segment, "ParentCurve");
82
+ if (!isReference(parent)) {
83
+ return undefined;
84
+ }
85
+ const reversed = this.model.attribute(segment, "SameSense") === false;
86
+ const [start, end] = this.endTangents(parent.ref, depth + 1);
87
+ const chosen = last !== reversed ? end : start;
88
+ return chosen && reversed ? scale3(chosen, -1) : chosen;
89
+ };
90
+ return [ends((_a = list[0]) === null || _a === void 0 ? void 0 : _a.ref, false), ends((_b = list[list.length - 1]) === null || _b === void 0 ? void 0 : _b.ref, true)];
91
+ }
92
+ if (type !== "IfcTrimmedCurve") {
93
+ return [undefined, undefined];
94
+ }
95
+ const basis = this.model.attribute(curve, "BasisCurve");
96
+ if (!isReference(basis) || !["IfcCircle", "IfcEllipse"].includes(this.model.entity(basis.ref).type)) {
97
+ return [undefined, undefined];
98
+ }
99
+ const preference = this.model.attribute(curve, "MasterRepresentation");
100
+ const byParameter = isEnumeration(preference) && preference.enum === "PARAMETER";
101
+ const first = this.conicAngle(basis.ref, this.trimOf(this.model.attribute(curve, "Trim1"), curve, "Trim1"), byParameter);
102
+ const second = this.conicAngle(basis.ref, this.trimOf(this.model.attribute(curve, "Trim2"), curve, "Trim2"), byParameter);
103
+ const sweep = sweepBetween(first, second, this.model.attribute(curve, "SenseAgreement") !== false);
104
+ return [this.conicTangent(basis.ref, first, Math.sign(sweep)), this.conicTangent(basis.ref, first + sweep, Math.sign(sweep))];
105
+ }
106
+ parameterSpan(curve) {
107
+ const type = this.model.entity(curve).type;
108
+ if (type === "IfcPolyline") {
109
+ return Math.max(0, this.polylinePoints(curve).length - 1);
110
+ }
111
+ if (type !== "IfcTrimmedCurve") {
112
+ return undefined;
113
+ }
114
+ const basis = this.model.attribute(curve, "BasisCurve");
115
+ const first = this.trimOf(this.model.attribute(curve, "Trim1"), curve, "Trim1");
116
+ const second = this.trimOf(this.model.attribute(curve, "Trim2"), curve, "Trim2");
117
+ if (!isReference(basis) || first.parameter === undefined || second.parameter === undefined) {
118
+ return undefined;
119
+ }
120
+ const basisType = this.model.entity(basis.ref).type;
121
+ if (basisType === "IfcLine") {
122
+ return Math.abs(second.parameter - first.parameter);
123
+ }
124
+ if (basisType !== "IfcCircle" && basisType !== "IfcEllipse") {
125
+ return undefined;
126
+ }
127
+ const forward = this.model.attribute(curve, "SenseAgreement") !== false;
128
+ const sweep = sweepBetween(first.parameter * this.radiansPerUnit, second.parameter * this.radiansPerUnit, forward);
129
+ return Math.abs(sweep) / this.radiansPerUnit;
130
+ }
131
+ planeOf(curve) {
132
+ if (this.model.entity(curve).type !== "IfcPolyline") {
133
+ return undefined;
134
+ }
135
+ const points = this.polylinePoints(curve);
136
+ if (points.every((point) => point[2] === 0)) {
137
+ return undefined;
138
+ }
139
+ const plane = points.length < ARC_POINTS ? "normal" : squareFrame(points[0], newellNormal(points.flat(), points.map((_, at) => at)), subtract3(points[1], points[0]));
140
+ if (typeof plane === "string") {
141
+ throw new UnsupportedGeometryError(`#${curve} is an outline with no area`);
142
+ }
143
+ const extent = points.reduce((largest, point) => Math.max(largest, ...point.map((value, axis) => Math.abs(value - points[0][axis]))), 0);
144
+ if (points.some((point) => Math.abs(pointToLocal(plane, point)[2]) > extent * PLANE_RATIO)) {
145
+ throw new UnsupportedGeometryError(`#${curve} is an outline whose points do not lie in one plane`);
146
+ }
147
+ return plane;
148
+ }
149
+ get radiansPerUnit() {
150
+ var _a;
151
+ (_a = this.angleUnit) !== null && _a !== void 0 ? _a : (this.angleUnit = radiansPerUnit(this.model));
152
+ return this.angleUnit;
153
+ }
154
+ sampled(curve, depth) {
155
+ if (depth > MAX_CURVE_DEPTH) {
156
+ throw new UnsupportedGeometryError(`#${curve} nests curves more than ${MAX_CURVE_DEPTH} deep, or through itself`);
157
+ }
158
+ const type = this.model.entity(curve).type;
159
+ switch (type) {
160
+ case "IfcPolyline":
161
+ return this.polylinePoints(curve);
162
+ case "IfcIndexedPolyCurve":
163
+ return this.indexed(curve);
164
+ case "IfcCompositeCurve":
165
+ case "IfcCompositeCurveOnSurface":
166
+ return this.composite(curve, depth);
167
+ case "IfcTrimmedCurve":
168
+ return this.trimmed(curve);
169
+ case "IfcCircle":
170
+ case "IfcEllipse":
171
+ return this.conic(curve, 0, FULL_TURN, undefined, undefined);
172
+ case "IfcBSplineCurveWithKnots":
173
+ case "IfcRationalBSplineCurveWithKnots": {
174
+ const spline = this.bspline(curve);
175
+ return this.splinePoints(spline, ...knotDomain(spline.degree, spline.knots));
176
+ }
177
+ default:
178
+ throw new UnsupportedGeometryError(`An ${type} outline is not supported yet`);
179
+ }
180
+ }
181
+ polylinePoints(curve) {
182
+ const points = this.model.attribute(curve, "Points");
183
+ return (isList(points) ? points : []).map((point) => pointOf(this.model, point));
184
+ }
185
+ polylineIn(curve, plane) {
186
+ if (this.model.entity(curve).type !== "IfcPolyline") {
187
+ throw new UnsupportedGeometryError(`#${curve} lies outside its profile's plane, which only a polyline may`);
188
+ }
189
+ return this.polylinePoints(curve).map((point) => {
190
+ const [x, y] = pointToLocal(plane, point);
191
+ return [x, y];
192
+ });
193
+ }
194
+ indexed(curve) {
195
+ const list = this.model.attribute(curve, "Points");
196
+ if (!isReference(list)) {
197
+ throw new UnsupportedGeometryError(`#${curve} has no points`);
198
+ }
199
+ const coordinates = this.model.attribute(list.ref, "CoordList");
200
+ const all = (isList(coordinates) ? coordinates : []).map((item, index) => {
201
+ var _a, _b, _c;
202
+ const numbers = numbersOf(item, `#${list.ref} point ${index}`);
203
+ return [(_a = numbers[0]) !== null && _a !== void 0 ? _a : 0, (_b = numbers[1]) !== null && _b !== void 0 ? _b : 0, (_c = numbers[2]) !== null && _c !== void 0 ? _c : 0];
204
+ });
205
+ const segments = this.model.attribute(curve, "Segments");
206
+ if (!isList(segments)) {
207
+ return all;
208
+ }
209
+ const pointAt = (index) => {
210
+ const point = all[index - 1];
211
+ if (!point) {
212
+ throw new UnsupportedGeometryError(`#${curve} names point ${index}, which its list does not hold`);
213
+ }
214
+ return point;
215
+ };
216
+ const points = [];
217
+ let last;
218
+ for (const segment of segments) {
219
+ const indices = indexList(segment);
220
+ const named = indices.map(pointAt);
221
+ const isArc = isTyped(segment) && segment.type.toUpperCase() === ARC_INDEX;
222
+ if (isArc && named.length !== ARC_POINTS) {
223
+ throw new UnsupportedGeometryError(`#${curve} has an arc through ${named.length} points, not ${ARC_POINTS}`);
224
+ }
225
+ const sampled = isArc ? threePointArc(named[0], named[1], named[2]) : named;
226
+ points.push(...(last !== undefined && last === indices[0] ? sampled.slice(1) : sampled));
227
+ last = indices[indices.length - 1];
228
+ }
229
+ return points;
230
+ }
231
+ composite(curve, depth) {
232
+ const segments = this.model.attribute(curve, "Segments");
233
+ const points = [];
234
+ for (const segment of isList(segments) ? segments : []) {
235
+ if (!isReference(segment)) {
236
+ throw new UnsupportedGeometryError(`#${curve} has a segment that is not a reference`);
237
+ }
238
+ const parent = this.model.attribute(segment.ref, "ParentCurve");
239
+ if (!isReference(parent)) {
240
+ throw new UnsupportedGeometryError(`#${segment.ref} has no parent curve`);
241
+ }
242
+ const path = this.sampled(parent.ref, depth + 1);
243
+ points.push(...(this.model.attribute(segment.ref, "SameSense") === false ? [...path].reverse() : path));
244
+ }
245
+ if (!points.length) {
246
+ throw new UnsupportedGeometryError(`#${curve} has no segments`);
247
+ }
248
+ return points;
249
+ }
250
+ trimOf(value, curve, name) {
251
+ let point;
252
+ let parameter;
253
+ for (const item of isList(value) ? value : []) {
254
+ if (isReference(item)) {
255
+ point = pointOf(this.model, item);
256
+ }
257
+ else if (isTyped(item) && item.type.toUpperCase() === PARAMETER_VALUE && typeof item.value === "number" && Number.isFinite(item.value)) {
258
+ parameter = item.value;
259
+ }
260
+ }
261
+ if (point === undefined && parameter === undefined) {
262
+ throw new UnsupportedGeometryError(`#${curve} has no ${name} it can be trimmed at`);
263
+ }
264
+ return { point, parameter };
265
+ }
266
+ trimmed(curve) {
267
+ const basis = this.model.attribute(curve, "BasisCurve");
268
+ if (!isReference(basis)) {
269
+ throw new UnsupportedGeometryError(`#${curve} has no basis curve`);
270
+ }
271
+ const preference = this.model.attribute(curve, "MasterRepresentation");
272
+ const byParameter = isEnumeration(preference) && preference.enum === "PARAMETER";
273
+ const first = this.trimOf(this.model.attribute(curve, "Trim1"), curve, "Trim1");
274
+ const second = this.trimOf(this.model.attribute(curve, "Trim2"), curve, "Trim2");
275
+ const forward = this.model.attribute(curve, "SenseAgreement") !== false;
276
+ const type = this.model.entity(basis.ref).type;
277
+ if (type === "IfcLine") {
278
+ return [this.linePoint(basis.ref, first, byParameter), this.linePoint(basis.ref, second, byParameter)];
279
+ }
280
+ if (type !== "IfcCircle" && type !== "IfcEllipse") {
281
+ throw new UnsupportedGeometryError(`A trimmed ${type} is not supported yet`);
282
+ }
283
+ const start = this.conicAngle(basis.ref, first, byParameter);
284
+ const end = this.conicAngle(basis.ref, second, byParameter);
285
+ const exact = (trim) => (byParameter && trim.parameter !== undefined ? undefined : trim.point);
286
+ return this.conic(basis.ref, start, sweepBetween(start, end, forward), exact(first), exact(second));
287
+ }
288
+ linePoint(line, trim, byParameter) {
289
+ if (trim.point !== undefined && !(byParameter && trim.parameter !== undefined)) {
290
+ return trim.point;
291
+ }
292
+ const [x, y, z] = pointOf(this.model, this.model.attribute(line, "Pnt"));
293
+ const vector = this.model.attribute(line, "Dir");
294
+ if (!isReference(vector)) {
295
+ throw new UnsupportedGeometryError(`#${line} has no direction`);
296
+ }
297
+ const [dx, dy, dz] = directionOf(this.model, this.model.attribute(vector.ref, "Orientation"), [1, 0, 0]);
298
+ const magnitude = this.model.attribute(vector.ref, "Magnitude");
299
+ const length = Math.hypot(dx, dy, dz);
300
+ if (typeof magnitude !== "number" || !(length > 0)) {
301
+ throw new UnsupportedGeometryError(`#${vector.ref} has no direction or no magnitude`);
302
+ }
303
+ const step = trim.parameter * magnitude / length;
304
+ return [x + dx * step, y + dy * step, z + dz * step];
305
+ }
306
+ conicAngle(conic, trim, byParameter) {
307
+ if (trim.parameter !== undefined && (byParameter || trim.point === undefined)) {
308
+ return trim.parameter * this.radiansPerUnit;
309
+ }
310
+ const { origin, x, y, radii } = this.conicFrame(conic);
311
+ const offset = subtract3(trim.point, origin);
312
+ return Math.atan2(dot3(offset, y) / radii[1], dot3(offset, x) / radii[0]);
313
+ }
314
+ conicFrame(conic) {
315
+ const position = this.model.attribute(conic, "Position");
316
+ if (!isReference(position)) {
317
+ throw new UnsupportedGeometryError(`#${conic} has no position`);
318
+ }
319
+ const frame = axisPlacementFrame(this.model, position.ref);
320
+ const circle = this.model.entity(conic).type === "IfcCircle";
321
+ const first = this.model.attribute(conic, circle ? "Radius" : "SemiAxis1");
322
+ const second = circle ? first : this.model.attribute(conic, "SemiAxis2");
323
+ if (typeof first !== "number" || typeof second !== "number" || !(first > 0) || !(second > 0)) {
324
+ throw new UnsupportedGeometryError(`#${conic} has no radius above zero`);
325
+ }
326
+ return { origin: frame.origin, x: frame.x, y: frame.y, radii: [first, second] };
327
+ }
328
+ bspline(curve) {
329
+ const degree = this.model.attribute(curve, "Degree");
330
+ const controls = this.model.attribute(curve, "ControlPointsList");
331
+ const multiplicities = this.model.attribute(curve, "KnotMultiplicities");
332
+ const knots = this.model.attribute(curve, "Knots");
333
+ const isWhole = (value) => isList(value) && value.every((item) => typeof item === "number" && Number.isFinite(item));
334
+ if (typeof degree !== "number" || !(degree >= 1) || !isList(controls) || !isWhole(multiplicities) || !isWhole(knots)) {
335
+ throw new UnsupportedGeometryError(`#${curve} is not a B-spline curve it can read`);
336
+ }
337
+ const points = controls.map((point) => pointOf(this.model, point));
338
+ const expanded = expandedKnots(knots, multiplicities);
339
+ if (expanded.length !== points.length + degree + 1) {
340
+ throw new UnsupportedGeometryError(`#${curve} has ${expanded.length} knots for ${points.length} control points of degree ${degree}`);
341
+ }
342
+ const weights = this.model.entity(curve).type === "IfcRationalBSplineCurveWithKnots" ? this.model.attribute(curve, "WeightsData") : null;
343
+ return { degree, points, weights: isWhole(weights) ? weights : undefined, knots: expanded };
344
+ }
345
+ splinePoints(spline, from, to) {
346
+ const spans = new Set(spline.knots).size - 1;
347
+ const segments = Math.min(MAX_SPLINE_SEGMENTS * this.density, Math.max(SEGMENTS_PER_SPAN, spans * SEGMENTS_PER_SPAN * spline.degree) * this.density);
348
+ return Array.from({ length: segments + 1 }, (_, at) => nurbsCurvePoint(spline.degree, spline.points, spline.weights, spline.knots, from + ((to - from) * at) / segments));
349
+ }
350
+ conicTangent(conic, angle, sense) {
351
+ const { x, y, radii } = this.conicFrame(conic);
352
+ return scale3(add3(scale3(x, -radii[0] * Math.sin(angle)), scale3(y, radii[1] * Math.cos(angle))), sense);
353
+ }
354
+ conic(conic, start, sweep, from, to) {
355
+ const { origin, x, y, radii } = this.conicFrame(conic);
356
+ const segments = arcSegments(sweep) * this.density;
357
+ const points = [];
358
+ for (let step = 0; step <= segments; step++) {
359
+ const angle = start + sweep * step / segments;
360
+ points.push(add3(origin, add3(scale3(x, radii[0] * Math.cos(angle)), scale3(y, radii[1] * Math.sin(angle)))));
361
+ }
362
+ if (from) {
363
+ points[0] = from;
364
+ }
365
+ if (to) {
366
+ points[segments] = to;
367
+ }
368
+ return points;
369
+ }
370
+ }
@@ -0,0 +1,5 @@
1
+ export declare const CLOSED_EDGE_RATIO = 1e-9;
2
+ export declare const PARAMETER_SEARCH_STEPS = 64;
3
+ export declare const PARAMETER_SEARCH_TOLERANCE = 1e-12;
4
+ export declare const GOLDEN_SECTION: number;
5
+ export declare const WEIGHTED_POINTS_RATIO = 0.000001;
@@ -0,0 +1,5 @@
1
+ export const CLOSED_EDGE_RATIO = 1e-9;
2
+ export const PARAMETER_SEARCH_STEPS = 64;
3
+ export const PARAMETER_SEARCH_TOLERANCE = 1e-12;
4
+ export const GOLDEN_SECTION = (Math.sqrt(5) - 1) / 2;
5
+ export const WEIGHTED_POINTS_RATIO = 1e-6;
@@ -0,0 +1,21 @@
1
+ import type { Base } from "@bitbybit-dev/base";
2
+ import type { ModelSnapshot } from "../model/snapshot";
3
+ import { type CurveSampler } from "./curves";
4
+ import type { BSplineCurve } from "./geometry-types";
5
+ export declare function dividedByWeights(spline: BSplineCurve): BSplineCurve;
6
+ export declare class EdgeSampler {
7
+ private readonly model;
8
+ private readonly curves;
9
+ private readonly sampled;
10
+ constructor(model: ModelSnapshot, curves: CurveSampler);
11
+ loop(loop: number): Base.Point3[];
12
+ private edge;
13
+ private along;
14
+ private conic;
15
+ private spline;
16
+ private meetingVertices;
17
+ private vertex;
18
+ private pointAt;
19
+ private value;
20
+ private references;
21
+ }
@@ -0,0 +1,164 @@
1
+ import { knotDomain, nurbsCurvePoint } from "@bitbybit-dev/base/lib/api/services/helpers/nurbs";
2
+ import { add3, dot3, length3, scale3, subtract3 } from "@bitbybit-dev/base/lib/api/services/helpers/vectors";
3
+ import { numbersOf } from "../build/placement";
4
+ import { isReference } from "../step/values";
5
+ import { arcSegments, sweepBetween } from "./curves";
6
+ import { UnsupportedGeometryError } from "./errors";
7
+ import { referencesIn } from "./references";
8
+ import { CLOSED_EDGE_RATIO, GOLDEN_SECTION, PARAMETER_SEARCH_STEPS, PARAMETER_SEARCH_TOLERANCE, WEIGHTED_POINTS_RATIO } from "./edges.constants";
9
+ const CURVE_ON_SURFACE = new Set(["IfcSurfaceCurve", "IfcIntersectionCurve", "IfcSeamCurve"]);
10
+ const SPLINES = new Set(["IfcBSplineCurveWithKnots", "IfcRationalBSplineCurveWithKnots"]);
11
+ function distance(a, b) {
12
+ return length3(subtract3(a, b));
13
+ }
14
+ function nearestParameter(spline, point, from, to) {
15
+ const at = (t) => distance(nurbsCurvePoint(spline.degree, spline.points, spline.weights, spline.knots, t), point);
16
+ let best = from;
17
+ let bestDistance = Infinity;
18
+ for (let step = 0; step <= PARAMETER_SEARCH_STEPS; step++) {
19
+ const t = from + ((to - from) * step) / PARAMETER_SEARCH_STEPS;
20
+ const d = at(t);
21
+ if (d < bestDistance) {
22
+ best = t;
23
+ bestDistance = d;
24
+ }
25
+ }
26
+ const width = (to - from) / PARAMETER_SEARCH_STEPS;
27
+ let low = Math.max(from, best - width);
28
+ let high = Math.min(to, best + width);
29
+ while (high - low > PARAMETER_SEARCH_TOLERANCE * (to - from)) {
30
+ const left = high - GOLDEN_SECTION * (high - low);
31
+ const right = low + GOLDEN_SECTION * (high - low);
32
+ if (at(left) < at(right)) {
33
+ high = right;
34
+ }
35
+ else {
36
+ low = left;
37
+ }
38
+ }
39
+ return (low + high) / 2;
40
+ }
41
+ export function dividedByWeights(spline) {
42
+ var _a;
43
+ const weights = (_a = spline.weights) !== null && _a !== void 0 ? _a : [];
44
+ return Object.assign(Object.assign({}, spline), { points: spline.points.map((point, at) => { var _a; return scale3(point, 1 / ((_a = weights[at]) !== null && _a !== void 0 ? _a : 1)); }) });
45
+ }
46
+ export class EdgeSampler {
47
+ constructor(model, curves) {
48
+ this.sampled = new Map();
49
+ this.model = model;
50
+ this.curves = curves;
51
+ }
52
+ loop(loop) {
53
+ const type = this.model.typeOf(loop);
54
+ if (type === "IfcPolyLoop") {
55
+ return this.references(loop, "Polygon").map((point) => this.pointAt(point));
56
+ }
57
+ if (type !== "IfcEdgeLoop") {
58
+ throw new UnsupportedGeometryError(`A face bounded by an ${type !== null && type !== void 0 ? type : `unknown #${loop}`} is not supported yet`);
59
+ }
60
+ const points = [];
61
+ for (const oriented of this.references(loop, "EdgeList")) {
62
+ const element = this.value(oriented, "EdgeElement");
63
+ if (!isReference(element)) {
64
+ throw new UnsupportedGeometryError(`#${oriented} has no edge`);
65
+ }
66
+ const along = this.edge(element.ref);
67
+ const run = this.value(oriented, "Orientation") === false ? [...along].reverse() : along;
68
+ points.push(...run.slice(0, -1));
69
+ }
70
+ return points;
71
+ }
72
+ edge(edge) {
73
+ const known = this.sampled.get(edge);
74
+ if (known) {
75
+ return known;
76
+ }
77
+ const start = this.vertex(this.value(edge, "EdgeStart"), edge);
78
+ const end = this.vertex(this.value(edge, "EdgeEnd"), edge);
79
+ const geometry = this.value(edge, "EdgeGeometry");
80
+ const points = isReference(geometry) ? this.along(geometry.ref, start, end, this.value(edge, "SameSense") !== false) : [start, end];
81
+ const exact = [start, ...points.slice(1, -1), end];
82
+ this.sampled.set(edge, exact);
83
+ return exact;
84
+ }
85
+ along(curve, start, end, sameSense) {
86
+ const type = this.model.entity(curve).type;
87
+ if (type === "IfcLine") {
88
+ return [start, end];
89
+ }
90
+ if (CURVE_ON_SURFACE.has(type)) {
91
+ const curve3 = this.model.attribute(curve, "Curve3D");
92
+ if (!isReference(curve3)) {
93
+ throw new UnsupportedGeometryError(`#${curve} has no curve in space`);
94
+ }
95
+ return this.along(curve3.ref, start, end, sameSense);
96
+ }
97
+ if (type === "IfcCircle" || type === "IfcEllipse") {
98
+ return this.conic(curve, start, end, sameSense);
99
+ }
100
+ if (SPLINES.has(type)) {
101
+ return this.spline(curve, start, end, sameSense);
102
+ }
103
+ const path = this.curves.path(curve);
104
+ const forward = distance(path[0], start) + distance(path[path.length - 1], end) <= distance(path[0], end) + distance(path[path.length - 1], start);
105
+ return forward ? path : [...path].reverse();
106
+ }
107
+ conic(conic, start, end, sameSense) {
108
+ const { origin, x, y, radii } = this.curves.conicFrame(conic);
109
+ const angleOf = (point) => {
110
+ const offset = subtract3(point, origin);
111
+ return Math.atan2(dot3(offset, y) / radii[1], dot3(offset, x) / radii[0]);
112
+ };
113
+ const first = angleOf(start);
114
+ const sweep = sweepBetween(first, angleOf(end), sameSense);
115
+ const segments = arcSegments(sweep);
116
+ return Array.from({ length: segments + 1 }, (_, step) => {
117
+ const angle = first + (sweep * step) / segments;
118
+ return add3(origin, add3(scale3(x, radii[0] * Math.cos(angle)), scale3(y, radii[1] * Math.sin(angle))));
119
+ });
120
+ }
121
+ spline(curve, start, end, sameSense) {
122
+ const spline = this.meetingVertices(this.curves.bspline(curve), start, end);
123
+ const [from, to] = knotDomain(spline.degree, spline.knots);
124
+ const closed = distance(start, end) <= CLOSED_EDGE_RATIO * Math.max(1, length3(start));
125
+ if (closed) {
126
+ return this.curves.splinePoints(spline, sameSense ? from : to, sameSense ? to : from);
127
+ }
128
+ return this.curves.splinePoints(spline, nearestParameter(spline, start, from, to), nearestParameter(spline, end, from, to));
129
+ }
130
+ meetingVertices(spline, start, end) {
131
+ if (spline.weights === undefined) {
132
+ return spline;
133
+ }
134
+ const reach = Math.max(1, length3(start), length3(end)) * WEIGHTED_POINTS_RATIO;
135
+ const misses = (candidate) => {
136
+ const [from, to] = knotDomain(candidate.degree, candidate.knots);
137
+ const [first, last] = [from, to].map((t) => nurbsCurvePoint(candidate.degree, candidate.points, candidate.weights, candidate.knots, t));
138
+ return Math.min(distance(first, start) + distance(last, end), distance(first, end) + distance(last, start));
139
+ };
140
+ if (misses(spline) <= reach) {
141
+ return spline;
142
+ }
143
+ const divided = dividedByWeights(spline);
144
+ return misses(divided) <= reach ? divided : spline;
145
+ }
146
+ vertex(value, edge) {
147
+ const geometry = isReference(value) ? this.value(value.ref, "VertexGeometry") : null;
148
+ if (!isReference(geometry)) {
149
+ throw new UnsupportedGeometryError(`#${edge} has a vertex without a point`);
150
+ }
151
+ return this.pointAt(geometry.ref);
152
+ }
153
+ pointAt(point) {
154
+ var _a, _b, _c;
155
+ const coordinates = numbersOf(this.value(point, "Coordinates"), `#${point} Coordinates`);
156
+ return [(_a = coordinates[0]) !== null && _a !== void 0 ? _a : 0, (_b = coordinates[1]) !== null && _b !== void 0 ? _b : 0, (_c = coordinates[2]) !== null && _c !== void 0 ? _c : 0];
157
+ }
158
+ value(id, name) {
159
+ return this.model.valueOf(this.model.peek(id), name);
160
+ }
161
+ references(id, name) {
162
+ return referencesIn(this.model, id, name);
163
+ }
164
+ }
@@ -0,0 +1,3 @@
1
+ import type { ModelSnapshot } from "../model/snapshot";
2
+ import type { ModelRecipe } from "./geometry-types";
3
+ export declare function modelRecipe(model: ModelSnapshot, globalIds: readonly string[] | undefined, millimetresPerUnit: number, fallbackTolerance: number, isGroupedByClass?: boolean): ModelRecipe;