scad_tree/
metric_thread.rs

1// MIT License
2//
3// Copyright (c) 2023 Michael H. Phillips
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22//
23
24use {
25    crate::prelude::*,
26    scad_tree_math::{dcos, dsin},
27    std::collections::HashMap,
28};
29
30fn lerp(start: Pt3, end: Pt3, n_steps: usize, step: usize) -> Pt3 {
31    start + ((end - start) / n_steps as f64 * step as f64)
32}
33
34/// Returns the dictionary for the given M size.
35///
36/// This function always returns a valid
37/// dictionary by giving the next smallest size if the requested size is not found. If
38/// a size smaller than the smallest is requested the smallest size in dict is returned.
39///
40/// m: The size of the thread you want dict for e.g. 6 for M6 screw threads.
41///
42/// return: The dictionary of thread attributes.
43fn m_table_lookup(m: i32) -> HashMap<&'static str, f64> {
44    let m_table = m_table();
45    let mut m = m;
46    if m < 2 {
47        m = 2;
48    }
49    loop {
50        if m_table.contains_key(&m) {
51            break;
52        }
53        m -= 1;
54    }
55    m_table[&m].clone()
56}
57
58/// Calculates the thread height from the given pitch.
59///
60/// pitch: The pitch of the threads.
61///
62/// return: The height of the threads.
63fn thread_height_from_pitch(pitch: f64) -> f64 {
64    3.0f64.sqrt() / 2.0 * pitch
65}
66
67///  Calculates the dMin of a thread based on the dMaj and pitch.
68///
69///  d_maj: The dMaj of the threads.
70///
71///  pitch: The pitch of the threads.
72///
73///  return: The dMin of the threads.
74fn d_min_from_d_maj_pitch(d_maj: f64, pitch: f64) -> f64 {
75    d_maj - 2.0 * 5.0 / 8.0 * thread_height_from_pitch(pitch)
76}
77
78/// Creates a threaded cylinder.
79///
80/// d_min: dMin of thread.
81///
82/// d_maj: dMaj of thread.
83///
84/// pitch: Pitch of the thread.
85///
86/// length: The length of the threaded rod.
87///
88/// segments: The number of segments in a full revolution.
89///
90/// lead_in_degrees: The total angle of lead in.
91///
92/// lead_out_degrees: The total angle of lead out.
93///
94/// left_hand_thread: lefty tighty?
95///
96/// center: Center vertically.
97///
98/// return: The threaded cylinder.
99#[allow(clippy::too_many_arguments)]
100fn threaded_cylinder(
101    d_min: f64,
102    d_maj: f64,
103    pitch: f64,
104    length: f64,
105    segments: u64,
106    lead_in_degrees: f64,
107    lead_out_degrees: f64,
108    left_hand_thread: bool,
109    center: bool,
110) -> Scad {
111    let lead_in = lead_in_degrees > 0.0;
112    let lead_out = lead_out_degrees > 0.0;
113    let thread_length = length - 0.7 * pitch;
114    let n_revolutions = thread_length / pitch;
115    let n_steps = (n_revolutions * segments as f64) as usize;
116    let z_step = thread_length / n_steps as f64;
117    let step_angle = 360.0 / segments as f64;
118    let n_lead_in_steps = (segments as f64 * lead_in_degrees / 360.0 + 2.0) as usize;
119    let n_lead_out_steps = (segments as f64 * lead_out_degrees / 360.0) as usize;
120    let mut lead_in_step = 2;
121    let mut lead_out_step = n_lead_out_steps;
122
123    let thread_profile0 = Pt3::new(d_min / 2.0, 0.0, 3.0 / 4.0 * pitch);
124    let thread_profile1 = Pt3::new(d_maj / 2.0, 0.0, 7.0 / 16.0 * pitch);
125    let thread_profile2 = Pt3::new(d_min / 2.0, 0.0, 0.0);
126    let thread_profile3 = Pt3::new(d_maj / 2.0, 0.0, 5.0 / 16.0 * pitch);
127
128    let lerp_profile1 = Pt3::new(d_min / 2.0, 0.0, 7.0 / 16.0 * pitch);
129    let lerp_profile3 = Pt3::new(d_min / 2.0, 0.0, 5.0 / 16.0 * pitch);
130
131    let lead_in_start_profile0 = thread_profile0;
132    let lead_in_start_profile2 = thread_profile2;
133    let lead_in_start_profile1 = lerp(
134        lerp_profile1,
135        thread_profile1,
136        n_lead_in_steps,
137        lead_in_step,
138    );
139    let lead_in_start_profile3 = lerp(
140        lerp_profile3,
141        thread_profile3,
142        n_lead_in_steps,
143        lead_in_step,
144    );
145    lead_in_step += 1;
146
147    let lead_out_end_profile1 = lerp(lerp_profile1, thread_profile1, n_lead_out_steps, 1);
148    let lead_out_end_profile3 = lerp(lerp_profile3, thread_profile3, n_lead_out_steps, 1);
149
150    let mut vertices: Vec<Pt3> = Vec::new();
151    let mut indices: Vec<usize> = Vec::new();
152
153    // Create the starting end face
154    vertices.push(lead_in_start_profile0);
155    vertices.push(lead_in_start_profile1);
156    vertices.push(lead_in_start_profile2);
157    vertices.push(lead_in_start_profile3);
158
159    if left_hand_thread {
160        indices.append(&mut vec![2, 1, 0]);
161        indices.append(&mut vec![3, 1, 2]);
162    } else {
163        indices.append(&mut vec![0, 1, 2]);
164        indices.append(&mut vec![2, 1, 3]);
165    }
166
167    // Vertices used for the middle sections
168    let mut p4;
169    let mut p5;
170    let mut p6;
171    let mut p7;
172
173    let lead_in_profile0 = lead_in_start_profile0;
174    let mut lead_in_profile1 = lead_in_start_profile1;
175    let lead_in_profile2 = lead_in_start_profile2;
176    let mut lead_in_profile3 = lead_in_start_profile3;
177
178    let lead_out_profile0 = thread_profile0;
179    let mut lead_out_profile1 = thread_profile1;
180    let lead_out_profile2 = thread_profile2;
181    let mut lead_out_profile3 = thread_profile3;
182
183    for step in 0..(n_steps - 1) {
184        let mut angle = step_angle * (step + 1) as f64;
185        if left_hand_thread {
186            angle *= -1.0;
187        }
188        let c = dcos(angle);
189        let s = dsin(angle);
190        if lead_in_step < n_lead_in_steps && lead_in {
191            p4 = Pt3::new(
192                c * lead_in_profile0.x,
193                s * lead_in_profile0.x,
194                z_step * step as f64 + lead_in_profile0.z,
195            );
196            p5 = Pt3::new(
197                c * lead_in_profile1.x,
198                s * lead_in_profile1.x,
199                z_step * step as f64 + lead_in_profile1.z,
200            );
201            p6 = Pt3::new(
202                c * lead_in_profile2.x,
203                s * lead_in_profile2.x,
204                z_step * step as f64 + lead_in_profile2.z,
205            );
206            p7 = Pt3::new(
207                c * lead_in_profile3.x,
208                s * lead_in_profile3.x,
209                z_step * step as f64 + lead_in_profile3.z,
210            );
211
212            lead_in_step += 1;
213            lead_in_profile1 = lerp(
214                lead_in_start_profile1,
215                thread_profile1,
216                n_lead_in_steps,
217                lead_in_step,
218            );
219            lead_in_profile3 = lerp(
220                lead_in_start_profile3,
221                thread_profile3,
222                n_lead_in_steps,
223                lead_in_step,
224            );
225        } else if lead_out_step > 0 && step >= n_steps - n_lead_out_steps && lead_out {
226            p4 = Pt3::new(
227                c * lead_out_profile0.x,
228                s * lead_out_profile0.x,
229                z_step * step as f64 + lead_out_profile0.z,
230            );
231            p5 = Pt3::new(
232                c * lead_out_profile1.x,
233                s * lead_out_profile1.x,
234                z_step * step as f64 + lead_out_profile1.z,
235            );
236            p6 = Pt3::new(
237                c * lead_out_profile2.x,
238                s * lead_out_profile2.x,
239                z_step * step as f64 + lead_out_profile2.z,
240            );
241            p7 = Pt3::new(
242                c * lead_out_profile3.x,
243                s * lead_out_profile3.x,
244                z_step * step as f64 + lead_out_profile3.z,
245            );
246            lead_out_step -= 1;
247            lead_out_profile1 = lerp(
248                thread_profile1,
249                lead_out_end_profile1,
250                n_lead_out_steps,
251                n_lead_out_steps - lead_out_step,
252            );
253            lead_out_profile3 = lerp(
254                thread_profile3,
255                lead_out_end_profile3,
256                n_lead_out_steps,
257                n_lead_out_steps - lead_out_step,
258            );
259        } else {
260            p4 = Pt3::new(
261                c * thread_profile0.x,
262                s * thread_profile0.x,
263                z_step * step as f64 + thread_profile0.z,
264            );
265            p5 = Pt3::new(
266                c * thread_profile1.x,
267                s * thread_profile1.x,
268                z_step * step as f64 + thread_profile1.z,
269            );
270            p6 = Pt3::new(
271                c * thread_profile2.x,
272                s * thread_profile2.x,
273                z_step * step as f64 + thread_profile2.z,
274            );
275            p7 = Pt3::new(
276                c * thread_profile3.x,
277                s * thread_profile3.x,
278                z_step * step as f64 + thread_profile3.z,
279            );
280        }
281
282        vertices.push(p4);
283        vertices.push(p5);
284        vertices.push(p6);
285        vertices.push(p7);
286
287        let index_offset = step * 4;
288        if left_hand_thread {
289            indices.append(&mut vec![
290                3 + index_offset,
291                5 + index_offset,
292                1 + index_offset,
293            ]);
294            indices.append(&mut vec![
295                7 + index_offset,
296                5 + index_offset,
297                3 + index_offset,
298            ]);
299            indices.append(&mut vec![1 + index_offset, 4 + index_offset, index_offset]);
300            indices.append(&mut vec![
301                5 + index_offset,
302                4 + index_offset,
303                1 + index_offset,
304            ]);
305            indices.append(&mut vec![index_offset, 6 + index_offset, 2 + index_offset]);
306            indices.append(&mut vec![4 + index_offset, 6 + index_offset, index_offset]);
307            indices.append(&mut vec![
308                2 + index_offset,
309                7 + index_offset,
310                3 + index_offset,
311            ]);
312            indices.append(&mut vec![
313                6 + index_offset,
314                7 + index_offset,
315                2 + index_offset,
316            ]);
317        } else {
318            indices.append(&mut vec![
319                1 + index_offset,
320                5 + index_offset,
321                3 + index_offset,
322            ]);
323            indices.append(&mut vec![
324                3 + index_offset,
325                5 + index_offset,
326                7 + index_offset,
327            ]);
328            indices.append(&mut vec![index_offset, 4 + index_offset, 1 + index_offset]);
329            indices.append(&mut vec![
330                1 + index_offset,
331                4 + index_offset,
332                5 + index_offset,
333            ]);
334            indices.append(&mut vec![2 + index_offset, 6 + index_offset, index_offset]);
335            indices.append(&mut vec![index_offset, 6 + index_offset, 4 + index_offset]);
336            indices.append(&mut vec![
337                3 + index_offset,
338                7 + index_offset,
339                2 + index_offset,
340            ]);
341            indices.append(&mut vec![
342                2 + index_offset,
343                7 + index_offset,
344                6 + index_offset,
345            ]);
346        }
347    } // end loop
348
349    let index_offset = (n_steps - 2) * 4;
350    if left_hand_thread {
351        indices.append(&mut vec![
352            5 + index_offset,
353            7 + index_offset,
354            6 + index_offset,
355        ]);
356        indices.append(&mut vec![
357            4 + index_offset,
358            5 + index_offset,
359            6 + index_offset,
360        ]);
361    } else {
362        indices.append(&mut vec![
363            6 + index_offset,
364            7 + index_offset,
365            5 + index_offset,
366        ]);
367        indices.append(&mut vec![
368            6 + index_offset,
369            5 + index_offset,
370            4 + index_offset,
371        ]);
372    }
373
374    let mut faces = Faces::with_capacity(indices.len() / 3);
375    for i in (0..indices.len()).step_by(3) {
376        faces.push(Indices::from_indices(vec![
377            indices[i] as u64,
378            indices[i + 1] as u64,
379            indices[i + 2] as u64,
380        ]));
381    }
382    let convexity = (length / pitch) as u64 + 1;
383    let threads = polyhedron!(Pt3s::from_pt3s(vertices), faces, convexity);
384
385    let rod = Polyhedron::cylinder(d_min / 2.0 + 0.0001, length, segments).into_scad();
386
387    let mut result = threads + rod;
388
389    if center {
390        result = translate!([0.0, 0.0, -length / 2.0], result;);
391    }
392    result
393}
394
395/// Creates a threaded rod at the world origin.
396///
397/// m: The metric size of the rod.
398///
399/// length: The length of the rod in mm.
400///
401/// segments: The number of segments in a circle.
402///
403/// lead_in_degrees: Span of the lead in.
404///
405/// lead_out_degrees: Span of the lead out.
406///
407/// left_hand_thread: lefty tighty?
408///
409/// center: Center vertically.
410///
411/// return: The threaded rod.
412pub fn threaded_rod(
413    m: i32,
414    length: f64,
415    segments: u64,
416    lead_in_degrees: f64,
417    lead_out_degrees: f64,
418    left_hand_thread: bool,
419    center: bool,
420) -> Scad {
421    let thread_info = m_table_lookup(m);
422    let pitch = thread_info["pitch"];
423    let d_maj = thread_info["external_dMaj"];
424    let d_min = d_min_from_d_maj_pitch(d_maj, pitch);
425
426    threaded_cylinder(
427        d_min,
428        d_maj,
429        pitch,
430        length,
431        segments,
432        lead_in_degrees,
433        lead_out_degrees,
434        left_hand_thread,
435        center,
436    )
437}
438
439/// Create a hex head bolt at the world origin.
440///
441/// m: The metric bolt size.
442///
443/// length: The length of the threaded part.
444///
445/// head_height: The height of the hex head.
446///
447/// segments: The number of segments in a circle.
448///
449/// lead_in_degrees: The amount of degrees the tapered thread occupies.
450///
451/// chamfered: Whether or not to chamfer the top and bottom of the head.
452///
453/// left_hand_thread: lefty tighty?
454///
455/// center: Center vertically.
456///
457/// return: The hex bolt.
458#[allow(clippy::too_many_arguments)]
459pub fn hex_bolt(
460    m: i32,
461    length: f64,
462    head_height: f64,
463    segments: u64,
464    lead_in_degrees: f64,
465    chamfered: bool,
466    left_hand_thread: bool,
467    center: bool,
468) -> Scad {
469    let thread_info = m_table_lookup(m);
470    let pitch = thread_info["pitch"];
471    let d_maj = thread_info["external_dMaj"];
472    let head_diameter = thread_info["nut_width"];
473    let d_min = d_min_from_d_maj_pitch(d_maj, pitch);
474
475    let mut rod = threaded_cylinder(
476        d_min,
477        d_maj,
478        pitch,
479        length,
480        segments,
481        0.0,
482        lead_in_degrees,
483        left_hand_thread,
484        false,
485    );
486    rod = translate!([0.0, 0.0, head_height], rod;);
487
488    let mut head = Polyhedron::linear_extrude(
489        &dim2::circumscribed_polygon(6, head_diameter / 2.0),
490        head_height,
491    )
492    .into_scad();
493    if chamfered {
494        let chamfer_size = thread_info["chamfer_size"];
495        head = head
496            - Scad::external_cylinder_chamfer(
497                chamfer_size,
498                1.0,
499                (0.25 * head_diameter * 0.25 * head_diameter
500                    + 0.5 * head_diameter * 0.5 * head_diameter)
501                    .sqrt(),
502                head_height,
503                segments,
504                center,
505            );
506    }
507    let mut bolt = rod + head;
508    if center {
509        bolt = translate!([0.0, 0.0, -((head_height + length) / 2.0)], bolt;);
510    }
511    bolt
512}
513
514/// Create a tap for making threaded holes in things.
515///
516/// m: The metric size of the tap.
517///
518/// length: The length of the tap.
519///
520/// segments: The number of segmentst in a circle.
521///
522/// left_hand_thread: lefty tighty?
523///
524/// center: Center vertically.
525///
526/// return: The tap.
527pub fn tap(m: i32, length: f64, segments: u64, left_hand_thread: bool, center: bool) -> Scad {
528    let thread_info = m_table_lookup(m);
529    let pitch = thread_info["pitch"];
530    let d_maj = thread_info["internal_dMaj"];
531    let d_min = d_min_from_d_maj_pitch(d_maj, pitch);
532
533    threaded_cylinder(
534        d_min,
535        d_maj,
536        pitch,
537        length,
538        segments,
539        0.0,
540        0.0,
541        left_hand_thread,
542        center,
543    )
544}
545
546/// Create a hex nut.
547///
548/// m: The metric size of the nut.
549///
550/// height: The height of the nut.
551///
552/// segments: The number of segments in a circle.
553///
554/// chamfered: Adds a chamfer to the nut.
555///
556/// left_hand_thread: lefty tighty?
557///
558/// center: Center horizontally.
559///
560/// return: The nut.
561pub fn hex_nut(
562    m: i32,
563    height: f64,
564    segments: u64,
565    chamfered: bool,
566    left_hand_thread: bool,
567    center: bool,
568) -> Scad {
569    let thread_info = m_table_lookup(m);
570    let nut_width = thread_info["nut_width"];
571
572    let mut nut_tap = tap(m, height + 20.0, segments, left_hand_thread, center);
573    nut_tap = translate!([0.0, 0.0, -10.0], nut_tap;);
574
575    let nut_blank =
576        Polyhedron::linear_extrude(&dim2::circumscribed_polygon(6, nut_width / 2.0), height)
577            .into_scad();
578
579    let mut nut = nut_blank - nut_tap;
580    if chamfered {
581        let chamfer_size = thread_info["chamfer_size"];
582        nut = nut
583            - Scad::external_cylinder_chamfer(
584                chamfer_size,
585                1.0,
586                (0.25 * nut_width * 0.25 * nut_width + 0.5 * nut_width * 0.5 * nut_width).sqrt(),
587                height,
588                segments,
589                center,
590            );
591    }
592
593    if center {
594        nut = translate!([0.0, 0.0, -height / 2.0], nut;);
595    }
596
597    nut
598}
599
600/// Returns the hashmap of iso metric thread profiles
601fn m_table() -> HashMap<i32, HashMap<&'static str, f64>> {
602    HashMap::from([
603        (
604            2,
605            HashMap::from([
606                ("pitch", 0.4),
607                ("external_dMaj", 1.886),
608                ("internal_dMaj", 2.148),
609                ("nut_width", 4.0),
610                ("chamfer_size", 1.45),
611            ]),
612        ),
613        (
614            3,
615            HashMap::from([
616                ("pitch", 0.5),
617                ("external_dMaj", 2.874),
618                ("internal_dMaj", 3.172),
619                ("nut_width", 5.5),
620                ("chamfer_size", 1.6),
621            ]),
622        ),
623        (
624            4,
625            HashMap::from([
626                ("pitch", 0.7),
627                ("external_dMaj", 3.838),
628                ("internal_dMaj", 4.219),
629                ("nut_width", 7.0),
630                ("chamfer_size", 1.8),
631            ]),
632        ),
633        (
634            5,
635            HashMap::from([
636                ("pitch", 0.8),
637                ("external_dMaj", 4.826),
638                ("internal_dMaj", 5.24),
639                ("nut_width", 8.0),
640                ("chamfer_size", 1.9),
641            ]),
642        ),
643        (
644            6,
645            HashMap::from([
646                ("pitch", 1.0),
647                ("external_dMaj", 5.794),
648                ("internal_dMaj", 6.294),
649                ("nut_width", 10.0),
650                ("chamfer_size", 2.1),
651            ]),
652        ),
653        // nut_width made up for next entry
654        (
655            7,
656            HashMap::from([
657                ("pitch", 1.0),
658                ("external_dMaj", 6.794),
659                ("internal_dMaj", 7.294),
660                ("nut_width", 13.0),
661                ("chamfer_size", 2.45),
662            ]),
663        ),
664        (
665            8,
666            HashMap::from([
667                ("pitch", 1.25),
668                ("external_dMaj", 7.76),
669                ("internal_dMaj", 8.34),
670                ("nut_width", 13.0),
671                ("chamfer_size", 2.45),
672            ]),
673        ),
674        // nut_width made up for next entry
675        (
676            9,
677            HashMap::from([
678                ("pitch", 1.25),
679                ("external_dMaj", 8.76),
680                ("internal_dMaj", 9.34),
681                ("nut_width", 16.0),
682                ("chamfer_size", 2.8),
683            ]),
684        ),
685        (
686            10,
687            HashMap::from([
688                ("pitch", 1.5),
689                ("external_dMaj", 9.732),
690                ("internal_dMaj", 10.396),
691                ("nut_width", 16.0),
692                ("chamfer_size", 2.8),
693            ]),
694        ),
695        // nut_width made up for next entry
696        (
697            11,
698            HashMap::from([
699                ("pitch", 1.5),
700                ("external_dMaj", 10.73),
701                ("internal_dMaj", 11.387),
702                ("nut_width", 18.0),
703                ("chamfer_size", 3.0),
704            ]),
705        ),
706        (
707            12,
708            HashMap::from([
709                ("pitch", 1.75),
710                ("external_dMaj", 11.7),
711                ("internal_dMaj", 12.453),
712                ("nut_width", 18.0),
713                ("chamfer_size", 3.0),
714            ]),
715        ),
716        (
717            14,
718            HashMap::from([
719                ("pitch", 2.0),
720                ("external_dMaj", 13.68),
721                ("internal_dMaj", 14.501),
722                ("nut_width", 21.0),
723                ("chamfer_size", 3.35),
724            ]),
725        ),
726        // nut_width made up for next entry
727        (
728            15,
729            HashMap::from([
730                ("pitch", 1.5),
731                ("external_dMaj", 14.73),
732                ("internal_dMaj", 15.407),
733                ("nut_width", 24.0),
734                ("chamfer_size", 3.7),
735            ]),
736        ),
737        (
738            16,
739            HashMap::from([
740                ("pitch", 2.0),
741                ("external_dMaj", 15.68),
742                ("internal_dMaj", 16.501),
743                ("nut_width", 24.0),
744                ("chamfer_size", 3.7),
745            ]),
746        ),
747        // nut_width made up for next entry
748        (
749            17,
750            HashMap::from([
751                ("pitch", 1.5),
752                ("external_dMaj", 16.73),
753                ("internal_dMaj", 17.407),
754                ("nut_width", 27.0),
755                ("chamfer_size", 3.9),
756            ]),
757        ),
758        (
759            18,
760            HashMap::from([
761                ("pitch", 2.5),
762                ("external_dMaj", 17.62),
763                ("internal_dMaj", 18.585),
764                ("nut_width", 27.0),
765                ("chamfer_size", 3.9),
766            ]),
767        ),
768        (
769            20,
770            HashMap::from([
771                ("pitch", 2.5),
772                ("external_dMaj", 19.62),
773                ("internal_dMaj", 20.585),
774                ("nut_width", 30.0),
775                ("chamfer_size", 4.25),
776            ]),
777        ),
778        (
779            22,
780            HashMap::from([
781                ("pitch", 3.0),
782                ("external_dMaj", 21.58),
783                ("internal_dMaj", 22.677),
784                ("nut_width", 34.0),
785                ("chamfer_size", 4.75),
786            ]),
787        ),
788        (
789            24,
790            HashMap::from([
791                ("pitch", 3.0),
792                ("external_dMaj", 23.58),
793                ("internal_dMaj", 24.698),
794                ("nut_width", 36.0),
795                ("chamfer_size", 4.9),
796            ]),
797        ),
798        // nut_width made up for next entry
799        (
800            25,
801            HashMap::from([
802                ("pitch", 2.0),
803                ("external_dMaj", 24.68),
804                ("internal_dMaj", 25.513),
805                ("nut_width", 41.0),
806                ("chamfer_size", 5.5),
807            ]),
808        ),
809        // nut_width made up for next entry
810        (
811            26,
812            HashMap::from([
813                ("pitch", 1.5),
814                ("external_dMaj", 25.73),
815                ("internal_dMaj", 26.417),
816                ("nut_width", 41.0),
817                ("chamfer_size", 5.5),
818            ]),
819        ),
820        (
821            27,
822            HashMap::from([
823                ("pitch", 3.0),
824                ("external_dMaj", 26.58),
825                ("internal_dMaj", 27.698),
826                ("nut_width", 41.0),
827                ("chamfer_size", 5.5),
828            ]),
829        ),
830        // nut_width made up for next entry
831        (
832            28,
833            HashMap::from([
834                ("pitch", 2.0),
835                ("external_dMaj", 27.68),
836                ("internal_dMaj", 28.513),
837                ("nut_width", 46.0),
838                ("chamfer_size", 6.0),
839            ]),
840        ),
841        (
842            30,
843            HashMap::from([
844                ("pitch", 3.5),
845                ("external_dMaj", 29.52),
846                ("internal_dMaj", 30.785),
847                ("nut_width", 46.0),
848                ("chamfer_size", 6.0),
849            ]),
850        ),
851        // nut_width made up for next entry
852        (
853            32,
854            HashMap::from([
855                ("pitch", 2.0),
856                ("external_dMaj", 31.68),
857                ("internal_dMaj", 32.513),
858                ("nut_width", 49.0),
859                ("chamfer_size", 6.4),
860            ]),
861        ),
862        (
863            33,
864            HashMap::from([
865                ("pitch", 3.5),
866                ("external_dMaj", 32.54),
867                ("internal_dMaj", 33.785),
868                ("nut_width", 49.0),
869                ("chamfer_size", 6.4),
870            ]),
871        ),
872        // nut_width made up for next entry
873        (
874            35,
875            HashMap::from([
876                ("pitch", 1.5),
877                ("external_dMaj", 34.73),
878                ("internal_dMaj", 35.416),
879                ("nut_width", 55.0),
880                ("chamfer_size", 7.0),
881            ]),
882        ),
883        (
884            36,
885            HashMap::from([
886                ("pitch", 4.0),
887                ("external_dMaj", 35.47),
888                ("internal_dMaj", 36.877),
889                ("nut_width", 55.0),
890                ("chamfer_size", 7.0),
891            ]),
892        ),
893        // nut_width made up for next entry
894        (
895            38,
896            HashMap::from([
897                ("pitch", 1.5),
898                ("external_dMaj", 37.73),
899                ("internal_dMaj", 38.417),
900                ("nut_width", 60.0),
901                ("chamfer_size", 7.5),
902            ]),
903        ),
904        (
905            39,
906            HashMap::from([
907                ("pitch", 4.0),
908                ("external_dMaj", 38.47),
909                ("internal_dMaj", 39.877),
910                ("nut_width", 60.0),
911                ("chamfer_size", 7.5),
912            ]),
913        ),
914        // nut_width made up for next entry
915        (
916            40,
917            HashMap::from([
918                ("pitch", 3.0),
919                ("external_dMaj", 39.58),
920                ("internal_dMaj", 40.698),
921                ("nut_width", 65.0),
922                ("chamfer_size", 8.2),
923            ]),
924        ),
925        (
926            42,
927            HashMap::from([
928                ("pitch", 4.5),
929                ("external_dMaj", 41.44),
930                ("internal_dMaj", 42.965),
931                ("nut_width", 65.0),
932                ("chamfer_size", 8.2),
933            ]),
934        ),
935        (
936            45,
937            HashMap::from([
938                ("pitch", 4.5),
939                ("external_dMaj", 44.44),
940                ("internal_dMaj", 45.965),
941                ("nut_width", 70.0),
942                ("chamfer_size", 8.75),
943            ]),
944        ),
945        (
946            48,
947            HashMap::from([
948                ("pitch", 5.0),
949                ("external_dMaj", 47.4),
950                ("internal_dMaj", 49.057),
951                ("nut_width", 75.0),
952                ("chamfer_size", 9.25),
953            ]),
954        ),
955        // nut_width made up for next entry
956        (
957            50,
958            HashMap::from([
959                ("pitch", 4.0),
960                ("external_dMaj", 49.47),
961                ("internal_dMaj", 50.892),
962                ("nut_width", 80.0),
963                ("chamfer_size", 9.5),
964            ]),
965        ),
966        (
967            52,
968            HashMap::from([
969                ("pitch", 5.0),
970                ("external_dMaj", 51.4),
971                ("internal_dMaj", 53.037),
972                ("nut_width", 80.0),
973                ("chamfer_size", 9.5),
974            ]),
975        ),
976        // nut_width made up for next entry
977        (
978            55,
979            HashMap::from([
980                ("pitch", 4.0),
981                ("external_dMaj", 54.47),
982                ("internal_dMaj", 55.892),
983                ("nut_width", 85.0),
984                ("chamfer_size", 10.25),
985            ]),
986        ),
987        (
988            56,
989            HashMap::from([
990                ("pitch", 5.5),
991                ("external_dMaj", 55.37),
992                ("internal_dMaj", 57.149),
993                ("nut_width", 85.0),
994                ("chamfer_size", 10.25),
995            ]),
996        ),
997        // nut_width made up for next entry
998        (
999            58,
1000            HashMap::from([
1001                ("pitch", 4.0),
1002                ("external_dMaj", 57.47),
1003                ("internal_dMaj", 58.892),
1004                ("nut_width", 90.0),
1005                ("chamfer_size", 10.75),
1006            ]),
1007        ),
1008        (
1009            60,
1010            HashMap::from([
1011                ("pitch", 5.5),
1012                ("external_dMaj", 59.37),
1013                ("internal_dMaj", 61.149),
1014                ("nut_width", 90.0),
1015                ("chamfer_size", 10.75),
1016            ]),
1017        ),
1018        // nut_width made up for next entry
1019        (
1020            62,
1021            HashMap::from([
1022                ("pitch", 4.0),
1023                ("external_dMaj", 61.47),
1024                ("internal_dMaj", 62.892),
1025                ("nut_width", 95.0),
1026                ("chamfer_size", 11.25),
1027            ]),
1028        ),
1029        // nut_width made up for next entry
1030        (
1031            63,
1032            HashMap::from([
1033                ("pitch", 1.5),
1034                ("external_dMaj", 62.73),
1035                ("internal_dMaj", 63.429),
1036                ("nut_width", 95.0),
1037                ("chamfer_size", 11.25),
1038            ]),
1039        ),
1040        (
1041            64,
1042            HashMap::from([
1043                ("pitch", 6.0),
1044                ("external_dMaj", 63.32),
1045                ("internal_dMaj", 65.421),
1046                ("nut_width", 95.0),
1047                ("chamfer_size", 11.25),
1048            ]),
1049        ),
1050        // nut_width made up for next entry
1051        (
1052            65,
1053            HashMap::from([
1054                ("pitch", 4.0),
1055                ("external_dMaj", 64.47),
1056                ("internal_dMaj", 65.892),
1057                ("nut_width", 100.0),
1058                ("chamfer_size", 11.75),
1059            ]),
1060        ),
1061        // nut_width made up for next entry
1062        (
1063            68,
1064            HashMap::from([
1065                ("pitch", 6.0),
1066                ("external_dMaj", 67.32),
1067                ("internal_dMaj", 69.241),
1068                ("nut_width", 100.0),
1069                ("chamfer_size", 11.75),
1070            ]),
1071        ),
1072        // nut_width made up for next entry
1073        (
1074            70,
1075            HashMap::from([
1076                ("pitch", 6.0),
1077                ("external_dMaj", 69.32),
1078                ("internal_dMaj", 71.241),
1079                ("nut_width", 100.0),
1080                ("chamfer_size", 11.75),
1081            ]),
1082        ),
1083        // nut_width made up for next entry
1084        (
1085            72,
1086            HashMap::from([
1087                ("pitch", 6.0),
1088                ("external_dMaj", 71.32),
1089                ("internal_dMaj", 73.241),
1090                ("nut_width", 110.0),
1091                ("chamfer_size", 13.0),
1092            ]),
1093        ),
1094        // nut_width made up for next entry
1095        (
1096            75,
1097            HashMap::from([
1098                ("pitch", 6.0),
1099                ("external_dMaj", 74.32),
1100                ("internal_dMaj", 76.241),
1101                ("nut_width", 110.0),
1102                ("chamfer_size", 13.0),
1103            ]),
1104        ),
1105        // nut_width made up for next entry
1106        (
1107            76,
1108            HashMap::from([
1109                ("pitch", 6.0),
1110                ("external_dMaj", 75.32),
1111                ("internal_dMaj", 77.241),
1112                ("nut_width", 110.0),
1113                ("chamfer_size", 13.0),
1114            ]),
1115        ),
1116        // nut_width made up for next entry
1117        (
1118            78,
1119            HashMap::from([
1120                ("pitch", 2.0),
1121                ("external_dMaj", 77.68),
1122                ("internal_dMaj", 78.525),
1123                ("nut_width", 120.0),
1124                ("chamfer_size", 14.25),
1125            ]),
1126        ),
1127        // nut_width made up for next entry
1128        (
1129            80,
1130            HashMap::from([
1131                ("pitch", 6.0),
1132                ("external_dMaj", 79.32),
1133                ("internal_dMaj", 81.241),
1134                ("nut_width", 120.0),
1135                ("chamfer_size", 14.25),
1136            ]),
1137        ),
1138        // nut_width made up for next entry
1139        (
1140            82,
1141            HashMap::from([
1142                ("pitch", 2.0),
1143                ("external_dMaj", 81.68),
1144                ("internal_dMaj", 82.525),
1145                ("nut_width", 120.0),
1146                ("chamfer_size", 14.25),
1147            ]),
1148        ),
1149        // nut_width made up for next entry
1150        (
1151            85,
1152            HashMap::from([
1153                ("pitch", 6.0),
1154                ("external_dMaj", 84.32),
1155                ("internal_dMaj", 86.241),
1156                ("nut_width", 130.0),
1157                ("chamfer_size", 15.25),
1158            ]),
1159        ),
1160        // nut_width made up for next entry
1161        (
1162            90,
1163            HashMap::from([
1164                ("pitch", 6.0),
1165                ("external_dMaj", 89.32),
1166                ("internal_dMaj", 91.241),
1167                ("nut_width", 130.0),
1168                ("chamfer_size", 15.25),
1169            ]),
1170        ),
1171        // nut_width made up for next entry
1172        (
1173            95,
1174            HashMap::from([
1175                ("pitch", 6.0),
1176                ("external_dMaj", 94.32),
1177                ("internal_dMaj", 96.266),
1178                ("nut_width", 130.0),
1179                ("chamfer_size", 15.25),
1180            ]),
1181        ),
1182        // nut_width made up for next entry
1183        (
1184            100,
1185            HashMap::from([
1186                ("pitch", 6.0),
1187                ("external_dMaj", 99.32),
1188                ("internal_dMaj", 101.27),
1189                ("nut_width", 140.0),
1190                ("chamfer_size", 16.5),
1191            ]),
1192        ),
1193    ])
1194}