fluidcad 0.0.33 → 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.
- package/README.md +72 -2
- package/bin/commands/init.js +55 -0
- package/bin/commands/login.js +120 -0
- package/bin/commands/mcp.js +33 -0
- package/bin/commands/pack.js +49 -0
- package/bin/commands/publish.js +136 -0
- package/bin/commands/serve.js +77 -0
- package/bin/fluidcad.js +21 -107
- package/bin/lib/api-client.js +40 -0
- package/bin/lib/browser.js +16 -0
- package/bin/lib/config.js +39 -0
- package/bin/lib/model-config.js +38 -0
- package/bin/lib/workspace.js +57 -0
- package/lib/dist/common/scene-object.d.ts +2 -1
- package/lib/dist/common/scene-object.js +3 -2
- 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/tarc.d.ts +20 -2
- package/lib/dist/core/2d/tarc.js +24 -0
- 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 +55 -25
- 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 +72 -54
- 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.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/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.d.ts +4 -0
- package/lib/dist/features/2d/sketch.js +25 -0
- 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-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 +6 -0
- package/lib/dist/features/2d/tarc-to-point.d.ts +2 -0
- package/lib/dist/features/2d/tarc-to-point.js +6 -0
- package/lib/dist/features/2d/tarc-with-tangent.d.ts +2 -0
- package/lib/dist/features/2d/tarc-with-tangent.js +6 -0
- package/lib/dist/features/2d/tarc.d.ts +2 -0
- package/lib/dist/features/2d/tarc.js +6 -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 +13 -3
- package/lib/dist/features/extrude-base.js +32 -3
- 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/mirror-feature.d.ts +3 -2
- package/lib/dist/features/mirror-feature.js +1 -1
- 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 +8 -7
- package/lib/dist/features/repeat-circular.js +7 -3
- package/lib/dist/features/repeat-linear.d.ts +9 -7
- package/lib/dist/features/repeat-linear.js +9 -3
- package/lib/dist/features/repeat-matrix.d.ts +3 -1
- package/lib/dist/features/repeat-matrix.js +7 -2
- package/lib/dist/features/shell.d.ts +4 -1
- package/lib/dist/features/shell.js +14 -3
- package/lib/dist/helpers/clone-transform.d.ts +2 -1
- package/lib/dist/index.d.ts +12 -1
- package/lib/dist/index.js +11 -4
- 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 +36 -0
- package/lib/dist/math/lazy-matrix.js +134 -0
- package/lib/dist/oc/boolean-ops.d.ts +2 -2
- 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/mesh.d.ts +9 -4
- package/lib/dist/oc/mesh.js +14 -13
- package/lib/dist/oc/shell-ops.d.ts +2 -1
- package/lib/dist/oc/shell-ops.js +5 -2
- package/lib/dist/param-registry.d.ts +34 -0
- package/lib/dist/param-registry.js +60 -0
- package/lib/dist/rendering/mesh-builder.d.ts +3 -0
- package/lib/dist/rendering/mesh-builder.js +10 -5
- 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 +2 -2
- 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 +3 -0
- package/lib/dist/rendering/render.js +7 -2
- 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/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/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/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/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 +41 -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 +65 -0
- package/mcp/dist/client.js +255 -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 +502 -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 +72 -0
- package/mcp/dist/tools/engine.js +190 -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 +27 -7
- package/server/dist/api.d.ts +37 -0
- package/server/dist/api.js +44 -0
- package/server/dist/code-editor.d.ts +100 -0
- package/server/dist/code-editor.js +528 -2
- package/server/dist/fluidcad-server.d.ts +118 -1
- package/server/dist/fluidcad-server.js +350 -62
- package/server/dist/global-registry.d.ts +30 -0
- package/server/dist/global-registry.js +126 -0
- 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 +175 -123
- 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/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 +229 -0
- package/server/dist/model-package/types.d.ts +78 -0
- package/server/dist/model-package/types.js +17 -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/hit-test.d.ts +3 -0
- package/server/dist/routes/hit-test.js +17 -0
- package/server/dist/routes/lint.d.ts +10 -0
- package/server/dist/routes/lint.js +28 -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/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/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 +156 -3
- 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-CFi9p7wR.js +0 -4946
- package/ui/dist/assets/index-DR7c2Qk9.css +0 -2
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
export function createTimelineRouter(fluidCadServer, sendToExtension, broadcastToUI) {
|
|
3
|
+
const router = Router();
|
|
4
|
+
router.post('/rollback', async (req, res) => {
|
|
5
|
+
const { index } = req.body;
|
|
6
|
+
if (typeof index !== 'number' || index < 0) {
|
|
7
|
+
res.status(400).json({ error: 'Invalid index' });
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const data = await fluidCadServer.rollbackFromUI(index);
|
|
11
|
+
if (!data) {
|
|
12
|
+
res.status(404).json({ error: 'No active scene' });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
sendToExtension({
|
|
16
|
+
type: 'scene-rendered',
|
|
17
|
+
absPath: data.absPath,
|
|
18
|
+
result: data.result,
|
|
19
|
+
rollbackStop: data.rollbackStop,
|
|
20
|
+
});
|
|
21
|
+
broadcastToUI({
|
|
22
|
+
type: 'scene-rendered',
|
|
23
|
+
result: data.result,
|
|
24
|
+
absPath: data.absPath,
|
|
25
|
+
rollbackStop: data.rollbackStop,
|
|
26
|
+
});
|
|
27
|
+
res.json({ success: true });
|
|
28
|
+
});
|
|
29
|
+
router.post('/add-breakpoint', (req, res) => {
|
|
30
|
+
const { sourceLocation } = req.body;
|
|
31
|
+
if (!sourceLocation ||
|
|
32
|
+
typeof sourceLocation.filePath !== 'string' ||
|
|
33
|
+
typeof sourceLocation.line !== 'number') {
|
|
34
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
sendToExtension({
|
|
38
|
+
type: 'add-breakpoint',
|
|
39
|
+
filePath: sourceLocation.filePath,
|
|
40
|
+
line: sourceLocation.line,
|
|
41
|
+
});
|
|
42
|
+
res.json({ success: true });
|
|
43
|
+
});
|
|
44
|
+
router.post('/clear-breakpoints', (_req, res) => {
|
|
45
|
+
sendToExtension({ type: 'clear-breakpoints' });
|
|
46
|
+
res.json({ success: true });
|
|
47
|
+
});
|
|
48
|
+
return router;
|
|
49
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { WebSocket, WebSocketServer } from 'ws';
|
|
2
|
+
import type { CameraStateMessage, ServerToUIMessage } from './ws-protocol.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Shared HTTP+WS plumbing for server entry points to compose. The desktop
|
|
5
|
+
* entry (`index.ts`) uses it directly; it's also exported via
|
|
6
|
+
* `fluidcad/server/api` so downstream hosts (e.g. a hub runtime) can build
|
|
7
|
+
* their own entry on top. The pieces here are mode-agnostic:
|
|
8
|
+
*
|
|
9
|
+
* - **WS client registry** with per-connection sessionId tracking. Hub mode
|
|
10
|
+
* dispatches param mutations to a specific sessionId; desktop ignores
|
|
11
|
+
* sessionId and broadcasts to every client.
|
|
12
|
+
* - **Screenshot request/response coordinator** — the screenshot endpoint
|
|
13
|
+
* round-trips through a WS client to get a PNG. Same flow either side.
|
|
14
|
+
* - **Camera state mirror** — the most recent camera-state message,
|
|
15
|
+
* exposed via getter for `/api/camera/state`.
|
|
16
|
+
*
|
|
17
|
+
* Mode-specific things (engine init, route mounting, file watching, IPC)
|
|
18
|
+
* stay in the calling entry point.
|
|
19
|
+
*/
|
|
20
|
+
export interface UIClient {
|
|
21
|
+
ws: WebSocket;
|
|
22
|
+
sessionId: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ServerCore {
|
|
25
|
+
wss: WebSocketServer;
|
|
26
|
+
uiClients: Set<UIClient>;
|
|
27
|
+
/** Broadcast to every connected UI client. */
|
|
28
|
+
broadcastToUI(msg: ServerToUIMessage): void;
|
|
29
|
+
/** Send to the single UI client with the matching sessionId. No-op if absent. */
|
|
30
|
+
sendToSession(sessionId: string, msg: ServerToUIMessage): void;
|
|
31
|
+
/** Trigger a screenshot via the first connected UI client; resolves with PNG bytes. */
|
|
32
|
+
requestScreenshot(options: Record<string, unknown>): Promise<Buffer>;
|
|
33
|
+
/** Latest camera-state observed from any UI client. */
|
|
34
|
+
getLastCameraState(): CameraStateMessage | null;
|
|
35
|
+
/**
|
|
36
|
+
* Hook for the entry point to handle non-core UI→server messages. The core
|
|
37
|
+
* already consumes `screenshot-result` and `camera-state`; everything else
|
|
38
|
+
* is forwarded here (with the sender's sessionId for routing).
|
|
39
|
+
*/
|
|
40
|
+
setMessageHandler(handler: (sessionId: string, msg: any, ws: WebSocket) => void | Promise<void>): void;
|
|
41
|
+
/**
|
|
42
|
+
* Hook for the entry point to seed a newly connected client. Called after
|
|
43
|
+
* the core has registered the client and assigned its sessionId; useful for
|
|
44
|
+
* sending initial scene-rendered, init-complete replay, etc.
|
|
45
|
+
*/
|
|
46
|
+
setConnectionHandler(handler: (sessionId: string, ws: WebSocket) => void | Promise<void>): void;
|
|
47
|
+
/**
|
|
48
|
+
* Hook called after a UI client disconnects. The hub entry uses this to
|
|
49
|
+
* drop per-session engine state.
|
|
50
|
+
*/
|
|
51
|
+
setDisconnectHandler(handler: (sessionId: string) => void): void;
|
|
52
|
+
}
|
|
53
|
+
export declare function createServerCore(httpServer: import('http').Server): ServerCore;
|