STEP: open/insert/drag-drop
Import base feature (FeatureKind::Import, timeline type BaseFeature) holding the STEP text and the file's product tree; bodies named from the file. doc.open accepts .step/.stp (any case) as a new design; Insert STEP (FusionImportCommandFromToolbar) adds one to the current design; File menu, SOLID > INSERT, drag-and-drop (insert into a non-empty design, else open), 'solvecraft part.step', MCP open. Features (fillet, hole, sketch-on-face cut, shell) work on imported bodies. Imports are cached so a file is read once per command. Measure tightens its chord tolerance for small radii. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
ed611ed7ba
commit
f3dbb12b23
18 files changed
+360
-18
No files matched your search
@@ -31,7 +31,7 @@ pub use ops::{BoolOp, boolean, split_by_plane, transform, transform_matrix};
|
||||
pub use polybool::planar_boolean;
|
||||
pub use polyhedron::{HalfSpace, convex_polyhedron, draft, shell};
|
||||
pub use step::step_export;
|
||||
pub use step_in::{ImportedBody, StepImport, StepNode, step_import};
|
||||
pub use step_in::{ImportedBody, StepImport, StepNode, step_import, step_import_shared};
|
||||
pub use topo::{TopoCounts, merged_topology, seam_flags};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
|
||||
|
||||
@@ -35,13 +35,38 @@ fn face_sums(m: &Mesh, nf: usize) -> Vec<(f64, f64, Vec3)> {
|
||||
out
|
||||
}
|
||||
|
||||
/// Measure a body from a fine tessellation (chord tolerance 5e-5 of the body size). Faces whose
|
||||
/// fine tessellation disagrees with a coarse one (a meshing failure) use the coarse result.
|
||||
/// Smallest radius of curvature along the mesh's edge polylines (straight edges ignored).
|
||||
fn min_edge_radius(m: &Mesh) -> Option<f64> {
|
||||
let mut best: Option<f64> = None;
|
||||
for e in &m.edges {
|
||||
for w in e.windows(3) {
|
||||
let (Some(&a), Some(&b), Some(&c)) = (w.first(), w.get(1), w.get(2)) else { continue };
|
||||
let (ab, bc, ca) = (b - a, c - b, a - c);
|
||||
let cross = ab.cross(bc).len();
|
||||
if !(cross > 1e-12 * ab.len() * bc.len()) {
|
||||
continue;
|
||||
}
|
||||
let r = ab.len() * bc.len() * ca.len() / (2.0 * cross);
|
||||
if r.is_finite() && r > 0.0 && best.is_none_or(|x| r < x) {
|
||||
best = Some(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
best
|
||||
}
|
||||
|
||||
/// Measure a body from a fine tessellation (chord tolerance 5e-5 of the body size, finer for
|
||||
/// small radii down to 5e-6). Faces whose fine tessellation disagrees with a coarse one (a
|
||||
/// meshing failure) use the coarse result.
|
||||
pub fn measure(b: &Body) -> Result<BodyMeasure> {
|
||||
let size = b.size();
|
||||
let fine = b.tessellate((size * 5e-5).max(1e-4))?;
|
||||
let medium = b.tessellate((size * 2.5e-4).max(2e-4))?;
|
||||
let coarse = b.tessellate((size * 1e-3).max(1e-3))?;
|
||||
// Chord error relative to a radius sets the volume error: keep it near 3e-4 of the
|
||||
// smallest radius (thin tubes, small fillets).
|
||||
let tight = min_edge_radius(&coarse).map(|r| r * 3e-4).unwrap_or(f64::INFINITY);
|
||||
let fine_tol = (size * 5e-5).min(tight).max(size * 5e-6).max(1e-4);
|
||||
let fine = b.tessellate(fine_tol)?;
|
||||
let medium = b.tessellate((fine_tol * 5.0).max(2e-4))?;
|
||||
let nf = b.face_count();
|
||||
let (sf, sm, sc) = (face_sums(&fine, nf), face_sums(&medium, nf), face_sums(&coarse, nf));
|
||||
let close = |x: f64, y: f64| (x - y).abs() <= 0.01 * x.abs().max(y.abs()) + 1e-9;
|
||||
|
||||
@@ -567,6 +567,32 @@ impl<'a> Reader<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Recent imports, so a file read for a command and then evaluated in the timeline is only
|
||||
/// read once.
|
||||
static RECENT: std::sync::Mutex<Vec<(u64, usize, std::sync::Arc<StepImport>)>> = std::sync::Mutex::new(Vec::new());
|
||||
|
||||
/// [`step_import`] through a small cache of recent results (keyed by the text).
|
||||
pub fn step_import_shared(text: &str) -> Result<std::sync::Arc<StepImport>> {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||
text.hash(&mut h);
|
||||
let key = (h.finish(), text.len());
|
||||
if let Ok(cache) = RECENT.lock()
|
||||
&& let Some((_, _, imp)) = cache.iter().find(|(k, n, _)| (*k, *n) == key)
|
||||
{
|
||||
return Ok(imp.clone());
|
||||
}
|
||||
let imp = std::sync::Arc::new(step_import(text)?);
|
||||
if let Ok(mut cache) = RECENT.lock() {
|
||||
cache.retain(|(k, n, _)| (*k, *n) != key);
|
||||
cache.push((key.0, key.1, imp.clone()));
|
||||
if cache.len() > 4 {
|
||||
cache.remove(0);
|
||||
}
|
||||
}
|
||||
Ok(imp)
|
||||
}
|
||||
|
||||
/// Read a STEP file: its solids (as bodies, placed by the assembly structure) and the tree.
|
||||
pub fn step_import(text: &str) -> Result<StepImport> {
|
||||
let ex = p21::parse(text).map_err(KernelError::Invalid)?;
|
||||
|
||||
Reference in new issue
Block a user