fix(mleader): improve behavior and persistence

Keep annotation contexts, grip placement, and style dictionary entries consistent across editing and file saves.

Refs #779
This commit is contained in:
Hakan Seven committed 2026-08-25 08:34:25 +03:00
commit 20c7a2df58
29 files changed
+858 -195

No files matched your search

+1
View File
@@ -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 حدد موقع رأس السهم:
+1
View File
@@ -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 Посочи местоположение на върха на стрелката:
+1
View File
@@ -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:
+1
View File
@@ -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:
+1
View File
@@ -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:
+1
View File
@@ -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:
+1
View File
@@ -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:
+1
View File
@@ -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:
+1
View File
@@ -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 एरोहेड का स्थान निर्दिष्ट करें:
+1
View File
@@ -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:
+1
View File
@@ -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:
+1
View File
@@ -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 矢印位置を指定:
+1
View File
@@ -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 화살촉 위치 지정:
+1
View File
@@ -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:
+1
View File
@@ -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:
+1
View File
@@ -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:
+1
View File
@@ -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 Укажите местоположение наконечника стрелки:
+1
View File
@@ -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:
+1
View File
@@ -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 指定箭头位置:
+1
View File
@@ -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 指定箭頭位置:
+20 -1
View File
@@ -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;
+14 -7
View File
@@ -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(
&current_mleader
)
})
.cloned()
.unwrap_or_default()
};
.or_else(|| {
mleader_names.first().cloned()
})
.unwrap_or_default();
let table_names: Vec<String> = doc
.objects
+17
View File
@@ -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)]);
+188 -1
View File
@@ -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<Handle> {
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<acadrust::objects::MultiLeaderStyle> =
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<acadrust::Handle> = {
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
+11 -2
View File
@@ -1673,9 +1673,18 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
}
}
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;
+405 -103
View File
@@ -182,8 +182,18 @@ fn to_render(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<Rende
style: &resolved,
// Side-anchored on the leader-facing edge so the text reads
// outward; flips live with the leader/text side.
attach_h_anchor: if text_sign >= 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<Rende
} else {
0.0
};
// text_location is the leader-facing (near) edge; the landing runs one
// dogleg from there back toward the leader.
let landing_pt = [
text_loc.x - text_sign * dogleg * tdx,
text_loc.y - text_sign * dogleg * tdy,
let landing_gap = ml.context.landing_gap.max(0.0);
// Landing endpoints.
let elbow_pt = [
text_loc.x - text_sign * (dogleg + landing_gap) * tdx,
text_loc.y - text_sign * (dogleg + landing_gap) * tdy,
text_loc.z,
];
let text_edge_pt = [
text_loc.x - text_sign * landing_gap * tdx,
text_loc.y - text_sign * landing_gap * tdy,
text_loc.z,
];
@@ -225,13 +243,17 @@ fn to_render(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<Rende
first = false;
// Build the full control-point list: line.points + landing point
let mut ctrl: Vec<[f64; 3]> = 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<Rende
// Horizontal landing from the leader end to the text's near edge.
if dogleg > 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<Rende
// ── Grips ──────────────────────────────────────────────────────────────────
//
// IDs are assigned in two passes:
// 0 .. total_line_pts - 1 : square grips on every LeaderLine vertex
// total_line_pts : diamond grip on context.text_location (if MText)
// Leader vertices come first, followed by optional elbow, text, and width grips.
/// Side the text reads toward (+1 right / -1 left) and the text box's far
/// edge (the wrap-width grip position). The box width is the explicit wrap
@@ -428,27 +448,131 @@ fn text_box_geom(ml: &MultiLeader) -> ([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<GripDef> {
let mut result: Vec<GripDef> = 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<GripDef> {
/// 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<crate::scene::model::object::GripMenuItem> {
use crate::scene::model::object::{GripMenuAction, GripMenuItem};
fn grip_menu(
&self,
grip_id: usize,
) -> Vec<crate::scene::model::object::GripMenuItem> {
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,
+1
View File
@@ -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")),
+52 -32
View File
@@ -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<DVec3> = 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<Vector3> = 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
}
+130 -49
View File
@@ -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 {
}
}