fluidcad 0.0.34 → 0.0.36

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 (176) hide show
  1. package/README.md +69 -0
  2. package/bin/commands/login.js +148 -0
  3. package/bin/commands/mcp.js +3 -2
  4. package/bin/commands/pack.js +49 -0
  5. package/bin/commands/publish.js +231 -0
  6. package/bin/fluidcad.js +6 -0
  7. package/bin/lib/api-client.js +48 -0
  8. package/bin/lib/browser.js +16 -0
  9. package/bin/lib/config.js +39 -0
  10. package/bin/lib/model-config.js +61 -0
  11. package/bin/lib/prompt.js +97 -0
  12. package/bin/lib/workspace.js +57 -0
  13. package/lib/dist/common/shape-factory.d.ts +2 -1
  14. package/lib/dist/common/shape-factory.js +4 -0
  15. package/lib/dist/common/transformable-primitive.d.ts +6 -5
  16. package/lib/dist/common/transformable-primitive.js +8 -7
  17. package/lib/dist/common/vertex.js +0 -1
  18. package/lib/dist/core/2d/aline.d.ts +4 -3
  19. package/lib/dist/core/2d/aline.js +3 -2
  20. package/lib/dist/core/2d/arc.d.ts +3 -2
  21. package/lib/dist/core/2d/arc.js +4 -3
  22. package/lib/dist/core/2d/bezier.d.ts +8 -6
  23. package/lib/dist/core/2d/circle.d.ts +4 -3
  24. package/lib/dist/core/2d/circle.js +3 -2
  25. package/lib/dist/core/2d/ellipse.d.ts +5 -4
  26. package/lib/dist/core/2d/ellipse.js +5 -4
  27. package/lib/dist/core/2d/hline.d.ts +4 -3
  28. package/lib/dist/core/2d/hline.js +5 -3
  29. package/lib/dist/core/2d/line.js +1 -0
  30. package/lib/dist/core/2d/offset.d.ts +3 -2
  31. package/lib/dist/core/2d/offset.js +6 -5
  32. package/lib/dist/core/2d/polygon.d.ts +5 -4
  33. package/lib/dist/core/2d/polygon.js +10 -9
  34. package/lib/dist/core/2d/rect.d.ts +4 -3
  35. package/lib/dist/core/2d/rect.js +10 -9
  36. package/lib/dist/core/2d/slot.d.ts +14 -6
  37. package/lib/dist/core/2d/slot.js +19 -8
  38. package/lib/dist/core/2d/vline.d.ts +4 -3
  39. package/lib/dist/core/2d/vline.js +5 -3
  40. package/lib/dist/core/chamfer.d.ts +5 -4
  41. package/lib/dist/core/chamfer.js +7 -6
  42. package/lib/dist/core/color.d.ts +3 -2
  43. package/lib/dist/core/color.js +2 -1
  44. package/lib/dist/core/cut.d.ts +4 -3
  45. package/lib/dist/core/cut.js +5 -4
  46. package/lib/dist/core/cylinder.d.ts +2 -1
  47. package/lib/dist/core/cylinder.js +2 -1
  48. package/lib/dist/core/draft.d.ts +3 -2
  49. package/lib/dist/core/draft.js +3 -2
  50. package/lib/dist/core/extrude.d.ts +4 -3
  51. package/lib/dist/core/extrude.js +5 -4
  52. package/lib/dist/core/fillet.d.ts +5 -4
  53. package/lib/dist/core/fillet.js +6 -5
  54. package/lib/dist/core/index.d.ts +1 -0
  55. package/lib/dist/core/index.js +1 -0
  56. package/lib/dist/core/interfaces.d.ts +25 -24
  57. package/lib/dist/core/param.d.ts +74 -0
  58. package/lib/dist/core/param.js +147 -0
  59. package/lib/dist/core/repeat.d.ts +2 -1
  60. package/lib/dist/core/repeat.js +10 -8
  61. package/lib/dist/core/revolve.d.ts +2 -1
  62. package/lib/dist/core/revolve.js +3 -2
  63. package/lib/dist/core/rib.d.ts +3 -2
  64. package/lib/dist/core/rib.js +6 -2
  65. package/lib/dist/core/rotate.d.ts +5 -4
  66. package/lib/dist/core/rotate.js +4 -3
  67. package/lib/dist/core/shell.d.ts +3 -2
  68. package/lib/dist/core/shell.js +3 -2
  69. package/lib/dist/core/sphere.d.ts +3 -2
  70. package/lib/dist/core/sphere.js +2 -1
  71. package/lib/dist/core/translate.d.ts +7 -6
  72. package/lib/dist/core/translate.js +6 -5
  73. package/lib/dist/features/2d/arc.js +5 -5
  74. package/lib/dist/features/2d/bezier.js +16 -16
  75. package/lib/dist/features/2d/circle.js +4 -0
  76. package/lib/dist/features/2d/ellipse.js +4 -0
  77. package/lib/dist/features/2d/hline.d.ts +3 -0
  78. package/lib/dist/features/2d/hline.js +9 -2
  79. package/lib/dist/features/2d/line.d.ts +3 -0
  80. package/lib/dist/features/2d/line.js +11 -3
  81. package/lib/dist/features/2d/sketch.js +5 -1
  82. package/lib/dist/features/2d/slot.d.ts +5 -0
  83. package/lib/dist/features/2d/slot.js +52 -7
  84. package/lib/dist/features/2d/tarc-to-point-tangent.js +3 -0
  85. package/lib/dist/features/2d/tarc-to-point.js +3 -0
  86. package/lib/dist/features/2d/tarc-with-tangent.js +3 -0
  87. package/lib/dist/features/2d/tarc.js +3 -0
  88. package/lib/dist/features/2d/vline.d.ts +3 -0
  89. package/lib/dist/features/2d/vline.js +9 -2
  90. package/lib/dist/features/copy-circular.d.ts +4 -3
  91. package/lib/dist/features/copy-circular.js +16 -9
  92. package/lib/dist/features/copy-circular2d.js +16 -9
  93. package/lib/dist/features/copy-linear.d.ts +4 -3
  94. package/lib/dist/features/copy-linear.js +18 -12
  95. package/lib/dist/features/copy-linear2d.js +18 -12
  96. package/lib/dist/features/extrude-base.d.ts +4 -3
  97. package/lib/dist/features/extrude-base.js +10 -3
  98. package/lib/dist/features/mirror-shape2d.js +2 -2
  99. package/lib/dist/features/repeat-base.d.ts +13 -0
  100. package/lib/dist/features/repeat-base.js +21 -0
  101. package/lib/dist/features/repeat-circular.d.ts +6 -5
  102. package/lib/dist/features/repeat-circular.js +3 -6
  103. package/lib/dist/features/repeat-linear.d.ts +7 -7
  104. package/lib/dist/features/repeat-linear.js +3 -6
  105. package/lib/dist/index.d.ts +5 -0
  106. package/lib/dist/index.js +8 -1
  107. package/lib/dist/io/file-import.d.ts +7 -0
  108. package/lib/dist/io/file-import.js +30 -10
  109. package/lib/dist/math/lazy-matrix.d.ts +5 -0
  110. package/lib/dist/math/lazy-matrix.js +78 -10
  111. package/lib/dist/oc/boolean-ops.d.ts +2 -2
  112. package/lib/dist/param-registry.d.ts +34 -0
  113. package/lib/dist/param-registry.js +60 -0
  114. package/lib/dist/rendering/mesh-builder.js +2 -1
  115. package/lib/dist/tests/features/copy-circular.test.js +1 -1
  116. package/lib/dist/tests/features/copy-linear.test.js +10 -10
  117. package/lib/dist/tests/features/repeat-user-repro-cache.test.d.ts +1 -0
  118. package/lib/dist/tests/features/repeat-user-repro-cache.test.js +97 -0
  119. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  120. package/llm-docs/api/bezier.md +10 -11
  121. package/llm-docs/api/index.json +1 -1
  122. package/llm-docs/api/types/arc-points.md +2 -2
  123. package/llm-docs/api/types/cut.md +10 -10
  124. package/llm-docs/api/types/extrude.md +10 -10
  125. package/llm-docs/api/types/loft.md +6 -6
  126. package/llm-docs/api/types/revolve.md +6 -6
  127. package/llm-docs/api/types/rib.md +2 -2
  128. package/llm-docs/api/types/slot.md +2 -2
  129. package/llm-docs/api/types/sweep.md +10 -10
  130. package/llm-docs/api/types/transformable.md +14 -14
  131. package/llm-docs/index.json +12 -12
  132. package/mcp/dist/client.d.ts +1 -0
  133. package/mcp/dist/client.js +8 -1
  134. package/mcp/dist/server.js +14 -1
  135. package/mcp/dist/tools/engine.d.ts +16 -0
  136. package/mcp/dist/tools/engine.js +45 -0
  137. package/package.json +9 -3
  138. package/server/dist/api.d.ts +37 -0
  139. package/server/dist/api.js +44 -0
  140. package/server/dist/code-editor.d.ts +64 -0
  141. package/server/dist/code-editor.js +520 -2
  142. package/server/dist/fluidcad-server.d.ts +87 -1
  143. package/server/dist/fluidcad-server.js +254 -88
  144. package/server/dist/host/blocked-imports.d.ts +8 -0
  145. package/server/dist/host/blocked-imports.js +30 -0
  146. package/server/dist/{vite-manager.d.ts → host/local-scene-host.d.ts} +3 -1
  147. package/server/dist/{vite-manager.js → host/local-scene-host.js} +6 -26
  148. package/server/dist/host/scene-host.d.ts +19 -0
  149. package/server/dist/host/scene-host.js +1 -0
  150. package/server/dist/index.js +24 -117
  151. package/server/dist/model-package/capture-params.d.ts +19 -0
  152. package/server/dist/model-package/capture-params.js +42 -0
  153. package/server/dist/model-package/pack.d.ts +23 -0
  154. package/server/dist/model-package/pack.js +230 -0
  155. package/server/dist/model-package/types.d.ts +79 -0
  156. package/server/dist/model-package/types.js +17 -0
  157. package/server/dist/routes/hit-test.d.ts +3 -0
  158. package/server/dist/routes/hit-test.js +17 -0
  159. package/server/dist/routes/pack.d.ts +10 -0
  160. package/server/dist/routes/pack.js +47 -0
  161. package/server/dist/routes/params.d.ts +3 -0
  162. package/server/dist/routes/params.js +75 -0
  163. package/server/dist/routes/sketch-edits.d.ts +3 -0
  164. package/server/dist/routes/sketch-edits.js +542 -0
  165. package/server/dist/routes/timeline.d.ts +3 -0
  166. package/server/dist/routes/timeline.js +49 -0
  167. package/server/dist/server-core.d.ts +53 -0
  168. package/server/dist/server-core.js +147 -0
  169. package/server/dist/ws-protocol.d.ts +101 -2
  170. package/ui/dist/assets/index-CDJmUpFI.css +2 -0
  171. package/ui/dist/assets/index-MRqwG9Vh.js +5417 -0
  172. package/ui/dist/index.html +2 -2
  173. package/server/dist/routes/actions.d.ts +0 -3
  174. package/server/dist/routes/actions.js +0 -309
  175. package/ui/dist/assets/index-BdqrMDRu.js +0 -4946
  176. package/ui/dist/assets/index-DR7c2Qk9.css +0 -2
