fluidcad 0.0.26 → 0.0.27

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 (58) hide show
  1. package/lib/dist/common/shape-factory.d.ts +1 -1
  2. package/lib/dist/common/shapes.d.ts +0 -1
  3. package/lib/dist/common/shapes.js +0 -1
  4. package/lib/dist/core/extrude.d.ts +12 -13
  5. package/lib/dist/core/extrude.js +19 -1
  6. package/lib/dist/core/part.d.ts +2 -1
  7. package/lib/dist/core/part.js +4 -1
  8. package/lib/dist/core/sketch.d.ts +4 -3
  9. package/lib/dist/core/sketch.js +4 -1
  10. package/lib/dist/features/extrude-base.d.ts +7 -1
  11. package/lib/dist/features/extrude-base.js +36 -3
  12. package/lib/dist/features/extrude-to-face.d.ts +1 -1
  13. package/lib/dist/features/extrude-to-face.js +29 -17
  14. package/lib/dist/features/extrude-two-distances.d.ts +1 -1
  15. package/lib/dist/features/extrude-two-distances.js +23 -12
  16. package/lib/dist/features/extrude.d.ts +1 -1
  17. package/lib/dist/features/extrude.js +47 -15
  18. package/lib/dist/features/mirror-shape.d.ts +1 -3
  19. package/lib/dist/features/mirror-shape.js +2 -1
  20. package/lib/dist/features/revolve.js +4 -2
  21. package/lib/dist/features/rotate.js +1 -0
  22. package/lib/dist/features/simple-extruder.js +5 -0
  23. package/lib/dist/features/translate.js +3 -1
  24. package/lib/dist/filters/face/face-filter.d.ts +12 -0
  25. package/lib/dist/filters/face/face-filter.js +21 -0
  26. package/lib/dist/filters/face/torus-filter.d.ts +19 -0
  27. package/lib/dist/filters/face/torus-filter.js +38 -0
  28. package/lib/dist/helpers/scene-helpers.d.ts +3 -1
  29. package/lib/dist/helpers/scene-helpers.js +6 -3
  30. package/lib/dist/index.d.ts +1 -0
  31. package/lib/dist/oc/boolean-ops.d.ts +5 -3
  32. package/lib/dist/oc/boolean-ops.js +15 -2
  33. package/lib/dist/oc/face-ops.d.ts +0 -1
  34. package/lib/dist/oc/face-ops.js +0 -13
  35. package/lib/dist/oc/face-query.d.ts +2 -0
  36. package/lib/dist/oc/face-query.js +30 -0
  37. package/lib/dist/oc/fillet-ops.js +84 -66
  38. package/lib/dist/oc/mesh.d.ts +25 -2
  39. package/lib/dist/oc/mesh.js +112 -35
  40. package/lib/dist/oc/shape-ops.d.ts +1 -21
  41. package/lib/dist/oc/shape-ops.js +0 -103
  42. package/lib/dist/rendering/mesh-transform.js +17 -1
  43. package/lib/dist/rendering/render-solid.js +19 -6
  44. package/lib/dist/rendering/render-wire.js +2 -0
  45. package/lib/dist/rendering/render.d.ts +12 -2
  46. package/lib/dist/rendering/render.js +195 -220
  47. package/lib/dist/scene-manager.d.ts +2 -0
  48. package/lib/dist/scene-manager.js +4 -3
  49. package/lib/dist/tests/features/extrude.test.js +71 -0
  50. package/lib/dist/tests/features/fillet2d.test.js +16 -1
  51. package/lib/dist/tests/features/select.test.js +50 -0
  52. package/lib/dist/tests/setup.js +3 -2
  53. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  54. package/package.json +3 -3
  55. package/ui/dist/assets/{index-BeLxRMCv.js → index-55iqIwnj.js} +37 -37
  56. package/ui/dist/index.html +1 -1
  57. package/lib/dist/common/solid-face.d.ts +0 -9
  58. package/lib/dist/common/solid-face.js +0 -22
@@ -4,5 +4,5 @@ import { Wire } from "./wire.js";
4
4
  import { Face } from "./face.js";
5
5
  import { Edge } from "./edge.js";
6
6
  export declare class ShapeFactory {
7
- static fromShape(shape: TopoDS_Shape): Wire | Edge | Solid | Face;
7
+ static fromShape(shape: TopoDS_Shape): Wire | Edge | Face | Solid;
8
8
  }
@@ -3,4 +3,3 @@ export { Edge } from "./edge.js";
3
3
  export { Vertex } from "./vertex.js";
4
4
  export { Face } from "./face.js";
5
5
  export { Solid } from "./solid.js";
