forked from 1b-agent/opencadstudio-upstream
PDF underlays: attach dialog with pages, underlay tab, layers, import settings and file import, unload on the definition
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0c50013ca7
commit
3f6bdf8c59
57 files changed
+5049
-365
No files matched your search
+162
-17
@@ -9,7 +9,7 @@ use crate::modules::insert::pdf_import::{self, ImportArea, PdfImportRequest, Und
|
||||
/// origin at full size.
|
||||
pub(crate) enum PdfImportSource {
|
||||
Underlay(PdfImportRequest),
|
||||
File(String),
|
||||
File(pdf_import::PdfFileImport),
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
@@ -26,11 +26,7 @@ impl OpenCADStudio {
|
||||
let Some(def) = crate::entities::underlay::definition(underlay, document) else {
|
||||
return;
|
||||
};
|
||||
if self.tabs[i]
|
||||
.scene
|
||||
.unloaded_underlay_definitions
|
||||
.contains(&underlay.definition_handle)
|
||||
{
|
||||
if def.unloaded {
|
||||
self.command_line
|
||||
.push_error("Cannot bind a PDF underlay that is unloaded.");
|
||||
return;
|
||||
@@ -44,10 +40,10 @@ impl OpenCADStudio {
|
||||
Some(request.underlay),
|
||||
)
|
||||
}
|
||||
PdfImportSource::File(path) => (
|
||||
acadrust::entities::Underlay::pdf(),
|
||||
path.clone(),
|
||||
"1".to_string(),
|
||||
PdfImportSource::File(import) => (
|
||||
import.placement(),
|
||||
import.path.clone(),
|
||||
import.page.clone(),
|
||||
ImportArea::All,
|
||||
None,
|
||||
None,
|
||||
@@ -62,14 +58,80 @@ impl OpenCADStudio {
|
||||
.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 {
|
||||
// The underlay's own layer overrides leave those layers out.
|
||||
let hidden = crate::scene::model::pdf_layers::hidden_layers(&underlay);
|
||||
let Some(content) =
|
||||
crate::scene::model::pdf_vector::page_content_by_layer(&path, &page, &hidden)
|
||||
else {
|
||||
self.command_line.push_error(&format!("{shown} not found."));
|
||||
return;
|
||||
};
|
||||
let result = pdf_import::convert(&vectors, &underlay, &area);
|
||||
let settings = pdf_import::import_settings();
|
||||
let current_layer = {
|
||||
let header = &self.tabs[i].scene.document.header;
|
||||
if header.current_layer_name.is_empty() {
|
||||
self.tabs[i].active_layer.clone()
|
||||
} else {
|
||||
header.current_layer_name.clone()
|
||||
}
|
||||
};
|
||||
let naming = pdf_import::LayerNaming {
|
||||
settings,
|
||||
prefix: import_prefix(&path),
|
||||
current: current_layer.clone(),
|
||||
};
|
||||
let mut result = pdf_import::convert(&content, &underlay, &area, &naming);
|
||||
let images = std::mem::take(&mut result.images);
|
||||
let image_dir = self.tabs[i]
|
||||
.current_path
|
||||
.as_deref()
|
||||
.and_then(|p| p.parent())
|
||||
.map(|p| p.to_path_buf())
|
||||
.or_else(|| std::path::Path::new(&path).parent().map(|p| p.to_path_buf()))
|
||||
.map(|dir| dir.join("PDF Images"));
|
||||
|
||||
self.push_undo_snapshot(i, "PDFIMPORT");
|
||||
let scene = &mut self.tabs[i].scene;
|
||||
if result.dashed && !scene.document.line_types.contains(pdf_import::DASH_LINETYPE) {
|
||||
let mut lt = acadrust::tables::LineType::new(pdf_import::DASH_LINETYPE);
|
||||
lt.handle = scene.document.allocate_handle();
|
||||
lt.add_element(acadrust::tables::LineTypeElement::dash(0.8));
|
||||
lt.add_element(acadrust::tables::LineTypeElement::space(0.2));
|
||||
lt.pattern_length = 1.0;
|
||||
let _ = scene.document.line_types.add(lt);
|
||||
}
|
||||
// Raster images: each written as a PNG under "PDF Images" next to the
|
||||
// drawing (or the PDF while the drawing is unsaved), then referenced.
|
||||
let stem = std::path::Path::new(&path.replace('\\', "/"))
|
||||
.file_stem()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| "PDF".to_string());
|
||||
for (n, image) in images.iter().enumerate() {
|
||||
let Some(dir) = &image_dir else { break };
|
||||
if std::fs::create_dir_all(dir).is_err() {
|
||||
break;
|
||||
}
|
||||
let file = dir.join(format!("{stem}{:08x}.png", image_file_id(&path, &page, n)));
|
||||
if image::save_buffer(&file, &image.rgba, image.width, image.height, image::ColorType::Rgba8)
|
||||
.is_err()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let mut raster = acadrust::entities::RasterImage::with_size(
|
||||
file.to_string_lossy().as_ref(),
|
||||
image.insertion,
|
||||
image.width as f64,
|
||||
image.height as f64,
|
||||
1.0,
|
||||
1.0,
|
||||
);
|
||||
raster.u_vector = image.u;
|
||||
raster.v_vector = image.v;
|
||||
raster.flags = acadrust::entities::ImageDisplayFlags::SHOW_IMAGE
|
||||
| acadrust::entities::ImageDisplayFlags::USE_CLIPPING_BOUNDARY;
|
||||
raster.common.layer = image.layer.clone();
|
||||
result.entities.push(acadrust::EntityType::RasterImage(raster));
|
||||
}
|
||||
for (name, color) in &result.layers {
|
||||
if !scene.document.layers.contains(name) {
|
||||
let mut layer = acadrust::tables::Layer::new(name.as_str());
|
||||
@@ -86,16 +148,40 @@ impl OpenCADStudio {
|
||||
let _ = scene.document.text_styles.add(style);
|
||||
}
|
||||
}
|
||||
for entity in result.entities {
|
||||
scene.add_entity(entity);
|
||||
if settings.as_block && !result.entities.is_empty() {
|
||||
// One block named after the file, inserted at the origin on the
|
||||
// current layer.
|
||||
let mut name = stem.clone();
|
||||
let mut n = 1;
|
||||
while scene.document.block_records.get(&name).is_some() {
|
||||
n += 1;
|
||||
name = format!("{stem}({n})");
|
||||
}
|
||||
if scene
|
||||
.define_block_from_owned_entities(result.entities, &name, glam::DVec3::ZERO)
|
||||
.is_ok()
|
||||
{
|
||||
let mut insert = acadrust::entities::Insert::new(
|
||||
name,
|
||||
acadrust::types::Vector3::new(0.0, 0.0, 0.0),
|
||||
);
|
||||
insert.common.layer = current_layer;
|
||||
scene.add_entity(acadrust::EntityType::Insert(insert));
|
||||
}
|
||||
} else {
|
||||
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);
|
||||
if let Some(ObjectType::UnderlayDefinition(definition)) =
|
||||
scene.document.objects.get_mut(&underlay.definition_handle)
|
||||
{
|
||||
definition.unloaded = true;
|
||||
}
|
||||
scene.reseed_underlays();
|
||||
}
|
||||
UnderlayMode::Detach => {
|
||||
@@ -122,3 +208,62 @@ impl OpenCADStudio {
|
||||
self.refresh_properties();
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// PDFATTACH's end: every page's definition (created or reused, with the
|
||||
/// definitions dictionary) and underlay in one undo step.
|
||||
pub(crate) fn attach_pdf_pages(
|
||||
&mut self,
|
||||
i: usize,
|
||||
label: String,
|
||||
path: &str,
|
||||
pages: Vec<(String, acadrust::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(
|
||||
&mut self.tabs[i].scene.document,
|
||||
path,
|
||||
&page,
|
||||
);
|
||||
if let acadrust::EntityType::Underlay(underlay) = &mut entity {
|
||||
underlay.definition_handle = definition;
|
||||
}
|
||||
self.commit_entity_handle(entity);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The layer prefix of an imported file: "PDF_" for the first file of the
|
||||
/// session, "PDF2_", "PDF3_" … for each other file.
|
||||
// ponytail: remembered for the session only; a reopened drawing starts from
|
||||
// "PDF_" again.
|
||||
fn import_prefix(path: &str) -> String {
|
||||
static FILES: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(Vec::new());
|
||||
let mut files = FILES.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let index = match files.iter().position(|f| f.eq_ignore_ascii_case(path)) {
|
||||
Some(at) => at,
|
||||
None => {
|
||||
files.push(path.to_string());
|
||||
files.len() - 1
|
||||
}
|
||||
};
|
||||
if index == 0 {
|
||||
"PDF_".to_string()
|
||||
} else {
|
||||
format!("PDF{}_", index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// A file name suffix for an imported image, different per import.
|
||||
fn image_file_id(path: &str, page: &str, n: usize) -> u32 {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||
(path, page, n, std::time::SystemTime::now()).hash(&mut h);
|
||||
h.finish() as u32
|
||||
}
|
||||
Reference in new issue
Block a user