fluidcad 0.0.36 → 0.0.38
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/LICENSE.txt +21 -504
- package/README.md +1 -1
- package/lib/dist/common/edge.d.ts +1 -1
- package/lib/dist/common/face.d.ts +1 -1
- package/lib/dist/common/scene-object.d.ts +6 -0
- package/lib/dist/common/scene-object.js +8 -0
- package/lib/dist/common/shape-factory.d.ts +1 -1
- package/lib/dist/common/shape-history-tracker.d.ts +1 -1
- package/lib/dist/common/shape.d.ts +1 -1
- package/lib/dist/common/solid.d.ts +1 -1
- package/lib/dist/common/transformable-primitive.d.ts +12 -1
- package/lib/dist/common/transformable-primitive.js +27 -0
- package/lib/dist/common/vertex.d.ts +1 -1
- package/lib/dist/common/wire.d.ts +1 -1
- package/lib/dist/core/2d/index.d.ts +1 -0
- package/lib/dist/core/2d/index.js +1 -0
- package/lib/dist/core/2d/text.d.ts +30 -0
- package/lib/dist/core/2d/text.js +37 -0
- package/lib/dist/core/helix.d.ts +20 -0
- package/lib/dist/core/helix.js +36 -0
- package/lib/dist/core/index.d.ts +3 -1
- package/lib/dist/core/index.js +2 -0
- package/lib/dist/core/interfaces.d.ts +180 -0
- package/lib/dist/core/plane.d.ts +26 -6
- package/lib/dist/core/plane.js +21 -44
- package/lib/dist/core/wrap.d.ts +17 -0
- package/lib/dist/core/wrap.js +39 -0
- package/lib/dist/features/2d/offset.js +2 -2
- package/lib/dist/features/2d/text.d.ts +67 -0
- package/lib/dist/features/2d/text.js +320 -0
- package/lib/dist/features/cylinder.d.ts +3 -1
- package/lib/dist/features/cylinder.js +5 -2
- package/lib/dist/features/extrude-base.d.ts +1 -0
- package/lib/dist/features/extrude-to-face.d.ts +1 -0
- package/lib/dist/features/extrude-to-face.js +6 -0
- package/lib/dist/features/fillet.d.ts +1 -1
- package/lib/dist/features/helix.d.ts +41 -0
- package/lib/dist/features/helix.js +337 -0
- package/lib/dist/features/plane-from-object.d.ts +16 -4
- package/lib/dist/features/plane-from-object.js +101 -8
- package/lib/dist/features/select.js +32 -8
- package/lib/dist/features/simple-extruder.d.ts +1 -1
- package/lib/dist/features/simple-extruder.js +7 -2
- package/lib/dist/features/sphere.d.ts +3 -1
- package/lib/dist/features/sphere.js +5 -2
- package/lib/dist/features/sweep.js +7 -2
- package/lib/dist/features/wrap.d.ts +39 -0
- package/lib/dist/features/wrap.js +116 -0
- package/lib/dist/filters/edge/belongs-to-face.d.ts +3 -1
- package/lib/dist/filters/edge/belongs-to-face.js +14 -10
- package/lib/dist/filters/filter.d.ts +1 -1
- package/lib/dist/filters/from-object.d.ts +1 -1
- package/lib/dist/filters/tangent-expander.d.ts +1 -1
- package/lib/dist/filters/tangent-expander.js +57 -40
- package/lib/dist/helpers/scene-helpers.d.ts +2 -0
- package/lib/dist/helpers/scene-helpers.js +1 -1
- package/lib/dist/index.d.ts +2 -0
- package/lib/dist/index.js +3 -1
- package/lib/dist/io/file-import.d.ts +7 -0
- package/lib/dist/io/file-import.js +28 -1
- package/lib/dist/io/font-registry.d.ts +45 -0
- package/lib/dist/io/font-registry.js +272 -0
- package/lib/dist/math/bspline-interpolation.d.ts +29 -0
- package/lib/dist/math/bspline-interpolation.js +194 -0
- package/lib/dist/oc/boolean-ops.d.ts +3 -1
- package/lib/dist/oc/boolean-ops.js +15 -1
- package/lib/dist/oc/color-transfer.d.ts +1 -1
- package/lib/dist/oc/constraints/constraint-helpers.d.ts +4 -4
- package/lib/dist/oc/constraints/curve/tangent-circle-solver.js +10 -9
- package/lib/dist/oc/constraints/curve/tangent-line-solver.js +5 -6
- package/lib/dist/oc/convert.d.ts +1 -1
- package/lib/dist/oc/draft-ops.d.ts +1 -1
- package/lib/dist/oc/edge-ops.d.ts +2 -2
- package/lib/dist/oc/edge-ops.js +13 -14
- package/lib/dist/oc/edge-props.d.ts +1 -1
- package/lib/dist/oc/edge-query.d.ts +1 -1
- package/lib/dist/oc/edge-query.js +3 -8
- package/lib/dist/oc/errors.d.ts +8 -0
- package/lib/dist/oc/errors.js +27 -0
- package/lib/dist/oc/explorer.d.ts +2 -2
- package/lib/dist/oc/extrude-ops.d.ts +28 -2
- package/lib/dist/oc/extrude-ops.js +56 -7
- package/lib/dist/oc/face-ops.d.ts +2 -1
- package/lib/dist/oc/face-ops.js +11 -0
- package/lib/dist/oc/face-props.d.ts +1 -1
- package/lib/dist/oc/face-query.d.ts +12 -1
- package/lib/dist/oc/face-query.js +39 -0
- package/lib/dist/oc/fillet-ops.d.ts +1 -1
- package/lib/dist/oc/fillet-ops.js +4 -4
- package/lib/dist/oc/geometry.d.ts +1 -1
- package/lib/dist/oc/geometry.js +12 -14
- package/lib/dist/oc/helix-ops.d.ts +37 -0
- package/lib/dist/oc/helix-ops.js +88 -0
- package/lib/dist/oc/hit-test.d.ts +1 -1
- package/lib/dist/oc/index.d.ts +4 -0
- package/lib/dist/oc/index.js +2 -0
- package/lib/dist/oc/init.d.ts +1 -1
- package/lib/dist/oc/init.js +1 -1
- package/lib/dist/oc/intersection.js +1 -1
- package/lib/dist/oc/io.d.ts +6 -6
- package/lib/dist/oc/io.js +31 -24
- package/lib/dist/oc/measure/classify.d.ts +34 -0
- package/lib/dist/oc/measure/classify.js +246 -0
- package/lib/dist/oc/measure/measure-ops.d.ts +9 -0
- package/lib/dist/oc/measure/measure-ops.js +210 -0
- package/lib/dist/oc/measure/measure-types.d.ts +39 -0
- package/lib/dist/oc/measure/measure-types.js +1 -0
- package/lib/dist/oc/measure/sampling.d.ts +9 -0
- package/lib/dist/oc/measure/sampling.js +77 -0
- package/lib/dist/oc/measure/vec.d.ts +13 -0
- package/lib/dist/oc/measure/vec.js +23 -0
- package/lib/dist/oc/mesh.d.ts +1 -1
- package/lib/dist/oc/mesh.js +40 -28
- package/lib/dist/oc/path-sampler.d.ts +29 -0
- package/lib/dist/oc/path-sampler.js +63 -0
- package/lib/dist/oc/props.d.ts +1 -1
- package/lib/dist/oc/props.js +4 -1
- package/lib/dist/oc/shape-hash.d.ts +26 -0
- package/lib/dist/oc/shape-hash.js +32 -0
- package/lib/dist/oc/shape-ops.d.ts +5 -3
- package/lib/dist/oc/shape-ops.js +6 -5
- package/lib/dist/oc/sweep-ops.d.ts +13 -1
- package/lib/dist/oc/sweep-ops.js +174 -18
- package/lib/dist/oc/text-outline.d.ts +62 -0
- package/lib/dist/oc/text-outline.js +212 -0
- package/lib/dist/oc/thin-face-maker.d.ts +0 -19
- package/lib/dist/oc/thin-face-maker.js +3 -68
- package/lib/dist/oc/topology-index.d.ts +1 -1
- package/lib/dist/oc/vertex-ops.d.ts +1 -1
- package/lib/dist/oc/wire-ops.d.ts +18 -3
- package/lib/dist/oc/wire-ops.js +56 -5
- package/lib/dist/oc/wrap-development.d.ts +105 -0
- package/lib/dist/oc/wrap-development.js +179 -0
- package/lib/dist/oc/wrap-ops.d.ts +100 -0
- package/lib/dist/oc/wrap-ops.js +406 -0
- package/lib/dist/rendering/render-solid.js +10 -2
- package/lib/dist/scene-manager.d.ts +2 -0
- package/lib/dist/scene-manager.js +29 -0
- package/lib/dist/tests/features/2d/offset.test.js +74 -1
- package/lib/dist/tests/features/cylinder-curve-filter.test.js +3 -3
- package/lib/dist/tests/features/extrude-to-face.test.js +38 -1
- package/lib/dist/tests/features/helix.test.d.ts +1 -0
- package/lib/dist/tests/features/helix.test.js +295 -0
- package/lib/dist/tests/features/plane.test.js +95 -0
- package/lib/dist/tests/features/repeat-primitive.test.d.ts +1 -0
- package/lib/dist/tests/features/repeat-primitive.test.js +60 -0
- package/lib/dist/tests/features/rib.test.js +6 -1
- package/lib/dist/tests/features/sweep.test.js +170 -1
- package/lib/dist/tests/features/text.test.d.ts +1 -0
- package/lib/dist/tests/features/text.test.js +347 -0
- package/lib/dist/tests/features/wrap-development.test.d.ts +1 -0
- package/lib/dist/tests/features/wrap-development.test.js +130 -0
- package/lib/dist/tests/features/wrap-extruded-target.test.d.ts +1 -0
- package/lib/dist/tests/features/wrap-extruded-target.test.js +106 -0
- package/lib/dist/tests/features/wrap-repeat.test.d.ts +1 -0
- package/lib/dist/tests/features/wrap-repeat.test.js +93 -0
- package/lib/dist/tests/features/wrap.test.d.ts +1 -0
- package/lib/dist/tests/features/wrap.test.js +331 -0
- package/lib/dist/tests/math/bspline-interpolation.test.d.ts +1 -0
- package/lib/dist/tests/math/bspline-interpolation.test.js +119 -0
- package/lib/dist/tests/measure.test.d.ts +1 -0
- package/lib/dist/tests/measure.test.js +288 -0
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/llm-docs/api/helix.md +64 -0
- package/llm-docs/api/index.json +11 -2
- package/llm-docs/api/text.md +52 -0
- package/llm-docs/api/types/helix.md +105 -0
- package/llm-docs/api/types/text.md +138 -0
- package/llm-docs/api/types/wrap.md +131 -0
- package/llm-docs/api/wrap.md +62 -0
- package/llm-docs/index.json +121 -1
- package/mcp/dist/server.js +20 -1
- package/mcp/dist/tools/inspection.d.ts +17 -0
- package/mcp/dist/tools/inspection.js +14 -0
- package/package.json +7 -3
- package/server/dist/fluidcad-server.d.ts +11 -1
- package/server/dist/fluidcad-server.js +21 -1
- package/server/dist/index.js +4 -2
- package/server/dist/preferences.d.ts +4 -0
- package/server/dist/preferences.js +2 -0
- package/server/dist/routes/measure.d.ts +3 -0
- package/server/dist/routes/measure.js +32 -0
- package/server/dist/routes/params.js +1 -1
- package/server/dist/routes/preferences.js +6 -0
- package/server/dist/routes/sketch-edits.js +2 -1
- package/ui/dist/assets/{index-MRqwG9Vh.js → index-D8zV21wB.js} +149 -102
- package/ui/dist/assets/{index-CDJmUpFI.css → index-dAFdg2Un.css} +1 -1
- package/ui/dist/index.html +2 -2
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { setupOC, render } from "./setup.js";
|
|
3
|
+
import sketch from "../core/sketch.js";
|
|
4
|
+
import extrude from "../core/extrude.js";
|
|
5
|
+
import { bezier, circle, line, rect } from "../core/2d/index.js";
|
|
6
|
+
import { getSceneManager, getCurrentScene } from "../scene-manager.js";
|
|
7
|
+
import { Explorer } from "../oc/explorer.js";
|
|
8
|
+
import { classifyEdge, classifyFace } from "../oc/measure/classify.js";
|
|
9
|
+
function findEntities(kind, predicate) {
|
|
10
|
+
const found = [];
|
|
11
|
+
for (const obj of getCurrentScene().getAllSceneObjects()) {
|
|
12
|
+
for (const shape of obj.getAddedShapes()) {
|
|
13
|
+
if (shape.isMetaShapeFlag || shape.isGuideFlag || shape.getType() !== 'solid') {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const subs = kind === 'face' ? Explorer.findFacesWrapped(shape) : Explorer.findEdgesWrapped(shape);
|
|
17
|
+
subs.forEach((sub, index) => {
|
|
18
|
+
const info = kind === 'face' ? classifyFace(sub.getShape()) : classifyEdge(sub.getShape());
|
|
19
|
+
if (predicate(info)) {
|
|
20
|
+
found.push({ ref: { shapeId: shape.id, kind, index }, info });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return found;
|
|
26
|
+
}
|
|
27
|
+
function measureRefs(refs) {
|
|
28
|
+
const result = getSceneManager().measure(getCurrentScene(), refs);
|
|
29
|
+
expect(result).not.toBeNull();
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
function dirAlong(c, x, y, z) {
|
|
33
|
+
if (!c.dir) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return Math.abs(c.dir.x * x + c.dir.y * y + c.dir.z * z) > 0.9999;
|
|
37
|
+
}
|
|
38
|
+
function delta(d) {
|
|
39
|
+
return { x: d.to.x - d.from.x, y: d.to.y - d.from.y, z: d.to.z - d.from.z };
|
|
40
|
+
}
|
|
41
|
+
function makeBox(width = 100, depth = 50, height = 30) {
|
|
42
|
+
sketch("xy", () => {
|
|
43
|
+
rect(width, depth);
|
|
44
|
+
});
|
|
45
|
+
extrude(height);
|
|
46
|
+
render();
|
|
47
|
+
}
|
|
48
|
+
// Right triangle in the XZ plane: legs 40 (x) and 30 (z), hypotenuse face at
|
|
49
|
+
// atan(30/40) = 36.8699° to the bottom face.
|
|
50
|
+
function makeWedge() {
|
|
51
|
+
sketch("xz", () => {
|
|
52
|
+
line([0, 0], [40, 0]);
|
|
53
|
+
line([40, 0], [0, 30]);
|
|
54
|
+
line([0, 30], [0, 0]);
|
|
55
|
+
});
|
|
56
|
+
extrude(10);
|
|
57
|
+
render();
|
|
58
|
+
}
|
|
59
|
+
// Same wedge footprint, but the bottom edge is a straight quadratic bezier:
|
|
60
|
+
// its edge and extruded side face sit on fitted (non-canonical) geometry, so
|
|
61
|
+
// classification must recover the line/plane carriers numerically.
|
|
62
|
+
function makeBezierWedge() {
|
|
63
|
+
sketch("xy", () => {
|
|
64
|
+
bezier([0, 0], [20, 0], [40, 0]);
|
|
65
|
+
line([40, 0], [40, 30]);
|
|
66
|
+
line([40, 30], [0, 0]);
|
|
67
|
+
});
|
|
68
|
+
extrude(10);
|
|
69
|
+
render();
|
|
70
|
+
}
|
|
71
|
+
// Two non-touching Ø20 cylinders whose axes are 40 apart.
|
|
72
|
+
function makeTwoCylinders() {
|
|
73
|
+
sketch("xy", () => {
|
|
74
|
+
circle([0, 0], 20);
|
|
75
|
+
circle([40, 0], 20);
|
|
76
|
+
});
|
|
77
|
+
extrude(10);
|
|
78
|
+
render();
|
|
79
|
+
}
|
|
80
|
+
describe("measure", () => {
|
|
81
|
+
setupOC();
|
|
82
|
+
describe("plane-plane", () => {
|
|
83
|
+
it("measures parallel distance between opposite box faces", () => {
|
|
84
|
+
makeBox();
|
|
85
|
+
const faces = findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 1, 0, 0));
|
|
86
|
+
expect(faces).toHaveLength(2);
|
|
87
|
+
const result = measureRefs(faces.map((f) => f.ref));
|
|
88
|
+
expect(result.primary).toBe('parallelDist');
|
|
89
|
+
expect(result.primaryLabel).toBe('Parallel dist');
|
|
90
|
+
expect(result.parallelDist.value).toBeCloseTo(100, 4);
|
|
91
|
+
const d = delta(result.parallelDist);
|
|
92
|
+
expect(Math.abs(d.x)).toBeCloseTo(100, 4);
|
|
93
|
+
expect(d.y).toBeCloseTo(0, 4);
|
|
94
|
+
expect(d.z).toBeCloseTo(0, 4);
|
|
95
|
+
expect(result.minDist.value).toBeCloseTo(100, 4);
|
|
96
|
+
expect(result.angleDeg).toBeUndefined();
|
|
97
|
+
expect(result.totalArea).toBeCloseTo(2 * 50 * 30, 2);
|
|
98
|
+
});
|
|
99
|
+
it("measures max distance as the diagonal between opposite faces", () => {
|
|
100
|
+
makeBox();
|
|
101
|
+
const faces = findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 1, 0, 0));
|
|
102
|
+
const result = measureRefs(faces.map((f) => f.ref));
|
|
103
|
+
const expected = Math.sqrt(100 * 100 + 50 * 50 + 30 * 30);
|
|
104
|
+
expect(result.maxDist.value).toBeCloseTo(expected, 3);
|
|
105
|
+
const d = delta(result.maxDist);
|
|
106
|
+
expect(Math.abs(d.x)).toBeCloseTo(100, 3);
|
|
107
|
+
expect(Math.abs(d.y)).toBeCloseTo(50, 3);
|
|
108
|
+
expect(Math.abs(d.z)).toBeCloseTo(30, 3);
|
|
109
|
+
});
|
|
110
|
+
it("reports 90° for perpendicular box faces", () => {
|
|
111
|
+
makeBox();
|
|
112
|
+
const xFace = findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 1, 0, 0))[0];
|
|
113
|
+
const zFace = findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 0, 0, 1))[0];
|
|
114
|
+
const result = measureRefs([xFace.ref, zFace.ref]);
|
|
115
|
+
expect(result.primary).toBe('angle');
|
|
116
|
+
expect(result.primaryLabel).toBe('Perp planes angle');
|
|
117
|
+
expect(result.angleDeg).toBeCloseTo(90, 5);
|
|
118
|
+
expect(result.minDist.value).toBeCloseTo(0, 5);
|
|
119
|
+
});
|
|
120
|
+
it("reports the slope angle of a wedge face", () => {
|
|
121
|
+
makeWedge();
|
|
122
|
+
const bottom = findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 0, 0, 1));
|
|
123
|
+
const slanted = findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 0.6, 0, 0.8));
|
|
124
|
+
expect(bottom).toHaveLength(1);
|
|
125
|
+
expect(slanted).toHaveLength(1);
|
|
126
|
+
const result = measureRefs([bottom[0].ref, slanted[0].ref]);
|
|
127
|
+
expect(result.primary).toBe('angle');
|
|
128
|
+
expect(result.primaryLabel).toBe('Planes angle');
|
|
129
|
+
expect(result.angleDeg).toBeCloseTo(36.8699, 3);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe("fitted geometry fallbacks", () => {
|
|
133
|
+
it("measures the angle between a bezier-carried planar face and a true plane", () => {
|
|
134
|
+
makeBezierWedge();
|
|
135
|
+
const bezierFace = findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 0, 1, 0) && Math.abs(c.anchor.y) < 1e-4);
|
|
136
|
+
const slanted = findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 0.6, -0.8, 0));
|
|
137
|
+
expect(bezierFace).toHaveLength(1);
|
|
138
|
+
expect(slanted).toHaveLength(1);
|
|
139
|
+
const result = measureRefs([bezierFace[0].ref, slanted[0].ref]);
|
|
140
|
+
expect(result.entities[0].geomType).toBe('plane');
|
|
141
|
+
expect(result.primary).toBe('angle');
|
|
142
|
+
expect(result.primaryLabel).toBe('Planes angle');
|
|
143
|
+
expect(result.angleDeg).toBeCloseTo(36.8699, 3);
|
|
144
|
+
});
|
|
145
|
+
it("measures angle and parallel distance between bezier-carried straight edges", () => {
|
|
146
|
+
makeBezierWedge();
|
|
147
|
+
const bezierEdges = findEntities('edge', (c) => c.form === 'line' && dirAlong(c, 1, 0, 0) && Math.abs(c.anchor.y) < 1e-4);
|
|
148
|
+
expect(bezierEdges).toHaveLength(2);
|
|
149
|
+
const parallel = measureRefs(bezierEdges.map((e) => e.ref));
|
|
150
|
+
expect(parallel.primary).toBe('parallelDist');
|
|
151
|
+
expect(parallel.parallelDist.value).toBeCloseTo(10, 4);
|
|
152
|
+
const hypotenuse = findEntities('edge', (c) => c.form === 'line' && dirAlong(c, 0.8, 0.6, 0));
|
|
153
|
+
expect(hypotenuse.length).toBeGreaterThan(0);
|
|
154
|
+
const result = measureRefs([bezierEdges[0].ref, hypotenuse[0].ref]);
|
|
155
|
+
expect(result.primary).toBe('angle');
|
|
156
|
+
expect(result.primaryLabel).toBe('Lines angle');
|
|
157
|
+
expect(result.angleDeg).toBeCloseTo(36.8699, 3);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe("edge-edge", () => {
|
|
161
|
+
it("measures parallel distance between parallel edges", () => {
|
|
162
|
+
makeBox();
|
|
163
|
+
const topXEdges = findEntities('edge', (c) => c.form === 'line' && dirAlong(c, 1, 0, 0) && Math.abs(c.anchor.z - 30) < 1e-6);
|
|
164
|
+
expect(topXEdges).toHaveLength(2);
|
|
165
|
+
const result = measureRefs(topXEdges.map((e) => e.ref));
|
|
166
|
+
expect(result.primary).toBe('parallelDist');
|
|
167
|
+
expect(result.parallelDist.value).toBeCloseTo(50, 4);
|
|
168
|
+
expect(result.totalLength).toBeCloseTo(200, 4);
|
|
169
|
+
});
|
|
170
|
+
it("measures angle and min distance between skew edges", () => {
|
|
171
|
+
makeBox();
|
|
172
|
+
const topXEdges = findEntities('edge', (c) => c.form === 'line' && dirAlong(c, 1, 0, 0) && Math.abs(c.anchor.z - 30) < 1e-6);
|
|
173
|
+
const vertical = findEntities('edge', (c) => c.form === 'line' && dirAlong(c, 0, 0, 1));
|
|
174
|
+
expect(vertical.length).toBeGreaterThan(0);
|
|
175
|
+
// Pick a vertical edge on the opposite Y side so the pair doesn't touch.
|
|
176
|
+
const topEdge = topXEdges[0];
|
|
177
|
+
const skew = vertical.find((e) => Math.abs(e.info.anchor.y - topEdge.info.anchor.y) > 1);
|
|
178
|
+
expect(skew).toBeDefined();
|
|
179
|
+
const result = measureRefs([topEdge.ref, skew.ref]);
|
|
180
|
+
expect(result.primary).toBe('angle');
|
|
181
|
+
expect(result.primaryLabel).toBe('Lines angle');
|
|
182
|
+
expect(result.angleDeg).toBeCloseTo(90, 5);
|
|
183
|
+
expect(result.minDist.value).toBeCloseTo(50, 4);
|
|
184
|
+
});
|
|
185
|
+
it("measures center distance between circle edges", () => {
|
|
186
|
+
makeTwoCylinders();
|
|
187
|
+
const topRims = findEntities('edge', (c) => c.form === 'circle' && Math.abs(c.center.z - 10) < 1e-6);
|
|
188
|
+
expect(topRims).toHaveLength(2);
|
|
189
|
+
const result = measureRefs(topRims.map((e) => e.ref));
|
|
190
|
+
expect(result.primary).toBe('centerDist');
|
|
191
|
+
expect(result.primaryLabel).toBe('Center dist');
|
|
192
|
+
expect(result.centerDist.value).toBeCloseTo(40, 4);
|
|
193
|
+
expect(result.minDist.value).toBeCloseTo(20, 4);
|
|
194
|
+
expect(result.angleDeg).toBeUndefined();
|
|
195
|
+
expect(result.entities[0].radius).toBeCloseTo(10, 4);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe("face-edge", () => {
|
|
199
|
+
it("measures parallel distance between a face and a parallel edge", () => {
|
|
200
|
+
makeBox();
|
|
201
|
+
const topFace = findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 0, 0, 1) && Math.abs(c.anchor.z - 30) < 1e-6);
|
|
202
|
+
const bottomXEdge = findEntities('edge', (c) => c.form === 'line' && dirAlong(c, 1, 0, 0) && Math.abs(c.anchor.z) < 1e-6);
|
|
203
|
+
expect(topFace).toHaveLength(1);
|
|
204
|
+
expect(bottomXEdge.length).toBeGreaterThan(0);
|
|
205
|
+
const result = measureRefs([topFace[0].ref, bottomXEdge[0].ref]);
|
|
206
|
+
expect(result.primary).toBe('parallelDist');
|
|
207
|
+
expect(result.parallelDist.value).toBeCloseTo(30, 4);
|
|
208
|
+
const d = delta(result.parallelDist);
|
|
209
|
+
expect(Math.abs(d.z)).toBeCloseTo(30, 4);
|
|
210
|
+
});
|
|
211
|
+
it("measures the angle between a slanted edge and a face", () => {
|
|
212
|
+
makeWedge();
|
|
213
|
+
const bottom = findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 0, 0, 1))[0];
|
|
214
|
+
const slantedEdges = findEntities('edge', (c) => c.form === 'line' && dirAlong(c, -0.8, 0, 0.6));
|
|
215
|
+
expect(slantedEdges.length).toBeGreaterThan(0);
|
|
216
|
+
const result = measureRefs([bottom.ref, slantedEdges[0].ref]);
|
|
217
|
+
expect(result.primary).toBe('angle');
|
|
218
|
+
expect(result.primaryLabel).toBe('Line-plane angle');
|
|
219
|
+
expect(result.angleDeg).toBeCloseTo(36.8699, 3);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
describe("cylinders", () => {
|
|
223
|
+
it("measures axis distance between parallel cylinders", () => {
|
|
224
|
+
makeTwoCylinders();
|
|
225
|
+
const cylinders = findEntities('face', (c) => c.form === 'cylinder');
|
|
226
|
+
expect(cylinders).toHaveLength(2);
|
|
227
|
+
const result = measureRefs(cylinders.map((f) => f.ref));
|
|
228
|
+
expect(result.primary).toBe('axisDist');
|
|
229
|
+
expect(result.primaryLabel).toBe('Axis dist');
|
|
230
|
+
expect(result.axisDist.value).toBeCloseTo(40, 4);
|
|
231
|
+
expect(result.minDist.value).toBeCloseTo(20, 4);
|
|
232
|
+
expect(result.maxDist.value).toBeGreaterThan(60);
|
|
233
|
+
expect(result.maxDist.value).toBeLessThan(61.5);
|
|
234
|
+
expect(result.entities[0].geomType).toBe('cylinder');
|
|
235
|
+
expect(result.entities[0].radius).toBeCloseTo(10, 4);
|
|
236
|
+
});
|
|
237
|
+
it("reports the axis-plane angle between a cylinder and its base plane", () => {
|
|
238
|
+
makeTwoCylinders();
|
|
239
|
+
const cylinder = findEntities('face', (c) => c.form === 'cylinder')[0];
|
|
240
|
+
const base = findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 0, 0, 1))[0];
|
|
241
|
+
const result = measureRefs([cylinder.ref, base.ref]);
|
|
242
|
+
expect(result.primary).toBe('minDist');
|
|
243
|
+
expect(result.angleDeg).toBeCloseTo(90, 4);
|
|
244
|
+
expect(result.angleLabel).toBe('Axis-plane angle');
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
describe("single entity and aggregates", () => {
|
|
248
|
+
it("returns area for a single face", () => {
|
|
249
|
+
makeWedge();
|
|
250
|
+
const cap = findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 0, 1, 0))[0];
|
|
251
|
+
const result = measureRefs([cap.ref]);
|
|
252
|
+
expect(result.primary).toBe('totalArea');
|
|
253
|
+
expect(result.primaryLabel).toBe('Area');
|
|
254
|
+
expect(result.totalArea).toBeCloseTo((40 * 30) / 2, 3);
|
|
255
|
+
expect(result.minDist).toBeUndefined();
|
|
256
|
+
});
|
|
257
|
+
it("returns length for a single edge", () => {
|
|
258
|
+
makeBox();
|
|
259
|
+
const edge = findEntities('edge', (c) => c.form === 'line' && dirAlong(c, 1, 0, 0) && Math.abs(c.anchor.z - 30) < 1e-6)[0];
|
|
260
|
+
const result = measureRefs([edge.ref]);
|
|
261
|
+
expect(result.primary).toBe('totalLength');
|
|
262
|
+
expect(result.primaryLabel).toBe('Length');
|
|
263
|
+
expect(result.totalLength).toBeCloseTo(100, 4);
|
|
264
|
+
});
|
|
265
|
+
it("sums areas across 3+ selected faces", () => {
|
|
266
|
+
makeBox();
|
|
267
|
+
const faces = [
|
|
268
|
+
...findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 1, 0, 0)),
|
|
269
|
+
...findEntities('face', (c) => c.form === 'plane' && dirAlong(c, 0, 1, 0)),
|
|
270
|
+
];
|
|
271
|
+
expect(faces).toHaveLength(4);
|
|
272
|
+
const result = measureRefs(faces.map((f) => f.ref));
|
|
273
|
+
expect(result.primary).toBe('totalArea');
|
|
274
|
+
expect(result.totalArea).toBeCloseTo(2 * 50 * 30 + 2 * 100 * 30, 2);
|
|
275
|
+
expect(result.minDist).toBeUndefined();
|
|
276
|
+
});
|
|
277
|
+
it("returns null for an unknown shape or out-of-range index", () => {
|
|
278
|
+
makeBox();
|
|
279
|
+
expect(getSceneManager().measure(getCurrentScene(), [
|
|
280
|
+
{ shapeId: 'nope', kind: 'face', index: 0 },
|
|
281
|
+
])).toBeNull();
|
|
282
|
+
const face = findEntities('face', (c) => c.form === 'plane')[0];
|
|
283
|
+
expect(getSceneManager().measure(getCurrentScene(), [
|
|
284
|
+
{ shapeId: face.ref.shapeId, kind: 'face', index: 999 },
|
|
285
|
+
])).toBeNull();
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
});
|