fix(blocks): preserve insert unit scaling
Compose unit conversion with authored transforms and attributes. Keep unit-factor display and the full INSUNITS mapping consistent. Refs #706
This commit is contained in:
parent
a65ba28270
commit
aab30f1b5e
1 changed files with 152 additions and 46 deletions
|
|
@ -3,8 +3,9 @@ use super::{OpenCADStudio, VARIES_LABEL};
|
|||
use crate::io::linetypes;
|
||||
use crate::scene::view::dispatch;
|
||||
use crate::ui;
|
||||
use acadrust::{EntityType, Handle};
|
||||
use crate::t;
|
||||
use acadrust::types::{Transform, Vector3};
|
||||
use acadrust::{Entity, EntityType, Handle};
|
||||
|
||||
/// Above this many selected objects the Properties panel skips per-entity
|
||||
/// property aggregation (which is O(n) per row, plus an O(n²) group filter) and
|
||||
|
|
@ -959,10 +960,8 @@ impl OpenCADStudio {
|
|||
.map(|br| br.units)
|
||||
.unwrap_or(0);
|
||||
set_row(&mut sections, "block_unit", insunits_name(src).to_string());
|
||||
let host_mm = if host == 0 { 1.0 } else { insunits_to_mm(host) };
|
||||
let src_mm = if src == 0 { 1.0 } else { insunits_to_mm(src) };
|
||||
let factor = if host_mm.abs() > 1e-12 { src_mm / host_mm } else { 1.0 };
|
||||
set_row(&mut sections, "unit_factor", format!("{factor:.4}"));
|
||||
let factor = insert_unit_scale(host, src).unwrap_or(1.0);
|
||||
set_row(&mut sections, "unit_factor", format_unit_factor(factor));
|
||||
|
||||
// Name row: editable for regular blocks — pick an
|
||||
// existing definition to re-point this reference, or
|
||||
|
|
@ -1805,9 +1804,12 @@ impl OpenCADStudio {
|
|||
.map(|br| br.units)
|
||||
.unwrap_or(0);
|
||||
if let Some(ratio) = insert_unit_scale(host_units, src_units) {
|
||||
ins.set_x_scale(ratio);
|
||||
ins.set_y_scale(ratio);
|
||||
ins.set_z_scale(ratio);
|
||||
if !apply_insert_unit_scale(ins, ratio) {
|
||||
self.command_line.push_error(
|
||||
t!("INSERT unit scale is outside the supported range.").as_ref(),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2370,60 +2372,89 @@ fn insunits_name(code: i16) -> &'static str {
|
|||
19 => "Light Years",
|
||||
20 => "Parsecs",
|
||||
21 => "US Survey Feet",
|
||||
_ => "Unitless",
|
||||
22 => "US Survey Inches",
|
||||
23 => "US Survey Yards",
|
||||
24 => "US Survey Miles",
|
||||
0 => "Unitless",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
/// Unit-conversion scale for a new INSERT, or `None` when the reference should
|
||||
/// keep scale 1.
|
||||
///
|
||||
/// A unitless (0) block carries no length to convert from, and a unitless
|
||||
/// drawing none to convert to, so either side being unitless means the
|
||||
/// reference is inserted as authored. This matches the `unit_factor` row shown
|
||||
/// for the same insert in the properties panel.
|
||||
/// Unit-conversion scale for a new INSERT.
|
||||
fn insert_unit_scale(host_units: i16, src_units: i16) -> Option<f64> {
|
||||
if host_units == 0 || src_units == 0 || src_units == host_units {
|
||||
return None;
|
||||
}
|
||||
let ratio = insunits_to_mm(src_units) / insunits_to_mm(host_units);
|
||||
let host_mm = insunits_to_mm(host_units)?;
|
||||
let src_mm = insunits_to_mm(src_units)?;
|
||||
let ratio = src_mm / host_mm;
|
||||
if !ratio.is_finite() || (ratio - 1.0).abs() <= 1e-9 {
|
||||
return None;
|
||||
}
|
||||
Some(ratio)
|
||||
}
|
||||
|
||||
/// Convert INSUNITS (DXF group 70) to millimetres.
|
||||
/// 0 = unitless / unknown: returns 1.0 so the caller treats it as "do not scale".
|
||||
fn insunits_to_mm(code: i16) -> f64 {
|
||||
match code {
|
||||
1 => 25.4, // Inches
|
||||
2 => 304.8, // Feet
|
||||
3 => 1_609_344.0, // Miles
|
||||
4 => 1.0, // Millimeters
|
||||
5 => 10.0, // Centimeters
|
||||
6 => 1_000.0, // Meters
|
||||
7 => 1_000_000.0, // Kilometers
|
||||
8 => 0.000_025_4, // Microinches
|
||||
9 => 0.025_4, // Mils
|
||||
10 => 914.4, // Yards
|
||||
11 => 1.0e-7, // Angstroms
|
||||
12 => 1.0e-6, // Nanometers
|
||||
13 => 0.001, // Microns
|
||||
14 => 100.0, // Decimeters
|
||||
15 => 10_000.0, // Decameters
|
||||
16 => 100_000.0, // Hectometers
|
||||
17 => 1.0e12, // Gigameters
|
||||
18 => 1.496e14, // Astronomical Units
|
||||
19 => 9.461e18, // Light Years
|
||||
20 => 3.086e19, // Parsecs
|
||||
21 => 304.800_609_6, // US Survey Feet
|
||||
_ => 1.0,
|
||||
fn format_unit_factor(factor: f64) -> String {
|
||||
let magnitude = factor.abs();
|
||||
if magnitude > 0.0 && !(1.0e-4..1.0e7).contains(&magnitude) {
|
||||
format!("{factor:.4e}")
|
||||
} else {
|
||||
format!("{factor:.4}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert INSUNITS (DXF group 70) to millimetres.
|
||||
fn insunits_to_mm(code: i16) -> Option<f64> {
|
||||
Some(match code {
|
||||
1 => 25.4, // Inches
|
||||
2 => 304.8, // Feet
|
||||
3 => 1_609_344.0, // Miles
|
||||
4 => 1.0, // Millimeters
|
||||
5 => 10.0, // Centimeters
|
||||
6 => 1_000.0, // Meters
|
||||
7 => 1_000_000.0, // Kilometers
|
||||
8 => 0.000_025_4, // Microinches
|
||||
9 => 0.025_4, // Mils
|
||||
10 => 914.4, // Yards
|
||||
11 => 1.0e-7, // Angstroms
|
||||
12 => 1.0e-6, // Nanometers
|
||||
13 => 0.001, // Microns
|
||||
14 => 100.0, // Decimeters
|
||||
15 => 10_000.0, // Decameters
|
||||
16 => 100_000.0, // Hectometers
|
||||
17 => 1.0e12, // Gigameters
|
||||
18 => 1.495_978_707e14, // Astronomical Units
|
||||
19 => 9.460_730_472_580_8e18, // Light Years
|
||||
20 => 3.085_677_581_491_367_3e19, // Parsecs
|
||||
21 => 1_200_000.0 / 3_937.0, // US Survey Feet
|
||||
22 => 100_000.0 / 3_937.0, // US Survey Inches
|
||||
23 => 3_600_000.0 / 3_937.0, // US Survey Yards
|
||||
24 => 6_336_000_000.0 / 3_937.0, // US Survey Miles
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_insert_unit_scale(ins: &mut acadrust::entities::Insert, ratio: f64) -> bool {
|
||||
const MIN_INSERT_SCALE: f64 = 1.0e-12;
|
||||
if [ins.x_scale(), ins.y_scale(), ins.z_scale()]
|
||||
.into_iter()
|
||||
.map(|scale| scale * ratio)
|
||||
.any(|scale| !scale.is_finite() || scale.abs() < MIN_INSERT_SCALE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let origin = ins.get_transform().apply(Vector3::ZERO);
|
||||
ins.apply_transform(&Transform::from_translation(-origin));
|
||||
ins.apply_transform(&Transform::from_scale(ratio));
|
||||
ins.apply_transform(&Transform::from_translation(origin));
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod insert_unit_scale_tests {
|
||||
use super::insert_unit_scale;
|
||||
use super::{
|
||||
apply_insert_unit_scale, format_unit_factor, insert_unit_scale, insunits_to_mm,
|
||||
};
|
||||
use acadrust::entities::{AttributeEntity, Insert};
|
||||
use acadrust::types::Vector3;
|
||||
|
||||
const UNITLESS: i16 = 0;
|
||||
const INCHES: i16 = 1;
|
||||
|
|
@ -2451,6 +2482,13 @@ mod insert_unit_scale_tests {
|
|||
assert_eq!(insert_unit_scale(METERS, METERS), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_units_do_not_scale() {
|
||||
assert_eq!(insunits_to_mm(0), None);
|
||||
assert_eq!(insunits_to_mm(25), None);
|
||||
assert_eq!(insert_unit_scale(MILLIMETERS, 25), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn differing_units_convert_through_millimetres() {
|
||||
let ratio = insert_unit_scale(MILLIMETERS, METERS).expect("metres into mm should scale");
|
||||
|
|
@ -2462,4 +2500,72 @@ mod insert_unit_scale_tests {
|
|||
let ratio = insert_unit_scale(METERS, CENTIMETERS).expect("cm into m should scale");
|
||||
assert!((ratio - 0.01).abs() < 1e-12, "got {ratio}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn survey_units_have_expected_ratios() {
|
||||
let survey_foot = insunits_to_mm(21).expect("survey feet");
|
||||
let survey_inch = insunits_to_mm(22).expect("survey inches");
|
||||
let survey_yard = insunits_to_mm(23).expect("survey yards");
|
||||
let survey_mile = insunits_to_mm(24).expect("survey miles");
|
||||
|
||||
assert!((survey_foot / survey_inch - 12.0).abs() < 1e-12);
|
||||
assert!((survey_yard / survey_foot - 3.0).abs() < 1e-12);
|
||||
assert!((survey_mile / survey_foot - 5280.0).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn astronomical_units_use_precise_si_values() {
|
||||
assert_eq!(insunits_to_mm(18), Some(1.495_978_707e14));
|
||||
assert_eq!(insunits_to_mm(19), Some(9.460_730_472_580_8e18));
|
||||
assert_eq!(
|
||||
insunits_to_mm(20),
|
||||
Some(3.085_677_581_491_367_3e19)
|
||||
);
|
||||
assert_ne!(format_unit_factor(1.0e-7), "0.0000");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applying_unit_scale_composes_with_insert_transform() {
|
||||
let mut ins = Insert::new("Block", Vector3::new(12.0, -4.0, 3.0));
|
||||
ins.set_x_scale(-2.0);
|
||||
ins.set_y_scale(3.0);
|
||||
ins.set_z_scale(-4.0);
|
||||
ins.rotation = 0.37;
|
||||
let insertion = ins.get_transform().apply(Vector3::ZERO);
|
||||
let attribute_position = insertion + Vector3::new(2.0, -1.0, 0.5);
|
||||
let mut attribute = AttributeEntity::simple("TAG", "Value");
|
||||
attribute.insertion_point = attribute_position;
|
||||
ins.attributes.push(attribute);
|
||||
|
||||
assert!(apply_insert_unit_scale(&mut ins, 25.4));
|
||||
|
||||
let scaled_insertion = ins.get_transform().apply(Vector3::ZERO);
|
||||
let expected_attribute = insertion + (attribute_position - insertion) * 25.4;
|
||||
assert!((scaled_insertion - insertion).length() < 1e-9);
|
||||
assert!((ins.attributes[0].insertion_point - expected_attribute).length() < 1e-9);
|
||||
assert!((ins.x_scale() + 50.8).abs() < 1e-9);
|
||||
assert!((ins.y_scale() - 76.2).abs() < 1e-9);
|
||||
assert!((ins.z_scale() + 101.6).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applying_large_unit_scale_keeps_distant_insert_fixed() {
|
||||
let mut ins = Insert::new("Block", Vector3::new(1.0e12, -2.0e12, 3.0e12));
|
||||
let insertion = ins.get_transform().apply(Vector3::ZERO);
|
||||
|
||||
assert!(apply_insert_unit_scale(&mut ins, 1.0e20));
|
||||
|
||||
let scaled_insertion = ins.get_transform().apply(Vector3::ZERO);
|
||||
assert_eq!(scaled_insertion, insertion);
|
||||
assert_eq!(ins.x_scale(), 1.0e20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_tiny_unit_scale_is_not_applied() {
|
||||
let mut ins = Insert::new("Block", Vector3::new(1.0, 2.0, 3.0));
|
||||
let before = ins.clone();
|
||||
|
||||
assert!(!apply_insert_unit_scale(&mut ins, 1.0e-13));
|
||||
assert_eq!(ins, before);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue