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.
@@ -1,13 +1,20 @@
1
1
  import { Point2DLike } from "../math/point.js";
2
- import { ISceneObject } from "./interfaces.js";
2
+ import { EdgeFilterBuilder } from "../filters/edge/edge-filter.js";
3
+ interface ITrim {
4
+ /**
5
+ * Enters interactive trimming mode, optionally trimming edges at the given points.
6
+ * @param points - Points where geometry should be trimmed; the nearest edge segment to each point is removed.
7
+ */
8
+ pick(...points: Point2DLike[]): ITrim;
9
+ }
3
10
  interface TrimFunction {
4
11
  /** Trims all sketch geometry segments. */
5
- (): ISceneObject;
12
+ (): ITrim;
6
13
  /**
7
- * Trims sketch geometry segments at the given points.
8
- * @param points - The points where geometry should be trimmed
14
+ * Trims sketch geometry segments matching the given edge filters.
15
+ * @param filters - Edge filters that select which edges to remove
9
16
  */
10
- (...points: Point2DLike[]): ISceneObject;
17
+ (...filters: EdgeFilterBuilder[]): ITrim;
11
18
  }
12
19
  declare const _default: TrimFunction;
13
20
  export default _default;
@@ -9,10 +9,15 @@ function build(context) {
9
9
  }
10
10
  const trim2d = new Trim2D();
11
11
  if (args.length > 0) {
12
- trim2d.points(...args.map(p => normalizePoint2D(p)));
12
+ trim2d.setFilters(...args);
13
13
  }
14
14
  context.addSceneObject(trim2d);
15
- return trim2d;
15
+ return {
16
+ pick(...points) {
17
+ trim2d.pick(...points.map(p => normalizePoint2D(p)));
18
+ return this;
19
+ },
20
+ };
16
21
  };
17
22
  }
18
23
  export default registerBuilder(build);
