From e35c557f6405a3d7d9eda39636c0af9fadf2b3e8 Mon Sep 17 00:00:00 2001 From: Kevin Griffin <117586586+KevinGriffin-new@users.noreply.github.com> Date: Tue, 14 Jul 2026 18:22:48 -0700 Subject: [PATCH] fix(matchprop): match text style and height between TEXT/MTEXT objects (#361) MATCHPROP only copied the generic properties (layer, color, linetype, linetype scale, lineweight), so matching text to text left the destination's style and height unchanged. Copy the source's text style and height to TEXT and MTEXT destinations, in both directions, like AutoCAD's "Text" special property. Adds an in-process regression test that drives the real interactive command path (source pick -> destination selection) and fails on the old code with the reported symptom. Fixes #361 Co-Authored-By: Claude Fable 5 --- src/app/automation.rs | 63 +++++++++++++++++++++++++++++++++++++++ src/app/command_driver.rs | 26 ++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/src/app/automation.rs b/src/app/automation.rs index b1dcaefbe..eebf1490d 100644 --- a/src/app/automation.rs +++ b/src/app/automation.rs @@ -541,6 +541,69 @@ mod tests { ); } + #[test] + fn matchprop_matches_text_style_and_height() { + // MATCHPROP between text objects must carry the text-specific + // properties (style, height) to TEXT and MTEXT destinations, not just + // the generic layer/color/linetype set. Regression for #361. + use crate::command::StepInput; + use acadrust::{EntityType, MText, Text}; + + let mut app = OpenCADStudio::new_for_test(); + app.automation_op(r#"{"op":"new"}"#); + let i = app.active_tab; + + let mut src = Text::new(); + src.value = "SRC".into(); + src.height = 5.0; + src.style = "BIG".into(); + let src_h = app.tabs[i].scene.add_entity(EntityType::Text(src)); + + let mut dst_text = Text::new(); + dst_text.value = "DST".into(); + dst_text.height = 1.0; + let dst_text_h = app.tabs[i].scene.add_entity(EntityType::Text(dst_text)); + + let mut dst_mtext = MText::new(); + dst_mtext.value = "DSTM".into(); + dst_mtext.height = 2.0; + let dst_mtext_h = app.tabs[i].scene.add_entity(EntityType::MText(dst_mtext)); + + // Drive the interactive command exactly as the viewport does: + // phase 1 source pick, phase 2 destination selection. + let _ = app.run_command_line("MATCHPROP"); + assert!(app.tabs[i].active_cmd.is_some(), "MATCHPROP must start"); + let _ = app.feed_command(StepInput::EntityPick(src_h, glam::DVec3::ZERO)); + let _ = app.feed_command(StepInput::SelectionComplete(vec![ + dst_text_h, + dst_mtext_h, + ])); + + let doc = &app.tabs[i].scene.document; + match doc.get_entity(dst_text_h) { + Some(EntityType::Text(t)) => { + assert_eq!(t.style, "BIG", "TEXT destination must take source style"); + assert!( + (t.height - 5.0).abs() < 1e-9, + "TEXT destination must take source height, got {}", + t.height + ); + } + other => panic!("dest TEXT missing: {other:?}"), + } + match doc.get_entity(dst_mtext_h) { + Some(EntityType::MText(m)) => { + assert_eq!(m.style, "BIG", "MTEXT destination must take source style"); + assert!( + (m.height - 5.0).abs() < 1e-9, + "MTEXT destination must take source height, got {}", + m.height + ); + } + other => panic!("dest MTEXT missing: {other:?}"), + } + } + #[test] fn save_then_open_round_trips() { let mut app = OpenCADStudio::new_for_test(); diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 322fe0e6b..d28c87479 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -759,6 +759,19 @@ 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, + }); if let Some((layer, color, linetype, lt_scale, lw)) = props { self.push_undo_snapshot(i, "MATCHPROP"); @@ -769,6 +782,19 @@ 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; + } + _ => {} + } + } } } self.tabs[i].dirty = true;