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
@@ -16,9 +16,11 @@ function isPlaneLazySource(p) {
16
16
  export class LazyMatrix {
17
17
  _resolver;
18
18
  _cached;
19
- constructor(resolver, cached = null) {
19
+ _identity;
20
+ constructor(resolver, identity, cached = null) {
20
21
  this._resolver = resolver;
21
22
  this._cached = cached;
23
+ this._identity = identity;
22
24
  }
23
25
  resolve() {
24
26
  if (this._cached === null) {
@@ -27,40 +29,106 @@ export class LazyMatrix {
27
29
  return this._cached;
28
30
  }
29
31
  equals(other, tolerance = 0) {
30
- return this.resolve().equals(other.resolve(), tolerance);
32
+ const a = this._identity;
33
+ const b = other._identity;
34
+ if (a.kind !== b.kind) {
35
+ return false;
36
+ }
37
+ switch (a.kind) {
38
+ case "eager":
39
+ return a.matrix.equals(b.matrix, tolerance);
40
+ case "rotation": {
41
+ const bo = b;
42
+ return Math.abs(a.angle - bo.angle) <= tolerance
43
+ && LazyMatrix.axisSourceEquals(a.axis, bo.axis, tolerance);
44
+ }
45
+ case "translation": {
46
+ const bo = b;
47
+ return Math.abs(a.distance - bo.distance) <= tolerance
48
+ && LazyMatrix.axisSourceEquals(a.axis, bo.axis, tolerance);
49
+ }
50
+ case "mirror": {
51
+ const bo = b;
52
+ return LazyMatrix.planeSourceEquals(a.plane, bo.plane, tolerance);
53
+ }
54
+ case "opaque":
55
+ // No structural information — can't compare safely without resolving,
56
+ // and resolving may dereference unbuilt sources. Treat as unequal so
57
+ // the caller takes the rebuild path instead of crashing.
58
+ return false;
59
+ }
60
+ }
61
+ static axisSourceEquals(a, b, tolerance) {
62
+ if (isAxisLazySource(a)) {
63
+ if (!isAxisLazySource(b)) {
64
+ return false;
65
+ }
66
+ if (a === b) {
67
+ return true;
68
+ }
69
+ if (typeof a.compareTo === "function" && typeof b.compareTo === "function") {
70
+ return a.compareTo(b);
71
+ }
72
+ return false;
73
+ }
74
+ if (isAxisLazySource(b)) {
75
+ return false;
76
+ }
77
+ return a.equals(b, tolerance);
78
+ }
79
+ static planeSourceEquals(a, b, tolerance) {
80
+ if (isPlaneLazySource(a)) {
81
+ if (!isPlaneLazySource(b)) {
82
+ return false;
83
+ }
84
+ if (a === b) {
85
+ return true;
86
+ }
87
+ if (typeof a.compareTo === "function" && typeof b.compareTo === "function") {
88
+ return a.compareTo(b);
89
+ }
90
+ return false;
91
+ }
92
+ if (isPlaneLazySource(b)) {
93
+ return false;
94
+ }
95
+ return a.compareTo(b, tolerance);
31
96
  }
32
97
  static of(matrix) {
33
- return new LazyMatrix(() => matrix, matrix);
98
+ return new LazyMatrix(() => matrix, { kind: "eager", matrix }, matrix);
34
99
  }
35
100
  static from(resolver) {
36
- return new LazyMatrix(resolver);
101
+ return new LazyMatrix(resolver, { kind: "opaque" });
37
102
  }
38
103
  static mirror(plane) {
39
104
  if (isPlaneLazySource(plane)) {
40
105
  return new LazyMatrix(() => {
41
106
  const p = plane.getPlane();
42
107
  return Matrix4.mirrorPlane(p.normal, p.origin);
43
- });
108
+ }, { kind: "mirror", plane });
44
109
  }
45
- return LazyMatrix.of(Matrix4.mirrorPlane(plane.normal, plane.origin));
110
+ const matrix = Matrix4.mirrorPlane(plane.normal, plane.origin);
111
+ return new LazyMatrix(() => matrix, { kind: "mirror", plane }, matrix);
46
112
  }
47
113
  static rotation(axis, angle) {
48
114
  if (isAxisLazySource(axis)) {
49
115
  return new LazyMatrix(() => {
50
116
  const a = axis.getAxis();
51
117
  return Matrix4.fromRotationAroundAxis(a.origin, a.direction, angle);
52
- });
118
+ }, { kind: "rotation", axis, angle });
53
119
  }
54
- return LazyMatrix.of(Matrix4.fromRotationAroundAxis(axis.origin, axis.direction, angle));
120
+ const matrix = Matrix4.fromRotationAroundAxis(axis.origin, axis.direction, angle);
121
+ return new LazyMatrix(() => matrix, { kind: "rotation", axis, angle }, matrix);
55
122
  }
56
123
  static translation(axis, distance) {
57
124
  if (isAxisLazySource(axis)) {
58
125
  return new LazyMatrix(() => {
59
126
  const dir = axis.getAxis().direction;
60
127
  return Matrix4.fromTranslation(dir.x * distance, dir.y * distance, dir.z * distance);
61
- });
128
+ }, { kind: "translation", axis, distance });
62
129
  }
