Navigation: choose the orbit centre: view centre, origin or point under the cursor (#1377)
Preferences ▸ Navigation ▸ Orbit around now offers View centre (the default, as before), Origin and Point under the cursor. A saved orbit_cursor preference migrates to the cursor option, and the control channel still accepts the old key. Thanks to @kuraneko1 for this. Co-authored-by: kuraneko1 <[email protected]>
This commit is contained in:
1 parent
4b3b48388c
commit
ad30a2350e
6 files changed
+161
-40
No files matched your search
@@ -31,7 +31,7 @@
|
||||
//! - `ui.confirm {accept?}`: the Delete confirmation (without `accept`: what it lists)
|
||||
//! - `ui.shortcut {command, key?, replace?}`: a command's key, or bind one ("" clears)
|
||||
//! - `ui.help {item?: about|shortcuts|report}`: the Help menu; returns the version and diagnostics
|
||||
//! - `ui.prefs {...}`: the preferences (default_units, nav, zoom_reverse, orbit_cursor, msaa,
|
||||
//! - `ui.prefs {...}`: the preferences (default_units, nav, zoom_reverse, orbit_around, msaa,
|
||||
//! length_decimals, angle_decimals); returns them all
|
||||
//! - `ui.capture {action?: capture|revert|cancel}`: the Capture Position question
|
||||
//! - `ui.agent_cursor {show?, speed?: 1–5 (times) | "instant", follow_camera?}`: the agent cursor
|
||||
|
||||
+103
-25
@@ -236,6 +236,19 @@ impl NavigatorStyle {
|
||||
}
|
||||
}
|
||||
|
||||
/// What an orbit turns around (Preferences: "Orbit around").
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum OrbitAround {
|
||||
/// The world origin: the view turns about it and keeps it still on screen.
|
||||
Origin,
|
||||
/// The view's centre: what Pan moves and Fit frames (the default, as it always was).
|
||||
#[default]
|
||||
ViewCentre,
|
||||
/// The model point under the cursor when the drag began.
|
||||
Cursor,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Prefs {
|
||||
@@ -249,7 +262,11 @@ pub struct Prefs {
|
||||
pub radial_context_menu: bool,
|
||||
/// The wheel zooms out when turned forward.
|
||||
pub zoom_reverse: bool,
|
||||
/// Orbit around the point under the cursor (else around the view's centre).
|
||||
/// What an orbit turns around.
|
||||
pub orbit_around: OrbitAround,
|
||||
/// The cursor preference as releases before the origin option saved it (`true` = the point
|
||||
/// under the cursor). [`migrate`] folds it into `orbit_around` when saved preferences are
|
||||
/// read, so an existing choice survives the upgrade; this version only writes `false`.
|
||||
pub orbit_cursor: bool,
|
||||
/// Whether a connected 3D mouse moves the view.
|
||||
pub space_mouse_enabled: bool,
|
||||
@@ -346,6 +363,7 @@ impl Default for Prefs {
|
||||
navigator_style: NavigatorStyle::default(),
|
||||
radial_context_menu: false,
|
||||
zoom_reverse: false,
|
||||
orbit_around: OrbitAround::default(),
|
||||
orbit_cursor: false,
|
||||
space_mouse_enabled: true,
|
||||
space_mouse_speed: 1.0,
|
||||
@@ -482,6 +500,7 @@ fn defaults(app: &mut SolveApp, page: usize) {
|
||||
p.zoom_to_cursor = d.zoom_to_cursor;
|
||||
p.smooth_wheel_zoom = d.smooth_wheel_zoom;
|
||||
p.trackpad_pan = d.trackpad_pan;
|
||||
p.orbit_around = d.orbit_around;
|
||||
p.orbit_cursor = d.orbit_cursor;
|
||||
p.space_mouse_enabled = d.space_mouse_enabled;
|
||||
p.space_mouse_speed = d.space_mouse_speed;
|
||||
@@ -585,25 +604,36 @@ pub fn zoom_sign(app: &SolveApp) -> f64 {
|
||||
if app.preferences.zoom_reverse { -1.0 } else { 1.0 }
|
||||
}
|
||||
|
||||
/// Orbit by pixel deltas: around the view's centre, or, with "orbit around the cursor", around
|
||||
/// the model point that was under the cursor when the drag began (kept still on screen). A drag
|
||||
/// that begins over empty space turns around the centre of the visible geometry instead, so the
|
||||
/// model never swings around a far-off point. The pivot is chosen once, when the drag begins.
|
||||
/// Orbit by pixel deltas about the configured centre: the view's centre (default), the world
|
||||
/// origin, or the model point that was under the cursor when the drag began. The origin and the
|
||||
/// cursor pivot are kept still on screen; a cursor drag that begins over empty space turns around
|
||||
/// the centre of the visible geometry instead, so the model never swings around a far-off point.
|
||||
/// The cursor pivot is chosen once, when the drag begins.
|
||||
pub fn orbit(app: &mut SolveApp, rect: egui::Rect, dx: f64, dy: f64, press: Option<Pos2>) {
|
||||
if !app.preferences.orbit_cursor {
|
||||
let pivot = match app.preferences.orbit_around {
|
||||
OrbitAround::ViewCentre => None,
|
||||
OrbitAround::Origin => Some(Vec3::ZERO),
|
||||
OrbitAround::Cursor => Some(match app.viewport.orbit_pivot {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
let proj = crate::viewport::projection(app, rect);
|
||||
let at = press.unwrap_or(rect.center());
|
||||
let p = pivot_under(app, &proj, at);
|
||||
app.viewport.orbit_pivot = Some(p);
|
||||
p
|
||||
}
|
||||
}),
|
||||
};
|
||||
let Some(pivot) = pivot else {
|
||||
app.cam.orbit(dx, dy);
|
||||
return;
|
||||
}
|
||||
let pivot = match app.viewport.orbit_pivot {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
let proj = crate::viewport::projection(app, rect);
|
||||
let at = press.unwrap_or(rect.center());
|
||||
let p = pivot_under(app, &proj, at);
|
||||
app.viewport.orbit_pivot = Some(p);
|
||||
p
|
||||
}
|
||||
};
|
||||
orbit_about(app, rect, dx, dy, pivot);
|
||||
}
|
||||
|
||||
/// Orbit by pixel deltas, keeping `pivot` still on screen: the camera turns about it and a pan
|
||||
/// correction puts it back where it was.
|
||||
fn orbit_about(app: &mut SolveApp, rect: egui::Rect, dx: f64, dy: f64, pivot: Vec3) {
|
||||
let before = crate::viewport::projection(app, rect).to_screen(pivot);
|
||||
app.cam.orbit(dx, dy);
|
||||
let after = crate::viewport::projection(app, rect).to_screen(pivot);
|
||||
@@ -1050,9 +1080,11 @@ fn page(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
});
|
||||
ui.end_row();
|
||||
ui.label(crate::i18n::tr_label("Orbit around"));
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(&mut app.preferences.orbit_cursor, false, crate::i18n::tr_label("View centre"));
|
||||
ui.radio_value(&mut app.preferences.orbit_cursor, true, crate::i18n::tr_label("Point under the cursor"));
|
||||
// Wrapped: three choices, and German or Russian labels are long.
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.radio_value(&mut app.preferences.orbit_around, OrbitAround::Origin, crate::i18n::tr_label("Origin"));
|
||||
ui.radio_value(&mut app.preferences.orbit_around, OrbitAround::ViewCentre, crate::i18n::tr_label("View centre"));
|
||||
ui.radio_value(&mut app.preferences.orbit_around, OrbitAround::Cursor, crate::i18n::tr_label("Point under the cursor"));
|
||||
});
|
||||
ui.end_row();
|
||||
ui.label(crate::i18n::tr_label("Middle double-click"));
|
||||
@@ -1312,12 +1344,22 @@ pub fn control(app: &mut SolveApp, p: &serde_json::Value) -> Result<serde_json::
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Fold a setting an older release kept in its own field into the one this version reads.
|
||||
/// `orbit_cursor` (`true` = the point under the cursor) became `orbit_around`.
|
||||
pub fn migrate(mut p: Prefs) -> Prefs {
|
||||
if p.orbit_cursor {
|
||||
p.orbit_around = OrbitAround::Cursor;
|
||||
p.orbit_cursor = false;
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
/// Saved preferences, keeping every one that still reads. A value this version doesn't
|
||||
/// understand (a setting a newer or older release saved differently, a hand edit) falls back to
|
||||
/// its default on its own instead of discarding all of them.
|
||||
pub fn lenient(saved: &serde_json::Value) -> Prefs {
|
||||
if let Ok(p) = serde_json::from_value::<Prefs>(saved.clone()) {
|
||||
return p;
|
||||
return migrate(p);
|
||||
}
|
||||
let Ok(mut cur) = serde_json::to_value(Prefs::default()) else { return Prefs::default() };
|
||||
if let (Some(o), Some(src)) = (cur.as_object_mut(), saved.as_object()) {
|
||||
@@ -1331,7 +1373,7 @@ pub fn lenient(saved: &serde_json::Value) -> Prefs {
|
||||
}
|
||||
}
|
||||
}
|
||||
serde_json::from_value(cur).unwrap_or_default()
|
||||
serde_json::from_value(cur).map(migrate).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Set preferences from JSON (control channel); returns them all.
|
||||
@@ -1351,7 +1393,7 @@ pub fn set(app: &mut SolveApp, p: &serde_json::Value) -> Result<serde_json::Valu
|
||||
{
|
||||
return Err("`color_vision` must be off, protan, deutan or tritan".into());
|
||||
}
|
||||
let new: Prefs = serde_json::from_value(cur).map_err(|e| e.to_string())?;
|
||||
let new: Prefs = migrate(serde_json::from_value(cur).map_err(|e| e.to_string())?);
|
||||
if !UNITS.contains(&new.default_units.as_str()) {
|
||||
return Err(format!("units must be one of {}", UNITS.join(", ")));
|
||||
}
|
||||
@@ -1832,11 +1874,47 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Orbiting about the world origin keeps the origin where it was drawn, even when Pan has
|
||||
/// moved it off to the side.
|
||||
#[test]
|
||||
fn orbiting_around_the_origin_keeps_it_still() {
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
app.run("solid.box", json!({"length": 40, "width": 40, "height": 40})).unwrap();
|
||||
let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(800.0, 600.0));
|
||||
app.fit_view();
|
||||
assert_eq!(app.preferences.orbit_around, OrbitAround::ViewCentre, "the view centre stays the default");
|
||||
app.preferences.orbit_around = OrbitAround::Origin;
|
||||
app.cam.pan(60.0, -25.0, 600.0);
|
||||
let was = (app.cam.yaw, app.cam.pitch);
|
||||
let before = crate::viewport::projection(&app, rect).to_screen(Vec3::ZERO).unwrap();
|
||||
orbit(&mut app, rect, 40.0, 25.0, None);
|
||||
let after = crate::viewport::projection(&app, rect).to_screen(Vec3::ZERO).unwrap();
|
||||
assert!(after.distance(before) < 2.0, "{before:?} -> {after:?}");
|
||||
assert_ne!((app.cam.yaw, app.cam.pitch), was, "the view must turn");
|
||||
}
|
||||
|
||||
/// Preferences saved before the origin option had `orbit_cursor`: that choice survives, and
|
||||
/// the control channel still accepts the old key.
|
||||
#[test]
|
||||
fn saved_cursor_preference_migrates_to_the_origin_option() {
|
||||
assert_eq!(lenient(&json!({"orbit_cursor": true})).orbit_around, OrbitAround::Cursor);
|
||||
assert_eq!(lenient(&json!({"orbit_cursor": false})).orbit_around, OrbitAround::ViewCentre);
|
||||
assert_eq!(lenient(&json!({})).orbit_around, OrbitAround::ViewCentre);
|
||||
assert_eq!(lenient(&json!({"orbit_around": "origin"})).orbit_around, OrbitAround::Origin);
|
||||
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
set(&mut app, &json!({"orbit_around": "origin"})).unwrap();
|
||||
assert_eq!(app.preferences.orbit_around, OrbitAround::Origin);
|
||||
set(&mut app, &json!({"orbit_cursor": true})).unwrap();
|
||||
assert_eq!(app.preferences.orbit_around, OrbitAround::Cursor, "the old key still moves the choice");
|
||||
assert!(!app.preferences.orbit_cursor, "and is folded away");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn orbiting_around_the_cursor_keeps_that_point_still() {
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
app.run("solid.box", json!({"length": 40, "width": 40, "height": 40})).unwrap();
|
||||
app.preferences.orbit_cursor = true;
|
||||
app.preferences.orbit_around = OrbitAround::Cursor;
|
||||
let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(800.0, 600.0));
|
||||
app.fit_view();
|
||||
let at = egui::pos2(500.0, 250.0);
|
||||
@@ -1852,7 +1930,7 @@ mod tests {
|
||||
fn orbiting_from_empty_space_turns_around_the_visible_geometry() {
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
app.run("solid.box", json!({"length": 40, "width": 40, "height": 40})).unwrap();
|
||||
app.preferences.orbit_cursor = true;
|
||||
app.preferences.orbit_around = OrbitAround::Cursor;
|
||||
let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(800.0, 600.0));
|
||||
app.fit_view();
|
||||
let corner = egui::pos2(10.0, 10.0);
|
||||
@@ -1865,7 +1943,7 @@ mod tests {
|
||||
assert_eq!(app.viewport.orbit_pivot, Some(centre));
|
||||
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
app.preferences.orbit_cursor = true;
|
||||
app.preferences.orbit_around = OrbitAround::Cursor;
|
||||
app.cam.target = solidcraft_engine::geom::Vec3::new(5.0, -3.0, 2.0);
|
||||
orbit(&mut app, rect, 40.0, 25.0, Some(corner));
|
||||
assert_eq!(app.viewport.orbit_pivot, Some(solidcraft_engine::geom::Vec3::new(5.0, -3.0, 2.0)));
|
||||
|
||||
@@ -2304,16 +2304,22 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
if let Some((mode, delta)) = motion {
|
||||
if delta != egui::Vec2::ZERO {
|
||||
let mut params = json!({"motion":if mode == NavMode::Pan {"pan"} else {"orbit"},"delta":[delta.x,delta.y]});
|
||||
if mode == NavMode::Orbit && app.preferences.orbit_cursor {
|
||||
let pivot = match app.viewport.orbit_pivot {
|
||||
Some(pivot) => pivot,
|
||||
None => {
|
||||
let pivot = crate::prefs::pivot_under(app, &projection(app, rect), hover.unwrap_or(rect.center()) - delta);
|
||||
app.viewport.orbit_pivot = Some(pivot);
|
||||
pivot
|
||||
}
|
||||
if mode == NavMode::Orbit {
|
||||
let pivot = match app.preferences.orbit_around {
|
||||
crate::prefs::OrbitAround::ViewCentre => None,
|
||||
crate::prefs::OrbitAround::Origin => Some(Vec3::ZERO),
|
||||
crate::prefs::OrbitAround::Cursor => Some(match app.viewport.orbit_pivot {
|
||||
Some(pivot) => pivot,
|
||||
None => {
|
||||
let pivot = crate::prefs::pivot_under(app, &projection(app, rect), hover.unwrap_or(rect.center()) - delta);
|
||||
app.viewport.orbit_pivot = Some(pivot);
|
||||
pivot
|
||||
}
|
||||
}),
|
||||
};
|
||||
params["pivot"] = json!(pivot);
|
||||
if let Some(pivot) = pivot {
|
||||
params["pivot"] = json!(pivot);
|
||||
}
|
||||
}
|
||||
camera_motion(app, rect, params);
|
||||
}
|
||||
@@ -3345,7 +3351,17 @@ fn orbit_drag(app: &mut SolveApp, rect: Rect, dx: f64, dy: f64, press: Option<Po
|
||||
target.pitch = previous.target.pitch;
|
||||
}
|
||||
}
|
||||
let pivot = app.viewport.orbit_pivot.filter(|_| around_cursor).and_then(|p| {
|
||||
// The cursor and origin pivots stay still while a snap eases in; the axis gizmo has no
|
||||
// pivot (it orbits about the view centre, see `axis_view`).
|
||||
let pivot = if around_cursor {
|
||||
match app.preferences.orbit_around {
|
||||
crate::prefs::OrbitAround::Origin => Some(Vec3::ZERO),
|
||||
_ => app.viewport.orbit_pivot,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.and_then(|p| {
|
||||
app.cam = displayed;
|
||||
projection(app, rect).to_screen(p).map(|at| (p, at))
|
||||
});
|
||||
@@ -4429,7 +4445,7 @@ mod tests {
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = hover_app(false);
|
||||
app.preferences.nav = "touchpad".into();
|
||||
app.preferences.orbit_cursor = true;
|
||||
app.preferences.orbit_around = crate::prefs::OrbitAround::Cursor;
|
||||
let p = pos2(350.0, 300.0);
|
||||
let alt = egui::Modifiers { alt: true, ..Default::default() };
|
||||
hover_frame(&mut app, &ctx, vec![egui::Event::PointerMoved(p)]);
|
||||
@@ -5499,10 +5515,35 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The origin pivot holds through a Shift drag that snaps to an axis: the spring eases the
|
||||
/// view round without letting the origin slide on screen.
|
||||
#[test]
|
||||
fn snapping_keeps_the_origin_still() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.preferences.orbit_around = crate::prefs::OrbitAround::Origin;
|
||||
h.app.run("solid.box", json!({"length":40,"width":40,"height":40})).unwrap();
|
||||
h.app.fit_view();
|
||||
let rect = h.app.viewport.rect.unwrap();
|
||||
let at = rect.center() + vec2(50.0, -20.0);
|
||||
orbit_drag(&mut h.app, rect, 0.0, 0.0, Some(at), true, true);
|
||||
assert_eq!(
|
||||
h.app.viewport.orbit_snap.as_ref().and_then(|s| s.pivot).map(|(p, _)| p),
|
||||
Some(Vec3::ZERO),
|
||||
"the origin is the pivot while snapping"
|
||||
);
|
||||
let before = projection(&h.app, rect).to_screen(Vec3::ZERO).unwrap();
|
||||
for _ in 0..30 {
|
||||
orbit_drag(&mut h.app, rect, 4.0, 2.0, Some(at), true, true);
|
||||
step_orbit_snap(&mut h.app, rect, 1.0 / 60.0);
|
||||
let after = projection(&h.app, rect).to_screen(Vec3::ZERO).unwrap();
|
||||
assert!(after.distance(before) < 2.0, "{before:?} -> {after:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_orbit_keeps_cursor_pivot_and_default_middle_pan() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.preferences.orbit_cursor = true;
|
||||
h.app.preferences.orbit_around = crate::prefs::OrbitAround::Cursor;
|
||||
h.app.run("solid.box", json!({"length":40,"width":40,"height":40})).unwrap();
|
||||
h.app.fit_view();
|
||||
let rect = h.app.viewport.rect.unwrap();
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
{"note": "Alias preset (#304): Shift+Alt+left drag orbits, Shift+Alt+middle pans, Shift+Alt+right zooms (down: out, up: in). Other presets don't navigate with Shift+Alt+left."},
|
||||
{"call": "engine.execute", "params": {"command": "solid.box", "params": {"length": 20, "width": 10, "height": 5}}},
|
||||
{"call": "ui.prefs", "params": {"nav": "alias"}},
|
||||
{"note": "The orbit centre is its own preference, and the origin and cursor pivots move the camera's target: pin the view centre (the default) here so `panned` measures the binding, not the pivot."},
|
||||
{"call": "ui.prefs", "params": {"orbit_around": "view_centre"}},
|
||||
{"expect": {"prefs": {"nav": "alias"}}},
|
||||
|
||||
{"note": "Orbit: like a right drag (from the drag threshold on), and Shift (part of the binding) doesn't snap."},
|
||||
|
||||
@@ -132,7 +132,7 @@ unit regardless of the design, pass an expression with one: `"value": "50 mm"` o
|
||||
| `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`, `zoom_to_cursor`, `smooth_wheel_zoom` (default true), `trackpad_pan` (default true: two-finger scroll pans, except Touchpad always zooms; pinch zooms), `orbit_cursor`, `look_at_anim`, `look_at_speed`, `middle_double_click` (`home` (default), `fit`, `off`), `msaa`, `length_decimals`, `angle_decimals`, `theme` (`dark`, `light`, `system`), `language` (`en`, `ja`, `de`, `fr`, `ko`, `ru`, `uk`, `zh`; see [localization](localization.md)), `ui_scale`, `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) opens the Preferences dialog there; `button`: `ok`, `cancel`, `apply`, `defaults`; `stand_up: true` stands up a just-opened Y-up file | 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`, `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`, `length_decimals`, `angle_decimals`, `theme` (`dark`, `light`, `system`), `language` (`en`, `ja`, `de`, `fr`, `ko`, `ru`, `uk`, `zh`; see [localization](localization.md)), `ui_scale`, `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) opens the Preferences dialog there; `button`: `ok`, `cancel`, `apply`, `defaults`; `stand_up: true` stands up a just-opened Y-up file | 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 |
|
||||
|
||||
@@ -738,7 +738,7 @@ def main():
|
||||
mcp.send("notifications/initialized", notify=True)
|
||||
d = Demo(a.port, mcp, open(a.log, "w") if a.log else None, a.speed, a.minimum_caption)
|
||||
# The pi story's camera moves orbit about the point under the pointer (Preferences > Navigation).
|
||||
d.ui("ui.prefs", {"language": "en", "theme": "dark", "ui_scale": 1, "nav": "fusion", "orbit_cursor": a.story.startswith("pi"), "button": "apply"})
|
||||
d.ui("ui.prefs", {"language": "en", "theme": "dark", "ui_scale": 1, "nav": "fusion", "orbit_around": "cursor" if a.story.startswith("pi") else "view_centre", "button": "apply"})
|
||||
d.ui("ui.resize", {"width": 1920, "height": 1080})
|
||||
d.ui("ui.home", {"open": False})
|
||||
d.ui("ui.documents", {"action": "new"})
|
||||
|
||||
Reference in new issue
Block a user