fluidcad 0.0.36 → 0.0.38

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 (188) hide show
  1. package/LICENSE.txt +21 -504
  2. package/README.md +1 -1
  3. package/lib/dist/common/edge.d.ts +1 -1
  4. package/lib/dist/common/face.d.ts +1 -1
  5. package/lib/dist/common/scene-object.d.ts +6 -0
  6. package/lib/dist/common/scene-object.js +8 -0
  7. package/lib/dist/common/shape-factory.d.ts +1 -1
  8. package/lib/dist/common/shape-history-tracker.d.ts +1 -1
  9. package/lib/dist/common/shape.d.ts +1 -1
  10. package/lib/dist/common/solid.d.ts +1 -1
  11. package/lib/dist/common/transformable-primitive.d.ts +12 -1
  12. package/lib/dist/common/transformable-primitive.js +27 -0
  13. package/lib/dist/common/vertex.d.ts +1 -1
  14. package/lib/dist/common/wire.d.ts +1 -1
  15. package/lib/dist/core/2d/index.d.ts +1 -0
  16. package/lib/dist/core/2d/index.js +1 -0
  17. package/lib/dist/core/2d/text.d.ts +30 -0
  18. package/lib/dist/core/2d/text.js +37 -0
  19. package/lib/dist/core/helix.d.ts +20 -0
  20. package/lib/dist/core/helix.js +36 -0
  21. package/lib/dist/core/index.d.ts +3 -1
  22. package/lib/dist/core/index.js +2 -0
  23. package/lib/dist/core/interfaces.d.ts +180 -0
  24. package/lib/dist/core/plane.d.ts +26 -6
  25. package/lib/dist/core/plane.js +21 -44
  26. package/lib/dist/core/wrap.d.ts +17 -0
  27. package/lib/dist/core/wrap.js +39 -0
  28. package/lib/dist/features/2d/offset.js +2 -2
  29. package/lib/dist/features/2d/text.d.ts +67 -0
  30. package/lib/dist/features/2d/text.js +320 -0
  31. package/lib/dist/features/cylinder.d.ts +3 -1
  32. package/lib/dist/features/cylinder.js +5 -2
  33. package/lib/dist/features/extrude-base.d.ts +1 -0
  34. package/lib/dist/features/extrude-to-face.d.ts +1 -0
  35. package/lib/dist/features/extrude-to-face.js +6 -0
  36. package/lib/dist/features/fillet.d.ts +1 -1
  37. package/lib/dist/features/helix.d.ts +41 -0
  38. package/lib/dist/features/helix.js +337 -0
  39. package/lib/dist/features/plane-from-object.d.ts +16 -4
  40. package/lib/dist/features/plane-from-object.js +101 -8
  41. package/lib/dist/features/select.js +32 -8
  42. package/lib/dist/features/simple-extruder.d.ts +1 -1
  43. package/lib/dist/features/simple-extruder.js +7 -2
  44. package/lib/dist/features/sphere.d.ts +3 -1
  45. package/lib/dist/features/sphere.js +5 -2
  46. package/lib/dist/features/sweep.js +7 -2
  47. package/lib/dist/features/wrap.d.ts +39 -0
  48. package/lib/dist/features/wrap.js +116 -0
  49. package/lib/dist/filters/edge/belongs-to-face.d.ts +3 -1
  50. package/lib/dist/filters/edge/belongs-to-face.js +14 -10
  51. package/lib/dist/filters/filter.d.ts +1 -1
  52. package/lib/dist/filters/from-object.d.ts +1 -1
  53. package/lib/dist/filters/tangent-expander.d.ts +1 -1
  54. package/lib/dist/filters/tangent-expander.js +57 -40
  55. package/lib/dist/helpers/scene-helpers.d.ts +2 -0
  56. package/lib/dist/helpers/scene-helpers.js +1 -1
  57. package/lib/dist/index.d.ts +2 -0
  58. package/lib/dist/index.js +3 -1
  59. package/lib/dist/io/file-import.d.ts +7 -0
  60. package/lib/dist/io/file-import.js +28 -1
  61. package/lib/dist/io/font-registry.d.ts +45 -0
  62. package/lib/dist/io/font-registry.js +272 -0
  63. package/lib/dist/math/bspline-interpolation.d.ts +29 -0
  64. package/lib/dist/math/bspline-interpolation.js +194 -0
  65. package/lib/dist/oc/boolean-ops.d.ts +3 -1
  66. package/lib/dist/oc/boolean-ops.js +15 -1
  67. package/lib/dist/oc/color-transfer.d.ts +1 -1
  68. package/lib/dist/oc/constraints/constraint-helpers.d.ts +4 -4
  69. package/lib/dist/oc/constraints/curve/tangent-circle-solver.js +10 -9
  70. package/lib/dist/oc/constraints/curve/tangent-line-solver.js +5 -6
  71. package/lib/dist/oc/convert.d.ts +1 -1
  72. package/lib/dist/oc/draft-ops.d.ts +1 -1
  73. package/lib/dist/oc/edge-ops.d.ts +2 -2
  74. package/lib/dist/oc/edge-ops.js +13 -14
  75. package/lib/dist/oc/edge-props.d.ts +1 -1
  76. package/lib/dist/oc/edge-query.d.ts +1 -1
  77. package/lib/dist/oc/edge-query.js +3 -8
  78. package/lib/dist/oc/errors.d.ts +8 -0
  79. package/lib/dist/oc/errors.js +27 -0
  80. package/lib/dist/oc/explorer.d.ts +2 -2
  81. package/lib/dist/oc/extrude-ops.d.ts +28 -2
  82. package/lib/dist/oc/extrude-ops.js +56 -7
  83. package/lib/dist/oc/face-ops.d.ts +2 -1
  84. package/lib/dist/oc/face-ops.js +11 -0
  85. package/lib/dist/oc/face-props.d.ts +1 -1
  86. package/lib/dist/oc/face-query.d.ts +12 -1
  87. package/lib/dist/oc/face-query.js +39 -0
  88. package/lib/dist/oc/fillet-ops.d.ts +1 -1
  89. package/lib/dist/oc/fillet-ops.js +4 -4
  90. package/lib/dist/oc/geometry.d.ts +1 -1
  91. package/lib/dist/oc/geometry.js +12 -14
  92. package/lib/dist/oc/helix-ops.d.ts +37 -0
  93. package/lib/dist/oc/helix-ops.js +88 -0
  94. package/lib/dist/oc/hit-test.d.ts +1 -1
  95. package/lib/dist/oc/index.d.ts +4 -0
  96. package/lib/dist/oc/index.js +2 -0
  97. package/lib/dist/oc/init.d.ts +1 -1
  98. package/lib/dist/oc/init.js +1 -1
  99. package/lib/dist/oc/intersection.js +1 -1
  100. package/lib/dist/oc/io.d.ts +6 -6
  101. package/lib/dist/oc/io.js +31 -24
  102. package/lib/dist/oc/measure/classify.d.ts +34 -0
  103. package/lib/dist/oc/measure/classify.js +246 -0
  104. package/lib/dist/oc/measure/measure-ops.d.ts +9 -0
  105. package/lib/dist/oc/measure/measure-ops.js +210 -0
  106. package/lib/dist/oc/measure/measure-types.d.ts +39 -0
  107. package/lib/dist/oc/measure/measure-types.js +1 -0
  108. package/lib/dist/oc/measure/sampling.d.ts +9 -0
  109. package/lib/dist/oc/measure/sampling.js +77 -0
  110. package/lib/dist/oc/measure/vec.d.ts +13 -0
  111. package/lib/dist/oc/measure/vec.js +23 -0
  112. package/lib/dist/oc/mesh.d.ts +1 -1
  113. package/lib/dist/oc/mesh.js +40 -28
  114. package/lib/dist/oc/path-sampler.d.ts +29 -0
  115. package/lib/dist/oc/path-sampler.js +63 -0
  116. package/lib/dist/oc/props.d.ts +1 -1
  117. package/lib/dist/oc/props.js +4 -1
  118. package/lib/dist/oc/shape-hash.d.ts +26 -0
  119. package/lib/dist/oc/shape-hash.js +32 -0
  120. package/lib/dist/oc/shape-ops.d.ts +5 -3
  121. package/lib/dist/oc/shape-ops.js +6 -5
  122. package/lib/dist/oc/sweep-ops.d.ts +13 -1
  123. package/lib/dist/oc/sweep-ops.js +174 -18
  124. package/lib/dist/oc/text-outline.d.ts +62 -0
  125. package/lib/dist/oc/text-outline.js +212 -0
  126. package/lib/dist/oc/thin-face-maker.d.ts +0 -19
  127. package/lib/dist/oc/thin-face-maker.js +3 -68
  128. package/lib/dist/oc/topology-index.d.ts +1 -1
  129. package/lib/dist/oc/vertex-ops.d.ts +1 -1
  130. package/lib/dist/oc/wire-ops.d.ts +18 -3
  131. package/lib/dist/oc/wire-ops.js +56 -5
  132. package/lib/dist/oc/wrap-development.d.ts +105 -0
  133. package/lib/dist/oc/wrap-development.js +179 -0
  134. package/lib/dist/oc/wrap-ops.d.ts +100 -0
  135. package/lib/dist/oc/wrap-ops.js +406 -0
  136. package/lib/dist/rendering/render-solid.js +10 -2
  137. package/lib/dist/scene-manager.d.ts +2 -0
  138. package/lib/dist/scene-manager.js +29 -0
  139. package/lib/dist/tests/features/2d/offset.test.js +74 -1
  140. package/lib/dist/tests/features/cylinder-curve-filter.test.js +3 -3
  141. package/lib/dist/tests/features/extrude-to-face.test.js +38 -1
  142. package/lib/dist/tests/features/helix.test.d.ts +1 -0
  143. package/lib/dist/tests/features/helix.test.js +295 -0
  144. package/lib/dist/tests/features/plane.test.js +95 -0
  145. package/lib/dist/tests/features/repeat-primitive.test.d.ts +1 -0
  146. package/lib/dist/tests/features/repeat-primitive.test.js +60 -0
  147. package/lib/dist/tests/features/rib.test.js +6 -1
  148. package/lib/dist/tests/features/sweep.test.js +170 -1
  149. package/lib/dist/tests/features/text.test.d.ts +1 -0
  150. package/lib/dist/tests/features/text.test.js +347 -0
  151. package/lib/dist/tests/features/wrap-development.test.d.ts +1 -0
  152. package/lib/dist/tests/features/wrap-development.test.js +130 -0
  153. package/lib/dist/tests/features/wrap-extruded-target.test.d.ts +1 -0
  154. package/lib/dist/tests/features/wrap-extruded-target.test.js +106 -0
  155. package/lib/dist/tests/features/wrap-repeat.test.d.ts +1 -0
  156. package/lib/dist/tests/features/wrap-repeat.test.js +93 -0
  157. package/lib/dist/tests/features/wrap.test.d.ts +1 -0
  158. package/lib/dist/tests/features/wrap.test.js +331 -0
  159. package/lib/dist/tests/math/bspline-interpolation.test.d.ts +1 -0
  160. package/lib/dist/tests/math/bspline-interpolation.test.js +119 -0
  161. package/lib/dist/tests/measure.test.d.ts +1 -0
  162. package/lib/dist/tests/measure.test.js +288 -0
  163. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  164. package/llm-docs/api/helix.md +64 -0
  165. package/llm-docs/api/index.json +11 -2
  166. package/llm-docs/api/text.md +52 -0
  167. package/llm-docs/api/types/helix.md +105 -0
  168. package/llm-docs/api/types/text.md +138 -0
  169. package/llm-docs/api/types/wrap.md +131 -0
  170. package/llm-docs/api/wrap.md +62 -0
  171. package/llm-docs/index.json +121 -1
  172. package/mcp/dist/server.js +20 -1
  173. package/mcp/dist/tools/inspection.d.ts +17 -0
  174. package/mcp/dist/tools/inspection.js +14 -0
  175. package/package.json +7 -3
  176. package/server/dist/fluidcad-server.d.ts +11 -1
  177. package/server/dist/fluidcad-server.js +21 -1
  178. package/server/dist/index.js +4 -2
  179. package/server/dist/preferences.d.ts +4 -0
  180. package/server/dist/preferences.js +2 -0
  181. package/server/dist/routes/measure.d.ts +3 -0
  182. package/server/dist/routes/measure.js +32 -0
  183. package/server/dist/routes/params.js +1 -1
  184. package/server/dist/routes/preferences.js +6 -0
  185. package/server/dist/routes/sketch-edits.js +2 -1
  186. package/ui/dist/assets/{index-MRqwG9Vh.js → index-D8zV21wB.js} +149 -102
  187. package/ui/dist/assets/{index-CDJmUpFI.css → index-dAFdg2Un.css} +1 -1
  188. package/ui/dist/index.html +2 -2
