diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 5c48a3ce6..34ae4ce3c 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -1878,6 +1878,7 @@ annotate = البيانات .lower = خفض .mleader-specify-arrowhead-point = MLEADER حدد نقطة رأس السهم: + .mleader-specify-landing-point = MLEADER حدد نقطة خط الإسناد: .mleader-specify-next-point-count-pts-enter-to = MLEADER حدد النقطة التالية [__ocs_arg_count__ نقاط — Enter لوضع النص]: .mleaderadd-select-a-multileader = MLEADERADD حدد خطًا قائدًا متعددًا: .mleaderadd-specify-arrowhead-location = MLEADERADD حدد موقع رأس السهم: diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 2df4f555e..6dbdbd6fa 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -1865,6 +1865,7 @@ annotate = данни .lower = Малки букви .mleader-specify-arrowhead-point = MLEADER Посочи точка на върха на стрелката: + .mleader-specify-landing-point = MLEADER Посочи точката на хоризонталния участък: .mleader-specify-next-point-count-pts-enter-to = MLEADER Посочи следваща точка [__ocs_arg_count__ т. — Enter за поставяне на текст]: .mleaderadd-select-a-multileader = MLEADERADD Избери многоводач: .mleaderadd-specify-arrowhead-location = MLEADERADD Посочи местоположение на върха на стрелката: diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index f3a9b7f40..67dd4b294 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -1865,6 +1865,7 @@ annotate = Data .lower = Nižší .mleader-specify-arrowhead-point = MLEADER Zadejte bod šipky: + .mleader-specify-landing-point = MLEADER Zadejte bod odskoku: .mleader-specify-next-point-count-pts-enter-to = MLEADER Zadejte další bod [__ocs_arg_count__ bodů — Enter pro umístění textu]: .mleaderadd-select-a-multileader = MLEADERADD Vyberte multiodkaz: .mleaderadd-specify-arrowhead-location =MLEADERADD Zadejte umístění šipky: diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 78ed63e07..1bb9dbca9 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -1866,6 +1866,7 @@ annotate = Daten .lower = niedriger .mleader-specify-arrowhead-point = MLEADER Pfeilspitze angeben: + .mleader-specify-landing-point = MLEADER Absetzpunkt angeben: .mleader-specify-next-point-count-pts-enter-to = MLEADER Nächsten Punkt angeben [__ocs_arg_count__ Punkte — Eingabe zum Platzieren des Texts]: .mleaderadd-select-a-multileader = MLEADERADD Multiführungslinie auswählen: .mleaderadd-specify-arrowhead-location = MLEADERADD Pfeilkopfposition angeben: diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 515e4526c..7b6dd7f8e 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -1896,6 +1896,7 @@ annotate = Data .lower = Lower .mleader-specify-arrowhead-point = MLEADER Specify arrowhead point: + .mleader-specify-landing-point = MLEADER Specify landing point: .mleader-specify-next-point-count-pts-enter-to = MLEADER Specify next point [__ocs_arg_count__ pts — Enter to place text]: .mleaderadd-select-a-multileader = MLEADERADD Select a multileader: .mleaderadd-specify-arrowhead-location = MLEADERADD Specify arrowhead location: diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index a929a1f0e..7c0614338 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -1866,6 +1866,7 @@ annotate = datos .lower = Inferior .mleader-specify-arrowhead-point = MLEADER Especifique el punto de la punta de flecha: + .mleader-specify-landing-point = MLEADER Especifique el punto de conexión: .mleader-specify-next-point-count-pts-enter-to = MLEADER Especifique el punto siguiente [__ocs_arg_count__ puntos; Enter para colocar texto]: .mleaderadd-select-a-multileader = MLEADERADD Seleccione una directriz múltiple: .mleaderadd-specify-arrowhead-location = MLEADERADD Especifique la ubicación de la punta de flecha: diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 8399b91f2..59306d658 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -1865,6 +1865,7 @@ annotate = Data .lower = Alempi .mleader-specify-arrowhead-point = MLEADER Määritä nuolenpää: + .mleader-specify-landing-point = MLEADER Määritä vaakaviivan piste: .mleader-specify-next-point-count-pts-enter-to = MLEADER Määritä seuraava piste [__ocs_arg_count__ pts — Enter tekstin sijoittamiseksi]: .mleaderadd-select-a-multileader = MLEADERADD Valitse monijohtaja: .mleaderadd-specify-arrowhead-location =MLEADERADD Määritä nuolenpään sijainti: diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index f0ced22ab..fc257706d 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -1866,6 +1866,7 @@ annotate = Données .lower = Moins .mleader-specify-arrowhead-point = MLEADER Spécifiez le point de la tête de flèche: + .mleader-specify-landing-point = MLEADER Spécifiez le point de palier: .mleader-specify-next-point-count-pts-enter-to = MLEADER Spécifiez le point suivant [__ocs_arg_count__ points — Entrée pour placer le texte] : .mleaderadd-select-a-multileader = MLEADERADD Sélectionnez une ligne de repère multiple : .mleaderadd-specify-arrowhead-location = MLEADERADD Spécifiez l'emplacement de la tête de flèche: diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 4356de5c4..f6b779702 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -1854,6 +1854,7 @@ annotate = डेटा .lower = कम .mleader-specify-arrowhead-point = MLEADER निर्दिष्ट तीरहेड बिंदु: + .mleader-specify-landing-point = MLEADER लैंडिंग बिंदु निर्दिष्ट करें: .mleader-specify-next-point-count-pts-enter-to = MLEADER अगला बिंदु निर्दिष्ट करें [__ocs_arg_count__ बिंदु — टेक्स्ट रखने के लिए Enter दबाएँ]: .mleaderadd-select-a-multileader = MLEADERADD मल्टीलीडर चुनें: .mleaderadd-specify-arrowhead-location = MLEADERADD एरोहेड का स्थान निर्दिष्ट करें: diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index af07478f9..3f7061c51 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -1865,6 +1865,7 @@ annotate = Adatok .lower = Lejjebb .mleader-specify-arrowhead-point = MLEADER Nyílhegy pont megadása: + .mleader-specify-landing-point = MLEADER Bekötési pont megadása: .mleader-specify-next-point-count-pts-enter-to = MLEADER Adja meg a következő pontot [__ocs_arg_count__ pts — Enter a szöveg elhelyezéséhez]: .mleaderadd-select-a-multileader = MLEADERADD Válasszon többvezetőt: .mleaderadd-specify-arrowhead-location =MLEADERADD Adja meg a nyílhegy helyét: diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 6ab5b5047..4a743466e 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -1865,6 +1865,7 @@ annotate = Dati .lower = Più in basso .mleader-specify-arrowhead-point = MLEADER Specificare il punto della freccia: + .mleader-specify-landing-point = MLEADER Specificare il punto di collegamento: .mleader-specify-next-point-count-pts-enter-to = MLEADER Specificare il punto successivo [__ocs_arg_count__ punti — Enter per posizionare il testo]: .mleaderadd-select-a-multileader = MLEADERADD Seleziona una multidirettrice: .mleaderadd-specify-arrowhead-location =MLEADERADD Specificare la posizione della punta della freccia: diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 7531bc568..16b42a35e 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -1856,6 +1856,7 @@ annotate = リンク .lower = 下げる .mleader-specify-arrowhead-point = MLEADER 矢印位置を指定: + .mleader-specify-landing-point = MLEADER ランディング点を指定: .mleader-specify-next-point-count-pts-enter-to = MLEADER 次の点を指定 [__ocs_arg_count__ 点 — Enter で文字を配置]: .mleaderadd-select-a-multileader = MLEADERADD マルチ引出線を選択: .mleaderadd-specify-arrowhead-location = MLEADERADD 矢印位置を指定: diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 0fd664f8b..971a807be 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -1865,6 +1865,7 @@ annotate = 데이터 .lower = 낮은 .mleader-specify-arrowhead-point = MLEADER 화살촉 점 지정: + .mleader-specify-landing-point = MLEADER 연결점 지정: .mleader-specify-next-point-count-pts-enter-to = MLEADER 다음 점 지정 [__ocs_arg_count__ 포인트 - 텍스트를 배치할 Enter]: .mleaderadd-select-a-multileader = MLEADERADD 다중 지시선 선택: .mleaderadd-specify-arrowhead-location =MLEADERADD 화살촉 위치 지정: diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index a2abe7406..3e63979bf 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -1866,6 +1866,7 @@ annotate = Gegevens .lower = Lager .mleader-specify-arrowhead-point = MLEADER pijlpunt specificeren: + .mleader-specify-landing-point = MLEADER Landingspunt opgeven: .mleader-specify-next-point-count-pts-enter-to = MLEADER Geef het volgende punt op [__ocs_arg_count__ punten — Enter om tekst te plaatsen]: .mleaderadd-select-a-multileader = MLEADERADD Selecteer een multileader: .mleaderadd-specify-arrowhead-location = MLEADERADD Plaats pijlpunt opgeven: diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 9f3d339dc..946df99c4 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -1865,6 +1865,7 @@ annotate = Dane .lower = Niższy .mleader-specify-arrowhead-point = MLEADER Określ punkt grotu strzałki: + .mleader-specify-landing-point = MLEADER Określ punkt odsadzenia: .mleader-specify-next-point-count-pts-enter-to = MLEADER Określ następny punkt [__ocs_arg_count__ pkt — Enter, aby umieścić tekst]: .mleaderadd-select-a-multileader = MLEADERADD Wybierz wielolinię odniesienia: .mleaderadd-specify-arrowhead-location =MLEADERADD Określ położenie grotu strzałki: diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 0c8d98869..1ccfdd355 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -1866,6 +1866,7 @@ annotate = dados .lower = Inferior .mleader-specify-arrowhead-point = MLEADER Especifique o ponto da ponta de seta: + .mleader-specify-landing-point = MLEADER Especifique o ponto de conexão: .mleader-specify-next-point-count-pts-enter-to = MLEADER Especifique o próximo ponto [__ocs_arg_count__ pontos — Enter para inserir texto]: .mleaderadd-select-a-multileader = MLEADERADD Selecione uma linha de chamada múltipla: .mleaderadd-specify-arrowhead-location = MLEADERADD Especifique a localização da ponta de seta: diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 37223a15f..a1020e34a 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -1869,6 +1869,7 @@ annotate = Данные .lower = ниже .mleader-specify-arrowhead-point = MLEADER Укажите точку наконечника стрелки: + .mleader-specify-landing-point = MLEADER Укажите точку полки: .mleader-specify-next-point-count-pts-enter-to = MLEADER Укажите следующую точку [точек: __ocs_arg_count__ — Enter для размещения текста]: .mleaderadd-select-a-multileader = MLEADERADD Выберите мультивыноску: .mleaderadd-specify-arrowhead-location = MLEADERADD Укажите местоположение наконечника стрелки: diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 06defcacc..f9ffa60fd 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -1872,6 +1872,7 @@ annotate = .link-data = Verileri Bağla .lower = Alt .mleader-specify-arrowhead-point = MLEADER Ok ucu noktasını belirtin: + .mleader-specify-landing-point = MLEADER Yatay bağlantı noktasını belirtin: .mleader-specify-next-point-count-pts-enter-to = MLEADER Sonraki noktayı belirtin [__ocs_arg_count__ nokta — metni yerleştirmek için Enter]: .mleaderadd-select-a-multileader = MLEADERADD Bir çoklu kılavuz seçin: .mleaderadd-specify-arrowhead-location = MLEADERADD Ok ucu konumunu belirtin: diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index daec9ff06..9e2747c66 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -1853,6 +1853,7 @@ annotate = 数据 .lower = 下调 .mleader-specify-arrowhead-point = MLEADER 指定箭头点: + .mleader-specify-landing-point = MLEADER 指定水平基线点: .mleader-specify-next-point-count-pts-enter-to = MLEADER 指定下一点 [__ocs_arg_count__ 个点 — 按 Enter 放置文字]: .mleaderadd-select-a-multileader = MLEADERADD 选择多重引线: .mleaderadd-specify-arrowhead-location = MLEADERADD 指定箭头位置: diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 460ed3f35..634e171b5 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -1865,6 +1865,7 @@ annotate = 資料 .lower = 下調 .mleader-specify-arrowhead-point = MLEADER 指定箭頭點: + .mleader-specify-landing-point = MLEADER 指定水平基線點: .mleader-specify-next-point-count-pts-enter-to = MLEADER 指定下一點 [__ocs_arg_count__ 個點 — 按 Enter 放置文字]: .mleaderadd-select-a-multileader = MLEADERADD 選擇多重引線: .mleaderadd-specify-arrowhead-location = MLEADERADD 指定箭頭位置: diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 77a1e1756..bf03b591f 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -1270,10 +1270,29 @@ impl OpenCADStudio { self.restore_pre_cmd_tangent(); } CmdResult::CommitAndEditText(entity) => { + let annotative_mleader = matches!( + &entity, + acadrust::EntityType::MultiLeader(ml) if ml.enable_annotation_scale + ); let label = self.history_label_from_active_cmd(i, "ENTITY"); - let delta_safe = self.delta_add_safe(i, &entity); + let delta_safe = self.delta_add_safe(i, &entity) && !annotative_mleader; let pending = self.begin_undo(i, label, 1, delta_safe); let handle = self.commit_entity_handle(entity); + + if annotative_mleader { + let scale_handle = self.tabs[i].scene.creation_annotation_scale_handle(); + if let (Some(handle), Some(scale_handle)) = (handle, scale_handle) { + crate::scene::annotative::create_annotation_context( + &mut self.tabs[i].scene.document, + handle, + scale_handle, + ); + self.tabs[i] + .scene + .bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]); + } + } + self.tabs[i].dirty = true; self.tabs[i].scene.clear_preview_wire(); self.tabs[i].active_cmd = None; diff --git a/src/app/layers.rs b/src/app/layers.rs index 6f00ce26c..6be69c8f3 100644 --- a/src/app/layers.rs +++ b/src/app/layers.rs @@ -143,15 +143,22 @@ impl OpenCADStudio { } }) .collect(); - let active_mleader = self.tabs[i].active_mleader_style.clone(); - let active_mleader = if mleader_names.contains(&active_mleader) { - active_mleader - } else { + let current_mleader = + doc.header.current_mleader_style_name.clone(); + + let active_mleader = mleader_names - .first() + .iter() + .find(|name| { + name.eq_ignore_ascii_case( + ¤t_mleader + ) + }) .cloned() - .unwrap_or_default() - }; + .or_else(|| { + mleader_names.first().cloned() + }) + .unwrap_or_default(); let table_names: Vec = doc .objects diff --git a/src/app/mtext_editor.rs b/src/app/mtext_editor.rs index ce6d370c0..f4566d813 100644 --- a/src/app/mtext_editor.rs +++ b/src/app/mtext_editor.rs @@ -1248,6 +1248,15 @@ impl super::OpenCADStudio { } _ => {} } + // Keep the displayed annotation context in sync. + if matches!( + self.tabs[i].scene.document.get_entity(h), + Some(EntityType::MultiLeader(_)) + ) { + self.tabs[i] + .scene + .sync_displayed_annotation_context(h); + } self.tabs[i] .scene .bump_entities(&[(h, crate::scene::ChangeKind::Modified)]); @@ -1336,6 +1345,14 @@ impl super::OpenCADStudio { } _ => {} } + if matches!( + self.tabs[i].scene.document.get_entity(h), + Some(EntityType::MultiLeader(_)) + ) { + self.tabs[i] + .scene + .sync_displayed_annotation_context(h); + } self.tabs[i] .scene .bump_entities(&[(h, crate::scene::ChangeKind::Modified)]); diff --git a/src/app/style_ops.rs b/src/app/style_ops.rs index 0bdad07be..e2ec1a2f2 100644 --- a/src/app/style_ops.rs +++ b/src/app/style_ops.rs @@ -18,16 +18,134 @@ //! style added without a handle (dropped on DWG save, issue #67). use super::OpenCADStudio; -use acadrust::objects::{MLineStyle, MultiLeaderStyle, ObjectType, TableStyle}; +use acadrust::objects::{ + Dictionary, MLineStyle, MultiLeaderStyle, ObjectType, TableStyle, +}; use acadrust::tables::{DimStyle, TextStyle}; use acadrust::types::Handle; +const MLEADERSTYLE_DICT_NAME: &str = "ACAD_MLEADERSTYLE"; + +fn mleaderstyle_dict_handle(doc: &acadrust::CadDocument) -> Option { + let root_h = doc.header.named_objects_dict_handle; + + let root = match doc.objects.get(&root_h) { + Some(ObjectType::Dictionary(root)) => root, + _ => return None, + }; + + root.entries + .iter() + .find(|(name, _)| name.eq_ignore_ascii_case(MLEADERSTYLE_DICT_NAME)) + .map(|(_, handle)| *handle) + .filter(|handle| { + matches!( + doc.objects.get(handle), + Some(ObjectType::Dictionary(_)) + ) + }) +} + +fn import_mleaderstyle_names_from_dictionary(doc: &mut acadrust::CadDocument) { + let Some(dict_h) = mleaderstyle_dict_handle(doc) else { + return; + }; + + let entries = match doc.objects.get(&dict_h) { + Some(ObjectType::Dictionary(dict)) => dict.entries.clone(), + _ => return, + }; + + for (name, handle) in entries { + if let Some(ObjectType::MultiLeaderStyle(style)) = + doc.objects.get_mut(&handle) + { + style.name = name; + style.owner_handle = dict_h; + } + } +} + +fn sync_mleaderstyle_dictionary(doc: &mut acadrust::CadDocument) { + let root_h = crate::scene::annotative::root_named_dict_handle(doc); + + let existing = match doc.objects.get(&root_h) { + Some(ObjectType::Dictionary(root)) => root + .entries + .iter() + .find(|(name, _)| name.eq_ignore_ascii_case(MLEADERSTYLE_DICT_NAME)) + .map(|(_, handle)| *handle), + _ => None, + }; + + let dict_h = match existing.filter(|handle| { + matches!( + doc.objects.get(handle), + Some(ObjectType::Dictionary(_)) + ) + }) { + Some(handle) => handle, + None => { + let handle = doc.allocate_handle(); + + let mut dict = Dictionary::new(); + dict.handle = handle; + dict.owner = root_h; + + doc.objects + .insert(handle, ObjectType::Dictionary(dict)); + + handle + } + }; + + if let Some(ObjectType::Dictionary(root)) = doc.objects.get_mut(&root_h) { + root.entries + .retain(|(name, _)| !name.eq_ignore_ascii_case(MLEADERSTYLE_DICT_NAME)); + + root.add_entry(MLEADERSTYLE_DICT_NAME, dict_h); + } + + let mut entries: Vec<(String, Handle)> = doc + .objects + .iter() + .filter_map(|(&handle, object)| match object { + ObjectType::MultiLeaderStyle(style) => { + Some((style.name.clone(), handle)) + } + _ => None, + }) + .collect(); + + entries.sort_by(|a, b| { + a.0.to_lowercase().cmp(&b.0.to_lowercase()) + }); + + for (_, handle) in &entries { + if let Some(ObjectType::MultiLeaderStyle(style)) = + doc.objects.get_mut(handle) + { + style.owner_handle = dict_h; + } + } + + if let Some(ObjectType::Dictionary(dict)) = doc.objects.get_mut(&dict_h) { + dict.owner = root_h; + dict.entries = entries; + + // MLEADERSTYLE uses soft-owner entries (group 350). + dict.hard_owner = false; + dict.hard_owner_entries.clear(); + } +} + /// Guarantee the built-in "Standard" style of every kind exists in `doc` — /// a foreign or damaged file saved without them leaves the style dropdowns /// broken with no way to recover, and new text/dimensions have nothing to /// reference (#366). Missing entries are re-seeded with the app defaults. /// Called on every file open; a no-op for healthy documents. pub(crate) fn ensure_standard_styles(doc: &mut acadrust::CadDocument) { + import_mleaderstyle_names_from_dictionary(doc); if !doc .text_styles .iter() @@ -68,6 +186,8 @@ pub(crate) fn ensure_standard_styles(doc: &mut acadrust::CadDocument) { s.handle = doc.allocate_handle(); doc.objects.insert(s.handle, ObjectType::MultiLeaderStyle(s)); } + sync_mleaderstyle_dictionary(doc); + if !has(doc, |o| match o { ObjectType::MLineStyle(s) => Some(&s.name), _ => None, @@ -741,6 +861,7 @@ impl OpenCADStudio { for (h, o) in &snap.style_objects { doc.objects.insert(*h, o.clone()); } + sync_mleaderstyle_dictionary(doc); doc.header.current_text_style_name = snap.current_text.clone(); doc.header.current_dimstyle_name = snap.current_dim.clone(); doc.header.multiline_style = snap.multiline_style.clone(); @@ -772,11 +893,77 @@ impl OpenCADStudio { self.sync_ribbon_styles(); return; }; + sync_mleaderstyle_dictionary( + &mut self.tabs[i].scene.document, + ); + let edited = self.capture_style_state(); let changed = edited != stage.baseline; if changed { self.tabs[i].dirty = true; let (text_names, dim_names, object_handles) = edited.changed_keys(&stage.baseline); + let changed_mleader_styles: + Vec = + object_handles + .iter() + .filter_map(|handle| { + match self.tabs[i] + .scene + .document + .objects + .get(handle) + { + Some( + acadrust::objects::ObjectType:: + MultiLeaderStyle(style), + ) => Some(style.clone()), + _ => None, + } + }) + .collect(); + + let mut changed_mleaders = Vec::new(); + + for style in changed_mleader_styles { + let entity_handles: Vec = { + let doc = &self.tabs[i].scene.document; + + doc.entities() + .filter_map(|entity| { + match entity { + acadrust::EntityType::MultiLeader(ml) + if ml.style_handle + == Some(style.handle) => + { + Some(ml.common.handle) + } + _ => None, + } + }) + .collect() + }; + + for handle in entity_handles { + if crate::scene::annotative:: + apply_mleader_style_to_object( + &mut self.tabs[i].scene.document, + handle, + &style, + ) + { + changed_mleaders.push(( + handle, + crate::scene::ChangeKind::Modified, + )); + } + } + } + + if !changed_mleaders.is_empty() { + self.tabs[i] + .scene + .bump_entities(&changed_mleaders); + } self.tabs[i].scene.invalidate_text_style_dependencies_many(&text_names); self.tabs[i] .scene diff --git a/src/app/update/command.rs b/src/app/update/command.rs index 7354f8d9a..ab25667d6 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -1673,9 +1673,18 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { } } StyleKey::MLeaderStyle => { - self.ribbon.active_mleader_style = name.clone(); let i = self.active_tab; - self.tabs[i].active_mleader_style = name; + + self.ribbon.active_mleader_style = name.clone(); + self.tabs[i].active_mleader_style = name.clone(); + + self.tabs[i] + .scene + .document + .header + .current_mleader_style_name = name; + + self.tabs[i].dirty = true; } StyleKey::TableStyle => { self.ribbon.active_table_style = name; diff --git a/src/entities/multileader.rs b/src/entities/multileader.rs index 2c483ea54..ed985a38b 100644 --- a/src/entities/multileader.rs +++ b/src/entities/multileader.rs @@ -182,8 +182,18 @@ fn to_render(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option= 0.0 { 0.0 } else { 1.0 }, - v_anchor: mleader_v_anchor(ctx.text_left_attachment), + attach_h_anchor: match ctx.text_attachment_point { + acadrust::entities::multileader::TextAttachmentPointType::Left => 0.0, + acadrust::entities::multileader::TextAttachmentPointType::Center => 0.5, + acadrust::entities::multileader::TextAttachmentPointType::Right => 1.0, + }, + v_anchor: mleader_v_anchor( + if text_sign >= 0.0 { + ctx.text_left_attachment + } else { + ctx.text_right_attachment + }, + ), line_spacing_factor: ctx.line_spacing_factor as f32, vertical_text: false, want_glyph_boxes: false, @@ -200,11 +210,19 @@ fn to_render(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option Option = line.points.iter().map(|p| p3(p)).collect(); - let last_f = *ctrl.last().unwrap_or(&landing_pt); - let dist = ((last_f[0] - landing_pt[0]).powi(2) - + (last_f[1] - landing_pt[1]).powi(2)) + let mut ctrl: Vec<[f64; 3]> = + line.points.iter().map(|p| p3(p)).collect(); + + let last_f = *ctrl.last().unwrap_or(&elbow_pt); + + let dist = ((last_f[0] - elbow_pt[0]).powi(2) + + (last_f[1] - elbow_pt[1]).powi(2)) .sqrt(); + if dist > 1e-9 { - ctrl.push(landing_pt); + ctrl.push(elbow_pt); } for &c in &ctrl { key_verts.push(c); @@ -295,8 +317,8 @@ fn to_render(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option 0.0 { points.push(nan); - points.push(landing_pt); - points.push([text_loc.x, text_loc.y, text_loc.z]); + points.push(elbow_pt); + points.push(text_edge_pt); } } @@ -339,9 +361,7 @@ fn to_render(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option ([f64; 2], [f64; 3]) { ], ) } +fn mleader_landing_geom( + ml: &MultiLeader, +) -> Option<(DVec3, DVec3, f64, f64, f64)> { + if ml.content_type != LeaderContentType::MText + || !ml.enable_landing + || !ml.enable_dogleg + { + return None; + } + + let text = DVec3::new( + ml.context.text_location.x, + ml.context.text_location.y, + ml.context.text_location.z, + ); + + let td = ml.context.text_direction; + + let axis = { + let d = DVec3::new(td.x, td.y, td.z); + + if d.length_squared() > 1.0e-18 { + d.normalize() + } else { + let a = ml.context.text_rotation; + + DVec3::new(a.cos(), a.sin(), 0.0) + } + }; + + let leader_ref = ml + .context + .leader_roots + .first() + .and_then(|root| root.lines.first()) + .and_then(|line| line.points.last()) + .map(|p| DVec3::new(p.x, p.y, p.z)) + .or_else(|| { + ml.context.leader_roots.first().map(|root| { + DVec3::new( + root.connection_point.x, + root.connection_point.y, + root.connection_point.z, + ) + }) + }) + .unwrap_or(text); + + let sign = if (text - leader_ref).dot(axis) >= 0.0 { + 1.0 + } else { + -1.0 + }; + + let dogleg = ml + .context + .leader_roots + .first() + .map(|root| root.landing_distance.max(0.0)) + .unwrap_or_else(|| ml.dogleg_length.max(0.0)); + + let gap = ml.context.landing_gap.max(0.0); + + if dogleg <= 1.0e-9 { + return None; + } + + let elbow = text - axis * (sign * (dogleg + gap)); + + Some((elbow, axis, sign, dogleg, gap)) +} + +fn set_mleader_text_location(ml: &mut MultiLeader, location: DVec3) { + let location = acadrust::types::Vector3::new(location.x, location.y, location.z); + ml.context.text_location = location; + ml.context.content_base_point = location; +} fn grips(ml: &MultiLeader) -> Vec { - let mut result: Vec = Vec::new(); + let mut result = Vec::new(); let mut id = 0usize; for root in &ml.context.leader_roots { for line in &root.lines { for p in &line.points { - result.push(square_grip(id, glam::DVec3::new(p.x, p.y, p.z))); + result.push(square_grip( + id, + DVec3::new(p.x, p.y, p.z), + )); + id += 1; } } } if ml.content_type == LeaderContentType::MText { + if let Some((elbow, _, _, _, _)) = mleader_landing_geom(ml) + { + result.push(square_grip( + id, + elbow, + )); + + id += 1; + } + let tl = &ml.context.text_location; - // Text-location grip, then the wrap-width grip at the box's far edge. - result.push(center_grip(id, glam::DVec3::new(tl.x, tl.y, tl.z))); + + result.push(center_grip( + id, + DVec3::new(tl.x, tl.y, tl.z), + )); + id += 1; + let (_, far) = text_box_geom(ml); - result.push(triangle_grip(id, glam::DVec3::new(far[0], far[1], far[2]))); + + result.push(triangle_grip( + id, + DVec3::new( + far[0], + far[1], + far[2], + ), + )); } result @@ -458,16 +582,29 @@ fn grips(ml: &MultiLeader) -> Vec { /// text grip's "Move with Leader" action so the leader follows the text. pub(crate) const MOVE_ALL_GRIP: usize = usize::MAX; -fn apply_grip(ml: &mut MultiLeader, grip_id: usize, apply: GripApply) { +fn apply_grip( + ml: &mut MultiLeader, + grip_id: usize, + apply: GripApply, +) { if grip_id == MOVE_ALL_GRIP { let (dx, dy, dz) = match apply { - GripApply::Translate(d) => (d.x as f64, d.y as f64, d.z as f64), + GripApply::Translate(d) => ( + d.x as f64, + d.y as f64, + d.z as f64, + ), + GripApply::Absolute(a) => ( - a.x as f64 - ml.context.text_location.x, - a.y as f64 - ml.context.text_location.y, - a.z as f64 - ml.context.text_location.z, + a.x as f64 + - ml.context.text_location.x, + a.y as f64 + - ml.context.text_location.y, + a.z as f64 + - ml.context.text_location.z, ), }; + for root in &mut ml.context.leader_roots { for line in &mut root.lines { for p in &mut line.points { @@ -476,13 +613,21 @@ fn apply_grip(ml: &mut MultiLeader, grip_id: usize, apply: GripApply) { p.z += dz; } } + root.connection_point.x += dx; root.connection_point.y += dy; root.connection_point.z += dz; } - ml.context.text_location.x += dx; - ml.context.text_location.y += dy; - ml.context.text_location.z += dz; + + set_mleader_text_location( + ml, + DVec3::new( + ml.context.text_location.x + dx, + ml.context.text_location.y + dy, + ml.context.text_location.z + dz, + ), + ); + return; } @@ -498,55 +643,130 @@ fn apply_grip(ml: &mut MultiLeader, grip_id: usize, apply: GripApply) { p.y = a.y as f64; p.z = a.z as f64; } + GripApply::Translate(d) => { p.x += d.x as f64; p.y += d.y as f64; p.z += d.z as f64; } } + return; } + idx += 1; } } } - // Text-location grip (idx == n_vertices), then the wrap-width grip. - if ml.content_type == LeaderContentType::MText { - if idx == grip_id { - let tl = &mut ml.context.text_location; - match apply { - GripApply::Absolute(a) => { - tl.x = a.x as f64; - tl.y = a.y as f64; - tl.z = a.z as f64; - } + if ml.content_type != LeaderContentType::MText { + return; + } + + let landing = mleader_landing_geom(ml); + + if let Some((old_elbow, axis, old_sign, dogleg, gap)) = landing { + if grip_id == idx { + let target = match apply { + GripApply::Absolute(a) => DVec3::new(a.x as f64, a.y as f64, a.z as f64), GripApply::Translate(d) => { - tl.x += d.x as f64; - tl.y += d.y as f64; - tl.z += d.z as f64; + old_elbow + DVec3::new(d.x as f64, d.y as f64, d.z as f64) } + }; + let arrow = ml + .context + .leader_roots + .first() + .and_then(|root| root.lines.first()) + .and_then(|line| line.points.first()) + .map(|p| DVec3::new(p.x, p.y, p.z)) + .unwrap_or(old_elbow); + let side_distance = (target - arrow).dot(axis); + let flip_tol = (dogleg * 0.10).max(1.0e-6); + let new_sign = if side_distance > flip_tol { + 1.0 + } else if side_distance < -flip_tol { + -1.0 + } else { + old_sign + }; + ml.context.text_attachment_point = if new_sign >= 0.0 { + acadrust::entities::multileader::TextAttachmentPointType::Left + } else { + acadrust::entities::multileader::TextAttachmentPointType::Right + }; + set_mleader_text_location(ml, target + axis * (new_sign * (dogleg + gap))); + + if let Some(root) = ml.context.leader_roots.first_mut() { + root.connection_point.x = target.x; + root.connection_point.y = target.y; + root.connection_point.z = target.z; + root.direction.x = axis.x * new_sign; + root.direction.y = axis.y * new_sign; + root.direction.z = axis.z * new_sign; } return; } + idx += 1; - if idx == grip_id { - // Dragging the box edge sets the MText wrap limit, projected on - // the box's flow axis (rotated baseline, or down the column for - // vertical flow). - let (dir, far) = text_box_geom(ml); - let (nx, ny) = match apply { - GripApply::Absolute(a) => (a.x as f64, a.y as f64), - GripApply::Translate(d) => (far[0] + d.x as f64, far[1] + d.y as f64), - }; - let tl = &ml.context.text_location; - // `dir` carries the attachment-side factor (may be ±half), so - // normalise by its squared length: width = proj(flow)/k. - let d2 = (dir[0] * dir[0] + dir[1] * dir[1]).max(1e-12); - let proj = ((nx - tl.x) * dir[0] + (ny - tl.y) * dir[1]) / d2; - let min_w = ml.text_height.max(1.0) * 0.5; - ml.context.text_width = proj.max(min_w); + } + + if grip_id == idx { + let old_end = DVec3::new( + ml.context.text_location.x, + ml.context.text_location.y, + ml.context.text_location.z, + ); + let target = match apply { + GripApply::Absolute(a) => DVec3::new(a.x as f64, a.y as f64, a.z as f64), + GripApply::Translate(d) => { + old_end + DVec3::new(d.x as f64, d.y as f64, d.z as f64) + } + }; + + if let Some((elbow, axis, sign, _, gap)) = landing { + let requested_total = (target - elbow).dot(axis) * sign; + let new_length = (requested_total - gap).max(1.0e-6); + set_mleader_text_location( + ml, + elbow + axis * (sign * (new_length + gap)), + ); + ml.dogleg_length = new_length; + for root in &mut ml.context.leader_roots { + root.landing_distance = new_length; + } + } else { + set_mleader_text_location(ml, target); } + return; + } + + idx += 1; + + if grip_id == idx { + let (dir, far) = text_box_geom(ml); + + let (nx, ny) = match apply { + GripApply::Absolute(a) => ( + a.x as f64, + a.y as f64, + ), + + GripApply::Translate(d) => ( + far[0] + d.x as f64, + far[1] + d.y as f64, + ), + }; + + let tl = &ml.context.text_location; + + let d2 = (dir[0] * dir[0] + dir[1] * dir[1]).max(1.0e-12); + + let proj = ((nx - tl.x) * dir[0] + (ny - tl.y) * dir[1]) / d2; + + let min_w = ml.text_height.max(1.0) * 0.5; + + ml.context.text_width = proj.max(min_w); } } @@ -842,16 +1062,19 @@ fn apply_geom_prop(ml: &mut MultiLeader, field: &str, value: &str) { "text_x" => { if let Some(v) = f64(value) { ml.context.text_location.x = v; + ml.context.content_base_point.x = v; } } "text_y" => { if let Some(v) = f64(value) { ml.context.text_location.y = v; + ml.context.content_base_point.y = v; } } "text_z" => { if let Some(v) = f64(value) { ml.context.text_location.z = v; + ml.context.content_base_point.z = v; } } "text_frame" => { @@ -1090,6 +1313,7 @@ fn apply_transform(ml: &mut MultiLeader, t: &EntityTransform) { p1, p2, ); + entity.context.content_base_point = entity.context.text_location; reflect_xy_direction( &mut entity.context.text_direction.x, &mut entity.context.text_direction.y, @@ -1133,53 +1357,83 @@ impl crate::entities::traits::Grippable for MultiLeader { fn apply_grip(&mut self, grip_id: usize, apply: GripApply) { apply_grip(self, grip_id, apply); } - fn grip_menu(&self, grip_id: usize) -> Vec { - use crate::scene::model::object::{GripMenuAction, GripMenuItem}; + fn grip_menu( + &self, + grip_id: usize, + ) -> Vec { + use crate::scene::model::object::{ + GripMenuAction, + GripMenuItem, + }; + let n_vertices: usize = self .context .leader_roots .iter() - .flat_map(|r| r.lines.iter()) - .map(|l| l.points.len()) + .flat_map(|root| root.lines.iter()) + .map(|line| line.points.len()) .sum(); - if self.content_type == LeaderContentType::MText && grip_id >= n_vertices { - if grip_id == n_vertices { - // Text-location grip. - vec![ + + if self.content_type + == LeaderContentType::MText + { + let has_elbow = + mleader_landing_geom(self).is_some(); + + let elbow_id = + n_vertices; + + let landing_id = + n_vertices + + usize::from(has_elbow); + + let width_id = + landing_id + 1; + + if has_elbow + && grip_id == elbow_id + { + return vec![ GripMenuItem { label: "Stretch", - action: GripMenuAction::Stretch, + action: + GripMenuAction::Stretch, }, + ]; + } + + if grip_id == landing_id { + return vec![ GripMenuItem { - label: "Move with Leader", - action: GripMenuAction::MoveWithLeader, + label: "Stretch", + action: + GripMenuAction::Stretch, }, - GripMenuItem { - label: "Move Independent", - action: GripMenuAction::MoveIndependent, - }, - ] - } else { - // Wrap-width grip: drag only. - Vec::new() + ]; + } + + if grip_id == width_id { + return Vec::new(); } - } else { - // Leader-line vertex. - vec![ - GripMenuItem { - label: "Stretch", - action: GripMenuAction::Stretch, - }, - GripMenuItem { - label: "Add Leader", - action: GripMenuAction::AddLeader, - }, - GripMenuItem { - label: "Remove Leader", - action: GripMenuAction::RemoveLeader, - }, - ] } + + vec![ + GripMenuItem { + label: "Stretch", + action: + GripMenuAction::Stretch, + }, + GripMenuItem { + label: "Add Leader", + action: + GripMenuAction::AddLeader, + }, + GripMenuItem { + label: "Remove Leader", + action: + GripMenuAction::RemoveLeader, + }, + ] } fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::model::object::GripMenuAction) { use crate::scene::model::object::GripMenuAction as A; @@ -1323,16 +1577,40 @@ impl MultiLeaderTess for MultiLeader { // ── Scaling ────────────────────────────────────────────────────────────── // Used only when a context omits an already-resolved content size. - let fallback_content_scale = (ml.scale_factor as f32) - * if crate::scene::annotative::mleader_is_annotative(document, ml) { - anno_scale + let annotative = + crate::scene::annotative::mleader_is_annotative(document, ml); + + // Context sizes are resolved at their stored annotation scale. + let base_scale = if ml.scale_factor.abs() > 1.0e-12 { + ml.scale_factor as f32 + } else { + 1.0 + }; + + let stored_context_scale = ml.context.scale_factor as f32; + + let context_scale_correction = + if annotative && stored_context_scale.abs() > 1.0e-12 { + let correction = + (base_scale * anno_scale) / stored_context_scale; + + if correction.is_finite() && correction > 0.0 { + correction + } else { + 1.0 + } } else { 1.0 }; + let fallback_content_scale = + base_scale * if annotative { anno_scale } else { 1.0 }; + // The active context stores the resolved world-space arrow size. // Reapplying the entity scale here makes context-sized arrows grow twice. - let arrow_size = ml.context.arrowhead_size as f32; + let arrow_size = + ml.context.arrowhead_size as f32 + * context_scale_correction; let draw_arrow = arrow_size > 0.0; let invisible = ml.path_type == MultiLeaderPathType::Invisible; // arrowhead_handle resolves through the block records to a named arrow @@ -1357,7 +1635,25 @@ impl MultiLeaderTess for MultiLeader { // Which side the text grip sits on, recomputed every frame so the text // alignment and the landing mirror live when the arrow or text moves. - let text_loc_w = ml.context.text_location; + let mut text_loc_w = ml.context.text_location; + + // Scale the annotation-side offset around the fixed elbow. + if annotative + && (context_scale_correction - 1.0).abs() > 1.0e-6 + { + if let Some(root) = ml.context.leader_roots.first() { + let anchor = root.connection_point; + let k = context_scale_correction as f64; + + text_loc_w = acadrust::types::Vector3::new( + anchor.x + + (text_loc_w.x - anchor.x) * k, + anchor.y + + (text_loc_w.y - anchor.y) * k, + text_loc_w.z, + ); + } + } let leader_ref_w = ml .context .leader_roots @@ -1471,7 +1767,9 @@ impl MultiLeaderTess for MultiLeader { // stray line up the side of the text. // Landing distance belongs to the selected leader-root context // and is already resolved in world units. - let d = root.landing_distance; + let d = + root.landing_distance + * context_scale_correction as f64; // The dogleg runs along the leader root's stored direction — // for a rotated leader that is the angled baseline, not world // X. Roots without a usable direction keep the legacy @@ -1634,11 +1932,13 @@ impl MultiLeaderTess for MultiLeader { // scale_factor + annotation scale applied. let height = if ctx.text_height > 0.0 { ctx.text_height as f32 + * context_scale_correction } else { - ml.text_height as f32 * fallback_content_scale + ml.text_height as f32 + * fallback_content_scale }; - let ins = &ctx.text_location; + let ins = &text_loc_w; // Subtract world_offset in f64 before casting to f32: drawings often // sit at large absolute coordinates and casting first then subtracting // throws away the precision needed for the rotated sub-glyph offsets. @@ -1731,7 +2031,9 @@ impl MultiLeaderTess for MultiLeader { value: &ctx.text_string, insertion: [local_ins_x as f64, local_ins_y as f64, z as f64], height, - rect_w: ctx.text_width as f32, + rect_w: + ctx.text_width as f32 + * context_scale_correction, rotation: rot, style: &style, attach_h_anchor: h_anchor, diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 7333869f3..eb7ea9f5c 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -1530,6 +1530,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "MIRROR Specify first mirror-line point [%{count} objects]:" => Some(("modify", "mirror-specify-first-mirror-line-point-count-o")), "MIRROR Specify second point [p1=%{px},%{py}]:" => Some(("modify", "mirror-specify-second-point-p1-px-py")), "MLEADER Specify arrowhead point:" => Some(("annotate", "mleader-specify-arrowhead-point")), + "MLEADER Specify landing point:" => Some(("annotate", "mleader-specify-landing-point")), "MLEADER Specify next point [%{count} pts — Enter to place text]:" => Some(("annotate", "mleader-specify-next-point-count-pts-enter-to")), "MLEADERADD Select a multileader:" => Some(("annotate", "mleaderadd-select-a-multileader")), "MLEADERADD Specify arrowhead location:" => Some(("annotate", "mleaderadd-specify-arrowhead-location")), diff --git a/src/modules/annotate/mleader_cmd.rs b/src/modules/annotate/mleader_cmd.rs index b319daa0b..5526c6d31 100644 --- a/src/modules/annotate/mleader_cmd.rs +++ b/src/modules/annotate/mleader_cmd.rs @@ -1,9 +1,4 @@ -// MLEADER command -// -// Flow: -// 1. CollectPoints — click arrowhead, then bend points; Enter (≥2) to finish -// 2. AskText — wants_text_input; blank Enter = no text -// → commit single MultiLeader entity +// MLEADER: pick the arrow and elbow, then edit the text. use acadrust::entities::MultiLeader; use acadrust::types::Vector3; @@ -74,17 +69,34 @@ impl CadCommand for MLeaderCommand { if self.verts.is_empty() { t!("MLEADER Specify arrowhead point:").into_owned() } else { - t!( - "MLEADER Specify next point [%{count} pts — Enter to place text]:", - count = self.verts.len() - ) - .into_owned() + t!("MLEADER Specify landing point:").into_owned() } } fn on_point(&mut self, pt: DVec3) -> CmdResult { self.verts.push(pt); - CmdResult::NeedPoint + + if self.verts.len() < 2 { + return CmdResult::NeedPoint; + } + + let local: Vec = self + .verts + .iter() + .map(|point| self.plane.to_local(*point)) + .collect(); + + let ml = build_mleader( + "", + &local, + Mat4::IDENTITY, + self.style.as_ref(), + self.display_scale, + ); + + CmdResult::CommitAndEditText( + self.plane.place_entity(EntityType::MultiLeader(ml)), + ) } fn on_enter(&mut self) -> CmdResult { @@ -162,14 +174,14 @@ fn build_mleader( style: Option<&acadrust::objects::MultiLeaderStyle>, display_scale: f64, ) -> MultiLeader { - // Last vertex = content/text location; remaining = leader line points - let (leader_pts, content_pt) = verts.split_at(verts.len() - 1); - let content_pt = content_pt[0]; + // First click is the arrow; second click is the elbow. + let arrow_pt = verts[0]; + let elbow_pt = verts[1]; - let leader_v3: Vec = leader_pts.iter().map(|p| v3(*p)).collect(); - let content_v3 = v3(content_pt); + let arrow_v3 = v3(arrow_pt); + let elbow_v3 = v3(elbow_pt); - let mut ml = MultiLeader::with_text(text, content_v3, leader_v3); + let mut ml = MultiLeader::with_text(text, elbow_v3, vec![arrow_v3]); if let Some(style) = style { crate::scene::annotative::apply_mleader_style(&mut ml, style); } else { @@ -186,33 +198,41 @@ fn build_mleader( ml.context.text_height = ml.text_height * display_scale; ml.context.arrowhead_size = ml.arrowhead_size * display_scale; ml.context.landing_gap = landing_gap; - // "Horizontal" landing + text run along the active UCS X axis (identity = - // world), so the annotation reads square to the user's coordinate system. + // Align the landing and text with the active UCS X axis. let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X); // Which side of the leader the text sits on, measured along the UCS X axis. - let last_leader = leader_pts.last().copied().unwrap_or(content_pt); - let to_right = (content_pt - last_leader).dot(ux.as_dvec3()) >= 0.0; + let to_right = (elbow_pt - arrow_pt).dot(ux.as_dvec3()) >= 0.0; let sign = if to_right { 1.0 } else { -1.0 }; let landing = ux * (sign as f32); + ml.context.text_attachment_point = + if to_right { + acadrust::entities::multileader::TextAttachmentPointType::Left + } else { + acadrust::entities::multileader::TextAttachmentPointType::Right + }; - // Text + landing read along the UCS X axis. text_direction is what the - // renderer consults first, so set both. ml.context.text_rotation = (ux.y as f64).atan2(ux.x as f64); ml.context.text_direction = Vector3::new(ux.x as f64, ux.y as f64, 0.0); if let Some(root) = ml.context.leader_roots.first_mut() { - // Leader ends at the clicked point; the landing runs from there toward - // the text along the UCS X axis. - root.direction = Vector3::new(landing.x as f64, landing.y as f64, 0.0); - root.connection_point = content_v3; + root.direction = + Vector3::new(landing.x as f64, landing.y as f64, 0.0); + + root.connection_point = elbow_v3; root.landing_distance = landing_distance; } - // Seed the text one landing-length past the leader end, on the side the - // user dragged toward, offset along the UCS X axis. + // Place text beyond the landing and gap. let off = landing * (landing_distance + landing_gap) as f32; - ml.context.text_location = - Vector3::new(content_v3.x + off.x as f64, content_v3.y + off.y as f64, content_v3.z); + + let text_location = Vector3::new( + elbow_v3.x + off.x as f64, + elbow_v3.y + off.y as f64, + elbow_v3.z, + ); + + ml.context.text_location = text_location; + ml.context.content_base_point = text_location; ml } diff --git a/src/scene/annotative.rs b/src/scene/annotative.rs index 0c8872776..8e3af638c 100644 --- a/src/scene/annotative.rs +++ b/src/scene/annotative.rs @@ -693,29 +693,9 @@ pub fn effective_annotation_scale_for( return fallback; } - // Unlike MTEXT / DIMENSION contexts, an MLEADER context carries its - // already-scaled text height and overall scale factor. Keep the text - // height as stored; make `ml.scale_factor * anno_scale` resolve to the - // active context's scale factor for arrows, doglegs, and fallback text. - if let EntityType::MultiLeader(mleader) = entity { - let Some(active) = - active_object_context_for_scale(doc, entity.common().handle, scale_handle) - else { - return fallback; - }; - let ObjectContextKind::MLeader(context) = &active.kind else { - return fallback; - }; - let base = mleader.scale_factor; - if base.abs() <= 1.0e-12 { - return fallback; - } - let relative = context.scale_factor / base; - return if relative.is_finite() && relative > 0.0 { - relative as f32 - } else { - fallback - }; + // MLEADER resolves the active-to-stored context ratio while tessellating. + if matches!(entity, EntityType::MultiLeader(_)) { + return fallback; } let Some(coll_h) = annotation_scales_dict(doc, entity.common().handle) else { @@ -1586,47 +1566,149 @@ pub fn apply_mleader_style( } } } +fn apply_mleader_style_at_display_scale( + entity: &mut acadrust::entities::MultiLeader, + style: &acadrust::objects::MultiLeaderStyle, + display_scale: f64, +) { + apply_mleader_style(entity, style); + + let scale = if display_scale.is_finite() + && display_scale > 1.0e-12 + { + display_scale + } else { + 1.0 + }; + + // Context sizes are stored at their display scale. + entity.context.scale_factor = scale; + + entity.context.text_height = + style.text_height * scale; + + entity.context.arrowhead_size = + style.arrowhead_size * scale; + + entity.context.landing_gap = + style.landing_gap * scale; + + for root in &mut entity.context.leader_roots { + root.landing_distance = + style.landing_distance * scale; + + root.text_attachment_direction = + entity.text_attachment_direction; + } +} pub fn apply_mleader_style_to_object( doc: &mut CadDocument, handle: Handle, style: &acadrust::objects::MultiLeaderStyle, ) -> bool { - let Some(EntityType::MultiLeader(original)) = doc.get_entity(handle).cloned() else { + let Some(EntityType::MultiLeader(original)) = + doc.get_entity(handle).cloned() + else { return false; }; + + let base_display_scale = if style.is_annotative { + let scale = original.context.scale_factor; + + if scale.is_finite() && scale > 1.0e-12 { + scale + } else { + 1.0 + } + } else { + let scale = style.scale_factor; + + if scale.is_finite() && scale > 1.0e-12 { + scale + } else { + 1.0 + } + }; + let mut styled = original.clone(); - apply_mleader_style(&mut styled, style); - if let Some(EntityType::MultiLeader(entity)) = doc.get_entity_mut(handle) { + + apply_mleader_style_at_display_scale( + &mut styled, + style, + base_display_scale, + ); + + if let Some(EntityType::MultiLeader(entity)) = + doc.get_entity_mut(handle) + { *entity = styled; } - let leaf_handles: Vec<_> = annotation_scales_dict(doc, handle) - .and_then(|collection| as_dict(doc, collection)) - .map(|collection| collection.entries.iter().map(|(_, leaf)| *leaf).collect()) - .unwrap_or_default(); + let leaf_handles: Vec<_> = + annotation_scales_dict(doc, handle) + .and_then(|collection| as_dict(doc, collection)) + .map(|collection| { + collection + .entries + .iter() + .map(|(_, leaf)| *leaf) + .collect() + }) + .unwrap_or_default(); + for leaf_handle in leaf_handles { - let Some(ObjectType::ObjectContextData(leaf)) = doc.objects.get_mut(&leaf_handle) else { - continue; - }; - let ObjectContextKind::MLeader(context) = &mut leaf.kind else { - continue; - }; - let context_scale = context.scale_factor; - let text_height_ratio = if original.text_height.abs() > 1.0e-12 { - context.text_height / original.text_height - } else { - 1.0 - }; + let context_before = + match doc.objects.get(&leaf_handle) { + Some( + ObjectType::ObjectContextData(leaf) + ) => { + let ObjectContextKind::MLeader(context) = + &leaf.kind + else { + continue; + }; + + context.clone() + } + + _ => continue, + }; + + let context_scale = + if context_before.scale_factor.is_finite() + && context_before.scale_factor > 1.0e-12 + { + context_before.scale_factor + } else { + base_display_scale + }; + let mut per_scale = original.clone(); - per_scale.context.clone_from(context); - apply_mleader_style(&mut per_scale, style); - per_scale.context.scale_factor = context_scale; - if style.text_height > 0.0 && text_height_ratio.is_finite() { - per_scale.context.text_height = style.text_height * text_height_ratio; + + per_scale.context = + context_before; + + apply_mleader_style_at_display_scale( + &mut per_scale, + style, + context_scale, + ); + + if let Some( + ObjectType::ObjectContextData(leaf) + ) = doc.objects.get_mut(&leaf_handle) + { + if let ObjectContextKind::MLeader(context) = + &mut leaf.kind + { + context.clone_from( + &per_scale.context, + ); + } } - context.clone_from(&per_scale.context); } + true } @@ -1773,4 +1855,3 @@ pub fn is_annotative(doc: &CadDocument, entity: &EntityType) -> bool { } } -