fluidcad 0.0.32 → 0.0.34
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.
- package/README.md +3 -2
- package/bin/commands/init.js +55 -0
- package/bin/commands/mcp.js +33 -0
- package/bin/commands/serve.js +77 -0
- package/bin/fluidcad.js +15 -107
- package/lib/dist/common/scene-object.d.ts +4 -1
- package/lib/dist/common/scene-object.js +9 -2
- package/lib/dist/common/solid.d.ts +4 -1
- package/lib/dist/common/solid.js +13 -0
- package/lib/dist/core/2d/tarc.d.ts +20 -2
- package/lib/dist/core/2d/tarc.js +24 -0
- package/lib/dist/core/index.d.ts +2 -1
- package/lib/dist/core/index.js +1 -0
- package/lib/dist/core/interfaces.d.ts +107 -2
- package/lib/dist/core/load.d.ts +2 -2
- package/lib/dist/core/repeat.js +62 -46
- package/lib/dist/core/rib.d.ts +18 -0
- package/lib/dist/core/rib.js +37 -0
- package/lib/dist/features/2d/arc.d.ts +8 -2
- package/lib/dist/features/2d/arc.js +94 -17
- package/lib/dist/features/2d/back.js +3 -2
- package/lib/dist/features/2d/sketch.d.ts +4 -0
- package/lib/dist/features/2d/sketch.js +21 -0
- package/lib/dist/features/2d/tarc-constrained.d.ts +2 -0
- package/lib/dist/features/2d/tarc-constrained.js +8 -0
- package/lib/dist/features/2d/tarc-radius-to-object.d.ts +16 -0
- package/lib/dist/features/2d/tarc-radius-to-object.js +58 -0
- package/lib/dist/features/2d/tarc-to-object.d.ts +18 -0
- package/lib/dist/features/2d/tarc-to-object.js +66 -0
- package/lib/dist/features/2d/tarc-to-point-tangent.d.ts +2 -0
- package/lib/dist/features/2d/tarc-to-point-tangent.js +3 -0
- package/lib/dist/features/2d/tarc-to-point.d.ts +2 -0
- package/lib/dist/features/2d/tarc-to-point.js +3 -0
- package/lib/dist/features/2d/tarc-with-tangent.d.ts +2 -0
- package/lib/dist/features/2d/tarc-with-tangent.js +3 -0
- package/lib/dist/features/2d/tarc.d.ts +2 -0
- package/lib/dist/features/2d/tarc.js +3 -0
- package/lib/dist/features/extrude-base.d.ts +9 -0
- package/lib/dist/features/extrude-base.js +22 -0
- package/lib/dist/features/extrude-to-face.js +1 -5
- package/lib/dist/features/extrude-two-distances.js +1 -2
- package/lib/dist/features/extrude.js +1 -2
- package/lib/dist/features/load.d.ts +6 -0
- package/lib/dist/features/load.js +53 -1
- package/lib/dist/features/mirror-feature.d.ts +3 -2
- package/lib/dist/features/mirror-feature.js +1 -1
- package/lib/dist/features/repeat-circular.d.ts +3 -3
- package/lib/dist/features/repeat-circular.js +8 -1
- package/lib/dist/features/repeat-linear.d.ts +4 -2
- package/lib/dist/features/repeat-linear.js +10 -1
- package/lib/dist/features/repeat-matrix.d.ts +3 -1
- package/lib/dist/features/repeat-matrix.js +7 -2
- package/lib/dist/features/rib.d.ts +31 -0
- package/lib/dist/features/rib.js +321 -0
- package/lib/dist/features/select.d.ts +1 -0
- package/lib/dist/features/select.js +81 -10
- package/lib/dist/features/shell.d.ts +4 -1
- package/lib/dist/features/shell.js +14 -3
- package/lib/dist/filters/edge/belongs-to-face.d.ts +12 -9
- package/lib/dist/filters/edge/belongs-to-face.js +64 -15
- package/lib/dist/filters/filter-builder-base.d.ts +25 -0
- package/lib/dist/filters/filter-builder-base.js +47 -0
- package/lib/dist/filters/filter.js +39 -14
- package/lib/dist/filters/from-object.d.ts +4 -0
- package/lib/dist/filters/from-object.js +10 -0
- package/lib/dist/helpers/clone-transform.d.ts +2 -1
- package/lib/dist/helpers/scene-helpers.d.ts +1 -1
- package/lib/dist/helpers/scene-helpers.js +146 -12
- package/lib/dist/index.d.ts +7 -1
- package/lib/dist/index.js +3 -3
- package/lib/dist/io/file-import.d.ts +5 -1
- package/lib/dist/io/file-import.js +29 -18
- package/lib/dist/math/lazy-matrix.d.ts +31 -0
- package/lib/dist/math/lazy-matrix.js +66 -0
- package/lib/dist/oc/color-transfer.d.ts +19 -8
- package/lib/dist/oc/color-transfer.js +70 -12
- package/lib/dist/oc/constraints/constraint-solver-adaptor.d.ts +5 -0
- package/lib/dist/oc/constraints/constraint-solver-adaptor.js +16 -0
- package/lib/dist/oc/constraints/constraint-solver.d.ts +4 -0
- package/lib/dist/oc/constraints/curve/curve-constraint-solver.d.ts +4 -0
- package/lib/dist/oc/constraints/curve/curve-constraint-solver.js +3 -0
- package/lib/dist/oc/constraints/geometric/geometric-constraint-solver.d.ts +6 -1
- package/lib/dist/oc/constraints/geometric/geometric-constraint-solver.js +4 -0
- package/lib/dist/oc/constraints/geometric/tangent-arc-from-point-tangent.d.ts +8 -0
- package/lib/dist/oc/constraints/geometric/tangent-arc-from-point-tangent.js +111 -0
- package/lib/dist/oc/constraints/geometric/tangent-arc-radius-to-object.d.ts +8 -0
- package/lib/dist/oc/constraints/geometric/tangent-arc-radius-to-object.js +161 -0
- package/lib/dist/oc/extrude-ops.d.ts +2 -1
- package/lib/dist/oc/extrude-ops.js +51 -2
- package/lib/dist/oc/mesh.d.ts +9 -4
- package/lib/dist/oc/mesh.js +14 -13
- package/lib/dist/oc/rib-ops.d.ts +35 -0
- package/lib/dist/oc/rib-ops.js +619 -0
- package/lib/dist/oc/shell-ops.d.ts +2 -1
- package/lib/dist/oc/shell-ops.js +5 -2
- package/lib/dist/oc/topology-index.d.ts +6 -0
- package/lib/dist/oc/topology-index.js +36 -0
- package/lib/dist/rendering/mesh-builder.d.ts +3 -0
- package/lib/dist/rendering/mesh-builder.js +8 -4
- package/lib/dist/rendering/render-edge.d.ts +2 -1
- package/lib/dist/rendering/render-edge.js +2 -2
- package/lib/dist/rendering/render-face.d.ts +2 -1
- package/lib/dist/rendering/render-face.js +2 -2
- package/lib/dist/rendering/render-solid.d.ts +2 -1
- package/lib/dist/rendering/render-solid.js +3 -5
- package/lib/dist/rendering/render-wire.d.ts +2 -1
- package/lib/dist/rendering/render-wire.js +2 -2
- package/lib/dist/rendering/render.d.ts +4 -0
- package/lib/dist/rendering/render.js +50 -2
- package/lib/dist/rendering/scene-compare.js +3 -0
- package/lib/dist/rendering/scene.d.ts +1 -0
- package/lib/dist/rendering/scene.js +4 -0
- package/lib/dist/scene-manager.d.ts +4 -2
- package/lib/dist/scene-manager.js +12 -4
- package/lib/dist/tests/features/2d/arc.test.js +64 -0
- package/lib/dist/tests/features/2d/back.test.js +17 -1
- package/lib/dist/tests/features/2d/tarc.test.js +157 -0
- package/lib/dist/tests/features/color-lineage.test.js +18 -0
- package/lib/dist/tests/features/filter-positional.test.d.ts +1 -0
- package/lib/dist/tests/features/filter-positional.test.js +129 -0
- package/lib/dist/tests/features/repeat-user-repro.test.d.ts +1 -0
- package/lib/dist/tests/features/repeat-user-repro.test.js +60 -0
- package/lib/dist/tests/features/rib.test.d.ts +1 -0
- package/lib/dist/tests/features/rib.test.js +598 -0
- package/lib/dist/tests/features/shell.test.js +36 -0
- package/lib/dist/tests/global-setup.js +2 -1
- package/lib/dist/tests/helpers/extract-blocks.d.ts +9 -0
- package/lib/dist/tests/helpers/extract-blocks.js +56 -0
- package/lib/dist/tests/llm-docs-examples.test.d.ts +1 -0
- package/lib/dist/tests/llm-docs-examples.test.js +62 -0
- package/lib/dist/tests/scene-compare.test.d.ts +1 -0
- package/lib/dist/tests/scene-compare.test.js +77 -0
- package/lib/dist/tests/setup.js +2 -1
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/llm-docs/.coverage-allowlist.txt +9 -0
- package/llm-docs/api/arc.md +48 -0
- package/llm-docs/api/axis.md +42 -0
- package/llm-docs/api/bezier.md +42 -0
- package/llm-docs/api/booleans.md +44 -0
- package/llm-docs/api/chamfer.md +40 -0
- package/llm-docs/api/circle.md +36 -0
- package/llm-docs/api/color.md +34 -0
- package/llm-docs/api/connect.md +41 -0
- package/llm-docs/api/constraint-qualifiers.md +48 -0
- package/llm-docs/api/copy.md +63 -0
- package/llm-docs/api/cursor-lines.md +50 -0
- package/llm-docs/api/cursor-move.md +61 -0
- package/llm-docs/api/cut.md +55 -0
- package/llm-docs/api/draft.md +36 -0
- package/llm-docs/api/edge-filter.md +57 -0
- package/llm-docs/api/ellipse.md +34 -0
- package/llm-docs/api/extrude.md +74 -0
- package/llm-docs/api/face-filter.md +61 -0
- package/llm-docs/api/fillet.md +51 -0
- package/llm-docs/api/index.json +139 -0
- package/llm-docs/api/line.md +42 -0
- package/llm-docs/api/load.md +37 -0
- package/llm-docs/api/local.md +38 -0
- package/llm-docs/api/loft.md +37 -0
- package/llm-docs/api/mirror.md +44 -0
- package/llm-docs/api/offset.md +36 -0
- package/llm-docs/api/part.md +40 -0
- package/llm-docs/api/plane.md +44 -0
- package/llm-docs/api/polygon.md +37 -0
- package/llm-docs/api/primitive-solids.md +39 -0
- package/llm-docs/api/project-intersect.md +48 -0
- package/llm-docs/api/rect.md +48 -0
- package/llm-docs/api/remove.md +32 -0
- package/llm-docs/api/repeat.md +79 -0
- package/llm-docs/api/revolve.md +38 -0
- package/llm-docs/api/rib.md +40 -0
- package/llm-docs/api/rotate.md +37 -0
- package/llm-docs/api/select.md +41 -0
- package/llm-docs/api/shell.md +41 -0
- package/llm-docs/api/sketch.md +76 -0
- package/llm-docs/api/slot.md +36 -0
- package/llm-docs/api/split-trim.md +42 -0
- package/llm-docs/api/sweep.md +43 -0
- package/llm-docs/api/tarc.md +45 -0
- package/llm-docs/api/tcircle.md +38 -0
- package/llm-docs/api/tline.md +42 -0
- package/llm-docs/api/translate.md +40 -0
- package/llm-docs/api/types/aline.md +35 -0
- package/llm-docs/api/types/arc-angles.md +29 -0
- package/llm-docs/api/types/arc-points.md +48 -0
- package/llm-docs/api/types/axis-like.md +38 -0
- package/llm-docs/api/types/axis.md +21 -0
- package/llm-docs/api/types/boolean-operation.md +50 -0
- package/llm-docs/api/types/circular-repeat-options.md +31 -0
- package/llm-docs/api/types/common.md +32 -0
- package/llm-docs/api/types/cut.md +125 -0
- package/llm-docs/api/types/draft.md +21 -0
- package/llm-docs/api/types/extrudable-geometry.md +23 -0
- package/llm-docs/api/types/extrude.md +194 -0
- package/llm-docs/api/types/geometry.md +51 -0
- package/llm-docs/api/types/hline.md +35 -0
- package/llm-docs/api/types/linear-repeat-options.md +31 -0
- package/llm-docs/api/types/loft.md +154 -0
- package/llm-docs/api/types/mirror.md +35 -0
- package/llm-docs/api/types/offset.md +31 -0
- package/llm-docs/api/types/plane-like.md +35 -0
- package/llm-docs/api/types/plane-transform-options.md +29 -0
- package/llm-docs/api/types/plane.md +21 -0
- package/llm-docs/api/types/point-like.md +22 -0
- package/llm-docs/api/types/point2dlike.md +26 -0
- package/llm-docs/api/types/polygon.md +46 -0
- package/llm-docs/api/types/rect.md +128 -0
- package/llm-docs/api/types/revolve.md +102 -0
- package/llm-docs/api/types/rib.md +133 -0
- package/llm-docs/api/types/scene-object.md +33 -0
- package/llm-docs/api/types/select.md +21 -0
- package/llm-docs/api/types/shell.md +54 -0
- package/llm-docs/api/types/slot.md +43 -0
- package/llm-docs/api/types/sweep.md +189 -0
- package/llm-docs/api/types/tangent-arc-two-objects.md +46 -0
- package/llm-docs/api/types/transformable.md +93 -0
- package/llm-docs/api/types/trim.md +27 -0
- package/llm-docs/api/types/two-objects-tangent-line.md +46 -0
- package/llm-docs/api/types/vertex.md +17 -0
- package/llm-docs/api/types/vline.md +35 -0
- package/llm-docs/concepts/coordinate-system.md +45 -0
- package/llm-docs/concepts/history-and-rollback.md +40 -0
- package/llm-docs/concepts/last-selection.md +49 -0
- package/llm-docs/concepts/scene-graph.md +37 -0
- package/llm-docs/index.json +1750 -0
- package/mcp/dist/client.d.ts +64 -0
- package/mcp/dist/client.js +248 -0
- package/mcp/dist/discovery.d.ts +11 -0
- package/mcp/dist/discovery.js +78 -0
- package/mcp/dist/docs-index.d.ts +81 -0
- package/mcp/dist/docs-index.js +261 -0
- package/mcp/dist/resources.d.ts +4 -0
- package/mcp/dist/resources.js +115 -0
- package/mcp/dist/server.d.ts +12 -0
- package/mcp/dist/server.js +489 -0
- package/mcp/dist/tools/coordination.d.ts +9 -0
- package/mcp/dist/tools/coordination.js +46 -0
- package/mcp/dist/tools/docs.d.ts +66 -0
- package/mcp/dist/tools/docs.js +122 -0
- package/mcp/dist/tools/engine.d.ts +56 -0
- package/mcp/dist/tools/engine.js +145 -0
- package/mcp/dist/tools/inspection.d.ts +75 -0
- package/mcp/dist/tools/inspection.js +121 -0
- package/mcp/dist/tools/screenshot.d.ts +63 -0
- package/mcp/dist/tools/screenshot.js +263 -0
- package/mcp/dist/tools/source.d.ts +84 -0
- package/mcp/dist/tools/source.js +434 -0
- package/mcp/dist/tools/workspaces.d.ts +13 -0
- package/mcp/dist/tools/workspaces.js +33 -0
- package/mcp/dist/types.d.ts +18 -0
- package/mcp/dist/types.js +11 -0
- package/package.json +19 -5
- package/server/dist/code-editor.d.ts +36 -0
- package/server/dist/code-editor.js +8 -0
- package/server/dist/fluidcad-server.d.ts +50 -0
- package/server/dist/fluidcad-server.js +153 -1
- package/server/dist/global-registry.d.ts +30 -0
- package/server/dist/global-registry.js +126 -0
- package/server/dist/index.js +171 -26
- package/server/dist/instance-file.d.ts +31 -0
- package/server/dist/instance-file.js +73 -0
- package/server/dist/lint-fluid-js.d.ts +15 -0
- package/server/dist/lint-fluid-js.js +271 -0
- package/server/dist/routes/editor.d.ts +24 -0
- package/server/dist/routes/editor.js +44 -0
- package/server/dist/routes/export.d.ts +1 -1
- package/server/dist/routes/export.js +45 -8
- package/server/dist/routes/health.d.ts +7 -0
- package/server/dist/routes/health.js +14 -0
- package/server/dist/routes/lint.d.ts +10 -0
- package/server/dist/routes/lint.js +28 -0
- package/server/dist/routes/render.d.ts +33 -0
- package/server/dist/routes/render.js +34 -0
- package/server/dist/routes/scene.d.ts +5 -0
- package/server/dist/routes/scene.js +48 -0
- package/server/dist/routes/screenshot.js +68 -1
- package/server/dist/ws-protocol.d.ts +56 -2
- package/ui/dist/assets/{index-DMw0OYCF.js → index-BdqrMDRu.js} +30 -30
- package/ui/dist/index.html +1 -1
|
@@ -1,16 +1,55 @@
|
|
|
1
|
+
import type { CompileError } from './ws-protocol.ts';
|
|
1
2
|
export type SceneRenderedData = {
|
|
2
3
|
absPath: string;
|
|
3
4
|
result: any[];
|
|
4
5
|
rollbackStop: number;
|
|
5
6
|
breakpointHit?: boolean;
|
|
6
7
|
};
|
|
8
|
+
export type SceneSummaryObject = {
|
|
9
|
+
index: number;
|
|
10
|
+
id: string;
|
|
11
|
+
kind: string;
|
|
12
|
+
uniqueKind: string;
|
|
13
|
+
name: string;
|
|
14
|
+
params: any;
|
|
15
|
+
sourceLocation?: {
|
|
16
|
+
filePath: string;
|
|
17
|
+
line: number;
|
|
18
|
+
column: number;
|
|
19
|
+
};
|
|
20
|
+
shapeIds: string[];
|
|
21
|
+
fromCache: boolean;
|
|
22
|
+
hasError: boolean;
|
|
23
|
+
errorMessage?: string;
|
|
24
|
+
containerId: string | null;
|
|
25
|
+
isContainer: boolean;
|
|
26
|
+
visible: boolean;
|
|
27
|
+
};
|
|
28
|
+
export type SceneSummary = {
|
|
29
|
+
schemaVersion: 1;
|
|
30
|
+
file: string;
|
|
31
|
+
objects: SceneSummaryObject[];
|
|
32
|
+
rollbackStop: number;
|
|
33
|
+
compileError: CompileError | null;
|
|
34
|
+
};
|
|
35
|
+
export type ShapeListEntry = {
|
|
36
|
+
shapeId: string;
|
|
37
|
+
type: string;
|
|
38
|
+
sceneObjectId: string;
|
|
39
|
+
};
|
|
40
|
+
export type ShapeList = {
|
|
41
|
+
shapes: ShapeListEntry[];
|
|
42
|
+
};
|
|
7
43
|
export declare class FluidCadServer {
|
|
8
44
|
private viteManager;
|
|
9
45
|
private sceneManager;
|
|
10
46
|
private previousScenes;
|
|
11
47
|
private renderingCache;
|
|
48
|
+
private lastRendered;
|
|
12
49
|
private currentFileName;
|
|
13
50
|
private currentFilePath;
|
|
51
|
+
private lastRollbackStop;
|
|
52
|
+
private compileError;
|
|
14
53
|
init(workspacePath: string): Promise<void>;
|
|
15
54
|
processFile(filePath: string, ignoreCache?: boolean): Promise<SceneRenderedData | null>;
|
|
16
55
|
updateLiveCode(fileName: string, code: string): Promise<SceneRenderedData | null>;
|
|
@@ -32,4 +71,15 @@ export declare class FluidCadServer {
|
|
|
32
71
|
fileName: string;
|
|
33
72
|
} | null;
|
|
34
73
|
hitTest(shapeId: string, rayOrigin: [number, number, number], rayDir: [number, number, number], edgeThreshold: number): any;
|
|
74
|
+
setCompileError(err: CompileError | null): void;
|
|
75
|
+
getCompileError(): CompileError | null;
|
|
76
|
+
getCurrentFileName(): string;
|
|
77
|
+
/**
|
|
78
|
+
* Test-only seam: stage a scene under the given file name so the inspection
|
|
79
|
+
* accessors can read it without running the vite pipeline. Production code
|
|
80
|
+
* never calls this — `processFile` populates the same map.
|
|
81
|
+
*/
|
|
82
|
+
_setSceneForTesting(fileName: string, scene: any, rollbackStop?: number): void;
|
|
83
|
+
getSceneSummary(): SceneSummary | null;
|
|
84
|
+
getShapesList(): ShapeList | null;
|
|
35
85
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
1
2
|
import { join } from 'path';
|
|
2
3
|
import { existsSync } from 'fs';
|
|
3
4
|
import { ViteManager } from "./vite-manager.js";
|
|
@@ -8,8 +9,16 @@ export class FluidCadServer {
|
|
|
8
9
|
sceneManager;
|
|
9
10
|
previousScenes = new Map();
|
|
10
11
|
renderingCache = new Map();
|
|
12
|
+
// Per-file hash + full result of the most recent successful render. Any
|
|
13
|
+
// incoming render request — IPC live-update from the extension, watcher-
|
|
14
|
+
// driven live-update under `fluidcad serve`, or HTTP /api/render from the
|
|
15
|
+
// MCP — short-circuits here when the new code hashes to the same value.
|
|
16
|
+
// Avoids redundant OCC work when multiple producers see the same write.
|
|
17
|
+
lastRendered = new Map();
|
|
11
18
|
currentFileName = '';
|
|
12
19
|
currentFilePath = '';
|
|
20
|
+
lastRollbackStop = -1;
|
|
21
|
+
compileError = null;
|
|
13
22
|
async init(workspacePath) {
|
|
14
23
|
await this.viteManager.init(workspacePath);
|
|
15
24
|
const initFilePath = normalizePath(join(workspacePath, 'init.js'));
|
|
@@ -29,6 +38,8 @@ export class FluidCadServer {
|
|
|
29
38
|
if (!ignoreCache) {
|
|
30
39
|
const fromCache = this.renderingCache.get(normalizedFileName);
|
|
31
40
|
if (fromCache) {
|
|
41
|
+
this.lastRollbackStop = fromCache.length - 1;
|
|
42
|
+
this.compileError = null;
|
|
32
43
|
return {
|
|
33
44
|
absPath: normalizedFileName,
|
|
34
45
|
result: fromCache,
|
|
@@ -67,6 +78,8 @@ export class FluidCadServer {
|
|
|
67
78
|
if (!filePath.startsWith('virtual:live-render')) {
|
|
68
79
|
this.renderingCache.set(normalizedFileName, result);
|
|
69
80
|
}
|
|
81
|
+
this.lastRollbackStop = result.length - 1;
|
|
82
|
+
this.compileError = null;
|
|
70
83
|
return {
|
|
71
84
|
absPath: normalizedFileName,
|
|
72
85
|
result,
|
|
@@ -82,10 +95,27 @@ export class FluidCadServer {
|
|
|
82
95
|
}
|
|
83
96
|
async updateLiveCode(fileName, code) {
|
|
84
97
|
fileName = normalizePath(fileName);
|
|
98
|
+
// Dedup against the last successful render of this file. Multiple
|
|
99
|
+
// producers (editor live-update, save-triggered process-file, watcher,
|
|
100
|
+
// MCP /api/render) commonly hand us identical content; without this
|
|
101
|
+
// short-circuit each one would trigger a redundant OCC pass.
|
|
102
|
+
const hash = hashCode(code);
|
|
103
|
+
const cached = this.lastRendered.get(fileName);
|
|
104
|
+
if (cached && cached.hash === hash) {
|
|
105
|
+
this.compileError = null;
|
|
106
|
+
this.currentFileName = fileName;
|
|
107
|
+
this.currentFilePath = `virtual:live-render:${fileName}`;
|
|
108
|
+
this.lastRollbackStop = cached.data.rollbackStop;
|
|
109
|
+
return cached.data;
|
|
110
|
+
}
|
|
85
111
|
const id = `virtual:live-render:${fileName}`;
|
|
86
112
|
this.viteManager.setBuffer(id, code);
|
|
87
113
|
this.renderingCache.delete(fileName);
|
|
88
|
-
|
|
114
|
+
const result = await this.processFile(id, true);
|
|
115
|
+
if (result) {
|
|
116
|
+
this.lastRendered.set(fileName, { hash, data: result });
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
89
119
|
}
|
|
90
120
|
async rollbackFromUI(index) {
|
|
91
121
|
return this.rollback(this.currentFileName, index);
|
|
@@ -96,6 +126,7 @@ export class FluidCadServer {
|
|
|
96
126
|
}
|
|
97
127
|
this.previousScenes.delete(this.currentFileName);
|
|
98
128
|
this.renderingCache.delete(this.currentFileName);
|
|
129
|
+
this.lastRendered.delete(this.currentFileName);
|
|
99
130
|
return this.processFile(this.currentFilePath, true);
|
|
100
131
|
}
|
|
101
132
|
async rollback(fileName, index) {
|
|
@@ -110,6 +141,7 @@ export class FluidCadServer {
|
|
|
110
141
|
const rollbackIndex = index >= totalObjects - 1 ? totalObjects - 1 : index;
|
|
111
142
|
this.sceneManager.rollbackScene(scene, rollbackIndex);
|
|
112
143
|
const result = scene.getRenderedObjects();
|
|
144
|
+
this.lastRollbackStop = index;
|
|
113
145
|
return {
|
|
114
146
|
absPath: fileName,
|
|
115
147
|
result,
|
|
@@ -173,4 +205,124 @@ export class FluidCadServer {
|
|
|
173
205
|
}
|
|
174
206
|
return this.sceneManager.hitTest(scene, shapeId, rayOrigin, rayDir, edgeThreshold);
|
|
175
207
|
}
|
|
208
|
+
setCompileError(err) {
|
|
209
|
+
this.compileError = err;
|
|
210
|
+
}
|
|
211
|
+
getCompileError() {
|
|
212
|
+
return this.compileError;
|
|
213
|
+
}
|
|
214
|
+
getCurrentFileName() {
|
|
215
|
+
return this.currentFileName;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Test-only seam: stage a scene under the given file name so the inspection
|
|
219
|
+
* accessors can read it without running the vite pipeline. Production code
|
|
220
|
+
* never calls this — `processFile` populates the same map.
|
|
221
|
+
*/
|
|
222
|
+
_setSceneForTesting(fileName, scene, rollbackStop = -1) {
|
|
223
|
+
this.currentFileName = fileName;
|
|
224
|
+
this.previousScenes.set(fileName, scene);
|
|
225
|
+
this.lastRollbackStop = rollbackStop;
|
|
226
|
+
}
|
|
227
|
+
getSceneSummary() {
|
|
228
|
+
if (!this.currentFileName) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
const scene = this.previousScenes.get(this.currentFileName);
|
|
232
|
+
if (!scene) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
const rendered = scene.getRenderedObjects();
|
|
236
|
+
const objects = rendered.map((r, index) => ({
|
|
237
|
+
index,
|
|
238
|
+
id: r.id,
|
|
239
|
+
kind: r.type,
|
|
240
|
+
uniqueKind: r.uniqueType,
|
|
241
|
+
name: r.name,
|
|
242
|
+
params: sanitizeParams(r.object),
|
|
243
|
+
sourceLocation: r.sourceLocation,
|
|
244
|
+
shapeIds: (r.sceneShapes ?? []).map((s) => s.shapeId),
|
|
245
|
+
fromCache: !!r.fromCache,
|
|
246
|
+
hasError: !!r.hasError,
|
|
247
|
+
errorMessage: r.errorMessage,
|
|
248
|
+
containerId: r.parentId ?? null,
|
|
249
|
+
isContainer: !!r.isContainer,
|
|
250
|
+
visible: r.visible !== false,
|
|
251
|
+
}));
|
|
252
|
+
return {
|
|
253
|
+
schemaVersion: 1,
|
|
254
|
+
file: this.currentFileName,
|
|
255
|
+
objects,
|
|
256
|
+
rollbackStop: this.lastRollbackStop,
|
|
257
|
+
compileError: this.compileError,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
getShapesList() {
|
|
261
|
+
if (!this.currentFileName) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const scene = this.previousScenes.get(this.currentFileName);
|
|
265
|
+
if (!scene) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
const rendered = scene.getRenderedObjects();
|
|
269
|
+
const shapes = [];
|
|
270
|
+
for (const r of rendered) {
|
|
271
|
+
const sceneShapes = (r.sceneShapes ?? []);
|
|
272
|
+
for (const s of sceneShapes) {
|
|
273
|
+
shapes.push({
|
|
274
|
+
shapeId: s.shapeId,
|
|
275
|
+
type: s.shapeType,
|
|
276
|
+
sceneObjectId: r.id,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return { shapes };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Hash a `.fluid.js` source for dedup. Newlines are normalised to LF so a
|
|
285
|
+
* round-trip through an editor (CRLF) and a disk write (LF) hashes the
|
|
286
|
+
* same. SHA1 is plenty here — we just need a stable equality check, not
|
|
287
|
+
* collision resistance against an adversary.
|
|
288
|
+
*/
|
|
289
|
+
function hashCode(code) {
|
|
290
|
+
return createHash('sha1').update(code.replace(/\r\n/g, '\n')).digest('hex');
|
|
291
|
+
}
|
|
292
|
+
const MAX_PARAM_DEPTH = 6;
|
|
293
|
+
function sanitizeParams(value, depth = 0) {
|
|
294
|
+
if (value === null || value === undefined) {
|
|
295
|
+
return value ?? null;
|
|
296
|
+
}
|
|
297
|
+
if (typeof value === 'number') {
|
|
298
|
+
return Number.isFinite(value) ? value : null;
|
|
299
|
+
}
|
|
300
|
+
if (typeof value === 'string' || typeof value === 'boolean') {
|
|
301
|
+
return value;
|
|
302
|
+
}
|
|
303
|
+
if (depth >= MAX_PARAM_DEPTH) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
if (Array.isArray(value)) {
|
|
307
|
+
return value.map((v) => sanitizeParams(v, depth + 1));
|
|
308
|
+
}
|
|
309
|
+
if (typeof value === 'object') {
|
|
310
|
+
// A scene-object reference. Render as { ref: id } so the agent can chase
|
|
311
|
+
// it through other tools without us shipping the whole subtree.
|
|
312
|
+
const maybeId = value.id;
|
|
313
|
+
const isSceneObjectRef = typeof maybeId === 'string' &&
|
|
314
|
+
typeof value.getType === 'function';
|
|
315
|
+
if (isSceneObjectRef) {
|
|
316
|
+
return { ref: maybeId };
|
|
317
|
+
}
|
|
318
|
+
const out = {};
|
|
319
|
+
for (const [k, v] of Object.entries(value)) {
|
|
320
|
+
if (typeof v === 'function') {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
out[k] = sanitizeParams(v, depth + 1);
|
|
324
|
+
}
|
|
325
|
+
return out;
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
176
328
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const REGISTRY_DIR_NAME = ".fluidcad";
|
|
2
|
+
export declare const REGISTRY_FILE_NAME = "instances.json";
|
|
3
|
+
export type RegistryEntry = {
|
|
4
|
+
workspacePath: string;
|
|
5
|
+
port: number;
|
|
6
|
+
pid: number;
|
|
7
|
+
version: string;
|
|
8
|
+
startedAt: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function registryFilePath(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Add or replace an entry keyed by `workspacePath`. A new entry for an
|
|
13
|
+
* existing workspace (second window) supersedes the prior one.
|
|
14
|
+
*/
|
|
15
|
+
export declare function addInstance(entry: RegistryEntry): void;
|
|
16
|
+
/**
|
|
17
|
+
* Remove the entry that matches both workspacePath and pid. Matching on pid
|
|
18
|
+
* prevents a clean shutdown from clobbering a freshly-restarted instance.
|
|
19
|
+
*/
|
|
20
|
+
export declare function removeInstance(workspacePath: string, pid: number): void;
|
|
21
|
+
/**
|
|
22
|
+
* Read the registry, prune entries whose PIDs are no longer alive, and
|
|
23
|
+
* persist the pruned result. Returns the live entries.
|
|
24
|
+
*
|
|
25
|
+
* Liveness is `process.kill(pid, 0)` — sending signal 0 doesn't deliver
|
|
26
|
+
* anything but throws ESRCH if the process is gone. EPERM (running but owned
|
|
27
|
+
* by another user) counts as alive.
|
|
28
|
+
*/
|
|
29
|
+
export declare function readInstances(): RegistryEntry[];
|
|
30
|
+
export declare function isPidAlive(pid: number): boolean;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Global instance registry at ~/.fluidcad/instances.json — lets a single MCP
|
|
2
|
+
// process enumerate every running FluidCAD workspace on this machine.
|
|
3
|
+
//
|
|
4
|
+
// Read-modify-write is not crash-safe across multiple writers (we have no
|
|
5
|
+
// file lock), but the worst case is a dropped registry entry that the next
|
|
6
|
+
// successful write or stale-prune restores. Atomicity for the file itself is
|
|
7
|
+
// via tmp + rename.
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
export const REGISTRY_DIR_NAME = '.fluidcad';
|
|
12
|
+
export const REGISTRY_FILE_NAME = 'instances.json';
|
|
13
|
+
const EMPTY = { schemaVersion: 1, instances: [] };
|
|
14
|
+
function registryDir() {
|
|
15
|
+
return path.join(os.homedir(), REGISTRY_DIR_NAME);
|
|
16
|
+
}
|
|
17
|
+
export function registryFilePath() {
|
|
18
|
+
return path.join(registryDir(), REGISTRY_FILE_NAME);
|
|
19
|
+
}
|
|
20
|
+
function readRaw() {
|
|
21
|
+
try {
|
|
22
|
+
const raw = fs.readFileSync(registryFilePath(), 'utf8');
|
|
23
|
+
const parsed = JSON.parse(raw);
|
|
24
|
+
if (isRegistryFile(parsed)) {
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
return EMPTY;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return EMPTY;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function writeRaw(file) {
|
|
34
|
+
const dir = registryDir();
|
|
35
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
36
|
+
const destination = registryFilePath();
|
|
37
|
+
const tmp = path.join(dir, `${REGISTRY_FILE_NAME}.${process.pid}.tmp`);
|
|
38
|
+
const payload = JSON.stringify(file, null, 2) + '\n';
|
|
39
|
+
fs.writeFileSync(tmp, payload, { encoding: 'utf8', mode: 0o644 });
|
|
40
|
+
fs.renameSync(tmp, destination);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Add or replace an entry keyed by `workspacePath`. A new entry for an
|
|
44
|
+
* existing workspace (second window) supersedes the prior one.
|
|
45
|
+
*/
|
|
46
|
+
export function addInstance(entry) {
|
|
47
|
+
const file = readRaw();
|
|
48
|
+
const filtered = file.instances.filter((e) => e.workspacePath !== entry.workspacePath);
|
|
49
|
+
filtered.push(entry);
|
|
50
|
+
writeRaw({ schemaVersion: 1, instances: filtered });
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Remove the entry that matches both workspacePath and pid. Matching on pid
|
|
54
|
+
* prevents a clean shutdown from clobbering a freshly-restarted instance.
|
|
55
|
+
*/
|
|
56
|
+
export function removeInstance(workspacePath, pid) {
|
|
57
|
+
const file = readRaw();
|
|
58
|
+
const filtered = file.instances.filter((e) => !(e.workspacePath === workspacePath && e.pid === pid));
|
|
59
|
+
if (filtered.length === file.instances.length) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
writeRaw({ schemaVersion: 1, instances: filtered });
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read the registry, prune entries whose PIDs are no longer alive, and
|
|
66
|
+
* persist the pruned result. Returns the live entries.
|
|
67
|
+
*
|
|
68
|
+
* Liveness is `process.kill(pid, 0)` — sending signal 0 doesn't deliver
|
|
69
|
+
* anything but throws ESRCH if the process is gone. EPERM (running but owned
|
|
70
|
+
* by another user) counts as alive.
|
|
71
|
+
*/
|
|
72
|
+
export function readInstances() {
|
|
73
|
+
const file = readRaw();
|
|
74
|
+
const alive = [];
|
|
75
|
+
const dead = [];
|
|
76
|
+
for (const entry of file.instances) {
|
|
77
|
+
if (isPidAlive(entry.pid)) {
|
|
78
|
+
alive.push(entry);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
dead.push(entry);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (dead.length > 0) {
|
|
85
|
+
try {
|
|
86
|
+
writeRaw({ schemaVersion: 1, instances: alive });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Pruning is best-effort; readers still get the live set.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return alive;
|
|
93
|
+
}
|
|
94
|
+
export function isPidAlive(pid) {
|
|
95
|
+
try {
|
|
96
|
+
process.kill(pid, 0);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (err && err.code === 'EPERM') {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function isRegistryFile(value) {
|
|
107
|
+
if (typeof value !== 'object' || value === null) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const v = value;
|
|
111
|
+
if (v.schemaVersion !== 1 || !Array.isArray(v.instances)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
return v.instances.every(isRegistryEntry);
|
|
115
|
+
}
|
|
116
|
+
function isRegistryEntry(value) {
|
|
117
|
+
if (typeof value !== 'object' || value === null) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const v = value;
|
|
121
|
+
return (typeof v.workspacePath === 'string' &&
|
|
122
|
+
typeof v.port === 'number' &&
|
|
123
|
+
typeof v.pid === 'number' &&
|
|
124
|
+
typeof v.version === 'string' &&
|
|
125
|
+
typeof v.startedAt === 'string');
|
|
126
|
+
}
|