1use super::*;
2
3pub struct DisplaySolidPayload {
5 pub faces: Vec<(u64, Option<String>)>,
8 pub mesh: Mesh,
10 pub edges: Vec<(u64, Option<String>, Vec<Vec3>)>,
12 pub vertices: Vec<(u64, Vec3)>,
14 pub chord_tolerance: f64,
16}
17
18pub fn display_chord_tolerance(solid: &BrepSolid, lod_factor: f64) -> f64 {
23 let mut extent = 0.0f64;
24 for vertex in &solid.vertices {
25 extent = extent
26 .max(vertex.point.x.abs())
27 .max(vertex.point.y.abs())
28 .max(vertex.point.z.abs());
29 }
30 if extent <= 0.0 {
31 let mut control_point_extent = 0.0f64;
32 for shell in &solid.shells {
33 for face in &shell.faces {
34 for row in &face.surface.control_points {
35 for cp in row {
36 let w = if cp.w != 0.0 { cp.w } else { 1.0 };
37 control_point_extent = control_point_extent
38 .max((cp.x / w).abs())
39 .max((cp.y / w).abs())
40 .max((cp.z / w).abs());
41 }
42 }
43 }
44 }
45 extent = control_point_extent / std::f64::consts::SQRT_2;
46 }
47 extent.max(1e-9) * 1.5e-3 * lod_factor
48}
49
50pub fn display_payload_handle_native(
56 handle: u32,
57 lod_factor: f64,
58) -> Result<DisplaySolidPayload, String> {
59 with_registered_solid_str(handle, |solid| {
60 let chord = display_chord_tolerance(solid, lod_factor);
61 let mesh = tessellate_brep_watertight(solid, chord)?;
62 let mut faces = Vec::new();
63 for shell in &solid.shells {
64 for face in &shell.faces {
65 faces.push((face.id, face.name.clone()));
66 }
67 }
68 let edge_names: std::collections::HashMap<u64, String> = solid
69 .edges
70 .iter()
71 .filter_map(|edge| edge.name.as_ref().map(|name| (edge.id, name.clone())))
72 .collect();
73 let edges = sample_edge_polylines(solid, chord)?
74 .into_iter()
75 .map(|(id, points)| (id, edge_names.get(&id).cloned(), points))
76 .collect();
77 let vertices = solid
78 .vertices
79 .iter()
80 .map(|vertex| (vertex.id, vertex.point))
81 .collect();
82 Ok(DisplaySolidPayload {
83 faces,
84 mesh,
85 edges,
86 vertices,
87 chord_tolerance: chord,
88 })
89 })
90}
91
92pub fn sketch_profile_display_payload(
107 profile: &crate::feature_pipeline::SketchProfile,
108) -> DisplaySolidPayload {
109 const SEGMENTS: usize = 24;
112 let origin = profile.origin;
113 let x_axis = profile.x_axis;
114 let y_axis = profile.y_axis;
115 let normal = profile.z_axis;
116 let to_uv = |p: Vec3| {
117 let d = p.sub(origin);
118 [d.dot(x_axis), d.dot(y_axis)]
119 };
120
121 let mut mesh = Mesh::default();
122 let mut edges: Vec<(u64, Option<String>, Vec<Vec3>)> = Vec::new();
123 let mut vertices: Vec<(u64, Vec3)> = Vec::new();
124 let mut next_edge_id: u64 = 0;
125 let mut next_vertex_id: u64 = 0;
126 let mut sketch_id: Option<String> = None;
127
128 for region in &profile.regions {
129 let Some((outer, holes)) = region.split_first() else {
130 continue;
131 };
132 let boundary = |lp: &crate::feature_pipeline::ProfileLoop| -> Vec<([f64; 2], Vec3)> {
135 let mut out = Vec::new();
136 for curve in &lp.curves {
137 let Ok([t0, t1]) = curve.domain() else { continue };
138 for step in 0..SEGMENTS {
139 let t = t0 + (t1 - t0) * step as f64 / SEGMENTS as f64;
140 if let Ok(p) = curve.evaluate(t) {
141 out.push((to_uv(p), p));
142 }
143 }
144 }
145 out
146 };
147 let outer_b = boundary(outer);
148 let holes_b: Vec<Vec<([f64; 2], Vec3)>> = holes.iter().map(boundary).collect();
149 for [a, b, c] in watertight_tessellation::triangulate_planar_region(&outer_b, &holes_b) {
150 let base = (mesh.positions.len() / 3) as u32;
151 for p in [a, b, c] {
152 mesh.positions.extend([p.x, p.y, p.z]);
153 mesh.normals.extend([normal.x, normal.y, normal.z]);
154 }
155 mesh.indices.extend([base, base + 1, base + 2]);
156 mesh.face_ids.push(0);
157 }
158
159 for lp in region {
161 for (index, curve) in lp.curves.iter().enumerate() {
162 let Ok([t0, t1]) = curve.domain() else { continue };
163 let mut polyline = Vec::with_capacity(SEGMENTS + 1);
164 for step in 0..=SEGMENTS {
165 let t = t0 + (t1 - t0) * step as f64 / SEGMENTS as f64;
166 if let Ok(p) = curve.evaluate(t) {
167 polyline.push(p);
168 }
169 }
170 let name = lp.edge_names.get(index).cloned().flatten();
171 if sketch_id.is_none() {
172 sketch_id = name
174 .as_deref()
175 .and_then(|n| n.split_once(":G").map(|(id, _)| id.to_string()));
176 }
177 if polyline.len() >= 2 {
178 edges.push((next_edge_id, name, polyline));
179 next_edge_id += 1;
180 }
181 if let Ok(p) = curve.evaluate(t0) {
182 vertices.push((next_vertex_id, p));
183 next_vertex_id += 1;
184 }
185 }
186 }
187 }
188
189 let faces = if mesh.face_ids.is_empty() {
191 Vec::new()
192 } else {
193 let face_name = sketch_id.map(|id| format!("{id}:FACE"));
194 vec![(0u64, face_name)]
195 };
196 DisplaySolidPayload {
197 faces,
198 mesh,
199 edges,
200 vertices,
201 chord_tolerance: 0.0,
202 }
203}
204
205pub fn mass_properties_handle_native(
215 handle: u32,
216 density: f64,
217) -> Result<DensityMassProperties, String> {
218 with_registered_solid_str(handle, |solid| {
219 Ok(solid_mass_properties_full(solid)?.with_density(density))
220 })
221}
222
223pub fn solid_edge_length_total_native(handle: u32) -> Result<f64, String> {
228 with_registered_solid_str(handle, solid_edge_length_total)
229}
230
231pub fn face_measurements_native(handle: u32, face_name: &str) -> Result<(f64, f64), String> {
235 with_registered_solid_str(handle, |solid| {
236 let face = solid
237 .shells
238 .iter()
239 .flat_map(|shell| &shell.faces)
240 .find(|face| face.name.as_deref() == Some(face_name))
241 .ok_or_else(|| format!("face '{face_name}' not found"))?;
242 Ok((face_area(face)?, face_boundary_length(solid, face)?))
243 })
244}
245
246pub fn edge_length_native(handle: u32, edge_name: &str) -> Result<f64, String> {
249 with_registered_solid_str(handle, |solid| {
250 let edge = solid
251 .edges
252 .iter()
253 .find(|edge| edge.name.as_deref() == Some(edge_name))
254 .ok_or_else(|| format!("edge '{edge_name}' not found"))?;
255 edge_arc_length(edge)
256 })
257}
258
259#[wasm_bindgen]
263pub fn solid_handle_to_buffer(handle: u32) -> Result<WasmSolidBuffer, JsValue> {
264 with_registered_solid(handle, |solid| solid_buffer(solid, "{}".into()))
265}
266
267pub fn export_step_handles(
275 handles: &[u32],
276 name: &str,
277 unit: &str,
278 timestamp: &str,
279) -> Result<String, String> {
280 if handles.is_empty() {
281 return Err("export_step_handles: no solids to export".into());
282 }
283 let mut solids = Vec::with_capacity(handles.len());
284 for &handle in handles {
285 let solid: BrepSolid = with_registered_solid_str(handle, |solid| Ok(solid.clone()))?;
286 solids.push(solid);
287 }
288 export_step(&solids, name, unit, timestamp)
289}