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
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Error thrown by feature pre-build validation when an operand or input is
3
+ * unsuitable for the operation. Carries an optional hint so the user can be
4
+ * told both what went wrong and how to fix it.
5
+ *
6
+ * Extends Error so the renderer's existing try/catch in `buildObject` and
7
+ * `describeError` keep working unchanged.
8
+ */
9
+ export declare class BuildError extends Error {
10
+ readonly diagnostic: string;
11
+ readonly hint?: string;
12
+ constructor(diagnostic: string, hint?: string);
13
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Error thrown by feature pre-build validation when an operand or input is
3
+ * unsuitable for the operation. Carries an optional hint so the user can be
4
+ * told both what went wrong and how to fix it.
5
+ *
6
+ * Extends Error so the renderer's existing try/catch in `buildObject` and
7
+ * `describeError` keep working unchanged.
8
+ */
9
+ export class BuildError extends Error {
10
+ diagnostic;
11
+ hint;
12
+ constructor(diagnostic, hint) {
13
+ super(hint ? `${diagnostic}\nHint: ${hint}` : diagnostic);
14
+ this.diagnostic = diagnostic;
15
+ this.hint = hint;
16
+ this.name = 'BuildError';
17
+ }
18
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Convert any thrown value to a readable string. OCC throws come through
3
+ * Emscripten as raw numeric pointers; decode those via OCJS.getStandard_FailureData
4
+ * so the actual reason ("Offset wire is not closed.", etc.) reaches the user.
5
+ */
6
+ export declare function describeError(error: unknown): string;
@@ -0,0 +1,26 @@
1
+ import { getOC } from "../oc/init.js";
2
+ /**
3
+ * Convert any thrown value to a readable string. OCC throws come through
4
+ * Emscripten as raw numeric pointers; decode those via OCJS.getStandard_FailureData
5
+ * so the actual reason ("Offset wire is not closed.", etc.) reaches the user.
6
+ */
7
+ export function describeError(error) {
8
+ if (error instanceof Error) {
9
+ return error.message;
10
+ }
11
+ if (typeof error === 'number' && Number.isFinite(error)) {
12
+ try {
13
+ const oc = getOC();
14
+ const failure = oc.OCJS.getStandard_FailureData(error);
15
+ const msg = failure.GetMessageString();
16
+ if (msg) {
17
+ return `OCC: ${msg}`;
18
+ }
19
+ return `OCC error (ptr=${error})`;
20
+ }
21
+ catch (e) {
22
+ console.log("[describeError] decode failed:", e);
23
+ }
24
+ }
25
+ return String(error);
26
+ }
@@ -0,0 +1,19 @@
1
+ import { SceneObject } from "./scene-object.js";
2
+ import { Shape } from "./shape.js";
3
+ interface RequireShapesOpts {
4
+ /** Require exactly this many shapes. */
5
+ count?: number;
6
+ /** Require every shape to be of this type. */
7
+ type?: string;
8
+ }
9
+ /**
10
+ * Validate that an operand SceneObject still owns shapes the consumer can use,
11
+ * and produce a uniform diagnostic when it doesn't.
12
+ *
13
+ * The most common failure mode is a SceneObject whose geometry was consumed by
14
+ * an earlier op (e.g. `translate(amount, target)` moves the shape from
15
+ * `target` onto the translate object). `getRemovedShapes()` records who took
16
+ * each shape, so we can name the consumer in the error.
17
+ */
18
+ export declare function requireShapes(obj: SceneObject, operandLabel: string, consumerType: string, opts?: RequireShapesOpts): Shape[];
19
+ export {};
@@ -0,0 +1,38 @@
1
+ import { BuildError } from "./build-error.js";
2
+ /**
3
+ * Validate that an operand SceneObject still owns shapes the consumer can use,
4
+ * and produce a uniform diagnostic when it doesn't.
5
+ *
6
+ * The most common failure mode is a SceneObject whose geometry was consumed by
7
+ * an earlier op (e.g. `translate(amount, target)` moves the shape from
8
+ * `target` onto the translate object). `getRemovedShapes()` records who took
9
+ * each shape, so we can name the consumer in the error.
10
+ */
11
+ export function requireShapes(obj, operandLabel, consumerType, opts) {
12
+ // Lazy operands (LazySelectionSceneObject, LazyVertex) only populate their
13
+ // shapes during build. Their pre-build emptiness is expected, so skip —
14
+ // the build itself still validates them.
15
+ if (obj.isLazy()) {
16
+ return [];
17
+ }
18
+ const shapes = obj.getShapes();
19
+ if (shapes.length === 0) {
20
+ const removed = obj.getRemovedShapes();
21
+ const objLabel = `${operandLabel} (${obj.getType()})`;
22
+ if (removed.length > 0) {
23
+ const consumers = [...new Set(removed.map(r => r.removedBy.getType()))].join(", ");
24
+ throw new BuildError(`${consumerType}: ${objLabel} has no shapes — its geometry was consumed by ${consumers}.`, `Reference the result of ${consumers} as the operand instead of ${obj.getType()}.`);
25
+ }
26
+ throw new BuildError(`${consumerType}: ${objLabel} has no shapes.`, `Make sure the upstream operation produced geometry.`);
27
+ }
28
+ if (opts?.count !== undefined && shapes.length !== opts.count) {
29
+ throw new BuildError(`${consumerType}: ${operandLabel} has ${shapes.length} shapes, expected ${opts.count}.`);
30
+ }
31
+ if (opts?.type) {
32
+ const wrong = shapes.find(s => s.getType() !== opts.type);
33
+ if (wrong) {
34
+ throw new BuildError(`${consumerType}: ${operandLabel} has a shape of type '${wrong.getType()}', expected '${opts.type}'.`);
35
+ }
36
+ }
37
+ return shapes;
38
+ }
@@ -75,6 +75,14 @@ export declare abstract class SceneObject implements Comparable<SceneObject>, Se
75
75
  abstract serialize(scope?: Set<SceneObject>): any;
76
76
  abstract getType(): string;
77
77
  abstract build(context?: BuildSceneObjectContext): void;
78
+ /**
79
+ * Pre-build validation hook. The renderer calls this before `build()` so
80
+ * features can fail fast with a clear diagnostic (typically a `BuildError`)
81
+ * before any OC work runs. Default is a no-op; overrides should not mutate
82
+ * state. Existing per-feature checks inside `build()` are intentionally not
83
+ * removed — this hook is additive.
84
+ */
85
+ validate(): void;
78
86
  getAppliedTransform(): Matrix4 | null;
79
87
  protected composeAppliedTransform(matrix: Matrix4): void;
80
88
  compareTo(other: SceneObject): boolean;
@@ -100,6 +100,16 @@ export class SceneObject {
100
100
  getSnapshot() {
101
101
  return this.getState('snapshot') || [];
102
102
  }
103
+ /**
104
+ * Pre-build validation hook. The renderer calls this before `build()` so
105
+ * features can fail fast with a clear diagnostic (typically a `BuildError`)
106
+ * before any OC work runs. Default is a no-op; overrides should not mutate
107
+ * state. Existing per-feature checks inside `build()` are intentionally not
108
+ * removed — this hook is additive.
109
+ */
110
+ validate() {
111
+ // Override in subclasses to add operand checks via `requireShapes` etc.
112
+ }
103
113
  getAppliedTransform() {
104
114
  return this._appliedTransform;
105
115
  }
@@ -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): Solid | Face | Edge | Wire;
7
+ static fromShape(shape: TopoDS_Shape): Edge | Wire | Solid | Face;
8
8
  }
@@ -18,10 +18,12 @@ interface ArcFunction {
18
18
  (startPoint: Point2DLike, endPoint: Point2DLike): IArcPoints;
19
19
  /**
20
20
  * Draws an arc by radius and angle range at the current position.
21
+ * Angles are measured relative to the current tangent (the tangent of the previous
22
+ * geometry, or +X when there is none).
21
23
  * Chain `.centered()` to center the arc symmetrically around the start angle.
22
24
  * @param radius - The arc radius
23
- * @param startAngle - The start angle in degrees (defaults to 0)
24
- * @param endAngle - The end angle in degrees (defaults to 180)
25
+ * @param startAngle - The start angle in degrees, relative to the current tangent (defaults to 0)
26
+ * @param endAngle - The end angle in degrees, relative to the current tangent (defaults to 180)
25
27
  */
26
28
  (radius: number, startAngle?: number, endAngle?: number): IArcAngles;
27
29
  /**
@@ -1,9 +1,16 @@
1
+ import { IGeometry, ISceneObject } from "../interfaces.js";
1
2
  interface HMoveFunction {
2
3
  /**
3
4
  * Moves the cursor horizontally by the given distance.
4
5
  * @param distance - The horizontal distance to move
5
6
  */
6
- (distance: number): void;
7
+ (distance: number): IGeometry;
8
+ /**
9
+ * Moves the cursor horizontally to the nearest intersection with the target geometry.
10
+ * The nearest intersection (in either direction along the X axis) is used.
11
+ * @param target - The geometry to intersect with
12
+ */
13
+ (target: ISceneObject): IGeometry;
7
14
  }
8
15
  declare const _default: HMoveFunction;
9
16
  export default _default;
@@ -1,9 +1,13 @@
1
1
  import { HMove } from "../../features/2d/hmove.js";
2
+ import { SceneObject } from "../../common/scene-object.js";
2
3
  import { registerBuilder } from "../../index.js";
3
4
  function build(context) {
4
5
  return function move() {
5
- const distance = arguments[0];
6
- const hmove = new HMove(distance);
6
+ const arg = arguments[0];
7
+ const distanceOrTarget = arg instanceof SceneObject
8
+ ? arg
9
+ : arg;
10
+ const hmove = new HMove(distanceOrTarget);
7
11
  context.addSceneObject(hmove);
8
12
  return hmove;
9
13
  };
@@ -1,11 +1,21 @@
1
- import { IGeometry } from "../interfaces.js";
1
+ import { IGeometry, ISceneObject } from "../interfaces.js";
2
2
  interface PolarMoveFunction {
3
3
  /**
4
- * Moves the cursor by polar coordinates.
4
+ * Moves the cursor by polar coordinates, relative to the current tangent.
5
+ * The angle is measured from the tangent of the previous geometry (defaults to +X
6
+ * when there is none).
5
7
  * @param radius - The distance to move
6
- * @param angle - The angle in degrees
8
+ * @param angle - The angle in degrees, relative to the current tangent
7
9
  */
8
10
  (radius: number, angle: number): IGeometry;
11
+ /**
12
+ * Moves the cursor along the given angle (relative to the current tangent) to the
13
+ * nearest intersection with the target geometry. The nearest intersection (in either
14
+ * direction along the ray) is used.
15
+ * @param target - The geometry to intersect with
16
+ * @param angle - The angle in degrees, relative to the current tangent
17
+ */
18
+ (target: ISceneObject, angle: number): IGeometry;
9
19
  }
10
20
  declare const _default: PolarMoveFunction;
11
21
  export default _default;
@@ -1,10 +1,14 @@
1
1
  import { PolarMove } from "../../features/2d/pmove.js";
2
+ import { SceneObject } from "../../common/scene-object.js";
2
3
  import { registerBuilder } from "../../index.js";
3
4
  function build(context) {
4
5
  return function pmove() {
5
- const radius = arguments[0];
6
+ const arg0 = arguments[0];
6
7
  const angle = arguments[1] * Math.PI / 180;
7
- const pmove = new PolarMove(radius, angle);
8
+ const radiusOrTarget = arg0 instanceof SceneObject
9
+ ? arg0
10
+ : arg0;
11
+ const pmove = new PolarMove(radiusOrTarget, angle);
8
12
  context.addSceneObject(pmove);
9
13
  return pmove;
10
14
  };
@@ -1,9 +1,16 @@
1
+ import { IGeometry, ISceneObject } from "../interfaces.js";
1
2
  interface VMoveFunction {
2
3
  /**
3
4
  * Moves the cursor vertically by the given distance.
4
5
  * @param distance - The vertical distance to move
5
6
  */
6
- (distance: number): void;
7
+ (distance: number): IGeometry;
8
+ /**
9
+ * Moves the cursor vertically to the nearest intersection with the target geometry.
10
+ * The nearest intersection (in either direction along the Y axis) is used.
11
+ * @param target - The geometry to intersect with
12
+ */
13
+ (target: ISceneObject): IGeometry;
7
14
  }
8
15
  declare const _default: VMoveFunction;
9
16
  export default _default;
@@ -1,11 +1,15 @@
1
1
  import { VMove } from "../../features/2d/vmove.js";
2
+ import { SceneObject } from "../../common/scene-object.js";
2
3
  import { registerBuilder } from "../../index.js";
3
4
  function build(context) {
4
5
  return function move() {
5
- const distance = arguments[0];
6
- const move = new VMove(distance);
7
- context.addSceneObject(move);
8
- return move;
6
+ const arg = arguments[0];
7
+ const distanceOrTarget = arg instanceof SceneObject
8
+ ? arg
9
+ : arg;
10
+ const vmove = new VMove(distanceOrTarget);
11
+ context.addSceneObject(vmove);
12
+ return vmove;
9
13
  };
10
14
  }
11
15
  export default registerBuilder(build);
@@ -1,4 +1,5 @@
1
1
  import { IExtrude, ISceneObject } from "./interfaces.js";
2
+ import { FaceFilterBuilder } from "../filters/face/face-filter.js";
2
3
  interface ExtrudeFunction {
3
4
  /**
4
5
  * Extrudes the last sketch with a default distance.
@@ -33,15 +34,27 @@ interface ExtrudeFunction {
33
34
  /**
34
35
  * Extrudes up to the first intersecting face.
35
36
  * @param face - The literal `'first-face'`
36
- * @param target - The sketch or face-bearing scene object to extrude
37
+ * @param filters - Optional face filters to narrow the candidate set
38
+ */
39
+ (face: 'first-face', ...filters: FaceFilterBuilder[]): IExtrude;
40
+ /**
41
+ * Extrudes up to the first intersecting face.
42
+ * @param face - The literal `'first-face'`
43
+ * @param filtersAndTarget - Optional face filters followed by the target to extrude
37
44
  */
38
- (face: 'first-face', target?: ISceneObject): IExtrude;
45
+ (face: 'first-face', ...filtersAndTarget: [...FaceFilterBuilder[], ISceneObject]): IExtrude;
39
46
  /**
40
47
  * Extrudes up to the last intersecting face.
41
48
  * @param face - The literal `'last-face'`
42
- * @param target - The sketch or face-bearing scene object to extrude
49
+ * @param filters - Optional face filters to narrow the candidate set
50
+ */
51
+ (face: 'last-face', ...filters: FaceFilterBuilder[]): IExtrude;
52
+ /**
53
+ * Extrudes up to the last intersecting face.
54
+ * @param face - The literal `'last-face'`
55
+ * @param filtersAndTarget - Optional face filters followed by the target to extrude
43
56
  */
44
- (face: 'last-face', target?: ISceneObject): IExtrude;
57
+ (face: 'last-face', ...filtersAndTarget: [...FaceFilterBuilder[], ISceneObject]): IExtrude;
45
58
  }
46
59
  declare const _default: ExtrudeFunction;
47
60
  export default _default;
@@ -4,6 +4,7 @@ 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
6
  import { SelectSceneObject } from "../features/select.js";
7
+ import { FaceFilterBuilder } from "../filters/face/face-filter.js";
7
8
  function isExtrudable(obj) {
8
9
  return obj instanceof SceneObject && obj.isExtrudable();
9
10
  }
@@ -25,16 +26,17 @@ function build(context) {
25
26
  if (params.length === 0) {
26
27
  return new Extrude(defaultDistance, extrudable);
27
28
  }
29
+ if (params[0] === 'first-face' || params[0] === 'last-face') {
30
+ const rest = params.slice(1);
31
+ if (!rest.every(a => a instanceof FaceFilterBuilder)) {
32
+ throw new Error("Invalid parameter for extrude function.");
33
+ }
34
+ return new ExtrudeToFace(params[0], extrudable, rest);
35
+ }
28
36
  if (params.length === 1) {
29
37
  if (typeof params[0] === 'number') {
30
38
  return new Extrude(params[0], extrudable);
31
39
  }
32
- else if (params[0] === 'first-face') {
33
- return new ExtrudeToFace('first-face', extrudable);
34
- }
35
- else if (params[0] === 'last-face') {
36
- return new ExtrudeToFace('last-face', extrudable);
37
- }
38
40
  else if (params[0] instanceof SceneObject) {
39
41
  context.addSceneObject(params[0]);
40
42
  return new ExtrudeToFace(params[0], extrudable);
@@ -11,11 +11,6 @@ export interface ISceneObject {
11
11
  * @param value - The display name to assign.
12
12
  */
13
13
  name(value: string): this;
14
- /**
15
- * Marks this object as construction geometry. Guide objects are excluded from
16
- * final geometry output unless explicitly included.
17
- */
18
- guide(): this;
19
14
  /**
20
15
  * Marks this object as reusable. Reusable objects retain their shapes when
21
16
  * consumed by features (e.g., extrude, revolve), allowing multiple features
@@ -107,6 +102,12 @@ export interface IAxis extends ISceneObject {
107
102
  export interface ISelect extends ISceneObject {
108
103
  }
109
104
  export interface IGeometry extends ISceneObject {
105
+ /**
106
+ * Marks this sketch geometry as construction geometry. Guide geometries are
107
+ * excluded from the final sketch output (e.g., extrude, revolve) unless
108
+ * explicitly included.
109
+ */
110
+ guide(): this;
110
111
  /**
111
112
  * Returns a lazy-evaluated vertex at the start point of this geometry element.
112
113
  */
@@ -656,7 +657,7 @@ export interface ISweep extends IBooleanOperation {
656
657
  */
657
658
  capEdges(...args: (number | EdgeFilterBuilder)[]): ISceneObject;
658
659
  }
659
- export interface IMirror extends ISceneObject {
660
+ export interface IMirror extends IBooleanOperation {
660
661
  /**
661
662
  * Excludes the given objects from the mirror operation. Useful when
662
663
  * mirroring "everything" but a few specific objects should be skipped,
@@ -665,6 +666,15 @@ export interface IMirror extends ISceneObject {
665
666
  */
666
667
  exclude(...objects: ISceneObject[]): this;
667
668
  }
669
+ export interface IMirror2D extends IGeometry {
670
+ /**
671
+ * Excludes the given sketch geometries from the mirror operation. Useful
672
+ * when mirroring "everything" but a few specific geometries should be
673
+ * skipped, or when narrowing an explicit target list.
674
+ * @param objects - The sketch geometries to exclude from mirroring.
675
+ */
676
+ exclude(...objects: ISceneObject[]): this;
677
+ }
668
678
  export interface ITranslate extends ISceneObject {
669
679
  /**
670
680
  * Excludes the given objects from the translate operation. Useful when
@@ -1,29 +1,29 @@
1
1
  import { PlaneLike } from "../math/plane.js";
2
2
  import { AxisLike } from "../math/axis.js";
3
- import { IMirror, ISceneObject } from "./interfaces.js";
3
+ import { IMirror, IMirror2D, ISceneObject } from "./interfaces.js";
4
4
  interface MirrorFunction {
5
5
  /**
6
6
  * [2D] Mirror all sketch geometries across a given line.
7
7
  * @param line The line to mirror across
8
8
  */
9
- (line: ISceneObject): IMirror;
9
+ (line: ISceneObject): IMirror2D;
10
10
  /**
11
11
  * [2D] Mirror all sketch geometries across a given axis.
12
12
  * @param axis The local axis to mirror across
13
13
  */
14
- (axis: AxisLike): IMirror;
14
+ (axis: AxisLike): IMirror2D;
15
15
  /**
16
16
  * [2D] Mirror given sketch geometries across a given line.
17
17
  * @param line The line to mirror across
18
18
  * @param geometries The geometries to mirror
19
19
  */
20
- (line: ISceneObject, ...geometries: ISceneObject[]): IMirror;
20
+ (line: ISceneObject, ...geometries: ISceneObject[]): IMirror2D;
21
21
  /**
22
22
  * [2D] Mirror given sketch geometries across a given axis.
23
23
  * @param axis The local axis to mirror across
24
24
  * @param geometries The geometries to mirror
25
25
  */
26
- (axis: AxisLike, ...geometries: ISceneObject[]): IMirror;
26
+ (axis: AxisLike, ...geometries: ISceneObject[]): IMirror2D;
27
27
  /**
28
28
  * [3D] Mirror all scene shapes across a given plane.
29
29
  * @param plane The plane to mirror across
@@ -22,7 +22,7 @@ export class AngledLine extends GeometrySceneObject {
22
22
  }
23
23
  build() {
24
24
  const plane = this.targetPlane?.getPlane() || this.sketch.getPlane();
25
- let tangent = this.sketch?.getTangentAt(this) || new Point2D(1, 0);
25
+ let tangent = this.sketch?.getTangentAt(this) ?? new Point2D(1, 0);
26
26
  tangent = tangent.normalize();
27
27
  const angleRad = rad(this.angle);
28
28
  // 2D rotation of tangent by angle
@@ -48,7 +48,11 @@ export class AngledLine extends GeometrySceneObject {
48
48
  throw new Error('aLine: .centered() cannot be combined with a target geometry');
49
49
  }
50
50
  startPoint = currentPos;
51
- endPoint = findNearestRayIntersection(plane, startPoint, direction, this.lengthOrTarget);
51
+ const hit = findNearestRayIntersection(plane, startPoint, direction, this.lengthOrTarget);
52
+ if (!hit) {
53
+ throw new Error("Line does not intersect target geometry");
54
+ }
55
+ endPoint = hit;
52
56
  }
53
57
  const start = plane.localToWorld(startPoint);
54
58
  const end = plane.localToWorld(endPoint);
@@ -3,6 +3,7 @@ import { PlaneObjectBase } from "../plane-renderable-base.js";
3
3
  import { GeometrySceneObject } from "./geometry.js";
4
4
  import { LazyVertex } from "../lazy-vertex.js";
5
5
  import { IArcPoints, IArcAngles } from "../../core/interfaces.js";
6
+ import { SceneObject } from "../../common/scene-object.js";
6
7
  export declare class Arc extends GeometrySceneObject implements IArcPoints, IArcAngles {
7
8
  private _startPoint;
8
9
  private _endPoint;
@@ -27,6 +28,8 @@ export declare class Arc extends GeometrySceneObject implements IArcPoints, IArc
27
28
  private buildWithCenter;
28
29
  private buildFromAngles;
29
30
  getType(): string;
31
+ getDependencies(): SceneObject[];
32
+ createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
30
33
  compareTo(other: Arc): boolean;
31
34
  serialize(): Record<string, unknown>;
32
35
  }
@@ -167,7 +167,6 @@ export class Arc extends GeometrySceneObject {
167
167
  const d = Math.sqrt(r * r - (chordLen / 2) * (chordLen / 2));
168
168
  const sign = cw ? -1 : 1;
169
169
  const centerPoint = new Point2D(mx + sign * d * px, my + sign * d * py);
170
- const startAngle = Math.atan2(startPoint.y - centerPoint.y, startPoint.x - centerPoint.x);
171
170
  const endAngle = Math.atan2(targetPoint.y - centerPoint.y, targetPoint.x - centerPoint.x);
172
171
  const normal = cw ? plane.normal.negate() : plane.normal;
173
172
  const center = plane.localToWorld(centerPoint);
@@ -224,6 +223,10 @@ export class Arc extends GeometrySceneObject {
224
223
  const centerPoint = this._targetPlane
225
224
  ? plane.worldToLocal(this._targetPlane.getPlaneCenter())
226
225
  : this.getCurrentPosition();
226
+ // Angles are measured relative to the current tangent (defaults to +X).
227
+ const tangent = (this._targetPlane ? null : this.sketch?.getTangentAt(this))
228
+ ?? new Point2D(1, 0);
229
+ const tangentAngle = Math.atan2(tangent.y, tangent.x);
227
230
  const cw = this._endAngle < 0;
228
231
  const absStartAngle = Math.abs(this._startAngle);
229
232
  const absEndAngle = Math.abs(this._endAngle);
@@ -239,6 +242,8 @@ export class Arc extends GeometrySceneObject {
239
242
  startAngleRad = rad(absStartAngle);
240
243
  endAngleRad = rad(absEndAngle);
241
244
  }
245
+ startAngleRad += tangentAngle;
246
+ endAngleRad += tangentAngle;
242
247
  const normal = cw ? plane.normal.negate() : plane.normal;
243
248
  const startPoint = Geometry.getPointOnCircle(centerPoint, radius, startAngleRad);
244
249
  const endPoint = Geometry.getPointOnCircle(centerPoint, radius, endAngleRad);
@@ -261,6 +266,28 @@ export class Arc extends GeometrySceneObject {
261
266
  getType() {
262
267
  return 'arc';
263
268
  }
269
+ getDependencies() {
270
+ return this._targetPlane ? [this._targetPlane] : [];
271
+ }
272
+ createCopy(remap) {
273
+ const targetPlane = this._targetPlane
274
+ ? (remap.get(this._targetPlane) || this._targetPlane)
275
+ : null;
276
+ let copy;
277
+ if (this._startPoint && this._endPoint) {
278
+ copy = Arc.twoPoints(this._startPoint, this._endPoint, targetPlane);
279
+ }
280
+ else if (this._endPoint) {
281
+ copy = Arc.toPoint(this._endPoint, targetPlane);
282
+ }
283
+ else {
284
+ copy = Arc.fromAngles(this._arcRadius, this._startAngle, this._endAngle, targetPlane);
285
+ }
286
+ copy._bulgeRadius = this._bulgeRadius;
287
+ copy._centerPoint = this._centerPoint;
288
+ copy._centered = this._centered;
289
+ return copy;
290
+ }
264
291
  compareTo(other) {
265
292
  if (!(other instanceof Arc)) {
266
293
  return false;
@@ -38,7 +38,11 @@ export class HorizontalLine extends GeometrySceneObject {
38
38
  throw new Error('hLine: .centered() cannot be combined with a target geometry');
39
39
  }
40
40
  startPoint = currentPos;
41
- endPoint = findNearestRayIntersection(plane, startPoint, new Point2D(1, 0), this.distanceOrTarget);
41
+ const hit = findNearestRayIntersection(plane, startPoint, new Point2D(1, 0), this.distanceOrTarget);
42
+ if (!hit) {
43
+ throw new Error("Line does not intersect target geometry");
44
+ }
45
+ endPoint = hit;
42
46
  signedLength = endPoint.x - startPoint.x;
43
47
  }
44
48
  const start = plane.localToWorld(startPoint);
@@ -1,8 +1,8 @@
1
1
  import { SceneObject } from "../../common/scene-object.js";
2
2
  import { GeometrySceneObject } from "./geometry.js";
3
3
  export declare class HMove extends GeometrySceneObject {
4
- distance: number;
5
- constructor(distance: number);
4
+ distanceOrTarget: number | SceneObject;
5
+ constructor(distanceOrTarget: number | SceneObject);
6
6
  getType(): string;
7
7
  build(): void;
8
8
  getDependencies(): SceneObject[];
@@ -1,24 +1,43 @@
1
1
  import { Point2D } from "../../math/point.js";
2
+ import { SceneObject } from "../../common/scene-object.js";
2
3
  import { GeometrySceneObject } from "./geometry.js";
4
+ import { findNearestRayIntersection } from "../../oc/ray-intersect.js";
3
5
  export class HMove extends GeometrySceneObject {
4
- distance;
5
- constructor(distance) {
6
+ distanceOrTarget;
7
+ constructor(distanceOrTarget) {
6
8
  super();
7
- this.distance = distance;
9
+ this.distanceOrTarget = distanceOrTarget;
8
10
  }
9
11
  getType() {
10
12
  return 'hmove';
11
13
  }
12
14
  build() {
13
15
  const pos = this.getCurrentPosition();
14
- const newPos = new Point2D(pos.x + this.distance, pos.y);
16
+ let newPos;
17
+ if (typeof this.distanceOrTarget === 'number') {
18
+ newPos = new Point2D(pos.x + this.distanceOrTarget, pos.y);
19
+ }
20
+ else {
21
+ const plane = this.sketch.getPlane();
22
+ const hit = findNearestRayIntersection(plane, pos, new Point2D(1, 0), this.distanceOrTarget);
23
+ if (!hit) {
24
+ throw new Error("Cannot move horizontally up to the specified geometry");
25
+ }
26
+ newPos = hit;
27
+ }
15
28
  this.setCurrentPosition(newPos);
16
29
  }
17
30
  getDependencies() {
31
+ if (this.distanceOrTarget instanceof SceneObject) {
32
+ return [this.distanceOrTarget];
33
+ }
18
34
  return [];
19
35
  }
20
36
  createCopy(remap) {
21
- return new HMove(this.distance);
37
+ const distanceOrTarget = this.distanceOrTarget instanceof SceneObject
38
+ ? (remap.get(this.distanceOrTarget) || this.distanceOrTarget)
39
+ : this.distanceOrTarget;
40
+ return new HMove(distanceOrTarget);
22
41
  }
23
42
  compareTo(other) {
24
43
  if (!(other instanceof HMove)) {
@@ -27,11 +46,17 @@ export class HMove extends GeometrySceneObject {
27
46
  if (!super.compareTo(other)) {
28
47
  return false;
29
48
  }
30
- return this.distance === other.distance;
49
+ if (typeof this.distanceOrTarget !== typeof other.distanceOrTarget) {
50
+ return false;
51
+ }
52
+ if (this.distanceOrTarget instanceof SceneObject && other.distanceOrTarget instanceof SceneObject) {
53
+ return this.distanceOrTarget.compareTo(other.distanceOrTarget);
54
+ }
55
+ return this.distanceOrTarget === other.distanceOrTarget;
31
56
  }
32
57
  serialize() {
33
58
  return {
34
- distance: this.distance
59
+ distance: typeof this.distanceOrTarget === 'number' ? this.distanceOrTarget : null
35
60
  };
36
61
  }
37
62
  }