forked from 1b-agent/opencadstudio-upstream
Merge pull request #1667 from ramox81/codex/view-presets
-VIEW command-line form: presets with orthographic UCS, sEttings and Window # Conflicts: # src/app/commands/mod.rs # src/io/mod.rs
This commit is contained in:
commit
e5df2fd113
13 files changed
+1101
-42
No files matched your search
@@ -787,6 +787,22 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
// -VIEW: the command-line form of VIEW (modules/view/view_cmd.rs).
|
||||
// The command gathers the input; `-VIEW >…` carries out what it
|
||||
// gathered. A whole line (`-VIEW _O _FRONT`) feeds its words to
|
||||
// the command one by one, as typed.
|
||||
"-VIEW" => {
|
||||
use crate::command::CadCommand;
|
||||
let c = crate::modules::view::view_cmd::DashViewCommand::new(self.dash_view_names(i));
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("-VIEW >") => {
|
||||
return Some(self.run_dash_view(&cmd["-VIEW >".len()..], i));
|
||||
}
|
||||
cmd if cmd.starts_with("-VIEW ") => {
|
||||
return Some(self.run_command_line(cmd));
|
||||
}
|
||||
"VIEW" => {
|
||||
use crate::command::KeywordCommand;
|
||||
let c = KeywordCommand::new(
|
||||
@@ -910,45 +926,14 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Standard orientation presets — snap the camera to a world
|
||||
// axis view (these names take precedence over a same-named
|
||||
// saved view, matching the standard orientation behaviour).
|
||||
"TOP" | "FRONT" | "BACK" | "LEFT" | "RIGHT" | "BOTTOM" => {
|
||||
use crate::scene::pipeline::viewcube::{
|
||||
FACE_BACK, FACE_BOTTOM, FACE_FRONT, FACE_LEFT, FACE_RIGHT, FACE_TOP,
|
||||
};
|
||||
let face = match sub.as_str() {
|
||||
"TOP" => FACE_TOP,
|
||||
"BOTTOM" => FACE_BOTTOM,
|
||||
"FRONT" => FACE_FRONT,
|
||||
"BACK" => FACE_BACK,
|
||||
"RIGHT" => FACE_RIGHT,
|
||||
_ => FACE_LEFT,
|
||||
};
|
||||
return Some(Task::done(Message::ViewCubeSnapWorld(
|
||||
crate::scene::CubeRegion::Face(face),
|
||||
)));
|
||||
// Standard orientation presets: the world views -VIEW sets,
|
||||
// with the orthographic UCS while UCSORTHO is on. These
|
||||
// names take precedence over a same-named saved view.
|
||||
"TOP" | "FRONT" | "BACK" | "LEFT" | "RIGHT" | "BOTTOM" | "SWISO" | "SEISO" | "NEISO" | "NWISO" => {
|
||||
return Some(self.apply_view_preset(&sub));
|
||||
}
|
||||
// Isometric presets snap to the matching ViewCube corner;
|
||||
// ISO alone is the south-west one.
|
||||
"ISO" | "ISOMETRIC" | "SWISO" | "SEISO" | "NEISO" | "NWISO" => {
|
||||
let (x, y) = match sub.as_str() {
|
||||
"SEISO" => (1.0, -1.0),
|
||||
"NEISO" => (1.0, 1.0),
|
||||
"NWISO" => (-1.0, 1.0),
|
||||
_ => (-1.0, -1.0),
|
||||
};
|
||||
let want = glam::Vec3::new(x, y, 1.0).normalize();
|
||||
let corner = (18..26)
|
||||
.map(crate::scene::CubeRegion::Corner)
|
||||
.max_by(|a, b| {
|
||||
a.snap_direction()
|
||||
.dot(want)
|
||||
.total_cmp(&b.snap_direction().dot(want))
|
||||
})
|
||||
.expect("the cube has corners");
|
||||
return Some(Task::done(Message::ViewCubeSnap(corner)));
|
||||
}
|
||||
"ISO" | "ISOMETRIC" => return Some(self.apply_view_preset("SWISO")),
|
||||
// VIEW <name> shortcut for restore
|
||||
_ => {
|
||||
let name = sub.clone();
|
||||
|
||||
@@ -26,6 +26,7 @@ mod point_cloud;
|
||||
pub(crate) mod sheet_set;
|
||||
mod styleprops;
|
||||
mod view;
|
||||
mod view_presets;
|
||||
mod xclip;
|
||||
mod xref_attach;
|
||||
|
||||
@@ -925,6 +926,7 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"USERI",
|
||||
"USERR",
|
||||
"VIEW",
|
||||
"-VIEW",
|
||||
"VPORTS",
|
||||
"VS",
|
||||
"VW",
|
||||
|
||||
@@ -1287,6 +1287,8 @@ impl OpenCADStudio {
|
||||
| "UOSNAP"
|
||||
| "FIELDDISPLAY"
|
||||
| "SURFACEASSOCIATIVITY"
|
||||
| "UCSORTHO"
|
||||
| "UCSVIEW"
|
||||
| "PDFIMPORTMODE"
|
||||
| "PDFIMPORTFILTER"
|
||||
| "PDFIMPORTLAYERS"
|
||||
@@ -1525,6 +1527,32 @@ impl OpenCADStudio {
|
||||
}
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
if matches!(name.as_str(), "UCSORTHO" | "UCSVIEW") {
|
||||
use crate::modules::view::view_cmd;
|
||||
let current = i16::from(if name == "UCSORTHO" { view_cmd::ucs_ortho() } else { view_cmd::ucs_view() });
|
||||
match value.as_deref().map(|v| v.trim().parse::<i16>().ok().filter(|v| (0..=1).contains(v))) {
|
||||
Some(Some(mode)) => {
|
||||
if current != mode {
|
||||
if name == "UCSORTHO" {
|
||||
view_cmd::set_ucs_ortho(mode == 1);
|
||||
} else {
|
||||
view_cmd::set_ucs_view(mode == 1);
|
||||
}
|
||||
self.save_config();
|
||||
}
|
||||
}
|
||||
Some(None) => {
|
||||
self.command_line.push_error(crate::t!("Requires 0 or 1 only.").as_ref());
|
||||
self.command_line.push_output(&format!("Enter new value for {name} <{current}>:"));
|
||||
self.pending_setvar = Some(name.clone());
|
||||
}
|
||||
None => {
|
||||
self.command_line.push_output(&format!("Enter new value for {name} <{current}>:"));
|
||||
self.pending_setvar = Some(name.clone());
|
||||
}
|
||||
}
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
if name == "SURFACEASSOCIATIVITY" {
|
||||
use crate::scene::model::sweep_model;
|
||||
let current = i16::from(sweep_model::surface_associativity());
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
//! What -VIEW carries out: the view presets (with the orthographic UCS of
|
||||
//! UCSORTHO), named views saved with the UCS of UCSVIEW, and the per-view
|
||||
//! settings of its sEttings option.
|
||||
|
||||
use super::super::{Message, OpenCADStudio};
|
||||
use crate::command::CadCommand;
|
||||
use crate::modules::view::view_cmd::{self, DashViewCommand, ViewInfo, ViewNames};
|
||||
use codec::objects::ObjectType;
|
||||
use codec::types::{Handle, Vector3};
|
||||
use iced::Task;
|
||||
|
||||
/// The xrecord in a view's extension dictionary holding its category and
|
||||
/// layer snapshot, as the reference keeps them.
|
||||
const VIEW_INFO: &str = "ADSK_XREC_VTRVIEWINFO";
|
||||
|
||||
/// The layer state a view's layer snapshot is stored under.
|
||||
fn snapshot_name(view: &str) -> String {
|
||||
format!("ACAD_VIEWS_{view}")
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// Names -VIEW checks its input against, taken from the drawing.
|
||||
pub(super) fn dash_view_names(&self, i: usize) -> ViewNames {
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let visual_styles = crate::io::visual_styles::offered(document);
|
||||
let live_sections: Vec<(String, Handle)> = document
|
||||
.entities()
|
||||
.filter_map(|entity| match entity {
|
||||
codec::EntityType::Extended(extended) => match &extended.data {
|
||||
codec::entities::ExtendedEntityData::SectionObject(section) => {
|
||||
Some((section.name.clone(), extended.common.handle))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let name_of = |list: &[(String, Handle)], handle: Handle| {
|
||||
(!handle.is_null())
|
||||
.then(|| list.iter().find(|(_, h)| *h == handle).map(|(name, _)| name.clone()))
|
||||
.flatten()
|
||||
};
|
||||
let views = document
|
||||
.views
|
||||
.iter()
|
||||
.map(|view| ViewInfo {
|
||||
name: view.name.clone(),
|
||||
category: (!view.handle.is_null())
|
||||
.then(|| document.xrecord(view.handle, VIEW_INFO))
|
||||
.flatten()
|
||||
.and_then(|record| record.get_string(300))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
visual_style: name_of(&visual_styles, view.visual_style_handle),
|
||||
live_section: name_of(&live_sections, view.live_section_handle),
|
||||
})
|
||||
.collect();
|
||||
ViewNames {
|
||||
views,
|
||||
visual_styles: visual_styles.into_iter().map(|(name, _)| name).collect(),
|
||||
live_sections: live_sections.into_iter().map(|(name, _)| name).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a standard view: the world orthographic or isometric view of
|
||||
/// `preset`. An orthographic one also sets its orthographic UCS while
|
||||
/// UCSORTHO is on; asking for the view shown already keeps it.
|
||||
pub(in crate::app) fn apply_view_preset(&mut self, preset: &str) -> Task<Message> {
|
||||
use crate::scene::pipeline::viewcube::{
|
||||
FACE_BACK, FACE_BOTTOM, FACE_FRONT, FACE_LEFT, FACE_RIGHT, FACE_TOP,
|
||||
};
|
||||
use crate::scene::CubeRegion;
|
||||
let i = self.active_tab;
|
||||
let region = match preset {
|
||||
"TOP" => CubeRegion::Face(FACE_TOP),
|
||||
"BOTTOM" => CubeRegion::Face(FACE_BOTTOM),
|
||||
"FRONT" => CubeRegion::Face(FACE_FRONT),
|
||||
"BACK" => CubeRegion::Face(FACE_BACK),
|
||||
"LEFT" => CubeRegion::Face(FACE_LEFT),
|
||||
"RIGHT" => CubeRegion::Face(FACE_RIGHT),
|
||||
_ => {
|
||||
let (x, y) = match preset {
|
||||
"SEISO" => (1.0, -1.0),
|
||||
"NEISO" => (1.0, 1.0),
|
||||
"NWISO" => (-1.0, 1.0),
|
||||
_ => (-1.0, -1.0),
|
||||
};
|
||||
let want = glam::Vec3::new(x, y, 1.0).normalize();
|
||||
(18..26)
|
||||
.map(CubeRegion::Corner)
|
||||
.max_by(|a, b| a.snap_direction().dot(want).total_cmp(&b.snap_direction().dot(want)))
|
||||
.expect("the cube has corners")
|
||||
}
|
||||
};
|
||||
if view_cmd::ucs_ortho() && self.tabs[i].active_block_edit.is_none() {
|
||||
if let Some((name, x, y, ortho)) = view_cmd::orthographic_ucs(preset) {
|
||||
// The top view's UCS is the world one.
|
||||
self.tabs[i].active_ucs = (ortho != 1).then(|| {
|
||||
let mut ucs = codec::tables::Ucs::new(name);
|
||||
ucs.origin = Vector3::ZERO;
|
||||
ucs.x_axis = Vector3::new(x[0], x[1], x[2]);
|
||||
ucs.y_axis = Vector3::new(y[0], y[1], y[2]);
|
||||
ucs.ortho_type = ortho;
|
||||
ucs
|
||||
});
|
||||
self.commit_active_ucs_change(i, "-VIEW");
|
||||
}
|
||||
}
|
||||
self.command_line.push_output(crate::t!("Regenerating model.").as_ref());
|
||||
self.snap_view_preset(region)
|
||||
}
|
||||
|
||||
/// Carry out what -VIEW gathered (`-VIEW >VERB args`).
|
||||
pub(super) fn run_dash_view(&mut self, rest: &str, i: usize) -> Task<Message> {
|
||||
let (verb, arg) = rest.split_once(' ').unwrap_or((rest, ""));
|
||||
let mut settings_again = false;
|
||||
match verb {
|
||||
"END" => {
|
||||
if !arg.is_empty() {
|
||||
self.command_line.push_output(arg);
|
||||
}
|
||||
}
|
||||
"PRESET" => return self.apply_view_preset(arg),
|
||||
"DELETE" => {
|
||||
let doomed: Vec<String> = view_cmd::matching(
|
||||
self.tabs[i].scene.document.views.iter().map(|view| view.name.as_str()),
|
||||
arg,
|
||||
)
|
||||
.into_iter()
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
if !doomed.is_empty() {
|
||||
self.push_undo_snapshot(i, "-VIEW");
|
||||
for name in &doomed {
|
||||
self.tabs[i].scene.document.views.remove(name);
|
||||
self.tabs[i].scene.document.delete_layer_state(&snapshot_name(name));
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
}
|
||||
"RESTORE" => {
|
||||
let Some(view) = self.tabs[i].scene.document.views.get(arg).cloned() else {
|
||||
return Task::none();
|
||||
};
|
||||
let before = self.tabs[i].scene.active_gaze_dir();
|
||||
self.tabs[i].scene.restore_named_view(&view);
|
||||
if view.ucs_associated && self.tabs[i].active_block_edit.is_none() {
|
||||
let world = view.ucs_x_axis == Vector3::new(1.0, 0.0, 0.0)
|
||||
&& view.ucs_y_axis == Vector3::new(0.0, 1.0, 0.0)
|
||||
&& view.ucs_origin == Vector3::ZERO;
|
||||
self.tabs[i].active_ucs = (!world).then(|| {
|
||||
let name = view_cmd::PRESETS
|
||||
.iter()
|
||||
.filter_map(|preset| view_cmd::orthographic_ucs(preset))
|
||||
.find(|(_, _, _, ortho)| *ortho == view.ucs_ortho_type)
|
||||
.map_or("*ACTIVE*", |(name, ..)| name);
|
||||
let mut ucs = codec::tables::Ucs::new(name);
|
||||
ucs.origin = view.ucs_origin;
|
||||
ucs.x_axis = view.ucs_x_axis;
|
||||
ucs.y_axis = view.ucs_y_axis;
|
||||
ucs.elevation = view.ucs_elevation;
|
||||
ucs.ortho_type = view.ucs_ortho_type;
|
||||
ucs
|
||||
});
|
||||
self.commit_active_ucs_change(i, "-VIEW");
|
||||
}
|
||||
if self.tabs[i].scene.active_gaze_dir().dot(before) < 0.9999 {
|
||||
self.command_line.push_output(crate::t!("Regenerating model.").as_ref());
|
||||
}
|
||||
}
|
||||
"SAVE" => {
|
||||
let mut view = self.tabs[i].scene.current_as_named_view(arg);
|
||||
if view_cmd::ucs_view() {
|
||||
self.command_line.push_output("UCSVIEW = 1 UCS will be saved with view");
|
||||
self.store_ucs_in_view(i, &mut view);
|
||||
}
|
||||
self.save_named_view(i, view);
|
||||
}
|
||||
"WINDOW" => {
|
||||
let parts: Vec<&str> = arg.split('\u{1f}').collect();
|
||||
let corner = |s: &str| {
|
||||
let (x, y) = s.split_once(',')?;
|
||||
Some((x.trim().parse::<f64>().ok()?, y.trim().parse::<f64>().ok()?))
|
||||
};
|
||||
match (parts.first(), parts.get(1).and_then(|s| corner(s)), parts.get(2).and_then(|s| corner(s))) {
|
||||
(Some(name), Some(a), Some(b)) if a.0 != b.0 && a.1 != b.1 => {
|
||||
// A plan view of the window between the corners; the
|
||||
// reference keeps no UCS with a window view.
|
||||
let mut view = self.tabs[i].scene.current_as_named_view(name);
|
||||
view.target = Vector3 { x: (a.0 + b.0) / 2.0, y: (a.1 + b.1) / 2.0, z: 0.0 };
|
||||
view.direction = Vector3 { x: 0.0, y: 0.0, z: 1.0 };
|
||||
view.width = (b.0 - a.0).abs();
|
||||
view.height = (b.1 - a.1).abs();
|
||||
self.save_named_view(i, view);
|
||||
}
|
||||
_ => self.command_line.push_output("Invalid window specification."),
|
||||
}
|
||||
}
|
||||
"SETTINGS" => settings_again = true,
|
||||
"UCSVIEW" => {
|
||||
view_cmd::set_ucs_view(arg == "1");
|
||||
self.save_config();
|
||||
settings_again = true;
|
||||
}
|
||||
"SET" => {
|
||||
let parts: Vec<&str> = arg.split('\u{1f}').collect();
|
||||
if let [setting, view, value] = parts[..] {
|
||||
self.apply_view_setting(i, setting, view, value);
|
||||
}
|
||||
settings_again = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if settings_again {
|
||||
let command = DashViewCommand::settings(self.dash_view_names(i));
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
self.finish_dispatch("-VIEW")
|
||||
}
|
||||
|
||||
/// Keep the current UCS with a view being saved (UCSVIEW).
|
||||
fn store_ucs_in_view(&self, i: usize, view: &mut codec::tables::View) {
|
||||
view.ucs_associated = true;
|
||||
match &self.tabs[i].active_ucs {
|
||||
Some(ucs) => {
|
||||
view.ucs_origin = ucs.origin;
|
||||
view.ucs_x_axis = ucs.x_axis;
|
||||
view.ucs_y_axis = ucs.y_axis;
|
||||
view.ucs_elevation = ucs.elevation;
|
||||
view.ucs_ortho_type = ucs.ortho_type;
|
||||
view.named_ucs_handle = ucs.named_ucs_handle;
|
||||
}
|
||||
None => {
|
||||
view.ucs_origin = Vector3::ZERO;
|
||||
view.ucs_x_axis = Vector3::new(1.0, 0.0, 0.0);
|
||||
view.ucs_y_axis = Vector3::new(0.0, 1.0, 0.0);
|
||||
view.ucs_elevation = 0.0;
|
||||
view.ucs_ortho_type = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add or replace a named view, keeping the record (and so its settings)
|
||||
/// of a view saved again under the same name.
|
||||
fn save_named_view(&mut self, i: usize, mut view: codec::tables::View) {
|
||||
self.push_undo_snapshot(i, "-VIEW");
|
||||
let document = &mut self.tabs[i].scene.document;
|
||||
match document.views.get(&view.name).cloned() {
|
||||
Some(old) => {
|
||||
view.name = old.name;
|
||||
view.handle = old.handle;
|
||||
view.visual_style_handle = old.visual_style_handle;
|
||||
view.live_section_handle = old.live_section_handle;
|
||||
view.background_handle = old.background_handle;
|
||||
}
|
||||
None => {
|
||||
view.handle = document.allocate_handle();
|
||||
if view.visual_style_handle.is_null() {
|
||||
view.visual_style_handle = crate::io::visual_styles::offered(document)
|
||||
.into_iter()
|
||||
.find(|(name, _)| name == "2D Wireframe")
|
||||
.map_or(Handle::NULL, |(_, handle)| handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
document.views.add_or_replace(view);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
|
||||
/// One setting of -VIEW's sEttings option on the named view.
|
||||
fn apply_view_setting(&mut self, i: usize, setting: &str, name: &str, value: &str) {
|
||||
self.push_undo_snapshot(i, "-VIEW");
|
||||
let document = &mut self.tabs[i].scene.document;
|
||||
let Some(mut view) = document.views.get(name).cloned() else {
|
||||
return;
|
||||
};
|
||||
if view.handle.is_null() {
|
||||
view.handle = document.allocate_handle();
|
||||
}
|
||||
let find = |list: Vec<(String, Handle)>| {
|
||||
list.into_iter().find(|(entry, _)| entry.eq_ignore_ascii_case(value)).map_or(Handle::NULL, |(_, h)| h)
|
||||
};
|
||||
match setting {
|
||||
"BACKGROUND" => view.background_handle = Handle::NULL,
|
||||
"VISUALSTYLE" => view.visual_style_handle = find(crate::io::visual_styles::offered(document)),
|
||||
"SECTION" => {
|
||||
let sections: Vec<(String, Handle)> = document
|
||||
.entities()
|
||||
.filter_map(|entity| match entity {
|
||||
codec::EntityType::Extended(extended) => match &extended.data {
|
||||
codec::entities::ExtendedEntityData::SectionObject(section) => {
|
||||
Some((section.name.clone(), extended.common.handle))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
view.live_section_handle = find(sections);
|
||||
}
|
||||
"CATEGORIZE" | "LAYER" => {
|
||||
let snapshot = snapshot_name(&view.name);
|
||||
let layer_name = match (setting, value) {
|
||||
("LAYER", "SAVE") => {
|
||||
document.capture_layer_state(snapshot.clone(), "");
|
||||
Some(snapshot)
|
||||
}
|
||||
("LAYER", _) => {
|
||||
document.delete_layer_state(&snapshot);
|
||||
Some(String::new())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let record = document.ensure_xrecord(view.handle, VIEW_INFO);
|
||||
if let Some(ObjectType::XRecord(record)) = document.objects.get_mut(&record) {
|
||||
let text = |record: &codec::objects::XRecord, code: i32| {
|
||||
record.get_string(code).unwrap_or_default().to_string()
|
||||
};
|
||||
let category = if setting == "CATEGORIZE" { value.to_string() } else { text(record, 300) };
|
||||
let layers = layer_name.unwrap_or_else(|| text(record, 302));
|
||||
let mut rest: Vec<_> =
|
||||
record.entries.iter().filter(|entry| !matches!(entry.code, 300 | 302)).cloned().collect();
|
||||
if rest.is_empty() {
|
||||
rest = vec![
|
||||
codec::objects::XRecordEntry::bool(293, false),
|
||||
codec::objects::XRecordEntry::bool(294, false),
|
||||
];
|
||||
}
|
||||
record.entries.clear();
|
||||
record.add_string(300, category);
|
||||
record.add_string(302, layers);
|
||||
record.entries.extend(rest);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
document.views.add_or_replace(view);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
}
|
||||
@@ -419,6 +419,10 @@ pub struct UserSettings {
|
||||
pub field_display: bool,
|
||||
/// SURFACEASSOCIATIVITY: new surfaces stay associative to their sources.
|
||||
pub surface_associativity: bool,
|
||||
/// UCSORTHO: an orthographic view preset also sets its orthographic UCS.
|
||||
pub ucs_ortho: bool,
|
||||
/// UCSVIEW: a saved named view keeps the current UCS.
|
||||
pub ucs_view: bool,
|
||||
/// Nested-copy symbol handling: false inserts, true binds.
|
||||
pub ncopy_bind: bool,
|
||||
/// Last Options page; unknown saved names fall back without rejecting the config.
|
||||
@@ -836,6 +840,8 @@ impl Default for UserSettings {
|
||||
attdef_on_screen: true,
|
||||
field_display: true,
|
||||
surface_associativity: true,
|
||||
ucs_ortho: true,
|
||||
ucs_view: true,
|
||||
ncopy_bind: false,
|
||||
cursor_type: CursorType::Crosshair,
|
||||
crosshair_color: None,
|
||||
|
||||
@@ -794,6 +794,8 @@ impl OpenCADStudio {
|
||||
attdef_on_screen: crate::modules::draw::draw::attdef::session().on_screen,
|
||||
field_display: crate::entities::field::display(),
|
||||
surface_associativity: crate::scene::model::sweep_model::surface_associativity(),
|
||||
ucs_ortho: crate::modules::view::view_cmd::ucs_ortho(),
|
||||
ucs_view: crate::modules::view::view_cmd::ucs_view(),
|
||||
ncopy_bind: self.ncopy_bind,
|
||||
cursor_type: self.cursor_type,
|
||||
crosshair_color: self.crosshair_color,
|
||||
@@ -907,6 +909,8 @@ impl OpenCADStudio {
|
||||
crate::modules::draw::draw::attdef::session().on_screen = s.attdef_on_screen;
|
||||
crate::entities::field::set_display(s.field_display);
|
||||
crate::scene::model::sweep_model::set_surface_associativity(s.surface_associativity);
|
||||
crate::modules::view::view_cmd::set_ucs_ortho(s.ucs_ortho);
|
||||
crate::modules::view::view_cmd::set_ucs_view(s.ucs_view);
|
||||
self.ncopy_bind = s.ncopy_bind;
|
||||
self.cursor_type = s.cursor_type;
|
||||
self.crosshair_color = s.crosshair_color;
|
||||
|
||||
@@ -5976,15 +5976,25 @@ properties={:.1}ms picked={}",
|
||||
}
|
||||
|
||||
fn snap_view_region(&mut self, region: CubeRegion, r_ucs: glam::Mat4) -> Task<Message> {
|
||||
self.snap_view_region_as(region, r_ucs, true)
|
||||
}
|
||||
|
||||
/// A standard view preset (-VIEW): the world view itself, kept when it
|
||||
/// is shown already, with no cube message.
|
||||
pub(in crate::app) fn snap_view_preset(&mut self, region: CubeRegion) -> Task<Message> {
|
||||
self.snap_view_region_as(region, glam::Mat4::IDENTITY, false)
|
||||
}
|
||||
|
||||
fn snap_view_region_as(&mut self, region: CubeRegion, r_ucs: glam::Mat4, cube: bool) -> 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.
|
||||
// "Already there → flip to opposite" check for a cube click: compare
|
||||
// the current gaze direction with the region's target gaze.
|
||||
let target_dir = r_ucs.transform_vector3(region.snap_direction());
|
||||
let cur_dir = self.tabs[i].scene.active_gaze_dir();
|
||||
if cur_dir.dot(target_dir) > 0.9999 {
|
||||
if cube && cur_dir.dot(target_dir) > 0.9999 {
|
||||
region = region.opposite();
|
||||
}
|
||||
let eye_dir = r_ucs.transform_vector3(region.snap_direction());
|
||||
@@ -6014,8 +6024,10 @@ properties={:.1}ms picked={}",
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
self.command_line
|
||||
.push_output(crate::tf!("View: {}", crate::t!(region.label())).as_ref());
|
||||
if cube {
|
||||
self.command_line
|
||||
.push_output(crate::tf!("View: {}", crate::t!(region.label())).as_ref());
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user