forked from 1b-agent/opencadstudio-upstream
Xref attach: placement preview, relative paths on the first save, no-match messages
The reference follows the cursor while its insertion point, scale or rotation is asked (preview-only PScale/PX/PY/PZ/PRotate included); a relative path chosen before the drawing had a file becomes relative on the first save, and relative paths start from "./" at or below the drawing's folder; -XREF name options report "No matching xref names found.". Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
6bd55bc32e
commit
498a97df0f
8 files changed
+204
-7
No files matched your search
@@ -136,6 +136,13 @@ pub struct XAttachCommand {
|
||||
/// Degrees.
|
||||
rotation: f64,
|
||||
rotation_fixed: bool,
|
||||
/// Ghost of the reference's model space in its own coordinates.
|
||||
preview: Vec<WireModel>,
|
||||
/// Drawing units per reference unit; the INSERT's scale carries it.
|
||||
unit: f64,
|
||||
/// PScale / PX / PY / PZ / PRotate: preview-only values.
|
||||
preview_scale: [f64; 3],
|
||||
preview_rotation: f64,
|
||||
}
|
||||
|
||||
const NONZERO: &str = "Value must be nonzero.";
|
||||
@@ -154,6 +161,10 @@ impl XAttachCommand {
|
||||
scale_fixed: placement.scale.is_some(),
|
||||
rotation: placement.rotation.unwrap_or(0.0),
|
||||
rotation_fixed: placement.rotation.is_some(),
|
||||
preview: Vec::new(),
|
||||
unit: 1.0,
|
||||
preview_scale: [1.0; 3],
|
||||
preview_rotation: 0.0,
|
||||
};
|
||||
if command.point.is_some() {
|
||||
command.step = command.next_step().unwrap_or(Step::Rotation);
|
||||
@@ -175,6 +186,44 @@ impl XAttachCommand {
|
||||
)
|
||||
}
|
||||
|
||||
/// Show `wires` (the reference's model space) while placing it.
|
||||
pub fn with_preview(mut self, wires: Vec<WireModel>, unit: f64) -> Self {
|
||||
self.preview = wires;
|
||||
self.unit = if unit.is_finite() && unit != 0.0 { unit } else { 1.0 };
|
||||
self
|
||||
}
|
||||
|
||||
/// Placement shown for the cursor at `cursor` (local): insertion point,
|
||||
/// scale and rotation (degrees) as far as they are known at this step.
|
||||
fn preview_placement(&self, cursor: DVec3) -> (DVec3, [f64; 3], f64) {
|
||||
let rotation = if self.rotation_fixed {
|
||||
self.rotation
|
||||
} else {
|
||||
self.preview_rotation
|
||||
};
|
||||
let base = self.point.unwrap_or(cursor);
|
||||
match self.step {
|
||||
Step::Insert | Step::Preset(_) => {
|
||||
let scale = if self.scale_fixed { self.scale } else { self.preview_scale };
|
||||
(cursor, scale, rotation)
|
||||
}
|
||||
Step::XScale | Step::Corner | Step::XyzX => {
|
||||
let (dx, dy) = (cursor.x - base.x, cursor.y - base.y);
|
||||
let scale = if dx.abs() > 1e-12 && dy.abs() > 1e-12 {
|
||||
[dx, dy, dx.abs()]
|
||||
} else {
|
||||
[1.0; 3]
|
||||
};
|
||||
(base, scale, rotation)
|
||||
}
|
||||
Step::Rotation => {
|
||||
let angle = (cursor.y - base.y).atan2(cursor.x - base.x).to_degrees();
|
||||
(base, self.scale, angle)
|
||||
}
|
||||
_ => (base, self.scale, rotation),
|
||||
}
|
||||
}
|
||||
|
||||
/// The finished INSERT when nothing is left to ask (all values fixed in
|
||||
/// the dialog).
|
||||
pub fn immediate(&self) -> Option<EntityType> {
|
||||
@@ -357,7 +406,11 @@ impl CadCommand for XAttachCommand {
|
||||
}
|
||||
// Preview-only values: the reference asks the real ones
|
||||
// after the insertion point as usual.
|
||||
Preset::PScale | Preset::PX | Preset::PY | Preset::PZ | Preset::PRotate => {}
|
||||
Preset::PScale => self.preview_scale = [value; 3],
|
||||
Preset::PX => self.preview_scale[0] = value,
|
||||
Preset::PY => self.preview_scale[1] = value,
|
||||
Preset::PZ => self.preview_scale[2] = value,
|
||||
Preset::PRotate => self.preview_rotation = value,
|
||||
}
|
||||
self.step = Step::Insert;
|
||||
Some(CmdResult::NeedPoint)
|
||||
@@ -451,8 +504,19 @@ impl CadCommand for XAttachCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_preview_wires(&mut self, _pt: DVec3) -> Vec<WireModel> {
|
||||
vec![]
|
||||
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> {
|
||||
if self.preview.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let (origin, scale, degrees) = self.preview_placement(self.plane.to_local(pt));
|
||||
let (sin, cos) = degrees.to_radians().sin_cos();
|
||||
let unit = self.unit;
|
||||
let plane = self.plane;
|
||||
let place = |p: DVec3| {
|
||||
let (x, y, z) = (p.x * scale[0] * unit, p.y * scale[1] * unit, p.z * scale[2] * unit);
|
||||
plane.to_world(origin + DVec3::new(x * cos - y * sin, x * sin + y * cos, z))
|
||||
};
|
||||
self.preview.iter().map(|wire| wire.mapped(place)).collect()
|
||||
}
|
||||
|
||||
fn xattach_request(&self) -> Option<XrefAttachRequest> {
|
||||
|
||||
@@ -36,6 +36,13 @@ impl XrefCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether any reference name matches `pattern` (`*` / `?` wildcards).
|
||||
fn matches(&self, pattern: &str) -> bool {
|
||||
self.saved_paths
|
||||
.iter()
|
||||
.any(|(name, _)| crate::io::xref_model::wildcard_match(name, pattern))
|
||||
}
|
||||
|
||||
fn choose(&mut self, token: &str) -> CmdResult {
|
||||
self.step = match token {
|
||||
"?" => Step::List,
|
||||
@@ -118,6 +125,15 @@ impl CadCommand for XrefCommand {
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
let value = text.trim().trim_matches('"').to_string();
|
||||
let token = value.to_uppercase();
|
||||
let names_step = matches!(
|
||||
self.step,
|
||||
Step::Names(_) | Step::PathTypeNames | Step::ShowNames
|
||||
);
|
||||
if names_step && !self.matches(&value) {
|
||||
return Some(CmdResult::Measurement(
|
||||
"No matching xref names found.".to_string(),
|
||||
));
|
||||
}
|
||||
Some(match self.step.clone() {
|
||||
Step::Option => self.choose(&token),
|
||||
Step::List => CmdResult::Dispatch(format!("XREFLIST {value}")),
|
||||
@@ -133,7 +149,7 @@ impl CadCommand for XrefCommand {
|
||||
.find(|(name, _)| crate::io::xref_model::wildcard_match(name, &value))
|
||||
.cloned()
|
||||
else {
|
||||
return Some(CmdResult::CancelWithMessage(
|
||||
return Some(CmdResult::Measurement(
|
||||
"No matching xref names found.".to_string(),
|
||||
));
|
||||
};
|
||||
@@ -171,7 +187,7 @@ impl CadCommand for XrefCommand {
|
||||
Step::Option => self.choose("A"),
|
||||
Step::List => CmdResult::Dispatch("XREFLIST *".to_string()),
|
||||
Step::NewPath { .. } => {
|
||||
CmdResult::CancelWithMessage("Path unchanged.".to_string())
|
||||
CmdResult::Measurement("Path unchanged.".to_string())
|
||||
}
|
||||
Step::PathTypeKind { .. } => CmdResult::NeedPoint,
|
||||
_ => CmdResult::Cancel,
|
||||
|
||||
Reference in new issue
Block a user