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
|
@@ -19,14 +19,15 @@ export class Loft extends ExtrudeBase {
|
|
|
19
19
|
if (this.profiles.length < 2) {
|
|
20
20
|
throw new Error("Loft requires at least two profiles.");
|
|
21
21
|
}
|
|
22
|
+
const p = context.getProfiler();
|
|
22
23
|
let newShapes;
|
|
23
24
|
if (this.isThin()) {
|
|
24
|
-
newShapes = this.buildThinLoft();
|
|
25
|
+
newShapes = p.record('Build thin loft', () => this.buildThinLoft());
|
|
25
26
|
}
|
|
26
27
|
else {
|
|
27
28
|
const allWires = [];
|
|
28
29
|
for (const profile of this.profiles) {
|
|
29
|
-
const wires = this.getWiresFromSceneObject(profile);
|
|
30
|
+
const wires = p.record('Get profile wires', () => this.getWiresFromSceneObject(profile));
|
|
30
31
|
if (wires.length === 0) {
|
|
31
32
|
throw new Error("Could not extract wire from profile.");
|
|
32
33
|
}
|
|
@@ -34,7 +35,7 @@ export class Loft extends ExtrudeBase {
|
|
|
34
35
|
allWires.push(wire);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
newShapes = LoftOps.makeLoft(allWires);
|
|
38
|
+
newShapes = p.record('Make loft', () => LoftOps.makeLoft(allWires));
|
|
38
39
|
}
|
|
39
40
|
for (const profile of this.profiles) {
|
|
40
41
|
profile.removeShapes(this);
|
|
@@ -64,13 +65,15 @@ export class Loft extends ExtrudeBase {
|
|
|
64
65
|
this.setState('side-faces', sideFaces);
|
|
65
66
|
// Handle boolean operation based on operation mode
|
|
66
67
|
if (this._operationMode === 'remove') {
|
|
67
|
-
const scope = this.resolveFusionScope(context.getSceneObjects());
|
|
68
|
+
const scope = p.record('Resolve fusion scope', () => this.resolveFusionScope(context.getSceneObjects()));
|
|
68
69
|
const plane = firstPlane || lastPlane;
|
|
69
|
-
|
|
70
|
+
p.record('Cut with scene objects', () => {
|
|
71
|
+
cutWithSceneObjects(scope, newShapes, plane, 0, this, { recordHistoryFor: this });
|
|
72
|
+
});
|
|
70
73
|
this.setFinalShapes(this.getShapes());
|
|
71
74
|
return;
|
|
72
75
|
}
|
|
73
|
-
const sceneObjects = this.resolveFusionScope(context.getSceneObjects());
|
|
76
|
+
const sceneObjects = p.record('Resolve fusion scope', () => this.resolveFusionScope(context.getSceneObjects()));
|
|
74
77
|
if (sceneObjects.length === 0) {
|
|
75
78
|
this.addShapes(newShapes);
|
|
76
79
|
this.recordShapeFacesAndEdgesAsAdditions(newShapes);
|
|
@@ -78,9 +81,9 @@ export class Loft extends ExtrudeBase {
|
|
|
78
81
|
this.setFinalShapes(this.getShapes());
|
|
79
82
|
return;
|
|
80
83
|
}
|
|
81
|
-
const fusionResult = fuseWithSceneObjects(sceneObjects, newShapes, {
|
|
84
|
+
const fusionResult = p.record('Fuse with scene objects', () => fuseWithSceneObjects(sceneObjects, newShapes, {
|
|
82
85
|
recordHistoryFor: this,
|
|
83
|
-
});
|
|
86
|
+
}));
|
|
84
87
|
for (const modifiedShape of fusionResult.modifiedShapes) {
|
|
85
88
|
if (modifiedShape.object) {
|
|
86
89
|
modifiedShape.object.removeShape(modifiedShape.shape, this);
|
|
@@ -3,7 +3,9 @@ import { PlaneObjectBase } from "./plane-renderable-base.js";
|
|
|
3
3
|
export declare class MirrorShape extends SceneObject {
|
|
4
4
|
private plane;
|
|
5
5
|
targetObjects: SceneObject[] | null;
|
|
6
|
+
private _excludedObjects;
|
|
6
7
|
constructor(plane: PlaneObjectBase, targetObjects?: SceneObject[] | null);
|
|
8
|
+
exclude(...objects: SceneObject[]): this;
|
|
7
9
|
build(context: BuildSceneObjectContext): void;
|
|
8
10
|
compareTo(other: MirrorShape): boolean;
|
|
9
11
|
getType(): string;
|
|
@@ -5,11 +5,16 @@ import { fuseWithSceneObjects } from "../helpers/scene-helpers.js";
|
|
|
5
5
|
export class MirrorShape extends SceneObject {
|
|
6
6
|
plane;
|
|
7
7
|
targetObjects;
|
|
8
|
+
_excludedObjects = [];
|
|
8
9
|
constructor(plane, targetObjects = null) {
|
|
9
10
|
super();
|
|
10
11
|
this.plane = plane;
|
|
11
12
|
this.targetObjects = targetObjects;
|
|
12
13
|
}
|
|
14
|
+
exclude(...objects) {
|
|
15
|
+
this._excludedObjects.push(...objects);
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
13
18
|
build(context) {
|
|
14
19
|
let objects;
|
|
15
20
|
let targetObjects = this.targetObjects;
|
|
@@ -30,6 +35,9 @@ export class MirrorShape extends SceneObject {
|
|
|
30
35
|
else {
|
|
31
36
|
targetObjects = lastObj ? [lastObj] : objects;
|
|
32
37
|
}
|
|
38
|
+
if (this._excludedObjects.length > 0) {
|
|
39
|
+
targetObjects = targetObjects.filter(obj => !this._excludedObjects.includes(obj));
|
|
40
|
+
}
|
|
33
41
|
if (this.plane) {
|
|
34
42
|
this.plane.removeShapes(this);
|
|
35
43
|
}
|
|
@@ -81,6 +89,14 @@ export class MirrorShape extends SceneObject {
|
|
|
81
89
|
return false;
|
|
82
90
|
}
|
|
83
91
|
}
|
|
92
|
+
if (this._excludedObjects.length !== other._excludedObjects.length) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
for (let i = 0; i < this._excludedObjects.length; i++) {
|
|
96
|
+
if (!this._excludedObjects[i].compareTo(other._excludedObjects[i])) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
84
100
|
return true;
|
|
85
101
|
}
|
|
86
102
|
getType() {
|
|
@@ -5,7 +5,9 @@ import { LazyVertex } from "./lazy-vertex.js";
|
|
|
5
5
|
export declare class MirrorShape2D extends GeometrySceneObject {
|
|
6
6
|
private axis;
|
|
7
7
|
private targetObjects;
|
|
8
|
+
private _excludedObjects;
|
|
8
9
|
constructor(axis: AxisObjectBase, targetObjects?: SceneObject[]);
|
|
10
|
+
exclude(...objects: SceneObject[]): this;
|
|
9
11
|
build(context: BuildSceneObjectContext): void;
|
|
10
12
|
start(): LazyVertex;
|
|
11
13
|
end(): LazyVertex;
|
|
@@ -6,11 +6,16 @@ import { Vertex } from "../common/vertex.js";
|
|
|
6
6
|
export class MirrorShape2D extends GeometrySceneObject {
|
|
7
7
|
axis;
|
|
8
8
|
targetObjects;
|
|
9
|
+
_excludedObjects = [];
|
|
9
10
|
constructor(axis, targetObjects = null) {
|
|
10
11
|
super();
|
|
11
12
|
this.axis = axis;
|
|
12
13
|
this.targetObjects = targetObjects;
|
|
13
14
|
}
|
|
15
|
+
exclude(...objects) {
|
|
16
|
+
this._excludedObjects.push(...objects);
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
14
19
|
build(context) {
|
|
15
20
|
let targetObjects = this.targetObjects;
|
|
16
21
|
let sketch = this.sketch;
|
|
@@ -23,6 +28,9 @@ export class MirrorShape2D extends GeometrySceneObject {
|
|
|
23
28
|
else {
|
|
24
29
|
targetObjects = objects;
|
|
25
30
|
}
|
|
31
|
+
if (this._excludedObjects.length > 0) {
|
|
32
|
+
targetObjects = targetObjects.filter(obj => !this._excludedObjects.includes(obj));
|
|
33
|
+
}
|
|
26
34
|
this.axis.removeShapes(this);
|
|
27
35
|
axis = this.axis.getAxis();
|
|
28
36
|
const transformedShapes = [];
|
|
@@ -79,7 +87,12 @@ export class MirrorShape2D extends GeometrySceneObject {
|
|
|
79
87
|
const targetObjects = this.targetObjects
|
|
80
88
|
? this.targetObjects.map(obj => remap.get(obj) || obj)
|
|
81
89
|
: null;
|
|
82
|
-
|
|
90
|
+
const copy = new MirrorShape2D(axis, targetObjects);
|
|
91
|
+
if (this._excludedObjects.length > 0) {
|
|
92
|
+
const remappedExcluded = this._excludedObjects.map(obj => remap.get(obj) || obj);
|
|
93
|
+
copy.exclude(...remappedExcluded);
|
|
94
|
+
}
|
|
95
|
+
return copy;
|
|
83
96
|
}
|
|
84
97
|
compareTo(other) {
|
|
85
98
|
if (!(other instanceof MirrorShape2D)) {
|
|
@@ -101,6 +114,14 @@ export class MirrorShape2D extends GeometrySceneObject {
|
|
|
101
114
|
return false;
|
|
102
115
|
}
|
|
103
116
|
}
|
|
117
|
+
if (this._excludedObjects.length !== other._excludedObjects.length) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
for (let i = 0; i < this._excludedObjects.length; i++) {
|
|
121
|
+
if (!this._excludedObjects[i].compareTo(other._excludedObjects[i])) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
104
125
|
return true;
|
|
105
126
|
}
|
|
106
127
|
getType() {
|
|
@@ -8,6 +8,37 @@ export declare class Revolve extends ExtrudeBase implements IRevolve {
|
|
|
8
8
|
angle: number;
|
|
9
9
|
constructor(axis: AxisObjectBase, angle: number, extrudable?: Extrudable);
|
|
10
10
|
build(context: BuildSceneObjectContext): void;
|
|
11
|
+
/** Plain revolve: classify by inner-wire detection on the source plane. */
|
|
12
|
+
private buildRevolve;
|
|
13
|
+
/** Thin revolve: shell-like profile with inward/outward offsets. */
|
|
14
|
+
private buildRevolveThin;
|
|
15
|
+
/**
|
|
16
|
+
* Classify a thin open-profile revolve via the per-edge history captured
|
|
17
|
+
* during `makeRevol`. Each entry in `revols[i].edgeFaces` pairs an input
|
|
18
|
+
* edge of the thin face with the swept face it produced; we route that
|
|
19
|
+
* face into internal / side / cap based on which edge category the input
|
|
20
|
+
* belongs to (`thinResult.inwardEdges`, `outwardEdges`, or neither →
|
|
21
|
+
* cap-line edge added by `makeOpenFaceWithCaps`).
|
|
22
|
+
*/
|
|
23
|
+
private classifyThinByEdgeHistory;
|
|
24
|
+
/**
|
|
25
|
+
* Run the revolutions for each fused profile face. Identifies start/end
|
|
26
|
+
* faces via the maker's `FirstShape` / `LastShape` and tracks which side
|
|
27
|
+
* face each input edge generated via `Generated()` — both survive the
|
|
28
|
+
* downstream `cleanShapeRaw`. Caller refines side → side/internal/cap
|
|
29
|
+
* using the per-edge mapping in `revols[i].edgeFaces`.
|
|
30
|
+
*/
|
|
31
|
+
private runRevolutions;
|
|
32
|
+
/**
|
|
33
|
+
* Rotate the revolved solid by `matrix` (used for `.symmetric()`) and
|
|
34
|
+
* remap firstFace / lastFace / edgeFaces through the transformer's
|
|
35
|
+
* `ModifiedShape` so classification keeps pointing at the right TShapes.
|
|
36
|
+
*/
|
|
37
|
+
private applySymmetricTransform;
|
|
38
|
+
/** Inner-wire classification used by both regular revolve and closed thin profiles. */
|
|
39
|
+
private classifyRevolveByInnerWires;
|
|
40
|
+
/** Remove source + axis, then dispatch to cut or fuse path. */
|
|
41
|
+
private dispatchFinalize;
|
|
11
42
|
getDependencies(): SceneObject[];
|
|
12
43
|
createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
|
|
13
44
|
compareTo(other: Revolve): boolean;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { rad } from "../helpers/math-helpers.js";
|
|
2
2
|
import { Solid } from "../common/shapes.js";
|
|
3
|
-
import {
|
|
3
|
+
import { cutWithSceneObjects } from "../helpers/scene-helpers.js";
|
|
4
4
|
import { ExtrudeOps } from "../oc/extrude-ops.js";
|
|
5
5
|
import { Explorer } from "../oc/explorer.js";
|
|
6
|
-
import { ShapeOps } from "../oc/shape-ops.js";
|
|
7
6
|
import { FaceMaker2 } from "../oc/face-maker2.js";
|
|
8
7
|
import { ExtrudeBase } from "./extrude-base.js";
|
|
9
8
|
import { BooleanOps } from "../oc/boolean-ops.js";
|
|
10
|
-
import {
|
|
9
|
+
import { Face } from "../common/face.js";
|
|
11
10
|
import { ThinFaceMaker } from "../oc/thin-face-maker.js";
|
|
12
11
|
import { Matrix4 } from "../math/matrix4.js";
|
|
12
|
+
import { Convert } from "../oc/convert.js";
|
|
13
|
+
import { ShapeFactory } from "../common/shape-factory.js";
|
|
14
|
+
import { getOC } from "../oc/init.js";
|
|
13
15
|
export class Revolve extends ExtrudeBase {
|
|
14
16
|
axis;
|
|
15
17
|
angle;
|
|
@@ -19,128 +21,209 @@ export class Revolve extends ExtrudeBase {
|
|
|
19
21
|
this.angle = angle;
|
|
20
22
|
}
|
|
21
23
|
build(context) {
|
|
24
|
+
const p = context.getProfiler();
|
|
22
25
|
const plane = this.extrudable.getPlane();
|
|
23
|
-
const pickedFaces = this.resolvePickedFaces(plane);
|
|
26
|
+
const pickedFaces = p.record('Resolve picked faces', () => this.resolvePickedFaces(plane));
|
|
24
27
|
if (pickedFaces !== null && pickedFaces.length === 0) {
|
|
25
28
|
return;
|
|
26
29
|
}
|
|
27
|
-
const solids = [];
|
|
28
|
-
const allStartFaces = [];
|
|
29
|
-
const allEndFaces = [];
|
|
30
|
-
let allSideFaces = [];
|
|
31
|
-
let allInternalFaces = [];
|
|
32
|
-
let allCapFaces = [];
|
|
33
|
-
let faces = pickedFaces ?? FaceMaker2.getRegions(this.extrudable.getGeometries(), plane);
|
|
34
|
-
let inwardEdges;
|
|
35
|
-
let outwardEdges;
|
|
36
30
|
if (this.isThin()) {
|
|
37
|
-
const thinResult = ThinFaceMaker.make(this.extrudable.getGeometries(), plane, this._thin[0], this._thin[1]);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
const thinResult = p.record('Make thin faces', () => ThinFaceMaker.make(this.extrudable.getGeometries(), plane, this._thin[0], this._thin[1]));
|
|
32
|
+
this.buildRevolveThin(thinResult, plane, context);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const faces = pickedFaces ?? p.record('Resolve faces', () => FaceMaker2.getRegions(this.extrudable.getGeometries(), plane));
|
|
36
|
+
this.buildRevolve(faces, plane, context);
|
|
37
|
+
}
|
|
38
|
+
this.setFinalShapes(this.getShapes());
|
|
39
|
+
}
|
|
40
|
+
/** Plain revolve: classify by inner-wire detection on the source plane. */
|
|
41
|
+
buildRevolve(faces, plane, context) {
|
|
42
|
+
const revolved = this.runRevolutions(faces, context);
|
|
43
|
+
const classified = this.classifyRevolveByInnerWires(revolved, plane);
|
|
44
|
+
this.dispatchFinalize(revolved.solids, classified, plane, context);
|
|
45
|
+
}
|
|
46
|
+
/** Thin revolve: shell-like profile with inward/outward offsets. */
|
|
47
|
+
buildRevolveThin(thinResult, plane, context) {
|
|
48
|
+
const revolved = this.runRevolutions(thinResult.faces, context);
|
|
49
|
+
let classified;
|
|
50
|
+
if (thinResult.inwardEdges.length > 0) {
|
|
51
|
+
// Open profile: each input edge of the thin face categorizes the side
|
|
52
|
+
// face it generated (inward → internal, outward → side, anything else
|
|
53
|
+
// is a cap edge → cap face).
|
|
54
|
+
classified = this.classifyThinByEdgeHistory(revolved, thinResult);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Closed profile: regular inner-wire detection.
|
|
58
|
+
classified = this.classifyRevolveByInnerWires(revolved, plane);
|
|
41
59
|
}
|
|
42
|
-
|
|
60
|
+
this.dispatchFinalize(revolved.solids, classified, plane, context);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Classify a thin open-profile revolve via the per-edge history captured
|
|
64
|
+
* during `makeRevol`. Each entry in `revols[i].edgeFaces` pairs an input
|
|
65
|
+
* edge of the thin face with the swept face it produced; we route that
|
|
66
|
+
* face into internal / side / cap based on which edge category the input
|
|
67
|
+
* belongs to (`thinResult.inwardEdges`, `outwardEdges`, or neither →
|
|
68
|
+
* cap-line edge added by `makeOpenFaceWithCaps`).
|
|
69
|
+
*/
|
|
70
|
+
classifyThinByEdgeHistory(revolved, thinResult) {
|
|
71
|
+
const sideFaces = [];
|
|
72
|
+
const internalFaces = [];
|
|
73
|
+
const capFaces = [];
|
|
74
|
+
const matchesAny = (edge, refs) => refs.some(r => r.getShape().IsSame(edge));
|
|
75
|
+
for (const revol of revolved.revols) {
|
|
76
|
+
for (const { edge, face } of revol.edgeFaces) {
|
|
77
|
+
if (!face) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (matchesAny(edge, thinResult.inwardEdges)) {
|
|
81
|
+
internalFaces.push(face);
|
|
82
|
+
}
|
|
83
|
+
else if (matchesAny(edge, thinResult.outwardEdges)) {
|
|
84
|
+
sideFaces.push(face);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
capFaces.push(face);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
startFaces: revolved.startFaces,
|
|
93
|
+
endFaces: revolved.endFaces,
|
|
94
|
+
sideFaces,
|
|
95
|
+
internalFaces,
|
|
96
|
+
capFaces,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Run the revolutions for each fused profile face. Identifies start/end
|
|
101
|
+
* faces via the maker's `FirstShape` / `LastShape` and tracks which side
|
|
102
|
+
* face each input edge generated via `Generated()` — both survive the
|
|
103
|
+
* downstream `cleanShapeRaw`. Caller refines side → side/internal/cap
|
|
104
|
+
* using the per-edge mapping in `revols[i].edgeFaces`.
|
|
105
|
+
*/
|
|
106
|
+
runRevolutions(faces, context) {
|
|
107
|
+
const p = context.getProfiler();
|
|
108
|
+
const { result: fusedFaces } = p.record('Fuse faces', () => BooleanOps.fuseFaces(faces));
|
|
43
109
|
const axis = this.axis.getAxis();
|
|
44
|
-
const
|
|
110
|
+
const solids = [];
|
|
111
|
+
const startFaces = [];
|
|
112
|
+
const endFaces = [];
|
|
113
|
+
const sideFaces = [];
|
|
114
|
+
const revols = [];
|
|
45
115
|
for (const face of fusedFaces) {
|
|
46
|
-
|
|
47
|
-
let resultSolid;
|
|
116
|
+
let revol = p.record('Revolve face', () => ExtrudeOps.makeRevol(face, axis, rad(this.angle)));
|
|
48
117
|
if (this._symmetric) {
|
|
49
118
|
const matrix = Matrix4.fromRotationAroundAxis(axis.origin, axis.direction, -rad(this.angle) / 2);
|
|
50
|
-
|
|
51
|
-
resultSolid = Solid.fromTopoDSSolid(Explorer.toSolid(rotated.getShape()));
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
resultSolid = Solid.fromTopoDSSolid(Explorer.toSolid(solid.getShape()));
|
|
119
|
+
revol = this.applySymmetricTransform(revol, matrix);
|
|
55
120
|
}
|
|
121
|
+
const resultSolid = Solid.fromTopoDSSolid(Explorer.toSolid(revol.solid.getShape()));
|
|
56
122
|
solids.push(resultSolid);
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
123
|
+
revols.push(revol);
|
|
124
|
+
const firstRaw = revol.firstFace?.getShape() ?? null;
|
|
125
|
+
const lastRaw = revol.lastFace?.getShape() ?? null;
|
|
126
|
+
for (const f of Explorer.findFacesWrapped(resultSolid)) {
|
|
127
|
+
const raw = f.getShape();
|
|
128
|
+
if (firstRaw && raw.IsSame(firstRaw)) {
|
|
129
|
+
startFaces.push(f);
|
|
130
|
+
}
|
|
131
|
+
else if (lastRaw && raw.IsSame(lastRaw)) {
|
|
132
|
+
endFaces.push(f);
|
|
63
133
|
}
|
|
64
134
|
else {
|
|
65
|
-
|
|
135
|
+
sideFaces.push(f);
|
|
66
136
|
}
|
|
67
137
|
}
|
|
68
138
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
139
|
+
return { solids, startFaces, endFaces, sideFaces, revols };
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Rotate the revolved solid by `matrix` (used for `.symmetric()`) and
|
|
143
|
+
* remap firstFace / lastFace / edgeFaces through the transformer's
|
|
144
|
+
* `ModifiedShape` so classification keeps pointing at the right TShapes.
|
|
145
|
+
*/
|
|
146
|
+
applySymmetricTransform(revol, matrix) {
|
|
147
|
+
const oc = getOC();
|
|
148
|
+
const [trsf, disposeTrsf] = Convert.toGpTrsf(matrix);
|
|
149
|
+
const transformer = new oc.BRepBuilderAPI_Transform(trsf);
|
|
150
|
+
transformer.Perform(revol.solid.getShape(), true);
|
|
151
|
+
const transformedSolid = ShapeFactory.fromShape(transformer.Shape());
|
|
152
|
+
const remapFace = (f) => {
|
|
153
|
+
if (!f) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const modified = transformer.ModifiedShape(f.getShape());
|
|
157
|
+
return Face.fromTopoDSFace(Explorer.toFace(modified));
|
|
158
|
+
};
|
|
159
|
+
const result = {
|
|
160
|
+
solid: transformedSolid,
|
|
161
|
+
firstFace: remapFace(revol.firstFace),
|
|
162
|
+
lastFace: remapFace(revol.lastFace),
|
|
163
|
+
edgeFaces: revol.edgeFaces.map(({ edge, face }) => ({
|
|
164
|
+
edge,
|
|
165
|
+
face: remapFace(face),
|
|
166
|
+
})),
|
|
167
|
+
};
|
|
168
|
+
transformer.delete();
|
|
169
|
+
disposeTrsf();
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
/** Inner-wire classification used by both regular revolve and closed thin profiles. */
|
|
173
|
+
classifyRevolveByInnerWires(revolved, plane) {
|
|
174
|
+
const innerWireEdges = [];
|
|
175
|
+
for (const sf of revolved.startFaces) {
|
|
176
|
+
for (const wire of sf.getWires()) {
|
|
177
|
+
if (!wire.isCW(plane.normal)) {
|
|
178
|
+
for (const edge of wire.getEdges()) {
|
|
179
|
+
innerWireEdges.push(edge);
|
|
92
180
|
}
|
|
93
181
|
}
|
|
94
182
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
183
|
+
}
|
|
184
|
+
const sideFaces = [];
|
|
185
|
+
const internalFaces = [];
|
|
186
|
+
if (innerWireEdges.length === 0) {
|
|
187
|
+
sideFaces.push(...revolved.sideFaces);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
for (const f of revolved.sideFaces) {
|
|
191
|
+
const isInternal = f.getEdges().some(fe => innerWireEdges.some(iwe => fe.getShape().IsPartner(iwe.getShape())));
|
|
192
|
+
if (isInternal) {
|
|
193
|
+
internalFaces.push(f);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
sideFaces.push(f);
|
|
105
197
|
}
|
|
106
|
-
allSideFaces = remaining;
|
|
107
198
|
}
|
|
108
199
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
200
|
+
return {
|
|
201
|
+
startFaces: revolved.startFaces,
|
|
202
|
+
endFaces: revolved.endFaces,
|
|
203
|
+
sideFaces,
|
|
204
|
+
internalFaces,
|
|
205
|
+
capFaces: [],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/** Remove source + axis, then dispatch to cut or fuse path. */
|
|
209
|
+
dispatchFinalize(solids, classified, plane, context) {
|
|
114
210
|
this.extrudable.removeShapes(this);
|
|
115
211
|
this.axis.removeShapes(this);
|
|
116
212
|
if (this._operationMode === 'remove') {
|
|
117
213
|
const scope = this.resolveFusionScope(context.getSceneObjects());
|
|
214
|
+
// Note: stash classification state up front — cutWithSceneObjects /
|
|
215
|
+
// classifyCutResult writes its own state keys, but the pre-classified
|
|
216
|
+
// faces are useful for the remove path's selection accessors when no
|
|
217
|
+
// cut-specific edges exist for that category.
|
|
218
|
+
this.setState('start-faces', classified.startFaces);
|
|
219
|
+
this.setState('end-faces', classified.endFaces);
|
|
220
|
+
this.setState('side-faces', classified.sideFaces);
|
|
221
|
+
this.setState('internal-faces', classified.internalFaces);
|
|
222
|
+
this.setState('cap-faces', classified.capFaces);
|
|
118
223
|
cutWithSceneObjects(scope, solids, plane, 0, this, { recordHistoryFor: this });
|
|
119
|
-
this.setFinalShapes(this.getShapes());
|
|
120
224
|
return;
|
|
121
225
|
}
|
|
122
|
-
|
|
123
|
-
if (sceneObjects.length === 0) {
|
|
124
|
-
this.addShapes(solids);
|
|
125
|
-
this.recordShapeFacesAndEdgesAsAdditions(solids);
|
|
126
|
-
this.classifyExtrudeEdges();
|
|
127
|
-
this.setFinalShapes(this.getShapes());
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const fusionResult = fuseWithSceneObjects(sceneObjects, solids, {
|
|
131
|
-
recordHistoryFor: this,
|
|
132
|
-
});
|
|
133
|
-
for (const modifiedShape of fusionResult.modifiedShapes) {
|
|
134
|
-
if (modifiedShape.object) {
|
|
135
|
-
modifiedShape.object.removeShape(modifiedShape.shape, this);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
this.addShapes(fusionResult.newShapes);
|
|
139
|
-
if (fusionResult.toolHistory) {
|
|
140
|
-
this.remapClassifiedFaces(fusionResult.toolHistory);
|
|
141
|
-
}
|
|
142
|
-
this.classifyExtrudeEdges();
|
|
143
|
-
this.setFinalShapes(this.getShapes());
|
|
226
|
+
this.finalizeAndFuse(solids, classified, context);
|
|
144
227
|
}
|
|
145
228
|
getDependencies() {
|
|
146
229
|
return this.extrudable ? [this.extrudable] : [];
|
|
@@ -5,8 +5,10 @@ export declare class Rotate extends SceneObject {
|
|
|
5
5
|
angle: number;
|
|
6
6
|
private copy;
|
|
7
7
|
private _targetObjects;
|
|
8
|
+
private _excludedObjects;
|
|
8
9
|
constructor(axis: AxisObjectBase, angle: number, copy?: boolean, ...targets: SceneObject[]);
|
|
9
10
|
get targetObjects(): SceneObject[];
|
|
11
|
+
exclude(...objects: SceneObject[]): this;
|
|
10
12
|
build(context: BuildSceneObjectContext): void;
|
|
11
13
|
compareTo(other: Rotate): boolean;
|
|
12
14
|
getType(): string;
|
|
@@ -7,6 +7,7 @@ export class Rotate extends SceneObject {
|
|
|
7
7
|
angle;
|
|
8
8
|
copy;
|
|
9
9
|
_targetObjects = null;
|
|
10
|
+
_excludedObjects = [];
|
|
10
11
|
constructor(axis, angle, copy = false, ...targets) {
|
|
11
12
|
super();
|
|
12
13
|
this.axis = axis;
|
|
@@ -17,6 +18,10 @@ export class Rotate extends SceneObject {
|
|
|
17
18
|
get targetObjects() {
|
|
18
19
|
return this._targetObjects;
|
|
19
20
|
}
|
|
21
|
+
exclude(...objects) {
|
|
22
|
+
this._excludedObjects.push(...objects);
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
20
25
|
build(context) {
|
|
21
26
|
let objects;
|
|
22
27
|
let targetObjects = this.targetObjects;
|
|
@@ -34,6 +39,9 @@ export class Rotate extends SceneObject {
|
|
|
34
39
|
else {
|
|
35
40
|
targetObjects = objects;
|
|
36
41
|
}
|
|
42
|
+
if (this._excludedObjects.length > 0) {
|
|
43
|
+
targetObjects = targetObjects.filter(obj => !this._excludedObjects.includes(obj));
|
|
44
|
+
}
|
|
37
45
|
this.axis.removeShapes(this);
|
|
38
46
|
const axis = this.axis.getAxis();
|
|
39
47
|
const matrix = Matrix4.fromRotationAroundAxis(axis.origin, axis.direction, rad(this.angle));
|
|
@@ -77,6 +85,14 @@ export class Rotate extends SceneObject {
|
|
|
77
85
|
return false;
|
|
78
86
|
}
|
|
79
87
|
}
|
|
88
|
+
if (this._excludedObjects.length !== other._excludedObjects.length) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
for (let i = 0; i < this._excludedObjects.length; i++) {
|
|
92
|
+
if (this._excludedObjects[i] !== other._excludedObjects[i]) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
80
96
|
return true;
|
|
81
97
|
}
|
|
82
98
|
getType() {
|
|
@@ -4,8 +4,10 @@ export declare class Rotate2D extends GeometrySceneObject {
|
|
|
4
4
|
angle: number;
|
|
5
5
|
private copy;
|
|
6
6
|
private _targetObjects;
|
|
7
|
+
private _excludedObjects;
|
|
7
8
|
constructor(angle: number, copy?: boolean, ...targets: SceneObject[]);
|
|
8
9
|
get targetObjects(): SceneObject[] | null;
|
|
10
|
+
exclude(...objects: SceneObject[]): this;
|
|
9
11
|
build(context: BuildSceneObjectContext): void;
|
|
10
12
|
compareTo(other: Rotate2D): boolean;
|
|
11
13
|
getType(): string;
|
|
@@ -7,6 +7,7 @@ export class Rotate2D extends GeometrySceneObject {
|
|
|
7
7
|
angle;
|
|
8
8
|
copy;
|
|
9
9
|
_targetObjects = null;
|
|
10
|
+
_excludedObjects = [];
|
|
10
11
|
constructor(angle, copy = false, ...targets) {
|
|
11
12
|
super();
|
|
12
13
|
this.angle = angle;
|
|
@@ -16,6 +17,10 @@ export class Rotate2D extends GeometrySceneObject {
|
|
|
16
17
|
get targetObjects() {
|
|
17
18
|
return this._targetObjects;
|
|
18
19
|
}
|
|
20
|
+
exclude(...objects) {
|
|
21
|
+
this._excludedObjects.push(...objects);
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
19
24
|
build(context) {
|
|
20
25
|
let objects;
|
|
21
26
|
let targetObjects = this.targetObjects;
|
|
@@ -27,6 +32,9 @@ export class Rotate2D extends GeometrySceneObject {
|
|
|
27
32
|
else {
|
|
28
33
|
targetObjects = objects;
|
|
29
34
|
}
|
|
35
|
+
if (this._excludedObjects.length > 0) {
|
|
36
|
+
targetObjects = targetObjects.filter(obj => !this._excludedObjects.includes(obj));
|
|
37
|
+
}
|
|
30
38
|
const plane = this.sketch.getPlane();
|
|
31
39
|
const currentPosition = plane.localToWorld(this.sketch.getPositionAt(this));
|
|
32
40
|
axis = new Axis(currentPosition, plane.zAxis.direction);
|
|
@@ -67,6 +75,14 @@ export class Rotate2D extends GeometrySceneObject {
|
|
|
67
75
|
return false;
|
|
68
76
|
}
|
|
69
77
|
}
|
|
78
|
+
if (this._excludedObjects.length !== other._excludedObjects.length) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
for (let i = 0; i < this._excludedObjects.length; i++) {
|
|
82
|
+
if (!this._excludedObjects[i].compareTo(other._excludedObjects[i])) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
70
86
|
if (!super.compareTo(other)) {
|
|
71
87
|
return false;
|
|
72
88
|
}
|
|
@@ -61,7 +61,8 @@ export class SelectSceneObject extends SceneObject {
|
|
|
61
61
|
const remappedConstraint = this.constraintObject
|
|
62
62
|
? (remap.get(this.constraintObject) || this.constraintObject)
|
|
63
63
|
: undefined;
|
|
64
|
-
|
|
64
|
+
const remappedFilters = this.filters.map(f => f.remap(remap));
|
|
65
|
+
return new SelectSceneObject(remappedFilters, remappedConstraint);
|
|
65
66
|
}
|
|
66
67
|
transform(matrix) {
|
|
67
68
|
const mirroredFilters = this.filters.map(f => f.transform(matrix));
|