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
|
@@ -13,12 +13,21 @@ export class FilterBuilderBase {
|
|
|
13
13
|
this._withTangents = true;
|
|
14
14
|
return this;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
16
19
|
hasTangentExpansion() {
|
|
17
20
|
return this._withTangents;
|
|
18
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
19
25
|
getFilters() {
|
|
20
26
|
return this.filters;
|
|
21
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
22
31
|
transform(matrix) {
|
|
23
32
|
const transformedBuilder = new FilterBuilderBase();
|
|
24
33
|
for (const filter of this.filters) {
|
|
@@ -27,6 +36,9 @@ export class FilterBuilderBase {
|
|
|
27
36
|
transformedBuilder._withTangents = this._withTangents;
|
|
28
37
|
return transformedBuilder;
|
|
29
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
30
42
|
equals(other) {
|
|
31
43
|
if (this._withTangents !== other._withTangents) {
|
|
32
44
|
return false;
|
|
@@ -34,6 +34,9 @@ export function cloneWithTransform(objects, transform, container) {
|
|
|
34
34
|
remap.set(obj, copy);
|
|
35
35
|
copy.setTransform(transform);
|
|
36
36
|
copy.setCloneSource(obj);
|
|
37
|
+
if (obj.isReusable()) {
|
|
38
|
+
copy.reusable();
|
|
39
|
+
}
|
|
37
40
|
allCloned.push(copy);
|
|
38
41
|
const parent = obj.getParent();
|
|
39
42
|
if (parent && remap.has(parent)) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { getOC } from "./init.js";
|
|
2
|
+
import { Convert } from "./convert.js";
|
|
3
|
+
import { ShapeFactory } from "../common/shape-factory.js";
|
|
4
|
+
import { ShapeOps } from "./shape-ops.js";
|
|
5
|
+
import { Vector3d } from "../math/vector3d.js";
|
|
6
|
+
import { Plane } from "../math/plane.js";
|
|
7
|
+
import { Point } from "../math/point.js";
|
|
8
|
+
export class DraftOps {
|
|
9
|
+
static applyDraft(solid, faceRaws, angle) {
|
|
10
|
+
const oc = getOC();
|
|
11
|
+
const solidRaw = solid.getShape();
|
|
12
|
+
const bbox = ShapeOps.getBoundingBox(solid);
|
|
13
|
+
const neutralPlane = new Plane(new Point(0, 0, bbox.minZ), new Vector3d(1, 0, 0), new Vector3d(0, 0, 1));
|
|
14
|
+
const [dir, disposeDir] = Convert.toGpDir(neutralPlane.normal);
|
|
15
|
+
const [pln, disposePln] = Convert.toGpPln(neutralPlane);
|
|
16
|
+
try {
|
|
17
|
+
const draftMaker = new oc.BRepOffsetAPI_DraftAngle(solidRaw);
|
|
18
|
+
let addedCount = 0;
|
|
19
|
+
const explorer = new oc.TopExp_Explorer(solidRaw, oc.TopAbs_ShapeEnum.TopAbs_FACE, oc.TopAbs_ShapeEnum.TopAbs_SHAPE);
|
|
20
|
+
while (explorer.More()) {
|
|
21
|
+
const currentShape = explorer.Current();
|
|
22
|
+
const isSelected = faceRaws.some(sel => sel.IsSame(currentShape));
|
|
23
|
+
if (isSelected) {
|
|
24
|
+
const face = oc.TopoDS.Face(currentShape);
|
|
25
|
+
draftMaker.Add(face, dir, -angle, pln, true);
|
|
26
|
+
addedCount++;
|
|
27
|
+
}
|
|
28
|
+
explorer.Next();
|
|
29
|
+
}
|
|
30
|
+
explorer.delete();
|
|
31
|
+
if (addedCount === 0) {
|
|
32
|
+
draftMaker.delete();
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const progress = new oc.Message_ProgressRange();
|
|
36
|
+
draftMaker.Build(progress);
|
|
37
|
+
progress.delete();
|
|
38
|
+
if (!draftMaker.IsDone()) {
|
|
39
|
+
draftMaker.delete();
|
|
40
|
+
throw new Error("Failed to apply draft angle.");
|
|
41
|
+
}
|
|
42
|
+
const result = draftMaker.Shape();
|
|
43
|
+
draftMaker.delete();
|
|
44
|
+
return ShapeFactory.fromShape(result);
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
disposeDir();
|
|
48
|
+
disposePln();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { gp_Pln, gp_Vec, TopoDS_Edge, TopoDS_Shape } from "occjs-wrapper";
|
|
1
|
+
import type { gp_Pln, gp_Vec, TopoDS_Edge, TopoDS_Face, TopoDS_Shape } from "occjs-wrapper";
|
|
2
2
|
import { Point } from "../math/point.js";
|
|
3
3
|
import { Vector3d } from "../math/vector3d.js";
|
|
4
4
|
import { Plane } from "../math/plane.js";
|
|
@@ -23,6 +23,8 @@ export declare class EdgeQuery {
|
|
|
23
23
|
radius: number;
|
|
24
24
|
axisDirection: Vector3d;
|
|
25
25
|
};
|
|
26
|
+
static doEdgesIntersect(edge1: Edge, edge2: Edge): boolean;
|
|
27
|
+
static doesEdgeIntersectPlane(edge: Edge, plane: Plane): boolean;
|
|
26
28
|
static isCircleEdgeRaw(edge: TopoDS_Shape, diameter?: number): boolean;
|
|
27
29
|
static isArcEdgeRaw(edge: TopoDS_Shape, radius?: number): boolean;
|
|
28
30
|
static isLineEdgeRaw(edge: TopoDS_Shape, length?: number): boolean;
|
|
@@ -41,4 +43,6 @@ export declare class EdgeQuery {
|
|
|
41
43
|
radius: number;
|
|
42
44
|
axisDirection: Vector3d;
|
|
43
45
|
};
|
|
46
|
+
static doEdgesIntersectRaw(edge1: TopoDS_Edge, edge2: TopoDS_Edge): boolean;
|
|
47
|
+
static doesEdgeIntersectPlaneRaw(edge: TopoDS_Edge, face: TopoDS_Face): boolean;
|
|
44
48
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getOC } from "./init.js";
|
|
2
2
|
import { Convert } from "./convert.js";
|
|
3
|
+
import { FaceOps } from "./face-ops.js";
|
|
3
4
|
import { Point } from "../math/point.js";
|
|
4
5
|
import { Vector3d } from "../math/vector3d.js";
|
|
5
6
|
export class EdgeQuery {
|
|
@@ -46,6 +47,17 @@ export class EdgeQuery {
|
|
|
46
47
|
static getCircleDataFromEdge(edge) {
|
|
47
48
|
return EdgeQuery.getCircleDataFromEdgeRaw(edge.getShape());
|
|
48
49
|
}
|
|
50
|
+
static doEdgesIntersect(edge1, edge2) {
|
|
51
|
+
return EdgeQuery.doEdgesIntersectRaw(edge1.getShape(), edge2.getShape());
|
|
52
|
+
}
|
|
53
|
+
static doesEdgeIntersectPlane(edge, plane) {
|
|
54
|
+
const [gpPln, dispose] = Convert.toGpPln(plane);
|
|
55
|
+
const face = FaceOps.makeFaceFromPlane2(gpPln);
|
|
56
|
+
const result = EdgeQuery.doesEdgeIntersectPlaneRaw(edge.getShape(), face);
|
|
57
|
+
face.delete();
|
|
58
|
+
dispose();
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
49
61
|
// Raw methods (for oc-internal and common/ use)
|
|
50
62
|
static isCircleEdgeRaw(edge, diameter) {
|
|
51
63
|
const oc = getOC();
|
|
@@ -241,4 +253,32 @@ export class EdgeQuery {
|
|
|
241
253
|
curve.delete();
|
|
242
254
|
return result;
|
|
243
255
|
}
|
|
256
|
+
static doEdgesIntersectRaw(edge1, edge2) {
|
|
257
|
+
const oc = getOC();
|
|
258
|
+
const tool = new oc.IntTools_EdgeEdge(edge1, edge2);
|
|
259
|
+
tool.Perform();
|
|
260
|
+
let intersects = false;
|
|
261
|
+
if (tool.IsDone()) {
|
|
262
|
+
const parts = tool.CommonParts();
|
|
263
|
+
intersects = parts.Length() > 0;
|
|
264
|
+
parts.delete();
|
|
265
|
+
}
|
|
266
|
+
tool.delete();
|
|
267
|
+
return intersects;
|
|
268
|
+
}
|
|
269
|
+
static doesEdgeIntersectPlaneRaw(edge, face) {
|
|
270
|
+
const oc = getOC();
|
|
271
|
+
const tool = new oc.IntTools_EdgeFace();
|
|
272
|
+
tool.SetEdge(edge);
|
|
273
|
+
tool.SetFace(face);
|
|
274
|
+
tool.Perform();
|
|
275
|
+
let intersects = false;
|
|
276
|
+
if (tool.IsDone()) {
|
|
277
|
+
const parts = tool.CommonParts();
|
|
278
|
+
intersects = parts.Length() > 0;
|
|
279
|
+
parts.delete();
|
|
280
|
+
}
|
|
281
|
+
tool.delete();
|
|
282
|
+
return intersects;
|
|
283
|
+
}
|
|
244
284
|
}
|
package/lib/dist/oc/mesh.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface MeshData {
|
|
|
10
10
|
export declare class Mesh {
|
|
11
11
|
static triangulateFace(face: Face, vertexOffset?: number): MeshData | null;
|
|
12
12
|
static discretizeEdge(edge: Shape): MeshData;
|
|
13
|
+
static premeshShape(shape: TopoDS_Shape): void;
|
|
13
14
|
static triangulateFaceRaw(face: TopoDS_Face, vertexOffset?: number): MeshData | null;
|
|
15
|
+
static extractFaceTriangulationRaw(face: TopoDS_Face, vertexOffset?: number): MeshData | null;
|
|
14
16
|
static discretizeEdgeRaw(edge: TopoDS_Shape): MeshData;
|
|
15
17
|
}
|
package/lib/dist/oc/mesh.js
CHANGED
|
@@ -7,12 +7,14 @@ export class Mesh {
|
|
|
7
7
|
static discretizeEdge(edge) {
|
|
8
8
|
return Mesh.discretizeEdgeRaw(edge.getShape());
|
|
9
9
|
}
|
|
10
|
+
static premeshShape(shape) {
|
|
11
|
+
const oc = getOC();
|
|
12
|
+
const inc = new oc.BRepMesh_IncrementalMesh(shape, 0.3, false, 0.3, true);
|
|
13
|
+
inc.delete();
|
|
14
|
+
}
|
|
10
15
|
// Raw methods (for oc-internal use)
|
|
11
16
|
static triangulateFaceRaw(face, vertexOffset = 0) {
|
|
12
17
|
const oc = getOC();
|
|
13
|
-
const vertices = [];
|
|
14
|
-
const normals = [];
|
|
15
|
-
const indices = [];
|
|
16
18
|
let inc;
|
|
17
19
|
try {
|
|
18
20
|
inc = new oc.BRepMesh_IncrementalMesh(face, 0.3, false, 0.3, true);
|
|
@@ -21,12 +23,19 @@ export class Mesh {
|
|
|
21
23
|
console.error("Face mesh failed", e);
|
|
22
24
|
return null;
|
|
23
25
|
}
|
|
26
|
+
inc.delete();
|
|
27
|
+
return Mesh.extractFaceTriangulationRaw(face, vertexOffset);
|
|
28
|
+
}
|
|
29
|
+
static extractFaceTriangulationRaw(face, vertexOffset = 0) {
|
|
30
|
+
const oc = getOC();
|
|
31
|
+
const vertices = [];
|
|
32
|
+
const normals = [];
|
|
33
|
+
const indices = [];
|
|
24
34
|
const aLocation = new oc.TopLoc_Location();
|
|
25
35
|
const myT = oc.BRep_Tool.Triangulation(face, aLocation, 0);
|
|
26
36
|
if (myT.IsNull()) {
|
|
27
37
|
aLocation.delete();
|
|
28
|
-
|
|
29
|
-
throw new Error("No triangulation for face");
|
|
38
|
+
return null;
|
|
30
39
|
}
|
|
31
40
|
const pc = new oc.Poly_Connect(myT);
|
|
32
41
|
const triangulation = myT.get();
|
|
@@ -69,7 +78,6 @@ export class Mesh {
|
|
|
69
78
|
triangles.delete();
|
|
70
79
|
myT.delete();
|
|
71
80
|
aLocation.delete();
|
|
72
|
-
inc.delete();
|
|
73
81
|
return { vertices, normals, indices, count: nbNodes };
|
|
74
82
|
}
|
|
75
83
|
static discretizeEdgeRaw(edge) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function transformMeshes(meshes, matrix) {
|
|
2
|
+
const m = matrix.elements;
|
|
3
|
+
return meshes.map(mesh => {
|
|
4
|
+
const srcV = mesh.vertices;
|
|
5
|
+
const srcN = mesh.normals;
|
|
6
|
+
const newV = new Array(srcV.length);
|
|
7
|
+
const newN = new Array(srcN.length);
|
|
8
|
+
for (let i = 0; i < srcV.length; i += 3) {
|
|
9
|
+
const x = srcV[i], y = srcV[i + 1], z = srcV[i + 2];
|
|
10
|
+
newV[i] = m[0] * x + m[4] * y + m[8] * z + m[12];
|
|
11
|
+
newV[i + 1] = m[1] * x + m[5] * y + m[9] * z + m[13];
|
|
12
|
+
newV[i + 2] = m[2] * x + m[6] * y + m[10] * z + m[14];
|
|
13
|
+
}
|
|
14
|
+
for (let i = 0; i < srcN.length; i += 3) {
|
|
15
|
+
const nx = srcN[i], ny = srcN[i + 1], nz = srcN[i + 2];
|
|
16
|
+
newN[i] = m[0] * nx + m[4] * ny + m[8] * nz;
|
|
17
|
+
newN[i + 1] = m[1] * nx + m[5] * ny + m[9] * nz;
|
|
18
|
+
newN[i + 2] = m[2] * nx + m[6] * ny + m[10] * nz;
|
|
19
|
+
}
|
|
20
|
+
return { ...mesh, vertices: newV, normals: newN };
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { renderFace } from "./render-face.js";
|
|
2
1
|
import { renderEdge } from "./render-edge.js";
|
|
3
2
|
import { Explorer } from "../oc/explorer.js";
|
|
3
|
+
import { Mesh } from "../oc/mesh.js";
|
|
4
4
|
export function renderSolid(shapeObj) {
|
|
5
|
+
Mesh.premeshShape(shapeObj.getShape());
|
|
5
6
|
const facesMeshes = getFacesMesh(shapeObj);
|
|
6
7
|
const edgesMesh = getEdgesMesh(shapeObj);
|
|
7
8
|
return [...facesMeshes, ...edgesMesh];
|
|
@@ -25,7 +26,7 @@ function getFacesMesh(shapeObj) {
|
|
|
25
26
|
for (let faceIdx = 0; faceIdx < faces.length; faceIdx++) {
|
|
26
27
|
const face = faces[faceIdx];
|
|
27
28
|
const color = shapeObj.getColor(face.getShape());
|
|
28
|
-
const faceResult =
|
|
29
|
+
const faceResult = Mesh.extractFaceTriangulationRaw(face.getShape(), 0);
|
|
29
30
|
if (faceResult) {
|
|
30
31
|
if (!groups.has(color)) {
|
|
31
32
|
groups.set(color, { vertices: [], normals: [], indices: [], faceMapping: [], vertexOffset: 0 });
|
|
@@ -2,6 +2,7 @@ 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
4
|
import { Sketch } from "../features/2d/sketch.js";
|
|
5
|
+
import { transformMeshes } from "./mesh-transform.js";
|
|
5
6
|
const meshBuilder = new MeshBuilder();
|
|
6
7
|
function renderSceneObject(obj, scene, buildDurationMs) {
|
|
7
8
|
const hasError = !!obj.getError();
|
|
@@ -12,7 +13,18 @@ function renderSceneObject(obj, scene, buildDurationMs) {
|
|
|
12
13
|
for (const shape of sceneShapes) {
|
|
13
14
|
let meshes = shape.getMeshes();
|
|
14
15
|
if (!meshes) {
|
|
15
|
-
|
|
16
|
+
const meshSource = shape.getMeshSource();
|
|
17
|
+
if (meshSource) {
|
|
18
|
+
let sourceMeshes = meshSource.shape.getMeshes();
|
|
19
|
+
if (!sourceMeshes) {
|
|
20
|
+
sourceMeshes = meshBuilder.build(meshSource.shape);
|
|
21
|
+
meshSource.shape.setMeshes(sourceMeshes);
|
|
22
|
+
}
|
|
23
|
+
meshes = sourceMeshes ? transformMeshes(sourceMeshes, meshSource.matrix) : meshBuilder.build(shape);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
meshes = meshBuilder.build(shape);
|
|
27
|
+
}
|
|
16
28
|
shape.setMeshes(meshes);
|
|
17
29
|
}
|
|
18
30
|
const shapeType = shape.getType();
|
|
@@ -71,7 +83,18 @@ export function renderSceneRollback(scene, rollbackIndex) {
|
|
|
71
83
|
for (const shape of sceneShapes) {
|
|
72
84
|
let meshes = shape.getMeshes();
|
|
73
85
|
if (!meshes) {
|
|
74
|
-
|
|
86
|
+
const meshSource = shape.getMeshSource();
|
|
87
|
+
if (meshSource) {
|
|
88
|
+
let sourceMeshes = meshSource.shape.getMeshes();
|
|
89
|
+
if (!sourceMeshes) {
|
|
90
|
+
sourceMeshes = meshBuilder.build(meshSource.shape);
|
|
91
|
+
meshSource.shape.setMeshes(sourceMeshes);
|
|
92
|
+
}
|
|
93
|
+
meshes = sourceMeshes ? transformMeshes(sourceMeshes, meshSource.matrix) : meshBuilder.build(shape);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
meshes = meshBuilder.build(shape);
|
|
97
|
+
}
|
|
75
98
|
shape.setMeshes(meshes);
|
|
76
99
|
}
|
|
77
100
|
renderedSceneShapes.push({
|
|
@@ -184,8 +207,9 @@ export function renderScene(scene) {
|
|
|
184
207
|
}
|
|
185
208
|
buildDurations.set(object, performance.now() - buildStart);
|
|
186
209
|
}
|
|
187
|
-
// After building, mark cloned sketches so their children are skipped
|
|
188
|
-
|
|
210
|
+
// After building, mark cloned sketches so their children are skipped —
|
|
211
|
+
// the sketch's build() already populated them with transformed shapes.
|
|
212
|
+
if (object instanceof Sketch && object.getCloneSource()) {
|
|
189
213
|
skippedContainers.add(object);
|
|
190
214
|
}
|
|
191
215
|
}
|
|
@@ -219,7 +243,5 @@ export function renderScene(scene) {
|
|
|
219
243
|
for (const object of sceneObjects) {
|
|
220
244
|
renderSceneObject(object, scene, buildDurations.get(object));
|
|
221
245
|
}
|
|
222
|
-
const result = scene.getRenderedObjects();
|
|
223
|
-
console.table(result);
|
|
224
246
|
return scene;
|
|
225
247
|
}
|
|
@@ -104,7 +104,7 @@ describe("chamfer", () => {
|
|
|
104
104
|
rect(100, 50);
|
|
105
105
|
});
|
|
106
106
|
extrude(30);
|
|
107
|
-
const sel = select(edge().onPlane("xy", 30));
|
|
107
|
+
const sel = select(edge().onPlane("xy", { offset: 30 }));
|
|
108
108
|
chamfer(3, sel);
|
|
109
109
|
render();
|
|
110
110
|
const solid = render().getAllSceneObjects()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
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 draft from "../../core/draft.js";
|
|
6
|
+
import select from "../../core/select.js";
|
|
7
|
+
import { rect } from "../../core/2d/index.js";
|
|
8
|
+
import { countShapes } from "../utils.js";
|
|
9
|
+
import { ShapeOps } from "../../oc/shape-ops.js";
|
|
10
|
+
import { ShapeProps } from "../../oc/props.js";
|
|
11
|
+
import { face } from "../../filters/index.js";
|
|
12
|
+
describe("draft", () => {
|
|
13
|
+
setupOC();
|
|
14
|
+
describe("draft with implicit selection", () => {
|
|
15
|
+
it("should apply draft to the last selected face", () => {
|
|
16
|
+
sketch("xy", () => {
|
|
17
|
+
rect(100, 100);
|
|
18
|
+
});
|
|
19
|
+
extrude(80);
|
|
20
|
+
select(face().onPlane("front"));
|
|
21
|
+
draft(5);
|
|
22
|
+
const scene = render();
|
|
23
|
+
expect(countShapes(scene)).toBe(1);
|
|
24
|
+
const solid = scene.getAllSceneObjects()
|
|
25
|
+
.flatMap(o => o.getShapes())
|
|
26
|
+
.find(s => s.getType() === "solid");
|
|
27
|
+
expect(solid).toBeDefined();
|
|
28
|
+
});
|
|
29
|
+
it("should change the bounding box when drafting a side face", () => {
|
|
30
|
+
sketch("xy", () => {
|
|
31
|
+
rect(100, 100);
|
|
32
|
+
});
|
|
33
|
+
extrude(80);
|
|
34
|
+
select(face().onPlane("front"));
|
|
35
|
+
draft(5);
|
|
36
|
+
const scene = render();
|
|
37
|
+
const solid = scene.getAllSceneObjects()
|
|
38
|
+
.flatMap(o => o.getShapes())
|
|
39
|
+
.find(s => s.getType() === "solid");
|
|
40
|
+
const bbox = ShapeOps.getBoundingBox(solid);
|
|
41
|
+
// 5 deg draft over 80mm height: tan(5°) * 80 ≈ 7mm extension
|
|
42
|
+
expect(bbox.maxY - bbox.minY).toBeGreaterThan(103);
|
|
43
|
+
});
|
|
44
|
+
it("should change volume compared to original box", () => {
|
|
45
|
+
sketch("xy", () => {
|
|
46
|
+
rect(100, 100);
|
|
47
|
+
});
|
|
48
|
+
extrude(80);
|
|
49
|
+
select(face().onPlane("front"));
|
|
50
|
+
draft(5);
|
|
51
|
+
const scene = render();
|
|
52
|
+
const solid = scene.getAllSceneObjects()
|
|
53
|
+
.flatMap(o => o.getShapes())
|
|
54
|
+
.find(s => s.getType() === "solid");
|
|
55
|
+
const props = ShapeProps.getProperties(solid.getShape());
|
|
56
|
+
const originalVolume = 100 * 100 * 80;
|
|
57
|
+
expect(Math.abs(props.volumeMm3 - originalVolume)).toBeGreaterThan(1000);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("draft with explicit selection", () => {
|
|
61
|
+
it("should apply draft using e.sideFaces()", () => {
|
|
62
|
+
sketch("xy", () => {
|
|
63
|
+
rect(100, 100);
|
|
64
|
+
});
|
|
65
|
+
const e = extrude(80);
|
|
66
|
+
draft(5, e.sideFaces());
|
|
67
|
+
const scene = render();
|
|
68
|
+
expect(countShapes(scene)).toBe(1);
|
|
69
|
+
const solid = scene.getAllSceneObjects()
|
|
70
|
+
.flatMap(o => o.getShapes())
|
|
71
|
+
.find(s => s.getType() === "solid");
|
|
72
|
+
expect(solid).toBeDefined();
|
|
73
|
+
});
|
|
74
|
+
it("should apply draft using explicit select()", () => {
|
|
75
|
+
sketch("xy", () => {
|
|
76
|
+
rect(100, 100);
|
|
77
|
+
});
|
|
78
|
+
extrude(80);
|
|
79
|
+
const sel = select(face().onPlane("front"));
|
|
80
|
+
draft(5, sel);
|
|
81
|
+
const scene = render();
|
|
82
|
+
expect(countShapes(scene)).toBe(1);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe("draft angle effect", () => {
|
|
86
|
+
it("should produce larger extension with 10 degrees than 3 degrees", () => {
|
|
87
|
+
sketch("xy", () => {
|
|
88
|
+
rect(100, 100);
|
|
89
|
+
});
|
|
90
|
+
extrude(80);
|
|
91
|
+
select(face().onPlane("front"));
|
|
92
|
+
draft(10);
|
|
93
|
+
const scene = render();
|
|
94
|
+
const solid = scene.getAllSceneObjects()
|
|
95
|
+
.flatMap(o => o.getShapes())
|
|
96
|
+
.find(s => s.getType() === "solid");
|
|
97
|
+
const bbox = ShapeOps.getBoundingBox(solid);
|
|
98
|
+
// tan(10°) * 80 ≈ 14.1mm
|
|
99
|
+
expect(bbox.maxY - bbox.minY).toBeGreaterThan(110);
|
|
100
|
+
});
|
|
101
|
+
it("should produce smaller extension with 3 degrees", () => {
|
|
102
|
+
sketch("xy", () => {
|
|
103
|
+
rect(100, 100);
|
|
104
|
+
});
|
|
105
|
+
extrude(80);
|
|
106
|
+
select(face().onPlane("front"));
|
|
107
|
+
draft(3);
|
|
108
|
+
const scene = render();
|
|
109
|
+
const solid = scene.getAllSceneObjects()
|
|
110
|
+
.flatMap(o => o.getShapes())
|
|
111
|
+
.find(s => s.getType() === "solid");
|
|
112
|
+
const bbox = ShapeOps.getBoundingBox(solid);
|
|
113
|
+
// tan(3°) * 80 ≈ 4.2mm
|
|
114
|
+
expect(bbox.maxY - bbox.minY).toBeGreaterThan(103);
|
|
115
|
+
expect(bbox.maxY - bbox.minY).toBeLessThan(110);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe("draft removes selection shapes", () => {
|
|
119
|
+
it("should remove the face selection after drafting", () => {
|
|
120
|
+
sketch("xy", () => {
|
|
121
|
+
rect(100, 100);
|
|
122
|
+
});
|
|
123
|
+
extrude(80);
|
|
124
|
+
const sel = select(face().onPlane("front"));
|
|
125
|
+
draft(5, sel);
|
|
126
|
+
render();
|
|
127
|
+
expect(sel.getShapes()).toHaveLength(0);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe("draft on multiple faces", () => {
|
|
131
|
+
it("should draft all four side faces via sideFaces()", () => {
|
|
132
|
+
sketch("xy", () => {
|
|
133
|
+
rect(100, 100);
|
|
134
|
+
});
|
|
135
|
+
const e = extrude(80);
|
|
136
|
+
draft(5, e.sideFaces());
|
|
137
|
+
const scene = render();
|
|
138
|
+
const solid = scene.getAllSceneObjects()
|
|
139
|
+
.flatMap(o => o.getShapes())
|
|
140
|
+
.find(s => s.getType() === "solid");
|
|
141
|
+
const bbox = ShapeOps.getBoundingBox(solid);
|
|
142
|
+
// All four sides drafted — bounding box should extend in both X and Y
|
|
143
|
+
expect(bbox.maxX - bbox.minX).toBeGreaterThan(103);
|
|
144
|
+
expect(bbox.maxY - bbox.minY).toBeGreaterThan(103);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -106,7 +106,7 @@ describe("fillet", () => {
|
|
|
106
106
|
});
|
|
107
107
|
extrude(30);
|
|
108
108
|
// Fillet only the top horizontal edges (4 edges)
|
|
109
|
-
const sel = select(edge().onPlane("xy", 30));
|
|
109
|
+
const sel = select(edge().onPlane("xy", { offset: 30 }));
|
|
110
110
|
fillet(3, sel);
|
|
111
111
|
render();
|
|
112
112
|
const solid = render().getAllSceneObjects()
|