@faicad/faijs 0.18.3 → 0.22.2

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.
Files changed (241) hide show
  1. package/dist/api/api-namespace.d.ts.map +1 -1
  2. package/dist/api/api-namespace.js +4 -1
  3. package/dist/api/api-namespace.js.map +1 -1
  4. package/dist/api/brep-mirror/joinery-brep.d.ts +1 -1
  5. package/dist/api/brep-mirror/joinery-brep.d.ts.map +1 -1
  6. package/dist/api/brep-mirror/joinery-brep.js +22 -20
  7. package/dist/api/brep-mirror/joinery-brep.js.map +1 -1
  8. package/dist/api/generated/script-face-manifest.d.ts +4 -0
  9. package/dist/api/generated/script-face-manifest.d.ts.map +1 -1
  10. package/dist/api/generated/script-face-manifest.js.map +1 -1
  11. package/dist/api/index.d.ts +4 -0
  12. package/dist/api/index.d.ts.map +1 -1
  13. package/dist/api/index.js +3 -0
  14. package/dist/api/index.js.map +1 -1
  15. package/dist/api/knurl.d.ts.map +1 -1
  16. package/dist/api/knurl.js +1 -0
  17. package/dist/api/knurl.js.map +1 -1
  18. package/dist/api/primitives.d.ts +4 -3
  19. package/dist/api/primitives.d.ts.map +1 -1
  20. package/dist/api/primitives.js +9 -3
  21. package/dist/api/primitives.js.map +1 -1
  22. package/dist/api/profile.d.ts +34 -5
  23. package/dist/api/profile.d.ts.map +1 -1
  24. package/dist/api/profile.js +168 -186
  25. package/dist/api/profile.js.map +1 -1
  26. package/dist/api/punch-hole.d.ts +77 -0
  27. package/dist/api/punch-hole.d.ts.map +1 -0
  28. package/dist/api/punch-hole.js +207 -0
  29. package/dist/api/punch-hole.js.map +1 -0
  30. package/dist/api/sketch-on-face.d.ts +85 -0
  31. package/dist/api/sketch-on-face.d.ts.map +1 -0
  32. package/dist/api/sketch-on-face.js +136 -0
  33. package/dist/api/sketch-on-face.js.map +1 -0
  34. package/dist/api/sketch-on-plane.d.ts +87 -0
  35. package/dist/api/sketch-on-plane.d.ts.map +1 -0
  36. package/dist/api/sketch-on-plane.js +106 -0
  37. package/dist/api/sketch-on-plane.js.map +1 -0
  38. package/dist/api/surface/arg-spec.d.ts +8 -0
  39. package/dist/api/surface/arg-spec.d.ts.map +1 -1
  40. package/dist/api/surface/arg-spec.js.map +1 -1
  41. package/dist/api/transform.d.ts +5 -5
  42. package/dist/api/transform.d.ts.map +1 -1
  43. package/dist/api/transform.js +12 -10
  44. package/dist/api/transform.js.map +1 -1
  45. package/dist/api/xml-dom.d.ts +27 -0
  46. package/dist/api/xml-dom.d.ts.map +1 -0
  47. package/dist/api/xml-dom.js +45 -0
  48. package/dist/api/xml-dom.js.map +1 -0
  49. package/dist/boolean/csg-backend.d.ts +6 -6
  50. package/dist/boolean/csg-backend.d.ts.map +1 -1
  51. package/dist/boolean/csg-backend.js +9 -9
  52. package/dist/boolean/csg-backend.js.map +1 -1
  53. package/dist/boolean/csg-core.d.ts +10 -10
  54. package/dist/boolean/csg-core.d.ts.map +1 -1
  55. package/dist/boolean/csg-core.js +24 -24
  56. package/dist/boolean/csg-core.js.map +1 -1
  57. package/dist/boolean/extrude-helpers.d.ts +8 -8
  58. package/dist/boolean/extrude-helpers.d.ts.map +1 -1
  59. package/dist/boolean/extrude-helpers.js +13 -13
  60. package/dist/boolean/extrude-helpers.js.map +1 -1
  61. package/dist/brep/brep-ops.d.ts +24 -10
  62. package/dist/brep/brep-ops.d.ts.map +1 -1
  63. package/dist/brep/brep-ops.js +46 -18
  64. package/dist/brep/brep-ops.js.map +1 -1
  65. package/dist/brep/brep-topology.d.ts.map +1 -1
  66. package/dist/brep/brep-topology.js +3 -1
  67. package/dist/brep/brep-topology.js.map +1 -1
  68. package/dist/brep/export/export-model.d.ts +79 -0
  69. package/dist/brep/export/export-model.d.ts.map +1 -0
  70. package/dist/brep/export/export-model.js +335 -0
  71. package/dist/brep/export/export-model.js.map +1 -0
  72. package/dist/brep/primitives-brep.d.ts.map +1 -1
  73. package/dist/brep/primitives-brep.js +3 -1
  74. package/dist/brep/primitives-brep.js.map +1 -1
  75. package/dist/brepkit-kernel/brepkitKernel.d.ts.map +1 -1
  76. package/dist/brepkit-kernel/brepkitKernel.js +4 -2
  77. package/dist/brepkit-kernel/brepkitKernel.js.map +1 -1
  78. package/dist/browser-host/csg-worker-protocol.d.ts +3 -3
  79. package/dist/browser-host/csg-worker-protocol.d.ts.map +1 -1
  80. package/dist/browser-host/csg-worker-protocol.js.map +1 -1
  81. package/dist/browser-host/csg-worker.js +3 -3
  82. package/dist/browser-host/csg-worker.js.map +1 -1
  83. package/dist/browser-host/inline-csg-backend.d.ts +3 -3
  84. package/dist/browser-host/inline-csg-backend.d.ts.map +1 -1
  85. package/dist/browser-host/inline-csg-backend.js +3 -3
  86. package/dist/browser-host/inline-csg-backend.js.map +1 -1
  87. package/dist/browser-host/worker-csg-backend.d.ts +3 -3
  88. package/dist/browser-host/worker-csg-backend.d.ts.map +1 -1
  89. package/dist/browser-host/worker-csg-backend.js.map +1 -1
  90. package/dist/cad-runtime/direct-executor.d.ts +4 -0
  91. package/dist/cad-runtime/direct-executor.d.ts.map +1 -1
  92. package/dist/cad-runtime/direct-executor.js +17 -1
  93. package/dist/cad-runtime/direct-executor.js.map +1 -1
  94. package/dist/cad-runtime/ports.d.ts +3 -3
  95. package/dist/cad-runtime/ports.d.ts.map +1 -1
  96. package/dist/cad-runtime/ports.js.map +1 -1
  97. package/dist/cad-runtime/runtime.d.ts +24 -1
  98. package/dist/cad-runtime/runtime.d.ts.map +1 -1
  99. package/dist/cad-runtime/runtime.js +53 -3
  100. package/dist/cad-runtime/runtime.js.map +1 -1
  101. package/dist/define-op.d.ts +22 -0
  102. package/dist/define-op.d.ts.map +1 -1
  103. package/dist/define-op.js +15 -0
  104. package/dist/define-op.js.map +1 -1
  105. package/dist/env-agnostic.d.ts +7 -7
  106. package/dist/env-agnostic.d.ts.map +1 -1
  107. package/dist/env-agnostic.js +3 -3
  108. package/dist/env-agnostic.js.map +1 -1
  109. package/dist/geometry2d/adapt.d.ts +46 -0
  110. package/dist/geometry2d/adapt.d.ts.map +1 -0
  111. package/dist/geometry2d/adapt.js +41 -0
  112. package/dist/geometry2d/adapt.js.map +1 -0
  113. package/dist/geometry2d/bbox2d.d.ts +82 -0
  114. package/dist/geometry2d/bbox2d.d.ts.map +1 -0
  115. package/dist/geometry2d/bbox2d.js +123 -0
  116. package/dist/geometry2d/bbox2d.js.map +1 -0
  117. package/dist/geometry2d/blueprint-sketcher.d.ts +27 -0
  118. package/dist/geometry2d/blueprint-sketcher.d.ts.map +1 -0
  119. package/dist/geometry2d/blueprint-sketcher.js +29 -0
  120. package/dist/geometry2d/blueprint-sketcher.js.map +1 -0
  121. package/dist/geometry2d/blueprint.d.ts +102 -0
  122. package/dist/geometry2d/blueprint.d.ts.map +1 -0
  123. package/dist/geometry2d/blueprint.js +225 -0
  124. package/dist/geometry2d/blueprint.js.map +1 -0
  125. package/dist/geometry2d/blueprints.d.ts +58 -0
  126. package/dist/geometry2d/blueprints.d.ts.map +1 -0
  127. package/dist/geometry2d/blueprints.js +72 -0
  128. package/dist/geometry2d/blueprints.js.map +1 -0
  129. package/dist/geometry2d/bridge/lift-on-plane.d.ts +50 -0
  130. package/dist/geometry2d/bridge/lift-on-plane.d.ts.map +1 -0
  131. package/dist/geometry2d/bridge/lift-on-plane.js +78 -0
  132. package/dist/geometry2d/bridge/lift-on-plane.js.map +1 -0
  133. package/dist/geometry2d/bridge/on-surface.d.ts +98 -0
  134. package/dist/geometry2d/bridge/on-surface.d.ts.map +1 -0
  135. package/dist/geometry2d/bridge/on-surface.js +94 -0
  136. package/dist/geometry2d/bridge/on-surface.js.map +1 -0
  137. package/dist/geometry2d/bridge/plane.d.ts +69 -0
  138. package/dist/geometry2d/bridge/plane.d.ts.map +1 -0
  139. package/dist/geometry2d/bridge/plane.js +96 -0
  140. package/dist/geometry2d/bridge/plane.js.map +1 -0
  141. package/dist/geometry2d/canned-blueprints.d.ts +44 -0
  142. package/dist/geometry2d/canned-blueprints.d.ts.map +1 -0
  143. package/dist/geometry2d/canned-blueprints.js +94 -0
  144. package/dist/geometry2d/canned-blueprints.js.map +1 -0
  145. package/dist/geometry2d/compound-blueprint.d.ts +72 -0
  146. package/dist/geometry2d/compound-blueprint.d.ts.map +1 -0
  147. package/dist/geometry2d/compound-blueprint.js +95 -0
  148. package/dist/geometry2d/compound-blueprint.js.map +1 -0
  149. package/dist/geometry2d/curve2d.d.ts +298 -0
  150. package/dist/geometry2d/curve2d.d.ts.map +1 -0
  151. package/dist/geometry2d/curve2d.js +1110 -0
  152. package/dist/geometry2d/curve2d.js.map +1 -0
  153. package/dist/geometry2d/index.d.ts +22 -0
  154. package/dist/geometry2d/index.d.ts.map +1 -0
  155. package/dist/geometry2d/index.js +21 -0
  156. package/dist/geometry2d/index.js.map +1 -0
  157. package/dist/geometry2d/organise.d.ts +28 -0
  158. package/dist/geometry2d/organise.d.ts.map +1 -0
  159. package/dist/geometry2d/organise.js +108 -0
  160. package/dist/geometry2d/organise.js.map +1 -0
  161. package/dist/geometry2d/pen-sketcher.d.ts +365 -0
  162. package/dist/geometry2d/pen-sketcher.d.ts.map +1 -0
  163. package/dist/geometry2d/pen-sketcher.js +520 -0
  164. package/dist/geometry2d/pen-sketcher.js.map +1 -0
  165. package/dist/geometry2d/point.d.ts +44 -0
  166. package/dist/geometry2d/point.d.ts.map +1 -0
  167. package/dist/geometry2d/point.js +57 -0
  168. package/dist/geometry2d/point.js.map +1 -0
  169. package/dist/geometry2d/svg-ellipse.d.ts +74 -0
  170. package/dist/geometry2d/svg-ellipse.d.ts.map +1 -0
  171. package/dist/geometry2d/svg-ellipse.js +163 -0
  172. package/dist/geometry2d/svg-ellipse.js.map +1 -0
  173. package/dist/lang/code-to-args.d.ts +29 -3
  174. package/dist/lang/code-to-args.d.ts.map +1 -1
  175. package/dist/lang/code-to-args.js +41 -2
  176. package/dist/lang/code-to-args.js.map +1 -1
  177. package/dist/lang/codegen.d.ts +45 -1
  178. package/dist/lang/codegen.d.ts.map +1 -1
  179. package/dist/lang/codegen.js +135 -6
  180. package/dist/lang/codegen.js.map +1 -1
  181. package/dist/lang/host-arg.d.ts +6 -0
  182. package/dist/lang/host-arg.d.ts.map +1 -1
  183. package/dist/lang/host-arg.js.map +1 -1
  184. package/dist/lang/metadata-extractor.d.ts +25 -0
  185. package/dist/lang/metadata-extractor.d.ts.map +1 -1
  186. package/dist/lang/metadata-extractor.js +289 -25
  187. package/dist/lang/metadata-extractor.js.map +1 -1
  188. package/dist/lang/parse-error.d.ts +7 -1
  189. package/dist/lang/parse-error.d.ts.map +1 -1
  190. package/dist/lang/parse-error.js.map +1 -1
  191. package/dist/lang/security-scanner.d.ts +9 -1
  192. package/dist/lang/security-scanner.d.ts.map +1 -1
  193. package/dist/lang/security-scanner.js +38 -1
  194. package/dist/lang/security-scanner.js.map +1 -1
  195. package/dist/lang/symbol-table.generated.d.ts +3 -0
  196. package/dist/lang/symbol-table.generated.d.ts.map +1 -1
  197. package/dist/lang/symbol-table.generated.js +3 -0
  198. package/dist/lang/symbol-table.generated.js.map +1 -1
  199. package/dist/mesh/io.d.ts +62 -9
  200. package/dist/mesh/io.d.ts.map +1 -1
  201. package/dist/mesh/io.js +124 -17
  202. package/dist/mesh/io.js.map +1 -1
  203. package/dist/mesh/threemf-loader.d.ts +15 -0
  204. package/dist/mesh/threemf-loader.d.ts.map +1 -0
  205. package/dist/mesh/threemf-loader.js +163 -0
  206. package/dist/mesh/threemf-loader.js.map +1 -0
  207. package/dist/mesh/transform.d.ts +2 -2
  208. package/dist/mesh/transform.d.ts.map +1 -1
  209. package/dist/mesh/transform.js +3 -3
  210. package/dist/mesh/transform.js.map +1 -1
  211. package/dist/mesh/types.d.ts +2 -2
  212. package/dist/mesh/types.d.ts.map +1 -1
  213. package/dist/mesh/types.js.map +1 -1
  214. package/dist/node-host/cli.d.ts.map +1 -1
  215. package/dist/node-host/cli.js +53 -26
  216. package/dist/node-host/cli.js.map +1 -1
  217. package/dist/occt-kernel/occtKernel.d.ts.map +1 -1
  218. package/dist/occt-kernel/occtKernel.js +4 -2
  219. package/dist/occt-kernel/occtKernel.js.map +1 -1
  220. package/dist/occt-kernel/topologyExt.d.ts.map +1 -1
  221. package/dist/occt-kernel/topologyExt.js +4 -1
  222. package/dist/occt-kernel/topologyExt.js.map +1 -1
  223. package/dist/runtime-state.d.ts +18 -0
  224. package/dist/runtime-state.d.ts.map +1 -1
  225. package/dist/runtime-state.js +23 -0
  226. package/dist/runtime-state.js.map +1 -1
  227. package/dist/sdk.d.ts +1 -1
  228. package/dist/sdk.d.ts.map +1 -1
  229. package/dist/sdk.js +1 -1
  230. package/dist/sdk.js.map +1 -1
  231. package/dist/tolerance.d.ts +38 -0
  232. package/dist/tolerance.d.ts.map +1 -0
  233. package/dist/tolerance.js +40 -0
  234. package/dist/tolerance.js.map +1 -0
  235. package/dist/topology/naming/score.js +3 -3
  236. package/dist/topology/naming/score.js.map +1 -1
  237. package/dist/units.d.ts +248 -0
  238. package/dist/units.d.ts.map +1 -0
  239. package/dist/units.js +397 -0
  240. package/dist/units.js.map +1 -0
  241. package/package.json +19 -2
