diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index d7f98e422..eea138efa 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -295,6 +295,9 @@ command-copy = .mode = COPY أدخل خيار وضع النسخ [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ هنا فقط (أخضر)، __ocs_fmt_1__ في الرسم الآخر فقط (أحمر)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = مقارنة مع رسم .label-associative-array-created = __ocs_fmt_0__: تم إنشاء مصفوفة ترابطية. .assoc-array-array-path = مصفوفة (مسار) .assoc-array-array-polar = مصفوفة (قطبية) diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index cb7085f4d..feee17bdc 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -282,6 +282,9 @@ command-copy = .displacement = COPY Задайте преместване: .mode = COPY Въведете режим на копиране [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ само тук (зелено), __ocs_fmt_1__ само в другия чертеж (червено)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Сравняване с чертеж .label-associative-array-created = __ocs_fmt_0__: създаден е асоциативен масив. .assoc-array-array-path = Масив (по път) .assoc-array-array-polar = Масив (полярен) diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 71bf17c6f..a32db3547 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -282,6 +282,9 @@ command-copy = .mode = COPY Zadejte režim kopírování [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ jen zde (zeleně), __ocs_fmt_1__ jen v druhém výkresu (červeně)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Porovnat s výkresem .label-associative-array-created = __ocs_fmt_0__: vytvořeno asociativní pole. .assoc-array-array-path = Pole (podél cesty) .assoc-array-array-polar = Pole (kruhové) diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 6ef673520..42ce8800d 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -283,6 +283,9 @@ command-copy = .mode = COPY Kopiermodus eingeben [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ nur hier (grün), __ocs_fmt_1__ nur in der anderen Zeichnung (rot)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Mit Zeichnung vergleichen .label-associative-array-created = __ocs_fmt_0__: assoziative Reihe erstellt. .assoc-array-array-path = Reihe (Pfad) .assoc-array-array-polar = Reihe (polar) diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 48f3a6273..f5646e118 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -282,6 +282,9 @@ command-copy = .mode = COPY Επιλέξτε λειτουργία αντιγραφής [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ μόνο εδώ (πράσινο), __ocs_fmt_1__ μόνο στο άλλο σχέδιο (κόκκινο)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Σύγκριση με σχέδιο .label-associative-array-created = __ocs_fmt_0__: δημιουργήθηκε συσχετιστικός πίνακας. .assoc-array-array-path = Πίνακας (διαδρομή) .assoc-array-array-polar = Πίνακας (πολικός) diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index d1781a219..f2539ea74 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -282,6 +282,9 @@ command-copy = .mode = COPY Enter a copy mode option [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ only here (green), __ocs_fmt_1__ only in the other drawing (red)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Compare with drawing .label-associative-array-created = __ocs_fmt_0__: associative array created. .assoc-array-array-path = Array (Path) .assoc-array-array-polar = Array (Polar) diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 96d19d420..47ec91dcf 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -283,6 +283,9 @@ command-copy = .mode = COPY Indique modo de copia [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ solo aquí (verde), __ocs_fmt_1__ solo en el otro dibujo (rojo)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Comparar con dibujo .label-associative-array-created = __ocs_fmt_0__: se ha creado una matriz asociativa. .assoc-array-array-path = Matriz (trayectoria) .assoc-array-array-polar = Matriz (polar) diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 672a1be0e..1eb2acba2 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -282,6 +282,9 @@ command-copy = .mode = COPY Valitse kopiointitila [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ vain täällä (vihreä), __ocs_fmt_1__ vain toisessa piirustuksessa (punainen)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Vertaa piirustukseen .label-associative-array-created = __ocs_fmt_0__: assosiatiivinen taulukko luotu. .assoc-array-array-path = Taulukko (polku) .assoc-array-array-polar = Taulukko (polaarinen) diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index eb9f59748..1193628d7 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -283,6 +283,9 @@ command-copy = .mode = COPY Entrez le mode de copie [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ seulement ici (vert), __ocs_fmt_1__ seulement dans l’autre dessin (rouge)__ocs_fmt_2__ [Next/Previous/eXit] : + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Comparer avec un dessin .label-associative-array-created = __ocs_fmt_0__ : réseau associatif créé. .assoc-array-array-path = Réseau (trajectoire) .assoc-array-array-polar = Réseau (polaire) diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 6c58c7b57..d3a7193ef 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -271,6 +271,9 @@ command-copy = .mode = COPY कॉपी मोड विकल्प दर्ज करें [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ केवल यहाँ (हरा), __ocs_fmt_1__ केवल दूसरी ड्रॉइंग में (लाल)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = ड्रॉइंग से तुलना करें .label-associative-array-created = __ocs_fmt_0__: सहयोगी ऐरे बनाया गया। .assoc-array-array-path = ऐरे (पथ) .assoc-array-array-polar = ऐरे (ध्रुवीय) diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 75d445a98..a5e19f84a 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -281,6 +281,9 @@ command-copy = .mode = COPY Adja meg a másolási módot [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ csak itt (zöld), __ocs_fmt_1__ csak a másik rajzban (piros)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Összehasonlítás rajzzal .label-associative-array-created = __ocs_fmt_0__: asszociatív tömb létrehozva. .assoc-array-array-path = Tömb (útvonal) .assoc-array-array-polar = Tömb (poláris) diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index fc91c30db..ee88209fc 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -282,6 +282,9 @@ command-copy = .mode = COPY Specificare modalità di copia [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ solo qui (verde), __ocs_fmt_1__ solo nell'altro disegno (rosso)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Confronta con disegno .label-associative-array-created = __ocs_fmt_0__: serie associativa creata. .assoc-array-array-path = Serie (percorso) .assoc-array-array-polar = Serie (polare) diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 5fbb8a6e6..c404bf79d 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -273,6 +273,9 @@ command-copy = .mode = COPY コピーモードを入力 [Single/Multiple] : common = + .compare-prompt = COMPARE ここのみ __ocs_fmt_0__ (緑)、もう一方の図面のみ __ocs_fmt_1__ (赤)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = 図面と比較 .label-associative-array-created = __ocs_fmt_0__: 自動調整配列複写を作成しました。 .assoc-array-array-path = 配列複写 (パス) .assoc-array-array-polar = 配列複写 (円形状) diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 8d25f85c4..9b090c1b2 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -282,6 +282,9 @@ command-copy = .mode = COPY 복사 모드 옵션 입력 [Single/Multiple] : common = + .compare-prompt = COMPARE 여기에만 __ocs_fmt_0__ (녹색), 다른 도면에만 __ocs_fmt_1__ (빨간색)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = 도면과 비교 .label-associative-array-created = __ocs_fmt_0__: 연관 배열을 만들었습니다. .assoc-array-array-path = 배열 (경로) .assoc-array-array-polar = 배열 (원형) diff --git a/locales/lo-LA/opencadstudio.ftl b/locales/lo-LA/opencadstudio.ftl index c188faa06..daa8e1ac4 100644 --- a/locales/lo-LA/opencadstudio.ftl +++ b/locales/lo-LA/opencadstudio.ftl @@ -282,6 +282,9 @@ command-copy = .mode = COPY ປ້ອນຕົວເລືອກໂໝດການຄັດລອກ [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ ຢູ່ນີ້ເທົ່ານັ້ນ (ຂຽວ), __ocs_fmt_1__ ຢູ່ໃນແບບອື່ນເທົ່ານັ້ນ (ແດງ)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = ປຽບທຽບກັບແບບ .label-associative-array-created = __ocs_fmt_0__: ສ້າງອາເຣແບບເຊື່ອມໂຍງແລ້ວ. .assoc-array-array-path = ອາເຣ (ເສັ້ນທາງ) .assoc-array-array-polar = ອາເຣ (ວົງມົນ) diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index f621a55f9..aa2947764 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -283,6 +283,9 @@ command-copy = .mode = COPY Geef kopieermodus op [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ alleen hier (groen), __ocs_fmt_1__ alleen in de andere tekening (rood)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Vergelijken met tekening .label-associative-array-created = __ocs_fmt_0__: associatieve reeks gemaakt. .assoc-array-array-path = Reeks (pad) .assoc-array-array-polar = Reeks (polair) diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 648827533..e14539fb9 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -282,6 +282,9 @@ command-copy = .mode = COPY Podaj tryb kopiowania [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ tylko tutaj (zielone), __ocs_fmt_1__ tylko w drugim rysunku (czerwone)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Porównaj z rysunkiem .label-associative-array-created = __ocs_fmt_0__: utworzono szyk skojarzony. .assoc-array-array-path = Szyk (ścieżka) .assoc-array-array-polar = Szyk (biegunowy) diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index eacb449da..a27c55e6f 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -283,6 +283,9 @@ command-copy = .mode = COPY Informe o modo de cópia [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ só aqui (verde), __ocs_fmt_1__ só no outro desenho (vermelho)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Comparar com desenho .label-associative-array-created = __ocs_fmt_0__: matriz associativa criada. .assoc-array-array-path = Matriz (caminho) .assoc-array-array-polar = Matriz (polar) diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index eac8d2f50..8ce95ea49 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -286,6 +286,9 @@ command-copy = .mode = COPY Задайте режим копирования [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ только здесь (зелёные), __ocs_fmt_1__ только в другом чертеже (красные)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Сравнить с чертежом .label-associative-array-created = __ocs_fmt_0__: создан ассоциативный массив. .assoc-array-array-path = Массив (по траектории) .assoc-array-array-polar = Массив (круговой) diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index de6964037..5230c586a 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -268,6 +268,9 @@ command-copy = .mode = COPY Kopyalama modu seçin [Single/Multiple] : common = + .compare-prompt = COMPARE __ocs_fmt_0__ yalnızca burada (yeşil), __ocs_fmt_1__ yalnızca diğer çizimde (kırmızı)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = Çizimle karşılaştır .label-associative-array-created = __ocs_fmt_0__: ilişkisel dizi oluşturuldu. .assoc-array-array-path = Dizi (Yol) .assoc-array-array-polar = Dizi (Kutupsal) diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index c1c21d60e..87f586862 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -270,6 +270,9 @@ command-copy = .mode = COPY 输入复制模式选项 [Single/Multiple] : common = + .compare-prompt = COMPARE 仅此处 __ocs_fmt_0__ (绿色),仅另一图形 __ocs_fmt_1__ (红色)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = 与图形比较 .label-associative-array-created = __ocs_fmt_0__: 已创建关联阵列。 .assoc-array-array-path = 阵列 (路径) .assoc-array-array-polar = 阵列 (环形) diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 11dd5b6ee..57e1801ad 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -282,6 +282,9 @@ command-copy = .mode = COPY 輸入複製模式選項 [Single/Multiple] : common = + .compare-prompt = COMPARE 僅此處 __ocs_fmt_0__ (綠色),僅另一圖面 __ocs_fmt_1__ (紅色)__ocs_fmt_2__ [Next/Previous/eXit]: + .compare-error = COMPARE: __ocs_fmt_0__ + .compare-with-drawing = 與圖面比較 .label-associative-array-created = __ocs_fmt_0__: 已建立關聯陣列。 .assoc-array-array-path = 陣列 (路徑) .assoc-array-array-polar = 陣列 (環形) diff --git a/src/app/command_driver/mod.rs b/src/app/command_driver/mod.rs index 7c4a40807..9300ab80e 100644 --- a/src/app/command_driver/mod.rs +++ b/src/app/command_driver/mod.rs @@ -885,6 +885,11 @@ impl OpenCADStudio { CmdResult::ZoomToWindow { p1, p2 } => { self.handle_zoom_to_window(p1, p2); } + CmdResult::Show { p1, p2 } => { + let i = self.active_tab; + self.tabs[i].scene.remember_current_view(); + self.tabs[i].scene.zoom_to_window(p1.as_vec3(), p2.as_vec3()); + } CmdResult::CommitAndZoom { entity, p1, p2 } => { self.handle_commit_entities_and_exit(vec![entity], false, false); self.handle_zoom_to_window(p1, p2); diff --git a/src/app/commands/styleprops.rs b/src/app/commands/styleprops.rs index fcf75ec7d..430256eaf 100644 --- a/src/app/commands/styleprops.rs +++ b/src/app/commands/styleprops.rs @@ -3335,6 +3335,42 @@ impl OpenCADStudio { // Read-only scan: flags entities on undefined layers and block // references to undefined block definitions. Reports only; it does // not auto-repair (so it can never make the drawing worse). + // COMPARE — overlay what differs from another drawing. Bare picks + // the drawing; with a path it compares at once. + "COMPARE" => { + return Some(Task::perform( + async { + crate::sys::file_dialog() + .set_title(crate::t!("Compare with drawing").as_ref()) + .add_filter(crate::t!("Drawing").as_ref(), &["dwg", "dxf"]) + .pick_file() + .await + .map(|handle| crate::sys::handle_path(&handle)) + }, + |path| match path { + Some(path) => Message::Command(format!("COMPARE {}", path.display())), + None => Message::Noop, + }, + )); + } + cmd if cmd.starts_with("COMPARE ") => { + let path = std::path::PathBuf::from(cmd.trim_start_matches("COMPARE").trim()); + match crate::io::load_file(&path) { + Ok(other) => { + let comparison = + crate::scene::compare::compare(&self.tabs[i].scene, other); + // The overlay shows at once, before the cursor moves. + self.tabs[i].scene.set_preview_wires(comparison.overlay.clone()); + let command = + crate::modules::manage::compare::CompareCommand::new(comparison); + self.command_line.push_info(&command.prompt()); + self.tabs[i].active_cmd = Some(Box::new(command)); + } + Err(error) => self + .command_line + .push_error(crate::tf!("COMPARE: {error}").as_ref()), + } + } "AUDIT" => { use std::collections::BTreeSet; let mut undefined_layers: BTreeSet = BTreeSet::new(); diff --git a/src/command.rs b/src/command.rs index b3b3a9b1c..6cd30c03c 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1948,6 +1948,11 @@ pub enum CmdResult { p1: DVec3, p2: DVec3, }, + /// Zoom to the given corner points; the command stays active. + Show { + p1: DVec3, + p2: DVec3, + }, /// Commit `entity`, then zoom to the given corner points; end command. CommitAndZoom { entity: EntityType, diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 9bb77b77b..4a4745e31 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -4227,6 +4227,9 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "UCS requires origin, X-axis point and XY-plane point." => Some(("common", "ucs-requires-origin-x-axis-point-and-xy-plane-point")), "DWGUNITS current unit: {current}" => Some(("common", "dwgunits-current-unit")), "LAYTRANS: {why}." => Some(("common", "laytrans")), + "Compare with drawing" => Some(("common", "compare-with-drawing")), + "COMPARE: {error}" => Some(("common", "compare-error")), + "COMPARE {added} only here (green), {removed} only in the other drawing (red){position} [Next/Previous/eXit]:" => Some(("common", "compare-prompt")), "LAYTRANS: no layer names in common — use LAYTRANS with no file to map them." => Some(("common", "laytrans-no-layer-names-in-common-use-laytrans-with-no-file-to-map-them")), "DWGUNITS: not a unit this drawing can use." => Some(("common", "dwgunits-not-a-unit-this-drawing-can-use")), "DWGUNITS: already in {label}." => Some(("common", "dwgunits-already-in")), diff --git a/src/modules/manage/compare.rs b/src/modules/manage/compare.rs new file mode 100644 index 000000000..45c4c05bd --- /dev/null +++ b/src/modules/manage/compare.rs @@ -0,0 +1,104 @@ +// COMPARE — show what differs from another drawing and step through it. +// +// COMPARE only here (green), only in the other drawing +// (red) [Next/Previous/eXit]: + +use crate::command::{CadCommand, CmdOption, CmdResult}; +use crate::scene::compare::Comparison; +use crate::scene::WireModel; +use glam::DVec3; + +pub struct CompareCommand { + comparison: Comparison, + /// The difference shown last; `None` before the first step. + current: Option, +} + +impl CompareCommand { + pub fn new(comparison: Comparison) -> Self { + Self { + comparison, + current: None, + } + } + + fn show(&mut self, forward: bool) -> CmdResult { + let count = self.comparison.spots.len(); + if count == 0 { + return CmdResult::NeedPoint; + } + let next = match (self.current, forward) { + (None, true) => 0, + (None, false) => count - 1, + (Some(index), true) => (index + 1) % count, + (Some(index), false) => (index + count - 1) % count, + }; + self.current = Some(next); + let (min, max) = self.comparison.spots[next]; + // A margin around the difference so it is seen in its context. + let margin = ((max - min).length() * 0.5).max(1.0); + CmdResult::Show { + p1: min - DVec3::splat(margin), + p2: max + DVec3::splat(margin), + } + } +} + +impl CadCommand for CompareCommand { + fn name(&self) -> &'static str { + "COMPARE" + } + + fn prompt(&self) -> String { + let (added, removed) = (self.comparison.added, self.comparison.removed); + let position = match self.current { + Some(index) => format!(" {}/{}", index + 1, self.comparison.spots.len()), + None => String::new(), + }; + crate::tf!( + "COMPARE {added} only here (green), {removed} only in the other drawing (red){position} [Next/Previous/eXit]:" + ) + .into_owned() + } + + fn options(&self) -> Vec { + vec![ + CmdOption::new("Next", "N"), + CmdOption::new("Previous", "P"), + CmdOption::new("eXit", "X"), + ] + } + + fn wants_text_input(&self) -> bool { + true + } + + fn on_text_input(&mut self, text: &str) -> Option { + match text.trim().trim_start_matches('_').to_ascii_uppercase().as_str() { + "" | "N" | "NEXT" => Some(self.show(true)), + "P" | "PREVIOUS" => Some(self.show(false)), + "X" | "EXIT" => Some(CmdResult::Cancel), + _ => None, + } + } + + fn on_point(&mut self, _pt: DVec3) -> CmdResult { + CmdResult::NeedPoint + } + + fn on_enter(&mut self) -> CmdResult { + self.show(true) + } + + fn on_escape(&mut self) -> CmdResult { + CmdResult::Cancel + } + + fn on_preview_wires(&mut self, _pt: DVec3) -> Vec { + // ponytail: the overlay is cloned per frame; cache it in the scene if + // comparisons of very large drawings stutter. + self.comparison.overlay.clone() + } +} + +inventory::submit!(crate::command::CommandRegistration { names: &["COMPARE"] }); diff --git a/src/modules/manage/mod.rs b/src/modules/manage/mod.rs index 48fda655e..7c7f3a7f0 100644 --- a/src/modules/manage/mod.rs +++ b/src/modules/manage/mod.rs @@ -2,6 +2,7 @@ mod about; mod audit; +pub mod compare; mod cui_export; mod cui_import; mod find_nonpurgeable; diff --git a/src/scene/compare.rs b/src/scene/compare.rs new file mode 100644 index 000000000..dd18ef101 --- /dev/null +++ b/src/scene/compare.rs @@ -0,0 +1,151 @@ +//! COMPARE: what differs between this drawing and another one. Objects are +//! matched by what they draw — type, layer and their tessellated geometry — +//! so a copy that kept its geometry matches whatever its handle, and an +//! object that moved or changed shows as removed in one place and added in +//! the other. + +use codec::{CadDocument, Handle}; +use glam::DVec3; +use rustc_hash::FxHashMap; + +use crate::scene::{Scene, WireModel}; + +/// The result of a comparison, ready to draw over the current drawing. +pub struct Comparison { + /// Objects only this drawing has. + pub added: usize, + /// Objects only the other drawing has. + pub removed: usize, + /// Their outlines: added in green, removed in red. + pub overlay: Vec, + /// Each difference's extent, in reading order, to step through. + pub spots: Vec<(DVec3, DVec3)>, +} + +const ADDED: [f32; 4] = [0.25, 0.85, 0.35, 1.0]; +const REMOVED: [f32; 4] = [0.95, 0.3, 0.25, 1.0]; + +fn wires_by_entity(scene: &Scene) -> FxHashMap> { + let mut map: FxHashMap> = FxHashMap::default(); + for wire in scene.entity_wires().iter() { + if let Some(handle) = Scene::handle_from_wire_name(&wire.name) { + map.entry(handle).or_default().push(wire.clone()); + } + } + map +} + +fn point(wire: &WireModel, index: usize) -> DVec3 { + let high = wire.points[index]; + let low = wire.points_low.get(index).copied().unwrap_or([0.0; 3]); + DVec3::new( + f64::from(high[0]) + f64::from(low[0]), + f64::from(high[1]) + f64::from(low[1]), + f64::from(high[2]) + f64::from(low[2]), + ) +} + +/// What an object draws, as a comparable key: its type, its layer and its +/// outline points rounded to 1e-4 drawing units. +fn signature(document: &CadDocument, handle: Handle, wires: &[WireModel]) -> String { + let mut key = match document.get_entity(handle) { + Some(entity) => format!( + "{}|{}", + crate::entities::names::ui_name(entity), + entity.common().layer + ), + None => String::new(), + }; + for wire in wires { + key.push('#'); + for index in 0..wire.points.len() { + let p = point(wire, index); + if !p.is_finite() { + key.push('/'); + continue; + } + let q = (p * 1e4).round(); + key.push_str(&format!("{},{},{};", q.x, q.y, q.z)); + } + } + key +} + +fn extent(wires: &[WireModel]) -> Option<(DVec3, DVec3)> { + let mut bounds: Option<(DVec3, DVec3)> = None; + for wire in wires { + for index in 0..wire.points.len() { + let p = point(wire, index); + if !p.is_finite() { + continue; + } + bounds = Some(match bounds { + None => (p, p), + Some((min, max)) => (min.min(p), max.max(p)), + }); + } + } + bounds +} + +/// Compare `current` with the drawing `other`, in the space `current` +/// shows. +pub fn compare(current: &Scene, other: CadDocument) -> Comparison { + let mut other_scene = Scene::new(); + other_scene.document = other; + other_scene.bump_geometry(); + let here = wires_by_entity(current); + let there = wires_by_entity(&other_scene); + + let mut unmatched: FxHashMap> = FxHashMap::default(); + for (handle, wires) in &there { + unmatched + .entry(signature(&other_scene.document, *handle, wires)) + .or_default() + .push(*handle); + } + let mut overlay = Vec::new(); + let mut spots = Vec::new(); + let mut added = 0; + let mut mark = |wires: &[WireModel], color: [f32; 4]| { + if let Some(bounds) = extent(wires) { + spots.push(bounds); + } + overlay.extend(wires.iter().cloned().map(|mut wire| { + wire.color = color; + wire.selected = false; + wire.line_weight_px = wire.line_weight_px.max(2.0); + wire + })); + }; + for (handle, wires) in &here { + let key = signature(¤t.document, *handle, wires); + match unmatched.get_mut(&key).and_then(Vec::pop) { + Some(_) => {} + None => { + added += 1; + mark(wires, ADDED); + } + } + } + let mut removed = 0; + for handle in unmatched.into_values().flatten() { + if let Some(wires) = there.get(&handle) { + removed += 1; + mark(wires, REMOVED); + } + } + // Reading order: top to bottom, then left to right. + spots.sort_by(|a, b| { + b.1.y + .partial_cmp(&a.1.y) + .unwrap_or(std::cmp::Ordering::Equal) + .then(a.0.x.partial_cmp(&b.0.x).unwrap_or(std::cmp::Ordering::Equal)) + }); + Comparison { + added, + removed, + overlay, + spots, + } +} diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 4546c5fe7..8f19519e2 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -7,6 +7,7 @@ // cache — block-definition and property caches pub mod annotative; pub mod assoc_array; +pub mod compare; pub mod block_service; pub mod cache; pub mod convert;