diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 29309aebe..ec1945846 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -294,6 +294,7 @@ command-copy = .mode = COPY أدخل خيار وضع النسخ [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: لم يتم تحديد نص. .angle-override-shown = تجاوز الزاوية: __ocs_fmt_0__ .plot-preview-task = معاينة الطباعة .link-a-csv-data-source = ربط مصدر بيانات CSV diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 0e9311cba..ac46e236a 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -281,6 +281,7 @@ command-copy = .displacement = COPY Задайте преместване: .mode = COPY Въведете режим на копиране [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: няма избран текст. .angle-override-shown = Замяна на ъгъла: __ocs_fmt_0__ .plot-preview-task = Преглед на печата .link-a-csv-data-source = Свързване на CSV източник на данни diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 776d38f4f..b70fa38a8 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -281,6 +281,7 @@ command-copy = .mode = COPY Zadejte režim kopírování [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: není vybrán žádný text. .angle-override-shown = Přepsání úhlu: __ocs_fmt_0__ .plot-preview-task = Náhled tisku .link-a-csv-data-source = Propojit zdroj dat CSV diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 4b79b2aaf..80f05cec8 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -282,6 +282,7 @@ command-copy = .mode = COPY Kopiermodus eingeben [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: kein Text gewählt. .angle-override-shown = Winkelvorgabe: __ocs_fmt_0__ .plot-preview-task = Plotvorschau .link-a-csv-data-source = CSV-Datenquelle verknüpfen diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index ea2c1bc31..6b70539b3 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -281,6 +281,7 @@ command-copy = .mode = COPY Επιλέξτε λειτουργία αντιγραφής [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: δεν επιλέχθηκε κείμενο. .angle-override-shown = Παράκαμψη γωνίας: __ocs_fmt_0__ .plot-preview-task = Προεπισκόπηση εκτύπωσης .link-a-csv-data-source = Σύνδεση πηγής δεδομένων CSV diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 5aaf066de..f39393ae5 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -281,6 +281,7 @@ command-copy = .mode = COPY Enter a copy mode option [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: no text selected. .angle-override-shown = Angle Override: __ocs_fmt_0__ .browser-cannot-open-typed-path = The browser cannot open a typed path; pick the file instead. .recover-drawing = Recover Drawing diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index a736af26d..0375abb4d 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -282,6 +282,7 @@ command-copy = .mode = COPY Indique modo de copia [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: no se ha seleccionado ningún texto. .angle-override-shown = Modificación de ángulo: __ocs_fmt_0__ .plot-preview-task = Vista previa de trazado .link-a-csv-data-source = Vincular un origen de datos CSV diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 8965ec556..44e0c7743 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -281,6 +281,7 @@ command-copy = .mode = COPY Valitse kopiointitila [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: tekstiä ei ole valittu. .angle-override-shown = Kulman ohitus: __ocs_fmt_0__ .plot-preview-task = Tulostuksen esikatselu .link-a-csv-data-source = Linkitä CSV-tietolähde diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index df8ae715c..a0f804fbb 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -282,6 +282,7 @@ command-copy = .mode = COPY Entrez le mode de copie [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP : aucun texte sélectionné. .angle-override-shown = Remplacement d’angle : __ocs_fmt_0__ .plot-preview-task = Aperçu du tracé .link-a-csv-data-source = Lier une source de données CSV diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index b27d163e6..af70fa6a1 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -270,6 +270,7 @@ command-copy = .mode = COPY कॉपी मोड विकल्प दर्ज करें [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: कोई टेक्स्ट चयनित नहीं। .angle-override-shown = कोण ओवरराइड: __ocs_fmt_0__ .plot-preview-task = प्लॉट पूर्वावलोकन .link-a-csv-data-source = CSV डेटा स्रोत लिंक करें diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 5a1cfd97e..5699219b3 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -280,6 +280,7 @@ command-copy = .mode = COPY Adja meg a másolási módot [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: nincs kijelölt szöveg. .angle-override-shown = Szög felülbírálása: __ocs_fmt_0__ .parametric-coincident = Egybeeső .parametric-colinear = Egy egyenesbe eső diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 330532d2c..f99f20f76 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -281,6 +281,7 @@ command-copy = .mode = COPY Specificare modalità di copia [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: nessun testo selezionato. .angle-override-shown = Sostituzione angolo: __ocs_fmt_0__ .plot-preview-task = Anteprima di stampa .link-a-csv-data-source = Collega un'origine dati CSV diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 5a03654fe..d42694451 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -272,6 +272,7 @@ command-copy = .mode = COPY コピーモードを入力 [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: 文字が選択されていません。 .angle-override-shown = 角度の優先: __ocs_fmt_0__ .plot-preview-task = 印刷プレビュー .link-a-csv-data-source = CSV データ ソースをリンク diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 34e1b6146..27a7f46be 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -281,6 +281,7 @@ command-copy = .mode = COPY 복사 모드 옵션 입력 [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: 선택된 문자가 없습니다. .angle-override-shown = 각도 재지정: __ocs_fmt_0__ .plot-preview-task = 플롯 미리 보기 .link-a-csv-data-source = CSV 데이터 원본 연결 diff --git a/locales/lo-LA/opencadstudio.ftl b/locales/lo-LA/opencadstudio.ftl index c0320371c..f2634923b 100644 --- a/locales/lo-LA/opencadstudio.ftl +++ b/locales/lo-LA/opencadstudio.ftl @@ -281,6 +281,7 @@ command-copy = .mode = COPY ປ້ອນຕົວເລືອກໂໝດການຄັດລອກ [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: ບໍ່ໄດ້ເລືອກຂໍ້ຄວາມ. .angle-override-shown = ກຳນົດມຸມແທນ: __ocs_fmt_0__ .remote-image-url-could-not-be-fetched-the-request-timed-out = ບໍ່ສາມາດດຶງຮູບພາບທາງໄກ "__ocs_fmt_0__" ໄດ້: ການຮ້ອງຂໍໝົດເວລາ. .remote-image-url-was-not-loaded-it-exceeds-the-64-mb-remote-image-limit = ບໍ່ໄດ້ໂຫຼດຮູບພາບທາງໄກ "__ocs_fmt_0__": ມັນເກີນຂີດຈຳກັດ 64 MB ຂອງຮູບພາບທາງໄກ. diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index ba4b42c03..ae7e2f9cb 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -282,6 +282,7 @@ command-copy = .mode = COPY Geef kopieermodus op [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: geen tekst geselecteerd. .angle-override-shown = Hoekoverschrijving: __ocs_fmt_0__ .plot-preview-task = Plotvoorbeeld .link-a-csv-data-source = CSV-gegevensbron koppelen diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 5bff69231..4779f1c48 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -281,6 +281,7 @@ command-copy = .mode = COPY Podaj tryb kopiowania [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: nie wybrano tekstu. .angle-override-shown = Zastąpienie kąta: __ocs_fmt_0__ .plot-preview-task = Podgląd wydruku .link-a-csv-data-source = Połącz źródło danych CSV diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 2fbe84c78..1878bd9bf 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -282,6 +282,7 @@ command-copy = .mode = COPY Informe o modo de cópia [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: nenhum texto selecionado. .angle-override-shown = Substituição de ângulo: __ocs_fmt_0__ .plot-preview-task = Visualização da plotagem .link-a-csv-data-source = Vincular uma fonte de dados CSV diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 5a3b3eec2..463bc5316 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -285,6 +285,7 @@ command-copy = .mode = COPY Задайте режим копирования [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: текст не выбран. .angle-override-shown = Переопределение угла: __ocs_fmt_0__ .plot-preview-task = Предварительный просмотр печати .link-a-csv-data-source = Связать источник данных CSV diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 131d2af77..246c460fe 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -267,6 +267,7 @@ command-copy = .mode = COPY Kopyalama modu seçin [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: metin seçilmedi. .angle-override-shown = Açı geçersiz kılma: __ocs_fmt_0__ .plot-preview-task = Çizim önizleme .link-a-csv-data-source = CSV veri kaynağı bağla diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index ae45b0d03..39b1f20aa 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -269,6 +269,7 @@ command-copy = .mode = COPY 输入复制模式选项 [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: 未选择文字。 .angle-override-shown = 角度替代:__ocs_fmt_0__ .plot-preview-task = 打印预览 .link-a-csv-data-source = 链接 CSV 数据源 diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 62df4ca51..1f56ac9dc 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -281,6 +281,7 @@ command-copy = .mode = COPY 輸入複製模式選項 [Single/Multiple] : common = + .txtexp-no-text-selected = TXTEXP: 未選擇文字。 .angle-override-shown = 角度取代:__ocs_fmt_0__ .plot-preview-task = 出圖預覽 .link-a-csv-data-source = 連結 CSV 資料來源 diff --git a/src/app/commands/dim.rs b/src/app/commands/dim.rs index 489c140ee..ba4a89d96 100644 --- a/src/app/commands/dim.rs +++ b/src/app/commands/dim.rs @@ -1303,6 +1303,38 @@ impl OpenCADStudio { } } + // TXTEXP — text becomes the polylines of its character outlines. + "TXTEXP" => { + let selected: Vec<_> = self.tabs[i] + .scene + .selected_entities() + .into_iter() + .filter(|(handle, _)| !self.tabs[i].scene.is_layer_locked(*handle)) + .collect(); + if selected.is_empty() { + use crate::modules::draw::select::SelectObjectsCommand; + let cmd = SelectObjectsCommand::new("TXTEXP"); + self.command_line.push_info(&cmd.prompt()); + self.tabs[i].active_cmd = Some(Box::new(cmd)); + } else { + use crate::modules::draw::modify::explode::apply_explode_replacements; + use crate::modules::draw::modify::txtexp::plan_text_explode; + let replacements = plan_text_explode(&selected, &self.tabs[i].scene.document); + let exploded = replacements.len(); + if exploded > 0 { + self.push_undo_snapshot(i, "TXTEXP"); + apply_explode_replacements(&mut self.tabs[i].scene, replacements); + self.tabs[i].dirty = true; + self.refresh_properties(); + self.command_line + .push_output(crate::tf!("{exploded} object(s) exploded.").as_ref()); + } else { + self.command_line + .push_info(crate::t!("TXTEXP: no text selected.").as_ref()); + } + } + } + "OFFSET" => { use crate::modules::draw::modify::offset::{is_offsettable, OffsetCommand}; let all_entities: Vec<_> = self.tabs[i] diff --git a/src/app/control/mod.rs b/src/app/control/mod.rs index b30ed9953..895eacb15 100644 --- a/src/app/control/mod.rs +++ b/src/app/control/mod.rs @@ -277,6 +277,7 @@ fn command_category(name: &str) -> &'static str { "BREAK", "JOIN", "EXPLODE", + "TXTEXP", "STRETCH", "ARRAY", "PEDIT", @@ -349,6 +350,7 @@ fn command_selection_policy(name: &str) -> &'static str { "CUTCLIP", "ERASE", "EXPLODE", + "TXTEXP", "GROUP", "LAYFRZ", "LAYLCK", diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 6647f6f77..472fa90d2 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -3697,6 +3697,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Width factor" => Some(("common", "width-factor-sentence-case")), "Width: %{value}" => Some(("ui", "width-value")), "Window" => Some(("common", "window")), + "TXTEXP: no text selected." => Some(("common", "txtexp-no-text-selected")), "Windows desktop" => Some(("styles", "windows-desktop")), "Wipeout" => Some(("common", "wipeout")), "Wireframe" => Some(("common", "wireframe")), diff --git a/src/modules/draw/modify/mod.rs b/src/modules/draw/modify/mod.rs index d8e35bd8d..0f821ac61 100644 --- a/src/modules/draw/modify/mod.rs +++ b/src/modules/draw/modify/mod.rs @@ -30,3 +30,4 @@ pub mod stretch; pub mod torient; pub mod translate; pub mod trim; +pub mod txtexp; diff --git a/src/modules/draw/modify/txtexp.rs b/src/modules/draw/modify/txtexp.rs new file mode 100644 index 000000000..adc851ad0 --- /dev/null +++ b/src/modules/draw/modify/txtexp.rs @@ -0,0 +1,87 @@ +// TXTEXP — replace text with the outlines of its characters. +// +// Each glyph contour of a TEXT or MTEXT becomes a polyline on the text's +// layer and with its properties: a lightweight polyline when the text lies +// flat, a 3D polyline when it is tilted. + +use codec::entities::{LwPolyline, LwVertex, Polyline3D, Vertex3DPolyline}; +use codec::types::Vector2; +use codec::{CadDocument, EntityType, Handle}; + +use crate::scene::convert::acad_to_render::{convert, RenderObject}; + +/// The outlines replacing each explodable text of `selected`, as +/// (text, polylines) pairs ready for `apply_explode_replacements`. +pub fn plan_text_explode( + selected: &[(Handle, &EntityType)], + document: &CadDocument, +) -> Vec<(Handle, Vec)> { + selected + .iter() + .filter_map(|(handle, entity)| { + let outlines = text_outlines(entity, document); + (!outlines.is_empty()).then_some((*handle, outlines)) + }) + .collect() +} + +/// The character outlines of a TEXT or MTEXT; empty for anything else. +pub fn text_outlines(entity: &EntityType, document: &CadDocument) -> Vec { + let (common, elevation) = match entity { + EntityType::Text(text) => (&text.common, text.insertion_point.z), + EntityType::MText(text) => (&text.common, text.insertion_point.z), + _ => return Vec::new(), + }; + let Some(RenderObject::Text(groups)) = convert(entity, document).map(|render| render.object) + else { + return Vec::new(); + }; + let mut common = common.clone(); + common.handle = Handle::NULL; + let mut outlines = Vec::new(); + for group in &groups { + for stroke in group.strokes.iter().filter(|stroke| stroke.len() >= 2) { + let mut points: Vec<[f64; 3]> = stroke + .iter() + .map(|&[x, y]| { + let (x, y) = (x as f64, y as f64); + match group.plane { + Some(plane) => std::array::from_fn(|axis| { + plane.origin[axis] + plane.x_axis[axis] * x + plane.y_axis[axis] * y + }), + None => [group.origin[0] + x, group.origin[1] + y, elevation], + } + }) + .collect(); + let closed = points.len() > 2 && { + let (first, last) = (points[0], points[points.len() - 1]); + (0..3).all(|axis| (first[axis] - last[axis]).abs() <= 1e-9) + }; + if closed { + points.pop(); + } + let z = points[0][2]; + if points.iter().all(|point| (point[2] - z).abs() <= 1e-9) { + let mut polyline = LwPolyline::new(); + polyline.common = common.clone(); + polyline.is_closed = closed; + polyline.elevation = z; + polyline.vertices = points + .iter() + .map(|point| LwVertex::new(Vector2::new(point[0], point[1]))) + .collect(); + outlines.push(EntityType::LwPolyline(polyline)); + } else { + let mut polyline = Polyline3D::new(); + polyline.common = common.clone(); + polyline.flags.closed = closed; + polyline.vertices = points + .iter() + .map(|point| Vertex3DPolyline::from_xyz(point[0], point[1], point[2])) + .collect(); + outlines.push(EntityType::Polyline3D(polyline)); + } + } + } + outlines +} diff --git a/src/modules/draw/select.rs b/src/modules/draw/select.rs index f434bfeeb..8b7798220 100644 --- a/src/modules/draw/select.rs +++ b/src/modules/draw/select.rs @@ -360,6 +360,7 @@ inventory::submit!(crate::command::CommandRegistration { "MOVE", "ROTATE", "SCALE", - "STRETCH" + "STRETCH", + "TXTEXP" ] }); // SelectObjectsCommand