Frames: state-following ribbon list, DWF/DGN frames, image frame encoding and profile, frame-only picking

- The Frames list shows the drawing's frame state (hide / display and plot /
  display but not plot / frames vary), follows commands, undo and document
  switches, and lists the mixed state as a disabled item.
- DWFFRAME and DGNFRAME are frame variables of their own, kept in the header,
  and FRAME sets them with the others.
- Out-of-range frame values say "Requires an integer between 0 and 2." and
  ask again; a set value reports "Regenerating model.".
- IMAGEFRAME is stored in the image variables as bits (2 is written as 3) and
  a drawing without them uses the profile value, which setting IMAGEFRAME
  updates and the settings file keeps.
- Raster images, PDF underlays and wipeouts are picked on their frame only;
  underlay snap geometry is never picked.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-09-28 10:53:53 +03:00
1 parent 801882314f
commit 7f8f8457c7
72 files changed
+451 -65

No files matched your search

Generated
+1 -1
View File
@@ -4201,7 +4201,7 @@ dependencies = [
[[package]]
name = "opencadcodec"
version = "0.5.5"
source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=42b44d2#42b44d2343e9925cff8a0c9e94c1d1c5ae8541c7"
source = "git+https://github.com/ramox81/cadcodec.git?rev=ea5edaa#ea5edaaac269ae957805a2ff8a595db7fd7f4002"
dependencies = [
"ahash 0.8.12",
"anyhow",
+5
View File
@@ -82,6 +82,11 @@ objc2-app-kit = { version = "0.2", features = ["NSApplication", "NSResponder", "
block2 = "0.5"
objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString", "NSObject", "NSNotification", "NSThread", "NSEnumerator"] }
# Frame header variables (DWFFRAME/DGNFRAME) until the codec change is released;
# every crate that pulls the codec must see the same build.
[patch."https://github.com/HakanSeven12/opencadcodec.git"]
opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "ea5edaa" }
[patch.crates-io]
iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
+6
View File
@@ -3667,6 +3667,12 @@ insert =
.frames-print = الإطارات والطباعة
.frames-off = إيقاف الإطارات
.frames-on = تشغيل الإطارات
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT أدخل اسم الكتلة:__ocs_arg_hint__
.insert-filling-attributes = INSERT يجري ملء السمات...
+6
View File
@@ -3654,6 +3654,12 @@ insert =
.frames-print = Рамки и печат
.frames-off = Рамки изкл.
.frames-on = Рамки вкл.
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Въведи име на блок:__ocs_arg_hint__
.insert-filling-attributes = INSERT Попълване на атрибути...
+6
View File
@@ -3652,6 +3652,12 @@ insert =
.frames-print = Rámy a tisk
.frames-off = Rámy vypnuty
.frames-on = Rámy zapnuty
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Název bloku Enter:__ocs_arg_hint__
.insert-filling-attributes = INSERT Vyplňování atributů...
+6
View File
@@ -3655,6 +3655,12 @@ insert =
.frames-print = Rahmen und Drucken
.frames-off = Rahmen aus
.frames-on = Rahmen ein
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Blockname eingeben:__ocs_arg_hint__
.insert-filling-attributes = INSERT Attribute ausfüllen...
+6
View File
@@ -3662,6 +3662,12 @@ insert =
.frames-print = Πλαίσια και εκτύπωση
.frames-off = Πλαίσια ανενεργά
.frames-on = Πλαίσια ενεργά
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Δώστε όνομα μπλοκ:__ocs_arg_hint__
.insert-filling-attributes = INSERT Συμπλήρωση χαρακτηριστικών...
+6
View File
@@ -3656,6 +3656,12 @@ insert =
.frames-print = Frames & Print
.frames-off = Frames Off
.frames-on = Frames On
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Enter block name:__ocs_arg_hint__
.insert-filling-attributes = INSERT Filling attributes...
+6
View File
@@ -3655,6 +3655,12 @@ insert =
.frames-print = Marcos e impresión
.frames-off = Marcos desactivados
.frames-on = Marcos activados
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Introduzca el nombre del bloque:__ocs_arg_hint__
.insert-filling-attributes = INSERT Rellenando atributos...
+6
View File
@@ -3654,6 +3654,12 @@ insert =
.frames-print = Kehykset ja painatus
.frames-off = Kehykset pois päältä
.frames-on = Kehykset päällä
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Enter -lohkon nimi:__ocs_arg_hint__
.insert-filling-attributes = INSERT Täytetään määritteitä...
+6
View File
@@ -3655,6 +3655,12 @@ insert =
.frames-print = Cadres & Imprimer
.frames-off = Cadres désactivés
.frames-on = Cadres en marche
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Saisissez le nom du bloc:__ocs_arg_hint__
.insert-filling-attributes = INSERT Attributs de remplissage...
+6
View File
@@ -3643,6 +3643,12 @@ insert =
.frames-print = फ्रेम्स एंड प्रिंट
.frames-off = फ़्रेम बंद
.frames-on = फ्रेम्स ऑन
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT ब्लॉक का नाम दर्ज करें:__ocs_arg_hint__
.insert-filling-attributes = INSERT गुण भरना ...
+6
View File
@@ -3654,6 +3654,12 @@ insert =
.frames-print = Keretek és nyomtatás
.frames-off = Keretek ki
.frames-on = Keretek bekapcsolva
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Enter blokknév: __ocs_arg_hint__
.insert-filling-attributes = INSERT Attribútumok kitöltése...
+6
View File
@@ -3654,6 +3654,12 @@ insert =
.frames-print = Cornici e stampa
.frames-off = Cornici disattivate
.frames-on = Cornici attivate
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Enter nome del blocco:__ocs_arg_hint__
.insert-filling-attributes = INSERT Attributi di riempimento...
+6
View File
@@ -3643,6 +3643,12 @@ insert =
.frames-print = フレームと印刷
.frames-off = フレームをオフ
.frames-on = フレームをオン
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT ブロック名を入力:__ocs_arg_hint__
.insert-filling-attributes = INSERT 属性を入力中...
+6
View File
@@ -3654,6 +3654,12 @@ insert =
.frames-print = 프레임 및 인쇄
.frames-off = 프레임 끄기
.frames-on = 프레임 켜짐
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Enter 블록 이름:__ocs_arg_hint__
.insert-filling-attributes = INSERT 채우기 속성...
+6
View File
@@ -3653,6 +3653,12 @@ insert =
.frames-print = Frames & afdrukken
.frames-off = Frames uitgeschakeld
.frames-on = Frames aan
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Geef symboolnaam in:__ocs_arg_hint__
.insert-filling-attributes = INSERT Attributen vullen...
+6
View File
@@ -3654,6 +3654,12 @@ insert =
.frames-print = Ramki i druk
.frames-off = Ramki wyłączone
.frames-on = Ramki włączone
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Enter nazwa bloku: __ocs_arg_hint__
.insert-filling-attributes = INSERT Wypełnianie atrybutów...
+6
View File
@@ -3655,6 +3655,12 @@ insert =
.frames-print = Molduras e impressão
.frames-off = Molduras desativadas
.frames-on = Molduras ativadas
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Insira o nome do bloco:__ocs_arg_hint__
.insert-filling-attributes = INSERT Preenchendo atributos...
+6
View File
@@ -3656,6 +3656,12 @@ insert =
.frames-print = Frames & Print (недоступная ссылка)
.frames-off = Снятие рам
.frames-on = В кадрах
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Введите имя блока: __ocs_arg_hint__
.insert-filling-attributes = INSERT Наполнение атрибутов...
+6
View File
@@ -3625,6 +3625,12 @@ insert =
.frames-print = Çerçeveler ve Yazdırma
.frames-off = Çerçeveler Kapalı
.frames-on = Çerçeveler Açık
.hide-frames = Çerçeveleri gizle
.display-and-plot-frames = Çerçeveleri göster ve çizdir
.display-not-plot-frames = Çerçeveleri göster, çizdirme
.frames-vary = *Çerçeveler farklı*
.regenerating-model = Model yeniden oluşturuluyor.
.requires-int-0-2 = 0 ile 2 arasında bir tamsayı gerekli.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT Blok adını girin:__ocs_arg_hint__
.insert-filling-attributes = INSERT Öznitelikler dolduruluyor...
+6
View File
@@ -3640,6 +3640,12 @@ insert =
.frames-print = 边框与打印
.frames-off = 边框关
.frames-on = 边框开
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT 输入块名:__ocs_arg_hint__
.insert-filling-attributes = INSERT 正在填充属性...
+6
View File
@@ -3652,6 +3652,12 @@ insert =
.frames-print = 邊框與列印
.frames-off = 邊框關
.frames-on = 邊框開
.hide-frames = Hide frames
.display-and-plot-frames = Display and plot frames
.display-not-plot-frames = Display but don't plot frames
.frames-vary = *Frames vary*
.regenerating-model = Regenerating model.
.requires-int-0-2 = Requires an integer between 0 and 2.
.insert-prompt-hint = INSERT __ocs_arg_prompt____ocs_arg_hint__:
.insert-enter-block-name-hint = INSERT 輸入圖塊名:__ocs_arg_hint__
.insert-filling-attributes = INSERT 正在填充屬性...
+3
View File
@@ -618,6 +618,8 @@ inventory::submit!(crate::command::CommandRegistration {
"FRAME",
"IMAGEFRAME",
"PDFFRAME",
"DWFFRAME",
"DGNFRAME",
"PDFOSNAP",
"UOSNAP",
"PDFIMPORTMODE",
@@ -630,6 +632,7 @@ inventory::submit!(crate::command::CommandRegistration {
"WIPEOUTFRAME",
"FRAMES0",
"FRAMES1",
"FRAMES3",
"FRAMES2",
"UOSNAP0",
"UOSNAP1",
+18 -4
View File
@@ -10,6 +10,8 @@ impl OpenCADStudio {
"FRAMES0" => return self.dispatch_styleprops("SETVAR FRAME 0", i),
"FRAMES1" => return self.dispatch_styleprops("SETVAR FRAME 1", i),
"FRAMES2" => return self.dispatch_styleprops("SETVAR FRAME 2", i),
// The mixed state is shown, never chosen.
"FRAMES3" => return Some(Task::none()),
"UOSNAP0" => return self.dispatch_styleprops("SETVAR UOSNAP 0", i),
"UOSNAP1" => return self.dispatch_styleprops("SETVAR UOSNAP 1", i),
// COLOR <ByLayer|ByBlock|1-255|name> — the colour applied to new
@@ -1017,6 +1019,8 @@ impl OpenCADStudio {
| "FRAME"
| "IMAGEFRAME"
| "PDFFRAME"
| "DWFFRAME"
| "DGNFRAME"
| "PDFOSNAP"
| "UOSNAP"
| "PDFIMPORTMODE"
@@ -1283,13 +1287,23 @@ impl OpenCADStudio {
self.tabs[i].scene.bump_entities(&changes);
}
self.tabs[i].dirty = true;
// IMAGEFRAME also lands in the profile.
self.save_config();
}
self.command_line
.push_output(&crate::tf!("{name} = {mode}"));
.push_output(crate::t!("Regenerating model.").as_ref());
}
// Out of range: say so and ask again.
_ => {
self.command_line.push_error(
crate::t!("Requires an integer between 0 and 2.").as_ref(),
);
self.command_line.push_output(
crate::tf!("Enter new value for {name} <{current}>:")
.as_ref(),
);
self.pending_setvar = Some(name.clone());
}
_ => self.command_line.push_error(
crate::tf!("SETVAR: {name} requires 0, 1, or 2.").as_ref(),
),
},
None => {
self.command_line.push_output(crate::tf!(
+4
View File
@@ -366,6 +366,9 @@ pub struct UserSettings {
/// GRIPOBJLIMIT: past this many selected objects, no grips are drawn at
/// all. 0 means no limit. The drawing header carries no slot for it.
pub grip_object_limit: i32,
/// IMAGEFRAME of the profile: used by new drawings and by drawings that
/// carry no image variables of their own.
pub image_frame: i16,
/// Nested-copy symbol handling: false inserts, true binds.
pub ncopy_bind: bool,
/// Last Options page; unknown saved names fall back without rejecting the config.
@@ -653,6 +656,7 @@ impl Default for UserSettings {
right_click_mode: RightClickMode::ShortcutMenu,
right_click_hold_ms: 250,
grip_object_limit: DEFAULT_GRIP_OBJECT_LIMIT,
image_frame: 1,
ncopy_bind: false,
cursor_type: CursorType::Crosshair,
crosshair_color: None,
+2
View File
@@ -614,6 +614,7 @@ impl OpenCADStudio {
right_click_mode: self.right_click_mode,
right_click_hold_ms: self.right_click_hold_ms,
grip_object_limit: self.grip_object_limit,
image_frame: crate::scene::frame::profile_image_mode(),
ncopy_bind: self.ncopy_bind,
cursor_type: self.cursor_type,
crosshair_color: self.crosshair_color,
@@ -698,6 +699,7 @@ impl OpenCADStudio {
self.right_click_mode = s.right_click_mode;
self.right_click_hold_ms = super::super::settings::clamp_right_click_hold_ms(s.right_click_hold_ms);
self.grip_object_limit = s.grip_object_limit.clamp(0, 32767);
crate::scene::frame::set_profile_image_mode(s.image_frame);
self.ncopy_bind = s.ncopy_bind;
self.cursor_type = s.cursor_type;
self.crosshair_color = s.crosshair_color;
+17
View File
@@ -326,9 +326,26 @@ impl OpenCADStudio {
if self.tabs.get(self.active_tab).map(|tab| tab.id) != before {
self.sync_underlay_tab();
}
self.sync_frame_dropdown();
task
}
/// The Frames list on the ribbon shows the active drawing's frame state
/// (FRAME), however it was changed: the list, a command, undo or another
/// drawing.
fn sync_frame_dropdown(&mut self) {
let Some(tab) = self.tabs.get(self.active_tab).filter(|tab| !tab.is_start) else {
return;
};
let current = match crate::scene::frame::master_mode(&tab.scene.document) {
0 => "FRAMES0",
1 => "FRAMES1",
2 => "FRAMES2",
_ => "FRAMES3",
};
self.ribbon.set_dropdown_current("FRAMES_DROPDOWN", current);
}
fn update_message(&mut self, msg: Message) -> Task<Message> {
if let Some(tab) = self.tabs.get(self.active_tab) {
crate::entities::common::set_unit_context(
+1
View File
@@ -2726,6 +2726,7 @@ impl OpenCADStudio {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+7
View File
@@ -4041,6 +4041,7 @@ fn tessellate_dimension_inner(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -4077,6 +4078,7 @@ fn tessellate_dimension_inner(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -4113,6 +4115,7 @@ fn tessellate_dimension_inner(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -4150,6 +4153,7 @@ fn tessellate_dimension_inner(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -4195,6 +4199,7 @@ fn tessellate_dimension_inner(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -4244,6 +4249,7 @@ fn tessellate_dimension_inner(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -4295,6 +4301,7 @@ fn tessellate_dimension_inner(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+2
View File
@@ -679,6 +679,7 @@ impl LeaderTess for Leader {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -860,6 +861,7 @@ impl LeaderTess for Leader {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+5
View File
@@ -2075,6 +2075,7 @@ impl MultiLeaderTess for MultiLeader {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -2389,6 +2390,7 @@ impl MultiLeaderTess for MultiLeader {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -2464,6 +2466,7 @@ impl MultiLeaderTess for MultiLeader {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -2554,6 +2557,7 @@ impl MultiLeaderTess for MultiLeader {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -2599,6 +2603,7 @@ impl MultiLeaderTess for MultiLeader {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+6 -32
View File
@@ -178,9 +178,9 @@ impl RenderConvertible for RasterImage {
path.is_empty() || crate::scene::model::image_model::resolve_image(path).is_some();
if resolvable {
return Some(RenderEntity {
// Interior pick surface: the image selects on a click anywhere
// inside its frame, not just on the border.
pick_tris: crate::entities::common::quad_pick_tris(&corners),
// Picked on its frame only (shown or hidden), like the other
// framed objects: a click inside the picture selects nothing.
pick_tris: vec![],
object: RenderObject::Lines(pts),
snap_pts: vec![],
tangent_geoms: vec![],
@@ -269,7 +269,7 @@ impl RenderConvertible for RasterImage {
}
Some(RenderEntity {
pick_tris: crate::entities::common::quad_pick_tris(&corners),
pick_tris: vec![],
object: RenderObject::Text(groups),
snap_pts: vec![],
tangent_geoms: vec![],
@@ -512,30 +512,6 @@ fn wipeout_axis_length(axis: &codec::types::Vector3, size: f64) -> f64 {
(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z).sqrt() * size.abs()
}
fn wipeout_pick_rings(wipeout: &Wipeout) -> Vec<Vec<[f64; 3]>> {
let boundary = wipeout_boundary(wipeout);
if wipeout_is_polygonal(wipeout)
&& matches!(
wipeout.clip_mode,
codec::entities::WipeoutClipMode::Inside
)
{
vec![
image_corners(
&wipeout.insertion_point,
&wipeout.u_vector,
&wipeout.v_vector,
wipeout.size.x,
wipeout.size.y,
)
.to_vec(),
boundary,
]
} else {
vec![boundary]
}
}
fn wipeout_world_to_clip(wipeout: &Wipeout, world: [f64; 3]) -> Option<codec::types::Vector2> {
let [x, y] = wipeout_plane(wipeout).project(world)?;
Some(codec::types::Vector2::new(
@@ -553,10 +529,8 @@ impl RenderConvertible for Wipeout {
}
Some(RenderEntity {
pick_tris: kernel::space::polygon::triangulate_rings(
&wipeout_pick_rings(self),
kernel::geom2d::Tolerance::default(),
),
// Picked on its frame only; the masked area selects nothing.
pick_tris: vec![],
object: RenderObject::Lines(pts),
snap_pts: boundary
.iter()
+1
View File
@@ -2036,6 +2036,7 @@ pub fn tessellate_table(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+4 -5
View File
@@ -288,21 +288,20 @@ impl RenderConvertible for Underlay {
});
}
// Pick surface over the shown area so a click inside selects it.
let pick_ring: Vec<[f64; 3]> = if !clip.is_empty() && !self.clip_inverted {
// Picked on its frame only; the page itself selects nothing.
let key_ring: Vec<[f64; 3]> = if !clip.is_empty() && !self.clip_inverted {
clip.clone()
} else {
quad.map(|q| q.to_vec()).unwrap_or_default()
};
let pick_tris = crate::entities::mesh::triangulate_planar(&pick_ring);
let mut snap_pts = vec![insertion_snap];
snap_pts.extend(crate::scene::model::pdf_vector::underlay_snap_points(self, document));
Some(RenderEntity {
pick_tris,
pick_tris: vec![],
object: RenderObject::Lines(pts),
snap_pts,
tangent_geoms: vec![],
key_vertices: pick_ring,
key_vertices: key_ring,
fill_tris: vec![],
})
}
+6
View File
@@ -1197,6 +1197,12 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"Frames & Print" => Some(("insert", "frames-print")),
"Frames Off" => Some(("insert", "frames-off")),
"Frames On" => Some(("insert", "frames-on")),
"Hide frames" => Some(("insert", "hide-frames")),
"Display and plot frames" => Some(("insert", "display-and-plot-frames")),
"Display but don't plot frames" => Some(("insert", "display-not-plot-frames")),
"*Frames vary*" => Some(("insert", "frames-vary")),
"Regenerating model." => Some(("insert", "regenerating-model")),
"Requires an integer between 0 and 2." => Some(("insert", "requires-int-0-2")),
"Frames On, Not Plotted" => Some(("draw", "frames-on-not-plotted")),
"Freeze" => Some(("layers", "freeze")),
"From DXF" => Some(("properties", "from-dxf")),
+2
View File
@@ -340,6 +340,7 @@ impl CadCommand for AlignedDimensionCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -432,6 +433,7 @@ fn preview_aligned(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -1249,6 +1249,7 @@ fn preview_wire(points: Vec<DVec3>) -> WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -108,6 +108,7 @@ impl DataLinkPlaceCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -332,6 +332,7 @@ fn preview_line(far_chord: Vec3, chord: Vec3, text: Vec3) -> WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -729,6 +729,7 @@ fn preview_for_dimension(dimension: &Dimension) -> WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -480,6 +480,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -444,6 +444,7 @@ fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -561,6 +561,7 @@ fn preview_wire(points: Vec<DVec3>) -> WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -773,6 +773,7 @@ fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -497,6 +497,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -289,6 +289,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -350,6 +350,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -315,6 +315,7 @@ impl TableCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -88,6 +88,7 @@ impl CadCommand for ToleranceCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -206,6 +206,7 @@ impl CadCommand for AttdefCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -187,6 +187,7 @@ impl MlineCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: true,
+1
View File
@@ -176,6 +176,7 @@ impl CadCommand for ImageCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+2
View File
@@ -93,6 +93,7 @@ impl CadCommand for RayCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -378,6 +379,7 @@ impl CadCommand for XLineCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -375,6 +375,7 @@ impl CadCommand for AreaCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -95,6 +95,7 @@ impl CadCommand for DistCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -77,6 +77,7 @@ impl MeasureGeomCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -4151,6 +4151,7 @@ fn preview_wire(points: Vec<[f32; 3]>, color: [f32; 4], name: &str) -> WireModel
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+6 -3
View File
@@ -62,9 +62,12 @@ impl CadModule for InsertModule {
label: "",
icon: FRAMES_ICON,
items: vec![
("FRAMES0", "Frames Off", FRAMES_ICON),
("FRAMES1", "Frames On", FRAMES_ICON),
("FRAMES2", "Frames On, Not Plotted", FRAMES_ICON),
("FRAMES0", "Hide frames", FRAMES_ICON),
("FRAMES1", "Display and plot frames", FRAMES_ICON),
("FRAMES2", "Display but don't plot frames", FRAMES_ICON),
// Shown (not selectable) while the frame
// variables differ from each other.
("FRAMES3", "*Frames vary*", FRAMES_ICON),
],
default: "FRAMES1",
},
+1
View File
@@ -3647,6 +3647,7 @@ fn wire(name: &str, points: Vec<[f32; 3]>) -> WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -61,6 +61,7 @@ impl CadCommand for PlotWindowCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+1
View File
@@ -143,6 +143,7 @@ impl CadCommand for ZoomWindowCommand {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+2
View File
@@ -1750,6 +1750,7 @@ impl Batches {
world_width: b.world_width,
depth_override: b.local_depth,
display_visible: !b.hide_unselected || selected,
snap_only: false,
plot_visible: b.plot_visible,
fill_is_3d: b.fill_is_3d,
fill_is_2d_solid: b.fill_is_2d_solid,
@@ -2516,6 +2517,7 @@ fn emit_wire(
world_width: 0.0,
depth_override: local_depth,
display_visible: !lw.hide_unselected || ctx.selected,
snap_only: false,
plot_visible: lw.plot_visible,
fill_is_3d: false,
fill_is_2d_solid: false,
+6
View File
@@ -931,6 +931,7 @@ fn tessellate_entity_inner(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1176,6 +1177,7 @@ fn tessellate_entity_inner(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1296,6 +1298,8 @@ fn tessellate_entity_inner(
wire.points_low = points_low;
wire.display_visible = false;
wire.plot_visible = false;
// Snaps find it; a click on the page does not select the underlay.
wire.snap_only = true;
if let Some((center, radius)) = piece.circle {
wire.tangent_geoms = vec![crate::scene::model::wire_model::TangentGeom::PlanarCircle {
center,
@@ -1549,6 +1553,7 @@ fn lod_stub_wire(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1645,6 +1650,7 @@ fn lod_stub_wire_3d(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+21
View File
@@ -373,6 +373,7 @@ fn split_mixed_polyline(
world_width,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -508,6 +509,7 @@ fn split_mixed_polyline(
world_width: line_world_width,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -571,6 +573,7 @@ fn point_cloud_wires(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -826,6 +829,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: true,
@@ -990,6 +994,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1382,6 +1387,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1439,6 +1445,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1493,6 +1500,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1528,6 +1536,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1593,6 +1602,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1640,6 +1650,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1684,6 +1695,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1721,6 +1733,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1792,6 +1805,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1951,6 +1965,7 @@ pub fn tessellate(
world_width: polyline_band_width(entity, document.header.fill_mode),
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d,
fill_is_2d_solid: false,
@@ -2022,6 +2037,7 @@ pub fn tessellate(
render_instance: None,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
});
}
@@ -2035,6 +2051,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -2094,6 +2111,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -2183,6 +2201,7 @@ pub fn tessellate(
world_width: polyline_band_width(entity, document.header.fill_mode),
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -2261,6 +2280,7 @@ pub fn tessellate(
world_width,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -2370,6 +2390,7 @@ pub fn tessellate(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+57 -13
View File
@@ -5,19 +5,52 @@ use codec::{CadDocument, EntityType, Handle};
pub(crate) enum FrameKind {
Image,
Pdf,
Dwf,
Dgn,
Wipeout,
Xclip,
PointCloudClip,
}
pub(crate) const ALL_KINDS: [FrameKind; 5] = [
pub(crate) const ALL_KINDS: [FrameKind; 7] = [
FrameKind::Image,
FrameKind::Pdf,
FrameKind::Dwf,
FrameKind::Dgn,
FrameKind::Wipeout,
FrameKind::Xclip,
FrameKind::PointCloudClip,
];
/// IMAGEFRAME of the user profile: what a drawing without its own image
/// variables shows, and what a new drawing starts with. Setting IMAGEFRAME
/// updates it, so it carries over to new drawings and later sessions.
static PROFILE_IMAGE_FRAME: std::sync::atomic::AtomicI16 = std::sync::atomic::AtomicI16::new(1);
pub(crate) fn profile_image_mode() -> i16 {
PROFILE_IMAGE_FRAME.load(std::sync::atomic::Ordering::Relaxed)
}
pub(crate) fn set_profile_image_mode(value: i16) {
PROFILE_IMAGE_FRAME.store(value.clamp(0, 2), std::sync::atomic::Ordering::Relaxed);
}
/// The image variables store the frame as bits: 1 shown, 2 not plotted, so
/// "shown but not plotted" is 3. Any other value is not one the variable
/// can hold, and the profile setting applies.
fn image_mode_from_flags(flags: i16) -> Option<i16> {
match flags {
0 => Some(0),
1 => Some(1),
3 => Some(2),
_ => None,
}
}
fn image_flags_from_mode(mode: i16) -> i16 {
if mode == 2 { 3 } else { mode }
}
fn root_entry(document: &CadDocument, name: &str) -> Option<Handle> {
let from_dictionary = |handle| match document.objects.get(&handle) {
Some(ObjectType::Dictionary(dictionary)) => dictionary
@@ -58,12 +91,15 @@ pub(crate) fn mode(document: &CadDocument, kind: FrameKind) -> i16 {
match kind {
FrameKind::Image => raster_variables_handle(document)
.and_then(|handle| match document.objects.get(&handle) {
Some(ObjectType::RasterVariables(value)) => Some(value.display_image_frame),
Some(ObjectType::RasterVariables(value)) => {
image_mode_from_flags(value.display_image_frame)
}
_ => None,
})
.unwrap_or(1)
.clamp(0, 2),
.unwrap_or_else(profile_image_mode),
FrameKind::Pdf => drawing_mode(document, "PDFFRAME", 1),
FrameKind::Dwf => document.header.dwf_frame.clamp(0, 2),
FrameKind::Dgn => document.header.dgn_frame.clamp(0, 2),
FrameKind::Wipeout => crate::io::drawing_variable(document, "WIPEOUTFRAME")
.and_then(|value| value.parse::<i16>().ok())
.or_else(|| {
@@ -115,8 +151,9 @@ fn set_image_mode(document: &mut CadDocument, value: i16) {
});
if let Some(ObjectType::RasterVariables(variables)) = document.objects.get_mut(&handle) {
variables.owner = owner;
variables.display_image_frame = value;
variables.display_image_frame = image_flags_from_mode(value);
}
set_profile_image_mode(value);
attach_root_entry(document, "ACAD_IMAGE_VARS", handle);
}
@@ -145,6 +182,8 @@ pub(crate) fn set_mode(document: &mut CadDocument, kind: FrameKind, value: i16)
match kind {
FrameKind::Image => set_image_mode(document, value),
FrameKind::Pdf => crate::io::set_drawing_variable(document, "PDFFRAME", &value.to_string()),
FrameKind::Dwf => document.header.dwf_frame = value,
FrameKind::Dgn => document.header.dgn_frame = value,
FrameKind::Wipeout => set_wipeout_mode(document, value),
FrameKind::Xclip => document.header.xclip_frame = value,
FrameKind::PointCloudClip => crate::io::set_drawing_variable(
@@ -168,6 +207,8 @@ pub(crate) fn kind_for_name(name: &str) -> Option<FrameKind> {
match name {
"IMAGEFRAME" => Some(FrameKind::Image),
"PDFFRAME" => Some(FrameKind::Pdf),
"DWFFRAME" => Some(FrameKind::Dwf),
"DGNFRAME" => Some(FrameKind::Dgn),
"WIPEOUTFRAME" => Some(FrameKind::Wipeout),
"XCLIPFRAME" => Some(FrameKind::Xclip),
"POINTCLOUDCLIPFRAME" => Some(FrameKind::PointCloudClip),
@@ -178,23 +219,26 @@ pub(crate) fn kind_for_name(name: &str) -> Option<FrameKind> {
pub(crate) fn entity_kind(entity: &EntityType) -> Option<FrameKind> {
match entity {
EntityType::RasterImage(_) => Some(FrameKind::Image),
EntityType::Underlay(underlay)
if matches!(underlay.underlay_type, codec::entities::UnderlayType::Pdf) =>
{
Some(FrameKind::Pdf)
}
EntityType::Underlay(underlay) => Some(underlay_kind(underlay)),
EntityType::Wipeout(_) => Some(FrameKind::Wipeout),
_ => None,
}
}
fn underlay_kind(underlay: &codec::entities::Underlay) -> FrameKind {
match underlay.underlay_type {
codec::entities::UnderlayType::Pdf => FrameKind::Pdf,
codec::entities::UnderlayType::Dwf => FrameKind::Dwf,
codec::entities::UnderlayType::Dgn => FrameKind::Dgn,
}
}
pub(crate) fn affected(entity: &EntityType, kind: FrameKind) -> bool {
match kind {
FrameKind::Image => matches!(entity, EntityType::RasterImage(_)),
FrameKind::Pdf => matches!(
FrameKind::Pdf | FrameKind::Dwf | FrameKind::Dgn => matches!(
entity,
EntityType::Underlay(underlay)
if matches!(underlay.underlay_type, codec::entities::UnderlayType::Pdf)
EntityType::Underlay(underlay) if underlay_kind(underlay) == kind
),
FrameKind::Wipeout => matches!(entity, EntityType::Wipeout(_)),
FrameKind::Xclip => matches!(entity, EntityType::Insert(_)),
+5
View File
@@ -506,6 +506,9 @@ pub struct WireModel {
pub depth_override: Option<f32>,
/// Whether this wire is drawn in the normal viewport pass.
pub display_visible: bool,
/// Geometry that only object snaps use (the content of an underlay):
/// never drawn, plotted or picked.
pub snap_only: bool,
/// Whether this wire is included in plotted output.
pub plot_visible: bool,
/// `true` when [`fill_tris`] is a real 3-D surface (PolyfaceMesh /
@@ -580,6 +583,7 @@ impl WireModel {
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -1009,6 +1013,7 @@ impl Default for WireModel {
fill_tris_low: Vec::new(),
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+90
View File
@@ -249,6 +249,9 @@ pub fn click_hit<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(segment.wire) else {
continue;
};
if wire.snap_only {
continue;
}
if wire.point_marker.is_some() {
continue;
}
@@ -277,6 +280,9 @@ pub fn click_hit<'a, W: WireSource + ?Sized>(
} else {
// Q: lazy projection — no Vec allocation per wire; NaN resets the segment chain.
for wire in wires.iter() {
if wire.snap_only {
continue;
}
if wire.point_marker.is_some() {
continue;
}
@@ -329,6 +335,9 @@ pub fn click_hit<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(triangle.wire) else {
continue;
};
if wire.snap_only {
continue;
}
if let Some(depth) = triangle_ref_hit_depth(
cursor,
wire,
@@ -345,6 +354,9 @@ pub fn click_hit<'a, W: WireSource + ?Sized>(
}
} else {
for wire in wires.iter() {
if wire.snap_only {
continue;
}
if wire.fill_tris.is_empty() {
continue;
}
@@ -381,6 +393,9 @@ pub fn click_hit<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(triangle.wire) else {
continue;
};
if wire.snap_only {
continue;
}
if let Some(depth) = triangle_ref_hit_depth(
cursor,
wire,
@@ -397,6 +412,9 @@ pub fn click_hit<'a, W: WireSource + ?Sized>(
}
} else {
for wire in wires.iter() {
if wire.snap_only {
continue;
}
if wire.pick_tris.is_empty() {
continue;
}
@@ -438,6 +456,9 @@ pub fn click_hit<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(glyph.wire) else {
continue;
};
if wire.snap_only {
continue;
}
let start = glyph.start as usize;
let Some(vertices) = wire.text_verts.get(start..start + 6) else {
continue;
@@ -451,6 +472,9 @@ pub fn click_hit<'a, W: WireSource + ?Sized>(
}
} else {
for wire in wires.iter() {
if wire.snap_only {
continue;
}
if wire.text_verts.is_empty() {
continue;
}
@@ -516,6 +540,9 @@ pub fn click_hits_all<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(segment.wire) else {
continue;
};
if wire.snap_only {
continue;
}
if wire.point_marker.is_some() {
continue;
}
@@ -550,6 +577,9 @@ pub fn click_hits_all<'a, W: WireSource + ?Sized>(
}));
} else {
for wire in wires.iter() {
if wire.snap_only {
continue;
}
if wire.point_marker.is_some() {
continue;
}
@@ -593,6 +623,9 @@ pub fn click_hits_all<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(triangle.wire) else {
continue;
};
if wire.snap_only {
continue;
}
if hits.iter().any(|&(_, name)| name == wire.name) {
matched.insert(triangle.wire);
continue;
@@ -614,6 +647,9 @@ pub fn click_hits_all<'a, W: WireSource + ?Sized>(
}
} else {
for wire in wires.iter() {
if wire.snap_only {
continue;
}
if wire.fill_tris.is_empty() || hits.iter().any(|&(_, name)| name == wire.name) {
continue;
}
@@ -647,6 +683,9 @@ pub fn click_hits_all<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(triangle.wire) else {
continue;
};
if wire.snap_only {
continue;
}
if hits.iter().any(|&(_, name)| name == wire.name) {
matched.insert(triangle.wire);
continue;
@@ -668,6 +707,9 @@ pub fn click_hits_all<'a, W: WireSource + ?Sized>(
}
} else {
for wire in wires.iter() {
if wire.snap_only {
continue;
}
if wire.pick_tris.is_empty() || hits.iter().any(|&(_, name)| name == wire.name) {
continue;
}
@@ -697,6 +739,9 @@ pub fn click_hits_all<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(glyph.wire) else {
continue;
};
if wire.snap_only {
continue;
}
if hits.iter().any(|&(_, name)| name == wire.name) {
matched.insert(glyph.wire);
continue;
@@ -712,6 +757,9 @@ pub fn click_hits_all<'a, W: WireSource + ?Sized>(
}
} else {
for wire in wires.iter() {
if wire.snap_only {
continue;
}
if wire.text_verts.is_empty() || hits.iter().any(|&(_, name)| name == wire.name) {
continue;
}
@@ -1252,6 +1300,9 @@ fn indexed_box_crossing_hits<'a, W: WireSource + ?Sized>(
// path below run identical code.
let hit_name = |segment: &SegmentRef| -> Option<&'a str> {
let wire = wires.source_wire(segment.wire)?;
if wire.snap_only {
return None;
}
let start = segment.start as usize;
if start + 1 >= wire.points.len() {
return None;
@@ -1310,6 +1361,9 @@ fn indexed_box_crossing_hits<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(triangle.wire) else {
continue;
};
if wire.snap_only {
continue;
}
if already(&wire_hit, triangle.wire) {
continue;
}
@@ -1334,6 +1388,9 @@ fn indexed_box_crossing_hits<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(glyph.wire) else {
continue;
};
if wire.snap_only {
continue;
}
if already(&wire_hit, glyph.wire) {
continue;
}
@@ -1355,6 +1412,9 @@ fn indexed_box_crossing_hits<'a, W: WireSource + ?Sized>(
// Degenerate point-only wires have no indexed segment or surface primitive.
for wire in wires.iter() {
if wire.snap_only {
continue;
}
if wire.points.len() >= 2
|| !wire.fill_tris.is_empty()
|| !wire.pick_tris.is_empty()
@@ -1411,6 +1471,9 @@ pub fn poly_fence_hit<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(segment.wire) else {
continue;
};
if wire.snap_only {
continue;
}
let start = segment.start as usize;
if start + 1 >= wire.points.len() || seen.contains(wire.name.as_str()) {
continue;
@@ -1442,6 +1505,9 @@ pub fn poly_fence_hit<'a, W: WireSource + ?Sized>(
return out;
}
for wire in wires.iter() {
if wire.snap_only {
continue;
}
if wire.points.len() < 2 {
continue;
}
@@ -1480,6 +1546,9 @@ fn indexed_polygon_crossing_hits<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(segment.wire) else {
continue;
};
if wire.snap_only {
continue;
}
let start = segment.start as usize;
if start + 1 >= wire.points.len() {
continue;
@@ -1524,6 +1593,9 @@ fn indexed_polygon_crossing_hits<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(triangle.wire) else {
continue;
};
if wire.snap_only {
continue;
}
if seen.contains(wire.name.as_str()) {
continue;
}
@@ -1546,6 +1618,9 @@ fn indexed_polygon_crossing_hits<'a, W: WireSource + ?Sized>(
let Some(wire) = wires.source_wire(glyph.wire) else {
continue;
};
if wire.snap_only {
continue;
}
if seen.contains(wire.name.as_str()) {
continue;
}
@@ -1565,6 +1640,9 @@ fn indexed_polygon_crossing_hits<'a, W: WireSource + ?Sized>(
}
}
for wire in wires.iter() {
if wire.snap_only {
continue;
}
if wire.points.len() >= 2
|| !wire.fill_tris.is_empty()
|| !wire.pick_tris.is_empty()
@@ -1644,6 +1722,9 @@ pub fn box_hit<'a, W: WireSource + ?Sized>(
let mut out = Vec::new();
let mut seen = HashSet::default();
for wire in wires.iter() {
if wire.snap_only {
continue;
}
// Fallback: when wire has no line geometry (e.g. greek text emits
// only fill_tris) treat the AABB rectangle as the hit-test shape
// so low-LOD text stays selectable. See #19.
@@ -1729,6 +1810,9 @@ pub fn box_hit<'a, W: WireSource + ?Sized>(
let mut seen = HashSet::default();
for wire in wires.iter() {
if wire.snap_only {
continue;
}
let name = wire.name.as_str();
if disqualified.contains(name) {
continue;
@@ -1838,6 +1922,9 @@ pub fn poly_hit<'a, W: WireSource + ?Sized>(
let mut out = Vec::new();
let mut seen = HashSet::default();
for wire in wires.iter() {
if wire.snap_only {
continue;
}
// Same AABB fallback as `box_hit`: when a wire has no line
// geometry (e.g. greek-LOD text emits only fill_tris) treat the
// AABB rectangle as the hit-test shape so low-LOD text stays
@@ -1925,6 +2012,9 @@ pub fn poly_hit<'a, W: WireSource + ?Sized>(
let mut seen = HashSet::default();
for wire in wires.iter() {
if wire.snap_only {
continue;
}
let aabb_pts: Vec<[f32; 3]>;
let empty_pts: [[f32; 3]; 0] = [];
let pts: &[[f32; 3]] = if !wire.points.is_empty() {
+1
View File
@@ -551,6 +551,7 @@ pub fn frame_wire(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+2
View File
@@ -316,6 +316,7 @@ pub fn apply_along(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
@@ -365,6 +366,7 @@ pub fn apply_along(
world_width: 0.0,
depth_override: None,
display_visible: true,
snap_only: false,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
+12 -3
View File
@@ -477,6 +477,14 @@ impl Ribbon {
self.close_dropdown();
}
/// Show `cmd` as the current item of a dropdown that mirrors a drawing
/// setting, without running it.
pub fn set_dropdown_current(&mut self, dropdown_id: &'static str, cmd: &'static str) {
if self.last_cmd.get(dropdown_id) != Some(&cmd) {
self.last_cmd.insert(dropdown_id, cmd);
}
}
// ── View ──────────────────────────────────────────────────────────────
pub fn view(
@@ -873,6 +881,7 @@ impl Ribbon {
.iter()
.map(|(cmd, label, item_icon)| {
let is_current = *cmd == last_cmd;
let disabled = widgets::is_disabled_item(cmd);
let checkmark: Element<'_, Message> =
crate::ui::icons::themed_check_cell(is_current);
let icon_el: Element<Message> =
@@ -884,7 +893,7 @@ impl Ribbon {
.size(11)
.wrapping(iced::advanced::text::Wrapping::None)
.style(move |theme: &Theme| iced::widget::text::Style {
color: (!is_current).then_some(
color: (!is_current || disabled).then_some(
theme
.palette()
.background
@@ -899,10 +908,10 @@ impl Ribbon {
.spacing(4)
.align_y(iced::Center),
)
.on_press(Message::DropdownSelectItem {
.on_press_maybe((!disabled).then_some(Message::DropdownSelectItem {
dropdown_id: dd_id,
cmd: *cmd,
})
}))
.style(popup_row_style)
.width(Fill)
.padding([4, 10])
+14 -4
View File
@@ -94,6 +94,11 @@ thread_local! {
RefCell::new(HashMap::default());
}
/// Dropdown items that report a state and cannot be picked.
pub(super) fn is_disabled_item(cmd: &str) -> bool {
cmd == "FRAMES3"
}
fn ribbon_label_bounds(
renderer: &iced::Renderer,
label: &str,
@@ -678,13 +683,18 @@ pub(super) fn render_small<'a>(
container(make_icon(cur_icon, SMALL_ICON)).width(Length::Fixed(SMALL_W)),
text(localized_label.clone()).size(10).wrapping(advanced_text::Wrapping::None),
].spacing(3).align_y(iced::Center);
// A state that is only shown (not chosen) opens the list instead.
let face_btn = button(face)
.on_press(Message::RibbonToolClick {
tool_id: last.to_string(),
event: ModuleEvent::Command(last.to_string()),
.on_press(if is_disabled_item(last) {
Message::ToggleRibbonDropdown(id.to_string())
} else {
Message::RibbonToolClick {
tool_id: last.to_string(),
event: ModuleEvent::Command(last.to_string()),
}
})
.style(move |theme: &Theme, status| tool_btn_style(theme, active, status))
.width(Length::Fixed(LABELED_SMALL_W)).height(ROW_H).padding([3, 4]);
.width(Length::Shrink).height(ROW_H).padding([3, 4]);
let arrow = button(container(icons::themed_arrow_down(8.0))
.width(Fill).height(Fill).align_x(iced::Center).align_y(iced::Center))
.on_press(Message::ToggleRibbonDropdown(id.to_string()))