forked from 1b-agent/opencadstudio-upstream
feat(scale): drive scales from the drawing's scale list
Read the model-space annotation scale from the standard CANNOSCALE / CANNOSCALEVALUE header variables instead of a user-variable hack, and populate both the status-bar scale picker and the viewport Scale property purely from the drawing's ACAD_SCALELIST — no built-in fallback set. Xref-derived scales (`_XREF` suffix, `name|` prefix) are filtered out so only the host drawing's own scales appear. Bumps the patched acadrust to the commit that adds CANNOSCALE/ CANNOSCALEVALUE read/write. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
b5ce376d5c
commit
edc8cbc969
8 files changed
+128
-30
No files matched your search
Generated
+2
-2
@@ -55,7 +55,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||
[[package]]
|
||||
name = "acadrust"
|
||||
version = "0.3.4"
|
||||
source = "git+https://github.com/HakanSeven12/acadrust?branch=main#535247755829ff7971d749a424fb2134df34f6d4"
|
||||
source = "git+https://github.com/HakanSeven12/acadrust?branch=main#041df8c0568ea1242a39e22f2d04e770317bdf7c"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -3177,7 +3177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d"
|
||||
dependencies = [
|
||||
"bytecount",
|
||||
"memchr 1.0.2",
|
||||
"memchr 2.8.1",
|
||||
"nom 8.0.0",
|
||||
]
|
||||
|
||||
|
||||
+3
-2
@@ -34,6 +34,7 @@ windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_Window
|
||||
|
||||
[patch.crates-io]
|
||||
# Tracks HakanSeven12/acadrust main, which carries the hatch
|
||||
# boundary-handle cap, raster-image/wipeout clip-vertex fix, and 3DFACE
|
||||
# Z-decode fix on top of upstream. Revert to upstream once those PRs land.
|
||||
# boundary-handle cap, raster-image/wipeout clip-vertex fix, 3DFACE
|
||||
# Z-decode fix, and CANNOSCALE/CANNOSCALEVALUE header support on top of
|
||||
# upstream. Revert to upstream once those PRs land.
|
||||
acadrust = { git = "https://github.com/HakanSeven12/acadrust", branch = "main" }
|
||||
@@ -144,6 +144,34 @@ impl OpenCADStudio {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Drive the viewport scale picker from the drawing's
|
||||
// own scale list instead of a built-in set.
|
||||
let file_scales = self.tabs[i].scene.scale_list();
|
||||
if !file_scales.is_empty() {
|
||||
let eff = crate::scene::vp_effective_scale(
|
||||
vp.custom_scale,
|
||||
vp.view_height,
|
||||
vp.height,
|
||||
);
|
||||
let selected = file_scales
|
||||
.iter()
|
||||
.find(|(_, _, f)| (f - eff).abs() < 0.001 * f.max(0.001))
|
||||
.map(|(n, _, _)| n.clone())
|
||||
.unwrap_or_default();
|
||||
let options: Vec<String> =
|
||||
file_scales.iter().map(|(n, _, _)| n.clone()).collect();
|
||||
if let Some(geom) = sections.last_mut() {
|
||||
if let Some(prop) =
|
||||
geom.props.iter_mut().find(|p| p.field == "vscale_std")
|
||||
{
|
||||
prop.value = crate::scene::object::PropValue::Choice {
|
||||
selected,
|
||||
options,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inject DimStyle picker for Dimension entities.
|
||||
|
||||
+37
-4
@@ -163,9 +163,12 @@ impl OpenCADStudio {
|
||||
|
||||
self.tabs[i].current_path = Some(path.clone());
|
||||
self.tabs[i].scene.document = doc;
|
||||
let stored = self.tabs[i].scene.document.header.user_real1;
|
||||
// Current model-space annotation scale comes from the drawing's
|
||||
// CANNOSCALEVALUE (paper/drawing factor); the multiplier we use
|
||||
// for text/dim sizing is its inverse (1:50 -> 0.02 -> 50.0).
|
||||
let cannoscale_value = self.tabs[i].scene.document.header.annotation_scale_value;
|
||||
self.tabs[i].scene.annotation_scale =
|
||||
if stored > 1e-9 { stored as f32 } else { 1.0 };
|
||||
if cannoscale_value > 1e-9 { (1.0 / cannoscale_value) as f32 } else { 1.0 };
|
||||
|
||||
// Auto-resolve XREFs relative to the opened file's directory.
|
||||
if let Some(base_dir) = path.parent() {
|
||||
@@ -632,8 +635,7 @@ impl OpenCADStudio {
|
||||
let (_, version) = crate::io::parse_save_format(&self.save_dialog_format);
|
||||
let close = self.close_save_dialog_window();
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.document.header.user_real1 =
|
||||
self.tabs[i].scene.annotation_scale as f64;
|
||||
sync_annotation_scale_header(&mut self.tabs[i].scene);
|
||||
match crate::io::save_as_version(&self.tabs[i].scene.document, &path, version) {
|
||||
Ok(()) => {
|
||||
self.command_line
|
||||
@@ -7691,6 +7693,37 @@ impl OpenCADStudio {
|
||||
|
||||
/// Parse a scale string like "1:50" or "2:1" into (numerator, denominator).
|
||||
/// Returns (1.0, 1.0) for "Fit" or unknown formats.
|
||||
/// Sync the model-space annotation scale into the standard CANNOSCALE /
|
||||
/// CANNOSCALEVALUE header variables before a save, so the scale round-trips
|
||||
/// through the file (and is read correctly by other CAD applications).
|
||||
fn sync_annotation_scale_header(scene: &mut Scene) {
|
||||
let anno = scene.annotation_scale;
|
||||
let value = if anno.abs() > 1e-9 { 1.0 / anno as f64 } else { 1.0 };
|
||||
// Prefer the name of a matching scale already in the drawing's list;
|
||||
// fall back to a formatted ratio when none matches.
|
||||
let name = scene
|
||||
.scale_list()
|
||||
.into_iter()
|
||||
.find(|(_, a, _)| (a - anno).abs() < 0.001 * anno.max(0.001))
|
||||
.map(|(n, _, _)| n)
|
||||
.unwrap_or_else(|| format_annotation_scale_name(anno));
|
||||
let hdr = &mut scene.document.header;
|
||||
hdr.current_annotation_scale = name;
|
||||
hdr.annotation_scale_value = value;
|
||||
}
|
||||
|
||||
/// Format an annotation-scale multiplier as a ratio name: 50.0 -> "1:50",
|
||||
/// 0.5 -> "2:1", 1.0 -> "1:1".
|
||||
fn format_annotation_scale_name(anno: f32) -> String {
|
||||
if anno >= 1.0 {
|
||||
format!("1:{}", anno.round() as i64)
|
||||
} else if anno > 0.0 {
|
||||
format!("{}:1", (1.0 / anno).round() as i64)
|
||||
} else {
|
||||
"1:1".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_plot_scale(s: &str) -> (f64, f64) {
|
||||
if s == "Fit" {
|
||||
return (1.0, 1.0);
|
||||
|
||||
@@ -1163,6 +1163,7 @@ impl OpenCADStudio {
|
||||
is_model,
|
||||
tab.scene.annotation_scale,
|
||||
tab.scene.first_viewport_scale(),
|
||||
tab.scene.scale_list(),
|
||||
)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).height(0).into()
|
||||
|
||||
@@ -25,6 +25,20 @@ const STANDARD_SCALES: &[(&str, f64)] = &[
|
||||
("10:1", 10.0),
|
||||
];
|
||||
|
||||
/// Parse a scale ratio name to its paper/drawing factor: "1:50" -> 0.02,
|
||||
/// "2:1" -> 2.0. Also accepts a plain decimal ("0.02").
|
||||
fn parse_scale_ratio(label: &str) -> Option<f64> {
|
||||
if let Some((a, b)) = label.split_once(':') {
|
||||
let n: f64 = a.trim().parse().ok()?;
|
||||
let d: f64 = b.trim().parse().ok()?;
|
||||
if d.abs() > 1e-12 {
|
||||
return Some(n / d);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
label.trim().parse::<f64>().ok()
|
||||
}
|
||||
|
||||
fn scale_label(scale: f64) -> String {
|
||||
for (label, val) in STANDARD_SCALES {
|
||||
if (scale - val).abs() < val * 0.01 {
|
||||
@@ -411,9 +425,11 @@ fn apply_geom_prop(vp: &mut Viewport, field: &str, value: &str) {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Standard scale picker.
|
||||
// Scale picker. The label is a ratio name from the drawing's scale list
|
||||
// ("1:50", "2:1"); parse it to the paper/drawing factor so any named
|
||||
// scale resolves, not just the built-in set.
|
||||
if field == "vscale_std" {
|
||||
if let Some(&(_, scale)) = STANDARD_SCALES.iter().find(|(label, _)| *label == value) {
|
||||
if let Some(scale) = parse_scale_ratio(value) {
|
||||
vp.custom_scale = scale;
|
||||
if scale > 1e-9 {
|
||||
vp.view_height = vp.height / scale;
|
||||
|
||||
@@ -1225,6 +1225,37 @@ impl Scene {
|
||||
})
|
||||
}
|
||||
|
||||
/// Annotation/viewport scales defined in the drawing's scale list
|
||||
/// (the `ACAD_SCALELIST` dictionary), as `(label, annotation_multiplier,
|
||||
/// viewport_factor)`. The annotation multiplier sizes model-space
|
||||
/// text/dims (50.0 for "1:50"); the viewport factor is the paper/drawing
|
||||
/// ratio (0.02 for "1:50"). Sorted smallest ratio first (1:100 … 1:1 …
|
||||
/// 10:1). Empty when the drawing carries no scale list — the caller
|
||||
/// substitutes its built-in defaults.
|
||||
pub fn scale_list(&self) -> Vec<(String, f32, f64)> {
|
||||
let mut list: Vec<(String, f32, f64)> = self
|
||||
.document
|
||||
.objects
|
||||
.values()
|
||||
.filter_map(|o| match o {
|
||||
// Skip xref-derived scales. Scales pulled in from an external
|
||||
// reference get an "_XREF" suffix ("1:50_XREF"); unbound
|
||||
// dependent symbols carry a "xref|name" prefix. Neither
|
||||
// belongs to this drawing's own scale list.
|
||||
ObjectType::Scale(s)
|
||||
if !s.is_temporary
|
||||
&& !s.name.contains('|')
|
||||
&& !s.name.to_ascii_uppercase().ends_with("_XREF") =>
|
||||
{
|
||||
Some((s.name.clone(), s.inverse_factor() as f32, s.factor()))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
list.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(std::cmp::Ordering::Equal));
|
||||
list
|
||||
}
|
||||
|
||||
/// List of user viewports in the current layout: (handle, label, frozen_layer_handles).
|
||||
pub fn viewport_list(&self) -> Vec<(acadrust::Handle, String, Vec<acadrust::Handle>)> {
|
||||
if self.current_layout == "Model" {
|
||||
|
||||
+8
-20
@@ -7,35 +7,23 @@ use iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
|
||||
/// (label, annotation_scale_multiplier, viewport_scale_factor)
|
||||
/// annotation_scale = multiplier for text/dim sizes (50.0 for "1:50")
|
||||
/// vp_scale = custom_scale value on the Viewport entity (0.02 for "1:50")
|
||||
const COMMON_SCALES: &[(&str, f32, f64)] = &[
|
||||
("1:100", 100.0, 0.01),
|
||||
("1:50", 50.0, 0.02),
|
||||
("1:20", 20.0, 0.05),
|
||||
("1:10", 10.0, 0.10),
|
||||
("1:5", 5.0, 0.20),
|
||||
("1:2", 2.0, 0.50),
|
||||
("1:1", 1.0, 1.00),
|
||||
("2:1", 0.5, 2.00),
|
||||
("5:1", 0.2, 5.00),
|
||||
("10:1", 0.1, 10.00),
|
||||
];
|
||||
|
||||
/// Full-screen overlay: transparent click-catcher + scale list panel pinned bottom-right.
|
||||
///
|
||||
/// - `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).
|
||||
/// - `viewport_scale`: current effective vp scale, view_height-first (used to highlight in paper space).
|
||||
/// - `file_scales`: scale list read from the drawing (`ACAD_SCALELIST`). Only
|
||||
/// scales actually stored in the file are shown; the picker never injects
|
||||
/// scales of its own.
|
||||
pub fn scale_popup_overlay(
|
||||
is_model: bool,
|
||||
current_anno_scale: f32,
|
||||
viewport_scale: Option<f64>,
|
||||
file_scales: Vec<(String, f32, f64)>,
|
||||
) -> Element<'static, Message> {
|
||||
let rows: Vec<Element<'static, Message>> = COMMON_SCALES
|
||||
.iter()
|
||||
.map(|&(label, anno_scale, vp_scale)| {
|
||||
let rows: Vec<Element<'static, Message>> = file_scales
|
||||
.into_iter()
|
||||
.map(|(label, anno_scale, vp_scale)| {
|
||||
let active = if is_model {
|
||||
(current_anno_scale - anno_scale).abs() < 0.001 * current_anno_scale.max(0.001)
|
||||
} else {
|
||||
@@ -81,7 +69,7 @@ pub fn scale_popup_overlay(
|
||||
.into()
|
||||
}
|
||||
|
||||
fn scale_row(label: &'static str, active: bool, msg: Message) -> Element<'static, Message> {
|
||||
fn scale_row(label: String, active: bool, msg: Message) -> Element<'static, Message> {
|
||||
let check = text(if active { "✓" } else { " " })
|
||||
.size(11)
|
||||
.color(if active {
|
||||
|
||||
Reference in new issue
Block a user