#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RunReply {
pub generation: u64,
pub output: crate::pipeline::RunOutput,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RunProgress {
pub generation: u64,
pub index: usize,
pub total: usize,
pub feature_id: String,
pub feature_type: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct StepProbeRequest {
pub id: u64,
pub text: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct StepProbeReply {
pub id: u64,
pub result: Result<Option<brep_kernel::StepAssembly>, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum MeasureKind {
Solid,
Face,
Edge,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeasureQuery {
pub id: u64,
pub kind: MeasureKind,
pub owner: String,
pub entity: String,
pub density: f64,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeasureReply {
pub id: u64,
pub result: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TopologyRequest {
pub id: u64,
pub solids: Vec<(String, u32)>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TopologyReply {
pub id: u64,
pub solids: Vec<(String, u32, brep_kernel::BrepSolid)>,
}
fn topology_reply(request: TopologyRequest) -> TopologyReply {
let solids = request
.solids
.into_iter()
.filter_map(|(name, handle)| {
brep_kernel::registered_solid_clone(handle)
.ok()
.map(|solid| (name, handle, solid))
})
.collect();
TopologyReply { id: request.id, solids }
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SheetLinesRequest {
pub id: u64,
pub job: crate::sheets::project::LinesJob,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SheetLinesReply {
pub id: u64,
pub key: String,
pub lines: Result<crate::sheets::project::ExactLines, String>,
}
fn sheet_lines_reply(request: SheetLinesRequest) -> SheetLinesReply {
SheetLinesReply {
id: request.id,
lines: crate::sheets::project::run_lines_job(&request.job),
key: request.job.key,
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CamJob {
pub id: u64,
pub operation: brep_cam::model::Operation,
pub setup: brep_cam::model::Setup,
pub tools: Vec<brep_cam::model::Tool>,
pub solids: Vec<(String, u32)>,
pub chord_tolerance: f64,
pub budget_chunk: u64,
#[serde(default)]
pub chain_curves: Vec<(String, String, brep_kernel::NurbsCurve)>,
#[serde(default)]
pub chain_profiles: Vec<(String, brep_kernel::SketchProfile)>,
#[serde(default)]
pub machine: Option<brep_cam::model::Machine>,
#[serde(default)]
pub autoprogram: Option<brep_cam::autoprogram::binding::Binding>,
#[serde(default)]
pub earlier: Vec<(brep_cam::model::Operation, Option<brep_cam::model::Toolpath>)>,
#[serde(default)]
pub previous_setups: Vec<brep_cam::SetupHistory>,
#[serde(default)]
pub source: Option<brep_cam::program::OperationInput>,
#[serde(default)]
pub source_operation: Option<brep_cam::model::Operation>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CamReply {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub turning_profile: Option<brep_cam::turning::profile::TurningProfile>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub setup_start: Option<brep_cam::SetupStart>,
pub id: u64,
pub operation: String,
pub toolpath: Result<brep_cam::model::Toolpath, String>,
pub work_units: u64,
pub cancelled: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CamProgress {
pub id: u64,
pub operation: String,
pub phase: String,
pub message: String,
pub current: u64,
pub total: u64,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum CamSimRequest {
Load {
timeline: brep_cam::multiaxis::sim::Timeline,
stock: brep_cam::multiaxis::sim::StockModel,
ranges: Vec<brep_cam::multiaxis::sim::RangeCutter>,
options: brep_cam::multiaxis::sim::DriverOptions,
#[serde(default)]
machine: Option<brep_cam::model::Machine>,
},
Advance { target: usize, budget: usize, stock: bool, known_version: u64 },
Drop,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CamSimJob {
pub session: u64,
pub seq: u64,
pub request: CamSimRequest,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CamSimReply {
pub session: u64,
pub seq: u64,
pub applied: usize,
pub version: u64,
pub removed_volume: f64,
pub stock_volume: f64,
pub unsupported: Vec<usize>,
pub warnings: Vec<(usize, brep_cam::model::Diagnostic)>,
pub stock: Option<brep_cam::multiaxis::sim::StockModel>,
pub reached: bool,
pub cancelled: bool,
pub error: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CamSimProgress {
pub session: u64,
pub seq: u64,
pub applied: usize,
pub target: usize,
}
struct CamSimSession {
session: u64,
timeline: brep_cam::multiaxis::sim::Timeline,
ranges: Vec<brep_cam::multiaxis::sim::RangeCutter>,
driver: brep_cam::multiaxis::sim::SimDriver,
}
thread_local! {
static CAM_SIM: std::cell::RefCell<Option<CamSimSession>> = const { std::cell::RefCell::new(None) };
}
fn cam_sim_clear() {
CAM_SIM.with(|c| *c.borrow_mut() = None);
}
thread_local! {
static PRE_OP_STOCK: std::cell::RefCell<brep_cam::PreOpStockCache> = std::cell::RefCell::new(brep_cam::PreOpStockCache::new(16));
}
fn pre_op_stock_clear() {
PRE_OP_STOCK.with(|c| c.borrow_mut().clear());
}
fn cam_sim_reply(job: CamSimJob, post: &mut dyn FnMut(Reply) -> bool) -> CamSimReply {
let (session, seq) = (job.session, job.seq);
let empty = |error: Option<String>| CamSimReply {
session,
seq,
applied: 0,
version: 0,
removed_volume: 0.0,
stock_volume: 0.0,
unsupported: Vec::new(),
warnings: Vec::new(),
stock: None,
reached: false,
cancelled: false,
error,
};
match job.request {
CamSimRequest::Drop => {
cam_sim_clear();
empty(None)
}
CamSimRequest::Load { timeline, stock, ranges, options, machine } => match brep_cam::multiaxis::sim::SimDriver::new(stock, options) {
Ok(driver) => {
let driver = driver.with_chain(brep_cam::multiaxis::sim::sim_chain(machine.as_ref()));
let reply = CamSimReply {
applied: driver.applied,
version: driver.version,
stock_volume: driver.stock.volume(),
reached: true,
..empty(None)
};
CAM_SIM.with(|c| *c.borrow_mut() = Some(CamSimSession { session, timeline, ranges, driver }));
reply
}
Err(e) => empty(Some(e.to_string())),
},
CamSimRequest::Advance { target, budget, stock, known_version } => CAM_SIM.with(|c| {
let mut guard = c.borrow_mut();
let Some(sim) = guard.as_mut().filter(|s| s.session == session) else {
return empty(Some("no simulation session is loaded on the runner".into()));
};
let mut cancelled = false;
let tolerance = brep_cam::Tolerance::default();
let outcome = sim.driver.advance_ranges(&sim.timeline, &sim.ranges, target, budget, &tolerance, &mut |applied| {
let keep = post(Reply::CamSimProgress(CamSimProgress { session, seq, applied, target }));
if !keep {
cancelled = true;
}
keep
});
let d = &sim.driver;
let (reached, error) = match outcome {
Ok(o) => (o == brep_cam::multiaxis::sim::AdvanceOutcome::Reached, None),
Err(e) => (false, Some(e.to_string())),
};
CamSimReply {
session,
seq,
applied: d.applied,
version: d.version,
removed_volume: d.removed_volume,
stock_volume: d.stock.volume(),
unsupported: d.unsupported.clone(),
warnings: d.warnings.clone(),
stock: (stock && d.version != known_version).then(|| d.stock.clone()),
reached,
cancelled,
error,
}
}),
}
}
struct CamSink<'a> {
id: u64,
operation: String,
token: brep_cam::CancelToken,
post: &'a mut dyn FnMut(Reply) -> bool,
}
impl brep_cam::ProgressSink for CamSink<'_> {
fn progress(&mut self, progress: &brep_cam::Progress) {
if std::env::var_os("BREP_CAM_DEBUG").is_some() {
eprintln!("cam job {} progress: {} {}/{}", self.operation, progress.phase, progress.current, progress.total);
}
let keep_going = (self.post)(Reply::CamProgress(CamProgress {
id: self.id,
operation: self.operation.clone(),
phase: progress.phase.clone(),
message: progress.message.clone(),
current: progress.current,
total: progress.total,
}));
if !keep_going {
if std::env::var_os("BREP_CAM_DEBUG").is_some() {
eprintln!("cam job {}: the driver said stop; cancelling", self.operation);
}
self.token.cancel();
}
}
}
pub const CAM_TRIANGLE_BUDGET: usize = 4_000_000;
fn cam_generate_reply(job: CamJob, post: &mut dyn FnMut(Reply) -> bool) -> CamReply {
use brep_cam::kernel_adapter::{machine_snapshot, revolved_faces, BodyInput};
use brep_cam::{Runner, Tolerance, TreeIndex, TreeOptions, WorkBudget};
let operation = job.operation.id.as_str().to_string();
let token = brep_cam::CancelToken::new();
let mut sink = CamSink { id: job.id, operation: operation.clone(), token: token.clone(), post };
let mut certified_turning_profile = None;
let mut setup_start = None;
let mut outcome = || -> Result<(brep_cam::model::Toolpath, u64), String> {
let mut bodies: Vec<(String, brep_kernel::BrepSolid)> = Vec::new();
for (name, handle) in &job.solids {
let solid = brep_kernel::registered_solid_clone(*handle)
.map_err(|e| format!("body `{name}` is not resident on the runner ({e}); run the history again"))?;
bodies.push((name.clone(), solid));
}
let diagonal = bodies
.iter()
.filter_map(|(_, s)| brep_cam::kernel_adapter::body_bounds(s, job.chord_tolerance).ok())
.fold(brep_cam::model::Bounds3::empty(), |acc, b| acc.union(&b))
.diagonal();
let tolerance = if diagonal.is_finite() && diagonal > 0.0 { Tolerance::scaled_for_diagonal(diagonal) } else { Tolerance::default() };
let mut snapshot_setup = job.setup.clone();
for name in &job.operation.targets.target_solids {
if !snapshot_setup.targets.iter().any(|t| &t.solid == name) {
snapshot_setup.targets.push(brep_cam::model::TargetRef { solid: name.clone(), face: None });
}
}
for name in &job.operation.targets.protected_solids {
let listed = snapshot_setup.targets.iter().chain(snapshot_setup.protected.iter()).any(|t| &t.solid == name);
if !listed {
snapshot_setup.protected.push(brep_cam::model::TargetRef { solid: name.clone(), face: None });
}
}
let (mesh, unresolved) = machine_snapshot(&snapshot_setup, job.chord_tolerance, tolerance, |name| bodies.iter().find(|(n, _)| n == name).map(|(_, s)| s))
.map_err(|e| format!("{e}"))?;
if !unresolved.is_empty() {
return Err(format!("the setup names bodies the model does not have: {}", unresolved.join(", ")));
}
if std::env::var_os("BREP_CAM_DEBUG").is_some() {
eprintln!("cam job {}: {} bodies, snapshot {} faces / {} triangles, bounds {:?}", operation, bodies.len(), mesh.faces.len(), mesh.triangles.len(), mesh.bounds);
}
if mesh.triangles.len() > CAM_TRIANGLE_BUDGET {
return Err(format!(
"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",
mesh.triangles.len(),
job.chord_tolerance
));
}
let index = TreeIndex::build(&mesh, TreeOptions::default());
let mut runner = Runner::new(token.clone(), &mut sink, WorkBudget::new(job.budget_chunk.max(1))).with_operation(operation.clone());
let holes = if matches!(job.operation.strategy, brep_cam::model::Strategy::Drill(_)) {
let mut carriers = Vec::new();
for (name, solid) in &bodies {
if job.setup.targets.iter().any(|t| t.solid == *name) || job.operation.targets.target_solids.contains(name) {
let body = BodyInput { name, solid, protected_only: false };
carriers.extend(revolved_faces(&body, &job.setup));
}
}
brep_cam::holes::recognize(&carriers, &mesh, tolerance, &mut runner).map_err(|e| e.to_string())?.holes
} else {
Vec::new()
};
let mut effective_operation = job.operation.clone();
let mut automatic_geometry = None;
let mut rebound = false;
if let Some(binding) = &job.autoprogram {
let tool = job.tools.iter().find(|t| t.id == job.operation.tool).ok_or("automatic programming tool is missing")?;
binding.validate_inputs(&job.operation, &job.setup, tool)?;
let fingerprints: std::collections::BTreeMap<_, _> = bodies.iter().map(|(name, solid)| (name.clone(), crate::scene::brep_fingerprint(solid))).collect();
if fingerprints == binding.body_fingerprints {
automatic_geometry = Some(binding.geometry.clone());
} else {
let recognized = brep_cam::autoprogram::binding::recognize_bodies(&bodies, &job.setup, &mesh, job.chord_tolerance, tolerance, &mut runner).map_err(|e| e.to_string())?;
let model_bounds = bodies.iter().filter(|(name, _)| job.setup.targets.iter().any(|t| &t.solid == name))
.filter_map(|(_, s)| brep_cam::kernel_adapter::body_bounds(s, job.chord_tolerance).ok())
.fold(brep_cam::model::Bounds3::empty(), |a, b| a.union(&b));
let stock_bounds = match &job.setup.stock {
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()),
_ => job.setup.stock.model_bounds(&model_bounds),
};
let stock_top = stock_bounds.map(|b| job.setup.model_to_machine.apply_bounds(&b).max.z);
let (op, geometry) = binding.rebind_with_mesh(&recognized, &mesh, &job.operation, &job.setup, &job.tools, job.machine.as_ref(), stock_top, tolerance)?;
effective_operation = op;
automatic_geometry = Some(geometry);
rebound = true;
}
}
let resolved_planar = match &automatic_geometry { Some(brep_cam::autoprogram::OperationGeometry::Planar(p)) => Some((&effective_operation.id, p)), _ => None };
let holes = match &automatic_geometry { Some(brep_cam::autoprogram::OperationGeometry::Holes(h)) => h.as_slice(), _ => holes.as_slice() };
let mut pre_op_warning = None;
let live_contour = matches!(job.operation.strategy, brep_cam::Strategy::Contour2D(_) | brep_cam::Strategy::Trace(_))
&& job.operation.live_tool.unwrap_or(true)
&& job.machine.as_ref().is_some_and(|m| matches!(m.kind, brep_cam::MachineKind::MillTurn { .. }));
let pre_op = if !live_contour && brep_cam::uses_pre_op_stock(&job.operation.strategy, job.machine.as_ref()) {
let fallback = job.operation.targets.target_solids.clone();
let request = brep_cam::JobStock {
setup: &job.setup,
machine: job.machine.as_ref(),
tools: &job.tools,
mesh: &mesh,
fallback_targets: &fallback,
earlier: &job.earlier,
previous: &job.previous_setups,
tolerance,
};
match PRE_OP_STOCK.with(|c| brep_cam::job_stock(&mut c.borrow_mut(), &request, &mut runner)) {
Ok(result) => Some(result),
Err(brep_cam::PreOpStockError::Cancelled) => return Err("cancelled".to_string()),
Err(error) => {
pre_op_warning = Some(brep_cam::unavailable_warning(&error));
None
}
}
} else {
None
};
let input = brep_cam::dispatch::GenerateInput {
operation: &effective_operation,
setup: &job.setup,
tools: &job.tools,
mesh: &mesh,
index: Some(&index),
tolerance,
holes,
};
let solid_refs: Vec<(&str, &brep_kernel::BrepSolid)> = bodies.iter().map(|(n, s)| (n.as_str(), s)).collect();
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();
let profile_refs: Vec<(&str, &brep_kernel::SketchProfile)> = job.chain_profiles.iter().map(|(s, p)| (s.as_str(), p)).collect();
let chain_geometry = brep_cam::dispatch::chains::KernelChainGeometry { solids: &solid_refs, curves: &curve_refs, profiles: &profile_refs };
let is_turning = matches!(job.operation.strategy, brep_cam::Strategy::TurnFace(_) | brep_cam::Strategy::TurnProfile(_));
let mut turning_carriers = Vec::new();
let turning_tool = if is_turning {
let tool = job.tools.iter().find(|t| t.id == job.operation.tool).ok_or("turning tool is missing")?;
if tool.turning_geometry.is_none() { return Err(format!("tool {} has no insert geometry", tool.id)); }
if job.setup.turning_stick_out.is_none() { return Err("setup has no stick-out".into()); }
let targets: std::collections::BTreeSet<_> = job.setup.targets.iter().map(|t| t.solid.as_str()).collect();
if targets.len() != 1 || job.operation.targets.target_solids.iter().any(|n| !targets.contains(n.as_str())) {
return Err("turning requires one exact setup solid and matching operation targets".into());
}
let name = *targets.iter().next().unwrap();
let solid = bodies.iter().find(|(n, _)| n == name).map(|(_, s)| s).ok_or("turning setup solid is missing")?;
if !job.machine.as_ref().is_some_and(|m| matches!(m.kind, brep_cam::MachineKind::Lathe | brep_cam::MachineKind::MillTurn { .. })) {
return Err("turning envelope requires a defined lathe spindle axis".into());
}
certified_turning_profile = Some(match brep_cam::kernel_adapter::turning_faces(&BodyInput { name, solid, protected_only: false }, &job.setup, tolerance) {
Ok(carriers) => {
turning_carriers = carriers;
brep_cam::turning::profile::extract(&turning_carriers, &mesh, job.operation.tolerance * 0.125, &mut runner)
}
Err(_) => brep_cam::turning::envelope::extract(&mesh, job.chord_tolerance, &mut runner),
}.map_err(|e| e.to_string())?);
Some(tool)
} else { None };
let turning = turning_tool.map(|tool| brep_cam::dispatch::TurningInput {
tool_id: &tool.id, tool_geometry: tool.turning_geometry.as_ref().unwrap(),
carriers: &turning_carriers, profile: certified_turning_profile.as_ref(), contour: None,
stick_out: job.setup.turning_stick_out.unwrap(), groove_center_z: None, thread_radius: None,
});
let context = brep_cam::dispatch::GenerateContext {
turning: turning.as_ref(),
chain_geometry: Some(&chain_geometry),
machine: job.machine.as_ref(),
resolved_planar,
rest_stock: pre_op.as_ref().map(|r| &r.stock.grid),
pre_op_stock: pre_op.as_ref().map(|r| &r.stock.envelope),
source: job.source.as_ref(),
source_operation: job.source_operation.as_ref(),
..Default::default()
};
let mut path = brep_cam::dispatch::generate_with_context(&input, &context, &mut runner).map_err(|e| e.to_string())?;
if rebound { path.report.warn(brep_cam::model::Diagnostic::new("autoprogram-re-recognised", "re-recognised after model change")); }
if let Some(warning) = pre_op_warning {
path.report.warn(warning);
path.report.warnings.rotate_right(1);
}
if let Some(result) = pre_op {
path.report.approximations.extend(result.limits);
setup_start = Some(result.start);
}
Ok((path, runner.budget().used()))
};
match outcome() {
Ok((toolpath, work_units)) => CamReply { turning_profile: certified_turning_profile, setup_start, id: job.id, operation, toolpath: Ok(toolpath), work_units, cancelled: false },
Err(message) => {
let cancelled = token.is_cancelled();
CamReply { turning_profile: None, setup_start: None, id: job.id, operation, toolpath: Err(if cancelled { "cancelled".to_string() } else { message }), work_units: 0, cancelled }
}
}
}
pub use brep_reconstruction::stl_conversion::{
ConversionPolicy, StlConversionOptions, StlConversionOutput,
};
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub enum MeshImportFormat {
Stl,
Obj,
ThreeMf,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct MeshImportRequest {
pub id: u64,
pub format: MeshImportFormat,
pub bytes: Vec<u8>,
pub options: StlConversionOptions,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct MeshImportReply {
pub id: u64,
pub result: Result<StlConversionOutput, String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PluginInstallRequest {
pub id: u64,
pub packages: serde_json::Value,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PluginInstallReply {
pub id: u64,
pub result: Result<brep_plugins::Registry, String>,
}
fn validate_plugins(request: PluginInstallRequest) -> PluginInstallReply {
let result = serde_json::from_value(request.packages)
.map_err(|e| format!("plugin packages: {e}"))
.and_then(brep_plugins::Runtime::new)
.map(|runtime| runtime.registry().clone());
PluginInstallReply { id: request.id, result }
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PluginActionRequest {
pub id: u64,
pub pins: Vec<brep_plugins::PluginPin>,
pub action: String,
pub input: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub script: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PluginActionReply {
pub id: u64,
pub result: Result<brep_plugins::ActionPlan, String>,
}
pub enum SessionOnMain {
Resident,
Copied(brep_kernel::SessionTransfer),
Unavailable,
}
pub trait HistoryRunner {
fn session_for_main(&mut self, _generation: u64) -> SessionOnMain {
SessionOnMain::Unavailable
}
fn submit_plugin_install(&mut self, _request: PluginInstallRequest) -> bool { false }
fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> { None }
fn sync_plugins(&mut self, _revision: u64, _fetch: &mut dyn FnMut() -> serde_json::Value) {}
fn submit_plugin_action(&mut self, _request: PluginActionRequest) -> bool { false }
fn poll_plugin_action(&mut self) -> Option<PluginActionReply> { None }
fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64);
fn poll_run(&mut self) -> Option<RunReply>;
fn submit_query(&mut self, query: MeasureQuery);
fn poll_query(&mut self) -> Option<MeasureReply>;
fn submit_mesh_import(&mut self, request: MeshImportRequest);
fn poll_mesh_import(&mut self) -> Option<MeshImportReply>;
fn submit_step_probe(&mut self, request: StepProbeRequest);
fn poll_step_probe(&mut self) -> Option<StepProbeReply>;
fn submit_topology(&mut self, _request: TopologyRequest) {}
fn poll_topology(&mut self) -> Option<TopologyReply> {
None
}
fn wait_topology(&mut self, _timeout: std::time::Duration) -> bool {
false
}
fn submit_sheet_lines(&mut self, _request: SheetLinesRequest) -> bool {
false
}
fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
None
}
fn submit_cam(&mut self, _job: CamJob) -> bool {
false
}
fn poll_cam(&mut self) -> Option<CamReply> {
None
}
fn poll_cam_progress(&mut self) -> Option<CamProgress> {
None
}
fn cancel_cam(&mut self) {}
fn hold_cam_replies(&mut self, _hold: bool) {}
fn submit_cam_sim(&mut self, _job: CamSimJob) -> bool {
false
}
fn poll_cam_sim(&mut self) -> Option<CamSimReply> {
None
}
fn poll_cam_sim_progress(&mut self) -> Option<CamSimProgress> {
None
}
fn cancel_cam_sim(&mut self) {}
fn reset(&mut self);
fn sync_parts_library(
&mut self,
_revision: u64,
_fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
) {
}
fn poll_library_request(&mut self) -> bool {
false
}
fn run_reply_waiting(&mut self) -> bool {
true
}
fn poll_progress(&mut self) -> Option<RunProgress> {
None
}
fn cancel(&mut self) -> bool {
false
}
}
fn measure_json(runner: &crate::pipeline::SceneRunner, query: &MeasureQuery) -> String {
let Some(handle) = runner.handle_of(&query.owner) else {
return serde_json::json!({
"ok": false,
"message": format!("solid '{}' has no resident geometry", query.owner),
})
.to_string();
};
match query.kind {
MeasureKind::Solid => {
match brep_kernel::mass_properties_handle_native(handle, query.density) {
Ok(properties) => {
let edge_total =
brep_kernel::solid_edge_length_total_native(handle).unwrap_or(0.0);
serde_json::json!({
"ok": true,
"kind": "solid",
"volume": properties.volume,
"surfaceArea": properties.surface_area,
"edgeLengthTotal": edge_total,
"density": properties.density,
"weight": properties.mass,
})
.to_string()
}
Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
}
}
MeasureKind::Face => match brep_kernel::face_measurements_native(handle, &query.entity) {
Ok((area, edge_total, surface_type)) => {
let dimensions =
brep_kernel::face_carrier_dimensions_native(handle, &query.entity)
.ok()
.flatten();
let surface_type = dimensions.as_ref().map_or(surface_type, |d| d.label);
let mut fragment = serde_json::json!({
"ok": true,
"kind": "face",
"solid": query.owner,
"surfaceType": surface_type,
"area": area,
"edgeLengthTotal": edge_total,
});
insert_dimensions(&mut fragment, dimensions.as_ref());
fragment.to_string()
}
Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
},
MeasureKind::Edge => match brep_kernel::edge_length_native(handle, &query.entity) {
Ok(length) => {
let dimensions =
brep_kernel::edge_carrier_dimensions_native(handle, &query.entity)
.ok()
.flatten();
let mut fragment = serde_json::json!({
"ok": true,
"kind": "edge",
"solid": query.owner,
"length": length,
});
if let Some(dimensions) = &dimensions {
fragment["curveType"] = serde_json::Value::from(dimensions.label);
}
insert_dimensions(&mut fragment, dimensions.as_ref());
fragment.to_string()
}
Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
},
}
}
fn insert_dimensions(
fragment: &mut serde_json::Value,
dimensions: Option<&brep_kernel::CarrierDimensions>,
) {
let (Some(dimensions), Some(object)) = (dimensions, fragment.as_object_mut()) else {
return;
};
for (key, value) in &dimensions.values {
object.insert((*key).to_string(), serde_json::Value::from(*value));
}
}
pub struct InlineRunner {
runner: crate::pipeline::SceneRunner,
pending: std::collections::VecDeque<RunReply>,
query_pending: std::collections::VecDeque<MeasureReply>,
mesh_import_pending: std::collections::VecDeque<MeshImportReply>,
step_probe_pending: std::collections::VecDeque<StepProbeReply>,
topology_pending: std::collections::VecDeque<TopologyReply>,
sheet_lines_pending: std::collections::VecDeque<SheetLinesReply>,
plugin_action_pending: std::collections::VecDeque<PluginActionReply>,
plugin_install_pending: std::collections::VecDeque<PluginInstallReply>,
}
impl InlineRunner {
pub fn new() -> Self {
Self {
runner: crate::pipeline::SceneRunner::new(),
pending: std::collections::VecDeque::new(),
query_pending: std::collections::VecDeque::new(),
mesh_import_pending: std::collections::VecDeque::new(),
step_probe_pending: std::collections::VecDeque::new(),
topology_pending: std::collections::VecDeque::new(),
sheet_lines_pending: std::collections::VecDeque::new(),
plugin_action_pending: std::collections::VecDeque::new(),
plugin_install_pending: std::collections::VecDeque::new(),
}
}
}
impl HistoryRunner for InlineRunner {
fn submit_plugin_install(&mut self, request: PluginInstallRequest) -> bool {
self.plugin_install_pending.push_back(validate_plugins(request));
true
}
fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> {
self.plugin_install_pending.pop_front()
}
fn sync_plugins(&mut self, revision: u64, fetch: &mut dyn FnMut() -> serde_json::Value) {
if self.runner.plugin_revision != Some(revision) {
self.runner.set_plugins(revision, fetch());
}
}
fn submit_plugin_action(&mut self, request: PluginActionRequest) -> bool {
self.plugin_action_pending.push_back(self.runner.plugin_action(request));
true
}
fn poll_plugin_action(&mut self) -> Option<PluginActionReply> {
self.plugin_action_pending.pop_front()
}
fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64) {
let serial = brep_kernel::assembly_session_serial();
let output = self.runner.run(&request);
let after = brep_kernel::assembly_session_serial();
self.runner.session_of = (after != serial).then_some((generation, after));
self.pending.push_back(RunReply { generation, output });
}
fn session_for_main(&mut self, generation: u64) -> SessionOnMain {
match self.runner.session_of {
Some((ran, serial)) if ran == generation && serial == brep_kernel::assembly_session_serial() => {
SessionOnMain::Resident
}
_ => SessionOnMain::Unavailable,
}
}
fn sync_parts_library(
&mut self,
_revision: u64,
_fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
) {
}
fn poll_run(&mut self) -> Option<RunReply> {
self.pending.pop_front()
}
fn submit_query(&mut self, query: MeasureQuery) {
let result = measure_json(&self.runner, &query);
self.query_pending.push_back(MeasureReply { id: query.id, result });
}
fn poll_query(&mut self) -> Option<MeasureReply> {
self.query_pending.pop_front()
}
fn submit_mesh_import(&mut self, request: MeshImportRequest) {
self.mesh_import_pending
.push_back(reconstruct_mesh(request));
}
fn poll_mesh_import(&mut self) -> Option<MeshImportReply> {
self.mesh_import_pending.pop_front()
}
fn submit_step_probe(&mut self, request: StepProbeRequest) {
self.step_probe_pending.push_back(probe_step(request));
}
fn poll_step_probe(&mut self) -> Option<StepProbeReply> {
self.step_probe_pending.pop_front()
}
fn submit_topology(&mut self, request: TopologyRequest) {
self.topology_pending.push_back(topology_reply(request));
}
fn poll_topology(&mut self) -> Option<TopologyReply> {
self.topology_pending.pop_front()
}
fn submit_sheet_lines(&mut self, request: SheetLinesRequest) -> bool {
self.sheet_lines_pending.push_back(sheet_lines_reply(request));
true
}
fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
self.sheet_lines_pending.pop_front()
}
fn reset(&mut self) {
self.runner.reset();
}
}
impl Default for InlineRunner {
fn default() -> Self {
Self::new()
}
}
#[derive(serde::Serialize, serde::Deserialize)]
pub enum Command {
ValidatePlugins(PluginInstallRequest),
SetPlugins { revision: u64, packages: serde_json::Value },
PluginAction(PluginActionRequest),
Run {
request: brep_kernel::HistoryRequest,
generation: u64,
#[serde(default)]
parts_library_revision: u64,
},
SetPartsLibrary {
library: brep_kernel::PartsLibraryMap,
revision: u64,
},
Query(MeasureQuery),
MeshImport(MeshImportRequest),
StepProbe(StepProbeRequest),
Topology(TopologyRequest),
SheetLines(SheetLinesRequest),
CamGenerate(CamJob),
CamSimulate(CamSimJob),
Reset,
#[serde(skip)]
ExportSession { generation: u64 },
}
#[derive(serde::Serialize, serde::Deserialize)]
pub enum Reply {
ValidatedPlugins(PluginInstallReply),
PluginAction(PluginActionReply),
Run(RunReply),
Query(MeasureReply),
MeshImport(MeshImportReply),
StepProbe(StepProbeReply),
Topology(TopologyReply),
SheetLines(SheetLinesReply),
CamGenerate(CamReply),
CamProgress(CamProgress),
CamSimulate(CamSimReply),
CamSimProgress(CamSimProgress),
NeedPartsLibrary,
Progress(RunProgress),
#[serde(skip)]
SessionExport { generation: u64, transfer: Option<brep_kernel::SessionTransfer> },
}
fn probe_step(request: StepProbeRequest) -> StepProbeReply {
StepProbeReply {
id: request.id,
result: brep_kernel::read_step_assembly(&request.text),
}
}
fn reconstruct_mesh(request: MeshImportRequest) -> MeshImportReply {
let result = (|| {
use brep_reconstruction::stl_conversion::{
binary_stl_coordinate_precision_tolerance, convert_stl_mesh_to_step_with_colors,
};
use brep_reconstruction::{Mesh, Vec3};
let mut instances = Vec::new();
let mut reader_messages = Vec::new();
let mut triangle_colors: Vec<Option<[u8; 3]>> = Vec::new();
let (mesh, positions, indices, coordinate_precision_tolerance) = match request.format {
MeshImportFormat::Stl => {
use brep_reconstruction::stl::{parse_stl_bytes, StlFormat, StlReadOptions};
let read_options = StlReadOptions {
weld_tolerance: (request.options.weld_tolerance >= 0.0)
.then_some(request.options.weld_tolerance),
};
let imported = parse_stl_bytes(&request.bytes, &read_options)
.map_err(|error| format!("STL import failed: {error}"))?;
let positions = imported
.mesh
.vertices
.iter()
.flat_map(|point| [point.x, point.y, point.z])
.collect::<Vec<_>>();
let indices = imported
.mesh
.triangles
.iter()
.flatten()
.copied()
.collect::<Vec<_>>();
let precision = if imported.format == StlFormat::Binary {
binary_stl_coordinate_precision_tolerance(&imported.mesh)
} else {
0.0
};
if !imported.dropped_facets.is_empty() {
reader_messages.push(format!(
"the STL reader dropped {} of {} facet(s) with no area: {}",
imported.dropped_facets.len(),
imported.source_triangle_count,
imported.dropped_facets.iter().take(8).map(ToString::to_string).collect::<Vec<_>>().join("; ")
));
}
(imported.mesh, positions, indices, precision)
}
MeshImportFormat::Obj => {
let text = std::str::from_utf8(&request.bytes)
.map_err(|_| "OBJ import failed: file is not UTF-8 text".to_string())?;
let obj = brep_kernel::read_obj(text)
.map_err(|error| format!("OBJ import failed: {error}"))?;
let vertices = obj
.positions
.chunks_exact(3)
.map(|point| Vec3::new(point[0], point[1], point[2]))
.collect::<Vec<_>>();
let triangles = obj
.indices
.chunks_exact(3)
.map(|triangle| [triangle[0], triangle[1], triangle[2]])
.collect::<Vec<_>>();
(
Mesh::new(vertices, triangles),
obj.positions,
obj.indices,
0.0,
)
}
MeshImportFormat::ThreeMf => {
let model = brep_kernel::read_3mf(&request.bytes)
.map_err(|error| format!("3MF import failed: {error}"))?;
instances = model.instances;
triangle_colors = model.triangle_colors;
let vertices = model
.positions
.chunks_exact(3)
.map(|point| Vec3::new(point[0], point[1], point[2]))
.collect::<Vec<_>>();
let triangles = model
.indices
.chunks_exact(3)
.map(|triangle| [triangle[0], triangle[1], triangle[2]])
.collect::<Vec<_>>();
(
Mesh::new(vertices, triangles),
model.positions,
model.indices,
0.0,
)
}
};
let mut options = request.options;
options.coordinate_precision_tolerance = options.coordinate_precision_tolerance
.max(coordinate_precision_tolerance);
convert_stl_mesh_to_step_with_colors(
&mesh,
&positions,
Some(&indices),
(!triangle_colors.is_empty()).then_some(triangle_colors.as_slice()),
&options,
"Imported mesh",
"MM",
"",
)
.map(|mut output| {
name_bodies(&mut output.report, &instances);
output.report.messages.splice(0..0, reader_messages);
output
})
.map_err(|error| format!("RANSAC reconstruction failed: {error}"))
})();
MeshImportReply {
id: request.id,
result,
}
}
fn name_bodies(
report: &mut brep_reconstruction::stl_conversion::StlConversionReport,
instances: &[brep_kernel::ThreeMfInstance],
) {
for component in &mut report.components {
component.source_name = instances
.iter()
.find(|instance| {
(instance.first_triangle..instance.first_triangle + instance.triangle_count)
.contains(&component.first_triangle)
})
.and_then(|instance| instance.name.clone());
}
}
pub(crate) fn refused_bodies_notice(
report: &brep_reconstruction::stl_conversion::StlConversionReport,
) -> Option<String> {
let refused = report
.components
.iter()
.filter_map(|component| {
component.refusal.as_ref().map(|reason| {
let body = match &component.source_name {
Some(name) => format!("body {} \"{name}\"", component.index + 1),
None => format!(
"body {} (from source triangle {})",
component.index + 1,
component.first_triangle
),
};
format!("{body}: {reason}")
})
})
.collect::<Vec<_>>();
(!refused.is_empty()).then(|| {
format!(
"mesh import: {} of {} bodies could not be reconstructed and are NOT in the model — {}",
refused.len(),
report.components.len(),
refused.join("; ")
)
})
}
pub fn process_command(
runner: &mut crate::pipeline::SceneRunner,
command: Command,
progress: &mut dyn FnMut(Reply) -> bool,
) -> Option<Reply> {
match command {
Command::ValidatePlugins(request) => Some(Reply::ValidatedPlugins(validate_plugins(request))),
Command::SetPlugins { revision, packages } => {
runner.set_plugins(revision, packages);
None
}
Command::PluginAction(request) => Some(Reply::PluginAction(runner.plugin_action(request))),
Command::Run {
request,
generation,
parts_library_revision,
} => {
let stale = match runner.parts_library_revision {
Some(installed) => installed != parts_library_revision,
None => parts_library_revision != 0,
};
let recoverable = brep_kernel::missing_library_parts(&request)
.iter()
.any(|name| runner.parts_library_names.contains(name));
if stale || recoverable {
runner.parts_library_revision = None;
return Some(Reply::NeedPartsLibrary);
}
let serial = brep_kernel::assembly_session_serial();
let output = runner.run_observed(&request, &mut |event| {
progress(Reply::Progress(RunProgress {
generation,
index: event.index,
total: event.total,
feature_id: event.id.to_string(),
feature_type: event.feature_type.to_string(),
}))
});
let after = brep_kernel::assembly_session_serial();
runner.session_of = (after != serial).then_some((generation, after));
Some(Reply::Run(RunReply { generation, output }))
}
Command::SetPartsLibrary { library, revision } => {
runner.parts_library_names = library.keys().cloned().collect();
brep_kernel::install_parts_library(&library);
runner.parts_library_revision = Some(revision);
None
}
Command::Query(query) => {
let result = measure_json(runner, &query);
Some(Reply::Query(MeasureReply { id: query.id, result }))
}
Command::MeshImport(request) => Some(Reply::MeshImport(reconstruct_mesh(request))),
Command::StepProbe(request) => Some(Reply::StepProbe(probe_step(request))),
Command::Topology(request) => Some(Reply::Topology(topology_reply(request))),
Command::SheetLines(request) => Some(Reply::SheetLines(sheet_lines_reply(request))),
Command::CamGenerate(job) => Some(Reply::CamGenerate(cam_generate_reply(job, progress))),
Command::CamSimulate(job) => Some(Reply::CamSimulate(cam_sim_reply(job, progress))),
Command::Reset => {
runner.reset();
brep_kernel::clear_history_cache();
cam_sim_clear();
pre_op_stock_clear();
None
}
Command::ExportSession { generation } => {
Some(Reply::SessionExport { generation, transfer: runner.export_session(generation) })
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub struct ThreadRunner {
tx: Option<std::sync::mpsc::Sender<Command>>,
rx: std::sync::mpsc::Receiver<Reply>,
handle: Option<std::thread::JoinHandle<()>>,
run_buf: std::collections::VecDeque<RunReply>,
query_buf: std::collections::VecDeque<MeasureReply>,
mesh_import_buf: std::collections::VecDeque<MeshImportReply>,
step_probe_buf: std::collections::VecDeque<StepProbeReply>,
topology_buf: std::collections::VecDeque<TopologyReply>,
sheet_lines_buf: std::collections::VecDeque<SheetLinesReply>,
cam_buf: std::collections::VecDeque<CamReply>,
cam_hold: bool,
cam_progress_buf: std::collections::VecDeque<CamProgress>,
cam_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
cam_sim_buf: std::collections::VecDeque<CamSimReply>,
cam_sim_progress_buf: std::collections::VecDeque<CamSimProgress>,
cam_sim_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
plugin_action_buf: std::collections::VecDeque<PluginActionReply>,
plugin_install_buf: std::collections::VecDeque<PluginInstallReply>,
sent_plugin_revision: Option<u64>,
progress_buf: std::collections::VecDeque<RunProgress>,
sent_library_revision: Option<u64>,
library_requested: bool,
stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
}
#[cfg(not(target_arch = "wasm32"))]
impl ThreadRunner {
pub fn new() -> Self {
let (tx, cmd_rx) = std::sync::mpsc::channel::<Command>();
let (reply_tx, rx) = std::sync::mpsc::channel::<Reply>();
let stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let thread_stop = stop.clone();
let cam_cancel = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let thread_cam_cancel = cam_cancel.clone();
let cam_sim_cancel = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let thread_cam_sim_cancel = cam_sim_cancel.clone();
let handle = std::thread::Builder::new()
.name("brep-history-runner".to_string())
.spawn(move || thread_main(cmd_rx, reply_tx, thread_stop, thread_cam_cancel, thread_cam_sim_cancel))
.expect("spawn brep-history-runner thread");
Self {
tx: Some(tx),
rx,
handle: Some(handle),
run_buf: std::collections::VecDeque::new(),
query_buf: std::collections::VecDeque::new(),
mesh_import_buf: std::collections::VecDeque::new(),
step_probe_buf: std::collections::VecDeque::new(),
topology_buf: std::collections::VecDeque::new(),
sheet_lines_buf: std::collections::VecDeque::new(),
cam_buf: std::collections::VecDeque::new(),
cam_hold: false,
cam_progress_buf: std::collections::VecDeque::new(),
cam_cancel,
cam_sim_buf: std::collections::VecDeque::new(),
cam_sim_progress_buf: std::collections::VecDeque::new(),
cam_sim_cancel,
plugin_action_buf: std::collections::VecDeque::new(),
plugin_install_buf: std::collections::VecDeque::new(),
sent_plugin_revision: None,
progress_buf: std::collections::VecDeque::new(),
sent_library_revision: None,
library_requested: false,
stop,
}
}
fn drain(&mut self) {
while let Ok(reply) = self.rx.try_recv() {
self.file(reply);
}
}
fn file(&mut self, reply: Reply) -> Option<(u64, Option<brep_kernel::SessionTransfer>)> {
match reply {
Reply::SessionExport { generation, transfer } => return Some((generation, transfer)),
Reply::ValidatedPlugins(reply) => self.plugin_install_buf.push_back(reply),
Reply::PluginAction(reply) => self.plugin_action_buf.push_back(reply),
Reply::Run(run) => self.run_buf.push_back(run),
Reply::Query(query) => self.query_buf.push_back(query),
Reply::MeshImport(reply) => self.mesh_import_buf.push_back(reply),
Reply::StepProbe(reply) => self.step_probe_buf.push_back(reply),
Reply::Topology(reply) => self.topology_buf.push_back(reply),
Reply::SheetLines(reply) => self.sheet_lines_buf.push_back(reply),
Reply::CamGenerate(reply) => self.cam_buf.push_back(reply),
Reply::CamProgress(progress) => self.cam_progress_buf.push_back(progress),
Reply::CamSimulate(reply) => self.cam_sim_buf.push_back(reply),
Reply::CamSimProgress(progress) => self.cam_sim_progress_buf.push_back(progress),
Reply::Progress(progress) => self.progress_buf.push_back(progress),
Reply::NeedPartsLibrary => {
self.sent_library_revision = None;
self.library_requested = true;
}
}
None
}
}
#[cfg(not(target_arch = "wasm32"))]
const SESSION_EXPORT_WAIT: std::time::Duration = std::time::Duration::from_millis(250);
#[cfg(not(target_arch = "wasm32"))]
impl Default for ThreadRunner {
fn default() -> Self {
Self::new()
}
}
#[cfg(not(target_arch = "wasm32"))]
fn thread_main(
cmd_rx: std::sync::mpsc::Receiver<Command>,
reply_tx: std::sync::mpsc::Sender<Reply>,
stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
cam_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
cam_sim_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
) {
let mut runner = crate::pipeline::SceneRunner::new();
while let Ok(first) = cmd_rx.recv() {
if stop.load(std::sync::atomic::Ordering::Relaxed) {
return;
}
let mut batch = vec![first];
loop {
match cmd_rx.try_recv() {
Ok(command) => batch.push(command),
Err(_) => break, }
}
let mut run_here: Vec<bool> = vec![true; batch.len()];
for i in 0..batch.len() {
if matches!(batch[i], Command::Run { .. })
&& matches!(batch.get(i + 1), Some(Command::Run { .. }))
{
run_here[i] = false;
}
}
for (i, command) in batch.into_iter().enumerate() {
if !run_here[i] {
continue;
}
let is_cam = matches!(command, Command::CamGenerate(_));
if is_cam {
cam_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
}
let is_sim = matches!(command, Command::CamSimulate(_));
let mut progress = |reply: Reply| {
reply_tx.send(reply).is_ok()
&& !stop.load(std::sync::atomic::Ordering::Relaxed)
&& !(is_cam && cam_cancel.load(std::sync::atomic::Ordering::SeqCst))
&& !(is_sim && cam_sim_cancel.load(std::sync::atomic::Ordering::SeqCst))
};
if let Some(reply) = process_command(&mut runner, command, &mut progress) {
if reply_tx.send(reply).is_err() {
return; }
}
}
}
}
#[cfg(not(target_arch = "wasm32"))]
impl HistoryRunner for ThreadRunner {
fn session_for_main(&mut self, generation: u64) -> SessionOnMain {
let Some(tx) = &self.tx else { return SessionOnMain::Unavailable };
if tx.send(Command::ExportSession { generation }).is_err() {
return SessionOnMain::Unavailable;
}
let deadline = std::time::Instant::now() + SESSION_EXPORT_WAIT;
loop {
let left = deadline.saturating_duration_since(std::time::Instant::now());
let Ok(reply) = self.rx.recv_timeout(left) else { return SessionOnMain::Unavailable };
if let Some((answered, transfer)) = self.file(reply) {
if answered != generation {
continue; }
return transfer.map_or(SessionOnMain::Unavailable, SessionOnMain::Copied);
}
}
}
fn submit_plugin_install(&mut self, request: PluginInstallRequest) -> bool {
self.tx.as_ref().is_some_and(|tx| tx.send(Command::ValidatePlugins(request)).is_ok())
}
fn poll_plugin_install(&mut self) -> Option<PluginInstallReply> {
self.drain();
self.plugin_install_buf.pop_front()
}
fn sync_plugins(&mut self, revision: u64, fetch: &mut dyn FnMut() -> serde_json::Value) {
if self.sent_plugin_revision != Some(revision) {
if let Some(tx) = &self.tx {
if tx.send(Command::SetPlugins { revision, packages: fetch() }).is_ok() {
self.sent_plugin_revision = Some(revision);
}
}
}
}
fn submit_plugin_action(&mut self, request: PluginActionRequest) -> bool {
self.tx.as_ref().is_some_and(|tx| tx.send(Command::PluginAction(request)).is_ok())
}
fn poll_plugin_action(&mut self) -> Option<PluginActionReply> {
self.drain();
self.plugin_action_buf.pop_front()
}
fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64) {
if let Some(tx) = &self.tx {
let _ = tx.send(Command::Run {
request,
generation,
parts_library_revision: self.sent_library_revision.unwrap_or(0),
});
}
}
fn sync_parts_library(
&mut self,
revision: u64,
fetch: &mut dyn FnMut() -> brep_kernel::PartsLibraryMap,
) {
if self.sent_library_revision == Some(revision) {
return;
}
if let Some(tx) = &self.tx {
let _ = tx.send(Command::SetPartsLibrary {
library: fetch(),
revision,
});
self.sent_library_revision = Some(revision);
}
}
fn poll_library_request(&mut self) -> bool {
self.drain();
std::mem::take(&mut self.library_requested)
}
fn poll_run(&mut self) -> Option<RunReply> {
self.drain();
self.run_buf.pop_front()
}
fn run_reply_waiting(&mut self) -> bool {
self.drain();
!self.run_buf.is_empty() || self.library_requested
}
fn submit_query(&mut self, query: MeasureQuery) {
if let Some(tx) = &self.tx {
let _ = tx.send(Command::Query(query));
}
}
fn poll_query(&mut self) -> Option<MeasureReply> {
self.drain();
self.query_buf.pop_front()
}
fn submit_mesh_import(&mut self, request: MeshImportRequest) {
if let Some(tx) = &self.tx {
let _ = tx.send(Command::MeshImport(request));
}
}
fn poll_mesh_import(&mut self) -> Option<MeshImportReply> {
self.drain();
self.mesh_import_buf.pop_front()
}
fn submit_step_probe(&mut self, request: StepProbeRequest) {
if let Some(tx) = &self.tx {
let _ = tx.send(Command::StepProbe(request));
}
}
fn poll_step_probe(&mut self) -> Option<StepProbeReply> {
self.drain();
self.step_probe_buf.pop_front()
}
fn submit_topology(&mut self, request: TopologyRequest) {
if let Some(tx) = &self.tx {
let _ = tx.send(Command::Topology(request));
}
}
fn poll_topology(&mut self) -> Option<TopologyReply> {
self.drain();
self.topology_buf.pop_front()
}
fn wait_topology(&mut self, timeout: std::time::Duration) -> bool {
self.drain();
let deadline = std::time::Instant::now() + timeout;
while self.topology_buf.is_empty() {
let now = std::time::Instant::now();
if now >= deadline {
return false;
}
match self.rx.recv_timeout(deadline - now) {
Ok(reply) => {
self.file(reply);
}
Err(_) => return false,
}
}
true
}
fn submit_sheet_lines(&mut self, request: SheetLinesRequest) -> bool {
match &self.tx {
Some(tx) => tx.send(Command::SheetLines(request)).is_ok(),
None => false,
}
}
fn poll_sheet_lines(&mut self) -> Option<SheetLinesReply> {
self.drain();
self.sheet_lines_buf.pop_front()
}
fn submit_cam(&mut self, job: CamJob) -> bool {
self.cam_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
match &self.tx {
Some(tx) => tx.send(Command::CamGenerate(job)).is_ok(),
None => false,
}
}
fn poll_cam(&mut self) -> Option<CamReply> {
self.drain();
if self.cam_hold {
return None;
}
self.cam_buf.pop_front()
}
fn hold_cam_replies(&mut self, hold: bool) {
self.cam_hold = hold;
}
fn poll_cam_progress(&mut self) -> Option<CamProgress> {
self.drain();
let latest = self.cam_progress_buf.pop_back();
self.cam_progress_buf.clear();
latest
}
fn cancel_cam(&mut self) {
self.cam_cancel.store(true, std::sync::atomic::Ordering::SeqCst);
}
fn submit_cam_sim(&mut self, job: CamSimJob) -> bool {
self.cam_sim_cancel.store(false, std::sync::atomic::Ordering::SeqCst);
match &self.tx {
Some(tx) => tx.send(Command::CamSimulate(job)).is_ok(),
None => false,
}
}
fn poll_cam_sim(&mut self) -> Option<CamSimReply> {
self.drain();
self.cam_sim_buf.pop_front()
}
fn poll_cam_sim_progress(&mut self) -> Option<CamSimProgress> {
self.drain();
let latest = self.cam_sim_progress_buf.pop_back();
self.cam_sim_progress_buf.clear();
latest
}
fn cancel_cam_sim(&mut self) {
self.cam_sim_cancel.store(true, std::sync::atomic::Ordering::SeqCst);
}
fn poll_progress(&mut self) -> Option<RunProgress> {
self.drain();
let latest = self.progress_buf.pop_back();
self.progress_buf.clear();
latest
}
fn cancel(&mut self) -> bool {
self.stop.store(true, std::sync::atomic::Ordering::Relaxed);
self.tx.take();
self.handle.take();
*self = Self::new();
true
}
fn reset(&mut self) {
self.sent_plugin_revision = None;
self.plugin_action_buf.clear();
if let Some(tx) = &self.tx {
let _ = tx.send(Command::Reset);
}
self.run_buf.clear();
self.query_buf.clear();
self.mesh_import_buf.clear();
self.step_probe_buf.clear();
self.topology_buf.clear();
self.sheet_lines_buf.clear();
self.progress_buf.clear();
self.sent_library_revision = None;
self.library_requested = false;
}
}
#[cfg(not(target_arch = "wasm32"))]
impl Drop for ThreadRunner {
fn drop(&mut self) {
self.tx.take();
self.handle.take();
}
}