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 <noreply@anthropic.com>
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

+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(_)),