fluidcad 0.0.28 → 0.0.29

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 (101) 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 +2 -0
  4. package/lib/dist/common/shape-history-tracker.d.ts +9 -1
  5. package/lib/dist/common/shape-history-tracker.js +37 -23
  6. package/lib/dist/core/2d/aline.d.ts +4 -4
  7. package/lib/dist/core/2d/aline.js +14 -10
  8. package/lib/dist/core/2d/arc.d.ts +6 -6
  9. package/lib/dist/core/2d/arc.js +19 -15
  10. package/lib/dist/core/2d/circle.d.ts +2 -2
  11. package/lib/dist/core/2d/circle.js +14 -10
  12. package/lib/dist/core/2d/ellipse.d.ts +35 -0
  13. package/lib/dist/core/2d/ellipse.js +65 -0
  14. package/lib/dist/core/2d/hline.d.ts +4 -4
  15. package/lib/dist/core/2d/hline.js +14 -10
  16. package/lib/dist/core/2d/index.d.ts +1 -0
  17. package/lib/dist/core/2d/index.js +1 -0
  18. package/lib/dist/core/2d/intersect.d.ts +2 -2
  19. package/lib/dist/core/2d/intersect.js +7 -3
  20. package/lib/dist/core/2d/line.d.ts +2 -2
  21. package/lib/dist/core/2d/line.js +14 -10
  22. package/lib/dist/core/2d/offset.d.ts +4 -4
  23. package/lib/dist/core/2d/offset.js +9 -5
  24. package/lib/dist/core/2d/polygon.d.ts +4 -4
  25. package/lib/dist/core/2d/polygon.js +24 -20
  26. package/lib/dist/core/2d/project.d.ts +2 -2
  27. package/lib/dist/core/2d/project.js +7 -3
  28. package/lib/dist/core/2d/rect.d.ts +2 -2
  29. package/lib/dist/core/2d/rect.js +22 -21
  30. package/lib/dist/core/2d/slot.d.ts +6 -6
  31. package/lib/dist/core/2d/slot.js +29 -32
  32. package/lib/dist/core/2d/vline.d.ts +4 -4
  33. package/lib/dist/core/2d/vline.js +14 -10
  34. package/lib/dist/core/interfaces.d.ts +8 -0
  35. package/lib/dist/core/mirror.js +17 -11
  36. package/lib/dist/core/part.d.ts +3 -1
  37. package/lib/dist/core/part.js +1 -1
  38. package/lib/dist/core/rotate.js +4 -1
  39. package/lib/dist/core/sketch.d.ts +3 -1
  40. package/lib/dist/core/sketch.js +1 -1
  41. package/lib/dist/features/2d/ellipse.d.ts +23 -0
  42. package/lib/dist/features/2d/ellipse.js +75 -0
  43. package/lib/dist/features/2d/offset.d.ts +3 -0
  44. package/lib/dist/features/2d/offset.js +27 -3
  45. package/lib/dist/features/common.js +2 -1
  46. package/lib/dist/features/extrude-base.d.ts +19 -1
  47. package/lib/dist/features/extrude-base.js +75 -12
  48. package/lib/dist/features/extrude-two-distances.js +32 -27
  49. package/lib/dist/features/extrude.d.ts +39 -0
  50. package/lib/dist/features/extrude.js +196 -156
  51. package/lib/dist/features/fuse.js +2 -1
  52. package/lib/dist/features/loft.js +11 -8
  53. package/lib/dist/features/revolve.d.ts +31 -0
  54. package/lib/dist/features/revolve.js +178 -95
  55. package/lib/dist/features/select.js +2 -1
  56. package/lib/dist/features/simple-extruder.d.ts +3 -1
  57. package/lib/dist/features/simple-extruder.js +13 -9
  58. package/lib/dist/features/subtract.d.ts +2 -2
  59. package/lib/dist/features/subtract.js +3 -3
  60. package/lib/dist/features/sweep.d.ts +14 -0
  61. package/lib/dist/features/sweep.js +93 -80
  62. package/lib/dist/filters/edge/edge-filter.d.ts +6 -0
  63. package/lib/dist/filters/edge/edge-filter.js +11 -0
  64. package/lib/dist/filters/face/face-filter.d.ts +6 -0
  65. package/lib/dist/filters/face/face-filter.js +11 -0
  66. package/lib/dist/filters/filter-base.d.ts +7 -1
  67. package/lib/dist/filters/filter-base.js +8 -0
  68. package/lib/dist/filters/filter-builder-base.js +11 -0
  69. package/lib/dist/filters/from-object.d.ts +14 -0
  70. package/lib/dist/filters/from-object.js +40 -0
  71. package/lib/dist/helpers/scene-helpers.d.ts +2 -0
  72. package/lib/dist/helpers/scene-helpers.js +68 -48
  73. package/lib/dist/oc/color-transfer.js +6 -0
  74. package/lib/dist/oc/edge-ops.d.ts +1 -0
  75. package/lib/dist/oc/edge-ops.js +17 -0
  76. package/lib/dist/oc/extrude-ops.d.ts +18 -1
  77. package/lib/dist/oc/extrude-ops.js +34 -1
  78. package/lib/dist/oc/geometry.d.ts +1 -0
  79. package/lib/dist/oc/geometry.js +27 -0
  80. package/lib/dist/oc/mesh.js +11 -9
  81. package/lib/dist/oc/thin-face-maker.d.ts +0 -1
  82. package/lib/dist/oc/thin-face-maker.js +2 -20
  83. package/lib/dist/rendering/render.d.ts +2 -1
  84. package/lib/dist/rendering/render.js +68 -32
  85. package/lib/dist/rendering/scene.d.ts +4 -0
  86. package/lib/dist/tests/features/2d/circle.test.js +1 -1
  87. package/lib/dist/tests/features/2d/ellipse.test.d.ts +1 -0
  88. package/lib/dist/tests/features/2d/ellipse.test.js +100 -0
  89. package/lib/dist/tests/features/2d/line.test.js +1 -1
  90. package/lib/dist/tests/features/2d/offset.test.js +1 -1
  91. package/lib/dist/tests/features/2d/polygon.test.js +2 -2
  92. package/lib/dist/tests/features/2d/rect.test.js +1 -1
  93. package/lib/dist/tests/features/2d/slot.test.js +1 -1
  94. package/lib/dist/tests/features/thin-revolve.test.js +37 -1
  95. package/lib/dist/tests/perf/record-fusion-history.bench.test.d.ts +1 -0
  96. package/lib/dist/tests/perf/record-fusion-history.bench.test.js +77 -0
  97. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  98. package/package.json +1 -1
  99. package/ui/dist/assets/{index-gPoNOiIs.css → index-VKkXzLfR.css} +1 -1
  100. package/ui/dist/assets/{index-BrW_x4uc.js → index-VY48_dgc.js} +64 -47
  101. package/ui/dist/index.html +2 -2
