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 ConversionBackend, ConversionPolicy, HybridDemotion, StlConversionOptions, StlConversionOutput,
689 StlConversionReport,
690};
691pub use brep_reconstruction::import_session::{
692 FitAttempt, FitKind, ImportRegion, ImportRegions, NurbsFaceChoice, NurbsFaceOutcome, NurbsFit, RegionFit,
693 RegionFitRequest, RegionKind, UNASSIGNED as UNASSIGNED_REGION,
694};
695
696#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
698pub enum MeshImportFormat {
699 Stl,
700 Obj,
701 ThreeMf,
704}
705
706#[derive(Debug, serde::Serialize, serde::Deserialize)]
709pub struct MeshImportRequest {
710 pub id: u64,
711 pub format: MeshImportFormat,
712 pub bytes: Vec<u8>,
713 pub options: StlConversionOptions,
714 #[serde(default)]
718 pub phases: bool,
719}
720
721#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
724pub enum MeshImportPhase {
725 Mesh { positions: Vec<f64>, indices: Vec<u32> },
727 Regions(brep_reconstruction::import_session::ImportRegions),
729 RegionsFailed(String),
731}
732
733#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
734pub struct MeshImportPhaseReply {
735 pub id: u64,
736 pub phase: MeshImportPhase,
737}
738
739#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
742pub struct RegionFitJob {
743 pub id: u64,
744 pub positions: Vec<f64>,
745 pub indices: Vec<u32>,
746 pub request: brep_reconstruction::import_session::RegionFitRequest,
747}
748
749#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
750pub struct RegionFitReply {
751 pub id: u64,
752 pub fit: brep_reconstruction::import_session::RegionFit,
753}
754
755pub fn region_fit_reply(job: RegionFitJob) -> RegionFitReply {
757 RegionFitReply {
758 id: job.id,
759 fit: brep_reconstruction::import_session::fit_region(&job.positions, &job.indices, &job.request),
760 }
761}
762
763#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
767pub struct ApplyRegionChoicesJob {
768 pub id: u64,
769 pub step_text: String,
770 pub positions: Vec<f64>,
771 pub indices: Vec<u32>,
772 pub choices: Vec<NurbsFaceChoice>,
773}
774
775#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
776pub struct ApplyRegionChoicesReply {
777 pub id: u64,
778 pub result: Result<(String, Vec<NurbsFaceOutcome>), String>,
781}
782
783pub fn apply_region_choices_reply(job: ApplyRegionChoicesJob) -> ApplyRegionChoicesReply {
785 ApplyRegionChoicesReply {
786 id: job.id,
787 result: brep_reconstruction::import_session::apply_region_choices(&job.step_text, &job.positions, &job.indices, &job.choices),
788 }
789}
790
791#[derive(Debug, serde::Serialize, serde::Deserialize)]
794pub struct MeshImportReply {
795 pub id: u64,
796 pub result: Result<StlConversionOutput, String>,
797}
798
799#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
801pub struct PluginInstallRequest {
802 pub id: u64,
803 pub packages: serde_json::Value,
804}
805#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
806pub struct PluginInstallReply {
807 pub id: u64,
808 pub result: Result<brep_plugins::Registry, String>,
809}
810fn validate_plugins(request: PluginInstallRequest) -> PluginInstallReply {
811 let result = serde_json::from_value(request.packages)
812 .map_err(|e| format!("plugin packages: {e}"))
813 .and_then(brep_plugins::Runtime::new)
814 .map(|runtime| runtime.registry().clone());
815 PluginInstallReply { id: request.id, result }
816}
817
818#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
820pub struct PluginActionRequest {
821 pub id: u64,
822 pub pins: Vec<brep_plugins::PluginPin>,
823 pub action: String,
824 pub input: serde_json::Value,
825 #[serde(default, skip_serializing_if = "Option::is_none")]
826 pub script: Option<String>,
827}
828
829#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
830pub struct PluginActionReply {
831 pub id: u64,
832 pub result: Result<brep_plugins::ActionPlan, String>,
833}
834
835pub enum SessionOnMain {
843 Resident,
846 Copied(brep_kernel::SessionTransfer),
848 Unavailable,
850}
851
852pub trait HistoryRunner {
853 fn session_for_main(&mut self, _generation: u64) -> SessionOnMain {
857 SessionOnMain::Unavailable
858 }
859 fn submit_plugin_install(&mut self, _request: PluginInstallRequest) -> bool { false }
860 fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> { None }
861 fn sync_plugins(&mut self, _revision: u64, _fetch: &mut dyn FnMut() -> serde_json::Value) {}
863 fn submit_plugin_action(&mut self, _request: PluginActionRequest) -> bool { false }
865 fn poll_plugin_action(&mut self) -> Option<PluginActionReply> { None }
866
867 fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64);
871 fn poll_run(&mut self) -> Option<RunReply>;
873 fn submit_query(&mut self, query: MeasureQuery);
877 fn poll_query(&mut self) -> Option<MeasureReply>;
879 fn submit_mesh_import(&mut self, request: MeshImportRequest);
881 fn poll_mesh_import(&mut self) -> Option<MeshImportReply>;
883 fn poll_mesh_import_phase(&mut self) -> Option<MeshImportPhaseReply> {
886 None
887 }
888 fn submit_region_fit(&mut self, _job: RegionFitJob) -> bool {
891 false
892 }
893 fn poll_region_fit(&mut self) -> Option<RegionFitReply> {
895 None
896 }
897 fn submit_apply_region_choices(&mut self, _job: ApplyRegionChoicesJob) -> bool {
900 false
901 }
902 fn poll_apply_region_choices(&mut self) -> Option<ApplyRegionChoicesReply> {
904 None
905 }
906 fn submit_step_probe(&mut self, request: StepProbeRequest);
908 fn poll_step_probe(&mut self) -> Option<StepProbeReply>;
910 fn submit_topology(&mut self, _request: TopologyRequest) {}
914 fn poll_topology(&mut self) -> Option<TopologyReply> {
916 None
917 }
918 fn wait_topology(&mut self, _timeout: std::time::Duration) -> bool {
925 false
926 }
927 fn submit_sheet_lines(&mut self, _request: SheetLinesRequest) -> bool {
931 false
932 }
933 fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
935 None
936 }
937 fn submit_cam(&mut self, _job: CamJob) -> bool {
940 false
941 }
942 fn poll_cam(&mut self) -> Option<CamReply> {
943 None
944 }
945 fn poll_cam_progress(&mut self) -> Option<CamProgress> {
946 None
947 }
948 fn cancel_cam(&mut self) {}
952 fn hold_cam_replies(&mut self, _hold: bool) {}
958 fn submit_cam_sim(&mut self, _job: CamSimJob) -> bool {
961 false
962 }
963 fn poll_cam_sim(&mut self) -> Option<CamSimReply> {
964 None
965 }
966 fn poll_cam_sim_progress(&mut self) -> Option<CamSimProgress> {
967 None
968 }
969 fn cancel_cam_sim(&mut self) {}
973 fn reset(&mut self);
975
976 fn sync_parts_library(
986 &mut self,
987 _revision: u64,
988 _fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
989 ) {
990 }
991
992 fn poll_library_request(&mut self) -> bool {
998 false
999 }
1000
1001 fn run_reply_waiting(&mut self) -> bool {
1005 true
1006 }
1007
1008 fn poll_progress(&mut self) -> Option<RunProgress> {
1012 None
1013 }
1014
1015 fn cancel(&mut self) -> bool {
1025 false
1026 }
1027}
1028
1029fn measure_json(runner: &crate::pipeline::SceneRunner, query: &MeasureQuery) -> String {
1037 let Some(handle) = runner.handle_of(&query.owner) else {
1038 return serde_json::json!({
1039 "ok": false,
1040 "message": format!("solid '{}' has no resident geometry", query.owner),
1041 })
1042 .to_string();
1043 };
1044 match query.kind {
1045 MeasureKind::Inspect => {
1046 let issues: Vec<serde_json::Value> = match brep_kernel::validate_handle_native(handle) {
1047 Ok(issues) => issues.into_iter().map(|(severity, message)| serde_json::json!({ "severity": severity, "message": message })).collect(),
1048 Err(error) => vec![serde_json::json!({ "severity": "error", "message": format!("validation unavailable: {error}") })],
1049 };
1050 let (volume, volume_error) = match brep_kernel::mass_measures_handle_native(handle) {
1051 Ok(properties) => (Some(properties.volume), None),
1052 Err(error) => (None, Some(error)),
1053 };
1054 serde_json::json!({ "ok": true, "kind": "inspect", "issues": issues, "volume": volume, "volumeError": volume_error }).to_string()
1055 }
1056 MeasureKind::Solid => {
1057 match brep_kernel::mass_properties_handle_native(handle, query.density) {
1058 Ok(properties) => {
1059 let edge_total =
1060 brep_kernel::solid_edge_length_total_native(handle).unwrap_or(0.0);
1061 serde_json::json!({
1062 "ok": true,
1063 "kind": "solid",
1064 "volume": properties.volume,
1065 "surfaceArea": properties.surface_area,
1066 "edgeLengthTotal": edge_total,
1067 "density": properties.density,
1068 "weight": properties.mass,
1069 })
1070 .to_string()
1071 }
1072 Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
1073 }
1074 }
1075 MeasureKind::Face => match brep_kernel::face_measurements_native(handle, &query.entity) {
1076 Ok((area, edge_total, surface_type)) => {
1077 let dimensions =
1078 brep_kernel::face_carrier_dimensions_native(handle, &query.entity)
1079 .ok()
1080 .flatten();
1081 let surface_type = dimensions.as_ref().map_or(surface_type, |d| d.label);
1084 let mut fragment = serde_json::json!({
1085 "ok": true,
1086 "kind": "face",
1087 "solid": query.owner,
1088 "surfaceType": surface_type,
1089 "area": area,
1090 "edgeLengthTotal": edge_total,
1091 });
1092 insert_dimensions(&mut fragment, dimensions.as_ref());
1093 fragment.to_string()
1094 }
1095 Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
1096 },
1097 MeasureKind::Edge => match brep_kernel::edge_length_native(handle, &query.entity) {
1098 Ok(length) => {
1099 let dimensions =
1100 brep_kernel::edge_carrier_dimensions_native(handle, &query.entity)
1101 .ok()
1102 .flatten();
1103 let mut fragment = serde_json::json!({
1104 "ok": true,
1105 "kind": "edge",
1106 "solid": query.owner,
1107 "length": length,
1108 });
1109 if let Some(dimensions) = &dimensions {
1110 fragment["curveType"] = serde_json::Value::from(dimensions.label);
1111 }
1112 insert_dimensions(&mut fragment, dimensions.as_ref());
1113 fragment.to_string()
1114 }
1115 Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
1116 },
1117 }
1118}
1119
1120fn insert_dimensions(
1123 fragment: &mut serde_json::Value,
1124 dimensions: Option<&brep_kernel::CarrierDimensions>,
1125) {
1126 let (Some(dimensions), Some(object)) = (dimensions, fragment.as_object_mut()) else {
1127 return;
1128 };
1129 for (key, value) in &dimensions.values {
1130 object.insert((*key).to_string(), serde_json::Value::from(*value));
1131 }
1132}
1133
1134pub struct InlineRunner {
1137 runner: crate::pipeline::SceneRunner,
1139 pending: std::collections::VecDeque<RunReply>,
1142 query_pending: std::collections::VecDeque<MeasureReply>,
1146 mesh_import_pending: std::collections::VecDeque<MeshImportReply>,
1147 mesh_phase_pending: std::collections::VecDeque<MeshImportPhaseReply>,
1148 region_fit_pending: std::collections::VecDeque<RegionFitReply>,
1149 apply_choices_pending: std::collections::VecDeque<ApplyRegionChoicesReply>,
1150 step_probe_pending: std::collections::VecDeque<StepProbeReply>,
1151 topology_pending: std::collections::VecDeque<TopologyReply>,
1152 sheet_lines_pending: std::collections::VecDeque<SheetLinesReply>,
1153 plugin_action_pending: std::collections::VecDeque<PluginActionReply>,
1154 plugin_install_pending: std::collections::VecDeque<PluginInstallReply>,
1155}
1156
1157impl InlineRunner {
1158 pub fn new() -> Self {
1159 Self {
1160 runner: crate::pipeline::SceneRunner::new(),
1161 pending: std::collections::VecDeque::new(),
1162 query_pending: std::collections::VecDeque::new(),
1163 mesh_import_pending: std::collections::VecDeque::new(),
1164 mesh_phase_pending: std::collections::VecDeque::new(),
1165 region_fit_pending: std::collections::VecDeque::new(),
1166 apply_choices_pending: std::collections::VecDeque::new(),
1167 step_probe_pending: std::collections::VecDeque::new(),
1168 topology_pending: std::collections::VecDeque::new(),
1169 sheet_lines_pending: std::collections::VecDeque::new(),
1170 plugin_action_pending: std::collections::VecDeque::new(),
1171 plugin_install_pending: std::collections::VecDeque::new(),
1172 }
1173 }
1174}
1175
1176impl HistoryRunner for InlineRunner {
1177 fn submit_plugin_install(&mut self, request: PluginInstallRequest) -> bool {
1178 self.plugin_install_pending.push_back(validate_plugins(request));
1179 true
1180 }
1181 fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> {
1182 self.plugin_install_pending.pop_front()
1183 }
1184 fn sync_plugins(&mut self, revision: u64, fetch: &mut dyn FnMut() -> serde_json::Value) {
1185 if self.runner.plugin_revision != Some(revision) {
1186 self.runner.set_plugins(revision, fetch());
1187 }
1188 }
1189 fn submit_plugin_action(&mut self, request: PluginActionRequest) -> bool {
1190 self.plugin_action_pending.push_back(self.runner.plugin_action(request));
1191 true
1192 }
1193 fn poll_plugin_action(&mut self) -> Option<PluginActionReply> {
1194 self.plugin_action_pending.pop_front()
1195 }
1196
1197 fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64) {
1198 let serial = brep_kernel::assembly_session_serial();
1199 let output = self.runner.run(&request);
1200 let after = brep_kernel::assembly_session_serial();
1201 self.runner.session_of = (after != serial).then_some((generation, after));
1202 self.pending.push_back(RunReply { generation, output });
1203 }
1204
1205 fn session_for_main(&mut self, generation: u64) -> SessionOnMain {
1208 match self.runner.session_of {
1209 Some((ran, serial)) if ran == generation && serial == brep_kernel::assembly_session_serial() => {
1210 SessionOnMain::Resident
1211 }
1212 _ => SessionOnMain::Unavailable,
1213 }
1214 }
1215
1216 fn sync_parts_library(
1221 &mut self,
1222 _revision: u64,
1223 _fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
1224 ) {
1225 }
1226
1227 fn poll_run(&mut self) -> Option<RunReply> {
1228 self.pending.pop_front()
1229 }
1230
1231 fn submit_query(&mut self, query: MeasureQuery) {
1232 let result = measure_json(&self.runner, &query);
1233 self.query_pending.push_back(MeasureReply { id: query.id, result });
1234 }
1235
1236 fn poll_query(&mut self) -> Option<MeasureReply> {
1237 self.query_pending.pop_front()
1238 }
1239
1240 fn submit_mesh_import(&mut self, request: MeshImportRequest) {
1241 let phases = &mut self.mesh_phase_pending;
1242 let reply = reconstruct_mesh_phased(request, &mut |phase| {
1243 phases.push_back(phase);
1244 true
1245 });
1246 self.mesh_import_pending.push_back(reply);
1247 }
1248
1249 fn poll_mesh_import(&mut self) -> Option<MeshImportReply> {
1250 self.mesh_import_pending.pop_front()
1251 }
1252
1253 fn poll_mesh_import_phase(&mut self) -> Option<MeshImportPhaseReply> {
1254 self.mesh_phase_pending.pop_front()
1255 }
1256
1257 fn submit_region_fit(&mut self, job: RegionFitJob) -> bool {
1258 self.region_fit_pending.push_back(region_fit_reply(job));
1259 true
1260 }
1261
1262 fn poll_region_fit(&mut self) -> Option<RegionFitReply> {
1263 self.region_fit_pending.pop_front()
1264 }
1265
1266 fn submit_apply_region_choices(&mut self, job: ApplyRegionChoicesJob) -> bool {
1267 self.apply_choices_pending.push_back(apply_region_choices_reply(job));
1268 true
1269 }
1270
1271 fn poll_apply_region_choices(&mut self) -> Option<ApplyRegionChoicesReply> {
1272 self.apply_choices_pending.pop_front()
1273 }
1274
1275 fn submit_step_probe(&mut self, request: StepProbeRequest) {
1276 self.step_probe_pending.push_back(probe_step(request));
1277 }
1278
1279 fn poll_step_probe(&mut self) -> Option<StepProbeReply> {
1280 self.step_probe_pending.pop_front()
1281 }
1282
1283 fn submit_topology(&mut self, request: TopologyRequest) {
1284 self.topology_pending.push_back(topology_reply(request));
1285 }
1286
1287 fn poll_topology(&mut self) -> Option<TopologyReply> {
1288 self.topology_pending.pop_front()
1289 }
1290
1291 fn submit_sheet_lines(&mut self, request: SheetLinesRequest) -> bool {
1294 self.sheet_lines_pending.push_back(sheet_lines_reply(request));
1295 true
1296 }
1297
1298 fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
1299 self.sheet_lines_pending.pop_front()
1300 }
1301
1302 fn reset(&mut self) {
1303 self.runner.reset();
1304 }
1305}
1306
1307impl Default for InlineRunner {
1308 fn default() -> Self {
1309 Self::new()
1310 }
1311}
1312
1313#[derive(serde::Serialize, serde::Deserialize)]
1327pub enum Command {
1328 ValidatePlugins(PluginInstallRequest),
1329 SetPlugins { revision: u64, packages: serde_json::Value },
1330 PluginAction(PluginActionRequest),
1331 Run {
1332 request: brep_kernel::HistoryRequest,
1333 generation: u64,
1334 #[serde(default)]
1338 parts_library_revision: u64,
1339 },
1340 SetPartsLibrary {
1352 library: brep_kernel::PartsLibraryMap,
1353 revision: u64,
1354 },
1355 Query(MeasureQuery),
1356 MeshImport(MeshImportRequest),
1357 RegionFit(RegionFitJob),
1358 ApplyRegionChoices(ApplyRegionChoicesJob),
1359 StepProbe(StepProbeRequest),
1360 Topology(TopologyRequest),
1361 SheetLines(SheetLinesRequest),
1362 CamGenerate(CamJob),
1364 CamSimulate(CamSimJob),
1366 Reset,
1367 #[serde(skip)]
1370 ExportSession { generation: u64 },
1371}
1372
1373#[derive(serde::Serialize, serde::Deserialize)]
1375pub enum Reply {
1376 ValidatedPlugins(PluginInstallReply),
1377 PluginAction(PluginActionReply),
1378 Run(RunReply),
1379 Query(MeasureReply),
1380 MeshImport(MeshImportReply),
1381 MeshImportPhase(MeshImportPhaseReply),
1383 RegionFit(RegionFitReply),
1384 ApplyRegionChoices(ApplyRegionChoicesReply),
1385 StepProbe(StepProbeReply),
1386 Topology(TopologyReply),
1387 SheetLines(SheetLinesReply),
1388 CamGenerate(CamReply),
1389 CamProgress(CamProgress),
1391 CamSimulate(CamSimReply),
1392 CamSimProgress(CamSimProgress),
1394 NeedPartsLibrary,
1401 Progress(RunProgress),
1405 #[serde(skip)]
1407 SessionExport { generation: u64, transfer: Option<brep_kernel::SessionTransfer> },
1408}
1409
1410fn probe_step(request: StepProbeRequest) -> StepProbeReply {
1413 StepProbeReply {
1414 id: request.id,
1415 result: if crate::engine_state::is_iges_text(&request.text) {
1418 brep_kernel::read_iges_assembly(&request.text)
1419 } else {
1420 brep_kernel::read_step_assembly(&request.text)
1421 },
1422 }
1423}
1424
1425#[cfg_attr(not(test), allow(dead_code))]
1428fn reconstruct_mesh(request: MeshImportRequest) -> MeshImportReply {
1429 reconstruct_mesh_phased(request, &mut |_| true)
1430}
1431
1432fn reconstruct_mesh_phased(
1435 request: MeshImportRequest,
1436 phase: &mut dyn FnMut(MeshImportPhaseReply) -> bool,
1437) -> MeshImportReply {
1438 let id = request.id;
1439 let phases = request.phases;
1440 let result = (|| {
1441 use brep_reconstruction::stl_conversion::{
1442 binary_stl_coordinate_precision_tolerance, convert_stl_mesh_to_step_with_colors,
1443 };
1444 use brep_reconstruction::{Mesh, Vec3};
1445
1446 let mut instances = Vec::new();
1448 let mut reader_messages = Vec::new();
1449 let mut triangle_colors: Vec<Option<[u8; 3]>> = Vec::new();
1452 let (mesh, positions, indices, coordinate_precision_tolerance) = match request.format {
1453 MeshImportFormat::Stl => {
1454 use brep_reconstruction::stl::{parse_stl_bytes, StlFormat, StlReadOptions};
1455 let read_options = StlReadOptions {
1456 weld_tolerance: (request.options.weld_tolerance >= 0.0)
1457 .then_some(request.options.weld_tolerance),
1458 };
1459 let imported = parse_stl_bytes(&request.bytes, &read_options)
1460 .map_err(|error| format!("STL import failed: {error}"))?;
1461 let positions = imported
1462 .mesh
1463 .vertices
1464 .iter()
1465 .flat_map(|point| [point.x, point.y, point.z])
1466 .collect::<Vec<_>>();
1467 let indices = imported
1468 .mesh
1469 .triangles
1470 .iter()
1471 .flatten()
1472 .copied()
1473 .collect::<Vec<_>>();
1474 let precision = if imported.format == StlFormat::Binary {
1475 binary_stl_coordinate_precision_tolerance(&imported.mesh)
1476 } else {
1477 0.0
1478 };
1479 if !imported.dropped_facets.is_empty() {
1480 reader_messages.push(format!(
1481 "the STL reader dropped {} of {} facet(s) with no area: {}",
1482 imported.dropped_facets.len(),
1483 imported.source_triangle_count,
1484 imported.dropped_facets.iter().take(8).map(ToString::to_string).collect::<Vec<_>>().join("; ")
1485 ));
1486 }
1487 (imported.mesh, positions, indices, precision)
1488 }
1489 MeshImportFormat::Obj => {
1490 let text = std::str::from_utf8(&request.bytes)
1491 .map_err(|_| "OBJ import failed: file is not UTF-8 text".to_string())?;
1492 let obj = brep_kernel::read_obj(text)
1493 .map_err(|error| format!("OBJ import failed: {error}"))?;
1494 let vertices = obj
1495 .positions
1496 .chunks_exact(3)
1497 .map(|point| Vec3::new(point[0], point[1], point[2]))
1498 .collect::<Vec<_>>();
1499 let triangles = obj
1500 .indices
1501 .chunks_exact(3)
1502 .map(|triangle| [triangle[0], triangle[1], triangle[2]])
1503 .collect::<Vec<_>>();
1504 (
1505 Mesh::new(vertices, triangles),
1506 obj.positions,
1507 obj.indices,
1508 0.0,
1509 )
1510 }
1511 MeshImportFormat::ThreeMf => {
1520 let model = brep_kernel::read_3mf(&request.bytes)
1521 .map_err(|error| format!("3MF import failed: {error}"))?;
1522 instances = model.instances;
1523 triangle_colors = model.triangle_colors;
1524 let vertices = model
1525 .positions
1526 .chunks_exact(3)
1527 .map(|point| Vec3::new(point[0], point[1], point[2]))
1528 .collect::<Vec<_>>();
1529 let triangles = model
1530 .indices
1531 .chunks_exact(3)
1532 .map(|triangle| [triangle[0], triangle[1], triangle[2]])
1533 .collect::<Vec<_>>();
1534 (
1535 Mesh::new(vertices, triangles),
1536 model.positions,
1537 model.indices,
1538 0.0,
1539 )
1540 }
1541 };
1542 let mut options = request.options;
1543 options.coordinate_precision_tolerance = options.coordinate_precision_tolerance
1544 .max(coordinate_precision_tolerance);
1545 if phases {
1546 let listening = phase(MeshImportPhaseReply {
1549 id,
1550 phase: MeshImportPhase::Mesh { positions: positions.clone(), indices: indices.clone() },
1551 });
1552 if !listening {
1553 return Err("cancelled".to_string());
1554 }
1555 let regions = brep_reconstruction::import_session::analyze_regions(&positions, &indices, &options);
1556 let listening = phase(MeshImportPhaseReply {
1557 id,
1558 phase: match regions {
1559 Ok(regions) => MeshImportPhase::Regions(regions),
1560 Err(error) => MeshImportPhase::RegionsFailed(error),
1561 },
1562 });
1563 if !listening {
1564 return Err("cancelled".to_string());
1565 }
1566 }
1567 convert_stl_mesh_to_step_with_colors(
1568 &mesh,
1569 &positions,
1570 Some(&indices),
1571 (!triangle_colors.is_empty()).then_some(triangle_colors.as_slice()),
1572 &options,
1573 "Imported mesh",
1574 "MM",
1575 "",
1576 )
1577 .map(|mut output| {
1578 name_bodies(&mut output.report, &instances);
1579 output.report.messages.splice(0..0, reader_messages);
1580 output
1581 })
1582 .map_err(|error| format!("RANSAC reconstruction failed: {error}"))
1583 })();
1584 MeshImportReply { id, result }
1585}
1586
1587fn name_bodies(
1591 report: &mut brep_reconstruction::stl_conversion::StlConversionReport,
1592 instances: &[brep_kernel::ThreeMfInstance],
1593) {
1594 for component in &mut report.components {
1595 component.source_name = instances
1596 .iter()
1597 .find(|instance| {
1598 (instance.first_triangle..instance.first_triangle + instance.triangle_count)
1599 .contains(&component.first_triangle)
1600 })
1601 .and_then(|instance| instance.name.clone());
1602 }
1603}
1604
1605pub(crate) fn refused_bodies_notice(
1609 report: &brep_reconstruction::stl_conversion::StlConversionReport,
1610) -> Option<String> {
1611 let refused = report
1612 .components
1613 .iter()
1614 .filter_map(|component| {
1615 component.refusal.as_ref().map(|reason| {
1616 let body = match &component.source_name {
1617 Some(name) => format!("body {} \"{name}\"", component.index + 1),
1618 None => format!(
1619 "body {} (from source triangle {})",
1620 component.index + 1,
1621 component.first_triangle
1622 ),
1623 };
1624 format!("{body}: {reason}")
1625 })
1626 })
1627 .collect::<Vec<_>>();
1628 (!refused.is_empty()).then(|| {
1629 format!(
1630 "mesh import: {} of {} bodies could not be reconstructed and are NOT in the model — {}",
1631 refused.len(),
1632 report.components.len(),
1633 refused.join("; ")
1634 )
1635 })
1636}
1637
1638pub fn process_command(
1651 runner: &mut crate::pipeline::SceneRunner,
1652 command: Command,
1653 progress: &mut dyn FnMut(Reply) -> bool,
1654) -> Option<Reply> {
1655 match command {
1656 Command::ValidatePlugins(request) => Some(Reply::ValidatedPlugins(validate_plugins(request))),
1657 Command::SetPlugins { revision, packages } => {
1658 runner.set_plugins(revision, packages);
1659 None
1660 }
1661 Command::PluginAction(request) => Some(Reply::PluginAction(runner.plugin_action(request))),
1662 Command::Run {
1663 request,
1664 generation,
1665 parts_library_revision,
1666 } => {
1667 let stale = match runner.parts_library_revision {
1679 Some(installed) => installed != parts_library_revision,
1680 None => parts_library_revision != 0,
1686 };
1687 let recoverable = brep_kernel::missing_library_parts(&request)
1694 .iter()
1695 .any(|name| runner.parts_library_names.contains(name));
1696 if stale || recoverable {
1697 runner.parts_library_revision = None;
1698 return Some(Reply::NeedPartsLibrary);
1699 }
1700 let serial = brep_kernel::assembly_session_serial();
1701 let output = runner.run_observed(&request, &mut |event| {
1702 progress(Reply::Progress(RunProgress {
1703 generation,
1704 index: event.index,
1705 total: event.total,
1706 feature_id: event.id.to_string(),
1707 feature_type: event.feature_type.to_string(),
1708 }))
1709 });
1710 let after = brep_kernel::assembly_session_serial();
1711 runner.session_of = (after != serial).then_some((generation, after));
1712 Some(Reply::Run(RunReply { generation, output }))
1713 }
1714 Command::SetPartsLibrary { library, revision } => {
1715 runner.parts_library_names = library.keys().cloned().collect();
1716 brep_kernel::install_parts_library(&library);
1717 runner.parts_library_revision = Some(revision);
1718 None
1719 }
1720 Command::Query(query) => {
1721 let result = measure_json(runner, &query);
1722 Some(Reply::Query(MeasureReply { id: query.id, result }))
1723 }
1724 Command::MeshImport(request) => Some(Reply::MeshImport(reconstruct_mesh_phased(request, &mut |phase| {
1725 progress(Reply::MeshImportPhase(phase))
1726 }))),
1727 Command::RegionFit(job) => Some(Reply::RegionFit(region_fit_reply(job))),
1728 Command::ApplyRegionChoices(job) => Some(Reply::ApplyRegionChoices(apply_region_choices_reply(job))),
1729 Command::StepProbe(request) => Some(Reply::StepProbe(probe_step(request))),
1730 Command::Topology(request) => Some(Reply::Topology(topology_reply(request))),
1731 Command::SheetLines(request) => Some(Reply::SheetLines(sheet_lines_reply(request))),
1732 Command::CamGenerate(job) => Some(Reply::CamGenerate(cam_generate_reply(job, progress))),
1733 Command::CamSimulate(job) => Some(Reply::CamSimulate(cam_sim_reply(job, progress))),
1734 Command::Reset => {
1735 runner.reset();
1740 brep_kernel::clear_history_cache();
1741 cam_sim_clear();
1742 pre_op_stock_clear();
1743 None
1744 }
1745 Command::ExportSession { generation } => {
1746 Some(Reply::SessionExport { generation, transfer: runner.export_session(generation) })
1747 }
1748 }
1749}
1750
1751#[cfg(not(target_arch = "wasm32"))]
1769static REPLY_WAKER: std::sync::RwLock<Option<std::sync::Arc<dyn Fn() + Send + Sync>>> = std::sync::RwLock::new(None);
1770
1771#[cfg(not(target_arch = "wasm32"))]
1774pub fn set_reply_waker(waker: Option<std::sync::Arc<dyn Fn() + Send + Sync>>) {
1775 *REPLY_WAKER.write().unwrap_or_else(|p| p.into_inner()) = waker;
1776}
1777
1778#[cfg(not(target_arch = "wasm32"))]
1779fn wake_main() {
1780 let waker = REPLY_WAKER.read().unwrap_or_else(|p| p.into_inner()).clone();
1781 if let Some(waker) = waker {
1782 waker();
1783 }
1784}
1785
1786#[cfg(not(target_arch = "wasm32"))]
1787pub struct ThreadRunner {
1788 tx: Option<std::sync::mpsc::Sender<Command>>,
1791 rx: std::sync::mpsc::Receiver<Reply>,
1793 handle: Option<std::thread::JoinHandle<()>>,
1795 run_buf: std::collections::VecDeque<RunReply>,
1797 query_buf: std::collections::VecDeque<MeasureReply>,
1799 mesh_import_buf: std::collections::VecDeque<MeshImportReply>,
1800 mesh_phase_buf: std::collections::VecDeque<MeshImportPhaseReply>,
1801 region_fit_buf: std::collections::VecDeque<RegionFitReply>,
1802 apply_choices_buf: std::collections::VecDeque<ApplyRegionChoicesReply>,
1803 step_probe_buf: std::collections::VecDeque<StepProbeReply>,
1804 topology_buf: std::collections::VecDeque<TopologyReply>,
1805 sheet_lines_buf: std::collections::VecDeque<SheetLinesReply>,
1806 cam_buf: std::collections::VecDeque<CamReply>,
1807 cam_hold: bool,
1809 cam_progress_buf: std::collections::VecDeque<CamProgress>,
1810 cam_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1813 cam_sim_buf: std::collections::VecDeque<CamSimReply>,
1814 cam_sim_progress_buf: std::collections::VecDeque<CamSimProgress>,
1815 cam_sim_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1817 plugin_action_buf: std::collections::VecDeque<PluginActionReply>,
1818 plugin_install_buf: std::collections::VecDeque<PluginInstallReply>,
1819 sent_plugin_revision: Option<u64>,
1820 progress_buf: std::collections::VecDeque<RunProgress>,
1823 sent_library_revision: Option<u64>,
1827 library_requested: bool,
1830 stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
1834}
1835
1836#[cfg(not(target_arch = "wasm32"))]
1837impl ThreadRunner {
1838 pub fn new() -> Self {
1839 let (tx, cmd_rx) = std::sync::mpsc::channel::<Command>();
1840 let (reply_tx, rx) = std::sync::mpsc::channel::<Reply>();
1841 let stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1842 let thread_stop = stop.clone();
1843 let cam_cancel = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1844 let thread_cam_cancel = cam_cancel.clone();
1845 let cam_sim_cancel = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1846 let thread_cam_sim_cancel = cam_sim_cancel.clone();
1847 let handle = std::thread::Builder::new()
1848 .name("brep-history-runner".to_string())
1849 .spawn(move || thread_main(cmd_rx, reply_tx, thread_stop, thread_cam_cancel, thread_cam_sim_cancel))
1850 .expect("spawn brep-history-runner thread");
1851 Self {
1852 tx: Some(tx),
1853 rx,
1854 handle: Some(handle),
1855 run_buf: std::collections::VecDeque::new(),
1856 query_buf: std::collections::VecDeque::new(),
1857 mesh_import_buf: std::collections::VecDeque::new(),
1858 mesh_phase_buf: std::collections::VecDeque::new(),
1859 region_fit_buf: std::collections::VecDeque::new(),
1860 apply_choices_buf: std::collections::VecDeque::new(),
1861 step_probe_buf: std::collections::VecDeque::new(),
1862 topology_buf: std::collections::VecDeque::new(),
1863 sheet_lines_buf: std::collections::VecDeque::new(),
1864 cam_buf: std::collections::VecDeque::new(),
1865 cam_hold: false,
1866 cam_progress_buf: std::collections::VecDeque::new(),
1867 cam_cancel,
1868 cam_sim_buf: std::collections::VecDeque::new(),
1869 cam_sim_progress_buf: std::collections::VecDeque::new(),
1870 cam_sim_cancel,
1871 plugin_action_buf: std::collections::VecDeque::new(),
1872 plugin_install_buf: std::collections::VecDeque::new(),
1873 sent_plugin_revision: None,
1874 progress_buf: std::collections::VecDeque::new(),
1875 sent_library_revision: None,
1876 library_requested: false,
1877 stop,
1878 }
1879 }
1880
1881 fn drain(&mut self) {
1884 while let Ok(reply) = self.rx.try_recv() {
1885 self.file(reply);
1886 }
1887 }
1888
1889 fn file(&mut self, reply: Reply) -> Option<(u64, Option<brep_kernel::SessionTransfer>)> {
1892 match reply {
1893 Reply::SessionExport { generation, transfer } => return Some((generation, transfer)),
1894 Reply::ValidatedPlugins(reply) => self.plugin_install_buf.push_back(reply),
1895 Reply::PluginAction(reply) => self.plugin_action_buf.push_back(reply),
1896 Reply::Run(run) => self.run_buf.push_back(run),
1897 Reply::Query(query) => self.query_buf.push_back(query),
1898 Reply::MeshImport(reply) => self.mesh_import_buf.push_back(reply),
1899 Reply::MeshImportPhase(reply) => self.mesh_phase_buf.push_back(reply),
1900 Reply::RegionFit(reply) => self.region_fit_buf.push_back(reply),
1901 Reply::ApplyRegionChoices(reply) => self.apply_choices_buf.push_back(reply),
1902 Reply::StepProbe(reply) => self.step_probe_buf.push_back(reply),
1903 Reply::Topology(reply) => self.topology_buf.push_back(reply),
1904 Reply::SheetLines(reply) => self.sheet_lines_buf.push_back(reply),
1905 Reply::CamGenerate(reply) => self.cam_buf.push_back(reply),
1906 Reply::CamProgress(progress) => self.cam_progress_buf.push_back(progress),
1907 Reply::CamSimulate(reply) => self.cam_sim_buf.push_back(reply),
1908 Reply::CamSimProgress(progress) => self.cam_sim_progress_buf.push_back(progress),
1909 Reply::Progress(progress) => self.progress_buf.push_back(progress),
1910 Reply::NeedPartsLibrary => {
1911 self.sent_library_revision = None;
1916 self.library_requested = true;
1917 }
1918 }
1919 None
1920 }
1921}
1922
1923#[cfg(not(target_arch = "wasm32"))]
1926const SESSION_EXPORT_WAIT: std::time::Duration = std::time::Duration::from_millis(250);
1927
1928#[cfg(not(target_arch = "wasm32"))]
1929impl Default for ThreadRunner {
1930 fn default() -> Self {
1931 Self::new()
1932 }
1933}
1934
1935#[cfg(not(target_arch = "wasm32"))]
1949fn thread_main(
1950 cmd_rx: std::sync::mpsc::Receiver<Command>,
1951 reply_tx: std::sync::mpsc::Sender<Reply>,
1952 stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
1953 cam_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1954 cam_sim_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1955) {
1956 let mut runner = crate::pipeline::SceneRunner::new();
1957 while let Ok(first) = cmd_rx.recv() {
1958 if stop.load(std::sync::atomic::Ordering::Relaxed) {
1959 return;
1960 }
1961 let mut batch = vec![first];
1963 loop {
1964 match cmd_rx.try_recv() {
1965 Ok(command) => batch.push(command),
1966 Err(_) => break, }
1968 }
1969 let mut run_here: Vec<bool> = vec![true; batch.len()];
1973 for i in 0..batch.len() {
1974 if matches!(batch[i], Command::Run { .. })
1975 && matches!(batch.get(i + 1), Some(Command::Run { .. }))
1976 {
1977 run_here[i] = false;
1978 }
1979 }
1980 for (i, command) in batch.into_iter().enumerate() {
1985 if !run_here[i] {
1986 continue;
1987 }
1988 let is_cam = matches!(command, Command::CamGenerate(_));
1991 if is_cam {
1992 cam_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
1993 }
1994 let is_sim = matches!(command, Command::CamSimulate(_));
1995 let mut progress = |reply: Reply| {
1996 let sent = reply_tx.send(reply).is_ok();
1999 if sent {
2000 wake_main();
2001 }
2002 sent
2003 && !stop.load(std::sync::atomic::Ordering::Relaxed)
2004 && !(is_cam && cam_cancel.load(std::sync::atomic::Ordering::SeqCst))
2005 && !(is_sim && cam_sim_cancel.load(std::sync::atomic::Ordering::SeqCst))
2006 };
2007 if let Some(reply) = process_command(&mut runner, command, &mut progress) {
2008 if reply_tx.send(reply).is_err() {
2009 return; }
2011 wake_main();
2012 }
2013 }
2014 }
2015}
2016
2017#[cfg(not(target_arch = "wasm32"))]
2018impl HistoryRunner for ThreadRunner {
2019 fn session_for_main(&mut self, generation: u64) -> SessionOnMain {
2020 let Some(tx) = &self.tx else { return SessionOnMain::Unavailable };
2021 if tx.send(Command::ExportSession { generation }).is_err() {
2022 return SessionOnMain::Unavailable;
2023 }
2024 let deadline = std::time::Instant::now() + SESSION_EXPORT_WAIT;
2025 loop {
2026 let left = deadline.saturating_duration_since(std::time::Instant::now());
2027 let Ok(reply) = self.rx.recv_timeout(left) else { return SessionOnMain::Unavailable };
2028 if let Some((answered, transfer)) = self.file(reply) {
2029 if answered != generation {
2030 continue; }
2032 return transfer.map_or(SessionOnMain::Unavailable, SessionOnMain::Copied);
2033 }
2034 }
2035 }
2036 fn submit_plugin_install(&mut self, request: PluginInstallRequest) -> bool {
2037 self.tx.as_ref().is_some_and(|tx| tx.send(Command::ValidatePlugins(request)).is_ok())
2038 }
2039 fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> {
2040 self.drain();
2041 self.plugin_install_buf.pop_front()
2042 }
2043 fn sync_plugins(&mut self, revision: u64, fetch: &mut dyn FnMut() -> serde_json::Value) {
2044 if self.sent_plugin_revision != Some(revision) {
2045 if let Some(tx) = &self.tx {
2046 if tx.send(Command::SetPlugins { revision, packages: fetch() }).is_ok() {
2047 self.sent_plugin_revision = Some(revision);
2048 }
2049 }
2050 }
2051 }
2052 fn submit_plugin_action(&mut self, request: PluginActionRequest) -> bool {
2053 self.tx.as_ref().is_some_and(|tx| tx.send(Command::PluginAction(request)).is_ok())
2054 }
2055 fn poll_plugin_action(&mut self) -> Option<PluginActionReply> {
2056 self.drain();
2057 self.plugin_action_buf.pop_front()
2058 }
2059
2060 fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64) {
2061 if let Some(tx) = &self.tx {
2062 let _ = tx.send(Command::Run {
2063 request,
2064 generation,
2065 parts_library_revision: self.sent_library_revision.unwrap_or(0),
2066 });
2067 }
2068 }
2069
2070 fn sync_parts_library(
2071 &mut self,
2072 revision: u64,
2073 fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
2074 ) {
2075 if self.sent_library_revision == Some(revision) {
2076 return;
2077 }
2078 if let Some(tx) = &self.tx {
2079 let _ = tx.send(Command::SetPartsLibrary {
2080 library: fetch(),
2081 revision,
2082 });
2083 self.sent_library_revision = Some(revision);
2084 }
2085 }
2086
2087 fn poll_library_request(&mut self) -> bool {
2088 self.drain();
2089 std::mem::take(&mut self.library_requested)
2090 }
2091
2092 fn poll_run(&mut self) -> Option<RunReply> {
2093 self.drain();
2094 self.run_buf.pop_front()
2095 }
2096
2097 fn run_reply_waiting(&mut self) -> bool {
2098 self.drain();
2099 !self.run_buf.is_empty() || self.library_requested
2100 }
2101
2102 fn submit_query(&mut self, query: MeasureQuery) {
2103 if let Some(tx) = &self.tx {
2104 let _ = tx.send(Command::Query(query));
2105 }
2106 }
2107
2108 fn poll_query(&mut self) -> Option<MeasureReply> {
2109 self.drain();
2110 self.query_buf.pop_front()
2111 }
2112
2113 fn submit_mesh_import(&mut self, request: MeshImportRequest) {
2114 if let Some(tx) = &self.tx {
2115 let _ = tx.send(Command::MeshImport(request));
2116 }
2117 }
2118
2119 fn poll_mesh_import(&mut self) -> Option<MeshImportReply> {
2120 self.drain();
2121 self.mesh_import_buf.pop_front()
2122 }
2123
2124 fn poll_mesh_import_phase(&mut self) -> Option<MeshImportPhaseReply> {
2125 self.drain();
2126 self.mesh_phase_buf.pop_front()
2127 }
2128
2129 fn submit_region_fit(&mut self, job: RegionFitJob) -> bool {
2130 match &self.tx {
2131 Some(tx) => tx.send(Command::RegionFit(job)).is_ok(),
2132 None => false,
2133 }
2134 }
2135
2136 fn poll_region_fit(&mut self) -> Option<RegionFitReply> {
2137 self.drain();
2138 self.region_fit_buf.pop_front()
2139 }
2140
2141 fn submit_apply_region_choices(&mut self, job: ApplyRegionChoicesJob) -> bool {
2142 match &self.tx {
2143 Some(tx) => tx.send(Command::ApplyRegionChoices(job)).is_ok(),
2144 None => false,
2145 }
2146 }
2147
2148 fn poll_apply_region_choices(&mut self) -> Option<ApplyRegionChoicesReply> {
2149 self.drain();
2150 self.apply_choices_buf.pop_front()
2151 }
2152
2153 fn submit_step_probe(&mut self, request: StepProbeRequest) {
2154 if let Some(tx) = &self.tx {
2155 let _ = tx.send(Command::StepProbe(request));
2156 }
2157 }
2158
2159 fn poll_step_probe(&mut self) -> Option<StepProbeReply> {
2160 self.drain();
2161 self.step_probe_buf.pop_front()
2162 }
2163
2164 fn submit_topology(&mut self, request: TopologyRequest) {
2165 if let Some(tx) = &self.tx {
2166 let _ = tx.send(Command::Topology(request));
2167 }
2168 }
2169
2170 fn poll_topology(&mut self) -> Option<TopologyReply> {
2171 self.drain();
2172 self.topology_buf.pop_front()
2173 }
2174
2175 fn wait_topology(&mut self, timeout: std::time::Duration) -> bool {
2176 self.drain();
2177 let deadline = std::time::Instant::now() + timeout;
2178 while self.topology_buf.is_empty() {
2179 let now = std::time::Instant::now();
2180 if now >= deadline {
2181 return false;
2182 }
2183 match self.rx.recv_timeout(deadline - now) {
2184 Ok(reply) => {
2185 self.file(reply);
2186 }
2187 Err(_) => return false,
2188 }
2189 }
2190 true
2191 }
2192
2193 fn submit_sheet_lines(&mut self, request: SheetLinesRequest) -> bool {
2194 match &self.tx {
2195 Some(tx) => tx.send(Command::SheetLines(request)).is_ok(),
2196 None => false,
2197 }
2198 }
2199
2200 fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
2201 self.drain();
2202 self.sheet_lines_buf.pop_front()
2203 }
2204
2205 fn submit_cam(&mut self, job: CamJob) -> bool {
2206 self.cam_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
2207 match &self.tx {
2208 Some(tx) => tx.send(Command::CamGenerate(job)).is_ok(),
2209 None => false,
2210 }
2211 }
2212
2213 fn poll_cam(&mut self) -> Option<CamReply> {
2214 self.drain();
2215 if self.cam_hold {
2216 return None;
2217 }
2218 self.cam_buf.pop_front()
2219 }
2220
2221 fn hold_cam_replies(&mut self, hold: bool) {
2222 self.cam_hold = hold;
2223 }
2224
2225 fn poll_cam_progress(&mut self) -> Option<CamProgress> {
2226 self.drain();
2227 let latest = self.cam_progress_buf.pop_back();
2228 self.cam_progress_buf.clear();
2229 latest
2230 }
2231
2232 fn cancel_cam(&mut self) {
2233 self.cam_cancel.store(true, std::sync::atomic::Ordering::SeqCst);
2234 }
2235
2236 fn submit_cam_sim(&mut self, job: CamSimJob) -> bool {
2237 self.cam_sim_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
2239 match &self.tx {
2240 Some(tx) => tx.send(Command::CamSimulate(job)).is_ok(),
2241 None => false,
2242 }
2243 }
2244
2245 fn poll_cam_sim(&mut self) -> Option<CamSimReply> {
2246 self.drain();
2247 self.cam_sim_buf.pop_front()
2248 }
2249
2250 fn poll_cam_sim_progress(&mut self) -> Option<CamSimProgress> {
2251 self.drain();
2252 let latest = self.cam_sim_progress_buf.pop_back();
2253 self.cam_sim_progress_buf.clear();
2254 latest
2255 }
2256
2257 fn cancel_cam_sim(&mut self) {
2258 self.cam_sim_cancel.store(true, std::sync::atomic::Ordering::SeqCst);
2259 }
2260
2261 fn poll_progress(&mut self) -> Option<RunProgress> {
2262 self.drain();
2263 let latest = self.progress_buf.pop_back();
2264 self.progress_buf.clear();
2265 latest
2266 }
2267
2268 fn cancel(&mut self) -> bool {
2274 self.stop.store(true, std::sync::atomic::Ordering::Relaxed);
2275 self.tx.take();
2276 self.handle.take();
2277 *self = Self::new();
2278 true
2279 }
2280
2281 fn reset(&mut self) {
2282 self.sent_plugin_revision = None;
2283 self.plugin_action_buf.clear();
2284 if let Some(tx) = &self.tx {
2285 let _ = tx.send(Command::Reset);
2286 }
2287 self.run_buf.clear();
2290 self.query_buf.clear();
2291 self.mesh_import_buf.clear();
2292 self.mesh_phase_buf.clear();
2293 self.region_fit_buf.clear();
2294 self.apply_choices_buf.clear();
2295 self.step_probe_buf.clear();
2296 self.topology_buf.clear();
2297 self.sheet_lines_buf.clear();
2298 self.progress_buf.clear();
2299 self.sent_library_revision = None;
2302 self.library_requested = false;
2303 }
2304}
2305
2306#[cfg(not(target_arch = "wasm32"))]
2307impl Drop for ThreadRunner {
2308 fn drop(&mut self) {
2309 self.tx.take();
2313 self.handle.take();
2314 }
2315}
2316