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:
parent
7a1c0ead0d
commit
ceddb287f5
3 changed files with 131 additions and 35 deletions
|
|
@ -910,10 +910,10 @@ impl OpenCADStudio {
|
|||
// style crosses Dimension / Leader / Tolerance.
|
||||
let src_clone = self.tabs[i].scene.document.get_entity(src).cloned();
|
||||
let transparency = src_clone.as_ref().map(|e| e.common().transparency.clone());
|
||||
// Dimension-style OVERRIDES ride the ACAD_DSTYLE xdata record;
|
||||
// matching replicates the source's record (or clears the
|
||||
// destination's when the source has none).
|
||||
let dstyle_xdata: Option<Vec<acadrust::xdata::XDataValue>> = src_clone
|
||||
// Dimension-style overrides ride the ACAD record, identified
|
||||
// by a leading DSTYLE string. Matching replicates that payload
|
||||
// (or clears the destination when the source has none).
|
||||
let dstyle_xdata: Option<Vec<(i16, acadrust::xdata::XDataValue)>> = src_clone
|
||||
.as_ref()
|
||||
.filter(|e| {
|
||||
matches!(
|
||||
|
|
@ -921,8 +921,7 @@ impl OpenCADStudio {
|
|||
acadrust::EntityType::Dimension(_) | acadrust::EntityType::Leader(_)
|
||||
)
|
||||
})
|
||||
.and_then(|e| e.common().extended_data.get_record("ACAD_DSTYLE"))
|
||||
.map(|r| r.values.clone());
|
||||
.map(|e| crate::entities::dim_override::pairs(&e.common().extended_data));
|
||||
|
||||
if let Some((layer, color, linetype, lt_scale, lw)) = props {
|
||||
self.push_undo_snapshot(i, "MATCHPROP");
|
||||
|
|
@ -944,9 +943,9 @@ impl OpenCADStudio {
|
|||
match_special_props(se, e);
|
||||
}
|
||||
}
|
||||
// Dim-style overrides (ACAD_DSTYLE) follow the style
|
||||
// for dimension / leader destinations — through
|
||||
// set_entity_xdata so no stale raw record survives.
|
||||
// Dim-style overrides follow the style for dimension /
|
||||
// leader destinations — through set_entity_xdata so no
|
||||
// stale raw record survives.
|
||||
if dstyle_xdata.is_some()
|
||||
|| matches!(
|
||||
self.tabs[i].scene.document.get_entity(*h),
|
||||
|
|
@ -969,11 +968,10 @@ impl OpenCADStudio {
|
|||
| acadrust::EntityType::Leader(_)
|
||||
)
|
||||
) {
|
||||
crate::scene::view::dispatch::set_entity_xdata(
|
||||
crate::entities::dim_override::replace(
|
||||
&mut self.tabs[i].scene.document,
|
||||
*h,
|
||||
"ACAD_DSTYLE",
|
||||
dstyle_xdata.clone(),
|
||||
dstyle_xdata.clone().unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2931,4 +2929,3 @@ fn match_special_props(src: &acadrust::EntityType, dst: &mut acadrust::EntityTyp
|
|||
dm.property_override_flags = sm.property_override_flags;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
56
src/scene/cache/block_cache.rs
vendored
56
src/scene/cache/block_cache.rs
vendored
|
|
@ -1306,6 +1306,49 @@ fn resolve_wire_color(lw: &LocalWire, ctx: &ExpandCtx) -> [f32; 4] {
|
|||
}
|
||||
}
|
||||
|
||||
/// Effective linetype scale along this wire after an INSERT transform.
|
||||
///
|
||||
/// A non-uniform INSERT has no single global scale. Weight each segment by its
|
||||
/// local length, producing the exact factor for a line and a stable
|
||||
/// path-weighted approximation for a polyline or tessellated curve.
|
||||
fn transformed_wire_length_scale(lw: &LocalWire, xform: &Transform) -> f32 {
|
||||
let mut local_length = 0.0_f64;
|
||||
let mut transformed_length = 0.0_f64;
|
||||
let mut previous: Option<Vector3> = None;
|
||||
|
||||
for (index, point) in lw.points.iter().enumerate() {
|
||||
if !point.iter().all(|v| v.is_finite()) {
|
||||
previous = None;
|
||||
continue;
|
||||
}
|
||||
let low = lw.points_low.get(index).copied().unwrap_or([0.0; 3]);
|
||||
let current = Vector3::new(
|
||||
point[0] as f64 + low[0] as f64,
|
||||
point[1] as f64 + low[1] as f64,
|
||||
point[2] as f64 + low[2] as f64,
|
||||
);
|
||||
if let Some(prev) = previous {
|
||||
let delta = current - prev;
|
||||
let segment_length = (delta.x * delta.x + delta.y * delta.y + delta.z * delta.z).sqrt();
|
||||
if segment_length > 1e-12 {
|
||||
let transformed = xform.matrix.transform_direction(delta);
|
||||
local_length += segment_length;
|
||||
transformed_length += (transformed.x * transformed.x
|
||||
+ transformed.y * transformed.y
|
||||
+ transformed.z * transformed.z)
|
||||
.sqrt();
|
||||
}
|
||||
}
|
||||
previous = Some(current);
|
||||
}
|
||||
|
||||
if local_length > 1e-12 && transformed_length.is_finite() {
|
||||
(transformed_length / local_length) as f32
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_wire(
|
||||
lw: &LocalWire,
|
||||
accum_xform: &Transform,
|
||||
|
|
@ -1338,8 +1381,17 @@ fn emit_wire(
|
|||
} else {
|
||||
lw.line_weight_px
|
||||
};
|
||||
let final_pat_len = final_pat_len * ctx.pslt_factor;
|
||||
let final_pat = final_pat.map(|v| v * ctx.pslt_factor);
|
||||
|
||||
// Pattern distances are stored in block-local units. Scale them by the
|
||||
// wire's actual path-length ratio so uniform and non-uniform INSERTs both
|
||||
// stay dimensionally consistent with their transformed geometry.
|
||||
let pattern_scale = if final_pat_len > 0.0 {
|
||||
transformed_wire_length_scale(lw, accum_xform)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let final_pat_len = final_pat_len * ctx.pslt_factor * pattern_scale;
|
||||
let final_pat = final_pat.map(|v| v * ctx.pslt_factor * pattern_scale);
|
||||
|
||||
// A wide polyline's band width is baked in block-local units; scale it by
|
||||
// the insert transform so the shader band matches the scaled geometry.
|
||||
|
|
|
|||
Loading…
Reference in a new issue