mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
Preferences: symmetric surface extrudes read only the total distance
The Design preference surface_extrude_symmetric_per_side, which gave new symmetric surface extrudes their distance on each side, is removed with its Surfaces section, its session field and its labels. A symmetric surface.extrude given no convention splits the total around the start plane, as solid.extrude does; timeline.edit and timeline.redefine give a one-sided extrude made symmetric the same. default_extrude_convention, which only existed to read the preference, goes too. An explicit `convention: legacy_per_side` and the marker saved designs carry are unchanged. A saved preferences file that carries the key loads as before (the prefs test now covers both removed keys). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"prefs.surface_extrude_symmetric_per_side": "Symmetric surface extrudes go the distance each way (10 mm: 20 mm in all), as before, not the total like solid extrudes",
|
||||
"prefs.surfaces": "Surfaces"
|
||||
},
|
||||
"ar": {
|
||||
"prefs.surface_extrude_symmetric_per_side": "يمتد بثق السطح المتماثل بالمسافة في كل جانب كما كان من قبل (10 مم: 20 مم إجمالًا)، لا بالمسافة الكلية كبثق المجسم",
|
||||
"prefs.surfaces": "الأسطح"
|
||||
},
|
||||
"he": {
|
||||
"prefs.surface_extrude_symmetric_per_side": "שליפת משטח סימטרית נשלפת את המרחק לכל צד כמו קודם (10 מ\"מ: 20 מ\"מ בסך הכול), ולא את המרחק הכולל כמו שליפת גוף",
|
||||
"prefs.surfaces": "משטחים"
|
||||
},
|
||||
"ja": {
|
||||
"prefs.surface_extrude_symmetric_per_side": "対称のサーフェス押し出しを、ソリッドの押し出しのような合計ではなく、以前のように両側にそれぞれその距離だけ押し出す(10 mm: 合計 20 mm)",
|
||||
"prefs.surfaces": "サーフェス"
|
||||
},
|
||||
"zh": {
|
||||
"prefs.surface_extrude_symmetric_per_side": "对称曲面拉伸像以前一样向两侧各拉伸该距离(10 mm:共 20 mm),而非像实体拉伸那样取总距离",
|
||||
"prefs.surfaces": "曲面"
|
||||
},
|
||||
"de": {
|
||||
"prefs.surface_extrude_symmetric_per_side": "Symmetrische Flächen-Extrusionen wie bisher um den Abstand nach jeder Seite (10 mm: insgesamt 20 mm), nicht um die Gesamtlänge wie Volumen-Extrusionen",
|
||||
"prefs.surfaces": "Flächen"
|
||||
},
|
||||
"ko": {
|
||||
"prefs.surface_extrude_symmetric_per_side": "대칭 서피스 돌출을 솔리드 돌출처럼 전체 거리가 아니라 이전처럼 양쪽으로 각각 그 거리만큼 돌출 (10 mm: 총 20 mm)",
|
||||
"prefs.surfaces": "서피스"
|
||||
}
|
||||
}
|
||||
@@ -1051,14 +1051,13 @@ fn edit_feature(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
nf.id = f.id;
|
||||
// A one-sided extrude made symmetric takes the convention a new one would: its old marker
|
||||
// never read a symmetric distance.
|
||||
let maker = if matches!(f.kind, FeatureKind::SurfaceExtrude { .. }) { "surface.extrude" } else { "solid.extrude" };
|
||||
if let (FeatureKind::Extrude { extent: old, .. }, FeatureKind::Extrude { extent: new, .. })
|
||||
| (FeatureKind::SurfaceExtrude { extent: old, .. }, FeatureKind::SurfaceExtrude { extent: new, .. }) = (&f.kind, &mut nf.kind)
|
||||
&& old.direction != solidcraft_doc::Direction::Symmetric
|
||||
&& new.direction == solidcraft_doc::Direction::Symmetric
|
||||
&& set.get("extent").and_then(|e| e.get("convention")).is_none()
|
||||
{
|
||||
new.convention = super::features::default_extrude_convention(s, maker);
|
||||
new.convention = solidcraft_doc::ExtrudeConvention::TotalDistance;
|
||||
}
|
||||
if std::mem::discriminant(&nf.kind) != std::mem::discriminant(&f.kind) {
|
||||
return Err(bad(cmd, "the feature type cannot change"));
|
||||
@@ -1419,7 +1418,7 @@ fn redefine(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let convention = if extent.direction == solidcraft_doc::Direction::Symmetric {
|
||||
extent.convention
|
||||
} else {
|
||||
super::features::default_extrude_convention(s, cid)
|
||||
solidcraft_doc::ExtrudeConvention::TotalDistance
|
||||
};
|
||||
parameters.entry("convention").or_insert_with(|| json!(convention));
|
||||
}
|
||||
|
||||
@@ -193,34 +193,6 @@ fn older_symmetric_surface_extrudes_keep_the_distance_on_each_side() {
|
||||
}
|
||||
}
|
||||
|
||||
/// The preference brings back the per-side reading for new surface extrudes only: solid
|
||||
/// extrudes and an explicit `convention` are unchanged by it.
|
||||
#[test]
|
||||
fn the_per_side_preference_keeps_the_older_surface_extrude_reading() {
|
||||
let mut s = fixture(json!({}));
|
||||
s.surface_extrude_symmetric_per_side = true;
|
||||
run(&mut s, "surface.extrude", json!({"sketch":"Square","distance":"10 mm","direction":"symmetric","name":"Sheet","body_name":"Sheet"}));
|
||||
assert_z(&s, "Sheet", -10.0, 10.0);
|
||||
let FeatureKind::SurfaceExtrude { extent, .. } = &s.doc.find_feature("Sheet").unwrap().kind else { panic!("surface extrude") };
|
||||
assert_eq!(extent.convention, ExtrudeConvention::LegacyPerSide);
|
||||
run(
|
||||
&mut s,
|
||||
"surface.extrude",
|
||||
json!({"sketch":"Square","distance":"10 mm","direction":"symmetric","convention":"total_distance","name":"Total","body_name":"Total"}),
|
||||
);
|
||||
assert_z(&s, "Total", -5.0, 5.0);
|
||||
run(&mut s, "solid.extrude", json!({"sketch":"Square","distance":"10 mm","direction":"symmetric","operation":"new","body_name":"Solid"}));
|
||||
assert_z(&s, "Solid", -5.0, 5.0);
|
||||
// The saved marker, not the preference, decides on reopening.
|
||||
s.surface_extrude_symmetric_per_side = false;
|
||||
let reopened = Session::new(Document::from_json(&s.doc.to_json()).unwrap());
|
||||
assert_z(&reopened, "Sheet", -10.0, 10.0);
|
||||
// A design opened or started keeps the user's choice.
|
||||
s.surface_extrude_symmetric_per_side = true;
|
||||
s.replace_design(Session::default());
|
||||
assert!(s.surface_extrude_symmetric_per_side);
|
||||
}
|
||||
|
||||
/// A second distance goes the other way from the first side (#2700): along the normal when the
|
||||
/// first side goes against it, whether by `direction: negative` or by a negative distance.
|
||||
#[test]
|
||||
|
||||
@@ -1120,18 +1120,6 @@ fn thin_regions(s: &Session, ss: &solidcraft_doc::SolvedSketch, thin: &ThinWall)
|
||||
solidcraft_sketch::thin_wall_regions(&curves, inside, outside).map_err(|e| format!("thin wall: {e}"))
|
||||
}
|
||||
|
||||
/// How a new extent of `cmd` (`solid.extrude` or `surface.extrude`) reads a symmetric distance
|
||||
/// when the call doesn't say: the total, split evenly around the start plane, for both. A surface
|
||||
/// extrude reads it on each side, as surface extrudes did before, while the user's preference
|
||||
/// says so ([`Session::surface_extrude_symmetric_per_side`]).
|
||||
pub(crate) fn default_extrude_convention(s: &Session, cmd: &str) -> solidcraft_doc::ExtrudeConvention {
|
||||
if cmd == "surface.extrude" && s.surface_extrude_symmetric_per_side {
|
||||
solidcraft_doc::ExtrudeConvention::LegacyPerSide
|
||||
} else {
|
||||
solidcraft_doc::ExtrudeConvention::TotalDistance
|
||||
}
|
||||
}
|
||||
|
||||
/// An extrude's extent from its parameters: distance (or through all, or to an object), the
|
||||
/// side(s), a second distance, a start offset, a taper, and from an object.
|
||||
pub(super) fn extent_param(s: &Session, p: &Value, cmd: &str) -> Result<Extent> {
|
||||
@@ -1167,8 +1155,10 @@ pub(super) fn extent_param(s: &Session, p: &Value, cmd: &str) -> Result<Extent>
|
||||
if let Some(d) = &start_offset {
|
||||
check_expr(s, d, Kind::Length, cmd, "start_offset")?;
|
||||
}
|
||||
// A symmetric distance the call doesn't qualify is the total, split evenly around the start
|
||||
// plane, for solid and surface extrudes alike.
|
||||
let convention = match p.get("convention") {
|
||||
None => default_extrude_convention(s, cmd),
|
||||
None => solidcraft_doc::ExtrudeConvention::TotalDistance,
|
||||
Some(value) => serde_json::from_value(value.clone()).map_err(|_| bad(cmd, "`convention` must be total_distance or legacy_per_side"))?,
|
||||
};
|
||||
Ok(Extent {
|
||||
|
||||
@@ -39,7 +39,7 @@ pub static COMMANDS: &[CommandSpec] = &[
|
||||
CommandSpec::new("surface.extrude", "Surface Extrude", surface_extrude)
|
||||
.at("SURFACE", "CREATE")
|
||||
.icon("surface_extrude")
|
||||
.params("sketch?: id|name; curves?: [curve ids] (open or closed chains of curves meeting end to end) and/or profiles? (their boundaries; the default when no curves are given); distance: expr; direction?: positive|negative|symmetric (symmetric: distance is the total, split evenly around the start plane, as solid.extrude); convention?: total_distance|legacy_per_side (as solid.extrude; default total_distance, or legacy_per_side, the distance on each side, under the user's preference for the older reading); distance2?: expr; start_offset?: expr; name?, body_name? (a surface body: walls only, no caps)"),
|
||||
.params("sketch?: id|name; curves?: [curve ids] (open or closed chains of curves meeting end to end) and/or profiles? (their boundaries; the default when no curves are given); distance: expr; direction?: positive|negative|symmetric (symmetric: distance is the total, split evenly around the start plane, as solid.extrude); convention?: total_distance|legacy_per_side (as solid.extrude; default total_distance; legacy_per_side: the distance on each side, as older symmetric surface extrudes keep); distance2?: expr; start_offset?: expr; name?, body_name? (a surface body: walls only, no caps)"),
|
||||
CommandSpec::new("surface.revolve", "Surface Revolve", surface_revolve)
|
||||
.at("SURFACE", "CREATE")
|
||||
.icon("surface_revolve")
|
||||
|
||||
@@ -377,10 +377,6 @@ pub struct Session {
|
||||
pub(crate) followed: Option<cmd::inspect::Followed>,
|
||||
/// Project a face's edges into a sketch created on it (the default; `sketch.auto_project`).
|
||||
pub auto_project: bool,
|
||||
/// A new symmetric surface extrude given no `convention` reads its distance on each side, as
|
||||
/// surface extrudes did before they matched solid ones (a user preference); false: the total,
|
||||
/// split evenly around the start plane, as a solid extrude reads it.
|
||||
pub surface_extrude_symmetric_per_side: bool,
|
||||
/// How calls that break a command's parameter contract are treated (off by default). It
|
||||
/// belongs to the session, not the design, so it survives opening and starting designs.
|
||||
pub param_check: schema::ParamCheck,
|
||||
@@ -648,7 +644,6 @@ impl Session {
|
||||
document_id: cmd::inspect::new_document_id(),
|
||||
followed: None,
|
||||
auto_project: true,
|
||||
surface_extrude_symmetric_per_side: false,
|
||||
param_check: schema::ParamCheck::Off,
|
||||
easyprint_upload: None,
|
||||
live_links: None,
|
||||
@@ -698,7 +693,6 @@ impl Session {
|
||||
pub fn replace_design(&mut self, fresh: Session) {
|
||||
let auto_project = self.auto_project;
|
||||
let validation = self.validation();
|
||||
let surface_extrude_symmetric_per_side = self.surface_extrude_symmetric_per_side;
|
||||
let param_check = self.param_check;
|
||||
let easyprint_upload = self.easyprint_upload.take();
|
||||
let render_viewport = self.render_viewport.clone();
|
||||
@@ -717,7 +711,6 @@ impl Session {
|
||||
// The user's, like `auto_project`: the commands that open a design evaluate it under it
|
||||
// already (`Session::opened_validating`).
|
||||
self.set_validation(validation);
|
||||
self.surface_extrude_symmetric_per_side = surface_extrude_symmetric_per_side;
|
||||
self.param_check = param_check;
|
||||
self.easyprint_upload = easyprint_upload;
|
||||
self.render_viewport = render_viewport;
|
||||
@@ -1474,7 +1467,6 @@ impl Session {
|
||||
document_id: self.document_id.clone(),
|
||||
followed: None,
|
||||
auto_project: self.auto_project,
|
||||
surface_extrude_symmetric_per_side: self.surface_extrude_symmetric_per_side,
|
||||
param_check: self.param_check,
|
||||
easyprint_upload: self.easyprint_upload.clone(),
|
||||
live_links: None,
|
||||
|
||||
@@ -158,7 +158,6 @@ fn swap_in(app: &mut SolveApp, mut slot: DocSlot) -> DocSlot {
|
||||
put_view(app, slot.view);
|
||||
// Preferences the session holds go with the user, not the tab (#680).
|
||||
app.session.auto_project = out.session.auto_project;
|
||||
app.session.surface_extrude_symmetric_per_side = out.session.surface_extrude_symmetric_per_side;
|
||||
app.session.set_validation(out.session.validation());
|
||||
// So does the host's pre-command check: every tab is held to it.
|
||||
app.session.set_command_check(out.session.command_check());
|
||||
|
||||
@@ -978,9 +978,6 @@ impl SolveApp {
|
||||
if matches!(id, "file.save_mesh" | "file.send_to_slicer") {
|
||||
solidcraft_engine::slicers::set_chosen_programs(&self.preferences.custom_slicers);
|
||||
}
|
||||
// The preference the surface extrude command reads, current even before the next frame
|
||||
// applies it.
|
||||
self.session.surface_extrude_symmetric_per_side = self.preferences.surface_extrude_symmetric_per_side;
|
||||
self.session.set_validation(self.preferences.validation);
|
||||
let t0 = now_ms();
|
||||
let r = self.session.execute(id, ¶ms).map_err(|e| e.to_string());
|
||||
|
||||
@@ -633,10 +633,6 @@ preference_settings! {
|
||||
/// Up/Down and the wheel over a focused value field step its plain number (`value_step`,
|
||||
/// #2320). Off by default: the keys move the text cursor and the wheel scrolls, as before.
|
||||
step_values: bool = false, PAGE_DESIGN => check_id("ui.step_values");
|
||||
/// New symmetric surface extrudes go their distance each way, as before they matched solid
|
||||
/// extrudes; off by default: the distance is the total, split evenly around the start plane.
|
||||
/// Saved designs keep the reading they stored either way.
|
||||
surface_extrude_symmetric_per_side: bool = false, PAGE_DESIGN => check_id("prefs.surface_extrude_symmetric_per_side");
|
||||
/// When SMLib's full check runs while features are computed (`diagnostics.validation`):
|
||||
/// `each_feature` (the default) checks the bodies each feature keeps once; `each_step`, as
|
||||
/// before, checks after every sweep, boolean and blend; `off` (experimental) never does.
|
||||
@@ -1126,8 +1122,6 @@ fn defaults(app: &mut SolveApp, page: usize) {
|
||||
/// the interface scale.
|
||||
pub fn apply(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
apply_network(&app.preferences);
|
||||
// Surface Extrude reads its symmetric reading from the session.
|
||||
app.session.surface_extrude_symmetric_per_side = app.preferences.surface_extrude_symmetric_per_side;
|
||||
// The features computed from now on are checked as the preference says.
|
||||
app.session.set_validation(app.preferences.validation);
|
||||
// The profiler records while the preference is on (switched when it changes; at start-up only
|
||||
@@ -2460,39 +2454,13 @@ mod tests {
|
||||
#[test]
|
||||
fn saved_preferences_with_a_removed_setting_still_load() {
|
||||
use serde_json::json;
|
||||
let saved = json!({"length_decimals": 4, "metric_tap_drill_major_less_pitch": true});
|
||||
let p = serde_json::from_value::<super::Prefs>(saved.clone()).unwrap();
|
||||
assert_eq!(p.length_decimals, 4);
|
||||
assert_eq!(super::lenient(&saved).length_decimals, 4);
|
||||
assert!(serde_json::to_value(&p).unwrap().get("metric_tap_drill_major_less_pitch").is_none(), "not written back");
|
||||
}
|
||||
|
||||
/// New symmetric surface extrudes read their distance as the total, as solid extrudes do; the
|
||||
/// preference brings back the distance on each side they took before. It reaches the command
|
||||
/// whether set through the control channel or the Preferences page, and survives a new design.
|
||||
#[test]
|
||||
fn the_surface_extrude_preference_keeps_the_distance_on_each_side() {
|
||||
use serde_json::json;
|
||||
let height = |h: &mut crate::scenario::Harness, name: &str| {
|
||||
h.app.run("sketch.create", json!({"plane":"XY"})).unwrap();
|
||||
h.app.run("sketch.rectangle.two_point", json!({"p0":[0,0],"p1":[10,10]})).unwrap();
|
||||
h.app.run("sketch.finish", json!({})).unwrap();
|
||||
h.app.run("surface.extrude", json!({"distance":"10 mm","direction":"symmetric","body_name":name})).unwrap();
|
||||
let st = h.app.session.world_state().unwrap();
|
||||
let b = st.body(name).unwrap().mesh().bounds();
|
||||
b.max.z - b.min.z
|
||||
};
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.frames(1);
|
||||
assert!(!h.app.preferences.surface_extrude_symmetric_per_side, "off by default");
|
||||
assert!((height(&mut h, "Total") - 10.0).abs() < 1e-7);
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.call("ui.prefs", json!({"surface_extrude_symmetric_per_side": true}));
|
||||
h.frames(2);
|
||||
assert!(h.app.session.surface_extrude_symmetric_per_side);
|
||||
assert!((height(&mut h, "Each") - 20.0).abs() < 1e-7);
|
||||
h.app.run("file.new", json!({})).unwrap();
|
||||
assert!(h.app.session.surface_extrude_symmetric_per_side);
|
||||
for removed in ["metric_tap_drill_major_less_pitch", "surface_extrude_symmetric_per_side"] {
|
||||
let saved = json!({"length_decimals": 4, removed: true});
|
||||
let p = serde_json::from_value::<super::Prefs>(saved.clone()).unwrap();
|
||||
assert_eq!(p.length_decimals, 4, "{removed}");
|
||||
assert_eq!(super::lenient(&saved).length_decimals, 4, "{removed}");
|
||||
assert!(serde_json::to_value(&p).unwrap().get(removed).is_none(), "{removed} is not written back");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -36,10 +36,6 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let step = setting::step_values(app, ui).on_hover_text(tip);
|
||||
crate::scenario::publish_handle("prefs:step_values", step.rect.center());
|
||||
});
|
||||
ui_kit::settings_section(ui, "surfaces", app.tr("prefs.surfaces"), |ui| {
|
||||
let check = setting::surface_extrude_symmetric_per_side(app, ui);
|
||||
crate::scenario::publish_handle("prefs:surface_extrude_symmetric_per_side", check.rect.center());
|
||||
});
|
||||
// When SMLib's full check runs while features are computed (`diagnostics.validation`).
|
||||
ui_kit::settings_section(ui, "validation", app.tr("prefs.validation"), |ui| {
|
||||
use solidcraft_engine::kernel::Validation;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"sources": [],
|
||||
"what": "a line extruded symmetric 10 as a surface with no convention: the total, 5 each side of the sketch plane, as for a solid extrude (#9091)",
|
||||
"source": "analytic",
|
||||
"method": "surface.extrude without `convention` reads the distance as the total (default_extrude_convention): the plane y = 0, x 0..10, z -5..5, area 100, 4 free edges",
|
||||
"method": "surface.extrude without `convention` reads the distance as the total: the plane y = 0, x 0..10, z -5..5, area 100, 4 free edges",
|
||||
"valid": false,
|
||||
"body_count": 1,
|
||||
"faces": 1,
|
||||
|
||||
Reference in New Issue
Block a user