fluidcad 0.0.38 → 0.0.39
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/core/2d/aline.d.ts +0 -8
- package/lib/dist/core/2d/aline.js +3 -22
- package/lib/dist/core/2d/arc.d.ts +1 -23
- package/lib/dist/core/2d/arc.js +11 -28
- package/lib/dist/core/2d/circle.d.ts +1 -8
- package/lib/dist/core/2d/circle.js +7 -24
- package/lib/dist/core/2d/ellipse.d.ts +1 -17
- package/lib/dist/core/2d/ellipse.js +7 -45
- package/lib/dist/core/2d/hline.d.ts +0 -7
- package/lib/dist/core/2d/hline.js +5 -27
- package/lib/dist/core/2d/intersect.d.ts +0 -7
- package/lib/dist/core/2d/intersect.js +0 -13
- package/lib/dist/core/2d/line.d.ts +1 -8
- package/lib/dist/core/2d/line.js +6 -24
- package/lib/dist/core/2d/offset.d.ts +1 -11
- package/lib/dist/core/2d/offset.js +0 -16
- package/lib/dist/core/2d/polygon.d.ts +1 -17
- package/lib/dist/core/2d/polygon.js +18 -37
- package/lib/dist/core/2d/project.d.ts +0 -7
- package/lib/dist/core/2d/project.js +0 -13
- package/lib/dist/core/2d/rect.d.ts +1 -9
- package/lib/dist/core/2d/rect.js +0 -19
- package/lib/dist/core/2d/slot.d.ts +0 -23
- package/lib/dist/core/2d/slot.js +18 -37
- package/lib/dist/core/2d/text.d.ts +0 -7
- package/lib/dist/core/2d/text.js +0 -14
- package/lib/dist/core/2d/vline.d.ts +0 -7
- package/lib/dist/core/2d/vline.js +5 -23
- package/lib/dist/core/fillet.js +7 -0
- package/lib/dist/core/index.d.ts +1 -1
- package/lib/dist/core/interfaces.d.ts +43 -0
- package/lib/dist/features/2d/connect.d.ts +2 -0
- package/lib/dist/features/2d/connect.js +3 -0
- package/lib/dist/features/2d/intersect.js +4 -1
- package/lib/dist/features/frozen-geometry.d.ts +29 -0
- package/lib/dist/features/frozen-geometry.js +62 -0
- package/lib/dist/features/loft.d.ts +38 -1
- package/lib/dist/features/loft.js +133 -8
- package/lib/dist/features/rib.js +11 -1
- package/lib/dist/features/shell.d.ts +1 -1
- package/lib/dist/features/shell.js +9 -0
- package/lib/dist/features/sweep.d.ts +11 -1
- package/lib/dist/features/sweep.js +43 -7
- package/lib/dist/helpers/clone-transform.js +62 -5
- package/lib/dist/helpers/scene-helpers.d.ts +12 -0
- package/lib/dist/helpers/scene-helpers.js +26 -0
- package/lib/dist/math/bspline-interpolation.d.ts +33 -6
- package/lib/dist/math/bspline-interpolation.js +187 -84
- package/lib/dist/oc/loft/constrained-loft.d.ts +32 -0
- package/lib/dist/oc/loft/constrained-loft.js +61 -0
- package/lib/dist/oc/loft/curve-data.d.ts +18 -0
- package/lib/dist/oc/loft/curve-data.js +54 -0
- package/lib/dist/oc/loft/curve-eval.d.ts +23 -0
- package/lib/dist/oc/loft/curve-eval.js +84 -0
- package/lib/dist/oc/loft/guided-loft.d.ts +89 -0
- package/lib/dist/oc/loft/guided-loft.js +368 -0
- package/lib/dist/oc/loft/section-compatibility.d.ts +131 -0
- package/lib/dist/oc/loft/section-compatibility.js +481 -0
- package/lib/dist/oc/loft/section-curve.d.ts +53 -0
- package/lib/dist/oc/loft/section-curve.js +264 -0
- package/lib/dist/oc/loft/skinning.d.ts +104 -0
- package/lib/dist/oc/loft/skinning.js +299 -0
- package/lib/dist/oc/loft-ops.d.ts +28 -1
- package/lib/dist/oc/loft-ops.js +31 -1
- package/lib/dist/oc/ncollection.d.ts +19 -0
- package/lib/dist/oc/ncollection.js +69 -0
- package/lib/dist/oc/rib-ops.js +5 -17
- package/lib/dist/oc/wire-extend-ops.d.ts +12 -0
- package/lib/dist/oc/wire-extend-ops.js +42 -0
- package/lib/dist/oc/wire-ops.d.ts +11 -0
- package/lib/dist/oc/wire-ops.js +33 -0
- package/lib/dist/oc/wrap-ops.js +8 -15
- package/lib/dist/tests/features/2d/circle.test.js +0 -10
- package/lib/dist/tests/features/2d/ellipse.test.js +0 -30
- package/lib/dist/tests/features/2d/line.test.js +0 -5
- package/lib/dist/tests/features/2d/offset.test.js +0 -10
- package/lib/dist/tests/features/2d/polygon.test.js +0 -16
- package/lib/dist/tests/features/2d/rect.test.js +0 -11
- package/lib/dist/tests/features/2d/slot.test.js +0 -8
- package/lib/dist/tests/features/fillet2d.test.js +37 -1
- package/lib/dist/tests/features/loft-conditions.test.d.ts +1 -0
- package/lib/dist/tests/features/loft-conditions.test.js +246 -0
- package/lib/dist/tests/features/loft-guides.test.d.ts +1 -0
- package/lib/dist/tests/features/loft-guides.test.js +276 -0
- package/lib/dist/tests/features/repeat-sweep-borrowed-path.test.d.ts +1 -0
- package/lib/dist/tests/features/repeat-sweep-borrowed-path.test.js +35 -0
- package/lib/dist/tests/features/rib.test.js +50 -0
- package/lib/dist/tests/features/shell.test.js +35 -1
- package/lib/dist/tests/features/sweep.test.js +81 -0
- package/lib/dist/tests/features/text.test.js +33 -14
- package/lib/dist/tests/math/bspline-interpolation.test.js +97 -1
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/llm-docs/api/index.json +1 -1
- package/llm-docs/api/types/loft.md +36 -1
- package/llm-docs/api/types/sweep.md +13 -1
- package/llm-docs/api/types/text.md +2 -2
- package/llm-docs/index.json +5 -5
- package/package.json +1 -1
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
import { Solid } from "../common/solid.js";
|
|
2
2
|
import { Wire } from "../common/wire.js";
|
|
3
|
+
import { LoftEndCondition, LoftConditionKind } from "./loft/constrained-loft.js";
|
|
4
|
+
export type { LoftEndCondition, LoftConditionKind };
|
|
5
|
+
export interface LoftOptions {
|
|
6
|
+
/** Constrains how the surface leaves the first profile. */
|
|
7
|
+
startCondition?: LoftEndCondition;
|
|
8
|
+
/** Constrains how the surface arrives at the last profile. */
|
|
9
|
+
endCondition?: LoftEndCondition;
|
|
10
|
+
/** One or two side rails the loft surface must follow. */
|
|
11
|
+
guides?: Wire[];
|
|
12
|
+
}
|
|
3
13
|
export declare class LoftOps {
|
|
4
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Lofts through the section wires. Plain lofts run OCC's ThruSections;
|
|
16
|
+
* guides dispatch to `GuidedLoft` (virtual sections carried onto the
|
|
17
|
+
* rails) and start/end conditions to `ConstrainedLoft` (derivative-pinned
|
|
18
|
+
* skinning) — both in-house skins, since OCC can neither constrain end
|
|
19
|
+
* tangency nor follow rails without distorting sections. Conditions
|
|
20
|
+
* compose with guides: the condition fades out around each guide contact
|
|
21
|
+
* (the rails own their sides of the surface).
|
|
22
|
+
*/
|
|
23
|
+
static makeLoft(wires: Wire[], options?: LoftOptions): Solid[];
|
|
24
|
+
/**
|
|
25
|
+
* Thin-walled loft with start/end conditions: both walls are skinned with
|
|
26
|
+
* the same conditions and assembled directly with ring caps — see
|
|
27
|
+
* `ConstrainedLoft.buildThin`. (The condition-less thin loft stays on the
|
|
28
|
+
* legacy ThruSections + boolean path in the feature layer.)
|
|
29
|
+
*/
|
|
30
|
+
static makeThinLoft(outerWires: Wire[], innerWires: Wire[], options: LoftOptions): Solid[];
|
|
31
|
+
private static makeThruSectionsLoft;
|
|
5
32
|
}
|
package/lib/dist/oc/loft-ops.js
CHANGED
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
import { getOC } from "./init.js";
|
|
2
2
|
import { Explorer } from "./explorer.js";
|
|
3
3
|
import { Solid } from "../common/solid.js";
|
|
4
|
+
import { ConstrainedLoft } from "./loft/constrained-loft.js";
|
|
5
|
+
import { GuidedLoft } from "./loft/guided-loft.js";
|
|
4
6
|
export class LoftOps {
|
|
5
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Lofts through the section wires. Plain lofts run OCC's ThruSections;
|
|
9
|
+
* guides dispatch to `GuidedLoft` (virtual sections carried onto the
|
|
10
|
+
* rails) and start/end conditions to `ConstrainedLoft` (derivative-pinned
|
|
11
|
+
* skinning) — both in-house skins, since OCC can neither constrain end
|
|
12
|
+
* tangency nor follow rails without distorting sections. Conditions
|
|
13
|
+
* compose with guides: the condition fades out around each guide contact
|
|
14
|
+
* (the rails own their sides of the surface).
|
|
15
|
+
*/
|
|
16
|
+
static makeLoft(wires, options) {
|
|
17
|
+
const guides = options?.guides ?? [];
|
|
18
|
+
if (guides.length > 0) {
|
|
19
|
+
return GuidedLoft.build(wires, guides, options?.startCondition, options?.endCondition);
|
|
20
|
+
}
|
|
21
|
+
if (options?.startCondition || options?.endCondition) {
|
|
22
|
+
return ConstrainedLoft.build(wires, options.startCondition, options.endCondition);
|
|
23
|
+
}
|
|
24
|
+
return LoftOps.makeThruSectionsLoft(wires);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Thin-walled loft with start/end conditions: both walls are skinned with
|
|
28
|
+
* the same conditions and assembled directly with ring caps — see
|
|
29
|
+
* `ConstrainedLoft.buildThin`. (The condition-less thin loft stays on the
|
|
30
|
+
* legacy ThruSections + boolean path in the feature layer.)
|
|
31
|
+
*/
|
|
32
|
+
static makeThinLoft(outerWires, innerWires, options) {
|
|
33
|
+
return ConstrainedLoft.buildThin(outerWires, innerWires, options.startCondition, options.endCondition);
|
|
34
|
+
}
|
|
35
|
+
static makeThruSectionsLoft(wires) {
|
|
6
36
|
const oc = getOC();
|
|
7
37
|
const thruSections = new oc.BRepOffsetAPI_ThruSections(true, false, 1e-6);
|
|
8
38
|
for (const wire of wires) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { NCollection_Array1_double, NCollection_Array1_int, NCollection_Array1_gp_Pnt, NCollection_Array1_gp_Pnt2d, NCollection_Array2_gp_Pnt, NCollection_Array2_double } from "ocjs-fluidcad";
|
|
2
|
+
import type { Disposable } from "./convert.js";
|
|
3
|
+
import { Point2D } from "../math/point.js";
|
|
4
|
+
/**
|
|
5
|
+
* Builders for the 1-based NCollection arrays that OCC geometry constructors
|
|
6
|
+
* consume. Each returns the codebase's `Disposable` pair so call sites free
|
|
7
|
+
* the native array (and nothing else) once the consumer has copied it.
|
|
8
|
+
*/
|
|
9
|
+
export declare class NCollections {
|
|
10
|
+
static toArray1Double(values: ReadonlyArray<number>): Disposable<NCollection_Array1_double>;
|
|
11
|
+
static toArray1Int(values: ReadonlyArray<number>): Disposable<NCollection_Array1_int>;
|
|
12
|
+
/** Each point is an [x, y, z] triple. */
|
|
13
|
+
static toArray1Pnt(points: ReadonlyArray<ReadonlyArray<number>>): Disposable<NCollection_Array1_gp_Pnt>;
|
|
14
|
+
static toArray1Pnt2d(points: ReadonlyArray<Point2D>): Disposable<NCollection_Array1_gp_Pnt2d>;
|
|
15
|
+
/** Grid of [x, y, z] triples, indexed [row][column]. */
|
|
16
|
+
static toArray2Pnt(grid: ReadonlyArray<ReadonlyArray<ReadonlyArray<number>>>): Disposable<NCollection_Array2_gp_Pnt>;
|
|
17
|
+
/** Grid of numbers, indexed [row][column]. */
|
|
18
|
+
static toArray2Double(grid: ReadonlyArray<ReadonlyArray<number>>): Disposable<NCollection_Array2_double>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { getOC } from "./init.js";
|
|
2
|
+
/**
|
|
3
|
+
* Builders for the 1-based NCollection arrays that OCC geometry constructors
|
|
4
|
+
* consume. Each returns the codebase's `Disposable` pair so call sites free
|
|
5
|
+
* the native array (and nothing else) once the consumer has copied it.
|
|
6
|
+
*/
|
|
7
|
+
export class NCollections {
|
|
8
|
+
static toArray1Double(values) {
|
|
9
|
+
const oc = getOC();
|
|
10
|
+
const array = new oc.NCollection_Array1_double(1, values.length);
|
|
11
|
+
for (let i = 0; i < values.length; i++) {
|
|
12
|
+
array.SetValue(i + 1, values[i]);
|
|
13
|
+
}
|
|
14
|
+
return [array, () => array.delete()];
|
|
15
|
+
}
|
|
16
|
+
static toArray1Int(values) {
|
|
17
|
+
const oc = getOC();
|
|
18
|
+
const array = new oc.NCollection_Array1_int(1, values.length);
|
|
19
|
+
for (let i = 0; i < values.length; i++) {
|
|
20
|
+
array.SetValue(i + 1, values[i]);
|
|
21
|
+
}
|
|
22
|
+
return [array, () => array.delete()];
|
|
23
|
+
}
|
|
24
|
+
/** Each point is an [x, y, z] triple. */
|
|
25
|
+
static toArray1Pnt(points) {
|
|
26
|
+
const oc = getOC();
|
|
27
|
+
const array = new oc.NCollection_Array1_gp_Pnt(1, points.length);
|
|
28
|
+
for (let i = 0; i < points.length; i++) {
|
|
29
|
+
const point = new oc.gp_Pnt(points[i][0], points[i][1], points[i][2]);
|
|
30
|
+
array.SetValue(i + 1, point);
|
|
31
|
+
point.delete();
|
|
32
|
+
}
|
|
33
|
+
return [array, () => array.delete()];
|
|
34
|
+
}
|
|
35
|
+
static toArray1Pnt2d(points) {
|
|
36
|
+
const oc = getOC();
|
|
37
|
+
const array = new oc.NCollection_Array1_gp_Pnt2d(1, points.length);
|
|
38
|
+
for (let i = 0; i < points.length; i++) {
|
|
39
|
+
const point = new oc.gp_Pnt2d(points[i].x, points[i].y);
|
|
40
|
+
array.SetValue(i + 1, point);
|
|
41
|
+
point.delete();
|
|
42
|
+
}
|
|
43
|
+
return [array, () => array.delete()];
|
|
44
|
+
}
|
|
45
|
+
/** Grid of [x, y, z] triples, indexed [row][column]. */
|
|
46
|
+
static toArray2Pnt(grid) {
|
|
47
|
+
const oc = getOC();
|
|
48
|
+
const array = new oc.NCollection_Array2_gp_Pnt(1, grid.length, 1, grid[0].length);
|
|
49
|
+
for (let row = 0; row < grid.length; row++) {
|
|
50
|
+
for (let col = 0; col < grid[row].length; col++) {
|
|
51
|
+
const point = new oc.gp_Pnt(grid[row][col][0], grid[row][col][1], grid[row][col][2]);
|
|
52
|
+
array.SetValue(row + 1, col + 1, point);
|
|
53
|
+
point.delete();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return [array, () => array.delete()];
|
|
57
|
+
}
|
|
58
|
+
/** Grid of numbers, indexed [row][column]. */
|
|
59
|
+
static toArray2Double(grid) {
|
|
60
|
+
const oc = getOC();
|
|
61
|
+
const array = new oc.NCollection_Array2_double(1, grid.length, 1, grid[0].length);
|
|
62
|
+
for (let row = 0; row < grid.length; row++) {
|
|
63
|
+
for (let col = 0; col < grid[row].length; col++) {
|
|
64
|
+
array.SetValue(row + 1, col + 1, grid[row][col]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return [array, () => array.delete()];
|
|
68
|
+
}
|
|
69
|
+
}
|
package/lib/dist/oc/rib-ops.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Point } from "../math/point.js";
|
|
|
4
4
|
import { Vector3d } from "../math/vector3d.js";
|
|
5
5
|
import { Matrix4 } from "../math/matrix4.js";
|
|
6
6
|
import { WireOps } from "./wire-ops.js";
|
|
7
|
+
import { WireExtendOps } from "./wire-extend-ops.js";
|
|
7
8
|
import { FaceOps } from "./face-ops.js";
|
|
8
9
|
import { EdgeOps } from "./edge-ops.js";
|
|
9
10
|
import { ShapeOps } from "./shape-ops.js";
|
|
@@ -144,17 +145,9 @@ export class RibOps {
|
|
|
144
145
|
// overlaps every cavity boundary regardless of curvature (drafted cones,
|
|
145
146
|
// fillets, etc.) — so the cut produces a clean blend on every face it touches.
|
|
146
147
|
static extendSpineWire(spineWire, scopeShapes, _plane) {
|
|
147
|
-
|
|
148
|
-
if (edges.length === 0) {
|
|
148
|
+
if (spineWire.getEdges().length === 0) {
|
|
149
149
|
return spineWire;
|
|
150
150
|
}
|
|
151
|
-
const firstVertex = spineWire.getFirstVertex().toPoint();
|
|
152
|
-
const lastVertex = spineWire.getLastVertex().toPoint();
|
|
153
|
-
const lastEdge = edges[edges.length - 1];
|
|
154
|
-
const endTangent = EdgeOps.getEdgeTangentAtEnd(lastEdge).normalize();
|
|
155
|
-
const firstEdge = edges[0];
|
|
156
|
-
const firstEdgeEnd = EdgeOps.getLastVertex(firstEdge).toPoint();
|
|
157
|
-
const startTangent = firstVertex.vectorTo(firstEdgeEnd).normalize();
|
|
158
151
|
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
159
152
|
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
160
153
|
for (const s of scopeShapes) {
|
|
@@ -184,14 +177,9 @@ export class RibOps {
|
|
|
184
177
|
return spineWire;
|
|
185
178
|
}
|
|
186
179
|
const ext = 2 * diag;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
EdgeOps.makeLineEdge(startExtPoint, firstVertex),
|
|
191
|
-
...edges,
|
|
192
|
-
EdgeOps.makeLineEdge(lastVertex, endExtPoint),
|
|
193
|
-
];
|
|
194
|
-
return WireOps.makeWireFromEdges(newEdges);
|
|
180
|
+
let extended = WireExtendOps.extendWire(spineWire, "start", ext);
|
|
181
|
+
extended = WireExtendOps.extendWire(extended, "end", ext);
|
|
182
|
+
return extended;
|
|
195
183
|
}
|
|
196
184
|
static computeExtrudeDistanceAlongDirection(direction, origin, scopeShapes) {
|
|
197
185
|
let maxDist = 0;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Wire } from "../common/wire.js";
|
|
2
|
+
export type WireEnd = "start" | "end";
|
|
3
|
+
export declare class WireExtendOps {
|
|
4
|
+
/**
|
|
5
|
+
* Extends an open wire beyond one end by `length`, continuing straight along
|
|
6
|
+
* the wire's true tangent there. The new straight edge joins the existing
|
|
7
|
+
* first/last vertex, so the result is a single connected wire.
|
|
8
|
+
*
|
|
9
|
+
* No-op for closed wires, empty wires, or `length <= 0`.
|
|
10
|
+
*/
|
|
11
|
+
static extendWire(wire: Wire, side: WireEnd, length: number): Wire;
|
|
12
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EdgeOps } from "./edge-ops.js";
|
|
2
|
+
import { WireOps } from "./wire-ops.js";
|
|
3
|
+
import { PathSampler } from "./path-sampler.js";
|
|
4
|
+
export class WireExtendOps {
|
|
5
|
+
/**
|
|
6
|
+
* Extends an open wire beyond one end by `length`, continuing straight along
|
|
7
|
+
* the wire's true tangent there. The new straight edge joins the existing
|
|
8
|
+
* first/last vertex, so the result is a single connected wire.
|
|
9
|
+
*
|
|
10
|
+
* No-op for closed wires, empty wires, or `length <= 0`.
|
|
11
|
+
*/
|
|
12
|
+
static extendWire(wire, side, length) {
|
|
13
|
+
if (length <= 0) {
|
|
14
|
+
return wire;
|
|
15
|
+
}
|
|
16
|
+
const edges = wire.getEdges();
|
|
17
|
+
if (edges.length === 0) {
|
|
18
|
+
return wire;
|
|
19
|
+
}
|
|
20
|
+
const sampler = new PathSampler(wire);
|
|
21
|
+
try {
|
|
22
|
+
if (sampler.closed) {
|
|
23
|
+
return wire;
|
|
24
|
+
}
|
|
25
|
+
if (side === "start") {
|
|
26
|
+
// Tangent at the start points into the wire; extend the opposite way.
|
|
27
|
+
const startPt = wire.getFirstVertex().toPoint();
|
|
28
|
+
const inward = sampler.evalAt(0).tangent;
|
|
29
|
+
const extPt = startPt.add(inward.multiply(-length));
|
|
30
|
+
return WireOps.makeWireFromEdges([EdgeOps.makeLineEdge(extPt, startPt), ...edges]);
|
|
31
|
+
}
|
|
32
|
+
// Tangent at the end already points out of the wire.
|
|
33
|
+
const endPt = wire.getLastVertex().toPoint();
|
|
34
|
+
const outward = sampler.evalAt(sampler.length).tangent;
|
|
35
|
+
const extPt = endPt.add(outward.multiply(length));
|
|
36
|
+
return WireOps.makeWireFromEdges([...edges, EdgeOps.makeLineEdge(endPt, extPt)]);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
sampler.dispose();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -26,6 +26,17 @@ export declare class WireOps {
|
|
|
26
26
|
*/
|
|
27
27
|
static offsetWireOnPlane(wire: Wire, distance: number, isClosed: boolean, plane: Plane): Wire;
|
|
28
28
|
static isCWRaw(wire: TopoDS_Wire, normal: Vector3d): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Groups edges into connected chains — one wire per chain — via
|
|
31
|
+
* `ShapeAnalysis_FreeBounds.ConnectEdgesToWires`. Edge ends within
|
|
32
|
+
* `tolerance` of each other connect; disconnected groups come back as
|
|
33
|
+
* separate wires (e.g. a guide sketch holding a curve and its mirror).
|
|
34
|
+
*
|
|
35
|
+
* V8-binding gotcha: the plain-name dispatcher can resolve to the
|
|
36
|
+
* deprecated out-parameter overload, which returns a `{ wires }` envelope
|
|
37
|
+
* instead of the sequence itself — unwrap at runtime.
|
|
38
|
+
*/
|
|
39
|
+
static connectEdgesToWires(edges: Edge[], tolerance?: number): Wire[];
|
|
29
40
|
static makeWireFromEdgesRaw(edges: TopoDS_Edge[]): TopoDS_Wire;
|
|
30
41
|
static reverseWireRaw(wire: TopoDS_Wire): TopoDS_Wire;
|
|
31
42
|
static buildWireRaw(edges: TopoDS_Edge[]): TopoDS_Wire;
|
package/lib/dist/oc/wire-ops.js
CHANGED
|
@@ -70,6 +70,39 @@ export class WireOps {
|
|
|
70
70
|
adaptor.delete();
|
|
71
71
|
return signedArea < 0;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Groups edges into connected chains — one wire per chain — via
|
|
75
|
+
* `ShapeAnalysis_FreeBounds.ConnectEdgesToWires`. Edge ends within
|
|
76
|
+
* `tolerance` of each other connect; disconnected groups come back as
|
|
77
|
+
* separate wires (e.g. a guide sketch holding a curve and its mirror).
|
|
78
|
+
*
|
|
79
|
+
* V8-binding gotcha: the plain-name dispatcher can resolve to the
|
|
80
|
+
* deprecated out-parameter overload, which returns a `{ wires }` envelope
|
|
81
|
+
* instead of the sequence itself — unwrap at runtime.
|
|
82
|
+
*/
|
|
83
|
+
static connectEdgesToWires(edges, tolerance = 1e-6) {
|
|
84
|
+
const oc = getOC();
|
|
85
|
+
const input = new oc.NCollection_HSequence_TopoDS_Shape();
|
|
86
|
+
for (const edge of edges) {
|
|
87
|
+
input.Append(edge.getShape());
|
|
88
|
+
}
|
|
89
|
+
const result = oc.ShapeAnalysis_FreeBounds.ConnectEdgesToWires(input, tolerance, false);
|
|
90
|
+
const isEnvelope = result && typeof result.Sequence !== "function" && result.wires;
|
|
91
|
+
const handle = isEnvelope ? result.wires : result;
|
|
92
|
+
const wires = [];
|
|
93
|
+
const sequence = handle.Sequence();
|
|
94
|
+
for (let i = 1; i <= sequence.Length(); i++) {
|
|
95
|
+
wires.push(Wire.fromTopoDSWire(oc.TopoDS.Wire(sequence.Value(i))));
|
|
96
|
+
}
|
|
97
|
+
input.delete();
|
|
98
|
+
if (isEnvelope && typeof result[Symbol.dispose] === "function") {
|
|
99
|
+
result[Symbol.dispose]();
|
|
100
|
+
}
|
|
101
|
+
else if (typeof handle.delete === "function") {
|
|
102
|
+
handle.delete();
|
|
103
|
+
}
|
|
104
|
+
return wires;
|
|
105
|
+
}
|
|
73
106
|
static makeWireFromEdgesRaw(edges) {
|
|
74
107
|
const oc = getOC();
|
|
75
108
|
const wireMaker = new oc.BRepBuilderAPI_MakeWire();
|
package/lib/dist/oc/wrap-ops.js
CHANGED
|
@@ -4,6 +4,8 @@ import { Explorer } from "./explorer.js";
|
|
|
4
4
|
import { FaceQuery } from "./face-query.js";
|
|
5
5
|
import { WireOps } from "./wire-ops.js";
|
|
6
6
|
import { ShapeFactory } from "../common/shape-factory.js";
|
|
7
|
+
import { NCollections } from "./ncollection.js";
|
|
8
|
+
import { Point2D } from "../math/point.js";
|
|
7
9
|
import { ConeDevelopment, CylinderDevelopment, } from "./wrap-development.js";
|
|
8
10
|
import { interpolateBSpline2d } from "../math/bspline-interpolation.js";
|
|
9
11
|
/** Sample points per curved sketch edge before fitting its UV pcurve. */
|
|
@@ -284,22 +286,13 @@ export class WrapOps {
|
|
|
284
286
|
static fitUvCurve(samples) {
|
|
285
287
|
const oc = getOC();
|
|
286
288
|
const data = interpolateBSpline2d(samples.map(s => ({ x: s.u, y: s.v })));
|
|
287
|
-
const poles = new
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
poles.SetValue(i + 1, point);
|
|
291
|
-
point.delete();
|
|
292
|
-
}
|
|
293
|
-
const knots = new oc.NCollection_Array1_double(1, data.knots.length);
|
|
294
|
-
const multiplicities = new oc.NCollection_Array1_int(1, data.knots.length);
|
|
295
|
-
for (let i = 0; i < data.knots.length; i++) {
|
|
296
|
-
knots.SetValue(i + 1, data.knots[i]);
|
|
297
|
-
multiplicities.SetValue(i + 1, data.multiplicities[i]);
|
|
298
|
-
}
|
|
289
|
+
const [poles, disposePoles] = NCollections.toArray1Pnt2d(data.poles.map(p => new Point2D(p.x, p.y)));
|
|
290
|
+
const [knots, disposeKnots] = NCollections.toArray1Double(data.knots);
|
|
291
|
+
const [multiplicities, disposeMultiplicities] = NCollections.toArray1Int(data.multiplicities);
|
|
299
292
|
const curve = new oc.Geom2d_BSplineCurve(poles, knots, multiplicities, data.degree);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
293
|
+
disposePoles();
|
|
294
|
+
disposeKnots();
|
|
295
|
+
disposeMultiplicities();
|
|
303
296
|
return curve;
|
|
304
297
|
}
|
|
305
298
|
/** Thickens the wrapped face along the surface normal into a solid. */
|
|
@@ -51,14 +51,4 @@ describe("circle", () => {
|
|
|
51
51
|
expect(getFacesByType(solid, "cylinder")).toHaveLength(1);
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
|
-
describe("standalone with targetPlane", () => {
|
|
55
|
-
it("should create a circle on a specific plane", () => {
|
|
56
|
-
circle("xy", 60);
|
|
57
|
-
const e = extrude(10);
|
|
58
|
-
render();
|
|
59
|
-
const solid = e.getShapes()[0];
|
|
60
|
-
const bbox = ShapeOps.getBoundingBox(solid);
|
|
61
|
-
expect(bbox.maxX - bbox.minX).toBeCloseTo(60, 0);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
54
|
});
|
|
@@ -66,35 +66,5 @@ describe("ellipse", () => {
|
|
|
66
66
|
render();
|
|
67
67
|
expect(negEllipse?.getError()).toMatch(/positive/i);
|
|
68
68
|
});
|
|
69
|
-
it("throws when given a plane inside a sketch", () => {
|
|
70
|
-
expect(() => {
|
|
71
|
-
sketch("xy", () => {
|
|
72
|
-
ellipse("xy", 30, 20);
|
|
73
|
-
});
|
|
74
|
-
render();
|
|
75
|
-
}).toThrow();
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
describe("standalone with targetPlane", () => {
|
|
79
|
-
it("creates an ellipse on a specific plane", () => {
|
|
80
|
-
ellipse("xy", 60, 40);
|
|
81
|
-
const e = extrude(10);
|
|
82
|
-
render();
|
|
83
|
-
const solid = e.getShapes()[0];
|
|
84
|
-
const bbox = ShapeOps.getBoundingBox(solid);
|
|
85
|
-
expect(bbox.maxX - bbox.minX).toBeCloseTo(120, 0);
|
|
86
|
-
expect(bbox.maxY - bbox.minY).toBeCloseTo(80, 0);
|
|
87
|
-
});
|
|
88
|
-
it("creates an ellipse on a plane at a given center", () => {
|
|
89
|
-
ellipse("xy", [10, 20], 30, 15);
|
|
90
|
-
const e = extrude(10);
|
|
91
|
-
render();
|
|
92
|
-
const solid = e.getShapes()[0];
|
|
93
|
-
const bbox = ShapeOps.getBoundingBox(solid);
|
|
94
|
-
expect(bbox.centerX).toBeCloseTo(10, 0);
|
|
95
|
-
expect(bbox.centerY).toBeCloseTo(20, 0);
|
|
96
|
-
expect(bbox.maxX - bbox.minX).toBeCloseTo(60, 0);
|
|
97
|
-
expect(bbox.maxY - bbox.minY).toBeCloseTo(30, 0);
|
|
98
|
-
});
|
|
99
69
|
});
|
|
100
70
|
});
|
|
@@ -32,11 +32,6 @@ describe("line functions", () => {
|
|
|
32
32
|
expect(bbox.maxX - bbox.minX).toBeCloseTo(80, 0);
|
|
33
33
|
expect(bbox.maxY - bbox.minY).toBeCloseTo(40, 0);
|
|
34
34
|
});
|
|
35
|
-
it("should support standalone mode with targetPlane", () => {
|
|
36
|
-
hLine("xy", 50);
|
|
37
|
-
render();
|
|
38
|
-
// Just verify no error — standalone line doesn't form a closed shape
|
|
39
|
-
});
|
|
40
35
|
});
|
|
41
36
|
describe("vLine", () => {
|
|
42
37
|
it("should create a vertical line and form a closed rect", () => {
|
|
@@ -159,16 +159,6 @@ describe("offset", () => {
|
|
|
159
159
|
expect(shapes).toHaveLength(1);
|
|
160
160
|
});
|
|
161
161
|
});
|
|
162
|
-
describe("specific targets (standalone)", () => {
|
|
163
|
-
it("should offset source geometries onto a target plane", () => {
|
|
164
|
-
const s = sketch("xy", () => {
|
|
165
|
-
circle(40);
|
|
166
|
-
});
|
|
167
|
-
offset("xy", 5, false, s);
|
|
168
|
-
render();
|
|
169
|
-
// Offset edges should exist on the target plane
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
162
|
describe("default distance", () => {
|
|
173
163
|
it("should use default distance of 1", () => {
|
|
174
164
|
const s = sketch("xy", () => {
|
|
@@ -131,20 +131,4 @@ describe("polygon", () => {
|
|
|
131
131
|
expect(bbox.centerY).toBeCloseTo(30, 0);
|
|
132
132
|
});
|
|
133
133
|
});
|
|
134
|
-
describe("standalone with targetPlane", () => {
|
|
135
|
-
it("should create a polygon on a specific plane", () => {
|
|
136
|
-
polygon("xy", 5, 50);
|
|
137
|
-
const e = extrude(10);
|
|
138
|
-
render();
|
|
139
|
-
const solid = e.getShapes()[0];
|
|
140
|
-
expect(solid.getFaces()).toHaveLength(7);
|
|
141
|
-
});
|
|
142
|
-
it("should create a circumscribed polygon on a specific plane", () => {
|
|
143
|
-
polygon("xy", 6, 60, "circumscribed");
|
|
144
|
-
const e = extrude(10);
|
|
145
|
-
render();
|
|
146
|
-
const solid = e.getShapes()[0];
|
|
147
|
-
expect(solid.getFaces()).toHaveLength(8);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
134
|
});
|
|
@@ -52,15 +52,4 @@ describe("rect", () => {
|
|
|
52
52
|
expect(solid.getEdges().length).toBeGreaterThan(12);
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
|
-
describe("standalone with targetPlane", () => {
|
|
56
|
-
it("should create a rectangle on a specific plane", () => {
|
|
57
|
-
rect("xy", 80, 40);
|
|
58
|
-
const e = extrude(10);
|
|
59
|
-
render();
|
|
60
|
-
const solid = e.getShapes()[0];
|
|
61
|
-
const bbox = ShapeOps.getBoundingBox(solid);
|
|
62
|
-
expect(bbox.maxX - bbox.minX).toBeCloseTo(80, 0);
|
|
63
|
-
expect(bbox.maxY - bbox.minY).toBeCloseTo(40, 0);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
55
|
});
|
|
@@ -33,12 +33,4 @@ describe("slot", () => {
|
|
|
33
33
|
expect(arcEdges.length).toBeGreaterThan(0);
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
|
-
describe("standalone with targetPlane", () => {
|
|
37
|
-
it("should create a slot on a specific plane", () => {
|
|
38
|
-
slot("xy", 60, 10);
|
|
39
|
-
const e = extrude(10);
|
|
40
|
-
render();
|
|
41
|
-
expect(e.getShapes()).toHaveLength(1);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
36
|
});
|
|
@@ -4,7 +4,7 @@ import sketch from "../../core/sketch.js";
|
|
|
4
4
|
import extrude from "../../core/extrude.js";
|
|
5
5
|
import fillet from "../../core/fillet.js";
|
|
6
6
|
import fuse from "../../core/fuse.js";
|
|
7
|
-
import { hMove, rect } from "../../core/2d/index.js";
|
|
7
|
+
import { arc, hMove, rect, vLine, hLine, vMove, back } from "../../core/2d/index.js";
|
|
8
8
|
import { getEdgesByType } from "../utils.js";
|
|
9
9
|
describe("fillet2d", () => {
|
|
10
10
|
setupOC();
|
|
@@ -51,6 +51,42 @@ describe("fillet2d", () => {
|
|
|
51
51
|
// 4 shortened sides per face (8) + 4 vertical edges each split into segments (8) = 16
|
|
52
52
|
expect(getEdgesByType(solid, "line")).toHaveLength(16);
|
|
53
53
|
});
|
|
54
|
+
it("should fillet a corner passed as radius-first spread args", () => {
|
|
55
|
+
const s = sketch("xy", () => {
|
|
56
|
+
const l1 = hLine(40);
|
|
57
|
+
const l2 = vLine(-40);
|
|
58
|
+
fillet(8, l1, l2);
|
|
59
|
+
});
|
|
60
|
+
render();
|
|
61
|
+
// The corner is rounded: each line is shortened and one fillet arc inserted.
|
|
62
|
+
// Before the radius-first spread dispatch existed, fillet(8, l1, l2) was a
|
|
63
|
+
// silent no-op (returned undefined) and produced 0 arcs.
|
|
64
|
+
expect(getEdgesByType(s.getEdges(), "arc")).toHaveLength(1);
|
|
65
|
+
expect(getEdgesByType(s.getEdges(), "line")).toHaveLength(2);
|
|
66
|
+
});
|
|
67
|
+
it("fillets an arc/line corner passed as radius-first spread args", () => {
|
|
68
|
+
// The exact shape from the original bug report: a fillet between an arc (l1)
|
|
69
|
+
// and a line (l2) requested as fillet(radius, l2, l1). Before the radius-first
|
|
70
|
+
// spread form was dispatched, this was a silent no-op while fillet(8) (all
|
|
71
|
+
// corners) rounded the same arc/line corner.
|
|
72
|
+
const s = sketch("front", () => {
|
|
73
|
+
vMove(10);
|
|
74
|
+
const gl = hLine(140).guide().centered();
|
|
75
|
+
back();
|
|
76
|
+
vMove(33 + 16);
|
|
77
|
+
hLine(8);
|
|
78
|
+
vLine(-16);
|
|
79
|
+
hLine(15 - 8);
|
|
80
|
+
const l1 = arc(gl.end()).radius(55);
|
|
81
|
+
const l2 = vLine(-10);
|
|
82
|
+
fillet(8, l2, l1);
|
|
83
|
+
});
|
|
84
|
+
render();
|
|
85
|
+
// Baseline (no fillet) is 1 arc; rounding the arc/line corner shortens the
|
|
86
|
+
// original arc and inserts the fillet arc -> 2 arcs. The array form
|
|
87
|
+
// fillet([l2, l1], 8) produces the identical result.
|
|
88
|
+
expect(getEdgesByType(s.getEdges(), "arc")).toHaveLength(2);
|
|
89
|
+
});
|
|
54
90
|
});
|
|
55
91
|
describe("fillet radius", () => {
|
|
56
92
|
it("should use default radius of 1", () => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|