1#![cfg_attr(
38 not(test),
39 deny(
40 clippy::unwrap_used,
41 clippy::expect_used,
42 clippy::panic,
43 clippy::todo,
44 clippy::unimplemented,
45 clippy::unreachable
46 )
47)]
48
49mod components;
50mod drawing;
51mod drive;
52mod fasteners;
53mod involute;
54mod steps;
55mod tables;
56
57pub use drawing::Drawing;
58pub use drive::TEETH;
59pub use steps::{SIZE, outline_plane};
60
61use geop_core_program::{
62 program::Program,
63 workspace::{FileStore, FileStoreMut},
64};
65use geop_ops_model::parameters::{parameters_of, set_parameters};
66use std::{
67 ops::{Deref, DerefMut},
68 sync::OnceLock,
69};
70
71use geop_core_math::geop_error::{GeopError, GeopResult};
72
73pub const PREFIX: &str = "std:";
75
76pub struct StandardPart {
79 pub file: &'static str,
81 pub program: Program,
83 pub base: &'static str,
87 pub threaded: Vec<String>,
90}
91
92fn titled(mut program: Program, title: &str, designation: &str) -> Program {
98 let mut parameters = parameters_of(&program);
99 parameters.title = Some(title.into());
100 parameters.designation = Some(designation.into());
101 set_parameters(&mut program, parameters);
102 program
103}
104
105struct Entry {
107 part: StandardPart,
108 text: String,
109}
110
111type Family = fn(&'static str) -> GeopResult<StandardPart>;
113
114const FAMILIES: [(&str, Family); 25] = [
118 ("std:iso4762_socket_head_cap_screw.geop", fasteners::iso4762),
119 ("std:iso7380_button_head_screw.geop", fasteners::iso7380),
120 ("std:iso10642_countersunk_screw.geop", fasteners::iso10642),
121 ("std:iso4017_hex_head_screw.geop", fasteners::iso4017),
122 ("std:iso4032_hex_nut.geop", fasteners::iso4032),
123 ("std:iso10511_nylon_insert_nut.geop", fasteners::iso10511),
124 ("std:iso7089_washer.geop", fasteners::iso7089),
125 ("std:iso7090_chamfered_washer.geop", fasteners::iso7090),
126 ("std:iso8734_dowel_pin.geop", fasteners::iso8734),
127 ("std:hex_standoff.geop", fasteners::hex_standoff),
128 ("std:ball_bearing.geop", components::ball_bearing),
129 ("std:tslot_2020.geop", components::tslot_2020),
130 ("std:tslot_2040.geop", components::tslot_2040),
131 ("std:nema17_stepper.geop", components::nema17),
132 ("std:spur_gear.geop", drive::spur_gear),
133 ("std:gt2_pulley_16t.geop", drive::gt2_pulley_16),
134 ("std:gt2_pulley_20t.geop", drive::gt2_pulley_20),
135 ("std:gt2_pulley_36t.geop", drive::gt2_pulley_36),
136 ("std:shaft_collar.geop", drive::shaft_collar),
137 ("std:flange_coupling.geop", drive::flange_coupling),
138 ("std:gear_rack.geop", drive::rack),
139 ("std:linear_rail.geop", components::linear_rail),
140 ("std:linear_carriage.geop", components::linear_carriage),
141 ("std:servo_sg90.geop", components::servo_sg90),
142 ("std:servo_mg996r.geop", components::servo_mg996r),
143];
144
145fn entry(file: &str) -> Option<GeopResult<&'static Entry>> {
149 static ENTRIES: [OnceLock<GeopResult<Entry>>; FAMILIES.len()] =
150 [const { OnceLock::new() }; FAMILIES.len()];
151 let k = FAMILIES.iter().position(|(f, _)| *f == file)?;
152 let (file, family) = FAMILIES[k];
153 let built = ENTRIES[k].get_or_init(|| {
154 let part = family(file)
155 .map_err(|e| e.with_context(format!("building the standard part {file}")))?;
156 let text = (part.program.to_json())
157 .map_err(|e| e.with_context(format!("writing the standard part {file}")))?;
158 Ok(Entry { part, text })
159 });
160 Some(built.as_ref().map_err(GeopError::clone))
161}
162
163pub fn parts() -> GeopResult<impl Iterator<Item = &'static StandardPart>> {
165 let parts = FAMILIES
166 .iter()
167 .map(|(file, _)| part(file))
168 .collect::<GeopResult<Vec<_>>>()?;
169 Ok(parts.into_iter())
170}
171
172pub fn part(file: &str) -> GeopResult<&'static StandardPart> {
174 Ok(&entry(file).ok_or_else(|| no_such_part(file))??.part)
175}
176
177fn no_such_part(file: &str) -> GeopError {
178 let known: Vec<&str> = FAMILIES.iter().map(|(f, _)| *f).collect();
179 GeopError::new(format!(
180 "there is no standard part {file:?}: there are {}",
181 known.join(", ")
182 ))
183}
184
185#[derive(Clone, Debug, Default)]
189pub struct WithStandardParts<F>(pub F);
190
191impl<F> Deref for WithStandardParts<F> {
192 type Target = F;
193
194 fn deref(&self) -> &F {
195 &self.0
196 }
197}
198
199impl<F> DerefMut for WithStandardParts<F> {
200 fn deref_mut(&mut self) -> &mut F {
201 &mut self.0
202 }
203}
204
205impl<F: FileStore> FileStore for WithStandardParts<F> {
206 fn read(&self, path: &str) -> GeopResult<String> {
207 if !path.starts_with(PREFIX) {
208 return self.0.read(path);
209 }
210 Ok(entry(path).ok_or_else(|| no_such_part(path))??.text.clone())
211 }
212
213 fn list(&self) -> Vec<String> {
214 let mut files: Vec<String> = self
215 .0
216 .list()
217 .into_iter()
218 .filter(|f| !f.starts_with(PREFIX))
219 .collect();
220 files.extend(FAMILIES.iter().map(|(file, _)| file.to_string()));
221 files
222 }
223}
224
225impl<F: FileStoreMut> FileStoreMut for WithStandardParts<F> {
228 fn write(&mut self, path: &str, text: Option<String>) {
229 if !path.starts_with(PREFIX) {
230 self.0.write(path, text);
231 }
232 }
233}