forked from 1b-agent/opencadstudio-upstream
PDF underlays: attach prompts and definitions, display adjustments and clipping, Properties, snapping, PDFCLIP and PDFIMPORT
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cc9d5edbff
commit
0c50013ca7
50 files changed
+2770
-325
No files matched your search
@@ -660,8 +660,48 @@ impl OpenCADStudio {
|
||||
self.open_attedit_dialog();
|
||||
}
|
||||
|
||||
"PDFATTACH" => {
|
||||
return Some(Task::done(Message::PdfAttachPick));
|
||||
"PDFIMPORT" => {
|
||||
use crate::command::CadCommand;
|
||||
let command = crate::modules::insert::pdf_import::PdfImportCommand::new();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
"_PDFIMPORTFILE" => {
|
||||
return Some(Task::done(Message::PdfImportPick));
|
||||
}
|
||||
"PDFCLIP" | "CLIP" => {
|
||||
use crate::command::CadCommand;
|
||||
let command = crate::modules::insert::pdf_clip::PdfClipCommand::new(cmd == "CLIP");
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
// PDFATTACH picks the file; -PDFATTACH asks for it on the command
|
||||
// line. A path after either names the file at once.
|
||||
cmd if matches!(
|
||||
cmd.split_whitespace().next().map(|v| v.to_ascii_uppercase()).as_deref(),
|
||||
Some("PDFATTACH") | Some("-PDFATTACH")
|
||||
) =>
|
||||
{
|
||||
use crate::command::CadCommand;
|
||||
let (verb, rest) = cmd
|
||||
.split_once(char::is_whitespace)
|
||||
.map(|(verb, rest)| (verb.to_ascii_uppercase(), rest.trim()))
|
||||
.unwrap_or_else(|| (cmd.to_ascii_uppercase(), ""));
|
||||
if verb == "PDFATTACH" && rest.is_empty() {
|
||||
return Some(Task::done(Message::PdfAttachPick));
|
||||
}
|
||||
let insunits = self.tabs[i].scene.document.header.insertion_units;
|
||||
let mut command =
|
||||
crate::modules::insert::pdf_attach::PdfAttachCommand::new(insunits);
|
||||
if !rest.is_empty() {
|
||||
if let Some(crate::command::CmdResult::ReportError(message)) =
|
||||
command.on_text_input(rest)
|
||||
{
|
||||
self.command_line.push_error(&message);
|
||||
}
|
||||
}
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
"XATTACH" => {
|
||||
// Launch the file picker; XAttachPickResult will start the command.
|
||||
|
||||
@@ -1861,8 +1861,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
"POINTCLOUDATTACH" | "RECAP" | "SYNCPVIEWPORTS" | "UNDERLAYLAYERS"
|
||||
| "UOSNAP" => {
|
||||
"POINTCLOUDATTACH" | "RECAP" | "SYNCPVIEWPORTS" | "UNDERLAYLAYERS" => {
|
||||
self.command_line
|
||||
.push_info(crate::tf!("{cmd}: not yet implemented.").as_ref());
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ mod fileops;
|
||||
mod inquiry;
|
||||
mod layerprops;
|
||||
mod layers;
|
||||
pub(crate) mod pdf_import;
|
||||
mod plotvars;
|
||||
mod styleprops;
|
||||
mod view;
|
||||
@@ -606,6 +607,8 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"FRAME",
|
||||
"IMAGEFRAME",
|
||||
"PDFFRAME",
|
||||
"PDFOSNAP",
|
||||
"UOSNAP",
|
||||
"POINTCLOUDCLIPFRAME",
|
||||
"XCLIPFRAME",
|
||||
"WIPEOUTFRAME",
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
//! PDFIMPORT: adding the converted PDF content to the drawing as one undo
|
||||
//! step, with the layers and text styles it uses, and what then happens to
|
||||
//! the underlay (Keep / Detach / Unload).
|
||||
|
||||
use super::*;
|
||||
use crate::modules::insert::pdf_import::{self, ImportArea, PdfImportRequest, UnderlayMode};
|
||||
|
||||
/// What an import reads: a placed underlay, or a file imported at the
|
||||
/// origin at full size.
|
||||
pub(crate) enum PdfImportSource {
|
||||
Underlay(PdfImportRequest),
|
||||
File(String),
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
pub(crate) fn run_pdf_import(&mut self, i: usize, source: PdfImportSource) {
|
||||
use acadrust::objects::ObjectType;
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let (underlay, path, page, area, mode, handle) = match &source {
|
||||
PdfImportSource::Underlay(request) => {
|
||||
let Some(acadrust::EntityType::Underlay(underlay)) =
|
||||
document.get_entity(request.underlay)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(def) = crate::entities::underlay::definition(underlay, document) else {
|
||||
return;
|
||||
};
|
||||
if self.tabs[i]
|
||||
.scene
|
||||
.unloaded_underlay_definitions
|
||||
.contains(&underlay.definition_handle)
|
||||
{
|
||||
self.command_line
|
||||
.push_error("Cannot bind a PDF underlay that is unloaded.");
|
||||
return;
|
||||
}
|
||||
(
|
||||
underlay.clone(),
|
||||
def.file_path.clone(),
|
||||
crate::entities::underlay::page_of(def).to_string(),
|
||||
request.area.clone(),
|
||||
Some(request.mode),
|
||||
Some(request.underlay),
|
||||
)
|
||||
}
|
||||
PdfImportSource::File(path) => (
|
||||
acadrust::entities::Underlay::pdf(),
|
||||
path.clone(),
|
||||
"1".to_string(),
|
||||
ImportArea::All,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
};
|
||||
let shown = crate::entities::underlay::display_path(&path);
|
||||
match &source {
|
||||
PdfImportSource::Underlay(_) => self
|
||||
.command_line
|
||||
.push_output(&format!("Binding PDF file {shown}, page {page} ...")),
|
||||
PdfImportSource::File(_) => self
|
||||
.command_line
|
||||
.push_output(&format!("Importing page {page} of PDF file: {shown}...")),
|
||||
}
|
||||
let Some(vectors) = crate::scene::model::pdf_vector::page_vectors(&path, &page) else {
|
||||
self.command_line.push_error(&format!("{shown} not found."));
|
||||
return;
|
||||
};
|
||||
let result = pdf_import::convert(&vectors, &underlay, &area);
|
||||
|
||||
self.push_undo_snapshot(i, "PDFIMPORT");
|
||||
let scene = &mut self.tabs[i].scene;
|
||||
for (name, color) in &result.layers {
|
||||
if !scene.document.layers.contains(name) {
|
||||
let mut layer = acadrust::tables::Layer::new(name.as_str());
|
||||
layer.handle = scene.document.allocate_handle();
|
||||
layer.color = *color;
|
||||
let _ = scene.document.layers.add(layer);
|
||||
}
|
||||
}
|
||||
for (name, font) in &result.text_styles {
|
||||
if !scene.document.text_styles.contains(name) {
|
||||
let mut style = acadrust::tables::TextStyle::new(name.as_str());
|
||||
style.handle = scene.document.allocate_handle();
|
||||
style.font_file = font.clone();
|
||||
let _ = scene.document.text_styles.add(style);
|
||||
}
|
||||
}
|
||||
for entity in result.entities {
|
||||
scene.add_entity(entity);
|
||||
}
|
||||
if let (Some(mode), Some(handle)) = (mode, handle) {
|
||||
match mode {
|
||||
UnderlayMode::Keep => {}
|
||||
UnderlayMode::Unload => {
|
||||
scene
|
||||
.unloaded_underlay_definitions
|
||||
.insert(underlay.definition_handle);
|
||||
scene.reseed_underlays();
|
||||
}
|
||||
UnderlayMode::Detach => {
|
||||
scene.erase_entities(&[handle]);
|
||||
// Drop the definition once nothing references it.
|
||||
let definition = underlay.definition_handle;
|
||||
let referenced = scene.document.entities().any(|entity| {
|
||||
matches!(entity, acadrust::EntityType::Underlay(other)
|
||||
if other.definition_handle == definition)
|
||||
});
|
||||
if !referenced {
|
||||
scene.document.objects.remove(&definition);
|
||||
for object in scene.document.objects.values_mut() {
|
||||
if let ObjectType::Dictionary(dictionary) = object {
|
||||
dictionary.entries.retain(|(_, target)| *target != definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_layer_panel();
|
||||
self.refresh_properties();
|
||||
}
|
||||
}
|
||||
@@ -1015,6 +1015,8 @@ impl OpenCADStudio {
|
||||
| "FRAME"
|
||||
| "IMAGEFRAME"
|
||||
| "PDFFRAME"
|
||||
| "PDFOSNAP"
|
||||
| "UOSNAP"
|
||||
| "POINTCLOUDCLIPFRAME"
|
||||
| "XCLIPFRAME"
|
||||
| "WIPEOUTFRAME"
|
||||
@@ -1106,6 +1108,26 @@ impl OpenCADStudio {
|
||||
}
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
// Snapping to the geometry inside underlays (one switch for
|
||||
// PDF underlays and underlays in general).
|
||||
if matches!(name.as_str(), "PDFOSNAP" | "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)) {
|
||||
Some(mode) => {
|
||||
if current != mode {
|
||||
crate::scene::model::pdf_vector::set_pdf_osnap(mode == 1);
|
||||
self.tabs[i].scene.reseed_underlays();
|
||||
}
|
||||
}
|
||||
None => self.command_line.push_error("Requires 0 or 1 only"),
|
||||
}
|
||||
} else {
|
||||
self.command_line.push_output(&format!("Enter new value for {name} <{current}>:"));
|
||||
self.pending_setvar = Some(name.clone());
|
||||
}
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
if matches!(name.as_str(), "SHOWHIST" | "SOLIDHIST") {
|
||||
let current = if name == "SHOWHIST" {
|
||||
self.tabs[i].scene.document.header.show_solid_history.clamp(0, 2)
|
||||
|
||||
Reference in new issue
Block a user