fluidcad 0.0.30 → 0.0.32

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 (135) hide show
  1. package/lib/dist/common/build-error.d.ts +13 -0
  2. package/lib/dist/common/build-error.js +18 -0
  3. package/lib/dist/common/describe-error.d.ts +6 -0
  4. package/lib/dist/common/describe-error.js +26 -0
  5. package/lib/dist/common/operand-check.d.ts +19 -0
  6. package/lib/dist/common/operand-check.js +38 -0
  7. package/lib/dist/common/scene-object.d.ts +8 -0
  8. package/lib/dist/common/scene-object.js +10 -0
  9. package/lib/dist/common/shape-factory.d.ts +1 -1
  10. package/lib/dist/core/2d/arc.d.ts +4 -2
  11. package/lib/dist/core/2d/hmove.d.ts +8 -1
  12. package/lib/dist/core/2d/hmove.js +6 -2
  13. package/lib/dist/core/2d/pmove.d.ts +13 -3
  14. package/lib/dist/core/2d/pmove.js +6 -2
  15. package/lib/dist/core/2d/vmove.d.ts +8 -1
  16. package/lib/dist/core/2d/vmove.js +8 -4
  17. package/lib/dist/core/extrude.d.ts +17 -4
  18. package/lib/dist/core/extrude.js +8 -6
  19. package/lib/dist/core/interfaces.d.ts +16 -6
  20. package/lib/dist/core/mirror.d.ts +5 -5
  21. package/lib/dist/features/2d/aline.js +6 -2
  22. package/lib/dist/features/2d/arc.d.ts +3 -0
  23. package/lib/dist/features/2d/arc.js +28 -1
  24. package/lib/dist/features/2d/hline.js +5 -1
  25. package/lib/dist/features/2d/hmove.d.ts +2 -2
  26. package/lib/dist/features/2d/hmove.js +32 -7
  27. package/lib/dist/features/2d/intersect.js +17 -10
  28. package/lib/dist/features/2d/line.d.ts +2 -0
  29. package/lib/dist/features/2d/line.js +4 -0
  30. package/lib/dist/features/2d/pmove.d.ts +2 -2
  31. package/lib/dist/features/2d/pmove.js +47 -7
  32. package/lib/dist/features/2d/projection.d.ts +1 -1
  33. package/lib/dist/features/2d/projection.js +25 -15
  34. package/lib/dist/features/2d/sketch.d.ts +2 -2
  35. package/lib/dist/features/2d/sketch.js +10 -4
  36. package/lib/dist/features/2d/tarc-to-point.js +0 -3
  37. package/lib/dist/features/2d/tarc.js +0 -3
  38. package/lib/dist/features/2d/tline.js +0 -3
  39. package/lib/dist/features/2d/vline.js +5 -1
  40. package/lib/dist/features/2d/vmove.d.ts +2 -2
  41. package/lib/dist/features/2d/vmove.js +32 -7
  42. package/lib/dist/features/axis-from-edge.d.ts +1 -0
  43. package/lib/dist/features/axis-from-edge.js +8 -0
  44. package/lib/dist/features/chamfer.d.ts +1 -0
  45. package/lib/dist/features/chamfer.js +6 -0
  46. package/lib/dist/features/color.d.ts +1 -0
  47. package/lib/dist/features/color.js +6 -0
  48. package/lib/dist/features/common.d.ts +1 -0
  49. package/lib/dist/features/common.js +9 -0
  50. package/lib/dist/features/common2d.d.ts +1 -0
  51. package/lib/dist/features/common2d.js +9 -0
  52. package/lib/dist/features/draft.d.ts +1 -0
  53. package/lib/dist/features/draft.js +6 -0
  54. package/lib/dist/features/extrude-to-face.d.ts +5 -1
  55. package/lib/dist/features/extrude-to-face.js +50 -8
  56. package/lib/dist/features/extrude.js +19 -28
  57. package/lib/dist/features/fillet.d.ts +1 -0
  58. package/lib/dist/features/fillet.js +6 -0
  59. package/lib/dist/features/fillet2d.d.ts +1 -0
  60. package/lib/dist/features/fillet2d.js +9 -0
  61. package/lib/dist/features/fuse.d.ts +1 -0
  62. package/lib/dist/features/fuse.js +6 -0
  63. package/lib/dist/features/fuse2d.d.ts +1 -0
  64. package/lib/dist/features/fuse2d.js +9 -0
  65. package/lib/dist/features/loft.d.ts +1 -0
  66. package/lib/dist/features/loft.js +6 -0
  67. package/lib/dist/features/mirror-shape.d.ts +1 -0
  68. package/lib/dist/features/mirror-shape.js +28 -8
  69. package/lib/dist/features/plane-from-object.d.ts +1 -0
  70. package/lib/dist/features/plane-from-object.js +8 -0
  71. package/lib/dist/features/rotate.d.ts +1 -0
  72. package/lib/dist/features/rotate.js +11 -2
  73. package/lib/dist/features/select.d.ts +1 -0
  74. package/lib/dist/features/select.js +40 -12
  75. package/lib/dist/features/shell.d.ts +1 -0
  76. package/lib/dist/features/shell.js +6 -0
  77. package/lib/dist/features/simple-extruder.js +6 -3
  78. package/lib/dist/features/subtract.d.ts +1 -0
  79. package/lib/dist/features/subtract.js +5 -0
  80. package/lib/dist/features/subtract2d.d.ts +1 -0
  81. package/lib/dist/features/subtract2d.js +5 -0
  82. package/lib/dist/features/sweep.d.ts +1 -0
  83. package/lib/dist/features/sweep.js +4 -0
  84. package/lib/dist/features/translate.d.ts +1 -0
  85. package/lib/dist/features/translate.js +9 -0
  86. package/lib/dist/filters/face/above-below.d.ts +20 -0
  87. package/lib/dist/filters/face/above-below.js +57 -0
  88. package/lib/dist/filters/face/face-filter.d.ts +26 -0
  89. package/lib/dist/filters/face/face-filter.js +64 -0
  90. package/lib/dist/filters/face/planar-filter.d.ts +15 -0
  91. package/lib/dist/filters/face/planar-filter.js +30 -0
  92. package/lib/dist/filters/from-object.d.ts +1 -0
  93. package/lib/dist/filters/from-object.js +3 -0
  94. package/lib/dist/oc/boolean-ops.d.ts +2 -2
  95. package/lib/dist/oc/boolean-ops.js +8 -3
  96. package/lib/dist/oc/edge-ops.d.ts +17 -0
  97. package/lib/dist/oc/edge-ops.js +60 -0
  98. package/lib/dist/oc/face-maker2.d.ts +8 -0
  99. package/lib/dist/oc/face-maker2.js +42 -1
  100. package/lib/dist/oc/face-ops.d.ts +6 -1
  101. package/lib/dist/oc/face-ops.js +3 -2
  102. package/lib/dist/oc/face-query.js +19 -15
  103. package/lib/dist/oc/ray-intersect.d.ts +3 -2
  104. package/lib/dist/oc/ray-intersect.js +2 -4
  105. package/lib/dist/oc/shell-ops.js +15 -2
  106. package/lib/dist/oc/thin-face-maker.d.ts +15 -0
  107. package/lib/dist/oc/thin-face-maker.js +48 -7
  108. package/lib/dist/oc/wire-ops.d.ts +14 -0
  109. package/lib/dist/oc/wire-ops.js +38 -0
  110. package/lib/dist/rendering/render.js +6 -4
  111. package/lib/dist/tests/common/describe-error.test.d.ts +1 -0
  112. package/lib/dist/tests/common/describe-error.test.js +36 -0
  113. package/lib/dist/tests/features/2d/intersect.test.js +43 -0
  114. package/lib/dist/tests/features/2d/move.test.js +72 -1
  115. package/lib/dist/tests/features/2d/project-regression.test.js +35 -0
  116. package/lib/dist/tests/features/color-lineage.test.js +24 -0
  117. package/lib/dist/tests/features/cut.test.js +40 -0
  118. package/lib/dist/tests/features/cylinder-curve-filter.test.d.ts +1 -0
  119. package/lib/dist/tests/features/cylinder-curve-filter.test.js +99 -0
  120. package/lib/dist/tests/features/extrude-to-face.test.js +52 -0
  121. package/lib/dist/tests/features/extrude.test.js +46 -8
  122. package/lib/dist/tests/features/mirror.test.js +74 -0
  123. package/lib/dist/tests/features/select.test.js +141 -0
  124. package/lib/dist/tests/features/subtract-consumed-input.test.d.ts +1 -0
  125. package/lib/dist/tests/features/subtract-consumed-input.test.js +28 -0
  126. package/lib/dist/tests/features/thin-extrude-offset-fix.test.d.ts +1 -0
  127. package/lib/dist/tests/features/thin-extrude-offset-fix.test.js +34 -0
  128. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  129. package/package.json +2 -3
  130. package/ui/dist/assets/{index-6Ep4GPxf.js → index-DMw0OYCF.js} +70 -70
  131. package/ui/dist/assets/index-DR7c2Qk9.css +2 -0
  132. package/ui/dist/index.html +2 -2
  133. package/lib/dist/features/infinite-extrude.d.ts +0 -13
  134. package/lib/dist/features/infinite-extrude.js +0 -79
  135. package/ui/dist/assets/index-DRKfe6N9.css +0 -2
