feat: correct viewport scale calculation with correct dimension and text scaling
This commit is contained in:
parent
a0aa0f3c51
commit
66243e53ca
6 changed files with 326 additions and 71 deletions
233
examples/inspect_anno.rs
Normal file
233
examples/inspect_anno.rs
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
// Inspect DIMSCALE, dimstyles, text heights, and xdata to diagnose
|
||||||
|
// annotation-scale bugs in paper-space viewports.
|
||||||
|
//
|
||||||
|
// cargo run --release --example inspect_anno -- <file>
|
||||||
|
|
||||||
|
use acadrust::entities::{Dimension, EntityType};
|
||||||
|
use acadrust::io::dwg::DwgReader;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let path = std::env::args().nth(1).expect("usage: inspect_anno <file>");
|
||||||
|
let doc = DwgReader::from_file(&path)?.read()?;
|
||||||
|
println!("file: {}\n", path);
|
||||||
|
|
||||||
|
println!("─── header ──────────────────────────────");
|
||||||
|
println!(" DIMSCALE (dim_scale) = {}", doc.header.dim_scale);
|
||||||
|
println!(" DIMTXT (dim_text_height)= {}", doc.header.dim_text_height);
|
||||||
|
println!(" DIMASZ (dim_arrow_size) = {}", doc.header.dim_arrow_size);
|
||||||
|
println!(" TEXTSIZE (text_height) = {}", doc.header.text_height);
|
||||||
|
println!(" LTSCALE = {}", doc.header.linetype_scale);
|
||||||
|
println!(" PSLTSCALE (psls) = {}", doc.header.paper_space_linetype_scaling);
|
||||||
|
println!(" CELTSCALE = {}", doc.header.current_entity_linetype_scale);
|
||||||
|
println!(" viewport_scale_factor = {}", doc.header.viewport_scale_factor);
|
||||||
|
println!();
|
||||||
|
println!("─── linetypes (first 10) ────────────────");
|
||||||
|
let mut shown = 0;
|
||||||
|
for lt in doc.line_types.iter() {
|
||||||
|
if shown >= 10 { break; }
|
||||||
|
println!(
|
||||||
|
" {:<24} pattern_len={:.4} elements={}",
|
||||||
|
lt.name, lt.pattern_length, lt.elements.len()
|
||||||
|
);
|
||||||
|
shown += 1;
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!("─── dim_styles ──────────────────────────");
|
||||||
|
println!(
|
||||||
|
" {:<24} {:>10} {:>10} {:>10} {:>10}",
|
||||||
|
"name", "dimscale", "dimtxt", "dimasz", "dimexe"
|
||||||
|
);
|
||||||
|
for s in doc.dim_styles.iter() {
|
||||||
|
println!(
|
||||||
|
" {:<24} {:>10.4} {:>10.4} {:>10.4} {:>10.4}",
|
||||||
|
s.name, s.dimscale, s.dimtxt, s.dimasz, s.dimexe
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!("─── text_styles ─────────────────────────");
|
||||||
|
for s in doc.text_styles.iter() {
|
||||||
|
println!(
|
||||||
|
" {:<24} height={:.4} last_height={:.4} font={:?}",
|
||||||
|
s.name, s.height, s.last_height, s.font_file
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
let mut n_text = 0u32;
|
||||||
|
let mut n_text_with_xdata = 0u32;
|
||||||
|
let mut n_text_with_acanno = 0u32;
|
||||||
|
let mut n_mtext = 0u32;
|
||||||
|
let mut n_mtext_with_xdata = 0u32;
|
||||||
|
let mut n_mtext_with_acanno = 0u32;
|
||||||
|
let mut n_dim = 0u32;
|
||||||
|
let mut n_dim_with_xdata = 0u32;
|
||||||
|
let mut n_dim_with_acanno = 0u32;
|
||||||
|
let mut xd_app_counts: std::collections::BTreeMap<String, u32> = Default::default();
|
||||||
|
|
||||||
|
let mut sample_text_h: Vec<f64> = vec![];
|
||||||
|
let mut sample_mtext_h: Vec<f64> = vec![];
|
||||||
|
let mut sample_dim_style: std::collections::BTreeMap<String, u32> = Default::default();
|
||||||
|
// For MTEXT: bucket by owner block name (paper vs model) and height range.
|
||||||
|
let mut mtext_by_owner: std::collections::BTreeMap<String, Vec<f64>> = Default::default();
|
||||||
|
|
||||||
|
let acanno_keys = ["AcAnnoPO", "AcAnnotativeData", "AcAnnoScaleObj", "AcDbXrecObject"];
|
||||||
|
|
||||||
|
for e in doc.entities() {
|
||||||
|
match e {
|
||||||
|
EntityType::Text(t) => {
|
||||||
|
n_text += 1;
|
||||||
|
let xd = &t.common.extended_data;
|
||||||
|
let has_xd = !xd.records().is_empty();
|
||||||
|
if has_xd {
|
||||||
|
n_text_with_xdata += 1;
|
||||||
|
for r in xd.records() {
|
||||||
|
*xd_app_counts.entry(r.application_name.clone()).or_default() += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if acanno_keys.iter().any(|k| xd.get_record(k).is_some()) {
|
||||||
|
n_text_with_acanno += 1;
|
||||||
|
}
|
||||||
|
if sample_text_h.len() < 10 {
|
||||||
|
sample_text_h.push(t.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EntityType::MText(t) => {
|
||||||
|
n_mtext += 1;
|
||||||
|
let xd = &t.common.extended_data;
|
||||||
|
let has_xd = !xd.records().is_empty();
|
||||||
|
if has_xd {
|
||||||
|
n_mtext_with_xdata += 1;
|
||||||
|
for r in xd.records() {
|
||||||
|
*xd_app_counts.entry(r.application_name.clone()).or_default() += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if acanno_keys.iter().any(|k| xd.get_record(k).is_some()) {
|
||||||
|
n_mtext_with_acanno += 1;
|
||||||
|
}
|
||||||
|
if sample_mtext_h.len() < 10 {
|
||||||
|
sample_mtext_h.push(t.height);
|
||||||
|
}
|
||||||
|
// Look up the owning block-record by handle and label as
|
||||||
|
// "*Model_Space" / "*Paper_Space*" / "<other>".
|
||||||
|
let owner_h = t.common.owner_handle;
|
||||||
|
let owner_label = doc
|
||||||
|
.block_records
|
||||||
|
.iter()
|
||||||
|
.find_map(|br: &acadrust::BlockRecord| {
|
||||||
|
if br.handle == owner_h {
|
||||||
|
Some(br.name.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| format!("h{}", owner_h.value()));
|
||||||
|
mtext_by_owner
|
||||||
|
.entry(owner_label)
|
||||||
|
.or_default()
|
||||||
|
.push(t.height);
|
||||||
|
}
|
||||||
|
EntityType::Dimension(d) => {
|
||||||
|
n_dim += 1;
|
||||||
|
let base = match d {
|
||||||
|
Dimension::Aligned(d) => &d.base,
|
||||||
|
Dimension::Linear(d) => &d.base,
|
||||||
|
Dimension::Radius(d) => &d.base,
|
||||||
|
Dimension::Diameter(d) => &d.base,
|
||||||
|
Dimension::Angular2Ln(d) => &d.base,
|
||||||
|
Dimension::Angular3Pt(d) => &d.base,
|
||||||
|
Dimension::Ordinate(d) => &d.base,
|
||||||
|
};
|
||||||
|
let xd = &base.common.extended_data;
|
||||||
|
let has_xd = !xd.records().is_empty();
|
||||||
|
if has_xd {
|
||||||
|
n_dim_with_xdata += 1;
|
||||||
|
for r in xd.records() {
|
||||||
|
*xd_app_counts.entry(r.application_name.clone()).or_default() += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if acanno_keys.iter().any(|k| xd.get_record(k).is_some()) {
|
||||||
|
n_dim_with_acanno += 1;
|
||||||
|
}
|
||||||
|
*sample_dim_style.entry(base.style_name.clone()).or_default() += 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("─── annotation entity counts ────────────");
|
||||||
|
println!(
|
||||||
|
" TEXT total={:<5} xdata={:<5} AcAnno={}",
|
||||||
|
n_text, n_text_with_xdata, n_text_with_acanno
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" MTEXT total={:<5} xdata={:<5} AcAnno={}",
|
||||||
|
n_mtext, n_mtext_with_xdata, n_mtext_with_acanno
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" DIMENSION total={:<5} xdata={:<5} AcAnno={}",
|
||||||
|
n_dim, n_dim_with_xdata, n_dim_with_acanno
|
||||||
|
);
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!("─── xdata application names seen ────────");
|
||||||
|
for (k, v) in &xd_app_counts {
|
||||||
|
println!(" {:>5} {}", v, k);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
if !sample_text_h.is_empty() {
|
||||||
|
println!("─── first 10 TEXT heights ───────────────");
|
||||||
|
for h in &sample_text_h {
|
||||||
|
println!(" {:.4}", h);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
if !sample_mtext_h.is_empty() {
|
||||||
|
println!("─── first 10 MTEXT heights ──────────────");
|
||||||
|
for h in &sample_mtext_h {
|
||||||
|
println!(" {:.4}", h);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
if !mtext_by_owner.is_empty() {
|
||||||
|
println!("─── MTEXT by owner ──────────────────────");
|
||||||
|
for (owner, heights) in &mtext_by_owner {
|
||||||
|
let mut h = heights.clone();
|
||||||
|
h.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||||
|
let min = h.first().copied().unwrap_or(0.0);
|
||||||
|
let max = h.last().copied().unwrap_or(0.0);
|
||||||
|
let median = h[h.len() / 2];
|
||||||
|
let n_below_10 = h.iter().filter(|&&x| x < 10.0).count();
|
||||||
|
let n_above_100 = h.iter().filter(|&&x| x > 100.0).count();
|
||||||
|
println!(
|
||||||
|
" {:<20} n={:<4} min={:.2} med={:.2} max={:.2} <10:{} >100:{}",
|
||||||
|
owner,
|
||||||
|
h.len(),
|
||||||
|
min,
|
||||||
|
median,
|
||||||
|
max,
|
||||||
|
n_below_10,
|
||||||
|
n_above_100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sample_dim_style.is_empty() {
|
||||||
|
println!("─── dimension styles used (count) ───────");
|
||||||
|
for (k, v) in &sample_dim_style {
|
||||||
|
let used_style = doc.dim_styles.iter().find(|s| s.name == *k);
|
||||||
|
let ds = used_style.map(|s| s.dimscale).unwrap_or(f64::NAN);
|
||||||
|
let dt = used_style.map(|s| s.dimtxt).unwrap_or(f64::NAN);
|
||||||
|
let da = used_style.map(|s| s.dimasz).unwrap_or(f64::NAN);
|
||||||
|
println!(
|
||||||
|
" {:>5}× style=\"{}\" dimscale={:.4} dimtxt={:.4} dimasz={:.4}",
|
||||||
|
v, k, ds, dt, da
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -2493,7 +2493,11 @@ impl H7CAD {
|
||||||
vp.center.clone(),
|
vp.center.clone(),
|
||||||
vp.width,
|
vp.width,
|
||||||
vp.height,
|
vp.height,
|
||||||
vp.custom_scale,
|
crate::scene::vp_effective_scale(
|
||||||
|
vp.custom_scale,
|
||||||
|
vp.view_height,
|
||||||
|
vp.height,
|
||||||
|
),
|
||||||
vp.status.is_on,
|
vp.status.is_on,
|
||||||
vp.status.locked,
|
vp.status.locked,
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,12 @@ fn grips(vp: &Viewport) -> Vec<GripDef> {
|
||||||
|
|
||||||
fn properties(vp: &Viewport) -> PropSection {
|
fn properties(vp: &Viewport) -> PropSection {
|
||||||
let scale_opts: Vec<String> = STANDARD_SCALES.iter().map(|(s, _)| s.to_string()).collect();
|
let scale_opts: Vec<String> = STANDARD_SCALES.iter().map(|(s, _)| s.to_string()).collect();
|
||||||
let current_scale_label = scale_label(vp.custom_scale);
|
let effective_scale = crate::scene::vp_effective_scale(
|
||||||
|
vp.custom_scale,
|
||||||
|
vp.view_height,
|
||||||
|
vp.height,
|
||||||
|
);
|
||||||
|
let current_scale_label = scale_label(effective_scale);
|
||||||
|
|
||||||
let view_opts: Vec<String> = STD_VIEWS.iter().map(|s| s.to_string()).collect();
|
let view_opts: Vec<String> = STD_VIEWS.iter().map(|s| s.to_string()).collect();
|
||||||
let current_view = viewport_view_label(vp);
|
let current_view = viewport_view_label(vp);
|
||||||
|
|
@ -124,8 +129,8 @@ fn properties(vp: &Viewport) -> PropSection {
|
||||||
edit("Center Z", "center_z", vp.center.z),
|
edit("Center Z", "center_z", vp.center.z),
|
||||||
edit("Width", "vp_w", vp.width),
|
edit("Width", "vp_w", vp.width),
|
||||||
edit("Height", "vp_h", vp.height),
|
edit("Height", "vp_h", vp.height),
|
||||||
// Numeric scale entry.
|
// Numeric scale entry (derived from view_height; writes both).
|
||||||
edit("Scale (num)", "vscale", vp.custom_scale),
|
edit("Scale (num)", "vscale", effective_scale),
|
||||||
// Standard scale picker.
|
// Standard scale picker.
|
||||||
Property {
|
Property {
|
||||||
label: "Scale".into(),
|
label: "Scale".into(),
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,24 @@ use std::sync::Arc;
|
||||||
/// GPU Pipeline to skip re-uploading geometry when switching tabs.
|
/// GPU Pipeline to skip re-uploading geometry when switching tabs.
|
||||||
static GEOMETRY_EPOCH: AtomicU64 = AtomicU64::new(1);
|
static GEOMETRY_EPOCH: AtomicU64 = AtomicU64::new(1);
|
||||||
|
|
||||||
|
/// Resolve a viewport's paper-to-model scale ratio from its two
|
||||||
|
/// DXF-derived sources.
|
||||||
|
///
|
||||||
|
/// `view_height` (model-space view extent) is the canonical source — it
|
||||||
|
/// is what AutoCAD actually uses to draw, and what we keep in sync on
|
||||||
|
/// every write. `custom_scale` is consulted only when `view_height` is
|
||||||
|
/// missing or zero (some third-party exporters omit it).
|
||||||
|
#[inline]
|
||||||
|
pub fn vp_effective_scale(custom_scale: f64, view_height: f64, vp_height: f64) -> f64 {
|
||||||
|
if view_height.abs() > 1e-9 {
|
||||||
|
return vp_height / view_height;
|
||||||
|
}
|
||||||
|
if custom_scale.abs() > 1e-9 {
|
||||||
|
return custom_scale;
|
||||||
|
}
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
|
||||||
/// Pre-built entity caches returned by [`build_derived_caches`].
|
/// Pre-built entity caches returned by [`build_derived_caches`].
|
||||||
/// Produced in the file-load background task so the UI thread only assigns.
|
/// Produced in the file-load background task so the UI thread only assigns.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -839,14 +857,11 @@ impl Scene {
|
||||||
self.document.entities().find_map(|e| {
|
self.document.entities().find_map(|e| {
|
||||||
if let EntityType::Viewport(vp) = e {
|
if let EntityType::Viewport(vp) = e {
|
||||||
if Self::is_content_viewport(vp) && vp.common.owner_handle == layout_block {
|
if Self::is_content_viewport(vp) && vp.common.owner_handle == layout_block {
|
||||||
let scale = if vp.custom_scale.abs() > 1e-9 {
|
return Some(vp_effective_scale(
|
||||||
vp.custom_scale
|
vp.custom_scale,
|
||||||
} else if vp.view_height.abs() > 1e-9 {
|
vp.view_height,
|
||||||
vp.height / vp.view_height
|
vp.height,
|
||||||
} else {
|
));
|
||||||
1.0
|
|
||||||
};
|
|
||||||
return Some(scale);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|
@ -1821,12 +1836,18 @@ impl Scene {
|
||||||
effective_view_center = (self.world_offset[0], self.world_offset[1]);
|
effective_view_center = (self.world_offset[0], self.world_offset[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let scale = if effective_view_height.abs() > 1e-9 {
|
// When the saved view did not overlap the model cluster we just
|
||||||
|
// forced `effective_view_height` to a fit value above — that
|
||||||
|
// override must win regardless of the configured priority,
|
||||||
|
// otherwise broken files render blank under custom_scale-first.
|
||||||
|
let scale = if !saved_overlaps {
|
||||||
vp.height as f32 / effective_view_height
|
vp.height as f32 / effective_view_height
|
||||||
} else if vp.custom_scale.abs() > 1e-9 {
|
|
||||||
vp.custom_scale as f32
|
|
||||||
} else {
|
} else {
|
||||||
1.0
|
vp_effective_scale(
|
||||||
|
vp.custom_scale,
|
||||||
|
effective_view_height as f64,
|
||||||
|
vp.height,
|
||||||
|
) as f32
|
||||||
};
|
};
|
||||||
|
|
||||||
let pcx = vp.center.x as f32;
|
let pcx = vp.center.x as f32;
|
||||||
|
|
@ -1994,13 +2015,8 @@ impl Scene {
|
||||||
Some(acadrust::EntityType::Viewport(vp)) => vp,
|
Some(acadrust::EntityType::Viewport(vp)) => vp,
|
||||||
_ => return paper_pt,
|
_ => return paper_pt,
|
||||||
};
|
};
|
||||||
let scale = if vp.custom_scale.abs() > 1e-9 {
|
let scale =
|
||||||
vp.custom_scale as f32
|
vp_effective_scale(vp.custom_scale, vp.view_height, vp.height) as f32;
|
||||||
} else if vp.view_height.abs() > 1e-9 {
|
|
||||||
(vp.height / vp.view_height) as f32
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
};
|
|
||||||
if scale.abs() < 1e-9 {
|
if scale.abs() < 1e-9 {
|
||||||
return paper_pt;
|
return paper_pt;
|
||||||
}
|
}
|
||||||
|
|
@ -2083,13 +2099,8 @@ impl Scene {
|
||||||
|
|
||||||
if let Some(cp) = cursor_paper {
|
if let Some(cp) = cursor_paper {
|
||||||
// Compute the model-space point under the cursor before zoom.
|
// Compute the model-space point under the cursor before zoom.
|
||||||
let scale_before = if vp.custom_scale.abs() > 1e-9 {
|
let scale_before =
|
||||||
vp.custom_scale as f32
|
vp_effective_scale(vp.custom_scale, vp.view_height, vp.height) as f32;
|
||||||
} else if vp.view_height.abs() > 1e-9 {
|
|
||||||
(vp.height / vp.view_height) as f32
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
};
|
|
||||||
let cx = vp.center.x as f32;
|
let cx = vp.center.x as f32;
|
||||||
let cy = vp.center.y as f32;
|
let cy = vp.center.y as f32;
|
||||||
let tx = vp.view_target.x as f32;
|
let tx = vp.view_target.x as f32;
|
||||||
|
|
@ -4327,13 +4338,8 @@ impl Scene {
|
||||||
let (frozen, vp_anno_scale) = match self.document.get_entity(vp_handle) {
|
let (frozen, vp_anno_scale) = match self.document.get_entity(vp_handle) {
|
||||||
Some(EntityType::Viewport(vp)) => {
|
Some(EntityType::Viewport(vp)) => {
|
||||||
let f: HSet<Handle> = vp.frozen_layers.iter().cloned().collect();
|
let f: HSet<Handle> = vp.frozen_layers.iter().cloned().collect();
|
||||||
let vp_scale = if vp.custom_scale.abs() > 1e-9 {
|
let vp_scale =
|
||||||
vp.custom_scale
|
vp_effective_scale(vp.custom_scale, vp.view_height, vp.height);
|
||||||
} else if vp.view_height.abs() > 1e-9 {
|
|
||||||
vp.height / vp.view_height
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
};
|
|
||||||
let anno = if vp_scale > 1e-9 {
|
let anno = if vp_scale > 1e-9 {
|
||||||
(1.0 / vp_scale) as f32
|
(1.0 / vp_scale) as f32
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -120,17 +120,20 @@ pub fn tessellate(
|
||||||
let name = handle.value().to_string();
|
let name = handle.value().to_string();
|
||||||
|
|
||||||
// Determine effective annotation scale for this entity.
|
// Determine effective annotation scale for this entity.
|
||||||
// AutoCAD marks annotative objects with "AcAnnoPO" xdata (R2007+).
|
//
|
||||||
// If no xdata at all: old file without annotation system → treat as annotative.
|
// AutoCAD's R2007+ "annotative" system marks objects via extension-
|
||||||
// If xdata present but no "AcAnnoPO": R2007+ file, entity is non-annotative → no scaling.
|
// dictionary records or "AcAnnoPO" / "AcAnnotativeData" xdata. Only
|
||||||
|
// entities so marked should be auto-scaled by the viewport's
|
||||||
|
// paper-scale; everything else is treated as manually pre-scaled
|
||||||
|
// (old DXF/DWG convention with $DIMSCALE and oversized text).
|
||||||
|
//
|
||||||
|
// Default: NOT annotative (anno_scale = 1.0). Opt-in via explicit
|
||||||
|
// xdata marker. Files that mark every entity annotative are rare; the
|
||||||
|
// pre-R2007 manual-scale convention is far more common in field data.
|
||||||
let anno_scale = {
|
let anno_scale = {
|
||||||
let xdata = &entity.common().extended_data;
|
let xdata = &entity.common().extended_data;
|
||||||
let is_annotative = if xdata.is_empty() {
|
let is_annotative = xdata.get_record("AcAnnoPO").is_some()
|
||||||
// Old DXF / no annotation metadata — treat as annotative.
|
|| xdata.get_record("AcAnnotativeData").is_some();
|
||||||
true
|
|
||||||
} else {
|
|
||||||
xdata.get_record("AcAnnoPO").is_some()
|
|
||||||
};
|
|
||||||
if is_annotative {
|
if is_annotative {
|
||||||
anno_scale
|
anno_scale
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -465,22 +468,33 @@ pub fn tessellate_dimension(
|
||||||
};
|
};
|
||||||
let name = handle.value().to_string();
|
let name = handle.value().to_string();
|
||||||
let style_name = &dim.base().style_name;
|
let style_name = &dim.base().style_name;
|
||||||
let (arrow_size, dimexo, dimexe) = document
|
let style = document.dim_styles.iter().find(|s| {
|
||||||
.dim_styles
|
s.name.eq_ignore_ascii_case(style_name)
|
||||||
.iter()
|
|| (style_name.trim().is_empty() && s.name.eq_ignore_ascii_case("Standard"))
|
||||||
.find(|s| {
|
});
|
||||||
s.name.eq_ignore_ascii_case(style_name)
|
|
||||||
|| (style_name.trim().is_empty() && s.name.eq_ignore_ascii_case("Standard"))
|
// DIMSCALE rule:
|
||||||
})
|
// dimstyle.dimscale > 0 → that's the *final* multiplier; ignore anno_scale.
|
||||||
|
// dimstyle.dimscale == 0 → annotative: use anno_scale (= 1/vp_scale in viewports).
|
||||||
|
let dim_scale = style
|
||||||
|
.map(|s| {
|
||||||
|
if s.dimscale > 1e-6 {
|
||||||
|
s.dimscale
|
||||||
|
} else {
|
||||||
|
anno_scale as f64
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(1.0);
|
||||||
|
let (arrow_size, dimexo, dimexe, dim_txt) = style
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
let scale = (if s.dimscale > 1e-6 { s.dimscale } else { 1.0 }) * anno_scale as f64;
|
|
||||||
(
|
(
|
||||||
((s.dimasz * scale) as f32).max(0.001),
|
((s.dimasz * dim_scale) as f32).max(0.001),
|
||||||
(s.dimexo * scale) as f32,
|
(s.dimexo * dim_scale) as f32,
|
||||||
(s.dimexe * scale) as f32,
|
(s.dimexe * dim_scale) as f32,
|
||||||
|
s.dimtxt * dim_scale,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or((0.12, 0.0, 0.0));
|
.unwrap_or((0.12, 0.0, 0.0, 2.5));
|
||||||
let points = dimension_geometry(dim, arrow_size, dimexo, dimexe, world_offset);
|
let points = dimension_geometry(dim, arrow_size, dimexo, dimexe, world_offset);
|
||||||
let key_vertices = points
|
let key_vertices = points
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -508,7 +522,7 @@ pub fn tessellate_dimension(
|
||||||
fill_tris: vec![],
|
fill_tris: vec![],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
if let Some(text) = dimension_text_entity(dim) {
|
if let Some(text) = dimension_text_entity(dim, dim_txt) {
|
||||||
let mut wire = tessellate(
|
let mut wire = tessellate(
|
||||||
document,
|
document,
|
||||||
handle,
|
handle,
|
||||||
|
|
@ -519,7 +533,9 @@ pub fn tessellate_dimension(
|
||||||
[0.0; 8],
|
[0.0; 8],
|
||||||
line_weight_px,
|
line_weight_px,
|
||||||
world_offset,
|
world_offset,
|
||||||
anno_scale,
|
// dim text already baked dim_scale into its height — don't
|
||||||
|
// let the inner tessellate re-apply anno_scale.
|
||||||
|
1.0,
|
||||||
);
|
);
|
||||||
wire.name = name;
|
wire.name = name;
|
||||||
wires.push(wire);
|
wires.push(wire);
|
||||||
|
|
@ -1688,7 +1704,7 @@ fn dimension_snap_pts(dim: &Dimension, world_offset: [f64; 3]) -> Vec<(Vec3, Sna
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dimension_text_entity(dim: &Dimension) -> Option<Text> {
|
fn dimension_text_entity(dim: &Dimension, text_height: f64) -> Option<Text> {
|
||||||
let value = dimension_text_value(dim)?;
|
let value = dimension_text_value(dim)?;
|
||||||
// Use f64 position directly to avoid f32 round-trip precision loss at large
|
// Use f64 position directly to avoid f32 round-trip precision loss at large
|
||||||
// coordinates (e.g. Turkish UTM ~4,000,000 m). tessellate() will apply
|
// coordinates (e.g. Turkish UTM ~4,000,000 m). tessellate() will apply
|
||||||
|
|
@ -1701,7 +1717,7 @@ fn dimension_text_entity(dim: &Dimension) -> Option<Text> {
|
||||||
dimension_text_natural_rotation(dim)
|
dimension_text_natural_rotation(dim)
|
||||||
};
|
};
|
||||||
let mut text = Text::with_value(value, pos_f64)
|
let mut text = Text::with_value(value, pos_f64)
|
||||||
.with_height(dimension_text_height(dim))
|
.with_height(text_height)
|
||||||
.with_rotation(rotation);
|
.with_rotation(rotation);
|
||||||
text.style = base.style_name.clone();
|
text.style = base.style_name.clone();
|
||||||
text.common = base.common.clone();
|
text.common = base.common.clone();
|
||||||
|
|
@ -1767,15 +1783,6 @@ fn dimension_text_position(dim: &Dimension, world_offset: [f64; 3]) -> Vec3 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dimension_text_height(dim: &Dimension) -> f64 {
|
|
||||||
let scale = (dim.measurement().abs() * 0.12).clamp(0.25, 2.0);
|
|
||||||
if scale.is_finite() {
|
|
||||||
scale
|
|
||||||
} else {
|
|
||||||
0.25
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vec3_local(v: Vector3, off: [f64; 3]) -> Vec3 {
|
fn vec3_local(v: Vector3, off: [f64; 3]) -> Vec3 {
|
||||||
Vec3::new(
|
Vec3::new(
|
||||||
(v.x - off[0]) as f32,
|
(v.x - off[0]) as f32,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ const COMMON_SCALES: &[(&str, f32, f64)] = &[
|
||||||
///
|
///
|
||||||
/// - `is_model`: true = model space (dispatches SetAnnotationScale), false = paper space (SetViewportScale).
|
/// - `is_model`: true = model space (dispatches SetAnnotationScale), false = paper space (SetViewportScale).
|
||||||
/// - `current_anno_scale`: current annotation_scale from Scene (used to highlight active row in model space).
|
/// - `current_anno_scale`: current annotation_scale from Scene (used to highlight active row in model space).
|
||||||
/// - `viewport_scale`: current vp.custom_scale (used to highlight in paper space).
|
/// - `viewport_scale`: current effective vp scale, view_height-first (used to highlight in paper space).
|
||||||
pub fn scale_popup_overlay(
|
pub fn scale_popup_overlay(
|
||||||
is_model: bool,
|
is_model: bool,
|
||||||
current_anno_scale: f32,
|
current_anno_scale: f32,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue