@faicad/faijs 0.18.2 → 0.19.0
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/api/api-namespace.d.ts.map +1 -1
- package/dist/api/api-namespace.js +4 -1
- package/dist/api/api-namespace.js.map +1 -1
- package/dist/api/brep-mirror/joinery-brep.d.ts +1 -1
- package/dist/api/brep-mirror/joinery-brep.d.ts.map +1 -1
- package/dist/api/brep-mirror/joinery-brep.js +19 -19
- package/dist/api/brep-mirror/joinery-brep.js.map +1 -1
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +3 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/profile.d.ts.map +1 -1
- package/dist/api/profile.js +113 -182
- package/dist/api/profile.js.map +1 -1
- package/dist/api/punch-hole.d.ts +74 -0
- package/dist/api/punch-hole.d.ts.map +1 -0
- package/dist/api/punch-hole.js +207 -0
- package/dist/api/punch-hole.js.map +1 -0
- package/dist/api/sketch-on-face.d.ts +85 -0
- package/dist/api/sketch-on-face.d.ts.map +1 -0
- package/dist/api/sketch-on-face.js +136 -0
- package/dist/api/sketch-on-face.js.map +1 -0
- package/dist/api/sketch-on-plane.d.ts +81 -0
- package/dist/api/sketch-on-plane.d.ts.map +1 -0
- package/dist/api/sketch-on-plane.js +103 -0
- package/dist/api/sketch-on-plane.js.map +1 -0
- package/dist/boolean/csg-backend.d.ts +6 -6
- package/dist/boolean/csg-backend.d.ts.map +1 -1
- package/dist/boolean/csg-backend.js +9 -9
- package/dist/boolean/csg-backend.js.map +1 -1
- package/dist/boolean/csg-core.d.ts +10 -10
- package/dist/boolean/csg-core.d.ts.map +1 -1
- package/dist/boolean/csg-core.js +24 -24
- package/dist/boolean/csg-core.js.map +1 -1
- package/dist/boolean/extrude-helpers.d.ts +8 -8
- package/dist/boolean/extrude-helpers.d.ts.map +1 -1
- package/dist/boolean/extrude-helpers.js +13 -13
- package/dist/boolean/extrude-helpers.js.map +1 -1
- package/dist/brep/brep-ops.d.ts +20 -6
- package/dist/brep/brep-ops.d.ts.map +1 -1
- package/dist/brep/brep-ops.js +38 -12
- package/dist/brep/brep-ops.js.map +1 -1
- package/dist/browser-host/csg-worker-protocol.d.ts +3 -3
- package/dist/browser-host/csg-worker-protocol.d.ts.map +1 -1
- package/dist/browser-host/csg-worker-protocol.js.map +1 -1
- package/dist/browser-host/csg-worker.js +3 -3
- package/dist/browser-host/csg-worker.js.map +1 -1
- package/dist/browser-host/inline-csg-backend.d.ts +3 -3
- package/dist/browser-host/inline-csg-backend.d.ts.map +1 -1
- package/dist/browser-host/inline-csg-backend.js +3 -3
- package/dist/browser-host/inline-csg-backend.js.map +1 -1
- package/dist/browser-host/worker-csg-backend.d.ts +3 -3
- package/dist/browser-host/worker-csg-backend.d.ts.map +1 -1
- package/dist/browser-host/worker-csg-backend.js.map +1 -1
- package/dist/cad-runtime/ports.d.ts +3 -3
- package/dist/cad-runtime/ports.d.ts.map +1 -1
- package/dist/cad-runtime/ports.js.map +1 -1
- package/dist/geometry2d/adapt.d.ts +46 -0
- package/dist/geometry2d/adapt.d.ts.map +1 -0
- package/dist/geometry2d/adapt.js +41 -0
- package/dist/geometry2d/adapt.js.map +1 -0
- package/dist/geometry2d/bbox2d.d.ts +82 -0
- package/dist/geometry2d/bbox2d.d.ts.map +1 -0
- package/dist/geometry2d/bbox2d.js +123 -0
- package/dist/geometry2d/bbox2d.js.map +1 -0
- package/dist/geometry2d/blueprint-sketcher.d.ts +27 -0
- package/dist/geometry2d/blueprint-sketcher.d.ts.map +1 -0
- package/dist/geometry2d/blueprint-sketcher.js +29 -0
- package/dist/geometry2d/blueprint-sketcher.js.map +1 -0
- package/dist/geometry2d/blueprint.d.ts +102 -0
- package/dist/geometry2d/blueprint.d.ts.map +1 -0
- package/dist/geometry2d/blueprint.js +225 -0
- package/dist/geometry2d/blueprint.js.map +1 -0
- package/dist/geometry2d/blueprints.d.ts +58 -0
- package/dist/geometry2d/blueprints.d.ts.map +1 -0
- package/dist/geometry2d/blueprints.js +72 -0
- package/dist/geometry2d/blueprints.js.map +1 -0
- package/dist/geometry2d/bridge/lift-on-plane.d.ts +50 -0
- package/dist/geometry2d/bridge/lift-on-plane.d.ts.map +1 -0
- package/dist/geometry2d/bridge/lift-on-plane.js +72 -0
- package/dist/geometry2d/bridge/lift-on-plane.js.map +1 -0
- package/dist/geometry2d/bridge/on-surface.d.ts +98 -0
- package/dist/geometry2d/bridge/on-surface.d.ts.map +1 -0
- package/dist/geometry2d/bridge/on-surface.js +94 -0
- package/dist/geometry2d/bridge/on-surface.js.map +1 -0
- package/dist/geometry2d/bridge/plane.d.ts +69 -0
- package/dist/geometry2d/bridge/plane.d.ts.map +1 -0
- package/dist/geometry2d/bridge/plane.js +96 -0
- package/dist/geometry2d/bridge/plane.js.map +1 -0
- package/dist/geometry2d/canned-blueprints.d.ts +44 -0
- package/dist/geometry2d/canned-blueprints.d.ts.map +1 -0
- package/dist/geometry2d/canned-blueprints.js +94 -0
- package/dist/geometry2d/canned-blueprints.js.map +1 -0
- package/dist/geometry2d/compound-blueprint.d.ts +72 -0
- package/dist/geometry2d/compound-blueprint.d.ts.map +1 -0
- package/dist/geometry2d/compound-blueprint.js +95 -0
- package/dist/geometry2d/compound-blueprint.js.map +1 -0
- package/dist/geometry2d/curve2d.d.ts +298 -0
- package/dist/geometry2d/curve2d.d.ts.map +1 -0
- package/dist/geometry2d/curve2d.js +1110 -0
- package/dist/geometry2d/curve2d.js.map +1 -0
- package/dist/geometry2d/index.d.ts +22 -0
- package/dist/geometry2d/index.d.ts.map +1 -0
- package/dist/geometry2d/index.js +21 -0
- package/dist/geometry2d/index.js.map +1 -0
- package/dist/geometry2d/organise.d.ts +28 -0
- package/dist/geometry2d/organise.d.ts.map +1 -0
- package/dist/geometry2d/organise.js +108 -0
- package/dist/geometry2d/organise.js.map +1 -0
- package/dist/geometry2d/pen-sketcher.d.ts +340 -0
- package/dist/geometry2d/pen-sketcher.d.ts.map +1 -0
- package/dist/geometry2d/pen-sketcher.js +477 -0
- package/dist/geometry2d/pen-sketcher.js.map +1 -0
- package/dist/geometry2d/point.d.ts +44 -0
- package/dist/geometry2d/point.d.ts.map +1 -0
- package/dist/geometry2d/point.js +57 -0
- package/dist/geometry2d/point.js.map +1 -0
- package/dist/geometry2d/svg-ellipse.d.ts +74 -0
- package/dist/geometry2d/svg-ellipse.d.ts.map +1 -0
- package/dist/geometry2d/svg-ellipse.js +163 -0
- package/dist/geometry2d/svg-ellipse.js.map +1 -0
- package/dist/lang/symbol-table.generated.d.ts +3 -0
- package/dist/lang/symbol-table.generated.d.ts.map +1 -1
- package/dist/lang/symbol-table.generated.js +3 -0
- package/dist/lang/symbol-table.generated.js.map +1 -1
- package/dist/mesh/types.d.ts +1 -1
- package/dist/mesh/types.d.ts.map +1 -1
- package/dist/mesh/types.js.map +1 -1
- package/package.json +9 -1
package/dist/api/profile.js
CHANGED
|
@@ -18,6 +18,11 @@ import { getBrepApi } from '../brep/handle-bridge.js';
|
|
|
18
18
|
import { solidToShape } from '../brep/brep-ops.js';
|
|
19
19
|
import { fromBrep, fromBrepCurve } from '../shape.js';
|
|
20
20
|
import { defineOp } from '../sdk.js';
|
|
21
|
+
import { Blueprint } from '../geometry2d/blueprint.js';
|
|
22
|
+
import { CompoundBlueprint } from '../geometry2d/compound-blueprint.js';
|
|
23
|
+
import { organiseBlueprints } from '../geometry2d/organise.js';
|
|
24
|
+
import { curveBounds, evaluateCurve2d } from '../geometry2d/curve2d.js';
|
|
25
|
+
import { profileSegToCurve } from '../geometry2d/adapt.js';
|
|
21
26
|
// ── 参数自校验 ──
|
|
22
27
|
/**
|
|
23
28
|
* Validate profile parameters: `contours` must be a non-empty array of loops,
|
|
@@ -38,133 +43,6 @@ export function assertProfileParams(params) {
|
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
|
-
/**
|
|
42
|
-
* 单环带符号面积(对每段做有向面积积分,而非只用段起点 shoelace)。
|
|
43
|
-
*
|
|
44
|
-
* 直线段:1/2 (x1·y2 − x2·y1)。
|
|
45
|
-
* 圆弧段:1/2 ∫(x dy − y dx) 的解析解 = 1/2 [r²·sweep + cx·r(sin a1 − sin a0) − cy·r(cos a1 − cos a0)]。
|
|
46
|
-
* 整圆(单段弧、起终点重合)由此得到 πr²,不再是 0——否则「整圆 + 方孔」会把孔误判成外环。
|
|
47
|
-
*/
|
|
48
|
-
function loopSignedArea(loop) {
|
|
49
|
-
let a = 0;
|
|
50
|
-
for (const seg of loop.segments) {
|
|
51
|
-
if (seg.kind === 'line') {
|
|
52
|
-
a += (seg.x1 * seg.y2 - seg.x2 * seg.y1) / 2;
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
let sweep = seg.endAngle - seg.startAngle;
|
|
56
|
-
while (sweep <= 0)
|
|
57
|
-
sweep += 2 * Math.PI;
|
|
58
|
-
while (sweep > 2 * Math.PI)
|
|
59
|
-
sweep -= 2 * Math.PI;
|
|
60
|
-
const { cx, cy, radius: r, startAngle: a0 } = seg;
|
|
61
|
-
const a1 = a0 + sweep;
|
|
62
|
-
a += 0.5 * (r * r * sweep + cx * r * (Math.sin(a1) - Math.sin(a0)) - cy * r * (Math.cos(a1) - Math.cos(a0)));
|
|
63
|
-
}
|
|
64
|
-
return a;
|
|
65
|
-
}
|
|
66
|
-
/** 圆弧 → 一条或两条 makeArcEdge(整圆拆分)。 */
|
|
67
|
-
function arcToHandles(kernel, seg) {
|
|
68
|
-
const { cx, cy, radius, startAngle, endAngle, ccw = true } = seg;
|
|
69
|
-
// GOTCHA(2026-09-25,A3 FCBL_tree_entourage):ccw 必须参与弧的几何方向。
|
|
70
|
-
// makeArcEdge 按「起点-中点-终点」三点定弧,旧实现一律取 CCW 中点
|
|
71
|
-
// ((start+end)/2 归一化到 (0,2π]),ccw:false 的弧被画成 CCW **长弧**(补角)。
|
|
72
|
-
// 实测后果:长弧穿过旋转轴 → revolve 产物退化(bbox NaN)→ BRepMesh 挂死
|
|
73
|
-
// (进程无声死亡);正确 CW 短弧下内核 revolve 仅 ~19ms。
|
|
74
|
-
let sweep;
|
|
75
|
-
let midAngle;
|
|
76
|
-
if (ccw) {
|
|
77
|
-
sweep = endAngle - startAngle;
|
|
78
|
-
while (sweep <= 0)
|
|
79
|
-
sweep += 2 * Math.PI;
|
|
80
|
-
while (sweep > 2 * Math.PI)
|
|
81
|
-
sweep -= 2 * Math.PI;
|
|
82
|
-
midAngle = startAngle + sweep / 2;
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
// 顺时针:从 startAngle 递减到 endAngle,扫角取反向归一化
|
|
86
|
-
sweep = startAngle - endAngle;
|
|
87
|
-
while (sweep <= 0)
|
|
88
|
-
sweep += 2 * Math.PI;
|
|
89
|
-
while (sweep > 2 * Math.PI)
|
|
90
|
-
sweep -= 2 * Math.PI;
|
|
91
|
-
midAngle = startAngle - sweep / 2;
|
|
92
|
-
}
|
|
93
|
-
const makeOne = (a0, am, a1) => {
|
|
94
|
-
const start = { x: cx + radius * Math.cos(a0), y: cy + radius * Math.sin(a0), z: 0 };
|
|
95
|
-
const m = { x: cx + radius * Math.cos(am), y: cy + radius * Math.sin(am), z: 0 };
|
|
96
|
-
const end = { x: cx + radius * Math.cos(a1), y: cy + radius * Math.sin(a1), z: 0 };
|
|
97
|
-
return kernel.makeArcEdge(start, m, end);
|
|
98
|
-
};
|
|
99
|
-
// 整圆(sweep≈2π):拆成两段半圆,避免 makeArcEdge 起点≈终点退化
|
|
100
|
-
if (sweep >= 2 * Math.PI - 1e-9) {
|
|
101
|
-
const half = 2 * Math.PI / 2;
|
|
102
|
-
const dir = ccw ? 1 : -1;
|
|
103
|
-
return [
|
|
104
|
-
makeOne(startAngle, startAngle + dir * half / 2, startAngle + dir * half),
|
|
105
|
-
makeOne(startAngle + dir * half, startAngle + dir * (half + half / 2), startAngle + dir * 2 * Math.PI),
|
|
106
|
-
];
|
|
107
|
-
}
|
|
108
|
-
return [makeOne(startAngle, midAngle, endAngle)];
|
|
109
|
-
}
|
|
110
|
-
/** 单环 → wire handle(仅构面,不释放中间句柄,遵循现有惯例)。 */
|
|
111
|
-
function loopToWire(kernel, loop) {
|
|
112
|
-
const edges = [];
|
|
113
|
-
for (const seg of loop.segments) {
|
|
114
|
-
if (seg.kind === 'line') {
|
|
115
|
-
edges.push(kernel.makeLineEdge({ x: seg.x1, y: seg.y1, z: 0 }, { x: seg.x2, y: seg.y2, z: 0 }));
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
// 极小扫角(退化弧)→ 退化为线段,避免构边失败
|
|
119
|
-
let sweep = seg.endAngle - seg.startAngle;
|
|
120
|
-
while (sweep <= 0)
|
|
121
|
-
sweep += 2 * Math.PI;
|
|
122
|
-
while (sweep > 2 * Math.PI)
|
|
123
|
-
sweep -= 2 * Math.PI;
|
|
124
|
-
if (sweep < 1e-9) {
|
|
125
|
-
edges.push(kernel.makeLineEdge({ x: seg.x1, y: seg.y1, z: 0 }, { x: seg.x2, y: seg.y2, z: 0 }));
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
edges.push(...arcToHandles(kernel, seg));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return kernel.makeWire(edges);
|
|
133
|
-
}
|
|
134
|
-
/** 环 → 2D 采样多边形(直线取端点、弧等角采样),用于环间包含性判定。 */
|
|
135
|
-
function loopToPolygon(loop) {
|
|
136
|
-
const pts = [];
|
|
137
|
-
for (const seg of loop.segments) {
|
|
138
|
-
if (seg.kind === 'line') {
|
|
139
|
-
pts.push({ x: seg.x1, y: seg.y1 });
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
let sweep = seg.endAngle - seg.startAngle;
|
|
143
|
-
while (sweep <= 0)
|
|
144
|
-
sweep += 2 * Math.PI;
|
|
145
|
-
while (sweep > 2 * Math.PI)
|
|
146
|
-
sweep -= 2 * Math.PI;
|
|
147
|
-
const n = Math.max(8, Math.ceil((sweep / (2 * Math.PI)) * 32));
|
|
148
|
-
for (let k = 0; k < n; k++) {
|
|
149
|
-
const a = seg.startAngle + (sweep * k) / n;
|
|
150
|
-
pts.push({ x: seg.cx + seg.radius * Math.cos(a), y: seg.cy + seg.radius * Math.sin(a) });
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return pts;
|
|
154
|
-
}
|
|
155
|
-
/** 射线法:点是否在多边形内(采样多边形足够密,顶点/边界角落情形由环间不共边保证不出现)。 */
|
|
156
|
-
function polygonContains(poly, px, py) {
|
|
157
|
-
let inside = false;
|
|
158
|
-
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
159
|
-
const xi = poly[i].x;
|
|
160
|
-
const yi = poly[i].y;
|
|
161
|
-
const xj = poly[j].x;
|
|
162
|
-
const yj = poly[j].y;
|
|
163
|
-
if (yi > py !== yj > py && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi)
|
|
164
|
-
inside = !inside;
|
|
165
|
-
}
|
|
166
|
-
return inside;
|
|
167
|
-
}
|
|
168
46
|
/**
|
|
169
47
|
* BREP 路径:2D 轮廓集合 → planar face 集合。
|
|
170
48
|
*
|
|
@@ -180,68 +58,44 @@ function polygonContains(poly, px, py) {
|
|
|
180
58
|
export function buildProfileShape(params) {
|
|
181
59
|
const kernel = getBrepApi();
|
|
182
60
|
const as = params.as ?? 'face';
|
|
183
|
-
const loops = params.contours.map((loop) => ({
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
continue;
|
|
194
|
-
if (parent[i] === -1 || loops[j].area < loops[parent[i]].area)
|
|
195
|
-
parent[i] = j;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
const depthOf = (i) => {
|
|
199
|
-
let d = 0;
|
|
200
|
-
let cur = i;
|
|
201
|
-
while (parent[cur] !== -1) {
|
|
202
|
-
d++;
|
|
203
|
-
cur = parent[cur];
|
|
204
|
-
}
|
|
205
|
-
return d;
|
|
206
|
-
};
|
|
207
|
-
const islands = [];
|
|
208
|
-
const holesOf = new Map();
|
|
209
|
-
for (let i = 0; i < loops.length; i++) {
|
|
210
|
-
if (depthOf(i) % 2 === 0)
|
|
211
|
-
islands.push(i);
|
|
212
|
-
else {
|
|
213
|
-
const p = parent[i];
|
|
214
|
-
const arr = holesOf.get(p);
|
|
215
|
-
if (arr)
|
|
216
|
-
arr.push(i);
|
|
217
|
-
else
|
|
218
|
-
holesOf.set(p, [i]);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
// 'wire' 形态:只交最大岛的外环 wire(1D 曲线),不构面 —— 供扫掠族作 spine。
|
|
61
|
+
const loops = params.contours.map((loop) => ({
|
|
62
|
+
loop,
|
|
63
|
+
curves: loop.segments.map((s) => profileSegToCurve(s)),
|
|
64
|
+
}));
|
|
65
|
+
// F2(2026-09-27):环分类统一到纯 2D 管线 `organiseBlueprints`
|
|
66
|
+
// (geometry2d/,brepjs 移植)。「嵌套=孔,不相交=独立岛」语义与既有
|
|
67
|
+
// `profile-multi-island.test.ts` 断言一致;多岛 → 多个顶层,岛+孔 →
|
|
68
|
+
// CompoundBlueprint([0]=外环,[1..]=孔)。
|
|
69
|
+
const organised = organiseBlueprints(loops.map((l) => new Blueprint(l.curves)));
|
|
70
|
+
// 'wire' 形态:只挑面积最大的岛的外环 wire(1D 曲线),供扫掠族作 spine。
|
|
222
71
|
if (as === 'wire') {
|
|
223
|
-
let
|
|
224
|
-
for (const
|
|
225
|
-
if (
|
|
226
|
-
|
|
72
|
+
let best = organised.blueprints[0];
|
|
73
|
+
for (const entry of organised.blueprints) {
|
|
74
|
+
if (blueprintArea(entry) > blueprintArea(best))
|
|
75
|
+
best = entry;
|
|
227
76
|
}
|
|
228
|
-
const
|
|
77
|
+
const outerBp = best instanceof CompoundBlueprint ? best.blueprints[0] : best;
|
|
78
|
+
const wire = blueprintToWire(kernel, outerBp);
|
|
229
79
|
return fromBrepCurve(solidToShape(kernel, wire), { solid: wire });
|
|
230
80
|
}
|
|
231
|
-
//
|
|
81
|
+
// 每岛一面(岛的外环 + 直接子环为孔)
|
|
232
82
|
const faces = [];
|
|
233
|
-
for (const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
83
|
+
for (const entry of organised.blueprints) {
|
|
84
|
+
if (entry instanceof CompoundBlueprint) {
|
|
85
|
+
const outerWire = blueprintToWire(kernel, entry.blueprints[0]);
|
|
86
|
+
const face = kernel.makeFace(outerWire);
|
|
87
|
+
const holes = entry.blueprints.slice(1).map((h) => blueprintToWire(kernel, h));
|
|
88
|
+
if (holes.length > 0) {
|
|
89
|
+
const faced = kernel.addHolesInFace(face, holes);
|
|
90
|
+
kernel.release(face);
|
|
91
|
+
faces.push(faced);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
faces.push(face);
|
|
95
|
+
}
|
|
242
96
|
}
|
|
243
97
|
else {
|
|
244
|
-
faces.push(
|
|
98
|
+
faces.push(kernel.makeFace(blueprintToWire(kernel, entry)));
|
|
245
99
|
}
|
|
246
100
|
}
|
|
247
101
|
if (faces.length === 1) {
|
|
@@ -251,6 +105,83 @@ export function buildProfileShape(params) {
|
|
|
251
105
|
const compound = kernel.makeCompound(faces);
|
|
252
106
|
return fromBrep(solidToShape(kernel, compound), { solid: compound });
|
|
253
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Convert a pure-2D Blueprint to a 3D wire (z=0) for planar face construction.
|
|
110
|
+
* @param kernel - the BREP engine handle-bridge.
|
|
111
|
+
* @param bp - the blueprint whose ordered curves become wire edges.
|
|
112
|
+
* @returns the wire handle.
|
|
113
|
+
*/
|
|
114
|
+
function blueprintToWire(kernel, bp) {
|
|
115
|
+
const edges = [];
|
|
116
|
+
const pt3 = ([x, y]) => ({ x, y, z: 0 });
|
|
117
|
+
for (const c of bp.curves) {
|
|
118
|
+
switch (c.kind2d) {
|
|
119
|
+
case 'line':
|
|
120
|
+
edges.push(kernel.makeLineEdge({ x: c.ox, y: c.oy, z: 0 }, { x: c.ox + c.dx * c.len, y: c.oy + c.dy * c.len, z: 0 }));
|
|
121
|
+
break;
|
|
122
|
+
case 'bezier':
|
|
123
|
+
edges.push(kernel.makeBezierEdge(c.poles.map(pt3)));
|
|
124
|
+
break;
|
|
125
|
+
case 'circle': {
|
|
126
|
+
// 完整圆:拆两段半圆 makeArcEdge(避免三点退化)。
|
|
127
|
+
edges.push(kernel.makeArcEdge(pt3(evaluateCurve2d(c, 0)), pt3(evaluateCurve2d(c, Math.PI / 2)), pt3(evaluateCurve2d(c, Math.PI))));
|
|
128
|
+
edges.push(kernel.makeArcEdge(pt3(evaluateCurve2d(c, Math.PI)), pt3(evaluateCurve2d(c, (3 * Math.PI) / 2)), pt3(evaluateCurve2d(c, 2 * Math.PI))));
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case 'trimmed': {
|
|
132
|
+
if (c.basis.kind2d === 'circle') {
|
|
133
|
+
// 圆弧:以 start/mid/end 三点构真实 makeArcEdge(保留光滑柱面,勿拆成折线段)
|
|
134
|
+
edges.push(kernel.makeArcEdge(pt3(evaluateCurve2d(c, 0)), pt3(evaluateCurve2d(c, 0.5)), pt3(evaluateCurve2d(c, 1))));
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
const b = curveBounds(c);
|
|
138
|
+
const pts = [];
|
|
139
|
+
for (let k = 0; k <= 32; k++) {
|
|
140
|
+
const t = b.first + ((b.last - b.first) * k) / 32;
|
|
141
|
+
pts.push(pt3(evaluateCurve2d(c, t)));
|
|
142
|
+
}
|
|
143
|
+
for (let k = 0; k < pts.length - 1; k++)
|
|
144
|
+
edges.push(kernel.makeLineEdge(pts[k], pts[k + 1]));
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case 'ellipse':
|
|
148
|
+
case 'bspline': {
|
|
149
|
+
const b = curveBounds(c);
|
|
150
|
+
const n = 32;
|
|
151
|
+
const pts = [];
|
|
152
|
+
for (let k = 0; k <= n; k++) {
|
|
153
|
+
const t = b.first + ((b.last - b.first) * k) / n;
|
|
154
|
+
pts.push(pt3(evaluateCurve2d(c, t)));
|
|
155
|
+
}
|
|
156
|
+
for (let k = 0; k < pts.length - 1; k++)
|
|
157
|
+
edges.push(kernel.makeLineEdge(pts[k], pts[k + 1]));
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return kernel.makeWire(edges);
|
|
163
|
+
}
|
|
164
|
+
/** 顶层 Blueprint/CompoundBlueprint 的近似有符号面积(用于 wire 最大岛选择)。 */
|
|
165
|
+
function blueprintArea(entry) {
|
|
166
|
+
const bp = entry instanceof CompoundBlueprint ? entry.blueprints[0] : entry;
|
|
167
|
+
let a = 0;
|
|
168
|
+
for (const c of bp.curves) {
|
|
169
|
+
if (c.kind2d === 'line') {
|
|
170
|
+
a += (c.ox * (c.oy + c.dy * c.len) - (c.ox + c.dx * c.len) * c.oy) / 2;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const b = curveBounds(c);
|
|
174
|
+
const nSamples = 32;
|
|
175
|
+
for (let k = 0; k < nSamples; k++) {
|
|
176
|
+
const t0 = b.first + ((b.last - b.first) * k) / nSamples;
|
|
177
|
+
const t1 = b.first + ((b.last - b.first) * (k + 1)) / nSamples;
|
|
178
|
+
const [x0, y0] = evaluateCurve2d(c, t0);
|
|
179
|
+
const [x1, y1] = evaluateCurve2d(c, t1);
|
|
180
|
+
a += (x0 * y1 - x1 * y0) / 2;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return a;
|
|
184
|
+
}
|
|
254
185
|
/**
|
|
255
186
|
* 从 2D 轮廓构造平面(creator,无输入)。仅 BREP 可用。
|
|
256
187
|
* @group 创建
|
package/dist/api/profile.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/api/profile.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * stdlib profile — 从 2D 轮廓构面(creator 函数,无输入,brep-only)。\n *\n * 用途:faijs 此前没有「从 2D 轮廓构造 planar face」的能力,导致草图轮廓无法喂给\n * extrude/revolve。本 op 接收 2D 轮廓(线段 + 圆弧),在 z=0 平面用 OCCT 构面:\n * makeLineEdge/makeArcEdge → makeWire → makeFace(+ addHolesInFace)。\n *\n * 设计:\n * - 输入:{ contours: ProfileLoop[] },每个 loop = 有序 segments(line / arc,z=0)。\n * - 环语义:嵌套 = 孔,不相交 = 独立岛(多岛 → face 的 compound),见 buildProfileShape。\n * - 圆弧:用起点/中点/终点三点的 makeArcEdge;整圆(sweep≈2π)拆成两段弧。\n * - 输出:Shape(mesh 三角化 + 句柄登记)。brep-only(下游 extrude/revolve 亦 brep-only)。\n *\n * 坐标系:输入 2D 点即 (x, y, 0),法向 +Z;草图在 Body 局部系内的定位由调用方(fcstd 端口)\n * 在轮廓坐标里预先变换,或由下游特征的 Placement 承担。\n */\n\nimport type { Shape } from '../mesh/types'\nimport type { BrepEngineApi } from '../brep/engine/primitives'\nimport { getBrepApi } from '../brep/handle-bridge'\nimport { solidToShape } from '../brep/brep-ops'\nimport { fromBrep, fromBrepCurve } from '../shape'\nimport { defineOp } from '../sdk'\nimport type { Provenance } from '../topology/naming/lineage'\n\n// ── 参数形状(与 fcstd Contour 结构相同,避免引擎依赖端口层)──\n\n/** 直线段(2D,z=0;端点为绝对坐标)。 */\nexport interface ProfileLineSeg {\n kind: 'line'\n x1: number\n y1: number\n x2: number\n y2: number\n}\n\n/** 圆弧段(圆心 + 半径 + 起止角,弧度;z=0)。 */\nexport interface ProfileArcSeg {\n kind: 'arc'\n cx: number\n cy: number\n radius: number\n /** 起始角(弧度) */\n startAngle: number\n /** 终止角(弧度) */\n endAngle: number\n ccw: boolean\n x1: number\n y1: number\n x2: number\n y2: number\n}\n\n/** 一条有序 2D 轮廓段(直线或圆弧)。 */\nexport type ProfileSeg = ProfileLineSeg | ProfileArcSeg\n\n/** 单个轮廓环:有序段序列(首尾相接)。 */\nexport interface ProfileLoop {\n segments: ProfileSeg[]\n}\n\n/** `cad.profile` 参数:轮廓环集合。语义见 {@link buildProfileShape}(嵌套=孔,不相交=独立岛)。 */\nexport interface ProfileParams {\n contours: ProfileLoop[]\n /**\n * 产物形态:'face'(默认)构面;'wire' 只交出不带面的最大岛外环 wire(1D 曲线),\n * 供扫掠族(sweep/loft/…)直接作 spine。'wire' 形态丢弃孔环(扫掠脊柱为单闭合轮廓)。\n */\n as?: 'face' | 'wire'\n}\n\n// ── 参数自校验 ──\n\n/**\n * Validate profile parameters: `contours` must be a non-empty array of loops,\n * each with at least one segment.\n * @param params the raw profile operation parameters.\n */\nexport function assertProfileParams(params: Record<string, unknown>): void {\n const contours = params.contours\n if (!Array.isArray(contours) || contours.length === 0) {\n throw new Error('E_PROFILE_NO_CONTOURS: profile requires at least one contour')\n }\n for (const loop of contours) {\n if (!loop || typeof loop !== 'object' || !Array.isArray((loop as ProfileLoop).segments)) {\n throw new Error('E_PROFILE_BAD_LOOP: each contour must have a segments array')\n }\n if ((loop as ProfileLoop).segments.length === 0) {\n throw new Error('E_PROFILE_EMPTY_LOOP: a contour must have at least one segment')\n }\n }\n}\n\n/**\n * 单环带符号面积(对每段做有向面积积分,而非只用段起点 shoelace)。\n *\n * 直线段:1/2 (x1·y2 − x2·y1)。\n * 圆弧段:1/2 ∫(x dy − y dx) 的解析解 = 1/2 [r²·sweep + cx·r(sin a1 − sin a0) − cy·r(cos a1 − cos a0)]。\n * 整圆(单段弧、起终点重合)由此得到 πr²,不再是 0——否则「整圆 + 方孔」会把孔误判成外环。\n */\nfunction loopSignedArea(loop: ProfileLoop): number {\n let a = 0\n for (const seg of loop.segments) {\n if (seg.kind === 'line') {\n a += (seg.x1 * seg.y2 - seg.x2 * seg.y1) / 2\n continue\n }\n let sweep = seg.endAngle - seg.startAngle\n while (sweep <= 0) sweep += 2 * Math.PI\n while (sweep > 2 * Math.PI) sweep -= 2 * Math.PI\n const { cx, cy, radius: r, startAngle: a0 } = seg\n const a1 = a0 + sweep\n a += 0.5 * (r * r * sweep + cx * r * (Math.sin(a1) - Math.sin(a0)) - cy * r * (Math.cos(a1) - Math.cos(a0)))\n }\n return a\n}\n\n/** 圆弧 → 一条或两条 makeArcEdge(整圆拆分)。 */\nfunction arcToHandles(\n kernel: BrepEngineApi,\n seg: ProfileArcSeg,\n): ReturnType<BrepEngineApi['makeArcEdge']>[] {\n const { cx, cy, radius, startAngle, endAngle, ccw = true } = seg\n // GOTCHA(2026-09-25,A3 FCBL_tree_entourage):ccw 必须参与弧的几何方向。\n // makeArcEdge 按「起点-中点-终点」三点定弧,旧实现一律取 CCW 中点\n // ((start+end)/2 归一化到 (0,2π]),ccw:false 的弧被画成 CCW **长弧**(补角)。\n // 实测后果:长弧穿过旋转轴 → revolve 产物退化(bbox NaN)→ BRepMesh 挂死\n // (进程无声死亡);正确 CW 短弧下内核 revolve 仅 ~19ms。\n let sweep: number\n let midAngle: number\n if (ccw) {\n sweep = endAngle - startAngle\n while (sweep <= 0) sweep += 2 * Math.PI\n while (sweep > 2 * Math.PI) sweep -= 2 * Math.PI\n midAngle = startAngle + sweep / 2\n } else {\n // 顺时针:从 startAngle 递减到 endAngle,扫角取反向归一化\n sweep = startAngle - endAngle\n while (sweep <= 0) sweep += 2 * Math.PI\n while (sweep > 2 * Math.PI) sweep -= 2 * Math.PI\n midAngle = startAngle - sweep / 2\n }\n\n const makeOne = (a0: number, am: number, a1: number): ReturnType<BrepEngineApi['makeArcEdge']> => {\n const start = { x: cx + radius * Math.cos(a0), y: cy + radius * Math.sin(a0), z: 0 }\n const m = { x: cx + radius * Math.cos(am), y: cy + radius * Math.sin(am), z: 0 }\n const end = { x: cx + radius * Math.cos(a1), y: cy + radius * Math.sin(a1), z: 0 }\n return kernel.makeArcEdge(start, m, end)\n }\n\n // 整圆(sweep≈2π):拆成两段半圆,避免 makeArcEdge 起点≈终点退化\n if (sweep >= 2 * Math.PI - 1e-9) {\n const half = 2 * Math.PI / 2\n const dir = ccw ? 1 : -1\n return [\n makeOne(startAngle, startAngle + dir * half / 2, startAngle + dir * half),\n makeOne(startAngle + dir * half, startAngle + dir * (half + half / 2), startAngle + dir * 2 * Math.PI),\n ]\n }\n return [makeOne(startAngle, midAngle, endAngle)]\n}\n\n/** 单环 → wire handle(仅构面,不释放中间句柄,遵循现有惯例)。 */\nfunction loopToWire(kernel: BrepEngineApi, loop: ProfileLoop): ReturnType<BrepEngineApi['makeWire']> {\n const edges: ReturnType<BrepEngineApi['makeLineEdge']>[] = []\n for (const seg of loop.segments) {\n if (seg.kind === 'line') {\n edges.push(kernel.makeLineEdge({ x: seg.x1, y: seg.y1, z: 0 }, { x: seg.x2, y: seg.y2, z: 0 }))\n } else {\n // 极小扫角(退化弧)→ 退化为线段,避免构边失败\n let sweep = seg.endAngle - seg.startAngle\n while (sweep <= 0) sweep += 2 * Math.PI\n while (sweep > 2 * Math.PI) sweep -= 2 * Math.PI\n if (sweep < 1e-9) {\n edges.push(kernel.makeLineEdge({ x: seg.x1, y: seg.y1, z: 0 }, { x: seg.x2, y: seg.y2, z: 0 }))\n } else {\n edges.push(...arcToHandles(kernel, seg))\n }\n }\n }\n return kernel.makeWire(edges as ReturnType<BrepEngineApi['makeLineEdge']>[])\n}\n\n/** 环 → 2D 采样多边形(直线取端点、弧等角采样),用于环间包含性判定。 */\nfunction loopToPolygon(loop: ProfileLoop): Array<{ x: number; y: number }> {\n const pts: Array<{ x: number; y: number }> = []\n for (const seg of loop.segments) {\n if (seg.kind === 'line') {\n pts.push({ x: seg.x1, y: seg.y1 })\n continue\n }\n let sweep = seg.endAngle - seg.startAngle\n while (sweep <= 0) sweep += 2 * Math.PI\n while (sweep > 2 * Math.PI) sweep -= 2 * Math.PI\n const n = Math.max(8, Math.ceil((sweep / (2 * Math.PI)) * 32))\n for (let k = 0; k < n; k++) {\n const a = seg.startAngle + (sweep * k) / n\n pts.push({ x: seg.cx + seg.radius * Math.cos(a), y: seg.cy + seg.radius * Math.sin(a) })\n }\n }\n return pts\n}\n\n/** 射线法:点是否在多边形内(采样多边形足够密,顶点/边界角落情形由环间不共边保证不出现)。 */\nfunction polygonContains(poly: Array<{ x: number; y: number }>, px: number, py: number): boolean {\n let inside = false\n for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {\n const xi = poly[i]!.x\n const yi = poly[i]!.y\n const xj = poly[j]!.x\n const yj = poly[j]!.y\n if (yi > py !== yj > py && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) inside = !inside\n }\n return inside\n}\n\n/**\n * BREP 路径:2D 轮廓集合 → planar face 集合。\n *\n * GOTCHA(2026-09-26,Beds part16/39):环语义是「**嵌套 = 孔,不相交 = 独立岛**」\n * (与 FreeCAD 草图一致),不是「面积最大者当外环、其余全当孔」。旧实现把双岛轮廓的\n * 第二个岛判成「洞外面」→ `addHolesInFace` 收到与外环不相交的 wire → 构面退化\n * (7 面、volume 0)。多岛产物 = compound(每岛一面、各带自己的孔),下游\n * `cad.extrude` 对 compound 输入逐面挤出(kernel MakePrism 收复合形状)。\n *\n * @param params - 轮廓环集合与产物形态。\n * @returns Shape(单岛为 face;多岛为 face 的 compound;`as:'wire'` 为 1D 曲线)。\n */\nexport function buildProfileShape(params: ProfileParams): Shape {\n const kernel = getBrepApi()\n const as = params.as ?? 'face'\n\n const loops = params.contours.map((loop) => ({ loop, area: Math.abs(loopSignedArea(loop)) }))\n\n // 包含分类:每个环的直接父环 = 面积最小的严格包含它的环;深度偶数 = 岛,奇数 = 父环的孔。\n const polys = loops.map((l) => loopToPolygon(l.loop))\n const parent = new Array<number>(loops.length).fill(-1)\n for (let i = 0; i < loops.length; i++) {\n const probe = polys[i]![0]!\n for (let j = 0; j < loops.length; j++) {\n if (i === j || loops[j]!.area <= loops[i]!.area) continue\n if (!polygonContains(polys[j]!, probe.x, probe.y)) continue\n if (parent[i] === -1 || loops[j]!.area < loops[parent[i]]!.area) parent[i] = j\n }\n }\n const depthOf = (i: number): number => {\n let d = 0\n let cur = i\n while (parent[cur] !== -1) {\n d++\n cur = parent[cur]!\n }\n return d\n }\n\n const islands: number[] = []\n const holesOf = new Map<number, number[]>()\n for (let i = 0; i < loops.length; i++) {\n if (depthOf(i) % 2 === 0) islands.push(i)\n else {\n const p = parent[i]!\n const arr = holesOf.get(p)\n if (arr) arr.push(i)\n else holesOf.set(p, [i])\n }\n }\n\n // 'wire' 形态:只交最大岛的外环 wire(1D 曲线),不构面 —— 供扫掠族作 spine。\n if (as === 'wire') {\n let outer = islands[0]!\n for (const isl of islands) {\n if (loops[isl]!.area > loops[outer]!.area) outer = isl\n }\n const wire = loopToWire(kernel, loops[outer]!.loop)\n return fromBrepCurve(solidToShape(kernel, wire), { solid: wire })\n }\n\n // 每岛一面(岛环为外环,直接子环为孔)\n const faces: ReturnType<BrepEngineApi['makeFace']>[] = []\n for (const isl of islands) {\n const wire = loopToWire(kernel, loops[isl]!.loop)\n const face = kernel.makeFace(wire)\n const holes = holesOf.get(isl)\n if (holes && holes.length > 0) {\n const holeWires = holes.map((h) => loopToWire(kernel, loops[h]!.loop))\n const faced = kernel.addHolesInFace(face, holeWires)\n kernel.release(face)\n faces.push(faced)\n } else {\n faces.push(face)\n }\n }\n\n if (faces.length === 1) {\n const f = faces[0]!\n return fromBrep(solidToShape(kernel, f), { solid: f })\n }\n const compound = kernel.makeCompound(faces as never)\n return fromBrep(solidToShape(kernel, compound), { solid: compound })\n}\n\n/**\n * 从 2D 轮廓构造平面(creator,无输入)。仅 BREP 可用。\n * @group 创建\n * @inputs 0\n * @async false\n * @qual ok\n * @name profile\n * @returns Shape 平面几何(mesh 三角化 + BREP 句柄);`as:'wire'` 时返回 1D 曲线(kind:'curve')。\n * @param params.contours - 有序 2D 轮廓(线段/圆弧;外环 + 孔)。type:ProfileLoop[] required:true\n * @param params.as - 产物形态:'face'(默认)构面;'wire' 只交外环 wire(1D 曲线)。type:'face'|'wire' required:false\n * @example\n * const f = cad.profile({ contours: [{ segments: [{ kind:'line', x1:0,y1:0,x2:10,y2:0 }, ...] }] })\n * const w = cad.profile({ contours: [{ segments: [{ kind:'line', x1:0,y1:0,x2:10,y2:0 }, ...] }], as: 'wire' })\n */\nexport const profile = defineOp({\n capabilities: ['directEdit'],\n brep(params: Record<string, unknown>) {\n assertProfileParams(params)\n return buildProfileShape(params as unknown as ProfileParams)\n },\n naming: { kind: 'construct', newFaces: { via: 'explicit', vocab: [] } } as Provenance,\n})"]}
|
|
1
|
+
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/api/profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAEjC,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,iBAAiB,EAAuB,MAAM,qBAAqB,CAAA;AAgD5E,cAAc;AAEd;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAA+B;IACjE,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAA;IAChC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAA;IACjF,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAE,IAAoB,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxF,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAA;QAChF,CAAC;QACD,IAAK,IAAoB,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAA;QACnF,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAqB;IACrD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAA;IAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM,CAAA;IAE9B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC3C,IAAI;QACJ,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAmB,CAAC,CAAC;KACzE,CAAC,CAAC,CAAA;IAEH,oDAAoD;IACpD,8CAA8C;IAC9C,sDAAsD;IACtD,qCAAqC;IACrC,MAAM,SAAS,GAAG,kBAAkB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IAE/E,iDAAiD;IACjD,IAAI,EAAE,KAAK,MAAM,EAAE,CAAC;QAClB,IAAI,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAE,CAAA;QACnC,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC;YACzC,IAAI,aAAa,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC;gBAAE,IAAI,GAAG,KAAK,CAAA;QAC9D,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,YAAY,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAA;QAC9E,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAC7C,OAAO,aAAa,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACnE,CAAC;IAED,sBAAsB;IACtB,MAAM,KAAK,GAA4C,EAAE,CAAA;IACzD,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC;QACzC,IAAI,KAAK,YAAY,iBAAiB,EAAE,CAAC;YACvC,MAAM,SAAS,GAAG,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,CAAA;YAC/D,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;YACvC,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAA;YAC9E,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrB,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;gBAChD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;gBACpB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACnB,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAClB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;QAC7D,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,CAAA;QACnB,OAAO,QAAQ,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;IACxD,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,KAAc,CAAC,CAAA;IACpD,OAAO,QAAQ,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;AACtE,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,MAAqB,EAAE,EAAa;IAC3D,MAAM,KAAK,GAAgD,EAAE,CAAA;IAC7D,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAmB,EAAuC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IAC/F,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;QAC1B,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC;YACjB,KAAK,MAAM;gBACT,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;gBACrH,MAAK;YACP,KAAK,QAAQ;gBACX,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;gBACnD,MAAK;YACP,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,iCAAiC;gBACjC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;gBAClI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;gBAClJ,MAAK;YACP,CAAC;YACD,KAAK,SAAS,CAAC,CAAC,CAAC;gBACf,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;oBAChC,sDAAsD;oBACtD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;oBACpH,MAAK;gBACP,CAAC;gBACD,MAAM,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;gBACxB,MAAM,GAAG,GAA+C,EAAE,CAAA;gBAC1D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC7B,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAA;oBACjD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;gBACtC,CAAC;gBACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE;oBAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAE,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC,CAAA;gBAC9F,MAAK;YACP,CAAC;YACD,KAAK,SAAS,CAAC;YACf,KAAK,SAAS,CAAC,CAAC,CAAC;gBACf,MAAM,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;gBACxB,MAAM,CAAC,GAAG,EAAE,CAAA;gBACZ,MAAM,GAAG,GAA+C,EAAE,CAAA;gBAC1D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC5B,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;oBAChD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;gBACtC,CAAC;gBACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE;oBAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAE,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC,CAAA;gBAC9F,MAAK;YACP,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC/B,CAAC;AAED,8DAA8D;AAC9D,SAAS,aAAa,CAAC,KAAoC;IACzD,MAAM,EAAE,GAAG,KAAK,YAAY,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,KAAK,CAAA;IAC5E,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;QAC1B,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YACxB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;YACtE,SAAQ;QACV,CAAC;QACD,MAAM,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;QACxB,MAAM,QAAQ,GAAG,EAAE,CAAA;QACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;YAClC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAA;YACxD,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAA;YAC9D,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;YACvC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;YACvC,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAA;QAC9B,CAAC;IACH,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,QAAQ,CAAC;IAC9B,YAAY,EAAE,CAAC,YAAY,CAAC;IAC5B,IAAI,CAAC,MAA+B;QAClC,mBAAmB,CAAC,MAAM,CAAC,CAAA;QAC3B,OAAO,iBAAiB,CAAC,MAAkC,CAAC,CAAA;IAC9D,CAAC;IACD,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE,EAAgB;CACtF,CAAC,CAAA","sourcesContent":["/**\n * stdlib profile — 从 2D 轮廓构面(creator 函数,无输入,brep-only)。\n *\n * 用途:faijs 此前没有「从 2D 轮廓构造 planar face」的能力,导致草图轮廓无法喂给\n * extrude/revolve。本 op 接收 2D 轮廓(线段 + 圆弧),在 z=0 平面用 OCCT 构面:\n * makeLineEdge/makeArcEdge → makeWire → makeFace(+ addHolesInFace)。\n *\n * 设计:\n * - 输入:{ contours: ProfileLoop[] },每个 loop = 有序 segments(line / arc,z=0)。\n * - 环语义:嵌套 = 孔,不相交 = 独立岛(多岛 → face 的 compound),见 buildProfileShape。\n * - 圆弧:用起点/中点/终点三点的 makeArcEdge;整圆(sweep≈2π)拆成两段弧。\n * - 输出:Shape(mesh 三角化 + 句柄登记)。brep-only(下游 extrude/revolve 亦 brep-only)。\n *\n * 坐标系:输入 2D 点即 (x, y, 0),法向 +Z;草图在 Body 局部系内的定位由调用方(fcstd 端口)\n * 在轮廓坐标里预先变换,或由下游特征的 Placement 承担。\n */\n\nimport type { Shape } from '../mesh/types'\nimport type { BrepEngineApi } from '../brep/engine/primitives'\nimport { getBrepApi } from '../brep/handle-bridge'\nimport { solidToShape } from '../brep/brep-ops'\nimport { fromBrep, fromBrepCurve } from '../shape'\nimport { defineOp } from '../sdk'\nimport type { Provenance } from '../topology/naming/lineage'\nimport { Blueprint } from '../geometry2d/blueprint'\nimport { CompoundBlueprint } from '../geometry2d/compound-blueprint'\nimport { organiseBlueprints } from '../geometry2d/organise'\nimport { curveBounds, evaluateCurve2d } from '../geometry2d/curve2d'\nimport { profileSegToCurve, type ProfileSegLike } from '../geometry2d/adapt'\n\n// ── 参数形状(与 fcstd Contour 结构相同,避免引擎依赖端口层)──\n\n/** 直线段(2D,z=0;端点为绝对坐标)。 */\nexport interface ProfileLineSeg {\n kind: 'line'\n x1: number\n y1: number\n x2: number\n y2: number\n}\n\n/** 圆弧段(圆心 + 半径 + 起止角,弧度;z=0)。 */\nexport interface ProfileArcSeg {\n kind: 'arc'\n cx: number\n cy: number\n radius: number\n /** 起始角(弧度) */\n startAngle: number\n /** 终止角(弧度) */\n endAngle: number\n ccw: boolean\n x1: number\n y1: number\n x2: number\n y2: number\n}\n\n/** 一条有序 2D 轮廓段(直线或圆弧)。 */\nexport type ProfileSeg = ProfileLineSeg | ProfileArcSeg\n\n/** 单个轮廓环:有序段序列(首尾相接)。 */\nexport interface ProfileLoop {\n segments: ProfileSeg[]\n}\n\n/** `cad.profile` 参数:轮廓环集合。语义见 {@link buildProfileShape}(嵌套=孔,不相交=独立岛)。 */\nexport interface ProfileParams {\n contours: ProfileLoop[]\n /**\n * 产物形态:'face'(默认)构面;'wire' 只交出不带面的最大岛外环 wire(1D 曲线),\n * 供扫掠族(sweep/loft/…)直接作 spine。'wire' 形态丢弃孔环(扫掠脊柱为单闭合轮廓)。\n */\n as?: 'face' | 'wire'\n}\n\n// ── 参数自校验 ──\n\n/**\n * Validate profile parameters: `contours` must be a non-empty array of loops,\n * each with at least one segment.\n * @param params the raw profile operation parameters.\n */\nexport function assertProfileParams(params: Record<string, unknown>): void {\n const contours = params.contours\n if (!Array.isArray(contours) || contours.length === 0) {\n throw new Error('E_PROFILE_NO_CONTOURS: profile requires at least one contour')\n }\n for (const loop of contours) {\n if (!loop || typeof loop !== 'object' || !Array.isArray((loop as ProfileLoop).segments)) {\n throw new Error('E_PROFILE_BAD_LOOP: each contour must have a segments array')\n }\n if ((loop as ProfileLoop).segments.length === 0) {\n throw new Error('E_PROFILE_EMPTY_LOOP: a contour must have at least one segment')\n }\n }\n}\n\n/**\n * BREP 路径:2D 轮廓集合 → planar face 集合。\n *\n * GOTCHA(2026-09-26,Beds part16/39):环语义是「**嵌套 = 孔,不相交 = 独立岛**」\n * (与 FreeCAD 草图一致),不是「面积最大者当外环、其余全当孔」。旧实现把双岛轮廓的\n * 第二个岛判成「洞外面」→ `addHolesInFace` 收到与外环不相交的 wire → 构面退化\n * (7 面、volume 0)。多岛产物 = compound(每岛一面、各带自己的孔),下游\n * `cad.extrude` 对 compound 输入逐面挤出(kernel MakePrism 收复合形状)。\n *\n * @param params - 轮廓环集合与产物形态。\n * @returns Shape(单岛为 face;多岛为 face 的 compound;`as:'wire'` 为 1D 曲线)。\n */\nexport function buildProfileShape(params: ProfileParams): Shape {\n const kernel = getBrepApi()\n const as = params.as ?? 'face'\n\n const loops = params.contours.map((loop) => ({\n loop,\n curves: loop.segments.map((s) => profileSegToCurve(s as ProfileSegLike)),\n }))\n\n // F2(2026-09-27):环分类统一到纯 2D 管线 `organiseBlueprints`\n // (geometry2d/,brepjs 移植)。「嵌套=孔,不相交=独立岛」语义与既有\n // `profile-multi-island.test.ts` 断言一致;多岛 → 多个顶层,岛+孔 →\n // CompoundBlueprint([0]=外环,[1..]=孔)。\n const organised = organiseBlueprints(loops.map((l) => new Blueprint(l.curves)))\n\n // 'wire' 形态:只挑面积最大的岛的外环 wire(1D 曲线),供扫掠族作 spine。\n if (as === 'wire') {\n let best = organised.blueprints[0]!\n for (const entry of organised.blueprints) {\n if (blueprintArea(entry) > blueprintArea(best)) best = entry\n }\n const outerBp = best instanceof CompoundBlueprint ? best.blueprints[0]! : best\n const wire = blueprintToWire(kernel, outerBp)\n return fromBrepCurve(solidToShape(kernel, wire), { solid: wire })\n }\n\n // 每岛一面(岛的外环 + 直接子环为孔)\n const faces: ReturnType<BrepEngineApi['makeFace']>[] = []\n for (const entry of organised.blueprints) {\n if (entry instanceof CompoundBlueprint) {\n const outerWire = blueprintToWire(kernel, entry.blueprints[0]!)\n const face = kernel.makeFace(outerWire)\n const holes = entry.blueprints.slice(1).map((h) => blueprintToWire(kernel, h))\n if (holes.length > 0) {\n const faced = kernel.addHolesInFace(face, holes)\n kernel.release(face)\n faces.push(faced)\n } else {\n faces.push(face)\n }\n } else {\n faces.push(kernel.makeFace(blueprintToWire(kernel, entry)))\n }\n }\n\n if (faces.length === 1) {\n const f = faces[0]!\n return fromBrep(solidToShape(kernel, f), { solid: f })\n }\n const compound = kernel.makeCompound(faces as never)\n return fromBrep(solidToShape(kernel, compound), { solid: compound })\n}\n\n/**\n * Convert a pure-2D Blueprint to a 3D wire (z=0) for planar face construction.\n * @param kernel - the BREP engine handle-bridge.\n * @param bp - the blueprint whose ordered curves become wire edges.\n * @returns the wire handle.\n */\nfunction blueprintToWire(kernel: BrepEngineApi, bp: Blueprint): ReturnType<BrepEngineApi['makeWire']> {\n const edges: ReturnType<BrepEngineApi['makeLineEdge']>[] = []\n const pt3 = ([x, y]: [number, number]): { x: number; y: number; z: number } => ({ x, y, z: 0 })\n for (const c of bp.curves) {\n switch (c.kind2d) {\n case 'line':\n edges.push(kernel.makeLineEdge({ x: c.ox, y: c.oy, z: 0 }, { x: c.ox + c.dx * c.len, y: c.oy + c.dy * c.len, z: 0 }))\n break\n case 'bezier':\n edges.push(kernel.makeBezierEdge(c.poles.map(pt3)))\n break\n case 'circle': {\n // 完整圆:拆两段半圆 makeArcEdge(避免三点退化)。\n edges.push(kernel.makeArcEdge(pt3(evaluateCurve2d(c, 0)), pt3(evaluateCurve2d(c, Math.PI / 2)), pt3(evaluateCurve2d(c, Math.PI))))\n edges.push(kernel.makeArcEdge(pt3(evaluateCurve2d(c, Math.PI)), pt3(evaluateCurve2d(c, (3 * Math.PI) / 2)), pt3(evaluateCurve2d(c, 2 * Math.PI))))\n break\n }\n case 'trimmed': {\n if (c.basis.kind2d === 'circle') {\n // 圆弧:以 start/mid/end 三点构真实 makeArcEdge(保留光滑柱面,勿拆成折线段)\n edges.push(kernel.makeArcEdge(pt3(evaluateCurve2d(c, 0)), pt3(evaluateCurve2d(c, 0.5)), pt3(evaluateCurve2d(c, 1))))\n break\n }\n const b = curveBounds(c)\n const pts: Array<{ x: number; y: number; z: number }> = []\n for (let k = 0; k <= 32; k++) {\n const t = b.first + ((b.last - b.first) * k) / 32\n pts.push(pt3(evaluateCurve2d(c, t)))\n }\n for (let k = 0; k < pts.length - 1; k++) edges.push(kernel.makeLineEdge(pts[k]!, pts[k + 1]!))\n break\n }\n case 'ellipse':\n case 'bspline': {\n const b = curveBounds(c)\n const n = 32\n const pts: Array<{ x: number; y: number; z: number }> = []\n for (let k = 0; k <= n; k++) {\n const t = b.first + ((b.last - b.first) * k) / n\n pts.push(pt3(evaluateCurve2d(c, t)))\n }\n for (let k = 0; k < pts.length - 1; k++) edges.push(kernel.makeLineEdge(pts[k]!, pts[k + 1]!))\n break\n }\n }\n }\n return kernel.makeWire(edges)\n}\n\n/** 顶层 Blueprint/CompoundBlueprint 的近似有符号面积(用于 wire 最大岛选择)。 */\nfunction blueprintArea(entry: Blueprint | CompoundBlueprint): number {\n const bp = entry instanceof CompoundBlueprint ? entry.blueprints[0]! : entry\n let a = 0\n for (const c of bp.curves) {\n if (c.kind2d === 'line') {\n a += (c.ox * (c.oy + c.dy * c.len) - (c.ox + c.dx * c.len) * c.oy) / 2\n continue\n }\n const b = curveBounds(c)\n const nSamples = 32\n for (let k = 0; k < nSamples; k++) {\n const t0 = b.first + ((b.last - b.first) * k) / nSamples\n const t1 = b.first + ((b.last - b.first) * (k + 1)) / nSamples\n const [x0, y0] = evaluateCurve2d(c, t0)\n const [x1, y1] = evaluateCurve2d(c, t1)\n a += (x0 * y1 - x1 * y0) / 2\n }\n }\n return a\n}\n\n/**\n * 从 2D 轮廓构造平面(creator,无输入)。仅 BREP 可用。\n * @group 创建\n * @inputs 0\n * @async false\n * @qual ok\n * @name profile\n * @returns Shape 平面几何(mesh 三角化 + BREP 句柄);`as:'wire'` 时返回 1D 曲线(kind:'curve')。\n * @param params.contours - 有序 2D 轮廓(线段/圆弧;外环 + 孔)。type:ProfileLoop[] required:true\n * @param params.as - 产物形态:'face'(默认)构面;'wire' 只交外环 wire(1D 曲线)。type:'face'|'wire' required:false\n * @example\n * const f = cad.profile({ contours: [{ segments: [{ kind:'line', x1:0,y1:0,x2:10,y2:0 }, ...] }] })\n * const w = cad.profile({ contours: [{ segments: [{ kind:'line', x1:0,y1:0,x2:10,y2:0 }, ...] }], as: 'wire' })\n */\nexport const profile = defineOp({\n capabilities: ['directEdit'],\n brep(params: Record<string, unknown>) {\n assertProfileParams(params)\n return buildProfileShape(params as unknown as ProfileParams)\n },\n naming: { kind: 'construct', newFaces: { via: 'explicit', vocab: [] } } as Provenance,\n})"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cad.punchHole`: cut a face-placed 2D profile out of a solid (E4 pocket/through
|
|
3
|
+
* hole with optional taper).
|
|
4
|
+
*
|
|
5
|
+
* The hole profile is a set of closed 2D contours placed on a target face of the
|
|
6
|
+
* solid (the same placement step as `cad.sketchOnFace`). Each placed contour is
|
|
7
|
+
* extruded from the face *into* the part along the face inward normal, and the
|
|
8
|
+
* resulting prism is the "punch" tool; a Boolean `cut` removes it from the host —
|
|
9
|
+
* a profile-based pocket (`height` given) or through-hole (`height: null`).
|
|
10
|
+
*
|
|
11
|
+
* v1 scope: the punch is a straight linear prism, or — when `draftAngle > 0` — a
|
|
12
|
+
* **tapered frustum** whose walls recede by `draftAngle` over the punch depth
|
|
13
|
+
* (built as an occt `loft` between a mouth wire in the host plane and an inset
|
|
14
|
+
* end wire at depth `depth·tan(draftAngle)`). The op is a composition over the
|
|
15
|
+
* E3 on-surface placement + `kernel.extrude`/occt `loft` + `kernel.cut` and
|
|
16
|
+
* declares `engines: ['occt']` (surface sampling + Boolean cut + draft loft need
|
|
17
|
+
* the BREP platform).
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import type { Shape } from '../mesh/types.js';
|
|
21
|
+
import type { BrepEngineApi } from '../brep/engine/primitives.js';
|
|
22
|
+
import type { BrepHandle } from '../brep/engine/types.js';
|
|
23
|
+
import { type FaceSelector } from './sketch-on-face.js';
|
|
24
|
+
import { type ProfileLoop } from './profile.js';
|
|
25
|
+
/** Parameters for `cad.punchHole`. */
|
|
26
|
+
export interface PunchHoleParams {
|
|
27
|
+
/** Ordered 2D closed contour(s) describing the hole profile (same shape as `cad.profile`). */
|
|
28
|
+
contours: ProfileLoop[];
|
|
29
|
+
/** The target solid on which the hole is punched. */
|
|
30
|
+
on: Shape;
|
|
31
|
+
/** The host face: a 1-based ordinal or a `cad.faceRef(on, n)` result. */
|
|
32
|
+
face: FaceSelector;
|
|
33
|
+
/** Removal depth along the face inward normal. `null`/absent = through hole. */
|
|
34
|
+
height?: number | null;
|
|
35
|
+
/** Taper on the hole walls (degrees; default 0 = straight wall). Built as a frustum via occt loft. */
|
|
36
|
+
draftAngle?: number;
|
|
37
|
+
/** UV mapping mode for contour placement (default `'original'`). */
|
|
38
|
+
scaleMode?: 'original' | 'bounds' | 'native';
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build and cut the punch solid for every placed profile on the host face and
|
|
42
|
+
* Boolean-cut it from `solid`, returning the holed solid handle.
|
|
43
|
+
* @param kernel - the BREP engine surface.
|
|
44
|
+
* @param solid - the host solid handle.
|
|
45
|
+
* @param params - the punch parameters.
|
|
46
|
+
* @returns the holed solid handle.
|
|
47
|
+
*/
|
|
48
|
+
export declare function punchSolidOf(kernel: BrepEngineApi, solid: BrepHandle, params: PunchHoleParams): BrepHandle;
|
|
49
|
+
/**
|
|
50
|
+
* Build a Shape: the host solid with the profile(s) punched out of `params.on`.
|
|
51
|
+
* @param kernel - the BREP engine surface.
|
|
52
|
+
* @param params - the punch parameters.
|
|
53
|
+
* @returns the holed Shape.
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildPunchHole(kernel: BrepEngineApi, params: PunchHoleParams): Shape;
|
|
56
|
+
/**
|
|
57
|
+
* `cad.punchHole`: cut a face-placed 2D profile out of a solid.
|
|
58
|
+
* @group 创建
|
|
59
|
+
* @inputs 1
|
|
60
|
+
* @async false
|
|
61
|
+
* @qual ok
|
|
62
|
+
* @name punchHole
|
|
63
|
+
* @returns Shape with the profile punched out of `on`.
|
|
64
|
+
* @param params.contours - ordered 2D closed contours (the hole profile).type:any[] required:true
|
|
65
|
+
* @param params.on - the target solid to punch.type:Shape required:true
|
|
66
|
+
* @param params.face - host face: 1-based ordinal or `cad.faceRef(on, n)`.type:any required:true
|
|
67
|
+
* @param params.height - blind depth along the face inward normal (null/absent = through).type:number|object required:false
|
|
68
|
+
* @param params.draftAngle - wall taper in degrees (default 0 = straight; built as an occt loft frustum).type:number required:false
|
|
69
|
+
* @param params.scaleMode - UV mapping: 'original' | 'bounds' | 'native'.type:string required:false
|
|
70
|
+
*/
|
|
71
|
+
export declare const punchHole: ((params: Record<string, unknown>) => Promise<Shape>) & {
|
|
72
|
+
__faijs__dualOp?: import("../define-op.js").DualOpMeta;
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=punch-hole.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"punch-hole.d.ts","sourceRoot":"","sources":["../../src/api/punch-hole.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAQtD,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAIvE,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAEjE,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC9B,8FAA8F;IAC9F,QAAQ,EAAE,WAAW,EAAE,CAAA;IACvB,qDAAqD;IACrD,EAAE,EAAE,KAAK,CAAA;IACT,yEAAyE;IACzE,IAAI,EAAE,YAAY,CAAA;IAClB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,sGAAsG;IACtG,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oEAAoE;IACpE,SAAS,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAA;CAC7C;AAuED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,GAAG,UAAU,CA+F1G;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,eAAe,GAAG,KAAK,CAOpF;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS;;CAQpB,CAAA"}
|