fluidcad 0.0.35 → 0.0.37
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/bin/commands/login.js +33 -5
- package/bin/commands/mcp.js +3 -2
- package/bin/commands/publish.js +103 -8
- package/bin/lib/api-client.js +8 -0
- package/bin/lib/model-config.js +27 -4
- package/bin/lib/prompt.js +97 -0
- 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/wrap.d.ts +17 -0
- package/lib/dist/core/wrap.js +39 -0
- 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/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 +22 -1
- package/lib/dist/oc/sweep-ops.js +206 -18
- package/lib/dist/oc/text-outline.d.ts +62 -0
- package/lib/dist/oc/text-outline.js +212 -0
- 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 +1 -1
- package/lib/dist/oc/wire-ops.js +1 -1
- 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/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/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 +125 -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 +29 -0
- package/server/dist/fluidcad-server.js +40 -0
- package/server/dist/index.js +4 -2
- package/server/dist/model-package/pack.js +7 -6
- package/server/dist/model-package/types.d.ts +4 -3
- 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/preferences.js +6 -0
- package/server/dist/routes/sketch-edits.js +2 -1
- package/ui/dist/assets/{index-CDJmUpFI.css → index-dAFdg2Un.css} +1 -1
- package/ui/dist/assets/{index-MRqwG9Vh.js → index-no7mtr5s.js} +149 -102
- package/ui/dist/index.html +2 -2
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { getOC } from "../init.js";
|
|
2
|
+
import { classifyEdge, classifyFace } from "./classify.js";
|
|
3
|
+
import { maxDistanceBetween, sampleEntityPoints } from "./sampling.js";
|
|
4
|
+
import { acuteAngleDeg, add, areParallel, dist, dot, projectPointOnLine, scale, sub } from "./vec.js";
|
|
5
|
+
// sin(angle) tolerance for treating two directions as exactly parallel; loose
|
|
6
|
+
// enough to absorb numeric noise from booleans/imports, far below any
|
|
7
|
+
// deliberately modeled angle.
|
|
8
|
+
const PARALLEL_SIN_TOL = 1e-6;
|
|
9
|
+
const PERP_ANGLE_TOL_DEG = 1e-3;
|
|
10
|
+
const PRIMARY_LABELS = {
|
|
11
|
+
parallelDist: 'Parallel dist',
|
|
12
|
+
centerDist: 'Center dist',
|
|
13
|
+
axisDist: 'Axis dist',
|
|
14
|
+
minDist: 'Min dist',
|
|
15
|
+
totalArea: 'Area',
|
|
16
|
+
totalLength: 'Length',
|
|
17
|
+
};
|
|
18
|
+
function mkDist(from, to) {
|
|
19
|
+
return { value: dist(from, to), from, to };
|
|
20
|
+
}
|
|
21
|
+
function entityInfo(entity, ref) {
|
|
22
|
+
const info = { ref, geomType: entity.form };
|
|
23
|
+
if (entity.kind === 'face') {
|
|
24
|
+
info.area = entity.area;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
info.length = entity.length;
|
|
28
|
+
}
|
|
29
|
+
if (entity.radius !== undefined) {
|
|
30
|
+
info.radius = entity.radius;
|
|
31
|
+
}
|
|
32
|
+
return info;
|
|
33
|
+
}
|
|
34
|
+
/** Closest-pair distance between the two B-rep entities via BRepExtrema. */
|
|
35
|
+
function minDistanceBetween(a, b) {
|
|
36
|
+
const oc = getOC();
|
|
37
|
+
const progress = new oc.Message_ProgressRange();
|
|
38
|
+
const calc = new oc.BRepExtrema_DistShapeShape(a.shape, b.shape, oc.Extrema_ExtFlag.Extrema_ExtFlag_MIN, oc.Extrema_ExtAlgo.Extrema_ExtAlgo_Grad, progress);
|
|
39
|
+
let result;
|
|
40
|
+
if (calc.IsDone() && calc.NbSolution() > 0) {
|
|
41
|
+
const p1 = calc.PointOnShape1(1);
|
|
42
|
+
const p2 = calc.PointOnShape2(1);
|
|
43
|
+
result = {
|
|
44
|
+
value: calc.Value(),
|
|
45
|
+
from: { x: p1.X(), y: p1.Y(), z: p1.Z() },
|
|
46
|
+
to: { x: p2.X(), y: p2.Y(), z: p2.Z() },
|
|
47
|
+
};
|
|
48
|
+
p1.delete();
|
|
49
|
+
p2.delete();
|
|
50
|
+
}
|
|
51
|
+
calc.delete();
|
|
52
|
+
progress.delete();
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
/** A point on the entity's carrier line: the anchor itself for edges, the anchor projected onto the axis for cylinders/cones. */
|
|
56
|
+
function axisAnchor(entity) {
|
|
57
|
+
if (entity.form === 'line') {
|
|
58
|
+
return entity.anchor;
|
|
59
|
+
}
|
|
60
|
+
return projectPointOnLine(entity.anchor, entity.point, entity.dir);
|
|
61
|
+
}
|
|
62
|
+
/** Perpendicular distance from a parallel pair: plane↔plane, plane↔axis, axis↔axis. */
|
|
63
|
+
function parallelDistanceBetween(a, b) {
|
|
64
|
+
const aPlane = a.form === 'plane';
|
|
65
|
+
const bPlane = b.form === 'plane';
|
|
66
|
+
const aAxis = a.dirKind === 'axis';
|
|
67
|
+
const bAxis = b.dirKind === 'axis';
|
|
68
|
+
if (aPlane && bPlane) {
|
|
69
|
+
if (!areParallel(a.dir, b.dir, PARALLEL_SIN_TOL)) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const signed = dot(sub(b.anchor, a.anchor), a.dir);
|
|
73
|
+
return { value: Math.abs(signed), from: a.anchor, to: add(a.anchor, scale(a.dir, signed)) };
|
|
74
|
+
}
|
|
75
|
+
if ((aPlane && bAxis) || (aAxis && bPlane)) {
|
|
76
|
+
const [plane, axis] = aPlane ? [a, b] : [b, a];
|
|
77
|
+
if (Math.abs(dot(plane.dir, axis.dir)) >= PARALLEL_SIN_TOL) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const onAxis = axisAnchor(axis);
|
|
81
|
+
const signed = dot(sub(onAxis, plane.anchor), plane.dir);
|
|
82
|
+
const onPlane = sub(onAxis, scale(plane.dir, signed));
|
|
83
|
+
return aPlane ? mkDist(onPlane, onAxis) : mkDist(onAxis, onPlane);
|
|
84
|
+
}
|
|
85
|
+
if (a.form === 'line' && b.form === 'line') {
|
|
86
|
+
if (!areParallel(a.dir, b.dir, PARALLEL_SIN_TOL)) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
return mkDist(a.anchor, projectPointOnLine(a.anchor, b.point, b.dir));
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
/** Distance between the carrier axes of two parallel axis-bearing entities (cylinder/cone/line), at least one a surface. */
|
|
94
|
+
function axisDistanceBetween(a, b) {
|
|
95
|
+
if (a.dirKind !== 'axis' || b.dirKind !== 'axis') {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
if (a.form === 'line' && b.form === 'line') {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
if (!areParallel(a.dir, b.dir, PARALLEL_SIN_TOL)) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const from = axisAnchor(a);
|
|
105
|
+
return mkDist(from, projectPointOnLine(from, b.point, b.dir));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Angle between two entities, normalized to [0, 90]°. Normal-vs-normal and
|
|
109
|
+
* axis-vs-axis compare directly; normal-vs-axis measures the axis against the
|
|
110
|
+
* plane (0° when the axis lies in the plane).
|
|
111
|
+
*/
|
|
112
|
+
function angleBetween(a, b) {
|
|
113
|
+
if (!a.dir || !b.dir || !a.dirKind || !b.dirKind) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
if (a.dirKind === b.dirKind) {
|
|
117
|
+
const deg = acuteAngleDeg(a.dir, b.dir);
|
|
118
|
+
if (a.form === 'plane' && b.form === 'plane') {
|
|
119
|
+
const label = Math.abs(deg - 90) < PERP_ANGLE_TOL_DEG ? 'Perp planes angle' : 'Planes angle';
|
|
120
|
+
return { deg, label };
|
|
121
|
+
}
|
|
122
|
+
if (a.form === 'line' && b.form === 'line') {
|
|
123
|
+
return { deg, label: 'Lines angle' };
|
|
124
|
+
}
|
|
125
|
+
return { deg, label: a.dirKind === 'axis' ? 'Axes angle' : 'Angle' };
|
|
126
|
+
}
|
|
127
|
+
const [normal, axis] = a.dirKind === 'normal' ? [a, b] : [b, a];
|
|
128
|
+
const deg = 90 - acuteAngleDeg(normal.dir, axis.dir);
|
|
129
|
+
if (normal.form === 'plane') {
|
|
130
|
+
return { deg, label: axis.form === 'line' ? 'Line-plane angle' : 'Axis-plane angle' };
|
|
131
|
+
}
|
|
132
|
+
return { deg, label: 'Angle' };
|
|
133
|
+
}
|
|
134
|
+
function isParallelPair(a, b) {
|
|
135
|
+
if (!a.dir || !b.dir || !a.dirKind || !b.dirKind) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (a.dirKind === b.dirKind) {
|
|
139
|
+
return areParallel(a.dir, b.dir, PARALLEL_SIN_TOL);
|
|
140
|
+
}
|
|
141
|
+
return Math.abs(dot(a.dir, b.dir)) < PARALLEL_SIN_TOL;
|
|
142
|
+
}
|
|
143
|
+
function pickPrimary(a, b, result) {
|
|
144
|
+
const parallel = isParallelPair(a, b);
|
|
145
|
+
const planeLike = (e) => e.form === 'plane' || e.form === 'line';
|
|
146
|
+
if (planeLike(a) && planeLike(b)) {
|
|
147
|
+
return parallel && result.parallelDist ? 'parallelDist' : 'angle';
|
|
148
|
+
}
|
|
149
|
+
if (result.centerDist) {
|
|
150
|
+
return 'centerDist';
|
|
151
|
+
}
|
|
152
|
+
if (result.axisDist) {
|
|
153
|
+
return 'axisDist';
|
|
154
|
+
}
|
|
155
|
+
return 'minDist';
|
|
156
|
+
}
|
|
157
|
+
export class MeasureOps {
|
|
158
|
+
static measure(inputs) {
|
|
159
|
+
const classified = inputs.map((input) => input.ref.kind === 'face' ? classifyFace(input.shape) : classifyEdge(input.shape));
|
|
160
|
+
const result = {
|
|
161
|
+
entities: classified.map((entity, i) => entityInfo(entity, inputs[i].ref)),
|
|
162
|
+
primary: 'minDist',
|
|
163
|
+
primaryLabel: PRIMARY_LABELS.minDist,
|
|
164
|
+
};
|
|
165
|
+
const faces = classified.filter((e) => e.kind === 'face');
|
|
166
|
+
const edges = classified.filter((e) => e.kind === 'edge');
|
|
167
|
+
if (faces.length > 0) {
|
|
168
|
+
result.totalArea = faces.reduce((sum, e) => sum + (e.area ?? 0), 0);
|
|
169
|
+
}
|
|
170
|
+
if (edges.length > 0) {
|
|
171
|
+
result.totalLength = edges.reduce((sum, e) => sum + (e.length ?? 0), 0);
|
|
172
|
+
}
|
|
173
|
+
if (classified.length === 2) {
|
|
174
|
+
const [a, b] = classified;
|
|
175
|
+
result.minDist = minDistanceBetween(a, b);
|
|
176
|
+
result.maxDist = maxDistanceBetween(sampleEntityPoints(a), sampleEntityPoints(b));
|
|
177
|
+
result.parallelDist = parallelDistanceBetween(a, b);
|
|
178
|
+
result.axisDist = axisDistanceBetween(a, b);
|
|
179
|
+
if (a.center && b.center) {
|
|
180
|
+
result.centerDist = mkDist(a.center, b.center);
|
|
181
|
+
}
|
|
182
|
+
if (!isParallelPair(a, b)) {
|
|
183
|
+
const angle = angleBetween(a, b);
|
|
184
|
+
if (angle) {
|
|
185
|
+
result.angleDeg = angle.deg;
|
|
186
|
+
result.angleLabel = angle.label;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
let primary = pickPrimary(a, b, result);
|
|
190
|
+
if (primary === 'angle' && result.angleDeg === undefined) {
|
|
191
|
+
primary = 'minDist';
|
|
192
|
+
}
|
|
193
|
+
if (primary !== 'angle' && primary !== 'minDist' && !result[primary]) {
|
|
194
|
+
primary = 'minDist';
|
|
195
|
+
}
|
|
196
|
+
result.primary = primary;
|
|
197
|
+
result.primaryLabel = primary === 'angle' ? result.angleLabel : PRIMARY_LABELS[primary];
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
// Single entity or 3+ entities: aggregate values only.
|
|
201
|
+
if (result.totalArea !== undefined) {
|
|
202
|
+
result.primary = 'totalArea';
|
|
203
|
+
}
|
|
204
|
+
else if (result.totalLength !== undefined) {
|
|
205
|
+
result.primary = 'totalLength';
|
|
206
|
+
}
|
|
207
|
+
result.primaryLabel = PRIMARY_LABELS[result.primary];
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface MeasureVec {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
z: number;
|
|
5
|
+
}
|
|
6
|
+
/** A distance value together with the two world-space endpoints that realize it. */
|
|
7
|
+
export interface MeasureDistanceValue {
|
|
8
|
+
value: number;
|
|
9
|
+
from: MeasureVec;
|
|
10
|
+
to: MeasureVec;
|
|
11
|
+
}
|
|
12
|
+
export type MeasureEntityKind = 'face' | 'edge';
|
|
13
|
+
export interface MeasureEntityRef {
|
|
14
|
+
shapeId: string;
|
|
15
|
+
kind: MeasureEntityKind;
|
|
16
|
+
index: number;
|
|
17
|
+
}
|
|
18
|
+
export interface MeasureEntityInfo {
|
|
19
|
+
ref: MeasureEntityRef;
|
|
20
|
+
geomType: string;
|
|
21
|
+
area?: number;
|
|
22
|
+
length?: number;
|
|
23
|
+
radius?: number;
|
|
24
|
+
}
|
|
25
|
+
export type MeasurePrimaryKey = 'parallelDist' | 'centerDist' | 'axisDist' | 'minDist' | 'angle' | 'totalArea' | 'totalLength';
|
|
26
|
+
export interface MeasureResult {
|
|
27
|
+
entities: MeasureEntityInfo[];
|
|
28
|
+
primary: MeasurePrimaryKey;
|
|
29
|
+
primaryLabel: string;
|
|
30
|
+
minDist?: MeasureDistanceValue;
|
|
31
|
+
maxDist?: MeasureDistanceValue;
|
|
32
|
+
parallelDist?: MeasureDistanceValue;
|
|
33
|
+
centerDist?: MeasureDistanceValue;
|
|
34
|
+
axisDist?: MeasureDistanceValue;
|
|
35
|
+
angleDeg?: number;
|
|
36
|
+
angleLabel?: string;
|
|
37
|
+
totalArea?: number;
|
|
38
|
+
totalLength?: number;
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MeasureDistanceValue, MeasureVec } from "./measure-types.js";
|
|
2
|
+
import type { ClassifiedEntity } from "./classify.js";
|
|
3
|
+
export declare function sampleEntityPoints(entity: ClassifiedEntity): MeasureVec[];
|
|
4
|
+
/**
|
|
5
|
+
* Approximate maximum distance between two entities as the farthest pair of
|
|
6
|
+
* sampled points (triangulation nodes / curve samples), so accuracy is bounded
|
|
7
|
+
* by the mesh deflection.
|
|
8
|
+
*/
|
|
9
|
+
export declare function maxDistanceBetween(samplesA: MeasureVec[], samplesB: MeasureVec[]): MeasureDistanceValue;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { getOC } from "../init.js";
|
|
2
|
+
import { Mesh } from "../mesh.js";
|
|
3
|
+
const MAX_FACE_SAMPLES = 400;
|
|
4
|
+
const EDGE_SAMPLES = 64;
|
|
5
|
+
function sampleFacePoints(shape) {
|
|
6
|
+
const oc = getOC();
|
|
7
|
+
const face = oc.TopoDS.Face(shape);
|
|
8
|
+
Mesh.ensureTriangulated(face);
|
|
9
|
+
const location = new oc.TopLoc_Location();
|
|
10
|
+
const triangulation = oc.BRep_Tool.Triangulation(face, location, 0);
|
|
11
|
+
if (triangulation.isNull()) {
|
|
12
|
+
location.delete();
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const transform = location.Transformation();
|
|
16
|
+
const nbNodes = triangulation.NbNodes();
|
|
17
|
+
const stride = Math.max(1, Math.ceil(nbNodes / MAX_FACE_SAMPLES));
|
|
18
|
+
const points = [];
|
|
19
|
+
for (let i = 1; i <= nbNodes; i += stride) {
|
|
20
|
+
const node = triangulation.Node(i);
|
|
21
|
+
const p = node.Transformed(transform);
|
|
22
|
+
points.push({ x: p.X(), y: p.Y(), z: p.Z() });
|
|
23
|
+
node.delete();
|
|
24
|
+
p.delete();
|
|
25
|
+
}
|
|
26
|
+
transform.delete();
|
|
27
|
+
triangulation.delete();
|
|
28
|
+
location.delete();
|
|
29
|
+
return points;
|
|
30
|
+
}
|
|
31
|
+
function sampleEdgePoints(shape) {
|
|
32
|
+
const oc = getOC();
|
|
33
|
+
const edge = oc.TopoDS.Edge(shape);
|
|
34
|
+
const adaptor = new oc.BRepAdaptor_Curve(edge);
|
|
35
|
+
const first = adaptor.FirstParameter();
|
|
36
|
+
const last = adaptor.LastParameter();
|
|
37
|
+
const points = [];
|
|
38
|
+
for (let i = 0; i <= EDGE_SAMPLES; i++) {
|
|
39
|
+
const u = first + ((last - first) * i) / EDGE_SAMPLES;
|
|
40
|
+
const p = adaptor.Value(u);
|
|
41
|
+
points.push({ x: p.X(), y: p.Y(), z: p.Z() });
|
|
42
|
+
p.delete();
|
|
43
|
+
}
|
|
44
|
+
adaptor.delete();
|
|
45
|
+
return points;
|
|
46
|
+
}
|
|
47
|
+
export function sampleEntityPoints(entity) {
|
|
48
|
+
const points = entity.kind === 'face' ? sampleFacePoints(entity.shape) : sampleEdgePoints(entity.shape);
|
|
49
|
+
if (points.length === 0) {
|
|
50
|
+
points.push(entity.anchor);
|
|
51
|
+
}
|
|
52
|
+
return points;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Approximate maximum distance between two entities as the farthest pair of
|
|
56
|
+
* sampled points (triangulation nodes / curve samples), so accuracy is bounded
|
|
57
|
+
* by the mesh deflection.
|
|
58
|
+
*/
|
|
59
|
+
export function maxDistanceBetween(samplesA, samplesB) {
|
|
60
|
+
let best = -1;
|
|
61
|
+
let from = samplesA[0];
|
|
62
|
+
let to = samplesB[0];
|
|
63
|
+
for (const a of samplesA) {
|
|
64
|
+
for (const b of samplesB) {
|
|
65
|
+
const dx = a.x - b.x;
|
|
66
|
+
const dy = a.y - b.y;
|
|
67
|
+
const dz = a.z - b.z;
|
|
68
|
+
const d2 = dx * dx + dy * dy + dz * dz;
|
|
69
|
+
if (d2 > best) {
|
|
70
|
+
best = d2;
|
|
71
|
+
from = a;
|
|
72
|
+
to = b;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { value: Math.sqrt(best), from, to };
|
|
77
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MeasureVec } from "./measure-types.js";
|
|
2
|
+
export declare const sub: (a: MeasureVec, b: MeasureVec) => MeasureVec;
|
|
3
|
+
export declare const add: (a: MeasureVec, b: MeasureVec) => MeasureVec;
|
|
4
|
+
export declare const scale: (a: MeasureVec, s: number) => MeasureVec;
|
|
5
|
+
export declare const dot: (a: MeasureVec, b: MeasureVec) => number;
|
|
6
|
+
export declare const cross: (a: MeasureVec, b: MeasureVec) => MeasureVec;
|
|
7
|
+
export declare const len: (a: MeasureVec) => number;
|
|
8
|
+
export declare const dist: (a: MeasureVec, b: MeasureVec) => number;
|
|
9
|
+
export declare function projectPointOnLine(p: MeasureVec, linePoint: MeasureVec, lineDir: MeasureVec): MeasureVec;
|
|
10
|
+
/** Acute angle between two unit directions, in degrees ([0, 90], orientation-insensitive). */
|
|
11
|
+
export declare function acuteAngleDeg(a: MeasureVec, b: MeasureVec): number;
|
|
12
|
+
/** True when two unit directions are parallel (or anti-parallel) within `sinTol`. */
|
|
13
|
+
export declare function areParallel(a: MeasureVec, b: MeasureVec, sinTol: number): boolean;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y, z: a.z - b.z });
|
|
2
|
+
export const add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y, z: a.z + b.z });
|
|
3
|
+
export const scale = (a, s) => ({ x: a.x * s, y: a.y * s, z: a.z * s });
|
|
4
|
+
export const dot = (a, b) => a.x * b.x + a.y * b.y + a.z * b.z;
|
|
5
|
+
export const cross = (a, b) => ({
|
|
6
|
+
x: a.y * b.z - a.z * b.y,
|
|
7
|
+
y: a.z * b.x - a.x * b.z,
|
|
8
|
+
z: a.x * b.y - a.y * b.x,
|
|
9
|
+
});
|
|
10
|
+
export const len = (a) => Math.sqrt(dot(a, a));
|
|
11
|
+
export const dist = (a, b) => len(sub(a, b));
|
|
12
|
+
export function projectPointOnLine(p, linePoint, lineDir) {
|
|
13
|
+
return add(linePoint, scale(lineDir, dot(sub(p, linePoint), lineDir)));
|
|
14
|
+
}
|
|
15
|
+
/** Acute angle between two unit directions, in degrees ([0, 90], orientation-insensitive). */
|
|
16
|
+
export function acuteAngleDeg(a, b) {
|
|
17
|
+
const c = Math.min(1, Math.abs(dot(a, b)));
|
|
18
|
+
return Math.acos(c) * (180 / Math.PI);
|
|
19
|
+
}
|
|
20
|
+
/** True when two unit directions are parallel (or anti-parallel) within `sinTol`. */
|
|
21
|
+
export function areParallel(a, b, sinTol) {
|
|
22
|
+
return len(cross(a, b)) < sinTol;
|
|
23
|
+
}
|
package/lib/dist/oc/mesh.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TopoDS_Edge, TopoDS_Face, TopoDS_Shape } from "
|
|
1
|
+
import type { TopoDS_Edge, TopoDS_Face, TopoDS_Shape } from "ocjs-fluidcad";
|
|
2
2
|
import { Face } from "../common/face.js";
|
|
3
3
|
import { Shape } from "../common/shape.js";
|
|
4
4
|
export interface MeshData {
|
package/lib/dist/oc/mesh.js
CHANGED
|
@@ -48,13 +48,12 @@ export class Mesh {
|
|
|
48
48
|
const normals = [];
|
|
49
49
|
const indices = [];
|
|
50
50
|
const aLocation = new oc.TopLoc_Location();
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
51
|
+
const triangulation = oc.BRep_Tool.Triangulation(face, aLocation, 0);
|
|
52
|
+
if (triangulation.isNull()) {
|
|
53
53
|
aLocation.delete();
|
|
54
54
|
return null;
|
|
55
55
|
}
|
|
56
|
-
const pc = new oc.Poly_Connect(
|
|
57
|
-
const triangulation = faceTriangulation.get();
|
|
56
|
+
const pc = new oc.Poly_Connect(triangulation);
|
|
58
57
|
const nbNodes = triangulation.NbNodes();
|
|
59
58
|
for (let i = 1; i <= nbNodes; i++) {
|
|
60
59
|
const t1 = aLocation.Transformation();
|
|
@@ -65,34 +64,50 @@ export class Mesh {
|
|
|
65
64
|
p1.delete();
|
|
66
65
|
t1.delete();
|
|
67
66
|
}
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
// OCCT 8.0: StdPrs_ToolTriangulatedShape (TKV3d) is gone; the surface-normals
|
|
68
|
+
// utility moved to BRepLib_ToolTriangulatedShape (TKTopAlgo). It computes nodal
|
|
69
|
+
// normals into the triangulation itself (no-op if it already has them), which we
|
|
70
|
+
// then read back via Poly_Triangulation.Normal(i).
|
|
71
|
+
oc.BRepLib_ToolTriangulatedShape.ComputeNormals(face, triangulation, pc);
|
|
72
|
+
// A triangulation stores normals in the natural orientation of the
|
|
73
|
+
// underlying surface, NOT the face's topological orientation. For a
|
|
74
|
+
// TopAbs_REVERSED face the outward normal is the opposite of the surface
|
|
75
|
+
// normal, so we must flip the nodal normals here. (Pre-OCCT-8 this was done
|
|
76
|
+
// internally by StdPrs_ToolTriangulatedShape::Normal; BRepLib_ToolTriangulatedShape
|
|
77
|
+
// ::ComputeNormals does not, which left reversed faces shaded as if lit from
|
|
78
|
+
// inside the solid — they rendered dark.) The triangle winding below is
|
|
79
|
+
// swapped under the same condition so winding and shading normals agree.
|
|
80
|
+
const orient = face.Orientation();
|
|
81
|
+
const reversed = orient !== oc.TopAbs_Orientation.TopAbs_FORWARD;
|
|
70
82
|
for (let i = 1; i <= nbNodes; i++) {
|
|
71
83
|
const t1 = aLocation.Transformation();
|
|
72
|
-
const d1 =
|
|
84
|
+
const d1 = triangulation.Normal(i);
|
|
73
85
|
const d = d1.Transformed(t1);
|
|
74
|
-
|
|
86
|
+
if (reversed) {
|
|
87
|
+
normals.push(-d.X(), -d.Y(), -d.Z());
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
normals.push(d.X(), d.Y(), d.Z());
|
|
91
|
+
}
|
|
75
92
|
d1.delete();
|
|
76
93
|
d.delete();
|
|
77
94
|
t1.delete();
|
|
78
95
|
}
|
|
79
|
-
const orient = face.Orientation();
|
|
80
96
|
const triangles = triangulation.Triangles();
|
|
81
97
|
for (let nt = 1; nt <= triangulation.NbTriangles(); nt++) {
|
|
82
98
|
const t = triangles.Value(nt);
|
|
83
99
|
let n1 = t.Value(1) - 1;
|
|
84
100
|
let n2 = t.Value(2) - 1;
|
|
85
101
|
let n3 = t.Value(3) - 1;
|
|
86
|
-
if (
|
|
102
|
+
if (reversed) {
|
|
87
103
|
[n1, n2] = [n2, n1];
|
|
88
104
|
}
|
|
89
105
|
indices.push(vertexOffset + n1, vertexOffset + n2, vertexOffset + n3);
|
|
90
106
|
t.delete();
|
|
91
107
|
}
|
|
92
108
|
pc.delete();
|
|
93
|
-
faceNormals.delete();
|
|
94
109
|
triangles.delete();
|
|
95
|
-
|
|
110
|
+
triangulation.delete();
|
|
96
111
|
aLocation.delete();
|
|
97
112
|
return { vertices, normals, indices, count: nbNodes };
|
|
98
113
|
}
|
|
@@ -107,21 +122,19 @@ export class Mesh {
|
|
|
107
122
|
return null;
|
|
108
123
|
}
|
|
109
124
|
const loc = new oc.TopLoc_Location();
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
|
|
125
|
+
const tri = oc.BRep_Tool.Triangulation(face, loc, 0);
|
|
126
|
+
if (tri.isNull()) {
|
|
127
|
+
tri.delete();
|
|
113
128
|
loc.delete();
|
|
114
129
|
return null;
|
|
115
130
|
}
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
131
|
+
const poly = oc.BRep_Tool.PolygonOnTriangulation(edge, tri, loc);
|
|
132
|
+
if (poly.isNull()) {
|
|
133
|
+
poly.delete();
|
|
134
|
+
tri.delete();
|
|
120
135
|
loc.delete();
|
|
121
136
|
return null;
|
|
122
137
|
}
|
|
123
|
-
const tri = triHandle.get();
|
|
124
|
-
const poly = polyHandle.get();
|
|
125
138
|
const nbNodes = poly.NbNodes();
|
|
126
139
|
const tx = loc.Transformation();
|
|
127
140
|
const vertices = new Array(nbNodes * 3);
|
|
@@ -142,8 +155,8 @@ export class Mesh {
|
|
|
142
155
|
indices[i * 2 + 1] = i + 1;
|
|
143
156
|
}
|
|
144
157
|
tx.delete();
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
poly.delete();
|
|
159
|
+
tri.delete();
|
|
147
160
|
loc.delete();
|
|
148
161
|
return { vertices, normals: [], indices };
|
|
149
162
|
}
|
|
@@ -161,15 +174,14 @@ export class Mesh {
|
|
|
161
174
|
}
|
|
162
175
|
Mesh.ensureTriangulated(edge, opts);
|
|
163
176
|
const loc = new oc.TopLoc_Location();
|
|
164
|
-
const
|
|
165
|
-
if (
|
|
166
|
-
|
|
177
|
+
const poly = oc.BRep_Tool.Polygon3D(ocEdge, loc);
|
|
178
|
+
if (poly.isNull()) {
|
|
179
|
+
poly.delete();
|
|
167
180
|
loc.delete();
|
|
168
181
|
ocEdge.delete();
|
|
169
182
|
console.warn("Edge has no stored Polygon3D after meshing; returning empty polyline.");
|
|
170
183
|
return { vertices: [], normals: [], indices: [] };
|
|
171
184
|
}
|
|
172
|
-
const poly = polyHandle.get();
|
|
173
185
|
const nbNodes = poly.NbNodes();
|
|
174
186
|
const nodes = poly.Nodes();
|
|
175
187
|
const tx = loc.Transformation();
|
|
@@ -190,7 +202,7 @@ export class Mesh {
|
|
|
190
202
|
indices[i * 2 + 1] = i + 1;
|
|
191
203
|
}
|
|
192
204
|
tx.delete();
|
|
193
|
-
|
|
205
|
+
poly.delete();
|
|
194
206
|
loc.delete();
|
|
195
207
|
ocEdge.delete();
|
|
196
208
|
return { vertices, normals: [], indices };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Wire } from "../common/wire.js";
|
|
2
|
+
import { Point } from "../math/point.js";
|
|
3
|
+
import { Vector3d } from "../math/vector3d.js";
|
|
4
|
+
export interface PathFrame {
|
|
5
|
+
point: Point;
|
|
6
|
+
/** Unit tangent in the direction of increasing arc length. */
|
|
7
|
+
tangent: Vector3d;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Arc-length parameterization of a wire. Wraps `BRepAdaptor_CompCurve` +
|
|
11
|
+
* `GCPnts_AbscissaPoint` so callers can evaluate a point and unit tangent at a
|
|
12
|
+
* distance measured along the path, independent of how the underlying curves
|
|
13
|
+
* are parameterized (exact on Béziers/B-splines, not just lines and arcs).
|
|
14
|
+
*
|
|
15
|
+
* Out-of-range distances wrap around on a closed wire and extrapolate
|
|
16
|
+
* linearly along the end tangents on an open one.
|
|
17
|
+
*/
|
|
18
|
+
export declare class PathSampler {
|
|
19
|
+
private adaptor;
|
|
20
|
+
private firstParam;
|
|
21
|
+
readonly length: number;
|
|
22
|
+
readonly closed: boolean;
|
|
23
|
+
constructor(wire: Wire);
|
|
24
|
+
evalAt(s: number): PathFrame;
|
|
25
|
+
/** `count + 1` points evenly spaced by arc length, including both ends. */
|
|
26
|
+
sample(count: number): Point[];
|
|
27
|
+
private evalOn;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getOC } from "./init.js";
|
|
2
|
+
import { Convert } from "./convert.js";
|
|
3
|
+
/**
|
|
4
|
+
* Arc-length parameterization of a wire. Wraps `BRepAdaptor_CompCurve` +
|
|
5
|
+
* `GCPnts_AbscissaPoint` so callers can evaluate a point and unit tangent at a
|
|
6
|
+
* distance measured along the path, independent of how the underlying curves
|
|
7
|
+
* are parameterized (exact on Béziers/B-splines, not just lines and arcs).
|
|
8
|
+
*
|
|
9
|
+
* Out-of-range distances wrap around on a closed wire and extrapolate
|
|
10
|
+
* linearly along the end tangents on an open one.
|
|
11
|
+
*/
|
|
12
|
+
export class PathSampler {
|
|
13
|
+
adaptor;
|
|
14
|
+
firstParam;
|
|
15
|
+
length;
|
|
16
|
+
closed;
|
|
17
|
+
constructor(wire) {
|
|
18
|
+
const oc = getOC();
|
|
19
|
+
this.adaptor = new oc.BRepAdaptor_CompCurve(wire.getShape(), false);
|
|
20
|
+
this.firstParam = this.adaptor.FirstParameter();
|
|
21
|
+
this.length = oc.GCPnts_AbscissaPoint.Length(this.adaptor);
|
|
22
|
+
this.closed = this.adaptor.IsClosed();
|
|
23
|
+
}
|
|
24
|
+
evalAt(s) {
|
|
25
|
+
if (this.closed) {
|
|
26
|
+
s = ((s % this.length) + this.length) % this.length;
|
|
27
|
+
}
|
|
28
|
+
else if (s < 0) {
|
|
29
|
+
const start = this.evalOn(0);
|
|
30
|
+
return { point: start.point.add(start.tangent.multiply(s)), tangent: start.tangent };
|
|
31
|
+
}
|
|
32
|
+
else if (s > this.length) {
|
|
33
|
+
const end = this.evalOn(this.length);
|
|
34
|
+
return { point: end.point.add(end.tangent.multiply(s - this.length)), tangent: end.tangent };
|
|
35
|
+
}
|
|
36
|
+
return this.evalOn(s);
|
|
37
|
+
}
|
|
38
|
+
/** `count + 1` points evenly spaced by arc length, including both ends. */
|
|
39
|
+
sample(count) {
|
|
40
|
+
const points = [];
|
|
41
|
+
for (let i = 0; i <= count; i++) {
|
|
42
|
+
points.push(this.evalOn((this.length * i) / count).point);
|
|
43
|
+
}
|
|
44
|
+
return points;
|
|
45
|
+
}
|
|
46
|
+
evalOn(s) {
|
|
47
|
+
const oc = getOC();
|
|
48
|
+
const abscissa = new oc.GCPnts_AbscissaPoint(this.adaptor, s, this.firstParam);
|
|
49
|
+
if (!abscissa.IsDone()) {
|
|
50
|
+
abscissa.delete();
|
|
51
|
+
throw new Error(`Failed to locate arc-length ${s} on path (length ${this.length}).`);
|
|
52
|
+
}
|
|
53
|
+
const u = abscissa.Parameter();
|
|
54
|
+
abscissa.delete();
|
|
55
|
+
const res = this.adaptor.EvalD1(u);
|
|
56
|
+
const point = Convert.toPoint(res.Point, true);
|
|
57
|
+
const tangent = Convert.toVector3d(res.D1, true).normalize();
|
|
58
|
+
return { point, tangent };
|
|
59
|
+
}
|
|
60
|
+
dispose() {
|
|
61
|
+
this.adaptor.delete();
|
|
62
|
+
}
|
|
63
|
+
}
|
package/lib/dist/oc/props.d.ts
CHANGED
package/lib/dist/oc/props.js
CHANGED
|
@@ -3,7 +3,10 @@ export class ShapeProps {
|
|
|
3
3
|
static getProperties(shape) {
|
|
4
4
|
const oc = getOC();
|
|
5
5
|
const volumeProps = new oc.GProp_GProps();
|
|
6
|
-
|
|
6
|
+
// Eps-driven adaptive integration: the default fixed-order quadrature
|
|
7
|
+
// under-integrates faces trimmed by fitted B-spline pcurves (seen with
|
|
8
|
+
// wrap() pads — ~7% volume error) while this overload stays exact.
|
|
9
|
+
oc.BRepGProp.VolumeProperties(shape, volumeProps, 1e-6, false, false);
|
|
7
10
|
const volumeMm3 = volumeProps.Mass();
|
|
8
11
|
const cog = volumeProps.CentreOfMass();
|
|
9
12
|
const centroid = { x: cog.X(), y: cog.Y(), z: cog.Z() };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { TopoDS_Shape } from "ocjs-fluidcad";
|
|
2
|
+
/**
|
|
3
|
+
* Produces stable, `IsSame`-consistent integer keys for TopoDS shapes so they can
|
|
4
|
+
* be used as JS `Map`/`Set` keys.
|
|
5
|
+
*
|
|
6
|
+
* OCCT 8.0 removed `TopoDS_Shape::HashCode`. This interns shapes in a
|
|
7
|
+
* `TopTools_IndexedMapOfShape` — whose hasher compares keys with `IsSame` — and
|
|
8
|
+
* returns each shape's stable 1-based index. That gives the same bucketing the old
|
|
9
|
+
* `HashCode` + `IsSame` check provided, but collision-free (the same `IsSame`
|
|
10
|
+
* sub-shape always maps to the same key, distinct sub-shapes never collide).
|
|
11
|
+
*
|
|
12
|
+
* Wraps an OCCT map, so it owns native memory: scope one per operation and
|
|
13
|
+
* `delete()` it when done. Follows the same indexed-map convention as
|
|
14
|
+
* {@link TopologyIndex}.
|
|
15
|
+
*/
|
|
16
|
+
export declare class ShapeHasher {
|
|
17
|
+
private readonly map;
|
|
18
|
+
constructor();
|
|
19
|
+
/**
|
|
20
|
+
* Stable, `IsSame`-consistent key for `shape`. Interns the shape on first use; an
|
|
21
|
+
* as-yet-unseen shape gets a fresh key, so a `Map` lookup against keys from other
|
|
22
|
+
* shapes correctly misses.
|
|
23
|
+
*/
|
|
24
|
+
key(shape: TopoDS_Shape): number;
|
|
25
|
+
delete(): void;
|
|
26
|
+
}
|