fluidcad 0.0.32 → 0.0.34

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 (279) hide show
  1. package/README.md +3 -2
  2. package/bin/commands/init.js +55 -0
  3. package/bin/commands/mcp.js +33 -0
  4. package/bin/commands/serve.js +77 -0
  5. package/bin/fluidcad.js +15 -107
  6. package/lib/dist/common/scene-object.d.ts +4 -1
  7. package/lib/dist/common/scene-object.js +9 -2
  8. package/lib/dist/common/solid.d.ts +4 -1
  9. package/lib/dist/common/solid.js +13 -0
  10. package/lib/dist/core/2d/tarc.d.ts +20 -2
  11. package/lib/dist/core/2d/tarc.js +24 -0
  12. package/lib/dist/core/index.d.ts +2 -1
  13. package/lib/dist/core/index.js +1 -0
  14. package/lib/dist/core/interfaces.d.ts +107 -2
  15. package/lib/dist/core/load.d.ts +2 -2
  16. package/lib/dist/core/repeat.js +62 -46
  17. package/lib/dist/core/rib.d.ts +18 -0
  18. package/lib/dist/core/rib.js +37 -0
  19. package/lib/dist/features/2d/arc.d.ts +8 -2
  20. package/lib/dist/features/2d/arc.js +94 -17
  21. package/lib/dist/features/2d/back.js +3 -2
  22. package/lib/dist/features/2d/sketch.d.ts +4 -0
  23. package/lib/dist/features/2d/sketch.js +21 -0
  24. package/lib/dist/features/2d/tarc-constrained.d.ts +2 -0
  25. package/lib/dist/features/2d/tarc-constrained.js +8 -0
  26. package/lib/dist/features/2d/tarc-radius-to-object.d.ts +16 -0
  27. package/lib/dist/features/2d/tarc-radius-to-object.js +58 -0
  28. package/lib/dist/features/2d/tarc-to-object.d.ts +18 -0
  29. package/lib/dist/features/2d/tarc-to-object.js +66 -0
  30. package/lib/dist/features/2d/tarc-to-point-tangent.d.ts +2 -0
  31. package/lib/dist/features/2d/tarc-to-point-tangent.js +3 -0
  32. package/lib/dist/features/2d/tarc-to-point.d.ts +2 -0
  33. package/lib/dist/features/2d/tarc-to-point.js +3 -0
  34. package/lib/dist/features/2d/tarc-with-tangent.d.ts +2 -0
  35. package/lib/dist/features/2d/tarc-with-tangent.js +3 -0
  36. package/lib/dist/features/2d/tarc.d.ts +2 -0
  37. package/lib/dist/features/2d/tarc.js +3 -0
  38. package/lib/dist/features/extrude-base.d.ts +9 -0
  39. package/lib/dist/features/extrude-base.js +22 -0
  40. package/lib/dist/features/extrude-to-face.js +1 -5
  41. package/lib/dist/features/extrude-two-distances.js +1 -2
  42. package/lib/dist/features/extrude.js +1 -2
  43. package/lib/dist/features/load.d.ts +6 -0
  44. package/lib/dist/features/load.js +53 -1
  45. package/lib/dist/features/mirror-feature.d.ts +3 -2
  46. package/lib/dist/features/mirror-feature.js +1 -1
  47. package/lib/dist/features/repeat-circular.d.ts +3 -3
  48. package/lib/dist/features/repeat-circular.js +8 -1
  49. package/lib/dist/features/repeat-linear.d.ts +4 -2
  50. package/lib/dist/features/repeat-linear.js +10 -1
  51. package/lib/dist/features/repeat-matrix.d.ts +3 -1
  52. package/lib/dist/features/repeat-matrix.js +7 -2
  53. package/lib/dist/features/rib.d.ts +31 -0
  54. package/lib/dist/features/rib.js +321 -0
  55. package/lib/dist/features/select.d.ts +1 -0
  56. package/lib/dist/features/select.js +81 -10
  57. package/lib/dist/features/shell.d.ts +4 -1
  58. package/lib/dist/features/shell.js +14 -3
  59. package/lib/dist/filters/edge/belongs-to-face.d.ts +12 -9
  60. package/lib/dist/filters/edge/belongs-to-face.js +64 -15
  61. package/lib/dist/filters/filter-builder-base.d.ts +25 -0
  62. package/lib/dist/filters/filter-builder-base.js +47 -0
  63. package/lib/dist/filters/filter.js +39 -14
  64. package/lib/dist/filters/from-object.d.ts +4 -0
  65. package/lib/dist/filters/from-object.js +10 -0
  66. package/lib/dist/helpers/clone-transform.d.ts +2 -1
  67. package/lib/dist/helpers/scene-helpers.d.ts +1 -1
  68. package/lib/dist/helpers/scene-helpers.js +146 -12
  69. package/lib/dist/index.d.ts +7 -1
  70. package/lib/dist/index.js +3 -3
  71. package/lib/dist/io/file-import.d.ts +5 -1
  72. package/lib/dist/io/file-import.js +29 -18
  73. package/lib/dist/math/lazy-matrix.d.ts +31 -0
  74. package/lib/dist/math/lazy-matrix.js +66 -0
  75. package/lib/dist/oc/color-transfer.d.ts +19 -8
  76. package/lib/dist/oc/color-transfer.js +70 -12
  77. package/lib/dist/oc/constraints/constraint-solver-adaptor.d.ts +5 -0
  78. package/lib/dist/oc/constraints/constraint-solver-adaptor.js +16 -0
  79. package/lib/dist/oc/constraints/constraint-solver.d.ts +4 -0
  80. package/lib/dist/oc/constraints/curve/curve-constraint-solver.d.ts +4 -0
  81. package/lib/dist/oc/constraints/curve/curve-constraint-solver.js +3 -0
  82. package/lib/dist/oc/constraints/geometric/geometric-constraint-solver.d.ts +6 -1
  83. package/lib/dist/oc/constraints/geometric/geometric-constraint-solver.js +4 -0
  84. package/lib/dist/oc/constraints/geometric/tangent-arc-from-point-tangent.d.ts +8 -0
  85. package/lib/dist/oc/constraints/geometric/tangent-arc-from-point-tangent.js +111 -0
  86. package/lib/dist/oc/constraints/geometric/tangent-arc-radius-to-object.d.ts +8 -0
  87. package/lib/dist/oc/constraints/geometric/tangent-arc-radius-to-object.js +161 -0
  88. package/lib/dist/oc/extrude-ops.d.ts +2 -1
  89. package/lib/dist/oc/extrude-ops.js +51 -2
  90. package/lib/dist/oc/mesh.d.ts +9 -4
  91. package/lib/dist/oc/mesh.js +14 -13
  92. package/lib/dist/oc/rib-ops.d.ts +35 -0
  93. package/lib/dist/oc/rib-ops.js +619 -0
  94. package/lib/dist/oc/shell-ops.d.ts +2 -1
  95. package/lib/dist/oc/shell-ops.js +5 -2
  96. package/lib/dist/oc/topology-index.d.ts +6 -0
  97. package/lib/dist/oc/topology-index.js +36 -0
  98. package/lib/dist/rendering/mesh-builder.d.ts +3 -0
  99. package/lib/dist/rendering/mesh-builder.js +8 -4
  100. package/lib/dist/rendering/render-edge.d.ts +2 -1
  101. package/lib/dist/rendering/render-edge.js +2 -2
  102. package/lib/dist/rendering/render-face.d.ts +2 -1
  103. package/lib/dist/rendering/render-face.js +2 -2
  104. package/lib/dist/rendering/render-solid.d.ts +2 -1
  105. package/lib/dist/rendering/render-solid.js +3 -5
  106. package/lib/dist/rendering/render-wire.d.ts +2 -1
  107. package/lib/dist/rendering/render-wire.js +2 -2
  108. package/lib/dist/rendering/render.d.ts +4 -0
  109. package/lib/dist/rendering/render.js +50 -2
  110. package/lib/dist/rendering/scene-compare.js +3 -0
  111. package/lib/dist/rendering/scene.d.ts +1 -0
  112. package/lib/dist/rendering/scene.js +4 -0
  113. package/lib/dist/scene-manager.d.ts +4 -2
  114. package/lib/dist/scene-manager.js +12 -4
  115. package/lib/dist/tests/features/2d/arc.test.js +64 -0
  116. package/lib/dist/tests/features/2d/back.test.js +17 -1
  117. package/lib/dist/tests/features/2d/tarc.test.js +157 -0
  118. package/lib/dist/tests/features/color-lineage.test.js +18 -0
  119. package/lib/dist/tests/features/filter-positional.test.d.ts +1 -0
  120. package/lib/dist/tests/features/filter-positional.test.js +129 -0
  121. package/lib/dist/tests/features/repeat-user-repro.test.d.ts +1 -0
  122. package/lib/dist/tests/features/repeat-user-repro.test.js +60 -0
  123. package/lib/dist/tests/features/rib.test.d.ts +1 -0
  124. package/lib/dist/tests/features/rib.test.js +598 -0
  125. package/lib/dist/tests/features/shell.test.js +36 -0
  126. package/lib/dist/tests/global-setup.js +2 -1
  127. package/lib/dist/tests/helpers/extract-blocks.d.ts +9 -0
  128. package/lib/dist/tests/helpers/extract-blocks.js +56 -0
  129. package/lib/dist/tests/llm-docs-examples.test.d.ts +1 -0
  130. package/lib/dist/tests/llm-docs-examples.test.js +62 -0
  131. package/lib/dist/tests/scene-compare.test.d.ts +1 -0
  132. package/lib/dist/tests/scene-compare.test.js +77 -0
  133. package/lib/dist/tests/setup.js +2 -1
  134. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  135. package/llm-docs/.coverage-allowlist.txt +9 -0
  136. package/llm-docs/api/arc.md +48 -0
  137. package/llm-docs/api/axis.md +42 -0
  138. package/llm-docs/api/bezier.md +42 -0
  139. package/llm-docs/api/booleans.md +44 -0
  140. package/llm-docs/api/chamfer.md +40 -0
  141. package/llm-docs/api/circle.md +36 -0
  142. package/llm-docs/api/color.md +34 -0
  143. package/llm-docs/api/connect.md +41 -0
  144. package/llm-docs/api/constraint-qualifiers.md +48 -0
  145. package/llm-docs/api/copy.md +63 -0
  146. package/llm-docs/api/cursor-lines.md +50 -0
  147. package/llm-docs/api/cursor-move.md +61 -0
  148. package/llm-docs/api/cut.md +55 -0
  149. package/llm-docs/api/draft.md +36 -0
  150. package/llm-docs/api/edge-filter.md +57 -0
  151. package/llm-docs/api/ellipse.md +34 -0
  152. package/llm-docs/api/extrude.md +74 -0
  153. package/llm-docs/api/face-filter.md +61 -0
  154. package/llm-docs/api/fillet.md +51 -0
  155. package/llm-docs/api/index.json +139 -0
  156. package/llm-docs/api/line.md +42 -0
  157. package/llm-docs/api/load.md +37 -0
  158. package/llm-docs/api/local.md +38 -0
  159. package/llm-docs/api/loft.md +37 -0
  160. package/llm-docs/api/mirror.md +44 -0
  161. package/llm-docs/api/offset.md +36 -0
  162. package/llm-docs/api/part.md +40 -0
  163. package/llm-docs/api/plane.md +44 -0
  164. package/llm-docs/api/polygon.md +37 -0
  165. package/llm-docs/api/primitive-solids.md +39 -0
  166. package/llm-docs/api/project-intersect.md +48 -0
  167. package/llm-docs/api/rect.md +48 -0
  168. package/llm-docs/api/remove.md +32 -0
  169. package/llm-docs/api/repeat.md +79 -0
  170. package/llm-docs/api/revolve.md +38 -0
  171. package/llm-docs/api/rib.md +40 -0
  172. package/llm-docs/api/rotate.md +37 -0
  173. package/llm-docs/api/select.md +41 -0
  174. package/llm-docs/api/shell.md +41 -0
  175. package/llm-docs/api/sketch.md +76 -0
  176. package/llm-docs/api/slot.md +36 -0
  177. package/llm-docs/api/split-trim.md +42 -0
  178. package/llm-docs/api/sweep.md +43 -0
  179. package/llm-docs/api/tarc.md +45 -0
  180. package/llm-docs/api/tcircle.md +38 -0
  181. package/llm-docs/api/tline.md +42 -0
  182. package/llm-docs/api/translate.md +40 -0
  183. package/llm-docs/api/types/aline.md +35 -0
  184. package/llm-docs/api/types/arc-angles.md +29 -0
  185. package/llm-docs/api/types/arc-points.md +48 -0
  186. package/llm-docs/api/types/axis-like.md +38 -0
  187. package/llm-docs/api/types/axis.md +21 -0
  188. package/llm-docs/api/types/boolean-operation.md +50 -0
  189. package/llm-docs/api/types/circular-repeat-options.md +31 -0
  190. package/llm-docs/api/types/common.md +32 -0
  191. package/llm-docs/api/types/cut.md +125 -0
  192. package/llm-docs/api/types/draft.md +21 -0
  193. package/llm-docs/api/types/extrudable-geometry.md +23 -0
  194. package/llm-docs/api/types/extrude.md +194 -0
  195. package/llm-docs/api/types/geometry.md +51 -0
  196. package/llm-docs/api/types/hline.md +35 -0
  197. package/llm-docs/api/types/linear-repeat-options.md +31 -0
  198. package/llm-docs/api/types/loft.md +154 -0
  199. package/llm-docs/api/types/mirror.md +35 -0
  200. package/llm-docs/api/types/offset.md +31 -0
  201. package/llm-docs/api/types/plane-like.md +35 -0
  202. package/llm-docs/api/types/plane-transform-options.md +29 -0
  203. package/llm-docs/api/types/plane.md +21 -0
  204. package/llm-docs/api/types/point-like.md +22 -0
  205. package/llm-docs/api/types/point2dlike.md +26 -0
  206. package/llm-docs/api/types/polygon.md +46 -0
  207. package/llm-docs/api/types/rect.md +128 -0
  208. package/llm-docs/api/types/revolve.md +102 -0
  209. package/llm-docs/api/types/rib.md +133 -0
  210. package/llm-docs/api/types/scene-object.md +33 -0
  211. package/llm-docs/api/types/select.md +21 -0
  212. package/llm-docs/api/types/shell.md +54 -0
  213. package/llm-docs/api/types/slot.md +43 -0
  214. package/llm-docs/api/types/sweep.md +189 -0
  215. package/llm-docs/api/types/tangent-arc-two-objects.md +46 -0
  216. package/llm-docs/api/types/transformable.md +93 -0
  217. package/llm-docs/api/types/trim.md +27 -0
  218. package/llm-docs/api/types/two-objects-tangent-line.md +46 -0
  219. package/llm-docs/api/types/vertex.md +17 -0
  220. package/llm-docs/api/types/vline.md +35 -0
  221. package/llm-docs/concepts/coordinate-system.md +45 -0
  222. package/llm-docs/concepts/history-and-rollback.md +40 -0
  223. package/llm-docs/concepts/last-selection.md +49 -0
  224. package/llm-docs/concepts/scene-graph.md +37 -0
  225. package/llm-docs/index.json +1750 -0
  226. package/mcp/dist/client.d.ts +64 -0
  227. package/mcp/dist/client.js +248 -0
  228. package/mcp/dist/discovery.d.ts +11 -0
  229. package/mcp/dist/discovery.js +78 -0
  230. package/mcp/dist/docs-index.d.ts +81 -0
  231. package/mcp/dist/docs-index.js +261 -0
  232. package/mcp/dist/resources.d.ts +4 -0
  233. package/mcp/dist/resources.js +115 -0
  234. package/mcp/dist/server.d.ts +12 -0
  235. package/mcp/dist/server.js +489 -0
  236. package/mcp/dist/tools/coordination.d.ts +9 -0
  237. package/mcp/dist/tools/coordination.js +46 -0
  238. package/mcp/dist/tools/docs.d.ts +66 -0
  239. package/mcp/dist/tools/docs.js +122 -0
  240. package/mcp/dist/tools/engine.d.ts +56 -0
  241. package/mcp/dist/tools/engine.js +145 -0
  242. package/mcp/dist/tools/inspection.d.ts +75 -0
  243. package/mcp/dist/tools/inspection.js +121 -0
  244. package/mcp/dist/tools/screenshot.d.ts +63 -0
  245. package/mcp/dist/tools/screenshot.js +263 -0
  246. package/mcp/dist/tools/source.d.ts +84 -0
  247. package/mcp/dist/tools/source.js +434 -0
  248. package/mcp/dist/tools/workspaces.d.ts +13 -0
  249. package/mcp/dist/tools/workspaces.js +33 -0
  250. package/mcp/dist/types.d.ts +18 -0
  251. package/mcp/dist/types.js +11 -0
  252. package/package.json +19 -5
  253. package/server/dist/code-editor.d.ts +36 -0
  254. package/server/dist/code-editor.js +8 -0
  255. package/server/dist/fluidcad-server.d.ts +50 -0
  256. package/server/dist/fluidcad-server.js +153 -1
  257. package/server/dist/global-registry.d.ts +30 -0
  258. package/server/dist/global-registry.js +126 -0
  259. package/server/dist/index.js +171 -26
  260. package/server/dist/instance-file.d.ts +31 -0
  261. package/server/dist/instance-file.js +73 -0
  262. package/server/dist/lint-fluid-js.d.ts +15 -0
  263. package/server/dist/lint-fluid-js.js +271 -0
  264. package/server/dist/routes/editor.d.ts +24 -0
  265. package/server/dist/routes/editor.js +44 -0
  266. package/server/dist/routes/export.d.ts +1 -1
  267. package/server/dist/routes/export.js +45 -8
  268. package/server/dist/routes/health.d.ts +7 -0
  269. package/server/dist/routes/health.js +14 -0
  270. package/server/dist/routes/lint.d.ts +10 -0
  271. package/server/dist/routes/lint.js +28 -0
  272. package/server/dist/routes/render.d.ts +33 -0
  273. package/server/dist/routes/render.js +34 -0
  274. package/server/dist/routes/scene.d.ts +5 -0
  275. package/server/dist/routes/scene.js +48 -0
  276. package/server/dist/routes/screenshot.js +68 -1
  277. package/server/dist/ws-protocol.d.ts +56 -2
  278. package/ui/dist/assets/{index-DMw0OYCF.js → index-BdqrMDRu.js} +30 -30
  279. package/ui/dist/index.html +1 -1