@@ -96,9 +96,8 @@ export class EdgeOps {
96
96
  static edgeMiddlePoint(edge) {
97
97
  const oc = getOC();
98
98
  const curveAdaptor = new oc.BRepAdaptor_Curve(oc.TopoDS.Edge(edge));
99
- const curve = curveAdaptor.Curve();
100
- const midParam = (curve.FirstParameter() + curve.LastParameter()) / 2.0;
101
- const midPoint = curve.Value(midParam);
99
+ const midParam = (curveAdaptor.FirstParameter() + curveAdaptor.LastParameter()) / 2.0;
100
+ const midPoint = curveAdaptor.Value(midParam);
102
101
  const result = new oc.gp_Pnt(midPoint.X(), midPoint.Y(), midPoint.Z());
103
102
  curveAdaptor.delete();
104
103
  return result;
@@ -131,7 +130,7 @@ export class EdgeOps {
131
130
  const oc = getOC();
132
131
  const isReversed = edge.Orientation() === oc.TopAbs_Orientation.TopAbs_REVERSED;
133
132
  const curveHandle = oc.BRep_Tool.Curve(edge, 0, 1);
134
- const curve = curveHandle.get();
133
+ const curve = curveHandle.returnValue;
135
134
  const param = isReversed ? curve.FirstParameter() : curve.LastParameter();
136
135
  const edgeSign = isReversed ? -1 : 1;
137
136
  const tangentVec = new oc.gp_Vec();
@@ -143,11 +142,11 @@ export class EdgeOps {
143
142
  }
144
143
  static makeEdgeFromCurveAndVerticesRaw(curve, v1, v2) {
145
144
  const oc = getOC();
146
- const handle = new oc.Handle_Geom_Curve(curve);
147
- const edgeMaker = new oc.BRepBuilderAPI_MakeEdge(handle, v1, v2);
145
+ // Handles are unwrapped in V8: pass the Geom_Curve straight to MakeEdge
146
+ // (constructing the abstract oc.Geom_Curve base throws at runtime).
147
+ const edgeMaker = new oc.BRepBuilderAPI_MakeEdge(curve, v1, v2);
148
148
  const edge = edgeMaker.Edge();
149
149
  edgeMaker.delete();
150
- handle.delete();
151
150
  return edge;
152
151
  }
153
152
  static splitEdges(edges) {
@@ -233,17 +232,17 @@ export class EdgeOps {
233
232
  sourceIndex.push(srcIdx);
234
233
  continue;
235
234
  }
236
- // Get the underlying curve for creating sub-edges
237
- const curveHandle = oc.BRep_Tool.Curve(edge, 0, 1);
238
- const curve = curveHandle.get();
239
- const handle = new oc.Handle_Geom_Curve(curve);
235
+ // Get the underlying curve for creating sub-edges. Handles are unwrapped in
236
+ // V8 use the returned Geom_Curve directly (the abstract oc.Geom_Curve base
237
+ // has no constructor).
238
+ const geomCurve = oc.BRep_Tool.Curve(edge, 0, 1).returnValue;
240
239
  if (isClosed && interior.length >= 2) {
241
240
  // Closed curve: N split points → N arcs (no seam split)
242
241
  const period = last - first;
243
242
  for (let k = 0; k < interior.length; k++) {
244
243
  const u1 = interior[k];
245
244
  const u2 = k < interior.length - 1 ? interior[k + 1] : interior[0] + period;
246
- const maker = new oc.BRepBuilderAPI_MakeEdge(handle, u1, u2);
245
+ const maker = new oc.BRepBuilderAPI_MakeEdge(geomCurve, u1, u2);
247
246
  if (maker.IsDone()) {
248
247
  result.push(Edge.fromTopoDSEdge(maker.Edge()));
249
248
  sourceIndex.push(srcIdx);
@@ -255,7 +254,7 @@ export class EdgeOps {
255
254
  // Open curve: include edge endpoints as boundaries
256
255
  const allParams = [first, ...interior, last];
257
256
  for (let k = 0; k < allParams.length - 1; k++) {
258
- const maker = new oc.BRepBuilderAPI_MakeEdge(handle, allParams[k], allParams[k + 1]);
257
+ const maker = new oc.BRepBuilderAPI_MakeEdge(geomCurve, allParams[k], allParams[k + 1]);
259
258
  if (maker.IsDone()) {
260
259
  result.push(Edge.fromTopoDSEdge(maker.Edge()));
261
260
  sourceIndex.push(srcIdx);
@@ -268,7 +267,7 @@ export class EdgeOps {
268
267
  result.push(Edge.fromTopoDSEdge(edge));
269
268
  sourceIndex.push(srcIdx);
270
269
  }
271
- handle.delete();
270
+ geomCurve.delete();
272
271
  }
273
272
  return { edges: result, sourceIndex };
274
273
  }
@@ -1,4 +1,4 @@
1
- import type { TopoDS_Shape } from "occjs-wrapper";
1
+ import type { TopoDS_Shape } from "ocjs-fluidcad";
2
2
  export interface EdgeProperties {
3
3
  curveType: 'line' | 'circle' | 'arc' | 'ellipse' | 'other';
4
4
  length?: number;
@@ -1,4 +1,4 @@
1
- import type { gp_Pln, gp_Vec, TopoDS_Edge, TopoDS_Face, TopoDS_Shape } from "occjs-wrapper";
1
+ import type { gp_Pln, gp_Vec, TopoDS_Edge, TopoDS_Face, TopoDS_Shape } from "ocjs-fluidcad";
2
2
  import { Point } from "../math/point.js";
3
3
  import { Vector3d } from "../math/vector3d.js";
4
4
  import { Plane } from "../math/plane.js";
@@ -148,17 +148,12 @@ export class EdgeQuery {
148
148
  const oc = getOC();
149
149
  const ocEdge = oc.TopoDS.Edge(edge);
150
150
  const adaptor = new oc.BRepAdaptor_Curve(ocEdge);
151
- const curve = adaptor.Curve().Curve()?.get();
152
- if (!curve) {
153
- adaptor.delete();
154
- return false;
155
- }
156
- const firstParam = curve.FirstParameter();
157
- const lastParam = curve.LastParameter();
151
+ const firstParam = adaptor.FirstParameter();
152
+ const lastParam = adaptor.LastParameter();
158
153
  const midParam = (firstParam + lastParam) / 2;
159
154
  const tangent = new oc.gp_Vec();
160
155
  const tempPnt = new oc.gp_Pnt();
161
- curve.D1(midParam, tempPnt, tangent);
156
+ adaptor.D1(midParam, tempPnt, tangent);
162
157
  const dotProduct = Math.abs(tangent.Dot(planeNormal));
163
158
  const tolerance = 1e-6;
164
159
  const result = dotProduct < tolerance;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ocjs v3 / OCCT V8 raises C++ failures as native `WebAssembly.Exception`
3
+ * objects. Their default stringification is the opaque
4
+ * `[object WebAssembly.Exception]`, so decode the OCCT failure type + message
5
+ * via the module's `getExceptionMessage`. Ordinary JS errors pass through with
6
+ * their stack/message.
7
+ */
8
+ export declare function describeOcException(e: unknown): string;
@@ -0,0 +1,27 @@
1
+ import { getOC } from "./init.js";
2
+ /**
3
+ * ocjs v3 / OCCT V8 raises C++ failures as native `WebAssembly.Exception`
4
+ * objects. Their default stringification is the opaque
5
+ * `[object WebAssembly.Exception]`, so decode the OCCT failure type + message
6
+ * via the module's `getExceptionMessage`. Ordinary JS errors pass through with
7
+ * their stack/message.
8
+ */
9
+ export function describeOcException(e) {
10
+ if (isWasmException(e)) {
11
+ try {
12
+ const [type, message] = getOC().getExceptionMessage(e);
13
+ return `${type}: ${message}`;
14
+ }
15
+ catch {
16
+ return "WebAssembly.Exception (failed to decode OCCT message)";
17
+ }
18
+ }
19
+ if (e instanceof Error) {
20
+ return e.stack ?? e.message;
21
+ }
22
+ return String(e);
23
+ }
24
+ function isWasmException(e) {
25
+ const ctor = globalThis.WebAssembly?.Exception;
26
+ return typeof ctor === "function" && e instanceof ctor;
27
+ }
@@ -1,4 +1,4 @@
1
- import type { TopAbs_ShapeEnum, TopoDS_Compound, TopoDS_Edge, TopoDS_Face, TopoDS_Shape, TopoDS_Solid, TopoDS_Wire, TopoDS_Vertex } from "occjs-wrapper";
1
+ import type { TopAbs_ShapeEnum, TopoDS_Compound, TopoDS_Edge, TopoDS_Face, TopoDS_Shape, TopoDS_Solid, TopoDS_Wire, TopoDS_Vertex } from "ocjs-fluidcad";
2
2
  import { Shape } from "../common/shape.js";
3
3
  import { Face } from "../common/face.js";
4
4
  import { Edge } from "../common/edge.js";
@@ -18,7 +18,7 @@ export declare class Explorer {
18
18
  static findShapes<T = TopoDS_Shape>(shape: TopoDS_Shape, lookFor: TopAbs_ShapeEnum): T[];
19
19
  static findFirstShapeOfType<T = TopoDS_Shape>(shape: TopoDS_Shape, lookFor: TopAbs_ShapeEnum): T | null;
20
20
  static findAllShapes(shape: TopoDS_Compound): any[];
21
- static getOcShapeType(type: string): any;
21
+ static getOcShapeType(type: string): "TopAbs_SOLID" | "TopAbs_FACE" | "TopAbs_WIRE" | "TopAbs_EDGE" | "TopAbs_VERTEX";
22
22
  static toSolid(shape: TopoDS_Shape): TopoDS_Solid;
23
23
  static toFace(shape: TopoDS_Shape): TopoDS_Face;
24
24
  static toWire(shape: TopoDS_Shape): TopoDS_Wire;
@@ -1,4 +1,4 @@
1
- import type { TopoDS_Shape } from "occjs-wrapper";
1
+ import type { TopoDS_Shape } from "ocjs-fluidcad";
2
2
  import { Vector3d } from "../math/vector3d.js";
3
3
  import { Plane } from "../math/plane.js";
4
4
  import { Axis } from "../math/axis.js";
@@ -22,11 +22,37 @@ export interface MakeRevolResult {
22
22
  }
23
23
  export declare class ExtrudeOps {
24
24
  static makePrism(shape: Shape, direction: Vector3d, distance: number): Shape;
25
- static makePrismFromVec(shape: Shape, vec: Vector3d): {
25
+ /**
26
+ * Build a prism by sweeping `shape` along `vec`. `firstFace` is the cap at the
27
+ * profile location, `lastFace` the cap at `profile + vec`.
28
+ *
29
+ * When `canonicalizeSweep` is set, the sweep is reoriented to a canonical
30
+ * direction (see below). The swept lateral surfaces (cylinders, cones from a
31
+ * non-drafted profile) take their axis/parametrization from the sweep
32
+ * direction, and OCCT 8's `ShapeUpgrade_UnifySameDomain` refuses to merge two
33
+ * such faces swept in opposite directions. So a symmetric extrude (one half
34
+ * up, one half down) otherwise fuses into a solid whose lateral surface stays
35
+ * split at the mid-plane. Canonicalizing makes anti-parallel extrudes share
36
+ * parametrization so the fused result merges cleanly.
37
+ *
38
+ * This must stay OFF for drafted extrudes: `BRepOffsetAPI_DraftAngle` tilts
39
+ * each side face relative to its own parametrization, so flipping the sweep
40
+ * would invert the draft. Drafted halves are cones of differing half-angle
41
+ * anyway and are not expected to merge across the mid-plane.
42
+ */
43
+ static makePrismFromVec(shape: Shape, vec: Vector3d, canonicalizeSweep?: boolean): {
26
44
  solid: Shape;
27
45
  firstFace: Shape;
28
46
  lastFace: Shape;
29
47
  };
48
+ /**
49
+ * Whether a sweep vector points into the canonical hemisphere (dominant
50
+ * component positive). Anti-parallel vectors map to opposite results, so a
51
+ * `v` / `-v` pair always agree on a single canonical sweep direction — which
52
+ * is what keeps their swept surfaces mergeable. `>=` ties pick the same axis
53
+ * for `v` and `-v`.
54
+ */
55
+ private static isCanonicalSweep;
30
56
  static makePrismInfinite(shape: Shape, direction: Vector3d): Shape;
31
57
  static makePrismSymmetric(shape: Shape, direction: Vector3d): Shape;
32
58
  static makeRevol(shape: Shape, axis: Axis, angle: number): MakeRevolResult;
@@ -1,5 +1,6 @@
1
1
  import { getOC } from "./init.js";
2
2
  import { Convert } from "./convert.js";
3
+ import { Matrix4 } from "../math/matrix4.js";
3
4
  import { Explorer } from "./explorer.js";
4
5
  import { Face } from "../common/face.js";
5
6
  import { ShapeFactory } from "../common/shape-factory.js";
@@ -14,26 +15,74 @@ export class ExtrudeOps {
14
15
  disposeVec();
15
16
  return ShapeFactory.fromShape(result);
16
17
  }
17
- static makePrismFromVec(shape, vec) {
18
+ /**
19
+ * Build a prism by sweeping `shape` along `vec`. `firstFace` is the cap at the
20
+ * profile location, `lastFace` the cap at `profile + vec`.
21
+ *
22
+ * When `canonicalizeSweep` is set, the sweep is reoriented to a canonical
23
+ * direction (see below). The swept lateral surfaces (cylinders, cones from a
24
+ * non-drafted profile) take their axis/parametrization from the sweep
25
+ * direction, and OCCT 8's `ShapeUpgrade_UnifySameDomain` refuses to merge two
26
+ * such faces swept in opposite directions. So a symmetric extrude (one half
27
+ * up, one half down) otherwise fuses into a solid whose lateral surface stays
28
+ * split at the mid-plane. Canonicalizing makes anti-parallel extrudes share
29
+ * parametrization so the fused result merges cleanly.
30
+ *
31
+ * This must stay OFF for drafted extrudes: `BRepOffsetAPI_DraftAngle` tilts
32
+ * each side face relative to its own parametrization, so flipping the sweep
33
+ * would invert the draft. Drafted halves are cones of differing half-angle
34
+ * anyway and are not expected to merge across the mid-plane.
35
+ */
36
+ static makePrismFromVec(shape, vec, canonicalizeSweep = false) {
18
37
  const oc = getOC();
19
- const [gpVec, disposeVec] = Convert.toGpVec(vec);
20
- const prism = new oc.BRepPrimAPI_MakePrism(shape.getShape(), gpVec, true, true);
38
+ // Sweep toward the hemisphere whose dominant component is positive; for a
39
+ // `v` / `-v` pair this resolves to the same direction. When we flip, sweep
40
+ // a copy of the profile translated to the far end, then swap the caps back.
41
+ const flip = canonicalizeSweep && !ExtrudeOps.isCanonicalSweep(vec);
42
+ const sweep = flip ? vec.multiply(-1) : vec;
43
+ const profile = flip
44
+ ? ShapeOps.transform(shape, Matrix4.fromTranslationVector(vec))
45
+ : shape;
46
+ const [gpVec, disposeVec] = Convert.toGpVec(sweep);
47
+ const prism = new oc.BRepPrimAPI_MakePrism(profile.getShape(), gpVec, true, true);
21
48
  if (!prism.IsDone()) {
22
49
  prism.delete();
23
50
  disposeVec();
24
51
  throw new Error("Extrusion failed");
25
52
  }
26
53
  const solid = prism.Shape();
27
- const firstFace = prism.FirstShape();
28
- const lastFace = prism.LastShape();
54
+ // When flipped, the swept profile is the far cap: FirstShape is the far end
55
+ // and LastShape sits at the original profile location — swap them back to
56
+ // preserve the (firstFace = profile, lastFace = far end) contract.
57
+ const first = prism.FirstShape();
58
+ const last = prism.LastShape();
29
59
  prism.delete();
30
60
  disposeVec();
31
61
  return {
32
62
  solid: ShapeFactory.fromShape(solid),
33
- firstFace: ShapeFactory.fromShape(firstFace),
34
- lastFace: ShapeFactory.fromShape(lastFace),
63
+ firstFace: ShapeFactory.fromShape(flip ? last : first),
64
+ lastFace: ShapeFactory.fromShape(flip ? first : last),
35
65
  };
36
66
  }
67
+ /**
68
+ * Whether a sweep vector points into the canonical hemisphere (dominant
69
+ * component positive). Anti-parallel vectors map to opposite results, so a
70
+ * `v` / `-v` pair always agree on a single canonical sweep direction — which
71
+ * is what keeps their swept surfaces mergeable. `>=` ties pick the same axis
72
+ * for `v` and `-v`.
73
+ */
74
+ static isCanonicalSweep(vec) {
75
+ const ax = Math.abs(vec.x);
76
+ const ay = Math.abs(vec.y);
77
+ const az = Math.abs(vec.z);
78
+ if (az >= ax && az >= ay) {
79
+ return vec.z > 0;
80
+ }
81
+ if (ay >= ax) {
82
+ return vec.y > 0;
83
+ }
84
+ return vec.x > 0;
85
+ }
37
86
  static makePrismInfinite(shape, direction) {
38
87
  const oc = getOC();
39
88
  const [vec, disposeVec] = Convert.toGpVec(direction);
@@ -1,4 +1,4 @@
1
- import type { gp_Cylinder, gp_Pln, TopoDS_Face, TopoDS_Wire } from "occjs-wrapper";
1
+ import type { gp_Cone, gp_Cylinder, gp_Pln, TopoDS_Face, TopoDS_Wire } from "ocjs-fluidcad";
2
2
  import { Plane } from "../math/plane.js";
3
3
  import { Point } from "../math/point.js";
4
4
  import { Vector3d } from "../math/vector3d.js";
@@ -28,5 +28,6 @@ export declare class FaceOps {
28
28
  }): TopoDS_Face;
29
29
  static makeFaceFromPlane(plane: gp_Pln): TopoDS_Face;
30
30
  static makeFaceFromCylinder(cylinder: gp_Cylinder): TopoDS_Face;
31
+ static makeFaceFromCone(cone: gp_Cone): TopoDS_Face;
31
32
  static planeToFace(plane: Plane, center?: Point): Face;
32
33
  }
@@ -223,6 +223,17 @@ export class FaceOps {
223
223
  faceMaker.delete();
224
224
  return face;
225
225
  }
226
+ static makeFaceFromCone(cone) {
227
+ const oc = getOC();
228
+ const faceMaker = new oc.BRepBuilderAPI_MakeFace(cone);
229
+ if (!faceMaker.IsDone()) {
230
+ faceMaker.delete();
231
+ throw new Error("Failed to create face from cone");
232
+ }
233
+ const face = faceMaker.Face();
234
+ faceMaker.delete();
235
+ return face;
236
+ }
226
237
  static planeToFace(plane, center) {
227
238
  const oc = getOC();
228
239
  const size = 100;
@@ -1,4 +1,4 @@
1
- import type { TopoDS_Shape } from "occjs-wrapper";
1
+ import type { TopoDS_Shape } from "ocjs-fluidcad";
2
2
  export interface FaceProperties {
3
3
  surfaceType: 'plane' | 'circle' | 'cylinder' | 'sphere' | 'torus' | 'cone' | 'other';
4
4
  areaMm2?: number;
@@ -1,4 +1,4 @@
1
- import type { gp_Pln, gp_Cylinder, TopoDS_Shape } from "occjs-wrapper";
1
+ import type { gp_Pln, gp_Cylinder, gp_Cone, TopoDS_Shape } from "ocjs-fluidcad";
2
2
  import { Vector3d } from "../math/vector3d.js";
3
3
  import { Plane } from "../math/plane.js";
4
4
  import { Shape } from "../common/shape.js";
@@ -20,6 +20,7 @@ export declare class FaceQuery {
20
20
  static findFarthestCornerDistanceFromFace(targetFace: Shape, sourceFace: Shape): number;
21
21
  static makeInfinitePlanarFace(face: Shape, offset?: number, offsetDirection?: Vector3d): Face;
22
22
  static makeInfiniteCylindricalFace(face: Shape, offset?: number): Face;
23
+ static makeInfiniteConicalFace(face: Shape, offset?: number): Face;
23
24
  static isCircleFaceRaw(face: TopoDS_Shape, diameter?: number): boolean;
24
25
  static isConeFaceRaw(face: TopoDS_Shape): boolean;
25
26
  static isCylinderFaceRaw(face: TopoDS_Shape, diameter?: number): boolean;
@@ -31,6 +32,16 @@ export declare class FaceQuery {
31
32
  static getSurfaceTypeRaw(face: TopoDS_Shape): string;
32
33
  static getSurfaceAdaptorPlaneRaw(face: TopoDS_Shape): gp_Pln;
33
34
  static getSurfaceAdaptorCylinderRaw(face: TopoDS_Shape): gp_Cylinder;
35
+ static getSurfaceAdaptorConeRaw(face: TopoDS_Shape): gp_Cone;
36
+ /**
37
+ * Returns the parametric V-bounds of `face`'s underlying surface. For a
38
+ * cylindrical surface V is the axial coordinate; for a conical surface V is
39
+ * along the slant (caller multiplies by `cos(semiAngle)` to get axial Z).
40
+ */
41
+ static getSurfaceVBoundsRaw(face: TopoDS_Shape): {
42
+ vMin: number;
43
+ vMax: number;
44
+ };
34
45
  static findFarthestCornerDistanceRaw(face: TopoDS_Shape, plane: gp_Pln): number;
35
46
  static arePlanesParallelRaw(plane1: gp_Pln, plane2: gp_Pln): boolean;
36
47
  static findFaceByDistance(faces: Face[], plane: Plane, mode: 'first' | 'last'): Face | null;
@@ -102,6 +102,23 @@ export class FaceQuery {
102
102
  cylMaker.delete();
103
103
  return Face.fromTopoDSFace(rawFace);
104
104
  }
105
+ static makeInfiniteConicalFace(face, offset) {
106
+ const cone = FaceQuery.getSurfaceAdaptorConeRaw(face.getShape());
107
+ if (offset) {
108
+ // The parallel surface at normal distance `offset` is the coaxial cone
109
+ // with the same semi-angle and a reference radius enlarged by
110
+ // offset / cos(semiAngle).
111
+ const radius = cone.RefRadius() + offset / Math.cos(cone.SemiAngle());
112
+ if (radius < 0) {
113
+ cone.delete();
114
+ throw new Error("endOffset is too large for the conical target face");
115
+ }
116
+ cone.SetRadius(radius);
117
+ }
118
+ const rawFace = FaceOps.makeFaceFromCone(cone);
119
+ cone.delete();
120
+ return Face.fromTopoDSFace(rawFace);
121
+ }
105
122
  // Raw methods (for oc-internal and common/ use)
106
123
  static isCircleFaceRaw(face, diameter) {
107
124
  const oc = getOC();
@@ -296,6 +313,28 @@ export class FaceQuery {
296
313
  adaptor.delete();
297
314
  return cylinder;
298
315
  }
316
+ static getSurfaceAdaptorConeRaw(face) {
317
+ const oc = getOC();
318
+ const ocFace = oc.TopoDS.Face(face);
319
+ const adaptor = new oc.BRepAdaptor_Surface(ocFace, true);
320
+ const cone = adaptor.Cone();
321
+ adaptor.delete();
322
+ return cone;
323
+ }
324
+ /**
325
+ * Returns the parametric V-bounds of `face`'s underlying surface. For a
326
+ * cylindrical surface V is the axial coordinate; for a conical surface V is
327
+ * along the slant (caller multiplies by `cos(semiAngle)` to get axial Z).
328
+ */
329
+ static getSurfaceVBoundsRaw(face) {
330
+ const oc = getOC();
331
+ const ocFace = oc.TopoDS.Face(face);
332
+ const adaptor = new oc.BRepAdaptor_Surface(ocFace, true);
333
+ const vMin = adaptor.FirstVParameter();
334
+ const vMax = adaptor.LastVParameter();
335
+ adaptor.delete();
336
+ return { vMin, vMax };
337
+ }
299
338
  static findFarthestCornerDistanceRaw(face, plane) {
300
339
  const oc = getOC();
301
340
  const bbox = new oc.Bnd_Box();
@@ -1,4 +1,4 @@
1
- import type { gp_Pln, TopoDS_Wire } from "occjs-wrapper";
1
+ import type { gp_Pln, TopoDS_Wire } from "ocjs-fluidcad";
2
2
  import { Shape } from "../common/shape.js";
3
3
  import { Edge } from "../common/edge.js";
4
4
  import { Face } from "../common/face.js";
@@ -105,7 +105,7 @@ export class FilletOps {
105
105
  const explorer = new oc.BRepTools_WireExplorer(wire);
106
106
  while (explorer.More()) {
107
107
  const raw = oc.TopoDS.Edge(explorer.Current());
108
- const isReversed = raw.Orientation().value === oc.TopAbs_Orientation.TopAbs_REVERSED.value;
108
+ const isReversed = raw.Orientation() === oc.TopAbs_Orientation.TopAbs_REVERSED;
109
109
  if (!isReversed) {
110
110
  wireEdges.push(raw);
111
111
  ownedEdges.push(raw);
@@ -116,13 +116,13 @@ export class FilletOps {
116
116
  const edgeLast = adaptor.LastParameter();
117
117
  adaptor.delete();
118
118
  const curveHandle = oc.BRep_Tool.Curve(raw, 0, 1);
119
- if (!curveHandle || curveHandle.IsNull()) {
119
+ if (!curveHandle) {
120
120
  raw.delete();
121
121
  explorer.delete();
122
122
  ownedEdges.forEach(e => e.delete());
123
123
  throw new Error("fillet2d: edge has no 3D curve");
124
124
  }
125
- const curve = curveHandle.get();
125
+ const curve = curveHandle.returnValue;
126
126
  const reversedHandle = curve.Reversed();
127
127
  const newFirst = curve.ReversedParameter(edgeLast);
128
128
  const newLast = curve.ReversedParameter(edgeFirst);
@@ -130,7 +130,7 @@ export class FilletOps {
130
130
  const newEdge = oc.TopoDS.Edge(maker.Edge());
131
131
  maker.delete();
132
132
  reversedHandle.delete();
133
- curveHandle.delete();
133
+ curve.delete();
134
134
  raw.delete();
135
135
  wireEdges.push(newEdge);
136
136
  ownedEdges.push(newEdge);
@@ -1,4 +1,4 @@
1
- import type { Geom_BezierCurve, Geom_Circle, Geom_TrimmedCurve, TopoDS_Edge } from "occjs-wrapper";
1
+ import type { Geom_BezierCurve, Geom_Circle, Geom_TrimmedCurve, TopoDS_Edge } from "ocjs-fluidcad";
2
2
  import { Point, Point2D } from "../math/point.js";
3
3
  import { Vector3d } from "../math/vector3d.js";
4
4
  import { Edge } from "../common/edge.js";
@@ -16,7 +16,7 @@ export class Geometry {
16
16
  disposeP2();
17
17
  throw new Error("Failed to create segment: " + status);
18
18
  }
19
- const geometry = segmentMaker.Value().get();
19
+ const geometry = segmentMaker.Value();
20
20
  segmentMaker.delete();
21
21
  disposeP1();
22
22
  disposeP2();
@@ -29,7 +29,7 @@ export class Geometry {
29
29
  const [gpP3, disposeP3] = Convert.toGpPnt(p3);
30
30
  const arcMaker = new oc.GC_MakeArcOfCircle(gpStart, gpEnd, gpP3);
31
31
  if (arcMaker.IsDone()) {
32
- const curve = arcMaker.Value().get();
32
+ const curve = arcMaker.Value();
33
33
  arcMaker.delete();
34
34
  disposeStart();
35
35
  disposeEnd();
@@ -54,7 +54,7 @@ export class Geometry {
54
54
  const arcMaker = new oc.GC_MakeArcOfCircle(circle, s, e, true);
55
55
  let curve = null;
56
56
  if (arcMaker.IsDone()) {
57
- curve = arcMaker.Value().get();
57
+ curve = arcMaker.Value();
58
58
  }
59
59
  disposeC();
60
60
  disposeS();
@@ -77,7 +77,7 @@ export class Geometry {
77
77
  const circle = new oc.gp_Circ(ax2, radius);
78
78
  const arcMaker = new oc.GC_MakeArcOfCircle(circle, gpStart, angle, true);
79
79
  if (arcMaker.IsDone()) {
80
- const curve = arcMaker.Value().get();
80
+ const curve = arcMaker.Value();
81
81
  arcMaker.delete();
82
82
  ax2.delete();
83
83
  circle.delete();
@@ -102,7 +102,7 @@ export class Geometry {
102
102
  const [gpEnd, disposeEnd] = Convert.toGpPnt(end);
103
103
  const arcMaker = new oc.GC_MakeArcOfCircle(gpStart, gpTangent, gpEnd);
104
104
  if (arcMaker.IsDone()) {
105
- const curve = arcMaker.Value().get();
105
+ const curve = arcMaker.Value();
106
106
  arcMaker.delete();
107
107
  disposeStart();
108
108
  disposeTangent();
@@ -124,7 +124,7 @@ export class Geometry {
124
124
  const gpCircle = new oc.gp_Circ(ax2, radius);
125
125
  const circleMaker = new oc.GC_MakeCircle(gpCircle);
126
126
  if (circleMaker.IsDone()) {
127
- const circle = circleMaker.Value().get();
127
+ const circle = circleMaker.Value();
128
128
  circleMaker.delete();
129
129
  ax2.delete();
130
130
  gpCircle.delete();
@@ -185,17 +185,16 @@ export class Geometry {
185
185
  }
186
186
  static makeEdgeFromBezier(curve) {
187
187
  const oc = getOC();
188
- const handle = new oc.Handle_Geom_Curve(curve);
189
- const edgeMaker = new oc.BRepBuilderAPI_MakeEdge(handle, curve.StartPoint(), curve.EndPoint());
188
+ // Handles are unwrapped in V8: pass the Geom_Curve straight to MakeEdge
189
+ // (constructing the abstract oc.Geom_Curve base throws at runtime).
190
+ const edgeMaker = new oc.BRepBuilderAPI_MakeEdge(curve, curve.StartPoint(), curve.EndPoint());
190
191
  if (edgeMaker.IsDone()) {
191
192
  const edge = edgeMaker.Edge();
192
193
  edgeMaker.delete();
193
- handle.delete();
194
194
  return Edge.fromTopoDSEdge(edge);
195
195
  }
196
196
  const status = edgeMaker.Error();
197
197
  edgeMaker.delete();
198
- handle.delete();
199
198
  throw new Error('Failed to create edge from bezier curve: ' + status);
200
199
  }
201
200
  // ── Edge factories ─────────────────────────────────────────────────────────
@@ -223,17 +222,16 @@ export class Geometry {
223
222
  }
224
223
  static makeEdgeFromCurveRaw(curve) {
225
224
  const oc = getOC();
226
- const handle = new oc.Handle_Geom_Curve(curve);
227
- const edgeMaker = new oc.BRepBuilderAPI_MakeEdge(handle, curve.StartPoint(), curve.EndPoint());
225
+ // Handles are unwrapped in V8: pass the Geom_Curve straight to MakeEdge
226
+ // (constructing the abstract oc.Geom_Curve base throws at runtime).
227
+ const edgeMaker = new oc.BRepBuilderAPI_MakeEdge(curve, curve.StartPoint(), curve.EndPoint());
228
228
  if (edgeMaker.IsDone()) {
229
229
  const edge = edgeMaker.Edge();
230
230
  edgeMaker.delete();
231
- handle.delete();
232
231
  return edge;
233
232
  }
234
233
  const status = edgeMaker.Error();
235
234
  edgeMaker.delete();
236
- handle.delete();
237
235
  throw new Error('Failed to create edge from arc: ' + status);
238
236
  }
239
237
  static makeEdgeFromCircleRaw(circle) {
@@ -0,0 +1,37 @@
1
+ import { Edge } from "../common/edge.js";
2
+ import { CoordinateSystem } from "../math/coordinate-system.js";
3
+ /**
4
+ * Builds 3D helix edges with OCCT's TKHelix package. The analytic helix
5
+ * (`HelixGeom_HelixCurve`) is approximated as a single B-spline via
6
+ * `HelixGeom_Tools.ApprCurve3D`. Cylindrical and tapered (conical) helixes come
7
+ * from one uniform call — the taper is driven purely by the difference between
8
+ * the start and end radii over the axial range.
9
+ *
10
+ * NB: we deliberately do NOT use `HelixGeom_Tools.ApprHelix`, which hardcodes a
11
+ * 150-segment approximation budget. That is plenty for a cylinder (~3 segments
12
+ * per turn) but far too few for a many-turn tapered helix (~25+ per turn): the
13
+ * single-B-spline fit silently saturates the budget and oscillates wildly over
14
+ * the final turns while still reporting success (returnValue 0, theMaxError in
15
+ * the tens of mm). ApprCurve3D lets us scale the segment budget with the turn
16
+ * count so the adaptive fit can actually reach tolerance.
17
+ */
18
+ export declare class HelixOps {
19
+ private static readonly APPROX_TOLERANCE;
20
+ private static readonly MAX_DEGREE;
21
+ private static readonly SEGMENTS_PER_TURN;
22
+ private static readonly MIN_SEGMENTS;
23
+ private static readonly MAX_SEGMENTS;
24
+ /**
25
+ * Build a helix edge running `turns` full revolutions, climbing along
26
+ * `cs.mainDirection` from axial coordinate `zStart` to `zEnd` and starting at
27
+ * angle 0 (the `cs.xDirection` ray). `startRadius` is the radius at `zStart`;
28
+ * when `endRadius` differs the radius tapers linearly to it at `zEnd`, yielding
29
+ * a conical helix (`endRadius === startRadius` gives a constant-radius cylinder).
30
+ */
31
+ static makeHelix(cs: CoordinateSystem, startRadius: number, endRadius: number, zStart: number, zEnd: number, turns: number): Edge;
32
+ /**
33
+ * Rigidly move an edge built in the canonical frame (axis +Z through the
34
+ * origin) so it sits in `cs`, lifted along `cs.mainDirection` by `zStart`.
35
+ */
36
+ private static placeInFrame;
37
+ }