1#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
26pub struct RunReply {
27 pub generation: u64,
29 pub output: crate::pipeline::RunOutput,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
39pub struct RunProgress {
40 pub generation: u64,
42 pub index: usize,
44 pub total: usize,
46 pub feature_id: String,
47 pub feature_type: String,
48}
49
50#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
55pub struct StepProbeRequest {
56 pub id: u64,
57 pub text: String,
58}
59
60#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
65pub struct StepProbeReply {
66 pub id: u64,
67 pub result: Result<Option<brep_kernel::StepAssembly>, String>,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
73pub enum MeasureKind {
74 Solid,
75 Face,
76 Edge,
77 Inspect,
81}
82
83#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
89pub struct MeasureQuery {
90 pub id: u64,
92 pub kind: MeasureKind,
94 pub owner: String,
96 pub entity: String,
98 pub density: f64,
100}
101
102#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
106pub struct MeasureReply {
107 pub id: u64,
109 pub result: String,
111}
112
113#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
120pub struct TopologyRequest {
121 pub id: u64,
123 pub solids: Vec<(String, u32)>,
125}
126
127#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
131pub struct TopologyReply {
132 pub id: u64,
134 pub solids: Vec<(String, u32, brep_kernel::BrepSolid)>,
135}
136
137fn topology_reply(request: TopologyRequest) -> TopologyReply {
140 let solids = request
141 .solids
142 .into_iter()
143 .filter_map(|(name, handle)| {
144 brep_kernel::registered_solid_clone(handle)
145 .ok()
146 .map(|solid| (name, handle, solid))
147 })
148 .collect();
149 TopologyReply { id: request.id, solids }
150}
151
152#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
158pub struct SheetLinesRequest {
159 pub id: u64,
161 pub job: crate::sheets::project::LinesJob,
162}
163
164#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
168pub struct SheetLinesReply {
169 pub id: u64,
171 pub key: String,
172 pub lines: Result<crate::sheets::project::ExactLines, String>,
173}
174
175fn sheet_lines_reply(request: SheetLinesRequest) -> SheetLinesReply {
178 SheetLinesReply {
179 id: request.id,
180 lines: crate::sheets::project::run_lines_job(&request.job),
181 key: request.job.key,
182 }
183}
184
185#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
198pub struct CamJob {
199 pub id: u64,
201 pub operation: brep_cam::model::Operation,
202 pub setup: brep_cam::model::Setup,
203 pub tools: Vec<brep_cam::model::Tool>,
204 pub solids: Vec<(String, u32)>,
206 pub chord_tolerance: f64,
208 pub budget_chunk: u64,
210 #[serde(default)]
214 pub chain_curves: Vec<(String, String, brep_kernel::NurbsCurve)>,
215 #[serde(default)]
217 pub chain_profiles: Vec<(String, brep_kernel::SketchProfile)>,
218 #[serde(default)]
221 pub machine: Option<brep_cam::model::Machine>,
222 #[serde(default)]
224 pub autoprogram: Option<brep_cam::autoprogram::binding::Binding>,
225 #[serde(default)]
229 pub earlier: Vec<(brep_cam::model::Operation, Option<brep_cam::model::Toolpath>)>,
230 #[serde(default)]
232 pub previous_setups: Vec<brep_cam::SetupHistory>,
233 #[serde(default)]
236 pub source: Option<brep_cam::program::OperationInput>,
237 #[serde(default)]
240 pub source_operation: Option<brep_cam::model::Operation>,
241}
242
243#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
246pub struct CamReply {
247 #[serde(default, skip_serializing_if = "Option::is_none")]
249 pub turning_profile: Option<brep_cam::turning::profile::TurningProfile>,
250 #[serde(default, skip_serializing_if = "Option::is_none")]
253 pub setup_start: Option<brep_cam::SetupStart>,
254 pub id: u64,
255 pub operation: String,
256 pub toolpath: Result<brep_cam::model::Toolpath, String>,
257 pub work_units: u64,
258 pub cancelled: bool,
260}
261
262#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
265pub struct CamProgress {
266 pub id: u64,
267 pub operation: String,
268 pub phase: String,
269 pub message: String,
270 pub current: u64,
271 pub total: u64,
272}
273
274#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
281pub enum CamSimRequest {
282 Load {
284 timeline: brep_cam::multiaxis::sim::Timeline,
285 stock: brep_cam::multiaxis::sim::StockModel,
286 ranges: Vec<brep_cam::multiaxis::sim::RangeCutter>,
287 options: brep_cam::multiaxis::sim::DriverOptions,
288 #[serde(default)]
291 machine: Option<brep_cam::model::Machine>,
292 },
293 Advance { target: usize, budget: usize, stock: bool, known_version: u64 },
298 Drop,
300}
301
302#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
304pub struct CamSimJob {
305 pub session: u64,
306 pub seq: u64,
307 pub request: CamSimRequest,
308}
309
310#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
312pub struct CamSimReply {
313 pub session: u64,
314 pub seq: u64,
315 pub applied: usize,
316 pub version: u64,
317 pub removed_volume: f64,
318 pub stock_volume: f64,
319 pub unsupported: Vec<usize>,
320 pub warnings: Vec<(usize, brep_cam::model::Diagnostic)>,
321 pub stock: Option<brep_cam::multiaxis::sim::StockModel>,
323 pub reached: bool,
325 pub cancelled: bool,
327 pub error: Option<String>,
328}
329
330#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
332pub struct CamSimProgress {
333 pub session: u64,
334 pub seq: u64,
335 pub applied: usize,
336 pub target: usize,
337}
338
339struct CamSimSession {
341 session: u64,
342 timeline: brep_cam::multiaxis::sim::Timeline,
343 ranges: Vec<brep_cam::multiaxis::sim::RangeCutter>,
344 driver: brep_cam::multiaxis::sim::SimDriver,
345}
346
347thread_local! {
348 static CAM_SIM: std::cell::RefCell<Option<CamSimSession>> = const { std::cell::RefCell::new(None) };
349}
350
351fn cam_sim_clear() {
353 CAM_SIM.with(|c| *c.borrow_mut() = None);
354}
355
356thread_local! {
357 static PRE_OP_STOCK: std::cell::RefCell<brep_cam::PreOpStockCache> = std::cell::RefCell::new(brep_cam::PreOpStockCache::new(16));
360}
361
362fn pre_op_stock_clear() {
364 PRE_OP_STOCK.with(|c| c.borrow_mut().clear());
365}
366
367fn cam_sim_reply(job: CamSimJob, post: &mut dyn FnMut(Reply) -> bool) -> CamSimReply {
368 let (session, seq) = (job.session, job.seq);
369 let empty = |error: Option<String>| CamSimReply {
370 session,
371 seq,
372 applied: 0,
373 version: 0,
374 removed_volume: 0.0,
375 stock_volume: 0.0,
376 unsupported: Vec::new(),
377 warnings: Vec::new(),
378 stock: None,
379 reached: false,
380 cancelled: false,
381 error,
382 };
383 match job.request {
384 CamSimRequest::Drop => {
385 cam_sim_clear();
386 empty(None)
387 }
388 CamSimRequest::Load { timeline, stock, ranges, options, machine } => match brep_cam::multiaxis::sim::SimDriver::new(stock, options) {
389 Ok(driver) => {
390 let driver = driver.with_chain(brep_cam::multiaxis::sim::sim_chain(machine.as_ref()));
391 let reply = CamSimReply {
392 applied: driver.applied,
393 version: driver.version,
394 stock_volume: driver.stock.volume(),
395 reached: true,
396 ..empty(None)
397 };
398 CAM_SIM.with(|c| *c.borrow_mut() = Some(CamSimSession { session, timeline, ranges, driver }));
399 reply
400 }
401 Err(e) => empty(Some(e.to_string())),
402 },
403 CamSimRequest::Advance { target, budget, stock, known_version } => CAM_SIM.with(|c| {
404 let mut guard = c.borrow_mut();
405 let Some(sim) = guard.as_mut().filter(|s| s.session == session) else {
406 return empty(Some("no simulation session is loaded on the runner".into()));
407 };
408 let mut cancelled = false;
409 let tolerance = brep_cam::Tolerance::default();
410 let outcome = sim.driver.advance_ranges(&sim.timeline, &sim.ranges, target, budget, &tolerance, &mut |applied| {
411 let keep = post(Reply::CamSimProgress(CamSimProgress { session, seq, applied, target }));
412 if !keep {
413 cancelled = true;
414 }
415 keep
416 });
417 let d = &sim.driver;
418 let (reached, error) = match outcome {
419 Ok(o) => (o == brep_cam::multiaxis::sim::AdvanceOutcome::Reached, None),
420 Err(e) => (false, Some(e.to_string())),
421 };
422 CamSimReply {
423 session,
424 seq,
425 applied: d.applied,
426 version: d.version,
427 removed_volume: d.removed_volume,
428 stock_volume: d.stock.volume(),
429 unsupported: d.unsupported.clone(),
430 warnings: d.warnings.clone(),
431 stock: (stock && d.version != known_version).then(|| d.stock.clone()),
432 reached,
433 cancelled,
434 error,
435 }
436 }),
437 }
438}
439
440struct CamSink<'a> {
443 id: u64,
444 operation: String,
445 token: brep_cam::CancelToken,
446 post: &'a mut dyn FnMut(Reply) -> bool,
447}
448
449impl brep_cam::ProgressSink for CamSink<'_> {
450 fn progress(&mut self, progress: &brep_cam::Progress) {
451 if std::env::var_os("BREP_CAM_DEBUG").is_some() {
452 eprintln!("cam job {} progress: {} {}/{}", self.operation, progress.phase, progress.current, progress.total);
453 }
454 let keep_going = (self.post)(Reply::CamProgress(CamProgress {
455 id: self.id,
456 operation: self.operation.clone(),
457 phase: progress.phase.clone(),
458 message: progress.message.clone(),
459 current: progress.current,
460 total: progress.total,
461 }));
462 if !keep_going {
463 if std::env::var_os("BREP_CAM_DEBUG").is_some() {
464 eprintln!("cam job {}: the driver said stop; cancelling", self.operation);
465 }
466 self.token.cancel();
467 }
468 }
469}
470
471pub const CAM_TRIANGLE_BUDGET: usize = 4_000_000;
474
475fn cam_generate_reply(job: CamJob, post: &mut dyn FnMut(Reply) -> bool) -> CamReply {
477 use brep_cam::kernel_adapter::{machine_snapshot, revolved_faces, BodyInput};
478 use brep_cam::{Runner, Tolerance, TreeIndex, TreeOptions, WorkBudget};
479 let operation = job.operation.id.as_str().to_string();
480 let token = brep_cam::CancelToken::new();
481 let mut sink = CamSink { id: job.id, operation: operation.clone(), token: token.clone(), post };
482 let mut certified_turning_profile = None;
483 let mut setup_start = None;
484 let mut outcome = || -> Result<(brep_cam::model::Toolpath, u64), String> {
485 let mut bodies: Vec<(String, brep_kernel::BrepSolid)> = Vec::new();
487 for (name, handle) in &job.solids {
488 let solid = brep_kernel::registered_solid_clone(*handle)
489 .map_err(|e| format!("body `{name}` is not resident on the runner ({e}); run the history again"))?;
490 bodies.push((name.clone(), solid));
491 }
492 let diagonal = bodies
493 .iter()
494 .filter_map(|(_, s)| brep_cam::kernel_adapter::body_bounds(s, job.chord_tolerance).ok())
495 .fold(brep_cam::model::Bounds3::empty(), |acc, b| acc.union(&b))
496 .diagonal();
497 let tolerance = if diagonal.is_finite() && diagonal > 0.0 { Tolerance::scaled_for_diagonal(diagonal) } else { Tolerance::default() };
498 let mut snapshot_setup = job.setup.clone();
502 for name in &job.operation.targets.target_solids {
503 if !snapshot_setup.targets.iter().any(|t| &t.solid == name) {
504 snapshot_setup.targets.push(brep_cam::model::TargetRef { solid: name.clone(), face: None });
505 }
506 }
507 for name in &job.operation.targets.protected_solids {
508 let listed = snapshot_setup.targets.iter().chain(snapshot_setup.protected.iter()).any(|t| &t.solid == name);
509 if !listed {
510 snapshot_setup.protected.push(brep_cam::model::TargetRef { solid: name.clone(), face: None });
511 }
512 }
513 let (mesh, unresolved) = machine_snapshot(&snapshot_setup, job.chord_tolerance, tolerance, |name| bodies.iter().find(|(n, _)| n == name).map(|(_, s)| s))
514 .map_err(|e| format!("{e}"))?;
515 if !unresolved.is_empty() {
516 return Err(format!("the setup names bodies the model does not have: {}", unresolved.join(", ")));
517 }
518 if std::env::var_os("BREP_CAM_DEBUG").is_some() {
519 eprintln!("cam job {}: {} bodies, snapshot {} faces / {} triangles, bounds {:?}", operation, bodies.len(), mesh.faces.len(), mesh.triangles.len(), mesh.bounds);
520 }
521 if mesh.triangles.len() > CAM_TRIANGLE_BUDGET {
525 return Err(format!(
526 "the part mesh needs {} triangles at a chord of {}, over the budget of {CAM_TRIANGLE_BUDGET}; raise the operation's tolerance or machine fewer bodies at once",
527 mesh.triangles.len(),
528 job.chord_tolerance
529 ));
530 }
531 let index = TreeIndex::build(&mesh, TreeOptions::default());
532 let mut runner = Runner::new(token.clone(), &mut sink, WorkBudget::new(job.budget_chunk.max(1))).with_operation(operation.clone());
533 let holes = if matches!(job.operation.strategy, brep_cam::model::Strategy::Drill(_)) {
535 let mut carriers = Vec::new();
536 for (name, solid) in &bodies {
537 if job.setup.targets.iter().any(|t| t.solid == *name) || job.operation.targets.target_solids.contains(name) {
538 let body = BodyInput { name, solid, protected_only: false };
539 carriers.extend(revolved_faces(&body, &job.setup));
540 }
541 }
542 brep_cam::holes::recognize(&carriers, &mesh, tolerance, &mut runner).map_err(|e| e.to_string())?.holes
543 } else {
544 Vec::new()
545 };
546 let mut effective_operation = job.operation.clone();
550 let mut automatic_geometry = None;
551 let mut rebound = false;
552 if let Some(binding) = &job.autoprogram {
553 let tool = job.tools.iter().find(|t| t.id == job.operation.tool).ok_or("automatic programming tool is missing")?;
554 binding.validate_inputs(&job.operation, &job.setup, tool)?;
555 let fingerprints: std::collections::BTreeMap<_, _> = bodies.iter().map(|(name, solid)| (name.clone(), crate::scene::brep_fingerprint(solid))).collect();
556 if fingerprints == binding.body_fingerprints {
557 automatic_geometry = Some(binding.geometry.clone());
558 } else {
559 let recognized = brep_cam::autoprogram::binding::recognize_bodies(&bodies, &job.setup, &mesh, job.chord_tolerance, tolerance, &mut runner).map_err(|e| e.to_string())?;
560 let model_bounds = bodies.iter().filter(|(name, _)| job.setup.targets.iter().any(|t| &t.solid == name))
561 .filter_map(|(_, s)| brep_cam::kernel_adapter::body_bounds(s, job.chord_tolerance).ok())
562 .fold(brep_cam::model::Bounds3::empty(), |a, b| a.union(&b));
563 let stock_bounds = match &job.setup.stock {
564 brep_cam::model::Stock::FromSolid { solid } => bodies.iter().find(|(name, _)| name == solid).and_then(|(_, body)| brep_cam::kernel_adapter::body_bounds(body, job.chord_tolerance).ok()),
565 _ => job.setup.stock.model_bounds(&model_bounds),
566 };
567 let stock_top = stock_bounds.map(|b| job.setup.model_to_machine.apply_bounds(&b).max.z);
568 let (op, geometry) = binding.rebind_with_mesh(&recognized, &mesh, &job.operation, &job.setup, &job.tools, job.machine.as_ref(), stock_top, tolerance)?;
569 effective_operation = op;
570 automatic_geometry = Some(geometry);
571 rebound = true;
572 }
573 }
574 let resolved_planar = match &automatic_geometry { Some(brep_cam::autoprogram::OperationGeometry::Planar(p)) => Some((&effective_operation.id, p)), _ => None };
575 let holes = match &automatic_geometry { Some(brep_cam::autoprogram::OperationGeometry::Holes(h)) => h.as_slice(), _ => holes.as_slice() };
576 let mut pre_op_warning = None;
581 let live_contour = matches!(job.operation.strategy, brep_cam::Strategy::Contour2D(_) | brep_cam::Strategy::Trace(_))
585 && job.operation.live_tool.unwrap_or(true)
586 && job.machine.as_ref().is_some_and(|m| matches!(m.kind, brep_cam::MachineKind::MillTurn { .. }));
587 let pre_op = if !live_contour && brep_cam::uses_pre_op_stock(&job.operation.strategy, job.machine.as_ref()) {
588 let fallback = job.operation.targets.target_solids.clone();
589 let request = brep_cam::JobStock {
590 setup: &job.setup,
591 machine: job.machine.as_ref(),
592 tools: &job.tools,
593 mesh: &mesh,
594 fallback_targets: &fallback,
595 earlier: &job.earlier,
596 previous: &job.previous_setups,
597 tolerance,
598 };
599 match PRE_OP_STOCK.with(|c| brep_cam::job_stock(&mut c.borrow_mut(), &request, &mut runner)) {
600 Ok(result) => Some(result),
601 Err(brep_cam::PreOpStockError::Cancelled) => return Err("cancelled".to_string()),
602 Err(error) => {
603 pre_op_warning = Some(brep_cam::unavailable_warning(&error));
604 None
605 }
606 }
607 } else {
608 None
609 };
610 let input = brep_cam::dispatch::GenerateInput {
611 operation: &effective_operation,
612 setup: &job.setup,
613 tools: &job.tools,
614 mesh: &mesh,
615 index: Some(&index),
616 tolerance,
617 holes,
618 };
619 let solid_refs: Vec<(&str, &brep_kernel::BrepSolid)> = bodies.iter().map(|(n, s)| (n.as_str(), s)).collect();
622 let curve_refs: Vec<(&str, &str, &brep_kernel::NurbsCurve)> = job.chain_curves.iter().map(|(s, g, c)| (s.as_str(), g.as_str(), c)).collect();
623 let profile_refs: Vec<(&str, &brep_kernel::SketchProfile)> = job.chain_profiles.iter().map(|(s, p)| (s.as_str(), p)).collect();
624 let chain_geometry = brep_cam::dispatch::chains::KernelChainGeometry { solids: &solid_refs, curves: &curve_refs, profiles: &profile_refs };
625 let is_turning = matches!(job.operation.strategy, brep_cam::Strategy::TurnFace(_) | brep_cam::Strategy::TurnProfile(_));
626 let mut turning_carriers = Vec::new();
627 let turning_tool = if is_turning {
628 let tool = job.tools.iter().find(|t| t.id == job.operation.tool).ok_or("turning tool is missing")?;
629 if tool.turning_geometry.is_none() { return Err(format!("tool {} has no insert geometry", tool.id)); }
630 if job.setup.turning_stick_out.is_none() { return Err("setup has no stick-out".into()); }
631 let targets: std::collections::BTreeSet<_> = job.setup.targets.iter().map(|t| t.solid.as_str()).collect();
632 if targets.len() != 1 || job.operation.targets.target_solids.iter().any(|n| !targets.contains(n.as_str())) {
633 return Err("turning requires one exact setup solid and matching operation targets".into());
634 }
635 let name = *targets.iter().next().unwrap();
636 let solid = bodies.iter().find(|(n, _)| n == name).map(|(_, s)| s).ok_or("turning setup solid is missing")?;
637 if !job.machine.as_ref().is_some_and(|m| matches!(m.kind, brep_cam::MachineKind::Lathe | brep_cam::MachineKind::MillTurn { .. })) {
638 return Err("turning envelope requires a defined lathe spindle axis".into());
639 }
640 certified_turning_profile = Some(match brep_cam::kernel_adapter::turning_faces(&BodyInput { name, solid, protected_only: false }, &job.setup, tolerance) {
641 Ok(carriers) => {
642 turning_carriers = carriers;
643 brep_cam::turning::profile::extract(&turning_carriers, &mesh, job.operation.tolerance * 0.125, &mut runner)
644 }
645 Err(_) => brep_cam::turning::envelope::extract(&mesh, job.chord_tolerance, &mut runner),
646 }.map_err(|e| e.to_string())?);
647 Some(tool)
648 } else { None };
649 let turning = turning_tool.map(|tool| brep_cam::dispatch::TurningInput {
650 tool_id: &tool.id, tool_geometry: tool.turning_geometry.as_ref().unwrap(),
651 carriers: &turning_carriers, profile: certified_turning_profile.as_ref(), contour: None,
652 stick_out: job.setup.turning_stick_out.unwrap(), groove_center_z: None, thread_radius: None,
653 });
654 let context = brep_cam::dispatch::GenerateContext {
655 turning: turning.as_ref(),
656 chain_geometry: Some(&chain_geometry),
657 machine: job.machine.as_ref(),
658 resolved_planar,
659 rest_stock: pre_op.as_ref().map(|r| &r.stock.grid),
660 pre_op_stock: pre_op.as_ref().map(|r| &r.stock.envelope),
661 source: job.source.as_ref(),
662 source_operation: job.source_operation.as_ref(),
663 ..Default::default()
664 };
665 let mut path = brep_cam::dispatch::generate_with_context(&input, &context, &mut runner).map_err(|e| e.to_string())?;
666 if rebound { path.report.warn(brep_cam::model::Diagnostic::new("autoprogram-re-recognised", "re-recognised after model change")); }
667 if let Some(warning) = pre_op_warning {
668 path.report.warn(warning);
670 path.report.warnings.rotate_right(1);
671 }
672 if let Some(result) = pre_op {
673 path.report.approximations.extend(result.limits);
674 setup_start = Some(result.start);
675 }
676 Ok((path, runner.budget().used()))
677 };
678 match outcome() {
679 Ok((toolpath, work_units)) => CamReply { turning_profile: certified_turning_profile, setup_start, id: job.id, operation, toolpath: Ok(toolpath), work_units, cancelled: false },
680 Err(message) => {
681 let cancelled = token.is_cancelled();
682 CamReply { turning_profile: None, setup_start: None, id: job.id, operation, toolpath: Err(if cancelled { "cancelled".to_string() } else { message }), work_units: 0, cancelled }
683 }
684 }
685}
686
687pub use brep_reconstruction::stl_conversion::{
688 ConversionPolicy, StlConversionOptions, StlConversionOutput,
689};
690
691#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
693pub enum MeshImportFormat {
694 Stl,
695 Obj,
696 ThreeMf,
699}
700
701#[derive(Debug, serde::Serialize, serde::Deserialize)]
704pub struct MeshImportRequest {
705 pub id: u64,
706 pub format: MeshImportFormat,
707 pub bytes: Vec<u8>,
708 pub options: StlConversionOptions,
709}
710
711#[derive(Debug, serde::Serialize, serde::Deserialize)]
714pub struct MeshImportReply {
715 pub id: u64,
716 pub result: Result<StlConversionOutput, String>,
717}
718
719#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
721pub struct PluginInstallRequest {
722 pub id: u64,
723 pub packages: serde_json::Value,
724}
725#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
726pub struct PluginInstallReply {
727 pub id: u64,
728 pub result: Result<brep_plugins::Registry, String>,
729}
730fn validate_plugins(request: PluginInstallRequest) -> PluginInstallReply {
731 let result = serde_json::from_value(request.packages)
732 .map_err(|e| format!("plugin packages: {e}"))
733 .and_then(brep_plugins::Runtime::new)
734 .map(|runtime| runtime.registry().clone());
735 PluginInstallReply { id: request.id, result }
736}
737
738#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
740pub struct PluginActionRequest {
741 pub id: u64,
742 pub pins: Vec<brep_plugins::PluginPin>,
743 pub action: String,
744 pub input: serde_json::Value,
745 #[serde(default, skip_serializing_if = "Option::is_none")]
746 pub script: Option<String>,
747}
748
749#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
750pub struct PluginActionReply {
751 pub id: u64,
752 pub result: Result<brep_plugins::ActionPlan, String>,
753}
754
755pub enum SessionOnMain {
763 Resident,
766 Copied(brep_kernel::SessionTransfer),
768 Unavailable,
770}
771
772pub trait HistoryRunner {
773 fn session_for_main(&mut self, _generation: u64) -> SessionOnMain {
777 SessionOnMain::Unavailable
778 }
779 fn submit_plugin_install(&mut self, _request: PluginInstallRequest) -> bool { false }
780 fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> { None }
781 fn sync_plugins(&mut self, _revision: u64, _fetch: &mut dyn FnMut() -> serde_json::Value) {}
783 fn submit_plugin_action(&mut self, _request: PluginActionRequest) -> bool { false }
785 fn poll_plugin_action(&mut self) -> Option<PluginActionReply> { None }
786
787 fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64);
791 fn poll_run(&mut self) -> Option<RunReply>;
793 fn submit_query(&mut self, query: MeasureQuery);
797 fn poll_query(&mut self) -> Option<MeasureReply>;
799 fn submit_mesh_import(&mut self, request: MeshImportRequest);
801 fn poll_mesh_import(&mut self) -> Option<MeshImportReply>;
803 fn submit_step_probe(&mut self, request: StepProbeRequest);
805 fn poll_step_probe(&mut self) -> Option<StepProbeReply>;
807 fn submit_topology(&mut self, _request: TopologyRequest) {}
811 fn poll_topology(&mut self) -> Option<TopologyReply> {
813 None
814 }
815 fn wait_topology(&mut self, _timeout: std::time::Duration) -> bool {
822 false
823 }
824 fn submit_sheet_lines(&mut self, _request: SheetLinesRequest) -> bool {
828 false
829 }
830 fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
832 None
833 }
834 fn submit_cam(&mut self, _job: CamJob) -> bool {
837 false
838 }
839 fn poll_cam(&mut self) -> Option<CamReply> {
840 None
841 }
842 fn poll_cam_progress(&mut self) -> Option<CamProgress> {
843 None
844 }
845 fn cancel_cam(&mut self) {}
849 fn hold_cam_replies(&mut self, _hold: bool) {}
855 fn submit_cam_sim(&mut self, _job: CamSimJob) -> bool {
858 false
859 }
860 fn poll_cam_sim(&mut self) -> Option<CamSimReply> {
861 None
862 }
863 fn poll_cam_sim_progress(&mut self) -> Option<CamSimProgress> {
864 None
865 }
866 fn cancel_cam_sim(&mut self) {}
870 fn reset(&mut self);
872
873 fn sync_parts_library(
883 &mut self,
884 _revision: u64,
885 _fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
886 ) {
887 }
888
889 fn poll_library_request(&mut self) -> bool {
895 false
896 }
897
898 fn run_reply_waiting(&mut self) -> bool {
902 true
903 }
904
905 fn poll_progress(&mut self) -> Option<RunProgress> {
909 None
910 }
911
912 fn cancel(&mut self) -> bool {
922 false
923 }
924}
925
926fn measure_json(runner: &crate::pipeline::SceneRunner, query: &MeasureQuery) -> String {
934 let Some(handle) = runner.handle_of(&query.owner) else {
935 return serde_json::json!({
936 "ok": false,
937 "message": format!("solid '{}' has no resident geometry", query.owner),
938 })
939 .to_string();
940 };
941 match query.kind {
942 MeasureKind::Inspect => {
943 let issues: Vec<serde_json::Value> = match brep_kernel::validate_handle_native(handle) {
944 Ok(issues) => issues.into_iter().map(|(severity, message)| serde_json::json!({ "severity": severity, "message": message })).collect(),
945 Err(error) => vec![serde_json::json!({ "severity": "error", "message": format!("validation unavailable: {error}") })],
946 };
947 let (volume, volume_error) = match brep_kernel::mass_measures_handle_native(handle) {
948 Ok(properties) => (Some(properties.volume), None),
949 Err(error) => (None, Some(error)),
950 };
951 serde_json::json!({ "ok": true, "kind": "inspect", "issues": issues, "volume": volume, "volumeError": volume_error }).to_string()
952 }
953 MeasureKind::Solid => {
954 match brep_kernel::mass_properties_handle_native(handle, query.density) {
955 Ok(properties) => {
956 let edge_total =
957 brep_kernel::solid_edge_length_total_native(handle).unwrap_or(0.0);
958 serde_json::json!({
959 "ok": true,
960 "kind": "solid",
961 "volume": properties.volume,
962 "surfaceArea": properties.surface_area,
963 "edgeLengthTotal": edge_total,
964 "density": properties.density,
965 "weight": properties.mass,
966 })
967 .to_string()
968 }
969 Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
970 }
971 }
972 MeasureKind::Face => match brep_kernel::face_measurements_native(handle, &query.entity) {
973 Ok((area, edge_total, surface_type)) => {
974 let dimensions =
975 brep_kernel::face_carrier_dimensions_native(handle, &query.entity)
976 .ok()
977 .flatten();
978 let surface_type = dimensions.as_ref().map_or(surface_type, |d| d.label);
981 let mut fragment = serde_json::json!({
982 "ok": true,
983 "kind": "face",
984 "solid": query.owner,
985 "surfaceType": surface_type,
986 "area": area,
987 "edgeLengthTotal": edge_total,
988 });
989 insert_dimensions(&mut fragment, dimensions.as_ref());
990 fragment.to_string()
991 }
992 Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
993 },
994 MeasureKind::Edge => match brep_kernel::edge_length_native(handle, &query.entity) {
995 Ok(length) => {
996 let dimensions =
997 brep_kernel::edge_carrier_dimensions_native(handle, &query.entity)
998 .ok()
999 .flatten();
1000 let mut fragment = serde_json::json!({
1001 "ok": true,
1002 "kind": "edge",
1003 "solid": query.owner,
1004 "length": length,
1005 });
1006 if let Some(dimensions) = &dimensions {
1007 fragment["curveType"] = serde_json::Value::from(dimensions.label);
1008 }
1009 insert_dimensions(&mut fragment, dimensions.as_ref());
1010 fragment.to_string()
1011 }
1012 Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
1013 },
1014 }
1015}
1016
1017fn insert_dimensions(
1020 fragment: &mut serde_json::Value,
1021 dimensions: Option<&brep_kernel::CarrierDimensions>,
1022) {
1023 let (Some(dimensions), Some(object)) = (dimensions, fragment.as_object_mut()) else {
1024 return;
1025 };
1026 for (key, value) in &dimensions.values {
1027 object.insert((*key).to_string(), serde_json::Value::from(*value));
1028 }
1029}
1030
1031pub struct InlineRunner {
1034 runner: crate::pipeline::SceneRunner,
1036 pending: std::collections::VecDeque<RunReply>,
1039 query_pending: std::collections::VecDeque<MeasureReply>,
1043 mesh_import_pending: std::collections::VecDeque<MeshImportReply>,
1044 step_probe_pending: std::collections::VecDeque<StepProbeReply>,
1045 topology_pending: std::collections::VecDeque<TopologyReply>,
1046 sheet_lines_pending: std::collections::VecDeque<SheetLinesReply>,
1047 plugin_action_pending: std::collections::VecDeque<PluginActionReply>,
1048 plugin_install_pending: std::collections::VecDeque<PluginInstallReply>,
1049}
1050
1051impl InlineRunner {
1052 pub fn new() -> Self {
1053 Self {
1054 runner: crate::pipeline::SceneRunner::new(),
1055 pending: std::collections::VecDeque::new(),
1056 query_pending: std::collections::VecDeque::new(),
1057 mesh_import_pending: std::collections::VecDeque::new(),
1058 step_probe_pending: std::collections::VecDeque::new(),
1059 topology_pending: std::collections::VecDeque::new(),
1060 sheet_lines_pending: std::collections::VecDeque::new(),
1061 plugin_action_pending: std::collections::VecDeque::new(),
1062 plugin_install_pending: std::collections::VecDeque::new(),
1063 }
1064 }
1065}
1066
1067impl HistoryRunner for InlineRunner {
1068 fn submit_plugin_install(&mut self, request: PluginInstallRequest) -> bool {
1069 self.plugin_install_pending.push_back(validate_plugins(request));
1070 true
1071 }
1072 fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> {
1073 self.plugin_install_pending.pop_front()
1074 }
1075 fn sync_plugins(&mut self, revision: u64, fetch: &mut dyn FnMut() -> serde_json::Value) {
1076 if self.runner.plugin_revision != Some(revision) {
1077 self.runner.set_plugins(revision, fetch());
1078 }
1079 }
1080 fn submit_plugin_action(&mut self, request: PluginActionRequest) -> bool {
1081 self.plugin_action_pending.push_back(self.runner.plugin_action(request));
1082 true
1083 }
1084 fn poll_plugin_action(&mut self) -> Option<PluginActionReply> {
1085 self.plugin_action_pending.pop_front()
1086 }
1087
1088 fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64) {
1089 let serial = brep_kernel::assembly_session_serial();
1090 let output = self.runner.run(&request);
1091 let after = brep_kernel::assembly_session_serial();
1092 self.runner.session_of = (after != serial).then_some((generation, after));
1093 self.pending.push_back(RunReply { generation, output });
1094 }
1095
1096 fn session_for_main(&mut self, generation: u64) -> SessionOnMain {
1099 match self.runner.session_of {
1100 Some((ran, serial)) if ran == generation && serial == brep_kernel::assembly_session_serial() => {
1101 SessionOnMain::Resident
1102 }
1103 _ => SessionOnMain::Unavailable,
1104 }
1105 }
1106
1107 fn sync_parts_library(
1112 &mut self,
1113 _revision: u64,
1114 _fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
1115 ) {
1116 }
1117
1118 fn poll_run(&mut self) -> Option<RunReply> {
1119 self.pending.pop_front()
1120 }
1121
1122 fn submit_query(&mut self, query: MeasureQuery) {
1123 let result = measure_json(&self.runner, &query);
1124 self.query_pending.push_back(MeasureReply { id: query.id, result });
1125 }
1126
1127 fn poll_query(&mut self) -> Option<MeasureReply> {
1128 self.query_pending.pop_front()
1129 }
1130
1131 fn submit_mesh_import(&mut self, request: MeshImportRequest) {
1132 self.mesh_import_pending
1133 .push_back(reconstruct_mesh(request));
1134 }
1135
1136 fn poll_mesh_import(&mut self) -> Option<MeshImportReply> {
1137 self.mesh_import_pending.pop_front()
1138 }
1139
1140 fn submit_step_probe(&mut self, request: StepProbeRequest) {
1141 self.step_probe_pending.push_back(probe_step(request));
1142 }
1143
1144 fn poll_step_probe(&mut self) -> Option<StepProbeReply> {
1145 self.step_probe_pending.pop_front()
1146 }
1147
1148 fn submit_topology(&mut self, request: TopologyRequest) {
1149 self.topology_pending.push_back(topology_reply(request));
1150 }
1151
1152 fn poll_topology(&mut self) -> Option<TopologyReply> {
1153 self.topology_pending.pop_front()
1154 }
1155
1156 fn submit_sheet_lines(&mut self, request: SheetLinesRequest) -> bool {
1159 self.sheet_lines_pending.push_back(sheet_lines_reply(request));
1160 true
1161 }
1162
1163 fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
1164 self.sheet_lines_pending.pop_front()
1165 }
1166
1167 fn reset(&mut self) {
1168 self.runner.reset();
1169 }
1170}
1171
1172impl Default for InlineRunner {
1173 fn default() -> Self {
1174 Self::new()
1175 }
1176}
1177
1178#[derive(serde::Serialize, serde::Deserialize)]
1192pub enum Command {
1193 ValidatePlugins(PluginInstallRequest),
1194 SetPlugins { revision: u64, packages: serde_json::Value },
1195 PluginAction(PluginActionRequest),
1196 Run {
1197 request: brep_kernel::HistoryRequest,
1198 generation: u64,
1199 #[serde(default)]
1203 parts_library_revision: u64,
1204 },
1205 SetPartsLibrary {
1217 library: brep_kernel::PartsLibraryMap,
1218 revision: u64,
1219 },
1220 Query(MeasureQuery),
1221 MeshImport(MeshImportRequest),
1222 StepProbe(StepProbeRequest),
1223 Topology(TopologyRequest),
1224 SheetLines(SheetLinesRequest),
1225 CamGenerate(CamJob),
1227 CamSimulate(CamSimJob),
1229 Reset,
1230 #[serde(skip)]
1233 ExportSession { generation: u64 },
1234}
1235
1236#[derive(serde::Serialize, serde::Deserialize)]
1238pub enum Reply {
1239 ValidatedPlugins(PluginInstallReply),
1240 PluginAction(PluginActionReply),
1241 Run(RunReply),
1242 Query(MeasureReply),
1243 MeshImport(MeshImportReply),
1244 StepProbe(StepProbeReply),
1245 Topology(TopologyReply),
1246 SheetLines(SheetLinesReply),
1247 CamGenerate(CamReply),
1248 CamProgress(CamProgress),
1250 CamSimulate(CamSimReply),
1251 CamSimProgress(CamSimProgress),
1253 NeedPartsLibrary,
1260 Progress(RunProgress),
1264 #[serde(skip)]
1266 SessionExport { generation: u64, transfer: Option<brep_kernel::SessionTransfer> },
1267}
1268
1269fn probe_step(request: StepProbeRequest) -> StepProbeReply {
1272 StepProbeReply {
1273 id: request.id,
1274 result: brep_kernel::read_step_assembly(&request.text),
1275 }
1276}
1277
1278fn reconstruct_mesh(request: MeshImportRequest) -> MeshImportReply {
1279 let result = (|| {
1280 use brep_reconstruction::stl_conversion::{
1281 binary_stl_coordinate_precision_tolerance, convert_stl_mesh_to_step_with_colors,
1282 };
1283 use brep_reconstruction::{Mesh, Vec3};
1284
1285 let mut instances = Vec::new();
1287 let mut reader_messages = Vec::new();
1288 let mut triangle_colors: Vec<Option<[u8; 3]>> = Vec::new();
1291 let (mesh, positions, indices, coordinate_precision_tolerance) = match request.format {
1292 MeshImportFormat::Stl => {
1293 use brep_reconstruction::stl::{parse_stl_bytes, StlFormat, StlReadOptions};
1294 let read_options = StlReadOptions {
1295 weld_tolerance: (request.options.weld_tolerance >= 0.0)
1296 .then_some(request.options.weld_tolerance),
1297 };
1298 let imported = parse_stl_bytes(&request.bytes, &read_options)
1299 .map_err(|error| format!("STL import failed: {error}"))?;
1300 let positions = imported
1301 .mesh
1302 .vertices
1303 .iter()
1304 .flat_map(|point| [point.x, point.y, point.z])
1305 .collect::<Vec<_>>();
1306 let indices = imported
1307 .mesh
1308 .triangles
1309 .iter()
1310 .flatten()
1311 .copied()
1312 .collect::<Vec<_>>();
1313 let precision = if imported.format == StlFormat::Binary {
1314 binary_stl_coordinate_precision_tolerance(&imported.mesh)
1315 } else {
1316 0.0
1317 };
1318 if !imported.dropped_facets.is_empty() {
1319 reader_messages.push(format!(
1320 "the STL reader dropped {} of {} facet(s) with no area: {}",
1321 imported.dropped_facets.len(),
1322 imported.source_triangle_count,
1323 imported.dropped_facets.iter().take(8).map(ToString::to_string).collect::<Vec<_>>().join("; ")
1324 ));
1325 }
1326 (imported.mesh, positions, indices, precision)
1327 }
1328 MeshImportFormat::Obj => {
1329 let text = std::str::from_utf8(&request.bytes)
1330 .map_err(|_| "OBJ import failed: file is not UTF-8 text".to_string())?;
1331 let obj = brep_kernel::read_obj(text)
1332 .map_err(|error| format!("OBJ import failed: {error}"))?;
1333 let vertices = obj
1334 .positions
1335 .chunks_exact(3)
1336 .map(|point| Vec3::new(point[0], point[1], point[2]))
1337 .collect::<Vec<_>>();
1338 let triangles = obj
1339 .indices
1340 .chunks_exact(3)
1341 .map(|triangle| [triangle[0], triangle[1], triangle[2]])
1342 .collect::<Vec<_>>();
1343 (
1344 Mesh::new(vertices, triangles),
1345 obj.positions,
1346 obj.indices,
1347 0.0,
1348 )
1349 }
1350 MeshImportFormat::ThreeMf => {
1359 let model = brep_kernel::read_3mf(&request.bytes)
1360 .map_err(|error| format!("3MF import failed: {error}"))?;
1361 instances = model.instances;
1362 triangle_colors = model.triangle_colors;
1363 let vertices = model
1364 .positions
1365 .chunks_exact(3)
1366 .map(|point| Vec3::new(point[0], point[1], point[2]))
1367 .collect::<Vec<_>>();
1368 let triangles = model
1369 .indices
1370 .chunks_exact(3)
1371 .map(|triangle| [triangle[0], triangle[1], triangle[2]])
1372 .collect::<Vec<_>>();
1373 (
1374 Mesh::new(vertices, triangles),
1375 model.positions,
1376 model.indices,
1377 0.0,
1378 )
1379 }
1380 };
1381 let mut options = request.options;
1382 options.coordinate_precision_tolerance = options.coordinate_precision_tolerance
1383 .max(coordinate_precision_tolerance);
1384 convert_stl_mesh_to_step_with_colors(
1385 &mesh,
1386 &positions,
1387 Some(&indices),
1388 (!triangle_colors.is_empty()).then_some(triangle_colors.as_slice()),
1389 &options,
1390 "Imported mesh",
1391 "MM",
1392 "",
1393 )
1394 .map(|mut output| {
1395 name_bodies(&mut output.report, &instances);
1396 output.report.messages.splice(0..0, reader_messages);
1397 output
1398 })
1399 .map_err(|error| format!("RANSAC reconstruction failed: {error}"))
1400 })();
1401 MeshImportReply {
1402 id: request.id,
1403 result,
1404 }
1405}
1406
1407fn name_bodies(
1411 report: &mut brep_reconstruction::stl_conversion::StlConversionReport,
1412 instances: &[brep_kernel::ThreeMfInstance],
1413) {
1414 for component in &mut report.components {
1415 component.source_name = instances
1416 .iter()
1417 .find(|instance| {
1418 (instance.first_triangle..instance.first_triangle + instance.triangle_count)
1419 .contains(&component.first_triangle)
1420 })
1421 .and_then(|instance| instance.name.clone());
1422 }
1423}
1424
1425pub(crate) fn refused_bodies_notice(
1429 report: &brep_reconstruction::stl_conversion::StlConversionReport,
1430) -> Option<String> {
1431 let refused = report
1432 .components
1433 .iter()
1434 .filter_map(|component| {
1435 component.refusal.as_ref().map(|reason| {
1436 let body = match &component.source_name {
1437 Some(name) => format!("body {} \"{name}\"", component.index + 1),
1438 None => format!(
1439 "body {} (from source triangle {})",
1440 component.index + 1,
1441 component.first_triangle
1442 ),
1443 };
1444 format!("{body}: {reason}")
1445 })
1446 })
1447 .collect::<Vec<_>>();
1448 (!refused.is_empty()).then(|| {
1449 format!(
1450 "mesh import: {} of {} bodies could not be reconstructed and are NOT in the model — {}",
1451 refused.len(),
1452 report.components.len(),
1453 refused.join("; ")
1454 )
1455 })
1456}
1457
1458pub fn process_command(
1471 runner: &mut crate::pipeline::SceneRunner,
1472 command: Command,
1473 progress: &mut dyn FnMut(Reply) -> bool,
1474) -> Option<Reply> {
1475 match command {
1476 Command::ValidatePlugins(request) => Some(Reply::ValidatedPlugins(validate_plugins(request))),
1477 Command::SetPlugins { revision, packages } => {
1478 runner.set_plugins(revision, packages);
1479 None
1480 }
1481 Command::PluginAction(request) => Some(Reply::PluginAction(runner.plugin_action(request))),
1482 Command::Run {
1483 request,
1484 generation,
1485 parts_library_revision,
1486 } => {
1487 let stale = match runner.parts_library_revision {
1499 Some(installed) => installed != parts_library_revision,
1500 None => parts_library_revision != 0,
1506 };
1507 let recoverable = brep_kernel::missing_library_parts(&request)
1514 .iter()
1515 .any(|name| runner.parts_library_names.contains(name));
1516 if stale || recoverable {
1517 runner.parts_library_revision = None;
1518 return Some(Reply::NeedPartsLibrary);
1519 }
1520 let serial = brep_kernel::assembly_session_serial();
1521 let output = runner.run_observed(&request, &mut |event| {
1522 progress(Reply::Progress(RunProgress {
1523 generation,
1524 index: event.index,
1525 total: event.total,
1526 feature_id: event.id.to_string(),
1527 feature_type: event.feature_type.to_string(),
1528 }))
1529 });
1530 let after = brep_kernel::assembly_session_serial();
1531 runner.session_of = (after != serial).then_some((generation, after));
1532 Some(Reply::Run(RunReply { generation, output }))
1533 }
1534 Command::SetPartsLibrary { library, revision } => {
1535 runner.parts_library_names = library.keys().cloned().collect();
1536 brep_kernel::install_parts_library(&library);
1537 runner.parts_library_revision = Some(revision);
1538 None
1539 }
1540 Command::Query(query) => {
1541 let result = measure_json(runner, &query);
1542 Some(Reply::Query(MeasureReply { id: query.id, result }))
1543 }
1544 Command::MeshImport(request) => Some(Reply::MeshImport(reconstruct_mesh(request))),
1545 Command::StepProbe(request) => Some(Reply::StepProbe(probe_step(request))),
1546 Command::Topology(request) => Some(Reply::Topology(topology_reply(request))),
1547 Command::SheetLines(request) => Some(Reply::SheetLines(sheet_lines_reply(request))),
1548 Command::CamGenerate(job) => Some(Reply::CamGenerate(cam_generate_reply(job, progress))),
1549 Command::CamSimulate(job) => Some(Reply::CamSimulate(cam_sim_reply(job, progress))),
1550 Command::Reset => {
1551 runner.reset();
1556 brep_kernel::clear_history_cache();
1557 cam_sim_clear();
1558 pre_op_stock_clear();
1559 None
1560 }
1561 Command::ExportSession { generation } => {
1562 Some(Reply::SessionExport { generation, transfer: runner.export_session(generation) })
1563 }
1564 }
1565}
1566
1567#[cfg(not(target_arch = "wasm32"))]
1581pub struct ThreadRunner {
1582 tx: Option<std::sync::mpsc::Sender<Command>>,
1585 rx: std::sync::mpsc::Receiver<Reply>,
1587 handle: Option<std::thread::JoinHandle<()>>,
1589 run_buf: std::collections::VecDeque<RunReply>,
1591 query_buf: std::collections::VecDeque<MeasureReply>,
1593 mesh_import_buf: std::collections::VecDeque<MeshImportReply>,
1594 step_probe_buf: std::collections::VecDeque<StepProbeReply>,
1595 topology_buf: std::collections::VecDeque<TopologyReply>,
1596 sheet_lines_buf: std::collections::VecDeque<SheetLinesReply>,
1597 cam_buf: std::collections::VecDeque<CamReply>,
1598 cam_hold: bool,
1600 cam_progress_buf: std::collections::VecDeque<CamProgress>,
1601 cam_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1604 cam_sim_buf: std::collections::VecDeque<CamSimReply>,
1605 cam_sim_progress_buf: std::collections::VecDeque<CamSimProgress>,
1606 cam_sim_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1608 plugin_action_buf: std::collections::VecDeque<PluginActionReply>,
1609 plugin_install_buf: std::collections::VecDeque<PluginInstallReply>,
1610 sent_plugin_revision: Option<u64>,
1611 progress_buf: std::collections::VecDeque<RunProgress>,
1614 sent_library_revision: Option<u64>,
1618 library_requested: bool,
1621 stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
1625}
1626
1627#[cfg(not(target_arch = "wasm32"))]
1628impl ThreadRunner {
1629 pub fn new() -> Self {
1630 let (tx, cmd_rx) = std::sync::mpsc::channel::<Command>();
1631 let (reply_tx, rx) = std::sync::mpsc::channel::<Reply>();
1632 let stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1633 let thread_stop = stop.clone();
1634 let cam_cancel = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1635 let thread_cam_cancel = cam_cancel.clone();
1636 let cam_sim_cancel = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1637 let thread_cam_sim_cancel = cam_sim_cancel.clone();
1638 let handle = std::thread::Builder::new()
1639 .name("brep-history-runner".to_string())
1640 .spawn(move || thread_main(cmd_rx, reply_tx, thread_stop, thread_cam_cancel, thread_cam_sim_cancel))
1641 .expect("spawn brep-history-runner thread");
1642 Self {
1643 tx: Some(tx),
1644 rx,
1645 handle: Some(handle),
1646 run_buf: std::collections::VecDeque::new(),
1647 query_buf: std::collections::VecDeque::new(),
1648 mesh_import_buf: std::collections::VecDeque::new(),
1649 step_probe_buf: std::collections::VecDeque::new(),
1650 topology_buf: std::collections::VecDeque::new(),
1651 sheet_lines_buf: std::collections::VecDeque::new(),
1652 cam_buf: std::collections::VecDeque::new(),
1653 cam_hold: false,
1654 cam_progress_buf: std::collections::VecDeque::new(),
1655 cam_cancel,
1656 cam_sim_buf: std::collections::VecDeque::new(),
1657 cam_sim_progress_buf: std::collections::VecDeque::new(),
1658 cam_sim_cancel,
1659 plugin_action_buf: std::collections::VecDeque::new(),
1660 plugin_install_buf: std::collections::VecDeque::new(),
1661 sent_plugin_revision: None,
1662 progress_buf: std::collections::VecDeque::new(),
1663 sent_library_revision: None,
1664 library_requested: false,
1665 stop,
1666 }
1667 }
1668
1669 fn drain(&mut self) {
1672 while let Ok(reply) = self.rx.try_recv() {
1673 self.file(reply);
1674 }
1675 }
1676
1677 fn file(&mut self, reply: Reply) -> Option<(u64, Option<brep_kernel::SessionTransfer>)> {
1680 match reply {
1681 Reply::SessionExport { generation, transfer } => return Some((generation, transfer)),
1682 Reply::ValidatedPlugins(reply) => self.plugin_install_buf.push_back(reply),
1683 Reply::PluginAction(reply) => self.plugin_action_buf.push_back(reply),
1684 Reply::Run(run) => self.run_buf.push_back(run),
1685 Reply::Query(query) => self.query_buf.push_back(query),
1686 Reply::MeshImport(reply) => self.mesh_import_buf.push_back(reply),
1687 Reply::StepProbe(reply) => self.step_probe_buf.push_back(reply),
1688 Reply::Topology(reply) => self.topology_buf.push_back(reply),
1689 Reply::SheetLines(reply) => self.sheet_lines_buf.push_back(reply),
1690 Reply::CamGenerate(reply) => self.cam_buf.push_back(reply),
1691 Reply::CamProgress(progress) => self.cam_progress_buf.push_back(progress),
1692 Reply::CamSimulate(reply) => self.cam_sim_buf.push_back(reply),
1693 Reply::CamSimProgress(progress) => self.cam_sim_progress_buf.push_back(progress),
1694 Reply::Progress(progress) => self.progress_buf.push_back(progress),
1695 Reply::NeedPartsLibrary => {
1696 self.sent_library_revision = None;
1701 self.library_requested = true;
1702 }
1703 }
1704 None
1705 }
1706}
1707
1708#[cfg(not(target_arch = "wasm32"))]
1711const SESSION_EXPORT_WAIT: std::time::Duration = std::time::Duration::from_millis(250);
1712
1713#[cfg(not(target_arch = "wasm32"))]
1714impl Default for ThreadRunner {
1715 fn default() -> Self {
1716 Self::new()
1717 }
1718}
1719
1720#[cfg(not(target_arch = "wasm32"))]
1734fn thread_main(
1735 cmd_rx: std::sync::mpsc::Receiver<Command>,
1736 reply_tx: std::sync::mpsc::Sender<Reply>,
1737 stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
1738 cam_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1739 cam_sim_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1740) {
1741 let mut runner = crate::pipeline::SceneRunner::new();
1742 while let Ok(first) = cmd_rx.recv() {
1743 if stop.load(std::sync::atomic::Ordering::Relaxed) {
1744 return;
1745 }
1746 let mut batch = vec![first];
1748 loop {
1749 match cmd_rx.try_recv() {
1750 Ok(command) => batch.push(command),
1751 Err(_) => break, }
1753 }
1754 let mut run_here: Vec<bool> = vec![true; batch.len()];
1758 for i in 0..batch.len() {
1759 if matches!(batch[i], Command::Run { .. })
1760 && matches!(batch.get(i + 1), Some(Command::Run { .. }))
1761 {
1762 run_here[i] = false;
1763 }
1764 }
1765 for (i, command) in batch.into_iter().enumerate() {
1770 if !run_here[i] {
1771 continue;
1772 }
1773 let is_cam = matches!(command, Command::CamGenerate(_));
1776 if is_cam {
1777 cam_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
1778 }
1779 let is_sim = matches!(command, Command::CamSimulate(_));
1780 let mut progress = |reply: Reply| {
1781 reply_tx.send(reply).is_ok()
1784 && !stop.load(std::sync::atomic::Ordering::Relaxed)
1785 && !(is_cam && cam_cancel.load(std::sync::atomic::Ordering::SeqCst))
1786 && !(is_sim && cam_sim_cancel.load(std::sync::atomic::Ordering::SeqCst))
1787 };
1788 if let Some(reply) = process_command(&mut runner, command, &mut progress) {
1789 if reply_tx.send(reply).is_err() {
1790 return; }
1792 }
1793 }
1794 }
1795}
1796
1797#[cfg(not(target_arch = "wasm32"))]
1798impl HistoryRunner for ThreadRunner {
1799 fn session_for_main(&mut self, generation: u64) -> SessionOnMain {
1800 let Some(tx) = &self.tx else { return SessionOnMain::Unavailable };
1801 if tx.send(Command::ExportSession { generation }).is_err() {
1802 return SessionOnMain::Unavailable;
1803 }
1804 let deadline = std::time::Instant::now() + SESSION_EXPORT_WAIT;
1805 loop {
1806 let left = deadline.saturating_duration_since(std::time::Instant::now());
1807 let Ok(reply) = self.rx.recv_timeout(left) else { return SessionOnMain::Unavailable };
1808 if let Some((answered, transfer)) = self.file(reply) {
1809 if answered != generation {
1810 continue; }
1812 return transfer.map_or(SessionOnMain::Unavailable, SessionOnMain::Copied);
1813 }
1814 }
1815 }
1816 fn submit_plugin_install(&mut self, request: PluginInstallRequest) -> bool {
1817 self.tx.as_ref().is_some_and(|tx| tx.send(Command::ValidatePlugins(request)).is_ok())
1818 }
1819 fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> {
1820 self.drain();
1821 self.plugin_install_buf.pop_front()
1822 }
1823 fn sync_plugins(&mut self, revision: u64, fetch: &mut dyn FnMut() -> serde_json::Value) {
1824 if self.sent_plugin_revision != Some(revision) {
1825 if let Some(tx) = &self.tx {
1826 if tx.send(Command::SetPlugins { revision, packages: fetch() }).is_ok() {
1827 self.sent_plugin_revision = Some(revision);
1828 }
1829 }
1830 }
1831 }
1832 fn submit_plugin_action(&mut self, request: PluginActionRequest) -> bool {
1833 self.tx.as_ref().is_some_and(|tx| tx.send(Command::PluginAction(request)).is_ok())
1834 }
1835 fn poll_plugin_action(&mut self) -> Option<PluginActionReply> {
1836 self.drain();
1837 self.plugin_action_buf.pop_front()
1838 }
1839
1840 fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64) {
1841 if let Some(tx) = &self.tx {
1842 let _ = tx.send(Command::Run {
1843 request,
1844 generation,
1845 parts_library_revision: self.sent_library_revision.unwrap_or(0),
1846 });
1847 }
1848 }
1849
1850 fn sync_parts_library(
1851 &mut self,
1852 revision: u64,
1853 fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
1854 ) {
1855 if self.sent_library_revision == Some(revision) {
1856 return;
1857 }
1858 if let Some(tx) = &self.tx {
1859 let _ = tx.send(Command::SetPartsLibrary {
1860 library: fetch(),
1861 revision,
1862 });
1863 self.sent_library_revision = Some(revision);
1864 }
1865 }
1866
1867 fn poll_library_request(&mut self) -> bool {
1868 self.drain();
1869 std::mem::take(&mut self.library_requested)
1870 }
1871
1872 fn poll_run(&mut self) -> Option<RunReply> {
1873 self.drain();
1874 self.run_buf.pop_front()
1875 }
1876
1877 fn run_reply_waiting(&mut self) -> bool {
1878 self.drain();
1879 !self.run_buf.is_empty() || self.library_requested
1880 }
1881
1882 fn submit_query(&mut self, query: MeasureQuery) {
1883 if let Some(tx) = &self.tx {
1884 let _ = tx.send(Command::Query(query));
1885 }
1886 }
1887
1888 fn poll_query(&mut self) -> Option<MeasureReply> {
1889 self.drain();
1890 self.query_buf.pop_front()
1891 }
1892
1893 fn submit_mesh_import(&mut self, request: MeshImportRequest) {
1894 if let Some(tx) = &self.tx {
1895 let _ = tx.send(Command::MeshImport(request));
1896 }
1897 }
1898
1899 fn poll_mesh_import(&mut self) -> Option<MeshImportReply> {
1900 self.drain();
1901 self.mesh_import_buf.pop_front()
1902 }
1903
1904 fn submit_step_probe(&mut self, request: StepProbeRequest) {
1905 if let Some(tx) = &self.tx {
1906 let _ = tx.send(Command::StepProbe(request));
1907 }
1908 }
1909
1910 fn poll_step_probe(&mut self) -> Option<StepProbeReply> {
1911 self.drain();
1912 self.step_probe_buf.pop_front()
1913 }
1914
1915 fn submit_topology(&mut self, request: TopologyRequest) {
1916 if let Some(tx) = &self.tx {
1917 let _ = tx.send(Command::Topology(request));
1918 }
1919 }
1920
1921 fn poll_topology(&mut self) -> Option<TopologyReply> {
1922 self.drain();
1923 self.topology_buf.pop_front()
1924 }
1925
1926 fn wait_topology(&mut self, timeout: std::time::Duration) -> bool {
1927 self.drain();
1928 let deadline = std::time::Instant::now() + timeout;
1929 while self.topology_buf.is_empty() {
1930 let now = std::time::Instant::now();
1931 if now >= deadline {
1932 return false;
1933 }
1934 match self.rx.recv_timeout(deadline - now) {
1935 Ok(reply) => {
1936 self.file(reply);
1937 }
1938 Err(_) => return false,
1939 }
1940 }
1941 true
1942 }
1943
1944 fn submit_sheet_lines(&mut self, request: SheetLinesRequest) -> bool {
1945 match &self.tx {
1946 Some(tx) => tx.send(Command::SheetLines(request)).is_ok(),
1947 None => false,
1948 }
1949 }
1950
1951 fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
1952 self.drain();
1953 self.sheet_lines_buf.pop_front()
1954 }
1955
1956 fn submit_cam(&mut self, job: CamJob) -> bool {
1957 self.cam_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
1958 match &self.tx {
1959 Some(tx) => tx.send(Command::CamGenerate(job)).is_ok(),
1960 None => false,
1961 }
1962 }
1963
1964 fn poll_cam(&mut self) -> Option<CamReply> {
1965 self.drain();
1966 if self.cam_hold {
1967 return None;
1968 }
1969 self.cam_buf.pop_front()
1970 }
1971
1972 fn hold_cam_replies(&mut self, hold: bool) {
1973 self.cam_hold = hold;
1974 }
1975
1976 fn poll_cam_progress(&mut self) -> Option<CamProgress> {
1977 self.drain();
1978 let latest = self.cam_progress_buf.pop_back();
1979 self.cam_progress_buf.clear();
1980 latest
1981 }
1982
1983 fn cancel_cam(&mut self) {
1984 self.cam_cancel.store(true, std::sync::atomic::Ordering::SeqCst);
1985 }
1986
1987 fn submit_cam_sim(&mut self, job: CamSimJob) -> bool {
1988 self.cam_sim_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
1990 match &self.tx {
1991 Some(tx) => tx.send(Command::CamSimulate(job)).is_ok(),
1992 None => false,
1993 }
1994 }
1995
1996 fn poll_cam_sim(&mut self) -> Option<CamSimReply> {
1997 self.drain();
1998 self.cam_sim_buf.pop_front()
1999 }
2000
2001 fn poll_cam_sim_progress(&mut self) -> Option<CamSimProgress> {
2002 self.drain();
2003 let latest = self.cam_sim_progress_buf.pop_back();
2004 self.cam_sim_progress_buf.clear();
2005 latest
2006 }
2007
2008 fn cancel_cam_sim(&mut self) {
2009 self.cam_sim_cancel.store(true, std::sync::atomic::Ordering::SeqCst);
2010 }
2011
2012 fn poll_progress(&mut self) -> Option<RunProgress> {
2013 self.drain();
2014 let latest = self.progress_buf.pop_back();
2015 self.progress_buf.clear();
2016 latest
2017 }
2018
2019 fn cancel(&mut self) -> bool {
2025 self.stop.store(true, std::sync::atomic::Ordering::Relaxed);
2026 self.tx.take();
2027 self.handle.take();
2028 *self = Self::new();
2029 true
2030 }
2031
2032 fn reset(&mut self) {
2033 self.sent_plugin_revision = None;
2034 self.plugin_action_buf.clear();
2035 if let Some(tx) = &self.tx {
2036 let _ = tx.send(Command::Reset);
2037 }
2038 self.run_buf.clear();
2041 self.query_buf.clear();
2042 self.mesh_import_buf.clear();
2043 self.step_probe_buf.clear();
2044 self.topology_buf.clear();
2045 self.sheet_lines_buf.clear();
2046 self.progress_buf.clear();
2047 self.sent_library_revision = None;
2050 self.library_requested = false;
2051 }
2052}
2053
2054#[cfg(not(target_arch = "wasm32"))]
2055impl Drop for ThreadRunner {
2056 fn drop(&mut self) {
2057 self.tx.take();
2061 self.handle.take();
2062 }
2063}
2064