#[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,
}
pub use brep_reconstruction::stl_conversion::{
ConversionPolicy, StlConversionOptions, StlConversionOutput,
};
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub enum MeshImportFormat {
Stl,
Obj,
}
#[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>,
}
pub trait HistoryRunner {
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 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 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)) => serde_json::json!({
"ok": true,
"kind": "face",
"solid": query.owner,
"surfaceType": surface_type,
"area": area,
"edgeLengthTotal": edge_total,
})
.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) => serde_json::json!({
"ok": true,
"kind": "edge",
"solid": query.owner,
"length": length,
})
.to_string(),
Err(error) => serde_json::json!({ "ok": false, "message": error }).to_string(),
},
}
}
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>,
}
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(),
}
}
}
impl HistoryRunner for InlineRunner {
fn submit_run(&mut self, request: brep_kernel::HistoryRequest, generation: u64) {
let output = self.runner.run(&request);
self.pending.push_back(RunReply { generation, output });
}
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 reset(&mut self) {
self.runner.reset();
}
}
impl Default for InlineRunner {
fn default() -> Self {
Self::new()
}
}
#[derive(serde::Serialize, serde::Deserialize)]
pub enum Command {
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),
Reset,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub enum Reply {
Run(RunReply),
Query(MeasureReply),
MeshImport(MeshImportReply),
StepProbe(StepProbeReply),
NeedPartsLibrary,
Progress(RunProgress),
}
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,
};
use brep_reconstruction::{Mesh, Vec3};
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
};
(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,
)
}
};
let mut options = request.options;
options.coordinate_precision_tolerance = options.coordinate_precision_tolerance
.max(coordinate_precision_tolerance);
convert_stl_mesh_to_step(
&mesh,
&positions,
Some(&indices),
&options,
"Imported mesh",
"MM",
"",
)
.map_err(|error| format!("RANSAC reconstruction failed: {error}"))
})();
MeshImportReply {
id: request.id,
result,
}
}
pub fn process_command(
runner: &mut crate::pipeline::SceneRunner,
command: Command,
progress: &mut dyn FnMut(RunProgress) -> bool,
) -> Option<Reply> {
match command {
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 output = runner.run_observed(&request, &mut |event| {
progress(RunProgress {
generation,
index: event.index,
total: event.total,
feature_id: event.id.to_string(),
feature_type: event.feature_type.to_string(),
})
});
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::Reset => {
runner.reset();
brep_kernel::clear_history_cache();
None
}
}
}
#[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>,
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 handle = std::thread::Builder::new()
.name("brep-history-runner".to_string())
.spawn(move || thread_main(cmd_rx, reply_tx, thread_stop))
.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(),
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() {
match 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::Progress(progress) => self.progress_buf.push_back(progress),
Reply::NeedPartsLibrary => {
self.sent_library_revision = None;
self.library_requested = true;
}
}
}
}
}
#[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>,
) {
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 mut progress = |report: RunProgress| {
reply_tx.send(Reply::Progress(report)).is_ok()
&& !stop.load(std::sync::atomic::Ordering::Relaxed)
};
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 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 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 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) {
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.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();
}
}