fluidcad 0.0.35 → 0.0.37

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 (186) hide show
  1. package/LICENSE.txt +21 -504
  2. package/README.md +1 -1
  3. package/bin/commands/login.js +33 -5
  4. package/bin/commands/mcp.js +3 -2
  5. package/bin/commands/publish.js +103 -8
  6. package/bin/lib/api-client.js +8 -0
  7. package/bin/lib/model-config.js +27 -4
  8. package/bin/lib/prompt.js +97 -0
  9. package/lib/dist/common/edge.d.ts +1 -1
  10. package/lib/dist/common/face.d.ts +1 -1
  11. package/lib/dist/common/scene-object.d.ts +6 -0
  12. package/lib/dist/common/scene-object.js +8 -0
  13. package/lib/dist/common/shape-factory.d.ts +1 -1
  14. package/lib/dist/common/shape-history-tracker.d.ts +1 -1
  15. package/lib/dist/common/shape.d.ts +1 -1
  16. package/lib/dist/common/solid.d.ts +1 -1
  17. package/lib/dist/common/transformable-primitive.d.ts +12 -1
  18. package/lib/dist/common/transformable-primitive.js +27 -0
  19. package/lib/dist/common/vertex.d.ts +1 -1
  20. package/lib/dist/common/wire.d.ts +1 -1
  21. package/lib/dist/core/2d/index.d.ts +1 -0
  22. package/lib/dist/core/2d/index.js +1 -0
  23. package/lib/dist/core/2d/text.d.ts +30 -0
  24. package/lib/dist/core/2d/text.js +37 -0
  25. package/lib/dist/core/helix.d.ts +20 -0
  26. package/lib/dist/core/helix.js +36 -0
  27. package/lib/dist/core/index.d.ts +3 -1
  28. package/lib/dist/core/index.js +2 -0
  29. package/lib/dist/core/interfaces.d.ts +180 -0
  30. package/lib/dist/core/wrap.d.ts +17 -0
  31. package/lib/dist/core/wrap.js +39 -0
  32. package/lib/dist/features/2d/text.d.ts +67 -0
  33. package/lib/dist/features/2d/text.js +320 -0
  34. package/lib/dist/features/cylinder.d.ts +3 -1
  35. package/lib/dist/features/cylinder.js +5 -2
  36. package/lib/dist/features/extrude-base.d.ts +1 -0
  37. package/lib/dist/features/extrude-to-face.d.ts +1 -0
  38. package/lib/dist/features/extrude-to-face.js +6 -0
  39. package/lib/dist/features/fillet.d.ts +1 -1
  40. package/lib/dist/features/helix.d.ts +41 -0
  41. package/lib/dist/features/helix.js +337 -0
  42. package/lib/dist/features/select.js +32 -8
  43. package/lib/dist/features/simple-extruder.d.ts +1 -1
  44. package/lib/dist/features/simple-extruder.js +7 -2
  45. package/lib/dist/features/sphere.d.ts +3 -1
  46. package/lib/dist/features/sphere.js +5 -2
  47. package/lib/dist/features/sweep.js +7 -2
  48. package/lib/dist/features/wrap.d.ts +39 -0
  49. package/lib/dist/features/wrap.js +116 -0
  50. package/lib/dist/filters/edge/belongs-to-face.d.ts +3 -1
  51. package/lib/dist/filters/edge/belongs-to-face.js +14 -10
  52. package/lib/dist/filters/filter.d.ts +1 -1
  53. package/lib/dist/filters/from-object.d.ts +1 -1
  54. package/lib/dist/filters/tangent-expander.d.ts +1 -1
  55. package/lib/dist/filters/tangent-expander.js +57 -40
  56. package/lib/dist/helpers/scene-helpers.d.ts +2 -0
  57. package/lib/dist/helpers/scene-helpers.js +1 -1
  58. package/lib/dist/index.d.ts +2 -0
  59. package/lib/dist/index.js +3 -1
  60. package/lib/dist/io/file-import.d.ts +7 -0
  61. package/lib/dist/io/file-import.js +28 -1
  62. package/lib/dist/io/font-registry.d.ts +45 -0
  63. package/lib/dist/io/font-registry.js +272 -0
  64. package/lib/dist/math/bspline-interpolation.d.ts +29 -0
  65. package/lib/dist/math/bspline-interpolation.js +194 -0
  66. package/lib/dist/oc/boolean-ops.d.ts +3 -1
  67. package/lib/dist/oc/boolean-ops.js +15 -1
  68. package/lib/dist/oc/color-transfer.d.ts +1 -1
  69. package/lib/dist/oc/constraints/constraint-helpers.d.ts +4 -4
  70. package/lib/dist/oc/constraints/curve/tangent-circle-solver.js +10 -9
  71. package/lib/dist/oc/constraints/curve/tangent-line-solver.js +5 -6
  72. package/lib/dist/oc/convert.d.ts +1 -1
  73. package/lib/dist/oc/draft-ops.d.ts +1 -1
  74. package/lib/dist/oc/edge-ops.d.ts +2 -2
  75. package/lib/dist/oc/edge-ops.js +13 -14
  76. package/lib/dist/oc/edge-props.d.ts +1 -1
  77. package/lib/dist/oc/edge-query.d.ts +1 -1
  78. package/lib/dist/oc/edge-query.js +3 -8
  79. package/lib/dist/oc/errors.d.ts +8 -0
  80. package/lib/dist/oc/errors.js +27 -0
  81. package/lib/dist/oc/explorer.d.ts +2 -2
  82. package/lib/dist/oc/extrude-ops.d.ts +28 -2
  83. package/lib/dist/oc/extrude-ops.js +56 -7
  84. package/lib/dist/oc/face-ops.d.ts +2 -1
  85. package/lib/dist/oc/face-ops.js +11 -0
  86. package/lib/dist/oc/face-props.d.ts +1 -1
  87. package/lib/dist/oc/face-query.d.ts +12 -1
  88. package/lib/dist/oc/face-query.js +39 -0
  89. package/lib/dist/oc/fillet-ops.d.ts +1 -1
  90. package/lib/dist/oc/fillet-ops.js +4 -4
  91. package/lib/dist/oc/geometry.d.ts +1 -1
  92. package/lib/dist/oc/geometry.js +12 -14
  93. package/lib/dist/oc/helix-ops.d.ts +37 -0
  94. package/lib/dist/oc/helix-ops.js +88 -0
  95. package/lib/dist/oc/hit-test.d.ts +1 -1
  96. package/lib/dist/oc/index.d.ts +4 -0
  97. package/lib/dist/oc/index.js +2 -0
  98. package/lib/dist/oc/init.d.ts +1 -1
  99. package/lib/dist/oc/init.js +1 -1
  100. package/lib/dist/oc/intersection.js +1 -1
  101. package/lib/dist/oc/io.d.ts +6 -6
  102. package/lib/dist/oc/io.js +31 -24
  103. package/lib/dist/oc/measure/classify.d.ts +34 -0
  104. package/lib/dist/oc/measure/classify.js +246 -0
  105. package/lib/dist/oc/measure/measure-ops.d.ts +9 -0
  106. package/lib/dist/oc/measure/measure-ops.js +210 -0
  107. package/lib/dist/oc/measure/measure-types.d.ts +39 -0
  108. package/lib/dist/oc/measure/measure-types.js +1 -0
  109. package/lib/dist/oc/measure/sampling.d.ts +9 -0
  110. package/lib/dist/oc/measure/sampling.js +77 -0
  111. package/lib/dist/oc/measure/vec.d.ts +13 -0
  112. package/lib/dist/oc/measure/vec.js +23 -0
  113. package/lib/dist/oc/mesh.d.ts +1 -1
  114. package/lib/dist/oc/mesh.js +40 -28
  115. package/lib/dist/oc/path-sampler.d.ts +29 -0
  116. package/lib/dist/oc/path-sampler.js +63 -0
  117. package/lib/dist/oc/props.d.ts +1 -1
  118. package/lib/dist/oc/props.js +4 -1
  119. package/lib/dist/oc/shape-hash.d.ts +26 -0
  120. package/lib/dist/oc/shape-hash.js +32 -0
  121. package/lib/dist/oc/shape-ops.d.ts +5 -3
  122. package/lib/dist/oc/shape-ops.js +6 -5
  123. package/lib/dist/oc/sweep-ops.d.ts +22 -1
  124. package/lib/dist/oc/sweep-ops.js +206 -18
  125. package/lib/dist/oc/text-outline.d.ts +62 -0
  126. package/lib/dist/oc/text-outline.js +212 -0
  127. package/lib/dist/oc/topology-index.d.ts +1 -1
  128. package/lib/dist/oc/vertex-ops.d.ts +1 -1
  129. package/lib/dist/oc/wire-ops.d.ts +1 -1
  130. package/lib/dist/oc/wire-ops.js +1 -1
  131. package/lib/dist/oc/wrap-development.d.ts +105 -0
  132. package/lib/dist/oc/wrap-development.js +179 -0
  133. package/lib/dist/oc/wrap-ops.d.ts +100 -0
  134. package/lib/dist/oc/wrap-ops.js +406 -0
  135. package/lib/dist/rendering/render-solid.js +10 -2
  136. package/lib/dist/scene-manager.d.ts +2 -0
  137. package/lib/dist/scene-manager.js +29 -0
  138. package/lib/dist/tests/features/cylinder-curve-filter.test.js +3 -3
  139. package/lib/dist/tests/features/extrude-to-face.test.js +38 -1
  140. package/lib/dist/tests/features/helix.test.d.ts +1 -0
  141. package/lib/dist/tests/features/helix.test.js +295 -0
  142. package/lib/dist/tests/features/repeat-primitive.test.d.ts +1 -0
  143. package/lib/dist/tests/features/repeat-primitive.test.js +60 -0
  144. package/lib/dist/tests/features/rib.test.js +6 -1
  145. package/lib/dist/tests/features/sweep.test.js +125 -1
  146. package/lib/dist/tests/features/text.test.d.ts +1 -0
  147. package/lib/dist/tests/features/text.test.js +347 -0
  148. package/lib/dist/tests/features/wrap-development.test.d.ts +1 -0
  149. package/lib/dist/tests/features/wrap-development.test.js +130 -0
  150. package/lib/dist/tests/features/wrap-extruded-target.test.d.ts +1 -0
  151. package/lib/dist/tests/features/wrap-extruded-target.test.js +106 -0
  152. package/lib/dist/tests/features/wrap-repeat.test.d.ts +1 -0
  153. package/lib/dist/tests/features/wrap-repeat.test.js +93 -0
  154. package/lib/dist/tests/features/wrap.test.d.ts +1 -0
  155. package/lib/dist/tests/features/wrap.test.js +331 -0
  156. package/lib/dist/tests/math/bspline-interpolation.test.d.ts +1 -0
  157. package/lib/dist/tests/math/bspline-interpolation.test.js +119 -0
  158. package/lib/dist/tests/measure.test.d.ts +1 -0
  159. package/lib/dist/tests/measure.test.js +288 -0
  160. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  161. package/llm-docs/api/helix.md +64 -0
  162. package/llm-docs/api/index.json +11 -2
  163. package/llm-docs/api/text.md +52 -0
  164. package/llm-docs/api/types/helix.md +105 -0
  165. package/llm-docs/api/types/text.md +138 -0
  166. package/llm-docs/api/types/wrap.md +131 -0
  167. package/llm-docs/api/wrap.md +62 -0
  168. package/llm-docs/index.json +121 -1
  169. package/mcp/dist/server.js +20 -1
  170. package/mcp/dist/tools/inspection.d.ts +17 -0
  171. package/mcp/dist/tools/inspection.js +14 -0
  172. package/package.json +7 -3
  173. package/server/dist/fluidcad-server.d.ts +29 -0
  174. package/server/dist/fluidcad-server.js +40 -0
  175. package/server/dist/index.js +4 -2
  176. package/server/dist/model-package/pack.js +7 -6
  177. package/server/dist/model-package/types.d.ts +4 -3
  178. package/server/dist/preferences.d.ts +4 -0
  179. package/server/dist/preferences.js +2 -0
  180. package/server/dist/routes/measure.d.ts +3 -0
  181. package/server/dist/routes/measure.js +32 -0
  182. package/server/dist/routes/preferences.js +6 -0
  183. package/server/dist/routes/sketch-edits.js +2 -1
  184. package/ui/dist/assets/{index-CDJmUpFI.css → index-dAFdg2Un.css} +1 -1
  185. package/ui/dist/assets/{index-MRqwG9Vh.js → index-no7mtr5s.js} +149 -102
  186. package/ui/dist/index.html +2 -2
