Patch: G1/G2 to the faces beside the loop, general and N-sided loops, points (#759) (#1521)

Thanks @jyviko.
This commit is contained in:
Irakli Kourtis authored and GitHub committed 2026-10-15 23:07:24 -04:00
1 parent 77bdcc47e9
commit fac3f96bdd
24 files changed
+2688 -77

No files matched your search

+47
View File
@@ -1276,6 +1276,14 @@ pub enum FeatureKind {
/// exact plane whatever this says).
#[serde(default, skip_serializing_if = "is_zero")]
crown: f64,
/// How a patch on edges meets the faces beside its loop (G0 everywhere by default, and
/// then not saved).
#[serde(default, skip_serializing_if = "PatchContinuity::is_g0")]
continuity: PatchContinuity,
/// Points a patch on edges passes through.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[walk(pos)]
points: Vec<Vec3>,
},
/// Surface bodies sewn along shared edges into the first one (a solid when they close).
Stitch {
@@ -1666,6 +1674,45 @@ pub struct BlendSide {
pub edges: Vec<Vec3>,
}
/// How a patch on a loop of edges meets the faces beside the loop: every edge with `all`, but the
/// edges picked in `edges` with their own.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, solidcraft_derive::Walk)]
pub struct PatchContinuity {
#[serde(default = "patch_g0", skip_serializing_if = "is_g0")]
pub all: Continuity,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edges: Vec<EdgeContinuity>,
}
/// One edge of a patch's loop (a point on it) and how the patch meets the face beside it.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, solidcraft_derive::Walk)]
pub struct EdgeContinuity {
#[walk(pos)]
pub edge: Vec3,
pub g: Continuity,
}
impl Default for PatchContinuity {
fn default() -> PatchContinuity {
PatchContinuity { all: Continuity::G0, edges: Vec::new() }
}
}
impl PatchContinuity {
/// G0 everywhere: the patch only meets its edges.
pub fn is_g0(&self) -> bool {
self.all == Continuity::G0 && self.edges.iter().all(|e| e.g == Continuity::G0)
}
}
fn patch_g0() -> Continuity {
Continuity::G0
}
fn is_g0(g: &Continuity) -> bool {
*g == Continuity::G0
}
/// The continuity a blend repeats by default: the tangent plane on both sides.
fn blend_orders() -> [Continuity; 2] {
[Continuity::G1, Continuity::G1]
+8 -2
View File
@@ -8,7 +8,7 @@ use solidcraft_geom::Seg2;
pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState, warning: &mut Option<String>) -> Result<()> {
match &f.kind {
FeatureKind::Patch { sketch, profiles, body, edges, crown } => {
FeatureKind::Patch { sketch, profiles, body, edges, crown, continuity, points } => {
let mut made = Vec::new();
if let Some(sk) = sketch {
let ss = st.sketch(*sk).ok_or_else(|| DocError::Unknown(format!("sketch {sk} (it must come earlier in the timeline)")))?;
@@ -20,7 +20,13 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
if !edges.is_empty() {
let i = body_at(st, body, edges)?;
let b = st.bodies.get(i).ok_or_else(|| DocError::Invalid("body".into()))?;
made.push(kernel::patch_edges_crowned(&b.body, edges, *crown)?);
let spec = kernel::PatchSpec {
continuity: continuity.all,
edges: continuity.edges.iter().map(|e| (e.edge, e.g)).collect(),
points: points.clone(),
crown: *crown,
};
made.push(kernel::patch_edges_with(&b.body, edges, &spec)?);
}
if made.is_empty() {
return Err(DocError::Invalid("a patch needs a sketch region or a loop of edges".into()));
@@ -0,0 +1,6 @@
//! A `Patch` on edges can carry `continuity` (`all`: `g0`, `g1` or `g2`, and `edges`:
//! `[{edge: [x, y, z], g}]` for edges met otherwise; G0 everywhere when absent, and then not
//! saved) and `points` it passes through (none when absent). Older designs need no
//! transformation: a patch without them fills as it always did; a loop that is not a height field
//! over its plane, which 0.13.0 refused, now fills with one face. 0.13.x asks for an update
//! instead of dropping them.