1use geop_core_math::{geop_error::GeopResult, primitives::FrameAxis};
32use geop_ops_extrude_revolve::{Extent, Extents};
33use geop_ops_model::Combine;
34use geop_ops_model::datums::{origin_axis, origin_plane};
35use geop_ops_model::parameters::{Parameter, ParameterKind, Row};
36use geop_ops_model::spacing::Spacing;
37use geop_ops_pattern::{CircularPatternArgs, Seeds, TurnAxis, body};
38
39use super::{
40 StandardPart,
41 drawing::Drawing,
42 involute::{self, DEGREE, FEWEST_TEETH},
43 steps::{
44 Around, axis_datum, col, datum_plane, extrude, family, offset_datum, outline_plane,
45 plane_datum, profile_plane, revolve, size,
46 },
47 tables, titled,
48};
49
50pub const TEETH: &str = "teeth";
52
53pub const MOST_TEETH: usize = 120;
55
56const GAP_COLUMNS: [&str; 3 + 2 * (DEGREE + 1)] = [
60 "z", "fx", "fy", "c0x", "c0y", "c1x", "c1y", "c2x", "c2y", "c3x", "c3y", "c4x", "c4y", "c5x",
61 "c5y", "c6x", "c6y", "c7x", "c7y", "c8x", "c8y", "c9x", "c9y",
62];
63
64fn teeth() -> GeopResult<Parameter> {
67 let rows = (FEWEST_TEETH..=MOST_TEETH)
68 .map(|z| {
69 let gap = involute::gap(z)?;
70 let mut values = vec![z as f64];
71 values.extend(gap.foot);
72 values.extend(gap.flank.iter().flatten());
73 Ok(Row {
74 name: format!("z{z}"),
75 values,
76 })
77 })
78 .collect::<GeopResult<_>>()?;
79 Ok(Parameter {
80 name: TEETH.into(),
81 kind: ParameterKind::Table {
82 columns: GAP_COLUMNS.iter().map(|c| c.to_string()).collect(),
83 rows,
84 selected: "z20".into(),
85 },
86 })
87}
88
89pub fn spur_gear(file: &'static str) -> GeopResult<StandardPart> {
95 let mut program = family(
96 "Steel",
97 Some("#9aa0a8"),
98 vec![
99 size(tables::gear_modules()),
100 teeth()?,
101 Parameter::number("width", &format!("8 * {}", col("m")), 1.0, 100.0),
102 Parameter::number(
103 "bore",
104 &format!("max(2, round({} * {TEETH}.z / 4))", col("m")),
105 1.0,
106 100.0,
107 ),
108 ],
109 );
110 let m = col("m");
111 let z = format!("{TEETH}.z");
112 let mut blank = Drawing::new(&program)?;
116 let centre = blank.origin();
117 blank.circle(centre, &format!("{m} * ({z} + 2)"))?;
118 blank.circle(centre, "bore")?;
119 extrude(
120 &mut program,
121 "blank",
122 "blank_outline",
123 (blank, outline_plane()),
124 Extents::blind("width"),
125 Combine::NewBody,
126 );
127
128 let mut gap = Drawing::new(&program)?;
139 let at = |column: &str, sign: &str| format!("{sign}{m} * {TEETH}.{column}");
140 let lower =
141 |d: &mut Drawing, k: &str| d.point(at(&format!("{k}x"), ""), at(&format!("{k}y"), ""));
142 let upper =
143 |d: &mut Drawing, k: &str| d.point(at(&format!("{k}x"), ""), at(&format!("{k}y"), "-"));
144 let foot = lower(&mut gap, "f")?;
145 let flank = (0..=DEGREE)
146 .map(|k| lower(&mut gap, &format!("c{k}")))
147 .collect::<GeopResult<Vec<_>>>()?;
148 let upper_flank = (0..=DEGREE)
149 .rev()
150 .map(|k| upper(&mut gap, &format!("c{k}")))
151 .collect::<GeopResult<Vec<_>>>()?;
152 let upper_foot = upper(&mut gap, "f")?;
153 gap.line(foot, flank[0]);
154 gap.bezier(flank.clone());
155 let past = gap.point(format!("{m} * ({z} / 2 + 2.5)"), "0")?;
156 gap.line(flank[DEGREE], past);
157 gap.line(past, upper_flank[0]);
158 gap.bezier(upper_flank.clone());
159 gap.line(upper_flank[DEGREE], upper_foot);
160 gap.line(upper_foot, foot);
161 plane_datum(&mut program, "middle", "width / 2");
162 extrude(
163 &mut program,
164 "gap",
165 "gap_outline",
166 (gap, datum_plane("middle")),
167 Extents {
168 symmetric: true,
169 ..Extents::blind(format!("width + 2 * {m}"))
170 },
171 Combine::NewBody,
172 );
173 program.push(
174 "teeth",
175 CircularPatternArgs {
176 seeds: Seeds::bodies(vec![body("extrude(gap)")], Combine::cut("extrude(blank)")),
177 axis: Some(TurnAxis::Datum(origin_axis(FrameAxis::Z))),
178 reversed: false,
179 count: z.as_str().into(),
180 angle: Spacing::extent(360.0),
181 },
182 );
183
184 let mut pitch = Drawing::new(&program)?;
185 let centre = pitch.origin();
186 pitch.circle(centre, &format!("{m} * {z}"))?;
187 program.push("pitch", pitch.on(outline_plane()));
188 axis_datum(&mut program);
189 plane_datum(&mut program, "side", "0");
190 Ok(StandardPart {
191 file,
192 base: "side",
193 program: titled(
194 program,
195 "Involute spur gear, 20° pressure angle",
196 "Spur gear",
197 ),
198 threaded: Vec::new(),
199 })
200}
201
202pub const RACK_TEETH: usize = 30;
204
205pub fn rack(file: &'static str) -> GeopResult<StandardPart> {
219 let mut program = family(
220 "Steel",
221 Some("#9aa0a8"),
222 vec![
223 size(tables::racks()),
224 Parameter::number("teeth", "20", 1.0, RACK_TEETH as f64),
225 ],
226 );
227 let (m, b, h) = (col("m"), col("b"), col("h"));
228 let half = |height: &str| format!("pi * {m} / 4 - {height} * tan(20)");
230 let (root, tip) = (format!("-1.25 * {m}"), m.clone());
231 let along = |k: usize, sign: &str, height: &str| {
232 format!("{}.5 * pi * {m} {sign} ({})", k, half(height))
233 };
234 let mut corners = vec![
235 [format!("-{h}"), "0".to_string()],
236 [root.clone(), "0".into()],
237 ];
238 for k in 0..=RACK_TEETH {
239 corners.push([root.clone(), along(k, "-", &root)]);
240 corners.push([tip.clone(), along(k, "-", &tip)]);
241 corners.push([tip.clone(), along(k, "+", &tip)]);
242 corners.push([root.clone(), along(k, "+", &root)]);
243 }
244 let end = format!("{} * pi * {m}", RACK_TEETH + 1);
245 corners.push([root.clone(), end.clone()]);
246 corners.push([format!("-{h}"), end]);
247 let mut profile = Drawing::new(&program)?;
248 profile.polygon(&corners)?;
249 let across = |length: String| Extents {
250 symmetric: true,
251 ..Extents::blind(length)
252 };
253 extrude(
254 &mut program,
255 "teeth",
256 "profile",
257 (profile, profile_plane()),
258 across(b.clone()),
259 Combine::NewBody,
260 );
261 let mut length = Drawing::new(&program)?;
262 length.polygon(&[
263 [format!("-{h} - {m}"), format!("-{m}")],
264 [format!("2 * {m}"), format!("-{m}")],
265 [format!("2 * {m}"), format!("teeth * pi * {m}")],
266 [format!("-{h} - {m}"), format!("teeth * pi * {m}")],
267 ])?;
268 extrude(
269 &mut program,
270 "rack",
271 "length",
272 (length, profile_plane()),
273 across(format!("{b} + 2 * {m}")),
274 Combine::intersect("extrude(teeth)"),
275 );
276 axis_datum(&mut program);
277 let xz = origin_plane(FrameAxis::Y);
278 offset_datum(&mut program, "pitch", xz.name(), "0");
279 offset_datum(&mut program, "back", xz.name(), &format!("-{h}"));
280 plane_datum(&mut program, "end", "0");
281 Ok(StandardPart {
282 file,
283 base: "back",
284 program: titled(program, "Gear rack, 20° pressure angle", "Gear rack"),
285 threaded: Vec::new(),
286 })
287}
288
289const GT2_GROOVE: f64 = 0.555;
291
292const GT2_DEPTH: f64 = 0.76;
294
295fn gt2_pulley(z: usize, file: &'static str) -> GeopResult<StandardPart> {
309 let mut program = family(
310 "Aluminium 6061",
311 Some("#c8ccd2"),
312 vec![size(tables::gt2_pulleys(z))],
313 );
314 let (d, dh, lh) = (col("d"), col("dh"), col("lh"));
315 let (t, w) = (1.0, 7.0);
316 let pitch = 2.0 * z as f64 / std::f64::consts::PI;
317 let outside = (pitch - 0.508) / 2.0;
318 let flange = format!("{}", outside + 2.0);
319 let core = format!("{}", outside - 1.0);
320 let (bore, hub) = (format!("{d} / 2"), format!("{dh} / 2"));
321 let (inner_face, outer_face, end) = (
322 format!("{lh} + {t}"),
323 format!("{lh} + {}", t + w),
324 format!("{lh} + {}", 2.0 * t + w),
325 );
326 let zero = || "0".to_string();
327 let mut profile = Drawing::new(&program)?;
328 profile.polygon(&[
329 [bore.clone(), zero()],
330 [hub.clone(), zero()],
331 [hub, lh.clone()],
332 [flange.clone(), lh.clone()],
333 [flange.clone(), inner_face.clone()],
334 [core.clone(), inner_face],
335 [core, outer_face.clone()],
336 [flange.clone(), outer_face],
337 [flange, end.clone()],
338 [bore, end],
339 ])?;
340 revolve(
341 &mut program,
342 "body",
343 "profile",
344 profile,
345 Around::ZAxis,
346 Combine::NewBody,
347 );
348
349 plane_datum(&mut program, "ring_floor", &format!("{lh} + {}", t / 2.0));
354 let centre = outside - GT2_DEPTH + GT2_GROOVE;
355 let half =
356 ((outside.powi(2) + centre.powi(2) - GT2_GROOVE.powi(2)) / (2.0 * outside * centre)).acos();
357 let mut ring = Drawing::new(&program)?;
358 let at = |r: f64, a: f64| [format!("{}", r * a.cos()), format!("{}", r * a.sin())];
359 let mut groove_ends = Vec::new();
360 for k in 0..z {
361 let a = 2.0 * std::f64::consts::PI * (k as f64 + 0.5) / z as f64;
364 let [x0, y0] = at(outside, a - half);
365 let [xb, yb] = at(outside - GT2_DEPTH, a);
366 let [x1, y1] = at(outside, a + half);
367 groove_ends.push([
368 ring.point(x0, y0)?,
369 ring.point(xb, yb)?,
370 ring.point(x1, y1)?,
371 ]);
372 }
373 let (groove, land) = (format!("{GT2_GROOVE}"), format!("{outside}"));
374 for k in 0..z {
375 let [start, bottom, end] = groove_ends[k];
376 ring.arc(start, bottom, &groove, false)?;
377 ring.arc(bottom, end, &groove, false)?;
378 ring.arc(end, groove_ends[(k + 1) % z][0], &land, true)?;
379 }
380 let centre_point = ring.origin();
381 ring.circle(centre_point, &format!("{}", 2.0 * (outside - 1.5)))?;
382 extrude(
383 &mut program,
384 "ring",
385 "ring_outline",
386 (ring, datum_plane("ring_floor")),
387 Extents::blind(t + w),
388 Combine::join("revolve(body)"),
389 );
390
391 axis_datum(&mut program);
392 plane_datum(&mut program, "end", "0");
393 plane_datum(&mut program, "belt", &format!("{lh} + {}", t + w / 2.0));
394 let mut circle = Drawing::new(&program)?;
395 let centre_point = circle.origin();
396 circle.circle(centre_point, &format!("{pitch}"))?;
397 program.push("pitch", circle.on(datum_plane("belt")));
398 Ok(StandardPart {
399 file,
400 base: "end",
401 program: titled(program, "GT2 timing pulley, 6 mm belt", "GT2 pulley"),
402 threaded: Vec::new(),
403 })
404}
405
406pub fn gt2_pulley_16(file: &'static str) -> GeopResult<StandardPart> {
407 gt2_pulley(16, file)
408}
409
410pub fn gt2_pulley_20(file: &'static str) -> GeopResult<StandardPart> {
411 gt2_pulley(20, file)
412}
413
414pub fn gt2_pulley_36(file: &'static str) -> GeopResult<StandardPart> {
415 gt2_pulley(36, file)
416}
417
418pub fn shaft_collar(file: &'static str) -> GeopResult<StandardPart> {
424 let mut program = family(
425 "Steel",
426 Some("#8e949c"),
427 vec![size(tables::shaft_collars())],
428 );
429 let (d, big, b) = (col("d"), col("D"), col("b"));
430 let mut profile = Drawing::new(&program)?;
431 profile.polygon(&[
432 [format!("{d} / 2"), "0".into()],
433 [format!("{big} / 2"), "0".into()],
434 [format!("{big} / 2"), b.clone()],
435 [format!("{d} / 2"), b.clone()],
436 ])?;
437 revolve(
438 &mut program,
439 "ring",
440 "profile",
441 profile,
442 Around::ZAxis,
443 Combine::NewBody,
444 );
445 let mut hole = Drawing::new(&program)?;
448 let centre = hole.point("0", format!("{b} / 2"))?;
449 hole.circle(centre, &col("ds"))?;
450 extrude(
451 &mut program,
452 "set_screw",
453 "set_screw_hole",
454 (hole, profile_plane()),
455 Extents {
456 side1: Extent::ThroughAll,
457 symmetric: false,
458 side2: None,
459 reversed: false,
460 },
461 Combine::cut("revolve(ring)"),
462 );
463 axis_datum(&mut program);
464 plane_datum(&mut program, "base", "0");
465 Ok(StandardPart {
466 file,
467 base: "base",
468 program: titled(
469 program,
470 "Shaft collar with set screw, DIN 705 A",
471 "DIN 705 A",
472 ),
473 threaded: Vec::new(),
474 })
475}
476
477pub fn flange_coupling(file: &'static str) -> GeopResult<StandardPart> {
483 let mut program = family(
484 "Aluminium 6061",
485 Some("#c8ccd2"),
486 vec![size(tables::flange_couplings())],
487 );
488 let (d1, d2, big, flange, l, f) =
489 (col("d1"), col("d2"), col("D"), col("F"), col("L"), col("f"));
490 let (hub, rim) = (format!("{big} / 2"), format!("{flange} / 2"));
491 let (low, mid, high) = (
492 format!("({l} - {f}) / 2"),
493 format!("{l} / 2"),
494 format!("({l} + {f}) / 2"),
495 );
496 let mut profile = Drawing::new(&program)?;
497 profile.polygon(&[
498 [format!("{d1} / 2"), "0".into()],
499 [hub.clone(), "0".into()],
500 [hub.clone(), low.clone()],
501 [rim.clone(), low],
502 [rim, high.clone()],
503 [hub.clone(), high],
504 [hub, l.clone()],
505 [format!("{d2} / 2"), l.clone()],
506 [format!("{d2} / 2"), mid.clone()],
507 [format!("{d1} / 2"), mid],
508 ])?;
509 revolve(
510 &mut program,
511 "coupling",
512 "profile",
513 profile,
514 Around::ZAxis,
515 Combine::NewBody,
516 );
517 axis_datum(&mut program);
518 plane_datum(&mut program, "end", "0");
519 plane_datum(&mut program, "far_end", &l);
520 Ok(StandardPart {
521 file,
522 base: "end",
523 program: titled(program, "Rigid flange shaft coupling", "Flange coupling"),
524 threaded: Vec::new(),
525 })
526}