forked from 1b-agent/opencadstudio-upstream
feat(ucs): integrate coordinate systems
Route coordinate input, geometry creation, transforms, properties,\nviews, and viewport persistence through a shared working plane.\n\nRefs #484
This commit is contained in:
1 parent
b1d55c6a7e
commit
657d023fb7
61 files changed
+2218
-747
No files matched your search
+32
-12
@@ -610,7 +610,12 @@ impl OpenCADStudio {
|
||||
if nums.len() < 3 {
|
||||
return Some(Task::none());
|
||||
}
|
||||
let place = glam::DVec3::new(nums[0], nums[1], nums[2]);
|
||||
let plane = if self.tabs[i].editing_model_space() {
|
||||
self.tabs[i].ucs_xform().working_plane()
|
||||
} else {
|
||||
crate::command::WorkingPlane::default()
|
||||
};
|
||||
let place = plane.to_local(glam::DVec3::new(nums[0], nums[1], nums[2]));
|
||||
let handles: Vec<acadrust::Handle> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
@@ -623,6 +628,9 @@ impl OpenCADStudio {
|
||||
qdim_collect_points(e, &mut pts);
|
||||
}
|
||||
}
|
||||
for point in &mut pts {
|
||||
*point = plane.to_local(*point);
|
||||
}
|
||||
if pts.len() < 2 {
|
||||
self.command_line
|
||||
.push_error(crate::t!("QDIM: no dimensionable endpoints in the selection.").as_ref());
|
||||
@@ -671,9 +679,10 @@ impl OpenCADStudio {
|
||||
dim.definition_point = v(def);
|
||||
dim.base.definition_point = v(def);
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
self.commit_entity(acadrust::EntityType::Dimension(
|
||||
let entity = acadrust::EntityType::Dimension(
|
||||
acadrust::entities::Dimension::Linear(dim),
|
||||
));
|
||||
);
|
||||
self.commit_entity(plane.place_entity(entity));
|
||||
made += 1;
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
@@ -1325,6 +1334,13 @@ fn find_last_linear_dim(
|
||||
fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec<glam::DVec3>) {
|
||||
use acadrust::EntityType as ET;
|
||||
let p = |v: &acadrust::types::Vector3| glam::DVec3::new(v.x, v.y, v.z);
|
||||
let ocs = |point: (f64, f64, f64), normal: acadrust::types::Vector3| {
|
||||
let world = crate::scene::view::transform::ocs_point_to_wcs(
|
||||
point,
|
||||
(normal.x, normal.y, normal.z),
|
||||
);
|
||||
glam::DVec3::new(world.0, world.1, world.2)
|
||||
};
|
||||
match e {
|
||||
ET::Line(l) => {
|
||||
out.push(p(&l.start));
|
||||
@@ -1332,11 +1348,12 @@ fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec<glam::DVec3>) {
|
||||
}
|
||||
ET::LwPolyline(pl) => {
|
||||
for v in &pl.vertices {
|
||||
out.push(glam::DVec3::new(
|
||||
v.location.x,
|
||||
v.location.y,
|
||||
pl.elevation,
|
||||
));
|
||||
out.push(ocs((v.location.x, v.location.y, pl.elevation), pl.normal));
|
||||
}
|
||||
}
|
||||
ET::Polyline2D(pl) => {
|
||||
for v in &pl.vertices {
|
||||
out.push(ocs((v.location.x, v.location.y, pl.elevation), pl.normal));
|
||||
}
|
||||
}
|
||||
ET::Polyline(pl) => {
|
||||
@@ -1346,10 +1363,13 @@ fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec<glam::DVec3>) {
|
||||
}
|
||||
ET::Arc(a) => {
|
||||
for &ang in &[a.start_angle, a.end_angle] {
|
||||
out.push(glam::DVec3::new(
|
||||
a.center.x + a.radius * ang.cos(),
|
||||
a.center.y + a.radius * ang.sin(),
|
||||
a.center.z,
|
||||
out.push(ocs(
|
||||
(
|
||||
a.center.x + a.radius * ang.cos(),
|
||||
a.center.y + a.radius * ang.sin(),
|
||||
a.center.z,
|
||||
),
|
||||
a.normal,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ impl OpenCADStudio {
|
||||
self.command_line.push_error(crate::t!("Usage: UCS SAVE <name>").as_ref());
|
||||
} else {
|
||||
// Save the current active UCS under this name.
|
||||
let ucs = match &self.tabs[i].active_ucs {
|
||||
let mut ucs = match &self.tabs[i].active_ucs {
|
||||
Some(u) => {
|
||||
let mut saved = u.clone();
|
||||
saved.name = name.clone();
|
||||
@@ -228,7 +228,23 @@ impl OpenCADStudio {
|
||||
}
|
||||
None => Ucs::new(&name), // save WCS (identity)
|
||||
};
|
||||
self.tabs[i].scene.document.ucss.add_or_replace(ucs);
|
||||
ucs.handle = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.ucss
|
||||
.get(&name)
|
||||
.map(|existing| existing.handle)
|
||||
.filter(|handle| !handle.is_null())
|
||||
.unwrap_or_else(|| {
|
||||
self.tabs[i].scene.document.allocate_handle()
|
||||
});
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.ucss
|
||||
.add_or_replace(ucs.clone());
|
||||
self.tabs[i].active_ucs = Some(ucs);
|
||||
active_changed = true;
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
.push_output(crate::tf!("UCS '{}' saved.", name).as_ref());
|
||||
@@ -238,7 +254,30 @@ impl OpenCADStudio {
|
||||
let name = parts.get(2).map(|s| s.trim()).unwrap_or("").to_string();
|
||||
if name.is_empty() {
|
||||
self.command_line.push_error(crate::t!("Usage: UCS DELETE <name>").as_ref());
|
||||
} else if self.tabs[i].scene.document.ucss.remove(&name).is_some() {
|
||||
} else if let Some(removed) =
|
||||
self.tabs[i].scene.document.ucss.remove(&name)
|
||||
{
|
||||
let removed_handle = removed.handle;
|
||||
for entity in self.tabs[i].scene.document.entities_mut() {
|
||||
if let acadrust::EntityType::Viewport(viewport) = entity {
|
||||
if viewport.ucs_handle == removed_handle {
|
||||
viewport.ucs_handle = acadrust::Handle::NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
let active_matches = self.tabs[i].active_ucs.as_ref().is_some_and(|ucs| {
|
||||
(!removed_handle.is_null() && ucs.handle == removed_handle)
|
||||
|| ucs.name.eq_ignore_ascii_case(&name)
|
||||
});
|
||||
if active_matches {
|
||||
if let Some(active) = self.tabs[i].active_ucs.as_mut() {
|
||||
active.name = "*ACTIVE*".to_string();
|
||||
active.handle = acadrust::Handle::NULL;
|
||||
active.named_ucs_handle = acadrust::Handle::NULL;
|
||||
active.base_ucs_handle = acadrust::Handle::NULL;
|
||||
}
|
||||
active_changed = true;
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
.push_output(crate::tf!("UCS '{}' deleted.", name).as_ref());
|
||||
@@ -253,14 +292,73 @@ impl OpenCADStudio {
|
||||
self.command_line
|
||||
.push_output(crate::t!("UCS reset to World Coordinate System.").as_ref());
|
||||
}
|
||||
"VIEW" | "V" => {
|
||||
let rotation = self.tabs[i].scene.active_camera_rotation();
|
||||
let origin = self.tabs[i]
|
||||
.active_ucs
|
||||
.as_ref()
|
||||
.map(|ucs| ucs.origin)
|
||||
.unwrap_or(Vector3::ZERO);
|
||||
let x = rotation * glam::Vec3::X;
|
||||
let y = rotation * glam::Vec3::Y;
|
||||
let mut ucs = Ucs::new("*VIEW*");
|
||||
ucs.origin = origin;
|
||||
ucs.x_axis = Vector3::new(x.x as f64, x.y as f64, x.z as f64);
|
||||
ucs.y_axis = Vector3::new(y.x as f64, y.y as f64, y.z as f64);
|
||||
self.tabs[i].active_ucs = Some(ucs);
|
||||
active_changed = true;
|
||||
self.command_line
|
||||
.push_output(crate::t!("UCS aligned to the current view.").as_ref());
|
||||
}
|
||||
"3POINTW" => {
|
||||
let raw = parts.get(2).copied().unwrap_or("");
|
||||
let points: Vec<glam::DVec3> = raw
|
||||
.split('|')
|
||||
.filter_map(|value| {
|
||||
super::super::helpers::parse_coord(value).map(|(point, _)| point)
|
||||
})
|
||||
.collect();
|
||||
if points.len() == 3 {
|
||||
let x = (points[1] - points[0]).normalize_or_zero();
|
||||
let toward_y = points[2] - points[0];
|
||||
let z = x.cross(toward_y).normalize_or_zero();
|
||||
let y = z.cross(x).normalize_or_zero();
|
||||
if x.length_squared() > 1e-12
|
||||
&& y.length_squared() > 1e-12
|
||||
&& z.length_squared() > 1e-12
|
||||
{
|
||||
let mut ucs = Ucs::new("*ACTIVE*");
|
||||
ucs.origin = Vector3::new(points[0].x, points[0].y, points[0].z);
|
||||
ucs.x_axis = Vector3::new(x.x, x.y, x.z);
|
||||
ucs.y_axis = Vector3::new(y.x, y.y, y.z);
|
||||
self.tabs[i].active_ucs = Some(ucs);
|
||||
active_changed = true;
|
||||
self.command_line.push_output(
|
||||
crate::t!("UCS defined from three points.").as_ref(),
|
||||
);
|
||||
} else {
|
||||
self.command_line.push_error(
|
||||
crate::t!("UCS points must define two non-collinear axes.")
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.command_line.push_error(
|
||||
crate::t!("UCS requires origin, X-axis point and XY-plane point.")
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// UCS ORIGIN x,y,z — shift the active UCS origin, keep axes
|
||||
"ORIGIN" | "O" => {
|
||||
"ORIGIN" | "O" | "ORIGINW" => {
|
||||
let coord_str = parts.get(2).copied().unwrap_or("");
|
||||
if let Some((pt, _)) = super::super::helpers::parse_coord(coord_str) {
|
||||
// `pt` is in current UCS space; convert to WCS.
|
||||
// The @/# relative-coordinate prefix is ignored
|
||||
// here — a UCS origin is always absolute.
|
||||
let wcs_origin = if let Some(ref ucs) = self.tabs[i].active_ucs {
|
||||
let wcs_origin = if sub == "ORIGINW" {
|
||||
pt
|
||||
} else if let Some(ref ucs) = self.tabs[i].active_ucs {
|
||||
ucs_to_wcs(pt, ucs)
|
||||
} else {
|
||||
pt
|
||||
@@ -401,9 +499,6 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
// ── Named Views (VIEW command) ────────────────────────────────
|
||||
// PLAN — look straight down at the drawing (top view). The optional
|
||||
// World/Ucs/Current keyword is accepted; all map to the world top
|
||||
// view for now.
|
||||
// PLAN — plan view of a UCS (#326). Shows its options right on
|
||||
// activation instead of flipping the view like a cube click; the
|
||||
// view only changes once an option (or the Current default) runs.
|
||||
|
||||
@@ -1787,6 +1787,14 @@ impl OpenCADStudio {
|
||||
{
|
||||
self.tabs[i].active_layer = new_name.clone();
|
||||
}
|
||||
if type_str == "UCS" {
|
||||
if let Some(active) = self.tabs[i].active_ucs.as_mut() {
|
||||
if active.name.eq_ignore_ascii_case(&old_name) {
|
||||
active.name = new_name.clone();
|
||||
self.tabs[i].persist_active_ucs();
|
||||
}
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
.push_output(crate::tf!("RENAME: '{}' → '{}'.", old_name, new_name).as_ref());
|
||||
@@ -2357,7 +2365,15 @@ fn rename_symbol(doc: &mut acadrust::CadDocument, ty: &str, old: &str, new: &str
|
||||
}
|
||||
true
|
||||
}
|
||||
"UCS" => rekey(&mut doc.ucss, old, new),
|
||||
"UCS" => {
|
||||
if !rekey(&mut doc.ucss, old, new) {
|
||||
return false;
|
||||
}
|
||||
if doc.header.model_space_ucs_name.eq_ignore_ascii_case(old) {
|
||||
doc.header.model_space_ucs_name = new.to_string();
|
||||
}
|
||||
true
|
||||
}
|
||||
"VIEW" => rekey(&mut doc.views, old, new),
|
||||
_ => false,
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user