fluidcad 0.0.26 → 0.0.27
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/shape-factory.d.ts +1 -1
- package/lib/dist/common/shapes.d.ts +0 -1
- package/lib/dist/common/shapes.js +0 -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/extrude-base.d.ts +7 -1
- package/lib/dist/features/extrude-base.js +36 -3
- package/lib/dist/features/extrude-to-face.d.ts +1 -1
- package/lib/dist/features/extrude-to-face.js +29 -17
- package/lib/dist/features/extrude-two-distances.d.ts +1 -1
- package/lib/dist/features/extrude-two-distances.js +23 -12
- package/lib/dist/features/extrude.d.ts +1 -1
- package/lib/dist/features/extrude.js +47 -15
- 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 +4 -2
- package/lib/dist/features/rotate.js +1 -0
- package/lib/dist/features/simple-extruder.js +5 -0
- 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 +3 -1
- package/lib/dist/helpers/scene-helpers.js +6 -3
- package/lib/dist/index.d.ts +1 -0
- package/lib/dist/oc/boolean-ops.d.ts +5 -3
- package/lib/dist/oc/boolean-ops.js +15 -2
- 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.js +84 -66
- 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 +1 -21
- package/lib/dist/oc/shape-ops.js +0 -103
- 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/features/extrude.test.js +71 -0
- package/lib/dist/tests/features/fillet2d.test.js +16 -1
- package/lib/dist/tests/features/select.test.js +50 -0
- 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-55iqIwnj.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
|
@@ -47,6 +47,7 @@ export class MirrorShape extends SceneObject {
|
|
|
47
47
|
for (const shape of shapes) {
|
|
48
48
|
const matrix = Matrix4.mirrorPlane(plane.normal, plane.origin);
|
|
49
49
|
const transformed = ShapeOps.transform(shape, matrix);
|
|
50
|
+
transformed.setMeshSource(shape, matrix);
|
|
50
51
|
transformedShapes.push(transformed);
|
|
51
52
|
}
|
|
52
53
|
}
|
|
@@ -90,7 +91,7 @@ export class MirrorShape extends SceneObject {
|
|
|
90
91
|
}
|
|
91
92
|
serialize() {
|
|
92
93
|
return {
|
|
93
|
-
|
|
94
|
+
// plane: this.plane,
|
|
94
95
|
};
|
|
95
96
|
}
|
|
96
97
|
}
|
|
@@ -9,6 +9,7 @@ import { ExtrudeBase } from "./extrude-base.js";
|
|
|
9
9
|
import { BooleanOps } from "../oc/boolean-ops.js";
|
|
10
10
|
import { FaceOps } from "../oc/face-ops.js";
|
|
11
11
|
import { ThinFaceMaker } from "../oc/thin-face-maker.js";
|
|
12
|
+
import { Matrix4 } from "../math/matrix4.js";
|
|
12
13
|
export class Revolve extends ExtrudeBase {
|
|
13
14
|
axis;
|
|
14
15
|
angle;
|
|
@@ -45,8 +46,9 @@ export class Revolve extends ExtrudeBase {
|
|
|
45
46
|
const solid = ExtrudeOps.makeRevol(face, axis, rad(this.angle));
|
|
46
47
|
let resultSolid;
|
|
47
48
|
if (this._symmetric) {
|
|
48
|
-
const
|
|
49
|
-
|
|
49
|
+
const matrix = Matrix4.fromRotationAroundAxis(axis.origin, axis.direction, -rad(this.angle) / 2);
|
|
50
|
+
const rotated = ShapeOps.transform(solid, matrix);
|
|
51
|
+
resultSolid = Solid.fromTopoDSSolid(Explorer.toSolid(rotated.getShape()));
|
|
50
52
|
}
|
|
51
53
|
else {
|
|
52
54
|
resultSolid = Solid.fromTopoDSSolid(Explorer.toSolid(solid.getShape()));
|
|
@@ -41,6 +41,7 @@ export class Rotate extends SceneObject {
|
|
|
41
41
|
const shapes = obj.getShapes();
|
|
42
42
|
for (const shape of shapes) {
|
|
43
43
|
const transformed = ShapeOps.transform(shape, matrix);
|
|
44
|
+
transformed.setMeshSource(shape, matrix);
|
|
44
45
|
this.addShape(transformed);
|
|
45
46
|
if (!this.copy) {
|
|
46
47
|
obj.removeShape(shape, this);
|
|
@@ -42,9 +42,14 @@ export class Extruder {
|
|
|
42
42
|
let lastFaces = [];
|
|
43
43
|
let sideFaces = [];
|
|
44
44
|
let internalFaces = [];
|
|
45
|
+
console.log("Fusing faces before extrusion...", this.faces.length);
|
|
46
|
+
const tFuseFaces = performance.now();
|
|
45
47
|
const fusedFaces = BooleanOps.fuseFaces(this.faces);
|
|
48
|
+
console.log(`[perf] Extruder.fuseFaces (in=${this.faces.length}, out=${fusedFaces.result.length}): ${(performance.now() - tFuseFaces).toFixed(1)} ms`);
|
|
46
49
|
for (const face of fusedFaces.result) {
|
|
50
|
+
const time = performance.now();
|
|
47
51
|
let { solid, firstFace, lastFace } = ExtrudeOps.makePrismFromVec(face, vec);
|
|
52
|
+
console.log(`[perf] Extruder.makePrismFromVec: ${(performance.now() - time).toFixed(1)} ms`);
|
|
48
53
|
if (this.draft) {
|
|
49
54
|
const draftResult = this.applyDraft(solid, firstFace, lastFace, this.plane);
|
|
50
55
|
solid = draftResult.solid;
|
|
@@ -23,7 +23,9 @@ export class Translate extends SceneObject {
|
|
|
23
23
|
continue;
|
|
24
24
|
}
|
|
25
25
|
const amount = this.amount.asPoint();
|
|
26
|
-
|
|
26
|
+
const matrix = Matrix4.fromTranslation(amount.x, amount.y, amount.z);
|
|
27
|
+
const transformed = ShapeOps.transform(shape, matrix);
|
|
28
|
+
transformed.setMeshSource(shape, matrix);
|
|
27
29
|
this.addShape(transformed);
|
|
28
30
|
if (!this.copy) {
|
|
29
31
|
obj.removeShape(shape, this);
|
|
@@ -58,6 +58,18 @@ export declare class FaceFilterBuilder extends FilterBuilderBase<Face> {
|
|
|
58
58
|
* @param plane - The reference plane.
|
|
59
59
|
*/
|
|
60
60
|
notParallelTo(plane: PlaneLike | PlaneObjectBase): this;
|
|
61
|
+
/**
|
|
62
|
+
* Selects toroidal faces, optionally matching major and/or minor radius.
|
|
63
|
+
* @param majorRadius - Optional radius from the torus axis to the tube center.
|
|
64
|
+
* @param minorRadius - Optional radius of the tube itself.
|
|
65
|
+
*/
|
|
66
|
+
torus(majorRadius?: number, minorRadius?: number): this;
|
|
67
|
+
/**
|
|
68
|
+
* Excludes toroidal faces, optionally matching major and/or minor radius.
|
|
69
|
+
* @param majorRadius - Optional radius from the torus axis to the tube center.
|
|
70
|
+
* @param minorRadius - Optional radius of the tube itself.
|
|
71
|
+
*/
|
|
72
|
+
notTorus(majorRadius?: number, minorRadius?: number): this;
|
|
61
73
|
/**
|
|
62
74
|
* Selects conical faces.
|
|
63
75
|
*/
|
|
@@ -4,6 +4,7 @@ import { CircleFilter, NotCircleFilter } from "./circle-filter.js";
|
|
|
4
4
|
import { ConeFilter, NotConeFilter } from "./cone-filter.js";
|
|
5
5
|
import { CylinderCurveFilter, NotCylinderCurveFilter } from "./cylinder-curve.js";
|
|
6
6
|
import { CylinderFilter, NotCylinderFilter } from "./cylinder.js";
|
|
7
|
+
import { TorusFilter, NotTorusFilter } from "./torus-filter.js";
|
|
7
8
|
import { NotOnPlaneFilter, OnPlaneFilter } from "./on-plane.js";
|
|
8
9
|
import { NotParallelFilter, ParallelFilter } from "./parallel.js";
|
|
9
10
|
import { PlaneObject } from "../../features/plane.js";
|
|
@@ -182,6 +183,26 @@ export class FaceFilterBuilder extends FilterBuilderBase {
|
|
|
182
183
|
this.filters.push(filter);
|
|
183
184
|
return this;
|
|
184
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Selects toroidal faces, optionally matching major and/or minor radius.
|
|
188
|
+
* @param majorRadius - Optional radius from the torus axis to the tube center.
|
|
189
|
+
* @param minorRadius - Optional radius of the tube itself.
|
|
190
|
+
*/
|
|
191
|
+
torus(majorRadius, minorRadius) {
|
|
192
|
+
const filter = new TorusFilter(majorRadius, minorRadius);
|
|
193
|
+
this.filters.push(filter);
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Excludes toroidal faces, optionally matching major and/or minor radius.
|
|
198
|
+
* @param majorRadius - Optional radius from the torus axis to the tube center.
|
|
199
|
+
* @param minorRadius - Optional radius of the tube itself.
|
|
200
|
+
*/
|
|
201
|
+
notTorus(majorRadius, minorRadius) {
|
|
202
|
+
const filter = new NotTorusFilter(majorRadius, minorRadius);
|
|
203
|
+
this.filters.push(filter);
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
185
206
|
/**
|
|
186
207
|
* Selects conical faces.
|
|
187
208
|
*/
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Matrix4 } from "../../math/matrix4.js";
|
|
2
|
+
import { Face } from "../../common/shapes.js";
|
|
3
|
+
import { FilterBase } from "../filter-base.js";
|
|
4
|
+
export declare class TorusFilter extends FilterBase<Face> {
|
|
5
|
+
private majorRadius?;
|
|
6
|
+
private minorRadius?;
|
|
7
|
+
constructor(majorRadius?: number, minorRadius?: number);
|
|
8
|
+
match(shape: Face): boolean;
|
|
9
|
+
compareTo(other: TorusFilter): boolean;
|
|
10
|
+
transform(matrix: Matrix4): TorusFilter;
|
|
11
|
+
}
|
|
12
|
+
export declare class NotTorusFilter extends FilterBase<Face> {
|
|
13
|
+
private majorRadius?;
|
|
14
|
+
private minorRadius?;
|
|
15
|
+
constructor(majorRadius?: number, minorRadius?: number);
|
|
16
|
+
match(shape: Face): boolean;
|
|
17
|
+
compareTo(other: NotTorusFilter): boolean;
|
|
18
|
+
transform(matrix: Matrix4): NotTorusFilter;
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { FilterBase } from "../filter-base.js";
|
|
2
|
+
import { FaceQuery } from "../../oc/face-query.js";
|
|
3
|
+
export class TorusFilter extends FilterBase {
|
|
4
|
+
majorRadius;
|
|
5
|
+
minorRadius;
|
|
6
|
+
constructor(majorRadius, minorRadius) {
|
|
7
|
+
super();
|
|
8
|
+
this.majorRadius = majorRadius;
|
|
9
|
+
this.minorRadius = minorRadius;
|
|
10
|
+
}
|
|
11
|
+
match(shape) {
|
|
12
|
+
return FaceQuery.isTorusFace(shape, this.majorRadius, this.minorRadius);
|
|
13
|
+
}
|
|
14
|
+
compareTo(other) {
|
|
15
|
+
return this.majorRadius === other.majorRadius && this.minorRadius === other.minorRadius;
|
|
16
|
+
}
|
|
17
|
+
transform(matrix) {
|
|
18
|
+
return new TorusFilter(this.majorRadius, this.minorRadius);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class NotTorusFilter extends FilterBase {
|
|
22
|
+
majorRadius;
|
|
23
|
+
minorRadius;
|
|
24
|
+
constructor(majorRadius, minorRadius) {
|
|
25
|
+
super();
|
|
26
|
+
this.majorRadius = majorRadius;
|
|
27
|
+
this.minorRadius = minorRadius;
|
|
28
|
+
}
|
|
29
|
+
match(shape) {
|
|
30
|
+
return !FaceQuery.isTorusFace(shape, this.majorRadius, this.minorRadius);
|
|
31
|
+
}
|
|
32
|
+
compareTo(other) {
|
|
33
|
+
return this.majorRadius === other.majorRadius && this.minorRadius === other.minorRadius;
|
|
34
|
+
}
|
|
35
|
+
transform(matrix) {
|
|
36
|
+
return new NotTorusFilter(this.majorRadius, this.minorRadius);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { SceneObject } from "../common/scene-object.js";
|
|
2
2
|
import { Shape } from "../common/shapes.js";
|
|
3
3
|
import { Plane } from "../math/plane.js";
|
|
4
|
-
export declare function fuseWithSceneObjects(sceneObjects: SceneObject[], extrusions: Shape<any>[]
|
|
4
|
+
export declare function fuseWithSceneObjects(sceneObjects: SceneObject[], extrusions: Shape<any>[], opts?: {
|
|
5
|
+
glue?: 'full' | 'shift';
|
|
6
|
+
}): {
|
|
5
7
|
newShapes: Shape<any>[];
|
|
6
8
|
modifiedShapes: any[];
|
|
7
9
|
} | {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { BooleanOps } from "../oc/boolean-ops.js";
|
|
2
2
|
import { ShapeOps } from "../oc/shape-ops.js";
|
|
3
3
|
import { classifyCutResult } from "./cut-helpers.js";
|
|
4
|
-
export function fuseWithSceneObjects(sceneObjects, extrusions) {
|
|
4
|
+
export function fuseWithSceneObjects(sceneObjects, extrusions, opts) {
|
|
5
5
|
const modified = [];
|
|
6
|
+
const tCollect = performance.now();
|
|
6
7
|
const objShapeMap = new Map();
|
|
7
8
|
for (const obj of sceneObjects) {
|
|
8
9
|
const shapes = obj.getShapes({}, 'solid');
|
|
@@ -11,9 +12,11 @@ export function fuseWithSceneObjects(sceneObjects, extrusions) {
|
|
|
11
12
|
}
|
|
12
13
|
}
|
|
13
14
|
let sceneShapes = Array.from(objShapeMap.keys());
|
|
14
|
-
console.log(
|
|
15
|
+
console.log(`[perf] fuseWithSceneObjects.collect (scenes=${sceneShapes.length}, extrusions=${extrusions.length}): ${(performance.now() - tCollect).toFixed(1)} ms`);
|
|
15
16
|
const all = [...sceneShapes, ...extrusions];
|
|
16
|
-
const
|
|
17
|
+
const tFuse = performance.now();
|
|
18
|
+
const { result, newShapes, modifiedShapes } = BooleanOps.fuse(all, opts);
|
|
19
|
+
console.log(`[perf] fuseWithSceneObjects.BooleanOps.fuse (glue=${opts?.glue ?? 'off'}): ${(performance.now() - tFuse).toFixed(1)} ms`);
|
|
17
20
|
if (newShapes.length === 0 && modifiedShapes.length === 0) {
|
|
18
21
|
console.log("No fusions were made.");
|
|
19
22
|
return {
|
package/lib/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export declare function registerBuilder<T extends Function>(builder: (context: S
|
|
|
20
20
|
export declare function init(rootPath?: string): Promise<{
|
|
21
21
|
currentScene: Scene;
|
|
22
22
|
currentFile: string;
|
|
23
|
+
renderer: import("./rendering/render.js").SceneRenderer;
|
|
23
24
|
rootPath: string;
|
|
24
25
|
setCurrentFile(filePath: string): void;
|
|
25
26
|
startScene(): Scene;
|
|
@@ -8,15 +8,17 @@ export declare class BooleanOps {
|
|
|
8
8
|
static cutShapes(shape: Shape, tool: Shape): Shape;
|
|
9
9
|
static cutShapesRaw(shape: TopoDS_Shape, tool: TopoDS_Shape): TopoDS_Shape;
|
|
10
10
|
static cutMultiShape(stocks: Shape[], tools: Shape[], plane?: Plane, cutDistance?: number): {
|
|
11
|
-
result: import("../common/wire.js").Wire | Edge |
|
|
12
|
-
modified: (shape: Shape) => (import("../common/wire.js").Wire | Edge |
|
|
11
|
+
result: import("../common/wire.js").Wire | Edge | Face | Solid;
|
|
12
|
+
modified: (shape: Shape) => (import("../common/wire.js").Wire | Edge | Face | Solid)[];
|
|
13
13
|
sectionEdges: Edge[];
|
|
14
14
|
startEdges: Edge[];
|
|
15
15
|
endEdges: Edge[];
|
|
16
16
|
internalEdges: Edge[];
|
|
17
17
|
internalFaces: Face[];
|
|
18
18
|
};
|
|
19
|
-
static fuse(args: Shape[]
|
|
19
|
+
static fuse(args: Shape[], opts?: {
|
|
20
|
+
glue?: 'full' | 'shift';
|
|
21
|
+
}): {
|
|
20
22
|
result: Shape[];
|
|
21
23
|
modifiedShapes: Shape[];
|
|
22
24
|
newShapes: Shape[];
|
|
@@ -118,12 +118,18 @@ export class BooleanOps {
|
|
|
118
118
|
toolList.delete();
|
|
119
119
|
return { result: wrappedResult, modified, sectionEdges, startEdges, endEdges, internalEdges, internalFaces };
|
|
120
120
|
}
|
|
121
|
-
static fuse(args) {
|
|
121
|
+
static fuse(args, opts) {
|
|
122
122
|
const oc = getOC();
|
|
123
123
|
const builder = new oc.BRepAlgoAPI_Fuse();
|
|
124
124
|
builder.SetNonDestructive(true);
|
|
125
125
|
builder.SetCheckInverted(true);
|
|
126
126
|
builder.SetRunParallel(true);
|
|
127
|
+
if (opts?.glue === 'full') {
|
|
128
|
+
builder.SetGlue(oc.BOPAlgo_GlueEnum.BOPAlgo_GlueFull);
|
|
129
|
+
}
|
|
130
|
+
else if (opts?.glue === 'shift') {
|
|
131
|
+
builder.SetGlue(oc.BOPAlgo_GlueEnum.BOPAlgo_GlueShift);
|
|
132
|
+
}
|
|
127
133
|
const argsList = new oc.TopTools_ListOfShape();
|
|
128
134
|
for (const arg of args) {
|
|
129
135
|
argsList.Append(arg.getShape());
|
|
@@ -134,11 +140,16 @@ export class BooleanOps {
|
|
|
134
140
|
builder.SetArguments(list);
|
|
135
141
|
builder.SetTools(argsList);
|
|
136
142
|
const progress = new oc.Message_ProgressRange();
|
|
143
|
+
const tBuild = performance.now();
|
|
137
144
|
builder.Build(progress);
|
|
145
|
+
console.log(`[perf] BooleanOps.fuse.Build (args=${args.length}): ${(performance.now() - tBuild).toFixed(1)} ms`);
|
|
146
|
+
const tSimplify = performance.now();
|
|
138
147
|
builder.SimplifyResult(false, true, oc.Precision.Angular());
|
|
148
|
+
console.log(`[perf] BooleanOps.fuse.SimplifyResult: ${(performance.now() - tSimplify).toFixed(1)} ms`);
|
|
139
149
|
const resultShape = builder.Shape();
|
|
150
|
+
const tExplore = performance.now();
|
|
140
151
|
const rawShapes = Explorer.findAllShapes(resultShape);
|
|
141
|
-
console.log(
|
|
152
|
+
console.log(`[perf] BooleanOps.fuse.findAllShapes (count=${rawShapes.length}): ${(performance.now() - tExplore).toFixed(1)} ms`);
|
|
142
153
|
const result = rawShapes.map(s => ShapeFactory.fromShape(s));
|
|
143
154
|
const modifiedShapes = [];
|
|
144
155
|
for (const shape of args) {
|
|
@@ -149,12 +160,14 @@ export class BooleanOps {
|
|
|
149
160
|
builder.delete();
|
|
150
161
|
progress.delete();
|
|
151
162
|
const newShapes = [];
|
|
163
|
+
const tPartner = performance.now();
|
|
152
164
|
for (const s of result) {
|
|
153
165
|
const existsInArgs = args.some(arg => arg.getShape().IsPartner(s.getShape()));
|
|
154
166
|
if (!existsInArgs) {
|
|
155
167
|
newShapes.push(s);
|
|
156
168
|
}
|
|
157
169
|
}
|
|
170
|
+
console.log(`[perf] BooleanOps.fuse.IsPartner check (result=${result.length} x args=${args.length}): ${(performance.now() - tPartner).toFixed(1)} ms`);
|
|
158
171
|
return { result, newShapes, modifiedShapes };
|
|
159
172
|
}
|
|
160
173
|
static fuseFaces(args) {
|
|
@@ -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);
|
|
@@ -74,80 +74,98 @@ export class FilletOps {
|
|
|
74
74
|
}
|
|
75
75
|
static fillet2dRaw(wire, plane, radius) {
|
|
76
76
|
const oc = getOC();
|
|
77
|
-
let currentWire = wire;
|
|
78
|
-
let cornerIndex = 0;
|
|
79
77
|
const isClosed = wire.Closed();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
const ownedEdges = [];
|
|
79
|
+
// Extract edges in wire traversal order and canonicalize: for each REVERSED edge,
|
|
80
|
+
// build a new FORWARD edge whose natural parameterization matches the wire traversal.
|
|
81
|
+
// ChFi2d_FilletAPI returns modified edges whose natural direction matches the input's
|
|
82
|
+
// natural direction, so aligning natural direction with wire traversal makes the
|
|
83
|
+
// modEdges directly usable when rebuilding the final wire.
|
|
84
|
+
const wireEdges = [];
|
|
85
|
+
{
|
|
86
|
+
const explorer = new oc.BRepTools_WireExplorer(wire);
|
|
87
|
+
while (explorer.More()) {
|
|
88
|
+
const raw = oc.TopoDS.Edge(explorer.Current());
|
|
89
|
+
const isReversed = raw.Orientation().value === oc.TopAbs_Orientation.TopAbs_REVERSED.value;
|
|
90
|
+
if (!isReversed) {
|
|
91
|
+
wireEdges.push(raw);
|
|
92
|
+
ownedEdges.push(raw);
|
|
87
93
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
94
|
+
else {
|
|
95
|
+
const adaptor = new oc.BRepAdaptor_Curve(raw);
|
|
96
|
+
const edgeFirst = adaptor.FirstParameter();
|
|
97
|
+
const edgeLast = adaptor.LastParameter();
|
|
98
|
+
adaptor.delete();
|
|
99
|
+
const curveHandle = oc.BRep_Tool.Curve(raw, 0, 1);
|
|
100
|
+
if (!curveHandle || curveHandle.IsNull()) {
|
|
101
|
+
raw.delete();
|
|
102
|
+
explorer.delete();
|
|
103
|
+
ownedEdges.forEach(e => e.delete());
|
|
104
|
+
throw new Error("fillet2d: edge has no 3D curve");
|
|
105
|
+
}
|
|
106
|
+
const curve = curveHandle.get();
|
|
107
|
+
const reversedHandle = curve.Reversed();
|
|
108
|
+
const newFirst = curve.ReversedParameter(edgeLast);
|
|
109
|
+
const newLast = curve.ReversedParameter(edgeFirst);
|
|
110
|
+
const maker = new oc.BRepBuilderAPI_MakeEdge(reversedHandle, newFirst, newLast);
|
|
111
|
+
const newEdge = oc.TopoDS.Edge(maker.Edge());
|
|
112
|
+
maker.delete();
|
|
113
|
+
reversedHandle.delete();
|
|
114
|
+
curveHandle.delete();
|
|
115
|
+
raw.delete();
|
|
116
|
+
wireEdges.push(newEdge);
|
|
117
|
+
ownedEdges.push(newEdge);
|
|
110
118
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
explorer.Next();
|
|
120
|
+
}
|
|
121
|
+
explorer.delete();
|
|
122
|
+
}
|
|
123
|
+
const currentEdges = wireEdges.slice();
|
|
124
|
+
const filletArcs = new Map();
|
|
125
|
+
const maxCorners = isClosed ? currentEdges.length : currentEdges.length - 1;
|
|
126
|
+
for (let cornerIndex = 0; cornerIndex < maxCorners; cornerIndex++) {
|
|
127
|
+
const nextIndex = (cornerIndex + 1) % currentEdges.length;
|
|
128
|
+
const edge1 = currentEdges[cornerIndex];
|
|
129
|
+
const edge2 = currentEdges[nextIndex];
|
|
130
|
+
const sharedVertex = oc.TopExp.LastVertex(edge1, true);
|
|
131
|
+
const sharedPoint = oc.BRep_Tool.Pnt(sharedVertex);
|
|
132
|
+
sharedVertex.delete();
|
|
133
|
+
const filletAPI = new oc.ChFi2d_FilletAPI(edge1, edge2, plane);
|
|
134
|
+
const success = filletAPI.Perform(radius);
|
|
135
|
+
if (!success || filletAPI.NbResults(sharedPoint) === 0) {
|
|
114
136
|
sharedPoint.delete();
|
|
115
137
|
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;
|
|
138
|
+
continue;
|
|
143
139
|
}
|
|
144
|
-
|
|
140
|
+
const modEdge1 = new oc.TopoDS_Edge();
|
|
141
|
+
const modEdge2 = new oc.TopoDS_Edge();
|
|
142
|
+
const filletEdge = filletAPI.Result(sharedPoint, modEdge1, modEdge2, -1);
|
|
143
|
+
sharedPoint.delete();
|
|
144
|
+
filletAPI.delete();
|
|
145
|
+
currentEdges[cornerIndex] = modEdge1;
|
|
146
|
+
currentEdges[nextIndex] = modEdge2;
|
|
147
|
+
filletArcs.set(cornerIndex, filletEdge);
|
|
148
|
+
ownedEdges.push(modEdge1, modEdge2, filletEdge);
|
|
145
149
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
const edgeList = new oc.TopTools_ListOfShape();
|
|
151
|
+
for (let i = 0; i < currentEdges.length; i++) {
|
|
152
|
+
edgeList.Append(oc.TopoDS.Edge(currentEdges[i]));
|
|
153
|
+
const arc = filletArcs.get(i);
|
|
154
|
+
if (arc) {
|
|
155
|
+
edgeList.Append(oc.TopoDS.Edge(arc));
|
|
149
156
|
}
|
|
150
|
-
throw e;
|
|
151
157
|
}
|
|
158
|
+
const wireBuilder = new oc.BRepBuilderAPI_MakeWire();
|
|
159
|
+
wireBuilder.Add(edgeList);
|
|
160
|
+
edgeList.delete();
|
|
161
|
+
if (!wireBuilder.IsDone()) {
|
|
162
|
+
wireBuilder.delete();
|
|
163
|
+
ownedEdges.forEach(e => e.delete());
|
|
164
|
+
throw new Error("fillet2d: failed to build filleted wire");
|
|
165
|
+
}
|
|
166
|
+
const result = wireBuilder.Wire();
|
|
167
|
+
wireBuilder.delete();
|
|
168
|
+
ownedEdges.forEach(e => e.delete());
|
|
169
|
+
return result;
|
|
152
170
|
}
|
|
153
171
|
}
|
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
|
}
|