mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd45fc5fe6 |
@@ -1346,6 +1346,10 @@
|
||||
"ui.newSketchGroup": "New Sketch Group",
|
||||
"ui.new_body": "New Body",
|
||||
"ui.new_design": "New Design",
|
||||
"ui.new_extrude_distance": "New extrude distance",
|
||||
"ui.new_extrude_distance_fixed": "Fixed (10 mm)",
|
||||
"ui.new_extrude_distance_last_used": "Last used",
|
||||
"ui.new_extrude_distance_tip": "The distance a new Extrude starts with. Editing an existing extrude keeps its own.",
|
||||
"ui.no_slicer_found": "No slicer found",
|
||||
"ui.normal": "Normal",
|
||||
"ui.normalConstruction": "Normal / Construction",
|
||||
|
||||
@@ -1346,6 +1346,10 @@
|
||||
"ui.newSketchGroup": "新しいスケッチグループ",
|
||||
"ui.new_body": "新規ボディ",
|
||||
"ui.new_design": "新規デザイン",
|
||||
"ui.new_extrude_distance": "新規押し出しの距離",
|
||||
"ui.new_extrude_distance_fixed": "固定 (10 mm)",
|
||||
"ui.new_extrude_distance_last_used": "前回の値",
|
||||
"ui.new_extrude_distance_tip": "新しい押し出しの開始距離です。既存の押し出しを編集するときは、その押し出しの値が使われます。",
|
||||
"ui.no_slicer_found": "スライサーが見つかりません",
|
||||
"ui.normal": "標準",
|
||||
"ui.normalConstruction": "通常 / コンストラクション",
|
||||
|
||||
@@ -1346,6 +1346,10 @@
|
||||
"ui.newSketchGroup": "新建草图组",
|
||||
"ui.new_body": "新建实体",
|
||||
"ui.new_design": "新建设计",
|
||||
"ui.new_extrude_distance": "新建拉伸距离",
|
||||
"ui.new_extrude_distance_fixed": "固定 (10 mm)",
|
||||
"ui.new_extrude_distance_last_used": "上次使用",
|
||||
"ui.new_extrude_distance_tip": "新建拉伸的起始距离。编辑已有拉伸时保留其自身的值。",
|
||||
"ui.no_slicer_found": "未找到切片软件",
|
||||
"ui.normal": "法向",
|
||||
"ui.normalConstruction": "普通 / 构造",
|
||||
|
||||
@@ -463,7 +463,7 @@ impl Dialog {
|
||||
"sketch.create" => Dialog::new(Kind::Sketch, vec![SelInput::new("Plane", PLANES | PLANAR_FACES, false)]),
|
||||
"solid.extrude" => Dialog::new(
|
||||
Kind::Extrude {
|
||||
distance: "10 mm".into(),
|
||||
distance: crate::prefs::extrude_start_distance(app),
|
||||
direction: 0,
|
||||
operation: usize::from(has_bodies),
|
||||
auto_op: true,
|
||||
@@ -3616,6 +3616,7 @@ pub(crate) fn run_dialog(app: &mut SolveApp, d: &Dialog) -> Result<(), String> {
|
||||
return crate::dialogs_save_mesh::run(app, k, &d.inputs);
|
||||
}
|
||||
let commands = apply_commands(app, d)?;
|
||||
let new_extrude = remembered_extrude_distance(app, d);
|
||||
if let Some((_, marker)) = d.editing {
|
||||
// Record the actual pre-edit timeline state in the command's undo snapshot.
|
||||
roll_marker(app, marker)?;
|
||||
@@ -3639,9 +3640,23 @@ pub(crate) fn run_dialog(app: &mut SolveApp, d: &Dialog) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(distance) = new_extrude {
|
||||
app.preferences.extrude_last_distance = distance;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The distance a new (not edited) extrude was committed with, in mm, for the "Last used"
|
||||
/// starting distance; none when the extent isn't a plain distance.
|
||||
pub(crate) fn remembered_extrude_distance(app: &SolveApp, d: &Dialog) -> Option<String> {
|
||||
let Kind::Extrude { distance, all, .. } = &d.kind else { return None };
|
||||
if d.editing.is_some() || *all || to_object_on(&d.inputs) {
|
||||
return None;
|
||||
}
|
||||
let v = app.session.doc.eval(distance, ValueKind::Length).ok()?;
|
||||
(v.is_finite() && v.abs() > 1e-9).then(|| format!("{v} mm"))
|
||||
}
|
||||
|
||||
/// Placement used to edit a definition in the selected authoring occurrence.
|
||||
pub(crate) fn feature_frame(s: &Session, component: u64) -> solidcraft_engine::doc::Mat {
|
||||
if component == s.active_component { s.active_component_frame() } else { s.placed_document().component_transform(component) }
|
||||
|
||||
@@ -251,6 +251,22 @@ pub enum OrbitAround {
|
||||
Cursor,
|
||||
}
|
||||
|
||||
/// Where a new Extrude dialog starts its distance (Preferences: "New extrude distance").
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExtrudeStart {
|
||||
/// 10 mm, as it always was.
|
||||
#[default]
|
||||
Fixed,
|
||||
/// The distance of the last extrude that was committed.
|
||||
LastUsed,
|
||||
/// The value typed in `extrude_custom_distance`.
|
||||
Custom,
|
||||
}
|
||||
|
||||
/// The distance the Extrude dialog opened at before this was a preference.
|
||||
pub const DEFAULT_EXTRUDE_DISTANCE: &str = "10 mm";
|
||||
|
||||
/// The browser sidebar's first width, and the width [`Prefs::default`] restores; the panel
|
||||
/// itself clamps what the user drags to its own range.
|
||||
pub const DEFAULT_BROWSER_WIDTH: f32 = 250.0;
|
||||
@@ -336,6 +352,12 @@ pub struct Prefs {
|
||||
pub smooth_wheel_zoom: bool,
|
||||
/// Two-finger trackpad scroll pans (off: it zooms like the wheel); pinch always zooms.
|
||||
pub trackpad_pan: bool,
|
||||
/// Where a new Extrude dialog starts its distance (editing an extrude keeps its own).
|
||||
pub extrude_start: ExtrudeStart,
|
||||
/// The distance a new Extrude starts at when `extrude_start` is `custom` (a length expression).
|
||||
pub extrude_custom_distance: String,
|
||||
/// The last distance a new Extrude was committed with, in mm (empty until one is).
|
||||
pub extrude_last_distance: String,
|
||||
/// Horizontal trackpad and thumb-wheel scrolling pans the view the other way (#2007).
|
||||
pub pan_reverse_x: bool,
|
||||
/// View changes (axis view, Look At, Home) animate; `look_at_speed` scales how fast.
|
||||
@@ -459,6 +481,9 @@ impl Default for Prefs {
|
||||
smooth_wheel_zoom: true,
|
||||
trackpad_pan: true,
|
||||
pan_reverse_x: false,
|
||||
extrude_start: ExtrudeStart::Fixed,
|
||||
extrude_custom_distance: DEFAULT_EXTRUDE_DISTANCE.into(),
|
||||
extrude_last_distance: String::new(),
|
||||
look_at_anim: true,
|
||||
look_at_speed: 1.0,
|
||||
up_axis: "z".into(),
|
||||
@@ -757,6 +782,8 @@ fn defaults(app: &mut SolveApp, page: usize) {
|
||||
PAGE_DESIGN => {
|
||||
p.up_axis = d.up_axis;
|
||||
p.offer_stand_up = d.offer_stand_up;
|
||||
p.extrude_start = d.extrude_start;
|
||||
p.extrude_custom_distance = d.extrude_custom_distance;
|
||||
}
|
||||
PAGE_GRAPHICS => {
|
||||
let msaa = p.msaa != d.msaa;
|
||||
@@ -856,6 +883,19 @@ pub fn nav_mode(app: &SolveApp, button: NavButton, mods: Modifiers) -> Option<Na
|
||||
nav_preset(&app.preferences.nav).mode(button, mods)
|
||||
}
|
||||
|
||||
/// The distance a new Extrude dialog opens with. Anything unusable (no extrude yet, a custom
|
||||
/// value that isn't a length) falls back to the fixed 10 mm.
|
||||
pub fn extrude_start_distance(app: &SolveApp) -> String {
|
||||
let p = &app.preferences;
|
||||
let wanted = match p.extrude_start {
|
||||
ExtrudeStart::Fixed => return DEFAULT_EXTRUDE_DISTANCE.into(),
|
||||
ExtrudeStart::LastUsed => p.extrude_last_distance.as_str(),
|
||||
ExtrudeStart::Custom => p.extrude_custom_distance.as_str(),
|
||||
};
|
||||
let ok = app.session.doc.eval(wanted, solidcraft_engine::doc::expr::Kind::Length).is_ok_and(|v| v.is_finite() && v.abs() > 1e-9);
|
||||
if ok { wanted.trim().to_string() } else { DEFAULT_EXTRUDE_DISTANCE.into() }
|
||||
}
|
||||
|
||||
/// +1, or −1 when horizontal scrolling pans the other way (#2007).
|
||||
pub fn pan_sign_x(app: &SolveApp) -> f32 {
|
||||
if app.preferences.pan_reverse_x { -1.0 } else { 1.0 }
|
||||
@@ -1365,6 +1405,18 @@ fn page(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
});
|
||||
ui.end_row();
|
||||
note(ui, crate::i18n::tr_label("Which axis points up in the views (axis view, Home, Look At). Designs are unchanged."));
|
||||
ui.label(crate::i18n::tr_label("New extrude distance"));
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.radio_value(&mut app.preferences.extrude_start, ExtrudeStart::Fixed, crate::i18n::tr_label("Fixed (10 mm)"));
|
||||
ui.radio_value(&mut app.preferences.extrude_start, ExtrudeStart::LastUsed, crate::i18n::tr_label("Last used"));
|
||||
ui.radio_value(&mut app.preferences.extrude_start, ExtrudeStart::Custom, crate::i18n::tr_label("Custom"));
|
||||
ui.add_enabled(
|
||||
app.preferences.extrude_start == ExtrudeStart::Custom,
|
||||
egui::TextEdit::singleline(&mut app.preferences.extrude_custom_distance).desired_width(80.0),
|
||||
);
|
||||
});
|
||||
ui.end_row();
|
||||
note(ui, crate::i18n::tr_label("The distance a new Extrude starts with. Editing an existing extrude keeps its own."));
|
||||
ui.label(crate::i18n::tr_label("Offer to stand up Y-up files"));
|
||||
ui.checkbox(&mut app.preferences.offer_stand_up, "")
|
||||
.on_hover_text(crate::i18n::tr_label("After a STEP or IGES file opens with Z up, offer a quarter turn about X"));
|
||||
@@ -2154,6 +2206,50 @@ mod tests {
|
||||
assert_eq!(other.preferences.nav, OPENSCAD_NAV);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extrude_start_distance_has_three_modes_and_defaults_to_ten_mm() {
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
assert_eq!(serde_json::from_value::<Prefs>(json!({})).unwrap().extrude_start, ExtrudeStart::Fixed);
|
||||
let opened = |app: &SolveApp| match crate::dialogs::Dialog::for_command(app, "solid.extrude").unwrap().kind {
|
||||
crate::dialogs::Kind::Extrude { distance, .. } => distance,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
assert_eq!(opened(&app), "10 mm", "the default is unchanged");
|
||||
// Fixed ignores whatever was used or typed.
|
||||
set(&mut app, &json!({"extrude_custom_distance": "25 mm", "extrude_last_distance": "7 mm"})).unwrap();
|
||||
assert_eq!(opened(&app), "10 mm");
|
||||
set(&mut app, &json!({"extrude_start": "custom"})).unwrap();
|
||||
assert_eq!(opened(&app), "25 mm");
|
||||
set(&mut app, &json!({"extrude_custom_distance": "nonsense"})).unwrap();
|
||||
assert_eq!(opened(&app), "10 mm", "an unusable custom value falls back");
|
||||
set(&mut app, &json!({"extrude_start": "last_used", "extrude_last_distance": ""})).unwrap();
|
||||
assert_eq!(opened(&app), "10 mm", "nothing used yet");
|
||||
set(&mut app, &json!({"extrude_last_distance": "7 mm"})).unwrap();
|
||||
assert_eq!(opened(&app), "7 mm");
|
||||
let mut other = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
other.load_prefs(&app.prefs());
|
||||
assert_eq!(opened(&other), "7 mm", "persists with the preferences");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_used_remembers_new_extrudes_only() {
|
||||
let app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
let mut d = crate::dialogs::Dialog::for_command(&app, "solid.extrude").unwrap();
|
||||
if let Some((_, _, text)) = d.primary() {
|
||||
*text = "12.5 mm".into();
|
||||
}
|
||||
assert_eq!(crate::dialogs::remembered_extrude_distance(&app, &d).as_deref(), Some("12.5 mm"));
|
||||
if let Some((_, _, text)) = d.primary() {
|
||||
*text = "bogus".into();
|
||||
}
|
||||
assert_eq!(crate::dialogs::remembered_extrude_distance(&app, &d), None, "not a length");
|
||||
if let Some((_, _, text)) = d.primary() {
|
||||
*text = "3 mm".into();
|
||||
}
|
||||
d.editing = Some((1, None));
|
||||
assert_eq!(crate::dialogs::remembered_extrude_distance(&app, &d), None, "editing an extrude doesn't count");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pan_reverse_x_defaults_off_persists_and_flips_the_sign() {
|
||||
assert!(!serde_json::from_value::<Prefs>(json!({})).unwrap().pan_reverse_x);
|
||||
|
||||
@@ -152,7 +152,7 @@ labels; `prefs:measure_axis_distances` identifies the checkbox.
|
||||
| `ui.home` | `open?`: bool; `sample?`: index | the start page: show or hide it, open a built-in sample (once built); returns `open` and the recent designs |
|
||||
| `ui.shortcut` | `command`, `key?`, `replace?` | a command's key, or rebinds it (`""` clears; a key another command uses is refused unless `replace`) |
|
||||
| `ui.help` | `item?`: `about`, `shortcuts` (read only), `report` (opens a report draft for review), `crashes` (the Crash Reports window) | the version, commit, build date and diagnostic text |
|
||||
| `ui.prefs` | any of `default_units`, `nav` (`fusion`, `solidworks`, `inventor`, `alias`, `blender`, `tinkercad`, `openscad`, `touchpad`), `zoom_reverse`, `pan_reverse_x` (default false: horizontal scrolling pans the other way), `zoom_to_cursor`, `smooth_wheel_zoom` (default true), `trackpad_pan` (default true: two-finger scroll pans, except Touchpad always zooms; pinch zooms), `orbit_around` (`view_centre` (default: what Pan moves and Fit frames), `origin` (the world origin stays still on screen), `cursor` (the model point under the pointer when the drag began); the older `orbit_cursor` boolean still sets `cursor` when true), `look_at_anim`, `look_at_speed`, `middle_double_click` (`home` (default), `fit`, `off`), `msaa`, `show_frame_rate` (default false: frames per second in the view's bottom-right corner; the view redraws every frame while it is on), `length_decimals`, `angle_decimals`, `theme` (`dark`, `light`, `system` or a theme file's id: a built-in one such as `gruvbox-dark`, or an imported one; the reply's `theme_ids` lists them), `theme_import` (a `solidcraft-theme/1` file's JSON or text: adds the theme and turns it on; the reply's `theme_imported` is its id), `theme_remove` (an imported theme's id), `theme_export: true` (the reply's `theme_file` is the theme on now as a complete file; see [colour themes](book/src/guide/ui-tour.md#colour-themes)), `language` (`en`, `ja`, `de`, `fr`, `ko`, `ms`, `ru`, `uk`, `zh`; see [localization](localization.md)), `ui_scale`, `ui_font` (an installed font family or a font file for the interface; empty for the built-in font), `up_axis` (`z`, `y`), `offer_stand_up`, `color_vision` (`off` (default), `protan`, `deutan`, `tritan`: colour vision modes, #778), `check_updates` (default true; [updates](book/src/guide/updates.md)); `open` (a page: General, Navigation, Keyboard Shortcuts, Design, Graphics, Sketch, Selection, AI) opens the Preferences dialog there; `button`: `ok`, `cancel`, `apply`, `defaults`; `stand_up: true` stands up a just-opened Y-up file, `stand_up: false` leaves it as it is ("Leave it": the offer closes and `offer_stand_up` stays on); the offer's buttons are the handles `stand_up:stand`, `stand_up:leave` and `stand_up:never` | sets preferences (kept between runs), returns them all plus `dialog` (the open page or null), `dark` and `stand_up_offer` |
|
||||
| `ui.prefs` | any of `default_units`, `nav` (`fusion`, `solidworks`, `inventor`, `alias`, `blender`, `tinkercad`, `openscad`, `touchpad`), `zoom_reverse`, `pan_reverse_x` (default false: horizontal scrolling pans the other way), `zoom_to_cursor`, `smooth_wheel_zoom` (default true), `trackpad_pan` (default true: two-finger scroll pans, except Touchpad always zooms; pinch zooms), `extrude_start` (`fixed` (default: a new Extrude opens at 10 mm), `last_used` (the last committed new extrude's distance), `custom` (`extrude_custom_distance`, default `10 mm`); `extrude_last_distance` holds the remembered one), `orbit_around` (`view_centre` (default: what Pan moves and Fit frames), `origin` (the world origin stays still on screen), `cursor` (the model point under the pointer when the drag began); the older `orbit_cursor` boolean still sets `cursor` when true), `look_at_anim`, `look_at_speed`, `middle_double_click` (`home` (default), `fit`, `off`), `msaa`, `show_frame_rate` (default false: frames per second in the view's bottom-right corner; the view redraws every frame while it is on), `length_decimals`, `angle_decimals`, `theme` (`dark`, `light`, `system` or a theme file's id: a built-in one such as `gruvbox-dark`, or an imported one; the reply's `theme_ids` lists them), `theme_import` (a `solidcraft-theme/1` file's JSON or text: adds the theme and turns it on; the reply's `theme_imported` is its id), `theme_remove` (an imported theme's id), `theme_export: true` (the reply's `theme_file` is the theme on now as a complete file; see [colour themes](book/src/guide/ui-tour.md#colour-themes)), `language` (`en`, `ja`, `de`, `fr`, `ko`, `ms`, `ru`, `uk`, `zh`; see [localization](localization.md)), `ui_scale`, `ui_font` (an installed font family or a font file for the interface; empty for the built-in font), `up_axis` (`z`, `y`), `offer_stand_up`, `color_vision` (`off` (default), `protan`, `deutan`, `tritan`: colour vision modes, #778), `check_updates` (default true; [updates](book/src/guide/updates.md)); `open` (a page: General, Navigation, Keyboard Shortcuts, Design, Graphics, Sketch, Selection, AI) opens the Preferences dialog there; `button`: `ok`, `cancel`, `apply`, `defaults`; `stand_up: true` stands up a just-opened Y-up file, `stand_up: false` leaves it as it is ("Leave it": the offer closes and `offer_stand_up` stays on); the offer's buttons are the handles `stand_up:stand`, `stand_up:leave` and `stand_up:never` | sets preferences (kept between runs), returns them all plus `dialog` (the open page or null), `dark` and `stand_up_offer` |
|
||||
| `ui.capture` | `action?`: `capture`, `revert`, `cancel` | answers the Capture Position question (asked when a command starts or the design is saved while components were moved without capturing); without `action`, what waits |
|
||||
| `ui.window` | `action`: minimize, maximize, restore, toggle, close | what the title bar's caption buttons do (on Windows and Linux the application bar is the title bar); close asks about unsaved designs first |
|
||||
| `ui.agent_cursor` | `show` (bool), `speed`: a number from 1 to 5 (times the normal speed) or `"instant"` (no animation: the command runs at once and the pointer jumps to where it acted), `follow_camera` (bool); all optional | the settings. While the cursor is shown, `engine.execute` first glides to the command's toolbar button and to each pick, then runs and replies; later requests wait their turn. `"animate": false` on a call skips it |
|
||||
|
||||
Reference in New Issue
Block a user