@@ -18,17 +18,74 @@ 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
- * Validate profile parameters: `contours` must be a non-empty array of loops,
24
- * each with at least one segment.
28
+ * True when a `contours` entry is an already-drawn contour rather than a
29
+ * `ProfileLoop` data record. Discriminated on `curves` vs `segments`, so a
30
+ * `ProfileLoop` can never be mistaken for one.
31
+ * @param v - the candidate value.
32
+ * @returns true when `v` is a `Blueprint`-shaped drawn contour.
33
+ */
34
+ function isDrawnContour(v) {
35
+ return !!v && typeof v === 'object' && !Array.isArray(v) && Array.isArray(v.curves);
36
+ }
37
+ /**
38
+ * Normalize a `contours` argument to `Blueprint[]`.
39
+ *
40
+ * Accepts the segment-loop data form, a single drawn contour, or an array of
41
+ * drawn contours. The two forms are never mixed — a mixed array is a caller bug
42
+ * and is rejected rather than half-interpreted.
43
+ *
44
+ * @param contours - the raw `contours` argument (`ProfileLoop[]` | `Blueprint` | `Blueprint[]`).
45
+ * @returns one `Blueprint` per contour, in input order.
46
+ */
47
+ export function toContourBlueprints(contours) {
48
+ if (isDrawnContour(contours))
49
+ return [contours];
50
+ if (!Array.isArray(contours)) {
51
+ throw new Error('E_PROFILE_NO_CONTOURS: profile requires at least one contour');
52
+ }
53
+ if (contours.length === 0) {
54
+ throw new Error('E_PROFILE_NO_CONTOURS: profile requires at least one contour');
55
+ }
56
+ if (isDrawnContour(contours[0])) {
57
+ for (const [i, c] of contours.entries()) {
58
+ if (!isDrawnContour(c)) {
59
+ throw new Error(`E_PROFILE_MIXED_CONTOURS: contour ${i} is a segment loop, not a drawn contour`);
60
+ }
61
+ }
62
+ return contours;
63
+ }
64
+ return contours.map((loop) => new Blueprint((loop?.segments ?? []).map((s) => profileSegToCurve(s))));
65
+ }
66
+ /**
67
+ * Validate profile parameters: `contours` must be a non-empty contour set —
68
+ * either segment loops (each with at least one segment) or drawn contours.
25
69
  * @param params the raw profile operation parameters.
26
70
  */
