fluidcad 0.0.30 → 0.0.32
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/build-error.d.ts +13 -0
- package/lib/dist/common/build-error.js +18 -0
- package/lib/dist/common/describe-error.d.ts +6 -0
- package/lib/dist/common/describe-error.js +26 -0
- package/lib/dist/common/operand-check.d.ts +19 -0
- package/lib/dist/common/operand-check.js +38 -0
- package/lib/dist/common/scene-object.d.ts +8 -0
- package/lib/dist/common/scene-object.js +10 -0
- package/lib/dist/common/shape-factory.d.ts +1 -1
- package/lib/dist/core/2d/arc.d.ts +4 -2
- package/lib/dist/core/2d/hmove.d.ts +8 -1
- package/lib/dist/core/2d/hmove.js +6 -2
- package/lib/dist/core/2d/pmove.d.ts +13 -3
- package/lib/dist/core/2d/pmove.js +6 -2
- package/lib/dist/core/2d/vmove.d.ts +8 -1
- package/lib/dist/core/2d/vmove.js +8 -4
- package/lib/dist/core/extrude.d.ts +17 -4
- package/lib/dist/core/extrude.js +8 -6
- package/lib/dist/core/interfaces.d.ts +16 -6
- package/lib/dist/core/mirror.d.ts +5 -5
- package/lib/dist/features/2d/aline.js +6 -2
- package/lib/dist/features/2d/arc.d.ts +3 -0
- package/lib/dist/features/2d/arc.js +28 -1
- package/lib/dist/features/2d/hline.js +5 -1
- package/lib/dist/features/2d/hmove.d.ts +2 -2
- package/lib/dist/features/2d/hmove.js +32 -7
- package/lib/dist/features/2d/intersect.js +17 -10
- package/lib/dist/features/2d/line.d.ts +2 -0
- package/lib/dist/features/2d/line.js +4 -0
- package/lib/dist/features/2d/pmove.d.ts +2 -2
- package/lib/dist/features/2d/pmove.js +47 -7
- package/lib/dist/features/2d/projection.d.ts +1 -1
- package/lib/dist/features/2d/projection.js +25 -15
- package/lib/dist/features/2d/sketch.d.ts +2 -2
- package/lib/dist/features/2d/sketch.js +10 -4
- package/lib/dist/features/2d/tarc-to-point.js +0 -3
- package/lib/dist/features/2d/tarc.js +0 -3
- package/lib/dist/features/2d/tline.js +0 -3
- package/lib/dist/features/2d/vline.js +5 -1
- package/lib/dist/features/2d/vmove.d.ts +2 -2
- package/lib/dist/features/2d/vmove.js +32 -7
- package/lib/dist/features/axis-from-edge.d.ts +1 -0
- package/lib/dist/features/axis-from-edge.js +8 -0
- package/lib/dist/features/chamfer.d.ts +1 -0
- package/lib/dist/features/chamfer.js +6 -0
- package/lib/dist/features/color.d.ts +1 -0
- package/lib/dist/features/color.js +6 -0
- package/lib/dist/features/common.d.ts +1 -0
- package/lib/dist/features/common.js +9 -0
- package/lib/dist/features/common2d.d.ts +1 -0
- package/lib/dist/features/common2d.js +9 -0
- package/lib/dist/features/draft.d.ts +1 -0
- package/lib/dist/features/draft.js +6 -0
- package/lib/dist/features/extrude-to-face.d.ts +5 -1
- package/lib/dist/features/extrude-to-face.js +50 -8
- package/lib/dist/features/extrude.js +19 -28
- package/lib/dist/features/fillet.d.ts +1 -0
- package/lib/dist/features/fillet.js +6 -0
- package/lib/dist/features/fillet2d.d.ts +1 -0
- package/lib/dist/features/fillet2d.js +9 -0
- package/lib/dist/features/fuse.d.ts +1 -0
- package/lib/dist/features/fuse.js +6 -0
- package/lib/dist/features/fuse2d.d.ts +1 -0
- package/lib/dist/features/fuse2d.js +9 -0
- package/lib/dist/features/loft.d.ts +1 -0
- package/lib/dist/features/loft.js +6 -0
- package/lib/dist/features/mirror-shape.d.ts +1 -0
- package/lib/dist/features/mirror-shape.js +28 -8
- package/lib/dist/features/plane-from-object.d.ts +1 -0
- package/lib/dist/features/plane-from-object.js +8 -0
- package/lib/dist/features/rotate.d.ts +1 -0
- package/lib/dist/features/rotate.js +11 -2
- package/lib/dist/features/select.d.ts +1 -0
- package/lib/dist/features/select.js +40 -12
- package/lib/dist/features/shell.d.ts +1 -0
- package/lib/dist/features/shell.js +6 -0
- package/lib/dist/features/simple-extruder.js +6 -3
- package/lib/dist/features/subtract.d.ts +1 -0
- package/lib/dist/features/subtract.js +5 -0
- package/lib/dist/features/subtract2d.d.ts +1 -0
- package/lib/dist/features/subtract2d.js +5 -0
- package/lib/dist/features/sweep.d.ts +1 -0
- package/lib/dist/features/sweep.js +4 -0
- package/lib/dist/features/translate.d.ts +1 -0
- package/lib/dist/features/translate.js +9 -0
- package/lib/dist/filters/face/above-below.d.ts +20 -0
- package/lib/dist/filters/face/above-below.js +57 -0
- package/lib/dist/filters/face/face-filter.d.ts +26 -0
- package/lib/dist/filters/face/face-filter.js +64 -0
- package/lib/dist/filters/face/planar-filter.d.ts +15 -0
- package/lib/dist/filters/face/planar-filter.js +30 -0
- package/lib/dist/filters/from-object.d.ts +1 -0
- package/lib/dist/filters/from-object.js +3 -0
- package/lib/dist/oc/boolean-ops.d.ts +2 -2
- package/lib/dist/oc/boolean-ops.js +8 -3
- package/lib/dist/oc/edge-ops.d.ts +17 -0
- package/lib/dist/oc/edge-ops.js +60 -0
- package/lib/dist/oc/face-maker2.d.ts +8 -0
- package/lib/dist/oc/face-maker2.js +42 -1
- package/lib/dist/oc/face-ops.d.ts +6 -1
- package/lib/dist/oc/face-ops.js +3 -2
- package/lib/dist/oc/face-query.js +19 -15
- package/lib/dist/oc/ray-intersect.d.ts +3 -2
- package/lib/dist/oc/ray-intersect.js +2 -4
- package/lib/dist/oc/shell-ops.js +15 -2
- package/lib/dist/oc/thin-face-maker.d.ts +15 -0
- package/lib/dist/oc/thin-face-maker.js +48 -7
- package/lib/dist/oc/wire-ops.d.ts +14 -0
- package/lib/dist/oc/wire-ops.js +38 -0
- package/lib/dist/rendering/render.js +6 -4
- package/lib/dist/tests/common/describe-error.test.d.ts +1 -0
- package/lib/dist/tests/common/describe-error.test.js +36 -0
- package/lib/dist/tests/features/2d/intersect.test.js +43 -0
- package/lib/dist/tests/features/2d/move.test.js +72 -1
- package/lib/dist/tests/features/2d/project-regression.test.js +35 -0
- package/lib/dist/tests/features/color-lineage.test.js +24 -0
- package/lib/dist/tests/features/cut.test.js +40 -0
- package/lib/dist/tests/features/cylinder-curve-filter.test.d.ts +1 -0
- package/lib/dist/tests/features/cylinder-curve-filter.test.js +99 -0
- package/lib/dist/tests/features/extrude-to-face.test.js +52 -0
- package/lib/dist/tests/features/extrude.test.js +46 -8
- package/lib/dist/tests/features/mirror.test.js +74 -0
- package/lib/dist/tests/features/select.test.js +141 -0
- package/lib/dist/tests/features/subtract-consumed-input.test.d.ts +1 -0
- package/lib/dist/tests/features/subtract-consumed-input.test.js +28 -0
- package/lib/dist/tests/features/thin-extrude-offset-fix.test.d.ts +1 -0
- package/lib/dist/tests/features/thin-extrude-offset-fix.test.js +34 -0
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -3
- package/ui/dist/assets/{index-6Ep4GPxf.js → index-DMw0OYCF.js} +70 -70
- package/ui/dist/assets/index-DR7c2Qk9.css +2 -0
- package/ui/dist/index.html +2 -2
- package/lib/dist/features/infinite-extrude.d.ts +0 -13
- package/lib/dist/features/infinite-extrude.js +0 -79
- package/ui/dist/assets/index-DRKfe6N9.css +0 -2
|
@@ -2,9 +2,11 @@ import { BuildSceneObjectContext, SceneObject } from "../common/scene-object.js"
|
|
|
2
2
|
import { ExtrudeBase } from "./extrude-base.js";
|
|
3
3
|
import { Extrudable } from "../helpers/types.js";
|
|
4
4
|
import { Point } from "../math/point.js";
|
|
5
|
+
import { FaceFilterBuilder } from "../filters/face/face-filter.js";
|
|
5
6
|
export declare class ExtrudeToFace extends ExtrudeBase {
|
|
6
7
|
face: SceneObject | 'first-face' | 'last-face';
|
|
7
|
-
|
|
8
|
+
private faceFilters;
|
|
9
|
+
constructor(face: SceneObject | 'first-face' | 'last-face', source?: Extrudable | SceneObject, faceFilters?: FaceFilterBuilder[]);
|
|
8
10
|
build(context: BuildSceneObjectContext): void;
|
|
9
11
|
private createAdvancedExtrude;
|
|
10
12
|
private resizePlanarFace;
|
|
@@ -20,6 +22,7 @@ export declare class ExtrudeToFace extends ExtrudeBase {
|
|
|
20
22
|
private createSimpleExtrude;
|
|
21
23
|
private getFace;
|
|
22
24
|
private getFirstOrLastFace;
|
|
25
|
+
private collectFromSceneObjects;
|
|
23
26
|
getDependencies(): SceneObject[];
|
|
24
27
|
createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
|
|
25
28
|
compareTo(other: ExtrudeToFace): boolean;
|
|
@@ -39,6 +42,7 @@ export declare class ExtrudeToFace extends ExtrudeBase {
|
|
|
39
42
|
draft: [number, number];
|
|
40
43
|
endOffset: number;
|
|
41
44
|
face: string;
|
|
45
|
+
faceFilterCount: number;
|
|
42
46
|
thin: [number, number] | [number];
|
|
43
47
|
};
|
|
44
48
|
}
|
|
@@ -11,11 +11,15 @@ import { Explorer } from "../oc/explorer.js";
|
|
|
11
11
|
import { FaceMaker2 } from "../oc/face-maker2.js";
|
|
12
12
|
import { ThinFaceMaker } from "../oc/thin-face-maker.js";
|
|
13
13
|
import { Point } from "../math/point.js";
|
|
14
|
+
import { ShapeFilter } from "../filters/filter.js";
|
|
15
|
+
import { FromSceneObjectFilter } from "../filters/from-object.js";
|
|
14
16
|
export class ExtrudeToFace extends ExtrudeBase {
|
|
15
17
|
face;
|
|
16
|
-
|
|
18
|
+
faceFilters;
|
|
19
|
+
constructor(face, source, faceFilters = []) {
|
|
17
20
|
super(source);
|
|
18
21
|
this.face = face;
|
|
22
|
+
this.faceFilters = faceFilters;
|
|
19
23
|
}
|
|
20
24
|
build(context) {
|
|
21
25
|
const allSceneObjects = context.getSceneObjects();
|
|
@@ -252,12 +256,31 @@ export class ExtrudeToFace extends ExtrudeBase {
|
|
|
252
256
|
}
|
|
253
257
|
}
|
|
254
258
|
}
|
|
255
|
-
|
|
259
|
+
let candidates = allFaces;
|
|
260
|
+
if (this.faceFilters.length > 0) {
|
|
261
|
+
candidates = new ShapeFilter(allFaces, ...this.faceFilters).apply();
|
|
262
|
+
}
|
|
263
|
+
const result = FaceQuery.findFaceByDistance(candidates, plane, mode);
|
|
256
264
|
if (!result) {
|
|
257
265
|
throw new Error(`No face found for '${mode}-face' extrusion`);
|
|
258
266
|
}
|
|
259
267
|
return result;
|
|
260
268
|
}
|
|
269
|
+
collectFromSceneObjects() {
|
|
270
|
+
const objects = [];
|
|
271
|
+
for (const builder of this.faceFilters) {
|
|
272
|
+
for (const filter of builder.getFilters()) {
|
|
273
|
+
if (filter instanceof FromSceneObjectFilter) {
|
|
274
|
+
for (const obj of filter.getSceneObjects()) {
|
|
275
|
+
if (!objects.includes(obj)) {
|
|
276
|
+
objects.push(obj);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return objects;
|
|
283
|
+
}
|
|
261
284
|
getDependencies() {
|
|
262
285
|
const deps = [];
|
|
263
286
|
const source = this.getSource();
|
|
@@ -267,6 +290,11 @@ export class ExtrudeToFace extends ExtrudeBase {
|
|
|
267
290
|
if (this.face instanceof SceneObject) {
|
|
268
291
|
deps.push(this.face);
|
|
269
292
|
}
|
|
293
|
+
for (const obj of this.collectFromSceneObjects()) {
|
|
294
|
+
if (!deps.includes(obj)) {
|
|
295
|
+
deps.push(obj);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
270
298
|
return deps;
|
|
271
299
|
}
|
|
272
300
|
createCopy(remap) {
|
|
@@ -275,7 +303,8 @@ export class ExtrudeToFace extends ExtrudeBase {
|
|
|
275
303
|
: this.face;
|
|
276
304
|
const source = this.getSource();
|
|
277
305
|
const remapped = source ? (remap.get(source) || source) : undefined;
|
|
278
|
-
|
|
306
|
+
const remappedFilters = this.faceFilters.map(f => f.remap(remap));
|
|
307
|
+
return new ExtrudeToFace(newFace, remapped, remappedFilters).syncWith(this);
|
|
279
308
|
}
|
|
280
309
|
compareTo(other) {
|
|
281
310
|
if (!(other instanceof ExtrudeToFace)) {
|
|
@@ -292,15 +321,27 @@ export class ExtrudeToFace extends ExtrudeBase {
|
|
|
292
321
|
if (thisSource && otherSource && !thisSource.compareTo(otherSource)) {
|
|
293
322
|
return false;
|
|
294
323
|
}
|
|
295
|
-
if (
|
|
296
|
-
|
|
324
|
+
if (this.face instanceof SceneObject) {
|
|
325
|
+
if (!(other.face instanceof SceneObject)) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
if (!this.face.compareTo(other.face)) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
297
331
|
}
|
|
298
|
-
|
|
299
|
-
|
|
332
|
+
else {
|
|
333
|
+
if (this.face !== other.face) {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
300
336
|
}
|
|
301
|
-
if (this.
|
|
337
|
+
if (this.faceFilters.length !== other.faceFilters.length) {
|
|
302
338
|
return false;
|
|
303
339
|
}
|
|
340
|
+
for (let i = 0; i < this.faceFilters.length; i++) {
|
|
341
|
+
if (!this.faceFilters[i].equals(other.faceFilters[i])) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
304
345
|
return true;
|
|
305
346
|
}
|
|
306
347
|
getUniqueType() {
|
|
@@ -316,6 +357,7 @@ export class ExtrudeToFace extends ExtrudeBase {
|
|
|
316
357
|
draft: this.getDraft(),
|
|
317
358
|
endOffset: this.getEndOffset(),
|
|
318
359
|
face: typeof (this.face) === 'string' ? this.face : 'selection',
|
|
360
|
+
faceFilterCount: this.faceFilters.length,
|
|
319
361
|
thin: this._thin,
|
|
320
362
|
...this.serializePickFields(),
|
|
321
363
|
};
|
|
@@ -5,8 +5,11 @@ import { FaceMaker2 } from "../oc/face-maker2.js";
|
|
|
5
5
|
import { BooleanOps } from "../oc/boolean-ops.js";
|
|
6
6
|
import { EdgeOps } from "../oc/edge-ops.js";
|
|
7
7
|
import { Explorer } from "../oc/explorer.js";
|
|
8
|
-
import { ExtrudeThroughAll } from "./infinite-extrude.js";
|
|
9
8
|
import { ThinFaceMaker } from "../oc/thin-face-maker.js";
|
|
9
|
+
/** Finite stand-in for "infinity" in through-all cuts. Truly infinite prisms
|
|
10
|
+
* (via OC's `Inf=true` flag) silently fail inside `BRepAlgoAPI_Cut` — verified
|
|
11
|
+
* experimentally — so use a large finite extrusion instead. */
|
|
12
|
+
const THROUGH_ALL_LENGTH = 100000;
|
|
10
13
|
export class Extrude extends ExtrudeBase {
|
|
11
14
|
distance;
|
|
12
15
|
constructor(distance, source) {
|
|
@@ -257,36 +260,24 @@ export class Extrude extends ExtrudeBase {
|
|
|
257
260
|
const scope = this.resolveFusionScope(context.getSceneObjects());
|
|
258
261
|
let toolShapes;
|
|
259
262
|
const isThroughAll = this.distance === 0;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (this.isFaceSourced()) {
|
|
264
|
-
throw new Error("through-all is not supported with a face-sourced extrude");
|
|
265
|
-
}
|
|
266
|
-
const extrudeThroughAll = new ExtrudeThroughAll(this.extrudable, true, true, faces);
|
|
267
|
-
toolShapes = extrudeThroughAll.build();
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
const extruder1 = new Extruder(faces, plane, -this.distance / 2, this.getDraft(), this.getEndOffset());
|
|
271
|
-
const extrusions1 = extruder1.extrude();
|
|
272
|
-
const extruder2 = new Extruder(faces, plane, this.distance / 2, this.getDraft(), this.getEndOffset());
|
|
273
|
-
const extrusions2 = extruder2.extrude();
|
|
274
|
-
const all = [...extrusions1, ...extrusions2];
|
|
275
|
-
const halvesFuse = BooleanOps.fuse(all);
|
|
276
|
-
toolShapes = halvesFuse.result;
|
|
277
|
-
halvesFuse.dispose();
|
|
278
|
-
}
|
|
263
|
+
const draft = this.getDraft();
|
|
264
|
+
if (isThroughAll && this.isFaceSourced()) {
|
|
265
|
+
throw new Error("through-all is not supported with a face-sourced extrude");
|
|
279
266
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
267
|
+
if (this._symmetric) {
|
|
268
|
+
// Symmetric cut: create tool centered on sketch plane, fusing two halves
|
|
269
|
+
// (one on each side). For through-all, each half uses THROUGH_ALL_LENGTH.
|
|
270
|
+
const halfDistance = isThroughAll ? THROUGH_ALL_LENGTH : this.distance / 2;
|
|
271
|
+
const extruder1 = new Extruder(faces, plane, -halfDistance, draft, this.getEndOffset());
|
|
272
|
+
const extruder2 = new Extruder(faces, plane, halfDistance, draft, this.getEndOffset());
|
|
273
|
+
const all = [...extruder1.extrude(), ...extruder2.extrude()];
|
|
274
|
+
const halvesFuse = BooleanOps.fuse(all);
|
|
275
|
+
toolShapes = halvesFuse.result;
|
|
276
|
+
halvesFuse.dispose();
|
|
286
277
|
}
|
|
287
278
|
else {
|
|
288
|
-
const distance = -this.distance;
|
|
289
|
-
const extruder = new Extruder(faces, plane, distance,
|
|
279
|
+
const distance = isThroughAll ? -THROUGH_ALL_LENGTH : -this.distance;
|
|
280
|
+
const extruder = new Extruder(faces, plane, distance, draft, this.getEndOffset());
|
|
290
281
|
toolShapes = extruder.extrude();
|
|
291
282
|
}
|
|
292
283
|
this.getSource()?.removeShapes(this);
|
|
@@ -5,6 +5,7 @@ export declare class Fillet extends SceneObject {
|
|
|
5
5
|
private _targetEdges;
|
|
6
6
|
constructor(radius: number, ...selections: SceneObject[]);
|
|
7
7
|
get targetEdges(): SceneObject[];
|
|
8
|
+
validate(): void;
|
|
8
9
|
build(context: BuildSceneObjectContext): void;
|
|
9
10
|
doBuild(sceneObjectsMap: Map<SceneObject, Shape[]>, selections: SceneObject[]): {
|
|
10
11
|
addedShapes: Shape<import("occjs-wrapper").TopoDS_Shape>[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SceneObject } from "../common/scene-object.js";
|
|
2
2
|
import { FilletOps } from "../oc/fillet-ops.js";
|
|
3
3
|
import { Explorer } from "../oc/explorer.js";
|
|
4
|
+
import { requireShapes } from "../common/operand-check.js";
|
|
4
5
|
export class Fillet extends SceneObject {
|
|
5
6
|
radius;
|
|
6
7
|
_targetEdges = [];
|
|
@@ -12,6 +13,11 @@ export class Fillet extends SceneObject {
|
|
|
12
13
|
get targetEdges() {
|
|
13
14
|
return this._targetEdges;
|
|
14
15
|
}
|
|
16
|
+
validate() {
|
|
17
|
+
for (let i = 0; i < this._targetEdges.length; i++) {
|
|
18
|
+
requireShapes(this._targetEdges[i], `edge selection ${i + 1}`, "fillet");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
15
21
|
build(context) {
|
|
16
22
|
const selections = this.targetEdges;
|
|
17
23
|
let sceneObjects;
|
|
@@ -5,6 +5,7 @@ export declare class Fillet2D extends GeometrySceneObject {
|
|
|
5
5
|
private _targetObjects;
|
|
6
6
|
constructor(radius: number, ...targets: SceneObject[]);
|
|
7
7
|
get targetObjects(): GeometrySceneObject[] | null;
|
|
8
|
+
validate(): void;
|
|
8
9
|
build(context: BuildSceneObjectContext): void;
|
|
9
10
|
getDependencies(): SceneObject[];
|
|
10
11
|
createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
|
|
@@ -3,6 +3,7 @@ import { GeometrySceneObject } from "./2d/geometry.js";
|
|
|
3
3
|
import { FilletOps } from "../oc/fillet-ops.js";
|
|
4
4
|
import { Edge } from "../common/edge.js";
|
|
5
5
|
import { WireOps } from "../oc/wire-ops.js";
|
|
6
|
+
import { requireShapes } from "../common/operand-check.js";
|
|
6
7
|
export class Fillet2D extends GeometrySceneObject {
|
|
7
8
|
radius;
|
|
8
9
|
_targetObjects = null;
|
|
@@ -14,6 +15,14 @@ export class Fillet2D extends GeometrySceneObject {
|
|
|
14
15
|
get targetObjects() {
|
|
15
16
|
return this._targetObjects;
|
|
16
17
|
}
|
|
18
|
+
validate() {
|
|
19
|
+
if (!this._targetObjects) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
for (let i = 0; i < this._targetObjects.length; i++) {
|
|
23
|
+
requireShapes(this._targetObjects[i], `target ${i + 1}`, "fillet2d");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
17
26
|
build(context) {
|
|
18
27
|
let edges = new Map();
|
|
19
28
|
if (this.targetObjects === null) {
|
|
@@ -3,6 +3,7 @@ export declare class Fuse extends SceneObject {
|
|
|
3
3
|
private _sceneObjects;
|
|
4
4
|
constructor(...objects: SceneObject[]);
|
|
5
5
|
get sceneObjects(): SceneObject[];
|
|
6
|
+
validate(): void;
|
|
6
7
|
build(context: BuildSceneObjectContext): void;
|
|
7
8
|
compareTo(other: Fuse): boolean;
|
|
8
9
|
getType(): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SceneObject } from "../common/scene-object.js";
|
|
2
2
|
import { BooleanOps } from "../oc/boolean-ops.js";
|
|
3
3
|
import { ColorTransfer } from "../oc/color-transfer.js";
|
|
4
|
+
import { requireShapes } from "../common/operand-check.js";
|
|
4
5
|
export class Fuse extends SceneObject {
|
|
5
6
|
_sceneObjects = [];
|
|
6
7
|
constructor(...objects) {
|
|
@@ -10,6 +11,11 @@ export class Fuse extends SceneObject {
|
|
|
10
11
|
get sceneObjects() {
|
|
11
12
|
return this._sceneObjects;
|
|
12
13
|
}
|
|
14
|
+
validate() {
|
|
15
|
+
for (let i = 0; i < this._sceneObjects.length; i++) {
|
|
16
|
+
requireShapes(this._sceneObjects[i], `operand ${i + 1}`, "fuse");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
13
19
|
build(context) {
|
|
14
20
|
const p = context.getProfiler();
|
|
15
21
|
let sceneObjects = this.sceneObjects;
|
|
@@ -4,6 +4,7 @@ export declare class Fuse2D extends GeometrySceneObject {
|
|
|
4
4
|
private _targetObjects;
|
|
5
5
|
constructor(...targets: GeometrySceneObject[]);
|
|
6
6
|
get targetObjects(): GeometrySceneObject[] | null;
|
|
7
|
+
validate(): void;
|
|
7
8
|
build(context: BuildSceneObjectContext): void;
|
|
8
9
|
getDependencies(): SceneObject[];
|
|
9
10
|
createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
|
|
@@ -3,6 +3,7 @@ import { Edge } from "../common/edge.js";
|
|
|
3
3
|
import { GeometrySceneObject } from "./2d/geometry.js";
|
|
4
4
|
import { BooleanOps } from "../oc/boolean-ops.js";
|
|
5
5
|
import { FaceMaker2 } from "../oc/face-maker2.js";
|
|
6
|
+
import { requireShapes } from "../common/operand-check.js";
|
|
6
7
|
export class Fuse2D extends GeometrySceneObject {
|
|
7
8
|
_targetObjects = null;
|
|
8
9
|
constructor(...targets) {
|
|
@@ -12,6 +13,14 @@ export class Fuse2D extends GeometrySceneObject {
|
|
|
12
13
|
get targetObjects() {
|
|
13
14
|
return this._targetObjects;
|
|
14
15
|
}
|
|
16
|
+
validate() {
|
|
17
|
+
if (!this._targetObjects) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
for (let i = 0; i < this._targetObjects.length; i++) {
|
|
21
|
+
requireShapes(this._targetObjects[i], `operand ${i + 1}`, "fuse2d");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
15
24
|
build(context) {
|
|
16
25
|
let sourceEdges;
|
|
17
26
|
if (this._targetObjects === null) {
|
|
@@ -5,6 +5,7 @@ export declare class Loft extends ExtrudeBase implements ILoft {
|
|
|
5
5
|
private _profiles;
|
|
6
6
|
constructor(...profiles: SceneObject[]);
|
|
7
7
|
get profiles(): SceneObject[];
|
|
8
|
+
validate(): void;
|
|
8
9
|
build(context: BuildSceneObjectContext): void;
|
|
9
10
|
private buildThinLoft;
|
|
10
11
|
private getProfilePlane;
|
|
@@ -6,6 +6,7 @@ import { BooleanOps } from "../oc/boolean-ops.js";
|
|
|
6
6
|
import { fuseWithSceneObjects, cutWithSceneObjects } from "../helpers/scene-helpers.js";
|
|
7
7
|
import { ExtrudeBase } from "./extrude-base.js";
|
|
8
8
|
import { ThinFaceMaker } from "../oc/thin-face-maker.js";
|
|
9
|
+
import { requireShapes } from "../common/operand-check.js";
|
|
9
10
|
export class Loft extends ExtrudeBase {
|
|
10
11
|
_profiles = [];
|
|
11
12
|
constructor(...profiles) {
|
|
@@ -15,6 +16,11 @@ export class Loft extends ExtrudeBase {
|
|
|
15
16
|
get profiles() {
|
|
16
17
|
return this._profiles;
|
|
17
18
|
}
|
|
19
|
+
validate() {
|
|
20
|
+
for (let i = 0; i < this._profiles.length; i++) {
|
|
21
|
+
requireShapes(this._profiles[i], `profile ${i + 1}`, "loft");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
18
24
|
build(context) {
|
|
19
25
|
if (this.profiles.length < 2) {
|
|
20
26
|
throw new Error("Loft requires at least two profiles.");
|
|
@@ -6,6 +6,7 @@ export declare class MirrorShape extends SceneObject {
|
|
|
6
6
|
private _excludedObjects;
|
|
7
7
|
constructor(plane: PlaneObjectBase, targetObjects?: SceneObject[] | null);
|
|
8
8
|
exclude(...objects: SceneObject[]): this;
|
|
9
|
+
validate(): void;
|
|
9
10
|
build(context: BuildSceneObjectContext): void;
|
|
10
11
|
compareTo(other: MirrorShape): boolean;
|
|
11
12
|
getType(): string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { SceneObject } from "../common/scene-object.js";
|
|
2
2
|
import { Matrix4 } from "../math/matrix4.js";
|
|
3
3
|
import { ShapeOps } from "../oc/shape-ops.js";
|
|
4
|
-
import { fuseWithSceneObjects } from "../helpers/scene-helpers.js";
|
|
4
|
+
import { fuseWithSceneObjects, cutWithSceneObjects } from "../helpers/scene-helpers.js";
|
|
5
|
+
import { requireShapes } from "../common/operand-check.js";
|
|
5
6
|
export class MirrorShape extends SceneObject {
|
|
6
7
|
plane;
|
|
7
8
|
targetObjects;
|
|
@@ -15,6 +16,14 @@ export class MirrorShape extends SceneObject {
|
|
|
15
16
|
this._excludedObjects.push(...objects);
|
|
16
17
|
return this;
|
|
17
18
|
}
|
|
19
|
+
validate() {
|
|
20
|
+
if (!this.targetObjects || this.targetObjects.length === 0) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (let i = 0; i < this.targetObjects.length; i++) {
|
|
24
|
+
requireShapes(this.targetObjects[i], `target ${i + 1}`, "mirror");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
18
27
|
build(context) {
|
|
19
28
|
let objects;
|
|
20
29
|
let targetObjects = this.targetObjects;
|
|
@@ -59,14 +68,25 @@ export class MirrorShape extends SceneObject {
|
|
|
59
68
|
transformedShapes.push(transformed);
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
modifiedShape.object.removeShape(modifiedShape.shape, this);
|
|
66
|
-
}
|
|
71
|
+
const scope = this.resolveFusionScope(allSceneObjects);
|
|
72
|
+
if (this._operationMode === 'new') {
|
|
73
|
+
this.addShapes(transformedShapes);
|
|
67
74
|
}
|
|
68
|
-
|
|
69
|
-
this
|
|
75
|
+
else if (this._operationMode === 'remove') {
|
|
76
|
+
cutWithSceneObjects(scope, transformedShapes, plane, 0, this, {
|
|
77
|
+
recordHistoryFor: this,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const fusionResult = fuseWithSceneObjects(scope, transformedShapes);
|
|
82
|
+
for (const modifiedShape of fusionResult.modifiedShapes) {
|
|
83
|
+
if (modifiedShape.object) {
|
|
84
|
+
modifiedShape.object.removeShape(modifiedShape.shape, this);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const shape of fusionResult.newShapes) {
|
|
88
|
+
this.addShape(shape);
|
|
89
|
+
}
|
|
70
90
|
}
|
|
71
91
|
}
|
|
72
92
|
compareTo(other) {
|
|
@@ -8,6 +8,7 @@ export declare class PlaneFromObject extends PlaneObjectBase {
|
|
|
8
8
|
sourceObject: SceneObject;
|
|
9
9
|
options?: PlaneRenderableOptions;
|
|
10
10
|
constructor(sourceObject: SceneObject, options?: PlaneRenderableOptions);
|
|
11
|
+
validate(): void;
|
|
11
12
|
build(context?: BuildSceneObjectContext): void;
|
|
12
13
|
getFromSceneObject(sceneObject: SceneObject): {
|
|
13
14
|
plane: Plane;
|
|
@@ -2,6 +2,7 @@ import { PlaneObjectBase } from "./plane-renderable-base.js";
|
|
|
2
2
|
import { FaceOps } from "../oc/face-ops.js";
|
|
3
3
|
import { ShapeOps } from "../oc/shape-ops.js";
|
|
4
4
|
import { Point } from "../math/point.js";
|
|
5
|
+
import { requireShapes } from "../common/operand-check.js";
|
|
5
6
|
export class PlaneFromObject extends PlaneObjectBase {
|
|
6
7
|
sourceObject;
|
|
7
8
|
options;
|
|
@@ -10,6 +11,13 @@ export class PlaneFromObject extends PlaneObjectBase {
|
|
|
10
11
|
this.sourceObject = sourceObject;
|
|
11
12
|
this.options = options;
|
|
12
13
|
}
|
|
14
|
+
validate() {
|
|
15
|
+
// PlaneObjectBase sources expose the plane directly — no shapes required.
|
|
16
|
+
if (this.sourceObject instanceof PlaneObjectBase) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
requireShapes(this.sourceObject, "source", "plane");
|
|
20
|
+
}
|
|
13
21
|
build(context) {
|
|
14
22
|
let plane;
|
|
15
23
|
let sourceFace;
|
|
@@ -9,6 +9,7 @@ export declare class Rotate extends SceneObject {
|
|
|
9
9
|
constructor(axis: AxisObjectBase, angle: number, copy?: boolean, ...targets: SceneObject[]);
|
|
10
10
|
get targetObjects(): SceneObject[];
|
|
11
11
|
exclude(...objects: SceneObject[]): this;
|
|
12
|
+
validate(): void;
|
|
12
13
|
build(context: BuildSceneObjectContext): void;
|
|
13
14
|
compareTo(other: Rotate): boolean;
|
|
14
15
|
getType(): string;
|
|
@@ -2,6 +2,7 @@ import { SceneObject } from "../common/scene-object.js";
|
|
|
2
2
|
import { Matrix4 } from "../math/matrix4.js";
|
|
3
3
|
import { rad } from "../helpers/math-helpers.js";
|
|
4
4
|
import { ShapeOps } from "../oc/shape-ops.js";
|
|
5
|
+
import { requireShapes } from "../common/operand-check.js";
|
|
5
6
|
export class Rotate extends SceneObject {
|
|
6
7
|
axis;
|
|
7
8
|
angle;
|
|
@@ -22,6 +23,14 @@ export class Rotate extends SceneObject {
|
|
|
22
23
|
this._excludedObjects.push(...objects);
|
|
23
24
|
return this;
|
|
24
25
|
}
|
|
26
|
+
validate() {
|
|
27
|
+
if (!this._targetObjects) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (let i = 0; i < this._targetObjects.length; i++) {
|
|
31
|
+
requireShapes(this._targetObjects[i], `target ${i + 1}`, "rotate");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
25
34
|
build(context) {
|
|
26
35
|
let objects;
|
|
27
36
|
let targetObjects = this.targetObjects;
|
|
@@ -81,7 +90,7 @@ export class Rotate extends SceneObject {
|
|
|
81
90
|
return false;
|
|
82
91
|
}
|
|
83
92
|
for (let i = 0; i < thisTargetObjects.length; i++) {
|
|
84
|
-
if (thisTargetObjects[i]
|
|
93
|
+
if (!thisTargetObjects[i].compareTo(otherTargetObjects[i])) {
|
|
85
94
|
return false;
|
|
86
95
|
}
|
|
87
96
|
}
|
|
@@ -89,7 +98,7 @@ export class Rotate extends SceneObject {
|
|
|
89
98
|
return false;
|
|
90
99
|
}
|
|
91
100
|
for (let i = 0; i < this._excludedObjects.length; i++) {
|
|
92
|
-
if (this._excludedObjects[i]
|
|
101
|
+
if (!this._excludedObjects[i].compareTo(other._excludedObjects[i])) {
|
|
93
102
|
return false;
|
|
94
103
|
}
|
|
95
104
|
}
|
|
@@ -11,6 +11,7 @@ export declare class SelectSceneObject extends SceneObject implements ISelect {
|
|
|
11
11
|
private shapes;
|
|
12
12
|
constructor(filters: FilterBuilderBase<Shape>[], constraintObject?: SceneObject);
|
|
13
13
|
build(context: BuildSceneObjectContext): void;
|
|
14
|
+
private collectFromSceneObjects;
|
|
14
15
|
private getAllShapes;
|
|
15
16
|
getDependencies(): SceneObject[];
|
|
16
17
|
createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
|
|
@@ -2,6 +2,7 @@ import { FaceFilterBuilder } from "../filters/face/face-filter.js";
|
|
|
2
2
|
import { ShapeFilter } from "../filters/filter.js";
|
|
3
3
|
import { SceneObject } from "../common/scene-object.js";
|
|
4
4
|
import { BelongsToFaceFilter, NotBelongsToFaceFilter } from "../filters/edge/belongs-to-face.js";
|
|
5
|
+
import { FromSceneObjectFilter } from "../filters/from-object.js";
|
|
5
6
|
export class SelectSceneObject extends SceneObject {
|
|
6
7
|
filters;
|
|
7
8
|
constraintObject;
|
|
@@ -32,30 +33,57 @@ export class SelectSceneObject extends SceneObject {
|
|
|
32
33
|
sceneObjects = context.getSceneObjectsFromTo(parent, this);
|
|
33
34
|
}
|
|
34
35
|
}
|
|
36
|
+
// Objects passed explicitly via `from(...)` bypass the part scope so that
|
|
37
|
+
// cross-part selection works (e.g. select(face().from(p1)) from inside p2).
|
|
38
|
+
if (!this.constraintObject) {
|
|
39
|
+
const fromObjects = this.collectFromSceneObjects(filters);
|
|
40
|
+
if (fromObjects.length > 0) {
|
|
41
|
+
sceneObjects = sceneObjects.slice();
|
|
42
|
+
for (const obj of fromObjects) {
|
|
43
|
+
if (!sceneObjects.includes(obj)) {
|
|
44
|
+
sceneObjects.push(obj);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
35
49
|
const allShapes = this.constraintObject ? this.constraintObject.getShapes() : this.getAllShapes(sceneObjects, excludedObjects);
|
|
36
50
|
if (this.type === "edge") {
|
|
37
51
|
this.injectScopeFaces(filters, sceneObjects);
|
|
38
52
|
}
|
|
39
53
|
const filteredShapes = this.applyFilters(allShapes, filters);
|
|
40
|
-
console.log(`SelectSceneObject: shapes after filtering: ${filteredShapes[0]}`);
|
|
41
54
|
this.addShapes(filteredShapes);
|
|
42
55
|
}
|
|
56
|
+
collectFromSceneObjects(filters) {
|
|
57
|
+
const objects = [];
|
|
58
|
+
for (const builder of filters) {
|
|
59
|
+
for (const filter of builder.getFilters()) {
|
|
60
|
+
if (filter instanceof FromSceneObjectFilter) {
|
|
61
|
+
for (const obj of filter.getSceneObjects()) {
|
|
62
|
+
if (!objects.includes(obj)) {
|
|
63
|
+
objects.push(obj);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return objects;
|
|
70
|
+
}
|
|
43
71
|
getAllShapes(scope, exludedShapes) {
|
|
44
72
|
const scopeShapes = scope.flatMap(obj => obj.getShapes({}, 'solid').map(s => s.getSubShapes(this.type)).flat());
|
|
45
73
|
exludedShapes = exludedShapes.flatMap(s => s.getSubShapes(this.type));
|
|
46
|
-
|
|
47
|
-
console.log('=== Scope Objects:', scope.length, ' Shapes:', scopeShapes.length);
|
|
48
|
-
console.log('=== Excluded Shapes:', exludedShapes.length);
|
|
49
|
-
console.log('=== Final Shapes after exclusion:', finalShapes.length);
|
|
50
|
-
let allShapes = [];
|
|
51
|
-
for (const shape of finalShapes) {
|
|
52
|
-
allShapes.push(shape);
|
|
53
|
-
}
|
|
54
|
-
console.log('SelectSceneObject: total shapes collected for filtering:', allShapes.length);
|
|
55
|
-
return allShapes;
|
|
74
|
+
return scopeShapes.filter(shape => !exludedShapes.some(exShape => exShape.isSame(shape)));
|
|
56
75
|
}
|
|
57
76
|
getDependencies() {
|
|
58
|
-
|
|
77
|
+
const deps = [];
|
|
78
|
+
if (this.constraintObject) {
|
|
79
|
+
deps.push(this.constraintObject);
|
|
80
|
+
}
|
|
81
|
+
for (const obj of this.collectFromSceneObjects(this.filters)) {
|
|
82
|
+
if (!deps.includes(obj)) {
|
|
83
|
+
deps.push(obj);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return deps;
|
|
59
87
|
}
|
|
60
88
|
createCopy(remap) {
|
|
61
89
|
const remappedConstraint = this.constraintObject
|
|
@@ -8,6 +8,7 @@ export declare class Shell extends SceneObject implements IShell {
|
|
|
8
8
|
private _faceSelections;
|
|
9
9
|
constructor(thickness: number, faceSelections?: SelectSceneObject[]);
|
|
10
10
|
get faceSelections(): SelectSceneObject[];
|
|
11
|
+
validate(): void;
|
|
11
12
|
build(context: BuildSceneObjectContext): void;
|
|
12
13
|
internalFaces(...args: (number | FaceFilterBuilder)[]): SceneObject;
|
|
13
14
|
internalEdges(...args: (number | EdgeFilterBuilder)[]): SceneObject;
|
|
@@ -7,6 +7,7 @@ import { FaceQuery } from "../oc/face-query.js";
|
|
|
7
7
|
import { FaceFilterBuilder } from "../filters/face/face-filter.js";
|
|
8
8
|
import { EdgeFilterBuilder } from "../filters/edge/edge-filter.js";
|
|
9
9
|
import { ShapeFilter } from "../filters/filter.js";
|
|
10
|
+
import { requireShapes } from "../common/operand-check.js";
|
|
10
11
|
export class Shell extends SceneObject {
|
|
11
12
|
thickness;
|
|
12
13
|
_faceSelections = [];
|
|
@@ -18,6 +19,11 @@ export class Shell extends SceneObject {
|
|
|
18
19
|
get faceSelections() {
|
|
19
20
|
return this._faceSelections;
|
|
20
21
|
}
|
|
22
|
+
validate() {
|
|
23
|
+
for (let i = 0; i < this._faceSelections.length; i++) {
|
|
24
|
+
requireShapes(this._faceSelections[i], `face selection ${i + 1}`, "shell");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
21
27
|
build(context) {
|
|
22
28
|
const shapeObjMap = new Map();
|
|
23
29
|
for (const obj of context.getSceneObjects()) {
|
|
@@ -73,13 +73,16 @@ export class Extruder {
|
|
|
73
73
|
remainingFaces.push(f);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
// Use the firstFace from the solid to detect inner wires
|
|
77
|
-
//
|
|
76
|
+
// Use the firstFace from the solid to detect inner wires. Test against
|
|
77
|
+
// the face's actual outward normal — for negative-distance extrudes the
|
|
78
|
+
// firstFace's outward normal flips relative to plane.normal, which would
|
|
79
|
+
// otherwise invert outer/inner detection and swap side/internal faces.
|
|
78
80
|
const resolvedFirst = firstFace;
|
|
81
|
+
const outwardNormal = resolvedFirst.calculateNormal();
|
|
79
82
|
const innerWireEdgeShapes = [];
|
|
80
83
|
const wires = resolvedFirst.getWires();
|
|
81
84
|
for (const wire of wires) {
|
|
82
|
-
if (
|
|
85
|
+
if (wire.isCW(outwardNormal)) {
|
|
83
86
|
for (const edge of wire.getEdges()) {
|
|
84
87
|
innerWireEdgeShapes.push(edge);
|
|
85
88
|
}
|
|
@@ -3,6 +3,7 @@ export declare class Subtract extends SceneObject {
|
|
|
3
3
|
solid1: SceneObject;
|
|
4
4
|
solid2: SceneObject;
|
|
5
5
|
constructor(solid1: SceneObject, solid2: SceneObject);
|
|
6
|
+
validate(): void;
|
|
6
7
|
build(context: BuildSceneObjectContext): void;
|
|
7
8
|
compareTo(other: Subtract): boolean;
|
|
8
9
|
getType(): string;
|