Merge main and localize surveyor units

This commit is contained in:
Hakan Seven committed 2026-09-03 12:45:24 +03:00
commit 612ec8d60f
23 files changed
+179 -17

No files matched your search

+1
View File
@@ -3599,6 +3599,7 @@ dimstyle =
.move-text-outside-first = نقل النص إلى الخارج أولًا .move-text-outside-first = نقل النص إلى الخارج أولًا
.period = نقطة .period = نقطة
.space = مسافة .space = مسافة
.surveyors-units = وحدات المسّاح
style-manager = style-manager =
.alignment-spacing = تباعد المحاذاة .alignment-spacing = تباعد المحاذاة
+1
View File
@@ -3587,6 +3587,7 @@ dimstyle =
.move-text-outside-first = Първо премести текста навън .move-text-outside-first = Първо премести текста навън
.period = Точка .period = Точка
.space = Интервал .space = Интервал
.surveyors-units = Геодезически единици
style-manager = style-manager =
.alignment-spacing = Разстояние на подравняване .alignment-spacing = Разстояние на подравняване
+1
View File
@@ -3586,6 +3586,7 @@ dimstyle =
.move-text-outside-first = Nejprve přesuňte text ven .move-text-outside-first = Nejprve přesuňte text ven
.period = Období .period = Období
.space = Mezerník .space = Mezerník
.surveyors-units = Zeměměřické jednotky
style-manager = style-manager =
.alignment-spacing = Rozteč zarovnání .alignment-spacing = Rozteč zarovnání
+1
View File
@@ -3585,6 +3585,7 @@ dimstyle =
.move-text-outside-first = Zuerst Text nach außen verschieben .move-text-outside-first = Zuerst Text nach außen verschieben
.period = Punkt .period = Punkt
.space = Leerzeichen .space = Leerzeichen
.surveyors-units = Vermessungseinheiten
style-manager = style-manager =
.alignment-spacing = Ausrichtungsabstand .alignment-spacing = Ausrichtungsabstand
+1
View File
@@ -3588,6 +3588,7 @@ dimstyle =
.move-text-outside-first = Move text outside first .move-text-outside-first = Move text outside first
.period = Period .period = Period
.space = Space .space = Space
.surveyors-units = Surveyor's units
style-manager = style-manager =
.alignment-spacing = Alignment spacing .alignment-spacing = Alignment spacing
+1
View File
@@ -3587,6 +3587,7 @@ dimstyle =
.move-text-outside-first = Mover primero el texto fuera .move-text-outside-first = Mover primero el texto fuera
.period = Punto .period = Punto
.space = Espacio .space = Espacio
.surveyors-units = Unidades de topografía
style-manager = style-manager =
.alignment-spacing = Espaciado de alineación .alignment-spacing = Espaciado de alineación
+1
View File
@@ -3586,6 +3586,7 @@ dimstyle =
.move-text-outside-first = Siirrä teksti ensin ulos .move-text-outside-first = Siirrä teksti ensin ulos
.period = Kausi .period = Kausi
.space = Välilyönti .space = Välilyönti
.surveyors-units = Maanmittausyksiköt
style-manager = style-manager =
.alignment-spacing = Tasausvälit .alignment-spacing = Tasausvälit
+1
View File
@@ -3586,6 +3586,7 @@ dimstyle =
.move-text-outside-first = Déplacer d’abord le texte à l’extérieur .move-text-outside-first = Déplacer d’abord le texte à l’extérieur
.period = Point .period = Point
.space = Espace .space = Espace
.surveyors-units = Unités d’arpentage
style-manager = style-manager =
.alignment-spacing = Espacement d’alignement .alignment-spacing = Espacement d’alignement
+1
View File
@@ -3575,6 +3575,7 @@ dimstyle =
.move-text-outside-first = पहले पाठ बाहर ले जाएँ .move-text-outside-first = पहले पाठ बाहर ले जाएँ
.period = दशमलव बिंदु .period = दशमलव बिंदु
.space = रिक्त स्थान .space = रिक्त स्थान
.surveyors-units = सर्वेक्षण इकाइयाँ
style-manager = style-manager =
.alignment-spacing = संरेखण अंतराल .alignment-spacing = संरेखण अंतराल
+1
View File
@@ -3586,6 +3586,7 @@ dimstyle =
.move-text-outside-first = Először mozgassa ki a szöveget .move-text-outside-first = Először mozgassa ki a szöveget
.period = Időszak .period = Időszak
.space = Szóköz .space = Szóköz
.surveyors-units = Földmérési egységek
style-manager = style-manager =
.alignment-spacing = Igazítási távolság .alignment-spacing = Igazítási távolság
+1
View File
@@ -3586,6 +3586,7 @@ dimstyle =
.move-text-outside-first = Sposta prima il testo all'esterno .move-text-outside-first = Sposta prima il testo all'esterno
.period = Periodo .period = Periodo
.space = Spazio .space = Spazio
.surveyors-units = Unità topografiche
style-manager = style-manager =
.alignment-spacing = Spaziatura di allineamento .alignment-spacing = Spaziatura di allineamento
+1
View File
@@ -3575,6 +3575,7 @@ dimstyle =
.move-text-outside-first = 先に寸法値を外側へ移動 .move-text-outside-first = 先に寸法値を外側へ移動
.period = ピリオド .period = ピリオド
.space = 空白 .space = 空白
.surveyors-units = 測量単位
style-manager = style-manager =
.alignment-spacing = 位置合わせ間隔 .alignment-spacing = 位置合わせ間隔
+1
View File
@@ -3586,6 +3586,7 @@ dimstyle =
.move-text-outside-first = 먼저 텍스트를 외부로 이동하세요. .move-text-outside-first = 먼저 텍스트를 외부로 이동하세요.
.period = 기간 .period = 기간
.space = 스페이스바 .space = 스페이스바
.surveyors-units = 측량 단위
style-manager = style-manager =
.alignment-spacing = 정렬 간격 .alignment-spacing = 정렬 간격
+1
View File
@@ -3587,6 +3587,7 @@ dimstyle =
.move-text-outside-first = Eerst tekst naar buiten verplaatsen .move-text-outside-first = Eerst tekst naar buiten verplaatsen
.period = Punt .period = Punt
.space = Spatie .space = Spatie
.surveyors-units = Landmeeteenheden
style-manager = style-manager =
.alignment-spacing = Uitlijningsafstand .alignment-spacing = Uitlijningsafstand
+1
View File
@@ -3586,6 +3586,7 @@ dimstyle =
.move-text-outside-first = Najpierw przenieś tekst na zewnątrz .move-text-outside-first = Najpierw przenieś tekst na zewnątrz
.period = Okres .period = Okres
.space = Spacja .space = Spacja
.surveyors-units = Jednostki geodezyjne
style-manager = style-manager =
.alignment-spacing = Rozstaw wyrównania .alignment-spacing = Rozstaw wyrównania
+1
View File
@@ -3587,6 +3587,7 @@ dimstyle =
.move-text-outside-first = Mover primeiro o texto para fora .move-text-outside-first = Mover primeiro o texto para fora
.period = Ponto .period = Ponto
.space = Espaço .space = Espaço
.surveyors-units = Unidades de agrimensura
style-manager = style-manager =
.alignment-spacing = Espaçamento de alinhamento .alignment-spacing = Espaçamento de alinhamento
+1
View File
@@ -3590,6 +3590,7 @@ dimstyle =
.move-text-outside-first = Сначала вынести текст .move-text-outside-first = Сначала вынести текст
.period = Точка .period = Точка
.space = Пробел .space = Пробел
.surveyors-units = Геодезические единицы
style-manager = style-manager =
.alignment-spacing = Интервал выравнивания .alignment-spacing = Интервал выравнивания
+1
View File
@@ -3511,6 +3511,7 @@ dimstyle =
.move-text-outside-first = Önce metni dışarı taşı .move-text-outside-first = Önce metni dışarı taşı
.period = Nokta .period = Nokta
.space = Boşluk .space = Boşluk
.surveyors-units = Haritacılık birimleri
style-manager = style-manager =
.alignment-spacing = Hizalama aralığı .alignment-spacing = Hizalama aralığı
+1
View File
@@ -3570,6 +3570,7 @@ dimstyle =
.move-text-outside-first = 优先将文字移到外部 .move-text-outside-first = 优先将文字移到外部
.period = 句点 .period = 句点
.space = 空格 .space = 空格
.surveyors-units = 测量单位
style-manager = style-manager =
.alignment-spacing = 对齐间距 .alignment-spacing = 对齐间距
+1
View File
@@ -3582,6 +3582,7 @@ dimstyle =
.move-text-outside-first = 優先將文字移到外部 .move-text-outside-first = 優先將文字移到外部
.period = 句點 .period = 句點
.space = 空白鍵 .space = 空白鍵
.surveyors-units = 測量單位
style-manager = style-manager =
.alignment-spacing = 對齊間距 .alignment-spacing = 對齊間距
+23 -2
View File
@@ -207,7 +207,13 @@ impl Parser {
Ok(left) Ok(left)
} }
// power → unary ('^' unary)? // power → unary ('^' power)?
//
// The exponent recurses into `parse_power`, not `parse_unary`, so `^` is
// right-associative — `2^3^2` is `2^(3^2)`, the standard reading. Binding
// it to the left both gave the wrong number and left the trailing `^2`
// unconsumed, which the caller's scan then handed back as literal text:
// `2^3^2` evaluated to the string "8^2" instead of 512.
fn parse_power(&mut self, depth: usize) -> Result<f64, ()> { fn parse_power(&mut self, depth: usize) -> Result<f64, ()> {
if depth > MAX_RECURSION { if depth > MAX_RECURSION {
return Err(()); return Err(());
@@ -215,7 +221,7 @@ impl Parser {
let base = self.parse_unary(depth + 1)?; let base = self.parse_unary(depth + 1)?;
if let Some(Token::Caret) = self.peek() { if let Some(Token::Caret) = self.peek() {
self.advance(); self.advance();
let exp = self.parse_unary(depth + 1)?; let exp = self.parse_power(depth + 1)?;
return Ok(base.powf(exp)); return Ok(base.powf(exp));
} }
Ok(base) Ok(base)
@@ -591,6 +597,21 @@ mod tests {
assert_eq!(eval_to_string("(((10)))"), "10"); assert_eq!(eval_to_string("(((10)))"), "10");
} }
// `^` is right-associative, so a chained exponent evaluates as one number
// instead of stopping at the first `^` and leaving the rest as text.
#[test]
fn power_is_right_associative() {
assert_eq!(eval_to_string("2^3^2"), "512");
assert_eq!(eval_to_string("2^3^2+1"), "513");
assert_eq!(eval_to_string("4^0.5^2"), "1.4142135623730951"); // 4^(1/4)
assert_eq!(eval_to_string("2^2^2^2"), "65536");
// A single exponent, and a parenthesised one, are unaffected.
assert_eq!(eval_to_string("2^3"), "8");
assert_eq!(eval_to_string("2^(3^2)"), "512");
assert_eq!(eval_to_string("(2^3)^2"), "64");
assert_eq!(eval_to_string("2^-1"), "0.5");
}
#[test] #[test]
fn unary_ops() { fn unary_ops() {
assert_eq!(eval_to_string("--5"), "5"); assert_eq!(eval_to_string("--5"), "5");
+135 -15
View File
@@ -3803,28 +3803,45 @@ fn tessellate_dimension_inner(
); );
// Per-spec colours: DIMCLRD (dim/arrows), DIMCLRE (ext), DIMCLRT (text). // Per-spec colours: DIMCLRD (dim/arrows), DIMCLRE (ext), DIMCLRT (text).
// 0=ByBlock and 256=ByLayer fall through to entity_color. DIMCLRD also // 0=ByBlock and 256=ByLayer fall through to entity_color. Each honours the
// honours a per-object ACAD_DSTYLE override (code 176) so an edited // per-object ACAD_DSTYLE override (codes 176/177/178) the Properties panel
// dim-line colour renders even without touching the style. // reads and writes, so an edited colour renders without touching the style.
let xd = &dim.base().common.extended_data;
let dim_color = if selected { let dim_color = if selected {
WireModel::SELECTED WireModel::SELECTED
} else { } else {
let dim_clr = crate::entities::dim_override::int( resolve_dim_color(
&dim.base().common.extended_data, dim_color_index(
crate::entities::dim_override::DIMCLRD, xd,
crate::entities::dim_override::DIMCLRD,
style.map(|s| s.dimclrd).unwrap_or(0),
),
entity_color,
) )
.unwrap_or_else(|| style.map(|s| s.dimclrd).unwrap_or(0));
resolve_dim_color(dim_clr, entity_color)
}; };
let ext_color = if selected { let ext_color = if selected {
WireModel::SELECTED WireModel::SELECTED
} else { } else {
resolve_dim_color(style.map(|s| s.dimclre).unwrap_or(0), entity_color) resolve_dim_color(
dim_color_index(
xd,
crate::entities::dim_override::DIMCLRE,
style.map(|s| s.dimclre).unwrap_or(0),
),
entity_color,
)
}; };
let text_color = if selected { let text_color = if selected {
entity_color // text wire color set by inner tessellate; keep entity tint entity_color // text wire color set by inner tessellate; keep entity tint
} else { } else {
resolve_dim_color(style.map(|s| s.dimclrt).unwrap_or(0), entity_color) resolve_dim_color(
dim_color_index(
xd,
crate::entities::dim_override::DIMCLRT,
style.map(|s| s.dimclrt).unwrap_or(0),
),
entity_color,
)
}; };
let snap_pts = dimension_snap_pts(dim); let snap_pts = dimension_snap_pts(dim);
@@ -4194,6 +4211,18 @@ fn tessellate_dimension_inner(
wires wires
} }
/// The ACI index a dimension colour renders with: the per-object ACAD_DSTYLE
/// override when the entity carries one, else the dimension style's value.
/// The Properties panel reads and writes these same overrides, so the renderer
/// has to consult them or an edited colour shows in the panel and nowhere else.
fn dim_color_index(
xd: &acadrust::xdata::ExtendedData,
code: i16,
inherited: i16,
) -> i16 {
crate::entities::dim_override::int(xd, code).unwrap_or(inherited)
}
fn resolve_dim_color(idx: i16, fallback: [f32; 4]) -> [f32; 4] { fn resolve_dim_color(idx: i16, fallback: [f32; 4]) -> [f32; 4] {
// DIMCLR* convention: 0 = BYBLOCK, 256 = BYLAYER → entity colour wins. // DIMCLR* convention: 0 = BYBLOCK, 256 = BYLAYER → entity colour wins.
if idx == 0 || idx == 256 { if idx == 0 || idx == 256 {
@@ -6432,10 +6461,16 @@ fn format_with_unit(
} }
fn format_engineering(inches: f64, dec: usize) -> String { fn format_engineering(inches: f64, dec: usize) -> String {
let sign = if inches < 0.0 { "-" } else { "" }; // Round to the printed precision *first*, then split, so inches that round
let abs = inches.abs(); // up to a full foot carry into the feet instead of printing an
let feet = (abs / 12.0).trunc(); // out-of-range "2'-12.00"". Same reasoning as `format_architectural`,
let rem_in = abs - feet * 12.0; // where the grid is the fraction denominator rather than a decimal place.
let scale = 10f64.powi(dec.min(15) as i32);
let ticks = (inches.abs() * scale).round();
let per_foot = 12.0 * scale;
let sign = if inches < 0.0 && ticks != 0.0 { "-" } else { "" };
let feet = (ticks / per_foot).trunc();
let rem_in = (ticks - feet * per_foot) / scale;
format!("{}{:.0}'-{:.*}\"", sign, feet, dec, rem_in) format!("{}{:.0}'-{:.*}\"", sign, feet, dec, rem_in)
} }
@@ -7349,9 +7384,71 @@ mod angular_unit_tests {
} }
} }
#[cfg(test)]
mod dim_color_override_tests {
use super::dim_color_index;
use crate::entities::dim_override::{DIMCLRD, DIMCLRE, DIMCLRT};
use acadrust::xdata::{ExtendedData, ExtendedDataRecord, XDataValue};
// An ACAD/DSTYLE record holding the overrides the Properties panel writes.
fn xdata(overrides: &[(i16, i16)]) -> ExtendedData {
let mut values = vec![
XDataValue::String("DSTYLE".to_string()),
XDataValue::ControlString("{".to_string()),
];
for (code, aci) in overrides {
values.push(XDataValue::Integer16(*code));
values.push(XDataValue::Integer16(*aci));
}
values.push(XDataValue::ControlString("}".to_string()));
let mut record = ExtendedDataRecord::new("ACAD");
for value in values {
record.add_value(value);
}
let mut xd = ExtendedData::new();
xd.add_record(record);
xd
}
// Every dim colour honours its per-object override, not just DIMCLRD.
// Text and extension-line colours edited on the Properties panel used to
// show there and render with the style/layer colour anyway. (#898)
#[test]
fn override_wins_over_style_for_every_dim_colour() {
let xd = xdata(&[(DIMCLRD, 1), (DIMCLRE, 3), (DIMCLRT, 5)]);
assert_eq!(dim_color_index(&xd, DIMCLRD, 256), 1);
assert_eq!(dim_color_index(&xd, DIMCLRE, 256), 3);
assert_eq!(dim_color_index(&xd, DIMCLRT, 256), 5);
}
// Only the overridden variable changes; the rest still inherit the style.
#[test]
fn absent_override_falls_back_to_the_style_value() {
let xd = xdata(&[(DIMCLRT, 5)]);
assert_eq!(dim_color_index(&xd, DIMCLRT, 256), 5);
assert_eq!(dim_color_index(&xd, DIMCLRD, 7), 7);
assert_eq!(dim_color_index(&xd, DIMCLRE, 256), 256);
}
// An entity with no override record at all is unaffected.
#[test]
fn no_record_uses_the_style_value() {
let xd = ExtendedData::new();
assert_eq!(dim_color_index(&xd, DIMCLRT, 2), 2);
}
// ByLayer (256) and ByBlock (0) stay meaningful as override values, so an
// explicit "back to ByLayer" edit is not mistaken for "no override".
#[test]
fn bylayer_override_is_kept() {
let xd = xdata(&[(DIMCLRT, 256)]);
assert_eq!(dim_color_index(&xd, DIMCLRT, 5), 256);
}
}
#[cfg(test)] #[cfg(test)]
mod arch_format_tests { mod arch_format_tests {
use super::{format_architectural, format_fractional}; use super::{format_architectural, format_engineering, format_fractional};
// A 3ft object that measures 35.99" (float noise) must carry the rounded // A 3ft object that measures 35.99" (float noise) must carry the rounded
// fraction up through inches into feet — not print "2'-11 1"". (Regression // fraction up through inches into feet — not print "2'-11 1"". (Regression
@@ -7384,4 +7481,27 @@ mod arch_format_tests {
assert_eq!(format_fractional(11.999, 4, 2), "12"); assert_eq!(format_fractional(11.999, 4, 2), "12");
assert_eq!(format_fractional(6.5, 4, 2), "6 1/2"); assert_eq!(format_fractional(6.5, 4, 2), "6 1/2");
} }
// Engineering units round to DIMDEC before the feet/inches split, so a
// remainder that rounds up to 12" carries instead of printing "2'-12.00"".
#[test]
fn engineering_carries_inches_up_to_feet() {
assert_eq!(format_engineering(35.999, 2), "3'-0.00\"");
assert_eq!(format_engineering(11.999, 2), "1'-0.00\"");
assert_eq!(format_engineering(11.9, 0), "1'-0\"");
}
#[test]
fn engineering_normal_values_unchanged() {
assert_eq!(format_engineering(30.5, 2), "2'-6.50\"");
assert_eq!(format_engineering(36.0, 2), "3'-0.00\"");
assert_eq!(format_engineering(0.0, 2), "0'-0.00\"");
assert_eq!(format_engineering(-30.25, 2), "-2'-6.25\"");
}
// A magnitude that rounds away to zero must not keep a lone minus sign.
#[test]
fn engineering_negative_zero_has_no_sign() {
assert_eq!(format_engineering(-0.001, 2), "0'-0.00\"");
}
} }
+1
View File
@@ -3224,6 +3224,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"Suppress second extension line" => Some(("dimstyle", "suppress-second-extension-line")), "Suppress second extension line" => Some(("dimstyle", "suppress-second-extension-line")),
"Suppress zero feet and zero inches" => Some(("dimstyle", "suppress-zero-feet-and-inches")), "Suppress zero feet and zero inches" => Some(("dimstyle", "suppress-zero-feet-and-inches")),
"Suppress zero feet; show zero inches" => Some(("dimstyle", "suppress-zero-feet-show-zero-inches")), "Suppress zero feet; show zero inches" => Some(("dimstyle", "suppress-zero-feet-show-zero-inches")),
"Surveyor's units" => Some(("dimstyle", "surveyors-units")),
"Symbols and Arrows" => Some(("dimstyle", "symbols-and-arrows")), "Symbols and Arrows" => Some(("dimstyle", "symbols-and-arrows")),
"Symmetrical" => Some(("dimstyle", "symmetrical")), "Symmetrical" => Some(("dimstyle", "symmetrical")),
"Text background" => Some(("dimstyle", "text-background")), "Text background" => Some(("dimstyle", "text-background")),