@@ -0,0 +1,36 @@
1
+ import { getOC } from "./init.js";
2
+ export class TopologyIndex {
3
+ static buildEdgeToFaces(root) {
4
+ const oc = getOC();
5
+ const map = new oc.TopTools_IndexedDataMapOfShapeListOfShape();
6
+ oc.TopExp.MapShapesAndAncestors(root, oc.TopAbs_ShapeEnum.TopAbs_EDGE, oc.TopAbs_ShapeEnum.TopAbs_FACE, map);
7
+ return map;
8
+ }
9
+ static buildShapeSet(shapes) {
10
+ const oc = getOC();
11
+ const map = new oc.TopTools_MapOfShape();
12
+ for (const s of shapes) {
13
+ map.Add(s);
14
+ }
15
+ return map;
16
+ }
17
+ static seekShapes(index, key) {
18
+ const idx = index.FindIndex(key);
19
+ if (idx === 0) {
20
+ return [];
21
+ }
22
+ const list = index.ChangeFromIndex(idx);
23
+ if (!list || list.Size() === 0) {
24
+ return [];
25
+ }
26
+ const oc = getOC();
27
+ const copy = new oc.TopTools_ListOfShape(list);
28
+ const out = [];
29
+ while (copy.Size() > 0) {
30
+ out.push(copy.First());
31
+ copy.RemoveFirst();
32
+ }
33
+ copy.delete();
34
+ return out;
35
+ }
36
+ }
@@ -1,5 +1,8 @@
1
1
  import { Shape } from "../common/shape.js";
