Merge pull request #1462 from ramox81:codex/xref-attach-parity

This commit is contained in:
Hakan Seven committed 2026-09-24 15:09:56 +03:00
commit 1988424638
47 files changed
+2716 -362

No files matched your search

+1
View File
@@ -43,6 +43,7 @@ SS, *STRETCH
S, *STRETCH
O, *OFFSET
TR, *TRIM
ER, *EXTERNALREFERENCES
EX, *EXTEND
F, *FILLET
CHA, *CHAMFER
+22
View File
@@ -3510,6 +3510,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE حدد نقطة الإدراج:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
سحب
النقاط
+22
View File
@@ -3497,6 +3497,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Посочи точка на вмъкване:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Облаци
от точки
+22
View File
@@ -3495,6 +3495,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Zadejte bod vložení:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Bod
Mraky
+22
View File
@@ -3498,6 +3498,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Einführstelle angeben:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Punkt-
wolken
+22
View File
@@ -3498,6 +3498,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Ορίστε σημείο εισαγωγής:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.revolve-creation-mode-solid-surface = REVOLVE Τρόπος δημιουργίας [Solid/Surface]
.revolve-enter-angle-expression = REVOLVE Δώστε έκφραση γωνίας:
.revolve-select-line-ray-or-construction-line-for-axis = REVOLVE Επιλέξτε γραμμή, ημιευθεία ή βοηθητική ευθεία ως άξονα:
+22
View File
@@ -3499,6 +3499,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Specify insertion point:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Point
Clouds
+22
View File
@@ -3498,6 +3498,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Especifique el punto de inserción:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Nubes de
puntos
+22
View File
@@ -3497,6 +3497,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Määritä lisäyskohta:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Point
Pilviä
+22
View File
@@ -3498,6 +3498,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Spécifier le point d'insertion:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Nuages de
points
+22
View File
@@ -3486,6 +3486,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE निर्दिष्ट सम्मिलन बिंदु:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
बिंदु
बादल
+22
View File
@@ -3497,6 +3497,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Beszúrási pont megadása:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Pont
Felhők
+22
View File
@@ -3497,6 +3497,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Specificare il punto di inserimento:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Punto
Nuvole
+22
View File
@@ -3488,6 +3488,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE 挿入点を指定:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds = 点群
.attsync-block-name-to-sync = ATTSYNC 同期するブロック名:
.attsync-no-block-named-arg = ATTSYNC: "__ocs_fmt_0__" というブロックはありません。
+22
View File
@@ -3497,6 +3497,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE 삽입점 지정:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
포인트
구름
+22
View File
@@ -3496,6 +3496,28 @@ annotate =
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Selecteer een boog, cirkel of polylijnboog:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Punten-
wolken
+22
View File
@@ -3497,6 +3497,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Określ punkt wstawienia:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Punkt
Chmury
+22
View File
@@ -3498,6 +3498,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Especifique o ponto de inserção:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Nuvens de
pontos
+22
View File
@@ -3499,6 +3499,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Укажите точку вставки:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds =
Облака
точек
+25 -3
View File
@@ -2004,7 +2004,7 @@ common =
.fit-data-does-not-define-a-valid-control-curve = Fit data does not define a valid control curve.
.fit-points-do-not-define-a-valid-spline = Fit points do not define a valid spline.
.formula = Formula
.full-path = Full path
.full-path = Tam yol
.fully-constrained-no-remaining-degrees-of-freedom = Fully constrained — no remaining degrees of freedom
.grid-on-f7 = Grid On (F7)
.grid-x-spacing = Grid X spacing:
@@ -2059,7 +2059,7 @@ common =
.named-parameters = Named Parameters
.named-range = Named range
.nearest-to-face = Nearest to face
.no-path = No path
.no-path = Yol yok
.no-preview-available = No preview available.
.no-surfaces-selected = No surfaces selected.
.objectscale-the-selected-objects-already-have-the-current-scale-or-do-not-support-annotation-scales = OBJECTSCALE: the selected objects already have the current scale, or do not support annotation scales.
@@ -2099,7 +2099,7 @@ common =
.recent-colors = Recent Colors
.reference-changes-are-not-available-on-web-the-reference-list-is-read-only = Reference changes are not available on web — the reference list is read-only.
.refine-data = Refine data
.relative-path = Relative path
.relative-path = Göreli yol
.remaining-degrees-of-freedom-in-the-current-parametric-constraint-scope = Remaining degrees of freedom in the current parametric constraint scope
.requires-an-integer-between-0-and-99 = Requires an integer between 0 and 99.
.requires-numeric-distance-or-two-points = Requires numeric distance or two points.
@@ -3472,6 +3472,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE Ekleme noktasını belirtin:
insert =
.xref-attach-dialog-title = Harici Referans Ekle
.xref-attach-browse = Gözat...
.xref-attach-reference-type = Referans Türü
.xref-attach-specify-on-screen = Ekranda belirt
.xref-attach-uniform-scale = Eşit ölçek
.xref-attach-insertion-point = Ekleme noktası
.xref-attach-angle = Açı:
.xref-attach-block-unit = Blok birimi
.xref-attach-unit = Birim:
.xref-attach-factor = Çarpan:
.xref-attach-found-in = Bulunduğu yer:
.xref-attach-saved-path = Kayıtlı yol:
.xref-attach-show-details = Ayrıntıları Göster
.xref-attach-hide-details = Ayrıntıları Gizle
.xref-attach-select-file = Referans Dosyası Seç
.xref-attach-all-files = Tüm Referans Dosyaları
.xref-attach-help = Diğer çizimler, raster görüntüler ve altlıklar gibi harici dosyalara referans ekler.
.xref-attach-invalid-input = Geçersiz giriş.
.xref-insertion-point-x = Ekleme noktası X
.xref-insertion-point-y = Ekleme noktası Y
.xref-insertion-point-z = Ekleme noktası Z
.xref-layer-property-overrides = Katman özelliği geçersiz kılmaları
.point-clouds =
Nokta
Bulutları
+22
View File
@@ -3485,6 +3485,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE 指定插入点:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds = 点云
.attsync-block-name-to-sync = ATTSYNC 要同步的块名 :
.attsync-no-block-named-arg = ATTSYNC:没有块名“__ocs_fmt_0__”。
+22
View File
@@ -3497,6 +3497,28 @@ annotate =
.tolerance-specify-insertion-point = TOLERANCE 指定插入點:
insert =
.xref-attach-dialog-title = Attach External Reference
.xref-attach-browse = Browse...
.xref-attach-reference-type = Reference Type
.xref-attach-specify-on-screen = Specify On-screen
.xref-attach-uniform-scale = Uniform Scale
.xref-attach-insertion-point = Insertion point
.xref-attach-angle = Angle:
.xref-attach-block-unit = Block Unit
.xref-attach-unit = Unit:
.xref-attach-factor = Factor:
.xref-attach-found-in = Found in:
.xref-attach-saved-path = Saved path:
.xref-attach-show-details = Show Details
.xref-attach-hide-details = Hide Details
.xref-attach-select-file = Select Reference File
.xref-attach-all-files = All Reference Files
.xref-attach-help = Inserts references to external files such as other drawings, raster images, and underlays.
.xref-attach-invalid-input = Invalid input.
.xref-insertion-point-x = Insertion point X
.xref-insertion-point-y = Insertion point Y
.xref-insertion-point-z = Insertion point Z
.xref-layer-property-overrides = Layer property overrides
.point-clouds = 點雲
.attsync-block-name-to-sync = ATTSYNC 要同步的圖塊名 :
.attsync-no-block-named-arg = ATTSYNC:沒有圖塊名“__ocs_fmt_0__”。
+3 -2
View File
@@ -74,8 +74,8 @@ const WEB_ALIAS_KEY: &str = "opencadstudio.aliases";
/// defaults add aliases or change a default target, so existing profiles can be
/// migrated forward (see `introduced_at` / `migrate_aliases`). Version 1 shipped
/// the pre-REDRAW table; version 2 introduced the REDRAW-family aliases; version 3
/// introduced HB (HATCHTOBACK).
const DEFAULT_ALIASES_VERSION: u32 = 3;
/// introduced HB (HATCHTOBACK); version 4 introduced ER (EXTERNALREFERENCES).
const DEFAULT_ALIASES_VERSION: u32 = 4;
#[cfg(target_arch = "wasm32")]
const WEB_ALIAS_VERSION_KEY: &str = "opencadstudio.aliases.version";
@@ -93,6 +93,7 @@ fn introduced_at(version: u32) -> &'static [(&'static str, &'static str)] {
("REA", "REGENALL"),
],
3 => &[("HB", "HATCHTOBACK")],
4 => &[("ER", "EXTERNALREFERENCES")],
_ => &[],
}
}
+12 -55
View File
@@ -3127,60 +3127,17 @@ impl OpenCADStudio {
self.sync_dyn_fields();
self.refresh_area_preview(i);
}
CmdResult::CommitAndExit(mut entity) => {
// For XATTACH: ensure the xref block definition exists before
// committing the INSERT entity that references it.
// Extract path early to avoid borrow conflicts.
let xattach_path: Option<String> = {
let tab = &self.tabs[i];
if let Some(cmd) = tab.active_cmd.as_ref() {
if cmd.name() == "XATTACH" {
cmd.xattach_path()
} else {
None
}
} else {
None
}
};
if let Some(path) = xattach_path {
// XREF-Task6: the host drawing path lives in the tab
// (`current_path`), not in `Scene`, so the self-attach
// guard runs here via the pure `is_self_attach` helper.
let host_file = self.tabs[i].current_path.clone();
let host_base = host_file
.as_deref()
.and_then(|p| p.parent())
.map(|p| p.to_path_buf());
if let Some(host) = host_file.as_deref() {
if crate::modules::insert::xattach::is_self_attach(
host,
&path,
host_base.as_deref(),
) {
self.command_line.push_error(crate::t!("XATTACH: cannot attach the host drawing into itself.").as_ref());
self.tabs[i].scene.clear_preview_wire();
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.restore_pre_cmd_tangent();
return Task::none();
}
}
let prepared_name = crate::modules::insert::xattach::prepare_xref_block(
&mut self.tabs[i].scene,
&path,
host_base.as_deref(),
);
// `prepare_xref_block` may suffix a colliding stem. The
// command was created before that collision was known, so
// retarget its pending INSERT to the actual new block.
if let acadrust::EntityType::Insert(insert) = &mut entity {
insert.block_name = prepared_name;
}
// Resolving the xref merged its layer / linetype tables
// into the document — mirror them into the Layers panel
// and ribbon dropdowns now, not on the next reopen (#407).
self.refresh_layer_panel();
CmdResult::CommitAndExit(entity) => {
// XATTACH: the definition and its INSERT are created together
// in one undo step.
let xattach_request = self.tabs[i]
.active_cmd
.as_ref()
.filter(|cmd| cmd.name() == "XATTACH")
.and_then(|cmd| cmd.xattach_request());
if let Some(request) = xattach_request {
self.commit_xref_attach(i, request, entity);
return Task::none();
}
let insert_block_name = match &entity {
acadrust::EntityType::Insert(ins) => Some(ins.block_name.clone()),
@@ -9274,7 +9231,7 @@ impl OpenCADStudio {
out
}
fn restore_pre_cmd_tangent(&mut self) {
pub(super) fn restore_pre_cmd_tangent(&mut self) {
if let Some(was_on) = self.pre_cmd_tangent.take() {
if !was_on {
self.snapper.enabled.remove(&crate::snap::SnapType::Tangent);
+9 -8
View File
@@ -663,10 +663,6 @@ impl OpenCADStudio {
"PDFATTACH" => {
return Some(Task::done(Message::PdfAttachPick));
}
"XATTACH" => {
// Launch the file picker; XAttachPickResult will start the command.
return Some(Task::done(Message::XAttachPick));
}
cmd if cmd == "WBLOCK" || cmd == "WB" || cmd.starts_with("WBLOCK ") => {
let arg = cmd.splitn(2, ' ').nth(1).unwrap_or("").trim();
if arg.is_empty() {
@@ -1366,11 +1362,16 @@ impl OpenCADStudio {
if after.is_empty() {
return Some(Task::done(Message::XAttachPick));
}
let cmd = crate::modules::insert::xattach::XAttachCommand::with_path(
after.to_string(),
self.start_xref_attach(
i,
crate::modules::insert::xattach::XrefAttachRequest {
path: after.trim_matches('"').to_string(),
overlay: false,
path_type: crate::io::xref_model::Pathtype::Full,
},
crate::modules::insert::xattach::XrefPlacement::on_screen(),
"-XREF",
);
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
} else {
self.command_line.push_error(crate::tf!(
"XREF: unknown option '{}'. Options: ? Reload Unload Detach Path Pathtype Bind Overlay Attach",
+5
View File
@@ -5,6 +5,7 @@ use iced::Task;
use std::path::PathBuf;
mod blocks;
mod xref_attach;
mod dim;
pub(crate) mod display;
mod draw;
@@ -246,6 +247,9 @@ impl OpenCADStudio {
if let Some(t) = self.dispatch_layers(cmd, i) {
return Some(t);
}
if let Some(t) = self.dispatch_xref_attach(cmd, i) {
return Some(t);
}
if let Some(t) = self.dispatch_blocks(cmd, i) {
return Some(t);
}
@@ -652,6 +656,7 @@ inventory::submit!(crate::command::CommandRegistration {
"3DORBIT",
"3O",
"ABOUT",
"ATTACH",
"ATTDISP",
"ATTEXT",
"BACKGROUND",
+477
View File
@@ -0,0 +1,477 @@
//! Attaching drawing references: ATTACH / XATTACH (file picker, then the
//! Attach External Reference dialog), the -XREF command line, the `?` list,
//! and the commit that creates the definition and its INSERT as one undo step.
use super::*;
use crate::io::xref_model::Pathtype;
use crate::modules::insert::xattach::{
existing_reference, path_to_block_name, prepare_xref_definition, stored_path,
XAttachCommand, XrefAttachRequest, XrefPlacement,
};
use crate::modules::insert::xref_cmd::display_path;
use crate::ui::window::xref_attach::{XrefAttachMsg, XrefAttachState};
impl OpenCADStudio {
/// Commands owned here; `None` defers to the other families.
pub(super) fn dispatch_xref_attach(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
let upper = cmd.to_ascii_uppercase();
let (verb, rest) = match cmd.split_once(char::is_whitespace) {
Some((verb, rest)) => (verb.to_ascii_uppercase(), rest.trim()),
None => (upper.clone(), ""),
};
match (verb.as_str(), rest.is_empty()) {
("ATTACH", true) => Some(Task::done(Message::AttachPick)),
("XATTACH", true) => Some(Task::done(Message::XAttachPick)),
// A file named on the command line skips the picker.
// Handled at once, so the placement it starts belongs to this
// command line (automation included).
("ATTACH", false) => Some(self.update(Message::AttachPickResult(Ok(
std::path::PathBuf::from(rest.trim_matches('"')),
)))),
("XATTACH", false) => {
self.open_xref_attach_dialog(std::path::PathBuf::from(rest.trim_matches('"')));
Some(Task::none())
}
// XREF opens the External References palette; -XREF asks on the
// command line. Both keep their argument forms for scripts.
("XREF", true) | ("XREFSHOW", true) => {
if !self.show_external_references {
return self.dispatch_blocks("EXTERNALREFERENCES", i);
}
self.refresh_xref_manager();
Some(self.finish_dispatch(cmd))
}
("-XREF", true) => {
let saved: Vec<(String, String)> = self.tabs[i]
.scene
.document
.block_records
.iter()
.filter(|br| br.flags.is_xref || br.flags.is_xref_overlay)
.map(|br| (br.name.clone(), br.xref_path.clone()))
.collect();
let command = crate::modules::insert::xref_cmd::XrefCommand::new(saved);
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
Some(Task::none())
}
("XREFLIST", _) => {
self.list_xrefs(i, if rest.is_empty() { "*" } else { rest });
Some(self.finish_dispatch(cmd))
}
("XREFATTACHFILE", false) | ("XREFOVERLAYFILE", false) => {
let request = XrefAttachRequest {
path: rest.trim_matches('"').to_string(),
overlay: verb == "XREFOVERLAYFILE",
// The reference default (REFPATHTYPE 1): relative once the
// drawing is saved, the full path before that.
path_type: Pathtype::Relative,
};
self.start_xref_attach(i, request, XrefPlacement::on_screen(), "-XREF");
Some(Task::none())
}
_ => None,
}
}
/// `-XREF ?` — the reference table as the reference application prints it.
fn list_xrefs(&mut self, i: usize, pattern: &str) {
let rows: Vec<(String, &'static str, String)> = self.tabs[i]
.scene
.document
.block_records
.iter()
.filter(|br| br.flags.is_xref || br.flags.is_xref_overlay)
.filter(|br| {
pattern == "*" || crate::io::xref_model::wildcard_match(&br.name, pattern)
})
.map(|br| {
let kind = if br.flags.is_xref_overlay { "Overlay" } else { "Attach" };
(br.name.clone(), kind, display_path(&br.xref_path))
})
.collect();
let out = &mut self.command_line;
out.push_output("");
out.push_output(&format!(
"{:<34}{:<13}{}",
"Xref name",
"Xref Type",
"Path"
));
out.push_output(&format!("{:<34}{:<13}{}", "----------------------", "---------", "----------"));
out.push_output("");
for (name, kind, path) in &rows {
out.push_output(&format!("{:<34}{:<13}{}", format!("\"{name}\""), *kind, path));
}
out.push_output("");
out.push_output(&format!("Total Xref(s): {}", rows.len()));
}
/// Checks the file, reports it the way the reference does, then asks
/// for whatever is left on-screen (or commits straight away).
pub(in crate::app) fn start_xref_attach(
&mut self,
i: usize,
request: XrefAttachRequest,
placement: XrefPlacement,
label: &'static str,
) {
let name = path_to_block_name(&request.path);
let full = crate::modules::insert::xattach::resolve_xref_store_path(&request.path, None);
let host = self.tabs[i].current_path.clone();
if let Some(host) = host.as_deref() {
if crate::modules::insert::xattach::is_self_attach(host, &full, host.parent()) {
self.command_line.push_error(
"Error: Possible circular reference to current drawing. *Invalid*",
);
return;
}
}
let existing = existing_reference(&self.tabs[i].scene, &name);
if let Some(existing) = existing.as_ref() {
self.command_line
.push_output(&format!("Xref \"{}\" has already been defined.", existing));
self.command_line
.push_output("Using existing definition.");
} else {
let verb = if request.overlay {
format!("Overlay Xref \"{}\": {}", name, display_path(&full))
} else {
format!("Attach Xref \"{}\": {}", name, display_path(&full))
};
self.command_line.push_output(&verb);
if !std::path::Path::new(&full).is_file() {
let file = std::path::Path::new(&full.replace('\\', "/"))
.file_name()
.map(|f| f.to_string_lossy().into_owned())
.unwrap_or(full.clone());
self.command_line
.push_output(&format!("\"{}\" cannot be found.", file));
self.command_line.push_error("*Invalid*");
return;
}
self.command_line
.push_output(&format!("\"{}\" loaded.", name));
}
let command = XAttachCommand::new(request.clone(), placement, label);
if let Some(entity) = command.immediate() {
self.commit_xref_attach(i, request, entity);
return;
}
let (wires, source_units) = self.xref_preview(i, existing.as_deref(), &full);
let host_units = self.tabs[i].scene.document.header.insertion_units;
let unit = crate::app::properties::insert_unit_scale(host_units, source_units).unwrap_or(1.0);
let command = command.with_preview(wires, unit);
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
}
/// The reference's geometry for the placement ghost, in its own units:
/// an existing definition's content, or the file's model space.
fn xref_preview(
&self,
i: usize,
existing: Option<&str>,
path: &str,
) -> (Vec<crate::scene::model::wire_model::WireModel>, i16) {
// ponytail: a huge reference skips the ghost; cap by entity count.
const MAX_PREVIEW_ENTITIES: usize = 20_000;
let scene = &self.tabs[i].scene;
if let Some(name) = existing {
let units = scene
.document
.block_records
.get(name)
.map(|br| br.units)
.unwrap_or(0);
return (scene.block_preview_wires(name), units);
}
#[cfg(not(target_arch = "wasm32"))]
if let Ok(source) = crate::io::load_file(std::path::Path::new(path)) {
let owner = source.header.model_space_block_handle;
let entities: Vec<acadrust::EntityType> = source
.entities()
.filter(|e| e.common().owner_handle == owner)
.filter(|e| {
!matches!(
e,
acadrust::EntityType::Block(_) | acadrust::EntityType::BlockEnd(_)
)
})
.take(MAX_PREVIEW_ENTITIES + 1)
.cloned()
.collect();
if entities.len() <= MAX_PREVIEW_ENTITIES {
return (
scene.wires_for_entities(&entities),
source.header.insertion_units,
);
}
}
let _ = path;
(Vec::new(), 0)
}
/// Creates (or reuses) the definition and commits its INSERT — one undo
/// step for both, so undo leaves no definition behind.
pub(in crate::app) fn commit_xref_attach(
&mut self,
i: usize,
request: XrefAttachRequest,
mut entity: acadrust::EntityType,
) {
let pending = self.begin_undo(i, "XATTACH", 1, false);
let host = self.tabs[i].current_path.clone();
let name = prepare_xref_definition(&mut self.tabs[i].scene, &request, host.as_deref());
if host.is_none() && request.path_type == Pathtype::Relative {
self.tabs[i].xref_relative_on_save.insert(name.clone());
}
if let acadrust::EntityType::Insert(insert) = &mut entity {
insert.block_name = name;
}
// Resolving merged the reference's layers and linetypes — mirror
// them into the Layers panel and ribbon dropdowns now (#407).
self.refresh_layer_panel();
let _ = self.commit_entity_handle(entity);
self.tabs[i].dirty = true;
self.tabs[i].scene.clear_preview_wire();
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.restore_pre_cmd_tangent();
if let Some(pending) = pending {
self.commit_undo_delta(i, pending);
}
self.refresh_xref_manager();
}
/// Opens the Attach External Reference dialog for `path`.
pub(in crate::app) fn open_xref_attach_dialog(&mut self, path: std::path::PathBuf) {
let i = self.active_tab;
let existing: Vec<(String, String)> = self.tabs[i]
.scene
.document
.block_records
.iter()
.filter(|br| br.flags.is_xref || br.flags.is_xref_overlay)
.map(|br| (br.name.clone(), br.xref_path.clone()))
.collect();
let mut state = XrefAttachState::new(existing);
self.load_xref_attach_file(&mut state, &path.to_string_lossy());
self.xref_attach = Some(state);
self.active_modal = Some(crate::app::ModalKind::XrefAttach);
}
/// Fills the dialog from the chosen file: name, preview, block unit and
/// the paths shown under Details.
fn load_xref_attach_file(&self, state: &mut XrefAttachState, path: &str) {
let i = self.active_tab;
state.path = path.to_string();
state.name = path_to_block_name(path);
state.found_in = display_path(path);
#[cfg(not(target_arch = "wasm32"))]
{
state.preview = crate::io::thumbnail::read_handle(std::path::Path::new(path));
let host_units = self.tabs[i].scene.document.header.insertion_units;
let source_units = crate::io::load_file(std::path::Path::new(path))
.map(|doc| doc.header.insertion_units)
.unwrap_or(0);
state.unit_label = crate::t!(crate::app::properties::insunits_name(source_units))
.into_owned();
let factor =
crate::app::properties::insert_unit_scale(host_units, source_units).unwrap_or(1.0);
state.unit_factor = crate::app::properties::format_unit_factor(factor);
}
self.refresh_xref_attach_saved_path(state);
}
fn refresh_xref_attach_saved_path(&self, state: &mut XrefAttachState) {
let host = self.tabs[self.active_tab].current_path.clone();
let request = XrefAttachRequest {
path: state.path.clone(),
overlay: state.overlay,
path_type: state.path_type.0,
};
state.saved_path = display_path(&stored_path(&request, host.as_deref()));
}
/// Messages of the ATTACH picker and the dialog.
pub(in crate::app) fn update_xref_attach(&mut self, message: Message) -> Task<Message> {
match message {
Message::AttachPick => Task::perform(
async {
let handle = crate::sys::file_dialog()
.set_title(crate::t!("Select Reference File").as_ref())
.add_filter(
crate::t!("All Reference Files").as_ref(),
&[
"dwg", "dxf", "pdf", "png", "jpg", "jpeg", "bmp", "tif", "tiff",
],
)
.add_filter(crate::t!("Drawing").as_ref(), &["dwg", "dxf"])
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf"])
.add_filter(
crate::t!("Images").as_ref(),
&["png", "jpg", "jpeg", "bmp", "tif", "tiff"],
)
.pick_file()
.await;
match handle {
Some(h) => Ok(crate::sys::handle_path(&h)),
None => Err("Cancelled".to_string()),
}
},
Message::AttachPickResult,
),
Message::AttachPickResult(Ok(path)) => {
let ext = path
.extension()
.map(|e| e.to_string_lossy().to_ascii_lowercase())
.unwrap_or_default();
match ext.as_str() {
"pdf" => match std::fs::read(&path) {
Ok(bytes) => self.update(Message::PdfAttachPickResult(Ok((
path,
std::sync::Arc::new(bytes),
)))),
Err(e) => self.update(Message::PdfAttachPickResult(Err(e.to_string()))),
},
"png" | "jpg" | "jpeg" | "bmp" | "tif" | "tiff" => {
let result = image::image_dimensions(&path)
.map(|(w, h)| (path.clone(), w, h))
.map_err(|e| e.to_string());
self.update(Message::ImagePickResult(result))
}
_ => {
self.open_xref_attach_dialog(path);
Task::none()
}
}
}
Message::AttachPickResult(Err(e)) => {
if e != "Cancelled" {
self.command_line
.push_error(format!("ATTACH: {e}").as_ref());
}
Task::none()
}
Message::XrefAttachBrowseResult(Ok(path)) => {
if let Some(mut state) = self.xref_attach.take() {
self.load_xref_attach_file(&mut state, &path.to_string_lossy());
self.xref_attach = Some(state);
}
Task::none()
}
Message::XrefAttachBrowseResult(Err(_)) => Task::none(),
Message::XrefAttach(edit) => self.edit_xref_attach(edit),
_ => Task::none(),
}
}
fn edit_xref_attach(&mut self, edit: XrefAttachMsg) -> Task<Message> {
let Some(mut state) = self.xref_attach.take() else {
return Task::none();
};
let mut task = Task::none();
match edit {
XrefAttachMsg::Name(name) => {
if let Some((_, saved)) = state
.existing
.iter()
.find(|(n, _)| n.eq_ignore_ascii_case(&name))
.cloned()
{
let host = self.tabs[self.active_tab].current_path.clone();
let base = host.as_deref().and_then(|h| h.parent());
let full = crate::modules::insert::xattach::resolve_xref_store_path(&saved, base);
self.load_xref_attach_file(&mut state, &full);
}
}
XrefAttachMsg::Browse => {
task = Task::perform(
async {
let handle = crate::sys::file_dialog()
.set_title(crate::t!("Select Reference File").as_ref())
.add_filter(crate::t!("Drawing").as_ref(), &["dwg", "dxf"])
.pick_file()
.await;
match handle {
Some(h) => Ok(crate::sys::handle_path(&h)),
None => Err("Cancelled".to_string()),
}
},
Message::XrefAttachBrowseResult,
);
}
XrefAttachMsg::Overlay(overlay) => state.overlay = overlay,
XrefAttachMsg::ScaleOnScreen(on) => state.scale_on_screen = on,
XrefAttachMsg::Scale(axis, value) => {
if axis == 0 && state.uniform {
state.scale = [value.clone(), value.clone(), value];
} else if let Some(slot) = state.scale.get_mut(axis) {
*slot = value;
}
}
XrefAttachMsg::Uniform(on) => {
state.uniform = on;
if on {
let x = state.scale[0].clone();
state.scale = [x.clone(), x.clone(), x];
}
}
XrefAttachMsg::InsertOnScreen(on) => state.insert_on_screen = on,
XrefAttachMsg::Insert(axis, value) => {
if let Some(slot) = state.insert.get_mut(axis) {
*slot = value;
}
}
XrefAttachMsg::PathType(choice) => {
state.path_type = choice;
self.refresh_xref_attach_saved_path(&mut state);
}
XrefAttachMsg::RotationOnScreen(on) => state.rotation_on_screen = on,
XrefAttachMsg::Rotation(value) => state.rotation = value,
XrefAttachMsg::Details(on) => state.details = on,
XrefAttachMsg::Help => {
self.command_line.push_info(
crate::t!("Inserts references to external files such as other drawings, raster images, and underlays.")
.as_ref(),
);
}
XrefAttachMsg::Apply => {
let placement = XrefPlacement {
insert: (!state.insert_on_screen)
.then(|| state.insert_point())
.flatten(),
scale: (!state.scale_on_screen).then(|| state.scale_values()).flatten(),
rotation: (!state.rotation_on_screen)
.then(|| crate::entities::common::parse_typed_angle(&state.rotation))
.flatten()
.map(f64::to_degrees),
};
let invalid = (!state.insert_on_screen && placement.insert.is_none())
|| (!state.scale_on_screen
&& placement
.scale
.is_none_or(|s| s.iter().any(|v| *v == 0.0)))
|| (!state.rotation_on_screen && placement.rotation.is_none());
if invalid || state.path.is_empty() {
self.command_line
.push_error(crate::t!("Invalid input.").as_ref());
self.xref_attach = Some(state);
return Task::none();
}
let request = XrefAttachRequest {
path: state.path.clone(),
overlay: state.overlay,
path_type: state.path_type.0,
};
self.active_modal = None;
let i = self.active_tab;
self.start_xref_attach(i, request, placement, "XATTACH");
return Task::none();
}
}
self.xref_attach = Some(state);
task
}
}
+9
View File
@@ -47,6 +47,7 @@ fn color_value(color: acadrust::types::Color) -> Value {
}
pub(super) const NAMES: &[&str] = &[
"close_modal",
"dialog_ok",
"close_document",
"toggle_properties",
"toggle_layers",
@@ -663,6 +664,14 @@ impl OpenCADStudio {
let name = string(req, "name")?;
let msg = match name {
"close_modal" => Message::CloseModal,
// The open dialog's OK button.
"dialog_ok" => match self.active_modal {
Some(crate::app::ModalKind::XrefAttach) => Message::XrefAttach(
crate::ui::window::xref_attach::XrefAttachMsg::Apply,
),
Some(crate::app::ModalKind::BlockDefinition) => Message::BlockDefApply,
_ => return Err(failure("no_dialog", "No dialog with an OK button is open")),
},
"close_document" => Message::TabClose(self.active_tab),
"toggle_properties" => Message::ToggleProperties,
"toggle_layers" => Message::ToggleLayers,
+4
View File
@@ -231,6 +231,9 @@ pub(super) struct DocumentTab {
/// Session set of unloaded reference keys (Task 8b). Owns the set that
/// `collect_entries` takes as `unloaded`, so CLI and palette agree.
pub(super) xref_unloaded: crate::io::xref_model::UnloadSet,
/// References attached with a relative path while the drawing had no
/// file yet: stored full until the first save, then made relative.
pub(super) xref_relative_on_save: rustc_hash::FxHashSet<String>,
/// Load-time mtimes per reference key (Task 8b). Written on every
/// palette refresh; `Stale` is detectable from the second refresh on.
pub(super) xref_stat_cache: crate::io::xref_model::RefStatCache,
@@ -650,6 +653,7 @@ impl DocumentTab {
active_mleader_style: "Standard".to_string(),
last_synced_camera_gen: 0,
xref_unloaded: crate::io::xref_model::UnloadSet::default(),
xref_relative_on_save: rustc_hash::FxHashSet::default(),
xref_stat_cache: crate::io::xref_model::RefStatCache::default(),
xref_missing: 0,
is_start: false,
+12
View File
@@ -493,6 +493,8 @@ pub(super) struct OpenCADStudio {
drawing_units: Option<crate::ui::window::drawing_units::State>,
/// Working copy of the Block Definition dialog; `None` while it is closed.
block_definition: Option<crate::ui::window::block_definition::BlockDefinitionState>,
/// Working copy of the Attach External Reference dialog; None while closed.
xref_attach: Option<crate::ui::window::xref_attach::XrefAttachState>,
/// Working copy of the structured feature-control-frame editor.
geometric_tolerance: Option<crate::ui::window::geometric_tolerance::State>,
/// PICKDRAG (#226): false (default) = press-drag lassoes; true =
@@ -1880,6 +1882,7 @@ pub enum ModalKind {
LayerTranslator,
DrawingUnits,
BlockDefinition,
XrefAttach,
GeometricTolerance,
DraftingSettings,
AutoConstrainSettings,
@@ -3788,6 +3791,14 @@ pub enum Message {
XAttachPick,
/// Result of the XATTACH file picker.
XAttachPickResult(Result<std::path::PathBuf, String>),
/// ATTACH: pick a drawing, image or PDF to reference.
AttachPick,
/// Result of the ATTACH file picker.
AttachPickResult(Result<std::path::PathBuf, String>),
/// An edit in the Attach External Reference dialog.
XrefAttach(crate::ui::window::xref_attach::XrefAttachMsg),
/// Result of the dialog's Browse picker.
XrefAttachBrowseResult(Result<std::path::PathBuf, String>),
// ── WBLOCK ────────────────────────────────────────────────────────────
/// Trigger the WBLOCK save dialog for `block_name` (or `*` = selection).
WblockSave(String),
@@ -3950,6 +3961,7 @@ impl OpenCADStudio {
layer_translator: None,
drawing_units: None,
block_definition: None,
xref_attach: None,
geometric_tolerance: None,
pick_drag_rect: false,
perf_hud: false,
+85 -3
View File
@@ -1478,6 +1478,25 @@ impl OpenCADStudio {
},
);
}
if let Some(br) = doc
.block_records
.get(&ins.block_name)
.filter(|br| br.flags.is_xref || br.flags.is_xref_overlay)
{
let base_dir = self.tabs[i]
.current_path
.as_deref()
.and_then(|p| p.parent())
.map(|p| p.to_path_buf())
.unwrap_or_else(|| std::path::PathBuf::from("."));
let overrides = crate::io::xref::layer_overrides(
doc,
&br.name,
&br.xref_path,
&base_dir,
);
xref_rows(&mut sections, ins, &br.xref_path, factor, overrides);
}
}
// Underlay: name + path from the referenced definition.
acadrust::EntityType::Underlay(ul) => {
@@ -3702,6 +3721,69 @@ fn merge_prop_value(
/// Set the first property row matching `field` (across all sections) to a
/// read-only `value`. No-op when the field is absent. Used to fill the
/// doc-dependent placeholder rows the entity builders leave empty.
/// An external reference's rows as the reference application lists them:
/// Insertion point X/Y/Z, Scale X/Y/Z without the unit conversion the INSERT
/// carries (no uniform-scale or Annotative rows), Saved Path after the name
/// and Layer property overrides last.
fn xref_rows(
sections: &mut [crate::scene::model::object::PropSection],
ins: &acadrust::entities::Insert,
saved_path: &str,
factor: f64,
overrides: bool,
) {
use crate::entities::common::{edit_prop, ro_prop};
let factor = if factor.abs() > 0.0 { factor } else { 1.0 };
for section in sections.iter_mut() {
for prop in section.props.iter_mut() {
let label = match prop.field {
"ins_x" => "Insertion point X",
"ins_y" => "Insertion point Y",
"ins_z" => "Insertion point Z",
_ => continue,
};
prop.label = t!(label).into_owned();
}
let scale_at = section.props.iter().position(|p| {
matches!(p.field, "x_scale" | "y_scale" | "z_scale" | "u_scale" | "ins_uniform")
});
section.props.retain(|p| {
!matches!(
p.field,
"x_scale" | "y_scale" | "z_scale" | "u_scale" | "ins_uniform" | "annotative"
)
});
if let Some(at) = scale_at {
let at = at.min(section.props.len());
section.props.splice(
at..at,
[
edit_prop(t!("Scale X").as_ref(), "xref_x_scale", ins.x_scale() / factor),
edit_prop(t!("Scale Y").as_ref(), "xref_y_scale", ins.y_scale() / factor),
edit_prop(t!("Scale Z").as_ref(), "xref_z_scale", ins.z_scale() / factor),
],
);
}
if let Some(at) = section.props.iter().position(|p| p.field == "block") {
section.props.insert(
at + 1,
ro_prop(
t!("Saved Path").as_ref(),
"xref_saved_path",
crate::modules::insert::xref_cmd::display_path(saved_path),
),
);
}
if section.props.iter().any(|p| p.field == "unit_factor") {
section.props.push(ro_prop(
t!("Layer property overrides").as_ref(),
"xref_layer_overrides",
if overrides { t!("Yes") } else { t!("No") }.into_owned(),
));
}
}
}
fn set_row(sections: &mut [crate::scene::model::object::PropSection], field: &str, value: String) {
for section in sections.iter_mut() {
if let Some(row) = section.props.iter_mut().find(|p| p.field == field) {
@@ -4092,7 +4174,7 @@ fn dim_lineweight_label(dimlwd: i16) -> String {
}
/// Human-readable INSUNITS name (DXF group 70 unit codes).
fn insunits_name(code: i16) -> &'static str {
pub(in crate::app) fn insunits_name(code: i16) -> &'static str {
match code {
1 => "Inches",
2 => "Feet",
@@ -4124,7 +4206,7 @@ fn insunits_name(code: i16) -> &'static str {
}
/// Unit-conversion scale for a new INSERT.
fn insert_unit_scale(host_units: i16, src_units: i16) -> Option<f64> {
pub(in crate::app) fn insert_unit_scale(host_units: i16, src_units: i16) -> Option<f64> {
let host_mm = insunits_to_mm(host_units)?;
let src_mm = insunits_to_mm(src_units)?;
let ratio = src_mm / host_mm;
@@ -4134,7 +4216,7 @@ fn insert_unit_scale(host_units: i16, src_units: i16) -> Option<f64> {
Some(ratio)
}
fn format_unit_factor(factor: f64) -> String {
pub(in crate::app) fn format_unit_factor(factor: f64) -> String {
let magnitude = factor.abs();
if magnitude > 0.0 && !(1.0e-4..1.0e7).contains(&magnitude) {
format!("{factor:.4e}")
+40
View File
@@ -3515,6 +3515,46 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
}
}
}
} else if let Some(axis) = match field {
"xref_x_scale" => Some(0),
"xref_y_scale" => Some(1),
"xref_z_scale" => Some(2),
_ => None,
} {
// An xref's Scale rows show the scale without the
// unit conversion its INSERT carries; write it back
// with that conversion.
let value = val
.trim()
.parse::<f64>()
.ok()
.filter(|v| v.is_finite() && *v != 0.0);
if let Some(value) = value {
let host = self.tabs[i].scene.document.header.insertion_units;
for &handle in &handles {
let factor = match self.tabs[i].scene.document.get_entity(handle) {
Some(acadrust::EntityType::Insert(ins)) => self.tabs[i]
.scene
.document
.block_records
.get(&ins.block_name)
.and_then(|br| {
crate::app::properties::insert_unit_scale(host, br.units)
})
.unwrap_or(1.0),
_ => continue,
};
if let Some(acadrust::EntityType::Insert(ins)) =
self.tabs[i].scene.document.get_entity_mut(handle)
{
match axis {
0 => ins.set_x_scale(value * factor),
1 => ins.set_y_scale(value * factor),
_ => ins.set_z_scale(value * factor),
}
}
}
}
} else if field == "block" {
// Name row on a block reference: an existing name
// re-points the selected inserts; a new one renames
+19 -1
View File
@@ -2149,7 +2149,8 @@ impl OpenCADStudio {
}
let mut recent_task = Task::none();
let saved = match crate::io::save_to_bytes(&self.tabs[i].scene.document, &ext, version) {
let document = self.tabs[i].scene.document_for_save();
let saved = match crate::io::save_to_bytes(&document, &ext, version) {
Ok(bytes) => {
crate::sys::download_bytes(&filename, &bytes);
let cache_name = std::path::Path::new(&filename)
@@ -2347,6 +2348,15 @@ impl OpenCADStudio {
});
let clone_started = iced::time::Instant::now();
let mut snapshot = self.tabs[i].scene.document_for_save();
// First save: references attached with a relative path type while
// the drawing had no file become relative to it.
if self.tabs[i].current_path.is_none() && purpose != crate::app::SavePurpose::Autosave {
crate::io::xref::make_relative(
&mut snapshot,
&self.tabs[i].xref_relative_on_save,
&path,
);
}
// Save-As across folders: rebase relative reference paths onto the
// new base dir inside the snapshot only (live strings are untouched).
if purpose == crate::app::SavePurpose::SaveAs {
@@ -2649,6 +2659,14 @@ impl OpenCADStudio {
}
if outcome.set_current_path {
let old_path = self.tabs[i].current_path.clone();
if old_path.is_none() {
let pending = std::mem::take(&mut self.tabs[i].xref_relative_on_save);
crate::io::xref::make_relative(
&mut self.tabs[i].scene.document,
&pending,
&outcome.path,
);
}
self.tabs[i].current_path = Some(outcome.path.clone());
self.tabs[i].scene.document.version = outcome.version;
if outcome.purpose == crate::app::SavePurpose::SaveAs {
+6 -7
View File
@@ -208,6 +208,7 @@ impl OpenCADStudio {
}
Some(GeometricTolerance) => self.geometric_tolerance = None,
Some(BlockDefinition) => self.block_definition = None,
Some(XrefAttach) => self.xref_attach = None,
Some(Hyperlink) => {
self.hyperlink_editor_handles.clear();
self.hyperlink_editor_url.clear();
@@ -1354,15 +1355,13 @@ impl OpenCADStudio {
),
Message::XAttachPickResult(Ok(path)) => {
use crate::command::CadCommand;
use crate::modules::insert::xattach::XAttachCommand;
let path_str = path.to_string_lossy().into_owned();
let cmd = XAttachCommand::with_path(path_str);
let i = self.active_tab;
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
self.open_xref_attach_dialog(path);
Task::none()
}
message @ (Message::AttachPick
| Message::AttachPickResult(_)
| Message::XrefAttach(_)
| Message::XrefAttachBrowseResult(_)) => self.update_xref_attach(message),
Message::XAttachPickResult(Err(e)) => {
if e != "Cancelled" {
+8
View File
@@ -24,6 +24,7 @@ impl OpenCADStudio {
Some(K::LayerTranslator) => crate::t!("Layer Translator").into_owned(),
Some(K::DrawingUnits) => crate::t!("Drawing Units").into_owned(),
Some(K::BlockDefinition) => crate::t!("Block Definition").into_owned(),
Some(K::XrefAttach) => crate::t!("Attach External Reference").into_owned(),
Some(K::GeometricTolerance) => crate::t!("Geometric Tolerance").into_owned(),
Some(K::DraftingSettings) => crate::t!("Drafting Settings").into_owned(),
Some(K::AutoConstrainSettings) => crate::t!("Constraint Settings").into_owned(),
@@ -506,6 +507,13 @@ impl OpenCADStudio {
crate::ui::window::block_definition::view_window(state, flow)
})
}
super::super::ModalKind::XrefAttach => {
let state = self.xref_attach.as_ref()?;
let height = if state.details { 520 } else { 450 };
sized_flow(ex, 740, height, |flow| {
crate::ui::window::xref_attach::view_window(state, flow)
})
}
super::super::ModalKind::GeometricTolerance => {
let state = self.geometric_tolerance.as_ref()?;
sized_flow(ex, 670, 530, |flow| {
+3 -3
View File
@@ -2838,9 +2838,9 @@ pub trait CadCommand: Send {
false
}
/// If this command is XATTACH, returns the file path to attach.
/// Default: None.
fn xattach_path(&self) -> Option<String> {
/// If this command is XATTACH, what it attaches and how the reference is
/// stored. Default: None.
fn xattach_request(&self) -> Option<crate::modules::insert::xattach::XrefAttachRequest> {
None
}
+260 -26
View File
@@ -305,6 +305,82 @@ fn resolve_xrefs_with_filter(
(result, dropped)
}
/// Layer properties an override can change, as compared for the
/// "Layer property overrides" row.
#[derive(Clone, PartialEq)]
struct LayerLook {
color: acadrust::types::Color,
line_type: String,
line_weight: acadrust::types::LineWeight,
off: bool,
frozen: bool,
locked: bool,
plottable: bool,
}
impl LayerLook {
fn of(layer: &acadrust::tables::Layer) -> Self {
Self {
color: layer.color.clone(),
line_type: layer.line_type.to_uppercase(),
line_weight: layer.line_weight,
off: layer.flags.off,
frozen: layer.flags.frozen,
locked: layer.flags.locked,
plottable: layer.is_plottable,
}
}
}
/// The source file's layers by upper-case name, read once per file version.
fn source_layers(path: &Path) -> Option<HashMap<String, LayerLook>> {
type Cache = std::sync::Mutex<HashMap<PathBuf, (std::time::SystemTime, HashMap<String, LayerLook>)>>;
static CACHE: std::sync::OnceLock<Cache> = std::sync::OnceLock::new();
let modified = std::fs::metadata(path).and_then(|m| m.modified()).ok()?;
let cache = CACHE.get_or_init(Default::default);
if let Some((stamp, layers)) = cache.lock().ok()?.get(path) {
if *stamp == modified {
return Some(layers.clone());
}
}
let doc = super::load_file(path).ok()?;
let layers: HashMap<String, LayerLook> = doc
.layers
.iter()
.map(|layer| (layer.name.to_uppercase(), LayerLook::of(layer)))
.collect();
cache
.lock()
.ok()?
.insert(path.to_path_buf(), (modified, layers.clone()));
Some(layers)
}
/// Whether any of reference `name`'s layers was changed in the host — the
/// Properties "Layer property overrides" row. Compares the host's dependent
/// layers with the source file's own.
pub fn layer_overrides(doc: &CadDocument, name: &str, raw_path: &str, base_dir: &Path) -> bool {
let Some(source) = resolve_path(raw_path, base_dir).and_then(|p| source_layers(&p)) else {
return false;
};
let prefix = format!("{}|", name.to_uppercase());
doc.layers.iter().any(|layer| {
let upper = layer.name.to_uppercase();
let Some(own) = upper.strip_prefix(&prefix) else {
return false;
};
let Some(original) = source.get(own) else {
return false;
};
let mut here = LayerLook::of(layer);
// Dependent linetypes carry the reference prefix.
if let Some(stripped) = here.line_type.strip_prefix(&prefix) {
here.line_type = stripped.to_string();
}
here != *original
})
}
/// Try to build an absolute path from a raw xref path string.
/// Handles absolute paths, relative paths, and Windows-style separators.
fn resolve_path(raw: &str, base_dir: &Path) -> Option<PathBuf> {
@@ -342,18 +418,44 @@ fn resolve_path(raw: &str, base_dir: &Path) -> Option<PathBuf> {
None
}
/// Make sure `doc` has BLOCK + ENDBLK entities for `block_name`.
/// These are required so renderers can find the block content.
fn ensure_block_entities(doc: &mut CadDocument, block_name: &str) {
let has_block = doc
.entities()
.any(|e| matches!(e, EntityType::Block(b) if b.name == block_name));
if has_block {
/// Give a block record its BLOCK / ENDBLK markers, linked through the
/// record's marker handles. The DWG writer emits the markers the record
/// points at; a record left pointing at a null handle is written with
/// handle 0, which makes the whole drawing unreadable elsewhere.
pub(crate) fn ensure_block_entities(doc: &mut CadDocument, block_name: &str) {
let Some((owner, begin, end, path)) = doc.block_records.get(block_name).map(|br| {
(
br.handle,
br.block_entity_handle,
br.block_end_handle,
br.xref_path.clone(),
)
}) else {
return;
};
if begin.is_null() || !matches!(doc.get_entity(begin), Some(EntityType::Block(_))) {
let handle = doc.allocate_handle();
let mut block = Block::new(block_name, Vector3::zero());
if !path.is_empty() {
block = block.with_xref_path(&path);
}
block.common.handle = handle;
block.common.owner_handle = owner;
let _ = doc.add_entity(EntityType::Block(block));
if let Some(br) = doc.block_records.get_mut(block_name) {
br.block_entity_handle = handle;
}
}
if end.is_null() || !matches!(doc.get_entity(end), Some(EntityType::BlockEnd(_))) {
let handle = doc.allocate_handle();
let mut block_end = BlockEnd::new();
block_end.common.handle = handle;
block_end.common.owner_handle = owner;
let _ = doc.add_entity(EntityType::BlockEnd(block_end));
if let Some(br) = doc.block_records.get_mut(block_name) {
br.block_end_handle = handle;
}
}
let b = Block::new(block_name, Vector3::zero());
let _ = doc.add_entity(EntityType::Block(b));
let _ = doc.add_entity(EntityType::BlockEnd(BlockEnd::new()));
}
/// Shared symbol-remap core for XREF load-merge and BIND (SPIKE3 REFACTOR
@@ -471,6 +573,7 @@ struct XrefSymbolMaps {
layers: HashMap<String, String>,
linetypes: HashMap<String, String>,
text_styles: HashMap<String, String>,
text_style_handles: HashMap<Handle, Handle>,
dim_styles: HashMap<String, String>,
blocks: HashMap<String, String>,
br_handles: HashMap<Handle, Handle>,
@@ -493,16 +596,55 @@ fn import_xref_symbols(
) -> XrefSymbolMaps {
let mut maps = XrefSymbolMaps::default();
// A layer's plot style and material point at objects of the file it came
// from; carried over verbatim they point at whatever the host keeps under
// those handles. Take the host's defaults (its layer 0) instead.
// ponytail: every imported layer gets the default plot style and material;
// map them by name if a reference ever carries non-default ones.
let (host_plotstyle, host_material) = doc
.layers
.get("0")
.map(|layer| (layer.plotstyle_handle, layer.material))
.unwrap_or((Handle::NULL, Handle::NULL));
for layer in xref_doc.layers.iter() {
let old = layer.name.clone();
let new = naming.name_for(SymbolKind::Layer, &old);
let mut cloned = layer.clone();
cloned.name = new.clone();
cloned.plotstyle_handle = host_plotstyle;
cloned.material = host_material;
cloned.set_handle(doc.allocate_handle());
doc.layers.add_or_replace(cloned);
maps.layers.insert(old.to_uppercase(), new);
}
for ts in xref_doc.text_styles.iter() {
// A shape-file style has no name of its own; the host's style for the
// same shape file serves the imported linetypes.
if ts.is_shape_file {
let same_file = doc
.text_styles
.iter()
.find(|host| host.is_shape_file && host.font_file.eq_ignore_ascii_case(&ts.font_file))
.map(|host| host.handle);
if let Some(host_handle) = same_file {
maps.text_style_handles.insert(ts.handle, host_handle);
continue;
}
}
let old = ts.name.clone();
let new = naming.name_for(SymbolKind::TextStyle, &old);
let mut cloned = ts.clone();
cloned.name = new.clone();
let new_handle = doc.allocate_handle();
cloned.set_handle(new_handle);
cloned.xref_dependent = false;
doc.text_styles.add_or_replace(cloned);
maps.text_style_handles.insert(ts.handle, new_handle);
maps.text_styles.insert(old.to_uppercase(), new);
}
for lt in xref_doc.line_types.iter() {
let old = lt.name.clone();
if is_sentinel_linetype(&old) {
@@ -511,22 +653,20 @@ fn import_xref_symbols(
let new = naming.name_for(SymbolKind::Linetype, &old);
let mut cloned = lt.clone();
cloned.name = new.clone();
// Shape and text segments name their style by handle: point them at
// the imported copy, not at the source file's record.
for element in &mut cloned.elements {
if let Some(complex) = element.complex.as_mut() {
if let Some(new_style) = maps.text_style_handles.get(&complex.style_handle) {
complex.style_handle = *new_style;
}
}
}
cloned.set_handle(doc.allocate_handle());
doc.line_types.add_or_replace(cloned);
maps.linetypes.insert(old.to_uppercase(), new);
}
for ts in xref_doc.text_styles.iter() {
let old = ts.name.clone();
let new = naming.name_for(SymbolKind::TextStyle, &old);
let mut cloned = ts.clone();
cloned.name = new.clone();
cloned.set_handle(doc.allocate_handle());
cloned.xref_dependent = false;
doc.text_styles.add_or_replace(cloned);
maps.text_styles.insert(old.to_uppercase(), new);
}
for ds in xref_doc.dim_styles.iter() {
let old = ds.name.clone();
let new = naming.name_for(SymbolKind::DimStyle, &old);
@@ -798,6 +938,21 @@ fn merge_xref_into_block(
prefix: prefix.to_string(),
};
let maps = import_xref_symbols(doc, &xref_doc, &mut naming);
// Dependent layers and linetypes carry the xref flag and their owning
// reference, the way the file stores them; without it the host writes
// plain records with a "|" in the name, which other readers reject.
for name in maps.layers.values() {
if let Some(layer) = doc.layers.get_mut(name) {
layer.flags.xref_dependent = true;
layer.xref_block_record_handle = br_handle;
}
}
for name in maps.linetypes.values() {
if let Some(linetype) = doc.line_types.get_mut(name) {
linetype.xref_dependent = true;
linetype.xref_block_record_handle = br_handle;
}
}
// ── Entities (shared helper) ────────────────────────────────────────
// The unremapped-handle count is ignored on the load path (BIND reports
@@ -1709,7 +1864,69 @@ fn same_file(a: &Path, b: &Path) -> bool {
/// Shared by Detach (which then also erases the BlockRecord itself) and BIND
/// (which drops the previous merge before re-merging under `$N$` names). The
/// xref BlockRecord itself, its INSERTs, and non-prefixed symbols stay.
/// Make the named references' paths relative to `host` (their drawing's
/// first save); a reference that cannot be relative keeps its full path.
pub fn make_relative(
doc: &mut CadDocument,
names: &rustc_hash::FxHashSet<String>,
host: &Path,
) {
for name in names {
let Some(key) = doc.block_records.get(name).map(|br| br.handle.value()) else {
continue;
};
let _ = apply_pathtype(doc, key, crate::io::xref_model::Pathtype::Relative, host);
}
}
/// Drop from a save copy what resolving each xref merged into the host but
/// the file never stores: the reference's own geometry, its nested block
/// definitions and its text and dimension styles. Dependent layers and
/// linetypes stay (flagged), so layer overrides kept under VISRETAIN persist.
pub fn strip_resolved_xref_content(doc: &mut CadDocument) {
let xrefs: Vec<(String, Handle)> = doc
.block_records
.iter()
.filter(|br| br.flags.is_xref || br.flags.is_xref_overlay)
.map(|br| (br.name.clone(), br.handle))
.collect();
for (name, handle) in xrefs {
let (begin, end) = doc
.block_records
.get(&name)
.map(|br| (br.block_entity_handle, br.block_end_handle))
.unwrap_or((Handle::NULL, Handle::NULL));
let owned: Vec<Handle> = doc
.entities()
.filter(|e| e.common().owner_handle == handle)
.map(|e| e.common().handle)
.filter(|h| *h != begin && *h != end)
.collect();
for h in owned {
doc.remove_entity(h);
}
if let Some(br) = doc.block_records.get_mut(&name) {
br.entity_handles.clear();
}
remove_dependent_symbols(doc, &name, false);
}
}
/// Text styles a kept linetype draws its shapes or text with.
fn linetype_styles(doc: &CadDocument) -> HashSet<Handle> {
doc.line_types
.iter()
.flat_map(|linetype| linetype.elements.iter())
.filter_map(|element| element.complex.as_ref().map(|complex| complex.style_handle))
.filter(|handle| !handle.is_null())
.collect()
}
fn remove_pipe_symbols(doc: &mut CadDocument, name: &str) {
remove_dependent_symbols(doc, name, true);
}
fn remove_dependent_symbols(doc: &mut CadDocument, name: &str, layers_too: bool) {
let prefix = format!("{}|", name.to_uppercase());
let is_dep = |n: &str| n.to_uppercase().starts_with(&prefix);
// Dependent blocks first — their handles route the owned-entity cleanup.
@@ -1746,7 +1963,7 @@ fn remove_pipe_symbols(doc: &mut CadDocument, name: &str) {
let layers: Vec<String> = doc
.layers
.names()
.filter(|n| is_dep(n))
.filter(|n| layers_too && is_dep(n))
.map(|s| s.to_string())
.collect();
for n in layers {
@@ -1755,21 +1972,38 @@ fn remove_pipe_symbols(doc: &mut CadDocument, name: &str) {
let lts: Vec<String> = doc
.line_types
.names()
.filter(|n| is_dep(n))
.filter(|n| layers_too && is_dep(n))
.map(|s| s.to_string())
.collect();
for n in lts {
doc.line_types.remove(&n);
}
// Styles a kept dependent linetype still draws with stay, flagged.
let used = if layers_too {
HashSet::default()
} else {
linetype_styles(doc)
};
let styles: Vec<String> = doc
.text_styles
.names()
.filter(|n| is_dep(n))
.map(|s| s.to_string())
.iter()
.filter(|style| is_dep(&style.name) && !used.contains(&style.handle))
.map(|style| style.name.clone())
.collect();
for n in styles {
doc.text_styles.remove(&n);
}
let owner = doc
.block_records
.get(name)
.map(|br| br.handle)
.unwrap_or(Handle::NULL);
for style in doc.text_styles.iter_mut() {
if is_dep(&style.name) {
style.xref_dependent = true;
style.xref_block_record_handle = owner;
}
}
let dims: Vec<String> = doc
.dim_styles
.names()
+6 -1
View File
@@ -247,6 +247,11 @@ pub fn to_pathtype_result(
if parts.is_empty() {
return Ok(file_name_only(path));
}
// A path at or below the host folder starts "./" ("./plan.dwg",
// "./refs/plan.dwg"), as the reference writes it; one above starts "../".
if parts[0] != ".." {
parts.insert(0, ".".to_string());
}
Ok(parts.join("/"))
}
}
@@ -846,7 +851,7 @@ mod tests {
let full = to_pathtype("C:/Drawings/refs/plan.dwg", host, Pathtype::Full);
assert_eq!(full, "C:/Drawings/refs/plan.dwg");
let rel = to_pathtype("C:/Drawings/refs/plan.dwg", host, Pathtype::Relative);
assert_eq!(rel, "refs/plan.dwg");
assert_eq!(rel, "./refs/plan.dwg");
let none = to_pathtype("C:/Drawings/refs/plan.dwg", host, Pathtype::None);
assert_eq!(none, "plan.dwg");
}
+22
View File
@@ -265,6 +265,28 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"Added annotation scale {arg}." => Some(("view", "added-annotation-scale-arg")),
"Added from GitHub" => Some(("plugins", "added-from-github")),
"Adjust" => Some(("insert", "adjust")),
"Attach External Reference" => Some(("insert", "xref-attach-dialog-title")),
"Browse..." => Some(("insert", "xref-attach-browse")),
"Reference Type" => Some(("insert", "xref-attach-reference-type")),
"Specify On-screen" => Some(("insert", "xref-attach-specify-on-screen")),
"Uniform Scale" => Some(("insert", "xref-attach-uniform-scale")),
"Insertion point" => Some(("insert", "xref-attach-insertion-point")),
"Angle:" => Some(("insert", "xref-attach-angle")),
"Block Unit" => Some(("insert", "xref-attach-block-unit")),
"Unit:" => Some(("insert", "xref-attach-unit")),
"Factor:" => Some(("insert", "xref-attach-factor")),
"Found in:" => Some(("insert", "xref-attach-found-in")),
"Saved path:" => Some(("insert", "xref-attach-saved-path")),
"Show Details" => Some(("insert", "xref-attach-show-details")),
"Hide Details" => Some(("insert", "xref-attach-hide-details")),
"Select Reference File" => Some(("insert", "xref-attach-select-file")),
"All Reference Files" => Some(("insert", "xref-attach-all-files")),
"Inserts references to external files such as other drawings, raster images, and underlays." => Some(("insert", "xref-attach-help")),
"Invalid input." => Some(("insert", "xref-attach-invalid-input")),
"Insertion point X" => Some(("insert", "xref-insertion-point-x")),
"Insertion point Y" => Some(("insert", "xref-insertion-point-y")),
"Insertion point Z" => Some(("insert", "xref-insertion-point-z")),
"Layer property overrides" => Some(("insert", "xref-layer-property-overrides")),
"Adjust Colors for Background" => Some(("properties", "adjust-colors-for-background")),
"Advanced" => Some(("properties", "advanced")),
"Alias" => Some(("command", "alias-title-case")),
+1
View File
@@ -23,6 +23,7 @@ mod underlay_layers;
pub(crate) mod wblock;
mod xadjust;
pub(crate) mod xattach;
pub(crate) mod xref_cmd;
mod xclip;
use crate::modules::{CadModule, IconKind, RibbonGroup, RibbonItem};
File diff suppressed because it is too large. Load diff
+237
View File
@@ -0,0 +1,237 @@
// -XREF — the command-line form of the reference manager.
//
// Enter an option [?/Bind/Detach/Path/pathType/Unload/Reload/Overlay/Attach/Show] <Attach>:
//
// Each option collects its answers with the reference prompts and hands them
// to the existing reference operations through `CmdResult::Dispatch`.
use glam::DVec3;
use crate::command::{CadCommand, CmdOption, CmdResult, InputKind};
#[derive(Debug, Clone, PartialEq, Eq)]
enum Step {
Option,
List,
Names(&'static str),
File { overlay: bool },
PathNames,
NewPath { name: String },
PathTypeNames,
PathTypeKind { names: String },
ShowNames,
}
pub struct XrefCommand {
step: Step,
/// Saved paths by reference name, for the Path option's "Old path:" line.
saved_paths: Vec<(String, String)>,
}
impl XrefCommand {
pub fn new(saved_paths: Vec<(String, String)>) -> Self {
Self {
step: Step::Option,
saved_paths,
}
}
/// Whether any reference name matches `pattern` (`*` / `?` wildcards).
fn matches(&self, pattern: &str) -> bool {
self.saved_paths
.iter()
.any(|(name, _)| crate::io::xref_model::wildcard_match(name, pattern))
}
fn choose(&mut self, token: &str) -> CmdResult {
self.step = match token {
"?" => Step::List,
"B" | "BIND" => Step::Names("Bind"),
"D" | "DETACH" => Step::Names("Detach"),
"P" | "PATH" => Step::PathNames,
"T" | "PATHTYPE" => Step::PathTypeNames,
"U" | "UNLOAD" => Step::Names("Unload"),
"R" | "RELOAD" => Step::Names("Reload"),
"O" | "OVERLAY" => Step::File { overlay: true },
"A" | "ATTACH" => Step::File { overlay: false },
"S" | "SHOW" => Step::ShowNames,
_ => {
return CmdResult::ReportError("Invalid option keyword.".to_string());
}
};
CmdResult::NeedPoint
}
}
impl CadCommand for XrefCommand {
fn name(&self) -> &'static str {
"-XREF"
}
fn prompt(&self) -> String {
let text = match &self.step {
Step::Option => "Enter an option [?/Bind/Detach/Path/pathType/Unload/Reload/Overlay/Attach/Show] <Attach>:",
Step::List => "Enter xref name(s) to list <*>:",
Step::Names("Bind") => "Enter xref name(s) to bind:",
Step::Names("Detach") => "Enter xref name(s) to detach:",
Step::Names("Unload") => "Enter xref name(s) to unload:",
Step::Names(_) => "Enter xref name(s) to reload:",
Step::File { overlay: false } => "Enter name of file to attach:",
Step::File { overlay: true } => "Enter name of file to overlay:",
Step::PathNames => "Edit xref name(s) to edit path:",
Step::NewPath { .. } => "Enter new path:",
Step::PathTypeNames => "Enter xref name(s) to edit path type:",
Step::PathTypeKind { .. } => "Enter new path type [Full/Relative/None]:",
Step::ShowNames => "Enter xref name(s) to show:",
};
format!("-XREF {text}")
}
fn options(&self) -> Vec<CmdOption> {
match self.step {
Step::Option => vec![
CmdOption::new("?", "?"),
CmdOption::new("Bind", "B"),
CmdOption::new("Detach", "D"),
CmdOption::new("Path", "P"),
CmdOption::new("pathType", "T"),
CmdOption::new("Unload", "U"),
CmdOption::new("Reload", "R"),
CmdOption::new("Overlay", "O"),
CmdOption::new("Attach", "A"),
CmdOption::new("Show", "S"),
],
Step::PathTypeKind { .. } => vec![
CmdOption::new("Full", "F"),
CmdOption::new("Relative", "R"),
CmdOption::new("None", "N"),
],
_ => Vec::new(),
}
}
fn input_kind(&self) -> InputKind {
match self.step {
// Paths and names can hold spaces.
Step::File { .. } | Step::NewPath { .. } => InputKind::FreeText,
_ => InputKind::SingleToken,
}
}
fn on_point(&mut self, _pt: DVec3) -> CmdResult {
CmdResult::NeedPoint
}
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
let value = text.trim().trim_matches('"').to_string();
let token = value.to_uppercase();
let names_step = matches!(
self.step,
Step::Names(_) | Step::PathTypeNames | Step::ShowNames
);
if names_step && !self.matches(&value) {
return Some(CmdResult::Measurement(
"No matching xref names found.".to_string(),
));
}
Some(match self.step.clone() {
Step::Option => self.choose(&token),
Step::List => CmdResult::Dispatch(format!("XREFLIST {value}")),
Step::Names(op) => CmdResult::Dispatch(format!("XREF {op} {value}")),
Step::File { overlay } => {
let verb = if overlay { "XREFOVERLAYFILE" } else { "XREFATTACHFILE" };
CmdResult::Dispatch(format!("{verb} {value}"))
}
Step::PathNames => {
let Some((name, old)) = self
.saved_paths
.iter()
.find(|(name, _)| crate::io::xref_model::wildcard_match(name, &value))
.cloned()
else {
return Some(CmdResult::Measurement(
"No matching xref names found.".to_string(),
));
};
self.step = Step::NewPath { name: name.clone() };
CmdResult::ReportMeasurement(format!(
"{}\n{}",
format!("xref name: \"{}\"", name),
format!("Old path: {}", display_path(&old))
))
}
Step::NewPath { name } => CmdResult::Dispatch(format!("XREF Path {name} {value}")),
Step::PathTypeNames => {
self.step = Step::PathTypeKind { names: value };
CmdResult::NeedPoint
}
Step::PathTypeKind { names } => {
let kind = match token.as_str() {
"F" | "FULL" => "Full",
"R" | "RELATIVE" => "Relative",
"N" | "NONE" => "None",
_ => {
return Some(CmdResult::ReportError(
"Invalid option keyword.".to_string(),
))
}
};
CmdResult::Dispatch(format!("XREF Pathtype {kind} {names}"))
}
Step::ShowNames => CmdResult::Dispatch("XREFSHOW".to_string()),
})
}
fn on_enter(&mut self) -> CmdResult {
match self.step.clone() {
Step::Option => self.choose("A"),
Step::List => CmdResult::Dispatch("XREFLIST *".to_string()),
Step::NewPath { .. } => {
CmdResult::Measurement("Path unchanged.".to_string())
}
Step::PathTypeKind { .. } => CmdResult::NeedPoint,
_ => CmdResult::Cancel,
}
}
}
/// A stored path shown the way the platform writes it.
pub fn display_path(path: &str) -> String {
if cfg!(windows) {
path.replace('/', "\\")
} else {
path.to_string()
}
}
#[cfg(test)]
mod tests {
use super::XrefCommand;
use crate::command::{CadCommand, CmdResult};
#[test]
fn enter_chooses_attach() {
let mut cmd = XrefCommand::new(Vec::new());
assert!(matches!(cmd.on_enter(), CmdResult::NeedPoint));
assert!(cmd.prompt().contains("Enter name of file to attach:"));
}
#[test]
fn list_defaults_to_every_reference() {
let mut cmd = XrefCommand::new(Vec::new());
cmd.on_text_input("?");
assert!(cmd.prompt().contains("Enter xref name(s) to list <*>:"));
assert!(matches!(cmd.on_enter(), CmdResult::Dispatch(ref s) if s == "XREFLIST *"));
}
#[test]
fn overlay_file_is_dispatched() {
let mut cmd = XrefCommand::new(Vec::new());
cmd.on_text_input("O");
assert!(cmd.prompt().contains("Enter name of file to overlay:"));
assert!(matches!(
cmd.on_text_input("C:/refs/site plan.dwg"),
Some(CmdResult::Dispatch(ref s)) if s == "XREFOVERLAYFILE C:/refs/site plan.dwg"
));
}
}
+22
View File
@@ -633,6 +633,28 @@ impl WireModel {
out
}
/// Return a preview clone with every point mapped through `map` (an
/// INSERT-style placement: per-axis scale, rotation, translation).
/// Tangent geometry is dropped; a preview is not snapped to.
pub fn mapped(&self, map: impl Fn(glam::DVec3) -> glam::DVec3) -> Self {
let mut out = self.clone();
out.name = format!("preview_{}", self.name);
out.color = Self::CYAN;
out.selected = false;
map_points(&mut out.points, &mut out.points_low, &map);
if let Some(marker) = &mut out.point_marker {
marker.origin = map(marker.origin);
}
if !out.text_verts.is_empty() {
out.text_verts = map_text_verts(&self.text_verts, |x, y, z| {
let p = map(glam::DVec3::new(x, y, z));
(p.x, p.y, p.z)
});
}
out.tangent_geoms.clear();
out
}
/// Return a clone with every point rotated around `center` by `angle_rad`.
pub fn rotated(&self, center: glam::Vec3, angle_rad: f32) -> Self {
self.rotated_about_axis(center, glam::Vec3::Z, angle_rad)
+1
View File
@@ -2530,6 +2530,7 @@ impl super::Scene {
}
}
}
crate::io::xref::strip_resolved_xref_content(&mut document);
document
}
+2 -2
View File
@@ -158,7 +158,7 @@ impl BlockDefinitionState {
}
}
fn group<'a>(
pub(crate) fn group<'a>(
title: impl Into<String>,
body: impl Into<Element<'a, Message>>,
height: impl Into<Length>,
@@ -188,7 +188,7 @@ fn group<'a>(
.into()
}
fn labeled_checkbox<'a>(
pub(crate) fn labeled_checkbox<'a>(
label: impl Into<String>,
is_checked: bool,
on_toggle: impl Fn(bool) -> Message + 'a,
+1
View File
@@ -24,5 +24,6 @@ pub mod annotation_data;
pub mod alias_editor;
pub mod find_replace;
pub mod named_parameters;
pub mod xref_attach;
pub mod xref_help;
pub mod xref_manager;
+414
View File
@@ -0,0 +1,414 @@
//! Attach External Reference dialog — the options chosen after picking a
//! drawing with ATTACH / XATTACH: reference type, scale, insertion point,
//! path type and rotation, with the file's preview and block unit.
use std::fmt;
use iced::widget::{
button, column, combo_box, container, image, pick_list, radio, row, text, text_input, Space,
};
use iced::{Element, Fill, Length, Theme};
use crate::app::Message;
use crate::io::xref_model::Pathtype;
use crate::t;
use crate::ui::style::common::muted_style;
use crate::ui::style::form::{button_style, field_style};
use crate::ui::window::block_definition::{group, labeled_checkbox};
/// One edit in the dialog.
#[derive(Debug, Clone)]
pub enum XrefAttachMsg {
/// Name box: a reference already in the drawing, picked or typed.
Name(String),
Browse,
Overlay(bool),
ScaleOnScreen(bool),
Scale(usize, String),
Uniform(bool),
InsertOnScreen(bool),
Insert(usize, String),
PathType(PathTypeChoice),
RotationOnScreen(bool),
Rotation(String),
Details(bool),
Apply,
Help,
}
/// Path type choice as the dialog lists it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PathTypeChoice(pub Pathtype);
impl PathTypeChoice {
pub const ALL: [PathTypeChoice; 3] = [
PathTypeChoice(Pathtype::Full),
PathTypeChoice(Pathtype::Relative),
PathTypeChoice(Pathtype::None),
];
}
impl fmt::Display for PathTypeChoice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let label = match self.0 {
Pathtype::Full => t!("Full path"),
Pathtype::Relative => t!("Relative path"),
Pathtype::None => t!("No path"),
};
f.write_str(label.as_ref())
}
}
/// The dialog's working copy.
#[derive(Clone)]
pub struct XrefAttachState {
/// Absolute path of the drawing to attach.
pub path: String,
/// Reference name (the file name without extension).
pub name: String,
/// References already in the drawing: name and saved path.
pub existing: Vec<(String, String)>,
pub name_combo: combo_box::State<String>,
pub overlay: bool,
pub scale_on_screen: bool,
pub scale: [String; 3],
pub uniform: bool,
pub insert_on_screen: bool,
pub insert: [String; 3],
pub path_type: PathTypeChoice,
pub rotation_on_screen: bool,
pub rotation: String,
/// Block unit of the chosen drawing and its factor to the host's unit.
pub unit_label: String,
pub unit_factor: String,
pub found_in: String,
pub saved_path: String,
pub details: bool,
pub preview: Option<image::Handle>,
}
impl XrefAttachState {
pub fn new(existing: Vec<(String, String)>) -> Self {
let names = existing.iter().map(|(name, _)| name.clone()).collect();
Self {
path: String::new(),
name: String::new(),
existing,
name_combo: combo_box::State::new(names),
overlay: false,
scale_on_screen: false,
scale: ["1.0000".into(), "1.0000".into(), "1.0000".into()],
uniform: false,
insert_on_screen: true,
insert: ["0.0000".into(), "0.0000".into(), "0.0000".into()],
path_type: PathTypeChoice(Pathtype::Relative),
rotation_on_screen: false,
rotation: "0".into(),
unit_label: String::new(),
unit_factor: String::new(),
found_in: String::new(),
saved_path: String::new(),
details: false,
preview: None,
}
}
/// Parsed X/Y/Z scale; a uniform scale takes X for all three.
pub fn scale_values(&self) -> Option<[f64; 3]> {
let parse = |s: &String| s.trim().parse::<f64>().ok();
let x = parse(&self.scale[0])?;
if self.uniform {
return Some([x, x, x]);
}
Some([x, parse(&self.scale[1])?, parse(&self.scale[2])?])
}
pub fn insert_point(&self) -> Option<glam::DVec3> {
let parse = |s: &String| s.trim().parse::<f64>().ok();
Some(glam::DVec3::new(
parse(&self.insert[0])?,
parse(&self.insert[1])?,
parse(&self.insert[2])?,
))
}
}
fn msg(m: XrefAttachMsg) -> Message {
Message::XrefAttach(m)
}
fn number_field<'a>(
label: &'a str,
value: &'a str,
enabled: bool,
on_input: impl Fn(String) -> Message + 'a,
) -> Element<'a, Message> {
let mut field = text_input("", value)
.size(11)
.padding([3, 6])
.width(Fill)
.style(field_style);
if enabled {
field = field.on_input(on_input);
}
row![
text(label).size(11).style(muted_style).width(Length::Fixed(16.0)),
field
]
.spacing(6)
.align_y(iced::Center)
.into()
}
fn read_only_field<'a>(label: String, value: &'a str, label_width: f32) -> Element<'a, Message> {
row![
text(label)
.size(11)
.style(muted_style)
.width(Length::Fixed(label_width)),
text_input("", value)
.size(11)
.padding([3, 6])
.width(Fill)
.style(field_style),
]
.spacing(6)
.align_y(iced::Center)
.into()
}
pub fn view_window<'a>(
state: &'a XrefAttachState,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
// ── Name ────────────────────────────────────────────────────────────
let name_combo = combo_box(
&state.name_combo,
"",
if state.name.is_empty() {
None
} else {
Some(&state.name)
},
|chosen: String| msg(XrefAttachMsg::Name(chosen)),
)
.size(11)
.padding([3, 6])
.width(Fill);
let name_section = column![
text(t!("Name:")).size(11).style(muted_style),
row![
name_combo,
button(text(t!("Browse...")).size(11))
.on_press(msg(XrefAttachMsg::Browse))
.style(button_style(false))
.padding([4, 12])
.width(Length::Fixed(96.0)),
]
.spacing(8)
.align_y(iced::Center),
]
.spacing(3);
// ── Preview + reference type ────────────────────────────────────────
let preview_body: Element<'a, Message> = match &state.preview {
Some(handle) => image(handle.clone()).width(Fill).height(Fill).into(),
None => Space::new().width(Fill).height(Fill).into(),
};
let preview = group(
t!("Preview").into_owned(),
container(preview_body)
.width(Fill)
.height(Length::Fixed(132.0))
.style(|theme: &Theme| container::Style {
background: Some(iced::Background::Color(
theme.palette().background.base.color,
)),
border: iced::Border {
width: 1.0,
radius: 2.0.into(),
color: theme.palette().background.strong.color,
},
..Default::default()
}),
Length::Shrink,
);
let reference_type = group(
t!("Reference Type").into_owned(),
column![
radio(
t!("Attachment"),
false,
Some(state.overlay),
|v| msg(XrefAttachMsg::Overlay(v))
)
.size(14)
.text_size(11),
radio(t!("Overlay"), true, Some(state.overlay), |v| msg(
XrefAttachMsg::Overlay(v)
))
.size(14)
.text_size(11),
]
.spacing(6),
Length::Shrink,
);
let left = column![preview, reference_type]
.spacing(6)
.width(Length::FillPortion(1));
// ── Scale + insertion point ─────────────────────────────────────────
let scale_enabled = !state.scale_on_screen;
let scale = group(
t!("Scale").into_owned(),
column![
labeled_checkbox(t!("Specify On-screen"), state.scale_on_screen, |v| msg(
XrefAttachMsg::ScaleOnScreen(v)
)),
number_field("X:", &state.scale[0], scale_enabled, |v| msg(
XrefAttachMsg::Scale(0, v)
)),
number_field(
"Y:",
&state.scale[1],
scale_enabled && !state.uniform,
|v| msg(XrefAttachMsg::Scale(1, v))
),
number_field(
"Z:",
&state.scale[2],
scale_enabled && !state.uniform,
|v| msg(XrefAttachMsg::Scale(2, v))
),
labeled_checkbox(t!("Uniform Scale"), state.uniform, |v| msg(
XrefAttachMsg::Uniform(v)
)),
]
.spacing(5),
Length::Shrink,
);
let insert_enabled = !state.insert_on_screen;
let insertion = group(
t!("Insertion point").into_owned(),
column![
labeled_checkbox(t!("Specify On-screen"), state.insert_on_screen, |v| msg(
XrefAttachMsg::InsertOnScreen(v)
)),
number_field("X:", &state.insert[0], insert_enabled, |v| msg(
XrefAttachMsg::Insert(0, v)
)),
number_field("Y:", &state.insert[1], insert_enabled, |v| msg(
XrefAttachMsg::Insert(1, v)
)),
number_field("Z:", &state.insert[2], insert_enabled, |v| msg(
XrefAttachMsg::Insert(2, v)
)),
]
.spacing(5),
Length::Shrink,
);
let middle = column![scale, insertion]
.spacing(6)
.width(Length::FillPortion(1));
// ── Path type, rotation, block unit ─────────────────────────────────
let path_type = group(
t!("Path type").into_owned(),
pick_list(Some(state.path_type), PathTypeChoice::ALL, |choice| choice.to_string())
.on_select(|v| msg(XrefAttachMsg::PathType(v)))
.text_size(11)
.padding([3, 6])
.width(Fill),
Length::Shrink,
);
let mut angle = text_input("", &state.rotation)
.size(11)
.padding([3, 6])
.width(Fill)
.style(field_style);
if !state.rotation_on_screen {
angle = angle.on_input(|v| msg(XrefAttachMsg::Rotation(v)));
}
let rotation = group(
t!("Rotation").into_owned(),
column![
labeled_checkbox(t!("Specify On-screen"), state.rotation_on_screen, |v| msg(
XrefAttachMsg::RotationOnScreen(v)
)),
row![
text(t!("Angle:"))
.size(11)
.style(muted_style)
.width(Length::Fixed(44.0)),
angle
]
.spacing(6)
.align_y(iced::Center),
]
.spacing(5),
Length::Shrink,
);
let block_unit = group(
t!("Block Unit").into_owned(),
column![
read_only_field(t!("Unit:").into_owned(), &state.unit_label, 52.0),
read_only_field(t!("Factor:").into_owned(), &state.unit_factor, 52.0),
]
.spacing(5),
Length::Shrink,
);
let right = column![path_type, rotation, block_unit]
.spacing(6)
.width(Length::FillPortion(1));
let body = row![left, middle, right].spacing(6).width(Fill);
// ── Details ─────────────────────────────────────────────────────────
let details: Element<'a, Message> = if state.details {
group(
t!("Location").into_owned(),
column![
read_only_field(t!("Found in:").into_owned(), &state.found_in, 92.0),
read_only_field(t!("Saved path:").into_owned(), &state.saved_path, 92.0),
]
.spacing(5),
Length::Shrink,
)
} else {
Space::new().height(0).into()
};
// ── Footer ──────────────────────────────────────────────────────────
let details_label = if state.details {
t!("Hide Details")
} else {
t!("Show Details")
};
let footer = row![
button(text(details_label).size(11))
.on_press(msg(XrefAttachMsg::Details(!state.details)))
.style(button_style(false))
.padding([4, 12]),
Space::new().width(Fill),
button(text(t!("OK")).size(11))
.on_press(msg(XrefAttachMsg::Apply))
.style(button_style(true))
.padding([4, 16]),
button(text(t!("Cancel")).size(11))
.on_press(Message::CloseModal)
.style(button_style(false))
.padding([4, 12]),
button(text(t!("Help")).size(11))
.on_press(msg(XrefAttachMsg::Help))
.style(button_style(false))
.padding([4, 12]),
]
.spacing(6)
.align_y(iced::Center);
column![name_section, body, details, footer]
.spacing(6)
.padding([8, 10])
.width(sizing.width)
.into()
}