forked from 1b-agent/opencadstudio-upstream
fix: UCS FACE/OBJECT pick from the interactive prompt; reuse ocs_axes
Follow-up to #1341: - interactive UCS offers Face / OBject; bare UCS OBJECT starts an entity pick - one UcsPickCommand serves both FACE and OBJECT - ucs_from_normal calls the existing arbitrary-axis helper instead of a copy - prompt catalogue key and en-US text follow the new option list Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
3ba4ab9e7f
commit
9978713acc
7 files changed
+50
-43
No files matched your search
@@ -4198,7 +4198,7 @@ view =
|
||||
.cmd-not-yet-implemented = __ocs_fmt_0__: not yet implemented.
|
||||
.viewport-s-in-layout = __ocs_fmt_0__ viewport(s) in layout "__ocs_fmt_1__":
|
||||
|
||||
.ucs-option-world-view-3point-z-x-y-origin-save-delete-or-name = UCS option [World/View/3Point/Z/X/Y/Origin/Save/Delete] or name:
|
||||
.ucs-option-world-view-3point-z-x-y-origin-save-delete-or-name = UCS option [Face/OBject/World/View/3Point/Z/X/Y/Origin/Save/Delete] or name:
|
||||
.ucs-specify-new-origin = UCS specify new origin:
|
||||
.ucs-specify-point-on-positive-x-axis = UCS specify point on positive X axis:
|
||||
.ucs-specify-point-in-positive-xy-plane = UCS specify point in positive XY plane:
|
||||
|
||||
@@ -338,7 +338,7 @@ impl OpenCADStudio {
|
||||
// surface point, so both entry points share the
|
||||
// plane construction below.
|
||||
None => {
|
||||
let picker = crate::command::UcsFaceCommand;
|
||||
let picker = crate::command::UcsPickCommand { face: true };
|
||||
self.command_line.push_info(&picker.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(picker));
|
||||
}
|
||||
@@ -386,9 +386,13 @@ impl OpenCADStudio {
|
||||
})
|
||||
.map(acadrust::Handle::new);
|
||||
match target {
|
||||
None => self
|
||||
.command_line
|
||||
.push_error(crate::t!("Usage: UCS OBJECT <handle>").as_ref()),
|
||||
// Bare `UCS OBJECT` starts the pick; the click
|
||||
// comes back through this arm with the handle.
|
||||
None => {
|
||||
let picker = crate::command::UcsPickCommand { face: false };
|
||||
self.command_line.push_info(&picker.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(picker));
|
||||
}
|
||||
Some(handle) => {
|
||||
let built = self.tabs[i]
|
||||
.scene
|
||||
|
||||
@@ -739,9 +739,10 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"SELSIM",
|
||||
// Draw a new object of the same type as the selected one. (#239)
|
||||
"ADDSELECTED",
|
||||
// Outline of origin planes, open sketch and solid bodies.
|
||||
"BROWSER",
|
||||
// Open and close a sketch: a drawing plane, a square-on view and
|
||||
// snapping, held together until the profile is done.
|
||||
"BROWSER",
|
||||
"CREATESKETCH",
|
||||
"FINISHSKETCH",
|
||||
"SHEETSET",
|
||||
|
||||
+7
-22
@@ -249,35 +249,20 @@ pub(super) fn ucs_z_axis(ucs: &Ucs) -> glam::DVec3 {
|
||||
|
||||
/// Build a UCS whose Z axis is `normal`, anchored at `origin`.
|
||||
///
|
||||
/// X and Y come from the Arbitrary Axis Algorithm, the same rule DWG uses to
|
||||
/// turn an extrusion direction into an object coordinate system. Picking the
|
||||
/// world axis that is *least* parallel to the normal keeps the cross product
|
||||
/// well conditioned, and makes the result stable: the same face always yields
|
||||
/// the same X axis, so a sketch drawn on it does not spin when the UCS is
|
||||
/// rebuilt.
|
||||
///
|
||||
/// Returns `None` when `normal` is degenerate and defines no plane.
|
||||
/// X and Y come from the arbitrary-axis algorithm in
|
||||
/// [`crate::scene::view::transform::ocs_axes`], the same frame entities get
|
||||
/// from their extrusion direction, so the same face always yields the same X
|
||||
/// axis. Returns `None` when `normal` is degenerate and defines no plane.
|
||||
pub(super) fn ucs_from_normal(origin: glam::DVec3, normal: glam::DVec3) -> Option<Ucs> {
|
||||
let z = normal.normalize_or_zero();
|
||||
if z.length_squared() < 1e-12 {
|
||||
return None;
|
||||
}
|
||||
// 1/64 is the threshold the DWG spec names for "close enough to the world
|
||||
// Z axis that using it as the reference would be ill conditioned".
|
||||
let reference = if z.x.abs() < 1.0 / 64.0 && z.y.abs() < 1.0 / 64.0 {
|
||||
glam::DVec3::Y
|
||||
} else {
|
||||
glam::DVec3::Z
|
||||
};
|
||||
let x = reference.cross(z).normalize_or_zero();
|
||||
if x.length_squared() < 1e-12 {
|
||||
return None;
|
||||
}
|
||||
let y = z.cross(x).normalize_or_zero();
|
||||
let ((xx, xy, xz), (yx, yy, yz)) = crate::scene::view::transform::ocs_axes((z.x, z.y, z.z));
|
||||
let mut ucs = Ucs::new("*ACTIVE*");
|
||||
ucs.origin = acadrust::types::Vector3::new(origin.x, origin.y, origin.z);
|
||||
ucs.x_axis = acadrust::types::Vector3::new(x.x, x.y, x.z);
|
||||
ucs.y_axis = acadrust::types::Vector3::new(y.x, y.y, y.z);
|
||||
ucs.x_axis = acadrust::types::Vector3::new(xx, xy, xz);
|
||||
ucs.y_axis = acadrust::types::Vector3::new(yx, yy, yz);
|
||||
Some(ucs)
|
||||
}
|
||||
|
||||
|
||||
+21
-11
@@ -347,23 +347,30 @@ impl CadCommand for ValuePromptCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// Interactive front-end for `UCS FACE` — Fusion's "create sketch on a face".
|
||||
/// Interactive pick for `UCS FACE` and `UCS OBJECT`.
|
||||
///
|
||||
/// Picks one face of a solid and hands the resulting plane to the inline
|
||||
/// `UCS FACE <handle> <x,y,z>` handler via [`CmdResult::Dispatch`], so the
|
||||
/// plane construction lives in exactly one place whether the user clicked a
|
||||
/// face or typed the arguments. `entity_pick_uses_surface_point` is what
|
||||
/// makes the click land *on the solid's surface* rather than on the current
|
||||
/// drawing plane, which is the whole point: the pick coordinate is the face.
|
||||
pub struct UcsFaceCommand;
|
||||
/// Hands the picked entity to the inline `UCS FACE <handle> <x,y,z>` /
|
||||
/// `UCS OBJECT <handle>` handler via [`CmdResult::Dispatch`], so the plane
|
||||
/// construction lives in one place whether the user clicked or typed the
|
||||
/// arguments. For a face the click lands *on the solid's surface*
|
||||
/// (`entity_pick_uses_surface_point`) and that point becomes the plane
|
||||
/// origin; for an object only the handle matters.
|
||||
pub struct UcsPickCommand {
|
||||
/// `true` picks a solid face; `false` picks a planar entity.
|
||||
pub face: bool,
|
||||
}
|
||||
|
||||
impl CadCommand for UcsFaceCommand {
|
||||
impl CadCommand for UcsPickCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"UCS"
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
crate::t!("UCS FACE Select a face to draw on:").into_owned()
|
||||
if self.face {
|
||||
crate::t!("UCS FACE Select a face to draw on:").into_owned()
|
||||
} else {
|
||||
crate::t!("UCS OBJECT Select object to align UCS:").into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
fn needs_entity_pick(&self) -> bool {
|
||||
@@ -371,7 +378,7 @@ impl CadCommand for UcsFaceCommand {
|
||||
}
|
||||
|
||||
fn entity_pick_uses_surface_point(&self) -> bool {
|
||||
true
|
||||
self.face
|
||||
}
|
||||
|
||||
fn entity_pick_highlights_hover(&self) -> bool {
|
||||
@@ -382,6 +389,9 @@ impl CadCommand for UcsFaceCommand {
|
||||
if handle.is_null() {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
if !self.face {
|
||||
return CmdResult::Dispatch(format!("UCS OBJECT {:X}", handle.value()));
|
||||
}
|
||||
// Hex handle and comma-separated coordinates are what the inline
|
||||
// parser reads back. Full `{}` precision, not a rounded format: the
|
||||
// point has to stay on the face for the planar-face lookup.
|
||||
|
||||
@@ -4149,7 +4149,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"TORUS Specify second end point of tube diameter:" => Some(("model", "torus-specify-second-end-point-of-tube-diameter")),
|
||||
"2Point" => Some(("model", "value-2point")),
|
||||
"Cube" => Some(("model", "cube")),
|
||||
"UCS option [World/View/3Point/Z/X/Y/Origin/Save/Delete] or name:" => Some(("view", "ucs-option-world-view-3point-z-x-y-origin-save-delete-or-name")),
|
||||
"UCS option [Face/OBject/World/View/3Point/Z/X/Y/Origin/Save/Delete] or name:" => Some(("view", "ucs-option-world-view-3point-z-x-y-origin-save-delete-or-name")),
|
||||
"UCS specify new origin:" => Some(("view", "ucs-specify-new-origin")),
|
||||
"UCS specify point on positive X axis:" => Some(("view", "ucs-specify-point-on-positive-x-axis")),
|
||||
"UCS specify point in positive XY plane:" => Some(("view", "ucs-specify-point-in-positive-xy-plane")),
|
||||
|
||||
@@ -24,9 +24,13 @@ impl UcsCommand {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Options that take no further argument and execute immediately.
|
||||
/// Options that take no further typed argument: they execute immediately
|
||||
/// or, for Face / OBject, start their own entity pick.
|
||||
fn is_zero_arg(opt: &str) -> bool {
|
||||
matches!(opt, "W" | "WORLD" | "VIEW" | "V" | "LIST" | "?")
|
||||
matches!(
|
||||
opt,
|
||||
"W" | "WORLD" | "VIEW" | "V" | "LIST" | "?" | "FACE" | "F" | "OBJECT" | "OB"
|
||||
)
|
||||
}
|
||||
|
||||
/// Options whose argument is a coordinate (so a click is accepted too).
|
||||
@@ -50,7 +54,10 @@ impl CadCommand for UcsCommand {
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
match self.option.as_deref() {
|
||||
None => t!("UCS option [World/View/3Point/Z/X/Y/Origin/Save/Delete] or name:").into_owned(),
|
||||
None => t!(
|
||||
"UCS option [Face/OBject/World/View/3Point/Z/X/Y/Origin/Save/Delete] or name:"
|
||||
)
|
||||
.into_owned(),
|
||||
Some("Z") => t!("UCS rotation angle about Z (degrees):").into_owned(),
|
||||
Some("X") => t!("UCS rotation angle about X (degrees):").into_owned(),
|
||||
Some("Y") => t!("UCS rotation angle about Y (degrees):").into_owned(),
|
||||
|
||||
Reference in new issue
Block a user