1use super::{EngineState, RefSelectState, RefSelectTarget};
14use crate::engine_state::selection_filter::SelectionFilter;
15use serde_json::Value;
16
17pub fn set_cam_json_at(root: &mut Value, path: &[String], value: Value) -> Result<(), String> {
22 let Some((last, parents)) = path.split_last() else {
23 *root = value;
24 return Ok(());
25 };
26 let mut cur = root;
27 for seg in parents {
28 cur = step_into(cur, seg)?;
29 }
30 if let Some(id) = last.strip_prefix('#') {
31 let slot = select_by_id(cur, id)?;
32 *slot = value;
33 return Ok(());
34 }
35 if !cur.is_object() {
36 *cur = Value::Object(serde_json::Map::new());
37 }
38 cur.as_object_mut().expect("object by construction").insert(last.clone(), value);
39 Ok(())
40}
41
42fn step_into<'a>(cur: &'a mut Value, seg: &str) -> Result<&'a mut Value, String> {
43 if let Some(id) = seg.strip_prefix('#') {
44 return select_by_id(cur, id);
45 }
46 if !cur.is_object() {
47 *cur = Value::Object(serde_json::Map::new());
48 }
49 Ok(cur
50 .as_object_mut()
51 .expect("object by construction")
52 .entry(seg.to_string())
53 .or_insert(Value::Object(serde_json::Map::new())))
54}
55
56fn select_by_id<'a>(cur: &'a mut Value, id: &str) -> Result<&'a mut Value, String> {
57 let Some(array) = cur.as_array_mut() else {
58 return Err(format!("`#{id}` addresses a record, but the path is not at a list"));
59 };
60 array
61 .iter_mut()
62 .find(|record| record.get("id").and_then(Value::as_str) == Some(id))
63 .ok_or_else(|| format!("no CAM record `{id}`"))
64}
65
66impl EngineState {
67 #[allow(clippy::too_many_arguments)]
70 pub fn begin_ref_select_for_cam(
71 &mut self,
72 id: &str,
73 path: Vec<String>,
74 label: String,
75 filter: Vec<String>,
76 multiple: bool,
77 seed_names: Vec<String>,
78 ) {
79 let restore_index = self.history.rollback();
80 self.selection_filter = SelectionFilter::from_ref_filter(&filter);
81 self.ref_select = Some(RefSelectState {
82 feature_id: id.to_string(),
83 path,
84 label,
85 filter,
86 multiple,
87 names: seed_names,
88 restore_index,
89 target: RefSelectTarget::Cam,
90 picks: 0,
91 claimed: false,
92 finish_requested: false,
93 });
94 self.sync_ref_select_emphasis();
95 }
96
97 pub(crate) fn cam_commit_refs(&mut self, path: &[String], names: &[String], multiple: bool) -> Result<(), String> {
102 let mut block = self.history.cam_block().cloned().ok_or_else(|| "the document has no CAM data".to_string())?;
103 let value = if multiple {
104 Value::Array(names.iter().cloned().map(Value::String).collect())
105 } else {
106 Value::String(names.first().cloned().unwrap_or_default())
107 };
108 set_cam_json_at(&mut block, path, value)?;
109 self.history.set_cam_block(Some(block), None);
110 self.history.break_coalescing();
113 Ok(())
114 }
115}
116
117
118#[derive(Debug, Clone)]
126pub struct CamOutcome {
127 pub turning_profile: Option<brep_cam::turning::profile::TurningProfile>,
128 pub setup_start: Option<brep_cam::SetupStart>,
130 pub toolpath: Option<brep_cam::model::Toolpath>,
131 pub error: Option<String>,
132 pub inputs_hash: u64,
135 pub work_units: u64,
136}
137
138#[derive(Debug, Default)]
142pub struct CamGeneration {
143 queue: std::collections::VecDeque<(crate::runner::CamJob, u64)>,
144 in_flight: Option<(u64, String, u64)>,
145 next_id: u64,
146 cancelled: std::collections::BTreeSet<u64>,
148 pub results: std::collections::BTreeMap<String, CamOutcome>,
149 pub results_stamp: u64,
152 pub progress: Option<crate::runner::CamProgress>,
153 pub notice: Option<String>,
155 pub job_builder: Option<CamJobBuilder>,
161}
162
163pub type CamJobBuilder = fn(&EngineState, &str) -> Result<(crate::runner::CamJob, u64), String>;
165
166impl CamGeneration {
167 pub fn busy(&self) -> bool {
169 self.in_flight.is_some() || !self.queue.is_empty()
170 }
171
172 pub fn queued(&self) -> Vec<String> {
174 self.queue.iter().map(|(job, _)| job.operation.id.as_str().to_string()).collect()
175 }
176
177 pub fn in_flight(&self) -> Option<&str> {
179 self.in_flight.as_ref().map(|(_, op, _)| op.as_str())
180 }
181
182 pub fn clear(&mut self) {
184 if let Some((id, _, _)) = self.in_flight.take() {
185 self.cancelled.insert(id);
186 }
187 self.queue.clear();
188 self.results.clear();
189 self.results_stamp += 1;
190 self.progress = None;
191 self.notice = None;
192 }
193}
194
195impl EngineState {
196 pub fn cam_named_axis(&self, name: &str) -> Option<([f64; 3], [f64; 3])> {
199 self.sketch_axes
200 .iter()
201 .find(|(n, _)| n == name)
202 .map(|(_, a)| ([a.point.x, a.point.y, a.point.z], [a.direction.x, a.direction.y, a.direction.z]))
203 }
204
205 #[allow(clippy::type_complexity)]
211 pub fn cam_chain_geometry(
212 &self,
213 chains: &[brep_cam::model::ChainRef],
214 ) -> Result<(Vec<(String, String, brep_kernel::NurbsCurve)>, Vec<(String, brep_kernel::SketchProfile)>), String> {
215 use brep_cam::model::ChainSource;
216 let mut curves: Vec<(String, String, brep_kernel::NurbsCurve)> = Vec::new();
217 let mut profiles: Vec<(String, brep_kernel::SketchProfile)> = Vec::new();
218 for chain in chains {
219 match &chain.source {
220 ChainSource::SketchCurves { sketch, curves: ids } => {
221 for id in ids {
222 if curves.iter().any(|(s, g, _)| s == sketch && g == id) {
223 continue;
224 }
225 let name = format!("{sketch}:G{id}");
226 let entry = self.sketch_paths.iter().find(|(n, _)| *n == name).ok_or_else(|| format!("sketch curve `{name}` is not in the model (run the history, or pick the chain again)"))?;
227 match entry.1.as_slice() {
228 [curve] => curves.push((sketch.clone(), id.clone(), curve.clone())),
229 other => return Err(format!("sketch curve `{name}` is {} curves, not one", other.len())),
230 }
231 }
232 }
233 ChainSource::SketchProfile { sketch, .. } => {
234 if profiles.iter().any(|(s, _)| s == sketch) {
235 continue;
236 }
237 let profile = self.sketch_profiles.iter().find(|(n, _)| n == sketch).ok_or_else(|| format!("sketch `{sketch}` has no closed profile in the model"))?;
238 profiles.push((sketch.clone(), profile.1.clone()));
239 }
240 ChainSource::Edges { .. } => {}
241 }
242 }
243 Ok((curves, profiles))
244 }
245}
246
247impl EngineState {
248 pub fn cam_enqueue(&mut self, jobs: Vec<(crate::runner::CamJob, u64)>) -> Vec<String> {
252 let mut queued = Vec::new();
253 for (mut job, hash) in jobs {
254 let op = job.operation.id.as_str().to_string();
255 let already = self.cam.in_flight().is_some_and(|f| f == op) || self.cam.queue.iter().any(|(j, _)| j.operation.id.as_str() == op);
256 if already {
257 continue;
258 }
259 self.cam.next_id += 1;
260 job.id = self.cam.next_id;
261 queued.push(op);
264 self.cam.queue.push_back((job, hash));
265 }
266 self.cam.notice = None;
267 self.cam_pump();
268 self.dirty = true;
269 queued
270 }
271
272 pub fn cam_cancel(&mut self) -> usize {
276 let dropped = self.cam.queue.len() + usize::from(self.cam.in_flight.is_some());
277 self.cam.queue.clear();
278 if let Some((id, op, _)) = self.cam.in_flight.take() {
279 self.cam.cancelled.insert(id);
280 self.runner.cancel_cam();
281 self.cam.notice = Some(format!("cancelled while generating {op}"));
282 } else if dropped > 0 {
283 self.cam.notice = Some("cancelled".to_string());
284 }
285 self.cam.progress = None;
286 self.dirty = true;
287 dropped
288 }
289
290 pub fn cam_pump(&mut self) {
293 while let Some(progress) = self.runner.poll_cam_progress() {
294 if self.cam.in_flight.as_ref().is_some_and(|(id, _, _)| *id == progress.id) {
295 self.cam.progress = Some(progress);
296 self.dirty = true;
297 }
298 }
299 while let Some(reply) = self.runner.poll_cam() {
300 let debug = std::env::var_os("BREP_CAM_DEBUG").is_some();
301 if self.cam.cancelled.remove(&reply.id) {
302 if debug {
303 eprintln!("cam reply {} ({}): dropped, cancelled", reply.id, reply.operation);
304 }
305 continue;
306 }
307 let Some((id, op, hash)) = self.cam.in_flight.clone() else {
308 if debug {
309 eprintln!("cam reply {} ({}): dropped, nothing in flight", reply.id, reply.operation);
310 }
311 continue;
312 };
313 if id != reply.id {
314 if debug {
315 eprintln!("cam reply {} ({}): dropped, in flight is {id} ({op})", reply.id, reply.operation);
316 }
317 continue;
318 }
319 if debug {
320 eprintln!("cam reply {} ({}): landed, ok {}", reply.id, reply.operation, reply.toolpath.is_ok());
321 }
322 self.cam.in_flight = None;
323 self.cam.progress = None;
324 let (toolpath, error) = match reply.toolpath {
325 Ok(path) => (Some(path), None),
326 Err(message) => (None, Some(message)),
327 };
328 self.cam.results_stamp += 1;
329 self.cam.results.insert(op, CamOutcome { turning_profile: reply.turning_profile, setup_start: reply.setup_start, toolpath, error, inputs_hash: hash, work_units: reply.work_units });
330 self.dirty = true;
331 }
332 while self.cam.in_flight.is_none() {
333 let Some((job, hash)) = self.cam.queue.pop_front() else { break };
334 let id = job.id;
335 let op = job.operation.id.as_str().to_string();
336 let rebuilt = match self.cam.job_builder {
339 Some(build) => build(self, &op).map(|(mut job, hash)| {
340 job.id = id;
341 (job, hash)
342 }),
343 None => Ok((job, hash)),
344 };
345 let (job, hash) = match rebuilt {
346 Ok(built) => built,
347 Err(why) => {
348 self.cam.results_stamp += 1;
349 self.cam.results.insert(op, CamOutcome { toolpath: None, error: Some(why), inputs_hash: hash, work_units: 0, turning_profile: None, setup_start: None });
350 self.dirty = true;
351 continue;
352 }
353 };
354 {
355 if self.runner.submit_cam(job) {
356 self.cam.in_flight = Some((id, op, hash));
357 } else {
358 self.cam.results_stamp += 1;
359 self.cam.results.insert(op, CamOutcome { turning_profile: None, setup_start: None, toolpath: None, error: Some("this runner cannot generate toolpaths".to_string()), inputs_hash: hash, work_units: 0 });
360 }
361 self.dirty = true;
362 }
363 }
364 }
365
366 pub fn cam_hold_replies(&mut self, hold: bool) {
372 self.runner.hold_cam_replies(hold);
373 }
374
375 pub fn cam_generation_json(&self) -> serde_json::Value {
376 serde_json::json!({
377 "busy": self.cam.busy(),
378 "inFlight": self.cam.in_flight(),
379 "queue": self.cam.queued(),
380 "progress": self.cam.progress.as_ref().map(|p| serde_json::json!({ "operation": p.operation, "phase": p.phase, "message": p.message, "current": p.current, "total": p.total })),
381 "notice": self.cam.notice,
382 "results": self.cam.results.iter().map(|(op, r)| (op.clone(), serde_json::json!({
383 "ok": r.error.is_none(),
384 "error": r.error,
385 "inputsHash": r.inputs_hash.to_string(),
386 "workUnits": r.work_units,
387 "motions": r.toolpath.as_ref().map(|t| t.motions.len()).unwrap_or(0),
388 "cuttingLength": r.toolpath.as_ref().map(|t| t.cutting_length()),
389 "status": r.toolpath.as_ref().map(|t| format!("{:?}", t.report.status)),
390 "warnings": r.toolpath.as_ref().map(|t| t.report.warnings.len()).unwrap_or(0),
391 }))).collect::<serde_json::Map<String, serde_json::Value>>(),
392 })
393 }
394}
395