fluidcad 0.0.28 → 0.0.30
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/lib/dist/common/profiler.d.ts +12 -0
- package/lib/dist/common/profiler.js +35 -0
- package/lib/dist/common/scene-object.d.ts +3 -0
- package/lib/dist/common/scene-object.js +3 -0
- package/lib/dist/common/shape-history-tracker.d.ts +9 -1
- package/lib/dist/common/shape-history-tracker.js +37 -23
- package/lib/dist/core/2d/aline.d.ts +13 -13
- package/lib/dist/core/2d/aline.js +20 -11
- package/lib/dist/core/2d/arc.d.ts +6 -6
- package/lib/dist/core/2d/arc.js +19 -15
- package/lib/dist/core/2d/back.d.ts +12 -0
- package/lib/dist/core/2d/back.js +11 -0
- package/lib/dist/core/2d/circle.d.ts +2 -2
- package/lib/dist/core/2d/circle.js +14 -10
- package/lib/dist/core/2d/ellipse.d.ts +35 -0
- package/lib/dist/core/2d/ellipse.js +65 -0
- package/lib/dist/core/2d/hline.d.ts +20 -13
- package/lib/dist/core/2d/hline.js +33 -15
- package/lib/dist/core/2d/index.d.ts +2 -0
- package/lib/dist/core/2d/index.js +2 -0
- package/lib/dist/core/2d/intersect.d.ts +2 -2
- package/lib/dist/core/2d/intersect.js +7 -3
- package/lib/dist/core/2d/line.d.ts +2 -2
- package/lib/dist/core/2d/line.js +14 -10
- package/lib/dist/core/2d/offset.d.ts +4 -4
- package/lib/dist/core/2d/offset.js +9 -5
- package/lib/dist/core/2d/polygon.d.ts +4 -4
- package/lib/dist/core/2d/polygon.js +24 -20
- package/lib/dist/core/2d/project.d.ts +2 -2
- package/lib/dist/core/2d/project.js +7 -3
- package/lib/dist/core/2d/rect.d.ts +2 -2
- package/lib/dist/core/2d/rect.js +22 -21
- package/lib/dist/core/2d/slot.d.ts +6 -6
- package/lib/dist/core/2d/slot.js +29 -32
- package/lib/dist/core/2d/vline.d.ts +20 -13
- package/lib/dist/core/2d/vline.js +29 -15
- package/lib/dist/core/interfaces.d.ts +62 -0
- package/lib/dist/core/mirror.d.ts +7 -7
- package/lib/dist/core/mirror.js +17 -11
- package/lib/dist/core/part.d.ts +3 -1
- package/lib/dist/core/part.js +1 -1
- package/lib/dist/core/rotate.d.ts +5 -5
- package/lib/dist/core/rotate.js +4 -1
- package/lib/dist/core/sketch.d.ts +3 -1
- package/lib/dist/core/sketch.js +1 -1
- package/lib/dist/core/translate.d.ts +9 -9
- package/lib/dist/features/2d/aline.d.ts +8 -5
- package/lib/dist/features/2d/aline.js +70 -18
- package/lib/dist/features/2d/back.d.ts +14 -0
- package/lib/dist/features/2d/back.js +35 -0
- package/lib/dist/features/2d/ellipse.d.ts +23 -0
- package/lib/dist/features/2d/ellipse.js +75 -0
- package/lib/dist/features/2d/hline.d.ts +9 -4
- package/lib/dist/features/2d/hline.js +65 -14
- package/lib/dist/features/2d/offset.d.ts +3 -0
- package/lib/dist/features/2d/offset.js +27 -3
- package/lib/dist/features/2d/sketch.d.ts +1 -0
- package/lib/dist/features/2d/sketch.js +15 -0
- package/lib/dist/features/2d/vline.d.ts +9 -4
- package/lib/dist/features/2d/vline.js +67 -15
- package/lib/dist/features/common.js +2 -1
- package/lib/dist/features/extrude-base.d.ts +19 -1
- package/lib/dist/features/extrude-base.js +75 -12
- package/lib/dist/features/extrude-two-distances.js +32 -27
- package/lib/dist/features/extrude.d.ts +39 -0
- package/lib/dist/features/extrude.js +196 -156
- package/lib/dist/features/fuse.js +2 -1
- package/lib/dist/features/lazy-scene-object.d.ts +1 -0
- package/lib/dist/features/lazy-scene-object.js +3 -0
- package/lib/dist/features/lazy-vertex.d.ts +1 -0
- package/lib/dist/features/lazy-vertex.js +3 -0
- package/lib/dist/features/loft.js +11 -8
- package/lib/dist/features/mirror-shape.d.ts +2 -0
- package/lib/dist/features/mirror-shape.js +16 -0
- package/lib/dist/features/mirror-shape2d.d.ts +2 -0
- package/lib/dist/features/mirror-shape2d.js +22 -1
- package/lib/dist/features/revolve.d.ts +31 -0
- package/lib/dist/features/revolve.js +178 -95
- package/lib/dist/features/rotate.d.ts +2 -0
- package/lib/dist/features/rotate.js +16 -0
- package/lib/dist/features/rotate2d.d.ts +2 -0
- package/lib/dist/features/rotate2d.js +16 -0
- package/lib/dist/features/select.js +2 -1
- package/lib/dist/features/simple-extruder.d.ts +3 -1
- package/lib/dist/features/simple-extruder.js +13 -9
- package/lib/dist/features/subtract.d.ts +2 -2
- package/lib/dist/features/subtract.js +3 -3
- package/lib/dist/features/sweep.d.ts +14 -0
- package/lib/dist/features/sweep.js +93 -80
- package/lib/dist/features/translate.d.ts +2 -0
- package/lib/dist/features/translate.js +23 -2
- package/lib/dist/filters/edge/edge-filter.d.ts +6 -0
- package/lib/dist/filters/edge/edge-filter.js +11 -0
- package/lib/dist/filters/face/face-filter.d.ts +6 -0
- package/lib/dist/filters/face/face-filter.js +11 -0
- package/lib/dist/filters/filter-base.d.ts +7 -1
- package/lib/dist/filters/filter-base.js +8 -0
- package/lib/dist/filters/filter-builder-base.js +11 -0
- package/lib/dist/filters/from-object.d.ts +14 -0
- package/lib/dist/filters/from-object.js +40 -0
- package/lib/dist/helpers/scene-helpers.d.ts +2 -0
- package/lib/dist/helpers/scene-helpers.js +68 -48
- package/lib/dist/oc/color-transfer.js +6 -0
- package/lib/dist/oc/edge-ops.d.ts +1 -0
- package/lib/dist/oc/edge-ops.js +17 -0
- package/lib/dist/oc/extrude-ops.d.ts +18 -1
- package/lib/dist/oc/extrude-ops.js +34 -1
- package/lib/dist/oc/geometry.d.ts +1 -0
- package/lib/dist/oc/geometry.js +27 -0
- package/lib/dist/oc/mesh.js +11 -9
- package/lib/dist/oc/ray-intersect.d.ts +16 -0
- package/lib/dist/oc/ray-intersect.js +91 -0
- package/lib/dist/oc/thin-face-maker.d.ts +0 -1
- package/lib/dist/oc/thin-face-maker.js +2 -20
- package/lib/dist/rendering/render.d.ts +2 -1
- package/lib/dist/rendering/render.js +72 -33
- package/lib/dist/rendering/scene.d.ts +4 -0
- package/lib/dist/tests/features/2d/back.test.d.ts +1 -0
- package/lib/dist/tests/features/2d/back.test.js +60 -0
- package/lib/dist/tests/features/2d/circle.test.js +1 -1
- package/lib/dist/tests/features/2d/constrained.test.js +4 -4
- package/lib/dist/tests/features/2d/ellipse.test.d.ts +1 -0
- package/lib/dist/tests/features/2d/ellipse.test.js +100 -0
- package/lib/dist/tests/features/2d/line.test.js +89 -3
- package/lib/dist/tests/features/2d/offset.test.js +1 -1
- package/lib/dist/tests/features/2d/polygon.test.js +2 -2
- package/lib/dist/tests/features/2d/rect.test.js +1 -1
- package/lib/dist/tests/features/2d/slot-from-edge.test.js +1 -1
- package/lib/dist/tests/features/2d/slot.test.js +1 -1
- package/lib/dist/tests/features/mirror.test.js +58 -0
- package/lib/dist/tests/features/mirror2d.test.js +63 -0
- package/lib/dist/tests/features/rotate.test.js +62 -0
- package/lib/dist/tests/features/rotate2d.test.js +47 -0
- package/lib/dist/tests/features/thin-revolve.test.js +37 -1
- package/lib/dist/tests/features/translate.test.js +63 -0
- package/lib/dist/tests/perf/record-fusion-history.bench.test.d.ts +1 -0
- package/lib/dist/tests/perf/record-fusion-history.bench.test.js +77 -0
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/server/dist/index.js +77 -45
- package/server/dist/ws-protocol.d.ts +11 -0
- package/ui/dist/assets/{index-BrW_x4uc.js → index-6Ep4GPxf.js} +131 -77
- package/ui/dist/assets/index-DRKfe6N9.css +2 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-gPoNOiIs.css +0 -2
|
@@ -109,4 +109,62 @@ describe("mirror (3D)", () => {
|
|
|
109
109
|
expect(countShapes(scene)).toBe(1);
|
|
110
110
|
});
|
|
111
111
|
});
|
|
112
|
+
describe("mirror with .exclude()", () => {
|
|
113
|
+
it("should skip excluded objects when mirroring everything", () => {
|
|
114
|
+
sketch("xy", () => {
|
|
115
|
+
move([20, 0]);
|
|
116
|
+
rect(30, 30);
|
|
117
|
+
});
|
|
118
|
+
const e1 = extrude(10).new();
|
|
119
|
+
sketch("xy", () => {
|
|
120
|
+
move([50, 50]);
|
|
121
|
+
rect(30, 30);
|
|
122
|
+
});
|
|
123
|
+
extrude(10).new();
|
|
124
|
+
// mirror all, but exclude e1 → only e2 gets mirrored
|
|
125
|
+
// e1 + e2 + mirror(e2) = 3 shapes (e2 is well clear of YZ plane, no fusion)
|
|
126
|
+
mirror("yz").exclude(e1);
|
|
127
|
+
const scene = render();
|
|
128
|
+
expect(countShapes(scene)).toBe(3);
|
|
129
|
+
});
|
|
130
|
+
it("should narrow an explicit target list with exclude", () => {
|
|
131
|
+
sketch("xy", () => {
|
|
132
|
+
move([20, 0]);
|
|
133
|
+
rect(30, 30);
|
|
134
|
+
});
|
|
135
|
+
const e1 = extrude(10).new();
|
|
136
|
+
sketch("xy", () => {
|
|
137
|
+
move([50, 50]);
|
|
138
|
+
rect(30, 30);
|
|
139
|
+
});
|
|
140
|
+
const e2 = extrude(10).new();
|
|
141
|
+
// explicit target [e1, e2], exclude e2 → only e1 mirrored
|
|
142
|
+
// e1 + e2 + mirror(e1) = 3
|
|
143
|
+
mirror("yz", e1, e2).exclude(e2);
|
|
144
|
+
const scene = render();
|
|
145
|
+
expect(countShapes(scene)).toBe(3);
|
|
146
|
+
});
|
|
147
|
+
it("should accumulate exclusions across chained calls", () => {
|
|
148
|
+
sketch("xy", () => {
|
|
149
|
+
move([20, 0]);
|
|
150
|
+
rect(30, 30);
|
|
151
|
+
});
|
|
152
|
+
const e1 = extrude(10).new();
|
|
153
|
+
sketch("xy", () => {
|
|
154
|
+
move([50, 50]);
|
|
155
|
+
rect(30, 30);
|
|
156
|
+
});
|
|
157
|
+
const e2 = extrude(10).new();
|
|
158
|
+
sketch("xy", () => {
|
|
159
|
+
move([100, 100]);
|
|
160
|
+
rect(30, 30);
|
|
161
|
+
});
|
|
162
|
+
extrude(10).new();
|
|
163
|
+
// mirror everything but exclude e1 and e2 across two chained .exclude() calls
|
|
164
|
+
// e1 + e2 + e3 + mirror(e3) = 4 (all on +X side, mirror lands on -X side)
|
|
165
|
+
mirror("yz").exclude(e1).exclude(e2);
|
|
166
|
+
const scene = render();
|
|
167
|
+
expect(countShapes(scene)).toBe(4);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
112
170
|
});
|
|
@@ -106,4 +106,67 @@ describe("mirror (2D)", () => {
|
|
|
106
106
|
expect(() => local("y")).toThrow();
|
|
107
107
|
});
|
|
108
108
|
});
|
|
109
|
+
describe("mirror with .exclude()", () => {
|
|
110
|
+
it("should skip an excluded geometry when mirroring all sketch siblings", () => {
|
|
111
|
+
sketch("xy", () => {
|
|
112
|
+
move([30, 0]);
|
|
113
|
+
const c1 = circle(10);
|
|
114
|
+
move([30, 50]);
|
|
115
|
+
const c2 = circle(10);
|
|
116
|
+
// mirror everything in the sketch but skip c2
|
|
117
|
+
mirror("y").exclude(c2);
|
|
118
|
+
// sanity: keep references referenced
|
|
119
|
+
void c1;
|
|
120
|
+
});
|
|
121
|
+
const e = extrude(5);
|
|
122
|
+
render();
|
|
123
|
+
const shapes = e.getShapes();
|
|
124
|
+
expect(shapes.length).toBeGreaterThanOrEqual(3);
|
|
125
|
+
const bboxes = shapes.map(s => ShapeOps.getBoundingBox(s));
|
|
126
|
+
// the c2 mirror would have minX < -10 AND minY > 40 — should NOT exist
|
|
127
|
+
const c2MirrorPresent = bboxes.some(b => b.minX < -10 && b.minY > 40);
|
|
128
|
+
expect(c2MirrorPresent).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
it("should narrow an explicit target list with exclude", () => {
|
|
131
|
+
sketch("xy", () => {
|
|
132
|
+
move([30, 0]);
|
|
133
|
+
const c1 = circle(10);
|
|
134
|
+
move([30, 50]);
|
|
135
|
+
const c2 = circle(10);
|
|
136
|
+
// explicit targets [c1, c2], then exclude c2 → only c1 is mirrored
|
|
137
|
+
mirror("y", c1, c2).exclude(c2);
|
|
138
|
+
});
|
|
139
|
+
const e = extrude(5);
|
|
140
|
+
render();
|
|
141
|
+
const shapes = e.getShapes();
|
|
142
|
+
const bboxes = shapes.map(s => ShapeOps.getBoundingBox(s));
|
|
143
|
+
// c2 mirror at (-30, 50) should be absent
|
|
144
|
+
const c2MirrorPresent = bboxes.some(b => b.minX < -10 && b.minY > 40);
|
|
145
|
+
expect(c2MirrorPresent).toBe(false);
|
|
146
|
+
// c1 mirror at (-30, 0) should be present
|
|
147
|
+
const c1MirrorPresent = bboxes.some(b => b.minX < -10 && b.maxY < 40);
|
|
148
|
+
expect(c1MirrorPresent).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
it("should accumulate exclusions across chained calls", () => {
|
|
151
|
+
sketch("xy", () => {
|
|
152
|
+
move([30, 0]);
|
|
153
|
+
const c1 = circle(10);
|
|
154
|
+
move([30, 50]);
|
|
155
|
+
const c2 = circle(10);
|
|
156
|
+
move([30, 100]);
|
|
157
|
+
const c3 = circle(10);
|
|
158
|
+
// exclude c1 and c2 in two calls; only c3 should be mirrored
|
|
159
|
+
mirror("y").exclude(c1).exclude(c2);
|
|
160
|
+
void c3;
|
|
161
|
+
});
|
|
162
|
+
const e = extrude(5);
|
|
163
|
+
render();
|
|
164
|
+
const shapes = e.getShapes();
|
|
165
|
+
const bboxes = shapes.map(s => ShapeOps.getBoundingBox(s));
|
|
166
|
+
// c1 mirror (-30, 0) absent
|
|
167
|
+
expect(bboxes.some(b => b.minX < -10 && b.maxY < 40)).toBe(false);
|
|
168
|
+
// c3 mirror (-30, 100) present
|
|
169
|
+
expect(bboxes.some(b => b.minX < -10 && b.minY > 90)).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
109
172
|
});
|
|
@@ -130,4 +130,66 @@ describe("rotate", () => {
|
|
|
130
130
|
expect(bbox.maxY).toBeCloseTo(0, 0);
|
|
131
131
|
});
|
|
132
132
|
});
|
|
133
|
+
describe("rotate with .exclude()", () => {
|
|
134
|
+
it("should skip excluded objects when rotating everything", () => {
|
|
135
|
+
sketch("xy", () => {
|
|
136
|
+
rect(20, 10);
|
|
137
|
+
});
|
|
138
|
+
const e1 = extrude(5).new();
|
|
139
|
+
sketch("xy", () => {
|
|
140
|
+
rect(20, 10);
|
|
141
|
+
});
|
|
142
|
+
const e2 = extrude(5).new();
|
|
143
|
+
// No explicit target → rotate all, exclude e1 → only e2 rotates
|
|
144
|
+
rotate("z", 90, true).exclude(e1);
|
|
145
|
+
render();
|
|
146
|
+
// e1 still at (0..20, 0..10) — unrotated
|
|
147
|
+
expect(e1.getShapes()).toHaveLength(1);
|
|
148
|
+
const e1Bbox = ShapeOps.getBoundingBox(e1.getShapes()[0]);
|
|
149
|
+
expect(e1Bbox.maxX).toBeCloseTo(20, 0);
|
|
150
|
+
expect(e1Bbox.maxY).toBeCloseTo(10, 0);
|
|
151
|
+
// e2 still present (copy=true)
|
|
152
|
+
expect(e2.getShapes()).toHaveLength(1);
|
|
153
|
+
});
|
|
154
|
+
it("should narrow an explicit target list with exclude", () => {
|
|
155
|
+
sketch("xy", () => {
|
|
156
|
+
rect(20, 10);
|
|
157
|
+
});
|
|
158
|
+
const e1 = extrude(5).new();
|
|
159
|
+
sketch("xy", () => {
|
|
160
|
+
rect(20, 10);
|
|
161
|
+
});
|
|
162
|
+
const e2 = extrude(5).new();
|
|
163
|
+
// Explicit targets [e1, e2], exclude e2 → only e1 rotated
|
|
164
|
+
rotate("z", 90, e1, e2).exclude(e2);
|
|
165
|
+
render();
|
|
166
|
+
// e1 moved (removed from source), e2 stays
|
|
167
|
+
expect(e1.getShapes()).toHaveLength(0);
|
|
168
|
+
expect(e2.getShapes()).toHaveLength(1);
|
|
169
|
+
const e2Bbox = ShapeOps.getBoundingBox(e2.getShapes()[0]);
|
|
170
|
+
expect(e2Bbox.maxX).toBeCloseTo(20, 0);
|
|
171
|
+
expect(e2Bbox.maxY).toBeCloseTo(10, 0);
|
|
172
|
+
});
|
|
173
|
+
it("should accumulate exclusions across chained calls", () => {
|
|
174
|
+
sketch("xy", () => {
|
|
175
|
+
rect(20, 10);
|
|
176
|
+
});
|
|
177
|
+
const e1 = extrude(5).new();
|
|
178
|
+
sketch("xy", () => {
|
|
179
|
+
rect(20, 10);
|
|
180
|
+
});
|
|
181
|
+
const e2 = extrude(5).new();
|
|
182
|
+
sketch("xy", () => {
|
|
183
|
+
rect(20, 10);
|
|
184
|
+
});
|
|
185
|
+
const e3 = extrude(5).new();
|
|
186
|
+
// Rotate all (copy), exclude e1 and e2 across two calls → only e3 rotated copy
|
|
187
|
+
rotate("z", 90, true).exclude(e1).exclude(e2);
|
|
188
|
+
render();
|
|
189
|
+
// e1 and e2 unchanged at (0..20, 0..10)
|
|
190
|
+
expect(ShapeOps.getBoundingBox(e1.getShapes()[0]).maxY).toBeCloseTo(10, 0);
|
|
191
|
+
expect(ShapeOps.getBoundingBox(e2.getShapes()[0]).maxY).toBeCloseTo(10, 0);
|
|
192
|
+
expect(e3.getShapes()).toHaveLength(1);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
133
195
|
});
|
|
@@ -71,4 +71,51 @@ describe("rotate 2D", () => {
|
|
|
71
71
|
expect(shapes.length).toBeGreaterThanOrEqual(1);
|
|
72
72
|
});
|
|
73
73
|
});
|
|
74
|
+
describe("rotate (2D) with .exclude()", () => {
|
|
75
|
+
it("should skip an excluded geometry when rotating all sketch siblings", () => {
|
|
76
|
+
let rot;
|
|
77
|
+
sketch("xy", () => {
|
|
78
|
+
move([30, 0]);
|
|
79
|
+
const r1 = rect(20, 10);
|
|
80
|
+
move([30, 50]);
|
|
81
|
+
const r2 = rect(20, 10);
|
|
82
|
+
rot = rotate(90, true).exclude(r2);
|
|
83
|
+
void r1;
|
|
84
|
+
});
|
|
85
|
+
render();
|
|
86
|
+
// Only r1 should have been rotated (r2 excluded)
|
|
87
|
+
// 1 rotated rect = 4 edges
|
|
88
|
+
expect(rot.getAddedShapes()).toHaveLength(4);
|
|
89
|
+
});
|
|
90
|
+
it("should narrow an explicit target list with exclude", () => {
|
|
91
|
+
let rot;
|
|
92
|
+
sketch("xy", () => {
|
|
93
|
+
move([30, 0]);
|
|
94
|
+
const r1 = rect(20, 10);
|
|
95
|
+
move([30, 50]);
|
|
96
|
+
const r2 = rect(20, 10);
|
|
97
|
+
rot = rotate(90, true, r1, r2).exclude(r2);
|
|
98
|
+
});
|
|
99
|
+
render();
|
|
100
|
+
// 1 rotated rect = 4 edges
|
|
101
|
+
expect(rot.getAddedShapes()).toHaveLength(4);
|
|
102
|
+
});
|
|
103
|
+
it("should accumulate exclusions across chained calls", () => {
|
|
104
|
+
let rot;
|
|
105
|
+
sketch("xy", () => {
|
|
106
|
+
move([30, 0]);
|
|
107
|
+
const r1 = rect(20, 10);
|
|
108
|
+
move([30, 50]);
|
|
109
|
+
const r2 = rect(20, 10);
|
|
110
|
+
move([30, 100]);
|
|
111
|
+
const r3 = rect(20, 10);
|
|
112
|
+
rot = rotate(90, true).exclude(r1).exclude(r2);
|
|
113
|
+
void r3;
|
|
114
|
+
});
|
|
115
|
+
render();
|
|
116
|
+
// Only r3 rotated; r1 and r2 excluded
|
|
117
|
+
// 1 rotated rect = 4 edges
|
|
118
|
+
expect(rot.getAddedShapes()).toHaveLength(4);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
74
121
|
});
|
|
@@ -3,7 +3,7 @@ import { setupOC, render } from "../setup.js";
|
|
|
3
3
|
import sketch from "../../core/sketch.js";
|
|
4
4
|
import revolve from "../../core/revolve.js";
|
|
5
5
|
import extrude from "../../core/extrude.js";
|
|
6
|
-
import { move, rect, circle, line } from "../../core/2d/index.js";
|
|
6
|
+
import { move, rect, circle, line, vLine } from "../../core/2d/index.js";
|
|
7
7
|
import { ShapeProps } from "../../oc/props.js";
|
|
8
8
|
import { EdgeQuery } from "../../oc/edge-query.js";
|
|
9
9
|
describe("thin revolve", () => {
|
|
@@ -133,6 +133,42 @@ describe("thin revolve", () => {
|
|
|
133
133
|
expect(internalFaces.length).toBeGreaterThan(0);
|
|
134
134
|
expect(capFaces.length).toBe(2);
|
|
135
135
|
});
|
|
136
|
+
it("should classify cap faces for vertical line profile parallel to axis (90 deg)", () => {
|
|
137
|
+
sketch("xy", () => {
|
|
138
|
+
move([50, 0]);
|
|
139
|
+
vLine(100).centered();
|
|
140
|
+
});
|
|
141
|
+
const r = revolve("y", -90).thin(20).new();
|
|
142
|
+
render();
|
|
143
|
+
const startFaces = r.getState('start-faces');
|
|
144
|
+
const endFaces = r.getState('end-faces');
|
|
145
|
+
const sideFaces = r.getState('side-faces');
|
|
146
|
+
const internalFaces = r.getState('internal-faces');
|
|
147
|
+
const capFaces = r.getState('cap-faces');
|
|
148
|
+
expect(startFaces.length).toBe(1);
|
|
149
|
+
expect(endFaces.length).toBe(1);
|
|
150
|
+
expect(sideFaces.length).toBe(1);
|
|
151
|
+
expect(internalFaces.length).toBe(1);
|
|
152
|
+
expect(capFaces.length).toBe(2);
|
|
153
|
+
});
|
|
154
|
+
it("should classify cap faces for vertical line profile parallel to axis (180 deg)", () => {
|
|
155
|
+
sketch("xy", () => {
|
|
156
|
+
move([50, 0]);
|
|
157
|
+
vLine(100).centered();
|
|
158
|
+
});
|
|
159
|
+
const r = revolve("y", 180).thin(20).new();
|
|
160
|
+
render();
|
|
161
|
+
const startFaces = r.getState('start-faces');
|
|
162
|
+
const endFaces = r.getState('end-faces');
|
|
163
|
+
const sideFaces = r.getState('side-faces');
|
|
164
|
+
const internalFaces = r.getState('internal-faces');
|
|
165
|
+
const capFaces = r.getState('cap-faces');
|
|
166
|
+
expect(startFaces.length).toBe(1);
|
|
167
|
+
expect(endFaces.length).toBe(1);
|
|
168
|
+
expect(sideFaces.length).toBe(1);
|
|
169
|
+
expect(internalFaces.length).toBe(1);
|
|
170
|
+
expect(capFaces.length).toBe(2);
|
|
171
|
+
});
|
|
136
172
|
});
|
|
137
173
|
describe("remove mode", () => {
|
|
138
174
|
it("should cut a thin-walled revolve from existing geometry", () => {
|
|
@@ -114,4 +114,67 @@ describe("translate", () => {
|
|
|
114
114
|
expect(bbox.minX).toBeCloseTo(0, 0);
|
|
115
115
|
});
|
|
116
116
|
});
|
|
117
|
+
describe("translate with .exclude()", () => {
|
|
118
|
+
it("should skip excluded objects when translating everything", () => {
|
|
119
|
+
sketch("xy", () => {
|
|
120
|
+
rect(20, 20);
|
|
121
|
+
});
|
|
122
|
+
const e1 = extrude(10).new();
|
|
123
|
+
sketch("xy", () => {
|
|
124
|
+
rect(20, 20);
|
|
125
|
+
});
|
|
126
|
+
const e2 = extrude(10).new();
|
|
127
|
+
// No explicit target → translate all, exclude e1 → only e2 moves
|
|
128
|
+
translate(100, true).exclude(e1);
|
|
129
|
+
render();
|
|
130
|
+
// e1 stays at origin
|
|
131
|
+
expect(e1.getShapes()).toHaveLength(1);
|
|
132
|
+
const e1Bbox = ShapeOps.getBoundingBox(e1.getShapes()[0]);
|
|
133
|
+
expect(e1Bbox.minX).toBeCloseTo(0, 0);
|
|
134
|
+
// e2 was copied to x=100 (original still present because copy=true)
|
|
135
|
+
expect(e2.getShapes()).toHaveLength(1);
|
|
136
|
+
});
|
|
137
|
+
it("should narrow an explicit target list with exclude", () => {
|
|
138
|
+
sketch("xy", () => {
|
|
139
|
+
rect(20, 20);
|
|
140
|
+
});
|
|
141
|
+
const e1 = extrude(10).new();
|
|
142
|
+
sketch("xy", () => {
|
|
143
|
+
rect(20, 20);
|
|
144
|
+
});
|
|
145
|
+
const e2 = extrude(10).new();
|
|
146
|
+
// Explicit targets [e1, e2], exclude e2 → only e1 moves
|
|
147
|
+
translate(100, e1, e2).exclude(e2);
|
|
148
|
+
render();
|
|
149
|
+
// e1 moved (removed from source), e2 stays
|
|
150
|
+
expect(e1.getShapes()).toHaveLength(0);
|
|
151
|
+
expect(e2.getShapes()).toHaveLength(1);
|
|
152
|
+
const e2Bbox = ShapeOps.getBoundingBox(e2.getShapes()[0]);
|
|
153
|
+
expect(e2Bbox.minX).toBeCloseTo(0, 0);
|
|
154
|
+
});
|
|
155
|
+
it("should accumulate exclusions across chained calls", () => {
|
|
156
|
+
sketch("xy", () => {
|
|
157
|
+
rect(20, 20);
|
|
158
|
+
});
|
|
159
|
+
const e1 = extrude(10).new();
|
|
160
|
+
sketch("xy", () => {
|
|
161
|
+
rect(20, 20);
|
|
162
|
+
});
|
|
163
|
+
const e2 = extrude(10).new();
|
|
164
|
+
sketch("xy", () => {
|
|
165
|
+
rect(20, 20);
|
|
166
|
+
});
|
|
167
|
+
const e3 = extrude(10).new();
|
|
168
|
+
// Translate all (copy), exclude e1 and e2 across two calls → only e3 copied
|
|
169
|
+
translate(200, true).exclude(e1).exclude(e2);
|
|
170
|
+
render();
|
|
171
|
+
// All originals preserved (copy=true)
|
|
172
|
+
expect(e1.getShapes()).toHaveLength(1);
|
|
173
|
+
expect(e2.getShapes()).toHaveLength(1);
|
|
174
|
+
expect(e3.getShapes()).toHaveLength(1);
|
|
175
|
+
// e1 and e2 still at origin
|
|
176
|
+
expect(ShapeOps.getBoundingBox(e1.getShapes()[0]).minX).toBeCloseTo(0, 0);
|
|
177
|
+
expect(ShapeOps.getBoundingBox(e2.getShapes()[0]).minX).toBeCloseTo(0, 0);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
117
180
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, it } 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 cylinder from "../../core/cylinder.js";
|
|
6
|
+
import { circle, rect, move } from "../../core/2d/index.js";
|
|
7
|
+
// Run with `LOG_PERF=1 npm test -- --run lib/tests/perf/` to see the breakdown.
|
|
8
|
+
const LOG = process.env.LOG_PERF === "1";
|
|
9
|
+
function dump(scene, label, target) {
|
|
10
|
+
if (!LOG)
|
|
11
|
+
return;
|
|
12
|
+
const rendered = scene.getRenderedObject(target);
|
|
13
|
+
const categories = rendered?.profileCategories ?? [];
|
|
14
|
+
console.log(`\n=== ${label} ===`);
|
|
15
|
+
console.log(`buildDurationMs=${rendered?.buildDurationMs?.toFixed(1)}ms`);
|
|
16
|
+
for (const c of categories) {
|
|
17
|
+
console.log(` ${c.category.padEnd(30)} ${c.durationMs.toFixed(1)}ms`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
describe("perf: record fusion history breakdown", () => {
|
|
21
|
+
setupOC();
|
|
22
|
+
it("simple cylinder + extrude fuse", () => {
|
|
23
|
+
cylinder(20, 20);
|
|
24
|
+
sketch("xy", () => { circle(8); });
|
|
25
|
+
const e = extrude(40);
|
|
26
|
+
const s = render();
|
|
27
|
+
dump(s, "simple cylinder + extrude(40) fuse", e);
|
|
28
|
+
});
|
|
29
|
+
it("box + extrude fuse — bigger result", () => {
|
|
30
|
+
sketch("xy", () => { rect(80, 80); });
|
|
31
|
+
extrude(40);
|
|
32
|
+
sketch("xy", () => { circle(15); });
|
|
33
|
+
const e = extrude(80);
|
|
34
|
+
const s = render();
|
|
35
|
+
dump(s, "box(80,80,40) + extrude(80) fuse", e);
|
|
36
|
+
});
|
|
37
|
+
it("complex: many small holes through a big block", () => {
|
|
38
|
+
sketch("xy", () => { rect(100, 100); });
|
|
39
|
+
extrude(40);
|
|
40
|
+
for (let i = 0; i < 5; i++) {
|
|
41
|
+
for (let j = 0; j < 5; j++) {
|
|
42
|
+
sketch("xy", () => { move([i * 18 - 36, j * 18 - 36]); circle(3); });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
sketch("xy", () => { circle(40); });
|
|
46
|
+
const e = extrude(40);
|
|
47
|
+
const s = render();
|
|
48
|
+
dump(s, "block + extrude over many holes", e);
|
|
49
|
+
});
|
|
50
|
+
it("very complex: big block with N small holes (single fuse)", () => {
|
|
51
|
+
sketch("xy", () => { rect(200, 200); });
|
|
52
|
+
extrude(40);
|
|
53
|
+
const N = 8;
|
|
54
|
+
sketch("xy", () => {
|
|
55
|
+
for (let i = 0; i < N; i++) {
|
|
56
|
+
for (let j = 0; j < N; j++) {
|
|
57
|
+
move([i * 22 - 80, j * 22 - 80]);
|
|
58
|
+
circle(4);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const e = extrude(60);
|
|
63
|
+
const s = render();
|
|
64
|
+
dump(s, `block + single extrude with ${N * N} holes`, e);
|
|
65
|
+
});
|
|
66
|
+
it("incremental fuses: extrude many small features one-by-one", () => {
|
|
67
|
+
sketch("xy", () => { rect(150, 150); });
|
|
68
|
+
extrude(30);
|
|
69
|
+
let last = null;
|
|
70
|
+
for (let i = 0; i < 10; i++) {
|
|
71
|
+
sketch("xy", () => { move([i * 12 - 50, 0]); circle(4); });
|
|
72
|
+
last = extrude(40);
|
|
73
|
+
}
|
|
74
|
+
const s = render();
|
|
75
|
+
dump(s, "10th incremental extrude (after building up complexity)", last);
|
|
76
|
+
});
|
|
77
|
+
});
|