fluidcad 0.0.34 → 0.0.35

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 (174) hide show
  1. package/README.md +69 -0
  2. package/bin/commands/login.js +120 -0
  3. package/bin/commands/pack.js +49 -0
  4. package/bin/commands/publish.js +136 -0
  5. package/bin/fluidcad.js +6 -0
  6. package/bin/lib/api-client.js +40 -0
  7. package/bin/lib/browser.js +16 -0
  8. package/bin/lib/config.js +39 -0
  9. package/bin/lib/model-config.js +38 -0
  10. package/bin/lib/workspace.js +57 -0
  11. package/lib/dist/common/shape-factory.d.ts +2 -1
  12. package/lib/dist/common/shape-factory.js +4 -0
  13. package/lib/dist/common/transformable-primitive.d.ts +6 -5
  14. package/lib/dist/common/transformable-primitive.js +8 -7
  15. package/lib/dist/common/vertex.js +0 -1
  16. package/lib/dist/core/2d/aline.d.ts +4 -3
  17. package/lib/dist/core/2d/aline.js +3 -2
  18. package/lib/dist/core/2d/arc.d.ts +3 -2
  19. package/lib/dist/core/2d/arc.js +4 -3
  20. package/lib/dist/core/2d/bezier.d.ts +8 -6
  21. package/lib/dist/core/2d/circle.d.ts +4 -3
  22. package/lib/dist/core/2d/circle.js +3 -2
  23. package/lib/dist/core/2d/ellipse.d.ts +5 -4
  24. package/lib/dist/core/2d/ellipse.js +5 -4
  25. package/lib/dist/core/2d/hline.d.ts +4 -3
  26. package/lib/dist/core/2d/hline.js +5 -3
  27. package/lib/dist/core/2d/line.js +1 -0
  28. package/lib/dist/core/2d/offset.d.ts +3 -2
  29. package/lib/dist/core/2d/offset.js +6 -5
  30. package/lib/dist/core/2d/polygon.d.ts +5 -4
  31. package/lib/dist/core/2d/polygon.js +10 -9
  32. package/lib/dist/core/2d/rect.d.ts +4 -3
  33. package/lib/dist/core/2d/rect.js +10 -9
  34. package/lib/dist/core/2d/slot.d.ts +14 -6
  35. package/lib/dist/core/2d/slot.js +19 -8
  36. package/lib/dist/core/2d/vline.d.ts +4 -3
  37. package/lib/dist/core/2d/vline.js +5 -3
  38. package/lib/dist/core/chamfer.d.ts +5 -4
  39. package/lib/dist/core/chamfer.js +7 -6
  40. package/lib/dist/core/color.d.ts +3 -2
  41. package/lib/dist/core/color.js +2 -1
  42. package/lib/dist/core/cut.d.ts +4 -3
  43. package/lib/dist/core/cut.js +5 -4
  44. package/lib/dist/core/cylinder.d.ts +2 -1
  45. package/lib/dist/core/cylinder.js +2 -1
  46. package/lib/dist/core/draft.d.ts +3 -2
  47. package/lib/dist/core/draft.js +3 -2
  48. package/lib/dist/core/extrude.d.ts +4 -3
  49. package/lib/dist/core/extrude.js +5 -4
  50. package/lib/dist/core/fillet.d.ts +5 -4
  51. package/lib/dist/core/fillet.js +6 -5
  52. package/lib/dist/core/index.d.ts +1 -0
  53. package/lib/dist/core/index.js +1 -0
  54. package/lib/dist/core/interfaces.d.ts +25 -24
  55. package/lib/dist/core/param.d.ts +74 -0
  56. package/lib/dist/core/param.js +147 -0
  57. package/lib/dist/core/repeat.d.ts +2 -1
  58. package/lib/dist/core/repeat.js +10 -8
  59. package/lib/dist/core/revolve.d.ts +2 -1
  60. package/lib/dist/core/revolve.js +3 -2
  61. package/lib/dist/core/rib.d.ts +3 -2
  62. package/lib/dist/core/rib.js +6 -2
  63. package/lib/dist/core/rotate.d.ts +5 -4
  64. package/lib/dist/core/rotate.js +4 -3
  65. package/lib/dist/core/shell.d.ts +3 -2
  66. package/lib/dist/core/shell.js +3 -2
  67. package/lib/dist/core/sphere.d.ts +3 -2
  68. package/lib/dist/core/sphere.js +2 -1
  69. package/lib/dist/core/translate.d.ts +7 -6
  70. package/lib/dist/core/translate.js +6 -5
  71. package/lib/dist/features/2d/arc.js +5 -5
  72. package/lib/dist/features/2d/bezier.js +16 -16
  73. package/lib/dist/features/2d/circle.js +4 -0
  74. package/lib/dist/features/2d/ellipse.js +4 -0
  75. package/lib/dist/features/2d/hline.d.ts +3 -0
  76. package/lib/dist/features/2d/hline.js +9 -2
  77. package/lib/dist/features/2d/line.d.ts +3 -0
  78. package/lib/dist/features/2d/line.js +11 -3
  79. package/lib/dist/features/2d/sketch.js +5 -1
  80. package/lib/dist/features/2d/slot.d.ts +5 -0
  81. package/lib/dist/features/2d/slot.js +52 -7
  82. package/lib/dist/features/2d/tarc-to-point-tangent.js +3 -0
  83. package/lib/dist/features/2d/tarc-to-point.js +3 -0
  84. package/lib/dist/features/2d/tarc-with-tangent.js +3 -0
  85. package/lib/dist/features/2d/tarc.js +3 -0
  86. package/lib/dist/features/2d/vline.d.ts +3 -0
  87. package/lib/dist/features/2d/vline.js +9 -2
  88. package/lib/dist/features/copy-circular.d.ts +4 -3
  89. package/lib/dist/features/copy-circular.js +16 -9
  90. package/lib/dist/features/copy-circular2d.js +16 -9
  91. package/lib/dist/features/copy-linear.d.ts +4 -3
  92. package/lib/dist/features/copy-linear.js +18 -12
  93. package/lib/dist/features/copy-linear2d.js +18 -12
  94. package/lib/dist/features/extrude-base.d.ts +4 -3
  95. package/lib/dist/features/extrude-base.js +10 -3
  96. package/lib/dist/features/mirror-shape2d.js +2 -2
  97. package/lib/dist/features/repeat-base.d.ts +13 -0
  98. package/lib/dist/features/repeat-base.js +21 -0
  99. package/lib/dist/features/repeat-circular.d.ts +6 -5
  100. package/lib/dist/features/repeat-circular.js +3 -6
  101. package/lib/dist/features/repeat-linear.d.ts +7 -7
  102. package/lib/dist/features/repeat-linear.js +3 -6
  103. package/lib/dist/index.d.ts +5 -0
  104. package/lib/dist/index.js +8 -1
  105. package/lib/dist/io/file-import.d.ts +7 -0
  106. package/lib/dist/io/file-import.js +30 -10
  107. package/lib/dist/math/lazy-matrix.d.ts +5 -0
  108. package/lib/dist/math/lazy-matrix.js +78 -10
  109. package/lib/dist/oc/boolean-ops.d.ts +2 -2
  110. package/lib/dist/param-registry.d.ts +34 -0
  111. package/lib/dist/param-registry.js +60 -0
  112. package/lib/dist/rendering/mesh-builder.js +2 -1
  113. package/lib/dist/tests/features/copy-circular.test.js +1 -1
  114. package/lib/dist/tests/features/copy-linear.test.js +10 -10
  115. package/lib/dist/tests/features/repeat-user-repro-cache.test.d.ts +1 -0
  116. package/lib/dist/tests/features/repeat-user-repro-cache.test.js +97 -0
  117. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  118. package/llm-docs/api/bezier.md +10 -11
  119. package/llm-docs/api/index.json +1 -1
  120. package/llm-docs/api/types/arc-points.md +2 -2
  121. package/llm-docs/api/types/cut.md +10 -10
  122. package/llm-docs/api/types/extrude.md +10 -10
  123. package/llm-docs/api/types/loft.md +6 -6
  124. package/llm-docs/api/types/revolve.md +6 -6
  125. package/llm-docs/api/types/rib.md +2 -2
  126. package/llm-docs/api/types/slot.md +2 -2
  127. package/llm-docs/api/types/sweep.md +10 -10
  128. package/llm-docs/api/types/transformable.md +14 -14
  129. package/llm-docs/index.json +12 -12
  130. package/mcp/dist/client.d.ts +1 -0
  131. package/mcp/dist/client.js +8 -1
  132. package/mcp/dist/server.js +14 -1
  133. package/mcp/dist/tools/engine.d.ts +16 -0
  134. package/mcp/dist/tools/engine.js +45 -0
  135. package/package.json +9 -3
  136. package/server/dist/api.d.ts +37 -0
  137. package/server/dist/api.js +44 -0
  138. package/server/dist/code-editor.d.ts +64 -0
  139. package/server/dist/code-editor.js +520 -2
  140. package/server/dist/fluidcad-server.d.ts +68 -1
  141. package/server/dist/fluidcad-server.js +224 -88
  142. package/server/dist/host/blocked-imports.d.ts +8 -0
  143. package/server/dist/host/blocked-imports.js +30 -0
  144. package/server/dist/{vite-manager.d.ts → host/local-scene-host.d.ts} +3 -1
  145. package/server/dist/{vite-manager.js → host/local-scene-host.js} +6 -26
  146. package/server/dist/host/scene-host.d.ts +19 -0
  147. package/server/dist/host/scene-host.js +1 -0
  148. package/server/dist/index.js +24 -117
  149. package/server/dist/model-package/capture-params.d.ts +19 -0
  150. package/server/dist/model-package/capture-params.js +42 -0
  151. package/server/dist/model-package/pack.d.ts +23 -0
  152. package/server/dist/model-package/pack.js +229 -0
  153. package/server/dist/model-package/types.d.ts +78 -0
  154. package/server/dist/model-package/types.js +17 -0
  155. package/server/dist/routes/hit-test.d.ts +3 -0
  156. package/server/dist/routes/hit-test.js +17 -0
  157. package/server/dist/routes/pack.d.ts +10 -0
  158. package/server/dist/routes/pack.js +47 -0
  159. package/server/dist/routes/params.d.ts +3 -0
  160. package/server/dist/routes/params.js +75 -0
  161. package/server/dist/routes/sketch-edits.d.ts +3 -0
  162. package/server/dist/routes/sketch-edits.js +542 -0
  163. package/server/dist/routes/timeline.d.ts +3 -0
  164. package/server/dist/routes/timeline.js +49 -0
  165. package/server/dist/server-core.d.ts +53 -0
  166. package/server/dist/server-core.js +147 -0
  167. package/server/dist/ws-protocol.d.ts +101 -2
  168. package/ui/dist/assets/index-CDJmUpFI.css +2 -0
  169. package/ui/dist/assets/index-MRqwG9Vh.js +5417 -0
  170. package/ui/dist/index.html +2 -2
  171. package/server/dist/routes/actions.d.ts +0 -3
  172. package/server/dist/routes/actions.js +0 -309
  173. package/ui/dist/assets/index-BdqrMDRu.js +0 -4946
  174. package/ui/dist/assets/index-DR7c2Qk9.css +0 -2
