@snaptrude/plugin-core 0.9.16 → 0.9.18-edgesetbacks-20261021.1

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 (221) hide show
  1. package/AGENTS.md +231 -231
  2. package/CHANGELOG.md +408 -402
  3. package/CLAUDE.md +11 -11
  4. package/api-manifest.json +1510 -701
  5. package/dist/api/analysis/daylight.d.ts +5 -5
  6. package/dist/api/analysis/solar.d.ts +2 -2
  7. package/dist/api/core/camera/index.d.ts +29 -0
  8. package/dist/api/core/camera/index.d.ts.map +1 -1
  9. package/dist/api/core/geom/create/index.d.ts +7 -1
  10. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  11. package/dist/api/core/geom/update/contour.d.ts +8 -2
  12. package/dist/api/core/geom/update/contour.d.ts.map +1 -1
  13. package/dist/api/core/storeys/index.d.ts +5 -2
  14. package/dist/api/core/storeys/index.d.ts.map +1 -1
  15. package/dist/api/core/zoom/index.d.ts +13 -0
  16. package/dist/api/core/zoom/index.d.ts.map +1 -1
  17. package/dist/api/design/bim.d.ts +299 -0
  18. package/dist/api/design/bim.d.ts.map +1 -0
  19. package/dist/api/design/boolean/index.d.ts +2 -0
  20. package/dist/api/design/boolean/index.d.ts.map +1 -1
  21. package/dist/api/design/catalog.d.ts +320 -0
  22. package/dist/api/design/catalog.d.ts.map +1 -0
  23. package/dist/api/design/create/bulk-items.d.ts +65 -25
  24. package/dist/api/design/create/bulk-items.d.ts.map +1 -1
  25. package/dist/api/design/create/index.d.ts +299 -100
  26. package/dist/api/design/create/index.d.ts.map +1 -1
  27. package/dist/api/design/create/opening-fields.d.ts +13 -12
  28. package/dist/api/design/create/opening-fields.d.ts.map +1 -1
  29. package/dist/api/design/delete/index.d.ts +1 -0
  30. package/dist/api/design/delete/index.d.ts.map +1 -1
  31. package/dist/api/design/doors/index.d.ts +18 -24
  32. package/dist/api/design/doors/index.d.ts.map +1 -1
  33. package/dist/api/design/family.d.ts +813 -35
  34. package/dist/api/design/family.d.ts.map +1 -1
  35. package/dist/api/design/index.d.ts +16 -0
  36. package/dist/api/design/index.d.ts.map +1 -1
  37. package/dist/api/design/networks.d.ts +1703 -0
  38. package/dist/api/design/networks.d.ts.map +1 -0
  39. package/dist/api/design/query/grids.d.ts +2 -2
  40. package/dist/api/design/query/index.d.ts +42 -32
  41. package/dist/api/design/query/index.d.ts.map +1 -1
  42. package/dist/api/design/selection/index.d.ts +10 -10
  43. package/dist/api/design/types/index.d.ts +40 -24
  44. package/dist/api/design/types/index.d.ts.map +1 -1
  45. package/dist/api/design/update/index.d.ts +111 -6
  46. package/dist/api/design/update/index.d.ts.map +1 -1
  47. package/dist/api/design/windows/index.d.ts +8 -8
  48. package/dist/api/design/windows/index.d.ts.map +1 -1
  49. package/dist/api/entity/buildableEnvelope.d.ts +84 -2
  50. package/dist/api/entity/buildableEnvelope.d.ts.map +1 -1
  51. package/dist/api/presentation/aiInspiration.d.ts +10 -10
  52. package/dist/api/presentation/annotate.d.ts +38 -22
  53. package/dist/api/presentation/annotate.d.ts.map +1 -1
  54. package/dist/api/presentation/drawingContract.d.ts +32 -0
  55. package/dist/api/presentation/drawingContract.d.ts.map +1 -0
  56. package/dist/api/presentation/drawings.d.ts +3523 -0
  57. package/dist/api/presentation/drawings.d.ts.map +1 -0
  58. package/dist/api/presentation/export.d.ts +23 -5
  59. package/dist/api/presentation/export.d.ts.map +1 -1
  60. package/dist/api/presentation/index.d.ts +41 -12
  61. package/dist/api/presentation/index.d.ts.map +1 -1
  62. package/dist/api/presentation/placedViews.d.ts +106 -6
  63. package/dist/api/presentation/placedViews.d.ts.map +1 -1
  64. package/dist/api/presentation/sections.d.ts +461 -0
  65. package/dist/api/presentation/sections.d.ts.map +1 -0
  66. package/dist/api/presentation/shapes.d.ts +2 -2
  67. package/dist/api/presentation/sheets.d.ts +175 -19
  68. package/dist/api/presentation/sheets.d.ts.map +1 -1
  69. package/dist/api/presentation/viewPresets.d.ts +222 -0
  70. package/dist/api/presentation/viewPresets.d.ts.map +1 -0
  71. package/dist/api/presentation/views.d.ts +141 -8
  72. package/dist/api/presentation/views.d.ts.map +1 -1
  73. package/dist/api/presentation/walkthroughs.d.ts +6 -6
  74. package/dist/errors/codes.d.ts +3 -1
  75. package/dist/errors/codes.d.ts.map +1 -1
  76. package/dist/index.cjs +3883 -2385
  77. package/dist/index.cjs.map +1 -1
  78. package/dist/index.js +3744 -2385
  79. package/dist/index.js.map +1 -1
  80. package/package.json +1 -1
  81. package/scripts/check-dist-fresh.mjs +22 -22
  82. package/scripts/generate-manifest.mjs +243 -238
  83. package/scripts/generate-manifest.test.mjs +303 -303
  84. package/src/api/analysis/daylight.ts +470 -470
  85. package/src/api/analysis/heatmaps.ts +683 -683
  86. package/src/api/analysis/illuminance.ts +155 -155
  87. package/src/api/analysis/index.ts +61 -61
  88. package/src/api/analysis/shadows.ts +183 -183
  89. package/src/api/analysis/solar.ts +237 -237
  90. package/src/api/analysis/sunlightHours.ts +211 -211
  91. package/src/api/analysis/sunpath.ts +83 -83
  92. package/src/api/analysis/weather.ts +179 -179
  93. package/src/api/core/buildings.ts +485 -485
  94. package/src/api/core/camera/index.ts +696 -667
  95. package/src/api/core/comment/index.ts +376 -376
  96. package/src/api/core/geom/create/index.ts +2139 -2133
  97. package/src/api/core/geom/delete/index.ts +84 -84
  98. package/src/api/core/geom/index.ts +35 -35
  99. package/src/api/core/geom/query/arc.ts +169 -169
  100. package/src/api/core/geom/query/brep.ts +621 -621
  101. package/src/api/core/geom/query/circle.ts +404 -404
  102. package/src/api/core/geom/query/contour.ts +540 -540
  103. package/src/api/core/geom/query/curve.ts +1102 -1102
  104. package/src/api/core/geom/query/edge.ts +130 -130
  105. package/src/api/core/geom/query/face.ts +508 -508
  106. package/src/api/core/geom/query/halfedge.ts +210 -210
  107. package/src/api/core/geom/query/index.ts +63 -63
  108. package/src/api/core/geom/query/profile.ts +473 -473
  109. package/src/api/core/geom/query/vertex.ts +205 -205
  110. package/src/api/core/geom/update/contour.ts +323 -317
  111. package/src/api/core/geom/update/curve.ts +209 -209
  112. package/src/api/core/geom/update/index.ts +29 -29
  113. package/src/api/core/geom/update/profile.ts +379 -379
  114. package/src/api/core/groups.ts +521 -521
  115. package/src/api/core/handles/index.ts +234 -234
  116. package/src/api/core/history/index.ts +84 -84
  117. package/src/api/core/index.ts +99 -99
  118. package/src/api/core/io/export/index.ts +126 -126
  119. package/src/api/core/io/import/index.ts +504 -504
  120. package/src/api/core/io/index.ts +42 -42
  121. package/src/api/core/io/job/index.ts +140 -140
  122. package/src/api/core/io/query/index.ts +71 -71
  123. package/src/api/core/io/terrain/index.ts +360 -360
  124. package/src/api/core/io/underlay/index.ts +705 -705
  125. package/src/api/core/layers.ts +540 -540
  126. package/src/api/core/math/index.ts +20 -20
  127. package/src/api/core/math/quat.ts +172 -172
  128. package/src/api/core/math/vec3.ts +179 -179
  129. package/src/api/core/mode/index.ts +108 -108
  130. package/src/api/core/project/index.ts +479 -479
  131. package/src/api/core/proposals/index.ts +570 -570
  132. package/src/api/core/storeys/index.ts +311 -308
  133. package/src/api/core/storeys/layers.ts +339 -339
  134. package/src/api/core/tags.ts +911 -911
  135. package/src/api/core/units/index.ts +164 -164
  136. package/src/api/core/user.ts +46 -46
  137. package/src/api/core/zoom/index.ts +64 -51
  138. package/src/api/design/bim.ts +210 -0
  139. package/src/api/design/boolean/index.ts +181 -179
  140. package/src/api/design/catalog.ts +203 -0
  141. package/src/api/design/create/bulk-items.ts +294 -254
  142. package/src/api/design/create/grids.ts +137 -137
  143. package/src/api/design/create/index.ts +2178 -1980
  144. package/src/api/design/create/opening-fields.ts +40 -37
  145. package/src/api/design/delete/index.ts +75 -74
  146. package/src/api/design/dimensions.ts +453 -453
  147. package/src/api/design/doors/index.ts +516 -500
  148. package/src/api/design/edit/index.ts +54 -54
  149. package/src/api/design/erase/index.ts +151 -151
  150. package/src/api/design/family.ts +1342 -704
  151. package/src/api/design/furniture/index.ts +319 -319
  152. package/src/api/design/grids.ts +237 -237
  153. package/src/api/design/index.ts +304 -274
  154. package/src/api/design/lock.ts +69 -69
  155. package/src/api/design/materials/index.ts +608 -608
  156. package/src/api/design/networks.ts +607 -0
  157. package/src/api/design/query/geometry/index.ts +319 -319
  158. package/src/api/design/query/grids.ts +197 -197
  159. package/src/api/design/query/index.ts +1236 -1226
  160. package/src/api/design/query/referenceLines.ts +52 -52
  161. package/src/api/design/query/spaces.ts +275 -275
  162. package/src/api/design/selection/index.ts +273 -273
  163. package/src/api/design/shared.ts +40 -40
  164. package/src/api/design/transform/index.ts +254 -254
  165. package/src/api/design/types/index.ts +163 -156
  166. package/src/api/design/update/index.ts +1096 -983
  167. package/src/api/design/visibility.ts +143 -143
  168. package/src/api/design/windows/index.ts +351 -333
  169. package/src/api/entity/buildableEnvelope.ts +397 -322
  170. package/src/api/entity/index.ts +30 -30
  171. package/src/api/entity/referenceLine.ts +235 -235
  172. package/src/api/entity/space.ts +612 -612
  173. package/src/api/entity/story.ts +517 -517
  174. package/src/api/index.ts +47 -47
  175. package/src/api/presentation/aiInspiration.ts +1116 -1116
  176. package/src/api/presentation/annotate.ts +417 -385
  177. package/src/api/presentation/camera.ts +289 -289
  178. package/src/api/presentation/diagrams.ts +194 -194
  179. package/src/api/presentation/drawingContract.ts +70 -0
  180. package/src/api/presentation/drawings.ts +1179 -0
  181. package/src/api/presentation/export.ts +134 -108
  182. package/src/api/presentation/import.ts +159 -159
  183. package/src/api/presentation/index.ts +160 -126
  184. package/src/api/presentation/placedViews.ts +1211 -1120
  185. package/src/api/presentation/sections.ts +420 -0
  186. package/src/api/presentation/shapes.ts +274 -274
  187. package/src/api/presentation/sheets.ts +746 -585
  188. package/src/api/presentation/slideshow.ts +134 -134
  189. package/src/api/presentation/tables.ts +84 -84
  190. package/src/api/presentation/viewPresets.ts +241 -0
  191. package/src/api/presentation/views.ts +742 -673
  192. package/src/api/presentation/walkthroughs.ts +1201 -1201
  193. package/src/api/program/adjacency.ts +164 -164
  194. package/src/api/program/areas.ts +408 -408
  195. package/src/api/program/classification.ts +138 -138
  196. package/src/api/program/cores.ts +83 -83
  197. package/src/api/program/departments.ts +517 -517
  198. package/src/api/program/index.ts +73 -73
  199. package/src/api/program/labels.ts +332 -332
  200. package/src/api/program/layout.ts +422 -422
  201. package/src/api/program/metadata.ts +364 -364
  202. package/src/api/program/metrics.ts +120 -120
  203. package/src/api/program/site.ts +748 -748
  204. package/src/api/program/spreadsheet.ts +2698 -2698
  205. package/src/api/workspace/index.ts +612 -612
  206. package/src/errors/codes.ts +143 -136
  207. package/src/errors/envelope.ts +75 -75
  208. package/src/errors/index.ts +21 -21
  209. package/src/errors/plugin-error.ts +134 -134
  210. package/src/handles.ts +297 -297
  211. package/src/host-utils.ts +67 -67
  212. package/src/index.ts +7 -7
  213. package/src/massParameters.ops.ts +246 -246
  214. package/src/massParameters.ts +427 -427
  215. package/src/types.ts +16 -16
  216. package/test/drawingContract.test.mjs +288 -0
  217. package/test/errors.test.mjs +185 -184
  218. package/test/massParameters.test.mjs +952 -952
  219. package/test/presentationViewSettings.test.mjs +85 -0
  220. package/tsconfig.json +22 -22
  221. package/tsup.config.ts +12 -12