2
2
  import { SceneObjectMesh } from "./scene.js";
3
+ import type { MeshConfig } from "../oc/mesh.js";
3
4
  export declare class MeshBuilder {
5
+ private readonly meshConfig;
6
+ constructor(meshConfig: MeshConfig);
4
7
  build(shapeObj: Shape): SceneObjectMesh[];
5
8
  }
@@ -4,20 +4,24 @@ import { renderFace } from "./render-face.js";
4
4
  import { renderWire } from "./render-wire.js";
5
5
  import { renderEdge } from "./render-edge.js";
6
6
  export class MeshBuilder {
7
+ meshConfig;
8
+ constructor(meshConfig) {
9
+ this.meshConfig = meshConfig;
10
+ }
7
11
  build(shapeObj) {
8
12
  const shape = shapeObj.getShape();
9
13
  let result = null;
10
14
  if (Explorer.isSolid(shape)) {
11
- result = renderSolid(shapeObj);
15
+ result = renderSolid(shapeObj, this.meshConfig);
12
16
  }
13
17
  else if (Explorer.isFace(shape)) {
14
- result = renderFace(shapeObj);
18
+ result = renderFace(shapeObj, 0, this.meshConfig);
15
19
  }
16
20
  else if (Explorer.isWire(shape)) {
17
- result = renderWire(shapeObj);
21
+ result = renderWire(shapeObj, this.meshConfig);
18
22
  }
19
23
  else if (Explorer.isEdge(shape)) {
20
- result = renderEdge(shapeObj);
24
+ result = renderEdge(shapeObj, this.meshConfig);
21
25
  }
22
26
  else if (Explorer.isCompound(shape)) {
23
27
  console.warn("Compound shapes are not supported yet.");
@@ -1,3 +1,4 @@
1
1
  import { Shape } from "../common/shape.js";
2
+ import type { MeshConfig } from "../oc/mesh.js";
2
3
  import type { SceneObjectMesh } from "./scene.js";
3
- export declare function renderEdge(edgeObj: Shape): SceneObjectMesh;
4
+ export declare function renderEdge(edgeObj: Shape, meshConfig?: MeshConfig): SceneObjectMesh;
@@ -1,4 +1,4 @@
1
1
  import { Mesh } from "../oc/mesh.js";
2
- export function renderEdge(edgeObj) {
3
- return Mesh.discretizeEdge(edgeObj);
2
+ export function renderEdge(edgeObj, meshConfig) {
3
+ return Mesh.discretizeEdge(edgeObj, meshConfig);
4
4
  }
@@ -1,2 +1,3 @@
1
1
  import { Shape } from "../common/shape.js";
2
- export declare function renderFace(faceObj: Shape, vertexOffset?: number): import("../oc/mesh.js").MeshData;
2
+ import type { MeshConfig } from "../oc/mesh.js";
3
+ export declare function renderFace(faceObj: Shape, vertexOffset?: number, meshConfig?: MeshConfig): import("../oc/mesh.js").MeshData;
@@ -1,5 +1,5 @@
1
1
  import { Mesh } from "../oc/mesh.js";
2
- export function renderFace(faceObj, vertexOffset = 0) {
2
+ export function renderFace(faceObj, vertexOffset = 0, meshConfig) {
3
3
  const face = faceObj.getShape();
4
- return Mesh.triangulateFaceRaw(face, vertexOffset);
4
+ return Mesh.triangulateFaceRaw(face, vertexOffset, meshConfig);
5
5
  }
@@ -1,3 +1,4 @@
1
1
  import { Shape } from "../common/shape.js";
2
2
  import { SceneObjectMesh } from "./scene.js";
3
- export declare function renderSolid(shapeObj: Shape): SceneObjectMesh[];
3
+ import type { MeshConfig } from "../oc/mesh.js";
4
+ export declare function renderSolid(shapeObj: Shape, meshConfig?: MeshConfig): SceneObjectMesh[];
@@ -1,8 +1,8 @@
1
1
  import { Explorer } from "../oc/explorer.js";
2
2
  import { Mesh } from "../oc/mesh.js";
3
3
  import { getOC } from "../oc/init.js";
4
- export function renderSolid(shapeObj) {
5
- Mesh.ensureTriangulated(shapeObj.getShape());
4
+ export function renderSolid(shapeObj, meshConfig) {
5
+ Mesh.ensureTriangulated(shapeObj.getShape(), meshConfig);
6
6
  const facesMeshes = getFacesMesh(shapeObj);
7
7
  const edgesMesh = getEdgesMesh(shapeObj);
8
8
  return [...facesMeshes, ...edgesMesh];
@@ -10,8 +10,7 @@ export function renderSolid(shapeObj) {
10
10
  function getEdgesMesh(shapeObj) {
11
11
  const oc = getOC();
12
12
  const result = [];
13
- const edgeToFaces = new oc.TopTools_IndexedDataMapOfShapeListOfShape();
14
- oc.TopExp.MapShapesAndAncestors(shapeObj.getShape(), oc.TopAbs_ShapeEnum.TopAbs_EDGE, oc.TopAbs_ShapeEnum.TopAbs_FACE, edgeToFaces);
13
+ const edgeToFaces = shapeObj.getEdgeToFacesIndex();
15
14
  const edges = Explorer.findEdgesWrapped(shapeObj);
16
15
  for (let edgeIdx = 0; edgeIdx < edges.length; edgeIdx++) {
17
16
  const edgeShape = edges[edgeIdx].getShape();
@@ -30,7 +29,6 @@ function getEdgesMesh(shapeObj) {
30
29
  });
31
30
  }
32
31
  }
33
- edgeToFaces.delete();
34
32
  return result;
35
33
  }
36
34
  function getFacesMesh(shapeObj) {
@@ -1,5 +1,6 @@
1
1
  import { Shape } from "../common/shape.js";
2
- export declare function renderWire(shapeObj: Shape): {
2
+ import type { MeshConfig } from "../oc/mesh.js";
3
+ export declare function renderWire(shapeObj: Shape, meshConfig?: MeshConfig): {
3
4
  vertices: number[];
4
5
  normals: number[];
5
6
  indices: number[];
@@ -2,9 +2,9 @@ import { renderEdge } from "./render-edge.js";
2
2
  import { Edge } from "../common/edge.js";
3
3
  import { Explorer } from "../oc/explorer.js";
4
4
  import { Mesh } from "../oc/mesh.js";
5
- export function renderWire(shapeObj) {
5
+ export function renderWire(shapeObj, meshConfig) {
6
6
  const shape = shapeObj.getShape();
7
- Mesh.ensureTriangulated(shape);
7
+ Mesh.ensureTriangulated(shape, meshConfig);
8
8
  const edges = Explorer.findShapes(shape, Explorer.getOcShapeType("edge"));
9
9
  const allVertices = [];
10
10
  const allNormals = [];
@@ -1,8 +1,12 @@
1
1
  import { Scene } from "./scene.js";
2
+ import type { MeshConfig } from "../oc/mesh.js";
2
3
  export declare class SceneRenderer {
4
+ private readonly meshConfig;
3
5
  private readonly meshBuilder;
6
+ constructor(meshConfig: MeshConfig);
4
7
  render(scene: Scene): Scene;
5
8
  renderRollback(scene: Scene, rollbackIndex: number): Scene;
9
+ private batchTriangulate;
6
10
  private prepareRenderedShapes;
7
11
  private emitRenderObject;
8
12
  private buildObject;
@@ -4,10 +4,16 @@ import { AxisObjectBase } from "../features/axis-renderable-base.js";
4
4
  import { Sketch } from "../features/2d/sketch.js";
5
5
  import { transformMeshes } from "./mesh-transform.js";
6
6
  import { ShapeOps } from "../oc/shape-ops.js";
7
+ import { Mesh } from "../oc/mesh.js";
7
8
  import { Profiler } from "../common/profiler.js";
8
9
  import { describeError } from "../common/describe-error.js";
9
10
  export class SceneRenderer {
10
- meshBuilder = new MeshBuilder();
11
+ meshConfig;
12
+ meshBuilder;
13
+ constructor(meshConfig) {
14
+ this.meshConfig = meshConfig;
15
+ this.meshBuilder = new MeshBuilder(meshConfig);
16
+ }
11
17
  render(scene) {
12
18
  const sceneObjects = scene.getAllSceneObjects();
13
19
  console.log("============ Rendering ==============", sceneObjects.length);
@@ -40,6 +46,7 @@ export class SceneRenderer {
40
46
  }
41
47
  object.clean(scene.getPartScopedAllObjects(object));
42
48
  }
49
+ this.batchTriangulate(sceneObjects, skippedContainers);
43
50
  const prepared = new Map();
44
51
  for (const object of sceneObjects) {
45
52
  const profiler = profilers.get(object);
@@ -88,6 +95,44 @@ export class SceneRenderer {
88
95
  console.table(result);
89
96
  return scene;
90
97
  }
98
+ // Mesh every shape that still needs triangulation in a single
99
+ // BRepMesh_IncrementalMesh call. OC's parallel mode then balances faces
100
+ // across all shapes at once instead of running per-shape sequentially.
101
+ // The triangulation lives on each TFace, so the existing per-shape
102
+ // extraction in prepareRenderedShapes finds it cached and short-circuits
103
+ // its own ensureTriangulated call.
104
+ batchTriangulate(sceneObjects, skippedContainers) {
105
+ const targets = new Set();
106
+ for (const object of sceneObjects) {
107
+ if (skippedContainers.has(object) || object.isLazy()) {
108
+ continue;
109
+ }
110
+ const shapes = object.getOwnShapes({ excludeMeta: false, excludeGuide: false });
111
+ for (const shape of shapes) {
112
+ if (shape.getMeshes()) {
113
+ continue;
114
+ }
115
+ const source = shape.getMeshSource();
116
+ const target = source ? source.shape : shape;
117
+ if (target.getMeshes()) {
118
+ continue;
119
+ }
120
+ targets.add(target);
121
+ }
122
+ }
123
+ if (targets.size <= 1) {
124
+ return;
125
+ }
126
+ const compound = ShapeOps.makeCompoundRaw([...targets].map(s => s.getShape()));
127
+ try {
128
+ const t0 = performance.now();
129
+ Mesh.ensureTriangulated(compound, this.meshConfig);
130
+ console.log(`Batched mesh: ${targets.size} shapes in ${(performance.now() - t0).toFixed(1)}ms`);
131
+ }
132
+ finally {
133
+ compound.delete();
134
+ }
135
+ }
91
136
  prepareRenderedShapes(obj, profiler) {
92
137
  const renderedSceneShapes = [];
93
138
  if (obj.isLazy()) {
@@ -249,9 +294,12 @@ export class SceneRenderer {
249
294
  emitRendered(obj, scene, opts) {
250
295
  const categories = opts.profiler?.getCategories();
251
296
  const profileCategories = categories && categories.length > 0 ? categories : undefined;
297
+ const displayName = obj.hasCustomName()
298
+ ? obj.getName()
299
+ : obj.getType().charAt(0).toUpperCase() + obj.getType().slice(1);
252
300
  const rendered = {
253
301
  id: obj.id,
254
- name: obj.getName(),
302
+ name: displayName,
255
303
  parentId: obj.parentId,
256
304
  object: obj.serialize(opts.scope),
257
305
  sceneShapes: opts.sceneShapes,
@@ -31,6 +31,9 @@ export class SceneCompare {
31
31
  }
32
32
  oldSttate.set('removedShapes', newRemovedShapes);
33
33
  newObj.restoreState(oldSttate);
34
+ const staleId = newObj.id;
35
+ newObj.inheritIdentityFrom(oldObj);
36
+ newScene.reindexObject(newObj, staleId);
34
37
  const oldError = oldObj.getError();
35
38
  if (oldError) {
36
39
  newObj.setError(oldError);
@@ -82,5 +82,6 @@ export declare class Scene {
82
82
  indexOf(obj: SceneObject): number;
83
83
  getSceneObjectAt(index: number): SceneObject;
84
84
  getSceneObjectById(id: string): SceneObject | null;
85
+ reindexObject(obj: SceneObject, oldId: string): void;
85
86
  getChildren(parent: SceneObject): SceneObject[];
86
87
  }
@@ -201,6 +201,10 @@ export class Scene {
201
201
  getSceneObjectById(id) {
202
202
  return this.idMap.get(id) || null;
203
203
  }
204
+ reindexObject(obj, oldId) {
205
+ this.idMap.delete(oldId);
206
+ this.idMap.set(obj.id, obj);
207
+ }
204
208
  getChildren(parent) {
205
209
  return this.sceneObjects.filter(obj => obj.parentId === parent.id);
206
210
  }
@@ -1,5 +1,7 @@
1
1
  import { Scene } from "./rendering/scene.js";
2
2
  import { SceneRenderer } from "./rendering/render.js";
3
+ import type { MeshConfig } from "./oc/mesh.js";
4
+ import type { FluidCADOptions } from "./index.js";
3
5
  import type { ExportOptions } from "./io/file-export.js";
4
6
  import type { ShapeProperties } from "./oc/props.js";
5
7
  import type { FaceProperties } from "./oc/face-props.js";
@@ -10,7 +12,7 @@ declare class SceneManager {
10
12
  currentScene: Scene;
11
13
  currentFile: string;
12
14
  renderer: SceneRenderer;
13
- constructor(rootPath: string);
15
+ constructor(rootPath: string, meshConfig: MeshConfig);
14
16
  setCurrentFile(filePath: string): void;
15
17
  startScene(): Scene;
16
18
  renderScene(scene: Scene): Scene;
@@ -26,7 +28,7 @@ declare class SceneManager {
26
28
  };
27
29
  hitTest(scene: Scene, shapeId: string, rayOrigin: [number, number, number], rayDir: [number, number, number], edgeThreshold: number): HitTestResult;
28
30
  }
29
- export declare function createManager(rootPath: string): SceneManager;
31
+ export declare function createManager(rootPath: string, options?: FluidCADOptions): SceneManager;
30
32
  export declare function getCurrentScene(): Scene;
31
33
  export declare function getCurrentFile(): string;
32
34
  export declare function setCurrentFile(filePath: string): void;
@@ -1,6 +1,7 @@
1
1
  import { Scene } from "./rendering/scene.js";
2
2
  import { SceneRenderer } from "./rendering/render.js";
3
3
  import { SceneCompare } from "./rendering/scene-compare.js";
4
+ import { DEFAULT_MESH_CONFIG } from "./oc/mesh.js";
4
5
  import { FileImport } from "./io/file-import.js";
5
6
  import { FileExport } from "./io/file-export.js";
6
7
  import { ShapeProps } from "./oc/props.js";
@@ -12,9 +13,10 @@ class SceneManager {
12
13
  rootPath;
13
14
  currentScene = new Scene();
14
15
  currentFile = '';
15
- renderer = new SceneRenderer();
16
- constructor(rootPath) {
16
+ renderer;
17
+ constructor(rootPath, meshConfig) {
17
18
  this.rootPath = rootPath;
19
+ this.renderer = new SceneRenderer(meshConfig);
18
20
  }
19
21
  setCurrentFile(filePath) {
20
22
  this.currentFile = filePath;
@@ -100,9 +102,15 @@ class SceneManager {
100
102
  }
101
103
  }
102
104
  let currentManager = null;
103
- export function createManager(rootPath) {
105
+ function resolveMeshConfig(options) {
106
+ return {
107
+ linDefl: options?.mesh?.lineDeflection ?? DEFAULT_MESH_CONFIG.linDefl,
108
+ angDefl: options?.mesh?.angularDeflection ?? DEFAULT_MESH_CONFIG.angDefl,
109
+ };
110
+ }
111
+ export function createManager(rootPath, options) {
104
112
  console.log(`Creating SceneManager with root path: ${rootPath}`);
105
- currentManager = new SceneManager(rootPath);
113
+ currentManager = new SceneManager(rootPath, resolveMeshConfig(options));
106
114
  return currentManager;
107
115
  }
108
116
  export function getCurrentScene() {
@@ -36,6 +36,70 @@ describe("arc", () => {
36
36
  expect(arcEdges.length).toBeGreaterThan(0);
37
37
  });
38
38
  });
39
+ describe("clockwise arc with .cw()", () => {
40
+ it("should create a CW arc from start to end around a center point", () => {
41
+ sketch("xy", () => {
42
+ arc([0, 0], [20, 0]).center([10, 0]).cw();
43
+ line([0, 0]);
44
+ });
45
+ const e = extrude(10);
46
+ render();
47
+ expect(e.getShapes()).toHaveLength(1);
48
+ const solid = e.getShapes()[0];
49
+ const arcEdges = getEdgesByType(solid, "arc");
50
+ expect(arcEdges.length).toBeGreaterThan(0);
51
+ });
52
+ it("should create a valid arc when chained from current position", () => {
53
+ sketch("xy", () => {
54
+ hLine(20);
55
+ arc([0, 0]).center([10, 0]).cw();
56
+ });
57
+ const e = extrude(10);
58
+ render();
59
+ expect(e.getShapes()).toHaveLength(1);
60
+ const solid = e.getShapes()[0];
61
+ const arcEdges = getEdgesByType(solid, "arc");
62
+ expect(arcEdges.length).toBeGreaterThan(0);
63
+ });
64
+ });
65
+ describe("major arc with .major()", () => {
66
+ it("should create a major arc (>180°) from current position", () => {
67
+ sketch("xy", () => {
68
+ arc([30, 0]).radius(20).major();
69
+ line([0, 0]);
70
+ });
71
+ const e = extrude(10);
72
+ render();
73
+ expect(e.getShapes()).toHaveLength(1);
74
+ const solid = e.getShapes()[0];
75
+ const arcEdges = getEdgesByType(solid, "arc");
76
+ expect(arcEdges.length).toBeGreaterThan(0);
77
+ });
78
+ it("should create a major arc with two explicit points", () => {
79
+ sketch("xy", () => {
80
+ arc([0, 0], [30, 0]).radius(20).major();
81
+ line([0, 0]);
82
+ });
83
+ const e = extrude(10);
84
+ render();
85
+ expect(e.getShapes()).toHaveLength(1);
86
+ const solid = e.getShapes()[0];
87
+ const arcEdges = getEdgesByType(solid, "arc");
88
+ expect(arcEdges.length).toBeGreaterThan(0);
89
+ });
90
+ it("should create a CW major arc with negative radius and major", () => {
91
+ sketch("xy", () => {
92
+ arc([30, 0]).radius(-20).major();
93
+ line([0, 0]);
94
+ });
95
+ const e = extrude(10);
96
+ render();
97
+ expect(e.getShapes()).toHaveLength(1);
98
+ const solid = e.getShapes()[0];
99
+ const arcEdges = getEdgesByType(solid, "arc");
100
+ expect(arcEdges.length).toBeGreaterThan(0);
101
+ });
102
+ });
39
103
  describe("combined with lines", () => {
40
104
  it("should create a shape with straight and curved edges", () => {
41
105
  sketch("xy", () => {
@@ -2,8 +2,9 @@ import { describe, it, expect } from "vitest";
2
2
  import { setupOC, render } from "../../setup.js";
3
3
  import sketch from "../../../core/sketch.js";
4
4
  import extrude from "../../../core/extrude.js";
5
- import { move, hMove, back, rect } from "../../../core/2d/index.js";
5
+ import { move, hMove, back, rect, hLine, vLine, tLine } from "../../../core/2d/index.js";
6
6
  import { ShapeOps } from "../../../oc/shape-ops.js";
7
+ import { Edge } from "../../../common/edge.js";
7
8
  describe("back", () => {
8
9
  setupOC();
9
10
  it("should revert cursor to the previous position", () => {
@@ -57,4 +58,19 @@ describe("back", () => {
57
58
  const bbox = ShapeOps.getBoundingBox(e.getShapes()[0]);
58
59
  expect(bbox.minX).toBeCloseTo(40, 0);
59
60
  });
61
+ it("should restore the tangent to its prior value, not just the position", () => {
62
+ let t;
63
+ sketch("xy", () => {
64
+ hLine(10); // tangent (+1, 0), pos (10, 0)
65
+ vLine(10); // tangent (0, +1), pos (10, 10)
66
+ back(); // pos reverts to (10, 0); tangent should revert to (+1, 0)
67
+ t = tLine(5); // with restored tangent, this should go to (15, 0)
68
+ });
69
+ render();
70
+ const edges = t.getOwnShapes().filter((sh) => sh instanceof Edge);
71
+ expect(edges).toHaveLength(1);
72
+ const endPoint = edges[0].getLastVertex().toPoint();
73
+ expect(endPoint.x).toBeCloseTo(15, 1);
74
+ expect(endPoint.y).toBeCloseTo(0, 1);
75
+ });
60
76
  });
@@ -234,4 +234,161 @@ describe("tArc", () => {
234
234
  render();
235
235
  });
236
236
  });
237
+ describe("tangent arc from current position to target line (target)", () => {
238
+ it("should create a tangent arc from a horizontal line ending tangent to a vertical line", () => {
239
+ const s = sketch("xy", () => {
240
+ const v = vLine([200, 200], 100).guide();
241
+ move([0, 0]);
242
+ hLine(100);
243
+ tArc(v);
244
+ });
245
+ render();
246
+ const shapes = s.getShapes();
247
+ // hLine + tArc (guide excluded) = at least 2 edges
248
+ expect(shapes.length).toBeGreaterThanOrEqual(2);
249
+ });
250
+ it("should accept a qualified line target", () => {
251
+ const s = sketch("xy", () => {
252
+ const v = vLine([200, 200], 100).guide();
253
+ move([0, 0]);
254
+ hLine(100);
255
+ tArc(outside(v));
256
+ });
257
+ render();
258
+ const shapes = s.getShapes();
259
+ expect(shapes.length).toBeGreaterThanOrEqual(2);
260
+ });
261
+ it("should allow chaining geometry after the solved arc", () => {
262
+ const s = sketch("xy", () => {
263
+ const v = vLine([200, 200], 100).guide();
264
+ move([0, 0]);
265
+ hLine(100);
266
+ tArc(v);
267
+ vLine(40);
268
+ });
269
+ render();
270
+ const shapes = s.getShapes();
271
+ // chain continues — hLine + tArc + vLine
272
+ expect(shapes.length).toBeGreaterThanOrEqual(3);
273
+ });
274
+ it("default arc curves to the left of the start tangent", () => {
275
+ const s = sketch("xy", () => {
276
+ const h = hLine([-150, 100], 300).guide();
277
+ move([0, 0]);
278
+ vLine(80);
279
+ tArc(h);
280
+ });
281
+ render();
282
+ const arcs = getEdgesByType(s.getShapes(), "arc");
283
+ expect(arcs.length).toBe(1);
284
+ // start tangent is +Y; "left" of +Y is -X. End vertex sits at negative x.
285
+ const endX = arcs[0].getLastVertex().toPoint().x;
286
+ expect(endX).toBeLessThan(0);
287
+ });
288
+ it(".flip() reverses the curve to the right of the start tangent", () => {
289
+ const s = sketch("xy", () => {
290
+ const h = hLine([-150, 100], 300).guide();
291
+ move([0, 0]);
292
+ vLine(80);
293
+ tArc(h).flip();
294
+ });
295
+ render();
296
+ const arcs = getEdgesByType(s.getShapes(), "arc");
297
+ expect(arcs.length).toBe(1);
298
+ const endX = arcs[0].getLastVertex().toPoint().x;
299
+ expect(endX).toBeGreaterThan(0);
300
+ });
301
+ it("should reject circle targets", () => {
302
+ const s = sketch("xy", () => {
303
+ const c = circle([200, 80], 30).guide();
304
+ move([0, 0]);
305
+ hLine(100);
306
+ tArc(c);
307
+ });
308
+ render();
309
+ const children = s.getChildren();
310
+ const arc = children[children.length - 1];
311
+ expect(arc.getError()).toMatch(/only line targets are supported/);
312
+ });
313
+ });
314
+ describe("tangent arc with radius ending at intersection (radius, target)", () => {
315
+ it("should end at the intersection with a line target", () => {
316
+ const s = sketch("xy", () => {
317
+ const h = hLine([-200, 100], 400).guide();
318
+ move([0, 0]);
319
+ vLine(50);
320
+ tArc(60, h);
321
+ });
322
+ render();
323
+ const arcs = getEdgesByType(s.getShapes(), "arc");
324
+ expect(arcs.length).toBe(1);
325
+ // End vertex must lie on the target line y = 100.
326
+ const endY = arcs[0].getLastVertex().toPoint().y;
327
+ expect(endY).toBeCloseTo(100, 6);
328
+ });
329
+ it("should end at the intersection with a circle target", () => {
330
+ const s = sketch("xy", () => {
331
+ // circle(diameter); 80 diameter = 40 radius
332
+ const c = circle([80, 0], 80).guide();
333
+ move([0, 0]);
334
+ hLine(40);
335
+ tArc(50, c);
336
+ });
337
+ render();
338
+ const arcs = getEdgesByType(s.getShapes(), "arc");
339
+ expect(arcs.length).toBe(1);
340
+ // End vertex must lie on the target circle (radius 40 from (80, 0)).
341
+ const end = arcs[0].getLastVertex().toPoint();
342
+ const dist = Math.hypot(end.x - 80, end.y - 0);
343
+ expect(dist).toBeCloseTo(40, 6);
344
+ });
345
+ it("negative radius flips the sweep direction", () => {
346
+ // Same line, same start, same tangent — only the sign of the radius
347
+ // differs. Positive radius (CCW) curves to the left of the tangent;
348
+ // negative radius (CW) curves to the right.
349
+ const sCCW = sketch("xy", () => {
350
+ const h = hLine([-200, 100], 400).guide();
351
+ move([0, 0]);
352
+ vLine(50);
353
+ tArc(60, h);
354
+ });
355
+ render();
356
+ const sCW = sketch("xy", () => {
357
+ const h = hLine([-200, 100], 400).guide();
358
+ move([0, 0]);
359
+ vLine(50);
360
+ tArc(-60, h);
361
+ });
362
+ render();
363
+ const ccwArc = getEdgesByType(sCCW.getShapes(), "arc")[0];
364
+ const cwArc = getEdgesByType(sCW.getShapes(), "arc")[0];
365
+ // T̂ = (0, +1); "left" of the tangent is −x, "right" is +x.
366
+ expect(ccwArc.getLastVertex().toPoint().x).toBeLessThan(0);
367
+ expect(cwArc.getLastVertex().toPoint().x).toBeGreaterThan(0);
368
+ });
369
+ it("should chain geometry after the solved arc", () => {
370
+ const s = sketch("xy", () => {
371
+ const h = hLine([-200, 100], 400).guide();
372
+ move([0, 0]);
373
+ vLine(50);
374
+ tArc(60, h);
375
+ hLine(40);
376
+ });
377
+ render();
378
+ const shapes = s.getShapes();
379
+ expect(shapes.length).toBeGreaterThanOrEqual(3);
380
+ });
381
+ it("should record an error when there is no intersection", () => {
382
+ const s = sketch("xy", () => {
383
+ const h = hLine([-200, 500], 400).guide();
384
+ move([0, 0]);
385
+ vLine(10);
386
+ tArc(20, h);
387
+ });
388
+ render();
389
+ const children = s.getChildren();
390
+ const arc = children[children.length - 1];
391
+ expect(arc.getError()).toMatch(/does not intersect target/);
392
+ });
393
+ });
237
394
  });
@@ -159,6 +159,24 @@ describe("color preservation through operations (Phase 3 lineage)", () => {
159
159
  const orangeFaces = filleted.colorMap.filter(e => e.color === '#ffa500');
160
160
  expect(orangeFaces.length).toBeGreaterThanOrEqual(2);
161
161
  });
162
+ it("color does NOT bleed: filleting a side edge of a colored-top box leaves fillet faces uncolored", () => {
163
+ // The colored top face does NOT own the side (vertical) edges. Filleting
164
+ // those edges generates new corner faces whose lineage traces back to the
165
+ // side faces — so they must not inherit the top's color, even though
166
+ // their top arc is adjacent to the (still-orange) top face.
167
+ sketch("top", () => {
168
+ rect(100, 50).centered();
169
+ });
170
+ const e = extrude(20);
171
+ color("orange", e.endFaces());
172
+ const f = fillet(10, e.sideEdges());
173
+ render();
174
+ const filleted = f.getShapes()[0];
175
+ // Only the (Modified) top face should still be orange. The fillet faces
176
+ // came from side edges (uncolored), and the bottom was never colored.
177
+ const orangeCount = filleted.colorMap.filter(e => e.color === '#ffa500').length;
178
+ expect(orangeCount).toBe(1);
179
+ });
162
180
  it("color survives a chamfer", () => {
163
181
  sketch("xy", () => {
164
182
  circle(40);