fluidcad 0.0.13 → 0.0.15

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 (68) hide show
  1. package/lib/dist/common/scene-object.d.ts +3 -0
  2. package/lib/dist/common/scene-object.js +7 -0
  3. package/lib/dist/core/2d/arc.d.ts +14 -56
  4. package/lib/dist/core/2d/arc.js +8 -23
  5. package/lib/dist/core/2d/center.d.ts +9 -0
  6. package/lib/dist/core/2d/center.js +10 -0
  7. package/lib/dist/core/2d/index.d.ts +2 -0
  8. package/lib/dist/core/2d/index.js +2 -0
  9. package/lib/dist/core/2d/intersect.d.ts +17 -0
  10. package/lib/dist/core/2d/intersect.js +20 -0
  11. package/lib/dist/core/interfaces.d.ts +50 -2
  12. package/lib/dist/core/repeat.js +10 -3
  13. package/lib/dist/features/2d/arc.d.ts +27 -14
  14. package/lib/dist/features/2d/arc.js +269 -38
  15. package/lib/dist/features/2d/intersect.d.ts +15 -0
  16. package/lib/dist/features/2d/intersect.js +83 -0
  17. package/lib/dist/features/2d/plane-center.d.ts +13 -0
  18. package/lib/dist/features/2d/plane-center.js +40 -0
  19. package/lib/dist/features/2d/projection.js +3 -4
  20. package/lib/dist/features/2d/rect.d.ts +2 -2
  21. package/lib/dist/features/2d/rect.js +3 -3
  22. package/lib/dist/features/2d/sketch.d.ts +2 -2
  23. package/lib/dist/features/2d/sketch.js +13 -1
  24. package/lib/dist/features/2d/slot.d.ts +2 -2
  25. package/lib/dist/features/2d/slot.js +3 -3
  26. package/lib/dist/features/extrude-base.d.ts +4 -0
  27. package/lib/dist/features/extrude-base.js +27 -0
  28. package/lib/dist/features/extrude-to-face.d.ts +1 -0
  29. package/lib/dist/features/extrude-to-face.js +5 -1
  30. package/lib/dist/features/extrude-two-distances.d.ts +1 -0
  31. package/lib/dist/features/extrude-two-distances.js +5 -2
  32. package/lib/dist/features/extrude.d.ts +1 -0
  33. package/lib/dist/features/extrude.js +5 -1
  34. package/lib/dist/features/plane-renderable-base.d.ts +2 -1
  35. package/lib/dist/features/plane-renderable-base.js +1 -1
  36. package/lib/dist/features/plane.d.ts +1 -1
  37. package/lib/dist/filters/face/face-filter.d.ts +10 -0
  38. package/lib/dist/filters/face/face-filter.js +39 -0
  39. package/lib/dist/filters/face/intersects-with.d.ts +18 -0
  40. package/lib/dist/filters/face/intersects-with.js +41 -0
  41. package/lib/dist/helpers/clone-transform.js +1 -0
  42. package/lib/dist/oc/boolean-ops.js +0 -2
  43. package/lib/dist/oc/face-query.d.ts +1 -0
  44. package/lib/dist/oc/face-query.js +15 -0
  45. package/lib/dist/oc/index.d.ts +1 -0
  46. package/lib/dist/oc/index.js +1 -0
  47. package/lib/dist/oc/section-ops.d.ts +6 -0
  48. package/lib/dist/oc/section-ops.js +26 -0
  49. package/lib/dist/oc/thin-face-maker.d.ts +29 -0
  50. package/lib/dist/oc/thin-face-maker.js +163 -0
  51. package/lib/dist/rendering/render.js +16 -0
  52. package/lib/dist/tests/features/2d/arc.test.js +2 -2
  53. package/lib/dist/tests/features/2d/intersect.test.d.ts +1 -0
  54. package/lib/dist/tests/features/2d/intersect.test.js +22 -0
  55. package/lib/dist/tests/features/2d/rect.test.js +1 -1
  56. package/lib/dist/tests/features/loft.test.js +1 -1
  57. package/lib/dist/tests/features/select.test.js +49 -1
  58. package/lib/dist/tests/features/thin-extrude.test.d.ts +1 -0
  59. package/lib/dist/tests/features/thin-extrude.test.js +137 -0
  60. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +3 -3
  62. package/server/dist/index.js +23 -15
  63. package/ui/dist/assets/{index-mLcpjEcV.js → index-BJG141m7.js} +2 -2
  64. package/ui/dist/index.html +1 -1
  65. package/lib/dist/features/2d/arc-three-points.d.ts +0 -19
  66. package/lib/dist/features/2d/arc-three-points.js +0 -75
  67. package/lib/dist/features/2d/arc-to-point.d.ts +0 -17
  68. package/lib/dist/features/2d/arc-to-point.js +0 -95
