DWF and DGN underlays: attach, display, snaps, frames and references

DWFATTACH / -DWFATTACH and DGNATTACH / -DGNATTACH attach a DWF / DWFx sheet
or a DGN (V7 or V8) model with the reference's prompts; ATTACH routes the
file types to them. The sheet or model is read into vector paths, drawn as a
transparent underlay raster (contrast, fade, monochrome and background
adjustment apply) and offers its geometry to object snaps. Definitions are
kept in ACAD_DWFDEFINITIONS / ACAD_DGNDEFINITIONS, the External References
palette lists and reloads them, and image textures get line-keeping mip
levels so thin underlay lines stay visible when zoomed out.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-09-28 12:53:25 +03:00
1 parent 7f8f8457c7
commit cb7860e50e
46 files changed
+2723 -129

No files matched your search

+2 -2
View File
@@ -613,12 +613,12 @@ impl OpenCADStudio {
crate::app::commands::pdf_import::PdfImportSource::File(import),
);
}
CmdResult::AttachPdfPages { path, pages } => {
CmdResult::AttachPdfPages { kind, path, pages } => {
let label = self.history_label_from_active_cmd(i, "PDFATTACH");
self.tabs[i].scene.clear_preview_wire();
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.attach_pdf_pages(i, label, &path, pages);
self.attach_pdf_pages(i, label, kind, &path, pages);
self.restore_pre_cmd_tangent();
}
CmdResult::PdfImport(request) => {
+3 -1
View File
@@ -218,14 +218,16 @@ impl OpenCADStudio {
&mut self,
i: usize,
label: String,
kind: codec::entities::UnderlayType,
path: &str,
pages: Vec<(String, codec::EntityType)>,
) {
let pending = self.begin_undo(i, label, pages.len(), false);
for (page, mut entity) in pages {
let definition = crate::modules::insert::pdf_attach::ensure_pdf_definition(
let definition = crate::modules::insert::pdf_attach::ensure_underlay_definition(
&mut self.tabs[i].scene.document,
kind,
path,
&page,
);
+5 -1
View File
@@ -1022,6 +1022,8 @@ impl OpenCADStudio {
| "DWFFRAME"
| "DGNFRAME"
| "PDFOSNAP"
| "DWFOSNAP"
| "DGNOSNAP"
| "UOSNAP"
| "PDFIMPORTMODE"
| "PDFIMPORTFILTER"
@@ -1181,7 +1183,9 @@ impl OpenCADStudio {
}
return Some(self.finish_dispatch(cmd));
}
if matches!(name.as_str(), "PDFOSNAP" | "UOSNAP") {
// ponytail: one flag for every underlay kind; split it if a drawing
// needs PDF snaps on while DWF/DGN snaps are off.
if matches!(name.as_str(), "PDFOSNAP" | "DWFOSNAP" | "DGNOSNAP" | "UOSNAP") {
let current = i16::from(crate::scene::model::pdf_vector::pdf_osnap());
if let Some(value) = &value {
match value.parse::<i16>().ok().filter(|value| (0..=1).contains(value)) {
+70 -1
View File
@@ -12,6 +12,26 @@ use crate::modules::insert::xref_cmd::display_path;
use crate::ui::window::xref_attach::{XrefAttachMsg, XrefAttachState};
impl OpenCADStudio {
/// DWFATTACH / DGNATTACH on the command line: with a file, from its
/// sheet / model prompt; without one, from the path prompt.
pub(super) fn start_underlay_attach(
&mut self,
i: usize,
kind: codec::entities::UnderlayType,
file: &str,
) {
use crate::command::CadCommand;
let insunits = self.tabs[i].scene.document.header.insertion_units;
let mut command = crate::modules::insert::pdf_attach::PdfAttachCommand::for_kind(kind, insunits);
if !file.trim().is_empty() {
if let Some(crate::command::CmdResult::ReportError(message)) = command.on_text_input(file) {
self.command_line.push_error(&message);
}
}
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
}
/// 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();
@@ -21,6 +41,22 @@ impl OpenCADStudio {
};
match (verb.as_str(), rest.is_empty()) {
("ATTACH", true) => Some(Task::done(Message::AttachPick)),
("DWFATTACH", true) => Some(Task::done(Message::UnderlayAttachPick(
codec::entities::UnderlayType::Dwf,
))),
("DGNATTACH", true) => Some(Task::done(Message::UnderlayAttachPick(
codec::entities::UnderlayType::Dgn,
))),
// A file after DWFATTACH / DGNATTACH, or the - forms, go on the
// command line.
("DWFATTACH" | "-DWFATTACH", _) => {
self.start_underlay_attach(i, codec::entities::UnderlayType::Dwf, rest);
Some(Task::none())
}
("DGNATTACH" | "-DGNATTACH", _) => {
self.start_underlay_attach(i, codec::entities::UnderlayType::Dgn, rest);
Some(Task::none())
}
("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
@@ -299,6 +335,26 @@ impl OpenCADStudio {
/// 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::UnderlayAttachPick(kind) => Task::perform(
async move {
let (title, filter, extensions): (&str, &str, &[&str]) = match kind {
codec::entities::UnderlayType::Dgn => {
("Select DGN File", "DGN Files", &["dgn"])
}
_ => ("Select DWF File", "DWF Files", &["dwf", "dwfx"]),
};
let handle = crate::sys::file_dialog()
.set_title(crate::t!(title).as_ref())
.add_filter(crate::t!(filter).as_ref(), extensions)
.pick_file()
.await;
match handle {
Some(h) => Ok(crate::sys::handle_path(&h)),
None => Err("Cancelled".to_string()),
}
},
Message::AttachPickResult,
),
Message::AttachPick => Task::perform(
async {
let handle = crate::sys::file_dialog()
@@ -306,11 +362,14 @@ impl OpenCADStudio {
.add_filter(
crate::t!("All Reference Files").as_ref(),
&[
"dwg", "dxf", "pdf", "png", "jpg", "jpeg", "bmp", "tif", "tiff",
"dwg", "dxf", "pdf", "dwf", "dwfx", "dgn", "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!("DWF Files").as_ref(), &["dwf", "dwfx"])
.add_filter(crate::t!("DGN Files").as_ref(), &["dgn"])
.add_filter(
crate::t!("Images").as_ref(),
&["png", "jpg", "jpeg", "bmp", "tif", "tiff"],
@@ -337,6 +396,16 @@ impl OpenCADStudio {
)))),
Err(e) => self.update(Message::PdfAttachPickResult(Err(e.to_string()))),
},
"dwf" | "dwfx" | "dgn" => {
let kind = if ext == "dgn" {
codec::entities::UnderlayType::Dgn
} else {
codec::entities::UnderlayType::Dwf
};
let i = self.active_tab;
self.start_underlay_attach(i, kind, &path.to_string_lossy());
Task::none()
}
"png" | "jpg" | "jpeg" | "bmp" | "tif" | "tiff" => {
let result = image::image_dimensions(&path)
.map(|(w, h)| (path.clone(), w, h))
+2
View File
@@ -3851,6 +3851,8 @@ pub enum Message {
XAttachPickResult(Result<std::path::PathBuf, String>),
/// ATTACH: pick a drawing, image or PDF to reference.
AttachPick,
/// DWFATTACH / DGNATTACH: pick the file (the result goes the ATTACH way).
UnderlayAttachPick(codec::entities::UnderlayType),
/// The Reference slide-out's xref fading: amount dragged, drag done,
/// switch.
XrefFadeSlide(u8),
+3 -4
View File
@@ -867,8 +867,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
Some(entity.common().handle)
}
codec::EntityType::Underlay(underlay)
if matches!(underlay.underlay_type, codec::entities::UnderlayType::Pdf)
&& self.tabs[i].xref_unloaded.is_unloaded(underlay.definition_handle.value()) =>
if self.tabs[i].xref_unloaded.is_unloaded(underlay.definition_handle.value()) =>
{
Some(entity.common().handle)
}
@@ -1021,8 +1020,8 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
"XREF: cannot reload nested reference '{}'. Reload it in its host drawing.",
name
).as_ref());
} else if *kind == crate::io::xref_model::RefKind::Pdf {
// A PDF reloads by clearing its definition's unloaded state.
} else if *kind == crate::io::xref_model::RefKind::Underlay {
// An underlay reloads by clearing its definition's unloaded state.
for (key, row_name, row_kind, _) in &picked {
if row_kind != kind || row_name != name {
continue;
+1
View File
@@ -1426,6 +1426,7 @@ impl OpenCADStudio {
Task::none()
}
message @ (Message::AttachPick
| Message::UnderlayAttachPick(_)
| Message::AttachPickResult(_)
| Message::XrefAttach(_)
| Message::XrefAttachBrowseResult(_)) => self.update_xref_attach(message),