@@ -0,0 +1,88 @@
1
+ import { getOC } from "./init.js";
2
+ import { Convert } from "./convert.js";
3
+ import { Edge } from "../common/edge.js";
4
+ import { CoordinateSystem } from "../math/coordinate-system.js";
5
+ /**
6
+ * Builds 3D helix edges with OCCT's TKHelix package. The analytic helix
7
+ * (`HelixGeom_HelixCurve`) is approximated as a single B-spline via
8
+ * `HelixGeom_Tools.ApprCurve3D`. Cylindrical and tapered (conical) helixes come
9
+ * from one uniform call — the taper is driven purely by the difference between
10
+ * the start and end radii over the axial range.
11
+ *
12
+ * NB: we deliberately do NOT use `HelixGeom_Tools.ApprHelix`, which hardcodes a
13
+ * 150-segment approximation budget. That is plenty for a cylinder (~3 segments
14
+ * per turn) but far too few for a many-turn tapered helix (~25+ per turn): the
15
+ * single-B-spline fit silently saturates the budget and oscillates wildly over
16
+ * the final turns while still reporting success (returnValue 0, theMaxError in
17
+ * the tens of mm). ApprCurve3D lets us scale the segment budget with the turn
18
+ * count so the adaptive fit can actually reach tolerance.
19
+ */
20
+ export class HelixOps {
21
+ // B-spline approximation tolerance for the analytic helix, in mm.
22
+ static APPROX_TOLERANCE = 1e-4;
23
+ // Max B-spline degree for the fit (matches OCCT's ApprHelix default).
24
+ static MAX_DEGREE = 8;
25
+ // Segment budget per turn. A tapered helix needs ~25 segments/turn to reach
26
+ // APPROX_TOLERANCE; 40 leaves headroom. This is only a ceiling — the adaptive
27
+ // fit stops as soon as tolerance is met (a cylinder converges far below it).
28
+ static SEGMENTS_PER_TURN = 40;
29
+ // Floor (OCCT's ApprHelix default, ample for few-turn helixes) and ceiling on
30
+ // the per-build segment budget.
31
+ static MIN_SEGMENTS = 150;
32
+ static MAX_SEGMENTS = 4000;
33
+ /**
34
+ * Build a helix edge running `turns` full revolutions, climbing along
35
+ * `cs.mainDirection` from axial coordinate `zStart` to `zEnd` and starting at
36
+ * angle 0 (the `cs.xDirection` ray). `startRadius` is the radius at `zStart`;
37
+ * when `endRadius` differs the radius tapers linearly to it at `zEnd`, yielding
38
+ * a conical helix (`endRadius === startRadius` gives a constant-radius cylinder).
39
+ */
40
+ static makeHelix(cs, startRadius, endRadius, zStart, zEnd, turns) {
41
+ const oc = getOC();
42
+ const height = zEnd - zStart;
43
+ const pitch = height / turns;
44
+ const taperAngle = Math.atan2(endRadius - startRadius, height);
45
+ const lastAngle = 2 * Math.PI * turns;
46
+ const maxSegments = Math.min(HelixOps.MAX_SEGMENTS, Math.max(HelixOps.MIN_SEGMENTS, Math.ceil(turns) * HelixOps.SEGMENTS_PER_TURN));
47
+ // Build the analytic helix in the canonical frame (axis +Z through the
48
+ // origin, angle 0 on +X) and approximate it as one B-spline spanning every
49
+ // turn. ApprCurve3D (not ApprHelix) so the segment budget scales with turns.
50
+ const adaptor = new oc.HelixGeom_HelixCurve();
51
+ adaptor.Load(0, lastAngle, pitch, startRadius, taperAngle, false);
52
+ const appr = oc.HelixGeom_Tools.ApprCurve3D(adaptor, HelixOps.APPROX_TOLERANCE, oc.GeomAbs_Shape.GeomAbs_C2, maxSegments, HelixOps.MAX_DEGREE);
53
+ if (appr.returnValue !== 0) {
54
+ appr[Symbol.dispose]();
55
+ adaptor.delete();
56
+ throw new Error(`HelixOps: helix approximation failed (status ${appr.returnValue}).`);
57
+ }
58
+ const edgeMaker = new oc.BRepBuilderAPI_MakeEdge(appr.theBSpl);
59
+ const canonicalEdge = edgeMaker.Edge();
60
+ // Relocate the canonical helix into the target frame (copies the geometry, so
61
+ // the result is independent of the approximation envelope released below).
62
+ const placedEdge = HelixOps.placeInFrame(canonicalEdge, cs, zStart);
63
+ edgeMaker.delete();
64
+ canonicalEdge.delete();
65
+ appr[Symbol.dispose]();
66
+ adaptor.delete();
67
+ return Edge.fromTopoDSEdge(placedEdge);
68
+ }
69
+ /**
70
+ * Rigidly move an edge built in the canonical frame (axis +Z through the
71
+ * origin) so it sits in `cs`, lifted along `cs.mainDirection` by `zStart`.
72
+ */
73
+ static placeInFrame(edge, cs, zStart) {
74
+ const oc = getOC();
75
+ const origin = cs.origin.add(cs.mainDirection.normalize().multiply(zStart));
76
+ const placedCs = new CoordinateSystem(origin, cs.mainDirection, cs.xDirection);
77
+ const [ax3, disposeAx3] = Convert.toGpAx3(placedCs);
78
+ const trsf = new oc.gp_Trsf();
79
+ trsf.SetTransformation(ax3); // global coords → target frame …
80
+ trsf.Invert(); // … inverted places canonical geometry at the target frame
81
+ const transform = new oc.BRepBuilderAPI_Transform(edge, trsf, true);
82
+ const placedEdge = oc.TopoDS.Edge(transform.Shape());
83
+ transform.delete();
84
+ trsf.delete();
85
+ disposeAx3();
86
+ return placedEdge;
87
+ }
88
+ }
@@ -1,4 +1,4 @@
1
- import type { TopoDS_Shape } from "occjs-wrapper";
1
+ import type { TopoDS_Shape } from "ocjs-fluidcad";
2
2
  export type HitTestResult = {
3
3
  type: 'face';
4
4
  index: number;
@@ -22,4 +22,8 @@ export { OcIO } from "./io.js";
22
22
  export { ShapeProps } from './props.js';
23
23
  export { SweepOps } from "./sweep-ops.js";
24
24
  export { SectionOps } from "./section-ops.js";
25
+ export { HelixOps } from "./helix-ops.js";
25
26
  export type { ShapeProperties } from './props.js';
27
+ export { MeasureOps } from "./measure/measure-ops.js";
28
+ export type { MeasureInput } from "./measure/measure-ops.js";
29
+ export type { MeasureResult, MeasureEntityRef, MeasureEntityInfo, MeasureEntityKind, MeasureDistanceValue, MeasurePrimaryKey, MeasureVec, } from "./measure/measure-types.js";
@@ -20,3 +20,5 @@ export { OcIO } from "./io.js";
20
20
  export { ShapeProps } from './props.js';
21
21
  export { SweepOps } from "./sweep-ops.js";
22
22
  export { SectionOps } from "./section-ops.js";
23
+ export { HelixOps } from "./helix-ops.js";
24
+ export { MeasureOps } from "./measure/measure-ops.js";
@@ -1,3 +1,3 @@
1
- import type { OpenCascadeInstance } from "occjs-wrapper/node/multi-threaded";
1
+ import type { OpenCascadeInstance } from "ocjs-fluidcad";
2
2
  export declare function getOC(): OpenCascadeInstance;
3
3
  export declare function loadOC(): Promise<OpenCascadeInstance>;
@@ -1,4 +1,4 @@
1
- import initOpenCascade from "occjs-wrapper/node/multi-threaded";
1
+ import initOpenCascade from "ocjs-fluidcad";
2
2
  let oc = null;
3
3
  export function getOC() {
4
4
  if (!oc) {
@@ -20,7 +20,7 @@ export class ProjectionOps {
20
20
  let wirePlane;
21
21
  if (wirePlaneFinder.Found()) {
22
22
  const handle = wirePlaneFinder.Plane();
23
- const geomPln = handle.get();
23
+ const geomPln = handle;
24
24
  const pln = geomPln.Pln();
25
25
  wirePlane = Convert.toPlane(pln);
26
26
  // geomPln.delete();
@@ -1,4 +1,4 @@
1
- import type { Handle_TDocStd_Document, TopoDS_Shape } from "occjs-wrapper";
1
+ import type { TDocStd_Document, TopoDS_Shape } from "ocjs-fluidcad";
2
2
  import { Shape } from "../common/shape.js";
3
3
  import { Solid } from "../common/solid.js";
4
4
  import { Face } from "../common/face.js";
@@ -11,7 +11,7 @@ export declare class OcIO {
11
11
  static makeCompound(shapes: Shape[]): Shape;
12
12
  static readBRepSolids(fileName: string, content: string): Solid[];
13
13
  static writeSolidsAsBRep(solids: Solid[], fileName: string): string;
14
- static extractSolidsAndColors(docHandle: Handle_TDocStd_Document): {
14
+ static extractSolidsAndColors(docHandle: TDocStd_Document): {
15
15
  solids: Array<{
16
16
  shape: Solid;
17
17
  faceColors: Array<{
@@ -24,14 +24,14 @@ export declare class OcIO {
24
24
  static writeBRepRaw(shape: TopoDS_Shape, fileName: string): string;
25
25
  static readStepRaw(fileName: string, data: Uint8Array): TopoDS_Shape;
26
26
  static readStepXCAF(fileName: string, data: Uint8Array): {
27
- docHandle: Handle_TDocStd_Document;
27
+ docHandle: TDocStd_Document;
28
28
  cleanup: () => void;
29
29
  };
30
- static castToSolid(shape: TopoDS_Shape): import("occjs-wrapper").TopoDS_Solid;
30
+ static castToSolid(shape: TopoDS_Shape): import("ocjs-fluidcad").TopoDS_Solid;
31
31
  static findSolidsRaw(shape: TopoDS_Shape): TopoDS_Shape[];
32
32
  static findFacesRaw(shape: TopoDS_Shape): TopoDS_Shape[];
33
- static makeCompoundRaw(shapes: TopoDS_Shape[]): import("occjs-wrapper").TopoDS_Compound;
34
- static extractSolidsAndColorsRaw(docHandle: Handle_TDocStd_Document): {
33
+ static makeCompoundRaw(shapes: TopoDS_Shape[]): import("ocjs-fluidcad").TopoDS_Compound;
34
+ static extractSolidsAndColorsRaw(docHandle: TDocStd_Document): {
35
35
  solids: Array<{
36
36
  shape: TopoDS_Shape;
37
37
  faceColors: Array<{
package/lib/dist/oc/io.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { getOC } from "./init.js";
2
+ import { ShapeHasher } from "./shape-hash.js";
2
3
  import { Explorer } from "./explorer.js";
3
4
  import { Solid } from "../common/solid.js";
4
5
  import { Face } from "../common/face.js";
@@ -79,7 +80,7 @@ export class OcIO {
79
80
  static readStepRaw(fileName, data) {
80
81
  const oc = getOC();
81
82
  const uint8 = new Uint8Array(data);
82
- oc.FS.createDataFile("/", fileName, uint8, true, true, true);
83
+ oc.FS.writeFile("/" + fileName, uint8);
83
84
  const reader = new oc.STEPControl_Reader();
84
85
  const readResult = reader.ReadFile(fileName);
85
86
  if (readResult !== oc.IFSelect_ReturnStatus.IFSelect_RetDone) {
@@ -98,11 +99,11 @@ export class OcIO {
98
99
  static readStepXCAF(fileName, data) {
99
100
  const oc = getOC();
100
101
  const uint8 = new Uint8Array(data);
101
- oc.FS.createDataFile("/", fileName, uint8, true, true, true);
102
+ oc.FS.writeFile("/" + fileName, uint8);
102
103
  const app = new oc.TDocStd_Application();
103
- const docHandle = new oc.Handle_TDocStd_Document();
104
104
  const format = new oc.TCollection_ExtendedString('MDTV-XCAF');
105
- app.NewDocument(format, docHandle);
105
+ const docHandle = new oc.TDocStd_Document(format);
106
+ app.InitDocument(docHandle);
106
107
  format.delete();
107
108
  const reader = new oc.STEPCAFControl_Reader();
108
109
  reader.SetColorMode(true);
@@ -132,7 +133,10 @@ export class OcIO {
132
133
  const cleanup = () => {
133
134
  reader.delete();
134
135
  oc.FS.unlink(fileName);
135
- app.Close(docHandle);
136
+ // No app.Close(): the doc was created via `new TDocStd_Document` +
137
+ // InitDocument, which (in OCCT V8) does NOT open it in the application
138
+ // session. Close() therefore throws "cannot close a document that has not
139
+ // been opened"; deleting the handle releases it.
136
140
  docHandle.delete();
137
141
  app.delete();
138
142
  };
@@ -161,11 +165,11 @@ export class OcIO {
161
165
  }
162
166
  static extractSolidsAndColorsRaw(docHandle) {
163
167
  const oc = getOC();
164
- const doc = docHandle.get();
168
+ const doc = docHandle;
165
169
  const shapeToolHandle = oc.XCAFDoc_DocumentTool.ShapeTool(doc.Main());
166
170
  const colorToolHandle = oc.XCAFDoc_DocumentTool.ColorTool(doc.Main());
167
- const shapeTool = shapeToolHandle.get();
168
- const colorTool = colorToolHandle.get();
171
+ const shapeTool = shapeToolHandle;
172
+ const colorTool = colorToolHandle;
169
173
  const surfType = oc.XCAFDoc_ColorType.XCAFDoc_ColorSurf;
170
174
  const colorLabels = new oc.TDF_LabelSequence();
171
175
  colorTool.GetColors(colorLabels);
@@ -173,6 +177,7 @@ export class OcIO {
173
177
  colorLabels.delete();
174
178
  const faceColorByHash = new Map();
175
179
  const solidColorByHash = new Map();
180
+ const shapeHasher = new ShapeHasher();
176
181
  const allLabels = new oc.TDF_LabelSequence();
177
182
  shapeTool.GetShapes(allLabels);
178
183
  let simpleShapeCount = 0;
@@ -184,15 +189,15 @@ export class OcIO {
184
189
  simpleShapeCount++;
185
190
  const shape = oc.XCAFDoc_ShapeTool.GetShape(label);
186
191
  const labelColor = new oc.Quantity_Color();
187
- if (colorTool.GetColor(label, surfType, labelColor)) {
192
+ if (oc.XCAFDoc_ColorTool.GetColor(label, surfType, labelColor)) {
188
193
  const hex = OcIO.rgbToHex(labelColor.Red(), labelColor.Green(), labelColor.Blue());
189
194
  const labelSolids = OcIO.findSolidsRaw(shape);
190
195
  for (const s of labelSolids) {
191
- solidColorByHash.set(s.HashCode(2147483647), hex);
196
+ solidColorByHash.set(shapeHasher.key(s), hex);
192
197
  }
193
198
  const labelFaces = OcIO.findFacesRaw(shape);
194
199
  for (const f of labelFaces) {
195
- faceColorByHash.set(f.HashCode(2147483647), hex);
200
+ faceColorByHash.set(shapeHasher.key(f), hex);
196
201
  }
197
202
  }
198
203
  labelColor.delete();
@@ -201,9 +206,9 @@ export class OcIO {
201
206
  for (let j = 1; j <= subLabels.Length(); j++) {
202
207
  const subLabel = subLabels.Value(j);
203
208
  const subColor = new oc.Quantity_Color();
204
- if (colorTool.GetColor(subLabel, surfType, subColor)) {
209
+ if (oc.XCAFDoc_ColorTool.GetColor(subLabel, surfType, subColor)) {
205
210
  const subShape = oc.XCAFDoc_ShapeTool.GetShape(subLabel);
206
- faceColorByHash.set(subShape.HashCode(2147483647), OcIO.rgbToHex(subColor.Red(), subColor.Green(), subColor.Blue()));
211
+ faceColorByHash.set(shapeHasher.key(subShape), OcIO.rgbToHex(subColor.Red(), subColor.Green(), subColor.Blue()));
207
212
  subShapeColorCount++;
208
213
  }
209
214
  subColor.delete();
@@ -224,7 +229,7 @@ export class OcIO {
224
229
  const faceColors = [];
225
230
  for (let fi = 0; fi < faces.length; fi++) {
226
231
  const face = faces[fi];
227
- const color = OcIO.findFaceColor(face, solidShape, colorTool, shapeTool, oc, surfType, faceColorByHash, solidColorByHash);
232
+ const color = OcIO.findFaceColor(face, solidShape, colorTool, shapeTool, oc, surfType, faceColorByHash, solidColorByHash, shapeHasher);
228
233
  if (color) {
229
234
  faceColors.push({ faceIndex: fi, color });
230
235
  }
@@ -235,6 +240,7 @@ export class OcIO {
235
240
  freeLabels.delete();
236
241
  shapeToolHandle.delete();
237
242
  colorToolHandle.delete();
243
+ shapeHasher.delete();
238
244
  return { solids };
239
245
  }
240
246
  static writeStepRaw(compound, fileName) {
@@ -260,15 +266,15 @@ export class OcIO {
260
266
  static writeStepXCAF(solids, fileName) {
261
267
  const oc = getOC();
262
268
  const app = new oc.TDocStd_Application();
263
- const docHandle = new oc.Handle_TDocStd_Document();
264
269
  const format = new oc.TCollection_ExtendedString('MDTV-XCAF');
265
- app.NewDocument(format, docHandle);
270
+ const docHandle = new oc.TDocStd_Document(format);
271
+ app.InitDocument(docHandle);
266
272
  format.delete();
267
- const doc = docHandle.get();
273
+ const doc = docHandle;
268
274
  const shapeToolHandle = oc.XCAFDoc_DocumentTool.ShapeTool(doc.Main());
269
275
  const colorToolHandle = oc.XCAFDoc_DocumentTool.ColorTool(doc.Main());
270
- const shapeTool = shapeToolHandle.get();
271
- const colorTool = colorToolHandle.get();
276
+ const shapeTool = shapeToolHandle;
277
+ const colorTool = colorToolHandle;
272
278
  const surfType = oc.XCAFDoc_ColorType.XCAFDoc_ColorSurf;
273
279
  // Use NewShape + SetShape (per OCC docs) instead of AddShape to avoid
274
280
  // compound wrapping that triggers multi-file assembly output.
@@ -286,7 +292,8 @@ export class OcIO {
286
292
  const cleanup = () => {
287
293
  shapeToolHandle.delete();
288
294
  colorToolHandle.delete();
289
- app.Close(docHandle);
295
+ // No app.Close(): see readStepXCAF — an InitDocument'd (never opened) doc
296
+ // cannot be closed in OCCT V8; deleting the handle releases it.
290
297
  docHandle.delete();
291
298
  };
292
299
  const writer = new oc.STEPCAFControl_Writer();
@@ -364,8 +371,8 @@ export class OcIO {
364
371
  parseInt(h.substring(4, 6), 16) / 255,
365
372
  ];
366
373
  }
367
- static findFaceColor(face, solidShape, colorTool, shapeTool, oc, surfType, faceColorByHash, solidColorByHash) {
368
- const hashColor = faceColorByHash.get(face.HashCode(2147483647));
374
+ static findFaceColor(face, solidShape, colorTool, shapeTool, oc, surfType, faceColorByHash, solidColorByHash, shapeHasher) {
375
+ const hashColor = faceColorByHash.get(shapeHasher.key(face));
369
376
  if (hashColor)
370
377
  return hashColor;
371
378
  const color = new oc.Quantity_Color();
@@ -390,14 +397,14 @@ export class OcIO {
390
397
  identityLoc.delete();
391
398
  const faceLabel = new oc.TDF_Label();
392
399
  if (shapeTool.SearchUsingMap(face, faceLabel, true, true)) {
393
- if (colorTool.GetColor(faceLabel, surfType, color)) {
400
+ if (oc.XCAFDoc_ColorTool.GetColor(faceLabel, surfType, color)) {
394
401
  const hex = OcIO.rgbToHex(color.Red(), color.Green(), color.Blue());
395
402
  color.delete();
396
403
  return hex;
397
404
  }
398
405
  }
399
406
  color.delete();
400
- const solidHash = solidColorByHash.get(solidShape.HashCode(2147483647));
407
+ const solidHash = solidColorByHash.get(shapeHasher.key(solidShape));
401
408
  if (solidHash)
402
409
  return solidHash;
403
410
  const solidColor = new oc.Quantity_Color();
@@ -0,0 +1,34 @@
1
+ import type { TopoDS_Shape } from "ocjs-fluidcad";
2
+ import type { MeasureEntityKind, MeasureVec } from "./measure-types.js";
3
+ export type FaceForm = 'plane' | 'cylinder' | 'cone' | 'sphere' | 'torus' | 'surface';
4
+ export type EdgeForm = 'line' | 'circle' | 'arc' | 'ellipse' | 'curve';
5
+ /**
6
+ * Geometric summary of a selected face or edge, in plain JS values so all the
7
+ * pair math (angles, parallel distances, projections) runs without further
8
+ * OCCT calls.
9
+ */
10
+ export interface ClassifiedEntity {
11
+ kind: MeasureEntityKind;
12
+ form: FaceForm | EdgeForm;
13
+ /** Unit direction characterizing the entity, or null when it has none. */
14
+ dir: MeasureVec | null;
15
+ /**
16
+ * How `dir` relates to the entity: a 'normal' is perpendicular to the entity
17
+ * (plane normal, circle-plane normal, torus axis), an 'axis' runs along it
18
+ * (line direction, cylinder/cone axis). Two entities lie parallel when
19
+ * normal∥normal, axis∥axis, or normal⊥axis.
20
+ */
21
+ dirKind: 'normal' | 'axis' | null;
22
+ /** A point on the entity's defining element (plane location, axis location). */
23
+ point: MeasureVec | null;
24
+ /** True geometric center (circle/arc/ellipse/sphere/torus), else null. */
25
+ center: MeasureVec | null;
26
+ /** Representative point on the entity (face area centroid, edge midpoint) used to anchor measurement lines. */
27
+ anchor: MeasureVec;
28
+ radius?: number;
29
+ area?: number;
30
+ length?: number;
31
+ shape: TopoDS_Shape;
32
+ }
33
+ export declare function classifyFace(shape: TopoDS_Shape): ClassifiedEntity;
34
+ export declare function classifyEdge(shape: TopoDS_Shape): ClassifiedEntity;
@@ -0,0 +1,246 @@
1
+ import { getOC } from "../init.js";
2
+ import { cross, dot, len, scale, sub } from "./vec.js";
3
+ function vecFromPnt(p) {
4
+ const v = { x: p.X(), y: p.Y(), z: p.Z() };
5
+ p.delete();
6
+ return v;
7
+ }
8
+ function vecFromDir(d) {
9
+ const v = { x: d.X(), y: d.Y(), z: d.Z() };
10
+ d.delete();
11
+ return v;
12
+ }
13
+ function axisData(axis) {
14
+ const data = { point: vecFromPnt(axis.Location()), dir: vecFromDir(axis.Direction()) };
15
+ axis.delete();
16
+ return data;
17
+ }
18
+ // Booleans, text outlines, wraps and lofts often carry geometrically planar
19
+ // faces / straight edges on fitted B-spline (or extruded-curve) geometry, so
20
+ // the adaptor type alone misses them. These sampling fallbacks recover the
21
+ // carrier plane/line numerically. (OCCT's GeomLib_IsPlanarSurface would do the
22
+ // face check natively but is not in the current ocjs binding.)
23
+ const CARRIER_FIT_TOL = 1e-6;
24
+ const PLANAR_GRID_STEPS = 5;
25
+ const STRAIGHT_EDGE_STEPS = 8;
26
+ function detectPlanarSurface(adaptor) {
27
+ const u1 = adaptor.FirstUParameter();
28
+ const u2 = adaptor.LastUParameter();
29
+ const v1 = adaptor.FirstVParameter();
30
+ const v2 = adaptor.LastVParameter();
31
+ if (!isFinite(u1) || !isFinite(u2) || !isFinite(v1) || !isFinite(v2)) {
32
+ return null;
33
+ }
34
+ const points = [];
35
+ for (let i = 0; i <= PLANAR_GRID_STEPS; i++) {
36
+ for (let j = 0; j <= PLANAR_GRID_STEPS; j++) {
37
+ const p = adaptor.Value(u1 + ((u2 - u1) * i) / PLANAR_GRID_STEPS, v1 + ((v2 - v1) * j) / PLANAR_GRID_STEPS);
38
+ points.push({ x: p.X(), y: p.Y(), z: p.Z() });
39
+ p.delete();
40
+ }
41
+ }
42
+ const origin = points[0];
43
+ let span = points[0];
44
+ let spanDist = 0;
45
+ for (const p of points) {
46
+ const d = len(sub(p, origin));
47
+ if (d > spanDist) {
48
+ spanDist = d;
49
+ span = p;
50
+ }
51
+ }
52
+ if (spanDist < CARRIER_FIT_TOL) {
53
+ return null;
54
+ }
55
+ const e1 = scale(sub(span, origin), 1 / spanDist);
56
+ let normal = null;
57
+ let normalLen = 0;
58
+ for (const p of points) {
59
+ const n = cross(e1, sub(p, origin));
60
+ const nLen = len(n);
61
+ if (nLen > normalLen) {
62
+ normalLen = nLen;
63
+ normal = n;
64
+ }
65
+ }
66
+ if (!normal || normalLen < CARRIER_FIT_TOL * spanDist) {
67
+ return null;
68
+ }
69
+ const dir = scale(normal, 1 / normalLen);
70
+ const tol = CARRIER_FIT_TOL * (1 + spanDist);
71
+ for (const p of points) {
72
+ if (Math.abs(dot(sub(p, origin), dir)) > tol) {
73
+ return null;
74
+ }
75
+ }
76
+ return { point: origin, dir };
77
+ }
78
+ function detectStraightCurve(adaptor, first, last) {
79
+ const points = [];
80
+ for (let i = 0; i <= STRAIGHT_EDGE_STEPS; i++) {
81
+ const p = adaptor.Value(first + ((last - first) * i) / STRAIGHT_EDGE_STEPS);
82
+ points.push({ x: p.X(), y: p.Y(), z: p.Z() });
83
+ p.delete();
84
+ }
85
+ const chord = sub(points[points.length - 1], points[0]);
86
+ const chordLen = len(chord);
87
+ if (chordLen < CARRIER_FIT_TOL) {
88
+ return null;
89
+ }
90
+ const dir = scale(chord, 1 / chordLen);
91
+ const tol = CARRIER_FIT_TOL * (1 + chordLen);
92
+ for (const p of points) {
93
+ if (len(cross(sub(p, points[0]), dir)) > tol) {
94
+ return null;
95
+ }
96
+ }
97
+ return { point: points[0], dir };
98
+ }
99
+ export function classifyFace(shape) {
100
+ const oc = getOC();
101
+ const face = oc.TopoDS.Face(shape);
102
+ const props = new oc.GProp_GProps();
103
+ oc.BRepGProp.SurfaceProperties(face, props, false, false);
104
+ const area = props.Mass();
105
+ const anchor = vecFromPnt(props.CentreOfMass());
106
+ props.delete();
107
+ const result = {
108
+ kind: 'face',
109
+ form: 'surface',
110
+ dir: null,
111
+ dirKind: null,
112
+ point: null,
113
+ center: null,
114
+ anchor,
115
+ area,
116
+ shape,
117
+ };
118
+ const adaptor = new oc.BRepAdaptor_Surface(face, true);
119
+ const type = adaptor.GetType();
120
+ if (type === oc.GeomAbs_SurfaceType.GeomAbs_Plane) {
121
+ const plane = adaptor.Plane();
122
+ const { point, dir } = axisData(plane.Axis());
123
+ plane.delete();
124
+ result.form = 'plane';
125
+ result.point = point;
126
+ result.dir = dir;
127
+ result.dirKind = 'normal';
128
+ }
129
+ else if (type === oc.GeomAbs_SurfaceType.GeomAbs_Cylinder) {
130
+ const cylinder = adaptor.Cylinder();
131
+ const { point, dir } = axisData(cylinder.Axis());
132
+ result.radius = cylinder.Radius();
133
+ cylinder.delete();
134
+ result.form = 'cylinder';
135
+ result.point = point;
136
+ result.dir = dir;
137
+ result.dirKind = 'axis';
138
+ }
139
+ else if (type === oc.GeomAbs_SurfaceType.GeomAbs_Cone) {
140
+ const cone = adaptor.Cone();
141
+ const { point, dir } = axisData(cone.Axis());
142
+ cone.delete();
143
+ result.form = 'cone';
144
+ result.point = point;
145
+ result.dir = dir;
146
+ result.dirKind = 'axis';
147
+ }
148
+ else if (type === oc.GeomAbs_SurfaceType.GeomAbs_Sphere) {
149
+ const sphere = adaptor.Sphere();
150
+ const center = vecFromPnt(sphere.Location());
151
+ result.radius = sphere.Radius();
152
+ sphere.delete();
153
+ result.form = 'sphere';
154
+ result.point = center;
155
+ result.center = center;
156
+ }
157
+ else if (type === oc.GeomAbs_SurfaceType.GeomAbs_Torus) {
158
+ const torus = adaptor.Torus();
159
+ const { point, dir } = axisData(torus.Axis());
160
+ torus.delete();
161
+ result.form = 'torus';
162
+ result.point = point;
163
+ result.center = point;
164
+ result.dir = dir;
165
+ result.dirKind = 'normal';
166
+ }
167
+ else {
168
+ const planar = detectPlanarSurface(adaptor);
169
+ if (planar) {
170
+ result.form = 'plane';
171
+ result.point = planar.point;
172
+ result.dir = planar.dir;
173
+ result.dirKind = 'normal';
174
+ }
175
+ }
176
+ adaptor.delete();
177
+ return result;
178
+ }
179
+ export function classifyEdge(shape) {
180
+ const oc = getOC();
181
+ const edge = oc.TopoDS.Edge(shape);
182
+ const props = new oc.GProp_GProps();
183
+ oc.BRepGProp.LinearProperties(edge, props, false, false);
184
+ const length = props.Mass();
185
+ props.delete();
186
+ const adaptor = new oc.BRepAdaptor_Curve(edge);
187
+ const first = adaptor.FirstParameter();
188
+ const last = adaptor.LastParameter();
189
+ const anchor = vecFromPnt(adaptor.Value((first + last) / 2));
190
+ const result = {
191
+ kind: 'edge',
192
+ form: 'curve',
193
+ dir: null,
194
+ dirKind: null,
195
+ point: null,
196
+ center: null,
197
+ anchor,
198
+ length,
199
+ shape,
200
+ };
201
+ const type = adaptor.GetType();
202
+ if (type === oc.GeomAbs_CurveType.GeomAbs_Line) {
203
+ const line = adaptor.Line();
204
+ const point = vecFromPnt(line.Location());
205
+ const dir = vecFromDir(line.Direction());
206
+ line.delete();
207
+ result.form = 'line';
208
+ result.point = point;
209
+ result.dir = dir;
210
+ result.dirKind = 'axis';
211
+ }
212
+ else if (type === oc.GeomAbs_CurveType.GeomAbs_Circle) {
213
+ const circle = adaptor.Circle();
214
+ const center = vecFromPnt(circle.Location());
215
+ const { dir } = axisData(circle.Axis());
216
+ result.radius = circle.Radius();
217
+ circle.delete();
218
+ result.form = adaptor.IsClosed() ? 'circle' : 'arc';
219
+ result.point = center;
220
+ result.center = center;
221
+ result.dir = dir;
222
+ result.dirKind = 'normal';
223
+ }
224
+ else if (type === oc.GeomAbs_CurveType.GeomAbs_Ellipse) {
225
+ const ellipse = adaptor.Ellipse();
226
+ const center = vecFromPnt(ellipse.Location());
227
+ const { dir } = axisData(ellipse.Axis());
228
+ ellipse.delete();
229
+ result.form = 'ellipse';
230
+ result.point = center;
231
+ result.center = center;
232
+ result.dir = dir;
233
+ result.dirKind = 'normal';
234
+ }
235
+ else {
236
+ const straight = detectStraightCurve(adaptor, first, last);
237
+ if (straight) {
238
+ result.form = 'line';
239
+ result.point = straight.point;
240
+ result.dir = straight.dir;
241
+ result.dirKind = 'axis';
242
+ }
243
+ }
244
+ adaptor.delete();
245
+ return result;
246
+ }
@@ -0,0 +1,9 @@
1
+ import type { TopoDS_Shape } from "ocjs-fluidcad";
2
+ import type { MeasureEntityRef, MeasureResult } from "./measure-types.js";
3
+ export interface MeasureInput {
4
+ ref: MeasureEntityRef;
5
+ shape: TopoDS_Shape;
6
+ }
7
+ export declare class MeasureOps {
8
+ static measure(inputs: MeasureInput[]): MeasureResult;
9
+ }