@@ -3,6 +3,7 @@ import { BooleanOps } from "../oc/boolean-ops.js";
3
3
  import { Explorer } from "../oc/explorer.js";
4
4
  import { ShapeOps } from "../oc/shape-ops.js";
5
5
  import { Solid } from "../common/shapes.js";
6
+ import { requireShapes } from "../common/operand-check.js";
6
7
  export class Subtract extends SceneObject {
7
8
  solid1;
8
9
  solid2;
@@ -11,6 +12,10 @@ export class Subtract extends SceneObject {
11
12
  this.solid1 = solid1;
12
13
  this.solid2 = solid2;
13
14
  }
15
+ validate() {
16
+ requireShapes(this.solid1, "first operand", "subtract");
17
+ requireShapes(this.solid2, "second operand", "subtract");
18
+ }
14
19
  build(context) {
15
20
  const p = context.getProfiler();
16
21
  const stock = this.solid1.getShapes();
@@ -4,6 +4,7 @@ export declare class Subtract2D extends GeometrySceneObject {
4
4
  target1: GeometrySceneObject;
5
5
  target2: GeometrySceneObject;
6
6
  constructor(target1: GeometrySceneObject, target2: GeometrySceneObject);
7
+ validate(): void;
7
8
  private collectEdges;
8
9
  build(context: BuildSceneObjectContext): void;
9
10
  getDependencies(): SceneObject[];
@@ -6,6 +6,7 @@ import { BooleanOps } from "../oc/boolean-ops.js";
6
6
  import { ShapeOps } from "../oc/shape-ops.js";
7
7
  import { Explorer } from "../oc/explorer.js";
8
8
  import { FaceMaker2 } from "../oc/face-maker2.js";
9
+ import { requireShapes } from "../common/operand-check.js";
9
10
  export class Subtract2D extends GeometrySceneObject {
10
11
  target1;
11
12
  target2;
@@ -14,6 +15,10 @@ export class Subtract2D extends GeometrySceneObject {
14
15
  this.target1 = target1;
15
16
  this.target2 = target2;
16
17
  }
18
+ validate() {
19
+ requireShapes(this.target1, "first operand", "subtract2d");
20
+ requireShapes(this.target2, "second operand", "subtract2d");
21
+ }
17
22
  collectEdges(target) {
18
23
  const edges = new Map();
19
24
  for (const shape of target.getShapes()) {
@@ -6,6 +6,7 @@ export declare class Sweep extends ExtrudeBase implements ISweep {
6
6
  private _path;
7
7
  constructor(path: SceneObject, extrudable?: Extrudable);
8
8
  get path(): SceneObject;
9
+ validate(): void;
9
10
  build(context: BuildSceneObjectContext): void;
10
11
  /** Plain sweep: classify by inner-wire detection on the start face. */
11
12
  private buildSweep;
@@ -5,6 +5,7 @@ import { FaceMaker2 } from "../oc/face-maker2.js";
5
5
  import { ExtrudeBase } from "./extrude-base.js";
6
6
  import { cutWithSceneObjects } from "../helpers/scene-helpers.js";
7
7
  import { ThinFaceMaker } from "../oc/thin-face-maker.js";
8
+ import { requireShapes } from "../common/operand-check.js";
8
9
  export class Sweep extends ExtrudeBase {
9
10
  _path;
10
11
  constructor(path, extrudable) {
@@ -14,6 +15,9 @@ export class Sweep extends ExtrudeBase {
14
15
  get path() {
15
16
  return this._path;
16
17
  }
18
+ validate() {
19
+ requireShapes(this._path, "path", "sweep");
20
+ }
17
21
  build(context) {
18
22
  const p = context.getProfiler();
19
23
  const plane = this.extrudable.getPlane();
@@ -8,6 +8,7 @@ export declare class Translate extends SceneObject {
8
8
  constructor(amount: LazyVertex, copy?: boolean, ...targets: SceneObject[]);
9
9
  get targetObjects(): SceneObject[];
10
10
  exclude(...objects: SceneObject[]): this;
11
+ validate(): void;
11
12
  build(context: BuildSceneObjectContext): void;
12
13
  getDependencies(): SceneObject[];
13
14
  createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
@@ -1,6 +1,7 @@
1
1
  import { SceneObject } from "../common/scene-object.js";
2
2
  import { Matrix4 } from "../math/matrix4.js";
3
3
  import { ShapeOps } from "../oc/shape-ops.js";
4
+ import { requireShapes } from "../common/operand-check.js";
4
5
  export class Translate extends SceneObject {
5
6
  amount;
6
7
  copy;
@@ -19,6 +20,14 @@ export class Translate extends SceneObject {
19
20
  this._excludedObjects.push(...objects);
20
21
  return this;
21
22
  }
23
+ validate() {
24
+ if (!this._targetObjects) {
25
+ return;
26
+ }
27
+ for (let i = 0; i < this._targetObjects.length; i++) {
28
+ requireShapes(this._targetObjects[i], `target ${i + 1}`, "translate");
29
+ }
30
+ }
22
31
  build(context) {
23
32
  let objects = this.targetObjects || context.getSceneObjects();
24
33
  if (this._excludedObjects.length > 0) {
@@ -0,0 +1,20 @@
1
+ import { Matrix4 } from "../../math/matrix4.js";
2
+ import { Face } from "../../common/shapes.js";
3
+ import { FilterBase } from "../filter-base.js";
4
+ import { PlaneObjectBase } from "../../features/plane-renderable-base.js";
5
+ export declare class AboveFacePlaneFilter extends FilterBase<Face> {
6
+ private plane;
7
+ private partial;
8
+ constructor(plane: PlaneObjectBase, partial?: boolean);
9
+ match(shape: Face): boolean;
10
+ compareTo(other: AboveFacePlaneFilter): boolean;
11
+ transform(matrix: Matrix4): AboveFacePlaneFilter;
12
+ }
13
+ export declare class BelowFacePlaneFilter extends FilterBase<Face> {
14
+ private plane;
15
+ private partial;
16
+ constructor(plane: PlaneObjectBase, partial?: boolean);
17
+ match(shape: Face): boolean;
18
+ compareTo(other: BelowFacePlaneFilter): boolean;
19
+ transform(matrix: Matrix4): BelowFacePlaneFilter;
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
+ function getBoundaryPoints(face) {
5
+ return face.getEdges().flatMap(edge => [
6
+ EdgeOps.getVertexPoint(EdgeOps.getFirstVertex(edge)),
7
+ EdgeOps.getVertexPoint(EdgeOps.getLastVertex(edge)),
8
+ ]);
9
+ }
10
+ export class AboveFacePlaneFilter extends FilterBase {
11
+ plane;
12
+ partial;
13
+ constructor(plane, partial = false) {
14
+ super();
15
+ this.plane = plane;
16
+ this.partial = partial;
17
+ }
18
+ match(shape) {
19
+ const plane = this.plane.getPlane();
20
+ const flags = getBoundaryPoints(shape).map(p => plane.signedDistanceToPoint(p) > 0);
21
+ if (flags.length === 0) {
22
+ return false;
23
+ }
24
+ return this.partial ? flags.some(Boolean) : flags.every(Boolean);
25
+ }
26
+ compareTo(other) {
27
+ return this.plane.compareTo(other.plane) && this.partial === other.partial;
28
+ }
29
+ transform(matrix) {
30
+ const transformedPlane = this.plane.getPlane().applyMatrix(matrix);
31
+ return new AboveFacePlaneFilter(new PlaneObject(transformedPlane), this.partial);
32
+ }
33
+ }
34
+ export class BelowFacePlaneFilter extends FilterBase {
35
+ plane;
36
+ partial;
37
+ constructor(plane, partial = false) {
38
+ super();
39
+ this.plane = plane;
40
+ this.partial = partial;
41
+ }
42
+ match(shape) {
43
+ const plane = this.plane.getPlane();
44
+ const flags = getBoundaryPoints(shape).map(p => plane.signedDistanceToPoint(p) < 0);
45
+ if (flags.length === 0) {
46
+ return false;
47
+ }
48
+ return this.partial ? flags.some(Boolean) : flags.every(Boolean);
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 BelowFacePlaneFilter(new PlaneObject(transformedPlane), this.partial);
56
+ }
57
+ }
@@ -70,6 +70,14 @@ export declare class FaceFilterBuilder extends FilterBuilderBase<Face> {
70
70
  * @param minorRadius - Optional radius of the tube itself.
71
71
  */
72
72
  notTorus(majorRadius?: number, minorRadius?: number): this;
73
+ /**
74
+ * Selects planar (flat) faces.
75
+ */
76
+ planar(): this;
77
+ /**
78
+ * Excludes planar (flat) faces.
79
+ */
80
+ notPlanar(): this;
73
81
  /**
74
82
  * Selects conical faces.
75
83
  */
@@ -119,6 +127,24 @@ export declare class FaceFilterBuilder extends FilterBuilderBase<Face> {
119
127
  * @param count - The number of edges to exclude.
120
128
  */
121
129
  notEdgeCount(count: number): this;
130
+ /**
131
+ * Selects faces that are entirely above the given plane (in the direction of its normal).
132
+ * @param plane - The reference plane.
133
+ * @param offsetOrOptions - Offset distance, or an options object with `offset` and `partial`.
134
+ */
135
+ above(plane: PlaneLike | PlaneObjectBase, offsetOrOptions?: number | {
136
+ offset?: number;
137
+ partial?: boolean;
138
+ }): this;
139
+ /**
140
+ * Selects faces that are entirely below the given plane (opposite to its normal direction).
141
+ * @param plane - The reference plane.
142
+ * @param offsetOrOptions - Offset distance, or an options object with `offset` and `partial`.
143
+ */
144
+ below(plane: PlaneLike | PlaneObjectBase, offsetOrOptions?: number | {
145
+ offset?: number;
146
+ partial?: boolean;
147
+ }): this;
122
148
  /**
123
149
  * Restricts the selection to faces originating from the given scene objects.
124
150
  * Recursive: passing a container picks up faces from its descendants.
@@ -5,6 +5,7 @@ import { ConeFilter, NotConeFilter } from "./cone-filter.js";
5
5
  import { CylinderCurveFilter, NotCylinderCurveFilter } from "./cylinder-curve.js";
6
6
  import { CylinderFilter, NotCylinderFilter } from "./cylinder.js";
7
7
  import { TorusFilter, NotTorusFilter } from "./torus-filter.js";
8
+ import { PlanarFilter, NotPlanarFilter } from "./planar-filter.js";
8
9
  import { NotOnPlaneFilter, OnPlaneFilter } from "./on-plane.js";
9
10
  import { NotParallelFilter, ParallelFilter } from "./parallel.js";
10
11
  import { PlaneObject } from "../../features/plane.js";
@@ -15,6 +16,7 @@ import { HasEdgeFromSceneObjectFilter, NotHasEdgeFromSceneObjectFilter } from ".
15
16
  import { FromSceneObjectFilter } from "../from-object.js";
16
17
  import { EdgeCountFilter, NotEdgeCountFilter } from "./edge-count.js";
17
18
  import { IntersectsWithFilter, NotIntersectsWithFilter } from "./intersects-with.js";
19
+ import { AboveFacePlaneFilter, BelowFacePlaneFilter } from "./above-below.js";
18
20
  import { SceneObject } from "../../common/scene-object.js";
19
21
  export class FaceFilterBuilder extends FilterBuilderBase {
20
22
  constructor() {
@@ -204,6 +206,22 @@ export class FaceFilterBuilder extends FilterBuilderBase {
204
206
  this.filters.push(filter);
205
207
  return this;
206
208
  }
209
+ /**
210
+ * Selects planar (flat) faces.
211
+ */
212
+ planar() {
213
+ const filter = new PlanarFilter();
214
+ this.filters.push(filter);
215
+ return this;
216
+ }
217
+ /**
218
+ * Excludes planar (flat) faces.
219
+ */
220
+ notPlanar() {
221
+ const filter = new NotPlanarFilter();
222
+ this.filters.push(filter);
223
+ return this;
224
+ }
207
225
  /**
208
226
  * Selects conical faces.
209
227
  */
@@ -306,6 +324,52 @@ export class FaceFilterBuilder extends FilterBuilderBase {
306
324
  this.filters.push(filter);
307
325
  return this;
308
326
  }
327
+ /**
328
+ * Selects faces that are entirely above the given plane (in the direction of its normal).
329
+ * @param plane - The reference plane.
330
+ * @param offsetOrOptions - Offset distance, or an options object with `offset` and `partial`.
331
+ */
332
+ above(plane, offsetOrOptions) {
333
+ if (!plane) {
334
+ throw new Error('Plane is required');
335
+ }
336
+ const opts = typeof offsetOrOptions === 'number' ? { offset: offsetOrOptions } : (offsetOrOptions ?? {});
337
+ const { offset = 0, partial = false } = opts;
338
+ let planeObj;
339
+ if (plane instanceof PlaneObjectBase) {
340
+ planeObj = plane;
341
+ }
342
+ else {
343
+ let normalized = normalizePlane(plane);
344
+ planeObj = offset ? new PlaneObject(normalized.offset(offset)) : new PlaneObject(normalized);
345
+ }
346
+ const filter = new AboveFacePlaneFilter(planeObj, partial);
347
+ this.filters.push(filter);
348
+ return this;
349
+ }
350
+ /**
351
+ * Selects faces that are entirely below the given plane (opposite to its normal direction).
352
+ * @param plane - The reference plane.
353
+ * @param offsetOrOptions - Offset distance, or an options object with `offset` and `partial`.
354
+ */
355
+ below(plane, offsetOrOptions) {
356
+ if (!plane) {
357
+ throw new Error('Plane is required');
358
+ }
359
+ const opts = typeof offsetOrOptions === 'number' ? { offset: offsetOrOptions } : (offsetOrOptions ?? {});
360
+ const { offset = 0, partial = false } = opts;
361
+ let planeObj;
362
+ if (plane instanceof PlaneObjectBase) {
363
+ planeObj = plane;
364
+ }
365
+ else {
366
+ let normalized = normalizePlane(plane);
367
+ planeObj = offset ? new PlaneObject(normalized.offset(offset)) : new PlaneObject(normalized);
368
+ }
369
+ const filter = new BelowFacePlaneFilter(planeObj, partial);
370
+ this.filters.push(filter);
371
+ return this;
372
+ }
309
373
  /**
310
374
  * Restricts the selection to faces originating from the given scene objects.
311
375
  * Recursive: passing a container picks up faces from its descendants.
@@ -0,0 +1,15 @@
1
+ import { Matrix4 } from "../../math/matrix4.js";
2
+ import { Face } from "../../common/shapes.js";
3
+ import { FilterBase } from "../filter-base.js";
4
+ export declare class PlanarFilter extends FilterBase<Face> {
5
+ constructor();
6
+ match(shape: Face): boolean;
7
+ compareTo(other: PlanarFilter): boolean;
8
+ transform(matrix: Matrix4): PlanarFilter;
9
+ }
10
+ export declare class NotPlanarFilter extends FilterBase<Face> {
11
+ constructor();
12
+ match(shape: Face): boolean;
13
+ compareTo(other: NotPlanarFilter): boolean;
14
+ transform(matrix: Matrix4): NotPlanarFilter;
15
+ }
@@ -0,0 +1,30 @@
1
+ import { FilterBase } from "../filter-base.js";
2
+ import { FaceQuery } from "../../oc/face-query.js";
3
+ export class PlanarFilter extends FilterBase {
4
+ constructor() {
5
+ super();
6
+ }
7
+ match(shape) {
8
+ return FaceQuery.isPlanarFace(shape);
9
+ }
10
+ compareTo(other) {
11
+ return true;
12
+ }
13
+ transform(matrix) {
14
+ return new PlanarFilter();
15
+ }
16
+ }
17
+ export class NotPlanarFilter extends FilterBase {
18
+ constructor() {
19
+ super();
20
+ }
21
+ match(shape) {
22
+ return !FaceQuery.isPlanarFace(shape);
23
+ }
24
+ compareTo(other) {
25
+ return true;
26
+ }
27
+ transform(matrix) {
28
+ return new NotPlanarFilter();
29
+ }
30
+ }
@@ -7,6 +7,7 @@ export declare class FromSceneObjectFilter<TShape extends Shape> extends FilterB
7
7
  private sceneObjects;
8
8
  private shapeType;
9
9
  constructor(sceneObjects: SceneObject[], shapeType: ShapeType);
10
+ getSceneObjects(): SceneObject[];
10
11
  match(shape: TShape): boolean;
11
12
  compareTo(other: FromSceneObjectFilter<TShape>): boolean;
12
13
  transform(_matrix: Matrix4): FromSceneObjectFilter<TShape>;
@@ -7,6 +7,9 @@ export class FromSceneObjectFilter extends FilterBase {
7
7
  this.sceneObjects = sceneObjects;
8
8
  this.shapeType = shapeType;
9
9
  }
10
+ getSceneObjects() {
11
+ return this.sceneObjects;
12
+ }
10
13
  match(shape) {
11
14
  for (const obj of this.sceneObjects) {
12
15
  const subShapes = obj.getShapes().flatMap(s => s.getSubShapes(this.shapeType));
@@ -8,8 +8,8 @@ export declare class BooleanOps {
8
8
  static cutShapes(shape: Shape, tool: Shape): Shape;
9
9
  static cutShapesRaw(shape: TopoDS_Shape, tool: TopoDS_Shape): TopoDS_Shape;
10
10
  static cutMultiShape(stocks: Shape[], tools: Shape[], plane?: Plane, cutDistance?: number): {
11
- result: Solid | Face | Edge | import("../common/wire.js").Wire;
12
- modified: (shape: Shape) => (Solid | Face | Edge | import("../common/wire.js").Wire)[];
11
+ result: Edge | import("../common/wire.js").Wire | Solid | Face;
12
+ modified: (shape: Shape) => (Edge | import("../common/wire.js").Wire | Solid | Face)[];
13
13
  sectionEdges: Edge[];
14
14
  startEdges: Edge[];
15
15
  endEdges: Edge[];
@@ -168,10 +168,15 @@ export class BooleanOps {
168
168
  else if (opts?.glue === 'shift') {
169
169
  builder.SetGlue(oc.BOPAlgo_GlueEnum.BOPAlgo_GlueShift);
170
170
  }
171
+ // Wrap all stocks in a single compound argument. OCC's pave-filling step
172
+ // computes intersections between distinct arguments — so two touching
173
+ // stocks passed as separate args end up merged with each other even when
174
+ // the tool doesn't touch them. Bundling them under one TopoDS_Compound
175
+ // keeps stock-to-stock relationships out of the result; only stock↔tool
176
+ // interactions are computed.
177
+ const stockCompound = ShapeOps.makeCompoundRaw(stock.map(s => s.getShape()));
171
178
  const stockList = new oc.TopTools_ListOfShape();
172
- for (const s of stock) {
173
- stockList.Append(s.getShape());
174
- }
179
+ stockList.Append(stockCompound);
175
180
  const toolList = new oc.TopTools_ListOfShape();
176
181
  for (const t of tools) {
177
182
  toolList.Append(t.getShape());
@@ -35,4 +35,21 @@ export declare class EdgeOps {
35
35
  static findNearestEdgeIndex(edges: Edge[], point: Point, tolerance?: number): number;
36
36
  static findNearestEdgeIndices(edges: Edge[], point: Point, tolerance?: number): number[];
37
37
  static isClosed(edge: Edge): boolean;
38
+ /**
39
+ * Removes coincident edges from an edge set and splits any pairs that
40
+ * intersect, while preserving each input edge's original boundaries.
41
+ *
42
+ * Uses OCCT's General Fuse: it splits inputs at mutual intersections and
43
+ * detects same-domain (coincident) pieces, keeping a single representative
44
+ * per group. `SimplifyResult` is intentionally not called — its
45
+ * edge-unification step concatenates tangent-continuous chains (line + arc
46
+ * fillets, etc.) into single BSpline curves, which destroys the per-edge
47
+ * structure callers need for downstream filter/trim operations.
48
+ *
49
+ * `fuzzy` overrides the matching tolerance — defaults to a value loose enough
50
+ * to absorb the vertex-tolerance drift produced by `BRepAlgo_NormalProjection`
51
+ * on coincident edges from independent face projections.
52
+ */
53
+ static unifyCoincident(edges: Edge[], fuzzy?: number): Edge[];
54
+ static unifyCoincidentRaw(edges: TopoDS_Edge[], fuzzy?: number): TopoDS_Edge[];
38
55
  }
@@ -320,4 +320,64 @@ export class EdgeOps {
320
320
  adaptor.delete();
321
321
  return closed;
322
322
  }
323
+ /**
324
+ * Removes coincident edges from an edge set and splits any pairs that
325
+ * intersect, while preserving each input edge's original boundaries.
326
+ *
327
+ * Uses OCCT's General Fuse: it splits inputs at mutual intersections and
328
+ * detects same-domain (coincident) pieces, keeping a single representative
329
+ * per group. `SimplifyResult` is intentionally not called — its
330
+ * edge-unification step concatenates tangent-continuous chains (line + arc
331
+ * fillets, etc.) into single BSpline curves, which destroys the per-edge
332
+ * structure callers need for downstream filter/trim operations.
333
+ *
334
+ * `fuzzy` overrides the matching tolerance — defaults to a value loose enough
335
+ * to absorb the vertex-tolerance drift produced by `BRepAlgo_NormalProjection`
336
+ * on coincident edges from independent face projections.
337
+ */
338
+ static unifyCoincident(edges, fuzzy) {
339
+ const raw = edges.map(e => e.getShape());
340
+ const unique = EdgeOps.unifyCoincidentRaw(raw, fuzzy);
341
+ return unique.map(e => Edge.fromTopoDSEdge(e));
342
+ }
343
+ static unifyCoincidentRaw(edges, fuzzy) {
344
+ const oc = getOC();
345
+ // Filter out degenerate edges (e.g. projection of an edge parallel to the
346
+ // projection direction collapses to zero length). These confuse the fuse.
347
+ const live = edges.filter(e => !oc.BRep_Tool.Degenerated(e));
348
+ if (live.length <= 1) {
349
+ return live;
350
+ }
351
+ const fuzzValue = fuzzy ?? Math.max(oc.Precision.Confusion() * 100, 1e-6);
352
+ const args = new oc.TopTools_ListOfShape();
353
+ let builder = null;
354
+ let progress = null;
355
+ try {
356
+ for (const e of live) {
357
+ args.Append(e);
358
+ }
359
+ builder = new oc.BRepAlgoAPI_BuilderAlgo();
360
+ builder.SetArguments(args);
361
+ builder.SetNonDestructive(true);
362
+ builder.SetFuzzyValue(fuzzValue);
363
+ progress = new oc.Message_ProgressRange();
364
+ builder.Build(progress);
365
+ const result = builder.Shape();
366
+ const unique = Explorer.findShapes(result, oc.TopAbs_ShapeEnum.TopAbs_EDGE).map(s => oc.TopoDS.Edge(s));
367
+ return unique;
368
+ }
369
+ catch (err) {
370
+ console.error('EdgeOps.unifyCoincidentRaw failed, falling back to input edges:', err);
371
+ return live;
372
+ }
373
+ finally {
374
+ args.delete();
375
+ if (builder) {
376
+ builder.delete();
377
+ }
378
+ if (progress) {
379
+ progress.delete();
380
+ }
381
+ }
382
+ }
323
383
  }
@@ -6,5 +6,13 @@ export declare class FaceMaker2 {
6
6
  static getRegions(shapes: Array<Wire | Edge>, plane: Plane, drill?: boolean): Face[];
7
7
  private static getDrilledFaces;
8
8
  private static getFaces;
9
+ /**
10
+ * Sizes the bounded plane face used by `getFaces`'s splitter so it always
11
+ * encloses the input edges with margin. The default ±1000 face used to
12
+ * silently swallow sketches placed far from origin: edges that crossed the
13
+ * boundary produced regions touching `boundaryEdges`, which the filter at
14
+ * the end of `getFaces` then dropped — leaving an extrude with zero faces.
15
+ */
16
+ private static computePlaneFaceBounds;
9
17
  private static getSplitEdges;
10
18
  }
@@ -1,4 +1,5 @@
1
1
  import { Edge } from "../common/edge.js";
2
+ import { Point } from "../math/point.js";
2
3
  import { Explorer } from "./explorer.js";
3
4
  import { getOC } from "./init.js";
4
5
  import { Convert } from "./convert.js";
@@ -67,7 +68,7 @@ export class FaceMaker2 {
67
68
  static getFaces(edges, plane) {
68
69
  const [gpPln, dispose] = Convert.toGpPln(plane);
69
70
  const oc = getOC();
70
- const planeFace = FaceOps.makeFaceFromPlane2(gpPln);
71
+ const planeFace = FaceOps.makeFaceFromPlane2(gpPln, this.computePlaneFaceBounds(edges, plane));
71
72
  // Collect boundary edges of the big face before splitting
72
73
  const boundaryEdges = Explorer.findShapes(planeFace, oc.TopAbs_ShapeEnum.TopAbs_EDGE);
73
74
  const splitter = new oc.BRepAlgoAPI_Splitter();
@@ -108,6 +109,46 @@ export class FaceMaker2 {
108
109
  dispose();
109
110
  return filtered.map(f => Face.fromTopoDSFace(oc.TopoDS.Face(f)));
110
111
  }
112
+ /**
113
+ * Sizes the bounded plane face used by `getFaces`'s splitter so it always
114
+ * encloses the input edges with margin. The default ±1000 face used to
115
+ * silently swallow sketches placed far from origin: edges that crossed the
116
+ * boundary produced regions touching `boundaryEdges`, which the filter at
117
+ * the end of `getFaces` then dropped — leaving an extrude with zero faces.
118
+ */
119
+ static computePlaneFaceBounds(edges, plane) {
120
+ let uMin = Infinity, uMax = -Infinity, vMin = Infinity, vMax = -Infinity;
121
+ for (const edge of edges) {
122
+ const bbox = ShapeOps.getBoundingBox(edge);
123
+ const corners = [
124
+ new Point(bbox.minX, bbox.minY, bbox.minZ),
125
+ new Point(bbox.maxX, bbox.minY, bbox.minZ),
126
+ new Point(bbox.minX, bbox.maxY, bbox.minZ),
127
+ new Point(bbox.maxX, bbox.maxY, bbox.minZ),
128
+ new Point(bbox.minX, bbox.minY, bbox.maxZ),
129
+ new Point(bbox.maxX, bbox.minY, bbox.maxZ),
130
+ new Point(bbox.minX, bbox.maxY, bbox.maxZ),
131
+ new Point(bbox.maxX, bbox.maxY, bbox.maxZ),
132
+ ];
133
+ for (const c of corners) {
134
+ const uv = plane.worldToLocal(c);
135
+ if (uv.x < uMin) {
136
+ uMin = uv.x;
137
+ }
138
+ if (uv.x > uMax) {
139
+ uMax = uv.x;
140
+ }
141
+ if (uv.y < vMin) {
142
+ vMin = uv.y;
143
+ }
144
+ if (uv.y > vMax) {
145
+ vMax = uv.y;
146
+ }
147
+ }
148
+ }
149
+ const half = Math.max(Math.abs(uMin), Math.abs(uMax), Math.abs(vMin), Math.abs(vMax), 1000) * 1.5;
150
+ return { uMin: -half, uMax: half, vMin: -half, vMax: half };
151
+ }
111
152
  static getSplitEdges(shapes) {
112
153
  const oc = getOC();
113
154
  console.log('Getting split edges for shapes:', shapes.length);
@@ -20,7 +20,12 @@ export declare class FaceOps {
20
20
  static fixFaceOrientation(face: Face | TopoDS_Face): Face;
21
21
  static makeFaceWithHoles(outerWire: Wire, holes: Wire[]): Face;
22
22
  static isPointInsideFace(point: Point, face: Face | TopoDS_Face): boolean;
23
- static makeFaceFromPlane2(plane: gp_Pln): TopoDS_Face;
23
+ static makeFaceFromPlane2(plane: gp_Pln, bounds?: {
24
+ uMin: number;
25
+ uMax: number;
26
+ vMin: number;
27
+ vMax: number;
28
+ }): TopoDS_Face;
24
29
  static makeFaceFromPlane(plane: gp_Pln): TopoDS_Face;
25
30
  static makeFaceFromCylinder(cylinder: gp_Cylinder): TopoDS_Face;
26
31
  static planeToFace(plane: Plane, center?: Point): Face;
@@ -197,9 +197,10 @@ export class FaceOps {
197
197
  disposePnt();
198
198
  return isInside;
199
199
  }
200
- static makeFaceFromPlane2(plane) {
200
+ static makeFaceFromPlane2(plane, bounds) {
201
201
  const oc = getOC();
202
- const faceMaker = new oc.BRepBuilderAPI_MakeFace(plane, -1000, 1000, -1000, 1000);
202
+ const b = bounds ?? { uMin: -1000, uMax: 1000, vMin: -1000, vMax: 1000 };
203
+ const faceMaker = new oc.BRepBuilderAPI_MakeFace(plane, b.uMin, b.uMax, b.vMin, b.vMax);
203
204
  const face = faceMaker.Face();
204
205
  faceMaker.delete();
205
206
  return face;