fluidcad 0.0.36 → 0.0.38
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/LICENSE.txt +21 -504
- package/README.md +1 -1
- package/lib/dist/common/edge.d.ts +1 -1
- package/lib/dist/common/face.d.ts +1 -1
- package/lib/dist/common/scene-object.d.ts +6 -0
- package/lib/dist/common/scene-object.js +8 -0
- package/lib/dist/common/shape-factory.d.ts +1 -1
- package/lib/dist/common/shape-history-tracker.d.ts +1 -1
- package/lib/dist/common/shape.d.ts +1 -1
- package/lib/dist/common/solid.d.ts +1 -1
- package/lib/dist/common/transformable-primitive.d.ts +12 -1
- package/lib/dist/common/transformable-primitive.js +27 -0
- package/lib/dist/common/vertex.d.ts +1 -1
- package/lib/dist/common/wire.d.ts +1 -1
- package/lib/dist/core/2d/index.d.ts +1 -0
- package/lib/dist/core/2d/index.js +1 -0
- package/lib/dist/core/2d/text.d.ts +30 -0
- package/lib/dist/core/2d/text.js +37 -0
- package/lib/dist/core/helix.d.ts +20 -0
- package/lib/dist/core/helix.js +36 -0
- package/lib/dist/core/index.d.ts +3 -1
- package/lib/dist/core/index.js +2 -0
- package/lib/dist/core/interfaces.d.ts +180 -0
- package/lib/dist/core/plane.d.ts +26 -6
- package/lib/dist/core/plane.js +21 -44
- package/lib/dist/core/wrap.d.ts +17 -0
- package/lib/dist/core/wrap.js +39 -0
- package/lib/dist/features/2d/offset.js +2 -2
- package/lib/dist/features/2d/text.d.ts +67 -0
- package/lib/dist/features/2d/text.js +320 -0
- package/lib/dist/features/cylinder.d.ts +3 -1
- package/lib/dist/features/cylinder.js +5 -2
- package/lib/dist/features/extrude-base.d.ts +1 -0
- package/lib/dist/features/extrude-to-face.d.ts +1 -0
- package/lib/dist/features/extrude-to-face.js +6 -0
- package/lib/dist/features/fillet.d.ts +1 -1
- package/lib/dist/features/helix.d.ts +41 -0
- package/lib/dist/features/helix.js +337 -0
- package/lib/dist/features/plane-from-object.d.ts +16 -4
- package/lib/dist/features/plane-from-object.js +101 -8
- package/lib/dist/features/select.js +32 -8
- package/lib/dist/features/simple-extruder.d.ts +1 -1
- package/lib/dist/features/simple-extruder.js +7 -2
- package/lib/dist/features/sphere.d.ts +3 -1
- package/lib/dist/features/sphere.js +5 -2
- package/lib/dist/features/sweep.js +7 -2
- package/lib/dist/features/wrap.d.ts +39 -0
- package/lib/dist/features/wrap.js +116 -0
- package/lib/dist/filters/edge/belongs-to-face.d.ts +3 -1
- package/lib/dist/filters/edge/belongs-to-face.js +14 -10
- package/lib/dist/filters/filter.d.ts +1 -1
- package/lib/dist/filters/from-object.d.ts +1 -1
- package/lib/dist/filters/tangent-expander.d.ts +1 -1
- package/lib/dist/filters/tangent-expander.js +57 -40
- package/lib/dist/helpers/scene-helpers.d.ts +2 -0
- package/lib/dist/helpers/scene-helpers.js +1 -1
- package/lib/dist/index.d.ts +2 -0
- package/lib/dist/index.js +3 -1
- package/lib/dist/io/file-import.d.ts +7 -0
- package/lib/dist/io/file-import.js +28 -1
- package/lib/dist/io/font-registry.d.ts +45 -0
- package/lib/dist/io/font-registry.js +272 -0
- package/lib/dist/math/bspline-interpolation.d.ts +29 -0
- package/lib/dist/math/bspline-interpolation.js +194 -0
- package/lib/dist/oc/boolean-ops.d.ts +3 -1
- package/lib/dist/oc/boolean-ops.js +15 -1
- package/lib/dist/oc/color-transfer.d.ts +1 -1
- package/lib/dist/oc/constraints/constraint-helpers.d.ts +4 -4
- package/lib/dist/oc/constraints/curve/tangent-circle-solver.js +10 -9
- package/lib/dist/oc/constraints/curve/tangent-line-solver.js +5 -6
- package/lib/dist/oc/convert.d.ts +1 -1
- package/lib/dist/oc/draft-ops.d.ts +1 -1
- package/lib/dist/oc/edge-ops.d.ts +2 -2
- package/lib/dist/oc/edge-ops.js +13 -14
- package/lib/dist/oc/edge-props.d.ts +1 -1
- package/lib/dist/oc/edge-query.d.ts +1 -1
- package/lib/dist/oc/edge-query.js +3 -8
- package/lib/dist/oc/errors.d.ts +8 -0
- package/lib/dist/oc/errors.js +27 -0
- package/lib/dist/oc/explorer.d.ts +2 -2
- package/lib/dist/oc/extrude-ops.d.ts +28 -2
- package/lib/dist/oc/extrude-ops.js +56 -7
- package/lib/dist/oc/face-ops.d.ts +2 -1
- package/lib/dist/oc/face-ops.js +11 -0
- package/lib/dist/oc/face-props.d.ts +1 -1
- package/lib/dist/oc/face-query.d.ts +12 -1
- package/lib/dist/oc/face-query.js +39 -0
- package/lib/dist/oc/fillet-ops.d.ts +1 -1
- package/lib/dist/oc/fillet-ops.js +4 -4
- package/lib/dist/oc/geometry.d.ts +1 -1
- package/lib/dist/oc/geometry.js +12 -14
- package/lib/dist/oc/helix-ops.d.ts +37 -0
- package/lib/dist/oc/helix-ops.js +88 -0
- package/lib/dist/oc/hit-test.d.ts +1 -1
- package/lib/dist/oc/index.d.ts +4 -0
- package/lib/dist/oc/index.js +2 -0
- package/lib/dist/oc/init.d.ts +1 -1
- package/lib/dist/oc/init.js +1 -1
- package/lib/dist/oc/intersection.js +1 -1
- package/lib/dist/oc/io.d.ts +6 -6
- package/lib/dist/oc/io.js +31 -24
- package/lib/dist/oc/measure/classify.d.ts +34 -0
- package/lib/dist/oc/measure/classify.js +246 -0
- package/lib/dist/oc/measure/measure-ops.d.ts +9 -0
- package/lib/dist/oc/measure/measure-ops.js +210 -0
- package/lib/dist/oc/measure/measure-types.d.ts +39 -0
- package/lib/dist/oc/measure/measure-types.js +1 -0
- package/lib/dist/oc/measure/sampling.d.ts +9 -0
- package/lib/dist/oc/measure/sampling.js +77 -0
- package/lib/dist/oc/measure/vec.d.ts +13 -0
- package/lib/dist/oc/measure/vec.js +23 -0
- package/lib/dist/oc/mesh.d.ts +1 -1
- package/lib/dist/oc/mesh.js +40 -28
- package/lib/dist/oc/path-sampler.d.ts +29 -0
- package/lib/dist/oc/path-sampler.js +63 -0
- package/lib/dist/oc/props.d.ts +1 -1
- package/lib/dist/oc/props.js +4 -1
- package/lib/dist/oc/shape-hash.d.ts +26 -0
- package/lib/dist/oc/shape-hash.js +32 -0
- package/lib/dist/oc/shape-ops.d.ts +5 -3
- package/lib/dist/oc/shape-ops.js +6 -5
- package/lib/dist/oc/sweep-ops.d.ts +13 -1
- package/lib/dist/oc/sweep-ops.js +174 -18
- package/lib/dist/oc/text-outline.d.ts +62 -0
- package/lib/dist/oc/text-outline.js +212 -0
- package/lib/dist/oc/thin-face-maker.d.ts +0 -19
- package/lib/dist/oc/thin-face-maker.js +3 -68
- package/lib/dist/oc/topology-index.d.ts +1 -1
- package/lib/dist/oc/vertex-ops.d.ts +1 -1
- package/lib/dist/oc/wire-ops.d.ts +18 -3
- package/lib/dist/oc/wire-ops.js +56 -5
- package/lib/dist/oc/wrap-development.d.ts +105 -0
- package/lib/dist/oc/wrap-development.js +179 -0
- package/lib/dist/oc/wrap-ops.d.ts +100 -0
- package/lib/dist/oc/wrap-ops.js +406 -0
- package/lib/dist/rendering/render-solid.js +10 -2
- package/lib/dist/scene-manager.d.ts +2 -0
- package/lib/dist/scene-manager.js +29 -0
- package/lib/dist/tests/features/2d/offset.test.js +74 -1
- package/lib/dist/tests/features/cylinder-curve-filter.test.js +3 -3
- package/lib/dist/tests/features/extrude-to-face.test.js +38 -1
- package/lib/dist/tests/features/helix.test.d.ts +1 -0
- package/lib/dist/tests/features/helix.test.js +295 -0
- package/lib/dist/tests/features/plane.test.js +95 -0
- package/lib/dist/tests/features/repeat-primitive.test.d.ts +1 -0
- package/lib/dist/tests/features/repeat-primitive.test.js +60 -0
- package/lib/dist/tests/features/rib.test.js +6 -1
- package/lib/dist/tests/features/sweep.test.js +170 -1
- package/lib/dist/tests/features/text.test.d.ts +1 -0
- package/lib/dist/tests/features/text.test.js +347 -0
- package/lib/dist/tests/features/wrap-development.test.d.ts +1 -0
- package/lib/dist/tests/features/wrap-development.test.js +130 -0
- package/lib/dist/tests/features/wrap-extruded-target.test.d.ts +1 -0
- package/lib/dist/tests/features/wrap-extruded-target.test.js +106 -0
- package/lib/dist/tests/features/wrap-repeat.test.d.ts +1 -0
- package/lib/dist/tests/features/wrap-repeat.test.js +93 -0
- package/lib/dist/tests/features/wrap.test.d.ts +1 -0
- package/lib/dist/tests/features/wrap.test.js +331 -0
- package/lib/dist/tests/math/bspline-interpolation.test.d.ts +1 -0
- package/lib/dist/tests/math/bspline-interpolation.test.js +119 -0
- package/lib/dist/tests/measure.test.d.ts +1 -0
- package/lib/dist/tests/measure.test.js +288 -0
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/llm-docs/api/helix.md +64 -0
- package/llm-docs/api/index.json +11 -2
- package/llm-docs/api/text.md +52 -0
- package/llm-docs/api/types/helix.md +105 -0
- package/llm-docs/api/types/text.md +138 -0
- package/llm-docs/api/types/wrap.md +131 -0
- package/llm-docs/api/wrap.md +62 -0
- package/llm-docs/index.json +121 -1
- package/mcp/dist/server.js +20 -1
- package/mcp/dist/tools/inspection.d.ts +17 -0
- package/mcp/dist/tools/inspection.js +14 -0
- package/package.json +7 -3
- package/server/dist/fluidcad-server.d.ts +11 -1
- package/server/dist/fluidcad-server.js +21 -1
- package/server/dist/index.js +4 -2
- package/server/dist/preferences.d.ts +4 -0
- package/server/dist/preferences.js +2 -0
- package/server/dist/routes/measure.d.ts +3 -0
- package/server/dist/routes/measure.js +32 -0
- package/server/dist/routes/params.js +1 -1
- package/server/dist/routes/preferences.js +6 -0
- package/server/dist/routes/sketch-edits.js +2 -1
- package/ui/dist/assets/{index-MRqwG9Vh.js → index-D8zV21wB.js} +149 -102
- package/ui/dist/assets/{index-CDJmUpFI.css → index-dAFdg2Un.css} +1 -1
- package/ui/dist/index.html +2 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getOC } from "./init.js";
|
|
2
|
+
/**
|
|
3
|
+
* Produces stable, `IsSame`-consistent integer keys for TopoDS shapes so they can
|
|
4
|
+
* be used as JS `Map`/`Set` keys.
|
|
5
|
+
*
|
|
6
|
+
* OCCT 8.0 removed `TopoDS_Shape::HashCode`. This interns shapes in a
|
|
7
|
+
* `TopTools_IndexedMapOfShape` — whose hasher compares keys with `IsSame` — and
|
|
8
|
+
* returns each shape's stable 1-based index. That gives the same bucketing the old
|
|
9
|
+
* `HashCode` + `IsSame` check provided, but collision-free (the same `IsSame`
|
|
10
|
+
* sub-shape always maps to the same key, distinct sub-shapes never collide).
|
|
11
|
+
*
|
|
12
|
+
* Wraps an OCCT map, so it owns native memory: scope one per operation and
|
|
13
|
+
* `delete()` it when done. Follows the same indexed-map convention as
|
|
14
|
+
* {@link TopologyIndex}.
|
|
15
|
+
*/
|
|
16
|
+
export class ShapeHasher {
|
|
17
|
+
map;
|
|
18
|
+
constructor() {
|
|
19
|
+
this.map = new (getOC().TopTools_IndexedMapOfShape)();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Stable, `IsSame`-consistent key for `shape`. Interns the shape on first use; an
|
|
23
|
+
* as-yet-unseen shape gets a fresh key, so a `Map` lookup against keys from other
|
|
24
|
+
* shapes correctly misses.
|
|
25
|
+
*/
|
|
26
|
+
key(shape) {
|
|
27
|
+
return this.map.Add(shape);
|
|
28
|
+
}
|
|
29
|
+
delete() {
|
|
30
|
+
this.map.delete();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TopTools_ListOfShape, TopoDS_Shape } from "
|
|
1
|
+
import type { TopTools_ListOfShape, TopoDS_Shape } from "ocjs-fluidcad";
|
|
2
2
|
import { Matrix4 } from "../math/matrix4.js";
|
|
3
3
|
import { Shape } from "../common/shape.js";
|
|
4
4
|
import { Face } from "../common/face.js";
|
|
@@ -26,14 +26,16 @@ export declare class ShapeOps {
|
|
|
26
26
|
static getBoundingBox(shape: Shape | TopoDS_Shape): BoundingBox;
|
|
27
27
|
static getBoundingBoxRaw(shape: TopoDS_Shape): BoundingBox;
|
|
28
28
|
static makeCompound(shapes: Shape[]): Shape;
|
|
29
|
-
static makeCompoundRaw(shapes: TopoDS_Shape[]): import("
|
|
29
|
+
static makeCompoundRaw(shapes: TopoDS_Shape[]): import("ocjs-fluidcad").TopoDS_Compound;
|
|
30
30
|
static cleanShape(shape: Shape): Shape;
|
|
31
31
|
/**
|
|
32
32
|
* Variant of `cleanShape` that preserves UnifySameDomain lineage via
|
|
33
33
|
* `BRepTools_History`. Caller must call `dispose()` exactly once to free
|
|
34
34
|
* the OC wrappers.
|
|
35
35
|
*/
|
|
36
|
-
static cleanShapeWithLineage(shape: Shape
|
|
36
|
+
static cleanShapeWithLineage(shape: Shape, opts?: {
|
|
37
|
+
skipSimplify?: boolean;
|
|
38
|
+
}): CleanShapeLineage;
|
|
37
39
|
static cleanShapeRaw(shape: TopoDS_Shape): TopoDS_Shape;
|
|
38
40
|
static shapeListToArray(list: TopTools_ListOfShape): TopoDS_Shape[];
|
|
39
41
|
}
|
package/lib/dist/oc/shape-ops.js
CHANGED
|
@@ -78,11 +78,13 @@ export class ShapeOps {
|
|
|
78
78
|
* `BRepTools_History`. Caller must call `dispose()` exactly once to free
|
|
79
79
|
* the OC wrappers.
|
|
80
80
|
*/
|
|
81
|
-
static cleanShapeWithLineage(shape) {
|
|
81
|
+
static cleanShapeWithLineage(shape, opts) {
|
|
82
82
|
const oc = getOC();
|
|
83
83
|
const FACE = oc.TopAbs_ShapeEnum.TopAbs_FACE;
|
|
84
84
|
const EDGE = oc.TopAbs_ShapeEnum.TopAbs_EDGE;
|
|
85
|
-
|
|
85
|
+
// skipSimplify: pass unifyFaces=false to avoid the slow face-merging step
|
|
86
|
+
// that hangs on tangent contact along curves (e.g., helix sweep + cylinder).
|
|
87
|
+
const unify = new oc.ShapeUpgrade_UnifySameDomain(shape.getShape(), false, opts?.skipSimplify ? false : true, false);
|
|
86
88
|
unify.Build();
|
|
87
89
|
const cleanedRaw = unify.Shape();
|
|
88
90
|
// Pre-compute which faces/edges this cleanup saw so the remap can
|
|
@@ -127,15 +129,14 @@ export class ShapeOps {
|
|
|
127
129
|
dispose,
|
|
128
130
|
};
|
|
129
131
|
}
|
|
130
|
-
const
|
|
131
|
-
const history = historyHandle.get();
|
|
132
|
+
const history = unify.History();
|
|
132
133
|
let disposed = false;
|
|
133
134
|
const dispose = () => {
|
|
134
135
|
if (disposed) {
|
|
135
136
|
return;
|
|
136
137
|
}
|
|
137
138
|
disposed = true;
|
|
138
|
-
|
|
139
|
+
history.delete();
|
|
139
140
|
unify.delete();
|
|
140
141
|
knownFaces.delete();
|
|
141
142
|
knownEdges.delete();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TopoDS_Shape } from "
|
|
1
|
+
import type { TopoDS_Shape } from "ocjs-fluidcad";
|
|
2
2
|
import { Solid } from "../common/solid.js";
|
|
3
3
|
import { Wire } from "../common/wire.js";
|
|
4
4
|
import { Face } from "../common/face.js";
|
|
@@ -8,5 +8,17 @@ export interface SweepResult {
|
|
|
8
8
|
lastShape: TopoDS_Shape;
|
|
9
9
|
}
|
|
10
10
|
export declare class SweepOps {
|
|
11
|
+
private static readonly MAX_PIPE_SEGMENTS;
|
|
11
12
|
static makeSweep(spineWire: Wire, profileFaces: Face[]): SweepResult;
|
|
13
|
+
/** Sweep a single wire along the spine with a fixed binormal. */
|
|
14
|
+
private static sweepWire;
|
|
15
|
+
/**
|
|
16
|
+
* The axis the spine's tangent rotates around, = normalize(Σ Tᵢ × Tᵢ₊₁) over
|
|
17
|
+
* tangents sampled along the spine. For a planar spine this is the plane
|
|
18
|
+
* normal; for a helix it is the coil axis. For a straight spine the tangent
|
|
19
|
+
* is constant, every cross product vanishes, and it returns null.
|
|
20
|
+
*/
|
|
21
|
+
private static tangentRotationAxis;
|
|
22
|
+
/** Unit tangent of the spine wire at its first parameter. */
|
|
23
|
+
private static getSpineTangent;
|
|
12
24
|
}
|
package/lib/dist/oc/sweep-ops.js
CHANGED
|
@@ -1,32 +1,107 @@
|
|
|
1
1
|
import { getOC } from "./init.js";
|
|
2
|
+
import { Convert } from "./convert.js";
|
|
2
3
|
import { Explorer } from "./explorer.js";
|
|
4
|
+
import { ShapeOps } from "./shape-ops.js";
|
|
3
5
|
import { Solid } from "../common/solid.js";
|
|
6
|
+
import { Vector3d } from "../math/vector3d.js";
|
|
4
7
|
export class SweepOps {
|
|
8
|
+
// Ceiling for MakePipeShell's swept-surface approximation. OCCT's default
|
|
9
|
+
// (~30) is too small for tapered or tightly-coiled helical spines, whose
|
|
10
|
+
// swept surfaces need many spans to fit within tolerance — at the default the
|
|
11
|
+
// build silently fails (BRepBuilderAPI_PipeNotDone). This only caps the
|
|
12
|
+
// adaptive fit; simple spines converge far below it at no extra cost.
|
|
13
|
+
static MAX_PIPE_SEGMENTS = 1000;
|
|
5
14
|
static makeSweep(spineWire, profileFaces) {
|
|
6
15
|
const oc = getOC();
|
|
7
16
|
const allSolids = [];
|
|
8
17
|
let firstShape = null;
|
|
9
18
|
let lastShape = null;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
const profilePlane = profileFaces[0].getPlane();
|
|
20
|
+
// Fixed binormal for MakePipeShell's `SetMode`: it locks the section's
|
|
21
|
+
// "up", so the profile keeps a constant angle to it instead of twisting
|
|
22
|
+
// along the spine. The correct direction is the axis the spine's tangent
|
|
23
|
+
// rotates around — the plane normal for a planar spine, the coil axis for a
|
|
24
|
+
// helix. The tangent keeps a constant, non-zero angle to that axis, so the
|
|
25
|
+
// section never flips and the result is a clean coil.
|
|
26
|
+
//
|
|
27
|
+
// The profile plane's own "up" (used previously) only works when it happens
|
|
28
|
+
// to equal that axis — true for a profile sketched on a world plane, but
|
|
29
|
+
// NOT for a plane built off a helix, whose in-plane axes are arbitrary.
|
|
30
|
+
// A wrong (e.g. roughly horizontal) binormal lets the helix tangent rotate
|
|
31
|
+
// into it, collapsing `Normal = BiNormal × Tangent` ~twice per turn and
|
|
32
|
+
// shredding the section into a self-intersecting ribbon. A straight spine
|
|
33
|
+
// has no rotation axis (the cross products vanish); there the profile's up
|
|
34
|
+
// is well-defined and never aligns with the constant tangent, so use it.
|
|
35
|
+
const spineAxis = SweepOps.tangentRotationAxis(spineWire.getShape());
|
|
36
|
+
const binormalVec = spineAxis ?? profilePlane.yDirection;
|
|
37
|
+
const [binormalDir, disposeBinormal] = Convert.toGpDir(binormalVec);
|
|
38
|
+
// `Add(_, false, true)` (no contact, with correction) rotates the profile
|
|
39
|
+
// to sit perpendicular to the spine tangent, about an axis given by
|
|
40
|
+
// `profile.normal × spine.tangent`. That axis is undefined when the two are
|
|
41
|
+
// anti-parallel — but then the profile plane is *already* perpendicular to
|
|
42
|
+
// the spine (its normal is ∥ -tangent), so no correction is needed: skip it
|
|
43
|
+
// and keep the profile's drawn position.
|
|
44
|
+
const spineTangent = SweepOps.getSpineTangent(spineWire.getShape());
|
|
45
|
+
const isAntiParallel = profilePlane.normal.dot(spineTangent) < -0.999;
|
|
46
|
+
const withCorrection = !isAntiParallel;
|
|
47
|
+
try {
|
|
48
|
+
for (const face of profileFaces) {
|
|
49
|
+
const ocFace = oc.TopoDS.Face(face.getShape());
|
|
50
|
+
const outerWire = oc.BRepTools.OuterWire(ocFace);
|
|
51
|
+
const innerWires = face.getWires()
|
|
52
|
+
.map(w => w.getShape())
|
|
53
|
+
.filter(w => !w.IsSame(outerWire));
|
|
54
|
+
const outer = SweepOps.sweepWire(spineWire.getShape(), outerWire, binormalDir, withCorrection);
|
|
55
|
+
let resultSolid = outer.solid;
|
|
56
|
+
let resultFirst = outer.firstFace;
|
|
57
|
+
let resultLast = outer.lastFace;
|
|
58
|
+
for (const innerWire of innerWires) {
|
|
59
|
+
const inner = SweepOps.sweepWire(spineWire.getShape(), oc.TopoDS.Wire(innerWire), binormalDir, withCorrection);
|
|
60
|
+
const stockList = new oc.TopTools_ListOfShape();
|
|
61
|
+
stockList.Append(resultSolid);
|
|
62
|
+
const toolList = new oc.TopTools_ListOfShape();
|
|
63
|
+
toolList.Append(inner.solid);
|
|
64
|
+
const cut = new oc.BRepAlgoAPI_Cut();
|
|
65
|
+
cut.SetArguments(stockList);
|
|
66
|
+
cut.SetTools(toolList);
|
|
67
|
+
const progress = new oc.Message_ProgressRange();
|
|
68
|
+
cut.Build(progress);
|
|
69
|
+
progress.delete();
|
|
70
|
+
if (!cut.IsDone()) {
|
|
71
|
+
cut.delete();
|
|
72
|
+
stockList.delete();
|
|
73
|
+
toolList.delete();
|
|
74
|
+
throw new Error("Sweep hole cut failed.");
|
|
75
|
+
}
|
|
76
|
+
const newSolid = cut.Shape();
|
|
77
|
+
// Track first/last faces through the cut. The outer's start/end
|
|
78
|
+
// face becomes a hole-bearing face after cutting through it.
|
|
79
|
+
const modFirst = ShapeOps.shapeListToArray(cut.Modified(resultFirst));
|
|
80
|
+
const modLast = ShapeOps.shapeListToArray(cut.Modified(resultLast));
|
|
81
|
+
if (modFirst.length > 0) {
|
|
82
|
+
resultFirst = modFirst[0];
|
|
83
|
+
}
|
|
84
|
+
if (modLast.length > 0) {
|
|
85
|
+
resultLast = modLast[0];
|
|
86
|
+
}
|
|
87
|
+
cut.delete();
|
|
88
|
+
stockList.delete();
|
|
89
|
+
toolList.delete();
|
|
90
|
+
resultSolid = newSolid;
|
|
91
|
+
}
|
|
92
|
+
if (!firstShape) {
|
|
93
|
+
firstShape = resultFirst;
|
|
94
|
+
lastShape = resultLast;
|
|
95
|
+
}
|
|
96
|
+
const solids = Explorer.findShapes(resultSolid, Explorer.getOcShapeType("solid"));
|
|
97
|
+
for (const s of solids) {
|
|
98
|
+
allSolids.push(Solid.fromTopoDSSolid(Explorer.toSolid(s)));
|
|
99
|
+
}
|
|
28
100
|
}
|
|
29
101
|
}
|
|
102
|
+
finally {
|
|
103
|
+
disposeBinormal();
|
|
104
|
+
}
|
|
30
105
|
if (allSolids.length === 0) {
|
|
31
106
|
throw new Error("Sweep produced no solids.");
|
|
32
107
|
}
|
|
@@ -36,4 +111,85 @@ export class SweepOps {
|
|
|
36
111
|
lastShape: lastShape,
|
|
37
112
|
};
|
|
38
113
|
}
|
|
114
|
+
/** Sweep a single wire along the spine with a fixed binormal. */
|
|
115
|
+
static sweepWire(spine, profile, binormalDir, withCorrection) {
|
|
116
|
+
const oc = getOC();
|
|
117
|
+
const pipe = new oc.BRepOffsetAPI_MakePipeShell(spine);
|
|
118
|
+
// Fixed binormal (the spine's tangent-rotation axis; see makeSweep): keeps
|
|
119
|
+
// the swept section from twisting — a clean coil rather than a wobbling
|
|
120
|
+
// ribbon — and is well-defined on straight spines, where Frenet is not
|
|
121
|
+
// (zero curvature ⇒ undefined normal).
|
|
122
|
+
pipe.SetMode(binormalDir);
|
|
123
|
+
// Give the swept-surface approximation enough spans for tapered/tight
|
|
124
|
+
// helical spines (see MAX_PIPE_SEGMENTS) — at OCCT's default budget the
|
|
125
|
+
// build fails on, e.g., a conical helix or a many-turn helix on a cone face.
|
|
126
|
+
pipe.SetMaxSegments(SweepOps.MAX_PIPE_SEGMENTS);
|
|
127
|
+
pipe.Add(profile, false, withCorrection);
|
|
128
|
+
const progress = new oc.Message_ProgressRange();
|
|
129
|
+
pipe.Build(progress);
|
|
130
|
+
progress.delete();
|
|
131
|
+
if (!pipe.IsDone()) {
|
|
132
|
+
pipe.delete();
|
|
133
|
+
throw new Error("Sweep operation failed.");
|
|
134
|
+
}
|
|
135
|
+
if (!pipe.MakeSolid()) {
|
|
136
|
+
pipe.delete();
|
|
137
|
+
throw new Error("Sweep failed to produce a solid.");
|
|
138
|
+
}
|
|
139
|
+
const firstFace = pipe.FirstShape();
|
|
140
|
+
const lastFace = pipe.LastShape();
|
|
141
|
+
const solid = pipe.Shape();
|
|
142
|
+
pipe.delete();
|
|
143
|
+
return { solid, firstFace, lastFace };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The axis the spine's tangent rotates around, = normalize(Σ Tᵢ × Tᵢ₊₁) over
|
|
147
|
+
* tangents sampled along the spine. For a planar spine this is the plane
|
|
148
|
+
* normal; for a helix it is the coil axis. For a straight spine the tangent
|
|
149
|
+
* is constant, every cross product vanishes, and it returns null.
|
|
150
|
+
*/
|
|
151
|
+
static tangentRotationAxis(spine) {
|
|
152
|
+
const oc = getOC();
|
|
153
|
+
const adaptor = new oc.BRepAdaptor_CompCurve(spine, false);
|
|
154
|
+
const u0 = adaptor.FirstParameter();
|
|
155
|
+
const u1 = adaptor.LastParameter();
|
|
156
|
+
const SAMPLES = 64;
|
|
157
|
+
const tangents = [];
|
|
158
|
+
const pnt = new oc.gp_Pnt();
|
|
159
|
+
const vec = new oc.gp_Vec();
|
|
160
|
+
for (let i = 0; i <= SAMPLES; i++) {
|
|
161
|
+
const u = u0 + ((u1 - u0) * i) / SAMPLES;
|
|
162
|
+
adaptor.D1(u, pnt, vec);
|
|
163
|
+
const t = new Vector3d(vec.X(), vec.Y(), vec.Z());
|
|
164
|
+
if (t.length() > 1e-9) {
|
|
165
|
+
tangents.push(t.normalize());
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
pnt.delete();
|
|
169
|
+
vec.delete();
|
|
170
|
+
adaptor.delete();
|
|
171
|
+
let axis = new Vector3d(0, 0, 0);
|
|
172
|
+
for (let i = 0; i + 1 < tangents.length; i++) {
|
|
173
|
+
axis = axis.add(tangents[i].cross(tangents[i + 1]));
|
|
174
|
+
}
|
|
175
|
+
if (axis.length() < 1e-6) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return axis.normalize();
|
|
179
|
+
}
|
|
180
|
+
/** Unit tangent of the spine wire at its first parameter. */
|
|
181
|
+
static getSpineTangent(spine) {
|
|
182
|
+
const oc = getOC();
|
|
183
|
+
const adaptor = new oc.BRepAdaptor_CompCurve(spine, false);
|
|
184
|
+
const u0 = adaptor.FirstParameter();
|
|
185
|
+
const pnt = new oc.gp_Pnt();
|
|
186
|
+
const tan = new oc.gp_Vec();
|
|
187
|
+
adaptor.D1(u0, pnt, tan);
|
|
188
|
+
tan.Normalize();
|
|
189
|
+
const tangent = new Vector3d(tan.X(), tan.Y(), tan.Z());
|
|
190
|
+
pnt.delete();
|
|
191
|
+
tan.delete();
|
|
192
|
+
adaptor.delete();
|
|
193
|
+
return tangent;
|
|
194
|
+
}
|
|
39
195
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Font } from "fontkit";
|
|
2
|
+
import { Edge } from "../common/edge.js";
|
|
3
|
+
import { Plane } from "../math/plane.js";
|
|
4
|
+
import { Point, Point2D } from "../math/point.js";
|
|
5
|
+
import { Vector3d } from "../math/vector3d.js";
|
|
6
|
+
export type TextAlign = "left" | "center" | "right" | "start" | "end" | "space-between" | "space-around";
|
|
7
|
+
export interface TextLayoutOptions {
|
|
8
|
+
/** Em size in model units (mm). */
|
|
9
|
+
size: number;
|
|
10
|
+
align: TextAlign;
|
|
11
|
+
/** Multiplier applied to the font's natural line height (default 1). */
|
|
12
|
+
lineSpacing: number;
|
|
13
|
+
/** Extra advance added between glyphs, in model units (default 0). */
|
|
14
|
+
letterSpacing: number;
|
|
15
|
+
}
|
|
16
|
+
export interface TextPathOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Evaluates a point + unit tangent at an arc-length distance along the
|
|
19
|
+
* path (expected to wrap on closed paths and extrapolate on open ones).
|
|
20
|
+
*/
|
|
21
|
+
evalAt(s: number): {
|
|
22
|
+
point: Point;
|
|
23
|
+
tangent: Vector3d;
|
|
24
|
+
};
|
|
25
|
+
/** Total path length in model units; used for alignment. */
|
|
26
|
+
length: number;
|
|
27
|
+
/** Unit normal of the path's plane. Glyph "up" is `normal × tangent`. */
|
|
28
|
+
normal: Vector3d;
|
|
29
|
+
/** Perpendicular baseline shift in model units (toward glyph "up"). */
|
|
30
|
+
offset: number;
|
|
31
|
+
/** Extra arc-length shift of the text start along the path. */
|
|
32
|
+
startAt: number;
|
|
33
|
+
/** Mirror the text to the other side of the path, reversing direction. */
|
|
34
|
+
flip: boolean;
|
|
35
|
+
/** Whether the path is a closed loop (affects `space-between` gap distribution). */
|
|
36
|
+
closed: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Turns a text string into outline edges laid out on `plane`, suitable for
|
|
40
|
+
* FaceMaker2 (which resolves letter counters like the holes in `o`/`A`/`e` as
|
|
41
|
+
* faces-with-holes). Glyph outlines come from fontkit as M/L/Q/C/Z commands in
|
|
42
|
+
* font units; quadratic/cubic curves map to OCCT Bézier edges, lines to
|
|
43
|
+
* segment edges.
|
|
44
|
+
*/
|
|
45
|
+
export declare class TextOutline {
|
|
46
|
+
static buildEdges(font: Font, text: string, opts: TextLayoutOptions, plane: Plane, origin: Point2D): Edge[];
|
|
47
|
+
/**
|
|
48
|
+
* Lays out `text` along a curve: each glyph is placed rigidly (not bent) at
|
|
49
|
+
* the arc-length position of its advance midpoint, rotated to the local
|
|
50
|
+
* tangent. Spacing is measured as arc length, so kerning/letterSpacing
|
|
51
|
+
* carry over from straight text. Multi-line strings stack below the
|
|
52
|
+
* baseline via perpendicular offsets.
|
|
53
|
+
*/
|
|
54
|
+
static buildEdgesAlongPath(font: Font, text: string, opts: TextLayoutOptions, path: TextPathOptions): Edge[];
|
|
55
|
+
private static buildLineAlongPath;
|
|
56
|
+
/** Maps the path-friendly alignment synonyms onto the base values. */
|
|
57
|
+
private static resolveAlign;
|
|
58
|
+
/** Total advance of a laid-out line (for alignment), excluding trailing letter spacing. */
|
|
59
|
+
private static totalAdvance;
|
|
60
|
+
private static buildLine;
|
|
61
|
+
private static buildGlyph;
|
|
62
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { Geometry } from "./geometry.js";
|
|
2
|
+
import { Point2D } from "../math/point.js";
|
|
3
|
+
/**
|
|
4
|
+
* Minimum edge length (model units) below which a segment/curve is treated as
|
|
5
|
+
* degenerate and skipped. Safely above OCCT's Precision::Confusion (1e-7) and
|
|
6
|
+
* far below any real glyph feature at typical sizes.
|
|
7
|
+
*/
|
|
8
|
+
const EPS = 1e-6;
|
|
9
|
+
/**
|
|
10
|
+
* Turns a text string into outline edges laid out on `plane`, suitable for
|
|
11
|
+
* FaceMaker2 (which resolves letter counters like the holes in `o`/`A`/`e` as
|
|
12
|
+
* faces-with-holes). Glyph outlines come from fontkit as M/L/Q/C/Z commands in
|
|
13
|
+
* font units; quadratic/cubic curves map to OCCT Bézier edges, lines to
|
|
14
|
+
* segment edges.
|
|
15
|
+
*/
|
|
16
|
+
export class TextOutline {
|
|
17
|
+
static buildEdges(font, text, opts, plane, origin) {
|
|
18
|
+
const scale = opts.size / font.unitsPerEm;
|
|
19
|
+
const lineHeight = (font.ascent - font.descent + font.lineGap) * scale * opts.lineSpacing;
|
|
20
|
+
const lines = text.split(/\r?\n/);
|
|
21
|
+
const edges = [];
|
|
22
|
+
for (let li = 0; li < lines.length; li++) {
|
|
23
|
+
const lineY = origin.y - li * lineHeight;
|
|
24
|
+
this.buildLine(font, lines[li], opts, scale, plane, new Point2D(origin.x, lineY), edges);
|
|
25
|
+
}
|
|
26
|
+
return edges;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Lays out `text` along a curve: each glyph is placed rigidly (not bent) at
|
|
30
|
+
* the arc-length position of its advance midpoint, rotated to the local
|
|
31
|
+
* tangent. Spacing is measured as arc length, so kerning/letterSpacing
|
|
32
|
+
* carry over from straight text. Multi-line strings stack below the
|
|
33
|
+
* baseline via perpendicular offsets.
|
|
34
|
+
*/
|
|
35
|
+
static buildEdgesAlongPath(font, text, opts, path) {
|
|
36
|
+
const scale = opts.size / font.unitsPerEm;
|
|
37
|
+
const lineHeight = (font.ascent - font.descent + font.lineGap) * scale * opts.lineSpacing;
|
|
38
|
+
const lines = text.split(/\r?\n/);
|
|
39
|
+
const edges = [];
|
|
40
|
+
for (let li = 0; li < lines.length; li++) {
|
|
41
|
+
this.buildLineAlongPath(font, lines[li], opts, scale, path, -li * lineHeight, edges);
|
|
42
|
+
}
|
|
43
|
+
return edges;
|
|
44
|
+
}
|
|
45
|
+
static buildLineAlongPath(font, line, opts, scale, path, lineOffset, out) {
|
|
46
|
+
if (line.length === 0) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const run = font.layout(line);
|
|
50
|
+
const total = this.totalAdvance(run.positions, opts, scale);
|
|
51
|
+
const align = this.resolveAlign(opts.align);
|
|
52
|
+
let s0 = path.startAt;
|
|
53
|
+
let justifyGap = 0;
|
|
54
|
+
if (align === "center") {
|
|
55
|
+
s0 += (path.length - total) / 2;
|
|
56
|
+
}
|
|
57
|
+
else if (align === "right") {
|
|
58
|
+
s0 += path.length - total;
|
|
59
|
+
}
|
|
60
|
+
else if (align === "space-between" && run.glyphs.length > 1) {
|
|
61
|
+
// Justify across the whole path: distribute the leftover arc length
|
|
62
|
+
// evenly between glyphs. On a closed loop the wrap-around gap counts
|
|
63
|
+
// too, so the glyphs end up evenly spaced around the loop.
|
|
64
|
+
const gaps = path.closed ? run.glyphs.length : run.glyphs.length - 1;
|
|
65
|
+
justifyGap = (path.length - total) / gaps;
|
|
66
|
+
}
|
|
67
|
+
else if (align === "space-around" && run.glyphs.length > 0) {
|
|
68
|
+
// Every glyph gets an equal share of the leftover arc length, half on
|
|
69
|
+
// each side — so the run starts and ends half a gap from the path's
|
|
70
|
+
// ends (on a closed loop this just phase-shifts the even spacing).
|
|
71
|
+
justifyGap = (path.length - total) / run.glyphs.length;
|
|
72
|
+
s0 += justifyGap / 2;
|
|
73
|
+
}
|
|
74
|
+
let pen = 0;
|
|
75
|
+
for (let i = 0; i < run.glyphs.length; i++) {
|
|
76
|
+
const pos = run.positions[i];
|
|
77
|
+
const adv = pos.xAdvance * scale;
|
|
78
|
+
// Anchor each glyph at the midpoint of its advance so it straddles the
|
|
79
|
+
// curve symmetrically (less lift-off on tight curvature).
|
|
80
|
+
const sMid = s0 + pen + adv / 2;
|
|
81
|
+
const frame = path.evalAt(path.flip ? path.length - sMid : sMid);
|
|
82
|
+
const tangent = path.flip ? frame.tangent.multiply(-1) : frame.tangent;
|
|
83
|
+
const up = path.normal.cross(tangent).normalize();
|
|
84
|
+
const anchor = frame.point;
|
|
85
|
+
const toWorld = (p) => {
|
|
86
|
+
const dx = ((pos.xOffset || 0) + p.x) * scale - adv / 2;
|
|
87
|
+
const dy = ((pos.yOffset || 0) + p.y) * scale + path.offset + lineOffset;
|
|
88
|
+
return anchor.add(tangent.multiply(dx)).add(up.multiply(dy));
|
|
89
|
+
};
|
|
90
|
+
this.buildGlyph(run.glyphs[i], scale, toWorld, out);
|
|
91
|
+
pen += adv + opts.letterSpacing + justifyGap;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Maps the path-friendly alignment synonyms onto the base values. */
|
|
95
|
+
static resolveAlign(align) {
|
|
96
|
+
if (align === "start") {
|
|
97
|
+
return "left";
|
|
98
|
+
}
|
|
99
|
+
if (align === "end") {
|
|
100
|
+
return "right";
|
|
101
|
+
}
|
|
102
|
+
return align;
|
|
103
|
+
}
|
|
104
|
+
/** Total advance of a laid-out line (for alignment), excluding trailing letter spacing. */
|
|
105
|
+
static totalAdvance(positions, opts, scale) {
|
|
106
|
+
let total = 0;
|
|
107
|
+
for (const pos of positions) {
|
|
108
|
+
total += pos.xAdvance * scale + opts.letterSpacing;
|
|
109
|
+
}
|
|
110
|
+
if (positions.length > 0) {
|
|
111
|
+
total -= opts.letterSpacing;
|
|
112
|
+
}
|
|
113
|
+
return total;
|
|
114
|
+
}
|
|
115
|
+
static buildLine(font, line, opts, scale, plane, origin, out) {
|
|
116
|
+
if (line.length === 0) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const run = font.layout(line);
|
|
120
|
+
const total = this.totalAdvance(run.positions, opts, scale);
|
|
121
|
+
const align = this.resolveAlign(opts.align);
|
|
122
|
+
let penX = origin.x;
|
|
123
|
+
if (align === "center") {
|
|
124
|
+
penX -= total / 2;
|
|
125
|
+
}
|
|
126
|
+
else if (align === "right") {
|
|
127
|
+
penX -= total;
|
|
128
|
+
}
|
|
129
|
+
for (let i = 0; i < run.glyphs.length; i++) {
|
|
130
|
+
const pos = run.positions[i];
|
|
131
|
+
const gx = penX + (pos.xOffset || 0) * scale;
|
|
132
|
+
const gy = origin.y + (pos.yOffset || 0) * scale;
|
|
133
|
+
const toWorld = (p) => plane.localToWorld(new Point2D(gx + p.x * scale, gy + p.y * scale));
|
|
134
|
+
this.buildGlyph(run.glyphs[i], scale, toWorld, out);
|
|
135
|
+
penX += pos.xAdvance * scale + opts.letterSpacing;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
static buildGlyph(glyph, scale, toWorld, out) {
|
|
139
|
+
const commands = glyph.path?.commands;
|
|
140
|
+
if (!commands || commands.length === 0) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const dist = (a, b) => Math.hypot((a.x - b.x) * scale, (a.y - b.y) * scale);
|
|
144
|
+
const addSegment = (a, b) => {
|
|
145
|
+
if (dist(a, b) < EPS) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
out.push(Geometry.makeEdge(Geometry.makeSegment(toWorld(a), toWorld(b))));
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Skip a segment OCCT rejects; one bad edge shouldn't drop the glyph.
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const addBezier = (poles) => {
|
|
156
|
+
if (poles.every(p => dist(p, poles[0]) < EPS)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
out.push(Geometry.makeEdgeFromBezier(Geometry.makeBezierCurve(poles.map(toWorld))));
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Fall back to a straight chord if the curve can't be built.
|
|
164
|
+
addSegment(poles[0], poles[poles.length - 1]);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
let cur = null;
|
|
168
|
+
let startPt = null;
|
|
169
|
+
const closeContour = () => {
|
|
170
|
+
if (cur && startPt) {
|
|
171
|
+
addSegment(cur, startPt);
|
|
172
|
+
}
|
|
173
|
+
cur = null;
|
|
174
|
+
startPt = null;
|
|
175
|
+
};
|
|
176
|
+
for (const cmd of commands) {
|
|
177
|
+
const a = cmd.args;
|
|
178
|
+
switch (cmd.command) {
|
|
179
|
+
case "moveTo":
|
|
180
|
+
closeContour();
|
|
181
|
+
cur = { x: a[0], y: a[1] };
|
|
182
|
+
startPt = cur;
|
|
183
|
+
break;
|
|
184
|
+
case "lineTo":
|
|
185
|
+
if (cur) {
|
|
186
|
+
const next = { x: a[0], y: a[1] };
|
|
187
|
+
addSegment(cur, next);
|
|
188
|
+
cur = next;
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
case "quadraticCurveTo":
|
|
192
|
+
if (cur) {
|
|
193
|
+
const next = { x: a[2], y: a[3] };
|
|
194
|
+
addBezier([cur, { x: a[0], y: a[1] }, next]);
|
|
195
|
+
cur = next;
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
case "bezierCurveTo":
|
|
199
|
+
if (cur) {
|
|
200
|
+
const next = { x: a[4], y: a[5] };
|
|
201
|
+
addBezier([cur, { x: a[0], y: a[1] }, { x: a[2], y: a[3] }, next]);
|
|
202
|
+
cur = next;
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
case "closePath":
|
|
206
|
+
closeContour();
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
closeContour();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -11,19 +11,6 @@ export declare class ThinFaceMaker {
|
|
|
11
11
|
static make(edges: (Wire | Edge)[], plane: Plane, offset1: number, offset2?: number): ThinFaceResult;
|
|
12
12
|
private static makeSingleOffsetFace;
|
|
13
13
|
private static makeDualOffsetFace;
|
|
14
|
-
/**
|
|
15
|
-
* Offsets a wire by the given distance, handling both closed and open wires.
|
|
16
|
-
* For closed wires, WireOps.offsetWire handles negative distances natively.
|
|
17
|
-
* For open wires, negative distances are handled by reversing the wire,
|
|
18
|
-
* offsetting with the absolute value, then reversing back.
|
|
19
|
-
*
|
|
20
|
-
* If the wire-only offset throws (e.g. "Offset wire is not closed." on
|
|
21
|
-
* wires whose corners are GeomAbs_OffsetCurve segments from `offset()` over
|
|
22
|
-
* a drafted body's filleted bottom), retries with a planar face as the
|
|
23
|
-
* offset spine — that path supplies an explicit normal which keeps the
|
|
24
|
-
* algorithm stable on the same input.
|
|
25
|
-
*/
|
|
26
|
-
private static doOffset;
|
|
27
14
|
/**
|
|
28
15
|
* Merges adjacent edges that share the same underlying curve into a single
|
|
29
16
|
* edge (e.g. two conic-arc segments at a filleted corner produced by
|
|
@@ -33,12 +20,6 @@ export declare class ThinFaceMaker {
|
|
|
33
20
|
* upgrader produces no usable result.
|
|
34
21
|
*/
|
|
35
22
|
private static unifyWireEdges;
|
|
36
|
-
/**
|
|
37
|
-
* Offsets an open wire on a given plane, using a planar face as reference
|
|
38
|
-
* so that BRepOffsetAPI_MakeOffset knows the offset direction.
|
|
39
|
-
* Only handles positive distances — use doOffset for sign handling.
|
|
40
|
-
*/
|
|
41
|
-
private static offsetWireOnPlane;
|
|
42
23
|
/**
|
|
43
24
|
* Finds face edges that geometrically match the given wire edges by comparing midpoints.
|
|
44
25
|
* This is needed because wire reversal (ShapeExtend_WireData.Reverse) creates new TShapes,
|