@@ -0,0 +1,12 @@
1
+ export type ProfileCategory = {
2
+ category: string;
3
+ durationMs: number;
4
+ };
5
+ export declare class Profiler {
6
+ private categories;
7
+ private stack;
8
+ start(category: string): void;
9
+ end(category: string): void;
10
+ record<T>(category: string, fn: () => T): T;
11
+ getCategories(): ProfileCategory[];
12
+ }
@@ -0,0 +1,35 @@
1
+ export class Profiler {
2
+ categories = new Map();
3
+ stack = [];
4
+ start(category) {
5
+ this.stack.push({ category, startTime: performance.now() });
6
+ }
7
+ end(category) {
8
+ for (let i = this.stack.length - 1; i >= 0; i--) {
9
+ if (this.stack[i].category === category) {
10
+ const entry = this.stack.splice(i, 1)[0];
11
+ const elapsed = performance.now() - entry.startTime;
12
+ const current = this.categories.get(category) || 0;
13
+ this.categories.set(category, current + elapsed);
14
+ this.stack.length = i;
15
+ return;
16
+ }
17
+ }
18
+ }
19
+ record(category, fn) {
20
+ this.start(category);
21
+ try {
22
+ return fn();
23
+ }
24
+ finally {
25
+ this.end(category);
26
+ }
27
+ }
28
+ getCategories() {
29
+ const result = [];
30
+ for (const [category, durationMs] of this.categories.entries()) {
31
+ result.push({ category, durationMs: Math.round(durationMs * 10) / 10 });
32
+ }
33
+ return result;
34
+ }
35
+ }
@@ -5,6 +5,7 @@ import { Matrix4 } from "../math/matrix4.js";
5
5
  import { ISceneObject } from "../core/interfaces.js";
6
6
  import { FusionScope, OperationMode } from "../features/extrude-options.js";
7
7
  import { ShapeType } from "./shape-type.js";
