fluidcad 0.0.28 → 0.0.30

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 (145) hide show
  1. package/lib/dist/common/profiler.d.ts +12 -0
  2. package/lib/dist/common/profiler.js +35 -0
  3. package/lib/dist/common/scene-object.d.ts +3 -0
  4. package/lib/dist/common/scene-object.js +3 -0
  5. package/lib/dist/common/shape-history-tracker.d.ts +9 -1
  6. package/lib/dist/common/shape-history-tracker.js +37 -23
  7. package/lib/dist/core/2d/aline.d.ts +13 -13
  8. package/lib/dist/core/2d/aline.js +20 -11
  9. package/lib/dist/core/2d/arc.d.ts +6 -6
  10. package/lib/dist/core/2d/arc.js +19 -15
  11. package/lib/dist/core/2d/back.d.ts +12 -0
  12. package/lib/dist/core/2d/back.js +11 -0
  13. package/lib/dist/core/2d/circle.d.ts +2 -2
  14. package/lib/dist/core/2d/circle.js +14 -10
  15. package/lib/dist/core/2d/ellipse.d.ts +35 -0
  16. package/lib/dist/core/2d/ellipse.js +65 -0
  17. package/lib/dist/core/2d/hline.d.ts +20 -13
  18. package/lib/dist/core/2d/hline.js +33 -15
  19. package/lib/dist/core/2d/index.d.ts +2 -0
  20. package/lib/dist/core/2d/index.js +2 -0
  21. package/lib/dist/core/2d/intersect.d.ts +2 -2
  22. package/lib/dist/core/2d/intersect.js +7 -3
  23. package/lib/dist/core/2d/line.d.ts +2 -2
  24. package/lib/dist/core/2d/line.js +14 -10
  25. package/lib/dist/core/2d/offset.d.ts +4 -4
  26. package/lib/dist/core/2d/offset.js +9 -5
  27. package/lib/dist/core/2d/polygon.d.ts +4 -4
  28. package/lib/dist/core/2d/polygon.js +24 -20
  29. package/lib/dist/core/2d/project.d.ts +2 -2
  30. package/lib/dist/core/2d/project.js +7 -3
  31. package/lib/dist/core/2d/rect.d.ts +2 -2
  32. package/lib/dist/core/2d/rect.js +22 -21
  33. package/lib/dist/core/2d/slot.d.ts +6 -6
  34. package/lib/dist/core/2d/slot.js +29 -32
  35. package/lib/dist/core/2d/vline.d.ts +20 -13
  36. package/lib/dist/core/2d/vline.js +29 -15
  37. package/lib/dist/core/interfaces.d.ts +62 -0
  38. package/lib/dist/core/mirror.d.ts +7 -7
  39. package/lib/dist/core/mirror.js +17 -11
  40. package/lib/dist/core/part.d.ts +3 -1
  41. package/lib/dist/core/part.js +1 -1
  42. package/lib/dist/core/rotate.d.ts +5 -5
  43. package/lib/dist/core/rotate.js +4 -1
  44. package/lib/dist/core/sketch.d.ts +3 -1
  45. package/lib/dist/core/sketch.js +1 -1
  46. package/lib/dist/core/translate.d.ts +9 -9
  47. package/lib/dist/features/2d/aline.d.ts +8 -5
  48. package/lib/dist/features/2d/aline.js +70 -18
  49. package/lib/dist/features/2d/back.d.ts +14 -0
  50. package/lib/dist/features/2d/back.js +35 -0
  51. package/lib/dist/features/2d/ellipse.d.ts +23 -0
  52. package/lib/dist/features/2d/ellipse.js +75 -0
  53. package/lib/dist/features/2d/hline.d.ts +9 -4
  54. package/lib/dist/features/2d/hline.js +65 -14
  55. package/lib/dist/features/2d/offset.d.ts +3 -0
  56. package/lib/dist/features/2d/offset.js +27 -3
  57. package/lib/dist/features/2d/sketch.d.ts +1 -0
  58. package/lib/dist/features/2d/sketch.js +15 -0
  59. package/lib/dist/features/2d/vline.d.ts +9 -4
  60. package/lib/dist/features/2d/vline.js +67 -15
  61. package/lib/dist/features/common.js +2 -1
  62. package/lib/dist/features/extrude-base.d.ts +19 -1
  63. package/lib/dist/features/extrude-base.js +75 -12
  64. package/lib/dist/features/extrude-two-distances.js +32 -27
  65. package/lib/dist/features/extrude.d.ts +39 -0
  66. package/lib/dist/features/extrude.js +196 -156
  67. package/lib/dist/features/fuse.js +2 -1
  68. package/lib/dist/features/lazy-scene-object.d.ts +1 -0
  69. package/lib/dist/features/lazy-scene-object.js +3 -0
  70. package/lib/dist/features/lazy-vertex.d.ts +1 -0
  71. package/lib/dist/features/lazy-vertex.js +3 -0
  72. package/lib/dist/features/loft.js +11 -8
  73. package/lib/dist/features/mirror-shape.d.ts +2 -0
  74. package/lib/dist/features/mirror-shape.js +16 -0
  75. package/lib/dist/features/mirror-shape2d.d.ts +2 -0
  76. package/lib/dist/features/mirror-shape2d.js +22 -1
  77. package/lib/dist/features/revolve.d.ts +31 -0
  78. package/lib/dist/features/revolve.js +178 -95
  79. package/lib/dist/features/rotate.d.ts +2 -0
  80. package/lib/dist/features/rotate.js +16 -0
  81. package/lib/dist/features/rotate2d.d.ts +2 -0
  82. package/lib/dist/features/rotate2d.js +16 -0
  83. package/lib/dist/features/select.js +2 -1
  84. package/lib/dist/features/simple-extruder.d.ts +3 -1
  85. package/lib/dist/features/simple-extruder.js +13 -9
  86. package/lib/dist/features/subtract.d.ts +2 -2
  87. package/lib/dist/features/subtract.js +3 -3
  88. package/lib/dist/features/sweep.d.ts +14 -0
  89. package/lib/dist/features/sweep.js +93 -80
  90. package/lib/dist/features/translate.d.ts +2 -0
  91. package/lib/dist/features/translate.js +23 -2
  92. package/lib/dist/filters/edge/edge-filter.d.ts +6 -0
  93. package/lib/dist/filters/edge/edge-filter.js +11 -0
  94. package/lib/dist/filters/face/face-filter.d.ts +6 -0
  95. package/lib/dist/filters/face/face-filter.js +11 -0
  96. package/lib/dist/filters/filter-base.d.ts +7 -1
  97. package/lib/dist/filters/filter-base.js +8 -0
  98. package/lib/dist/filters/filter-builder-base.js +11 -0
  99. package/lib/dist/filters/from-object.d.ts +14 -0
  100. package/lib/dist/filters/from-object.js +40 -0
  101. package/lib/dist/helpers/scene-helpers.d.ts +2 -0
  102. package/lib/dist/helpers/scene-helpers.js +68 -48
  103. package/lib/dist/oc/color-transfer.js +6 -0
  104. package/lib/dist/oc/edge-ops.d.ts +1 -0
  105. package/lib/dist/oc/edge-ops.js +17 -0
  106. package/lib/dist/oc/extrude-ops.d.ts +18 -1
  107. package/lib/dist/oc/extrude-ops.js +34 -1
  108. package/lib/dist/oc/geometry.d.ts +1 -0
  109. package/lib/dist/oc/geometry.js +27 -0
  110. package/lib/dist/oc/mesh.js +11 -9
  111. package/lib/dist/oc/ray-intersect.d.ts +16 -0
  112. package/lib/dist/oc/ray-intersect.js +91 -0
  113. package/lib/dist/oc/thin-face-maker.d.ts +0 -1
  114. package/lib/dist/oc/thin-face-maker.js +2 -20
  115. package/lib/dist/rendering/render.d.ts +2 -1
  116. package/lib/dist/rendering/render.js +72 -33
  117. package/lib/dist/rendering/scene.d.ts +4 -0
  118. package/lib/dist/tests/features/2d/back.test.d.ts +1 -0
  119. package/lib/dist/tests/features/2d/back.test.js +60 -0
  120. package/lib/dist/tests/features/2d/circle.test.js +1 -1
  121. package/lib/dist/tests/features/2d/constrained.test.js +4 -4
  122. package/lib/dist/tests/features/2d/ellipse.test.d.ts +1 -0
  123. package/lib/dist/tests/features/2d/ellipse.test.js +100 -0
  124. package/lib/dist/tests/features/2d/line.test.js +89 -3
  125. package/lib/dist/tests/features/2d/offset.test.js +1 -1
  126. package/lib/dist/tests/features/2d/polygon.test.js +2 -2
  127. package/lib/dist/tests/features/2d/rect.test.js +1 -1
  128. package/lib/dist/tests/features/2d/slot-from-edge.test.js +1 -1
  129. package/lib/dist/tests/features/2d/slot.test.js +1 -1
  130. package/lib/dist/tests/features/mirror.test.js +58 -0
  131. package/lib/dist/tests/features/mirror2d.test.js +63 -0
  132. package/lib/dist/tests/features/rotate.test.js +62 -0
  133. package/lib/dist/tests/features/rotate2d.test.js +47 -0
  134. package/lib/dist/tests/features/thin-revolve.test.js +37 -1
  135. package/lib/dist/tests/features/translate.test.js +63 -0
  136. package/lib/dist/tests/perf/record-fusion-history.bench.test.d.ts +1 -0
  137. package/lib/dist/tests/perf/record-fusion-history.bench.test.js +77 -0
  138. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  139. package/package.json +1 -1
  140. package/server/dist/index.js +77 -45
  141. package/server/dist/ws-protocol.d.ts +11 -0
  142. package/ui/dist/assets/{index-BrW_x4uc.js → index-6Ep4GPxf.js} +131 -77
  143. package/ui/dist/assets/index-DRKfe6N9.css +2 -0
  144. package/ui/dist/index.html +2 -2
  145. package/ui/dist/assets/index-gPoNOiIs.css +0 -2
