fluidcad 0.0.15 → 0.0.16
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/bin/fluidcad.js +2 -2
- package/lib/dist/common/scene-object.d.ts +4 -1
- package/lib/dist/common/scene-object.js +14 -3
- package/lib/dist/core/interfaces.d.ts +18 -0
- package/lib/dist/core/part.js +1 -1
- package/lib/dist/core/repeat.js +2 -1
- package/lib/dist/features/2d/aline.d.ts +2 -0
- package/lib/dist/features/2d/aline.js +4 -0
- package/lib/dist/features/axis.d.ts +2 -0
- package/lib/dist/features/axis.js +3 -0
- package/lib/dist/features/color.d.ts +1 -0
- package/lib/dist/features/color.js +4 -0
- package/lib/dist/features/extrude-base.d.ts +13 -0
- package/lib/dist/features/extrude-base.js +64 -0
- package/lib/dist/features/extrude-to-face.js +28 -7
- package/lib/dist/features/extrude-two-distances.js +72 -17
- package/lib/dist/features/extrude.js +90 -21
- package/lib/dist/features/mirror-shape2d.d.ts +1 -0
- package/lib/dist/features/mirror-shape2d.js +7 -0
- package/lib/dist/features/remove.js +1 -1
- package/lib/dist/filters/filter-builder-base.d.ts +7 -0
- package/lib/dist/filters/filter-builder-base.js +16 -0
- package/lib/dist/filters/filter.js +6 -0
- package/lib/dist/filters/tangent-expander.d.ts +47 -0
- package/lib/dist/filters/tangent-expander.js +223 -0
- package/lib/dist/index.js +6 -1
- package/lib/dist/oc/thin-face-maker.d.ts +12 -1
- package/lib/dist/oc/thin-face-maker.js +54 -13
- package/lib/dist/rendering/scene.js +5 -4
- package/lib/dist/tests/features/extrude-two-distances.test.js +16 -0
- package/lib/dist/tests/features/repeat-circular.test.js +12 -0
- package/lib/dist/tests/features/repeat-linear.test.js +22 -0
- package/lib/dist/tests/features/select.test.js +55 -0
- package/lib/dist/tests/features/thin-extrude.test.js +61 -0
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/ui/dist/assets/{index-BJG141m7.js → index-CLQhpG6A.js} +1 -1
- package/ui/dist/index.html +1 -1
package/bin/fluidcad.js
CHANGED
|
@@ -26,14 +26,14 @@ if (positionals[0] === 'init') {
|
|
|
26
26
|
process.exit(1);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
writeFileSync(initPath, `import { init } from 'fluidcad'\n\nexport default init()\n`);
|
|
29
|
+
writeFileSync(initPath, `import { init } from 'fluidcad'\n\nexport default await init()\n`);
|
|
30
30
|
|
|
31
31
|
const testPath = resolve(cwd, 'test.fluid.js');
|
|
32
32
|
if (!existsSync(testPath)) {
|
|
33
33
|
writeFileSync(testPath, `import { extrude, fillet, rect, shell, sketch } from "fluidcad/core";
|
|
34
34
|
|
|
35
35
|
sketch("xy", () => {
|
|
36
|
-
rect(100, 50).radius(10).
|
|
36
|
+
rect(100, 50).radius(10).centered();
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
const e = extrude(30);
|
|
@@ -32,6 +32,7 @@ export declare abstract class SceneObject implements Comparable<SceneObject>, Se
|
|
|
32
32
|
private _alwaysVisible;
|
|
33
33
|
private _name;
|
|
34
34
|
private _guide;
|
|
35
|
+
private _reusable;
|
|
35
36
|
private _sourceLocation;
|
|
36
37
|
private _error;
|
|
37
38
|
protected _fusionScope?: FusionScope;
|
|
@@ -74,7 +75,7 @@ export declare abstract class SceneObject implements Comparable<SceneObject>, Se
|
|
|
74
75
|
addShape(shape: Shape): void;
|
|
75
76
|
addShapes(shapes: Shape[]): void;
|
|
76
77
|
removeShape(shape: Shape, removedBy: SceneObject): void;
|
|
77
|
-
removeShapes(removedBy: SceneObject): void;
|
|
78
|
+
removeShapes(removedBy: SceneObject, force?: boolean): void;
|
|
78
79
|
getOwnShapes(filter?: ShapeFilter, scope?: Set<SceneObject>): Shape[];
|
|
79
80
|
getChildShapes(filter?: ShapeFilter, type?: ShapeType): Shape[];
|
|
80
81
|
getShapes(filter?: ShapeFilter, type?: ShapeType): Shape[];
|
|
@@ -92,6 +93,8 @@ export declare abstract class SceneObject implements Comparable<SceneObject>, Se
|
|
|
92
93
|
getName(): string;
|
|
93
94
|
name(value: string): this;
|
|
94
95
|
guide(): this;
|
|
96
|
+
reusable(): this;
|
|
97
|
+
isReusable(): boolean;
|
|
95
98
|
setSourceLocation(loc: SourceLocation): void;
|
|
96
99
|
getSourceLocation(): SourceLocation | null;
|
|
97
100
|
setError(message: string): void;
|
|
@@ -10,6 +10,7 @@ export class SceneObject {
|
|
|
10
10
|
_alwaysVisible = false;
|
|
11
11
|
_name = null;
|
|
12
12
|
_guide = false;
|
|
13
|
+
_reusable = false;
|
|
13
14
|
_sourceLocation = null;
|
|
14
15
|
_error = null;
|
|
15
16
|
_fusionScope = 'all';
|
|
@@ -89,7 +90,7 @@ export class SceneObject {
|
|
|
89
90
|
return this.getState('snapshot') || [];
|
|
90
91
|
}
|
|
91
92
|
compareTo(other) {
|
|
92
|
-
const match = this._guide === other._guide;
|
|
93
|
+
const match = this._guide === other._guide && this._reusable === other._reusable;
|
|
93
94
|
if (!match) {
|
|
94
95
|
return false;
|
|
95
96
|
}
|
|
@@ -217,10 +218,13 @@ export class SceneObject {
|
|
|
217
218
|
removedBy
|
|
218
219
|
});
|
|
219
220
|
}
|
|
220
|
-
removeShapes(removedBy) {
|
|
221
|
+
removeShapes(removedBy, force) {
|
|
222
|
+
if (this._reusable && !force) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
221
225
|
if (this.isContainer()) {
|
|
222
226
|
for (const child of this.children) {
|
|
223
|
-
child.removeShapes(removedBy);
|
|
227
|
+
child.removeShapes(removedBy, force);
|
|
224
228
|
}
|
|
225
229
|
return;
|
|
226
230
|
}
|
|
@@ -303,6 +307,13 @@ export class SceneObject {
|
|
|
303
307
|
this._guide = true;
|
|
304
308
|
return this;
|
|
305
309
|
}
|
|
310
|
+
reusable() {
|
|
311
|
+
this._reusable = true;
|
|
312
|
+
return this;
|
|
313
|
+
}
|
|
314
|
+
isReusable() {
|
|
315
|
+
return this._reusable;
|
|
316
|
+
}
|
|
306
317
|
setSourceLocation(loc) {
|
|
307
318
|
this._sourceLocation = loc;
|
|
308
319
|
}
|
|
@@ -13,6 +13,13 @@ export interface ISceneObject {
|
|
|
13
13
|
* final geometry output unless explicitly included.
|
|
14
14
|
*/
|
|
15
15
|
guide(): this;
|
|
16
|
+
/**
|
|
17
|
+
* Marks this object as reusable. Reusable objects retain their shapes when
|
|
18
|
+
* consumed by features (e.g., extrude, revolve), allowing multiple features
|
|
19
|
+
* to reference the same source geometry. Use `remove(obj)` to force-remove
|
|
20
|
+
* shapes from a reusable object.
|
|
21
|
+
*/
|
|
22
|
+
reusable(): this;
|
|
16
23
|
}
|
|
17
24
|
export interface IFuseable extends ISceneObject {
|
|
18
25
|
/**
|
|
@@ -243,6 +250,17 @@ export interface IExtrude extends IFuseable {
|
|
|
243
250
|
* @param args - Numeric indices or {@link EdgeFilterBuilder} instances to filter the selection.
|
|
244
251
|
*/
|
|
245
252
|
internalEdges(...args: (number | EdgeFilterBuilder)[]): ISceneObject;
|
|
253
|
+
/**
|
|
254
|
+
* Selects the cap faces at the open ends of a thin-walled extrusion from an open profile.
|
|
255
|
+
* These are the small faces connecting the inner and outer walls at the profile endpoints.
|
|
256
|
+
* @param args - Numeric indices or {@link FaceFilterBuilder} instances to filter the selection.
|
|
257
|
+
*/
|
|
258
|
+
capFaces(...args: (number | FaceFilterBuilder)[]): ISceneObject;
|
|
259
|
+
/**
|
|
260
|
+
* Selects edges on the cap faces of a thin-walled extrusion from an open profile.
|
|
261
|
+
* @param args - Numeric indices or {@link EdgeFilterBuilder} instances to filter the selection.
|
|
262
|
+
*/
|
|
263
|
+
capEdges(...args: (number | EdgeFilterBuilder)[]): ISceneObject;
|
|
246
264
|
/**
|
|
247
265
|
* Applies a draft (taper) angle to the extrusion walls.
|
|
248
266
|
* @param value - A single angle for uniform draft, or a `[start, end]` tuple for asymmetric draft.
|
package/lib/dist/core/part.js
CHANGED
|
@@ -11,7 +11,7 @@ function part(name, callback) {
|
|
|
11
11
|
const currentFile = getCurrentFile();
|
|
12
12
|
const isDirectEdit = sourceLocation
|
|
13
13
|
&& currentFile
|
|
14
|
-
&& sourceLocation.filePath
|
|
14
|
+
&& sourceLocation.filePath === currentFile;
|
|
15
15
|
if (isDirectEdit) {
|
|
16
16
|
const scene = getCurrentScene();
|
|
17
17
|
if (scene) {
|
package/lib/dist/core/repeat.js
CHANGED
|
@@ -117,7 +117,8 @@ function build(context) {
|
|
|
117
117
|
offset = circularOptions.offset;
|
|
118
118
|
}
|
|
119
119
|
else {
|
|
120
|
-
|
|
120
|
+
const angle = circularOptions.angle;
|
|
121
|
+
offset = angle % 360 === 0 ? angle / count : angle / (count - 1);
|
|
121
122
|
}
|
|
122
123
|
const startOffset = centered ? -(count * offset) / 2 : 0;
|
|
123
124
|
const transformedObjects = [];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SceneObject } from "../../common/scene-object.js";
|
|
1
2
|
import { PlaneObjectBase } from "../plane-renderable-base.js";
|
|
2
3
|
import { GeometrySceneObject } from "./geometry.js";
|
|
3
4
|
export declare class AngledLine extends GeometrySceneObject {
|
|
@@ -7,6 +8,7 @@ export declare class AngledLine extends GeometrySceneObject {
|
|
|
7
8
|
private targetPlane;
|
|
8
9
|
constructor(length: number, angle: number, centered?: boolean, targetPlane?: PlaneObjectBase);
|
|
9
10
|
build(): void;
|
|
11
|
+
createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
|
|
10
12
|
compareTo(other: AngledLine): boolean;
|
|
11
13
|
getType(): string;
|
|
12
14
|
getUniqueType(): string;
|
|
@@ -47,6 +47,10 @@ export class AngledLine extends GeometrySceneObject {
|
|
|
47
47
|
if (this.targetPlane)
|
|
48
48
|
this.targetPlane.removeShapes(this);
|
|
49
49
|
}
|
|
50
|
+
createCopy(remap) {
|
|
51
|
+
const targetPlane = this.targetPlane ? (remap.get(this.targetPlane) || this.targetPlane) : null;
|
|
52
|
+
return new AngledLine(this.length, this.angle, this.centered, targetPlane);
|
|
53
|
+
}
|
|
50
54
|
compareTo(other) {
|
|
51
55
|
if (!(other instanceof AngledLine)) {
|
|
52
56
|
return false;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SceneObject } from "../common/scene-object.js";
|
|
1
2
|
import { Axis, AxisTransformOptions } from "../math/axis.js";
|
|
2
3
|
import { AxisObjectBase } from "./axis-renderable-base.js";
|
|
3
4
|
export declare class AxisObject extends AxisObjectBase {
|
|
@@ -5,6 +6,7 @@ export declare class AxisObject extends AxisObjectBase {
|
|
|
5
6
|
private options?;
|
|
6
7
|
constructor(axis: Axis, options?: AxisTransformOptions);
|
|
7
8
|
build(): void;
|
|
9
|
+
createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
|
|
8
10
|
compareTo(other: AxisObject): boolean;
|
|
9
11
|
serialize(): {
|
|
10
12
|
origin: import("../math/point.js").Point;
|
|
@@ -18,6 +18,9 @@ export class AxisObject extends AxisObjectBase {
|
|
|
18
18
|
edge.markAsMetaShape();
|
|
19
19
|
this.addShape(edge);
|
|
20
20
|
}
|
|
21
|
+
createCopy(remap) {
|
|
22
|
+
return new AxisObject(this.axis, this.options);
|
|
23
|
+
}
|
|
21
24
|
compareTo(other) {
|
|
22
25
|
if (!(other instanceof AxisObject)) {
|
|
23
26
|
return false;
|
|
@@ -5,6 +5,7 @@ export declare class Color extends SceneObject {
|
|
|
5
5
|
constructor(color: string, selection?: SceneObject);
|
|
6
6
|
get selection(): SceneObject;
|
|
7
7
|
build(context: BuildSceneObjectContext): void;
|
|
8
|
+
createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
|
|
8
9
|
compareTo(other: Color): boolean;
|
|
9
10
|
getType(): string;
|
|
10
11
|
serialize(): {};
|
|
@@ -69,6 +69,10 @@ export class Color extends SceneObject {
|
|
|
69
69
|
this.addShape(newSolid);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
+
createCopy(remap) {
|
|
73
|
+
const selection = this._selection ? (remap.get(this._selection) || this._selection) : undefined;
|
|
74
|
+
return new Color(this.color, selection);
|
|
75
|
+
}
|
|
72
76
|
compareTo(other) {
|
|
73
77
|
if (!(other instanceof Color)) {
|
|
74
78
|
return false;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Face } from "../common/face.js";
|
|
2
|
+
import { Edge } from "../common/edge.js";
|
|
2
3
|
import { SceneObject } from "../common/scene-object.js";
|
|
3
4
|
import { Extrudable } from "../helpers/types.js";
|
|
4
5
|
import { IExtrude } from "../core/interfaces.js";
|
|
@@ -30,6 +31,8 @@ export declare abstract class ExtrudeBase extends SceneObject implements IExtrud
|
|
|
30
31
|
edges(...indices: number[]): SceneObject;
|
|
31
32
|
internalFaces(...args: number[] | FaceFilterBuilder[]): SceneObject;
|
|
32
33
|
internalEdges(...args: number[] | EdgeFilterBuilder[]): SceneObject;
|
|
34
|
+
capFaces(...args: number[] | FaceFilterBuilder[]): SceneObject;
|
|
35
|
+
capEdges(...args: number[] | EdgeFilterBuilder[]): SceneObject;
|
|
33
36
|
private buildSuffix;
|
|
34
37
|
private resolveFaces;
|
|
35
38
|
private resolveEdges;
|
|
@@ -66,6 +69,16 @@ export declare abstract class ExtrudeBase extends SceneObject implements IExtrud
|
|
|
66
69
|
getEndOffset(): number | undefined;
|
|
67
70
|
getDrill(): boolean;
|
|
68
71
|
protected syncWith(other: ExtrudeBase): this;
|
|
72
|
+
/**
|
|
73
|
+
* Reclassifies faces for thin open-profile extrusions into side, internal, and cap faces.
|
|
74
|
+
* Uses 2D midpoint projection (onto sketch plane) to match reference face edges to
|
|
75
|
+
* the inward/outward wire edges, then IsPartner within the solid to classify faces.
|
|
76
|
+
*/
|
|
77
|
+
protected reclassifyThinFaces(remainingFaces: Face[], referenceFaces: Face[], plane: Plane, inwardEdges: Edge[], outwardEdges: Edge[]): {
|
|
78
|
+
sideFaces: Face[];
|
|
79
|
+
internalFaces: Face[];
|
|
80
|
+
capFaces: Face[];
|
|
81
|
+
};
|
|
69
82
|
compareTo(other: ExtrudeBase): boolean;
|
|
70
83
|
getType(): string;
|
|
71
84
|
}
|
|
@@ -6,6 +6,7 @@ import { FaceMaker2 } from "../oc/face-maker2.js";
|
|
|
6
6
|
import { FaceFilterBuilder } from "../filters/face/face-filter.js";
|
|
7
7
|
import { EdgeFilterBuilder } from "../filters/edge/edge-filter.js";
|
|
8
8
|
import { ShapeFilter } from "../filters/filter.js";
|
|
9
|
+
import { EdgeOps } from "../oc/edge-ops.js";
|
|
9
10
|
export class ExtrudeBase extends SceneObject {
|
|
10
11
|
_extrudable = null;
|
|
11
12
|
_draft;
|
|
@@ -144,6 +145,25 @@ export class ExtrudeBase extends SceneObject {
|
|
|
144
145
|
return this.resolveEdges(edges, args);
|
|
145
146
|
}, this);
|
|
146
147
|
}
|
|
148
|
+
capFaces(...args) {
|
|
149
|
+
const suffix = this.buildSuffix('cap-faces', args);
|
|
150
|
+
return new LazySelectionSceneObject(`${this.generateUniqueName(suffix)}`, (parent) => {
|
|
151
|
+
const faces = parent.getState('cap-faces') || [];
|
|
152
|
+
const transform = parent.getTransform();
|
|
153
|
+
const originalFaces = transform
|
|
154
|
+
? (this.getState('cap-faces') || [])
|
|
155
|
+
: null;
|
|
156
|
+
return this.resolveFaces(faces, args, transform, originalFaces);
|
|
157
|
+
}, this);
|
|
158
|
+
}
|
|
159
|
+
capEdges(...args) {
|
|
160
|
+
const suffix = this.buildSuffix('cap-edges', args);
|
|
161
|
+
return new LazySelectionSceneObject(`${this.generateUniqueName(suffix)}`, (parent) => {
|
|
162
|
+
const faces = parent.getState('cap-faces') || [];
|
|
163
|
+
const edges = faces.flatMap(f => f.getEdges());
|
|
164
|
+
return this.resolveEdges(edges, args);
|
|
165
|
+
}, this);
|
|
166
|
+
}
|
|
147
167
|
buildSuffix(prefix, args) {
|
|
148
168
|
if (args.length === 0) {
|
|
149
169
|
return prefix;
|
|
@@ -321,6 +341,50 @@ export class ExtrudeBase extends SceneObject {
|
|
|
321
341
|
this._thin = other._thin;
|
|
322
342
|
return this;
|
|
323
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* Reclassifies faces for thin open-profile extrusions into side, internal, and cap faces.
|
|
346
|
+
* Uses 2D midpoint projection (onto sketch plane) to match reference face edges to
|
|
347
|
+
* the inward/outward wire edges, then IsPartner within the solid to classify faces.
|
|
348
|
+
*/
|
|
349
|
+
reclassifyThinFaces(remainingFaces, referenceFaces, plane, inwardEdges, outwardEdges) {
|
|
350
|
+
// Project edge midpoints to 2D on the sketch plane for height-independent matching
|
|
351
|
+
const to2D = (edge) => plane.worldToLocal(EdgeOps.getEdgeMidPointRaw(edge.getShape()));
|
|
352
|
+
const inwardMids = inwardEdges.map(to2D);
|
|
353
|
+
const outwardMids = outwardEdges.map(to2D);
|
|
354
|
+
// Find reference face edges matching inward/outward in 2D
|
|
355
|
+
const solidInwardEdges = [];
|
|
356
|
+
const solidOutwardEdges = [];
|
|
357
|
+
for (const rf of referenceFaces) {
|
|
358
|
+
for (const rfe of rf.getEdges()) {
|
|
359
|
+
const rfeMid = to2D(rfe);
|
|
360
|
+
if (inwardMids.some(mp => rfeMid.distanceTo(mp) < 1e-4)) {
|
|
361
|
+
solidInwardEdges.push(rfe);
|
|
362
|
+
}
|
|
363
|
+
else if (outwardMids.some(mp => rfeMid.distanceTo(mp) < 1e-4)) {
|
|
364
|
+
solidOutwardEdges.push(rfe);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Classify remaining faces using IsPartner within the solid
|
|
369
|
+
const sideFaces = [];
|
|
370
|
+
const internalFaces = [];
|
|
371
|
+
const capFaces = [];
|
|
372
|
+
for (const f of remainingFaces) {
|
|
373
|
+
const faceEdges = f.getEdges();
|
|
374
|
+
const isInward = solidInwardEdges.length > 0 && faceEdges.some(fe => solidInwardEdges.some(ie => fe.getShape().IsPartner(ie.getShape())));
|
|
375
|
+
const isOutward = solidOutwardEdges.length > 0 && faceEdges.some(fe => solidOutwardEdges.some(oe => fe.getShape().IsPartner(oe.getShape())));
|
|
376
|
+
if (isInward) {
|
|
377
|
+
internalFaces.push(f);
|
|
378
|
+
}
|
|
379
|
+
else if (isOutward) {
|
|
380
|
+
sideFaces.push(f);
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
capFaces.push(f);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return { sideFaces, internalFaces, capFaces };
|
|
387
|
+
}
|
|
324
388
|
compareTo(other) {
|
|
325
389
|
if (!super.compareTo(other)) {
|
|
326
390
|
return false;
|
|
@@ -32,19 +32,39 @@ export class ExtrudeToFace extends ExtrudeBase {
|
|
|
32
32
|
const allEndFaces = [];
|
|
33
33
|
const allSideFaces = [];
|
|
34
34
|
const allInternalFaces = [];
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
let faces;
|
|
36
|
+
let inwardEdges;
|
|
37
|
+
let outwardEdges;
|
|
38
|
+
if (this.isThin()) {
|
|
39
|
+
const thinResult = ThinFaceMaker.make(this.extrudable.getGeometries(), plane, this._thin[0], this._thin[1]);
|
|
40
|
+
faces = thinResult.faces;
|
|
41
|
+
inwardEdges = thinResult.inwardEdges;
|
|
42
|
+
outwardEdges = thinResult.outwardEdges;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
faces = pickedFaces ?? FaceMaker2.getRegions(this.extrudable.getGeometries(), plane);
|
|
46
|
+
}
|
|
47
|
+
const allCapFaces = [];
|
|
38
48
|
for (const startFace of faces) {
|
|
39
49
|
if (isPlanar && FaceQuery.areFacePlanesParallel(startFace, targetFace)) {
|
|
40
50
|
const { shapes, extruder } = this.createSimpleExtrude(startFace, targetFace, plane);
|
|
41
51
|
for (const s of shapes) {
|
|
42
52
|
solids.push(s);
|
|
43
53
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
if (inwardEdges && inwardEdges.length > 0) {
|
|
55
|
+
const result = this.reclassifyThinFaces([...extruder.getSideFaces(), ...extruder.getInternalFaces()], extruder.getStartFaces(), plane, inwardEdges, outwardEdges || []);
|
|
56
|
+
allStartFaces.push(...extruder.getStartFaces());
|
|
57
|
+
allEndFaces.push(...extruder.getEndFaces());
|
|
58
|
+
allSideFaces.push(...result.sideFaces);
|
|
59
|
+
allInternalFaces.push(...result.internalFaces);
|
|
60
|
+
allCapFaces.push(...result.capFaces);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
allStartFaces.push(...extruder.getStartFaces());
|
|
64
|
+
allEndFaces.push(...extruder.getEndFaces());
|
|
65
|
+
allSideFaces.push(...extruder.getSideFaces());
|
|
66
|
+
allInternalFaces.push(...extruder.getInternalFaces());
|
|
67
|
+
}
|
|
48
68
|
}
|
|
49
69
|
else {
|
|
50
70
|
console.log("Creating advanced extrude for face:");
|
|
@@ -58,6 +78,7 @@ export class ExtrudeToFace extends ExtrudeBase {
|
|
|
58
78
|
this.setState('end-faces', allEndFaces);
|
|
59
79
|
this.setState('side-faces', allSideFaces);
|
|
60
80
|
this.setState('internal-faces', allInternalFaces);
|
|
81
|
+
this.setState('cap-faces', allCapFaces);
|
|
61
82
|
this.extrudable.removeShapes(this);
|
|
62
83
|
if (this.face instanceof SceneObject) {
|
|
63
84
|
this.face.removeShapes(this);
|
|
@@ -3,6 +3,7 @@ import { fuseWithSceneObjects, cutWithSceneObjects } from "../helpers/scene-help
|
|
|
3
3
|
import { BooleanOps } from "../oc/boolean-ops.js";
|
|
4
4
|
import { Explorer } from "../oc/explorer.js";
|
|
5
5
|
import { FaceMaker2 } from "../oc/face-maker2.js";
|
|
6
|
+
import { EdgeOps } from "../oc/edge-ops.js";
|
|
6
7
|
import { Extruder } from "./simple-extruder.js";
|
|
7
8
|
import { ThinFaceMaker } from "../oc/thin-face-maker.js";
|
|
8
9
|
export class ExtrudeTwoDistances extends ExtrudeBase {
|
|
@@ -20,43 +21,97 @@ export class ExtrudeTwoDistances extends ExtrudeBase {
|
|
|
20
21
|
if (pickedFaces !== null && pickedFaces.length === 0) {
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
let faces;
|
|
25
|
+
let inwardEdges;
|
|
26
|
+
let outwardEdges;
|
|
27
|
+
if (this.isThin()) {
|
|
28
|
+
const thinResult = ThinFaceMaker.make(this.extrudable.getGeometries(), plane, this._thin[0], this._thin[1]);
|
|
29
|
+
faces = thinResult.faces;
|
|
30
|
+
inwardEdges = thinResult.inwardEdges;
|
|
31
|
+
outwardEdges = thinResult.outwardEdges;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
faces = pickedFaces ?? FaceMaker2.getRegions(this.extrudable.getGeometries(), plane, this.getDrill());
|
|
35
|
+
}
|
|
36
|
+
const draft = this.getDraft();
|
|
37
|
+
const draft1 = draft ? [draft[0], draft[0]] : undefined;
|
|
38
|
+
const draft2 = draft ? [draft[1], draft[1]] : undefined;
|
|
39
|
+
const extruder1 = new Extruder(faces, plane, this.distance1, draft1, this.getEndOffset());
|
|
27
40
|
const extrusions1 = extruder1.extrude();
|
|
28
41
|
const startFaces = extruder1.getEndFaces();
|
|
29
|
-
const extruder2 = new Extruder(faces, plane, -this.distance2,
|
|
42
|
+
const extruder2 = new Extruder(faces, plane, -this.distance2, draft2, this.getEndOffset());
|
|
30
43
|
const extrusions2 = extruder2.extrude();
|
|
31
44
|
const endFaces = extruder2.getEndFaces();
|
|
32
|
-
const preFusionInternalFaces = [
|
|
33
|
-
...extruder1.getInternalFaces(),
|
|
34
|
-
...extruder2.getInternalFaces(),
|
|
35
|
-
];
|
|
36
45
|
const all = [...extrusions1, ...extrusions2];
|
|
37
46
|
const { result: extrusions } = BooleanOps.fuse(all);
|
|
38
|
-
const
|
|
39
|
-
const
|
|
47
|
+
const remainingFaces = [];
|
|
48
|
+
const fusedStartFaces = [];
|
|
49
|
+
const fusedEndFaces = [];
|
|
40
50
|
for (const solid of extrusions) {
|
|
41
51
|
const allFaces = Explorer.findFacesWrapped(solid);
|
|
42
52
|
for (const f of allFaces) {
|
|
43
53
|
const isStart = startFaces.some(sf => f.getShape().IsSame(sf.getShape()));
|
|
44
54
|
const isEnd = endFaces.some(ef => f.getShape().IsSame(ef.getShape()));
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
if (isStart) {
|
|
56
|
+
fusedStartFaces.push(f);
|
|
57
|
+
}
|
|
58
|
+
else if (isEnd) {
|
|
59
|
+
fusedEndFaces.push(f);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
remainingFaces.push(f);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
let sideFaces;
|
|
67
|
+
let internalFaces;
|
|
68
|
+
let capFaces = [];
|
|
69
|
+
if (inwardEdges && inwardEdges.length > 0) {
|
|
70
|
+
const result = this.reclassifyThinFaces(remainingFaces, [...fusedStartFaces, ...fusedEndFaces], plane, inwardEdges, outwardEdges || []);
|
|
71
|
+
sideFaces = result.sideFaces;
|
|
72
|
+
internalFaces = result.internalFaces;
|
|
73
|
+
capFaces = result.capFaces;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const preInnerEdges = [];
|
|
77
|
+
for (const sf of extruder1.getStartFaces()) {
|
|
78
|
+
for (const wire of sf.getWires()) {
|
|
79
|
+
if (!wire.isCW(plane.normal)) {
|
|
80
|
+
for (const edge of wire.getEdges()) {
|
|
81
|
+
preInnerEdges.push(edge);
|
|
82
|
+
}
|
|
49
83
|
}
|
|
50
|
-
|
|
51
|
-
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const fusedInnerEdges = [];
|
|
87
|
+
if (preInnerEdges.length > 0) {
|
|
88
|
+
const innerMids = preInnerEdges.map(e => plane.worldToLocal(EdgeOps.getEdgeMidPointRaw(e.getShape())));
|
|
89
|
+
for (const sf of fusedStartFaces) {
|
|
90
|
+
for (const sfe of sf.getEdges()) {
|
|
91
|
+
const mid = plane.worldToLocal(EdgeOps.getEdgeMidPointRaw(sfe.getShape()));
|
|
92
|
+
if (innerMids.some(im => mid.distanceTo(im) < 1e-4)) {
|
|
93
|
+
fusedInnerEdges.push(sfe);
|
|
94
|
+
}
|
|
52
95
|
}
|
|
53
96
|
}
|
|
54
97
|
}
|
|
98
|
+
sideFaces = [];
|
|
99
|
+
internalFaces = [];
|
|
100
|
+
for (const f of remainingFaces) {
|
|
101
|
+
const isInternal = fusedInnerEdges.length > 0 && f.getEdges().some(fe => fusedInnerEdges.some(ie => fe.getShape().IsPartner(ie.getShape())));
|
|
102
|
+
if (isInternal) {
|
|
103
|
+
internalFaces.push(f);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
sideFaces.push(f);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
55
109
|
}
|
|
56
110
|
this.setState('start-faces', startFaces);
|
|
57
111
|
this.setState('end-faces', endFaces);
|
|
58
112
|
this.setState('side-faces', sideFaces);
|
|
59
113
|
this.setState('internal-faces', internalFaces);
|
|
114
|
+
this.setState('cap-faces', capFaces);
|
|
60
115
|
this.extrudable.removeShapes(this);
|
|
61
116
|
if (this._operationMode === 'remove') {
|
|
62
117
|
const scope = this.resolveFusionScope(context.getSceneObjects());
|