Class-A surfacing 3/3: blend curves, curved-edge fillets, the G2 corner and continuity analysis (#1642)

Thanks @midasdf.
This commit is contained in:
midasdf authored and GitHub committed 2026-10-16 06:26:27 -04:00
1 parent 3126962cd4
commit bbd4f29ebb
43 files changed
+4753 -132

No files matched your search

+47
View File
@@ -563,6 +563,10 @@ pub enum FeatureKind {
/// What to do when the radius is too large for the geometry around the edges.
#[serde(default, skip_serializing_if = "FilletFit::is_exact")]
fit: FilletFit,
/// What fills the corners where three blended edges meet: the rolling ball's sphere
/// (default) or a G2 patch repeating each blend up to the second order.
#[serde(default, skip_serializing_if = "FilletCorner::is_rolling")]
corner: FilletCorner,
},
Chamfer {
edges: Vec<Position>,
@@ -1311,6 +1315,10 @@ pub enum FeatureKind {
/// sketch curves: walls only, no caps.
SurfaceLoft {
sections: Vec<SheetSection>,
#[serde(default = "loft_plain_continuity", skip_serializing_if = "loft_continuity_is_plain")]
continuity: [Continuity; 2],
#[serde(default, skip_serializing_if = "loft_crown_is_plain")]
crown: [f64; 2],
},
/// The faces at `faces` copied out at a signed `distance` along their normals as a new
/// surface body (0 copies them); `chain` takes each pick's whole smooth chain of faces;
@@ -1491,6 +1499,23 @@ pub enum FeatureKind {
},
}
/// What fills the corner where three blended edges meet.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FilletCorner {
/// A piece of the rolling ball's sphere: tangent (G1) to each blend.
#[default]
Rolling,
/// A G2 patch repeating each blend's surface up to the second order (see `corner_g2`).
G2,
}
impl FilletCorner {
pub fn is_rolling(&self) -> bool {
matches!(self, FilletCorner::Rolling)
}
}
/// How Extend continues a surface past its edges.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@@ -1734,6 +1759,28 @@ pub struct SheetSection {
pub profiles: Option<ProfileSel>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub curves: Vec<String>,
/// A section picked as an edge chain of a body instead of a sketch: `edges` are points on the
/// chain, and the face the chain bounds is the surface the loft leaves there.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub body: Option<BodyName>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edges: Vec<Position>,
}
/// A loft's continuity where a design does not say: both ends G0, the loft of old, which only
/// passes through its sections.
fn loft_plain_continuity() -> [Continuity; 2] {
[Continuity::G0, Continuity::G0]
}
/// A loft's continuity is plain when it only passes through its sections: both ends G0.
fn loft_continuity_is_plain(c: &[Continuity; 2]) -> bool {
*c == [Continuity::G0, Continuity::G0]
}
/// A loft's crown is plain when neither end is filled out.
fn loft_crown_is_plain(c: &[f64; 2]) -> bool {
c.iter().all(|v| *v == 0.0)
}
/// Coil cross-sections.