@@ -1,17 +1,19 @@
1
1
  import { Face } from "../common/face.js";
2
2
  import { Shape } from "../common/shape.js";
3
3
  import { Plane } from "../math/plane.js";
4
+ import { Profiler } from "../common/profiler.js";
4
5
  export declare class Extruder {
5
6
  private faces;
6
7
  private plane;
7
8
  distance: number;
8
9
  draft?: [number, number];
9
10
  endOffset?: number;
11
+ private profiler?;
10
12
  private firstFaces;
11
13
  private lastFaces;
12
14
  private sideFaces;
13
15
  private _internalFaces;
14
- constructor(faces: Face[], plane: Plane, distance: number, draft?: [number, number], endOffset?: number);
16
+ constructor(faces: Face[], plane: Plane, distance: number, draft?: [number, number], endOffset?: number, profiler?: Profiler);
15
17
  getStartFaces(): Face[];
16
18
  getEndFaces(): Face[];
17
19
  getSideFaces(): Face[];
@@ -8,16 +8,18 @@ export class Extruder {
8
8
  distance;
9
9
  draft;
10
10
  endOffset;
11
+ profiler;
11
12
  firstFaces;
12
13
  lastFaces;
13
14
  sideFaces;
14
15
  _internalFaces;
15
- constructor(faces, plane, distance, draft, endOffset) {
16
+ constructor(faces, plane, distance, draft, endOffset, profiler) {
16
17
  this.faces = faces;
17
18
  this.plane = plane;
18
19
  this.distance = distance;
19
20
  this.draft = draft;
20
21
  this.endOffset = endOffset;
22
+ this.profiler = profiler;
21
23
  }
22
24
  getStartFaces() {
23
25
  return this.firstFaces;
@@ -42,16 +44,18 @@ export class Extruder {
42
44
  let lastFaces = [];
43
45
  let sideFaces = [];
44
46
  let internalFaces = [];
45
- console.log("Fusing faces before extrusion...", this.faces.length);
46
- const tFuseFaces = performance.now();
47
- const fusedFaces = BooleanOps.fuseFaces(this.faces);
48
- console.log(`[perf] Extruder.fuseFaces (in=${this.faces.length}, out=${fusedFaces.result.length}): ${(performance.now() - tFuseFaces).toFixed(1)} ms`);
47
+ const p = this.profiler;
48
+ const fusedFaces = p
49
+ ? p.record('Fuse profile faces', () => BooleanOps.fuseFaces(this.faces))
50
+ : BooleanOps.fuseFaces(this.faces);
49
51
  for (const face of fusedFaces.result) {
50
- const time = performance.now();
51
- let { solid, firstFace, lastFace } = ExtrudeOps.makePrismFromVec(face, vec);
52
- console.log(`[perf] Extruder.makePrismFromVec: ${(performance.now() - time).toFixed(1)} ms`);
52
+ let { solid, firstFace, lastFace } = p
53
+ ? p.record('Make prism from face', () => ExtrudeOps.makePrismFromVec(face, vec))
54
+ : ExtrudeOps.makePrismFromVec(face, vec);
53
55
  if (this.draft) {
54
- const draftResult = this.applyDraft(solid, firstFace, lastFace, this.plane);
56
+ const draftResult = p
57
+ ? p.record('Apply draft', () => this.applyDraft(solid, firstFace, lastFace, this.plane))
58
+ : this.applyDraft(solid, firstFace, lastFace, this.plane);
55
59
  solid = draftResult.solid;
56
60
  firstFace = draftResult.firstFace;
57
61
  lastFace = draftResult.lastFace;
@@ -1,9 +1,9 @@
1
- import { SceneObject } from "../common/scene-object.js";
1
+ import { BuildSceneObjectContext, SceneObject } from "../common/scene-object.js";
2
2
  export declare class Subtract extends SceneObject {
3
3
  solid1: SceneObject;
4
4
  solid2: SceneObject;
5
5
  constructor(solid1: SceneObject, solid2: SceneObject);
6
- build(): void;
6
+ build(context: BuildSceneObjectContext): void;
7
7
  compareTo(other: Subtract): boolean;
8
8
  getType(): string;
9
9
  serialize(): {
@@ -11,14 +11,14 @@ export class Subtract extends SceneObject {
11
11
  this.solid1 = solid1;
12
12
  this.solid2 = solid2;
13
13
  }
14
- build() {
14
+ build(context) {
15
+ const p = context.getProfiler();
15
16
  const stock = this.solid1.getShapes();
16
17
  const toBeRemoved = this.solid2.getShapes();
17
18
  const stockCompound = ShapeOps.makeCompound(stock);
18
19
  const toBeRemovedCompound = ShapeOps.makeCompound(toBeRemoved);
19
- const result = BooleanOps.cutShapes(stockCompound, toBeRemovedCompound);
20
+ const result = p.record('Cut solids', () => BooleanOps.cutShapes(stockCompound, toBeRemovedCompound));
20
21
  const shapes = Explorer.findShapes(result.getShape(), Explorer.getOcShapeType("solid"));
21
- console.log('Subtract Result:::::', shapes.length);
22
22
  const newShapes = shapes.map(s => ShapeOps.cleanShapeRaw(s)).map(s => Solid.fromTopoDSSolid(Explorer.toSolid(s)));
23
23
  for (const shape of toBeRemoved) {
24
24
  this.solid2.removeShape(shape, this);
@@ -7,6 +7,20 @@ export declare class Sweep extends ExtrudeBase implements ISweep {
7
7
  constructor(path: SceneObject, extrudable?: Extrudable);
8
8
  get path(): SceneObject;
9
9
  build(context: BuildSceneObjectContext): void;
10
+ /** Plain sweep: classify by inner-wire detection on the start face. */
11
+ private buildSweep;
12
+ /** Thin sweep: shell-like profile with inward/outward offsets. */
13
+ private buildSweepThin;
14
+ /**
15
+ * Run the sweep and split the resulting faces using OC's `FirstShape` /
16
+ * `LastShape` (the profile at each end of the spine). Anything else is a
17
+ * side face for the caller to refine.
18
+ */
19
+ private runSweep;
20
+ /** Inner-wire classification used by both regular sweep and closed thin profiles. */
21
+ private classifySweepByInnerWires;
22
+ /** Remove source + path, then dispatch to cut or fuse path. */
23
+ private dispatchFinalize;
10
24
  private getSpineWire;
11
25
  getDependencies(): SceneObject[];
12
26
  createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
@@ -3,7 +3,7 @@ import { SweepOps } from "../oc/sweep-ops.js";
3
3
  import { WireOps } from "../oc/wire-ops.js";
4
4
  import { FaceMaker2 } from "../oc/face-maker2.js";
5
5
  import { ExtrudeBase } from "./extrude-base.js";
6
- import { fuseWithSceneObjects, cutWithSceneObjects } from "../helpers/scene-helpers.js";
6
+ import { cutWithSceneObjects } from "../helpers/scene-helpers.js";
7
7
  import { ThinFaceMaker } from "../oc/thin-face-maker.js";
8
8
  export class Sweep extends ExtrudeBase {
9
9
  _path;
@@ -15,42 +15,72 @@ export class Sweep extends ExtrudeBase {
15
15
  return this._path;
16
16
  }
17
17
  build(context) {
18
+ const p = context.getProfiler();
18
19
  const plane = this.extrudable.getPlane();
19
- const pickedFaces = this.resolvePickedFaces(plane);
20
+ const pickedFaces = p.record('Resolve picked faces', () => this.resolvePickedFaces(plane));
20
21
  if (pickedFaces !== null && pickedFaces.length === 0) {
21
22
  return;
22
23
  }
23
- // Extract spine wire from path
24
- const spineWire = this.getSpineWire(this._path);
25
- // Extract profile faces from extrudable
26
- let profileFaces = pickedFaces ?? FaceMaker2.getRegions(this.extrudable.getGeometries(), plane, this.getDrill());
27
- let inwardEdges;
28
- let outwardEdges;
29
24
  if (this.isThin()) {
30
- const thinResult = ThinFaceMaker.make(this.extrudable.getGeometries(), plane, this._thin[0], this._thin[1]);
31
- profileFaces = thinResult.faces;
32
- inwardEdges = thinResult.inwardEdges;
33
- outwardEdges = thinResult.outwardEdges;
25
+ const thinResult = p.record('Make thin faces', () => ThinFaceMaker.make(this.extrudable.getGeometries(), plane, this._thin[0], this._thin[1]));
26
+ this.buildSweepThin(thinResult, plane, context);
34
27
  }
28
+ else {
29
+ const profileFaces = pickedFaces ?? p.record('Resolve faces', () => FaceMaker2.getRegions(this.extrudable.getGeometries(), plane, this.getDrill()));
30
+ this.buildSweep(profileFaces, plane, context);
31
+ }
32
+ this.setFinalShapes(this.getShapes());
33
+ }
34
+ /** Plain sweep: classify by inner-wire detection on the start face. */
35
+ buildSweep(profileFaces, plane, context) {
36
+ const swept = this.runSweep(profileFaces, context);
37
+ const classified = this.classifySweepByInnerWires(swept, plane);
38
+ this.dispatchFinalize(swept.solids, classified, plane, context);
39
+ }
40
+ /** Thin sweep: shell-like profile with inward/outward offsets. */
41
+ buildSweepThin(thinResult, plane, context) {
42
+ const swept = this.runSweep(thinResult.faces, context);
43
+ let classified;
44
+ if (thinResult.inwardEdges.length > 0) {
45
+ const reclass = this.reclassifyThinFaces(swept.sideFaces, swept.startFaces, plane, thinResult.inwardEdges, thinResult.outwardEdges);
46
+ classified = {
47
+ startFaces: swept.startFaces,
48
+ endFaces: swept.endFaces,
49
+ sideFaces: reclass.sideFaces,
50
+ internalFaces: reclass.internalFaces,
51
+ capFaces: reclass.capFaces,
52
+ };
53
+ }
54
+ else {
55
+ classified = this.classifySweepByInnerWires(swept, plane);
56
+ }
57
+ this.dispatchFinalize(swept.solids, classified, plane, context);
58
+ }
59
+ /**
60
+ * Run the sweep and split the resulting faces using OC's `FirstShape` /
61
+ * `LastShape` (the profile at each end of the spine). Anything else is a
62
+ * side face for the caller to refine.
63
+ */
64
+ runSweep(profileFaces, context) {
35
65
  if (profileFaces.length === 0) {
36
66
  throw new Error("Could not extract profile faces from extrudable.");
37
67
  }
38
- // Perform sweep
39
- const sweepResult = SweepOps.makeSweep(spineWire, profileFaces);
40
- const newShapes = sweepResult.solids;
41
- // Classify faces using FirstShape/LastShape from the OC result
68
+ const p = context.getProfiler();
69
+ const spineWire = p.record('Get spine wire', () => this.getSpineWire(this._path));
70
+ const sweepResult = p.record('Make sweep', () => SweepOps.makeSweep(spineWire, profileFaces));
71
+ const solids = sweepResult.solids;
42
72
  const startFaces = [];
43
73
  const endFaces = [];
44
- let sideFaces = [];
74
+ const sideFaces = [];
45
75
  const firstShapeFromOC = sweepResult.firstShape;
46
76
  const lastShapeFromOC = sweepResult.lastShape;
47
- for (const shape of newShapes) {
48
- const shapeFaces = Explorer.findFacesWrapped(shape);
49
- for (const f of shapeFaces) {
50
- if (firstShapeFromOC && f.getShape().IsSame(firstShapeFromOC)) {
77
+ for (const shape of solids) {
78
+ for (const f of Explorer.findFacesWrapped(shape)) {
79
+ const raw = f.getShape();
80
+ if (firstShapeFromOC && raw.IsSame(firstShapeFromOC)) {
51
81
  startFaces.push(f);
52
82
  }
53
- else if (lastShapeFromOC && f.getShape().IsSame(lastShapeFromOC)) {
83
+ else if (lastShapeFromOC && raw.IsSame(lastShapeFromOC)) {
54
84
  endFaces.push(f);
55
85
  }
56
86
  else {
@@ -58,76 +88,59 @@ export class Sweep extends ExtrudeBase {
58
88
  }
59
89
  }
60
90
  }
61
- let internalFaces = [];
62
- let capFaces = [];
63
- if (inwardEdges && inwardEdges.length > 0) {
64
- const result = this.reclassifyThinFaces(sideFaces, startFaces, plane, inwardEdges, outwardEdges || []);
65
- sideFaces = result.sideFaces;
66
- internalFaces = result.internalFaces;
67
- capFaces = result.capFaces;
68
- }
69
- else {
70
- const innerWireEdges = [];
71
- for (const sf of startFaces) {
72
- for (const wire of sf.getWires()) {
73
- if (!wire.isCW(plane.normal)) {
74
- for (const edge of wire.getEdges()) {
75
- innerWireEdges.push(edge);
76
- }
91
+ return { solids, startFaces, endFaces, sideFaces };
92
+ }
93
+ /** Inner-wire classification used by both regular sweep and closed thin profiles. */
94
+ classifySweepByInnerWires(swept, plane) {
95
+ const innerWireEdges = [];
96
+ for (const sf of swept.startFaces) {
97
+ for (const wire of sf.getWires()) {
98
+ if (!wire.isCW(plane.normal)) {
99
+ for (const edge of wire.getEdges()) {
100
+ innerWireEdges.push(edge);
77
101
  }
78
102
  }
79
103
  }
80
- if (innerWireEdges.length > 0) {
81
- const remaining = [];
82
- for (const f of sideFaces) {
83
- const isInternal = f.getEdges().some(fe => innerWireEdges.some(iwe => fe.getShape().IsPartner(iwe.getShape())));
84
- if (isInternal) {
85
- internalFaces.push(f);
86
- }
87
- else {
88
- remaining.push(f);
89
- }
104
+ }
105
+ const sideFaces = [];
106
+ const internalFaces = [];
107
+ if (innerWireEdges.length === 0) {
108
+ sideFaces.push(...swept.sideFaces);
109
+ }
110
+ else {
111
+ for (const f of swept.sideFaces) {
112
+ const isInternal = f.getEdges().some(fe => innerWireEdges.some(iwe => fe.getShape().IsPartner(iwe.getShape())));
113
+ if (isInternal) {
114
+ internalFaces.push(f);
115
+ }
116
+ else {
117
+ sideFaces.push(f);
90
118
  }
91
- sideFaces = remaining;
92
119
  }
93
120
  }
94
- this.setState('start-faces', startFaces);
95
- this.setState('end-faces', endFaces);
96
- this.setState('side-faces', sideFaces);
97
- this.setState('internal-faces', internalFaces);
98
- this.setState('cap-faces', capFaces);
99
- // Remove consumed input shapes
121
+ return {
122
+ startFaces: swept.startFaces,
123
+ endFaces: swept.endFaces,
124
+ sideFaces,
125
+ internalFaces,
126
+ capFaces: [],
127
+ };
128
+ }
129
+ /** Remove source + path, then dispatch to cut or fuse path. */
130
+ dispatchFinalize(solids, classified, plane, context) {
100
131
  this.extrudable.removeShapes(this);
101
132
  this._path.removeShapes(this);
102
- // Handle boolean operation based on operation mode
103
133
  if (this._operationMode === 'remove') {
104
134
  const scope = this.resolveFusionScope(context.getSceneObjects());
105
- cutWithSceneObjects(scope, newShapes, plane, 0, this, { recordHistoryFor: this });
106
- this.setFinalShapes(this.getShapes());
107
- return;
108
- }
109
- const sceneObjects = this.resolveFusionScope(context.getSceneObjects());
110
- if (sceneObjects.length === 0) {
111
- this.addShapes(newShapes);
112
- this.recordShapeFacesAndEdgesAsAdditions(newShapes);
113
- this.classifyExtrudeEdges();
114
- this.setFinalShapes(this.getShapes());
135
+ this.setState('start-faces', classified.startFaces);
136
+ this.setState('end-faces', classified.endFaces);
137
+ this.setState('side-faces', classified.sideFaces);
138
+ this.setState('internal-faces', classified.internalFaces);
139
+ this.setState('cap-faces', classified.capFaces);
140
+ cutWithSceneObjects(scope, solids, plane, 0, this, { recordHistoryFor: this });
115
141
  return;
116
142
  }
117
- const fusionResult = fuseWithSceneObjects(sceneObjects, newShapes, {
118
- recordHistoryFor: this,
119
- });
120
- for (const modifiedShape of fusionResult.modifiedShapes) {
121
- if (modifiedShape.object) {
122
- modifiedShape.object.removeShape(modifiedShape.shape, this);
123
- }
124
- }
125
- this.addShapes(fusionResult.newShapes);
126
- if (fusionResult.toolHistory) {
127
- this.remapClassifiedFaces(fusionResult.toolHistory);
128
- }
129
- this.classifyExtrudeEdges();
130
- this.setFinalShapes(this.getShapes());
143
+ this.finalizeAndFuse(solids, classified, context);
131
144
  }
132
145
  getSpineWire(pathObj) {
133
146
  const shapes = pathObj.getShapes({ excludeMeta: false });
@@ -4,8 +4,10 @@ export declare class Translate extends SceneObject {
4
4
  private amount;
5
5
  private copy;
6
6
  private _targetObjects;
7
+ private _excludedObjects;
7
8
  constructor(amount: LazyVertex, copy?: boolean, ...targets: SceneObject[]);
8
9
  get targetObjects(): SceneObject[];
10
+ exclude(...objects: SceneObject[]): this;
9
11
  build(context: BuildSceneObjectContext): void;
10
12
  getDependencies(): SceneObject[];
11
13
  createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
@@ -5,6 +5,7 @@ export class Translate extends SceneObject {
5
5
  amount;
6
6
  copy;
7
7
  _targetObjects = null;
8
+ _excludedObjects = [];
8
9
  constructor(amount, copy = false, ...targets) {
9
10
  super();
10
11
  this.amount = amount;
@@ -14,8 +15,15 @@ export class Translate extends SceneObject {
14
15
  get targetObjects() {
15
16
  return this._targetObjects;
16
17
  }
18
+ exclude(...objects) {
19
+ this._excludedObjects.push(...objects);
20
+ return this;
21
+ }
17
22
  build(context) {
18
- const objects = this.targetObjects || context.getSceneObjects();
23
+ let objects = this.targetObjects || context.getSceneObjects();
24
+ if (this._excludedObjects.length > 0) {
25
+ objects = objects.filter(obj => !this._excludedObjects.includes(obj));
26
+ }
19
27
  for (const obj of objects) {
20
28
  const shapes = obj.getShapes();
21
29
  for (const shape of shapes) {
@@ -40,7 +48,12 @@ export class Translate extends SceneObject {
40
48
  const targets = this.targetObjects
41
49
  ? this.targetObjects.map(obj => remap.get(obj) || obj)
42
50
  : [];
43
- return new Translate(this.amount, this.copy, ...targets);
51
+ const copy = new Translate(this.amount, this.copy, ...targets);
52
+ if (this._excludedObjects.length > 0) {
53
+ const remappedExcluded = this._excludedObjects.map(obj => remap.get(obj) || obj);
54
+ copy.exclude(...remappedExcluded);
55
+ }
56
+ return copy;
44
57
  }
45
58
  compareTo(other) {
46
59
  if (!(other instanceof Translate)) {
@@ -65,6 +78,14 @@ export class Translate extends SceneObject {
65
78
  return false;
66
79
  }
67
80
  }
81
+ if (this._excludedObjects.length !== other._excludedObjects.length) {
82
+ return false;
83
+ }
84
+ for (let i = 0; i < this._excludedObjects.length; i++) {
85
+ if (!this._excludedObjects[i].compareTo(other._excludedObjects[i])) {
86
+ return false;
87
+ }
88
+ }
68
89
  return true;
69
90
  }
70
91
  getType() {
@@ -123,5 +123,11 @@ export declare class EdgeFilterBuilder extends FilterBuilderBase<Edge> {
123
123
  offset?: number;
124
124
  partial?: boolean;
125
125
  }): this;
126
+ /**
127
+ * Restricts the selection to edges originating from the given scene objects.
128
+ * Recursive: passing a container picks up edges from its descendants.
129
+ * @param sceneObjects - Scene objects whose edges (and edges of their sub-shapes) are matched against.
130
+ */
131
+ from(...sceneObjects: ISceneObject[]): this;
126
132
  static build(): EdgeFilterBuilder;
127
133
  }
@@ -11,6 +11,7 @@ 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 { FromSceneObjectFilter } from "../from-object.js";
14
15
  import { IntersectsWithFilter, NotIntersectsWithFilter } from "./intersects-with.js";
15
16
  import { AbovePlaneFilter, BelowPlaneFilter } from "./above-below.js";
16
17
  import { SceneObject } from "../../common/scene-object.js";
@@ -330,6 +331,16 @@ export class EdgeFilterBuilder extends FilterBuilderBase {
330
331
  this.filters.push(filter);
331
332
  return this;
332
333
  }
334
+ /**
335
+ * Restricts the selection to edges originating from the given scene objects.
336
+ * Recursive: passing a container picks up edges from its descendants.
337
+ * @param sceneObjects - Scene objects whose edges (and edges of their sub-shapes) are matched against.
338
+ */
339
+ from(...sceneObjects) {
340
+ const filter = new FromSceneObjectFilter(sceneObjects, "edge");
341
+ this.filters.push(filter);
342
+ return this;
343
+ }
333
344
  static build() {
334
345
  return new EdgeFilterBuilder();
335
346
  }
@@ -119,4 +119,10 @@ export declare class FaceFilterBuilder extends FilterBuilderBase<Face> {
119
119
  * @param count - The number of edges to exclude.
120
120
  */
121
121
  notEdgeCount(count: number): this;
122
+ /**
123
+ * Restricts the selection to faces originating from the given scene objects.
124
+ * Recursive: passing a container picks up faces from its descendants.
125
+ * @param sceneObjects - Scene objects whose faces (and faces of their sub-shapes) are matched against.
126
+ */
127
+ from(...sceneObjects: ISceneObject[]): this;
122
128
  }
@@ -12,6 +12,7 @@ import { PlaneObjectBase } from "../../features/plane-renderable-base.js";
12
12
  import { AtIndexFilter, NotAtIndexFilter } from "./at-index.js";
13
13
  import { HasEdgeFilter, NotHasEdgeFilter } from "./has-edge.js";
14
14
  import { HasEdgeFromSceneObjectFilter, NotHasEdgeFromSceneObjectFilter } from "./has-object.js";
15
+ import { FromSceneObjectFilter } from "../from-object.js";
15
16
  import { EdgeCountFilter, NotEdgeCountFilter } from "./edge-count.js";
16
17
  import { IntersectsWithFilter, NotIntersectsWithFilter } from "./intersects-with.js";
17
18
  import { SceneObject } from "../../common/scene-object.js";
@@ -305,4 +306,14 @@ export class FaceFilterBuilder extends FilterBuilderBase {
305
306
  this.filters.push(filter);
306
307
  return this;
307
308
  }
309
+ /**
310
+ * Restricts the selection to faces originating from the given scene objects.
311
+ * Recursive: passing a container picks up faces from its descendants.
312
+ * @param sceneObjects - Scene objects whose faces (and faces of their sub-shapes) are matched against.
313
+ */
314
+ from(...sceneObjects) {
315
+ const filter = new FromSceneObjectFilter(sceneObjects, "face");
316
+ this.filters.push(filter);
317
+ return this;
318
+ }
308
319
  }
@@ -1,8 +1,14 @@
1
1
  import { Matrix4 } from "../math/matrix4.js";
2
- import { Comparable } from "../common/scene-object.js";
2
+ import { Comparable, SceneObject } from "../common/scene-object.js";
3
3
  import { Shape } from "../common/shapes.js";
4
4
  export declare abstract class FilterBase<TShape extends Shape> implements Comparable<FilterBase<TShape>> {
5
5
  abstract match(shape: TShape): boolean;
6
6
  abstract compareTo(other: FilterBase<TShape>): boolean;
7
7
  abstract transform(matrix: Matrix4): FilterBase<TShape>;
8
+ /**
9
+ * Returns a copy of this filter with any internal SceneObject references
10
+ * rewritten through the given remap. Filters that don't hold SceneObject
11
+ * references can keep the default no-op.
12
+ */
13
+ remap(_remap: Map<SceneObject, SceneObject>): FilterBase<TShape>;
8
14
  }
@@ -1,2 +1,10 @@
1
1
  export class FilterBase {
2
+ /**
3
+ * Returns a copy of this filter with any internal SceneObject references
4
+ * rewritten through the given remap. Filters that don't hold SceneObject
5
+ * references can keep the default no-op.
6
+ */
7
+ remap(_remap) {
8
+ return this;
9
+ }
2
10
  }
@@ -39,6 +39,17 @@ export class FilterBuilderBase {
39
39
  /**
40
40
  * @internal
41
41
  */
42
+ remap(remap) {
43
+ const remappedBuilder = new FilterBuilderBase();
44
+ for (const filter of this.filters) {
45
+ remappedBuilder.filter(filter.remap(remap));
46
+ }
47
+ remappedBuilder._withTangents = this._withTangents;
48
+ return remappedBuilder;
49
+ }
50
+ /**
51
+ * @internal
52
+ */
42
53
  equals(other) {
43
54
  if (this._withTangents !== other._withTangents) {
44
55
  return false;
@@ -0,0 +1,14 @@
1
+ import { Matrix4 } from "../math/matrix4.js";
2
+ import { Shape } from "../common/shapes.js";
3
+ import { ShapeType } from "../common/shape-type.js";
4
+ import { SceneObject } from "../common/scene-object.js";
5
+ import { FilterBase } from "./filter-base.js";
6
+ export declare class FromSceneObjectFilter<TShape extends Shape> extends FilterBase<TShape> {
7
+ private sceneObjects;
8
+ private shapeType;
9
+ constructor(sceneObjects: SceneObject[], shapeType: ShapeType);
10
+ match(shape: TShape): boolean;
11
+ compareTo(other: FromSceneObjectFilter<TShape>): boolean;
12
+ transform(_matrix: Matrix4): FromSceneObjectFilter<TShape>;
13
+ remap(remap: Map<SceneObject, SceneObject>): FromSceneObjectFilter<TShape>;
14
+ }
@@ -0,0 +1,40 @@
1
+ import { FilterBase } from "./filter-base.js";
2
+ export class FromSceneObjectFilter extends FilterBase {
3
+ sceneObjects;
4
+ shapeType;
5
+ constructor(sceneObjects, shapeType) {
6
+ super();
7
+ this.sceneObjects = sceneObjects;
8
+ this.shapeType = shapeType;
9
+ }
10
+ match(shape) {
11
+ for (const obj of this.sceneObjects) {
12
+ const subShapes = obj.getShapes().flatMap(s => s.getSubShapes(this.shapeType));
13
+ if (subShapes.some(sub => sub.isSame(shape))) {
14
+ return true;
15
+ }
16
+ }
17
+ return false;
18
+ }
19
+ compareTo(other) {
20
+ if (this.shapeType !== other.shapeType) {
21
+ return false;
22
+ }
23
+ if (this.sceneObjects.length !== other.sceneObjects.length) {
24
+ return false;
25
+ }
26
+ for (let i = 0; i < this.sceneObjects.length; i++) {
27
+ if (!this.sceneObjects[i].compareTo(other.sceneObjects[i])) {
28
+ return false;
29
+ }
30
+ }
31
+ return true;
32
+ }
33
+ transform(_matrix) {
34
+ return new FromSceneObjectFilter(this.sceneObjects, this.shapeType);
35
+ }
36
+ remap(remap) {
37
+ const remapped = this.sceneObjects.map(obj => remap.get(obj) ?? obj);
38
+ return new FromSceneObjectFilter(remapped, this.shapeType);
39
+ }
40
+ }
@@ -2,9 +2,11 @@ import { SceneObject } from "../common/scene-object.js";
2
2
  import { Shape } from "../common/shapes.js";
3
3
  import { Plane } from "../math/plane.js";
4
4
  import { ShapeHistory } from "../common/shape-history-tracker.js";
5
+ import { Profiler } from "../common/profiler.js";
5
6
  export declare function fuseWithSceneObjects(sceneObjects: SceneObject[], extrusions: Shape<any>[], opts?: {
6
7
  glue?: 'full' | 'shift';
7
8
  recordHistoryFor?: SceneObject;
9
+ profiler?: Profiler;
8
10
  }): {
9
11
  newShapes: Shape<any>[];
10
12
  modifiedShapes: any[];