Surface Loft: sheets through open or closed sections (#847)

Surface Loft lofts open or closed curve chains, one section per sketch in pick order, into surface bodies. Adds the SurfaceLoft feature (format 18 note), the surface.loft command, a dialog with Edit Feature support, and kernel and engine tests.

Part of #757.
This commit is contained in:
Irakli Kourtis authored and GitHub committed 2026-10-13 21:07:16 -04:00
1 parent 7c85970bfc
commit 897b7d93b5
15 files changed
+365 -55

No files matched your search

+27 -1
View File
@@ -1,5 +1,5 @@
//! Evaluation of the surface features: Patch, Stitch, Thicken, Trim, Extend, Surface Extrude,
//! Revolve and Sweep, Unstitch and Reverse Normal (kernel `surfaces`).
//! Revolve, Sweep and Loft, Unstitch and Reverse Normal (kernel `surfaces`).
use super::*;
use solidcraft_geom::Seg2;
@@ -181,6 +181,32 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
st.bodies.push(ModelBody::new(name, body, f.id));
Ok(())
}
FeatureKind::SurfaceLoft { sections } => {
let mut secs = Vec::with_capacity(sections.len());
for sec in sections {
let ss = sketch_in_feature_frame(
doc,
st.sketch(sec.sketch)
.ok_or_else(|| DocError::Unknown(format!("sketch {} (it must come earlier in the timeline)", sec.sketch)))?,
f.component,
)?;
let profiles = match (&sec.profiles, sec.curves.is_empty()) {
(None, true) => Some(&ProfileSel::All),
(p, _) => p.as_ref(),
};
let mut chains = sheet_chains(&ss, profiles, &sec.curves)?.into_iter();
let (Some(chain), None) = (chains.next(), chains.next()) else {
return Err(DocError::Invalid(
"each loft section must be one chain: one profile without holes, or curves meeting end to end".into(),
));
};
secs.push((ss.plane, chain));
}
let body = kernel::loft_sheet(&secs)?;
let name = new_name(st, f, 0);
st.bodies.push(ModelBody::new(name, body, f.id));
Ok(())
}
_ => Err(DocError::Invalid("not a surface feature".into())),
}
}