@faicad/faijs-extra 0.16.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.
Files changed (99) hide show
  1. package/LICENSE +29 -0
  2. package/NOTICE +29 -0
  3. package/dist/browser.d.ts +12 -0
  4. package/dist/browser.d.ts.map +1 -0
  5. package/dist/browser.js +12 -0
  6. package/dist/browser.js.map +1 -0
  7. package/dist/editor-ops.d.ts +50 -0
  8. package/dist/editor-ops.d.ts.map +1 -0
  9. package/dist/editor-ops.js +77 -0
  10. package/dist/editor-ops.js.map +1 -0
  11. package/dist/extras/svg-extrude.d.ts +43 -0
  12. package/dist/extras/svg-extrude.d.ts.map +1 -0
  13. package/dist/extras/svg-extrude.js +78 -0
  14. package/dist/extras/svg-extrude.js.map +1 -0
  15. package/dist/extras/text-cjk.d.ts +36 -0
  16. package/dist/extras/text-cjk.d.ts.map +1 -0
  17. package/dist/extras/text-cjk.js +146 -0
  18. package/dist/extras/text-cjk.js.map +1 -0
  19. package/dist/extras/text-geometry.d.ts +47 -0
  20. package/dist/extras/text-geometry.d.ts.map +1 -0
  21. package/dist/extras/text-geometry.js +127 -0
  22. package/dist/extras/text-geometry.js.map +1 -0
  23. package/dist/index.d.ts +47 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +47 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/mesh/drill-hole/DrillHoleCore.d.ts +90 -0
  28. package/dist/mesh/drill-hole/DrillHoleCore.d.ts.map +1 -0
  29. package/dist/mesh/drill-hole/DrillHoleCore.js +287 -0
  30. package/dist/mesh/drill-hole/DrillHoleCore.js.map +1 -0
  31. package/dist/mesh/drill-hole/drill-types.d.ts +34 -0
  32. package/dist/mesh/drill-hole/drill-types.d.ts.map +1 -0
  33. package/dist/mesh/drill-hole/drill-types.js +7 -0
  34. package/dist/mesh/drill-hole/drill-types.js.map +1 -0
  35. package/dist/mesh/engrave-decoration.d.ts +13 -0
  36. package/dist/mesh/engrave-decoration.d.ts.map +1 -0
  37. package/dist/mesh/engrave-decoration.js +39 -0
  38. package/dist/mesh/engrave-decoration.js.map +1 -0
  39. package/dist/mesh/fai_drill.d.ts +21 -0
  40. package/dist/mesh/fai_drill.d.ts.map +1 -0
  41. package/dist/mesh/fai_drill.js +92 -0
  42. package/dist/mesh/fai_drill.js.map +1 -0
  43. package/dist/mesh/fai_extrude.d.ts +24 -0
  44. package/dist/mesh/fai_extrude.d.ts.map +1 -0
  45. package/dist/mesh/fai_extrude.js +42 -0
  46. package/dist/mesh/fai_extrude.js.map +1 -0
  47. package/dist/mesh/fai_split.d.ts +164 -0
  48. package/dist/mesh/fai_split.d.ts.map +1 -0
  49. package/dist/mesh/fai_split.js +269 -0
  50. package/dist/mesh/fai_split.js.map +1 -0
  51. package/dist/mesh/index.d.ts +57 -0
  52. package/dist/mesh/index.d.ts.map +1 -0
  53. package/dist/mesh/index.js +34 -0
  54. package/dist/mesh/index.js.map +1 -0
  55. package/dist/mesh/primitives.d.ts +17 -0
  56. package/dist/mesh/primitives.d.ts.map +1 -0
  57. package/dist/mesh/primitives.js +53 -0
  58. package/dist/mesh/primitives.js.map +1 -0
  59. package/dist/namespace.d.ts +82 -0
  60. package/dist/namespace.d.ts.map +1 -0
  61. package/dist/namespace.js +109 -0
  62. package/dist/namespace.js.map +1 -0
  63. package/dist/op-names.d.ts +36 -0
  64. package/dist/op-names.d.ts.map +1 -0
  65. package/dist/op-names.js +45 -0
  66. package/dist/op-names.js.map +1 -0
  67. package/dist/ops/compound.d.ts +110 -0
  68. package/dist/ops/compound.d.ts.map +1 -0
  69. package/dist/ops/compound.js +164 -0
  70. package/dist/ops/compound.js.map +1 -0
  71. package/dist/ops/copy.d.ts +40 -0
  72. package/dist/ops/copy.d.ts.map +1 -0
  73. package/dist/ops/copy.js +83 -0
  74. package/dist/ops/copy.js.map +1 -0
  75. package/dist/ops/fai_drill.d.ts +46 -0
  76. package/dist/ops/fai_drill.d.ts.map +1 -0
  77. package/dist/ops/fai_drill.js +243 -0
  78. package/dist/ops/fai_drill.js.map +1 -0
  79. package/dist/ops/fai_extrude.d.ts +36 -0
  80. package/dist/ops/fai_extrude.d.ts.map +1 -0
  81. package/dist/ops/fai_extrude.js +79 -0
  82. package/dist/ops/fai_extrude.js.map +1 -0
  83. package/dist/ops/fai_split.d.ts +46 -0
  84. package/dist/ops/fai_split.d.ts.map +1 -0
  85. package/dist/ops/fai_split.js +255 -0
  86. package/dist/ops/fai_split.js.map +1 -0
  87. package/dist/ops/load.d.ts +41 -0
  88. package/dist/ops/load.d.ts.map +1 -0
  89. package/dist/ops/load.js +84 -0
  90. package/dist/ops/load.js.map +1 -0
  91. package/dist/ops/svg-extrude.d.ts +32 -0
  92. package/dist/ops/svg-extrude.d.ts.map +1 -0
  93. package/dist/ops/svg-extrude.js +74 -0
  94. package/dist/ops/svg-extrude.js.map +1 -0
  95. package/dist/ops/text.d.ts +34 -0
  96. package/dist/ops/text.d.ts.map +1 -0
  97. package/dist/ops/text.js +101 -0
  98. package/dist/ops/text.js.map +1 -0
  99. package/package.json +60 -0
