use super::*;
pub struct DisplaySolidPayload {
pub faces: Vec<(u64, Option<String>)>,
pub mesh: Mesh,
pub edges: Vec<(u64, Option<String>, Vec<Vec3>)>,
pub vertices: Vec<(u64, Vec3)>,
pub chord_tolerance: f64,
}
pub fn display_chord_tolerance(solid: &BrepSolid, lod_factor: f64) -> f64 {
let mut extent = 0.0f64;
for vertex in &solid.vertices {
extent = extent
.max(vertex.point.x.abs())
.max(vertex.point.y.abs())
.max(vertex.point.z.abs());
}
if extent <= 0.0 {
let mut control_point_extent = 0.0f64;
for shell in &solid.shells {
for face in &shell.faces {
for row in &face.surface.control_points {
for cp in row {
let w = if cp.w != 0.0 { cp.w } else { 1.0 };
control_point_extent = control_point_extent
.max((cp.x / w).abs())
.max((cp.y / w).abs())
.max((cp.z / w).abs());
}
}
}
}
extent = control_point_extent / std::f64::consts::SQRT_2;
}
extent.max(1e-9) * 1.5e-3 * lod_factor
}
pub fn display_payload_handle_native(
handle: u32,
lod_factor: f64,
) -> Result<DisplaySolidPayload, String> {
with_registered_solid_str(handle, |solid| {
let chord = display_chord_tolerance(solid, lod_factor);
let mesh = tessellate_brep_watertight(solid, chord)?;
let mut faces = Vec::new();
for shell in &solid.shells {
for face in &shell.faces {
faces.push((face.id, face.name.clone()));
}
}
let edge_names: std::collections::HashMap<u64, String> = solid
.edges
.iter()
.filter_map(|edge| edge.name.as_ref().map(|name| (edge.id, name.clone())))
.collect();
let edges = sample_edge_polylines(solid, chord)?
.into_iter()
.map(|(id, points)| (id, edge_names.get(&id).cloned(), points))
.collect();
let vertices = solid
.vertices
.iter()
.map(|vertex| (vertex.id, vertex.point))
.collect();
Ok(DisplaySolidPayload {
faces,
mesh,
edges,
vertices,
chord_tolerance: chord,
})
})
}
pub fn sketch_profile_display_payload(
profile: &crate::feature_pipeline::SketchProfile,
) -> DisplaySolidPayload {
const SEGMENTS: usize = 24;
let origin = profile.origin;
let x_axis = profile.x_axis;
let y_axis = profile.y_axis;
let normal = profile.z_axis;
let to_uv = |p: Vec3| {
let d = p.sub(origin);
[d.dot(x_axis), d.dot(y_axis)]
};
let mut mesh = Mesh::default();
let mut edges: Vec<(u64, Option<String>, Vec<Vec3>)> = Vec::new();
let mut vertices: Vec<(u64, Vec3)> = Vec::new();
let mut next_edge_id: u64 = 0;
let mut next_vertex_id: u64 = 0;
let mut sketch_id: Option<String> = None;
for region in &profile.regions {
let Some((outer, holes)) = region.split_first() else {
continue;
};
let boundary = |lp: &crate::feature_pipeline::ProfileLoop| -> Vec<([f64; 2], Vec3)> {
let mut out = Vec::new();
for curve in &lp.curves {
let Ok([t0, t1]) = curve.domain() else { continue };
for step in 0..SEGMENTS {
let t = t0 + (t1 - t0) * step as f64 / SEGMENTS as f64;
if let Ok(p) = curve.evaluate(t) {
out.push((to_uv(p), p));
}
}
}
out
};
let outer_b = boundary(outer);
let holes_b: Vec<Vec<([f64; 2], Vec3)>> = holes.iter().map(boundary).collect();
for [a, b, c] in watertight_tessellation::triangulate_planar_region(&outer_b, &holes_b) {
let base = (mesh.positions.len() / 3) as u32;
for p in [a, b, c] {
mesh.positions.extend([p.x, p.y, p.z]);
mesh.normals.extend([normal.x, normal.y, normal.z]);
}
mesh.indices.extend([base, base + 1, base + 2]);
mesh.face_ids.push(0);
}
for lp in region {
for (index, curve) in lp.curves.iter().enumerate() {
let Ok([t0, t1]) = curve.domain() else { continue };
let mut polyline = Vec::with_capacity(SEGMENTS + 1);
for step in 0..=SEGMENTS {
let t = t0 + (t1 - t0) * step as f64 / SEGMENTS as f64;
if let Ok(p) = curve.evaluate(t) {
polyline.push(p);
}
}
let name = lp.edge_names.get(index).cloned().flatten();
if sketch_id.is_none() {
sketch_id = name
.as_deref()
.and_then(|n| n.split_once(":G").map(|(id, _)| id.to_string()));
}
if polyline.len() >= 2 {
edges.push((next_edge_id, name, polyline));
next_edge_id += 1;
}
if let Ok(p) = curve.evaluate(t0) {
vertices.push((next_vertex_id, p));
next_vertex_id += 1;
}
}
}
}
let faces = if mesh.face_ids.is_empty() {
Vec::new()
} else {
let face_name = sketch_id.map(|id| format!("{id}:FACE"));
vec![(0u64, face_name)]
};
DisplaySolidPayload {
faces,
mesh,
edges,
vertices,
chord_tolerance: 0.0,
}
}
pub fn sketch_display_payload(
profile: Option<&crate::feature_pipeline::SketchProfile>,
segments: &[(String, Vec<NurbsCurve>)],
points: &[Vec3],
) -> DisplaySolidPayload {
const SEGMENTS: usize = 24;
const SAMPLES_PER_SPAN: usize = 4;
const MAX_SEGMENTS: usize = 4096;
const SAME_POINT: f64 = 1e-9;
let mut payload = match profile {
Some(profile) => sketch_profile_display_payload(profile),
None => DisplaySolidPayload {
faces: Vec::new(),
mesh: Mesh::default(),
edges: Vec::new(),
vertices: Vec::new(),
chord_tolerance: 0.0,
},
};
let drawn: std::collections::HashSet<String> = payload
.edges
.iter()
.filter_map(|(_, name, _)| name.clone())
.collect();
let mut next_edge_id = payload
.edges
.iter()
.map(|(id, _, _)| id + 1)
.max()
.unwrap_or(0);
let mut next_vertex_id = payload.vertices.iter().map(|(id, _)| id + 1).max().unwrap_or(0);
for (name, curves) in segments {
if drawn.contains(name) {
continue;
}
for curve in curves {
let Ok([t0, t1]) = curve.domain() else { continue };
let spans = curve
.knots
.windows(2)
.filter(|pair| pair[1] > pair[0])
.count();
let segments = (spans * SAMPLES_PER_SPAN).clamp(SEGMENTS, MAX_SEGMENTS);
let mut polyline = Vec::with_capacity(segments + 1);
for step in 0..=segments {
let t = t0 + (t1 - t0) * step as f64 / segments as f64;
if let Ok(point) = curve.evaluate(t) {
polyline.push(point);
}
}
if polyline.len() < 2 {
continue;
}
for end in [polyline[0], polyline[polyline.len() - 1]] {
if payload
.vertices
.iter()
.any(|(_, point)| point.sub(end).length() <= SAME_POINT)
{
continue;
}
payload.vertices.push((next_vertex_id, end));
next_vertex_id += 1;
}
payload.edges.push((next_edge_id, Some(name.clone()), polyline));
next_edge_id += 1;
}
}
for &point in points {
if payload
.vertices
.iter()
.any(|(_, drawn)| drawn.sub(point).length() <= SAME_POINT)
{
continue;
}
payload.vertices.push((next_vertex_id, point));
next_vertex_id += 1;
}
payload
}
pub fn mass_properties_handle_native(
handle: u32,
density: f64,
) -> Result<DensityMassProperties, String> {
with_registered_solid_str(handle, |solid| {
Ok(solid_mass_properties_full(solid)?.with_density(density))
})
}
pub fn validate_handle_native(handle: u32) -> Result<Vec<(String, String)>, String> {
with_registered_solid_str(handle, |solid| {
Ok(solid
.validate()
.into_iter()
.map(|issue| (issue.severity.to_string(), issue.message))
.collect())
})
}
pub fn topology_counts_native(handle: u32) -> Result<(usize, usize, usize, usize), String> {
with_registered_solid_str(handle, |solid| {
Ok((
solid.shells.iter().map(|shell| shell.faces.len()).sum(),
solid.edges.len(),
solid.edges.iter().filter(|edge| !edge.degenerate).count(),
solid.vertices.len(),
))
})
}
pub fn connectivity_handle_native(handle: u32) -> Result<crate::ConnectivityReport, String> {
with_registered_solid_str(handle, |solid| Ok(crate::solid_connectivity(solid)))
}
pub fn self_intersections_handle_native(
handle: u32,
chord_tolerance: f64,
) -> Result<crate::SelfIntersectionReport, String> {
with_registered_solid_str(handle, |solid| {
let mut options = crate::SelfIntersectionOptions::for_solid(solid);
if chord_tolerance > 0.0 && chord_tolerance.is_finite() {
options.chord_tolerance = chord_tolerance;
}
crate::solid_self_intersections(solid, options)
})
}
pub fn transform_pivot_native(handle: u32) -> Result<[f64; 3], String> {
with_registered_solid_str(handle, |solid| {
Ok(crate::feature_pipeline::transform_bbox_center(solid))
})
}
pub fn solid_edge_length_total_native(handle: u32) -> Result<f64, String> {
with_registered_solid_str(handle, solid_edge_length_total)
}
pub fn face_measurements_native(
handle: u32,
face_name: &str,
) -> Result<(f64, f64, &'static str), String> {
with_registered_solid_str(handle, |solid| {
let face = solid
.shells
.iter()
.flat_map(|shell| &shell.faces)
.find(|face| face.name.as_deref() == Some(face_name))
.ok_or_else(|| format!("face '{face_name}' not found"))?;
let surface_type = face
.surface
.analytic()
.map(|analytic| analytic.kind_label())
.unwrap_or("NURBS");
Ok((
face_area(face)?,
face_boundary_length(solid, face)?,
surface_type,
))
})
}
pub fn edge_length_native(handle: u32, edge_name: &str) -> Result<f64, String> {
with_registered_solid_str(handle, |solid| {
let edge = solid
.edges
.iter()
.find(|edge| edge.name.as_deref() == Some(edge_name))
.ok_or_else(|| format!("edge '{edge_name}' not found"))?;
edge_arc_length(edge)
})
}
#[wasm_bindgen]
pub fn solid_handle_to_buffer(handle: u32) -> Result<WasmSolidBuffer, JsValue> {
with_registered_solid(handle, |solid| solid_buffer(solid, "{}".into()))
}
pub fn export_step_handles(
handles: &[u32],
name: &str,
unit: &str,
timestamp: &str,
) -> Result<String, String> {
if handles.is_empty() {
return Err("export_step_handles: no solids to export".into());
}
let mut solids = Vec::with_capacity(handles.len());
for &handle in handles {
let solid: BrepSolid = with_registered_solid_str(handle, |solid| Ok(solid.clone()))?;
solids.push(solid);
}
export_step(&solids, name, unit, timestamp)
}
pub fn export_step_named_handles(
named: &[(String, u32)],
name: &str,
unit: &str,
timestamp: &str,
pmi: Option<&crate::StepPmi<'_>>,
) -> Result<crate::StepExportReport, String> {
if named.is_empty() {
return Err("export_step_named_handles: no solids to export".into());
}
let mut solids = Vec::with_capacity(named.len());
for (solid_name, handle) in named {
let solid: BrepSolid = with_registered_solid_str(*handle, |solid| Ok(solid.clone()))?;
solids.push((solid_name.clone(), solid));
}
let borrowed: Vec<(String, &BrepSolid)> = solids
.iter()
.map(|(solid_name, solid)| (solid_name.clone(), solid))
.collect();
crate::export_step_report_named(&borrowed, name, unit, timestamp, pmi)
}
pub fn export_step_assembly_handles(
document_name: &str,
named: &[(String, u32)],
components: &[(String, String, crate::Mat4)],
unit: &str,
timestamp: &str,
pmi: Option<&crate::StepPmi<'_>>,
) -> Result<crate::StepExportReport, String> {
if named.is_empty() {
return Err("export_step_assembly_handles: no solids to export".into());
}
let mut solids = Vec::with_capacity(named.len());
for (solid_name, handle) in named {
let solid: BrepSolid = with_registered_solid_str(*handle, |solid| Ok(solid.clone()))?;
solids.push((solid_name.clone(), solid));
}
let assembly = crate::assembly_export_tree(document_name, solids, components)?;
crate::export_step_assembly_report(&assembly, unit, timestamp, pmi)
}
pub fn export_iges_handles(
handles: &[u32],
name: &str,
unit: &str,
timestamp: &str,
) -> Result<String, String> {
if handles.is_empty() {
return Err("export_iges_handles: no solids to export".into());
}
let mut solids = Vec::with_capacity(handles.len());
for &handle in handles {
let solid: BrepSolid = with_registered_solid_str(handle, |solid| Ok(solid.clone()))?;
solids.push(solid);
}
export_iges(&solids, name, unit, timestamp)
}