1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::{program::*, tools::*, types::*};
8
9#[derive(Serialize, Deserialize, Debug, PartialEq)]
12#[serde(rename_all = "lowercase")]
13pub enum ResolutionMode {
14 High,
16 Low,
18 Manual,
20}
21
22#[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
25pub struct Workpiece {
26 pub automatic: bool,
28 pub margin: f64,
30 pub bounds: Bounds,
33}
34
35#[derive(Serialize, Deserialize, Debug, PartialEq)]
37#[serde(rename_all = "lowercase")]
38pub enum CamoticsToolShape {
39 Cylindrical,
41 Ballnose,
43 Conical,
45}
46
47impl Default for CamoticsToolShape {
48 fn default() -> Self {
49 Self::Cylindrical
50 }
51}
52
53#[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
56pub struct CamoticsTool {
57 pub units: Units,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub angle: Option<f64>,
62 pub length: f64,
64 pub diameter: f64,
66 pub number: u8,
68 pub shape: CamoticsToolShape,
70}
71
72impl CamoticsTool {
73 #[must_use]
75 pub fn from_tool(tool: Tool, number: u8) -> Self {
76 match tool {
77 Tool::Cylindrical(t) => CamoticsTool {
78 units: t.units,
79 angle: None,
80 length: t.length,
81 diameter: t.diameter,
82 number,
83 shape: CamoticsToolShape::Cylindrical,
84 },
85 Tool::Ballnose(t) => CamoticsTool {
86 units: t.units,
87 angle: None,
88 length: t.length,
89 diameter: t.diameter,
90 number,
91 shape: CamoticsToolShape::Ballnose,
92 },
93 Tool::Conical(t) => CamoticsTool {
94 units: t.units,
95 angle: Some(t.angle),
96 length: t.length,
97 diameter: t.diameter,
98 number,
99 shape: CamoticsToolShape::Conical,
100 },
101 }
102 }
103}
104
105#[derive(Serialize, Deserialize, Debug, PartialEq)]
112pub struct Camotics {
113 #[serde(skip_serializing)]
115 pub name: String,
116 pub units: Units,
118 #[serde(rename(serialize = "resolution-mode"))]
121 pub resolution_mode: ResolutionMode,
122 pub resolution: f64,
127 pub tools: HashMap<u8, CamoticsTool>,
131 pub workpiece: Workpiece,
133 pub files: Vec<String>,
137}
138
139impl Camotics {
140 #[must_use]
142 pub fn new(name: &str, tools: &HashMap<Tool, u8>, workpiece: Bounds, resolution: f64) -> Self {
143 let mut tools_map = HashMap::new();
144 for (_, (tool, number)) in tools.iter().enumerate() {
145 tools_map.insert(*number, CamoticsTool::from_tool(*tool, *number));
146 }
147
148 Self {
149 name: name.to_string(),
150 units: Units::Metric,
151 resolution_mode: ResolutionMode::Manual,
152 resolution,
153 tools: tools_map,
154 workpiece: Workpiece {
155 automatic: false,
156 margin: 0.0,
157 bounds: workpiece,
158 },
159 files: vec![format!("{}.gcode", name)],
160 }
161 }
162
163 #[must_use]
165 pub fn from_program(name: &str, program: &Program, resolution: f64) -> Self {
166 let mut tools = HashMap::new();
167
168 for tool in program.tools() {
169 tools.insert(tool, program.tool_ordering(&tool).unwrap());
170 }
171
172 let workpiece = program.bounds();
173 Self::new(name, &tools, workpiece, resolution)
174 }
175
176 #[must_use]
179 pub fn to_json_string(&self) -> String {
180 serde_json::to_string_pretty(&self).unwrap()
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use serde_json::Value;
187
188 use super::*;
189 use crate::cuts::*;
190
191 #[test]
192 fn test_serialization() {
193 let mut tools = HashMap::new();
194
195 tools.insert(
196 1,
197 CamoticsTool {
198 number: 1,
199 angle: None,
200 units: Units::Metric,
201 shape: CamoticsToolShape::Cylindrical,
202 length: 50.0,
203 diameter: 4.0,
204 },
205 );
206
207 let camotics = Camotics {
208 name: "testing".to_string(),
209 units: Units::Metric,
210 resolution_mode: ResolutionMode::Manual,
211 resolution: 0.3,
212 tools,
213 workpiece: Workpiece {
214 automatic: false,
215 margin: 5.0,
216 bounds: Bounds {
217 min: Vector3 {
218 x: -60.5,
219 y: -60.5,
220 z: -3.0,
221 },
222 max: Vector3 {
223 x: 119.5,
224 y: 60.5,
225 z: 0.0,
226 },
227 },
228 },
229 files: vec!["file.gcode".to_string()],
230 };
231
232 let serialized = serde_json::to_string(&camotics).unwrap();
233 let output: Value = serde_json::from_str(&serialized).unwrap();
234
235 let expected: Value = serde_json::from_str(
236 r#"
237 {
238 "units": "metric",
239 "resolution-mode": "manual",
240 "resolution": 0.3,
241 "tools": {
242 "1": {
243 "number": 1,
244 "units": "metric",
245 "shape": "cylindrical",
246 "length": 50.0,
247 "diameter": 4.0
248 }
249 },
250 "workpiece": {
251 "automatic": false,
252 "margin": 5.0,
253 "bounds": {
254 "min": [-60.5, -60.5, -3.0],
255 "max": [119.5, 60.5, 0.0]
256 }
257 },
258 "files": [
259 "file.gcode"
260 ]
261 }"#,
262 )
263 .unwrap();
264
265 assert_eq!(output, expected);
266 }
267
268 #[test]
269 fn test_camotics_from_program() {
270 let mut program = Program::new(Units::Metric, 10.0, 50.0);
271
272 let tool = Tool::cylindrical(
273 Units::Metric,
274 50.0,
275 4.0,
276 Direction::Clockwise,
277 5000.0,
278 400.0,
279 );
280
281 let mut context = program.context(tool);
282
283 context.append_cut(Cut::path(
284 Vector3::new(0.0, 0.0, 3.0),
285 vec![Segment::line(
286 Vector2::default(),
287 Vector2::new(-28.0, -30.0),
288 )],
289 -0.1,
290 1.0,
291 ));
292
293 context.append_cut(Cut::path(
294 Vector3::new(0.0, 0.0, 3.0),
295 vec![
296 Segment::line(Vector2::new(23.0, 12.0), Vector2::new(5.0, 10.0)),
297 Segment::line(Vector2::new(5.0, 10.0), Vector2::new(67.0, 102.0)),
298 Segment::line(Vector2::new(67.0, 102.0), Vector2::new(23.0, 12.0)),
299 ],
300 -0.1,
301 1.0,
302 ));
303
304 let camotics = Camotics::from_program("test-project", &program, 1.0);
305
306 let mut tools = HashMap::new();
307 tools.insert(1, CamoticsTool::from_tool(tool, 1));
308
309 assert_eq!(
310 camotics,
311 Camotics {
312 name: "test-project".to_string(),
313 units: Units::Metric,
314 resolution_mode: ResolutionMode::Manual,
315 resolution: 1.0,
316 tools,
317 workpiece: Workpiece {
318 automatic: false,
319 margin: 0.0,
320 bounds: program.bounds()
321 },
322 files: vec!["test-project.gcode".to_string()]
323 }
324 );
325 }
326}