fluidcad 0.0.28 → 0.0.30
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/lib/dist/common/profiler.d.ts +12 -0
- package/lib/dist/common/profiler.js +35 -0
- package/lib/dist/common/scene-object.d.ts +3 -0
- package/lib/dist/common/scene-object.js +3 -0
- package/lib/dist/common/shape-history-tracker.d.ts +9 -1
- package/lib/dist/common/shape-history-tracker.js +37 -23
- package/lib/dist/core/2d/aline.d.ts +13 -13
- package/lib/dist/core/2d/aline.js +20 -11
- package/lib/dist/core/2d/arc.d.ts +6 -6
- package/lib/dist/core/2d/arc.js +19 -15
- package/lib/dist/core/2d/back.d.ts +12 -0
- package/lib/dist/core/2d/back.js +11 -0
- package/lib/dist/core/2d/circle.d.ts +2 -2
- package/lib/dist/core/2d/circle.js +14 -10
- package/lib/dist/core/2d/ellipse.d.ts +35 -0
- package/lib/dist/core/2d/ellipse.js +65 -0
- package/lib/dist/core/2d/hline.d.ts +20 -13
- package/lib/dist/core/2d/hline.js +33 -15
- package/lib/dist/core/2d/index.d.ts +2 -0
- package/lib/dist/core/2d/index.js +2 -0
- package/lib/dist/core/2d/intersect.d.ts +2 -2
- package/lib/dist/core/2d/intersect.js +7 -3
- package/lib/dist/core/2d/line.d.ts +2 -2
- package/lib/dist/core/2d/line.js +14 -10
- package/lib/dist/core/2d/offset.d.ts +4 -4
- package/lib/dist/core/2d/offset.js +9 -5
- package/lib/dist/core/2d/polygon.d.ts +4 -4
- package/lib/dist/core/2d/polygon.js +24 -20
- package/lib/dist/core/2d/project.d.ts +2 -2
- package/lib/dist/core/2d/project.js +7 -3
- package/lib/dist/core/2d/rect.d.ts +2 -2
- package/lib/dist/core/2d/rect.js +22 -21
- package/lib/dist/core/2d/slot.d.ts +6 -6
- package/lib/dist/core/2d/slot.js +29 -32
- package/lib/dist/core/2d/vline.d.ts +20 -13
- package/lib/dist/core/2d/vline.js +29 -15
- package/lib/dist/core/interfaces.d.ts +62 -0
- package/lib/dist/core/mirror.d.ts +7 -7
- package/lib/dist/core/mirror.js +17 -11
- package/lib/dist/core/part.d.ts +3 -1
- package/lib/dist/core/part.js +1 -1
- package/lib/dist/core/rotate.d.ts +5 -5
- package/lib/dist/core/rotate.js +4 -1
- package/lib/dist/core/sketch.d.ts +3 -1
- package/lib/dist/core/sketch.js +1 -1
- package/lib/dist/core/translate.d.ts +9 -9
- package/lib/dist/features/2d/aline.d.ts +8 -5
- package/lib/dist/features/2d/aline.js +70 -18
- package/lib/dist/features/2d/back.d.ts +14 -0
- package/lib/dist/features/2d/back.js +35 -0
- package/lib/dist/features/2d/ellipse.d.ts +23 -0
- package/lib/dist/features/2d/ellipse.js +75 -0
- package/lib/dist/features/2d/hline.d.ts +9 -4
- package/lib/dist/features/2d/hline.js +65 -14
- package/lib/dist/features/2d/offset.d.ts +3 -0
- package/lib/dist/features/2d/offset.js +27 -3
- package/lib/dist/features/2d/sketch.d.ts +1 -0
- package/lib/dist/features/2d/sketch.js +15 -0
- package/lib/dist/features/2d/vline.d.ts +9 -4
- package/lib/dist/features/2d/vline.js +67 -15
- package/lib/dist/features/common.js +2 -1
- package/lib/dist/features/extrude-base.d.ts +19 -1
- package/lib/dist/features/extrude-base.js +75 -12
- package/lib/dist/features/extrude-two-distances.js +32 -27
- package/lib/dist/features/extrude.d.ts +39 -0
- package/lib/dist/features/extrude.js +196 -156
- package/lib/dist/features/fuse.js +2 -1
- package/lib/dist/features/lazy-scene-object.d.ts +1 -0
- package/lib/dist/features/lazy-scene-object.js +3 -0
- package/lib/dist/features/lazy-vertex.d.ts +1 -0
- package/lib/dist/features/lazy-vertex.js +3 -0
- package/lib/dist/features/loft.js +11 -8
- package/lib/dist/features/mirror-shape.d.ts +2 -0
- package/lib/dist/features/mirror-shape.js +16 -0
- package/lib/dist/features/mirror-shape2d.d.ts +2 -0
- package/lib/dist/features/mirror-shape2d.js +22 -1
- package/lib/dist/features/revolve.d.ts +31 -0
- package/lib/dist/features/revolve.js +178 -95
- package/lib/dist/features/rotate.d.ts +2 -0
- package/lib/dist/features/rotate.js +16 -0
- package/lib/dist/features/rotate2d.d.ts +2 -0
- package/lib/dist/features/rotate2d.js +16 -0
- package/lib/dist/features/select.js +2 -1
- package/lib/dist/features/simple-extruder.d.ts +3 -1
- package/lib/dist/features/simple-extruder.js +13 -9
- package/lib/dist/features/subtract.d.ts +2 -2
- package/lib/dist/features/subtract.js +3 -3
- package/lib/dist/features/sweep.d.ts +14 -0
- package/lib/dist/features/sweep.js +93 -80
- package/lib/dist/features/translate.d.ts +2 -0
- package/lib/dist/features/translate.js +23 -2
- package/lib/dist/filters/edge/edge-filter.d.ts +6 -0
- package/lib/dist/filters/edge/edge-filter.js +11 -0
- package/lib/dist/filters/face/face-filter.d.ts +6 -0
- package/lib/dist/filters/face/face-filter.js +11 -0
- package/lib/dist/filters/filter-base.d.ts +7 -1
- package/lib/dist/filters/filter-base.js +8 -0
- package/lib/dist/filters/filter-builder-base.js +11 -0
- package/lib/dist/filters/from-object.d.ts +14 -0
- package/lib/dist/filters/from-object.js +40 -0
- package/lib/dist/helpers/scene-helpers.d.ts +2 -0
- package/lib/dist/helpers/scene-helpers.js +68 -48
- package/lib/dist/oc/color-transfer.js +6 -0
- package/lib/dist/oc/edge-ops.d.ts +1 -0
- package/lib/dist/oc/edge-ops.js +17 -0
- package/lib/dist/oc/extrude-ops.d.ts +18 -1
- package/lib/dist/oc/extrude-ops.js +34 -1
- package/lib/dist/oc/geometry.d.ts +1 -0
- package/lib/dist/oc/geometry.js +27 -0
- package/lib/dist/oc/mesh.js +11 -9
- package/lib/dist/oc/ray-intersect.d.ts +16 -0
- package/lib/dist/oc/ray-intersect.js +91 -0
- package/lib/dist/oc/thin-face-maker.d.ts +0 -1
- package/lib/dist/oc/thin-face-maker.js +2 -20
- package/lib/dist/rendering/render.d.ts +2 -1
- package/lib/dist/rendering/render.js +72 -33
- package/lib/dist/rendering/scene.d.ts +4 -0
- package/lib/dist/tests/features/2d/back.test.d.ts +1 -0
- package/lib/dist/tests/features/2d/back.test.js +60 -0
- package/lib/dist/tests/features/2d/circle.test.js +1 -1
- package/lib/dist/tests/features/2d/constrained.test.js +4 -4
- package/lib/dist/tests/features/2d/ellipse.test.d.ts +1 -0
- package/lib/dist/tests/features/2d/ellipse.test.js +100 -0
- package/lib/dist/tests/features/2d/line.test.js +89 -3
- package/lib/dist/tests/features/2d/offset.test.js +1 -1
- package/lib/dist/tests/features/2d/polygon.test.js +2 -2
- package/lib/dist/tests/features/2d/rect.test.js +1 -1
- package/lib/dist/tests/features/2d/slot-from-edge.test.js +1 -1
- package/lib/dist/tests/features/2d/slot.test.js +1 -1
- package/lib/dist/tests/features/mirror.test.js +58 -0
- package/lib/dist/tests/features/mirror2d.test.js +63 -0
- package/lib/dist/tests/features/rotate.test.js +62 -0
- package/lib/dist/tests/features/rotate2d.test.js +47 -0
- package/lib/dist/tests/features/thin-revolve.test.js +37 -1
- package/lib/dist/tests/features/translate.test.js +63 -0
- package/lib/dist/tests/perf/record-fusion-history.bench.test.d.ts +1 -0
- package/lib/dist/tests/perf/record-fusion-history.bench.test.js +77 -0
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/server/dist/index.js +77 -45
- package/server/dist/ws-protocol.d.ts +11 -0
- package/ui/dist/assets/{index-BrW_x4uc.js → index-6Ep4GPxf.js} +131 -77
- package/ui/dist/assets/index-DRKfe6N9.css +2 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-gPoNOiIs.css +0 -2
|
@@ -8,8 +8,8 @@ import { Edge } from "../common/edge.js";
|
|
|
8
8
|
import { getOC } from "../oc/init.js";
|
|
9
9
|
import { ColorTransfer } from "../oc/color-transfer.js";
|
|
10
10
|
export function fuseWithSceneObjects(sceneObjects, extrusions, opts) {
|
|
11
|
+
const p = opts?.profiler;
|
|
11
12
|
const modified = [];
|
|
12
|
-
const tCollect = performance.now();
|
|
13
13
|
const objShapeMap = new Map();
|
|
14
14
|
for (const obj of sceneObjects) {
|
|
15
15
|
const shapes = obj.getShapes({}, 'solid');
|
|
@@ -18,15 +18,15 @@ export function fuseWithSceneObjects(sceneObjects, extrusions, opts) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
let sceneShapes = Array.from(objShapeMap.keys());
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const fuseRun = () => BooleanOps.fuseStockAndTools(sceneShapes, extrusions, opts);
|
|
22
|
+
const { result, newShapes, modifiedShapes, maker, dispose } = p
|
|
23
|
+
? p.record('Boolean fuse', fuseRun)
|
|
24
|
+
: fuseRun();
|
|
25
25
|
if (newShapes.length === 0 && modifiedShapes.length === 0) {
|
|
26
|
-
console.log("No fusions were made.");
|
|
27
26
|
dispose();
|
|
28
27
|
if (opts?.recordHistoryFor) {
|
|
29
|
-
recordShapesAsAdditions(opts.recordHistoryFor, extrusions);
|
|
28
|
+
const run = () => recordShapesAsAdditions(opts.recordHistoryFor, extrusions);
|
|
29
|
+
p ? p.record('Record fusion history', run) : run();
|
|
30
30
|
}
|
|
31
31
|
return {
|
|
32
32
|
newShapes: extrusions,
|
|
@@ -44,12 +44,18 @@ export function fuseWithSceneObjects(sceneObjects, extrusions, opts) {
|
|
|
44
44
|
const shapesToAdd = result.filter(s => !unconsumed.some(u => u.getShape().IsPartner(s.getShape())));
|
|
45
45
|
let toolHistory;
|
|
46
46
|
if (opts?.recordHistoryFor) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
const recordHistory = () => {
|
|
48
|
+
recordFusionHistory(opts.recordHistoryFor, sceneShapes, objShapeMap, shapesToAdd, maker, p);
|
|
49
|
+
// Separately track tool-side (extrusion) lineage so callers can remap
|
|
50
|
+
// pre-fusion categorizations (start/end/side/…) onto the post-fusion
|
|
51
|
+
// faces. We don't store these as modifications on any scene object —
|
|
52
|
+
// from the user's POV they are additions on the caller already.
|
|
53
|
+
// Tool-side history is only consumed by `remapClassifiedFaces`, which
|
|
54
|
+
// touches modifiedFaces only — skip the added* output traversal.
|
|
55
|
+
const collectTools = () => ShapeHistoryTracker.collect(maker, extrusions, { skipAdded: true });
|
|
56
|
+
toolHistory = p ? p.record('Collect tool history', collectTools) : collectTools();
|
|
57
|
+
};
|
|
58
|
+
p ? p.record('Record fusion history', recordHistory) : recordHistory();
|
|
53
59
|
}
|
|
54
60
|
dispose();
|
|
55
61
|
return { newShapes: shapesToAdd, modifiedShapes: modified, toolHistory };
|
|
@@ -80,51 +86,62 @@ function recordShapesAsAdditions(caller, shapes) {
|
|
|
80
86
|
* of a scene-shape modification. This captures both extrusion-derived faces
|
|
81
87
|
* (which appear in the result via tool-side Modified()) and truly new faces.
|
|
82
88
|
*/
|
|
83
|
-
function recordFusionHistory(caller, sceneShapes, owners, newShapes, maker) {
|
|
89
|
+
function recordFusionHistory(caller, sceneShapes, owners, newShapes, maker, p) {
|
|
84
90
|
const oc = getOC();
|
|
85
91
|
const FACE = oc.TopAbs_ShapeEnum.TopAbs_FACE;
|
|
86
92
|
const EDGE = oc.TopAbs_ShapeEnum.TopAbs_EDGE;
|
|
87
93
|
const claimedFaces = new oc.TopTools_MapOfShape();
|
|
88
94
|
const claimedEdges = new oc.TopTools_MapOfShape();
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const history = ShapeHistoryTracker.collect(maker, [sceneShape]);
|
|
95
|
-
for (const record of history.modifiedFaces) {
|
|
96
|
-
owner.recordModifiedFaces(record.sources, record.results, caller);
|
|
97
|
-
for (const r of record.results) {
|
|
98
|
-
claimedFaces.Add(r.getShape());
|
|
95
|
+
const collectScene = () => {
|
|
96
|
+
for (const sceneShape of sceneShapes) {
|
|
97
|
+
const owner = owners.get(sceneShape);
|
|
98
|
+
if (!owner) {
|
|
99
|
+
continue;
|
|
99
100
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
// recordFusionHistory aggregates additions across the full result via
|
|
102
|
+
// `claimedFaces`/`claimedEdges` below — each per-shape collect doesn't
|
|
103
|
+
// need to compute its own added* sets.
|
|
104
|
+
const history = ShapeHistoryTracker.collect(maker, [sceneShape], { skipAdded: true });
|
|
105
|
+
for (const record of history.modifiedFaces) {
|
|
106
|
+
owner.recordModifiedFaces(record.sources, record.results, caller);
|
|
107
|
+
for (const r of record.results) {
|
|
108
|
+
claimedFaces.Add(r.getShape());
|
|
109
|
+
}
|
|
105
110
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
caller.recordAddedFace(Face.fromTopoDSFace(Explorer.toFace(raw)), caller);
|
|
111
|
+
for (const record of history.modifiedEdges) {
|
|
112
|
+
owner.recordModifiedEdges(record.sources, record.results, caller);
|
|
113
|
+
for (const r of record.results) {
|
|
114
|
+
claimedEdges.Add(r.getShape());
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const face of history.removedFaces) {
|
|
118
|
+
owner.recordRemovedFace(face, caller);
|
|
119
|
+
}
|
|
120
|
+
for (const edge of history.removedEdges) {
|
|
121
|
+
owner.recordRemovedEdge(edge, caller);
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
};
|
|
125
|
+
p ? p.record('Collect scene history', collectScene) : collectScene();
|
|
126
|
+
const recordAdditions = () => {
|
|
127
|
+
for (const newShape of newShapes) {
|
|
128
|
+
for (const raw of Explorer.findShapes(newShape.getShape(), FACE)) {
|
|
129
|
+
if (!claimedFaces.Contains(raw)) {
|
|
130
|
+
caller.recordAddedFace(Face.fromTopoDSFace(Explorer.toFace(raw)), caller);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const raw of Explorer.findShapes(newShape.getShape(), EDGE)) {
|
|
134
|
+
if (!claimedEdges.Contains(raw)) {
|
|
135
|
+
caller.recordAddedEdge(Edge.fromTopoDSEdge(Explorer.toEdge(raw)), caller);
|
|
136
|
+
}
|
|
123
137
|
}
|
|
124
138
|
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
ColorTransfer.
|
|
139
|
+
};
|
|
140
|
+
p ? p.record('Record additions', recordAdditions) : recordAdditions();
|
|
141
|
+
const colorThrough = () => ColorTransfer.applyThroughMaker(sceneShapes, newShapes, maker);
|
|
142
|
+
p ? p.record('Color through maker', colorThrough) : colorThrough();
|
|
143
|
+
const colorBleed = () => ColorTransfer.applyBleeding(sceneShapes, newShapes, maker);
|
|
144
|
+
p ? p.record('Color bleeding', colorBleed) : colorBleed();
|
|
128
145
|
claimedFaces.delete();
|
|
129
146
|
claimedEdges.delete();
|
|
130
147
|
}
|
|
@@ -229,7 +246,10 @@ function recordCutHistory(caller, stock, owners, cleanedShapes, maker, cleanups)
|
|
|
229
246
|
if (!owner) {
|
|
230
247
|
continue;
|
|
231
248
|
}
|
|
232
|
-
|
|
249
|
+
// Same as the fuse path: additions are aggregated below across the full
|
|
250
|
+
// cleaned result via `claimedFaces`/`claimedEdges`, so each per-shape
|
|
251
|
+
// collect can skip its own added* output traversal.
|
|
252
|
+
const history = ShapeHistoryTracker.collect(maker, [stockShape], { skipAdded: true });
|
|
233
253
|
for (const record of history.modifiedFaces) {
|
|
234
254
|
const postCleanResults = remapPreCleanFaces(record.results);
|
|
235
255
|
if (postCleanResults.length === 0) {
|
|
@@ -58,6 +58,12 @@ export class ColorTransfer {
|
|
|
58
58
|
* Call AFTER `applyThroughMaker` so the colored seeds are in place.
|
|
59
59
|
*/
|
|
60
60
|
static applyBleeding(sceneSources, results, maker) {
|
|
61
|
+
// No colors anywhere on the source side means the bleed loop will iterate
|
|
62
|
+
// every result face only to find nothing to spread. Short-circuit before
|
|
63
|
+
// building maps or running the O(N²·E²) adjacency scan.
|
|
64
|
+
if (!sceneSources.some(s => s.hasColors())) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
61
67
|
const oc = getOC();
|
|
62
68
|
const FACE = oc.TopAbs_ShapeEnum.TopAbs_FACE;
|
|
63
69
|
const EDGE = oc.TopAbs_ShapeEnum.TopAbs_EDGE;
|
|
@@ -9,6 +9,7 @@ export declare class EdgeOps {
|
|
|
9
9
|
static getLastVertex(edge: Edge): Vertex;
|
|
10
10
|
static edgeToAxis(edge: Edge): Axis;
|
|
11
11
|
static axisToEdge(axis: Axis): Edge;
|
|
12
|
+
static makeLineEdge(p1: Point, p2: Point): Edge;
|
|
12
13
|
static getVertexPoint(vertex: Vertex): Point;
|
|
13
14
|
static getEdgeMidPoint(edge: Edge): Point;
|
|
14
15
|
static reverseEdge(edge: Edge): Edge;
|
package/lib/dist/oc/edge-ops.js
CHANGED
|
@@ -19,6 +19,23 @@ export class EdgeOps {
|
|
|
19
19
|
static axisToEdge(axis) {
|
|
20
20
|
return Edge.fromTopoDSEdge(EdgeOps.axisToEdgeRaw(axis));
|
|
21
21
|
}
|
|
22
|
+
static makeLineEdge(p1, p2) {
|
|
23
|
+
const oc = getOC();
|
|
24
|
+
const [gp1, dispose1] = Convert.toGpPnt(p1);
|
|
25
|
+
const [gp2, dispose2] = Convert.toGpPnt(p2);
|
|
26
|
+
const edgeMaker = new oc.BRepBuilderAPI_MakeEdge(gp1, gp2);
|
|
27
|
+
if (!edgeMaker.IsDone()) {
|
|
28
|
+
edgeMaker.delete();
|
|
29
|
+
dispose1();
|
|
30
|
+
dispose2();
|
|
31
|
+
throw new Error("Failed to create line edge");
|
|
32
|
+
}
|
|
33
|
+
const edge = edgeMaker.Edge();
|
|
34
|
+
edgeMaker.delete();
|
|
35
|
+
dispose1();
|
|
36
|
+
dispose2();
|
|
37
|
+
return Edge.fromTopoDSEdge(edge);
|
|
38
|
+
}
|
|
22
39
|
static getVertexPoint(vertex) {
|
|
23
40
|
return EdgeOps.getVertexPointRaw(vertex.getShape());
|
|
24
41
|
}
|
|
@@ -3,6 +3,23 @@ import { Vector3d } from "../math/vector3d.js";
|
|
|
3
3
|
import { Plane } from "../math/plane.js";
|
|
4
4
|
import { Axis } from "../math/axis.js";
|
|
5
5
|
import { Shape } from "../common/shape.js";
|
|
6
|
+
import { Face } from "../common/face.js";
|
|
7
|
+
/**
|
|
8
|
+
* History-based result of a `BRepPrimAPI_MakeRevol` operation. `firstFace` /
|
|
9
|
+
* `lastFace` come from the maker's `FirstShape` / `LastShape` (null on full
|
|
10
|
+
* revolution where the source face is absorbed). `edgeFaces` maps each edge
|
|
11
|
+
* of the input face to the swept face it generated, so callers can classify
|
|
12
|
+
* by input-edge category instead of geometric heuristics.
|
|
13
|
+
*/
|
|
14
|
+
export interface MakeRevolResult {
|
|
15
|
+
solid: Shape;
|
|
16
|
+
firstFace: Face | null;
|
|
17
|
+
lastFace: Face | null;
|
|
18
|
+
edgeFaces: {
|
|
19
|
+
edge: TopoDS_Shape;
|
|
20
|
+
face: Face | null;
|
|
21
|
+
}[];
|
|
22
|
+
}
|
|
6
23
|
export declare class ExtrudeOps {
|
|
7
24
|
static makePrism(shape: Shape, direction: Vector3d, distance: number): Shape;
|
|
8
25
|
static makePrismFromVec(shape: Shape, vec: Vector3d): {
|
|
@@ -12,7 +29,7 @@ export declare class ExtrudeOps {
|
|
|
12
29
|
};
|
|
13
30
|
static makePrismInfinite(shape: Shape, direction: Vector3d): Shape;
|
|
14
31
|
static makePrismSymmetric(shape: Shape, direction: Vector3d): Shape;
|
|
15
|
-
static makeRevol(shape: Shape, axis: Axis, angle: number):
|
|
32
|
+
static makeRevol(shape: Shape, axis: Axis, angle: number): MakeRevolResult;
|
|
16
33
|
static applyDraftOnSideFaces(solid: Shape, firstFace: Shape, lastFace: Shape, plane: Plane, angle: number): {
|
|
17
34
|
solid: Shape;
|
|
18
35
|
firstFace: Shape;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getOC } from "./init.js";
|
|
2
2
|
import { Convert } from "./convert.js";
|
|
3
3
|
import { Explorer } from "./explorer.js";
|
|
4
|
+
import { Face } from "../common/face.js";
|
|
4
5
|
import { ShapeFactory } from "../common/shape-factory.js";
|
|
5
6
|
import { ShapeOps } from "./shape-ops.js";
|
|
6
7
|
export class ExtrudeOps {
|
|
@@ -73,6 +74,20 @@ export class ExtrudeOps {
|
|
|
73
74
|
throw new Error("Revolution failed");
|
|
74
75
|
}
|
|
75
76
|
const rawResult = revol.Shape();
|
|
77
|
+
// Capture history while the maker is alive. For full revolution the
|
|
78
|
+
// source face is absorbed (IsDeleted), so first/last are meaningless.
|
|
79
|
+
const FACE = oc.TopAbs_ShapeEnum.TopAbs_FACE;
|
|
80
|
+
const EDGE = oc.TopAbs_ShapeEnum.TopAbs_EDGE;
|
|
81
|
+
const sourceDeleted = revol.IsDeleted(shape.getShape());
|
|
82
|
+
const firstShapeRaw = sourceDeleted ? null : revol.FirstShape();
|
|
83
|
+
const lastShapeRaw = sourceDeleted ? null : revol.LastShape();
|
|
84
|
+
const inputEdgesRaw = Explorer.findShapes(shape.getShape(), EDGE);
|
|
85
|
+
const edgeFacesRaw = [];
|
|
86
|
+
for (const edge of inputEdgesRaw) {
|
|
87
|
+
const generated = ShapeOps.shapeListToArray(revol.Generated(edge))
|
|
88
|
+
.filter(s => s.ShapeType() === FACE);
|
|
89
|
+
edgeFacesRaw.push({ edge, face: generated[0] ?? null });
|
|
90
|
+
}
|
|
76
91
|
revol.delete();
|
|
77
92
|
disposeAx1();
|
|
78
93
|
// A profile face whose normal points "backwards" relative to the axis
|
|
@@ -86,7 +101,25 @@ export class ExtrudeOps {
|
|
|
86
101
|
oriented = solid;
|
|
87
102
|
}
|
|
88
103
|
const clean = ShapeOps.cleanShapeRaw(oriented);
|
|
89
|
-
|
|
104
|
+
const cleanedFaceRaws = Explorer.findShapes(clean, FACE);
|
|
105
|
+
const wrappedFaces = cleanedFaceRaws.map(f => Face.fromTopoDSFace(Explorer.toFace(f)));
|
|
106
|
+
const findFace = (raw) => {
|
|
107
|
+
if (!raw) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
for (let i = 0; i < cleanedFaceRaws.length; i++) {
|
|
111
|
+
if (cleanedFaceRaws[i].IsSame(raw)) {
|
|
112
|
+
return wrappedFaces[i];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
};
|
|
117
|
+
return {
|
|
118
|
+
solid: ShapeFactory.fromShape(clean),
|
|
119
|
+
firstFace: findFace(firstShapeRaw),
|
|
120
|
+
lastFace: findFace(lastShapeRaw),
|
|
121
|
+
edgeFaces: edgeFacesRaw.map(({ edge, face }) => ({ edge, face: findFace(face) })),
|
|
122
|
+
};
|
|
90
123
|
}
|
|
91
124
|
static applyDraftOnSideFaces(solid, firstFace, lastFace, plane, angle) {
|
|
92
125
|
const oc = getOC();
|
|
@@ -9,6 +9,7 @@ export declare class Geometry {
|
|
|
9
9
|
static makeArcFromAngle(center: Point, radius: number, normal: Vector3d, start: Point, angle: number): Geom_TrimmedCurve;
|
|
10
10
|
static makeArcFromTangent(start: Point, end: Point, tangent: Vector3d): Geom_TrimmedCurve;
|
|
11
11
|
static makeCircle(center: Point, radius: number, normal: Vector3d): Geom_Circle;
|
|
12
|
+
static makeEllipseEdge(center: Point, majorRadius: number, minorRadius: number, normal: Vector3d, majorAxisDir: Vector3d): Edge;
|
|
12
13
|
static makeBezierCurve(poles: Point[]): Geom_BezierCurve;
|
|
13
14
|
static makeEdgeFromBezier(curve: Geom_BezierCurve): Edge;
|
|
14
15
|
static makeEdge(geometry: Geom_TrimmedCurve): Edge;
|
package/lib/dist/oc/geometry.js
CHANGED
|
@@ -140,6 +140,33 @@ export class Geometry {
|
|
|
140
140
|
disposeDir();
|
|
141
141
|
throw new Error('Failed to create circle edge: ' + status);
|
|
142
142
|
}
|
|
143
|
+
static makeEllipseEdge(center, majorRadius, minorRadius, normal, majorAxisDir) {
|
|
144
|
+
const oc = getOC();
|
|
145
|
+
const [gpCenter, disposeCenter] = Convert.toGpPnt(center);
|
|
146
|
+
const [gpNormal, disposeNormal] = Convert.toGpDir(normal);
|
|
147
|
+
const [gpAxisDir, disposeAxisDir] = Convert.toGpDir(majorAxisDir);
|
|
148
|
+
const ax2 = new oc.gp_Ax2(gpCenter, gpNormal, gpAxisDir);
|
|
149
|
+
const gpEllipse = new oc.gp_Elips(ax2, majorRadius, minorRadius);
|
|
150
|
+
const edgeMaker = new oc.BRepBuilderAPI_MakeEdge(gpEllipse);
|
|
151
|
+
if (edgeMaker.IsDone()) {
|
|
152
|
+
const edge = edgeMaker.Edge();
|
|
153
|
+
edgeMaker.delete();
|
|
154
|
+
gpEllipse.delete();
|
|
155
|
+
ax2.delete();
|
|
156
|
+
disposeAxisDir();
|
|
157
|
+
disposeNormal();
|
|
158
|
+
disposeCenter();
|
|
159
|
+
return Edge.fromTopoDSEdge(edge);
|
|
160
|
+
}
|
|
161
|
+
const status = edgeMaker.Error();
|
|
162
|
+
edgeMaker.delete();
|
|
163
|
+
gpEllipse.delete();
|
|
164
|
+
ax2.delete();
|
|
165
|
+
disposeAxisDir();
|
|
166
|
+
disposeNormal();
|
|
167
|
+
disposeCenter();
|
|
168
|
+
throw new Error('Failed to create ellipse edge: ' + status);
|
|
169
|
+
}
|
|
143
170
|
static makeBezierCurve(poles) {
|
|
144
171
|
const oc = getOC();
|
|
145
172
|
const polesArray = new oc.TColgp_Array1OfPnt(1, poles.length);
|
package/lib/dist/oc/mesh.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getOC } from "./init.js";
|
|
2
|
+
import { Explorer } from "./explorer.js";
|
|
2
3
|
// Flip to false to benchmark single-threaded meshing.
|
|
3
4
|
const DEFAULT_LIN_DEFLECTION = 0.1;
|
|
4
5
|
const DEFAULT_ANG_DEFLECTION = 0.5;
|
|
@@ -24,6 +25,7 @@ export class Mesh {
|
|
|
24
25
|
if (oc.BRepTools.Triangulation(shape, linDefl, checkFreeEdges)) {
|
|
25
26
|
return false;
|
|
26
27
|
}
|
|
28
|
+
console.log('Triangulating shape of type', Explorer.getShapeType(shape));
|
|
27
29
|
const inc = new oc.BRepMesh_IncrementalMesh(shape, linDefl, relative, angDefl, true);
|
|
28
30
|
inc.delete();
|
|
29
31
|
return true;
|
|
@@ -45,13 +47,13 @@ export class Mesh {
|
|
|
45
47
|
const normals = [];
|
|
46
48
|
const indices = [];
|
|
47
49
|
const aLocation = new oc.TopLoc_Location();
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
+
const faceTriangulation = oc.BRep_Tool.Triangulation(face, aLocation, 0);
|
|
51
|
+
if (faceTriangulation.IsNull()) {
|
|
50
52
|
aLocation.delete();
|
|
51
53
|
return null;
|
|
52
54
|
}
|
|
53
|
-
const pc = new oc.Poly_Connect(
|
|
54
|
-
const triangulation =
|
|
55
|
+
const pc = new oc.Poly_Connect(faceTriangulation);
|
|
56
|
+
const triangulation = faceTriangulation.get();
|
|
55
57
|
const nbNodes = triangulation.NbNodes();
|
|
56
58
|
for (let i = 1; i <= nbNodes; i++) {
|
|
57
59
|
const t1 = aLocation.Transformation();
|
|
@@ -62,11 +64,11 @@ export class Mesh {
|
|
|
62
64
|
p1.delete();
|
|
63
65
|
t1.delete();
|
|
64
66
|
}
|
|
65
|
-
const
|
|
66
|
-
oc.StdPrs_ToolTriangulatedShape.Normal(face, pc,
|
|
67
|
+
const faceNormals = new oc.TColgp_Array1OfDir(1, nbNodes);
|
|
68
|
+
oc.StdPrs_ToolTriangulatedShape.Normal(face, pc, faceNormals);
|
|
67
69
|
for (let i = 1; i <= nbNodes; i++) {
|
|
68
70
|
const t1 = aLocation.Transformation();
|
|
69
|
-
const d1 =
|
|
71
|
+
const d1 = faceNormals.Value(i);
|
|
70
72
|
const d = d1.Transformed(t1);
|
|
71
73
|
normals.push(d.X(), d.Y(), d.Z());
|
|
72
74
|
d1.delete();
|
|
@@ -87,9 +89,9 @@ export class Mesh {
|
|
|
87
89
|
t.delete();
|
|
88
90
|
}
|
|
89
91
|
pc.delete();
|
|
90
|
-
|
|
92
|
+
faceNormals.delete();
|
|
91
93
|
triangles.delete();
|
|
92
|
-
|
|
94
|
+
faceTriangulation.delete();
|
|
93
95
|
aLocation.delete();
|
|
94
96
|
return { vertices, normals, indices, count: nbNodes };
|
|
95
97
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Plane } from "../math/plane.js";
|
|
2
|
+
import { Point2D } from "../math/point.js";
|
|
3
|
+
import { SceneObject } from "../common/scene-object.js";
|
|
4
|
+
/**
|
|
5
|
+
* Finds the nearest intersection of an oriented "ray" with a target geometry's
|
|
6
|
+
* edges, returning the result in sketch-local 2D coordinates.
|
|
7
|
+
*
|
|
8
|
+
* The ray is modelled as a long bounded segment centered on `start` along
|
|
9
|
+
* `direction`, so hits on either side of `start` are considered. The nearest
|
|
10
|
+
* hit (by absolute signed distance) is returned. Hits that coincide with
|
|
11
|
+
* `start` (within tolerance) are skipped so a start point already on the
|
|
12
|
+
* target is not picked.
|
|
13
|
+
*
|
|
14
|
+
* Throws if the target produces no usable edge hits.
|
|
15
|
+
*/
|
|
16
|
+
export declare function findNearestRayIntersection(plane: Plane, start: Point2D, direction: Point2D, target: SceneObject): Point2D;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getOC } from "./init.js";
|
|
2
|
+
import { Geometry } from "./geometry.js";
|
|
3
|
+
import { Explorer } from "./explorer.js";
|
|
4
|
+
import { Point, Point2D } from "../math/point.js";
|
|
5
|
+
const PROBE_HALF_LENGTH = 1e6;
|
|
6
|
+
const ON_TARGET_EPSILON = 1e-7;
|
|
7
|
+
/**
|
|
8
|
+
* Finds the nearest intersection of an oriented "ray" with a target geometry's
|
|
9
|
+
* edges, returning the result in sketch-local 2D coordinates.
|
|
10
|
+
*
|
|
11
|
+
* The ray is modelled as a long bounded segment centered on `start` along
|
|
12
|
+
* `direction`, so hits on either side of `start` are considered. The nearest
|
|
13
|
+
* hit (by absolute signed distance) is returned. Hits that coincide with
|
|
14
|
+
* `start` (within tolerance) are skipped so a start point already on the
|
|
15
|
+
* target is not picked.
|
|
16
|
+
*
|
|
17
|
+
* Throws if the target produces no usable edge hits.
|
|
18
|
+
*/
|
|
19
|
+
export function findNearestRayIntersection(plane, start, direction, target) {
|
|
20
|
+
const oc = getOC();
|
|
21
|
+
const dirLen = Math.hypot(direction.x, direction.y);
|
|
22
|
+
if (dirLen < 1e-12) {
|
|
23
|
+
throw new Error("findNearestRayIntersection: direction vector is zero");
|
|
24
|
+
}
|
|
25
|
+
const dir = new Point2D(direction.x / dirLen, direction.y / dirLen);
|
|
26
|
+
const probeStart2d = new Point2D(start.x - dir.x * PROBE_HALF_LENGTH, start.y - dir.y * PROBE_HALF_LENGTH);
|
|
27
|
+
const probeEnd2d = new Point2D(start.x + dir.x * PROBE_HALF_LENGTH, start.y + dir.y * PROBE_HALF_LENGTH);
|
|
28
|
+
const probeStartWorld = plane.localToWorld(probeStart2d);
|
|
29
|
+
const probeEndWorld = plane.localToWorld(probeEnd2d);
|
|
30
|
+
const probeCurve = Geometry.makeSegment(probeStartWorld, probeEndWorld);
|
|
31
|
+
const probeEdge = Geometry.makeEdgeRaw(probeCurve);
|
|
32
|
+
const targetEdges = [];
|
|
33
|
+
for (const shape of target.getShapes({ excludeGuide: false })) {
|
|
34
|
+
const inner = shape.getShape();
|
|
35
|
+
if (inner.ShapeType() === oc.TopAbs_ShapeEnum.TopAbs_EDGE) {
|
|
36
|
+
targetEdges.push(inner);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const edges = Explorer.findShapes(inner, oc.TopAbs_ShapeEnum.TopAbs_EDGE);
|
|
40
|
+
for (const edge of edges) {
|
|
41
|
+
targetEdges.push(edge);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (targetEdges.length === 0) {
|
|
46
|
+
probeEdge.delete();
|
|
47
|
+
throw new Error("Target geometry has no edges to intersect with");
|
|
48
|
+
}
|
|
49
|
+
// Probe edge is parameterized 0..2L by arc-length (GC_MakeSegment), with the
|
|
50
|
+
// start at param 0. Recover the world point at a given param via the curve
|
|
51
|
+
// adaptor, then convert to plane-local 2D.
|
|
52
|
+
const probeAdaptor = new oc.BRepAdaptor_Curve(probeEdge);
|
|
53
|
+
let bestHit = null;
|
|
54
|
+
let bestSignedDist = Infinity;
|
|
55
|
+
for (const targetEdge of targetEdges) {
|
|
56
|
+
const tool = new oc.IntTools_EdgeEdge(probeEdge, targetEdge);
|
|
57
|
+
tool.Perform();
|
|
58
|
+
if (!tool.IsDone()) {
|
|
59
|
+
tool.delete();
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const parts = tool.CommonParts();
|
|
63
|
+
const partCount = parts.Length();
|
|
64
|
+
for (let i = 1; i <= partCount; i++) {
|
|
65
|
+
const cp = parts.Value(i);
|
|
66
|
+
if (cp.Type() !== oc.TopAbs_ShapeEnum.TopAbs_VERTEX) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const probeParam = cp.VertexParameter1();
|
|
70
|
+
const gpHit = probeAdaptor.Value(probeParam);
|
|
71
|
+
const hitWorld = new Point(gpHit.X(), gpHit.Y(), gpHit.Z());
|
|
72
|
+
gpHit.delete();
|
|
73
|
+
const hit2d = plane.worldToLocal(hitWorld);
|
|
74
|
+
const signedDist = (hit2d.x - start.x) * dir.x + (hit2d.y - start.y) * dir.y;
|
|
75
|
+
if (Math.abs(signedDist) < ON_TARGET_EPSILON) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (Math.abs(signedDist) < Math.abs(bestSignedDist)) {
|
|
79
|
+
bestSignedDist = signedDist;
|
|
80
|
+
bestHit = hit2d;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
tool.delete();
|
|
84
|
+
}
|
|
85
|
+
probeAdaptor.delete();
|
|
86
|
+
probeEdge.delete();
|
|
87
|
+
if (!bestHit) {
|
|
88
|
+
throw new Error("Line does not intersect target geometry");
|
|
89
|
+
}
|
|
90
|
+
return bestHit;
|
|
91
|
+
}
|
|
@@ -30,7 +30,6 @@ export declare class ThinFaceMaker {
|
|
|
30
30
|
* breaking IsPartner identity between original wire edges and face edges.
|
|
31
31
|
*/
|
|
32
32
|
private static matchFaceEdgesByMidpoint;
|
|
33
|
-
private static makeLineEdge;
|
|
34
33
|
/**
|
|
35
34
|
* Creates a closed face from two open wires by capping the ends with straight lines.
|
|
36
35
|
* wire1 goes A->B, wire2 goes C->D.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Edge } from "../common/edge.js";
|
|
2
1
|
import { Face } from "../common/face.js";
|
|
3
2
|
import { Wire } from "../common/wire.js";
|
|
4
3
|
import { WireOps } from "./wire-ops.js";
|
|
@@ -162,23 +161,6 @@ export class ThinFaceMaker {
|
|
|
162
161
|
return wireMidpoints.some(mp => feMid.distanceTo(mp) < 1e-4);
|
|
163
162
|
});
|
|
164
163
|
}
|
|
165
|
-
static makeLineEdge(p1, p2) {
|
|
166
|
-
const oc = getOC();
|
|
167
|
-
const [gp1, dispose1] = Convert.toGpPnt(p1);
|
|
168
|
-
const [gp2, dispose2] = Convert.toGpPnt(p2);
|
|
169
|
-
const edgeMaker = new oc.BRepBuilderAPI_MakeEdge(gp1, gp2);
|
|
170
|
-
if (!edgeMaker.IsDone()) {
|
|
171
|
-
edgeMaker.delete();
|
|
172
|
-
dispose1();
|
|
173
|
-
dispose2();
|
|
174
|
-
throw new Error("Failed to create cap edge for thin extrude");
|
|
175
|
-
}
|
|
176
|
-
const edge = edgeMaker.Edge();
|
|
177
|
-
edgeMaker.delete();
|
|
178
|
-
dispose1();
|
|
179
|
-
dispose2();
|
|
180
|
-
return Edge.fromTopoDSEdge(edge);
|
|
181
|
-
}
|
|
182
164
|
/**
|
|
183
165
|
* Creates a closed face from two open wires by capping the ends with straight lines.
|
|
184
166
|
* wire1 goes A->B, wire2 goes C->D.
|
|
@@ -189,8 +171,8 @@ export class ThinFaceMaker {
|
|
|
189
171
|
const wire2Start = wire2.getFirstVertex().toPoint();
|
|
190
172
|
const wire2End = wire2.getLastVertex().toPoint();
|
|
191
173
|
const wire1Start = wire1.getFirstVertex().toPoint();
|
|
192
|
-
const cap1 =
|
|
193
|
-
const cap2 =
|
|
174
|
+
const cap1 = EdgeOps.makeLineEdge(wire1End, wire2End);
|
|
175
|
+
const cap2 = EdgeOps.makeLineEdge(wire2Start, wire1Start);
|
|
194
176
|
const reversedWire2 = WireOps.reverseWire(wire2);
|
|
195
177
|
const allEdges = [
|
|
196
178
|
...wire1.getEdges(),
|
|
@@ -3,7 +3,8 @@ export declare class SceneRenderer {
|
|
|
3
3
|
private readonly meshBuilder;
|
|
4
4
|
render(scene: Scene): Scene;
|
|
5
5
|
renderRollback(scene: Scene, rollbackIndex: number): Scene;
|
|
6
|
-
private
|
|
6
|
+
private prepareRenderedShapes;
|
|
7
|
+
private emitRenderObject;
|
|
7
8
|
private buildObject;
|
|
8
9
|
private getOrBuildMeshes;
|
|
9
10
|
private toRenderedShape;
|