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.
- package/README.md +69 -0
- package/bin/commands/login.js +148 -0
- package/bin/commands/mcp.js +3 -2
- package/bin/commands/pack.js +49 -0
- package/bin/commands/publish.js +231 -0
- package/bin/fluidcad.js +6 -0
- package/bin/lib/api-client.js +48 -0
- package/bin/lib/browser.js +16 -0
- package/bin/lib/config.js +39 -0
- package/bin/lib/model-config.js +61 -0
- package/bin/lib/prompt.js +97 -0
- package/bin/lib/workspace.js +57 -0
- package/lib/dist/common/shape-factory.d.ts +2 -1
- package/lib/dist/common/shape-factory.js +4 -0
- package/lib/dist/common/transformable-primitive.d.ts +6 -5
- package/lib/dist/common/transformable-primitive.js +8 -7
- package/lib/dist/common/vertex.js +0 -1
- package/lib/dist/core/2d/aline.d.ts +4 -3
- package/lib/dist/core/2d/aline.js +3 -2
- package/lib/dist/core/2d/arc.d.ts +3 -2
- package/lib/dist/core/2d/arc.js +4 -3
- package/lib/dist/core/2d/bezier.d.ts +8 -6
- package/lib/dist/core/2d/circle.d.ts +4 -3
- package/lib/dist/core/2d/circle.js +3 -2
- package/lib/dist/core/2d/ellipse.d.ts +5 -4
- package/lib/dist/core/2d/ellipse.js +5 -4
- package/lib/dist/core/2d/hline.d.ts +4 -3
- package/lib/dist/core/2d/hline.js +5 -3
- package/lib/dist/core/2d/line.js +1 -0
- package/lib/dist/core/2d/offset.d.ts +3 -2
- package/lib/dist/core/2d/offset.js +6 -5
- package/lib/dist/core/2d/polygon.d.ts +5 -4
- package/lib/dist/core/2d/polygon.js +10 -9
- package/lib/dist/core/2d/rect.d.ts +4 -3
- package/lib/dist/core/2d/rect.js +10 -9
- package/lib/dist/core/2d/slot.d.ts +14 -6
- package/lib/dist/core/2d/slot.js +19 -8
- package/lib/dist/core/2d/vline.d.ts +4 -3
- package/lib/dist/core/2d/vline.js +5 -3
- package/lib/dist/core/chamfer.d.ts +5 -4
- package/lib/dist/core/chamfer.js +7 -6
- package/lib/dist/core/color.d.ts +3 -2
- package/lib/dist/core/color.js +2 -1
- package/lib/dist/core/cut.d.ts +4 -3
- package/lib/dist/core/cut.js +5 -4
- package/lib/dist/core/cylinder.d.ts +2 -1
- package/lib/dist/core/cylinder.js +2 -1
- package/lib/dist/core/draft.d.ts +3 -2
- package/lib/dist/core/draft.js +3 -2
- package/lib/dist/core/extrude.d.ts +4 -3
- package/lib/dist/core/extrude.js +5 -4
- package/lib/dist/core/fillet.d.ts +5 -4
- package/lib/dist/core/fillet.js +6 -5
- package/lib/dist/core/index.d.ts +1 -0
- package/lib/dist/core/index.js +1 -0
- package/lib/dist/core/interfaces.d.ts +25 -24
- package/lib/dist/core/param.d.ts +74 -0
- package/lib/dist/core/param.js +147 -0
- package/lib/dist/core/repeat.d.ts +2 -1
- package/lib/dist/core/repeat.js +10 -8
- package/lib/dist/core/revolve.d.ts +2 -1
- package/lib/dist/core/revolve.js +3 -2
- package/lib/dist/core/rib.d.ts +3 -2
- package/lib/dist/core/rib.js +6 -2
- package/lib/dist/core/rotate.d.ts +5 -4
- package/lib/dist/core/rotate.js +4 -3
- package/lib/dist/core/shell.d.ts +3 -2
- package/lib/dist/core/shell.js +3 -2
- package/lib/dist/core/sphere.d.ts +3 -2
- package/lib/dist/core/sphere.js +2 -1
- package/lib/dist/core/translate.d.ts +7 -6
- package/lib/dist/core/translate.js +6 -5
- package/lib/dist/features/2d/arc.js +5 -5
- package/lib/dist/features/2d/bezier.js +16 -16
- package/lib/dist/features/2d/circle.js +4 -0
- package/lib/dist/features/2d/ellipse.js +4 -0
- package/lib/dist/features/2d/hline.d.ts +3 -0
- package/lib/dist/features/2d/hline.js +9 -2
- package/lib/dist/features/2d/line.d.ts +3 -0
- package/lib/dist/features/2d/line.js +11 -3
- package/lib/dist/features/2d/sketch.js +5 -1
- package/lib/dist/features/2d/slot.d.ts +5 -0
- package/lib/dist/features/2d/slot.js +52 -7
- package/lib/dist/features/2d/tarc-to-point-tangent.js +3 -0
- package/lib/dist/features/2d/tarc-to-point.js +3 -0
- package/lib/dist/features/2d/tarc-with-tangent.js +3 -0
- package/lib/dist/features/2d/tarc.js +3 -0
- package/lib/dist/features/2d/vline.d.ts +3 -0
- package/lib/dist/features/2d/vline.js +9 -2
- package/lib/dist/features/copy-circular.d.ts +4 -3
- package/lib/dist/features/copy-circular.js +16 -9
- package/lib/dist/features/copy-circular2d.js +16 -9
- package/lib/dist/features/copy-linear.d.ts +4 -3
- package/lib/dist/features/copy-linear.js +18 -12
- package/lib/dist/features/copy-linear2d.js +18 -12
- package/lib/dist/features/extrude-base.d.ts +4 -3
- package/lib/dist/features/extrude-base.js +10 -3
- package/lib/dist/features/mirror-shape2d.js +2 -2
- package/lib/dist/features/repeat-base.d.ts +13 -0
- package/lib/dist/features/repeat-base.js +21 -0
- package/lib/dist/features/repeat-circular.d.ts +6 -5
- package/lib/dist/features/repeat-circular.js +3 -6
- package/lib/dist/features/repeat-linear.d.ts +7 -7
- package/lib/dist/features/repeat-linear.js +3 -6
- package/lib/dist/index.d.ts +5 -0
- package/lib/dist/index.js +8 -1
- package/lib/dist/io/file-import.d.ts +7 -0
- package/lib/dist/io/file-import.js +30 -10
- package/lib/dist/math/lazy-matrix.d.ts +5 -0
- package/lib/dist/math/lazy-matrix.js +78 -10
- package/lib/dist/oc/boolean-ops.d.ts +2 -2
- package/lib/dist/param-registry.d.ts +34 -0
- package/lib/dist/param-registry.js +60 -0
- package/lib/dist/rendering/mesh-builder.js +2 -1
- package/lib/dist/tests/features/copy-circular.test.js +1 -1
- package/lib/dist/tests/features/copy-linear.test.js +10 -10
- package/lib/dist/tests/features/repeat-user-repro-cache.test.d.ts +1 -0
- package/lib/dist/tests/features/repeat-user-repro-cache.test.js +97 -0
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/llm-docs/api/bezier.md +10 -11
- package/llm-docs/api/index.json +1 -1
- package/llm-docs/api/types/arc-points.md +2 -2
- package/llm-docs/api/types/cut.md +10 -10
- package/llm-docs/api/types/extrude.md +10 -10
- package/llm-docs/api/types/loft.md +6 -6
- package/llm-docs/api/types/revolve.md +6 -6
- package/llm-docs/api/types/rib.md +2 -2
- package/llm-docs/api/types/slot.md +2 -2
- package/llm-docs/api/types/sweep.md +10 -10
- package/llm-docs/api/types/transformable.md +14 -14
- package/llm-docs/index.json +12 -12
- package/mcp/dist/client.d.ts +1 -0
- package/mcp/dist/client.js +8 -1
- package/mcp/dist/server.js +14 -1
- package/mcp/dist/tools/engine.d.ts +16 -0
- package/mcp/dist/tools/engine.js +45 -0
- package/package.json +9 -3
- package/server/dist/api.d.ts +37 -0
- package/server/dist/api.js +44 -0
- package/server/dist/code-editor.d.ts +64 -0
- package/server/dist/code-editor.js +520 -2
- package/server/dist/fluidcad-server.d.ts +87 -1
- package/server/dist/fluidcad-server.js +254 -88
- package/server/dist/host/blocked-imports.d.ts +8 -0
- package/server/dist/host/blocked-imports.js +30 -0
- package/server/dist/{vite-manager.d.ts → host/local-scene-host.d.ts} +3 -1
- package/server/dist/{vite-manager.js → host/local-scene-host.js} +6 -26
- package/server/dist/host/scene-host.d.ts +19 -0
- package/server/dist/host/scene-host.js +1 -0
- package/server/dist/index.js +24 -117
- package/server/dist/model-package/capture-params.d.ts +19 -0
- package/server/dist/model-package/capture-params.js +42 -0
- package/server/dist/model-package/pack.d.ts +23 -0
- package/server/dist/model-package/pack.js +230 -0
- package/server/dist/model-package/types.d.ts +79 -0
- package/server/dist/model-package/types.js +17 -0
- package/server/dist/routes/hit-test.d.ts +3 -0
- package/server/dist/routes/hit-test.js +17 -0
- package/server/dist/routes/pack.d.ts +10 -0
- package/server/dist/routes/pack.js +47 -0
- package/server/dist/routes/params.d.ts +3 -0
- package/server/dist/routes/params.js +75 -0
- package/server/dist/routes/sketch-edits.d.ts +3 -0
- package/server/dist/routes/sketch-edits.js +542 -0
- package/server/dist/routes/timeline.d.ts +3 -0
- package/server/dist/routes/timeline.js +49 -0
- package/server/dist/server-core.d.ts +53 -0
- package/server/dist/server-core.js +147 -0
- package/server/dist/ws-protocol.d.ts +101 -2
- package/ui/dist/assets/index-CDJmUpFI.css +2 -0
- package/ui/dist/assets/index-MRqwG9Vh.js +5417 -0
- package/ui/dist/index.html +2 -2
- package/server/dist/routes/actions.d.ts +0 -3
- package/server/dist/routes/actions.js +0 -309
- package/ui/dist/assets/index-BdqrMDRu.js +0 -4946
- package/ui/dist/assets/index-DR7c2Qk9.css +0 -2
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { packModel } from "../model-package/pack.js";
|
|
3
|
+
/**
|
|
4
|
+
* `POST /api/pack` — produce a `.fluidpkg` (zip) archive of the currently
|
|
5
|
+
* rendered file. Pulls live param overrides and the last-known camera state
|
|
6
|
+
* from the running server so the archive matches what the user is seeing.
|
|
7
|
+
* Returns the binary archive directly (application/zip).
|
|
8
|
+
*/
|
|
9
|
+
export function createPackRouter(fluidCadServer, workspacePath, fluidcadVersion, getLastCameraState) {
|
|
10
|
+
const router = Router();
|
|
11
|
+
router.post('/pack', async (req, res) => {
|
|
12
|
+
const currentFile = fluidCadServer.getCurrentFileName();
|
|
13
|
+
if (!currentFile) {
|
|
14
|
+
res.status(404).json({ error: 'No active scene to pack' });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const { name, description } = (req.body ?? {});
|
|
18
|
+
const cameraMsg = getLastCameraState();
|
|
19
|
+
const camera = cameraMsg
|
|
20
|
+
? {
|
|
21
|
+
position: cameraMsg.position,
|
|
22
|
+
target: cameraMsg.target,
|
|
23
|
+
up: cameraMsg.up,
|
|
24
|
+
projection: cameraMsg.projection,
|
|
25
|
+
}
|
|
26
|
+
: undefined;
|
|
27
|
+
try {
|
|
28
|
+
const result = await packModel({
|
|
29
|
+
entryPath: currentFile,
|
|
30
|
+
workspacePath,
|
|
31
|
+
fluidcadVersion,
|
|
32
|
+
name,
|
|
33
|
+
description,
|
|
34
|
+
paramOverrides: fluidCadServer.getParamOverrides(currentFile),
|
|
35
|
+
camera,
|
|
36
|
+
});
|
|
37
|
+
res.setHeader('Content-Type', 'application/zip');
|
|
38
|
+
res.setHeader('X-FluidCAD-Package-Name', result.manifest.name);
|
|
39
|
+
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(result.manifest.name)}.fluidpkg"`);
|
|
40
|
+
res.send(result.zip);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
res.status(500).json({ error: err?.message ?? String(err) });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return router;
|
|
47
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
export function createParamsRouter(fluidCadServer, sendToExtension, broadcastToUI) {
|
|
3
|
+
const router = Router();
|
|
4
|
+
router.post('/recompute', async (_req, res) => {
|
|
5
|
+
const data = await fluidCadServer.recomputeCurrentFile();
|
|
6
|
+
if (!data) {
|
|
7
|
+
res.status(404).json({ error: 'No active scene' });
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
sendToExtension({
|
|
11
|
+
type: 'scene-rendered',
|
|
12
|
+
absPath: data.absPath,
|
|
13
|
+
result: data.result,
|
|
14
|
+
rollbackStop: data.rollbackStop,
|
|
15
|
+
});
|
|
16
|
+
broadcastToUI({
|
|
17
|
+
type: 'scene-rendered',
|
|
18
|
+
result: data.result,
|
|
19
|
+
absPath: data.absPath,
|
|
20
|
+
breakpointHit: data.breakpointHit,
|
|
21
|
+
params: data.params,
|
|
22
|
+
});
|
|
23
|
+
res.json({ success: true });
|
|
24
|
+
});
|
|
25
|
+
router.post('/set-param', async (req, res) => {
|
|
26
|
+
const { label, value } = req.body;
|
|
27
|
+
if (typeof label !== 'string') {
|
|
28
|
+
res.status(400).json({ error: 'Invalid label' });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
fluidCadServer.setParam(fluidCadServer.getCurrentFileName(), label, value);
|
|
32
|
+
const data = await fluidCadServer.recomputeCurrentFile();
|
|
33
|
+
if (!data) {
|
|
34
|
+
res.status(404).json({ error: 'No active scene' });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
sendToExtension({
|
|
38
|
+
type: 'scene-rendered',
|
|
39
|
+
absPath: data.absPath,
|
|
40
|
+
result: data.result,
|
|
41
|
+
rollbackStop: data.rollbackStop,
|
|
42
|
+
});
|
|
43
|
+
broadcastToUI({
|
|
44
|
+
type: 'scene-rendered',
|
|
45
|
+
result: data.result,
|
|
46
|
+
absPath: data.absPath,
|
|
47
|
+
rollbackStop: data.rollbackStop,
|
|
48
|
+
params: data.params,
|
|
49
|
+
});
|
|
50
|
+
res.json({ success: true });
|
|
51
|
+
});
|
|
52
|
+
router.post('/reset-params', async (_req, res) => {
|
|
53
|
+
fluidCadServer.resetParams(fluidCadServer.getCurrentFileName());
|
|
54
|
+
const data = await fluidCadServer.recomputeCurrentFile();
|
|
55
|
+
if (!data) {
|
|
56
|
+
res.status(404).json({ error: 'No active scene' });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
sendToExtension({
|
|
60
|
+
type: 'scene-rendered',
|
|
61
|
+
absPath: data.absPath,
|
|
62
|
+
result: data.result,
|
|
63
|
+
rollbackStop: data.rollbackStop,
|
|
64
|
+
});
|
|
65
|
+
broadcastToUI({
|
|
66
|
+
type: 'scene-rendered',
|
|
67
|
+
result: data.result,
|
|
68
|
+
absPath: data.absPath,
|
|
69
|
+
rollbackStop: data.rollbackStop,
|
|
70
|
+
params: data.params,
|
|
71
|
+
});
|
|
72
|
+
res.json({ success: true });
|
|
73
|
+
});
|
|
74
|
+
return router;
|
|
75
|
+
}
|
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { addBreakpoint, removeBreakpoint, toggleBreakpoint, clearBreakpoints, insertPoint, removePoint, addPick, removePick, setPickPoints, insertGeometryCallWithVariable, updateGeometryPosition, setLinePosition, setChainPositions, updateDimension, updateDimensionExpressionWithVariable, getDimensionExpression, extractVariablesInScope, setRectDimensions, } from "../code-editor.js";
|
|
3
|
+
const NEW_VAR_NAME_RE = /^[a-zA-Z_$][\w$]*$/;
|
|
4
|
+
function validateNewVariable(input) {
|
|
5
|
+
if (input === undefined || input === null) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
if (typeof input !== 'object') {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const obj = input;
|
|
12
|
+
if (typeof obj.name !== 'string' || !NEW_VAR_NAME_RE.test(obj.name)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (typeof obj.initializer !== 'string' || obj.initializer.trim() === '') {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return { name: obj.name, initializer: obj.initializer };
|
|
19
|
+
}
|
|
20
|
+
export function createSketchEditsRouter(fluidCadServer, sendToExtension, workspacePath) {
|
|
21
|
+
const router = Router();
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// /api/import-file — file I/O for STEP/STP imports
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
router.post('/import-file', async (req, res) => {
|
|
26
|
+
const { fileName, data } = req.body;
|
|
27
|
+
if (typeof fileName !== 'string' || typeof data !== 'string') {
|
|
28
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
await fluidCadServer.importFile(workspacePath, fileName, data);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
res.status(500).json({ error: err.message || String(err) });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const loadName = fileName.replace(/\.(step|stp)$/i, '');
|
|
39
|
+
res.json({ success: true, fileName: loadName });
|
|
40
|
+
});
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Sketch interactive — IPC pass-through to the extension
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
router.post('/insert-point', (req, res) => {
|
|
45
|
+
const { point, sourceLocation } = req.body;
|
|
46
|
+
if (!Array.isArray(point) || point.length !== 2 ||
|
|
47
|
+
!sourceLocation || typeof sourceLocation.line !== 'number' || typeof sourceLocation.column !== 'number') {
|
|
48
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
sendToExtension({
|
|
52
|
+
type: 'insert-point',
|
|
53
|
+
point: point,
|
|
54
|
+
sourceLocation,
|
|
55
|
+
});
|
|
56
|
+
res.json({ success: true });
|
|
57
|
+
});
|
|
58
|
+
router.post('/remove-point', (req, res) => {
|
|
59
|
+
const { point, sourceLocation } = req.body;
|
|
60
|
+
if (!Array.isArray(point) || point.length !== 2 ||
|
|
61
|
+
!sourceLocation || typeof sourceLocation.line !== 'number' || typeof sourceLocation.column !== 'number') {
|
|
62
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
sendToExtension({
|
|
66
|
+
type: 'remove-point',
|
|
67
|
+
point: point,
|
|
68
|
+
sourceLocation,
|
|
69
|
+
});
|
|
70
|
+
res.json({ success: true });
|
|
71
|
+
});
|
|
72
|
+
router.post('/add-pick', (req, res) => {
|
|
73
|
+
const { sourceLocation } = req.body;
|
|
74
|
+
if (!sourceLocation || typeof sourceLocation.line !== 'number' || typeof sourceLocation.column !== 'number') {
|
|
75
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
sendToExtension({
|
|
79
|
+
type: 'add-pick',
|
|
80
|
+
sourceLocation,
|
|
81
|
+
});
|
|
82
|
+
res.json({ success: true });
|
|
83
|
+
});
|
|
84
|
+
router.post('/remove-pick', (req, res) => {
|
|
85
|
+
const { sourceLocation } = req.body;
|
|
86
|
+
if (!sourceLocation || typeof sourceLocation.line !== 'number' || typeof sourceLocation.column !== 'number') {
|
|
87
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
sendToExtension({
|
|
91
|
+
type: 'remove-pick',
|
|
92
|
+
sourceLocation,
|
|
93
|
+
});
|
|
94
|
+
res.json({ success: true });
|
|
95
|
+
});
|
|
96
|
+
router.post('/set-pick-points', (req, res) => {
|
|
97
|
+
const { points, sourceLocation } = req.body;
|
|
98
|
+
if (!Array.isArray(points) ||
|
|
99
|
+
!sourceLocation || typeof sourceLocation.line !== 'number' || typeof sourceLocation.column !== 'number') {
|
|
100
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
sendToExtension({
|
|
104
|
+
type: 'set-pick-points',
|
|
105
|
+
points: points,
|
|
106
|
+
sourceLocation,
|
|
107
|
+
});
|
|
108
|
+
res.json({ success: true });
|
|
109
|
+
});
|
|
110
|
+
router.post('/insert-geometry', (req, res) => {
|
|
111
|
+
const { statement, sketchSourceLocation, newVariable } = req.body;
|
|
112
|
+
if (typeof statement !== 'string' ||
|
|
113
|
+
!sketchSourceLocation || typeof sketchSourceLocation.line !== 'number') {
|
|
114
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const nv = validateNewVariable(newVariable);
|
|
118
|
+
if (nv === false) {
|
|
119
|
+
res.status(400).json({ error: 'Invalid newVariable' });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
sendToExtension({
|
|
123
|
+
type: 'insert-geometry',
|
|
124
|
+
statement,
|
|
125
|
+
sketchSourceLocation,
|
|
126
|
+
newVariable: nv,
|
|
127
|
+
});
|
|
128
|
+
res.json({ success: true });
|
|
129
|
+
});
|
|
130
|
+
router.post('/update-position', (req, res) => {
|
|
131
|
+
const { newPosition, sourceLocation, pointIndex } = req.body;
|
|
132
|
+
if (!Array.isArray(newPosition) || newPosition.length !== 2 ||
|
|
133
|
+
!sourceLocation || typeof sourceLocation.line !== 'number') {
|
|
134
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
sendToExtension({
|
|
138
|
+
type: 'update-position',
|
|
139
|
+
newPosition: newPosition,
|
|
140
|
+
sourceLocation,
|
|
141
|
+
pointIndex: typeof pointIndex === 'number' ? pointIndex : undefined,
|
|
142
|
+
});
|
|
143
|
+
res.json({ success: true });
|
|
144
|
+
});
|
|
145
|
+
router.post('/set-line-position', (req, res) => {
|
|
146
|
+
const { newStart, newEnd, sourceLocation } = req.body;
|
|
147
|
+
if (!Array.isArray(newStart) || newStart.length !== 2 ||
|
|
148
|
+
!Array.isArray(newEnd) || newEnd.length !== 2 ||
|
|
149
|
+
!sourceLocation || typeof sourceLocation.line !== 'number') {
|
|
150
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
sendToExtension({
|
|
154
|
+
type: 'set-line-position',
|
|
155
|
+
newStart: newStart,
|
|
156
|
+
newEnd: newEnd,
|
|
157
|
+
sourceLocation,
|
|
158
|
+
});
|
|
159
|
+
res.json({ success: true });
|
|
160
|
+
});
|
|
161
|
+
router.post('/set-chain-positions', (req, res) => {
|
|
162
|
+
const { updates, sourceLocation } = req.body;
|
|
163
|
+
if (!Array.isArray(updates) || updates.length === 0 ||
|
|
164
|
+
!sourceLocation || typeof sourceLocation.line !== 'number') {
|
|
165
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
sendToExtension({
|
|
169
|
+
type: 'set-chain-positions',
|
|
170
|
+
updates,
|
|
171
|
+
sourceLocation,
|
|
172
|
+
});
|
|
173
|
+
res.json({ success: true });
|
|
174
|
+
});
|
|
175
|
+
router.post('/update-dimension', (req, res) => {
|
|
176
|
+
const { newValue, sourceLocation } = req.body;
|
|
177
|
+
if (typeof newValue !== 'number' ||
|
|
178
|
+
!sourceLocation || typeof sourceLocation.line !== 'number') {
|
|
179
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
sendToExtension({
|
|
183
|
+
type: 'update-dimension',
|
|
184
|
+
newValue,
|
|
185
|
+
sourceLocation,
|
|
186
|
+
});
|
|
187
|
+
res.json({ success: true });
|
|
188
|
+
});
|
|
189
|
+
router.post('/update-dimension-expression', (req, res) => {
|
|
190
|
+
const { expression, sourceLocation, sketchSourceLine, newVariable, dimensionOffset } = req.body;
|
|
191
|
+
if (typeof expression !== 'string' ||
|
|
192
|
+
!sourceLocation || typeof sourceLocation.line !== 'number') {
|
|
193
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const nv = validateNewVariable(newVariable);
|
|
197
|
+
if (nv === false) {
|
|
198
|
+
res.status(400).json({ error: 'Invalid newVariable' });
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (nv && typeof sketchSourceLine !== 'number') {
|
|
202
|
+
res.status(400).json({ error: 'sketchSourceLine required when newVariable is provided' });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
sendToExtension({
|
|
206
|
+
type: 'update-dimension-expression',
|
|
207
|
+
expression,
|
|
208
|
+
sourceLocation,
|
|
209
|
+
sketchSourceLine: typeof sketchSourceLine === 'number' ? sketchSourceLine : null,
|
|
210
|
+
newVariable: nv,
|
|
211
|
+
dimensionOffset: typeof dimensionOffset === 'number' ? dimensionOffset : 0,
|
|
212
|
+
});
|
|
213
|
+
res.json({ success: true });
|
|
214
|
+
});
|
|
215
|
+
router.post('/set-rect-dimensions', (req, res) => {
|
|
216
|
+
const { startPoint, width, height, sourceLocation } = req.body;
|
|
217
|
+
if (typeof width !== 'number' || typeof height !== 'number' ||
|
|
218
|
+
!sourceLocation || typeof sourceLocation.line !== 'number') {
|
|
219
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const sp = Array.isArray(startPoint) && startPoint.length === 2 ? startPoint : null;
|
|
223
|
+
sendToExtension({
|
|
224
|
+
type: 'set-rect-dimensions',
|
|
225
|
+
startPoint: sp,
|
|
226
|
+
width,
|
|
227
|
+
height,
|
|
228
|
+
sourceLocation,
|
|
229
|
+
});
|
|
230
|
+
res.json({ success: true });
|
|
231
|
+
});
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Sketch queries — read current code and answer; no mutation, but only
|
|
234
|
+
// useful to the sketch tooling so categorized here.
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
router.post('/scope-variables', async (req, res) => {
|
|
237
|
+
const { sketchSourceLine } = req.body;
|
|
238
|
+
if (typeof sketchSourceLine !== 'number') {
|
|
239
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const code = fluidCadServer.getCurrentCode();
|
|
243
|
+
if (!code) {
|
|
244
|
+
res.json({ variables: [] });
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
const variables = await extractVariablesInScope(code, sketchSourceLine);
|
|
249
|
+
res.json({ variables });
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
router.post('/dimension-expression', async (req, res) => {
|
|
256
|
+
const { sourceLine } = req.body;
|
|
257
|
+
if (typeof sourceLine !== 'number') {
|
|
258
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const code = fluidCadServer.getCurrentCode();
|
|
262
|
+
if (!code) {
|
|
263
|
+
res.json({ expression: null });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const result = await getDimensionExpression(code, sourceLine);
|
|
268
|
+
res.json({ expression: result?.expression ?? null });
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// /api/code/* — extensions send the current buffer text plus operation
|
|
276
|
+
// params; the server returns the fully edited text. All source-text
|
|
277
|
+
// manipulation lives here so VSCode and Neovim share one implementation.
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
router.post('/code/add-breakpoint', async (req, res) => {
|
|
280
|
+
const { code, referenceRow } = req.body;
|
|
281
|
+
if (typeof code !== 'string' || typeof referenceRow !== 'number') {
|
|
282
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const result = await addBreakpoint(code, referenceRow);
|
|
287
|
+
res.json(result);
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
router.post('/code/remove-breakpoint', async (req, res) => {
|
|
294
|
+
const { code, line } = req.body;
|
|
295
|
+
if (typeof code !== 'string' || typeof line !== 'number') {
|
|
296
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const result = await removeBreakpoint(code, line);
|
|
301
|
+
res.json(result);
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
router.post('/code/toggle-breakpoint', async (req, res) => {
|
|
308
|
+
const { code, cursorRow } = req.body;
|
|
309
|
+
if (typeof code !== 'string' || typeof cursorRow !== 'number') {
|
|
310
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
const result = await toggleBreakpoint(code, cursorRow);
|
|
315
|
+
res.json(result);
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
router.post('/code/clear-breakpoints', async (req, res) => {
|
|
322
|
+
const { code } = req.body;
|
|
323
|
+
if (typeof code !== 'string') {
|
|
324
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
const result = await clearBreakpoints(code);
|
|
329
|
+
res.json(result);
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
router.post('/code/insert-point', async (req, res) => {
|
|
336
|
+
const { code, sourceLine, point } = req.body;
|
|
337
|
+
if (typeof code !== 'string' || typeof sourceLine !== 'number' ||
|
|
338
|
+
!Array.isArray(point) || point.length !== 2) {
|
|
339
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
const result = await insertPoint(code, sourceLine, point);
|
|
344
|
+
res.json(result);
|
|
345
|
+
}
|
|
346
|
+
catch (err) {
|
|
347
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
router.post('/code/remove-point', async (req, res) => {
|
|
351
|
+
const { code, sourceLine, point } = req.body;
|
|
352
|
+
if (typeof code !== 'string' || typeof sourceLine !== 'number' ||
|
|
353
|
+
!Array.isArray(point) || point.length !== 2) {
|
|
354
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
const result = await removePoint(code, sourceLine, point);
|
|
359
|
+
res.json(result);
|
|
360
|
+
}
|
|
361
|
+
catch (err) {
|
|
362
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
router.post('/code/add-pick', async (req, res) => {
|
|
366
|
+
const { code, sourceLine } = req.body;
|
|
367
|
+
if (typeof code !== 'string' || typeof sourceLine !== 'number') {
|
|
368
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
const result = await addPick(code, sourceLine);
|
|
373
|
+
res.json(result);
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
router.post('/code/remove-pick', async (req, res) => {
|
|
380
|
+
const { code, sourceLine } = req.body;
|
|
381
|
+
if (typeof code !== 'string' || typeof sourceLine !== 'number') {
|
|
382
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
const result = await removePick(code, sourceLine);
|
|
387
|
+
res.json(result);
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
router.post('/code/goto-source', (req, res) => {
|
|
394
|
+
const { filePath, line, column } = req.body;
|
|
395
|
+
if (typeof filePath !== 'string' ||
|
|
396
|
+
typeof line !== 'number' ||
|
|
397
|
+
typeof column !== 'number') {
|
|
398
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
sendToExtension({ type: 'goto-source', filePath, line, column });
|
|
402
|
+
res.json({ success: true });
|
|
403
|
+
});
|
|
404
|
+
router.post('/code/set-pick-points', async (req, res) => {
|
|
405
|
+
const { code, sourceLine, points } = req.body;
|
|
406
|
+
if (typeof code !== 'string' || typeof sourceLine !== 'number' ||
|
|
407
|
+
!Array.isArray(points)) {
|
|
408
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const result = await setPickPoints(code, sourceLine, points);
|
|
413
|
+
res.json(result);
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
router.post('/code/insert-geometry', async (req, res) => {
|
|
420
|
+
const { code, sketchSourceLine, statement, newVariable } = req.body;
|
|
421
|
+
if (typeof code !== 'string' || typeof sketchSourceLine !== 'number' ||
|
|
422
|
+
typeof statement !== 'string') {
|
|
423
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const nv = validateNewVariable(newVariable);
|
|
427
|
+
if (nv === false) {
|
|
428
|
+
res.status(400).json({ error: 'Invalid newVariable' });
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
const result = await insertGeometryCallWithVariable(code, sketchSourceLine, statement, nv);
|
|
433
|
+
res.json(result);
|
|
434
|
+
}
|
|
435
|
+
catch (err) {
|
|
436
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
router.post('/code/update-position', async (req, res) => {
|
|
440
|
+
const { code, sourceLine, newPosition, pointIndex } = req.body;
|
|
441
|
+
if (typeof code !== 'string' || typeof sourceLine !== 'number' ||
|
|
442
|
+
!Array.isArray(newPosition) || newPosition.length !== 2) {
|
|
443
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
const result = await updateGeometryPosition(code, sourceLine, newPosition, typeof pointIndex === 'number' ? pointIndex : 0);
|
|
448
|
+
res.json(result);
|
|
449
|
+
}
|
|
450
|
+
catch (err) {
|
|
451
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
router.post('/code/set-line-position', async (req, res) => {
|
|
455
|
+
const { code, sourceLine, newStart, newEnd } = req.body;
|
|
456
|
+
if (typeof code !== 'string' || typeof sourceLine !== 'number' ||
|
|
457
|
+
!Array.isArray(newStart) || newStart.length !== 2 ||
|
|
458
|
+
!Array.isArray(newEnd) || newEnd.length !== 2) {
|
|
459
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
try {
|
|
463
|
+
const result = await setLinePosition(code, sourceLine, newStart, newEnd);
|
|
464
|
+
res.json(result);
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
router.post('/code/set-chain-positions', async (req, res) => {
|
|
471
|
+
const { code, sourceLine, updates } = req.body;
|
|
472
|
+
if (typeof code !== 'string' || typeof sourceLine !== 'number' ||
|
|
473
|
+
!Array.isArray(updates) || updates.length === 0) {
|
|
474
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
try {
|
|
478
|
+
const result = await setChainPositions(code, sourceLine, updates);
|
|
479
|
+
res.json(result);
|
|
480
|
+
}
|
|
481
|
+
catch (err) {
|
|
482
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
router.post('/code/update-dimension', async (req, res) => {
|
|
486
|
+
const { code, sourceLine, newValue } = req.body;
|
|
487
|
+
if (typeof code !== 'string' || typeof sourceLine !== 'number' ||
|
|
488
|
+
typeof newValue !== 'number') {
|
|
489
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
try {
|
|
493
|
+
const result = await updateDimension(code, sourceLine, newValue);
|
|
494
|
+
res.json(result);
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
router.post('/code/update-dimension-expression', async (req, res) => {
|
|
501
|
+
const { code, sourceLine, expression, sketchSourceLine, newVariable, dimensionOffset } = req.body;
|
|
502
|
+
if (typeof code !== 'string' || typeof sourceLine !== 'number' ||
|
|
503
|
+
typeof expression !== 'string') {
|
|
504
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const nv = validateNewVariable(newVariable);
|
|
508
|
+
if (nv === false) {
|
|
509
|
+
res.status(400).json({ error: 'Invalid newVariable' });
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (nv && typeof sketchSourceLine !== 'number') {
|
|
513
|
+
res.status(400).json({ error: 'sketchSourceLine required when newVariable is provided' });
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const offset = typeof dimensionOffset === 'number' ? dimensionOffset : 0;
|
|
517
|
+
try {
|
|
518
|
+
const result = await updateDimensionExpressionWithVariable(code, sourceLine, expression, typeof sketchSourceLine === 'number' ? sketchSourceLine : sourceLine, nv, offset);
|
|
519
|
+
res.json(result);
|
|
520
|
+
}
|
|
521
|
+
catch (err) {
|
|
522
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
router.post('/code/set-rect-dimensions', async (req, res) => {
|
|
526
|
+
const { code, sourceLine, startPoint, width, height } = req.body;
|
|
527
|
+
if (typeof code !== 'string' || typeof sourceLine !== 'number' ||
|
|
528
|
+
typeof width !== 'number' || typeof height !== 'number') {
|
|
529
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const sp = Array.isArray(startPoint) && startPoint.length === 2 ? startPoint : null;
|
|
533
|
+
try {
|
|
534
|
+
const result = await setRectDimensions(code, sourceLine, sp, width, height);
|
|
535
|
+
res.json(result);
|
|
536
|
+
}
|
|
537
|
+
catch (err) {
|
|
538
|
+
res.status(500).json({ error: err?.message || String(err) });
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
return router;
|
|
542
|
+
}
|