mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
3D mouse: driver choice, guided calibration, button mapping and a picture of the device (#1463) (#3825)
* Add a small test boat to steer around with the 3D mouse A little hull in its own camera, drawn by the UI itself and not the CAD viewport, so a 3D mouse can be tried out without opening a design. It brings the model (generated by assets/models/make_test_boat.py) and the tests for how the camera moves it. Nothing uses it yet; Preferences picks it up with the rest of the 3D mouse work. * UI kit: keep a choice row from flipping with the scrollbar A choice row picks segments or a dropdown by whether its labels fit. A page scrollbar that comes and goes narrows the row by its own width, so the row could flip back and forth between the two on a page that sits right at the edge. It is now judged on the whole row and through a small helper, fits_steadily, that only changes its answer once the miss is wider than the scrollbar. Also tests for that, for typed numbers in a slider's value box, and a test hook to put up the issue-report input barrier by hand. * 3D mouse: device model, guided calibration, buttons and a picture The 3D mouse becomes a device we know something about instead of six raw numbers. The reader picks between spacenavd and the direct hidraw driver, falls back from one to the other, and says so when it can't open the device. Guided calibration asks whether the device is a spring cap or a trackball, adds a touch step, and uses a settle filter so a ball let go of stops reading as motion. Buttons get jobs (keys, held modifiers, commands), saved per device, with defaults for the SpaceMouse Pro. Preferences gets a picture of the device that moves as it does, next to the test boat, and the same picture can sit in the 3D view at Left Viewport, Right Viewport or Left Window, above the floating History card like the legend and readouts. These ride together because the crate's calibration types, the Preferences page, the Linux reader and the button hook all use each other. * 3D mouse: offer to calibrate an uncalibrated device When a 3D mouse shows up that has not been calibrated, a card in the 3D view offers to calibrate it, to swap drivers when the other one would work better, to close, or to never ask again. It is made once per device, goes away after 30 seconds in view, and gives way to a status or computing toast instead of stacking on it. The card is the shared toast, which now takes buttons: they group by purpose, wrap inside the card at narrow widths, and do nothing while an issue report holds CAD input. The picture of the device in the 3D view keeps clear of the card. * 3D mouse: translations and attribution The 3D mouse strings (ui.3d_mouse_*, the calibration steps and the offer card) in every shipped language, the Japanese catalog note, and the credit for the test boat model in ATTRIBUTION.md. This is last because the code before it only looks keys up: it builds and runs without these, falling back to English until they land. * 3D mouse: draw its preferences with the settings kit like the rest of Navigation The 3D mouse section was a folded header with its own layout. It now uses the same kit as the sections above it: a settings_section card, settings_form rows with the shared label column, dividers between the device, axes, picture and buttons groups, Text::Hint for the helper lines, and settings_control_width for the dropdowns. The section is open like every other one (the header still folds it), so the device badge on the folded header is gone; the calibration row already names the device. Every setting, string and key is unchanged. Tests that unfolded the section no longer click it, and the hint lines are looked up as hints instead of notes. * 3D mouse: show each axis's live tilt in Preferences, like main's axis rows Each view axis row is main's axis row again: a meter that follows the tilt of the motion driving it, with the dead zone marked, next to Invert, Speed and the "driven by" menu. The meter reads the calibrated travel when the device has a calibration and the raw reading over full scale when it does not, and asks for a frame whenever the reading changes. The helpers are back as they are on main, and the calibration state is a badge by the enable toggle. * 3D mouse: line its preferences up with the rest of Navigation - The two checkboxes sit side by side in the same two-column grid as Wheel and trackpad. - Row labels stop repeating "3D mouse" and use sentence case: Calibration, Driver, Restore defaults, Reset view, Left viewport, Right viewport, Left window. - Hints line up under the control column instead of the left edge. - The six view axes are named Pan X, Pan Y, Zoom, Orbit X, Orbit Y and Roll so they fit the name column. - Buttons are a divider and rows in a form like the other groups, without the bold sub-heading. - The driver, dead zone, axes, picture and buttons sit behind a collapsed Advanced section, since most people never touch them. * 3D mouse: tidy the Advanced test for clippy
This commit is contained in:
@@ -25,6 +25,8 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/icons/app/macos/exports/Assets.car | SolidCraft contributors | Apple actool compilation of assets/icons/app/macos/source/SolidCraft.icon for macOS; contains original artwork only | MIT OR Apache-2.0 |
|
||||
| assets/archive/icons/macos-v1/source/icon.vectorcraft | SolidCraft contributors | retained editable historical VectorCraft source, with named face and background layers; redundant exports retired | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/macos/exports/icon.icns | SolidCraft contributors | macOS fallback compiled by Apple's actool from the approved assets/icons/app/macos/source/SolidCraft.icon composition | MIT OR Apache-2.0 |
|
||||
| assets/models/test_boat.bin | 3DBenchy.com | 3DBenchy.com | Public Domain |
|
||||
| assets/models/make_test_boat.py | SolidCraft contributors | original script that packs a model into the compact file above | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/class-a-blend.png | SolidCraft contributors | screenshot of SolidCraft itself: two boxes bridged by `surface.blend` (a 40x10 sheet, `solidcraft-cli snapshot` of plan/evidence script ev_blend) | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/class-a-crown.png | SolidCraft contributors | screenshot of SolidCraft itself: a patch crowned with `surface.patch crown=0.3` over a saddle rim (`solidcraft-cli snapshot`) | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/class-a-boundary.png | SolidCraft contributors | screenshot of SolidCraft itself: a patch through two sketch lines built by `surface.boundary` with `crown: 0.4` (`solidcraft-cli snapshot`) | MIT OR Apache-2.0 |
|
||||
|
||||
@@ -246,11 +246,11 @@ mod tests {
|
||||
let axis =
|
||||
|source, rest, full| solidcraft_ui_egui::space_mouse::AxisCalibration { source, reversed: false, rest, dead_zone: 0, full: [full; 2] };
|
||||
// A short-range device: 15 of its 50 is under the raw dead zone, but 105 of 350 calibrated.
|
||||
let short = Calibration { dead_zone_share: 0.0, axes: std::array::from_fn(|r| axis(r, 0, 50)) };
|
||||
let short = Calibration { dead_zone_share: 0.0, kind: Default::default(), axes: std::array::from_fn(|r| axis(r, 0, 50)) };
|
||||
let push = Axes { translation: [15, 0, 0], rotation: [0; 3] };
|
||||
assert!(!push.moving() && moving(push, Some(short)));
|
||||
// A device resting at 100 on every axis: past the raw dead zone, but at rest calibrated.
|
||||
let offset = Calibration { dead_zone_share: 0.0, axes: std::array::from_fn(|r| axis(r, 100, 350)) };
|
||||
let offset = Calibration { dead_zone_share: 0.0, kind: Default::default(), axes: std::array::from_fn(|r| axis(r, 100, 350)) };
|
||||
let resting = Axes { translation: [100; 3], rotation: [100; 3] };
|
||||
assert!(resting.moving() && !moving(resting, Some(offset)));
|
||||
// Without a calibration, as read.
|
||||
|
||||
@@ -141,14 +141,17 @@ impl eframe::App for App {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
}
|
||||
fn raw_input_hook(&mut self, _ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
fn raw_input_hook(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(menu) = &mut self.native_menu {
|
||||
menu.raw_input_hook(&mut self.solve, _ctx, raw);
|
||||
menu.raw_input_hook(&mut self.solve, ctx, raw);
|
||||
}
|
||||
// Before the app's own hook, which adds the control channel's events.
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
self.space_mouse.raw_input_hook(self.solve.preferences.space_mouse_enabled, raw);
|
||||
// The 3D mouse's buttons, before the app reads the frame: Esc and held modifiers join its input.
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
solidcraft_ui_egui::space_mouse::buttons_hook(&mut self.solve, ctx, self.space_mouse.buttons(), raw);
|
||||
self.solve.raw_input_hook(raw);
|
||||
}
|
||||
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
|
||||
+1216
-55
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,12 @@ const DEV: &str = "/dev";
|
||||
/// a read with nothing to read returns at once and the reader can see whether it should stop.
|
||||
pub const O_NONBLOCK: i32 = 0o4000;
|
||||
|
||||
/// Opens a hidraw node the way the Direct reader reads it: read-only and non-blocking.
|
||||
pub fn open(path: &Path) -> std::io::Result<std::fs::File> {
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
std::fs::OpenOptions::new().read(true).custom_flags(O_NONBLOCK).open(path)
|
||||
}
|
||||
|
||||
/// How long the reader waits when the device has nothing to say, which is also the most a report
|
||||
/// can be delayed.
|
||||
const IDLE_POLL: Duration = Duration::from_millis(5);
|
||||
@@ -76,6 +82,44 @@ fn ids(uevent: &str) -> Option<(u16, u16)> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A real EACCES, as a user without the udev rule gets: a file of the user's own with no permission
|
||||
/// bits. Skipped for root, which can open anything.
|
||||
#[test]
|
||||
fn a_device_without_permission_is_denied_and_the_daemon_takes_over_within_the_backoff() {
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
if std::fs::metadata("/proc/self").is_ok_and(|meta| meta.uid() == 0) {
|
||||
return;
|
||||
}
|
||||
let dir = std::env::temp_dir().join(format!("sc-hidraw-denied-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let node = dir.join("hidraw0");
|
||||
std::fs::write(&node, [0_u8; 4]).unwrap();
|
||||
std::fs::set_permissions(&node, std::fs::Permissions::from_mode(0o000)).unwrap();
|
||||
let denied = open(&node);
|
||||
std::fs::set_permissions(&node, std::fs::Permissions::from_mode(0o600)).unwrap();
|
||||
assert!(open(&node).is_ok(), "the same node opens once it is readable");
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
assert_eq!(denied.err().map(|error| error.kind()), Some(std::io::ErrorKind::PermissionDenied));
|
||||
// The failure is the choice's `since_failed`, with an injected clock: inside the backoff the
|
||||
// daemon is read, whichever driver was asked for; after it the device is tried again.
|
||||
use super::super::{Pick, SpaceMouseDriver, UNREADABLE_RETRY, choose, direct_usable};
|
||||
for wanted in [SpaceMouseDriver::Automatic, SpaceMouseDriver::Spacenavd, SpaceMouseDriver::Direct] {
|
||||
for ago in [Duration::ZERO, Duration::from_secs(29)] {
|
||||
assert_eq!(choose(wanted, true, direct_usable(true, Some(ago), UNREADABLE_RETRY)), Some(Pick::Spacenavd), "{wanted:?} {ago:?}");
|
||||
}
|
||||
}
|
||||
assert_eq!(choose(SpaceMouseDriver::Direct, true, direct_usable(true, Some(UNREADABLE_RETRY), UNREADABLE_RETRY)), Some(Pick::Direct));
|
||||
// And the daemon's own reader delivers over a fake connection.
|
||||
let (mut daemon, client) = std::os::unix::net::UnixStream::pair().unwrap();
|
||||
let mut record = [0_u8; 32];
|
||||
record[4..8].copy_from_slice(&100_i32.to_ne_bytes());
|
||||
std::io::Write::write_all(&mut daemon, &record).unwrap();
|
||||
drop(daemon);
|
||||
let mut seen = 0;
|
||||
super::super::spacenavd::read(client, |_| seen += 1, |_, _| {}, || false, Duration::from_secs(3600));
|
||||
assert_eq!(seen, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_come_through_idle_gaps_until_the_device_ends_or_a_stop() {
|
||||
use crate::space_mouse::fake::Script;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
//! to its clients, so a program that reads the HID device itself bypasses that setup. Over its
|
||||
//! socket the daemon sends fixed 32-byte records: eight native-endian `i32`, the first the kind of
|
||||
//! record (0 for motion, then x y z rx ry rz and the milliseconds since the last record).
|
||||
//! Other records (button presses, from newer daemons also configuration changes) are ignored.
|
||||
//! Button records (1 pressed, 2 released, then the button's number from 0) are read too; other
|
||||
//! records (from newer daemons also configuration changes) are ignored.
|
||||
//!
|
||||
//! The socket is plain file I/O, so no library is needed, and it needs no udev rule: the daemon
|
||||
//! is what has access to the device.
|
||||
@@ -11,6 +12,7 @@
|
||||
use std::io::Read;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use solidcraft_ui_egui::space_mouse::{Axes, FULL_SCALE};
|
||||
|
||||
@@ -54,6 +56,22 @@ fn decode(record: &[u8; RECORD_BYTES]) -> Option<Axes> {
|
||||
})
|
||||
}
|
||||
|
||||
/// The button a button record is about, from 0, and whether it went down; `None` for any other
|
||||
/// record, or a button past the 32 a bitmask holds.
|
||||
fn button(record: &[u8; RECORD_BYTES]) -> Option<(u32, bool)> {
|
||||
let mut data = [0_i32; 2];
|
||||
for (slot, bytes) in data.iter_mut().zip(record.as_chunks::<4>().0) {
|
||||
*slot = i32::from_ne_bytes(*bytes);
|
||||
}
|
||||
let [kind, number] = data;
|
||||
let number = u32::try_from(number).ok().filter(|&n| n < 32)?;
|
||||
match kind {
|
||||
1 => Some((number, true)),
|
||||
2 => Some((number, false)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// A daemon value in our raw units. Out-of-range values stay in `i16`: they are input from another
|
||||
/// process.
|
||||
fn unit(value: impl Into<i64>) -> i16 {
|
||||
@@ -61,14 +79,30 @@ fn unit(value: impl Into<i64>) -> i16 {
|
||||
i16::try_from(scaled.clamp(i64::from(i16::MIN), i64::from(i16::MAX))).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Hands every motion record to `publish` until the daemon closes the connection or fails, or
|
||||
/// `stop` says so. A stream with a read timeout (`set_read_timeout`) times out when the daemon is
|
||||
/// quiet, which is no record and no disconnect: `stop` is asked, and the read goes on, with what
|
||||
/// of a record had arrived kept. A partial record at the end is dropped.
|
||||
pub fn read(mut stream: impl Read, mut publish: impl FnMut(Axes), mut stop: impl FnMut() -> bool) {
|
||||
/// Hands every motion record to `publish` and every button record to `pressed` until the daemon
|
||||
/// closes the connection or fails, or `stop` says so. A stream with a read timeout
|
||||
/// (`set_read_timeout`) times out when the daemon is quiet, which is no record and no disconnect:
|
||||
/// `stop` is asked, and the read goes on, with what of a record had arrived kept. `stop` is also asked
|
||||
/// at least every `check_every` while records keep arriving, so a daemon that never pauses for that
|
||||
/// long (a moving cap sends every few milliseconds) does not keep the reader from noticing that the
|
||||
/// device it prefers is back. A partial record at the end is dropped.
|
||||
pub fn read(
|
||||
mut stream: impl Read,
|
||||
mut publish: impl FnMut(Axes),
|
||||
mut pressed: impl FnMut(u32, bool),
|
||||
mut stop: impl FnMut() -> bool,
|
||||
check_every: Duration,
|
||||
) {
|
||||
let mut record = [0_u8; RECORD_BYTES];
|
||||
let mut filled = 0;
|
||||
let mut asked = Instant::now();
|
||||
loop {
|
||||
if asked.elapsed() >= check_every {
|
||||
asked = Instant::now();
|
||||
if stop() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
match stream.read(record.get_mut(filled..).unwrap_or_default()) {
|
||||
Ok(0) => {
|
||||
log::info!("3D mouse: spacenavd connection ended");
|
||||
@@ -80,11 +114,14 @@ pub fn read(mut stream: impl Read, mut publish: impl FnMut(Axes), mut stop: impl
|
||||
filled = 0;
|
||||
if let Some(axes) = decode(&record) {
|
||||
publish(axes);
|
||||
} else if let Some((number, down)) = button(&record) {
|
||||
pressed(number, down);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
|
||||
Err(error) if matches!(error.kind(), std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut) => {
|
||||
asked = Instant::now();
|
||||
if stop() {
|
||||
return;
|
||||
}
|
||||
@@ -134,6 +171,10 @@ mod tests {
|
||||
fn other_records_and_hostile_values_are_harmless() {
|
||||
// A button record (kind 1, press) and one of an unknown kind are not motion.
|
||||
assert_eq!(decode(&record([1, 0, 1, 0, 0, 0, 0, 0])), None);
|
||||
// Nor are hostile button numbers a button.
|
||||
assert_eq!(button(&record([1, -1, 0, 0, 0, 0, 0, 0])), None);
|
||||
assert_eq!(button(&record([1, i32::MAX, 0, 0, 0, 0, 0, 0])), None);
|
||||
assert_eq!(button(&record([7, 3, 0, 0, 0, 0, 0, 0])), None);
|
||||
assert_eq!(decode(&record([7, 500, 500, 500, 500, 500, 500, 0])), None);
|
||||
// Nothing overflows on the extremes of `i32`.
|
||||
let extreme = decode(&record([0, i32::MIN, i32::MIN, i32::MAX, i32::MAX, i32::MIN, i32::MIN, -1]));
|
||||
@@ -147,14 +188,17 @@ mod tests {
|
||||
let (mut daemon, client) = UnixStream::pair().expect("a socket pair");
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend(record([0, 100, 0, 0, 0, 0, 0, 8]));
|
||||
bytes.extend(record([2, 1, 0, 0, 0, 0, 0, 0])); // a button release: skipped
|
||||
bytes.extend(record([1, 4, 0, 0, 0, 0, 0, 0])); // button 4 down
|
||||
bytes.extend(record([2, 4, 0, 0, 0, 0, 0, 0])); // and up
|
||||
bytes.extend(record([1, 40, 0, 0, 0, 0, 0, 0])); // past the 32 a bitmask holds: skipped
|
||||
bytes.extend(record([0, 0, 0, 0, 0, 0, 0, 900]));
|
||||
bytes.extend([1, 2, 3]); // the daemon went away mid-record
|
||||
daemon.write_all(&bytes).expect("written");
|
||||
drop(daemon);
|
||||
let mut seen = Vec::new();
|
||||
read(client, |axes| seen.push(axes.translation), || false);
|
||||
let (mut seen, mut buttons) = (Vec::new(), Vec::new());
|
||||
read(client, |axes| seen.push(axes.translation), |number, down| buttons.push((number, down)), || false, Duration::from_secs(3600));
|
||||
assert_eq!(seen, [[70, 0, 0], [0, 0, 0]]);
|
||||
assert_eq!(buttons, [(4, true), (4, false)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -168,10 +212,12 @@ mod tests {
|
||||
read(
|
||||
script,
|
||||
|axes| seen.push(axes.translation),
|
||||
|_, _| {},
|
||||
|| {
|
||||
asked += 1;
|
||||
false
|
||||
},
|
||||
Duration::from_secs(3600),
|
||||
);
|
||||
assert_eq!(seen, [[70, 0, 0]]);
|
||||
assert_eq!(asked, 4, "each timeout asks whether to stop");
|
||||
@@ -184,10 +230,44 @@ mod tests {
|
||||
let mut script = Script::new();
|
||||
script.data(&bytes).timeout(std::io::ErrorKind::WouldBlock).data(&bytes);
|
||||
let mut seen = 0;
|
||||
read(script, |_| seen += 1, || true);
|
||||
read(script, |_| seen += 1, |_, _| {}, || true, Duration::from_secs(3600));
|
||||
assert_eq!(seen, 1, "the record before the timeout arrived; the one after never was read");
|
||||
}
|
||||
|
||||
/// A daemon that never goes quiet (a moving cap sends every few milliseconds) is still asked whether to
|
||||
/// stop, at the interval given, so the reader can go back to a device that returned.
|
||||
#[test]
|
||||
fn a_busy_daemon_is_asked_whether_to_stop_on_a_timer() {
|
||||
// Ends by itself after half a second, so a read that never asks fails the assertion below
|
||||
// instead of hanging the test.
|
||||
struct Busy(Instant);
|
||||
impl Read for Busy {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
if self.0.elapsed() > Duration::from_millis(500) {
|
||||
return Ok(0);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(2));
|
||||
let bytes = record([0, 100, 0, 0, 0, 0, 0, 8]);
|
||||
let size = bytes.len().min(buf.len());
|
||||
buf[..size].copy_from_slice(&bytes[..size]);
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
let (mut seen, mut asked) = (0, 0);
|
||||
read(
|
||||
Busy(Instant::now()),
|
||||
|_| seen += 1,
|
||||
|_, _| {},
|
||||
|| {
|
||||
asked += 1;
|
||||
asked == 3
|
||||
},
|
||||
Duration::from_millis(10),
|
||||
);
|
||||
assert_eq!(asked, 3, "asked on the timer although no read ever timed out");
|
||||
assert!(seen > 3, "records kept coming meanwhile: {seen}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_daemon_means_no_connection() {
|
||||
let missing = std::env::temp_dir().join(format!("sc-no-spnav-{}.sock", std::process::id()));
|
||||
|
||||
+100
-53
@@ -1125,7 +1125,106 @@
|
||||
"trace.stopped_by_contact": "Končí ve vzorku {sample}: {a} by prošlo skrz {b}",
|
||||
"trace.stopped_by_loop": "Končí ve vzorku {sample}: smyčku přes {joint} už nelze dále uzavřít",
|
||||
"ui.1_degree_of_freedom": "1 stupeň volnosti",
|
||||
"ui.3d_mouse_axes_reassigned": "Osy jsou v Předvolbách přeřazené a to platí i po této kalibraci.",
|
||||
"ui.3d_mouse_button": "Tlačítko {number}",
|
||||
"ui.3d_mouse_button_nothing": "Nic",
|
||||
"ui.3d_mouse_button_settings": "Nastavení 3D myši",
|
||||
"ui.3d_mouse_buttons": "Tlačítka",
|
||||
"ui.3d_mouse_buttons_hint": "Stiskněte tlačítko na 3D myši, ať ho tu najdete, a vyberte, co má dělat.",
|
||||
"ui.3d_mouse_buttons_no_device": "Připojte 3D myš a nastavte, co dělají její tlačítka.",
|
||||
"ui.3d_mouse_buttons_reset": "Obnovit výchozí tlačítka",
|
||||
"ui.3d_mouse_calibrate": "Kalibrovat…",
|
||||
"ui.3d_mouse_calibrate_tip": "Pohybujte hlavicí podle pokynů v okně, aby se SolidCraft naučil, kterým směrem a jak daleko se pohybuje každá osa této 3D myši. Uloží se pro tuto 3D myš.",
|
||||
"ui.3d_mouse_calibrate_title": "Kalibrace 3D myši",
|
||||
"ui.3d_mouse_calibrate_wait": "Jednou pohněte 3D myší, aby ji SolidCraft uviděl.",
|
||||
"ui.3d_mouse_calibrated": "Zkalibrováno",
|
||||
"ui.3d_mouse_calibration": "Kalibrace 3D myši",
|
||||
"ui.3d_mouse_calibration_done": "Hotovo. Uložte ji a použije se vždy, když bude tato 3D myš připojená.",
|
||||
"ui.3d_mouse_calibration_no_response": "Jedna osa se skoro nepohnula, takže ji nelze zkalibrovat. Začněte znovu a pokaždé hlavicí pohněte dál.",
|
||||
"ui.3d_mouse_circles_help": "Kruhy ukazují, co zařízení posílá po kalibraci. Zkušební model ukazuje, co s tím udělá pohled po obrácení, rychlosti a přiřazení os.",
|
||||
"ui.3d_mouse_dead_zone": "Mrtvá zóna",
|
||||
"ui.3d_mouse_dead_zone_tip": "Jak daleko se musí hlavice pohnout, než se pohne pohled, jako podíl jejího plného rozsahu. Zvyšte ji, pokud pohled ujíždí, i když je hlavice v klidu.",
|
||||
"ui.3d_mouse_denied": "SolidCraft vidí 3D myš, ale nesmí ji číst. Pravidlo udev najdete v poznámkách k nastavení 3D myši.",
|
||||
"ui.3d_mouse_denied_short": "Nalezeno, ale nelze číst",
|
||||
"ui.3d_mouse_driven_by": "Který kalibrovaný pohyb zařízení ovládá tuto osu. Když vyberete pohyb, který už používá jiná osa, obě se prohodí.",
|
||||
"ui.3d_mouse_driven_by_label": "ovládá",
|
||||
"ui.3d_mouse_driver": "Ovladač 3D myši",
|
||||
"ui.3d_mouse_driver_auto": "Automaticky",
|
||||
"ui.3d_mouse_driver_direct": "Přímo (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Zatím se čte přes spacenavd: zařízení samotné se nepodařilo otevřít.",
|
||||
"ui.3d_mouse_driver_only_direct": "Zařízení se čte přímo; spacenavd neběží",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Čte se přes spacenavd, jiná možnost není",
|
||||
"ui.3d_mouse_forget": "Zapomenout kalibraci",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Vlevo ve výřezu",
|
||||
"ui.3d_mouse_ghost_left_window": "Vlevo v okně",
|
||||
"ui.3d_mouse_ghost_position": "Poloha",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Vpravo ve výřezu",
|
||||
"ui.3d_mouse_hint": "Posun a otáčení pohledu současně; náčrty používají jen posun a přiblížení.",
|
||||
"ui.3d_mouse_kind_cap": "Návrat do středu",
|
||||
"ui.3d_mouse_kind_question": "Jaký typ 3D myši to je?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "Hodnota zůstává, dokud ji držíte, jako u zařízení typu „Návrat do středu“.",
|
||||
"ui.3d_mouse_looks_like_trackball": "Hodnota se vrací zpět, jakmile přestanete hýbat, jako u trackballu.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotace X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotace Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotace Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Posun X",
|
||||
"ui.3d_mouse_motion_translation_y": "Posun Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Posun Z",
|
||||
"ui.3d_mouse_navigate": "Navigace pomocí 3D myši",
|
||||
"ui.3d_mouse_none": "3D myš nenalezena",
|
||||
"ui.3d_mouse_not_calibrated": "Nezkalibrováno",
|
||||
"ui.3d_mouse_offer": "Kalibrace 3D myši opraví prohozené nebo obrácené osy a nastaví její rychlost a pocit z ovládání.",
|
||||
"ui.3d_mouse_offer_never": "Už nezobrazovat",
|
||||
"ui.3d_mouse_offer_swap": "Přepnout na Hidraw (přímý ovladač)",
|
||||
"ui.3d_mouse_reading": "Hodnoty",
|
||||
"ui.3d_mouse_reset_axes": "Obnovit přiřazení os",
|
||||
"ui.3d_mouse_reset_view": "Obnovit pohled",
|
||||
"ui.3d_mouse_restore": "Obnovit výchozí nastavení 3D myši",
|
||||
"ui.3d_mouse_restore_ask": "Vrátit nastavení 3D myši na výchozí hodnoty? Kalibrace a přiřazení tlačítek zůstanou.",
|
||||
"ui.3d_mouse_section": "3D myš",
|
||||
"ui.3d_mouse_show_in_view": "Zobrazit 3D myš ve 3D pohledu",
|
||||
"ui.3d_mouse_show_in_view_tip": "Obrázek vaší 3D myši v levém dolním rohu, který se hýbe s ní.",
|
||||
"ui.3d_mouse_skip": "Moje zařízení to neumí",
|
||||
"ui.3d_mouse_skip_tip": "Přeskočí tento pohyb v obou směrech a kalibrace ho vynechá.",
|
||||
"ui.3d_mouse_speed": "Rychlost 3D myši",
|
||||
"ui.3d_mouse_step_lift": "Zvedněte hlavici nahoru, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_press": "Zatlačte hlavici dolů, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_pull": "Přitáhněte hlavici k sobě, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_push": "Odtlačte hlavici od sebe, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_rest": "Dejte ruku z hlavice a nechte ji v klidu.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Dejte ruku z kuličky a nechte ji v klidu.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Otočte rolovacím kroužkem o pár cvaknutí po směru hodinových ručiček.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Otočte rolovacím kroužkem o pár cvaknutí proti směru hodinových ručiček.",
|
||||
"ui.3d_mouse_step_roll_away": "Několikrát kutálejte kuličkou od sebe, pak ji nechte zastavit.",
|
||||
"ui.3d_mouse_step_roll_left": "Několikrát kutálejte kuličkou doleva, pak ji nechte zastavit.",
|
||||
"ui.3d_mouse_step_roll_right": "Několikrát kutálejte kuličkou doprava, pak ji nechte zastavit.",
|
||||
"ui.3d_mouse_step_roll_toward": "Několikrát kutálejte kuličkou k sobě, pak ji nechte zastavit.",
|
||||
"ui.3d_mouse_step_slide_left": "Posuňte hlavici doleva, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_slide_right": "Posuňte hlavici doprava, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Několikrát roztočte kuličku po směru hodinových ručiček, pak ji nechte zastavit.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Několikrát roztočte kuličku proti směru hodinových ručiček, pak ji nechte zastavit.",
|
||||
"ui.3d_mouse_step_tilt_away": "Nakloňte vršek hlavice od sebe (okraj nejblíž vám jde nahoru), podržte a pusťte.",
|
||||
"ui.3d_mouse_step_tilt_left": "Nakloňte hlavici doleva (levá hrana dolů), podržte a pusťte.",
|
||||
"ui.3d_mouse_step_tilt_right": "Nakloňte hlavici doprava (pravá hrana dolů), podržte a pusťte.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Nakloňte vršek hlavice k sobě (okraj nejblíž vám jde dolů), podržte a pusťte.",
|
||||
"ui.3d_mouse_step_touch": "Položte ruku na hlavici, ale netlačte na ni.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Otočte hlavici po směru hodinových ručiček, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Otočte hlavici proti směru hodinových ručiček, podržte a pusťte.",
|
||||
"ui.3d_mouse_switch_cap": "Kalibrovat jako návrat do středu",
|
||||
"ui.3d_mouse_switch_trackball": "Kalibrovat jako trackball",
|
||||
"ui.3d_mouse_view_axis": "Osa pohledu",
|
||||
"ui.3d_mouse_view_off": "Vypnuto",
|
||||
"ui.3d_mouse_view_off_tip": "Profil ovladače 3D myši tuto osu vypíná, takže její pohyb s pohledem nehýbe. Když tu vyberete pohyb jiné osy, prohodí se s ní a s pohledem přestane hýbat.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbit vlevo / vpravo",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbit nahoru / dolů",
|
||||
"ui.3d_mouse_view_pan_sideways": "Posun vlevo / vpravo",
|
||||
"ui.3d_mouse_view_pan_up_down": "Posun nahoru / dolů",
|
||||
"ui.3d_mouse_view_roll": "Natočení",
|
||||
"ui.3d_mouse_view_unmapped": "Osa {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "Čeká na pohyb",
|
||||
"ui.3d_mouse_wiggle": "Pokud máte nějakou připojenou, zahýbejte s ní.",
|
||||
"ui.3d_sketch": "3D náčrt",
|
||||
"ui.activate": "Aktivovat",
|
||||
"ui.active_component": "Aktivní komponenta",
|
||||
@@ -1355,6 +1454,7 @@
|
||||
"ui.blocked": "Blokováno",
|
||||
"ui.bodies": "Tělesa",
|
||||
"ui.bodies_all_if_none": "Tělesa (všechna, pokud žádné)",
|
||||
"ui.bottom": "Zespodu",
|
||||
"ui.box": "Kvádr",
|
||||
"ui.break": "Přerušit",
|
||||
"ui.browser_bodies_one": "{count} těleso",
|
||||
@@ -1798,59 +1898,6 @@
|
||||
"ui.smooth_wheel_zoom": "Plynulé přiblížení kolečkem",
|
||||
"ui.smooth_wheel_zoom_tip": "Vypnutím se každý pohyb kolečka projeví okamžitě, bez dobíhání",
|
||||
"ui.snap": "Přichytávání",
|
||||
"ui.space_mouse_axes_reassigned": "Osy jsou v Předvolbách přeřazené a to platí i po této kalibraci.",
|
||||
"ui.space_mouse_calibrate": "Kalibrovat…",
|
||||
"ui.space_mouse_calibrate_tip": "Pohybujte hlavicí podle pokynů v okně, aby se SolidCraft naučil, kterým směrem a jak daleko se pohybuje každá osa této 3D myši. Uloží se pro tuto 3D myš.",
|
||||
"ui.space_mouse_calibrate_title": "Kalibrace 3D myši",
|
||||
"ui.space_mouse_calibrated": "Zkalibrováno",
|
||||
"ui.space_mouse_calibration": "Kalibrace 3D myši",
|
||||
"ui.space_mouse_calibration_done": "Hotovo. Uložte ji a použije se vždy, když bude tato 3D myš připojená.",
|
||||
"ui.space_mouse_calibration_no_response": "Jedna osa se skoro nepohnula, takže ji nelze zkalibrovat. Začněte znovu a pokaždé hlavicí pohněte dál.",
|
||||
"ui.space_mouse_dead_zone": "Mrtvá zóna",
|
||||
"ui.space_mouse_dead_zone_tip": "Jak daleko se musí hlavice pohnout, než se pohne pohled, jako podíl jejího plného rozsahu. Zvyšte ji, pokud pohled ujíždí, i když je hlavice v klidu.",
|
||||
"ui.space_mouse_driven_by": "Který kalibrovaný pohyb zařízení ovládá tuto osu. Když vyberete pohyb, který už používá jiná osa, obě se prohodí.",
|
||||
"ui.space_mouse_driven_by_label": "ovládá",
|
||||
"ui.space_mouse_driver": "Ovladač 3D myši",
|
||||
"ui.space_mouse_driver_auto": "Automaticky",
|
||||
"ui.space_mouse_driver_direct": "Přímo (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "Zařízení se čte přímo; spacenavd neběží",
|
||||
"ui.space_mouse_driver_only_spacenavd": "Čte se přes spacenavd, jiná možnost není",
|
||||
"ui.space_mouse_forget": "Zapomenout kalibraci",
|
||||
"ui.space_mouse_hint": "Posun a otáčení pohledu současně; náčrty používají jen posun a přiblížení.",
|
||||
"ui.space_mouse_motion_rotation_x": "Rotace X",
|
||||
"ui.space_mouse_motion_rotation_y": "Rotace Y",
|
||||
"ui.space_mouse_motion_rotation_z": "Rotace Z",
|
||||
"ui.space_mouse_motion_translation_x": "Posun X",
|
||||
"ui.space_mouse_motion_translation_y": "Posun Y",
|
||||
"ui.space_mouse_motion_translation_z": "Posun Z",
|
||||
"ui.space_mouse_navigate": "Navigace pomocí SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "3D myš nenalezena",
|
||||
"ui.space_mouse_not_calibrated": "Nezkalibrováno",
|
||||
"ui.space_mouse_reading": "Hodnoty",
|
||||
"ui.space_mouse_reset_axes": "Obnovit přiřazení os",
|
||||
"ui.space_mouse_step_lift": "Zvedněte hlavici nahoru, podržte a pusťte.",
|
||||
"ui.space_mouse_step_press": "Zatlačte hlavici dolů, podržte a pusťte.",
|
||||
"ui.space_mouse_step_pull": "Přitáhněte hlavici k sobě, podržte a pusťte.",
|
||||
"ui.space_mouse_step_push": "Odtlačte hlavici od sebe, podržte a pusťte.",
|
||||
"ui.space_mouse_step_rest": "Dejte ruku z hlavice a nechte ji v klidu.",
|
||||
"ui.space_mouse_step_slide_left": "Posuňte hlavici doleva, podržte a pusťte.",
|
||||
"ui.space_mouse_step_slide_right": "Posuňte hlavici doprava, podržte a pusťte.",
|
||||
"ui.space_mouse_step_tilt_back": "Nakloňte hlavici dozadu (přední hrana nahoru), podržte a pusťte.",
|
||||
"ui.space_mouse_step_tilt_forward": "Nakloňte hlavici dopředu (přední hrana dolů), podržte a pusťte.",
|
||||
"ui.space_mouse_step_tilt_left": "Nakloňte hlavici doleva (levá hrana dolů), podržte a pusťte.",
|
||||
"ui.space_mouse_step_tilt_right": "Nakloňte hlavici doprava (pravá hrana dolů), podržte a pusťte.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Otočte hlavici po směru hodinových ručiček, podržte a pusťte.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Otočte hlavici proti směru hodinových ručiček, podržte a pusťte.",
|
||||
"ui.space_mouse_view_axis": "Osa pohledu",
|
||||
"ui.space_mouse_view_off": "Vypnuto",
|
||||
"ui.space_mouse_view_off_tip": "Profil ovladače 3D myši tuto osu vypíná, takže její pohyb s pohledem nehýbe. Když tu vyberete pohyb jiné osy, prohodí se s ní a s pohledem přestane hýbat.",
|
||||
"ui.space_mouse_view_orbit_sideways": "Orbit vlevo / vpravo",
|
||||
"ui.space_mouse_view_orbit_up_down": "Orbit nahoru / dolů",
|
||||
"ui.space_mouse_view_pan_sideways": "Posun vlevo / vpravo",
|
||||
"ui.space_mouse_view_pan_up_down": "Posun nahoru / dolů",
|
||||
"ui.space_mouse_view_roll": "Natočení",
|
||||
"ui.space_mouse_view_unmapped": "Osa {n}",
|
||||
"ui.space_mouse_view_zoom": "Zoom",
|
||||
"ui.spacing": "Rozteč",
|
||||
"ui.spacing_2": "Rozteč 2",
|
||||
"ui.sphere": "Koule",
|
||||
|
||||
+100
-28
@@ -1125,7 +1125,106 @@
|
||||
"trace.stopped_by_contact": "Slutter ved prøve {sample}: {a} ville gå gennem {b}",
|
||||
"trace.stopped_by_loop": "Slutter ved prøve {sample}: Løkken gennem {joint} kan ikke lukkes yderligere",
|
||||
"ui.1_degree_of_freedom": "1 frihedsgrad",
|
||||
"ui.3d_mouse_axes_reassigned": "Akserne er omtildelt i Indstillinger, og det gælder stadig oven på denne kalibrering.",
|
||||
"ui.3d_mouse_button": "Knap {number}",
|
||||
"ui.3d_mouse_button_nothing": "Ingenting",
|
||||
"ui.3d_mouse_button_settings": "Indstillinger for 3D-mus",
|
||||
"ui.3d_mouse_buttons": "Knapper",
|
||||
"ui.3d_mouse_buttons_hint": "Tryk på en knap på din 3D-mus for at finde den her, og vælg så, hvad den gør.",
|
||||
"ui.3d_mouse_buttons_no_device": "Tilslut en 3D-mus for at vælge, hvad dens knapper gør.",
|
||||
"ui.3d_mouse_buttons_reset": "Gendan standardknapper",
|
||||
"ui.3d_mouse_calibrate": "Kalibrér…",
|
||||
"ui.3d_mouse_calibrate_tip": "Bevæg knoppen som vist i vinduet, så SolidCraft kan registrere retningen og bevægelsesområdet for hver akse. Indstillingerne gemmes til denne 3D-mus.",
|
||||
"ui.3d_mouse_calibrate_title": "Kalibrér 3D-mus",
|
||||
"ui.3d_mouse_calibrate_wait": "Bevæg 3D-musen én gang, så SolidCraft kan se den.",
|
||||
"ui.3d_mouse_calibrated": "Kalibreret",
|
||||
"ui.3d_mouse_calibration": "Kalibrering af 3D-mus",
|
||||
"ui.3d_mouse_calibration_done": "Færdig. Gem kalibreringen, så den bruges, hver gang denne 3D-mus tilsluttes.",
|
||||
"ui.3d_mouse_calibration_no_response": "En af akserne bevægede sig for lidt til at kunne kalibreres. Start forfra, og bevæg knoppen længere hver gang.",
|
||||
"ui.3d_mouse_circles_help": "Cirklerne viser, hvad enheden sender efter kalibrering. Testmodellen viser, hvad visningen gør med det efter omvending, hastighed og akseplacering.",
|
||||
"ui.3d_mouse_dead_zone": "Dødzone",
|
||||
"ui.3d_mouse_dead_zone_tip": "Hvor langt knoppen skal bevæges, før visningen reagerer, angivet som en andel af dens fulde bevægelse. Øg værdien, hvis visningen bevæger sig, når du ikke rører knoppen.",
|
||||
"ui.3d_mouse_denied": "SolidCraft kan se en 3D-mus, men må ikke læse den. Se opsætningsnoterne til 3D-mus for udev-reglen.",
|
||||
"ui.3d_mouse_denied_short": "Fundet, men kan ikke læses",
|
||||
"ui.3d_mouse_driven_by": "Hvilken kalibreret bevægelse af enheden der styrer denne akse. Vælger du en bevægelse, som en anden akse allerede bruger, bytter de to plads.",
|
||||
"ui.3d_mouse_driven_by_label": "styres af",
|
||||
"ui.3d_mouse_driver": "3D-musedriver",
|
||||
"ui.3d_mouse_driver_auto": "Automatisk",
|
||||
"ui.3d_mouse_driver_direct": "Direkte (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Læser indtil videre gennem spacenavd: selve enheden kunne ikke åbnes.",
|
||||
"ui.3d_mouse_driver_only_direct": "Læser enheden direkte; spacenavd kører ikke",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Læser gennem spacenavd, den eneste mulighed",
|
||||
"ui.3d_mouse_forget": "Slet kalibrering",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Venstre visningsområde",
|
||||
"ui.3d_mouse_ghost_left_window": "Venstre vindue",
|
||||
"ui.3d_mouse_ghost_position": "Placering",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Højre visningsområde",
|
||||
"ui.3d_mouse_hint": "Flyt og rotér visningen samtidigt; skitser bruger kun panorering og zoom.",
|
||||
"ui.3d_mouse_kind_cap": "Vend tilbage til midten",
|
||||
"ui.3d_mouse_kind_question": "Hvilken slags 3D-mus er det?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "Værdien bliver, mens du holder, som på en enhed af typen „Vend tilbage til midten“.",
|
||||
"ui.3d_mouse_looks_like_trackball": "Værdien falder tilbage, så snart du stopper, som på en trackball.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotation X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotation Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotation Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Forskydning X",
|
||||
"ui.3d_mouse_motion_translation_y": "Forskydning Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Forskydning Z",
|
||||
"ui.3d_mouse_navigate": "Navigér med 3D-musen",
|
||||
"ui.3d_mouse_none": "Ingen 3D-mus fundet",
|
||||
"ui.3d_mouse_not_calibrated": "Ikke kalibreret",
|
||||
"ui.3d_mouse_offer": "Kalibrering af din 3D-mus retter ombyttede eller vendte akser og indstiller dens hastighed og fornemmelse.",
|
||||
"ui.3d_mouse_offer_never": "Vis ikke igen",
|
||||
"ui.3d_mouse_offer_swap": "Skift til Hidraw (direkte driver)",
|
||||
"ui.3d_mouse_reading": "Aflæser",
|
||||
"ui.3d_mouse_reset_axes": "Nulstil aksetildeling",
|
||||
"ui.3d_mouse_reset_view": "Nulstil visning",
|
||||
"ui.3d_mouse_restore": "Gendan 3D-musens standardværdier",
|
||||
"ui.3d_mouse_restore_ask": "Sæt 3D-musens indstillinger tilbage til standard? Din kalibrering og knaptildeling bliver.",
|
||||
"ui.3d_mouse_section": "3D-mus",
|
||||
"ui.3d_mouse_show_in_view": "Vis 3D-musen i 3D-visningen",
|
||||
"ui.3d_mouse_show_in_view_tip": "Et billede af din 3D-mus i nederste venstre hjørne, der bevæger sig, når du bevæger den.",
|
||||
"ui.3d_mouse_skip": "Min enhed kan ikke det",
|
||||
"ui.3d_mouse_skip_tip": "Springer bevægelsen over begge veje, og kalibreringen udelader den.",
|
||||
"ui.3d_mouse_speed": "3D-musens hastighed",
|
||||
"ui.3d_mouse_step_lift": "Løft knoppen op, hold den, og slip.",
|
||||
"ui.3d_mouse_step_press": "Tryk knoppen ned, hold den, og slip.",
|
||||
"ui.3d_mouse_step_pull": "Træk knoppen mod dig, hold den, og slip.",
|
||||
"ui.3d_mouse_step_push": "Skub knoppen væk fra dig, hold den, og slip.",
|
||||
"ui.3d_mouse_step_rest": "Fjern hånden fra knoppen, og lad den stå stille.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Fjern hånden fra kuglen, og lad den ligge stille.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Drej rullehjulet et par klik med uret.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Drej rullehjulet et par klik mod uret.",
|
||||
"ui.3d_mouse_step_roll_away": "Rul kuglen væk fra dig et par gange, og lad den stoppe.",
|
||||
"ui.3d_mouse_step_roll_left": "Rul kuglen mod venstre et par gange, og lad den stoppe.",
|
||||
"ui.3d_mouse_step_roll_right": "Rul kuglen mod højre et par gange, og lad den stoppe.",
|
||||
"ui.3d_mouse_step_roll_toward": "Rul kuglen ind mod dig et par gange, og lad den stoppe.",
|
||||
"ui.3d_mouse_step_slide_left": "Skub knoppen mod venstre, hold den, og slip.",
|
||||
"ui.3d_mouse_step_slide_right": "Skub knoppen mod højre, hold den, og slip.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Drej kuglen rundt med uret et par gange, og lad den stoppe.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Drej kuglen rundt mod uret et par gange, og lad den stoppe.",
|
||||
"ui.3d_mouse_step_tilt_away": "Vip toppen af knoppen væk fra dig (kanten nærmest dig går op), hold den, og slip.",
|
||||
"ui.3d_mouse_step_tilt_left": "Vip knoppen mod venstre (venstre kant ned), hold den, og slip.",
|
||||
"ui.3d_mouse_step_tilt_right": "Vip knoppen mod højre (højre kant ned), hold den, og slip.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Vip toppen af knoppen ind mod dig (kanten nærmest dig går ned), hold den, og slip.",
|
||||
"ui.3d_mouse_step_touch": "Hvil hånden på knoppen uden at skubbe til den.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Drej knoppen med uret, hold den, og slip.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Drej knoppen mod uret, hold den, og slip.",
|
||||
"ui.3d_mouse_switch_cap": "Kalibrer som vend tilbage til midten",
|
||||
"ui.3d_mouse_switch_trackball": "Kalibrér som trackball",
|
||||
"ui.3d_mouse_view_axis": "Visningsakse",
|
||||
"ui.3d_mouse_view_off": "Fra",
|
||||
"ui.3d_mouse_view_off_tip": "3D-musedriverens profil slår denne akse fra, så dens bevægelse flytter ikke visningen. Vælger du en anden akses bevægelse her, bytter de to plads, og den bevægelse flytter ikke længere visningen.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbit venstre / højre",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbit op / ned",
|
||||
"ui.3d_mouse_view_pan_sideways": "Panorér venstre / højre",
|
||||
"ui.3d_mouse_view_pan_up_down": "Panorér op / ned",
|
||||
"ui.3d_mouse_view_roll": "Rul",
|
||||
"ui.3d_mouse_view_unmapped": "Akse {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "Venter på bevægelse",
|
||||
"ui.3d_mouse_wiggle": "Hvis du har en tilsluttet, så bevæg den lidt.",
|
||||
"ui.3d_sketch": "3D-skitse",
|
||||
"ui.activate": "Aktivér",
|
||||
"ui.active_component": "Aktiv komponent",
|
||||
@@ -1355,6 +1454,7 @@
|
||||
"ui.blocked": "Blokeret",
|
||||
"ui.bodies": "Emner",
|
||||
"ui.bodies_all_if_none": "Emner (alle, hvis ingen er valgt)",
|
||||
"ui.bottom": "Nedenfra",
|
||||
"ui.box": "Boks",
|
||||
"ui.break": "Bryd",
|
||||
"ui.browser_bodies_one": "{count} emne",
|
||||
@@ -1798,34 +1898,6 @@
|
||||
"ui.smooth_wheel_zoom": "Jævn zoom med musehjul",
|
||||
"ui.smooth_wheel_zoom_tip": "Slå fra for at anvende hver hjulbevægelse straks uden efterfølgende udjævning",
|
||||
"ui.snap": "Fastgør til geometri",
|
||||
"ui.space_mouse_calibrate": "Kalibrér…",
|
||||
"ui.space_mouse_calibrate_tip": "Bevæg knoppen som vist i vinduet, så SolidCraft kan registrere retningen og bevægelsesområdet for hver akse. Indstillingerne gemmes til denne 3D-mus.",
|
||||
"ui.space_mouse_calibrate_title": "Kalibrér 3D-mus",
|
||||
"ui.space_mouse_calibrated": "Kalibreret",
|
||||
"ui.space_mouse_calibration": "Kalibrering af 3D-mus",
|
||||
"ui.space_mouse_calibration_done": "Færdig. Gem indstillingerne, så de bruges, hver gang denne 3D-mus tilsluttes.",
|
||||
"ui.space_mouse_calibration_no_response": "En af akserne bevægede sig for lidt til at kunne kalibreres. Start forfra, og bevæg knoppen længere hver gang.",
|
||||
"ui.space_mouse_dead_zone": "Dødzone",
|
||||
"ui.space_mouse_dead_zone_tip": "Hvor langt knoppen skal bevæges, før visningen reagerer, angivet som en andel af dens fulde bevægelse. Øg værdien, hvis visningen bevæger sig, når du ikke rører knoppen.",
|
||||
"ui.space_mouse_forget": "Nulstil kalibrering",
|
||||
"ui.space_mouse_hint": "Flyt og rotér visningen samtidigt; skitser bruger kun panorering og zoom.",
|
||||
"ui.space_mouse_navigate": "Navigér med SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Ingen 3D-mus fundet",
|
||||
"ui.space_mouse_not_calibrated": "Ikke kalibreret",
|
||||
"ui.space_mouse_reading": "Aflæser",
|
||||
"ui.space_mouse_step_lift": "Løft knoppen op, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_press": "Tryk knoppen ned, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_pull": "Træk knoppen mod dig, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_push": "Skub knoppen væk fra dig, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_rest": "Fjern hånden fra knoppen, og lad den stå stille.",
|
||||
"ui.space_mouse_step_slide_left": "Skub knoppen mod venstre, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_slide_right": "Skub knoppen mod højre, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_tilt_back": "Vip knoppen bagud (forkanten op), hold den dér, og slip.",
|
||||
"ui.space_mouse_step_tilt_forward": "Vip knoppen fremad (forkanten ned), hold den dér, og slip.",
|
||||
"ui.space_mouse_step_tilt_left": "Vip knoppen mod venstre (venstre kant ned), hold den dér, og slip.",
|
||||
"ui.space_mouse_step_tilt_right": "Vip knoppen mod højre (højre kant ned), hold den dér, og slip.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Drej knoppen med uret, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Drej knoppen mod uret, hold den dér, og slip.",
|
||||
"ui.spacing": "Afstand",
|
||||
"ui.spacing_2": "Afstand 2",
|
||||
"ui.sphere": "Kugle",
|
||||
|
||||
+100
-53
@@ -1248,7 +1248,106 @@
|
||||
"trace.stopped_by_contact": "Endet bei Probe {sample}: {a} würde {b} durchdringen",
|
||||
"trace.stopped_by_loop": "Endet bei Probe {sample}: die Schleife über {joint} lässt sich nicht weiter schließen",
|
||||
"ui.1_degree_of_freedom": "1 Freiheitsgrad",
|
||||
"ui.3d_mouse_axes_reassigned": "Die Achsen sind in den Einstellungen neu zugeordnet, und das gilt auch nach dieser Kalibrierung.",
|
||||
"ui.3d_mouse_button": "Taste {number}",
|
||||
"ui.3d_mouse_button_nothing": "Nichts",
|
||||
"ui.3d_mouse_button_settings": "3D-Maus-Einstellungen",
|
||||
"ui.3d_mouse_buttons": "Tasten",
|
||||
"ui.3d_mouse_buttons_hint": "Drücken Sie eine Taste an Ihrer 3D-Maus, um sie hier zu finden, und wählen Sie dann, was sie tut.",
|
||||
"ui.3d_mouse_buttons_no_device": "Schließen Sie eine 3D-Maus an, um ihre Tasten zu belegen.",
|
||||
"ui.3d_mouse_buttons_reset": "Standardbelegung wiederherstellen",
|
||||
"ui.3d_mouse_calibrate": "Kalibrieren…",
|
||||
"ui.3d_mouse_calibrate_tip": "Bewegen Sie die Kappe so, wie das Fenster es verlangt. So lernt SolidCraft, in welche Richtung und wie weit jede Achse dieser 3D-Maus geht. Wird für diese 3D-Maus gespeichert.",
|
||||
"ui.3d_mouse_calibrate_title": "3D-Maus kalibrieren",
|
||||
"ui.3d_mouse_calibrate_wait": "Bewegen Sie die 3D-Maus einmal, damit SolidCraft sie erkennt.",
|
||||
"ui.3d_mouse_calibrated": "Kalibriert",
|
||||
"ui.3d_mouse_calibration": "3D-Maus-Kalibrierung",
|
||||
"ui.3d_mouse_calibration_done": "Fertig. Speichern Sie die Kalibrierung, damit sie immer gilt, wenn diese 3D-Maus angeschlossen ist.",
|
||||
"ui.3d_mouse_calibration_no_response": "Eine Achse hat sich kaum bewegt und lässt sich nicht kalibrieren. Beginnen Sie von vorn und bewegen Sie die Kappe jedes Mal weiter.",
|
||||
"ui.3d_mouse_circles_help": "Die Kreise zeigen, was das Gerät nach der Kalibrierung sendet. Das Testmodell zeigt, was die Ansicht daraus macht, nach Umkehren, Geschwindigkeit und Achsenzuordnung.",
|
||||
"ui.3d_mouse_dead_zone": "Totzone",
|
||||
"ui.3d_mouse_dead_zone_tip": "Wie weit sich die Kappe bewegen muss, bevor sich die Ansicht bewegt, als Anteil ihres vollen Wegs. Erhöhen Sie sie, wenn die Ansicht wandert, obwohl die Kappe ruht.",
|
||||
"ui.3d_mouse_denied": "SolidCraft sieht eine 3D-Maus, darf sie aber nicht lesen. Die udev-Regel steht in den Einrichtungshinweisen zur 3D-Maus.",
|
||||
"ui.3d_mouse_denied_short": "Gefunden, aber nicht lesbar",
|
||||
"ui.3d_mouse_driven_by": "Welche kalibrierte Bewegung des Geräts diese Achse steuert. Wählen Sie eine, die schon eine andere Achse nutzt, werden die beiden getauscht.",
|
||||
"ui.3d_mouse_driven_by_label": "gesteuert von",
|
||||
"ui.3d_mouse_driver": "3D-Maus-Treiber",
|
||||
"ui.3d_mouse_driver_auto": "Automatisch",
|
||||
"ui.3d_mouse_driver_direct": "Direkt (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Wird vorerst über spacenavd gelesen: das Gerät selbst ließ sich nicht öffnen.",
|
||||
"ui.3d_mouse_driver_only_direct": "Das Gerät wird direkt gelesen; spacenavd läuft nicht",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Lesen über spacenavd, die einzige verfügbare Möglichkeit",
|
||||
"ui.3d_mouse_forget": "Kalibrierung verwerfen",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Ansicht links",
|
||||
"ui.3d_mouse_ghost_left_window": "Fenster links",
|
||||
"ui.3d_mouse_ghost_position": "Position",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Ansicht rechts",
|
||||
"ui.3d_mouse_hint": "Ansicht gleichzeitig verschieben und drehen; Skizzen verwenden nur Schwenken und Zoomen.",
|
||||
"ui.3d_mouse_kind_cap": "Zurück zur Mitte",
|
||||
"ui.3d_mouse_kind_question": "Was für eine 3D-Maus ist es?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "Der Wert bleibt stehen, solange Sie halten, wie bei einem Gerät vom Typ „Zurück zur Mitte“.",
|
||||
"ui.3d_mouse_looks_like_trackball": "Der Wert fällt zurück, sobald Sie aufhören, wie bei einem Trackball.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotation X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotation Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotation Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Translation X",
|
||||
"ui.3d_mouse_motion_translation_y": "Translation Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Translation Z",
|
||||
"ui.3d_mouse_navigate": "Mit der 3D-Maus navigieren",
|
||||
"ui.3d_mouse_none": "Keine 3D-Maus gefunden",
|
||||
"ui.3d_mouse_not_calibrated": "Nicht kalibriert",
|
||||
"ui.3d_mouse_offer": "Die Kalibrierung Ihrer 3D-Maus behebt vertauschte oder umgekehrte Achsen und stellt Geschwindigkeit und Gefühl ein.",
|
||||
"ui.3d_mouse_offer_never": "Nicht mehr anzeigen",
|
||||
"ui.3d_mouse_offer_swap": "Zu Hidraw wechseln (direkter Treiber)",
|
||||
"ui.3d_mouse_reading": "Werte",
|
||||
"ui.3d_mouse_reset_axes": "Achsenzuordnung zurücksetzen",
|
||||
"ui.3d_mouse_reset_view": "Ansicht zurücksetzen",
|
||||
"ui.3d_mouse_restore": "3D-Maus-Standardwerte wiederherstellen",
|
||||
"ui.3d_mouse_restore_ask": "3D-Maus-Einstellungen auf die Standardwerte zurücksetzen? Kalibrierung und Tastenbelegung bleiben.",
|
||||
"ui.3d_mouse_section": "3D-Maus",
|
||||
"ui.3d_mouse_show_in_view": "3D-Maus in der 3D-Ansicht anzeigen",
|
||||
"ui.3d_mouse_show_in_view_tip": "Ein Bild Ihrer 3D-Maus unten links, das sich mit ihr bewegt.",
|
||||
"ui.3d_mouse_skip": "Mein Gerät kann das nicht",
|
||||
"ui.3d_mouse_skip_tip": "Überspringt diese Bewegung in beide Richtungen; die Kalibrierung lässt sie aus.",
|
||||
"ui.3d_mouse_speed": "Geschwindigkeit der 3D-Maus",
|
||||
"ui.3d_mouse_step_lift": "Kappe nach oben ziehen, halten, dann loslassen.",
|
||||
"ui.3d_mouse_step_press": "Kappe nach unten drücken, halten, dann loslassen.",
|
||||
"ui.3d_mouse_step_pull": "Kappe zu sich heranziehen, halten, dann loslassen.",
|
||||
"ui.3d_mouse_step_push": "Kappe von sich wegschieben, halten, dann loslassen.",
|
||||
"ui.3d_mouse_step_rest": "Nehmen Sie die Hand von der Kappe und lassen Sie sie ruhen.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Nehmen Sie die Hand von der Kugel und lassen Sie sie ruhen.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Drehen Sie den Scrollring ein paar Rasten im Uhrzeigersinn.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Drehen Sie den Scrollring ein paar Rasten gegen den Uhrzeigersinn.",
|
||||
"ui.3d_mouse_step_roll_away": "Rollen Sie die Kugel ein paarmal von sich weg und lassen Sie sie dann auslaufen.",
|
||||
"ui.3d_mouse_step_roll_left": "Rollen Sie die Kugel ein paarmal nach links und lassen Sie sie dann auslaufen.",
|
||||
"ui.3d_mouse_step_roll_right": "Rollen Sie die Kugel ein paarmal nach rechts und lassen Sie sie dann auslaufen.",
|
||||
"ui.3d_mouse_step_roll_toward": "Rollen Sie die Kugel ein paarmal zu sich hin und lassen Sie sie dann auslaufen.",
|
||||
"ui.3d_mouse_step_slide_left": "Kappe nach links schieben, halten, dann loslassen.",
|
||||
"ui.3d_mouse_step_slide_right": "Kappe nach rechts schieben, halten, dann loslassen.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Drehen Sie die Kugel ein paarmal im Uhrzeigersinn und lassen Sie sie dann auslaufen.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Drehen Sie die Kugel ein paarmal gegen den Uhrzeigersinn und lassen Sie sie dann auslaufen.",
|
||||
"ui.3d_mouse_step_tilt_away": "Oberseite der Kappe von sich weg kippen (die Ihnen nächste Kante geht nach oben), halten, dann loslassen.",
|
||||
"ui.3d_mouse_step_tilt_left": "Kappe nach links kippen (linke Kante runter), halten, dann loslassen.",
|
||||
"ui.3d_mouse_step_tilt_right": "Kappe nach rechts kippen (rechte Kante runter), halten, dann loslassen.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Oberseite der Kappe zu sich kippen (die Ihnen nächste Kante geht nach unten), halten, dann loslassen.",
|
||||
"ui.3d_mouse_step_touch": "Legen Sie die Hand auf die Kappe, ohne zu drücken.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Kappe im Uhrzeigersinn drehen, halten, dann loslassen.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Kappe gegen den Uhrzeigersinn drehen, halten, dann loslassen.",
|
||||
"ui.3d_mouse_switch_cap": "Als Zurück zur Mitte kalibrieren",
|
||||
"ui.3d_mouse_switch_trackball": "Als Trackball kalibrieren",
|
||||
"ui.3d_mouse_view_axis": "Ansichtsachse",
|
||||
"ui.3d_mouse_view_off": "Aus",
|
||||
"ui.3d_mouse_view_off_tip": "Das Profil des 3D-Maus-Treibers schaltet diese Achse ab, daher bewegt ihre Bewegung die Ansicht nicht. Wählen Sie hier die Bewegung einer anderen Achse, werden die beiden getauscht und sie bewegt die Ansicht nicht mehr.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbit links / rechts",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbit hoch / runter",
|
||||
"ui.3d_mouse_view_pan_sideways": "Schwenken links / rechts",
|
||||
"ui.3d_mouse_view_pan_up_down": "Schwenken hoch / runter",
|
||||
"ui.3d_mouse_view_roll": "Rollen",
|
||||
"ui.3d_mouse_view_unmapped": "Achse {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "Warte auf Bewegung",
|
||||
"ui.3d_mouse_wiggle": "Falls eine angeschlossen ist, bewegen Sie sie kurz.",
|
||||
"ui.3d_sketch": "3D-Skizze",
|
||||
"ui.activate": "Aktivieren",
|
||||
"ui.active_component": "Aktive Komponente",
|
||||
@@ -1478,6 +1577,7 @@
|
||||
"ui.blocked": "Verdeckt",
|
||||
"ui.bodies": "Körper",
|
||||
"ui.bodies_all_if_none": "Körper (alle, wenn keine)",
|
||||
"ui.bottom": "Unten",
|
||||
"ui.box": "Block",
|
||||
"ui.break": "Unterbrechen",
|
||||
"ui.browser_bodies_one": "{count} Körper",
|
||||
@@ -1935,59 +2035,6 @@
|
||||
"ui.smooth_wheel_zoom": "Sanftes Zoomen mit dem Rad",
|
||||
"ui.smooth_wheel_zoom_tip": "Ausschalten, um jedes Radereignis sofort anzuwenden, ohne Nachlauf",
|
||||
"ui.snap": "Einrasten",
|
||||
"ui.space_mouse_axes_reassigned": "Die Achsen sind in den Einstellungen neu zugeordnet, und das gilt auch nach dieser Kalibrierung.",
|
||||
"ui.space_mouse_calibrate": "Kalibrieren…",
|
||||
"ui.space_mouse_calibrate_tip": "Bewegen Sie die Kappe so, wie das Fenster es verlangt. So lernt SolidCraft, in welche Richtung und wie weit jede Achse dieser 3D-Maus geht. Wird für diese 3D-Maus gespeichert.",
|
||||
"ui.space_mouse_calibrate_title": "3D-Maus kalibrieren",
|
||||
"ui.space_mouse_calibrated": "Kalibriert",
|
||||
"ui.space_mouse_calibration": "3D-Maus-Kalibrierung",
|
||||
"ui.space_mouse_calibration_done": "Fertig. Speichern Sie die Kalibrierung, damit sie immer gilt, wenn diese 3D-Maus angeschlossen ist.",
|
||||
"ui.space_mouse_calibration_no_response": "Eine Achse hat sich kaum bewegt und lässt sich nicht kalibrieren. Beginnen Sie von vorn und bewegen Sie die Kappe jedes Mal weiter.",
|
||||
"ui.space_mouse_dead_zone": "Totzone",
|
||||
"ui.space_mouse_dead_zone_tip": "Wie weit sich die Kappe bewegen muss, bevor sich die Ansicht bewegt, als Anteil ihres vollen Wegs. Erhöhen Sie sie, wenn die Ansicht wandert, obwohl die Kappe ruht.",
|
||||
"ui.space_mouse_driven_by": "Welche kalibrierte Bewegung des Geräts diese Achse steuert. Wählen Sie eine, die schon eine andere Achse nutzt, werden die beiden getauscht.",
|
||||
"ui.space_mouse_driven_by_label": "gesteuert von",
|
||||
"ui.space_mouse_driver": "3D-Maus-Treiber",
|
||||
"ui.space_mouse_driver_auto": "Automatisch",
|
||||
"ui.space_mouse_driver_direct": "Direkt (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "Das Gerät wird direkt gelesen; spacenavd läuft nicht",
|
||||
"ui.space_mouse_driver_only_spacenavd": "Lesen über spacenavd, die einzige verfügbare Möglichkeit",
|
||||
"ui.space_mouse_forget": "Kalibrierung vergessen",
|
||||
"ui.space_mouse_hint": "Ansicht gleichzeitig verschieben und drehen; Skizzen verwenden nur Schwenken und Zoomen.",
|
||||
"ui.space_mouse_motion_rotation_x": "Rotation X",
|
||||
"ui.space_mouse_motion_rotation_y": "Rotation Y",
|
||||
"ui.space_mouse_motion_rotation_z": "Rotation Z",
|
||||
"ui.space_mouse_motion_translation_x": "Translation X",
|
||||
"ui.space_mouse_motion_translation_y": "Translation Y",
|
||||
"ui.space_mouse_motion_translation_z": "Translation Z",
|
||||
"ui.space_mouse_navigate": "Mit SpaceNavigator / SpaceMouse navigieren",
|
||||
"ui.space_mouse_none": "Keine 3D-Maus gefunden",
|
||||
"ui.space_mouse_not_calibrated": "Nicht kalibriert",
|
||||
"ui.space_mouse_reading": "Werte",
|
||||
"ui.space_mouse_reset_axes": "Achsenzuordnung zurücksetzen",
|
||||
"ui.space_mouse_step_lift": "Kappe nach oben ziehen, halten, dann loslassen.",
|
||||
"ui.space_mouse_step_press": "Kappe nach unten drücken, halten, dann loslassen.",
|
||||
"ui.space_mouse_step_pull": "Kappe zu sich heranziehen, halten, dann loslassen.",
|
||||
"ui.space_mouse_step_push": "Kappe von sich wegschieben, halten, dann loslassen.",
|
||||
"ui.space_mouse_step_rest": "Nehmen Sie die Hand von der Kappe und lassen Sie sie ruhen.",
|
||||
"ui.space_mouse_step_slide_left": "Kappe nach links schieben, halten, dann loslassen.",
|
||||
"ui.space_mouse_step_slide_right": "Kappe nach rechts schieben, halten, dann loslassen.",
|
||||
"ui.space_mouse_step_tilt_back": "Kappe nach hinten kippen (Vorderkante hoch), halten, dann loslassen.",
|
||||
"ui.space_mouse_step_tilt_forward": "Kappe nach vorn kippen (Vorderkante runter), halten, dann loslassen.",
|
||||
"ui.space_mouse_step_tilt_left": "Kappe nach links kippen (linke Kante runter), halten, dann loslassen.",
|
||||
"ui.space_mouse_step_tilt_right": "Kappe nach rechts kippen (rechte Kante runter), halten, dann loslassen.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Kappe im Uhrzeigersinn drehen, halten, dann loslassen.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Kappe gegen den Uhrzeigersinn drehen, halten, dann loslassen.",
|
||||
"ui.space_mouse_view_axis": "Ansichtsachse",
|
||||
"ui.space_mouse_view_off": "Aus",
|
||||
"ui.space_mouse_view_off_tip": "Das Profil des 3D-Maus-Treibers schaltet diese Achse ab, daher bewegt ihre Bewegung die Ansicht nicht. Wählen Sie hier die Bewegung einer anderen Achse, werden die beiden getauscht und sie bewegt die Ansicht nicht mehr.",
|
||||
"ui.space_mouse_view_orbit_sideways": "Orbit links / rechts",
|
||||
"ui.space_mouse_view_orbit_up_down": "Orbit hoch / runter",
|
||||
"ui.space_mouse_view_pan_sideways": "Schwenken links / rechts",
|
||||
"ui.space_mouse_view_pan_up_down": "Schwenken hoch / runter",
|
||||
"ui.space_mouse_view_roll": "Rollen",
|
||||
"ui.space_mouse_view_unmapped": "Achse {n}",
|
||||
"ui.space_mouse_view_zoom": "Zoom",
|
||||
"ui.spacing": "Abstand",
|
||||
"ui.spacing_2": "Abstand 2",
|
||||
"ui.sphere": "Sphäre",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"pattern.alignment_hint": "Counts include the original. Centre balances copies; Flip chooses the extra side for even totals.",
|
||||
"render.opaque_preview_help": "Opaque PBR and simple colours; blended transparency. Refraction, HDRI and final rendering are not available yet.",
|
||||
"sketch_text.align_center": "Centre",
|
||||
"ui.3d_mouse_step_pull": "Pull the cap towards you, hold it, then let go.",
|
||||
"ui.center": "Centre",
|
||||
"ui.centerline": "Centreline",
|
||||
"ui.customizeToolbar": "Customise Toolbar…",
|
||||
@@ -24,7 +25,6 @@
|
||||
"ui.layout_compact": "Compact",
|
||||
"ui.layout_floating": "Floating",
|
||||
"ui.ribbon_labels": "Show text under ribbon icons",
|
||||
"ui.space_mouse_step_pull": "Pull the cap towards you, hold it, then let go.",
|
||||
"ui.workspace_layout": "Workspace layout",
|
||||
"ui.zoom_to_cursor_tip": "The wheel zooms towards the point under the cursor (off: towards the view's centre)",
|
||||
"update.prefs_flatpak": "Flatpak updates this copy ({version}): run {command} or use your software centre. SolidCraft doesn't check."
|
||||
|
||||
+100
-53
@@ -1461,7 +1461,106 @@
|
||||
"trace.stopped_by_contact": "Ends at sample {sample}: {a} would pass through {b}",
|
||||
"trace.stopped_by_loop": "Ends at sample {sample}: the loop through {joint} can't close any further",
|
||||
"ui.1_degree_of_freedom": "1 degree of freedom",
|
||||
"ui.3d_mouse_axes_reassigned": "Axes are reassigned in Preferences, and that still applies on top of this calibration.",
|
||||
"ui.3d_mouse_button": "Button {number}",
|
||||
"ui.3d_mouse_button_nothing": "Nothing",
|
||||
"ui.3d_mouse_button_settings": "3D mouse settings",
|
||||
"ui.3d_mouse_buttons": "Buttons",
|
||||
"ui.3d_mouse_buttons_hint": "Press a button on your 3D mouse to find it here, then pick what it does.",
|
||||
"ui.3d_mouse_buttons_no_device": "Connect a 3D mouse to set what its buttons do.",
|
||||
"ui.3d_mouse_buttons_reset": "Restore default buttons",
|
||||
"ui.3d_mouse_calibrate": "Calibrate…",
|
||||
"ui.3d_mouse_calibrate_tip": "Move the cap as the window asks, so SolidCraft learns which way each axis of this 3D mouse goes and how far. Saved for this 3D mouse.",
|
||||
"ui.3d_mouse_calibrate_title": "Calibrate 3D mouse",
|
||||
"ui.3d_mouse_calibrate_wait": "Move the 3D mouse once so SolidCraft can see it.",
|
||||
"ui.3d_mouse_calibrated": "Calibrated",
|
||||
"ui.3d_mouse_calibration": "Calibration",
|
||||
"ui.3d_mouse_calibration_done": "Done. Save it to use it whenever this 3D mouse is connected.",
|
||||
"ui.3d_mouse_calibration_no_response": "One axis barely moved, so it can't be calibrated. Start over and move the cap further each time.",
|
||||
"ui.3d_mouse_circles_help": "The circles show what the device sends, after calibration. The test model shows what the view does with it, after invert, speed and the axis assignment.",
|
||||
"ui.3d_mouse_dead_zone": "Dead zone",
|
||||
"ui.3d_mouse_dead_zone_tip": "How far the cap has to move before the view does, as a share of its full travel. Raise it if the view drifts while the cap is at rest.",
|
||||
"ui.3d_mouse_denied": "SolidCraft can see a 3D mouse but isn't allowed to read it. See the 3D mouse setup notes for the udev rule.",
|
||||
"ui.3d_mouse_denied_short": "Found, but not readable",
|
||||
"ui.3d_mouse_driven_by": "Which calibrated motion of the device drives this axis. Picking a motion another axis already uses swaps the two.",
|
||||
"ui.3d_mouse_driven_by_label": "driven by",
|
||||
"ui.3d_mouse_driver": "Driver",
|
||||
"ui.3d_mouse_driver_auto": "Automatic",
|
||||
"ui.3d_mouse_driver_direct": "Direct (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Reading through spacenavd for now: the device itself could not be opened.",
|
||||
"ui.3d_mouse_driver_only_direct": "Reading the device directly; spacenavd is not running",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Reading through spacenavd, the only way available",
|
||||
"ui.3d_mouse_forget": "Forget calibration",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Left viewport",
|
||||
"ui.3d_mouse_ghost_left_window": "Left window",
|
||||
"ui.3d_mouse_ghost_position": "Position",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Right viewport",
|
||||
"ui.3d_mouse_hint": "Move and rotate the view together; sketches use pan and zoom only.",
|
||||
"ui.3d_mouse_kind_cap": "Return to Center",
|
||||
"ui.3d_mouse_kind_question": "What kind of 3D mouse is it?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "The reading stays put while you hold it, the way a Return to Center device does.",
|
||||
"ui.3d_mouse_looks_like_trackball": "The reading keeps dropping back as soon as you stop moving, the way a trackball's does.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotation X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotation Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotation Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Translation X",
|
||||
"ui.3d_mouse_motion_translation_y": "Translation Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Translation Z",
|
||||
"ui.3d_mouse_navigate": "Navigate with the 3D mouse",
|
||||
"ui.3d_mouse_none": "No 3D mouse found",
|
||||
"ui.3d_mouse_not_calibrated": "Not calibrated",
|
||||
"ui.3d_mouse_offer": "Calibrating your 3D mouse fixes swapped or reversed axes and sets its speed and feel.",
|
||||
"ui.3d_mouse_offer_never": "Never show again",
|
||||
"ui.3d_mouse_offer_swap": "Swap to Hidraw (Direct Driver)",
|
||||
"ui.3d_mouse_reading": "Reading",
|
||||
"ui.3d_mouse_reset_axes": "Reset axis assignment",
|
||||
"ui.3d_mouse_reset_view": "Reset view",
|
||||
"ui.3d_mouse_restore": "Restore defaults",
|
||||
"ui.3d_mouse_restore_ask": "Set the 3D mouse settings back to their defaults? Your calibration and button mapping stay.",
|
||||
"ui.3d_mouse_section": "3D mouse",
|
||||
"ui.3d_mouse_show_in_view": "Show the 3D mouse in the 3D view",
|
||||
"ui.3d_mouse_show_in_view_tip": "A picture of your 3D mouse in the lower left corner, moving as you move it.",
|
||||
"ui.3d_mouse_skip": "My device can't do this",
|
||||
"ui.3d_mouse_skip_tip": "Skips this motion both ways, and the calibration leaves it out.",
|
||||
"ui.3d_mouse_speed": "3D mouse speed",
|
||||
"ui.3d_mouse_step_lift": "Lift the cap up, hold it, then let go.",
|
||||
"ui.3d_mouse_step_press": "Press the cap down, hold it, then let go.",
|
||||
"ui.3d_mouse_step_pull": "Pull the cap toward you, hold it, then let go.",
|
||||
"ui.3d_mouse_step_push": "Push the cap away from you, hold it, then let go.",
|
||||
"ui.3d_mouse_step_rest": "Take your hand off the cap and leave it still.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Take your hand off the ball and leave it still.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Turn the scroll ring clockwise a few clicks.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Turn the scroll ring anticlockwise a few clicks.",
|
||||
"ui.3d_mouse_step_roll_away": "Roll the ball away from you a few times, then let it stop.",
|
||||
"ui.3d_mouse_step_roll_left": "Roll the ball left a few times, then let it stop.",
|
||||
"ui.3d_mouse_step_roll_right": "Roll the ball right a few times, then let it stop.",
|
||||
"ui.3d_mouse_step_roll_toward": "Roll the ball toward you a few times, then let it stop.",
|
||||
"ui.3d_mouse_step_slide_left": "Slide the cap left, hold it, then let go.",
|
||||
"ui.3d_mouse_step_slide_right": "Slide the cap right, hold it, then let go.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Spin the ball clockwise a few times, then let it stop.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Spin the ball anticlockwise a few times, then let it stop.",
|
||||
"ui.3d_mouse_step_tilt_away": "Tilt the top of the cap away from you (the edge nearest you goes up), hold it, then let go.",
|
||||
"ui.3d_mouse_step_tilt_left": "Tilt the cap left (left edge down), hold it, then let go.",
|
||||
"ui.3d_mouse_step_tilt_right": "Tilt the cap right (right edge down), hold it, then let go.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Tilt the top of the cap toward you (the edge nearest you goes down), hold it, then let go.",
|
||||
"ui.3d_mouse_step_touch": "Rest your hand on the cap without pushing it.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Twist the cap clockwise, hold it, then let go.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Twist the cap anticlockwise, hold it, then let go.",
|
||||
"ui.3d_mouse_switch_cap": "Calibrate as Return to Center",
|
||||
"ui.3d_mouse_switch_trackball": "Calibrate as a trackball",
|
||||
"ui.3d_mouse_view_axis": "View axis",
|
||||
"ui.3d_mouse_view_off": "Off",
|
||||
"ui.3d_mouse_view_off_tip": "The 3D mouse driver's profile turns this axis off, so its motion doesn't move the view. Picking another axis's motion here swaps the two, and that motion stops moving the view.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbit Y",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbit X",
|
||||
"ui.3d_mouse_view_pan_sideways": "Pan X",
|
||||
"ui.3d_mouse_view_pan_up_down": "Pan Y",
|
||||
"ui.3d_mouse_view_roll": "Roll",
|
||||
"ui.3d_mouse_view_unmapped": "Axis {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "Waiting for Movement",
|
||||
"ui.3d_mouse_wiggle": "If you have one plugged in, wiggle it.",
|
||||
"ui.3d_sketch": "3D Sketch",
|
||||
"ui.activate": "Activate",
|
||||
"ui.active_component": "Active component",
|
||||
@@ -1699,6 +1798,7 @@
|
||||
"ui.blocked": "Blocked",
|
||||
"ui.bodies": "Bodies",
|
||||
"ui.bodies_all_if_none": "Bodies (all if none)",
|
||||
"ui.bottom": "Bottom",
|
||||
"ui.box": "Box",
|
||||
"ui.break": "Break",
|
||||
"ui.browser_bodies_one": "{count} body",
|
||||
@@ -2165,59 +2265,6 @@
|
||||
"ui.smooth_wheel_zoom": "Smooth wheel zoom",
|
||||
"ui.smooth_wheel_zoom_tip": "Turn off to apply each wheel event immediately, without a smoothing tail",
|
||||
"ui.snap": "Snap",
|
||||
"ui.space_mouse_axes_reassigned": "Axes are reassigned in Preferences, and that still applies on top of this calibration.",
|
||||
"ui.space_mouse_calibrate": "Calibrate…",
|
||||
"ui.space_mouse_calibrate_tip": "Move the cap as the window asks, so SolidCraft learns which way each axis of this 3D mouse goes and how far. Saved for this 3D mouse.",
|
||||
"ui.space_mouse_calibrate_title": "Calibrate 3D mouse",
|
||||
"ui.space_mouse_calibrated": "Calibrated",
|
||||
"ui.space_mouse_calibration": "3D mouse calibration",
|
||||
"ui.space_mouse_calibration_done": "Done. Save it to use it whenever this 3D mouse is connected.",
|
||||
"ui.space_mouse_calibration_no_response": "One axis barely moved, so it can't be calibrated. Start over and move the cap further each time.",
|
||||
"ui.space_mouse_dead_zone": "Dead zone",
|
||||
"ui.space_mouse_dead_zone_tip": "How far the cap has to move before the view does, as a share of its full travel. Raise it if the view drifts while the cap is at rest.",
|
||||
"ui.space_mouse_driven_by": "Which calibrated motion of the device drives this axis. Picking a motion another axis already uses swaps the two.",
|
||||
"ui.space_mouse_driven_by_label": "driven by",
|
||||
"ui.space_mouse_driver": "3D mouse driver",
|
||||
"ui.space_mouse_driver_auto": "Automatic",
|
||||
"ui.space_mouse_driver_direct": "Direct (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "Reading the device directly; spacenavd is not running",
|
||||
"ui.space_mouse_driver_only_spacenavd": "Reading through spacenavd, the only way available",
|
||||
"ui.space_mouse_forget": "Forget calibration",
|
||||
"ui.space_mouse_hint": "Move and rotate the view together; sketches use pan and zoom only.",
|
||||
"ui.space_mouse_motion_rotation_x": "Rotation X",
|
||||
"ui.space_mouse_motion_rotation_y": "Rotation Y",
|
||||
"ui.space_mouse_motion_rotation_z": "Rotation Z",
|
||||
"ui.space_mouse_motion_translation_x": "Translation X",
|
||||
"ui.space_mouse_motion_translation_y": "Translation Y",
|
||||
"ui.space_mouse_motion_translation_z": "Translation Z",
|
||||
"ui.space_mouse_navigate": "Navigate with SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "No 3D mouse found",
|
||||
"ui.space_mouse_not_calibrated": "Not calibrated",
|
||||
"ui.space_mouse_reading": "Reading",
|
||||
"ui.space_mouse_reset_axes": "Reset axis assignment",
|
||||
"ui.space_mouse_step_lift": "Lift the cap up, hold it, then let go.",
|
||||
"ui.space_mouse_step_press": "Press the cap down, hold it, then let go.",
|
||||
"ui.space_mouse_step_pull": "Pull the cap toward you, hold it, then let go.",
|
||||
"ui.space_mouse_step_push": "Push the cap away from you, hold it, then let go.",
|
||||
"ui.space_mouse_step_rest": "Take your hand off the cap and leave it still.",
|
||||
"ui.space_mouse_step_slide_left": "Slide the cap left, hold it, then let go.",
|
||||
"ui.space_mouse_step_slide_right": "Slide the cap right, hold it, then let go.",
|
||||
"ui.space_mouse_step_tilt_back": "Tilt the cap back (front edge up), hold it, then let go.",
|
||||
"ui.space_mouse_step_tilt_forward": "Tilt the cap forward (front edge down), hold it, then let go.",
|
||||
"ui.space_mouse_step_tilt_left": "Tilt the cap left (left edge down), hold it, then let go.",
|
||||
"ui.space_mouse_step_tilt_right": "Tilt the cap right (right edge down), hold it, then let go.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Twist the cap clockwise, hold it, then let go.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Twist the cap anticlockwise, hold it, then let go.",
|
||||
"ui.space_mouse_view_axis": "View axis",
|
||||
"ui.space_mouse_view_off": "Off",
|
||||
"ui.space_mouse_view_off_tip": "The 3D mouse driver's profile turns this axis off, so its motion doesn't move the view. Picking another axis's motion here swaps the two, and that motion stops moving the view.",
|
||||
"ui.space_mouse_view_orbit_sideways": "Orbit left / right",
|
||||
"ui.space_mouse_view_orbit_up_down": "Orbit up / down",
|
||||
"ui.space_mouse_view_pan_sideways": "Pan left / right",
|
||||
"ui.space_mouse_view_pan_up_down": "Pan up / down",
|
||||
"ui.space_mouse_view_roll": "Roll",
|
||||
"ui.space_mouse_view_unmapped": "Axis {n}",
|
||||
"ui.space_mouse_view_zoom": "Zoom",
|
||||
"ui.spacing": "Spacing",
|
||||
"ui.spacing_2": "Spacing 2",
|
||||
"ui.sphere": "Sphere",
|
||||
|
||||
+100
-53
@@ -1133,7 +1133,106 @@
|
||||
"trace.stopped_by_contact": "Termina en la muestra {sample}: {a} atravesaría {b}",
|
||||
"trace.stopped_by_loop": "Termina en la muestra {sample}: el bucle que pasa por {joint} no puede cerrarse más",
|
||||
"ui.1_degree_of_freedom": "1 grado de libertad",
|
||||
"ui.3d_mouse_axes_reassigned": "Los ejes están reasignados en Preferencias, y eso sigue aplicándose sobre esta calibración.",
|
||||
"ui.3d_mouse_button": "Botón {number}",
|
||||
"ui.3d_mouse_button_nothing": "Nada",
|
||||
"ui.3d_mouse_button_settings": "Ajustes del ratón 3D",
|
||||
"ui.3d_mouse_buttons": "Botones",
|
||||
"ui.3d_mouse_buttons_hint": "Pulsa un botón de tu ratón 3D para encontrarlo aquí y elige qué hace.",
|
||||
"ui.3d_mouse_buttons_no_device": "Conecta un ratón 3D para elegir qué hacen sus botones.",
|
||||
"ui.3d_mouse_buttons_reset": "Restaurar los botones predeterminados",
|
||||
"ui.3d_mouse_calibrate": "Calibrar…",
|
||||
"ui.3d_mouse_calibrate_tip": "Mueve el capuchón como pide la ventana para que SolidCraft aprenda hacia dónde y hasta dónde va cada eje de este ratón 3D. Se guarda para este ratón 3D.",
|
||||
"ui.3d_mouse_calibrate_title": "Calibrar ratón 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Mueve el ratón 3D una vez para que SolidCraft lo vea.",
|
||||
"ui.3d_mouse_calibrated": "Calibrado",
|
||||
"ui.3d_mouse_calibration": "Calibración del ratón 3D",
|
||||
"ui.3d_mouse_calibration_done": "Listo. Guárdala para usarla siempre que este ratón 3D esté conectado.",
|
||||
"ui.3d_mouse_calibration_no_response": "Un eje apenas se movió, así que no se puede calibrar. Vuelve a empezar y mueve el capuchón más lejos cada vez.",
|
||||
"ui.3d_mouse_circles_help": "Los círculos muestran lo que envía el dispositivo tras la calibración. El modelo de prueba muestra lo que hace la vista con ello, tras la inversión, la velocidad y la asignación de ejes.",
|
||||
"ui.3d_mouse_dead_zone": "Zona muerta",
|
||||
"ui.3d_mouse_dead_zone_tip": "Cuánto tiene que moverse el capuchón antes de que se mueva la vista, como parte de su recorrido completo. Auméntala si la vista se desplaza con el capuchón en reposo.",
|
||||
"ui.3d_mouse_denied": "SolidCraft ve un ratón 3D, pero no tiene permiso para leerlo. La regla udev está en las notas de configuración del ratón 3D.",
|
||||
"ui.3d_mouse_denied_short": "Encontrado, pero no se puede leer",
|
||||
"ui.3d_mouse_driven_by": "Qué movimiento calibrado del dispositivo controla este eje. Si eliges uno que ya usa otro eje, se intercambian.",
|
||||
"ui.3d_mouse_driven_by_label": "controlado por",
|
||||
"ui.3d_mouse_driver": "Controlador del ratón 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automático",
|
||||
"ui.3d_mouse_driver_direct": "Directo (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Leyendo por ahora a través de spacenavd: no se pudo abrir el dispositivo.",
|
||||
"ui.3d_mouse_driver_only_direct": "Lectura directa del dispositivo; spacenavd no se está ejecutando",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Lectura mediante spacenavd, la única opción disponible",
|
||||
"ui.3d_mouse_forget": "Olvidar calibración",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Vista izquierda",
|
||||
"ui.3d_mouse_ghost_left_window": "Ventana izquierda",
|
||||
"ui.3d_mouse_ghost_position": "Posición",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Vista derecha",
|
||||
"ui.3d_mouse_hint": "Desplaza y gira la vista a la vez; los bocetos solo permiten desplazamiento y zoom.",
|
||||
"ui.3d_mouse_kind_cap": "Retorno al centro",
|
||||
"ui.3d_mouse_kind_question": "¿Qué tipo de ratón 3D es?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "La lectura se mantiene mientras sujetas, como en un dispositivo de tipo «Retorno al centro».",
|
||||
"ui.3d_mouse_looks_like_trackball": "La lectura vuelve atrás en cuanto dejas de moverte, como en un trackball.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotación X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotación Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotación Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Traslación X",
|
||||
"ui.3d_mouse_motion_translation_y": "Traslación Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Traslación Z",
|
||||
"ui.3d_mouse_navigate": "Navegar con el ratón 3D",
|
||||
"ui.3d_mouse_none": "No se encontró ningún ratón 3D",
|
||||
"ui.3d_mouse_not_calibrated": "Sin calibrar",
|
||||
"ui.3d_mouse_offer": "Calibrar tu ratón 3D corrige los ejes intercambiados o invertidos y ajusta su velocidad y sensación.",
|
||||
"ui.3d_mouse_offer_never": "No volver a mostrar",
|
||||
"ui.3d_mouse_offer_swap": "Cambiar a Hidraw (controlador directo)",
|
||||
"ui.3d_mouse_reading": "Lectura",
|
||||
"ui.3d_mouse_reset_axes": "Restablecer asignación de ejes",
|
||||
"ui.3d_mouse_reset_view": "Restablecer vista",
|
||||
"ui.3d_mouse_restore": "Restablecer valores del ratón 3D",
|
||||
"ui.3d_mouse_restore_ask": "¿Volver a los valores predeterminados de la configuración del ratón 3D? La calibración y la asignación de botones se mantienen.",
|
||||
"ui.3d_mouse_section": "Ratón 3D",
|
||||
"ui.3d_mouse_show_in_view": "Mostrar el ratón 3D en la vista 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Una imagen de tu ratón 3D en la esquina inferior izquierda que se mueve cuando lo mueves.",
|
||||
"ui.3d_mouse_skip": "Mi dispositivo no puede hacer esto",
|
||||
"ui.3d_mouse_skip_tip": "Omite este movimiento en ambos sentidos; la calibración lo deja fuera.",
|
||||
"ui.3d_mouse_speed": "Velocidad del ratón 3D",
|
||||
"ui.3d_mouse_step_lift": "Levanta el capuchón, mantenlo y suéltalo.",
|
||||
"ui.3d_mouse_step_press": "Presiona el capuchón hacia abajo, mantenlo y suéltalo.",
|
||||
"ui.3d_mouse_step_pull": "Tira del capuchón hacia ti, mantenlo y suéltalo.",
|
||||
"ui.3d_mouse_step_push": "Empuja el capuchón lejos de ti, mantenlo y suéltalo.",
|
||||
"ui.3d_mouse_step_rest": "Suelta el capuchón y déjalo quieto.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Suelta la bola y déjala quieta.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Gira el anillo de desplazamiento unos clics en sentido horario.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Gira el anillo de desplazamiento unos clics en sentido antihorario.",
|
||||
"ui.3d_mouse_step_roll_away": "Haz rodar la bola alejándola de ti unas cuantas veces y deja que se pare.",
|
||||
"ui.3d_mouse_step_roll_left": "Haz rodar la bola a la izquierda unas cuantas veces y deja que se pare.",
|
||||
"ui.3d_mouse_step_roll_right": "Haz rodar la bola a la derecha unas cuantas veces y deja que se pare.",
|
||||
"ui.3d_mouse_step_roll_toward": "Haz rodar la bola hacia ti unas cuantas veces y deja que se pare.",
|
||||
"ui.3d_mouse_step_slide_left": "Desliza el capuchón a la izquierda, mantenlo y suéltalo.",
|
||||
"ui.3d_mouse_step_slide_right": "Desliza el capuchón a la derecha, mantenlo y suéltalo.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Gira la bola en sentido horario unas cuantas veces y deja que se pare.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Gira la bola en sentido antihorario unas cuantas veces y deja que se pare.",
|
||||
"ui.3d_mouse_step_tilt_away": "Inclina la parte de arriba del capuchón alejándola de ti (el borde más cercano sube), mantenlo y suéltalo.",
|
||||
"ui.3d_mouse_step_tilt_left": "Inclina el capuchón a la izquierda (borde izquierdo abajo), mantenlo y suéltalo.",
|
||||
"ui.3d_mouse_step_tilt_right": "Inclina el capuchón a la derecha (borde derecho abajo), mantenlo y suéltalo.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Inclina la parte de arriba del capuchón hacia ti (el borde más cercano baja), mantenlo y suéltalo.",
|
||||
"ui.3d_mouse_step_touch": "Apoya la mano en el capuchón sin empujarlo.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Gira el capuchón en sentido horario, mantenlo y suéltalo.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Gira el capuchón en sentido antihorario, mantenlo y suéltalo.",
|
||||
"ui.3d_mouse_switch_cap": "Calibrar como retorno al centro",
|
||||
"ui.3d_mouse_switch_trackball": "Calibrar como trackball",
|
||||
"ui.3d_mouse_view_axis": "Eje de la vista",
|
||||
"ui.3d_mouse_view_off": "Desactivado",
|
||||
"ui.3d_mouse_view_off_tip": "El perfil del controlador del ratón 3D desactiva este eje, así que su movimiento no mueve la vista. Si eliges aquí el movimiento de otro eje, se intercambian y ese movimiento deja de mover la vista.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbitar izquierda / derecha",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbitar arriba / abajo",
|
||||
"ui.3d_mouse_view_pan_sideways": "Desplazar izquierda / derecha",
|
||||
"ui.3d_mouse_view_pan_up_down": "Desplazar arriba / abajo",
|
||||
"ui.3d_mouse_view_roll": "Girar sobre la vista",
|
||||
"ui.3d_mouse_view_unmapped": "Eje {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "Esperando movimiento",
|
||||
"ui.3d_mouse_wiggle": "Si tienes uno conectado, muévelo un poco.",
|
||||
"ui.3d_sketch": "Boceto 3D",
|
||||
"ui.activate": "Activar",
|
||||
"ui.active_component": "Componente activo",
|
||||
@@ -1365,6 +1464,7 @@
|
||||
"ui.blocked": "Bloqueado",
|
||||
"ui.bodies": "Cuerpos",
|
||||
"ui.bodies_all_if_none": "Cuerpos (todos si no hay ninguno)",
|
||||
"ui.bottom": "Inferior",
|
||||
"ui.box": "Caja",
|
||||
"ui.break": "Romper",
|
||||
"ui.browser_bodies_one": "{count} cuerpo",
|
||||
@@ -1808,59 +1908,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zoom suave con la rueda",
|
||||
"ui.smooth_wheel_zoom_tip": "Desactívalo para aplicar cada movimiento de la rueda inmediatamente, sin suavizado posterior",
|
||||
"ui.snap": "Referencia a objetos",
|
||||
"ui.space_mouse_axes_reassigned": "Los ejes están reasignados en Preferencias, y eso sigue aplicándose sobre esta calibración.",
|
||||
"ui.space_mouse_calibrate": "Calibrar…",
|
||||
"ui.space_mouse_calibrate_tip": "Mueve el capuchón como pide la ventana para que SolidCraft aprenda hacia dónde y hasta dónde va cada eje de este ratón 3D. Se guarda para este ratón 3D.",
|
||||
"ui.space_mouse_calibrate_title": "Calibrar ratón 3D",
|
||||
"ui.space_mouse_calibrated": "Calibrado",
|
||||
"ui.space_mouse_calibration": "Calibración del ratón 3D",
|
||||
"ui.space_mouse_calibration_done": "Listo. Guárdala para usarla siempre que este ratón 3D esté conectado.",
|
||||
"ui.space_mouse_calibration_no_response": "Un eje apenas se movió, así que no se puede calibrar. Vuelve a empezar y mueve el capuchón más lejos cada vez.",
|
||||
"ui.space_mouse_dead_zone": "Zona muerta",
|
||||
"ui.space_mouse_dead_zone_tip": "Cuánto tiene que moverse el capuchón antes de que se mueva la vista, como parte de su recorrido completo. Auméntala si la vista se desplaza con el capuchón en reposo.",
|
||||
"ui.space_mouse_driven_by": "Qué movimiento calibrado del dispositivo controla este eje. Si eliges uno que ya usa otro eje, se intercambian.",
|
||||
"ui.space_mouse_driven_by_label": "controlado por",
|
||||
"ui.space_mouse_driver": "Controlador del ratón 3D",
|
||||
"ui.space_mouse_driver_auto": "Automático",
|
||||
"ui.space_mouse_driver_direct": "Directo (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "Lectura directa del dispositivo; spacenavd no se está ejecutando",
|
||||
"ui.space_mouse_driver_only_spacenavd": "Lectura mediante spacenavd, la única opción disponible",
|
||||
"ui.space_mouse_forget": "Olvidar calibración",
|
||||
"ui.space_mouse_hint": "Desplaza y gira la vista a la vez; los bocetos solo permiten desplazamiento y zoom.",
|
||||
"ui.space_mouse_motion_rotation_x": "Rotación X",
|
||||
"ui.space_mouse_motion_rotation_y": "Rotación Y",
|
||||
"ui.space_mouse_motion_rotation_z": "Rotación Z",
|
||||
"ui.space_mouse_motion_translation_x": "Traslación X",
|
||||
"ui.space_mouse_motion_translation_y": "Traslación Y",
|
||||
"ui.space_mouse_motion_translation_z": "Traslación Z",
|
||||
"ui.space_mouse_navigate": "Navegar con SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "No se encontró ningún ratón 3D",
|
||||
"ui.space_mouse_not_calibrated": "Sin calibrar",
|
||||
"ui.space_mouse_reading": "Lectura",
|
||||
"ui.space_mouse_reset_axes": "Restablecer asignación de ejes",
|
||||
"ui.space_mouse_step_lift": "Levanta el capuchón, mantenlo y suéltalo.",
|
||||
"ui.space_mouse_step_press": "Presiona el capuchón hacia abajo, mantenlo y suéltalo.",
|
||||
"ui.space_mouse_step_pull": "Tira del capuchón hacia ti, mantenlo y suéltalo.",
|
||||
"ui.space_mouse_step_push": "Empuja el capuchón lejos de ti, mantenlo y suéltalo.",
|
||||
"ui.space_mouse_step_rest": "Suelta el capuchón y déjalo quieto.",
|
||||
"ui.space_mouse_step_slide_left": "Desliza el capuchón a la izquierda, mantenlo y suéltalo.",
|
||||
"ui.space_mouse_step_slide_right": "Desliza el capuchón a la derecha, mantenlo y suéltalo.",
|
||||
"ui.space_mouse_step_tilt_back": "Inclina el capuchón hacia atrás (borde delantero arriba), mantenlo y suéltalo.",
|
||||
"ui.space_mouse_step_tilt_forward": "Inclina el capuchón hacia delante (borde delantero abajo), mantenlo y suéltalo.",
|
||||
"ui.space_mouse_step_tilt_left": "Inclina el capuchón a la izquierda (borde izquierdo abajo), mantenlo y suéltalo.",
|
||||
"ui.space_mouse_step_tilt_right": "Inclina el capuchón a la derecha (borde derecho abajo), mantenlo y suéltalo.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Gira el capuchón en sentido horario, mantenlo y suéltalo.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Gira el capuchón en sentido antihorario, mantenlo y suéltalo.",
|
||||
"ui.space_mouse_view_axis": "Eje de la vista",
|
||||
"ui.space_mouse_view_off": "Desactivado",
|
||||
"ui.space_mouse_view_off_tip": "El perfil del controlador del ratón 3D desactiva este eje, así que su movimiento no mueve la vista. Si eliges aquí el movimiento de otro eje, se intercambian y ese movimiento deja de mover la vista.",
|
||||
"ui.space_mouse_view_orbit_sideways": "Orbitar izquierda / derecha",
|
||||
"ui.space_mouse_view_orbit_up_down": "Orbitar arriba / abajo",
|
||||
"ui.space_mouse_view_pan_sideways": "Desplazar izquierda / derecha",
|
||||
"ui.space_mouse_view_pan_up_down": "Desplazar arriba / abajo",
|
||||
"ui.space_mouse_view_roll": "Girar sobre la vista",
|
||||
"ui.space_mouse_view_unmapped": "Eje {n}",
|
||||
"ui.space_mouse_view_zoom": "Zoom",
|
||||
"ui.spacing": "Espaciado",
|
||||
"ui.spacing_2": "Espaciado 2",
|
||||
"ui.sphere": "Esfera",
|
||||
|
||||
+100
-53
@@ -1133,7 +1133,106 @@
|
||||
"trace.stopped_by_contact": "Se termine à l'échantillon {sample} : {a} traverserait {b}",
|
||||
"trace.stopped_by_loop": "Se termine à l'échantillon {sample} : la boucle passant par {joint} ne peut plus se fermer",
|
||||
"ui.1_degree_of_freedom": "1 degré de liberté",
|
||||
"ui.3d_mouse_axes_reassigned": "Les axes sont réattribués dans les Préférences, et cela s'applique toujours en plus de ce calibrage.",
|
||||
"ui.3d_mouse_button": "Bouton {number}",
|
||||
"ui.3d_mouse_button_nothing": "Rien",
|
||||
"ui.3d_mouse_button_settings": "Réglages de la souris 3D",
|
||||
"ui.3d_mouse_buttons": "Boutons",
|
||||
"ui.3d_mouse_buttons_hint": "Appuyez sur un bouton de votre souris 3D pour le trouver ici, puis choisissez ce qu'il fait.",
|
||||
"ui.3d_mouse_buttons_no_device": "Branchez une souris 3D pour choisir ce que font ses boutons.",
|
||||
"ui.3d_mouse_buttons_reset": "Rétablir les boutons par défaut",
|
||||
"ui.3d_mouse_calibrate": "Calibrer…",
|
||||
"ui.3d_mouse_calibrate_tip": "Déplacez le capuchon comme la fenêtre le demande, pour que SolidCraft apprenne dans quel sens et jusqu'où va chaque axe de cette souris 3D. Enregistré pour cette souris 3D.",
|
||||
"ui.3d_mouse_calibrate_title": "Calibrer la souris 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Bougez la souris 3D une fois pour que SolidCraft la voie.",
|
||||
"ui.3d_mouse_calibrated": "Calibrée",
|
||||
"ui.3d_mouse_calibration": "Calibrage de la souris 3D",
|
||||
"ui.3d_mouse_calibration_done": "Terminé. Enregistrez-le pour l'utiliser chaque fois que cette souris 3D est connectée.",
|
||||
"ui.3d_mouse_calibration_no_response": "Un axe a à peine bougé, il ne peut donc pas être calibré. Recommencez en déplaçant le capuchon plus loin à chaque fois.",
|
||||
"ui.3d_mouse_circles_help": "Les cercles montrent ce que l'appareil envoie après calibrage. Le modèle d'essai montre ce que la vue en fait, après inversion, vitesse et affectation des axes.",
|
||||
"ui.3d_mouse_dead_zone": "Zone morte",
|
||||
"ui.3d_mouse_dead_zone_tip": "De combien le capuchon doit bouger avant que la vue ne bouge, en part de sa course complète. Augmentez-la si la vue dérive alors que le capuchon est au repos.",
|
||||
"ui.3d_mouse_denied": "SolidCraft voit une souris 3D mais n'a pas le droit de la lire. La règle udev est dans les notes d'installation de la souris 3D.",
|
||||
"ui.3d_mouse_denied_short": "Trouvée, mais illisible",
|
||||
"ui.3d_mouse_driven_by": "Quel mouvement calibré de l'appareil pilote cet axe. Choisir un mouvement déjà utilisé par un autre axe les échange.",
|
||||
"ui.3d_mouse_driven_by_label": "piloté par",
|
||||
"ui.3d_mouse_driver": "Pilote de la souris 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automatique",
|
||||
"ui.3d_mouse_driver_direct": "Direct (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Lecture via spacenavd pour l'instant : l'appareil lui-même n'a pas pu être ouvert.",
|
||||
"ui.3d_mouse_driver_only_direct": "Lecture directe du périphérique ; spacenavd n'est pas lancé",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Lecture via spacenavd, seule possibilité disponible",
|
||||
"ui.3d_mouse_forget": "Oublier le calibrage",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Vue à gauche",
|
||||
"ui.3d_mouse_ghost_left_window": "Fenêtre à gauche",
|
||||
"ui.3d_mouse_ghost_position": "Position",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Vue à droite",
|
||||
"ui.3d_mouse_hint": "Déplace et fait pivoter la vue en même temps ; dans les esquisses, seuls le panoramique et le zoom sont utilisés.",
|
||||
"ui.3d_mouse_kind_cap": "Retour au centre",
|
||||
"ui.3d_mouse_kind_question": "Quel type de souris 3D est-ce ?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "La valeur reste tant que vous maintenez, comme sur un appareil de type « Retour au centre ».",
|
||||
"ui.3d_mouse_looks_like_trackball": "La valeur retombe dès que vous arrêtez de bouger, comme sur un trackball.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotation X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotation Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotation Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Translation X",
|
||||
"ui.3d_mouse_motion_translation_y": "Translation Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Translation Z",
|
||||
"ui.3d_mouse_navigate": "Naviguer avec la souris 3D",
|
||||
"ui.3d_mouse_none": "Aucune souris 3D trouvée",
|
||||
"ui.3d_mouse_not_calibrated": "Non calibrée",
|
||||
"ui.3d_mouse_offer": "Calibrer votre souris 3D corrige les axes échangés ou inversés et règle sa vitesse et sa sensation.",
|
||||
"ui.3d_mouse_offer_never": "Ne plus afficher",
|
||||
"ui.3d_mouse_offer_swap": "Passer à Hidraw (pilote direct)",
|
||||
"ui.3d_mouse_reading": "Lecture",
|
||||
"ui.3d_mouse_reset_axes": "Réinitialiser l'attribution des axes",
|
||||
"ui.3d_mouse_reset_view": "Réinitialiser la vue",
|
||||
"ui.3d_mouse_restore": "Rétablir les réglages de la souris 3D",
|
||||
"ui.3d_mouse_restore_ask": "Remettre les réglages de la souris 3D par défaut ? Votre calibrage et l'assignation des boutons sont conservés.",
|
||||
"ui.3d_mouse_section": "Souris 3D",
|
||||
"ui.3d_mouse_show_in_view": "Afficher la souris 3D dans la vue 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Une image de votre souris 3D dans le coin inférieur gauche, qui bouge quand vous la bougez.",
|
||||
"ui.3d_mouse_skip": "Mon appareil ne peut pas le faire",
|
||||
"ui.3d_mouse_skip_tip": "Ignore ce mouvement dans les deux sens ; la calibration le laisse de côté.",
|
||||
"ui.3d_mouse_speed": "Vitesse de la souris 3D",
|
||||
"ui.3d_mouse_step_lift": "Soulevez le capuchon, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_press": "Appuyez sur le capuchon, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_pull": "Tirez le capuchon vers vous, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_push": "Poussez le capuchon loin de vous, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_rest": "Lâchez le capuchon et laissez-le immobile.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Lâchez la boule et laissez-la immobile.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Tournez l'anneau de défilement de quelques crans dans le sens horaire.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Tournez l'anneau de défilement de quelques crans dans le sens antihoraire.",
|
||||
"ui.3d_mouse_step_roll_away": "Faites rouler la boule loin de vous plusieurs fois, puis laissez-la s'arrêter.",
|
||||
"ui.3d_mouse_step_roll_left": "Faites rouler la boule vers la gauche plusieurs fois, puis laissez-la s'arrêter.",
|
||||
"ui.3d_mouse_step_roll_right": "Faites rouler la boule vers la droite plusieurs fois, puis laissez-la s'arrêter.",
|
||||
"ui.3d_mouse_step_roll_toward": "Faites rouler la boule vers vous plusieurs fois, puis laissez-la s'arrêter.",
|
||||
"ui.3d_mouse_step_slide_left": "Glissez le capuchon vers la gauche, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_slide_right": "Glissez le capuchon vers la droite, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Faites tourner la boule dans le sens horaire plusieurs fois, puis laissez-la s'arrêter.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Faites tourner la boule dans le sens antihoraire plusieurs fois, puis laissez-la s'arrêter.",
|
||||
"ui.3d_mouse_step_tilt_away": "Inclinez le haut du capuchon loin de vous (le bord le plus proche monte), maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_tilt_left": "Inclinez le capuchon vers la gauche (bord gauche vers le bas), maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_tilt_right": "Inclinez le capuchon vers la droite (bord droit vers le bas), maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Inclinez le haut du capuchon vers vous (le bord le plus proche descend), maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_touch": "Posez la main sur le capuchon sans le pousser.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Tournez le capuchon dans le sens horaire, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Tournez le capuchon dans le sens antihoraire, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_switch_cap": "Calibrer comme retour au centre",
|
||||
"ui.3d_mouse_switch_trackball": "Calibrer comme un trackball",
|
||||
"ui.3d_mouse_view_axis": "Axe de la vue",
|
||||
"ui.3d_mouse_view_off": "Désactivé",
|
||||
"ui.3d_mouse_view_off_tip": "Le profil du pilote de la souris 3D désactive cet axe : son mouvement ne déplace pas la vue. Choisir ici le mouvement d'un autre axe les échange, et ce mouvement cesse de déplacer la vue.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbite gauche / droite",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbite haut / bas",
|
||||
"ui.3d_mouse_view_pan_sideways": "Panoramique gauche / droite",
|
||||
"ui.3d_mouse_view_pan_up_down": "Panoramique haut / bas",
|
||||
"ui.3d_mouse_view_roll": "Roulis",
|
||||
"ui.3d_mouse_view_unmapped": "Axe {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "En attente de mouvement",
|
||||
"ui.3d_mouse_wiggle": "Si vous en avez une de branchée, remuez-la.",
|
||||
"ui.3d_sketch": "Esquisse 3D",
|
||||
"ui.activate": "Activer",
|
||||
"ui.active_component": "Composant actif",
|
||||
@@ -1363,6 +1462,7 @@
|
||||
"ui.blocked": "Inaccessible",
|
||||
"ui.bodies": "Corps",
|
||||
"ui.bodies_all_if_none": "Corps (tous si aucun)",
|
||||
"ui.bottom": "Dessous",
|
||||
"ui.box": "Boîte",
|
||||
"ui.break": "Fractionner",
|
||||
"ui.browser_bodies_one": "{count} corps",
|
||||
@@ -1806,59 +1906,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zoom fluide à la molette",
|
||||
"ui.smooth_wheel_zoom_tip": "Désactiver pour appliquer chaque cran de molette immédiatement, sans lissage",
|
||||
"ui.snap": "Accrochage",
|
||||
"ui.space_mouse_axes_reassigned": "Les axes sont réattribués dans les Préférences, et cela s'applique toujours en plus de ce calibrage.",
|
||||
"ui.space_mouse_calibrate": "Calibrer…",
|
||||
"ui.space_mouse_calibrate_tip": "Déplacez le capuchon comme la fenêtre le demande, pour que SolidCraft apprenne dans quel sens et jusqu'où va chaque axe de cette souris 3D. Enregistré pour cette souris 3D.",
|
||||
"ui.space_mouse_calibrate_title": "Calibrer la souris 3D",
|
||||
"ui.space_mouse_calibrated": "Calibrée",
|
||||
"ui.space_mouse_calibration": "Calibrage de la souris 3D",
|
||||
"ui.space_mouse_calibration_done": "Terminé. Enregistrez-le pour l'utiliser chaque fois que cette souris 3D est connectée.",
|
||||
"ui.space_mouse_calibration_no_response": "Un axe a à peine bougé, il ne peut donc pas être calibré. Recommencez en déplaçant le capuchon plus loin à chaque fois.",
|
||||
"ui.space_mouse_dead_zone": "Zone morte",
|
||||
"ui.space_mouse_dead_zone_tip": "De combien le capuchon doit bouger avant que la vue ne bouge, en part de sa course complète. Augmentez-la si la vue dérive alors que le capuchon est au repos.",
|
||||
"ui.space_mouse_driven_by": "Quel mouvement calibré de l'appareil pilote cet axe. Choisir un mouvement déjà utilisé par un autre axe les échange.",
|
||||
"ui.space_mouse_driven_by_label": "piloté par",
|
||||
"ui.space_mouse_driver": "Pilote de la souris 3D",
|
||||
"ui.space_mouse_driver_auto": "Automatique",
|
||||
"ui.space_mouse_driver_direct": "Direct (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "Lecture directe du périphérique ; spacenavd n'est pas lancé",
|
||||
"ui.space_mouse_driver_only_spacenavd": "Lecture via spacenavd, seule possibilité disponible",
|
||||
"ui.space_mouse_forget": "Oublier le calibrage",
|
||||
"ui.space_mouse_hint": "Déplace et fait pivoter la vue en même temps ; dans les esquisses, seuls le panoramique et le zoom sont utilisés.",
|
||||
"ui.space_mouse_motion_rotation_x": "Rotation X",
|
||||
"ui.space_mouse_motion_rotation_y": "Rotation Y",
|
||||
"ui.space_mouse_motion_rotation_z": "Rotation Z",
|
||||
"ui.space_mouse_motion_translation_x": "Translation X",
|
||||
"ui.space_mouse_motion_translation_y": "Translation Y",
|
||||
"ui.space_mouse_motion_translation_z": "Translation Z",
|
||||
"ui.space_mouse_navigate": "Naviguer avec SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Aucune souris 3D trouvée",
|
||||
"ui.space_mouse_not_calibrated": "Non calibrée",
|
||||
"ui.space_mouse_reading": "Lecture",
|
||||
"ui.space_mouse_reset_axes": "Réinitialiser l'attribution des axes",
|
||||
"ui.space_mouse_step_lift": "Soulevez le capuchon, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_press": "Appuyez sur le capuchon, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_pull": "Tirez le capuchon vers vous, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_push": "Poussez le capuchon loin de vous, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_rest": "Lâchez le capuchon et laissez-le immobile.",
|
||||
"ui.space_mouse_step_slide_left": "Glissez le capuchon vers la gauche, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_slide_right": "Glissez le capuchon vers la droite, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_tilt_back": "Inclinez le capuchon vers l'arrière (bord avant vers le haut), maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_tilt_forward": "Inclinez le capuchon vers l'avant (bord avant vers le bas), maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_tilt_left": "Inclinez le capuchon vers la gauche (bord gauche vers le bas), maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_tilt_right": "Inclinez le capuchon vers la droite (bord droit vers le bas), maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Tournez le capuchon dans le sens horaire, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Tournez le capuchon dans le sens antihoraire, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_view_axis": "Axe de la vue",
|
||||
"ui.space_mouse_view_off": "Désactivé",
|
||||
"ui.space_mouse_view_off_tip": "Le profil du pilote de la souris 3D désactive cet axe : son mouvement ne déplace pas la vue. Choisir ici le mouvement d'un autre axe les échange, et ce mouvement cesse de déplacer la vue.",
|
||||
"ui.space_mouse_view_orbit_sideways": "Orbite gauche / droite",
|
||||
"ui.space_mouse_view_orbit_up_down": "Orbite haut / bas",
|
||||
"ui.space_mouse_view_pan_sideways": "Panoramique gauche / droite",
|
||||
"ui.space_mouse_view_pan_up_down": "Panoramique haut / bas",
|
||||
"ui.space_mouse_view_roll": "Roulis",
|
||||
"ui.space_mouse_view_unmapped": "Axe {n}",
|
||||
"ui.space_mouse_view_zoom": "Zoom",
|
||||
"ui.spacing": "Espacement",
|
||||
"ui.spacing_2": "Espacement 2",
|
||||
"ui.sphere": "Sphère",
|
||||
|
||||
+100
-53
@@ -1125,7 +1125,106 @@
|
||||
"trace.stopped_by_contact": "Remata na mostra {sample}: {a} atravesaría {b}",
|
||||
"trace.stopped_by_loop": "Remata na mostra {sample}: o bucle a través de {joint} non pode pecharse máis",
|
||||
"ui.1_degree_of_freedom": "1 grao de liberdade",
|
||||
"ui.3d_mouse_axes_reassigned": "Os eixes están reasignados en Preferencias, e iso segue aplicándose sobre esta calibración.",
|
||||
"ui.3d_mouse_button": "Botón {number}",
|
||||
"ui.3d_mouse_button_nothing": "Nada",
|
||||
"ui.3d_mouse_button_settings": "Axustes do rato 3D",
|
||||
"ui.3d_mouse_buttons": "Botóns",
|
||||
"ui.3d_mouse_buttons_hint": "Preme un botón do teu rato 3D para atopalo aquí e escolle que fai.",
|
||||
"ui.3d_mouse_buttons_no_device": "Conecta un rato 3D para escoller que fan os seus botóns.",
|
||||
"ui.3d_mouse_buttons_reset": "Restaurar os botóns predeterminados",
|
||||
"ui.3d_mouse_calibrate": "Calibrar…",
|
||||
"ui.3d_mouse_calibrate_tip": "Move o capuchón como pide a xanela para que SolidCraft aprenda cara a onde e ata onde vai cada eixe deste rato 3D. Gárdase para este rato 3D.",
|
||||
"ui.3d_mouse_calibrate_title": "Calibrar o rato 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Move o rato 3D unha vez para que SolidCraft o vexa.",
|
||||
"ui.3d_mouse_calibrated": "Calibrado",
|
||||
"ui.3d_mouse_calibration": "Calibración do rato 3D",
|
||||
"ui.3d_mouse_calibration_done": "Feito. Gárdaa para usala sempre que este rato 3D estea conectado.",
|
||||
"ui.3d_mouse_calibration_no_response": "Un eixe case non se moveu, así que non se pode calibrar. Volve comezar e move o capuchón máis lonxe cada vez.",
|
||||
"ui.3d_mouse_circles_help": "Os círculos mostran o que envía o dispositivo tras a calibración. O modelo de proba mostra o que fai a vista con iso, tras a inversión, a velocidade e a asignación de eixes.",
|
||||
"ui.3d_mouse_dead_zone": "Zona morta",
|
||||
"ui.3d_mouse_dead_zone_tip": "Canto ten que moverse o capuchón antes de que se mova a vista, como parte do seu percorrido completo. Auméntaa se a vista se despraza co capuchón en repouso.",
|
||||
"ui.3d_mouse_denied": "SolidCraft ve un rato 3D, pero non ten permiso para lelo. A regra udev está nas notas de configuración do rato 3D.",
|
||||
"ui.3d_mouse_denied_short": "Atopado, pero non se pode ler",
|
||||
"ui.3d_mouse_driven_by": "Que movemento calibrado do dispositivo controla este eixe. Se escolles un que xa usa outro eixe, intercámbianse.",
|
||||
"ui.3d_mouse_driven_by_label": "controlado por",
|
||||
"ui.3d_mouse_driver": "Controlador do rato 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automático",
|
||||
"ui.3d_mouse_driver_direct": "Directo (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Lendo polo de agora a través de spacenavd: non se puido abrir o dispositivo.",
|
||||
"ui.3d_mouse_driver_only_direct": "Lectura directa do dispositivo; spacenavd non está en execución",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Lectura mediante spacenavd, a única opción dispoñible",
|
||||
"ui.3d_mouse_forget": "Esquecer a calibración",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Vista esquerda",
|
||||
"ui.3d_mouse_ghost_left_window": "Xanela esquerda",
|
||||
"ui.3d_mouse_ghost_position": "Posición",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Vista dereita",
|
||||
"ui.3d_mouse_hint": "Move e rota a vista á vez; os bosquexos só usan desprazamento e zoom.",
|
||||
"ui.3d_mouse_kind_cap": "Retorno ao centro",
|
||||
"ui.3d_mouse_kind_question": "Que tipo de rato 3D é?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "A lectura mantense mentres a suxeitas, como nun dispositivo de tipo «Retorno ao centro».",
|
||||
"ui.3d_mouse_looks_like_trackball": "A lectura volve atrás en canto deixas de moverte, como nun trackball.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotación X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotación Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotación Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Translación X",
|
||||
"ui.3d_mouse_motion_translation_y": "Translación Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Translación Z",
|
||||
"ui.3d_mouse_navigate": "Navegar co rato 3D",
|
||||
"ui.3d_mouse_none": "Non se atopou ningún rato 3D",
|
||||
"ui.3d_mouse_not_calibrated": "Sen calibrar",
|
||||
"ui.3d_mouse_offer": "Calibrar o teu rato 3D corrixe os eixes trocados ou invertidos e axusta a súa velocidade e sensación.",
|
||||
"ui.3d_mouse_offer_never": "Non mostrar de novo",
|
||||
"ui.3d_mouse_offer_swap": "Cambiar a Hidraw (controlador directo)",
|
||||
"ui.3d_mouse_reading": "Lectura",
|
||||
"ui.3d_mouse_reset_axes": "Restablecer asignación de eixes",
|
||||
"ui.3d_mouse_reset_view": "Restablecer vista",
|
||||
"ui.3d_mouse_restore": "Restaurar valores do rato 3D",
|
||||
"ui.3d_mouse_restore_ask": "Volver aos valores predeterminados da configuración do rato 3D? A calibración e a asignación de botóns consérvanse.",
|
||||
"ui.3d_mouse_section": "Rato 3D",
|
||||
"ui.3d_mouse_show_in_view": "Mostrar o rato 3D na vista 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Unha imaxe do teu rato 3D na esquina inferior esquerda que se move cando o moves.",
|
||||
"ui.3d_mouse_skip": "O meu dispositivo non fai isto",
|
||||
"ui.3d_mouse_skip_tip": "Omite este movemento nos dous sentidos; a calibración déixao fóra.",
|
||||
"ui.3d_mouse_speed": "Velocidade do rato 3D",
|
||||
"ui.3d_mouse_step_lift": "Levanta o capuchón, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_press": "Preme o capuchón cara abaixo, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_pull": "Tira do capuchón cara a ti, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_push": "Empurra o capuchón lonxe de ti, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_rest": "Solta o capuchón e déixao quieto.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Solta a bóla e déixaa quieta.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Xira o anel de desprazamento uns clics no sentido horario.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Xira o anel de desprazamento uns clics no sentido antihorario.",
|
||||
"ui.3d_mouse_step_roll_away": "Fai rodar a bóla lonxe de ti unhas cantas veces e déixaa parar.",
|
||||
"ui.3d_mouse_step_roll_left": "Fai rodar a bóla á esquerda unhas cantas veces e déixaa parar.",
|
||||
"ui.3d_mouse_step_roll_right": "Fai rodar a bóla á dereita unhas cantas veces e déixaa parar.",
|
||||
"ui.3d_mouse_step_roll_toward": "Fai rodar a bóla cara a ti unhas cantas veces e déixaa parar.",
|
||||
"ui.3d_mouse_step_slide_left": "Despraza o capuchón á esquerda, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_slide_right": "Despraza o capuchón á dereita, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Xira a bóla no sentido horario unhas cantas veces e déixaa parar.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Xira a bóla no sentido antihorario unhas cantas veces e déixaa parar.",
|
||||
"ui.3d_mouse_step_tilt_away": "Inclina a parte de arriba do capuchón lonxe de ti (o bordo máis próximo sobe), mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_tilt_left": "Inclina o capuchón á esquerda (bordo esquerdo abaixo), mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_tilt_right": "Inclina o capuchón á dereita (bordo dereito abaixo), mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Inclina a parte de arriba do capuchón cara a ti (o bordo máis próximo baixa), mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_touch": "Apoia a man no capuchón sen empurralo.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Xira o capuchón no sentido horario, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Xira o capuchón no sentido antihorario, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_switch_cap": "Calibrar como retorno ao centro",
|
||||
"ui.3d_mouse_switch_trackball": "Calibrar como trackball",
|
||||
"ui.3d_mouse_view_axis": "Eixe da vista",
|
||||
"ui.3d_mouse_view_off": "Desactivado",
|
||||
"ui.3d_mouse_view_off_tip": "O perfil do controlador do rato 3D desactiva este eixe, así que o seu movemento non move a vista. Se escolles aquí o movemento doutro eixe, intercámbianse e ese movemento deixa de mover a vista.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbitar á esquerda / dereita",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbitar arriba / abaixo",
|
||||
"ui.3d_mouse_view_pan_sideways": "Desprazar á esquerda / dereita",
|
||||
"ui.3d_mouse_view_pan_up_down": "Desprazar arriba / abaixo",
|
||||
"ui.3d_mouse_view_roll": "Xirar sobre a vista",
|
||||
"ui.3d_mouse_view_unmapped": "Eixe {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "Agardando movemento",
|
||||
"ui.3d_mouse_wiggle": "Se tes un conectado, móveo un pouco.",
|
||||
"ui.3d_sketch": "Bosquexo 3D",
|
||||
"ui.activate": "Activar",
|
||||
"ui.active_component": "Compoñente activo",
|
||||
@@ -1355,6 +1454,7 @@
|
||||
"ui.blocked": "Bloqueado",
|
||||
"ui.bodies": "Corpos",
|
||||
"ui.bodies_all_if_none": "Corpos (todos se non hai ningún)",
|
||||
"ui.bottom": "Inferior",
|
||||
"ui.box": "Caixa",
|
||||
"ui.break": "Romper",
|
||||
"ui.browser_bodies_one": "{count} corpo",
|
||||
@@ -1798,59 +1898,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zoom suave coa roda",
|
||||
"ui.smooth_wheel_zoom_tip": "Desactívao para aplicar cada movemento da roda de inmediato, sen suavizado",
|
||||
"ui.snap": "Axuste",
|
||||
"ui.space_mouse_axes_reassigned": "Os eixes están reasignados en Preferencias, e iso segue aplicándose sobre esta calibración.",
|
||||
"ui.space_mouse_calibrate": "Calibrar…",
|
||||
"ui.space_mouse_calibrate_tip": "Move o capuchón como pide a xanela para que SolidCraft aprenda cara a onde e ata onde vai cada eixe deste rato 3D. Gárdase para este rato 3D.",
|
||||
"ui.space_mouse_calibrate_title": "Calibrar o rato 3D",
|
||||
"ui.space_mouse_calibrated": "Calibrado",
|
||||
"ui.space_mouse_calibration": "Calibración do rato 3D",
|
||||
"ui.space_mouse_calibration_done": "Feito. Gárdaa para usala sempre que este rato 3D estea conectado.",
|
||||
"ui.space_mouse_calibration_no_response": "Un eixe case non se moveu, así que non se pode calibrar. Volve comezar e move o capuchón máis lonxe cada vez.",
|
||||
"ui.space_mouse_dead_zone": "Zona morta",
|
||||
"ui.space_mouse_dead_zone_tip": "Canto ten que moverse o capuchón antes de que se mova a vista, como parte do seu percorrido completo. Auméntaa se a vista se despraza co capuchón en repouso.",
|
||||
"ui.space_mouse_driven_by": "Que movemento calibrado do dispositivo controla este eixe. Se escolles un que xa usa outro eixe, intercámbianse.",
|
||||
"ui.space_mouse_driven_by_label": "controlado por",
|
||||
"ui.space_mouse_driver": "Controlador do rato 3D",
|
||||
"ui.space_mouse_driver_auto": "Automático",
|
||||
"ui.space_mouse_driver_direct": "Directo (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "Lectura directa do dispositivo; spacenavd non está en execución",
|
||||
"ui.space_mouse_driver_only_spacenavd": "Lectura mediante spacenavd, a única opción dispoñible",
|
||||
"ui.space_mouse_forget": "Esquecer a calibración",
|
||||
"ui.space_mouse_hint": "Move e rota a vista á vez; os bosquexos só usan desprazamento e zoom.",
|
||||
"ui.space_mouse_motion_rotation_x": "Rotación X",
|
||||
"ui.space_mouse_motion_rotation_y": "Rotación Y",
|
||||
"ui.space_mouse_motion_rotation_z": "Rotación Z",
|
||||
"ui.space_mouse_motion_translation_x": "Translación X",
|
||||
"ui.space_mouse_motion_translation_y": "Translación Y",
|
||||
"ui.space_mouse_motion_translation_z": "Translación Z",
|
||||
"ui.space_mouse_navigate": "Navegar con SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Non se atopou ningún rato 3D",
|
||||
"ui.space_mouse_not_calibrated": "Sen calibrar",
|
||||
"ui.space_mouse_reading": "Lectura",
|
||||
"ui.space_mouse_reset_axes": "Restablecer asignación de eixes",
|
||||
"ui.space_mouse_step_lift": "Levanta o capuchón, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_press": "Preme o capuchón cara abaixo, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_pull": "Tira do capuchón cara a ti, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_push": "Empurra o capuchón lonxe de ti, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_rest": "Solta o capuchón e déixao quieto.",
|
||||
"ui.space_mouse_step_slide_left": "Despraza o capuchón á esquerda, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_slide_right": "Despraza o capuchón á dereita, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_tilt_back": "Inclina o capuchón cara atrás (bordo dianteiro arriba), mantéño e sóltao.",
|
||||
"ui.space_mouse_step_tilt_forward": "Inclina o capuchón cara adiante (bordo dianteiro abaixo), mantéño e sóltao.",
|
||||
"ui.space_mouse_step_tilt_left": "Inclina o capuchón á esquerda (bordo esquerdo abaixo), mantéño e sóltao.",
|
||||
"ui.space_mouse_step_tilt_right": "Inclina o capuchón á dereita (bordo dereito abaixo), mantéño e sóltao.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Xira o capuchón no sentido horario, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Xira o capuchón no sentido antihorario, mantéño e sóltao.",
|
||||
"ui.space_mouse_view_axis": "Eixe da vista",
|
||||
"ui.space_mouse_view_off": "Desactivado",
|
||||
"ui.space_mouse_view_off_tip": "O perfil do controlador do rato 3D desactiva este eixe, así que o seu movemento non move a vista. Se escolles aquí o movemento doutro eixe, intercámbianse e ese movemento deixa de mover a vista.",
|
||||
"ui.space_mouse_view_orbit_sideways": "Orbitar á esquerda / dereita",
|
||||
"ui.space_mouse_view_orbit_up_down": "Orbitar arriba / abaixo",
|
||||
"ui.space_mouse_view_pan_sideways": "Desprazar á esquerda / dereita",
|
||||
"ui.space_mouse_view_pan_up_down": "Desprazar arriba / abaixo",
|
||||
"ui.space_mouse_view_roll": "Xirar sobre a vista",
|
||||
"ui.space_mouse_view_unmapped": "Eixe {n}",
|
||||
"ui.space_mouse_view_zoom": "Zoom",
|
||||
"ui.spacing": "Espazado",
|
||||
"ui.spacing_2": "Espazado 2",
|
||||
"ui.sphere": "Esfera",
|
||||
|
||||
+43
-28
@@ -1124,7 +1124,50 @@
|
||||
"trace.stopped_by_contact": "Vége a(z) {sample}. mintánál: {a} áthatolna ezen: {b}",
|
||||
"trace.stopped_by_loop": "Vége a(z) {sample}. mintánál: a(z) {joint} csuklón átmenő hurok nem zárható tovább",
|
||||
"ui.1_degree_of_freedom": "1 szabadságfok",
|
||||
"ui.3d_mouse_calibrate": "Kalibrálás…",
|
||||
"ui.3d_mouse_calibrate_tip": "Mozgassa a kupakot az ablak kérése szerint, hogy a SolidCraft megtanulja, merre és mennyire mozdul a 3D-egér mindegyik tengelye. Ehhez a 3D-egérhez mentve.",
|
||||
"ui.3d_mouse_calibrate_title": "3D-egér kalibrálása",
|
||||
"ui.3d_mouse_calibrate_wait": "Mozdítsa meg egyszer a 3D-egeret, hogy a SolidCraft lássa.",
|
||||
"ui.3d_mouse_calibrated": "Kalibrálva",
|
||||
"ui.3d_mouse_calibration": "3D-egér kalibrálása",
|
||||
"ui.3d_mouse_calibration_done": "Kész. Mentse, hogy a 3D-egér csatlakoztatásakor mindig használatos legyen.",
|
||||
"ui.3d_mouse_calibration_no_response": "Az egyik tengely alig mozdult, így nem kalibrálható. Kezdje elölről, és minden alkalommal mozgassa a kupakot messzebb.",
|
||||
"ui.3d_mouse_circles_help": "A körök azt mutatják, mit küld az eszköz kalibrálás után. A tesztmodell azt mutatja, mit tesz ezzel a nézet a megfordítás, sebesség és tengelykiosztás után.",
|
||||
"ui.3d_mouse_dead_zone": "Holtsáv",
|
||||
"ui.3d_mouse_dead_zone_tip": "Mennyit kell mozdítani a kupakot, mielőtt a nézet mozdulna, a teljes elmozdulás arányában. Növelje, ha a nézet sodródik, miközben a kupak nyugalomban van.",
|
||||
"ui.3d_mouse_denied": "A SolidCraft látja a 3D-egeret, de nem olvashatja. Az udev-szabályt a 3D-egér beállítási jegyzetei tartalmazzák.",
|
||||
"ui.3d_mouse_denied_short": "Megvan, de nem olvasható",
|
||||
"ui.3d_mouse_driver_fallback": "Egyelőre a spacenavd-n át olvassa: magát az eszközt nem sikerült megnyitni.",
|
||||
"ui.3d_mouse_forget": "Kalibrálás elfelejtése",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Bal nézet",
|
||||
"ui.3d_mouse_ghost_left_window": "Bal ablak",
|
||||
"ui.3d_mouse_ghost_position": "Helyzet",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Jobb nézet",
|
||||
"ui.3d_mouse_hint": "A nézetet együtt mozgatja és forgatja; a vázlatok csak pásztázást és nagyítást használnak.",
|
||||
"ui.3d_mouse_navigate": "Navigálás a 3D-egérrel",
|
||||
"ui.3d_mouse_none": "Nem található 3D-egér",
|
||||
"ui.3d_mouse_not_calibrated": "Nincs kalibrálva",
|
||||
"ui.3d_mouse_offer": "A 3D-egér kalibrálása kijavítja a felcserélt vagy megfordított tengelyeket, és beállítja a sebességét és az érzetét.",
|
||||
"ui.3d_mouse_offer_never": "Ne jelenjen meg többé",
|
||||
"ui.3d_mouse_offer_swap": "Váltás Hidrawra (közvetlen illesztőprogram)",
|
||||
"ui.3d_mouse_reading": "Beolvasás",
|
||||
"ui.3d_mouse_reset_view": "Nézet alaphelyzetbe állítása",
|
||||
"ui.3d_mouse_restore": "3D-egér alapértékeinek visszaállítása",
|
||||
"ui.3d_mouse_restore_ask": "Visszaállítja a 3D-egér beállításait az alapértékekre? A kalibrálás és a gombkiosztás megmarad.",
|
||||
"ui.3d_mouse_speed": "3D-egér sebessége",
|
||||
"ui.3d_mouse_step_lift": "Emelje fel a kupakot, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_press": "Nyomja le a kupakot, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_pull": "Húzza a kupakot maga felé, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_push": "Tolja a kupakot magától el, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_rest": "Vegye le a kezét a kupakról, és hagyja nyugodtan.",
|
||||
"ui.3d_mouse_step_slide_left": "Csúsztassa a kupakot balra, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_slide_right": "Csúsztassa a kupakot jobbra, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_tilt_left": "Billentse balra a kupakot (a bal él lefelé), tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_tilt_right": "Billentse jobbra a kupakot (a jobb él lefelé), tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Csavarja el a kupakot az óramutató járásával megegyezően, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Csavarja el a kupakot az óramutató járásával ellentétesen, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_waiting": "Mozgásra vár",
|
||||
"ui.3d_mouse_wiggle": "Ha van csatlakoztatva egy, mozgassa meg egy kicsit.",
|
||||
"ui.3d_sketch": "3D vázlat",
|
||||
"ui.activate": "Aktiválás",
|
||||
"ui.active_component": "Aktív komponens",
|
||||
@@ -1797,34 +1840,6 @@
|
||||
"ui.smooth_wheel_zoom": "Sima görgőnagyítás",
|
||||
"ui.smooth_wheel_zoom_tip": "Kapcsolja ki az egyes görgőesemények azonnali alkalmazásához, kifutás nélkül",
|
||||
"ui.snap": "Illesztés",
|
||||
"ui.space_mouse_calibrate": "Kalibrálás…",
|
||||
"ui.space_mouse_calibrate_tip": "Mozgassa a kupakot az ablak kérése szerint, hogy a SolidCraft megtanulja, merre és mennyire mozdul a 3D-egér mindegyik tengelye. Ehhez a 3D-egérhez mentve.",
|
||||
"ui.space_mouse_calibrate_title": "3D-egér kalibrálása",
|
||||
"ui.space_mouse_calibrated": "Kalibrálva",
|
||||
"ui.space_mouse_calibration": "3D-egér kalibrálása",
|
||||
"ui.space_mouse_calibration_done": "Kész. Mentse, hogy a 3D-egér csatlakoztatásakor mindig használatos legyen.",
|
||||
"ui.space_mouse_calibration_no_response": "Az egyik tengely alig mozdult, így nem kalibrálható. Kezdje elölről, és minden alkalommal mozgassa a kupakot messzebb.",
|
||||
"ui.space_mouse_dead_zone": "Holtsáv",
|
||||
"ui.space_mouse_dead_zone_tip": "Mennyit kell mozdítani a kupakot, mielőtt a nézet mozdulna, a teljes elmozdulás arányában. Növelje, ha a nézet sodródik, miközben a kupak nyugalomban van.",
|
||||
"ui.space_mouse_forget": "Kalibrálás elfelejtése",
|
||||
"ui.space_mouse_hint": "A nézetet együtt mozgatja és forgatja; a vázlatok csak pásztázást és nagyítást használnak.",
|
||||
"ui.space_mouse_navigate": "Navigálás SpaceNavigator / SpaceMouse eszközzel",
|
||||
"ui.space_mouse_none": "Nem található 3D-egér",
|
||||
"ui.space_mouse_not_calibrated": "Nincs kalibrálva",
|
||||
"ui.space_mouse_reading": "Beolvasás",
|
||||
"ui.space_mouse_step_lift": "Emelje fel a kupakot, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_press": "Nyomja le a kupakot, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_pull": "Húzza a kupakot maga felé, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_push": "Tolja a kupakot magától el, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_rest": "Vegye le a kezét a kupakról, és hagyja nyugodtan.",
|
||||
"ui.space_mouse_step_slide_left": "Csúsztassa a kupakot balra, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_slide_right": "Csúsztassa a kupakot jobbra, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_tilt_back": "Billentse hátra a kupakot (az elülső él felfelé), tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_tilt_forward": "Billentse előre a kupakot (az elülső él lefelé), tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_tilt_left": "Billentse balra a kupakot (a bal él lefelé), tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_tilt_right": "Billentse jobbra a kupakot (a jobb él lefelé), tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Csavarja el a kupakot az óramutató járásával megegyezően, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Csavarja el a kupakot az óramutató járásával ellentétesen, tartsa, majd engedje el.",
|
||||
"ui.spacing": "Térköz",
|
||||
"ui.spacing_2": "2. térköz",
|
||||
"ui.sphere": "Gömb",
|
||||
|
||||
+43
-28
@@ -1124,7 +1124,50 @@
|
||||
"trace.stopped_by_contact": "Berakhir di sampel {sample}: {a} akan menembus {b}",
|
||||
"trace.stopped_by_loop": "Berakhir di sampel {sample}: loop melalui {joint} tidak dapat menutup lebih jauh",
|
||||
"ui.1_degree_of_freedom": "1 derajat kebebasan",
|
||||
"ui.3d_mouse_calibrate": "Kalibrasi…",
|
||||
"ui.3d_mouse_calibrate_tip": "Gerakkan cap sesuai permintaan jendela, agar SolidCraft mempelajari arah dan jarak setiap sumbu mouse 3D ini. Disimpan untuk mouse 3D ini.",
|
||||
"ui.3d_mouse_calibrate_title": "Kalibrasi mouse 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Gerakkan mouse 3D sekali agar SolidCraft dapat melihatnya.",
|
||||
"ui.3d_mouse_calibrated": "Terkalibrasi",
|
||||
"ui.3d_mouse_calibration": "Kalibrasi mouse 3D",
|
||||
"ui.3d_mouse_calibration_done": "Selesai. Simpan untuk dipakai setiap kali mouse 3D ini terhubung.",
|
||||
"ui.3d_mouse_calibration_no_response": "Satu sumbu hampir tidak bergerak, sehingga tidak dapat dikalibrasi. Mulai ulang dan gerakkan cap lebih jauh setiap kali.",
|
||||
"ui.3d_mouse_circles_help": "Lingkaran menunjukkan apa yang dikirim perangkat setelah kalibrasi. Model uji menunjukkan apa yang dilakukan tampilan dengannya, setelah pembalikan, kecepatan, dan penetapan sumbu.",
|
||||
"ui.3d_mouse_dead_zone": "Zona mati",
|
||||
"ui.3d_mouse_dead_zone_tip": "Seberapa jauh cap harus bergerak sebelum tampilan bergerak, sebagai bagian dari total jangkauannya. Naikkan jika tampilan bergeser saat cap diam.",
|
||||
"ui.3d_mouse_denied": "SolidCraft melihat mouse 3D tetapi tidak diizinkan membacanya. Lihat catatan penyiapan mouse 3D untuk aturan udev.",
|
||||
"ui.3d_mouse_denied_short": "Ditemukan, tetapi tidak bisa dibaca",
|
||||
"ui.3d_mouse_driver_fallback": "Sementara dibaca lewat spacenavd: perangkatnya sendiri tidak dapat dibuka.",
|
||||
"ui.3d_mouse_forget": "Lupakan kalibrasi",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Viewport kiri",
|
||||
"ui.3d_mouse_ghost_left_window": "Jendela kiri",
|
||||
"ui.3d_mouse_ghost_position": "Posisi",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Viewport kanan",
|
||||
"ui.3d_mouse_hint": "Gerakkan dan putar tampilan bersama; sketsa hanya memakai pan dan zoom.",
|
||||
"ui.3d_mouse_navigate": "Navigasi dengan mouse 3D",
|
||||
"ui.3d_mouse_none": "Mouse 3D tidak ditemukan",
|
||||
"ui.3d_mouse_not_calibrated": "Belum dikalibrasi",
|
||||
"ui.3d_mouse_offer": "Mengkalibrasi mouse 3D memperbaiki sumbu yang tertukar atau terbalik dan mengatur kecepatan serta rasanya.",
|
||||
"ui.3d_mouse_offer_never": "Jangan tampilkan lagi",
|
||||
"ui.3d_mouse_offer_swap": "Beralih ke Hidraw (driver langsung)",
|
||||
"ui.3d_mouse_reading": "Pembacaan",
|
||||
"ui.3d_mouse_reset_view": "Atur ulang tampilan",
|
||||
"ui.3d_mouse_restore": "Pulihkan bawaan mouse 3D",
|
||||
"ui.3d_mouse_restore_ask": "Kembalikan pengaturan mouse 3D ke bawaan? Kalibrasi dan pemetaan tombol tetap ada.",
|
||||
"ui.3d_mouse_speed": "Kecepatan mouse 3D",
|
||||
"ui.3d_mouse_step_lift": "Angkat cap ke atas, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_press": "Tekan cap ke bawah, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_pull": "Tarik cap ke arah Anda, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_push": "Dorong cap menjauh dari Anda, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_rest": "Lepaskan tangan dari cap dan biarkan diam.",
|
||||
"ui.3d_mouse_step_slide_left": "Geser cap ke kiri, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_slide_right": "Geser cap ke kanan, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_tilt_left": "Miringkan cap ke kiri (tepi kiri turun), tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_tilt_right": "Miringkan cap ke kanan (tepi kanan turun), tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Putar cap searah jarum jam, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Putar cap berlawanan arah jarum jam, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_waiting": "Menunggu gerakan",
|
||||
"ui.3d_mouse_wiggle": "Jika Anda memasang satu, goyangkan sedikit.",
|
||||
"ui.3d_sketch": "Sketsa 3D",
|
||||
"ui.activate": "Aktifkan",
|
||||
"ui.active_component": "Komponen aktif",
|
||||
@@ -1797,34 +1840,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zoom roda halus",
|
||||
"ui.smooth_wheel_zoom_tip": "Matikan untuk menerapkan setiap peristiwa roda segera, tanpa ekor penghalusan",
|
||||
"ui.snap": "Snap",
|
||||
"ui.space_mouse_calibrate": "Kalibrasi…",
|
||||
"ui.space_mouse_calibrate_tip": "Gerakkan cap sesuai permintaan jendela, agar SolidCraft mempelajari arah dan jarak setiap sumbu mouse 3D ini. Disimpan untuk mouse 3D ini.",
|
||||
"ui.space_mouse_calibrate_title": "Kalibrasi mouse 3D",
|
||||
"ui.space_mouse_calibrated": "Terkalibrasi",
|
||||
"ui.space_mouse_calibration": "Kalibrasi mouse 3D",
|
||||
"ui.space_mouse_calibration_done": "Selesai. Simpan untuk dipakai setiap kali mouse 3D ini terhubung.",
|
||||
"ui.space_mouse_calibration_no_response": "Satu sumbu hampir tidak bergerak, sehingga tidak dapat dikalibrasi. Mulai ulang dan gerakkan cap lebih jauh setiap kali.",
|
||||
"ui.space_mouse_dead_zone": "Zona mati",
|
||||
"ui.space_mouse_dead_zone_tip": "Seberapa jauh cap harus bergerak sebelum tampilan bergerak, sebagai bagian dari total jangkauannya. Naikkan jika tampilan bergeser saat cap diam.",
|
||||
"ui.space_mouse_forget": "Lupakan kalibrasi",
|
||||
"ui.space_mouse_hint": "Gerakkan dan putar tampilan bersama; sketsa hanya memakai pan dan zoom.",
|
||||
"ui.space_mouse_navigate": "Navigasi dengan SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Mouse 3D tidak ditemukan",
|
||||
"ui.space_mouse_not_calibrated": "Belum dikalibrasi",
|
||||
"ui.space_mouse_reading": "Pembacaan",
|
||||
"ui.space_mouse_step_lift": "Angkat cap ke atas, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_press": "Tekan cap ke bawah, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_pull": "Tarik cap ke arah Anda, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_push": "Dorong cap menjauh dari Anda, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_rest": "Lepaskan tangan dari cap dan biarkan diam.",
|
||||
"ui.space_mouse_step_slide_left": "Geser cap ke kiri, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_slide_right": "Geser cap ke kanan, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_tilt_back": "Miringkan cap ke belakang (tepi depan naik), tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_tilt_forward": "Miringkan cap ke depan (tepi depan turun), tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_tilt_left": "Miringkan cap ke kiri (tepi kiri turun), tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_tilt_right": "Miringkan cap ke kanan (tepi kanan turun), tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Putar cap searah jarum jam, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Putar cap berlawanan arah jarum jam, tahan, lalu lepaskan.",
|
||||
"ui.spacing": "Jarak antar",
|
||||
"ui.spacing_2": "Jarak antar 2",
|
||||
"ui.sphere": "Bola",
|
||||
|
||||
+100
-53
@@ -1128,7 +1128,106 @@
|
||||
"trace.stopped_by_contact": "Termina al campione {sample}: {a} attraverserebbe {b}",
|
||||
"trace.stopped_by_loop": "Termina al campione {sample}: la catena chiusa che passa per {joint} non può chiudersi ulteriormente",
|
||||
"ui.1_degree_of_freedom": "1 grado di libertà",
|
||||
"ui.3d_mouse_axes_reassigned": "Gli assi sono riassegnati nelle Preferenze, e questo vale anche dopo questa calibrazione.",
|
||||
"ui.3d_mouse_button": "Pulsante {number}",
|
||||
"ui.3d_mouse_button_nothing": "Niente",
|
||||
"ui.3d_mouse_button_settings": "Impostazioni del mouse 3D",
|
||||
"ui.3d_mouse_buttons": "Pulsanti",
|
||||
"ui.3d_mouse_buttons_hint": "Premi un pulsante del tuo mouse 3D per trovarlo qui, poi scegli cosa fa.",
|
||||
"ui.3d_mouse_buttons_no_device": "Collega un mouse 3D per scegliere cosa fanno i suoi pulsanti.",
|
||||
"ui.3d_mouse_buttons_reset": "Ripristina i pulsanti predefiniti",
|
||||
"ui.3d_mouse_calibrate": "Calibra…",
|
||||
"ui.3d_mouse_calibrate_tip": "Muovi la manopola come chiede la finestra, così SolidCraft impara in che direzione e quanto va ogni asse di questo mouse 3D. Viene salvata per questo mouse 3D.",
|
||||
"ui.3d_mouse_calibrate_title": "Calibra mouse 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Muovi il mouse 3D una volta così SolidCraft lo vede.",
|
||||
"ui.3d_mouse_calibrated": "Calibrato",
|
||||
"ui.3d_mouse_calibration": "Calibrazione del mouse 3D",
|
||||
"ui.3d_mouse_calibration_done": "Fatto. Salvala per usarla ogni volta che questo mouse 3D è collegato.",
|
||||
"ui.3d_mouse_calibration_no_response": "Un asse si è mosso appena, quindi non si può calibrare. Ricomincia e muovi la manopola più a fondo ogni volta.",
|
||||
"ui.3d_mouse_circles_help": "I cerchi mostrano ciò che il dispositivo invia dopo la calibrazione. Il modello di prova mostra cosa ne fa la vista, dopo inversione, velocità e assegnazione degli assi.",
|
||||
"ui.3d_mouse_dead_zone": "Zona morta",
|
||||
"ui.3d_mouse_dead_zone_tip": "Quanto deve muoversi la manopola prima che si muova la vista, come quota della sua corsa completa. Aumentala se la vista si sposta con la manopola a riposo.",
|
||||
"ui.3d_mouse_denied": "SolidCraft vede un mouse 3D ma non ha il permesso di leggerlo. La regola udev è nelle note di configurazione del mouse 3D.",
|
||||
"ui.3d_mouse_denied_short": "Trovato, ma non leggibile",
|
||||
"ui.3d_mouse_driven_by": "Quale movimento calibrato del dispositivo comanda questo asse. Se ne scegli uno già usato da un altro asse, i due si scambiano.",
|
||||
"ui.3d_mouse_driven_by_label": "comandato da",
|
||||
"ui.3d_mouse_driver": "Driver del mouse 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automatico",
|
||||
"ui.3d_mouse_driver_direct": "Diretto (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Per ora legge tramite spacenavd: il dispositivo stesso non si è potuto aprire.",
|
||||
"ui.3d_mouse_driver_only_direct": "Lettura diretta del dispositivo; spacenavd non è in esecuzione",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Lettura tramite spacenavd, l'unica possibilità disponibile",
|
||||
"ui.3d_mouse_forget": "Dimentica calibrazione",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Vista a sinistra",
|
||||
"ui.3d_mouse_ghost_left_window": "Finestra a sinistra",
|
||||
"ui.3d_mouse_ghost_position": "Posizione",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Vista a destra",
|
||||
"ui.3d_mouse_hint": "Sposta e ruota la vista insieme; gli schizzi usano solo panoramica e zoom.",
|
||||
"ui.3d_mouse_kind_cap": "Ritorno al centro",
|
||||
"ui.3d_mouse_kind_question": "Che tipo di mouse 3D è?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "Il valore resta finché tieni, come in un dispositivo di tipo «Ritorno al centro».",
|
||||
"ui.3d_mouse_looks_like_trackball": "Il valore torna indietro appena smetti di muovere, come in una trackball.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotazione X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotazione Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotazione Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Traslazione X",
|
||||
"ui.3d_mouse_motion_translation_y": "Traslazione Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Traslazione Z",
|
||||
"ui.3d_mouse_navigate": "Naviga con il mouse 3D",
|
||||
"ui.3d_mouse_none": "Nessun mouse 3D trovato",
|
||||
"ui.3d_mouse_not_calibrated": "Non calibrato",
|
||||
"ui.3d_mouse_offer": "Calibrare il mouse 3D corregge gli assi scambiati o invertiti e imposta velocità e sensibilità.",
|
||||
"ui.3d_mouse_offer_never": "Non mostrare più",
|
||||
"ui.3d_mouse_offer_swap": "Passa a Hidraw (driver diretto)",
|
||||
"ui.3d_mouse_reading": "Lettura",
|
||||
"ui.3d_mouse_reset_axes": "Ripristina assegnazione assi",
|
||||
"ui.3d_mouse_reset_view": "Ripristina vista",
|
||||
"ui.3d_mouse_restore": "Ripristina impostazioni mouse 3D",
|
||||
"ui.3d_mouse_restore_ask": "Riportare le impostazioni del mouse 3D ai valori predefiniti? Calibrazione e assegnazione dei pulsanti restano.",
|
||||
"ui.3d_mouse_section": "Mouse 3D",
|
||||
"ui.3d_mouse_show_in_view": "Mostra il mouse 3D nella vista 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Un'immagine del tuo mouse 3D nell'angolo in basso a sinistra, che si muove quando lo muovi.",
|
||||
"ui.3d_mouse_skip": "Il mio dispositivo non può farlo",
|
||||
"ui.3d_mouse_skip_tip": "Salta questo movimento in entrambi i versi; la calibrazione lo esclude.",
|
||||
"ui.3d_mouse_speed": "Velocità del mouse 3D",
|
||||
"ui.3d_mouse_step_lift": "Solleva la manopola, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_press": "Premi la manopola verso il basso, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_pull": "Tira la manopola verso di te, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_push": "Spingi la manopola lontano da te, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_rest": "Togli la mano dalla manopola e lasciala ferma.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Togli la mano dalla sfera e lasciala ferma.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Gira la ghiera di scorrimento di qualche scatto in senso orario.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Gira la ghiera di scorrimento di qualche scatto in senso antiorario.",
|
||||
"ui.3d_mouse_step_roll_away": "Fai rotolare la sfera lontano da te qualche volta, poi lasciala fermare.",
|
||||
"ui.3d_mouse_step_roll_left": "Fai rotolare la sfera verso sinistra qualche volta, poi lasciala fermare.",
|
||||
"ui.3d_mouse_step_roll_right": "Fai rotolare la sfera verso destra qualche volta, poi lasciala fermare.",
|
||||
"ui.3d_mouse_step_roll_toward": "Fai rotolare la sfera verso di te qualche volta, poi lasciala fermare.",
|
||||
"ui.3d_mouse_step_slide_left": "Fai scorrere la manopola a sinistra, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_slide_right": "Fai scorrere la manopola a destra, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Ruota la sfera in senso orario qualche volta, poi lasciala fermare.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Ruota la sfera in senso antiorario qualche volta, poi lasciala fermare.",
|
||||
"ui.3d_mouse_step_tilt_away": "Inclina la parte superiore della manopola lontano da te (il bordo più vicino sale), tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_tilt_left": "Inclina la manopola a sinistra (bordo sinistro in basso), tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_tilt_right": "Inclina la manopola a destra (bordo destro in basso), tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Inclina la parte superiore della manopola verso di te (il bordo più vicino scende), tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_touch": "Appoggia la mano sulla manopola senza spingerla.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Ruota la manopola in senso orario, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Ruota la manopola in senso antiorario, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_switch_cap": "Calibra come ritorno al centro",
|
||||
"ui.3d_mouse_switch_trackball": "Calibra come trackball",
|
||||
"ui.3d_mouse_view_axis": "Asse della vista",
|
||||
"ui.3d_mouse_view_off": "Disattivato",
|
||||
"ui.3d_mouse_view_off_tip": "Il profilo del driver del mouse 3D disattiva questo asse, quindi il suo movimento non muove la vista. Se qui scegli il movimento di un altro asse, i due si scambiano e quel movimento smette di muovere la vista.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbita sinistra / destra",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbita su / giù",
|
||||
"ui.3d_mouse_view_pan_sideways": "Panoramica sinistra / destra",
|
||||
"ui.3d_mouse_view_pan_up_down": "Panoramica su / giù",
|
||||
"ui.3d_mouse_view_roll": "Rollio",
|
||||
"ui.3d_mouse_view_unmapped": "Asse {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "In attesa di movimento",
|
||||
"ui.3d_mouse_wiggle": "Se ne hai uno collegato, muovilo un po'.",
|
||||
"ui.3d_sketch": "Schizzo 3D",
|
||||
"ui.activate": "Attiva",
|
||||
"ui.active_component": "Componente attivo",
|
||||
@@ -1358,6 +1457,7 @@
|
||||
"ui.blocked": "Bloccato",
|
||||
"ui.bodies": "Corpi",
|
||||
"ui.bodies_all_if_none": "Corpi (tutti se nessuno)",
|
||||
"ui.bottom": "Inferiore",
|
||||
"ui.box": "Parallelepipedo",
|
||||
"ui.break": "Spezza",
|
||||
"ui.browser_bodies_one": "{count} corpo",
|
||||
@@ -1801,59 +1901,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zoom fluido con rotellina",
|
||||
"ui.smooth_wheel_zoom_tip": "Disattiva per applicare immediatamente ogni evento della rotellina, senza coda di interpolazione",
|
||||
"ui.snap": "Aggancio",
|
||||
"ui.space_mouse_axes_reassigned": "Gli assi sono riassegnati nelle Preferenze, e questo vale anche dopo questa calibrazione.",
|
||||
"ui.space_mouse_calibrate": "Calibra…",
|
||||
"ui.space_mouse_calibrate_tip": "Muovi la manopola come chiede la finestra, così SolidCraft impara in che direzione e quanto va ogni asse di questo mouse 3D. Viene salvata per questo mouse 3D.",
|
||||
"ui.space_mouse_calibrate_title": "Calibra mouse 3D",
|
||||
"ui.space_mouse_calibrated": "Calibrato",
|
||||
"ui.space_mouse_calibration": "Calibrazione del mouse 3D",
|
||||
"ui.space_mouse_calibration_done": "Fatto. Salvala per usarla ogni volta che questo mouse 3D è collegato.",
|
||||
"ui.space_mouse_calibration_no_response": "Un asse si è mosso appena, quindi non si può calibrare. Ricomincia e muovi la manopola più a fondo ogni volta.",
|
||||
"ui.space_mouse_dead_zone": "Zona morta",
|
||||
"ui.space_mouse_dead_zone_tip": "Quanto deve muoversi la manopola prima che si muova la vista, come quota della sua corsa completa. Aumentala se la vista si sposta con la manopola a riposo.",
|
||||
"ui.space_mouse_driven_by": "Quale movimento calibrato del dispositivo comanda questo asse. Se ne scegli uno già usato da un altro asse, i due si scambiano.",
|
||||
"ui.space_mouse_driven_by_label": "comandato da",
|
||||
"ui.space_mouse_driver": "Driver del mouse 3D",
|
||||
"ui.space_mouse_driver_auto": "Automatico",
|
||||
"ui.space_mouse_driver_direct": "Diretto (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "Lettura diretta del dispositivo; spacenavd non è in esecuzione",
|
||||
"ui.space_mouse_driver_only_spacenavd": "Lettura tramite spacenavd, l'unica possibilità disponibile",
|
||||
"ui.space_mouse_forget": "Dimentica calibrazione",
|
||||
"ui.space_mouse_hint": "Sposta e ruota la vista insieme; gli schizzi usano solo panoramica e zoom.",
|
||||
"ui.space_mouse_motion_rotation_x": "Rotazione X",
|
||||
"ui.space_mouse_motion_rotation_y": "Rotazione Y",
|
||||
"ui.space_mouse_motion_rotation_z": "Rotazione Z",
|
||||
"ui.space_mouse_motion_translation_x": "Traslazione X",
|
||||
"ui.space_mouse_motion_translation_y": "Traslazione Y",
|
||||
"ui.space_mouse_motion_translation_z": "Traslazione Z",
|
||||
"ui.space_mouse_navigate": "Naviga con SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Nessun mouse 3D trovato",
|
||||
"ui.space_mouse_not_calibrated": "Non calibrato",
|
||||
"ui.space_mouse_reading": "Lettura",
|
||||
"ui.space_mouse_reset_axes": "Ripristina assegnazione assi",
|
||||
"ui.space_mouse_step_lift": "Solleva la manopola, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_press": "Premi la manopola verso il basso, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_pull": "Tira la manopola verso di te, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_push": "Spingi la manopola lontano da te, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_rest": "Togli la mano dalla manopola e lasciala ferma.",
|
||||
"ui.space_mouse_step_slide_left": "Fai scorrere la manopola a sinistra, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_slide_right": "Fai scorrere la manopola a destra, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_tilt_back": "Inclina la manopola all'indietro (bordo anteriore in alto), tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_tilt_forward": "Inclina la manopola in avanti (bordo anteriore in basso), tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_tilt_left": "Inclina la manopola a sinistra (bordo sinistro in basso), tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_tilt_right": "Inclina la manopola a destra (bordo destro in basso), tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Ruota la manopola in senso orario, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Ruota la manopola in senso antiorario, tienila, poi lasciala.",
|
||||
"ui.space_mouse_view_axis": "Asse della vista",
|
||||
"ui.space_mouse_view_off": "Disattivato",
|
||||
"ui.space_mouse_view_off_tip": "Il profilo del driver del mouse 3D disattiva questo asse, quindi il suo movimento non muove la vista. Se qui scegli il movimento di un altro asse, i due si scambiano e quel movimento smette di muovere la vista.",
|
||||
"ui.space_mouse_view_orbit_sideways": "Orbita sinistra / destra",
|
||||
"ui.space_mouse_view_orbit_up_down": "Orbita su / giù",
|
||||
"ui.space_mouse_view_pan_sideways": "Panoramica sinistra / destra",
|
||||
"ui.space_mouse_view_pan_up_down": "Panoramica su / giù",
|
||||
"ui.space_mouse_view_roll": "Rollio",
|
||||
"ui.space_mouse_view_unmapped": "Asse {n}",
|
||||
"ui.space_mouse_view_zoom": "Zoom",
|
||||
"ui.spacing": "Spaziatura",
|
||||
"ui.spacing_2": "Spaziatura 2",
|
||||
"ui.sphere": "Sfera",
|
||||
|
||||
+100
-53
@@ -1461,7 +1461,106 @@
|
||||
"trace.stopped_by_contact": "サンプル {sample} で終了: {a} が {b} を通り抜けてしまいます",
|
||||
"trace.stopped_by_loop": "サンプル {sample} で終了: {joint} を通るループはこれ以上閉じられません",
|
||||
"ui.1_degree_of_freedom": "自由度 1",
|
||||
"ui.3d_mouse_axes_reassigned": "軸の割り当ては環境設定で変更されており、このキャリブレーションの後も適用されます。",
|
||||
"ui.3d_mouse_button": "ボタン {number}",
|
||||
"ui.3d_mouse_button_nothing": "なし",
|
||||
"ui.3d_mouse_button_settings": "3Dマウスの設定",
|
||||
"ui.3d_mouse_buttons": "ボタン",
|
||||
"ui.3d_mouse_buttons_hint": "3Dマウスのボタンを押すとここに表示されます。動作を選んでください。",
|
||||
"ui.3d_mouse_buttons_no_device": "ボタンの割り当てを設定するには3Dマウスを接続してください。",
|
||||
"ui.3d_mouse_buttons_reset": "ボタンを既定に戻す",
|
||||
"ui.3d_mouse_calibrate": "キャリブレーション…",
|
||||
"ui.3d_mouse_calibrate_tip": "ウィンドウの指示どおりにキャップを動かすと、この3Dマウスの各軸の向きと移動量をSolidCraftが学習します。この3Dマウス用に保存されます。",
|
||||
"ui.3d_mouse_calibrate_title": "3Dマウスのキャリブレーション",
|
||||
"ui.3d_mouse_calibrate_wait": "SolidCraftが認識できるように、3Dマウスを一度動かしてください。",
|
||||
"ui.3d_mouse_calibrated": "キャリブレーション済み",
|
||||
"ui.3d_mouse_calibration": "3Dマウスのキャリブレーション",
|
||||
"ui.3d_mouse_calibration_done": "完了しました。保存すると、この3Dマウスを接続したときに毎回使われます。",
|
||||
"ui.3d_mouse_calibration_no_response": "ほとんど動かなかった軸があるため、キャリブレーションできません。最初からやり直し、毎回キャップをもっと大きく動かしてください。",
|
||||
"ui.3d_mouse_circles_help": "円はデバイスがキャリブレーション後に送る値を示します。テストモデルは、反転・速度・軸の割り当てを経て、表示がそれをどう扱うかを示します。",
|
||||
"ui.3d_mouse_dead_zone": "デッドゾーン",
|
||||
"ui.3d_mouse_dead_zone_tip": "視点が動き出すまでにキャップを動かす量(全ストロークに対する割合)。キャップに触れていないのに視点が動く場合は大きくしてください。",
|
||||
"ui.3d_mouse_denied": "SolidCraftは3Dマウスを認識していますが、読み取る権限がありません。udevルールは3Dマウスのセットアップ手順を参照してください。",
|
||||
"ui.3d_mouse_denied_short": "見つかりましたが読み取れません",
|
||||
"ui.3d_mouse_driven_by": "この軸を操作するデバイスのキャリブレーション済みの動き。他の軸で使われている動きを選ぶと、2つが入れ替わります。",
|
||||
"ui.3d_mouse_driven_by_label": "操作元",
|
||||
"ui.3d_mouse_driver": "3Dマウスのドライバー",
|
||||
"ui.3d_mouse_driver_auto": "自動",
|
||||
"ui.3d_mouse_driver_direct": "直接 (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "いまは spacenavd 経由で読み取っています。デバイス自体を開けませんでした。",
|
||||
"ui.3d_mouse_driver_only_direct": "デバイスを直接読み取り中(spacenavd は起動していません)",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "spacenavd 経由で読み取り中(他の方法はありません)",
|
||||
"ui.3d_mouse_forget": "キャリブレーションを削除",
|
||||
"ui.3d_mouse_ghost_left_viewport": "ビューポート左",
|
||||
"ui.3d_mouse_ghost_left_window": "ウィンドウ左",
|
||||
"ui.3d_mouse_ghost_position": "位置",
|
||||
"ui.3d_mouse_ghost_right_viewport": "ビューポート右",
|
||||
"ui.3d_mouse_hint": "視点の移動と回転を同時に行います(スケッチ中はパンとズームのみ)",
|
||||
"ui.3d_mouse_kind_cap": "センターに戻る",
|
||||
"ui.3d_mouse_kind_question": "どの種類の3Dマウスですか?",
|
||||
"ui.3d_mouse_kind_trackball": "トラックボール",
|
||||
"ui.3d_mouse_looks_like_cap": "押さえている間は値がそのまま残ります。「センターに戻る」タイプの動き方です。",
|
||||
"ui.3d_mouse_looks_like_trackball": "動きを止めるとすぐに値が戻ります。トラックボールの動き方です。",
|
||||
"ui.3d_mouse_motion_rotation_x": "回転 X",
|
||||
"ui.3d_mouse_motion_rotation_y": "回転 Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "回転 Z",
|
||||
"ui.3d_mouse_motion_translation_x": "平行移動 X",
|
||||
"ui.3d_mouse_motion_translation_y": "平行移動 Y",
|
||||
"ui.3d_mouse_motion_translation_z": "平行移動 Z",
|
||||
"ui.3d_mouse_navigate": "3Dマウスで視点を操作",
|
||||
"ui.3d_mouse_none": "3Dマウスが見つかりません",
|
||||
"ui.3d_mouse_not_calibrated": "未キャリブレーション",
|
||||
"ui.3d_mouse_offer": "3Dマウスをキャリブレーションすると、入れ替わった軸や反転した軸が直り、速度と操作感も整います。",
|
||||
"ui.3d_mouse_offer_never": "今後表示しない",
|
||||
"ui.3d_mouse_offer_swap": "Hidraw(直接ドライバー)に切り替える",
|
||||
"ui.3d_mouse_reading": "読み取り値",
|
||||
"ui.3d_mouse_reset_axes": "軸の割り当てをリセット",
|
||||
"ui.3d_mouse_reset_view": "表示をリセット",
|
||||
"ui.3d_mouse_restore": "3Dマウスの設定を既定に戻す",
|
||||
"ui.3d_mouse_restore_ask": "3Dマウスの設定を既定に戻しますか?キャリブレーションとボタン割り当ては残ります。",
|
||||
"ui.3d_mouse_section": "3Dマウス",
|
||||
"ui.3d_mouse_show_in_view": "3Dビューに3Dマウスを表示",
|
||||
"ui.3d_mouse_show_in_view_tip": "左下に3Dマウスの絵を表示し、操作に合わせて動かします。",
|
||||
"ui.3d_mouse_skip": "このデバイスではできません",
|
||||
"ui.3d_mouse_skip_tip": "この動きを両方向とも飛ばし、キャリブレーションから外します。",
|
||||
"ui.3d_mouse_speed": "3D マウス速度",
|
||||
"ui.3d_mouse_step_lift": "キャップを持ち上げて保持し、離してください。",
|
||||
"ui.3d_mouse_step_press": "キャップを押し下げて保持し、離してください。",
|
||||
"ui.3d_mouse_step_pull": "キャップを手前に引いて保持し、離してください。",
|
||||
"ui.3d_mouse_step_push": "キャップを奥に押して保持し、離してください。",
|
||||
"ui.3d_mouse_step_rest": "キャップから手を離し、動かさないでください。",
|
||||
"ui.3d_mouse_step_rest_trackball": "ボールから手を離し、動かさないでください。",
|
||||
"ui.3d_mouse_step_ring_clockwise": "スクロールリングを時計回りに数クリック回してください。",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "スクロールリングを反時計回りに数クリック回してください。",
|
||||
"ui.3d_mouse_step_roll_away": "ボールを奥に数回転がし、止まるまで待ってください。",
|
||||
"ui.3d_mouse_step_roll_left": "ボールを左に数回転がし、止まるまで待ってください。",
|
||||
"ui.3d_mouse_step_roll_right": "ボールを右に数回転がし、止まるまで待ってください。",
|
||||
"ui.3d_mouse_step_roll_toward": "ボールを手前に数回転がし、止まるまで待ってください。",
|
||||
"ui.3d_mouse_step_slide_left": "キャップを左にスライドして保持し、離してください。",
|
||||
"ui.3d_mouse_step_slide_right": "キャップを右にスライドして保持し、離してください。",
|
||||
"ui.3d_mouse_step_spin_clockwise": "ボールを時計回りに数回ひねり、止まるまで待ってください。",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "ボールを反時計回りに数回ひねり、止まるまで待ってください。",
|
||||
"ui.3d_mouse_step_tilt_away": "キャップの上側を奥に傾けて(手前の縁が上がります)保持し、離してください。",
|
||||
"ui.3d_mouse_step_tilt_left": "キャップを左に傾け(左の縁を下げて)保持し、離してください。",
|
||||
"ui.3d_mouse_step_tilt_right": "キャップを右に傾け(右の縁を下げて)保持し、離してください。",
|
||||
"ui.3d_mouse_step_tilt_toward": "キャップの上側を手前に傾けて(手前の縁が下がります)保持し、離してください。",
|
||||
"ui.3d_mouse_step_touch": "押さずにキャップに手を置いてください。",
|
||||
"ui.3d_mouse_step_twist_clockwise": "キャップを時計回りにひねって保持し、離してください。",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "キャップを反時計回りにひねって保持し、離してください。",
|
||||
"ui.3d_mouse_switch_cap": "センターに戻るタイプとしてキャリブレーション",
|
||||
"ui.3d_mouse_switch_trackball": "トラックボールとしてキャリブレーション",
|
||||
"ui.3d_mouse_view_axis": "ビューの軸",
|
||||
"ui.3d_mouse_view_off": "オフ",
|
||||
"ui.3d_mouse_view_off_tip": "3D マウスのドライバーのプロファイルがこの軸をオフにしているため、この軸の動きではビューは動きません。ここで他の軸の動きを選ぶと 2 つが入れ替わり、その動きではビューが動かなくなります。",
|
||||
"ui.3d_mouse_view_orbit_sideways": "左右オービット",
|
||||
"ui.3d_mouse_view_orbit_up_down": "上下オービット",
|
||||
"ui.3d_mouse_view_pan_sideways": "左右パン",
|
||||
"ui.3d_mouse_view_pan_up_down": "上下パン",
|
||||
"ui.3d_mouse_view_roll": "ロール",
|
||||
"ui.3d_mouse_view_unmapped": "軸 {n}",
|
||||
"ui.3d_mouse_view_zoom": "ズーム",
|
||||
"ui.3d_mouse_waiting": "動きを待機中",
|
||||
"ui.3d_mouse_wiggle": "接続している場合は、少し動かしてみてください。",
|
||||
"ui.3d_sketch": "3D スケッチ",
|
||||
"ui.activate": "アクティブ化",
|
||||
"ui.active_component": "アクティブなコンポーネント",
|
||||
@@ -1699,6 +1798,7 @@
|
||||
"ui.blocked": "到達不可",
|
||||
"ui.bodies": "ボディ",
|
||||
"ui.bodies_all_if_none": "ボディ(未選択ならすべて)",
|
||||
"ui.bottom": "底面",
|
||||
"ui.box": "直方体",
|
||||
"ui.break": "分割",
|
||||
"ui.browser_bodies_one": "{count} ボディ",
|
||||
@@ -2165,59 +2265,6 @@
|
||||
"ui.smooth_wheel_zoom": "滑らかなホイールズーム",
|
||||
"ui.smooth_wheel_zoom_tip": "オフにすると、ホイール操作のたびにズームが即座に反映され、滑らかな減速効果は適用されません。",
|
||||
"ui.snap": "スナップ",
|
||||
"ui.space_mouse_axes_reassigned": "軸の割り当ては環境設定で変更されており、このキャリブレーションの後も適用されます。",
|
||||
"ui.space_mouse_calibrate": "キャリブレーション…",
|
||||
"ui.space_mouse_calibrate_tip": "ウィンドウの指示どおりにキャップを動かすと、この3Dマウスの各軸の向きと移動量をSolidCraftが学習します。この3Dマウス用に保存されます。",
|
||||
"ui.space_mouse_calibrate_title": "3Dマウスのキャリブレーション",
|
||||
"ui.space_mouse_calibrated": "キャリブレーション済み",
|
||||
"ui.space_mouse_calibration": "3Dマウスのキャリブレーション",
|
||||
"ui.space_mouse_calibration_done": "完了しました。保存すると、この3Dマウスを接続したときに毎回使われます。",
|
||||
"ui.space_mouse_calibration_no_response": "ほとんど動かなかった軸があるため、キャリブレーションできません。最初からやり直し、毎回キャップをもっと大きく動かしてください。",
|
||||
"ui.space_mouse_dead_zone": "デッドゾーン",
|
||||
"ui.space_mouse_dead_zone_tip": "視点が動き出すまでにキャップを動かす量(全ストロークに対する割合)。キャップに触れていないのに視点が動く場合は大きくしてください。",
|
||||
"ui.space_mouse_driven_by": "この軸を操作するデバイスのキャリブレーション済みの動き。他の軸で使われている動きを選ぶと、2つが入れ替わります。",
|
||||
"ui.space_mouse_driven_by_label": "操作元",
|
||||
"ui.space_mouse_driver": "3Dマウスのドライバー",
|
||||
"ui.space_mouse_driver_auto": "自動",
|
||||
"ui.space_mouse_driver_direct": "直接 (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "デバイスを直接読み取り中(spacenavd は起動していません)",
|
||||
"ui.space_mouse_driver_only_spacenavd": "spacenavd 経由で読み取り中(他の方法はありません)",
|
||||
"ui.space_mouse_forget": "キャリブレーションを削除",
|
||||
"ui.space_mouse_hint": "視点の移動と回転を同時に行います(スケッチ中はパンとズームのみ)",
|
||||
"ui.space_mouse_motion_rotation_x": "回転 X",
|
||||
"ui.space_mouse_motion_rotation_y": "回転 Y",
|
||||
"ui.space_mouse_motion_rotation_z": "回転 Z",
|
||||
"ui.space_mouse_motion_translation_x": "平行移動 X",
|
||||
"ui.space_mouse_motion_translation_y": "平行移動 Y",
|
||||
"ui.space_mouse_motion_translation_z": "平行移動 Z",
|
||||
"ui.space_mouse_navigate": "SpaceNavigator / SpaceMouseで視点を操作",
|
||||
"ui.space_mouse_none": "3Dマウスが見つかりません",
|
||||
"ui.space_mouse_not_calibrated": "未キャリブレーション",
|
||||
"ui.space_mouse_reading": "読み取り値",
|
||||
"ui.space_mouse_reset_axes": "軸の割り当てをリセット",
|
||||
"ui.space_mouse_step_lift": "キャップを持ち上げて保持し、離してください。",
|
||||
"ui.space_mouse_step_press": "キャップを押し下げて保持し、離してください。",
|
||||
"ui.space_mouse_step_pull": "キャップを手前に引いて保持し、離してください。",
|
||||
"ui.space_mouse_step_push": "キャップを奥に押して保持し、離してください。",
|
||||
"ui.space_mouse_step_rest": "キャップから手を離し、動かさないでください。",
|
||||
"ui.space_mouse_step_slide_left": "キャップを左にスライドして保持し、離してください。",
|
||||
"ui.space_mouse_step_slide_right": "キャップを右にスライドして保持し、離してください。",
|
||||
"ui.space_mouse_step_tilt_back": "キャップを後ろに傾け(手前の縁を上げて)保持し、離してください。",
|
||||
"ui.space_mouse_step_tilt_forward": "キャップを前に傾け(手前の縁を下げて)保持し、離してください。",
|
||||
"ui.space_mouse_step_tilt_left": "キャップを左に傾け(左の縁を下げて)保持し、離してください。",
|
||||
"ui.space_mouse_step_tilt_right": "キャップを右に傾け(右の縁を下げて)保持し、離してください。",
|
||||
"ui.space_mouse_step_twist_clockwise": "キャップを時計回りにひねって保持し、離してください。",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "キャップを反時計回りにひねって保持し、離してください。",
|
||||
"ui.space_mouse_view_axis": "ビューの軸",
|
||||
"ui.space_mouse_view_off": "オフ",
|
||||
"ui.space_mouse_view_off_tip": "3D マウスのドライバーのプロファイルがこの軸をオフにしているため、この軸の動きではビューは動きません。ここで他の軸の動きを選ぶと 2 つが入れ替わり、その動きではビューが動かなくなります。",
|
||||
"ui.space_mouse_view_orbit_sideways": "左右オービット",
|
||||
"ui.space_mouse_view_orbit_up_down": "上下オービット",
|
||||
"ui.space_mouse_view_pan_sideways": "左右パン",
|
||||
"ui.space_mouse_view_pan_up_down": "上下パン",
|
||||
"ui.space_mouse_view_roll": "ロール",
|
||||
"ui.space_mouse_view_unmapped": "軸 {n}",
|
||||
"ui.space_mouse_view_zoom": "ズーム",
|
||||
"ui.spacing": "間隔",
|
||||
"ui.spacing_2": "間隔 2",
|
||||
"ui.sphere": "球",
|
||||
|
||||
+100
-53
@@ -1131,7 +1131,106 @@
|
||||
"trace.stopped_by_contact": "샘플 {sample}에서 끝남: {a}이(가) {b}을(를) 통과합니다",
|
||||
"trace.stopped_by_loop": "샘플 {sample}에서 끝남: {joint}을(를) 지나는 루프를 더 이상 닫을 수 없습니다",
|
||||
"ui.1_degree_of_freedom": "자유도 1개",
|
||||
"ui.3d_mouse_axes_reassigned": "축이 환경설정에서 다시 지정되어 있으며, 이 보정 후에도 적용됩니다.",
|
||||
"ui.3d_mouse_button": "버튼 {number}",
|
||||
"ui.3d_mouse_button_nothing": "없음",
|
||||
"ui.3d_mouse_button_settings": "3D 마우스 설정",
|
||||
"ui.3d_mouse_buttons": "버튼",
|
||||
"ui.3d_mouse_buttons_hint": "3D 마우스의 버튼을 누르면 여기에 나타납니다. 동작을 고르세요.",
|
||||
"ui.3d_mouse_buttons_no_device": "버튼 동작을 정하려면 3D 마우스를 연결하세요.",
|
||||
"ui.3d_mouse_buttons_reset": "기본 버튼으로 되돌리기",
|
||||
"ui.3d_mouse_calibrate": "보정…",
|
||||
"ui.3d_mouse_calibrate_tip": "창의 안내대로 캡을 움직이면 SolidCraft가 이 3D 마우스의 각 축이 어느 방향으로 얼마나 움직이는지 학습합니다. 이 3D 마우스에 저장됩니다.",
|
||||
"ui.3d_mouse_calibrate_title": "3D 마우스 보정",
|
||||
"ui.3d_mouse_calibrate_wait": "SolidCraft가 인식할 수 있도록 3D 마우스를 한 번 움직여 주세요.",
|
||||
"ui.3d_mouse_calibrated": "보정됨",
|
||||
"ui.3d_mouse_calibration": "3D 마우스 보정",
|
||||
"ui.3d_mouse_calibration_done": "완료했습니다. 저장하면 이 3D 마우스를 연결할 때마다 사용됩니다.",
|
||||
"ui.3d_mouse_calibration_no_response": "거의 움직이지 않은 축이 있어 보정할 수 없습니다. 처음부터 다시 하고 매번 캡을 더 멀리 움직이세요.",
|
||||
"ui.3d_mouse_circles_help": "원은 보정 후 장치가 보내는 값을 보여 줍니다. 테스트 모델은 반전, 속도, 축 할당을 거쳐 화면이 그 값으로 하는 일을 보여 줍니다.",
|
||||
"ui.3d_mouse_dead_zone": "데드존",
|
||||
"ui.3d_mouse_dead_zone_tip": "뷰가 움직이기 전에 캡이 움직여야 하는 정도로, 전체 이동 범위에 대한 비율입니다. 캡을 건드리지 않았는데 뷰가 움직이면 높이세요.",
|
||||
"ui.3d_mouse_denied": "SolidCraft가 3D 마우스를 찾았지만 읽을 권한이 없습니다. udev 규칙은 3D 마우스 설정 안내를 참고하세요.",
|
||||
"ui.3d_mouse_denied_short": "찾았지만 읽을 수 없음",
|
||||
"ui.3d_mouse_driven_by": "이 축을 움직이는 장치의 보정된 동작입니다. 다른 축이 이미 쓰는 동작을 고르면 둘이 서로 바뀝니다.",
|
||||
"ui.3d_mouse_driven_by_label": "제어 동작",
|
||||
"ui.3d_mouse_driver": "3D 마우스 드라이버",
|
||||
"ui.3d_mouse_driver_auto": "자동",
|
||||
"ui.3d_mouse_driver_direct": "직접 (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "지금은 spacenavd를 통해 읽는 중입니다. 장치 자체를 열 수 없었습니다.",
|
||||
"ui.3d_mouse_driver_only_direct": "장치를 직접 읽는 중 (spacenavd가 실행 중이 아님)",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "spacenavd로 읽는 중 (다른 방법이 없음)",
|
||||
"ui.3d_mouse_forget": "보정 삭제",
|
||||
"ui.3d_mouse_ghost_left_viewport": "뷰포트 왼쪽",
|
||||
"ui.3d_mouse_ghost_left_window": "창 왼쪽",
|
||||
"ui.3d_mouse_ghost_position": "위치",
|
||||
"ui.3d_mouse_ghost_right_viewport": "뷰포트 오른쪽",
|
||||
"ui.3d_mouse_hint": "뷰를 함께 이동하고 회전합니다. 스케치는 이동과 확대/축소만 사용합니다.",
|
||||
"ui.3d_mouse_kind_cap": "중앙 복귀",
|
||||
"ui.3d_mouse_kind_question": "어떤 종류의 3D 마우스인가요?",
|
||||
"ui.3d_mouse_kind_trackball": "트랙볼",
|
||||
"ui.3d_mouse_looks_like_cap": "잡고 있는 동안 값이 그대로 유지됩니다. ‘중앙 복귀’ 방식 장치처럼요.",
|
||||
"ui.3d_mouse_looks_like_trackball": "움직임을 멈추면 바로 값이 돌아옵니다. 트랙볼처럼요.",
|
||||
"ui.3d_mouse_motion_rotation_x": "회전 X",
|
||||
"ui.3d_mouse_motion_rotation_y": "회전 Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "회전 Z",
|
||||
"ui.3d_mouse_motion_translation_x": "이동 X",
|
||||
"ui.3d_mouse_motion_translation_y": "이동 Y",
|
||||
"ui.3d_mouse_motion_translation_z": "이동 Z",
|
||||
"ui.3d_mouse_navigate": "3D 마우스로 탐색",
|
||||
"ui.3d_mouse_none": "3D 마우스를 찾을 수 없음",
|
||||
"ui.3d_mouse_not_calibrated": "보정 안 됨",
|
||||
"ui.3d_mouse_offer": "3D 마우스를 보정하면 바뀌거나 반전된 축이 바로잡히고 속도와 조작감이 맞춰집니다.",
|
||||
"ui.3d_mouse_offer_never": "다시 표시하지 않음",
|
||||
"ui.3d_mouse_offer_swap": "Hidraw(직접 드라이버)로 전환",
|
||||
"ui.3d_mouse_reading": "읽은 값",
|
||||
"ui.3d_mouse_reset_axes": "축 지정 초기화",
|
||||
"ui.3d_mouse_reset_view": "뷰 초기화",
|
||||
"ui.3d_mouse_restore": "3D 마우스 기본값 복원",
|
||||
"ui.3d_mouse_restore_ask": "3D 마우스 설정을 기본값으로 되돌릴까요? 보정과 버튼 할당은 그대로 유지됩니다.",
|
||||
"ui.3d_mouse_section": "3D 마우스",
|
||||
"ui.3d_mouse_show_in_view": "3D 뷰에 3D 마우스 표시",
|
||||
"ui.3d_mouse_show_in_view_tip": "왼쪽 아래에 3D 마우스 그림을 띄워 움직임을 따라 보여 줍니다.",
|
||||
"ui.3d_mouse_skip": "내 장치는 이 동작을 못 해요",
|
||||
"ui.3d_mouse_skip_tip": "이 동작을 양방향 모두 건너뛰고 보정에서 뺍니다.",
|
||||
"ui.3d_mouse_speed": "3D 마우스 속도",
|
||||
"ui.3d_mouse_step_lift": "캡을 위로 들어 유지한 다음 놓으세요.",
|
||||
"ui.3d_mouse_step_press": "캡을 아래로 눌러 유지한 다음 놓으세요.",
|
||||
"ui.3d_mouse_step_pull": "캡을 몸 쪽으로 당겨 유지한 다음 놓으세요.",
|
||||
"ui.3d_mouse_step_push": "캡을 앞쪽으로 밀어 유지한 다음 놓으세요.",
|
||||
"ui.3d_mouse_step_rest": "캡에서 손을 떼고 가만히 두세요.",
|
||||
"ui.3d_mouse_step_rest_trackball": "볼에서 손을 떼고 가만히 두세요.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "스크롤 링을 시계 방향으로 몇 칸 돌리세요.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "스크롤 링을 시계 반대 방향으로 몇 칸 돌리세요.",
|
||||
"ui.3d_mouse_step_roll_away": "볼을 바깥쪽으로 몇 번 굴린 다음 멈추게 두세요.",
|
||||
"ui.3d_mouse_step_roll_left": "볼을 왼쪽으로 몇 번 굴린 다음 멈추게 두세요.",
|
||||
"ui.3d_mouse_step_roll_right": "볼을 오른쪽으로 몇 번 굴린 다음 멈추게 두세요.",
|
||||
"ui.3d_mouse_step_roll_toward": "볼을 몸 쪽으로 몇 번 굴린 다음 멈추게 두세요.",
|
||||
"ui.3d_mouse_step_slide_left": "캡을 왼쪽으로 밀어 유지한 다음 놓으세요.",
|
||||
"ui.3d_mouse_step_slide_right": "캡을 오른쪽으로 밀어 유지한 다음 놓으세요.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "볼을 시계 방향으로 몇 번 돌린 다음 멈추게 두세요.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "볼을 시계 반대 방향으로 몇 번 돌린 다음 멈추게 두세요.",
|
||||
"ui.3d_mouse_step_tilt_away": "캡 윗부분을 바깥쪽으로 기울여(가까운 쪽 가장자리가 올라감) 유지한 다음 놓으세요.",
|
||||
"ui.3d_mouse_step_tilt_left": "캡을 왼쪽으로 기울여(왼쪽 가장자리를 아래로) 유지한 다음 놓으세요.",
|
||||
"ui.3d_mouse_step_tilt_right": "캡을 오른쪽으로 기울여(오른쪽 가장자리를 아래로) 유지한 다음 놓으세요.",
|
||||
"ui.3d_mouse_step_tilt_toward": "캡 윗부분을 몸 쪽으로 기울여(가까운 쪽 가장자리가 내려감) 유지한 다음 놓으세요.",
|
||||
"ui.3d_mouse_step_touch": "캡을 누르지 말고 손만 올려 두세요.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "캡을 시계 방향으로 돌려 유지한 다음 놓으세요.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "캡을 시계 반대 방향으로 돌려 유지한 다음 놓으세요.",
|
||||
"ui.3d_mouse_switch_cap": "중앙 복귀형으로 보정",
|
||||
"ui.3d_mouse_switch_trackball": "트랙볼로 보정",
|
||||
"ui.3d_mouse_view_axis": "뷰 축",
|
||||
"ui.3d_mouse_view_off": "끔",
|
||||
"ui.3d_mouse_view_off_tip": "3D 마우스 드라이버 프로필이 이 축을 끄므로, 이 축의 동작으로는 뷰가 움직이지 않습니다. 여기서 다른 축의 동작을 고르면 둘이 바뀌고, 그 동작은 더 이상 뷰를 움직이지 않습니다.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "좌우 궤도",
|
||||
"ui.3d_mouse_view_orbit_up_down": "상하 궤도",
|
||||
"ui.3d_mouse_view_pan_sideways": "좌우 이동",
|
||||
"ui.3d_mouse_view_pan_up_down": "상하 이동",
|
||||
"ui.3d_mouse_view_roll": "롤",
|
||||
"ui.3d_mouse_view_unmapped": "축 {n}",
|
||||
"ui.3d_mouse_view_zoom": "확대/축소",
|
||||
"ui.3d_mouse_waiting": "움직임 대기 중",
|
||||
"ui.3d_mouse_wiggle": "연결되어 있다면 살짝 움직여 보세요.",
|
||||
"ui.3d_sketch": "3D 스케치",
|
||||
"ui.activate": "활성화",
|
||||
"ui.active_component": "활성 컴포넌트",
|
||||
@@ -1361,6 +1460,7 @@
|
||||
"ui.blocked": "막힘",
|
||||
"ui.bodies": "바디",
|
||||
"ui.bodies_all_if_none": "바디 (없으면 전체)",
|
||||
"ui.bottom": "저면",
|
||||
"ui.box": "박스",
|
||||
"ui.break": "끊기",
|
||||
"ui.browser_bodies_one": "{count}개 바디",
|
||||
@@ -1810,59 +1910,6 @@
|
||||
"ui.smooth_wheel_zoom": "부드러운 휠 확대/축소",
|
||||
"ui.smooth_wheel_zoom_tip": "끄면 휠 이벤트를 부드러운 꼬리 없이 즉시 적용합니다",
|
||||
"ui.snap": "스냅",
|
||||
"ui.space_mouse_axes_reassigned": "축이 환경설정에서 다시 지정되어 있으며, 이 보정 후에도 적용됩니다.",
|
||||
"ui.space_mouse_calibrate": "보정…",
|
||||
"ui.space_mouse_calibrate_tip": "창의 안내대로 캡을 움직이면 SolidCraft가 이 3D 마우스의 각 축이 어느 방향으로 얼마나 움직이는지 학습합니다. 이 3D 마우스에 저장됩니다.",
|
||||
"ui.space_mouse_calibrate_title": "3D 마우스 보정",
|
||||
"ui.space_mouse_calibrated": "보정됨",
|
||||
"ui.space_mouse_calibration": "3D 마우스 보정",
|
||||
"ui.space_mouse_calibration_done": "완료했습니다. 저장하면 이 3D 마우스를 연결할 때마다 사용됩니다.",
|
||||
"ui.space_mouse_calibration_no_response": "거의 움직이지 않은 축이 있어 보정할 수 없습니다. 처음부터 다시 하고 매번 캡을 더 멀리 움직이세요.",
|
||||
"ui.space_mouse_dead_zone": "데드존",
|
||||
"ui.space_mouse_dead_zone_tip": "보기가 움직이기 전에 캡이 움직여야 하는 정도로, 전체 이동 범위에 대한 비율입니다. 캡을 건드리지 않았는데 보기가 움직이면 높이세요.",
|
||||
"ui.space_mouse_driven_by": "이 축을 움직이는 장치의 보정된 동작입니다. 다른 축이 이미 쓰는 동작을 고르면 둘이 서로 바뀝니다.",
|
||||
"ui.space_mouse_driven_by_label": "제어 동작",
|
||||
"ui.space_mouse_driver": "3D 마우스 드라이버",
|
||||
"ui.space_mouse_driver_auto": "자동",
|
||||
"ui.space_mouse_driver_direct": "직접 (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "장치를 직접 읽는 중 (spacenavd가 실행 중이 아님)",
|
||||
"ui.space_mouse_driver_only_spacenavd": "spacenavd로 읽는 중 (다른 방법이 없음)",
|
||||
"ui.space_mouse_forget": "보정 삭제",
|
||||
"ui.space_mouse_hint": "뷰를 함께 이동하고 회전합니다. 스케치는 이동과 확대/축소만 사용합니다.",
|
||||
"ui.space_mouse_motion_rotation_x": "회전 X",
|
||||
"ui.space_mouse_motion_rotation_y": "회전 Y",
|
||||
"ui.space_mouse_motion_rotation_z": "회전 Z",
|
||||
"ui.space_mouse_motion_translation_x": "이동 X",
|
||||
"ui.space_mouse_motion_translation_y": "이동 Y",
|
||||
"ui.space_mouse_motion_translation_z": "이동 Z",
|
||||
"ui.space_mouse_navigate": "SpaceNavigator / SpaceMouse로 탐색",
|
||||
"ui.space_mouse_none": "3D 마우스를 찾을 수 없음",
|
||||
"ui.space_mouse_not_calibrated": "보정 안 됨",
|
||||
"ui.space_mouse_reading": "읽은 값",
|
||||
"ui.space_mouse_reset_axes": "축 지정 초기화",
|
||||
"ui.space_mouse_step_lift": "캡을 위로 들어 유지한 다음 놓으세요.",
|
||||
"ui.space_mouse_step_press": "캡을 아래로 눌러 유지한 다음 놓으세요.",
|
||||
"ui.space_mouse_step_pull": "캡을 몸 쪽으로 당겨 유지한 다음 놓으세요.",
|
||||
"ui.space_mouse_step_push": "캡을 앞쪽으로 밀어 유지한 다음 놓으세요.",
|
||||
"ui.space_mouse_step_rest": "캡에서 손을 떼고 가만히 두세요.",
|
||||
"ui.space_mouse_step_slide_left": "캡을 왼쪽으로 밀어 유지한 다음 놓으세요.",
|
||||
"ui.space_mouse_step_slide_right": "캡을 오른쪽으로 밀어 유지한 다음 놓으세요.",
|
||||
"ui.space_mouse_step_tilt_back": "캡을 뒤로 기울여(앞쪽 가장자리를 위로) 유지한 다음 놓으세요.",
|
||||
"ui.space_mouse_step_tilt_forward": "캡을 앞으로 기울여(앞쪽 가장자리를 아래로) 유지한 다음 놓으세요.",
|
||||
"ui.space_mouse_step_tilt_left": "캡을 왼쪽으로 기울여(왼쪽 가장자리를 아래로) 유지한 다음 놓으세요.",
|
||||
"ui.space_mouse_step_tilt_right": "캡을 오른쪽으로 기울여(오른쪽 가장자리를 아래로) 유지한 다음 놓으세요.",
|
||||
"ui.space_mouse_step_twist_clockwise": "캡을 시계 방향으로 돌려 유지한 다음 놓으세요.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "캡을 시계 반대 방향으로 돌려 유지한 다음 놓으세요.",
|
||||
"ui.space_mouse_view_axis": "보기 축",
|
||||
"ui.space_mouse_view_off": "끔",
|
||||
"ui.space_mouse_view_off_tip": "3D 마우스 드라이버 프로필이 이 축을 끄므로, 이 축의 동작으로는 뷰가 움직이지 않습니다. 여기서 다른 축의 동작을 고르면 둘이 바뀌고, 그 동작은 더 이상 뷰를 움직이지 않습니다.",
|
||||
"ui.space_mouse_view_orbit_sideways": "좌우 궤도",
|
||||
"ui.space_mouse_view_orbit_up_down": "상하 궤도",
|
||||
"ui.space_mouse_view_pan_sideways": "좌우 이동",
|
||||
"ui.space_mouse_view_pan_up_down": "상하 이동",
|
||||
"ui.space_mouse_view_roll": "롤",
|
||||
"ui.space_mouse_view_unmapped": "축 {n}",
|
||||
"ui.space_mouse_view_zoom": "확대/축소",
|
||||
"ui.spacing": "간격",
|
||||
"ui.spacing_2": "간격 2",
|
||||
"ui.sphere": "구",
|
||||
|
||||
+100
-28
@@ -1125,7 +1125,106 @@
|
||||
"trace.stopped_by_contact": "Berakhir pada sampel {sample}: {a} akan menembusi {b}",
|
||||
"trace.stopped_by_loop": "Berakhir pada sampel {sample}: gelung melalui {joint} tidak lagi dapat ditutup",
|
||||
"ui.1_degree_of_freedom": "1 darjah kebebasan",
|
||||
"ui.3d_mouse_axes_reassigned": "Paksi ditetapkan semula dalam Keutamaan, dan itu masih terpakai di atas penentukuran ini.",
|
||||
"ui.3d_mouse_button": "Butang {number}",
|
||||
"ui.3d_mouse_button_nothing": "Tiada",
|
||||
"ui.3d_mouse_button_settings": "Tetapan tetikus 3D",
|
||||
"ui.3d_mouse_buttons": "Butang",
|
||||
"ui.3d_mouse_buttons_hint": "Tekan butang pada tetikus 3D anda untuk mencarinya di sini, kemudian pilih fungsinya.",
|
||||
"ui.3d_mouse_buttons_no_device": "Sambungkan tetikus 3D untuk menetapkan fungsi butangnya.",
|
||||
"ui.3d_mouse_buttons_reset": "Pulihkan butang lalai",
|
||||
"ui.3d_mouse_calibrate": "Tentukur…",
|
||||
"ui.3d_mouse_calibrate_tip": "Gerakkan penutup seperti yang diminta tetingkap, supaya SolidCraft mempelajari arah dan jarak setiap paksi tetikus 3D ini. Disimpan untuk tetikus 3D ini.",
|
||||
"ui.3d_mouse_calibrate_title": "Tentukur tetikus 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Gerakkan tetikus 3D sekali supaya SolidCraft dapat melihatnya.",
|
||||
"ui.3d_mouse_calibrated": "Telah ditentukur",
|
||||
"ui.3d_mouse_calibration": "Penentukuran tetikus 3D",
|
||||
"ui.3d_mouse_calibration_done": "Selesai. Simpan untuk digunakan setiap kali tetikus 3D ini disambungkan.",
|
||||
"ui.3d_mouse_calibration_no_response": "Satu paksi hampir tidak bergerak, jadi tidak dapat ditentukur. Mulakan semula dan gerakkan penutup lebih jauh setiap kali.",
|
||||
"ui.3d_mouse_circles_help": "Bulatan menunjukkan apa yang dihantar peranti selepas penentukuran. Model ujian menunjukkan apa yang dilakukan paparan dengannya, selepas songsangan, kelajuan dan penetapan paksi.",
|
||||
"ui.3d_mouse_dead_zone": "Zon mati",
|
||||
"ui.3d_mouse_dead_zone_tip": "Sejauh mana penutup perlu bergerak sebelum pandangan bergerak, sebagai bahagian daripada julat penuhnya. Naikkan jika pandangan hanyut semasa penutup dalam keadaan rehat.",
|
||||
"ui.3d_mouse_denied": "SolidCraft nampak tetikus 3D tetapi tidak dibenarkan membacanya. Lihat nota persediaan tetikus 3D untuk peraturan udev.",
|
||||
"ui.3d_mouse_denied_short": "Dijumpai, tetapi tidak boleh dibaca",
|
||||
"ui.3d_mouse_driven_by": "Gerakan peranti yang telah ditentukur yang memacu paksi ini. Memilih gerakan yang sudah digunakan paksi lain akan menukar kedua-duanya.",
|
||||
"ui.3d_mouse_driven_by_label": "dipacu oleh",
|
||||
"ui.3d_mouse_driver": "Pemacu tetikus 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automatik",
|
||||
"ui.3d_mouse_driver_direct": "Terus (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Buat masa ini dibaca melalui spacenavd: peranti itu sendiri tidak dapat dibuka.",
|
||||
"ui.3d_mouse_driver_only_direct": "Membaca peranti secara terus; spacenavd tidak berjalan",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Membaca melalui spacenavd, satu-satunya cara yang ada",
|
||||
"ui.3d_mouse_forget": "Lupakan penentukuran",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Viewport kiri",
|
||||
"ui.3d_mouse_ghost_left_window": "Tetingkap kiri",
|
||||
"ui.3d_mouse_ghost_position": "Kedudukan",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Viewport kanan",
|
||||
"ui.3d_mouse_hint": "Alih dan putar pandangan serentak; dalam lakaran, hanya sorot dan zum digunakan.",
|
||||
"ui.3d_mouse_kind_cap": "Kembali ke tengah",
|
||||
"ui.3d_mouse_kind_question": "Apakah jenis tetikus 3D ini?",
|
||||
"ui.3d_mouse_kind_trackball": "Bola jejak",
|
||||
"ui.3d_mouse_looks_like_cap": "Bacaan kekal selagi anda menahannya, seperti peranti jenis “Kembali ke tengah”.",
|
||||
"ui.3d_mouse_looks_like_trackball": "Bacaan kembali sebaik sahaja anda berhenti bergerak, seperti bola jejak.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Putaran X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Putaran Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Putaran Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Anjakan X",
|
||||
"ui.3d_mouse_motion_translation_y": "Anjakan Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Anjakan Z",
|
||||
"ui.3d_mouse_navigate": "Navigasi dengan tetikus 3D",
|
||||
"ui.3d_mouse_none": "Tiada tetikus 3D ditemui",
|
||||
"ui.3d_mouse_not_calibrated": "Belum ditentukur",
|
||||
"ui.3d_mouse_offer": "Menentukur tetikus 3D membetulkan paksi yang bertukar atau terbalik dan menetapkan kelajuan serta rasanya.",
|
||||
"ui.3d_mouse_offer_never": "Jangan tunjuk lagi",
|
||||
"ui.3d_mouse_offer_swap": "Tukar ke Hidraw (pemacu terus)",
|
||||
"ui.3d_mouse_reading": "Bacaan",
|
||||
"ui.3d_mouse_reset_axes": "Tetapkan semula penetapan paksi",
|
||||
"ui.3d_mouse_reset_view": "Set semula paparan",
|
||||
"ui.3d_mouse_restore": "Pulihkan lalai tetikus 3D",
|
||||
"ui.3d_mouse_restore_ask": "Tetapkan semula tetapan tetikus 3D kepada lalai? Penentukuran dan pemetaan butang kekal.",
|
||||
"ui.3d_mouse_section": "Tetikus 3D",
|
||||
"ui.3d_mouse_show_in_view": "Tunjukkan tetikus 3D dalam paparan 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Gambar tetikus 3D anda di sudut kiri bawah, yang bergerak apabila anda menggerakkannya.",
|
||||
"ui.3d_mouse_skip": "Peranti saya tidak boleh buat ini",
|
||||
"ui.3d_mouse_skip_tip": "Langkau gerakan ini kedua-dua arah; penentukuran tidak menggunakannya.",
|
||||
"ui.3d_mouse_speed": "Kelajuan tetikus 3D",
|
||||
"ui.3d_mouse_step_lift": "Angkat penutup ke atas, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_press": "Tekan penutup ke bawah, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_pull": "Tarik penutup ke arah anda, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_push": "Tolak penutup menjauhi anda, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_rest": "Alihkan tangan anda daripada penutup dan biarkan ia diam.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Alihkan tangan anda daripada bola dan biarkan ia diam.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Putar gelang tatal ikut arah jam beberapa klik.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Putar gelang tatal lawan arah jam beberapa klik.",
|
||||
"ui.3d_mouse_step_roll_away": "Gulingkan bola menjauhi anda beberapa kali, kemudian biarkan ia berhenti.",
|
||||
"ui.3d_mouse_step_roll_left": "Gulingkan bola ke kiri beberapa kali, kemudian biarkan ia berhenti.",
|
||||
"ui.3d_mouse_step_roll_right": "Gulingkan bola ke kanan beberapa kali, kemudian biarkan ia berhenti.",
|
||||
"ui.3d_mouse_step_roll_toward": "Gulingkan bola ke arah anda beberapa kali, kemudian biarkan ia berhenti.",
|
||||
"ui.3d_mouse_step_slide_left": "Luncurkan penutup ke kiri, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_slide_right": "Luncurkan penutup ke kanan, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Pusing bola ikut arah jam beberapa kali, kemudian biarkan ia berhenti.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Pusing bola lawan arah jam beberapa kali, kemudian biarkan ia berhenti.",
|
||||
"ui.3d_mouse_step_tilt_away": "Condongkan bahagian atas penutup menjauhi anda (tepi terdekat naik), tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_tilt_left": "Condongkan penutup ke kiri (tepi kiri turun), tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_tilt_right": "Condongkan penutup ke kanan (tepi kanan turun), tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Condongkan bahagian atas penutup ke arah anda (tepi terdekat turun), tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_touch": "Letakkan tangan anda pada penutup tanpa menolaknya.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Pusing penutup ikut arah jam, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Pusing penutup lawan arah jam, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_switch_cap": "Tentukur sebagai kembali ke tengah",
|
||||
"ui.3d_mouse_switch_trackball": "Tentukur sebagai bola jejak",
|
||||
"ui.3d_mouse_view_axis": "Paksi paparan",
|
||||
"ui.3d_mouse_view_off": "Mati",
|
||||
"ui.3d_mouse_view_off_tip": "Profil pemacu tetikus 3D mematikan paksi ini, jadi gerakannya tidak menggerakkan paparan. Memilih gerakan paksi lain di sini menukar kedua-duanya, dan gerakan itu tidak lagi menggerakkan paparan.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbit kiri / kanan",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbit atas / bawah",
|
||||
"ui.3d_mouse_view_pan_sideways": "Pan kiri / kanan",
|
||||
"ui.3d_mouse_view_pan_up_down": "Pan atas / bawah",
|
||||
"ui.3d_mouse_view_roll": "Gelek",
|
||||
"ui.3d_mouse_view_unmapped": "Paksi {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zum",
|
||||
"ui.3d_mouse_waiting": "Menunggu pergerakan",
|
||||
"ui.3d_mouse_wiggle": "Jika anda ada yang disambungkan, gerakkannya sedikit.",
|
||||
"ui.3d_sketch": "Lakaran 3D",
|
||||
"ui.activate": "Aktifkan",
|
||||
"ui.active_component": "Komponen aktif",
|
||||
@@ -1355,6 +1454,7 @@
|
||||
"ui.blocked": "Disekat",
|
||||
"ui.bodies": "Jasad",
|
||||
"ui.bodies_all_if_none": "Jasad (semua jika tiada)",
|
||||
"ui.bottom": "Bawah",
|
||||
"ui.box": "Kotak",
|
||||
"ui.break": "Putuskan",
|
||||
"ui.browser_bodies_one": "{count} jasad",
|
||||
@@ -1798,34 +1898,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zum roda lancar",
|
||||
"ui.smooth_wheel_zoom_tip": "Matikan untuk mengezum serta-merta bagi setiap putaran roda tetikus, tanpa gerakan susulan",
|
||||
"ui.snap": "Lekap",
|
||||
"ui.space_mouse_calibrate": "Tentukur…",
|
||||
"ui.space_mouse_calibrate_tip": "Gerakkan penutup seperti yang diminta tetingkap, supaya SolidCraft mempelajari arah dan jarak setiap paksi tetikus 3D ini. Disimpan untuk tetikus 3D ini.",
|
||||
"ui.space_mouse_calibrate_title": "Tentukur tetikus 3D",
|
||||
"ui.space_mouse_calibrated": "Telah ditentukur",
|
||||
"ui.space_mouse_calibration": "Penentukuran tetikus 3D",
|
||||
"ui.space_mouse_calibration_done": "Selesai. Simpan untuk digunakan setiap kali tetikus 3D ini disambungkan.",
|
||||
"ui.space_mouse_calibration_no_response": "Satu paksi hampir tidak bergerak, jadi tidak dapat ditentukur. Mulakan semula dan gerakkan penutup lebih jauh setiap kali.",
|
||||
"ui.space_mouse_dead_zone": "Zon mati",
|
||||
"ui.space_mouse_dead_zone_tip": "Sejauh mana penutup perlu bergerak sebelum pandangan bergerak, sebagai bahagian daripada julat penuhnya. Naikkan jika pandangan hanyut semasa penutup dalam keadaan rehat.",
|
||||
"ui.space_mouse_forget": "Lupakan penentukuran",
|
||||
"ui.space_mouse_hint": "Alih dan putar pandangan serentak; dalam lakaran, hanya sorot dan zum digunakan.",
|
||||
"ui.space_mouse_navigate": "Navigasi dengan SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Tiada tetikus 3D ditemui",
|
||||
"ui.space_mouse_not_calibrated": "Belum ditentukur",
|
||||
"ui.space_mouse_reading": "Bacaan",
|
||||
"ui.space_mouse_step_lift": "Angkat penutup ke atas, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_press": "Tekan penutup ke bawah, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_pull": "Tarik penutup ke arah anda, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_push": "Tolak penutup menjauhi anda, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_rest": "Alihkan tangan anda daripada penutup dan biarkan ia diam.",
|
||||
"ui.space_mouse_step_slide_left": "Luncurkan penutup ke kiri, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_slide_right": "Luncurkan penutup ke kanan, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_tilt_back": "Condongkan penutup ke belakang (tepi depan naik), tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_tilt_forward": "Condongkan penutup ke hadapan (tepi depan turun), tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_tilt_left": "Condongkan penutup ke kiri (tepi kiri turun), tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_tilt_right": "Condongkan penutup ke kanan (tepi kanan turun), tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Pusing penutup ikut arah jam, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Pusing penutup lawan arah jam, tahan, kemudian lepaskan.",
|
||||
"ui.spacing": "Selang",
|
||||
"ui.spacing_2": "Selang 2",
|
||||
"ui.sphere": "Sfera",
|
||||
|
||||
+43
-28
@@ -1124,7 +1124,50 @@
|
||||
"trace.stopped_by_contact": "Eindigt bij monster {sample}: {a} zou door {b} heen gaan",
|
||||
"trace.stopped_by_loop": "Eindigt bij monster {sample}: de lus via {joint} kan niet verder sluiten",
|
||||
"ui.1_degree_of_freedom": "1 vrijheidsgraad",
|
||||
"ui.3d_mouse_calibrate": "Kalibreren…",
|
||||
"ui.3d_mouse_calibrate_tip": "Beweeg de dop zoals het venster vraagt, zodat SolidCraft leert welke kant elke as van deze 3D-muis op gaat en hoe ver. Opgeslagen voor deze 3D-muis.",
|
||||
"ui.3d_mouse_calibrate_title": "3D-muis kalibreren",
|
||||
"ui.3d_mouse_calibrate_wait": "Beweeg de 3D-muis één keer, zodat SolidCraft hem ziet.",
|
||||
"ui.3d_mouse_calibrated": "Gekalibreerd",
|
||||
"ui.3d_mouse_calibration": "Kalibratie van 3D-muis",
|
||||
"ui.3d_mouse_calibration_done": "Gereed. Sla op om het te gebruiken wanneer deze 3D-muis is aangesloten.",
|
||||
"ui.3d_mouse_calibration_no_response": "Een as bewoog nauwelijks en kan dus niet worden gekalibreerd. Begin opnieuw en beweeg de dop telkens verder.",
|
||||
"ui.3d_mouse_circles_help": "De cirkels tonen wat het apparaat na kalibratie stuurt. Het testmodel toont wat de weergave ermee doet, na omkeren, snelheid en asindeling.",
|
||||
"ui.3d_mouse_dead_zone": "Dode zone",
|
||||
"ui.3d_mouse_dead_zone_tip": "Hoe ver de dop moet bewegen voordat de weergave dat doet, als aandeel van de volledige slag. Verhoog dit als de weergave afdrijft terwijl de dop in rust is.",
|
||||
"ui.3d_mouse_denied": "SolidCraft ziet een 3D-muis maar mag hem niet lezen. Zie de installatienotities voor de 3D-muis voor de udev-regel.",
|
||||
"ui.3d_mouse_denied_short": "Gevonden, maar niet leesbaar",
|
||||
"ui.3d_mouse_driver_fallback": "Voorlopig via spacenavd gelezen: het apparaat zelf kon niet worden geopend.",
|
||||
"ui.3d_mouse_forget": "Kalibratie vergeten",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Viewport links",
|
||||
"ui.3d_mouse_ghost_left_window": "Venster links",
|
||||
"ui.3d_mouse_ghost_position": "Positie",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Viewport rechts",
|
||||
"ui.3d_mouse_hint": "Verplaats en roteer de weergave samen; schetsen gebruiken alleen pannen en zoomen.",
|
||||
"ui.3d_mouse_navigate": "Navigeren met de 3D-muis",
|
||||
"ui.3d_mouse_none": "Geen 3D-muis gevonden",
|
||||
"ui.3d_mouse_not_calibrated": "Niet gekalibreerd",
|
||||
"ui.3d_mouse_offer": "Je 3D-muis kalibreren verhelpt verwisselde of omgekeerde assen en stelt snelheid en gevoel in.",
|
||||
"ui.3d_mouse_offer_never": "Niet meer tonen",
|
||||
"ui.3d_mouse_offer_swap": "Naar Hidraw wisselen (direct)",
|
||||
"ui.3d_mouse_reading": "Uitlezen",
|
||||
"ui.3d_mouse_reset_view": "Weergave resetten",
|
||||
"ui.3d_mouse_restore": "3D-muis standaardwaarden herstellen",
|
||||
"ui.3d_mouse_restore_ask": "De 3D-muisinstellingen terugzetten naar de standaardwaarden? Je kalibratie en knoppentoewijzing blijven staan.",
|
||||
"ui.3d_mouse_speed": "Snelheid van 3D-muis",
|
||||
"ui.3d_mouse_step_lift": "Til de dop omhoog, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_press": "Druk de dop omlaag, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_pull": "Trek de dop naar u toe, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_push": "Duw de dop van u af, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_rest": "Haal uw hand van de dop en laat hem stilstaan.",
|
||||
"ui.3d_mouse_step_slide_left": "Schuif de dop naar links, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_slide_right": "Schuif de dop naar rechts, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_tilt_left": "Kantel de dop naar links (linkerrand omlaag), houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_tilt_right": "Kantel de dop naar rechts (rechterrand omlaag), houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Draai de dop met de klok mee, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Draai de dop tegen de klok in, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_waiting": "Wacht op beweging",
|
||||
"ui.3d_mouse_wiggle": "Als je er een hebt aangesloten, beweeg hem even.",
|
||||
"ui.3d_sketch": "3D-schets",
|
||||
"ui.activate": "Activeren",
|
||||
"ui.active_component": "Actief component",
|
||||
@@ -1797,34 +1840,6 @@
|
||||
"ui.smooth_wheel_zoom": "Vloeiend zoomen met wiel",
|
||||
"ui.smooth_wheel_zoom_tip": "Uitschakelen om elke wielgebeurtenis direct toe te passen, zonder uitloop",
|
||||
"ui.snap": "Snap",
|
||||
"ui.space_mouse_calibrate": "Kalibreren…",
|
||||
"ui.space_mouse_calibrate_tip": "Beweeg de dop zoals het venster vraagt, zodat SolidCraft leert welke kant elke as van deze 3D-muis op gaat en hoe ver. Opgeslagen voor deze 3D-muis.",
|
||||
"ui.space_mouse_calibrate_title": "3D-muis kalibreren",
|
||||
"ui.space_mouse_calibrated": "Gekalibreerd",
|
||||
"ui.space_mouse_calibration": "Kalibratie van 3D-muis",
|
||||
"ui.space_mouse_calibration_done": "Gereed. Sla op om het te gebruiken wanneer deze 3D-muis is aangesloten.",
|
||||
"ui.space_mouse_calibration_no_response": "Een as bewoog nauwelijks en kan dus niet worden gekalibreerd. Begin opnieuw en beweeg de dop telkens verder.",
|
||||
"ui.space_mouse_dead_zone": "Dode zone",
|
||||
"ui.space_mouse_dead_zone_tip": "Hoe ver de dop moet bewegen voordat de weergave dat doet, als aandeel van de volledige slag. Verhoog dit als de weergave afdrijft terwijl de dop in rust is.",
|
||||
"ui.space_mouse_forget": "Kalibratie vergeten",
|
||||
"ui.space_mouse_hint": "Verplaats en roteer de weergave samen; schetsen gebruiken alleen pannen en zoomen.",
|
||||
"ui.space_mouse_navigate": "Navigeren met SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Geen 3D-muis gevonden",
|
||||
"ui.space_mouse_not_calibrated": "Niet gekalibreerd",
|
||||
"ui.space_mouse_reading": "Uitlezen",
|
||||
"ui.space_mouse_step_lift": "Til de dop omhoog, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_press": "Druk de dop omlaag, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_pull": "Trek de dop naar u toe, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_push": "Duw de dop van u af, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_rest": "Haal uw hand van de dop en laat hem stilstaan.",
|
||||
"ui.space_mouse_step_slide_left": "Schuif de dop naar links, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_slide_right": "Schuif de dop naar rechts, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_tilt_back": "Kantel de dop naar achteren (voorrand omhoog), houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_tilt_forward": "Kantel de dop naar voren (voorrand omlaag), houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_tilt_left": "Kantel de dop naar links (linkerrand omlaag), houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_tilt_right": "Kantel de dop naar rechts (rechterrand omlaag), houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Draai de dop met de klok mee, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Draai de dop tegen de klok in, houd vast en laat dan los.",
|
||||
"ui.spacing": "Tussenruimte",
|
||||
"ui.spacing_2": "Tussenruimte 2",
|
||||
"ui.sphere": "Bol",
|
||||
|
||||
+100
-53
@@ -1309,7 +1309,106 @@
|
||||
"trace.stopped_by_contact": "Kończy się na próbce {sample}: {a} przeszedłby przez {b}",
|
||||
"trace.stopped_by_loop": "Kończy się na próbce {sample}: pętla przez {joint} nie może się bardziej zamknąć",
|
||||
"ui.1_degree_of_freedom": "1 stopień swobody",
|
||||
"ui.3d_mouse_axes_reassigned": "Osie są przypisane na nowo w Ustawieniach i to nadal obowiązuje razem z tą kalibracją.",
|
||||
"ui.3d_mouse_button": "Przycisk {number}",
|
||||
"ui.3d_mouse_button_nothing": "Nic",
|
||||
"ui.3d_mouse_button_settings": "Ustawienia myszy 3D",
|
||||
"ui.3d_mouse_buttons": "Przyciski",
|
||||
"ui.3d_mouse_buttons_hint": "Naciśnij przycisk na myszy 3D, aby go tu znaleźć, a potem wybierz, co ma robić.",
|
||||
"ui.3d_mouse_buttons_no_device": "Podłącz mysz 3D, aby ustawić, co robią jej przyciski.",
|
||||
"ui.3d_mouse_buttons_reset": "Przywróć domyślne przyciski",
|
||||
"ui.3d_mouse_calibrate": "Kalibruj…",
|
||||
"ui.3d_mouse_calibrate_tip": "Przesuwaj nakładkę zgodnie z poleceniami okna, aby SolidCraft nauczył się kierunku i zakresu każdej osi tej myszy 3D. Zapisywane dla tej myszy 3D.",
|
||||
"ui.3d_mouse_calibrate_title": "Kalibracja myszy 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Rusz raz myszą 3D, aby SolidCraft ją zobaczył.",
|
||||
"ui.3d_mouse_calibrated": "Skalibrowano",
|
||||
"ui.3d_mouse_calibration": "Kalibracja myszy 3D",
|
||||
"ui.3d_mouse_calibration_done": "Gotowe. Zapisz, aby używać kalibracji po każdym podłączeniu tej myszy 3D.",
|
||||
"ui.3d_mouse_calibration_no_response": "Jedna oś prawie się nie poruszyła, więc nie można jej skalibrować. Zacznij od nowa i za każdym razem wychylaj nakładkę mocniej.",
|
||||
"ui.3d_mouse_circles_help": "Koła pokazują, co urządzenie wysyła po kalibracji. Model testowy pokazuje, co widok z tym robi po odwróceniu, szybkości i przypisaniu osi.",
|
||||
"ui.3d_mouse_dead_zone": "Strefa martwa",
|
||||
"ui.3d_mouse_dead_zone_tip": "O ile trzeba wychylić nakładkę, zanim widok się poruszy, jako część pełnego wychylenia. Zwiększ, jeśli widok dryfuje przy nieruszanej nakładce.",
|
||||
"ui.3d_mouse_denied": "SolidCraft widzi mysz 3D, ale nie ma pozwolenia, by ją odczytać. Regułę udev opisują uwagi o konfiguracji myszy 3D.",
|
||||
"ui.3d_mouse_denied_short": "Znaleziono, ale nie można odczytać",
|
||||
"ui.3d_mouse_driven_by": "Który skalibrowany ruch urządzenia steruje tą osią. Wybranie ruchu używanego już przez inną oś zamienia je miejscami.",
|
||||
"ui.3d_mouse_driven_by_label": "sterowana przez",
|
||||
"ui.3d_mouse_driver": "Sterownik myszy 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automatyczny",
|
||||
"ui.3d_mouse_driver_direct": "Bezpośredni (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Na razie odczyt przez spacenavd: samego urządzenia nie udało się otworzyć.",
|
||||
"ui.3d_mouse_driver_only_direct": "Urządzenie jest czytane bezpośrednio; spacenavd nie działa",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Odczyt przez spacenavd, jedyna dostępna droga",
|
||||
"ui.3d_mouse_forget": "Zapomnij kalibrację",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Lewy widok",
|
||||
"ui.3d_mouse_ghost_left_window": "Lewe okno",
|
||||
"ui.3d_mouse_ghost_position": "Położenie",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Prawy widok",
|
||||
"ui.3d_mouse_hint": "Przesuwaj i obracaj widok jednocześnie; w szkicach działa tylko przesuwanie i powiększanie.",
|
||||
"ui.3d_mouse_kind_cap": "Powrót do środka",
|
||||
"ui.3d_mouse_kind_question": "Jaki to rodzaj myszy 3D?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "Odczyt zostaje, dopóki trzymasz, jak w urządzeniu typu „Powrót do środka”.",
|
||||
"ui.3d_mouse_looks_like_trackball": "Odczyt wraca, gdy tylko przestajesz ruszać, jak w trackballu.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Obrót X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Obrót Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Obrót Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Przesunięcie X",
|
||||
"ui.3d_mouse_motion_translation_y": "Przesunięcie Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Przesunięcie Z",
|
||||
"ui.3d_mouse_navigate": "Nawiguj za pomocą myszy 3D",
|
||||
"ui.3d_mouse_none": "Nie znaleziono myszy 3D",
|
||||
"ui.3d_mouse_not_calibrated": "Nieskalibrowano",
|
||||
"ui.3d_mouse_offer": "Kalibracja myszy 3D naprawia zamienione lub odwrócone osie oraz ustawia jej szybkość i odczucie.",
|
||||
"ui.3d_mouse_offer_never": "Nie pokazuj więcej",
|
||||
"ui.3d_mouse_offer_swap": "Przełącz na Hidraw (sterownik bezpośredni)",
|
||||
"ui.3d_mouse_reading": "Odczyt",
|
||||
"ui.3d_mouse_reset_axes": "Przywróć przypisanie osi",
|
||||
"ui.3d_mouse_reset_view": "Resetuj widok",
|
||||
"ui.3d_mouse_restore": "Przywróć ustawienia myszy 3D",
|
||||
"ui.3d_mouse_restore_ask": "Przywrócić domyślne ustawienia myszy 3D? Kalibracja i przypisanie przycisków zostają.",
|
||||
"ui.3d_mouse_section": "Mysz 3D",
|
||||
"ui.3d_mouse_show_in_view": "Pokazuj mysz 3D w widoku 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Obraz twojej myszy 3D w lewym dolnym rogu, który porusza się razem z nią.",
|
||||
"ui.3d_mouse_skip": "Moje urządzenie tego nie potrafi",
|
||||
"ui.3d_mouse_skip_tip": "Pomija ten ruch w obu kierunkach; kalibracja go nie uwzględnia.",
|
||||
"ui.3d_mouse_speed": "Czułość myszy 3D",
|
||||
"ui.3d_mouse_step_lift": "Unieś nakładkę, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_press": "Wciśnij nakładkę w dół, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_pull": "Pociągnij nakładkę do siebie, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_push": "Popchnij nakładkę od siebie, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_rest": "Zdejmij dłoń z nakładki i nie ruszaj jej.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Zdejmij dłoń z kulki i nie ruszaj jej.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Obróć pierścień przewijania o kilka kliknięć zgodnie z ruchem wskazówek zegara.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Obróć pierścień przewijania o kilka kliknięć przeciwnie do ruchu wskazówek zegara.",
|
||||
"ui.3d_mouse_step_roll_away": "Potocz kulkę kilka razy od siebie, a potem pozwól jej się zatrzymać.",
|
||||
"ui.3d_mouse_step_roll_left": "Potocz kulkę kilka razy w lewo, a potem pozwól jej się zatrzymać.",
|
||||
"ui.3d_mouse_step_roll_right": "Potocz kulkę kilka razy w prawo, a potem pozwól jej się zatrzymać.",
|
||||
"ui.3d_mouse_step_roll_toward": "Potocz kulkę kilka razy do siebie, a potem pozwól jej się zatrzymać.",
|
||||
"ui.3d_mouse_step_slide_left": "Przesuń nakładkę w lewo, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_slide_right": "Przesuń nakładkę w prawo, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Zakręć kulką kilka razy zgodnie z ruchem wskazówek zegara, a potem pozwól jej się zatrzymać.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Zakręć kulką kilka razy przeciwnie do ruchu wskazówek zegara, a potem pozwól jej się zatrzymać.",
|
||||
"ui.3d_mouse_step_tilt_away": "Przechyl górę nakładki od siebie (krawędź najbliżej ciebie idzie w górę), przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_tilt_left": "Przechyl nakładkę w lewo (lewa krawędź w dół), przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_tilt_right": "Przechyl nakładkę w prawo (prawa krawędź w dół), przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Przechyl górę nakładki do siebie (krawędź najbliżej ciebie idzie w dół), przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_touch": "Połóż dłoń na nakładce, nie naciskając jej.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Obróć nakładkę zgodnie z ruchem wskazówek zegara, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Obróć nakładkę przeciwnie do ruchu wskazówek zegara, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_switch_cap": "Kalibruj jako powrót do środka",
|
||||
"ui.3d_mouse_switch_trackball": "Kalibruj jako trackball",
|
||||
"ui.3d_mouse_view_axis": "Oś widoku",
|
||||
"ui.3d_mouse_view_off": "Wyłączona",
|
||||
"ui.3d_mouse_view_off_tip": "Profil sterownika myszy 3D wyłącza tę oś, więc jej ruch nie porusza widokiem. Wybranie tu ruchu innej osi zamienia je miejscami, a ten ruch przestaje poruszać widokiem.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbita w lewo / prawo",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbita w górę / dół",
|
||||
"ui.3d_mouse_view_pan_sideways": "Przesuwanie w lewo / prawo",
|
||||
"ui.3d_mouse_view_pan_up_down": "Przesuwanie w górę / dół",
|
||||
"ui.3d_mouse_view_roll": "Przechył",
|
||||
"ui.3d_mouse_view_unmapped": "Oś {n}",
|
||||
"ui.3d_mouse_view_zoom": "Powiększenie",
|
||||
"ui.3d_mouse_waiting": "Czekam na ruch",
|
||||
"ui.3d_mouse_wiggle": "Jeśli masz podłączoną, poruszaj nią chwilę.",
|
||||
"ui.3d_sketch": "Szkic 3D",
|
||||
"ui.activate": "Aktywuj",
|
||||
"ui.active_component": "Aktywny komponent",
|
||||
@@ -1541,6 +1640,7 @@
|
||||
"ui.blocked": "Zablokowane",
|
||||
"ui.bodies": "Bryły",
|
||||
"ui.bodies_all_if_none": "Bryły (wszystkie, jeśli brak)",
|
||||
"ui.bottom": "Dół",
|
||||
"ui.box": "Prostopadłościan",
|
||||
"ui.break": "Podziel",
|
||||
"ui.browser_bodies_one": "{count} bryła",
|
||||
@@ -1991,59 +2091,6 @@
|
||||
"ui.smooth_wheel_zoom": "Płynne powiększanie kółkiem",
|
||||
"ui.smooth_wheel_zoom_tip": "Wyłącz, aby każdy ruch kółka działał od razu, bez wygładzania",
|
||||
"ui.snap": "Przyciąganie",
|
||||
"ui.space_mouse_axes_reassigned": "Osie są przypisane inaczej w Preferencjach i to przypisanie nadal działa niezależnie od tej kalibracji.",
|
||||
"ui.space_mouse_calibrate": "Kalibruj…",
|
||||
"ui.space_mouse_calibrate_tip": "Przesuwaj nakładkę zgodnie z poleceniami okna, aby SolidCraft nauczył się kierunku i zakresu każdej osi tej myszy 3D. Zapisywane dla tej myszy 3D.",
|
||||
"ui.space_mouse_calibrate_title": "Kalibracja myszy 3D",
|
||||
"ui.space_mouse_calibrated": "Skalibrowano",
|
||||
"ui.space_mouse_calibration": "Kalibracja myszy 3D",
|
||||
"ui.space_mouse_calibration_done": "Gotowe. Zapisz, aby używać kalibracji po każdym podłączeniu tej myszy 3D.",
|
||||
"ui.space_mouse_calibration_no_response": "Jedna oś prawie się nie poruszyła, więc nie można jej skalibrować. Zacznij od nowa i za każdym razem wychylaj nakładkę mocniej.",
|
||||
"ui.space_mouse_dead_zone": "Strefa martwa",
|
||||
"ui.space_mouse_dead_zone_tip": "O ile trzeba wychylić nakładkę, zanim widok się poruszy, jako część pełnego wychylenia. Zwiększ, jeśli widok dryfuje przy nieruszanej nakładce.",
|
||||
"ui.space_mouse_driven_by": "Który skalibrowany ruch urządzenia steruje tą osią. Wybranie ruchu używanego już przez inną oś zamienia je miejscami.",
|
||||
"ui.space_mouse_driven_by_label": "sterowana przez",
|
||||
"ui.space_mouse_driver": "Sterownik myszy 3D",
|
||||
"ui.space_mouse_driver_auto": "Automatycznie",
|
||||
"ui.space_mouse_driver_direct": "Bezpośrednio (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "Bezpośredni odczyt urządzenia; spacenavd nie działa",
|
||||
"ui.space_mouse_driver_only_spacenavd": "Odczyt przez spacenavd, jedyny dostępny sposób",
|
||||
"ui.space_mouse_forget": "Zapomnij kalibrację",
|
||||
"ui.space_mouse_hint": "Przesuwaj i obracaj widok jednocześnie; w szkicach działa tylko przesuwanie i powiększanie.",
|
||||
"ui.space_mouse_motion_rotation_x": "Obrót X",
|
||||
"ui.space_mouse_motion_rotation_y": "Obrót Y",
|
||||
"ui.space_mouse_motion_rotation_z": "Obrót Z",
|
||||
"ui.space_mouse_motion_translation_x": "Przesunięcie X",
|
||||
"ui.space_mouse_motion_translation_y": "Przesunięcie Y",
|
||||
"ui.space_mouse_motion_translation_z": "Przesunięcie Z",
|
||||
"ui.space_mouse_navigate": "Nawiguj za pomocą SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Nie znaleziono myszy 3D",
|
||||
"ui.space_mouse_not_calibrated": "Nieskalibrowano",
|
||||
"ui.space_mouse_reading": "Odczyt",
|
||||
"ui.space_mouse_reset_axes": "Resetuj przypisanie osi",
|
||||
"ui.space_mouse_step_lift": "Unieś nakładkę, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_press": "Wciśnij nakładkę w dół, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_pull": "Pociągnij nakładkę do siebie, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_push": "Popchnij nakładkę od siebie, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_rest": "Zdejmij dłoń z nakładki i nie ruszaj jej.",
|
||||
"ui.space_mouse_step_slide_left": "Przesuń nakładkę w lewo, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_slide_right": "Przesuń nakładkę w prawo, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_tilt_back": "Przechyl nakładkę do tyłu (przednia krawędź w górę), przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_tilt_forward": "Przechyl nakładkę do przodu (przednia krawędź w dół), przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_tilt_left": "Przechyl nakładkę w lewo (lewa krawędź w dół), przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_tilt_right": "Przechyl nakładkę w prawo (prawa krawędź w dół), przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Obróć nakładkę zgodnie z ruchem wskazówek zegara, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Obróć nakładkę przeciwnie do ruchu wskazówek zegara, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_view_axis": "Oś widoku",
|
||||
"ui.space_mouse_view_off": "Wyłączona",
|
||||
"ui.space_mouse_view_off_tip": "Profil sterownika myszy 3D wyłącza tę oś, więc jej ruch nie porusza widokiem. Wybranie tutaj ruchu innej osi zamienia je miejscami, a ten ruch przestaje poruszać widokiem.",
|
||||
"ui.space_mouse_view_orbit_sideways": "Obracanie w lewo / w prawo",
|
||||
"ui.space_mouse_view_orbit_up_down": "Obracanie w górę / w dół",
|
||||
"ui.space_mouse_view_pan_sideways": "Przesuwanie w lewo / w prawo",
|
||||
"ui.space_mouse_view_pan_up_down": "Przesuwanie w górę / w dół",
|
||||
"ui.space_mouse_view_roll": "Przechylenie",
|
||||
"ui.space_mouse_view_unmapped": "Oś {n}",
|
||||
"ui.space_mouse_view_zoom": "Powiększanie",
|
||||
"ui.spacing": "Odstęp",
|
||||
"ui.spacing_2": "Odstęp 2",
|
||||
"ui.sphere": "Sfera",
|
||||
|
||||
+100
-53
@@ -1125,7 +1125,106 @@
|
||||
"trace.stopped_by_contact": "Termina na amostra {sample}: {a} atravessaria {b}",
|
||||
"trace.stopped_by_loop": "Termina na amostra {sample}: o laço por {joint} não pode fechar mais",
|
||||
"ui.1_degree_of_freedom": "1 grau de liberdade",
|
||||
"ui.3d_mouse_axes_reassigned": "Os eixos estão reatribuídos nas Preferências, e isso continua valendo sobre esta calibração.",
|
||||
"ui.3d_mouse_button": "Botão {number}",
|
||||
"ui.3d_mouse_button_nothing": "Nada",
|
||||
"ui.3d_mouse_button_settings": "Configurações do mouse 3D",
|
||||
"ui.3d_mouse_buttons": "Botões",
|
||||
"ui.3d_mouse_buttons_hint": "Aperte um botão do seu mouse 3D para encontrá-lo aqui e escolha o que ele faz.",
|
||||
"ui.3d_mouse_buttons_no_device": "Conecte um mouse 3D para escolher o que os botões fazem.",
|
||||
"ui.3d_mouse_buttons_reset": "Restaurar os botões padrão",
|
||||
"ui.3d_mouse_calibrate": "Calibrar…",
|
||||
"ui.3d_mouse_calibrate_tip": "Mova a tampa como a janela pedir, para que o SolidCraft aprenda para onde e até onde vai cada eixo deste mouse 3D. Salvo para este mouse 3D.",
|
||||
"ui.3d_mouse_calibrate_title": "Calibrar mouse 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Mova o mouse 3D uma vez para o SolidCraft vê-lo.",
|
||||
"ui.3d_mouse_calibrated": "Calibrado",
|
||||
"ui.3d_mouse_calibration": "Calibração do mouse 3D",
|
||||
"ui.3d_mouse_calibration_done": "Pronto. Salve para usá-la sempre que este mouse 3D estiver conectado.",
|
||||
"ui.3d_mouse_calibration_no_response": "Um eixo quase não se moveu, então não dá para calibrá-lo. Recomece e mova a tampa mais longe a cada vez.",
|
||||
"ui.3d_mouse_circles_help": "Os círculos mostram o que o dispositivo envia após a calibração. O modelo de teste mostra o que a vista faz com isso, após inversão, velocidade e atribuição de eixos.",
|
||||
"ui.3d_mouse_dead_zone": "Zona morta",
|
||||
"ui.3d_mouse_dead_zone_tip": "Quanto a tampa precisa se mover antes de a vista se mover, como fração do curso completo. Aumente se a vista se mexer com a tampa parada.",
|
||||
"ui.3d_mouse_denied": "O SolidCraft vê um mouse 3D, mas não tem permissão para lê-lo. A regra udev está nas notas de configuração do mouse 3D.",
|
||||
"ui.3d_mouse_denied_short": "Encontrado, mas não legível",
|
||||
"ui.3d_mouse_driven_by": "Qual movimento calibrado do dispositivo controla este eixo. Escolher um que outro eixo já usa troca os dois.",
|
||||
"ui.3d_mouse_driven_by_label": "controlado por",
|
||||
"ui.3d_mouse_driver": "Driver do mouse 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automático",
|
||||
"ui.3d_mouse_driver_direct": "Direto (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Lendo por enquanto pelo spacenavd: não foi possível abrir o dispositivo em si.",
|
||||
"ui.3d_mouse_driver_only_direct": "Lendo o dispositivo diretamente; o spacenavd não está em execução",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Lendo pelo spacenavd, a única opção disponível",
|
||||
"ui.3d_mouse_forget": "Esquecer calibração",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Viewport à esquerda",
|
||||
"ui.3d_mouse_ghost_left_window": "Janela à esquerda",
|
||||
"ui.3d_mouse_ghost_position": "Posição",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Viewport à direita",
|
||||
"ui.3d_mouse_hint": "Move e gira a vista juntos; esboços usam apenas deslocamento e zoom.",
|
||||
"ui.3d_mouse_kind_cap": "Retorno ao centro",
|
||||
"ui.3d_mouse_kind_question": "Que tipo de mouse 3D é?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "O valor se mantém enquanto você segura, como em um dispositivo do tipo “Retorno ao centro”.",
|
||||
"ui.3d_mouse_looks_like_trackball": "O valor volta assim que você para de mover, como em um trackball.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotação X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotação Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotação Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Translação X",
|
||||
"ui.3d_mouse_motion_translation_y": "Translação Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Translação Z",
|
||||
"ui.3d_mouse_navigate": "Navegar com o mouse 3D",
|
||||
"ui.3d_mouse_none": "Nenhum mouse 3D encontrado",
|
||||
"ui.3d_mouse_not_calibrated": "Não calibrado",
|
||||
"ui.3d_mouse_offer": "Calibrar o mouse 3D corrige eixos trocados ou invertidos e ajusta a velocidade e a sensação.",
|
||||
"ui.3d_mouse_offer_never": "Não mostrar novamente",
|
||||
"ui.3d_mouse_offer_swap": "Mudar para Hidraw (driver direto)",
|
||||
"ui.3d_mouse_reading": "Leitura",
|
||||
"ui.3d_mouse_reset_axes": "Redefinir atribuição de eixos",
|
||||
"ui.3d_mouse_reset_view": "Redefinir visualização",
|
||||
"ui.3d_mouse_restore": "Restaurar padrões do mouse 3D",
|
||||
"ui.3d_mouse_restore_ask": "Voltar as configurações do mouse 3D aos padrões? A calibração e o mapeamento dos botões continuam.",
|
||||
"ui.3d_mouse_section": "Mouse 3D",
|
||||
"ui.3d_mouse_show_in_view": "Mostrar o mouse 3D na vista 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Uma imagem do seu mouse 3D no canto inferior esquerdo, que se move quando você o move.",
|
||||
"ui.3d_mouse_skip": "Meu dispositivo não faz isso",
|
||||
"ui.3d_mouse_skip_tip": "Pula este movimento nos dois sentidos; a calibração o deixa de fora.",
|
||||
"ui.3d_mouse_speed": "Velocidade do mouse 3D",
|
||||
"ui.3d_mouse_step_lift": "Levante a tampa, segure e solte.",
|
||||
"ui.3d_mouse_step_press": "Pressione a tampa para baixo, segure e solte.",
|
||||
"ui.3d_mouse_step_pull": "Puxe a tampa na sua direção, segure e solte.",
|
||||
"ui.3d_mouse_step_push": "Empurre a tampa para longe de você, segure e solte.",
|
||||
"ui.3d_mouse_step_rest": "Tire a mão da tampa e deixe-a parada.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Tire a mão da bola e deixe-a parada.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Gire o anel de rolagem alguns cliques no sentido horário.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Gire o anel de rolagem alguns cliques no sentido anti-horário.",
|
||||
"ui.3d_mouse_step_roll_away": "Role a bola para longe de você algumas vezes e deixe-a parar.",
|
||||
"ui.3d_mouse_step_roll_left": "Role a bola para a esquerda algumas vezes e deixe-a parar.",
|
||||
"ui.3d_mouse_step_roll_right": "Role a bola para a direita algumas vezes e deixe-a parar.",
|
||||
"ui.3d_mouse_step_roll_toward": "Role a bola na sua direção algumas vezes e deixe-a parar.",
|
||||
"ui.3d_mouse_step_slide_left": "Deslize a tampa para a esquerda, segure e solte.",
|
||||
"ui.3d_mouse_step_slide_right": "Deslize a tampa para a direita, segure e solte.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Gire a bola no sentido horário algumas vezes e deixe-a parar.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Gire a bola no sentido anti-horário algumas vezes e deixe-a parar.",
|
||||
"ui.3d_mouse_step_tilt_away": "Incline o topo da tampa para longe de você (a borda mais próxima sobe), segure e solte.",
|
||||
"ui.3d_mouse_step_tilt_left": "Incline a tampa para a esquerda (borda esquerda para baixo), segure e solte.",
|
||||
"ui.3d_mouse_step_tilt_right": "Incline a tampa para a direita (borda direita para baixo), segure e solte.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Incline o topo da tampa na sua direção (a borda mais próxima desce), segure e solte.",
|
||||
"ui.3d_mouse_step_touch": "Apoie a mão na tampa sem empurrá-la.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Gire a tampa no sentido horário, segure e solte.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Gire a tampa no sentido anti-horário, segure e solte.",
|
||||
"ui.3d_mouse_switch_cap": "Calibrar como retorno ao centro",
|
||||
"ui.3d_mouse_switch_trackball": "Calibrar como trackball",
|
||||
"ui.3d_mouse_view_axis": "Eixo da vista",
|
||||
"ui.3d_mouse_view_off": "Desligado",
|
||||
"ui.3d_mouse_view_off_tip": "O perfil do driver do mouse 3D desliga este eixo, então o movimento dele não move a vista. Escolher aqui o movimento de outro eixo troca os dois, e esse movimento deixa de mover a vista.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbitar esquerda / direita",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbitar cima / baixo",
|
||||
"ui.3d_mouse_view_pan_sideways": "Deslocar esquerda / direita",
|
||||
"ui.3d_mouse_view_pan_up_down": "Deslocar cima / baixo",
|
||||
"ui.3d_mouse_view_roll": "Girar a vista",
|
||||
"ui.3d_mouse_view_unmapped": "Eixo {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "Aguardando movimento",
|
||||
"ui.3d_mouse_wiggle": "Se você tiver um conectado, mexa nele um pouco.",
|
||||
"ui.3d_sketch": "Esboço 3D",
|
||||
"ui.activate": "Ativar",
|
||||
"ui.active_component": "Componente ativo",
|
||||
@@ -1355,6 +1454,7 @@
|
||||
"ui.blocked": "Bloqueado",
|
||||
"ui.bodies": "Corpos",
|
||||
"ui.bodies_all_if_none": "Corpos (todos se nenhum)",
|
||||
"ui.bottom": "Base",
|
||||
"ui.box": "Caixa",
|
||||
"ui.break": "Quebrar",
|
||||
"ui.browser_bodies_one": "{count} corpo",
|
||||
@@ -1798,59 +1898,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zoom suave com a roda",
|
||||
"ui.smooth_wheel_zoom_tip": "Desative para aplicar cada evento da roda na hora, sem rastro de suavização",
|
||||
"ui.snap": "Encaixe",
|
||||
"ui.space_mouse_axes_reassigned": "Os eixos estão reatribuídos nas Preferências, e isso continua valendo sobre esta calibração.",
|
||||
"ui.space_mouse_calibrate": "Calibrar…",
|
||||
"ui.space_mouse_calibrate_tip": "Mova a tampa como a janela pedir, para que o SolidCraft aprenda para onde e até onde vai cada eixo deste mouse 3D. Salvo para este mouse 3D.",
|
||||
"ui.space_mouse_calibrate_title": "Calibrar mouse 3D",
|
||||
"ui.space_mouse_calibrated": "Calibrado",
|
||||
"ui.space_mouse_calibration": "Calibração do mouse 3D",
|
||||
"ui.space_mouse_calibration_done": "Pronto. Salve para usá-la sempre que este mouse 3D estiver conectado.",
|
||||
"ui.space_mouse_calibration_no_response": "Um eixo quase não se moveu, então não dá para calibrá-lo. Recomece e mova a tampa mais longe a cada vez.",
|
||||
"ui.space_mouse_dead_zone": "Zona morta",
|
||||
"ui.space_mouse_dead_zone_tip": "Quanto a tampa precisa se mover antes de a vista se mover, como fração do curso completo. Aumente se a vista se mexer com a tampa parada.",
|
||||
"ui.space_mouse_driven_by": "Qual movimento calibrado do dispositivo controla este eixo. Escolher um que outro eixo já usa troca os dois.",
|
||||
"ui.space_mouse_driven_by_label": "controlado por",
|
||||
"ui.space_mouse_driver": "Driver do mouse 3D",
|
||||
"ui.space_mouse_driver_auto": "Automático",
|
||||
"ui.space_mouse_driver_direct": "Direto (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "Lendo o dispositivo diretamente; o spacenavd não está em execução",
|
||||
"ui.space_mouse_driver_only_spacenavd": "Lendo pelo spacenavd, a única opção disponível",
|
||||
"ui.space_mouse_forget": "Esquecer calibração",
|
||||
"ui.space_mouse_hint": "Move e gira a vista juntos; esboços usam apenas deslocamento e zoom.",
|
||||
"ui.space_mouse_motion_rotation_x": "Rotação X",
|
||||
"ui.space_mouse_motion_rotation_y": "Rotação Y",
|
||||
"ui.space_mouse_motion_rotation_z": "Rotação Z",
|
||||
"ui.space_mouse_motion_translation_x": "Translação X",
|
||||
"ui.space_mouse_motion_translation_y": "Translação Y",
|
||||
"ui.space_mouse_motion_translation_z": "Translação Z",
|
||||
"ui.space_mouse_navigate": "Navegar com SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Nenhum mouse 3D encontrado",
|
||||
"ui.space_mouse_not_calibrated": "Não calibrado",
|
||||
"ui.space_mouse_reading": "Leitura",
|
||||
"ui.space_mouse_reset_axes": "Redefinir atribuição de eixos",
|
||||
"ui.space_mouse_step_lift": "Levante a tampa, segure e solte.",
|
||||
"ui.space_mouse_step_press": "Pressione a tampa para baixo, segure e solte.",
|
||||
"ui.space_mouse_step_pull": "Puxe a tampa na sua direção, segure e solte.",
|
||||
"ui.space_mouse_step_push": "Empurre a tampa para longe de você, segure e solte.",
|
||||
"ui.space_mouse_step_rest": "Tire a mão da tampa e deixe-a parada.",
|
||||
"ui.space_mouse_step_slide_left": "Deslize a tampa para a esquerda, segure e solte.",
|
||||
"ui.space_mouse_step_slide_right": "Deslize a tampa para a direita, segure e solte.",
|
||||
"ui.space_mouse_step_tilt_back": "Incline a tampa para trás (borda da frente para cima), segure e solte.",
|
||||
"ui.space_mouse_step_tilt_forward": "Incline a tampa para a frente (borda da frente para baixo), segure e solte.",
|
||||
"ui.space_mouse_step_tilt_left": "Incline a tampa para a esquerda (borda esquerda para baixo), segure e solte.",
|
||||
"ui.space_mouse_step_tilt_right": "Incline a tampa para a direita (borda direita para baixo), segure e solte.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Gire a tampa no sentido horário, segure e solte.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Gire a tampa no sentido anti-horário, segure e solte.",
|
||||
"ui.space_mouse_view_axis": "Eixo da vista",
|
||||
"ui.space_mouse_view_off": "Desligado",
|
||||
"ui.space_mouse_view_off_tip": "O perfil do driver do mouse 3D desliga este eixo, então o movimento dele não move a vista. Escolher aqui o movimento de outro eixo troca os dois, e esse movimento deixa de mover a vista.",
|
||||
"ui.space_mouse_view_orbit_sideways": "Orbitar esquerda / direita",
|
||||
"ui.space_mouse_view_orbit_up_down": "Orbitar cima / baixo",
|
||||
"ui.space_mouse_view_pan_sideways": "Deslocar esquerda / direita",
|
||||
"ui.space_mouse_view_pan_up_down": "Deslocar cima / baixo",
|
||||
"ui.space_mouse_view_roll": "Girar a vista",
|
||||
"ui.space_mouse_view_unmapped": "Eixo {n}",
|
||||
"ui.space_mouse_view_zoom": "Zoom",
|
||||
"ui.spacing": "Espaçamento",
|
||||
"ui.spacing_2": "Espaçamento 2",
|
||||
"ui.sphere": "Esfera",
|
||||
|
||||
+43
-28
@@ -1124,7 +1124,50 @@
|
||||
"trace.stopped_by_contact": "Se termină la eșantionul {sample}: {a} ar trece prin {b}",
|
||||
"trace.stopped_by_loop": "Se termină la eșantionul {sample}: bucla prin {joint} nu se mai poate închide",
|
||||
"ui.1_degree_of_freedom": "1 grad de libertate",
|
||||
"ui.3d_mouse_calibrate": "Calibrează…",
|
||||
"ui.3d_mouse_calibrate_tip": "Mișcați capacul așa cum cere fereastra, ca SolidCraft să învețe în ce direcție merge fiecare axă a acestui mouse 3D și cât de departe. Salvat pentru acest mouse 3D.",
|
||||
"ui.3d_mouse_calibrate_title": "Calibrează mouse-ul 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Mișcă o dată mouse-ul 3D ca SolidCraft să îl vadă.",
|
||||
"ui.3d_mouse_calibrated": "Calibrat",
|
||||
"ui.3d_mouse_calibration": "Calibrarea mouse-ului 3D",
|
||||
"ui.3d_mouse_calibration_done": "Gata. Salvați-o pentru a o folosi ori de câte ori acest mouse 3D este conectat.",
|
||||
"ui.3d_mouse_calibration_no_response": "O axă abia s-a mișcat, deci nu poate fi calibrată. Reîncepeți și mișcați capacul mai mult de fiecare dată.",
|
||||
"ui.3d_mouse_circles_help": "Cercurile arată ce trimite dispozitivul după calibrare. Modelul de test arată ce face vizualizarea cu acestea, după inversare, viteză și atribuirea axelor.",
|
||||
"ui.3d_mouse_dead_zone": "Zonă moartă",
|
||||
"ui.3d_mouse_dead_zone_tip": "Cât trebuie să se miște capacul înainte ca vizualizarea să se miște, ca fracțiune din cursa completă. Măriți-o dacă vizualizarea derivă cât capacul este în repaus.",
|
||||
"ui.3d_mouse_denied": "SolidCraft vede un mouse 3D, dar nu are voie să îl citească. Regula udev este în notele de configurare a mouse-ului 3D.",
|
||||
"ui.3d_mouse_denied_short": "Găsit, dar nu poate fi citit",
|
||||
"ui.3d_mouse_driver_fallback": "Deocamdată se citește prin spacenavd: dispozitivul în sine nu a putut fi deschis.",
|
||||
"ui.3d_mouse_forget": "Uită calibrarea",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Viewport stânga",
|
||||
"ui.3d_mouse_ghost_left_window": "Fereastră stânga",
|
||||
"ui.3d_mouse_ghost_position": "Poziție",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Viewport dreapta",
|
||||
"ui.3d_mouse_hint": "Mută și rotește vizualizarea împreună; schițele folosesc doar panoramare și zoom.",
|
||||
"ui.3d_mouse_navigate": "Navighează cu mouse-ul 3D",
|
||||
"ui.3d_mouse_none": "Niciun mouse 3D găsit",
|
||||
"ui.3d_mouse_not_calibrated": "Necalibrat",
|
||||
"ui.3d_mouse_offer": "Calibrarea mouse-ului 3D corectează axele inversate sau schimbate între ele și îi reglează viteza și senzația.",
|
||||
"ui.3d_mouse_offer_never": "Nu mai afișa",
|
||||
"ui.3d_mouse_offer_swap": "Treci la Hidraw (driver direct)",
|
||||
"ui.3d_mouse_reading": "Valoare citită",
|
||||
"ui.3d_mouse_reset_view": "Resetează vizualizarea",
|
||||
"ui.3d_mouse_restore": "Restaurează implicitele mouse-ului 3D",
|
||||
"ui.3d_mouse_restore_ask": "Revii la valorile implicite ale setărilor mouse-ului 3D? Calibrarea și atribuirea butoanelor rămân.",
|
||||
"ui.3d_mouse_speed": "Viteza mouse-ului 3D",
|
||||
"ui.3d_mouse_step_lift": "Ridicați capacul în sus, țineți-l, apoi eliberați-l.",
|
||||
"ui.3d_mouse_step_press": "Apăsați capacul în jos, țineți-l, apoi eliberați-l.",
|
||||
"ui.3d_mouse_step_pull": "Trageți capacul spre dumneavoastră, țineți-l, apoi eliberați-l.",
|
||||
"ui.3d_mouse_step_push": "Împingeți capacul de la dumneavoastră, țineți-l, apoi eliberați-l.",
|
||||
"ui.3d_mouse_step_rest": "Luați mâna de pe capac și lăsați-l nemișcat.",
|
||||
"ui.3d_mouse_step_slide_left": "Glisați capacul spre stânga, țineți-l, apoi eliberați-l.",
|
||||
"ui.3d_mouse_step_slide_right": "Glisați capacul spre dreapta, țineți-l, apoi eliberați-l.",
|
||||
"ui.3d_mouse_step_tilt_left": "Înclinați capacul spre stânga (marginea stângă în jos), țineți-l, apoi eliberați-l.",
|
||||
"ui.3d_mouse_step_tilt_right": "Înclinați capacul spre dreapta (marginea dreaptă în jos), țineți-l, apoi eliberați-l.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Răsuciți capacul în sensul acelor de ceasornic, țineți-l, apoi eliberați-l.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Răsuciți capacul în sens invers acelor de ceasornic, țineți-l, apoi eliberați-l.",
|
||||
"ui.3d_mouse_waiting": "Se așteaptă mișcare",
|
||||
"ui.3d_mouse_wiggle": "Dacă ai unul conectat, mișcă-l puțin.",
|
||||
"ui.3d_sketch": "Schiță 3D",
|
||||
"ui.activate": "Activează",
|
||||
"ui.active_component": "Componentă activă",
|
||||
@@ -1797,34 +1840,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zoom lin cu rotița",
|
||||
"ui.smooth_wheel_zoom_tip": "Dezactivați pentru a aplica imediat fiecare eveniment al rotiței, fără coadă de netezire",
|
||||
"ui.snap": "Agățare",
|
||||
"ui.space_mouse_calibrate": "Calibrează…",
|
||||
"ui.space_mouse_calibrate_tip": "Mișcați capacul așa cum cere fereastra, ca SolidCraft să învețe în ce direcție merge fiecare axă a acestui mouse 3D și cât de departe. Salvat pentru acest mouse 3D.",
|
||||
"ui.space_mouse_calibrate_title": "Calibrează mouse-ul 3D",
|
||||
"ui.space_mouse_calibrated": "Calibrat",
|
||||
"ui.space_mouse_calibration": "Calibrarea mouse-ului 3D",
|
||||
"ui.space_mouse_calibration_done": "Gata. Salvați-o pentru a o folosi ori de câte ori acest mouse 3D este conectat.",
|
||||
"ui.space_mouse_calibration_no_response": "O axă abia s-a mișcat, deci nu poate fi calibrată. Reîncepeți și mișcați capacul mai mult de fiecare dată.",
|
||||
"ui.space_mouse_dead_zone": "Zonă moartă",
|
||||
"ui.space_mouse_dead_zone_tip": "Cât trebuie să se miște capacul înainte ca vizualizarea să se miște, ca fracțiune din cursa completă. Măriți-o dacă vizualizarea derivă cât capacul este în repaus.",
|
||||
"ui.space_mouse_forget": "Uită calibrarea",
|
||||
"ui.space_mouse_hint": "Mută și rotește vizualizarea împreună; schițele folosesc doar panoramare și zoom.",
|
||||
"ui.space_mouse_navigate": "Navighează cu SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Niciun mouse 3D găsit",
|
||||
"ui.space_mouse_not_calibrated": "Necalibrat",
|
||||
"ui.space_mouse_reading": "Valoare citită",
|
||||
"ui.space_mouse_step_lift": "Ridicați capacul în sus, țineți-l, apoi eliberați-l.",
|
||||
"ui.space_mouse_step_press": "Apăsați capacul în jos, țineți-l, apoi eliberați-l.",
|
||||
"ui.space_mouse_step_pull": "Trageți capacul spre dumneavoastră, țineți-l, apoi eliberați-l.",
|
||||
"ui.space_mouse_step_push": "Împingeți capacul de la dumneavoastră, țineți-l, apoi eliberați-l.",
|
||||
"ui.space_mouse_step_rest": "Luați mâna de pe capac și lăsați-l nemișcat.",
|
||||
"ui.space_mouse_step_slide_left": "Glisați capacul spre stânga, țineți-l, apoi eliberați-l.",
|
||||
"ui.space_mouse_step_slide_right": "Glisați capacul spre dreapta, țineți-l, apoi eliberați-l.",
|
||||
"ui.space_mouse_step_tilt_back": "Înclinați capacul spre spate (marginea din față în sus), țineți-l, apoi eliberați-l.",
|
||||
"ui.space_mouse_step_tilt_forward": "Înclinați capacul spre față (marginea din față în jos), țineți-l, apoi eliberați-l.",
|
||||
"ui.space_mouse_step_tilt_left": "Înclinați capacul spre stânga (marginea stângă în jos), țineți-l, apoi eliberați-l.",
|
||||
"ui.space_mouse_step_tilt_right": "Înclinați capacul spre dreapta (marginea dreaptă în jos), țineți-l, apoi eliberați-l.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Răsuciți capacul în sensul acelor de ceasornic, țineți-l, apoi eliberați-l.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Răsuciți capacul în sens invers acelor de ceasornic, țineți-l, apoi eliberați-l.",
|
||||
"ui.spacing": "Spațiere",
|
||||
"ui.spacing_2": "Spațierea 2",
|
||||
"ui.sphere": "Sferă",
|
||||
|
||||
+100
-53
@@ -1124,7 +1124,106 @@
|
||||
"trace.stopped_by_contact": "Заканчивается на отсчёте {sample}: {a} прошёл бы сквозь {b}",
|
||||
"trace.stopped_by_loop": "Заканчивается на отсчёте {sample}: контур через {joint} дальше не замыкается",
|
||||
"ui.1_degree_of_freedom": "1 степень свободы",
|
||||
"ui.3d_mouse_axes_reassigned": "Оси переназначены в настройках, и это действует и поверх этой калибровки.",
|
||||
"ui.3d_mouse_button": "Кнопка {number}",
|
||||
"ui.3d_mouse_button_nothing": "Ничего",
|
||||
"ui.3d_mouse_button_settings": "Настройки 3D-мыши",
|
||||
"ui.3d_mouse_buttons": "Кнопки",
|
||||
"ui.3d_mouse_buttons_hint": "Нажмите кнопку на 3D-мыши, чтобы найти её здесь, и выберите, что она делает.",
|
||||
"ui.3d_mouse_buttons_no_device": "Подключите 3D-мышь, чтобы назначить её кнопки.",
|
||||
"ui.3d_mouse_buttons_reset": "Вернуть кнопки по умолчанию",
|
||||
"ui.3d_mouse_calibrate": "Калибровать…",
|
||||
"ui.3d_mouse_calibrate_tip": "Двигайте колпачок так, как просит окно, чтобы SolidCraft узнал, куда и насколько далеко движется каждая ось этой 3D-мыши. Сохраняется для этой 3D-мыши.",
|
||||
"ui.3d_mouse_calibrate_title": "Калибровка 3D-мыши",
|
||||
"ui.3d_mouse_calibrate_wait": "Один раз подвигайте 3D-мышь, чтобы SolidCraft её увидел.",
|
||||
"ui.3d_mouse_calibrated": "Откалибрована",
|
||||
"ui.3d_mouse_calibration": "Калибровка 3D-мыши",
|
||||
"ui.3d_mouse_calibration_done": "Готово. Сохраните её, чтобы она применялась всякий раз, когда подключена эта 3D-мышь.",
|
||||
"ui.3d_mouse_calibration_no_response": "Одна ось почти не сдвинулась, поэтому её нельзя откалибровать. Начните заново и каждый раз двигайте колпачок дальше.",
|
||||
"ui.3d_mouse_circles_help": "Круги показывают, что устройство отправляет после калибровки. Тестовая модель показывает, что вид делает с этим после инверсии, скорости и назначения осей.",
|
||||
"ui.3d_mouse_dead_zone": "Мёртвая зона",
|
||||
"ui.3d_mouse_dead_zone_tip": "Насколько нужно сдвинуть колпачок, прежде чем сдвинется вид, в доле от полного хода. Увеличьте, если вид смещается, когда колпачок в покое.",
|
||||
"ui.3d_mouse_denied": "SolidCraft видит 3D-мышь, но не имеет права её читать. Правило udev описано в заметках по настройке 3D-мыши.",
|
||||
"ui.3d_mouse_denied_short": "Найдена, но недоступна для чтения",
|
||||
"ui.3d_mouse_driven_by": "Какое откалиброванное движение устройства управляет этой осью. Если выбрать движение, которое уже использует другая ось, они поменяются местами.",
|
||||
"ui.3d_mouse_driven_by_label": "управляется",
|
||||
"ui.3d_mouse_driver": "Драйвер 3D-мыши",
|
||||
"ui.3d_mouse_driver_auto": "Автоматически",
|
||||
"ui.3d_mouse_driver_direct": "Напрямую (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Пока чтение идёт через spacenavd: само устройство открыть не удалось.",
|
||||
"ui.3d_mouse_driver_only_direct": "Чтение напрямую с устройства; spacenavd не запущен",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Чтение через spacenavd, других вариантов нет",
|
||||
"ui.3d_mouse_forget": "Забыть калибровку",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Область просмотра слева",
|
||||
"ui.3d_mouse_ghost_left_window": "Окно слева",
|
||||
"ui.3d_mouse_ghost_position": "Положение",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Область просмотра справа",
|
||||
"ui.3d_mouse_hint": "Совместное перемещение и вращение вида; в эскизе — только панорама и масштаб.",
|
||||
"ui.3d_mouse_kind_cap": "Возврат в центр",
|
||||
"ui.3d_mouse_kind_question": "Какая у вас 3D-мышь?",
|
||||
"ui.3d_mouse_kind_trackball": "Трекбол",
|
||||
"ui.3d_mouse_looks_like_cap": "Значение держится, пока вы удерживаете, — как у устройства типа «Возврат в центр».",
|
||||
"ui.3d_mouse_looks_like_trackball": "Значение возвращается, как только вы останавливаетесь, — как у трекбола.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Вращение X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Вращение Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Вращение Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Смещение X",
|
||||
"ui.3d_mouse_motion_translation_y": "Смещение Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Смещение Z",
|
||||
"ui.3d_mouse_navigate": "Навигация с помощью 3D-мыши",
|
||||
"ui.3d_mouse_none": "3D-мышь не найдена",
|
||||
"ui.3d_mouse_not_calibrated": "Не откалибрована",
|
||||
"ui.3d_mouse_offer": "Калибровка 3D-мыши исправляет перепутанные или перевёрнутые оси и настраивает скорость и ощущения.",
|
||||
"ui.3d_mouse_offer_never": "Больше не показывать",
|
||||
"ui.3d_mouse_offer_swap": "Переключить на Hidraw (прямой драйвер)",
|
||||
"ui.3d_mouse_reading": "Показания",
|
||||
"ui.3d_mouse_reset_axes": "Сбросить назначение осей",
|
||||
"ui.3d_mouse_reset_view": "Сбросить вид",
|
||||
"ui.3d_mouse_restore": "Восстановить настройки 3D-мыши",
|
||||
"ui.3d_mouse_restore_ask": "Вернуть настройки 3D-мыши к значениям по умолчанию? Калибровка и назначение кнопок останутся.",
|
||||
"ui.3d_mouse_section": "3D-мышь",
|
||||
"ui.3d_mouse_show_in_view": "Показывать 3D-мышь в 3D-виде",
|
||||
"ui.3d_mouse_show_in_view_tip": "Изображение вашей 3D-мыши в левом нижнем углу, которое двигается вместе с ней.",
|
||||
"ui.3d_mouse_skip": "Моё устройство так не умеет",
|
||||
"ui.3d_mouse_skip_tip": "Пропускает это движение в обе стороны; калибровка его не учитывает.",
|
||||
"ui.3d_mouse_speed": "Скорость 3D-мыши",
|
||||
"ui.3d_mouse_step_lift": "Поднимите колпачок вверх, удержите и отпустите.",
|
||||
"ui.3d_mouse_step_press": "Нажмите на колпачок вниз, удержите и отпустите.",
|
||||
"ui.3d_mouse_step_pull": "Потяните колпачок на себя, удержите и отпустите.",
|
||||
"ui.3d_mouse_step_push": "Толкните колпачок от себя, удержите и отпустите.",
|
||||
"ui.3d_mouse_step_rest": "Уберите руку с колпачка и не трогайте его.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Уберите руку с шара и не трогайте его.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Поверните кольцо прокрутки на несколько щелчков по часовой стрелке.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Поверните кольцо прокрутки на несколько щелчков против часовой стрелки.",
|
||||
"ui.3d_mouse_step_roll_away": "Прокатите шар от себя несколько раз и дайте ему остановиться.",
|
||||
"ui.3d_mouse_step_roll_left": "Прокатите шар влево несколько раз и дайте ему остановиться.",
|
||||
"ui.3d_mouse_step_roll_right": "Прокатите шар вправо несколько раз и дайте ему остановиться.",
|
||||
"ui.3d_mouse_step_roll_toward": "Прокатите шар к себе несколько раз и дайте ему остановиться.",
|
||||
"ui.3d_mouse_step_slide_left": "Сдвиньте колпачок влево, удержите и отпустите.",
|
||||
"ui.3d_mouse_step_slide_right": "Сдвиньте колпачок вправо, удержите и отпустите.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Покрутите шар по часовой стрелке несколько раз и дайте ему остановиться.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Покрутите шар против часовой стрелки несколько раз и дайте ему остановиться.",
|
||||
"ui.3d_mouse_step_tilt_away": "Наклоните верх колпачка от себя (ближний край поднимается), удержите и отпустите.",
|
||||
"ui.3d_mouse_step_tilt_left": "Наклоните колпачок влево (левый край вниз), удержите и отпустите.",
|
||||
"ui.3d_mouse_step_tilt_right": "Наклоните колпачок вправо (правый край вниз), удержите и отпустите.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Наклоните верх колпачка к себе (ближний край опускается), удержите и отпустите.",
|
||||
"ui.3d_mouse_step_touch": "Положите руку на колпачок, не нажимая на него.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Поверните колпачок по часовой стрелке, удержите и отпустите.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Поверните колпачок против часовой стрелки, удержите и отпустите.",
|
||||
"ui.3d_mouse_switch_cap": "Калибровать как возврат в центр",
|
||||
"ui.3d_mouse_switch_trackball": "Калибровать как трекбол",
|
||||
"ui.3d_mouse_view_axis": "Ось вида",
|
||||
"ui.3d_mouse_view_off": "Выкл.",
|
||||
"ui.3d_mouse_view_off_tip": "Профиль драйвера 3D-мыши отключает эту ось, поэтому её движение не двигает вид. Если выбрать здесь движение другой оси, они поменяются местами, и это движение перестанет двигать вид.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Орбита влево / вправо",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Орбита вверх / вниз",
|
||||
"ui.3d_mouse_view_pan_sideways": "Сдвиг влево / вправо",
|
||||
"ui.3d_mouse_view_pan_up_down": "Сдвиг вверх / вниз",
|
||||
"ui.3d_mouse_view_roll": "Крен",
|
||||
"ui.3d_mouse_view_unmapped": "Ось {n}",
|
||||
"ui.3d_mouse_view_zoom": "Масштаб",
|
||||
"ui.3d_mouse_waiting": "Ожидание движения",
|
||||
"ui.3d_mouse_wiggle": "Если 3D-мышь подключена, слегка подвигайте её.",
|
||||
"ui.3d_sketch": "3D-эскиз",
|
||||
"ui.activate": "Активировать",
|
||||
"ui.active_component": "Активный компонент",
|
||||
@@ -1354,6 +1453,7 @@
|
||||
"ui.blocked": "Заблокировано",
|
||||
"ui.bodies": "Тела",
|
||||
"ui.bodies_all_if_none": "Тела (все, если не выбраны)",
|
||||
"ui.bottom": "Снизу",
|
||||
"ui.box": "Бокс",
|
||||
"ui.break": "Разорвать",
|
||||
"ui.browser_bodies_one": "{count} тело",
|
||||
@@ -1797,59 +1897,6 @@
|
||||
"ui.smooth_wheel_zoom": "Плавное масштабирование колесом",
|
||||
"ui.smooth_wheel_zoom_tip": "Отключите, чтобы каждое движение колеса применялось сразу, без сглаживания",
|
||||
"ui.snap": "Привязка",
|
||||
"ui.space_mouse_axes_reassigned": "Оси переназначены в настройках, и это действует и поверх этой калибровки.",
|
||||
"ui.space_mouse_calibrate": "Калибровать…",
|
||||
"ui.space_mouse_calibrate_tip": "Двигайте колпачок так, как просит окно, чтобы SolidCraft узнал, куда и насколько далеко движется каждая ось этой 3D-мыши. Сохраняется для этой 3D-мыши.",
|
||||
"ui.space_mouse_calibrate_title": "Калибровка 3D-мыши",
|
||||
"ui.space_mouse_calibrated": "Откалибрована",
|
||||
"ui.space_mouse_calibration": "Калибровка 3D-мыши",
|
||||
"ui.space_mouse_calibration_done": "Готово. Сохраните её, чтобы она применялась всякий раз, когда подключена эта 3D-мышь.",
|
||||
"ui.space_mouse_calibration_no_response": "Одна ось почти не сдвинулась, поэтому её нельзя откалибровать. Начните заново и каждый раз двигайте колпачок дальше.",
|
||||
"ui.space_mouse_dead_zone": "Мёртвая зона",
|
||||
"ui.space_mouse_dead_zone_tip": "Насколько нужно сдвинуть колпачок, прежде чем сдвинется вид, в доле от полного хода. Увеличьте, если вид смещается, когда колпачок в покое.",
|
||||
"ui.space_mouse_driven_by": "Какое откалиброванное движение устройства управляет этой осью. Если выбрать движение, которое уже использует другая ось, они поменяются местами.",
|
||||
"ui.space_mouse_driven_by_label": "управляется",
|
||||
"ui.space_mouse_driver": "Драйвер 3D-мыши",
|
||||
"ui.space_mouse_driver_auto": "Автоматически",
|
||||
"ui.space_mouse_driver_direct": "Напрямую (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "Чтение напрямую с устройства; spacenavd не запущен",
|
||||
"ui.space_mouse_driver_only_spacenavd": "Чтение через spacenavd, других вариантов нет",
|
||||
"ui.space_mouse_forget": "Забыть калибровку",
|
||||
"ui.space_mouse_hint": "Совместное перемещение и вращение вида; в эскизе — только панорама и масштаб.",
|
||||
"ui.space_mouse_motion_rotation_x": "Вращение X",
|
||||
"ui.space_mouse_motion_rotation_y": "Вращение Y",
|
||||
"ui.space_mouse_motion_rotation_z": "Вращение Z",
|
||||
"ui.space_mouse_motion_translation_x": "Смещение X",
|
||||
"ui.space_mouse_motion_translation_y": "Смещение Y",
|
||||
"ui.space_mouse_motion_translation_z": "Смещение Z",
|
||||
"ui.space_mouse_navigate": "Навигация через SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "3D-мышь не найдена",
|
||||
"ui.space_mouse_not_calibrated": "Не откалибрована",
|
||||
"ui.space_mouse_reading": "Показания",
|
||||
"ui.space_mouse_reset_axes": "Сбросить назначение осей",
|
||||
"ui.space_mouse_step_lift": "Поднимите колпачок вверх, удержите и отпустите.",
|
||||
"ui.space_mouse_step_press": "Нажмите на колпачок вниз, удержите и отпустите.",
|
||||
"ui.space_mouse_step_pull": "Потяните колпачок на себя, удержите и отпустите.",
|
||||
"ui.space_mouse_step_push": "Толкните колпачок от себя, удержите и отпустите.",
|
||||
"ui.space_mouse_step_rest": "Уберите руку с колпачка и не трогайте его.",
|
||||
"ui.space_mouse_step_slide_left": "Сдвиньте колпачок влево, удержите и отпустите.",
|
||||
"ui.space_mouse_step_slide_right": "Сдвиньте колпачок вправо, удержите и отпустите.",
|
||||
"ui.space_mouse_step_tilt_back": "Наклоните колпачок назад (передний край вверх), удержите и отпустите.",
|
||||
"ui.space_mouse_step_tilt_forward": "Наклоните колпачок вперёд (передний край вниз), удержите и отпустите.",
|
||||
"ui.space_mouse_step_tilt_left": "Наклоните колпачок влево (левый край вниз), удержите и отпустите.",
|
||||
"ui.space_mouse_step_tilt_right": "Наклоните колпачок вправо (правый край вниз), удержите и отпустите.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Поверните колпачок по часовой стрелке, удержите и отпустите.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Поверните колпачок против часовой стрелки, удержите и отпустите.",
|
||||
"ui.space_mouse_view_axis": "Ось вида",
|
||||
"ui.space_mouse_view_off": "Выкл.",
|
||||
"ui.space_mouse_view_off_tip": "Профиль драйвера 3D-мыши отключает эту ось, поэтому её движение не двигает вид. Если выбрать здесь движение другой оси, они поменяются местами, и это движение перестанет двигать вид.",
|
||||
"ui.space_mouse_view_orbit_sideways": "Орбита влево / вправо",
|
||||
"ui.space_mouse_view_orbit_up_down": "Орбита вверх / вниз",
|
||||
"ui.space_mouse_view_pan_sideways": "Сдвиг влево / вправо",
|
||||
"ui.space_mouse_view_pan_up_down": "Сдвиг вверх / вниз",
|
||||
"ui.space_mouse_view_roll": "Крен",
|
||||
"ui.space_mouse_view_unmapped": "Ось {n}",
|
||||
"ui.space_mouse_view_zoom": "Масштаб",
|
||||
"ui.spacing": "Интервал",
|
||||
"ui.spacing_2": "Интервал 2",
|
||||
"ui.sphere": "Сфера",
|
||||
|
||||
+43
-28
@@ -1124,7 +1124,50 @@
|
||||
"trace.stopped_by_contact": "Slutar vid prov {sample}: {a} skulle gå igenom {b}",
|
||||
"trace.stopped_by_loop": "Slutar vid prov {sample}: slingan genom {joint} kan inte stängas längre",
|
||||
"ui.1_degree_of_freedom": "1 frihetsgrad",
|
||||
"ui.3d_mouse_calibrate": "Kalibrera…",
|
||||
"ui.3d_mouse_calibrate_tip": "Flytta knappen som fönstret ber om, så att SolidCraft lär sig åt vilket håll och hur långt varje axel på denna 3D-mus går. Sparas för denna 3D-mus.",
|
||||
"ui.3d_mouse_calibrate_title": "Kalibrera 3D-mus",
|
||||
"ui.3d_mouse_calibrate_wait": "Rör 3D-musen en gång så att SolidCraft ser den.",
|
||||
"ui.3d_mouse_calibrated": "Kalibrerad",
|
||||
"ui.3d_mouse_calibration": "Kalibrering av 3D-mus",
|
||||
"ui.3d_mouse_calibration_done": "Klart. Spara den för att använda den när denna 3D-mus är ansluten.",
|
||||
"ui.3d_mouse_calibration_no_response": "En axel rörde sig knappt, så den kan inte kalibreras. Börja om och flytta knappen längre varje gång.",
|
||||
"ui.3d_mouse_circles_help": "Cirklarna visar vad enheten skickar efter kalibrering. Testmodellen visar vad vyn gör med det, efter inversion, hastighet och axeltilldelning.",
|
||||
"ui.3d_mouse_dead_zone": "Dödzon",
|
||||
"ui.3d_mouse_dead_zone_tip": "Hur långt knappen måste röra sig innan vyn gör det, som andel av dess fulla rörelse. Höj den om vyn driver medan knappen vilar.",
|
||||
"ui.3d_mouse_denied": "SolidCraft ser en 3D-mus men får inte läsa den. Se installationsanteckningarna för 3D-musen om udev-regeln.",
|
||||
"ui.3d_mouse_denied_short": "Hittad, men går inte att läsa",
|
||||
"ui.3d_mouse_driver_fallback": "Läser tills vidare via spacenavd: själva enheten kunde inte öppnas.",
|
||||
"ui.3d_mouse_forget": "Glöm kalibrering",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Vyn vänster",
|
||||
"ui.3d_mouse_ghost_left_window": "Fönster vänster",
|
||||
"ui.3d_mouse_ghost_position": "Placering",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Vyn höger",
|
||||
"ui.3d_mouse_hint": "Flytta och rotera vyn tillsammans; skisser använder endast panorering och zoom.",
|
||||
"ui.3d_mouse_navigate": "Navigera med 3D-musen",
|
||||
"ui.3d_mouse_none": "Ingen 3D-mus hittades",
|
||||
"ui.3d_mouse_not_calibrated": "Inte kalibrerad",
|
||||
"ui.3d_mouse_offer": "Att kalibrera din 3D-mus rättar förväxlade eller omvända axlar och ställer in hastighet och känsla.",
|
||||
"ui.3d_mouse_offer_never": "Visa inte igen",
|
||||
"ui.3d_mouse_offer_swap": "Byt till Hidraw (direkt drivrutin)",
|
||||
"ui.3d_mouse_reading": "Avläsning",
|
||||
"ui.3d_mouse_reset_view": "Återställ vyn",
|
||||
"ui.3d_mouse_restore": "Återställ 3D-musens standardvärden",
|
||||
"ui.3d_mouse_restore_ask": "Vill du återställa 3D-musens inställningar till standard? Din kalibrering och knapptilldelning behålls.",
|
||||
"ui.3d_mouse_speed": "3D-musens hastighet",
|
||||
"ui.3d_mouse_step_lift": "Lyft knappen uppåt, håll den och släpp sedan.",
|
||||
"ui.3d_mouse_step_press": "Tryck ned knappen, håll den och släpp sedan.",
|
||||
"ui.3d_mouse_step_pull": "Dra knappen mot dig, håll den och släpp sedan.",
|
||||
"ui.3d_mouse_step_push": "Skjut knappen bort från dig, håll den och släpp sedan.",
|
||||
"ui.3d_mouse_step_rest": "Ta bort handen från knappen och låt den vara stilla.",
|
||||
"ui.3d_mouse_step_slide_left": "Skjut knappen åt vänster, håll den och släpp sedan.",
|
||||
"ui.3d_mouse_step_slide_right": "Skjut knappen åt höger, håll den och släpp sedan.",
|
||||
"ui.3d_mouse_step_tilt_left": "Luta knappen åt vänster (vänsterkanten nedåt), håll den och släpp sedan.",
|
||||
"ui.3d_mouse_step_tilt_right": "Luta knappen åt höger (högerkanten nedåt), håll den och släpp sedan.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Vrid knappen medurs, håll den och släpp sedan.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Vrid knappen moturs, håll den och släpp sedan.",
|
||||
"ui.3d_mouse_waiting": "Väntar på rörelse",
|
||||
"ui.3d_mouse_wiggle": "Om du har en ansluten, rör lite på den.",
|
||||
"ui.3d_sketch": "3D-skiss",
|
||||
"ui.activate": "Aktivera",
|
||||
"ui.active_component": "Aktiv komponent",
|
||||
@@ -1797,34 +1840,6 @@
|
||||
"ui.smooth_wheel_zoom": "Jämn hjulzoom",
|
||||
"ui.smooth_wheel_zoom_tip": "Stäng av för att tillämpa varje hjulhändelse direkt, utan utjämningsfördröjning",
|
||||
"ui.snap": "Snappa",
|
||||
"ui.space_mouse_calibrate": "Kalibrera…",
|
||||
"ui.space_mouse_calibrate_tip": "Flytta knappen som fönstret ber om, så att SolidCraft lär sig åt vilket håll och hur långt varje axel på denna 3D-mus går. Sparas för denna 3D-mus.",
|
||||
"ui.space_mouse_calibrate_title": "Kalibrera 3D-mus",
|
||||
"ui.space_mouse_calibrated": "Kalibrerad",
|
||||
"ui.space_mouse_calibration": "Kalibrering av 3D-mus",
|
||||
"ui.space_mouse_calibration_done": "Klart. Spara den för att använda den när denna 3D-mus är ansluten.",
|
||||
"ui.space_mouse_calibration_no_response": "En axel rörde sig knappt, så den kan inte kalibreras. Börja om och flytta knappen längre varje gång.",
|
||||
"ui.space_mouse_dead_zone": "Dödzon",
|
||||
"ui.space_mouse_dead_zone_tip": "Hur långt knappen måste röra sig innan vyn gör det, som andel av dess fulla rörelse. Höj den om vyn driver medan knappen vilar.",
|
||||
"ui.space_mouse_forget": "Glöm kalibrering",
|
||||
"ui.space_mouse_hint": "Flytta och rotera vyn tillsammans; skisser använder endast panorering och zoom.",
|
||||
"ui.space_mouse_navigate": "Navigera med SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Ingen 3D-mus hittades",
|
||||
"ui.space_mouse_not_calibrated": "Inte kalibrerad",
|
||||
"ui.space_mouse_reading": "Avläsning",
|
||||
"ui.space_mouse_step_lift": "Lyft knappen uppåt, håll den och släpp sedan.",
|
||||
"ui.space_mouse_step_press": "Tryck ned knappen, håll den och släpp sedan.",
|
||||
"ui.space_mouse_step_pull": "Dra knappen mot dig, håll den och släpp sedan.",
|
||||
"ui.space_mouse_step_push": "Skjut knappen bort från dig, håll den och släpp sedan.",
|
||||
"ui.space_mouse_step_rest": "Ta bort handen från knappen och låt den vara stilla.",
|
||||
"ui.space_mouse_step_slide_left": "Skjut knappen åt vänster, håll den och släpp sedan.",
|
||||
"ui.space_mouse_step_slide_right": "Skjut knappen åt höger, håll den och släpp sedan.",
|
||||
"ui.space_mouse_step_tilt_back": "Luta knappen bakåt (framkanten uppåt), håll den och släpp sedan.",
|
||||
"ui.space_mouse_step_tilt_forward": "Luta knappen framåt (framkanten nedåt), håll den och släpp sedan.",
|
||||
"ui.space_mouse_step_tilt_left": "Luta knappen åt vänster (vänsterkanten nedåt), håll den och släpp sedan.",
|
||||
"ui.space_mouse_step_tilt_right": "Luta knappen åt höger (högerkanten nedåt), håll den och släpp sedan.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Vrid knappen medurs, håll den och släpp sedan.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Vrid knappen moturs, håll den och släpp sedan.",
|
||||
"ui.spacing": "Avstånd",
|
||||
"ui.spacing_2": "Avstånd 2",
|
||||
"ui.sphere": "Sfär",
|
||||
|
||||
+100
-53
@@ -1124,7 +1124,106 @@
|
||||
"trace.stopped_by_contact": "Örnek {sample} noktasında biter: {a}, {b} içinden geçecek",
|
||||
"trace.stopped_by_loop": "Örnek {sample} noktasında biter: {joint} üzerinden geçen döngü daha fazla kapanamaz",
|
||||
"ui.1_degree_of_freedom": "1 serbestlik derecesi",
|
||||
"ui.3d_mouse_axes_reassigned": "Eksenler Tercihler'de yeniden atanmış; bu, bu kalibrasyonun üstüne de uygulanır.",
|
||||
"ui.3d_mouse_button": "Düğme {number}",
|
||||
"ui.3d_mouse_button_nothing": "Hiçbir şey",
|
||||
"ui.3d_mouse_button_settings": "3B fare ayarları",
|
||||
"ui.3d_mouse_buttons": "Düğmeler",
|
||||
"ui.3d_mouse_buttons_hint": "Burada bulmak için 3B farenizdeki bir düğmeye basın, sonra ne yapacağını seçin.",
|
||||
"ui.3d_mouse_buttons_no_device": "Düğmelerinin ne yapacağını seçmek için bir 3B fare bağlayın.",
|
||||
"ui.3d_mouse_buttons_reset": "Varsayılan düğmelere dön",
|
||||
"ui.3d_mouse_calibrate": "Kalibre et…",
|
||||
"ui.3d_mouse_calibrate_tip": "Pencerenin istediği gibi başlığı hareket ettirin; SolidCraft bu 3B farenin her ekseninin hangi yöne ve ne kadar gittiğini öğrensin. Bu 3B fare için kaydedilir.",
|
||||
"ui.3d_mouse_calibrate_title": "3B fareyi kalibre et",
|
||||
"ui.3d_mouse_calibrate_wait": "SolidCraft'ın görebilmesi için 3D fareyi bir kez hareket ettirin.",
|
||||
"ui.3d_mouse_calibrated": "Kalibre edildi",
|
||||
"ui.3d_mouse_calibration": "3B fare kalibrasyonu",
|
||||
"ui.3d_mouse_calibration_done": "Bitti. Bu 3B fare her bağlandığında kullanılması için kaydedin.",
|
||||
"ui.3d_mouse_calibration_no_response": "Bir eksen neredeyse hiç hareket etmedi, bu yüzden kalibre edilemiyor. Baştan başlayın ve başlığı her seferinde daha fazla hareket ettirin.",
|
||||
"ui.3d_mouse_circles_help": "Daireler, aygıtın kalibrasyondan sonra gönderdiğini gösterir. Test modeli, görünümün bunu ters çevirme, hız ve eksen atamasından sonra ne yaptığını gösterir.",
|
||||
"ui.3d_mouse_dead_zone": "Ölü bölge",
|
||||
"ui.3d_mouse_dead_zone_tip": "Görünüm hareket etmeden önce başlığın ne kadar hareket etmesi gerektiği, tam hareket aralığının oranı olarak. Başlık dururken görünüm kayıyorsa artırın.",
|
||||
"ui.3d_mouse_denied": "SolidCraft bir 3D fare görüyor ama onu okumasına izin verilmiyor. udev kuralı için 3D fare kurulum notlarına bakın.",
|
||||
"ui.3d_mouse_denied_short": "Bulundu, ancak okunamıyor",
|
||||
"ui.3d_mouse_driven_by": "Bu ekseni cihazın hangi kalibre edilmiş hareketinin yönettiği. Başka bir eksenin zaten kullandığını seçerseniz ikisi yer değiştirir.",
|
||||
"ui.3d_mouse_driven_by_label": "kumanda eden",
|
||||
"ui.3d_mouse_driver": "3B fare sürücüsü",
|
||||
"ui.3d_mouse_driver_auto": "Otomatik",
|
||||
"ui.3d_mouse_driver_direct": "Doğrudan (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Şimdilik spacenavd üzerinden okunuyor: aygıtın kendisi açılamadı.",
|
||||
"ui.3d_mouse_driver_only_direct": "Aygıt doğrudan okunuyor; spacenavd çalışmıyor",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "spacenavd üzerinden okunuyor, tek seçenek bu",
|
||||
"ui.3d_mouse_forget": "Kalibrasyonu unut",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Sol görünüm",
|
||||
"ui.3d_mouse_ghost_left_window": "Sol pencere",
|
||||
"ui.3d_mouse_ghost_position": "Konum",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Sağ görünüm",
|
||||
"ui.3d_mouse_hint": "Görünümü aynı anda kaydırın ve döndürün; eskizlerde yalnızca kaydırma ve yakınlaştırma kullanılır.",
|
||||
"ui.3d_mouse_kind_cap": "Merkeze dönüş",
|
||||
"ui.3d_mouse_kind_question": "Ne tür bir 3B fare?",
|
||||
"ui.3d_mouse_kind_trackball": "İztopu",
|
||||
"ui.3d_mouse_looks_like_cap": "Tuttuğunuz sürece değer yerinde kalıyor, “Merkeze dönüş” türü bir aygıttaki gibi.",
|
||||
"ui.3d_mouse_looks_like_trackball": "Hareketi bırakır bırakmaz değer geri dönüyor, iztopunda olduğu gibi.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Dönme X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Dönme Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Dönme Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Öteleme X",
|
||||
"ui.3d_mouse_motion_translation_y": "Öteleme Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Öteleme Z",
|
||||
"ui.3d_mouse_navigate": "3D fare ile gezin",
|
||||
"ui.3d_mouse_none": "3B fare bulunamadı",
|
||||
"ui.3d_mouse_not_calibrated": "Kalibre edilmedi",
|
||||
"ui.3d_mouse_offer": "3D farenizi kalibre etmek, yer değiştirmiş veya ters dönmüş eksenleri düzeltir; hızını ve hissini ayarlar.",
|
||||
"ui.3d_mouse_offer_never": "Bir daha gösterme",
|
||||
"ui.3d_mouse_offer_swap": "Hidraw'a geç (doğrudan sürücü)",
|
||||
"ui.3d_mouse_reading": "Okunan değer",
|
||||
"ui.3d_mouse_reset_axes": "Eksen atamasını sıfırla",
|
||||
"ui.3d_mouse_reset_view": "Görünümü sıfırla",
|
||||
"ui.3d_mouse_restore": "3D fare varsayılanlarını geri yükle",
|
||||
"ui.3d_mouse_restore_ask": "3D fare ayarları varsayılanlara döndürülsün mü? Kalibrasyon ve düğme eşlemesi korunur.",
|
||||
"ui.3d_mouse_section": "3D fare",
|
||||
"ui.3d_mouse_show_in_view": "3B fareyi 3B görünümde göster",
|
||||
"ui.3d_mouse_show_in_view_tip": "Sol alt köşede, siz hareket ettirdikçe hareket eden bir 3B fare resmi.",
|
||||
"ui.3d_mouse_skip": "Cihazım bunu yapamıyor",
|
||||
"ui.3d_mouse_skip_tip": "Bu hareketi iki yönde de atlar; kalibrasyon onu dışarıda bırakır.",
|
||||
"ui.3d_mouse_speed": "3D fare hızı",
|
||||
"ui.3d_mouse_step_lift": "Başlığı yukarı kaldırın, tutun, sonra bırakın.",
|
||||
"ui.3d_mouse_step_press": "Başlığı aşağı bastırın, tutun, sonra bırakın.",
|
||||
"ui.3d_mouse_step_pull": "Başlığı kendinize doğru çekin, tutun, sonra bırakın.",
|
||||
"ui.3d_mouse_step_push": "Başlığı sizden uzağa itin, tutun, sonra bırakın.",
|
||||
"ui.3d_mouse_step_rest": "Elinizi başlıktan çekin ve hareketsiz bırakın.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Elinizi toptan çekin ve hareketsiz bırakın.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Kaydırma halkasını saat yönünde birkaç tık çevirin.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Kaydırma halkasını saat yönünün tersine birkaç tık çevirin.",
|
||||
"ui.3d_mouse_step_roll_away": "Topu birkaç kez sizden uzağa yuvarlayın, sonra durmasını bekleyin.",
|
||||
"ui.3d_mouse_step_roll_left": "Topu birkaç kez sola yuvarlayın, sonra durmasını bekleyin.",
|
||||
"ui.3d_mouse_step_roll_right": "Topu birkaç kez sağa yuvarlayın, sonra durmasını bekleyin.",
|
||||
"ui.3d_mouse_step_roll_toward": "Topu birkaç kez kendinize doğru yuvarlayın, sonra durmasını bekleyin.",
|
||||
"ui.3d_mouse_step_slide_left": "Başlığı sola kaydırın, tutun, sonra bırakın.",
|
||||
"ui.3d_mouse_step_slide_right": "Başlığı sağa kaydırın, tutun, sonra bırakın.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Topu birkaç kez saat yönünde döndürün, sonra durmasını bekleyin.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Topu birkaç kez saat yönünün tersine döndürün, sonra durmasını bekleyin.",
|
||||
"ui.3d_mouse_step_tilt_away": "Başlığın üstünü sizden uzağa eğin (size en yakın kenar yukarı kalkar), tutun, sonra bırakın.",
|
||||
"ui.3d_mouse_step_tilt_left": "Başlığı sola eğin (sol kenar aşağı), tutun, sonra bırakın.",
|
||||
"ui.3d_mouse_step_tilt_right": "Başlığı sağa eğin (sağ kenar aşağı), tutun, sonra bırakın.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Başlığın üstünü kendinize doğru eğin (size en yakın kenar aşağı iner), tutun, sonra bırakın.",
|
||||
"ui.3d_mouse_step_touch": "Elinizi başlığa itmeden koyun.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Başlığı saat yönünde çevirin, tutun, sonra bırakın.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Başlığı saat yönünün tersine çevirin, tutun, sonra bırakın.",
|
||||
"ui.3d_mouse_switch_cap": "Merkeze dönüş olarak kalibre et",
|
||||
"ui.3d_mouse_switch_trackball": "İztopu olarak kalibre et",
|
||||
"ui.3d_mouse_view_axis": "Görünüm ekseni",
|
||||
"ui.3d_mouse_view_off": "Kapalı",
|
||||
"ui.3d_mouse_view_off_tip": "3D fare sürücüsünün profili bu ekseni kapatıyor, bu yüzden hareketi görünümü oynatmaz. Burada başka bir eksenin hareketini seçerseniz ikisi yer değiştirir ve o hareket görünümü oynatmayı bırakır.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Sola / sağa yörünge",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Yukarı / aşağı yörünge",
|
||||
"ui.3d_mouse_view_pan_sideways": "Sola / sağa kaydır",
|
||||
"ui.3d_mouse_view_pan_up_down": "Yukarı / aşağı kaydır",
|
||||
"ui.3d_mouse_view_roll": "Yuvarlanma",
|
||||
"ui.3d_mouse_view_unmapped": "Eksen {n}",
|
||||
"ui.3d_mouse_view_zoom": "Yakınlaştır",
|
||||
"ui.3d_mouse_waiting": "Hareket bekleniyor",
|
||||
"ui.3d_mouse_wiggle": "Takılı bir tane varsa biraz oynatın.",
|
||||
"ui.3d_sketch": "3D eskiz",
|
||||
"ui.activate": "Etkinleştir",
|
||||
"ui.active_component": "Etkin bileşen",
|
||||
@@ -1354,6 +1453,7 @@
|
||||
"ui.blocked": "Engellenmiş",
|
||||
"ui.bodies": "Gövdeler",
|
||||
"ui.bodies_all_if_none": "Gövdeler (yoksa tümü)",
|
||||
"ui.bottom": "Alt",
|
||||
"ui.box": "Kutu",
|
||||
"ui.break": "Kır",
|
||||
"ui.browser_bodies_one": "{count} gövde",
|
||||
@@ -1797,59 +1897,6 @@
|
||||
"ui.smooth_wheel_zoom": "Yumuşak tekerlek yakınlaştırması",
|
||||
"ui.smooth_wheel_zoom_tip": "Her tekerlek hareketini yumuşatma olmadan hemen uygulamak için kapatın",
|
||||
"ui.snap": "Yakala",
|
||||
"ui.space_mouse_axes_reassigned": "Eksenler Tercihler'de yeniden atanmış; bu, bu kalibrasyonun üstüne de uygulanır.",
|
||||
"ui.space_mouse_calibrate": "Kalibre et…",
|
||||
"ui.space_mouse_calibrate_tip": "Pencerenin istediği gibi başlığı hareket ettirin; SolidCraft bu 3B farenin her ekseninin hangi yöne ve ne kadar gittiğini öğrensin. Bu 3B fare için kaydedilir.",
|
||||
"ui.space_mouse_calibrate_title": "3B fareyi kalibre et",
|
||||
"ui.space_mouse_calibrated": "Kalibre edildi",
|
||||
"ui.space_mouse_calibration": "3B fare kalibrasyonu",
|
||||
"ui.space_mouse_calibration_done": "Bitti. Bu 3B fare her bağlandığında kullanılması için kaydedin.",
|
||||
"ui.space_mouse_calibration_no_response": "Bir eksen neredeyse hiç hareket etmedi, bu yüzden kalibre edilemiyor. Baştan başlayın ve başlığı her seferinde daha fazla hareket ettirin.",
|
||||
"ui.space_mouse_dead_zone": "Ölü bölge",
|
||||
"ui.space_mouse_dead_zone_tip": "Görünüm hareket etmeden önce başlığın ne kadar hareket etmesi gerektiği, tam hareket aralığının oranı olarak. Başlık dururken görünüm kayıyorsa artırın.",
|
||||
"ui.space_mouse_driven_by": "Bu ekseni cihazın hangi kalibre edilmiş hareketinin yönettiği. Başka bir eksenin zaten kullandığını seçerseniz ikisi yer değiştirir.",
|
||||
"ui.space_mouse_driven_by_label": "kumanda eden",
|
||||
"ui.space_mouse_driver": "3B fare sürücüsü",
|
||||
"ui.space_mouse_driver_auto": "Otomatik",
|
||||
"ui.space_mouse_driver_direct": "Doğrudan (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "Aygıt doğrudan okunuyor; spacenavd çalışmıyor",
|
||||
"ui.space_mouse_driver_only_spacenavd": "spacenavd üzerinden okunuyor, tek seçenek bu",
|
||||
"ui.space_mouse_forget": "Kalibrasyonu unut",
|
||||
"ui.space_mouse_hint": "Görünümü aynı anda kaydırın ve döndürün; eskizlerde yalnızca kaydırma ve yakınlaştırma kullanılır.",
|
||||
"ui.space_mouse_motion_rotation_x": "Dönme X",
|
||||
"ui.space_mouse_motion_rotation_y": "Dönme Y",
|
||||
"ui.space_mouse_motion_rotation_z": "Dönme Z",
|
||||
"ui.space_mouse_motion_translation_x": "Öteleme X",
|
||||
"ui.space_mouse_motion_translation_y": "Öteleme Y",
|
||||
"ui.space_mouse_motion_translation_z": "Öteleme Z",
|
||||
"ui.space_mouse_navigate": "SpaceNavigator / SpaceMouse ile gezin",
|
||||
"ui.space_mouse_none": "3B fare bulunamadı",
|
||||
"ui.space_mouse_not_calibrated": "Kalibre edilmedi",
|
||||
"ui.space_mouse_reading": "Okunan değer",
|
||||
"ui.space_mouse_reset_axes": "Eksen atamasını sıfırla",
|
||||
"ui.space_mouse_step_lift": "Başlığı yukarı kaldırın, tutun, sonra bırakın.",
|
||||
"ui.space_mouse_step_press": "Başlığı aşağı bastırın, tutun, sonra bırakın.",
|
||||
"ui.space_mouse_step_pull": "Başlığı kendinize doğru çekin, tutun, sonra bırakın.",
|
||||
"ui.space_mouse_step_push": "Başlığı sizden uzağa itin, tutun, sonra bırakın.",
|
||||
"ui.space_mouse_step_rest": "Elinizi başlıktan çekin ve hareketsiz bırakın.",
|
||||
"ui.space_mouse_step_slide_left": "Başlığı sola kaydırın, tutun, sonra bırakın.",
|
||||
"ui.space_mouse_step_slide_right": "Başlığı sağa kaydırın, tutun, sonra bırakın.",
|
||||
"ui.space_mouse_step_tilt_back": "Başlığı geriye eğin (ön kenar yukarı), tutun, sonra bırakın.",
|
||||
"ui.space_mouse_step_tilt_forward": "Başlığı öne eğin (ön kenar aşağı), tutun, sonra bırakın.",
|
||||
"ui.space_mouse_step_tilt_left": "Başlığı sola eğin (sol kenar aşağı), tutun, sonra bırakın.",
|
||||
"ui.space_mouse_step_tilt_right": "Başlığı sağa eğin (sağ kenar aşağı), tutun, sonra bırakın.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Başlığı saat yönünde çevirin, tutun, sonra bırakın.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Başlığı saat yönünün tersine çevirin, tutun, sonra bırakın.",
|
||||
"ui.space_mouse_view_axis": "Görünüm ekseni",
|
||||
"ui.space_mouse_view_off": "Kapalı",
|
||||
"ui.space_mouse_view_off_tip": "3D fare sürücüsünün profili bu ekseni kapatıyor, bu yüzden hareketi görünümü oynatmaz. Burada başka bir eksenin hareketini seçerseniz ikisi yer değiştirir ve o hareket görünümü oynatmayı bırakır.",
|
||||
"ui.space_mouse_view_orbit_sideways": "Sola / sağa yörünge",
|
||||
"ui.space_mouse_view_orbit_up_down": "Yukarı / aşağı yörünge",
|
||||
"ui.space_mouse_view_pan_sideways": "Sola / sağa kaydır",
|
||||
"ui.space_mouse_view_pan_up_down": "Yukarı / aşağı kaydır",
|
||||
"ui.space_mouse_view_roll": "Yuvarlanma",
|
||||
"ui.space_mouse_view_unmapped": "Eksen {n}",
|
||||
"ui.space_mouse_view_zoom": "Yakınlaştır",
|
||||
"ui.spacing": "Aralık",
|
||||
"ui.spacing_2": "Aralık 2",
|
||||
"ui.sphere": "Küre",
|
||||
|
||||
+100
-53
@@ -1125,7 +1125,106 @@
|
||||
"trace.stopped_by_contact": "Закінчується на відліку {sample}: {a} пройшов би крізь {b}",
|
||||
"trace.stopped_by_loop": "Закінчується на відліку {sample}: контур через {joint} далі не замикається",
|
||||
"ui.1_degree_of_freedom": "1 ступінь вільності",
|
||||
"ui.3d_mouse_axes_reassigned": "Осі перепризначено в налаштуваннях, і це діє й поверх цього калібрування.",
|
||||
"ui.3d_mouse_button": "Кнопка {number}",
|
||||
"ui.3d_mouse_button_nothing": "Нічого",
|
||||
"ui.3d_mouse_button_settings": "Налаштування 3D-миші",
|
||||
"ui.3d_mouse_buttons": "Кнопки",
|
||||
"ui.3d_mouse_buttons_hint": "Натисніть кнопку на 3D-миші, щоб знайти її тут, і виберіть, що вона робить.",
|
||||
"ui.3d_mouse_buttons_no_device": "Під'єднайте 3D-мишу, щоб призначити її кнопки.",
|
||||
"ui.3d_mouse_buttons_reset": "Повернути типові кнопки",
|
||||
"ui.3d_mouse_calibrate": "Калібрувати…",
|
||||
"ui.3d_mouse_calibrate_tip": "Рухайте ковпачок так, як просить вікно, щоб SolidCraft дізнався, куди й наскільки далеко рухається кожна вісь цієї 3D-миші. Зберігається для цієї 3D-миші.",
|
||||
"ui.3d_mouse_calibrate_title": "Калібрування 3D-миші",
|
||||
"ui.3d_mouse_calibrate_wait": "Один раз порухайте 3D-мишу, щоб SolidCraft її побачив.",
|
||||
"ui.3d_mouse_calibrated": "Відкалібровано",
|
||||
"ui.3d_mouse_calibration": "Калібрування 3D-миші",
|
||||
"ui.3d_mouse_calibration_done": "Готово. Збережіть його, щоб воно застосовувалося щоразу, коли підключено цю 3D-мишу.",
|
||||
"ui.3d_mouse_calibration_no_response": "Одна вісь майже не зрушила, тому її не можна відкалібрувати. Почніть спочатку й щоразу рухайте ковпачок далі.",
|
||||
"ui.3d_mouse_circles_help": "Кола показують, що пристрій надсилає після калібрування. Тестова модель показує, що вигляд робить із цим після інверсії, швидкості та призначення осей.",
|
||||
"ui.3d_mouse_dead_zone": "Мертва зона",
|
||||
"ui.3d_mouse_dead_zone_tip": "Наскільки треба зсунути ковпачок, перш ніж зрушить вигляд, у частці від повного ходу. Збільште, якщо вигляд зміщується, коли ковпачок у спокої.",
|
||||
"ui.3d_mouse_denied": "SolidCraft бачить 3D-мишу, але не має права її читати. Правило udev описано в нотатках із налаштування 3D-миші.",
|
||||
"ui.3d_mouse_denied_short": "Знайдено, але не читається",
|
||||
"ui.3d_mouse_driven_by": "Який відкалібрований рух пристрою керує цією віссю. Якщо вибрати рух, який уже використовує інша вісь, вони поміняються місцями.",
|
||||
"ui.3d_mouse_driven_by_label": "керується",
|
||||
"ui.3d_mouse_driver": "Драйвер 3D-миші",
|
||||
"ui.3d_mouse_driver_auto": "Автоматично",
|
||||
"ui.3d_mouse_driver_direct": "Напряму (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Наразі читання йде через spacenavd: сам пристрій відкрити не вдалося.",
|
||||
"ui.3d_mouse_driver_only_direct": "Читання напряму з пристрою; spacenavd не запущено",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Читання через spacenavd, інших варіантів немає",
|
||||
"ui.3d_mouse_forget": "Забути калібрування",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Ліворуч у вікні перегляду",
|
||||
"ui.3d_mouse_ghost_left_window": "Ліворуч у вікні",
|
||||
"ui.3d_mouse_ghost_position": "Розташування",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Праворуч у вікні перегляду",
|
||||
"ui.3d_mouse_hint": "Переміщуйте й обертайте вид разом; в ескізах доступні лише панорамування та масштаб.",
|
||||
"ui.3d_mouse_kind_cap": "Повернення до центру",
|
||||
"ui.3d_mouse_kind_question": "Яка у вас 3D-миша?",
|
||||
"ui.3d_mouse_kind_trackball": "Трекбол",
|
||||
"ui.3d_mouse_looks_like_cap": "Значення тримається, поки ви утримуєте, — як у пристрою типу «Повернення до центру».",
|
||||
"ui.3d_mouse_looks_like_trackball": "Значення повертається, щойно ви зупиняєтеся, — як у трекбола.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Обертання X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Обертання Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Обертання Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Зміщення X",
|
||||
"ui.3d_mouse_motion_translation_y": "Зміщення Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Зміщення Z",
|
||||
"ui.3d_mouse_navigate": "Навігація за допомогою 3D-миші",
|
||||
"ui.3d_mouse_none": "3D-мишу не знайдено",
|
||||
"ui.3d_mouse_not_calibrated": "Не відкалібровано",
|
||||
"ui.3d_mouse_offer": "Калібрування 3D-миші виправляє переплутані або обернені осі та налаштовує швидкість і відчуття.",
|
||||
"ui.3d_mouse_offer_never": "Більше не показувати",
|
||||
"ui.3d_mouse_offer_swap": "Перемкнути на Hidraw (прямий драйвер)",
|
||||
"ui.3d_mouse_reading": "Показники",
|
||||
"ui.3d_mouse_reset_axes": "Скинути призначення осей",
|
||||
"ui.3d_mouse_reset_view": "Скинути вигляд",
|
||||
"ui.3d_mouse_restore": "Відновити налаштування 3D-миші",
|
||||
"ui.3d_mouse_restore_ask": "Повернути налаштування 3D-миші до типових? Калібрування і призначення кнопок залишаться.",
|
||||
"ui.3d_mouse_section": "3D-миша",
|
||||
"ui.3d_mouse_show_in_view": "Показувати 3D-мишу в 3D-вигляді",
|
||||
"ui.3d_mouse_show_in_view_tip": "Зображення вашої 3D-миші в лівому нижньому куті, що рухається разом із нею.",
|
||||
"ui.3d_mouse_skip": "Мій пристрій так не вміє",
|
||||
"ui.3d_mouse_skip_tip": "Пропускає цей рух в обидва боки; калібрування його не враховує.",
|
||||
"ui.3d_mouse_speed": "Швидкість 3D-миші",
|
||||
"ui.3d_mouse_step_lift": "Підніміть ковпачок угору, утримайте й відпустіть.",
|
||||
"ui.3d_mouse_step_press": "Натисніть ковпачок униз, утримайте й відпустіть.",
|
||||
"ui.3d_mouse_step_pull": "Потягніть ковпачок до себе, утримайте й відпустіть.",
|
||||
"ui.3d_mouse_step_push": "Штовхніть ковпачок від себе, утримайте й відпустіть.",
|
||||
"ui.3d_mouse_step_rest": "Приберіть руку з ковпачка й не торкайтеся його.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Приберіть руку з кулі й не торкайтеся її.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Поверніть кільце прокручування на кілька клацань за годинниковою стрілкою.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Поверніть кільце прокручування на кілька клацань проти годинникової стрілки.",
|
||||
"ui.3d_mouse_step_roll_away": "Прокотіть кулю від себе кілька разів і дайте їй зупинитися.",
|
||||
"ui.3d_mouse_step_roll_left": "Прокотіть кулю ліворуч кілька разів і дайте їй зупинитися.",
|
||||
"ui.3d_mouse_step_roll_right": "Прокотіть кулю праворуч кілька разів і дайте їй зупинитися.",
|
||||
"ui.3d_mouse_step_roll_toward": "Прокотіть кулю до себе кілька разів і дайте їй зупинитися.",
|
||||
"ui.3d_mouse_step_slide_left": "Зсуньте ковпачок ліворуч, утримайте й відпустіть.",
|
||||
"ui.3d_mouse_step_slide_right": "Зсуньте ковпачок праворуч, утримайте й відпустіть.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Покрутіть кулю за годинниковою стрілкою кілька разів і дайте їй зупинитися.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Покрутіть кулю проти годинникової стрілки кілька разів і дайте їй зупинитися.",
|
||||
"ui.3d_mouse_step_tilt_away": "Нахиліть верх ковпачка від себе (ближній край піднімається), утримайте й відпустіть.",
|
||||
"ui.3d_mouse_step_tilt_left": "Нахиліть ковпачок ліворуч (лівий край униз), утримайте й відпустіть.",
|
||||
"ui.3d_mouse_step_tilt_right": "Нахиліть ковпачок праворуч (правий край униз), утримайте й відпустіть.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Нахиліть верх ковпачка до себе (ближній край опускається), утримайте й відпустіть.",
|
||||
"ui.3d_mouse_step_touch": "Покладіть руку на ковпачок, не натискаючи на нього.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Поверніть ковпачок за годинниковою стрілкою, утримайте й відпустіть.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Поверніть ковпачок проти годинникової стрілки, утримайте й відпустіть.",
|
||||
"ui.3d_mouse_switch_cap": "Калібрувати як повернення до центру",
|
||||
"ui.3d_mouse_switch_trackball": "Калібрувати як трекбол",
|
||||
"ui.3d_mouse_view_axis": "Вісь вигляду",
|
||||
"ui.3d_mouse_view_off": "Вимк.",
|
||||
"ui.3d_mouse_view_off_tip": "Профіль драйвера 3D-миші вимикає цю вісь, тож її рух не рухає вигляд. Якщо вибрати тут рух іншої осі, вони поміняються місцями, і цей рух перестане рухати вигляд.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Орбіта ліворуч / праворуч",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Орбіта вгору / вниз",
|
||||
"ui.3d_mouse_view_pan_sideways": "Зсув ліворуч / праворуч",
|
||||
"ui.3d_mouse_view_pan_up_down": "Зсув угору / вниз",
|
||||
"ui.3d_mouse_view_roll": "Крен",
|
||||
"ui.3d_mouse_view_unmapped": "Вісь {n}",
|
||||
"ui.3d_mouse_view_zoom": "Масштаб",
|
||||
"ui.3d_mouse_waiting": "Очікування руху",
|
||||
"ui.3d_mouse_wiggle": "Якщо 3D-миша підключена, трохи порухайте нею.",
|
||||
"ui.3d_sketch": "3D-ескіз",
|
||||
"ui.activate": "Активувати",
|
||||
"ui.active_component": "Активний компонент",
|
||||
@@ -1355,6 +1454,7 @@
|
||||
"ui.blocked": "Заблоковані",
|
||||
"ui.bodies": "Тіла",
|
||||
"ui.bodies_all_if_none": "Тіла (усі, якщо не вибрано)",
|
||||
"ui.bottom": "Знизу",
|
||||
"ui.box": "Куб",
|
||||
"ui.break": "Розірвати",
|
||||
"ui.browser_bodies_one": "{count} тіло",
|
||||
@@ -1798,59 +1898,6 @@
|
||||
"ui.smooth_wheel_zoom": "Плавне масштабування колесом",
|
||||
"ui.smooth_wheel_zoom_tip": "Вимкніть, щоб застосовувати кожну подію колеса негайно, без плавного завершення",
|
||||
"ui.snap": "Прив'язка",
|
||||
"ui.space_mouse_axes_reassigned": "Осі перепризначено в налаштуваннях, і це діє й поверх цього калібрування.",
|
||||
"ui.space_mouse_calibrate": "Калібрувати…",
|
||||
"ui.space_mouse_calibrate_tip": "Рухайте ковпачок так, як просить вікно, щоб SolidCraft дізнався, куди й наскільки далеко рухається кожна вісь цієї 3D-миші. Зберігається для цієї 3D-миші.",
|
||||
"ui.space_mouse_calibrate_title": "Калібрування 3D-миші",
|
||||
"ui.space_mouse_calibrated": "Відкалібровано",
|
||||
"ui.space_mouse_calibration": "Калібрування 3D-миші",
|
||||
"ui.space_mouse_calibration_done": "Готово. Збережіть його, щоб воно застосовувалося щоразу, коли підключено цю 3D-мишу.",
|
||||
"ui.space_mouse_calibration_no_response": "Одна вісь майже не зрушила, тому її не можна відкалібрувати. Почніть спочатку й щоразу рухайте ковпачок далі.",
|
||||
"ui.space_mouse_dead_zone": "Мертва зона",
|
||||
"ui.space_mouse_dead_zone_tip": "Наскільки треба зсунути ковпачок, перш ніж зрушить вигляд, у частці від повного ходу. Збільште, якщо вигляд зміщується, коли ковпачок у спокої.",
|
||||
"ui.space_mouse_driven_by": "Який відкалібрований рух пристрою керує цією віссю. Якщо вибрати рух, який уже використовує інша вісь, вони поміняються місцями.",
|
||||
"ui.space_mouse_driven_by_label": "керується",
|
||||
"ui.space_mouse_driver": "Драйвер 3D-миші",
|
||||
"ui.space_mouse_driver_auto": "Автоматично",
|
||||
"ui.space_mouse_driver_direct": "Напряму (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "Читання напряму з пристрою; spacenavd не запущено",
|
||||
"ui.space_mouse_driver_only_spacenavd": "Читання через spacenavd, інших варіантів немає",
|
||||
"ui.space_mouse_forget": "Забути калібрування",
|
||||
"ui.space_mouse_hint": "Переміщуйте й обертайте вид разом; в ескізах доступні лише панорамування та масштаб.",
|
||||
"ui.space_mouse_motion_rotation_x": "Обертання X",
|
||||
"ui.space_mouse_motion_rotation_y": "Обертання Y",
|
||||
"ui.space_mouse_motion_rotation_z": "Обертання Z",
|
||||
"ui.space_mouse_motion_translation_x": "Зміщення X",
|
||||
"ui.space_mouse_motion_translation_y": "Зміщення Y",
|
||||
"ui.space_mouse_motion_translation_z": "Зміщення Z",
|
||||
"ui.space_mouse_navigate": "Навігація за допомогою SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "3D-мишу не знайдено",
|
||||
"ui.space_mouse_not_calibrated": "Не відкалібровано",
|
||||
"ui.space_mouse_reading": "Показники",
|
||||
"ui.space_mouse_reset_axes": "Скинути призначення осей",
|
||||
"ui.space_mouse_step_lift": "Підніміть ковпачок угору, утримайте й відпустіть.",
|
||||
"ui.space_mouse_step_press": "Натисніть ковпачок униз, утримайте й відпустіть.",
|
||||
"ui.space_mouse_step_pull": "Потягніть ковпачок до себе, утримайте й відпустіть.",
|
||||
"ui.space_mouse_step_push": "Штовхніть ковпачок від себе, утримайте й відпустіть.",
|
||||
"ui.space_mouse_step_rest": "Приберіть руку з ковпачка й не торкайтеся його.",
|
||||
"ui.space_mouse_step_slide_left": "Зсуньте ковпачок ліворуч, утримайте й відпустіть.",
|
||||
"ui.space_mouse_step_slide_right": "Зсуньте ковпачок праворуч, утримайте й відпустіть.",
|
||||
"ui.space_mouse_step_tilt_back": "Нахиліть ковпачок назад (передній край угору), утримайте й відпустіть.",
|
||||
"ui.space_mouse_step_tilt_forward": "Нахиліть ковпачок уперед (передній край униз), утримайте й відпустіть.",
|
||||
"ui.space_mouse_step_tilt_left": "Нахиліть ковпачок ліворуч (лівий край униз), утримайте й відпустіть.",
|
||||
"ui.space_mouse_step_tilt_right": "Нахиліть ковпачок праворуч (правий край униз), утримайте й відпустіть.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Поверніть ковпачок за годинниковою стрілкою, утримайте й відпустіть.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Поверніть ковпачок проти годинникової стрілки, утримайте й відпустіть.",
|
||||
"ui.space_mouse_view_axis": "Вісь вигляду",
|
||||
"ui.space_mouse_view_off": "Вимк.",
|
||||
"ui.space_mouse_view_off_tip": "Профіль драйвера 3D-миші вимикає цю вісь, тож її рух не рухає вигляд. Якщо вибрати тут рух іншої осі, вони поміняються місцями, і цей рух перестане рухати вигляд.",
|
||||
"ui.space_mouse_view_orbit_sideways": "Орбіта ліворуч / праворуч",
|
||||
"ui.space_mouse_view_orbit_up_down": "Орбіта вгору / вниз",
|
||||
"ui.space_mouse_view_pan_sideways": "Зсув ліворуч / праворуч",
|
||||
"ui.space_mouse_view_pan_up_down": "Зсув угору / вниз",
|
||||
"ui.space_mouse_view_roll": "Крен",
|
||||
"ui.space_mouse_view_unmapped": "Вісь {n}",
|
||||
"ui.space_mouse_view_zoom": "Масштаб",
|
||||
"ui.spacing": "Крок",
|
||||
"ui.spacing_2": "Крок 2",
|
||||
"ui.sphere": "Куля",
|
||||
|
||||
+100
-28
@@ -1125,7 +1125,106 @@
|
||||
"trace.stopped_by_contact": "Kết thúc tại mẫu {sample}: {a} sẽ xuyên qua {b}",
|
||||
"trace.stopped_by_loop": "Kết thúc tại mẫu {sample}: vòng lặp qua {joint} không thể khép kín thêm được nữa",
|
||||
"ui.1_degree_of_freedom": "1 bậc tự do",
|
||||
"ui.3d_mouse_axes_reassigned": "Các trục được gán lại trong Tùy chọn, và điều đó vẫn áp dụng cùng với hiệu chuẩn này.",
|
||||
"ui.3d_mouse_button": "Nút {number}",
|
||||
"ui.3d_mouse_button_nothing": "Không có",
|
||||
"ui.3d_mouse_button_settings": "Cài đặt chuột 3D",
|
||||
"ui.3d_mouse_buttons": "Nút",
|
||||
"ui.3d_mouse_buttons_hint": "Nhấn một nút trên chuột 3D để tìm nó ở đây, rồi chọn chức năng.",
|
||||
"ui.3d_mouse_buttons_no_device": "Kết nối chuột 3D để chọn chức năng cho các nút của nó.",
|
||||
"ui.3d_mouse_buttons_reset": "Khôi phục nút mặc định",
|
||||
"ui.3d_mouse_calibrate": "Hiệu chuẩn…",
|
||||
"ui.3d_mouse_calibrate_tip": "Di chuyển núm điều khiển theo yêu cầu của cửa sổ để SolidCraft biết mỗi trục của chuột 3D này đi theo hướng nào và xa bao nhiêu. Được lưu cho chuột 3D này.",
|
||||
"ui.3d_mouse_calibrate_title": "Hiệu chuẩn chuột 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Hãy di chuyển chuột 3D một lần để SolidCraft nhận ra nó.",
|
||||
"ui.3d_mouse_calibrated": "Đã hiệu chuẩn",
|
||||
"ui.3d_mouse_calibration": "Hiệu chuẩn chuột 3D",
|
||||
"ui.3d_mouse_calibration_done": "Xong. Hãy lưu để dùng mỗi khi chuột 3D này được kết nối.",
|
||||
"ui.3d_mouse_calibration_no_response": "Một trục hầu như không di chuyển nên không thể hiệu chuẩn. Hãy bắt đầu lại và di chuyển núm điều khiển xa hơn mỗi lần.",
|
||||
"ui.3d_mouse_circles_help": "Các vòng tròn cho thấy thiết bị gửi gì sau khi hiệu chuẩn. Mô hình thử cho thấy khung nhìn làm gì với nó, sau đảo chiều, tốc độ và gán trục.",
|
||||
"ui.3d_mouse_dead_zone": "Vùng chết",
|
||||
"ui.3d_mouse_dead_zone_tip": "Núm điều khiển phải di chuyển bao xa thì khung nhìn mới di chuyển, tính theo tỷ lệ hành trình tối đa. Hãy tăng giá trị này nếu khung nhìn bị trôi khi núm đang đứng yên.",
|
||||
"ui.3d_mouse_denied": "SolidCraft thấy một chuột 3D nhưng không được phép đọc nó. Xem ghi chú thiết lập chuột 3D để biết quy tắc udev.",
|
||||
"ui.3d_mouse_denied_short": "Đã thấy, nhưng không đọc được",
|
||||
"ui.3d_mouse_driven_by": "Chuyển động đã hiệu chuẩn nào của thiết bị điều khiển trục này. Chọn chuyển động mà trục khác đang dùng sẽ hoán đổi hai trục.",
|
||||
"ui.3d_mouse_driven_by_label": "điều khiển bởi",
|
||||
"ui.3d_mouse_driver": "Trình điều khiển chuột 3D",
|
||||
"ui.3d_mouse_driver_auto": "Tự động",
|
||||
"ui.3d_mouse_driver_direct": "Trực tiếp (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Tạm thời đọc qua spacenavd: không mở được chính thiết bị.",
|
||||
"ui.3d_mouse_driver_only_direct": "Đang đọc thiết bị trực tiếp; spacenavd không chạy",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Đang đọc qua spacenavd, cách duy nhất có sẵn",
|
||||
"ui.3d_mouse_forget": "Xóa hiệu chuẩn",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Khung nhìn bên trái",
|
||||
"ui.3d_mouse_ghost_left_window": "Cửa sổ bên trái",
|
||||
"ui.3d_mouse_ghost_position": "Vị trí",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Khung nhìn bên phải",
|
||||
"ui.3d_mouse_hint": "Di chuyển và xoay khung nhìn đồng thời; môi trường phác thảo chỉ sử dụng dời khung nhìn và thu phóng.",
|
||||
"ui.3d_mouse_kind_cap": "Trở về giữa",
|
||||
"ui.3d_mouse_kind_question": "Đây là loại chuột 3D nào?",
|
||||
"ui.3d_mouse_kind_trackball": "Bi xoay",
|
||||
"ui.3d_mouse_looks_like_cap": "Giá trị giữ nguyên khi bạn giữ, giống thiết bị kiểu “Trở về giữa”.",
|
||||
"ui.3d_mouse_looks_like_trackball": "Giá trị trở về ngay khi bạn ngừng di chuyển, giống bi xoay.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Xoay X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Xoay Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Xoay Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Tịnh tiến X",
|
||||
"ui.3d_mouse_motion_translation_y": "Tịnh tiến Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Tịnh tiến Z",
|
||||
"ui.3d_mouse_navigate": "Điều hướng bằng chuột 3D",
|
||||
"ui.3d_mouse_none": "Không tìm thấy chuột 3D",
|
||||
"ui.3d_mouse_not_calibrated": "Chưa hiệu chuẩn",
|
||||
"ui.3d_mouse_offer": "Hiệu chuẩn chuột 3D sẽ sửa các trục bị hoán đổi hoặc đảo ngược và chỉnh tốc độ cũng như cảm giác.",
|
||||
"ui.3d_mouse_offer_never": "Không hiện lại",
|
||||
"ui.3d_mouse_offer_swap": "Chuyển sang Hidraw (điều khiển trực tiếp)",
|
||||
"ui.3d_mouse_reading": "Giá trị đọc",
|
||||
"ui.3d_mouse_reset_axes": "Đặt lại phép gán trục",
|
||||
"ui.3d_mouse_reset_view": "Đặt lại khung nhìn",
|
||||
"ui.3d_mouse_restore": "Khôi phục mặc định chuột 3D",
|
||||
"ui.3d_mouse_restore_ask": "Đặt cài đặt chuột 3D về mặc định? Hiệu chuẩn và gán nút được giữ nguyên.",
|
||||
"ui.3d_mouse_section": "Chuột 3D",
|
||||
"ui.3d_mouse_show_in_view": "Hiện chuột 3D trong khung nhìn 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Hình chuột 3D của bạn ở góc dưới bên trái, chuyển động theo bạn.",
|
||||
"ui.3d_mouse_skip": "Thiết bị của tôi không làm được điều này",
|
||||
"ui.3d_mouse_skip_tip": "Bỏ qua chuyển động này theo cả hai chiều; việc hiệu chuẩn sẽ không dùng nó.",
|
||||
"ui.3d_mouse_speed": "Tốc độ chuột 3D",
|
||||
"ui.3d_mouse_step_lift": "Nâng núm điều khiển lên, giữ nguyên, rồi thả ra.",
|
||||
"ui.3d_mouse_step_press": "Nhấn núm điều khiển xuống, giữ nguyên, rồi thả ra.",
|
||||
"ui.3d_mouse_step_pull": "Kéo núm điều khiển về phía bạn, giữ nguyên, rồi thả ra.",
|
||||
"ui.3d_mouse_step_push": "Đẩy núm điều khiển ra xa bạn, giữ nguyên, rồi thả ra.",
|
||||
"ui.3d_mouse_step_rest": "Bỏ tay khỏi núm điều khiển và để yên.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Bỏ tay khỏi viên bi và để yên.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Xoay vòng cuộn theo chiều kim đồng hồ vài nấc.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Xoay vòng cuộn ngược chiều kim đồng hồ vài nấc.",
|
||||
"ui.3d_mouse_step_roll_away": "Lăn viên bi ra xa bạn vài lần, rồi để nó dừng lại.",
|
||||
"ui.3d_mouse_step_roll_left": "Lăn viên bi sang trái vài lần, rồi để nó dừng lại.",
|
||||
"ui.3d_mouse_step_roll_right": "Lăn viên bi sang phải vài lần, rồi để nó dừng lại.",
|
||||
"ui.3d_mouse_step_roll_toward": "Lăn viên bi về phía bạn vài lần, rồi để nó dừng lại.",
|
||||
"ui.3d_mouse_step_slide_left": "Trượt núm điều khiển sang trái, giữ nguyên, rồi thả ra.",
|
||||
"ui.3d_mouse_step_slide_right": "Trượt núm điều khiển sang phải, giữ nguyên, rồi thả ra.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Xoay viên bi theo chiều kim đồng hồ vài lần, rồi để nó dừng lại.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Xoay viên bi ngược chiều kim đồng hồ vài lần, rồi để nó dừng lại.",
|
||||
"ui.3d_mouse_step_tilt_away": "Nghiêng phần trên núm điều khiển ra xa bạn (cạnh gần bạn nhất nâng lên), giữ nguyên, rồi thả ra.",
|
||||
"ui.3d_mouse_step_tilt_left": "Nghiêng núm điều khiển sang trái (cạnh trái hạ xuống), giữ nguyên, rồi thả ra.",
|
||||
"ui.3d_mouse_step_tilt_right": "Nghiêng núm điều khiển sang phải (cạnh phải hạ xuống), giữ nguyên, rồi thả ra.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Nghiêng phần trên núm điều khiển về phía bạn (cạnh gần bạn nhất hạ xuống), giữ nguyên, rồi thả ra.",
|
||||
"ui.3d_mouse_step_touch": "Đặt tay lên núm điều khiển nhưng không đẩy.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Xoay núm điều khiển theo chiều kim đồng hồ, giữ nguyên, rồi thả ra.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Xoay núm điều khiển ngược chiều kim đồng hồ, giữ nguyên, rồi thả ra.",
|
||||
"ui.3d_mouse_switch_cap": "Hiệu chuẩn theo kiểu trở về giữa",
|
||||
"ui.3d_mouse_switch_trackball": "Hiệu chuẩn như bi xoay",
|
||||
"ui.3d_mouse_view_axis": "Trục khung nhìn",
|
||||
"ui.3d_mouse_view_off": "Tắt",
|
||||
"ui.3d_mouse_view_off_tip": "Hồ sơ của trình điều khiển chuột 3D tắt trục này, nên chuyển động của nó không làm khung nhìn di chuyển. Chọn chuyển động của trục khác ở đây sẽ hoán đổi hai trục, và chuyển động đó không còn di chuyển khung nhìn.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Xoay quanh trái / phải",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Xoay quanh lên / xuống",
|
||||
"ui.3d_mouse_view_pan_sideways": "Kéo trái / phải",
|
||||
"ui.3d_mouse_view_pan_up_down": "Kéo lên / xuống",
|
||||
"ui.3d_mouse_view_roll": "Lăn",
|
||||
"ui.3d_mouse_view_unmapped": "Trục {n}",
|
||||
"ui.3d_mouse_view_zoom": "Thu phóng",
|
||||
"ui.3d_mouse_waiting": "Đang chờ chuyển động",
|
||||
"ui.3d_mouse_wiggle": "Nếu bạn đã cắm một chiếc, hãy lắc nhẹ nó.",
|
||||
"ui.3d_sketch": "3D Phác thảo",
|
||||
"ui.activate": "Kích hoạt",
|
||||
"ui.active_component": "Cụm chi tiết đang kích hoạt",
|
||||
@@ -1355,6 +1454,7 @@
|
||||
"ui.blocked": "Bị chặn",
|
||||
"ui.bodies": "Các thân khối",
|
||||
"ui.bodies_all_if_none": "Thân khối (tất cả nếu không chọn)",
|
||||
"ui.bottom": "Dưới",
|
||||
"ui.box": "Khối hộp",
|
||||
"ui.break": "Ngắt",
|
||||
"ui.browser_bodies_one": "{count} thân khối",
|
||||
@@ -1798,34 +1898,6 @@
|
||||
"ui.smooth_wheel_zoom": "Thu phóng mượt bằng con lăn",
|
||||
"ui.smooth_wheel_zoom_tip": "Tắt tùy chọn này để áp dụng từng sự kiện con lăn ngay lập tức mà không có hiệu ứng làm mượt chuyển động",
|
||||
"ui.snap": "Bắt điểm",
|
||||
"ui.space_mouse_calibrate": "Hiệu chuẩn…",
|
||||
"ui.space_mouse_calibrate_tip": "Di chuyển núm điều khiển theo yêu cầu của cửa sổ để SolidCraft biết mỗi trục của chuột 3D này đi theo hướng nào và xa bao nhiêu. Được lưu cho chuột 3D này.",
|
||||
"ui.space_mouse_calibrate_title": "Hiệu chuẩn chuột 3D",
|
||||
"ui.space_mouse_calibrated": "Đã hiệu chuẩn",
|
||||
"ui.space_mouse_calibration": "Hiệu chuẩn chuột 3D",
|
||||
"ui.space_mouse_calibration_done": "Xong. Hãy lưu để dùng mỗi khi chuột 3D này được kết nối.",
|
||||
"ui.space_mouse_calibration_no_response": "Một trục hầu như không di chuyển nên không thể hiệu chuẩn. Hãy bắt đầu lại và di chuyển núm điều khiển xa hơn mỗi lần.",
|
||||
"ui.space_mouse_dead_zone": "Vùng chết",
|
||||
"ui.space_mouse_dead_zone_tip": "Núm điều khiển phải di chuyển bao xa thì góc nhìn mới di chuyển, tính theo tỷ lệ hành trình tối đa. Hãy tăng giá trị này nếu góc nhìn bị trôi khi núm đang đứng yên.",
|
||||
"ui.space_mouse_forget": "Xóa hiệu chuẩn",
|
||||
"ui.space_mouse_hint": "Di chuyển và xoay khung nhìn đồng thời; môi trường phác thảo chỉ sử dụng dời góc nhìn và thu phóng.",
|
||||
"ui.space_mouse_navigate": "Điều hướng bằng SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Không tìm thấy chuột 3D",
|
||||
"ui.space_mouse_not_calibrated": "Chưa hiệu chuẩn",
|
||||
"ui.space_mouse_reading": "Giá trị đọc",
|
||||
"ui.space_mouse_step_lift": "Nâng núm điều khiển lên, giữ nguyên, rồi thả ra.",
|
||||
"ui.space_mouse_step_press": "Nhấn núm điều khiển xuống, giữ nguyên, rồi thả ra.",
|
||||
"ui.space_mouse_step_pull": "Kéo núm điều khiển về phía bạn, giữ nguyên, rồi thả ra.",
|
||||
"ui.space_mouse_step_push": "Đẩy núm điều khiển ra xa bạn, giữ nguyên, rồi thả ra.",
|
||||
"ui.space_mouse_step_rest": "Bỏ tay khỏi núm điều khiển và để yên.",
|
||||
"ui.space_mouse_step_slide_left": "Trượt núm điều khiển sang trái, giữ nguyên, rồi thả ra.",
|
||||
"ui.space_mouse_step_slide_right": "Trượt núm điều khiển sang phải, giữ nguyên, rồi thả ra.",
|
||||
"ui.space_mouse_step_tilt_back": "Nghiêng núm điều khiển về sau (cạnh trước nâng lên), giữ nguyên, rồi thả ra.",
|
||||
"ui.space_mouse_step_tilt_forward": "Nghiêng núm điều khiển về trước (cạnh trước hạ xuống), giữ nguyên, rồi thả ra.",
|
||||
"ui.space_mouse_step_tilt_left": "Nghiêng núm điều khiển sang trái (cạnh trái hạ xuống), giữ nguyên, rồi thả ra.",
|
||||
"ui.space_mouse_step_tilt_right": "Nghiêng núm điều khiển sang phải (cạnh phải hạ xuống), giữ nguyên, rồi thả ra.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Xoay núm điều khiển theo chiều kim đồng hồ, giữ nguyên, rồi thả ra.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Xoay núm điều khiển ngược chiều kim đồng hồ, giữ nguyên, rồi thả ra.",
|
||||
"ui.spacing": "Khoảng cách",
|
||||
"ui.spacing_2": "Khoảng cách 2",
|
||||
"ui.sphere": "Hình cầu",
|
||||
|
||||
+43
-28
@@ -1124,7 +1124,50 @@
|
||||
"trace.stopped_by_contact": "在取樣 {sample} 處結束:{a} 會穿過 {b}",
|
||||
"trace.stopped_by_loop": "在取樣 {sample} 處結束:經過 {joint} 的環路無法再閉合",
|
||||
"ui.1_degree_of_freedom": "1 個自由度",
|
||||
"ui.3d_mouse_calibrate": "校準…",
|
||||
"ui.3d_mouse_calibrate_tip": "按視窗提示移動搖桿帽,SolidCraft 會記住這個 3D 滑鼠每個軸的方向和行程。校準結果按此 3D 滑鼠儲存。",
|
||||
"ui.3d_mouse_calibrate_title": "校準 3D 滑鼠",
|
||||
"ui.3d_mouse_calibrate_wait": "請移動一次 3D 滑鼠,讓 SolidCraft 能夠辨識它。",
|
||||
"ui.3d_mouse_calibrated": "已校準",
|
||||
"ui.3d_mouse_calibration": "3D 滑鼠校準",
|
||||
"ui.3d_mouse_calibration_done": "完成。儲存後,每次連線此 3D 滑鼠都會使用它。",
|
||||
"ui.3d_mouse_calibration_no_response": "有一個軸幾乎沒有移動,因此無法校準。請重新開始,並且每次把搖桿帽移得更遠。",
|
||||
"ui.3d_mouse_circles_help": "圓圈顯示裝置在校準後送出的內容。測試模型顯示檢視在反向、速度與軸指定之後如何處理它。",
|
||||
"ui.3d_mouse_dead_zone": "死區",
|
||||
"ui.3d_mouse_dead_zone_tip": "檢視開始移動前搖桿帽需要移動的距離,以其全行程的比例表示。如果搖桿帽靜止時檢視仍在漂移,請調大。",
|
||||
"ui.3d_mouse_denied": "SolidCraft 看得到 3D 滑鼠,但沒有權限讀取。udev 規則請見 3D 滑鼠的設定說明。",
|
||||
"ui.3d_mouse_denied_short": "已找到,但無法讀取",
|
||||
"ui.3d_mouse_driver_fallback": "目前改透過 spacenavd 讀取:無法開啟裝置本身。",
|
||||
"ui.3d_mouse_forget": "清除校準",
|
||||
"ui.3d_mouse_ghost_left_viewport": "視埠左側",
|
||||
"ui.3d_mouse_ghost_left_window": "視窗左側",
|
||||
"ui.3d_mouse_ghost_position": "位置",
|
||||
"ui.3d_mouse_ghost_right_viewport": "視埠右側",
|
||||
"ui.3d_mouse_hint": "同時移動和旋轉檢視;草圖中僅支援平移和縮放。",
|
||||
"ui.3d_mouse_navigate": "使用 3D 滑鼠導覽",
|
||||
"ui.3d_mouse_none": "未找到 3D 滑鼠",
|
||||
"ui.3d_mouse_not_calibrated": "未校準",
|
||||
"ui.3d_mouse_offer": "校準 3D 滑鼠可以修正互換或反向的軸,並調整它的速度和手感。",
|
||||
"ui.3d_mouse_offer_never": "不再顯示",
|
||||
"ui.3d_mouse_offer_swap": "切換到 Hidraw(直接驅動)",
|
||||
"ui.3d_mouse_reading": "讀值",
|
||||
"ui.3d_mouse_reset_view": "重設檢視",
|
||||
"ui.3d_mouse_restore": "還原 3D 滑鼠預設值",
|
||||
"ui.3d_mouse_restore_ask": "要將 3D 滑鼠設定還原為預設值嗎?校準和按鈕對應會保留。",
|
||||
"ui.3d_mouse_speed": "3D 滑鼠速度",
|
||||
"ui.3d_mouse_step_lift": "向上提起搖桿帽,保持住,然後鬆開。",
|
||||
"ui.3d_mouse_step_press": "向下按壓搖桿帽,保持住,然後鬆開。",
|
||||
"ui.3d_mouse_step_pull": "把搖桿帽拉向自己,保持住,然後鬆開。",
|
||||
"ui.3d_mouse_step_push": "把搖桿帽推離自己,保持住,然後鬆開。",
|
||||
"ui.3d_mouse_step_rest": "請把手從搖桿帽上移開,讓它保持靜止。",
|
||||
"ui.3d_mouse_step_slide_left": "向左推動搖桿帽,保持住,然後鬆開。",
|
||||
"ui.3d_mouse_step_slide_right": "向右推動搖桿帽,保持住,然後鬆開。",
|
||||
"ui.3d_mouse_step_tilt_left": "向左傾斜搖桿帽(左緣向下),保持住,然後鬆開。",
|
||||
"ui.3d_mouse_step_tilt_right": "向右傾斜搖桿帽(右緣向下),保持住,然後鬆開。",
|
||||
"ui.3d_mouse_step_twist_clockwise": "順時針扭轉搖桿帽,保持住,然後鬆開。",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "逆時針扭轉搖桿帽,保持住,然後鬆開。",
|
||||
"ui.3d_mouse_waiting": "等待移動",
|
||||
"ui.3d_mouse_wiggle": "如果已插上,請輕輕晃動一下。",
|
||||
"ui.3d_sketch": "3D 草圖",
|
||||
"ui.activate": "啟用",
|
||||
"ui.active_component": "作用中元件",
|
||||
@@ -1797,34 +1840,6 @@
|
||||
"ui.smooth_wheel_zoom": "平滑滾輪縮放",
|
||||
"ui.smooth_wheel_zoom_tip": "關閉後每個滾輪事件立即生效,不做平滑延遲",
|
||||
"ui.snap": "鎖點",
|
||||
"ui.space_mouse_calibrate": "校準…",
|
||||
"ui.space_mouse_calibrate_tip": "按視窗提示移動搖桿帽,SolidCraft 會記住這個 3D 滑鼠每個軸的方向和行程。校準結果按此 3D 滑鼠儲存。",
|
||||
"ui.space_mouse_calibrate_title": "校準 3D 滑鼠",
|
||||
"ui.space_mouse_calibrated": "已校準",
|
||||
"ui.space_mouse_calibration": "3D 滑鼠校準",
|
||||
"ui.space_mouse_calibration_done": "完成。儲存後,每次連線此 3D 滑鼠都會使用它。",
|
||||
"ui.space_mouse_calibration_no_response": "有一個軸幾乎沒有移動,因此無法校準。請重新開始,並且每次把搖桿帽移得更遠。",
|
||||
"ui.space_mouse_dead_zone": "死區",
|
||||
"ui.space_mouse_dead_zone_tip": "檢視開始移動前搖桿帽需要移動的距離,以其全行程的比例表示。如果搖桿帽靜止時檢視仍在漂移,請調大。",
|
||||
"ui.space_mouse_forget": "清除校準",
|
||||
"ui.space_mouse_hint": "同時移動和旋轉檢視;草圖中僅支援平移和縮放。",
|
||||
"ui.space_mouse_navigate": "使用 SpaceNavigator / SpaceMouse 導覽",
|
||||
"ui.space_mouse_none": "未找到 3D 滑鼠",
|
||||
"ui.space_mouse_not_calibrated": "未校準",
|
||||
"ui.space_mouse_reading": "讀值",
|
||||
"ui.space_mouse_step_lift": "向上提起搖桿帽,保持住,然後鬆開。",
|
||||
"ui.space_mouse_step_press": "向下按壓搖桿帽,保持住,然後鬆開。",
|
||||
"ui.space_mouse_step_pull": "把搖桿帽拉向自己,保持住,然後鬆開。",
|
||||
"ui.space_mouse_step_push": "把搖桿帽推離自己,保持住,然後鬆開。",
|
||||
"ui.space_mouse_step_rest": "請把手從搖桿帽上移開,讓它保持靜止。",
|
||||
"ui.space_mouse_step_slide_left": "向左推動搖桿帽,保持住,然後鬆開。",
|
||||
"ui.space_mouse_step_slide_right": "向右推動搖桿帽,保持住,然後鬆開。",
|
||||
"ui.space_mouse_step_tilt_back": "向後傾斜搖桿帽(前緣向上),保持住,然後鬆開。",
|
||||
"ui.space_mouse_step_tilt_forward": "向前傾斜搖桿帽(前緣向下),保持住,然後鬆開。",
|
||||
"ui.space_mouse_step_tilt_left": "向左傾斜搖桿帽(左緣向下),保持住,然後鬆開。",
|
||||
"ui.space_mouse_step_tilt_right": "向右傾斜搖桿帽(右緣向下),保持住,然後鬆開。",
|
||||
"ui.space_mouse_step_twist_clockwise": "順時針扭轉搖桿帽,保持住,然後鬆開。",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "逆時針扭轉搖桿帽,保持住,然後鬆開。",
|
||||
"ui.spacing": "間距",
|
||||
"ui.spacing_2": "間距 2",
|
||||
"ui.sphere": "球體",
|
||||
|
||||
+100
-53
@@ -1461,7 +1461,106 @@
|
||||
"trace.stopped_by_contact": "在采样 {sample} 处结束:{a} 会穿过 {b}",
|
||||
"trace.stopped_by_loop": "在采样 {sample} 处结束:经过 {joint} 的环路无法再闭合",
|
||||
"ui.1_degree_of_freedom": "1 个自由度",
|
||||
"ui.3d_mouse_axes_reassigned": "轴已在偏好设置中重新分配,在此次校准之上仍然生效。",
|
||||
"ui.3d_mouse_button": "按钮 {number}",
|
||||
"ui.3d_mouse_button_nothing": "无",
|
||||
"ui.3d_mouse_button_settings": "3D 鼠标设置",
|
||||
"ui.3d_mouse_buttons": "按钮",
|
||||
"ui.3d_mouse_buttons_hint": "按下 3D 鼠标上的按钮即可在此找到它,然后选择它的功能。",
|
||||
"ui.3d_mouse_buttons_no_device": "连接 3D 鼠标后即可设置其按钮功能。",
|
||||
"ui.3d_mouse_buttons_reset": "恢复默认按钮",
|
||||
"ui.3d_mouse_calibrate": "校准…",
|
||||
"ui.3d_mouse_calibrate_tip": "按窗口提示移动摇杆帽,SolidCraft 会记住这个 3D 鼠标每个轴的方向和行程。校准结果按此 3D 鼠标保存。",
|
||||
"ui.3d_mouse_calibrate_title": "校准 3D 鼠标",
|
||||
"ui.3d_mouse_calibrate_wait": "请移动一次 3D 鼠标,让 SolidCraft 能够识别它。",
|
||||
"ui.3d_mouse_calibrated": "已校准",
|
||||
"ui.3d_mouse_calibration": "3D 鼠标校准",
|
||||
"ui.3d_mouse_calibration_done": "完成。保存后,每次连接此 3D 鼠标都会使用它。",
|
||||
"ui.3d_mouse_calibration_no_response": "有一个轴几乎没有移动,因此无法校准。请重新开始,并且每次把摇杆帽移得更远。",
|
||||
"ui.3d_mouse_circles_help": "圆圈显示设备在校准后发送的内容。测试模型显示视图在反向、速度和轴分配之后如何处理它。",
|
||||
"ui.3d_mouse_dead_zone": "死区",
|
||||
"ui.3d_mouse_dead_zone_tip": "视图开始移动前摇杆帽需要移动的距离,以其全行程的比例表示。如果摇杆帽静止时视图仍在漂移,请调大。",
|
||||
"ui.3d_mouse_denied": "SolidCraft 能看到 3D 鼠标,但没有权限读取。udev 规则请见 3D 鼠标的设置说明。",
|
||||
"ui.3d_mouse_denied_short": "已找到,但无法读取",
|
||||
"ui.3d_mouse_driven_by": "由设备的哪个校准动作控制此轴。选择另一个轴已在使用的动作时,两者会互换。",
|
||||
"ui.3d_mouse_driven_by_label": "控制来源",
|
||||
"ui.3d_mouse_driver": "3D 鼠标驱动",
|
||||
"ui.3d_mouse_driver_auto": "自动",
|
||||
"ui.3d_mouse_driver_direct": "直接 (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "目前改通过 spacenavd 读取:无法打开设备本身。",
|
||||
"ui.3d_mouse_driver_only_direct": "直接读取设备(spacenavd 未运行)",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "通过 spacenavd 读取(没有其他方式)",
|
||||
"ui.3d_mouse_forget": "清除校准",
|
||||
"ui.3d_mouse_ghost_left_viewport": "视口左侧",
|
||||
"ui.3d_mouse_ghost_left_window": "窗口左侧",
|
||||
"ui.3d_mouse_ghost_position": "位置",
|
||||
"ui.3d_mouse_ghost_right_viewport": "视口右侧",
|
||||
"ui.3d_mouse_hint": "同时移动和旋转视图;草图中仅支持平移和缩放。",
|
||||
"ui.3d_mouse_kind_cap": "回中",
|
||||
"ui.3d_mouse_kind_question": "这是哪种 3D 鼠标?",
|
||||
"ui.3d_mouse_kind_trackball": "轨迹球",
|
||||
"ui.3d_mouse_looks_like_cap": "按住时读数保持不变,就像“回中”型设备那样。",
|
||||
"ui.3d_mouse_looks_like_trackball": "一停下来读数就回落,这是轨迹球的特征。",
|
||||
"ui.3d_mouse_motion_rotation_x": "旋转 X",
|
||||
"ui.3d_mouse_motion_rotation_y": "旋转 Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "旋转 Z",
|
||||
"ui.3d_mouse_motion_translation_x": "平移 X",
|
||||
"ui.3d_mouse_motion_translation_y": "平移 Y",
|
||||
"ui.3d_mouse_motion_translation_z": "平移 Z",
|
||||
"ui.3d_mouse_navigate": "使用 3D 鼠标导航",
|
||||
"ui.3d_mouse_none": "未找到 3D 鼠标",
|
||||
"ui.3d_mouse_not_calibrated": "未校准",
|
||||
"ui.3d_mouse_offer": "校准 3D 鼠标可以修正互换或反向的轴,并调整它的速度和手感。",
|
||||
"ui.3d_mouse_offer_never": "不再显示",
|
||||
"ui.3d_mouse_offer_swap": "切换到 Hidraw(直接驱动)",
|
||||
"ui.3d_mouse_reading": "读数",
|
||||
"ui.3d_mouse_reset_axes": "重置轴分配",
|
||||
"ui.3d_mouse_reset_view": "重置视图",
|
||||
"ui.3d_mouse_restore": "恢复 3D 鼠标默认设置",
|
||||
"ui.3d_mouse_restore_ask": "要将 3D 鼠标设置恢复为默认值吗?校准和按钮映射会保留。",
|
||||
"ui.3d_mouse_section": "3D 鼠标",
|
||||
"ui.3d_mouse_show_in_view": "在 3D 视图中显示 3D 鼠标",
|
||||
"ui.3d_mouse_show_in_view_tip": "在左下角显示你的 3D 鼠标图示,随你的操作而动。",
|
||||
"ui.3d_mouse_skip": "我的设备做不到",
|
||||
"ui.3d_mouse_skip_tip": "在两个方向上都跳过这个动作,校准时不包含它。",
|
||||
"ui.3d_mouse_speed": "3D 鼠标速度",
|
||||
"ui.3d_mouse_step_lift": "向上提起摇杆帽,保持住,然后松开。",
|
||||
"ui.3d_mouse_step_press": "向下按压摇杆帽,保持住,然后松开。",
|
||||
"ui.3d_mouse_step_pull": "把摇杆帽拉向自己,保持住,然后松开。",
|
||||
"ui.3d_mouse_step_push": "把摇杆帽推离自己,保持住,然后松开。",
|
||||
"ui.3d_mouse_step_rest": "请把手从摇杆帽上移开,让它保持静止。",
|
||||
"ui.3d_mouse_step_rest_trackball": "请把手从球上移开,让它保持静止。",
|
||||
"ui.3d_mouse_step_ring_clockwise": "把滚轮环顺时针转几格。",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "把滚轮环逆时针转几格。",
|
||||
"ui.3d_mouse_step_roll_away": "把球向远离你的方向滚动几次,然后让它停下。",
|
||||
"ui.3d_mouse_step_roll_left": "把球向左滚动几次,然后让它停下。",
|
||||
"ui.3d_mouse_step_roll_right": "把球向右滚动几次,然后让它停下。",
|
||||
"ui.3d_mouse_step_roll_toward": "把球朝你滚动几次,然后让它停下。",
|
||||
"ui.3d_mouse_step_slide_left": "向左推动摇杆帽,保持住,然后松开。",
|
||||
"ui.3d_mouse_step_slide_right": "向右推动摇杆帽,保持住,然后松开。",
|
||||
"ui.3d_mouse_step_spin_clockwise": "把球顺时针旋转几次,然后让它停下。",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "把球逆时针旋转几次,然后让它停下。",
|
||||
"ui.3d_mouse_step_tilt_away": "把摇杆帽顶部向远离你的方向倾斜(离你最近的边向上),保持住,然后松开。",
|
||||
"ui.3d_mouse_step_tilt_left": "向左倾斜摇杆帽(左缘向下),保持住,然后松开。",
|
||||
"ui.3d_mouse_step_tilt_right": "向右倾斜摇杆帽(右缘向下),保持住,然后松开。",
|
||||
"ui.3d_mouse_step_tilt_toward": "把摇杆帽顶部朝你倾斜(离你最近的边向下),保持住,然后松开。",
|
||||
"ui.3d_mouse_step_touch": "把手放在摇杆帽上,不要用力推。",
|
||||
"ui.3d_mouse_step_twist_clockwise": "顺时针扭转摇杆帽,保持住,然后松开。",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "逆时针扭转摇杆帽,保持住,然后松开。",
|
||||
"ui.3d_mouse_switch_cap": "按回中类型校准",
|
||||
"ui.3d_mouse_switch_trackball": "按轨迹球校准",
|
||||
"ui.3d_mouse_view_axis": "视图轴",
|
||||
"ui.3d_mouse_view_off": "关闭",
|
||||
"ui.3d_mouse_view_off_tip": "3D 鼠标驱动程序的配置文件关闭了此轴,因此它的动作不会移动视图。在此选择另一个轴的动作会将两者互换,该动作将不再移动视图。",
|
||||
"ui.3d_mouse_view_orbit_sideways": "左右环绕",
|
||||
"ui.3d_mouse_view_orbit_up_down": "上下环绕",
|
||||
"ui.3d_mouse_view_pan_sideways": "左右平移",
|
||||
"ui.3d_mouse_view_pan_up_down": "上下平移",
|
||||
"ui.3d_mouse_view_roll": "滚转",
|
||||
"ui.3d_mouse_view_unmapped": "轴 {n}",
|
||||
"ui.3d_mouse_view_zoom": "缩放",
|
||||
"ui.3d_mouse_waiting": "等待移动",
|
||||
"ui.3d_mouse_wiggle": "如果已插上,请轻轻晃动一下。",
|
||||
"ui.3d_sketch": "3D 草图",
|
||||
"ui.activate": "激活",
|
||||
"ui.active_component": "活动组件",
|
||||
@@ -1699,6 +1798,7 @@
|
||||
"ui.blocked": "被遮挡",
|
||||
"ui.bodies": "实体",
|
||||
"ui.bodies_all_if_none": "实体(未选择则全部)",
|
||||
"ui.bottom": "下视",
|
||||
"ui.box": "长方体",
|
||||
"ui.break": "打断",
|
||||
"ui.browser_bodies_one": "{count} 个实体",
|
||||
@@ -2165,59 +2265,6 @@
|
||||
"ui.smooth_wheel_zoom": "平滑滚轮缩放",
|
||||
"ui.smooth_wheel_zoom_tip": "关闭后每个滚轮事件立即生效,不做平滑延迟",
|
||||
"ui.snap": "捕捉",
|
||||
"ui.space_mouse_axes_reassigned": "轴已在偏好设置中重新分配,在此次校准之上仍然生效。",
|
||||
"ui.space_mouse_calibrate": "校准…",
|
||||
"ui.space_mouse_calibrate_tip": "按窗口提示移动摇杆帽,SolidCraft 会记住这个 3D 鼠标每个轴的方向和行程。校准结果按此 3D 鼠标保存。",
|
||||
"ui.space_mouse_calibrate_title": "校准 3D 鼠标",
|
||||
"ui.space_mouse_calibrated": "已校准",
|
||||
"ui.space_mouse_calibration": "3D 鼠标校准",
|
||||
"ui.space_mouse_calibration_done": "完成。保存后,每次连接此 3D 鼠标都会使用它。",
|
||||
"ui.space_mouse_calibration_no_response": "有一个轴几乎没有移动,因此无法校准。请重新开始,并且每次把摇杆帽移得更远。",
|
||||
"ui.space_mouse_dead_zone": "死区",
|
||||
"ui.space_mouse_dead_zone_tip": "视图开始移动前摇杆帽需要移动的距离,以其全行程的比例表示。如果摇杆帽静止时视图仍在漂移,请调大。",
|
||||
"ui.space_mouse_driven_by": "由设备的哪个校准动作控制此轴。选择另一个轴已在使用的动作时,两者会互换。",
|
||||
"ui.space_mouse_driven_by_label": "控制来源",
|
||||
"ui.space_mouse_driver": "3D 鼠标驱动",
|
||||
"ui.space_mouse_driver_auto": "自动",
|
||||
"ui.space_mouse_driver_direct": "直接 (hidraw)",
|
||||
"ui.space_mouse_driver_only_direct": "直接读取设备(spacenavd 未运行)",
|
||||
"ui.space_mouse_driver_only_spacenavd": "通过 spacenavd 读取(没有其他方式)",
|
||||
"ui.space_mouse_forget": "清除校准",
|
||||
"ui.space_mouse_hint": "同时移动和旋转视图;草图中仅支持平移和缩放。",
|
||||
"ui.space_mouse_motion_rotation_x": "旋转 X",
|
||||
"ui.space_mouse_motion_rotation_y": "旋转 Y",
|
||||
"ui.space_mouse_motion_rotation_z": "旋转 Z",
|
||||
"ui.space_mouse_motion_translation_x": "平移 X",
|
||||
"ui.space_mouse_motion_translation_y": "平移 Y",
|
||||
"ui.space_mouse_motion_translation_z": "平移 Z",
|
||||
"ui.space_mouse_navigate": "使用 SpaceNavigator / SpaceMouse 导航",
|
||||
"ui.space_mouse_none": "未找到 3D 鼠标",
|
||||
"ui.space_mouse_not_calibrated": "未校准",
|
||||
"ui.space_mouse_reading": "读数",
|
||||
"ui.space_mouse_reset_axes": "重置轴分配",
|
||||
"ui.space_mouse_step_lift": "向上提起摇杆帽,保持住,然后松开。",
|
||||
"ui.space_mouse_step_press": "向下按压摇杆帽,保持住,然后松开。",
|
||||
"ui.space_mouse_step_pull": "把摇杆帽拉向自己,保持住,然后松开。",
|
||||
"ui.space_mouse_step_push": "把摇杆帽推离自己,保持住,然后松开。",
|
||||
"ui.space_mouse_step_rest": "请把手从摇杆帽上移开,让它保持静止。",
|
||||
"ui.space_mouse_step_slide_left": "向左推动摇杆帽,保持住,然后松开。",
|
||||
"ui.space_mouse_step_slide_right": "向右推动摇杆帽,保持住,然后松开。",
|
||||
"ui.space_mouse_step_tilt_back": "向后倾斜摇杆帽(前缘向上),保持住,然后松开。",
|
||||
"ui.space_mouse_step_tilt_forward": "向前倾斜摇杆帽(前缘向下),保持住,然后松开。",
|
||||
"ui.space_mouse_step_tilt_left": "向左倾斜摇杆帽(左缘向下),保持住,然后松开。",
|
||||
"ui.space_mouse_step_tilt_right": "向右倾斜摇杆帽(右缘向下),保持住,然后松开。",
|
||||
"ui.space_mouse_step_twist_clockwise": "顺时针扭转摇杆帽,保持住,然后松开。",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "逆时针扭转摇杆帽,保持住,然后松开。",
|
||||
"ui.space_mouse_view_axis": "视图轴",
|
||||
"ui.space_mouse_view_off": "关闭",
|
||||
"ui.space_mouse_view_off_tip": "3D 鼠标驱动程序的配置文件关闭了此轴,因此它的动作不会移动视图。在此选择另一个轴的动作会将两者互换,该动作将不再移动视图。",
|
||||
"ui.space_mouse_view_orbit_sideways": "左右环绕",
|
||||
"ui.space_mouse_view_orbit_up_down": "上下环绕",
|
||||
"ui.space_mouse_view_pan_sideways": "左右平移",
|
||||
"ui.space_mouse_view_pan_up_down": "上下平移",
|
||||
"ui.space_mouse_view_roll": "滚转",
|
||||
"ui.space_mouse_view_unmapped": "轴 {n}",
|
||||
"ui.space_mouse_view_zoom": "缩放",
|
||||
"ui.spacing": "间距",
|
||||
"ui.spacing_2": "间距 2",
|
||||
"ui.sphere": "球体",
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Packs the boat for the 3D mouse test model in Preferences into assets/models/test_boat.bin, from a
|
||||
binary STL of the low-poly boat (about 850 triangles). Needs numpy.
|
||||
|
||||
python3 assets/models/make_test_boat.py SOURCE.stl assets/models/test_boat.bin
|
||||
|
||||
No reduction happens here: the vertices are welded, and each welded vertex is split by the angle
|
||||
between the faces that meet there (more than 40 degrees apart is a crease), so smooth surfaces share
|
||||
a vertex normal and edges stay sharp. The model is centred and scaled into a unit box, then
|
||||
quantised. File layout, little-endian: u32 vertex count, u32 triangle count, f32 centre (x, y, z),
|
||||
f32 half size of the longest side; then per vertex its position as i16 x 3 (32767 is the half size)
|
||||
and its normal as i8 x 3 (127 is a unit); then per triangle three u16 vertex indices. The model
|
||||
sits with z up.
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
CREASE_DEGREES = 40.0
|
||||
|
||||
|
||||
def main() -> None:
|
||||
source, target = sys.argv[1], sys.argv[2]
|
||||
data = open(source, "rb").read()
|
||||
count = struct.unpack_from("<I", data, 80)[0]
|
||||
record = np.dtype([("n", "<f4", 3), ("v", "<f4", (3, 3)), ("attr", "<u2")])
|
||||
tris = np.frombuffer(data, dtype=record, count=count, offset=84)["v"].astype(np.float64)
|
||||
points, inverse = np.unique(np.round(tris.reshape(-1, 3), 3), axis=0, return_inverse=True)
|
||||
faces = inverse.reshape(-1, 3)
|
||||
keep = (faces[:, 0] != faces[:, 1]) & (faces[:, 1] != faces[:, 2]) & (faces[:, 0] != faces[:, 2])
|
||||
faces = faces[keep]
|
||||
a, b, c = points[faces[:, 0]], points[faces[:, 1]], points[faces[:, 2]]
|
||||
cross = np.cross(b - a, c - a)
|
||||
area = np.linalg.norm(cross, axis=1)
|
||||
ok = area > 1e-9
|
||||
faces, cross, area = faces[ok], cross[ok], area[ok]
|
||||
face_normal = cross / area[:, None]
|
||||
# Split each welded vertex into one vertex per group of faces that agree (within the crease angle).
|
||||
cos_limit = np.cos(np.radians(CREASE_DEGREES))
|
||||
corners = {}
|
||||
for f, tri in enumerate(faces):
|
||||
for k in range(3):
|
||||
corners.setdefault(int(tri[k]), []).append((f, k))
|
||||
out_points, out_normals, index = [], [], np.zeros(faces.shape, dtype=np.int64)
|
||||
for v, incident in corners.items():
|
||||
groups = [] # each: [summed normal, [(face, corner)]]
|
||||
for f, k in incident:
|
||||
n = face_normal[f]
|
||||
for g in groups:
|
||||
mean = g[0] / np.linalg.norm(g[0])
|
||||
if mean @ n > cos_limit:
|
||||
g[0] = g[0] + n * area[f]
|
||||
g[1].append((f, k))
|
||||
break
|
||||
else:
|
||||
groups.append([n * area[f], [(f, k)]])
|
||||
for total, members in groups:
|
||||
length = np.linalg.norm(total)
|
||||
out_normals.append(total / length if length > 0 else np.array([0.0, 0.0, 1.0]))
|
||||
out_points.append(points[v])
|
||||
for f, k in members:
|
||||
index[f, k] = len(out_points) - 1
|
||||
out_points, out_normals = np.array(out_points), np.array(out_normals)
|
||||
low, high = out_points.min(axis=0), out_points.max(axis=0)
|
||||
centre, half = (low + high) / 2.0, float((high - low).max() / 2.0)
|
||||
positions = np.clip(np.round((out_points - centre) / half * 32767.0), -32767, 32767).astype("<i2")
|
||||
normals = np.clip(np.round(out_normals * 127.0), -127, 127).astype("i1")
|
||||
assert len(out_points) <= 65535
|
||||
with open(target, "wb") as out:
|
||||
out.write(struct.pack("<II3ff", len(out_points), len(index), *centre.tolist(), half))
|
||||
for p, n in zip(positions, normals):
|
||||
out.write(p.tobytes() + n.tobytes())
|
||||
out.write(index.astype("<u2").tobytes())
|
||||
print(f"{len(out_points)} vertices, {len(index)} triangles, centre {centre}, half size {half}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
@@ -0,0 +1,153 @@
|
||||
//! What each button on a 3D mouse does by default, for the devices we have measured. The jobs
|
||||
//! come from 3Dconnexion's public SpaceMouse Pro manual (Feature Guide); the button numbers come
|
||||
//! only from our own measurements. Nothing here is guessed: a device or button that isn't in
|
||||
//! [`MEASURED`] has no default.
|
||||
|
||||
use crate::MAX_BUTTONS;
|
||||
|
||||
/// A job a button can do by default.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Action {
|
||||
/// Opens the device's settings.
|
||||
Menu,
|
||||
/// Brings the model back to the centre of the screen.
|
||||
Fit,
|
||||
ViewTop,
|
||||
ViewRight,
|
||||
ViewFront,
|
||||
/// Rolls the view.
|
||||
Roll,
|
||||
Esc,
|
||||
Alt,
|
||||
Shift,
|
||||
Ctrl,
|
||||
/// Locks rotation with one press.
|
||||
RotationLock,
|
||||
}
|
||||
|
||||
/// One measured button: device (USB vendor and product id), the button number the device
|
||||
/// reports (counting from 1, like [`crate::Layout::buttons`]), and the job it does by default.
|
||||
/// `None` means the key is measured but has no fixed default (the numbered function keys, whose
|
||||
/// commands the vendor's driver assigns per application).
|
||||
type Row = (u16, u16, usize, Option<Action>);
|
||||
|
||||
/// The only place button numbers live. To add a key, add a row from a measurement; don't
|
||||
/// guess a number from memory or from anyone else's code.
|
||||
const MEASURED: &[Row] = &[
|
||||
// SpaceMouse Pro (046d:c62b): our own measurement on a SpaceMouse Pro, 2026-10-16.
|
||||
// Each row is one printed key.
|
||||
(0x046d, 0xc62b, 1, Some(Action::Menu)),
|
||||
(0x046d, 0xc62b, 2, Some(Action::Fit)),
|
||||
(0x046d, 0xc62b, 3, Some(Action::ViewTop)),
|
||||
(0x046d, 0xc62b, 5, Some(Action::ViewRight)),
|
||||
(0x046d, 0xc62b, 6, Some(Action::ViewFront)),
|
||||
(0x046d, 0xc62b, 9, Some(Action::Roll)),
|
||||
(0x046d, 0xc62b, 13, None), // printed "1"
|
||||
(0x046d, 0xc62b, 14, None), // printed "2"
|
||||
(0x046d, 0xc62b, 15, None), // printed "3"
|
||||
(0x046d, 0xc62b, 16, None), // printed "4"
|
||||
(0x046d, 0xc62b, 23, Some(Action::Esc)),
|
||||
(0x046d, 0xc62b, 24, Some(Action::Alt)),
|
||||
(0x046d, 0xc62b, 25, Some(Action::Shift)),
|
||||
(0x046d, 0xc62b, 26, Some(Action::Ctrl)),
|
||||
(0x046d, 0xc62b, 27, Some(Action::RotationLock)),
|
||||
];
|
||||
|
||||
/// The default job of each button on this device, indexed like [`crate::Layout::buttons`] (button
|
||||
/// number minus 1). `None` for a device we haven't measured, including homemade ones. For a
|
||||
/// measured device, a button's entry is `None` unless we've measured it and it has a default.
|
||||
pub fn defaults(vendor: u16, product: u16) -> Option<[Option<Action>; MAX_BUTTONS]> {
|
||||
from_rows(MEASURED, vendor, product)
|
||||
}
|
||||
|
||||
fn from_rows(rows: &[Row], vendor: u16, product: u16) -> Option<[Option<Action>; MAX_BUTTONS]> {
|
||||
let mut rows = rows.iter().filter(|row| row.0 == vendor && row.1 == product).peekable();
|
||||
rows.peek()?;
|
||||
let mut out = [None; MAX_BUTTONS];
|
||||
for &(_, _, number, action) in rows {
|
||||
// A row with a number outside 1..=MAX_BUTTONS is ignored rather than trusted.
|
||||
if let Some(slot) = number.checked_sub(1).and_then(|i| out.get_mut(i)) {
|
||||
*slot = action;
|
||||
}
|
||||
}
|
||||
// A device whose rows give no action has no defaults at all.
|
||||
out.iter().any(Option::is_some).then_some(out)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn pro_rows_resolve() {
|
||||
let table = defaults(0x046d, 0xc62b).expect("measured device");
|
||||
let at = |n: usize| table.get(n - 1).copied().flatten();
|
||||
assert_eq!(at(1), Some(Action::Menu));
|
||||
assert_eq!(at(2), Some(Action::Fit));
|
||||
assert_eq!(at(3), Some(Action::ViewTop));
|
||||
assert_eq!(at(5), Some(Action::ViewRight));
|
||||
assert_eq!(at(6), Some(Action::ViewFront));
|
||||
assert_eq!(at(9), Some(Action::Roll));
|
||||
assert_eq!(at(23), Some(Action::Esc));
|
||||
assert_eq!(at(24), Some(Action::Alt));
|
||||
assert_eq!(at(25), Some(Action::Shift));
|
||||
assert_eq!(at(26), Some(Action::Ctrl));
|
||||
assert_eq!(at(27), Some(Action::RotationLock));
|
||||
// The numbered keys 1 to 4, undeclared numbers and the unused tail have no action.
|
||||
for n in [4, 7, 8, 10, 13, 14, 15, 16, 28, 32] {
|
||||
assert_eq!(at(n), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rows_resolve_to_their_action() {
|
||||
let rows: &[Row] = &[
|
||||
// Synthetic numbers, not real mappings.
|
||||
(1, 2, 5, Some(Action::Fit)),
|
||||
// Synthetic numbers, not real mappings.
|
||||
(1, 2, 1, Some(Action::Menu)),
|
||||
// Synthetic numbers, not real mappings: zero and past MAX_BUTTONS are dropped.
|
||||
(1, 2, 0, Some(Action::Esc)),
|
||||
// Synthetic numbers, not real mappings.
|
||||
(1, 2, MAX_BUTTONS + 1, Some(Action::Alt)),
|
||||
// Synthetic numbers, not real mappings: a device with only no-action rows.
|
||||
(1, 4, 7, None),
|
||||
];
|
||||
let table = from_rows(rows, 1, 2).expect("known device");
|
||||
assert_eq!(table.get(4), Some(&Some(Action::Fit)));
|
||||
assert_eq!(table.first(), Some(&Some(Action::Menu)));
|
||||
assert_eq!(table.iter().flatten().count(), 2);
|
||||
assert!(from_rows(rows, 1, 3).is_none());
|
||||
assert!(from_rows(rows, 1, 4).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn measured_table_has_no_duplicates() {
|
||||
for (i, a) in MEASURED.iter().enumerate() {
|
||||
for b in MEASURED.iter().skip(i + 1) {
|
||||
assert!((a.0, a.1, a.2) != (b.0, b.1, b.2), "duplicate row for button {}", a.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_devices_have_no_defaults() {
|
||||
assert!(defaults(0x046d, 0xc62a).is_none());
|
||||
assert!(defaults(0x256f, 0xc63a).is_none());
|
||||
assert!(defaults(0, 0).is_none());
|
||||
assert!(defaults(u16::MAX, u16::MAX).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn measured_table_stays_in_range() {
|
||||
for &(_, _, number, _) in MEASURED {
|
||||
assert!((1..=MAX_BUTTONS).contains(&number));
|
||||
}
|
||||
// Whatever the ids, lookups never panic.
|
||||
for v in [0, 1, 0x046d, u16::MAX] {
|
||||
for p in [0, 0xc62b, u16::MAX] {
|
||||
let _ = defaults(v, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,10 +10,12 @@
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod buttons;
|
||||
mod calibration;
|
||||
mod descriptor;
|
||||
|
||||
pub use calibration::{
|
||||
AxisCalibration, Calibration, HOLD_SECONDS, MAX_DEAD_ZONE_SHARE, MIN_SHARE, Problem, REST_SECONDS, Recording, STEPS, Stage, Step, Wizard,
|
||||
AxisCalibration, Calibration, FEATHER_SHARE, HOLD_SECONDS, Kind, MAX_DEAD_ZONE_SHARE, MIN_SHARE, NO_SOURCE, Problem, REST_SECONDS,
|
||||
RESTARTS_FOR_TRACKBALL, Recording, SETTLE_AFTER_SECONDS, SETTLE_SHARE, SETTLE_WANDER, STEPS, Settle, Stage, Step, Wizard,
|
||||
};
|
||||
pub use descriptor::{Descriptor, FULL_SCALE, Field, Layout, MAX_BUTTONS, Report};
|
||||
|
||||
@@ -23,6 +23,12 @@ thread_local! {
|
||||
static CAD_INPUT_BLOCKED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
|
||||
}
|
||||
|
||||
/// Tests set the barrier by hand to see what ignores it.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn set_cad_input_blocked_for_test(blocked: bool) {
|
||||
CAD_INPUT_BLOCKED.with(|cell| cell.set(blocked));
|
||||
}
|
||||
|
||||
pub(crate) fn cad_input_blocked() -> bool {
|
||||
CAD_INPUT_BLOCKED.with(std::cell::Cell::get)
|
||||
}
|
||||
|
||||
@@ -227,9 +227,12 @@ pub mod smart_extrude;
|
||||
mod smart_extrude_ui_tests;
|
||||
pub mod space_mouse;
|
||||
pub mod space_mouse_calibration;
|
||||
pub mod space_mouse_model;
|
||||
pub mod space_mouse_offer;
|
||||
pub mod stand_up;
|
||||
#[cfg(test)]
|
||||
mod sweep_dialog_tests;
|
||||
pub(crate) mod test_boat;
|
||||
pub mod theme;
|
||||
pub mod theme_files;
|
||||
#[cfg(test)]
|
||||
|
||||
+1519
-2
File diff suppressed because it is too large
Load Diff
@@ -167,172 +167,7 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
// The existing ring editor is part of the real product and remains available.
|
||||
crate::mouse_gesture::nav_rows(app, ui, t);
|
||||
});
|
||||
ui_kit::settings_section(ui, "devices", app.tr("prefs.devices"), |ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
setting::space_mouse_enabled(app, ui);
|
||||
let device = app.space_mouse.device.as_ref();
|
||||
let saved = device.is_some_and(|device| app.preferences.space_mouse_calibrations.contains_key(device));
|
||||
let status = match device {
|
||||
None => app.tr("ui.space_mouse_none").to_owned(),
|
||||
Some(device) => format!("{} · {device}", app.tr(if saved { "ui.space_mouse_calibrated" } else { "ui.space_mouse_not_calibrated" })),
|
||||
};
|
||||
ui_kit::settings_badge(ui, &status, !saved);
|
||||
});
|
||||
let mapping = app.space_mouse.mapping;
|
||||
let rows: Vec<(String, bool)> = (0..6)
|
||||
.map(|row| {
|
||||
let label = crate::space_mouse::row_label(mapping, row);
|
||||
let text = crate::i18n::Locale::for_language(&app.preferences.language).format(label, &[("n", &(row + 1).to_string())]);
|
||||
(text, label == "ui.space_mouse_view_off")
|
||||
})
|
||||
.collect();
|
||||
let driven_by = app.tr("ui.space_mouse_driven_by").to_owned();
|
||||
let off_tip = app.tr("ui.space_mouse_view_off_tip").to_owned();
|
||||
let motions: Vec<String> = crate::space_mouse::MOTIONS.iter().map(|motion| app.tr(motion).to_owned()).collect();
|
||||
let calibration = app.space_mouse.device.as_ref().and_then(|device| app.preferences.space_mouse_calibrations.get(device));
|
||||
let raw: [i16; 6] = app.space_mouse.raw.into();
|
||||
let travel = calibration.map(|calibration| calibration.travel(raw));
|
||||
let zones = calibration.map(|calibration| calibration.dead_zone_shares());
|
||||
ui_kit::settings_axis_header(ui);
|
||||
for (axis, (name, off)) in rows.iter().enumerate() {
|
||||
ui.push_id(("space_mouse_axis", axis), |ui| {
|
||||
let from = app.preferences.space_mouse_axis_source.get(axis).copied().unwrap_or(axis);
|
||||
let value = travel
|
||||
.and_then(|travel| travel.get(from).copied())
|
||||
.unwrap_or_else(|| f32::from(raw.get(from).copied().unwrap_or(0)) / f32::from(crate::space_mouse::FULL_SCALE));
|
||||
let zone = zones.and_then(|zones| zones.get(from).copied()).unwrap_or([20.0 / f32::from(crate::space_mouse::FULL_SCALE); 2]);
|
||||
let enabled = app.preferences.space_mouse_enabled;
|
||||
let preferences = &mut app.preferences;
|
||||
let source = &mut preferences.space_mouse_axis_source;
|
||||
let reverse = preferences.space_mouse_reverse.get_mut(axis);
|
||||
let speed = preferences.space_mouse_axis_speed.get_mut(axis);
|
||||
ui_kit::settings_axis_row(
|
||||
ui,
|
||||
name,
|
||||
value,
|
||||
zone,
|
||||
|ui| {
|
||||
ui.add_enabled_ui(enabled && !off, |ui| {
|
||||
if let Some(reverse) = reverse {
|
||||
ui_kit::checkbox(ui, crate::i18n::tr_label("Invert"), reverse);
|
||||
}
|
||||
});
|
||||
},
|
||||
|ui| {
|
||||
ui.add_enabled_ui(enabled && !off, |ui| {
|
||||
if let Some(speed) = speed {
|
||||
ui_kit::settings_axis_gain(ui, speed);
|
||||
}
|
||||
});
|
||||
},
|
||||
|ui| {
|
||||
ui.add_enabled_ui(enabled, |ui| {
|
||||
let current = source.get(axis).copied().unwrap_or(axis);
|
||||
let mut chosen = current;
|
||||
ui_kit::dropdown_menu(ui, ("sc_space_mouse_source", axis), motions.get(current).map_or("", String::as_str), |ui| {
|
||||
for (from, name) in motions.iter().enumerate() {
|
||||
if ui_kit::list_picker(ui, name, name, chosen == from).clicked() {
|
||||
chosen = from;
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_hover_text(if *off { &off_tip } else { &driven_by });
|
||||
if chosen != current {
|
||||
crate::space_mouse::reassign(source, axis, chosen);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
ui_kit::settings_form(ui, 100.0, |ui| {
|
||||
ui_kit::row(ui, app.tr("prefs.overall_speed"), |ui| {
|
||||
ui.add_enabled_ui(app.preferences.space_mouse_enabled, |ui| {
|
||||
ui_kit::slider(
|
||||
ui,
|
||||
&mut app.preferences.space_mouse_speed,
|
||||
0.25..=3.0,
|
||||
ui_kit::Slider { step: Some(0.05), format: |value| format!("{value:.2}×"), ..Default::default() },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
ui_kit::text(ui, app.tr("prefs.dead_zone_note"), ui_kit::Text::Hint("space_mouse_zone"));
|
||||
// Only where the reader has seen the choice: Linux, with spacenavd and the device both there.
|
||||
if let Some(present) = app.space_mouse.drivers.filter(|present| present.spacenavd || present.direct) {
|
||||
let [label, auto, direct, only_spacenavd, only_direct] =
|
||||
["driver", "driver_auto", "driver_direct", "driver_only_spacenavd", "driver_only_direct"]
|
||||
.map(|key| app.tr(&format!("ui.space_mouse_{key}")).to_owned());
|
||||
let driver = &mut app.preferences.space_mouse_driver;
|
||||
ui_kit::row(ui, &label, |ui| match (present.spacenavd, present.direct) {
|
||||
(true, true) => {
|
||||
let name = |driver| match driver {
|
||||
SpaceMouseDriver::Automatic => auto.as_str(),
|
||||
SpaceMouseDriver::Spacenavd => "spacenavd",
|
||||
SpaceMouseDriver::Direct => direct.as_str(),
|
||||
};
|
||||
ui_kit::dropdown_menu(ui, "sc_space_mouse_driver", name(*driver), |ui| {
|
||||
for choice in SpaceMouseDriver::ALL {
|
||||
if ui_kit::list_picker(ui, name(choice), name(choice), *driver == choice).clicked() {
|
||||
*driver = choice;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
(true, false) => {
|
||||
ui_kit::text(ui, &only_spacenavd, ui_kit::Text::Hint("space_mouse_driver"));
|
||||
}
|
||||
(false, _) => {
|
||||
ui_kit::text(ui, &only_direct, ui_kit::Text::Hint("space_mouse_driver"));
|
||||
}
|
||||
});
|
||||
}
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let device = app.space_mouse.device.clone();
|
||||
let saved = device.as_ref().is_some_and(|device| app.preferences.space_mouse_calibrations.contains_key(device));
|
||||
ui.add_enabled_ui(app.preferences.space_mouse_enabled && device.is_some(), |ui| {
|
||||
if ui_kit::button(ui, app.tr("ui.space_mouse_calibrate"), ui_kit::Button::Normal)
|
||||
.on_hover_text(app.tr("ui.space_mouse_calibrate_tip"))
|
||||
.clicked()
|
||||
{
|
||||
crate::space_mouse_calibration::start(app);
|
||||
}
|
||||
});
|
||||
ui.add_enabled_ui(app.preferences.space_mouse_enabled && saved, |ui| {
|
||||
if ui_kit::button(ui, app.tr("ui.space_mouse_forget"), ui_kit::Button::Ghost).clicked()
|
||||
&& let Some(device) = &device
|
||||
{
|
||||
app.preferences.space_mouse_calibrations.remove(device);
|
||||
}
|
||||
});
|
||||
let reassigned = app.preferences.space_mouse_axis_source != crate::space_mouse::SAME_AXES;
|
||||
ui.add_enabled_ui(app.preferences.space_mouse_enabled && reassigned, |ui| {
|
||||
if ui_kit::button(ui, app.tr("ui.space_mouse_reset_axes"), ui_kit::Button::Ghost).clicked() {
|
||||
app.preferences.space_mouse_axis_source = crate::space_mouse::SAME_AXES;
|
||||
}
|
||||
});
|
||||
});
|
||||
let enabled = app.preferences.space_mouse_enabled;
|
||||
let (label, tip) = (app.tr("ui.space_mouse_dead_zone").to_owned(), app.tr("ui.space_mouse_dead_zone_tip").to_owned());
|
||||
let (device, raw) = (app.space_mouse.device.clone(), app.space_mouse.raw.into());
|
||||
if let Some(calibration) = device.and_then(|device| app.preferences.space_mouse_calibrations.get_mut(&device)) {
|
||||
ui_kit::row(ui, &label, |ui| {
|
||||
ui.add_enabled_ui(enabled, |ui| {
|
||||
ui_kit::slider(
|
||||
ui,
|
||||
&mut calibration.dead_zone_share,
|
||||
0.0..=solidcraft_space_mouse::MAX_DEAD_ZONE_SHARE,
|
||||
ui_kit::Slider { step: Some(0.01), format: |v| format!("{:.0} %", v * 100.0), ..Default::default() },
|
||||
)
|
||||
.on_hover_text(tip);
|
||||
});
|
||||
});
|
||||
crate::space_mouse_calibration::dead_zone_pads(ui, calibration, raw);
|
||||
}
|
||||
ui_kit::text(
|
||||
ui,
|
||||
crate::i18n::tr_label("Move and rotate the view together; sketches use pan and zoom only."),
|
||||
ui_kit::Text::Hint("space_mouse"),
|
||||
);
|
||||
});
|
||||
// The 3D mouse is a section like the ones above: the driver, calibration, axes, picture and buttons
|
||||
// (`prefs::space_mouse_section`).
|
||||
space_mouse_section(app, ui);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,44 @@ pub struct Input {
|
||||
pub raw: Axes,
|
||||
/// The guided calibration while its window is open, and the device it is for.
|
||||
pub calibrating: Option<(String, solidcraft_space_mouse::Wizard)>,
|
||||
/// Drops readings that have stopped changing close to the dead zone.
|
||||
pub settle: solidcraft_space_mouse::Settle,
|
||||
/// The buttons down as of the last frame, one bit each (bit N for button N + 1).
|
||||
pub buttons: u32,
|
||||
/// Every button pressed since the app started, so Preferences can list the ones the device has.
|
||||
pub seen: u32,
|
||||
/// The modifiers the buttons hold down, as of the last frame.
|
||||
pub held: egui::Modifiers,
|
||||
/// The modifiers the keyboard holds, as the window last reported them (without `held`).
|
||||
pub keyboard: egui::Modifiers,
|
||||
/// The device `seen` is about, and the device and calibration `settle` is about.
|
||||
pub read_device: Option<String>,
|
||||
/// The device `buttons` are of, as the reader said with them.
|
||||
pub buttons_device: Option<String>,
|
||||
pub settled_for: Option<Calibration>,
|
||||
/// The device the calibration notice is up for and when it was shown or last pointed at
|
||||
/// (egui time), and the devices it went away from for this run ().
|
||||
pub offer_since: Option<(String, f64)>,
|
||||
pub offer_hidden: std::collections::BTreeSet<String>,
|
||||
/// When the offer was last looked at (egui time): a long gap is time away, not time shown.
|
||||
pub offer_seen: f64,
|
||||
/// The device last written to the log (one line per connect), `None` for none.
|
||||
pub logged_device: Option<String>,
|
||||
/// "Restore defaults" was clicked and waits for its answer.
|
||||
pub restore_pending: bool,
|
||||
/// The last look at the buttons had no state yet (`Buttons::pending`).
|
||||
pub buttons_pending: bool,
|
||||
/// The reader's connection the last look at the buttons came from (`Buttons::generation`).
|
||||
pub buttons_generation: u32,
|
||||
/// Nothing arrived since the last frame: the reading is the last value kept (spacenavd sends only on
|
||||
/// change, a HID device goes quiet), not a report or record just received, whatever its value. The
|
||||
/// calibration window needs the difference.
|
||||
pub retained: bool,
|
||||
/// The motion `navigate_frame` handed the view this frame (calibrated, reassigned, inverted and
|
||||
/// scaled), for the test model on the Preferences page to move with; none when the view got none.
|
||||
pub motion: Option<DeviceMotion>,
|
||||
/// The test model's view on that page, and when it was last drawn (`test_boat`).
|
||||
pub boat: Option<crate::test_boat::Boat>,
|
||||
/// The driver mapping navigation used last frame, which says what each axis does to the view.
|
||||
pub mapping: Option<DriverMapping>,
|
||||
/// Which of the Linux readers exist, as the app's reader last saw them; none where there is
|
||||
@@ -58,11 +96,226 @@ pub struct Input {
|
||||
pub drivers: Option<DriversPresent>,
|
||||
}
|
||||
|
||||
/// What the Linux reader found: the spacenavd socket and a direct (hidraw) 3D mouse.
|
||||
/// What the Linux reader found: the spacenavd socket and a direct (hidraw) 3D mouse, and whether that
|
||||
/// device was found but could not be opened (no permission), the last time it tried.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct DriversPresent {
|
||||
pub spacenavd: bool,
|
||||
pub direct: bool,
|
||||
pub denied: bool,
|
||||
}
|
||||
|
||||
/// What the reader saw of the buttons, taken in one look: the device they came from, those down
|
||||
/// now (bit N for button N + 1) and those that went down since the last look, even when they
|
||||
/// came up again before it. The taps are a set of buttons, not a queue of presses: two taps of one
|
||||
/// button between two looks reach the app as one, and run its job once.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Buttons {
|
||||
pub device: Option<String>,
|
||||
pub down: u32,
|
||||
pub taps: u32,
|
||||
/// The reader has not yet seen the state of this device's buttons (a HID device's first report that
|
||||
/// carries them is still to come), so `down` is not known yet. The first look after that is a
|
||||
/// baseline, not a press.
|
||||
pub pending: bool,
|
||||
/// Which connection of the reader these buttons come from; a different one than the last look means
|
||||
/// the reader connected again in between, so what is down now is held, not pressed.
|
||||
pub generation: u32,
|
||||
}
|
||||
|
||||
/// A key a 3D mouse button can stand in for.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ButtonKey {
|
||||
Esc,
|
||||
Shift,
|
||||
Ctrl,
|
||||
Alt,
|
||||
}
|
||||
|
||||
/// What a 3D mouse button does.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ButtonJob {
|
||||
/// Opens Preferences at Navigation, where the 3D mouse's settings are.
|
||||
Settings,
|
||||
/// A view as `SolveApp::animate_view` takes it: `fit`, `home` or a standard view (`top`, …).
|
||||
View(String),
|
||||
/// Stands in for a key: Esc when pressed, Shift, Ctrl and Alt for as long as it's held.
|
||||
Key(ButtonKey),
|
||||
/// Runs a command, as its keyboard shortcut would.
|
||||
Command(String),
|
||||
}
|
||||
|
||||
/// What each of `device`'s buttons does, by number from 1: what the user set, else for a device
|
||||
/// we have measured, its printed keys' jobs.
|
||||
pub fn button_jobs(app: &SolveApp, device: &str) -> std::collections::BTreeMap<u8, ButtonJob> {
|
||||
app.preferences.space_mouse_buttons.get(device).cloned().unwrap_or_else(|| default_button_jobs(device))
|
||||
}
|
||||
|
||||
/// The printed keys' jobs on a device we have measured (`solidcraft_space_mouse::buttons`), from
|
||||
/// its key (`hid 046d:c62b`, `web 046d:c62b`). Only a key with vendor and product ids gets them,
|
||||
/// which in practice is a raw HID device: the `spacenavd` and `3dxware` keys carry no ids, and
|
||||
/// spacenavd numbers its buttons from 0, not 1 as the table here does. Keys with no job here (Roll,
|
||||
/// the rotation lock) start empty.
|
||||
pub fn default_button_jobs(device: &str) -> std::collections::BTreeMap<u8, ButtonJob> {
|
||||
use solidcraft_space_mouse::buttons::{Action, defaults};
|
||||
let ids = device.split_once(' ').and_then(|(_, ids)| ids.split_once(':'));
|
||||
let table = ids.and_then(|(vendor, product)| Some((u16::from_str_radix(vendor, 16).ok()?, u16::from_str_radix(product, 16).ok()?)));
|
||||
let Some(table) = table.and_then(|(vendor, product)| defaults(vendor, product)) else { return Default::default() };
|
||||
table
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, action)| {
|
||||
let job = match (*action)? {
|
||||
Action::Menu => ButtonJob::Settings,
|
||||
Action::Fit => ButtonJob::View("fit".into()),
|
||||
Action::ViewTop => ButtonJob::View("top".into()),
|
||||
Action::ViewRight => ButtonJob::View("right".into()),
|
||||
Action::ViewFront => ButtonJob::View("front".into()),
|
||||
Action::Esc => ButtonJob::Key(ButtonKey::Esc),
|
||||
Action::Shift => ButtonJob::Key(ButtonKey::Shift),
|
||||
Action::Ctrl => ButtonJob::Key(ButtonKey::Ctrl),
|
||||
Action::Alt => ButtonJob::Key(ButtonKey::Alt),
|
||||
Action::Roll | Action::RotationLock => return None,
|
||||
};
|
||||
Some((u8::try_from(index + 1).ok()?, job))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Whether a button may do anything now: only where navigation and the keyboard shortcuts would
|
||||
/// work (`navigate_frame`, `toolbar::shortcuts`), so a press in the background, with a picker open
|
||||
/// or while a field takes the keyboard doesn't move the view or run a command.
|
||||
fn buttons_active(app: &SolveApp, ctx: &egui::Context, focused: bool) -> bool {
|
||||
app.preferences.space_mouse_enabled
|
||||
&& focused
|
||||
&& !app.prefs_window.open
|
||||
&& app.space_mouse.calibrating.is_none()
|
||||
&& !app.home.open
|
||||
&& !app.file_picker_busy()
|
||||
&& !app.file_load.busy()
|
||||
// The keyboard shortcuts wait for a report draft too (`SolveApp::logic`).
|
||||
&& app.issue_report.draft.is_none()
|
||||
&& !crate::keymap::capturing(app)
|
||||
&& !egui::Popup::is_any_open(ctx)
|
||||
&& !ctx.egui_wants_keyboard_input()
|
||||
&& !crate::dim_view::editing()
|
||||
&& !crate::dialogs_save_mesh::popup_open(ctx)
|
||||
&& crate::toolbar::design_shown(app)
|
||||
}
|
||||
|
||||
/// The 3D mouse's buttons this frame (`buttons`, bit N for button N + 1), before the app sees the
|
||||
/// frame's input: each button just pressed does its job, and those standing in for Shift, Ctrl or
|
||||
/// Alt hold them while down. Nothing runs where navigation wouldn't (`buttons_active`), so pressing
|
||||
/// a button in Preferences finds it in the list instead, and a press made then isn't run later.
|
||||
pub fn buttons_hook(app: &mut SolveApp, ctx: &egui::Context, seen: Buttons, raw: &mut egui::RawInput) {
|
||||
// The device the buttons came from, not the one the last frame ended with: a switch of device
|
||||
// (or a first one) starts the button history afresh at what is down now, so a button already down
|
||||
// when the device changes (held through a switch of reader) is not a press on the new device. A
|
||||
// press made on it is in `taps`, so it still runs the new device's own mapping.
|
||||
// The same for the first look at the buttons' state after a look that had none to give (the reader
|
||||
// had connected but the device had not yet reported them): what is down in it is held, not pressed.
|
||||
if app.space_mouse.buttons_device != seen.device
|
||||
|| app.space_mouse.buttons_generation != seen.generation
|
||||
|| (app.space_mouse.buttons_pending && !seen.pending)
|
||||
{
|
||||
app.space_mouse.buttons_device = seen.device.clone();
|
||||
app.space_mouse.buttons = seen.down;
|
||||
}
|
||||
app.space_mouse.buttons_pending = seen.pending;
|
||||
app.space_mouse.buttons_generation = seen.generation;
|
||||
let buttons = seen.down;
|
||||
let pressed = (buttons & !app.space_mouse.buttons) | seen.taps;
|
||||
app.space_mouse.buttons = buttons;
|
||||
app.space_mouse.seen |= buttons;
|
||||
// The keyboard's own modifiers, from what the window was told before anything is added to it.
|
||||
// egui drops its modifiers when the window loses focus, so those go too.
|
||||
let mut focused = ctx.input(|i| i.focused);
|
||||
for event in &raw.events {
|
||||
match event {
|
||||
egui::Event::ModifiersChanged(modifiers) => app.space_mouse.keyboard = *modifiers,
|
||||
egui::Event::WindowFocused(now) => {
|
||||
focused = *now;
|
||||
if !now {
|
||||
app.space_mouse.keyboard = egui::Modifiers::NONE;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let keyboard = app.space_mouse.keyboard;
|
||||
let mut held = egui::Modifiers::NONE;
|
||||
let device = seen.device.clone().filter(|_| buttons_active(app, ctx, focused));
|
||||
let jobs = device.map(|device| button_jobs(app, &device)).unwrap_or_default();
|
||||
let before = std::mem::replace(&mut app.space_mouse.held, egui::Modifiers::NONE);
|
||||
for (number, job) in jobs {
|
||||
let Some(bit) = number.checked_sub(1).and_then(|index| 1_u32.checked_shl(u32::from(index))) else { continue };
|
||||
if (buttons | seen.taps) & bit == 0 {
|
||||
continue;
|
||||
}
|
||||
match job {
|
||||
ButtonJob::Key(ButtonKey::Shift) if buttons & bit != 0 => held.shift = true,
|
||||
ButtonJob::Key(ButtonKey::Alt) if buttons & bit != 0 => held.alt = true,
|
||||
ButtonJob::Key(ButtonKey::Ctrl) if buttons & bit != 0 => {
|
||||
held.ctrl = true;
|
||||
// Ctrl is the command key everywhere but on a Mac.
|
||||
held.command |= !cfg!(target_os = "macos");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if pressed & bit != 0 {
|
||||
run_button_job(app, &job, keyboard, raw);
|
||||
}
|
||||
}
|
||||
app.space_mouse.held = held;
|
||||
let add = |modifiers: &mut egui::Modifiers| {
|
||||
modifiers.shift |= held.shift;
|
||||
modifiers.alt |= held.alt;
|
||||
modifiers.ctrl |= held.ctrl;
|
||||
modifiers.command |= held.command;
|
||||
};
|
||||
// egui keeps modifiers from frame to frame and takes new ones from `ModifiersChanged`: the
|
||||
// keyboard's get the held ones added, and a change in what's held is one of its own.
|
||||
let mut changed = false;
|
||||
for event in &mut raw.events {
|
||||
match event {
|
||||
egui::Event::ModifiersChanged(modifiers) => {
|
||||
add(modifiers);
|
||||
changed = true;
|
||||
}
|
||||
egui::Event::PointerButton { modifiers, .. } | egui::Event::Key { modifiers, .. } | egui::Event::MouseWheel { modifiers, .. } => {
|
||||
add(modifiers);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if !changed && held != before {
|
||||
let mut modifiers = keyboard;
|
||||
add(&mut modifiers);
|
||||
raw.events.insert(0, egui::Event::ModifiersChanged(modifiers));
|
||||
}
|
||||
}
|
||||
|
||||
// `modifiers` are the keyboard's own; the Esc event pushed here still gets the modifiers other buttons hold
|
||||
// down, because `buttons_hook` adds them to every key event of the frame after all the jobs have run.
|
||||
fn run_button_job(app: &mut SolveApp, job: &ButtonJob, modifiers: egui::Modifiers, raw: &mut egui::RawInput) {
|
||||
match job {
|
||||
// Navigation, where the 3D mouse's section is.
|
||||
ButtonJob::Settings => crate::prefs::open_at(app, crate::prefs::PAGE_NAVIGATION),
|
||||
ButtonJob::View(view) => app.animate_view(view),
|
||||
ButtonJob::Key(ButtonKey::Esc) => {
|
||||
for pressed in [true, false] {
|
||||
raw.events.push(egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed, repeat: false, modifiers });
|
||||
}
|
||||
}
|
||||
ButtonJob::Key(_) => {}
|
||||
ButtonJob::Command(id) => {
|
||||
if solidcraft_engine::find_command(id).is_some_and(|c| c.info(&app.session).enabled) {
|
||||
app.start(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Seconds after the cap last moved that wheel events are still taken for 3DxWare's: one can be
|
||||
@@ -189,22 +442,22 @@ pub fn calibrated(axes: Axes, calibration: Option<Calibration>) -> Axes {
|
||||
/// by. They name no kind of device, since calibration defined them: Translation X is whatever the
|
||||
/// user did for "slide right".
|
||||
pub const MOTIONS: [&str; 6] = [
|
||||
"ui.space_mouse_motion_translation_x",
|
||||
"ui.space_mouse_motion_translation_y",
|
||||
"ui.space_mouse_motion_translation_z",
|
||||
"ui.space_mouse_motion_rotation_x",
|
||||
"ui.space_mouse_motion_rotation_y",
|
||||
"ui.space_mouse_motion_rotation_z",
|
||||
"ui.3d_mouse_motion_translation_x",
|
||||
"ui.3d_mouse_motion_translation_y",
|
||||
"ui.3d_mouse_motion_translation_z",
|
||||
"ui.3d_mouse_motion_rotation_x",
|
||||
"ui.3d_mouse_motion_rotation_y",
|
||||
"ui.3d_mouse_motion_rotation_z",
|
||||
];
|
||||
|
||||
/// What each logical axis does to the view, as `navigation::device_camera` applies it.
|
||||
const VIEW_AXES: [&str; 6] = [
|
||||
"ui.space_mouse_view_pan_sideways",
|
||||
"ui.space_mouse_view_zoom",
|
||||
"ui.space_mouse_view_pan_up_down",
|
||||
"ui.space_mouse_view_orbit_up_down",
|
||||
"ui.space_mouse_view_orbit_sideways",
|
||||
"ui.space_mouse_view_roll",
|
||||
"ui.3d_mouse_view_pan_sideways",
|
||||
"ui.3d_mouse_view_zoom",
|
||||
"ui.3d_mouse_view_pan_up_down",
|
||||
"ui.3d_mouse_view_orbit_up_down",
|
||||
"ui.3d_mouse_view_orbit_sideways",
|
||||
"ui.3d_mouse_view_roll",
|
||||
];
|
||||
|
||||
/// The label of axis row `row`: what it does to the view through `mapping`, the mapping
|
||||
@@ -214,12 +467,32 @@ const VIEW_AXES: [&str; 6] = [
|
||||
/// Two rows a driver profile sends to the same output both get its label; that's what they do.
|
||||
pub fn row_label(mapping: Option<DriverMapping>, row: usize) -> &'static str {
|
||||
match mapping.and_then(|mapping| mapping.axes.get(row).copied()) {
|
||||
Some(rule) if rule.enabled => VIEW_AXES.get(rule.output).copied().unwrap_or("ui.space_mouse_view_off"),
|
||||
Some(_) => "ui.space_mouse_view_off",
|
||||
None => "ui.space_mouse_view_unmapped",
|
||||
Some(rule) if rule.enabled => VIEW_AXES.get(rule.output).copied().unwrap_or("ui.3d_mouse_view_off"),
|
||||
Some(_) => "ui.3d_mouse_view_off",
|
||||
None => "ui.3d_mouse_view_unmapped",
|
||||
}
|
||||
}
|
||||
|
||||
/// What the view does with calibrated motion `motion` under the axis assignment `source`: the label
|
||||
/// of the row that reads it, the same label the rows show (and so the same after an invert or a
|
||||
/// reassignment). `None` for a motion no row reads (an assignment that is not one motion per row).
|
||||
pub fn motion_story(mapping: Option<DriverMapping>, source: [usize; 6], motion: usize) -> Option<&'static str> {
|
||||
source.iter().position(|&s| s == motion).map(|row| row_label(mapping, row))
|
||||
}
|
||||
|
||||
/// What the circles' tooltips say each calibrated motion does, in `language`: `motion_story`, worded
|
||||
/// as the rows are (a motion nothing reads says nothing). It names the view axis the motion drives,
|
||||
/// not a direction, so an inverted axis reads the same; the page says the circles are before invert.
|
||||
pub(crate) fn stories(language: &str, mapping: Option<DriverMapping>, source: [usize; 6]) -> [String; 6] {
|
||||
std::array::from_fn(|motion| {
|
||||
let row = source.iter().position(|&s| s == motion);
|
||||
motion_story(mapping, source, motion)
|
||||
.zip(row)
|
||||
.map(|(label, row)| crate::i18n::Locale::for_language(language).format(label, &[("n", &(row + 1).to_string())]))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Which of the cap's motions drives each of its axes (`Prefs::space_mouse_axis_source`): row
|
||||
/// `r` reads motion `source[r]`. Each motion drives exactly one row.
|
||||
pub const SAME_AXES: [usize; 6] = [0, 1, 2, 3, 4, 5];
|
||||
@@ -248,6 +521,18 @@ pub fn drives_each_once(source: [usize; 6]) -> bool {
|
||||
source.iter().all(|&from| seen.get_mut(from).is_some_and(|seen| !std::mem::replace(seen, true)))
|
||||
}
|
||||
|
||||
/// How long the calibration window waits before the next frame: 30 a second while the reading
|
||||
/// changes, 10 while it holds still (the wizard goes by elapsed time, so a still reading needs only
|
||||
/// enough frames for its timers), and 4 while the window is not in use. Every frame redraws the 3D
|
||||
/// view behind the window as well, so this is most of what the window costs.
|
||||
pub(crate) fn calibration_repaint_after(reading_changed: bool, focused: bool) -> Duration {
|
||||
Duration::from_millis(match (focused, reading_changed) {
|
||||
(false, _) => 250,
|
||||
(true, true) => 33,
|
||||
(true, false) => 100,
|
||||
})
|
||||
}
|
||||
|
||||
/// Hands one frame of 3D mouse input, `seconds` after the previous frame, to the page showing
|
||||
/// the camera. Nothing happens with navigation switched off, on the start page or while a file
|
||||
/// picker is open, or while the reading is inside the dead zone (after calibration, which takes
|
||||
@@ -256,20 +541,42 @@ pub fn drives_each_once(source: [usize; 6]) -> bool {
|
||||
/// reading goes through the device's saved calibration first, when it has one; while the
|
||||
/// calibration window is open, the reading goes to it instead and the view stays put.
|
||||
pub fn navigate_frame(app: &mut SolveApp, ctx: &egui::Context, mapping: DriverMapping, axes: Axes, seconds: f64) -> bool {
|
||||
let changed = app.space_mouse.raw != axes;
|
||||
app.space_mouse.raw = axes;
|
||||
app.space_mouse.motion = None;
|
||||
app.space_mouse.mapping = Some(mapping);
|
||||
if app.space_mouse.read_device != app.space_mouse.device {
|
||||
// Another device: its buttons and its resting hand are not the last one's.
|
||||
app.space_mouse.read_device = app.space_mouse.device.clone();
|
||||
app.space_mouse.seen = 0;
|
||||
app.space_mouse.settle = Default::default();
|
||||
}
|
||||
let calibration = calibration(app);
|
||||
if app.space_mouse.settled_for != calibration {
|
||||
app.space_mouse.settled_for = calibration;
|
||||
app.space_mouse.settle = Default::default();
|
||||
}
|
||||
if let Some((device, wizard)) = &mut app.space_mouse.calibrating {
|
||||
if app.space_mouse.device.as_ref() == Some(device) {
|
||||
wizard.feed(axes.into(), seconds);
|
||||
ctx.request_repaint_after(Duration::from_millis(16));
|
||||
wizard.feed_reading(axes.into(), seconds, !app.space_mouse.retained);
|
||||
ctx.request_repaint_after(calibration_repaint_after(changed, ctx.input(|i| i.focused)));
|
||||
return false;
|
||||
}
|
||||
// Another device is being read now, or none: what was recorded isn't its.
|
||||
app.space_mouse.calibrating = None;
|
||||
}
|
||||
// Calibrated first, so the calibration puts the device's axes in order, then the user's choice
|
||||
// of which motion drives which axis. Invert and speed then go with the axis, not the motion.
|
||||
let axes = reassigned(calibrated(axes, calibration(app)), app.preferences.space_mouse_axis_source);
|
||||
let axes = calibrated(axes, calibration);
|
||||
// A hand leaning on a cap, holding it just past the dead zone, doesn't creep the view. A
|
||||
// trackball's slow roll is steady and meant, and a device without a calibration is read as it reports.
|
||||
let axes: Axes = match calibration {
|
||||
Some(calibration) if calibration.kind == solidcraft_space_mouse::Kind::ReturnToCenter => {
|
||||
app.space_mouse.settle.filter(axes.into(), seconds).into()
|
||||
}
|
||||
_ => axes,
|
||||
};
|
||||
// Then the user's choice of which motion drives which axis (invert and speed then go with the
|
||||
// axis, not the motion). After the settle: that works on the device's own calibrated axes.
|
||||
let axes = reassigned(axes, app.preferences.space_mouse_axis_source);
|
||||
// Only the window with focus: with two windows open, the cap moves the one being used, not both.
|
||||
if !app.preferences.space_mouse_enabled || app.home.open || app.file_picker_busy() || !axes.moving() || !ctx.input(|i| i.focused) {
|
||||
return false;
|
||||
@@ -281,10 +588,12 @@ pub fn navigate_frame(app: &mut SolveApp, ctx: &egui::Context, mapping: DriverMa
|
||||
app.begin_external_navigation();
|
||||
// A long gap (the first frame after a pause) moves as far as a 50 ms one.
|
||||
let step = if seconds.is_finite() { seconds.min(0.05) } else { 0.0 };
|
||||
app.viewport.device_motion = Some(DeviceMotion {
|
||||
let handed = DeviceMotion {
|
||||
motion: mapping.with_user_axes(app.preferences.space_mouse_reverse, app.preferences.space_mouse_axis_speed).apply(axes),
|
||||
seconds: step * speed.clamp(0.1, 5.0),
|
||||
});
|
||||
};
|
||||
app.space_mouse.motion = Some(handed);
|
||||
app.viewport.device_motion = Some(handed);
|
||||
ctx.request_repaint_after(Duration::from_millis(16));
|
||||
true
|
||||
}
|
||||
@@ -326,6 +635,32 @@ mod tests {
|
||||
handed
|
||||
}
|
||||
|
||||
/// The calibration window is told whether a reading is a report the device still sends or a value a
|
||||
/// daemon keeps: a ball that slows to a lower speed and keeps sending it is rolling (fresh), while the
|
||||
/// same readings as a kept value end the step.
|
||||
#[test]
|
||||
fn the_calibration_window_is_told_whether_the_reading_is_fresh() {
|
||||
use solidcraft_space_mouse::{Kind, Stage, Wizard};
|
||||
for (retained, step_done) in [(false, false), (true, true)] {
|
||||
let mut app = app();
|
||||
app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
app.space_mouse.retained = retained;
|
||||
app.space_mouse.calibrating = Some(("hid 046d:c62b".into(), Wizard::new(Kind::Trackball)));
|
||||
let ctx = egui::Context::default();
|
||||
ctx.run_ui(egui::RawInput::default(), |_| {}).textures_delta.clear();
|
||||
let mut feed = |axes: Axes, frames: usize| {
|
||||
for _ in 0..frames {
|
||||
frame(&mut app, &ctx, DriverMapping::default(), axes, 0.02);
|
||||
}
|
||||
};
|
||||
feed(Axes::default(), 400);
|
||||
feed(Axes { translation: [40, 3, 0], rotation: [0; 3] }, 30);
|
||||
feed(Axes { translation: [10, 0, 0], rotation: [0; 3] }, 35);
|
||||
let stage = app.space_mouse.calibrating.as_ref().map(|(_, wizard)| wizard.stage());
|
||||
assert_eq!(stage == Some(Stage::Step(1)), step_done, "retained {retained}: {stage:?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn raw(axes: Axes) -> Motion {
|
||||
DriverMapping::default().apply(axes)
|
||||
}
|
||||
@@ -336,6 +671,325 @@ mod tests {
|
||||
app
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_calibration_window_repaints_fast_only_while_the_reading_moves() {
|
||||
use std::time::Duration;
|
||||
assert_eq!(calibration_repaint_after(true, true), Duration::from_millis(33));
|
||||
assert_eq!(calibration_repaint_after(false, true), Duration::from_millis(100), "a still reading: 10 frames a second");
|
||||
assert_eq!(calibration_repaint_after(true, false), Duration::from_millis(250));
|
||||
assert_eq!(calibration_repaint_after(false, false), Duration::from_millis(250));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_measured_device_starts_with_its_printed_keys() {
|
||||
let jobs = default_button_jobs("hid 046d:c62b");
|
||||
assert_eq!(jobs.get(&1), Some(&ButtonJob::Settings));
|
||||
assert_eq!(jobs.get(&3), Some(&ButtonJob::View("top".into())));
|
||||
assert_eq!(jobs.get(&25), Some(&ButtonJob::Key(ButtonKey::Shift)));
|
||||
// Roll and the rotation lock have nothing to do here yet; the numbered keys start empty.
|
||||
assert_eq!((jobs.get(&9), jobs.get(&27), jobs.get(&13)), (None, None, None));
|
||||
// Unmeasured, spacenavd (its own numbers) and nonsense keys start empty.
|
||||
for device in ["hid 256f:c63a", "spacenavd", "web 046d", "hid zz:c62b", ""] {
|
||||
assert!(default_button_jobs(device).is_empty(), "{device}");
|
||||
}
|
||||
}
|
||||
|
||||
/// One frame of the window: the hook sees the frame's input, then egui runs it, as the app does.
|
||||
fn press(app: &mut SolveApp, ctx: &egui::Context, buttons: u32, events: Vec<egui::Event>) -> egui::RawInput {
|
||||
let mut raw = egui::RawInput { focused: true, events, ..Default::default() };
|
||||
let device = app.space_mouse.device.clone();
|
||||
// As the reader reports them: a button newly down since its own last look is a tap, and a reader
|
||||
// that has just connected to this device starts from nothing down.
|
||||
let before = if app.space_mouse.buttons_device == device { app.space_mouse.buttons } else { 0 };
|
||||
buttons_hook(app, ctx, Buttons { device, down: buttons, taps: buttons & !before, pending: false, generation: 0 }, &mut raw);
|
||||
ctx.run_ui(raw.clone(), |_| {}).textures_delta.clear();
|
||||
raw
|
||||
}
|
||||
|
||||
fn escapes(raw: &egui::RawInput) -> usize {
|
||||
raw.events.iter().filter(|e| matches!(e, egui::Event::Key { key: egui::Key::Escape, pressed: true, .. })).count()
|
||||
}
|
||||
|
||||
/// One frame with the reader's own snapshot (`taps` included).
|
||||
fn press_seen(app: &mut SolveApp, ctx: &egui::Context, seen: Buttons) -> egui::RawInput {
|
||||
let mut raw = egui::RawInput { focused: true, ..Default::default() };
|
||||
buttons_hook(app, ctx, seen, &mut raw);
|
||||
ctx.run_ui(raw.clone(), |_| {}).textures_delta.clear();
|
||||
raw
|
||||
}
|
||||
|
||||
const ESC: u32 = 1 << 22;
|
||||
const SHIFT: u32 = 1 << 24;
|
||||
|
||||
#[test]
|
||||
fn a_button_does_its_job_once_per_press_and_holds_its_modifier() {
|
||||
let mut app = app();
|
||||
app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
let ctx = window(true);
|
||||
let shifted = || ctx.input(|i| i.modifiers.shift);
|
||||
// Esc (button 23) presses Escape once, not again while it's held.
|
||||
assert_eq!(escapes(&press(&mut app, &ctx, ESC, vec![])), 1);
|
||||
assert_eq!(escapes(&press(&mut app, &ctx, ESC, vec![])), 0);
|
||||
// Shift (button 25) holds Shift for as long as it's down.
|
||||
press(&mut app, &ctx, SHIFT, vec![]);
|
||||
assert!(shifted());
|
||||
press(&mut app, &ctx, SHIFT, vec![]);
|
||||
assert!(shifted());
|
||||
press(&mut app, &ctx, 0, vec![]);
|
||||
assert!(!shifted());
|
||||
// Menu (button 1) opens Preferences; there, buttons only light up their rows.
|
||||
press(&mut app, &ctx, 1, vec![]);
|
||||
assert!(app.prefs_window.open);
|
||||
press(&mut app, &ctx, 0, vec![]);
|
||||
assert_eq!(escapes(&press(&mut app, &ctx, ESC, vec![])), 0);
|
||||
assert_eq!(app.space_mouse.seen & ESC, ESC);
|
||||
// A job the user set replaces the defaults.
|
||||
app.prefs_window.open = false;
|
||||
press(&mut app, &ctx, 0, vec![]);
|
||||
app.preferences.space_mouse_buttons.insert("hid 046d:c62b".into(), [(23, ButtonJob::Key(ButtonKey::Alt))].into());
|
||||
let raw = press(&mut app, &ctx, ESC, vec![]);
|
||||
assert_eq!((escapes(&raw), ctx.input(|i| i.modifiers.alt)), (0, true));
|
||||
}
|
||||
|
||||
/// The keyboard and a button hold the same modifier: it stays down until both let go, whichever
|
||||
/// lets go first, for each of Shift, Ctrl and Alt.
|
||||
#[test]
|
||||
fn mapped_buttons_do_nothing_behind_an_issue_report_draft() {
|
||||
let device = "hid 046d:c62b";
|
||||
let mut app = app();
|
||||
app.space_mouse.device = Some(device.into());
|
||||
app.preferences.space_mouse_buttons.insert(
|
||||
device.into(),
|
||||
[
|
||||
(1, ButtonJob::Settings),
|
||||
(2, ButtonJob::Key(ButtonKey::Esc)),
|
||||
(3, ButtonJob::Key(ButtonKey::Shift)),
|
||||
(4, ButtonJob::View("top".into())),
|
||||
(5, ButtonJob::Command("edit.undo".into())),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
let ctx = window(true);
|
||||
// A document change that Undo would take back.
|
||||
app.run("document.metadata.set", serde_json::json!({"values": {"author": "Jane"}})).unwrap();
|
||||
app.issue_report.draft = Some(crate::issue_report::draft_for(&app, None, None));
|
||||
let (cam, undo, dialog) = (app.cam, app.session.undo.len(), app.dialog.is_some());
|
||||
assert!(undo > 0 && app.session.doc.metadata.author() == Some("Jane"));
|
||||
let raw = press(&mut app, &ctx, 0b11111, vec![]);
|
||||
assert_eq!(app.session.doc.metadata.author(), Some("Jane"), "the mapped Undo did not run");
|
||||
assert_eq!(escapes(&raw), 0, "no Esc");
|
||||
assert!(!app.prefs_window.open, "no Preferences");
|
||||
assert!(!ctx.input(|i| i.modifiers.shift), "no held Shift");
|
||||
assert_eq!((app.cam, app.session.undo.len(), app.dialog.is_some()), (cam, undo, dialog));
|
||||
// Once the draft is closed the same buttons work again (a fresh press).
|
||||
app.issue_report.draft = None;
|
||||
press(&mut app, &ctx, 0, vec![]);
|
||||
press(&mut app, &ctx, 0b11111, vec![]);
|
||||
assert!(app.prefs_window.open);
|
||||
assert_eq!(app.session.doc.metadata.author(), None, "and the mapped Undo runs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_esc_button_pressed_while_a_shift_button_is_held_goes_out_shifted() {
|
||||
let device = "hid 046d:c62b";
|
||||
let mut app = app();
|
||||
app.space_mouse.device = Some(device.into());
|
||||
app.preferences
|
||||
.space_mouse_buttons
|
||||
.insert(device.into(), [(1, ButtonJob::Key(ButtonKey::Shift)), (2, ButtonJob::Key(ButtonKey::Esc))].into());
|
||||
let ctx = window(true);
|
||||
press(&mut app, &ctx, 0b01, vec![]);
|
||||
let raw = press(&mut app, &ctx, 0b11, vec![]);
|
||||
let shifted =
|
||||
raw.events.iter().any(|e| matches!(e, egui::Event::Key { key: egui::Key::Escape, pressed: true, modifiers, .. } if modifiers.shift));
|
||||
assert!(shifted, "{:?}", raw.events);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tap_between_two_frames_runs_its_job_once() {
|
||||
let mut app = app();
|
||||
app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
let ctx = window(true);
|
||||
// Down and up before the hook looked: nothing is held, the press is in `taps`.
|
||||
let tap = Buttons { device: Some("hid 046d:c62b".into()), down: 0, taps: ESC, pending: false, generation: 0 };
|
||||
assert_eq!(escapes(&press_seen(&mut app, &ctx, tap)), 1);
|
||||
assert_eq!(app.space_mouse.buttons, 0);
|
||||
let nothing = Buttons { device: Some("hid 046d:c62b".into()), down: 0, taps: 0, pending: false, generation: 0 };
|
||||
assert_eq!(escapes(&press_seen(&mut app, &ctx, nothing)), 0, "and not again");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_press_on_a_new_device_runs_that_devices_mapping_whatever_the_frame_before_had() {
|
||||
let (a, b) = ("hid 046d:c62b", "hid 256f:c63a");
|
||||
let device = |name: Option<&str>, down: u32| Buttons { device: name.map(Into::into), down, taps: 0, pending: false, generation: 0 };
|
||||
let setup = || {
|
||||
let mut app = app();
|
||||
app.preferences.space_mouse_buttons.insert(a.into(), [(1, ButtonJob::Key(ButtonKey::Shift))].into());
|
||||
app.preferences.space_mouse_buttons.insert(b.into(), [(1, ButtonJob::Key(ButtonKey::Esc))].into());
|
||||
(app, window(true))
|
||||
};
|
||||
// A -> B with button 1 still down from before: A's Shift lets go, and the held button is not a
|
||||
// press on B. A fresh press on B (it arrives as a tap) runs B's Esc once.
|
||||
let (mut app, ctx) = setup();
|
||||
app.space_mouse.device = Some(a.into());
|
||||
press_seen(&mut app, &ctx, device(Some(a), 1));
|
||||
assert!(app.space_mouse.held.shift);
|
||||
assert_eq!(escapes(&press_seen(&mut app, &ctx, device(Some(b), 1))), 0, "held through the switch is not a press");
|
||||
assert!(!app.space_mouse.held.shift, "A's hold does not carry over");
|
||||
assert_eq!(escapes(&press_seen(&mut app, &ctx, device(Some(b), 1))), 0, "and holding is not pressing");
|
||||
press_seen(&mut app, &ctx, device(Some(b), 0));
|
||||
let tap = Buttons { device: Some(b.into()), down: 1, taps: 1, pending: false, generation: 0 };
|
||||
assert_eq!(escapes(&press_seen(&mut app, &ctx, tap.clone())), 1, "a press on B runs B's job");
|
||||
// None -> B: the first press is not swallowed by there having been no device (it is a tap).
|
||||
let (mut app, ctx) = setup();
|
||||
app.space_mouse.device = None;
|
||||
press_seen(&mut app, &ctx, device(None, 0));
|
||||
assert_eq!(escapes(&press_seen(&mut app, &ctx, tap)), 1);
|
||||
// The device appears (reader connected, no state yet), a frame is drawn, and only then the first
|
||||
// report shows button 1 still down: held, not pressed.
|
||||
let (mut app, ctx) = setup();
|
||||
app.space_mouse.device = None;
|
||||
press_seen(&mut app, &ctx, device(None, 0));
|
||||
let waiting = Buttons { device: Some(b.into()), down: 0, taps: 0, pending: true, generation: 0 };
|
||||
assert_eq!(escapes(&press_seen(&mut app, &ctx, waiting)), 0);
|
||||
assert_eq!(escapes(&press_seen(&mut app, &ctx, device(Some(b), 1))), 0, "the first state is a baseline");
|
||||
press_seen(&mut app, &ctx, device(Some(b), 0));
|
||||
assert_eq!(escapes(&press_seen(&mut app, &ctx, Buttons { device: Some(b.into()), down: 1, taps: 1, pending: false, generation: 0 })), 1);
|
||||
// And a button already down when the first device appears is not a press.
|
||||
let (mut app, ctx) = setup();
|
||||
press_seen(&mut app, &ctx, device(None, 0));
|
||||
assert_eq!(escapes(&press_seen(&mut app, &ctx, device(Some(b), 1))), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_modifier_the_keyboard_and_a_button_share_stays_down_until_both_let_go() {
|
||||
for key in [ButtonKey::Shift, ButtonKey::Ctrl, ButtonKey::Alt] {
|
||||
let down = |ctx: &egui::Context| {
|
||||
let m = ctx.input(|i| i.modifiers);
|
||||
match key {
|
||||
ButtonKey::Shift => m.shift,
|
||||
ButtonKey::Ctrl => m.ctrl,
|
||||
_ => m.alt,
|
||||
}
|
||||
};
|
||||
let keyboard = |on: bool| {
|
||||
let mut m = egui::Modifiers::NONE;
|
||||
match key {
|
||||
ButtonKey::Shift => m.shift = on,
|
||||
ButtonKey::Ctrl => m.ctrl = on,
|
||||
_ => m.alt = on,
|
||||
}
|
||||
vec![egui::Event::ModifiersChanged(m)]
|
||||
};
|
||||
for button_first in [true, false] {
|
||||
let mut app = app();
|
||||
app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
app.preferences.space_mouse_buttons.insert("hid 046d:c62b".into(), [(1, ButtonJob::Key(key))].into());
|
||||
let ctx = window(true);
|
||||
press(&mut app, &ctx, 0, keyboard(true));
|
||||
press(&mut app, &ctx, 1, vec![]);
|
||||
assert!(down(&ctx), "{key:?} both");
|
||||
if button_first {
|
||||
press(&mut app, &ctx, 0, vec![]);
|
||||
assert!(down(&ctx), "{key:?} the keyboard still holds it");
|
||||
press(&mut app, &ctx, 0, keyboard(false));
|
||||
} else {
|
||||
press(&mut app, &ctx, 1, keyboard(false));
|
||||
assert!(down(&ctx), "{key:?} the button still holds it");
|
||||
press(&mut app, &ctx, 0, vec![]);
|
||||
}
|
||||
assert!(!down(&ctx), "{key:?} neither");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buttons_do_nothing_where_navigation_would_not() {
|
||||
let mut app = app();
|
||||
app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
app.preferences
|
||||
.space_mouse_buttons
|
||||
.insert("hid 046d:c62b".into(), [(1, ButtonJob::Key(ButtonKey::Esc)), (2, ButtonJob::Key(ButtonKey::Shift))].into());
|
||||
let (focused, unfocused) = (window(true), window(false));
|
||||
// A press while the window is in the background does nothing, and isn't run on refocus.
|
||||
assert_eq!(escapes(&press(&mut app, &unfocused, 1, vec![])), 0);
|
||||
assert_eq!(escapes(&press(&mut app, &focused, 1, vec![])), 0, "still held: not a new press");
|
||||
press(&mut app, &focused, 0, vec![]);
|
||||
assert_eq!(escapes(&press(&mut app, &focused, 1, vec![])), 1, "a press that is its own");
|
||||
// A held modifier button lets go in the background, and takes hold again on refocus.
|
||||
press(&mut app, &focused, 2, vec![]);
|
||||
assert!(focused.input(|i| i.modifiers.shift));
|
||||
press(&mut app, &unfocused, 2, vec![]);
|
||||
assert!(!unfocused.input(|i| i.modifiers.shift));
|
||||
// A file picker being open is no place for one either.
|
||||
press(&mut app, &focused, 0, vec![]);
|
||||
let slot: crate::picker_test_support::Slot = Default::default();
|
||||
crate::picker_test_support::open(&mut app, &slot);
|
||||
app.pick_open(|_, _| {});
|
||||
assert_eq!(escapes(&press(&mut app, &focused, 1, vec![])), 0);
|
||||
app.file_picker = None;
|
||||
press(&mut app, &focused, 0, vec![]);
|
||||
assert_eq!(escapes(&press(&mut app, &focused, 1, vec![])), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferences_and_calibration_let_go_of_a_held_modifier_button() {
|
||||
let mut app = app();
|
||||
app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
let ctx = window(true);
|
||||
press(&mut app, &ctx, SHIFT, vec![]);
|
||||
assert!(ctx.input(|i| i.modifiers.shift));
|
||||
app.prefs_window.open = true;
|
||||
press(&mut app, &ctx, SHIFT, vec![]);
|
||||
assert!(!ctx.input(|i| i.modifiers.shift), "Preferences open");
|
||||
app.prefs_window.open = false;
|
||||
press(&mut app, &ctx, SHIFT, vec![]);
|
||||
assert!(ctx.input(|i| i.modifiers.shift));
|
||||
crate::space_mouse_calibration::start(&mut app);
|
||||
press(&mut app, &ctx, SHIFT, vec![]);
|
||||
assert!(!ctx.input(|i| i.modifiers.shift), "calibrating");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn losing_focus_clears_what_the_buttons_hold_and_the_keyboard_is_remembered_again_on_regain() {
|
||||
let mut app = app();
|
||||
app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
let ctx = window(true);
|
||||
press(&mut app, &ctx, SHIFT, vec![]);
|
||||
assert!(app.space_mouse.held.shift);
|
||||
press(&mut app, &ctx, SHIFT, vec![egui::Event::WindowFocused(false)]);
|
||||
assert_eq!(app.space_mouse.held, egui::Modifiers::NONE);
|
||||
assert!(!ctx.input(|i| i.modifiers.shift));
|
||||
// Back, the button still down: it holds Shift again, and a keyboard Shift lost with the
|
||||
// focus stays lost until the window says it is down.
|
||||
press(&mut app, &ctx, SHIFT, vec![egui::Event::WindowFocused(true)]);
|
||||
assert!(ctx.input(|i| i.modifiers.shift));
|
||||
assert_eq!(escapes(&press(&mut app, &ctx, SHIFT, vec![])), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wheel_events_carry_the_held_modifiers() {
|
||||
let mut app = app();
|
||||
app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
let ctx = window(true);
|
||||
let raw = press(&mut app, &ctx, SHIFT, vec![wheel(1.0)]);
|
||||
assert!(raw.events.iter().any(|e| matches!(e, egui::Event::MouseWheel { modifiers, .. } if modifiers.shift)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn another_device_starts_with_no_buttons_seen_and_no_settling() {
|
||||
let mut app = app();
|
||||
let ctx = window(true);
|
||||
app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
frame(&mut app, &ctx, DriverMapping::default(), Axes::default(), 0.016);
|
||||
app.space_mouse.seen = 0b101;
|
||||
frame(&mut app, &ctx, DriverMapping::default(), Axes::default(), 0.016);
|
||||
assert_eq!(app.space_mouse.seen, 0b101);
|
||||
app.space_mouse.device = Some("hid 256f:c63a".into());
|
||||
frame(&mut app, &ctx, DriverMapping::default(), Axes::default(), 0.016);
|
||||
assert_eq!(app.space_mouse.seen, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn per_axis_choices_flip_and_scale_one_physical_axis() {
|
||||
let mapping = DriverMapping::default().with_user_axes([false, true, false, false, false, false], [1.0, 1.0, 1.0, 0.5, f32::NAN, 1.0]);
|
||||
@@ -487,6 +1141,45 @@ mod tests {
|
||||
assert_eq!(DriverMapping::object_mode().apply(all), DriverMapping::default().apply(all));
|
||||
}
|
||||
|
||||
/// A steady lean of 60 on X for two seconds, 20 ms a frame: whether the cap still moves the view at the end.
|
||||
fn still_moving_after_a_steady_lean(calibration: Option<Calibration>) -> bool {
|
||||
let mut app = app();
|
||||
app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
if let Some(calibration) = calibration {
|
||||
app.preferences.space_mouse_calibrations.insert("hid 046d:c62b".into(), calibration);
|
||||
}
|
||||
let ctx = window(true);
|
||||
let lean = Axes { translation: [60, 0, 0], rotation: [0; 3] };
|
||||
(0..100).map(|_| frame(&mut app, &ctx, DriverMapping::default(), lean, 0.02)).last().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_a_calibrated_cap_settles() {
|
||||
let axes = std::array::from_fn(|source| AxisCalibration { source, reversed: false, rest: 0, dead_zone: 0, full: [350; 2] });
|
||||
let calibration = |kind| Calibration { dead_zone_share: 0.0, kind, axes };
|
||||
assert!(!still_moving_after_a_steady_lean(Some(calibration(solidcraft_space_mouse::Kind::ReturnToCenter))), "a leaning hand on a cap");
|
||||
assert!(still_moving_after_a_steady_lean(Some(calibration(solidcraft_space_mouse::Kind::Trackball))), "a slow steady roll");
|
||||
assert!(still_moving_after_a_steady_lean(None), "a device read as it reports");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_calibration_starts_settling_afresh() {
|
||||
let mut app = app();
|
||||
app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
let ctx = window(true);
|
||||
let axes = std::array::from_fn(|source| AxisCalibration { source, reversed: false, rest: 0, dead_zone: 0, full: [350; 2] });
|
||||
app.preferences
|
||||
.space_mouse_calibrations
|
||||
.insert("hid 046d:c62b".into(), Calibration { dead_zone_share: 0.0, kind: solidcraft_space_mouse::Kind::ReturnToCenter, axes });
|
||||
let lean = Axes { translation: [60, 0, 0], rotation: [0; 3] };
|
||||
for _ in 0..30 {
|
||||
frame(&mut app, &ctx, DriverMapping::default(), lean, 0.02);
|
||||
}
|
||||
assert!(!frame(&mut app, &ctx, DriverMapping::default(), lean, 0.02), "settled");
|
||||
app.preferences.space_mouse_calibrations.get_mut("hid 046d:c62b").unwrap().dead_zone_share = 0.01;
|
||||
assert!(frame(&mut app, &ctx, DriverMapping::default(), lean, 0.02), "a changed calibration is a fresh start");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_driver_choice_is_saved_and_an_older_file_reads_automatic() {
|
||||
use crate::prefs::SpaceMouseDriver;
|
||||
@@ -660,6 +1353,7 @@ mod tests {
|
||||
let axis = |source, reversed| solidcraft_space_mouse::AxisCalibration { source, reversed, rest: 0, dead_zone: 0, full: [3500; 2] };
|
||||
solidcraft_space_mouse::Calibration {
|
||||
dead_zone_share: 0.0,
|
||||
kind: Default::default(),
|
||||
axes: [axis(0, false), axis(4, true), axis(2, false), axis(3, false), axis(1, false), axis(5, false)],
|
||||
}
|
||||
}
|
||||
@@ -691,6 +1385,10 @@ mod tests {
|
||||
assert!(app.space_mouse.calibrating.is_none(), "nothing to calibrate without a device");
|
||||
app.space_mouse.device = Some("hid 256f:c63a".into());
|
||||
crate::space_mouse_calibration::start(&mut app);
|
||||
// It asks what kind of device it is first.
|
||||
let Some((_, wizard)) = &mut app.space_mouse.calibrating else { panic!("calibration didn't start") };
|
||||
assert_eq!(wizard.stage(), solidcraft_space_mouse::Stage::Choose);
|
||||
wizard.choose(solidcraft_space_mouse::Kind::ReturnToCenter);
|
||||
let before = app.cam;
|
||||
assert!(!frame(&mut app, &ctx, DriverMapping::default(), PUSH, 0.02));
|
||||
assert_eq!(app.cam, before);
|
||||
@@ -855,8 +1553,9 @@ mod tests {
|
||||
app
|
||||
};
|
||||
// Mixed motion: a press and a push back together, on the device's own channels.
|
||||
let mixed = Axes { translation: [0, 0, 1750], rotation: [0, -3500, 0] };
|
||||
let expected = Axes { translation: [0, 175, 350], rotation: [0; 3] };
|
||||
// A press at full travel, where the calibration's feather no longer shows, so the comparison is exact.
|
||||
let mixed = Axes { translation: [0, 0, 3500], rotation: [0; 3] };
|
||||
let expected = Axes { translation: [0, 350, 0], rotation: [0; 3] };
|
||||
let mut reference = app();
|
||||
frame(&mut reference, &ctx, DriverMapping::default(), expected, 0.02);
|
||||
let mut swapped = setup([0, 2, 1, 3, 4, 5]);
|
||||
@@ -866,7 +1565,7 @@ mod tests {
|
||||
let saved = serde_json::to_value(&swapped.preferences).unwrap();
|
||||
assert_eq!(crate::prefs::lenient(&saved).space_mouse_axis_source, [0, 2, 1, 3, 4, 5]);
|
||||
let mut unswapped_reference = app();
|
||||
frame(&mut unswapped_reference, &ctx, DriverMapping::default(), Axes { translation: [0, 350, 175], rotation: [0; 3] }, 0.02);
|
||||
frame(&mut unswapped_reference, &ctx, DriverMapping::default(), Axes { translation: [0, 0, 350], rotation: [0; 3] }, 0.02);
|
||||
let mut reset = setup(SAME_AXES);
|
||||
frame(&mut reset, &ctx, DriverMapping::default(), mixed, 0.02);
|
||||
assert_eq!(reset.cam, unswapped_reference.cam);
|
||||
@@ -883,9 +1582,9 @@ mod tests {
|
||||
// An axis the mapping switches off says so.
|
||||
let mut off = DriverMapping::default();
|
||||
off.axes[2].enabled = false;
|
||||
assert_eq!(row_label(Some(off), 2), "ui.space_mouse_view_off");
|
||||
assert_eq!(row_label(Some(off), 2), "ui.3d_mouse_view_off");
|
||||
// Without a mapping, rows claim no view effect and name none of the dropdown's motions.
|
||||
assert_eq!(labels(None), ["ui.space_mouse_view_unmapped"; 6]);
|
||||
assert_eq!(labels(None), ["ui.3d_mouse_view_unmapped"; 6]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -914,12 +1613,16 @@ mod tests {
|
||||
app.space_mouse.device = Some("hid 1234:5678".into());
|
||||
app.preferences.space_mouse_axis_source = [0, 2, 1, 3, 4, 5];
|
||||
crate::space_mouse_calibration::start(&mut app);
|
||||
// The calibration asks what kind of device it is first.
|
||||
app.space_mouse.calibrating.as_mut().unwrap().1.choose(solidcraft_space_mouse::Kind::ReturnToCenter);
|
||||
let feed = |app: &mut SolveApp, axes: Axes, seconds: f64| {
|
||||
for _ in 0..(seconds / 0.02).round() as usize {
|
||||
frame(app, &ctx, DriverMapping::default(), axes, 0.02);
|
||||
}
|
||||
};
|
||||
feed(&mut app, Axes::default(), 1.6);
|
||||
// And a hand resting on the cap, before the steps.
|
||||
feed(&mut app, Axes::default(), 2.6);
|
||||
for step in solidcraft_space_mouse::STEPS {
|
||||
let mut raw = [0_i16; 6];
|
||||
raw[step.axis] = if step.positive { 300 } else { -300 };
|
||||
|
||||
@@ -1,28 +1,48 @@
|
||||
//! The 3D mouse calibration window, opened from Preferences › Navigation. It asks the user to
|
||||
//! leave the cap still, then to move it along each axis both ways, moving on by itself after each
|
||||
//! The 3D mouse calibration window, opened from Preferences › Navigation. It asks what kind of
|
||||
//! device it is (Return to Center, or a trackball), then asks the user to leave it still and
|
||||
//! to move it along each axis both ways, moving on by itself after each
|
||||
//! (`solidcraft_space_mouse::Wizard`), and saves what it found for the device it started with.
|
||||
//! The window closes with Preferences, and when another device (or none) is read instead.
|
||||
|
||||
use solidcraft_space_mouse::{Calibration, Problem, Stage, Wizard};
|
||||
use solidcraft_space_mouse::{Calibration, Kind, Problem, Stage, Wizard};
|
||||
|
||||
use crate::SolveApp;
|
||||
|
||||
/// What each of `solidcraft_space_mouse::STEPS` asks for, in order.
|
||||
const STEP_PROMPTS: [&str; 12] = [
|
||||
"ui.space_mouse_step_slide_right",
|
||||
"ui.space_mouse_step_slide_left",
|
||||
"ui.space_mouse_step_pull",
|
||||
"ui.space_mouse_step_push",
|
||||
"ui.space_mouse_step_press",
|
||||
"ui.space_mouse_step_lift",
|
||||
"ui.space_mouse_step_tilt_back",
|
||||
"ui.space_mouse_step_tilt_forward",
|
||||
"ui.space_mouse_step_tilt_left",
|
||||
"ui.space_mouse_step_tilt_right",
|
||||
"ui.space_mouse_step_twist_clockwise",
|
||||
"ui.space_mouse_step_twist_counterclockwise",
|
||||
"ui.3d_mouse_step_slide_right",
|
||||
"ui.3d_mouse_step_slide_left",
|
||||
"ui.3d_mouse_step_pull",
|
||||
"ui.3d_mouse_step_push",
|
||||
"ui.3d_mouse_step_press",
|
||||
"ui.3d_mouse_step_lift",
|
||||
"ui.3d_mouse_step_tilt_toward",
|
||||
"ui.3d_mouse_step_tilt_away",
|
||||
"ui.3d_mouse_step_tilt_left",
|
||||
"ui.3d_mouse_step_tilt_right",
|
||||
"ui.3d_mouse_step_twist_clockwise",
|
||||
"ui.3d_mouse_step_twist_counterclockwise",
|
||||
];
|
||||
|
||||
/// The same for a trackball: it has nothing to tilt, so those steps aren't asked.
|
||||
const TRACKBALL_PROMPTS: [&str; 12] = [
|
||||
"ui.3d_mouse_step_roll_right",
|
||||
"ui.3d_mouse_step_roll_left",
|
||||
"ui.3d_mouse_step_roll_toward",
|
||||
"ui.3d_mouse_step_roll_away",
|
||||
"ui.3d_mouse_step_ring_clockwise",
|
||||
"ui.3d_mouse_step_ring_counterclockwise",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"ui.3d_mouse_step_spin_clockwise",
|
||||
"ui.3d_mouse_step_spin_counterclockwise",
|
||||
];
|
||||
|
||||
/// Each kind of device, as the first step offers it.
|
||||
const KINDS: [(Kind, &str); 2] = [(Kind::ReturnToCenter, "ui.3d_mouse_kind_cap"), (Kind::Trackball, "ui.3d_mouse_kind_trackball")];
|
||||
|
||||
/// The dead zone, as a share of full travel, shown as a percentage.
|
||||
pub(crate) fn dead_zone_slider(ui: &mut egui::Ui, share: &mut f32) -> egui::Response {
|
||||
crate::ui_kit::slider(
|
||||
@@ -33,24 +53,42 @@ pub(crate) fn dead_zone_slider(ui: &mut egui::Ui, share: &mut f32) -> egui::Resp
|
||||
)
|
||||
}
|
||||
|
||||
/// The tooltip of the circle for axes `(h, v)`: each axis's dead zone on its two sides and, where the
|
||||
/// page says (not in the calibration window), the view axis it drives.
|
||||
pub(crate) fn pad_tooltip(shares: &[[f32; 2]; 6], (h, v): (usize, usize), stories: &[String; 6]) -> String {
|
||||
const NAMES: [&str; 6] = ["X", "Y", "Z", "Rx", "Ry", "Rz"];
|
||||
let line = |axis: usize| {
|
||||
let percent = format!("{} −{:.0} % +{:.0} %", NAMES[axis], shares[axis][0] * 100.0, shares[axis][1] * 100.0);
|
||||
if stories[axis].is_empty() { percent } else { format!("{percent} → {}", stories[axis]) }
|
||||
};
|
||||
format!("{}\n{}", line(h), line(v))
|
||||
}
|
||||
|
||||
/// The calibration's dead zones the way a game controller tester shows a stick: per pair of axes,
|
||||
/// a circle for the full travel, the dead zone shaded inside it (each direction its own size), and
|
||||
/// a dot where the cap is now, before the dead zone, so it's plain when the view starts to move.
|
||||
pub(crate) fn dead_zone_pads(ui: &mut egui::Ui, calibration: &Calibration, raw: [i16; 6]) {
|
||||
pub(crate) fn dead_zone_pads(ui: &mut egui::Ui, calibration: &Calibration, raw: [i16; 6], centred: bool, stories: &[String; 6]) {
|
||||
const NAMES: [&str; 6] = ["X", "Y", "Z", "Rx", "Ry", "Rz"];
|
||||
// (horizontal axis, its positive way on screen, vertical axis, its positive way down), so the
|
||||
// dot moves as the cap does: slid right and pulled toward you, twisted and pressed, tilted.
|
||||
const PADS: [(usize, f32, usize, f32); 3] = [(0, 1.0, 1, 1.0), (5, 1.0, 2, 1.0), (4, -1.0, 3, -1.0)];
|
||||
const SIZE: f32 = 76.0;
|
||||
let (shares, travel, moving) = (calibration.dead_zone_shares(), calibration.travel(raw), calibration.apply(raw));
|
||||
const GAP: f32 = 12.0;
|
||||
let visuals = ui.visuals().clone();
|
||||
ui.horizontal(|ui| {
|
||||
// The three together with small gaps: centred in the calibration window, from the left edge
|
||||
// (under the device's picture) in Preferences.
|
||||
ui.spacing_mut().item_spacing.x = GAP;
|
||||
if centred {
|
||||
ui.add_space(((ui.available_width() - PADS.len() as f32 * SIZE - (PADS.len() - 1) as f32 * GAP) / 2.0).max(0.0));
|
||||
}
|
||||
for (h, h_way, v, v_way) in PADS {
|
||||
ui.vertical(|ui| {
|
||||
let (rect, response) = ui.allocate_exact_size(egui::vec2(SIZE, SIZE), egui::Sense::hover());
|
||||
let painter = ui.painter_at(rect);
|
||||
let (centre, radius) = (rect.center(), SIZE / 2.0 - 2.0);
|
||||
let faint = visuals.weak_text_color();
|
||||
let faint = crate::theme::Tokens::get().text_dim;
|
||||
// Which side of an axis's dead zone lies along a way on screen.
|
||||
let side = |axis: usize, way: f32, screen: f32| shares[axis][usize::from(way * screen > 0.0)];
|
||||
let zone: Vec<egui::Pos2> = (0..64)
|
||||
@@ -74,29 +112,66 @@ pub(crate) fn dead_zone_pads(ui: &mut egui::Ui, calibration: &Calibration, raw:
|
||||
let dot = if dot.length() > 1.0 { dot.normalized() } else { dot };
|
||||
let live = moving[h] != 0 || moving[v] != 0;
|
||||
painter.circle_filled(centre + dot * radius, 3.5, if live { visuals.selection.stroke.color } else { faint });
|
||||
let percent = |axis: usize| format!("{} −{:.0} % +{:.0} %", NAMES[axis], shares[axis][0] * 100.0, shares[axis][1] * 100.0);
|
||||
response.on_hover_text(format!("{}\n{}", percent(h), percent(v)));
|
||||
ui.label(egui::RichText::new(format!("{} · {}", NAMES[h], NAMES[v])).small().color(faint));
|
||||
response.on_hover_text(pad_tooltip(&shares, (h, v), stories));
|
||||
// As wide as the circle, so the labels don't push the circles apart.
|
||||
ui.allocate_ui(egui::vec2(SIZE, 0.0), |ui| {
|
||||
crate::ui_kit::text(ui, &format!("{} · {}", NAMES[h], NAMES[v]), crate::ui_kit::Text::Note("space_mouse_pad"));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// The device's six raw axes as they come, so the jitter at rest is there to see: six equal
|
||||
/// columns, each axis name centred on one line and its number centred under it, the group centred
|
||||
/// like the dead-zone pads. Painted in one go so every name and number shares its column and baseline.
|
||||
fn reading_grid(ui: &mut egui::Ui, raw: [i16; 6]) {
|
||||
const NAMES: [&str; 6] = ["X", "Y", "Z", "Rx", "Ry", "Rz"];
|
||||
const CELL: f32 = 48.0;
|
||||
let t = crate::theme::Tokens::get();
|
||||
let (name_font, number_font) = (egui::FontId::proportional(t.font_small), egui::FontId::monospace(t.font_small));
|
||||
let painter = ui.painter().clone();
|
||||
let line = |font: &egui::FontId| painter.layout_no_wrap("0".into(), font.clone(), t.text_dim).size().y;
|
||||
let (name_height, number_height) = (line(&name_font), line(&number_font));
|
||||
let (area, _) = ui.allocate_exact_size(egui::vec2(ui.available_width(), name_height + number_height + t.space_4), egui::Sense::hover());
|
||||
crate::scenario::publish_rect("space_mouse:reading_block", area);
|
||||
let left = area.left() + ((area.width() - CELL * 6.0) / 2.0).max(0.0);
|
||||
for (i, (name, value)) in NAMES.into_iter().zip(raw).enumerate() {
|
||||
let centre = left + (i as f32 + 0.5) * CELL;
|
||||
let cell = |text: &str, font: &egui::FontId, colour: egui::Color32, top: f32, key: &str| {
|
||||
let galley = painter.layout_no_wrap(text.to_owned(), font.clone(), colour);
|
||||
let rect = egui::Rect::from_center_size(egui::pos2(centre, top + galley.size().y / 2.0), galley.size());
|
||||
painter.galley(rect.min, galley, colour);
|
||||
crate::scenario::publish_rect(&format!("space_mouse:reading_{key}:{i}"), rect);
|
||||
};
|
||||
cell(name, &name_font, t.text_dim, area.top(), "name");
|
||||
cell(&value.to_string(), &number_font, t.form_ink, area.top() + name_height + t.space_4, "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts calibrating the device being read, if there is one.
|
||||
pub fn start(app: &mut SolveApp) {
|
||||
app.space_mouse.calibrating = app.space_mouse.device.clone().map(|device| (device, Wizard::default()));
|
||||
app.space_mouse.calibrating = app.space_mouse.device.clone().map(|device| (device, Wizard::choosing()));
|
||||
}
|
||||
|
||||
pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let Some((_, wizard)) = &app.space_mouse.calibrating else { return };
|
||||
let (stage, progress, result) = (wizard.stage(), wizard.progress(), wizard.result());
|
||||
let (stage, kind, progress, result) = (wizard.stage(), wizard.kind(), wizard.progress(), wizard.result());
|
||||
let (can_skip, mismatch, (step_number, steps)) = (wizard.can_skip(), wizard.mismatch(), wizard.counter());
|
||||
let mut dead_zone = wizard.dead_zone_share();
|
||||
let raw: [i16; 6] = app.space_mouse.raw.into();
|
||||
let prompts = match kind {
|
||||
Kind::ReturnToCenter => &STEP_PROMPTS,
|
||||
Kind::Trackball => &TRACKBALL_PROMPTS,
|
||||
};
|
||||
let prompt = match (stage, result) {
|
||||
(Stage::Rest, _) => "ui.space_mouse_step_rest",
|
||||
(Stage::Step(index), _) => STEP_PROMPTS.get(index).copied().unwrap_or_default(),
|
||||
(Stage::Done, Some(Ok(_))) => "ui.space_mouse_calibration_done",
|
||||
(Stage::Done, _) => "ui.space_mouse_calibration_no_response",
|
||||
(Stage::Choose, _) => "ui.3d_mouse_kind_question",
|
||||
(Stage::Rest, _) if kind == Kind::Trackball => "ui.3d_mouse_step_rest_trackball",
|
||||
(Stage::Rest, _) => "ui.3d_mouse_step_rest",
|
||||
(Stage::Touch, _) => "ui.3d_mouse_step_touch",
|
||||
(Stage::Step(index), _) => prompts.get(index).copied().unwrap_or_default(),
|
||||
(Stage::Done, Some(Ok(_))) => "ui.3d_mouse_calibration_done",
|
||||
(Stage::Done, _) => "ui.3d_mouse_calibration_no_response",
|
||||
};
|
||||
// The calibrated motion that barely moved, as Preferences names it.
|
||||
let weak = match result {
|
||||
@@ -104,47 +179,93 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
_ => None,
|
||||
};
|
||||
let counter = match stage {
|
||||
Stage::Rest => String::new(),
|
||||
Stage::Step(index) => format!("{} / {}", index + 1, STEP_PROMPTS.len()),
|
||||
Stage::Done => format!("{0} / {0}", STEP_PROMPTS.len()),
|
||||
Stage::Choose | Stage::Rest | Stage::Touch => String::new(),
|
||||
Stage::Step(_) | Stage::Done => format!("{step_number} / {steps}"),
|
||||
};
|
||||
let (mut back, mut over, mut cancel, mut save) = (false, false, false, false);
|
||||
let closed = crate::ui_kit::Window::new(egui::Id::new("sc_space_mouse_calibration"), "settings", app.tr("ui.space_mouse_calibrate_title"))
|
||||
.show_body(ctx, |ui| {
|
||||
ui.label(app.tr(prompt));
|
||||
if let Some(axis) = weak {
|
||||
ui.label(egui::RichText::new(app.tr(axis)).strong());
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
crate::ui_kit::progress(ui, "space_mouse_calibration", progress as f32);
|
||||
ui.label(counter);
|
||||
if let Some(Ok(calibration)) = result {
|
||||
ui.add_space(6.0);
|
||||
dead_zone_pads(ui, &Calibration { dead_zone_share: dead_zone, ..calibration }, raw);
|
||||
}
|
||||
// The device's six raw axes as they come, so the jitter at rest is there to see.
|
||||
let reading = raw.map(|value| format!("{value:>6}")).join(" ");
|
||||
ui.label(egui::RichText::new(format!("{}: {reading}", app.tr("ui.space_mouse_reading"))).monospace());
|
||||
// A reassignment made in Preferences stays on top of whatever this records.
|
||||
if app.preferences.space_mouse_axis_source != crate::space_mouse::SAME_AXES {
|
||||
ui.label(egui::RichText::new(app.tr("ui.space_mouse_axes_reassigned")).small());
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(app.tr("ui.space_mouse_dead_zone"));
|
||||
dead_zone_slider(ui, &mut dead_zone).on_hover_text(app.tr("ui.space_mouse_dead_zone_tip"));
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
ui.horizontal(|ui| {
|
||||
back = ui
|
||||
.add_enabled_ui(stage != Stage::Rest, |ui| crate::ui_kit::button(ui, app.tr("ui.back"), crate::ui_kit::Button::Normal))
|
||||
.inner
|
||||
.clicked();
|
||||
over = matches!(result, Some(Err(_))) && crate::ui_kit::button(ui, app.tr("ui.start_over"), crate::ui_kit::Button::Normal).clicked();
|
||||
cancel = crate::ui_kit::button(ui, app.tr("ui.cancel"), crate::ui_kit::Button::Normal).clicked();
|
||||
save = matches!(result, Some(Ok(_))) && crate::ui_kit::button(ui, app.tr("ui.save"), crate::ui_kit::Button::Normal).clicked();
|
||||
});
|
||||
});
|
||||
if cancel || closed {
|
||||
let (mut back, mut over, mut skip) = (false, false, false);
|
||||
let mut chosen = None;
|
||||
let done = matches!(result, Some(Ok(_)));
|
||||
let text = |ui: &mut egui::Ui, text: &str, key: &str| crate::ui_kit::text(ui, text, crate::ui_kit::Text::Report(key, false));
|
||||
let (closed, action) = crate::ui_kit::Window::new(egui::Id::new("sc_space_mouse_calibration"), "settings", app.tr("ui.3d_mouse_calibrate_title"))
|
||||
.actions(if done { app.tr("ui.save") } else { "" }, app.tr("ui.cancel"), None)
|
||||
.show_with_left(
|
||||
ctx,
|
||||
done,
|
||||
|ui| {
|
||||
text(ui, app.tr(prompt), "prompt");
|
||||
if let Some(axis) = weak {
|
||||
crate::ui_kit::text(ui, app.tr(axis), crate::ui_kit::Text::Heading("weak_axis"));
|
||||
}
|
||||
// The device doesn't behave like the kind picked: say so, and offer the other.
|
||||
if let Some(other) = mismatch {
|
||||
let (hint, switch) = match other {
|
||||
Kind::Trackball => ("ui.3d_mouse_looks_like_trackball", "ui.3d_mouse_switch_trackball"),
|
||||
Kind::ReturnToCenter => ("ui.3d_mouse_looks_like_cap", "ui.3d_mouse_switch_cap"),
|
||||
};
|
||||
crate::ui_kit::text(ui, app.tr(hint), crate::ui_kit::Text::Hint("space_mouse_kind_mismatch"));
|
||||
if crate::ui_kit::button(ui, app.tr(switch), crate::ui_kit::Button::Normal).clicked() {
|
||||
chosen = Some(other);
|
||||
}
|
||||
}
|
||||
if stage == Stage::Choose {
|
||||
// Side by side where they fit, one under the other where the window is narrow.
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (kind, label) in KINDS {
|
||||
ui.vertical(|ui| {
|
||||
crate::space_mouse_model::rest(ui, kind);
|
||||
if crate::ui_kit::button(ui, app.tr(label), crate::ui_kit::Button::Normal).clicked() {
|
||||
chosen = Some(kind);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
crate::ui_kit::progress(ui, "space_mouse_calibration", progress as f32);
|
||||
crate::ui_kit::text(ui, &counter, crate::ui_kit::Text::Note("space_mouse_counter"));
|
||||
// Each step shows the motion it asks for; once calibrated, the device's picture follows it.
|
||||
let step = match stage {
|
||||
Stage::Step(index) => solidcraft_space_mouse::STEPS.get(index).copied(),
|
||||
_ => None,
|
||||
};
|
||||
match (step, result) {
|
||||
(Some(step), _) => crate::space_mouse_model::demo(ui, step, kind),
|
||||
(_, Some(Ok(calibration))) => {
|
||||
let calibration = Calibration { dead_zone_share: dead_zone, ..calibration };
|
||||
crate::space_mouse_model::live(ui, &calibration, raw);
|
||||
dead_zone_pads(ui, &calibration, raw, true, &Default::default());
|
||||
}
|
||||
_ => crate::space_mouse_model::rest(ui, kind),
|
||||
}
|
||||
}
|
||||
// The device's six raw axes as they come, so the jitter at rest is there to see.
|
||||
crate::ui_kit::text(ui, app.tr("ui.3d_mouse_reading"), crate::ui_kit::Text::Note("space_mouse_reading"));
|
||||
reading_grid(ui, raw);
|
||||
// A reassignment made in Preferences stays on top of whatever this records.
|
||||
if app.preferences.space_mouse_axis_source != crate::space_mouse::SAME_AXES {
|
||||
crate::ui_kit::text(ui, app.tr("ui.3d_mouse_axes_reassigned"), crate::ui_kit::Text::Note("space_mouse_axes_reassigned"));
|
||||
}
|
||||
crate::ui_kit::row(ui, app.tr("ui.3d_mouse_dead_zone"), |ui| {
|
||||
dead_zone_slider(ui, &mut dead_zone).on_hover_text(app.tr("ui.3d_mouse_dead_zone_tip"));
|
||||
});
|
||||
},
|
||||
|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
back = ui
|
||||
.add_enabled_ui(stage != Stage::Choose, |ui| crate::ui_kit::button(ui, app.tr("ui.back"), crate::ui_kit::Button::Normal))
|
||||
.inner
|
||||
.clicked();
|
||||
skip = can_skip
|
||||
&& crate::ui_kit::button(ui, app.tr("ui.3d_mouse_skip"), crate::ui_kit::Button::Normal)
|
||||
.on_hover_text(app.tr("ui.3d_mouse_skip_tip"))
|
||||
.clicked();
|
||||
over =
|
||||
matches!(result, Some(Err(_))) && crate::ui_kit::button(ui, app.tr("ui.start_over"), crate::ui_kit::Button::Normal).clicked();
|
||||
});
|
||||
},
|
||||
);
|
||||
let cancel = closed || action == crate::ui_kit::Action::Cancel;
|
||||
let save = action == crate::ui_kit::Action::Ok;
|
||||
if cancel {
|
||||
app.space_mouse.calibrating = None;
|
||||
} else if let Some((device, wizard)) = &mut app.space_mouse.calibrating {
|
||||
wizard.set_dead_zone_share(dead_zone);
|
||||
@@ -152,10 +273,13 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
if save && let Some(Ok(calibration)) = result {
|
||||
app.preferences.space_mouse_calibrations.insert(std::mem::take(device), calibration);
|
||||
app.space_mouse.calibrating = None;
|
||||
} else if let Some(kind) = chosen {
|
||||
wizard.choose(kind);
|
||||
} else if over {
|
||||
// A new recording; the dead zone set so far stays.
|
||||
*wizard = Wizard::default();
|
||||
wizard.set_dead_zone_share(dead_zone);
|
||||
// A new recording of the same kind; the dead zone set so far stays.
|
||||
wizard.choose(wizard.kind());
|
||||
} else if skip {
|
||||
wizard.skip();
|
||||
} else if back {
|
||||
wizard.back();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,546 @@
|
||||
//! A picture of a 3D mouse, seen as the user sits at it (from in front, a little above), whose
|
||||
//! cap moves as the real one does. Each way of moving it has a marker that lights while it reads
|
||||
//! as motion: the groove on the cap's front and the pointer on its top turn against the ticks on
|
||||
//! the base (twist), the mast tilts away from the dashed upright (tilt), the cap leaves the ring
|
||||
//! it rests in (slide), and the gauge beside it rises and falls (pull up, press down).
|
||||
//! It's a generic cap on a round base, drawn in code, not any maker's device.
|
||||
|
||||
use egui::{Pos2, Shape, Stroke};
|
||||
use solidcraft_space_mouse::{Calibration, Kind, Step};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "space_mouse_model_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
/// A point in the device's space: right, away from the user, up. The base's radius is 1.
|
||||
type P3 = [f32; 3];
|
||||
|
||||
/// How far down the view looks, in radians.
|
||||
const PITCH: f32 = 0.52;
|
||||
/// Full deflection, in base radii and radians.
|
||||
const SLIDE: f32 = 0.18;
|
||||
const LIFT: f32 = 0.09;
|
||||
const TILT: f32 = 0.35;
|
||||
const TWIST: f32 = 0.6;
|
||||
const BASE_HEIGHT: f32 = 0.22;
|
||||
/// The base under the cap, and under the trackball's scroll ring.
|
||||
const BASE_RADIUS: f32 = 0.8;
|
||||
/// The cap's bottom at rest; the gap under it shows the light, closing when pressed.
|
||||
const CAP_BOTTOM: f32 = BASE_HEIGHT + 0.1;
|
||||
const CAP_RADIUS: f32 = 0.5;
|
||||
const CAP_HEIGHT: f32 = 0.62;
|
||||
/// Where the gauge stands, and how far its pointer goes at full deflection.
|
||||
const GAUGE: [f32; 2] = [1.05, 0.6];
|
||||
const GAUGE_TRAVEL: f32 = 0.3;
|
||||
|
||||
/// The cap's position and turn for a reading on the canonical axes (±1 at full deflection):
|
||||
/// slid right +X, pulled toward the user +Y, pressed down +Z, its top tilted toward the user +Rx
|
||||
/// (the near edge down; the axes are right-handed with Y toward the user and Z down), tilted left +Ry,
|
||||
/// twisted clockwise +Rz.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct Pose {
|
||||
offset: P3,
|
||||
/// Columns of the cap's rotation.
|
||||
turn: [P3; 3],
|
||||
}
|
||||
|
||||
impl Pose {
|
||||
pub(crate) fn new(travel: [f32; 6]) -> Self {
|
||||
let t = travel.map(|v| if v.is_finite() { v.clamp(-1.5, 1.5) } else { 0.0 });
|
||||
// Twisting clockwise as seen from above is a negative turn about up; +Rx takes the top
|
||||
// toward the user, tilting left takes it to the left.
|
||||
let twist = rotate_z(-t[5] * TWIST);
|
||||
let toward = rotate_x(-t[3] * TILT);
|
||||
let left = rotate_y(-t[4] * TILT);
|
||||
let turn = [0, 1, 2].map(|i| apply(&left, apply(&toward, apply(&twist, unit(i)))));
|
||||
Self { offset: [t[0] * SLIDE, -t[1] * SLIDE, CAP_BOTTOM - t[2] * LIFT], turn }
|
||||
}
|
||||
|
||||
/// A point on the cap (from the centre of its bottom) in the device's space.
|
||||
fn place(&self, p: P3) -> P3 {
|
||||
add(apply(&self.turn, p), self.offset)
|
||||
}
|
||||
}
|
||||
|
||||
/// Which way the user looks at the device.
|
||||
fn eye() -> P3 {
|
||||
[0.0, -PITCH.cos(), PITCH.sin()]
|
||||
}
|
||||
|
||||
/// The device's space on screen, as a share of `scale`, y down.
|
||||
fn project(p: P3) -> [f32; 2] {
|
||||
[p[0], -(p[1] * PITCH.sin() + p[2] * PITCH.cos())]
|
||||
}
|
||||
|
||||
/// How big the device is in `rect`, in points per base radius, and where a point in its space
|
||||
/// lands there.
|
||||
fn view(rect: egui::Rect) -> (f32, impl Fn(P3) -> Pos2 + Copy) {
|
||||
let scale = (rect.width() / 2.9).min(rect.height() / 2.1);
|
||||
let origin = rect.center() + egui::vec2(-0.15, 0.4) * scale;
|
||||
(scale, move |p: P3| {
|
||||
let [x, y] = project(p);
|
||||
origin + egui::vec2(x, y) * scale
|
||||
})
|
||||
}
|
||||
|
||||
/// Where the gauge's pointer is for `pose`, as the three corners of its arrow.
|
||||
fn gauge_pointer(at: &impl Fn(P3) -> Pos2, pose: &Pose) -> Vec<Pos2> {
|
||||
let [gx, gh] = GAUGE;
|
||||
let pointer = at([gx, 0.0, gh + (pose.offset[2] - CAP_BOTTOM) / LIFT * GAUGE_TRAVEL]);
|
||||
vec![pointer + egui::vec2(-4.0, 0.0), pointer + egui::vec2(5.0, -5.0), pointer + egui::vec2(5.0, 5.0)]
|
||||
}
|
||||
|
||||
/// Draws the device in `rect` for `pose`; `moving` lights each canonical axis's marker.
|
||||
pub(crate) fn paint(ui: &egui::Ui, rect: egui::Rect, pose: &Pose, moving: [bool; 6]) {
|
||||
let painter = ui.painter_at(rect);
|
||||
let visuals = ui.visuals();
|
||||
let (_, at) = view(rect);
|
||||
let (faint, accent) = (crate::theme::Tokens::get().text_dim, visuals.selection.stroke.color);
|
||||
let marker = |live: bool| Stroke::new(if live { 2.5 } else { 1.5 }, if live { accent } else { faint });
|
||||
let outline = Stroke::new(1.0, faint);
|
||||
let (base_side, base_top) = (visuals.widgets.noninteractive.bg_stroke.color, visuals.widgets.inactive.bg_fill);
|
||||
let (cap_side, cap_top) = (visuals.widgets.active.bg_fill, visuals.widgets.hovered.bg_fill);
|
||||
|
||||
// The base, the ticks round it to read a twist against, and the ring the cap rests in.
|
||||
let base: Vec<Pos2> = ring(BASE_RADIUS, 0.0).into_iter().chain(ring(BASE_RADIUS, BASE_HEIGHT)).map(at).collect();
|
||||
painter.add(Shape::convex_polygon(hull(base), base_side, outline));
|
||||
painter.add(Shape::convex_polygon(ring(BASE_RADIUS, BASE_HEIGHT).into_iter().map(at).collect(), base_top, outline));
|
||||
for i in 0..24 {
|
||||
let (sin, cos) = (i as f32 / 24.0 * std::f32::consts::TAU).sin_cos();
|
||||
let (inner, outer) = if i % 6 == 0 { (0.58, 0.76) } else { (0.62, 0.71) };
|
||||
painter.line_segment([at([inner * cos, inner * sin, BASE_HEIGHT]), at([outer * cos, outer * sin, BASE_HEIGHT])], outline);
|
||||
}
|
||||
let sliding = moving[0] || moving[1];
|
||||
painter.add(Shape::closed_line(ring(CAP_RADIUS + 0.03, BASE_HEIGHT).into_iter().map(at).collect(), marker(sliding)));
|
||||
// The light under the cap, showing in the gap that pulling up widens and pressing closes.
|
||||
let glow = ring(CAP_RADIUS - 0.04, BASE_HEIGHT).into_iter().map(|p| at(add(p, [pose.offset[0], pose.offset[1], 0.0])));
|
||||
painter.add(Shape::convex_polygon(glow.collect(), visuals.selection.bg_fill, Stroke::NONE));
|
||||
|
||||
// The gauge: its notch is rest, its pointer where pulling up or pressing has the cap.
|
||||
let [gx, gh] = GAUGE;
|
||||
let gauge = |h: f32| at([gx, 0.0, h]);
|
||||
painter.line_segment([gauge(gh - GAUGE_TRAVEL), gauge(gh + GAUGE_TRAVEL)], outline);
|
||||
for h in [gh - GAUGE_TRAVEL, gh, gh + GAUGE_TRAVEL] {
|
||||
painter.line_segment([gauge(h) - egui::vec2(3.0, 0.0), gauge(h) + egui::vec2(3.0, 0.0)], outline);
|
||||
}
|
||||
let lifting = moving[2];
|
||||
painter.add(Shape::convex_polygon(gauge_pointer(&at, pose), if lifting { accent } else { faint }, Stroke::NONE));
|
||||
|
||||
// The cap: its side, the groove down its front where the side faces the user, then its top.
|
||||
let bottom = ring(CAP_RADIUS, 0.0);
|
||||
let top = ring(CAP_RADIUS, CAP_HEIGHT);
|
||||
let side: Vec<Pos2> = bottom.iter().chain(&top).map(|&p| at(pose.place(p))).collect();
|
||||
painter.add(Shape::convex_polygon(hull(side), cap_side, outline));
|
||||
let twisting = moving[5];
|
||||
let front = apply(&pose.turn, [0.0, -1.0, 0.0]);
|
||||
if dot(front, eye()) > 0.0 {
|
||||
let groove = [0.1, CAP_HEIGHT - 0.1].map(|h| at(pose.place([0.0, -CAP_RADIUS, h])));
|
||||
painter.line_segment(groove, marker(twisting));
|
||||
}
|
||||
painter.add(Shape::convex_polygon(top.iter().map(|&p| at(pose.place(p))).collect(), cap_top, Stroke::new(1.5, faint)));
|
||||
let centre = pose.place([0.0, 0.0, CAP_HEIGHT]);
|
||||
let pointer = pose.place([0.0, -0.36, CAP_HEIGHT]);
|
||||
painter.line_segment([at(centre), at(pointer)], marker(twisting));
|
||||
painter.circle_filled(at(pointer), 2.5, if twisting { accent } else { faint });
|
||||
|
||||
// The mast stands on the cap's axis; the dashed upright is where it stands at rest.
|
||||
let tilting = moving[3] || moving[4];
|
||||
let rest = [0.0, 0.0, CAP_BOTTOM + CAP_HEIGHT];
|
||||
painter.extend(Shape::dashed_line(&[at(rest), at(add(rest, [0.0, 0.0, 0.5]))], Stroke::new(1.0, faint.gamma_multiply(0.6)), 3.0, 3.0));
|
||||
let mast = add(centre, scaled(apply(&pose.turn, [0.0, 0.0, 1.0]), 0.5));
|
||||
painter.line_segment([at(centre), at(mast)], marker(tilting));
|
||||
painter.circle_filled(at(mast), 3.0, if tilting { accent } else { faint });
|
||||
}
|
||||
|
||||
/// The picture's size in a window.
|
||||
const SIZE: egui::Vec2 = egui::vec2(200.0, 150.0);
|
||||
|
||||
/// How a trackball moves for a reading on the canonical axes, as calibration asks for its motions:
|
||||
/// rolled away from the user (-Y), rolled right (+X), spun clockwise (+Rz), its scroll ring turned
|
||||
/// clockwise (+Z). The reading is a speed, so this is too.
|
||||
pub(crate) fn ball_rate(travel: [f32; 6]) -> [f32; 4] {
|
||||
[-travel[1], travel[0], travel[5], travel[2]]
|
||||
}
|
||||
|
||||
/// A trackball kept in `ui`'s memory for one call `site` (`ui` and `site` together are its key, so
|
||||
/// the Preferences picture, the corner and the demo each roll their own once a frame), rolled by
|
||||
/// `travel` since the last frame: unlike a cap, its ball and scroll ring keep their turn from frame to frame.
|
||||
/// With `repaint` a rolling ball asks for the next frame in 16 ms; the demo sets its own pace instead
|
||||
/// (egui keeps the shortest request, so a 16 ms one would beat its slower ones, unfocused too).
|
||||
fn rolled(ui: &egui::Ui, site: &str, travel: [f32; 6], repaint: bool) -> ball::Ball {
|
||||
let (rate, seconds) = (ball_rate(travel), ui.input(|i| i.stable_dt));
|
||||
if repaint && rate.iter().any(|&v| v != 0.0) {
|
||||
ui.ctx().request_repaint_after(std::time::Duration::from_millis(16));
|
||||
}
|
||||
ui.data_mut(|data| {
|
||||
let ball = data.get_temp_mut_or_insert_with(ui.id().with(("sc_space_mouse_ball", site)), ball::Ball::new);
|
||||
ball.roll(rate, seconds);
|
||||
*ball
|
||||
})
|
||||
}
|
||||
|
||||
/// The call site of the demo, which paces its own repaints.
|
||||
const DEMO: &str = "demo";
|
||||
|
||||
/// Draws a device of `kind` in `rect` moved by `travel`, its markers lit by `moving`.
|
||||
fn draw(ui: &egui::Ui, site: &str, rect: egui::Rect, kind: Kind, travel: [f32; 6], moving: [bool; 6]) {
|
||||
match kind {
|
||||
Kind::ReturnToCenter => paint(ui, rect, &Pose::new(travel), moving),
|
||||
Kind::Trackball => {
|
||||
let shown = rolled(ui, site, travel, site != DEMO);
|
||||
ball::paint(ui, rect, &shown, moving[0] || moving[1] || moving[5], moving[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A device of `kind`, still.
|
||||
pub(crate) fn rest(ui: &mut egui::Ui, kind: Kind) {
|
||||
let (rect, _) = ui.allocate_exact_size(SIZE, egui::Sense::hover());
|
||||
crate::scenario::publish_rect("space_mouse:picture", rect);
|
||||
draw(ui, "rest", rect, kind, [0.0; 6], [false; 6]);
|
||||
}
|
||||
|
||||
const CORNER: egui::Vec2 = egui::vec2(160.0, 120.0);
|
||||
|
||||
/// Where the corner picture sits in `view`: the two left positions at its lower left, the right one
|
||||
/// at its lower right, each a margin in from the edge (the caller passes the viewport area for the
|
||||
/// Viewport positions and the whole window for Left Window). Any of `obstacles` (the History bar,
|
||||
/// the navigation bar, the agent chip) that would cover it moves it up to just above them, and it
|
||||
/// stays inside `view`.
|
||||
pub(crate) fn corner_rect(view: egui::Rect, position: crate::prefs::SpaceMouseGhost, obstacles: &[egui::Rect]) -> egui::Rect {
|
||||
use crate::prefs::SpaceMouseGhost;
|
||||
const MARGIN: f32 = 12.0;
|
||||
let x = match position {
|
||||
SpaceMouseGhost::LeftViewport | SpaceMouseGhost::LeftWindow => view.left() + MARGIN,
|
||||
SpaceMouseGhost::RightViewport => (view.right() - MARGIN - CORNER.x).max(view.left() + MARGIN),
|
||||
};
|
||||
let mut rect = egui::Rect::from_min_size(egui::pos2(x, view.bottom() - MARGIN - CORNER.y), CORNER);
|
||||
for _ in 0..obstacles.len() {
|
||||
let Some(blocking) = obstacles.iter().find(|bar| bar.is_positive() && bar.intersects(rect)) else { break };
|
||||
rect = rect.translate(egui::vec2(0.0, blocking.top() - MARGIN - rect.bottom()));
|
||||
}
|
||||
// Stacked obstacles in a short view must not push it out of the top.
|
||||
rect.translate(egui::vec2(0.0, (view.top() - rect.top()).max(0.0)))
|
||||
}
|
||||
|
||||
/// The device in a corner of the 3D view (`rect`, from `corner_rect`), see-through so it doesn't hide the design:
|
||||
/// cap or trackball as `calibration` says, moving as the device does. A device without a
|
||||
/// calibration is read as it reports: each axis as a share of the full deflection a reading is
|
||||
/// scaled to (`FULL_SCALE`).
|
||||
pub(crate) fn corner(ui: &mut egui::Ui, rect: egui::Rect, calibration: Option<&Calibration>, raw: [i16; 6]) {
|
||||
let (kind, travel, moving) = match calibration {
|
||||
Some(calibration) => (calibration.kind, calibration.travel(raw), calibration.apply(raw).map(|value| value != 0)),
|
||||
None => {
|
||||
let travel = raw.map(|value| f32::from(value) / f32::from(solidcraft_space_mouse::FULL_SCALE));
|
||||
(Kind::ReturnToCenter, travel, travel.map(|value| value.abs() > 0.05))
|
||||
}
|
||||
};
|
||||
let travel = still_unless_moving(kind, travel, moving);
|
||||
crate::scenario::publish_rect("space_mouse:corner", rect);
|
||||
let mut corner = ui.new_child(egui::UiBuilder::new().id_salt("sc_space_mouse_corner").max_rect(rect));
|
||||
corner.set_opacity(0.6);
|
||||
draw(&corner, "corner", rect, kind, travel, moving);
|
||||
}
|
||||
|
||||
/// A trackball's reading is a speed: within the dead zone it is noise, and rolling the picture by
|
||||
/// it would make it creep.
|
||||
fn still_unless_moving(kind: Kind, mut travel: [f32; 6], moving: [bool; 6]) -> [f32; 6] {
|
||||
if kind == Kind::Trackball {
|
||||
for (value, &moving) in travel.iter_mut().zip(&moving) {
|
||||
if !moving {
|
||||
*value = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
travel
|
||||
}
|
||||
|
||||
/// The calibrated device as it is now, cap or trackball; a marker lights once its axis is past
|
||||
/// the dead zone, so it lights when the view would move. A trackball only rolls then, so it
|
||||
/// doesn't creep with the noise a dead zone hides.
|
||||
pub(crate) fn live(ui: &mut egui::Ui, calibration: &Calibration, raw: [i16; 6]) {
|
||||
let (rect, _) = ui.allocate_exact_size(SIZE, egui::Sense::hover());
|
||||
let (travel, moving) = (calibration.travel(raw), calibration.apply(raw).map(|value| value != 0));
|
||||
draw(ui, "live", rect, calibration.kind, still_unless_moving(calibration.kind, travel, moving), moving);
|
||||
}
|
||||
|
||||
/// One showing of a step's motion, in seconds: easing out to full deflection, holding there,
|
||||
/// easing back, then resting before it starts again.
|
||||
pub(crate) const DEMO_SECONDS: [f32; 4] = [1.5, 0.8, 0.7, 0.5];
|
||||
|
||||
/// How far through its travel a step's motion is, `seconds` after the step started (0 to 1).
|
||||
/// It always starts at rest, so every step begins with the cap or ball still.
|
||||
pub(crate) fn demo_swing(seconds: f32) -> f32 {
|
||||
let [out, hold, back, rest] = DEMO_SECONDS;
|
||||
let t = if seconds.is_finite() { seconds.max(0.0) % (out + hold + back + rest) } else { 0.0 };
|
||||
// Smoothstep: starts and stops gently.
|
||||
let ease = |x: f32| {
|
||||
let x = x.clamp(0.0, 1.0);
|
||||
x * x * (3.0 - 2.0 * x)
|
||||
};
|
||||
if t < out {
|
||||
ease(t / out)
|
||||
} else if t < out + hold {
|
||||
1.0
|
||||
} else {
|
||||
1.0 - ease((t - out - hold) / back)
|
||||
}
|
||||
}
|
||||
|
||||
/// The motion a calibration step asks for, played over and over, from rest each time and from
|
||||
/// rest again whenever the step changes.
|
||||
pub(crate) fn demo(ui: &mut egui::Ui, step: Step, kind: Kind) {
|
||||
let now = ui.input(|i| i.time);
|
||||
let id = egui::Id::new("sc_space_mouse_demo");
|
||||
let key = (step.axis, step.positive);
|
||||
let started = ui.data_mut(|data| {
|
||||
let (shown, started) = data.get_temp_mut_or_insert_with(id, || (key, now));
|
||||
if *shown != key {
|
||||
*shown = key;
|
||||
*started = now;
|
||||
}
|
||||
*started
|
||||
});
|
||||
let swing = demo_swing((now - started) as f32);
|
||||
let (mut travel, mut moving) = ([0.0; 6], [false; 6]);
|
||||
if let (Some(value), Some(live)) = (travel.get_mut(step.axis), moving.get_mut(step.axis)) {
|
||||
*value = if step.positive { swing } else { -swing };
|
||||
*live = swing > 0.15;
|
||||
}
|
||||
let (rect, _) = ui.allocate_exact_size(SIZE, egui::Sense::hover());
|
||||
draw(ui, DEMO, rect, kind, travel, moving);
|
||||
let focused = ui.input(|i| i.focused);
|
||||
ui.ctx().request_repaint_after(demo_repaint_after((now - started) as f32, focused));
|
||||
}
|
||||
|
||||
/// How long the demo waits before it is drawn again: 20 frames a second while the cap or ball moves,
|
||||
/// 4 while it holds or rests (nothing changes), and 2 when the window is not in use.
|
||||
pub(crate) fn demo_repaint_after(seconds: f32, focused: bool) -> std::time::Duration {
|
||||
use std::time::Duration;
|
||||
if !focused {
|
||||
Duration::from_millis(500)
|
||||
} else if (demo_swing(seconds) - demo_swing(seconds + 0.1)).abs() < 1e-6 {
|
||||
Duration::from_millis(250)
|
||||
} else {
|
||||
Duration::from_millis(50)
|
||||
}
|
||||
}
|
||||
|
||||
/// A level circle round the device's centre line.
|
||||
fn ring(radius: f32, height: f32) -> Vec<P3> {
|
||||
(0..48)
|
||||
.map(|i| {
|
||||
let (sin, cos) = (i as f32 / 48.0 * std::f32::consts::TAU).sin_cos();
|
||||
[radius * cos, radius * sin, height]
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) mod ball {
|
||||
use egui::{Pos2, Shape, Stroke};
|
||||
|
||||
use super::{BASE_HEIGHT, BASE_RADIUS, P3, add, apply, dot, eye, hull, ring, rotate_x, rotate_y, rotate_z, scaled, view};
|
||||
|
||||
const RADIUS: f32 = 0.6;
|
||||
/// The ball's centre; its lower part sits down in the scroll ring.
|
||||
const CENTRE: P3 = [0.0, 0.0, RING_HEIGHT + 0.2];
|
||||
/// The dots on the ball, spread evenly over it.
|
||||
pub(crate) const DOTS: usize = 90;
|
||||
/// The scroll ring round the ball, inside and outside, and its notches.
|
||||
const RING: [f32; 2] = [0.58, 0.72];
|
||||
const RING_HEIGHT: f32 = BASE_HEIGHT + 0.05;
|
||||
const NOTCHES: usize = 24;
|
||||
|
||||
/// A trackball: unlike a cap it doesn't spring back, so the ball's and the scroll ring's
|
||||
/// turns add up as they move.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct Ball {
|
||||
/// Columns of the ball's rotation.
|
||||
pub(super) turn: [P3; 3],
|
||||
/// How far the scroll ring has turned, in radians, clockwise seen from above.
|
||||
pub(super) wheel: f32,
|
||||
}
|
||||
|
||||
impl Ball {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { turn: rotate_z(0.0), wheel: 0.0 }
|
||||
}
|
||||
|
||||
/// Moves the ball for `seconds` at `rate` (turns per second at full deflection): rolled
|
||||
/// away from the user, rolled to the right, spun clockwise (seen from above), and the
|
||||
/// scroll ring turned clockwise.
|
||||
pub(crate) fn roll(&mut self, rate: [f32; 4], seconds: f32) {
|
||||
let [away, right, clockwise, wheel] = rate.map(|v| if v.is_finite() { v.clamp(-3.0, 3.0) } else { 0.0 });
|
||||
let seconds = if seconds.is_finite() { seconds.clamp(0.0, 0.1) } else { 0.0 };
|
||||
let turn = std::f32::consts::TAU * seconds;
|
||||
// Rolling away turns the top away (about right); rolling right turns it right (about away).
|
||||
let step = apply_all(&rotate_z(-clockwise * turn), apply_all(&rotate_y(right * turn), rotate_x(away * turn)));
|
||||
self.turn = orthonormal(apply_all(&step, self.turn));
|
||||
self.wheel = (self.wheel + wheel * turn) % std::f32::consts::TAU;
|
||||
}
|
||||
|
||||
/// Where dot `i` of the ball is now, as a direction from its centre.
|
||||
pub(crate) fn dot(&self, i: usize) -> P3 {
|
||||
// A Fibonacci sphere: even spacing without poles that crowd.
|
||||
let k = i as f32 + 0.5;
|
||||
let up = 1.0 - 2.0 * k / DOTS as f32;
|
||||
let round = (1.0 - up * up).max(0.0).sqrt();
|
||||
let (sin, cos) = (k * std::f32::consts::PI * (3.0 - 5.0_f32.sqrt())).sin_cos();
|
||||
apply(&self.turn, [round * cos, round * sin, up])
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws a trackball in `rect`: the ball in its scroll ring on the same base as the cap. The
|
||||
/// ball's dots slide as it rolls and spins and light while `rolling`; the ring's notches turn
|
||||
/// with it and light while `scrolling`.
|
||||
pub(crate) fn paint(ui: &egui::Ui, rect: egui::Rect, ball: &Ball, rolling: bool, scrolling: bool) {
|
||||
let painter = ui.painter_at(rect);
|
||||
let visuals = ui.visuals();
|
||||
let (scale, at) = view(rect);
|
||||
let (faint, accent) = (crate::theme::Tokens::get().text_dim, visuals.selection.stroke.color);
|
||||
let outline = Stroke::new(1.0, faint);
|
||||
let flat = |radius: f32, height: f32| ring(radius, height).into_iter().map(at).collect::<Vec<Pos2>>();
|
||||
let base: Vec<Pos2> = ring(BASE_RADIUS, 0.0).into_iter().chain(ring(BASE_RADIUS, BASE_HEIGHT)).map(at).collect();
|
||||
painter.add(Shape::convex_polygon(hull(base), visuals.widgets.noninteractive.bg_stroke.color, outline));
|
||||
painter.add(Shape::convex_polygon(flat(BASE_RADIUS, BASE_HEIGHT), visuals.widgets.inactive.bg_fill, outline));
|
||||
|
||||
// The scroll ring: raised a little off the base, its notches turned by the wheel.
|
||||
let [inner, outer] = RING;
|
||||
let raised: Vec<Pos2> = ring(outer, BASE_HEIGHT).into_iter().chain(ring(outer, RING_HEIGHT)).map(at).collect();
|
||||
painter.add(Shape::convex_polygon(hull(raised), visuals.widgets.active.bg_fill, outline));
|
||||
painter.add(Shape::convex_polygon(flat(outer, RING_HEIGHT), visuals.widgets.hovered.bg_fill, outline));
|
||||
let notch = Stroke::new(if scrolling { 2.0 } else { 1.0 }, if scrolling { accent } else { faint });
|
||||
let (ring_top, ring_side) = (visuals.widgets.hovered.bg_fill, visuals.widgets.active.bg_fill);
|
||||
// A band between two circles (radius, height), from ring point `from` to `to` (of 48).
|
||||
let band = |from: usize, to: usize, [ra, ha]: [f32; 2], [rb, hb]: [f32; 2], colour| {
|
||||
let mut mesh = egui::Mesh::default();
|
||||
for i in from..=to {
|
||||
let (sin, cos) = (i as f32 / 48.0 * std::f32::consts::TAU).sin_cos();
|
||||
mesh.colored_vertex(at([ra * cos, ra * sin, ha]), colour);
|
||||
mesh.colored_vertex(at([rb * cos, rb * sin, hb]), colour);
|
||||
}
|
||||
for k in 0..(to - from) as u32 {
|
||||
mesh.add_triangle(2 * k, 2 * k + 1, 2 * k + 2);
|
||||
mesh.add_triangle(2 * k + 1, 2 * k + 3, 2 * k + 2);
|
||||
}
|
||||
painter.add(Shape::mesh(mesh));
|
||||
};
|
||||
// Half the ring's top, from ring point `from` to `to`, with its notches.
|
||||
let half = |from: usize, to: usize, front: bool| {
|
||||
band(from, to, [inner, RING_HEIGHT], [outer, RING_HEIGHT], ring_top);
|
||||
for i in 0..NOTCHES {
|
||||
let (sin, cos) = (i as f32 / NOTCHES as f32 * std::f32::consts::TAU - ball.wheel).sin_cos();
|
||||
if (sin < 0.0) == front {
|
||||
let (a, b) = (inner + 0.03, outer - 0.03);
|
||||
painter.line_segment([at([a * cos, a * sin, RING_HEIGHT]), at([b * cos, b * sin, RING_HEIGHT])], notch);
|
||||
}
|
||||
}
|
||||
};
|
||||
// The far half and the hole go behind the ball, the near half in front of it, so the
|
||||
// ball sits down in the ring.
|
||||
half(0, 24, false);
|
||||
painter.add(Shape::closed_line(flat(outer, RING_HEIGHT), outline));
|
||||
painter.add(Shape::convex_polygon(flat(inner, RING_HEIGHT), visuals.extreme_bg_color, outline));
|
||||
|
||||
// Seen straight along any line, a sphere is a circle.
|
||||
painter.circle(at(CENTRE), RADIUS * scale, visuals.widgets.active.bg_fill, outline);
|
||||
let colour = if rolling { accent } else { faint };
|
||||
for i in 0..DOTS {
|
||||
let d = ball.dot(i);
|
||||
// Only the near half shows; dots shrink and fade toward the edge, which reads as round.
|
||||
let facing = dot(d, eye());
|
||||
if facing <= 0.05 {
|
||||
continue;
|
||||
}
|
||||
let p = at(add(CENTRE, scaled(d, RADIUS)));
|
||||
painter.circle_filled(p, scale * 0.045 * (0.35 + 0.65 * facing), colour.gamma_multiply(0.35 + 0.65 * facing));
|
||||
}
|
||||
// Everything nearer than the ball, base and ring, over the ball's lower part.
|
||||
let (base_side, base_top) = (visuals.widgets.noninteractive.bg_stroke.color, visuals.widgets.inactive.bg_fill);
|
||||
band(24, 48, [BASE_RADIUS, 0.0], [BASE_RADIUS, BASE_HEIGHT], base_side);
|
||||
band(24, 48, [outer, BASE_HEIGHT], [BASE_RADIUS, BASE_HEIGHT], base_top);
|
||||
band(24, 48, [outer, BASE_HEIGHT], [outer, RING_HEIGHT], ring_side);
|
||||
half(24, 48, true);
|
||||
let edges = [[inner, RING_HEIGHT], [outer, RING_HEIGHT], [outer, BASE_HEIGHT], [BASE_RADIUS, BASE_HEIGHT], [BASE_RADIUS, 0.0]];
|
||||
for [radius, height] in edges {
|
||||
let edge = ring(radius, height);
|
||||
painter.add(Shape::line(edge.iter().skip(24).chain(edge.first()).map(|&p| at(p)).collect(), outline));
|
||||
}
|
||||
}
|
||||
|
||||
/// `a` after `b`, as rotations.
|
||||
fn apply_all(a: &[P3; 3], b: [P3; 3]) -> [P3; 3] {
|
||||
b.map(|column| apply(a, column))
|
||||
}
|
||||
|
||||
/// `m` put back to a pure rotation, so many small turns don't skew or shrink it.
|
||||
fn orthonormal(m: [P3; 3]) -> [P3; 3] {
|
||||
let unit = |v: P3| {
|
||||
let length = dot(v, v).sqrt();
|
||||
if length > 1e-6 && length.is_finite() { scaled(v, 1.0 / length) } else { [0.0; 3] }
|
||||
};
|
||||
let x = unit(m[0]);
|
||||
let y = unit(add(m[1], scaled(x, -dot(m[1], x))));
|
||||
let z = [x[1] * y[2] - x[2] * y[1], x[2] * y[0] - x[0] * y[2], x[0] * y[1] - x[1] * y[0]];
|
||||
if dot(z, z) < 0.5 { rotate_z(0.0) } else { [x, y, z] }
|
||||
}
|
||||
}
|
||||
|
||||
/// The convex hull of `points`, in order round it (Andrew's monotone chain).
|
||||
fn hull(mut points: Vec<Pos2>) -> Vec<Pos2> {
|
||||
points.sort_by(|a, b| a.x.total_cmp(&b.x).then(a.y.total_cmp(&b.y)));
|
||||
let cross = |o: Pos2, a: Pos2, b: Pos2| (a - o).x * (b - o).y - (a - o).y * (b - o).x;
|
||||
let mut out: Vec<Pos2> = Vec::with_capacity(points.len() + 1);
|
||||
for pass in [false, true] {
|
||||
let start = out.len();
|
||||
let ordered: Box<dyn Iterator<Item = &Pos2>> = if pass { Box::new(points.iter().rev()) } else { Box::new(points.iter()) };
|
||||
for &p in ordered {
|
||||
while out.len() >= start + 2 && cross(out[out.len() - 2], out[out.len() - 1], p) <= 0.0 {
|
||||
out.pop();
|
||||
}
|
||||
out.push(p);
|
||||
}
|
||||
out.pop();
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn unit(i: usize) -> P3 {
|
||||
let mut v = [0.0; 3];
|
||||
v[i] = 1.0;
|
||||
v
|
||||
}
|
||||
|
||||
fn apply(columns: &[P3; 3], p: P3) -> P3 {
|
||||
[0, 1, 2].map(|row| columns[0][row] * p[0] + columns[1][row] * p[1] + columns[2][row] * p[2])
|
||||
}
|
||||
|
||||
fn add(a: P3, b: P3) -> P3 {
|
||||
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
|
||||
}
|
||||
|
||||
fn scaled(a: P3, k: f32) -> P3 {
|
||||
a.map(|v| v * k)
|
||||
}
|
||||
|
||||
fn dot(a: P3, b: P3) -> f32 {
|
||||
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
|
||||
}
|
||||
|
||||
/// Turns about right: positive takes up toward away.
|
||||
fn rotate_x(angle: f32) -> [P3; 3] {
|
||||
let (sin, cos) = angle.sin_cos();
|
||||
[[1.0, 0.0, 0.0], [0.0, cos, -sin], [0.0, sin, cos]]
|
||||
}
|
||||
|
||||
/// Turns about away: positive takes up toward right.
|
||||
fn rotate_y(angle: f32) -> [P3; 3] {
|
||||
let (sin, cos) = angle.sin_cos();
|
||||
[[cos, 0.0, -sin], [0.0, 1.0, 0.0], [sin, 0.0, cos]]
|
||||
}
|
||||
|
||||
/// Turns about up: positive is anticlockwise seen from above.
|
||||
fn rotate_z(angle: f32) -> [P3; 3] {
|
||||
let (sin, cos) = angle.sin_cos();
|
||||
[[cos, sin, 0.0], [-sin, cos, 0.0], [0.0, 0.0, 1.0]]
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
use super::ball::{Ball, DOTS};
|
||||
use super::*;
|
||||
|
||||
fn reading(axis: usize, value: f32) -> [f32; 6] {
|
||||
let mut travel = [0.0; 6];
|
||||
travel[axis] = value;
|
||||
travel
|
||||
}
|
||||
|
||||
/// Where a point on the cap lands on screen for a reading.
|
||||
fn on_screen(travel: [f32; 6], p: P3) -> [f32; 2] {
|
||||
project(Pose::new(travel).place(p))
|
||||
}
|
||||
|
||||
const TOP_CENTRE: P3 = [0.0, 0.0, CAP_HEIGHT];
|
||||
const FRONT: P3 = [0.0, -CAP_RADIUS, CAP_HEIGHT / 2.0];
|
||||
|
||||
#[test]
|
||||
fn the_cap_moves_the_way_the_reading_says() {
|
||||
let rest = on_screen([0.0; 6], TOP_CENTRE);
|
||||
// Slid right: right on screen.
|
||||
assert!(on_screen(reading(0, 1.0), TOP_CENTRE)[0] > rest[0]);
|
||||
// Pressed down: lower on screen; pulled up: higher.
|
||||
assert!(on_screen(reading(2, 1.0), TOP_CENTRE)[1] > rest[1]);
|
||||
assert!(on_screen(reading(2, -1.0), TOP_CENTRE)[1] < rest[1]);
|
||||
// Pulled toward the user: nearer, so lower on screen.
|
||||
assert!(Pose::new(reading(1, 1.0)).place(TOP_CENTRE)[1] < 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tilts_and_twists_turn_the_cap_the_right_way() {
|
||||
let axis = |travel| apply(&Pose::new(travel).turn, [0.0, 0.0, 1.0]);
|
||||
assert!((axis([0.0; 6])[2] - 1.0).abs() < 1e-6);
|
||||
// +Rx: the top leans toward the user (the near edge down); tilted left: to the left.
|
||||
assert!(axis(reading(3, 1.0))[1] < -0.1);
|
||||
assert!(axis(reading(4, 1.0))[0] < -0.1);
|
||||
// Twisted clockwise (from above): the groove on the front swings to the user's left.
|
||||
let rest = Pose::new([0.0; 6]).place(FRONT);
|
||||
let twisted = Pose::new(reading(5, 1.0)).place(FRONT);
|
||||
assert!(twisted[0] < rest[0] - 0.1);
|
||||
assert!((twisted[2] - rest[2]).abs() < 1e-6, "a twist alone keeps the cap's height");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_readings_stay_finite() {
|
||||
for value in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, 1e30] {
|
||||
let pose = Pose::new([value; 6]);
|
||||
assert!(pose.place(TOP_CENTRE).iter().all(|v| v.is_finite()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_hull_keeps_only_the_outline() {
|
||||
let square = [(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (1.0, 1.0), (1.0, 0.0)];
|
||||
let hull = hull(square.iter().map(|&(x, y)| Pos2::new(x, y)).collect());
|
||||
assert_eq!(hull.len(), 4);
|
||||
assert!(!hull.contains(&Pos2::new(1.0, 1.0)));
|
||||
assert!(super::hull(Vec::new()).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_ball_rolls_the_way_it_is_pushed() {
|
||||
let top_after = |rate: [f32; 3]| {
|
||||
let mut ball = Ball::new();
|
||||
// A quarter turn, in small steps as frames give it.
|
||||
for _ in 0..25 {
|
||||
ball.roll([rate[0], rate[1], rate[2], 0.0], 0.01);
|
||||
}
|
||||
apply(&ball.turn, [0.0, 0.0, 1.0])
|
||||
};
|
||||
assert!(top_after([1.0, 0.0, 0.0])[1] > 0.9, "rolled away: the top goes away");
|
||||
assert!(top_after([0.0, 1.0, 0.0])[0] > 0.9, "rolled right: the top goes right");
|
||||
let mut ball = Ball::new();
|
||||
for _ in 0..25 {
|
||||
ball.roll([0.0, 0.0, 1.0, 0.0], 0.01);
|
||||
}
|
||||
// Spun clockwise from above: what faced the user turns to their left.
|
||||
assert!(apply(&ball.turn, [0.0, -1.0, 0.0])[0] < -0.9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_ball_stays_a_rotation() {
|
||||
let mut ball = Ball::new();
|
||||
for i in 0..20_000 {
|
||||
ball.roll([0.7, -0.3, (i % 7) as f32 * 0.2, 0.5], 0.016);
|
||||
}
|
||||
for (i, a) in ball.turn.iter().enumerate() {
|
||||
for (j, b) in ball.turn.iter().enumerate() {
|
||||
let expected = if i == j { 1.0 } else { 0.0 };
|
||||
assert!((dot(*a, *b) - expected).abs() < 1e-4);
|
||||
}
|
||||
}
|
||||
for value in [f32::NAN, f32::INFINITY, -1e30] {
|
||||
ball.roll([value; 4], value);
|
||||
assert!(ball.turn.iter().flatten().all(|v| v.is_finite()) && ball.wheel.is_finite());
|
||||
assert!((0..DOTS).all(|i| ball.dot(i).iter().all(|v| v.is_finite())));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_scroll_ring_turns_and_keeps_its_turn() {
|
||||
let mut ball = Ball::new();
|
||||
for _ in 0..10 {
|
||||
ball.roll([0.0, 0.0, 0.0, 1.0], 0.01);
|
||||
}
|
||||
let turned = ball.wheel;
|
||||
assert!((turned - std::f32::consts::TAU * 0.1).abs() < 1e-4);
|
||||
// Let go: a scroll ring stays where it was turned to, and the ball doesn't move with it.
|
||||
ball.roll([0.0; 4], 0.01);
|
||||
assert_eq!(ball.wheel, turned);
|
||||
assert!((apply(&ball.turn, [0.0, 0.0, 1.0])[2] - 1.0).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_step_starts_at_rest_and_eases_out_to_full() {
|
||||
let [out, hold, back, rest] = DEMO_SECONDS;
|
||||
assert_eq!(demo_swing(0.0), 0.0);
|
||||
assert!((demo_swing(out) - 1.0).abs() < 1e-6);
|
||||
assert_eq!(demo_swing(out + hold / 2.0), 1.0);
|
||||
assert_eq!(demo_swing(out + hold + back + rest / 2.0), 0.0);
|
||||
// Gently: it creeps away from rest rather than jumping, and never goes back on itself on the way out.
|
||||
assert!(demo_swing(out * 0.05) < 0.01);
|
||||
let samples: Vec<f32> = (0..=100).map(|i| demo_swing(out * i as f32 / 100.0)).collect();
|
||||
assert!(samples.windows(2).all(|w| w[1] >= w[0]));
|
||||
// Again from rest after a full showing; nothing odd for odd times.
|
||||
assert_eq!(demo_swing(out + hold + back + rest), 0.0);
|
||||
for seconds in [f32::NAN, f32::INFINITY, -5.0] {
|
||||
assert_eq!(demo_swing(seconds), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_corner_picture_goes_where_it_is_told_and_clears_the_bars() {
|
||||
use crate::prefs::SpaceMouseGhost::{LeftViewport as Current, LeftWindow as Left, RightViewport as Right};
|
||||
let view = egui::Rect::from_min_size(egui::pos2(0.0, 0.0), egui::vec2(800.0, 600.0));
|
||||
let size = egui::vec2(160.0, 120.0);
|
||||
// Free: lower left for Current and Left, lower right for Right.
|
||||
assert_eq!(corner_rect(view, Current, &[]).left_bottom(), egui::pos2(12.0, 588.0));
|
||||
assert_eq!(corner_rect(view, Left, &[]).left_bottom(), egui::pos2(12.0, 588.0));
|
||||
assert_eq!(corner_rect(view, Right, &[]).right_bottom(), egui::pos2(788.0, 588.0));
|
||||
// The History bar along the bottom pushes each one up to clear it.
|
||||
let history = egui::Rect::from_min_max(egui::pos2(0.0, 556.0), egui::pos2(800.0, 596.0));
|
||||
for position in [Current, Left, Right] {
|
||||
let rect = corner_rect(view, position, &[history]);
|
||||
assert_eq!(rect.size(), size);
|
||||
assert!(!rect.intersects(history) && rect.bottom() <= history.top() - 12.0 + f32::EPSILON, "{position:?}");
|
||||
}
|
||||
// A bar that does not reach the picture leaves it where it was.
|
||||
let elsewhere = egui::Rect::from_min_max(egui::pos2(300.0, 556.0), egui::pos2(500.0, 596.0));
|
||||
for position in [Current, Left, Right] {
|
||||
assert_eq!(corner_rect(view, position, &[elsewhere]), corner_rect(view, position, &[]));
|
||||
}
|
||||
// The navigation bar in the middle of a narrow view is not covered either, nor is anything else stacked.
|
||||
let narrow = egui::Rect::from_min_size(egui::pos2(0.0, 0.0), egui::vec2(420.0, 600.0));
|
||||
let navigation = egui::Rect::from_min_max(egui::pos2(100.0, 520.0), egui::pos2(330.0, 560.0));
|
||||
let history = egui::Rect::from_min_max(egui::pos2(0.0, 566.0), egui::pos2(420.0, 600.0));
|
||||
for position in [Left, Right] {
|
||||
let rect = corner_rect(narrow, position, &[history, navigation]);
|
||||
assert!(narrow.contains_rect(rect), "{position:?} stays inside the view");
|
||||
assert!(!rect.intersects(history) && !rect.intersects(navigation), "{position:?} clears both bars: {rect:?}");
|
||||
}
|
||||
// A view narrower than the picture keeps it at the left edge rather than outside.
|
||||
let tiny = egui::Rect::from_min_size(egui::pos2(50.0, 0.0), egui::vec2(100.0, 400.0));
|
||||
assert!(corner_rect(tiny, Right, &[]).left() >= tiny.left());
|
||||
// An empty bar is no bar.
|
||||
assert_eq!(corner_rect(view, Left, &[egui::Rect::NOTHING]), corner_rect(view, Left, &[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stacked_bars_in_a_short_view_do_not_push_the_picture_out_of_it() {
|
||||
use crate::prefs::SpaceMouseGhost::{LeftViewport, LeftWindow, RightViewport};
|
||||
let view = egui::Rect::from_min_size(egui::pos2(0.0, 0.0), egui::vec2(500.0, 220.0));
|
||||
let history = egui::Rect::from_min_max(egui::pos2(0.0, 180.0), egui::pos2(500.0, 220.0));
|
||||
let navigation = egui::Rect::from_min_max(egui::pos2(0.0, 130.0), egui::pos2(500.0, 170.0));
|
||||
let chip = egui::Rect::from_min_max(egui::pos2(0.0, 80.0), egui::pos2(500.0, 120.0));
|
||||
for position in [LeftViewport, RightViewport, LeftWindow] {
|
||||
let rect = corner_rect(view, position, &[history, navigation, chip]);
|
||||
assert!(rect.top() >= view.top() && view.contains_rect(rect), "{position:?}: {rect:?} is outside {view:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// How much one frame of each picture costs the painter: shapes and tessellated vertices. A ceiling
|
||||
/// each, so a picture cannot quietly grow expensive (the calibration window once ran a core flat out).
|
||||
#[test]
|
||||
fn the_pictures_cost_a_bounded_amount_to_draw_per_frame() {
|
||||
let ctx = crate::fonts::test_context();
|
||||
let calibration = Calibration {
|
||||
dead_zone_share: 0.0,
|
||||
kind: Kind::ReturnToCenter,
|
||||
axes: std::array::from_fn(|source| solidcraft_space_mouse::AxisCalibration {
|
||||
source,
|
||||
reversed: false,
|
||||
rest: 0,
|
||||
dead_zone: 0,
|
||||
full: [350; 2],
|
||||
}),
|
||||
};
|
||||
let cost = |draw_it: &mut dyn FnMut(&mut egui::Ui)| {
|
||||
let mut output = ctx.run_ui(
|
||||
egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(800.0, 600.0))), ..Default::default() },
|
||||
|ui| draw_it(ui),
|
||||
);
|
||||
output.textures_delta.clear();
|
||||
let shapes = output.shapes.len();
|
||||
let vertices: usize = ctx
|
||||
.tessellate(output.shapes, 1.0)
|
||||
.iter()
|
||||
.filter_map(|p| if let egui::epaint::Primitive::Mesh(m) = &p.primitive { Some(m.vertices.len()) } else { None })
|
||||
.sum();
|
||||
(shapes, vertices)
|
||||
};
|
||||
let cap = cost(&mut |ui| live(ui, &calibration, [60, -40, 100, 10, -20, 30]));
|
||||
let ball = Calibration { kind: Kind::Trackball, ..calibration };
|
||||
let rolling = cost(&mut |ui| live(ui, &ball, [60, -40, 100, 10, -20, 30]));
|
||||
let demo_cost = cost(&mut |ui| demo(ui, solidcraft_space_mouse::STEPS[0], Kind::ReturnToCenter));
|
||||
eprintln!("PICTURE COST cap (shapes, vertices) {cap:?} trackball {rolling:?} demo {demo_cost:?}");
|
||||
for (name, (shapes, vertices)) in [("cap", cap), ("trackball", rolling), ("demo", demo_cost)] {
|
||||
assert!(shapes <= CEILING_SHAPES && vertices <= CEILING_VERTICES, "{name}: {shapes} shapes, {vertices} vertices");
|
||||
}
|
||||
}
|
||||
|
||||
/// Measured at 45 shapes and 1,374 vertices for the cap, 82 and 2,534 for the trackball.
|
||||
const CEILING_SHAPES: usize = 150;
|
||||
const CEILING_VERTICES: usize = 4_000;
|
||||
|
||||
#[test]
|
||||
fn the_demo_repaints_at_20_fps_only_while_it_moves() {
|
||||
use std::time::Duration;
|
||||
let [out, hold, back, rest] = DEMO_SECONDS;
|
||||
assert_eq!(demo_repaint_after(out / 2.0, true), Duration::from_millis(50), "moving out");
|
||||
assert_eq!(demo_repaint_after(out + hold / 2.0, true), Duration::from_millis(250), "holding");
|
||||
assert_eq!(demo_repaint_after(out + hold + back / 2.0, true), Duration::from_millis(50), "moving back");
|
||||
assert_eq!(demo_repaint_after(out + hold + back + rest / 2.0 - 0.1, true), Duration::from_millis(250), "resting");
|
||||
assert_eq!(demo_repaint_after(out / 2.0, false), Duration::from_millis(500), "not in use");
|
||||
}
|
||||
|
||||
/// The demo asks for its own pace and nothing shorter, for a ball as for a cap: 50 ms while it moves with the
|
||||
/// window in use, and 500 ms when the window is not (the ball's own 16 ms request used to win both).
|
||||
#[test]
|
||||
fn the_demo_asks_for_its_own_repaint_pace_for_a_ball_and_a_cap() {
|
||||
let step = Step { axis: 1, positive: true };
|
||||
for kind in [Kind::Trackball, Kind::ReturnToCenter] {
|
||||
for (focused, expected) in [(true, std::time::Duration::from_millis(50)), (false, std::time::Duration::from_millis(500))] {
|
||||
let ctx = crate::fonts::test_context();
|
||||
let mut delay = None;
|
||||
for time in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6] {
|
||||
let mut output = ctx.run_ui(egui::RawInput { time: Some(time), focused, ..Default::default() }, |ui| demo(ui, step, kind));
|
||||
output.textures_delta.clear();
|
||||
delay = output.viewport_output.get(&egui::ViewportId::ROOT).map(|viewport| viewport.repaint_delay);
|
||||
}
|
||||
// egui takes a frame's worth off the delay it reports, so allow for that.
|
||||
let delay = delay.expect("a repaint delay");
|
||||
assert!(
|
||||
delay <= expected && delay + std::time::Duration::from_millis(20) >= expected,
|
||||
"{kind:?}, focused {focused}: {delay:?}, wanted about {expected:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,904 @@
|
||||
//! The offer to calibrate a 3D mouse that has no calibration yet: people don't know calibration
|
||||
//! exists, and it is what fixes swapped or reversed axes. It shows once per device, as one of the
|
||||
//! shared toasts (`viewport::transient_messages`), until the user calibrates or closes it.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::SolveApp;
|
||||
|
||||
/// Whether to offer calibration for `device`: 3D mouse navigation is on, there is a device, it
|
||||
/// has no calibration saved, the offer has not been made (and dismissed) for it, and the user has
|
||||
/// not asked never to see it (`never`, one flag for every device, not kept per device). The 3Dconnexion
|
||||
/// driver (macOS, key `3dxware`) already applies its own settings, so calibration isn't offered
|
||||
/// for it.
|
||||
pub fn should_offer(device: Option<&str>, calibrated: bool, offered: &BTreeSet<String>, enabled: bool, never: bool) -> bool {
|
||||
let Some(device) = device else { return false };
|
||||
enabled && !never && !calibrated && !offered.contains(device) && !device.starts_with("3dxware")
|
||||
}
|
||||
|
||||
/// Whether the card offers to swap to the direct (hidraw) reader: the user chose spacenavd, it is
|
||||
/// what is being read, and a direct device is there to read instead. Under Automatic the device is
|
||||
/// read whenever it opens, so spacenavd being read with a device there means the device does not open
|
||||
/// (and Direct would fall back the same way): a swap would do nothing, so it is not offered.
|
||||
pub fn swap_offered(device: &str, drivers: Option<crate::space_mouse::DriversPresent>, driver: crate::prefs::SpaceMouseDriver) -> bool {
|
||||
device == "spacenavd" && driver == crate::prefs::SpaceMouseDriver::Spacenavd && drivers.is_some_and(|present| present.direct)
|
||||
}
|
||||
|
||||
/// How long the notice stays when nobody is pointing at it: long enough to read it, find the
|
||||
/// mouse and decide, which 15 seconds was too short for.
|
||||
const SHOW_SECONDS: f64 = 30.0;
|
||||
|
||||
/// A longer gap than this between two looks at the offer means it was not on screen meanwhile.
|
||||
const AWAY_SECONDS: f64 = 2.0;
|
||||
|
||||
/// Whether the notice, first shown or last pointed at `since`, has been up long enough at `now`.
|
||||
pub fn expired(since: f64, now: f64) -> bool {
|
||||
now - since >= SHOW_SECONDS
|
||||
}
|
||||
|
||||
/// What the offer's toast can do when clicked.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Action {
|
||||
/// Opens Preferences at Navigation and starts the calibration window.
|
||||
Calibrate,
|
||||
/// Records this device as offered.
|
||||
Close,
|
||||
/// Records that no offer is wanted any more.
|
||||
Never,
|
||||
/// Reads the device directly instead of through spacenavd.
|
||||
Swap,
|
||||
}
|
||||
|
||||
impl Action {
|
||||
/// What the button is for, which sets its look and place on the toast.
|
||||
pub(crate) fn kind(self) -> crate::viewport::ToastKind {
|
||||
use crate::viewport::ToastKind;
|
||||
match self {
|
||||
Self::Calibrate => ToastKind::Primary,
|
||||
Self::Swap => ToastKind::Secondary,
|
||||
Self::Close | Self::Never => ToastKind::Dismiss,
|
||||
}
|
||||
}
|
||||
|
||||
/// The button's text, as a message id.
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Calibrate => "ui.3d_mouse_calibrate",
|
||||
Self::Close => "ui.close",
|
||||
Self::Never => "ui.3d_mouse_offer_never",
|
||||
Self::Swap => "ui.3d_mouse_offer_swap",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An offer to show now: its device and its actions (the text is `ui.3d_mouse_offer`).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Offer {
|
||||
pub device: String,
|
||||
pub actions: Vec<Action>,
|
||||
}
|
||||
|
||||
/// The offer due this frame, if any, for the shared toast (`viewport::transient_messages`) to draw.
|
||||
/// It is due when `should_offer` says so, the window has focus, no picker or load has the app, no
|
||||
/// issue report holds CAD input, and neither Preferences nor the calibration window is open. It goes
|
||||
/// after `SHOW_SECONDS` unless the pointer is on it (`keep`; then only for this run: it is offered
|
||||
/// again next time until closed or calibrated).
|
||||
///
|
||||
/// A status or computing toast appearing while it is up, or already up, hides it for the run (nothing
|
||||
/// is recorded).
|
||||
///
|
||||
/// Everything it reads and writes outside its own state, so a reviewer can check it:
|
||||
/// - Reads: the device key, saved calibrations, the offered set, the never switch and the 3D mouse
|
||||
/// enable switch (`app.space_mouse.device`, `app.preferences`); the readers present
|
||||
/// (`space_mouse.drivers`); `ctx.input` focus, time and pointer position; `app.status`, `computing_since`,
|
||||
/// `file_picker_busy`, `file_load.busy`, `prefs_window.open`, `space_mouse.calibrating`,
|
||||
/// `issue_report::cad_input_blocked`.
|
||||
/// - Writes, only its own: `space_mouse.offer_since`, `space_mouse.offer_hidden`;
|
||||
/// `space_mouse_offered` and `space_mouse_offer_never` (preferences) when the user clicks Close,
|
||||
/// Calibrate or Never.
|
||||
/// - Calibrate opens Preferences at Navigation and starts the calibration window
|
||||
/// (`prefs::open_at`, `space_mouse_calibration::start`), exactly as Preferences does. Swap (offered only
|
||||
/// when the user chose spacenavd and a device is there) sets `space_mouse_driver` to Direct, the same
|
||||
/// setting Preferences does.
|
||||
/// - It draws nothing and creates no layer: the toast is the shared one. It never touches
|
||||
/// `ctx.memory`, focus, layer order, keyboard events, dialogs, tools, the selection or the document.
|
||||
pub fn due(app: &mut SolveApp, ctx: &egui::Context) -> Option<Offer> {
|
||||
let device = offerable(app)?;
|
||||
if input_blocked(app, ctx) {
|
||||
return None;
|
||||
}
|
||||
// Once the user is working, a status or computing toast says so: the offer goes for this run (not
|
||||
// recorded, so it can come back next start), and does not show while one is up.
|
||||
if status_up(app) {
|
||||
app.space_mouse.offer_hidden.insert(device);
|
||||
app.space_mouse.offer_since = None;
|
||||
return None;
|
||||
}
|
||||
let now = ctx.input(|i| i.time);
|
||||
// Time away does not count as time shown: after a gap (the window was in the background, Preferences
|
||||
// or the start page was up, the toast could not be drawn) the 30 seconds start again.
|
||||
let away = now - app.space_mouse.offer_seen > AWAY_SECONDS;
|
||||
app.space_mouse.offer_seen = now;
|
||||
let since = match &app.space_mouse.offer_since {
|
||||
Some((shown, since)) if *shown == device && !away => *since,
|
||||
_ => now,
|
||||
};
|
||||
app.space_mouse.offer_since = Some((device.clone(), since));
|
||||
if expired(since, now) {
|
||||
app.space_mouse.offer_hidden.insert(device);
|
||||
return None;
|
||||
}
|
||||
let mut actions = vec![Action::Calibrate];
|
||||
if swap_offered(&device, app.space_mouse.drivers, app.preferences.space_mouse_driver) {
|
||||
actions.push(Action::Swap);
|
||||
}
|
||||
actions.extend([Action::Close, Action::Never]);
|
||||
Some(Offer { device, actions })
|
||||
}
|
||||
|
||||
/// The toast was drawn at `rect`: a pointer on it keeps it from expiring.
|
||||
pub fn keep(app: &mut SolveApp, ctx: &egui::Context, rect: egui::Rect) {
|
||||
if ctx.input(|i| i.pointer.hover_pos()).is_some_and(|pos| rect.contains(pos)) {
|
||||
let now = ctx.input(|i| i.time);
|
||||
app.space_mouse.offer_since = app.space_mouse.offer_since.take().map(|(device, _)| (device, now));
|
||||
}
|
||||
}
|
||||
|
||||
/// The device the offer is for now: the current device when it should be offered one and the offer
|
||||
/// has not been hidden for the run. No side effects.
|
||||
pub(crate) fn offerable(app: &SolveApp) -> Option<String> {
|
||||
let calibrated = app.space_mouse.device.as_ref().is_some_and(|device| app.preferences.space_mouse_calibrations.contains_key(device));
|
||||
app.space_mouse.device.clone().filter(|device| {
|
||||
should_offer(
|
||||
Some(device),
|
||||
calibrated,
|
||||
&app.preferences.space_mouse_offered,
|
||||
app.preferences.space_mouse_enabled,
|
||||
app.preferences.space_mouse_offer_never,
|
||||
) && !app.space_mouse.offer_hidden.contains(device)
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether something else has the input or the screen: the window is in the background, a file is
|
||||
/// being picked or loaded, an issue report takes input, Preferences or the calibration window is up, or
|
||||
/// the start page is. No side effects.
|
||||
fn input_blocked(app: &SolveApp, ctx: &egui::Context) -> bool {
|
||||
!ctx.input(|i| i.focused)
|
||||
|| app.file_picker_busy()
|
||||
|| app.file_load.busy()
|
||||
|| crate::issue_report::cad_input_blocked()
|
||||
|| app.prefs_window.open
|
||||
|| app.space_mouse.calibrating.is_some()
|
||||
|| app.home.open
|
||||
}
|
||||
|
||||
/// A status or computing toast is up. No side effects.
|
||||
fn status_up(app: &SolveApp) -> bool {
|
||||
app.status.is_some() || app.computing_since().is_some()
|
||||
}
|
||||
|
||||
/// Whether `action` of `offer` may be taken now, with no side effects: the offer is still for the
|
||||
/// current device and still due (not dismissed, not hidden, navigation on), nothing blocks input, no
|
||||
/// status toast has taken its place, and the action is still on offer (Swap needs `swap_offered`).
|
||||
/// `due` and `act` both stand on the same pieces.
|
||||
pub fn eligible(app: &SolveApp, ctx: &egui::Context, offer: &Offer, action: Action) -> bool {
|
||||
offerable(app).as_deref() == Some(offer.device.as_str())
|
||||
&& !input_blocked(app, ctx)
|
||||
&& !status_up(app)
|
||||
&& match action {
|
||||
Action::Swap => swap_offered(&offer.device, app.space_mouse.drivers, app.preferences.space_mouse_driver),
|
||||
Action::Calibrate | Action::Close | Action::Never => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// The user clicked `action` on the toast of `offer`. Taken only if it is still eligible (`eligible`): a
|
||||
/// click that arrives after the device changed, after something took the input, or for an action no
|
||||
/// longer on offer does nothing, records nothing for the old device and changes neither the Never flag
|
||||
/// nor the driver setting.
|
||||
pub fn act(app: &mut SolveApp, ctx: &egui::Context, offer: &Offer, action: Action) {
|
||||
if !eligible(app, ctx, offer, action) {
|
||||
return;
|
||||
}
|
||||
match action {
|
||||
Action::Calibrate | Action::Close => {
|
||||
app.space_mouse.offer_since = None;
|
||||
app.preferences.space_mouse_offered.insert(offer.device.clone());
|
||||
if action == Action::Calibrate {
|
||||
// The calibration window lives with Preferences, at Navigation.
|
||||
crate::prefs::open_at(app, crate::prefs::PAGE_NAVIGATION);
|
||||
crate::space_mouse_calibration::start(app);
|
||||
}
|
||||
}
|
||||
Action::Never => {
|
||||
app.preferences.space_mouse_offer_never = true;
|
||||
app.space_mouse.offer_since = None;
|
||||
}
|
||||
Action::Swap => {
|
||||
// The same setting as Preferences, read live by the reader. The device is not marked
|
||||
// offered: read directly it has its own key, and it may still want a calibration.
|
||||
app.preferences.space_mouse_driver = crate::prefs::SpaceMouseDriver::Direct;
|
||||
app.space_mouse.offer_since = None;
|
||||
app.space_mouse.offer_hidden.insert(offer.device.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn the_offer_is_made_once_to_an_uncalibrated_device_with_navigation_on() {
|
||||
let should_offer = |device, calibrated, offered: &BTreeSet<String>, enabled| should_offer(device, calibrated, offered, enabled, false);
|
||||
let none = BTreeSet::new();
|
||||
let seen: BTreeSet<String> = ["hid 046d:c62b".to_string()].into();
|
||||
for device in [Some("hid 046d:c62b"), Some("spacenavd"), Some("web 256f:c63a"), None] {
|
||||
for calibrated in [false, true] {
|
||||
for offered in [&none, &seen] {
|
||||
for enabled in [false, true] {
|
||||
let expected = device.is_some() && !calibrated && enabled && !device.is_some_and(|d| offered.contains(d));
|
||||
assert_eq!(should_offer(device, calibrated, offered, enabled), expected, "{device:?} {calibrated} {offered:?} {enabled}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The driver on macOS has its own settings; there is nothing to calibrate.
|
||||
assert!(!should_offer(Some("3dxware"), false, &none, true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn never_show_again_stops_every_offer() {
|
||||
let none = BTreeSet::new();
|
||||
assert!(should_offer(Some("hid 046d:c62b"), false, &none, true, false));
|
||||
for device in ["hid 046d:c62b", "spacenavd", "web 256f:c63a"] {
|
||||
assert!(!should_offer(Some(device), false, &none, true, true), "{device}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_swap_to_direct_is_offered_only_when_spacenavd_was_chosen_and_is_read_with_a_device_there() {
|
||||
use crate::prefs::SpaceMouseDriver::{Automatic, Direct, Spacenavd};
|
||||
use crate::space_mouse::DriversPresent;
|
||||
for device in ["spacenavd", "hid 046d:c62b", "hid 256f:c63a", "3dxware", "web 256f:c63a"] {
|
||||
for drivers in [
|
||||
None,
|
||||
Some(DriversPresent { spacenavd: true, direct: false, denied: false }),
|
||||
Some(DriversPresent { spacenavd: true, direct: true, denied: false }),
|
||||
Some(DriversPresent { spacenavd: false, direct: true, denied: false }),
|
||||
] {
|
||||
for driver in [Automatic, Spacenavd, Direct] {
|
||||
let expected = device == "spacenavd" && driver == Spacenavd && drivers.is_some_and(|d| d.direct);
|
||||
assert_eq!(swap_offered(device, drivers, driver), expected, "{device} {drivers:?} {driver:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn press(h: &mut crate::scenario::Harness, widget: &str) {
|
||||
h.frames(3);
|
||||
h.step(&serde_json::json!({"widget": widget})).unwrap_or_else(|e| panic!("{widget}: {e}"));
|
||||
h.frames(2);
|
||||
}
|
||||
|
||||
fn offered() -> crate::scenario::Harness {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
h.frames(3);
|
||||
h
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_notice_goes_after_thirty_seconds() {
|
||||
assert!(!expired(100.0, 129.9) && expired(100.0, 130.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_the_offer_records_the_device_so_it_does_not_come_back() {
|
||||
let mut h = offered();
|
||||
assert!(crate::scenario::rect_of("toast:space_mouse_offer").is_some(), "the notice is drawn");
|
||||
press(&mut h, "Close");
|
||||
assert!(h.app.preferences.space_mouse_offered.contains("hid 046d:c62b"));
|
||||
assert!(h.app.space_mouse.calibrating.is_none());
|
||||
h.frames(3);
|
||||
assert!(h.app.preferences.space_mouse_offered.contains("hid 046d:c62b"), "gone, and it stays gone");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calibrating_from_the_offer_opens_the_calibration_and_records_the_device() {
|
||||
let mut h = offered();
|
||||
press(&mut h, "Calibrate…");
|
||||
h.frames(3);
|
||||
assert!(h.app.space_mouse.calibrating.is_some() && h.app.prefs_window.open);
|
||||
assert!(h.app.preferences.space_mouse_offered.contains("hid 046d:c62b"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_stays_away_from_a_calibrated_device_and_navigation_off() {
|
||||
let axes =
|
||||
std::array::from_fn(|source| solidcraft_space_mouse::AxisCalibration { source, reversed: false, rest: 0, dead_zone: 0, full: [350; 2] });
|
||||
let calibration = solidcraft_space_mouse::Calibration { dead_zone_share: 0.0, kind: solidcraft_space_mouse::Kind::ReturnToCenter, axes };
|
||||
for (enabled, calibrated) in [(false, false), (true, true)] {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
h.app.preferences.space_mouse_enabled = enabled;
|
||||
if calibrated {
|
||||
h.app.preferences.space_mouse_calibrations.insert("hid 046d:c62b".into(), calibration);
|
||||
}
|
||||
h.frames(4);
|
||||
assert!(h.app.space_mouse.offer_since.is_none(), "no notice for enabled {enabled}, calibrated {calibrated}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod preferences_tests {
|
||||
/// File, then Preferences…, by clicks: the window must be open, drawn and on top (nothing of ours over it), for settings like a real
|
||||
/// user with a 3D mouse (saved calibration, driver choice, picture in the view).
|
||||
#[test]
|
||||
fn preferences_open_and_draw_with_a_3d_mouse_users_settings() {
|
||||
// An uncalibrated device (the notice card is up first), the calibrated one and none.
|
||||
for device in [None, Some("hid 256f:c63a"), Some("spacenavd"), Some("hid 046d:c62b")] {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.load_prefs(include_str!("test_data/prefs_space_mouse_user.json"));
|
||||
h.app.space_mouse.device = device.map(Into::into);
|
||||
h.frames(4);
|
||||
// The real way in: File menu, then its Preferences item, by clicks.
|
||||
h.app.custom_titlebar = true;
|
||||
h.frames(3);
|
||||
h.step(&serde_json::json!({"click": {"handle": "appbar:file"}})).unwrap();
|
||||
h.frames(3);
|
||||
h.step(&serde_json::json!({"widget": "Preferences…"})).unwrap_or_else(|e| panic!("{device:?}: {e}"));
|
||||
h.frames(4);
|
||||
let window = crate::scenario::rect_of("dialog:window");
|
||||
let centre = window.map(|r| r.center());
|
||||
let top = centre.and_then(|c| h.ctx.layer_id_at(c));
|
||||
assert!(top.is_some_and(|layer| layer.id == egui::Id::new("sc_prefs")), "{device:?}: the layer on top at the window centre is {top:?}");
|
||||
assert!(h.app.prefs_window.open, "{device:?}: still open");
|
||||
assert!(crate::scenario::rect_of("dialog:window").is_some_and(|r| r.is_positive()), "{device:?}: the window is drawn");
|
||||
}
|
||||
}
|
||||
|
||||
/// Preferences stays open while the calibration notice comes and goes around it.
|
||||
#[test]
|
||||
fn the_notice_never_closes_preferences() {
|
||||
for (open_first, frames_before) in [(false, 0), (false, 40), (true, 0), (true, 90)] {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.custom_titlebar = true;
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
h.frames(3);
|
||||
if open_first {
|
||||
crate::prefs::open_at(&mut h.app, crate::prefs::PAGE_NAVIGATION);
|
||||
}
|
||||
for _ in 0..frames_before {
|
||||
h.frame();
|
||||
}
|
||||
if !open_first && frames_before > 0 {
|
||||
assert!(h.app.space_mouse.offer_since.is_some(), "the notice is up before Preferences opens");
|
||||
}
|
||||
if !open_first {
|
||||
h.step(&serde_json::json!({"click": {"handle": "appbar:file"}})).unwrap();
|
||||
h.frames(3);
|
||||
h.step(&serde_json::json!({"widget": "Preferences…"})).unwrap();
|
||||
}
|
||||
for frame in 0..200 {
|
||||
h.frame();
|
||||
assert!(h.app.prefs_window.open, "open_first {open_first}, before {frames_before}: closed at frame {frame}");
|
||||
let window = crate::scenario::rect_of("dialog:window").expect("drawn");
|
||||
// The window sizes itself over its first frames.
|
||||
if frame < 4 {
|
||||
continue;
|
||||
}
|
||||
for f in [0.1_f32, 0.5, 0.9] {
|
||||
for g in [0.1_f32, 0.5, 0.9] {
|
||||
let at = window.min + egui::vec2(window.width() * f, window.height() * g);
|
||||
let top = h.ctx.layer_id_at(at);
|
||||
let order = h.ctx.memory(|m| m.areas().visible_layer_ids());
|
||||
assert!(top.is_some_and(|l| l.id == egui::Id::new("sc_prefs")), "frame {frame} at {at:?}: top is {top:?}; order {order:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The genuine first run: default settings, the start page up, a 3D mouse that connects, then
|
||||
/// File, Preferences… by clicks. It stays open, on top, for 240 frames, with and without a
|
||||
/// device (so with and without the calibration notice).
|
||||
#[test]
|
||||
fn a_fresh_profile_opens_preferences_from_the_start_page_and_keeps_it() {
|
||||
for device in [None, Some("hid 046d:c62b"), Some("spacenavd")] {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.custom_titlebar = true;
|
||||
h.app.home.open = true;
|
||||
h.frames(3);
|
||||
h.app.space_mouse.device = device.map(Into::into);
|
||||
h.frames(30);
|
||||
h.step(&serde_json::json!({"click": {"handle": "appbar:file"}})).unwrap();
|
||||
h.frames(3);
|
||||
h.step(&serde_json::json!({"widget": "Preferences…"})).unwrap_or_else(|e| panic!("{device:?}: {e}"));
|
||||
for frame in 0..240 {
|
||||
h.frame();
|
||||
assert!(h.app.prefs_window.open, "{device:?}: closed at frame {frame}");
|
||||
if frame > 4 {
|
||||
let window = crate::scenario::rect_of("dialog:window").expect("drawn");
|
||||
let top = h.ctx.layer_id_at(window.center());
|
||||
assert!(top.is_some_and(|l| l.id == egui::Id::new("sc_prefs")), "{device:?} frame {frame}: top is {top:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Behind an issue report (CAD input blocked) the offer is not drawn, so it cannot be clicked.
|
||||
#[test]
|
||||
fn the_offer_cannot_be_used_while_an_issue_report_blocks_input() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
crate::issue_report::set_cad_input_blocked_for_test(true);
|
||||
h.frames(6);
|
||||
let hidden = h.app.space_mouse.offer_since.is_none() && crate::scenario::rect_of("toast:space_mouse_offer").is_none();
|
||||
crate::issue_report::set_cad_input_blocked_for_test(false);
|
||||
assert!(hidden, "not drawn while input is blocked");
|
||||
assert!(h.app.space_mouse.calibrating.is_none() && h.app.preferences.space_mouse_offered.is_empty());
|
||||
// And it is there again once the report is gone.
|
||||
h.frames(4);
|
||||
assert!(h.app.space_mouse.offer_since.is_some());
|
||||
}
|
||||
|
||||
/// Showing, expiring and closing the offer changes nothing outside its own state: the open
|
||||
/// dialog, tool, selection, undo history, Preferences and calibration are as they were.
|
||||
#[test]
|
||||
fn showing_expiring_and_closing_the_offer_touch_no_other_window() {
|
||||
let state = |h: &crate::scenario::Harness| {
|
||||
(
|
||||
h.app.dialog.is_some(),
|
||||
h.app.tool.is_some(),
|
||||
h.app.session.selection.len(),
|
||||
h.app.session.undo.len(),
|
||||
h.app.prefs_window.open,
|
||||
h.app.space_mouse.calibrating.is_some(),
|
||||
h.app.keymap.open,
|
||||
h.app.home.open,
|
||||
)
|
||||
};
|
||||
for how in ["expire", "close"] {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.run("solid.box", serde_json::json!({"length": 10, "width": 10, "height": 10})).unwrap();
|
||||
h.app.start("solid.extrude");
|
||||
h.frames(3);
|
||||
let before = state(&h);
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
h.frames(4);
|
||||
assert!(h.app.space_mouse.offer_since.is_some(), "{how}: the offer is up");
|
||||
assert_eq!(state(&h), before, "{how}: showing it changes nothing else");
|
||||
if how == "close" {
|
||||
h.step(&serde_json::json!({"widget": "Close"})).unwrap();
|
||||
} else if let Some((device, _)) = h.app.space_mouse.offer_since.take() {
|
||||
h.app.space_mouse.offer_since = Some((device, -1000.0));
|
||||
}
|
||||
h.frames(4);
|
||||
assert!(h.app.space_mouse.offer_since.is_none() || how == "expire");
|
||||
assert_eq!(state(&h), before, "{how}: going away changes nothing else");
|
||||
}
|
||||
}
|
||||
|
||||
fn spacenavd_harness() -> crate::scenario::Harness {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.space_mouse.device = Some("spacenavd".into());
|
||||
h.app.space_mouse.drivers = Some(crate::space_mouse::DriversPresent { spacenavd: true, direct: true, denied: false });
|
||||
// Spacenavd chosen by the user, so the card may offer the swap to the device.
|
||||
h.app.preferences.space_mouse_driver = crate::prefs::SpaceMouseDriver::Spacenavd;
|
||||
h.frames(4);
|
||||
h
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swapping_to_the_direct_driver_sets_it_and_leaves_the_offered_set_alone() {
|
||||
let mut h = spacenavd_harness();
|
||||
assert!(h.app.space_mouse.offer_since.is_some(), "the card is up");
|
||||
h.step(&serde_json::json!({"widget": "Swap to Hidraw (Direct Driver)"})).unwrap();
|
||||
h.frames(3);
|
||||
assert_eq!(h.app.preferences.space_mouse_driver, crate::prefs::SpaceMouseDriver::Direct);
|
||||
assert!(h.app.preferences.space_mouse_offered.is_empty(), "the new source may still be offered");
|
||||
assert!(h.app.space_mouse.offer_since.is_none());
|
||||
// Without a direct device there is no such button.
|
||||
let mut h = spacenavd_harness();
|
||||
h.app.space_mouse.drivers = Some(crate::space_mouse::DriversPresent { spacenavd: true, direct: false, denied: false });
|
||||
h.frames(3);
|
||||
assert!(h.step(&serde_json::json!({"widget": "Swap to Hidraw (Direct Driver)"})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn never_show_again_is_saved_and_stops_the_card() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
h.frames(4);
|
||||
h.step(&serde_json::json!({"widget": "Never show again"})).unwrap();
|
||||
h.frames(4);
|
||||
assert!(h.app.preferences.space_mouse_offer_never);
|
||||
assert!(h.app.preferences.space_mouse_offered.is_empty(), "not just this device");
|
||||
assert!(h.app.space_mouse.offer_since.is_none());
|
||||
// Saved with the other settings, and no other device brings the card back.
|
||||
let saved = serde_json::to_value(&h.app.preferences).unwrap();
|
||||
assert_eq!(saved["space_mouse_offer_never"], true);
|
||||
h.app.space_mouse.device = Some("hid 256f:c63a".into());
|
||||
h.frames(4);
|
||||
assert!(h.app.space_mouse.offer_since.is_none());
|
||||
}
|
||||
|
||||
/// Frames of the harness are 1/60 s: this many make `seconds`.
|
||||
fn run(h: &mut crate::scenario::Harness, seconds: f64) {
|
||||
for _ in 0..(seconds * 60.0).round() as usize {
|
||||
h.frame();
|
||||
}
|
||||
}
|
||||
|
||||
fn card_up(h: &crate::scenario::Harness) -> bool {
|
||||
h.app.space_mouse.offer_since.is_some() && crate::scenario::rect_of("toast:space_mouse_offer").is_some()
|
||||
}
|
||||
|
||||
/// The offer stays its own 30 seconds, and time away (Preferences open) is not time shown.
|
||||
#[test]
|
||||
fn the_offer_stays_its_thirty_seconds_and_time_away_is_not_time_shown() {
|
||||
let mut h = spacenavd_harness();
|
||||
run(&mut h, 2.0);
|
||||
for second in 0..26 {
|
||||
run(&mut h, 1.0);
|
||||
assert!(card_up(&h), "second {second}: the offer is gone");
|
||||
}
|
||||
// Over its own time it goes, for this run only: no record.
|
||||
run(&mut h, 5.0);
|
||||
assert!(!card_up(&h));
|
||||
assert!(h.app.preferences.space_mouse_offered.is_empty());
|
||||
// Time away: Preferences open for 45 s must not use the time up.
|
||||
let mut h = spacenavd_harness();
|
||||
run(&mut h, 3.0);
|
||||
assert!(card_up(&h));
|
||||
h.app.prefs_window.open = true;
|
||||
run(&mut h, 45.0);
|
||||
h.app.prefs_window.open = false;
|
||||
run(&mut h, 3.0);
|
||||
assert!(card_up(&h), "the offer waited for Preferences to close");
|
||||
run(&mut h, 25.0);
|
||||
assert!(card_up(&h), "and has its 30 seconds again");
|
||||
}
|
||||
|
||||
/// Once the user is working (a status toast), the offer goes for the run, and nothing is recorded.
|
||||
#[test]
|
||||
fn a_status_toast_dismisses_the_offer_for_the_run_without_a_record() {
|
||||
let mut h = spacenavd_harness();
|
||||
run(&mut h, 3.0);
|
||||
assert!(card_up(&h));
|
||||
h.app.status = Some(("Done".into(), crate::now_ms(), false));
|
||||
run(&mut h, 1.0);
|
||||
assert!(!card_up(&h), "the offer went");
|
||||
assert!(h.app.preferences.space_mouse_offered.is_empty() && !h.app.preferences.space_mouse_offer_never, "nothing recorded");
|
||||
// And it does not come back when the status goes, nor with the key shown again.
|
||||
h.app.status = None;
|
||||
run(&mut h, 10.0);
|
||||
assert!(!card_up(&h));
|
||||
// A status already up when the offer would first show keeps it away too.
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.status = Some(("Opened".into(), crate::now_ms(), false));
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
run(&mut h, 3.0);
|
||||
assert!(!card_up(&h));
|
||||
assert!(h.app.preferences.space_mouse_offered.is_empty());
|
||||
}
|
||||
|
||||
/// A status toast takes the offer's place for the run, and the 3D mouse picture in the view keeps
|
||||
/// clear of whichever toast is up: the offer card, then the status that replaced it.
|
||||
#[test]
|
||||
fn the_picture_keeps_clear_of_the_toast_that_is_up_and_a_status_replaces_the_offer() {
|
||||
use crate::prefs::SpaceMouseGhost::{LeftViewport, LeftWindow, RightViewport};
|
||||
// Wide, and narrow enough that the toast reaches across the corners the picture sits in.
|
||||
for (position, width) in [LeftViewport, RightViewport, LeftWindow].into_iter().flat_map(|position| [(position, 1600), (position, 760)]) {
|
||||
let mut h = spacenavd_harness();
|
||||
h.call("ui.resize", serde_json::json!({"width": width, "height": 700}));
|
||||
h.app.preferences.space_mouse_in_view = true;
|
||||
h.app.preferences.space_mouse_ghost_position = position;
|
||||
run(&mut h, 3.0);
|
||||
assert!(card_up(&h), "{position:?}");
|
||||
let rect = |name| crate::scenario::rect_of(name).unwrap_or_else(|| panic!("{position:?}: no {name}"));
|
||||
let (card, picture) = (rect("toast:space_mouse_offer"), rect("space_mouse:corner"));
|
||||
eprintln!("RECTS {width} {position:?} card {card:?} picture {picture:?}");
|
||||
assert!(!card.intersects(picture), "{position:?}: the picture {picture:?} overlaps the card {card:?}");
|
||||
h.app.status = Some(("Done".into(), crate::now_ms(), false));
|
||||
run(&mut h, 1.0);
|
||||
assert!(!card_up(&h), "{position:?}: the offer went");
|
||||
assert!(crate::scenario::rect_of("toast:space_mouse_offer").is_none());
|
||||
let (toast, picture) = (rect("toast"), rect("space_mouse:corner"));
|
||||
eprintln!("RECTS {width} {position:?} toast {toast:?} picture {picture:?}");
|
||||
assert!(!toast.intersects(picture), "{position:?}: the picture {picture:?} overlaps the status toast {toast:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A click that lands after the device changed acts on the device now shown (the card is drawn
|
||||
/// afresh for it) or on nothing: never on the device the card was first drawn for.
|
||||
#[test]
|
||||
fn a_click_after_the_device_changed_acts_on_the_device_now_shown_or_on_nothing() {
|
||||
let (first, second) = ("spacenavd", "hid 256f:c63a");
|
||||
let mut h = spacenavd_harness();
|
||||
run(&mut h, 2.0);
|
||||
assert!(card_up(&h));
|
||||
h.app.space_mouse.device = Some(second.into());
|
||||
h.step(&serde_json::json!({"widget": "Calibrate…"})).unwrap();
|
||||
h.frames(3);
|
||||
// The card is drawn afresh for the new device in the frame of the click, which may or may not take the
|
||||
// click; either way nothing is done for the device it was first drawn for.
|
||||
let wizard = h.app.space_mouse.calibrating.as_ref().map(|(device, _)| device.as_str());
|
||||
assert!(wizard.is_none() || wizard == Some(second), "wizard for {wizard:?}");
|
||||
assert!(!h.app.preferences.space_mouse_offered.contains(first), "nothing recorded for the first device");
|
||||
// The device went away: the click finds no card.
|
||||
let mut h = spacenavd_harness();
|
||||
run(&mut h, 2.0);
|
||||
assert!(card_up(&h));
|
||||
let before = h.app.preferences.clone();
|
||||
h.app.space_mouse.device = None;
|
||||
h.step(&serde_json::json!({"widget": "Calibrate…"})).ok();
|
||||
h.frames(3);
|
||||
assert!(h.app.space_mouse.calibrating.is_none() && !h.app.prefs_window.open, "no wizard for a device that is gone");
|
||||
assert_eq!(h.app.preferences, before);
|
||||
}
|
||||
|
||||
/// Behind a guard that became active after the card was drawn (an issue report taking input), none of the
|
||||
/// card's four buttons does anything.
|
||||
#[test]
|
||||
fn a_click_after_a_guard_became_active_does_nothing_behind_it() {
|
||||
for widget in ["Calibrate…", "Close", "Never show again", "Swap to Hidraw (Direct Driver)"] {
|
||||
let mut h = spacenavd_harness();
|
||||
run(&mut h, 2.0);
|
||||
assert!(card_up(&h), "{widget}");
|
||||
let before = h.app.preferences.clone();
|
||||
crate::issue_report::set_cad_input_blocked_for_test(true);
|
||||
h.step(&serde_json::json!({"widget": widget})).ok();
|
||||
h.frames(3);
|
||||
crate::issue_report::set_cad_input_blocked_for_test(false);
|
||||
assert!(h.app.space_mouse.calibrating.is_none() && !h.app.prefs_window.open, "{widget}: no wizard, no Preferences");
|
||||
assert_eq!(h.app.preferences, before, "{widget}: nothing recorded or changed");
|
||||
// And the card is back once the guard is gone, untouched.
|
||||
run(&mut h, 1.0);
|
||||
assert!(card_up(&h), "{widget}: the card is still offered");
|
||||
}
|
||||
}
|
||||
|
||||
/// `act` itself refuses a stale action: the device changed since the card was drawn, something took
|
||||
/// the input, or the action is no longer on offer. Nothing is recorded for the old device, and neither
|
||||
/// the Never flag nor the driver changes. (The control: with nothing stale each action does its job.)
|
||||
#[test]
|
||||
fn act_refuses_a_stale_action_and_changes_nothing() {
|
||||
use crate::prefs::SpaceMouseDriver::{Automatic, Direct};
|
||||
use crate::space_mouse_offer::{Action, act, due};
|
||||
for action in [Action::Calibrate, Action::Close, Action::Never, Action::Swap] {
|
||||
let fresh = || {
|
||||
let mut h = spacenavd_harness();
|
||||
run(&mut h, 2.0);
|
||||
let offer = due(&mut h.app, &h.ctx).expect("due");
|
||||
assert!(offer.actions.contains(&action), "{action:?} is on the card");
|
||||
(h, offer)
|
||||
};
|
||||
// Control.
|
||||
let (mut h, offer) = fresh();
|
||||
act(&mut h.app, &h.ctx, &offer, action);
|
||||
let done = match action {
|
||||
Action::Calibrate => h.app.space_mouse.calibrating.is_some() && h.app.preferences.space_mouse_offered.contains("spacenavd"),
|
||||
Action::Close => h.app.preferences.space_mouse_offered.contains("spacenavd"),
|
||||
Action::Never => h.app.preferences.space_mouse_offer_never,
|
||||
Action::Swap => h.app.preferences.space_mouse_driver == Direct && h.app.preferences.space_mouse_offered.is_empty(),
|
||||
};
|
||||
assert!(done, "{action:?}: the fresh action works");
|
||||
type Stale = (&'static str, fn(&mut crate::scenario::Harness));
|
||||
let cases: [Stale; 7] = [
|
||||
("the device changed", |h| h.app.space_mouse.device = Some("hid 256f:c63a".into())),
|
||||
("the device went", |h| h.app.space_mouse.device = None),
|
||||
("an issue report took the input", |_| crate::issue_report::set_cad_input_blocked_for_test(true)),
|
||||
("Preferences opened", |h| h.app.prefs_window.open = true),
|
||||
("a status toast is up", |h| h.app.status = Some(("Done".into(), crate::now_ms(), false))),
|
||||
("the device was dismissed elsewhere", |h| {
|
||||
h.app.preferences.space_mouse_offered.insert("spacenavd".into());
|
||||
}),
|
||||
("the action is no longer on offer", |h| {
|
||||
// Swap needs spacenavd chosen; for the others the driver is irrelevant, so the offer is
|
||||
// taken away by Never being set elsewhere.
|
||||
h.app.preferences.space_mouse_driver = Automatic;
|
||||
h.app.preferences.space_mouse_offer_never = true;
|
||||
}),
|
||||
];
|
||||
for (what, stale) in cases {
|
||||
let (mut h, offer) = fresh();
|
||||
stale(&mut h);
|
||||
let (before, calibrating, window, hidden) = (
|
||||
h.app.preferences.clone(),
|
||||
h.app.space_mouse.calibrating.is_some(),
|
||||
h.app.prefs_window.open,
|
||||
h.app.space_mouse.offer_hidden.clone(),
|
||||
);
|
||||
act(&mut h.app, &h.ctx, &offer, action);
|
||||
crate::issue_report::set_cad_input_blocked_for_test(false);
|
||||
assert_eq!(h.app.preferences, before, "{action:?}, {what}: nothing recorded or changed");
|
||||
assert_eq!(h.app.space_mouse.calibrating.is_some(), calibrating, "{action:?}, {what}: no wizard");
|
||||
assert_eq!(h.app.prefs_window.open, window, "{action:?}, {what}: Preferences untouched");
|
||||
assert_eq!(h.app.space_mouse.offer_hidden, hidden, "{action:?}, {what}");
|
||||
}
|
||||
// Swap alone: with Automatic chosen (the device is read when it opens) it is no longer on offer, the
|
||||
// other actions still are.
|
||||
let (mut h, offer) = fresh();
|
||||
h.app.preferences.space_mouse_driver = Automatic;
|
||||
let before = h.app.preferences.clone();
|
||||
act(&mut h.app, &h.ctx, &offer, action);
|
||||
if action == Action::Swap {
|
||||
assert_eq!(h.app.preferences, before, "Swap is refused once it is not on offer");
|
||||
} else {
|
||||
assert!(h.app.preferences != before || h.app.space_mouse.calibrating.is_some(), "{action:?} still works with Automatic chosen");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A different device key while the offer is up gets its own offer (its own calibration key); the
|
||||
/// first one is left alone, with no record made for either.
|
||||
#[test]
|
||||
fn a_new_device_key_gets_its_own_offer_and_leaves_the_old_one_alone() {
|
||||
let mut h = spacenavd_harness();
|
||||
run(&mut h, 10.0);
|
||||
assert!(card_up(&h));
|
||||
h.app.space_mouse.device = Some("hid 256f:c63a".into());
|
||||
run(&mut h, 1.0);
|
||||
assert!(card_up(&h));
|
||||
assert_eq!(h.app.space_mouse.offer_since.as_ref().map(|(device, _)| device.as_str()), Some("hid 256f:c63a"));
|
||||
assert!(h.app.preferences.space_mouse_offered.is_empty() && h.app.space_mouse.offer_hidden.is_empty());
|
||||
// Back to the first within its 30 seconds: shown again, its timer starting afresh.
|
||||
h.app.space_mouse.device = Some("spacenavd".into());
|
||||
run(&mut h, 1.0);
|
||||
assert!(card_up(&h));
|
||||
// Switching the driver in Preferences while it is up changes nothing by itself.
|
||||
h.app.preferences.space_mouse_driver = crate::prefs::SpaceMouseDriver::Direct;
|
||||
run(&mut h, 2.0);
|
||||
assert!(card_up(&h));
|
||||
}
|
||||
|
||||
/// Hot-plug: a daemon with no records is no device (no offer), the first record is, and an offer
|
||||
/// hidden for the run stays hidden.
|
||||
#[test]
|
||||
fn the_offer_follows_the_first_record_and_a_hidden_offer_stays_hidden() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.space_mouse.drivers = Some(crate::space_mouse::DriversPresent { spacenavd: true, direct: true, denied: false });
|
||||
h.app.preferences.space_mouse_driver = crate::prefs::SpaceMouseDriver::Spacenavd;
|
||||
h.app.space_mouse.device = None;
|
||||
run(&mut h, 3.0);
|
||||
assert!(!card_up(&h) && h.app.preferences.space_mouse_offered.is_empty());
|
||||
h.app.space_mouse.device = Some("spacenavd".into());
|
||||
run(&mut h, 2.0);
|
||||
assert!(card_up(&h), "the first record shows the offer");
|
||||
assert!(h.widget("Swap to Hidraw (Direct Driver)").is_some(), "with a direct device there, Swap shows");
|
||||
// Hidden for the run (a status toast), it stays hidden when the device is seen again.
|
||||
h.app.status = Some(("Done".into(), crate::now_ms(), false));
|
||||
run(&mut h, 1.0);
|
||||
h.app.status = None;
|
||||
h.app.space_mouse.device = None;
|
||||
run(&mut h, 1.0);
|
||||
h.app.space_mouse.device = Some("spacenavd".into());
|
||||
run(&mut h, 2.0);
|
||||
assert!(!card_up(&h));
|
||||
// Under Automatic (the default) the device is read whenever it opens, so spacenavd being read with
|
||||
// a device there is a device that does not open: a swap would do nothing, and is not offered.
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.space_mouse.drivers = Some(crate::space_mouse::DriversPresent { spacenavd: true, direct: true, denied: false });
|
||||
h.app.space_mouse.device = Some("spacenavd".into());
|
||||
run(&mut h, 2.0);
|
||||
assert!(card_up(&h) && h.widget("Swap to Hidraw (Direct Driver)").is_none(), "no Swap under Automatic");
|
||||
// Without a direct device there is no Swap.
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.space_mouse.drivers = Some(crate::space_mouse::DriversPresent { spacenavd: true, direct: false, denied: false });
|
||||
h.app.space_mouse.device = Some("spacenavd".into());
|
||||
run(&mut h, 2.0);
|
||||
assert!(card_up(&h) && h.widget("Swap to Hidraw (Direct Driver)").is_none());
|
||||
}
|
||||
|
||||
/// The same device, plugged back in, gets what it had: its saved calibration applies, Preferences
|
||||
/// calls it calibrated, and the offer stays away, whether Preferences was open or not and on whatever
|
||||
/// node it comes up (the key is its ids).
|
||||
#[test]
|
||||
fn an_unplugged_and_replugged_device_keeps_its_calibration_and_is_not_offered_again() {
|
||||
let axes =
|
||||
std::array::from_fn(|source| solidcraft_space_mouse::AxisCalibration { source, reversed: false, rest: 0, dead_zone: 0, full: [350; 2] });
|
||||
let calibration = solidcraft_space_mouse::Calibration { dead_zone_share: 0.0, kind: solidcraft_space_mouse::Kind::ReturnToCenter, axes };
|
||||
for prefs_open in [false, true] {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.preferences.space_mouse_calibrations.insert("hid 046d:c62b".into(), calibration);
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
if prefs_open {
|
||||
crate::prefs::open_at(&mut h.app, crate::prefs::PAGE_NAVIGATION);
|
||||
}
|
||||
run(&mut h, 2.0);
|
||||
assert_eq!(crate::space_mouse::calibration(&h.app), Some(calibration));
|
||||
// Unplugged (a few seconds), then back, on another node: the same key.
|
||||
h.app.space_mouse.device = None;
|
||||
run(&mut h, 4.0);
|
||||
assert!(crate::space_mouse::calibration(&h.app).is_none(), "nothing to apply while it is away");
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
run(&mut h, 2.0);
|
||||
assert_eq!(crate::space_mouse::calibration(&h.app), Some(calibration), "prefs_open {prefs_open}: the calibration applies again");
|
||||
assert!(!card_up(&h) && h.app.space_mouse.offer_since.is_none(), "no offer for a calibrated device");
|
||||
assert!(h.app.preferences.space_mouse_offered.is_empty() && h.app.preferences.space_mouse_calibrations.contains_key("hid 046d:c62b"));
|
||||
}
|
||||
}
|
||||
|
||||
/// A device that was already offered (closed, say) is not offered again when it is plugged back in.
|
||||
#[test]
|
||||
fn a_device_already_offered_is_not_offered_again_after_a_replug() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.preferences.space_mouse_offered.insert("hid 046d:c62b".into());
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
run(&mut h, 2.0);
|
||||
assert!(!card_up(&h));
|
||||
h.app.space_mouse.device = None;
|
||||
run(&mut h, 3.0);
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
run(&mut h, 3.0);
|
||||
assert!(!card_up(&h) && h.app.space_mouse.offer_since.is_none());
|
||||
}
|
||||
|
||||
/// The offer keeps away while Preferences (and so its 3D mouse section) is open, and is back when it is
|
||||
/// closed if nothing dismissed it; Calibrate from the card and from Preferences reach the same wizard.
|
||||
#[test]
|
||||
fn the_offer_keeps_away_from_the_open_section_and_calibrate_starts_the_same_wizard_either_way() {
|
||||
let mut h = spacenavd_harness();
|
||||
run(&mut h, 2.0);
|
||||
assert!(card_up(&h));
|
||||
crate::prefs::open_at(&mut h.app, crate::prefs::PAGE_NAVIGATION);
|
||||
run(&mut h, 3.0);
|
||||
assert!(!card_up(&h), "no card over Preferences");
|
||||
h.app.prefs_window.open = false;
|
||||
run(&mut h, 2.0);
|
||||
assert!(card_up(&h), "back once Preferences is closed, nothing was dismissed");
|
||||
assert!(h.app.preferences.space_mouse_offered.is_empty());
|
||||
let offer = crate::space_mouse_offer::due(&mut h.app, &h.ctx).expect("due");
|
||||
crate::space_mouse_offer::act(&mut h.app, &h.ctx, &offer, crate::space_mouse_offer::Action::Calibrate);
|
||||
let from_card = h.app.space_mouse.calibrating.as_ref().map(|(device, wizard)| (device.clone(), wizard.stage(), wizard.kind()));
|
||||
assert!(h.app.prefs_window.open);
|
||||
let mut other = spacenavd_harness();
|
||||
crate::space_mouse_calibration::start(&mut other.app);
|
||||
let from_prefs = other.app.space_mouse.calibrating.as_ref().map(|(device, wizard)| (device.clone(), wizard.stage(), wizard.kind()));
|
||||
assert_eq!(from_card, from_prefs);
|
||||
assert!(from_card.is_some());
|
||||
}
|
||||
|
||||
/// The driver fallback round trip: Direct asked for, the device away so the daemon is read (its own
|
||||
/// key), the device back: the saved calibration under the device's key applies again and the saved
|
||||
/// driver choice never changed.
|
||||
#[test]
|
||||
fn a_driver_fallback_round_trip_keeps_the_calibration_and_the_driver_choice() {
|
||||
let axes =
|
||||
std::array::from_fn(|source| solidcraft_space_mouse::AxisCalibration { source, reversed: false, rest: 0, dead_zone: 0, full: [350; 2] });
|
||||
let calibration = solidcraft_space_mouse::Calibration { dead_zone_share: 0.0, kind: solidcraft_space_mouse::Kind::ReturnToCenter, axes };
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.preferences.space_mouse_driver = crate::prefs::SpaceMouseDriver::Direct;
|
||||
h.app.preferences.space_mouse_calibrations.insert("hid 046d:c62b".into(), calibration);
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
run(&mut h, 2.0);
|
||||
assert_eq!(crate::space_mouse::calibration(&h.app), Some(calibration));
|
||||
// Away: the daemon is read under its own key, which has no calibration (it is a different reading).
|
||||
h.app.space_mouse.device = Some("spacenavd".into());
|
||||
run(&mut h, 2.0);
|
||||
assert!(crate::space_mouse::calibration(&h.app).is_none());
|
||||
assert_eq!(h.app.preferences.space_mouse_driver, crate::prefs::SpaceMouseDriver::Direct, "the choice is not changed by the fallback");
|
||||
// Back: the device's own key, and its calibration, at once.
|
||||
h.app.space_mouse.device = Some("hid 046d:c62b".into());
|
||||
run(&mut h, 1.0);
|
||||
assert_eq!(crate::space_mouse::calibration(&h.app), Some(calibration));
|
||||
assert!(h.app.preferences.space_mouse_calibrations.contains_key("hid 046d:c62b"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
//! The test model in Preferences > Navigation > 3D mouse: a small boat that moves with the 3D mouse
|
||||
//! exactly as the view does, so what each setting on that page does can be felt on it. It is a
|
||||
//! tessellation of a CC0 model (`assets/models/test_boat.bin`, made by
|
||||
//! `assets/models/make_test_boat.py`; see ATTRIBUTION.md).
|
||||
//!
|
||||
//! The boat is drawn as one indexed mesh: the vertices are projected once with a [`Camera`] of its
|
||||
//! own and shaded smoothly from their normals by one light that follows the camera, coloured from the
|
||||
//! theme; the triangles facing away are dropped and the rest sorted far to near. The camera takes the same per-frame motion the viewport's
|
||||
//! camera takes (`navigation::device_camera`), so calibration, reassigned axes, invert, speed and
|
||||
//! the zoom direction all show. Pan and zoom are held so the boat never leaves its box.
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use egui::{Pos2, Rect, pos2};
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::render::Camera;
|
||||
|
||||
use crate::space_mouse::DeviceMotion;
|
||||
use crate::theme::Tokens;
|
||||
|
||||
const DATA: &[u8] = include_bytes!("../../../assets/models/test_boat.bin");
|
||||
|
||||
/// The model: triangles in millimetres about its centre. Each vertex has its own normal (smooth
|
||||
/// where the surface is, split at creases); each triangle has the face normal and centre that the
|
||||
/// culling and sorting use.
|
||||
pub(crate) struct Model {
|
||||
pub vertices: Vec<Vec3>,
|
||||
pub vertex_normals: Vec<Vec3>,
|
||||
pub triangles: Vec<[u16; 3]>,
|
||||
pub normals: Vec<Vec3>,
|
||||
pub centroids: Vec<Vec3>,
|
||||
/// The furthest a vertex is from the centre.
|
||||
pub radius: f64,
|
||||
}
|
||||
|
||||
/// Bytes of one vertex in the file: its position (i16 x 3) and its normal (i8 x 3).
|
||||
const VERTEX_BYTES: usize = 9;
|
||||
|
||||
/// Reads the file: u32 vertex count, u32 triangle count, f32 centre (3) and half size, then per
|
||||
/// vertex i16 x 3 and i8 x 3, then the triangles as u16 x 3. `None` for anything that does not add up.
|
||||
pub(crate) fn parse(data: &[u8]) -> Option<Model> {
|
||||
let word = |at: usize| data.get(at..at + 4).map(|b| [b[0], b[1], b[2], b[3]]);
|
||||
let (vertex_count, triangle_count) = (u32::from_le_bytes(word(0)?) as usize, u32::from_le_bytes(word(4)?) as usize);
|
||||
let half = f64::from(f32::from_le_bytes(word(20)?));
|
||||
let (vertices_at, triangles_at) = (24, 24usize.checked_add(vertex_count.checked_mul(VERTEX_BYTES)?)?);
|
||||
if data.len() != triangles_at.checked_add(triangle_count.checked_mul(6)?)?
|
||||
|| !half.is_finite()
|
||||
|| half <= 0.0
|
||||
|| vertex_count == 0
|
||||
|| vertex_count > usize::from(u16::MAX) + 1
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let short = |at: usize| data.get(at..at + 2).map(|b| i16::from_le_bytes([b[0], b[1]]));
|
||||
// Centred on the origin: the file's own centre is where the model sat, not where it is drawn.
|
||||
let vertices: Vec<Vec3> = (0..vertex_count)
|
||||
.map(|i| {
|
||||
let at = vertices_at + VERTEX_BYTES * i;
|
||||
Some(Vec3::new(f64::from(short(at)?), f64::from(short(at + 2)?), f64::from(short(at + 4)?)) * (half / 32767.0))
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
let vertex_normals: Vec<Vec3> = (0..vertex_count)
|
||||
.map(|i| {
|
||||
let at = vertices_at + VERTEX_BYTES * i + 6;
|
||||
let byte = |k: usize| data.get(at + k).map(|&b| f64::from(i8::from_le_bytes([b])));
|
||||
Some(Vec3::new(byte(0)?, byte(1)?, byte(2)?).normalized().unwrap_or(Vec3::Z))
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
let triangles: Vec<[u16; 3]> = (0..triangle_count)
|
||||
.map(|i| {
|
||||
let at = triangles_at + 6 * i;
|
||||
let index = |k: usize| data.get(at + 2 * k..at + 2 * k + 2).map(|b| u16::from_le_bytes([b[0], b[1]]));
|
||||
Some([index(0)?, index(1)?, index(2)?]).filter(|t| t.iter().all(|&v| usize::from(v) < vertex_count))
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
let corner = |t: &[u16; 3], k: usize| vertices[usize::from(t[k])];
|
||||
let normals =
|
||||
triangles.iter().map(|t| (corner(t, 1) - corner(t, 0)).cross(corner(t, 2) - corner(t, 0)).normalized().unwrap_or(Vec3::Z)).collect();
|
||||
let centroids = triangles.iter().map(|t| (corner(t, 0) + corner(t, 1) + corner(t, 2)) * (1.0 / 3.0)).collect();
|
||||
let radius = vertices.iter().map(|v| v.len()).fold(0.0, f64::max);
|
||||
Some(Model { vertices, vertex_normals, triangles, normals, centroids, radius })
|
||||
}
|
||||
|
||||
/// The built-in model, read once.
|
||||
pub(crate) fn model() -> Option<&'static Model> {
|
||||
static MODEL: OnceLock<Option<Model>> = OnceLock::new();
|
||||
MODEL.get_or_init(|| parse(DATA)).as_ref()
|
||||
}
|
||||
|
||||
/// Vertical field of view of the boat's camera.
|
||||
const FOV: f64 = 0.6;
|
||||
/// How near and far the boat's camera may be: nearest still shows the whole boat in the box.
|
||||
const NEAREST: f64 = 3.0;
|
||||
const FARTHEST: f64 = 12.0;
|
||||
|
||||
/// The boat's view: a camera on the boat and when it was last drawn, so a page opened again starts
|
||||
/// afresh.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Boat {
|
||||
pub cam: Camera,
|
||||
pub seen: f64,
|
||||
}
|
||||
|
||||
impl Boat {
|
||||
pub fn new(radius: f64) -> Self {
|
||||
Self { cam: Camera { distance: radius * 6.0, fov: FOV, ..Camera::default() }, seen: 0.0 }
|
||||
}
|
||||
}
|
||||
|
||||
/// `motion` for `seconds` on the boat's camera in a box `aspect` wide for its height of `height`
|
||||
/// points, with pan and zoom held so a sphere of `radius` stays inside it.
|
||||
pub(crate) fn step(
|
||||
cam: &mut Camera,
|
||||
motion: crate::space_mouse::Motion,
|
||||
seconds: f64,
|
||||
height: f64,
|
||||
radius: f64,
|
||||
aspect: f64,
|
||||
reverse_zoom: bool,
|
||||
free_orbit: bool,
|
||||
) {
|
||||
crate::navigation::device_camera(cam, motion, seconds, height, true, reverse_zoom, free_orbit);
|
||||
cam.fov = FOV;
|
||||
// Zoom within limits: the nearest still shows the whole boat in the shorter side of the box.
|
||||
let shorter = aspect.min(1.0);
|
||||
cam.distance = cam.distance.clamp(radius * NEAREST / shorter.max(0.2), radius * FARTHEST);
|
||||
// Pan so the boat's centre stays where the whole boat is still inside: within the box less its radius.
|
||||
let half = cam.distance * (FOV / 2.0).tan();
|
||||
let room = (half * aspect.min(1.0) - radius).max(0.0);
|
||||
let off = cam.target.len();
|
||||
if off > room && off > 0.0 {
|
||||
cam.target = cam.target * (room / off);
|
||||
}
|
||||
if !cam.is_valid() {
|
||||
*cam = Boat::new(radius).cam;
|
||||
}
|
||||
}
|
||||
|
||||
/// The boat for one frame: every vertex projected once with its shade (0 to 1, from its own normal,
|
||||
/// so surfaces are smooth), and the triangles facing the eye as indices, far to near.
|
||||
pub(crate) struct Frame {
|
||||
pub points: Vec<Pos2>,
|
||||
pub shades: Vec<f32>,
|
||||
pub indices: Vec<u32>,
|
||||
}
|
||||
|
||||
/// Projects `model` through `cam` into `rect`. Pure, for the drawing and for tests.
|
||||
pub(crate) fn project(model: &Model, cam: &Camera, rect: Rect) -> Frame {
|
||||
let aspect = f64::from(rect.width()) / f64::from(rect.height().max(1.0));
|
||||
let vp = cam.view_proj(aspect, model.radius * 2.0);
|
||||
let (right, up, back) = cam.basis();
|
||||
let light = (right * -0.4 + up * 0.6 + back * 0.7).normalized().unwrap_or(back);
|
||||
let (width, height) = (f64::from(rect.width()), f64::from(rect.height()));
|
||||
let (mut points, mut shades) = (Vec::with_capacity(model.vertices.len()), Vec::with_capacity(model.vertices.len()));
|
||||
let mut behind = vec![false; model.vertices.len()];
|
||||
for (i, (vertex, normal)) in model.vertices.iter().zip(&model.vertex_normals).enumerate() {
|
||||
let c = vp.apply(*vertex);
|
||||
behind[i] = c[3] <= 1e-9;
|
||||
let w = if behind[i] { 1.0 } else { c[3] };
|
||||
points.push(pos2(rect.left() + ((c[0] / w + 1.0) * 0.5 * width) as f32, rect.top() + ((1.0 - c[1] / w) * 0.5 * height) as f32));
|
||||
shades.push(normal.dot(light).max(0.0) as f32);
|
||||
}
|
||||
let eye = cam.eye();
|
||||
let mut visible: Vec<(f64, usize)> = (0..model.triangles.len())
|
||||
.filter(|&i| model.normals[i].dot(eye - model.centroids[i]) > 0.0 && !model.triangles[i].iter().any(|&v| behind[usize::from(v)]))
|
||||
.map(|i| ((eye - model.centroids[i]).len(), i))
|
||||
.collect();
|
||||
visible.sort_by(|a, b| b.0.total_cmp(&a.0));
|
||||
let indices = visible.into_iter().flat_map(|(_, i)| model.triangles[i].map(u32::from)).collect();
|
||||
Frame { points, shades, indices }
|
||||
}
|
||||
|
||||
/// Draws the boat in a box of `size` and moves it by this frame's motion. `boat` is the page's state.
|
||||
pub(crate) fn show(
|
||||
ui: &mut egui::Ui,
|
||||
boat: &mut Option<Boat>,
|
||||
motion: Option<DeviceMotion>,
|
||||
reverse_zoom: bool,
|
||||
free_orbit: bool,
|
||||
size: egui::Vec2,
|
||||
) -> Option<Rect> {
|
||||
let model = model()?;
|
||||
let t = Tokens::get();
|
||||
let now = ui.input(|i| i.time);
|
||||
let (rect, _) = ui.allocate_exact_size(size, egui::Sense::hover());
|
||||
// A page opened again (not drawn for a moment) starts with the boat back.
|
||||
let mut state = boat.filter(|b| now - b.seen < 0.5).unwrap_or_else(|| Boat::new(model.radius));
|
||||
state.seen = now;
|
||||
if let Some(motion) = motion {
|
||||
let aspect = f64::from(rect.width()) / f64::from(rect.height().max(1.0));
|
||||
step(&mut state.cam, motion.motion, motion.seconds, f64::from(rect.height()), model.radius, aspect, reverse_zoom, free_orbit);
|
||||
}
|
||||
let painter = ui.painter_at(rect);
|
||||
painter.rect(rect, t.radius_sm, t.dialog_body, egui::Stroke::new(1.0, t.dialog_rule), egui::StrokeKind::Inside);
|
||||
let (dark, lit) = (t.border, t.text);
|
||||
let frame = project(model, &state.cam, rect.shrink(1.0));
|
||||
let vertices = frame
|
||||
.points
|
||||
.iter()
|
||||
.zip(&frame.shades)
|
||||
.map(|(&pos, &shade)| egui::epaint::Vertex { pos, uv: egui::epaint::WHITE_UV, color: dark.lerp_to_gamma(lit, 0.25 + 0.75 * shade) })
|
||||
.collect();
|
||||
let mesh = egui::Mesh { indices: frame.indices, vertices, texture_id: egui::TextureId::default() };
|
||||
painter.add(egui::Shape::mesh(mesh));
|
||||
*boat = Some(state);
|
||||
Some(rect)
|
||||
}
|
||||
|
||||
/// The boat back where it began.
|
||||
pub(crate) fn reset(boat: &mut Option<Boat>) {
|
||||
*boat = None;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "test_boat_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,156 @@
|
||||
use super::*;
|
||||
use crate::space_mouse::Motion;
|
||||
use egui::vec2;
|
||||
|
||||
fn boat() -> &'static Model {
|
||||
model().expect("the built-in model reads")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boat_asset_reads_with_its_counts_and_bounds() {
|
||||
let m = boat();
|
||||
assert!(m.triangles.len() >= 500 && m.triangles.len() <= 2_000, "{} triangles", m.triangles.len());
|
||||
// Smooth shading: vertices are shared where the surface is smooth, and split at creases.
|
||||
assert!(m.vertices.len() > m.triangles.len() / 3 && m.vertices.len() <= m.triangles.len() * 3, "{} vertices", m.vertices.len());
|
||||
assert!(DATA.len() < 100_000, "{} bytes", DATA.len());
|
||||
// About 60 mm long, centred on the origin.
|
||||
let (low, high) = m.vertices.iter().fold((Vec3::new(1e9, 1e9, 1e9), Vec3::new(-1e9, -1e9, -1e9)), |(lo, hi), v| (lo.min(*v), hi.max(*v)));
|
||||
let size = high - low;
|
||||
assert!((size.x.max(size.y).max(size.z) - 60.0).abs() < 1.0, "{size:?}");
|
||||
assert!((m.radius - 30.0).abs() < 15.0 && m.radius >= 25.0, "radius {}", m.radius);
|
||||
// Every normal is a unit vector.
|
||||
assert!(m.normals.iter().all(|n| (n.len() - 1.0).abs() < 1e-6));
|
||||
assert!(m.vertex_normals.iter().all(|n| (n.len() - 1.0).abs() < 1e-6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boat_rejects_files_that_do_not_add_up() {
|
||||
assert!(parse(&[]).is_none() && parse(&DATA[..40]).is_none());
|
||||
let mut long = DATA.to_vec();
|
||||
long.push(0);
|
||||
assert!(parse(&long).is_none());
|
||||
// An index past the vertices.
|
||||
let mut bad = DATA.to_vec();
|
||||
let at = bad.len() - 2;
|
||||
bad[at..].copy_from_slice(&u16::MAX.to_le_bytes());
|
||||
assert!(parse(&bad).is_none());
|
||||
// A half size that is not a number.
|
||||
let mut nan = DATA.to_vec();
|
||||
nan[20..24].copy_from_slice(&f32::NAN.to_le_bytes());
|
||||
assert!(parse(&nan).is_none());
|
||||
}
|
||||
|
||||
fn motion(translation: [f64; 3], rotation: [f64; 3]) -> Motion {
|
||||
Motion { translation, rotation }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boat_step_orbits_pans_zooms_rolls_and_holds_the_boat_in_its_box() {
|
||||
let radius = boat().radius;
|
||||
let start = Boat::new(radius).cam;
|
||||
let aspect = 1.4;
|
||||
let run = |m: Motion, seconds: f64| {
|
||||
let mut cam = start;
|
||||
step(&mut cam, m, seconds, 150.0, radius, aspect, false, false);
|
||||
cam
|
||||
};
|
||||
// Nothing moves nothing.
|
||||
assert_eq!(run(motion([0.0; 3], [0.0; 3]), 0.05), start);
|
||||
// Orbit turns the view about the boat; roll rolls it; both keep the target.
|
||||
let orbit = run(motion([0.0; 3], [0.5, 0.5, 0.0]), 0.05);
|
||||
assert!(orbit.yaw != start.yaw || orbit.pitch != start.pitch);
|
||||
assert_eq!(orbit.target, start.target);
|
||||
assert!(run(motion([0.0; 3], [0.0, 0.0, 0.5]), 0.05).roll != start.roll);
|
||||
// Zoom changes the distance one way for push and the other for pull.
|
||||
let (push, pull) = (run(motion([0.0, 1.0, 0.0], [0.0; 3]), 0.05), run(motion([0.0, -1.0, 0.0], [0.0; 3]), 0.05));
|
||||
assert!(push.distance < start.distance && pull.distance > start.distance);
|
||||
// Pan moves the target, within the room the box leaves.
|
||||
let panned = run(motion([1.0, 0.0, 0.0], [0.0; 3]), 0.05);
|
||||
assert!(panned.target != start.target);
|
||||
// Held down for a long time, the limits hold: nearest, farthest, and the pan room.
|
||||
let mut cam = start;
|
||||
for _ in 0..400 {
|
||||
step(&mut cam, motion([1.0, 1.0, 1.0], [0.0; 3]), 0.05, 150.0, radius, aspect, false, false);
|
||||
}
|
||||
assert!(cam.distance >= radius * NEAREST / 1.0 - 1e-6 && cam.target.len() <= cam.distance * (FOV / 2.0).tan() + 1e-6);
|
||||
let mut cam = start;
|
||||
for _ in 0..400 {
|
||||
step(&mut cam, motion([-1.0, -1.0, -1.0], [0.0; 3]), 0.05, 150.0, radius, aspect, false, false);
|
||||
}
|
||||
assert!(cam.distance <= radius * FARTHEST + 1e-6);
|
||||
// Hostile numbers leave a valid camera.
|
||||
let mut cam = start;
|
||||
step(&mut cam, motion([f64::NAN, f64::INFINITY, 1e300], [f64::NAN; 3]), 0.05, 150.0, radius, aspect, false, false);
|
||||
assert!(cam.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boat_draws_inside_its_box_at_every_size_and_pose() {
|
||||
let m = boat();
|
||||
for (w, h) in [(120.0_f32, 90.0_f32), (200.0, 150.0), (260.0, 150.0), (150.0, 260.0), (400.0, 100.0)] {
|
||||
let rect = Rect::from_min_size(pos2(10.0, 20.0), vec2(w, h));
|
||||
let aspect = f64::from(w) / f64::from(h);
|
||||
let mut cam = Boat::new(m.radius).cam;
|
||||
// Through the extremes of what the 3D mouse can do.
|
||||
for (translation, rotation) in [([0.0; 3], [0.0; 3]), ([1.0, -1.0, 1.0], [1.0, 0.0, 1.0]), ([-1.0, 1.0, -1.0], [0.0, 1.0, -1.0])] {
|
||||
for _ in 0..200 {
|
||||
step(&mut cam, motion(translation, rotation), 0.05, f64::from(h), m.radius, aspect, false, false);
|
||||
}
|
||||
let frame = project(m, &cam, rect.shrink(1.0));
|
||||
assert!(!frame.indices.is_empty(), "{w}x{h}: something is drawn");
|
||||
assert!(frame.shades.iter().all(|s| (0.0..=1.0).contains(s)));
|
||||
for &index in &frame.indices {
|
||||
let corner = frame.points[index as usize];
|
||||
assert!(rect.expand(0.5).contains(corner), "{w}x{h}: {corner:?} is outside {rect:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What a frame asks of the painter: every vertex once and the facing triangles' indices, with a
|
||||
/// ceiling, and the time it takes to build one (printed; a loose limit so a slow runner is not a failure).
|
||||
#[test]
|
||||
fn test_boat_costs_a_bounded_amount_to_project_per_frame() {
|
||||
let m = boat();
|
||||
let rect = Rect::from_min_size(pos2(0.0, 0.0), vec2(260.0, 150.0));
|
||||
let cam = Boat::new(m.radius).cam;
|
||||
let started = std::time::Instant::now();
|
||||
let frames = 20;
|
||||
let mut last = None;
|
||||
for _ in 0..frames {
|
||||
last = Some(project(m, &cam, rect));
|
||||
}
|
||||
let per_frame = started.elapsed() / frames;
|
||||
let frame = last.unwrap();
|
||||
eprintln!("BOAT COST {} vertices, {} indices, {per_frame:?} per frame", frame.points.len(), frame.indices.len());
|
||||
assert!(frame.points.len() <= 5_000 && frame.indices.len() <= 3 * 5_000, "{} vertices, {} indices", frame.points.len(), frame.indices.len());
|
||||
assert!(per_frame < std::time::Duration::from_millis(100), "{per_frame:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boat_far_faces_come_first_and_a_page_opened_again_starts_afresh() {
|
||||
let m = boat();
|
||||
let cam = Boat::new(m.radius).cam;
|
||||
let rect = Rect::from_min_size(pos2(0.0, 0.0), vec2(200.0, 150.0));
|
||||
let drawn = project(m, &cam, rect).indices.len() / 3;
|
||||
// Only the faces turned to the eye: roughly half the triangles of a closed boat.
|
||||
assert!(drawn < m.triangles.len() * 3 / 4 && drawn > m.triangles.len() / 4, "{drawn} of {}", m.triangles.len());
|
||||
// The boat comes back with the page: not drawn for a moment, the state starts afresh.
|
||||
let ctx = crate::fonts::test_context();
|
||||
let mut state = None;
|
||||
let moved = Some(DeviceMotion { motion: motion([0.0; 3], [1.0, 0.0, 0.0]), seconds: 0.05 });
|
||||
let draw = |time: f64, motion: Option<DeviceMotion>, state: &mut Option<Boat>| {
|
||||
let mut output = ctx.run_ui(egui::RawInput { time: Some(time), ..Default::default() }, |ui| {
|
||||
show(ui, state, motion, false, false, vec2(200.0, 150.0));
|
||||
});
|
||||
output.textures_delta.clear();
|
||||
};
|
||||
draw(1.0, moved, &mut state);
|
||||
draw(1.016, moved, &mut state);
|
||||
let turned = state.unwrap().cam;
|
||||
assert_ne!((turned.yaw, turned.pitch), (Boat::new(m.radius).cam.yaw, Boat::new(m.radius).cam.pitch));
|
||||
draw(5.0, None, &mut state);
|
||||
assert_eq!(state.unwrap().cam, Boat::new(m.radius).cam, "away for a while, it starts afresh");
|
||||
reset(&mut state);
|
||||
assert!(state.is_none());
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"agent_cursor":{"follow_camera":false,"show":false,"speed":1.0},"auto_project":true,"autosave_minutes":5.0,"browser_collapsed":[],"browser_expanded":[],"dark":true,"grid":true,"ground_shadow":true,"perspective":false,"pick_bodies":false,"preferences":{"agent_always_allow":{},"agent_chat_minimized":false,"agent_chat_popped":false,"agent_chat_side":"right","agent_chat_turn_limit":24,"agent_chat_width":380.0,"agent_chat_window":null,"agent_default":"","agent_models":{},"agent_off_thread":false,"agent_permissions":{},"agents":[],"ai_features":true,"ai_permissions":"edits","ai_toolbar_button":true,"ambient_occlusion":false,"angle_decimals":1,"browser_float":false,"browser_width":250.0,"check_updates":true,"color_vision":"off","custom_slicers":[],"custom_themes":[],"default_units":"mm","disabled_plugins":[],"display_quality":"normal","grid_follows_view":false,"language":"en","length_decimals":2,"look_at_anim":true,"look_at_speed":1.0,"manipulator_free_alt":true,"measure_axis_distances":true,"middle_double_click":"home","mouse_gestures":{"enabled":false,"wheels":{}},"msaa":4,"nav":"fusion","navigator_style":"axes","offer_stand_up":true,"orbit_around":"view_centre","orbit_cursor":false,"pan_reverse_x":false,"radial_context_menu":false,"save_mesh_format":"stl","save_mesh_send":false,"save_mesh_slicer":"","selection_shift_add_only":false,"show_frame_rate":false,"smooth_wheel_zoom":true,"space_mouse_axis_source":[0,1,2,3,4,5],"space_mouse_axis_speed":[1.0,1.0,1.0,1.0,1.0,1.0],"space_mouse_buttons":{},"space_mouse_calibrations":{"hid 256f:c63a":{"axes":[{"dead_zone":0,"full":[350,350],"rest":0,"reversed":false,"source":0},{"dead_zone":0,"full":[350,350],"rest":0,"reversed":false,"source":1},{"dead_zone":22,"full":[350,350],"rest":0,"reversed":false,"source":2},{"dead_zone":0,"full":[350,350],"rest":0,"reversed":false,"source":3},{"dead_zone":0,"full":[350,350],"rest":0,"reversed":false,"source":4},{"dead_zone":0,"full":[350,350],"rest":0,"reversed":false,"source":5}],"dead_zone_share":0.0,"kind":"ReturnToCenter"}},"space_mouse_driver":"direct","space_mouse_enabled":true,"space_mouse_ghost_position":"current","space_mouse_in_view":true,"space_mouse_offered":["spacenavd"],"space_mouse_reverse":[false,false,false,false,false,false],"space_mouse_speed":1.0,"supersampling":1.0,"supersampling_adaptive":true,"suppress_sketch_snap":{"alt":true,"ctrl":true,"shift":false},"theme":"dark","timeline_left":false,"timeline_left_browser_collapsed":false,"timeline_left_height":360.0,"trackpad_pan":true,"ui_font":"","ui_scale":1.0,"up_axis":"z","update":{"last_check":0.0,"next_check":0.0,"rate_limited_until":0.0,"remind_after":0.0,"skipped":""},"view_dxf_dashed":false,"view_dxf_tolerance":0.01,"zoom_reverse":false,"zoom_to_cursor":true},"recent":[],"recent_opened_at":{},"shortcut_box":{"pinned":[],"recent":[]},"shortcuts":{},"toolbar":{"panels":{},"version":2},"visual_style":0}
|
||||
@@ -362,7 +362,6 @@ pub(crate) fn button_at(ui: &mut Ui, id: &str, rect: egui::Rect, label: impl Int
|
||||
}
|
||||
|
||||
/// Accent action outside the footer, using the same shared button component.
|
||||
#[cfg(any(not(target_arch = "wasm32"), test))]
|
||||
pub(crate) fn primary_button(ui: &mut Ui, label: &str) -> Response {
|
||||
let response = button(ui, crate::i18n::tr_label(label), Button::Primary);
|
||||
scenario::publish_target(&format!("dialog:button:{label}"), response.rect);
|
||||
|
||||
@@ -29,7 +29,12 @@ pub(crate) fn segmented(ui: &mut Ui, selected: &mut usize, options: &[&str], con
|
||||
ui.painter().layout_no_wrap(crate::i18n::tr_label(option).to_string(), crate::fonts::semibold(t.font_label), t.form_ink).size().x
|
||||
})
|
||||
.fold(0.0, f32::max);
|
||||
if !intrinsic && !proportional && !compact && widest + 2.0 * t.space_8 > segment_width {
|
||||
// Judged on the whole row, which is what a scrollbar narrows, not on one segment.
|
||||
if !intrinsic
|
||||
&& !proportional
|
||||
&& !compact
|
||||
&& !super::fits_steadily(ui, label(ui), (widest + 2.0 * t.space_8) * options.len() as f32, segment_width * options.len() as f32)
|
||||
{
|
||||
let before = *selected;
|
||||
let id = format!("segments_fallback_{}", label(ui));
|
||||
dropdown(ui, &id, selected, options);
|
||||
|
||||
@@ -44,6 +44,46 @@ fn optional_field_keeps_empty_default_and_options_use_two_columns() {
|
||||
assert!(flip && copy);
|
||||
}
|
||||
|
||||
/// A scrollbar that takes its width from the page, coming and going, narrows the row by that width; the
|
||||
/// choice between the segments and the dropdown must not flip because of that, only because the row
|
||||
/// really is too narrow.
|
||||
#[test]
|
||||
fn segments_stay_segments_when_a_scrollbar_appears_and_disappears() {
|
||||
let options = ["Origin", "View centre", "Point under the cursor"];
|
||||
let solid = egui::style::ScrollStyle::solid();
|
||||
let scrollbar = solid.allocated_width();
|
||||
assert!(scrollbar > 0.0);
|
||||
let fresh = || {
|
||||
let ctx = crate::fonts::test_context();
|
||||
ctx.all_styles_mut(|style| style.spacing.scroll = solid);
|
||||
ctx
|
||||
};
|
||||
let form = |ctx: &egui::Context, width: f32| {
|
||||
scenario::clear_handles();
|
||||
let mut selected = 0;
|
||||
frame(ctx, vec![], &mut |ui: &mut Ui| {
|
||||
ui.allocate_ui(egui::vec2(width, 60.0), |ui| {
|
||||
row(ui, "Orbit around", |ui| segmented(ui, &mut selected, &options, Default::default()));
|
||||
});
|
||||
});
|
||||
scenario::rect_of("dialog:segments:Orbit around").is_some()
|
||||
};
|
||||
// The width where the segments just fit, found without any memory of an earlier frame being
|
||||
// anything but the generous start (so it is the width less one scrollbar).
|
||||
let strict = (200..1200).map(|w| w as f32).find(|&w| form(&fresh(), w) && form(&fresh(), w + scrollbar + 1.0)).expect("a width fits");
|
||||
// From wherever it starts, with and without the scrollbar's width taken off, the form holds.
|
||||
for start in [strict + scrollbar, strict + scrollbar + 3.0] {
|
||||
let ctx = fresh();
|
||||
for width in [start, start - scrollbar, start, start - scrollbar, start - scrollbar] {
|
||||
assert!(form(&ctx, width), "start {start}: segments at {width}");
|
||||
}
|
||||
}
|
||||
// Really too narrow (more than the scrollbar short), it is the dropdown, and it comes back when widened.
|
||||
let ctx = fresh();
|
||||
assert!(!form(&ctx, strict - 3.0 * scrollbar) && !form(&ctx, strict - 3.0 * scrollbar), "far too narrow: the dropdown");
|
||||
assert!(form(&ctx, strict + scrollbar + 3.0), "wide again: segments");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beveled_segments_share_fill_corners_at_internal_joins() {
|
||||
use crate::theme::Tokens;
|
||||
|
||||
@@ -140,7 +140,7 @@ pub(crate) fn caption(ui: &mut Ui, aligned_to: &str, text: &str) {
|
||||
/// Memory belongs to the enclosing dialog ID; animation is avoided so each row keeps its fixed height.
|
||||
/// Persisted disclosure with a rule and uppercase title. Tokens: `control_height, space_2, radius_sm, hover, dialog_rule, form_section_icon_size, form_gap_y, font_caption, font_section_tracking, text_dim`.
|
||||
pub(crate) fn section<R>(ui: &mut Ui, id: impl egui::AsIdSalt, title: &str, default_open: bool, add: impl FnOnce(&mut Ui) -> R) -> Option<R> {
|
||||
section_count(ui, id, title, default_open, None, add)
|
||||
section_count(ui, id, title, default_open, None::<usize>, add)
|
||||
}
|
||||
|
||||
/// Section heading with an optional count badge; disclosure state uses the same identity.
|
||||
@@ -149,7 +149,7 @@ pub(crate) fn section_count<R>(
|
||||
id: impl egui::AsIdSalt,
|
||||
title: &str,
|
||||
default_open: bool,
|
||||
count: Option<usize>,
|
||||
count: Option<impl std::fmt::Display>,
|
||||
add: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<R> {
|
||||
let t = Tokens::get();
|
||||
|
||||
@@ -71,3 +71,17 @@ mod slider_tests;
|
||||
mod test_support;
|
||||
|
||||
pub(crate) use layout::label as row_label;
|
||||
|
||||
/// Whether `need` points fit in `have`, answered so that a scrollbar coming and going (it takes
|
||||
/// `ui.spacing().scroll.allocated_width()` of the width) does not flip it: what has fitted keeps
|
||||
/// fitting until it misses by more than the scrollbar's width, and what has not fitted must fit
|
||||
/// outright. With nothing remembered yet it is the generous answer, so the first frame with a
|
||||
/// scrollbar and the first without agree. Keyed by `key` within `ui`.
|
||||
pub(crate) fn fits_steadily(ui: &egui::Ui, key: impl std::hash::Hash + std::fmt::Debug, need: f32, have: f32) -> bool {
|
||||
let slack = ui.spacing().scroll.allocated_width();
|
||||
let id = ui.make_persistent_id(("fits_steadily", key));
|
||||
let was = ui.data(|data| data.get_temp::<bool>(id)).unwrap_or(true);
|
||||
let fits = need <= have + if was { slack } else { 0.0 };
|
||||
ui.data_mut(|data| data.insert_temp(id, fits));
|
||||
fits
|
||||
}
|
||||
|
||||
@@ -130,3 +130,26 @@ fn formatted_slider_editor_fits_without_shifting_the_track_during_a_scale_drag()
|
||||
}
|
||||
assert!(widths.windows(2).all(|pair| pair[0] == pair[1]), "dragging must not move the input range: {widths:?}");
|
||||
}
|
||||
|
||||
/// The value box of a slider takes typed numbers (the per-axis speeds among them): Tab order reaches it
|
||||
/// and Enter commits; a number past the range is held to the range.
|
||||
#[test]
|
||||
fn a_sliders_value_box_takes_typed_numbers_and_clamps_them_to_its_range() {
|
||||
use crate::ui_kit::test_support::*;
|
||||
for (typed, expected) in [("1.5", 1.5), ("9", 3.0), ("-4", 0.0), ("0", 0.0)] {
|
||||
let ctx = crate::fonts::test_context();
|
||||
let mut value = 1.0_f32;
|
||||
let mut draw = |ui: &mut Ui| {
|
||||
slider(ui, &mut value, 0.0..=3.0, Slider { step: Some(0.05), format: |v| format!("{v:.2}×"), ..Default::default() });
|
||||
};
|
||||
frame(&ctx, vec![], &mut draw);
|
||||
let track = scenario::rect_of("dialog:slider_track:").unwrap_or_else(|| scenario::rects("dialog:slider").first().expect("the slider").1);
|
||||
let at = egui::pos2(track.right() + 30.0, track.center().y);
|
||||
frame(&ctx, pointer(at, true), &mut draw);
|
||||
frame(&ctx, pointer(at, false), &mut draw);
|
||||
frame(&ctx, vec![egui::Event::Text(typed.into())], &mut draw);
|
||||
frame(&ctx, key(egui::Key::Enter), &mut draw);
|
||||
frame(&ctx, vec![], &mut draw);
|
||||
assert!((value - expected).abs() < 1e-3, "typed {typed}: {value}");
|
||||
}
|
||||
}
|
||||
|
||||
+159
-15
@@ -3480,7 +3480,27 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
axis_view(app, ui, rect);
|
||||
nav_bar(app, ui, rect);
|
||||
agent_chip(app, ui, overlay_rect);
|
||||
transient_messages(app, ui, rect);
|
||||
// The notices first, so the 3D mouse picture can keep clear of them.
|
||||
let toasts = transient_messages(app, ui, rect);
|
||||
if app.preferences.space_mouse_in_view
|
||||
&& app.space_mouse.calibrating.is_none()
|
||||
&& let Some(device) = &app.space_mouse.device
|
||||
{
|
||||
use crate::prefs::SpaceMouseGhost;
|
||||
let calibration = app.preferences.space_mouse_calibrations.get(device);
|
||||
let position = app.preferences.space_mouse_ghost_position;
|
||||
// The agent chip (an agent command computing off the window thread) sits at the lower left
|
||||
// too: the picture stays clear of it.
|
||||
let chip = app.agent_computing().map(|_| Rect::from_min_size(agent_chip_anchor(app, overlay_rect), vec2(400.0, 34.0)));
|
||||
let navigation = app.viewport.layout.as_ref().map(|layout| layout.navigation);
|
||||
let obstacles = [navigation, chip].into_iter().flatten().chain(toasts).collect::<Vec<_>>();
|
||||
// The Viewport positions sit in the content area (right of the browser), Left Window in the
|
||||
// corner of the whole window; both stay above the floating History card, like the legend.
|
||||
let area = if position == SpaceMouseGhost::LeftWindow { rect } else { crate::shell::content_rect(app, rect) };
|
||||
let area = crate::shell::bottom_overlay_rect(app, area);
|
||||
let picture = crate::space_mouse_model::corner_rect(area, position, &obstacles);
|
||||
crate::space_mouse_model::corner(ui, picture, calibration, app.space_mouse.raw.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_box_selection(painter: &egui::Painter, bx: BoxSel, sketch: bool) {
|
||||
@@ -3503,6 +3523,13 @@ fn paint_box_selection(painter: &egui::Painter, bx: BoxSel, sketch: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Where the agent chip's top left is: above the History bar, and above the computing notice when
|
||||
/// that is up too.
|
||||
fn agent_chip_anchor(app: &SolveApp, rect: Rect) -> Pos2 {
|
||||
let above = if app.computing_since().is_some() { 106.0 } else { 76.0 };
|
||||
pos2(rect.left() + 12.0, rect.bottom() - above)
|
||||
}
|
||||
|
||||
/// An agent's command computing off the window thread (#1989): which one, in the agent cursor's
|
||||
/// colour, and a button that stops waiting for it.
|
||||
fn agent_chip(app: &mut SolveApp, ui: &egui::Ui, rect: Rect) {
|
||||
@@ -3511,8 +3538,7 @@ fn agent_chip(app: &mut SolveApp, ui: &egui::Ui, rect: Rect) {
|
||||
if let Some((since, label)) = app.agent_computing().map(|(since, label)| (since, label.to_string())) {
|
||||
let seconds = ((crate::now_ms() - since) / 1000.0).floor().max(0.0);
|
||||
let computing = crate::i18n::format("dialog.computing", &[("seconds", &format!("{seconds:.0}"))]);
|
||||
let above = if app.computing_since().is_some() { 106.0 } else { 76.0 };
|
||||
let pos = pos2(rect.left() + 12.0, rect.bottom() - above);
|
||||
let pos = agent_chip_anchor(app, rect);
|
||||
let galley = painter.layout_no_wrap(format!("{label} · {computing}"), FontId::proportional(t.font_label), t.text);
|
||||
let stop = painter.layout_no_wrap(crate::i18n::tr("ui.agent_chat_stop").to_string(), FontId::proportional(t.font_label), t.text);
|
||||
let r = Rect::from_min_size(pos, vec2(galley.size().x + stop.size().x + 54.0, galley.size().y.max(stop.size().y) + 8.0));
|
||||
@@ -3538,23 +3564,43 @@ fn agent_chip(app: &mut SolveApp, ui: &egui::Ui, rect: Rect) {
|
||||
/// How long a status message stays in the view (milliseconds).
|
||||
const STATUS_TOAST_MS: f64 = 6000.0;
|
||||
|
||||
/// A single visual pattern for ongoing work and the existing six-second status.
|
||||
fn transient_messages(app: &mut SolveApp, ui: &egui::Ui, viewport: Rect) {
|
||||
if app.status.is_none() && app.computing_since().is_none() {
|
||||
return;
|
||||
/// A single visual pattern for ongoing work, the existing six-second status and the 3D mouse
|
||||
/// calibration offer, stacked from the bottom of the free view. Returns the toasts' rects.
|
||||
fn transient_messages(app: &mut SolveApp, ui: &egui::Ui, viewport: Rect) -> Vec<Rect> {
|
||||
// Nothing to show or time: an idle window skips the layout. The offer is checked too, as its clock
|
||||
// runs from the frames `due` sees.
|
||||
if app.status.is_none() && app.computing_since().is_none() && crate::space_mouse_offer::offerable(app).is_none() {
|
||||
return Vec::new();
|
||||
}
|
||||
let t = Tokens::get();
|
||||
let mut shown = Vec::new();
|
||||
let mut obstacles = crate::shell::covered_rects(app);
|
||||
if let Some(layout) = &app.viewport.layout {
|
||||
obstacles.extend([layout.navigation, layout.axis_view]);
|
||||
}
|
||||
let mut free = crate::viewport_layout::free_rect(crate::shell::content_rect(app, viewport), &obstacles).shrink(t.space_8);
|
||||
// The 3D mouse calibration offer: text and actions from its module, drawn by the shared toast.
|
||||
if let Some(offer) = crate::space_mouse_offer::due(app, ui.ctx()) {
|
||||
let labels: Vec<String> = offer.actions.iter().map(|action| crate::i18n::tr(action.label()).to_string()).collect();
|
||||
let labels: Vec<(&str, ToastKind)> = labels.iter().map(String::as_str).zip(offer.actions.iter().map(|action| action.kind())).collect();
|
||||
if let Some((r, clicked)) = paint_toast_with(ui, free, crate::i18n::tr("ui.3d_mouse_offer").to_string(), ToastLead::Dot(false), &labels) {
|
||||
crate::scenario::publish_rect("toast:space_mouse_offer", r);
|
||||
free.max.y = r.top() - t.space_8;
|
||||
shown.push(r);
|
||||
crate::space_mouse_offer::keep(app, ui.ctx(), r);
|
||||
if let Some(action) = clicked.and_then(|i| offer.actions.get(i)) {
|
||||
crate::space_mouse_offer::act(app, ui.ctx(), &offer, *action);
|
||||
}
|
||||
ui.ctx().request_repaint_after(std::time::Duration::from_millis(500));
|
||||
}
|
||||
}
|
||||
if let Some((msg, at, error)) = app.status.clone() {
|
||||
let shown_ms = crate::now_ms() - at;
|
||||
if shown_ms < STATUS_TOAST_MS {
|
||||
if let Some(r) = paint_toast(ui, free, msg, error, false) {
|
||||
crate::scenario::publish_rect("toast", r);
|
||||
free.max.y = r.top() - t.space_8;
|
||||
shown.push(r);
|
||||
// Open the run's messages from the tokenised status toast.
|
||||
crate::scenario::publish_handle("status:chip", r.center());
|
||||
let hint = if app.status_undo { crate::i18n::tr_label("Undo") } else { crate::i18n::tr("messages.chip_hint") };
|
||||
@@ -3583,19 +3629,95 @@ fn transient_messages(app: &mut SolveApp, ui: &egui::Ui, viewport: Rect) {
|
||||
if app.status.is_none() {
|
||||
crate::scenario::publish_rect("toast", r);
|
||||
}
|
||||
shown.push(r);
|
||||
}
|
||||
ui.ctx().request_repaint_after(std::time::Duration::from_millis(100));
|
||||
}
|
||||
shown
|
||||
}
|
||||
|
||||
/// What a toast's button is for, which sets its look and where it goes: the main action first
|
||||
/// (the accent button), then an alternative, then the quiet dismissals.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ToastKind {
|
||||
Primary,
|
||||
Secondary,
|
||||
Dismiss,
|
||||
}
|
||||
|
||||
/// Where each button of a toast goes, relative to the row's top left: `buttons` are (width, kind) in
|
||||
/// the order given; the primary and the alternative form one group and the dismissals another.
|
||||
/// Groups go side by side while they fit in `row_width` and wrap as a whole, so a button is not
|
||||
/// left alone on a row while its group-mate fits beside it; a group wider than the row wraps
|
||||
/// button by button.
|
||||
fn toast_action_spots(buttons: &[(f32, ToastKind)], row_width: f32, gap: f32, height: f32) -> Vec<Rect> {
|
||||
let mut spots = vec![Rect::NOTHING; buttons.len()];
|
||||
let groups: [Vec<usize>; 2] = [
|
||||
(0..buttons.len()).filter(|&i| buttons[i].1 != ToastKind::Dismiss).collect(),
|
||||
(0..buttons.len()).filter(|&i| buttons[i].1 == ToastKind::Dismiss).collect(),
|
||||
];
|
||||
let (mut x, mut y) = (0.0_f32, 0.0_f32);
|
||||
for group in groups.iter().filter(|g| !g.is_empty()) {
|
||||
let whole: f32 = group.iter().map(|&i| buttons[i].0).sum::<f32>() + gap * (group.len() - 1) as f32;
|
||||
if x > 0.0 && x + whole > row_width {
|
||||
(x, y) = (0.0, y + height + gap);
|
||||
}
|
||||
for &i in group {
|
||||
let width = buttons[i].0;
|
||||
// Only a group wider than a whole row wraps inside itself.
|
||||
if x > 0.0 && x + width > row_width.max(width) && whole > row_width {
|
||||
(x, y) = (0.0, y + height + gap);
|
||||
}
|
||||
spots[i] = Rect::from_min_size(pos2(x, y), vec2(width, height));
|
||||
x += width + gap;
|
||||
}
|
||||
x += gap;
|
||||
}
|
||||
spots
|
||||
}
|
||||
|
||||
/// What leads a toast's text: a dot (red for an error) or a spinner.
|
||||
#[derive(Clone, Copy)]
|
||||
enum ToastLead {
|
||||
Dot(bool),
|
||||
Spinner,
|
||||
}
|
||||
|
||||
fn paint_toast(ui: &egui::Ui, free: Rect, text: String, error: bool, computing: bool) -> Option<Rect> {
|
||||
let lead = if computing { ToastLead::Spinner } else { ToastLead::Dot(error) };
|
||||
paint_toast_with(ui, free, text, lead, &[]).map(|(rect, _)| rect)
|
||||
}
|
||||
|
||||
/// The one toast painter: its text in a card at the bottom centre of `free`, led by a dot or a
|
||||
/// spinner, and, below the text, a wrapping row of buttons (`actions`, kit buttons). Returns the
|
||||
/// card's rect and which button was clicked, if any. With no actions the card is exactly the status
|
||||
/// and computing toast it always was. The buttons are not live while an issue report holds CAD
|
||||
/// input; none is laid outside the card, whose width is bounded by `free`.
|
||||
fn paint_toast_with(ui: &egui::Ui, free: Rect, text: String, lead: ToastLead, actions: &[(&str, ToastKind)]) -> Option<(Rect, Option<usize>)> {
|
||||
if !free.is_finite() || !free.is_positive() || free.width() <= 40.0 {
|
||||
return None;
|
||||
}
|
||||
let t = Tokens::get();
|
||||
let painter = ui.painter().with_clip_rect(free);
|
||||
let galley = painter.layout(text, crate::fonts::medium(t.font_label), t.text, (free.width() - 40.0).min(440.0));
|
||||
let size = galley.size() + vec2(40.0, 16.0);
|
||||
// The buttons: each as wide as its text and a margin, in rows no wider than the text may be.
|
||||
let gap = t.space_8;
|
||||
let button_height = crate::ui_kit::row_height();
|
||||
let row_width = (free.width() - 40.0).clamp(0.0, 440.0);
|
||||
let sizes: Vec<(f32, ToastKind)> = actions
|
||||
.iter()
|
||||
.map(|(label, kind)| {
|
||||
let padding = if *kind == ToastKind::Primary { t.space_16 } else { t.space_12 };
|
||||
let width = painter.layout_no_wrap((*label).to_string(), crate::fonts::medium(t.font_label), t.text).size().x + 2.0 * padding;
|
||||
(width.min(row_width), *kind)
|
||||
})
|
||||
.collect();
|
||||
let spots = toast_action_spots(&sizes, row_width, gap, button_height);
|
||||
let widest = spots.iter().map(|spot| spot.right()).fold(0.0, f32::max);
|
||||
let y = spots.iter().map(|spot| spot.top()).fold(0.0, f32::max);
|
||||
let actions_height = if spots.is_empty() { 0.0 } else { y + button_height + gap };
|
||||
let content = vec2(galley.size().x.max(widest), galley.size().y + actions_height);
|
||||
let size = content + vec2(40.0, 16.0);
|
||||
if !size.is_finite() || size.x > free.width() || size.y > free.height() {
|
||||
return None;
|
||||
}
|
||||
@@ -3605,14 +3727,36 @@ fn paint_toast(ui: &egui::Ui, free: Rect, text: String, error: bool, computing:
|
||||
}
|
||||
let frame = Container::new(ui.ctx(), Surface::Toast).frame(ui.ctx(), t.panel, crate::frame::Side::Left);
|
||||
Container::paint_frame(ui.painter(), frame, r.shrink(frame.stroke.width));
|
||||
let dot = pos2(r.left() + 14.0, r.center().y);
|
||||
if computing {
|
||||
egui::Spinner::new().size(12.0).color(t.text).paint_at(ui, Rect::from_center_size(dot, vec2(12.0, 12.0)));
|
||||
} else {
|
||||
painter.circle_filled(dot, 3.0, if error { t.error } else { t.text });
|
||||
let dot = pos2(r.left() + 14.0, r.center().y.min(r.top() + 8.0 + galley.size().y / 2.0));
|
||||
match lead {
|
||||
ToastLead::Spinner => egui::Spinner::new().size(12.0).color(t.text).paint_at(ui, Rect::from_center_size(dot, vec2(12.0, 12.0))),
|
||||
ToastLead::Dot(error) => drop(painter.circle_filled(dot, 3.0, if error { t.error } else { t.text })),
|
||||
}
|
||||
painter.galley(r.min + vec2(28.0, 8.0), galley, t.text);
|
||||
Some(r)
|
||||
let text_top = r.min + vec2(28.0, 8.0);
|
||||
painter.galley(text_top, galley.clone(), t.text);
|
||||
let mut clicked = None;
|
||||
let origin = text_top + vec2(0.0, galley.size().y + gap);
|
||||
for (index, ((label, kind), spot)) in actions.iter().zip(&spots).enumerate() {
|
||||
let at = spot.translate(origin.to_vec2());
|
||||
// A UI of its own on the viewport's layer, so the toast needs no `&mut Ui`.
|
||||
let mut button_ui = egui::Ui::new(
|
||||
ui.ctx().clone(),
|
||||
egui::Id::new(("sc_toast_action", index)),
|
||||
egui::UiBuilder::new().layer_id(ui.layer_id()).max_rect(at).layout(egui::Layout::left_to_right(egui::Align::Center)),
|
||||
);
|
||||
button_ui.set_clip_rect(button_ui.clip_rect().intersect(r));
|
||||
let response = button_ui
|
||||
.add_enabled_ui(!crate::issue_report::cad_input_blocked(), |ui| match kind {
|
||||
ToastKind::Primary => crate::ui_kit::primary_button(ui, label),
|
||||
ToastKind::Secondary => crate::ui_kit::button(ui, *label, crate::ui_kit::Button::Normal),
|
||||
ToastKind::Dismiss => crate::ui_kit::button(ui, *label, crate::ui_kit::Button::Small),
|
||||
})
|
||||
.inner;
|
||||
if response.clicked() {
|
||||
clicked = Some(index);
|
||||
}
|
||||
}
|
||||
Some((r, clicked))
|
||||
}
|
||||
|
||||
/// A click without a command: select the candidate (replacing the selection, or adding /
|
||||
|
||||
@@ -357,6 +357,130 @@ fn compass_circle_grabs_on_hover_and_the_home_chip_points() {
|
||||
assert_ne!(h.output.cursor_icon, egui::CursorIcon::Grab, "no hand away from the compass");
|
||||
}
|
||||
|
||||
/// The status and computing toasts keep the size and place they always had: the text's size plus
|
||||
/// 40 by 16, centred at the bottom of the free rect.
|
||||
#[test]
|
||||
fn the_shared_toast_draws_old_callers_exactly_as_before() {
|
||||
let ctx = crate::fonts::test_context();
|
||||
for width in [200.0_f32, 300.0, 500.0, 900.0] {
|
||||
let free = Rect::from_min_size(pos2(10.0, 20.0), vec2(width, 200.0));
|
||||
for (error, computing) in [(false, false), (true, false), (false, true)] {
|
||||
draw(&ctx, |ui| {
|
||||
let t = Tokens::get();
|
||||
let text = "A message that is long enough to wrap in the narrow cases, and not in the wide ones".to_string();
|
||||
let galley = ui.painter().layout(text.clone(), crate::fonts::medium(t.font_label), t.text, (free.width() - 40.0).min(440.0));
|
||||
let size = galley.size() + vec2(40.0, 16.0);
|
||||
let expected = Rect::from_center_size(pos2(free.center().x, free.bottom() - size.y * 0.5), size);
|
||||
let got = paint_toast(ui, free, text, error, computing);
|
||||
if size.x > free.width() || size.y > free.height() {
|
||||
assert!(got.is_none());
|
||||
} else {
|
||||
assert_eq!(got, Some(expected), "width {width} error {error} computing {computing}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The toast's buttons stay inside its card at narrow widths, and do nothing while an issue report
|
||||
/// holds CAD input.
|
||||
#[test]
|
||||
fn toast_buttons_wrap_inside_the_card_and_are_dead_behind_an_issue_report() {
|
||||
let labels = [
|
||||
("Calibrate…", ToastKind::Primary),
|
||||
("Swap to Hidraw (Direct Driver)", ToastKind::Secondary),
|
||||
("Close", ToastKind::Dismiss),
|
||||
("Never show again", ToastKind::Dismiss),
|
||||
];
|
||||
for width in [260.0_f32, 360.0, 600.0, 1000.0] {
|
||||
for blocked in [false, true] {
|
||||
crate::issue_report::set_cad_input_blocked_for_test(blocked);
|
||||
let ctx = crate::fonts::test_context();
|
||||
let free = Rect::from_min_size(pos2(0.0, 0.0), vec2(width, 300.0));
|
||||
let run = |events: Vec<egui::Event>| {
|
||||
let mut seen = None;
|
||||
let mut output = ctx.run_ui(egui::RawInput { events, screen_rect: Some(free), focused: true, ..Default::default() }, |ui| {
|
||||
seen = paint_toast_with(ui, free, "Your 3D mouse isn't calibrated.".into(), ToastLead::Dot(false), &labels);
|
||||
});
|
||||
output.textures_delta.clear();
|
||||
seen
|
||||
};
|
||||
run(vec![]);
|
||||
let (card, _) = run(vec![]).unwrap_or_else(|| panic!("width {width}: no card"));
|
||||
assert!(free.contains_rect(card), "width {width}: {card:?}");
|
||||
let mut hits = Vec::new();
|
||||
for step_x in 1..12 {
|
||||
for step_y in 1..16 {
|
||||
let at = pos2(card.left() + card.width() * step_x as f32 / 12.0, card.top() + card.height() * step_y as f32 / 16.0);
|
||||
run(vec![egui::Event::PointerMoved(at)]);
|
||||
run(vec![egui::Event::PointerButton {
|
||||
pos: at,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: true,
|
||||
modifiers: Default::default(),
|
||||
}]);
|
||||
let clicked = run(vec![egui::Event::PointerButton {
|
||||
pos: at,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: false,
|
||||
modifiers: Default::default(),
|
||||
}]);
|
||||
if let Some((_, Some(index))) = clicked {
|
||||
hits.push(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
crate::issue_report::set_cad_input_blocked_for_test(false);
|
||||
if blocked {
|
||||
assert!(hits.is_empty(), "width {width}: a button worked behind a report: {hits:?}");
|
||||
} else {
|
||||
hits.sort_unstable();
|
||||
hits.dedup();
|
||||
assert_eq!(hits, [0, 1, 2, 3], "width {width}: every button can be clicked inside the card");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The toast's buttons group by purpose and wrap as whole groups: the main action first, then the
|
||||
/// alternative, then the dismissals; no button stands alone on a row while its group-mate fits.
|
||||
#[test]
|
||||
fn toast_buttons_group_by_purpose_and_wrap_as_groups() {
|
||||
use ToastKind::{Dismiss, Primary, Secondary};
|
||||
let four = [(110.0, Primary), (220.0, Secondary), (60.0, Dismiss), (140.0, Dismiss)];
|
||||
let three = [(110.0, Primary), (60.0, Dismiss), (140.0, Dismiss)];
|
||||
for card in [260.0_f32, 360.0, 460.0, 560.0, 700.0, 1000.0] {
|
||||
let row_width = (card - 40.0).min(440.0);
|
||||
for buttons in [&four[..], &three[..]] {
|
||||
let spots = toast_action_spots(buttons, row_width, 8.0, 28.0);
|
||||
let widest = buttons.iter().map(|b| b.0).fold(0.0, f32::max);
|
||||
// Nothing leaves the row (a button wider than the row is cut to it by the painter), and none overlaps.
|
||||
for (i, spot) in spots.iter().enumerate() {
|
||||
assert!(spot.left() >= 0.0 && spot.right() <= row_width.max(widest) + 0.01, "card {card}: {spot:?}");
|
||||
for other in &spots[i + 1..] {
|
||||
assert!(!spot.intersects(*other), "card {card}: {spot:?} overlaps {other:?}");
|
||||
}
|
||||
}
|
||||
// The primary is first: top left, and no button is above it or left of it on its row.
|
||||
assert!(spots.iter().all(|s| s.top() > spots[0].top() || s.left() >= spots[0].left()), "card {card}: primary first");
|
||||
let mut rows: std::collections::BTreeMap<i32, usize> = Default::default();
|
||||
for spot in &spots {
|
||||
*rows.entry(spot.top() as i32).or_default() += 1;
|
||||
}
|
||||
// Whole groups fit the row: then no row has a button on its own, except the primary of the
|
||||
// three-button set (its group is only itself).
|
||||
let group_fits = if buttons.len() == 4 { 110.0 + 8.0 + 220.0 <= row_width } else { 140.0 + 8.0 + 60.0 <= row_width };
|
||||
if group_fits {
|
||||
let singles = rows.values().filter(|&&n| n == 1).count();
|
||||
assert!(singles <= usize::from(buttons.len() == 3), "card {card}, {} buttons: a button is left alone: {rows:?}", buttons.len());
|
||||
}
|
||||
if row_width >= 110.0 + 8.0 + 220.0 + 8.0 + 60.0 + 8.0 + 140.0 {
|
||||
assert_eq!(rows.len(), 1, "card {card}: one row when everything fits");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bottom_readouts_clear_floating_history_on_the_first_frame() {
|
||||
let mut h = Harness::new();
|
||||
|
||||
@@ -474,7 +474,7 @@
|
||||
| Move | 移動 | `ui.move` |
|
||||
| MOVE | 移動 | `ui.heading_move` |
|
||||
| Move 3D Point | 3D 点を移動 | `cmd.sketch_move3d` |
|
||||
| Move and rotate the view together; sketches use pan and zoom only. | 視点の移動と回転を同時に行います(スケッチ中はパンとズームのみ) | `ui.space_mouse_hint` |
|
||||
| Move and rotate the view together; sketches use pan and zoom only. | 視点の移動と回転を同時に行います(スケッチ中はパンとズームのみ) | `ui.3d_mouse_hint` |
|
||||
| Move Bodies to Component | ボディをコンポーネントに移動 | `cmd.component_move_bodies` |
|
||||
| Move Browser Items | ブラウザ項目を移動 | `cmd.browser_move` |
|
||||
| Move Dimension Text | 寸法テキストを移動 | `cmd.sketch_dimension_text` |
|
||||
@@ -490,7 +490,7 @@
|
||||
| Name | 名前 | `Name` |
|
||||
| Named Views | 名前付きビュー | `ui.named_views` |
|
||||
| Navigate View | ビューナビゲーション | `cmd.view_navigate` |
|
||||
| Navigate with SpaceNavigator / SpaceMouse | SpaceNavigator / SpaceMouseで視点を操作 | `ui.space_mouse_navigate` |
|
||||
| Navigate with the 3D mouse | 3Dマウスで視点を操作 | `ui.3d_mouse_navigate` |
|
||||
| Navigation | ナビゲーション | `ui.navigation` |
|
||||
| needs two joints | 2つのジョイントが必要 | `needs two joints` |
|
||||
| New Body | 新規ボディ | `ui.new_body` |
|
||||
|
||||
Reference in New Issue
Block a user