@@ -1,5 +1,6 @@
1
1
  import { Point2D } from "../../math/point.js";
2
2
  import { Geometry } from "../../oc/geometry.js";
3
+ import { Vertex } from "../../common/vertex.js";
3
4
  import { rad } from "../../helpers/math-helpers.js";
4
5
  import { ExtrudableGeometryBase } from "./extrudable-base.js";
5
6
  export class Slot extends ExtrudableGeometryBase {
@@ -7,11 +8,19 @@ export class Slot extends ExtrudableGeometryBase {
7
8
  radius;
8
9
  _center = false;
9
10
  _angle = 0;
11
+ _startPoint = null;
12
+ _endPoint = null;
10
13
  constructor(distance, radius, targetPlane = null) {
11
14
  super(targetPlane);
12
15
  this.distance = distance;
13
16
  this.radius = radius;
14
17
  }
18
+ static fromTwoPoints(start, end, radius, targetPlane = null) {
19
+ const slot = new Slot(0, radius, targetPlane);
20
+ slot._startPoint = start;
21
+ slot._endPoint = end;
22
+ return slot;
23
+ }
15
24
  centered(value = true) {
16
25
  this._center = value;
17
26
  return this;
@@ -21,21 +30,28 @@ export class Slot extends ExtrudableGeometryBase {
21
30
  return this;
22
31
  }
23
32
  build() {
24
- if (this.distance < 0) {
25
- throw new Error("Slot distance must be positive");
33
+ if (this._startPoint && this._endPoint) {
34
+ const s = this._startPoint.asPoint2D();
35
+ const e = this._endPoint.asPoint2D();
36
+ const dx = e.x - s.x;
37
+ const dy = e.y - s.y;
38
+ this.distance = Math.sqrt(dx * dx + dy * dy);
39
+ this._angle = Math.atan2(dy, dx) * 180 / Math.PI;
26
40
  }
41
+ const absDistance = Math.abs(this.distance);
42
+ const flipAngle = this.distance < 0 ? 180 : 0;
27
43
  const plane = this.targetPlane?.getPlane() || this.getParent().getPlane();
28
44
  const localToWorld = plane.localToWorld.bind(plane);
29
45
  let leftCenter = this.targetPlane
30
46
  ? plane.worldToLocal(this.targetPlane.getPlaneCenter())
31
47
  : this.getCurrentPosition();
32
48
  if (this._center) {
33
- const angleRad = rad(this._angle);
49
+ const angleRad = rad(this._angle + flipAngle);
34
50
  const cos = Math.cos(angleRad);
35
51
  const sin = Math.sin(angleRad);
36
- leftCenter = leftCenter.translate(-this.distance / 2 * cos, -this.distance / 2 * sin);
52
+ leftCenter = leftCenter.translate(-absDistance / 2 * cos, -absDistance / 2 * sin);
37
53
  }
38
- const angleRad = rad(this._angle);
54
+ const angleRad = rad(this._angle + flipAngle);
39
55
  const cos = Math.cos(angleRad);
40
56
  const sin = Math.sin(angleRad);
41
57
  // Direction along the slot axis
@@ -44,7 +60,7 @@ export class Slot extends ExtrudableGeometryBase {
44
60
  // Perpendicular direction (90 degrees CCW)
45
61
  const perpX = -sin;
46
62
  const perpY = cos;
47
- const rightCenter = new Point2D(leftCenter.x + this.distance * dirX, leftCenter.y + this.distance * dirY);
63
+ const rightCenter = new Point2D(leftCenter.x + absDistance * dirX, leftCenter.y + absDistance * dirY);
48
64
  // Four key points where lines meet arcs
49
65
  const topLeft = new Point2D(leftCenter.x + this.radius * perpX, leftCenter.y + this.radius * perpY);
50
66
  const topRight = new Point2D(rightCenter.x + this.radius * perpX, rightCenter.y + this.radius * perpY);
@@ -65,6 +81,12 @@ export class Slot extends ExtrudableGeometryBase {
65
81
  Geometry.makeEdgeFromCurve(leftArc),
66
82
  ];
67
83
  this.addShapes(edges);
84
+ const leftCenterVertex = Vertex.fromPoint2D(leftCenter);
85
+ leftCenterVertex.markAsMetaShape();
86
+ this.addShape(leftCenterVertex);
87
+ const rightCenterVertex = Vertex.fromPoint2D(rightCenter);
88
+ rightCenterVertex.markAsMetaShape();
89
+ this.addShape(rightCenterVertex);
68
90
  if (this.sketch) {
69
91
  if (this._center) {
70
92
  this.setCurrentPosition(this.getCurrentPosition());
@@ -85,7 +107,13 @@ export class Slot extends ExtrudableGeometryBase {
85
107
  }
86
108
  createCopy(remap) {
87
109
  const targetPlane = this.targetPlane ? (remap.get(this.targetPlane) || this.targetPlane) : null;
88
- const s = new Slot(this.distance, this.radius, targetPlane);
110
+ let s;
111
+ if (this._startPoint && this._endPoint) {
112
+ s = Slot.fromTwoPoints(this._startPoint, this._endPoint, this.radius, targetPlane);
113
+ }
114
+ else {
115
+ s = new Slot(this.distance, this.radius, targetPlane);
116
+ }
89
117
  s.centered(this._center);
90
118
  s.rotate(this._angle);
91
119
  return s;
@@ -103,6 +131,22 @@ export class Slot extends ExtrudableGeometryBase {
103
131
  if (this.targetPlane && other.targetPlane && !this.targetPlane.compareTo(other.targetPlane)) {
104
132
  return false;
105
133
  }
134
+ if ((this._startPoint === null) !== (other._startPoint === null)) {
135
+ return false;
136
+ }
137
+ if ((this._endPoint === null) !== (other._endPoint === null)) {
138
+ return false;
139
+ }
140
+ if (this._startPoint && other._startPoint && !this._startPoint.compareTo(other._startPoint)) {
141
+ return false;
142
+ }
143
+ if (this._endPoint && other._endPoint && !this._endPoint.compareTo(other._endPoint)) {
144
+ return false;
145
+ }
146
+ if (this._startPoint && this._endPoint) {
147
+ return this.radius === other.radius &&
148
+ this._center === other._center;
149
+ }
106
150
  return this.distance === other.distance &&
107
151
  this.radius === other.radius &&
108
152
  this._center === other._center &&
@@ -114,6 +158,7 @@ export class Slot extends ExtrudableGeometryBase {
114
158
  radius: this.radius,
115
159
  centered: this._center,
116
160
  angle: this._angle,
161
+ hasTwoPoints: this._startPoint !== null && this._endPoint !== null,
117
162
  };
118
163
  }
119
164
  }
@@ -60,6 +60,9 @@ export class TangentArcToPointTangent extends GeometrySceneObject {
60
60
  this.setState('start', Vertex.fromPoint2D(startPoint));
61
61
  this.setState('end', Vertex.fromPoint2D(targetPoint));
62
62
  this.addShape(edge);
63
+ const centerVertex = Vertex.fromPoint2D(centerPoint);
64
+ centerVertex.markAsMetaShape();
65
+ this.addShape(centerVertex);
63
66
  this.setCurrentPosition(targetPoint);
64
67
  }
65
68
  createCopy(remap) {
@@ -58,6 +58,9 @@ export class TangentArcToPoint extends GeometrySceneObject {
58
58
  this.setState('start', Vertex.fromPoint2D(startPoint));
59
59
  this.setState('end', Vertex.fromPoint2D(targetPoint));
60
60
  this.addShape(edge);
61
+ const centerVertex = Vertex.fromPoint2D(centerPoint);
62
+ centerVertex.markAsMetaShape();
63
+ this.addShape(centerVertex);
61
64
  this.setCurrentPosition(targetPoint);
62
65
  }
63
66
  createCopy(remap) {
@@ -44,6 +44,9 @@ export class TangentArcWithTangent extends GeometrySceneObject {
44
44
  const ty = sign * Math.cos(endAngleRad);
45
45
  this.setTangent(new Point2D(tx, ty));
46
46
  this.addShape(edge);
47
+ const centerVertex = Vertex.fromPoint2D(centerPoint);
48
+ centerVertex.markAsMetaShape();
49
+ this.addShape(centerVertex);
47
50
  this.setCurrentPosition(endPoint);
48
51
  }
49
52
  createCopy(remap) {
@@ -46,6 +46,9 @@ export class TangentArc extends GeometrySceneObject {
46
46
  const ty = sign * Math.cos(endAngleRad);
47
47
  this.setTangent(new Point2D(tx, ty));
48
48
  this.addShape(edge);
49
+ const centerVertex = Vertex.fromPoint2D(centerPoint);
50
+ centerVertex.markAsMetaShape();
51
+ this.addShape(centerVertex);
49
52
  this.setCurrentPosition(endPoint);
50
53
  }
51
54
  createCopy(remap) {
@@ -6,8 +6,10 @@ export declare class VerticalLine extends GeometrySceneObject implements IVLine
6
6
  distanceOrTarget: number | SceneObject;
7
7
  private targetPlane;
8
8
  private _centered;
9
+ private _hasExplicitStart;
9
10
  constructor(distanceOrTarget: number | SceneObject, targetPlane?: PlaneObjectBase);
10
11
  centered(value?: boolean): this;
12
+ setHasExplicitStart(value?: boolean): this;
11
13
  build(): void;
12
14
  getDependencies(): SceneObject[];
13
15
  createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
@@ -17,5 +19,6 @@ export declare class VerticalLine extends GeometrySceneObject implements IVLine
17
19
  serialize(): {
18
20
  distance: number;
19
21
  centered: boolean;
22
+ hasExplicitStart: boolean;
20
23
  };
21
24
  }
@@ -8,6 +8,7 @@ export class VerticalLine extends GeometrySceneObject {
8
8
  distanceOrTarget;
9
9
  targetPlane;
10
10
  _centered = false;
11
+ _hasExplicitStart = false;
11
12
  constructor(distanceOrTarget, targetPlane = null) {
12
13
  super();
13
14
  this.distanceOrTarget = distanceOrTarget;
@@ -17,6 +18,10 @@ export class VerticalLine extends GeometrySceneObject {
17
18
  this._centered = value;
18
19
  return this;
19
20
  }
21
+ setHasExplicitStart(value = true) {
22
+ this._hasExplicitStart = value;
23
+ return this;
24
+ }
20
25
  build() {
21
26
  const plane = this.targetPlane?.getPlane() || this.sketch.getPlane();
22
27
  const currentPos = this.targetPlane
@@ -78,6 +83,7 @@ export class VerticalLine extends GeometrySceneObject {
78
83
  : this.distanceOrTarget;
79
84
  const copy = new VerticalLine(distanceOrTarget, targetPlane);
80
85
  copy.centered(this._centered);
86
+ copy._hasExplicitStart = this._hasExplicitStart;
81
87
  return copy;
82
88
  }
83
89
  compareTo(other) {
@@ -104,7 +110,7 @@ export class VerticalLine extends GeometrySceneObject {
104
110
  else if (this.distanceOrTarget !== other.distanceOrTarget) {
105
111
  return false;
106
112
  }
107
- return this._centered === other._centered;
113
+ return this._centered === other._centered && this._hasExplicitStart === other._hasExplicitStart;
108
114
  }
109
115
  getType() {
110
116
  return 'line';
@@ -115,7 +121,8 @@ export class VerticalLine extends GeometrySceneObject {
115
121
  serialize() {
116
122
  return {
117
123
  distance: typeof this.distanceOrTarget === 'number' ? this.distanceOrTarget : null,
118
- centered: this._centered
124
+ centered: this._centered,
125
+ hasExplicitStart: this._hasExplicitStart,
119
126
  };
120
127
  }
121
128
  }
@@ -1,14 +1,15 @@
1
1
  import { BuildSceneObjectContext, SceneObject } from "../common/scene-object.js";
2
2
  import { Axis } from "../math/axis.js";
3
+ import { type NumberParam } from "../core/param.js";
3
4
  export type CircularCopyOptions = {
4
- count: number;
5
+ count: NumberParam;
5
6
  centered?: boolean;
6
7
  skip?: number[];
7
8
  } & ({
8
- offset: number;
9
+ offset: NumberParam;
9
10
  angle?: never;
10
11
  } | {
11
- angle: number;
12
+ angle: NumberParam;
12
13
  offset?: never;
13
14
  });
14
15
  export declare class CopyCircular extends SceneObject {
@@ -2,6 +2,7 @@ import { SceneObject } from "../common/scene-object.js";
2
2
  import { Matrix4 } from "../math/matrix4.js";
3
3
  import { rad } from "../helpers/math-helpers.js";
4
4
  import { ShapeOps } from "../oc/shape-ops.js";
5
+ import { resolveParam } from "../core/param.js";
5
6
  export class CopyCircular extends SceneObject {
6
7
  axis;
7
8
  options;
@@ -17,13 +18,21 @@ export class CopyCircular extends SceneObject {
17
18
  if (!this.targetObjects) {
18
19
  objects = context.getActiveSceneObjects();
19
20
  }
20
- const { count, centered, skip } = this.options;
21
+ const originalShapes = objects.flatMap(obj => obj.getShapes());
22
+ for (const obj of objects) {
23
+ obj.removeShapes(this);
24
+ }
25
+ for (const shape of originalShapes) {
26
+ this.addShape(shape);
27
+ }
28
+ const count = resolveParam(this.options.count);
29
+ const { centered, skip } = this.options;
21
30
  let offset;
22
31
  if ('offset' in this.options && this.options.offset !== undefined) {
23
- offset = this.options.offset;
32
+ offset = resolveParam(this.options.offset);
24
33
  }
25
34
  else {
26
- offset = this.options.angle / count;
35
+ offset = resolveParam(this.options.angle) / count;
27
36
  }
28
37
  const startOffset = centered ? -(count * offset) / 2 : 0;
29
38
  for (let i = 1; i < count; i++) {
@@ -31,12 +40,10 @@ export class CopyCircular extends SceneObject {
31
40
  continue;
32
41
  const angle = startOffset + offset * i;
33
42
  const matrix = Matrix4.fromRotationAroundAxis(this.axis.origin, this.axis.direction, rad(angle));
34
- for (const obj of objects) {
35
- for (const shape of obj.getShapes()) {
36
- const transformed = ShapeOps.transform(shape, matrix);
37
- transformed.setMeshSource(shape, matrix);
38
- this.addShape(transformed);
39
- }
43
+ for (const shape of originalShapes) {
44
+ const transformed = ShapeOps.transform(shape, matrix);
45
+ transformed.setMeshSource(shape, matrix);
46
+ this.addShape(transformed);
40
47
  }
41
48
  }
42
49
  }
@@ -2,6 +2,7 @@ import { Matrix4 } from "../math/matrix4.js";
2
2
  import { rad } from "../helpers/math-helpers.js";
3
3
  import { ShapeOps } from "../oc/shape-ops.js";
4
4
  import { GeometrySceneObject } from "./2d/geometry.js";
5
+ import { resolveParam } from "../core/param.js";
5
6
  export class CopyCircular2D extends GeometrySceneObject {
6
7
  center;
7
8
  options;
@@ -21,16 +22,24 @@ export class CopyCircular2D extends GeometrySceneObject {
21
22
  else {
22
23
  objects = allSiblings;
23
24
  }
25
+ const originalShapes = objects.flatMap(obj => obj.getShapes());
26
+ for (const obj of objects) {
27
+ obj.removeShapes(this);
28
+ }
29
+ for (const shape of originalShapes) {
30
+ this.addShape(shape);
31
+ }
24
32
  const plane = this.sketch.getPlane();
25
33
  const origin = plane.localToWorld(this.center.asPoint2D());
26
34
  const direction = plane.normal;
27
- const { count, centered, skip } = this.options;
35
+ const count = resolveParam(this.options.count);
36
+ const { centered, skip } = this.options;
28
37
  let offset;
29
38
  if ('offset' in this.options && this.options.offset !== undefined) {
30
- offset = this.options.offset;
39
+ offset = resolveParam(this.options.offset);
31
40
  }
32
41
  else {
33
- offset = this.options.angle / count;
42
+ offset = resolveParam(this.options.angle) / count;
34
43
  }
35
44
  const startOffset = centered ? -(count * offset) / 2 : 0;
36
45
  for (let i = 1; i < count; i++) {
@@ -38,12 +47,10 @@ export class CopyCircular2D extends GeometrySceneObject {
38
47
  continue;
39
48
  const angle = startOffset + offset * i;
40
49
  const matrix = Matrix4.fromRotationAroundAxis(origin, direction, rad(angle));
41
- for (const obj of objects) {
42
- for (const shape of obj.getShapes()) {
43
- const transformed = ShapeOps.transform(shape, matrix);
44
- transformed.setMeshSource(shape, matrix);
45
- this.addShape(transformed);
46
- }
50
+ for (const shape of originalShapes) {
51
+ const transformed = ShapeOps.transform(shape, matrix);
52
+ transformed.setMeshSource(shape, matrix);
53
+ this.addShape(transformed);
47
54
  }
48
55
  }
49
56
  this.setCurrentPosition(this.center.asPoint2D());
@@ -1,14 +1,15 @@
1
1
  import { BuildSceneObjectContext, SceneObject } from "../common/scene-object.js";
2
2
  import { Axis } from "../math/axis.js";
3
+ import { type NumberParam } from "../core/param.js";
3
4
  export type LinearCopyOptions = {
4
- count: number | number[];
5
+ count: NumberParam | number[];
5
6
  centered?: boolean;
6
7
  skip?: number[][];
7
8
  } & ({
8
- offset: number | number[];
9
+ offset: NumberParam | number[];
9
10
  length?: never;
10
11
  } | {
11
- length: number | number[];
12
+ length: NumberParam | number[];
12
13
  offset?: never;
13
14
  });
14
15
  export declare class CopyLinear extends SceneObject {
@@ -1,6 +1,7 @@
1
1
  import { SceneObject } from "../common/scene-object.js";
2
2
  import { Matrix4 } from "../math/matrix4.js";
3
3
  import { ShapeOps } from "../oc/shape-ops.js";
4
+ import { resolveParam } from "../core/param.js";
4
5
  export class CopyLinear extends SceneObject {
5
6
  axes;
6
7
  options;
@@ -16,15 +17,22 @@ export class CopyLinear extends SceneObject {
16
17
  if (!this.targetObjects) {
17
18
  objects = context.getActiveSceneObjects();
18
19
  }
19
- const { count, centered, skip } = this.options;
20
- const counts = Array.isArray(count)
21
- ? count
22
- : this.axes.map(() => count);
20
+ const originalShapes = objects.flatMap(obj => obj.getShapes());
21
+ for (const obj of objects) {
22
+ obj.removeShapes(this);
23
+ }
24
+ for (const shape of originalShapes) {
25
+ this.addShape(shape);
26
+ }
27
+ const { centered, skip } = this.options;
28
+ const counts = Array.isArray(this.options.count)
29
+ ? this.options.count
30
+ : this.axes.map(() => resolveParam(this.options.count));
23
31
  const offsets = 'offset' in this.options && this.options.offset !== undefined
24
- ? (Array.isArray(this.options.offset) ? this.options.offset : this.axes.map(() => this.options.offset))
32
+ ? (Array.isArray(this.options.offset) ? this.options.offset : this.axes.map(() => resolveParam(this.options.offset)))
25
33
  : null;
26
34
  const lengths = 'length' in this.options && this.options.length !== undefined
27
- ? (Array.isArray(this.options.length) ? this.options.length : this.axes.map(() => this.options.length))
35
+ ? (Array.isArray(this.options.length) ? this.options.length : this.axes.map(() => resolveParam(this.options.length)))
28
36
  : null;
29
37
  const axisOffsets = this.axes.map((_, a) => {
30
38
  if (offsets) {
@@ -58,12 +66,10 @@ export class CopyLinear extends SceneObject {
58
66
  const translation = this.axes[a].direction.multiply(distance);
59
67
  matrix = matrix.multiply(Matrix4.fromTranslationVector(translation));
60
68
  }
61
- for (const obj of objects) {
62
- for (const shape of obj.getShapes()) {
63
- const transformed = ShapeOps.transform(shape, matrix);
64
- transformed.setMeshSource(shape, matrix);
65
- this.addShape(transformed);
66
- }
69
+ for (const shape of originalShapes) {
70
+ const transformed = ShapeOps.transform(shape, matrix);
71
+ transformed.setMeshSource(shape, matrix);
72
+ this.addShape(transformed);
67
73
  }
68
74
  }
69
75
  }
@@ -2,6 +2,7 @@ import { Matrix4 } from "../math/matrix4.js";
2
2
  import { ShapeOps } from "../oc/shape-ops.js";
3
3
  import { GeometrySceneObject } from "./2d/geometry.js";
4
4
  import { AxisObjectBase } from "./axis-renderable-base.js";
5
+ import { resolveParam } from "../core/param.js";
5
6
  export class CopyLinear2D extends GeometrySceneObject {
6
7
  axes;
7
8
  options;
@@ -21,16 +22,23 @@ export class CopyLinear2D extends GeometrySceneObject {
21
22
  else {
22
23
  objects = allSiblings;
23
24
  }
25
+ const originalShapes = objects.flatMap(obj => obj.getShapes());
26
+ for (const obj of objects) {
27
+ obj.removeShapes(this);
28
+ }
29
+ for (const shape of originalShapes) {
30
+ this.addShape(shape);
31
+ }
24
32
  const resolvedAxes = this.axes.map(a => a instanceof AxisObjectBase ? a.getAxis() : a);
25
- const { count, centered, skip } = this.options;
26
- const counts = Array.isArray(count)
27
- ? count
28
- : resolvedAxes.map(() => count);
33
+ const { centered, skip } = this.options;
34
+ const counts = Array.isArray(this.options.count)
35
+ ? this.options.count
36
+ : resolvedAxes.map(() => resolveParam(this.options.count));
29
37
  const offsets = 'offset' in this.options && this.options.offset !== undefined
30
- ? (Array.isArray(this.options.offset) ? this.options.offset : resolvedAxes.map(() => this.options.offset))
38
+ ? (Array.isArray(this.options.offset) ? this.options.offset : resolvedAxes.map(() => resolveParam(this.options.offset)))
31
39
  : null;
32
40
  const lengths = 'length' in this.options && this.options.length !== undefined
33
- ? (Array.isArray(this.options.length) ? this.options.length : resolvedAxes.map(() => this.options.length))
41
+ ? (Array.isArray(this.options.length) ? this.options.length : resolvedAxes.map(() => resolveParam(this.options.length)))
34
42
  : null;
35
43
  const axisOffsets = resolvedAxes.map((_, a) => {
36
44
  if (offsets) {
@@ -64,12 +72,10 @@ export class CopyLinear2D extends GeometrySceneObject {
64
72
  const translation = resolvedAxes[a].direction.multiply(distance);
65
73
  matrix = matrix.multiply(Matrix4.fromTranslationVector(translation));
66
74
  }
67
- for (const obj of objects) {
68
- for (const shape of obj.getShapes()) {
69
- const transformed = ShapeOps.transform(shape, matrix);
70
- transformed.setMeshSource(shape, matrix);
71
- this.addShape(transformed);
72
- }
75
+ for (const shape of originalShapes) {
76
+ const transformed = ShapeOps.transform(shape, matrix);
77
+ transformed.setMeshSource(shape, matrix);
78
+ this.addShape(transformed);
73
79
  }
74
80
  }
75
81
  }
@@ -9,6 +9,7 @@ import { Point2DLike } from "../math/point.js";
9
9
  import { Plane } from "../math/plane.js";
10
10
  import { FaceFilterBuilder } from "../filters/face/face-filter.js";
11
11
  import { EdgeFilterBuilder } from "../filters/edge/edge-filter.js";
12
+ import { type NumberParam } from "../core/param.js";
12
13
  import { ShapeHistory } from "../common/shape-history-tracker.js";
13
14
  /** A 3D op's classified face buckets. Each is empty if the op doesn't produce that category. */
14
15
  export type ClassifiedFaces = {
@@ -105,10 +106,10 @@ export declare abstract class ExtrudeBase extends SceneObject implements IExtrud
105
106
  private buildSuffix;
106
107
  private resolveFaces;
107
108
  private resolveEdges;
108
- draft(value: number | [number, number]): this;
109
- endOffset(value: number): this;
109
+ draft(value: NumberParam | [NumberParam, NumberParam]): this;
110
+ endOffset(value: NumberParam): this;
110
111
  drill(value?: boolean): this;
111
- thin(offset1: number, offset2?: number): this;
112
+ thin(offset1: NumberParam, offset2?: NumberParam): this;
112
113
  isThin(): boolean;
113
114
  getThinOffsets(): [number] | [number, number] | undefined;
114
115
  protected serializePickFields(): {
@@ -11,6 +11,7 @@ import { EdgeFilterBuilder } from "../filters/edge/edge-filter.js";
11
11
  import { ShapeFilter } from "../filters/filter.js";
12
12
  import { EdgeOps } from "../oc/edge-ops.js";
13
13
  import { Explorer } from "../oc/explorer.js";
14
+ import { resolveParam } from "../core/param.js";
14
15
  import { getOC } from "../oc/init.js";
15
16
  import { ShapeHistoryTracker } from "../common/shape-history-tracker.js";
16
17
  import { fuseWithSceneObjects } from "../helpers/scene-helpers.js";
@@ -398,11 +399,16 @@ export class ExtrudeBase extends SceneObject {
398
399
  return new ShapeFilter(shapes, ...filters).apply();
399
400
  }
400
401
  draft(value) {
401
- this._draft = value;
402
+ if (Array.isArray(value)) {
403
+ this._draft = [resolveParam(value[0]), resolveParam(value[1])];
404
+ }
405
+ else {
406
+ this._draft = resolveParam(value);
407
+ }
402
408
  return this;
403
409
  }
404
410
  endOffset(value) {
405
- this._endOffset = value;
411
+ this._endOffset = resolveParam(value);
406
412
  return this;
407
413
  }
408
414
  drill(value = true) {
@@ -410,7 +416,8 @@ export class ExtrudeBase extends SceneObject {
410
416
  return this;
411
417
  }
412
418
  thin(offset1, offset2) {
413
- this._thin = offset2 !== undefined ? [offset1, offset2] : [offset1];
419
+ const o1 = resolveParam(offset1);
420
+ this._thin = offset2 !== undefined ? [o1, resolveParam(offset2)] : [o1];
414
421
  return this;
415
422
  }
416
423
  isThin() {
@@ -44,8 +44,8 @@ export class MirrorShape2D extends GeometrySceneObject {
44
44
  transformedShapes.push(transformed);
45
45
  }
46
46
  }
47
- const firstShape = transformedShapes[0];
48
- const lastShape = transformedShapes[transformedShapes.length - 1];
47
+ const firstShape = transformedShapes.find(shape => !shape.isMetaShape() && !shape.isGuideShape());
48
+ const lastShape = transformedShapes.toReversed().find(shape => !shape.isMetaShape() && !shape.isGuideShape());
49
49
  if (firstShape) {
50
50
  const start = firstShape.getFirstVertex();
51
51
  if (start) {
@@ -0,0 +1,13 @@
1
+ import { SceneObject } from "../common/scene-object.js";
2
+ import { Axis } from "../math/axis.js";
3
+ import { AxisObjectBase } from "./axis-renderable-base.js";
4
+ export type RepeatAxisSource = Axis | AxisObjectBase;
5
+ /**
6
+ * Shared base for repeat features. Holds structural equality helpers used
7
+ * by `compareTo`, which runs during cache-compare — before any render —
8
+ * when an `AxisObjectBase` source may not yet have its resolved `Axis`
9
+ * state. Comparing via `getAxis()` at that point would NPE.
10
+ */
11
+ export declare abstract class RepeatBase extends SceneObject {
12
+ protected static axisSourceEquals(a: RepeatAxisSource, b: RepeatAxisSource): boolean;
13
+ }
@@ -0,0 +1,21 @@
1
+ import { SceneObject } from "../common/scene-object.js";
2
+ import { AxisObjectBase } from "./axis-renderable-base.js";
3
+ /**
4
+ * Shared base for repeat features. Holds structural equality helpers used
5
+ * by `compareTo`, which runs during cache-compare — before any render —
6
+ * when an `AxisObjectBase` source may not yet have its resolved `Axis`
7
+ * state. Comparing via `getAxis()` at that point would NPE.
8
+ */
9
+ export class RepeatBase extends SceneObject {
10
+ static axisSourceEquals(a, b) {
11
+ const aObj = a instanceof AxisObjectBase;
12
+ const bObj = b instanceof AxisObjectBase;
13
+ if (aObj !== bObj) {
14
+ return false;
15
+ }
16
+ if (aObj) {
17
+ return a.compareTo(b);
18
+ }
19
+ return a.equals(b);
20
+ }
21
+ }
@@ -1,17 +1,18 @@
1
1
  import { BuildSceneObjectContext, SceneObject } from "../common/scene-object.js";
2
- import { RepeatAxisSource } from "./repeat-linear.js";
2
+ import { RepeatBase, RepeatAxisSource } from "./repeat-base.js";
3
+ import { type NumberParam } from "../core/param.js";
3
4
  export type CircularRepeatOptions = {
4
- count: number;
5
+ count: NumberParam;
5
6
  centered?: boolean;
6
7
  skip?: number[];
7
8
  } & ({
8
- offset: number;
9
+ offset: NumberParam;
9
10
  angle?: never;
10
11
  } | {
11
- angle: number;
12
+ angle: NumberParam;
12
13
  offset?: never;
13
14
  });
14
- export declare class RepeatCircular extends SceneObject {
15
+ export declare class RepeatCircular extends RepeatBase {
15
16
  axis: RepeatAxisSource;
16
17
  options: CircularRepeatOptions;
17
18
  targetObjects: SceneObject[] | null;
@@ -1,9 +1,6 @@
1
- import { SceneObject } from "../common/scene-object.js";
2
1
  import { AxisObjectBase } from "./axis-renderable-base.js";
3
- function axisOf(source) {
4
- return source instanceof AxisObjectBase ? source.getAxis() : source;
5
- }
6
- export class RepeatCircular extends SceneObject {
2
+ import { RepeatBase } from "./repeat-base.js";
3
+ export class RepeatCircular extends RepeatBase {
7
4
  axis;
8
5
  options;
9
6
  targetObjects;
@@ -30,7 +27,7 @@ export class RepeatCircular extends SceneObject {
30
27
  if (!super.compareTo(other)) {
31
28
  return false;
32
29
  }
33
- if (!axisOf(this.axis).equals(axisOf(other.axis))) {
30
+ if (!RepeatCircular.axisSourceEquals(this.axis, other.axis)) {
34
31
  return false;
35
32
  }
36
33
  const thisTargetObjects = this.targetObjects || [];