forked from 1b-agent/opencadstudio-upstream
- 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]>
256 lines
9.2 KiB
Rust
256 lines
9.2 KiB
Rust
use codec::objects::{ObjectType, RasterVariables, WipeoutVariables};
|
|
use codec::{CadDocument, EntityType, Handle};
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub(crate) enum FrameKind {
|
|
Image,
|
|
Pdf,
|
|
Dwf,
|
|
Dgn,
|
|
Wipeout,
|
|
Xclip,
|
|
PointCloudClip,
|
|
}
|
|
|
|
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
|
|
.entries
|
|
.iter()
|
|
.find(|(key, _)| key.eq_ignore_ascii_case(name))
|
|
.map(|(_, handle)| *handle),
|
|
_ => None,
|
|
};
|
|
from_dictionary(document.header.named_objects_dict_handle).or_else(|| {
|
|
document.objects.values().find_map(|object| match object {
|
|
ObjectType::Dictionary(dictionary) if dictionary.owner.is_null() => {
|
|
from_dictionary(dictionary.handle)
|
|
}
|
|
_ => None,
|
|
})
|
|
})
|
|
}
|
|
|
|
fn raster_variables_handle(document: &CadDocument) -> Option<Handle> {
|
|
root_entry(document, "ACAD_IMAGE_VARS")
|
|
.filter(|handle| matches!(document.objects.get(handle), Some(ObjectType::RasterVariables(_))))
|
|
}
|
|
|
|
fn wipeout_variables_handle(document: &CadDocument) -> Option<Handle> {
|
|
root_entry(document, "ACAD_WIPEOUT_VARS")
|
|
.filter(|handle| matches!(document.objects.get(handle), Some(ObjectType::WipeoutVariables(_))))
|
|
}
|
|
|
|
fn drawing_mode(document: &CadDocument, name: &str, default: i16) -> i16 {
|
|
crate::io::drawing_variable(document, name)
|
|
.and_then(|value| value.parse::<i16>().ok())
|
|
.unwrap_or(default)
|
|
.clamp(0, 2)
|
|
}
|
|
|
|
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)) => {
|
|
image_mode_from_flags(value.display_image_frame)
|
|
}
|
|
_ => None,
|
|
})
|
|
.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(|| {
|
|
wipeout_variables_handle(document).and_then(|handle| {
|
|
match document.objects.get(&handle) {
|
|
Some(ObjectType::WipeoutVariables(value)) => Some(value.display_frame),
|
|
_ => None,
|
|
}
|
|
})
|
|
})
|
|
.unwrap_or(1)
|
|
.clamp(0, 2),
|
|
FrameKind::Xclip => document.header.xclip_frame.clamp(0, 2),
|
|
FrameKind::PointCloudClip => drawing_mode(document, "POINTCLOUDCLIPFRAME", 2),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn master_mode(document: &CadDocument) -> i16 {
|
|
let first = mode(document, ALL_KINDS[0]);
|
|
ALL_KINDS
|
|
.iter()
|
|
.skip(1)
|
|
.all(|kind| mode(document, *kind) == first)
|
|
.then_some(first)
|
|
.unwrap_or(3)
|
|
}
|
|
|
|
fn attach_root_entry(document: &mut CadDocument, name: &str, handle: Handle) {
|
|
let root = crate::scene::annotative::root_named_dict_handle(document);
|
|
if let Some(ObjectType::Dictionary(dictionary)) = document.objects.get_mut(&root) {
|
|
dictionary
|
|
.entries
|
|
.retain(|(key, _)| !key.eq_ignore_ascii_case(name));
|
|
dictionary.add_entry(name, handle);
|
|
}
|
|
}
|
|
|
|
fn set_image_mode(document: &mut CadDocument, value: i16) {
|
|
let owner = crate::scene::annotative::root_named_dict_handle(document);
|
|
let handle = raster_variables_handle(document).unwrap_or_else(|| {
|
|
let handle = document.allocate_handle();
|
|
let mut variables = RasterVariables::new();
|
|
variables.handle = handle;
|
|
variables.owner = owner;
|
|
document
|
|
.objects
|
|
.insert(handle, ObjectType::RasterVariables(variables));
|
|
handle
|
|
});
|
|
if let Some(ObjectType::RasterVariables(variables)) = document.objects.get_mut(&handle) {
|
|
variables.owner = owner;
|
|
variables.display_image_frame = image_flags_from_mode(value);
|
|
}
|
|
set_profile_image_mode(value);
|
|
attach_root_entry(document, "ACAD_IMAGE_VARS", handle);
|
|
}
|
|
|
|
fn set_wipeout_mode(document: &mut CadDocument, value: i16) {
|
|
crate::io::set_drawing_variable(document, "WIPEOUTFRAME", &value.to_string());
|
|
let owner = crate::scene::annotative::root_named_dict_handle(document);
|
|
let handle = wipeout_variables_handle(document).unwrap_or_else(|| {
|
|
let handle = document.allocate_handle();
|
|
let mut variables = WipeoutVariables::new();
|
|
variables.handle = handle;
|
|
variables.owner = owner;
|
|
document
|
|
.objects
|
|
.insert(handle, ObjectType::WipeoutVariables(variables));
|
|
handle
|
|
});
|
|
if let Some(ObjectType::WipeoutVariables(variables)) = document.objects.get_mut(&handle) {
|
|
variables.owner = owner;
|
|
variables.display_frame = value;
|
|
}
|
|
attach_root_entry(document, "ACAD_WIPEOUT_VARS", handle);
|
|
}
|
|
|
|
pub(crate) fn set_mode(document: &mut CadDocument, kind: FrameKind, value: i16) {
|
|
let value = value.clamp(0, 2);
|
|
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(
|
|
document,
|
|
"POINTCLOUDCLIPFRAME",
|
|
&value.to_string(),
|
|
),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn set_master_mode(document: &mut CadDocument, value: i16) {
|
|
let value = value.clamp(0, 2);
|
|
for kind in ALL_KINDS {
|
|
if mode(document, kind) != value {
|
|
set_mode(document, kind, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
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),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn entity_kind(entity: &EntityType) -> Option<FrameKind> {
|
|
match entity {
|
|
EntityType::RasterImage(_) => Some(FrameKind::Image),
|
|
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 | FrameKind::Dwf | FrameKind::Dgn => matches!(
|
|
entity,
|
|
EntityType::Underlay(underlay) if underlay_kind(underlay) == kind
|
|
),
|
|
FrameKind::Wipeout => matches!(entity, EntityType::Wipeout(_)),
|
|
FrameKind::Xclip => matches!(entity, EntityType::Insert(_)),
|
|
FrameKind::PointCloudClip => matches!(
|
|
entity,
|
|
EntityType::Extended(extended)
|
|
if matches!(
|
|
&extended.data,
|
|
codec::entities::ExtendedEntityData::PointCloud(_)
|
|
| codec::entities::ExtendedEntityData::PointCloudEx(_)
|
|
)
|
|
),
|
|
}
|
|
}
|