fix(renderer): match leader and dash styles

Read canonical ACAD/DSTYLE overrides for leader color and arrow size. Scale block-local linetype patterns through INSERT transforms.

Closes #436
This commit is contained in:
Hakan Seven committed 2026-07-24 19:04:57 +03:00
1 parent 7a1c0ead0d
commit ceddb287f5
3 files changed
+131 -35

No files matched your search

+67 -20
View File
@@ -1,10 +1,11 @@
//! Per-object dimension-variable overrides.
//!
//! A leader (or dimension) that departs from its dimension style stores the
//! changed variables in the standard `ACAD_DSTYLE` XDATA record as a list of
//! (dimvar group code, value) pairs wrapped in `{ }` control strings. Both the
//! renderer and the properties panel prefer an override over the style default,
//! so editing one of these rows writes here and the change round-trips to file.
//! changed variables in the standard `ACAD` XDATA record, identified by a
//! leading `DSTYLE` string, as a list of (dimvar group code, value) pairs wrapped
//! in `{ }` control strings. Both the renderer and the properties panel prefer
//! an override over the style default, so editing one of these rows writes here
//! and the change round-trips to file.
use acadrust::types::Color;
use acadrust::xdata::{ExtendedData, XDataValue};
@@ -19,13 +20,24 @@ pub const DIMGAP: i16 = 147; // text offset / gap (real)
pub const DIMLWD: i16 = 371; // dim line lineweight (int16)
pub const DIMLDRBLK: i16 = 341; // leader arrow block (handle)
/// Every (code, value) override present in the `ACAD_DSTYLE` record.
/// Every (code, value) override present in the `ACAD`/`DSTYLE` record.
pub fn pairs(xd: &ExtendedData) -> Vec<(i16, XDataValue)> {
let Some(rec) = xd.get_record("ACAD_DSTYLE") else {
let values = xd
.get_record("ACAD")
.and_then(|rec| match rec.values.first() {
Some(XDataValue::String(name)) if name == "DSTYLE" => Some(&rec.values[1..]),
_ => None,
})
// Retain compatibility with records produced by older OCS versions.
.or_else(|| {
xd.get_record("ACAD_DSTYLE")
.map(|rec| rec.values.as_slice())
});
let Some(values) = values else {
return Vec::new();
};
let mut out = Vec::new();
let mut it = rec.values.iter();
let mut it = values.iter();
// The record is a flat stream: a 1070 code marker followed by its typed
// value, bracketed by 1002 "{" / "}" control strings (which are skipped).
while let Some(v) = it.next() {
@@ -78,9 +90,55 @@ pub fn handle(xd: &ExtendedData, code: i16) -> Option<Handle> {
})
}
fn write_pairs(doc: &mut CadDocument, handle: Handle, pairs: Vec<(i16, XDataValue)>) {
let Some(entity) = doc.get_entity(handle) else {
return;
};
let use_canonical_record = entity
.common()
.extended_data
.get_record("ACAD")
.map(|rec| {
matches!(
rec.values.first(),
Some(XDataValue::String(name)) if name == "DSTYLE"
)
})
.unwrap_or(true);
let mut values = if pairs.is_empty() {
None
} else {
let mut vals = vec![XDataValue::ControlString("{".to_string())];
for (code, value) in pairs {
vals.push(XDataValue::Integer16(code));
vals.push(value);
}
vals.push(XDataValue::ControlString("}".to_string()));
Some(vals)
};
if use_canonical_record {
if let Some(vals) = &mut values {
vals.insert(0, XDataValue::String("DSTYLE".to_string()));
}
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "ACAD_DSTYLE", None);
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "ACAD", values);
} else {
// Preserve unrelated Autodesk XDATA already occupying the ACAD record.
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "ACAD_DSTYLE", values);
}
}
/// Replace every dimension-variable override on entity `handle`.
pub fn replace(doc: &mut CadDocument, handle: Handle, values: Vec<(i16, XDataValue)>) {
write_pairs(doc, handle, values);
}
/// Set — or, with `value: None`, clear — a single override on entity `handle`,
/// leaving the other overrides in the record untouched. Clearing the last one
/// drops the whole `ACAD_DSTYLE` record.
/// drops the whole `ACAD`/`DSTYLE` record. Legacy `ACAD_DSTYLE` records written
/// by older OCS versions are migrated when the canonical `ACAD` slot is free.
pub fn set(doc: &mut CadDocument, handle: Handle, code: i16, value: Option<XDataValue>) {
let Some(entity) = doc.get_entity(handle) else {
return;
@@ -92,16 +150,5 @@ pub fn set(doc: &mut CadDocument, handle: Handle, code: i16, value: Option<XData
if let Some(v) = value {
kept.push((code, v));
}
let values = if kept.is_empty() {
None
} else {
let mut vals = vec![XDataValue::ControlString("{".to_string())];
for (c, v) in kept {
vals.push(XDataValue::Integer16(c));
vals.push(v);
}
vals.push(XDataValue::ControlString("}".to_string()));
Some(vals)
};
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "ACAD_DSTYLE", values);
write_pairs(doc, handle, kept);
}