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,619 @@
|
|
|
1
|
+
import { Face } from "../common/face.js";
|
|
2
|
+
import { Wire } from "../common/wire.js";
|
|
3
|
+
import { Point } from "../math/point.js";
|
|
4
|
+
import { Vector3d } from "../math/vector3d.js";
|
|
5
|
+
import { Matrix4 } from "../math/matrix4.js";
|
|
6
|
+
import { WireOps } from "./wire-ops.js";
|
|
7
|
+
import { FaceOps } from "./face-ops.js";
|
|
8
|
+
import { EdgeOps } from "./edge-ops.js";
|
|
9
|
+
import { ShapeOps } from "./shape-ops.js";
|
|
10
|
+
import { ShapeFactory } from "../common/shape-factory.js";
|
|
11
|
+
import { Explorer } from "./explorer.js";
|
|
12
|
+
import { BooleanOps } from "./boolean-ops.js";
|
|
13
|
+
import { Convert } from "./convert.js";
|
|
14
|
+
import { getOC } from "./init.js";
|
|
15
|
+
function planesEqual(a, b, distTol, _angTol) {
|
|
16
|
+
// Two unit normals are aligned (parallel or anti-parallel) when |dot| ≈ 1.
|
|
17
|
+
// 1e-6 is well below any practical face precision and well above OCC's
|
|
18
|
+
// numerical noise.
|
|
19
|
+
const dot = Math.abs(a.normal.dot(b.normal));
|
|
20
|
+
if (1 - dot > 1e-6) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const offset = a.origin.vectorTo(b.origin);
|
|
24
|
+
const d = Math.abs(offset.dot(b.normal));
|
|
25
|
+
return d <= distTol;
|
|
26
|
+
}
|
|
27
|
+
// Linearly samples points along a polyline wire by edge length. Used by
|
|
28
|
+
// the rib conformance to test which output solid encloses the spine
|
|
29
|
+
// (real rib) vs. only touches its boundary (phantom fragment).
|
|
30
|
+
function sampleSpinePoints(spineWire, count) {
|
|
31
|
+
const edges = spineWire.getEdges();
|
|
32
|
+
if (edges.length === 0) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const verts = [edges[0].getFirstVertex().toPoint()];
|
|
36
|
+
for (const e of edges) {
|
|
37
|
+
verts.push(e.getLastVertex().toPoint());
|
|
38
|
+
}
|
|
39
|
+
// Cumulative arc length along the polyline.
|
|
40
|
+
const cum = [0];
|
|
41
|
+
for (let i = 1; i < verts.length; i++) {
|
|
42
|
+
cum.push(cum[i - 1] + verts[i - 1].vectorTo(verts[i]).length());
|
|
43
|
+
}
|
|
44
|
+
const total = cum[cum.length - 1];
|
|
45
|
+
if (total <= 0) {
|
|
46
|
+
return [verts[0]];
|
|
47
|
+
}
|
|
48
|
+
const out = [];
|
|
49
|
+
// Skip pure endpoints — they often coincide with cut boundaries and
|
|
50
|
+
// classify as TopAbs_ON. Sample at fractions 1/(N+1) … N/(N+1).
|
|
51
|
+
for (let i = 1; i <= count; i++) {
|
|
52
|
+
const target = (total * i) / (count + 1);
|
|
53
|
+
let seg = 1;
|
|
54
|
+
while (seg < cum.length - 1 && cum[seg] < target) {
|
|
55
|
+
seg++;
|
|
56
|
+
}
|
|
57
|
+
const segLen = cum[seg] - cum[seg - 1];
|
|
58
|
+
const t = segLen > 0 ? (target - cum[seg - 1]) / segLen : 0;
|
|
59
|
+
const a = verts[seg - 1];
|
|
60
|
+
const b = verts[seg];
|
|
61
|
+
out.push(new Point(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t));
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
export class RibOps {
|
|
66
|
+
static makeRibProfile(spineWire, thickness, plane) {
|
|
67
|
+
const halfThickness = Math.abs(thickness) / 2;
|
|
68
|
+
const wire1 = RibOps.offsetWireOnPlane(spineWire, plane, halfThickness);
|
|
69
|
+
const wire2 = RibOps.offsetWireOnPlane(spineWire, plane, -halfThickness);
|
|
70
|
+
return RibOps.makeOpenFaceWithCaps(wire1, wire2);
|
|
71
|
+
}
|
|
72
|
+
static makeRibProfileParallel(spineWire, thickness, plane) {
|
|
73
|
+
const closedWire = RibOps.makeParallelRibClosedWire(spineWire, thickness, plane);
|
|
74
|
+
return Face.fromTopoDSFace(FaceOps.makeFaceFromWires([closedWire.getShape()]));
|
|
75
|
+
}
|
|
76
|
+
// Closed boundary wire for the parallel-mode rib profile: spine offset
|
|
77
|
+
// by ±halfThickness along plane.normal with two cap edges joining the
|
|
78
|
+
// ends. Used as a section wire for `makeTaperedRibPrism` and as the
|
|
79
|
+
// outer boundary of `makeRibProfileParallel`.
|
|
80
|
+
static makeParallelRibClosedWire(spineWire, thickness, plane) {
|
|
81
|
+
const halfThickness = Math.abs(thickness) / 2;
|
|
82
|
+
const offset1 = plane.normal.multiply(halfThickness);
|
|
83
|
+
const offset2 = plane.normal.multiply(-halfThickness);
|
|
84
|
+
const wire1 = ShapeOps.transform(spineWire, Matrix4.fromTranslationVector(offset1));
|
|
85
|
+
const wire2 = ShapeOps.transform(spineWire, Matrix4.fromTranslationVector(offset2));
|
|
86
|
+
return RibOps.makeClosedWireWithCaps(wire1, wire2);
|
|
87
|
+
}
|
|
88
|
+
// Lofts a tapered prism between two parallel-mode rib profile wires:
|
|
89
|
+
// the base on the spine plane (full thickness) and the tip translated
|
|
90
|
+
// along `direction × extrudeLength` with thickness shrunk by
|
|
91
|
+
// `extrudeLength × tan(draftAngleRad)`. The base profile face is the
|
|
92
|
+
// returned `firstFace`, exact by construction — no draft API is
|
|
93
|
+
// involved, so the spine-plane face stays at the original thickness
|
|
94
|
+
// with zero drift. Use this in place of an extrude + post-draft when
|
|
95
|
+
// exact start-face preservation matters.
|
|
96
|
+
//
|
|
97
|
+
// `draftAngleRad` follows the user-facing convention: positive tapers
|
|
98
|
+
// the tip inward, negative widens it. For very large positive angles
|
|
99
|
+
// the tip would invert past the spine; the tip half-thickness is
|
|
100
|
+
// clamped to a sub-precision positive value and conformance trims
|
|
101
|
+
// anything past the cavity.
|
|
102
|
+
static makeTaperedRibPrism(spineWire, thickness, plane, direction, extrudeLength, draftAngleRad) {
|
|
103
|
+
const oc = getOC();
|
|
104
|
+
const halfThickness = Math.abs(thickness) / 2;
|
|
105
|
+
const baseClosedWire = RibOps.makeParallelRibClosedWire(spineWire, thickness, plane);
|
|
106
|
+
const minHalf = oc.Precision.Confusion() * 100;
|
|
107
|
+
let tipHalfThickness = halfThickness - extrudeLength * Math.tan(draftAngleRad);
|
|
108
|
+
if (tipHalfThickness <= 0) {
|
|
109
|
+
tipHalfThickness = minHalf;
|
|
110
|
+
}
|
|
111
|
+
const tipFullThickness = tipHalfThickness * 2;
|
|
112
|
+
const tipBaseWire = RibOps.makeParallelRibClosedWire(spineWire, tipFullThickness, plane);
|
|
113
|
+
const translation = direction.multiply(extrudeLength);
|
|
114
|
+
const tipClosedWire = ShapeOps.transform(tipBaseWire, Matrix4.fromTranslationVector(translation));
|
|
115
|
+
const loft = new oc.BRepOffsetAPI_ThruSections(true, true, oc.Precision.Confusion());
|
|
116
|
+
loft.AddWire(baseClosedWire.getShape());
|
|
117
|
+
loft.AddWire(tipClosedWire.getShape());
|
|
118
|
+
const progress = new oc.Message_ProgressRange();
|
|
119
|
+
loft.Build(progress);
|
|
120
|
+
progress.delete();
|
|
121
|
+
if (!loft.IsDone()) {
|
|
122
|
+
loft.delete();
|
|
123
|
+
throw new Error("Tapered rib loft failed");
|
|
124
|
+
}
|
|
125
|
+
const solid = loft.Shape();
|
|
126
|
+
const firstFace = loft.FirstShape();
|
|
127
|
+
const lastFace = loft.LastShape();
|
|
128
|
+
loft.delete();
|
|
129
|
+
return {
|
|
130
|
+
solid: ShapeFactory.fromShape(solid),
|
|
131
|
+
firstFace: ShapeFactory.fromShape(firstFace),
|
|
132
|
+
lastFace: ShapeFactory.fromShape(lastFace),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
static computeSpinePerpendicularDirection(spineWire, plane) {
|
|
136
|
+
const start = spineWire.getFirstVertex().toPoint().toVector3d();
|
|
137
|
+
const end = spineWire.getLastVertex().toPoint().toVector3d();
|
|
138
|
+
const spineDir = end.subtract(start).normalize();
|
|
139
|
+
return plane.normal.cross(spineDir).normalize();
|
|
140
|
+
}
|
|
141
|
+
// Over-extends the spine endpoints along their tangents by 2× the scope bbox
|
|
142
|
+
// diagonal. The downstream BRepAlgoAPI_Cut against the scope is what actually
|
|
143
|
+
// carves the rib to the cavity; this just guarantees the pre-cut profile fully
|
|
144
|
+
// overlaps every cavity boundary regardless of curvature (drafted cones,
|
|
145
|
+
// fillets, etc.) — so the cut produces a clean blend on every face it touches.
|
|
146
|
+
static extendSpineWire(spineWire, scopeShapes, _plane) {
|
|
147
|
+
const edges = spineWire.getEdges();
|
|
148
|
+
if (edges.length === 0) {
|
|
149
|
+
return spineWire;
|
|
150
|
+
}
|
|
151
|
+
const firstVertex = spineWire.getFirstVertex().toPoint();
|
|
152
|
+
const lastVertex = spineWire.getLastVertex().toPoint();
|
|
153
|
+
const lastEdge = edges[edges.length - 1];
|
|
154
|
+
const endTangent = EdgeOps.getEdgeTangentAtEnd(lastEdge).normalize();
|
|
155
|
+
const firstEdge = edges[0];
|
|
156
|
+
const firstEdgeEnd = EdgeOps.getLastVertex(firstEdge).toPoint();
|
|
157
|
+
const startTangent = firstVertex.vectorTo(firstEdgeEnd).normalize();
|
|
158
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
159
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
160
|
+
for (const s of scopeShapes) {
|
|
161
|
+
const bb = ShapeOps.getBoundingBox(s);
|
|
162
|
+
if (bb.minX < minX) {
|
|
163
|
+
minX = bb.minX;
|
|
164
|
+
}
|
|
165
|
+
if (bb.minY < minY) {
|
|
166
|
+
minY = bb.minY;
|
|
167
|
+
}
|
|
168
|
+
if (bb.minZ < minZ) {
|
|
169
|
+
minZ = bb.minZ;
|
|
170
|
+
}
|
|
171
|
+
if (bb.maxX > maxX) {
|
|
172
|
+
maxX = bb.maxX;
|
|
173
|
+
}
|
|
174
|
+
if (bb.maxY > maxY) {
|
|
175
|
+
maxY = bb.maxY;
|
|
176
|
+
}
|
|
177
|
+
if (bb.maxZ > maxZ) {
|
|
178
|
+
maxZ = bb.maxZ;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const dx = maxX - minX, dy = maxY - minY, dz = maxZ - minZ;
|
|
182
|
+
const diag = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
183
|
+
if (!isFinite(diag) || diag <= 0) {
|
|
184
|
+
return spineWire;
|
|
185
|
+
}
|
|
186
|
+
const ext = 2 * diag;
|
|
187
|
+
const startExtPoint = firstVertex.add(startTangent.multiply(-ext));
|
|
188
|
+
const endExtPoint = lastVertex.add(endTangent.multiply(ext));
|
|
189
|
+
const newEdges = [
|
|
190
|
+
EdgeOps.makeLineEdge(startExtPoint, firstVertex),
|
|
191
|
+
...edges,
|
|
192
|
+
EdgeOps.makeLineEdge(lastVertex, endExtPoint),
|
|
193
|
+
];
|
|
194
|
+
return WireOps.makeWireFromEdges(newEdges);
|
|
195
|
+
}
|
|
196
|
+
static computeExtrudeDistanceAlongDirection(direction, origin, scopeShapes) {
|
|
197
|
+
let maxDist = 0;
|
|
198
|
+
for (const shape of scopeShapes) {
|
|
199
|
+
const bbox = ShapeOps.getBoundingBox(shape);
|
|
200
|
+
const corners = [
|
|
201
|
+
new Point(bbox.minX, bbox.minY, bbox.minZ),
|
|
202
|
+
new Point(bbox.maxX, bbox.minY, bbox.minZ),
|
|
203
|
+
new Point(bbox.minX, bbox.maxY, bbox.minZ),
|
|
204
|
+
new Point(bbox.maxX, bbox.maxY, bbox.minZ),
|
|
205
|
+
new Point(bbox.minX, bbox.minY, bbox.maxZ),
|
|
206
|
+
new Point(bbox.maxX, bbox.minY, bbox.maxZ),
|
|
207
|
+
new Point(bbox.minX, bbox.maxY, bbox.maxZ),
|
|
208
|
+
new Point(bbox.maxX, bbox.maxY, bbox.maxZ),
|
|
209
|
+
];
|
|
210
|
+
for (const corner of corners) {
|
|
211
|
+
const offset = origin.vectorTo(corner);
|
|
212
|
+
const dist = Math.abs(offset.dot(direction));
|
|
213
|
+
if (dist > maxDist) {
|
|
214
|
+
maxDist = dist;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return maxDist + 1e-3;
|
|
219
|
+
}
|
|
220
|
+
static computeExtrudeDistance(plane, scopeShapes) {
|
|
221
|
+
let maxDist = 0;
|
|
222
|
+
const origin = plane.origin;
|
|
223
|
+
const normal = plane.normal;
|
|
224
|
+
for (const shape of scopeShapes) {
|
|
225
|
+
const bbox = ShapeOps.getBoundingBox(shape);
|
|
226
|
+
const corners = [
|
|
227
|
+
new Point(bbox.minX, bbox.minY, bbox.minZ),
|
|
228
|
+
new Point(bbox.maxX, bbox.minY, bbox.minZ),
|
|
229
|
+
new Point(bbox.minX, bbox.maxY, bbox.minZ),
|
|
230
|
+
new Point(bbox.maxX, bbox.maxY, bbox.minZ),
|
|
231
|
+
new Point(bbox.minX, bbox.minY, bbox.maxZ),
|
|
232
|
+
new Point(bbox.maxX, bbox.minY, bbox.maxZ),
|
|
233
|
+
new Point(bbox.minX, bbox.maxY, bbox.maxZ),
|
|
234
|
+
new Point(bbox.maxX, bbox.maxY, bbox.maxZ),
|
|
235
|
+
];
|
|
236
|
+
for (const corner of corners) {
|
|
237
|
+
const offset = origin.vectorTo(corner);
|
|
238
|
+
const dist = Math.abs(offset.dot(normal));
|
|
239
|
+
if (dist > maxDist) {
|
|
240
|
+
maxDist = dist;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return maxDist + 1e-3;
|
|
245
|
+
}
|
|
246
|
+
// Conforms a (possibly over-extended) prismatic rib to the cavity defined by
|
|
247
|
+
// `scopeShapes`. The bbox slabs clip protrusion through openings; the scope
|
|
248
|
+
// cut blends the rib into the cavity walls (planar, fillet, drafted cone,
|
|
249
|
+
// etc.). The original (un-extended) spine wire is used to keep the connected
|
|
250
|
+
// component(s) the user actually drew and drop outer fragments left behind
|
|
251
|
+
// when the prism pokes past the scope's outer walls. Face classification
|
|
252
|
+
// uses the scope cut's lineage (Modified) on the rib's first/last/side
|
|
253
|
+
// faces, fixed up by IsSame against the post-slab faces.
|
|
254
|
+
static conformRibToScope(ribSolid, scopeShapes, originalSpineWire, prismFirstFace, prismLastFace, extrudeDirection) {
|
|
255
|
+
const oc = getOC();
|
|
256
|
+
// Phase 1: clip protrusion through openings using axis-aligned bbox slabs.
|
|
257
|
+
// Sequential cuts here — multi-tool cuts with very large slabs trigger BOP
|
|
258
|
+
// failures in some configurations.
|
|
259
|
+
let trimmed = ribSolid;
|
|
260
|
+
const slabs = RibOps.buildBoundingBoxSlabs(scopeShapes);
|
|
261
|
+
for (const slab of slabs) {
|
|
262
|
+
trimmed = BooleanOps.cutShapes(trimmed, slab);
|
|
263
|
+
}
|
|
264
|
+
// Phase 2: cut by scope compound, with lineage so the result faces can be
|
|
265
|
+
// mapped back to the prism's first / last / side faces.
|
|
266
|
+
const scopeCompound = ShapeOps.makeCompound(scopeShapes);
|
|
267
|
+
const stockList = new oc.TopTools_ListOfShape();
|
|
268
|
+
stockList.Append(trimmed.getShape());
|
|
269
|
+
const toolList = new oc.TopTools_ListOfShape();
|
|
270
|
+
toolList.Append(scopeCompound.getShape());
|
|
271
|
+
const progress = new oc.Message_ProgressRange();
|
|
272
|
+
const cutMaker = new oc.BRepAlgoAPI_Cut();
|
|
273
|
+
cutMaker.SetArguments(stockList);
|
|
274
|
+
cutMaker.SetTools(toolList);
|
|
275
|
+
cutMaker.SetNonDestructive(true);
|
|
276
|
+
cutMaker.SetRunParallel(true);
|
|
277
|
+
cutMaker.Build(progress);
|
|
278
|
+
if (!cutMaker.IsDone() || cutMaker.HasErrors()) {
|
|
279
|
+
cutMaker.delete();
|
|
280
|
+
stockList.delete();
|
|
281
|
+
toolList.delete();
|
|
282
|
+
progress.delete();
|
|
283
|
+
throw new Error("Rib conformance cut failed");
|
|
284
|
+
}
|
|
285
|
+
const cutResult = cutMaker.Shape();
|
|
286
|
+
const rawSolids = Explorer.findShapes(cutResult, Explorer.getOcShapeType("solid"));
|
|
287
|
+
const allSolids = rawSolids.map(s => ShapeFactory.fromShape(s));
|
|
288
|
+
// Pick the solid(s) whose interior contains the original spine. The
|
|
289
|
+
// rib was built by extruding a profile centred on the spine, so the
|
|
290
|
+
// spine lies inside the rib's volume by construction. Phantom
|
|
291
|
+
// fragments left by the boolean cut (thin shells tracing cavity
|
|
292
|
+
// walls, slivers at wall corners, outer over-extension leftovers)
|
|
293
|
+
// touch the spine on their boundary at most — never enclose it.
|
|
294
|
+
//
|
|
295
|
+
// We sample multiple points along the spine and use OCC's
|
|
296
|
+
// BRepClass3d_SolidClassifier to test interior containment. A solid
|
|
297
|
+
// is kept iff at least one sampled point classifies as TopAbs_IN
|
|
298
|
+
// (= strictly inside, not on the boundary). This naturally handles
|
|
299
|
+
// every threshold-prone case the previous distance + volume filter
|
|
300
|
+
// missed:
|
|
301
|
+
// - L-shaped wall-trace phantoms (boundary contact, no interior
|
|
302
|
+
// containment) → dropped.
|
|
303
|
+
// - sub-mm³ corner slivers → dropped.
|
|
304
|
+
// - rib that legitimately splits past a cone into two halves →
|
|
305
|
+
// each half contains its own portion of the spine, both kept.
|
|
306
|
+
const tolPoint = oc.Precision.Confusion() * 10;
|
|
307
|
+
// The spine itself lies ON the prism's start face, so testing
|
|
308
|
+
// raw spine points returns TopAbs_ON for the real rib too. Nudge
|
|
309
|
+
// each sample point a small distance along the extrude direction
|
|
310
|
+
// (= into the rib body, away from the start face) so an interior
|
|
311
|
+
// hit means the solid encloses the spine plus a thin ribbon
|
|
312
|
+
// forward of it — characteristic of the real rib but not of
|
|
313
|
+
// wall-trace phantoms or boundary slivers.
|
|
314
|
+
const dn = extrudeDirection.normalize();
|
|
315
|
+
const nudge = 1e-2;
|
|
316
|
+
const samplePoints = sampleSpinePoints(originalSpineWire, 7).map(pt => pt.add(dn.multiply(nudge)));
|
|
317
|
+
const keptSolids = [];
|
|
318
|
+
for (const solid of allSolids) {
|
|
319
|
+
let containsSpine = false;
|
|
320
|
+
for (const pt of samplePoints) {
|
|
321
|
+
const [gpPnt, dispose] = Convert.toGpPnt(pt);
|
|
322
|
+
const classifier = new oc.BRepClass3d_SolidClassifier(solid.getShape(), gpPnt, tolPoint);
|
|
323
|
+
const state = classifier.State();
|
|
324
|
+
classifier.delete();
|
|
325
|
+
dispose();
|
|
326
|
+
if (state === oc.TopAbs_State.TopAbs_IN) {
|
|
327
|
+
containsSpine = true;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (containsSpine) {
|
|
332
|
+
keptSolids.push(solid);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
let resultSolids = keptSolids;
|
|
336
|
+
if (resultSolids.length === 0 && allSolids.length > 0) {
|
|
337
|
+
// Fallback: if no solid contained any sampled spine point (rare —
|
|
338
|
+
// would mean every sample landed exactly on a face), keep the
|
|
339
|
+
// largest by volume. Defensive — shouldn't trip in practice.
|
|
340
|
+
let best = null;
|
|
341
|
+
let bestVol = -Infinity;
|
|
342
|
+
for (const s of allSolids) {
|
|
343
|
+
const vp = new oc.GProp_GProps();
|
|
344
|
+
oc.BRepGProp.VolumeProperties(s.getShape(), vp, false, false, false);
|
|
345
|
+
const v = vp.Mass();
|
|
346
|
+
vp.delete();
|
|
347
|
+
if (v > bestVol) {
|
|
348
|
+
bestVol = v;
|
|
349
|
+
best = s;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (best) {
|
|
353
|
+
resultSolids = [best];
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// Map post-slab faces back to first / last / side categories. Slab cuts
|
|
357
|
+
// don't track lineage, so use IsSame against the prism's first/last faces
|
|
358
|
+
// to recognize unmodified faces, plus surface plane comparison for faces
|
|
359
|
+
// that slabs split into pieces. Side faces are everything else in the
|
|
360
|
+
// post-slab shape.
|
|
361
|
+
const trimmedFaces = Explorer.findShapes(trimmed.getShape(), Explorer.getOcShapeType("face"));
|
|
362
|
+
const trimmedFirstFaces = [];
|
|
363
|
+
const trimmedLastFaces = [];
|
|
364
|
+
const trimmedSideFaces = [];
|
|
365
|
+
const firstPlane = RibOps.tryGetFacePlane(prismFirstFace.getShape());
|
|
366
|
+
const lastPlane = RibOps.tryGetFacePlane(prismLastFace.getShape());
|
|
367
|
+
const planeTol = oc.Precision.Confusion() * 10;
|
|
368
|
+
const angTol = oc.Precision.Angular();
|
|
369
|
+
for (const tf of trimmedFaces) {
|
|
370
|
+
if (tf.IsSame(prismFirstFace.getShape())) {
|
|
371
|
+
trimmedFirstFaces.push(tf);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (tf.IsSame(prismLastFace.getShape())) {
|
|
375
|
+
trimmedLastFaces.push(tf);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const tfPlane = RibOps.tryGetFacePlane(tf);
|
|
379
|
+
if (tfPlane && firstPlane && planesEqual(tfPlane, firstPlane, planeTol, angTol)) {
|
|
380
|
+
trimmedFirstFaces.push(tf);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (tfPlane && lastPlane && planesEqual(tfPlane, lastPlane, planeTol, angTol)) {
|
|
384
|
+
trimmedLastFaces.push(tf);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
trimmedSideFaces.push(tf);
|
|
388
|
+
}
|
|
389
|
+
// Build maps of (original + modified images) for first / last / side
|
|
390
|
+
// through the scope cut. Anything in the kept solids' faces that doesn't
|
|
391
|
+
// fall in one of these is a new face from the scope cut — i.e. the
|
|
392
|
+
// conformal blend with a scope cavity face.
|
|
393
|
+
const firstMap = new oc.TopTools_MapOfShape();
|
|
394
|
+
for (const f of trimmedFirstFaces) {
|
|
395
|
+
RibOps.collectFaceImages(cutMaker, f, firstMap);
|
|
396
|
+
}
|
|
397
|
+
const lastMap = new oc.TopTools_MapOfShape();
|
|
398
|
+
for (const f of trimmedLastFaces) {
|
|
399
|
+
RibOps.collectFaceImages(cutMaker, f, lastMap);
|
|
400
|
+
}
|
|
401
|
+
const sideMap = new oc.TopTools_MapOfShape();
|
|
402
|
+
for (const f of trimmedSideFaces) {
|
|
403
|
+
RibOps.collectFaceImages(cutMaker, f, sideMap);
|
|
404
|
+
}
|
|
405
|
+
const startFaces = [];
|
|
406
|
+
const endFaces = [];
|
|
407
|
+
const sideFaces = [];
|
|
408
|
+
const internalFaces = [];
|
|
409
|
+
const seen = new oc.TopTools_MapOfShape();
|
|
410
|
+
for (const solid of resultSolids) {
|
|
411
|
+
const faces = Explorer.findShapes(solid.getShape(), Explorer.getOcShapeType("face"));
|
|
412
|
+
for (const f of faces) {
|
|
413
|
+
if (!seen.Add(f)) {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
const wrapped = Face.fromTopoDSFace(Explorer.toFace(f));
|
|
417
|
+
if (firstMap.Contains(f)) {
|
|
418
|
+
startFaces.push(wrapped);
|
|
419
|
+
}
|
|
420
|
+
else if (lastMap.Contains(f)) {
|
|
421
|
+
endFaces.push(wrapped);
|
|
422
|
+
}
|
|
423
|
+
else if (sideMap.Contains(f)) {
|
|
424
|
+
sideFaces.push(wrapped);
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
internalFaces.push(wrapped);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
firstMap.delete();
|
|
432
|
+
lastMap.delete();
|
|
433
|
+
sideMap.delete();
|
|
434
|
+
seen.delete();
|
|
435
|
+
cutMaker.delete();
|
|
436
|
+
stockList.delete();
|
|
437
|
+
toolList.delete();
|
|
438
|
+
progress.delete();
|
|
439
|
+
// Final pass: ShapeUpgrade_UnifySameDomain merges adjacent coplanar faces
|
|
440
|
+
// and redundant edges left by the cut sequence (slab clips split flat
|
|
441
|
+
// walls into multiple sub-faces with extraneous seam edges). The lineage
|
|
442
|
+
// returned by cleanShapeWithLineage maps each pre-clean face to its
|
|
443
|
+
// post-clean image so the start / end / side / internal buckets remain
|
|
444
|
+
// valid for downstream face-selection queries.
|
|
445
|
+
const cleanedSolids = [];
|
|
446
|
+
const lineages = [];
|
|
447
|
+
for (const solid of resultSolids) {
|
|
448
|
+
const lineage = ShapeOps.cleanShapeWithLineage(solid);
|
|
449
|
+
cleanedSolids.push(lineage.shape);
|
|
450
|
+
lineages.push(lineage);
|
|
451
|
+
}
|
|
452
|
+
const remapBucket = (faces) => {
|
|
453
|
+
const out = [];
|
|
454
|
+
const seenOut = new oc.TopTools_MapOfShape();
|
|
455
|
+
for (const f of faces) {
|
|
456
|
+
let mapped = null;
|
|
457
|
+
for (const lineage of lineages) {
|
|
458
|
+
const r = lineage.remapFace(f);
|
|
459
|
+
if (r !== null) {
|
|
460
|
+
mapped = r;
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const kept = mapped ?? [f];
|
|
465
|
+
for (const m of kept) {
|
|
466
|
+
if (seenOut.Add(m.getShape())) {
|
|
467
|
+
out.push(m);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
seenOut.delete();
|
|
472
|
+
return out;
|
|
473
|
+
};
|
|
474
|
+
const finalStart = remapBucket(startFaces);
|
|
475
|
+
const finalEnd = remapBucket(endFaces);
|
|
476
|
+
const finalSide = remapBucket(sideFaces);
|
|
477
|
+
const finalInternal = remapBucket(internalFaces);
|
|
478
|
+
for (const lineage of lineages) {
|
|
479
|
+
lineage.dispose();
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
solids: cleanedSolids,
|
|
483
|
+
startFaces: finalStart,
|
|
484
|
+
endFaces: finalEnd,
|
|
485
|
+
sideFaces: finalSide,
|
|
486
|
+
internalFaces: finalInternal,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
static collectFaceImages(cutMaker, raw, map) {
|
|
490
|
+
map.Add(raw);
|
|
491
|
+
const modList = cutMaker.Modified(raw);
|
|
492
|
+
while (modList.Size() > 0) {
|
|
493
|
+
map.Add(modList.First());
|
|
494
|
+
modList.RemoveFirst();
|
|
495
|
+
}
|
|
496
|
+
modList.delete();
|
|
497
|
+
}
|
|
498
|
+
static tryGetFacePlane(face) {
|
|
499
|
+
const oc = getOC();
|
|
500
|
+
try {
|
|
501
|
+
const adaptor = new oc.BRepAdaptor_Surface(oc.TopoDS.Face(face), true);
|
|
502
|
+
if (adaptor.GetType() !== oc.GeomAbs_SurfaceType.GeomAbs_Plane) {
|
|
503
|
+
adaptor.delete();
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
const pln = adaptor.Plane();
|
|
507
|
+
const loc = pln.Location();
|
|
508
|
+
const ax = pln.Axis().Direction();
|
|
509
|
+
const result = {
|
|
510
|
+
origin: new Point(loc.X(), loc.Y(), loc.Z()),
|
|
511
|
+
normal: new Vector3d(ax.X(), ax.Y(), ax.Z()).normalize(),
|
|
512
|
+
};
|
|
513
|
+
adaptor.delete();
|
|
514
|
+
return result;
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
static buildBoundingBoxSlabs(scopeShapes) {
|
|
521
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
522
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
523
|
+
for (const s of scopeShapes) {
|
|
524
|
+
const bbox = ShapeOps.getBoundingBox(s);
|
|
525
|
+
minX = Math.min(minX, bbox.minX);
|
|
526
|
+
minY = Math.min(minY, bbox.minY);
|
|
527
|
+
minZ = Math.min(minZ, bbox.minZ);
|
|
528
|
+
maxX = Math.max(maxX, bbox.maxX);
|
|
529
|
+
maxY = Math.max(maxY, bbox.maxY);
|
|
530
|
+
maxZ = Math.max(maxZ, bbox.maxZ);
|
|
531
|
+
}
|
|
532
|
+
const BIG = 10000;
|
|
533
|
+
return [
|
|
534
|
+
RibOps.makeAxisAlignedSlab(minX - BIG, -BIG, -BIG, minX, BIG, BIG),
|
|
535
|
+
RibOps.makeAxisAlignedSlab(maxX, -BIG, -BIG, maxX + BIG, BIG, BIG),
|
|
536
|
+
RibOps.makeAxisAlignedSlab(-BIG, minY - BIG, -BIG, BIG, minY, BIG),
|
|
537
|
+
RibOps.makeAxisAlignedSlab(-BIG, maxY, -BIG, BIG, maxY + BIG, BIG),
|
|
538
|
+
RibOps.makeAxisAlignedSlab(-BIG, -BIG, minZ - BIG, BIG, BIG, minZ),
|
|
539
|
+
RibOps.makeAxisAlignedSlab(-BIG, -BIG, maxZ, BIG, BIG, maxZ + BIG),
|
|
540
|
+
];
|
|
541
|
+
}
|
|
542
|
+
static makeAxisAlignedSlab(x1, y1, z1, x2, y2, z2) {
|
|
543
|
+
const c1 = new Point(x1, y1, z1);
|
|
544
|
+
const c2 = new Point(x2, y1, z1);
|
|
545
|
+
const c3 = new Point(x2, y2, z1);
|
|
546
|
+
const c4 = new Point(x1, y2, z1);
|
|
547
|
+
const wire = WireOps.makeWireFromEdges([
|
|
548
|
+
EdgeOps.makeLineEdge(c1, c2),
|
|
549
|
+
EdgeOps.makeLineEdge(c2, c3),
|
|
550
|
+
EdgeOps.makeLineEdge(c3, c4),
|
|
551
|
+
EdgeOps.makeLineEdge(c4, c1),
|
|
552
|
+
]);
|
|
553
|
+
const face = Face.fromTopoDSFace(FaceOps.makeFaceFromWires([wire.getShape()]));
|
|
554
|
+
const oc = getOC();
|
|
555
|
+
const dz = z2 - z1;
|
|
556
|
+
const [vec, dispose] = Convert.toGpVec(new Vector3d(0, 0, dz));
|
|
557
|
+
const prism = new oc.BRepPrimAPI_MakePrism(face.getShape(), vec, false, true);
|
|
558
|
+
const result = prism.Shape();
|
|
559
|
+
prism.delete();
|
|
560
|
+
dispose();
|
|
561
|
+
return ShapeFactory.fromShape(result);
|
|
562
|
+
}
|
|
563
|
+
static offsetWireOnPlane(wire, plane, distance) {
|
|
564
|
+
const oc = getOC();
|
|
565
|
+
if (distance < 0) {
|
|
566
|
+
const reversed = WireOps.reverseWire(wire);
|
|
567
|
+
const result = RibOps.offsetWireOnPlane(reversed, plane, -distance);
|
|
568
|
+
return WireOps.reverseWire(result);
|
|
569
|
+
}
|
|
570
|
+
const [pln, disposePlane] = Convert.toGpPln(plane);
|
|
571
|
+
const faceMaker = new oc.BRepBuilderAPI_MakeFace(pln);
|
|
572
|
+
if (!faceMaker.IsDone()) {
|
|
573
|
+
faceMaker.delete();
|
|
574
|
+
disposePlane();
|
|
575
|
+
throw new Error("Failed to create reference face for rib offset");
|
|
576
|
+
}
|
|
577
|
+
const face = faceMaker.Face();
|
|
578
|
+
faceMaker.delete();
|
|
579
|
+
disposePlane();
|
|
580
|
+
const maker = new oc.BRepOffsetAPI_MakeOffset();
|
|
581
|
+
maker.Init(face, oc.GeomAbs_JoinType.GeomAbs_Arc, true);
|
|
582
|
+
maker.AddWire(wire.getShape());
|
|
583
|
+
maker.Perform(distance, 0);
|
|
584
|
+
if (!maker.IsDone()) {
|
|
585
|
+
maker.delete();
|
|
586
|
+
throw new Error("Failed to offset wire for rib profile");
|
|
587
|
+
}
|
|
588
|
+
const result = maker.Shape();
|
|
589
|
+
maker.delete();
|
|
590
|
+
if (Explorer.isWire(result)) {
|
|
591
|
+
return Wire.fromTopoDSWire(oc.TopoDS.Wire(result));
|
|
592
|
+
}
|
|
593
|
+
const wires = Explorer.findShapes(result, oc.TopAbs_ShapeEnum.TopAbs_WIRE);
|
|
594
|
+
if (wires.length === 0) {
|
|
595
|
+
throw new Error("Rib offset produced no usable wire");
|
|
596
|
+
}
|
|
597
|
+
return Wire.fromTopoDSWire(oc.TopoDS.Wire(wires[0]));
|
|
598
|
+
}
|
|
599
|
+
static makeOpenFaceWithCaps(wire1, wire2) {
|
|
600
|
+
const closedWire = RibOps.makeClosedWireWithCaps(wire1, wire2);
|
|
601
|
+
return Face.fromTopoDSFace(FaceOps.makeFaceFromWires([closedWire.getShape()]));
|
|
602
|
+
}
|
|
603
|
+
static makeClosedWireWithCaps(wire1, wire2) {
|
|
604
|
+
const wire1End = wire1.getLastVertex().toPoint();
|
|
605
|
+
const wire2Start = wire2.getFirstVertex().toPoint();
|
|
606
|
+
const wire2End = wire2.getLastVertex().toPoint();
|
|
607
|
+
const wire1Start = wire1.getFirstVertex().toPoint();
|
|
608
|
+
const cap1 = EdgeOps.makeLineEdge(wire1End, wire2End);
|
|
609
|
+
const cap2 = EdgeOps.makeLineEdge(wire2Start, wire1Start);
|
|
610
|
+
const reversedWire2 = WireOps.reverseWire(wire2);
|
|
611
|
+
const allEdges = [
|
|
612
|
+
...wire1.getEdges(),
|
|
613
|
+
cap1,
|
|
614
|
+
...reversedWire2.getEdges(),
|
|
615
|
+
cap2,
|
|
616
|
+
];
|
|
617
|
+
return WireOps.makeWireFromEdges(allEdges);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Shape } from "../common/shape.js";
|
|
2
2
|
import { Face } from "../common/face.js";
|
|
3
|
+
import { ShellJoinType } from "../core/interfaces.js";
|
|
3
4
|
export declare class ShellOps {
|
|
4
|
-
static makeThickSolid(solid: Shape, faces: Face[], thickness: number): Shape;
|
|
5
|
+
static makeThickSolid(solid: Shape, faces: Face[], thickness: number, joinType?: ShellJoinType): Shape;
|
|
5
6
|
}
|
package/lib/dist/oc/shell-ops.js
CHANGED
|
@@ -3,15 +3,18 @@ import { ShapeOps } from "./shape-ops.js";
|
|
|
3
3
|
import { ShapeFactory } from "../common/shape-factory.js";
|
|
4
4
|
import { ColorTransfer } from "./color-transfer.js";
|
|
5
5
|
export class ShellOps {
|
|
6
|
-
static makeThickSolid(solid, faces, thickness) {
|
|
6
|
+
static makeThickSolid(solid, faces, thickness, joinType = 'arc') {
|
|
7
7
|
const oc = getOC();
|
|
8
8
|
const listOfFaces = new oc.TopTools_ListOfShape();
|
|
9
9
|
for (const f of faces) {
|
|
10
10
|
listOfFaces.Append(f.getShape());
|
|
11
11
|
}
|
|
12
|
+
const ocJoinType = joinType === 'intersection' ? oc.GeomAbs_JoinType.GeomAbs_Intersection
|
|
13
|
+
: joinType === 'tangent' ? oc.GeomAbs_JoinType.GeomAbs_Tangent
|
|
14
|
+
: oc.GeomAbs_JoinType.GeomAbs_Arc;
|
|
12
15
|
const maker = new oc.BRepOffsetAPI_MakeThickSolid();
|
|
13
16
|
const progress = new oc.Message_ProgressRange();
|
|
14
|
-
maker.MakeThickSolidByJoin(oc.TopoDS.Solid(solid.getShape()), listOfFaces, thickness, oc.Precision.Confusion(), oc.BRepOffset_Mode.BRepOffset_Skin, false, false,
|
|
17
|
+
maker.MakeThickSolidByJoin(oc.TopoDS.Solid(solid.getShape()), listOfFaces, thickness, oc.Precision.Confusion(), oc.BRepOffset_Mode.BRepOffset_Skin, false, false, ocJoinType, false, progress);
|
|
15
18
|
progress.delete();
|
|
16
19
|
if (!maker.IsDone()) {
|
|
17
20
|
maker.delete();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TopoDS_Shape, TopTools_IndexedDataMapOfShapeListOfShape, TopTools_MapOfShape } from "occjs-wrapper";
|
|
2
|
+
export declare class TopologyIndex {
|
|
3
|
+
static buildEdgeToFaces(root: TopoDS_Shape): TopTools_IndexedDataMapOfShapeListOfShape;
|
|
4
|
+
static buildShapeSet(shapes: TopoDS_Shape[]): TopTools_MapOfShape;
|
|
5
|
+
static seekShapes(index: TopTools_IndexedDataMapOfShapeListOfShape, key: TopoDS_Shape): TopoDS_Shape[];
|
|
6
|
+
}
|