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: brep_kernel::read_step_assembly(&request.text),
1416 }
1417}
1418
1419#[cfg_attr(not(test), allow(dead_code))]
1422fn reconstruct_mesh(request: MeshImportRequest) -> MeshImportReply {
1423 reconstruct_mesh_phased(request, &mut |_| true)
1424}
1425
1426fn reconstruct_mesh_phased(
1429 request: MeshImportRequest,
1430 phase: &mut dyn FnMut(MeshImportPhaseReply) -> bool,
1431) -> MeshImportReply {
1432 let id = request.id;
1433 let phases = request.phases;
1434 let result = (|| {
1435 use brep_reconstruction::stl_conversion::{
1436 binary_stl_coordinate_precision_tolerance, convert_stl_mesh_to_step_with_colors,
1437 };
1438 use brep_reconstruction::{Mesh, Vec3};
1439
1440 let mut instances = Vec::new();
1442 let mut reader_messages = Vec::new();
1443 let mut triangle_colors: Vec<Option<[u8; 3]>> = Vec::new();
1446 let (mesh, positions, indices, coordinate_precision_tolerance) = match request.format {
1447 MeshImportFormat::Stl => {
1448 use brep_reconstruction::stl::{parse_stl_bytes, StlFormat, StlReadOptions};
1449 let read_options = StlReadOptions {
1450 weld_tolerance: (request.options.weld_tolerance >= 0.0)
1451 .then_some(request.options.weld_tolerance),
1452 };
1453 let imported = parse_stl_bytes(&request.bytes, &read_options)
1454 .map_err(|error| format!("STL import failed: {error}"))?;
1455 let positions = imported
1456 .mesh
1457 .vertices
1458 .iter()
1459 .flat_map(|point| [point.x, point.y, point.z])
1460 .collect::<Vec<_>>();
1461 let indices = imported
1462 .mesh
1463 .triangles
1464 .iter()
1465 .flatten()
1466 .copied()
1467 .collect::<Vec<_>>();
1468 let precision = if imported.format == StlFormat::Binary {
1469 binary_stl_coordinate_precision_tolerance(&imported.mesh)
1470 } else {
1471 0.0
1472 };
1473 if !imported.dropped_facets.is_empty() {
1474 reader_messages.push(format!(
1475 "the STL reader dropped {} of {} facet(s) with no area: {}",
1476 imported.dropped_facets.len(),
1477 imported.source_triangle_count,
1478 imported.dropped_facets.iter().take(8).map(ToString::to_string).collect::<Vec<_>>().join("; ")
1479 ));
1480 }
1481 (imported.mesh, positions, indices, precision)
1482 }
1483 MeshImportFormat::Obj => {
1484 let text = std::str::from_utf8(&request.bytes)
1485 .map_err(|_| "OBJ import failed: file is not UTF-8 text".to_string())?;
1486 let obj = brep_kernel::read_obj(text)
1487 .map_err(|error| format!("OBJ import failed: {error}"))?;
1488 let vertices = obj
1489 .positions
1490 .chunks_exact(3)
1491 .map(|point| Vec3::new(point[0], point[1], point[2]))
1492 .collect::<Vec<_>>();
1493 let triangles = obj
1494 .indices
1495 .chunks_exact(3)
1496 .map(|triangle| [triangle[0], triangle[1], triangle[2]])
1497 .collect::<Vec<_>>();
1498 (
1499 Mesh::new(vertices, triangles),
1500 obj.positions,
1501 obj.indices,
1502 0.0,
1503 )
1504 }
1505 MeshImportFormat::ThreeMf => {
1514 let model = brep_kernel::read_3mf(&request.bytes)
1515 .map_err(|error| format!("3MF import failed: {error}"))?;
1516 instances = model.instances;
1517 triangle_colors = model.triangle_colors;
1518 let vertices = model
1519 .positions
1520 .chunks_exact(3)
1521 .map(|point| Vec3::new(point[0], point[1], point[2]))
1522 .collect::<Vec<_>>();
1523 let triangles = model
1524 .indices
1525 .chunks_exact(3)
1526 .map(|triangle| [triangle[0], triangle[1], triangle[2]])
1527 .collect::<Vec<_>>();
1528 (
1529 Mesh::new(vertices, triangles),
1530 model.positions,
1531 model.indices,
1532 0.0,
1533 )
1534 }
1535 };
1536 let mut options = request.options;
1537 options.coordinate_precision_tolerance = options.coordinate_precision_tolerance
1538 .max(coordinate_precision_tolerance);
1539 if phases {
1540 let listening = phase(MeshImportPhaseReply {
1543 id,
1544 phase: MeshImportPhase::Mesh { positions: positions.clone(), indices: indices.clone() },
1545 });
1546 if !listening {
1547 return Err("cancelled".to_string());
1548 }
1549 let regions = brep_reconstruction::import_session::analyze_regions(&positions, &indices, &options);
1550 let listening = phase(MeshImportPhaseReply {
1551 id,
1552 phase: match regions {
1553 Ok(regions) => MeshImportPhase::Regions(regions),
1554 Err(error) => MeshImportPhase::RegionsFailed(error),
1555 },
1556 });
1557 if !listening {
1558 return Err("cancelled".to_string());
1559 }
1560 }
1561 convert_stl_mesh_to_step_with_colors(
1562 &mesh,
1563 &positions,
1564 Some(&indices),
1565 (!triangle_colors.is_empty()).then_some(triangle_colors.as_slice()),
1566 &options,
1567 "Imported mesh",
1568 "MM",
1569 "",
1570 )
1571 .map(|mut output| {
1572 name_bodies(&mut output.report, &instances);
1573 output.report.messages.splice(0..0, reader_messages);
1574 output
1575 })
1576 .map_err(|error| format!("RANSAC reconstruction failed: {error}"))
1577 })();
1578 MeshImportReply { id, result }
1579}
1580
1581fn name_bodies(
1585 report: &mut brep_reconstruction::stl_conversion::StlConversionReport,
1586 instances: &[brep_kernel::ThreeMfInstance],
1587) {
1588 for component in &mut report.components {
1589 component.source_name = instances
1590 .iter()
1591 .find(|instance| {
1592 (instance.first_triangle..instance.first_triangle + instance.triangle_count)
1593 .contains(&component.first_triangle)
1594 })
1595 .and_then(|instance| instance.name.clone());
1596 }
1597}
1598
1599pub(crate) fn refused_bodies_notice(
1603 report: &brep_reconstruction::stl_conversion::StlConversionReport,
1604) -> Option<String> {
1605 let refused = report
1606 .components
1607 .iter()
1608 .filter_map(|component| {
1609 component.refusal.as_ref().map(|reason| {
1610 let body = match &component.source_name {
1611 Some(name) => format!("body {} \"{name}\"", component.index + 1),
1612 None => format!(
1613 "body {} (from source triangle {})",
1614 component.index + 1,
1615 component.first_triangle
1616 ),
1617 };
1618 format!("{body}: {reason}")
1619 })
1620 })
1621 .collect::<Vec<_>>();
1622 (!refused.is_empty()).then(|| {
1623 format!(
1624 "mesh import: {} of {} bodies could not be reconstructed and are NOT in the model — {}",
1625 refused.len(),
1626 report.components.len(),
1627 refused.join("; ")
1628 )
1629 })
1630}
1631
1632pub fn process_command(
1645 runner: &mut crate::pipeline::SceneRunner,
1646 command: Command,
1647 progress: &mut dyn FnMut(Reply) -> bool,
1648) -> Option<Reply> {
1649 match command {
1650 Command::ValidatePlugins(request) => Some(Reply::ValidatedPlugins(validate_plugins(request))),
1651 Command::SetPlugins { revision, packages } => {
1652 runner.set_plugins(revision, packages);
1653 None
1654 }
1655 Command::PluginAction(request) => Some(Reply::PluginAction(runner.plugin_action(request))),
1656 Command::Run {
1657 request,
1658 generation,
1659 parts_library_revision,
1660 } => {
1661 let stale = match runner.parts_library_revision {
1673 Some(installed) => installed != parts_library_revision,
1674 None => parts_library_revision != 0,
1680 };
1681 let recoverable = brep_kernel::missing_library_parts(&request)
1688 .iter()
1689 .any(|name| runner.parts_library_names.contains(name));
1690 if stale || recoverable {
1691 runner.parts_library_revision = None;
1692 return Some(Reply::NeedPartsLibrary);
1693 }
1694 let serial = brep_kernel::assembly_session_serial();
1695 let output = runner.run_observed(&request, &mut |event| {
1696 progress(Reply::Progress(RunProgress {
1697 generation,
1698 index: event.index,
1699 total: event.total,
1700 feature_id: event.id.to_string(),
1701 feature_type: event.feature_type.to_string(),
1702 }))
1703 });
1704 let after = brep_kernel::assembly_session_serial();
1705 runner.session_of = (after != serial).then_some((generation, after));
1706 Some(Reply::Run(RunReply { generation, output }))
1707 }
1708 Command::SetPartsLibrary { library, revision } => {
1709 runner.parts_library_names = library.keys().cloned().collect();
1710 brep_kernel::install_parts_library(&library);
1711 runner.parts_library_revision = Some(revision);
1712 None
1713 }
1714 Command::Query(query) => {
1715 let result = measure_json(runner, &query);
1716 Some(Reply::Query(MeasureReply { id: query.id, result }))
1717 }
1718 Command::MeshImport(request) => Some(Reply::MeshImport(reconstruct_mesh_phased(request, &mut |phase| {
1719 progress(Reply::MeshImportPhase(phase))
1720 }))),
1721 Command::RegionFit(job) => Some(Reply::RegionFit(region_fit_reply(job))),
1722 Command::ApplyRegionChoices(job) => Some(Reply::ApplyRegionChoices(apply_region_choices_reply(job))),
1723 Command::StepProbe(request) => Some(Reply::StepProbe(probe_step(request))),
1724 Command::Topology(request) => Some(Reply::Topology(topology_reply(request))),
1725 Command::SheetLines(request) => Some(Reply::SheetLines(sheet_lines_reply(request))),
1726 Command::CamGenerate(job) => Some(Reply::CamGenerate(cam_generate_reply(job, progress))),
1727 Command::CamSimulate(job) => Some(Reply::CamSimulate(cam_sim_reply(job, progress))),
1728 Command::Reset => {
1729 runner.reset();
1734 brep_kernel::clear_history_cache();
1735 cam_sim_clear();
1736 pre_op_stock_clear();
1737 None
1738 }
1739 Command::ExportSession { generation } => {
1740 Some(Reply::SessionExport { generation, transfer: runner.export_session(generation) })
1741 }
1742 }
1743}
1744
1745#[cfg(not(target_arch = "wasm32"))]
1763static REPLY_WAKER: std::sync::RwLock<Option<std::sync::Arc<dyn Fn() + Send + Sync>>> = std::sync::RwLock::new(None);
1764
1765#[cfg(not(target_arch = "wasm32"))]
1768pub fn set_reply_waker(waker: Option<std::sync::Arc<dyn Fn() + Send + Sync>>) {
1769 *REPLY_WAKER.write().unwrap_or_else(|p| p.into_inner()) = waker;
1770}
1771
1772#[cfg(not(target_arch = "wasm32"))]
1773fn wake_main() {
1774 let waker = REPLY_WAKER.read().unwrap_or_else(|p| p.into_inner()).clone();
1775 if let Some(waker) = waker {
1776 waker();
1777 }
1778}
1779
1780#[cfg(not(target_arch = "wasm32"))]
1781pub struct ThreadRunner {
1782 tx: Option<std::sync::mpsc::Sender<Command>>,
1785 rx: std::sync::mpsc::Receiver<Reply>,
1787 handle: Option<std::thread::JoinHandle<()>>,
1789 run_buf: std::collections::VecDeque<RunReply>,
1791 query_buf: std::collections::VecDeque<MeasureReply>,
1793 mesh_import_buf: std::collections::VecDeque<MeshImportReply>,
1794 mesh_phase_buf: std::collections::VecDeque<MeshImportPhaseReply>,
1795 region_fit_buf: std::collections::VecDeque<RegionFitReply>,
1796 apply_choices_buf: std::collections::VecDeque<ApplyRegionChoicesReply>,
1797 step_probe_buf: std::collections::VecDeque<StepProbeReply>,
1798 topology_buf: std::collections::VecDeque<TopologyReply>,
1799 sheet_lines_buf: std::collections::VecDeque<SheetLinesReply>,
1800 cam_buf: std::collections::VecDeque<CamReply>,
1801 cam_hold: bool,
1803 cam_progress_buf: std::collections::VecDeque<CamProgress>,
1804 cam_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1807 cam_sim_buf: std::collections::VecDeque<CamSimReply>,
1808 cam_sim_progress_buf: std::collections::VecDeque<CamSimProgress>,
1809 cam_sim_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1811 plugin_action_buf: std::collections::VecDeque<PluginActionReply>,
1812 plugin_install_buf: std::collections::VecDeque<PluginInstallReply>,
1813 sent_plugin_revision: Option<u64>,
1814 progress_buf: std::collections::VecDeque<RunProgress>,
1817 sent_library_revision: Option<u64>,
1821 library_requested: bool,
1824 stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
1828}
1829
1830#[cfg(not(target_arch = "wasm32"))]
1831impl ThreadRunner {
1832 pub fn new() -> Self {
1833 let (tx, cmd_rx) = std::sync::mpsc::channel::<Command>();
1834 let (reply_tx, rx) = std::sync::mpsc::channel::<Reply>();
1835 let stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1836 let thread_stop = stop.clone();
1837 let cam_cancel = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1838 let thread_cam_cancel = cam_cancel.clone();
1839 let cam_sim_cancel = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1840 let thread_cam_sim_cancel = cam_sim_cancel.clone();
1841 let handle = std::thread::Builder::new()
1842 .name("brep-history-runner".to_string())
1843 .spawn(move || thread_main(cmd_rx, reply_tx, thread_stop, thread_cam_cancel, thread_cam_sim_cancel))
1844 .expect("spawn brep-history-runner thread");
1845 Self {
1846 tx: Some(tx),
1847 rx,
1848 handle: Some(handle),
1849 run_buf: std::collections::VecDeque::new(),
1850 query_buf: std::collections::VecDeque::new(),
1851 mesh_import_buf: std::collections::VecDeque::new(),
1852 mesh_phase_buf: std::collections::VecDeque::new(),
1853 region_fit_buf: std::collections::VecDeque::new(),
1854 apply_choices_buf: std::collections::VecDeque::new(),
1855 step_probe_buf: std::collections::VecDeque::new(),
1856 topology_buf: std::collections::VecDeque::new(),
1857 sheet_lines_buf: std::collections::VecDeque::new(),
1858 cam_buf: std::collections::VecDeque::new(),
1859 cam_hold: false,
1860 cam_progress_buf: std::collections::VecDeque::new(),
1861 cam_cancel,
1862 cam_sim_buf: std::collections::VecDeque::new(),
1863 cam_sim_progress_buf: std::collections::VecDeque::new(),
1864 cam_sim_cancel,
1865 plugin_action_buf: std::collections::VecDeque::new(),
1866 plugin_install_buf: std::collections::VecDeque::new(),
1867 sent_plugin_revision: None,
1868 progress_buf: std::collections::VecDeque::new(),
1869 sent_library_revision: None,
1870 library_requested: false,
1871 stop,
1872 }
1873 }
1874
1875 fn drain(&mut self) {
1878 while let Ok(reply) = self.rx.try_recv() {
1879 self.file(reply);
1880 }
1881 }
1882
1883 fn file(&mut self, reply: Reply) -> Option<(u64, Option<brep_kernel::SessionTransfer>)> {
1886 match reply {
1887 Reply::SessionExport { generation, transfer } => return Some((generation, transfer)),
1888 Reply::ValidatedPlugins(reply) => self.plugin_install_buf.push_back(reply),
1889 Reply::PluginAction(reply) => self.plugin_action_buf.push_back(reply),
1890 Reply::Run(run) => self.run_buf.push_back(run),
1891 Reply::Query(query) => self.query_buf.push_back(query),
1892 Reply::MeshImport(reply) => self.mesh_import_buf.push_back(reply),
1893 Reply::MeshImportPhase(reply) => self.mesh_phase_buf.push_back(reply),
1894 Reply::RegionFit(reply) => self.region_fit_buf.push_back(reply),
1895 Reply::ApplyRegionChoices(reply) => self.apply_choices_buf.push_back(reply),
1896 Reply::StepProbe(reply) => self.step_probe_buf.push_back(reply),
1897 Reply::Topology(reply) => self.topology_buf.push_back(reply),
1898 Reply::SheetLines(reply) => self.sheet_lines_buf.push_back(reply),
1899 Reply::CamGenerate(reply) => self.cam_buf.push_back(reply),
1900 Reply::CamProgress(progress) => self.cam_progress_buf.push_back(progress),
1901 Reply::CamSimulate(reply) => self.cam_sim_buf.push_back(reply),
1902 Reply::CamSimProgress(progress) => self.cam_sim_progress_buf.push_back(progress),
1903 Reply::Progress(progress) => self.progress_buf.push_back(progress),
1904 Reply::NeedPartsLibrary => {
1905 self.sent_library_revision = None;
1910 self.library_requested = true;
1911 }
1912 }
1913 None
1914 }
1915}
1916
1917#[cfg(not(target_arch = "wasm32"))]
1920const SESSION_EXPORT_WAIT: std::time::Duration = std::time::Duration::from_millis(250);
1921
1922#[cfg(not(target_arch = "wasm32"))]
1923impl Default for ThreadRunner {
1924 fn default() -> Self {
1925 Self::new()
1926 }
1927}
1928
1929#[cfg(not(target_arch = "wasm32"))]
1943fn thread_main(
1944 cmd_rx: std::sync::mpsc::Receiver<Command>,
1945 reply_tx: std::sync::mpsc::Sender<Reply>,
1946 stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
1947 cam_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1948 cam_sim_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1949) {
1950 let mut runner = crate::pipeline::SceneRunner::new();
1951 while let Ok(first) = cmd_rx.recv() {
1952 if stop.load(std::sync::atomic::Ordering::Relaxed) {
1953 return;
1954 }
1955 let mut batch = vec![first];
1957 loop {
1958 match cmd_rx.try_recv() {
1959 Ok(command) => batch.push(command),
1960 Err(_) => break, }
1962 }
1963 let mut run_here: Vec<bool> = vec![true; batch.len()];
1967 for i in 0..batch.len() {
1968 if matches!(batch[i], Command::Run { .. })
1969 && matches!(batch.get(i + 1), Some(Command::Run { .. }))
1970 {
1971 run_here[i] = false;
1972 }
1973 }
1974 for (i, command) in batch.into_iter().enumerate() {
1979 if !run_here[i] {
1980 continue;
1981 }
1982 let is_cam = matches!(command, Command::CamGenerate(_));
1985 if is_cam {
1986 cam_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
1987 }
1988 let is_sim = matches!(command, Command::CamSimulate(_));
1989 let mut progress = |reply: Reply| {
1990 let sent = reply_tx.send(reply).is_ok();
1993 if sent {
1994 wake_main();
1995 }
1996 sent
1997 && !stop.load(std::sync::atomic::Ordering::Relaxed)
1998 && !(is_cam && cam_cancel.load(std::sync::atomic::Ordering::SeqCst))
1999 && !(is_sim && cam_sim_cancel.load(std::sync::atomic::Ordering::SeqCst))
2000 };
2001 if let Some(reply) = process_command(&mut runner, command, &mut progress) {
2002 if reply_tx.send(reply).is_err() {
2003 return; }
2005 wake_main();
2006 }
2007 }
2008 }
2009}
2010
2011#[cfg(not(target_arch = "wasm32"))]
2012impl HistoryRunner for ThreadRunner {
2013 fn session_for_main(&mut self, generation: u64) -> SessionOnMain {
2014 let Some(tx) = &self.tx else { return SessionOnMain::Unavailable };
2015 if tx.send(Command::ExportSession { generation }).is_err() {
2016 return SessionOnMain::Unavailable;
2017 }
2018 let deadline = std::time::Instant::now() + SESSION_EXPORT_WAIT;
2019 loop {
2020 let left = deadline.saturating_duration_since(std::time::Instant::now());
2021 let Ok(reply) = self.rx.recv_timeout(left) else { return SessionOnMain::Unavailable };
2022 if let Some((answered, transfer)) = self.file(reply) {
2023 if answered != generation {
2024 continue; }
2026 return transfer.map_or(SessionOnMain::Unavailable, SessionOnMain::Copied);
2027 }
2028 }
2029 }
2030 fn submit_plugin_install(&mut self, request: PluginInstallRequest) -> bool {
2031 self.tx.as_ref().is_some_and(|tx| tx.send(Command::ValidatePlugins(request)).is_ok())
2032 }
2033 fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> {
2034 self.drain();
2035 self.plugin_install_buf.pop_front()
2036 }
2037 fn sync_plugins(&mut self, revision: u64, fetch: &mut dyn FnMut() -> serde_json::Value) {
2038 if self.sent_plugin_revision != Some(revision) {
2039 if let Some(tx) = &self.tx {
2040 if tx.send(Command::SetPlugins { revision, packages: fetch() }).is_ok() {
2041 self.sent_plugin_revision = Some(revision);
2042 }
2043 }
2044 }
2045 }
2046 fn submit_plugin_action(&mut self, request: PluginActionRequest) -> bool {
2047 self.tx.as_ref().is_some_and(|tx| tx.send(Command::PluginAction(request)).is_ok())
2048 }
2049 fn poll_plugin_action(&mut self) -> Option<PluginActionReply> {
2050 self.drain();
2051 self.plugin_action_buf.pop_front()
2052 }
2053
2054 fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64) {
2055 if let Some(tx) = &self.tx {
2056 let _ = tx.send(Command::Run {
2057 request,
2058 generation,
2059 parts_library_revision: self.sent_library_revision.unwrap_or(0),
2060 });
2061 }
2062 }
2063
2064 fn sync_parts_library(
2065 &mut self,
2066 revision: u64,
2067 fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
2068 ) {
2069 if self.sent_library_revision == Some(revision) {
2070 return;
2071 }
2072 if let Some(tx) = &self.tx {
2073 let _ = tx.send(Command::SetPartsLibrary {
2074 library: fetch(),
2075 revision,
2076 });
2077 self.sent_library_revision = Some(revision);
2078 }
2079 }
2080
2081 fn poll_library_request(&mut self) -> bool {
2082 self.drain();
2083 std::mem::take(&mut self.library_requested)
2084 }
2085
2086 fn poll_run(&mut self) -> Option<RunReply> {
2087 self.drain();
2088 self.run_buf.pop_front()
2089 }
2090
2091 fn run_reply_waiting(&mut self) -> bool {
2092 self.drain();
2093 !self.run_buf.is_empty() || self.library_requested
2094 }
2095
2096 fn submit_query(&mut self, query: MeasureQuery) {
2097 if let Some(tx) = &self.tx {
2098 let _ = tx.send(Command::Query(query));
2099 }
2100 }
2101
2102 fn poll_query(&mut self) -> Option<MeasureReply> {
2103 self.drain();
2104 self.query_buf.pop_front()
2105 }
2106
2107 fn submit_mesh_import(&mut self, request: MeshImportRequest) {
2108 if let Some(tx) = &self.tx {
2109 let _ = tx.send(Command::MeshImport(request));
2110 }
2111 }
2112
2113 fn poll_mesh_import(&mut self) -> Option<MeshImportReply> {
2114 self.drain();
2115 self.mesh_import_buf.pop_front()
2116 }
2117
2118 fn poll_mesh_import_phase(&mut self) -> Option<MeshImportPhaseReply> {
2119 self.drain();
2120 self.mesh_phase_buf.pop_front()
2121 }
2122
2123 fn submit_region_fit(&mut self, job: RegionFitJob) -> bool {
2124 match &self.tx {
2125 Some(tx) => tx.send(Command::RegionFit(job)).is_ok(),
2126 None => false,
2127 }
2128 }
2129
2130 fn poll_region_fit(&mut self) -> Option<RegionFitReply> {
2131 self.drain();
2132 self.region_fit_buf.pop_front()
2133 }
2134
2135 fn submit_apply_region_choices(&mut self, job: ApplyRegionChoicesJob) -> bool {
2136 match &self.tx {
2137 Some(tx) => tx.send(Command::ApplyRegionChoices(job)).is_ok(),
2138 None => false,
2139 }
2140 }
2141
2142 fn poll_apply_region_choices(&mut self) -> Option<ApplyRegionChoicesReply> {
2143 self.drain();
2144 self.apply_choices_buf.pop_front()
2145 }
2146
2147 fn submit_step_probe(&mut self, request: StepProbeRequest) {
2148 if let Some(tx) = &self.tx {
2149 let _ = tx.send(Command::StepProbe(request));
2150 }
2151 }
2152
2153 fn poll_step_probe(&mut self) -> Option<StepProbeReply> {
2154 self.drain();
2155 self.step_probe_buf.pop_front()
2156 }
2157
2158 fn submit_topology(&mut self, request: TopologyRequest) {
2159 if let Some(tx) = &self.tx {
2160 let _ = tx.send(Command::Topology(request));
2161 }
2162 }
2163
2164 fn poll_topology(&mut self) -> Option<TopologyReply> {
2165 self.drain();
2166 self.topology_buf.pop_front()
2167 }
2168
2169 fn wait_topology(&mut self, timeout: std::time::Duration) -> bool {
2170 self.drain();
2171 let deadline = std::time::Instant::now() + timeout;
2172 while self.topology_buf.is_empty() {
2173 let now = std::time::Instant::now();
2174 if now >= deadline {
2175 return false;
2176 }
2177 match self.rx.recv_timeout(deadline - now) {
2178 Ok(reply) => {
2179 self.file(reply);
2180 }
2181 Err(_) => return false,
2182 }
2183 }
2184 true
2185 }
2186
2187 fn submit_sheet_lines(&mut self, request: SheetLinesRequest) -> bool {
2188 match &self.tx {
2189 Some(tx) => tx.send(Command::SheetLines(request)).is_ok(),
2190 None => false,
2191 }
2192 }
2193
2194 fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
2195 self.drain();
2196 self.sheet_lines_buf.pop_front()
2197 }
2198
2199 fn submit_cam(&mut self, job: CamJob) -> bool {
2200 self.cam_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
2201 match &self.tx {
2202 Some(tx) => tx.send(Command::CamGenerate(job)).is_ok(),
2203 None => false,
2204 }
2205 }
2206
2207 fn poll_cam(&mut self) -> Option<CamReply> {
2208 self.drain();
2209 if self.cam_hold {
2210 return None;
2211 }
2212 self.cam_buf.pop_front()
2213 }
2214
2215 fn hold_cam_replies(&mut self, hold: bool) {
2216 self.cam_hold = hold;
2217 }
2218
2219 fn poll_cam_progress(&mut self) -> Option<CamProgress> {
2220 self.drain();
2221 let latest = self.cam_progress_buf.pop_back();
2222 self.cam_progress_buf.clear();
2223 latest
2224 }
2225
2226 fn cancel_cam(&mut self) {
2227 self.cam_cancel.store(true, std::sync::atomic::Ordering::SeqCst);
2228 }
2229
2230 fn submit_cam_sim(&mut self, job: CamSimJob) -> bool {
2231 self.cam_sim_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
2233 match &self.tx {
2234 Some(tx) => tx.send(Command::CamSimulate(job)).is_ok(),
2235 None => false,
2236 }
2237 }
2238
2239 fn poll_cam_sim(&mut self) -> Option<CamSimReply> {
2240 self.drain();
2241 self.cam_sim_buf.pop_front()
2242 }
2243
2244 fn poll_cam_sim_progress(&mut self) -> Option<CamSimProgress> {
2245 self.drain();
2246 let latest = self.cam_sim_progress_buf.pop_back();
2247 self.cam_sim_progress_buf.clear();
2248 latest
2249 }
2250
2251 fn cancel_cam_sim(&mut self) {
2252 self.cam_sim_cancel.store(true, std::sync::atomic::Ordering::SeqCst);
2253 }
2254
2255 fn poll_progress(&mut self) -> Option<RunProgress> {
2256 self.drain();
2257 let latest = self.progress_buf.pop_back();
2258 self.progress_buf.clear();
2259 latest
2260 }
2261
2262 fn cancel(&mut self) -> bool {
2268 self.stop.store(true, std::sync::atomic::Ordering::Relaxed);
2269 self.tx.take();
2270 self.handle.take();
2271 *self = Self::new();
2272 true
2273 }
2274
2275 fn reset(&mut self) {
2276 self.sent_plugin_revision = None;
2277 self.plugin_action_buf.clear();
2278 if let Some(tx) = &self.tx {
2279 let _ = tx.send(Command::Reset);
2280 }
2281 self.run_buf.clear();
2284 self.query_buf.clear();
2285 self.mesh_import_buf.clear();
2286 self.mesh_phase_buf.clear();
2287 self.region_fit_buf.clear();
2288 self.apply_choices_buf.clear();
2289 self.step_probe_buf.clear();
2290 self.topology_buf.clear();
2291 self.sheet_lines_buf.clear();
2292 self.progress_buf.clear();
2293 self.sent_library_revision = None;
2296 self.library_requested = false;
2297 }
2298}
2299
2300#[cfg(not(target_arch = "wasm32"))]
2301impl Drop for ThreadRunner {
2302 fn drop(&mut self) {
2303 self.tx.take();
2307 self.handle.take();
2308 }
2309}
2310