6
- export { SolidFace } from "./solid-face.js";
@@ -3,4 +3,3 @@ export { Edge } from "./edge.js";
3
3
  export { Vertex } from "./vertex.js";
4
4
  export { Face } from "./face.js";
5
5
  export { Solid } from "./solid.js";
6
- export { SolidFace } from "./solid-face.js";
@@ -2,45 +2,44 @@ import { IExtrude, ISceneObject } from "./interfaces.js";
2
2
  interface ExtrudeFunction {
3
3
  /**
4
4
  * Extrudes the last sketch with a default distance.
5
- * @param target - The sketch to extrude
5
+ * @param target - The sketch or face-bearing scene object to extrude
6
6
  */
7
7
  (target?: ISceneObject): IExtrude;
8
8
  /**
9
- * Extrudes the last sketch by a given distance.
9
+ * Extrudes by a given distance.
10
10
  * @param distance - The extrusion distance
11
- * @param target - The sketch to extrude
11
+ * @param target - The sketch or face-bearing scene object to extrude
12
12
  */
13
13
  (distance: number, target?: ISceneObject): IExtrude;
14
14
  /**
15
- * Extrudes the last sketch between two distances.
15
+ * Extrudes between two distances.
16
16
  * @param distance1 - The first extrusion distance
17
17
  * @param distance2 - The second extrusion distance
18
- * @param target - The sketch to extrude
19
18
  */
20
19
  (distance1: number, distance2: number): IExtrude;
21
20
  /**
22
- * Extrudes the given sketch between two distances.
21
+ * Extrudes between two distances.
23
22
  * @param distance1 - The first extrusion distance
24
23
  * @param distance2 - The second extrusion distance
25
- * @param target - The sketch to extrude
24
+ * @param target - The sketch or face-bearing scene object to extrude
26
25
  */
27
26
  (distance1: number, distance2: number, target: ISceneObject): IExtrude;
28
27
  /**
29
- * Extrudes the last sketch up to a specific face.
28
+ * Extrudes up to a specific face.
30
29
  * @param face - A face selection to extrude up to
31
- * @param target - The sketch to extrude
30
+ * @param target - The sketch or face-bearing scene object to extrude
32
31
  */
33
32
  (face: ISceneObject, target?: ISceneObject): IExtrude;
34
33
  /**
35
- * Extrudes the last sketch up to the first intersecting face.
34
+ * Extrudes up to the first intersecting face.
36
35
  * @param face - The literal `'first-face'`
37
- * @param target - The sketch to extrude
36
+ * @param target - The sketch or face-bearing scene object to extrude
38
37
  */
39
38
  (face: 'first-face', target?: ISceneObject): IExtrude;
40
39
  /**
41
- * Extrudes the last sketch up to the last intersecting face.
40
+ * Extrudes up to the last intersecting face.
42
41
  * @param face - The literal `'last-face'`
43
- * @param target - The sketch to extrude
42
+ * @param target - The sketch or face-bearing scene object to extrude
44
43
  */
45
44
  (face: 'last-face', target?: ISceneObject): IExtrude;
46
45
  }
@@ -3,9 +3,22 @@ import { registerBuilder } from "../index.js";
3
3
  import { Extrude } from "../features/extrude.js";
4
4
  import { ExtrudeTwoDistances } from "../features/extrude-two-distances.js";
5
5
  import { ExtrudeToFace } from "../features/extrude-to-face.js";
6
+ import { SelectSceneObject } from "../features/select.js";
6
7
  function isExtrudable(obj) {
7
8
  return obj instanceof SceneObject && obj.isExtrudable();
8
9
  }
