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::io::linetypes;
|
||||||
use crate::scene::view::dispatch;
|
use crate::scene::view::dispatch;
|
||||||
use crate::ui;
|
use crate::ui;
|
||||||
use acadrust::{EntityType, Handle};
|
|
||||||
use crate::t;
|
use crate::t;
|
||||||
|
use acadrust::types::{Transform, Vector3};
|
||||||
|
use acadrust::{Entity, EntityType, Handle};
|
||||||
|
|
||||||
/// Above this many selected objects the Properties panel skips per-entity
|
/// 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
|
/// 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)
|
.map(|br| br.units)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
set_row(&mut sections, "block_unit", insunits_name(src).to_string());
|
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 factor = insert_unit_scale(host, src).unwrap_or(1.0);
|
||||||
let src_mm = if src == 0 { 1.0 } else { insunits_to_mm(src) };
|
set_row(&mut sections, "unit_factor", format_unit_factor(factor));
|
||||||
let factor = if host_mm.abs() > 1e-12 { src_mm / host_mm } else { 1.0 };
|
|
||||||
set_row(&mut sections, "unit_factor", format!("{factor:.4}"));
|
|
||||||
|
|
||||||
// Name row: editable for regular blocks — pick an
|
// Name row: editable for regular blocks — pick an
|
||||||
// existing definition to re-point this reference, or
|
// existing definition to re-point this reference, or
|
||||||
|
|
@ -1805,9 +1804,12 @@ impl OpenCADStudio {
|
||||||
.map(|br| br.units)
|
.map(|br| br.units)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
if let Some(ratio) = insert_unit_scale(host_units, src_units) {
|
if let Some(ratio) = insert_unit_scale(host_units, src_units) {
|
||||||
ins.set_x_scale(ratio);
|
if !apply_insert_unit_scale(ins, ratio) {
|
||||||
ins.set_y_scale(ratio);
|
self.command_line.push_error(
|
||||||
ins.set_z_scale(ratio);
|
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",
|
19 => "Light Years",
|
||||||
20 => "Parsecs",
|
20 => "Parsecs",
|
||||||
21 => "US Survey Feet",
|
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
|
/// Unit-conversion scale for a new INSERT.
|
||||||
/// 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.
|
|
||||||
fn insert_unit_scale(host_units: i16, src_units: i16) -> Option<f64> {
|
fn insert_unit_scale(host_units: i16, src_units: i16) -> Option<f64> {
|
||||||
if host_units == 0 || src_units == 0 || src_units == host_units {
|
let host_mm = insunits_to_mm(host_units)?;
|
||||||
return None;
|
let src_mm = insunits_to_mm(src_units)?;
|
||||||
}
|
let ratio = src_mm / host_mm;
|
||||||
let ratio = insunits_to_mm(src_units) / insunits_to_mm(host_units);
|
|
||||||
if !ratio.is_finite() || (ratio - 1.0).abs() <= 1e-9 {
|
if !ratio.is_finite() || (ratio - 1.0).abs() <= 1e-9 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(ratio)
|
Some(ratio)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert INSUNITS (DXF group 70) to millimetres.
|
fn format_unit_factor(factor: f64) -> String {
|
||||||
/// 0 = unitless / unknown: returns 1.0 so the caller treats it as "do not scale".
|
let magnitude = factor.abs();
|
||||||
fn insunits_to_mm(code: i16) -> f64 {
|
if magnitude > 0.0 && !(1.0e-4..1.0e7).contains(&magnitude) {
|
||||||
match code {
|
format!("{factor:.4e}")
|
||||||
1 => 25.4, // Inches
|
} else {
|
||||||
2 => 304.8, // Feet
|
format!("{factor:.4}")
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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)]
|
#[cfg(test)]
|
||||||
mod insert_unit_scale_tests {
|
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 UNITLESS: i16 = 0;
|
||||||
const INCHES: i16 = 1;
|
const INCHES: i16 = 1;
|
||||||
|
|
@ -2451,6 +2482,13 @@ mod insert_unit_scale_tests {
|
||||||
assert_eq!(insert_unit_scale(METERS, METERS), None);
|
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]
|
#[test]
|
||||||
fn differing_units_convert_through_millimetres() {
|
fn differing_units_convert_through_millimetres() {
|
||||||
let ratio = insert_unit_scale(MILLIMETERS, METERS).expect("metres into mm should scale");
|
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");
|
let ratio = insert_unit_scale(METERS, CENTIMETERS).expect("cm into m should scale");
|
||||||
assert!((ratio - 0.01).abs() < 1e-12, "got {ratio}");
|
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