forked from 1b-agent/opencadstudio-upstream
Merge upstream/main into fix/704-active-intersection-snap
This commit is contained in:
commit
17b4eafa3b
71 files changed
+4887
-2862
No files matched your search
@@ -100,6 +100,7 @@ impl OpenCADStudio {
|
||||
self.ucs_icon_hover = false;
|
||||
self.tabs[i].pan_mode = false;
|
||||
self.tabs[i].orbit_mode = false;
|
||||
self.tabs[i].zoom_dynamic_mode = false;
|
||||
let _ = self.on_viewport_exit();
|
||||
}
|
||||
|
||||
@@ -1939,6 +1940,7 @@ impl OpenCADStudio {
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.zoom_to_window(p1.as_vec3(), p2.as_vec3());
|
||||
|
||||
+70
-13
@@ -805,40 +805,94 @@ impl OpenCADStudio {
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
}
|
||||
|
||||
"ZOOM EXTENTS ALL" | "ZOOM EXTENTS ALL VIEWPORTS" | "ZEA" => {
|
||||
"ZOOM EXTENTS ALL" | "ZOOM EXTENTS ALL VIEWPORTS" | "ZOOM EA" | "ZEA" => {
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.tabs[i].scene.fit_all_model_viewports();
|
||||
self.command_line.push_output(crate::t!("Zoom Extents — All Viewports").as_ref());
|
||||
}
|
||||
|
||||
"ZOOM EXTENTS" | "ZOOMEXTENTS" | "ZE" => {
|
||||
"ZOOM EXTENTS" | "ZOOM E" | "ZOOMEXTENTS" | "ZE" => {
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.tabs[i].scene.fit_all();
|
||||
self.command_line.push_output(crate::t!("Zoom Extents").as_ref());
|
||||
}
|
||||
|
||||
"ZOOM IN" | "ZI" => {
|
||||
"ZOOM IN" | "ZOOM I" | "ZI" => {
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.tabs[i].scene.zoom_camera(1.0 / 1.5);
|
||||
self.command_line.push_output(crate::t!("Zoom In").as_ref());
|
||||
}
|
||||
|
||||
"ZOOM OUT" | "ZO" => {
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.tabs[i].scene.zoom_camera(1.5);
|
||||
self.command_line.push_output(crate::t!("Zoom Out").as_ref());
|
||||
}
|
||||
|
||||
// ZOOM ALL — fit the configured drawing limits.
|
||||
"ZOOM ALL" | "ZOOM A" | "ZA" => {
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.tabs[i].scene.fit_all_with_limits();
|
||||
self.command_line.push_output(crate::t!("Zoom All").as_ref());
|
||||
}
|
||||
|
||||
"ZOOM PREVIOUS" | "ZOOM P" | "ZP" => {
|
||||
if self.tabs[i].scene.restore_previous_view() {
|
||||
self.command_line.push_output(crate::t!("Zoom Previous").as_ref());
|
||||
} else {
|
||||
self.command_line
|
||||
.push_error(crate::t!("ZOOM: no previous view.").as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
"ZOOM OBJECT" | "ZOOM O" | "ZOBJ" => {
|
||||
let handles: Vec<_> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.into_iter()
|
||||
.map(|(handle, _)| handle)
|
||||
.collect();
|
||||
if handles.is_empty() {
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
let command = SelectObjectsCommand::new("ZOOM OBJECT");
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
} else {
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
if self.tabs[i].scene.zoom_to_entities(&handles) {
|
||||
self.command_line.push_output(crate::t!("Zoom Object").as_ref());
|
||||
} else {
|
||||
self.command_line.push_error(
|
||||
crate::t!("ZOOM: selected objects have no visible bounds.").as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"ZOOM DYNAMIC" | "ZOOM D" | "ZD" => {
|
||||
self.tabs[i].zoom_dynamic_mode = true;
|
||||
self.clear_navigation_hover(i);
|
||||
self.command_line.push_output(
|
||||
crate::t!(
|
||||
"ZOOM Dynamic: drag horizontally to pan and vertically to zoom. Press Esc to exit."
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
// ZOOM SCALE — set zoom factor (e.g. "ZOOM SCALE 2" or "ZS 0.5")
|
||||
cmd if cmd.starts_with("ZOOM SCALE ") || cmd.starts_with("ZS ") => {
|
||||
cmd if cmd.starts_with("ZOOM SCALE ")
|
||||
|| cmd.starts_with("ZOOM S ")
|
||||
|| cmd.starts_with("ZS ") =>
|
||||
{
|
||||
let rest = cmd
|
||||
.split_once(' ')
|
||||
.and_then(|(_, r)| r.split_once(' ').map(|(_, v)| v).or(Some(r)))
|
||||
.strip_prefix("ZOOM SCALE ")
|
||||
.or_else(|| cmd.strip_prefix("ZOOM S "))
|
||||
.or_else(|| cmd.strip_prefix("ZS "))
|
||||
.unwrap_or("1");
|
||||
if let Ok(factor) = rest.trim().parse::<f32>() {
|
||||
if factor > 0.0 {
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.tabs[i].scene.zoom_camera(1.0 / factor);
|
||||
self.command_line
|
||||
.push_output(crate::tf!("Zoom Scale ×{factor:.3}").as_ref());
|
||||
@@ -866,15 +920,18 @@ impl OpenCADStudio {
|
||||
use crate::command::KeywordCommand;
|
||||
let c = KeywordCommand::new(
|
||||
"ZOOM",
|
||||
"ZOOM [Window / Extents / Extents All / All / In / Out / Scale]:",
|
||||
"ZOOM [Window / Extents / Previous / Object / All / Dynamic / Extents All / In / Out / Scale]:",
|
||||
vec![
|
||||
("Window", "WINDOW", None),
|
||||
("Extents", "EXTENTS", None),
|
||||
("Extents All", "EXTENTS ALL", None),
|
||||
("All", "ALL", None),
|
||||
("In", "IN", None),
|
||||
("Window", "W", None),
|
||||
("Extents", "E", None),
|
||||
("Previous", "P", None),
|
||||
("Object", "O", None),
|
||||
("All", "A", None),
|
||||
("Dynamic", "D", None),
|
||||
("Extents All", "EA", None),
|
||||
("In", "I", None),
|
||||
("Out", "OUT", None),
|
||||
("Scale", "SCALE", Some("ZOOM scale factor (e.g. 2 or 0.5):")),
|
||||
("Scale", "S", Some("ZOOM scale factor (e.g. 2 or 0.5):")),
|
||||
],
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
|
||||
@@ -224,6 +224,43 @@ impl OpenCADStudio {
|
||||
"SNAP" => {
|
||||
return Some(Task::done(Message::ToggleGridSnap));
|
||||
}
|
||||
// ISOPLANE — cycle the isometric drafting axis pair (F5).
|
||||
"ISOPLANE" => {
|
||||
return Some(Task::done(Message::CycleIsoPlane));
|
||||
}
|
||||
cmd if cmd.starts_with("ISOPLANE ") => {
|
||||
let plane = match cmd.trim_start_matches("ISOPLANE").trim() {
|
||||
"LEFT" | "L" => Some(crate::app::settings::IsoPlane::Left),
|
||||
"TOP" | "T" => Some(crate::app::settings::IsoPlane::Top),
|
||||
"RIGHT" | "R" => Some(crate::app::settings::IsoPlane::Right),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(plane) = plane {
|
||||
return Some(Task::done(Message::SetIsoPlane(plane)));
|
||||
}
|
||||
self.command_line
|
||||
.push_error(crate::t!("ISOPLANE: expected Left, Top, or Right.").as_ref());
|
||||
}
|
||||
// ISODRAFT — enable or disable isometric drafting.
|
||||
"ISODRAFT" => {
|
||||
return Some(Task::done(Message::ToggleIsometricDrafting));
|
||||
}
|
||||
cmd if cmd.starts_with("ISODRAFT ") => {
|
||||
let requested = match cmd.trim_start_matches("ISODRAFT").trim() {
|
||||
"1" | "ON" => Some(true),
|
||||
"0" | "OFF" => Some(false),
|
||||
_ => None,
|
||||
};
|
||||
match requested {
|
||||
Some(value) if value != self.isometric_drafting => {
|
||||
return Some(Task::done(Message::ToggleIsometricDrafting));
|
||||
}
|
||||
Some(_) => {}
|
||||
None => self
|
||||
.command_line
|
||||
.push_error(crate::t!("ISODRAFT: expected On or Off.").as_ref()),
|
||||
}
|
||||
}
|
||||
// POLAR — toggle polar tracking.
|
||||
"POLAR" => {
|
||||
return Some(Task::done(Message::TogglePolar));
|
||||
|
||||
@@ -98,6 +98,7 @@ impl OpenCADStudio {
|
||||
// command arms below re-enable the selected one).
|
||||
self.tabs[i].pan_mode = false;
|
||||
self.tabs[i].orbit_mode = false;
|
||||
self.tabs[i].zoom_dynamic_mode = false;
|
||||
// Reset the last committed point so the first click of the new command
|
||||
// is not constrained by ortho/polar relative to a previous command's endpoint.
|
||||
self.last_point = None;
|
||||
@@ -300,6 +301,8 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"CUI",
|
||||
"DSETTINGS",
|
||||
"GRID",
|
||||
"ISODRAFT",
|
||||
"ISOPLANE",
|
||||
"OSNAP",
|
||||
"POLAR",
|
||||
"QUICKPROPERTIES",
|
||||
@@ -445,6 +448,12 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"FITSPLINE",
|
||||
// System variables (typeable directly).
|
||||
"MIRRTEXT",
|
||||
"ZOOMWHEEL",
|
||||
"ZOOMFACTOR",
|
||||
"CURSORSIZE",
|
||||
"PICKBOX",
|
||||
"CURSORTYPE",
|
||||
"SNAPANG",
|
||||
"ATTREQ",
|
||||
"ATTDIA",
|
||||
"DIMASSOC",
|
||||
|
||||
@@ -866,6 +866,12 @@ impl OpenCADStudio {
|
||||
cmd if matches!(
|
||||
cmd.split_whitespace().next().unwrap_or(""),
|
||||
"MIRRTEXT"
|
||||
| "ZOOMWHEEL"
|
||||
| "ZOOMFACTOR"
|
||||
| "CURSORSIZE"
|
||||
| "PICKBOX"
|
||||
| "CURSORTYPE"
|
||||
| "SNAPANG"
|
||||
| "TEXTFILL"
|
||||
| "ATTREQ"
|
||||
| "ATTDIA"
|
||||
@@ -936,7 +942,7 @@ impl OpenCADStudio {
|
||||
let value = it.next().map(|s| s.trim().to_string());
|
||||
if name.is_empty() || name == "?" {
|
||||
self.command_line.push_info(
|
||||
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT ATTREQ ATTDIA DIMASSOC ANGBASE ANGDIR | CLAYER CELTYPE TEXTSTYLE (read-only)",
|
||||
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG ATTREQ ATTDIA DIMASSOC ANGBASE ANGDIR | CLAYER CELTYPE TEXTSTYLE (read-only)",
|
||||
);
|
||||
} else {
|
||||
// Parse a boolean given as 0/1 or ON/OFF.
|
||||
@@ -1028,6 +1034,87 @@ impl OpenCADStudio {
|
||||
.ok_or_else(|| "SETVAR: 0 or 1 required.".into()),
|
||||
None => Ok((format!("MIRRTEXT = {}", h.mirror_text as i32), false)),
|
||||
},
|
||||
"ZOOMWHEEL" => match &value {
|
||||
Some(v) => match parse_bool(v) {
|
||||
Some(reversed) => {
|
||||
self.zoom_wheel_reversed = reversed;
|
||||
Ok((
|
||||
format!("ZOOMWHEEL = {}", reversed as i32),
|
||||
true,
|
||||
))
|
||||
}
|
||||
None => Err("SETVAR: 0 or 1 required.".into()),
|
||||
},
|
||||
None => Ok((
|
||||
format!(
|
||||
"ZOOMWHEEL = {}",
|
||||
self.zoom_wheel_reversed as i32
|
||||
),
|
||||
false,
|
||||
)),
|
||||
},
|
||||
"ZOOMFACTOR" => match &value {
|
||||
Some(v) => match v.parse::<i32>() {
|
||||
Ok(factor) if (3..=100).contains(&factor) => {
|
||||
self.zoom_factor = factor;
|
||||
Ok((format!("ZOOMFACTOR = {factor}"), true))
|
||||
}
|
||||
_ => Err("SETVAR: integer from 3 to 100 required.".into()),
|
||||
},
|
||||
None => {
|
||||
Ok((format!("ZOOMFACTOR = {}", self.zoom_factor), false))
|
||||
}
|
||||
},
|
||||
"CURSORSIZE" => match &value {
|
||||
Some(v) => match v.parse::<i32>() {
|
||||
Ok(size) if (1..=100).contains(&size) => {
|
||||
self.cursor_size = size;
|
||||
Ok((format!("CURSORSIZE = {size}"), true))
|
||||
}
|
||||
_ => Err("SETVAR: integer from 1 to 100 required.".into()),
|
||||
},
|
||||
None => Ok((format!("CURSORSIZE = {}", self.cursor_size), false)),
|
||||
},
|
||||
"PICKBOX" => match &value {
|
||||
Some(v) => match v.parse::<i32>() {
|
||||
Ok(size) if (0..=50).contains(&size) => {
|
||||
self.pick_box = size;
|
||||
Ok((format!("PICKBOX = {size}"), true))
|
||||
}
|
||||
_ => Err("SETVAR: integer from 0 to 50 required.".into()),
|
||||
},
|
||||
None => Ok((format!("PICKBOX = {}", self.pick_box), false)),
|
||||
},
|
||||
"CURSORTYPE" => match &value {
|
||||
Some(v) => match v.as_str() {
|
||||
"0" => {
|
||||
self.cursor_type = crate::app::settings::CursorType::Crosshair;
|
||||
Ok(("CURSORTYPE = 0".to_string(), true))
|
||||
}
|
||||
"1" => {
|
||||
self.cursor_type = crate::app::settings::CursorType::Pointer;
|
||||
Ok(("CURSORTYPE = 1".to_string(), true))
|
||||
}
|
||||
_ => Err("SETVAR: 0 or 1 required.".into()),
|
||||
},
|
||||
None => Ok((
|
||||
format!(
|
||||
"CURSORTYPE = {}",
|
||||
i32::from(self.cursor_type == crate::app::settings::CursorType::Pointer)
|
||||
),
|
||||
false,
|
||||
)),
|
||||
},
|
||||
"SNAPANG" => match &value {
|
||||
Some(v) => match v.parse::<f32>() {
|
||||
Ok(angle) if angle.is_finite() => {
|
||||
self.snap_angle_deg = angle.rem_euclid(360.0);
|
||||
Ok((format!("SNAPANG = {}", self.snap_angle_deg), true))
|
||||
}
|
||||
_ => Err("SETVAR: finite numeric value required.".into()),
|
||||
},
|
||||
None => Ok((format!("SNAPANG = {}", self.snap_angle_deg), false)),
|
||||
},
|
||||
// Global (not stored in the drawing): fill vs. hollow
|
||||
// TrueType text. The active tab re-tessellates below.
|
||||
"TEXTFILL" => match &value {
|
||||
@@ -1579,7 +1666,19 @@ impl OpenCADStudio {
|
||||
match outcome {
|
||||
Ok((msg, changed)) => {
|
||||
if changed {
|
||||
self.tabs[i].dirty = true;
|
||||
if matches!(
|
||||
name.as_str(),
|
||||
"ZOOMWHEEL"
|
||||
| "ZOOMFACTOR"
|
||||
| "CURSORSIZE"
|
||||
| "PICKBOX"
|
||||
| "CURSORTYPE"
|
||||
| "SNAPANG"
|
||||
) {
|
||||
self.persist_settings_if_changed();
|
||||
} else {
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
self.command_line.push_output(&msg);
|
||||
} else {
|
||||
// Queried with no value (`changed == false`):
|
||||
|
||||
+2
-2
@@ -209,11 +209,11 @@ fn rgb_to_color(rgb: [u8; 3]) -> iced::Color {
|
||||
iced::Color::from_rgb8(rgb[0], rgb[1], rgb[2])
|
||||
}
|
||||
|
||||
fn rgb_to_hex(rgb: [u8; 3]) -> String {
|
||||
pub(crate) fn rgb_to_hex(rgb: [u8; 3]) -> String {
|
||||
format!("#{:02X}{:02X}{:02X}", rgb[0], rgb[1], rgb[2])
|
||||
}
|
||||
|
||||
fn parse_hex(value: &str) -> Option<[u8; 3]> {
|
||||
pub(crate) fn parse_hex(value: &str) -> Option<[u8; 3]> {
|
||||
let value = value.trim().strip_prefix('#').unwrap_or(value.trim());
|
||||
if value.len() != 6 {
|
||||
return None;
|
||||
|
||||
@@ -212,6 +212,9 @@ pub(super) struct DocumentTab {
|
||||
/// Interactive 3-D orbit mode. While active, a left-button drag follows
|
||||
/// the same camera-orbit path as Shift + middle-button drag.
|
||||
pub(super) orbit_mode: bool,
|
||||
/// Interactive ZOOM Dynamic mode. A left drag pans horizontally and zooms
|
||||
/// vertically until Escape or another command ends the mode.
|
||||
pub(super) zoom_dynamic_mode: bool,
|
||||
/// Per-plugin document state (`plugin::BuiltinPlugin` manifest id → state).
|
||||
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||
pub(super) plugin_state: HashMap<&'static str, Box<dyn Any + Send + Sync>>,
|
||||
@@ -476,6 +479,7 @@ impl DocumentTab {
|
||||
is_start: false,
|
||||
pan_mode: false,
|
||||
orbit_mode: false,
|
||||
zoom_dynamic_mode: false,
|
||||
plugin_state: HashMap::new(),
|
||||
suspended_cmd: None,
|
||||
}
|
||||
|
||||
+65
-13
@@ -260,19 +260,56 @@ pub(super) fn ucs_rotated_z(origin: glam::DVec3, angle_z: f32) -> Ucs {
|
||||
|
||||
// ── Drawing constraint helpers ─────────────────────────────────────────────
|
||||
|
||||
/// Constrain `pt` to the nearest 90° direction from `base`, in the active UCS
|
||||
/// plane — ortho follows the user's coordinate system, not world axes. `xf` is
|
||||
/// identity for plain WCS, so the world-XY behaviour is unchanged there.
|
||||
pub(super) fn ortho_constrain(pt: glam::DVec3, base: glam::DVec3, xf: &UcsXform) -> glam::DVec3 {
|
||||
/// The two live drafting directions in degrees inside the active UCS plane.
|
||||
pub(super) fn drafting_angles(
|
||||
isometric: bool,
|
||||
iso_plane: super::settings::IsoPlane,
|
||||
snap_angle_deg: f32,
|
||||
) -> [f64; 2] {
|
||||
let base = if isometric {
|
||||
iso_plane.angles()
|
||||
} else {
|
||||
[0.0, 90.0]
|
||||
};
|
||||
base.map(|angle| angle + snap_angle_deg as f64)
|
||||
}
|
||||
|
||||
/// Convert the live drafting directions into world-space axes.
|
||||
pub(super) fn drafting_axes(
|
||||
x: glam::DVec3,
|
||||
y: glam::DVec3,
|
||||
z: glam::DVec3,
|
||||
isometric: bool,
|
||||
iso_plane: super::settings::IsoPlane,
|
||||
snap_angle_deg: f32,
|
||||
) -> (glam::DVec3, glam::DVec3, glam::DVec3) {
|
||||
let [a, b] = drafting_angles(isometric, iso_plane, snap_angle_deg);
|
||||
let direction = |degrees: f64| {
|
||||
let radians = degrees.to_radians();
|
||||
(x * radians.cos() + y * radians.sin()).normalize_or(x)
|
||||
};
|
||||
(direction(a), direction(b), z.normalize_or(x.cross(y)))
|
||||
}
|
||||
|
||||
/// Constrain `pt` to the nearest live drafting direction from `base`.
|
||||
pub(super) fn drafting_constrain(
|
||||
pt: glam::DVec3,
|
||||
base: glam::DVec3,
|
||||
xf: &UcsXform,
|
||||
isometric: bool,
|
||||
iso_plane: super::settings::IsoPlane,
|
||||
snap_angle_deg: f32,
|
||||
) -> glam::DVec3 {
|
||||
let p = xf.to_ucs(pt);
|
||||
let b = xf.to_ucs(base);
|
||||
let dx = (p.x - b.x).abs();
|
||||
let dy = (p.y - b.y).abs();
|
||||
let c = if dx >= dy {
|
||||
glam::DVec3::new(p.x, b.y, p.z)
|
||||
} else {
|
||||
glam::DVec3::new(b.x, p.y, p.z)
|
||||
};
|
||||
let delta = glam::DVec2::new(p.x - b.x, p.y - b.y);
|
||||
let [a, c] = drafting_angles(isometric, iso_plane, snap_angle_deg).map(|degrees| {
|
||||
let radians = degrees.to_radians();
|
||||
glam::DVec2::new(radians.cos(), radians.sin())
|
||||
});
|
||||
let direction = if delta.dot(a).abs() >= delta.dot(c).abs() { a } else { c };
|
||||
let projected = direction * delta.dot(direction);
|
||||
let c = glam::DVec3::new(b.x + projected.x, b.y + projected.y, p.z);
|
||||
xf.to_wcs(c)
|
||||
}
|
||||
|
||||
@@ -341,6 +378,9 @@ pub(super) fn axis_lock_capture(
|
||||
polar: bool,
|
||||
step_deg: f32,
|
||||
xf: &UcsXform,
|
||||
isometric: bool,
|
||||
iso_plane: super::settings::IsoPlane,
|
||||
snap_angle_deg: f32,
|
||||
) -> Option<glam::DVec3> {
|
||||
let p = xf.to_ucs(cursor);
|
||||
let b = xf.to_ucs(base);
|
||||
@@ -349,8 +389,20 @@ pub(super) fn axis_lock_capture(
|
||||
if dx.hypot(dy) < 1e-9 {
|
||||
return None;
|
||||
}
|
||||
let step = (if polar { step_deg as f64 } else { 90.0 }).to_radians();
|
||||
let ang = (dy.atan2(dx) / step).round() * step;
|
||||
let ang = if polar {
|
||||
let base = (snap_angle_deg as f64).to_radians();
|
||||
let step = (step_deg as f64).to_radians();
|
||||
((dy.atan2(dx) - base) / step).round() * step + base
|
||||
} else {
|
||||
let delta = glam::DVec2::new(dx, dy);
|
||||
let [a, b] = drafting_angles(isometric, iso_plane, snap_angle_deg).map(|degrees| {
|
||||
let radians = degrees.to_radians();
|
||||
glam::DVec2::new(radians.cos(), radians.sin())
|
||||
});
|
||||
let direction = if delta.dot(a).abs() >= delta.dot(b).abs() { a } else { b };
|
||||
let direction = if delta.dot(direction) < 0.0 { -direction } else { direction };
|
||||
direction.y.atan2(direction.x)
|
||||
};
|
||||
let dir_ucs = glam::DVec3::new(ang.cos(), ang.sin(), 0.0);
|
||||
let dir = xf.to_wcs(b + dir_ucs) - xf.to_wcs(b);
|
||||
(dir.length_squared() > 1e-12).then(|| dir.normalize())
|
||||
|
||||
+52
-1
@@ -18,7 +18,7 @@ mod mtext_editor;
|
||||
pub mod plugin_host;
|
||||
mod properties;
|
||||
mod recent;
|
||||
mod settings;
|
||||
pub(crate) mod settings;
|
||||
mod shortcuts;
|
||||
mod style_ops;
|
||||
mod text_inline;
|
||||
@@ -444,10 +444,31 @@ pub(super) struct OpenCADStudio {
|
||||
polar_mode: bool,
|
||||
/// Polar tracking angle increment in degrees (15 / 30 / 45 / 90).
|
||||
polar_increment_deg: f32,
|
||||
/// Reverse the mouse-wheel zoom direction when true (ZOOMWHEEL = 1).
|
||||
zoom_wheel_reversed: bool,
|
||||
/// Mouse-wheel zoom sensitivity, clamped to 3..=100 (ZOOMFACTOR).
|
||||
zoom_factor: i32,
|
||||
/// Crosshair size setting (CURSORSIZE, 1..=100).
|
||||
cursor_size: i32,
|
||||
/// Selection-box size setting (PICKBOX, 0..=50).
|
||||
pick_box: i32,
|
||||
/// Drawing viewport cursor style (CURSORTYPE).
|
||||
cursor_type: settings::CursorType,
|
||||
/// Explicit crosshair colour; `None` retains automatic contrast.
|
||||
crosshair_color: Option<[u8; 3]>,
|
||||
/// Editable Options buffer for the crosshair colour.
|
||||
crosshair_color_input: String,
|
||||
/// Isometric drafting state and active axis pair.
|
||||
isometric_drafting: bool,
|
||||
iso_plane: settings::IsoPlane,
|
||||
/// Drafting-grid/crosshair rotation in degrees (SNAPANG).
|
||||
snap_angle_deg: f32,
|
||||
/// Show grid lines in the viewport (F7).
|
||||
show_grid: bool,
|
||||
/// Dynamic input overlay (F12): show coordinate tooltip near cursor.
|
||||
dyn_input: bool,
|
||||
/// Currently visible page in the application Options dialog.
|
||||
options_tab: crate::ui::window::options::OptionsTab,
|
||||
/// Controls whether the TEXTEDIT command repeats automatically (0 = Multiple, 1 = Single).
|
||||
pub texteditmode: bool,
|
||||
/// When true (default), saving over an existing file first writes a `.bak`
|
||||
@@ -1498,6 +1519,7 @@ pub enum ModalKind {
|
||||
LayerStateManager,
|
||||
LayerTranslator,
|
||||
DrawingUnits,
|
||||
DraftingSettings,
|
||||
LayerStateEditor,
|
||||
Plot,
|
||||
PrintAll,
|
||||
@@ -1764,6 +1786,16 @@ pub enum Message {
|
||||
SaveDialogPathPicked(Option<std::path::PathBuf>),
|
||||
/// Open the application-wide Options dialog.
|
||||
OptionsOpen,
|
||||
/// Switch the visible page in Options.
|
||||
OptionsTabChanged(crate::ui::window::options::OptionsTab),
|
||||
/// Set CURSORSIZE from the Display-page slider.
|
||||
CursorSizeChanged(i32),
|
||||
/// Set PICKBOX from the Selection-page slider.
|
||||
PickBoxChanged(i32),
|
||||
/// Set CURSORTYPE from Options.
|
||||
CursorTypeChanged(settings::CursorType),
|
||||
/// Edit the optional crosshair RGB value; blank restores automatic contrast.
|
||||
CrosshairColorChanged(String),
|
||||
/// Set the default type/version used when first saving a new drawing.
|
||||
DefaultSaveFormatChanged(String),
|
||||
/// Select one of Iced's built-in themes or the editable Custom theme.
|
||||
@@ -2090,6 +2122,14 @@ pub enum Message {
|
||||
ToggleSnapEnabled,
|
||||
/// Toggle grid-snap on/off — F9 / SNAP status-bar button.
|
||||
ToggleGridSnap,
|
||||
/// Enable or disable isometric drafting.
|
||||
ToggleIsometricDrafting,
|
||||
/// Select one isometric drafting axis pair.
|
||||
SetIsoPlane(settings::IsoPlane),
|
||||
/// Advance Left → Top → Right, enabling isometric drafting if necessary.
|
||||
CycleIsoPlane,
|
||||
/// Reset SNAPANG and return the active drafting coordinate system to World.
|
||||
ResetDraftingRotation,
|
||||
/// Toggle the ViewCube 3D gizmo visibility (NAVVCUBE).
|
||||
ToggleViewCube,
|
||||
/// Toggle the Properties panel visibility (PROPERTIES).
|
||||
@@ -3030,8 +3070,19 @@ impl OpenCADStudio {
|
||||
ortho_mode: false,
|
||||
polar_mode: false,
|
||||
polar_increment_deg: 45.0,
|
||||
zoom_wheel_reversed: false,
|
||||
zoom_factor: 60,
|
||||
cursor_size: 5,
|
||||
pick_box: 3,
|
||||
cursor_type: settings::CursorType::Crosshair,
|
||||
crosshair_color: None,
|
||||
crosshair_color_input: String::new(),
|
||||
isometric_drafting: false,
|
||||
iso_plane: settings::IsoPlane::Left,
|
||||
snap_angle_deg: 0.0,
|
||||
show_grid: false,
|
||||
dyn_input: true,
|
||||
options_tab: crate::ui::window::options::OptionsTab::General,
|
||||
texteditmode: false,
|
||||
backup_on_save: true,
|
||||
file_assoc_enabled: true,
|
||||
|
||||
@@ -11,6 +11,63 @@
|
||||
use crate::snap::SnapType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Cursor shown over the drawing viewport.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CursorType {
|
||||
#[default]
|
||||
Crosshair,
|
||||
Pointer,
|
||||
}
|
||||
|
||||
impl CursorType {
|
||||
pub const ALL: [Self; 2] = [Self::Crosshair, Self::Pointer];
|
||||
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Crosshair => "Crosshair",
|
||||
Self::Pointer => "Desktop pointer",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Active pair of axes while isometric drafting is enabled.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum IsoPlane {
|
||||
#[default]
|
||||
Left,
|
||||
Top,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl IsoPlane {
|
||||
pub const ALL: [Self; 3] = [Self::Left, Self::Top, Self::Right];
|
||||
|
||||
pub fn next(self) -> Self {
|
||||
match self {
|
||||
Self::Left => Self::Top,
|
||||
Self::Top => Self::Right,
|
||||
Self::Right => Self::Left,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Left => "Left",
|
||||
Self::Top => "Top",
|
||||
Self::Right => "Right",
|
||||
}
|
||||
}
|
||||
|
||||
/// The two drafting directions, in degrees in the active UCS plane.
|
||||
pub fn angles(self) -> [f64; 2] {
|
||||
match self {
|
||||
Self::Left => [90.0, 150.0],
|
||||
Self::Top => [30.0, 150.0],
|
||||
Self::Right => [30.0, 90.0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Canonical order of the user-toggleable object-snap modes. Drives the
|
||||
/// deterministic order when decoding the `$OSMODE` bitmask (see
|
||||
/// [`snaps_from_osmode`]).
|
||||
@@ -97,6 +154,21 @@ pub struct UserSettings {
|
||||
pub dyn_input: bool,
|
||||
pub polar: bool,
|
||||
pub polar_increment_deg: f32,
|
||||
pub zoom_wheel_reversed: bool,
|
||||
pub zoom_factor: i32,
|
||||
/// CURSORSIZE: normalized crosshair reach; 5 retains the original 60 px arms.
|
||||
pub cursor_size: i32,
|
||||
/// PICKBOX: normalized visible-box and click-aperture size.
|
||||
pub pick_box: i32,
|
||||
/// CURSORTYPE: crosshair or the platform pointer over the drawing.
|
||||
pub cursor_type: CursorType,
|
||||
/// Explicit crosshair RGB. `None` keeps automatic background contrast.
|
||||
pub crosshair_color: Option<[u8; 3]>,
|
||||
/// Isometric drafting changes the grid and crosshair to the active axis pair.
|
||||
pub isometric_drafting: bool,
|
||||
pub iso_plane: IsoPlane,
|
||||
/// SNAPANG in degrees, applied in the active UCS plane.
|
||||
pub snap_angle_deg: f32,
|
||||
pub otrack: bool,
|
||||
// Ortho ($ORTHOMODE) and the running OSNAP set ($OSMODE) are per-drawing —
|
||||
// stored in the document header, not here (they used to be persisted app-
|
||||
@@ -166,6 +238,15 @@ impl Default for UserSettings {
|
||||
dyn_input: true,
|
||||
polar: false,
|
||||
polar_increment_deg: 45.0,
|
||||
zoom_wheel_reversed: false,
|
||||
zoom_factor: 60,
|
||||
cursor_size: 5,
|
||||
pick_box: 3,
|
||||
cursor_type: CursorType::Crosshair,
|
||||
crosshair_color: None,
|
||||
isometric_drafting: false,
|
||||
iso_plane: IsoPlane::Left,
|
||||
snap_angle_deg: 0.0,
|
||||
otrack: false,
|
||||
default_assoc_prompted: false,
|
||||
disabled_plugins: Vec::new(),
|
||||
|
||||
@@ -17,6 +17,7 @@ pub(super) fn default_bindings() -> BTreeMap<String, String> {
|
||||
("F1".to_string(), "HELP"),
|
||||
("F2".to_string(), "COMMANDHISTORY"),
|
||||
("F3".to_string(), "TOGGLEOSNAP"),
|
||||
("F5".to_string(), "ISOPLANE"),
|
||||
("F7".to_string(), "GRID"),
|
||||
("F8".to_string(), "ORTHO"),
|
||||
("F9".to_string(), "SNAP"),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use super::util::*;
|
||||
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
||||
use crate::app::helpers::{
|
||||
ortho_constrain, parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
CoordKind,
|
||||
};
|
||||
use crate::app::{Message, OpenCADStudio, POLY_START_DELAY_MS};
|
||||
@@ -660,16 +660,22 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
// drag. Orbit exits silently; PAN keeps its existing message.
|
||||
if self.tabs[self.active_tab].pan_mode
|
||||
|| self.tabs[self.active_tab].orbit_mode
|
||||
|| self.tabs[self.active_tab].zoom_dynamic_mode
|
||||
{
|
||||
let i = self.active_tab;
|
||||
let was_pan = self.tabs[i].pan_mode;
|
||||
self.tabs[i].pan_mode = false;
|
||||
self.tabs[i].orbit_mode = false;
|
||||
self.tabs[i].zoom_dynamic_mode = false;
|
||||
{
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.middle_down = false;
|
||||
sel.middle_last_pos = None;
|
||||
sel.orbit_pivot = None;
|
||||
sel.box_anchor = None;
|
||||
sel.box_anchor_world = None;
|
||||
sel.box_current = None;
|
||||
sel.box_crossing_locked = false;
|
||||
}
|
||||
if was_pan {
|
||||
self.command_line.push_output(crate::t!("PAN ended.").as_ref());
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::util::*;
|
||||
use crate::ui::window::block_palette::BlockPaletteMsg;
|
||||
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
||||
use crate::app::helpers::{
|
||||
ortho_constrain, parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
CoordKind,
|
||||
};
|
||||
use crate::app::{Message, OpenCADStudio, POLY_START_DELAY_MS};
|
||||
@@ -143,6 +143,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
&& self.active_modal.is_none()
|
||||
&& !self.tabs[i].pan_mode
|
||||
&& !self.tabs[i].orbit_mode
|
||||
&& !self.tabs[i].zoom_dynamic_mode
|
||||
{
|
||||
self.ribbon.deactivate_tool();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use super::util::*;
|
||||
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
||||
use crate::app::helpers::{
|
||||
ortho_constrain, parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
CoordKind,
|
||||
};
|
||||
use crate::app::{Message, OpenCADStudio, POLY_START_DELAY_MS};
|
||||
|
||||
+30
-1
@@ -4,7 +4,7 @@
|
||||
use super::util::*;
|
||||
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
||||
use crate::app::helpers::{
|
||||
ortho_constrain, parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
CoordKind,
|
||||
};
|
||||
use crate::app::{Message, OpenCADStudio, POLY_START_DELAY_MS};
|
||||
@@ -265,6 +265,15 @@ impl OpenCADStudio {
|
||||
dyn_input: self.dyn_input,
|
||||
polar: self.polar_mode,
|
||||
polar_increment_deg: self.polar_increment_deg,
|
||||
zoom_wheel_reversed: self.zoom_wheel_reversed,
|
||||
zoom_factor: self.zoom_factor,
|
||||
cursor_size: self.cursor_size,
|
||||
pick_box: self.pick_box,
|
||||
cursor_type: self.cursor_type,
|
||||
crosshair_color: self.crosshair_color,
|
||||
isometric_drafting: self.isometric_drafting,
|
||||
iso_plane: self.iso_plane,
|
||||
snap_angle_deg: self.snap_angle_deg,
|
||||
otrack: self.snapper.otrack_enabled,
|
||||
default_assoc_prompted: self.default_assoc_prompted,
|
||||
disabled_plugins: {
|
||||
@@ -299,6 +308,23 @@ impl OpenCADStudio {
|
||||
self.dyn_input = s.dyn_input;
|
||||
self.polar_mode = s.polar;
|
||||
self.polar_increment_deg = s.polar_increment_deg;
|
||||
self.zoom_wheel_reversed = s.zoom_wheel_reversed;
|
||||
self.zoom_factor = s.zoom_factor.clamp(3, 100);
|
||||
self.cursor_size = s.cursor_size.clamp(1, 100);
|
||||
self.pick_box = s.pick_box.clamp(0, 50);
|
||||
self.cursor_type = s.cursor_type;
|
||||
self.crosshair_color = s.crosshair_color;
|
||||
self.crosshair_color_input = s
|
||||
.crosshair_color
|
||||
.map(crate::app::config::rgb_to_hex)
|
||||
.unwrap_or_default();
|
||||
self.isometric_drafting = s.isometric_drafting;
|
||||
self.iso_plane = s.iso_plane;
|
||||
self.snap_angle_deg = if s.snap_angle_deg.is_finite() {
|
||||
s.snap_angle_deg.rem_euclid(360.0)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
// Ortho + running OSNAP are per-drawing (adopted from the header on
|
||||
// open / tab switch), not app-global, so they are not applied here.
|
||||
self.snapper.otrack_enabled = s.otrack;
|
||||
@@ -584,6 +610,9 @@ impl OpenCADStudio {
|
||||
self.ribbon.set_collapse_mode(cfg.ribbon.collapse);
|
||||
self.plot_dialog = cfg.plot;
|
||||
self.shortcut_bindings = cfg.shortcuts.bindings.into_iter().collect();
|
||||
self.shortcut_bindings
|
||||
.entry("F5".to_string())
|
||||
.or_insert_with(|| "ISOPLANE".to_string());
|
||||
}
|
||||
|
||||
/// Write the config only when it changed since the last write, so a toggle
|
||||
|
||||
+88
-1
@@ -149,6 +149,9 @@ impl OpenCADStudio {
|
||||
if self.active_modal == Some(ScaleManager) {
|
||||
self.scale_stage_discard();
|
||||
}
|
||||
if self.active_modal == Some(DraftingSettings) {
|
||||
self.snap_popup_open = false;
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if self.active_modal == Some(FileInUse) {
|
||||
self.pending_save_failure = None;
|
||||
@@ -2831,6 +2834,7 @@ impl OpenCADStudio {
|
||||
Message::ViewCubeHome => {
|
||||
let i = self.active_tab;
|
||||
self.clear_navigation_hover(i);
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
let r_ucs = self.tabs[i].scene.viewcube_ucs_mat();
|
||||
if self.tabs[i].scene.active_viewport.is_some() {
|
||||
self.tabs[i]
|
||||
@@ -2847,6 +2851,7 @@ impl OpenCADStudio {
|
||||
Message::ViewCubeRoll(cw) => {
|
||||
let i = self.active_tab;
|
||||
self.clear_navigation_hover(i);
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
let ang = if cw {
|
||||
std::f32::consts::FRAC_PI_2
|
||||
} else {
|
||||
@@ -2873,6 +2878,7 @@ impl OpenCADStudio {
|
||||
};
|
||||
let i = self.active_tab;
|
||||
self.clear_navigation_hover(i);
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
if self.tabs[i].scene.active_viewport.is_some() {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
@@ -3011,6 +3017,43 @@ impl OpenCADStudio {
|
||||
self.sync_vport_display(self.active_tab);
|
||||
Task::none()
|
||||
}
|
||||
Message::ToggleIsometricDrafting => {
|
||||
self.isometric_drafting = !self.isometric_drafting;
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
Message::SetIsoPlane(plane) => {
|
||||
self.isometric_drafting = true;
|
||||
self.iso_plane = plane;
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
Message::CycleIsoPlane => {
|
||||
if self.isometric_drafting {
|
||||
self.iso_plane = self.iso_plane.next();
|
||||
} else {
|
||||
self.isometric_drafting = true;
|
||||
}
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"Isometric plane: {}.",
|
||||
self.iso_plane.label()
|
||||
).as_ref());
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
Message::ResetDraftingRotation => {
|
||||
self.snap_angle_deg = 0.0;
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].active_ucs.is_some() {
|
||||
self.tabs[i].active_ucs = None;
|
||||
self.commit_active_ucs_change(i, "UCS");
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
}
|
||||
self.command_line
|
||||
.push_output(crate::t!("Drafting rotation reset to World at 0°.").as_ref());
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
Message::ToggleGrid => {
|
||||
self.show_grid ^= true;
|
||||
self.sync_vport_display(self.active_tab);
|
||||
@@ -3651,11 +3694,20 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
Message::ToggleSnapPopup => {
|
||||
self.snap_popup_open ^= true;
|
||||
if self.active_modal == Some(super::ModalKind::DraftingSettings) {
|
||||
self.close_active_modal();
|
||||
self.snap_popup_open = false;
|
||||
} else {
|
||||
self.active_modal = Some(super::ModalKind::DraftingSettings);
|
||||
self.snap_popup_open = true;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::CloseSnapPopup => {
|
||||
self.snap_popup_open = false;
|
||||
if self.active_modal == Some(super::ModalKind::DraftingSettings) {
|
||||
self.close_active_modal();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::SnapSelectAll => {
|
||||
@@ -5146,6 +5198,41 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::OptionsTabChanged(tab) => {
|
||||
self.options_tab = tab;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::CursorSizeChanged(value) => {
|
||||
self.cursor_size = value.clamp(1, 100);
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PickBoxChanged(value) => {
|
||||
self.pick_box = value.clamp(0, 50);
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::CursorTypeChanged(value) => {
|
||||
self.cursor_type = value;
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::CrosshairColorChanged(value) => {
|
||||
self.crosshair_color_input = value.clone();
|
||||
if value.trim().is_empty() {
|
||||
self.crosshair_color = None;
|
||||
self.persist_settings_if_changed();
|
||||
} else if let Some(rgb) = crate::app::config::parse_hex(&value) {
|
||||
self.crosshair_color = Some(rgb);
|
||||
self.persist_settings_if_changed();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::DefaultSaveFormatChanged(format) => {
|
||||
self.default_save_format =
|
||||
crate::io::canonical_save_format(&format).to_string();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use super::util::*;
|
||||
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
||||
use crate::app::helpers::{
|
||||
ortho_constrain, parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
CoordKind,
|
||||
};
|
||||
use crate::app::{Message, OpenCADStudio, POLY_START_DELAY_MS};
|
||||
|
||||
+169
-32
@@ -4,7 +4,7 @@
|
||||
use super::util::*;
|
||||
use super::{format_size, VIEWCUBE_HIT_SIZE};
|
||||
use crate::app::helpers::{
|
||||
axis_lock_apply, axis_lock_capture, ortho_constrain, parse_coord, polar_constrain_near,
|
||||
axis_lock_apply, axis_lock_capture, drafting_axes, drafting_constrain, parse_coord, polar_constrain_near,
|
||||
ucs_rotate_vec, ucs_to_wcs, ucs_z_axis, CoordKind,
|
||||
};
|
||||
use crate::app::{Message, OpenCADStudio, POLY_START_DELAY_MS};
|
||||
@@ -171,6 +171,26 @@ impl OpenCADStudio {
|
||||
.set_active_tile_grid_snap(grid_on, snap_on);
|
||||
}
|
||||
|
||||
/// Grid origin plus the rotated/isometric axes used by both drawing and snap.
|
||||
pub(in crate::app) fn drafting_grid_basis(
|
||||
&self,
|
||||
i: usize,
|
||||
) -> (glam::Vec3, (glam::Vec3, glam::Vec3, glam::Vec3)) {
|
||||
let (origin, rotation) = self.tabs[i].ucs_grid_basis();
|
||||
let x = rotation.transform_vector3(glam::Vec3::X).as_dvec3();
|
||||
let y = rotation.transform_vector3(glam::Vec3::Y).as_dvec3();
|
||||
let z = rotation.transform_vector3(glam::Vec3::Z).as_dvec3();
|
||||
let (x, y, z) = drafting_axes(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
);
|
||||
(origin, (x.as_vec3(), y.as_vec3(), z.as_vec3()))
|
||||
}
|
||||
|
||||
/// Adopt the active viewport's grid *display* into the live toggle. Called
|
||||
/// on load and whenever the active tab or viewport changes, so the grid
|
||||
/// drawing follows the active viewport.
|
||||
@@ -739,6 +759,39 @@ impl OpenCADStudio {
|
||||
if mid_down {
|
||||
if let Some(last) = mid_last {
|
||||
let (dx, dy) = (p.x - last.x, p.y - last.y);
|
||||
if self.tabs[i].zoom_dynamic_mode {
|
||||
let bounds = self.tabs[i]
|
||||
.scene
|
||||
.active_model_tile_bounds(vp_size.0, vp_size.1);
|
||||
drop(sel);
|
||||
let zoom_delta = -dy * 0.03;
|
||||
if self.tabs[i].scene.active_viewport.is_some() {
|
||||
self.tabs[i].scene.pan_active_viewport(dx, 0.0, bounds);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.zoom_active_viewport(zoom_delta, None);
|
||||
} else {
|
||||
let local = iced::Point {
|
||||
x: p.x - bounds.x,
|
||||
y: p.y - bounds.y,
|
||||
};
|
||||
let local_bounds = iced::Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
};
|
||||
let mut camera = self.tabs[i].scene.camera.borrow_mut();
|
||||
camera.pan_screen(dx, 0.0, bounds.height);
|
||||
camera.zoom_about_point(local, local_bounds, zoom_delta);
|
||||
}
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.record_nav_perf(crate::scene::NavPerfOp::Zoom, move_started);
|
||||
self.tabs[i].scene.selection.borrow_mut().middle_last_pos = Some(p);
|
||||
return Task::none();
|
||||
}
|
||||
// Shift+MMB drag orbits the model view instead of panning
|
||||
// — the requested Zoom=wheel / Pan=MMB / Rotate=Shift+MMB
|
||||
// scheme (#229). Floating viewports and paper keep the
|
||||
@@ -981,13 +1034,24 @@ impl OpenCADStudio {
|
||||
// snapping must drop its foot from this point, including when a
|
||||
// hot-grip set is moved by the same drag vector.
|
||||
self.snapper.from_point = Some(grip.origin_world.as_vec3());
|
||||
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
||||
self.snapper.from_point = Some(grip.origin_world.as_vec3());
|
||||
|
||||
// Keep the updated drafting basis from main so grid/snap follows the
|
||||
// current drafting axes, including isometric drafting and SNAPANG.
|
||||
let (go, gr) = self.drafting_grid_basis(i);
|
||||
|
||||
let base = grip.origin_world;
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
|
||||
let mut construction_cursor = if self.ortho_mode {
|
||||
Some(ortho_constrain(raw, base, &ucs_xf))
|
||||
Some(drafting_constrain(
|
||||
raw,
|
||||
base,
|
||||
&ucs_xf,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
))
|
||||
} else if self.polar_mode {
|
||||
let pt = polar_constrain_near(
|
||||
raw,
|
||||
@@ -1000,7 +1064,8 @@ impl OpenCADStudio {
|
||||
&ucs_xf,
|
||||
);
|
||||
|
||||
// POLAR must actually be engaged on one of its rays.
|
||||
// POLAR being enabled is not enough: the cursor must actually
|
||||
// be engaged on one of the polar tracking rays.
|
||||
let delta = pt - base;
|
||||
let step = (self.polar_increment_deg as f64).to_radians();
|
||||
let angle = delta.y.atan2(delta.x);
|
||||
@@ -1023,7 +1088,6 @@ impl OpenCADStudio {
|
||||
pt.z = base.z;
|
||||
}
|
||||
}
|
||||
|
||||
// `raw` is already model space (viewport camera or paper→model),
|
||||
// and the wires are model space, so the snap result is model.
|
||||
let snap_hit = self.snapper.snap(
|
||||
@@ -1061,7 +1125,7 @@ impl OpenCADStudio {
|
||||
None
|
||||
};
|
||||
|
||||
let (_, ucs_x, ucs_y, _) = self.tabs[i].ucs_xform().axes();
|
||||
let (_, (ucs_x, ucs_y, _)) = self.drafting_grid_basis(i);
|
||||
|
||||
self.snapper.otrack_snap(
|
||||
raw,
|
||||
@@ -1071,8 +1135,8 @@ impl OpenCADStudio {
|
||||
polar_step,
|
||||
Some(grip.origin_world),
|
||||
self.ortho_mode,
|
||||
ucs_x,
|
||||
ucs_y,
|
||||
ucs_x.as_dvec3(),
|
||||
ucs_y.as_dvec3(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@@ -1103,6 +1167,9 @@ impl OpenCADStudio {
|
||||
self.polar_mode,
|
||||
self.polar_increment_deg,
|
||||
&ucs_xf,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -1116,7 +1183,14 @@ impl OpenCADStudio {
|
||||
let base = grip.origin_world;
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
if self.ortho_mode {
|
||||
snapped = ortho_constrain(snapped, base, &ucs_xf);
|
||||
snapped = drafting_constrain(
|
||||
snapped,
|
||||
base,
|
||||
&ucs_xf,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
);
|
||||
} else if self.polar_mode {
|
||||
snapped = polar_constrain_near(
|
||||
snapped,
|
||||
@@ -1440,9 +1514,10 @@ impl OpenCADStudio {
|
||||
bounds,
|
||||
)
|
||||
} else {
|
||||
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
||||
|
||||
// The point the active rubber-band starts from.
|
||||
let (go, gr) = self.drafting_grid_basis(i);
|
||||
// The snapper is a screen-space (f32) engine; the f64
|
||||
// base only matters for typed-input precision, so hand it
|
||||
// the downcast point here.
|
||||
self.snapper.from_point = self.last_point.map(|p| p.as_vec3());
|
||||
|
||||
// Build the temporary construction direction before OSNAP.
|
||||
@@ -1455,7 +1530,14 @@ impl OpenCADStudio {
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
|
||||
let mut pt = if self.ortho_mode {
|
||||
Some(ortho_constrain(snap_cursor, base, &ucs_xf))
|
||||
Some(drafting_constrain(
|
||||
snap_cursor,
|
||||
base,
|
||||
&ucs_xf,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
))
|
||||
} else if self.polar_mode {
|
||||
let pt = polar_constrain_near(
|
||||
snap_cursor,
|
||||
@@ -1540,7 +1622,7 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (_, ucs_x, ucs_y, _) = self.tabs[i].ucs_xform().axes();
|
||||
let (_, (ucs_x, ucs_y, _)) = self.drafting_grid_basis(i);
|
||||
self.snapper.otrack_snap(
|
||||
cursor_world,
|
||||
view_rot,
|
||||
@@ -1549,8 +1631,8 @@ impl OpenCADStudio {
|
||||
step,
|
||||
self.last_point,
|
||||
self.ortho_mode && !is_window_corner,
|
||||
ucs_x,
|
||||
ucs_y,
|
||||
ucs_x.as_dvec3(),
|
||||
ucs_y.as_dvec3(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@@ -1613,6 +1695,9 @@ impl OpenCADStudio {
|
||||
self.polar_mode,
|
||||
self.polar_increment_deg,
|
||||
&ucs_xf,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
);
|
||||
}
|
||||
} else if !self.shift_down {
|
||||
@@ -1636,7 +1721,14 @@ impl OpenCADStudio {
|
||||
if let Some(base) = self.last_point {
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
if self.ortho_mode {
|
||||
pt = ortho_constrain(pt, base, &ucs_xf);
|
||||
pt = drafting_constrain(
|
||||
pt,
|
||||
base,
|
||||
&ucs_xf,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
);
|
||||
} else if self.polar_mode {
|
||||
pt = polar_constrain_near(
|
||||
pt,
|
||||
@@ -1806,6 +1898,7 @@ impl OpenCADStudio {
|
||||
eye,
|
||||
bounds,
|
||||
self.tabs[i].scene.document.header.lineweight_display,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box),
|
||||
)
|
||||
.and_then(|s| Scene::handle_from_wire_name(s))
|
||||
.or_else(|| {
|
||||
@@ -2145,7 +2238,7 @@ impl OpenCADStudio {
|
||||
self.snapper.grid_spacing = grid_spacing;
|
||||
// No rubber-band origin (perp/extension feet don't apply to a free drag).
|
||||
self.snapper.from_point = None;
|
||||
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
||||
let (go, gr) = self.drafting_grid_basis(i);
|
||||
let snap_hit = self
|
||||
.snapper
|
||||
.snap(raw, p, &snap_candidates, view_rot, eye, bounds, go, gr, None);
|
||||
@@ -2389,11 +2482,21 @@ impl OpenCADStudio {
|
||||
|
||||
// Interactive navigation tools reuse the middle-button movement path,
|
||||
// so no selection/pick logic runs while the left button drives them.
|
||||
if self.tabs[i].orbit_mode || self.tabs[i].pan_mode {
|
||||
if self.tabs[i].orbit_mode
|
||||
|| self.tabs[i].pan_mode
|
||||
|| self.tabs[i].zoom_dynamic_mode
|
||||
{
|
||||
self.clear_navigation_hover(i);
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.middle_down = true;
|
||||
sel.middle_last_pos = Some(p);
|
||||
if self.tabs[i].zoom_dynamic_mode {
|
||||
sel.box_anchor = Some(p);
|
||||
sel.box_current = Some(p);
|
||||
sel.box_crossing = false;
|
||||
sel.box_crossing_locked = true;
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
@@ -2561,11 +2664,18 @@ impl OpenCADStudio {
|
||||
|
||||
// Navigation mode: end this drag but keep the tool armed for the next
|
||||
// left drag (exit is Esc / another command). Mirror of the press.
|
||||
if self.tabs[i].orbit_mode || self.tabs[i].pan_mode {
|
||||
if self.tabs[i].orbit_mode
|
||||
|| self.tabs[i].pan_mode
|
||||
|| self.tabs[i].zoom_dynamic_mode
|
||||
{
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.middle_down = false;
|
||||
sel.middle_last_pos = None;
|
||||
sel.orbit_pivot = None;
|
||||
sel.box_anchor = None;
|
||||
sel.box_anchor_world = None;
|
||||
sel.box_current = None;
|
||||
sel.box_crossing_locked = false;
|
||||
drop(sel);
|
||||
self.arm_hover_after_navigation(i);
|
||||
return Task::none();
|
||||
@@ -2791,7 +2901,7 @@ impl OpenCADStudio {
|
||||
bounds,
|
||||
)
|
||||
} else {
|
||||
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
||||
let (go, gr) = self.drafting_grid_basis(i);
|
||||
self.snapper.from_point = self.last_point.map(|p| p.as_vec3());
|
||||
|
||||
let construction_cursor = if is_window_corner {
|
||||
@@ -2801,7 +2911,14 @@ impl OpenCADStudio {
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
|
||||
let mut pt = if self.ortho_mode {
|
||||
Some(ortho_constrain(snap_cursor, base, &ucs_xf))
|
||||
Some(drafting_constrain(
|
||||
snap_cursor,
|
||||
base,
|
||||
&ucs_xf,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
))
|
||||
} else if self.polar_mode {
|
||||
let pt = polar_constrain_near(
|
||||
snap_cursor,
|
||||
@@ -2879,7 +2996,7 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (_, ucs_x, ucs_y, _) = self.tabs[i].ucs_xform().axes();
|
||||
let (_, (ucs_x, ucs_y, _)) = self.drafting_grid_basis(i);
|
||||
self.snapper.otrack_snap(
|
||||
raw,
|
||||
view_rot,
|
||||
@@ -2888,8 +3005,8 @@ impl OpenCADStudio {
|
||||
step,
|
||||
self.last_point,
|
||||
self.ortho_mode && !is_window_corner,
|
||||
ucs_x,
|
||||
ucs_y,
|
||||
ucs_x.as_dvec3(),
|
||||
ucs_y.as_dvec3(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@@ -2908,7 +3025,14 @@ impl OpenCADStudio {
|
||||
if let Some(base) = self.last_point {
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
if self.ortho_mode {
|
||||
pt = ortho_constrain(pt, base, &ucs_xf);
|
||||
pt = drafting_constrain(
|
||||
pt,
|
||||
base,
|
||||
&ucs_xf,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
);
|
||||
} else if self.polar_mode {
|
||||
pt = polar_constrain_near(
|
||||
pt,
|
||||
@@ -2993,7 +3117,7 @@ impl OpenCADStudio {
|
||||
view_rot2,
|
||||
eye2,
|
||||
bounds,
|
||||
scene::pick::hit_test::CLICK_THRESHOLD_PX * 2.0,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box) * 2.0,
|
||||
);
|
||||
let include_fills = self.tabs[i]
|
||||
.active_cmd
|
||||
@@ -3014,6 +3138,7 @@ impl OpenCADStudio {
|
||||
eye2,
|
||||
bounds,
|
||||
self.tabs[i].scene.document.header.lineweight_display,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box),
|
||||
)
|
||||
.and_then(|s| Scene::handle_from_wire_name(s))
|
||||
.or_else(|| {
|
||||
@@ -3542,7 +3667,7 @@ impl OpenCADStudio {
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
scene::pick::hit_test::CLICK_THRESHOLD_PX * 2.0,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box) * 2.0,
|
||||
);
|
||||
let candidate_handles = self.tabs[i]
|
||||
.scene
|
||||
@@ -3562,6 +3687,7 @@ impl OpenCADStudio {
|
||||
eye,
|
||||
bounds,
|
||||
self.tabs[i].scene.document.header.lineweight_display,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box),
|
||||
)
|
||||
.into_iter()
|
||||
.filter_map(|s| Scene::handle_from_wire_name(s))
|
||||
@@ -3583,6 +3709,7 @@ impl OpenCADStudio {
|
||||
eye,
|
||||
bounds,
|
||||
self.tabs[i].scene.document.header.lineweight_display,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box),
|
||||
)
|
||||
.and_then(|s| Scene::handle_from_wire_name(s))
|
||||
.or_else(|| {
|
||||
@@ -3924,7 +4051,7 @@ impl OpenCADStudio {
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
scene::pick::hit_test::CLICK_THRESHOLD_PX * 2.0,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box) * 2.0,
|
||||
);
|
||||
let candidate_handles = self.tabs[i]
|
||||
.scene
|
||||
@@ -3939,6 +4066,7 @@ impl OpenCADStudio {
|
||||
eye,
|
||||
bounds,
|
||||
self.tabs[i].scene.document.header.lineweight_display,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box),
|
||||
)
|
||||
.and_then(|s| Scene::handle_from_wire_name(s))
|
||||
.or_else(|| {
|
||||
@@ -4043,7 +4171,7 @@ impl OpenCADStudio {
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
scene::pick::hit_test::CLICK_THRESHOLD_PX * 2.0,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box) * 2.0,
|
||||
);
|
||||
scene::pick::hit_test::click_hit(
|
||||
p,
|
||||
@@ -4052,6 +4180,7 @@ impl OpenCADStudio {
|
||||
eye,
|
||||
bounds,
|
||||
self.tabs[i].scene.document.header.lineweight_display,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box),
|
||||
)
|
||||
.and_then(|s| Scene::handle_from_wire_name(s))
|
||||
.and_then(|h| {
|
||||
@@ -4157,6 +4286,7 @@ impl OpenCADStudio {
|
||||
let i = self.active_tab;
|
||||
self.clear_navigation_hover(i);
|
||||
self.ribbon.close_dropdown();
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
let now = Instant::now();
|
||||
let is_double = {
|
||||
let sel = self.tabs[i].scene.selection.borrow();
|
||||
@@ -4185,11 +4315,16 @@ impl OpenCADStudio {
|
||||
|
||||
pub(super) fn on_viewport_scroll(&mut self, delta: mouse::ScrollDelta) -> Task<Message> {
|
||||
let nav_started = Instant::now();
|
||||
let s = match delta {
|
||||
let mut s = match delta {
|
||||
mouse::ScrollDelta::Lines { y, .. } => y,
|
||||
mouse::ScrollDelta::Pixels { y, .. } => y * 0.01,
|
||||
};
|
||||
s *= self.zoom_factor as f32 / 60.0;
|
||||
if self.zoom_wheel_reversed {
|
||||
s = -s;
|
||||
}
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.clear_navigation_hover(i);
|
||||
let cursor = self.tabs[i].scene.selection.borrow().last_move_pos;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
@@ -4354,6 +4489,7 @@ impl OpenCADStudio {
|
||||
fn snap_view_region(&mut self, region: CubeRegion, r_ucs: glam::Mat4) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
self.clear_navigation_hover(i);
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
let mut region = region;
|
||||
// "Already there → flip to opposite" check: compare the
|
||||
// current gaze direction with the region's target gaze.
|
||||
@@ -4450,7 +4586,7 @@ impl OpenCADStudio {
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
scene::pick::hit_test::CLICK_THRESHOLD_PX * 2.0,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box) * 2.0,
|
||||
);
|
||||
let candidate_ms = candidate_started.elapsed().as_secs_f64() * 1000.0;
|
||||
let candidate_count = hover_candidates.len();
|
||||
@@ -4470,6 +4606,7 @@ impl OpenCADStudio {
|
||||
eye,
|
||||
bounds,
|
||||
self.tabs[i].scene.document.header.lineweight_display,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box),
|
||||
)
|
||||
.and_then(Scene::handle_from_wire_name);
|
||||
let wire_ms = wire_started.elapsed().as_secs_f64() * 1000.0;
|
||||
|
||||
+32
-2
@@ -335,6 +335,14 @@ impl OpenCADStudio {
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let model_basis = {
|
||||
let (o, ux, uy, uz) = tab.ucs_xform().axes();
|
||||
let (ux, uy, uz) = super::helpers::drafting_axes(
|
||||
ux,
|
||||
uy,
|
||||
uz,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
);
|
||||
(o, (ux.as_vec3(), uy.as_vec3(), uz.as_vec3()))
|
||||
};
|
||||
let grid: Vec<crate::ui::overlay::GridParams> = tab
|
||||
@@ -342,7 +350,7 @@ impl OpenCADStudio {
|
||||
.grid_views(vw, vh)
|
||||
.into_iter()
|
||||
.map(|(bounds, cam, handle)| {
|
||||
let (origin, axes): (glam::DVec3, _) = if is_paper {
|
||||
let (origin, mut axes): (glam::DVec3, _) = if is_paper {
|
||||
match tab.ucs_from_viewport(handle) {
|
||||
Some(u) => {
|
||||
let (o, ux, uy, uz) =
|
||||
@@ -357,6 +365,17 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
model_basis
|
||||
};
|
||||
if is_paper {
|
||||
let (ux, uy, uz) = super::helpers::drafting_axes(
|
||||
axes.0.as_dvec3(),
|
||||
axes.1.as_dvec3(),
|
||||
axes.2.as_dvec3(),
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
);
|
||||
axes = (ux.as_vec3(), uy.as_vec3(), uz.as_vec3());
|
||||
}
|
||||
crate::ui::overlay::GridParams {
|
||||
view_rot: cam.view_proj_rte(bounds),
|
||||
eye: cam.eye(),
|
||||
@@ -680,10 +699,19 @@ impl OpenCADStudio {
|
||||
dividers,
|
||||
pane_move_rect,
|
||||
pane_drop_rect,
|
||||
tab.pan_mode || tab.orbit_mode,
|
||||
tab.pan_mode || tab.orbit_mode || tab.zoom_dynamic_mode,
|
||||
self.ribbon.open_dropdown.is_some(),
|
||||
hover_locked,
|
||||
crosshair_background(tab, is_paper),
|
||||
crate::ui::overlay::CrosshairOptions {
|
||||
size_percent: self.cursor_size,
|
||||
pick_box: self.pick_box,
|
||||
cursor_type: self.cursor_type,
|
||||
color: self.crosshair_color,
|
||||
isometric: self.isometric_drafting,
|
||||
iso_plane: self.iso_plane,
|
||||
snap_angle_deg: self.snap_angle_deg,
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
@@ -1693,6 +1721,8 @@ impl OpenCADStudio {
|
||||
self.polar_increment_deg,
|
||||
self.dyn_input,
|
||||
self.snapper.otrack_enabled,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
layout_names.clone(),
|
||||
block_tabs,
|
||||
layout_names.into_iter().skip(1).collect(),
|
||||
|
||||
@@ -21,6 +21,7 @@ impl OpenCADStudio {
|
||||
Some(K::LayerStateManager) => crate::tr!("modal-layer-state-manager"),
|
||||
Some(K::LayerTranslator) => crate::t!("Layer Translator").into_owned(),
|
||||
Some(K::DrawingUnits) => crate::t!("Drawing Units").into_owned(),
|
||||
Some(K::DraftingSettings) => crate::t!("Drafting Settings").into_owned(),
|
||||
Some(K::LayerStateEditor) => crate::tr!("modal-edit-layer-state"),
|
||||
Some(K::Plot) => crate::tr!("modal-plot"),
|
||||
Some(K::PrintAll) => t!("Print All").into_owned(),
|
||||
@@ -96,6 +97,31 @@ impl OpenCADStudio {
|
||||
&self.ui_theme,
|
||||
&self.theme_color_inputs,
|
||||
self.language,
|
||||
self.options_tab,
|
||||
self.cursor_size,
|
||||
self.pick_box,
|
||||
self.cursor_type,
|
||||
self.crosshair_color,
|
||||
&self.crosshair_color_input,
|
||||
flow,
|
||||
)
|
||||
},
|
||||
),
|
||||
super::super::ModalKind::DraftingSettings => sized_flow(
|
||||
ex,
|
||||
520,
|
||||
560,
|
||||
|flow| {
|
||||
crate::ui::window::drafting_settings::view_window(
|
||||
&self.snapper,
|
||||
self.show_grid,
|
||||
self.snapper.grid_snap(),
|
||||
self.ortho_mode,
|
||||
self.polar_mode,
|
||||
self.snapper.otrack_enabled,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
flow,
|
||||
)
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user