27
71
  export function assertProfileParams(params) {
28
72
  const contours = params.contours;
73
+ if (isDrawnContour(contours))
74
+ return;
29
75
  if (!Array.isArray(contours) || contours.length === 0) {
30
76
  throw new Error('E_PROFILE_NO_CONTOURS: profile requires at least one contour');
31
77
  }
78
+ // Discriminate the whole array at once so a mixed set names the real defect
79
+ // (E_PROFILE_MIXED_CONTOURS) instead of falling into the loop-shape branch and
80
+ // reporting a drawn contour as a malformed segment loop.
81
+ const drawn = contours.map((c) => isDrawnContour(c));
82
+ if (drawn.some(Boolean)) {
83
+ if (!drawn.every(Boolean)) {
84
+ const bad = drawn.indexOf(false);
85
+ throw new Error(`E_PROFILE_MIXED_CONTOURS: contour ${bad} is a segment loop while contour ${drawn.indexOf(true)} is a drawn contour — the two forms cannot be mixed`);
86
+ }
87
+ return;
88
+ }
32
89
  for (const loop of contours) {
33
90
  if (!loop || typeof loop !== 'object' || !Array.isArray(loop.segments)) {
34
91
  throw new Error('E_PROFILE_BAD_LOOP: each contour must have a segments array');
@@ -38,133 +95,6 @@ export function assertProfileParams(params) {
38
95
  }
39
96
  }
40
97
  }
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
98
  /**
169
99
  * BREP 路径:2D 轮廓集合 → planar face 集合。
170
100
  *
@@ -174,74 +104,49 @@ function polygonContains(poly, px, py) {
174
104
  * (7 面、volume 0)。多岛产物 = compound(每岛一面、各带自己的孔),下游
175
105
  * `cad.extrude` 对 compound 输入逐面挤出(kernel MakePrism 收复合形状)。
176
106
  *
177
- * @param params - 轮廓环集合与产物形态。
107
+ * @param params - 轮廓来源(段环数据或 `cad.draw` 产物)与产物形态。
178
108
  * @returns Shape(单岛为 face;多岛为 face 的 compound;`as:'wire'` 为 1D 曲线)。
179
109
  */
180
110
  export function buildProfileShape(params) {
181
111
  const kernel = getBrepApi();
182
112
  const as = params.as ?? 'face';
183
- const loops = params.contours.map((loop) => ({ loop, area: Math.abs(loopSignedArea(loop)) }));
184
- // 包含分类:每个环的直接父环 = 面积最小的严格包含它的环;深度偶数 = 岛,奇数 = 父环的孔。
185
- const polys = loops.map((l) => loopToPolygon(l.loop));
186
- const parent = new Array(loops.length).fill(-1);
187
- for (let i = 0; i < loops.length; i++) {
188
- const probe = polys[i][0];
189
- for (let j = 0; j < loops.length; j++) {
190
- if (i === j || loops[j].area <= loops[i].area)
191
- continue;
192
- if (!polygonContains(polys[j], probe.x, probe.y))
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。
113
+ // F2(2026-09-27):环分类统一到纯 2D 管线 `organiseBlueprints`
114
+ // (geometry2d/,brepjs 移植)。「嵌套=孔,不相交=独立岛」语义与既有
115
+ // `profile-multi-island.test.ts` 断言一致;多岛 → 多个顶层,岛+孔 →
116
+ // CompoundBlueprint([0]=外环,[1..]=孔)。
117
+ //
118
+ // 2026-09-28:段环数据与「已绘制轮廓」(`cad.draw` 产物)在此归一 —— 两条
119
+ // 来源只差一次 `toContourBlueprints`,下游分类/构面完全共享。
120
+ const organised = organiseBlueprints(toContourBlueprints(params.contours));
121
+ // 'wire' 形态:只挑面积最大的岛的外环 wire(1D 曲线),供扫掠族作 spine。
222
122
  if (as === 'wire') {
223
- let outer = islands[0];
224
- for (const isl of islands) {
225
- if (loops[isl].area > loops[outer].area)
226
- outer = isl;
123
+ let best = organised.blueprints[0];
124
+ for (const entry of organised.blueprints) {
125
+ if (blueprintArea(entry) > blueprintArea(best))
126
+ best = entry;
227
127
  }
228
- const wire = loopToWire(kernel, loops[outer].loop);
128
+ const outerBp = best instanceof CompoundBlueprint ? best.blueprints[0] : best;
129
+ const wire = blueprintToWire(kernel, outerBp);
229
130
  return fromBrepCurve(solidToShape(kernel, wire), { solid: wire });
230
131
  }
231
- // 每岛一面(岛环为外环,直接子环为孔)
132
+ // 每岛一面(岛的外环 + 直接子环为孔)
232
133
  const faces = [];
233
- for (const isl of islands) {
234
- const wire = loopToWire(kernel, loops[isl].loop);
235
- const face = kernel.makeFace(wire);
236
- const holes = holesOf.get(isl);
237
- if (holes && holes.length > 0) {
238
- const holeWires = holes.map((h) => loopToWire(kernel, loops[h].loop));
239
- const faced = kernel.addHolesInFace(face, holeWires);
240
- kernel.release(face);
241
- faces.push(faced);
134
+ for (const entry of organised.blueprints) {
135
+ if (entry instanceof CompoundBlueprint) {
136
+ const outerWire = blueprintToWire(kernel, entry.blueprints[0]);
137
+ const face = kernel.makeFace(outerWire);
138
+ const holes = entry.blueprints.slice(1).map((h) => blueprintToWire(kernel, h));
139
+ if (holes.length > 0) {
140
+ const faced = kernel.addHolesInFace(face, holes);
141
+ kernel.release(face);
142
+ faces.push(faced);
143
+ }
144
+ else {
145
+ faces.push(face);
146
+ }
242
147
  }
243
148
  else {
244
- faces.push(face);
149
+ faces.push(kernel.makeFace(blueprintToWire(kernel, entry)));
245
150
  }
246
151
  }
247
152
  if (faces.length === 1) {
@@ -251,6 +156,83 @@ export function buildProfileShape(params) {
251
156
  const compound = kernel.makeCompound(faces);
252
157
  return fromBrep(solidToShape(kernel, compound), { solid: compound });
253
158
  }
159
+ /**
160
+ * Convert a pure-2D Blueprint to a 3D wire (z=0) for planar face construction.
161
+ * @param kernel - the BREP engine handle-bridge.
162
+ * @param bp - the blueprint whose ordered curves become wire edges.
163
+ * @returns the wire handle.
164
+ */
165
+ function blueprintToWire(kernel, bp) {
166
+ const edges = [];
167
+ const pt3 = ([x, y]) => ({ x, y, z: 0 });
168
+ for (const c of bp.curves) {
169
+ switch (c.kind2d) {
170
+ case 'line':
171
+ 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 }));
172
+ break;
173
+ case 'bezier':
174
+ edges.push(kernel.makeBezierEdge(c.poles.map(pt3)));
175
+ break;
176
+ case 'circle': {
177
+ // 完整圆:拆两段半圆 makeArcEdge(避免三点退化)。
178
+ edges.push(kernel.makeArcEdge(pt3(evaluateCurve2d(c, 0)), pt3(evaluateCurve2d(c, Math.PI / 2)), pt3(evaluateCurve2d(c, Math.PI))));
179
+ edges.push(kernel.makeArcEdge(pt3(evaluateCurve2d(c, Math.PI)), pt3(evaluateCurve2d(c, (3 * Math.PI) / 2)), pt3(evaluateCurve2d(c, 2 * Math.PI))));
180
+ break;
181
+ }
182
+ case 'trimmed': {
183
+ if (c.basis.kind2d === 'circle') {
184
+ // 圆弧:以 start/mid/end 三点构真实 makeArcEdge(保留光滑柱面,勿拆成折线段)
185
+ edges.push(kernel.makeArcEdge(pt3(evaluateCurve2d(c, 0)), pt3(evaluateCurve2d(c, 0.5)), pt3(evaluateCurve2d(c, 1))));
186
+ break;
187
+ }
188
+ const b = curveBounds(c);
189
+ const pts = [];
190
+ for (let k = 0; k <= 32; k++) {
191
+ const t = b.first + ((b.last - b.first) * k) / 32;
192
+ pts.push(pt3(evaluateCurve2d(c, t)));
193
+ }
194
+ for (let k = 0; k < pts.length - 1; k++)
195
+ edges.push(kernel.makeLineEdge(pts[k], pts[k + 1]));
196
+ break;
197
+ }
198
+ case 'ellipse':
199
+ case 'bspline': {
200
+ const b = curveBounds(c);
201
+ const n = 32;
202
+ const pts = [];
203
+ for (let k = 0; k <= n; k++) {
204
+ const t = b.first + ((b.last - b.first) * k) / n;
205
+ pts.push(pt3(evaluateCurve2d(c, t)));
206
+ }
207
+ for (let k = 0; k < pts.length - 1; k++)
208
+ edges.push(kernel.makeLineEdge(pts[k], pts[k + 1]));
209
+ break;
210
+ }
211
+ }
212
+ }
213
+ return kernel.makeWire(edges);
214
+ }
215
+ /** 顶层 Blueprint/CompoundBlueprint 的近似有符号面积(用于 wire 最大岛选择)。 */
216
+ function blueprintArea(entry) {
217
+ const bp = entry instanceof CompoundBlueprint ? entry.blueprints[0] : entry;
218
+ let a = 0;
219
+ for (const c of bp.curves) {
220
+ if (c.kind2d === 'line') {
221
+ a += (c.ox * (c.oy + c.dy * c.len) - (c.ox + c.dx * c.len) * c.oy) / 2;
222
+ continue;
223
+ }
224
+ const b = curveBounds(c);
225
+ const nSamples = 32;
226
+ for (let k = 0; k < nSamples; k++) {
227
+ const t0 = b.first + ((b.last - b.first) * k) / nSamples;
228
+ const t1 = b.first + ((b.last - b.first) * (k + 1)) / nSamples;
229
+ const [x0, y0] = evaluateCurve2d(c, t0);
230
+ const [x1, y1] = evaluateCurve2d(c, t1);
231
+ a += (x0 * y1 - x1 * y0) / 2;
232
+ }
233
+ }
234
+ return a;
235
+ }
254
236
  /**
255
237
  * 从 2D 轮廓构造平面(creator,无输入)。仅 BREP 可用。
256
238
  * @group 创建
@@ -259,7 +241,7 @@ export function buildProfileShape(params) {
259
241
  * @qual ok
260
242
  * @name profile
261
243
  * @returns Shape 平面几何(mesh 三角化 + BREP 句柄);`as:'wire'` 时返回 1D 曲线(kind:'curve')。
262
- * @param params.contours - 有序 2D 轮廓(线段/圆弧;外环 + 孔)。type:ProfileLoop[] required:true
244
+ * @param params.contours - 有序 2D 轮廓(线段/圆弧;外环 + 孔),或已绘制轮廓(`cad.draw` 产物:单个 Blueprint 或其数组)。type:ProfileLoop[]|Blueprint|Blueprint[] required:true
263
245
  * @param params.as - 产物形态:'face'(默认)构面;'wire' 只交外环 wire(1D 曲线)。type:'face'|'wire' required:false
264
246
  * @example
265
247
  * const f = cad.profile({ contours: [{ segments: [{ kind:'line', x1:0,y1:0,x2:10,y2:0 }, ...] }] })
@@ -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":"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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'\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/**\n * A 2D contour that is already DRAWN — i.e. `cad.draw`'s product (a\n * `Blueprint`), or an array of them (one per contour).\n *\n * Why the 2D entries accept this: `cad.draw` returns a pure-data contour with no\n * OCCT handle, and without this path NO op can turn it into a Shape. Measured\n * 2026-09-28 — feeding a `cad.draw` product straight to `cad.extrude` fails with\n * `E_BREP_INPUT: argument carries no BREP handle`, and to `cad.sweep` with\n * `INVALID_SHAPE_ID`. The plan's \"draw → `sketchOnPlane` → `extrude`\" pipeline\n * therefore needs this bridge; `cad.profile` takes it too, because both entries\n * share {@link toContourBlueprints} and a split acceptance rule would be a trap.\n */\nexport type DrawnContours = Blueprint | Blueprint[]\n\n/** `cad.profile` 参数:轮廓环集合。语义见 {@link buildProfileShape}(嵌套=孔,不相交=独立岛)。 */\nexport interface ProfileParams {\n /**\n * 轮廓来源,两种等价形态:段环数据(`{segments: […]}`)或已绘制轮廓\n * (`cad.draw` 产物 / 其数组)。\n */\n contours: ProfileLoop[] | DrawnContours\n /**\n * 产物形态:'face'(默认)构面;'wire' 只交出不带面的最大岛外环 wire(1D 曲线),\n * 供扫掠族(sweep/loft/…)直接作 spine。'wire' 形态丢弃孔环(扫掠脊柱为单闭合轮廓)。\n */\n as?: 'face' | 'wire'\n}\n\n// ── 参数自校验 ──\n\n/**\n * True when a `contours` entry is an already-drawn contour rather than a\n * `ProfileLoop` data record. Discriminated on `curves` vs `segments`, so a\n * `ProfileLoop` can never be mistaken for one.\n * @param v - the candidate value.\n * @returns true when `v` is a `Blueprint`-shaped drawn contour.\n */\nfunction isDrawnContour(v: unknown): v is Blueprint {\n return !!v && typeof v === 'object' && !Array.isArray(v) && Array.isArray((v as Blueprint).curves)\n}\n\n/**\n * Normalize a `contours` argument to `Blueprint[]`.\n *\n * Accepts the segment-loop data form, a single drawn contour, or an array of\n * drawn contours. The two forms are never mixed — a mixed array is a caller bug\n * and is rejected rather than half-interpreted.\n *\n * @param contours - the raw `contours` argument (`ProfileLoop[]` | `Blueprint` | `Blueprint[]`).\n * @returns one `Blueprint` per contour, in input order.\n */\nexport function toContourBlueprints(contours: unknown): Blueprint[] {\n if (isDrawnContour(contours)) return [contours]\n if (!Array.isArray(contours)) {\n throw new Error('E_PROFILE_NO_CONTOURS: profile requires at least one contour')\n }\n if (contours.length === 0) {\n throw new Error('E_PROFILE_NO_CONTOURS: profile requires at least one contour')\n }\n if (isDrawnContour(contours[0])) {\n for (const [i, c] of contours.entries()) {\n if (!isDrawnContour(c)) {\n throw new Error(`E_PROFILE_MIXED_CONTOURS: contour ${i} is a segment loop, not a drawn contour`)\n }\n }\n return contours as Blueprint[]\n }\n return (contours as ProfileLoop[]).map(\n (loop) =>\n new Blueprint(\n (loop?.segments ?? []).map((s) => profileSegToCurve(s as ProfileSegLike)),\n ),\n )\n}\n\n/**\n * Validate profile parameters: `contours` must be a non-empty contour set —\n * either segment loops (each with at least one segment) or drawn contours.\n * @param params the raw profile operation parameters.\n */\nexport function assertProfileParams(params: Record<string, unknown>): void {\n const contours = params.contours\n if (isDrawnContour(contours)) return\n if (!Array.isArray(contours) || contours.length === 0) {\n throw new Error('E_PROFILE_NO_CONTOURS: profile requires at least one contour')\n }\n // Discriminate the whole array at once so a mixed set names the real defect\n // (E_PROFILE_MIXED_CONTOURS) instead of falling into the loop-shape branch and\n // reporting a drawn contour as a malformed segment loop.\n const drawn = contours.map((c) => isDrawnContour(c))\n if (drawn.some(Boolean)) {\n if (!drawn.every(Boolean)) {\n const bad = drawn.indexOf(false)\n throw new Error(\n `E_PROFILE_MIXED_CONTOURS: contour ${bad} is a segment loop while contour ${drawn.indexOf(true)} is a drawn contour — the two forms cannot be mixed`,\n )\n }\n return\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 - 轮廓来源(段环数据或 `cad.draw` 产物)与产物形态。\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 // F2(2026-09-27):环分类统一到纯 2D 管线 `organiseBlueprints`\n // (geometry2d/,brepjs 移植)。「嵌套=孔,不相交=独立岛」语义与既有\n // `profile-multi-island.test.ts` 断言一致;多岛 → 多个顶层,岛+孔 →\n // CompoundBlueprint([0]=外环,[1..]=孔)。\n //\n // 2026-09-28:段环数据与「已绘制轮廓」(`cad.draw` 产物)在此归一 —— 两条\n // 来源只差一次 `toContourBlueprints`,下游分类/构面完全共享。\n const organised = organiseBlueprints(toContourBlueprints(params.contours))\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 轮廓(线段/圆弧;外环 + 孔),或已绘制轮廓(`cad.draw` 产物:单个 Blueprint 或其数组)。type:ProfileLoop[]|Blueprint|Blueprint[] 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,77 @@
1
+ /**
2
+ * @platform occt — 本文件 import occt-kernel(getOcctKernel:draft-angle punch
3
+ * 的 frustum loft 走原生 occt `loft`,适配器层无此能力)。
4
+ *
5
+ * `cad.punchHole`: cut a face-placed 2D profile out of a solid (E4 pocket/through
6
+ * hole with optional taper).
7
+ *
8
+ * The hole profile is a set of closed 2D contours placed on a target face of the
9
+ * solid (the same placement step as `cad.sketchOnFace`). Each placed contour is
10
+ * extruded from the face *into* the part along the face inward normal, and the
11
+ * resulting prism is the "punch" tool; a Boolean `cut` removes it from the host —
12
+ * a profile-based pocket (`height` given) or through-hole (`height: null`).
13
+ *
14
+ * v1 scope: the punch is a straight linear prism, or — when `draftAngle > 0` — a
15
+ * **tapered frustum** whose walls recede by `draftAngle` over the punch depth
16
+ * (built as an occt `loft` between a mouth wire in the host plane and an inset
17
+ * end wire at depth `depth·tan(draftAngle)`). The op is a composition over the
18
+ * E3 on-surface placement + `kernel.extrude`/occt `loft` + `kernel.cut` and
19
+ * declares `engines: ['occt']` (surface sampling + Boolean cut + draft loft need
20
+ * the BREP platform).
21
+ * @module
22
+ */
23
+ import type { Shape } from '../mesh/types.js';
24
+ import type { BrepEngineApi } from '../brep/engine/primitives.js';
25
+ import type { BrepHandle } from '../brep/engine/types.js';
26
+ import { type FaceSelector } from './sketch-on-face.js';
27
+ import { type ProfileLoop } from './profile.js';
28
+ /** Parameters for `cad.punchHole`. */
29
+ export interface PunchHoleParams {
30
+ /** Ordered 2D closed contour(s) describing the hole profile (same shape as `cad.profile`). */
31
+ contours: ProfileLoop[];
32
+ /** The target solid on which the hole is punched. */
33
+ on: Shape;
34
+ /** The host face: a 1-based ordinal or a `cad.faceRef(on, n)` result. */
35
+ face: FaceSelector;
36
+ /** Removal depth along the face inward normal. `null`/absent = through hole. */
37
+ height?: number | null;
38
+ /** Taper on the hole walls (degrees; default 0 = straight wall). Built as a frustum via occt loft. */
39
+ draftAngle?: number;
40
+ /** UV mapping mode for contour placement (default `'original'`). */
41
+ scaleMode?: 'original' | 'bounds' | 'native';
42
+ }
43
+ /**
44
+ * Build and cut the punch solid for every placed profile on the host face and
45
+ * Boolean-cut it from `solid`, returning the holed solid handle.
46
+ * @param kernel - the BREP engine surface.
47
+ * @param solid - the host solid handle.
48
+ * @param params - the punch parameters.
49
+ * @returns the holed solid handle.
50
+ */
51
+ export declare function punchSolidOf(kernel: BrepEngineApi, solid: BrepHandle, params: PunchHoleParams): BrepHandle;
52
+ /**
53
+ * Build a Shape: the host solid with the profile(s) punched out of `params.on`.
54
+ * @param kernel - the BREP engine surface.
55
+ * @param params - the punch parameters.
56
+ * @returns the holed Shape.
57
+ */
58
+ export declare function buildPunchHole(kernel: BrepEngineApi, params: PunchHoleParams): Shape;
59
+ /**
60
+ * `cad.punchHole`: cut a face-placed 2D profile out of a solid.
61
+ * @group 创建
62
+ * @inputs 1
63
+ * @async false
64
+ * @qual ok
65
+ * @name punchHole
66
+ * @returns Shape with the profile punched out of `on`.
67
+ * @param params.contours - ordered 2D closed contours (the hole profile).type:any[] required:true
68
+ * @param params.on - the target solid to punch.type:Shape required:true
69
+ * @param params.face - host face: 1-based ordinal or `cad.faceRef(on, n)`.type:any required:true
70
+ * @param params.height - blind depth along the face inward normal (null/absent = through).type:number|object required:false
71
+ * @param params.draftAngle - wall taper in degrees (default 0 = straight; built as an occt loft frustum).type:number required:false
72
+ * @param params.scaleMode - UV mapping: 'original' | 'bounds' | 'native'.type:string required:false
73
+ */
74
+ export declare const punchHole: ((params: Record<string, unknown>) => Promise<Shape>) & {
75
+ __faijs__dualOp?: import("../define-op.js").DualOpMeta;
76
+ };
77
+ //# 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;;;;;;;;;;;;;;;;;;;;;GAqBG;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"}