8
+ import { Profiler } from "./profiler.js";
8
9
  export type SourceLocation = {
9
10
  filePath: string;
10
11
  line: number;
@@ -35,6 +36,7 @@ export type BuildSceneObjectContext = {
35
36
  getSceneObjectsFromTo(obj: SceneObject, to: SceneObject, type?: string): SceneObject[];
36
37
  getTransform(): Matrix4 | null;
37
38
  getLastObject(): SceneObject | null;
39
+ getProfiler(): Profiler;
38
40
  };
39
41
  export declare abstract class SceneObject implements Comparable<SceneObject>, Serializable, ISceneObject {
40
42
  private state;
@@ -30,6 +30,14 @@ export declare class ShapeHistoryTracker {
30
30
  */
31
31
  static remapFaces(faces: Face[], history: ShapeHistory): Face[];
32
32
  static remapEdges(edges: Edge[], history: ShapeHistory): Edge[];
33
- static collect(maker: BRepBuilderAPI_MakeShape, inputs: Shape[]): ShapeHistory;
33
+ /**
34
+ * Collect history for `inputs` against `maker`'s output. When `opts.skipAdded`
35
+ * is set, the added* fields come back empty — callers that compute additions
36
+ * themselves (e.g. `recordFusionHistory` aggregates across all scene shapes)
37
+ * skip the per-call output traversal and per-result claimed-map updates.
38
+ */
39
+ static collect(maker: BRepBuilderAPI_MakeShape, inputs: Shape[], opts?: {
40
+ skipAdded?: boolean;
41
+ }): ShapeHistory;
34
42
  private static collectForType;
35
43
  }
@@ -41,15 +41,22 @@ export class ShapeHistoryTracker {
41
41
  }
42
42
  return result;
43
43
  }
44
- static collect(maker, inputs) {
44
+ /**
45
+ * Collect history for `inputs` against `maker`'s output. When `opts.skipAdded`
46
+ * is set, the added* fields come back empty — callers that compute additions
47
+ * themselves (e.g. `recordFusionHistory` aggregates across all scene shapes)
48
+ * skip the per-call output traversal and per-result claimed-map updates.
49
+ */
50
+ static collect(maker, inputs, opts = {}) {
45
51
  const oc = getOC();
46
52
  const FACE = oc.TopAbs_ShapeEnum.TopAbs_FACE;
47
53
  const EDGE = oc.TopAbs_ShapeEnum.TopAbs_EDGE;
48
- const output = maker.Shape();
49
- const outputFaces = Explorer.findShapes(output, FACE);
50
- const outputEdges = Explorer.findShapes(output, EDGE);
51
- const faces = ShapeHistoryTracker.collectForType(maker, inputs, FACE, outputFaces, (raw) => Face.fromTopoDSFace(Explorer.toFace(raw)));
52
- const edges = ShapeHistoryTracker.collectForType(maker, inputs, EDGE, outputEdges, (raw) => Edge.fromTopoDSEdge(Explorer.toEdge(raw)));
54
+ const skipAdded = opts.skipAdded === true;
55
+ const output = skipAdded ? null : maker.Shape();
56
+ const outputFaces = output ? Explorer.findShapes(output, FACE) : [];
57
+ const outputEdges = output ? Explorer.findShapes(output, EDGE) : [];
58
+ const faces = ShapeHistoryTracker.collectForType(maker, inputs, FACE, outputFaces, (raw) => Face.fromTopoDSFace(Explorer.toFace(raw)), skipAdded);
59
+ const edges = ShapeHistoryTracker.collectForType(maker, inputs, EDGE, outputEdges, (raw) => Edge.fromTopoDSEdge(Explorer.toEdge(raw)), skipAdded);
53
60
  return {
54
61
  addedFaces: faces.added,
55
62
  modifiedFaces: faces.modified,
@@ -61,28 +68,30 @@ export class ShapeHistoryTracker {
61
68
  removedEdges: edges.removed,
62
69
  };
63
70
  }
64
- static collectForType(maker, inputs, type, outputRaws, wrap) {
71
+ static collectForType(maker, inputs, type, outputRaws, wrap, skipAdded) {
65
72
  const oc = getOC();
66
73
  const modified = [];
67
74
  const generated = [];
68
75
  const removed = [];
69
- // Collect every output raw that was either Modified or Generated from an input,
70
- // so we can later classify the remaining outputs as pure additions.
71
- const claimed = new oc.TopTools_MapOfShape();
76
+ // Track which output raws are claimed by a Modified/Generated record so the
77
+ // remaining outputs can be reported as pure additions. When `skipAdded`,
78
+ // we don't compute additions, so skip the bookkeeping entirely.
79
+ const claimed = skipAdded ? null : new oc.TopTools_MapOfShape();
72
80
  const isOfType = (raw) => raw.ShapeType() === type;
73
81
  for (const input of inputs) {
74
82
  const inputRaws = Explorer.findShapes(input.getShape(), type);
75
83
  for (const inputRaw of inputRaws) {
76
84
  const modifiedRaws = ShapeOps.shapeListToArray(maker.Modified(inputRaw)).filter(isOfType);
77
85
  const generatedRaws = ShapeOps.shapeListToArray(maker.Generated(inputRaw)).filter(isOfType);
78
- const isDeleted = maker.IsDeleted(inputRaw);
79
86
  if (modifiedRaws.length > 0) {
80
87
  modified.push({
81
88
  sources: [wrap(inputRaw)],
82
89
  results: modifiedRaws.map(wrap),
83
90
  });
84
- for (const r of modifiedRaws) {
85
- claimed.Add(r);
91
+ if (claimed) {
92
+ for (const r of modifiedRaws) {
93
+ claimed.Add(r);
94
+ }
86
95
  }
87
96
  }
88
97
  if (generatedRaws.length > 0) {
@@ -90,25 +99,30 @@ export class ShapeHistoryTracker {
90
99
  sources: [wrap(inputRaw)],
91
100
  results: generatedRaws.map(wrap),
92
101
  });
93
- for (const r of generatedRaws) {
94
- claimed.Add(r);
102
+ if (claimed) {
103
+ for (const r of generatedRaws) {
104
+ claimed.Add(r);
105
+ }
95
106
  }
96
107
  }
97
- // An input that is deleted with no successor is a removal. If it was
98
- // Modified into something, the modification record already captures
99
- // its fate — don't double-count as removed.
100
- if (isDeleted && modifiedRaws.length === 0 && generatedRaws.length === 0) {
108
+ // IsDeleted is only meaningful when the input has no successor. If
109
+ // Modified or Generated produced anything, that record already
110
+ // captures its fate — skip the (relatively expensive) IsDeleted call.
111
+ if (modifiedRaws.length === 0 && generatedRaws.length === 0
112
+ && maker.IsDeleted(inputRaw)) {
101
113
  removed.push(wrap(inputRaw));
102
114
  }
103
115
  }
104
116
  }
105
117
  const added = [];
106
- for (const raw of outputRaws) {
107
- if (!claimed.Contains(raw)) {
108
- added.push(wrap(raw));
118
+ if (claimed) {
119
+ for (const raw of outputRaws) {
120
+ if (!claimed.Contains(raw)) {
121
+ added.push(wrap(raw));
122
+ }
109
123
  }
124
+ claimed.delete();
110
125
  }
111
- claimed.delete();
112
126
  return { added, modified, generated, removed };
113
127
  }
114
128
  }
@@ -10,19 +10,19 @@ interface ALineFunction {
10
10
  (length: number, angle: number, centered?: boolean): IGeometry;
11
11
  /**
12
12
  * Draws a line at the given angle on a specific plane.
13
+ * @param targetPlane - The plane to draw on
13
14
  * @param length - The line length
14
15
  * @param angle - The angle in degrees
15
- * @param targetPlane - The plane to draw on
16
16
  */
17
- (length: number, angle: number, targetPlane: PlaneLike | ISceneObject): IGeometry;
17
+ (targetPlane: PlaneLike | ISceneObject, length: number, angle: number): IGeometry;
18
18
  /**
19
19
  * Draws a centered line at the given angle on a specific plane.
20
+ * @param targetPlane - The plane to draw on
20
21
  * @param length - The line length
21
22
  * @param angle - The angle in degrees
22
23
  * @param centered - Whether to center the line on the current position
23
- * @param targetPlane - The plane to draw on
24
24
  */
25
- (length: number, angle: number, centered: boolean, targetPlane: PlaneLike | ISceneObject): IGeometry;
25
+ (targetPlane: PlaneLike | ISceneObject, length: number, angle: number, centered: boolean): IGeometry;
26
26
  }
27
27
  declare const _default: ALineFunction;
28
28
  export default _default;
@@ -6,18 +6,22 @@ import { resolvePlane } from "../../helpers/resolve.js";
6
6
  function build(context) {
7
7
  return function line() {
8
8
  let planeObj = null;
9
- let argCount = arguments.length;
10
- // Detect plane as last argument
11
- if (argCount > 0) {
12
- const lastArg = arguments[argCount - 1];
13
- if (isPlaneLike(lastArg) || lastArg instanceof SceneObject) {
14
- planeObj = resolvePlane(lastArg, context);
15
- argCount--;
9
+ let argOffset = 0;
10
+ // Detect plane as first argument (only valid outside a sketch)
11
+ if (arguments.length > 0) {
12
+ const firstArg = arguments[0];
13
+ if (isPlaneLike(firstArg) || firstArg instanceof SceneObject) {
14
+ if (context.getActiveSketch() !== null) {
15
+ throw new Error("aLine(plane, ...) cannot be used inside a sketch. Use aLine(...) instead.");
16
+ }
17
+ planeObj = resolvePlane(firstArg, context);
18
+ argOffset = 1;
16
19
  }
17
20
  }
18
- const length = arguments[0];
19
- const angle = arguments[1];
20
- const centered = argCount >= 3 ? arguments[2] : false;
21
+ const argCount = arguments.length - argOffset;
22
+ const length = arguments[argOffset];
23
+ const angle = arguments[argOffset + 1];
24
+ const centered = argCount >= 3 ? arguments[argOffset + 2] : false;
21
25
  const aline = new AngledLine(length, angle, centered, planeObj);
22
26
  context.addSceneObject(aline);
23
27
  return aline;
@@ -26,25 +26,25 @@ interface ArcFunction {
26
26
  (radius: number, startAngle?: number, endAngle?: number): IArcAngles;
27
27
  /**
28
28
  * Draws an arc to an end point on a specific plane.
29
- * @param endPoint - The end point of the arc
30
29
  * @param targetPlane - The plane to draw on
30
+ * @param endPoint - The end point of the arc
31
31
  */
32
- (endPoint: Point2DLike, targetPlane: PlaneLike | ISceneObject): IArcPoints;
32
+ (targetPlane: PlaneLike | ISceneObject, endPoint: Point2DLike): IArcPoints;
33
33
  /**
34
34
  * Draws an arc between two points on a specific plane.
35
+ * @param targetPlane - The plane to draw on
35
36
  * @param startPoint - The start point of the arc
36
37
  * @param endPoint - The end point of the arc
37
- * @param targetPlane - The plane to draw on
38
38
  */
39
- (startPoint: Point2DLike, endPoint: Point2DLike, targetPlane: PlaneLike | ISceneObject): IArcPoints;
39
+ (targetPlane: PlaneLike | ISceneObject, startPoint: Point2DLike, endPoint: Point2DLike): IArcPoints;
40
40
  /**
41
41
  * Draws an arc by radius and angle range on a specific plane.
42
+ * @param targetPlane - The plane to draw on
42
43
  * @param radius - The arc radius
43
44
  * @param startAngle - The start angle in degrees
44
45
  * @param endAngle - The end angle in degrees
45
- * @param targetPlane - The plane to draw on
46
46
  */
47
- (radius: number, startAngle: number, endAngle: number, targetPlane: PlaneLike | ISceneObject): IArcAngles;
47
+ (targetPlane: PlaneLike | ISceneObject, radius: number, startAngle: number, endAngle: number): IArcAngles;
48
48
  }
49
49
  declare const _default: ArcFunction;
50
50
  export default _default;
@@ -8,34 +8,38 @@ import { resolvePlane } from "../../helpers/resolve.js";
8
8
  function build(context) {
9
9
  return function arc() {
10
10
  let planeObj = null;
11
- let argCount = arguments.length;
12
- // Detect plane as last argument
13
- if (argCount > 0) {
14
- const lastArg = arguments[argCount - 1];
15
- if (isPlaneLike(lastArg) || (lastArg instanceof SceneObject && !isPoint2DLike(lastArg))) {
16
- planeObj = resolvePlane(lastArg, context);
17
- argCount--;
11
+ let argOffset = 0;
12
+ // Detect plane as first argument (only valid outside a sketch)
13
+ if (arguments.length > 0) {
14
+ const firstArg = arguments[0];
15
+ if (isPlaneLike(firstArg) || (firstArg instanceof SceneObject && !isPoint2DLike(firstArg))) {
16
+ if (context.getActiveSketch() !== null) {
17
+ throw new Error("arc(plane, ...) cannot be used inside a sketch. Use arc(...) instead.");
18
+ }
19
+ planeObj = resolvePlane(firstArg, context);
20
+ argOffset = 1;
18
21
  }
19
22
  }
23
+ const argCount = arguments.length - argOffset;
20
24
  // (startPoint, endPoint) — two Point2DLike args, default center = current position
21
- if (argCount >= 2 && isPoint2DLike(arguments[0]) && isPoint2DLike(arguments[1])) {
22
- const start = normalizePoint2D(arguments[0]);
23
- const end = normalizePoint2D(arguments[1]);
25
+ if (argCount >= 2 && isPoint2DLike(arguments[argOffset]) && isPoint2DLike(arguments[argOffset + 1])) {
26
+ const start = normalizePoint2D(arguments[argOffset]);
27
+ const end = normalizePoint2D(arguments[argOffset + 1]);
24
28
  const arcObj = Arc.twoPoints(start, end, planeObj);
25
29
  context.addSceneObject(arcObj);
26
30
  return arcObj;
27
31
  }
28
32
  // (endPoint) — single Point2DLike arg
29
- if (isPoint2DLike(arguments[0])) {
30
- const end = normalizePoint2D(arguments[0]);
33
+ if (isPoint2DLike(arguments[argOffset])) {
34
+ const end = normalizePoint2D(arguments[argOffset]);
31
35
  const arcObj = Arc.toPoint(end, planeObj);
32
36
  context.addSceneObject(arcObj);
33
37
  return arcObj;
34
38
  }
35
39
  // (radius, startAngle?, endAngle?) — all numeric args
36
- const radius = arguments[0] || 100;
37
- const startAngle = arguments[1] || 0;
38
- const endAngle = argCount >= 3 ? arguments[2] : 180;
40
+ const radius = arguments[argOffset] || 100;
41
+ const startAngle = arguments[argOffset + 1] || 0;
42
+ const endAngle = argCount >= 3 ? arguments[argOffset + 2] : 180;
39
43
  const arcObj = Arc.fromAngles(radius, startAngle, endAngle, planeObj);
40
44
  context.addSceneObject(arcObj);
41
45
  return arcObj;
@@ -15,10 +15,10 @@ interface CircleFunction {
15
15
  (diameter?: number): IExtrudableGeometry;
16
16
  /**
17
17
  * Draws a circle with a given diameter on a specific plane.
18
- * @param diameter - The circle diameter
19
18
  * @param targetPlane - The plane to draw on
19
+ * @param diameter - The circle diameter
20
20
  */
21
- (diameter: number, targetPlane: PlaneLike | ISceneObject): IExtrudableGeometry;
21
+ (targetPlane: PlaneLike | ISceneObject, diameter: number): IExtrudableGeometry;
22
22
  }
23
23
  declare const _default: CircleFunction;
24
24
  export default _default;
@@ -12,28 +12,32 @@ function build(context) {
12
12
  let center;
13
13
  let circle;
14
14
  let planeObj = null;
15
- let argCount = arguments.length;
16
- // Detect plane as last argument
17
- if (argCount > 0) {
18
- const lastArg = arguments[argCount - 1];
19
- if (isPlaneLike(lastArg) || (lastArg instanceof SceneObject && !isPoint2DLike(lastArg))) {
20
- planeObj = resolvePlane(lastArg, context);
21
- argCount--;
15
+ let argOffset = 0;
16
+ // Detect plane as first argument (only valid outside a sketch)
17
+ if (arguments.length > 0) {
18
+ const firstArg = arguments[0];
19
+ if (isPlaneLike(firstArg) || (firstArg instanceof SceneObject && !isPoint2DLike(firstArg))) {
20
+ if (context.getActiveSketch() !== null) {
21
+ throw new Error("circle(plane, ...) cannot be used inside a sketch. Use circle(...) instead.");
22
+ }
23
+ planeObj = resolvePlane(firstArg, context);
24
+ argOffset = 1;
22
25
  }
23
26
  }
27
+ const argCount = arguments.length - argOffset;
24
28
  if (argCount === 0) {
25
29
  diameter = 40;
26
30
  circle = new Circle(diameter, null, planeObj);
27
31
  context.addSceneObject(circle);
28
32
  }
29
33
  else if (argCount === 1) {
30
- diameter = arguments[0] || 40;
34
+ diameter = arguments[argOffset] || 40;
31
35
  circle = new Circle(diameter, null, planeObj);
32
36
  context.addSceneObject(circle);
33
37
  }
34
38
  else {
35
- center = normalizePoint2D(arguments[0]);
36
- diameter = arguments[1] || 40;
39
+ center = normalizePoint2D(arguments[argOffset]);
40
+ diameter = arguments[argOffset + 1] || 40;
37
41
  circle = new Circle(diameter, null, planeObj);
38
42
  const move = new Move(center);
39
43
  context.addSceneObjects([move, circle]);
@@ -0,0 +1,35 @@
1
+ import { Point2DLike } from "../../math/point.js";
2
+ import { PlaneLike } from "../../math/plane.js";
3
+ import { IExtrudableGeometry, ISceneObject } from "../interfaces.js";
4
+ interface EllipseFunction {
5
+ /**
6
+ * Draws an ellipse at the current position.
7
+ * @param rx - Semi-radius along the plane's X axis
8
+ * @param ry - Semi-radius along the plane's Y axis
9
+ */
10
+ (rx: number, ry: number): IExtrudableGeometry;
11
+ /**
12
+ * Draws an ellipse at a given center.
13
+ * @param center - The center point
14
+ * @param rx - Semi-radius along the plane's X axis
15
+ * @param ry - Semi-radius along the plane's Y axis
16
+ */
17
+ (center: Point2DLike, rx: number, ry: number): IExtrudableGeometry;
18
+ /**
19
+ * Draws an ellipse on a specific plane.
20
+ * @param targetPlane - The plane to draw on
21
+ * @param rx - Semi-radius along the plane's X axis
22
+ * @param ry - Semi-radius along the plane's Y axis
23
+ */
24
+ (targetPlane: PlaneLike | ISceneObject, rx: number, ry: number): IExtrudableGeometry;
25
+ /**
26
+ * Draws an ellipse at a given center on a specific plane.
27
+ * @param targetPlane - The plane to draw on
28
+ * @param center - The center point in plane-local coordinates
29
+ * @param rx - Semi-radius along the plane's X axis
30
+ * @param ry - Semi-radius along the plane's Y axis
31
+ */
32
+ (targetPlane: PlaneLike | ISceneObject, center: Point2DLike, rx: number, ry: number): IExtrudableGeometry;
33
+ }
34
+ declare const _default: EllipseFunction;
35
+ export default _default;
@@ -0,0 +1,65 @@
1
+ import { Point2D, isPoint2DLike } from "../../math/point.js";
2
+ import { LazyVertex } from "../../features/lazy-vertex.js";
3
+ import { Ellipse } from "../../features/2d/ellipse.js";
4
+ import { Move } from "../../features/2d/move.js";
5
+ import { normalizePoint2D } from "../../helpers/normalize.js";
6
+ import { registerBuilder } from "../../index.js";
7
+ import { isPlaneLike } from "../../math/plane.js";
8
+ import { SceneObject } from "../../common/scene-object.js";
9
+ import { resolvePlane } from "../../helpers/resolve.js";
10
+ function toPoint2D(p) {
11
+ if (p instanceof Point2D) {
12
+ return p;
13
+ }
14
+ if (p instanceof LazyVertex) {
15
+ return p.asPoint2D();
16
+ }
17
+ if (Array.isArray(p)) {
18
+ return new Point2D(p[0], p[1]);
19
+ }
20
+ return new Point2D(p.x, p.y);
21
+ }
22
+ function build(context) {
23
+ return function ellipse() {
24
+ let planeObj = null;
25
+ let argOffset = 0;
26
+ if (arguments.length > 0) {
27
+ const firstArg = arguments[0];
28
+ if (isPlaneLike(firstArg) || (firstArg instanceof SceneObject && !isPoint2DLike(firstArg))) {
29
+ if (context.getActiveSketch() !== null) {
30
+ throw new Error("ellipse(plane, ...) cannot be used inside a sketch. Use ellipse(...) instead.");
31
+ }
32
+ planeObj = resolvePlane(firstArg, context);
33
+ argOffset = 1;
34
+ }
35
+ }
36
+ const argCount = arguments.length - argOffset;
37
+ if (argCount === 2) {
38
+ const rx = arguments[argOffset];
39
+ const ry = arguments[argOffset + 1];
40
+ const e = new Ellipse(rx, ry, planeObj);
41
+ context.addSceneObject(e);
42
+ return e;
43
+ }
44
+ if (argCount === 3) {
45
+ const centerArg = arguments[argOffset];
46
+ const rx = arguments[argOffset + 1];
47
+ const ry = arguments[argOffset + 2];
48
+ if (planeObj) {
49
+ // Standalone (plane, center, rx, ry): center is plane-local. Move
50
+ // can't run outside a sketch, so resolve the center eagerly into the
51
+ // Ellipse via centerOverride.
52
+ const center = toPoint2D(centerArg);
53
+ const e = new Ellipse(rx, ry, planeObj, center);
54
+ context.addSceneObject(e);
55
+ return e;
56
+ }
57
+ const center = normalizePoint2D(centerArg);
58
+ const e = new Ellipse(rx, ry, null);
59
+ context.addSceneObjects([new Move(center), e]);
60
+ return e;
61
+ }
62
+ throw new Error("Invalid arguments for ellipse()");
63
+ };
64
+ }
65
+ export default registerBuilder(build);
@@ -17,17 +17,17 @@ interface HLineFunction {
17
17
  (start: Point2DLike, distance: number, centered?: boolean): IGeometry;
18
18
  /**
19
19
  * Draws a horizontal line on a specific plane.
20
- * @param distance - The line length
21
20
  * @param targetPlane - The plane to draw on
21
+ * @param distance - The line length
22
22
  */
23
- (distance: number, targetPlane: PlaneLike | ISceneObject): IGeometry;
23
+ (targetPlane: PlaneLike | ISceneObject, distance: number): IGeometry;
24
24
  /**
25
25
  * Draws a horizontal line with centering on a specific plane.
26
+ * @param targetPlane - The plane to draw on
26
27
  * @param distance - The line length
27
28
  * @param centered - Whether to center the line on the current position
28
- * @param targetPlane - The plane to draw on
29
29
  */
30
- (distance: number, centered: boolean, targetPlane: PlaneLike | ISceneObject): IGeometry;
30
+ (targetPlane: PlaneLike | ISceneObject, distance: number, centered: boolean): IGeometry;
31
31
  }
32
32
  declare const _default: HLineFunction;
33
33
  export default _default;
@@ -9,16 +9,20 @@ import { resolvePlane } from "../../helpers/resolve.js";
9
9
  function build(context) {
10
10
  return function line() {
11
11
  let planeObj = null;
12
- let argCount = arguments.length;
13
- // Detect plane as last argument
14
- if (argCount > 0) {
15
- const lastArg = arguments[argCount - 1];
16
- if (isPlaneLike(lastArg) || (lastArg instanceof SceneObject && !isPoint2DLike(lastArg))) {
17
- planeObj = resolvePlane(lastArg, context);
18
- argCount--;
12
+ let argOffset = 0;
13
+ // Detect plane as first argument (only valid outside a sketch)
14
+ if (arguments.length > 0) {
15
+ const firstArg = arguments[0];
16
+ if (isPlaneLike(firstArg) || (firstArg instanceof SceneObject && !isPoint2DLike(firstArg))) {
17
+ if (context.getActiveSketch() !== null) {
18
+ throw new Error("hLine(plane, ...) cannot be used inside a sketch. Use hLine(...) instead.");
19
+ }
20
+ planeObj = resolvePlane(firstArg, context);
21
+ argOffset = 1;
19
22
  }
20
23
  }
21
- if (typeof arguments[0] !== 'number') {
24
+ const argCount = arguments.length - argOffset;
25
+ if (argOffset === 0 && typeof arguments[0] !== 'number') {
22
26
  // hline(start, distance) or hline(start, distance, centered)
23
27
  const start = normalizePoint2D(arguments[0]);
24
28
  const distance = arguments[1];
@@ -27,8 +31,8 @@ function build(context) {
27
31
  context.addSceneObjects([new Move(start), hline]);
28
32
  return hline;
29
33
  }
30
- const distance = arguments[0];
31
- const centered = argCount >= 2 ? arguments[1] : false;
34
+ const distance = arguments[argOffset];
35
+ const centered = argCount >= 2 ? arguments[argOffset + 1] : false;
32
36
  const hline = new HorizontalLine(distance, centered, planeObj);
33
37
  context.addSceneObject(hline);
34
38
  return hline;
@@ -1,5 +1,6 @@
1
1
  export { default as line } from './line.js';
2
2
  export { default as circle } from './circle.js';
3
+ export { default as ellipse } from './ellipse.js';
3
4
  export { default as rect } from './rect.js';
4
5
  export { default as hMove } from './hmove.js';
5
6
  export { default as vMove } from './vmove.js';
@@ -1,5 +1,6 @@
1
1
  export { default as line } from './line.js';
2
2
  export { default as circle } from './circle.js';
3
+ export { default as ellipse } from './ellipse.js';
3
4
  export { default as rect } from './rect.js';
4
5
  export { default as hMove } from './hmove.js';
5
6
  export { default as vMove } from './vmove.js';
@@ -8,10 +8,10 @@ interface IntersectFunction {
8
8
  (...sourceObjects: ISceneObject[]): IExtrudableGeometry;
9
9
  /**
10
10
  * Intersects 3D objects with a target plane, producing cross-section edges.
11
- * @param sourceObjects - The 3D objects to intersect
12
11
  * @param targetPlane - The plane to intersect with
12
+ * @param sourceObjects - The 3D objects to intersect
13
13
  */
14
- (sourceObjects: ISceneObject[], targetPlane: PlaneLike | ISceneObject): IExtrudableGeometry;
14
+ (targetPlane: PlaneLike | ISceneObject, sourceObjects: ISceneObject[]): IExtrudableGeometry;
15
15
  }
16
16
  declare const _default: IntersectFunction;
17
17
  export default _default;
@@ -3,10 +3,14 @@ import { resolvePlane } from "../../helpers/resolve.js";
3
3
  import { registerBuilder } from "../../index.js";
4
4
  function build(context) {
5
5
  return function intersect(...args) {
6
- if (Array.isArray(args[0])) {
7
- const sourceObjects = args[0];
6
+ // Plane-first mode: intersect(plane, sources[])
7
+ if (args.length === 2 && Array.isArray(args[1])) {
8
+ if (context.getActiveSketch() !== null) {
9
+ throw new Error("intersect(plane, sources[]) cannot be used inside a sketch. Use intersect(...sources) instead.");
10
+ }
11
+ const planeObj = resolvePlane(args[0], context);
12
+ const sourceObjects = args[1];
8
13
  context.addSceneObjects(sourceObjects);
9
- const planeObj = resolvePlane(args[1], context);
10
14
  const result = new Intersect(sourceObjects, planeObj);
11
15
  context.addSceneObject(result);
12
16
  return result;
@@ -15,10 +15,10 @@ interface LineFunction {
15
15
  (start: Point2DLike, end: Point2DLike): IGeometry;
16
16
  /**
17
17
  * Draws a line to the given point on a specific plane.
18
- * @param end - The end point
19
18
  * @param targetPlane - The plane to draw on
19
+ * @param end - The end point
20
20
  */
21
- (end: Point2DLike, targetPlane: PlaneLike | ISceneObject): IGeometry;
21
+ (targetPlane: PlaneLike | ISceneObject, end: Point2DLike): IGeometry;
22
22
  }
23
23
  declare const _default: LineFunction;
24
24
  export default _default;