63
130
  const dir = axis.direction;
64
- return LazyMatrix.of(Matrix4.fromTranslation(dir.x * distance, dir.y * distance, dir.z * distance));
131
+ const matrix = Matrix4.fromTranslation(dir.x * distance, dir.y * distance, dir.z * distance);
132
+ return new LazyMatrix(() => matrix, { kind: "translation", axis, distance }, matrix);
65
133
  }
66
134
  }
@@ -8,8 +8,8 @@ export declare class BooleanOps {
8
8
  static cutShapes(shape: Shape, tool: Shape): Shape;
9
9
  static cutShapesRaw(shape: TopoDS_Shape, tool: TopoDS_Shape): TopoDS_Shape;
10
10
  static cutMultiShape(stocks: Shape[], tools: Shape[], plane?: Plane, cutDistance?: number): {
11
- result: Edge | import("../common/wire.js").Wire | Solid | Face;
12
- modified: (shape: Shape) => (Edge | import("../common/wire.js").Wire | Solid | Face)[];
11
+ result: import("../common/vertex.js").Vertex | Edge | import("../common/wire.js").Wire | Solid | Face;
12
+ modified: (shape: Shape) => (import("../common/vertex.js").Vertex | Edge | import("../common/wire.js").Wire | Solid | Face)[];
13
13
  sectionEdges: Edge[];
14
14
  startEdges: Edge[];
15
15
  endEdges: Edge[];
@@ -0,0 +1,34 @@
1
+ export type ControlType = 'auto' | 'text' | 'number' | 'slider' | 'select' | 'checkbox' | 'color';
2
+ export type MultiControlType = 'select' | 'checkboxes' | 'chips';
3
+ export type SelectOption = {
4
+ label: string;
5
+ value: string | number;
6
+ };
7
+ export type ParamScalar = string | number | boolean;
8
+ export type ParamVal = ParamScalar | (string | number)[];
9
+ export type ParamDefinition = {
10
+ label: string;
11
+ defaultValue: ParamVal;
12
+ currentValue: ParamVal;
13
+ controlType: ControlType;
14
+ description?: string;
15
+ group?: string;
16
+ min?: number;
17
+ max?: number;
18
+ step?: number;
19
+ options?: SelectOption[];
20
+ multi?: boolean;
21
+ multiControlType?: MultiControlType;
22
+ };
23
+ export declare class ParamRegistry {
24
+ private definitions;
25
+ private overrides;
26
+ register(def: ParamDefinition): void;
27
+ resolve(label: string, defaultValue: (string | number)[]): (string | number)[];
28
+ resolve<T extends string | number | boolean>(label: string, defaultValue: T): T;
29
+ setOverrides(overrides: Map<string, any>): void;
30
+ getDefinitions(): ParamDefinition[];
31
+ clear(): void;
32
+ }
33
+ export declare function createParamRegistry(): ParamRegistry;
34
+ export declare function getParamRegistry(): ParamRegistry;
@@ -0,0 +1,60 @@
1
+ export class ParamRegistry {
2
+ definitions = new Map();
3
+ overrides = new Map();
4
+ register(def) {
5
+ this.definitions.set(def.label, def);
6
+ }
7
+ resolve(label, defaultValue) {
8
+ if (!this.overrides.has(label)) {
9
+ return defaultValue;
10
+ }
11
+ const override = this.overrides.get(label);
12
+ if (Array.isArray(defaultValue)) {
13
+ if (Array.isArray(override)) {
14
+ return override;
15
+ }
16
+ if (override != null) {
17
+ return [override];
18
+ }
19
+ return defaultValue;
20
+ }
21
+ if (typeof defaultValue === 'boolean') {
22
+ if (override === true || override === 'true' || override === 1) {
23
+ return true;
24
+ }
25
+ if (override === false || override === 'false' || override === 0) {
26
+ return false;
27
+ }
28
+ return defaultValue;
29
+ }
30
+ if (typeof defaultValue === 'number') {
31
+ const num = Number(override);
32
+ if (Number.isFinite(num)) {
33
+ return num;
34
+ }
35
+ return defaultValue;
36
+ }
37
+ return String(override);
38
+ }
39
+ setOverrides(overrides) {
40
+ this.overrides = overrides;
41
+ }
42
+ getDefinitions() {
43
+ return Array.from(this.definitions.values());
44
+ }
45
+ clear() {
46
+ this.definitions.clear();
47
+ this.overrides.clear();
48
+ }
49
+ }
50
+ let currentRegistry = null;
51
+ export function createParamRegistry() {
52
+ currentRegistry = new ParamRegistry();
53
+ return currentRegistry;
54
+ }
55
+ export function getParamRegistry() {
56
+ if (!currentRegistry) {
57
+ currentRegistry = new ParamRegistry();
58
+ }
59
+ return currentRegistry;
60
+ }
@@ -33,7 +33,8 @@ export class MeshBuilder {
33
33
  console.warn("Shell shapes are not supported yet.");
34
34
  }
35
35
  else if (Explorer.isVertex(shape)) {
36
- console.warn("Vertex shapes are not supported yet.");
36
+ const pt = shapeObj.toPoint();
37
+ result = { vertices: [pt.x, pt.y, pt.z], normals: [], indices: [] };
37
38
  }
38
39
  else {
39
40
  console.warn("Shape is not a valid TopoDS_Shape.");
@@ -28,7 +28,7 @@ describe("copy circular", () => {
28
28
  const c = copy("circular", "z", { count: 3, angle: 180 }, e);
29
29
  render();
30
30
  const shapes = c.getShapes();
31
- expect(shapes).toHaveLength(2);
31
+ expect(shapes).toHaveLength(3);
32
32
  });
33
33
  it("should skip specified indices", () => {
34
34
  sketch("xy", () => {
@@ -25,11 +25,11 @@ describe("copy linear", () => {
25
25
  const e = extrude(10).new();
26
26
  const c = copy("linear", "x", { count: 3, offset: 50 }, e);
27
27
  render();
28
- // 2 copies (index 1 and 2), each offset by 50 along X
28
+ // Original + 2 copies (index 1 and 2), each offset by 50 along X
29
29
  const shapes = c.getShapes();
30
- expect(shapes).toHaveLength(2);
31
- const bbox1 = ShapeOps.getBoundingBox(shapes[0]);
32
- const bbox2 = ShapeOps.getBoundingBox(shapes[1]);
30
+ expect(shapes).toHaveLength(3);
31
+ const bbox1 = ShapeOps.getBoundingBox(shapes[1]);
32
+ const bbox2 = ShapeOps.getBoundingBox(shapes[2]);
33
33
  expect(bbox1.minX).toBeCloseTo(50, 0);
34
34
  expect(bbox2.minX).toBeCloseTo(100, 0);
35
35
  });
@@ -41,8 +41,8 @@ describe("copy linear", () => {
41
41
  const c = copy("linear", "y", { count: 2, offset: 60 }, e);
42
42
  render();
43
43
  const shapes = c.getShapes();
44
- expect(shapes).toHaveLength(1);
45
- const bbox = ShapeOps.getBoundingBox(shapes[0]);
44
+ expect(shapes).toHaveLength(2);
45
+ const bbox = ShapeOps.getBoundingBox(shapes[1]);
46
46
  expect(bbox.minY).toBeCloseTo(60, 0);
47
47
  });
48
48
  it("should copy along Z axis", () => {
@@ -53,8 +53,8 @@ describe("copy linear", () => {
53
53
  const c = copy("linear", "z", { count: 2, offset: 30 }, e);
54
54
  render();
55
55
  const shapes = c.getShapes();
56
- expect(shapes).toHaveLength(1);
57
- const bbox = ShapeOps.getBoundingBox(shapes[0]);
56
+ expect(shapes).toHaveLength(2);
57
+ const bbox = ShapeOps.getBoundingBox(shapes[1]);
58
58
  expect(bbox.minZ).toBeCloseTo(30, 0);
59
59
  });
60
60
  it("should distribute copies by total length", () => {
@@ -66,8 +66,8 @@ describe("copy linear", () => {
66
66
  const c = copy("linear", "x", { count: 4, length: 120 }, e);
67
67
  render();
68
68
  const shapes = c.getShapes();
69
- expect(shapes).toHaveLength(3);
70
- const bbox = ShapeOps.getBoundingBox(shapes[0]);
69
+ expect(shapes).toHaveLength(4);
70
+ const bbox = ShapeOps.getBoundingBox(shapes[1]);
71
71
  expect(bbox.minX).toBeCloseTo(40, 0);
72
72
  });
73
73
  it("should create a 2D grid with multiple axes", () => {
@@ -0,0 +1,97 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { setupOC, render } from "../setup.js";
3
+ import { getSceneManager } from "../../scene-manager.js";
4
+ import { SceneCompare } from "../../rendering/scene-compare.js";
5
+ import sketch from "../../core/sketch.js";
6
+ import extrude from "../../core/extrude.js";
7
+ import axis from "../../core/axis.js";
8
+ import repeat from "../../core/repeat.js";
9
+ import sweep from "../../core/sweep.js";
10
+ import cut from "../../core/cut.js";
11
+ import remove from "../../core/remove.js";
12
+ import { rect, circle, vLine, tArc, tLine, slot, move, hMove } from "../../core/2d/index.js";
13
+ import copy from "../../core/copy.js";
14
+ // Regression: re-rendering a scene whose `repeat("circular", ...)` axis is an
15
+ // AxisFromEdge derived from a sketch line used to crash inside
16
+ // SceneCompare.compare with "Cannot read properties of undefined (reading
17
+ // 'origin')". RepeatCircular.compareTo eagerly called getAxis() on the
18
+ // new-scene axis source, which hadn't been built yet (compare runs pre-render).
19
+ describe("repeat circular cache-compare on unbuilt axis source", () => {
20
+ setupOC();
21
+ function buildScene(includeRemove) {
22
+ const spine = sketch("front", () => {
23
+ vLine(1.5);
24
+ tArc(-4, 45);
25
+ const topSegment = tLine(1.5);
26
+ return { topSegment };
27
+ }).reusable();
28
+ const profile = sketch("top", () => {
29
+ const innerPipe = circle(1.5);
30
+ const outerPipe = circle(2);
31
+ return { innerPipe, outerPipe };
32
+ });
33
+ const pipe = sweep(spine, profile.regions.outerPipe);
34
+ sketch("top", () => {
35
+ rect(3.5).centered().radius(0.5);
36
+ move([-2.5 / 2, -2.5 / 2]);
37
+ const c = circle(0.5);
38
+ copy("circular", [0, 0], { count: 4, angle: 360 }, c);
39
+ });
40
+ extrude(0.375);
41
+ sketch(pipe.endFaces(), () => {
42
+ circle(4);
43
+ });
44
+ const upperFlange = extrude(-0.625);
45
+ sweep(spine, profile.regions.innerPipe).remove();
46
+ const slots = sketch(upperFlange.endFaces(), () => {
47
+ hMove(3.25 / 2);
48
+ const outerSlot = slot(1, 0.75 / 2);
49
+ const innerSlot = slot(1, 0.45 / 2);
50
+ return { outerSlot, innerSlot };
51
+ });
52
+ const s1 = cut(slots.regions.innerSlot);
53
+ const s2 = cut(0.25, slots.regions.outerSlot);
54
+ const a = axis(spine.regions.topSegment);
55
+ repeat("circular", a, { count: 4, angle: 360 }, s1, s2);
56
+ if (includeRemove) {
57
+ remove(spine);
58
+ }
59
+ }
60
+ function currentScene() {
61
+ const mgr = getSceneManager();
62
+ if (!mgr) {
63
+ throw new Error("Scene manager not initialized");
64
+ }
65
+ return mgr.currentScene;
66
+ }
67
+ function startNewScene() {
68
+ const mgr = getSceneManager();
69
+ if (!mgr) {
70
+ throw new Error("Scene manager not initialized");
71
+ }
72
+ return mgr.startScene();
73
+ }
74
+ it("re-parsing the same script with cache compare does not throw", () => {
75
+ buildScene(false);
76
+ render();
77
+ const previousScene = currentScene();
78
+ expect(previousScene.getAllSceneObjects().some(o => o.getError())).toBe(false);
79
+ const newScene = startNewScene();
80
+ buildScene(false);
81
+ SceneCompare.compare(previousScene, newScene);
82
+ const rendered = render();
83
+ const errored = rendered.getAllSceneObjects().filter(o => o.getError());
84
+ expect(errored.map(o => `${o.getUniqueType()}: ${o.getError()}`)).toEqual([]);
85
+ });
86
+ it("appending remove(spine) after a cached render does not throw", () => {
87
+ buildScene(false);
88
+ render();
89
+ const previousScene = currentScene();
90
+ const newScene = startNewScene();
91
+ buildScene(true);
92
+ SceneCompare.compare(previousScene, newScene);
93
+ const rendered = render();
94
+ const errored = rendered.getAllSceneObjects().filter(o => o.getError());
95
+ expect(errored.map(o => `${o.getUniqueType()}: ${o.getError()}`)).toEqual([]);
96
+ });
97
+ });