@@ -1,6 +1,7 @@
1
1
  import { MeshBuilder } from "./mesh-builder.js";
2
2
  import { PlaneObjectBase } from "../features/plane-renderable-base.js";
3
3
  import { AxisObjectBase } from "../features/axis-renderable-base.js";
4
+ import { Sketch } from "../features/2d/sketch.js";
4
5
  const meshBuilder = new MeshBuilder();
5
6
  function renderSceneObject(obj, scene) {
6
7
  const hasError = !!obj.getError();
@@ -134,7 +135,15 @@ export function renderSceneRollback(scene, rollbackIndex) {
134
135
  export function renderScene(scene) {
135
136
  const sceneObjects = scene.getAllSceneObjects();
136
137
  console.log("============ Rendering ==============", sceneObjects.length);
138
+ const skippedContainers = new Set();
137
139
  for (const object of sceneObjects) {
140
+ // Skip descendants of cloned sketches — their edges are already
141
+ // computed by the parent sketch's clone-mode build.
142
+ const parent = object.getParent();
143
+ if (parent && skippedContainers.has(parent)) {
144
+ skippedContainers.add(object);
145
+ continue;
146
+ }
138
147
  console.log("Rendering object:", object.getUniqueType());
139
148
  const isCached = scene.isCached(object);
140
149
  if (!isCached) {
@@ -171,9 +180,16 @@ export function renderScene(scene) {
171
180
  object.setError(message);
172
181
  }
173
182
  }
183
+ // After building, mark cloned sketches so their children are skipped
184
+ if (object instanceof Sketch && object.getState('cloned-edges')) {
185
+ skippedContainers.add(object);
186
+ }
174
187
  }
175
188
  // Cleanup pass — let objects adjust based on final scene state
176
189
  for (const object of sceneObjects) {
190
+ if (skippedContainers.has(object)) {
191
+ continue;
192
+ }
177
193
  object.clean(scene.getPartScopedAllObjects(object));
178
194
  }
179
195
  for (const object of sceneObjects) {
@@ -10,7 +10,7 @@ describe("arc", () => {
10
10
  it("should create an arc and form a closed shape", () => {
11
11
  sketch("xy", () => {
12
12
  hLine(50);
13
- arc([50, 30], 20);
13
+ arc([50, 30]).radius(20);
14
14
  hLine(-50);
15
15
  vLine(-30);
16
16
  });
@@ -25,7 +25,7 @@ describe("arc", () => {
25
25
  describe("from three points (start, end, center)", () => {
26
26
  it("should create an arc from start to end around a center point", () => {
27
27
  sketch("xy", () => {
28
- arc([0, 0], [20, 0], [10, 0]);
28
+ arc([0, 0], [20, 0]).center([10, 0]);
29
29
  line([0, 0]);
30
30
  });
31
31
  const e = extrude(10);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { setupOC, render } from "../../setup.js";
3
+ import sketch from "../../../core/sketch.js";
4
+ import extrude from "../../../core/extrude.js";
5
+ import { intersect, rect } from "../../../core/2d/index.js";
6
+ describe("intersect", () => {
7
+ setupOC();
8
+ describe("intersect 3D shape with sketch plane", () => {
9
+ it("should produce section edges from a box intersected by a sketch plane", () => {
10
+ sketch("xy", () => {
11
+ rect(100, 50);
12
+ });
13
+ const e = extrude(30);
14
+ const s = sketch("xy", () => {
15
+ intersect(e);
16
+ });
17
+ render();
18
+ const shapes = s.getShapes();
19
+ expect(shapes.length).toBeGreaterThan(0);
20
+ });
21
+ });
22
+ });
@@ -31,7 +31,7 @@ describe("rect", () => {
31
31
  });
32
32
  it("should create a centered rectangle", () => {
33
33
  sketch("xy", () => {
34
- rect(100, 50).center();
34
+ rect(100, 50).centered();
35
35
  });
36
36
  const e = extrude(10);
37
37
  render();
@@ -70,7 +70,7 @@ describe("loft", () => {
70
70
  describe("loft between different shapes", () => {
71
71
  it("should loft between a rect and a circle", () => {
72
72
  const s1 = sketch("xy", () => {
73
- rect(60, 60).center();
73
+ rect(60, 60).centered();
74
74
  });
75
75
  const s2 = sketch(plane("xy", { offset: 50 }), () => {
76
76
  circle(60);
@@ -4,7 +4,7 @@ import sketch from "../../core/sketch.js";
4
4
  import extrude from "../../core/extrude.js";
5
5
  import select from "../../core/select.js";
6
6
  import cylinder from "../../core/cylinder.js";
7
- import { circle, rect } from "../../core/2d/index.js";
7
+ import { circle, move, rect } from "../../core/2d/index.js";
8
8
  import { face, edge } from "../../filters/index.js";
9
9
  describe("select", () => {
10
10
  setupOC();
@@ -75,6 +75,54 @@ describe("select", () => {
75
75
  expect(shapes).toHaveLength(4);
76
76
  });
77
77
  });
78
+ describe("intersectsWith / notIntersectsWith", () => {
79
+ it("should select faces that intersect with a plane", () => {
80
+ sketch("xy", () => {
81
+ move([-50, -25]);
82
+ rect(100, 50);
83
+ });
84
+ extrude(30);
85
+ // XZ plane (y=0) cuts through the centered box.
86
+ // Box spans x:-50..50, y:-25..25, z:0..30.
87
+ // Top, bottom, left, right faces are cut by the plane.
88
+ // Front (y=-25) and back (y=25) are parallel to XZ → not intersected.
89
+ const xzPlane = "xz";
90
+ const sel = select(face().intersectsWith(xzPlane));
91
+ render();
92
+ const shapes = sel.getShapes();
93
+ expect(shapes).toHaveLength(4);
94
+ });
95
+ it("should select faces not intersecting with a plane", () => {
96
+ sketch("xy", () => {
97
+ move([-50, -25]);
98
+ rect(100, 50);
99
+ });
100
+ extrude(30);
101
+ const xzPlane = "xz";
102
+ const sel = select(face().notIntersectsWith(xzPlane));
103
+ render();
104
+ // Front and back faces are parallel to XZ → not intersected
105
+ const shapes = sel.getShapes();
106
+ expect(shapes).toHaveLength(2);
107
+ });
108
+ it("should have complementary results between intersectsWith and notIntersectsWith", () => {
109
+ sketch("xy", () => {
110
+ move([-50, -25]);
111
+ rect(100, 50);
112
+ });
113
+ extrude(30);
114
+ const xzPlane = "xz";
115
+ const intersects = select(face().intersectsWith(xzPlane));
116
+ const notIntersects = select(face().notIntersectsWith(xzPlane));
117
+ render();
118
+ expect(intersects.getShapes()).toHaveLength(4);
119
+ expect(notIntersects.getShapes()).toHaveLength(2);
120
+ // No overlap
121
+ for (const s of intersects.getShapes()) {
122
+ expect(notIntersects.getShapes().some(ns => ns.isSame(s))).toBe(false);
123
+ }
124
+ });
125
+ });
78
126
  describe("circle / notCircle", () => {
79
127
  it("should select circular faces", () => {
80
128
  cylinder(30, 50);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,137 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { setupOC, render } from "../setup.js";
3
+ import sketch from "../../core/sketch.js";
4
+ import extrude from "../../core/extrude.js";
5
+ import { circle, rect, line, hLine, vLine } from "../../core/2d/index.js";
6
+ import { ShapeOps } from "../../oc/shape-ops.js";
7
+ describe("thin extrude", () => {
8
+ setupOC();
9
+ describe("closed profile", () => {
10
+ it("should create a thin-walled solid with single offset", () => {
11
+ sketch("xy", () => {
12
+ rect(100, 100);
13
+ });
14
+ const e = extrude(30).thin(5);
15
+ render();
16
+ const shapes = e.getShapes();
17
+ expect(shapes).toHaveLength(1);
18
+ expect(shapes[0].getType()).toBe('solid');
19
+ const bbox = ShapeOps.getBoundingBox(shapes[0]);
20
+ expect(bbox.maxZ - bbox.minZ).toBeCloseTo(30, 0);
21
+ });
22
+ it("should create a thin-walled solid with dual offset", () => {
23
+ sketch("xy", () => {
24
+ rect(100, 100);
25
+ });
26
+ const e = extrude(20).thin(5, -3).new();
27
+ render();
28
+ const shapes = e.getShapes();
29
+ expect(shapes).toHaveLength(1);
30
+ expect(shapes[0].getType()).toBe('solid');
31
+ const bbox = ShapeOps.getBoundingBox(shapes[0]);
32
+ expect(bbox.maxZ - bbox.minZ).toBeCloseTo(20, 0);
33
+ });
34
+ it("should create a thin-walled solid from a circle", () => {
35
+ sketch("xy", () => {
36
+ circle(50);
37
+ });
38
+ const e = extrude(25).thin(10).new();
39
+ render();
40
+ const shapes = e.getShapes();
41
+ expect(shapes).toHaveLength(1);
42
+ expect(shapes[0].getType()).toBe('solid');
43
+ const bbox = ShapeOps.getBoundingBox(shapes[0]);
44
+ // circle(50) = diameter 50 = radius 25. Outer radius = 25 + 10 = 35, so width = 70
45
+ expect(bbox.maxX - bbox.minX).toBeCloseTo(70, 0);
46
+ expect(bbox.maxZ - bbox.minZ).toBeCloseTo(25, 0);
47
+ });
48
+ });
49
+ describe("offset direction", () => {
50
+ it("should offset inward with a negative value", () => {
51
+ sketch("xy", () => {
52
+ rect(100, 100);
53
+ });
54
+ const e = extrude(20).thin(-5).new();
55
+ render();
56
+ const shapes = e.getShapes();
57
+ expect(shapes).toHaveLength(1);
58
+ expect(shapes[0].getType()).toBe('solid');
59
+ const bbox = ShapeOps.getBoundingBox(shapes[0]);
60
+ // Original rect is 100x100 centered. Inward offset by 5 means outer = original (100),
61
+ // inner = 90. So bounding box should be 100x100.
62
+ expect(bbox.maxX - bbox.minX).toBeCloseTo(100, 0);
63
+ expect(bbox.maxY - bbox.minY).toBeCloseTo(100, 0);
64
+ });
65
+ });
66
+ describe("open profile", () => {
67
+ it("should create a thin-walled solid from an open profile with single offset", () => {
68
+ sketch("xy", () => {
69
+ line([0, 0], [100, 0]);
70
+ });
71
+ const e = extrude(20).thin(10).new();
72
+ render();
73
+ const shapes = e.getShapes();
74
+ expect(shapes).toHaveLength(1);
75
+ expect(shapes[0].getType()).toBe('solid');
76
+ const bbox = ShapeOps.getBoundingBox(shapes[0]);
77
+ expect(bbox.maxX - bbox.minX).toBeCloseTo(100, 0);
78
+ expect(bbox.maxZ - bbox.minZ).toBeCloseTo(20, 0);
79
+ });
80
+ it("should create a thin-walled solid from an open profile with dual offset", () => {
81
+ sketch("xy", () => {
82
+ line([0, 0], [100, 0]);
83
+ });
84
+ const e = extrude(20).thin(5, -10).new();
85
+ render();
86
+ const shapes = e.getShapes();
87
+ expect(shapes).toHaveLength(1);
88
+ expect(shapes[0].getType()).toBe('solid');
89
+ const bbox = ShapeOps.getBoundingBox(shapes[0]);
90
+ expect(bbox.maxX - bbox.minX).toBeCloseTo(100, 0);
91
+ // Total Y extent = |5| + |-10| = 15
92
+ expect(bbox.maxY - bbox.minY).toBeCloseTo(15, 0);
93
+ expect(bbox.maxZ - bbox.minZ).toBeCloseTo(20, 0);
94
+ });
95
+ it("should create a thin-walled solid from an L-shaped open profile", () => {
96
+ sketch("xy", () => {
97
+ hLine([0, 0], 50);
98
+ vLine(50);
99
+ });
100
+ const e = extrude(15).thin(5).new();
101
+ render();
102
+ const shapes = e.getShapes();
103
+ expect(shapes).toHaveLength(1);
104
+ expect(shapes[0].getType()).toBe('solid');
105
+ });
106
+ });
107
+ describe("two-distance extrude", () => {
108
+ it("should create a thin-walled solid with two distances", () => {
109
+ sketch("xy", () => {
110
+ rect(80, 80);
111
+ });
112
+ const e = extrude(20, 10).thin(5).new();
113
+ render();
114
+ const shapes = e.getShapes();
115
+ expect(shapes).toHaveLength(1);
116
+ expect(shapes[0].getType()).toBe('solid');
117
+ const bbox = ShapeOps.getBoundingBox(shapes[0]);
118
+ expect(bbox.maxZ - bbox.minZ).toBeCloseTo(30, 0);
119
+ });
120
+ });
121
+ describe("remove mode", () => {
122
+ it("should cut a thin-walled shape from existing geometry", () => {
123
+ sketch("xy", () => {
124
+ rect(200, 200);
125
+ });
126
+ extrude(50);
127
+ render();
128
+ sketch("xy", () => {
129
+ rect(100, 100);
130
+ });
131
+ const e = extrude(50).thin(10).remove();
132
+ render();
133
+ const shapes = e.getShapes();
134
+ expect(shapes.length).toBeGreaterThan(0);
135
+ });
136
+ });
137
+ });