Extrude: a symmetric distance is the total thickness (#1260)

A symmetric solid extrude of 10 mm is now 10 mm thick, 5 mm each side of the sketch plane, and switching from One Side to Symmetric keeps the thickness entered. Under format 21 the extent records its convention; designs saved without it keep the per-side reading, so their geometry and parameters don't change. Fixes #1236.

Co-authored-by: Karl-VonRichter <[email protected]>
This commit is contained in:
Karl Von RichterandKarl-VonRichter authored and GitHub committed 2026-10-15 01:19:55 -04:00
1 parent faeeff1c64
commit 1ec33fcdc7
19 files changed
+323 -18

No files matched your search

+22 -1
View File
@@ -134,7 +134,7 @@ pub enum Direction {
#[default]
Positive,
Negative,
/// Both sides; `distance` per side.
/// Both sides; the extent convention determines whether `distance` is total or per side.
Symmetric,
}
@@ -186,12 +186,33 @@ pub enum RevolveConvention {
SketchNormal,
}
/// Symmetric distance semantics. Missing markers preserve existing designs' thickness.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExtrudeConvention {
#[default]
LegacyPerSide,
TotalDistance,
}
impl ExtrudeConvention {
/// Distance from the mid-plane to either cap for a distance-limited extent.
pub fn symmetric_half_distance(self, distance: f64) -> f64 {
match self {
Self::LegacyPerSide => distance,
Self::TotalDistance => distance * 0.5,
}
}
}
/// Extent of an extrude.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, solidcraft_derive::Walk)]
pub struct Extent {
#[walk(length = "Distance")]
pub distance: String,
#[serde(default)]
pub convention: ExtrudeConvention,
#[serde(default)]
pub direction: Direction,
/// Optional second side distance (two-sided extrude), along the negative normal.
#[serde(default, skip_serializing_if = "Option::is_none")]
+5 -1
View File
@@ -3291,7 +3291,11 @@ pub(crate) fn extent_sides(vals: &BTreeMap<String, Value>, extent: &crate::Exten
(Some(d2), _) => (-val(vals, d2, Kind::Length)?, d),
(None, crate::Direction::Positive) => (0.0, d),
(None, crate::Direction::Negative) => (-d, 0.0),
(None, crate::Direction::Symmetric) => (-d, d),
(None, crate::Direction::Symmetric) => {
// Through All is a reach computed from target bodies, not an entered total.
let half = if extent.through_all { d } else { extent.convention.symmetric_half_distance(d) };
(-half, half)
}
})
}
+6
View File
@@ -179,6 +179,11 @@
//! neighbouring edges as they are (`neighbours: false`). An Extend without them builds the
//! planar extension it always did and saves as before; 0.11.0 asks for an update instead of
//! dropping them and extending a plane by the distance.
//! Under the same version, an extrude's extent records how a symmetric `distance` is read
//! (`convention`: `total_distance`, split evenly around the start plane, which new solid
//! extrudes save; or `legacy_per_side`). An extent without it keeps the per-side reading
//! designs always had, so their thickness and parameters don't change; 0.11.0 asks for an
//! update instead of doubling a total-distance extrude.
use serde_json::Value;
@@ -1131,6 +1136,7 @@ mod tests {
sketch,
profiles: ProfileSel::All,
extent: Extent {
convention: crate::ExtrudeConvention::LegacyPerSide,
distance: "10".into(),
direction: Direction::Negative,
distance2: None,
+1
View File
@@ -427,6 +427,7 @@ mod tests {
sketch: if missing_sketch { 999_999 } else { id },
profiles: ProfileSel::OffsetBoundaries { associations: vec!["deleted-o1".into()] },
extent: crate::Extent {
convention: crate::ExtrudeConvention::LegacyPerSide,
distance: "2".into(),
direction: crate::Direction::Positive,
distance2: None,
+2
View File
@@ -312,6 +312,7 @@ mod tests {
sketch: feature,
profiles: selection.clone(),
extent: crate::Extent {
convention: crate::ExtrudeConvention::LegacyPerSide,
distance: "2".into(),
direction: crate::Direction::Positive,
distance2: None,
@@ -438,6 +439,7 @@ mod tests {
sketch: feature,
profiles: selection.clone(),
extent: crate::Extent {
convention: crate::ExtrudeConvention::LegacyPerSide,
distance: "2".into(),
direction: crate::Direction::Positive,
distance2: None,
+2
View File
@@ -33,6 +33,7 @@ fn plate_doc() -> (Document, u64) {
sketch: s,
profiles: ProfileSel::All,
extent: Extent {
convention: crate::ExtrudeConvention::LegacyPerSide,
distance: "20".into(),
direction: Direction::Positive,
distance2: None,
@@ -125,6 +126,7 @@ fn box_fillet_cut_timeline() {
sketch: s2,
profiles: ProfileSel::Points { points: vec![Vec2::new(20.0, 15.0)] },
extent: Extent {
convention: crate::ExtrudeConvention::LegacyPerSide,
distance: "20".into(),
direction: Direction::Positive,
distance2: None,
+1
View File
@@ -53,6 +53,7 @@ impl Walk for crate::document::ThinSide {}
impl Walk for crate::document::ExtendMode {}
impl Walk for crate::document::RuledMode {}
impl Walk for crate::document::RuledCorners {}
impl Walk for crate::document::ExtrudeConvention {}
/// Containers walk what they hold, in order.
macro_rules! walk_each {