fluidcad 0.0.22 → 0.0.24
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/core/trim.d.ts +12 -5
- package/lib/dist/core/trim.js +7 -2
- package/lib/dist/features/2d/sketch.d.ts +0 -1
- package/lib/dist/features/2d/sketch.js +22 -33
- 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/edge-query.d.ts +5 -1
- package/lib/dist/oc/edge-query.js +40 -0
- package/lib/dist/rendering/render.js +3 -2
- package/lib/dist/tests/features/chamfer.test.js +1 -1
- package/lib/dist/tests/features/fillet.test.js +1 -1
- package/lib/dist/tests/features/select.test.js +101 -3
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/ui/dist/assets/{index-C0JwQ8Bk.js → index-CqP_mgZk.js} +14 -7
- package/ui/dist/index.html +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Matrix4 } from "../../math/matrix4.js";
|
|
2
|
+
import { Edge } from "../../common/shapes.js";
|
|
3
|
+
import { FilterBase } from "../filter-base.js";
|
|
4
|
+
import { SceneObject } from "../../common/scene-object.js";
|
|
5
|
+
export declare class IntersectsWithFilter extends FilterBase<Edge> {
|
|
6
|
+
private sceneObject;
|
|
7
|
+
constructor(sceneObject: SceneObject);
|
|
8
|
+
match(shape: Edge): boolean;
|
|
9
|
+
compareTo(other: IntersectsWithFilter): boolean;
|
|
10
|
+
transform(_matrix: Matrix4): IntersectsWithFilter;
|
|
11
|
+
}
|
|
12
|
+
export declare class NotIntersectsWithFilter extends FilterBase<Edge> {
|
|
13
|
+
private sceneObject;
|
|
14
|
+
constructor(sceneObject: SceneObject);
|
|
15
|
+
match(shape: Edge): boolean;
|
|
16
|
+
compareTo(other: NotIntersectsWithFilter): boolean;
|
|
17
|
+
transform(_matrix: Matrix4): NotIntersectsWithFilter;
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { FilterBase } from "../filter-base.js";
|
|
2
|
+
import { EdgeQuery } from "../../oc/edge-query.js";
|
|
3
|
+
export class IntersectsWithFilter extends FilterBase {
|
|
4
|
+
sceneObject;
|
|
5
|
+
constructor(sceneObject) {
|
|
6
|
+
super();
|
|
7
|
+
this.sceneObject = sceneObject;
|
|
8
|
+
}
|
|
9
|
+
match(shape) {
|
|
10
|
+
const objectEdges = this.sceneObject.getShapes({ excludeGuide: false })
|
|
11
|
+
.flatMap(s => s.getSubShapes("edge"));
|
|
12
|
+
return objectEdges.some(objEdge => EdgeQuery.doEdgesIntersect(shape, objEdge));
|
|
13
|
+
}
|
|
14
|
+
compareTo(other) {
|
|
15
|
+
return this.sceneObject.compareTo(other.sceneObject);
|
|
16
|
+
}
|
|
17
|
+
transform(_matrix) {
|
|
18
|
+
return new IntersectsWithFilter(this.sceneObject);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class NotIntersectsWithFilter extends FilterBase {
|
|
22
|
+
sceneObject;
|
|
23
|
+
constructor(sceneObject) {
|
|
24
|
+
super();
|
|
25
|
+
this.sceneObject = sceneObject;
|
|
26
|
+
}
|
|
27
|
+
match(shape) {
|
|
28
|
+
const objectEdges = this.sceneObject.getShapes({ excludeGuide: false })
|
|
29
|
+
.flatMap(s => s.getSubShapes("edge"));
|
|
30
|
+
return !objectEdges.some(objEdge => EdgeQuery.doEdgesIntersect(shape, objEdge));
|
|
31
|
+
}
|
|
32
|
+
compareTo(other) {
|
|
33
|
+
return this.sceneObject.compareTo(other.sceneObject);
|
|
34
|
+
}
|
|
35
|
+
transform(_matrix) {
|
|
36
|
+
return new NotIntersectsWithFilter(this.sceneObject);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -5,7 +5,8 @@ import { PlaneObjectBase } from "../../features/plane-renderable-base.js";
|
|
|
5
5
|
export declare class OnPlaneFilter extends FilterBase<Edge> {
|
|
6
6
|
private plane;
|
|
7
7
|
private plane2?;
|
|
8
|
-
|
|
8
|
+
private partial;
|
|
9
|
+
constructor(plane: PlaneObjectBase, plane2?: PlaneObjectBase, partial?: boolean);
|
|
9
10
|
match(shape: Edge): boolean;
|
|
10
11
|
compareTo(other: OnPlaneFilter): boolean;
|
|
11
12
|
transform(matrix: Matrix4): OnPlaneFilter;
|
|
@@ -13,7 +14,8 @@ export declare class OnPlaneFilter extends FilterBase<Edge> {
|
|
|
13
14
|
export declare class NotOnPlaneFilter extends FilterBase<Edge> {
|
|
14
15
|
private plane;
|
|
15
16
|
private plane2?;
|
|
16
|
-
|
|
17
|
+
private partial;
|
|
18
|
+
constructor(plane: PlaneObjectBase, plane2?: PlaneObjectBase, partial?: boolean);
|
|
17
19
|
match(shape: Edge): boolean;
|
|
18
20
|
compareTo(other: NotOnPlaneFilter): boolean;
|
|
19
21
|
transform(matrix: Matrix4): NotOnPlaneFilter;
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import { FilterBase } from "../filter-base.js";
|
|
2
2
|
import { EdgeQuery } from "../../oc/edge-query.js";
|
|
3
|
+
import { EdgeOps } from "../../oc/edge-ops.js";
|
|
3
4
|
import { PlaneObject } from "../../features/plane.js";
|
|
4
5
|
export class OnPlaneFilter extends FilterBase {
|
|
5
6
|
plane;
|
|
6
7
|
plane2;
|
|
7
|
-
|
|
8
|
+
partial;
|
|
9
|
+
constructor(plane, plane2, partial = false) {
|
|
8
10
|
super();
|
|
9
11
|
this.plane = plane;
|
|
10
12
|
this.plane2 = plane2;
|
|
13
|
+
this.partial = partial;
|
|
11
14
|
}
|
|
12
15
|
match(shape) {
|
|
13
16
|
const plane = this.plane.getPlane();
|
|
17
|
+
if (this.partial) {
|
|
18
|
+
const firstPoint = EdgeOps.getVertexPoint(EdgeOps.getFirstVertex(shape));
|
|
19
|
+
const lastPoint = EdgeOps.getVertexPoint(EdgeOps.getLastVertex(shape));
|
|
20
|
+
if (plane.containsPoint(firstPoint) || plane.containsPoint(lastPoint)) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (this.plane2) {
|
|
24
|
+
const plane2 = this.plane2.getPlane();
|
|
25
|
+
return plane2.containsPoint(firstPoint) || plane2.containsPoint(lastPoint);
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
14
29
|
if (EdgeQuery.isEdgeOnPlane(shape, plane)) {
|
|
15
30
|
return true;
|
|
16
31
|
}
|
|
@@ -20,7 +35,7 @@ export class OnPlaneFilter extends FilterBase {
|
|
|
20
35
|
return false;
|
|
21
36
|
}
|
|
22
37
|
compareTo(other) {
|
|
23
|
-
if (!this.plane.compareTo(other.plane)) {
|
|
38
|
+
if (!this.plane.compareTo(other.plane) || this.partial !== other.partial) {
|
|
24
39
|
return false;
|
|
25
40
|
}
|
|
26
41
|
if (this.plane2 && other.plane2) {
|
|
@@ -29,31 +44,42 @@ export class OnPlaneFilter extends FilterBase {
|
|
|
29
44
|
return this.plane2 === other.plane2;
|
|
30
45
|
}
|
|
31
46
|
transform(matrix) {
|
|
32
|
-
const
|
|
33
|
-
const transformedPlane = plane.applyMatrix(matrix);
|
|
34
|
-
console.log('Plane', plane.normal, 'Origin:', plane.origin, ' Transformed plane:', transformedPlane.normal, ' Origin:', transformedPlane.origin);
|
|
35
|
-
const planeObj = new PlaneObject(transformedPlane);
|
|
47
|
+
const transformedPlane = this.plane.getPlane().applyMatrix(matrix);
|
|
36
48
|
const planeObj2 = this.plane2 ? new PlaneObject(this.plane2.getPlane().applyMatrix(matrix)) : undefined;
|
|
37
|
-
return new OnPlaneFilter(
|
|
49
|
+
return new OnPlaneFilter(new PlaneObject(transformedPlane), planeObj2, this.partial);
|
|
38
50
|
}
|
|
39
51
|
}
|
|
40
52
|
export class NotOnPlaneFilter extends FilterBase {
|
|
41
53
|
plane;
|
|
42
54
|
plane2;
|
|
43
|
-
|
|
55
|
+
partial;
|
|
56
|
+
constructor(plane, plane2, partial = false) {
|
|
44
57
|
super();
|
|
45
58
|
this.plane = plane;
|
|
46
59
|
this.plane2 = plane2;
|
|
60
|
+
this.partial = partial;
|
|
47
61
|
}
|
|
48
62
|
match(shape) {
|
|
49
63
|
const plane = this.plane.getPlane();
|
|
64
|
+
if (this.partial) {
|
|
65
|
+
const firstPoint = EdgeOps.getVertexPoint(EdgeOps.getFirstVertex(shape));
|
|
66
|
+
const lastPoint = EdgeOps.getVertexPoint(EdgeOps.getLastVertex(shape));
|
|
67
|
+
if (plane.containsPoint(firstPoint) || plane.containsPoint(lastPoint)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (this.plane2) {
|
|
71
|
+
const plane2 = this.plane2.getPlane();
|
|
72
|
+
return !plane2.containsPoint(firstPoint) && !plane2.containsPoint(lastPoint);
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
50
76
|
if (this.plane2) {
|
|
51
77
|
return !EdgeQuery.isEdgeOnPlane(shape, plane) && !EdgeQuery.isEdgeOnPlane(shape, this.plane2.getPlane());
|
|
52
78
|
}
|
|
53
79
|
return !EdgeQuery.isEdgeOnPlane(shape, plane);
|
|
54
80
|
}
|
|
55
81
|
compareTo(other) {
|
|
56
|
-
if (!this.plane.compareTo(other.plane)) {
|
|
82
|
+
if (!this.plane.compareTo(other.plane) || this.partial !== other.partial) {
|
|
57
83
|
return false;
|
|
58
84
|
}
|
|
59
85
|
if (this.plane2 && other.plane2) {
|
|
@@ -62,9 +88,8 @@ export class NotOnPlaneFilter extends FilterBase {
|
|
|
62
88
|
return this.plane2 === other.plane2;
|
|
63
89
|
}
|
|
64
90
|
transform(matrix) {
|
|
65
|
-
const
|
|
66
|
-
const planeObj = new PlaneObject(plane.applyMatrix(matrix));
|
|
91
|
+
const transformedPlane = this.plane.getPlane().applyMatrix(matrix);
|
|
67
92
|
const planeObj2 = this.plane2 ? new PlaneObject(this.plane2.getPlane().applyMatrix(matrix)) : undefined;
|
|
68
|
-
return new NotOnPlaneFilter(
|
|
93
|
+
return new NotOnPlaneFilter(new PlaneObject(transformedPlane), planeObj2, this.partial);
|
|
69
94
|
}
|
|
70
95
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Matrix4 } from "../math/matrix4.js";
|
|
2
1
|
import { Shape } from "../common/shapes.js";
|
|
3
2
|
import { FilterBase } from "./filter-base.js";
|
|
4
3
|
export declare class FilterBuilderBase<TShape extends Shape = Shape> {
|
|
@@ -10,8 +9,4 @@ export declare class FilterBuilderBase<TShape extends Shape = Shape> {
|
|
|
10
9
|
* by tangency (G1 continuity) to the initially matched shapes.
|
|
11
10
|
*/
|
|
12
11
|
withTangents(): this;
|
|
13
|
-
hasTangentExpansion(): boolean;
|
|
14
|
-
getFilters(): FilterBase<TShape>[];
|
|
15
|
-
transform(matrix: Matrix4): FilterBuilderBase<TShape>;
|
|
16
|
-
equals(other: FilterBuilderBase<TShape>): boolean;
|
|
17
12
|
}
|
|
@@ -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)) {
|
|
@@ -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
|
}
|
|
@@ -207,8 +207,9 @@ export function renderScene(scene) {
|
|
|
207
207
|
}
|
|
208
208
|
buildDurations.set(object, performance.now() - buildStart);
|
|
209
209
|
}
|
|
210
|
-
// After building, mark cloned sketches so their children are skipped
|
|
211
|
-
|
|
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()) {
|
|
212
213
|
skippedContainers.add(object);
|
|
213
214
|
}
|
|
214
215
|
}
|
|
@@ -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()
|
|
@@ -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()
|
|
@@ -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
|
});
|