@faicad/faijs-gears 0.0.0-stage → 0.29.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/LICENSE +202 -0
  2. package/dist/bevel_gear.d.ts +113 -0
  3. package/dist/bevel_gear.d.ts.map +1 -0
  4. package/dist/bevel_gear.js +327 -0
  5. package/dist/bevel_gear.js.map +1 -0
  6. package/dist/crossed_helical_gear.d.ts +24 -0
  7. package/dist/crossed_helical_gear.d.ts.map +1 -0
  8. package/dist/crossed_helical_gear.js +24 -0
  9. package/dist/crossed_helical_gear.js.map +1 -0
  10. package/dist/crossed_pair.d.ts +177 -0
  11. package/dist/crossed_pair.d.ts.map +1 -0
  12. package/dist/crossed_pair.js +237 -0
  13. package/dist/crossed_pair.js.map +1 -0
  14. package/dist/features.d.ts +184 -0
  15. package/dist/features.d.ts.map +1 -0
  16. package/dist/features.js +408 -0
  17. package/dist/features.js.map +1 -0
  18. package/dist/index.d.ts +178 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +223 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/kernel/gears.d.ts +231 -0
  23. package/dist/kernel/gears.d.ts.map +1 -0
  24. package/dist/kernel/gears.js +274 -0
  25. package/dist/kernel/gears.js.map +1 -0
  26. package/dist/kernel/index.d.ts +10 -0
  27. package/dist/kernel/index.d.ts.map +1 -0
  28. package/dist/kernel/index.js +9 -0
  29. package/dist/kernel/index.js.map +1 -0
  30. package/dist/math.d.ts +200 -0
  31. package/dist/math.d.ts.map +1 -0
  32. package/dist/math.js +274 -0
  33. package/dist/math.js.map +1 -0
  34. package/dist/pairs.d.ts +161 -0
  35. package/dist/pairs.d.ts.map +1 -0
  36. package/dist/pairs.js +183 -0
  37. package/dist/pairs.js.map +1 -0
  38. package/dist/planetary.d.ts +96 -0
  39. package/dist/planetary.d.ts.map +1 -0
  40. package/dist/planetary.js +139 -0
  41. package/dist/planetary.js.map +1 -0
  42. package/dist/profile.d.ts +439 -0
  43. package/dist/profile.d.ts.map +1 -0
  44. package/dist/profile.js +597 -0
  45. package/dist/profile.js.map +1 -0
  46. package/dist/rack_gear.d.ts +125 -0
  47. package/dist/rack_gear.d.ts.map +1 -0
  48. package/dist/rack_gear.js +344 -0
  49. package/dist/rack_gear.js.map +1 -0
  50. package/dist/ring_gear.d.ts +54 -0
  51. package/dist/ring_gear.d.ts.map +1 -0
  52. package/dist/ring_gear.js +133 -0
  53. package/dist/ring_gear.js.map +1 -0
  54. package/dist/spline-face.d.ts +99 -0
  55. package/dist/spline-face.d.ts.map +1 -0
  56. package/dist/spline-face.js +94 -0
  57. package/dist/spline-face.js.map +1 -0
  58. package/dist/spur_gear.d.ts +112 -0
  59. package/dist/spur_gear.d.ts.map +1 -0
  60. package/dist/spur_gear.js +206 -0
  61. package/dist/spur_gear.js.map +1 -0
  62. package/dist/worm_gear.d.ts +78 -0
  63. package/dist/worm_gear.d.ts.map +1 -0
  64. package/dist/worm_gear.js +256 -0
  65. package/dist/worm_gear.js.map +1 -0
  66. package/package.json +49 -4
  67. package/README.md +0 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,597 @@
