fluidcad 0.0.21 → 0.0.23
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.
- package/README.md +7 -2
- package/lib/dist/common/shape.d.ts +7 -0
- package/lib/dist/common/shape.js +7 -0
- package/lib/dist/core/copy.js +1 -1
- package/lib/dist/core/draft.d.ts +16 -0
- package/lib/dist/core/draft.js +29 -0
- package/lib/dist/core/index.d.ts +2 -3
- package/lib/dist/core/index.js +1 -1
- package/lib/dist/core/interfaces.d.ts +2 -0
- package/lib/dist/core/part.d.ts +2 -6
- package/lib/dist/core/part.js +12 -22
- package/lib/dist/core/trim.d.ts +12 -5
- package/lib/dist/core/trim.js +7 -2
- package/lib/dist/features/2d/sketch.js +23 -17
- package/lib/dist/features/copy-circular.js +1 -0
- package/lib/dist/features/copy-circular2d.js +1 -0
- package/lib/dist/features/copy-linear.js +4 -5
- package/lib/dist/features/copy-linear2d.js +4 -5
- package/lib/dist/features/draft.d.ts +15 -0
- package/lib/dist/features/draft.js +88 -0
- package/lib/dist/features/extrude-base.js +1 -0
- package/lib/dist/features/remove.d.ts +2 -0
- package/lib/dist/features/remove.js +7 -0
- package/lib/dist/features/trim2d.d.ts +16 -4
- package/lib/dist/features/trim2d.js +80 -29
- package/lib/dist/filters/edge/above-below.d.ts +20 -0
- package/lib/dist/filters/edge/above-below.js +57 -0
- package/lib/dist/filters/edge/edge-filter.d.ts +40 -6
- package/lib/dist/filters/edge/edge-filter.js +76 -8
- package/lib/dist/filters/edge/intersects-with.d.ts +18 -0
- package/lib/dist/filters/edge/intersects-with.js +38 -0
- package/lib/dist/filters/edge/on-plane.d.ts +4 -2
- package/lib/dist/filters/edge/on-plane.js +37 -12
- package/lib/dist/filters/filter-builder-base.d.ts +0 -5
- package/lib/dist/filters/filter-builder-base.js +12 -0
- package/lib/dist/helpers/clone-transform.js +3 -0
- package/lib/dist/oc/draft-ops.d.ts +5 -0
- package/lib/dist/oc/draft-ops.js +51 -0
- package/lib/dist/oc/edge-query.d.ts +5 -1
- package/lib/dist/oc/edge-query.js +40 -0
- package/lib/dist/oc/mesh.d.ts +2 -0
- package/lib/dist/oc/mesh.js +14 -6
- package/lib/dist/rendering/mesh-transform.d.ts +3 -0
- package/lib/dist/rendering/mesh-transform.js +22 -0
- package/lib/dist/rendering/render-solid.js +3 -2
- package/lib/dist/rendering/render.js +28 -6
- package/lib/dist/tests/features/chamfer.test.js +1 -1
- package/lib/dist/tests/features/draft.test.d.ts +1 -0
- package/lib/dist/tests/features/draft.test.js +147 -0
- package/lib/dist/tests/features/fillet.test.js +1 -1
- package/lib/dist/tests/features/part.test.js +69 -114
- package/lib/dist/tests/features/select.test.js +101 -3
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -3
- package/server/dist/fluidcad-server.d.ts +2 -0
- package/server/dist/fluidcad-server.js +10 -0
- package/server/dist/routes/actions.js +20 -0
- package/server/dist/vite-manager.js +7 -1
- package/ui/dist/assets/{index-B1LkrBga.js → index-CqP_mgZk.js} +23 -12
- package/ui/dist/assets/{index-BfcNNxXr.css → index-gPoNOiIs.css} +1 -1
- package/ui/dist/index.html +2 -2
- package/lib/dist/core/use.d.ts +0 -5
- package/lib/dist/core/use.js +0 -22
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { describe, it, expect
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { setupOC, render } from "../setup.js";
|
|
3
|
-
import { getCurrentScene
|
|
3
|
+
import { getCurrentScene } from "../../scene-manager.js";
|
|
4
4
|
import sketch from "../../core/sketch.js";
|
|
5
5
|
import extrude from "../../core/extrude.js";
|
|
6
6
|
import cut from "../../core/cut.js";
|
|
@@ -8,84 +8,67 @@ import repeat from "../../core/repeat.js";
|
|
|
8
8
|
import translate from "../../core/translate.js";
|
|
9
9
|
import { circle, rect } from "../../core/2d/index.js";
|
|
10
10
|
import part from "../../core/part.js";
|
|
11
|
-
import use from "../../core/use.js";
|
|
12
11
|
import { Part } from "../../features/part.js";
|
|
13
12
|
import { Extrude } from "../../features/extrude.js";
|
|
14
13
|
import { Sketch } from "../../features/2d/sketch.js";
|
|
15
14
|
import { countShapes } from "../utils.js";
|
|
16
15
|
describe("part", () => {
|
|
17
16
|
setupOC();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
setCurrentFile('');
|
|
23
|
-
});
|
|
24
|
-
describe("use()", () => {
|
|
25
|
-
it("should execute a part handle via use()", () => {
|
|
26
|
-
const handle = part("my-part", () => {
|
|
27
|
-
sketch("xy", () => {
|
|
28
|
-
circle(10);
|
|
29
|
-
});
|
|
30
|
-
extrude(20);
|
|
17
|
+
it("should execute a part callback immediately", () => {
|
|
18
|
+
part("my-part", () => {
|
|
19
|
+
sketch("xy", () => {
|
|
20
|
+
circle(10);
|
|
31
21
|
});
|
|
32
|
-
|
|
33
|
-
// We need use() to trigger it.
|
|
34
|
-
use(handle);
|
|
35
|
-
const scene = getCurrentScene();
|
|
36
|
-
const objects = scene.getAllSceneObjects();
|
|
37
|
-
// Should have Part, Plane, Sketch, Circle, Extrude
|
|
38
|
-
const parts = objects.filter(o => o instanceof Part);
|
|
39
|
-
expect(parts).toHaveLength(1);
|
|
40
|
-
expect(parts[0].partName).toBe("my-part");
|
|
22
|
+
extrude(20);
|
|
41
23
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
24
|
+
const scene = getCurrentScene();
|
|
25
|
+
const objects = scene.getAllSceneObjects();
|
|
26
|
+
const parts = objects.filter(o => o instanceof Part);
|
|
27
|
+
expect(parts).toHaveLength(1);
|
|
28
|
+
expect(parts[0].partName).toBe("my-part");
|
|
29
|
+
});
|
|
30
|
+
it("should produce a solid", () => {
|
|
31
|
+
part("solid-part", () => {
|
|
32
|
+
sketch("xy", () => {
|
|
33
|
+
circle(10);
|
|
48
34
|
});
|
|
49
|
-
|
|
50
|
-
render();
|
|
51
|
-
const scene = getCurrentScene();
|
|
52
|
-
const objects = scene.getAllSceneObjects();
|
|
53
|
-
const extrudeObj = objects.find(o => o instanceof Extrude);
|
|
54
|
-
expect(extrudeObj).toBeDefined();
|
|
55
|
-
const shapes = extrudeObj.getShapes();
|
|
56
|
-
expect(shapes.length).toBeGreaterThan(0);
|
|
57
|
-
expect(shapes[0].getType()).toBe("solid");
|
|
35
|
+
extrude(20);
|
|
58
36
|
});
|
|
37
|
+
render();
|
|
38
|
+
const scene = getCurrentScene();
|
|
39
|
+
const objects = scene.getAllSceneObjects();
|
|
40
|
+
const extrudeObj = objects.find(o => o instanceof Extrude);
|
|
41
|
+
expect(extrudeObj).toBeDefined();
|
|
42
|
+
const shapes = extrudeObj.getShapes();
|
|
43
|
+
expect(shapes.length).toBeGreaterThan(0);
|
|
44
|
+
expect(shapes[0].getType()).toBe("solid");
|
|
59
45
|
});
|
|
60
46
|
describe("fusion isolation", () => {
|
|
61
47
|
it("should keep two parts as separate solids", () => {
|
|
62
|
-
|
|
48
|
+
part("part1", () => {
|
|
63
49
|
sketch("xy", () => {
|
|
64
50
|
circle(10);
|
|
65
51
|
});
|
|
66
52
|
extrude(20);
|
|
67
53
|
});
|
|
68
|
-
|
|
54
|
+
part("part2", () => {
|
|
69
55
|
sketch("xy", () => {
|
|
70
56
|
circle(10);
|
|
71
57
|
});
|
|
72
58
|
extrude(20);
|
|
73
59
|
});
|
|
74
|
-
use(handle1);
|
|
75
|
-
use(handle2);
|
|
76
60
|
render();
|
|
77
61
|
const scene = getCurrentScene();
|
|
78
62
|
const objects = scene.getAllSceneObjects();
|
|
79
63
|
const extrudes = objects.filter(o => o instanceof Extrude);
|
|
80
64
|
expect(extrudes).toHaveLength(2);
|
|
81
|
-
// Each extrude should have its own solid — they should NOT be fused together
|
|
82
65
|
const shapes1 = extrudes[0].getShapes();
|
|
83
66
|
const shapes2 = extrudes[1].getShapes();
|
|
84
67
|
expect(shapes1.length).toBeGreaterThan(0);
|
|
85
68
|
expect(shapes2.length).toBeGreaterThan(0);
|
|
86
69
|
});
|
|
87
70
|
it("should fuse extrudes within the same part", () => {
|
|
88
|
-
|
|
71
|
+
part("fusing-part", () => {
|
|
89
72
|
sketch("xy", () => {
|
|
90
73
|
circle([-5, 0], 10);
|
|
91
74
|
});
|
|
@@ -95,14 +78,11 @@ describe("part", () => {
|
|
|
95
78
|
});
|
|
96
79
|
extrude(20);
|
|
97
80
|
});
|
|
98
|
-
use(handle);
|
|
99
81
|
render();
|
|
100
82
|
const scene = getCurrentScene();
|
|
101
83
|
const objects = scene.getAllSceneObjects();
|
|
102
84
|
const extrudes = objects.filter(o => o instanceof Extrude);
|
|
103
85
|
expect(extrudes).toHaveLength(2);
|
|
104
|
-
// The second extrude should have fused with the first (overlapping circles)
|
|
105
|
-
// So the first extrude's shapes should have been removed (consumed by fusion)
|
|
106
86
|
const shapes1 = extrudes[0].getShapes();
|
|
107
87
|
const shapes2 = extrudes[1].getShapes();
|
|
108
88
|
expect(shapes1).toHaveLength(0);
|
|
@@ -111,13 +91,12 @@ describe("part", () => {
|
|
|
111
91
|
});
|
|
112
92
|
describe("container structure", () => {
|
|
113
93
|
it("should make sketch a child of the part", () => {
|
|
114
|
-
|
|
94
|
+
part("container-test", () => {
|
|
115
95
|
sketch("xy", () => {
|
|
116
96
|
circle(10);
|
|
117
97
|
});
|
|
118
98
|
extrude(20);
|
|
119
99
|
});
|
|
120
|
-
use(handle);
|
|
121
100
|
const scene = getCurrentScene();
|
|
122
101
|
const objects = scene.getAllSceneObjects();
|
|
123
102
|
const partObj = objects.find(o => o instanceof Part);
|
|
@@ -153,41 +132,35 @@ describe("part", () => {
|
|
|
153
132
|
});
|
|
154
133
|
const e2 = extrude(20);
|
|
155
134
|
render();
|
|
156
|
-
// Second extrude should fuse with first (overlapping)
|
|
157
135
|
const shapes = e2.getShapes();
|
|
158
136
|
expect(shapes.length).toBeGreaterThan(0);
|
|
159
137
|
});
|
|
160
138
|
});
|
|
161
139
|
describe("repeat inside part", () => {
|
|
162
140
|
it("should repeat with explicit objects", () => {
|
|
163
|
-
|
|
141
|
+
part("repeat-explicit", () => {
|
|
164
142
|
sketch("xy", () => {
|
|
165
143
|
rect(50);
|
|
166
144
|
});
|
|
167
145
|
const e = extrude().new();
|
|
168
146
|
repeat("linear", "x", { count: 3, offset: 80 }, e);
|
|
169
147
|
});
|
|
170
|
-
use(handle);
|
|
171
148
|
const scene = render();
|
|
172
|
-
// Original + 2 clones = 3 shapes
|
|
173
149
|
expect(countShapes(scene)).toBe(3);
|
|
174
150
|
});
|
|
175
151
|
it("should repeat with default input (last object)", () => {
|
|
176
|
-
|
|
152
|
+
part("repeat-default", () => {
|
|
177
153
|
sketch("xy", () => {
|
|
178
154
|
rect(50);
|
|
179
155
|
});
|
|
180
156
|
extrude().new();
|
|
181
157
|
repeat("linear", "x", { count: 3, offset: 80 });
|
|
182
158
|
});
|
|
183
|
-
use(handle);
|
|
184
159
|
const scene = render();
|
|
185
160
|
expect(countShapes(scene)).toBe(3);
|
|
186
161
|
});
|
|
187
162
|
it("should not override clone parent-child relationships", () => {
|
|
188
|
-
|
|
189
|
-
// not be reparented to the Part container
|
|
190
|
-
const handle = part("clone-parents", () => {
|
|
163
|
+
part("clone-parents", () => {
|
|
191
164
|
sketch("xy", () => {
|
|
192
165
|
rect(50);
|
|
193
166
|
});
|
|
@@ -198,22 +171,19 @@ describe("part", () => {
|
|
|
198
171
|
const c = cut();
|
|
199
172
|
repeat("linear", "x", { count: 3, offset: 80 }, e, c);
|
|
200
173
|
});
|
|
201
|
-
use(handle);
|
|
202
174
|
const scene = render();
|
|
203
|
-
// Should produce 3 complete instances (original + 2 clones)
|
|
204
175
|
expect(countShapes(scene)).toBe(3);
|
|
205
176
|
});
|
|
206
177
|
});
|
|
207
178
|
describe("pick inside part", () => {
|
|
208
179
|
it("should preserve pick meta shapes on extrude inside a part", () => {
|
|
209
|
-
|
|
180
|
+
part("pick-test", () => {
|
|
210
181
|
sketch("xy", () => {
|
|
211
182
|
rect(50);
|
|
212
183
|
circle(20);
|
|
213
184
|
});
|
|
214
185
|
extrude().pick();
|
|
215
186
|
});
|
|
216
|
-
use(handle);
|
|
217
187
|
const scene = render();
|
|
218
188
|
const rendered = scene.getRenderedObjects();
|
|
219
189
|
const extrudeRender = rendered.find(r => r.type === 'extrude');
|
|
@@ -222,78 +192,71 @@ describe("part", () => {
|
|
|
222
192
|
expect(metaShapes.length).toBeGreaterThan(0);
|
|
223
193
|
});
|
|
224
194
|
it("should preserve pick meta shapes with multiple parts", () => {
|
|
225
|
-
|
|
195
|
+
part("pick-part1", () => {
|
|
226
196
|
sketch("xy", () => {
|
|
227
197
|
rect(50);
|
|
228
198
|
circle(20);
|
|
229
199
|
});
|
|
230
200
|
extrude().pick();
|
|
231
201
|
});
|
|
232
|
-
|
|
202
|
+
part("pick-part2", () => {
|
|
233
203
|
sketch("xy", () => {
|
|
234
204
|
circle(10);
|
|
235
205
|
});
|
|
236
206
|
extrude();
|
|
237
207
|
});
|
|
238
|
-
use(handle1);
|
|
239
|
-
use(handle2);
|
|
240
208
|
const scene = render();
|
|
241
209
|
const rendered = scene.getRenderedObjects();
|
|
242
210
|
const extrudeRenders = rendered.filter(r => r.type === 'extrude');
|
|
243
|
-
// First extrude (pick) should still have meta shapes
|
|
244
211
|
const metaShapes = extrudeRenders[0].sceneShapes.filter(s => s.isMetaShape);
|
|
245
212
|
expect(metaShapes.length).toBeGreaterThan(0);
|
|
246
213
|
});
|
|
247
214
|
});
|
|
248
|
-
describe("
|
|
215
|
+
describe("part() as transform target", () => {
|
|
249
216
|
it("should return an ISceneObject (Part instance)", () => {
|
|
250
|
-
const
|
|
217
|
+
const result = part("ret-test", () => {
|
|
251
218
|
sketch("xy", () => { circle(10); });
|
|
252
219
|
extrude(20);
|
|
253
220
|
});
|
|
254
|
-
const result = use(handle);
|
|
255
221
|
expect(result).toBeDefined();
|
|
256
222
|
expect(result).toBeInstanceOf(Part);
|
|
257
223
|
});
|
|
258
224
|
it("should translate a Part target", () => {
|
|
259
|
-
const
|
|
225
|
+
const p = part("translate-part", () => {
|
|
260
226
|
sketch("xy", () => { rect(20, 20); });
|
|
261
227
|
extrude(10);
|
|
262
228
|
});
|
|
263
|
-
const p = use(handle);
|
|
264
229
|
translate(50, 0, 0, p);
|
|
265
230
|
const scene = render();
|
|
266
231
|
expect(countShapes(scene)).toBe(1);
|
|
267
232
|
});
|
|
268
233
|
it("should translate-copy a Part target", () => {
|
|
269
|
-
const
|
|
234
|
+
const p = part("copy-part", () => {
|
|
270
235
|
sketch("xy", () => { rect(20, 20); });
|
|
271
236
|
extrude(10);
|
|
272
237
|
});
|
|
273
|
-
const p = use(handle);
|
|
274
238
|
translate(50, 0, 0, true, p);
|
|
275
239
|
const scene = render();
|
|
276
|
-
// Original + copy = 2 shapes
|
|
277
240
|
expect(countShapes(scene)).toBe(2);
|
|
278
241
|
});
|
|
279
242
|
it("should transform only the targeted Part", () => {
|
|
280
|
-
const
|
|
281
|
-
sketch("xy", () => { rect(
|
|
282
|
-
extrude(
|
|
243
|
+
const p1 = part("multi-a", () => {
|
|
244
|
+
sketch("xy", () => { rect(10, 10); });
|
|
245
|
+
extrude(10);
|
|
246
|
+
});
|
|
247
|
+
part("multi-b", () => {
|
|
248
|
+
sketch("xy", () => { rect(20, 20); });
|
|
249
|
+
extrude(20);
|
|
283
250
|
});
|
|
284
|
-
const p1 = use(handle, { size: 10 });
|
|
285
|
-
const p2 = use(handle, { size: 20 });
|
|
286
251
|
translate(100, 0, 0, p1);
|
|
287
252
|
const scene = render();
|
|
288
|
-
// Both parts should still produce shapes (2 total)
|
|
289
253
|
expect(countShapes(scene)).toBe(2);
|
|
290
254
|
});
|
|
291
255
|
it("should work inline with translate", () => {
|
|
292
|
-
|
|
256
|
+
translate(50, 0, 0, part("inline-part", () => {
|
|
293
257
|
sketch("xy", () => { circle(10); });
|
|
294
258
|
extrude(20);
|
|
295
|
-
});
|
|
296
|
-
translate(50, 0, 0, use(handle));
|
|
259
|
+
}));
|
|
297
260
|
const scene = render();
|
|
298
261
|
expect(countShapes(scene)).toBe(1);
|
|
299
262
|
});
|
|
@@ -304,15 +267,17 @@ describe("part", () => {
|
|
|
304
267
|
expect(p.isAlwaysVisible()).toBe(true);
|
|
305
268
|
});
|
|
306
269
|
});
|
|
307
|
-
describe("
|
|
308
|
-
it("should
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
270
|
+
describe("parameterized parts via closures", () => {
|
|
271
|
+
it("should support parameterization through closures", () => {
|
|
272
|
+
function makePart(radius, height) {
|
|
273
|
+
return part("opts-part", () => {
|
|
274
|
+
sketch("xy", () => {
|
|
275
|
+
circle(radius);
|
|
276
|
+
});
|
|
277
|
+
extrude(height);
|
|
312
278
|
});
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
use(handle, { radius: 15, height: 30 });
|
|
279
|
+
}
|
|
280
|
+
makePart(15, 30);
|
|
316
281
|
render();
|
|
317
282
|
const scene = getCurrentScene();
|
|
318
283
|
const objects = scene.getAllSceneObjects();
|
|
@@ -325,15 +290,17 @@ describe("part", () => {
|
|
|
325
290
|
expect(shapes.length).toBeGreaterThan(0);
|
|
326
291
|
expect(shapes[0].getType()).toBe("solid");
|
|
327
292
|
});
|
|
328
|
-
it("should
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
293
|
+
it("should create separate parts with different parameters", () => {
|
|
294
|
+
function makePart(name, size) {
|
|
295
|
+
return part(name, () => {
|
|
296
|
+
sketch("xy", () => {
|
|
297
|
+
circle(size);
|
|
298
|
+
});
|
|
299
|
+
extrude(size);
|
|
332
300
|
});
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
use(handle, { size: 20 });
|
|
301
|
+
}
|
|
302
|
+
makePart("reusable-a", 10);
|
|
303
|
+
makePart("reusable-b", 20);
|
|
337
304
|
render();
|
|
338
305
|
const scene = getCurrentScene();
|
|
339
306
|
const objects = scene.getAllSceneObjects();
|
|
@@ -341,16 +308,4 @@ describe("part", () => {
|
|
|
341
308
|
expect(parts).toHaveLength(2);
|
|
342
309
|
});
|
|
343
310
|
});
|
|
344
|
-
describe("PartHandle", () => {
|
|
345
|
-
it("should have correct properties", () => {
|
|
346
|
-
const handle = part("test-handle", () => { });
|
|
347
|
-
expect(handle.__fluidcad_part).toBe(true);
|
|
348
|
-
expect(handle.name).toBe("test-handle");
|
|
349
|
-
expect(handle._callback).toBeInstanceOf(Function);
|
|
350
|
-
});
|
|
351
|
-
it("should throw when use() is given invalid input", () => {
|
|
352
|
-
expect(() => use(null)).toThrow("use() expects a PartHandle");
|
|
353
|
-
expect(() => use({})).toThrow("use() expects a PartHandle");
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
311
|
});
|
|
@@ -222,7 +222,7 @@ describe("select", () => {
|
|
|
222
222
|
rect(100, 50);
|
|
223
223
|
});
|
|
224
224
|
extrude(30);
|
|
225
|
-
const sel = select(edge().onPlane("xy", 30));
|
|
225
|
+
const sel = select(edge().onPlane("xy", { offset: 30 }));
|
|
226
226
|
render();
|
|
227
227
|
expect(sel.getShapes()).toHaveLength(4);
|
|
228
228
|
});
|
|
@@ -236,6 +236,104 @@ describe("select", () => {
|
|
|
236
236
|
render();
|
|
237
237
|
expect(sel.getShapes()).toHaveLength(8);
|
|
238
238
|
});
|
|
239
|
+
it("should select edges on offset plane with bothDirections", () => {
|
|
240
|
+
sketch("xy", () => {
|
|
241
|
+
rect(100, 50);
|
|
242
|
+
});
|
|
243
|
+
extrude(30);
|
|
244
|
+
// Edges on z=15 or z=-15 — none exist on an extruded box
|
|
245
|
+
const sel = select(edge().onPlane("xy", { offset: 15, bothDirections: true }));
|
|
246
|
+
render();
|
|
247
|
+
expect(sel.getShapes()).toHaveLength(0);
|
|
248
|
+
});
|
|
249
|
+
it("should select edges with partial match", () => {
|
|
250
|
+
sketch("xy", () => {
|
|
251
|
+
rect(100, 50);
|
|
252
|
+
});
|
|
253
|
+
extrude(30);
|
|
254
|
+
// partial: true matches edges with at least one vertex on the plane
|
|
255
|
+
// Bottom 4 edges are fully on XY, plus the 4 vertical edges each have one vertex on XY
|
|
256
|
+
const sel = select(edge().onPlane("xy", { partial: true }));
|
|
257
|
+
render();
|
|
258
|
+
expect(sel.getShapes()).toHaveLength(8);
|
|
259
|
+
});
|
|
260
|
+
it("should exclude edges with partial notOnPlane", () => {
|
|
261
|
+
sketch("xy", () => {
|
|
262
|
+
rect(100, 50);
|
|
263
|
+
});
|
|
264
|
+
extrude(30);
|
|
265
|
+
// notOnPlane partial: true excludes edges where any vertex touches XY
|
|
266
|
+
// Only the 4 top edges (z=30) have no vertex on XY
|
|
267
|
+
const sel = select(edge().notOnPlane("xy", { partial: true }));
|
|
268
|
+
render();
|
|
269
|
+
expect(sel.getShapes()).toHaveLength(4);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
describe("above / below", () => {
|
|
273
|
+
it("should select edges entirely above a plane", () => {
|
|
274
|
+
sketch("xy", () => {
|
|
275
|
+
rect(100, 50);
|
|
276
|
+
});
|
|
277
|
+
extrude(30);
|
|
278
|
+
// Edges above z=10: the 4 top edges (z=30) have both vertices above
|
|
279
|
+
// The 4 vertical edges straddle z=10, so they don't match
|
|
280
|
+
const sel = select(edge().above("xy", { offset: 10 }));
|
|
281
|
+
render();
|
|
282
|
+
expect(sel.getShapes()).toHaveLength(4);
|
|
283
|
+
});
|
|
284
|
+
it("should select edges entirely below a plane", () => {
|
|
285
|
+
sketch("xy", () => {
|
|
286
|
+
rect(100, 50);
|
|
287
|
+
});
|
|
288
|
+
extrude(30);
|
|
289
|
+
// Edges below z=10: the 4 bottom edges (z=0) have both vertices below
|
|
290
|
+
const sel = select(edge().below("xy", { offset: 10 }));
|
|
291
|
+
render();
|
|
292
|
+
expect(sel.getShapes()).toHaveLength(4);
|
|
293
|
+
});
|
|
294
|
+
it("should not match edges on the plane itself", () => {
|
|
295
|
+
sketch("xy", () => {
|
|
296
|
+
rect(100, 50);
|
|
297
|
+
});
|
|
298
|
+
extrude(30);
|
|
299
|
+
// Edges above z=0: bottom edges are ON the plane (dist=0), not above
|
|
300
|
+
// Only top 4 edges are above, vertical edges straddle
|
|
301
|
+
const sel = select(edge().above("xy"));
|
|
302
|
+
render();
|
|
303
|
+
expect(sel.getShapes()).toHaveLength(4);
|
|
304
|
+
});
|
|
305
|
+
it("should select partially above edges", () => {
|
|
306
|
+
sketch("xy", () => {
|
|
307
|
+
rect(100, 50);
|
|
308
|
+
});
|
|
309
|
+
extrude(30);
|
|
310
|
+
// partial: true — match edges where at least one vertex is above z=0
|
|
311
|
+
// Top 4 edges (both vertices above) + 4 vertical edges (one vertex above)
|
|
312
|
+
const sel = select(edge().above("xy", { partial: true }));
|
|
313
|
+
render();
|
|
314
|
+
expect(sel.getShapes()).toHaveLength(8);
|
|
315
|
+
});
|
|
316
|
+
it("should select partially below edges", () => {
|
|
317
|
+
sketch("xy", () => {
|
|
318
|
+
rect(100, 50);
|
|
319
|
+
});
|
|
320
|
+
extrude(30);
|
|
321
|
+
// partial: true — match edges where at least one vertex is below z=30
|
|
322
|
+
// Bottom 4 edges (both vertices below) + 4 vertical edges (one vertex below)
|
|
323
|
+
const sel = select(edge().below("xy", { offset: 30, partial: true }));
|
|
324
|
+
render();
|
|
325
|
+
expect(sel.getShapes()).toHaveLength(8);
|
|
326
|
+
});
|
|
327
|
+
it("should return empty when no edges match", () => {
|
|
328
|
+
sketch("xy", () => {
|
|
329
|
+
rect(100, 50);
|
|
330
|
+
});
|
|
331
|
+
extrude(30);
|
|
332
|
+
// Nothing is below z=0 on a box sitting on XY
|
|
333
|
+
const sel = select(edge().below("xy"));
|
|
334
|
+
render();
|
|
335
|
+
expect(sel.getShapes()).toHaveLength(0);
|
|
336
|
+
});
|
|
239
337
|
});
|
|
240
338
|
describe("parallelTo / notParallelTo", () => {
|
|
241
339
|
it("should select edges parallel to a plane", () => {
|
|
@@ -369,7 +467,7 @@ describe("select", () => {
|
|
|
369
467
|
extrude(30);
|
|
370
468
|
// Faces that have both a line edge on XY AND a line edge on XY offset 30
|
|
371
469
|
// The 4 side faces each have edges on both bottom and top planes
|
|
372
|
-
const sel = select(face().hasEdge(edge().onPlane("xy"), edge().onPlane("xy", 30)));
|
|
470
|
+
const sel = select(face().hasEdge(edge().onPlane("xy"), edge().onPlane("xy", { offset: 30 })));
|
|
373
471
|
render();
|
|
374
472
|
expect(sel.getShapes()).toHaveLength(4);
|
|
375
473
|
});
|
|
@@ -459,7 +557,7 @@ describe("select", () => {
|
|
|
459
557
|
});
|
|
460
558
|
extrude(30);
|
|
461
559
|
// Edges on bottom OR edges on top
|
|
462
|
-
const sel = select(edge().onPlane("xy"), edge().onPlane("xy", 30));
|
|
560
|
+
const sel = select(edge().onPlane("xy"), edge().onPlane("xy", { offset: 30 }));
|
|
463
561
|
render();
|
|
464
562
|
expect(sel.getShapes()).toHaveLength(8);
|
|
465
563
|
});
|