fluidcad 0.0.26 → 0.0.28
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/scene-object.d.ts +45 -0
- package/lib/dist/common/scene-object.js +121 -0
- package/lib/dist/common/shape-factory.d.ts +1 -1
- package/lib/dist/common/shape-history-tracker.d.ts +35 -0
- package/lib/dist/common/shape-history-tracker.js +114 -0
- package/lib/dist/common/shape.js +7 -1
- package/lib/dist/common/shapes.d.ts +0 -1
- package/lib/dist/common/shapes.js +0 -1
- package/lib/dist/common/solid.js +5 -1
- package/lib/dist/core/extrude.d.ts +12 -13
- package/lib/dist/core/extrude.js +19 -1
- package/lib/dist/core/part.d.ts +2 -1
- package/lib/dist/core/part.js +4 -1
- package/lib/dist/core/sketch.d.ts +4 -3
- package/lib/dist/core/sketch.js +4 -1
- package/lib/dist/features/chamfer.js +12 -6
- package/lib/dist/features/extrude-base.d.ts +43 -1
- package/lib/dist/features/extrude-base.js +141 -36
- package/lib/dist/features/extrude-to-face.d.ts +1 -1
- package/lib/dist/features/extrude-to-face.js +42 -19
- package/lib/dist/features/extrude-two-distances.d.ts +1 -1
- package/lib/dist/features/extrude-two-distances.js +41 -15
- package/lib/dist/features/extrude.d.ts +1 -1
- package/lib/dist/features/extrude.js +75 -20
- package/lib/dist/features/fillet.js +3 -4
- package/lib/dist/features/fuse.js +14 -0
- package/lib/dist/features/infinite-extrude.d.ts +1 -0
- package/lib/dist/features/infinite-extrude.js +33 -4
- package/lib/dist/features/loft.js +18 -5
- package/lib/dist/features/mirror-shape.d.ts +1 -3
- package/lib/dist/features/mirror-shape.js +2 -1
- package/lib/dist/features/revolve.js +17 -4
- package/lib/dist/features/rotate.js +1 -0
- package/lib/dist/features/simple-extruder.js +5 -0
- package/lib/dist/features/sweep.js +13 -2
- package/lib/dist/features/translate.js +3 -1
- package/lib/dist/filters/face/face-filter.d.ts +12 -0
- package/lib/dist/filters/face/face-filter.js +21 -0
- package/lib/dist/filters/face/torus-filter.d.ts +19 -0
- package/lib/dist/filters/face/torus-filter.js +38 -0
- package/lib/dist/helpers/scene-helpers.d.ts +10 -2
- package/lib/dist/helpers/scene-helpers.js +278 -10
- package/lib/dist/index.d.ts +1 -0
- package/lib/dist/oc/boolean-ops.d.ts +32 -4
- package/lib/dist/oc/boolean-ops.js +122 -11
- package/lib/dist/oc/color-transfer.d.ts +37 -0
- package/lib/dist/oc/color-transfer.js +135 -0
- package/lib/dist/oc/extrude-ops.js +25 -3
- package/lib/dist/oc/face-ops.d.ts +0 -1
- package/lib/dist/oc/face-ops.js +0 -13
- package/lib/dist/oc/face-query.d.ts +2 -0
- package/lib/dist/oc/face-query.js +30 -0
- package/lib/dist/oc/fillet-ops.d.ts +5 -3
- package/lib/dist/oc/fillet-ops.js +107 -70
- package/lib/dist/oc/intersection.js +6 -3
- package/lib/dist/oc/mesh.d.ts +25 -2
- package/lib/dist/oc/mesh.js +112 -35
- package/lib/dist/oc/shape-ops.d.ts +25 -20
- package/lib/dist/oc/shape-ops.js +129 -113
- package/lib/dist/rendering/mesh-transform.js +17 -1
- package/lib/dist/rendering/render-solid.js +19 -6
- package/lib/dist/rendering/render-wire.js +2 -0
- package/lib/dist/rendering/render.d.ts +12 -2
- package/lib/dist/rendering/render.js +195 -220
- package/lib/dist/scene-manager.d.ts +2 -0
- package/lib/dist/scene-manager.js +4 -3
- package/lib/dist/tests/common/scene-object-history.test.d.ts +1 -0
- package/lib/dist/tests/common/scene-object-history.test.js +274 -0
- package/lib/dist/tests/common/shape-history-tracker.test.d.ts +1 -0
- package/lib/dist/tests/common/shape-history-tracker.test.js +110 -0
- package/lib/dist/tests/features/2d/project-regression.test.d.ts +1 -0
- package/lib/dist/tests/features/2d/project-regression.test.js +69 -0
- package/lib/dist/tests/features/2d/project-user-regression.test.d.ts +1 -0
- package/lib/dist/tests/features/2d/project-user-regression.test.js +37 -0
- package/lib/dist/tests/features/color-lineage.test.d.ts +1 -0
- package/lib/dist/tests/features/color-lineage.test.js +213 -0
- package/lib/dist/tests/features/cut-symmetric-through-all.test.d.ts +1 -0
- package/lib/dist/tests/features/cut-symmetric-through-all.test.js +32 -0
- package/lib/dist/tests/features/extrude-history.test.d.ts +1 -0
- package/lib/dist/tests/features/extrude-history.test.js +248 -0
- package/lib/dist/tests/features/extrude.test.js +71 -0
- package/lib/dist/tests/features/fillet2d.test.js +16 -1
- package/lib/dist/tests/features/peer-ops-history.test.d.ts +1 -0
- package/lib/dist/tests/features/peer-ops-history.test.js +119 -0
- package/lib/dist/tests/features/select.test.js +50 -0
- package/lib/dist/tests/features/subtract.test.js +21 -1
- package/lib/dist/tests/setup.js +3 -2
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/ui/dist/assets/{index-BeLxRMCv.js → index-BrW_x4uc.js} +37 -37
- package/ui/dist/index.html +1 -1
- package/lib/dist/common/solid-face.d.ts +0 -9
- package/lib/dist/common/solid-face.js +0 -22
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { getOC } from "./init.js";
|
|
2
|
+
import { Explorer } from "./explorer.js";
|
|
3
|
+
import { ShapeOps } from "./shape-ops.js";
|
|
4
|
+
import { Face } from "../common/face.js";
|
|
5
|
+
/**
|
|
6
|
+
* Walk each source shape's `colorMap`, find where each colored face ended up in
|
|
7
|
+
* the result shapes via `maker.Modified()` (falling back to the unchanged face
|
|
8
|
+
* if `!IsDeleted`), and apply the color to whichever result shape now owns it.
|
|
9
|
+
*
|
|
10
|
+
* Works for any `BRepBuilderAPI_MakeShape`-derived maker — fuse, cut, fillet,
|
|
11
|
+
* chamfer, transform, etc.
|
|
12
|
+
*/
|
|
13
|
+
export class ColorTransfer {
|
|
14
|
+
static applyThroughMaker(sources, results, maker) {
|
|
15
|
+
const oc = getOC();
|
|
16
|
+
const FACE = oc.TopAbs_ShapeEnum.TopAbs_FACE;
|
|
17
|
+
for (const source of sources) {
|
|
18
|
+
if (!source.hasColors()) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
for (const entry of source.colorMap) {
|
|
22
|
+
const modifiedRaws = ShapeOps.shapeListToArray(maker.Modified(entry.shape))
|
|
23
|
+
.filter(s => s.ShapeType() === FACE);
|
|
24
|
+
let targets;
|
|
25
|
+
if (modifiedRaws.length > 0) {
|
|
26
|
+
targets = modifiedRaws;
|
|
27
|
+
}
|
|
28
|
+
else if (!maker.IsDeleted(entry.shape)) {
|
|
29
|
+
targets = [entry.shape];
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
for (const target of targets) {
|
|
35
|
+
for (const result of results) {
|
|
36
|
+
const faces = Explorer.findShapes(result.getShape(), FACE);
|
|
37
|
+
if (faces.some(f => f.IsSame(target))) {
|
|
38
|
+
result.setColor(target, entry.color);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Color bleed pass: spreads colors to result faces that came from new
|
|
48
|
+
* geometry (tool inputs, generated faces, or just brand-new) by walking
|
|
49
|
+
* face-edge adjacency in each result solid.
|
|
50
|
+
*
|
|
51
|
+
* Faces that came from `sceneSources` (whether modified or unchanged) are
|
|
52
|
+
* NOT bled — those represent existing geometry whose color state the user
|
|
53
|
+
* explicitly chose. Faces NOT from any sceneSource are eligible: this
|
|
54
|
+
* covers tool extrusions, fillet/chamfer-generated surfaces, and cut
|
|
55
|
+
* section faces.
|
|
56
|
+
*
|
|
57
|
+
* Iterates until stable so newly-bled faces can spread color further.
|
|
58
|
+
* Call AFTER `applyThroughMaker` so the colored seeds are in place.
|
|
59
|
+
*/
|
|
60
|
+
static applyBleeding(sceneSources, results, maker) {
|
|
61
|
+
const oc = getOC();
|
|
62
|
+
const FACE = oc.TopAbs_ShapeEnum.TopAbs_FACE;
|
|
63
|
+
const EDGE = oc.TopAbs_ShapeEnum.TopAbs_EDGE;
|
|
64
|
+
const protectedFaces = new oc.TopTools_MapOfShape();
|
|
65
|
+
for (const scene of sceneSources) {
|
|
66
|
+
for (const inputFace of Explorer.findShapes(scene.getShape(), FACE)) {
|
|
67
|
+
const modified = ShapeOps.shapeListToArray(maker.Modified(inputFace))
|
|
68
|
+
.filter(s => s.ShapeType() === FACE);
|
|
69
|
+
if (modified.length > 0) {
|
|
70
|
+
for (const r of modified) {
|
|
71
|
+
protectedFaces.Add(r);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (!maker.IsDeleted(inputFace)) {
|
|
75
|
+
protectedFaces.Add(inputFace);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const result of results) {
|
|
80
|
+
const allFaces = Explorer.findShapes(result.getShape(), FACE);
|
|
81
|
+
// Cache edges per face — repeated `findShapes` is expensive.
|
|
82
|
+
const faceEdges = allFaces.map(f => Explorer.findShapes(f, EDGE));
|
|
83
|
+
let changed = true;
|
|
84
|
+
while (changed) {
|
|
85
|
+
changed = false;
|
|
86
|
+
for (let i = 0; i < allFaces.length; i++) {
|
|
87
|
+
const face = allFaces[i];
|
|
88
|
+
if (protectedFaces.Contains(face)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (result.getColor(face)) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const myEdges = faceEdges[i];
|
|
95
|
+
for (let j = 0; j < allFaces.length; j++) {
|
|
96
|
+
if (i === j) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const otherEdges = faceEdges[j];
|
|
100
|
+
const adjacent = myEdges.some(me => otherEdges.some(oe => me.IsSame(oe)));
|
|
101
|
+
if (!adjacent) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const otherColor = result.getColor(allFaces[j]);
|
|
105
|
+
if (otherColor) {
|
|
106
|
+
result.setColor(face, otherColor);
|
|
107
|
+
changed = true;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
protectedFaces.delete();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Transfer colors from a pre-clean source shape through a `cleanShapeWithLineage`
|
|
118
|
+
* cleanup's `BRepTools_History` onto the post-clean result. Use this when an
|
|
119
|
+
* op is chained as `maker → cleanShape` — first apply `applyThroughMaker` to
|
|
120
|
+
* move colors from the original source onto the pre-clean result, then call
|
|
121
|
+
* this to chain them through the cleanup's UnifySameDomain history.
|
|
122
|
+
*/
|
|
123
|
+
static applyThroughCleanup(source, cleanup) {
|
|
124
|
+
for (const entry of source.colorMap) {
|
|
125
|
+
const preFace = Face.fromTopoDSFace(Explorer.toFace(entry.shape));
|
|
126
|
+
const postFaces = cleanup.remapFace(preFace);
|
|
127
|
+
if (!postFaces) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
for (const postFace of postFaces) {
|
|
131
|
+
cleanup.shape.setColor(postFace.getShape(), entry.color);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -59,11 +59,33 @@ export class ExtrudeOps {
|
|
|
59
59
|
static makeRevol(shape, axis, angle) {
|
|
60
60
|
const oc = getOC();
|
|
61
61
|
const [ax1, disposeAx1] = Convert.toGpAx1(axis);
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
let revol;
|
|
63
|
+
try {
|
|
64
|
+
revol = new oc.BRepPrimAPI_MakeRevol(shape.getShape(), ax1, angle, true);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
disposeAx1();
|
|
68
|
+
throw new Error("Revolution failed");
|
|
69
|
+
}
|
|
70
|
+
if (!revol.IsDone()) {
|
|
71
|
+
revol.delete();
|
|
72
|
+
disposeAx1();
|
|
73
|
+
throw new Error("Revolution failed");
|
|
74
|
+
}
|
|
75
|
+
const rawResult = revol.Shape();
|
|
64
76
|
revol.delete();
|
|
65
77
|
disposeAx1();
|
|
66
|
-
|
|
78
|
+
// A profile face whose normal points "backwards" relative to the axis
|
|
79
|
+
// produces a closed solid with inverted shell orientation. Volume can
|
|
80
|
+
// still be positive but downstream boolean ops fail. OrientClosedSolid
|
|
81
|
+
// flips the shell to outward-facing when needed.
|
|
82
|
+
let oriented = rawResult;
|
|
83
|
+
if (Explorer.isSolid(rawResult)) {
|
|
84
|
+
const solid = Explorer.toSolid(rawResult);
|
|
85
|
+
oc.BRepLib.OrientClosedSolid(solid);
|
|
86
|
+
oriented = solid;
|
|
87
|
+
}
|
|
88
|
+
const clean = ShapeOps.cleanShapeRaw(oriented);
|
|
67
89
|
return ShapeFactory.fromShape(clean);
|
|
68
90
|
}
|
|
69
91
|
static applyDraftOnSideFaces(solid, firstFace, lastFace, plane, angle) {
|
|
@@ -20,7 +20,6 @@ export declare class FaceOps {
|
|
|
20
20
|
static fixFaceOrientation(face: Face | TopoDS_Face): Face;
|
|
21
21
|
static makeFaceWithHoles(outerWire: Wire, holes: Wire[]): Face;
|
|
22
22
|
static isPointInsideFace(point: Point, face: Face | TopoDS_Face): boolean;
|
|
23
|
-
static getFreeBoundsWire(compound: any): TopoDS_Wire | null;
|
|
24
23
|
static makeFaceFromPlane2(plane: gp_Pln): TopoDS_Face;
|
|
25
24
|
static makeFaceFromPlane(plane: gp_Pln): TopoDS_Face;
|
|
26
25
|
static makeFaceFromCylinder(cylinder: gp_Cylinder): TopoDS_Face;
|
package/lib/dist/oc/face-ops.js
CHANGED
|
@@ -197,19 +197,6 @@ export class FaceOps {
|
|
|
197
197
|
disposePnt();
|
|
198
198
|
return isInside;
|
|
199
199
|
}
|
|
200
|
-
static getFreeBoundsWire(compound) {
|
|
201
|
-
const oc = getOC();
|
|
202
|
-
const freeBounds = new oc.ShapeAnalysis_FreeBounds(compound, oc.Precision.Confusion(), true, true);
|
|
203
|
-
const closedWiresCompound = freeBounds.GetClosedWires();
|
|
204
|
-
const explorer = new oc.TopExp_Explorer(closedWiresCompound, oc.TopAbs_ShapeEnum.TopAbs_WIRE, oc.TopAbs_ShapeEnum.TopAbs_SHAPE);
|
|
205
|
-
let result = null;
|
|
206
|
-
if (explorer.More()) {
|
|
207
|
-
result = oc.TopoDS.Wire(explorer.Current());
|
|
208
|
-
}
|
|
209
|
-
explorer.delete();
|
|
210
|
-
freeBounds.delete();
|
|
211
|
-
return result;
|
|
212
|
-
}
|
|
213
200
|
static makeFaceFromPlane2(plane) {
|
|
214
201
|
const oc = getOC();
|
|
215
202
|
const faceMaker = new oc.BRepBuilderAPI_MakeFace(plane, -1000, 1000, -1000, 1000);
|
|
@@ -8,6 +8,7 @@ export declare class FaceQuery {
|
|
|
8
8
|
static isConeFace(face: Shape): boolean;
|
|
9
9
|
static isCylinderFace(face: Shape, diameter?: number): boolean;
|
|
10
10
|
static isCylinderCurveFace(face: Shape, diameter?: number): boolean;
|
|
11
|
+
static isTorusFace(face: Shape, majorRadius?: number, minorRadius?: number): boolean;
|
|
11
12
|
static isFaceOnPlane(face: Shape, plane: Plane): boolean;
|
|
12
13
|
static doesFaceIntersectPlane(face: Shape, plane: Plane): boolean;
|
|
13
14
|
static isFaceParallelToPlane(face: Shape, plane: Plane): boolean;
|
|
@@ -23,6 +24,7 @@ export declare class FaceQuery {
|
|
|
23
24
|
static isConeFaceRaw(face: TopoDS_Shape): boolean;
|
|
24
25
|
static isCylinderFaceRaw(face: TopoDS_Shape, diameter?: number): boolean;
|
|
25
26
|
static isCylinderCurveFaceRaw(face: TopoDS_Shape, diameter?: number): boolean;
|
|
27
|
+
static isTorusFaceRaw(face: TopoDS_Shape, majorRadius?: number, minorRadius?: number): boolean;
|
|
26
28
|
static isFaceOnPlaneRaw(face: TopoDS_Shape, plane: gp_Pln): boolean;
|
|
27
29
|
static isFaceParallelToPlaneRaw(face: TopoDS_Shape, plane: gp_Pln): boolean;
|
|
28
30
|
static isPlanarFaceRaw(face: TopoDS_Shape): boolean;
|
|
@@ -17,6 +17,9 @@ export class FaceQuery {
|
|
|
17
17
|
static isCylinderCurveFace(face, diameter) {
|
|
18
18
|
return FaceQuery.isCylinderCurveFaceRaw(face.getShape(), diameter);
|
|
19
19
|
}
|
|
20
|
+
static isTorusFace(face, majorRadius, minorRadius) {
|
|
21
|
+
return FaceQuery.isTorusFaceRaw(face.getShape(), majorRadius, minorRadius);
|
|
22
|
+
}
|
|
20
23
|
static isFaceOnPlane(face, plane) {
|
|
21
24
|
const [gpPln, dispose] = Convert.toGpPln(plane);
|
|
22
25
|
const result = FaceQuery.isFaceOnPlaneRaw(face.getShape(), gpPln);
|
|
@@ -198,6 +201,33 @@ export class FaceQuery {
|
|
|
198
201
|
}
|
|
199
202
|
return false;
|
|
200
203
|
}
|
|
204
|
+
static isTorusFaceRaw(face, majorRadius, minorRadius) {
|
|
205
|
+
const oc = getOC();
|
|
206
|
+
const ocFace = oc.TopoDS.Face(face);
|
|
207
|
+
const faceAdaptor = new oc.BRepAdaptor_Surface(ocFace, true);
|
|
208
|
+
const type = faceAdaptor.GetType();
|
|
209
|
+
if (type !== oc.GeomAbs_SurfaceType.GeomAbs_Torus) {
|
|
210
|
+
faceAdaptor.delete();
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
if (majorRadius === undefined && minorRadius === undefined) {
|
|
214
|
+
faceAdaptor.delete();
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
const torus = faceAdaptor.Torus();
|
|
218
|
+
const actualMajor = torus.MajorRadius();
|
|
219
|
+
const actualMinor = torus.MinorRadius();
|
|
220
|
+
torus.delete();
|
|
221
|
+
faceAdaptor.delete();
|
|
222
|
+
const tol = oc.Precision.Confusion();
|
|
223
|
+
if (majorRadius !== undefined && Math.abs(actualMajor - majorRadius) > tol) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
if (minorRadius !== undefined && Math.abs(actualMinor - minorRadius) > tol) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
201
231
|
static isFaceOnPlaneRaw(face, plane) {
|
|
202
232
|
const oc = getOC();
|
|
203
233
|
return FaceOps.faceOnPlane(oc.TopoDS.Face(face), plane);
|
|
@@ -2,12 +2,14 @@ import type { gp_Pln, TopoDS_Wire } from "occjs-wrapper";
|
|
|
2
2
|
import { Shape } from "../common/shape.js";
|
|
3
3
|
import { Edge } from "../common/edge.js";
|
|
4
4
|
import { Face } from "../common/face.js";
|
|
5
|
+
import { Solid } from "../common/solid.js";
|
|
5
6
|
import { Wire } from "../common/wire.js";
|
|
6
7
|
import { Plane } from "../math/plane.js";
|
|
7
8
|
export declare class FilletOps {
|
|
8
|
-
static makeFillet(solid: Shape, edges: Edge[], radius: number):
|
|
9
|
-
static makeChamfer(solid: Shape, edges: Edge[], distance: number):
|
|
10
|
-
static
|
|
9
|
+
static makeFillet(solid: Shape, edges: Edge[], radius: number): Solid[];
|
|
10
|
+
static makeChamfer(solid: Shape, edges: Edge[], distance: number): Solid[];
|
|
11
|
+
private static wrapResultSolids;
|
|
12
|
+
static makeChamferTwoDistances(solid: Shape, edges: Edge[], distance1: number, distance2: number, faces: Face[], isAngle?: boolean): Solid[];
|
|
11
13
|
static fillet2d(shape: Wire | Edge, plane: Plane, radius: number): Wire;
|
|
12
14
|
static fillet2dRaw(wire: TopoDS_Wire, plane: gp_Pln, radius: number): TopoDS_Wire;
|
|
13
15
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { getOC } from "./init.js";
|
|
2
2
|
import { Convert } from "./convert.js";
|
|
3
|
+
import { Solid } from "../common/solid.js";
|
|
3
4
|
import { Wire } from "../common/wire.js";
|
|
4
|
-
import { ShapeFactory } from "../common/shape-factory.js";
|
|
5
5
|
import { WireOps } from "./wire-ops.js";
|
|
6
6
|
import { rad } from "../helpers/math-helpers.js";
|
|
7
|
+
import { ColorTransfer } from "./color-transfer.js";
|
|
8
|
+
import { Explorer } from "./explorer.js";
|
|
7
9
|
export class FilletOps {
|
|
8
10
|
static makeFillet(solid, edges, radius) {
|
|
9
11
|
const oc = getOC();
|
|
@@ -19,8 +21,11 @@ export class FilletOps {
|
|
|
19
21
|
throw new Error("Failed to create fillet.");
|
|
20
22
|
}
|
|
21
23
|
const result = maker.Shape();
|
|
24
|
+
const solids = FilletOps.wrapResultSolids(result);
|
|
25
|
+
ColorTransfer.applyThroughMaker([solid], solids, maker);
|
|
26
|
+
ColorTransfer.applyBleeding([solid], solids, maker);
|
|
22
27
|
maker.delete();
|
|
23
|
-
return
|
|
28
|
+
return solids;
|
|
24
29
|
}
|
|
25
30
|
static makeChamfer(solid, edges, distance) {
|
|
26
31
|
const oc = getOC();
|
|
@@ -36,8 +41,19 @@ export class FilletOps {
|
|
|
36
41
|
throw new Error("Failed to create chamfer.");
|
|
37
42
|
}
|
|
38
43
|
const result = maker.Shape();
|
|
44
|
+
const solids = FilletOps.wrapResultSolids(result);
|
|
45
|
+
ColorTransfer.applyThroughMaker([solid], solids, maker);
|
|
46
|
+
ColorTransfer.applyBleeding([solid], solids, maker);
|
|
39
47
|
maker.delete();
|
|
40
|
-
return
|
|
48
|
+
return solids;
|
|
49
|
+
}
|
|
50
|
+
static wrapResultSolids(result) {
|
|
51
|
+
const oc = getOC();
|
|
52
|
+
if (Explorer.isSolid(result)) {
|
|
53
|
+
return [Solid.fromTopoDSSolid(Explorer.toSolid(result))];
|
|
54
|
+
}
|
|
55
|
+
const solidRaws = Explorer.findShapes(result, oc.TopAbs_ShapeEnum.TopAbs_SOLID);
|
|
56
|
+
return solidRaws.map(r => Solid.fromTopoDSSolid(Explorer.toSolid(r)));
|
|
41
57
|
}
|
|
42
58
|
static makeChamferTwoDistances(solid, edges, distance1, distance2, faces, isAngle = false) {
|
|
43
59
|
const oc = getOC();
|
|
@@ -62,8 +78,11 @@ export class FilletOps {
|
|
|
62
78
|
throw new Error("Failed to create chamfer.");
|
|
63
79
|
}
|
|
64
80
|
const result = maker.Shape();
|
|
81
|
+
const solids = FilletOps.wrapResultSolids(result);
|
|
82
|
+
ColorTransfer.applyThroughMaker([solid], solids, maker);
|
|
83
|
+
ColorTransfer.applyBleeding([solid], solids, maker);
|
|
65
84
|
maker.delete();
|
|
66
|
-
return
|
|
85
|
+
return solids;
|
|
67
86
|
}
|
|
68
87
|
static fillet2d(shape, plane, radius) {
|
|
69
88
|
const wire = shape instanceof Wire ? shape : WireOps.makeWireFromEdges([shape]);
|
|
@@ -74,80 +93,98 @@ export class FilletOps {
|
|
|
74
93
|
}
|
|
75
94
|
static fillet2dRaw(wire, plane, radius) {
|
|
76
95
|
const oc = getOC();
|
|
77
|
-
let currentWire = wire;
|
|
78
|
-
let cornerIndex = 0;
|
|
79
96
|
const isClosed = wire.Closed();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
const ownedEdges = [];
|
|
98
|
+
// Extract edges in wire traversal order and canonicalize: for each REVERSED edge,
|
|
99
|
+
// build a new FORWARD edge whose natural parameterization matches the wire traversal.
|
|
100
|
+
// ChFi2d_FilletAPI returns modified edges whose natural direction matches the input's
|
|
101
|
+
// natural direction, so aligning natural direction with wire traversal makes the
|
|
102
|
+
// modEdges directly usable when rebuilding the final wire.
|
|
103
|
+
const wireEdges = [];
|
|
104
|
+
{
|
|
105
|
+
const explorer = new oc.BRepTools_WireExplorer(wire);
|
|
106
|
+
while (explorer.More()) {
|
|
107
|
+
const raw = oc.TopoDS.Edge(explorer.Current());
|
|
108
|
+
const isReversed = raw.Orientation().value === oc.TopAbs_Orientation.TopAbs_REVERSED.value;
|
|
109
|
+
if (!isReversed) {
|
|
110
|
+
wireEdges.push(raw);
|
|
111
|
+
ownedEdges.push(raw);
|
|
93
112
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
else {
|
|
114
|
+
const adaptor = new oc.BRepAdaptor_Curve(raw);
|
|
115
|
+
const edgeFirst = adaptor.FirstParameter();
|
|
116
|
+
const edgeLast = adaptor.LastParameter();
|
|
117
|
+
adaptor.delete();
|
|
118
|
+
const curveHandle = oc.BRep_Tool.Curve(raw, 0, 1);
|
|
119
|
+
if (!curveHandle || curveHandle.IsNull()) {
|
|
120
|
+
raw.delete();
|
|
121
|
+
explorer.delete();
|
|
122
|
+
ownedEdges.forEach(e => e.delete());
|
|
123
|
+
throw new Error("fillet2d: edge has no 3D curve");
|
|
124
|
+
}
|
|
125
|
+
const curve = curveHandle.get();
|
|
126
|
+
const reversedHandle = curve.Reversed();
|
|
127
|
+
const newFirst = curve.ReversedParameter(edgeLast);
|
|
128
|
+
const newLast = curve.ReversedParameter(edgeFirst);
|
|
129
|
+
const maker = new oc.BRepBuilderAPI_MakeEdge(reversedHandle, newFirst, newLast);
|
|
130
|
+
const newEdge = oc.TopoDS.Edge(maker.Edge());
|
|
131
|
+
maker.delete();
|
|
132
|
+
reversedHandle.delete();
|
|
133
|
+
curveHandle.delete();
|
|
134
|
+
raw.delete();
|
|
135
|
+
wireEdges.push(newEdge);
|
|
136
|
+
ownedEdges.push(newEdge);
|
|
110
137
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
138
|
+
explorer.Next();
|
|
139
|
+
}
|
|
140
|
+
explorer.delete();
|
|
141
|
+
}
|
|
142
|
+
const currentEdges = wireEdges.slice();
|
|
143
|
+
const filletArcs = new Map();
|
|
144
|
+
const maxCorners = isClosed ? currentEdges.length : currentEdges.length - 1;
|
|
145
|
+
for (let cornerIndex = 0; cornerIndex < maxCorners; cornerIndex++) {
|
|
146
|
+
const nextIndex = (cornerIndex + 1) % currentEdges.length;
|
|
147
|
+
const edge1 = currentEdges[cornerIndex];
|
|
148
|
+
const edge2 = currentEdges[nextIndex];
|
|
149
|
+
const sharedVertex = oc.TopExp.LastVertex(edge1, true);
|
|
150
|
+
const sharedPoint = oc.BRep_Tool.Pnt(sharedVertex);
|
|
151
|
+
sharedVertex.delete();
|
|
152
|
+
const filletAPI = new oc.ChFi2d_FilletAPI(edge1, edge2, plane);
|
|
153
|
+
const success = filletAPI.Perform(radius);
|
|
154
|
+
if (!success || filletAPI.NbResults(sharedPoint) === 0) {
|
|
114
155
|
sharedPoint.delete();
|
|
115
156
|
filletAPI.delete();
|
|
116
|
-
|
|
117
|
-
pairWireBuilder.delete();
|
|
118
|
-
const newWireBuilder = new oc.BRepBuilderAPI_MakeWire();
|
|
119
|
-
const nextIndex = (cornerIndex + 1) % edges.length;
|
|
120
|
-
for (let i = 0; i < edges.length; i++) {
|
|
121
|
-
if (i === cornerIndex) {
|
|
122
|
-
newWireBuilder.Add(modEdge1);
|
|
123
|
-
newWireBuilder.Add(filletEdge);
|
|
124
|
-
}
|
|
125
|
-
else if (i === nextIndex) {
|
|
126
|
-
newWireBuilder.Add(modEdge2);
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
newWireBuilder.Add(edges[i]);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const prevWire = currentWire;
|
|
133
|
-
currentWire = newWireBuilder.Wire();
|
|
134
|
-
newWireBuilder.delete();
|
|
135
|
-
if (prevWire !== wire) {
|
|
136
|
-
prevWire.delete();
|
|
137
|
-
}
|
|
138
|
-
modEdge1.delete();
|
|
139
|
-
modEdge2.delete();
|
|
140
|
-
filletEdge.delete();
|
|
141
|
-
edges.forEach(e => e.delete());
|
|
142
|
-
cornerIndex += 2;
|
|
157
|
+
continue;
|
|
143
158
|
}
|
|
144
|
-
|
|
159
|
+
const modEdge1 = new oc.TopoDS_Edge();
|
|
160
|
+
const modEdge2 = new oc.TopoDS_Edge();
|
|
161
|
+
const filletEdge = filletAPI.Result(sharedPoint, modEdge1, modEdge2, -1);
|
|
162
|
+
sharedPoint.delete();
|
|
163
|
+
filletAPI.delete();
|
|
164
|
+
currentEdges[cornerIndex] = modEdge1;
|
|
165
|
+
currentEdges[nextIndex] = modEdge2;
|
|
166
|
+
filletArcs.set(cornerIndex, filletEdge);
|
|
167
|
+
ownedEdges.push(modEdge1, modEdge2, filletEdge);
|
|
145
168
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
169
|
+
const edgeList = new oc.TopTools_ListOfShape();
|
|
170
|
+
for (let i = 0; i < currentEdges.length; i++) {
|
|
171
|
+
edgeList.Append(oc.TopoDS.Edge(currentEdges[i]));
|
|
172
|
+
const arc = filletArcs.get(i);
|
|
173
|
+
if (arc) {
|
|
174
|
+
edgeList.Append(oc.TopoDS.Edge(arc));
|
|
149
175
|
}
|
|
150
|
-
throw e;
|
|
151
176
|
}
|
|
177
|
+
const wireBuilder = new oc.BRepBuilderAPI_MakeWire();
|
|
178
|
+
wireBuilder.Add(edgeList);
|
|
179
|
+
edgeList.delete();
|
|
180
|
+
if (!wireBuilder.IsDone()) {
|
|
181
|
+
wireBuilder.delete();
|
|
182
|
+
ownedEdges.forEach(e => e.delete());
|
|
183
|
+
throw new Error("fillet2d: failed to build filleted wire");
|
|
184
|
+
}
|
|
185
|
+
const result = wireBuilder.Wire();
|
|
186
|
+
wireBuilder.delete();
|
|
187
|
+
ownedEdges.forEach(e => e.delete());
|
|
188
|
+
return result;
|
|
152
189
|
}
|
|
153
190
|
}
|
|
@@ -27,9 +27,12 @@ export class ProjectionOps {
|
|
|
27
27
|
// handle.delete();
|
|
28
28
|
}
|
|
29
29
|
if (wirePlane && wirePlane.isParallelTo(targetPlane)) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
// Translation along the target normal that moves a point from the wire
|
|
31
|
+
// plane onto the target plane. Use the *signed* distance — `distanceToPlane`
|
|
32
|
+
// is abs-valued and picks the wrong direction when the wire sits on the
|
|
33
|
+
// negative side of the target normal.
|
|
34
|
+
const signedDist = targetPlane.signedDistanceToPoint(wirePlane.origin);
|
|
35
|
+
const translation = targetPlane.normal.multiply(-signedDist);
|
|
33
36
|
const matrix = Matrix4.fromTranslation(translation.x, translation.y, translation.z);
|
|
34
37
|
const transformed = ShapeOps.transform(wire, matrix);
|
|
35
38
|
return [transformed];
|
package/lib/dist/oc/mesh.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TopoDS_Face, TopoDS_Shape } from "occjs-wrapper";
|
|
1
|
+
import type { TopoDS_Edge, TopoDS_Face, TopoDS_Shape } from "occjs-wrapper";
|
|
2
2
|
import { Face } from "../common/face.js";
|
|
3
3
|
import { Shape } from "../common/shape.js";
|
|
4
4
|
export interface MeshData {
|
|
@@ -7,11 +7,34 @@ export interface MeshData {
|
|
|
7
7
|
indices: number[];
|
|
8
8
|
count?: number;
|
|
9
9
|
}
|
|
10
|
+
export interface EnsureTriangulatedOptions {
|
|
11
|
+
linDefl?: number;
|
|
12
|
+
angDefl?: number;
|
|
13
|
+
parallel?: boolean;
|
|
14
|
+
relative?: boolean;
|
|
15
|
+
checkFreeEdges?: boolean;
|
|
16
|
+
}
|
|
10
17
|
export declare class Mesh {
|
|
11
18
|
static triangulateFace(face: Face, vertexOffset?: number): MeshData | null;
|
|
12
19
|
static discretizeEdge(edge: Shape): MeshData;
|
|
13
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Triangulates `shape` only if it doesn't already have an up-to-date
|
|
22
|
+
* triangulation at the requested deflection. Returns true when a fresh
|
|
23
|
+
* mesh was built, false when the stored one was reused.
|
|
24
|
+
*/
|
|
25
|
+
static ensureTriangulated(shape: TopoDS_Shape, opts?: EnsureTriangulatedOptions): boolean;
|
|
14
26
|
static triangulateFaceRaw(face: TopoDS_Face, vertexOffset?: number): MeshData | null;
|
|
15
27
|
static extractFaceTriangulationRaw(face: TopoDS_Face, vertexOffset?: number): MeshData | null;
|
|
28
|
+
/**
|
|
29
|
+
* Reads the polyline stored for `edge` as a polygon-on-triangulation of
|
|
30
|
+
* `face`. Node indices point into the face's triangulation, so the edge
|
|
31
|
+
* samples coincide exactly with the face mesh vertices (watertight).
|
|
32
|
+
*/
|
|
33
|
+
static discretizeEdgeOnFace(edge: TopoDS_Edge, face: TopoDS_Face): MeshData | null;
|
|
34
|
+
/**
|
|
35
|
+
* Reads the stored 3D polygon for a free edge (one not attached to a
|
|
36
|
+
* meshed face). Caller must have already run `ensureTriangulated` on the
|
|
37
|
+
* edge or its parent wire.
|
|
38
|
+
*/
|
|
16
39
|
static discretizeEdgeRaw(edge: TopoDS_Shape): MeshData;
|
|
17
40
|
}
|