1
+ /**
2
+ * profile — SpurGear 齿廓与齿面点阵(cq_gears `spur_gear.py` 的数学部分)
3
+ *
4
+ * 只做**纯数学**,不碰内核:输出的是点集,可直接与 Python 侧
5
+ * `SpurGear.t_lflank_pts` 等内部数组逐点比对(见 `profile.test.ts`)。
6
+ *
7
+ * 参数名**逐字沿用 Python**(`module` / `teeth_number` / `pressure_angle` …),
8
+ * 这样 `regression_test_cases.json` 可以零转换直接喂进来。
9
+ */
10
+ import { angleBetween, circle3dBy3points, dot, linspace, norm, rotateRows, sArc, sInv, sphereToCartesian, vec3, } from './math.js';
11
+ /** `GearBase` 的类级常量(cq_gears `spur_gear.py:27-39`),逐字保留。 */
12
+ export const GEAR_BASE_CONSTANTS = {
13
+ ka: 1.0,
14
+ kd: 1.25,
15
+ curve_points: 20,
16
+ surface_splines: 5,
17
+ wire_comb_tol: 1e-2,
18
+ spline_approx_tol: 1e-2,
19
+ shell_sewing_tol: 1e-2,
20
+ isection_tol: 1e-7,
21
+ spline_approx_min_deg: 3,
22
+ spline_approx_max_deg: 8,
23
+ };
24
+ /**
25
+ * 计算 SpurGear 的全部几何量(`SpurGear.__init__` 的移植)。
26
+ *
27
+ * @throws 与 Python 同款校验:`addendum_coeff <= 0` / `dedendum_coeff <= 0` /
28
+ * 齿根圆直径 <= 0。
29
+ *
30
+ * @param params 齿轮参数(模数/齿数/宽度等)
31
+ * @returns 全部派生几何量
32
+ */
33
+ export function spurGearGeometry(params) {
34
+ const ka = params.addendum_coeff ?? GEAR_BASE_CONSTANTS.ka;
35
+ const kd = params.dedendum_coeff ?? GEAR_BASE_CONSTANTS.kd;
36
+ if (params.addendum_coeff !== undefined && params.addendum_coeff !== null && params.addendum_coeff <= 0) {
37
+ throw new RangeError('Addendum coefficient (addendum_coeff) must be greater than 0.');
38
+ }
39
+ if (params.dedendum_coeff !== undefined && params.dedendum_coeff !== null && params.dedendum_coeff <= 0) {
40
+ throw new RangeError('Dedendum coefficient (dedendum_coeff) must be greater than 0.');
41
+ }
42
+ const m = params.module;
43
+ const z = params.teeth_number;
44
+ const a0 = (params.pressure_angle ?? 20.0) * Math.PI / 180;
45
+ const clearance = params.clearance ?? 0.0;
46
+ const backlash = params.backlash ?? 0.0;
47
+ const helixAngle = (params.helix_angle ?? 0.0) * Math.PI / 180;
48
+ const width = params.width;
49
+ const d0 = m * z;
50
+ const adn = ka / (z / d0);
51
+ const ddn = kd / (z / d0);
52
+ if (2 * ddn + 2 * clearance >= d0) {
53
+ throw new RangeError('Invalid dedendum or clearance: resulting dedendum circle diameter is negative or zero.');
54
+ }
55
+ const da = d0 + 2 * adn;
56
+ const dd = d0 - 2 * ddn - 2 * clearance;
57
+ const s0 = m * (Math.PI / 2 - backlash * Math.tan(a0));
58
+ const invA0 = Math.tan(a0) - a0;
59
+ const r0 = d0 / 2;
60
+ const ra = da / 2;
61
+ const rd = dd / 2;
62
+ const rb = Math.cos(a0) * d0 / 2;
63
+ const rr = Math.max(rb, rd);
64
+ const tau = Math.PI * 2 / z;
65
+ const twistAngle = helixAngle !== 0
66
+ ? width / (r0 * Math.tan(Math.PI / 2 - helixAngle))
67
+ : 0.0;
68
+ const surfaceSplines = helixAngle !== 0 ? GEAR_BASE_CONSTANTS.surface_splines : 2;
69
+ const n = GEAR_BASE_CONSTANTS.curve_points;
70
+ // 左齿廓渐开线
71
+ const r = linspace(rr, ra, n);
72
+ const phi = r.map((ri) => {
73
+ const cosA = (r0 / ri) * Math.cos(a0);
74
+ const a = Math.acos(Math.min(1, Math.max(-1, cosA)));
75
+ const invA = Math.tan(a) - a;
76
+ const s = ri * (s0 / d0 + invA0 - invA);
77
+ return s / ri;
78
+ });
79
+ const t_lflank_pts = r.map((ri, i) => vec3(Math.cos(phi[i]) * ri, Math.sin(phi[i]) * ri, 0));
80
+ // 齿顶圆弧
81
+ const bTip = linspace(phi[n - 1], -phi[n - 1], n);
82
+ const t_tip_pts = bTip.map((bi) => vec3(Math.cos(bi) * ra, Math.sin(bi) * ra, 0));
83
+ // 右齿廓 = 左齿廓镜像并反向
84
+ const t_rflank_pts = r.map((ri, i) => vec3(Math.cos(-phi[i]) * ri, Math.sin(-phi[i]) * ri, 0)).reverse();
85
+ // 齿根圆弧:过右齿廓末点、齿根圆中点、左齿廓起点(下一个齿的起点)三点定圆
86
+ const rho = tau - phi[0] * 2;
87
+ const p1 = vec3(t_rflank_pts[n - 1].x, t_rflank_pts[n - 1].y, 0);
88
+ const p2 = vec3(Math.cos(-phi[0] - rho / 2) * rd, Math.sin(-phi[0] - rho / 2) * rd, 0);
89
+ const p3 = vec3(Math.cos(-phi[0] - rho) * rr, Math.sin(-phi[0] - rho) * rr, 0);
90
+ const { radius: bcr, center: bcxy } = circle3dBy3points(p1, p2, p3);
91
+ let t1 = Math.atan2(p1.y - bcxy.y, p1.x - bcxy.x);
92
+ let t2 = Math.atan2(p3.y - bcxy.y, p3.x - bcxy.x);
93
+ if (t1 < 0)
94
+ t1 += Math.PI * 2;
95
+ if (t2 < 0)
96
+ t2 += Math.PI * 2;
97
+ const ta = Math.min(t1, t2);
98
+ const tb = Math.max(t1, t2);
99
+ const tRoot = linspace(ta + Math.PI * 2, tb + Math.PI * 2, n);
100
+ const t_root_pts = tRoot.map((t) => vec3(bcxy.x + bcr * Math.cos(t), bcxy.y + bcr * Math.sin(t), 0));
101
+ return {
102
+ m, z, a0, clearance, backlash, helixAngle, width, ka, kd,
103
+ d0, adn, ddn, da, dd, s0, invA0,
104
+ r0, ra, rd, rb, rr, tau,
105
+ twistAngle, surfaceSplines, curvePoints: n,
106
+ t_lflank_pts, t_tip_pts, t_rflank_pts, t_root_pts,
107
+ };
108
+ }
109
+ /**
110
+ * 计算 HyperbolicGear(双曲面齿轮)的全部几何量
111
+ * (`crossed_helical_gear.py::HyperbolicGear.__init__` 的移植)。
112
+ *
113
+ * 它继承 `SpurGear`,**齿廓数学与 SpurGear 同构**——父类以 `helix_angle=0.0` 构造
114
+ * (故 `twistAngle=0` / `surfaceSplines=2`),然后本函数**覆盖**两处:
115
+ * - `self.twist_angle = radians(twist_angle)`:扭转角来自显式构造参数,而非 helix 推导;
116
+ * - `throat_r`(`throat_r = sqrt(rpx² + rpy²)`,`rpx=(r0+ln)/2`、`rpy=ht/2`、
117
+ * `ln=cos(twist)·r0`、`ht=sin(twist)·r0`):喉部半径,用于齿轮对装配的 X 向偏移。
118
+ *
119
+ * 返回的仍是 `SpurGearGeometry`(点集布局一致),可直接喂给 `buildGearSolid` /
120
+ * `toothFaceGrids`;`throatR` 作为扩展字段附带。
121
+ *
122
+ * @throws `twist_angle` 缺失(无默认值,与 Python 同款签名)由调用方负责。
123
+ *
124
+ * @param params 双曲面齿轮参数(模数/齿数/宽度/扭转角等)
125
+ * @returns `SpurGearGeometry` + `throatR`
126
+ */
127
+ export function hyperbolicGearGeometry(params) {
128
+ const base = spurGearGeometry({
129
+ module: params.module,
130
+ teeth_number: params.teeth_number,
131
+ width: params.width,
132
+ pressure_angle: params.pressure_angle,
133
+ clearance: params.clearance,
134
+ backlash: params.backlash,
135
+ helix_angle: 0.0,
136
+ });
137
+ const twistAngle = (params.twist_angle * Math.PI) / 180.0;
138
+ base.twistAngle = twistAngle;
139
+ base.surfaceSplines = 2;
140
+ const ln = Math.cos(twistAngle) * base.r0;
141
+ const ht = Math.sin(twistAngle) * base.r0;
142
+ const rpx = (base.r0 + ln) / 2.0;
143
+ const rpy = ht / 2.0;
144
+ const throatR = Math.sqrt(rpx * rpx + rpy * rpy);
145
+ return { ...base, throatR };
146
+ }
147
+ /**
148
+ * 计算 RingGear(内部齿)的全部几何量(`RingGear.__init__` 的移植)。
149
+ *
150
+ * 与 SpurGear 的关键差异(内部齿):
151
+ * - 齿顶圆 `da = d0 − 2·adn`(比分度圆小)、齿根圆 `dd = d0 + 2·ddn + 2·clearance`(比分度圆大);
152
+ * - 齿厚 `s0 = m·(π/2 + backlash·tan(a0))`(注意是 **+**,外齿是 −);
153
+ * - 渐开线 `r = linspace(ra, rr)`:从齿顶圆(内)走向齿根圆(外),与外齿方向相反;
154
+ * - 齿顶圆弧落在 `rd`(外齿是 `ra`)、齿根圆弧落在 `ra`(外齿是 `rd`)——齿尖朝外、齿根朝内。
155
+ *
156
+ * `ka` / `kd` 固定取 `GEAR_BASE_CONSTANTS`(内部齿在 `e73874c` 不支持 `addendum_coeff`)。
157
+ * 返回的仍是 `SpurGearGeometry`(点集布局一致),可直接喂给 `toothFaceGrids` /
158
+ * `buildToothFaces`,无需为 RingGear 另写齿面构造。
159
+ *
160
+ * @throws 与 Python 同款校验:齿根圆直径 <= 0。
161
+ *
162
+ * @param params 内部齿参数(模数/齿数/宽度/轮缘宽等)
163
+ * @returns 全部派生几何量(与 `SpurGearGeometry` 同构)
164
+ */
165
+ export function ringGearGeometry(params) {
166
+ const ka = GEAR_BASE_CONSTANTS.ka;
167
+ const kd = GEAR_BASE_CONSTANTS.kd;
168
+ // 防御性守卫:RingGear 不接受 addendum_coeff,但 JS 调用方可能多传字段——显式拒绝而不是静默忽略。
169
+ if (params.addendum_coeff !== undefined) {
170
+ throw new Error('RingGear does not support addendum_coeff at this source revision (e73874c).');
171
+ }
172
+ const m = params.module;
173
+ const z = params.teeth_number;
174
+ const a0 = (params.pressure_angle ?? 20.0) * Math.PI / 180;
175
+ const clearance = params.clearance ?? 0.0;
176
+ const backlash = params.backlash ?? 0.0;
177
+ const helixAngle = (params.helix_angle ?? 0.0) * Math.PI / 180;
178
+ const width = params.width;
179
+ // 注意:`rim_width` 只用于实体构造阶段的 rim 半径(`ring_gear.ts`),
180
+ // 齿廓几何量本身与它无关,故此函数不读该字段。
181
+ const d0 = m * z;
182
+ const adn = ka / (z / d0);
183
+ const ddn = kd / (z / d0);
184
+ if (2 * ddn + 2 * clearance >= d0) {
185
+ throw new RangeError('Invalid dedendum or clearance: resulting dedendum circle diameter is negative or zero.');
186
+ }
187
+ const da = d0 - 2 * adn;
188
+ const dd = d0 + 2 * ddn + 2 * clearance;
189
+ const s0 = m * (Math.PI / 2 + backlash * Math.tan(a0)); // 内部齿:+
190
+ const invA0 = Math.tan(a0) - a0;
191
+ const r0 = d0 / 2;
192
+ const ra = da / 2;
193
+ const rd = dd / 2;
194
+ const rb = Math.cos(a0) * d0 / 2;
195
+ const rr = Math.max(rb, rd);
196
+ const tau = Math.PI * 2 / z;
197
+ const twistAngle = helixAngle !== 0
198
+ ? width / (r0 * Math.tan(Math.PI / 2 - helixAngle))
199
+ : 0.0;
200
+ const surfaceSplines = helixAngle !== 0 ? GEAR_BASE_CONSTANTS.surface_splines : 2;
201
+ const n = GEAR_BASE_CONSTANTS.curve_points;
202
+ // 左齿廓渐开线:r 从齿顶圆 ra(内)到 rr(外)
203
+ const r = linspace(ra, rr, n);
204
+ const phi = r.map((ri) => {
205
+ const cosA = (r0 / ri) * Math.cos(a0);
206
+ const a = Math.acos(Math.min(1, Math.max(-1, cosA)));
207
+ const invA = Math.tan(a) - a;
208
+ const s = ri * (s0 / d0 + invA0 - invA);
209
+ return s / ri;
210
+ });
211
+ const t_lflank_pts = r.map((ri, i) => vec3(Math.cos(phi[i]) * ri, Math.sin(phi[i]) * ri, 0));
212
+ // 齿顶圆弧:落在 rd(外齿落在 ra)
213
+ const bTip = linspace(phi[n - 1], -phi[n - 1], n);
214
+ const t_tip_pts = bTip.map((bi) => vec3(Math.cos(bi) * rd, Math.sin(bi) * rd, 0));
215
+ // 右齿廓 = 左齿廓镜像并反向
216
+ const t_rflank_pts = r.map((ri, i) => vec3(Math.cos(-phi[i]) * ri, Math.sin(-phi[i]) * ri, 0)).reverse();
217
+ // 齿根圆弧:过右齿廓末点、齿根圆(ra)中点、左齿廓起点三点定圆
218
+ const rho = tau - phi[0] * 2;
219
+ const p1 = vec3(t_rflank_pts[n - 1].x, t_rflank_pts[n - 1].y, 0);
220
+ const p2 = vec3(Math.cos(-phi[0] - rho / 2) * ra, Math.sin(-phi[0] - rho / 2) * ra, 0);
221
+ const p3 = vec3(Math.cos(-phi[0] - rho) * ra, Math.sin(-phi[0] - rho) * ra, 0);
222
+ const { radius: bcr, center: bcxy } = circle3dBy3points(p1, p2, p3);
223
+ let t1 = Math.atan2(p1.y - bcxy.y, p1.x - bcxy.x);
224
+ let t2 = Math.atan2(p3.y - bcxy.y, p3.x - bcxy.x);
225
+ if (t1 < 0)
226
+ t1 += Math.PI * 2;
227
+ if (t2 < 0)
228
+ t2 += Math.PI * 2;
229
+ const ta = Math.min(t1, t2);
230
+ const tb = Math.max(t1, t2);
231
+ // 注意:内部齿 `ring_gear.py` 用 **递减** 采样 `linspace(t2 + 2π, t1 + 2π)`(与外齿
232
+ // `spur_gear.py` 的递增相反)。必须保持同向,否则大齿数非对称齿根弧整段错开(实测 ~2.6mm)。
233
+ const tRoot = linspace(tb + Math.PI * 2, ta + Math.PI * 2, n);
234
+ const t_root_pts = tRoot.map((t) => vec3(bcxy.x + bcr * Math.cos(t), bcxy.y + bcr * Math.sin(t), 0));
235
+ return {
236
+ m, z, a0, clearance, backlash, helixAngle, width, ka, kd,
237
+ d0, adn, ddn, da, dd, s0, invA0,
238
+ r0, ra, rd, rb, rr, tau,
239
+ twistAngle, surfaceSplines, curvePoints: n,
240
+ t_lflank_pts, t_tip_pts, t_rflank_pts, t_root_pts,
241
+ };
242
+ }
243
+ /**
244
+ * 计算 CrossedHelicalGear(交错轴斜齿轮)的全部几何量
245
+ * (`crossed_helical_gear.py::CrossedHelicalGear.__init__` 的移植)。
246
+ *
247
+ * 与 SpurGear 的**根本差异**是「端面模数」而非「法向模数」:
248
+ * - 端面压力角 `at0 = arctan(a0 / cos(helix))`——⚠️ 源码里 `a0` 是**弧度值**直接除以
249
+ * `cos(helix)`(并非 `tan(a0)`),这是 cq_gears 的原始写法,必须逐字复刻,
250
+ * 否则 `rb` 会偏(实测 case30 rb 由 20.2898 偏到 20.1227);
251
+ * - 端面模数 `mt = m / cos(helix)`,分度圆直径 `d0 = mt·z`(SpurGear 是 `m·z`);
252
+ * - 分度圆齿厚 `s0 = r0·π/z`(SpurGear 是 `m·(π/2 − backlash·tan(a0))`);
253
+ * - 基圆半径 `rb = cos(at0)·r0`。
254
+ *
255
+ * 其余(渐开线采样、齿顶/齿根圆弧、扭转角、齿面行数)与 SpurGear 同构,返回的仍是
256
+ * `SpurGearGeometry`,可直接喂给 `toothFaceGrids` / `buildToothFaces`。
257
+ *
258
+ * 源码此处**没有**齿根圆直径校验(与 SpurGear 不同),故不复刻该抛错。
259
+ *
260
+ * @param params 交错轴斜齿轮参数(模数/齿数/宽度/螺旋角等)
261
+ * @returns 全部派生几何量(与 `SpurGearGeometry` 同构)
262
+ */
263
+ export function crossedHelicalGearGeometry(params) {
264
+ const ka = GEAR_BASE_CONSTANTS.ka;
265
+ const kd = GEAR_BASE_CONSTANTS.kd;
266
+ const m = params.module;
267
+ const z = params.teeth_number;
268
+ const a0 = (params.pressure_angle ?? 20.0) * Math.PI / 180;
269
+ const clearance = params.clearance ?? 0.0;
270
+ const backlash = params.backlash ?? 0.0;
271
+ const helixAngle = (params.helix_angle ?? 0.0) * Math.PI / 180;
272
+ const width = params.width;
273
+ // ⚠️ 源码写法:a0(弧度)直接除以 cos(helix),不是 tan(a0)。
274
+ const at0 = Math.atan(a0 / Math.cos(helixAngle));
275
+ const mt = m / Math.cos(helixAngle);
276
+ const d0 = mt * z;
277
+ const adn = ka / (z / d0);
278
+ const ddn = kd / (z / d0);
279
+ const da = d0 + 2 * adn;
280
+ const dd = d0 - 2 * ddn - 2 * clearance;
281
+ const invA0 = Math.tan(at0) - at0;
282
+ const r0 = d0 / 2;
283
+ const ra = da / 2;
284
+ const rd = dd / 2;
285
+ const rb = Math.cos(at0) * r0;
286
+ const rr = Math.max(rb, rd);
287
+ const tau = Math.PI * 2 / z;
288
+ const s0 = r0 * Math.PI / z;
289
+ const twistAngle = helixAngle !== 0
290
+ ? width / (r0 * Math.tan(Math.PI / 2 - helixAngle))
291
+ : 0.0;
292
+ const surfaceSplines = helixAngle !== 0 ? GEAR_BASE_CONSTANTS.surface_splines : 2;
293
+ const n = GEAR_BASE_CONSTANTS.curve_points;
294
+ // 左齿廓渐开线(用端面压力角 at0)
295
+ const r = linspace(rr, ra, n);
296
+ const phi = r.map((ri) => {
297
+ const cosA = (r0 / ri) * Math.cos(at0);
298
+ const a = Math.acos(Math.min(1, Math.max(-1, cosA)));
299
+ const invA = Math.tan(a) - a;
300
+ const s = ri * (s0 / d0 + invA0 - invA);
301
+ return s / ri;
302
+ });
303
+ const t_lflank_pts = r.map((ri, i) => vec3(Math.cos(phi[i]) * ri, Math.sin(phi[i]) * ri, 0));
304
+ // 齿顶圆弧
305
+ const bTip = linspace(phi[n - 1], -phi[n - 1], n);
306
+ const t_tip_pts = bTip.map((bi) => vec3(Math.cos(bi) * ra, Math.sin(bi) * ra, 0));
307
+ // 右齿廓 = 左齿廓镜像并反向
308
+ const t_rflank_pts = r.map((ri, i) => vec3(Math.cos(-phi[i]) * ri, Math.sin(-phi[i]) * ri, 0)).reverse();
309
+ // 齿根圆弧(与外齿一致:p2 在 rd、p3 在 rr,递增采样)
310
+ const rho = tau - phi[0] * 2;
311
+ const p1 = vec3(t_rflank_pts[n - 1].x, t_rflank_pts[n - 1].y, 0);
312
+ const p2 = vec3(Math.cos(-phi[0] - rho / 2) * rd, Math.sin(-phi[0] - rho / 2) * rd, 0);
313
+ const p3 = vec3(Math.cos(-phi[0] - rho) * rr, Math.sin(-phi[0] - rho) * rr, 0);
314
+ const { radius: bcr, center: bcxy } = circle3dBy3points(p1, p2, p3);
315
+ let t1 = Math.atan2(p1.y - bcxy.y, p1.x - bcxy.x);
316
+ let t2 = Math.atan2(p3.y - bcxy.y, p3.x - bcxy.x);
317
+ if (t1 < 0)
318
+ t1 += Math.PI * 2;
319
+ if (t2 < 0)
320
+ t2 += Math.PI * 2;
321
+ const ta = Math.min(t1, t2);
322
+ const tb = Math.max(t1, t2);
323
+ const tRoot = linspace(ta + Math.PI * 2, tb + Math.PI * 2, n);
324
+ const t_root_pts = tRoot.map((t) => vec3(bcxy.x + bcr * Math.cos(t), bcxy.y + bcr * Math.sin(t), 0));
325
+ return {
326
+ m, z, a0, clearance, backlash, helixAngle, width, ka, kd,
327
+ d0, adn, ddn, da, dd, s0, invA0,
328
+ r0, ra, rd, rb, rr, tau,
329
+ twistAngle, surfaceSplines, curvePoints: n,
330
+ t_lflank_pts, t_tip_pts, t_rflank_pts, t_root_pts,
331
+ };
332
+ }
333
+ /** `BevelGear` 覆写 `GearBase` 的类常量(`bevel_gear.py:32`;`helix_angle = 0` 时被改成 2)。 */
334
+ export const BEVEL_SURFACE_SPLINES = 12;
335
+ /**
336
+ * 计算 BevelGear(锥齿轮)的全部几何量(`BevelGear.__init__` 的移植)。
337
+ *
338
+ * 与 SpurGear 的根本差异:齿廓画在**单位球面**上(球面渐开线),
339
+ * 齿面是「球面齿廓 × 锥距」的直纹面;`gamma_b/_f/_r` 分别是基/面/根锥角,
340
+ * 高度一律用 `r·cos(gamma)` 折算到齿轮轴(+Z)上。
341
+ *
342
+ * @throws `gs_r <= face_width` 时抛 RangeError(Python 同款 assert);
343
+ * `twist_angle` 为 NaN 时抛 Error(Python 同款 assert)。
344
+ *
345
+ * @param params 锥齿轮参数(模数/齿数/锥角/齿宽等)
346
+ * @returns 全部派生几何量
347
+ */
348
+ export function bevelGearGeometry(params) {
349
+ const ka = GEAR_BASE_CONSTANTS.ka;
350
+ const kd = GEAR_BASE_CONSTANTS.kd;
351
+ const m = params.module;
352
+ const z = params.teeth_number;
353
+ const a0 = ((params.pressure_angle ?? 20.0) * Math.PI) / 180.0;
354
+ const clearance = params.clearance ?? 0.0;
355
+ const backlash = params.backlash ?? 0.0;
356
+ const helixAngle = ((params.helix_angle ?? 0.0) * Math.PI) / 180.0;
357
+ const faceWidth = params.face_width;
358
+ const gammaP = (params.cone_angle * Math.PI) / 180.0;
359
+ const rp = (m * z) / 2.0;
360
+ const gsR = rp / Math.sin(gammaP);
361
+ if (!(gsR > faceWidth)) {
362
+ throw new RangeError(`face_width value is too big, it should be < ${gsR.toFixed(3)}`);
363
+ }
364
+ const gammaB = Math.asin(Math.cos(a0) * Math.sin(gammaP));
365
+ const gammaF = gammaP + Math.atan((ka * m) / gsR);
366
+ const gammaR = gammaP - Math.atan((kd * m) / gsR);
367
+ const tau = (Math.PI * 2.0) / z;
368
+ let twistAngle;
369
+ let surfaceSplines;
370
+ if (helixAngle !== 0.0) {
371
+ // 扭转(torsion)角
372
+ const beta = Math.atan((faceWidth * Math.tan(helixAngle)) / (2.0 * gsR - faceWidth));
373
+ twistAngle = Math.asin((gsR / rp) * Math.sin(beta)) * 2.0;
374
+ surfaceSplines = BEVEL_SURFACE_SPLINES;
375
+ }
376
+ else {
377
+ surfaceSplines = 2;
378
+ twistAngle = 0.0;
379
+ }
380
+ if (Number.isNaN(twistAngle)) {
381
+ throw new Error('Twist angle is NaN');
382
+ }
383
+ const coneH = Math.cos(gammaR) * gsR;
384
+ const phiR = sInv(gammaB, gammaP);
385
+ const mpTheta = Math.PI / z + 2.0 * phiR;
386
+ const n = GEAR_BASE_CONSTANTS.curve_points;
387
+ // 左齿廓:球面渐开线
388
+ const gammaTr = Math.max(gammaB, gammaR);
389
+ const gamma = linspace(gammaTr, gammaF, n);
390
+ const theta = gamma.map((g) => sInv(gammaB, g) + backlash / (m * z));
391
+ const t_lflank_pts = gamma.map((g, i) => sphereToCartesian(1.0, g, theta[i]));
392
+ // 齿顶圆弧(恒在 gamma_f)
393
+ const thetaTip = linspace(theta[n - 1], mpTheta - theta[n - 1], n);
394
+ const t_tip_pts = thetaTip.map((t) => sphereToCartesian(1.0, gammaF, t));
395
+ // 右齿廓 = 左齿廓的镜像(方位角取 mp_theta − theta)并反向
396
+ const t_rflank_pts = [];
397
+ for (let i = n - 1; i >= 0; i--) {
398
+ t_rflank_pts.push(sphereToCartesian(1.0, gamma[i], mpTheta - theta[i]));
399
+ }
400
+ // 齿根圆弧
401
+ let t_root_pts;
402
+ if (gammaR < gammaB) {
403
+ // 根锥在基锥之内:过右齿廓末点、基锥上的齿槽中点、根锥上的齿槽中点三点定圆
404
+ const p1 = t_rflank_pts[n - 1];
405
+ const p2 = sphereToCartesian(1.0, gammaB, theta[0] + tau);
406
+ const p3 = sphereToCartesian(1.0, gammaR, (tau + mpTheta) / 2.0);
407
+ const { center: rcc } = circle3dBy3points(p1, p2, p3);
408
+ const rccGamma = Math.acos(dot(p3, rcc) / (norm(p3) * norm(rcc)));
409
+ const p1p3 = angleBetween(rcc, p1, p3);
410
+ const aStart = (Math.PI - p1p3 * 2.0) / 2.0;
411
+ const aEnd = -aStart + Math.PI;
412
+ t_root_pts = sArc(1.0, gammaR + rccGamma, (tau + mpTheta) / 2.0, rccGamma, Math.PI / 2.0 + aStart, Math.PI / 2.0 + aEnd, n);
413
+ }
414
+ else {
415
+ const rTheta = linspace(mpTheta - theta[0], theta[0] + tau, n);
416
+ t_root_pts = rTheta.map((t) => sphereToCartesian(1.0, gammaTr, t));
417
+ }
418
+ return {
419
+ m, z, a0, clearance, backlash, helixAngle, faceWidth, ka, kd,
420
+ rp, gsR, gammaP, gammaB, gammaF, gammaR, tau, coneH, mpTheta,
421
+ twistAngle, surfaceSplines, curvePoints: n,
422
+ t_lflank_pts, t_tip_pts, t_rflank_pts, t_root_pts,
423
+ };
424
+ }
425
+ /** 四段齿廓的名字(`_build_tooth_faces` 的迭代顺序,勿改)。 */
426
+ export const TOOTH_SEGMENTS = ['lflank', 'tip', 'rflank', 'root'];
427
+ /**
428
+ * 取某段齿廓的基础点集(z=0 平面上的轮廓点,与 Python 内部数组逐点一致)。
429
+ *
430
+ * @param g 已计算的齿轮几何量
431
+ * @param seg 齿廓段名
432
+ * @returns 该段的点集
433
+ */
434
+ export function segmentPoints(g, seg) {
435
+ switch (seg) {
436
+ case 'lflank': return g.t_lflank_pts;
437
+ case 'tip': return g.t_tip_pts;
438
+ case 'rflank': return g.t_rflank_pts;
439
+ case 'root': return g.t_root_pts;
440
+ }
441
+ }
442
+ /**
443
+ * 复现 `SpurGear._build_tooth_faces` 的点阵构造。
444
+ *
445
+ * 注意扭转用 `pts @ rotation_matrix(...)`(行向量左乘 ⇒ 实际转 −a,见 `math.ts` 顶部说明),
446
+ * 与 Python 逐字一致。
447
+ *
448
+ * @param twistA 起始扭转角(弧度)
449
+ * @param twistB 终止扭转角(弧度)
450
+ * @param zPos 起始 z
451
+ * @param width 该段宽度
452
+ * @param g 齿轮几何量
453
+ * @returns 4 段齿廓各自的 row×col 点阵
454
+ */
455
+ export function toothFaceGrids(g, twistA = 0.0, twistB = g.twistAngle, zPos = 0.0, width = g.width) {
456
+ const twistSpan = Math.abs(g.twistAngle);
457
+ // Section density: cq uses 5 rows per π of twist (surface_splines=5). At
458
+ // large twist angles the 43° row spacing makes B-spline loft overshoot the
459
+ // tip circle (measured: 5 rows → +2.7mm bbox error on a -42° helix, 20 rows
460
+ // → 0.0mm). Densify to 12 rows per π so overshoot stays < 0.05mm while
461
+ // straight teeth (twistSpan=0) keep their 2-row extrusion.
462
+ const surfSplines = Math.max(g.surfaceSplines, Math.ceil((twistSpan / Math.PI) * 12));
463
+ const angles = linspace(twistA, twistB, surfSplines);
464
+ const zs = linspace(zPos, zPos + width, surfSplines);
465
+ return TOOTH_SEGMENTS.map((seg) => {
466
+ const base = segmentPoints(g, seg);
467
+ const points = [];
468
+ for (let r = 0; r < surfSplines; r++) {
469
+ const z = zs[r];
470
+ const row = base.map((p) => vec3(p.x, p.y, z));
471
+ points.push(rotateRows(row, vec3(0, 0, 1), angles[r]));
472
+ }
473
+ return { segment: seg, rows: surfSplines, cols: base.length, points };
474
+ });
475
+ }
476
+ /**
477
+ * 按 cq_gears 类名分派到对应的齿廓几何计算(manifest 的 `class` 字段直接驱动)。
478
+ *
479
+ * `HerringboneGear` 继承 `SpurGear`、`HerringboneRingGear` 继承 `RingGear`,
480
+ * 其**齿廓数学**与父类相同(人字形的差异只体现在建面阶段的 `_build_tooth_faces` 覆盖),
481
+ * 故分派到同一函数。
482
+ *
483
+ * @throws 未支持的类名直接抛错(不静默退回 SpurGear,避免掩盖分派错误)
484
+ *
485
+ * @param className cq_gears 类名(`SpurGear` / `RingGear` / `CrossedHelicalGear` …)
486
+ * @param args 该类构造函数的参数(逐字取自 manifest)
487
+ * @returns 全部派生几何量
488
+ */
489
+ export function gearGeometryForClass(className, args) {
490
+ switch (className) {
491
+ case 'SpurGear':
492
+ case 'HerringboneGear':
493
+ return spurGearGeometry(args);
494
+ case 'HyperbolicGear':
495
+ return hyperbolicGearGeometry(args);
496
+ case 'RingGear':
497
+ case 'HerringboneRingGear':
498
+ return ringGearGeometry(args);
499
+ case 'CrossedHelicalGear':
500
+ return crossedHelicalGearGeometry(args);
501
+ case 'Worm':
502
+ return wormGeometry(args);
503
+ case 'BevelGear':
504
+ return bevelGearGeometry(args);
505
+ default:
506
+ throw new Error(`gearGeometryForClass: unsupported gear class '${className}'`);
507
+ }
508
+ }
509
+ /**
510
+ * 计算 RackGear 的全部几何量(`RackGear.__init__` 的移植)。
511
+ *
512
+ * 齿条齿廓是**梯形直线段**(每段只有 2 个点),沿 X 以 π·m 为周期重复;
513
+ * 齿深方向是 +y(la 在上、ld 在下),实体沿 Z 挤出 width。
514
+ * `ka`/`kd` 沿用 GearBase 类常量(RackGear 构造函数不接受 addendum/dedendum 覆盖)。
515
+ *
516
+ * @param params 齿条参数(模数/长度/宽/高/螺旋角等)
517
+ * @returns 全部派生几何量
518
+ */
519
+ export function rackGearGeometry(params) {
520
+ const ka = GEAR_BASE_CONSTANTS.ka;
521
+ const kd = GEAR_BASE_CONSTANTS.kd;
522
+ const m = params.module;
523
+ const a0 = ((params.pressure_angle ?? 20.0) * Math.PI) / 180.0;
524
+ const clearance = params.clearance ?? 0.0;
525
+ const backlash = params.backlash ?? 0.0;
526
+ const helixAngle = ((params.helix_angle ?? 0.0) * Math.PI) / 180.0;
527
+ const width = params.width;
528
+ const length = params.length;
529
+ const height = params.height;
530
+ const la = ka * m;
531
+ const ld = -(kd * m + clearance);
532
+ const s0 = (m * (Math.PI / 2.0 - backlash * Math.tan(a0))) / 2.0;
533
+ const p1x = Math.tan(a0) * Math.abs(ld);
534
+ const p1p2 = (Math.abs(la) + Math.abs(ld)) / Math.cos(a0);
535
+ const p1 = vec3(-s0 - p1x, ld, 0.0);
536
+ const p2 = vec3(Math.sin(a0) * p1p2 + p1.x, Math.cos(a0) * p1p2 + p1.y, 0.0);
537
+ const p3 = vec3(-p2.x, p2.y, 0.0);
538
+ const p4 = vec3(-p1.x, p1.y, 0.0);
539
+ const p5 = vec3(p4.x + (Math.PI * m - p4.x * 2.0), p4.y, 0.0);
540
+ const toothHeight = Math.abs(la) + Math.abs(ld);
541
+ const z = Math.ceil(length / (Math.PI * m));
542
+ return {
543
+ m, a0, clearance, backlash, helixAngle, width, length, height,
544
+ la, ld, s0, toothHeight, z,
545
+ t_lflank_pts: [p1, p2],
546
+ t_tip_pts: [p2, p3],
547
+ t_rflank_pts: [p3, p4],
548
+ t_root_pts: [p4, p5],
549
+ };
550
+ }
551
+ /**
552
+ * 计算 Worm 的全部几何量(`Worm.__init__` 的移植)。
553
+ *
554
+ * 齿廓公式与 `RackGear.__init__` **逐字同构**(梯形直线段 p1–p5)——cq 里 Worm
555
+ * 的齿廓本来就是「展开到分度圆柱切平面上的齿条」。差异只在分度半径:
556
+ * `d0 = n_threads·m / |tan(lead_angle)|`,以及半径向的 ra/rd。
557
+ *
558
+ * @param params 蜗杆参数(模数/导程角/头数/长度等)
559
+ * @returns 全部派生几何量
560
+ */
561
+ export function wormGeometry(params) {
562
+ const ka = GEAR_BASE_CONSTANTS.ka;
563
+ const kd = GEAR_BASE_CONSTANTS.kd;
564
+ const m = params.module;
565
+ const a0 = ((params.pressure_angle ?? 20.0) * Math.PI) / 180.0;
566
+ const clearance = params.clearance ?? 0.0;
567
+ const backlash = params.backlash ?? 0.0;
568
+ const leadAngle = (params.lead_angle * Math.PI) / 180.0;
569
+ const length = params.length;
570
+ const nThreads = params.n_threads;
571
+ const d0 = (nThreads * m) / Math.abs(Math.tan(leadAngle));
572
+ const r0 = d0 / 2.0;
573
+ const adn = ka * m;
574
+ const ddn = kd * m;
575
+ const la = adn;
576
+ const ld = -(ddn + clearance);
577
+ const ra = r0 + adn;
578
+ const rd = r0 - ddn;
579
+ const s0 = (m * (Math.PI / 2.0 - backlash * Math.tan(a0))) / 2.0;
580
+ const p1x = Math.tan(a0) * Math.abs(ld);
581
+ const p1p2 = (Math.abs(la) + Math.abs(ld)) / Math.cos(a0);
582
+ const p1 = vec3(-s0 - p1x, ld, 0.0);
583
+ const p2 = vec3(Math.sin(a0) * p1p2 + p1.x, Math.cos(a0) * p1p2 + p1.y, 0.0);
584
+ const p3 = vec3(-p2.x, p2.y, 0.0);
585
+ const p4 = vec3(-p1.x, p1.y, 0.0);
586
+ const p5 = vec3(p4.x + (Math.PI * m - p4.x * 2.0), p4.y, 0.0);
587
+ const toothHeight = Math.abs(la) + Math.abs(ld);
588
+ return {
589
+ m, a0, clearance, backlash, leadAngle, length, nThreads,
590
+ r0, ra, rd, la, ld, s0, toothHeight,
591
+ t_lflank_pts: [p1, p2],
592
+ t_tip_pts: [p2, p3],
593
+ t_rflank_pts: [p3, p4],
594
+ t_root_pts: [p4, p5],
595
+ };
596
+ }
597
+ //# sourceMappingURL=profile.js.map