forked from 1b-agent/opencadstudio-upstream
fix(plot): write page setups in the file format's canonical form
One writer (plot_settings_from_dialog) now serves both the layout's embedded settings and named page setups, so the two can no longer drift apart: - media are stored as the portrait sheet with the orientation in the rotation code (0/90/180/270; upside-down adds a half turn) instead of swapped width/height with rotation limited to 0/180 - standard scales are written by their code (1:1 … 100:1) with matching numerator/denominator; other scales as a readable custom ratio in the layout's own paper unit, converting from the drawing's scale-family unit (a metre drawing plotting "1:100" stores 10 mm = 1 unit, an inch layout stores 1 in = 2540 units); reading converts back and keeps a ratio the scale list has no name for instead of snapping to 1:1 - the layout's paper unit and every flag the dialog does not edit survive a round trip untouched; layout limits follow the paper-space unit - new drawings, new layouts, CLEAR and the automation "new" op share one default page setup (canonical A4 name, rotation 90, none_device, 1:1 layout plot) through Scene::reset_to_new_drawing Scene gains scale_family_unit_mm, scale_ratio and a scale-name ratio parser (ratios and architectural names). Round-trip tests cover orientation, standard/custom/fit scales, unit conversion, named-setup parity, preserved flags and the default layout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ef0c6bf403
commit
276e7c7bf1
8 files changed
+641
-263
No files matched your search
@@ -33,6 +33,7 @@ pub(crate) mod centermark;
|
||||
pub(crate) mod dimension_assoc;
|
||||
pub(crate) mod dimension_assoc_chain;
|
||||
pub use dimension_assoc::{ReferenceStatus, ResolvedReference};
|
||||
pub use page_setup::apply_default_page_setup;
|
||||
mod dwg_native_constraints;
|
||||
mod entity;
|
||||
#[cfg(test)]
|
||||
@@ -4953,6 +4954,78 @@ impl Scene {
|
||||
.then_some(owner)
|
||||
}
|
||||
|
||||
/// Millimetres in one unit of the paper side of this drawing's scale
|
||||
/// family: 25.4 for an imperial drawing (whose `1/8" = 1'-0"` style scales
|
||||
/// measure paper in inches), 1 for a metric one. Plot-scale factors built
|
||||
/// from [`Scene::scale_list`] are expressed per this unit; page setups
|
||||
/// store theirs per the layout's own paper unit, so the two are converted
|
||||
/// through this value.
|
||||
pub(crate) fn scale_family_unit_mm(&self) -> f64 {
|
||||
if self.prefers_imperial_scales() == Some(true) {
|
||||
25.4
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The `paper : drawing` ratio behind a scale-list name, from the
|
||||
/// drawing's own `Scale` object when it has one, otherwise from the
|
||||
/// built-in family. A ratio keeps the plot scale readable in a page setup
|
||||
/// (`1 : 250` rather than `0.004 : 1`).
|
||||
pub(crate) fn scale_ratio(&self, name: &str) -> Option<(f64, f64)> {
|
||||
use acadrust::objects::ObjectType;
|
||||
let stored = self.document.objects.values().find_map(|o| match o {
|
||||
ObjectType::Scale(s) if !s.is_temporary && s.name.eq_ignore_ascii_case(name) => {
|
||||
Some((s.paper_units, s.drawing_units))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
stored
|
||||
.or_else(|| Self::parse_scale_name_ratio(name))
|
||||
.filter(|(paper, drawing)| *paper > 0.0 && *drawing > 0.0)
|
||||
}
|
||||
|
||||
/// The `paper : drawing` ratio spelled by a scale name: `1:250` gives
|
||||
/// `(1, 250)`, an architectural `1/8" = 1'-0"` gives `(0.125, 12)` in
|
||||
/// inches. Anything else is not a scale name.
|
||||
pub(crate) fn parse_scale_name_ratio(name: &str) -> Option<(f64, f64)> {
|
||||
if let Some((paper, drawing)) = name.split_once(':') {
|
||||
let paper: f64 = paper.trim().parse().ok()?;
|
||||
let drawing: f64 = drawing.trim().parse().ok()?;
|
||||
return (paper > 0.0 && drawing > 0.0).then_some((paper, drawing));
|
||||
}
|
||||
let (paper, drawing) = name.split_once('=')?;
|
||||
let paper = Self::parse_feet_inches(paper)?;
|
||||
let drawing = Self::parse_feet_inches(drawing)?;
|
||||
(paper > 0.0 && drawing > 0.0).then_some((paper, drawing))
|
||||
}
|
||||
|
||||
/// Inches in a `F'-I"` / `I"` / `F'` length, where the inch part may be a
|
||||
/// fraction (`3/32"`).
|
||||
fn parse_feet_inches(text: &str) -> Option<f64> {
|
||||
let text = text.trim();
|
||||
let (feet, inches) = match text.split_once('\'') {
|
||||
Some((feet, rest)) => (
|
||||
feet.trim().parse::<f64>().ok()?,
|
||||
rest.trim_start_matches('-').trim(),
|
||||
),
|
||||
None => (0.0, text),
|
||||
};
|
||||
let inches = inches.trim_end_matches('"').trim();
|
||||
let inches = if inches.is_empty() {
|
||||
0.0
|
||||
} else if let Some((num, den)) = inches.split_once('/') {
|
||||
let den: f64 = den.trim().parse().ok()?;
|
||||
if den <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
num.trim().parse::<f64>().ok()? / den
|
||||
} else {
|
||||
inches.parse::<f64>().ok()?
|
||||
};
|
||||
Some(feet * 12.0 + inches)
|
||||
}
|
||||
|
||||
/// Handle of the real (non-temporary) `Scale` object with this display name.
|
||||
/// Scales are matched by their `name` field, not the dictionary key — some
|
||||
/// files key the entries by an internal identifier (`*A1`, `A0`, …) that
|
||||
|
||||
Reference in new issue
Block a user