@@ -18,7 +18,6 @@ export declare class Sketch extends SceneObject implements Extrudable {
18
18
  build(context?: BuildSceneObjectContext): void;
19
19
  getEdges(): Edge[];
20
20
  getEdgesWithOwner(): Map<Edge, GeometrySceneObject>;
21
- getAllEdges(): Edge[];
22
21
  getGeometriesWithOwner(): Map<Edge, GeometrySceneObject>;
23
22
  getGeometries(): Edge[];
24
23
  getDependencies(): SceneObject[];
@@ -83,16 +83,31 @@ export class Sketch extends SceneObject {
83
83
  const source = this.getCloneSource();
84
84
  const transform = context?.getTransform();
85
85
  if (source instanceof Sketch && transform) {
86
- const originalEdges = source.getAllEdges();
87
- const transformedEdges = originalEdges.map(edge => ShapeOps.transform(edge, transform));
88
- this.setState('cloned-edges', transformedEdges);
86
+ const sourceChildren = source.getChildren();
87
+ const clonedChildren = this.getChildren();
88
+ for (let i = 0; i < sourceChildren.length; i++) {
89
+ const sourceChild = sourceChildren[i];
90
+ const clonedChild = clonedChildren[i];
91
+ if (!clonedChild) {
92
+ continue;
93
+ }
94
+ const shapes = sourceChild.getAddedShapes();
95
+ const removedShapes = sourceChild.getRemovedShapes();
96
+ for (const shape of shapes) {
97
+ if (shape.isMetaShape() || shape.isGuideShape()) {
98
+ continue;
99
+ }
100
+ const isRemovedBySibling = removedShapes.some(s => s.shape === shape && s.removedBy?.parentId === source.id);
101
+ if (isRemovedBySibling) {
102
+ continue;
103
+ }
104
+ const transformed = ShapeOps.transform(shape, transform);
105
+ clonedChild.addShape(transformed);
106
+ }
107
+ }
89
108
  }
90
109
  }
91
110
  getEdges() {
92
- const clonedEdges = this.getState('cloned-edges');
93
- if (clonedEdges) {
94
- return clonedEdges;
95
- }
96
111
  return [...this.getEdgesWithOwner().keys()];
97
112
  }
98
113
  getEdgesWithOwner() {
@@ -113,32 +128,6 @@ export class Sketch extends SceneObject {
113
128
  }
114
129
  return result;
115
130
  }
116
- getAllEdges() {
117
- const children = this.getChildren();
118
- const result = [];
119
- for (const child of children) {
120
- const shapes = child.getAddedShapes();
121
- const removedShapes = child.getRemovedShapes();
122
- for (const shape of shapes) {
123
- if (shape.isMetaShape() || shape.isGuideShape()) {
124
- continue;
125
- }
126
- const isRemovedBySibling = removedShapes.some(s => s.shape === shape && s.removedBy?.parentId === this.id);
127
- if (isRemovedBySibling) {
128
- continue;
129
- }
130
- if (shape instanceof Edge) {
131
- result.push(shape);
132
- }
133
- else if (shape instanceof Wire) {
134
- for (const edge of shape.getEdges()) {
135
- result.push(edge);
136
- }
137
- }
138
- }
139
- }
140
- return result;
141
- }
142
131
  getGeometriesWithOwner() {
143
132
  return this.getEdgesWithOwner();
144
133
  }
@@ -339,6 +339,7 @@ export class ExtrudeBase extends SceneObject {
339
339
  this._picking = other._picking;
340
340
  this._pickPoints = other._pickPoints;
341
341
  this._thin = other._thin;
342
+ this._drill = other._drill;
342
343
  return this;
343
344
  }
344
345
  /**
@@ -4,6 +4,8 @@ export declare class Remove extends SceneObject {
4
4
  constructor(objects: SceneObject[]);
5
5
  build(): void;
6
6
  compareTo(other: Remove): boolean;
7
+ getDependencies(): SceneObject[];
8
+ createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
7
9
  getType(): string;
8
10
  serialize(): {};
9
11
  }
@@ -24,6 +24,13 @@ export class Remove extends SceneObject {
24
24
  }
25
25
  return true;
26
26
  }
27
+ getDependencies() {
28
+ return this.objects;
29
+ }
30
+ createCopy(remap) {
31
+ const remappedObjects = this.objects.map(obj => remap.get(obj) || obj);
32
+ return new Remove(remappedObjects);
33
+ }
27
34
  getType() {
28
35
  return 'remove';
29
36
  }
@@ -1,15 +1,27 @@
1
1
  import { SceneObject } from "../common/scene-object.js";
2
2
  import { GeometrySceneObject } from "./2d/geometry.js";
3
3
  import { LazyVertex } from "./lazy-vertex.js";
4
+ import { EdgeFilterBuilder } from "../filters/edge/edge-filter.js";
4
5
  export declare class Trim2D extends GeometrySceneObject {
5
- private _points;
6
+ private _filters;
7
+ private _picking;
8
+ private _pickPoints;
6
9
  constructor();
7
- points(...ps: LazyVertex[]): this;
8
- get trimPoints(): LazyVertex[];
10
+ setFilters(...fs: EdgeFilterBuilder[]): this;
11
+ pick(...ps: LazyVertex[]): this;
12
+ isPicking(): boolean;
13
+ getPickPoints(): LazyVertex[];
14
+ get filters(): EdgeFilterBuilder[];
9
15
  build(): void;
16
+ private buildWithFilters;
17
+ private buildWithPicking;
10
18
  getDependencies(): SceneObject[];
11
19
  createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
12
20
  compareTo(other: Trim2D): boolean;
13
21
  getType(): string;
14
- serialize(): {};
22
+ serialize(): {
23
+ trigger: "trim-picking";
24
+ picking: true;
25
+ pickPoints: number[][];
26
+ };
15
27
  }
@@ -2,22 +2,35 @@ import { Wire } from "../common/wire.js";
2
2
  import { Edge } from "../common/edge.js";
3
3
  import { GeometrySceneObject } from "./2d/geometry.js";
4
4
  import { EdgeOps } from "../oc/edge-ops.js";
5
+ import { ShapeFilter } from "../filters/filter.js";
5
6
  export class Trim2D extends GeometrySceneObject {
6
- _points = [];
7
+ _filters = [];
8
+ _picking = false;
9
+ _pickPoints = [];
7
10
  constructor() {
8
11
  super();
9
12
  }
10
- points(...ps) {
11
- this._points = ps;
13
+ setFilters(...fs) {
14
+ this._filters = fs;
12
15
  return this;
13
16
  }
14
- get trimPoints() {
15
- return this._points;
17
+ pick(...ps) {
18
+ this._picking = true;
19
+ this._pickPoints = ps;
20
+ return this;
21
+ }
22
+ isPicking() {
23
+ return this._picking;
24
+ }
25
+ getPickPoints() {
26
+ return this._pickPoints;
27
+ }
28
+ get filters() {
29
+ return this._filters;
16
30
  }
17
31
  build() {
18
32
  const plane = this.sketch.getPlane();
19
33
  const sourceWires = this.sketch.getGeometriesWithOwner();
20
- // Collect all individual edges from wires/edges in the sketch
21
34
  const allEdges = [];
22
35
  const edgeToOwner = new Map();
23
36
  for (const [wireOrEdge, owner] of sourceWires) {
@@ -35,25 +48,49 @@ export class Trim2D extends GeometrySceneObject {
35
48
  if (allEdges.length === 0) {
36
49
  return;
37
50
  }
51
+ if (this._filters.length > 0) {
52
+ this.buildWithFilters(allEdges, edgeToOwner);
53
+ }
54
+ if (this._picking) {
55
+ let pickableEdges;
56
+ if (this._filters.length > 0) {
57
+ const matched = new Set(new ShapeFilter(allEdges, ...this._filters).apply());
58
+ pickableEdges = allEdges.filter(e => !matched.has(e));
59
+ }
60
+ else {
61
+ pickableEdges = allEdges;
62
+ }
63
+ this.buildWithPicking(pickableEdges, edgeToOwner, plane);
64
+ }
65
+ }
66
+ buildWithFilters(allEdges, edgeToOwner) {
67
+ const matchedEdges = new ShapeFilter(allEdges, ...this._filters).apply();
68
+ const removedWires = new Set();
69
+ for (const edge of matchedEdges) {
70
+ const entry = edgeToOwner.get(edge);
71
+ if (!removedWires.has(entry.wire)) {
72
+ removedWires.add(entry.wire);
73
+ entry.owner.removeShape(entry.wire, this);
74
+ }
75
+ }
76
+ }
77
+ buildWithPicking(pickableEdges, edgeToOwner, plane) {
38
78
  const TRIM_TOLERANCE = 50;
39
- // Split all edges at intersection points
40
- const splitResult = EdgeOps.splitEdgesWithMapping(allEdges);
79
+ const splitResult = EdgeOps.splitEdgesWithMapping(pickableEdges);
41
80
  const splitEdges = splitResult.edges;
42
81
  const sourceIndex = splitResult.sourceIndex;
43
- // Find split edges to remove
44
82
  const splitEdgesToRemove = new Set();
45
- if (this._points.length > 0) {
46
- for (const lazyPoint of this._points) {
83
+ if (this._pickPoints.length > 0) {
84
+ for (const lazyPoint of this._pickPoints) {
47
85
  const point2d = lazyPoint.asPoint2D();
48
86
  const point3d = plane.localToWorld(point2d);
49
87
  for (const idx of EdgeOps.findNearestEdgeIndices(splitEdges, point3d, TRIM_TOLERANCE)) {
50
88
  splitEdgesToRemove.add(idx);
51
89
  }
52
90
  }
53
- // Remove affected original wires and re-add surviving split edges
54
91
  const removedWires = new Set();
55
92
  for (const idx of splitEdgesToRemove) {
56
- const origEdge = allEdges[sourceIndex[idx]];
93
+ const origEdge = pickableEdges[sourceIndex[idx]];
57
94
  const entry = edgeToOwner.get(origEdge);
58
95
  if (!removedWires.has(entry.wire)) {
59
96
  removedWires.add(entry.wire);
@@ -64,7 +101,7 @@ export class Trim2D extends GeometrySceneObject {
64
101
  if (splitEdgesToRemove.has(i)) {
65
102
  continue;
66
103
  }
67
- const origEdge = allEdges[sourceIndex[i]];
104
+ const origEdge = pickableEdges[sourceIndex[i]];
68
105
  const entry = edgeToOwner.get(origEdge);
69
106
  if (removedWires.has(entry.wire)) {
70
107
  this.addShape(splitEdges[i]);
@@ -72,11 +109,8 @@ export class Trim2D extends GeometrySceneObject {
72
109
  }
73
110
  }
74
111
  // --- Meta shapes for segment-level hover ---
75
- // Originals with trims need the first-pass split (to preserve trim boundaries).
76
- // Originals without trims are re-split against only surviving edges
77
- // (so ghost intersections from fully-removed edges disappear).
78
112
  const origSurvives = new Set();
79
- const origHasTrim = new Array(allEdges.length).fill(false);
113
+ const origHasTrim = new Array(pickableEdges.length).fill(false);
80
114
  for (let i = 0; i < splitEdges.length; i++) {
81
115
  if (!splitEdgesToRemove.has(i)) {
82
116
  origSurvives.add(sourceIndex[i]);
@@ -85,17 +119,15 @@ export class Trim2D extends GeometrySceneObject {
85
119
  origHasTrim[sourceIndex[i]] = true;
86
120
  }
87
121
  }
88
- // Re-split only surviving originals (for clean meta shapes of untrimmed edges)
89
122
  const metaInputEdges = [];
90
123
  const metaInputToOrig = [];
91
- for (let i = 0; i < allEdges.length; i++) {
124
+ for (let i = 0; i < pickableEdges.length; i++) {
92
125
  if (origSurvives.has(i)) {
93
- metaInputEdges.push(allEdges[i]);
126
+ metaInputEdges.push(pickableEdges[i]);
94
127
  metaInputToOrig.push(i);
95
128
  }
96
129
  }
97
130
  const metaSplit = EdgeOps.splitEdgesWithMapping(metaInputEdges);
98
- // Untrimmed originals: use re-split result (clean, no ghost intersections)
99
131
  for (let i = 0; i < metaSplit.edges.length; i++) {
100
132
  const origIdx = metaInputToOrig[metaSplit.sourceIndex[i]];
101
133
  if (origHasTrim[origIdx]) {
@@ -105,7 +137,6 @@ export class Trim2D extends GeometrySceneObject {
105
137
  metaEdge.markAsMetaShape('trim');
106
138
  this.addShape(metaEdge);
107
139
  }
108
- // Trimmed originals: use first-pass surviving split edges (preserve boundaries)
109
140
  for (let i = 0; i < splitEdges.length; i++) {
110
141
  if (splitEdgesToRemove.has(i)) {
111
142
  continue;
@@ -123,8 +154,11 @@ export class Trim2D extends GeometrySceneObject {
123
154
  }
124
155
  createCopy(remap) {
125
156
  const copy = new Trim2D();
126
- if (this._points.length > 0) {
127
- copy.points(...this._points);
157
+ if (this._filters.length > 0) {
158
+ copy.setFilters(...this._filters);
159
+ }
160
+ if (this._picking) {
161
+ copy.pick(...this._pickPoints);
128
162
  }
129
163
  return copy;
130
164
  }
@@ -135,11 +169,22 @@ export class Trim2D extends GeometrySceneObject {
135
169
  if (!super.compareTo(other)) {
136
170
  return false;
137
171
  }
138
- if (this._points.length !== other._points.length) {
172
+ if (this._filters.length !== other._filters.length) {
173
+ return false;
174
+ }
175
+ for (let i = 0; i < this._filters.length; i++) {
176
+ if (!this._filters[i].equals(other._filters[i])) {
177
+ return false;
178
+ }
179
+ }
180
+ if (this._picking !== other._picking) {
181
+ return false;
182
+ }
183
+ if (this._pickPoints.length !== other._pickPoints.length) {
139
184
  return false;
140
185
  }
141
- for (let i = 0; i < this._points.length; i++) {
142
- if (!this._points[i].compareTo(other._points[i])) {
186
+ for (let i = 0; i < this._pickPoints.length; i++) {
187
+ if (!this._pickPoints[i].compareTo(other._pickPoints[i])) {
143
188
  return false;
144
189
  }
145
190
  }
@@ -149,6 +194,12 @@ export class Trim2D extends GeometrySceneObject {
149
194
  return "trim2d";
150
195
  }
151
196
  serialize() {
152
- return {};
197
+ return {
198
+ trigger: 'trim-picking',
199
+ picking: this._picking || undefined,
200
+ pickPoints: this._picking
201
+ ? this._pickPoints.map(p => { const pt = p.asPoint2D(); return [pt.x, pt.y]; })
202
+ : undefined,
203
+ };
153
204
  }
154
205
  }
@@ -0,0 +1,20 @@
1
+ import { Matrix4 } from "../../math/matrix4.js";
2
+ import { Edge } from "../../common/shapes.js";
3
+ import { FilterBase } from "../filter-base.js";
4
+ import { PlaneObjectBase } from "../../features/plane-renderable-base.js";
5
+ export declare class AbovePlaneFilter extends FilterBase<Edge> {
6
+ private plane;
7
+ private partial;
8
+ constructor(plane: PlaneObjectBase, partial?: boolean);
9
+ match(shape: Edge): boolean;
10
+ compareTo(other: AbovePlaneFilter): boolean;
11
+ transform(matrix: Matrix4): AbovePlaneFilter;
12
+ }
13
+ export declare class BelowPlaneFilter extends FilterBase<Edge> {
14
+ private plane;
15
+ private partial;
16
+ constructor(plane: PlaneObjectBase, partial?: boolean);
17
+ match(shape: Edge): boolean;
18
+ compareTo(other: BelowPlaneFilter): boolean;
19
+ transform(matrix: Matrix4): BelowPlaneFilter;
20
+ }
@@ -0,0 +1,57 @@
1
+ import { FilterBase } from "../filter-base.js";
2
+ import { EdgeOps } from "../../oc/edge-ops.js";
3
+ import { PlaneObject } from "../../features/plane.js";
4
+ export class AbovePlaneFilter extends FilterBase {
5
+ plane;
6
+ partial;
7
+ constructor(plane, partial = false) {
8
+ super();
9
+ this.plane = plane;
10
+ this.partial = partial;
11
+ }
12
+ match(shape) {
13
+ const plane = this.plane.getPlane();
14
+ const firstPoint = EdgeOps.getVertexPoint(EdgeOps.getFirstVertex(shape));
15
+ const lastPoint = EdgeOps.getVertexPoint(EdgeOps.getLastVertex(shape));
16
+ const firstAbove = plane.signedDistanceToPoint(firstPoint) > 0;
17
+ const lastAbove = plane.signedDistanceToPoint(lastPoint) > 0;
18
+ if (this.partial) {
19
+ return firstAbove || lastAbove;
20
+ }
21
+ return firstAbove && lastAbove;
22
+ }
23
+ compareTo(other) {
24
+ return this.plane.compareTo(other.plane) && this.partial === other.partial;
25
+ }
26
+ transform(matrix) {
27
+ const transformedPlane = this.plane.getPlane().applyMatrix(matrix);
28
+ return new AbovePlaneFilter(new PlaneObject(transformedPlane), this.partial);
29
+ }
30
+ }
31
+ export class BelowPlaneFilter extends FilterBase {
32
+ plane;
33
+ partial;
34
+ constructor(plane, partial = false) {
35
+ super();
36
+ this.plane = plane;
37
+ this.partial = partial;
38
+ }
39
+ match(shape) {
40
+ const plane = this.plane.getPlane();
41
+ const firstPoint = EdgeOps.getVertexPoint(EdgeOps.getFirstVertex(shape));
42
+ const lastPoint = EdgeOps.getVertexPoint(EdgeOps.getLastVertex(shape));
43
+ const firstBelow = plane.signedDistanceToPoint(firstPoint) < 0;
44
+ const lastBelow = plane.signedDistanceToPoint(lastPoint) < 0;
45
+ if (this.partial) {
46
+ return firstBelow || lastBelow;
47
+ }
48
+ return firstBelow && lastBelow;
49
+ }
50
+ compareTo(other) {
51
+ return this.plane.compareTo(other.plane) && this.partial === other.partial;
52
+ }
53
+ transform(matrix) {
54
+ const transformedPlane = this.plane.getPlane().applyMatrix(matrix);
55
+ return new BelowPlaneFilter(new PlaneObject(transformedPlane), this.partial);
56
+ }
57
+ }
@@ -8,17 +8,23 @@ export declare class EdgeFilterBuilder extends FilterBuilderBase<Edge> {
8
8
  /**
9
9
  * Selects edges that lie on the given plane.
10
10
  * @param plane - The reference plane.
11
- * @param offset - Optional distance to offset the plane before matching.
12
- * @param bothDirections - When true, also matches the plane offset in the opposite direction.
11
+ * @param offsetOrOptions - Offset distance, or an options object with `offset`, `bothDirections`, and `partial`.
13
12
  */
14
- onPlane(plane: PlaneLike | PlaneObjectBase, offset?: number, bothDirections?: boolean): this;
13
+ onPlane(plane: PlaneLike | PlaneObjectBase, offsetOrOptions?: number | {
14
+ offset?: number;
15
+ bothDirections?: boolean;
16
+ partial?: boolean;
17
+ }): this;
15
18
  /**
16
19
  * Excludes edges that lie on the given plane.
17
20
  * @param plane - The reference plane.
18
- * @param offset - Optional distance to offset the plane before matching.
19
- * @param bothDirections - When true, also excludes the plane offset in the opposite direction.
21
+ * @param offsetOrOptions - Offset distance, or an options object with `offset`, `bothDirections`, and `partial`.
20
22
  */
21
- notOnPlane(plane: PlaneLike | PlaneObjectBase, offset?: number, bothDirections?: boolean): this;
23
+ notOnPlane(plane: PlaneLike | PlaneObjectBase, offsetOrOptions?: number | {
24
+ offset?: number;
25
+ bothDirections?: boolean;
26
+ partial?: boolean;
27
+ }): this;
22
28
  /**
23
29
  * Selects edges that are parallel to the given plane.
24
30
  * @param plane - The reference plane.
@@ -89,5 +95,33 @@ export declare class EdgeFilterBuilder extends FilterBuilderBase<Edge> {
89
95
  * @param faceFilters - One or more face filter builders to match against.
90
96
  */
91
97
  notBelongsToFace(...faceFilters: FilterBuilderBase<Face>[]): this;
98
+ /**
99
+ * Selects edges that geometrically intersect with edges of the given scene object.
100
+ * @param sceneObject - A scene object whose edges are tested for intersection.
101
+ */
102
+ intersectsWith(sceneObject: ISceneObject): this;
103
+ /**
104
+ * Excludes edges that geometrically intersect with edges of the given scene object.
105
+ * @param sceneObject - A scene object whose edges are tested for intersection.
106
+ */
107
+ notIntersectsWith(sceneObject: ISceneObject): this;
108
+ /**
109
+ * Selects edges that are entirely above the given plane (in the direction of its normal).
110
+ * @param plane - The reference plane.
111
+ * @param offsetOrOptions - Offset distance, or an options object with `offset` and `partial`.
112
+ */
113
+ above(plane: PlaneLike | PlaneObjectBase, offsetOrOptions?: number | {
114
+ offset?: number;
115
+ partial?: boolean;
116
+ }): this;
117
+ /**
118
+ * Selects edges that are entirely below the given plane (opposite to its normal direction).
119
+ * @param plane - The reference plane.
120
+ * @param offsetOrOptions - Offset distance, or an options object with `offset` and `partial`.
121
+ */
122
+ below(plane: PlaneLike | PlaneObjectBase, offsetOrOptions?: number | {
123
+ offset?: number;
124
+ partial?: boolean;
125
+ }): this;
92
126
  static build(): EdgeFilterBuilder;
93
127
  }
