feat(matchprop): match every style-affecting property (#281)
MATCHPROP copied only the general properties (and text style/height), so matching dimension-to-dimension or hatch-to-hatch looked like it did nothing — the visible differences live in type-specific style fields. General set gains transparency. match_special_props transfers every STYLE-affecting field the destination supports — never content or placement: - Text: style, height, width factor, oblique (style+height cross TEXT <-> MTEXT); MText adds line spacing, drawing direction and the background fill block. - Dimension / Leader / Tolerance: the dim style name crosses all three, and the ACAD_DSTYLE override record is replicated through set_entity_xdata (cleared on the destination when the source carries none, so the match is exact). Restyled dimensions drop their baked *D block and repaint immediately. - Hatch: pattern definition, type, angle, scale, solid flag and gradient — the boundary stays. - LwPolyline: constant width (vertex widths included) and linetype generation. - MultiLeader: style plus all style overrides (path type, line color/type/weight, landing/dogleg, arrowhead, text style/color/ frame/height, overall scale, override flags); leader geometry, text and block content stay. Closes #281 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e7eb84b514
commit
90fd69df06
1 changed files with 172 additions and 24 deletions
|
|
@ -903,22 +903,30 @@ impl OpenCADStudio {
|
|||
c.line_weight,
|
||||
)
|
||||
});
|
||||
// Text-specific properties travel between text-like objects
|
||||
// (TEXT ↔ MTEXT, both directions), like AutoCAD's "Text"
|
||||
// special property (#361).
|
||||
let text_props =
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(src)
|
||||
.and_then(|e| match e {
|
||||
acadrust::EntityType::Text(t) => Some((t.style.clone(), t.height)),
|
||||
acadrust::EntityType::MText(m) => Some((m.style.clone(), m.height)),
|
||||
_ => None,
|
||||
});
|
||||
// Special (type-specific) properties travel like AutoCAD's
|
||||
// Special Properties: each is captured from the source when it
|
||||
// carries it and applied only to destinations that support it
|
||||
// (#281). Text formatting crosses TEXT ↔ MTEXT (#361); the dim
|
||||
// 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
|
||||
.as_ref()
|
||||
.filter(|e| {
|
||||
matches!(
|
||||
e,
|
||||
acadrust::EntityType::Dimension(_) | acadrust::EntityType::Leader(_)
|
||||
)
|
||||
})
|
||||
.and_then(|e| e.common().extended_data.get_record("ACAD_DSTYLE"))
|
||||
.map(|r| r.values.clone());
|
||||
|
||||
if let Some((layer, color, linetype, lt_scale, lw)) = props {
|
||||
self.push_undo_snapshot(i, "MATCHPROP");
|
||||
let mut any_dim = false;
|
||||
for h in &dest {
|
||||
if let Some(e) = self.tabs[i].scene.document.get_entity_mut(*h) {
|
||||
e.as_entity_mut().set_layer(layer.clone());
|
||||
|
|
@ -926,20 +934,59 @@ impl OpenCADStudio {
|
|||
crate::scene::view::dispatch::apply_line_weight(e, lw);
|
||||
e.common_mut().linetype = linetype.clone();
|
||||
e.common_mut().linetype_scale = lt_scale;
|
||||
if let Some((style, height)) = &text_props {
|
||||
match e {
|
||||
acadrust::EntityType::Text(t) => {
|
||||
t.style = style.clone();
|
||||
t.height = *height;
|
||||
}
|
||||
acadrust::EntityType::MText(m) => {
|
||||
m.style = style.clone();
|
||||
m.height = *height;
|
||||
}
|
||||
_ => {}
|
||||
if let Some(t) = &transparency {
|
||||
e.common_mut().transparency = t.clone();
|
||||
}
|
||||
if let Some(se) = &src_clone {
|
||||
if matches!(e, acadrust::EntityType::Dimension(_)) {
|
||||
any_dim = true;
|
||||
}
|
||||
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.
|
||||
if dstyle_xdata.is_some()
|
||||
|| matches!(
|
||||
self.tabs[i].scene.document.get_entity(*h),
|
||||
Some(
|
||||
acadrust::EntityType::Dimension(_)
|
||||
| acadrust::EntityType::Leader(_)
|
||||
)
|
||||
)
|
||||
{
|
||||
if matches!(
|
||||
self.tabs[i].scene.document.get_entity(*h),
|
||||
Some(
|
||||
acadrust::EntityType::Dimension(_)
|
||||
| acadrust::EntityType::Leader(_)
|
||||
)
|
||||
) && matches!(
|
||||
src_clone,
|
||||
Some(
|
||||
acadrust::EntityType::Dimension(_)
|
||||
| acadrust::EntityType::Leader(_)
|
||||
)
|
||||
) {
|
||||
crate::scene::view::dispatch::set_entity_xdata(
|
||||
&mut self.tabs[i].scene.document,
|
||||
*h,
|
||||
"ACAD_DSTYLE",
|
||||
dstyle_xdata.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// A restyled dimension renders from its baked *D block —
|
||||
// drop the stale block so the new style shows (#398).
|
||||
if any_dim {
|
||||
crate::modules::draw::modify::explode::invalidate_dim_block(
|
||||
&mut self.tabs[i].scene.document,
|
||||
*h,
|
||||
);
|
||||
}
|
||||
// Hatch fills render from a prebuilt model (#415).
|
||||
self.tabs[i].scene.refresh_fill_model(*h);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
// Color / linetype / lineweight are baked into the cached
|
||||
|
|
@ -2768,3 +2815,104 @@ fn apply_mleader_collect(scene: &mut crate::scene::Scene, encoded: &str) {
|
|||
// Erase the secondary multileaders.
|
||||
scene.erase_entities(&handles[1..]);
|
||||
}
|
||||
|
||||
/// MATCHPROP special properties (#281): copy every STYLE-affecting field from
|
||||
/// `src` to `dst` when the destination supports it — never content (text
|
||||
/// strings, block names) or placement (positions, rotations of the object
|
||||
/// itself). Text formatting crosses TEXT ↔ MTEXT; the dimension style crosses
|
||||
/// Dimension / Leader / Tolerance.
|
||||
fn match_special_props(src: &acadrust::EntityType, dst: &mut acadrust::EntityType) {
|
||||
use acadrust::EntityType as E;
|
||||
|
||||
// Text-ish source formatting.
|
||||
let text_fmt = match src {
|
||||
E::Text(t) => Some((t.style.clone(), t.height, t.width_factor, t.oblique_angle)),
|
||||
E::MText(m) => Some((m.style.clone(), m.height, 1.0, 0.0)),
|
||||
_ => None,
|
||||
};
|
||||
if let Some((style, height, wf, ob)) = text_fmt {
|
||||
match dst {
|
||||
E::Text(t) => {
|
||||
t.style = style;
|
||||
t.height = height;
|
||||
if matches!(src, E::Text(_)) {
|
||||
t.width_factor = wf;
|
||||
t.oblique_angle = ob;
|
||||
}
|
||||
}
|
||||
E::MText(m) => {
|
||||
m.style = style;
|
||||
m.height = height;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// MText-only extras (line spacing, background fill).
|
||||
if let (E::MText(sm), E::MText(dm)) = (src, dst as &mut E) {
|
||||
dm.drawing_direction = sm.drawing_direction;
|
||||
dm.line_spacing_factor = sm.line_spacing_factor;
|
||||
dm.line_spacing_style = sm.line_spacing_style;
|
||||
dm.background_fill_flags = sm.background_fill_flags;
|
||||
dm.background_scale = sm.background_scale;
|
||||
dm.background_color = sm.background_color;
|
||||
dm.background_transparency = sm.background_transparency;
|
||||
}
|
||||
|
||||
// Dimension style name crosses the three dim-styled families.
|
||||
let dim_style = match src {
|
||||
E::Dimension(d) => Some(d.base().style_name.clone()),
|
||||
E::Leader(l) => Some(l.dimension_style.clone()),
|
||||
E::Tolerance(t) => Some(t.dimension_style_name.clone()),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(ds) = dim_style {
|
||||
match dst {
|
||||
E::Dimension(d) => d.base_mut().style_name = ds,
|
||||
E::Leader(l) => l.dimension_style = ds,
|
||||
E::Tolerance(t) => t.dimension_style_name = ds,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Hatch pattern / gradient — everything but the boundary.
|
||||
if let (E::Hatch(sh), E::Hatch(dh)) = (src, dst as &mut E) {
|
||||
dh.pattern = sh.pattern.clone();
|
||||
dh.pattern_type = sh.pattern_type;
|
||||
dh.pattern_angle = sh.pattern_angle;
|
||||
dh.pattern_scale = sh.pattern_scale;
|
||||
dh.is_solid = sh.is_solid;
|
||||
dh.gradient_color = sh.gradient_color.clone();
|
||||
}
|
||||
|
||||
// Polyline display style: width + linetype generation.
|
||||
if let (E::LwPolyline(sp), E::LwPolyline(dp)) = (src, dst as &mut E) {
|
||||
dp.constant_width = sp.constant_width;
|
||||
dp.plinegen = sp.plinegen;
|
||||
for v in &mut dp.vertices {
|
||||
v.start_width = sp.constant_width;
|
||||
v.end_width = sp.constant_width;
|
||||
}
|
||||
}
|
||||
|
||||
// MultiLeader style + every style-affecting override; content and
|
||||
// geometry (leader points, text, block handle) stay.
|
||||
if let (E::MultiLeader(sm), E::MultiLeader(dm)) = (src, dst as &mut E) {
|
||||
dm.style_handle = sm.style_handle;
|
||||
dm.path_type = sm.path_type;
|
||||
dm.line_color = sm.line_color;
|
||||
dm.line_type_handle = sm.line_type_handle;
|
||||
dm.line_weight = sm.line_weight;
|
||||
dm.enable_landing = sm.enable_landing;
|
||||
dm.enable_dogleg = sm.enable_dogleg;
|
||||
dm.dogleg_length = sm.dogleg_length;
|
||||
dm.arrowhead_handle = sm.arrowhead_handle;
|
||||
dm.arrowhead_size = sm.arrowhead_size;
|
||||
dm.text_style_handle = sm.text_style_handle;
|
||||
dm.text_color = sm.text_color;
|
||||
dm.text_frame = sm.text_frame;
|
||||
dm.text_height = sm.text_height;
|
||||
dm.scale_factor = sm.scale_factor;
|
||||
dm.property_override_flags = sm.property_override_flags;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue