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
@@ -0,0 +1,61 @@
1
+ import { readFileSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ // `fluidcad.json` at the workspace root holds the stable, hub-minted model
5
+ // identity (and room to grow: default visibility, name, …). It's meant to be
6
+ // committed — like `fly.toml`, it ties a workspace to the model it publishes
7
+ // to, so re-publishing creates a new VERSION rather than an unrelated model.
8
+ const FILENAME = 'fluidcad.json';
9
+
10
+ export function modelConfigPath(workspace) {
11
+ return join(workspace, FILENAME);
12
+ }
13
+
14
+ /** Read `fluidcad.json`, or an empty object if missing/unreadable. */
15
+ export function readModelConfig(workspace) {
16
+ try {
17
+ const cfg = JSON.parse(readFileSync(modelConfigPath(workspace), 'utf8'));
18
+ return cfg && typeof cfg === 'object' ? cfg : {};
19
+ } catch {
20
+ return {};
21
+ }
22
+ }
23
+
24
+ /** The persisted hub model id for this workspace, or null on first publish. */
25
+ export function readModelId(workspace) {
26
+ const cfg = readModelConfig(workspace);
27
+ return typeof cfg.modelId === 'string' && cfg.modelId ? cfg.modelId : null;
28
+ }
29
+
30
+ /**
31
+ * The persisted identity for this workspace: the hub model `id` and its
32
+ * last-known `name`. Both come back null when absent — first publish, or a
33
+ * `fluidcad.json` the user deleted. The name is shown in the publish prompt so
34
+ * we can name the model offline without a round-trip.
35
+ */
36
+ export function readModelIdentity(workspace) {
37
+ const cfg = readModelConfig(workspace);
38
+ return {
39
+ modelId: typeof cfg.modelId === 'string' && cfg.modelId ? cfg.modelId : null,
40
+ name: typeof cfg.name === 'string' && cfg.name ? cfg.name : null,
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Shallow-merge `patch` into `fluidcad.json`, preserving any other fields, and
46
+ * write it back (creating the file if needed). `undefined`/`null` values in the
47
+ * patch are skipped, so callers can pass a partial identity without clobbering
48
+ * what's already there.
49
+ */
50
+ export function writeModelConfig(workspace, patch) {
51
+ const cfg = readModelConfig(workspace);
52
+ for (const [key, value] of Object.entries(patch)) {
53
+ if (value !== undefined && value !== null) cfg[key] = value;
54
+ }
55
+ writeFileSync(modelConfigPath(workspace), JSON.stringify(cfg, null, 2) + '\n');
56
+ }
57
+
58
+ /** Persist just the hub-minted model id (thin wrapper over `writeModelConfig`). */
59
+ export function writeModelId(workspace, modelId) {
60
+ writeModelConfig(workspace, { modelId });
61
+ }
@@ -0,0 +1,97 @@
1
+ import { emitKeypressEvents } from 'readline';
2
+
3
+ /**
4
+ * Minimal interactive prompts for the CLI, built on Node's built-in `readline`
5
+ * keypress events — no dependency (the CLI only ships `commander`). Used by
6
+ * `publish` to choose between a new model and a new version.
7
+ */
8
+
9
+ /**
10
+ * Whether we can run an interactive prompt — both stdin and stdout must be a
11
+ * TTY. CI and piped input are not, so callers fall back to flags/defaults there.
12
+ */
13
+ export function isInteractive() {
14
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
15
+ }
16
+
17
+ const HIDE_CURSOR = '\x1b[?25l';
18
+ const SHOW_CURSOR = '\x1b[?25h';
19
+ const CLEAR_LINE = '\x1b[2K';
20
+ const CYAN = '\x1b[36m';
21
+ const DIM = '\x1b[2m';
22
+ const RESET = '\x1b[0m';
23
+
24
+ /**
25
+ * Arrow-key (or j/k) single-select. Renders a menu with one highlighted row,
26
+ * moves the highlight on ↑/↓/k/j, confirms on Enter, and aborts on Ctrl-C.
27
+ * Resolves to the chosen entry's `value`. Assumes `isInteractive()` — it puts
28
+ * stdin in raw mode, so don't call it without a TTY.
29
+ *
30
+ * `choices`: `[{ label, value }]`.
31
+ */
32
+ export function select(message, choices) {
33
+ return new Promise((resolveChoice) => {
34
+ const input = process.stdin;
35
+ const output = process.stdout;
36
+ let active = 0;
37
+
38
+ // Truncate labels so a long one never wraps — a wrapped row would occupy two
39
+ // terminal lines and throw off the cursor-up redraw math below.
40
+ const width = Math.max(8, (output.columns || 80) - 2);
41
+ const fit = (s) => (s.length > width ? s.slice(0, width - 1) + '…' : s);
42
+
43
+ output.write(`${message}\n`);
44
+ output.write(`${DIM} ↑/↓ or j/k to move · Enter to confirm${RESET}\n`);
45
+ output.write(HIDE_CURSOR);
46
+
47
+ const draw = (initial) => {
48
+ if (!initial) output.write(`\x1b[${choices.length}A`); // back up to the first row
49
+ choices.forEach((c, i) => {
50
+ const on = i === active;
51
+ const row = `${on ? '❯' : ' '} ${fit(c.label)}`;
52
+ output.write(`${CLEAR_LINE}${on ? `${CYAN}${row}${RESET}` : row}\n`);
53
+ });
54
+ };
55
+ draw(true);
56
+
57
+ emitKeypressEvents(input);
58
+ const wasRaw = Boolean(input.isRaw);
59
+ input.setRawMode(true);
60
+ input.resume();
61
+
62
+ const restore = () => {
63
+ input.removeListener('keypress', onKey);
64
+ input.setRawMode(wasRaw);
65
+ input.pause();
66
+ output.write(SHOW_CURSOR);
67
+ };
68
+
69
+ const onKey = (_str, key) => {
70
+ if (!key) return;
71
+ if (key.ctrl && key.name === 'c') {
72
+ restore();
73
+ output.write('\n');
74
+ process.exit(130); // 128 + SIGINT, the shell convention for Ctrl-C
75
+ }
76
+ switch (key.name) {
77
+ case 'up':
78
+ case 'k':
79
+ active = (active - 1 + choices.length) % choices.length;
80
+ draw(false);
81
+ break;
82
+ case 'down':
83
+ case 'j':
84
+ active = (active + 1) % choices.length;
85
+ draw(false);
86
+ break;
87
+ case 'return':
88
+ case 'enter':
89
+ restore();
90
+ resolveChoice(choices[active].value);
91
+ break;
92
+ }
93
+ };
94
+
95
+ input.on('keypress', onKey);
96
+ });
97
+ }
@@ -0,0 +1,57 @@
1
+ import { readFileSync, existsSync, readdirSync } from 'fs';
2
+ import { dirname, join, resolve } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+
7
+ // Shared by `pack` and `publish`: both resolve the entry `.fluid.js` the same
8
+ // way and stamp the same fluidcad version onto the package.
9
+
10
+ /** The installed fluidcad version (from the package's own package.json). */
11
+ export function readPackageVersion() {
12
+ try {
13
+ const pkgPath = resolve(__dirname, '..', '..', 'package.json');
14
+ return JSON.parse(readFileSync(pkgPath, 'utf8')).version ?? '0.0.0';
15
+ } catch {
16
+ return '0.0.0';
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Read the MODEL workspace's own `package.json` for publish prefills: name and
22
+ * description (→ short description). Distinct from `readPackageVersion`, which
23
+ * reads the fluidcad engine's version. Missing/unreadable fields come back
24
+ * undefined. (The published version number is hub-assigned, not read here.)
25
+ */
26
+ export function readWorkspacePackage(workspace) {
27
+ try {
28
+ const pkg = JSON.parse(readFileSync(join(workspace, 'package.json'), 'utf8'));
29
+ return {
30
+ name: typeof pkg.name === 'string' ? pkg.name : undefined,
31
+ description: typeof pkg.description === 'string' ? pkg.description : undefined,
32
+ };
33
+ } catch {
34
+ return {};
35
+ }
36
+ }
37
+
38
+ /** Resolve the entry `.fluid.js`: the override if given, else the sole one. */
39
+ export function findEntry(workspace, override) {
40
+ if (override) {
41
+ const abs = resolve(workspace, override);
42
+ if (!existsSync(abs)) {
43
+ throw new Error(`Entry file not found: ${abs}`);
44
+ }
45
+ return abs;
46
+ }
47
+ const candidates = readdirSync(workspace).filter((f) => f.endsWith('.fluid.js'));
48
+ if (candidates.length === 0) {
49
+ throw new Error('No .fluid.js files found in the workspace. Pass --entry to specify one.');
50
+ }
51
+ if (candidates.length > 1) {
52
+ throw new Error(
53
+ `Multiple .fluid.js files found: ${candidates.join(', ')}. Pass --entry to choose one.`,
54
+ );
55
+ }
56
+ return resolve(workspace, candidates[0]);
57
+ }
@@ -3,6 +3,7 @@ import { Solid } from "./solid.js";
3
3
  import { Wire } from "./wire.js";
4
4
  import { Face } from "./face.js";
5
5
  import { Edge } from "./edge.js";
6
+ import { Vertex } from "./vertex.js";
6
7
  export declare class ShapeFactory {
7
- static fromShape(shape: TopoDS_Shape): Edge | Wire | Solid | Face;
8
+ static fromShape(shape: TopoDS_Shape): Vertex | Edge | Wire | Solid | Face;
8
9
  }
@@ -3,6 +3,7 @@ import { Solid } from "./solid.js";
3
3
  import { Wire } from "./wire.js";
4
4
  import { Face } from "./face.js";
5
5
  import { Edge } from "./edge.js";
6
+ import { Vertex } from "./vertex.js";
6
7
  export class ShapeFactory {
7
8
  static fromShape(shape) {
8
9
  if (Explorer.isSolid(shape)) {
@@ -17,6 +18,9 @@ export class ShapeFactory {
17
18
  if (Explorer.isEdge(shape)) {
18
19
  return Edge.fromTopoDSEdge(Explorer.toEdge(shape));
19
20
  }
21
+ if (Explorer.isVertex(shape)) {
22
+ return Vertex.fromTopoDSVertex(Explorer.toVertex(shape));
23
+ }
20
24
  if (Explorer.isCompound(shape) || Explorer.isCompoundSolid(shape)) {
21
25
  const solids = Explorer.findShapes(shape, Explorer.getOcShapeType("solid"));
22
26
  if (solids.length === 1) {
@@ -3,14 +3,15 @@ import { Matrix4 } from "../math/matrix4.js";
3
3
  import type { AxisLike } from "../math/axis.js";
4
4
  import type { PlaneLike } from "../math/plane.js";
5
5
  import type { PointLike } from "../math/point.js";
6
+ import { type NumberParam } from "../core/param.js";
6
7
  export declare abstract class TransformablePrimitive extends SceneObject {
7
8
  transform(matrix: Matrix4): this;
8
- translate(x: number): this;
9
- translate(x: number, y: number): this;
10
- translate(x: number, y: number, z: number): this;
9
+ translate(x: NumberParam): this;
10
+ translate(x: NumberParam, y: NumberParam): this;
11
+ translate(x: NumberParam, y: NumberParam, z: NumberParam): this;
11
12
  translate(p: PointLike): this;
12
- rotate(angle: number): this;
13
- rotate(axis: AxisLike, angle: number): this;
13
+ rotate(angle: NumberParam): this;
14
+ rotate(axis: AxisLike, angle: NumberParam): this;
14
15
  mirror(plane: PlaneLike): this;
15
16
  mirror(axis: AxisLike): this;
16
17
  }
@@ -3,6 +3,7 @@ import { Matrix4 } from "../math/matrix4.js";
3
3
  import { Point } from "../math/point.js";
4
4
  import { Vector3d } from "../math/vector3d.js";
5
5
  import { rad } from "../helpers/math-helpers.js";
6
+ import { isNumberParam, resolveParam } from "../core/param.js";
6
7
  export class TransformablePrimitive extends SceneObject {
7
8
  transform(matrix) {
8
9
  this.composeAppliedTransform(matrix);
@@ -10,10 +11,10 @@ export class TransformablePrimitive extends SceneObject {
10
11
  }
11
12
  translate(a, b, c) {
12
13
  let x, y, z;
13
- if (typeof a === 'number') {
14
- x = a;
15
- y = b ?? 0;
16
- z = c ?? 0;
14
+ if (isNumberParam(a)) {
15
+ x = resolveParam(a);
16
+ y = b != null ? resolveParam(b) : 0;
17
+ z = c != null ? resolveParam(c) : 0;
17
18
  }
18
19
  else if (Array.isArray(a)) {
19
20
  x = a[0] ?? 0;
@@ -31,16 +32,16 @@ export class TransformablePrimitive extends SceneObject {
31
32
  let origin;
32
33
  let direction;
33
34
  let angleDeg;
34
- if (typeof a === 'number') {
35
+ if (isNumberParam(a)) {
35
36
  origin = new Point(0, 0, 0);
36
37
  direction = Vector3d.unitZ();
37
- angleDeg = a;
38
+ angleDeg = resolveParam(a);
38
39
  }
39
40
  else {
40
41
  const resolved = resolveAxisLike(a);
41
42
  origin = resolved.origin;
42
43
  direction = resolved.direction;
43
- angleDeg = b;
44
+ angleDeg = resolveParam(b);
44
45
  }
45
46
  return this.transform(Matrix4.fromRotationAroundAxis(origin, direction, rad(angleDeg)));
46
47
  }
@@ -29,7 +29,6 @@ export class Vertex extends Shape {
29
29
  return new Vertex(vertex);
30
30
  }
31
31
  static fromPoint2D(point) {
32
- console.log("Creating vertex from point2D:", point);
33
32
  return Vertex.fromPoint(new Point(point.x, point.y, 0));
34
33
  }
35
34
  compareTo(other) {
@@ -1,5 +1,6 @@
1
1
  import { PlaneLike } from "../../math/plane.js";
2
2
  import { IALine, ISceneObject } from "../interfaces.js";
3
+ import { type NumberParam } from "../param.js";
3
4
  interface ALineFunction {
4
5
  /**
5
6
  * Draws a line at the given angle with the given length.
@@ -7,7 +8,7 @@ interface ALineFunction {
7
8
  * @param angle - The angle in degrees
8
9
  * @param length - The line length
9
10
  */
10
- (angle: number, length: number): IALine;
11
+ (angle: NumberParam, length: NumberParam): IALine;
11
12
  /**
12
13
  * Draws a line at the given angle that ends where it intersects the target
13
14
  * geometry. The nearest intersection along the line's direction (in either
@@ -15,14 +16,14 @@ interface ALineFunction {
15
16
  * @param angle - The angle in degrees
16
17
  * @param target - The geometry to intersect with
17
18
  */
18
- (angle: number, target: ISceneObject): IALine;
19
+ (angle: NumberParam, target: ISceneObject): IALine;
19
20
  /**
20
21
  * Draws a line at the given angle on a specific plane.
21
22
  * @param targetPlane - The plane to draw on
22
23
  * @param angle - The angle in degrees
23
24
  * @param length - The line length
24
25
  */
25
- (targetPlane: PlaneLike | ISceneObject, angle: number, length: number): IALine;
26
+ (targetPlane: PlaneLike | ISceneObject, angle: NumberParam, length: NumberParam): IALine;
26
27
  }
27
28
  declare const _default: ALineFunction;
28
29
  export default _default;
@@ -3,6 +3,7 @@ import { registerBuilder } from "../../index.js";
3
3
  import { isPlaneLike } from "../../math/plane.js";
4
4
  import { SceneObject } from "../../common/scene-object.js";
5
5
  import { resolvePlane } from "../../helpers/resolve.js";
6
+ import { resolveParam } from "../param.js";
6
7
  function build(context) {
7
8
  return function line() {
8
9
  let planeObj = null;
@@ -22,11 +23,11 @@ function build(context) {
22
23
  argOffset = 1;
23
24
  }
24
25
  }
25
- const angle = arguments[argOffset];
26
+ const angle = resolveParam(arguments[argOffset]);
26
27
  const second = arguments[argOffset + 1];
27
28
  const lengthOrTarget = second instanceof SceneObject
28
29
  ? second
29
- : second;
30
+ : resolveParam(second);
30
31
  const aline = new AngledLine(angle, lengthOrTarget, planeObj);
31
32
  context.addSceneObject(aline);
32
33
  return aline;
@@ -1,6 +1,7 @@
1
1
  import { Point2DLike } from "../../math/point.js";
2
2
  import { PlaneLike } from "../../math/plane.js";
3
3
  import { IArcPoints, IArcAngles, ISceneObject } from "../interfaces.js";
4
+ import { type NumberParam } from "../param.js";
4
5
  interface ArcFunction {
5
6
  /**
6
7
  * Draws an arc to an end point from the current position.
@@ -25,7 +26,7 @@ interface ArcFunction {
25
26
  * @param startAngle - The start angle in degrees, relative to the current tangent (defaults to 0)
26
27
  * @param endAngle - The end angle in degrees, relative to the current tangent (defaults to 180)
27
28
  */
28
- (radius: number, startAngle?: number, endAngle?: number): IArcAngles;
29
+ (radius: NumberParam, startAngle?: NumberParam, endAngle?: NumberParam): IArcAngles;
29
30
  /**
30
31
  * Draws an arc to an end point on a specific plane.
31
32
  * @param targetPlane - The plane to draw on
@@ -46,7 +47,7 @@ interface ArcFunction {
46
47
  * @param startAngle - The start angle in degrees
47
48
  * @param endAngle - The end angle in degrees
48
49
  */
49
- (targetPlane: PlaneLike | ISceneObject, radius: number, startAngle: number, endAngle: number): IArcAngles;
50
+ (targetPlane: PlaneLike | ISceneObject, radius: NumberParam, startAngle: NumberParam, endAngle: NumberParam): IArcAngles;
50
51
  }
51
52
  declare const _default: ArcFunction;
52
53
  export default _default;
@@ -5,6 +5,7 @@ import { registerBuilder } from "../../index.js";
5
5
  import { isPlaneLike } from "../../math/plane.js";
6
6
  import { SceneObject } from "../../common/scene-object.js";
7
7
  import { resolvePlane } from "../../helpers/resolve.js";
8
+ import { resolveParam } from "../param.js";
8
9
  function build(context) {
9
10
  return function arc() {
10
11
  let planeObj = null;
@@ -37,9 +38,9 @@ function build(context) {
37
38
  return arcObj;
38
39
  }
39
40
  // (radius, startAngle?, endAngle?) — all numeric args
40
- const radius = arguments[argOffset] || 100;
41
- const startAngle = arguments[argOffset + 1] || 0;
42
- const endAngle = argCount >= 3 ? arguments[argOffset + 2] : 180;
41
+ const radius = resolveParam(arguments[argOffset]) || 100;
42
+ const startAngle = resolveParam(arguments[argOffset + 1]) || 0;
43
+ const endAngle = argCount >= 3 ? resolveParam(arguments[argOffset + 2]) : 180;
43
44
  const arcObj = Arc.fromAngles(radius, startAngle, endAngle, planeObj);
44
45
  context.addSceneObject(arcObj);
45
46
  return arcObj;
@@ -2,13 +2,15 @@ import { Point2DLike } from "../../math/point.js";
2
2
  import { IGeometry } from "../interfaces.js";
3
3
  interface BezierFunction {
4
4
  /**
5
- * Draws a bezier curve from the current position through control points to the end point.
6
- * The last argument is the endpoint; all preceding arguments are control points.
5
+ * Draws a bezier curve through the given points. The first argument is the
6
+ * explicit start, the last is the endpoint, and any in between are control
7
+ * points. Sets the sketch cursor to the endpoint.
7
8
  * With 0 args: interactive mode placeholder (no geometry).
8
- * With 1 arg: degree 1 (line).
9
- * With 2 args: degree 2 (quadratic bezier).
10
- * With 3 args: degree 3 (cubic bezier).
11
- * @param points - Control points and end point
9
+ * With 1 arg: places only the start point (no curve yet).
10
+ * With 2 args: degree 1 (straight line from start to end).
11
+ * With 3 args: degree 2 (quadratic bezier with 1 control point).
12
+ * With 4 args: degree 3 (cubic bezier with 2 control points).
13
+ * @param points - Start, optional control points, and end point
12
14
  */
13
15
  (...points: Point2DLike[]): IGeometry;
14
16
  }
@@ -1,24 +1,25 @@
1
1
  import { Point2DLike } from "../../math/point.js";
2
2
  import { PlaneLike } from "../../math/plane.js";
3
3
  import { IExtrudableGeometry, ISceneObject } from "../interfaces.js";
4
+ import { type NumberParam } from "../param.js";
4
5
  interface CircleFunction {
5
6
  /**
6
7
  * Draws a circle at a given center with an optional diameter.
7
8
  * @param center - The center point
8
9
  * @param diameter - The circle diameter (defaults to 40)
9
10
  */
10
- (center: Point2DLike, diameter?: number): IExtrudableGeometry;
11
+ (center: Point2DLike, diameter?: NumberParam): IExtrudableGeometry;
11
12
  /**
12
13
  * Draws a circle at the origin with an optional diameter.
13
14
  * @param diameter - The circle diameter (defaults to 40)
14
15
  */
15
- (diameter?: number): IExtrudableGeometry;
16
+ (diameter?: NumberParam): IExtrudableGeometry;
16
17
  /**
17
18
  * Draws a circle with a given diameter on a specific plane.
18
19
  * @param targetPlane - The plane to draw on
19
20
  * @param diameter - The circle diameter
20
21
  */
21
- (targetPlane: PlaneLike | ISceneObject, diameter: number): IExtrudableGeometry;
22
+ (targetPlane: PlaneLike | ISceneObject, diameter: NumberParam): IExtrudableGeometry;
22
23
  }
23
24
  declare const _default: CircleFunction;
24
25
  export default _default;
@@ -6,6 +6,7 @@ import { registerBuilder } from "../../index.js";
6
6
  import { isPlaneLike } from "../../math/plane.js";
7
7
  import { SceneObject } from "../../common/scene-object.js";
8
8
  import { resolvePlane } from "../../helpers/resolve.js";
9
+ import { resolveParam } from "../param.js";
9
10
  function build(context) {
10
11
  return function circle() {
11
12
  let diameter;
@@ -31,13 +32,13 @@ function build(context) {
31
32
  context.addSceneObject(circle);
32
33
  }
33
34
  else if (argCount === 1) {
34
- diameter = arguments[argOffset] || 40;
35
+ diameter = resolveParam(arguments[argOffset]) || 40;
35
36
  circle = new Circle(diameter, null, planeObj);
36
37
  context.addSceneObject(circle);
37
38
  }
38
39
  else {
39
40
  center = normalizePoint2D(arguments[argOffset]);
40
- diameter = arguments[argOffset + 1] || 40;
41
+ diameter = resolveParam(arguments[argOffset + 1]) || 40;
41
42
  circle = new Circle(diameter, null, planeObj);
42
43
  const move = new Move(center);
43
44
  context.addSceneObjects([move, circle]);
@@ -1,27 +1,28 @@
1
1
  import { Point2DLike } from "../../math/point.js";
2
2
  import { PlaneLike } from "../../math/plane.js";
3
3
  import { IExtrudableGeometry, ISceneObject } from "../interfaces.js";
4
+ import { type NumberParam } from "../param.js";
4
5
  interface EllipseFunction {
5
6
  /**
6
7
  * Draws an ellipse at the current position.
7
8
  * @param rx - Semi-radius along the plane's X axis
8
9
  * @param ry - Semi-radius along the plane's Y axis
9
10
  */
10
- (rx: number, ry: number): IExtrudableGeometry;
11
+ (rx: NumberParam, ry: NumberParam): IExtrudableGeometry;
11
12
  /**
12
13
  * Draws an ellipse at a given center.
13
14
  * @param center - The center point
14
15
  * @param rx - Semi-radius along the plane's X axis
15
16
  * @param ry - Semi-radius along the plane's Y axis
16
17
  */
17
- (center: Point2DLike, rx: number, ry: number): IExtrudableGeometry;
18
+ (center: Point2DLike, rx: NumberParam, ry: NumberParam): IExtrudableGeometry;
18
19
  /**
19
20
  * Draws an ellipse on a specific plane.
20
21
  * @param targetPlane - The plane to draw on
21
22
  * @param rx - Semi-radius along the plane's X axis
22
23
  * @param ry - Semi-radius along the plane's Y axis
23
24
  */
24
- (targetPlane: PlaneLike | ISceneObject, rx: number, ry: number): IExtrudableGeometry;
25
+ (targetPlane: PlaneLike | ISceneObject, rx: NumberParam, ry: NumberParam): IExtrudableGeometry;
25
26
  /**
26
27
  * Draws an ellipse at a given center on a specific plane.
27
28
  * @param targetPlane - The plane to draw on
@@ -29,7 +30,7 @@ interface EllipseFunction {
29
30
  * @param rx - Semi-radius along the plane's X axis
30
31
  * @param ry - Semi-radius along the plane's Y axis
31
32
  */
32
- (targetPlane: PlaneLike | ISceneObject, center: Point2DLike, rx: number, ry: number): IExtrudableGeometry;
33
+ (targetPlane: PlaneLike | ISceneObject, center: Point2DLike, rx: NumberParam, ry: NumberParam): IExtrudableGeometry;
33
34
  }
34
35
  declare const _default: EllipseFunction;
35
36
  export default _default;
@@ -7,6 +7,7 @@ import { registerBuilder } from "../../index.js";
7
7
  import { isPlaneLike } from "../../math/plane.js";
8
8
  import { SceneObject } from "../../common/scene-object.js";
9
9
  import { resolvePlane } from "../../helpers/resolve.js";
10
+ import { resolveParam } from "../param.js";
10
11
  function toPoint2D(p) {
11
12
  if (p instanceof Point2D) {
12
13
  return p;
@@ -35,16 +36,16 @@ function build(context) {
35
36
  }
36
37
  const argCount = arguments.length - argOffset;
37
38
  if (argCount === 2) {
38
- const rx = arguments[argOffset];
39
- const ry = arguments[argOffset + 1];
39
+ const rx = resolveParam(arguments[argOffset]);
40
+ const ry = resolveParam(arguments[argOffset + 1]);
40
41
  const e = new Ellipse(rx, ry, planeObj);
41
42
  context.addSceneObject(e);
42
43
  return e;
43
44
  }
44
45
  if (argCount === 3) {
45
46
  const centerArg = arguments[argOffset];
46
- const rx = arguments[argOffset + 1];
47
- const ry = arguments[argOffset + 2];
47
+ const rx = resolveParam(arguments[argOffset + 1]);
48
+ const ry = resolveParam(arguments[argOffset + 2]);
48
49
  if (planeObj) {
49
50
  // Standalone (plane, center, rx, ry): center is plane-local. Move
50
51
  // can't run outside a sketch, so resolve the center eagerly into the
@@ -1,13 +1,14 @@
1
1
  import { Point2DLike } from "../../math/point.js";
2
2
  import { PlaneLike } from "../../math/plane.js";
3
3
  import { IHLine, ISceneObject } from "../interfaces.js";
4
+ import { type NumberParam } from "../param.js";
4
5
  interface HLineFunction {
5
6
  /**
6
7
  * Draws a horizontal line of the given distance.
7
8
  * Chain `.centered()` to center the line on the current position.
8
9
  * @param distance - The line length
9
10
  */
10
- (distance: number): IHLine;
11
+ (distance: NumberParam): IHLine;
11
12
  /**
12
13
  * Draws a horizontal line that ends where it intersects the target geometry.
13
14
  * The nearest intersection (in either direction along the X axis) is used.
@@ -20,7 +21,7 @@ interface HLineFunction {
20
21
  * @param start - The start point
21
22
  * @param distance - The line length
22
23
  */
23
- (start: Point2DLike, distance: number): IHLine;
24
+ (start: Point2DLike, distance: NumberParam): IHLine;
24
25
  /**
25
26
  * Draws a horizontal line from a start point that ends where it intersects
26
27
  * the target geometry. The nearest intersection (in either direction along
@@ -34,7 +35,7 @@ interface HLineFunction {
34
35
  * @param targetPlane - The plane to draw on
35
36
  * @param distance - The line length
36
37
  */
37
- (targetPlane: PlaneLike | ISceneObject, distance: number): IHLine;
38
+ (targetPlane: PlaneLike | ISceneObject, distance: NumberParam): IHLine;
38
39
  }
39
40
  declare const _default: HLineFunction;
40
41
  export default _default;
@@ -6,6 +6,7 @@ import { registerBuilder } from "../../index.js";
6
6
  import { isPlaneLike } from "../../math/plane.js";
7
7
  import { SceneObject } from "../../common/scene-object.js";
8
8
  import { resolvePlane } from "../../helpers/resolve.js";
9
+ import { isNumberParam, resolveParam } from "../param.js";
9
10
  function build(context) {
10
11
  return function line() {
11
12
  let planeObj = null;
@@ -35,18 +36,19 @@ function build(context) {
35
36
  context.addSceneObject(hline);
36
37
  return hline;
37
38
  }
38
- if (argOffset === 0 && typeof arguments[0] !== 'number') {
39
+ if (argOffset === 0 && !isNumberParam(arguments[0])) {
39
40
  // hLine(start, distance) or hLine(start, target)
40
41
  const start = normalizePoint2D(arguments[0]);
41
42
  const second = arguments[1];
42
43
  const distanceOrTarget = second instanceof SceneObject
43
44
  ? second
44
- : second;
45
+ : resolveParam(second);
45
46
  const hline = new HorizontalLine(distanceOrTarget, planeObj);
47
+ hline.setHasExplicitStart();
46
48
  context.addSceneObjects([new Move(start), hline]);
47
49
  return hline;
48
50
  }
49
- const distance = arguments[argOffset];
51
+ const distance = resolveParam(arguments[argOffset]);
50
52
  const hline = new HorizontalLine(distance, planeObj);
51
53
  context.addSceneObject(hline);
52
54
  return hline;
@@ -33,6 +33,7 @@ function build(context) {
33
33
  const start = normalizePoint2D(arguments[argOffset]);
34
34
  const end = normalizePoint2D(arguments[argOffset + 1]);
35
35
  line = new LineTo(end, planeObj);
36
+ line.setHasExplicitStart();
36
37
  context.addSceneObjects([new Move(start), line]);
37
38
  }
38
39
  else {