@@ -11,6 +11,8 @@ import { PlaneObjectBase } from "../../features/plane-renderable-base.js";
11
11
  import { AtIndexFilter, NotAtIndexFilter } from "./at-index.js";
12
12
  import { BelongsToFaceFilter, NotBelongsToFaceFilter } from "./belongs-to-face.js";
13
13
  import { BelongsToFaceFromSceneObjectFilter, NotBelongsToFaceFromSceneObjectFilter } from "./belongs-to-object.js";
14
+ import { IntersectsWithFilter, NotIntersectsWithFilter } from "./intersects-with.js";
15
+ import { AbovePlaneFilter, BelowPlaneFilter } from "./above-below.js";
14
16
  import { SceneObject } from "../../common/scene-object.js";
15
17
  export class EdgeFilterBuilder extends FilterBuilderBase {
16
18
  constructor() {
@@ -43,13 +45,14 @@ export class EdgeFilterBuilder extends FilterBuilderBase {
43
45
  /**
44
46
  * Selects edges that lie on the given plane.
45
47
  * @param plane - The reference plane.
46
- * @param offset - Optional distance to offset the plane before matching.
47
- * @param bothDirections - When true, also matches the plane offset in the opposite direction.
48
+ * @param offsetOrOptions - Offset distance, or an options object with `offset`, `bothDirections`, and `partial`.
48
49
  */
49
- onPlane(plane, offset = 0, bothDirections = false) {
50
+ onPlane(plane, offsetOrOptions) {
50
51
  if (!plane) {
51
52
  throw new Error('Plane is required');
52
53
  }
54
+ const opts = typeof offsetOrOptions === 'number' ? { offset: offsetOrOptions } : (offsetOrOptions ?? {});
55
+ const { offset = 0, bothDirections = false, partial = false } = opts;
53
56
  let planeObj;
54
57
  let planeObj2;
55
58
  if (plane instanceof PlaneObjectBase) {
@@ -67,20 +70,21 @@ export class EdgeFilterBuilder extends FilterBuilderBase {
67
70
  planeObj = new PlaneObject(normalized);
68
71
  }
69
72
  }
70
- const filter = new OnPlaneFilter(planeObj, planeObj2);
73
+ const filter = new OnPlaneFilter(planeObj, planeObj2, partial);
71
74
  this.filters.push(filter);
72
75
  return this;
73
76
  }
74
77
  /**
75
78
  * Excludes edges that lie on the given plane.
76
79
  * @param plane - The reference plane.
77
- * @param offset - Optional distance to offset the plane before matching.
78
- * @param bothDirections - When true, also excludes the plane offset in the opposite direction.
80
+ * @param offsetOrOptions - Offset distance, or an options object with `offset`, `bothDirections`, and `partial`.
79
81
  */
80
- notOnPlane(plane, offset = 0, bothDirections = false) {
82
+ notOnPlane(plane, offsetOrOptions) {
81
83
  if (!plane) {
82
84
  throw new Error('Plane is required');
83
85
  }
86
+ const opts = typeof offsetOrOptions === 'number' ? { offset: offsetOrOptions } : (offsetOrOptions ?? {});
87
+ const { offset = 0, bothDirections = false, partial = false } = opts;
84
88
  let planeObj;
85
89
  let planeObj2;
86
90
  if (plane instanceof PlaneObjectBase) {
@@ -98,7 +102,7 @@ export class EdgeFilterBuilder extends FilterBuilderBase {
98
102
  planeObj = new PlaneObject(normalized);
99
103
  }
100
104
  }
101
- const filter = new NotOnPlaneFilter(planeObj, planeObj2);
105
+ const filter = new NotOnPlaneFilter(planeObj, planeObj2, partial);
102
106
  this.filters.push(filter);
103
107
  return this;
104
108
  }
@@ -262,6 +266,70 @@ export class EdgeFilterBuilder extends FilterBuilderBase {
262
266
  }
263
267
  return this;
264
268
  }
269
+ /**
270
+ * Selects edges that geometrically intersect with edges of the given scene object.
271
+ * @param sceneObject - A scene object whose edges are tested for intersection.
272
+ */
273
+ intersectsWith(sceneObject) {
274
+ const filter = new IntersectsWithFilter(sceneObject);
275
+ this.filters.push(filter);
276
+ return this;
277
+ }
278
+ /**
279
+ * Excludes edges that geometrically intersect with edges of the given scene object.
280
+ * @param sceneObject - A scene object whose edges are tested for intersection.
281
+ */
282
+ notIntersectsWith(sceneObject) {
283
+ const filter = new NotIntersectsWithFilter(sceneObject);
284
+ this.filters.push(filter);
285
+ return this;
286
+ }
287
+ /**
288
+ * Selects edges that are entirely above the given plane (in the direction of its normal).
289
+ * @param plane - The reference plane.
290
+ * @param offsetOrOptions - Offset distance, or an options object with `offset` and `partial`.
291
+ */
292
+ above(plane, offsetOrOptions) {
293
+ if (!plane) {
294
+ throw new Error('Plane is required');
295
+ }
296
+ const opts = typeof offsetOrOptions === 'number' ? { offset: offsetOrOptions } : (offsetOrOptions ?? {});
297
+ const { offset = 0, partial = false } = opts;
298
+ let planeObj;
299
+ if (plane instanceof PlaneObjectBase) {
300
+ planeObj = plane;
301
+ }
302
+ else {
303
+ let normalized = normalizePlane(plane);
304
+ planeObj = offset ? new PlaneObject(normalized.offset(offset)) : new PlaneObject(normalized);
305
+ }
306
+ const filter = new AbovePlaneFilter(planeObj, partial);
307
+ this.filters.push(filter);
308
+ return this;
309
+ }
310
+ /**
311
+ * Selects edges that are entirely below the given plane (opposite to its normal direction).
312
+ * @param plane - The reference plane.
313
+ * @param offsetOrOptions - Offset distance, or an options object with `offset` and `partial`.
314
+ */
315
+ below(plane, offsetOrOptions) {
316
+ if (!plane) {
317
+ throw new Error('Plane is required');
318
+ }
319
+ const opts = typeof offsetOrOptions === 'number' ? { offset: offsetOrOptions } : (offsetOrOptions ?? {});
320
+ const { offset = 0, partial = false } = opts;
321
+ let planeObj;
322
+ if (plane instanceof PlaneObjectBase) {
323
+ planeObj = plane;
324
+ }
325
+ else {
326
+ let normalized = normalizePlane(plane);
327
+ planeObj = offset ? new PlaneObject(normalized.offset(offset)) : new PlaneObject(normalized);
328
+ }
329
+ const filter = new BelowPlaneFilter(planeObj, partial);
330
+ this.filters.push(filter);
331
+ return this;
332
+ }
265
333
  static build() {
266
334
  return new EdgeFilterBuilder();
267
335
  }