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}
78
79#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
85pub struct MeasureQuery {
86 pub id: u64,
88 pub kind: MeasureKind,
90 pub owner: String,
92 pub entity: String,
94 pub density: f64,
96}
97
98#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
102pub struct MeasureReply {
103 pub id: u64,
105 pub result: String,
107}
108
109#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
116pub struct TopologyRequest {
117 pub id: u64,
119 pub solids: Vec<(String, u32)>,
121}
122
123#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
127pub struct TopologyReply {
128 pub id: u64,
130 pub solids: Vec<(String, u32, brep_kernel::BrepSolid)>,
131}
132
133fn topology_reply(request: TopologyRequest) -> TopologyReply {
136 let solids = request
137 .solids
138 .into_iter()
139 .filter_map(|(name, handle)| {
140 brep_kernel::registered_solid_clone(handle)
141 .ok()
142 .map(|solid| (name, handle, solid))
143 })
144 .collect();
145 TopologyReply { id: request.id, solids }
146}
147
148#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
154pub struct SheetLinesRequest {
155 pub id: u64,
157 pub job: crate::sheets::project::LinesJob,
158}
159
160#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
164pub struct SheetLinesReply {
165 pub id: u64,
167 pub key: String,
168 pub lines: Result<crate::sheets::project::ExactLines, String>,
169}
170
171fn sheet_lines_reply(request: SheetLinesRequest) -> SheetLinesReply {
174 SheetLinesReply {
175 id: request.id,
176 lines: crate::sheets::project::run_lines_job(&request.job),
177 key: request.job.key,
178 }
179}
180
181#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
194pub struct CamJob {
195 pub id: u64,
197 pub operation: brep_cam::model::Operation,
198 pub setup: brep_cam::model::Setup,
199 pub tools: Vec<brep_cam::model::Tool>,
200 pub solids: Vec<(String, u32)>,
202 pub chord_tolerance: f64,
204 pub budget_chunk: u64,
206 #[serde(default)]
210 pub chain_curves: Vec<(String, String, brep_kernel::NurbsCurve)>,
211 #[serde(default)]
213 pub chain_profiles: Vec<(String, brep_kernel::SketchProfile)>,
214 #[serde(default)]
217 pub machine: Option<brep_cam::model::Machine>,
218 #[serde(default)]
220 pub autoprogram: Option<brep_cam::autoprogram::binding::Binding>,
221 #[serde(default)]
225 pub earlier: Vec<(brep_cam::model::Operation, Option<brep_cam::model::Toolpath>)>,
226 #[serde(default)]
228 pub previous_setups: Vec<brep_cam::SetupHistory>,
229 #[serde(default)]
232 pub source: Option<brep_cam::program::OperationInput>,
233 #[serde(default)]
236 pub source_operation: Option<brep_cam::model::Operation>,
237}
238
239#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
242pub struct CamReply {
243 #[serde(default, skip_serializing_if = "Option::is_none")]
245 pub turning_profile: Option<brep_cam::turning::profile::TurningProfile>,
246 #[serde(default, skip_serializing_if = "Option::is_none")]
249 pub setup_start: Option<brep_cam::SetupStart>,
250 pub id: u64,
251 pub operation: String,
252 pub toolpath: Result<brep_cam::model::Toolpath, String>,
253 pub work_units: u64,
254 pub cancelled: bool,
256}
257
258#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
261pub struct CamProgress {
262 pub id: u64,
263 pub operation: String,
264 pub phase: String,
265 pub message: String,
266 pub current: u64,
267 pub total: u64,
268}
269
270#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
277pub enum CamSimRequest {
278 Load {
280 timeline: brep_cam::multiaxis::sim::Timeline,
281 stock: brep_cam::multiaxis::sim::StockModel,
282 ranges: Vec<brep_cam::multiaxis::sim::RangeCutter>,
283 options: brep_cam::multiaxis::sim::DriverOptions,
284 #[serde(default)]
287 machine: Option<brep_cam::model::Machine>,
288 },
289 Advance { target: usize, budget: usize, stock: bool, known_version: u64 },
294 Drop,
296}
297
298#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
300pub struct CamSimJob {
301 pub session: u64,
302 pub seq: u64,
303 pub request: CamSimRequest,
304}
305
306#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
308pub struct CamSimReply {
309 pub session: u64,
310 pub seq: u64,
311 pub applied: usize,
312 pub version: u64,
313 pub removed_volume: f64,
314 pub stock_volume: f64,
315 pub unsupported: Vec<usize>,
316 pub warnings: Vec<(usize, brep_cam::model::Diagnostic)>,
317 pub stock: Option<brep_cam::multiaxis::sim::StockModel>,
319 pub reached: bool,
321 pub cancelled: bool,
323 pub error: Option<String>,
324}
325
326#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
328pub struct CamSimProgress {
329 pub session: u64,
330 pub seq: u64,
331 pub applied: usize,
332 pub target: usize,
333}
334
335struct CamSimSession {
337 session: u64,
338 timeline: brep_cam::multiaxis::sim::Timeline,
339 ranges: Vec<brep_cam::multiaxis::sim::RangeCutter>,
340 driver: brep_cam::multiaxis::sim::SimDriver,
341}
342
343thread_local! {
344 static CAM_SIM: std::cell::RefCell<Option<CamSimSession>> = const { std::cell::RefCell::new(None) };
345}
346
347fn cam_sim_clear() {
349 CAM_SIM.with(|c| *c.borrow_mut() = None);
350}
351
352thread_local! {
353 static PRE_OP_STOCK: std::cell::RefCell<brep_cam::PreOpStockCache> = std::cell::RefCell::new(brep_cam::PreOpStockCache::new(16));
356}
357
358fn pre_op_stock_clear() {
360 PRE_OP_STOCK.with(|c| c.borrow_mut().clear());
361}
362
363fn cam_sim_reply(job: CamSimJob, post: &mut dyn FnMut(Reply) -> bool) -> CamSimReply {
364 let (session, seq) = (job.session, job.seq);
365 let empty = |error: Option<String>| CamSimReply {
366 session,
367 seq,
368 applied: 0,
369 version: 0,
370 removed_volume: 0.0,
371 stock_volume: 0.0,
372 unsupported: Vec::new(),
373 warnings: Vec::new(),
374 stock: None,
375 reached: false,
376 cancelled: false,
377 error,
378 };
379 match job.request {
380 CamSimRequest::Drop => {
381 cam_sim_clear();
382 empty(None)
383 }
384 CamSimRequest::Load { timeline, stock, ranges, options, machine } => match brep_cam::multiaxis::sim::SimDriver::new(stock, options) {
385 Ok(driver) => {
386 let driver = driver.with_chain(brep_cam::multiaxis::sim::sim_chain(machine.as_ref()));
387 let reply = CamSimReply {
388 applied: driver.applied,
389 version: driver.version,
390 stock_volume: driver.stock.volume(),
391 reached: true,
392 ..empty(None)
393 };
394 CAM_SIM.with(|c| *c.borrow_mut() = Some(CamSimSession { session, timeline, ranges, driver }));
395 reply
396 }
397 Err(e) => empty(Some(e.to_string())),
398 },
399 CamSimRequest::Advance { target, budget, stock, known_version } => CAM_SIM.with(|c| {
400 let mut guard = c.borrow_mut();
401 let Some(sim) = guard.as_mut().filter(|s| s.session == session) else {
402 return empty(Some("no simulation session is loaded on the runner".into()));
403 };
404 let mut cancelled = false;
405 let tolerance = brep_cam::Tolerance::default();
406 let outcome = sim.driver.advance_ranges(&sim.timeline, &sim.ranges, target, budget, &tolerance, &mut |applied| {
407 let keep = post(Reply::CamSimProgress(CamSimProgress { session, seq, applied, target }));
408 if !keep {
409 cancelled = true;
410 }
411 keep
412 });
413 let d = &sim.driver;
414 let (reached, error) = match outcome {
415 Ok(o) => (o == brep_cam::multiaxis::sim::AdvanceOutcome::Reached, None),
416 Err(e) => (false, Some(e.to_string())),
417 };
418 CamSimReply {
419 session,
420 seq,
421 applied: d.applied,
422 version: d.version,
423 removed_volume: d.removed_volume,
424 stock_volume: d.stock.volume(),
425 unsupported: d.unsupported.clone(),
426 warnings: d.warnings.clone(),
427 stock: (stock && d.version != known_version).then(|| d.stock.clone()),
428 reached,
429 cancelled,
430 error,
431 }
432 }),
433 }
434}
435
436struct CamSink<'a> {
439 id: u64,
440 operation: String,
441 token: brep_cam::CancelToken,
442 post: &'a mut dyn FnMut(Reply) -> bool,
443}
444
445impl brep_cam::ProgressSink for CamSink<'_> {
446 fn progress(&mut self, progress: &brep_cam::Progress) {
447 if std::env::var_os("BREP_CAM_DEBUG").is_some() {
448 eprintln!("cam job {} progress: {} {}/{}", self.operation, progress.phase, progress.current, progress.total);
449 }
450 let keep_going = (self.post)(Reply::CamProgress(CamProgress {
451 id: self.id,
452 operation: self.operation.clone(),
453 phase: progress.phase.clone(),
454 message: progress.message.clone(),
455 current: progress.current,
456 total: progress.total,
457 }));
458 if !keep_going {
459 if std::env::var_os("BREP_CAM_DEBUG").is_some() {
460 eprintln!("cam job {}: the driver said stop; cancelling", self.operation);
461 }
462 self.token.cancel();
463 }
464 }
465}
466
467pub const CAM_TRIANGLE_BUDGET: usize = 4_000_000;
470
471fn cam_generate_reply(job: CamJob, post: &mut dyn FnMut(Reply) -> bool) -> CamReply {
473 use brep_cam::kernel_adapter::{machine_snapshot, revolved_faces, BodyInput};
474 use brep_cam::{Runner, Tolerance, TreeIndex, TreeOptions, WorkBudget};
475 let operation = job.operation.id.as_str().to_string();
476 let token = brep_cam::CancelToken::new();
477 let mut sink = CamSink { id: job.id, operation: operation.clone(), token: token.clone(), post };
478 let mut certified_turning_profile = None;
479 let mut setup_start = None;
480 let mut outcome = || -> Result<(brep_cam::model::Toolpath, u64), String> {
481 let mut bodies: Vec<(String, brep_kernel::BrepSolid)> = Vec::new();
483 for (name, handle) in &job.solids {
484 let solid = brep_kernel::registered_solid_clone(*handle)
485 .map_err(|e| format!("body `{name}` is not resident on the runner ({e}); run the history again"))?;
486 bodies.push((name.clone(), solid));
487 }
488 let diagonal = bodies
489 .iter()
490 .filter_map(|(_, s)| brep_cam::kernel_adapter::body_bounds(s, job.chord_tolerance).ok())
491 .fold(brep_cam::model::Bounds3::empty(), |acc, b| acc.union(&b))
492 .diagonal();
493 let tolerance = if diagonal.is_finite() && diagonal > 0.0 { Tolerance::scaled_for_diagonal(diagonal) } else { Tolerance::default() };
494 let mut snapshot_setup = job.setup.clone();
498 for name in &job.operation.targets.target_solids {
499 if !snapshot_setup.targets.iter().any(|t| &t.solid == name) {
500 snapshot_setup.targets.push(brep_cam::model::TargetRef { solid: name.clone(), face: None });
501 }
502 }
503 for name in &job.operation.targets.protected_solids {
504 let listed = snapshot_setup.targets.iter().chain(snapshot_setup.protected.iter()).any(|t| &t.solid == name);
505 if !listed {
506 snapshot_setup.protected.push(brep_cam::model::TargetRef { solid: name.clone(), face: None });
507 }
508 }
509 let (mesh, unresolved) = machine_snapshot(&snapshot_setup, job.chord_tolerance, tolerance, |name| bodies.iter().find(|(n, _)| n == name).map(|(_, s)| s))
510 .map_err(|e| format!("{e}"))?;
511 if !unresolved.is_empty() {
512 return Err(format!("the setup names bodies the model does not have: {}", unresolved.join(", ")));
513 }
514 if std::env::var_os("BREP_CAM_DEBUG").is_some() {
515 eprintln!("cam job {}: {} bodies, snapshot {} faces / {} triangles, bounds {:?}", operation, bodies.len(), mesh.faces.len(), mesh.triangles.len(), mesh.bounds);
516 }
517 if mesh.triangles.len() > CAM_TRIANGLE_BUDGET {
521 return Err(format!(
522 "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",
523 mesh.triangles.len(),
524 job.chord_tolerance
525 ));
526 }
527 let index = TreeIndex::build(&mesh, TreeOptions::default());
528 let mut runner = Runner::new(token.clone(), &mut sink, WorkBudget::new(job.budget_chunk.max(1))).with_operation(operation.clone());
529 let holes = if matches!(job.operation.strategy, brep_cam::model::Strategy::Drill(_)) {
531 let mut carriers = Vec::new();
532 for (name, solid) in &bodies {
533 if job.setup.targets.iter().any(|t| t.solid == *name) || job.operation.targets.target_solids.contains(name) {
534 let body = BodyInput { name, solid, protected_only: false };
535 carriers.extend(revolved_faces(&body, &job.setup));
536 }
537 }
538 brep_cam::holes::recognize(&carriers, &mesh, tolerance, &mut runner).map_err(|e| e.to_string())?.holes
539 } else {
540 Vec::new()
541 };
542 let mut effective_operation = job.operation.clone();
546 let mut automatic_geometry = None;
547 let mut rebound = false;
548 if let Some(binding) = &job.autoprogram {
549 let tool = job.tools.iter().find(|t| t.id == job.operation.tool).ok_or("automatic programming tool is missing")?;
550 binding.validate_inputs(&job.operation, &job.setup, tool)?;
551 let fingerprints: std::collections::BTreeMap<_, _> = bodies.iter().map(|(name, solid)| (name.clone(), crate::scene::brep_fingerprint(solid))).collect();
552 if fingerprints == binding.body_fingerprints {
553 automatic_geometry = Some(binding.geometry.clone());
554 } else {
555 let recognized = brep_cam::autoprogram::binding::recognize_bodies(&bodies, &job.setup, &mesh, job.chord_tolerance, tolerance, &mut runner).map_err(|e| e.to_string())?;
556 let model_bounds = bodies.iter().filter(|(name, _)| job.setup.targets.iter().any(|t| &t.solid == name))
557 .filter_map(|(_, s)| brep_cam::kernel_adapter::body_bounds(s, job.chord_tolerance).ok())
558 .fold(brep_cam::model::Bounds3::empty(), |a, b| a.union(&b));
559 let stock_bounds = match &job.setup.stock {
560 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()),
561 _ => job.setup.stock.model_bounds(&model_bounds),
562 };
563 let stock_top = stock_bounds.map(|b| job.setup.model_to_machine.apply_bounds(&b).max.z);
564 let (op, geometry) = binding.rebind_with_mesh(&recognized, &mesh, &job.operation, &job.setup, &job.tools, job.machine.as_ref(), stock_top, tolerance)?;
565 effective_operation = op;
566 automatic_geometry = Some(geometry);
567 rebound = true;
568 }
569 }
570 let resolved_planar = match &automatic_geometry { Some(brep_cam::autoprogram::OperationGeometry::Planar(p)) => Some((&effective_operation.id, p)), _ => None };
571 let holes = match &automatic_geometry { Some(brep_cam::autoprogram::OperationGeometry::Holes(h)) => h.as_slice(), _ => holes.as_slice() };
572 let mut pre_op_warning = None;
577 let live_contour = matches!(job.operation.strategy, brep_cam::Strategy::Contour2D(_) | brep_cam::Strategy::Trace(_))
581 && job.operation.live_tool.unwrap_or(true)
582 && job.machine.as_ref().is_some_and(|m| matches!(m.kind, brep_cam::MachineKind::MillTurn { .. }));
583 let pre_op = if !live_contour && brep_cam::uses_pre_op_stock(&job.operation.strategy, job.machine.as_ref()) {
584 let fallback = job.operation.targets.target_solids.clone();
585 let request = brep_cam::JobStock {
586 setup: &job.setup,
587 machine: job.machine.as_ref(),
588 tools: &job.tools,
589 mesh: &mesh,
590 fallback_targets: &fallback,
591 earlier: &job.earlier,
592 previous: &job.previous_setups,
593 tolerance,
594 };
595 match PRE_OP_STOCK.with(|c| brep_cam::job_stock(&mut c.borrow_mut(), &request, &mut runner)) {
596 Ok(result) => Some(result),
597 Err(brep_cam::PreOpStockError::Cancelled) => return Err("cancelled".to_string()),
598 Err(error) => {
599 pre_op_warning = Some(brep_cam::unavailable_warning(&error));
600 None
601 }
602 }
603 } else {
604 None
605 };
606 let input = brep_cam::dispatch::GenerateInput {
607 operation: &effective_operation,
608 setup: &job.setup,
609 tools: &job.tools,
610 mesh: &mesh,
611 index: Some(&index),
612 tolerance,
613 holes,
614 };
615 let solid_refs: Vec<(&str, &brep_kernel::BrepSolid)> = bodies.iter().map(|(n, s)| (n.as_str(), s)).collect();
618 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();
619 let profile_refs: Vec<(&str, &brep_kernel::SketchProfile)> = job.chain_profiles.iter().map(|(s, p)| (s.as_str(), p)).collect();
620 let chain_geometry = brep_cam::dispatch::chains::KernelChainGeometry { solids: &solid_refs, curves: &curve_refs, profiles: &profile_refs };
621 let is_turning = matches!(job.operation.strategy, brep_cam::Strategy::TurnFace(_) | brep_cam::Strategy::TurnProfile(_));
622 let mut turning_carriers = Vec::new();
623 let turning_tool = if is_turning {
624 let tool = job.tools.iter().find(|t| t.id == job.operation.tool).ok_or("turning tool is missing")?;
625 if tool.turning_geometry.is_none() { return Err(format!("tool {} has no insert geometry", tool.id)); }
626 if job.setup.turning_stick_out.is_none() { return Err("setup has no stick-out".into()); }
627 let targets: std::collections::BTreeSet<_> = job.setup.targets.iter().map(|t| t.solid.as_str()).collect();
628 if targets.len() != 1 || job.operation.targets.target_solids.iter().any(|n| !targets.contains(n.as_str())) {
629 return Err("turning requires one exact setup solid and matching operation targets".into());
630 }
631 let name = *targets.iter().next().unwrap();
632 let solid = bodies.iter().find(|(n, _)| n == name).map(|(_, s)| s).ok_or("turning setup solid is missing")?;
633 if !job.machine.as_ref().is_some_and(|m| matches!(m.kind, brep_cam::MachineKind::Lathe | brep_cam::MachineKind::MillTurn { .. })) {
634 return Err("turning envelope requires a defined lathe spindle axis".into());
635 }
636 certified_turning_profile = Some(match brep_cam::kernel_adapter::turning_faces(&BodyInput { name, solid, protected_only: false }, &job.setup, tolerance) {
637 Ok(carriers) => {
638 turning_carriers = carriers;
639 brep_cam::turning::profile::extract(&turning_carriers, &mesh, job.operation.tolerance * 0.125, &mut runner)
640 }
641 Err(_) => brep_cam::turning::envelope::extract(&mesh, job.chord_tolerance, &mut runner),
642 }.map_err(|e| e.to_string())?);
643 Some(tool)
644 } else { None };
645 let turning = turning_tool.map(|tool| brep_cam::dispatch::TurningInput {
646 tool_id: &tool.id, tool_geometry: tool.turning_geometry.as_ref().unwrap(),
647 carriers: &turning_carriers, profile: certified_turning_profile.as_ref(), contour: None,
648 stick_out: job.setup.turning_stick_out.unwrap(), groove_center_z: None, thread_radius: None,
649 });
650 let context = brep_cam::dispatch::GenerateContext {
651 turning: turning.as_ref(),
652 chain_geometry: Some(&chain_geometry),
653 machine: job.machine.as_ref(),
654 resolved_planar,
655 rest_stock: pre_op.as_ref().map(|r| &r.stock.grid),
656 pre_op_stock: pre_op.as_ref().map(|r| &r.stock.envelope),
657 source: job.source.as_ref(),
658 source_operation: job.source_operation.as_ref(),
659 ..Default::default()
660 };
661 let mut path = brep_cam::dispatch::generate_with_context(&input, &context, &mut runner).map_err(|e| e.to_string())?;
662 if rebound { path.report.warn(brep_cam::model::Diagnostic::new("autoprogram-re-recognised", "re-recognised after model change")); }
663 if let Some(warning) = pre_op_warning {
664 path.report.warn(warning);
666 path.report.warnings.rotate_right(1);
667 }
668 if let Some(result) = pre_op {
669 path.report.approximations.extend(result.limits);
670 setup_start = Some(result.start);
671 }
672 Ok((path, runner.budget().used()))
673 };
674 match outcome() {
675 Ok((toolpath, work_units)) => CamReply { turning_profile: certified_turning_profile, setup_start, id: job.id, operation, toolpath: Ok(toolpath), work_units, cancelled: false },
676 Err(message) => {
677 let cancelled = token.is_cancelled();
678 CamReply { turning_profile: None, setup_start: None, id: job.id, operation, toolpath: Err(if cancelled { "cancelled".to_string() } else { message }), work_units: 0, cancelled }
679 }
680 }
681}
682
683pub use brep_reconstruction::stl_conversion::{
684 ConversionPolicy, StlConversionOptions, StlConversionOutput,
685};
686
687#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
689pub enum MeshImportFormat {
690 Stl,
691 Obj,
692 ThreeMf,
695}
696
697#[derive(Debug, serde::Serialize, serde::Deserialize)]
700pub struct MeshImportRequest {
701 pub id: u64,
702 pub format: MeshImportFormat,
703 pub bytes: Vec<u8>,
704 pub options: StlConversionOptions,
705}
706
707#[derive(Debug, serde::Serialize, serde::Deserialize)]
710pub struct MeshImportReply {
711 pub id: u64,
712 pub result: Result<StlConversionOutput, String>,
713}
714
715#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
717pub struct PluginInstallRequest {
718 pub id: u64,
719 pub packages: serde_json::Value,
720}
721#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
722pub struct PluginInstallReply {
723 pub id: u64,
724 pub result: Result<brep_plugins::Registry, String>,
725}
726fn validate_plugins(request: PluginInstallRequest) -> PluginInstallReply {
727 let result = serde_json::from_value(request.packages)
728 .map_err(|e| format!("plugin packages: {e}"))
729 .and_then(brep_plugins::Runtime::new)
730 .map(|runtime| runtime.registry().clone());
731 PluginInstallReply { id: request.id, result }
732}
733
734#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
736pub struct PluginActionRequest {
737 pub id: u64,
738 pub pins: Vec<brep_plugins::PluginPin>,
739 pub action: String,
740 pub input: serde_json::Value,
741 #[serde(default, skip_serializing_if = "Option::is_none")]
742 pub script: Option<String>,
743}
744
745#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
746pub struct PluginActionReply {
747 pub id: u64,
748 pub result: Result<brep_plugins::ActionPlan, String>,
749}
750
751pub enum SessionOnMain {
759 Resident,
762 Copied(brep_kernel::SessionTransfer),
764 Unavailable,
766}
767
768pub trait HistoryRunner {
769 fn session_for_main(&mut self, _generation: u64) -> SessionOnMain {
773 SessionOnMain::Unavailable
774 }
775 fn submit_plugin_install(&mut self, _request: PluginInstallRequest) -> bool { false }
776 fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> { None }
777 fn sync_plugins(&mut self, _revision: u64, _fetch: &mut dyn FnMut() -> serde_json::Value) {}
779 fn submit_plugin_action(&mut self, _request: PluginActionRequest) -> bool { false }
781 fn poll_plugin_action(&mut self) -> Option<PluginActionReply> { None }
782
783 fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64);
787 fn poll_run(&mut self) -> Option<RunReply>;
789 fn submit_query(&mut self, query: MeasureQuery);
793 fn poll_query(&mut self) -> Option<MeasureReply>;
795 fn submit_mesh_import(&mut self, request: MeshImportRequest);
797 fn poll_mesh_import(&mut self) -> Option<MeshImportReply>;
799 fn submit_step_probe(&mut self, request: StepProbeRequest);
801 fn poll_step_probe(&mut self) -> Option<StepProbeReply>;
803 fn submit_topology(&mut self, _request: TopologyRequest) {}
807 fn poll_topology(&mut self) -> Option<TopologyReply> {
809 None
810 }
811 fn wait_topology(&mut self, _timeout: std::time::Duration) -> bool {
818 false
819 }
820 fn submit_sheet_lines(&mut self, _request: SheetLinesRequest) -> bool {
824 false
825 }
826 fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
828 None
829 }
830 fn submit_cam(&mut self, _job: CamJob) -> bool {
833 false
834 }
835 fn poll_cam(&mut self) -> Option<CamReply> {
836 None
837 }
838 fn poll_cam_progress(&mut self) -> Option<CamProgress> {
839 None
840 }
841 fn cancel_cam(&mut self) {}
845 fn hold_cam_replies(&mut self, _hold: bool) {}
851 fn submit_cam_sim(&mut self, _job: CamSimJob) -> bool {
854 false
855 }
856 fn poll_cam_sim(&mut self) -> Option<CamSimReply> {
857 None
858 }
859 fn poll_cam_sim_progress(&mut self) -> Option<CamSimProgress> {
860 None
861 }
862 fn cancel_cam_sim(&mut self) {}
866 fn reset(&mut self);
868
869 fn sync_parts_library(
879 &mut self,
880 _revision: u64,
881 _fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
882 ) {
883 }
884
885 fn poll_library_request(&mut self) -> bool {
891 false
892 }
893
894 fn run_reply_waiting(&mut self) -> bool {
898 true
899 }
900
901 fn poll_progress(&mut self) -> Option<RunProgress> {
905 None
906 }
907
908 fn cancel(&mut self) -> bool {
918 false
919 }
920}
921
922fn measure_json(runner: &crate::pipeline::SceneRunner, query: &MeasureQuery) -> String {
930 let Some(handle) = runner.handle_of(&query.owner) else {
931 return serde_json::json!({
932 "ok": false,
933 "message": format!("solid '{}' has no resident geometry", query.owner),
934 })
935 .to_string();
936 };
937 match query.kind {
938 MeasureKind::Solid => {
939 match brep_kernel::mass_properties_handle_native(handle, query.density) {
940 Ok(properties) => {
941 let edge_total =
942 brep_kernel::solid_edge_length_total_native(handle).unwrap_or(0.0);
943 serde_json::json!({
944 "ok": true,
945 "kind": "solid",
946 "volume": properties.volume,
947 "surfaceArea": properties.surface_area,
948 "edgeLengthTotal": edge_total,
949 "density": properties.density,
950 "weight": properties.mass,
951 })
952 .to_string()
953 }
954 Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
955 }
956 }
957 MeasureKind::Face => match brep_kernel::face_measurements_native(handle, &query.entity) {
958 Ok((area, edge_total, surface_type)) => {
959 let dimensions =
960 brep_kernel::face_carrier_dimensions_native(handle, &query.entity)
961 .ok()
962 .flatten();
963 let surface_type = dimensions.as_ref().map_or(surface_type, |d| d.label);
966 let mut fragment = serde_json::json!({
967 "ok": true,
968 "kind": "face",
969 "solid": query.owner,
970 "surfaceType": surface_type,
971 "area": area,
972 "edgeLengthTotal": edge_total,
973 });
974 insert_dimensions(&mut fragment, dimensions.as_ref());
975 fragment.to_string()
976 }
977 Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
978 },
979 MeasureKind::Edge => match brep_kernel::edge_length_native(handle, &query.entity) {
980 Ok(length) => {
981 let dimensions =
982 brep_kernel::edge_carrier_dimensions_native(handle, &query.entity)
983 .ok()
984 .flatten();
985 let mut fragment = serde_json::json!({
986 "ok": true,
987 "kind": "edge",
988 "solid": query.owner,
989 "length": length,
990 });
991 if let Some(dimensions) = &dimensions {
992 fragment["curveType"] = serde_json::Value::from(dimensions.label);
993 }
994 insert_dimensions(&mut fragment, dimensions.as_ref());
995 fragment.to_string()
996 }
997 Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
998 },
999 }
1000}
1001
1002fn insert_dimensions(
1005 fragment: &mut serde_json::Value,
1006 dimensions: Option<&brep_kernel::CarrierDimensions>,
1007) {
1008 let (Some(dimensions), Some(object)) = (dimensions, fragment.as_object_mut()) else {
1009 return;
1010 };
1011 for (key, value) in &dimensions.values {
1012 object.insert((*key).to_string(), serde_json::Value::from(*value));
1013 }
1014}
1015
1016pub struct InlineRunner {
1019 runner: crate::pipeline::SceneRunner,
1021 pending: std::collections::VecDeque<RunReply>,
1024 query_pending: std::collections::VecDeque<MeasureReply>,
1028 mesh_import_pending: std::collections::VecDeque<MeshImportReply>,
1029 step_probe_pending: std::collections::VecDeque<StepProbeReply>,
1030 topology_pending: std::collections::VecDeque<TopologyReply>,
1031 sheet_lines_pending: std::collections::VecDeque<SheetLinesReply>,
1032 plugin_action_pending: std::collections::VecDeque<PluginActionReply>,
1033 plugin_install_pending: std::collections::VecDeque<PluginInstallReply>,
1034}
1035
1036impl InlineRunner {
1037 pub fn new() -> Self {
1038 Self {
1039 runner: crate::pipeline::SceneRunner::new(),
1040 pending: std::collections::VecDeque::new(),
1041 query_pending: std::collections::VecDeque::new(),
1042 mesh_import_pending: std::collections::VecDeque::new(),
1043 step_probe_pending: std::collections::VecDeque::new(),
1044 topology_pending: std::collections::VecDeque::new(),
1045 sheet_lines_pending: std::collections::VecDeque::new(),
1046 plugin_action_pending: std::collections::VecDeque::new(),
1047 plugin_install_pending: std::collections::VecDeque::new(),
1048 }
1049 }
1050}
1051
1052impl HistoryRunner for InlineRunner {
1053 fn submit_plugin_install(&mut self, request: PluginInstallRequest) -> bool {
1054 self.plugin_install_pending.push_back(validate_plugins(request));
1055 true
1056 }
1057 fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> {
1058 self.plugin_install_pending.pop_front()
1059 }
1060 fn sync_plugins(&mut self, revision: u64, fetch: &mut dyn FnMut() -> serde_json::Value) {
1061 if self.runner.plugin_revision != Some(revision) {
1062 self.runner.set_plugins(revision, fetch());
1063 }
1064 }
1065 fn submit_plugin_action(&mut self, request: PluginActionRequest) -> bool {
1066 self.plugin_action_pending.push_back(self.runner.plugin_action(request));
1067 true
1068 }
1069 fn poll_plugin_action(&mut self) -> Option<PluginActionReply> {
1070 self.plugin_action_pending.pop_front()
1071 }
1072
1073 fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64) {
1074 let serial = brep_kernel::assembly_session_serial();
1075 let output = self.runner.run(&request);
1076 let after = brep_kernel::assembly_session_serial();
1077 self.runner.session_of = (after != serial).then_some((generation, after));
1078 self.pending.push_back(RunReply { generation, output });
1079 }
1080
1081 fn session_for_main(&mut self, generation: u64) -> SessionOnMain {
1084 match self.runner.session_of {
1085 Some((ran, serial)) if ran == generation && serial == brep_kernel::assembly_session_serial() => {
1086 SessionOnMain::Resident
1087 }
1088 _ => SessionOnMain::Unavailable,
1089 }
1090 }
1091
1092 fn sync_parts_library(
1097 &mut self,
1098 _revision: u64,
1099 _fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
1100 ) {
1101 }
1102
1103 fn poll_run(&mut self) -> Option<RunReply> {
1104 self.pending.pop_front()
1105 }
1106
1107 fn submit_query(&mut self, query: MeasureQuery) {
1108 let result = measure_json(&self.runner, &query);
1109 self.query_pending.push_back(MeasureReply { id: query.id, result });
1110 }
1111
1112 fn poll_query(&mut self) -> Option<MeasureReply> {
1113 self.query_pending.pop_front()
1114 }
1115
1116 fn submit_mesh_import(&mut self, request: MeshImportRequest) {
1117 self.mesh_import_pending
1118 .push_back(reconstruct_mesh(request));
1119 }
1120
1121 fn poll_mesh_import(&mut self) -> Option<MeshImportReply> {
1122 self.mesh_import_pending.pop_front()
1123 }
1124
1125 fn submit_step_probe(&mut self, request: StepProbeRequest) {
1126 self.step_probe_pending.push_back(probe_step(request));
1127 }
1128
1129 fn poll_step_probe(&mut self) -> Option<StepProbeReply> {
1130 self.step_probe_pending.pop_front()
1131 }
1132
1133 fn submit_topology(&mut self, request: TopologyRequest) {
1134 self.topology_pending.push_back(topology_reply(request));
1135 }
1136
1137 fn poll_topology(&mut self) -> Option<TopologyReply> {
1138 self.topology_pending.pop_front()
1139 }
1140
1141 fn submit_sheet_lines(&mut self, request: SheetLinesRequest) -> bool {
1144 self.sheet_lines_pending.push_back(sheet_lines_reply(request));
1145 true
1146 }
1147
1148 fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
1149 self.sheet_lines_pending.pop_front()
1150 }
1151
1152 fn reset(&mut self) {
1153 self.runner.reset();
1154 }
1155}
1156
1157impl Default for InlineRunner {
1158 fn default() -> Self {
1159 Self::new()
1160 }
1161}
1162
1163#[derive(serde::Serialize, serde::Deserialize)]
1177pub enum Command {
1178 ValidatePlugins(PluginInstallRequest),
1179 SetPlugins { revision: u64, packages: serde_json::Value },
1180 PluginAction(PluginActionRequest),
1181 Run {
1182 request: brep_kernel::HistoryRequest,
1183 generation: u64,
1184 #[serde(default)]
1188 parts_library_revision: u64,
1189 },
1190 SetPartsLibrary {
1202 library: brep_kernel::PartsLibraryMap,
1203 revision: u64,
1204 },
1205 Query(MeasureQuery),
1206 MeshImport(MeshImportRequest),
1207 StepProbe(StepProbeRequest),
1208 Topology(TopologyRequest),
1209 SheetLines(SheetLinesRequest),
1210 CamGenerate(CamJob),
1212 CamSimulate(CamSimJob),
1214 Reset,
1215 #[serde(skip)]
1218 ExportSession { generation: u64 },
1219}
1220
1221#[derive(serde::Serialize, serde::Deserialize)]
1223pub enum Reply {
1224 ValidatedPlugins(PluginInstallReply),
1225 PluginAction(PluginActionReply),
1226 Run(RunReply),
1227 Query(MeasureReply),
1228 MeshImport(MeshImportReply),
1229 StepProbe(StepProbeReply),
1230 Topology(TopologyReply),
1231 SheetLines(SheetLinesReply),
1232 CamGenerate(CamReply),
1233 CamProgress(CamProgress),
1235 CamSimulate(CamSimReply),
1236 CamSimProgress(CamSimProgress),
1238 NeedPartsLibrary,
1245 Progress(RunProgress),
1249 #[serde(skip)]
1251 SessionExport { generation: u64, transfer: Option<brep_kernel::SessionTransfer> },
1252}
1253
1254fn probe_step(request: StepProbeRequest) -> StepProbeReply {
1257 StepProbeReply {
1258 id: request.id,
1259 result: brep_kernel::read_step_assembly(&request.text),
1260 }
1261}
1262
1263fn reconstruct_mesh(request: MeshImportRequest) -> MeshImportReply {
1264 let result = (|| {
1265 use brep_reconstruction::stl_conversion::{
1266 binary_stl_coordinate_precision_tolerance, convert_stl_mesh_to_step_with_colors,
1267 };
1268 use brep_reconstruction::{Mesh, Vec3};
1269
1270 let mut instances = Vec::new();
1272 let mut reader_messages = Vec::new();
1273 let mut triangle_colors: Vec<Option<[u8; 3]>> = Vec::new();
1276 let (mesh, positions, indices, coordinate_precision_tolerance) = match request.format {
1277 MeshImportFormat::Stl => {
1278 use brep_reconstruction::stl::{parse_stl_bytes, StlFormat, StlReadOptions};
1279 let read_options = StlReadOptions {
1280 weld_tolerance: (request.options.weld_tolerance >= 0.0)
1281 .then_some(request.options.weld_tolerance),
1282 };
1283 let imported = parse_stl_bytes(&request.bytes, &read_options)
1284 .map_err(|error| format!("STL import failed: {error}"))?;
1285 let positions = imported
1286 .mesh
1287 .vertices
1288 .iter()
1289 .flat_map(|point| [point.x, point.y, point.z])
1290 .collect::<Vec<_>>();
1291 let indices = imported
1292 .mesh
1293 .triangles
1294 .iter()
1295 .flatten()
1296 .copied()
1297 .collect::<Vec<_>>();
1298 let precision = if imported.format == StlFormat::Binary {
1299 binary_stl_coordinate_precision_tolerance(&imported.mesh)
1300 } else {
1301 0.0
1302 };
1303 if !imported.dropped_facets.is_empty() {
1304 reader_messages.push(format!(
1305 "the STL reader dropped {} of {} facet(s) with no area: {}",
1306 imported.dropped_facets.len(),
1307 imported.source_triangle_count,
1308 imported.dropped_facets.iter().take(8).map(ToString::to_string).collect::<Vec<_>>().join("; ")
1309 ));
1310 }
1311 (imported.mesh, positions, indices, precision)
1312 }
1313 MeshImportFormat::Obj => {
1314 let text = std::str::from_utf8(&request.bytes)
1315 .map_err(|_| "OBJ import failed: file is not UTF-8 text".to_string())?;
1316 let obj = brep_kernel::read_obj(text)
1317 .map_err(|error| format!("OBJ import failed: {error}"))?;
1318 let vertices = obj
1319 .positions
1320 .chunks_exact(3)
1321 .map(|point| Vec3::new(point[0], point[1], point[2]))
1322 .collect::<Vec<_>>();
1323 let triangles = obj
1324 .indices
1325 .chunks_exact(3)
1326 .map(|triangle| [triangle[0], triangle[1], triangle[2]])
1327 .collect::<Vec<_>>();
1328 (
1329 Mesh::new(vertices, triangles),
1330 obj.positions,
1331 obj.indices,
1332 0.0,
1333 )
1334 }
1335 MeshImportFormat::ThreeMf => {
1344 let model = brep_kernel::read_3mf(&request.bytes)
1345 .map_err(|error| format!("3MF import failed: {error}"))?;
1346 instances = model.instances;
1347 triangle_colors = model.triangle_colors;
1348 let vertices = model
1349 .positions
1350 .chunks_exact(3)
1351 .map(|point| Vec3::new(point[0], point[1], point[2]))
1352 .collect::<Vec<_>>();
1353 let triangles = model
1354 .indices
1355 .chunks_exact(3)
1356 .map(|triangle| [triangle[0], triangle[1], triangle[2]])
1357 .collect::<Vec<_>>();
1358 (
1359 Mesh::new(vertices, triangles),
1360 model.positions,
1361 model.indices,
1362 0.0,
1363 )
1364 }
1365 };
1366 let mut options = request.options;
1367 options.coordinate_precision_tolerance = options.coordinate_precision_tolerance
1368 .max(coordinate_precision_tolerance);
1369 convert_stl_mesh_to_step_with_colors(
1370 &mesh,
1371 &positions,
1372 Some(&indices),
1373 (!triangle_colors.is_empty()).then_some(triangle_colors.as_slice()),
1374 &options,
1375 "Imported mesh",
1376 "MM",
1377 "",
1378 )
1379 .map(|mut output| {
1380 name_bodies(&mut output.report, &instances);
1381 output.report.messages.splice(0..0, reader_messages);
1382 output
1383 })
1384 .map_err(|error| format!("RANSAC reconstruction failed: {error}"))
1385 })();
1386 MeshImportReply {
1387 id: request.id,
1388 result,
1389 }
1390}
1391
1392fn name_bodies(
1396 report: &mut brep_reconstruction::stl_conversion::StlConversionReport,
1397 instances: &[brep_kernel::ThreeMfInstance],
1398) {
1399 for component in &mut report.components {
1400 component.source_name = instances
1401 .iter()
1402 .find(|instance| {
1403 (instance.first_triangle..instance.first_triangle + instance.triangle_count)
1404 .contains(&component.first_triangle)
1405 })
1406 .and_then(|instance| instance.name.clone());
1407 }
1408}
1409
1410pub(crate) fn refused_bodies_notice(
1414 report: &brep_reconstruction::stl_conversion::StlConversionReport,
1415) -> Option<String> {
1416 let refused = report
1417 .components
1418 .iter()
1419 .filter_map(|component| {
1420 component.refusal.as_ref().map(|reason| {
1421 let body = match &component.source_name {
1422 Some(name) => format!("body {} \"{name}\"", component.index + 1),
1423 None => format!(
1424 "body {} (from source triangle {})",
1425 component.index + 1,
1426 component.first_triangle
1427 ),
1428 };
1429 format!("{body}: {reason}")
1430 })
1431 })
1432 .collect::<Vec<_>>();
1433 (!refused.is_empty()).then(|| {
1434 format!(
1435 "mesh import: {} of {} bodies could not be reconstructed and are NOT in the model — {}",
1436 refused.len(),
1437 report.components.len(),
1438 refused.join("; ")
1439 )
1440 })
1441}
1442
1443pub fn process_command(
1456 runner: &mut crate::pipeline::SceneRunner,
1457 command: Command,
1458 progress: &mut dyn FnMut(Reply) -> bool,
1459) -> Option<Reply> {
1460 match command {
1461 Command::ValidatePlugins(request) => Some(Reply::ValidatedPlugins(validate_plugins(request))),
1462 Command::SetPlugins { revision, packages } => {
1463 runner.set_plugins(revision, packages);
1464 None
1465 }
1466 Command::PluginAction(request) => Some(Reply::PluginAction(runner.plugin_action(request))),
1467 Command::Run {
1468 request,
1469 generation,
1470 parts_library_revision,
1471 } => {
1472 let stale = match runner.parts_library_revision {
1484 Some(installed) => installed != parts_library_revision,
1485 None => parts_library_revision != 0,
1491 };
1492 let recoverable = brep_kernel::missing_library_parts(&request)
1499 .iter()
1500 .any(|name| runner.parts_library_names.contains(name));
1501 if stale || recoverable {
1502 runner.parts_library_revision = None;
1503 return Some(Reply::NeedPartsLibrary);
1504 }
1505 let serial = brep_kernel::assembly_session_serial();
1506 let output = runner.run_observed(&request, &mut |event| {
1507 progress(Reply::Progress(RunProgress {
1508 generation,
1509 index: event.index,
1510 total: event.total,
1511 feature_id: event.id.to_string(),
1512 feature_type: event.feature_type.to_string(),
1513 }))
1514 });
1515 let after = brep_kernel::assembly_session_serial();
1516 runner.session_of = (after != serial).then_some((generation, after));
1517 Some(Reply::Run(RunReply { generation, output }))
1518 }
1519 Command::SetPartsLibrary { library, revision } => {
1520 runner.parts_library_names = library.keys().cloned().collect();
1521 brep_kernel::install_parts_library(&library);
1522 runner.parts_library_revision = Some(revision);
1523 None
1524 }
1525 Command::Query(query) => {
1526 let result = measure_json(runner, &query);
1527 Some(Reply::Query(MeasureReply { id: query.id, result }))
1528 }
1529 Command::MeshImport(request) => Some(Reply::MeshImport(reconstruct_mesh(request))),
1530 Command::StepProbe(request) => Some(Reply::StepProbe(probe_step(request))),
1531 Command::Topology(request) => Some(Reply::Topology(topology_reply(request))),
1532 Command::SheetLines(request) => Some(Reply::SheetLines(sheet_lines_reply(request))),
1533 Command::CamGenerate(job) => Some(Reply::CamGenerate(cam_generate_reply(job, progress))),
1534 Command::CamSimulate(job) => Some(Reply::CamSimulate(cam_sim_reply(job, progress))),
1535 Command::Reset => {
1536 runner.reset();
1541 brep_kernel::clear_history_cache();
1542 cam_sim_clear();
1543 pre_op_stock_clear();
1544 None
1545 }
1546 Command::ExportSession { generation } => {
1547 Some(Reply::SessionExport { generation, transfer: runner.export_session(generation) })
1548 }
1549 }
1550}
1551
1552#[cfg(not(target_arch = "wasm32"))]
1566pub struct ThreadRunner {
1567 tx: Option<std::sync::mpsc::Sender<Command>>,
1570 rx: std::sync::mpsc::Receiver<Reply>,
1572 handle: Option<std::thread::JoinHandle<()>>,
1574 run_buf: std::collections::VecDeque<RunReply>,
1576 query_buf: std::collections::VecDeque<MeasureReply>,
1578 mesh_import_buf: std::collections::VecDeque<MeshImportReply>,
1579 step_probe_buf: std::collections::VecDeque<StepProbeReply>,
1580 topology_buf: std::collections::VecDeque<TopologyReply>,
1581 sheet_lines_buf: std::collections::VecDeque<SheetLinesReply>,
1582 cam_buf: std::collections::VecDeque<CamReply>,
1583 cam_hold: bool,
1585 cam_progress_buf: std::collections::VecDeque<CamProgress>,
1586 cam_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1589 cam_sim_buf: std::collections::VecDeque<CamSimReply>,
1590 cam_sim_progress_buf: std::collections::VecDeque<CamSimProgress>,
1591 cam_sim_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1593 plugin_action_buf: std::collections::VecDeque<PluginActionReply>,
1594 plugin_install_buf: std::collections::VecDeque<PluginInstallReply>,
1595 sent_plugin_revision: Option<u64>,
1596 progress_buf: std::collections::VecDeque<RunProgress>,
1599 sent_library_revision: Option<u64>,
1603 library_requested: bool,
1606 stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
1610}
1611
1612#[cfg(not(target_arch = "wasm32"))]
1613impl ThreadRunner {
1614 pub fn new() -> Self {
1615 let (tx, cmd_rx) = std::sync::mpsc::channel::<Command>();
1616 let (reply_tx, rx) = std::sync::mpsc::channel::<Reply>();
1617 let stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1618 let thread_stop = stop.clone();
1619 let cam_cancel = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1620 let thread_cam_cancel = cam_cancel.clone();
1621 let cam_sim_cancel = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1622 let thread_cam_sim_cancel = cam_sim_cancel.clone();
1623 let handle = std::thread::Builder::new()
1624 .name("brep-history-runner".to_string())
1625 .spawn(move || thread_main(cmd_rx, reply_tx, thread_stop, thread_cam_cancel, thread_cam_sim_cancel))
1626 .expect("spawn brep-history-runner thread");
1627 Self {
1628 tx: Some(tx),
1629 rx,
1630 handle: Some(handle),
1631 run_buf: std::collections::VecDeque::new(),
1632 query_buf: std::collections::VecDeque::new(),
1633 mesh_import_buf: std::collections::VecDeque::new(),
1634 step_probe_buf: std::collections::VecDeque::new(),
1635 topology_buf: std::collections::VecDeque::new(),
1636 sheet_lines_buf: std::collections::VecDeque::new(),
1637 cam_buf: std::collections::VecDeque::new(),
1638 cam_hold: false,
1639 cam_progress_buf: std::collections::VecDeque::new(),
1640 cam_cancel,
1641 cam_sim_buf: std::collections::VecDeque::new(),
1642 cam_sim_progress_buf: std::collections::VecDeque::new(),
1643 cam_sim_cancel,
1644 plugin_action_buf: std::collections::VecDeque::new(),
1645 plugin_install_buf: std::collections::VecDeque::new(),
1646 sent_plugin_revision: None,
1647 progress_buf: std::collections::VecDeque::new(),
1648 sent_library_revision: None,
1649 library_requested: false,
1650 stop,
1651 }
1652 }
1653
1654 fn drain(&mut self) {
1657 while let Ok(reply) = self.rx.try_recv() {
1658 self.file(reply);
1659 }
1660 }
1661
1662 fn file(&mut self, reply: Reply) -> Option<(u64, Option<brep_kernel::SessionTransfer>)> {
1665 match reply {
1666 Reply::SessionExport { generation, transfer } => return Some((generation, transfer)),
1667 Reply::ValidatedPlugins(reply) => self.plugin_install_buf.push_back(reply),
1668 Reply::PluginAction(reply) => self.plugin_action_buf.push_back(reply),
1669 Reply::Run(run) => self.run_buf.push_back(run),
1670 Reply::Query(query) => self.query_buf.push_back(query),
1671 Reply::MeshImport(reply) => self.mesh_import_buf.push_back(reply),
1672 Reply::StepProbe(reply) => self.step_probe_buf.push_back(reply),
1673 Reply::Topology(reply) => self.topology_buf.push_back(reply),
1674 Reply::SheetLines(reply) => self.sheet_lines_buf.push_back(reply),
1675 Reply::CamGenerate(reply) => self.cam_buf.push_back(reply),
1676 Reply::CamProgress(progress) => self.cam_progress_buf.push_back(progress),
1677 Reply::CamSimulate(reply) => self.cam_sim_buf.push_back(reply),
1678 Reply::CamSimProgress(progress) => self.cam_sim_progress_buf.push_back(progress),
1679 Reply::Progress(progress) => self.progress_buf.push_back(progress),
1680 Reply::NeedPartsLibrary => {
1681 self.sent_library_revision = None;
1686 self.library_requested = true;
1687 }
1688 }
1689 None
1690 }
1691}
1692
1693#[cfg(not(target_arch = "wasm32"))]
1696const SESSION_EXPORT_WAIT: std::time::Duration = std::time::Duration::from_millis(250);
1697
1698#[cfg(not(target_arch = "wasm32"))]
1699impl Default for ThreadRunner {
1700 fn default() -> Self {
1701 Self::new()
1702 }
1703}
1704
1705#[cfg(not(target_arch = "wasm32"))]
1719fn thread_main(
1720 cmd_rx: std::sync::mpsc::Receiver<Command>,
1721 reply_tx: std::sync::mpsc::Sender<Reply>,
1722 stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
1723 cam_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1724 cam_sim_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
1725) {
1726 let mut runner = crate::pipeline::SceneRunner::new();
1727 while let Ok(first) = cmd_rx.recv() {
1728 if stop.load(std::sync::atomic::Ordering::Relaxed) {
1729 return;
1730 }
1731 let mut batch = vec![first];
1733 loop {
1734 match cmd_rx.try_recv() {
1735 Ok(command) => batch.push(command),
1736 Err(_) => break, }
1738 }
1739 let mut run_here: Vec<bool> = vec![true; batch.len()];
1743 for i in 0..batch.len() {
1744 if matches!(batch[i], Command::Run { .. })
1745 && matches!(batch.get(i + 1), Some(Command::Run { .. }))
1746 {
1747 run_here[i] = false;
1748 }
1749 }
1750 for (i, command) in batch.into_iter().enumerate() {
1755 if !run_here[i] {
1756 continue;
1757 }
1758 let is_cam = matches!(command, Command::CamGenerate(_));
1761 if is_cam {
1762 cam_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
1763 }
1764 let is_sim = matches!(command, Command::CamSimulate(_));
1765 let mut progress = |reply: Reply| {
1766 reply_tx.send(reply).is_ok()
1769 && !stop.load(std::sync::atomic::Ordering::Relaxed)
1770 && !(is_cam && cam_cancel.load(std::sync::atomic::Ordering::SeqCst))
1771 && !(is_sim && cam_sim_cancel.load(std::sync::atomic::Ordering::SeqCst))
1772 };
1773 if let Some(reply) = process_command(&mut runner, command, &mut progress) {
1774 if reply_tx.send(reply).is_err() {
1775 return; }
1777 }
1778 }
1779 }
1780}
1781
1782#[cfg(not(target_arch = "wasm32"))]
1783impl HistoryRunner for ThreadRunner {
1784 fn session_for_main(&mut self, generation: u64) -> SessionOnMain {
1785 let Some(tx) = &self.tx else { return SessionOnMain::Unavailable };
1786 if tx.send(Command::ExportSession { generation }).is_err() {
1787 return SessionOnMain::Unavailable;
1788 }
1789 let deadline = std::time::Instant::now() + SESSION_EXPORT_WAIT;
1790 loop {
1791 let left = deadline.saturating_duration_since(std::time::Instant::now());
1792 let Ok(reply) = self.rx.recv_timeout(left) else { return SessionOnMain::Unavailable };
1793 if let Some((answered, transfer)) = self.file(reply) {
1794 if answered != generation {
1795 continue; }
1797 return transfer.map_or(SessionOnMain::Unavailable, SessionOnMain::Copied);
1798 }
1799 }
1800 }
1801 fn submit_plugin_install(&mut self, request: PluginInstallRequest) -> bool {
1802 self.tx.as_ref().is_some_and(|tx| tx.send(Command::ValidatePlugins(request)).is_ok())
1803 }
1804 fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> {
1805 self.drain();
1806 self.plugin_install_buf.pop_front()
1807 }
1808 fn sync_plugins(&mut self, revision: u64, fetch: &mut dyn FnMut() -> serde_json::Value) {
1809 if self.sent_plugin_revision != Some(revision) {
1810 if let Some(tx) = &self.tx {
1811 if tx.send(Command::SetPlugins { revision, packages: fetch() }).is_ok() {
1812 self.sent_plugin_revision = Some(revision);
1813 }
1814 }
1815 }
1816 }
1817 fn submit_plugin_action(&mut self, request: PluginActionRequest) -> bool {
1818 self.tx.as_ref().is_some_and(|tx| tx.send(Command::PluginAction(request)).is_ok())
1819 }
1820 fn poll_plugin_action(&mut self) -> Option<PluginActionReply> {
1821 self.drain();
1822 self.plugin_action_buf.pop_front()
1823 }
1824
1825 fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64) {
1826 if let Some(tx) = &self.tx {
1827 let _ = tx.send(Command::Run {
1828 request,
1829 generation,
1830 parts_library_revision: self.sent_library_revision.unwrap_or(0),
1831 });
1832 }
1833 }
1834
1835 fn sync_parts_library(
1836 &mut self,
1837 revision: u64,
1838 fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
1839 ) {
1840 if self.sent_library_revision == Some(revision) {
1841 return;
1842 }
1843 if let Some(tx) = &self.tx {
1844 let _ = tx.send(Command::SetPartsLibrary {
1845 library: fetch(),
1846 revision,
1847 });
1848 self.sent_library_revision = Some(revision);
1849 }
1850 }
1851
1852 fn poll_library_request(&mut self) -> bool {
1853 self.drain();
1854 std::mem::take(&mut self.library_requested)
1855 }
1856
1857 fn poll_run(&mut self) -> Option<RunReply> {
1858 self.drain();
1859 self.run_buf.pop_front()
1860 }
1861
1862 fn run_reply_waiting(&mut self) -> bool {
1863 self.drain();
1864 !self.run_buf.is_empty() || self.library_requested
1865 }
1866
1867 fn submit_query(&mut self, query: MeasureQuery) {
1868 if let Some(tx) = &self.tx {
1869 let _ = tx.send(Command::Query(query));
1870 }
1871 }
1872
1873 fn poll_query(&mut self) -> Option<MeasureReply> {
1874 self.drain();
1875 self.query_buf.pop_front()
1876 }
1877
1878 fn submit_mesh_import(&mut self, request: MeshImportRequest) {
1879 if let Some(tx) = &self.tx {
1880 let _ = tx.send(Command::MeshImport(request));
1881 }
1882 }
1883
1884 fn poll_mesh_import(&mut self) -> Option<MeshImportReply> {
1885 self.drain();
1886 self.mesh_import_buf.pop_front()
1887 }
1888
1889 fn submit_step_probe(&mut self, request: StepProbeRequest) {
1890 if let Some(tx) = &self.tx {
1891 let _ = tx.send(Command::StepProbe(request));
1892 }
1893 }
1894
1895 fn poll_step_probe(&mut self) -> Option<StepProbeReply> {
1896 self.drain();
1897 self.step_probe_buf.pop_front()
1898 }
1899
1900 fn submit_topology(&mut self, request: TopologyRequest) {
1901 if let Some(tx) = &self.tx {
1902 let _ = tx.send(Command::Topology(request));
1903 }
1904 }
1905
1906 fn poll_topology(&mut self) -> Option<TopologyReply> {
1907 self.drain();
1908 self.topology_buf.pop_front()
1909 }
1910
1911 fn wait_topology(&mut self, timeout: std::time::Duration) -> bool {
1912 self.drain();
1913 let deadline = std::time::Instant::now() + timeout;
1914 while self.topology_buf.is_empty() {
1915 let now = std::time::Instant::now();
1916 if now >= deadline {
1917 return false;
1918 }
1919 match self.rx.recv_timeout(deadline - now) {
1920 Ok(reply) => {
1921 self.file(reply);
1922 }
1923 Err(_) => return false,
1924 }
1925 }
1926 true
1927 }
1928
1929 fn submit_sheet_lines(&mut self, request: SheetLinesRequest) -> bool {
1930 match &self.tx {
1931 Some(tx) => tx.send(Command::SheetLines(request)).is_ok(),
1932 None => false,
1933 }
1934 }
1935
1936 fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
1937 self.drain();
1938 self.sheet_lines_buf.pop_front()
1939 }
1940
1941 fn submit_cam(&mut self, job: CamJob) -> bool {
1942 self.cam_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
1943 match &self.tx {
1944 Some(tx) => tx.send(Command::CamGenerate(job)).is_ok(),
1945 None => false,
1946 }
1947 }
1948
1949 fn poll_cam(&mut self) -> Option<CamReply> {
1950 self.drain();
1951 if self.cam_hold {
1952 return None;
1953 }
1954 self.cam_buf.pop_front()
1955 }
1956
1957 fn hold_cam_replies(&mut self, hold: bool) {
1958 self.cam_hold = hold;
1959 }
1960
1961 fn poll_cam_progress(&mut self) -> Option<CamProgress> {
1962 self.drain();
1963 let latest = self.cam_progress_buf.pop_back();
1964 self.cam_progress_buf.clear();
1965 latest
1966 }
1967
1968 fn cancel_cam(&mut self) {
1969 self.cam_cancel.store(true, std::sync::atomic::Ordering::SeqCst);
1970 }
1971
1972 fn submit_cam_sim(&mut self, job: CamSimJob) -> bool {
1973 self.cam_sim_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
1975 match &self.tx {
1976 Some(tx) => tx.send(Command::CamSimulate(job)).is_ok(),
1977 None => false,
1978 }
1979 }
1980
1981 fn poll_cam_sim(&mut self) -> Option<CamSimReply> {
1982 self.drain();
1983 self.cam_sim_buf.pop_front()
1984 }
1985
1986 fn poll_cam_sim_progress(&mut self) -> Option<CamSimProgress> {
1987 self.drain();
1988 let latest = self.cam_sim_progress_buf.pop_back();
1989 self.cam_sim_progress_buf.clear();
1990 latest
1991 }
1992
1993 fn cancel_cam_sim(&mut self) {
1994 self.cam_sim_cancel.store(true, std::sync::atomic::Ordering::SeqCst);
1995 }
1996
1997 fn poll_progress(&mut self) -> Option<RunProgress> {
1998 self.drain();
1999 let latest = self.progress_buf.pop_back();
2000 self.progress_buf.clear();
2001 latest
2002 }
2003
2004 fn cancel(&mut self) -> bool {
2010 self.stop.store(true, std::sync::atomic::Ordering::Relaxed);
2011 self.tx.take();
2012 self.handle.take();
2013 *self = Self::new();
2014 true
2015 }
2016
2017 fn reset(&mut self) {
2018 self.sent_plugin_revision = None;
2019 self.plugin_action_buf.clear();
2020 if let Some(tx) = &self.tx {
2021 let _ = tx.send(Command::Reset);
2022 }
2023 self.run_buf.clear();
2026 self.query_buf.clear();
2027 self.mesh_import_buf.clear();
2028 self.step_probe_buf.clear();
2029 self.topology_buf.clear();
2030 self.sheet_lines_buf.clear();
2031 self.progress_buf.clear();
2032 self.sent_library_revision = None;
2035 self.library_requested = false;
2036 }
2037}
2038
2039#[cfg(not(target_arch = "wasm32"))]
2040impl Drop for ThreadRunner {
2041 fn drop(&mut self) {
2042 self.tx.take();
2046 self.handle.take();
2047 }
2048}
2049