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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getOC } from "./init.js";
|
|
2
|
+
export class TopologyIndex {
|
|
3
|
+
static buildEdgeToFaces(root) {
|
|
4
|
+
const oc = getOC();
|
|
5
|
+
const map = new oc.TopTools_IndexedDataMapOfShapeListOfShape();
|
|
6
|
+
oc.TopExp.MapShapesAndAncestors(root, oc.TopAbs_ShapeEnum.TopAbs_EDGE, oc.TopAbs_ShapeEnum.TopAbs_FACE, map);
|
|
7
|
+
return map;
|
|
8
|
+
}
|
|
9
|
+
static buildShapeSet(shapes) {
|
|
10
|
+
const oc = getOC();
|
|
11
|
+
const map = new oc.TopTools_MapOfShape();
|
|
12
|
+
for (const s of shapes) {
|
|
13
|
+
map.Add(s);
|
|
14
|
+
}
|
|
15
|
+
return map;
|
|
16
|
+
}
|
|
17
|
+
static seekShapes(index, key) {
|
|
18
|
+
const idx = index.FindIndex(key);
|
|
19
|
+
if (idx === 0) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
const list = index.ChangeFromIndex(idx);
|
|
23
|
+
if (!list || list.Size() === 0) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
const oc = getOC();
|
|
27
|
+
const copy = new oc.TopTools_ListOfShape(list);
|
|
28
|
+
const out = [];
|
|
29
|
+
while (copy.Size() > 0) {
|
|
30
|
+
out.push(copy.First());
|
|
31
|
+
copy.RemoveFirst();
|
|
32
|
+
}
|
|
33
|
+
copy.delete();
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Shape } from "../common/shape.js";
|
|
2
2
|
import { SceneObjectMesh } from "./scene.js";
|
|
3
|
+
import type { MeshConfig } from "../oc/mesh.js";
|
|
3
4
|
export declare class MeshBuilder {
|
|
5
|
+
private readonly meshConfig;
|
|
6
|
+
constructor(meshConfig: MeshConfig);
|
|
4
7
|
build(shapeObj: Shape): SceneObjectMesh[];
|
|
5
8
|
}
|
|
@@ -4,20 +4,24 @@ import { renderFace } from "./render-face.js";
|
|
|
4
4
|
import { renderWire } from "./render-wire.js";
|
|
5
5
|
import { renderEdge } from "./render-edge.js";
|
|
6
6
|
export class MeshBuilder {
|
|
7
|
+
meshConfig;
|
|
8
|
+
constructor(meshConfig) {
|
|
9
|
+
this.meshConfig = meshConfig;
|
|
10
|
+
}
|
|
7
11
|
build(shapeObj) {
|
|
8
12
|
const shape = shapeObj.getShape();
|
|
9
13
|
let result = null;
|
|
10
14
|
if (Explorer.isSolid(shape)) {
|
|
11
|
-
result = renderSolid(shapeObj);
|
|
15
|
+
result = renderSolid(shapeObj, this.meshConfig);
|
|
12
16
|
}
|
|
13
17
|
else if (Explorer.isFace(shape)) {
|
|
14
|
-
result = renderFace(shapeObj);
|
|
18
|
+
result = renderFace(shapeObj, 0, this.meshConfig);
|
|
15
19
|
}
|
|
16
20
|
else if (Explorer.isWire(shape)) {
|
|
17
|
-
result = renderWire(shapeObj);
|
|
21
|
+
result = renderWire(shapeObj, this.meshConfig);
|
|
18
22
|
}
|
|
19
23
|
else if (Explorer.isEdge(shape)) {
|
|
20
|
-
result = renderEdge(shapeObj);
|
|
24
|
+
result = renderEdge(shapeObj, this.meshConfig);
|
|
21
25
|
}
|
|
22
26
|
else if (Explorer.isCompound(shape)) {
|
|
23
27
|
console.warn("Compound shapes are not supported yet.");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { Shape } from "../common/shape.js";
|
|
2
|
+
import type { MeshConfig } from "../oc/mesh.js";
|
|
2
3
|
import type { SceneObjectMesh } from "./scene.js";
|
|
3
|
-
export declare function renderEdge(edgeObj: Shape): SceneObjectMesh;
|
|
4
|
+
export declare function renderEdge(edgeObj: Shape, meshConfig?: MeshConfig): SceneObjectMesh;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { Shape } from "../common/shape.js";
|
|
2
|
-
|
|
2
|
+
import type { MeshConfig } from "../oc/mesh.js";
|
|
3
|
+
export declare function renderFace(faceObj: Shape, vertexOffset?: number, meshConfig?: MeshConfig): import("../oc/mesh.js").MeshData;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Mesh } from "../oc/mesh.js";
|
|
2
|
-
export function renderFace(faceObj, vertexOffset = 0) {
|
|
2
|
+
export function renderFace(faceObj, vertexOffset = 0, meshConfig) {
|
|
3
3
|
const face = faceObj.getShape();
|
|
4
|
-
return Mesh.triangulateFaceRaw(face, vertexOffset);
|
|
4
|
+
return Mesh.triangulateFaceRaw(face, vertexOffset, meshConfig);
|
|
5
5
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { Shape } from "../common/shape.js";
|
|
2
2
|
import { SceneObjectMesh } from "./scene.js";
|
|
3
|
-
|
|
3
|
+
import type { MeshConfig } from "../oc/mesh.js";
|
|
4
|
+
export declare function renderSolid(shapeObj: Shape, meshConfig?: MeshConfig): SceneObjectMesh[];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Explorer } from "../oc/explorer.js";
|
|
2
2
|
import { Mesh } from "../oc/mesh.js";
|
|
3
3
|
import { getOC } from "../oc/init.js";
|
|
4
|
-
export function renderSolid(shapeObj) {
|
|
5
|
-
Mesh.ensureTriangulated(shapeObj.getShape());
|
|
4
|
+
export function renderSolid(shapeObj, meshConfig) {
|
|
5
|
+
Mesh.ensureTriangulated(shapeObj.getShape(), meshConfig);
|
|
6
6
|
const facesMeshes = getFacesMesh(shapeObj);
|
|
7
7
|
const edgesMesh = getEdgesMesh(shapeObj);
|
|
8
8
|
return [...facesMeshes, ...edgesMesh];
|
|
@@ -10,8 +10,7 @@ export function renderSolid(shapeObj) {
|
|
|
10
10
|
function getEdgesMesh(shapeObj) {
|
|
11
11
|
const oc = getOC();
|
|
12
12
|
const result = [];
|
|
13
|
-
const edgeToFaces =
|
|
14
|
-
oc.TopExp.MapShapesAndAncestors(shapeObj.getShape(), oc.TopAbs_ShapeEnum.TopAbs_EDGE, oc.TopAbs_ShapeEnum.TopAbs_FACE, edgeToFaces);
|
|
13
|
+
const edgeToFaces = shapeObj.getEdgeToFacesIndex();
|
|
15
14
|
const edges = Explorer.findEdgesWrapped(shapeObj);
|
|
16
15
|
for (let edgeIdx = 0; edgeIdx < edges.length; edgeIdx++) {
|
|
17
16
|
const edgeShape = edges[edgeIdx].getShape();
|
|
@@ -30,7 +29,6 @@ function getEdgesMesh(shapeObj) {
|
|
|
30
29
|
});
|
|
31
30
|
}
|
|
32
31
|
}
|
|
33
|
-
edgeToFaces.delete();
|
|
34
32
|
return result;
|
|
35
33
|
}
|
|
36
34
|
function getFacesMesh(shapeObj) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Shape } from "../common/shape.js";
|
|
2
|
-
|
|
2
|
+
import type { MeshConfig } from "../oc/mesh.js";
|
|
3
|
+
export declare function renderWire(shapeObj: Shape, meshConfig?: MeshConfig): {
|
|
3
4
|
vertices: number[];
|
|
4
5
|
normals: number[];
|
|
5
6
|
indices: number[];
|
|
@@ -2,9 +2,9 @@ import { renderEdge } from "./render-edge.js";
|
|
|
2
2
|
import { Edge } from "../common/edge.js";
|
|
3
3
|
import { Explorer } from "../oc/explorer.js";
|
|
4
4
|
import { Mesh } from "../oc/mesh.js";
|
|
5
|
-
export function renderWire(shapeObj) {
|
|
5
|
+
export function renderWire(shapeObj, meshConfig) {
|
|
6
6
|
const shape = shapeObj.getShape();
|
|
7
|
-
Mesh.ensureTriangulated(shape);
|
|
7
|
+
Mesh.ensureTriangulated(shape, meshConfig);
|
|
8
8
|
const edges = Explorer.findShapes(shape, Explorer.getOcShapeType("edge"));
|
|
9
9
|
const allVertices = [];
|
|
10
10
|
const allNormals = [];
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Scene } from "./scene.js";
|
|
2
|
+
import type { MeshConfig } from "../oc/mesh.js";
|
|
2
3
|
export declare class SceneRenderer {
|
|
4
|
+
private readonly meshConfig;
|
|
3
5
|
private readonly meshBuilder;
|
|
6
|
+
constructor(meshConfig: MeshConfig);
|
|
4
7
|
render(scene: Scene): Scene;
|
|
5
8
|
renderRollback(scene: Scene, rollbackIndex: number): Scene;
|
|
9
|
+
private batchTriangulate;
|
|
6
10
|
private prepareRenderedShapes;
|
|
7
11
|
private emitRenderObject;
|
|
8
12
|
private buildObject;
|
|
@@ -4,10 +4,16 @@ import { AxisObjectBase } from "../features/axis-renderable-base.js";
|
|
|
4
4
|
import { Sketch } from "../features/2d/sketch.js";
|
|
5
5
|
import { transformMeshes } from "./mesh-transform.js";
|
|
6
6
|
import { ShapeOps } from "../oc/shape-ops.js";
|
|
7
|
+
import { Mesh } from "../oc/mesh.js";
|
|
7
8
|
import { Profiler } from "../common/profiler.js";
|
|
8
9
|
import { describeError } from "../common/describe-error.js";
|
|
9
10
|
export class SceneRenderer {
|
|
10
|
-
|
|
11
|
+
meshConfig;
|
|
12
|
+
meshBuilder;
|
|
13
|
+
constructor(meshConfig) {
|
|
14
|
+
this.meshConfig = meshConfig;
|
|
15
|
+
this.meshBuilder = new MeshBuilder(meshConfig);
|
|
16
|
+
}
|
|
11
17
|
render(scene) {
|
|
12
18
|
const sceneObjects = scene.getAllSceneObjects();
|
|
13
19
|
console.log("============ Rendering ==============", sceneObjects.length);
|
|
@@ -40,6 +46,7 @@ export class SceneRenderer {
|
|
|
40
46
|
}
|
|
41
47
|
object.clean(scene.getPartScopedAllObjects(object));
|
|
42
48
|
}
|
|
49
|
+
this.batchTriangulate(sceneObjects, skippedContainers);
|
|
43
50
|
const prepared = new Map();
|
|
44
51
|
for (const object of sceneObjects) {
|
|
45
52
|
const profiler = profilers.get(object);
|
|
@@ -88,6 +95,44 @@ export class SceneRenderer {
|
|
|
88
95
|
console.table(result);
|
|
89
96
|
return scene;
|
|
90
97
|
}
|
|
98
|
+
// Mesh every shape that still needs triangulation in a single
|
|
99
|
+
// BRepMesh_IncrementalMesh call. OC's parallel mode then balances faces
|
|
100
|
+
// across all shapes at once instead of running per-shape sequentially.
|
|
101
|
+
// The triangulation lives on each TFace, so the existing per-shape
|
|
102
|
+
// extraction in prepareRenderedShapes finds it cached and short-circuits
|
|
103
|
+
// its own ensureTriangulated call.
|
|
104
|
+
batchTriangulate(sceneObjects, skippedContainers) {
|
|
105
|
+
const targets = new Set();
|
|
106
|
+
for (const object of sceneObjects) {
|
|
107
|
+
if (skippedContainers.has(object) || object.isLazy()) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const shapes = object.getOwnShapes({ excludeMeta: false, excludeGuide: false });
|
|
111
|
+
for (const shape of shapes) {
|
|
112
|
+
if (shape.getMeshes()) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const source = shape.getMeshSource();
|
|
116
|
+
const target = source ? source.shape : shape;
|
|
117
|
+
if (target.getMeshes()) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
targets.add(target);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (targets.size <= 1) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const compound = ShapeOps.makeCompoundRaw([...targets].map(s => s.getShape()));
|
|
127
|
+
try {
|
|
128
|
+
const t0 = performance.now();
|
|
129
|
+
Mesh.ensureTriangulated(compound, this.meshConfig);
|
|
130
|
+
console.log(`Batched mesh: ${targets.size} shapes in ${(performance.now() - t0).toFixed(1)}ms`);
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
compound.delete();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
91
136
|
prepareRenderedShapes(obj, profiler) {
|
|
92
137
|
const renderedSceneShapes = [];
|
|
93
138
|
if (obj.isLazy()) {
|
|
@@ -249,9 +294,12 @@ export class SceneRenderer {
|
|
|
249
294
|
emitRendered(obj, scene, opts) {
|
|
250
295
|
const categories = opts.profiler?.getCategories();
|
|
251
296
|
const profileCategories = categories && categories.length > 0 ? categories : undefined;
|
|
297
|
+
const displayName = obj.hasCustomName()
|
|
298
|
+
? obj.getName()
|
|
299
|
+
: obj.getType().charAt(0).toUpperCase() + obj.getType().slice(1);
|
|
252
300
|
const rendered = {
|
|
253
301
|
id: obj.id,
|
|
254
|
-
name:
|
|
302
|
+
name: displayName,
|
|
255
303
|
parentId: obj.parentId,
|
|
256
304
|
object: obj.serialize(opts.scope),
|
|
257
305
|
sceneShapes: opts.sceneShapes,
|
|
@@ -31,6 +31,9 @@ export class SceneCompare {
|
|
|
31
31
|
}
|
|
32
32
|
oldSttate.set('removedShapes', newRemovedShapes);
|
|
33
33
|
newObj.restoreState(oldSttate);
|
|
34
|
+
const staleId = newObj.id;
|
|
35
|
+
newObj.inheritIdentityFrom(oldObj);
|
|
36
|
+
newScene.reindexObject(newObj, staleId);
|
|
34
37
|
const oldError = oldObj.getError();
|
|
35
38
|
if (oldError) {
|
|
36
39
|
newObj.setError(oldError);
|
|
@@ -82,5 +82,6 @@ export declare class Scene {
|
|
|
82
82
|
indexOf(obj: SceneObject): number;
|
|
83
83
|
getSceneObjectAt(index: number): SceneObject;
|
|
84
84
|
getSceneObjectById(id: string): SceneObject | null;
|
|
85
|
+
reindexObject(obj: SceneObject, oldId: string): void;
|
|
85
86
|
getChildren(parent: SceneObject): SceneObject[];
|
|
86
87
|
}
|
|
@@ -201,6 +201,10 @@ export class Scene {
|
|
|
201
201
|
getSceneObjectById(id) {
|
|
202
202
|
return this.idMap.get(id) || null;
|
|
203
203
|
}
|
|
204
|
+
reindexObject(obj, oldId) {
|
|
205
|
+
this.idMap.delete(oldId);
|
|
206
|
+
this.idMap.set(obj.id, obj);
|
|
207
|
+
}
|
|
204
208
|
getChildren(parent) {
|
|
205
209
|
return this.sceneObjects.filter(obj => obj.parentId === parent.id);
|
|
206
210
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Scene } from "./rendering/scene.js";
|
|
2
2
|
import { SceneRenderer } from "./rendering/render.js";
|
|
3
|
+
import type { MeshConfig } from "./oc/mesh.js";
|
|
4
|
+
import type { FluidCADOptions } from "./index.js";
|
|
3
5
|
import type { ExportOptions } from "./io/file-export.js";
|
|
4
6
|
import type { ShapeProperties } from "./oc/props.js";
|
|
5
7
|
import type { FaceProperties } from "./oc/face-props.js";
|
|
@@ -10,7 +12,7 @@ declare class SceneManager {
|
|
|
10
12
|
currentScene: Scene;
|
|
11
13
|
currentFile: string;
|
|
12
14
|
renderer: SceneRenderer;
|
|
13
|
-
constructor(rootPath: string);
|
|
15
|
+
constructor(rootPath: string, meshConfig: MeshConfig);
|
|
14
16
|
setCurrentFile(filePath: string): void;
|
|
15
17
|
startScene(): Scene;
|
|
16
18
|
renderScene(scene: Scene): Scene;
|
|
@@ -26,7 +28,7 @@ declare class SceneManager {
|
|
|
26
28
|
};
|
|
27
29
|
hitTest(scene: Scene, shapeId: string, rayOrigin: [number, number, number], rayDir: [number, number, number], edgeThreshold: number): HitTestResult;
|
|
28
30
|
}
|
|
29
|
-
export declare function createManager(rootPath: string): SceneManager;
|
|
31
|
+
export declare function createManager(rootPath: string, options?: FluidCADOptions): SceneManager;
|
|
30
32
|
export declare function getCurrentScene(): Scene;
|
|
31
33
|
export declare function getCurrentFile(): string;
|
|
32
34
|
export declare function setCurrentFile(filePath: string): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Scene } from "./rendering/scene.js";
|
|
2
2
|
import { SceneRenderer } from "./rendering/render.js";
|
|
3
3
|
import { SceneCompare } from "./rendering/scene-compare.js";
|
|
4
|
+
import { DEFAULT_MESH_CONFIG } from "./oc/mesh.js";
|
|
4
5
|
import { FileImport } from "./io/file-import.js";
|
|
5
6
|
import { FileExport } from "./io/file-export.js";
|
|
6
7
|
import { ShapeProps } from "./oc/props.js";
|
|
@@ -12,9 +13,10 @@ class SceneManager {
|
|
|
12
13
|
rootPath;
|
|
13
14
|
currentScene = new Scene();
|
|
14
15
|
currentFile = '';
|
|
15
|
-
renderer
|
|
16
|
-
constructor(rootPath) {
|
|
16
|
+
renderer;
|
|
17
|
+
constructor(rootPath, meshConfig) {
|
|
17
18
|
this.rootPath = rootPath;
|
|
19
|
+
this.renderer = new SceneRenderer(meshConfig);
|
|
18
20
|
}
|
|
19
21
|
setCurrentFile(filePath) {
|
|
20
22
|
this.currentFile = filePath;
|
|
@@ -100,9 +102,15 @@ class SceneManager {
|
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
104
|
let currentManager = null;
|
|
103
|
-
|
|
105
|
+
function resolveMeshConfig(options) {
|
|
106
|
+
return {
|
|
107
|
+
linDefl: options?.mesh?.lineDeflection ?? DEFAULT_MESH_CONFIG.linDefl,
|
|
108
|
+
angDefl: options?.mesh?.angularDeflection ?? DEFAULT_MESH_CONFIG.angDefl,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function createManager(rootPath, options) {
|
|
104
112
|
console.log(`Creating SceneManager with root path: ${rootPath}`);
|
|
105
|
-
currentManager = new SceneManager(rootPath);
|
|
113
|
+
currentManager = new SceneManager(rootPath, resolveMeshConfig(options));
|
|
106
114
|
return currentManager;
|
|
107
115
|
}
|
|
108
116
|
export function getCurrentScene() {
|
|
@@ -36,6 +36,70 @@ describe("arc", () => {
|
|
|
36
36
|
expect(arcEdges.length).toBeGreaterThan(0);
|
|
37
37
|
});
|
|
38
38
|
});
|
|
39
|
+
describe("clockwise arc with .cw()", () => {
|
|
40
|
+
it("should create a CW arc from start to end around a center point", () => {
|
|
41
|
+
sketch("xy", () => {
|
|
42
|
+
arc([0, 0], [20, 0]).center([10, 0]).cw();
|
|
43
|
+
line([0, 0]);
|
|
44
|
+
});
|
|
45
|
+
const e = extrude(10);
|
|
46
|
+
render();
|
|
47
|
+
expect(e.getShapes()).toHaveLength(1);
|
|
48
|
+
const solid = e.getShapes()[0];
|
|
49
|
+
const arcEdges = getEdgesByType(solid, "arc");
|
|
50
|
+
expect(arcEdges.length).toBeGreaterThan(0);
|
|
51
|
+
});
|
|
52
|
+
it("should create a valid arc when chained from current position", () => {
|
|
53
|
+
sketch("xy", () => {
|
|
54
|
+
hLine(20);
|
|
55
|
+
arc([0, 0]).center([10, 0]).cw();
|
|
56
|
+
});
|
|
57
|
+
const e = extrude(10);
|
|
58
|
+
render();
|
|
59
|
+
expect(e.getShapes()).toHaveLength(1);
|
|
60
|
+
const solid = e.getShapes()[0];
|
|
61
|
+
const arcEdges = getEdgesByType(solid, "arc");
|
|
62
|
+
expect(arcEdges.length).toBeGreaterThan(0);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe("major arc with .major()", () => {
|
|
66
|
+
it("should create a major arc (>180°) from current position", () => {
|
|
67
|
+
sketch("xy", () => {
|
|
68
|
+
arc([30, 0]).radius(20).major();
|
|
69
|
+
line([0, 0]);
|
|
70
|
+
});
|
|
71
|
+
const e = extrude(10);
|
|
72
|
+
render();
|
|
73
|
+
expect(e.getShapes()).toHaveLength(1);
|
|
74
|
+
const solid = e.getShapes()[0];
|
|
75
|
+
const arcEdges = getEdgesByType(solid, "arc");
|
|
76
|
+
expect(arcEdges.length).toBeGreaterThan(0);
|
|
77
|
+
});
|
|
78
|
+
it("should create a major arc with two explicit points", () => {
|
|
79
|
+
sketch("xy", () => {
|
|
80
|
+
arc([0, 0], [30, 0]).radius(20).major();
|
|
81
|
+
line([0, 0]);
|
|
82
|
+
});
|
|
83
|
+
const e = extrude(10);
|
|
84
|
+
render();
|
|
85
|
+
expect(e.getShapes()).toHaveLength(1);
|
|
86
|
+
const solid = e.getShapes()[0];
|
|
87
|
+
const arcEdges = getEdgesByType(solid, "arc");
|
|
88
|
+
expect(arcEdges.length).toBeGreaterThan(0);
|
|
89
|
+
});
|
|
90
|
+
it("should create a CW major arc with negative radius and major", () => {
|
|
91
|
+
sketch("xy", () => {
|
|
92
|
+
arc([30, 0]).radius(-20).major();
|
|
93
|
+
line([0, 0]);
|
|
94
|
+
});
|
|
95
|
+
const e = extrude(10);
|
|
96
|
+
render();
|
|
97
|
+
expect(e.getShapes()).toHaveLength(1);
|
|
98
|
+
const solid = e.getShapes()[0];
|
|
99
|
+
const arcEdges = getEdgesByType(solid, "arc");
|
|
100
|
+
expect(arcEdges.length).toBeGreaterThan(0);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
39
103
|
describe("combined with lines", () => {
|
|
40
104
|
it("should create a shape with straight and curved edges", () => {
|
|
41
105
|
sketch("xy", () => {
|
|
@@ -2,8 +2,9 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
import { setupOC, render } from "../../setup.js";
|
|
3
3
|
import sketch from "../../../core/sketch.js";
|
|
4
4
|
import extrude from "../../../core/extrude.js";
|
|
5
|
-
import { move, hMove, back, rect } from "../../../core/2d/index.js";
|
|
5
|
+
import { move, hMove, back, rect, hLine, vLine, tLine } from "../../../core/2d/index.js";
|
|
6
6
|
import { ShapeOps } from "../../../oc/shape-ops.js";
|
|
7
|
+
import { Edge } from "../../../common/edge.js";
|
|
7
8
|
describe("back", () => {
|
|
8
9
|
setupOC();
|
|
9
10
|
it("should revert cursor to the previous position", () => {
|
|
@@ -57,4 +58,19 @@ describe("back", () => {
|
|
|
57
58
|
const bbox = ShapeOps.getBoundingBox(e.getShapes()[0]);
|
|
58
59
|
expect(bbox.minX).toBeCloseTo(40, 0);
|
|
59
60
|
});
|
|
61
|
+
it("should restore the tangent to its prior value, not just the position", () => {
|
|
62
|
+
let t;
|
|
63
|
+
sketch("xy", () => {
|
|
64
|
+
hLine(10); // tangent (+1, 0), pos (10, 0)
|
|
65
|
+
vLine(10); // tangent (0, +1), pos (10, 10)
|
|
66
|
+
back(); // pos reverts to (10, 0); tangent should revert to (+1, 0)
|
|
67
|
+
t = tLine(5); // with restored tangent, this should go to (15, 0)
|
|
68
|
+
});
|
|
69
|
+
render();
|
|
70
|
+
const edges = t.getOwnShapes().filter((sh) => sh instanceof Edge);
|
|
71
|
+
expect(edges).toHaveLength(1);
|
|
72
|
+
const endPoint = edges[0].getLastVertex().toPoint();
|
|
73
|
+
expect(endPoint.x).toBeCloseTo(15, 1);
|
|
74
|
+
expect(endPoint.y).toBeCloseTo(0, 1);
|
|
75
|
+
});
|
|
60
76
|
});
|
|
@@ -234,4 +234,161 @@ describe("tArc", () => {
|
|
|
234
234
|
render();
|
|
235
235
|
});
|
|
236
236
|
});
|
|
237
|
+
describe("tangent arc from current position to target line (target)", () => {
|
|
238
|
+
it("should create a tangent arc from a horizontal line ending tangent to a vertical line", () => {
|
|
239
|
+
const s = sketch("xy", () => {
|
|
240
|
+
const v = vLine([200, 200], 100).guide();
|
|
241
|
+
move([0, 0]);
|
|
242
|
+
hLine(100);
|
|
243
|
+
tArc(v);
|
|
244
|
+
});
|
|
245
|
+
render();
|
|
246
|
+
const shapes = s.getShapes();
|
|
247
|
+
// hLine + tArc (guide excluded) = at least 2 edges
|
|
248
|
+
expect(shapes.length).toBeGreaterThanOrEqual(2);
|
|
249
|
+
});
|
|
250
|
+
it("should accept a qualified line target", () => {
|
|
251
|
+
const s = sketch("xy", () => {
|
|
252
|
+
const v = vLine([200, 200], 100).guide();
|
|
253
|
+
move([0, 0]);
|
|
254
|
+
hLine(100);
|
|
255
|
+
tArc(outside(v));
|
|
256
|
+
});
|
|
257
|
+
render();
|
|
258
|
+
const shapes = s.getShapes();
|
|
259
|
+
expect(shapes.length).toBeGreaterThanOrEqual(2);
|
|
260
|
+
});
|
|
261
|
+
it("should allow chaining geometry after the solved arc", () => {
|
|
262
|
+
const s = sketch("xy", () => {
|
|
263
|
+
const v = vLine([200, 200], 100).guide();
|
|
264
|
+
move([0, 0]);
|
|
265
|
+
hLine(100);
|
|
266
|
+
tArc(v);
|
|
267
|
+
vLine(40);
|
|
268
|
+
});
|
|
269
|
+
render();
|
|
270
|
+
const shapes = s.getShapes();
|
|
271
|
+
// chain continues — hLine + tArc + vLine
|
|
272
|
+
expect(shapes.length).toBeGreaterThanOrEqual(3);
|
|
273
|
+
});
|
|
274
|
+
it("default arc curves to the left of the start tangent", () => {
|
|
275
|
+
const s = sketch("xy", () => {
|
|
276
|
+
const h = hLine([-150, 100], 300).guide();
|
|
277
|
+
move([0, 0]);
|
|
278
|
+
vLine(80);
|
|
279
|
+
tArc(h);
|
|
280
|
+
});
|
|
281
|
+
render();
|
|
282
|
+
const arcs = getEdgesByType(s.getShapes(), "arc");
|
|
283
|
+
expect(arcs.length).toBe(1);
|
|
284
|
+
// start tangent is +Y; "left" of +Y is -X. End vertex sits at negative x.
|
|
285
|
+
const endX = arcs[0].getLastVertex().toPoint().x;
|
|
286
|
+
expect(endX).toBeLessThan(0);
|
|
287
|
+
});
|
|
288
|
+
it(".flip() reverses the curve to the right of the start tangent", () => {
|
|
289
|
+
const s = sketch("xy", () => {
|
|
290
|
+
const h = hLine([-150, 100], 300).guide();
|
|
291
|
+
move([0, 0]);
|
|
292
|
+
vLine(80);
|
|
293
|
+
tArc(h).flip();
|
|
294
|
+
});
|
|
295
|
+
render();
|
|
296
|
+
const arcs = getEdgesByType(s.getShapes(), "arc");
|
|
297
|
+
expect(arcs.length).toBe(1);
|
|
298
|
+
const endX = arcs[0].getLastVertex().toPoint().x;
|
|
299
|
+
expect(endX).toBeGreaterThan(0);
|
|
300
|
+
});
|
|
301
|
+
it("should reject circle targets", () => {
|
|
302
|
+
const s = sketch("xy", () => {
|
|
303
|
+
const c = circle([200, 80], 30).guide();
|
|
304
|
+
move([0, 0]);
|
|
305
|
+
hLine(100);
|
|
306
|
+
tArc(c);
|
|
307
|
+
});
|
|
308
|
+
render();
|
|
309
|
+
const children = s.getChildren();
|
|
310
|
+
const arc = children[children.length - 1];
|
|
311
|
+
expect(arc.getError()).toMatch(/only line targets are supported/);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
describe("tangent arc with radius ending at intersection (radius, target)", () => {
|
|
315
|
+
it("should end at the intersection with a line target", () => {
|
|
316
|
+
const s = sketch("xy", () => {
|
|
317
|
+
const h = hLine([-200, 100], 400).guide();
|
|
318
|
+
move([0, 0]);
|
|
319
|
+
vLine(50);
|
|
320
|
+
tArc(60, h);
|
|
321
|
+
});
|
|
322
|
+
render();
|
|
323
|
+
const arcs = getEdgesByType(s.getShapes(), "arc");
|
|
324
|
+
expect(arcs.length).toBe(1);
|
|
325
|
+
// End vertex must lie on the target line y = 100.
|
|
326
|
+
const endY = arcs[0].getLastVertex().toPoint().y;
|
|
327
|
+
expect(endY).toBeCloseTo(100, 6);
|
|
328
|
+
});
|
|
329
|
+
it("should end at the intersection with a circle target", () => {
|
|
330
|
+
const s = sketch("xy", () => {
|
|
331
|
+
// circle(diameter); 80 diameter = 40 radius
|
|
332
|
+
const c = circle([80, 0], 80).guide();
|
|
333
|
+
move([0, 0]);
|
|
334
|
+
hLine(40);
|
|
335
|
+
tArc(50, c);
|
|
336
|
+
});
|
|
337
|
+
render();
|
|
338
|
+
const arcs = getEdgesByType(s.getShapes(), "arc");
|
|
339
|
+
expect(arcs.length).toBe(1);
|
|
340
|
+
// End vertex must lie on the target circle (radius 40 from (80, 0)).
|
|
341
|
+
const end = arcs[0].getLastVertex().toPoint();
|
|
342
|
+
const dist = Math.hypot(end.x - 80, end.y - 0);
|
|
343
|
+
expect(dist).toBeCloseTo(40, 6);
|
|
344
|
+
});
|
|
345
|
+
it("negative radius flips the sweep direction", () => {
|
|
346
|
+
// Same line, same start, same tangent — only the sign of the radius
|
|
347
|
+
// differs. Positive radius (CCW) curves to the left of the tangent;
|
|
348
|
+
// negative radius (CW) curves to the right.
|
|
349
|
+
const sCCW = sketch("xy", () => {
|
|
350
|
+
const h = hLine([-200, 100], 400).guide();
|
|
351
|
+
move([0, 0]);
|
|
352
|
+
vLine(50);
|
|
353
|
+
tArc(60, h);
|
|
354
|
+
});
|
|
355
|
+
render();
|
|
356
|
+
const sCW = sketch("xy", () => {
|
|
357
|
+
const h = hLine([-200, 100], 400).guide();
|
|
358
|
+
move([0, 0]);
|
|
359
|
+
vLine(50);
|
|
360
|
+
tArc(-60, h);
|
|
361
|
+
});
|
|
362
|
+
render();
|
|
363
|
+
const ccwArc = getEdgesByType(sCCW.getShapes(), "arc")[0];
|
|
364
|
+
const cwArc = getEdgesByType(sCW.getShapes(), "arc")[0];
|
|
365
|
+
// T̂ = (0, +1); "left" of the tangent is −x, "right" is +x.
|
|
366
|
+
expect(ccwArc.getLastVertex().toPoint().x).toBeLessThan(0);
|
|
367
|
+
expect(cwArc.getLastVertex().toPoint().x).toBeGreaterThan(0);
|
|
368
|
+
});
|
|
369
|
+
it("should chain geometry after the solved arc", () => {
|
|
370
|
+
const s = sketch("xy", () => {
|
|
371
|
+
const h = hLine([-200, 100], 400).guide();
|
|
372
|
+
move([0, 0]);
|
|
373
|
+
vLine(50);
|
|
374
|
+
tArc(60, h);
|
|
375
|
+
hLine(40);
|
|
376
|
+
});
|
|
377
|
+
render();
|
|
378
|
+
const shapes = s.getShapes();
|
|
379
|
+
expect(shapes.length).toBeGreaterThanOrEqual(3);
|
|
380
|
+
});
|
|
381
|
+
it("should record an error when there is no intersection", () => {
|
|
382
|
+
const s = sketch("xy", () => {
|
|
383
|
+
const h = hLine([-200, 500], 400).guide();
|
|
384
|
+
move([0, 0]);
|
|
385
|
+
vLine(10);
|
|
386
|
+
tArc(20, h);
|
|
387
|
+
});
|
|
388
|
+
render();
|
|
389
|
+
const children = s.getChildren();
|
|
390
|
+
const arc = children[children.length - 1];
|
|
391
|
+
expect(arc.getError()).toMatch(/does not intersect target/);
|
|
392
|
+
});
|
|
393
|
+
});
|
|
237
394
|
});
|
|
@@ -159,6 +159,24 @@ describe("color preservation through operations (Phase 3 lineage)", () => {
|
|
|
159
159
|
const orangeFaces = filleted.colorMap.filter(e => e.color === '#ffa500');
|
|
160
160
|
expect(orangeFaces.length).toBeGreaterThanOrEqual(2);
|
|
161
161
|
});
|
|
162
|
+
it("color does NOT bleed: filleting a side edge of a colored-top box leaves fillet faces uncolored", () => {
|
|
163
|
+
// The colored top face does NOT own the side (vertical) edges. Filleting
|
|
164
|
+
// those edges generates new corner faces whose lineage traces back to the
|
|
165
|
+
// side faces — so they must not inherit the top's color, even though
|
|
166
|
+
// their top arc is adjacent to the (still-orange) top face.
|
|
167
|
+
sketch("top", () => {
|
|
168
|
+
rect(100, 50).centered();
|
|
169
|
+
});
|
|
170
|
+
const e = extrude(20);
|
|
171
|
+
color("orange", e.endFaces());
|
|
172
|
+
const f = fillet(10, e.sideEdges());
|
|
173
|
+
render();
|
|
174
|
+
const filleted = f.getShapes()[0];
|
|
175
|
+
// Only the (Modified) top face should still be orange. The fillet faces
|
|
176
|
+
// came from side edges (uncolored), and the bottom was never colored.
|
|
177
|
+
const orangeCount = filleted.colorMap.filter(e => e.color === '#ffa500').length;
|
|
178
|
+
expect(orangeCount).toBe(1);
|
|
179
|
+
});
|
|
162
180
|
it("color survives a chamfer", () => {
|
|
163
181
|
sketch("xy", () => {
|
|
164
182
|
circle(40);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|