forked from 1b-agent/opencadstudio-upstream
The codec now holds wide EntityType variants boxed (MText, Helix, Dimension, Hatch, Viewport, MultiLeader, Surface, Table, Ole2Frame, Light, Extended). Constructors box their entity; nested patterns reach a dimension through entities::dimension::as_dimension(_mut). Also follows BlockAction::action_ids, LineTypeComplexData::source_flags and the decimal tab stop with an explicit character. Co-Authored-By: Claude Opus 5.5 <[email protected]>
167 lines
6.8 KiB
Rust
167 lines
6.8 KiB
Rust
//! UCSFOLLOW is a drawing/viewport variable, not an application preference.
|
|
use crate::app::{Message, OpenCADStudio};
|
|
use iced::Task;
|
|
|
|
inventory::submit!(crate::command::CommandRegistration {
|
|
names: &["UCSFOLLOW"]
|
|
});
|
|
|
|
impl OpenCADStudio {
|
|
pub(super) fn dispatch_ucsfollow(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
|
let rest = cmd.strip_prefix("SETVAR ").unwrap_or(cmd).trim();
|
|
let mut parts = rest.splitn(2, char::is_whitespace);
|
|
if parts.next()? != "UCSFOLLOW" {
|
|
return None;
|
|
}
|
|
let value = parts.next().map(str::trim).filter(|v| !v.is_empty());
|
|
let current = self.tabs[i].scene.ucs_follow();
|
|
match value {
|
|
None => {
|
|
self.command_line.push_output(
|
|
crate::tf!(
|
|
"UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <{}>:",
|
|
u8::from(current)
|
|
)
|
|
.as_ref(),
|
|
);
|
|
self.pending_setvar = Some("UCSFOLLOW".into());
|
|
}
|
|
Some("0" | "1") => {
|
|
let next = value == Some("1");
|
|
if next
|
|
&& self.tabs[i].scene.current_layout != "Model"
|
|
&& self.tabs[i].scene.active_viewport.is_none()
|
|
{
|
|
self.command_line.push_error(
|
|
crate::t!(
|
|
"UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS."
|
|
)
|
|
.as_ref(),
|
|
);
|
|
} else {
|
|
if current != next {
|
|
self.push_undo_snapshot(i, "UCSFOLLOW");
|
|
self.tabs[i].scene.set_ucs_follow(next);
|
|
self.tabs[i].dirty = true;
|
|
}
|
|
// Enabling UCSFOLLOW must align an already-defined UCS
|
|
// immediately, not only follow subsequent UCS edits. Also
|
|
// realign when the value was already 1 (e.g. after orbit).
|
|
if next && self.tabs[i].active_block_edit.is_none() {
|
|
if current == next {
|
|
self.push_undo_snapshot(i, "UCSFOLLOW");
|
|
}
|
|
self.tabs[i].sync_ucs_to_scene();
|
|
let rotation = self.tabs[i].scene.viewcube_ucs_mat();
|
|
self.plan_snap(i, rotation);
|
|
self.tabs[i].dirty = true;
|
|
}
|
|
self.command_line
|
|
.push_output(&format!("UCSFOLLOW = {}", u8::from(next)));
|
|
}
|
|
}
|
|
Some(_) => {
|
|
self.command_line
|
|
.push_error(crate::t!("UCSFOLLOW: only 0 or 1 is allowed.").as_ref());
|
|
}
|
|
}
|
|
Some(Task::none())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn app() -> OpenCADStudio {
|
|
let mut app = OpenCADStudio::new_for_test();
|
|
app.automation_op(r#"{"op":"new"}"#);
|
|
app
|
|
}
|
|
|
|
#[test]
|
|
fn variable_is_registered_prompted_and_validated() {
|
|
let mut app = app();
|
|
let i = app.active_tab;
|
|
assert!(!app.tabs[i].scene.ucs_follow());
|
|
let _ = app.run_command_line("UCSFOLLOW");
|
|
assert_eq!(app.pending_setvar.as_deref(), Some("UCSFOLLOW"));
|
|
app.pending_setvar = None;
|
|
let _ = app.run_command_line("SETVAR UCSFOLLOW 1");
|
|
assert!(app.tabs[i].scene.ucs_follow());
|
|
let _ = app.run_command_line("UCSFOLLOW 2");
|
|
assert!(app.tabs[i].scene.ucs_follow());
|
|
let _ = app.run_command_line("UCSFOLLOW 0");
|
|
assert!(!app.tabs[i].scene.ucs_follow());
|
|
assert!(crate::command::all_registered_command_names().contains(&"UCSFOLLOW"));
|
|
}
|
|
|
|
#[test]
|
|
fn ucs_changes_align_view_only_when_enabled_and_aliases_work() {
|
|
let mut app = app();
|
|
let i = app.active_tab;
|
|
// Test-only table: never writes or imports into the user's profile.
|
|
app.command_aliases.insert("UF".into(), "UCSFOLLOW".into());
|
|
app.command_aliases.insert("SQ".into(), "UCS".into());
|
|
let before = app.tabs[i].scene.camera.borrow().clone();
|
|
let _ = app.run_command_line("SQ Z 30");
|
|
let unchanged = app.tabs[i].scene.camera.borrow().clone();
|
|
assert_eq!(before.rotation, unchanged.rotation);
|
|
let _ = app.run_command_line("UF 1");
|
|
assert!(app.tabs[i].scene.ucs_follow());
|
|
let _ = app.run_command_line("SQ Z 30");
|
|
let followed = app.tabs[i].scene.camera.borrow().clone();
|
|
assert_ne!(unchanged.rotation, followed.rotation);
|
|
let _ = app.run_command_line("UF 0");
|
|
let _ = app.run_command_line("SQ Z 30");
|
|
assert_eq!(
|
|
followed.rotation,
|
|
app.tabs[i].scene.camera.borrow().rotation
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn floating_viewport_setting_does_not_leak_into_model_or_paper() {
|
|
let mut app = app();
|
|
let i = app.active_tab;
|
|
let first = app.tabs[i]
|
|
.scene
|
|
.add_entity(codec::EntityType::Viewport(Box::new(codec::entities::Viewport::new())));
|
|
let second = app.tabs[i]
|
|
.scene
|
|
.add_entity(codec::EntityType::Viewport(Box::new(codec::entities::Viewport::new())));
|
|
app.tabs[i].scene.active_viewport = Some(first);
|
|
let _ = app.run_command_line("UCSFOLLOW 1");
|
|
assert!(app.tabs[i].scene.ucs_follow());
|
|
app.tabs[i].scene.active_viewport = Some(second);
|
|
assert!(!app.tabs[i].scene.ucs_follow());
|
|
app.tabs[i].scene.active_viewport = None;
|
|
assert!(!app.tabs[i].scene.ucs_follow());
|
|
app.tabs[i].scene.current_layout = "Layout1".into();
|
|
let _ = app.run_command_line("UCSFOLLOW 1");
|
|
assert!(!app.tabs[i].scene.ucs_follow());
|
|
app.tabs[i].scene.active_viewport = Some(first);
|
|
assert!(app.tabs[i].scene.ucs_follow());
|
|
}
|
|
|
|
#[test]
|
|
fn enabling_after_defining_ucs_aligns_immediately() {
|
|
let mut app = app();
|
|
let i = app.active_tab;
|
|
let before = app.tabs[i].scene.camera.borrow().rotation;
|
|
let _ = app.run_command_line("UCS Z 30");
|
|
assert_eq!(before, app.tabs[i].scene.camera.borrow().rotation);
|
|
let _ = app.run_command_line("UCSFOLLOW 1");
|
|
let aligned = app.tabs[i].scene.camera.borrow().rotation;
|
|
assert_ne!(
|
|
before, aligned,
|
|
"enabling must align the existing UCS immediately"
|
|
);
|
|
// The same orientation must be obtained through the explicit PLAN path.
|
|
let _ = app.run_command_line("PLAN CURRENT");
|
|
assert_eq!(aligned, app.tabs[i].scene.camera.borrow().rotation);
|
|
let _ = app.run_command_line("UCSFOLLOW 0");
|
|
assert_eq!(aligned, app.tabs[i].scene.camera.borrow().rotation);
|
|
}
|
|
}
|