@@ -0,0 +1,269 @@
1
+ /**
2
+ * mesh 分割 API
3
+ *
4
+ * 提取来源:engine/boolean/csg.ts (computeSplit/computeDovetailSplit/...)
5
+ * 已是纯数据函数,直接包装。
6
+ *
7
+ * splitWithParams 是 UI 与重放共用的唯一分割入口:
8
+ * 参数编排(planeParams / planeBasis / 爆炸位移 / 世界坐标变换)
9
+ * 全部在此函数内部完成,调用方只需传入 cutMode + normal/offset/inPlaneAngleDeg + bbCenter
10
+ * 以及各 cutMode 专属参数。
11
+ */
12
+ import { computeSplit, computeDovetailSplit, computeDowelSplit, computeStraightTenonSplit, } from '@faicad/faijs/boolean/csg-backend';
13
+ import * as THREE from 'three';
14
+ // ── 派生量计算(从 split-store 下沉,UI 与重放共用同一份公式) ──
15
+ /**
16
+ * Derive the cutting-plane parameters from Euler angles (in degrees), the
17
+ * plane position and the bounding-box center. Behavior is identical to the
18
+ * former split-store.computePlaneParams.
19
+ * @param rotationX - rotation about X in degrees.
20
+ * @param rotationY - rotation about Y in degrees.
21
+ * @param rotationZ - rotation about Z in degrees.
22
+ * @param planePosition - offset of the cutting plane along the plane normal.
23
+ * @param bbCenter - world-space bounding-box center of the source mesh.
24
+ * @returns the plane normal, the plane's signed distance from the origin, and its center.
25
+ */
26
+ export function computePlaneParams(rotationX, rotationY, rotationZ, planePosition, bbCenter) {
27
+ const rx = (rotationX * Math.PI) / 180;
28
+ const ry = (rotationY * Math.PI) / 180;
29
+ const rz = (rotationZ * Math.PI) / 180;
30
+ const euler = new THREE.Euler(rx, ry, rz, 'XYZ');
31
+ const quat = new THREE.Quaternion().setFromEuler(euler);
32
+ const normalVec = new THREE.Vector3(0, 0, 1).applyQuaternion(quat);
33
+ const normal = [normalVec.x, normalVec.y, normalVec.z];
34
+ const planeCenter = [
35
+ bbCenter[0] + planePosition * normal[0],
36
+ bbCenter[1] + planePosition * normal[1],
37
+ bbCenter[2] + planePosition * normal[2],
38
+ ];
39
+ const originOffset = normal[0] * planeCenter[0] + normal[1] * planeCenter[1] + normal[2] * planeCenter[2];
40
+ return { normal, originOffset, planeCenter };
41
+ }
42
+ /**
43
+ * Derive the plane basis vectors from Euler angles (in degrees). Behavior is
44
+ * identical to the former split-store.computePlaneBasis.
45
+ * @param rotationX - rotation about X in degrees.
46
+ * @param rotationY - rotation about Y in degrees.
47
+ * @param rotationZ - rotation about Z in degrees.
48
+ * @returns the plane normal plus the in-plane width and depth directions.
49
+ */
50
+ export function computePlaneBasis(rotationX, rotationY, rotationZ) {
51
+ const rx = (rotationX * Math.PI) / 180;
52
+ const ry = (rotationY * Math.PI) / 180;
53
+ const rz = (rotationZ * Math.PI) / 180;
54
+ const euler = new THREE.Euler(rx, ry, rz, 'XYZ');
55
+ const quat = new THREE.Quaternion().setFromEuler(euler);
56
+ const normalVec = new THREE.Vector3(0, 0, 1).applyQuaternion(quat);
57
+ const widthVec = new THREE.Vector3(1, 0, 0).applyQuaternion(quat);
58
+ const depthVec = new THREE.Vector3(0, 1, 0).applyQuaternion(quat);
59
+ return {
60
+ normal: [normalVec.x, normalVec.y, normalVec.z],
61
+ widthDir: [widthVec.x, widthVec.y, widthVec.z],
62
+ depthDir: [depthVec.x, depthVec.y, depthVec.z],
63
+ };
64
+ }
65
+ // ── 从法线 + 面内旋转推导基向量(纯数学,无 THREE 依赖) ──
66
+ /**
67
+ * Derive widthDir/depthDir from the cutting-plane normal and the in-plane
68
+ * rotation angle. Matches computePlaneBasis(rx, ry, rz) with
69
+ * normal = eulerXYZToNormal(rx, ry, 0) and inPlaneAngleDeg = rz: the rx/ry
70
+ * Euler XYZ decomposition is recovered from the normal, then the rz rotation
71
+ * is applied.
72
+ *
73
+ * normal = [sin(ry), -sin(rx)*cos(ry), cos(rx)*cos(ry)]
74
+ * ry = asin(normal[0])
75
+ * rx = atan2(-normal[1], normal[2]) (when cos(ry) !== 0)
76
+ * @param normal - cutting-plane unit normal.
77
+ * @param inPlaneAngleDeg - in-plane rotation about the normal, in degrees.
78
+ * @returns the in-plane width and depth directions.
79
+ */
80
+ export function computeBasisFromNormal(normal, inPlaneAngleDeg) {
81
+ const sinRy = Math.max(-1, Math.min(1, normal[0]));
82
+ const ry = Math.asin(sinRy);
83
+ const cosRy = Math.cos(ry);
84
+ let rx;
85
+ if (Math.abs(cosRy) < 1e-10) {
86
+ // 万向锁:ry = ±90°,rx 与 rz 耦合,设 rx = 0
87
+ rx = 0;
88
+ }
89
+ else {
90
+ rx = Math.atan2(-normal[1], normal[2]);
91
+ }
92
+ const rz = (inPlaneAngleDeg * Math.PI) / 180;
93
+ // R = RX(rx) · RY(ry)(不含 rz 的部分)
94
+ // widthDir = R · (cos(rz), sin(rz), 0)
95
+ // depthDir = R · (-sin(rz), cos(rz), 0)
96
+ const cr = Math.cos(ry), sr = Math.sin(ry);
97
+ const cx = Math.cos(rx), sx = Math.sin(rx);
98
+ const cosRz = Math.cos(rz), sinRz = Math.sin(rz);
99
+ const widthDir = [
100
+ cr * cosRz,
101
+ sx * sr * cosRz + cx * sinRz,
102
+ -cx * sr * cosRz + sx * sinRz,
103
+ ];
104
+ const depthDir = [
105
+ -cr * sinRz,
106
+ -sx * sr * sinRz + cx * cosRz,
107
+ cx * sr * sinRz + sx * cosRz,
108
+ ];
109
+ return { widthDir, depthDir };
110
+ }
111
+ /**
112
+ * Unified split entry point: the single split function shared by the UI
113
+ * executor and script replay. All parameter orchestration (planeParams,
114
+ * planeBasis, bboxWidthOnWidthDir, explode offset) happens inside this
115
+ * function; callers pass only semantic parameters. Both interaction paths
116
+ * (mouse / code) converge here, so the geometry result is identical.
117
+ * @param input - the split mode-specific input (shape, plane, mode params).
118
+ * @returns the split result with front/back halves and derived plane data.
119
+ */
120
+ export async function splitWithParams(input) {
121
+ const { shape, cutMode, normal, offset, inPlaneAngleDeg, bbCenter, bboxSize } = input;
122
+ // 1. 从 normal + offset + bbCenter 派生切割平面参数
123
+ const planeCenter = [
124
+ bbCenter[0] + offset * normal[0],
125
+ bbCenter[1] + offset * normal[1],
126
+ bbCenter[2] + offset * normal[2],
127
+ ];
128
+ const originOffset = normal[0] * planeCenter[0] + normal[1] * planeCenter[1] + normal[2] * planeCenter[2];
129
+ // 2. 从 normal + inPlaneAngleDeg 派生基向量
130
+ const { widthDir } = computeBasisFromNormal(normal, inPlaneAngleDeg);
131
+ // 3. 按 cutMode 分发
132
+ let front;
133
+ let back;
134
+ if (cutMode === 'dovetail') {
135
+ const bboxWidthOnWidthDir = Math.abs(bboxSize[0] * widthDir[0]) +
136
+ Math.abs(bboxSize[1] * widthDir[1]) +
137
+ Math.abs(bboxSize[2] * widthDir[2]);
138
+ const groove = input.groove ?? {
139
+ depth: 0, depthTolerance: 0, width: 0, widthTolerance: 0, flapsAngle: 0,
140
+ };
141
+ const result = await computeDovetailSplit(shape, normal, originOffset, planeCenter, widthDir, bboxWidthOnWidthDir, groove);
142
+ front = result.front;
143
+ back = result.back;
144
+ const result2 = await finalizeExplode(front, back, normal, originOffset, planeCenter, widthDir, bboxSize, cutMode, input);
145
+ return result2;
146
+ }
147
+ else if (cutMode === 'dowel') {
148
+ const dowel = input.dowel ?? {
149
+ diameter: 0, diameterTolerance: 0, height: 0, heightTolerance: 0,
150
+ };
151
+ const result = await computeDowelSplit(shape, normal, originOffset, planeCenter, widthDir, dowel, input.selectedSections ?? null);
152
+ front = result.front;
153
+ back = result.back;
154
+ return finalizeExplode(front, back, normal, originOffset, planeCenter, widthDir, bboxSize, cutMode, input);
155
+ }
156
+ else if (cutMode === 'straight-tenon' || cutMode === 'tenon') {
157
+ const tenon = input.tenon ?? {
158
+ sideLength: 0, sideLengthTolerance: 0, height: 0, heightTolerance: 0,
159
+ };
160
+ const result = await computeStraightTenonSplit(shape, normal, originOffset, planeCenter, widthDir, tenon, input.selectedSections ?? null);
161
+ front = result.front;
162
+ back = result.back;
163
+ return finalizeExplode(front, back, normal, originOffset, planeCenter, widthDir, bboxSize, cutMode, input);
164
+ }
165
+ else {
166
+ // plane / straight / default
167
+ const result = await computeSplit(shape, normal, originOffset);
168
+ front = result.front;
169
+ back = result.back;
170
+ return finalizeExplode(front, back, normal, originOffset, planeCenter, widthDir, bboxSize, cutMode, input);
171
+ }
172
+ }
173
+ /** 计算爆炸位移并应用到 front/back */
174
+ async function finalizeExplode(front, back, normal, originOffset, planeCenter, widthDir, bboxSize, cutMode, input) {
175
+ const applyExplode = input.applyExplode ?? true;
176
+ // 爆炸位移:包围盒对角线的 2% + joinery 深度的一半
177
+ const bboxDiagonal = Math.sqrt(bboxSize[0] * bboxSize[0] + bboxSize[1] * bboxSize[1] + bboxSize[2] * bboxSize[2]);
178
+ const baseOffset = bboxDiagonal * 0.02;
179
+ let joineryOffset = 0;
180
+ if (cutMode === 'dovetail') {
181
+ joineryOffset = (input.groove?.depth ?? 0) / 2;
182
+ }
183
+ else if (cutMode === 'dowel') {
184
+ joineryOffset = (input.dowel?.height ?? 0) / 2;
185
+ }
186
+ else if (cutMode === 'straight-tenon' || cutMode === 'tenon') {
187
+ joineryOffset = (input.tenon?.height ?? 0) / 2;
188
+ }
189
+ const frontOffset = baseOffset + joineryOffset;
190
+ const backOffset = -(baseOffset + joineryOffset);
191
+ if (applyExplode) {
192
+ front = { positions: offsetPositions(front.positions, normal, frontOffset), indices: front.indices };
193
+ back = { positions: offsetPositions(back.positions, normal, backOffset), indices: back.indices };
194
+ }
195
+ return {
196
+ front,
197
+ back,
198
+ normal,
199
+ originOffset,
200
+ planeCenter,
201
+ widthDir,
202
+ frontExplodeOffset: applyExplode ? frontOffset : 0,
203
+ backExplodeOffset: applyExplode ? backOffset : 0,
204
+ };
205
+ }
206
+ /** 沿法线方向偏移所有顶点 */
207
+ function offsetPositions(positions, normal, offset) {
208
+ const out = new Float32Array(positions);
209
+ for (let i = 0; i < out.length; i += 3) {
210
+ out[i] += normal[0] * offset;
211
+ out[i + 1] += normal[1] * offset;
212
+ out[i + 2] += normal[2] * offset;
213
+ }
214
+ return out;
215
+ }
216
+ // ── 低级 API(保留向后兼容) ──
217
+ /**
218
+ * Plane split of a mesh shape.
219
+ * @param shape - the mesh shape to split.
220
+ * @param plane - the cutting plane (normal + offset).
221
+ * @returns the front and back halves of the shape.
222
+ */
223
+ export async function split(shape, plane) {
224
+ const result = await computeSplit(shape, plane.normal, plane.offset);
225
+ return { front: result.front, back: result.back };
226
+ }
227
+ /**
228
+ * Dovetail split of a mesh shape.
229
+ * @param shape - the mesh shape to split.
230
+ * @param params - dovetail groove parameters plus the cutting plane setup.
231
+ * @returns the front/back halves and the dovetail wedge.
232
+ */
233
+ export async function dovetailSplit(shape, params) {
234
+ const result = await computeDovetailSplit(shape, params.plane.normal, params.plane.offset, params.planeCenter, params.widthDir, params.bboxWidthOnWidthDir, params.groove);
235
+ return {
236
+ front: result.front,
237
+ back: result.back,
238
+ wedge: result.wedge,
239
+ };
240
+ }
241
+ /**
242
+ * Dowel-pin split of a mesh shape.
243
+ * @param shape - the mesh shape to split.
244
+ * @param params - dowel parameters plus the cutting plane setup.
245
+ * @returns the front/back halves and the dowel wedge.
246
+ */
247
+ export async function dowelSplit(shape, params) {
248
+ const result = await computeDowelSplit(shape, params.plane.normal, params.plane.offset, params.planeCenter, params.widthDir, params.dowel, params.selectedSections);
249
+ return {
250
+ front: result.front,
251
+ back: result.back,
252
+ wedge: result.wedge,
253
+ };
254
+ }
255
+ /**
256
+ * Straight-tenon split of a mesh shape.
257
+ * @param shape - the mesh shape to split.
258
+ * @param params - tenon parameters plus the cutting plane setup.
259
+ * @returns the front/back halves and the tenon wedge.
260
+ */
261
+ export async function tenonSplit(shape, params) {
262
+ const result = await computeStraightTenonSplit(shape, params.plane.normal, params.plane.offset, params.planeCenter, params.widthDir, params.tenon, params.selectedSections);
263
+ return {
264
+ front: result.front,
265
+ back: result.back,
266
+ wedge: result.wedge,
267
+ };
268
+ }
269
+ //# sourceMappingURL=fai_split.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fai_split.js","sourceRoot":"","sources":["../../src/mesh/fai_split.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * mesh 分割 API\n *\n * 提取来源:engine/boolean/csg.ts (computeSplit/computeDovetailSplit/...)\n * 已是纯数据函数,直接包装。\n *\n * splitWithParams 是 UI 与重放共用的唯一分割入口:\n * 参数编排(planeParams / planeBasis / 爆炸位移 / 世界坐标变换)\n * 全部在此函数内部完成,调用方只需传入 cutMode + normal/offset/inPlaneAngleDeg + bbCenter\n * 以及各 cutMode 专属参数。\n */\n\nimport {\n computeSplit,\n computeDovetailSplit,\n computeDowelSplit,\n computeStraightTenonSplit,\n} from '@faicad/faijs/boolean/csg-backend'\nimport * as THREE from 'three'\nimport type { Shape, SplitResult, SplitPlane, DovetailSplitParams, DowelSplitParams, TenonSplitParams, Vec3 } from '@faicad/faijs/mesh/types'\n\n// ── 派生量计算(从 split-store 下沉,UI 与重放共用同一份公式) ──\n\n/**\n * Derive the cutting-plane parameters from Euler angles (in degrees), the\n * plane position and the bounding-box center. Behavior is identical to the\n * former split-store.computePlaneParams.\n * @param rotationX - rotation about X in degrees.\n * @param rotationY - rotation about Y in degrees.\n * @param rotationZ - rotation about Z in degrees.\n * @param planePosition - offset of the cutting plane along the plane normal.\n * @param bbCenter - world-space bounding-box center of the source mesh.\n * @returns the plane normal, the plane's signed distance from the origin, and its center.\n */\nexport function computePlaneParams(\n rotationX: number,\n rotationY: number,\n rotationZ: number,\n planePosition: number,\n bbCenter: Vec3,\n): { normal: Vec3; originOffset: number; planeCenter: Vec3 } {\n const rx = (rotationX * Math.PI) / 180\n const ry = (rotationY * Math.PI) / 180\n const rz = (rotationZ * Math.PI) / 180\n\n const euler = new THREE.Euler(rx, ry, rz, 'XYZ')\n const quat = new THREE.Quaternion().setFromEuler(euler)\n const normalVec = new THREE.Vector3(0, 0, 1).applyQuaternion(quat)\n\n const normal: Vec3 = [normalVec.x, normalVec.y, normalVec.z]\n\n const planeCenter: Vec3 = [\n bbCenter[0] + planePosition * normal[0],\n bbCenter[1] + planePosition * normal[1],\n bbCenter[2] + planePosition * normal[2],\n ]\n\n const originOffset = normal[0] * planeCenter[0] + normal[1] * planeCenter[1] + normal[2] * planeCenter[2]\n\n return { normal, originOffset, planeCenter }\n}\n\n/**\n * Derive the plane basis vectors from Euler angles (in degrees). Behavior is\n * identical to the former split-store.computePlaneBasis.\n * @param rotationX - rotation about X in degrees.\n * @param rotationY - rotation about Y in degrees.\n * @param rotationZ - rotation about Z in degrees.\n * @returns the plane normal plus the in-plane width and depth directions.\n */\nexport function computePlaneBasis(\n rotationX: number,\n rotationY: number,\n rotationZ: number,\n): { normal: Vec3; widthDir: Vec3; depthDir: Vec3 } {\n const rx = (rotationX * Math.PI) / 180\n const ry = (rotationY * Math.PI) / 180\n const rz = (rotationZ * Math.PI) / 180\n\n const euler = new THREE.Euler(rx, ry, rz, 'XYZ')\n const quat = new THREE.Quaternion().setFromEuler(euler)\n\n const normalVec = new THREE.Vector3(0, 0, 1).applyQuaternion(quat)\n const widthVec = new THREE.Vector3(1, 0, 0).applyQuaternion(quat)\n const depthVec = new THREE.Vector3(0, 1, 0).applyQuaternion(quat)\n\n return {\n normal: [normalVec.x, normalVec.y, normalVec.z],\n widthDir: [widthVec.x, widthVec.y, widthVec.z],\n depthDir: [depthVec.x, depthVec.y, depthVec.z],\n }\n}\n\n// ── 从法线 + 面内旋转推导基向量(纯数学,无 THREE 依赖) ──\n\n/**\n * Derive widthDir/depthDir from the cutting-plane normal and the in-plane\n * rotation angle. Matches computePlaneBasis(rx, ry, rz) with\n * normal = eulerXYZToNormal(rx, ry, 0) and inPlaneAngleDeg = rz: the rx/ry\n * Euler XYZ decomposition is recovered from the normal, then the rz rotation\n * is applied.\n *\n * normal = [sin(ry), -sin(rx)*cos(ry), cos(rx)*cos(ry)]\n * ry = asin(normal[0])\n * rx = atan2(-normal[1], normal[2]) (when cos(ry) !== 0)\n * @param normal - cutting-plane unit normal.\n * @param inPlaneAngleDeg - in-plane rotation about the normal, in degrees.\n * @returns the in-plane width and depth directions.\n */\nexport function computeBasisFromNormal(\n normal: Vec3,\n inPlaneAngleDeg: number,\n): { widthDir: Vec3; depthDir: Vec3 } {\n const sinRy = Math.max(-1, Math.min(1, normal[0]))\n const ry = Math.asin(sinRy)\n const cosRy = Math.cos(ry)\n\n let rx: number\n if (Math.abs(cosRy) < 1e-10) {\n // 万向锁:ry = ±90°,rx 与 rz 耦合,设 rx = 0\n rx = 0\n } else {\n rx = Math.atan2(-normal[1], normal[2])\n }\n\n const rz = (inPlaneAngleDeg * Math.PI) / 180\n\n // R = RX(rx) · RY(ry)(不含 rz 的部分)\n // widthDir = R · (cos(rz), sin(rz), 0)\n // depthDir = R · (-sin(rz), cos(rz), 0)\n const cr = Math.cos(ry), sr = Math.sin(ry)\n const cx = Math.cos(rx), sx = Math.sin(rx)\n const cosRz = Math.cos(rz), sinRz = Math.sin(rz)\n\n const widthDir: Vec3 = [\n cr * cosRz,\n sx * sr * cosRz + cx * sinRz,\n -cx * sr * cosRz + sx * sinRz,\n ]\n\n const depthDir: Vec3 = [\n -cr * sinRz,\n -sx * sr * sinRz + cx * cosRz,\n cx * sr * sinRz + sx * cosRz,\n ]\n\n return { widthDir, depthDir }\n}\n\n// ── 统一分割入口 ──\n\n/**\n * Input parameters for splitWithParams.\n */\nexport interface SplitWithParamsInput {\n /** 源几何(已在世界空间) */\n shape: Shape\n /** 分割模式 */\n cutMode: 'plane' | 'dovetail' | 'dowel' | 'straight-tenon' | 'tenon' | 'straight'\n /** 切割面法线 */\n normal: Vec3\n /** 切割面沿法线方向相对 bbCenter 的偏移(标量) */\n offset: number\n /** 切割面绕法线的面内旋转(角度制,承载原 planeRotation 的 rz 分量) */\n inPlaneAngleDeg: number\n /** 源 mesh 的世界空间包围盒中心 */\n bbCenter: Vec3\n /** 源 mesh 的世界空间包围盒尺寸(用于计算 bboxWidthOnWidthDir 和爆炸位移) */\n bboxSize: Vec3\n /** 燕尾参数(cutMode='dovetail' 时需要) */\n groove?: {\n depth: number\n depthTolerance: number\n width: number\n widthTolerance: number\n flapsAngle: number\n }\n /** 定位销参数(cutMode='dowel' 时需要) */\n dowel?: {\n diameter: number\n diameterTolerance: number\n height: number\n heightTolerance: number\n }\n /** 直榫参数(cutMode='straight-tenon' 时需要) */\n tenon?: {\n sideLength: number\n sideLengthTolerance: number\n height: number\n heightTolerance: number\n }\n /** 选择的截面(dowel/straight-tenon 用) */\n selectedSections?: number[] | null\n /** 是否应用爆炸位移(默认 true;导出/重放纯几何时可设 false) */\n applyExplode?: boolean\n}\n\n/**\n * Output of splitWithParams: the two halves plus the derived splitting data.\n */\nexport interface SplitWithParamsResult {\n front: Shape\n back: Shape\n /** 实际使用的法线(派生量,供调用方记录/调试) */\n normal: Vec3\n /** 实际使用的 originOffset(派生量) */\n originOffset: number\n /** 实际使用的 planeCenter(派生量) */\n planeCenter: Vec3\n /** 实际使用的 widthDir(派生量) */\n widthDir: Vec3\n /** 爆炸位移(front 正、back 负) */\n frontExplodeOffset: number\n backExplodeOffset: number\n}\n\n/**\n * Unified split entry point: the single split function shared by the UI\n * executor and script replay. All parameter orchestration (planeParams,\n * planeBasis, bboxWidthOnWidthDir, explode offset) happens inside this\n * function; callers pass only semantic parameters. Both interaction paths\n * (mouse / code) converge here, so the geometry result is identical.\n * @param input - the split mode-specific input (shape, plane, mode params).\n * @returns the split result with front/back halves and derived plane data.\n */\nexport async function splitWithParams(input: SplitWithParamsInput): Promise<SplitWithParamsResult> {\n const { shape, cutMode, normal, offset, inPlaneAngleDeg, bbCenter, bboxSize } = input\n\n // 1. 从 normal + offset + bbCenter 派生切割平面参数\n const planeCenter: Vec3 = [\n bbCenter[0] + offset * normal[0],\n bbCenter[1] + offset * normal[1],\n bbCenter[2] + offset * normal[2],\n ]\n const originOffset = normal[0] * planeCenter[0] + normal[1] * planeCenter[1] + normal[2] * planeCenter[2]\n\n // 2. 从 normal + inPlaneAngleDeg 派生基向量\n const { widthDir } = computeBasisFromNormal(normal, inPlaneAngleDeg)\n\n // 3. 按 cutMode 分发\n let front: Shape\n let back: Shape\n\n if (cutMode === 'dovetail') {\n const bboxWidthOnWidthDir =\n Math.abs(bboxSize[0] * widthDir[0]) +\n Math.abs(bboxSize[1] * widthDir[1]) +\n Math.abs(bboxSize[2] * widthDir[2])\n const groove = input.groove ?? {\n depth: 0, depthTolerance: 0, width: 0, widthTolerance: 0, flapsAngle: 0,\n }\n const result = await computeDovetailSplit(shape, normal, originOffset, planeCenter, widthDir, bboxWidthOnWidthDir, groove)\n front = result.front; back = result.back\n\n const result2: SplitWithParamsResult = await finalizeExplode(\n front, back, normal, originOffset, planeCenter, widthDir, bboxSize, cutMode, input,\n )\n return result2\n } else if (cutMode === 'dowel') {\n const dowel = input.dowel ?? {\n diameter: 0, diameterTolerance: 0, height: 0, heightTolerance: 0,\n }\n const result = await computeDowelSplit(shape, normal, originOffset, planeCenter, widthDir, dowel, input.selectedSections ?? null)\n front = result.front; back = result.back\n return finalizeExplode(front, back, normal, originOffset, planeCenter, widthDir, bboxSize, cutMode, input)\n } else if (cutMode === 'straight-tenon' || cutMode === 'tenon') {\n const tenon = input.tenon ?? {\n sideLength: 0, sideLengthTolerance: 0, height: 0, heightTolerance: 0,\n }\n const result = await computeStraightTenonSplit(shape, normal, originOffset, planeCenter, widthDir, tenon, input.selectedSections ?? null)\n front = result.front; back = result.back\n return finalizeExplode(front, back, normal, originOffset, planeCenter, widthDir, bboxSize, cutMode, input)\n } else {\n // plane / straight / default\n const result = await computeSplit(shape, normal, originOffset)\n front = result.front; back = result.back\n return finalizeExplode(front, back, normal, originOffset, planeCenter, widthDir, bboxSize, cutMode, input)\n }\n}\n\n/** 计算爆炸位移并应用到 front/back */\nasync function finalizeExplode(\n front: Shape,\n back: Shape,\n normal: Vec3,\n originOffset: number,\n planeCenter: Vec3,\n widthDir: Vec3,\n bboxSize: Vec3,\n cutMode: string,\n input: SplitWithParamsInput,\n): Promise<SplitWithParamsResult> {\n const applyExplode = input.applyExplode ?? true\n\n // 爆炸位移:包围盒对角线的 2% + joinery 深度的一半\n const bboxDiagonal = Math.sqrt(\n bboxSize[0] * bboxSize[0] + bboxSize[1] * bboxSize[1] + bboxSize[2] * bboxSize[2],\n )\n const baseOffset = bboxDiagonal * 0.02\n let joineryOffset = 0\n if (cutMode === 'dovetail') {\n joineryOffset = (input.groove?.depth ?? 0) / 2\n } else if (cutMode === 'dowel') {\n joineryOffset = (input.dowel?.height ?? 0) / 2\n } else if (cutMode === 'straight-tenon' || cutMode === 'tenon') {\n joineryOffset = (input.tenon?.height ?? 0) / 2\n }\n const frontOffset = baseOffset + joineryOffset\n const backOffset = -(baseOffset + joineryOffset)\n\n if (applyExplode) {\n front = { positions: offsetPositions(front.positions, normal, frontOffset), indices: front.indices }\n back = { positions: offsetPositions(back.positions, normal, backOffset), indices: back.indices }\n }\n\n return {\n front,\n back,\n normal,\n originOffset,\n planeCenter,\n widthDir,\n frontExplodeOffset: applyExplode ? frontOffset : 0,\n backExplodeOffset: applyExplode ? backOffset : 0,\n }\n}\n\n/** 沿法线方向偏移所有顶点 */\nfunction offsetPositions(\n positions: Float32Array,\n normal: Vec3,\n offset: number,\n): Float32Array {\n const out = new Float32Array(positions)\n for (let i = 0; i < out.length; i += 3) {\n out[i] += normal[0] * offset\n out[i + 1] += normal[1] * offset\n out[i + 2] += normal[2] * offset\n }\n return out\n}\n\n// ── 低级 API(保留向后兼容) ──\n\n/**\n * Plane split of a mesh shape.\n * @param shape - the mesh shape to split.\n * @param plane - the cutting plane (normal + offset).\n * @returns the front and back halves of the shape.\n */\nexport async function split(shape: Shape, plane: SplitPlane): Promise<{ front: Shape; back: Shape }> {\n const result = await computeSplit(shape, plane.normal, plane.offset)\n return { front: result.front, back: result.back }\n}\n\n/**\n * Dovetail split of a mesh shape.\n * @param shape - the mesh shape to split.\n * @param params - dovetail groove parameters plus the cutting plane setup.\n * @returns the front/back halves and the dovetail wedge.\n */\nexport async function dovetailSplit(shape: Shape, params: DovetailSplitParams): Promise<SplitResult> {\n const result = await computeDovetailSplit(\n shape,\n params.plane.normal,\n params.plane.offset,\n params.planeCenter,\n params.widthDir,\n params.bboxWidthOnWidthDir,\n params.groove,\n )\n return {\n front: result.front,\n back: result.back,\n wedge: result.wedge,\n }\n}\n\n/**\n * Dowel-pin split of a mesh shape.\n * @param shape - the mesh shape to split.\n * @param params - dowel parameters plus the cutting plane setup.\n * @returns the front/back halves and the dowel wedge.\n */\nexport async function dowelSplit(shape: Shape, params: DowelSplitParams): Promise<SplitResult> {\n const result = await computeDowelSplit(\n shape,\n params.plane.normal,\n params.plane.offset,\n params.planeCenter,\n params.widthDir,\n params.dowel,\n params.selectedSections,\n )\n return {\n front: result.front,\n back: result.back,\n wedge: result.wedge,\n }\n}\n\n/**\n * Straight-tenon split of a mesh shape.\n * @param shape - the mesh shape to split.\n * @param params - tenon parameters plus the cutting plane setup.\n * @returns the front/back halves and the tenon wedge.\n */\nexport async function tenonSplit(shape: Shape, params: TenonSplitParams): Promise<SplitResult> {\n const result = await computeStraightTenonSplit(\n shape,\n params.plane.normal,\n params.plane.offset,\n params.planeCenter,\n params.widthDir,\n params.tenon,\n params.selectedSections,\n )\n return {\n front: result.front,\n back: result.back,\n wedge: result.wedge,\n }\n}\n"]}
@@ -0,0 +1,57 @@
1
+ import * as primitives from './primitives.js';
2
+ import * as splitOps from './fai_split.js';
3
+ import * as drillOps from './fai_drill.js';
4
+ import * as extrudeOps from './fai_extrude.js';
5
+ /** Core platform mesh aggregate plus the editor-owned mesh implementations. */
6
+ export declare const editorCad: {
7
+ text: typeof primitives.text;
8
+ svgExtrude: typeof primitives.svgExtrude;
9
+ fai_split: typeof splitOps.split;
10
+ fai_splitWithParams: typeof splitOps.splitWithParams;
11
+ dovetailSplit: typeof splitOps.dovetailSplit;
12
+ dowelSplit: typeof splitOps.dowelSplit;
13
+ tenonSplit: typeof splitOps.tenonSplit;
14
+ fai_drill: typeof drillOps.drill;
15
+ fai_extrude: typeof extrudeOps.extrude;
16
+ box: typeof import("@faicad/faijs/mesh/primitives").box;
17
+ sphere: typeof import("@faicad/faijs/mesh/primitives").sphere;
18
+ cylinder: typeof import("@faicad/faijs/mesh/primitives").cylinder;
19
+ cone: typeof import("@faicad/faijs/mesh/primitives").cone;
20
+ wedge: typeof import("@faicad/faijs/mesh/primitives").wedge;
21
+ screw: typeof import("@faicad/faijs/mesh/primitives").screw;
22
+ sdf: typeof import("@faicad/faijs/mesh/primitives").sdf;
23
+ boxBrep: typeof import("@faicad/faijs/brep/primitives-brep").boxBrep;
24
+ sphereBrep: typeof import("@faicad/faijs/brep/primitives-brep").sphereBrep;
25
+ cylinderBrep: typeof import("@faicad/faijs/brep/primitives-brep").cylinderBrep;
26
+ coneBrep: typeof import("@faicad/faijs/brep/primitives-brep").coneBrep;
27
+ wedgeBrep: typeof import("@faicad/faijs/brep/primitives-brep").wedgeBrep;
28
+ translate: typeof import("@faicad/faijs/mesh/transform").translate;
29
+ rotate_euler: typeof import("@faicad/faijs/mesh/transform").rotate_euler;
30
+ scale: typeof import("@faicad/faijs/mesh/transform").scale;
31
+ scale3d: typeof import("@faicad/faijs/mesh/transform").scale3d;
32
+ transformMatrix: typeof import("@faicad/faijs/mesh/transform").transformMatrix;
33
+ union: typeof import("@faicad/faijs/mesh/boolean").union;
34
+ subtract: typeof import("@faicad/faijs/mesh/boolean").subtract;
35
+ intersect: typeof import("@faicad/faijs/mesh/boolean").intersect;
36
+ engrave: typeof import("@faicad/faijs/mesh/engrave").engrave;
37
+ knurl: typeof import("@faicad/faijs/mesh/engrave").knurl;
38
+ boundingBox: typeof import("@faicad/faijs/mesh/query").boundingBox;
39
+ bboxCenter: typeof import("@faicad/faijs/mesh/query").bboxCenter;
40
+ volume: typeof import("@faicad/faijs/mesh/query").volume;
41
+ faceAt: typeof import("@faicad/faijs/mesh/query").faceAt;
42
+ load: typeof import("@faicad/faijs/mesh/io").importFile;
43
+ translateBrep: typeof import("@faicad/faijs/brep/brep-ops").translateBrep;
44
+ rotateBrep: typeof import("@faicad/faijs/brep/brep-ops").rotateBrep;
45
+ scaleBrep: typeof import("@faicad/faijs/brep/brep-ops").scaleBrep;
46
+ fuseBrep: typeof import("@faicad/faijs/brep/brep-ops").fuseBrep;
47
+ cutBrep: typeof import("@faicad/faijs/brep/brep-ops").cutBrep;
48
+ commonBrep: typeof import("@faicad/faijs/brep/brep-ops").commonBrep;
49
+ drillBrep: typeof import("@faicad/faijs/brep/brep-ops").drillBrep;
50
+ splitBrep: typeof import("@faicad/faijs/brep/brep-ops").splitBrep;
51
+ extrudeBrep: typeof import("@faicad/faijs/brep/brep-ops").extrudeBrep;
52
+ loadBrep: typeof import("@faicad/faijs/brep/brep-ops").loadBrep;
53
+ solidToShape: typeof import("@faicad/faijs/mesh").solidToShape;
54
+ };
55
+ export type { Shape, Vec3, BoundingBox, FaceDescriptor } from '@faicad/faijs/mesh/types';
56
+ export type { TextParams, SvgExtrudeParams, DrillParams, ExtrudeParams, SplitPlane, SplitResult, DovetailSplitParams, DowelSplitParams, TenonSplitParams, } from '@faicad/faijs/mesh/types';
57
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mesh/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,UAAU,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,QAAQ,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,QAAQ,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,UAAU,MAAM,eAAe,CAAA;AAE3C,+EAA+E;AAC/E,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmBrB,CAAA;AAED,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACxF,YAAY,EACV,UAAU,EAAE,gBAAgB,EAC5B,WAAW,EAAE,aAAa,EAC1B,UAAU,EAAE,WAAW,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,gBAAgB,GACjF,MAAM,0BAA0B,CAAA"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * mesh index — editor extension mesh aggregate.
3
+ *
4
+ * Core's `@faicad/faijs/mesh` `cad` object carries the platform mesh
5
+ * implementations. This module re-exposes it with the editor-owned keys put
6
+ * back, so the moved op definitions (`ops/*`) can call `cad.fai_drill(...)`,
7
+ * `cad.text(...)` and friends exactly the way they did inside core.
8
+ *
9
+ * Consumers that only need one implementation should import the specific module
10
+ * (`@faicad/faijs/mesh/query`, `./fai_split`, ...) instead of this aggregate.
11
+ */
12
+ import { cad as coreCad } from '@faicad/faijs/mesh';
13
+ import * as primitives from './primitives.js';
14
+ import * as splitOps from './fai_split.js';
15
+ import * as drillOps from './fai_drill.js';
16
+ import * as extrudeOps from './fai_extrude.js';
17
+ /** Core platform mesh aggregate plus the editor-owned mesh implementations. */
18
+ export const editorCad = {
19
+ ...coreCad,
20
+ // 创建(B 组:svg / 文字)
21
+ text: primitives.text,
22
+ svgExtrude: primitives.svgExtrude,
23
+ // 分割(A 组:fai_split 家族)
24
+ fai_split: splitOps.split,
25
+ fai_splitWithParams: splitOps.splitWithParams,
26
+ dovetailSplit: splitOps.dovetailSplit,
27
+ dowelSplit: splitOps.dowelSplit,
28
+ tenonSplit: splitOps.tenonSplit,
29
+ // 钻孔(A 组)
30
+ fai_drill: drillOps.drill,
31
+ // 拉伸(A 组)
32
+ fai_extrude: extrudeOps.extrude,
33
+ };
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mesh/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,GAAG,IAAI,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,UAAU,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,QAAQ,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,QAAQ,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,UAAU,MAAM,eAAe,CAAA;AAE3C,+EAA+E;AAC/E,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,GAAG,OAAO;IAEV,mBAAmB;IACnB,IAAI,EAAE,UAAU,CAAC,IAAI;IACrB,UAAU,EAAE,UAAU,CAAC,UAAU;IAEjC,uBAAuB;IACvB,SAAS,EAAE,QAAQ,CAAC,KAAK;IACzB,mBAAmB,EAAE,QAAQ,CAAC,eAAe;IAC7C,aAAa,EAAE,QAAQ,CAAC,aAAa;IACrC,UAAU,EAAE,QAAQ,CAAC,UAAU;IAC/B,UAAU,EAAE,QAAQ,CAAC,UAAU;IAE/B,UAAU;IACV,SAAS,EAAE,QAAQ,CAAC,KAAK;IAEzB,UAAU;IACV,WAAW,EAAE,UAAU,CAAC,OAAO;CAChC,CAAA","sourcesContent":["/**\n * mesh index — editor extension mesh aggregate.\n *\n * Core's `@faicad/faijs/mesh` `cad` object carries the platform mesh\n * implementations. This module re-exposes it with the editor-owned keys put\n * back, so the moved op definitions (`ops/*`) can call `cad.fai_drill(...)`,\n * `cad.text(...)` and friends exactly the way they did inside core.\n *\n * Consumers that only need one implementation should import the specific module\n * (`@faicad/faijs/mesh/query`, `./fai_split`, ...) instead of this aggregate.\n */\nimport { cad as coreCad } from '@faicad/faijs/mesh'\nimport * as primitives from './primitives'\nimport * as splitOps from './fai_split'\nimport * as drillOps from './fai_drill'\nimport * as extrudeOps from './fai_extrude'\n\n/** Core platform mesh aggregate plus the editor-owned mesh implementations. */\nexport const editorCad = {\n ...coreCad,\n\n // 创建(B 组:svg / 文字)\n text: primitives.text,\n svgExtrude: primitives.svgExtrude,\n\n // 分割(A 组:fai_split 家族)\n fai_split: splitOps.split,\n fai_splitWithParams: splitOps.splitWithParams,\n dovetailSplit: splitOps.dovetailSplit,\n dowelSplit: splitOps.dowelSplit,\n tenonSplit: splitOps.tenonSplit,\n\n // 钻孔(A 组)\n fai_drill: drillOps.drill,\n\n // 拉伸(A 组)\n fai_extrude: extrudeOps.extrude,\n}\n\nexport type { Shape, Vec3, BoundingBox, FaceDescriptor } from '@faicad/faijs/mesh/types'\nexport type {\n TextParams, SvgExtrudeParams,\n DrillParams, ExtrudeParams,\n SplitPlane, SplitResult, DovetailSplitParams, DowelSplitParams, TenonSplitParams,\n} from '@faicad/faijs/mesh/types'\n"]}
@@ -0,0 +1,17 @@
1
+ import type { Shape, TextParams, SvgExtrudeParams } from '@faicad/faijs/mesh/types';
2
+ /**
3
+ * Create text geometry. Font loading is asynchronous, so this function is
4
+ * async; CJK text is rendered with the system font when one is available.
5
+ *
6
+ * @param params - text parameters (text, size, depth).
7
+ * @returns the text shape.
8
+ */
9
+ export declare function text(params: TextParams): Promise<Shape>;
10
+ /**
11
+ * Create an extruded geometry from an SVG source.
12
+ *
13
+ * @param params - SVG extrude parameters (svg, depth, target size, optional natural size).
14
+ * @returns the extruded shape.
15
+ */
16
+ export declare function svgExtrude(params: SvgExtrudeParams): Shape;
17
+ //# sourceMappingURL=primitives.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../src/mesh/primitives.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAWnF;;;;;;GAMG;AACH,wBAAsB,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,CAkB7D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,KAAK,CAQ1D"}
@@ -0,0 +1,53 @@
1
+ import { geoToManifoldMesh } from '@faicad/faijs/boolean/csg-backend';
2
+ import { svgToExtrudedGeometry } from '../extras/svg-extrude.js';
3
+ import { createTextGeometry, getOpentypeFont } from '../extras/text-geometry.js';
4
+ import { createMixedTextGeometry } from '../extras/text-cjk.js';
5
+ import { containsCjk, loadSystemCjkFont } from '@faicad/faijs/primitives/text/cjk-font';
6
+ /** THREE.BufferGeometry → Shape (ManifoldMeshData) */
7
+ function geoToShape(geo) {
8
+ return geoToManifoldMesh(geo);
9
+ }
10
+ /**
11
+ * Create text geometry. Font loading is asynchronous, so this function is
12
+ * async; CJK text is rendered with the system font when one is available.
13
+ *
14
+ * @param params - text parameters (text, size, depth).
15
+ * @returns the text shape.
16
+ */
17
+ export async function text(params) {
18
+ const font = await getOpentypeFont();
19
+ let geo;
20
+ if (containsCjk(params.text)) {
21
+ const cjkFont = await loadSystemCjkFont();
22
+ if (cjkFont) {
23
+ geo = await createMixedTextGeometry(params.text, params.size, params.depth, cjkFont.font, font);
24
+ }
25
+ else {
26
+ // No CJK font available: replace CJK chars with '?' so geometry still
27
+ // can be created (graceful degradation). Without this, createTextGeometry
28
+ // would throw because CJK glyphs are .notdef in OpenSans Regular.
29
+ const fallback = params.text.replace(/[\u4E00-\u9FFF\u3400-\u4DBF\u2F800-\u2FA1F\u3000-\u303F\uFF00-\uFFEF]/g, '?');
30
+ geo = await createTextGeometry(fallback, params.size, params.depth, font);
31
+ }
32
+ }
33
+ else {
34
+ geo = await createTextGeometry(params.text, params.size, params.depth, font);
35
+ }
36
+ return geoToShape(geo);
37
+ }
38
+ /**
39
+ * Create an extruded geometry from an SVG source.
40
+ *
41
+ * @param params - SVG extrude parameters (svg, depth, target size, optional natural size).
42
+ * @returns the extruded shape.
43
+ */
44
+ export function svgExtrude(params) {
45
+ const geo = svgToExtrudedGeometry(params.svg, {
46
+ depth: params.depth,
47
+ targetLongSide: params.targetLongSide ?? 20,
48
+ naturalWidth: params.naturalWidth ?? 0,
49
+ naturalHeight: params.naturalHeight ?? 0,
50
+ });
51
+ return geoToShape(geo);
52
+ }
53
+ //# sourceMappingURL=primitives.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primitives.js","sourceRoot":"","sources":["../../src/mesh/primitives.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AAErE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AAEvF,sDAAsD;AACtD,SAAS,UAAU,CAAC,GAAyB;IAC3C,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAA;AAC/B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,MAAkB;IAC3C,MAAM,IAAI,GAAG,MAAM,eAAe,EAAE,CAAA;IACpC,IAAI,GAAyB,CAAA;IAC7B,IAAI,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,MAAM,iBAAiB,EAAE,CAAA;QACzC,IAAI,OAAO,EAAE,CAAC;YACZ,GAAG,GAAG,MAAM,uBAAuB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QACjG,CAAC;aAAM,CAAC;YACN,sEAAsE;YACtE,0EAA0E;YAC1E,kEAAkE;YAClE,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,wEAAwE,EAAE,GAAG,CAAC,CAAA;YACnH,GAAG,GAAG,MAAM,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAC3E,CAAC;IACH,CAAC;SAAM,CAAC;QACN,GAAG,GAAG,MAAM,kBAAkB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IAC9E,CAAC;IACD,OAAO,UAAU,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,MAAwB;IACjD,MAAM,GAAG,GAAG,qBAAqB,CAAC,MAAM,CAAC,GAAG,EAAE;QAC5C,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,cAAc,EAAE,MAAM,CAAC,cAAc,IAAI,EAAE;QAC3C,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,CAAC;QACtC,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,CAAC;KACzC,CAAC,CAAA;IACF,OAAO,UAAU,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC","sourcesContent":["/**\n * mesh primitives — editor extension mesh-path creators.\n *\n * `text` and `svgExtrude` are the two creators whose mesh implementations carry\n * three's non-basic geometry chain (`Shape` / `ExtrudeGeometry` / `SVGLoader`).\n * They moved here from core's `mesh/primitives.ts` so the platform mesh entry no\n * longer drags that chain (nor `three/examples/**`) into every consumer.\n *\n * Both return Shape (ManifoldMeshData): THREE.BufferGeometry →\n * geoToManifoldMesh conversion. Coordinates: Z-up, millimetres.\n */\nimport * as THREE from 'three'\nimport { geoToManifoldMesh } from '@faicad/faijs/boolean/csg-backend'\nimport type { Shape, TextParams, SvgExtrudeParams } from '@faicad/faijs/mesh/types'\nimport { svgToExtrudedGeometry } from '../extras/svg-extrude'\nimport { createTextGeometry, getOpentypeFont } from '../extras/text-geometry'\nimport { createMixedTextGeometry } from '../extras/text-cjk'\nimport { containsCjk, loadSystemCjkFont } from '@faicad/faijs/primitives/text/cjk-font'\n\n/** THREE.BufferGeometry → Shape (ManifoldMeshData) */\nfunction geoToShape(geo: THREE.BufferGeometry): Shape {\n return geoToManifoldMesh(geo)\n}\n\n/**\n * Create text geometry. Font loading is asynchronous, so this function is\n * async; CJK text is rendered with the system font when one is available.\n *\n * @param params - text parameters (text, size, depth).\n * @returns the text shape.\n */\nexport async function text(params: TextParams): Promise<Shape> {\n const font = await getOpentypeFont()\n let geo: THREE.BufferGeometry\n if (containsCjk(params.text)) {\n const cjkFont = await loadSystemCjkFont()\n if (cjkFont) {\n geo = await createMixedTextGeometry(params.text, params.size, params.depth, cjkFont.font, font)\n } else {\n // No CJK font available: replace CJK chars with '?' so geometry still\n // can be created (graceful degradation). Without this, createTextGeometry\n // would throw because CJK glyphs are .notdef in OpenSans Regular.\n const fallback = params.text.replace(/[\\u4E00-\\u9FFF\\u3400-\\u4DBF\\u2F800-\\u2FA1F\\u3000-\\u303F\\uFF00-\\uFFEF]/g, '?')\n geo = await createTextGeometry(fallback, params.size, params.depth, font)\n }\n } else {\n geo = await createTextGeometry(params.text, params.size, params.depth, font)\n }\n return geoToShape(geo)\n}\n\n/**\n * Create an extruded geometry from an SVG source.\n *\n * @param params - SVG extrude parameters (svg, depth, target size, optional natural size).\n * @returns the extruded shape.\n */\nexport function svgExtrude(params: SvgExtrudeParams): Shape {\n const geo = svgToExtrudedGeometry(params.svg, {\n depth: params.depth,\n targetLongSide: params.targetLongSide ?? 20,\n naturalWidth: params.naturalWidth ?? 0,\n naturalHeight: params.naturalHeight ?? 0,\n })\n return geoToShape(geo)\n}\n"]}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * namespace — the full editor `cad` namespace assembly point.
3
+ *
4
+ * Core's `createApiNamespace()` returns the platform surface only. The ops that
5
+ * serve the editor's canvas/drag/timeline model and the two non-basic-three
6
+ * creators are assembled here with the exact keys they had inside core:
7
+ *
8
+ * - A group (`createEditorOpsNamespace()`, from `./editor-ops`): `fai_drill`,
9
+ * `fai_extrude`, `fai_split`, `group`, `assembly`, `copy`, `load`;
10
+ * - B group: `text`, `svgExtrude`.
11
+ *
12
+ * The host merges the two namespaces and registers the result as its `cad`
13
+ * library:
14
+ *
15
+ * ```ts
16
+ * import { createRuntime } from '@faicad/faijs/browser'
17
+ * import { createEditorCadNamespace, registerEditorSymbols, installEditorMeshProviders }
18
+ * from '@faicad/faijs-extra/browser'
19
+ *
20
+ * const rt = createRuntime(ports, mode)
21
+ * rt.registerLib('cad', createEditorCadNamespace(), { default: true })
22
+ * registerEditorSymbols()
23
+ * installEditorMeshProviders()
24
+ * ```
25
+ *
26
+ * Hosts that must not bundle three's non-basic chain (the weapp worker) use
27
+ * `@faicad/faijs-extra/editor-ops` and `createEditorOwnedCadNamespace()` instead.
28
+ *
29
+ * Existing `.fai.js` scripts are unaffected: op names, signatures and semantics
30
+ * are unchanged.
31
+ */
32
+ import { type StdlibNamespace } from '@faicad/faijs/runtime-state';
33
+ /**
34
+ * Assemble the whole editor-owned op set (A + B) into a `cad`-shaped namespace.
35
+ *
36
+ * Keys match the pre-split core namespace exactly, so a host that already
37
+ * carried these ops keeps working after swapping the import source.
38
+ *
39
+ * @returns the editor namespace, ready to be spread over the platform surface.
40
+ */
41
+ export declare function createEditorNamespace(): StdlibNamespace;
42
+ /**
43
+ * Merge the editor namespace over a platform namespace.
44
+ *
45
+ * Pure: no registration side effects. Editor keys win on collision — today there
46
+ * is none, and the membership guard fails loudly if one is ever introduced.
47
+ *
48
+ * @param platform - the platform namespace (`createApiNamespace()`).
49
+ * @returns the merged namespace to register as the host's `cad` library.
50
+ */
51
+ export declare function mergeEditorNamespace(platform: StdlibNamespace): StdlibNamespace;
52
+ /**
53
+ * The `cad` namespace a host registers: core's platform surface plus the whole
54
+ * editor op set.
55
+ *
56
+ * Saves hosts from importing `createApiNamespace()` themselves; the extension
57
+ * library is the party that knows the platform surface it extends.
58
+ *
59
+ * @returns the namespace to hand to `registerLib('cad', …)`.
60
+ */
61
+ export declare function createEditorCadNamespace(): StdlibNamespace;
62
+ /**
63
+ * Register every editor op name in the core symbol table (P5).
64
+ *
65
+ * Static analysis that reads `SYMBOL_TABLE` (callee lookup, the fcstd boundary
66
+ * guard) then sees the editor ops. The runtime `check()` does not validate
67
+ * callee existence, so this is an analysis aid, not a gate.
68
+ */
69
+ export declare function registerEditorSymbols(): void;
70
+ /** Undo `registerEditorSymbols()`. */
71
+ export declare function unregisterEditorSymbols(): void;
72
+ /**
73
+ * Install the mesh-level capabilities core asks its host for.
74
+ *
75
+ * Today: the `cad.engrave` mesh decoration provider (text/SVG → geometry). The
76
+ * BREP path of `engrave` needs nothing from here — it uses OCCT directly.
77
+ */
78
+ export declare function installEditorMeshProviders(): void;
79
+ /** Uninstall the providers installed by `installEditorMeshProviders()`. */
80
+ export declare function uninstallEditorMeshProviders(): void;
81
+ export { ALL_EDITOR_OPS, CREATOR_OPS } from './op-names.js';
82
+ //# sourceMappingURL=namespace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../src/namespace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAUpF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAOvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CAE/E;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,IAAI,eAAe,CAE1D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,sCAAsC;AACtC,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAED,2EAA2E;AAC3E,wBAAgB,4BAA4B,IAAI,IAAI,CAEnD;AAED,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA"}