brepjs 8.8.2 → 8.8.3
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/dist/2d/curves.d.ts.map +1 -1
- package/dist/2d/lib/Curve2D.d.ts.map +1 -1
- package/dist/2d/lib/approximations.d.ts.map +1 -1
- package/dist/2d/lib/makeCurves.d.ts.map +1 -1
- package/dist/2d/lib/ocWrapper.d.ts.map +1 -1
- package/dist/2d.cjs +2 -2
- package/dist/2d.js +3 -3
- package/dist/Blueprint-BmFJ4caY.cjs +1439 -0
- package/dist/Blueprint-DsoGiJNJ.js +1440 -0
- package/dist/{boolean2D-q5FOdOQW.cjs → boolean2D-BQk8LNmZ.cjs} +212 -127
- package/dist/{boolean2D-Dgnuy63w.js → boolean2D-D5O0F3J8.js} +212 -127
- package/dist/{booleanFns-CFit7JYt.cjs → booleanFns-CVM3dOTP.cjs} +210 -130
- package/dist/{booleanFns--Orezl-b.js → booleanFns-DOyKxL7q.js} +210 -130
- package/dist/brepjs.cjs +457 -304
- package/dist/brepjs.js +551 -398
- package/dist/core/disposal.d.ts +44 -3
- package/dist/core/disposal.d.ts.map +1 -1
- package/dist/core/geometryHelpers.d.ts.map +1 -1
- package/dist/core/kernelCall.d.ts +20 -0
- package/dist/core/kernelCall.d.ts.map +1 -1
- package/dist/core/memory.d.ts +1 -1
- package/dist/core/memory.d.ts.map +1 -1
- package/dist/core.cjs +4 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +11 -8
- package/dist/{cornerFinder-KNTFoGrm.js → cornerFinder-DH6EwYfL.js} +1 -1
- package/dist/{cornerFinder-v4un1Fr9.cjs → cornerFinder-XAV2ywVS.cjs} +1 -1
- package/dist/curveFns-BHRYwxBM.js +281 -0
- package/dist/{curveFns-6ovDM_sR.cjs → curveFns-BsAHC3Qv.cjs} +137 -36
- package/dist/{drawFns-WgXeXHH1.cjs → drawFns-CsmUF97U.cjs} +181 -101
- package/dist/{drawFns-XwroLxdb.js → drawFns-hD05g0ZQ.js} +181 -101
- package/dist/faceFns-DNQss51F.cjs +358 -0
- package/dist/faceFns-q5CR9pOW.js +359 -0
- package/dist/{helpers-CRfqaW0Y.cjs → helpers-aylLv0_I.cjs} +13 -10
- package/dist/{helpers-CtBCzEqs.js → helpers-tNdaX01G.js} +13 -10
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/io/importFns.d.ts.map +1 -1
- package/dist/io.cjs +131 -63
- package/dist/io.js +131 -63
- package/dist/loft-CjEEqz2P.cjs +530 -0
- package/dist/loft-DTRcYrq2.js +531 -0
- package/dist/measurement-B6_cxjpw.cjs +200 -0
- package/dist/measurement-BXqFvcGh.js +201 -0
- package/dist/measurement.cjs +1 -1
- package/dist/measurement.js +1 -1
- package/dist/{meshFns-CPNNlpbw.cjs → meshFns-CTc1CRkF.cjs} +1 -1
- package/dist/{meshFns-DAmWVyEp.js → meshFns-DDFl7gLN.js} +1 -1
- package/dist/operations/exporterFns.d.ts.map +1 -1
- package/dist/operations/exporterUtils.d.ts +3 -3
- package/dist/operations/exporterUtils.d.ts.map +1 -1
- package/dist/operations/exporters.d.ts.map +1 -1
- package/dist/operations/extrude.d.ts.map +1 -1
- package/dist/operations/extrudeFns.d.ts.map +1 -1
- package/dist/operations/loft.d.ts.map +1 -1
- package/dist/operations/multiSweepFns.d.ts.map +1 -1
- package/dist/{operations-vN0tcoaU.js → operations-jRE2QbPo.js} +261 -166
- package/dist/{operations-BQ25CPI8.cjs → operations-pxjbW4Er.cjs} +261 -166
- package/dist/operations.cjs +2 -2
- package/dist/operations.js +2 -2
- package/dist/query/shapeDistanceFilter.d.ts.map +1 -1
- package/dist/query.cjs +66 -14
- package/dist/query.js +67 -15
- package/dist/{shapeFns-C785aeVn.cjs → shapeFns-D4CRxxmF.cjs} +61 -7
- package/dist/{shapeFns-ClpALED4.js → shapeFns-DNnBK8fG.js} +61 -7
- package/dist/{shapeTypes-DnwCo942.js → shapeTypes-Bi_9RZa2.js} +50 -19
- package/dist/{shapeTypes-CIijJxCz.cjs → shapeTypes-CWuX602K.cjs} +32 -1
- package/dist/sketching/CompoundSketch.d.ts.map +1 -1
- package/dist/sketching/Sketch.d.ts.map +1 -1
- package/dist/sketching/Sketcher.d.ts.map +1 -1
- package/dist/sketching/Sketcher2d.d.ts.map +1 -1
- package/dist/sketching/cannedSketches.d.ts.map +1 -1
- package/dist/sketching/draw.d.ts.map +1 -1
- package/dist/sketching.cjs +2 -2
- package/dist/sketching.js +2 -2
- package/dist/surfaceBuilders-CLal3WlK.cjs +429 -0
- package/dist/surfaceBuilders-W9Y25CIb.js +430 -0
- package/dist/topology/curveBuilders.d.ts.map +1 -1
- package/dist/topology/shapeFns.d.ts.map +1 -1
- package/dist/topology/solidBuilders.d.ts.map +1 -1
- package/dist/topology/surfaceBuilders.d.ts.map +1 -1
- package/dist/{topology-CqyxpmEh.js → topology-CMM6vAzx.js} +6 -6
- package/dist/{topology-zG8maSDK.cjs → topology-CNw-wsmG.cjs} +6 -6
- package/dist/topology.cjs +6 -6
- package/dist/topology.js +6 -6
- package/package.json +4 -1
- package/dist/Blueprint-BmbNUnGI.cjs +0 -1185
- package/dist/Blueprint-C-JJkkwL.js +0 -1186
- package/dist/curveFns-BhQECv8e.js +0 -180
- package/dist/faceFns-3PDjBeW7.js +0 -272
- package/dist/faceFns-CxaLWOjc.cjs +0 -271
- package/dist/loft-CVb-IjEI.cjs +0 -372
- package/dist/loft-DMFjK6lk.js +0 -373
- package/dist/measurement-CecYIt3s.cjs +0 -134
- package/dist/measurement-DHDLAH7-.js +0 -135
- package/dist/surfaceBuilders-CC0ZQGix.cjs +0 -289
- package/dist/surfaceBuilders-CrJtFu2a.js +0 -290
|
@@ -0,0 +1,1439 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
3
|
+
var __typeError = (msg) => {
|
|
4
|
+
throw TypeError(msg);
|
|
5
|
+
};
|
|
6
|
+
var __using = (stack, value, async) => {
|
|
7
|
+
if (value != null) {
|
|
8
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
9
|
+
var dispose, inner;
|
|
10
|
+
if (async) dispose = value[__knownSymbol("asyncDispose")];
|
|
11
|
+
if (dispose === void 0) {
|
|
12
|
+
dispose = value[__knownSymbol("dispose")];
|
|
13
|
+
if (async) inner = dispose;
|
|
14
|
+
}
|
|
15
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
16
|
+
if (inner) dispose = function() {
|
|
17
|
+
try {
|
|
18
|
+
inner.call(this);
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return Promise.reject(e);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
stack.push([async, dispose, value]);
|
|
24
|
+
} else if (async) {
|
|
25
|
+
stack.push([async]);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
};
|
|
29
|
+
var __callDispose = (stack, error, hasError) => {
|
|
30
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
31
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
32
|
+
};
|
|
33
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
34
|
+
var next = (it) => {
|
|
35
|
+
while (it = stack.pop()) {
|
|
36
|
+
try {
|
|
37
|
+
var result2 = it[1] && it[1].call(it[2]);
|
|
38
|
+
if (it[0]) return Promise.resolve(result2).then(next, (e) => (fail(e), next()));
|
|
39
|
+
} catch (e) {
|
|
40
|
+
fail(e);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (hasError) throw error;
|
|
44
|
+
};
|
|
45
|
+
return next();
|
|
46
|
+
};
|
|
47
|
+
const vectors = require("./vectors-CGLqemPY.cjs");
|
|
48
|
+
const occtBoundary = require("./occtBoundary-Cqfsau2c.cjs");
|
|
49
|
+
const shapeTypes = require("./shapeTypes-CWuX602K.cjs");
|
|
50
|
+
const faceFns = require("./faceFns-DNQss51F.cjs");
|
|
51
|
+
const curveFns = require("./curveFns-BsAHC3Qv.cjs");
|
|
52
|
+
const errors = require("./errors-NNmTtM5u.cjs");
|
|
53
|
+
const surfaceBuilders = require("./surfaceBuilders-CLal3WlK.cjs");
|
|
54
|
+
const helpers = require("./helpers-aylLv0_I.cjs");
|
|
55
|
+
const vecOps = require("./vecOps-CjRL1jau.cjs");
|
|
56
|
+
const result = require("./result.cjs");
|
|
57
|
+
function makePlane(plane, origin) {
|
|
58
|
+
if (plane && typeof plane !== "string") {
|
|
59
|
+
return { ...plane };
|
|
60
|
+
} else {
|
|
61
|
+
return vectors.resolvePlane(plane ?? "XY", origin);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function mirror(shape, inputPlane, origin) {
|
|
65
|
+
var _stack = [];
|
|
66
|
+
try {
|
|
67
|
+
const oc = occtBoundary.getKernel().oc;
|
|
68
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
69
|
+
let originVec;
|
|
70
|
+
let directionVec;
|
|
71
|
+
if (typeof inputPlane === "string") {
|
|
72
|
+
const plane = vectors.resolvePlane(inputPlane, origin);
|
|
73
|
+
originVec = plane.origin;
|
|
74
|
+
directionVec = plane.zDir;
|
|
75
|
+
} else if (inputPlane && typeof inputPlane === "object" && "origin" in inputPlane && "zDir" in inputPlane) {
|
|
76
|
+
originVec = origin ? occtBoundary.toVec3(origin) : inputPlane.origin;
|
|
77
|
+
directionVec = inputPlane.zDir;
|
|
78
|
+
} else if (inputPlane) {
|
|
79
|
+
originVec = origin ? occtBoundary.toVec3(origin) : [0, 0, 0];
|
|
80
|
+
directionVec = occtBoundary.toVec3(inputPlane);
|
|
81
|
+
} else {
|
|
82
|
+
const plane = vectors.resolvePlane("YZ", origin);
|
|
83
|
+
originVec = plane.origin;
|
|
84
|
+
directionVec = plane.zDir;
|
|
85
|
+
}
|
|
86
|
+
const mirrorAxis = scope.register(occtBoundary.makeOcAx2(originVec, directionVec));
|
|
87
|
+
const trsf = scope.register(new oc.gp_Trsf_1());
|
|
88
|
+
trsf.SetMirror_3(mirrorAxis);
|
|
89
|
+
const transformer = scope.register(new oc.BRepBuilderAPI_Transform_2(shape, trsf, true));
|
|
90
|
+
const newShape = transformer.ModifiedShape(shape);
|
|
91
|
+
return newShape;
|
|
92
|
+
} catch (_) {
|
|
93
|
+
var _error = _, _hasError = true;
|
|
94
|
+
} finally {
|
|
95
|
+
__callDispose(_stack, _error, _hasError);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function isPoint2D(point) {
|
|
99
|
+
return Array.isArray(point) && point.length === 2 && typeof point[0] === "number" && typeof point[1] === "number";
|
|
100
|
+
}
|
|
101
|
+
function precisionRound(number, precision) {
|
|
102
|
+
const factor = Math.pow(10, precision);
|
|
103
|
+
const n = precision < 0 ? number : 0.01 / factor + number;
|
|
104
|
+
return Math.round(n * factor) / factor;
|
|
105
|
+
}
|
|
106
|
+
function round2(v) {
|
|
107
|
+
return Math.round(v * 100) / 100;
|
|
108
|
+
}
|
|
109
|
+
const reprPnt = ([x, y]) => {
|
|
110
|
+
return `(${round2(x)},${round2(y)})`;
|
|
111
|
+
};
|
|
112
|
+
const asFixed = (p, precision = 1e-9) => {
|
|
113
|
+
let num = p;
|
|
114
|
+
if (Math.abs(p) < precision) num = 0;
|
|
115
|
+
return num.toFixed(-Math.log10(precision));
|
|
116
|
+
};
|
|
117
|
+
const removeDuplicatePoints = (points, precision = 1e-9) => {
|
|
118
|
+
return Array.from(
|
|
119
|
+
new Map(
|
|
120
|
+
points.map(([p0, p1]) => [
|
|
121
|
+
`[${asFixed(p0, precision)},${asFixed(p1, precision)}]`,
|
|
122
|
+
[p0, p1]
|
|
123
|
+
])
|
|
124
|
+
).values()
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
const pnt = ([x, y]) => {
|
|
128
|
+
const oc = occtBoundary.getKernel().oc;
|
|
129
|
+
return new oc.gp_Pnt2d_3(x, y);
|
|
130
|
+
};
|
|
131
|
+
const direction2d = ([x, y]) => {
|
|
132
|
+
const oc = occtBoundary.getKernel().oc;
|
|
133
|
+
return new oc.gp_Dir2d_4(x, y);
|
|
134
|
+
};
|
|
135
|
+
const vec = ([x, y]) => {
|
|
136
|
+
const oc = occtBoundary.getKernel().oc;
|
|
137
|
+
return new oc.gp_Vec2d_4(x, y);
|
|
138
|
+
};
|
|
139
|
+
const axis2d = (point, direction) => {
|
|
140
|
+
var _stack = [];
|
|
141
|
+
try {
|
|
142
|
+
const oc = occtBoundary.getKernel().oc;
|
|
143
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
144
|
+
const axis = new oc.gp_Ax2d_2(scope.register(pnt(point)), scope.register(direction2d(direction)));
|
|
145
|
+
return axis;
|
|
146
|
+
} catch (_) {
|
|
147
|
+
var _error = _, _hasError = true;
|
|
148
|
+
} finally {
|
|
149
|
+
__callDispose(_stack, _error, _hasError);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
class BoundingBox2d {
|
|
153
|
+
_wrapped;
|
|
154
|
+
_deleted = false;
|
|
155
|
+
constructor(wrapped) {
|
|
156
|
+
const oc = occtBoundary.getKernel().oc;
|
|
157
|
+
this._wrapped = wrapped ?? new oc.Bnd_Box2d();
|
|
158
|
+
shapeTypes.registerForCleanup(this, this._wrapped);
|
|
159
|
+
}
|
|
160
|
+
get wrapped() {
|
|
161
|
+
if (this._deleted) throw new Error("This object has been deleted");
|
|
162
|
+
return this._wrapped;
|
|
163
|
+
}
|
|
164
|
+
delete() {
|
|
165
|
+
if (!this._deleted) {
|
|
166
|
+
this._deleted = true;
|
|
167
|
+
shapeTypes.unregisterFromCleanup(this._wrapped);
|
|
168
|
+
this._wrapped.delete();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Return a human-readable string of the form `(xMin,yMin) - (xMax,yMax)`. */
|
|
172
|
+
get repr() {
|
|
173
|
+
const [min, max] = this.bounds;
|
|
174
|
+
return `${reprPnt(min)} - ${reprPnt(max)}`;
|
|
175
|
+
}
|
|
176
|
+
/** Return the `[min, max]` corner points of the bounding box. */
|
|
177
|
+
get bounds() {
|
|
178
|
+
const xMin = { current: 0 };
|
|
179
|
+
const yMin = { current: 0 };
|
|
180
|
+
const xMax = { current: 0 };
|
|
181
|
+
const yMax = { current: 0 };
|
|
182
|
+
this.wrapped.Get(xMin, yMin, xMax, yMax);
|
|
183
|
+
return [
|
|
184
|
+
[xMin.current, yMin.current],
|
|
185
|
+
[xMax.current, yMax.current]
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
/** Return the center point of the bounding box. */
|
|
189
|
+
get center() {
|
|
190
|
+
const [[xmin, ymin], [xmax, ymax]] = this.bounds;
|
|
191
|
+
return [xmin + (xmax - xmin) / 2, ymin + (ymax - ymin) / 2];
|
|
192
|
+
}
|
|
193
|
+
/** Return the width (x-extent) of the bounding box. */
|
|
194
|
+
get width() {
|
|
195
|
+
const [[xmin], [xmax]] = this.bounds;
|
|
196
|
+
return Math.abs(xmax - xmin);
|
|
197
|
+
}
|
|
198
|
+
/** Return the height (y-extent) of the bounding box. */
|
|
199
|
+
get height() {
|
|
200
|
+
const [[, ymin], [, ymax]] = this.bounds;
|
|
201
|
+
return Math.abs(ymax - ymin);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Return a point guaranteed to lie outside the bounding box.
|
|
205
|
+
*
|
|
206
|
+
* @param paddingPercent - Extra padding as a percentage of the box dimensions.
|
|
207
|
+
*/
|
|
208
|
+
outsidePoint(paddingPercent = 1) {
|
|
209
|
+
const [min, max] = this.bounds;
|
|
210
|
+
const width = max[0] - min[0];
|
|
211
|
+
const height = max[1] - min[1];
|
|
212
|
+
return [
|
|
213
|
+
max[0] + width / 100 * paddingPercent,
|
|
214
|
+
max[1] + height / 100 * paddingPercent * 0.9
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
/** Expand this bounding box to include `other`. */
|
|
218
|
+
add(other) {
|
|
219
|
+
this.wrapped.Add_1(other.wrapped);
|
|
220
|
+
}
|
|
221
|
+
/** Test whether this bounding box and `other` are completely disjoint. */
|
|
222
|
+
isOut(other) {
|
|
223
|
+
return this.wrapped.IsOut_4(other.wrapped);
|
|
224
|
+
}
|
|
225
|
+
/** Test whether the given point lies inside (or on the boundary of) this box. */
|
|
226
|
+
containsPoint(other) {
|
|
227
|
+
var _stack = [];
|
|
228
|
+
try {
|
|
229
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
230
|
+
const point = scope.register(pnt(other));
|
|
231
|
+
return !this.wrapped.IsOut_1(point);
|
|
232
|
+
} catch (_) {
|
|
233
|
+
var _error = _, _hasError = true;
|
|
234
|
+
} finally {
|
|
235
|
+
__callDispose(_stack, _error, _hasError);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function deserializeCurve2D(data) {
|
|
240
|
+
const oc = occtBoundary.getKernel().oc;
|
|
241
|
+
const handle = oc.GeomToolsWrapper.Read(data);
|
|
242
|
+
return new Curve2D(handle);
|
|
243
|
+
}
|
|
244
|
+
class Curve2D {
|
|
245
|
+
_wrapped;
|
|
246
|
+
_deleted = false;
|
|
247
|
+
_boundingBox;
|
|
248
|
+
_firstPoint = null;
|
|
249
|
+
_lastPoint = null;
|
|
250
|
+
constructor(handle) {
|
|
251
|
+
const oc = occtBoundary.getKernel().oc;
|
|
252
|
+
const inner = handle.get();
|
|
253
|
+
this._wrapped = new oc.Handle_Geom2d_Curve_2(inner);
|
|
254
|
+
this._boundingBox = null;
|
|
255
|
+
shapeTypes.registerForCleanup(this, this._wrapped);
|
|
256
|
+
}
|
|
257
|
+
get wrapped() {
|
|
258
|
+
if (this._deleted) throw new Error("This object has been deleted");
|
|
259
|
+
return this._wrapped;
|
|
260
|
+
}
|
|
261
|
+
delete() {
|
|
262
|
+
if (!this._deleted) {
|
|
263
|
+
this._deleted = true;
|
|
264
|
+
shapeTypes.unregisterFromCleanup(this._wrapped);
|
|
265
|
+
this._wrapped.delete();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/** Compute (and cache) the 2D bounding box of this curve. */
|
|
269
|
+
get boundingBox() {
|
|
270
|
+
if (this._boundingBox) return this._boundingBox;
|
|
271
|
+
const oc = occtBoundary.getKernel().oc;
|
|
272
|
+
const boundBox = new oc.Bnd_Box2d();
|
|
273
|
+
oc.BndLib_Add2dCurve.Add_3(this.wrapped, 1e-6, boundBox);
|
|
274
|
+
this._boundingBox = new BoundingBox2d(boundBox);
|
|
275
|
+
return this._boundingBox;
|
|
276
|
+
}
|
|
277
|
+
/** Return a human-readable representation, e.g. `LINE (0,0) - (1,1)`. */
|
|
278
|
+
get repr() {
|
|
279
|
+
return `${this.geomType} ${reprPnt(this.firstPoint)} - ${reprPnt(this.lastPoint)}`;
|
|
280
|
+
}
|
|
281
|
+
/** Access the underlying OCCT `Geom2d_Curve` (unwrapped from its handle). */
|
|
282
|
+
get innerCurve() {
|
|
283
|
+
return this.wrapped.get();
|
|
284
|
+
}
|
|
285
|
+
/** Serialize this curve to a string that can be restored with {@link deserializeCurve2D}. */
|
|
286
|
+
serialize() {
|
|
287
|
+
const oc = occtBoundary.getKernel().oc;
|
|
288
|
+
return oc.GeomToolsWrapper.Write(this.wrapped);
|
|
289
|
+
}
|
|
290
|
+
/** Evaluate the curve at the given parameter, returning the 2D point. */
|
|
291
|
+
value(parameter) {
|
|
292
|
+
const p = this.innerCurve.Value(parameter);
|
|
293
|
+
const v = [p.X(), p.Y()];
|
|
294
|
+
p.delete();
|
|
295
|
+
return v;
|
|
296
|
+
}
|
|
297
|
+
/** Return the point at the start of the curve (cached after first access). */
|
|
298
|
+
get firstPoint() {
|
|
299
|
+
if (this._firstPoint === null) {
|
|
300
|
+
this._firstPoint = this.value(this.firstParameter);
|
|
301
|
+
}
|
|
302
|
+
return this._firstPoint;
|
|
303
|
+
}
|
|
304
|
+
/** Return the point at the end of the curve (cached after first access). */
|
|
305
|
+
get lastPoint() {
|
|
306
|
+
if (this._lastPoint === null) {
|
|
307
|
+
this._lastPoint = this.value(this.lastParameter);
|
|
308
|
+
}
|
|
309
|
+
return this._lastPoint;
|
|
310
|
+
}
|
|
311
|
+
/** Return the parameter value at the start of the curve. */
|
|
312
|
+
get firstParameter() {
|
|
313
|
+
return this.innerCurve.FirstParameter();
|
|
314
|
+
}
|
|
315
|
+
/** Return the parameter value at the end of the curve. */
|
|
316
|
+
get lastParameter() {
|
|
317
|
+
return this.innerCurve.LastParameter();
|
|
318
|
+
}
|
|
319
|
+
/** Create a `Geom2dAdaptor_Curve` for algorithmic queries (caller must delete). */
|
|
320
|
+
adaptor() {
|
|
321
|
+
const oc = occtBoundary.getKernel().oc;
|
|
322
|
+
return new oc.Geom2dAdaptor_Curve_2(this.wrapped);
|
|
323
|
+
}
|
|
324
|
+
/** Return the geometric type of this curve (e.g. `LINE`, `CIRCLE`, `BSPLINE_CURVE`). */
|
|
325
|
+
get geomType() {
|
|
326
|
+
const adaptor = this.adaptor();
|
|
327
|
+
const curveType = errors.unwrap(curveFns.findCurveType(adaptor.GetType()));
|
|
328
|
+
adaptor.delete();
|
|
329
|
+
return curveType;
|
|
330
|
+
}
|
|
331
|
+
/** Create an independent deep copy of this curve. */
|
|
332
|
+
clone() {
|
|
333
|
+
const cloned = new Curve2D(this.innerCurve.Copy());
|
|
334
|
+
cloned._firstPoint = this._firstPoint;
|
|
335
|
+
cloned._lastPoint = this._lastPoint;
|
|
336
|
+
return cloned;
|
|
337
|
+
}
|
|
338
|
+
/** Reverse the orientation of this curve in place. */
|
|
339
|
+
reverse() {
|
|
340
|
+
this.innerCurve.Reverse();
|
|
341
|
+
const tmp = this._firstPoint;
|
|
342
|
+
this._firstPoint = this._lastPoint;
|
|
343
|
+
this._lastPoint = tmp;
|
|
344
|
+
}
|
|
345
|
+
distanceFromPoint(point) {
|
|
346
|
+
var _stack = [];
|
|
347
|
+
try {
|
|
348
|
+
const oc = occtBoundary.getKernel().oc;
|
|
349
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
350
|
+
const projector = scope.register(
|
|
351
|
+
new oc.Geom2dAPI_ProjectPointOnCurve_2(scope.register(pnt(point)), this.wrapped)
|
|
352
|
+
);
|
|
353
|
+
let curveToPoint;
|
|
354
|
+
try {
|
|
355
|
+
curveToPoint = projector.LowerDistance();
|
|
356
|
+
} catch {
|
|
357
|
+
curveToPoint = Infinity;
|
|
358
|
+
}
|
|
359
|
+
return Math.min(
|
|
360
|
+
curveToPoint,
|
|
361
|
+
helpers.distance2d(point, this.firstPoint),
|
|
362
|
+
helpers.distance2d(point, this.lastPoint)
|
|
363
|
+
);
|
|
364
|
+
} catch (_) {
|
|
365
|
+
var _error = _, _hasError = true;
|
|
366
|
+
} finally {
|
|
367
|
+
__callDispose(_stack, _error, _hasError);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
distanceFromCurve(curve) {
|
|
371
|
+
var _stack = [];
|
|
372
|
+
try {
|
|
373
|
+
const oc = occtBoundary.getKernel().oc;
|
|
374
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
375
|
+
let curveDistance;
|
|
376
|
+
const projector = scope.register(
|
|
377
|
+
new oc.Geom2dAPI_ExtremaCurveCurve(
|
|
378
|
+
this.wrapped,
|
|
379
|
+
curve.wrapped,
|
|
380
|
+
this.firstParameter,
|
|
381
|
+
this.lastParameter,
|
|
382
|
+
curve.firstParameter,
|
|
383
|
+
curve.lastParameter
|
|
384
|
+
)
|
|
385
|
+
);
|
|
386
|
+
try {
|
|
387
|
+
curveDistance = projector.LowerDistance();
|
|
388
|
+
} catch {
|
|
389
|
+
curveDistance = Infinity;
|
|
390
|
+
}
|
|
391
|
+
return Math.min(
|
|
392
|
+
curveDistance,
|
|
393
|
+
this.distanceFromPoint(curve.firstPoint),
|
|
394
|
+
this.distanceFromPoint(curve.lastPoint),
|
|
395
|
+
curve.distanceFromPoint(this.firstPoint),
|
|
396
|
+
curve.distanceFromPoint(this.lastPoint)
|
|
397
|
+
);
|
|
398
|
+
} catch (_) {
|
|
399
|
+
var _error = _, _hasError = true;
|
|
400
|
+
} finally {
|
|
401
|
+
__callDispose(_stack, _error, _hasError);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/** Compute the minimum distance from this curve to a point or another curve. */
|
|
405
|
+
distanceFrom(element) {
|
|
406
|
+
if (isPoint2D(element)) {
|
|
407
|
+
return this.distanceFromPoint(element);
|
|
408
|
+
}
|
|
409
|
+
return this.distanceFromCurve(element);
|
|
410
|
+
}
|
|
411
|
+
/** Test whether a point lies on the curve within a tight tolerance (1e-9). */
|
|
412
|
+
isOnCurve(point) {
|
|
413
|
+
return this.distanceFromPoint(point) < 1e-9;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Project a point onto the curve and return its parameter value.
|
|
417
|
+
*
|
|
418
|
+
* @returns `Ok(parameter)` when the point is on the curve, or an error result otherwise.
|
|
419
|
+
*/
|
|
420
|
+
parameter(point, precision = 1e-9) {
|
|
421
|
+
var _stack = [];
|
|
422
|
+
try {
|
|
423
|
+
const oc = occtBoundary.getKernel().oc;
|
|
424
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
425
|
+
let lowerDistance;
|
|
426
|
+
let lowerDistanceParameter;
|
|
427
|
+
try {
|
|
428
|
+
const projector = scope.register(
|
|
429
|
+
new oc.Geom2dAPI_ProjectPointOnCurve_2(scope.register(pnt(point)), this.wrapped)
|
|
430
|
+
);
|
|
431
|
+
lowerDistance = projector.LowerDistance();
|
|
432
|
+
lowerDistanceParameter = projector.LowerDistanceParameter();
|
|
433
|
+
} catch {
|
|
434
|
+
if (helpers.samePoint(point, this.firstPoint, precision)) return errors.ok(this.firstParameter);
|
|
435
|
+
if (helpers.samePoint(point, this.lastPoint, precision)) return errors.ok(this.lastParameter);
|
|
436
|
+
return errors.err(errors.computationError("PARAMETER_NOT_FOUND", "Failed to find parameter"));
|
|
437
|
+
}
|
|
438
|
+
if (lowerDistance > precision) {
|
|
439
|
+
return errors.err(
|
|
440
|
+
errors.computationError(
|
|
441
|
+
"POINT_NOT_ON_CURVE",
|
|
442
|
+
`Point ${reprPnt(point)} not on curve ${this.repr}, ${lowerDistance.toFixed(9)}`
|
|
443
|
+
)
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
return errors.ok(lowerDistanceParameter);
|
|
447
|
+
} catch (_) {
|
|
448
|
+
var _error = _, _hasError = true;
|
|
449
|
+
} finally {
|
|
450
|
+
__callDispose(_stack, _error, _hasError);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Compute the tangent vector at a parameter position or at the projection of a point.
|
|
455
|
+
*
|
|
456
|
+
* @param index - A normalized parameter (0..1) or a Point2D to project onto the curve.
|
|
457
|
+
*/
|
|
458
|
+
tangentAt(index) {
|
|
459
|
+
var _stack = [];
|
|
460
|
+
try {
|
|
461
|
+
const oc = occtBoundary.getKernel().oc;
|
|
462
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
463
|
+
let param;
|
|
464
|
+
if (Array.isArray(index)) {
|
|
465
|
+
param = errors.unwrap(this.parameter(index));
|
|
466
|
+
} else {
|
|
467
|
+
const paramLength = this.innerCurve.LastParameter() - this.innerCurve.FirstParameter();
|
|
468
|
+
param = paramLength * index + Number(this.innerCurve.FirstParameter());
|
|
469
|
+
}
|
|
470
|
+
const point = scope.register(new oc.gp_Pnt2d_1());
|
|
471
|
+
const dir = scope.register(new oc.gp_Vec2d_1());
|
|
472
|
+
this.innerCurve.D1(param, point, dir);
|
|
473
|
+
const tgtVec = [dir.X(), dir.Y()];
|
|
474
|
+
return tgtVec;
|
|
475
|
+
} catch (_) {
|
|
476
|
+
var _error = _, _hasError = true;
|
|
477
|
+
} finally {
|
|
478
|
+
__callDispose(_stack, _error, _hasError);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Split this curve at the given points or parameter values.
|
|
483
|
+
*
|
|
484
|
+
* @returns An array of sub-curves whose union covers the original curve.
|
|
485
|
+
*/
|
|
486
|
+
splitAt(points, precision = 1e-9) {
|
|
487
|
+
var _stack = [];
|
|
488
|
+
try {
|
|
489
|
+
const oc = occtBoundary.getKernel().oc;
|
|
490
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
491
|
+
let parameters = points.map((point) => {
|
|
492
|
+
if (isPoint2D(point)) return errors.unwrap(this.parameter(point, precision));
|
|
493
|
+
return point;
|
|
494
|
+
});
|
|
495
|
+
parameters = Array.from(
|
|
496
|
+
new Map(parameters.map((p) => [precisionRound(p, -Math.log10(precision)), p])).values()
|
|
497
|
+
).sort((a, b) => a - b);
|
|
498
|
+
const firstParam = this.firstParameter;
|
|
499
|
+
const lastParam = this.lastParameter;
|
|
500
|
+
if (firstParam > lastParam) {
|
|
501
|
+
parameters.reverse();
|
|
502
|
+
}
|
|
503
|
+
if (Math.abs(parameters[0] - firstParam) < precision * 100) parameters = parameters.slice(1);
|
|
504
|
+
if (!parameters.length) return [this];
|
|
505
|
+
if (
|
|
506
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
507
|
+
Math.abs(parameters[parameters.length - 1] - lastParam) < precision * 100
|
|
508
|
+
)
|
|
509
|
+
parameters = parameters.slice(0, -1);
|
|
510
|
+
if (!parameters.length) return [this];
|
|
511
|
+
return surfaceBuilders.zip([
|
|
512
|
+
[firstParam, ...parameters],
|
|
513
|
+
[...parameters, lastParam]
|
|
514
|
+
]).map(([first, last]) => {
|
|
515
|
+
try {
|
|
516
|
+
if (this.geomType === "BEZIER_CURVE") {
|
|
517
|
+
const curveCopy = new oc.Geom2d_BezierCurve_1(
|
|
518
|
+
scope.register(this.adaptor()).Bezier().get().Poles_2()
|
|
519
|
+
);
|
|
520
|
+
curveCopy.Segment(first, last);
|
|
521
|
+
return new Curve2D(new oc.Handle_Geom2d_Curve_2(curveCopy));
|
|
522
|
+
}
|
|
523
|
+
if (this.geomType === "BSPLINE_CURVE") {
|
|
524
|
+
const adapted = scope.register(this.adaptor()).BSpline().get();
|
|
525
|
+
const curveCopy = new oc.Geom2d_BSplineCurve_1(
|
|
526
|
+
adapted.Poles_2(),
|
|
527
|
+
adapted.Knots_2(),
|
|
528
|
+
adapted.Multiplicities_2(),
|
|
529
|
+
adapted.Degree(),
|
|
530
|
+
adapted.IsPeriodic()
|
|
531
|
+
);
|
|
532
|
+
curveCopy.Segment(first, last, precision);
|
|
533
|
+
return new Curve2D(new oc.Handle_Geom2d_Curve_2(curveCopy));
|
|
534
|
+
}
|
|
535
|
+
const trimmed = new oc.Geom2d_TrimmedCurve(this.wrapped, first, last, true, true);
|
|
536
|
+
return new Curve2D(new oc.Handle_Geom2d_Curve_2(trimmed));
|
|
537
|
+
} catch (e) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
`Failed to split the curve: ${e instanceof Error ? e.message : String(e)}`,
|
|
540
|
+
{ cause: e }
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
} catch (_) {
|
|
545
|
+
var _error = _, _hasError = true;
|
|
546
|
+
} finally {
|
|
547
|
+
__callDispose(_stack, _error, _hasError);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const approximateAsBSpline = (adaptor, tolerance = 1e-4, continuity = "C0", maxSegments = 200) => {
|
|
552
|
+
var _stack = [];
|
|
553
|
+
try {
|
|
554
|
+
const oc = occtBoundary.getKernel().oc;
|
|
555
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
556
|
+
const continuities = {
|
|
557
|
+
C0: oc.GeomAbs_Shape.GeomAbs_C0,
|
|
558
|
+
C1: oc.GeomAbs_Shape.GeomAbs_C1,
|
|
559
|
+
C2: oc.GeomAbs_Shape.GeomAbs_C2,
|
|
560
|
+
C3: oc.GeomAbs_Shape.GeomAbs_C3
|
|
561
|
+
};
|
|
562
|
+
const convert = scope.register(
|
|
563
|
+
new oc.Geom2dConvert_ApproxCurve_2(
|
|
564
|
+
adaptor.ShallowCopy(),
|
|
565
|
+
tolerance,
|
|
566
|
+
continuities[continuity],
|
|
567
|
+
maxSegments,
|
|
568
|
+
3
|
|
569
|
+
)
|
|
570
|
+
);
|
|
571
|
+
return new Curve2D(convert.Curve());
|
|
572
|
+
} catch (_) {
|
|
573
|
+
var _error = _, _hasError = true;
|
|
574
|
+
} finally {
|
|
575
|
+
__callDispose(_stack, _error, _hasError);
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
const BSplineToBezier = (adaptor) => {
|
|
579
|
+
if (errors.unwrap(curveFns.findCurveType(adaptor.GetType())) !== "BSPLINE_CURVE")
|
|
580
|
+
result.bug("BSplineToBezier", "You can only convert a Bspline");
|
|
581
|
+
const handle = adaptor.BSpline();
|
|
582
|
+
const oc = occtBoundary.getKernel().oc;
|
|
583
|
+
const convert = new oc.Geom2dConvert_BSplineCurveToBezierCurve_1(handle);
|
|
584
|
+
function* bezierCurves() {
|
|
585
|
+
const nArcs = convert.NbArcs();
|
|
586
|
+
if (!nArcs) return;
|
|
587
|
+
for (let i = 1; i <= nArcs; i++) {
|
|
588
|
+
const arc = convert.Arc(i);
|
|
589
|
+
yield new Curve2D(arc);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const curves = Array.from(bezierCurves());
|
|
593
|
+
convert.delete();
|
|
594
|
+
return curves;
|
|
595
|
+
};
|
|
596
|
+
function approximateAsSvgCompatibleCurve(curves, options = {
|
|
597
|
+
tolerance: 1e-4,
|
|
598
|
+
continuity: "C0",
|
|
599
|
+
maxSegments: 300
|
|
600
|
+
}) {
|
|
601
|
+
var _stack = [];
|
|
602
|
+
try {
|
|
603
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
604
|
+
return curves.flatMap((curve) => {
|
|
605
|
+
const adaptor = scope.register(curve.adaptor());
|
|
606
|
+
const curveType = errors.unwrap(curveFns.findCurveType(adaptor.GetType()));
|
|
607
|
+
if (curveType === "ELLIPSE" || curveType === "CIRCLE" && helpers.samePoint(curve.firstPoint, curve.lastPoint)) {
|
|
608
|
+
return curve.splitAt([0.5]);
|
|
609
|
+
}
|
|
610
|
+
if (["LINE", "ELLIPSE", "CIRCLE"].includes(curveType)) {
|
|
611
|
+
return curve;
|
|
612
|
+
}
|
|
613
|
+
if (curveType === "BEZIER_CURVE") {
|
|
614
|
+
const b = adaptor.Bezier().get();
|
|
615
|
+
const deg = b.Degree();
|
|
616
|
+
if ([1, 2, 3].includes(deg)) {
|
|
617
|
+
return curve;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (curveType === "BSPLINE_CURVE") {
|
|
621
|
+
const c = BSplineToBezier(adaptor);
|
|
622
|
+
return approximateAsSvgCompatibleCurve(c, options);
|
|
623
|
+
}
|
|
624
|
+
const bspline = approximateAsBSpline(
|
|
625
|
+
adaptor,
|
|
626
|
+
options.tolerance,
|
|
627
|
+
options.continuity,
|
|
628
|
+
options.maxSegments
|
|
629
|
+
);
|
|
630
|
+
return approximateAsSvgCompatibleCurve(
|
|
631
|
+
BSplineToBezier(scope.register(bspline.adaptor())),
|
|
632
|
+
options
|
|
633
|
+
);
|
|
634
|
+
});
|
|
635
|
+
} catch (_) {
|
|
636
|
+
var _error = _, _hasError = true;
|
|
637
|
+
} finally {
|
|
638
|
+
__callDispose(_stack, _error, _hasError);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const make2dSegmentCurve = (startPoint, endPoint) => {
|
|
642
|
+
var _stack = [];
|
|
643
|
+
try {
|
|
644
|
+
const oc = occtBoundary.getKernel().oc;
|
|
645
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
646
|
+
const segment = scope.register(
|
|
647
|
+
new oc.GCE2d_MakeSegment_1(scope.register(pnt(startPoint)), scope.register(pnt(endPoint)))
|
|
648
|
+
).Value();
|
|
649
|
+
const curve = new Curve2D(segment);
|
|
650
|
+
if (!helpers.samePoint(curve.firstPoint, startPoint)) {
|
|
651
|
+
curve.reverse();
|
|
652
|
+
}
|
|
653
|
+
return curve;
|
|
654
|
+
} catch (_) {
|
|
655
|
+
var _error = _, _hasError = true;
|
|
656
|
+
} finally {
|
|
657
|
+
__callDispose(_stack, _error, _hasError);
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
const make2dThreePointArc = (startPoint, midPoint, endPoint) => {
|
|
661
|
+
var _stack = [];
|
|
662
|
+
try {
|
|
663
|
+
const oc = occtBoundary.getKernel().oc;
|
|
664
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
665
|
+
const segment = scope.register(
|
|
666
|
+
new oc.GCE2d_MakeArcOfCircle_4(
|
|
667
|
+
scope.register(pnt(startPoint)),
|
|
668
|
+
scope.register(pnt(midPoint)),
|
|
669
|
+
scope.register(pnt(endPoint))
|
|
670
|
+
)
|
|
671
|
+
).Value();
|
|
672
|
+
const curve = new Curve2D(segment);
|
|
673
|
+
if (!helpers.samePoint(curve.firstPoint, startPoint)) {
|
|
674
|
+
curve.wrapped.get().SetTrim(curve.lastParameter, curve.firstParameter, true, true);
|
|
675
|
+
}
|
|
676
|
+
return curve;
|
|
677
|
+
} catch (_) {
|
|
678
|
+
var _error = _, _hasError = true;
|
|
679
|
+
} finally {
|
|
680
|
+
__callDispose(_stack, _error, _hasError);
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
const make2dTangentArc = (startPoint, tangent, endPoint) => {
|
|
684
|
+
var _stack = [];
|
|
685
|
+
try {
|
|
686
|
+
const oc = occtBoundary.getKernel().oc;
|
|
687
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
688
|
+
const segment = scope.register(
|
|
689
|
+
new oc.GCE2d_MakeArcOfCircle_5(
|
|
690
|
+
scope.register(pnt(startPoint)),
|
|
691
|
+
scope.register(vec(tangent)),
|
|
692
|
+
scope.register(pnt(endPoint))
|
|
693
|
+
)
|
|
694
|
+
).Value();
|
|
695
|
+
const curve = new Curve2D(segment);
|
|
696
|
+
if (!helpers.samePoint(curve.firstPoint, startPoint)) {
|
|
697
|
+
curve.wrapped.get().SetTrim(curve.lastParameter, curve.firstParameter, true, true);
|
|
698
|
+
}
|
|
699
|
+
return curve;
|
|
700
|
+
} catch (_) {
|
|
701
|
+
var _error = _, _hasError = true;
|
|
702
|
+
} finally {
|
|
703
|
+
__callDispose(_stack, _error, _hasError);
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
const make2dCircle = (radius, center = [0, 0]) => {
|
|
707
|
+
var _stack = [];
|
|
708
|
+
try {
|
|
709
|
+
const oc = occtBoundary.getKernel().oc;
|
|
710
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
711
|
+
const segment = scope.register(new oc.GCE2d_MakeCircle_7(scope.register(pnt(center)), radius, true)).Value();
|
|
712
|
+
return new Curve2D(segment);
|
|
713
|
+
} catch (_) {
|
|
714
|
+
var _error = _, _hasError = true;
|
|
715
|
+
} finally {
|
|
716
|
+
__callDispose(_stack, _error, _hasError);
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
const make2dEllipse = (majorRadius, minorRadius, xDir = [1, 0], center = [0, 0], direct = true) => {
|
|
720
|
+
var _stack = [];
|
|
721
|
+
try {
|
|
722
|
+
const oc = occtBoundary.getKernel().oc;
|
|
723
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
724
|
+
const ellipse = scope.register(
|
|
725
|
+
new oc.gp_Elips2d_2(scope.register(axis2d(center, xDir)), majorRadius, minorRadius, direct)
|
|
726
|
+
);
|
|
727
|
+
const segment = scope.register(new oc.GCE2d_MakeEllipse_1(ellipse)).Value();
|
|
728
|
+
return new Curve2D(segment);
|
|
729
|
+
} catch (_) {
|
|
730
|
+
var _error = _, _hasError = true;
|
|
731
|
+
} finally {
|
|
732
|
+
__callDispose(_stack, _error, _hasError);
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
const make2dEllipseArc = (majorRadius, minorRadius, startAngle, endAngle, center = [0, 0], xDir, direct = true) => {
|
|
736
|
+
var _stack = [];
|
|
737
|
+
try {
|
|
738
|
+
const oc = occtBoundary.getKernel().oc;
|
|
739
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
740
|
+
const ellipse = scope.register(
|
|
741
|
+
new oc.gp_Elips2d_2(scope.register(axis2d(center, xDir)), majorRadius, minorRadius, true)
|
|
742
|
+
);
|
|
743
|
+
const segment = scope.register(new oc.GCE2d_MakeArcOfEllipse_1(ellipse, startAngle, endAngle, direct)).Value();
|
|
744
|
+
return new Curve2D(segment);
|
|
745
|
+
} catch (_) {
|
|
746
|
+
var _error = _, _hasError = true;
|
|
747
|
+
} finally {
|
|
748
|
+
__callDispose(_stack, _error, _hasError);
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
const make2dBezierCurve = (startPoint, controls, endPoint) => {
|
|
752
|
+
var _stack = [];
|
|
753
|
+
try {
|
|
754
|
+
const oc = occtBoundary.getKernel().oc;
|
|
755
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
756
|
+
const arrayOfPoints = scope.register(new oc.TColgp_Array1OfPnt2d_2(1, controls.length + 2));
|
|
757
|
+
arrayOfPoints.SetValue(1, scope.register(pnt(startPoint)));
|
|
758
|
+
controls.forEach((p, i) => {
|
|
759
|
+
arrayOfPoints.SetValue(i + 2, scope.register(pnt(p)));
|
|
760
|
+
});
|
|
761
|
+
arrayOfPoints.SetValue(controls.length + 2, scope.register(pnt(endPoint)));
|
|
762
|
+
const bezCurve = new oc.Geom2d_BezierCurve_1(arrayOfPoints);
|
|
763
|
+
return new Curve2D(new oc.Handle_Geom2d_Curve_2(bezCurve));
|
|
764
|
+
} catch (_) {
|
|
765
|
+
var _error = _, _hasError = true;
|
|
766
|
+
} finally {
|
|
767
|
+
__callDispose(_stack, _error, _hasError);
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
function make2dInerpolatedBSplineCurve(points, {
|
|
771
|
+
tolerance = 1e-3,
|
|
772
|
+
smoothing = null,
|
|
773
|
+
degMax = 3,
|
|
774
|
+
degMin = 1
|
|
775
|
+
} = {}) {
|
|
776
|
+
var _stack = [];
|
|
777
|
+
try {
|
|
778
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
779
|
+
const oc = occtBoundary.getKernel().oc;
|
|
780
|
+
const pnts = scope.register(new oc.TColgp_Array1OfPnt2d_2(1, points.length));
|
|
781
|
+
points.forEach((point, index) => {
|
|
782
|
+
pnts.SetValue(index + 1, scope.register(pnt(point)));
|
|
783
|
+
});
|
|
784
|
+
let splineBuilder;
|
|
785
|
+
if (smoothing) {
|
|
786
|
+
splineBuilder = scope.register(
|
|
787
|
+
new oc.Geom2dAPI_PointsToBSpline_6(
|
|
788
|
+
pnts,
|
|
789
|
+
smoothing[0],
|
|
790
|
+
smoothing[1],
|
|
791
|
+
smoothing[2],
|
|
792
|
+
degMax,
|
|
793
|
+
oc.GeomAbs_Shape.GeomAbs_C2,
|
|
794
|
+
tolerance
|
|
795
|
+
)
|
|
796
|
+
);
|
|
797
|
+
} else {
|
|
798
|
+
splineBuilder = scope.register(
|
|
799
|
+
new oc.Geom2dAPI_PointsToBSpline_2(
|
|
800
|
+
pnts,
|
|
801
|
+
degMin,
|
|
802
|
+
degMax,
|
|
803
|
+
oc.GeomAbs_Shape.GeomAbs_C2,
|
|
804
|
+
tolerance
|
|
805
|
+
)
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
if (!splineBuilder.IsDone()) {
|
|
809
|
+
return errors.err(errors.computationError("BSPLINE_2D_FAILED", "B-spline approximation failed"));
|
|
810
|
+
}
|
|
811
|
+
return errors.ok(new Curve2D(splineBuilder.Curve()));
|
|
812
|
+
} catch (_) {
|
|
813
|
+
var _error = _, _hasError = true;
|
|
814
|
+
} finally {
|
|
815
|
+
__callDispose(_stack, _error, _hasError);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
const make2dArcFromCenter = (startPoint, endPoint, center, longArc = false) => {
|
|
819
|
+
const midChord = helpers.scalarMultiply2d(helpers.add2d(startPoint, endPoint), 0.5);
|
|
820
|
+
const orientedRadius = helpers.distance2d(center, startPoint) * (longArc ? -1 : 1);
|
|
821
|
+
const midChordDir = helpers.normalize2d(helpers.subtract2d(midChord, center));
|
|
822
|
+
return make2dThreePointArc(
|
|
823
|
+
startPoint,
|
|
824
|
+
helpers.add2d(helpers.scalarMultiply2d(midChordDir, orientedRadius), center),
|
|
825
|
+
endPoint
|
|
826
|
+
);
|
|
827
|
+
};
|
|
828
|
+
function round5(v) {
|
|
829
|
+
return Math.round(v * 1e5) / 1e5;
|
|
830
|
+
}
|
|
831
|
+
const fromPnt = (pnt2) => `${round2(pnt2.X())} ${round2(pnt2.Y())}`;
|
|
832
|
+
const adaptedCurveToPathElem = (adaptor, lastPoint) => {
|
|
833
|
+
var _stack = [];
|
|
834
|
+
try {
|
|
835
|
+
const oc = occtBoundary.getKernel().oc;
|
|
836
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
837
|
+
const curveType = errors.unwrap(curveFns.findCurveType(adaptor.GetType()));
|
|
838
|
+
const [endX, endY] = lastPoint;
|
|
839
|
+
const endpoint = `${round5(endX)} ${round5(endY)}`;
|
|
840
|
+
if (curveType === "LINE") {
|
|
841
|
+
return `L ${endpoint}`;
|
|
842
|
+
}
|
|
843
|
+
if (curveType === "BEZIER_CURVE") {
|
|
844
|
+
const bezierHandle = scope.register(adaptor.Bezier());
|
|
845
|
+
const curve = bezierHandle.get();
|
|
846
|
+
const deg = curve.Degree();
|
|
847
|
+
if (deg === 1) {
|
|
848
|
+
return `L ${endpoint}`;
|
|
849
|
+
}
|
|
850
|
+
if (deg === 2) {
|
|
851
|
+
const pole2 = scope.register(curve.Pole(2));
|
|
852
|
+
return `Q ${fromPnt(pole2)} ${endpoint}`;
|
|
853
|
+
}
|
|
854
|
+
if (deg === 3) {
|
|
855
|
+
const pole2 = scope.register(curve.Pole(2));
|
|
856
|
+
const pole3 = scope.register(curve.Pole(3));
|
|
857
|
+
const p1 = fromPnt(pole2);
|
|
858
|
+
const p2 = fromPnt(pole3);
|
|
859
|
+
return `C ${p1} ${p2} ${endpoint}`;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (curveType === "CIRCLE") {
|
|
863
|
+
const curve = scope.register(adaptor.Circle());
|
|
864
|
+
const radius = curve.Radius();
|
|
865
|
+
const p1 = adaptor.FirstParameter();
|
|
866
|
+
const p2 = adaptor.LastParameter();
|
|
867
|
+
const paramAngle = (p2 - p1) * vecOps.RAD2DEG;
|
|
868
|
+
const end = paramAngle !== 360 ? endpoint : `${round5(endX)} ${round5(endY + 1e-4)}`;
|
|
869
|
+
return `A ${radius} ${radius} 0 ${Math.abs(paramAngle) > 180 ? "1" : "0"} ${curve.IsDirect() ? "1" : "0"} ${end}`;
|
|
870
|
+
}
|
|
871
|
+
if (curveType === "ELLIPSE") {
|
|
872
|
+
const curve = scope.register(adaptor.Ellipse());
|
|
873
|
+
const rx = curve.MajorRadius();
|
|
874
|
+
const ry = curve.MinorRadius();
|
|
875
|
+
const p1 = adaptor.FirstParameter();
|
|
876
|
+
const p2 = adaptor.LastParameter();
|
|
877
|
+
const paramAngle = (p2 - p1) * vecOps.RAD2DEG;
|
|
878
|
+
const end = paramAngle !== 360 ? endpoint : `${round5(endX)} ${round5(endY + 1e-4)}`;
|
|
879
|
+
const dir0 = scope.register(new oc.gp_Dir2d_1());
|
|
880
|
+
const xAxis = scope.register(curve.XAxis());
|
|
881
|
+
const xDir = scope.register(xAxis.Direction());
|
|
882
|
+
const angle = 180 - xDir.Angle(dir0) * vecOps.RAD2DEG;
|
|
883
|
+
return `A ${round5(rx)} ${round5(ry)} ${round5(angle)} ${Math.abs(paramAngle) > 180 ? "1" : "0"} ${curve.IsDirect() ? "1" : "0"} ${end}`;
|
|
884
|
+
}
|
|
885
|
+
result.bug("adaptedCurveToPathElem", `Unsupported curve type: ${curveType}`);
|
|
886
|
+
} catch (_) {
|
|
887
|
+
var _error = _, _hasError = true;
|
|
888
|
+
} finally {
|
|
889
|
+
__callDispose(_stack, _error, _hasError);
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
const curvesBoundingBox = (curves) => {
|
|
893
|
+
const oc = occtBoundary.getKernel().oc;
|
|
894
|
+
const boundBox = new oc.Bnd_Box2d();
|
|
895
|
+
curves.forEach((c) => {
|
|
896
|
+
oc.BndLib_Add2dCurve.Add_3(c.wrapped, 1e-6, boundBox);
|
|
897
|
+
});
|
|
898
|
+
return new BoundingBox2d(boundBox);
|
|
899
|
+
};
|
|
900
|
+
function curvesAsEdgesOnPlane(curves, plane) {
|
|
901
|
+
var _stack = [];
|
|
902
|
+
try {
|
|
903
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
904
|
+
const ax = scope.register(occtBoundary.makeOcAx2(plane.origin, plane.zDir, plane.xDir));
|
|
905
|
+
const oc = occtBoundary.getKernel().oc;
|
|
906
|
+
const edges = curves.map((curve) => {
|
|
907
|
+
const curve3d = scope.register(oc.GeomLib.To3d(ax, curve.wrapped));
|
|
908
|
+
const edgeBuilder = scope.register(new oc.BRepBuilderAPI_MakeEdge_24(curve3d));
|
|
909
|
+
return shapeTypes.createEdge(edgeBuilder.Edge());
|
|
910
|
+
});
|
|
911
|
+
return edges;
|
|
912
|
+
} catch (_) {
|
|
913
|
+
var _error = _, _hasError = true;
|
|
914
|
+
} finally {
|
|
915
|
+
__callDispose(_stack, _error, _hasError);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
const curvesAsEdgesOnSurface = (curves, geomSurf) => {
|
|
919
|
+
var _stack = [];
|
|
920
|
+
try {
|
|
921
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
922
|
+
const oc = occtBoundary.getKernel().oc;
|
|
923
|
+
const modifiedCurves = curves.map((curve) => {
|
|
924
|
+
const edgeBuilder = scope.register(new oc.BRepBuilderAPI_MakeEdge_30(curve.wrapped, geomSurf));
|
|
925
|
+
return shapeTypes.createEdge(edgeBuilder.Edge());
|
|
926
|
+
});
|
|
927
|
+
return modifiedCurves;
|
|
928
|
+
} catch (_) {
|
|
929
|
+
var _error = _, _hasError = true;
|
|
930
|
+
} finally {
|
|
931
|
+
__callDispose(_stack, _error, _hasError);
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
const transformCurves = (curves, transformation) => {
|
|
935
|
+
const oc = occtBoundary.getKernel().oc;
|
|
936
|
+
const modifiedCurves = curves.map((curve) => {
|
|
937
|
+
if (!transformation) return curve.clone();
|
|
938
|
+
return new Curve2D(oc.GeomLib.GTransform(curve.wrapped, transformation));
|
|
939
|
+
});
|
|
940
|
+
return modifiedCurves;
|
|
941
|
+
};
|
|
942
|
+
const stretchTransform2d = (ratio, direction, origin = [0, 0]) => {
|
|
943
|
+
const oc = occtBoundary.getKernel().oc;
|
|
944
|
+
const ax = axis2d(origin, direction);
|
|
945
|
+
const transform = new oc.gp_GTrsf2d_1();
|
|
946
|
+
transform.SetAffinity(ax, ratio);
|
|
947
|
+
ax.delete();
|
|
948
|
+
return transform;
|
|
949
|
+
};
|
|
950
|
+
const translationTransform2d = (translation) => {
|
|
951
|
+
var _stack = [];
|
|
952
|
+
try {
|
|
953
|
+
const oc = occtBoundary.getKernel().oc;
|
|
954
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
955
|
+
const rotation = new oc.gp_Trsf2d_1();
|
|
956
|
+
rotation.SetTranslation_1(scope.register(vec(translation)));
|
|
957
|
+
const transform = new oc.gp_GTrsf2d_2(rotation);
|
|
958
|
+
return transform;
|
|
959
|
+
} catch (_) {
|
|
960
|
+
var _error = _, _hasError = true;
|
|
961
|
+
} finally {
|
|
962
|
+
__callDispose(_stack, _error, _hasError);
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
const mirrorTransform2d = (centerOrDirection, origin = [0, 0], mode = "center") => {
|
|
966
|
+
var _stack = [];
|
|
967
|
+
try {
|
|
968
|
+
const oc = occtBoundary.getKernel().oc;
|
|
969
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
970
|
+
const rotation = new oc.gp_Trsf2d_1();
|
|
971
|
+
if (mode === "center") {
|
|
972
|
+
rotation.SetMirror_1(scope.register(pnt(centerOrDirection)));
|
|
973
|
+
} else {
|
|
974
|
+
rotation.SetMirror_2(scope.register(axis2d(origin, centerOrDirection)));
|
|
975
|
+
}
|
|
976
|
+
const transform = new oc.gp_GTrsf2d_2(rotation);
|
|
977
|
+
return transform;
|
|
978
|
+
} catch (_) {
|
|
979
|
+
var _error = _, _hasError = true;
|
|
980
|
+
} finally {
|
|
981
|
+
__callDispose(_stack, _error, _hasError);
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
const rotateTransform2d = (angle, center = [0, 0]) => {
|
|
985
|
+
var _stack = [];
|
|
986
|
+
try {
|
|
987
|
+
const oc = occtBoundary.getKernel().oc;
|
|
988
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
989
|
+
const rotation = new oc.gp_Trsf2d_1();
|
|
990
|
+
rotation.SetRotation(scope.register(pnt(center)), angle);
|
|
991
|
+
const transform = new oc.gp_GTrsf2d_2(rotation);
|
|
992
|
+
return transform;
|
|
993
|
+
} catch (_) {
|
|
994
|
+
var _error = _, _hasError = true;
|
|
995
|
+
} finally {
|
|
996
|
+
__callDispose(_stack, _error, _hasError);
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
const scaleTransform2d = (scaleFactor, center = [0, 0]) => {
|
|
1000
|
+
var _stack = [];
|
|
1001
|
+
try {
|
|
1002
|
+
const oc = occtBoundary.getKernel().oc;
|
|
1003
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
1004
|
+
const scaling = new oc.gp_Trsf2d_1();
|
|
1005
|
+
scaling.SetScale(scope.register(pnt(center)), scaleFactor);
|
|
1006
|
+
const transform = new oc.gp_GTrsf2d_2(scaling);
|
|
1007
|
+
return transform;
|
|
1008
|
+
} catch (_) {
|
|
1009
|
+
var _error = _, _hasError = true;
|
|
1010
|
+
} finally {
|
|
1011
|
+
__callDispose(_stack, _error, _hasError);
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
function curvesAsEdgesOnFace(curves, face, scale = "original") {
|
|
1015
|
+
var _stack = [];
|
|
1016
|
+
try {
|
|
1017
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
1018
|
+
const oc = occtBoundary.getKernel().oc;
|
|
1019
|
+
let geomSurf = scope.register(oc.BRep_Tool.Surface_2(face.wrapped));
|
|
1020
|
+
const bounds = faceFns.uvBounds(face);
|
|
1021
|
+
let transformation = null;
|
|
1022
|
+
const uAxis = scope.register(axis2d([0, 0], [0, 1]));
|
|
1023
|
+
const _vAxis = scope.register(axis2d([0, 0], [1, 0]));
|
|
1024
|
+
if (scale === "original" && faceFns.faceGeomType(face) !== "PLANE") {
|
|
1025
|
+
if (faceFns.faceGeomType(face) !== "CYLINDRE")
|
|
1026
|
+
return errors.err(
|
|
1027
|
+
errors.validationError(
|
|
1028
|
+
"UNSUPPORTED_FACE_TYPE",
|
|
1029
|
+
"Only planar and cylindrical faces can be unwrapped for sketching"
|
|
1030
|
+
)
|
|
1031
|
+
);
|
|
1032
|
+
const cylinder = scope.register(geomSurf.get().Cylinder());
|
|
1033
|
+
if (!cylinder.Direct()) {
|
|
1034
|
+
geomSurf = geomSurf.get().UReversed();
|
|
1035
|
+
}
|
|
1036
|
+
const radius = cylinder.Radius();
|
|
1037
|
+
transformation = stretchTransform2d(1 / radius, [0, 1]);
|
|
1038
|
+
}
|
|
1039
|
+
if (scale === "bounds") {
|
|
1040
|
+
transformation = scope.register(new oc.gp_GTrsf2d_1());
|
|
1041
|
+
transformation.SetAffinity(uAxis, bounds.uMax - bounds.uMin);
|
|
1042
|
+
if (bounds.uMin !== 0) {
|
|
1043
|
+
const trans = scope.register(new oc.gp_GTrsf2d_1());
|
|
1044
|
+
trans.SetTranslationPart(new oc.gp_XY_2(0, -bounds.uMin));
|
|
1045
|
+
transformation.Multiply(trans);
|
|
1046
|
+
}
|
|
1047
|
+
const vTransformation = scope.register(new oc.gp_GTrsf2d_1());
|
|
1048
|
+
vTransformation.SetAffinity(_vAxis, bounds.vMax - bounds.vMin);
|
|
1049
|
+
transformation.Multiply(vTransformation);
|
|
1050
|
+
if (bounds.vMin !== 0) {
|
|
1051
|
+
const trans = scope.register(new oc.gp_GTrsf2d_1());
|
|
1052
|
+
trans.SetTranslationPart(scope.register(new oc.gp_XY_2(0, -bounds.vMin)));
|
|
1053
|
+
transformation.Multiply(trans);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
const modifiedCurves = transformCurves(curves, transformation);
|
|
1057
|
+
const edges = curvesAsEdgesOnSurface(modifiedCurves, geomSurf);
|
|
1058
|
+
return errors.ok(edges);
|
|
1059
|
+
} catch (_) {
|
|
1060
|
+
var _error = _, _hasError = true;
|
|
1061
|
+
} finally {
|
|
1062
|
+
__callDispose(_stack, _error, _hasError);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
function edgeToCurve(e, face) {
|
|
1066
|
+
var _stack = [];
|
|
1067
|
+
try {
|
|
1068
|
+
const oc = occtBoundary.getKernel().oc;
|
|
1069
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
1070
|
+
const adaptor = scope.register(new oc.BRepAdaptor_Curve2d_2(e.wrapped, face.wrapped));
|
|
1071
|
+
const trimmed = new oc.Geom2d_TrimmedCurve(
|
|
1072
|
+
adaptor.Curve(),
|
|
1073
|
+
adaptor.FirstParameter(),
|
|
1074
|
+
adaptor.LastParameter(),
|
|
1075
|
+
true,
|
|
1076
|
+
true
|
|
1077
|
+
);
|
|
1078
|
+
if (curveFns.getOrientation(e) === "backward") {
|
|
1079
|
+
trimmed.Reverse();
|
|
1080
|
+
}
|
|
1081
|
+
return new Curve2D(new oc.Handle_Geom2d_Curve_2(trimmed));
|
|
1082
|
+
} catch (_) {
|
|
1083
|
+
var _error = _, _hasError = true;
|
|
1084
|
+
} finally {
|
|
1085
|
+
__callDispose(_stack, _error, _hasError);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
const viewbox = (bbox, margin = 1) => {
|
|
1089
|
+
const minX = bbox.bounds[0][0] - margin;
|
|
1090
|
+
const minY = -bbox.bounds[1][1] - margin;
|
|
1091
|
+
return `${minX} ${minY} ${bbox.width + 2 * margin} ${bbox.height + 2 * margin}`;
|
|
1092
|
+
};
|
|
1093
|
+
const asSVG = (body, boundingBox, margin = 1) => {
|
|
1094
|
+
const vbox = viewbox(boundingBox, margin);
|
|
1095
|
+
return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="${vbox}" fill="none" stroke="black" stroke-width="0.6%" vector-effect="non-scaling-stroke">
|
|
1096
|
+
${body}
|
|
1097
|
+
</svg>`;
|
|
1098
|
+
};
|
|
1099
|
+
function assembleWire(listOfEdges) {
|
|
1100
|
+
const oc = occtBoundary.getKernel().oc;
|
|
1101
|
+
const builder = new oc.BRepBuilderAPI_MakeWire_1();
|
|
1102
|
+
listOfEdges.forEach((e) => {
|
|
1103
|
+
builder.Add_1(e.wrapped);
|
|
1104
|
+
});
|
|
1105
|
+
return shapeTypes.createWire(builder.Wire());
|
|
1106
|
+
}
|
|
1107
|
+
class Blueprint {
|
|
1108
|
+
/** Ordered 2D curve segments that compose this blueprint. */
|
|
1109
|
+
curves;
|
|
1110
|
+
_boundingBox;
|
|
1111
|
+
_orientation;
|
|
1112
|
+
_guessedOrientation;
|
|
1113
|
+
/** Create a blueprint from an ordered array of 2D curves.
|
|
1114
|
+
*
|
|
1115
|
+
* @throws Error if the curves array is empty.
|
|
1116
|
+
*/
|
|
1117
|
+
constructor(curves) {
|
|
1118
|
+
if (curves.length === 0) {
|
|
1119
|
+
throw new Error("Blueprint requires at least one curve");
|
|
1120
|
+
}
|
|
1121
|
+
this.curves = curves;
|
|
1122
|
+
this._boundingBox = null;
|
|
1123
|
+
this._orientation = null;
|
|
1124
|
+
this._guessedOrientation = null;
|
|
1125
|
+
}
|
|
1126
|
+
/** Release WASM resources held by the underlying curves and bounding box. */
|
|
1127
|
+
delete() {
|
|
1128
|
+
this.curves.forEach((c) => {
|
|
1129
|
+
c.delete();
|
|
1130
|
+
});
|
|
1131
|
+
if (this._boundingBox) this._boundingBox.delete();
|
|
1132
|
+
}
|
|
1133
|
+
/** Return a deep copy of this blueprint. */
|
|
1134
|
+
clone() {
|
|
1135
|
+
return new Blueprint(this.curves.map((c) => c.clone()));
|
|
1136
|
+
}
|
|
1137
|
+
/** Return a multi-line string representation for debugging. */
|
|
1138
|
+
get repr() {
|
|
1139
|
+
return ["Blueprint", ...this.curves.map((c) => c.repr)].join("\n");
|
|
1140
|
+
}
|
|
1141
|
+
/** Compute (and cache) the axis-aligned bounding box of all curves. */
|
|
1142
|
+
get boundingBox() {
|
|
1143
|
+
if (!this._boundingBox) {
|
|
1144
|
+
this._boundingBox = curvesBoundingBox(this.curves);
|
|
1145
|
+
}
|
|
1146
|
+
return this._boundingBox;
|
|
1147
|
+
}
|
|
1148
|
+
/** Determine the winding direction of the blueprint via the shoelace formula.
|
|
1149
|
+
*
|
|
1150
|
+
* @remarks Uses an approximation based on curve midpoints for non-linear
|
|
1151
|
+
* segments. The result is cached after the first call.
|
|
1152
|
+
*/
|
|
1153
|
+
get orientation() {
|
|
1154
|
+
if (this._orientation) return this._orientation;
|
|
1155
|
+
if (this._guessedOrientation) return this._guessedOrientation;
|
|
1156
|
+
const vertices = this.curves.flatMap((c) => {
|
|
1157
|
+
if (c.geomType !== "LINE") {
|
|
1158
|
+
return [c.firstPoint, c.value(0.5)];
|
|
1159
|
+
}
|
|
1160
|
+
return [c.firstPoint];
|
|
1161
|
+
});
|
|
1162
|
+
const approximateArea = vertices.map((v1, i) => {
|
|
1163
|
+
const v2 = vertices[(i + 1) % vertices.length];
|
|
1164
|
+
return (v2[0] - v1[0]) * (v2[1] + v1[1]);
|
|
1165
|
+
}).reduce((a, b) => a + b, 0);
|
|
1166
|
+
this._guessedOrientation = approximateArea > 0 ? "clockwise" : "counterClockwise";
|
|
1167
|
+
return this._guessedOrientation;
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Stretch the blueprint along a direction by a given ratio.
|
|
1171
|
+
*
|
|
1172
|
+
* @param ratio - Stretch factor (1 = unchanged).
|
|
1173
|
+
* @param direction - Unit direction vector to stretch along.
|
|
1174
|
+
* @param origin - Fixed point of the stretch (defaults to the origin).
|
|
1175
|
+
* @returns A new stretched Blueprint.
|
|
1176
|
+
*/
|
|
1177
|
+
stretch(ratio, direction, origin = [0, 0]) {
|
|
1178
|
+
const curves = transformCurves(this.curves, stretchTransform2d(ratio, direction, origin));
|
|
1179
|
+
return new Blueprint(curves);
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Uniformly scale the blueprint around a center point.
|
|
1183
|
+
*
|
|
1184
|
+
* @param scaleFactor - Scale multiplier (>1 enlarges, <1 shrinks).
|
|
1185
|
+
* @param center - Center of scaling (defaults to the bounding box center).
|
|
1186
|
+
* @returns A new scaled Blueprint.
|
|
1187
|
+
*/
|
|
1188
|
+
scale(scaleFactor, center) {
|
|
1189
|
+
const centerPoint = center || this.boundingBox.center;
|
|
1190
|
+
const curves = transformCurves(this.curves, scaleTransform2d(scaleFactor, centerPoint));
|
|
1191
|
+
return new Blueprint(curves);
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Rotate the blueprint by an angle in degrees.
|
|
1195
|
+
*
|
|
1196
|
+
* @param angle - Rotation angle in degrees (positive = counter-clockwise).
|
|
1197
|
+
* @param center - Center of rotation (defaults to the origin).
|
|
1198
|
+
* @returns A new rotated Blueprint.
|
|
1199
|
+
*/
|
|
1200
|
+
rotate(angle, center) {
|
|
1201
|
+
const curves = transformCurves(this.curves, rotateTransform2d(angle * vecOps.DEG2RAD, center));
|
|
1202
|
+
return new Blueprint(curves);
|
|
1203
|
+
}
|
|
1204
|
+
translate(xDistOrPoint, yDist = 0) {
|
|
1205
|
+
const translationVector = isPoint2D(xDistOrPoint) ? xDistOrPoint : [xDistOrPoint, yDist];
|
|
1206
|
+
const curves = transformCurves(this.curves, translationTransform2d(translationVector));
|
|
1207
|
+
return new Blueprint(curves);
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Mirror the blueprint across a point or plane.
|
|
1211
|
+
*
|
|
1212
|
+
* @param centerOrDirection - Mirror center (center mode) or plane normal (plane mode).
|
|
1213
|
+
* @param origin - Origin for plane-mode mirroring.
|
|
1214
|
+
* @param mode - `'center'` for point symmetry, `'plane'` for reflection across an axis.
|
|
1215
|
+
* @returns A new mirrored Blueprint.
|
|
1216
|
+
*/
|
|
1217
|
+
mirror(centerOrDirection, origin = [0, 0], mode = "center") {
|
|
1218
|
+
const curves = transformCurves(this.curves, mirrorTransform2d(centerOrDirection, origin, mode));
|
|
1219
|
+
return new Blueprint(curves);
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Project this 2D blueprint onto a 3D plane, producing a wire and metadata.
|
|
1223
|
+
*
|
|
1224
|
+
* @param inputPlane - Named plane (`"XY"`, `"XZ"`, etc.) or a custom Plane.
|
|
1225
|
+
* @param origin - Origin offset; a number sets the offset along the plane normal.
|
|
1226
|
+
* @returns Sketch data containing the projected wire and default orientation.
|
|
1227
|
+
*/
|
|
1228
|
+
sketchOnPlane(inputPlane, origin) {
|
|
1229
|
+
const plane = inputPlane && typeof inputPlane !== "string" ? { ...inputPlane } : makePlane(inputPlane, origin);
|
|
1230
|
+
const edges = curvesAsEdgesOnPlane(this.curves, plane);
|
|
1231
|
+
const wire = assembleWire(edges);
|
|
1232
|
+
return {
|
|
1233
|
+
wire,
|
|
1234
|
+
defaultOrigin: plane.origin,
|
|
1235
|
+
defaultDirection: plane.zDir
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Map this 2D blueprint onto a 3D face's UV surface.
|
|
1240
|
+
*
|
|
1241
|
+
* @param face - Target face to project onto.
|
|
1242
|
+
* @param scaleMode - How UV coordinates are interpreted (`'original'`, `'bounds'`, or `'native'`).
|
|
1243
|
+
* @returns Sketch data containing the wire mapped onto the face.
|
|
1244
|
+
*/
|
|
1245
|
+
sketchOnFace(face, scaleMode) {
|
|
1246
|
+
const oc = occtBoundary.getKernel().oc;
|
|
1247
|
+
const edges = errors.unwrap(curvesAsEdgesOnFace(this.curves, face, scaleMode));
|
|
1248
|
+
const wire = assembleWire(edges);
|
|
1249
|
+
oc.BRepLib.BuildCurves3d_2(wire.wrapped);
|
|
1250
|
+
const wireFixer = new oc.ShapeFix_Wire_2(wire.wrapped, face.wrapped, 1e-9);
|
|
1251
|
+
wireFixer.FixEdgeCurves();
|
|
1252
|
+
wireFixer.delete();
|
|
1253
|
+
return { wire, baseFace: face };
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Create a face on a target face's surface defined by this blueprint's profile.
|
|
1257
|
+
*
|
|
1258
|
+
* @param face - The face whose surface the sub-face lies on.
|
|
1259
|
+
* @param origin - Optional UV origin offset (defaults to the face center).
|
|
1260
|
+
* @returns A new Face bounded by the blueprint's profile.
|
|
1261
|
+
*/
|
|
1262
|
+
subFace(face, origin) {
|
|
1263
|
+
const originPoint = origin || [...faceFns.faceCenter(face)];
|
|
1264
|
+
const originVec3 = occtBoundary.toVec3(originPoint);
|
|
1265
|
+
const sketch = this.translate(faceFns.uvCoordinates(face, originVec3)).sketchOnFace(face, "original");
|
|
1266
|
+
return errors.unwrap(surfaceBuilders.makeFace(sketch.wire));
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Cut a prism-shaped hole through a solid along a face using this blueprint.
|
|
1270
|
+
*
|
|
1271
|
+
* @param shape - The solid to punch through.
|
|
1272
|
+
* @param face - The face on which the hole profile is placed.
|
|
1273
|
+
* @param options - Optional hole parameters.
|
|
1274
|
+
* @param options.height - Hole depth; `null` (default) cuts through the entire solid.
|
|
1275
|
+
* @param options.origin - UV origin on the face for the blueprint placement.
|
|
1276
|
+
* @param options.draftAngle - Taper angle in degrees (0 = straight hole).
|
|
1277
|
+
* @returns The modified shape with the hole removed.
|
|
1278
|
+
*/
|
|
1279
|
+
punchHole(shape, face, {
|
|
1280
|
+
height = null,
|
|
1281
|
+
origin = null,
|
|
1282
|
+
draftAngle = 0
|
|
1283
|
+
} = {}) {
|
|
1284
|
+
var _stack = [];
|
|
1285
|
+
try {
|
|
1286
|
+
const oc = occtBoundary.getKernel().oc;
|
|
1287
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
1288
|
+
const foundFace = errors.unwrap(helpers.getSingleFace(face, shape));
|
|
1289
|
+
const hole = this.subFace(foundFace, origin);
|
|
1290
|
+
const maker = scope.register(
|
|
1291
|
+
new oc.BRepFeat_MakeDPrism_1(
|
|
1292
|
+
shape.wrapped,
|
|
1293
|
+
hole.wrapped,
|
|
1294
|
+
foundFace.wrapped,
|
|
1295
|
+
draftAngle * vecOps.DEG2RAD,
|
|
1296
|
+
0,
|
|
1297
|
+
false
|
|
1298
|
+
)
|
|
1299
|
+
);
|
|
1300
|
+
if (height) {
|
|
1301
|
+
maker.Perform_1(height);
|
|
1302
|
+
} else {
|
|
1303
|
+
maker.PerformThruAll();
|
|
1304
|
+
}
|
|
1305
|
+
return errors.unwrap(faceFns.cast(maker.Shape()));
|
|
1306
|
+
} catch (_) {
|
|
1307
|
+
var _error = _, _hasError = true;
|
|
1308
|
+
} finally {
|
|
1309
|
+
__callDispose(_stack, _error, _hasError);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
/** Convert the blueprint to an SVG path `d` attribute string. */
|
|
1313
|
+
toSVGPathD() {
|
|
1314
|
+
var _stack = [];
|
|
1315
|
+
try {
|
|
1316
|
+
const scope = __using(_stack, new shapeTypes.DisposalScope());
|
|
1317
|
+
const bp = this.clone().mirror([1, 0], [0, 0], "plane");
|
|
1318
|
+
const compatibleCurves = approximateAsSvgCompatibleCurve(bp.curves);
|
|
1319
|
+
const path = compatibleCurves.flatMap((c) => {
|
|
1320
|
+
return adaptedCurveToPathElem(scope.register(c.adaptor()), c.lastPoint);
|
|
1321
|
+
});
|
|
1322
|
+
const [startX, startY] = bp.curves[0].firstPoint;
|
|
1323
|
+
return `M ${round5(startX)} ${round5(startY)} ${path.join(" ")}${bp.isClosed() ? " Z" : ""}`;
|
|
1324
|
+
} catch (_) {
|
|
1325
|
+
var _error = _, _hasError = true;
|
|
1326
|
+
} finally {
|
|
1327
|
+
__callDispose(_stack, _error, _hasError);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
/** Wrap the SVG path data in a `<path>` element string. */
|
|
1331
|
+
toSVGPath() {
|
|
1332
|
+
return `<path d="${this.toSVGPathD()}" />`;
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Compute the SVG `viewBox` attribute for this blueprint.
|
|
1336
|
+
*
|
|
1337
|
+
* @param margin - Extra padding around the bounding box in drawing units.
|
|
1338
|
+
*/
|
|
1339
|
+
toSVGViewBox(margin = 1) {
|
|
1340
|
+
return viewbox(this.boundingBox, margin);
|
|
1341
|
+
}
|
|
1342
|
+
/** Return the SVG path `d` strings for this blueprint as an array. */
|
|
1343
|
+
toSVGPaths() {
|
|
1344
|
+
return [this.toSVGPathD()];
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Render a complete SVG document string for this blueprint.
|
|
1348
|
+
*
|
|
1349
|
+
* @param margin - Extra padding around the bounding box in drawing units.
|
|
1350
|
+
*/
|
|
1351
|
+
toSVG(margin = 1) {
|
|
1352
|
+
return asSVG(this.toSVGPath(), this.boundingBox, margin);
|
|
1353
|
+
}
|
|
1354
|
+
/** Get the start point of the first curve. */
|
|
1355
|
+
get firstPoint() {
|
|
1356
|
+
return this.curves[0].firstPoint;
|
|
1357
|
+
}
|
|
1358
|
+
/** Get the end point of the last curve. */
|
|
1359
|
+
get lastPoint() {
|
|
1360
|
+
return this.curves[this.curves.length - 1].lastPoint;
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Test whether a 2D point lies inside this closed blueprint.
|
|
1364
|
+
*
|
|
1365
|
+
* Uses ray-casting (intersection counting) against a segment from the point
|
|
1366
|
+
* to a location guaranteed to be outside the bounding box.
|
|
1367
|
+
*
|
|
1368
|
+
* @remarks Returns `false` for points on the boundary.
|
|
1369
|
+
* @returns `true` if the point is strictly inside the blueprint.
|
|
1370
|
+
*/
|
|
1371
|
+
isInside(point) {
|
|
1372
|
+
if (!this.boundingBox.containsPoint(point)) return false;
|
|
1373
|
+
const oc = occtBoundary.getKernel().oc;
|
|
1374
|
+
const intersector = new oc.Geom2dAPI_InterCurveCurve_1();
|
|
1375
|
+
try {
|
|
1376
|
+
const segment = make2dSegmentCurve(point, this.boundingBox.outsidePoint());
|
|
1377
|
+
let crossCounts = 0;
|
|
1378
|
+
const onCurve = this.curves.find((c) => c.isOnCurve(point));
|
|
1379
|
+
if (onCurve) return false;
|
|
1380
|
+
this.curves.forEach((c) => {
|
|
1381
|
+
if (c.boundingBox.isOut(segment.boundingBox)) return;
|
|
1382
|
+
intersector.Init_1(segment.wrapped, c.wrapped, 1e-9);
|
|
1383
|
+
crossCounts += Number(intersector.NbPoints());
|
|
1384
|
+
});
|
|
1385
|
+
return !!(crossCounts % 2);
|
|
1386
|
+
} finally {
|
|
1387
|
+
intersector.delete();
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
/** Check whether the first and last points coincide (the profile is closed). */
|
|
1391
|
+
isClosed() {
|
|
1392
|
+
return helpers.samePoint(this.firstPoint, this.lastPoint);
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Test whether this blueprint's curves intersect with another blueprint's curves.
|
|
1396
|
+
*
|
|
1397
|
+
* @remarks Uses bounding-box pre-filtering for early rejection.
|
|
1398
|
+
*/
|
|
1399
|
+
intersects(other) {
|
|
1400
|
+
if (this.boundingBox.isOut(other.boundingBox)) return false;
|
|
1401
|
+
const oc = occtBoundary.getKernel().oc;
|
|
1402
|
+
const intersector = new oc.Geom2dAPI_InterCurveCurve_1();
|
|
1403
|
+
try {
|
|
1404
|
+
for (const myCurve of this.curves) {
|
|
1405
|
+
for (const otherCurve of other.curves) {
|
|
1406
|
+
if (myCurve.boundingBox.isOut(otherCurve.boundingBox)) continue;
|
|
1407
|
+
intersector.Init_1(myCurve.wrapped, otherCurve.wrapped, 1e-9);
|
|
1408
|
+
if (intersector.NbPoints() || intersector.NbSegments()) return true;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
return false;
|
|
1412
|
+
} finally {
|
|
1413
|
+
intersector.delete();
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
exports.Blueprint = Blueprint;
|
|
1418
|
+
exports.BoundingBox2d = BoundingBox2d;
|
|
1419
|
+
exports.Curve2D = Curve2D;
|
|
1420
|
+
exports.approximateAsBSpline = approximateAsBSpline;
|
|
1421
|
+
exports.approximateAsSvgCompatibleCurve = approximateAsSvgCompatibleCurve;
|
|
1422
|
+
exports.asSVG = asSVG;
|
|
1423
|
+
exports.axis2d = axis2d;
|
|
1424
|
+
exports.deserializeCurve2D = deserializeCurve2D;
|
|
1425
|
+
exports.edgeToCurve = edgeToCurve;
|
|
1426
|
+
exports.isPoint2D = isPoint2D;
|
|
1427
|
+
exports.make2dArcFromCenter = make2dArcFromCenter;
|
|
1428
|
+
exports.make2dBezierCurve = make2dBezierCurve;
|
|
1429
|
+
exports.make2dCircle = make2dCircle;
|
|
1430
|
+
exports.make2dEllipse = make2dEllipse;
|
|
1431
|
+
exports.make2dEllipseArc = make2dEllipseArc;
|
|
1432
|
+
exports.make2dInerpolatedBSplineCurve = make2dInerpolatedBSplineCurve;
|
|
1433
|
+
exports.make2dSegmentCurve = make2dSegmentCurve;
|
|
1434
|
+
exports.make2dTangentArc = make2dTangentArc;
|
|
1435
|
+
exports.make2dThreePointArc = make2dThreePointArc;
|
|
1436
|
+
exports.makePlane = makePlane;
|
|
1437
|
+
exports.mirror = mirror;
|
|
1438
|
+
exports.removeDuplicatePoints = removeDuplicatePoints;
|
|
1439
|
+
exports.viewbox = viewbox;
|