@@ -1,2133 +1,2139 @@
1
- import * as z from "zod"
2
- import { PluginApiReturn } from "../../../../types"
3
- import {
4
- MAX_SPLINE_THROUGH_POINTS,
5
- MAX_SPLINE_POLES,
6
- MAX_PATH_CURVES,
7
- MAX_TWIST_LEVELS,
8
- MAX_FILLET_LAW_SAMPLES,
9
- } from "../../../../massParameters"
10
- import type { SweepAlongCurveRecordOptions } from "../../../../massParameters"
11
- import { Vec3Handle, LineHandle, ArcHandle, SplineHandle, CircleHandle, CurveHandle, ProfileHandle, ContourHandle, BrepHandle, FaceHandle, EdgeHandle, Vec3Components } from "../../../../handles"
12
-
13
- /** Maximum boundary items one patch may span. */
14
- export const MAX_PATCH_BOUNDARY_CURVES = 16
15
- /** Maximum interior points one patch may be pulled through. */
16
- export const MAX_PATCH_INTERIOR_POINTS = 64
17
-
18
- /**
19
- * An edge named together with the solid it belongs to — an edge handle cannot
20
- * name its own solid, and one patch may meet two solids. `face` is the adjacent
21
- * face a tangent patch continues: REQUIRED under `continuity: "tangent"`
22
- * (a solid edge has two faces — the wall or the top of a rim — and only the
23
- * caller knows which), ignored otherwise. Owner LAST, like every read that
24
- * takes `(face, brep)`.
25
- */
26
- export type OwnedEdge = { edge: EdgeHandle; brep: BrepHandle; face?: FaceHandle }
27
-
28
- /**
29
- * The trailing bag of {@linkcode PluginGeomCreateApi.brepFromSweepAlongCurve}:
30
- * the recorded switches plus `rail`, a live curve handle that never enters a
31
- * record (a record is pure JSON, and a dead handle would not regenerate).
32
- */
33
- export type SweepAlongCurveOptions = SweepAlongCurveRecordOptions & { rail?: CurveHandle }
34
-
35
- /**
36
- * Curve creation — construct new geometric curves from point handles (all-handle
37
- * model, §11).
38
- *
39
- * Each method takes point handles ({@linkcode Vec3Handle}) and returns an opaque
40
- * curve handle ({@linkcode LineHandle}, {@linkcode ArcHandle}, or
41
- * {@linkcode SplineHandle}) that you pass to query, update, or entity-creation
42
- * methods. Read geometry back as plain values via the
43
- * `snaptrude.core.geom.query.*` methods.
44
- *
45
- * Accessed via `snaptrude.core.geom.create`.
46
- */
47
- export abstract class PluginGeomCreateApi {
48
- constructor() {}
49
-
50
- /**
51
- * Create a straight line segment between two point handles. Host API call —
52
- * returns a handle.
53
- *
54
- * @param startPoint Start point of the line segment
55
- * @param endPoint End point of the line segment
56
- * @returns The new line as a {@linkcode LineHandle}
57
- *
58
- * @examplePrompt Draw a line from point A to point B
59
- * @examplePrompt Draw a straight edge between these two points
60
- *
61
- * @example
62
- * ```ts
63
- * const line = snaptrude.core.geom.create.line(startPoint, endPoint)
64
- * ```
65
- */
66
- public abstract line(startPoint: Vec3Handle, endPoint: Vec3Handle): PluginApiReturn<LineHandle>
67
-
68
- /**
69
- * Create a circular arc from its start, end, centre, and axis point handles.
70
- * Host API call — returns a handle.
71
- *
72
- * @param startPoint Start point of the arc
73
- * @param endPoint End point of the arc
74
- * @param centrePoint Centre point of the arc
75
- * @param axis Axis direction of the arc
76
- * @returns The new arc as an {@linkcode ArcHandle}
77
- *
78
- * @examplePrompt Draw an arc between these two points around a centre
79
- * @examplePrompt Add a curved segment from A to B
80
- *
81
- * @example
82
- * ```ts
83
- * const arc = snaptrude.core.geom.create.arc(startPoint, endPoint, centrePoint, axis)
84
- * ```
85
- */
86
- public abstract arc(
87
- startPoint: Vec3Handle,
88
- endPoint: Vec3Handle,
89
- centrePoint: Vec3Handle,
90
- axis: Vec3Handle,
91
- ): PluginApiReturn<ArcHandle>
92
-
93
- /**
94
- * Create a circle from a centre point, an axis (plane normal), and a radius.
95
- * Host API call — returns a handle. A circle is a closed curve; read it back via
96
- * `snaptrude.core.geom.query.circle.*`.
97
- *
98
- * @param centrePoint Centre point of the circle
99
- * @param axis Axis direction (plane normal) of the circle
100
- * @param radius Radius of the circle
101
- * @returns The new circle as a {@linkcode CircleHandle}
102
- *
103
- * @examplePrompt Make a circle of radius 2 at this point
104
- * @examplePrompt Draw a circular outline 5m across
105
- *
106
- * @example
107
- * ```ts
108
- * const circle = snaptrude.core.geom.create.circle(centrePoint, axis, 2)
109
- * ```
110
- */
111
- public abstract circle(
112
- centrePoint: Vec3Handle,
113
- axis: Vec3Handle,
114
- radius: number,
115
- ): PluginApiReturn<CircleHandle>
116
-
117
- /**
118
- * Create an open NURBS curve passing exactly THROUGH each point, in order
119
- * (centripetal Catmull-Rom fit → clamped cubic B-spline; C1-smooth, no cusps
120
- * on uneven spacing). Host API call — returns a handle. The points are
121
- * interpolated, NOT control points. For a CLOSED smooth loop use
122
- * {@linkcode PluginGeomCreateApi.profileFromSplinePoints} — closed splines
123
- * exist only as profiles, never as a single curve handle.
124
- *
125
- * Pure host-side math — the geometry kernel is not loaded. The result is a
126
- * {@linkcode CurveHandle}: pass it to `profileFromCurves` alongside lines and
127
- * arcs, or read it back via `core.geom.query.curve.*`.
128
- *
129
- * @param points 3..256 ordered through-points; consecutive points must be distinct
130
- * @returns The new open curve as a {@linkcode SplineHandle}
131
- * @throws VALIDATION if fewer than 3 points (2 points are a straight line —
132
- * use {@linkcode PluginGeomCreateApi.line}), more than 256 points, or two
133
- * consecutive points coincide
134
- *
135
- * @examplePrompt Draw a smooth curve through these points
136
- * @examplePrompt Make a wavy edge that passes through A, B, C and D
137
- * @examplePrompt Freeform curved wall path through these clicks
138
- *
139
- * # Example
140
- * ```ts
141
- * const spline = await snaptrude.core.geom.create.splineFromPoints([p0, p1, p2, p3])
142
- * // Mixed profile: smooth top, straight closing base
143
- * const base = await snaptrude.core.geom.create.line(p3, p0)
144
- * const profile = await snaptrude.core.geom.create.profileFromCurves([spline, base])
145
- * ```
146
- */
147
- public abstract splineFromPoints(points: Vec3Handle[]): PluginApiReturn<SplineHandle>
148
-
149
- /**
150
- * Create an open NURBS curve DEFINED BY control points — the curve is pulled
151
- * TOWARD the interior points, not through them; it starts at the first point
152
- * and ends at the last. Knots are computed for you (clamped, uniform) —
153
- * there is no knot-vector argument. For a curve that must pass THROUGH given
154
- * points use {@linkcode PluginGeomCreateApi.splineFromPoints}.
155
- *
156
- * A curve made here records as `splinePoles` in a mass recipe and is
157
- * re-minted verbatim on edit — so an agent that read a `splinePoles` curve
158
- * back off a mass can move its poles and rebuild the mass with
159
- * `design.update.parameters`. Pure host-side math — the geometry kernel is
160
- * not loaded, and nothing is created in the scene. An edge's control points
161
- * rebuild the same curve here only when its knots are clamped uniform.
162
- *
163
- * @param controlPoints 3..512 control points ({@linkcode Vec3Handle}s), in order
164
- * @param options.degree 2 or 3; default `min(3, controlPoints.length - 1)`
165
- * @returns The new open curve as a {@linkcode SplineHandle}
166
- * @throws VALIDATION when fewer than `degree + 1` points are given, two
167
- * consecutive points coincide, or `degree` is not 2 or 3
168
- *
169
- * @examplePrompt Draw a smooth curve controlled by these four points
170
- *
171
- * # Example
172
- * ```ts
173
- * const curve = await snaptrude.core.geom.create.splineFromControlPoints([p0, p1, p2, p3])
174
- * // Quadratic instead of the default cubic
175
- * const tighter = await snaptrude.core.geom.create.splineFromControlPoints(
176
- * [p0, p1, p2, p3],
177
- * { degree: 2 },
178
- * )
179
- * ```
180
- */
181
- public abstract splineFromControlPoints(
182
- controlPoints: Vec3Handle[],
183
- options?: { degree?: number },
184
- ): PluginApiReturn<SplineHandle>
185
-
186
- /**
187
- * Drops a curve onto a face along the face's normal and returns the curves it
188
- * lands on — one per connected run, empty when the curve misses the face
189
- * entirely. Host API call — returns new curve handles; the inputs are
190
- * read-only. No lengths are involved: the result is a curve in the same
191
- * internal length units as its inputs.
192
- *
193
- * @param curve The curve to project
194
- * @param face The face to project onto
195
- * @param faceBrep The solid `face` belongs to — a face handle cannot name its
196
- * own solid
197
- * @returns The projected curves as {@linkcode CurveHandle}`[]`
198
- * @throws VALIDATION if `face` is not a face of `faceBrep`, or the curve's
199
- * midpoint lies on the face's surface (there is no drop direction there)
200
- * @throws OPERATION_FAILED if the kernel could not project the curve
201
- */
202
- public abstract curvesFromProjection(
203
- curve: CurveHandle,
204
- face: FaceHandle,
205
- faceBrep: BrepHandle,
206
- ): PluginApiReturn<CurveHandle[]>
207
-
208
- /**
209
- * Create a closed profile from an ordered list of point handles connected by
210
- * line segments (last auto-connected to first). Host API call — returns a handle.
211
- *
212
- * @param points Ordered vertex handles of the profile
213
- * @returns The new profile as a {@linkcode ProfileHandle}
214
- *
215
- * @examplePrompt Draw a closed polygon through these corner points
216
- * @examplePrompt Create an L-shaped outline from a list of points
217
- *
218
- * @example
219
- * ```ts
220
- * const profile = snaptrude.core.geom.create.profileFromLinePoints(points)
221
- * ```
222
- */
223
- public abstract profileFromLinePoints(points: Vec3Handle[]): PluginApiReturn<ProfileHandle>
224
-
225
- /**
226
- * Create a profile from an ordered list of curve handles forming a closed loop.
227
- * Host API call — returns a handle.
228
- *
229
- * @param curves Ordered curve handles forming a closed loop
230
- * @returns The new profile as a {@linkcode ProfileHandle}
231
- *
232
- * @examplePrompt Join these lines and arcs into one closed shape
233
- *
234
- * @example
235
- * ```ts
236
- * const profile = snaptrude.core.geom.create.profileFromCurves([line, arc])
237
- * ```
238
- */
239
- public abstract profileFromCurves(curves: CurveHandle[]): PluginApiReturn<ProfileHandle>
240
-
241
- /**
242
- * Create a CLOSED smooth profile passing through each point, in order, with
243
- * the loop closed smoothly back from the last point to the first (do NOT
244
- * repeat the first point). Host API call — returns a handle. This is the
245
- * closed counterpart of {@linkcode PluginGeomCreateApi.splineFromPoints} and
246
- * the one-call route from points to an extrudable outline: wrap in
247
- * `contourFromProfile` and extrude/loft/sweep as usual.
248
- *
249
- * The whole loop is smooth — this call takes no mixed straight/spline input.
250
- * For a profile that is part curve and part straight, build the pieces with
251
- * `splineFromPoints` + `line` and join them with
252
- * {@linkcode PluginGeomCreateApi.profileFromCurves}.
253
- *
254
- * @param points 3..256 ordered through-points of the loop (first point NOT repeated)
255
- * @returns The new closed profile as a {@linkcode ProfileHandle}
256
- * @throws VALIDATION if fewer than 3 points, more than 256 points,
257
- * consecutive points coincide (including last→first), or the loop
258
- * self-intersects
259
- *
260
- * @examplePrompt Make a blob-shaped floor plate through these points
261
- * @examplePrompt Organic curved outline through these corners
262
- * @examplePrompt Freeform podium profile
263
- *
264
- * # Example
265
- * ```ts
266
- * const profile = await snaptrude.core.geom.create.profileFromSplinePoints([p0, p1, p2, p3, p4])
267
- * const contour = await snaptrude.core.geom.create.contourFromProfile(profile)
268
- * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)
269
- * const podium = await snaptrude.design.create.massFromBrep(brep, "Podium")
270
- * ```
271
- */
272
- public abstract profileFromSplinePoints(points: Vec3Handle[]): PluginApiReturn<ProfileHandle>
273
-
274
- /**
275
- * Create an axis-aligned **rectangle** profile in the XZ plane,
276
- * `width` × `depth`, centred at `center` (defaults to the origin). Host API
277
- * call — returns a handle.
278
- *
279
- * Pair with `design.create.space` / `design.create.spaces` to author a rectangular
280
- * (box) space — there is no dedicated rectangular-space creator; build the profile
281
- * here, then extrude it.
282
- *
283
- * @param width Extent along X
284
- * @param depth Extent along Z
285
- * @param center Optional centre point (default origin)
286
- * @returns The new rectangle profile as a {@linkcode ProfileHandle}
287
- *
288
- * @examplePrompt Draw a 4m by 3m rectangle
289
- * @examplePrompt Make a rectangular outline for a room footprint
290
- *
291
- * @example
292
- * ```ts
293
- * const rect = snaptrude.core.geom.create.profileRect(4, 3)
294
- * const contour = snaptrude.core.geom.create.contourFromProfile(rect)
295
- * const space = snaptrude.design.create.space(contour, 3)
296
- * ```
297
- */
298
- public abstract profileRect(
299
- width: number,
300
- depth: number,
301
- center?: Vec3Handle,
302
- ): PluginApiReturn<ProfileHandle>
303
-
304
- /**
305
- * Create a contour (an outer profile loop, no holes). Host API call — returns a handle.
306
- *
307
- * @param outer The outer boundary loop
308
- * @returns The new contour as a {@linkcode ContourHandle}
309
- * @example
310
- * ```ts
311
- * const contour = snaptrude.core.geom.create.contourFromProfile(outer)
312
- * ```
313
- */
314
- public abstract contourFromProfile(outer: ProfileHandle): PluginApiReturn<ContourHandle>
315
-
316
- /**
317
- * Create a contour from an outer profile plus zero or more inner profiles (holes).
318
- * Host API call — returns a handle.
319
- *
320
- * @param outer The outer boundary loop
321
- * @param holes Optional inner hole loops
322
- * @returns The new contour as a {@linkcode ContourHandle}
323
- *
324
- * @examplePrompt Make a shape with a hole in the middle
325
- * @examplePrompt Cut a courtyard opening out of this floor outline
326
- *
327
- * @example
328
- * ```ts
329
- * const contour = snaptrude.core.geom.create.contourFromProfiles(outer, [hole])
330
- * ```
331
- */
332
- public abstract contourFromProfiles(
333
- outer: ProfileHandle,
334
- holes?: ProfileHandle[],
335
- ): PluginApiReturn<ContourHandle>
336
-
337
- /**
338
- * Create a closed solid **B-rep** from explicit face loops. Host API call —
339
- * returns a {@linkcode BrepHandle}. Faces are plain arrays of `{x, y, z}`
340
- * point components, NOT point handles (bulk-data precedent:
341
- * `design.query.geometry.getTriangulatedMeshes`), in raw Babylon units.
342
- *
343
- * Each face is one planar loop of at least 3 points; at least 4 faces are
344
- * required. Loops may be authored in any consistent winding — the host
345
- * validates edge coherence (every edge shared by exactly two faces, in
346
- * opposite directions) and fixes the global orientation so faces point
347
- * outward. Faces must be planar and the solid must be closed; holes in faces
348
- * are not supported (v1).
349
- *
350
- * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
351
- * with `design.create.massFromBrep`.
352
- *
353
- * @param faces Face loops — each an ordered array of `{x, y, z}` points (≥3 points per face, ≥4 faces)
354
- * @returns The new solid as a {@linkcode BrepHandle}
355
- * @throws if a face is degenerate or non-planar, an edge is not shared by
356
- * exactly two faces (open shell, non-manifold, or inconsistent winding), or
357
- * the faces do not form a valid closed solid
358
- *
359
- * @examplePrompt Create a brep from faces
360
- * @examplePrompt Build a custom solid from a set of faces
361
- * @examplePrompt Make a pyramid from triangular faces
362
- *
363
- * # Example
364
- * ```ts
365
- * // A pyramid: square base + 4 triangular sides
366
- * const apex = { x: 0, y: 4, z: 0 }
367
- * const a = { x: -2, y: 0, z: -2 }
368
- * const b = { x: 2, y: 0, z: -2 }
369
- * const c = { x: 2, y: 0, z: 2 }
370
- * const d = { x: -2, y: 0, z: 2 }
371
- * const brep = await snaptrude.core.geom.create.brepFromFaces([
372
- * [a, d, c, b], // base
373
- * [a, b, apex],
374
- * [b, c, apex],
375
- * [c, d, apex],
376
- * [d, a, apex],
377
- * ])
378
- * const faceCount = await snaptrude.core.geom.query.brep.getFaceCount(brep) // 5
379
- * ```
380
- */
381
- public abstract brepFromFaces(faces: Vec3Components[][]): PluginApiReturn<BrepHandle>
382
-
383
- /**
384
- * Create a closed solid **B-rep** by extruding a contour along a direction.
385
- * Host API call — returns a {@linkcode BrepHandle}. The direction is
386
- * normalised by the host, so `amount` is the extrusion distance in raw
387
- * Babylon units (negative extrudes the opposite way). The contour is copied —
388
- * the input handle is never mutated. Holes and arc/circle profiles extrude
389
- * natively (a circle profile yields a cylinder).
390
- *
391
- * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
392
- * with `design.create.massFromBrep`.
393
- *
394
- * @param contour The cross-section to extrude (outer profile + optional holes)
395
- * @param direction Extrusion direction as plain `{x, y, z}` components (non-zero, normalised by the host)
396
- * @param amount Extrusion distance in internal length units (non-zero; negative extrudes opposite to `direction`)
397
- * @param options Optional twist and taper, both about `direction` through the
398
- * base contour's centroid. `twistInDegrees` — total rotation base → top in
399
- * DEGREES (an angle: never scaled by the project's length unit), positive
400
- * turning the top CLOCKWISE seen from the far end of `direction` looking
401
- * back at the base — for the usual upward extrusion, the plan view;
402
- * `topScale` — the top outline's scale, a ratio (1 = no taper); `levels` —
403
- * how many sections to build through, a count (2..64; each step may turn
404
- * at most 22.5°). Leave `levels` out
405
- * and the host resolves one and writes it into the record, so the mass
406
- * regenerates identically ever after. An extrusion without twist or
407
- * taper (no `options`, or zero twist and a `topScale` of 1) is
408
- * built exactly as before these options existed.
409
- * @returns The new solid as a {@linkcode BrepHandle}
410
- * @throws if the extrusion is degenerate (zero amount, zero direction, or a
411
- * direction lying in the plane of the contour) or the contour cannot be
412
- * extruded into a valid solid
413
- * @throws VALIDATION if `twistInDegrees` is not finite or turns more than
414
- * 1440°, `topScale` is not a positive finite ratio, `levels` is outside
415
- * 2..64 or too few for the twist (more than 22.5° per step), a twist is
416
- * asked of a `direction` that is not the contour's normal, the contour has
417
- * holes under a twist or taper, or a scaled section self-intersects
418
- *
419
- * @examplePrompt Extrude this profile 3 metres up
420
- * @examplePrompt Make a solid by extruding this outline
421
- * @examplePrompt Create a cylinder from a circle profile
422
- * @examplePrompt Extrude this floor plate 40m up with a 90 degree twist
423
- * @examplePrompt Taper this tower to 60 percent of its footprint at the top
424
- *
425
- * # Example
426
- * ```ts
427
- * const rect = await snaptrude.core.geom.create.profileRect(4, 3)
428
- * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)
429
- * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)
430
- * const faceCount = await snaptrude.core.geom.query.brep.getFaceCount(brep) // 6
431
- * ```
432
- */
433
- public abstract brepFromExtrusion(
434
- contour: ContourHandle,
435
- direction: Vec3Components,
436
- amount: number,
437
- options?: { twistInDegrees?: number; topScale?: number; levels?: number },
438
- ): PluginApiReturn<BrepHandle>
439
-
440
- /**
441
- * Stretches a surface across a closed loop of boundary curves and thickens it
442
- * into a SOLID in one call — a roof over four curves, a canopy between two
443
- * rails, a blend across a gap. `thickness` is in internal length units and is
444
- * required: only solids reach the canvas, and there is no separate surface
445
- * object to keep. Pass edges as `{ edge, brep, face }` with
446
- * `continuity: "tangent"` to leave the named face smoothly. The result carries
447
- * NO recipe (`design.query.getParameters` returns `null`) — label it when you
448
- * commit it, because the label is all the provenance there is.
449
- *
450
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
451
- * of MB — expect a pause of seconds).
452
- *
453
- * @param boundary 3..16 items forming ONE closed loop (ends meeting within
454
- * tolerance): a curve, or an edge named WITH the solid it belongs to
455
- * ({@linkcode OwnedEdge}) — an edge handle cannot name its own solid, and
456
- * one patch may meet two solids. Read scene breps with
457
- * `design.query.geometry.getBrep(x, { frame: "world" })` so every item is in
458
- * one frame.
459
- * @param thickness Solid thickness in internal length units (> 0)
460
- * @param options `interiorPoints` pull the surface through them (≤ 64, plain
461
- * `{x, y, z}` in the same frame as the boundary). `continuity` —
462
- * `"position"` (default) or `"tangent"`: every boundary item must then be an
463
- * edge item that also names the `face` to continue (a solid edge has two
464
- * faces — the wall or the top of a rim). `side` — `"below"` (default) or
465
- * `"above"`: which side of the boundary the solid grows to.
466
- * @returns The thickened patch as a new {@linkcode BrepHandle}
467
- * @throws VALIDATION if the boundary is not one closed loop (the message names
468
- * the gap and its size), a count cap is exceeded, `thickness` is not
469
- * positive, `continuity: "tangent"` is used with a curve item or with an
470
- * edge item that names no `face` (the message names the edge's two candidate
471
- * faces), or a `face` is not adjacent to its `edge`
472
- * @throws OPERATION_FAILED if the kernel could not fill the boundary, the
473
- * filled surface misses its own boundary by more than 0.254 mm (the message
474
- * carries the measured gap), or the thicken produced no solid
475
- *
476
- * @examplePrompt Patch a roof over these four boundary curves, 250 mm thick
477
- * @examplePrompt Span a canopy between these two curved rails, 300 mm thick
478
- *
479
- * # Example
480
- * ```ts
481
- * // A roof over four curves
482
- * const roof = await snaptrude.core.geom.create.brepFromPatch(
483
- * [curveA, curveB, curveC, curveD],
484
- * 0.984, // 250 mm
485
- * )
486
- * const mass = await snaptrude.design.create.massFromBrep(roof, "Roof patch")
487
- * ```
488
- *
489
- * ```ts
490
- * // A canopy between two towers' rims, leaving every wall it meets smoothly:
491
- * // with `continuity: "tangent"` EVERY item is an edge naming its face.
492
- * const canopy = await snaptrude.core.geom.create.brepFromPatch(
493
- * [
494
- * { edge: rimA, brep: towerA, face: wallA },
495
- * { edge: sideA, brep: towerA, face: endA },
496
- * { edge: rimB, brep: towerB, face: wallB },
497
- * { edge: sideB, brep: towerB, face: endB },
498
- * ],
499
- * 1.181, // 300 mm
500
- * { continuity: "tangent" },
501
- * )
502
- * ```
503
- */
504
- public abstract brepFromPatch(
505
- boundary: (CurveHandle | OwnedEdge)[],
506
- thickness: number,
507
- options?: {
508
- interiorPoints?: Vec3Components[]
509
- continuity?: "position" | "tangent"
510
- side?: "below" | "above"
511
- },
512
- ): PluginApiReturn<BrepHandle>
513
-
514
- /**
515
- * Create a closed solid **B-rep** by lofting between a bottom and a top
516
- * contour, optionally through intermediate cross-sections. Host API call —
517
- * returns a {@linkcode BrepHandle}. Sections with different edge counts are
518
- * auto-matched: vertices are deterministically inserted on the smaller
519
- * sections at the larger section's perimeter positions, so the same input
520
- * always yields the same solid. For straight-edge (all-line) sections the
521
- * seam is auto-aligned too — winding and start-vertex correspondence are
522
- * chosen to avoid a twist, so outlines authored from any starting corner
523
- * loft cleanly; arc-bearing sections keep the authored correspondence
524
- * unchanged. A pair whose side faces would be non-planar under every
525
- * correspondence still builds for hole-free straight-edged pairs — planar
526
- * side quads stay quads and warped ones split into planar triangles (the
527
- * triangulated fallback); arc-bearing or holed twisted pairs are rejected, as
528
- * is auto-matching that would pair an arc with a straight edge. Matching hole
529
- * counts are allowed in the two-section form. With intermediates the loft
530
- * is a chain of ruled segments folded into one solid: sections must be
531
- * planar and hole-free, ordered bottom → intermediates → top. The contours
532
- * are copied — the input handles are never mutated.
533
- *
534
- * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
535
- * with `design.create.massFromBrep`.
536
- *
537
- * @param bottomContour The bottom cross-section
538
- * @param topContour The top cross-section (edge count may differ from the
539
- * bottom — sections are auto-matched)
540
- * @param intermediateContours Optional in-between cross-sections, ordered
541
- * bottom to top (hole-free; edge counts may differ — every section is
542
- * normalized to the largest). Any count is accepted; a loft of more
543
- * than 32 sections counting the bottom and the top still builds but
544
- * carries no parameter record (records are capped, calls are not)
545
- * @param options Optional behavior switches. `compatibility` — `"auto"`
546
- * (default) inserts vertices to match differing edge counts; `"strict"`
547
- * skips normalization and throws VALIDATION when outer edge counts (or,
548
- * in the two-section form, hole-pair counts) differ. `seamAlignment` —
549
- * `"auto"` (default) searches seam rotations on straight-edge sections to
550
- * avoid a twist; `"authored"` keeps the authored start-vertex
551
- * correspondence unchanged (a twisted correspondence then builds through
552
- * the triangulated fallback for hole-free straight-edged pairs).
553
- * `{ compatibility: "strict", seamAlignment: "authored" }` reproduces
554
- * pre-0.9.4 lofting exactly. `smoothing` — `"ruled"` (default) keeps the
555
- * straight-sided behaviour described above; `"smooth"` lofts every
556
- * section in ONE kernel pass, so the sides curve continuously through the
557
- * intermediates instead of creasing at each one (a vase, a curved tower —
558
- * the sections must still be planar and hole-free).
559
- * @returns The new solid as a {@linkcode BrepHandle}
560
- * @throws VALIDATION if the contours are coincident, their hole counts
561
- * differ, any section has holes when intermediates are present
562
- * (multi-section lofts take hole-free sections), a section is non-planar,
563
- * sections are too dissimilar to auto-match, auto-matching would pair an
564
- * arc with a straight edge, or — for arc-bearing or holed pairs only — no vertex correspondence avoids a twist (a
565
- * side face between corresponding edges would be non-planar — the error
566
- * names the offending sections); with `compatibility: "strict"`, if
567
- * section edge counts (or hole-pair counts) differ — the error names the
568
- * sections and counts and points at `compatibility: "auto"`
569
- * @throws OPERATION_FAILED if the lofted segments cannot be joined into a
570
- * valid solid
571
- *
572
- * @examplePrompt Make a tapered tower from these two outlines
573
- * @examplePrompt Loft between a large base and a smaller top
574
- * @examplePrompt Create a frustum from two squares
575
- * @examplePrompt Loft the tower through these three floor outlines
576
- * @examplePrompt Loft a four-sided base to a five-sided top
577
- * @examplePrompt Loft these outlines even though their corners don't line up
578
- * @examplePrompt Loft these outlines exactly as authored, corner to corner
579
- * @examplePrompt Fail the loft if my sections don't have matching edge counts
580
- * @examplePrompt Loft these three outlines with smoothly curving sides
581
- *
582
- * # Example
583
- * ```ts
584
- * // A square frustum: 4m base lofted to a 2m top, 3m up
585
- * const base = await snaptrude.core.geom.create.profileRect(4, 4)
586
- * const top = await snaptrude.core.geom.create.profileRect(2, 2, await snaptrude.core.math.vec3.new(0, 3, 0))
587
- * const brep = await snaptrude.core.geom.create.brepFromLoft(
588
- * await snaptrude.core.geom.create.contourFromProfile(base),
589
- * await snaptrude.core.geom.create.contourFromProfile(top),
590
- * )
591
- * const faceCount = await snaptrude.core.geom.query.brep.getFaceCount(brep) // 6
592
- * ```
593
- */
594
- public abstract brepFromLoft(
595
- bottomContour: ContourHandle,
596
- topContour: ContourHandle,
597
- intermediateContours?: ContourHandle[],
598
- options?: {
599
- compatibility?: "strict" | "auto"
600
- seamAlignment?: "authored" | "auto"
601
- smoothing?: "ruled" | "smooth"
602
- },
603
- ): PluginApiReturn<BrepHandle>
604
-
605
- /**
606
- * Create a closed solid **B-rep** from indexed mesh data: a vertex position
607
- * array plus face loops of indices into it. Host API call — returns a
608
- * {@linkcode BrepHandle}. The indexed form of
609
- * {@linkcode PluginGeomCreateApi.brepFromFaces} — same validation (planar
610
- * faces, closed manifold solid, every edge shared by exactly two faces) after
611
- * the indices are expanded to point loops.
612
- *
613
- * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
614
- * with `design.create.massFromBrep`.
615
- *
616
- * @param positions Vertex positions as plain `{x, y, z}` components (≥4)
617
- * @param faces Face loops — each an ordered array of indices into `positions` (≥3 indices per face, ≥4 faces)
618
- * @returns The new solid as a {@linkcode BrepHandle}
619
- * @throws if an index is not an integer within `positions` bounds, a face is
620
- * degenerate or non-planar, or the faces do not form a valid closed solid
621
- *
622
- * @examplePrompt Create a brep from mesh vertices and indices
623
- * @examplePrompt Turn this indexed mesh into a solid
624
- * @examplePrompt Build a solid from vertex positions and face indices
625
- *
626
- * # Example
627
- * ```ts
628
- * // A tetrahedron from 4 vertices and 4 triangular faces
629
- * const positions = [
630
- * { x: 0, y: 0, z: 0 },
631
- * { x: 4, y: 0, z: 0 },
632
- * { x: 2, y: 0, z: 4 },
633
- * { x: 2, y: 3, z: 1.5 },
634
- * ]
635
- * const brep = await snaptrude.core.geom.create.brepFromMesh(positions, [
636
- * [0, 2, 1], // base
637
- * [0, 1, 3],
638
- * [1, 2, 3],
639
- * [2, 0, 3],
640
- * ])
641
- * const vertexCount = await snaptrude.core.geom.query.brep.getVertexCount(brep) // 4
642
- * ```
643
- */
644
- public abstract brepFromMesh(
645
- positions: Vec3Components[],
646
- faces: number[][],
647
- ): PluginApiReturn<BrepHandle>
648
-
649
- /**
650
- * Create a closed solid **B-rep** as the boolean **union** of two breps
651
- * (`a ∪ b`). Host API call — returns a new {@linkcode BrepHandle}. Inputs are
652
- * read-only and may be authored breps (any `core.geom.create` brep
653
- * constructor) or scene-derived breps from `design.query.geometry.getBrep`;
654
- * coordinates are combined as-is, so both inputs must share a frame. The
655
- * result must be a single solid — disjoint inputs are rejected.
656
- *
657
- * The first boolean call loads the OpenCascade geometry kernel (a wasm of
658
- * tens of MB — expect a pause of seconds); subsequent calls are fast.
659
- *
660
- * @param a First solid
661
- * @param b Second solid
662
- * @returns The union as a new {@linkcode BrepHandle}
663
- * @throws if the inputs do not overlap or touch (the union would be disjoint
664
- * solids) or the result cannot be built as a valid single solid
665
- *
666
- * @examplePrompt Union these two solids
667
- * @examplePrompt Merge these breps into one mass
668
- * @examplePrompt Combine the tower and the podium into a single solid
669
- *
670
- * # Example
671
- * ```ts
672
- * const a = await snaptrude.core.geom.create.brepFromExtrusion(baseContour, { x: 0, y: 1, z: 0 }, 3)
673
- * const b = await snaptrude.core.geom.create.brepFromExtrusion(overlappingContour, { x: 0, y: 1, z: 0 }, 5)
674
- * const merged = await snaptrude.core.geom.create.brepFromUnion(a, b)
675
- * ```
676
- */
677
- public abstract brepFromUnion(a: BrepHandle, b: BrepHandle): PluginApiReturn<BrepHandle>
678
-
679
- /**
680
- * Create a closed solid **B-rep** as the boolean **subtraction** of two
681
- * breps: **`a` minus `b`** — `b` is cut away from `a`. Host API call —
682
- * returns a new {@linkcode BrepHandle}. Argument order matters:
683
- * `brepFromSubtraction(a, b)` removes `b`'s volume from `a`. Inputs are
684
- * read-only and may be authored or scene-derived breps (see
685
- * {@linkcode PluginGeomCreateApi.brepFromUnion}); coordinates are combined
686
- * as-is.
687
- *
688
- * The first boolean call loads the OpenCascade geometry kernel (a wasm of
689
- * tens of MB — expect a pause of seconds); subsequent calls are fast.
690
- *
691
- * @param a The solid to subtract from
692
- * @param b The solid to remove from `a`
693
- * @returns `a` minus `b` as a new {@linkcode BrepHandle}
694
- * @throws if `b` consumes `a` entirely (empty result), the result splits into
695
- * disjoint solids, or it cannot be built as a valid single solid
696
- *
697
- * @examplePrompt Subtract the courtyard from the building mass
698
- * @examplePrompt Cut this solid out of that one
699
- * @examplePrompt Punch a hole through the slab with this box
700
- *
701
- * # Example
702
- * ```ts
703
- * // buildingBrep minus courtyardBrep — order matters
704
- * const carved = await snaptrude.core.geom.create.brepFromSubtraction(buildingBrep, courtyardBrep)
705
- * ```
706
- */
707
- public abstract brepFromSubtraction(a: BrepHandle, b: BrepHandle): PluginApiReturn<BrepHandle>
708
-
709
- /**
710
- * Create a closed solid **B-rep** as the boolean **intersection** of two
711
- * breps (`a ∩ b` — the shared volume only). Host API call — returns a new
712
- * {@linkcode BrepHandle}. Inputs are read-only and may be authored or
713
- * scene-derived breps (see {@linkcode PluginGeomCreateApi.brepFromUnion});
714
- * coordinates are combined as-is.
715
- *
716
- * The first boolean call loads the OpenCascade geometry kernel (a wasm of
717
- * tens of MB — expect a pause of seconds); subsequent calls are fast.
718
- *
719
- * @param a First solid
720
- * @param b Second solid
721
- * @returns The shared volume as a new {@linkcode BrepHandle}
722
- * @throws if the inputs do not overlap (empty intersection), the result
723
- * splits into disjoint solids, or it cannot be built as a valid single
724
- * solid
725
- *
726
- * @examplePrompt Keep only the overlap of these two solids
727
- * @examplePrompt Intersect these two masses
728
- * @examplePrompt Clip the tower to the zoning envelope
729
- *
730
- * # Example
731
- * ```ts
732
- * const overlap = await snaptrude.core.geom.create.brepFromIntersection(towerBrep, envelopeBrep)
733
- * ```
734
- */
735
- public abstract brepFromIntersection(a: BrepHandle, b: BrepHandle): PluginApiReturn<BrepHandle>
736
-
737
- /**
738
- * Rounds the given edges of a solid with a constant radius. Host API call —
739
- * returns a new {@linkcode BrepHandle}; the input brep is read-only.
740
- *
741
- * Works on curved solids: edges may be straight, circular or spline, and
742
- * adjacent edges (corner blends) are allowed. To pick a **rim** — the circle
743
- * where a cylinder meets a flat cap — walk the edges and keep the ones whose
744
- * two faces are a `cylinder` and a `plane`:
745
- * `core.geom.query.brep.listEdges(brep)` → per edge
746
- * `core.geom.query.edge.listFaces(edge)` → `core.geom.query.face.getSurfaceKind`
747
- * on both faces. (`core.geom.query.brep.listEdgesBetween` takes two VERTEX
748
- * indices — it is NOT a face-pair lookup.) For one cap's rim only, the
749
- * shorter walk is: find the cap (call `getSurfaceKind` on each face from
750
- * `brep.listFaces`), then `core.geom.query.face.listEdges(capFace)`.
751
- *
752
- * When the input brep carries a creation recipe, the result carries it too,
753
- * with this operation appended to `operations`; `design.create.massFromBrep`
754
- * then keeps the mass editable through `design.update.parameters`.
755
- *
756
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
757
- * of MB — expect a pause of seconds).
758
- *
759
- * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
760
- * with `design.create.massFromBrep`.
761
- *
762
- * @param brep The solid whose edges to round
763
- * @param edges The edges to fillet (one or more, all on `brep`, no
764
- * duplicates; more than 64 still fillets but the result carries no
765
- * parameter record)
766
- * @param radius Fillet radius in internal length units (positive, finite) —
767
- * the radius at the START of the blend with `endRadius`; unused with
768
- * `radii` (the table is the whole law — pass its first row's radius)
769
- * @param options Optional VARIABLE blend, in one of two forms, each naming
770
- * exactly ONE edge: the law belongs to the whole tangent chain that edge
771
- * lies on, and it is measured from that edge's start. `endRadius` —
772
- * the radius at the far end, in internal length units, interpolated
773
- * linearly along the chain; on a closed rim it must equal `radius` (0 and
774
- * 1 are the same point) — use `radii` there. `radii` — a sampled law instead:
775
- * `position` is normalised ARC LENGTH along the chain (a ratio 0..1, never
776
- * converted), `radius` is internal length units; 2..32 rows with strictly
777
- * increasing `position`; before the first row and after the last the
778
- * radius is held constant. On a closed rim 0 and 1 are the same point, so
779
- * give them the same radius and put the variation in the interior rows. The two
780
- * forms are mutually exclusive; without either, the fillet has one constant
781
- * radius.
782
- * @returns The filleted solid as a new {@linkcode BrepHandle}
783
- * @throws VALIDATION if `edges` is empty or holds a duplicate, `radius` is
784
- * not a positive finite number, an edge is not on `brep`, or an edge has no
785
- * crease to round — it is a seam of a closed face, or it lies between two
786
- * patches of one smooth surface; for a variable blend, if `endRadius` and
787
- * `radii` are both given, more than one edge is passed, a `radii` table is
788
- * outside 2..32 rows or its positions do not strictly increase within
789
- * 0..1, or the law's two ends differ on a closed rim (an `endRadius`
790
- * other than `radius`, or a `radii` table whose first and last radii
791
- * differ)
792
- * @throws PRECONDITION_FAILED if an edge could not be located on the kernel
793
- * solid after healing (select a different edge, or fillet before the
794
- * operation that curved the solid), or the kernel could not blend the edge
795
- * named in the message (reduce the radius or drop that edge)
796
- * @throws OPERATION_FAILED if the kernel or the translation fails, or the
797
- * result is non-manifold, splits into several solids, or contains a surface
798
- * Snaptrude cannot represent
799
- *
800
- * @examplePrompt Round the rim of the column with 200 mm
801
- * @examplePrompt Soften where the dome meets the wall
802
- * @examplePrompt Fillet the corners of the podium with a 0.5m radius
803
- * @examplePrompt Soften the vertical edges of this tower
804
- * @examplePrompt Blend this edge from 200mm at one end to 600mm at the other
805
- * @examplePrompt Round this rim wider at the back than at the front
806
- *
807
- * # Example
808
- * ```ts
809
- * // Rim = every edge between the cylinder side and a plane cap
810
- * const edges = await snaptrude.core.geom.query.brep.listEdges(column)
811
- * const rim = []
812
- * for (const edge of edges) {
813
- * const faces = await snaptrude.core.geom.query.edge.listFaces(edge)
814
- * const kinds = await Promise.all(
815
- * faces.map((f) => snaptrude.core.geom.query.face.getSurfaceKind(f)),
816
- * )
817
- * if (kinds.includes("cylinder") && kinds.includes("plane")) rim.push(edge)
818
- * }
819
- * const rounded = await snaptrude.core.geom.create.brepFromFillet(column, rim, 0.787) // 200 mm
820
- * ```
821
- */
822
- public abstract brepFromFillet(
823
- brep: BrepHandle,
824
- edges: EdgeHandle[],
825
- radius: number,
826
- options?: { endRadius?: number; radii?: { position: number; radius: number }[] },
827
- ): PluginApiReturn<BrepHandle>
828
-
829
- /**
830
- * Grows or shrinks a solid by offsetting every face — positive distance
831
- * moves faces outward, negative moves them inward. Host API call — returns
832
- * a new {@linkcode BrepHandle}; the input brep is read-only.
833
- *
834
- * Works on curved solids: a sphere offsets to a sphere, a torus to a torus,
835
- * a cylinder to a cylinder. There is nothing to select — the whole solid is
836
- * offset.
837
- *
838
- * When the input brep carries a creation recipe, the result carries it too,
839
- * with this operation appended to `operations`; `design.create.massFromBrep`
840
- * then keeps the mass editable through `design.update.parameters`.
841
- *
842
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of
843
- * tens of MB — expect a pause of seconds).
844
- *
845
- * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
846
- * with `design.create.massFromBrep`.
847
- *
848
- * @param brep The solid to offset
849
- * @param distance Offset distance (finite, non-zero; positive grows, negative shrinks)
850
- * @returns The offset solid as a new {@linkcode BrepHandle}
851
- * @throws VALIDATION if `distance` is not finite, is too small to offset
852
- * anything, or an inward distance consumes the solid entirely
853
- * @throws OPERATION_FAILED if the kernel or the translation fails, or the
854
- * result is non-manifold, splits into several solids, or contains a surface
855
- * Snaptrude cannot represent (an offset free-form NURBS surface may have no
856
- * exact representation — the model is left unchanged). A distance too
857
- * large for the solid is reported here, with a hint asking for a smaller
858
- * one. There is no per-face refusal: the whole call fails, and the error
859
- * names no face
860
- *
861
- * @examplePrompt Thicken the torus ring by 100 mm
862
- * @examplePrompt Grow this solid by 0.5m in every direction
863
- * @examplePrompt Shrink this mass by 200mm
864
- * @examplePrompt Offset the building envelope outward by 1m
865
- *
866
- * # Example
867
- * ```ts
868
- * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)
869
- * const grown = await snaptrude.core.geom.create.brepFromOffset(brep, 0.5)
870
- * ```
871
- */
872
- public abstract brepFromOffset(brep: BrepHandle, distance: number): PluginApiReturn<BrepHandle>
873
-
874
- /**
875
- * Hollows a solid into constant-thickness walls, removing the given faces
876
- * as openings. Host API call — returns a new {@linkcode BrepHandle}; the
877
- * input brep is read-only. The outer surface is kept and the walls grow
878
- * inward.
879
- *
880
- * Works on curved solids: the open faces may be spherical, cylindrical,
881
- * conical, toroidal or free-form patches, not only flat ones. To open the
882
- * flat base of a dome, check every face first —
883
- * `core.geom.query.brep.listFaces` then
884
- * `core.geom.query.face.getSurfaceKind` on each — and keep the `"plane"`
885
- * one. A closed curved surface is stored as several patches (a sphere as
886
- * lunes), so opening "the sphere" may mean passing several faces.
887
- *
888
- * When the input brep carries a creation recipe, the result carries it too,
889
- * with this operation appended to `operations`; `design.create.massFromBrep`
890
- * then keeps the mass editable through `design.update.parameters`.
891
- *
892
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of
893
- * tens of MB — expect a pause of seconds).
894
- *
895
- * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
896
- * with `design.create.massFromBrep`.
897
- *
898
- * @param brep The solid to hollow
899
- * @param openFaces The faces to remove as openings (one or more, all on
900
- * `brep`, no duplicates; more than 32 still shells but the result carries
901
- * no parameter record)
902
- * @param thickness Wall thickness (positive, finite, smaller than half the solid's smallest span)
903
- * @returns The hollowed solid as a new {@linkcode BrepHandle}
904
- * @throws VALIDATION if `openFaces` is empty or holds a duplicate, a face is
905
- * not on `brep`, `thickness` is not a positive finite number, or the
906
- * thickness is too large (it must be smaller than half the solid's smallest
907
- * span)
908
- * @throws OPERATION_FAILED if the kernel or the translation fails, or the
909
- * result is non-manifold, splits into several solids, or contains a surface
910
- * Snaptrude cannot represent. A thickness too large for the solid is
911
- * reported here, with a hint asking for one below half the smallest span.
912
- * There is no per-face refusal: the whole call fails, and the error
913
- * names no face
914
- *
915
- * @examplePrompt Hollow the dome into a 300 mm shell, open at the base
916
- * @examplePrompt Hollow this mass into 200mm walls
917
- * @examplePrompt Shell this form with the top face open
918
- * @examplePrompt Turn this solid tower into a tube open at both ends
919
- *
920
- * # Example
921
- * ```ts
922
- * // Open the dome at its flat base: the one face whose surface is a plane
923
- * const faces = await snaptrude.core.geom.query.brep.listFaces(dome)
924
- * const base = []
925
- * for (const face of faces) {
926
- * if ((await snaptrude.core.geom.query.face.getSurfaceKind(face)) === "plane") base.push(face)
927
- * }
928
- * const hollow = await snaptrude.core.geom.create.brepFromShell(dome, base, 1.181) // 300 mm
929
- * ```
930
- */
931
- public abstract brepFromShell(
932
- brep: BrepHandle,
933
- openFaces: FaceHandle[],
934
- thickness: number,
935
- ): PluginApiReturn<BrepHandle>
936
-
937
- /**
938
- * Cuts a solid by an infinite plane and returns one brep per resulting
939
- * piece — a single piece if the plane misses the solid. Host API call —
940
- * returns new {@linkcode BrepHandle}s; the input brep is read-only. The
941
- * plane is defined by a point on it and its normal direction.
942
- *
943
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of
944
- * tens of MB — expect a pause of seconds).
945
- *
946
- * Inspect each result via `core.geom.query.brep.*`, or commit them to the
947
- * scene with `design.create.massFromBrep`.
948
- *
949
- * @param brep The solid to split
950
- * @param planeOrigin A point on the cutting plane as plain `{x, y, z}` components
951
- * @param planeNormal The plane normal as plain `{x, y, z}` components (non-zero)
952
- * @returns The resulting pieces as {@linkcode BrepHandle}`[]` (one per solid)
953
- * @throws VALIDATION if `planeNormal` is zero-length or the origin/normal
954
- * components are not finite
955
- * @throws OPERATION_FAILED if the kernel cannot split the solid
956
- *
957
- * @examplePrompt Cut this building at 12m height
958
- * @examplePrompt Split the tower from the podium with a horizontal plane
959
- * @examplePrompt Slice this mass along a vertical plane
960
- *
961
- * # Example
962
- * ```ts
963
- * const pieces = await snaptrude.core.geom.create.brepsFromSplit(
964
- * towerBrep,
965
- * { x: 0, y: 12, z: 0 },
966
- * { x: 0, y: 1, z: 0 },
967
- * )
968
- * console.log("pieces:", pieces.length)
969
- * ```
970
- */
971
- public abstract brepsFromSplit(
972
- brep: BrepHandle,
973
- planeOrigin: Vec3Components,
974
- planeNormal: Vec3Components,
975
- ): PluginApiReturn<BrepHandle[]>
976
-
977
- /**
978
- * Cuts a solid with a FACE of another solid — the curved twin of
979
- * {@linkcode PluginGeomCreateApi.brepsFromSplit}, which cuts with a plane.
980
- * Host API call — returns new {@linkcode BrepHandle}s; both inputs are
981
- * read-only. BOTH breps must be in the same frame: read scene masses with
982
- * `design.query.geometry.getBrep(x, { frame: "world" })` (the default is each
983
- * mass's own local frame, and two masses have two). The pieces come back in
984
- * DESCENDING volume order; pick the one you want with
985
- * `core.geom.query.brep.getCentroid` or `getVolume` and commit it. A piece
986
- * carries no recipe — label it.
987
- *
988
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
989
- * of MB — expect a pause of seconds).
990
- *
991
- * @param brep The solid to cut
992
- * @param toolFace The cutting face, read-only
993
- * @param toolBrep The solid `toolFace` belongs to — a face handle cannot name
994
- * its own solid
995
- * @returns The resulting pieces as {@linkcode BrepHandle}`[]`, largest first
996
- * @throws VALIDATION if `toolFace` is not a face of `toolBrep`, `toolBrep` IS
997
- * `brep`, or the cut produced a single part — the surface does not pass all
998
- * the way through (extend it past the mass on every side, and check both
999
- * breps were read in the same frame)
1000
- * @throws OPERATION_FAILED if the kernel could not split the solid
1001
- *
1002
- * @examplePrompt Cut this mass with the curved roof surface and keep the lower part
1003
- *
1004
- * # Example
1005
- * ```ts
1006
- * const target = await snaptrude.design.query.geometry.getBrep(tower, { frame: "world" })
1007
- * const tool = await snaptrude.design.query.geometry.getBrep(roof, { frame: "world" })
1008
- * const [toolFace] = await snaptrude.core.geom.query.brep.listFaces(tool)
1009
- * const pieces = await snaptrude.core.geom.create.brepsFromSplitByFace(target, toolFace, tool)
1010
- * ```
1011
- */
1012
- public abstract brepsFromSplitByFace(
1013
- brep: BrepHandle,
1014
- toolFace: FaceHandle,
1015
- toolBrep: BrepHandle,
1016
- ): PluginApiReturn<BrepHandle[]>
1017
-
1018
- /**
1019
- * Bevels the given edges of a solid with a symmetric planar cut. Host API
1020
- * call — returns a new {@linkcode BrepHandle}; the input brep is read-only.
1021
- *
1022
- * Works on curved solids: edges may be straight, circular or spline, and
1023
- * adjacent edges are allowed. Pick edges the same way as for
1024
- * {@linkcode PluginGeomCreateApi.brepFromFillet} — `brep.listEdges`, then
1025
- * `core.geom.query.edge.listFaces` and `core.geom.query.face.getSurfaceKind`
1026
- * on both faces of each edge. (`core.geom.query.brep.listEdgesBetween` takes
1027
- * two VERTEX indices — it is NOT a face-pair lookup.) Chamfering a cylinder's
1028
- * rim yields a cone patch where a fillet would yield a torus patch.
1029
- *
1030
- * When the input brep carries a creation recipe, the result carries it too,
1031
- * with this operation appended to `operations`; `design.create.massFromBrep`
1032
- * then keeps the mass editable through `design.update.parameters`.
1033
- *
1034
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
1035
- * of MB — expect a pause of seconds).
1036
- *
1037
- * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
1038
- * with `design.create.massFromBrep`.
1039
- *
1040
- * @param brep The solid whose edges to bevel
1041
- * @param edges The edges to chamfer (one or more, all on `brep`, no
1042
- * duplicates; more than 64 still chamfers but the result carries no
1043
- * parameter record)
1044
- * @param distance Chamfer distance from the edge on each adjacent face (positive, finite)
1045
- * @returns The chamfered solid as a new {@linkcode BrepHandle}
1046
- * @throws VALIDATION if `edges` is empty or holds a duplicate, `distance` is
1047
- * not a positive finite number, an edge is not on `brep`, or an edge has no
1048
- * crease to cut — it is a seam of a closed face, or it lies between two
1049
- * patches of one smooth surface
1050
- * @throws PRECONDITION_FAILED if an edge could not be located on the kernel
1051
- * solid after healing (select a different edge, or chamfer before the
1052
- * operation that curved the solid)
1053
- * @throws OPERATION_FAILED if the kernel or the translation fails, or the
1054
- * result is non-manifold, splits into several solids, or contains a surface
1055
- * Snaptrude cannot represent. A distance larger than an adjacent face
1056
- * is reported here, with a hint asking for a smaller one. Unlike
1057
- * {@linkcode PluginGeomCreateApi.brepFromFillet}, the kernel does not say
1058
- * which edge failed a chamfer, so the error names no edge
1059
- *
1060
- * @examplePrompt Chamfer the top edge of the cylinder
1061
- * @examplePrompt Bevel these edges by 100mm
1062
- * @examplePrompt Chamfer the top edges of the plinth
1063
- * @examplePrompt Cut a 45-degree flat along the corners of this mass
1064
- *
1065
- * # Example
1066
- * ```ts
1067
- * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)
1068
- * const edges = await snaptrude.core.geom.query.brep.listEdges(brep)
1069
- * const beveled = await snaptrude.core.geom.create.brepFromChamfer(brep, [edges[0]], 0.1)
1070
- * ```
1071
- */
1072
- public abstract brepFromChamfer(
1073
- brep: BrepHandle,
1074
- edges: EdgeHandle[],
1075
- distance: number,
1076
- ): PluginApiReturn<BrepHandle>
1077
-
1078
- /**
1079
- * Sweeps a planar profile along a polyline path, with mitred corners at
1080
- * each bend. Host API call — returns a new {@linkcode BrepHandle}; the
1081
- * input contour is read-only. The profile must be hole-free and must not
1082
- * lie in a plane containing the first path segment's direction. Repeating
1083
- * the first point as the last closes the path into a ring solid (a picture
1084
- * frame): closed paths must be flat and straight-segmented and take a
1085
- * straight-edged (arc-free) profile — the ring's cross-section is the
1086
- * authored profile projected along the first leg onto the first mitre
1087
- * plane, so a profile not perpendicular to the first leg appears sheared.
1088
- * An optional scale law tapers the sweep: `startScale`/`endScale`
1089
- * interpolate linearly by arc length, or `scales` gives one factor per path
1090
- * point (scaling is about the profile centroid). A uniform scale (all
1091
- * factors equal) works on any path today's sweep accepts; a varying scale
1092
- * takes straight (collinear) open paths with straight-edged profiles —
1093
- * tapering across a bend or along an arc edge would create faces Snaptrude
1094
- * cannot represent and throws (sweep each straight run separately and union
1095
- * the results instead). An optional `transition` picks the corner
1096
- * treatment: `"miter"` (default) is a sharp mitred bend; `"round"`
1097
- * carries the section around each turning corner on a tangent circular arc,
1098
- * so the corner patch is a real curved surface (geometrically cylindrical;
1099
- * `getSurfaceKind` reports it as `"spline"`) rather than a
1100
- * flat cut (open paths only — `"round"` on a closed ring path is refused);
1101
- * `{ bevel: b }` chamfers each turning corner by cutting `b` back along
1102
- * both adjacent legs (works on open and closed paths — each leg must be
1103
- * long enough for its cuts). On a corner-less path `transition` has no
1104
- * effect, and a varying scale only accepts straight paths, so it never meets
1105
- * a corner.
1106
- *
1107
- * Open paths run on the OpenCascade kernel (the first kernel call loads a
1108
- * wasm of tens of MB — expect a pause of seconds); closed paths are
1109
- * computed host-side and never load the kernel.
1110
- *
1111
- * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
1112
- * with `design.create.massFromBrep`.
1113
- *
1114
- * @param profile The cross-section to sweep (hole-free contour)
1115
- * @param path The polyline path as plain `{x, y, z}` points (2 or more;
1116
- * repeat the first point as the last to close the path into a ring; more
1117
- * than 512 points still sweeps but the result carries no parameter record)
1118
- * @param options Optional scale law and corner transition. Scale law:
1119
- * `startScale`/`endScale` (linear taper by arc length) or `scales` (one
1120
- * factor per path point; mutually exclusive with
1121
- * `startScale`/`endScale`); every factor must be finite and within
1122
- * 1e-3..1e3. `transition`: `"miter"` (default, sharp corners),
1123
- * `"round"` (arc-carried corners — curved corner patches; open paths
1124
- * only) or `{ bevel: b }` (chamfered corners, `b` finite within
1125
- * 1e-3..1e3; open and closed paths)
1126
- * @returns The swept solid as a new {@linkcode BrepHandle}
1127
- * @throws VALIDATION if the profile has holes, the path has fewer than 2
1128
- * points, consecutive path points coincide, a
1129
- * coordinate is not finite, or the profile plane contains the first
1130
- * path segment's direction
1131
- * (degenerate sweep); for closed paths, if the ring has fewer than 3
1132
- * distinct corners, the path is non-planar, a corner doubles back, the
1133
- * profile has an arc edge, or the profile is too large for a mitred
1134
- * corner; for scale options, if a factor is outside 1e-3..1e3, `scales`
1135
- * is combined with `startScale`/`endScale`, `scales` does not match the
1136
- * path length, or a varying scale is used on a bent or closed path or
1137
- * with an arc-edged profile; for `{ bevel }`, if a leg is too short for
1138
- * the cuts its bevelled corners take from it or a corner nearly doubles
1139
- * back so its bevel collapses (the errors name the corner)
1140
- * @throws PRECONDITION_FAILED if `transition` is `"round"` and the path is
1141
- * closed — round corners work on open paths only; use `"miter"` or
1142
- * `{ bevel }` on a ring
1143
- * @throws OPERATION_FAILED if the kernel cannot sweep the profile into a
1144
- * valid solid — including a `"round"` corner whose arc does not fit its
1145
- * legs, which is not pre-checked and so is not named per corner
1146
- *
1147
- * @examplePrompt Sweep this profile along the corridor path
1148
- * @examplePrompt Extrude the railing section along this route
1149
- * @examplePrompt Run a duct profile through these points
1150
- * @examplePrompt Sweep a frame profile around a closed rectangular loop
1151
- * @examplePrompt Taper this duct from 1x1 to 2x2 along its run
1152
- * @examplePrompt Sweep a column section that narrows toward the top
1153
- * @examplePrompt Chamfer the corners of this swept frame by 200mm
1154
- * @examplePrompt Sweep this handrail with rounded corners at the bends
1155
- *
1156
- * # Example
1157
- * ```ts
1158
- * const rect = await snaptrude.core.geom.create.profileRect(0.4, 0.4)
1159
- * const profile = await snaptrude.core.geom.create.contourFromProfile(rect)
1160
- * // profileRect lies in the XZ plane, so the path must START out of that
1161
- * // plane (here: straight up), then it can run horizontally.
1162
- * const duct = await snaptrude.core.geom.create.brepFromSweep(profile, [
1163
- * { x: 0, y: 0, z: 0 },
1164
- * { x: 0, y: 2.8, z: 0 },
1165
- * { x: 10, y: 2.8, z: 0 },
1166
- * ])
1167
- * // A closed ring (picture frame): repeat the first point as the last
1168
- * const frame = await snaptrude.core.geom.create.brepFromSweep(profile, [
1169
- * { x: 0, y: 0, z: 0 },
1170
- * { x: 0, y: 3, z: 0 },
1171
- * { x: 4, y: 3, z: 0 },
1172
- * { x: 4, y: 0, z: 0 },
1173
- * { x: 0, y: 0, z: 0 },
1174
- * ])
1175
- * // A tapered straight run: 0.4x0.4 at the start, 0.8x0.8 at the end
1176
- * const flue = await snaptrude.core.geom.create.brepFromSweep(
1177
- * profile,
1178
- * [
1179
- * { x: 0, y: 0, z: 0 },
1180
- * { x: 0, y: 10, z: 0 },
1181
- * ],
1182
- * { startScale: 1, endScale: 2 },
1183
- * )
1184
- * ```
1185
- */
1186
- public abstract brepFromSweep(
1187
- profile: ContourHandle,
1188
- path: Vec3Components[],
1189
- options?: {
1190
- startScale?: number
1191
- endScale?: number
1192
- scales?: number[]
1193
- transition?: "miter" | "round" | { bevel: number }
1194
- },
1195
- ): PluginApiReturn<BrepHandle>
1196
-
1197
- /**
1198
- * Sweeps a cross-section along a SMOOTH path — splines, arcs and lines chained
1199
- * end to end — with control of how the profile turns.
1200
- * {@linkcode PluginGeomCreateApi.brepFromSweep} takes a polyline path; this
1201
- * takes curves. Host API call — returns a new {@linkcode BrepHandle}; the
1202
- * inputs are read-only. Records as `sweepAlongCurve`, so the orientation stays
1203
- * editable through `design.update.parameters`. The properties panel shows it
1204
- * read-only, because it is a text value, not a number. A railed sweep records
1205
- * nothing (`getParameters` answers `null`).
1206
- *
1207
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
1208
- * of MB — expect a pause of seconds).
1209
- *
1210
- * @param profile Hole-free cross-section contour; placed at the first curve's start
1211
- * @param path 1..64 curves, ordered, meeting end to end within tolerance
1212
- * @param options `orientation` — `"upright"` (default) keeps the profile's up
1213
- * aligned with world up (ducts, handrails, ribbons that must not roll);
1214
- * `"frenet"` lets it roll with the path's curvature; `"fixed"` carries the
1215
- * authored frame along unchanged, which is a pure translation — use it to
1216
- * bend a footprint along an arc. `rail` is a second guide curve.
1217
- * @returns The swept solid as a new {@linkcode BrepHandle}
1218
- * @throws VALIDATION if the path curves are not connected, the path is closed,
1219
- * the profile has holes, the first tangent lies in the profile plane, the
1220
- * path runs vertically under `"upright"` (that frame is undefined there —
1221
- * the message names the run and points at `"frenet"`), the path turns
1222
- * parallel to the profile under `"fixed"`, `rail` is closed, or `rail` and
1223
- * `orientation` are both given
1224
- * @throws OPERATION_FAILED if the kernel could not sweep the profile — a
1225
- * self-intersection on a tight bend
1226
- *
1227
- * @examplePrompt Sweep a 600x400 duct along this curved route and keep it upright
1228
- * @examplePrompt Run this handrail along the curved stair edge
1229
- * @examplePrompt Bend this footprint along the arc
1230
- *
1231
- * # Example
1232
- * ```ts
1233
- * const route = await snaptrude.core.geom.create.splineFromPoints(points)
1234
- * const duct = await snaptrude.core.geom.create.brepFromSweepAlongCurve(profile, [route], {
1235
- * orientation: "upright",
1236
- * })
1237
- * ```
1238
- */
1239
- public abstract brepFromSweepAlongCurve(
1240
- profile: ContourHandle,
1241
- path: CurveHandle[],
1242
- options?: SweepAlongCurveOptions,
1243
- ): PluginApiReturn<BrepHandle>
1244
-
1245
- /**
1246
- * Revolves a planar profile about an axis to make a solid of revolution — a
1247
- * full turn by default. Host API call — returns a new
1248
- * {@linkcode BrepHandle}; the input contour is read-only. Holes in the
1249
- * profile are allowed, and segments may run at any angle to the axis: an
1250
- * inclined segment revolves into a cone, an arc into a sphere or torus
1251
- * section. This is the general route to domes, vases, columns and rotundas.
1252
- *
1253
- * Two rules bind the axis to the profile. First, the axis line must lie IN
1254
- * the profile's plane or run parallel to it — a revolution turns a planar
1255
- * section about a line drawn in that same plane; a parallel axis offset out
1256
- * of the plane is judged by its projection into the plane (the result is a
1257
- * hollow ring). A skew axis is refused: move the axis into the profile's
1258
- * plane. Second, within that plane the whole profile stays on ONE side of
1259
- * the axis.
1260
- * Vertices and whole segments may lie ON the axis — that is how a dome's
1261
- * pole and a cylinder's inner edge are authored — but no part of the profile
1262
- * may cross to the other side, which would sweep a self-intersecting solid.
1263
- *
1264
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of
1265
- * tens of MB — expect a pause of seconds).
1266
- *
1267
- * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
1268
- * with `design.create.massFromBrep`.
1269
- *
1270
- * @param profile The cross-section to revolve (planar contour, holes allowed)
1271
- * @param axisOrigin A point on the revolution axis as plain `{x, y, z}` components
1272
- * @param axisDirection The axis direction as plain `{x, y, z}` components (non-zero)
1273
- * @param angleInDegrees Optional revolution angle in degrees (0 < angle ≤ 360; default 360)
1274
- * @returns The revolved solid as a new {@linkcode BrepHandle}
1275
- * @throws VALIDATION if `axisDirection` is zero-length, `angleInDegrees` is
1276
- * not in (0, 360], a coordinate is not finite, or the profile crosses the
1277
- * axis (the message names the offending segment — keep the whole profile
1278
- * on one side; points may touch the axis but not cross it)
1279
- * @throws PRECONDITION_FAILED if the axis is skew to the profile's plane
1280
- * (neither in it nor parallel to it; the message asks you to move the axis
1281
- * into the plane of the profile)
1282
- * @throws OPERATION_FAILED if the kernel cannot revolve the profile into a
1283
- * valid solid
1284
- *
1285
- * @examplePrompt Create a dome from this section
1286
- * @examplePrompt Revolve this profile 360 degrees around the vertical axis
1287
- * @examplePrompt Build a rotunda by revolving this wall section
1288
- * @examplePrompt Make a dome by revolving a quarter-circle about the vertical axis
1289
- * @examplePrompt Turn this curved outline into a vase
1290
- *
1291
- * # Example
1292
- * ```ts
1293
- * // A cylinder: revolve a 2m-wide, 3m-tall rectangle about the Y axis at
1294
- * // its edge — the left edge sits ON the axis, which is legal.
1295
- * const rect = await snaptrude.core.geom.create.profileFromLinePoints([
1296
- * await snaptrude.core.math.vec3.new(0, 0, 0),
1297
- * await snaptrude.core.math.vec3.new(2, 0, 0),
1298
- * await snaptrude.core.math.vec3.new(2, 3, 0),
1299
- * await snaptrude.core.math.vec3.new(0, 3, 0),
1300
- * ])
1301
- * const profile = await snaptrude.core.geom.create.contourFromProfile(rect)
1302
- * const cylinder = await snaptrude.core.geom.create.brepFromRevolution(
1303
- * profile,
1304
- * { x: 0, y: 0, z: 0 },
1305
- * { x: 0, y: 1, z: 0 },
1306
- * )
1307
- * ```
1308
- */
1309
- public abstract brepFromRevolution(
1310
- profile: ContourHandle,
1311
- axisOrigin: Vec3Components,
1312
- axisDirection: Vec3Components,
1313
- angleInDegrees?: number,
1314
- ): PluginApiReturn<BrepHandle>
1315
-
1316
- /**
1317
- * Create a solid **sphere** B-rep (boundary representation: the solid's
1318
- * exact faces, edges and vertices) from a centre and radius. Host API call
1319
- * — returns a {@linkcode BrepHandle}; commit it to the scene with
1320
- * `design.create.massFromBrep`. The result carries an exact spherical
1321
- * surface (not a faceted approximation) and a creation recipe — read the
1322
- * committed mass's parameters back with `design.query.getParameters` and edit
1323
- * them with `design.update.parameters`.
1324
- *
1325
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
1326
- * of MB — expect a pause of seconds).
1327
- *
1328
- * @param centre Sphere centre as plain `{x, y, z}` components (internal length units)
1329
- * @param radius Sphere radius (> 0, internal length units)
1330
- * @returns The new solid as a {@linkcode BrepHandle}
1331
- * @throws VALIDATION if `radius` is not a positive finite number or a
1332
- * coordinate is not finite
1333
- *
1334
- * @examplePrompt Create a sphere of radius 5 at the origin
1335
- * @examplePrompt Add a 3m ball on top of the tower
1336
- *
1337
- * # Example
1338
- * ```ts
1339
- * const brep = await snaptrude.core.geom.create.brepFromSphere({ x: 0, y: 5, z: 0 }, 5)
1340
- * const mass = await snaptrude.design.create.massFromBrep(brep, "Sphere")
1341
- * ```
1342
- */
1343
- public abstract brepFromSphere(
1344
- centre: Vec3Components,
1345
- radius: number,
1346
- ): PluginApiReturn<BrepHandle>
1347
-
1348
- /**
1349
- * Create a solid **cylinder** B-rep from its bottom-cap centre, axis
1350
- * direction, radius and height. Host API call — returns a
1351
- * {@linkcode BrepHandle}; commit with `design.create.massFromBrep`. The
1352
- * bottom cap sits AT `base` and the solid extends `height` along `axis` —
1353
- * place `base` on the floor and use axis `{x: 0, y: 1, z: 0}` for an upright
1354
- * column. Carries a creation recipe (radius, height), editable through
1355
- * `design.update.parameters`.
1356
- *
1357
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
1358
- * of MB — expect a pause of seconds).
1359
- *
1360
- * @param base Centre of the bottom cap as plain `{x, y, z}` components
1361
- * @param axis Axis direction as plain `{x, y, z}` components (non-zero, normalised by the host)
1362
- * @param radius Cylinder radius (> 0)
1363
- * @param height Cylinder height along `axis` (> 0)
1364
- * @returns The new solid as a {@linkcode BrepHandle}
1365
- * @throws VALIDATION if `radius`/`height` are not positive finite numbers or
1366
- * `axis` is zero-length
1367
- *
1368
- * @examplePrompt Create a cylinder 2m across and 4m tall
1369
- * @examplePrompt Add a circular column here
1370
- *
1371
- * # Example
1372
- * ```ts
1373
- * const brep = await snaptrude.core.geom.create.brepFromCylinder(
1374
- * { x: 0, y: 0, z: 0 },
1375
- * { x: 0, y: 1, z: 0 },
1376
- * 1,
1377
- * 4,
1378
- * )
1379
- * const column = await snaptrude.design.create.massFromBrep(brep, "Column")
1380
- * ```
1381
- */
1382
- public abstract brepFromCylinder(
1383
- base: Vec3Components,
1384
- axis: Vec3Components,
1385
- radius: number,
1386
- height: number,
1387
- ): PluginApiReturn<BrepHandle>
1388
-
1389
- /**
1390
- * Create a solid **cone or frustum** B-rep: bottom-cap centre, axis, base
1391
- * radius, height, and an optional top radius (default 0 — a full cone to a
1392
- * point; > 0 — a frustum / tapered column). Host API call — returns a
1393
- * {@linkcode BrepHandle}; commit with `design.create.massFromBrep`. Carries
1394
- * a creation recipe (radii + height), editable through `design.update.parameters`.
1395
- *
1396
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
1397
- * of MB — expect a pause of seconds).
1398
- *
1399
- * @param base Centre of the bottom cap as plain `{x, y, z}` components
1400
- * @param axis Axis direction as plain `{x, y, z}` components (non-zero, normalised by the host)
1401
- * @param baseRadius Radius at the base (> 0)
1402
- * @param height Height along `axis` (> 0)
1403
- * @param topRadius Optional radius at the top (≥ 0, ≠ `baseRadius`; default 0 = apex)
1404
- * @returns The new solid as a {@linkcode BrepHandle}
1405
- * @throws VALIDATION if radii/height are invalid, `topRadius === baseRadius`
1406
- * (that is a cylinder — use {@linkcode PluginGeomCreateApi.brepFromCylinder}),
1407
- * or `axis` is zero-length
1408
- *
1409
- * @examplePrompt Create a cone roof 4m wide and 3m tall
1410
- * @examplePrompt Make a tapered column narrowing from 1m to 0.6m
1411
- *
1412
- * # Example
1413
- * ```ts
1414
- * const roof = await snaptrude.core.geom.create.brepFromCone(
1415
- * { x: 0, y: 9, z: 0 },
1416
- * { x: 0, y: 1, z: 0 },
1417
- * 2,
1418
- * 3,
1419
- * )
1420
- * ```
1421
- */
1422
- public abstract brepFromCone(
1423
- base: Vec3Components,
1424
- axis: Vec3Components,
1425
- baseRadius: number,
1426
- height: number,
1427
- topRadius?: number,
1428
- ): PluginApiReturn<BrepHandle>
1429
-
1430
- /**
1431
- * Create a solid **torus** (ring / donut) B-rep from its centre, plane
1432
- * normal, ring radius and tube radius. Host API call — returns a
1433
- * {@linkcode BrepHandle}; commit with `design.create.massFromBrep`.
1434
- * `majorRadius` is centre-to-tube-centre; `minorRadius` is the tube's own
1435
- * radius and must be strictly smaller. Carries a creation recipe, editable
1436
- * through `design.update.parameters`.
1437
- *
1438
- * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
1439
- * of MB — expect a pause of seconds).
1440
- *
1441
- * @param centre Torus centre as plain `{x, y, z}` components
1442
- * @param axis Plane normal of the ring as plain `{x, y, z}` components (non-zero)
1443
- * @param majorRadius Ring radius, centre to tube centre (> 0)
1444
- * @param minorRadius Tube radius (> 0 and < `majorRadius` — a self-intersecting torus is rejected)
1445
- * @returns The new solid as a {@linkcode BrepHandle}
1446
- * @throws VALIDATION if a radius is invalid, `minorRadius >= majorRadius`,
1447
- * or `axis` is zero-length
1448
- *
1449
- * @examplePrompt Create a donut-shaped ring 10m across with a 1m tube
1450
- * @examplePrompt Add a torus canopy around the tower
1451
- *
1452
- * # Example
1453
- * ```ts
1454
- * const ring = await snaptrude.core.geom.create.brepFromTorus(
1455
- * { x: 0, y: 6, z: 0 },
1456
- * { x: 0, y: 1, z: 0 },
1457
- * 5,
1458
- * 1,
1459
- * )
1460
- * ```
1461
- */
1462
- public abstract brepFromTorus(
1463
- centre: Vec3Components,
1464
- axis: Vec3Components,
1465
- majorRadius: number,
1466
- minorRadius: number,
1467
- ): PluginApiReturn<BrepHandle>
1468
- }
1469
-
1470
- /**
1471
- * Arguments for {@linkcode PluginGeomCreateApi.line}.
1472
- *
1473
- * | Property | Type | Description |
1474
- * |---|---|---|
1475
- * | `startPoint` | {@linkcode Vec3Handle} | Start point of the line segment |
1476
- * | `endPoint` | {@linkcode Vec3Handle} | End point of the line segment |
1477
- */
1478
- export const PluginGeomCreateLineArgs = z.object({
1479
- startPoint: Vec3Handle,
1480
- endPoint: Vec3Handle,
1481
- })
1482
-
1483
- export type PluginGeomCreateLineArgs = z.infer<typeof PluginGeomCreateLineArgs>
1484
-
1485
- /**
1486
- * Arguments for {@linkcode PluginGeomCreateApi.arc}.
1487
- *
1488
- * | Property | Type | Description |
1489
- * |---|---|---|
1490
- * | `startPoint` | {@linkcode Vec3Handle} | Start point of the arc |
1491
- * | `endPoint` | {@linkcode Vec3Handle} | End point of the arc |
1492
- * | `centrePoint` | {@linkcode Vec3Handle} | Centre point of the arc |
1493
- * | `axis` | {@linkcode Vec3Handle} | Axis direction of the arc |
1494
- */
1495
- export const PluginGeomCreateArcArgs = z.object({
1496
- startPoint: Vec3Handle,
1497
- endPoint: Vec3Handle,
1498
- centrePoint: Vec3Handle,
1499
- axis: Vec3Handle,
1500
- })
1501
-
1502
- export type PluginGeomCreateArcArgs = z.infer<typeof PluginGeomCreateArcArgs>
1503
-
1504
- /**
1505
- * Arguments for {@linkcode PluginGeomCreateApi.circle}.
1506
- *
1507
- * | Property | Type | Description |
1508
- * |---|---|---|
1509
- * | `centrePoint` | {@linkcode Vec3Handle} | Centre point of the circle |
1510
- * | `axis` | {@linkcode Vec3Handle} | Axis direction (plane normal) of the circle |
1511
- * | `radius` | `number` | Radius of the circle |
1512
- */
1513
- export const PluginGeomCreateCircleArgs = z.object({
1514
- centrePoint: Vec3Handle,
1515
- axis: Vec3Handle,
1516
- radius: z.number(),
1517
- })
1518
-
1519
- export type PluginGeomCreateCircleArgs = z.infer<typeof PluginGeomCreateCircleArgs>
1520
-
1521
- /**
1522
- * Arguments for {@linkcode PluginGeomCreateApi.splineFromPoints}.
1523
- *
1524
- * | Property | Type | Description |
1525
- * |---|---|---|
1526
- * | `points` | {@linkcode Vec3Handle}`[]` | 3..256 ordered through-points of the curve |
1527
- */
1528
- export const PluginGeomCreateSplineFromPointsArgs = z.object({
1529
- points: z.array(Vec3Handle).min(3).max(MAX_SPLINE_THROUGH_POINTS),
1530
- })
1531
-
1532
- export type PluginGeomCreateSplineFromPointsArgs = z.infer<
1533
- typeof PluginGeomCreateSplineFromPointsArgs
1534
- >
1535
-
1536
- /**
1537
- * Arguments for {@linkcode PluginGeomCreateApi.splineFromControlPoints}.
1538
- *
1539
- * | Property | Type | Description |
1540
- * |---|---|---|
1541
- * | `controlPoints` | {@linkcode Vec3Handle}`[]` | 3..512 control points of the curve, in order |
1542
- * | `options` | `object`? | `degree`: 2 or 3 (default `min(3, controlPoints.length - 1)`) |
1543
- */
1544
- export const PluginGeomCreateSplineFromControlPointsArgs = z.object({
1545
- controlPoints: z.array(Vec3Handle).min(3).max(MAX_SPLINE_POLES),
1546
- options: z.object({ degree: z.number().int().min(2).max(3).optional() }).optional(),
1547
- })
1548
-
1549
- export type PluginGeomCreateSplineFromControlPointsArgs = z.infer<
1550
- typeof PluginGeomCreateSplineFromControlPointsArgs
1551
- >
1552
-
1553
- /**
1554
- * Arguments for {@linkcode PluginGeomCreateApi.profileFromLinePoints}.
1555
- *
1556
- * | Property | Type | Description |
1557
- * |---|---|---|
1558
- * | `points` | {@linkcode Vec3Handle}`[]` | Ordered vertex handles of the profile |
1559
- */
1560
- export const PluginGeomCreateProfileFromLinePointsArgs = z.object({
1561
- points: z.array(Vec3Handle),
1562
- })
1563
-
1564
- export type PluginGeomCreateProfileFromLinePointsArgs = z.infer<
1565
- typeof PluginGeomCreateProfileFromLinePointsArgs
1566
- >
1567
-
1568
- /**
1569
- * Arguments for {@linkcode PluginGeomCreateApi.profileFromCurves}.
1570
- *
1571
- * | Property | Type | Description |
1572
- * |---|---|---|
1573
- * | `curves` | {@linkcode CurveHandle}`[]` | Ordered curve handles forming a closed loop |
1574
- */
1575
- export const PluginGeomCreateProfileFromCurvesArgs = z.object({
1576
- curves: z.array(CurveHandle),
1577
- })
1578
-
1579
- export type PluginGeomCreateProfileFromCurvesArgs = z.infer<
1580
- typeof PluginGeomCreateProfileFromCurvesArgs
1581
- >
1582
-
1583
- /**
1584
- * Arguments for {@linkcode PluginGeomCreateApi.profileFromSplinePoints}.
1585
- *
1586
- * | Property | Type | Description |
1587
- * |---|---|---|
1588
- * | `points` | {@linkcode Vec3Handle}`[]` | 3..256 ordered through-points of the closed loop (first point NOT repeated) |
1589
- */
1590
- export const PluginGeomCreateProfileFromSplinePointsArgs = z.object({
1591
- points: z.array(Vec3Handle).min(3).max(MAX_SPLINE_THROUGH_POINTS),
1592
- })
1593
-
1594
- export type PluginGeomCreateProfileFromSplinePointsArgs = z.infer<
1595
- typeof PluginGeomCreateProfileFromSplinePointsArgs
1596
- >
1597
-
1598
- /**
1599
- * Arguments for {@linkcode PluginGeomCreateApi.contourFromProfile}.
1600
- *
1601
- * | Property | Type | Description |
1602
- * |---|---|---|
1603
- * | `outer` | {@linkcode ProfileHandle} | The outer boundary loop |
1604
- */
1605
- export const PluginGeomCreateContourFromProfileArgs = z.object({
1606
- outer: ProfileHandle,
1607
- })
1608
-
1609
- export type PluginGeomCreateContourFromProfileArgs = z.infer<
1610
- typeof PluginGeomCreateContourFromProfileArgs
1611
- >
1612
-
1613
- /**
1614
- * Arguments for {@linkcode PluginGeomCreateApi.contourFromProfiles}.
1615
- *
1616
- * | Property | Type | Description |
1617
- * |---|---|---|
1618
- * | `outer` | {@linkcode ProfileHandle} | The outer boundary loop |
1619
- * | `holes` | {@linkcode ProfileHandle}`[]` | Optional inner hole loops |
1620
- */
1621
- export const PluginGeomCreateContourFromProfilesArgs = z.object({
1622
- outer: ProfileHandle,
1623
- holes: z.array(ProfileHandle).optional(),
1624
- })
1625
-
1626
- export type PluginGeomCreateContourFromProfilesArgs = z.infer<
1627
- typeof PluginGeomCreateContourFromProfilesArgs
1628
- >
1629
-
1630
- /**
1631
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromFaces}.
1632
- *
1633
- * | Property | Type | Description |
1634
- * |---|---|---|
1635
- * | `faces` | {@linkcode Vec3Components}`[][]` | Face loops — each an ordered array of `{x, y, z}` points (≥3 points per face, ≥4 faces) |
1636
- */
1637
- export const PluginGeomCreateBrepFromFacesArgs = z.object({
1638
- faces: z.array(z.array(Vec3Components).min(3)).min(4),
1639
- })
1640
-
1641
- export type PluginGeomCreateBrepFromFacesArgs = z.infer<
1642
- typeof PluginGeomCreateBrepFromFacesArgs
1643
- >
1644
-
1645
- /**
1646
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromExtrusion}.
1647
- *
1648
- * | Property | Type | Description |
1649
- * |---|---|---|
1650
- * | `contour` | {@linkcode ContourHandle} | The cross-section to extrude |
1651
- * | `direction` | {@linkcode Vec3Components} | Extrusion direction (non-zero, finite components) |
1652
- * | `amount` | `number` | Extrusion distance (finite, non-zero) |
1653
- * | `options` | `object`? | Optional twist and taper: `twistInDegrees` (an angle, finite), `topScale` (a ratio, finite and positive) and `levels` (a count, 2..64) |
1654
- */
1655
- export const PluginGeomCreateBrepFromExtrusionArgs = z.object({
1656
- contour: ContourHandle,
1657
- direction: z.object({
1658
- x: z.number().finite(),
1659
- y: z.number().finite(),
1660
- z: z.number().finite(),
1661
- }),
1662
- amount: z
1663
- .number()
1664
- .finite()
1665
- .refine((value) => value !== 0, { message: "amount must be non-zero" }),
1666
- options: z
1667
- .object({
1668
- twistInDegrees: z.number().finite().optional(),
1669
- topScale: z.number().finite().positive().optional(),
1670
- levels: z.number().int().min(2).max(MAX_TWIST_LEVELS).optional(),
1671
- })
1672
- .optional(),
1673
- })
1674
-
1675
- export type PluginGeomCreateBrepFromExtrusionArgs = z.infer<
1676
- typeof PluginGeomCreateBrepFromExtrusionArgs
1677
- >
1678
-
1679
- /**
1680
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromLoft}.
1681
- *
1682
- * | Property | Type | Description |
1683
- * |---|---|---|
1684
- * | `bottomContour` | {@linkcode ContourHandle} | The bottom cross-section |
1685
- * | `topContour` | {@linkcode ContourHandle} | The top cross-section |
1686
- * | `intermediateContours` | {@linkcode ContourHandle}`[]`? | Optional in-between cross-sections, ordered bottom to top (any count; more than 32 sections in all builds but carries no parameter record) |
1687
- * | `options` | `object`? | Optional behavior switches: `compatibility` (`"strict"` throws on mismatched edge counts instead of auto-matching; default `"auto"`), `seamAlignment` (`"authored"` keeps the authored correspondence instead of searching seam rotations; default `"auto"`) and `smoothing` (`"smooth"` lofts every section in one pass, so the sides curve through the intermediates; default `"ruled"`) |
1688
- */
1689
- export const PluginGeomCreateBrepFromLoftArgs = z.object({
1690
- bottomContour: ContourHandle,
1691
- topContour: ContourHandle,
1692
- intermediateContours: z.array(ContourHandle).optional(),
1693
- options: z
1694
- .object({
1695
- compatibility: z.enum(["strict", "auto"]).optional(),
1696
- seamAlignment: z.enum(["authored", "auto"]).optional(),
1697
- smoothing: z.enum(["ruled", "smooth"]).optional(),
1698
- })
1699
- .optional(),
1700
- })
1701
-
1702
- export type PluginGeomCreateBrepFromLoftArgs = z.infer<typeof PluginGeomCreateBrepFromLoftArgs>
1703
-
1704
- /**
1705
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromMesh}.
1706
- *
1707
- * | Property | Type | Description |
1708
- * |---|---|---|
1709
- * | `positions` | {@linkcode Vec3Components}`[]` | Vertex positions (≥4) |
1710
- * | `faces` | `number[][]` | Face loops of indices into `positions` (≥3 indices per face, ≥4 faces) |
1711
- */
1712
- export const PluginGeomCreateBrepFromMeshArgs = z.object({
1713
- positions: z.array(Vec3Components).min(4),
1714
- faces: z.array(z.array(z.number()).min(3)).min(4),
1715
- })
1716
-
1717
- export type PluginGeomCreateBrepFromMeshArgs = z.infer<typeof PluginGeomCreateBrepFromMeshArgs>
1718
-
1719
- /**
1720
- * Arguments for the brep boolean constructors —
1721
- * {@linkcode PluginGeomCreateApi.brepFromUnion},
1722
- * {@linkcode PluginGeomCreateApi.brepFromSubtraction} (`a` minus `b`), and
1723
- * {@linkcode PluginGeomCreateApi.brepFromIntersection}.
1724
- *
1725
- * | Property | Type | Description |
1726
- * |---|---|---|
1727
- * | `a` | {@linkcode BrepHandle} | First solid (the subtraction target) |
1728
- * | `b` | {@linkcode BrepHandle} | Second solid (the subtraction tool) |
1729
- */
1730
- export const PluginGeomCreateBrepBooleanArgs = z.object({
1731
- a: BrepHandle,
1732
- b: BrepHandle,
1733
- })
1734
-
1735
- export type PluginGeomCreateBrepBooleanArgs = z.infer<typeof PluginGeomCreateBrepBooleanArgs>
1736
-
1737
- // Plain {x, y, z} record with finite components (brepFromExtrusion `direction` pattern).
1738
- const FiniteVec3Components = z.object({
1739
- x: z.number().finite(),
1740
- y: z.number().finite(),
1741
- z: z.number().finite(),
1742
- })
1743
-
1744
- /**
1745
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromFillet}.
1746
- *
1747
- * | Property | Type | Description |
1748
- * |---|---|---|
1749
- * | `brep` | {@linkcode BrepHandle} | The solid whose edges to round |
1750
- * | `edges` | {@linkcode EdgeHandle}`[]` | The edges to fillet (one or more; more than 64 builds but carries no parameter record) |
1751
- * | `radius` | `number` | Fillet radius (positive, finite); the start radius with `endRadius`, unused with `radii` |
1752
- * | `options` | `object`? | Optional variable blend: `endRadius` (positive, finite) or `radii` (2..32 rows of `{ position, radius }`, `position` a ratio 0..1 strictly increasing). Mutually exclusive, and either one takes exactly ONE edge |
1753
- */
1754
- export const PluginGeomCreateBrepFromFilletArgs = z
1755
- .object({
1756
- brep: BrepHandle,
1757
- edges: z.array(EdgeHandle).min(1),
1758
- radius: z.number().finite().positive(),
1759
- options: z
1760
- .object({
1761
- endRadius: z.number().finite().positive().optional(),
1762
- radii: z
1763
- .array(
1764
- z.object({
1765
- position: z.number().finite().min(0).max(1),
1766
- radius: z.number().finite().positive(),
1767
- }),
1768
- )
1769
- .min(2)
1770
- .max(MAX_FILLET_LAW_SAMPLES)
1771
- .optional(),
1772
- })
1773
- .optional(),
1774
- })
1775
- .superRefine((a, ctx) => {
1776
- const { endRadius, radii } = a.options ?? {}
1777
- if (endRadius === undefined && radii === undefined) return
1778
- if (endRadius !== undefined && radii !== undefined) {
1779
- ctx.addIssue({ code: "custom", message: "endRadius is mutually exclusive with radii" })
1780
- }
1781
- // The law belongs to the blend contour the one edge lies on, and `position`
1782
- // is measured from that edge's start: a second edge would have no origin.
1783
- if (a.edges.length !== 1) {
1784
- ctx.addIssue({
1785
- code: "custom",
1786
- message: "a variable fillet takes exactly one edge — the blend contour it runs along",
1787
- })
1788
- }
1789
- if (radii?.some((row, i) => i > 0 && row.position <= radii[i - 1].position)) {
1790
- ctx.addIssue({ code: "custom", message: "radii positions must strictly increase" })
1791
- }
1792
- })
1793
-
1794
- export type PluginGeomCreateBrepFromFilletArgs = z.infer<typeof PluginGeomCreateBrepFromFilletArgs>
1795
-
1796
- /** An edge with the solid it belongs to. See {@linkcode OwnedEdge}. */
1797
- export const OwnedEdge = z.object({
1798
- edge: EdgeHandle,
1799
- brep: BrepHandle,
1800
- face: FaceHandle.optional(),
1801
- })
1802
-
1803
- /**
1804
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromPatch}.
1805
- *
1806
- * | Property | Type | Description |
1807
- * |---|---|---|
1808
- * | `boundary` | ({@linkcode CurveHandle} \| {@linkcode OwnedEdge})`[]` | 3..16 items forming one closed loop |
1809
- * | `thickness` | `number` | Solid thickness in internal length units (positive, finite) |
1810
- * | `options` | `object`? | `interiorPoints` (≤ 64 plain `{x, y, z}` the surface is pulled through), `continuity` (`"position"` default, or `"tangent"` — every item must then be an edge item naming its `face`), `side` (`"below"` default, or `"above"`) |
1811
- */
1812
- export const PluginGeomCreateBrepFromPatchArgs = z
1813
- .object({
1814
- boundary: z
1815
- .array(z.union([CurveHandle, OwnedEdge]))
1816
- .min(3)
1817
- .max(MAX_PATCH_BOUNDARY_CURVES),
1818
- thickness: z.number().finite().positive(),
1819
- options: z
1820
- .object({
1821
- interiorPoints: z.array(FiniteVec3Components).max(MAX_PATCH_INTERIOR_POINTS).optional(),
1822
- continuity: z.enum(["position", "tangent"]).optional(),
1823
- side: z.enum(["below", "above"]).optional(),
1824
- })
1825
- .optional(),
1826
- })
1827
- .superRefine((a, ctx) => {
1828
- if (a.options?.continuity !== "tangent") return
1829
- // The kernel's filling call (`Add_2`) follows exactly ONE support face per
1830
- // edge, but a solid edge touches two faces. So a tangent patch must say
1831
- // which face to follow, for every item, and this is checked before the host
1832
- // runs.
1833
- if (a.boundary.some((item) => !("edge" in item) || item.face === undefined)) {
1834
- ctx.addIssue({
1835
- code: "custom",
1836
- message:
1837
- 'continuity: "tangent" needs every boundary item to be an edge with its brep and the face to continue',
1838
- })
1839
- }
1840
- })
1841
-
1842
- export type PluginGeomCreateBrepFromPatchArgs = z.infer<typeof PluginGeomCreateBrepFromPatchArgs>
1843
-
1844
- /**
1845
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromOffset}.
1846
- *
1847
- * | Property | Type | Description |
1848
- * |---|---|---|
1849
- * | `brep` | {@linkcode BrepHandle} | The solid to offset |
1850
- * | `distance` | `number` | Offset distance (finite, non-zero; positive grows, negative shrinks) |
1851
- */
1852
- export const PluginGeomCreateBrepFromOffsetArgs = z.object({
1853
- brep: BrepHandle,
1854
- distance: z
1855
- .number()
1856
- .finite()
1857
- .refine((value) => value !== 0, { message: "distance must be non-zero" }),
1858
- })
1859
-
1860
- export type PluginGeomCreateBrepFromOffsetArgs = z.infer<typeof PluginGeomCreateBrepFromOffsetArgs>
1861
-
1862
- /**
1863
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromShell}.
1864
- *
1865
- * | Property | Type | Description |
1866
- * |---|---|---|
1867
- * | `brep` | {@linkcode BrepHandle} | The solid to hollow |
1868
- * | `openFaces` | {@linkcode FaceHandle}`[]` | The faces to remove as openings (one or more; more than 32 builds but carries no parameter record) |
1869
- * | `thickness` | `number` | Wall thickness (positive, finite) |
1870
- */
1871
- export const PluginGeomCreateBrepFromShellArgs = z.object({
1872
- brep: BrepHandle,
1873
- openFaces: z.array(FaceHandle).min(1),
1874
- thickness: z.number().finite().positive(),
1875
- })
1876
-
1877
- export type PluginGeomCreateBrepFromShellArgs = z.infer<typeof PluginGeomCreateBrepFromShellArgs>
1878
-
1879
- /**
1880
- * Arguments for {@linkcode PluginGeomCreateApi.brepsFromSplit}.
1881
- *
1882
- * | Property | Type | Description |
1883
- * |---|---|---|
1884
- * | `brep` | {@linkcode BrepHandle} | The solid to split |
1885
- * | `planeOrigin` | {@linkcode Vec3Components} | A point on the cutting plane (finite components) |
1886
- * | `planeNormal` | {@linkcode Vec3Components} | The plane normal (non-zero, finite components) |
1887
- */
1888
- export const PluginGeomCreateBrepsFromSplitArgs = z.object({
1889
- brep: BrepHandle,
1890
- planeOrigin: FiniteVec3Components,
1891
- planeNormal: FiniteVec3Components,
1892
- })
1893
-
1894
- export type PluginGeomCreateBrepsFromSplitArgs = z.infer<typeof PluginGeomCreateBrepsFromSplitArgs>
1895
-
1896
- /**
1897
- * Arguments for {@linkcode PluginGeomCreateApi.brepsFromSplitByFace}.
1898
- *
1899
- * | Property | Type | Description |
1900
- * |---|---|---|
1901
- * | `brep` | {@linkcode BrepHandle} | The solid to cut |
1902
- * | `toolFace` | {@linkcode FaceHandle} | The cutting face (read-only) |
1903
- * | `toolBrep` | {@linkcode BrepHandle} | The solid `toolFace` belongs to (owner last) |
1904
- */
1905
- export const PluginGeomCreateBrepsFromSplitByFaceArgs = z.object({
1906
- brep: BrepHandle,
1907
- toolFace: FaceHandle,
1908
- toolBrep: BrepHandle,
1909
- })
1910
-
1911
- export type PluginGeomCreateBrepsFromSplitByFaceArgs = z.infer<
1912
- typeof PluginGeomCreateBrepsFromSplitByFaceArgs
1913
- >
1914
-
1915
- /**
1916
- * Arguments for {@linkcode PluginGeomCreateApi.curvesFromProjection}.
1917
- *
1918
- * | Property | Type | Description |
1919
- * |---|---|---|
1920
- * | `curve` | {@linkcode CurveHandle} | The curve to project |
1921
- * | `face` | {@linkcode FaceHandle} | The face to project onto |
1922
- * | `faceBrep` | {@linkcode BrepHandle} | The solid `face` belongs to (owner last) |
1923
- */
1924
- export const PluginGeomCreateCurvesFromProjectionArgs = z.object({
1925
- curve: CurveHandle,
1926
- face: FaceHandle,
1927
- faceBrep: BrepHandle,
1928
- })
1929
-
1930
- export type PluginGeomCreateCurvesFromProjectionArgs = z.infer<
1931
- typeof PluginGeomCreateCurvesFromProjectionArgs
1932
- >
1933
-
1934
- /**
1935
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromChamfer}.
1936
- *
1937
- * | Property | Type | Description |
1938
- * |---|---|---|
1939
- * | `brep` | {@linkcode BrepHandle} | The solid whose edges to bevel |
1940
- * | `edges` | {@linkcode EdgeHandle}`[]` | The edges to chamfer (one or more; more than 64 builds but carries no parameter record) |
1941
- * | `distance` | `number` | Chamfer distance from the edge on each adjacent face (positive, finite) |
1942
- */
1943
- export const PluginGeomCreateBrepFromChamferArgs = z.object({
1944
- brep: BrepHandle,
1945
- edges: z.array(EdgeHandle).min(1),
1946
- distance: z.number().finite().positive(),
1947
- })
1948
-
1949
- export type PluginGeomCreateBrepFromChamferArgs = z.infer<
1950
- typeof PluginGeomCreateBrepFromChamferArgs
1951
- >
1952
-
1953
- // Taper scale factor — bounded so a near-zero scale cannot pinch a section
1954
- // into a degenerate sliver the downstream planarity gates would miss.
1955
- const SweepScaleFactor = z.number().finite().min(1e-3).max(1e3)
1956
-
1957
- /**
1958
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromSweep}.
1959
- *
1960
- * | Property | Type | Description |
1961
- * |---|---|---|
1962
- * | `profile` | {@linkcode ContourHandle} | The cross-section to sweep (hole-free) |
1963
- * | `path` | {@linkcode Vec3Components}`[]` | The polyline path (2 or more points, finite components — more than 512 builds but carries no parameter record; repeating the first point as the last closes it into a ring — closed paths must be flat and straight-segmented and take a straight-edged profile) |
1964
- * | `options` | `object`? | Optional scale law: `startScale`/`endScale` (linear taper by arc length) or per-point `scales` (mutually exclusive; factors finite, 1e-3..1e3; `scales.length` must equal `path.length`). Optional corner `transition`: `"miter"` (default), `"round"` (arc-carried corners) or `{ bevel }` (chamfered corners; finite, 1e-3..1e3) |
1965
- */
1966
- export const PluginGeomCreateBrepFromSweepArgs = z
1967
- .object({
1968
- profile: ContourHandle,
1969
- path: z.array(FiniteVec3Components).min(2),
1970
- options: z
1971
- .object({
1972
- startScale: SweepScaleFactor.optional(),
1973
- endScale: SweepScaleFactor.optional(),
1974
- scales: z.array(SweepScaleFactor).optional(),
1975
- transition: z
1976
- .union([
1977
- z.literal("miter"),
1978
- z.literal("round"),
1979
- z.object({ bevel: z.number().finite().min(1e-3).max(1e3) }),
1980
- ])
1981
- .optional(),
1982
- })
1983
- .optional(),
1984
- })
1985
- .superRefine((a, ctx) => {
1986
- const options = a.options
1987
- if (!options || options.scales === undefined) return
1988
- if (options.startScale !== undefined || options.endScale !== undefined) {
1989
- ctx.addIssue({
1990
- code: z.ZodIssueCode.custom,
1991
- message: "scales is mutually exclusive with startScale/endScale",
1992
- })
1993
- }
1994
- if (options.scales.length !== a.path.length) {
1995
- ctx.addIssue({
1996
- code: z.ZodIssueCode.custom,
1997
- message: "scales must have exactly one entry per path point",
1998
- })
1999
- }
2000
- })
2001
-
2002
- export type PluginGeomCreateBrepFromSweepArgs = z.infer<typeof PluginGeomCreateBrepFromSweepArgs>
2003
-
2004
- /**
2005
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromSweepAlongCurve}.
2006
- *
2007
- * | Property | Type | Description |
2008
- * |---|---|---|
2009
- * | `profile` | {@linkcode ContourHandle} | The cross-section to sweep (hole-free) |
2010
- * | `path` | {@linkcode CurveHandle}`[]` | 1..64 curves, ordered, meeting end to end |
2011
- * | `options` | `object`? | `orientation` — `"upright"` (default), `"frenet"` or `"fixed"`; `rail` — a second guide {@linkcode CurveHandle} |
2012
- */
2013
- export const PluginGeomCreateBrepFromSweepAlongCurveArgs = z.object({
2014
- profile: ContourHandle,
2015
- path: z.array(CurveHandle).min(1).max(MAX_PATH_CURVES),
2016
- options: z
2017
- .object({
2018
- orientation: z.enum(["frenet", "fixed", "upright"]).optional(),
2019
- rail: CurveHandle.optional(),
2020
- })
2021
- .optional(),
2022
- })
2023
-
2024
- export type PluginGeomCreateBrepFromSweepAlongCurveArgs = z.infer<
2025
- typeof PluginGeomCreateBrepFromSweepAlongCurveArgs
2026
- >
2027
-
2028
- /**
2029
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromRevolution}.
2030
- *
2031
- * | Property | Type | Description |
2032
- * |---|---|---|
2033
- * | `profile` | {@linkcode ContourHandle} | The cross-section to revolve (holes allowed) |
2034
- * | `axisOrigin` | {@linkcode Vec3Components} | A point on the revolution axis (finite components; the axis must lie in the plane of the profile) |
2035
- * | `axisDirection` | {@linkcode Vec3Components} | The axis direction (non-zero, finite components; the axis must lie in the plane of the profile) |
2036
- * | `angleInDegrees` | `number`? | Revolution angle in degrees (0 < angle ≤ 360; default 360) |
2037
- */
2038
- export const PluginGeomCreateBrepFromRevolutionArgs = z.object({
2039
- profile: ContourHandle,
2040
- axisOrigin: FiniteVec3Components,
2041
- axisDirection: FiniteVec3Components,
2042
- angleInDegrees: z.number().finite().positive().max(360).optional(),
2043
- })
2044
-
2045
- export type PluginGeomCreateBrepFromRevolutionArgs = z.infer<
2046
- typeof PluginGeomCreateBrepFromRevolutionArgs
2047
- >
2048
-
2049
- /**
2050
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromSphere}.
2051
- *
2052
- * | Property | Type | Description |
2053
- * |---|---|---|
2054
- * | `centre` | {@linkcode Vec3Components} | Sphere centre (finite components) |
2055
- * | `radius` | `number` | Sphere radius (finite; positivity is checked host-side so the error names the parameter) |
2056
- */
2057
- export const PluginGeomCreateBrepFromSphereArgs = z.object({
2058
- centre: FiniteVec3Components,
2059
- radius: z.number().finite(),
2060
- })
2061
-
2062
- export type PluginGeomCreateBrepFromSphereArgs = z.infer<
2063
- typeof PluginGeomCreateBrepFromSphereArgs
2064
- >
2065
-
2066
- /**
2067
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromCylinder}.
2068
- *
2069
- * | Property | Type | Description |
2070
- * |---|---|---|
2071
- * | `base` | {@linkcode Vec3Components} | Centre of the bottom cap (finite components) |
2072
- * | `axis` | {@linkcode Vec3Components} | Axis direction (non-zero, finite components) |
2073
- * | `radius` | `number` | Cylinder radius (finite, positive) |
2074
- * | `height` | `number` | Height along `axis` (finite, positive) |
2075
- */
2076
- export const PluginGeomCreateBrepFromCylinderArgs = z.object({
2077
- base: FiniteVec3Components,
2078
- axis: FiniteVec3Components,
2079
- radius: z.number().finite(),
2080
- height: z.number().finite(),
2081
- })
2082
-
2083
- export type PluginGeomCreateBrepFromCylinderArgs = z.infer<
2084
- typeof PluginGeomCreateBrepFromCylinderArgs
2085
- >
2086
-
2087
- /**
2088
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromCone}.
2089
- *
2090
- * | Property | Type | Description |
2091
- * |---|---|---|
2092
- * | `base` | {@linkcode Vec3Components} | Centre of the bottom cap (finite components) |
2093
- * | `axis` | {@linkcode Vec3Components} | Axis direction (non-zero, finite components) |
2094
- * | `baseRadius` | `number` | Radius at the base (finite, positive) |
2095
- * | `height` | `number` | Height along `axis` (finite, positive) |
2096
- * | `topRadius` | `number`? | Radius at the top (finite, ≥ 0, ≠ `baseRadius`; default 0 = apex) |
2097
- */
2098
- export const PluginGeomCreateBrepFromConeArgs = z
2099
- .object({
2100
- base: FiniteVec3Components,
2101
- axis: FiniteVec3Components,
2102
- baseRadius: z.number().finite(),
2103
- height: z.number().finite(),
2104
- topRadius: z.number().finite().optional(),
2105
- })
2106
- .refine((a) => (a.topRadius ?? 0) !== a.baseRadius, {
2107
- message: "topRadius must differ from baseRadius — equal radii are a cylinder",
2108
- })
2109
-
2110
- export type PluginGeomCreateBrepFromConeArgs = z.infer<typeof PluginGeomCreateBrepFromConeArgs>
2111
-
2112
- /**
2113
- * Arguments for {@linkcode PluginGeomCreateApi.brepFromTorus}.
2114
- *
2115
- * | Property | Type | Description |
2116
- * |---|---|---|
2117
- * | `centre` | {@linkcode Vec3Components} | Torus centre (finite components) |
2118
- * | `axis` | {@linkcode Vec3Components} | Plane normal of the ring (non-zero, finite components) |
2119
- * | `majorRadius` | `number` | Ring radius, centre to tube centre (finite, positive) |
2120
- * | `minorRadius` | `number` | Tube radius (finite, positive, strictly less than `majorRadius`) |
2121
- */
2122
- export const PluginGeomCreateBrepFromTorusArgs = z
2123
- .object({
2124
- centre: FiniteVec3Components,
2125
- axis: FiniteVec3Components,
2126
- majorRadius: z.number().finite(),
2127
- minorRadius: z.number().finite(),
2128
- })
2129
- .refine((a) => a.minorRadius < a.majorRadius, {
2130
- message: "minorRadius must be smaller than majorRadius — the torus would self-intersect",
2131
- })
2132
-
2133
- export type PluginGeomCreateBrepFromTorusArgs = z.infer<typeof PluginGeomCreateBrepFromTorusArgs>
1
+ import * as z from "zod"
2
+ import { PluginApiReturn } from "../../../../types"
3
+ import {
4
+ MAX_SPLINE_THROUGH_POINTS,
5
+ MAX_SPLINE_POLES,
6
+ MAX_PATH_CURVES,
7
+ MAX_TWIST_LEVELS,
8
+ MAX_FILLET_LAW_SAMPLES,
9
+ } from "../../../../massParameters"
10
+ import type { SweepAlongCurveRecordOptions } from "../../../../massParameters"
11
+ import { Vec3Handle, LineHandle, ArcHandle, SplineHandle, CircleHandle, CurveHandle, ProfileHandle, ContourHandle, BrepHandle, FaceHandle, EdgeHandle, Vec3Components } from "../../../../handles"
12
+
13
+ /** Maximum boundary items one patch may span. */
14
+ export const MAX_PATCH_BOUNDARY_CURVES = 16
15
+ /** Maximum interior points one patch may be pulled through. */
16
+ export const MAX_PATCH_INTERIOR_POINTS = 64
17
+
18
+ /**
19
+ * An edge named together with the solid it belongs to — an edge handle cannot
20
+ * name its own solid, and one patch may meet two solids. `face` is the adjacent
21
+ * face a tangent patch continues: REQUIRED under `continuity: "tangent"`
22
+ * (a solid edge has two faces — the wall or the top of a rim — and only the
23
+ * caller knows which), ignored otherwise. Owner LAST, like every read that
24
+ * takes `(face, brep)`.
25
+ */
26
+ export type OwnedEdge = { edge: EdgeHandle; brep: BrepHandle; face?: FaceHandle }
27
+
28
+ /**
29
+ * The trailing bag of {@linkcode PluginGeomCreateApi.brepFromSweepAlongCurve}:
30
+ * the recorded switches plus `rail`, a live curve handle that never enters a
31
+ * record (a record is pure JSON, and a dead handle would not regenerate).
32
+ */
33
+ export type SweepAlongCurveOptions = SweepAlongCurveRecordOptions & { rail?: CurveHandle }
34
+
35
+ /**
36
+ * Curve creation — construct new geometric curves from point handles (all-handle
37
+ * model, §11).
38
+ *
39
+ * Each method takes point handles ({@linkcode Vec3Handle}) and returns an opaque
40
+ * curve handle ({@linkcode LineHandle}, {@linkcode ArcHandle}, or
41
+ * {@linkcode SplineHandle}) that you pass to query, update, or entity-creation
42
+ * methods. Read geometry back as plain values via the
43
+ * `snaptrude.core.geom.query.*` methods.
44
+ *
45
+ * Accessed via `snaptrude.core.geom.create`.
46
+ */
47
+ export abstract class PluginGeomCreateApi {
48
+ constructor() {}
49
+
50
+ /**
51
+ * Create a straight line segment between two point handles. Host API call —
52
+ * returns a handle.
53
+ *
54
+ * @param startPoint Start point of the line segment
55
+ * @param endPoint End point of the line segment
56
+ * @returns The new line as a {@linkcode LineHandle}
57
+ *
58
+ * @examplePrompt Draw a line from point A to point B
59
+ * @examplePrompt Draw a straight edge between these two points
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const line = snaptrude.core.geom.create.line(startPoint, endPoint)
64
+ * ```
65
+ */
66
+ public abstract line(startPoint: Vec3Handle, endPoint: Vec3Handle): PluginApiReturn<LineHandle>
67
+
68
+ /**
69
+ * Create a circular arc from its start, end, centre, and axis point handles.
70
+ * Host API call — returns a handle.
71
+ *
72
+ * @param startPoint Start point of the arc
73
+ * @param endPoint End point of the arc
74
+ * @param centrePoint Centre point of the arc
75
+ * @param axis Axis direction of the arc
76
+ * @returns The new arc as an {@linkcode ArcHandle}
77
+ *
78
+ * @examplePrompt Draw an arc between these two points around a centre
79
+ * @examplePrompt Add a curved segment from A to B
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const arc = snaptrude.core.geom.create.arc(startPoint, endPoint, centrePoint, axis)
84
+ * ```
85
+ */
86
+ public abstract arc(
87
+ startPoint: Vec3Handle,
88
+ endPoint: Vec3Handle,
89
+ centrePoint: Vec3Handle,
90
+ axis: Vec3Handle,
91
+ ): PluginApiReturn<ArcHandle>
92
+
93
+ /**
94
+ * Create a circle from a centre point, an axis (plane normal), and a radius.
95
+ * Host API call — returns a handle. A circle is a closed curve; read it back via
96
+ * `snaptrude.core.geom.query.circle.*`.
97
+ *
98
+ * @param centrePoint Centre point of the circle
99
+ * @param axis Axis direction (plane normal) of the circle
100
+ * @param radius Radius of the circle
101
+ * @returns The new circle as a {@linkcode CircleHandle}
102
+ *
103
+ * @examplePrompt Make a circle of radius 2 at this point
104
+ * @examplePrompt Draw a circular outline 5m across
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * const circle = snaptrude.core.geom.create.circle(centrePoint, axis, 2)
109
+ * ```
110
+ */
111
+ public abstract circle(
112
+ centrePoint: Vec3Handle,
113
+ axis: Vec3Handle,
114
+ radius: number,
115
+ ): PluginApiReturn<CircleHandle>
116
+
117
+ /**
118
+ * Create an open NURBS curve passing exactly THROUGH each point, in order
119
+ * (centripetal Catmull-Rom fit → clamped cubic B-spline; C1-smooth, no cusps
120
+ * on uneven spacing). Host API call — returns a handle. The points are
121
+ * interpolated, NOT control points. For a CLOSED smooth loop use
122
+ * {@linkcode PluginGeomCreateApi.profileFromSplinePoints} — closed splines
123
+ * exist only as profiles, never as a single curve handle.
124
+ *
125
+ * Pure host-side math — the geometry kernel is not loaded. The result is a
126
+ * {@linkcode CurveHandle}: pass it to `profileFromCurves` alongside lines and
127
+ * arcs, or read it back via `core.geom.query.curve.*`.
128
+ *
129
+ * @param points 3..256 ordered through-points; consecutive points must be distinct
130
+ * @returns The new open curve as a {@linkcode SplineHandle}
131
+ * @throws VALIDATION if fewer than 3 points (2 points are a straight line —
132
+ * use {@linkcode PluginGeomCreateApi.line}), more than 256 points, or two
133
+ * consecutive points coincide
134
+ *
135
+ * @examplePrompt Draw a smooth curve through these points
136
+ * @examplePrompt Make a wavy edge that passes through A, B, C and D
137
+ * @examplePrompt Freeform curved wall path through these clicks
138
+ *
139
+ * # Example
140
+ * ```ts
141
+ * const spline = await snaptrude.core.geom.create.splineFromPoints([p0, p1, p2, p3])
142
+ * // Mixed profile: smooth top, straight closing base
143
+ * const base = await snaptrude.core.geom.create.line(p3, p0)
144
+ * const profile = await snaptrude.core.geom.create.profileFromCurves([spline, base])
145
+ * ```
146
+ */
147
+ public abstract splineFromPoints(points: Vec3Handle[]): PluginApiReturn<SplineHandle>
148
+
149
+ /**
150
+ * Create an open NURBS curve DEFINED BY control points — the curve is pulled
151
+ * TOWARD the interior points, not through them; it starts at the first point
152
+ * and ends at the last. Knots are computed for you (clamped, uniform) —
153
+ * there is no knot-vector argument. For a curve that must pass THROUGH given
154
+ * points use {@linkcode PluginGeomCreateApi.splineFromPoints}.
155
+ *
156
+ * A curve made here records as `splinePoles` in a mass recipe and is
157
+ * re-minted verbatim on edit — so an agent that read a `splinePoles` curve
158
+ * back off a mass can move its poles and rebuild the mass with
159
+ * `design.update.parameters`. Pure host-side math — the geometry kernel is
160
+ * not loaded, and nothing is created in the scene. An edge's control points
161
+ * rebuild the same curve here only when its knots are clamped uniform.
162
+ *
163
+ * @param controlPoints 3..512 control points ({@linkcode Vec3Handle}s), in order
164
+ * @param options.degree 2 or 3; default `min(3, controlPoints.length - 1)`
165
+ * @returns The new open curve as a {@linkcode SplineHandle}
166
+ * @throws VALIDATION when fewer than `degree + 1` points are given, two
167
+ * consecutive points coincide, or `degree` is not 2 or 3
168
+ *
169
+ * @examplePrompt Draw a smooth curve controlled by these four points
170
+ *
171
+ * # Example
172
+ * ```ts
173
+ * const curve = await snaptrude.core.geom.create.splineFromControlPoints([p0, p1, p2, p3])
174
+ * // Quadratic instead of the default cubic
175
+ * const tighter = await snaptrude.core.geom.create.splineFromControlPoints(
176
+ * [p0, p1, p2, p3],
177
+ * { degree: 2 },
178
+ * )
179
+ * ```
180
+ */
181
+ public abstract splineFromControlPoints(
182
+ controlPoints: Vec3Handle[],
183
+ options?: { degree?: number },
184
+ ): PluginApiReturn<SplineHandle>
185
+
186
+ /**
187
+ * Drops a curve onto a face along the face's normal and returns the curves it
188
+ * lands on — one per connected run, empty when the curve misses the face
189
+ * entirely. Host API call — returns new curve handles; the inputs are
190
+ * read-only. No lengths are involved: the result is a curve in the same
191
+ * internal length units as its inputs.
192
+ *
193
+ * @param curve The curve to project
194
+ * @param face The face to project onto
195
+ * @param faceBrep The solid `face` belongs to — a face handle cannot name its
196
+ * own solid
197
+ * @returns The projected curves as {@linkcode CurveHandle}`[]`
198
+ * @throws VALIDATION if `face` is not a face of `faceBrep`, or the curve's
199
+ * midpoint lies on the face's surface (there is no drop direction there)
200
+ * @throws OPERATION_FAILED if the kernel could not project the curve
201
+ */
202
+ public abstract curvesFromProjection(
203
+ curve: CurveHandle,
204
+ face: FaceHandle,
205
+ faceBrep: BrepHandle,
206
+ ): PluginApiReturn<CurveHandle[]>
207
+
208
+ /**
209
+ * Create a closed profile from an ordered list of point handles connected by
210
+ * line segments (last auto-connected to first). Host API call — returns a handle.
211
+ *
212
+ * @param points Ordered vertex handles of the profile
213
+ * @returns The new profile as a {@linkcode ProfileHandle}
214
+ *
215
+ * @examplePrompt Draw a closed polygon through these corner points
216
+ * @examplePrompt Create an L-shaped outline from a list of points
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * const profile = snaptrude.core.geom.create.profileFromLinePoints(points)
221
+ * ```
222
+ */
223
+ public abstract profileFromLinePoints(points: Vec3Handle[]): PluginApiReturn<ProfileHandle>
224
+
225
+ /**
226
+ * Create a profile from an ordered list of curve handles forming a closed loop.
227
+ * Host API call — returns a handle.
228
+ *
229
+ * @param curves Ordered curve handles forming a closed loop
230
+ * @returns The new profile as a {@linkcode ProfileHandle}
231
+ *
232
+ * @examplePrompt Join these lines and arcs into one closed shape
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * const profile = snaptrude.core.geom.create.profileFromCurves([line, arc])
237
+ * ```
238
+ */
239
+ public abstract profileFromCurves(curves: CurveHandle[]): PluginApiReturn<ProfileHandle>
240
+
241
+ /**
242
+ * Create a CLOSED smooth profile passing through each point, in order, with
243
+ * the loop closed smoothly back from the last point to the first (do NOT
244
+ * repeat the first point). Host API call — returns a handle. This is the
245
+ * closed counterpart of {@linkcode PluginGeomCreateApi.splineFromPoints} and
246
+ * the one-call route from points to an extrudable outline: wrap in
247
+ * `contourFromProfile` and extrude/loft/sweep as usual.
248
+ *
249
+ * The whole loop is smooth — this call takes no mixed straight/spline input.
250
+ * For a profile that is part curve and part straight, build the pieces with
251
+ * `splineFromPoints` + `line` and join them with
252
+ * {@linkcode PluginGeomCreateApi.profileFromCurves}.
253
+ *
254
+ * @param points 3..256 ordered through-points of the loop (first point NOT repeated)
255
+ * @returns The new closed profile as a {@linkcode ProfileHandle}
256
+ * @throws VALIDATION if fewer than 3 points, more than 256 points,
257
+ * consecutive points coincide (including last→first), or the loop
258
+ * self-intersects
259
+ *
260
+ * @examplePrompt Make a blob-shaped floor plate through these points
261
+ * @examplePrompt Organic curved outline through these corners
262
+ * @examplePrompt Freeform podium profile
263
+ *
264
+ * # Example
265
+ * ```ts
266
+ * const profile = await snaptrude.core.geom.create.profileFromSplinePoints([p0, p1, p2, p3, p4])
267
+ * const contour = await snaptrude.core.geom.create.contourFromProfile(profile)
268
+ * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)
269
+ * const podium = await snaptrude.design.create.massFromBrep(brep, "Podium")
270
+ * ```
271
+ */
272
+ public abstract profileFromSplinePoints(points: Vec3Handle[]): PluginApiReturn<ProfileHandle>
273
+
274
+ /**
275
+ * Create an axis-aligned **rectangle** profile in the XZ plane,
276
+ * `width` × `depth`, centred at `center` (defaults to the origin). Host API
277
+ * call — returns a handle.
278
+ *
279
+ * Pair with `design.create.space` / `design.create.spaces` to author a rectangular
280
+ * (box) space — there is no dedicated rectangular-space creator; build the profile
281
+ * here, then extrude it.
282
+ *
283
+ * @param width Extent along X
284
+ * @param depth Extent along Z
285
+ * @param center Optional centre point (default origin)
286
+ * @returns The new rectangle profile as a {@linkcode ProfileHandle}
287
+ *
288
+ * @examplePrompt Draw a 4m by 3m rectangle
289
+ * @examplePrompt Make a rectangular outline for a room footprint
290
+ *
291
+ * @example
292
+ * ```ts
293
+ * const rect = snaptrude.core.geom.create.profileRect(4, 3)
294
+ * const contour = snaptrude.core.geom.create.contourFromProfile(rect)
295
+ * const space = snaptrude.design.create.space(contour, 3)
296
+ * ```
297
+ */
298
+ public abstract profileRect(
299
+ width: number,
300
+ depth: number,
301
+ center?: Vec3Handle,
302
+ ): PluginApiReturn<ProfileHandle>
303
+
304
+ /**
305
+ * Create a contour (an outer profile loop, no holes). Host API call — returns a handle.
306
+ *
307
+ * @param outer The outer boundary loop
308
+ * @returns The new contour as a {@linkcode ContourHandle}
309
+ * @example
310
+ * ```ts
311
+ * const contour = snaptrude.core.geom.create.contourFromProfile(outer)
312
+ * ```
313
+ */
314
+ public abstract contourFromProfile(outer: ProfileHandle): PluginApiReturn<ContourHandle>
315
+
316
+ /**
317
+ * Create a contour from an outer profile plus zero or more inner profiles (holes).
318
+ * Host API call — returns a handle.
319
+ *
320
+ * A hole here is part of the footprint and cannot be moved or deleted later;
321
+ * for an editable hole in a slab use `design.create.void`.
322
+ *
323
+ * @param outer The outer boundary loop
324
+ * @param holes Optional inner hole loops
325
+ * @returns The new contour as a {@linkcode ContourHandle}
326
+ *
327
+ * @examplePrompt Make a shape with a hole in the middle
328
+ * @examplePrompt Cut a courtyard opening out of this floor outline
329
+ *
330
+ * @example
331
+ * ```ts
332
+ * const contour = snaptrude.core.geom.create.contourFromProfiles(outer, [hole])
333
+ * ```
334
+ */
335
+ public abstract contourFromProfiles(
336
+ outer: ProfileHandle,
337
+ holes?: ProfileHandle[],
338
+ ): PluginApiReturn<ContourHandle>
339
+
340
+ /**
341
+ * Create a closed solid **B-rep** from explicit face loops. Host API call —
342
+ * returns a {@linkcode BrepHandle}. Faces are plain arrays of `{x, y, z}`
343
+ * point components, NOT point handles (bulk-data precedent:
344
+ * `design.query.geometry.getTriangulatedMeshes`), in raw Babylon units.
345
+ *
346
+ * Each face is one planar loop of at least 3 points; at least 4 faces are
347
+ * required. Loops may be authored in any consistent winding — the host
348
+ * validates edge coherence (every edge shared by exactly two faces, in
349
+ * opposite directions) and fixes the global orientation so faces point
350
+ * outward. Faces must be planar and the solid must be closed; holes in faces
351
+ * are not supported (v1).
352
+ *
353
+ * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
354
+ * with `design.create.massFromBrep`.
355
+ *
356
+ * @param faces Face loops — each an ordered array of `{x, y, z}` points (≥3 points per face, ≥4 faces)
357
+ * @returns The new solid as a {@linkcode BrepHandle}
358
+ * @throws if a face is degenerate or non-planar, an edge is not shared by
359
+ * exactly two faces (open shell, non-manifold, or inconsistent winding), or
360
+ * the faces do not form a valid closed solid
361
+ *
362
+ * @examplePrompt Create a brep from faces
363
+ * @examplePrompt Build a custom solid from a set of faces
364
+ * @examplePrompt Make a pyramid from triangular faces
365
+ *
366
+ * # Example
367
+ * ```ts
368
+ * // A pyramid: square base + 4 triangular sides
369
+ * const apex = { x: 0, y: 4, z: 0 }
370
+ * const a = { x: -2, y: 0, z: -2 }
371
+ * const b = { x: 2, y: 0, z: -2 }
372
+ * const c = { x: 2, y: 0, z: 2 }
373
+ * const d = { x: -2, y: 0, z: 2 }
374
+ * const brep = await snaptrude.core.geom.create.brepFromFaces([
375
+ * [a, d, c, b], // base
376
+ * [a, b, apex],
377
+ * [b, c, apex],
378
+ * [c, d, apex],
379
+ * [d, a, apex],
380
+ * ])
381
+ * const faceCount = await snaptrude.core.geom.query.brep.getFaceCount(brep) // 5
382
+ * ```
383
+ */
384
+ public abstract brepFromFaces(faces: Vec3Components[][]): PluginApiReturn<BrepHandle>
385
+
386
+ /**
387
+ * Create a closed solid **B-rep** by extruding a contour along a direction.
388
+ * Host API call — returns a {@linkcode BrepHandle}. The direction is
389
+ * normalised by the host, so `amount` is the extrusion distance in raw
390
+ * Babylon units (negative extrudes the opposite way). The contour is copied —
391
+ * the input handle is never mutated. Holes and arc/circle profiles extrude
392
+ * natively (a circle profile yields a cylinder).
393
+ *
394
+ * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
395
+ * with `design.create.massFromBrep`.
396
+ *
397
+ * @param contour The cross-section to extrude (outer profile + optional holes)
398
+ * @param direction Extrusion direction as plain `{x, y, z}` components (non-zero, normalised by the host)
399
+ * @param amount Extrusion distance in internal length units (non-zero; negative extrudes opposite to `direction`)
400
+ * @param options Optional twist and taper, both about `direction` through the
401
+ * base contour's centroid. `twistInDegrees` — total rotation base → top in
402
+ * DEGREES (an angle: never scaled by the project's length unit), positive
403
+ * turning the top CLOCKWISE seen from the far end of `direction` looking
404
+ * back at the base — for the usual upward extrusion, the plan view;
405
+ * `topScale` — the top outline's scale, a ratio (1 = no taper); `levels` —
406
+ * how many sections to build through, a count (2..64; each step may turn
407
+ * at most 22.5°). Leave `levels` out
408
+ * and the host resolves one and writes it into the record, so the mass
409
+ * regenerates identically ever after. An extrusion without twist or
410
+ * taper (no `options`, or zero twist and a `topScale` of 1) is
411
+ * built exactly as before these options existed.
412
+ * @returns The new solid as a {@linkcode BrepHandle}
413
+ * @throws if the extrusion is degenerate (zero amount, zero direction, or a
414
+ * direction lying in the plane of the contour) or the contour cannot be
415
+ * extruded into a valid solid
416
+ * @throws VALIDATION if `twistInDegrees` is not finite or turns more than
417
+ * 1440°, `topScale` is not a positive finite ratio, `levels` is outside
418
+ * 2..64 or too few for the twist (more than 22.5° per step), a twist is
419
+ * asked of a `direction` that is not the contour's normal, the contour has
420
+ * holes under a twist or taper, or a scaled section self-intersects
421
+ *
422
+ * @examplePrompt Extrude this profile 3 metres up
423
+ * @examplePrompt Make a solid by extruding this outline
424
+ * @examplePrompt Create a cylinder from a circle profile
425
+ * @examplePrompt Extrude this floor plate 40m up with a 90 degree twist
426
+ * @examplePrompt Taper this tower to 60 percent of its footprint at the top
427
+ *
428
+ * # Example
429
+ * ```ts
430
+ * const rect = await snaptrude.core.geom.create.profileRect(4, 3)
431
+ * const contour = await snaptrude.core.geom.create.contourFromProfile(rect)
432
+ * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)
433
+ * const faceCount = await snaptrude.core.geom.query.brep.getFaceCount(brep) // 6
434
+ * ```
435
+ */
436
+ public abstract brepFromExtrusion(
437
+ contour: ContourHandle,
438
+ direction: Vec3Components,
439
+ amount: number,
440
+ options?: { twistInDegrees?: number; topScale?: number; levels?: number },
441
+ ): PluginApiReturn<BrepHandle>
442
+
443
+ /**
444
+ * Stretches a surface across a closed loop of boundary curves and thickens it
445
+ * into a SOLID in one call — a roof over four curves, a canopy between two
446
+ * rails, a blend across a gap. `thickness` is in internal length units and is
447
+ * required: only solids reach the canvas, and there is no separate surface
448
+ * object to keep. Pass edges as `{ edge, brep, face }` with
449
+ * `continuity: "tangent"` to leave the named face smoothly. The result carries
450
+ * NO recipe (`design.query.getParameters` returns `null`) — label it when you
451
+ * commit it, because the label is all the provenance there is.
452
+ *
453
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
454
+ * of MB — expect a pause of seconds).
455
+ *
456
+ * @param boundary 3..16 items forming ONE closed loop (ends meeting within
457
+ * tolerance): a curve, or an edge named WITH the solid it belongs to
458
+ * ({@linkcode OwnedEdge}) — an edge handle cannot name its own solid, and
459
+ * one patch may meet two solids. Read scene breps with
460
+ * `design.query.geometry.getBrep(x, { frame: "world" })` so every item is in
461
+ * one frame.
462
+ * @param thickness Solid thickness in internal length units (> 0)
463
+ * @param options `interiorPoints` pull the surface through them (≤ 64, plain
464
+ * `{x, y, z}` in the same frame as the boundary). `continuity` —
465
+ * `"position"` (default) or `"tangent"`: every boundary item must then be an
466
+ * edge item that also names the `face` to continue (a solid edge has two
467
+ * faces — the wall or the top of a rim). `side` — `"below"` (default) or
468
+ * `"above"`: which side of the boundary the solid grows to.
469
+ * @returns The thickened patch as a new {@linkcode BrepHandle}
470
+ * @throws VALIDATION if the boundary is not one closed loop (the message names
471
+ * the gap and its size), a count cap is exceeded, `thickness` is not
472
+ * positive, `continuity: "tangent"` is used with a curve item or with an
473
+ * edge item that names no `face` (the message names the edge's two candidate
474
+ * faces), or a `face` is not adjacent to its `edge`
475
+ * @throws OPERATION_FAILED if the kernel could not fill the boundary, the
476
+ * filled surface misses its own boundary by more than 0.254 mm (the message
477
+ * carries the measured gap), or the thicken produced no solid
478
+ *
479
+ * @examplePrompt Patch a roof over these four boundary curves, 250 mm thick
480
+ * @examplePrompt Span a canopy between these two curved rails, 300 mm thick
481
+ *
482
+ * # Example
483
+ * ```ts
484
+ * // A roof over four curves
485
+ * const roof = await snaptrude.core.geom.create.brepFromPatch(
486
+ * [curveA, curveB, curveC, curveD],
487
+ * 0.984, // 250 mm
488
+ * )
489
+ * const mass = await snaptrude.design.create.massFromBrep(roof, "Roof patch")
490
+ * ```
491
+ *
492
+ * ```ts
493
+ * // A canopy between two towers' rims, leaving every wall it meets smoothly:
494
+ * // with `continuity: "tangent"` EVERY item is an edge naming its face.
495
+ * const canopy = await snaptrude.core.geom.create.brepFromPatch(
496
+ * [
497
+ * { edge: rimA, brep: towerA, face: wallA },
498
+ * { edge: sideA, brep: towerA, face: endA },
499
+ * { edge: rimB, brep: towerB, face: wallB },
500
+ * { edge: sideB, brep: towerB, face: endB },
501
+ * ],
502
+ * 1.181, // 300 mm
503
+ * { continuity: "tangent" },
504
+ * )
505
+ * ```
506
+ */
507
+ public abstract brepFromPatch(
508
+ boundary: (CurveHandle | OwnedEdge)[],
509
+ thickness: number,
510
+ options?: {
511
+ interiorPoints?: Vec3Components[]
512
+ continuity?: "position" | "tangent"
513
+ side?: "below" | "above"
514
+ },
515
+ ): PluginApiReturn<BrepHandle>
516
+
517
+ /**
518
+ * Create a closed solid **B-rep** by lofting between a bottom and a top
519
+ * contour, optionally through intermediate cross-sections. Host API call —
520
+ * returns a {@linkcode BrepHandle}. Sections with different edge counts are
521
+ * auto-matched: vertices are deterministically inserted on the smaller
522
+ * sections at the larger section's perimeter positions, so the same input
523
+ * always yields the same solid. For straight-edge (all-line) sections the
524
+ * seam is auto-aligned too — winding and start-vertex correspondence are
525
+ * chosen to avoid a twist, so outlines authored from any starting corner
526
+ * loft cleanly; arc-bearing sections keep the authored correspondence
527
+ * unchanged. A pair whose side faces would be non-planar under every
528
+ * correspondence still builds for hole-free straight-edged pairs — planar
529
+ * side quads stay quads and warped ones split into planar triangles (the
530
+ * triangulated fallback); arc-bearing or holed twisted pairs are rejected, as
531
+ * is auto-matching that would pair an arc with a straight edge. Matching hole
532
+ * counts are allowed in the two-section form. With intermediates the loft
533
+ * is a chain of ruled segments folded into one solid: sections must be
534
+ * planar and hole-free, ordered bottom → intermediates → top. The contours
535
+ * are copied — the input handles are never mutated.
536
+ *
537
+ * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
538
+ * with `design.create.massFromBrep`.
539
+ *
540
+ * @param bottomContour The bottom cross-section
541
+ * @param topContour The top cross-section (edge count may differ from the
542
+ * bottom — sections are auto-matched)
543
+ * @param intermediateContours Optional in-between cross-sections, ordered
544
+ * bottom to top (hole-free; edge counts may differ — every section is
545
+ * normalized to the largest). Any count is accepted; a loft of more
546
+ * than 32 sections counting the bottom and the top still builds but
547
+ * carries no parameter record (records are capped, calls are not)
548
+ * @param options Optional behavior switches. `compatibility` — `"auto"`
549
+ * (default) inserts vertices to match differing edge counts; `"strict"`
550
+ * skips normalization and throws VALIDATION when outer edge counts (or,
551
+ * in the two-section form, hole-pair counts) differ. `seamAlignment` —
552
+ * `"auto"` (default) searches seam rotations on straight-edge sections to
553
+ * avoid a twist; `"authored"` keeps the authored start-vertex
554
+ * correspondence unchanged (a twisted correspondence then builds through
555
+ * the triangulated fallback for hole-free straight-edged pairs).
556
+ * `{ compatibility: "strict", seamAlignment: "authored" }` reproduces
557
+ * pre-0.9.4 lofting exactly. `smoothing` — `"ruled"` (default) keeps the
558
+ * straight-sided behaviour described above; `"smooth"` lofts every
559
+ * section in ONE kernel pass, so the sides curve continuously through the
560
+ * intermediates instead of creasing at each one (a vase, a curved tower —
561
+ * the sections must still be planar and hole-free).
562
+ * @returns The new solid as a {@linkcode BrepHandle}
563
+ * @throws VALIDATION if the contours are coincident, their hole counts
564
+ * differ, any section has holes when intermediates are present
565
+ * (multi-section lofts take hole-free sections), a section is non-planar,
566
+ * sections are too dissimilar to auto-match, auto-matching would pair an
567
+ * arc with a straight edge, or — for arc-bearing or holed pairs only — no vertex correspondence avoids a twist (a
568
+ * side face between corresponding edges would be non-planar — the error
569
+ * names the offending sections); with `compatibility: "strict"`, if
570
+ * section edge counts (or hole-pair counts) differ — the error names the
571
+ * sections and counts and points at `compatibility: "auto"`
572
+ * @throws OPERATION_FAILED if the lofted segments cannot be joined into a
573
+ * valid solid
574
+ *
575
+ * @examplePrompt Make a tapered tower from these two outlines
576
+ * @examplePrompt Loft between a large base and a smaller top
577
+ * @examplePrompt Create a frustum from two squares
578
+ * @examplePrompt Loft the tower through these three floor outlines
579
+ * @examplePrompt Loft a four-sided base to a five-sided top
580
+ * @examplePrompt Loft these outlines even though their corners don't line up
581
+ * @examplePrompt Loft these outlines exactly as authored, corner to corner
582
+ * @examplePrompt Fail the loft if my sections don't have matching edge counts
583
+ * @examplePrompt Loft these three outlines with smoothly curving sides
584
+ *
585
+ * # Example
586
+ * ```ts
587
+ * // A square frustum: 4m base lofted to a 2m top, 3m up
588
+ * const base = await snaptrude.core.geom.create.profileRect(4, 4)
589
+ * const top = await snaptrude.core.geom.create.profileRect(2, 2, await snaptrude.core.math.vec3.new(0, 3, 0))
590
+ * const brep = await snaptrude.core.geom.create.brepFromLoft(
591
+ * await snaptrude.core.geom.create.contourFromProfile(base),
592
+ * await snaptrude.core.geom.create.contourFromProfile(top),
593
+ * )
594
+ * const faceCount = await snaptrude.core.geom.query.brep.getFaceCount(brep) // 6
595
+ * ```
596
+ */
597
+ public abstract brepFromLoft(
598
+ bottomContour: ContourHandle,
599
+ topContour: ContourHandle,
600
+ intermediateContours?: ContourHandle[],
601
+ options?: {
602
+ compatibility?: "strict" | "auto"
603
+ seamAlignment?: "authored" | "auto"
604
+ smoothing?: "ruled" | "smooth"
605
+ },
606
+ ): PluginApiReturn<BrepHandle>
607
+
608
+ /**
609
+ * Create a closed solid **B-rep** from indexed mesh data: a vertex position
610
+ * array plus face loops of indices into it. Host API call — returns a
611
+ * {@linkcode BrepHandle}. The indexed form of
612
+ * {@linkcode PluginGeomCreateApi.brepFromFaces} — same validation (planar
613
+ * faces, closed manifold solid, every edge shared by exactly two faces) after
614
+ * the indices are expanded to point loops.
615
+ *
616
+ * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
617
+ * with `design.create.massFromBrep`.
618
+ *
619
+ * @param positions Vertex positions as plain `{x, y, z}` components (≥4)
620
+ * @param faces Face loops — each an ordered array of indices into `positions` (≥3 indices per face, ≥4 faces)
621
+ * @returns The new solid as a {@linkcode BrepHandle}
622
+ * @throws if an index is not an integer within `positions` bounds, a face is
623
+ * degenerate or non-planar, or the faces do not form a valid closed solid
624
+ *
625
+ * @examplePrompt Create a brep from mesh vertices and indices
626
+ * @examplePrompt Turn this indexed mesh into a solid
627
+ * @examplePrompt Build a solid from vertex positions and face indices
628
+ *
629
+ * # Example
630
+ * ```ts
631
+ * // A tetrahedron from 4 vertices and 4 triangular faces
632
+ * const positions = [
633
+ * { x: 0, y: 0, z: 0 },
634
+ * { x: 4, y: 0, z: 0 },
635
+ * { x: 2, y: 0, z: 4 },
636
+ * { x: 2, y: 3, z: 1.5 },
637
+ * ]
638
+ * const brep = await snaptrude.core.geom.create.brepFromMesh(positions, [
639
+ * [0, 2, 1], // base
640
+ * [0, 1, 3],
641
+ * [1, 2, 3],
642
+ * [2, 0, 3],
643
+ * ])
644
+ * const vertexCount = await snaptrude.core.geom.query.brep.getVertexCount(brep) // 4
645
+ * ```
646
+ */
647
+ public abstract brepFromMesh(
648
+ positions: Vec3Components[],
649
+ faces: number[][],
650
+ ): PluginApiReturn<BrepHandle>
651
+
652
+ /**
653
+ * Create a closed solid **B-rep** as the boolean **union** of two breps
654
+ * (`a ∪ b`). Host API call — returns a new {@linkcode BrepHandle}. Inputs are
655
+ * read-only and may be authored breps (any `core.geom.create` brep
656
+ * constructor) or scene-derived breps from `design.query.geometry.getBrep`;
657
+ * coordinates are combined as-is, so both inputs must share a frame. The
658
+ * result must be a single solid — disjoint inputs are rejected.
659
+ *
660
+ * The first boolean call loads the OpenCascade geometry kernel (a wasm of
661
+ * tens of MB — expect a pause of seconds); subsequent calls are fast.
662
+ *
663
+ * @param a First solid
664
+ * @param b Second solid
665
+ * @returns The union as a new {@linkcode BrepHandle}
666
+ * @throws if the inputs do not overlap or touch (the union would be disjoint
667
+ * solids) or the result cannot be built as a valid single solid
668
+ *
669
+ * @examplePrompt Union these two solids
670
+ * @examplePrompt Merge these breps into one mass
671
+ * @examplePrompt Combine the tower and the podium into a single solid
672
+ *
673
+ * # Example
674
+ * ```ts
675
+ * const a = await snaptrude.core.geom.create.brepFromExtrusion(baseContour, { x: 0, y: 1, z: 0 }, 3)
676
+ * const b = await snaptrude.core.geom.create.brepFromExtrusion(overlappingContour, { x: 0, y: 1, z: 0 }, 5)
677
+ * const merged = await snaptrude.core.geom.create.brepFromUnion(a, b)
678
+ * ```
679
+ */
680
+ public abstract brepFromUnion(a: BrepHandle, b: BrepHandle): PluginApiReturn<BrepHandle>
681
+
682
+ /**
683
+ * Create a closed solid **B-rep** as the boolean **subtraction** of two
684
+ * breps: **`a` minus `b`** — `b` is cut away from `a`. Host API call —
685
+ * returns a new {@linkcode BrepHandle}. Argument order matters:
686
+ * `brepFromSubtraction(a, b)` removes `b`'s volume from `a`. Inputs are
687
+ * read-only and may be authored or scene-derived breps (see
688
+ * {@linkcode PluginGeomCreateApi.brepFromUnion}); coordinates are combined
689
+ * as-is.
690
+ *
691
+ * The first boolean call loads the OpenCascade geometry kernel (a wasm of
692
+ * tens of MB — expect a pause of seconds); subsequent calls are fast.
693
+ *
694
+ * Returns a detached solid; to cut an editable hole in a slab, floor or roof
695
+ * in the model use `design.create.void`.
696
+ *
697
+ * @param a The solid to subtract from
698
+ * @param b The solid to remove from `a`
699
+ * @returns `a` minus `b` as a new {@linkcode BrepHandle}
700
+ * @throws if `b` consumes `a` entirely (empty result), the result splits into
701
+ * disjoint solids, or it cannot be built as a valid single solid
702
+ *
703
+ * @examplePrompt Subtract the courtyard from the building mass
704
+ * @examplePrompt Cut this solid out of that one
705
+ * @examplePrompt Subtract this box solid from that solid
706
+ *
707
+ * # Example
708
+ * ```ts
709
+ * // buildingBrep minus courtyardBrep — order matters
710
+ * const carved = await snaptrude.core.geom.create.brepFromSubtraction(buildingBrep, courtyardBrep)
711
+ * ```
712
+ */
713
+ public abstract brepFromSubtraction(a: BrepHandle, b: BrepHandle): PluginApiReturn<BrepHandle>
714
+
715
+ /**
716
+ * Create a closed solid **B-rep** as the boolean **intersection** of two
717
+ * breps (`a ∩ b` — the shared volume only). Host API call — returns a new
718
+ * {@linkcode BrepHandle}. Inputs are read-only and may be authored or
719
+ * scene-derived breps (see {@linkcode PluginGeomCreateApi.brepFromUnion});
720
+ * coordinates are combined as-is.
721
+ *
722
+ * The first boolean call loads the OpenCascade geometry kernel (a wasm of
723
+ * tens of MB — expect a pause of seconds); subsequent calls are fast.
724
+ *
725
+ * @param a First solid
726
+ * @param b Second solid
727
+ * @returns The shared volume as a new {@linkcode BrepHandle}
728
+ * @throws if the inputs do not overlap (empty intersection), the result
729
+ * splits into disjoint solids, or it cannot be built as a valid single
730
+ * solid
731
+ *
732
+ * @examplePrompt Keep only the overlap of these two solids
733
+ * @examplePrompt Intersect these two masses
734
+ * @examplePrompt Clip the tower to the zoning envelope
735
+ *
736
+ * # Example
737
+ * ```ts
738
+ * const overlap = await snaptrude.core.geom.create.brepFromIntersection(towerBrep, envelopeBrep)
739
+ * ```
740
+ */
741
+ public abstract brepFromIntersection(a: BrepHandle, b: BrepHandle): PluginApiReturn<BrepHandle>
742
+
743
+ /**
744
+ * Rounds the given edges of a solid with a constant radius. Host API call —
745
+ * returns a new {@linkcode BrepHandle}; the input brep is read-only.
746
+ *
747
+ * Works on curved solids: edges may be straight, circular or spline, and
748
+ * adjacent edges (corner blends) are allowed. To pick a **rim** — the circle
749
+ * where a cylinder meets a flat cap — walk the edges and keep the ones whose
750
+ * two faces are a `cylinder` and a `plane`:
751
+ * `core.geom.query.brep.listEdges(brep)` → per edge
752
+ * `core.geom.query.edge.listFaces(edge)` → `core.geom.query.face.getSurfaceKind`
753
+ * on both faces. (`core.geom.query.brep.listEdgesBetween` takes two VERTEX
754
+ * indices — it is NOT a face-pair lookup.) For one cap's rim only, the
755
+ * shorter walk is: find the cap (call `getSurfaceKind` on each face from
756
+ * `brep.listFaces`), then `core.geom.query.face.listEdges(capFace)`.
757
+ *
758
+ * When the input brep carries a creation recipe, the result carries it too,
759
+ * with this operation appended to `operations`; `design.create.massFromBrep`
760
+ * then keeps the mass editable through `design.update.parameters`.
761
+ *
762
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
763
+ * of MB — expect a pause of seconds).
764
+ *
765
+ * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
766
+ * with `design.create.massFromBrep`.
767
+ *
768
+ * @param brep The solid whose edges to round
769
+ * @param edges The edges to fillet (one or more, all on `brep`, no
770
+ * duplicates; more than 64 still fillets but the result carries no
771
+ * parameter record)
772
+ * @param radius Fillet radius in internal length units (positive, finite) —
773
+ * the radius at the START of the blend with `endRadius`; unused with
774
+ * `radii` (the table is the whole law — pass its first row's radius)
775
+ * @param options Optional VARIABLE blend, in one of two forms, each naming
776
+ * exactly ONE edge: the law belongs to the whole tangent chain that edge
777
+ * lies on, and it is measured from that edge's start. `endRadius` —
778
+ * the radius at the far end, in internal length units, interpolated
779
+ * linearly along the chain; on a closed rim it must equal `radius` (0 and
780
+ * 1 are the same point) — use `radii` there. `radii` — a sampled law instead:
781
+ * `position` is normalised ARC LENGTH along the chain (a ratio 0..1, never
782
+ * converted), `radius` is internal length units; 2..32 rows with strictly
783
+ * increasing `position`; before the first row and after the last the
784
+ * radius is held constant. On a closed rim 0 and 1 are the same point, so
785
+ * give them the same radius and put the variation in the interior rows. The two
786
+ * forms are mutually exclusive; without either, the fillet has one constant
787
+ * radius.
788
+ * @returns The filleted solid as a new {@linkcode BrepHandle}
789
+ * @throws VALIDATION if `edges` is empty or holds a duplicate, `radius` is
790
+ * not a positive finite number, an edge is not on `brep`, or an edge has no
791
+ * crease to round — it is a seam of a closed face, or it lies between two
792
+ * patches of one smooth surface; for a variable blend, if `endRadius` and
793
+ * `radii` are both given, more than one edge is passed, a `radii` table is
794
+ * outside 2..32 rows or its positions do not strictly increase within
795
+ * 0..1, or the law's two ends differ on a closed rim (an `endRadius`
796
+ * other than `radius`, or a `radii` table whose first and last radii
797
+ * differ)
798
+ * @throws PRECONDITION_FAILED if an edge could not be located on the kernel
799
+ * solid after healing (select a different edge, or fillet before the
800
+ * operation that curved the solid), or the kernel could not blend the edge
801
+ * named in the message (reduce the radius or drop that edge)
802
+ * @throws OPERATION_FAILED if the kernel or the translation fails, or the
803
+ * result is non-manifold, splits into several solids, or contains a surface
804
+ * Snaptrude cannot represent
805
+ *
806
+ * @examplePrompt Round the rim of the column with 200 mm
807
+ * @examplePrompt Soften where the dome meets the wall
808
+ * @examplePrompt Fillet the corners of the podium with a 0.5m radius
809
+ * @examplePrompt Soften the vertical edges of this tower
810
+ * @examplePrompt Blend this edge from 200mm at one end to 600mm at the other
811
+ * @examplePrompt Round this rim wider at the back than at the front
812
+ *
813
+ * # Example
814
+ * ```ts
815
+ * // Rim = every edge between the cylinder side and a plane cap
816
+ * const edges = await snaptrude.core.geom.query.brep.listEdges(column)
817
+ * const rim = []
818
+ * for (const edge of edges) {
819
+ * const faces = await snaptrude.core.geom.query.edge.listFaces(edge)
820
+ * const kinds = await Promise.all(
821
+ * faces.map((f) => snaptrude.core.geom.query.face.getSurfaceKind(f)),
822
+ * )
823
+ * if (kinds.includes("cylinder") && kinds.includes("plane")) rim.push(edge)
824
+ * }
825
+ * const rounded = await snaptrude.core.geom.create.brepFromFillet(column, rim, 0.787) // 200 mm
826
+ * ```
827
+ */
828
+ public abstract brepFromFillet(
829
+ brep: BrepHandle,
830
+ edges: EdgeHandle[],
831
+ radius: number,
832
+ options?: { endRadius?: number; radii?: { position: number; radius: number }[] },
833
+ ): PluginApiReturn<BrepHandle>
834
+
835
+ /**
836
+ * Grows or shrinks a solid by offsetting every face — positive distance
837
+ * moves faces outward, negative moves them inward. Host API call — returns
838
+ * a new {@linkcode BrepHandle}; the input brep is read-only.
839
+ *
840
+ * Works on curved solids: a sphere offsets to a sphere, a torus to a torus,
841
+ * a cylinder to a cylinder. There is nothing to select — the whole solid is
842
+ * offset.
843
+ *
844
+ * When the input brep carries a creation recipe, the result carries it too,
845
+ * with this operation appended to `operations`; `design.create.massFromBrep`
846
+ * then keeps the mass editable through `design.update.parameters`.
847
+ *
848
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of
849
+ * tens of MB — expect a pause of seconds).
850
+ *
851
+ * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
852
+ * with `design.create.massFromBrep`.
853
+ *
854
+ * @param brep The solid to offset
855
+ * @param distance Offset distance (finite, non-zero; positive grows, negative shrinks)
856
+ * @returns The offset solid as a new {@linkcode BrepHandle}
857
+ * @throws VALIDATION if `distance` is not finite, is too small to offset
858
+ * anything, or an inward distance consumes the solid entirely
859
+ * @throws OPERATION_FAILED if the kernel or the translation fails, or the
860
+ * result is non-manifold, splits into several solids, or contains a surface
861
+ * Snaptrude cannot represent (an offset free-form NURBS surface may have no
862
+ * exact representation — the model is left unchanged). A distance too
863
+ * large for the solid is reported here, with a hint asking for a smaller
864
+ * one. There is no per-face refusal: the whole call fails, and the error
865
+ * names no face
866
+ *
867
+ * @examplePrompt Thicken the torus ring by 100 mm
868
+ * @examplePrompt Grow this solid by 0.5m in every direction
869
+ * @examplePrompt Shrink this mass by 200mm
870
+ * @examplePrompt Offset the building envelope outward by 1m
871
+ *
872
+ * # Example
873
+ * ```ts
874
+ * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)
875
+ * const grown = await snaptrude.core.geom.create.brepFromOffset(brep, 0.5)
876
+ * ```
877
+ */
878
+ public abstract brepFromOffset(brep: BrepHandle, distance: number): PluginApiReturn<BrepHandle>
879
+
880
+ /**
881
+ * Hollows a solid into constant-thickness walls, removing the given faces
882
+ * as openings. Host API call — returns a new {@linkcode BrepHandle}; the
883
+ * input brep is read-only. The outer surface is kept and the walls grow
884
+ * inward.
885
+ *
886
+ * Works on curved solids: the open faces may be spherical, cylindrical,
887
+ * conical, toroidal or free-form patches, not only flat ones. To open the
888
+ * flat base of a dome, check every face first —
889
+ * `core.geom.query.brep.listFaces` then
890
+ * `core.geom.query.face.getSurfaceKind` on each — and keep the `"plane"`
891
+ * one. A closed curved surface is stored as several patches (a sphere as
892
+ * lunes), so opening "the sphere" may mean passing several faces.
893
+ *
894
+ * When the input brep carries a creation recipe, the result carries it too,
895
+ * with this operation appended to `operations`; `design.create.massFromBrep`
896
+ * then keeps the mass editable through `design.update.parameters`.
897
+ *
898
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of
899
+ * tens of MB — expect a pause of seconds).
900
+ *
901
+ * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
902
+ * with `design.create.massFromBrep`.
903
+ *
904
+ * @param brep The solid to hollow
905
+ * @param openFaces The faces to remove as openings (one or more, all on
906
+ * `brep`, no duplicates; more than 32 still shells but the result carries
907
+ * no parameter record)
908
+ * @param thickness Wall thickness (positive, finite, smaller than half the solid's smallest span)
909
+ * @returns The hollowed solid as a new {@linkcode BrepHandle}
910
+ * @throws VALIDATION if `openFaces` is empty or holds a duplicate, a face is
911
+ * not on `brep`, `thickness` is not a positive finite number, or the
912
+ * thickness is too large (it must be smaller than half the solid's smallest
913
+ * span)
914
+ * @throws OPERATION_FAILED if the kernel or the translation fails, or the
915
+ * result is non-manifold, splits into several solids, or contains a surface
916
+ * Snaptrude cannot represent. A thickness too large for the solid is
917
+ * reported here, with a hint asking for one below half the smallest span.
918
+ * There is no per-face refusal: the whole call fails, and the error
919
+ * names no face
920
+ *
921
+ * @examplePrompt Hollow the dome into a 300 mm shell, open at the base
922
+ * @examplePrompt Hollow this mass into 200mm walls
923
+ * @examplePrompt Shell this form with the top face open
924
+ * @examplePrompt Turn this solid tower into a tube open at both ends
925
+ *
926
+ * # Example
927
+ * ```ts
928
+ * // Open the dome at its flat base: the one face whose surface is a plane
929
+ * const faces = await snaptrude.core.geom.query.brep.listFaces(dome)
930
+ * const base = []
931
+ * for (const face of faces) {
932
+ * if ((await snaptrude.core.geom.query.face.getSurfaceKind(face)) === "plane") base.push(face)
933
+ * }
934
+ * const hollow = await snaptrude.core.geom.create.brepFromShell(dome, base, 1.181) // 300 mm
935
+ * ```
936
+ */
937
+ public abstract brepFromShell(
938
+ brep: BrepHandle,
939
+ openFaces: FaceHandle[],
940
+ thickness: number,
941
+ ): PluginApiReturn<BrepHandle>
942
+
943
+ /**
944
+ * Cuts a solid by an infinite plane and returns one brep per resulting
945
+ * piece — a single piece if the plane misses the solid. Host API call —
946
+ * returns new {@linkcode BrepHandle}s; the input brep is read-only. The
947
+ * plane is defined by a point on it and its normal direction.
948
+ *
949
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of
950
+ * tens of MB — expect a pause of seconds).
951
+ *
952
+ * Inspect each result via `core.geom.query.brep.*`, or commit them to the
953
+ * scene with `design.create.massFromBrep`.
954
+ *
955
+ * @param brep The solid to split
956
+ * @param planeOrigin A point on the cutting plane as plain `{x, y, z}` components
957
+ * @param planeNormal The plane normal as plain `{x, y, z}` components (non-zero)
958
+ * @returns The resulting pieces as {@linkcode BrepHandle}`[]` (one per solid)
959
+ * @throws VALIDATION if `planeNormal` is zero-length or the origin/normal
960
+ * components are not finite
961
+ * @throws OPERATION_FAILED if the kernel cannot split the solid
962
+ *
963
+ * @examplePrompt Cut this building at 12m height
964
+ * @examplePrompt Split the tower from the podium with a horizontal plane
965
+ * @examplePrompt Slice this mass along a vertical plane
966
+ *
967
+ * # Example
968
+ * ```ts
969
+ * const pieces = await snaptrude.core.geom.create.brepsFromSplit(
970
+ * towerBrep,
971
+ * { x: 0, y: 12, z: 0 },
972
+ * { x: 0, y: 1, z: 0 },
973
+ * )
974
+ * console.log("pieces:", pieces.length)
975
+ * ```
976
+ */
977
+ public abstract brepsFromSplit(
978
+ brep: BrepHandle,
979
+ planeOrigin: Vec3Components,
980
+ planeNormal: Vec3Components,
981
+ ): PluginApiReturn<BrepHandle[]>
982
+
983
+ /**
984
+ * Cuts a solid with a FACE of another solid — the curved twin of
985
+ * {@linkcode PluginGeomCreateApi.brepsFromSplit}, which cuts with a plane.
986
+ * Host API call — returns new {@linkcode BrepHandle}s; both inputs are
987
+ * read-only. BOTH breps must be in the same frame: read scene masses with
988
+ * `design.query.geometry.getBrep(x, { frame: "world" })` (the default is each
989
+ * mass's own local frame, and two masses have two). The pieces come back in
990
+ * DESCENDING volume order; pick the one you want with
991
+ * `core.geom.query.brep.getCentroid` or `getVolume` and commit it. A piece
992
+ * carries no recipe — label it.
993
+ *
994
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
995
+ * of MB — expect a pause of seconds).
996
+ *
997
+ * @param brep The solid to cut
998
+ * @param toolFace The cutting face, read-only
999
+ * @param toolBrep The solid `toolFace` belongs to — a face handle cannot name
1000
+ * its own solid
1001
+ * @returns The resulting pieces as {@linkcode BrepHandle}`[]`, largest first
1002
+ * @throws VALIDATION if `toolFace` is not a face of `toolBrep`, `toolBrep` IS
1003
+ * `brep`, or the cut produced a single part — the surface does not pass all
1004
+ * the way through (extend it past the mass on every side, and check both
1005
+ * breps were read in the same frame)
1006
+ * @throws OPERATION_FAILED if the kernel could not split the solid
1007
+ *
1008
+ * @examplePrompt Cut this mass with the curved roof surface and keep the lower part
1009
+ *
1010
+ * # Example
1011
+ * ```ts
1012
+ * const target = await snaptrude.design.query.geometry.getBrep(tower, { frame: "world" })
1013
+ * const tool = await snaptrude.design.query.geometry.getBrep(roof, { frame: "world" })
1014
+ * const [toolFace] = await snaptrude.core.geom.query.brep.listFaces(tool)
1015
+ * const pieces = await snaptrude.core.geom.create.brepsFromSplitByFace(target, toolFace, tool)
1016
+ * ```
1017
+ */
1018
+ public abstract brepsFromSplitByFace(
1019
+ brep: BrepHandle,
1020
+ toolFace: FaceHandle,
1021
+ toolBrep: BrepHandle,
1022
+ ): PluginApiReturn<BrepHandle[]>
1023
+
1024
+ /**
1025
+ * Bevels the given edges of a solid with a symmetric planar cut. Host API
1026
+ * call — returns a new {@linkcode BrepHandle}; the input brep is read-only.
1027
+ *
1028
+ * Works on curved solids: edges may be straight, circular or spline, and
1029
+ * adjacent edges are allowed. Pick edges the same way as for
1030
+ * {@linkcode PluginGeomCreateApi.brepFromFillet} — `brep.listEdges`, then
1031
+ * `core.geom.query.edge.listFaces` and `core.geom.query.face.getSurfaceKind`
1032
+ * on both faces of each edge. (`core.geom.query.brep.listEdgesBetween` takes
1033
+ * two VERTEX indices — it is NOT a face-pair lookup.) Chamfering a cylinder's
1034
+ * rim yields a cone patch where a fillet would yield a torus patch.
1035
+ *
1036
+ * When the input brep carries a creation recipe, the result carries it too,
1037
+ * with this operation appended to `operations`; `design.create.massFromBrep`
1038
+ * then keeps the mass editable through `design.update.parameters`.
1039
+ *
1040
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
1041
+ * of MB — expect a pause of seconds).
1042
+ *
1043
+ * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
1044
+ * with `design.create.massFromBrep`.
1045
+ *
1046
+ * @param brep The solid whose edges to bevel
1047
+ * @param edges The edges to chamfer (one or more, all on `brep`, no
1048
+ * duplicates; more than 64 still chamfers but the result carries no
1049
+ * parameter record)
1050
+ * @param distance Chamfer distance from the edge on each adjacent face (positive, finite)
1051
+ * @returns The chamfered solid as a new {@linkcode BrepHandle}
1052
+ * @throws VALIDATION if `edges` is empty or holds a duplicate, `distance` is
1053
+ * not a positive finite number, an edge is not on `brep`, or an edge has no
1054
+ * crease to cut — it is a seam of a closed face, or it lies between two
1055
+ * patches of one smooth surface
1056
+ * @throws PRECONDITION_FAILED if an edge could not be located on the kernel
1057
+ * solid after healing (select a different edge, or chamfer before the
1058
+ * operation that curved the solid)
1059
+ * @throws OPERATION_FAILED if the kernel or the translation fails, or the
1060
+ * result is non-manifold, splits into several solids, or contains a surface
1061
+ * Snaptrude cannot represent. A distance larger than an adjacent face
1062
+ * is reported here, with a hint asking for a smaller one. Unlike
1063
+ * {@linkcode PluginGeomCreateApi.brepFromFillet}, the kernel does not say
1064
+ * which edge failed a chamfer, so the error names no edge
1065
+ *
1066
+ * @examplePrompt Chamfer the top edge of the cylinder
1067
+ * @examplePrompt Bevel these edges by 100mm
1068
+ * @examplePrompt Chamfer the top edges of the plinth
1069
+ * @examplePrompt Cut a 45-degree flat along the corners of this mass
1070
+ *
1071
+ * # Example
1072
+ * ```ts
1073
+ * const brep = await snaptrude.core.geom.create.brepFromExtrusion(contour, { x: 0, y: 1, z: 0 }, 3)
1074
+ * const edges = await snaptrude.core.geom.query.brep.listEdges(brep)
1075
+ * const beveled = await snaptrude.core.geom.create.brepFromChamfer(brep, [edges[0]], 0.1)
1076
+ * ```
1077
+ */
1078
+ public abstract brepFromChamfer(
1079
+ brep: BrepHandle,
1080
+ edges: EdgeHandle[],
1081
+ distance: number,
1082
+ ): PluginApiReturn<BrepHandle>
1083
+
1084
+ /**
1085
+ * Sweeps a planar profile along a polyline path, with mitred corners at
1086
+ * each bend. Host API call — returns a new {@linkcode BrepHandle}; the
1087
+ * input contour is read-only. The profile must be hole-free and must not
1088
+ * lie in a plane containing the first path segment's direction. Repeating
1089
+ * the first point as the last closes the path into a ring solid (a picture
1090
+ * frame): closed paths must be flat and straight-segmented and take a
1091
+ * straight-edged (arc-free) profile — the ring's cross-section is the
1092
+ * authored profile projected along the first leg onto the first mitre
1093
+ * plane, so a profile not perpendicular to the first leg appears sheared.
1094
+ * An optional scale law tapers the sweep: `startScale`/`endScale`
1095
+ * interpolate linearly by arc length, or `scales` gives one factor per path
1096
+ * point (scaling is about the profile centroid). A uniform scale (all
1097
+ * factors equal) works on any path today's sweep accepts; a varying scale
1098
+ * takes straight (collinear) open paths with straight-edged profiles —
1099
+ * tapering across a bend or along an arc edge would create faces Snaptrude
1100
+ * cannot represent and throws (sweep each straight run separately and union
1101
+ * the results instead). An optional `transition` picks the corner
1102
+ * treatment: `"miter"` (default) is a sharp mitred bend; `"round"`
1103
+ * carries the section around each turning corner on a tangent circular arc,
1104
+ * so the corner patch is a real curved surface (geometrically cylindrical;
1105
+ * `getSurfaceKind` reports it as `"spline"`) rather than a
1106
+ * flat cut (open paths only — `"round"` on a closed ring path is refused);
1107
+ * `{ bevel: b }` chamfers each turning corner by cutting `b` back along
1108
+ * both adjacent legs (works on open and closed paths — each leg must be
1109
+ * long enough for its cuts). On a corner-less path `transition` has no
1110
+ * effect, and a varying scale only accepts straight paths, so it never meets
1111
+ * a corner.
1112
+ *
1113
+ * Open paths run on the OpenCascade kernel (the first kernel call loads a
1114
+ * wasm of tens of MB — expect a pause of seconds); closed paths are
1115
+ * computed host-side and never load the kernel.
1116
+ *
1117
+ * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
1118
+ * with `design.create.massFromBrep`.
1119
+ *
1120
+ * @param profile The cross-section to sweep (hole-free contour)
1121
+ * @param path The polyline path as plain `{x, y, z}` points (2 or more;
1122
+ * repeat the first point as the last to close the path into a ring; more
1123
+ * than 512 points still sweeps but the result carries no parameter record)
1124
+ * @param options Optional scale law and corner transition. Scale law:
1125
+ * `startScale`/`endScale` (linear taper by arc length) or `scales` (one
1126
+ * factor per path point; mutually exclusive with
1127
+ * `startScale`/`endScale`); every factor must be finite and within
1128
+ * 1e-3..1e3. `transition`: `"miter"` (default, sharp corners),
1129
+ * `"round"` (arc-carried corners — curved corner patches; open paths
1130
+ * only) or `{ bevel: b }` (chamfered corners, `b` finite within
1131
+ * 1e-3..1e3; open and closed paths)
1132
+ * @returns The swept solid as a new {@linkcode BrepHandle}
1133
+ * @throws VALIDATION if the profile has holes, the path has fewer than 2
1134
+ * points, consecutive path points coincide, a
1135
+ * coordinate is not finite, or the profile plane contains the first
1136
+ * path segment's direction
1137
+ * (degenerate sweep); for closed paths, if the ring has fewer than 3
1138
+ * distinct corners, the path is non-planar, a corner doubles back, the
1139
+ * profile has an arc edge, or the profile is too large for a mitred
1140
+ * corner; for scale options, if a factor is outside 1e-3..1e3, `scales`
1141
+ * is combined with `startScale`/`endScale`, `scales` does not match the
1142
+ * path length, or a varying scale is used on a bent or closed path or
1143
+ * with an arc-edged profile; for `{ bevel }`, if a leg is too short for
1144
+ * the cuts its bevelled corners take from it or a corner nearly doubles
1145
+ * back so its bevel collapses (the errors name the corner)
1146
+ * @throws PRECONDITION_FAILED if `transition` is `"round"` and the path is
1147
+ * closed — round corners work on open paths only; use `"miter"` or
1148
+ * `{ bevel }` on a ring
1149
+ * @throws OPERATION_FAILED if the kernel cannot sweep the profile into a
1150
+ * valid solid — including a `"round"` corner whose arc does not fit its
1151
+ * legs, which is not pre-checked and so is not named per corner
1152
+ *
1153
+ * @examplePrompt Sweep this profile along the corridor path
1154
+ * @examplePrompt Extrude the railing section along this route
1155
+ * @examplePrompt Run a duct profile through these points
1156
+ * @examplePrompt Sweep a frame profile around a closed rectangular loop
1157
+ * @examplePrompt Taper this duct from 1x1 to 2x2 along its run
1158
+ * @examplePrompt Sweep a column section that narrows toward the top
1159
+ * @examplePrompt Chamfer the corners of this swept frame by 200mm
1160
+ * @examplePrompt Sweep this handrail with rounded corners at the bends
1161
+ *
1162
+ * # Example
1163
+ * ```ts
1164
+ * const rect = await snaptrude.core.geom.create.profileRect(0.4, 0.4)
1165
+ * const profile = await snaptrude.core.geom.create.contourFromProfile(rect)
1166
+ * // profileRect lies in the XZ plane, so the path must START out of that
1167
+ * // plane (here: straight up), then it can run horizontally.
1168
+ * const duct = await snaptrude.core.geom.create.brepFromSweep(profile, [
1169
+ * { x: 0, y: 0, z: 0 },
1170
+ * { x: 0, y: 2.8, z: 0 },
1171
+ * { x: 10, y: 2.8, z: 0 },
1172
+ * ])
1173
+ * // A closed ring (picture frame): repeat the first point as the last
1174
+ * const frame = await snaptrude.core.geom.create.brepFromSweep(profile, [
1175
+ * { x: 0, y: 0, z: 0 },
1176
+ * { x: 0, y: 3, z: 0 },
1177
+ * { x: 4, y: 3, z: 0 },
1178
+ * { x: 4, y: 0, z: 0 },
1179
+ * { x: 0, y: 0, z: 0 },
1180
+ * ])
1181
+ * // A tapered straight run: 0.4x0.4 at the start, 0.8x0.8 at the end
1182
+ * const flue = await snaptrude.core.geom.create.brepFromSweep(
1183
+ * profile,
1184
+ * [
1185
+ * { x: 0, y: 0, z: 0 },
1186
+ * { x: 0, y: 10, z: 0 },
1187
+ * ],
1188
+ * { startScale: 1, endScale: 2 },
1189
+ * )
1190
+ * ```
1191
+ */
1192
+ public abstract brepFromSweep(
1193
+ profile: ContourHandle,
1194
+ path: Vec3Components[],
1195
+ options?: {
1196
+ startScale?: number
1197
+ endScale?: number
1198
+ scales?: number[]
1199
+ transition?: "miter" | "round" | { bevel: number }
1200
+ },
1201
+ ): PluginApiReturn<BrepHandle>
1202
+
1203
+ /**
1204
+ * Sweeps a cross-section along a SMOOTH path — splines, arcs and lines chained
1205
+ * end to end — with control of how the profile turns.
1206
+ * {@linkcode PluginGeomCreateApi.brepFromSweep} takes a polyline path; this
1207
+ * takes curves. Host API call — returns a new {@linkcode BrepHandle}; the
1208
+ * inputs are read-only. Records as `sweepAlongCurve`, so the orientation stays
1209
+ * editable through `design.update.parameters`. The properties panel shows it
1210
+ * read-only, because it is a text value, not a number. A railed sweep records
1211
+ * nothing (`getParameters` answers `null`).
1212
+ *
1213
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
1214
+ * of MB — expect a pause of seconds).
1215
+ *
1216
+ * @param profile Hole-free cross-section contour; placed at the first curve's start
1217
+ * @param path 1..64 curves, ordered, meeting end to end within tolerance
1218
+ * @param options `orientation` — `"upright"` (default) keeps the profile's up
1219
+ * aligned with world up (ducts, handrails, ribbons that must not roll);
1220
+ * `"frenet"` lets it roll with the path's curvature; `"fixed"` carries the
1221
+ * authored frame along unchanged, which is a pure translation — use it to
1222
+ * bend a footprint along an arc. `rail` is a second guide curve.
1223
+ * @returns The swept solid as a new {@linkcode BrepHandle}
1224
+ * @throws VALIDATION if the path curves are not connected, the path is closed,
1225
+ * the profile has holes, the first tangent lies in the profile plane, the
1226
+ * path runs vertically under `"upright"` (that frame is undefined there —
1227
+ * the message names the run and points at `"frenet"`), the path turns
1228
+ * parallel to the profile under `"fixed"`, `rail` is closed, or `rail` and
1229
+ * `orientation` are both given
1230
+ * @throws OPERATION_FAILED if the kernel could not sweep the profile — a
1231
+ * self-intersection on a tight bend
1232
+ *
1233
+ * @examplePrompt Sweep a 600x400 duct along this curved route and keep it upright
1234
+ * @examplePrompt Run this handrail along the curved stair edge
1235
+ * @examplePrompt Bend this footprint along the arc
1236
+ *
1237
+ * # Example
1238
+ * ```ts
1239
+ * const route = await snaptrude.core.geom.create.splineFromPoints(points)
1240
+ * const duct = await snaptrude.core.geom.create.brepFromSweepAlongCurve(profile, [route], {
1241
+ * orientation: "upright",
1242
+ * })
1243
+ * ```
1244
+ */
1245
+ public abstract brepFromSweepAlongCurve(
1246
+ profile: ContourHandle,
1247
+ path: CurveHandle[],
1248
+ options?: SweepAlongCurveOptions,
1249
+ ): PluginApiReturn<BrepHandle>
1250
+
1251
+ /**
1252
+ * Revolves a planar profile about an axis to make a solid of revolution — a
1253
+ * full turn by default. Host API call — returns a new
1254
+ * {@linkcode BrepHandle}; the input contour is read-only. Holes in the
1255
+ * profile are allowed, and segments may run at any angle to the axis: an
1256
+ * inclined segment revolves into a cone, an arc into a sphere or torus
1257
+ * section. This is the general route to domes, vases, columns and rotundas.
1258
+ *
1259
+ * Two rules bind the axis to the profile. First, the axis line must lie IN
1260
+ * the profile's plane or run parallel to it — a revolution turns a planar
1261
+ * section about a line drawn in that same plane; a parallel axis offset out
1262
+ * of the plane is judged by its projection into the plane (the result is a
1263
+ * hollow ring). A skew axis is refused: move the axis into the profile's
1264
+ * plane. Second, within that plane the whole profile stays on ONE side of
1265
+ * the axis.
1266
+ * Vertices and whole segments may lie ON the axis — that is how a dome's
1267
+ * pole and a cylinder's inner edge are authored — but no part of the profile
1268
+ * may cross to the other side, which would sweep a self-intersecting solid.
1269
+ *
1270
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of
1271
+ * tens of MB — expect a pause of seconds).
1272
+ *
1273
+ * Inspect the result via `core.geom.query.brep.*`, or commit it to the scene
1274
+ * with `design.create.massFromBrep`.
1275
+ *
1276
+ * @param profile The cross-section to revolve (planar contour, holes allowed)
1277
+ * @param axisOrigin A point on the revolution axis as plain `{x, y, z}` components
1278
+ * @param axisDirection The axis direction as plain `{x, y, z}` components (non-zero)
1279
+ * @param angleInDegrees Optional revolution angle in degrees (0 < angle ≤ 360; default 360)
1280
+ * @returns The revolved solid as a new {@linkcode BrepHandle}
1281
+ * @throws VALIDATION if `axisDirection` is zero-length, `angleInDegrees` is
1282
+ * not in (0, 360], a coordinate is not finite, or the profile crosses the
1283
+ * axis (the message names the offending segment — keep the whole profile
1284
+ * on one side; points may touch the axis but not cross it)
1285
+ * @throws PRECONDITION_FAILED if the axis is skew to the profile's plane
1286
+ * (neither in it nor parallel to it; the message asks you to move the axis
1287
+ * into the plane of the profile)
1288
+ * @throws OPERATION_FAILED if the kernel cannot revolve the profile into a
1289
+ * valid solid
1290
+ *
1291
+ * @examplePrompt Create a dome from this section
1292
+ * @examplePrompt Revolve this profile 360 degrees around the vertical axis
1293
+ * @examplePrompt Build a rotunda by revolving this wall section
1294
+ * @examplePrompt Make a dome by revolving a quarter-circle about the vertical axis
1295
+ * @examplePrompt Turn this curved outline into a vase
1296
+ *
1297
+ * # Example
1298
+ * ```ts
1299
+ * // A cylinder: revolve a 2m-wide, 3m-tall rectangle about the Y axis at
1300
+ * // its edge — the left edge sits ON the axis, which is legal.
1301
+ * const rect = await snaptrude.core.geom.create.profileFromLinePoints([
1302
+ * await snaptrude.core.math.vec3.new(0, 0, 0),
1303
+ * await snaptrude.core.math.vec3.new(2, 0, 0),
1304
+ * await snaptrude.core.math.vec3.new(2, 3, 0),
1305
+ * await snaptrude.core.math.vec3.new(0, 3, 0),
1306
+ * ])
1307
+ * const profile = await snaptrude.core.geom.create.contourFromProfile(rect)
1308
+ * const cylinder = await snaptrude.core.geom.create.brepFromRevolution(
1309
+ * profile,
1310
+ * { x: 0, y: 0, z: 0 },
1311
+ * { x: 0, y: 1, z: 0 },
1312
+ * )
1313
+ * ```
1314
+ */
1315
+ public abstract brepFromRevolution(
1316
+ profile: ContourHandle,
1317
+ axisOrigin: Vec3Components,
1318
+ axisDirection: Vec3Components,
1319
+ angleInDegrees?: number,
1320
+ ): PluginApiReturn<BrepHandle>
1321
+
1322
+ /**
1323
+ * Create a solid **sphere** B-rep (boundary representation: the solid's
1324
+ * exact faces, edges and vertices) from a centre and radius. Host API call
1325
+ * — returns a {@linkcode BrepHandle}; commit it to the scene with
1326
+ * `design.create.massFromBrep`. The result carries an exact spherical
1327
+ * surface (not a faceted approximation) and a creation recipe — read the
1328
+ * committed mass's parameters back with `design.query.getParameters` and edit
1329
+ * them with `design.update.parameters`.
1330
+ *
1331
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
1332
+ * of MB — expect a pause of seconds).
1333
+ *
1334
+ * @param centre Sphere centre as plain `{x, y, z}` components (internal length units)
1335
+ * @param radius Sphere radius (> 0, internal length units)
1336
+ * @returns The new solid as a {@linkcode BrepHandle}
1337
+ * @throws VALIDATION if `radius` is not a positive finite number or a
1338
+ * coordinate is not finite
1339
+ *
1340
+ * @examplePrompt Create a sphere of radius 5 at the origin
1341
+ * @examplePrompt Add a 3m ball on top of the tower
1342
+ *
1343
+ * # Example
1344
+ * ```ts
1345
+ * const brep = await snaptrude.core.geom.create.brepFromSphere({ x: 0, y: 5, z: 0 }, 5)
1346
+ * const mass = await snaptrude.design.create.massFromBrep(brep, "Sphere")
1347
+ * ```
1348
+ */
1349
+ public abstract brepFromSphere(
1350
+ centre: Vec3Components,
1351
+ radius: number,
1352
+ ): PluginApiReturn<BrepHandle>
1353
+
1354
+ /**
1355
+ * Create a solid **cylinder** B-rep from its bottom-cap centre, axis
1356
+ * direction, radius and height. Host API call — returns a
1357
+ * {@linkcode BrepHandle}; commit with `design.create.massFromBrep`. The
1358
+ * bottom cap sits AT `base` and the solid extends `height` along `axis` —
1359
+ * place `base` on the floor and use axis `{x: 0, y: 1, z: 0}` for an upright
1360
+ * column. Carries a creation recipe (radius, height), editable through
1361
+ * `design.update.parameters`.
1362
+ *
1363
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
1364
+ * of MB — expect a pause of seconds).
1365
+ *
1366
+ * @param base Centre of the bottom cap as plain `{x, y, z}` components
1367
+ * @param axis Axis direction as plain `{x, y, z}` components (non-zero, normalised by the host)
1368
+ * @param radius Cylinder radius (> 0)
1369
+ * @param height Cylinder height along `axis` (> 0)
1370
+ * @returns The new solid as a {@linkcode BrepHandle}
1371
+ * @throws VALIDATION if `radius`/`height` are not positive finite numbers or
1372
+ * `axis` is zero-length
1373
+ *
1374
+ * @examplePrompt Create a cylinder 2m across and 4m tall
1375
+ * @examplePrompt Add a circular column here
1376
+ *
1377
+ * # Example
1378
+ * ```ts
1379
+ * const brep = await snaptrude.core.geom.create.brepFromCylinder(
1380
+ * { x: 0, y: 0, z: 0 },
1381
+ * { x: 0, y: 1, z: 0 },
1382
+ * 1,
1383
+ * 4,
1384
+ * )
1385
+ * const column = await snaptrude.design.create.massFromBrep(brep, "Column")
1386
+ * ```
1387
+ */
1388
+ public abstract brepFromCylinder(
1389
+ base: Vec3Components,
1390
+ axis: Vec3Components,
1391
+ radius: number,
1392
+ height: number,
1393
+ ): PluginApiReturn<BrepHandle>
1394
+
1395
+ /**
1396
+ * Create a solid **cone or frustum** B-rep: bottom-cap centre, axis, base
1397
+ * radius, height, and an optional top radius (default 0 — a full cone to a
1398
+ * point; > 0 — a frustum / tapered column). Host API call — returns a
1399
+ * {@linkcode BrepHandle}; commit with `design.create.massFromBrep`. Carries
1400
+ * a creation recipe (radii + height), editable through `design.update.parameters`.
1401
+ *
1402
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
1403
+ * of MB — expect a pause of seconds).
1404
+ *
1405
+ * @param base Centre of the bottom cap as plain `{x, y, z}` components
1406
+ * @param axis Axis direction as plain `{x, y, z}` components (non-zero, normalised by the host)
1407
+ * @param baseRadius Radius at the base (> 0)
1408
+ * @param height Height along `axis` (> 0)
1409
+ * @param topRadius Optional radius at the top (≥ 0, ≠ `baseRadius`; default 0 = apex)
1410
+ * @returns The new solid as a {@linkcode BrepHandle}
1411
+ * @throws VALIDATION if radii/height are invalid, `topRadius === baseRadius`
1412
+ * (that is a cylinder — use {@linkcode PluginGeomCreateApi.brepFromCylinder}),
1413
+ * or `axis` is zero-length
1414
+ *
1415
+ * @examplePrompt Create a cone roof 4m wide and 3m tall
1416
+ * @examplePrompt Make a tapered column narrowing from 1m to 0.6m
1417
+ *
1418
+ * # Example
1419
+ * ```ts
1420
+ * const roof = await snaptrude.core.geom.create.brepFromCone(
1421
+ * { x: 0, y: 9, z: 0 },
1422
+ * { x: 0, y: 1, z: 0 },
1423
+ * 2,
1424
+ * 3,
1425
+ * )
1426
+ * ```
1427
+ */
1428
+ public abstract brepFromCone(
1429
+ base: Vec3Components,
1430
+ axis: Vec3Components,
1431
+ baseRadius: number,
1432
+ height: number,
1433
+ topRadius?: number,
1434
+ ): PluginApiReturn<BrepHandle>
1435
+
1436
+ /**
1437
+ * Create a solid **torus** (ring / donut) B-rep from its centre, plane
1438
+ * normal, ring radius and tube radius. Host API call — returns a
1439
+ * {@linkcode BrepHandle}; commit with `design.create.massFromBrep`.
1440
+ * `majorRadius` is centre-to-tube-centre; `minorRadius` is the tube's own
1441
+ * radius and must be strictly smaller. Carries a creation recipe, editable
1442
+ * through `design.update.parameters`.
1443
+ *
1444
+ * Runs on the OpenCascade kernel (the first kernel call loads a wasm of tens
1445
+ * of MB — expect a pause of seconds).
1446
+ *
1447
+ * @param centre Torus centre as plain `{x, y, z}` components
1448
+ * @param axis Plane normal of the ring as plain `{x, y, z}` components (non-zero)
1449
+ * @param majorRadius Ring radius, centre to tube centre (> 0)
1450
+ * @param minorRadius Tube radius (> 0 and < `majorRadius` — a self-intersecting torus is rejected)
1451
+ * @returns The new solid as a {@linkcode BrepHandle}
1452
+ * @throws VALIDATION if a radius is invalid, `minorRadius >= majorRadius`,
1453
+ * or `axis` is zero-length
1454
+ *
1455
+ * @examplePrompt Create a donut-shaped ring 10m across with a 1m tube
1456
+ * @examplePrompt Add a torus canopy around the tower
1457
+ *
1458
+ * # Example
1459
+ * ```ts
1460
+ * const ring = await snaptrude.core.geom.create.brepFromTorus(
1461
+ * { x: 0, y: 6, z: 0 },
1462
+ * { x: 0, y: 1, z: 0 },
1463
+ * 5,
1464
+ * 1,
1465
+ * )
1466
+ * ```
1467
+ */
1468
+ public abstract brepFromTorus(
1469
+ centre: Vec3Components,
1470
+ axis: Vec3Components,
1471
+ majorRadius: number,
1472
+ minorRadius: number,
1473
+ ): PluginApiReturn<BrepHandle>
1474
+ }
1475
+
1476
+ /**
1477
+ * Arguments for {@linkcode PluginGeomCreateApi.line}.
1478
+ *
1479
+ * | Property | Type | Description |
1480
+ * |---|---|---|
1481
+ * | `startPoint` | {@linkcode Vec3Handle} | Start point of the line segment |
1482
+ * | `endPoint` | {@linkcode Vec3Handle} | End point of the line segment |
1483
+ */
1484
+ export const PluginGeomCreateLineArgs = z.object({
1485
+ startPoint: Vec3Handle,
1486
+ endPoint: Vec3Handle,
1487
+ })
1488
+
1489
+ export type PluginGeomCreateLineArgs = z.infer<typeof PluginGeomCreateLineArgs>
1490
+
1491
+ /**
1492
+ * Arguments for {@linkcode PluginGeomCreateApi.arc}.
1493
+ *
1494
+ * | Property | Type | Description |
1495
+ * |---|---|---|
1496
+ * | `startPoint` | {@linkcode Vec3Handle} | Start point of the arc |
1497
+ * | `endPoint` | {@linkcode Vec3Handle} | End point of the arc |
1498
+ * | `centrePoint` | {@linkcode Vec3Handle} | Centre point of the arc |
1499
+ * | `axis` | {@linkcode Vec3Handle} | Axis direction of the arc |
1500
+ */
1501
+ export const PluginGeomCreateArcArgs = z.object({
1502
+ startPoint: Vec3Handle,
1503
+ endPoint: Vec3Handle,
1504
+ centrePoint: Vec3Handle,
1505
+ axis: Vec3Handle,
1506
+ })
1507
+
1508
+ export type PluginGeomCreateArcArgs = z.infer<typeof PluginGeomCreateArcArgs>
1509
+
1510
+ /**
1511
+ * Arguments for {@linkcode PluginGeomCreateApi.circle}.
1512
+ *
1513
+ * | Property | Type | Description |
1514
+ * |---|---|---|
1515
+ * | `centrePoint` | {@linkcode Vec3Handle} | Centre point of the circle |
1516
+ * | `axis` | {@linkcode Vec3Handle} | Axis direction (plane normal) of the circle |
1517
+ * | `radius` | `number` | Radius of the circle |
1518
+ */
1519
+ export const PluginGeomCreateCircleArgs = z.object({
1520
+ centrePoint: Vec3Handle,
1521
+ axis: Vec3Handle,
1522
+ radius: z.number(),
1523
+ })
1524
+
1525
+ export type PluginGeomCreateCircleArgs = z.infer<typeof PluginGeomCreateCircleArgs>
1526
+
1527
+ /**
1528
+ * Arguments for {@linkcode PluginGeomCreateApi.splineFromPoints}.
1529
+ *
1530
+ * | Property | Type | Description |
1531
+ * |---|---|---|
1532
+ * | `points` | {@linkcode Vec3Handle}`[]` | 3..256 ordered through-points of the curve |
1533
+ */
1534
+ export const PluginGeomCreateSplineFromPointsArgs = z.object({
1535
+ points: z.array(Vec3Handle).min(3).max(MAX_SPLINE_THROUGH_POINTS),
1536
+ })
1537
+
1538
+ export type PluginGeomCreateSplineFromPointsArgs = z.infer<
1539
+ typeof PluginGeomCreateSplineFromPointsArgs
1540
+ >
1541
+
1542
+ /**
1543
+ * Arguments for {@linkcode PluginGeomCreateApi.splineFromControlPoints}.
1544
+ *
1545
+ * | Property | Type | Description |
1546
+ * |---|---|---|
1547
+ * | `controlPoints` | {@linkcode Vec3Handle}`[]` | 3..512 control points of the curve, in order |
1548
+ * | `options` | `object`? | `degree`: 2 or 3 (default `min(3, controlPoints.length - 1)`) |
1549
+ */
1550
+ export const PluginGeomCreateSplineFromControlPointsArgs = z.object({
1551
+ controlPoints: z.array(Vec3Handle).min(3).max(MAX_SPLINE_POLES),
1552
+ options: z.object({ degree: z.number().int().min(2).max(3).optional() }).optional(),
1553
+ })
1554
+
1555
+ export type PluginGeomCreateSplineFromControlPointsArgs = z.infer<
1556
+ typeof PluginGeomCreateSplineFromControlPointsArgs
1557
+ >
1558
+
1559
+ /**
1560
+ * Arguments for {@linkcode PluginGeomCreateApi.profileFromLinePoints}.
1561
+ *
1562
+ * | Property | Type | Description |
1563
+ * |---|---|---|
1564
+ * | `points` | {@linkcode Vec3Handle}`[]` | Ordered vertex handles of the profile |
1565
+ */
1566
+ export const PluginGeomCreateProfileFromLinePointsArgs = z.object({
1567
+ points: z.array(Vec3Handle),
1568
+ })
1569
+
1570
+ export type PluginGeomCreateProfileFromLinePointsArgs = z.infer<
1571
+ typeof PluginGeomCreateProfileFromLinePointsArgs
1572
+ >
1573
+
1574
+ /**
1575
+ * Arguments for {@linkcode PluginGeomCreateApi.profileFromCurves}.
1576
+ *
1577
+ * | Property | Type | Description |
1578
+ * |---|---|---|
1579
+ * | `curves` | {@linkcode CurveHandle}`[]` | Ordered curve handles forming a closed loop |
1580
+ */
1581
+ export const PluginGeomCreateProfileFromCurvesArgs = z.object({
1582
+ curves: z.array(CurveHandle),
1583
+ })
1584
+
1585
+ export type PluginGeomCreateProfileFromCurvesArgs = z.infer<
1586
+ typeof PluginGeomCreateProfileFromCurvesArgs
1587
+ >
1588
+
1589
+ /**
1590
+ * Arguments for {@linkcode PluginGeomCreateApi.profileFromSplinePoints}.
1591
+ *
1592
+ * | Property | Type | Description |
1593
+ * |---|---|---|
1594
+ * | `points` | {@linkcode Vec3Handle}`[]` | 3..256 ordered through-points of the closed loop (first point NOT repeated) |
1595
+ */
1596
+ export const PluginGeomCreateProfileFromSplinePointsArgs = z.object({
1597
+ points: z.array(Vec3Handle).min(3).max(MAX_SPLINE_THROUGH_POINTS),
1598
+ })
1599
+
1600
+ export type PluginGeomCreateProfileFromSplinePointsArgs = z.infer<
1601
+ typeof PluginGeomCreateProfileFromSplinePointsArgs
1602
+ >
1603
+
1604
+ /**
1605
+ * Arguments for {@linkcode PluginGeomCreateApi.contourFromProfile}.
1606
+ *
1607
+ * | Property | Type | Description |
1608
+ * |---|---|---|
1609
+ * | `outer` | {@linkcode ProfileHandle} | The outer boundary loop |
1610
+ */
1611
+ export const PluginGeomCreateContourFromProfileArgs = z.object({
1612
+ outer: ProfileHandle,
1613
+ })
1614
+
1615
+ export type PluginGeomCreateContourFromProfileArgs = z.infer<
1616
+ typeof PluginGeomCreateContourFromProfileArgs
1617
+ >
1618
+
1619
+ /**
1620
+ * Arguments for {@linkcode PluginGeomCreateApi.contourFromProfiles}.
1621
+ *
1622
+ * | Property | Type | Description |
1623
+ * |---|---|---|
1624
+ * | `outer` | {@linkcode ProfileHandle} | The outer boundary loop |
1625
+ * | `holes` | {@linkcode ProfileHandle}`[]` | Optional inner hole loops |
1626
+ */
1627
+ export const PluginGeomCreateContourFromProfilesArgs = z.object({
1628
+ outer: ProfileHandle,
1629
+ holes: z.array(ProfileHandle).optional(),
1630
+ })
1631
+
1632
+ export type PluginGeomCreateContourFromProfilesArgs = z.infer<
1633
+ typeof PluginGeomCreateContourFromProfilesArgs
1634
+ >
1635
+
1636
+ /**
1637
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromFaces}.
1638
+ *
1639
+ * | Property | Type | Description |
1640
+ * |---|---|---|
1641
+ * | `faces` | {@linkcode Vec3Components}`[][]` | Face loops — each an ordered array of `{x, y, z}` points (≥3 points per face, ≥4 faces) |
1642
+ */
1643
+ export const PluginGeomCreateBrepFromFacesArgs = z.object({
1644
+ faces: z.array(z.array(Vec3Components).min(3)).min(4),
1645
+ })
1646
+
1647
+ export type PluginGeomCreateBrepFromFacesArgs = z.infer<
1648
+ typeof PluginGeomCreateBrepFromFacesArgs
1649
+ >
1650
+
1651
+ /**
1652
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromExtrusion}.
1653
+ *
1654
+ * | Property | Type | Description |
1655
+ * |---|---|---|
1656
+ * | `contour` | {@linkcode ContourHandle} | The cross-section to extrude |
1657
+ * | `direction` | {@linkcode Vec3Components} | Extrusion direction (non-zero, finite components) |
1658
+ * | `amount` | `number` | Extrusion distance (finite, non-zero) |
1659
+ * | `options` | `object`? | Optional twist and taper: `twistInDegrees` (an angle, finite), `topScale` (a ratio, finite and positive) and `levels` (a count, 2..64) |
1660
+ */
1661
+ export const PluginGeomCreateBrepFromExtrusionArgs = z.object({
1662
+ contour: ContourHandle,
1663
+ direction: z.object({
1664
+ x: z.number().finite(),
1665
+ y: z.number().finite(),
1666
+ z: z.number().finite(),
1667
+ }),
1668
+ amount: z
1669
+ .number()
1670
+ .finite()
1671
+ .refine((value) => value !== 0, { message: "amount must be non-zero" }),
1672
+ options: z
1673
+ .object({
1674
+ twistInDegrees: z.number().finite().optional(),
1675
+ topScale: z.number().finite().positive().optional(),
1676
+ levels: z.number().int().min(2).max(MAX_TWIST_LEVELS).optional(),
1677
+ })
1678
+ .optional(),
1679
+ })
1680
+
1681
+ export type PluginGeomCreateBrepFromExtrusionArgs = z.infer<
1682
+ typeof PluginGeomCreateBrepFromExtrusionArgs
1683
+ >
1684
+
1685
+ /**
1686
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromLoft}.
1687
+ *
1688
+ * | Property | Type | Description |
1689
+ * |---|---|---|
1690
+ * | `bottomContour` | {@linkcode ContourHandle} | The bottom cross-section |
1691
+ * | `topContour` | {@linkcode ContourHandle} | The top cross-section |
1692
+ * | `intermediateContours` | {@linkcode ContourHandle}`[]`? | Optional in-between cross-sections, ordered bottom to top (any count; more than 32 sections in all builds but carries no parameter record) |
1693
+ * | `options` | `object`? | Optional behavior switches: `compatibility` (`"strict"` throws on mismatched edge counts instead of auto-matching; default `"auto"`), `seamAlignment` (`"authored"` keeps the authored correspondence instead of searching seam rotations; default `"auto"`) and `smoothing` (`"smooth"` lofts every section in one pass, so the sides curve through the intermediates; default `"ruled"`) |
1694
+ */
1695
+ export const PluginGeomCreateBrepFromLoftArgs = z.object({
1696
+ bottomContour: ContourHandle,
1697
+ topContour: ContourHandle,
1698
+ intermediateContours: z.array(ContourHandle).optional(),
1699
+ options: z
1700
+ .object({
1701
+ compatibility: z.enum(["strict", "auto"]).optional(),
1702
+ seamAlignment: z.enum(["authored", "auto"]).optional(),
1703
+ smoothing: z.enum(["ruled", "smooth"]).optional(),
1704
+ })
1705
+ .optional(),
1706
+ })
1707
+
1708
+ export type PluginGeomCreateBrepFromLoftArgs = z.infer<typeof PluginGeomCreateBrepFromLoftArgs>
1709
+
1710
+ /**
1711
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromMesh}.
1712
+ *
1713
+ * | Property | Type | Description |
1714
+ * |---|---|---|
1715
+ * | `positions` | {@linkcode Vec3Components}`[]` | Vertex positions (≥4) |
1716
+ * | `faces` | `number[][]` | Face loops of indices into `positions` (≥3 indices per face, ≥4 faces) |
1717
+ */
1718
+ export const PluginGeomCreateBrepFromMeshArgs = z.object({
1719
+ positions: z.array(Vec3Components).min(4),
1720
+ faces: z.array(z.array(z.number()).min(3)).min(4),
1721
+ })
1722
+
1723
+ export type PluginGeomCreateBrepFromMeshArgs = z.infer<typeof PluginGeomCreateBrepFromMeshArgs>
1724
+
1725
+ /**
1726
+ * Arguments for the brep boolean constructors —
1727
+ * {@linkcode PluginGeomCreateApi.brepFromUnion},
1728
+ * {@linkcode PluginGeomCreateApi.brepFromSubtraction} (`a` minus `b`), and
1729
+ * {@linkcode PluginGeomCreateApi.brepFromIntersection}.
1730
+ *
1731
+ * | Property | Type | Description |
1732
+ * |---|---|---|
1733
+ * | `a` | {@linkcode BrepHandle} | First solid (the subtraction target) |
1734
+ * | `b` | {@linkcode BrepHandle} | Second solid (the subtraction tool) |
1735
+ */
1736
+ export const PluginGeomCreateBrepBooleanArgs = z.object({
1737
+ a: BrepHandle,
1738
+ b: BrepHandle,
1739
+ })
1740
+
1741
+ export type PluginGeomCreateBrepBooleanArgs = z.infer<typeof PluginGeomCreateBrepBooleanArgs>
1742
+
1743
+ // Plain {x, y, z} record with finite components (brepFromExtrusion `direction` pattern).
1744
+ const FiniteVec3Components = z.object({
1745
+ x: z.number().finite(),
1746
+ y: z.number().finite(),
1747
+ z: z.number().finite(),
1748
+ })
1749
+
1750
+ /**
1751
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromFillet}.
1752
+ *
1753
+ * | Property | Type | Description |
1754
+ * |---|---|---|
1755
+ * | `brep` | {@linkcode BrepHandle} | The solid whose edges to round |
1756
+ * | `edges` | {@linkcode EdgeHandle}`[]` | The edges to fillet (one or more; more than 64 builds but carries no parameter record) |
1757
+ * | `radius` | `number` | Fillet radius (positive, finite); the start radius with `endRadius`, unused with `radii` |
1758
+ * | `options` | `object`? | Optional variable blend: `endRadius` (positive, finite) or `radii` (2..32 rows of `{ position, radius }`, `position` a ratio 0..1 strictly increasing). Mutually exclusive, and either one takes exactly ONE edge |
1759
+ */
1760
+ export const PluginGeomCreateBrepFromFilletArgs = z
1761
+ .object({
1762
+ brep: BrepHandle,
1763
+ edges: z.array(EdgeHandle).min(1),
1764
+ radius: z.number().finite().positive(),
1765
+ options: z
1766
+ .object({
1767
+ endRadius: z.number().finite().positive().optional(),
1768
+ radii: z
1769
+ .array(
1770
+ z.object({
1771
+ position: z.number().finite().min(0).max(1),
1772
+ radius: z.number().finite().positive(),
1773
+ }),
1774
+ )
1775
+ .min(2)
1776
+ .max(MAX_FILLET_LAW_SAMPLES)
1777
+ .optional(),
1778
+ })
1779
+ .optional(),
1780
+ })
1781
+ .superRefine((a, ctx) => {
1782
+ const { endRadius, radii } = a.options ?? {}
1783
+ if (endRadius === undefined && radii === undefined) return
1784
+ if (endRadius !== undefined && radii !== undefined) {
1785
+ ctx.addIssue({ code: "custom", message: "endRadius is mutually exclusive with radii" })
1786
+ }
1787
+ // The law belongs to the blend contour the one edge lies on, and `position`
1788
+ // is measured from that edge's start: a second edge would have no origin.
1789
+ if (a.edges.length !== 1) {
1790
+ ctx.addIssue({
1791
+ code: "custom",
1792
+ message: "a variable fillet takes exactly one edge — the blend contour it runs along",
1793
+ })
1794
+ }
1795
+ if (radii?.some((row, i) => i > 0 && row.position <= radii[i - 1].position)) {
1796
+ ctx.addIssue({ code: "custom", message: "radii positions must strictly increase" })
1797
+ }
1798
+ })
1799
+
1800
+ export type PluginGeomCreateBrepFromFilletArgs = z.infer<typeof PluginGeomCreateBrepFromFilletArgs>
1801
+
1802
+ /** An edge with the solid it belongs to. See {@linkcode OwnedEdge}. */
1803
+ export const OwnedEdge = z.object({
1804
+ edge: EdgeHandle,
1805
+ brep: BrepHandle,
1806
+ face: FaceHandle.optional(),
1807
+ })
1808
+
1809
+ /**
1810
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromPatch}.
1811
+ *
1812
+ * | Property | Type | Description |
1813
+ * |---|---|---|
1814
+ * | `boundary` | ({@linkcode CurveHandle} \| {@linkcode OwnedEdge})`[]` | 3..16 items forming one closed loop |
1815
+ * | `thickness` | `number` | Solid thickness in internal length units (positive, finite) |
1816
+ * | `options` | `object`? | `interiorPoints` (≤ 64 plain `{x, y, z}` the surface is pulled through), `continuity` (`"position"` default, or `"tangent"` — every item must then be an edge item naming its `face`), `side` (`"below"` default, or `"above"`) |
1817
+ */
1818
+ export const PluginGeomCreateBrepFromPatchArgs = z
1819
+ .object({
1820
+ boundary: z
1821
+ .array(z.union([CurveHandle, OwnedEdge]))
1822
+ .min(3)
1823
+ .max(MAX_PATCH_BOUNDARY_CURVES),
1824
+ thickness: z.number().finite().positive(),
1825
+ options: z
1826
+ .object({
1827
+ interiorPoints: z.array(FiniteVec3Components).max(MAX_PATCH_INTERIOR_POINTS).optional(),
1828
+ continuity: z.enum(["position", "tangent"]).optional(),
1829
+ side: z.enum(["below", "above"]).optional(),
1830
+ })
1831
+ .optional(),
1832
+ })
1833
+ .superRefine((a, ctx) => {
1834
+ if (a.options?.continuity !== "tangent") return
1835
+ // The kernel's filling call (`Add_2`) follows exactly ONE support face per
1836
+ // edge, but a solid edge touches two faces. So a tangent patch must say
1837
+ // which face to follow, for every item, and this is checked before the host
1838
+ // runs.
1839
+ if (a.boundary.some((item) => !("edge" in item) || item.face === undefined)) {
1840
+ ctx.addIssue({
1841
+ code: "custom",
1842
+ message:
1843
+ 'continuity: "tangent" needs every boundary item to be an edge with its brep and the face to continue',
1844
+ })
1845
+ }
1846
+ })
1847
+
1848
+ export type PluginGeomCreateBrepFromPatchArgs = z.infer<typeof PluginGeomCreateBrepFromPatchArgs>
1849
+
1850
+ /**
1851
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromOffset}.
1852
+ *
1853
+ * | Property | Type | Description |
1854
+ * |---|---|---|
1855
+ * | `brep` | {@linkcode BrepHandle} | The solid to offset |
1856
+ * | `distance` | `number` | Offset distance (finite, non-zero; positive grows, negative shrinks) |
1857
+ */
1858
+ export const PluginGeomCreateBrepFromOffsetArgs = z.object({
1859
+ brep: BrepHandle,
1860
+ distance: z
1861
+ .number()
1862
+ .finite()
1863
+ .refine((value) => value !== 0, { message: "distance must be non-zero" }),
1864
+ })
1865
+
1866
+ export type PluginGeomCreateBrepFromOffsetArgs = z.infer<typeof PluginGeomCreateBrepFromOffsetArgs>
1867
+
1868
+ /**
1869
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromShell}.
1870
+ *
1871
+ * | Property | Type | Description |
1872
+ * |---|---|---|
1873
+ * | `brep` | {@linkcode BrepHandle} | The solid to hollow |
1874
+ * | `openFaces` | {@linkcode FaceHandle}`[]` | The faces to remove as openings (one or more; more than 32 builds but carries no parameter record) |
1875
+ * | `thickness` | `number` | Wall thickness (positive, finite) |
1876
+ */
1877
+ export const PluginGeomCreateBrepFromShellArgs = z.object({
1878
+ brep: BrepHandle,
1879
+ openFaces: z.array(FaceHandle).min(1),
1880
+ thickness: z.number().finite().positive(),
1881
+ })
1882
+
1883
+ export type PluginGeomCreateBrepFromShellArgs = z.infer<typeof PluginGeomCreateBrepFromShellArgs>
1884
+
1885
+ /**
1886
+ * Arguments for {@linkcode PluginGeomCreateApi.brepsFromSplit}.
1887
+ *
1888
+ * | Property | Type | Description |
1889
+ * |---|---|---|
1890
+ * | `brep` | {@linkcode BrepHandle} | The solid to split |
1891
+ * | `planeOrigin` | {@linkcode Vec3Components} | A point on the cutting plane (finite components) |
1892
+ * | `planeNormal` | {@linkcode Vec3Components} | The plane normal (non-zero, finite components) |
1893
+ */
1894
+ export const PluginGeomCreateBrepsFromSplitArgs = z.object({
1895
+ brep: BrepHandle,
1896
+ planeOrigin: FiniteVec3Components,
1897
+ planeNormal: FiniteVec3Components,
1898
+ })
1899
+
1900
+ export type PluginGeomCreateBrepsFromSplitArgs = z.infer<typeof PluginGeomCreateBrepsFromSplitArgs>
1901
+
1902
+ /**
1903
+ * Arguments for {@linkcode PluginGeomCreateApi.brepsFromSplitByFace}.
1904
+ *
1905
+ * | Property | Type | Description |
1906
+ * |---|---|---|
1907
+ * | `brep` | {@linkcode BrepHandle} | The solid to cut |
1908
+ * | `toolFace` | {@linkcode FaceHandle} | The cutting face (read-only) |
1909
+ * | `toolBrep` | {@linkcode BrepHandle} | The solid `toolFace` belongs to (owner last) |
1910
+ */
1911
+ export const PluginGeomCreateBrepsFromSplitByFaceArgs = z.object({
1912
+ brep: BrepHandle,
1913
+ toolFace: FaceHandle,
1914
+ toolBrep: BrepHandle,
1915
+ })
1916
+
1917
+ export type PluginGeomCreateBrepsFromSplitByFaceArgs = z.infer<
1918
+ typeof PluginGeomCreateBrepsFromSplitByFaceArgs
1919
+ >
1920
+
1921
+ /**
1922
+ * Arguments for {@linkcode PluginGeomCreateApi.curvesFromProjection}.
1923
+ *
1924
+ * | Property | Type | Description |
1925
+ * |---|---|---|
1926
+ * | `curve` | {@linkcode CurveHandle} | The curve to project |
1927
+ * | `face` | {@linkcode FaceHandle} | The face to project onto |
1928
+ * | `faceBrep` | {@linkcode BrepHandle} | The solid `face` belongs to (owner last) |
1929
+ */
1930
+ export const PluginGeomCreateCurvesFromProjectionArgs = z.object({
1931
+ curve: CurveHandle,
1932
+ face: FaceHandle,
1933
+ faceBrep: BrepHandle,
1934
+ })
1935
+
1936
+ export type PluginGeomCreateCurvesFromProjectionArgs = z.infer<
1937
+ typeof PluginGeomCreateCurvesFromProjectionArgs
1938
+ >
1939
+
1940
+ /**
1941
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromChamfer}.
1942
+ *
1943
+ * | Property | Type | Description |
1944
+ * |---|---|---|
1945
+ * | `brep` | {@linkcode BrepHandle} | The solid whose edges to bevel |
1946
+ * | `edges` | {@linkcode EdgeHandle}`[]` | The edges to chamfer (one or more; more than 64 builds but carries no parameter record) |
1947
+ * | `distance` | `number` | Chamfer distance from the edge on each adjacent face (positive, finite) |
1948
+ */
1949
+ export const PluginGeomCreateBrepFromChamferArgs = z.object({
1950
+ brep: BrepHandle,
1951
+ edges: z.array(EdgeHandle).min(1),
1952
+ distance: z.number().finite().positive(),
1953
+ })
1954
+
1955
+ export type PluginGeomCreateBrepFromChamferArgs = z.infer<
1956
+ typeof PluginGeomCreateBrepFromChamferArgs
1957
+ >
1958
+
1959
+ // Taper scale factor — bounded so a near-zero scale cannot pinch a section
1960
+ // into a degenerate sliver the downstream planarity gates would miss.
1961
+ const SweepScaleFactor = z.number().finite().min(1e-3).max(1e3)
1962
+
1963
+ /**
1964
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromSweep}.
1965
+ *
1966
+ * | Property | Type | Description |
1967
+ * |---|---|---|
1968
+ * | `profile` | {@linkcode ContourHandle} | The cross-section to sweep (hole-free) |
1969
+ * | `path` | {@linkcode Vec3Components}`[]` | The polyline path (2 or more points, finite components — more than 512 builds but carries no parameter record; repeating the first point as the last closes it into a ring — closed paths must be flat and straight-segmented and take a straight-edged profile) |
1970
+ * | `options` | `object`? | Optional scale law: `startScale`/`endScale` (linear taper by arc length) or per-point `scales` (mutually exclusive; factors finite, 1e-3..1e3; `scales.length` must equal `path.length`). Optional corner `transition`: `"miter"` (default), `"round"` (arc-carried corners) or `{ bevel }` (chamfered corners; finite, 1e-3..1e3) |
1971
+ */
1972
+ export const PluginGeomCreateBrepFromSweepArgs = z
1973
+ .object({
1974
+ profile: ContourHandle,
1975
+ path: z.array(FiniteVec3Components).min(2),
1976
+ options: z
1977
+ .object({
1978
+ startScale: SweepScaleFactor.optional(),
1979
+ endScale: SweepScaleFactor.optional(),
1980
+ scales: z.array(SweepScaleFactor).optional(),
1981
+ transition: z
1982
+ .union([
1983
+ z.literal("miter"),
1984
+ z.literal("round"),
1985
+ z.object({ bevel: z.number().finite().min(1e-3).max(1e3) }),
1986
+ ])
1987
+ .optional(),
1988
+ })
1989
+ .optional(),
1990
+ })
1991
+ .superRefine((a, ctx) => {
1992
+ const options = a.options
1993
+ if (!options || options.scales === undefined) return
1994
+ if (options.startScale !== undefined || options.endScale !== undefined) {
1995
+ ctx.addIssue({
1996
+ code: z.ZodIssueCode.custom,
1997
+ message: "scales is mutually exclusive with startScale/endScale",
1998
+ })
1999
+ }
2000
+ if (options.scales.length !== a.path.length) {
2001
+ ctx.addIssue({
2002
+ code: z.ZodIssueCode.custom,
2003
+ message: "scales must have exactly one entry per path point",
2004
+ })
2005
+ }
2006
+ })
2007
+
2008
+ export type PluginGeomCreateBrepFromSweepArgs = z.infer<typeof PluginGeomCreateBrepFromSweepArgs>
2009
+
2010
+ /**
2011
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromSweepAlongCurve}.
2012
+ *
2013
+ * | Property | Type | Description |
2014
+ * |---|---|---|
2015
+ * | `profile` | {@linkcode ContourHandle} | The cross-section to sweep (hole-free) |
2016
+ * | `path` | {@linkcode CurveHandle}`[]` | 1..64 curves, ordered, meeting end to end |
2017
+ * | `options` | `object`? | `orientation` — `"upright"` (default), `"frenet"` or `"fixed"`; `rail` — a second guide {@linkcode CurveHandle} |
2018
+ */
2019
+ export const PluginGeomCreateBrepFromSweepAlongCurveArgs = z.object({
2020
+ profile: ContourHandle,
2021
+ path: z.array(CurveHandle).min(1).max(MAX_PATH_CURVES),
2022
+ options: z
2023
+ .object({
2024
+ orientation: z.enum(["frenet", "fixed", "upright"]).optional(),
2025
+ rail: CurveHandle.optional(),
2026
+ })
2027
+ .optional(),
2028
+ })
2029
+
2030
+ export type PluginGeomCreateBrepFromSweepAlongCurveArgs = z.infer<
2031
+ typeof PluginGeomCreateBrepFromSweepAlongCurveArgs
2032
+ >
2033
+
2034
+ /**
2035
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromRevolution}.
2036
+ *
2037
+ * | Property | Type | Description |
2038
+ * |---|---|---|
2039
+ * | `profile` | {@linkcode ContourHandle} | The cross-section to revolve (holes allowed) |
2040
+ * | `axisOrigin` | {@linkcode Vec3Components} | A point on the revolution axis (finite components; the axis must lie in the plane of the profile) |
2041
+ * | `axisDirection` | {@linkcode Vec3Components} | The axis direction (non-zero, finite components; the axis must lie in the plane of the profile) |
2042
+ * | `angleInDegrees` | `number`? | Revolution angle in degrees (0 < angle ≤ 360; default 360) |
2043
+ */
2044
+ export const PluginGeomCreateBrepFromRevolutionArgs = z.object({
2045
+ profile: ContourHandle,
2046
+ axisOrigin: FiniteVec3Components,
2047
+ axisDirection: FiniteVec3Components,
2048
+ angleInDegrees: z.number().finite().positive().max(360).optional(),
2049
+ })
2050
+
2051
+ export type PluginGeomCreateBrepFromRevolutionArgs = z.infer<
2052
+ typeof PluginGeomCreateBrepFromRevolutionArgs
2053
+ >
2054
+
2055
+ /**
2056
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromSphere}.
2057
+ *
2058
+ * | Property | Type | Description |
2059
+ * |---|---|---|
2060
+ * | `centre` | {@linkcode Vec3Components} | Sphere centre (finite components) |
2061
+ * | `radius` | `number` | Sphere radius (finite; positivity is checked host-side so the error names the parameter) |
2062
+ */
2063
+ export const PluginGeomCreateBrepFromSphereArgs = z.object({
2064
+ centre: FiniteVec3Components,
2065
+ radius: z.number().finite(),
2066
+ })
2067
+
2068
+ export type PluginGeomCreateBrepFromSphereArgs = z.infer<
2069
+ typeof PluginGeomCreateBrepFromSphereArgs
2070
+ >
2071
+
2072
+ /**
2073
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromCylinder}.
2074
+ *
2075
+ * | Property | Type | Description |
2076
+ * |---|---|---|
2077
+ * | `base` | {@linkcode Vec3Components} | Centre of the bottom cap (finite components) |
2078
+ * | `axis` | {@linkcode Vec3Components} | Axis direction (non-zero, finite components) |
2079
+ * | `radius` | `number` | Cylinder radius (finite, positive) |
2080
+ * | `height` | `number` | Height along `axis` (finite, positive) |
2081
+ */
2082
+ export const PluginGeomCreateBrepFromCylinderArgs = z.object({
2083
+ base: FiniteVec3Components,
2084
+ axis: FiniteVec3Components,
2085
+ radius: z.number().finite(),
2086
+ height: z.number().finite(),
2087
+ })
2088
+
2089
+ export type PluginGeomCreateBrepFromCylinderArgs = z.infer<
2090
+ typeof PluginGeomCreateBrepFromCylinderArgs
2091
+ >
2092
+
2093
+ /**
2094
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromCone}.
2095
+ *
2096
+ * | Property | Type | Description |
2097
+ * |---|---|---|
2098
+ * | `base` | {@linkcode Vec3Components} | Centre of the bottom cap (finite components) |
2099
+ * | `axis` | {@linkcode Vec3Components} | Axis direction (non-zero, finite components) |
2100
+ * | `baseRadius` | `number` | Radius at the base (finite, positive) |
2101
+ * | `height` | `number` | Height along `axis` (finite, positive) |
2102
+ * | `topRadius` | `number`? | Radius at the top (finite, ≥ 0, ≠ `baseRadius`; default 0 = apex) |
2103
+ */
2104
+ export const PluginGeomCreateBrepFromConeArgs = z
2105
+ .object({
2106
+ base: FiniteVec3Components,
2107
+ axis: FiniteVec3Components,
2108
+ baseRadius: z.number().finite(),
2109
+ height: z.number().finite(),
2110
+ topRadius: z.number().finite().optional(),
2111
+ })
2112
+ .refine((a) => (a.topRadius ?? 0) !== a.baseRadius, {
2113
+ message: "topRadius must differ from baseRadius — equal radii are a cylinder",
2114
+ })
2115
+
2116
+ export type PluginGeomCreateBrepFromConeArgs = z.infer<typeof PluginGeomCreateBrepFromConeArgs>
2117
+
2118
+ /**
2119
+ * Arguments for {@linkcode PluginGeomCreateApi.brepFromTorus}.
2120
+ *
2121
+ * | Property | Type | Description |
2122
+ * |---|---|---|
2123
+ * | `centre` | {@linkcode Vec3Components} | Torus centre (finite components) |
2124
+ * | `axis` | {@linkcode Vec3Components} | Plane normal of the ring (non-zero, finite components) |
2125
+ * | `majorRadius` | `number` | Ring radius, centre to tube centre (finite, positive) |
2126
+ * | `minorRadius` | `number` | Tube radius (finite, positive, strictly less than `majorRadius`) |
2127
+ */
2128
+ export const PluginGeomCreateBrepFromTorusArgs = z
2129
+ .object({
2130
+ centre: FiniteVec3Components,
2131
+ axis: FiniteVec3Components,
2132
+ majorRadius: z.number().finite(),
2133
+ minorRadius: z.number().finite(),
2134
+ })
2135
+ .refine((a) => a.minorRadius < a.majorRadius, {
2136
+ message: "minorRadius must be smaller than majorRadius — the torus would self-intersect",
2137
+ })
2138
+
2139
+ export type PluginGeomCreateBrepFromTorusArgs = z.infer<typeof PluginGeomCreateBrepFromTorusArgs>