forked from 1b-agent/opencadstudio-upstream
Merge origin/main
This commit is contained in:
commit
de4387f9dc
8 files changed
+172
-30
No files matched your search
@@ -2403,4 +2403,39 @@ mod find_replace_command_tests {
|
||||
let _ = app.dispatch_inquiry(cmd, i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_command_respects_drawing_units_precision() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
let i = app.active_tab;
|
||||
// Simulate changing linear precision to 0 via UNITS dialog apply
|
||||
app.drawing_units = Some(crate::ui::window::drawing_units::State {
|
||||
linear_format: 2,
|
||||
linear_precision: 0,
|
||||
angular_format: 0,
|
||||
angular_precision: 0,
|
||||
clockwise: false,
|
||||
base_angle: "0".into(),
|
||||
insertion_units: 4,
|
||||
});
|
||||
let _ = app.update(Message::DrawingUnitsApply);
|
||||
|
||||
// Run DIST command
|
||||
let _ = app.dispatch_inquiry("DIST", i);
|
||||
assert!(app.tabs[i].active_cmd.is_some());
|
||||
|
||||
// First point
|
||||
let _ = app.tabs[i].active_cmd.as_mut().unwrap().on_point(glam::DVec3::new(0.0, 0.0, 0.0));
|
||||
// Second point
|
||||
let res = app.tabs[i].active_cmd.as_mut().unwrap().on_point(glam::DVec3::new(10.0, 0.0, 0.0));
|
||||
match res {
|
||||
crate::command::CmdResult::Measurement(msg) => {
|
||||
assert!(msg.contains("Distance = 10"), "Expected 'Distance = 10', got: {msg}");
|
||||
assert!(msg.contains("Delta X = 10"), "Expected 'Delta X = 10', got: {msg}");
|
||||
assert!(msg.contains("Delta Y = 0"), "Expected 'Delta Y = 0', got: {msg}");
|
||||
assert!(msg.contains("Delta Z = 0"), "Expected 'Delta Z = 0', got: {msg}");
|
||||
}
|
||||
_ => panic!("Expected CmdResult::Measurement"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2505,6 +2505,13 @@ impl OpenCADStudio {
|
||||
if name == "PDMODE" || name == "PDSIZE" {
|
||||
self.tabs[i].scene.invalidate_point_dependencies();
|
||||
}
|
||||
if matches!(name.as_str(), "LUNITS" | "LUPREC" | "AUNITS" | "AUPREC") {
|
||||
crate::entities::common::set_unit_context(
|
||||
crate::entities::common::UnitContext::from_header(
|
||||
&self.tabs[i].scene.document.header,
|
||||
),
|
||||
);
|
||||
}
|
||||
// ORTHOMODE / OSMODE set the header directly; mirror them into
|
||||
// the live Ortho / running OSNAP so the constraint + status
|
||||
// bar follow and the save-time stamp doesn't revert them.
|
||||
|
||||
@@ -283,6 +283,11 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
pub fn update(&mut self, msg: Message) -> Task<Message> {
|
||||
if let Some(tab) = self.tabs.get(self.active_tab) {
|
||||
crate::entities::common::set_unit_context(
|
||||
crate::entities::common::UnitContext::from_header(&tab.scene.document.header),
|
||||
);
|
||||
}
|
||||
self.control_observe_user_message(&msg);
|
||||
let perf_started = crate::perf::enabled().then(Instant::now);
|
||||
let perf_label = perf_message_label(&msg);
|
||||
@@ -4596,6 +4601,9 @@ impl OpenCADStudio {
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.refresh_properties();
|
||||
crate::entities::common::set_unit_context(
|
||||
crate::entities::common::UnitContext::from_header(&self.tabs[i].scene.document.header),
|
||||
);
|
||||
Task::none()
|
||||
}
|
||||
Message::ToleranceDialogField(field) => {
|
||||
@@ -4646,6 +4654,9 @@ impl OpenCADStudio {
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.refresh_properties();
|
||||
crate::entities::common::set_unit_context(
|
||||
crate::entities::common::UnitContext::from_header(&self.tabs[i].scene.document.header),
|
||||
);
|
||||
let label = crate::modules::draw::units::linear_format_label(code);
|
||||
self.command_line
|
||||
.push_output(crate::tf!("Length format is now {label}.").as_ref());
|
||||
|
||||
@@ -108,7 +108,7 @@ pub fn format_length(value: f64) -> String {
|
||||
|
||||
/// Drop the sign from a formatted number whose digits all rounded to zero. A
|
||||
/// rotation leaves coordinates like -1e-15 behind, which read as `-0.0000`.
|
||||
fn without_negative_zero(text: String) -> String {
|
||||
pub fn without_negative_zero(text: String) -> String {
|
||||
match text.strip_prefix('-') {
|
||||
Some(rest)
|
||||
if rest.chars().any(|c| c.is_ascii_digit())
|
||||
|
||||
@@ -169,10 +169,13 @@ impl AreaCommand {
|
||||
match measurement.perimeter {
|
||||
Some(perimeter) => crate::tr!(
|
||||
"area", "result",
|
||||
area = format!("{:.4}", measurement.area),
|
||||
perimeter = format!("{perimeter:.4}"),
|
||||
area = crate::entities::common::format_area(measurement.area),
|
||||
perimeter = crate::entities::common::format_length(perimeter),
|
||||
),
|
||||
None => crate::tr!(
|
||||
"area", "result-area-only",
|
||||
area = crate::entities::common::format_area(measurement.area),
|
||||
),
|
||||
None => crate::tr!("area", "result-area-only", area = format!("{:.4}", measurement.area)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,15 +183,15 @@ impl AreaCommand {
|
||||
match measurement.perimeter {
|
||||
Some(perimeter) => crate::tr!(
|
||||
"area", "running-result",
|
||||
area = format!("{:.4}", measurement.area),
|
||||
perimeter = format!("{perimeter:.4}"),
|
||||
total_area = format!("{:.4}", self.total_area),
|
||||
total_perimeter = format!("{:.4}", self.total_perimeter),
|
||||
area = crate::entities::common::format_area(measurement.area),
|
||||
perimeter = crate::entities::common::format_length(perimeter),
|
||||
total_area = crate::entities::common::format_area(self.total_area),
|
||||
total_perimeter = crate::entities::common::format_length(self.total_perimeter),
|
||||
),
|
||||
None => crate::tr!(
|
||||
"area", "running-result-area-only",
|
||||
area = format!("{:.4}", measurement.area),
|
||||
total_area = format!("{:.4}", self.total_area),
|
||||
area = crate::entities::common::format_area(measurement.area),
|
||||
total_area = crate::entities::common::format_area(self.total_area),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use glam::DVec3;
|
||||
use crate::t;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult, WorkingPlane};
|
||||
use crate::entities::common::{format_length, unit_context, without_negative_zero};
|
||||
use crate::modules::IconKind;
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
pub struct DistCommand {
|
||||
@@ -55,12 +56,13 @@ impl CadCommand for DistCommand {
|
||||
let dist_xy = dx.hypot(dy);
|
||||
let angle_z = dz.atan2(dist_xy).to_degrees();
|
||||
|
||||
let dist_s = format!("{dist:.4}");
|
||||
let angle_xy_s = format!("{angle_xy:.4}");
|
||||
let angle_z_s = format!("{angle_z:.4}");
|
||||
let dx_s = format!("{dx:.4}");
|
||||
let dy_s = format!("{dy:.4}");
|
||||
let dz_s = format!("{dz:.4}");
|
||||
let aprec = unit_context().auprec.max(0) as usize;
|
||||
let dist_s = format_length(dist);
|
||||
let angle_xy_s = without_negative_zero(format!("{angle_xy:.aprec$}"));
|
||||
let angle_z_s = without_negative_zero(format!("{angle_z:.aprec$}"));
|
||||
let dx_s = format_length(dx);
|
||||
let dy_s = format_length(dy);
|
||||
let dz_s = format_length(dz);
|
||||
let msg = t!(
|
||||
"Distance = %{dist}, Angle in XY Plane = %{angle_xy}°, Angle from XY Plane = %{angle_z}°\n Delta X = %{dx}, Delta Y = %{dy}, Delta Z = %{dz}",
|
||||
dist = dist_s,
|
||||
@@ -128,3 +130,84 @@ impl CadCommand for DistCommand {
|
||||
|
||||
// ── Autocomplete registry ─────────────────────────────────
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["DIST"] }); // DistCommand
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::command::CmdResult;
|
||||
use crate::entities::common::{set_unit_context, UnitContext};
|
||||
|
||||
#[test]
|
||||
fn dist_respects_precision_zero() {
|
||||
let mut cmd = DistCommand::new();
|
||||
set_unit_context(UnitContext {
|
||||
lunits: 2,
|
||||
luprec: 0,
|
||||
aunits: 0,
|
||||
auprec: 0,
|
||||
angbase: 0.0,
|
||||
angdir_cw: false,
|
||||
});
|
||||
|
||||
assert!(matches!(cmd.on_point(DVec3::new(0.0, 0.0, 0.0)), CmdResult::NeedPoint));
|
||||
let res = cmd.on_point(DVec3::new(10.0, 0.0, 0.0));
|
||||
match res {
|
||||
CmdResult::Measurement(msg) => {
|
||||
assert!(msg.contains("Distance = 10"), "Expected 'Distance = 10' in '{msg}'");
|
||||
assert!(msg.contains("Delta X = 10"), "Expected 'Delta X = 10' in '{msg}'");
|
||||
assert!(msg.contains("Delta Y = 0"), "Expected 'Delta Y = 0' in '{msg}'");
|
||||
assert!(msg.contains("Delta Z = 0"), "Expected 'Delta Z = 0' in '{msg}'");
|
||||
assert!(msg.contains("Angle in XY Plane = 0°"), "Expected 'Angle in XY Plane = 0°' in '{msg}'");
|
||||
}
|
||||
_ => panic!("Expected CmdResult::Measurement"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_respects_precision_four() {
|
||||
let mut cmd = DistCommand::new();
|
||||
set_unit_context(UnitContext {
|
||||
lunits: 2,
|
||||
luprec: 4,
|
||||
aunits: 0,
|
||||
auprec: 2,
|
||||
angbase: 0.0,
|
||||
angdir_cw: false,
|
||||
});
|
||||
|
||||
assert!(matches!(cmd.on_point(DVec3::new(0.0, 0.0, 0.0)), CmdResult::NeedPoint));
|
||||
let res = cmd.on_point(DVec3::new(10.0, 5.0, 0.0));
|
||||
match res {
|
||||
CmdResult::Measurement(msg) => {
|
||||
assert!(msg.contains("Distance = 11.1803"), "Expected 'Distance = 11.1803' in '{msg}'");
|
||||
assert!(msg.contains("Delta X = 10.0000"), "Expected 'Delta X = 10.0000' in '{msg}'");
|
||||
assert!(msg.contains("Delta Y = 5.0000"), "Expected 'Delta Y = 5.0000' in '{msg}'");
|
||||
assert!(msg.contains("Angle in XY Plane = 26.57°"), "Expected 'Angle in XY Plane = 26.57°' in '{msg}'");
|
||||
}
|
||||
_ => panic!("Expected CmdResult::Measurement"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_respects_architectural_units() {
|
||||
let mut cmd = DistCommand::new();
|
||||
set_unit_context(UnitContext {
|
||||
lunits: 4, // Architectural
|
||||
luprec: 4,
|
||||
aunits: 0,
|
||||
auprec: 0,
|
||||
angbase: 0.0,
|
||||
angdir_cw: false,
|
||||
});
|
||||
|
||||
assert!(matches!(cmd.on_point(DVec3::new(0.0, 0.0, 0.0)), CmdResult::NeedPoint));
|
||||
let res = cmd.on_point(DVec3::new(18.0, 0.0, 0.0));
|
||||
match res {
|
||||
CmdResult::Measurement(msg) => {
|
||||
// 18 inches = 1'-6"
|
||||
assert!(msg.contains("1'-6\""), "Expected 1'-6\" in '{msg}'");
|
||||
}
|
||||
_ => panic!("Expected CmdResult::Measurement"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,9 +35,9 @@ impl CadCommand for IdCommand {
|
||||
let x = local.x;
|
||||
let y = local.y;
|
||||
let z = local.z;
|
||||
let x_s = format!("{x:.4}");
|
||||
let y_s = format!("{y:.4}");
|
||||
let z_s = format!("{z:.4}");
|
||||
let x_s = crate::entities::common::format_length(x);
|
||||
let y_s = crate::entities::common::format_length(y);
|
||||
let z_s = crate::entities::common::format_length(z);
|
||||
let msg = t!(
|
||||
"X = %{x}, Y = %{y}, Z = %{z}",
|
||||
x = x_s,
|
||||
|
||||
@@ -124,11 +124,12 @@ impl MeasureGeomCommand {
|
||||
let dy = delta.y;
|
||||
let dz = delta.z;
|
||||
let angle_xy = dy.atan2(dx).to_degrees();
|
||||
let dist_s = format!("{dist:.4}");
|
||||
let angle_xy_s = format!("{angle_xy:.4}");
|
||||
let dx_s = format!("{dx:.4}");
|
||||
let dy_s = format!("{dy:.4}");
|
||||
let dz_s = format!("{dz:.4}");
|
||||
let aprec = crate::entities::common::unit_context().auprec.max(0) as usize;
|
||||
let dist_s = crate::entities::common::format_length(dist);
|
||||
let angle_xy_s = crate::entities::common::without_negative_zero(format!("{angle_xy:.aprec$}"));
|
||||
let dx_s = crate::entities::common::format_length(dx);
|
||||
let dy_s = crate::entities::common::format_length(dy);
|
||||
let dz_s = crate::entities::common::format_length(dz);
|
||||
t!(
|
||||
"Distance = %{dist}, Angle in XY Plane = %{angle_xy}°\n Delta X = %{dx}, Delta Y = %{dy}, Delta Z = %{dz}",
|
||||
dist = dist_s,
|
||||
@@ -154,8 +155,8 @@ impl MeasureGeomCommand {
|
||||
perimeter += (points[(idx + 1) % n] - points[idx]).length();
|
||||
}
|
||||
let area = (area_sum * 0.5).abs();
|
||||
let area_s = format!("{area:.4}");
|
||||
let perimeter_s = format!("{perimeter:.4}");
|
||||
let area_s = crate::entities::common::format_area(area);
|
||||
let perimeter_s = crate::entities::common::format_length(perimeter);
|
||||
t!(
|
||||
"Area = %{area}, Perimeter = %{perimeter}",
|
||||
area = area_s,
|
||||
@@ -170,12 +171,14 @@ impl MeasureGeomCommand {
|
||||
let vb = b - vertex;
|
||||
let la = va.length();
|
||||
let lb = vb.length();
|
||||
let aprec = crate::entities::common::unit_context().auprec.max(0) as usize;
|
||||
if la == 0.0 || lb == 0.0 {
|
||||
return t!("Angle = 0.0000° (degenerate rays)").into_owned();
|
||||
let zero = crate::entities::common::without_negative_zero(format!("{:.aprec$}", 0.0));
|
||||
return t!("Angle = %{angle}° (degenerate rays)", angle = zero).into_owned();
|
||||
}
|
||||
let cos = (va.dot(vb) / (la * lb)).clamp(-1.0, 1.0);
|
||||
let angle = cos.acos().to_degrees();
|
||||
let angle_s = format!("{angle:.4}");
|
||||
let angle_s = crate::entities::common::without_negative_zero(format!("{angle:.aprec$}"));
|
||||
t!("Angle = %{angle}°", angle = angle_s).into_owned()
|
||||
}
|
||||
}
|
||||
@@ -274,8 +277,8 @@ impl CadCommand for MeasureGeomCommand {
|
||||
match self.picked.as_ref().and_then(Self::radius_of) {
|
||||
Some(radius) => {
|
||||
let diameter = radius * 2.0;
|
||||
let radius_s = format!("{radius:.4}");
|
||||
let diameter_s = format!("{diameter:.4}");
|
||||
let radius_s = crate::entities::common::format_length(radius);
|
||||
let diameter_s = crate::entities::common::format_length(diameter);
|
||||
CmdResult::Measurement(
|
||||
t!(
|
||||
"Radius = %{radius}, Diameter = %{diameter}",
|
||||
|
||||
Reference in new issue
Block a user