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.
Files changed (63) hide show
  1. package/README.md +7 -2
  2. package/lib/dist/common/shape.d.ts +7 -0
  3. package/lib/dist/common/shape.js +7 -0
  4. package/lib/dist/core/copy.js +1 -1
  5. package/lib/dist/core/draft.d.ts +16 -0
  6. package/lib/dist/core/draft.js +29 -0
  7. package/lib/dist/core/index.d.ts +2 -3
  8. package/lib/dist/core/index.js +1 -1
  9. package/lib/dist/core/interfaces.d.ts +2 -0
  10. package/lib/dist/core/part.d.ts +2 -6
  11. package/lib/dist/core/part.js +12 -22
  12. package/lib/dist/core/trim.d.ts +12 -5
  13. package/lib/dist/core/trim.js +7 -2
  14. package/lib/dist/features/2d/sketch.js +23 -17
  15. package/lib/dist/features/copy-circular.js +1 -0
  16. package/lib/dist/features/copy-circular2d.js +1 -0
  17. package/lib/dist/features/copy-linear.js +4 -5
  18. package/lib/dist/features/copy-linear2d.js +4 -5
  19. package/lib/dist/features/draft.d.ts +15 -0
  20. package/lib/dist/features/draft.js +88 -0
  21. package/lib/dist/features/extrude-base.js +1 -0
  22. package/lib/dist/features/remove.d.ts +2 -0
  23. package/lib/dist/features/remove.js +7 -0
  24. package/lib/dist/features/trim2d.d.ts +16 -4
  25. package/lib/dist/features/trim2d.js +80 -29
  26. package/lib/dist/filters/edge/above-below.d.ts +20 -0
  27. package/lib/dist/filters/edge/above-below.js +57 -0
  28. package/lib/dist/filters/edge/edge-filter.d.ts +40 -6
  29. package/lib/dist/filters/edge/edge-filter.js +76 -8
  30. package/lib/dist/filters/edge/intersects-with.d.ts +18 -0
  31. package/lib/dist/filters/edge/intersects-with.js +38 -0
  32. package/lib/dist/filters/edge/on-plane.d.ts +4 -2
  33. package/lib/dist/filters/edge/on-plane.js +37 -12
  34. package/lib/dist/filters/filter-builder-base.d.ts +0 -5
  35. package/lib/dist/filters/filter-builder-base.js +12 -0
  36. package/lib/dist/helpers/clone-transform.js +3 -0
  37. package/lib/dist/oc/draft-ops.d.ts +5 -0
  38. package/lib/dist/oc/draft-ops.js +51 -0
  39. package/lib/dist/oc/edge-query.d.ts +5 -1
  40. package/lib/dist/oc/edge-query.js +40 -0
  41. package/lib/dist/oc/mesh.d.ts +2 -0
  42. package/lib/dist/oc/mesh.js +14 -6
  43. package/lib/dist/rendering/mesh-transform.d.ts +3 -0
  44. package/lib/dist/rendering/mesh-transform.js +22 -0
  45. package/lib/dist/rendering/render-solid.js +3 -2
  46. package/lib/dist/rendering/render.js +28 -6
  47. package/lib/dist/tests/features/chamfer.test.js +1 -1
  48. package/lib/dist/tests/features/draft.test.d.ts +1 -0
  49. package/lib/dist/tests/features/draft.test.js +147 -0
  50. package/lib/dist/tests/features/fillet.test.js +1 -1
  51. package/lib/dist/tests/features/part.test.js +69 -114
  52. package/lib/dist/tests/features/select.test.js +101 -3
  53. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  54. package/package.json +2 -3
  55. package/server/dist/fluidcad-server.d.ts +2 -0
  56. package/server/dist/fluidcad-server.js +10 -0
  57. package/server/dist/routes/actions.js +20 -0
  58. package/server/dist/vite-manager.js +7 -1
  59. package/ui/dist/assets/{index-B1LkrBga.js → index-CqP_mgZk.js} +23 -12
  60. package/ui/dist/assets/{index-BfcNNxXr.css → index-gPoNOiIs.css} +1 -1
  61. package/ui/dist/index.html +2 -2
  62. package/lib/dist/core/use.d.ts +0 -5
  63. package/lib/dist/core/use.js +0 -22
@@ -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
  }
@@ -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
- constructor(plane: PlaneObjectBase, plane2?: PlaneObjectBase);
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
- constructor(plane: PlaneObjectBase, plane2?: PlaneObjectBase);
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
- constructor(plane, plane2) {
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 plane = this.plane.getPlane();
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(planeObj, planeObj2);
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
- constructor(plane, plane2) {
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 plane = this.plane.getPlane();
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(planeObj, planeObj2);
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
  }