@@ -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 || [];
@@ -1,19 +1,19 @@
1
1
  import { BuildSceneObjectContext, 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;
2
+ import { RepeatBase, RepeatAxisSource } from "./repeat-base.js";
3
+ export { RepeatAxisSource } from "./repeat-base.js";
4
+ import { type NumberParam } from "../core/param.js";
5
5
  export type LinearRepeatOptions = {
6
- count: number | number[];
6
+ count: NumberParam | number[];
7
7
  centered?: boolean;
8
8
  skip?: number[][];
9
9
  } & ({
10
- offset: number | number[];
10
+ offset: NumberParam | number[];
11
11
  length?: never;
12
12
  } | {
13
- length: number | number[];
13
+ length: NumberParam | number[];
14
14
  offset?: never;
15
15
  });
16
- export declare class RepeatLinear extends SceneObject {
16
+ export declare class RepeatLinear extends RepeatBase {
17
17
  axes: RepeatAxisSource[];
18
18
  options: LinearRepeatOptions;
19
19
  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 RepeatLinear extends SceneObject {
2
+ import { RepeatBase } from "./repeat-base.js";
3
+ export class RepeatLinear extends RepeatBase {
7
4
  axes;
8
5
  options;
9
6
  targetObjects;
@@ -36,7 +33,7 @@ export class RepeatLinear extends SceneObject {
36
33
  return false;
37
34
  }
38
35
  for (let i = 0; i < this.axes.length; i++) {
39
- if (!axisOf(this.axes[i]).equals(axisOf(other.axes[i]))) {
36
+ if (!RepeatLinear.axisSourceEquals(this.axes[i], other.axes[i])) {
40
37
  return false;
41
38
  }
42
39
  }
@@ -17,6 +17,11 @@ export type SceneParserContext = {
17
17
  getActiveSketch(): Sketch | null;
18
18
  };
19
19
  export declare function registerBuilder<T extends Function>(builder: (context: SceneParserContext) => T): T;
20
+ export { createParamRegistry, getParamRegistry } from './param-registry.js';
21
+ export type { ParamDefinition, MultiControlType, SelectOption, ParamVal, ParamScalar } from './param-registry.js';
22
+ export { setAssetProvider } from './io/file-import.js';
23
+ export type { AssetProvider } from './io/file-import.js';
24
+ export { getSceneManager } from './scene-manager.js';
20
25
  export interface FluidCADOptions {
21
26
  mesh?: {
22
27
  lineDeflection?: number;
package/lib/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { loadOC } from "./load.js";
2
- import { createManager, getCurrentScene } from "./scene-manager.js";
2
+ import { createManager, getCurrentScene, getSceneManager } from "./scene-manager.js";
3
3
  import { parse as parseStackTrace } from "stacktrace-parser";
4
4
  export function captureSourceLocation() {
5
5
  const stack = new Error().stack;
@@ -88,8 +88,15 @@ export function registerBuilder(builder) {
88
88
  return fn;
89
89
  ;
90
90
  }
91
+ export { createParamRegistry, getParamRegistry } from './param-registry.js';
92
+ export { setAssetProvider } from './io/file-import.js';
93
+ export { getSceneManager } from './scene-manager.js';
91
94
  export async function init(options) {
92
95
  await loadOC();
96
+ const existing = getSceneManager();
97
+ if (existing) {
98
+ return existing;
99
+ }
93
100
  const resolvedPath = process.env.FLUIDCAD_WORKSPACE_PATH || '';
94
101
  return createManager(resolvedPath, options);
95
102
  }
@@ -1,5 +1,12 @@
1
1
  import { Shape } from "../common/shape.js";
2
2
  import { Solid } from "../common/solid.js";
3
+ /**
4
+ * Override hook for hub mode (and tests). When set, asset reads consult
5
+ * the provider instead of the filesystem — paths are workspace-relative
6
+ * (e.g. `imports/foo.brep`). Returning null falls back to disk.
7
+ */
8
+ export type AssetProvider = (workspaceRelPath: string) => Uint8Array | null;
9
+ export declare function setAssetProvider(provider: AssetProvider | null): void;
3
10
  export declare class FileImport {
4
11
  static deserializeShapes(fileName: string): Solid[];
5
12
  static serializeShape(shape: Shape, workspacePath: string, fileName: string): void;
@@ -2,17 +2,37 @@ import * as fs from "fs";
2
2
  import { join } from "path";
3
3
  import { OcIO } from "../oc/io.js";
4
4
  import { getSceneManager } from "../scene-manager.js";
5
+ let assetProvider = null;
6
+ export function setAssetProvider(provider) {
7
+ assetProvider = provider;
8
+ }
9
+ function readWorkspaceAsset(relPath) {
10
+ if (assetProvider) {
11
+ const bytes = assetProvider(relPath);
12
+ if (bytes) {
13
+ return { text: Buffer.from(bytes).toString('utf8'), exists: true };
14
+ }
15
+ }
16
+ const sceneManager = getSceneManager();
17
+ const filePath = join(sceneManager.rootPath, relPath);
18
+ if (!fs.existsSync(filePath)) {
19
+ return { exists: false };
20
+ }
21
+ return { text: fs.readFileSync(filePath, 'utf8'), exists: true };
22
+ }
5
23
  export class FileImport {
6
24
  static deserializeShapes(fileName) {
7
25
  if (!fileName.endsWith(('.brep'))) {
8
26
  fileName += '.brep';
9
27
  }
10
- const sceneManager = getSceneManager();
11
- const filePath = join(sceneManager.rootPath, 'imports', fileName);
28
+ const relPath = join('imports', fileName);
12
29
  console.log(`Reading file ${fileName}`);
13
- const file = fs.readFileSync(filePath, 'utf8');
14
- console.log(`File ${filePath} read successfully, size: ${file.length} bytes`);
15
- return OcIO.readBRepSolids(fileName, file);
30
+ const result = readWorkspaceAsset(relPath);
31
+ if (!result.exists) {
32
+ throw new Error(`Imported asset not found: ${relPath}`);
33
+ }
34
+ console.log(`File ${relPath} read successfully, size: ${result.text.length} bytes`);
35
+ return OcIO.readBRepSolids(fileName, result.text);
16
36
  }
17
37
  static serializeShape(shape, workspacePath, fileName) {
18
38
  const file = OcIO.writeBRep(shape, fileName);
@@ -56,11 +76,11 @@ export class FileImport {
56
76
  // Read color metadata from JSON sidecar (skipped when noColors is set)
57
77
  let colorData = [];
58
78
  if (!options?.noColors) {
59
- const sceneManager = getSceneManager();
60
- const jsonPath = join(sceneManager.rootPath, 'imports', brepFileName + '.colors.json');
61
- if (fs.existsSync(jsonPath)) {
62
- colorData = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
63
- console.log(`Loaded color metadata from ${jsonPath}`);
79
+ const relPath = join('imports', brepFileName + '.colors.json');
80
+ const result = readWorkspaceAsset(relPath);
81
+ if (result.exists) {
82
+ colorData = JSON.parse(result.text);
83
+ console.log(`Loaded color metadata from ${relPath}`);
64
84
  }
65
85
  }
66
86
  const include = options?.include;
@@ -4,10 +4,12 @@ import { Plane } from "./plane.js";
4
4
  /** Anything that can be resolved to an `Axis` once built — e.g. `AxisObjectBase`. */
5
5
  export interface AxisLazySource {
6
6
  getAxis(): Axis;
7
+ compareTo?(other: unknown): boolean;
7
8
  }
8
9
  /** Anything that can be resolved to a `Plane` once built — e.g. `PlaneObjectBase`. */
9
10
  export interface PlaneLazySource {
10
11
  getPlane(): Plane;
12
+ compareTo?(other: unknown): boolean;
11
13
  }
12
14
  /**
13
15
  * Memoized thunk over a Matrix4. Lets parse-time code thread a transform
@@ -20,9 +22,12 @@ export interface PlaneLazySource {
20
22
  export declare class LazyMatrix {
21
23
  private _resolver;
22
24
  private _cached;
25
+ private _identity;
23
26
  private constructor();
24
27
  resolve(): Matrix4;
25
28
  equals(other: LazyMatrix, tolerance?: number): boolean;
29
+ private static axisSourceEquals;
30
+ private static planeSourceEquals;
26
31
  static of(matrix: Matrix4): LazyMatrix;
27
32
  static from(resolver: () => Matrix4): LazyMatrix;
28
33
  static mirror(plane: Plane | PlaneLazySource): LazyMatrix;