10
+ function isFaceSource(obj) {
11
+ if (!(obj instanceof SceneObject)) {
12
+ return false;
13
+ }
14
+ if (isExtrudable(obj)) {
15
+ return false;
16
+ }
17
+ if (obj instanceof SelectSceneObject) {
18
+ return obj.shapeType() === 'face';
19
+ }
20
+ return true;
21
+ }
9
22
  function build(context) {
10
23
  function doExtrude(params, extrudable) {
11
24
  const defaultDistance = 25;
@@ -41,8 +54,13 @@ function build(context) {
41
54
  return function extrude() {
42
55
  const args = [...arguments];
43
56
  let extrudable;
44
- if (args.length > 0 && isExtrudable(args[args.length - 1])) {
57
+ const last = args.length > 0 ? args[args.length - 1] : undefined;
58
+ if (last !== undefined && isExtrudable(last)) {
59
+ extrudable = args.pop();
60
+ }
61
+ else if (last !== undefined && args.length >= 2 && isFaceSource(last)) {
45
62
  extrudable = args.pop();
63
+ context.addSceneObject(extrudable);
46
64
  }
47
65
  else {
48
66
  extrudable = context.getLastExtrudable() || undefined;
@@ -1,3 +1,4 @@
1
1
  import { ISceneObject } from "./interfaces.js";
2
- declare function part(name: string, callback: () => void): ISceneObject;
2
+ type Extend<T> = T extends object ? T : {};
3
+ declare function part<T>(name: string, callback: () => T): ISceneObject & Extend<T>;
3
4
  export default part;
@@ -12,8 +12,11 @@ function part(name, callback) {
12
12
  partObj.setSourceLocation(sourceLocation);
13
13
  }
14
14
  scene.startProgressiveContainer(partObj);
15
- callback();
15
+ const extensions = callback();
16
16
  scene.endProgressiveContainer();
17
+ if (extensions && typeof extensions === 'object') {
18
+ Object.assign(partObj, extensions);
19
+ }
17
20
  return partObj;
18
21
  }
19
22
  export default part;
@@ -1,24 +1,25 @@
1
1
  import { PlaneLike } from "../math/plane.js";
2
2
  import { IPlane, ISceneObject } from "./interfaces.js";
3
+ type Extend<T> = T extends object ? T : {};
3
4
  interface SketchFunction {
4
5
  /**
5
6
  * Draws 2D geometry on a standard plane.
6
7
  * @param plane - The plane to sketch on
7
8
  * @param sketcher - Callback containing sketch operations
8
9
  */
9
- (plane: PlaneLike, sketcher: () => void): ISceneObject;
10
+ <T>(plane: PlaneLike, sketcher: () => T): ISceneObject & Extend<T>;
10
11
  /**
11
12
  * Draws 2D geometry on a face selection.
12
13
  * @param face - The face to sketch on
13
14
  * @param sketcher - Callback containing sketch operations
14
15
  */
15
- (face: ISceneObject, sketcher: () => void): ISceneObject;
16
+ <T>(face: ISceneObject, sketcher: () => T): ISceneObject & Extend<T>;
16
17
  /**
17
18
  * Draws 2D geometry on an existing Plane object.
18
19
  * @param plane - The Plane object to sketch on
19
20
  * @param sketcher - Callback containing sketch operations
20
21
  */
21
- (plane: IPlane, sketcher: () => void): ISceneObject;
22
+ <T>(plane: IPlane, sketcher: () => T): ISceneObject & Extend<T>;
22
23
  }
23
24
  declare const _default: SketchFunction;
24
25
  export default _default;
@@ -26,8 +26,11 @@ function build(context) {
26
26
  }
27
27
  const sketch = new Sketch(planeObj);
28
28
  context.startProgressiveContainer(sketch);
29
- sketcher();
29
+ const extensions = sketcher();
30
30
  context.endProgressiveContainer();
31
+ if (extensions && typeof extensions === 'object') {
32
+ Object.assign(sketch, extensions);
33
+ }
31
34
  return sketch;
32
35
  };
33
36
  }
@@ -10,14 +10,20 @@ import { FaceFilterBuilder } from "../filters/face/face-filter.js";
10
10
  import { EdgeFilterBuilder } from "../filters/edge/edge-filter.js";
11
11
  export declare abstract class ExtrudeBase extends SceneObject implements IExtrude {
12
12
  protected _extrudable: Extrudable | null;
13
+ protected _faceSource: SceneObject | null;
13
14
  protected _draft?: number | [number, number];
14
15
  protected _endOffset?: number;
15
16
  protected _drill?: boolean;
16
17
  protected _picking: boolean;
17
18
  protected _pickPoints: LazyVertex[];
18
19
  protected _thin?: [number] | [number, number];
19
- constructor(extrudable?: Extrudable);
20
+ constructor(source?: Extrudable | SceneObject);
20
21
  get extrudable(): Extrudable;
22
+ get faceSource(): SceneObject | null;
23
+ isFaceSourced(): boolean;
24
+ getSource(): SceneObject | null;
25
+ getSourcePlane(): Plane | null;
26
+ getSourceFaces(): Face[];
21
27
  startFaces(...args: number[] | FaceFilterBuilder[]): SceneObject;
22
28
  endFaces(...args: number[] | FaceFilterBuilder[]): SceneObject;
23
29
  startEdges(...args: number[] | EdgeFilterBuilder[]): SceneObject;
@@ -1,3 +1,4 @@
1
+ import { Face } from "../common/face.js";
1
2
  import { SceneObject } from "../common/scene-object.js";
2
3
  import { LazySelectionSceneObject } from "./lazy-scene-object.js";
3
4
  import { normalizePoint2D } from "../helpers/normalize.js";
@@ -9,19 +10,51 @@ import { ShapeFilter } from "../filters/filter.js";
9
10
  import { EdgeOps } from "../oc/edge-ops.js";
10
11
  export class ExtrudeBase extends SceneObject {
11
12
  _extrudable = null;
13
+ _faceSource = null;
12
14
  _draft;
13
15
  _endOffset;
14
16
  _drill = true;
15
17
  _picking = false;
16
18
  _pickPoints = [];
17
19
  _thin;
18
- constructor(extrudable) {
20
+ constructor(source) {
19
21
  super();
20
- this._extrudable = extrudable ?? null;
22
+ if (source) {
23
+ if (source.isExtrudable()) {
24
+ this._extrudable = source;
25
+ }
26
+ else {
27
+ this._faceSource = source;
28
+ }
29
+ }
21
30
  }
22
31
  get extrudable() {
23
32
  return this._extrudable;
24
33
  }
34
+ get faceSource() {
35
+ return this._faceSource;
36
+ }
37
+ isFaceSourced() {
38
+ return this._faceSource !== null;
39
+ }
40
+ getSource() {
41
+ return this._extrudable ?? this._faceSource;
42
+ }
43
+ getSourcePlane() {
44
+ if (this._extrudable) {
45
+ return this._extrudable.getPlane();
46
+ }
47
+ const faces = this.getSourceFaces();
48
+ return faces.length > 0 ? faces[0].getPlane() : null;
49
+ }
50
+ getSourceFaces() {
51
+ if (!this._faceSource) {
52
+ return [];
53
+ }
54
+ return this._faceSource.getShapes()
55
+ .flatMap(s => s.getSubShapes('face'))
56
+ .filter((f) => f instanceof Face);
57
+ }
25
58
  startFaces(...args) {
26
59
  const suffix = this.buildSuffix('start-faces', args);
27
60
  return new LazySelectionSceneObject(`${this.generateUniqueName(suffix)}`, (parent) => {
@@ -230,7 +263,7 @@ export class ExtrudeBase extends SceneObject {
230
263
  return this._thin;
231
264
  }
232
265
  serializePickFields() {
233
- const plane = this._extrudable?.getPlane();
266
+ const plane = this.getSourcePlane();
234
267
  return {
235
268
  picking: this.isPicking() || undefined,
236
269
  pickPoints: this.isPicking()
@@ -4,7 +4,7 @@ import { Extrudable } from "../helpers/types.js";
4
4
  import { Point } from "../math/point.js";
5
5
  export declare class ExtrudeToFace extends ExtrudeBase {
6
6
  face: SceneObject | 'first-face' | 'last-face';
7
- constructor(face: SceneObject | 'first-face' | 'last-face', extrudable?: Extrudable);
7
+ constructor(face: SceneObject | 'first-face' | 'last-face', source?: Extrudable | SceneObject);
8
8
  build(context: BuildSceneObjectContext): void;
9
9
  private createAdvancedExtrude;
10
10
  private resizePlanarFace;
@@ -13,14 +13,14 @@ import { ThinFaceMaker } from "../oc/thin-face-maker.js";
13
13
  import { Point } from "../math/point.js";
14
14
  export class ExtrudeToFace extends ExtrudeBase {
15
15
  face;
16
- constructor(face, extrudable) {
17
- super(extrudable);
16
+ constructor(face, source) {
17
+ super(source);
18
18
  this.face = face;
19
19
  }
20
20
  build(context) {
21
21
  const allSceneObjects = context.getSceneObjects();
22
22
  const sceneObjects = this.resolveFusionScope(allSceneObjects);
23
- const plane = this.extrudable.getPlane();
23
+ const plane = this.getSourcePlane();
24
24
  const pickedFaces = this.resolvePickedFaces(plane);
25
25
  if (pickedFaces !== null && pickedFaces.length === 0) {
26
26
  return;
@@ -35,7 +35,13 @@ export class ExtrudeToFace extends ExtrudeBase {
35
35
  let faces;
36
36
  let inwardEdges;
37
37
  let outwardEdges;
38
- if (this.isThin()) {
38
+ if (this.isFaceSourced()) {
39
+ if (this.isThin()) {
40
+ throw new Error("thin() is not supported with a face-sourced extrude");
41
+ }
42
+ faces = pickedFaces ?? this.getSourceFaces();
43
+ }
44
+ else if (this.isThin()) {
39
45
  const thinResult = ThinFaceMaker.make(this.extrudable.getGeometries(), plane, this._thin[0], this._thin[1]);
40
46
  faces = thinResult.faces;
41
47
  inwardEdges = thinResult.inwardEdges;
@@ -79,7 +85,7 @@ export class ExtrudeToFace extends ExtrudeBase {
79
85
  this.setState('side-faces', allSideFaces);
80
86
  this.setState('internal-faces', allInternalFaces);
81
87
  this.setState('cap-faces', allCapFaces);
82
- this.extrudable.removeShapes(this);
88
+ this.getSource()?.removeShapes(this);
83
89
  if (this.face instanceof SceneObject) {
84
90
  this.face.removeShapes(this);
85
91
  }
@@ -128,7 +134,7 @@ export class ExtrudeToFace extends ExtrudeBase {
128
134
  resizePlanarFace(targetFace) {
129
135
  const endOffset = this.getEndOffset();
130
136
  if (endOffset) {
131
- const dir = this.extrudable.getPlane().normal.reverse();
137
+ const dir = this.getSourcePlane().normal.reverse();
132
138
  return FaceQuery.makeInfinitePlanarFace(targetFace, endOffset, dir);
133
139
  }
134
140
  return FaceQuery.makeInfinitePlanarFace(targetFace);
@@ -165,7 +171,7 @@ export class ExtrudeToFace extends ExtrudeBase {
165
171
  }
166
172
  splitShapesByFace(extrusions, targetFace) {
167
173
  const result = [];
168
- const sourcePlane = this.extrudable.getPlane();
174
+ const sourcePlane = this.getSourcePlane();
169
175
  for (const shape of extrusions) {
170
176
  const solids = BooleanOps.splitShape(shape, targetFace);
171
177
  if (solids.length === 1) {
@@ -221,10 +227,11 @@ export class ExtrudeToFace extends ExtrudeBase {
221
227
  }
222
228
  }
223
229
  getFirstOrLastFace(sceneObjects, mode) {
224
- const plane = this.extrudable.getPlane();
230
+ const plane = this.getSourcePlane();
231
+ const source = this.getSource();
225
232
  const allFaces = [];
226
233
  for (const obj of sceneObjects) {
227
- if (obj === this.extrudable) {
234
+ if (obj === source) {
228
235
  continue;
229
236
  }
230
237
  for (const shape of obj.getShapes()) {
@@ -242,8 +249,9 @@ export class ExtrudeToFace extends ExtrudeBase {
242
249
  }
243
250
  getDependencies() {
244
251
  const deps = [];
245
- if (this.extrudable) {
246
- deps.push(this.extrudable);
252
+ const source = this.getSource();
253
+ if (source) {
254
+ deps.push(source);
247
255
  }
248
256
  if (this.face instanceof SceneObject) {
249
257
  deps.push(this.face);
@@ -254,10 +262,9 @@ export class ExtrudeToFace extends ExtrudeBase {
254
262
  const newFace = this.face instanceof SceneObject
255
263
  ? (remap.get(this.face) || this.face)
256
264
  : this.face;
257
- const extrudable = this.extrudable
258
- ? (remap.get(this.extrudable) || this.extrudable)
259
- : undefined;
260
- return new ExtrudeToFace(newFace, extrudable).syncWith(this);
265
+ const source = this.getSource();
266
+ const remapped = source ? (remap.get(source) || source) : undefined;
267
+ return new ExtrudeToFace(newFace, remapped).syncWith(this);
261
268
  }
262
269
  compareTo(other) {
263
270
  if (!(other instanceof ExtrudeToFace)) {
@@ -266,7 +273,12 @@ export class ExtrudeToFace extends ExtrudeBase {
266
273
  if (!super.compareTo(other)) {
267
274
  return false;
268
275
  }
269
- if (!this.extrudable.compareTo(other.extrudable)) {
276
+ const thisSource = this.getSource();
277
+ const otherSource = other.getSource();
278
+ if (!thisSource !== !otherSource) {
279
+ return false;
280
+ }
281
+ if (thisSource && otherSource && !thisSource.compareTo(otherSource)) {
270
282
  return false;
271
283
  }
272
284
  if (typeof (this.face) !== typeof (other.face)) {
@@ -289,7 +301,7 @@ export class ExtrudeToFace extends ExtrudeBase {
289
301
  serialize() {
290
302
  return {
291
303
  sheptType: 'wire',
292
- extrudable: this.extrudable.serialize(),
304
+ extrudable: this.getSource()?.serialize(),
293
305
  draft: this.getDraft(),
294
306
  endOffset: this.getEndOffset(),
295
307
  face: typeof (this.face) === 'string' ? this.face : 'selection',
@@ -4,7 +4,7 @@ import { Extrudable } from "../helpers/types.js";
4
4
  export declare class ExtrudeTwoDistances extends ExtrudeBase {
5
5
  distance1: number;
6
6
  distance2: number;
7
- constructor(distance1: number, distance2: number, extrudable?: Extrudable);
7
+ constructor(distance1: number, distance2: number, source?: Extrudable | SceneObject);
8
8
  build(context: BuildSceneObjectContext): void;
9
9
  getDependencies(): SceneObject[];
10
10
  createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
@@ -9,14 +9,14 @@ import { ThinFaceMaker } from "../oc/thin-face-maker.js";
9
9
  export class ExtrudeTwoDistances extends ExtrudeBase {
10
10
  distance1;
11
11
  distance2;
12
- constructor(distance1, distance2, extrudable) {
13
- super(extrudable);
12
+ constructor(distance1, distance2, source) {
13
+ super(source);
14
14
  this.distance1 = distance1;
15
15
  this.distance2 = distance2;
16
16
  }
17
17
  build(context) {
18
18
  const sceneObjects = this.resolveFusionScope(context.getSceneObjects());
19
- const plane = this.extrudable.getPlane();
19
+ const plane = this.getSourcePlane();
20
20
  const pickedFaces = this.resolvePickedFaces(plane);
21
21
  if (pickedFaces !== null && pickedFaces.length === 0) {
22
22
  return;
@@ -24,7 +24,13 @@ export class ExtrudeTwoDistances extends ExtrudeBase {
24
24
  let faces;
25
25
  let inwardEdges;
26
26
  let outwardEdges;
27
- if (this.isThin()) {
27
+ if (this.isFaceSourced()) {
28
+ if (this.isThin()) {
29
+ throw new Error("thin() is not supported with a face-sourced extrude");
30
+ }
31
+ faces = pickedFaces ?? this.getSourceFaces();
32
+ }
33
+ else if (this.isThin()) {
28
34
  const thinResult = ThinFaceMaker.make(this.extrudable.getGeometries(), plane, this._thin[0], this._thin[1]);
29
35
  faces = thinResult.faces;
30
36
  inwardEdges = thinResult.inwardEdges;
@@ -112,7 +118,7 @@ export class ExtrudeTwoDistances extends ExtrudeBase {
112
118
  this.setState('side-faces', sideFaces);
113
119
  this.setState('internal-faces', internalFaces);
114
120
  this.setState('cap-faces', capFaces);
115
- this.extrudable.removeShapes(this);
121
+ this.getSource()?.removeShapes(this);
116
122
  if (this._operationMode === 'remove') {
117
123
  const scope = this.resolveFusionScope(context.getSceneObjects());
118
124
  cutWithSceneObjects(scope, extrusions, plane, this.distance1 + this.distance2, this);
@@ -132,13 +138,13 @@ export class ExtrudeTwoDistances extends ExtrudeBase {
132
138
  this.addShapes(fusionResult.newShapes);
133
139
  }
134
140
  getDependencies() {
135
- return this.extrudable ? [this.extrudable] : [];
141
+ const source = this.getSource();
142
+ return source ? [source] : [];
136
143
  }
137
144
  createCopy(remap) {
138
- const extrudable = this.extrudable
139
- ? (remap.get(this.extrudable) || this.extrudable)
140
- : undefined;
141
- return new ExtrudeTwoDistances(this.distance1, this.distance2, extrudable).syncWith(this);
145
+ const source = this.getSource();
146
+ const remapped = source ? (remap.get(source) || source) : undefined;
147
+ return new ExtrudeTwoDistances(this.distance1, this.distance2, remapped).syncWith(this);
142
148
  }
143
149
  compareTo(other) {
144
150
  if (!(other instanceof ExtrudeTwoDistances)) {
@@ -150,7 +156,12 @@ export class ExtrudeTwoDistances extends ExtrudeBase {
150
156
  if (this.distance1 !== other.distance1 || this.distance2 !== other.distance2) {
151
157
  return false;
152
158
  }
153
- if (!this.extrudable.compareTo(other.extrudable)) {
159
+ const thisSource = this.getSource();
160
+ const otherSource = other.getSource();
161
+ if (!thisSource !== !otherSource) {
162
+ return false;
163
+ }
164
+ if (thisSource && otherSource && !thisSource.compareTo(otherSource)) {
154
165
  return false;
155
166
  }
156
167
  return true;
@@ -163,7 +174,7 @@ export class ExtrudeTwoDistances extends ExtrudeBase {
163
174
  }
164
175
  serialize() {
165
176
  return {
166
- extrudable: this.extrudable.serialize(),
177
+ extrudable: this.getSource()?.serialize(),
167
178
  distance1: this.distance1,
168
179
  distance2: this.distance2,
169
180
  operationMode: this._operationMode !== 'add' ? this._operationMode : undefined,
@@ -3,7 +3,7 @@ import { Extrudable } from "../helpers/types.js";
3
3
  import { ExtrudeBase } from "./extrude-base.js";
4
4
  export declare class Extrude extends ExtrudeBase {
5
5
  distance: number;
6
- constructor(distance: number, extrudable?: Extrudable);
6
+ constructor(distance: number, source?: Extrudable | SceneObject);
7
7
  build(context: BuildSceneObjectContext): void;
8
8
  private buildAdd;
9
9
  private buildSymmetric;
@@ -9,20 +9,32 @@ import { ExtrudeThroughAll } from "./infinite-extrude.js";
9
9
  import { ThinFaceMaker } from "../oc/thin-face-maker.js";
10
10
  export class Extrude extends ExtrudeBase {
11
11
  distance;
12
- constructor(distance, extrudable) {
13
- super(extrudable);
12
+ constructor(distance, source) {
13
+ super(source);
14
14
  this.distance = distance;
15
15
  }
16
16
  build(context) {
17
- const plane = this.extrudable.getPlane();
17
+ const tBuild = performance.now();
18
+ let t = performance.now();
19
+ const plane = this.getSourcePlane();
20
+ console.log(`[perf] Extrude.getSourcePlane: ${(performance.now() - t).toFixed(1)} ms`);
21
+ t = performance.now();
18
22
  const pickedFaces = this.resolvePickedFaces(plane);
23
+ console.log(`[perf] Extrude.resolvePickedFaces: ${(performance.now() - t).toFixed(1)} ms`);
19
24
  if (pickedFaces !== null && pickedFaces.length === 0) {
20
25
  return;
21
26
  }
22
27
  let faces;
23
28
  let inwardEdges;
24
29
  let outwardEdges;
25
- if (this.isThin()) {
30
+ t = performance.now();
31
+ if (this.isFaceSourced()) {
32
+ if (this.isThin()) {
33
+ throw new Error("thin() is not supported with a face-sourced extrude");
34
+ }
35
+ faces = pickedFaces ?? this.getSourceFaces();
36
+ }
37
+ else if (this.isThin()) {
26
38
  const thinResult = ThinFaceMaker.make(this.extrudable.getGeometries(), plane, this._thin[0], this._thin[1]);
27
39
  faces = thinResult.faces;
28
40
  inwardEdges = thinResult.inwardEdges;
@@ -31,6 +43,7 @@ export class Extrude extends ExtrudeBase {
31
43
  else {
32
44
  faces = pickedFaces ?? FaceMaker2.getRegions(this.extrudable.getGeometries(), plane, this.getDrill());
33
45
  }
46
+ console.log(`[perf] Extrude.resolveFaces (faces=${faces.length}, faceSourced=${this.isFaceSourced()}): ${(performance.now() - t).toFixed(1)} ms`);
34
47
  if (this._operationMode === 'remove') {
35
48
  this.buildRemove(faces, plane, context);
36
49
  }
@@ -40,11 +53,16 @@ export class Extrude extends ExtrudeBase {
40
53
  else {
41
54
  this.buildAdd(faces, plane, context, inwardEdges, outwardEdges);
42
55
  }
56
+ console.log(`[perf] Extrude.build TOTAL: ${(performance.now() - tBuild).toFixed(1)} ms`);
43
57
  }
44
58
  buildAdd(faces, plane, context, inwardEdges, outwardEdges) {
59
+ let t = performance.now();
45
60
  const sceneObjects = this.resolveFusionScope(context.getSceneObjects());
61
+ console.log(`[perf] Extrude.buildAdd.resolveFusionScope (n=${sceneObjects.length}): ${(performance.now() - t).toFixed(1)} ms`);
62
+ t = performance.now();
46
63
  const extruder = new Extruder(faces, plane, this.distance, this.getDraft(), this.getEndOffset());
47
64
  let extrusions = extruder.extrude();
65
+ console.log(`[perf] Extrude.buildAdd.extruder.extrude (extrusions=${extrusions.length}): ${(performance.now() - t).toFixed(1)} ms`);
48
66
  let sideFaces = extruder.getSideFaces();
49
67
  let internalFaces = extruder.getInternalFaces();
50
68
  let capFaces = [];
@@ -59,12 +77,15 @@ export class Extrude extends ExtrudeBase {
59
77
  this.setState('side-faces', sideFaces);
60
78
  this.setState('internal-faces', internalFaces);
61
79
  this.setState('cap-faces', capFaces);
62
- this.extrudable.removeShapes(this);
80
+ this.getSource()?.removeShapes(this);
81
+ console.log("Extrusions before fusion:", extrusions.length);
63
82
  if (extrusions.length === 0 || sceneObjects.length === 0) {
64
83
  this.addShapes(extrusions);
65
84
  return;
66
85
  }
67
- const fusionResult = fuseWithSceneObjects(sceneObjects, extrusions);
86
+ const tFuse = performance.now();
87
+ const fusionResult = fuseWithSceneObjects(sceneObjects, extrusions, this.isFaceSourced() ? { glue: 'full' } : undefined);
88
+ console.log(`[perf] Extrude.buildAdd.fuseWithSceneObjects: ${(performance.now() - tFuse).toFixed(1)} ms`);
68
89
  for (const modifiedShape of fusionResult.modifiedShapes) {
69
90
  if (!modifiedShape.object) {
70
91
  continue;
@@ -158,7 +179,7 @@ export class Extrude extends ExtrudeBase {
158
179
  this.setState('side-faces', sideFaces);
159
180
  this.setState('internal-faces', internalFaces);
160
181
  this.setState('cap-faces', capFaces);
161
- this.extrudable.removeShapes(this);
182
+ this.getSource()?.removeShapes(this);
162
183
  if (extrusions.length === 0 || sceneObjects.length === 0) {
163
184
  this.addShapes(extrusions);
164
185
  return;
@@ -179,6 +200,9 @@ export class Extrude extends ExtrudeBase {
179
200
  if (this._symmetric) {
180
201
  // Symmetric cut: create tool centered on sketch plane
181
202
  if (isThroughAll) {
203
+ if (this.isFaceSourced()) {
204
+ throw new Error("through-all is not supported with a face-sourced extrude");
205
+ }
182
206
  const extrudeThroughAll = new ExtrudeThroughAll(this.extrudable, true, true, faces);
183
207
  toolShapes = extrudeThroughAll.build();
184
208
  }
@@ -193,6 +217,9 @@ export class Extrude extends ExtrudeBase {
193
217
  }
194
218
  }
195
219
  else if (isThroughAll) {
220
+ if (this.isFaceSourced()) {
221
+ throw new Error("through-all is not supported with a face-sourced extrude");
222
+ }
196
223
  const extrudeThroughAll = new ExtrudeThroughAll(this.extrudable, false, true, faces);
197
224
  toolShapes = extrudeThroughAll.build();
198
225
  }
@@ -201,17 +228,17 @@ export class Extrude extends ExtrudeBase {
201
228
  const extruder = new Extruder(faces, plane, distance, this.getDraft(), this.getEndOffset());
202
229
  toolShapes = extruder.extrude();
203
230
  }
204
- this.extrudable.removeShapes(this);
231
+ this.getSource()?.removeShapes(this);
205
232
  cutWithSceneObjects(scope, toolShapes, plane, this.distance, this);
206
233
  }
207
234
  getDependencies() {
208
- return this.extrudable ? [this.extrudable] : [];
235
+ const source = this.getSource();
236
+ return source ? [source] : [];
209
237
  }
210
238
  createCopy(remap) {
211
- const extrudable = this.extrudable
212
- ? (remap.get(this.extrudable) || this.extrudable)
213
- : undefined;
214
- return new Extrude(this.distance, extrudable).syncWith(this);
239
+ const source = this.getSource();
240
+ const remapped = source ? (remap.get(source) || source) : undefined;
241
+ return new Extrude(this.distance, remapped).syncWith(this);
215
242
  }
216
243
  compareTo(other) {
217
244
  if (!(other instanceof Extrude)) {
@@ -220,7 +247,12 @@ export class Extrude extends ExtrudeBase {
220
247
  if (!super.compareTo(other)) {
221
248
  return false;
222
249
  }
223
- if (!this.extrudable.compareTo(other.extrudable)) {
250
+ const thisSource = this.getSource();
251
+ const otherSource = other.getSource();
252
+ if (!thisSource !== !otherSource) {
253
+ return false;
254
+ }
255
+ if (thisSource && otherSource && !thisSource.compareTo(otherSource)) {
224
256
  return false;
225
257
  }
226
258
  if (this.distance !== other.distance) {
@@ -242,7 +274,7 @@ export class Extrude extends ExtrudeBase {
242
274
  }
243
275
  serialize() {
244
276
  return {
245
- extrudable: this.extrudable.serialize(),
277
+ extrudable: this.getSource()?.serialize(),
246
278
  distance: this.distance,
247
279
  operationMode: this._operationMode !== 'add' ? this._operationMode : undefined,
248
280
  symmetric: this._symmetric || undefined,