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.
- package/lib/dist/common/scene-object.d.ts +3 -0
- package/lib/dist/common/scene-object.js +7 -0
- package/lib/dist/core/2d/arc.d.ts +14 -56
- package/lib/dist/core/2d/arc.js +8 -23
- package/lib/dist/core/2d/center.d.ts +9 -0
- package/lib/dist/core/2d/center.js +10 -0
- 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 +17 -0
- package/lib/dist/core/2d/intersect.js +20 -0
- package/lib/dist/core/interfaces.d.ts +50 -2
- package/lib/dist/core/repeat.js +10 -3
- package/lib/dist/features/2d/arc.d.ts +27 -14
- package/lib/dist/features/2d/arc.js +269 -38
- package/lib/dist/features/2d/intersect.d.ts +15 -0
- package/lib/dist/features/2d/intersect.js +83 -0
- package/lib/dist/features/2d/plane-center.d.ts +13 -0
- package/lib/dist/features/2d/plane-center.js +40 -0
- package/lib/dist/features/2d/projection.js +3 -4
- package/lib/dist/features/2d/rect.d.ts +2 -2
- package/lib/dist/features/2d/rect.js +3 -3
- package/lib/dist/features/2d/sketch.d.ts +2 -2
- package/lib/dist/features/2d/sketch.js +13 -1
- package/lib/dist/features/2d/slot.d.ts +2 -2
- package/lib/dist/features/2d/slot.js +3 -3
- package/lib/dist/features/extrude-base.d.ts +4 -0
- package/lib/dist/features/extrude-base.js +27 -0
- package/lib/dist/features/extrude-to-face.d.ts +1 -0
- package/lib/dist/features/extrude-to-face.js +5 -1
- package/lib/dist/features/extrude-two-distances.d.ts +1 -0
- package/lib/dist/features/extrude-two-distances.js +5 -2
- package/lib/dist/features/extrude.d.ts +1 -0
- package/lib/dist/features/extrude.js +5 -1
- package/lib/dist/features/plane-renderable-base.d.ts +2 -1
- package/lib/dist/features/plane-renderable-base.js +1 -1
- package/lib/dist/features/plane.d.ts +1 -1
- package/lib/dist/filters/face/face-filter.d.ts +10 -0
- package/lib/dist/filters/face/face-filter.js +39 -0
- package/lib/dist/filters/face/intersects-with.d.ts +18 -0
- package/lib/dist/filters/face/intersects-with.js +41 -0
- package/lib/dist/helpers/clone-transform.js +1 -0
- package/lib/dist/oc/boolean-ops.js +0 -2
- package/lib/dist/oc/face-query.d.ts +1 -0
- package/lib/dist/oc/face-query.js +15 -0
- package/lib/dist/oc/index.d.ts +1 -0
- package/lib/dist/oc/index.js +1 -0
- package/lib/dist/oc/section-ops.d.ts +6 -0
- package/lib/dist/oc/section-ops.js +26 -0
- package/lib/dist/oc/thin-face-maker.d.ts +29 -0
- package/lib/dist/oc/thin-face-maker.js +163 -0
- package/lib/dist/rendering/render.js +16 -0
- package/lib/dist/tests/features/2d/arc.test.js +2 -2
- package/lib/dist/tests/features/2d/intersect.test.d.ts +1 -0
- package/lib/dist/tests/features/2d/intersect.test.js +22 -0
- package/lib/dist/tests/features/2d/rect.test.js +1 -1
- package/lib/dist/tests/features/loft.test.js +1 -1
- package/lib/dist/tests/features/select.test.js +49 -1
- package/lib/dist/tests/features/thin-extrude.test.d.ts +1 -0
- package/lib/dist/tests/features/thin-extrude.test.js +137 -0
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/server/dist/index.js +23 -15
- package/ui/dist/assets/{index-mLcpjEcV.js → index-BJG141m7.js} +2 -2
- package/ui/dist/index.html +1 -1
- package/lib/dist/features/2d/arc-three-points.d.ts +0 -19
- package/lib/dist/features/2d/arc-three-points.js +0 -75
- package/lib/dist/features/2d/arc-to-point.d.ts +0 -17
- 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]
|
|
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]
|
|
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
|
+
});
|
|
@@ -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).
|
|
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
|
+
});
|