forked from 1b-agent/opencadstudio-upstream
Merge pull request #1472 from ramox81:codex/pdf-attach-parity
This commit is contained in:
commit
2e60c4da1b
63 files changed
+7470
-405
No files matched your search
@@ -1066,7 +1066,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
if let Some(bounds) = bounds {
|
||||
let (min, max) = crate::scene::convert::tess::entity_bounds(e);
|
||||
let (min, max) = crate::scene::convert::tess::entity_bounds_in(&tab.scene.document, e);
|
||||
if max[0] < bounds[0]
|
||||
|| max[1] < bounds[1]
|
||||
|| min[0] > bounds[2]
|
||||
|
||||
@@ -3892,6 +3892,33 @@ impl OpenCADStudio {
|
||||
}
|
||||
self.refresh_properties();
|
||||
}
|
||||
CmdResult::OpenPdfImportSettings => self.open_pdf_import_settings(),
|
||||
CmdResult::PdfImportFile(import) => {
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.run_pdf_import(
|
||||
i,
|
||||
crate::app::commands::pdf_import::PdfImportSource::File(import),
|
||||
);
|
||||
}
|
||||
CmdResult::AttachPdfPages { 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.restore_pre_cmd_tangent();
|
||||
}
|
||||
CmdResult::PdfImport(request) => {
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.run_pdf_import(
|
||||
i,
|
||||
crate::app::commands::pdf_import::PdfImportSource::Underlay(request),
|
||||
);
|
||||
}
|
||||
CmdResult::UpdateEntityAndFinish { handle, entity } => {
|
||||
if self.reject_locked_edit(i, handle) {
|
||||
self.tabs[i].active_cmd = None;
|
||||
|
||||
@@ -695,8 +695,59 @@ 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));
|
||||
}
|
||||
// A file named after it skips the picker.
|
||||
cmd if cmd.starts_with("_PDFIMPORTFILE ") => {
|
||||
let path = cmd["_PDFIMPORTFILE ".len()..].trim().trim_matches('"').to_string();
|
||||
match std::fs::read(&path) {
|
||||
Ok(bytes) => {
|
||||
crate::scene::model::pdf_raster::register_source(&path, std::sync::Arc::new(bytes));
|
||||
self.open_pdf_import_file(&path);
|
||||
}
|
||||
Err(_) => self.command_line.push_error(&format!("{path} not found.")),
|
||||
}
|
||||
}
|
||||
"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,8 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
"POINTCLOUDATTACH" | "RECAP" | "SYNCPVIEWPORTS" | "UNDERLAYLAYERS"
|
||||
| "UOSNAP" => {
|
||||
"UNDERLAYLAYERS" | "ULAYERS" => self.open_underlay_layers_dialog(i),
|
||||
"POINTCLOUDATTACH" | "RECAP" | "SYNCPVIEWPORTS" => {
|
||||
self.command_line
|
||||
.push_info(crate::tf!("{cmd}: not yet implemented.").as_ref());
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ mod fileops;
|
||||
mod inquiry;
|
||||
mod layerprops;
|
||||
mod layers;
|
||||
pub(crate) mod pdf_import;
|
||||
mod pdf_underlay;
|
||||
mod pdf_dialogs;
|
||||
mod plotvars;
|
||||
mod styleprops;
|
||||
mod view;
|
||||
@@ -253,6 +256,9 @@ impl OpenCADStudio {
|
||||
if let Some(t) = self.dispatch_blocks(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
if let Some(t) = self.dispatch_pdf_underlay(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
if let Some(t) = self.dispatch_draw(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
@@ -610,6 +616,11 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"FRAME",
|
||||
"IMAGEFRAME",
|
||||
"PDFFRAME",
|
||||
"PDFOSNAP",
|
||||
"UOSNAP",
|
||||
"PDFIMPORTMODE",
|
||||
"PDFIMPORTFILTER",
|
||||
"PDFIMPORTLAYERS",
|
||||
"POINTCLOUDCLIPFRAME",
|
||||
"XCLIPFRAME",
|
||||
"WIPEOUTFRAME",
|
||||
|
||||
@@ -0,0 +1,507 @@
|
||||
//! The PDF dialogs' state changes: Attach PDF Underlay, Underlay Layers,
|
||||
//! PDF Import Settings and Import PDF.
|
||||
|
||||
use super::*;
|
||||
use crate::io::xref_model::Pathtype;
|
||||
use crate::modules::insert::pdf_import::{self, PdfFileImport};
|
||||
use crate::ui::window::pdf_dialogs::{
|
||||
click_page, page_thumbs, LayerTarget, PdfAttachState, PdfDialogMsg, PdfImportFileState,
|
||||
RotationChoice, UnderlayLayersState,
|
||||
};
|
||||
|
||||
/// "7.8740 × 3.9369" (inches) for a page.
|
||||
fn page_size_text(path: &str, page: &str) -> String {
|
||||
crate::scene::model::pdf_raster::page_size_inches(path, page)
|
||||
.map(|(w, h)| format!("{w:.4} × {h:.4}"))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn file_stem(path: &str) -> String {
|
||||
std::path::Path::new(&path.replace('\\', "/"))
|
||||
.file_stem()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// The path an attached file is stored with: full, relative to the saved
|
||||
/// drawing (the full path until it is saved), or the file name alone.
|
||||
fn pdf_stored_path(&self, path: &str, path_type: Pathtype) -> String {
|
||||
match path_type {
|
||||
Pathtype::Full => path.to_string(),
|
||||
Pathtype::Relative => self.tabs[self.active_tab]
|
||||
.current_path
|
||||
.as_deref()
|
||||
.and_then(|host| {
|
||||
crate::io::xref_model::to_pathtype_result(path, host, Pathtype::Relative).ok()
|
||||
})
|
||||
.unwrap_or_else(|| path.to_string()),
|
||||
Pathtype::None => std::path::Path::new(&path.replace('\\', "/"))
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| path.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// PDFATTACH after the file is chosen.
|
||||
pub(in crate::app) fn open_pdf_attach_dialog(&mut self, path: &str) {
|
||||
let i = self.active_tab;
|
||||
let mut existing: Vec<(String, String)> = Vec::new();
|
||||
for object in self.tabs[i].scene.document.objects.values() {
|
||||
if let acadrust::objects::ObjectType::UnderlayDefinition(def) = object {
|
||||
let name = file_stem(&def.file_path);
|
||||
if !existing.iter().any(|(n, _)| *n == name) {
|
||||
existing.push((name, def.file_path.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut state = PdfAttachState::new(existing);
|
||||
self.load_pdf_attach_file(&mut state, path);
|
||||
self.pdf_attach = Some(state);
|
||||
self.active_modal = Some(crate::app::ModalKind::PdfAttach);
|
||||
}
|
||||
|
||||
fn load_pdf_attach_file(&self, state: &mut PdfAttachState, path: &str) {
|
||||
state.path = path.to_string();
|
||||
state.name = file_stem(path);
|
||||
state.pages = page_thumbs(path);
|
||||
state.selected = vec![0];
|
||||
state.anchor = 0;
|
||||
state.found_in = crate::entities::underlay::display_path(path);
|
||||
state.page_size = page_size_text(path, "1");
|
||||
state.saved_path =
|
||||
crate::entities::underlay::display_path(&self.pdf_stored_path(path, state.path_type.0));
|
||||
}
|
||||
|
||||
/// ULAYERS: every PDF underlay of the drawing, the selected one first
|
||||
/// shown.
|
||||
pub(in crate::app) fn open_underlay_layers_dialog(&mut self, i: usize) {
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let selected: Vec<acadrust::Handle> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.iter()
|
||||
.map(|(h, _)| *h)
|
||||
.collect();
|
||||
let mut targets: Vec<LayerTarget> = Vec::new();
|
||||
for entity in document.entities() {
|
||||
let acadrust::EntityType::Underlay(u) = entity else {
|
||||
continue;
|
||||
};
|
||||
if u.underlay_type != acadrust::entities::UnderlayType::Pdf {
|
||||
continue;
|
||||
}
|
||||
let Some(def) = crate::entities::underlay::definition(u, document) else {
|
||||
continue;
|
||||
};
|
||||
let mut name = crate::entities::underlay::definition_display_name(def);
|
||||
let same = targets.iter().filter(|t| t.name.starts_with(&name)).count();
|
||||
if same > 0 {
|
||||
name = format!("{name} ({})", same + 1);
|
||||
}
|
||||
let mut layers: Vec<String> = crate::scene::model::pdf_layers::layers(&def.file_path)
|
||||
.into_iter()
|
||||
.map(|l| l.name)
|
||||
.collect();
|
||||
layers.sort_by_key(|n| n.to_lowercase());
|
||||
layers.dedup();
|
||||
targets.push(LayerTarget {
|
||||
name,
|
||||
handle: u.common.handle,
|
||||
layers,
|
||||
hidden: crate::scene::model::pdf_layers::hidden_layers(u),
|
||||
});
|
||||
}
|
||||
if targets.is_empty() {
|
||||
self.command_line.push_error("No PDF underlays found.");
|
||||
return;
|
||||
}
|
||||
let current = targets
|
||||
.iter()
|
||||
.position(|t| selected.contains(&t.handle))
|
||||
.unwrap_or(0);
|
||||
self.underlay_layers = Some(UnderlayLayersState {
|
||||
targets,
|
||||
current,
|
||||
search: String::new(),
|
||||
});
|
||||
self.active_modal = Some(crate::app::ModalKind::UnderlayLayers);
|
||||
}
|
||||
|
||||
/// PDFIMPORT's Settings option: the dialog over the running command.
|
||||
pub(in crate::app) fn open_pdf_import_settings(&mut self) {
|
||||
self.pdf_import_settings = Some(pdf_import::import_settings());
|
||||
self.active_modal = Some(crate::app::ModalKind::PdfImportSettings);
|
||||
}
|
||||
|
||||
/// PDFIMPORT's File option after the file is chosen.
|
||||
pub(in crate::app) fn open_pdf_import_file(&mut self, path: &str) {
|
||||
self.pdf_import_file = Some(PdfImportFileState {
|
||||
path: path.to_string(),
|
||||
pages: page_thumbs(path),
|
||||
selected: 0,
|
||||
page_text: "1".into(),
|
||||
insert_on_screen: false,
|
||||
scale: "1".into(),
|
||||
rotation: RotationChoice(0),
|
||||
page_size: page_size_mm(path, "1"),
|
||||
settings: pdf_import::import_settings(),
|
||||
});
|
||||
self.active_modal = Some(crate::app::ModalKind::PdfImportFile);
|
||||
}
|
||||
|
||||
/// Writes the layers each underlay turns off, as one undo step.
|
||||
fn apply_underlay_layers(&mut self, i: usize, targets: Vec<LayerTarget>) {
|
||||
use crate::scene::model::pdf_layers::{hidden_layers, LAYER_OVERRIDE_APP};
|
||||
let changed: Vec<LayerTarget> = targets
|
||||
.into_iter()
|
||||
.filter(|t| match self.tabs[i].scene.document.get_entity(t.handle) {
|
||||
Some(acadrust::EntityType::Underlay(u)) => hidden_layers(u) != t.hidden,
|
||||
_ => false,
|
||||
})
|
||||
.collect();
|
||||
if changed.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.push_undo_snapshot(i, "ULAYERS");
|
||||
self.tabs[i].scene.ensure_app_id(LAYER_OVERRIDE_APP);
|
||||
let app_handle = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.app_ids
|
||||
.get(LAYER_OVERRIDE_APP)
|
||||
.map(|a| a.handle.value());
|
||||
for target in &changed {
|
||||
if let Some(acadrust::EntityType::Underlay(u)) =
|
||||
self.tabs[i].scene.document.get_entity_mut(target.handle)
|
||||
{
|
||||
let data = &mut u.common.extended_data;
|
||||
if let Some(app) = app_handle {
|
||||
data.raw_dwg_eed.retain(|(a, _)| *a != app);
|
||||
}
|
||||
data.remove_record(LAYER_OVERRIDE_APP);
|
||||
if !target.hidden.is_empty() {
|
||||
let mut record = acadrust::xdata::ExtendedDataRecord::new(LAYER_OVERRIDE_APP);
|
||||
for name in &target.hidden {
|
||||
record.add_value(acadrust::xdata::XDataValue::String(name.clone()));
|
||||
}
|
||||
data.add_record(record);
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.reseed_derived_caches(target.handle);
|
||||
}
|
||||
let changes: Vec<_> = changed
|
||||
.iter()
|
||||
.map(|t| (t.handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
|
||||
pub(in crate::app) fn update_pdf_dialog(&mut self, message: PdfDialogMsg) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
match message {
|
||||
PdfDialogMsg::Help(which) => {
|
||||
let text = match which {
|
||||
"attach" => crate::t!("Attaches a PDF file as an underlay."),
|
||||
"layers" => crate::t!("Turns the layers of a PDF underlay on or off."),
|
||||
_ => crate::t!("Imports the geometry, fills, raster images and text of a PDF file as drawing objects."),
|
||||
};
|
||||
self.command_line.push_info(text.as_ref());
|
||||
}
|
||||
PdfDialogMsg::Options => {
|
||||
self.options_parent = self.active_modal;
|
||||
self.options_open();
|
||||
}
|
||||
|
||||
// ── Attach ────────────────────────────────────────────────────
|
||||
PdfDialogMsg::AttachBrowse => {
|
||||
if let Some(state) = &self.pdf_attach {
|
||||
state.remember();
|
||||
}
|
||||
return Task::done(Message::PdfAttachPick);
|
||||
}
|
||||
PdfDialogMsg::AttachName(name) => {
|
||||
let Some(mut state) = self.pdf_attach.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
if let Some((_, path)) = state.existing.iter().find(|(n, _)| *n == name).cloned() {
|
||||
self.load_pdf_attach_file(&mut state, &path);
|
||||
}
|
||||
self.pdf_attach = Some(state);
|
||||
}
|
||||
PdfDialogMsg::AttachPage(page) => {
|
||||
let (ctrl, shift) = (self.ctrl_down, self.shift_down);
|
||||
if let Some(state) = self.pdf_attach.as_mut() {
|
||||
click_page(&mut state.selected, &mut state.anchor, page, ctrl, shift);
|
||||
let first = state.page_labels().into_iter().next().unwrap_or_default();
|
||||
state.page_size = page_size_text(&state.path, &first);
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::AttachPathType(choice) => {
|
||||
let Some(mut state) = self.pdf_attach.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
state.path_type = choice;
|
||||
state.saved_path = crate::entities::underlay::display_path(
|
||||
&self.pdf_stored_path(&state.path, choice.0),
|
||||
);
|
||||
self.pdf_attach = Some(state);
|
||||
}
|
||||
PdfDialogMsg::AttachInsertOnScreen(on) => {
|
||||
if let Some(s) = self.pdf_attach.as_mut() {
|
||||
s.insert_on_screen = on;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::AttachInsert(axis, value) => {
|
||||
if let Some(s) = self.pdf_attach.as_mut() {
|
||||
s.insert[axis.min(2)] = value;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::AttachScaleOnScreen(on) => {
|
||||
if let Some(s) = self.pdf_attach.as_mut() {
|
||||
s.scale_on_screen = on;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::AttachScale(value) => {
|
||||
if let Some(s) = self.pdf_attach.as_mut() {
|
||||
s.scale = value;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::AttachRotationOnScreen(on) => {
|
||||
if let Some(s) = self.pdf_attach.as_mut() {
|
||||
s.rotation_on_screen = on;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::AttachRotation(value) => {
|
||||
if let Some(s) = self.pdf_attach.as_mut() {
|
||||
s.rotation = value;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::AttachDetails(on) => {
|
||||
if let Some(s) = self.pdf_attach.as_mut() {
|
||||
s.details = on;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::AttachOk => return self.pdf_attach_ok(i),
|
||||
|
||||
// ── Underlay Layers ───────────────────────────────────────────
|
||||
PdfDialogMsg::LayersUnderlay(name) => {
|
||||
if let Some(s) = self.underlay_layers.as_mut() {
|
||||
if let Some(at) = s.targets.iter().position(|t| t.name == name) {
|
||||
s.current = at;
|
||||
}
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::LayersSearch(text) => {
|
||||
if let Some(s) = self.underlay_layers.as_mut() {
|
||||
s.search = text;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::LayersToggle(layer) => {
|
||||
if let Some(s) = self.underlay_layers.as_mut() {
|
||||
if let Some(t) = s.targets.get_mut(s.current) {
|
||||
match t.hidden.iter().position(|h| *h == layer) {
|
||||
Some(at) => {
|
||||
t.hidden.remove(at);
|
||||
}
|
||||
None => t.hidden.push(layer),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::LayersOk => {
|
||||
if let Some(state) = self.underlay_layers.take() {
|
||||
self.apply_underlay_layers(i, state.targets);
|
||||
}
|
||||
self.close_active_modal();
|
||||
}
|
||||
|
||||
// ── Import settings ───────────────────────────────────────────
|
||||
PdfDialogMsg::Vector(_)
|
||||
| PdfDialogMsg::Fills(_)
|
||||
| PdfDialogMsg::Text(_)
|
||||
| PdfDialogMsg::Raster(_)
|
||||
| PdfDialogMsg::Layers(_)
|
||||
| PdfDialogMsg::AsBlock(_)
|
||||
| PdfDialogMsg::Join(_)
|
||||
| PdfDialogMsg::Hatches(_)
|
||||
| PdfDialogMsg::Lineweights(_)
|
||||
| PdfDialogMsg::Linetypes(_) => {
|
||||
let settings = if self.active_modal == Some(crate::app::ModalKind::PdfImportFile) {
|
||||
self.pdf_import_file.as_mut().map(|s| &mut s.settings)
|
||||
} else {
|
||||
self.pdf_import_settings.as_mut()
|
||||
};
|
||||
if let Some(s) = settings {
|
||||
match message {
|
||||
PdfDialogMsg::Vector(v) => s.vector = v,
|
||||
PdfDialogMsg::Fills(v) => s.fills = v,
|
||||
PdfDialogMsg::Text(v) => s.text = v,
|
||||
PdfDialogMsg::Raster(v) => s.raster = v,
|
||||
PdfDialogMsg::Layers(v) => s.layers = v,
|
||||
PdfDialogMsg::AsBlock(v) => s.as_block = v,
|
||||
PdfDialogMsg::Join(v) => s.join = v,
|
||||
PdfDialogMsg::Hatches(v) => s.hatches = v,
|
||||
PdfDialogMsg::Lineweights(v) => s.lineweights = v,
|
||||
PdfDialogMsg::Linetypes(v) => s.linetypes = v,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::SettingsOk => {
|
||||
if let Some(s) = self.pdf_import_settings.take() {
|
||||
pdf_import::set_import_settings(s);
|
||||
}
|
||||
self.close_active_modal();
|
||||
}
|
||||
|
||||
// ── Import PDF ────────────────────────────────────────────────
|
||||
PdfDialogMsg::ImportBrowse => return Task::done(Message::PdfImportPick),
|
||||
PdfDialogMsg::ImportPage(page) => {
|
||||
if let Some(s) = self.pdf_import_file.as_mut() {
|
||||
s.selected = page;
|
||||
s.page_text = (page + 1).to_string();
|
||||
s.page_size = page_size_mm(&s.path, &s.page_text);
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::ImportPageText(text) => {
|
||||
if let Some(s) = self.pdf_import_file.as_mut() {
|
||||
if let Some(n) = text.trim().parse::<usize>().ok().filter(|n| (1..=s.pages.len()).contains(n)) {
|
||||
s.selected = n - 1;
|
||||
s.page_size = page_size_mm(&s.path, &n.to_string());
|
||||
}
|
||||
s.page_text = text;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::ImportInsertOnScreen(on) => {
|
||||
if let Some(s) = self.pdf_import_file.as_mut() {
|
||||
s.insert_on_screen = on;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::ImportScale(value) => {
|
||||
if let Some(s) = self.pdf_import_file.as_mut() {
|
||||
s.scale = value;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::ImportRotation(r) => {
|
||||
if let Some(s) = self.pdf_import_file.as_mut() {
|
||||
s.rotation = r;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::ImportOk => {
|
||||
let Some(state) = self.pdf_import_file.as_ref() else {
|
||||
return Task::none();
|
||||
};
|
||||
let Some(scale) = state.scale.trim().parse::<f64>().ok().filter(|v| *v > 0.0) else {
|
||||
self.command_line.push_error("Value must be positive and nonzero.");
|
||||
return Task::none();
|
||||
};
|
||||
let state = self.pdf_import_file.take().expect("checked above");
|
||||
pdf_import::set_import_settings(state.settings);
|
||||
self.close_active_modal();
|
||||
let import = PdfFileImport {
|
||||
path: state.path.clone(),
|
||||
page: (state.selected + 1).to_string(),
|
||||
scale,
|
||||
rotation: (state.rotation.0 as f64).to_radians(),
|
||||
insertion: glam::DVec3::ZERO,
|
||||
};
|
||||
if state.insert_on_screen {
|
||||
use crate::command::CadCommand;
|
||||
let command = pdf_import::PdfImportPointCommand::new(import);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
return self.focus_cmd_input();
|
||||
}
|
||||
self.run_pdf_import(i, crate::app::commands::pdf_import::PdfImportSource::File(import));
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn pdf_attach_ok(&mut self, i: usize) -> Task<Message> {
|
||||
use crate::command::CadCommand;
|
||||
let Some(state) = self.pdf_attach.as_ref() else {
|
||||
return Task::none();
|
||||
};
|
||||
let number = |s: &str| s.trim().parse::<f64>().ok();
|
||||
let insertion = if state.insert_on_screen {
|
||||
None
|
||||
} else {
|
||||
match (number(&state.insert[0]), number(&state.insert[1]), number(&state.insert[2])) {
|
||||
(Some(x), Some(y), Some(z)) => Some(glam::DVec3::new(x, y, z)),
|
||||
_ => {
|
||||
self.command_line.push_error("Requires numeric value.");
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
};
|
||||
let scale = if state.scale_on_screen {
|
||||
None
|
||||
} else {
|
||||
match number(&state.scale).filter(|v| *v > 0.0) {
|
||||
Some(v) => Some(v),
|
||||
None => {
|
||||
self.command_line.push_error("Value must be positive and nonzero.");
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
};
|
||||
let rotation = if state.rotation_on_screen {
|
||||
None
|
||||
} else {
|
||||
match number(&state.rotation) {
|
||||
Some(v) => Some(v),
|
||||
None => {
|
||||
self.command_line.push_error("Requires numeric value.");
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
};
|
||||
let state = self.pdf_attach.take().expect("checked above");
|
||||
state.remember();
|
||||
let stored = self.pdf_stored_path(&state.path, state.path_type.0);
|
||||
if stored != state.path {
|
||||
if let Some(bytes) = crate::scene::model::pdf_raster::source_bytes(&state.path) {
|
||||
crate::scene::model::pdf_raster::register_source(&stored, bytes);
|
||||
}
|
||||
}
|
||||
self.close_active_modal();
|
||||
let insunits = self.tabs[i].scene.document.header.insertion_units;
|
||||
let mut command = crate::modules::insert::pdf_attach::PdfAttachCommand::from_dialog(
|
||||
&state.path,
|
||||
&stored,
|
||||
&state.page_labels(),
|
||||
scale,
|
||||
rotation,
|
||||
insunits,
|
||||
);
|
||||
match insertion {
|
||||
Some(point) => {
|
||||
let result = command.on_point(point);
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
if matches!(result, crate::command::CmdResult::NeedPoint) {
|
||||
self.command_line.push_info(
|
||||
&self.tabs[i].active_cmd.as_ref().map(|c| c.prompt()).unwrap_or_default(),
|
||||
);
|
||||
return self.focus_cmd_input();
|
||||
}
|
||||
self.apply_cmd_result(result)
|
||||
}
|
||||
None => {
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
self.focus_cmd_input()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// "200 × 100 mm" for a page.
|
||||
fn page_size_mm(path: &str, page: &str) -> String {
|
||||
crate::scene::model::pdf_raster::page_size_inches(path, page)
|
||||
.map(|(w, h)| format!("{:.0} × {:.0} mm", w * 25.4, h * 25.4))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
//! 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(pdf_import::PdfFileImport),
|
||||
}
|
||||
|
||||
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 def.unloaded {
|
||||
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(import) => (
|
||||
import.placement(),
|
||||
import.path.clone(),
|
||||
import.page.clone(),
|
||||
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}...")),
|
||||
}
|
||||
// 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 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());
|
||||
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);
|
||||
}
|
||||
}
|
||||
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 => {
|
||||
if let Some(ObjectType::UnderlayDefinition(definition)) =
|
||||
scene.document.objects.get_mut(&underlay.definition_handle)
|
||||
{
|
||||
definition.unloaded = true;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
//! The contextual PDF Underlay ribbon tab: its values come from the selected
|
||||
//! underlays, its buttons edit them.
|
||||
|
||||
use super::*;
|
||||
use acadrust::entities::{Underlay, UnderlayDisplayFlags, UnderlayType};
|
||||
use crate::ui::ribbon::{UnderlayContext, UnderlayTabMsg};
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// The selected PDF underlays, or nothing when anything else is selected.
|
||||
fn selected_pdf_underlays(&self, i: usize) -> Vec<(acadrust::Handle, Underlay)> {
|
||||
let mut out = Vec::new();
|
||||
for (handle, entity) in self.tabs[i].scene.selected_entities() {
|
||||
match entity {
|
||||
acadrust::EntityType::Underlay(u) if u.underlay_type == UnderlayType::Pdf => {
|
||||
out.push((handle, u.clone()));
|
||||
}
|
||||
_ => return Vec::new(),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Show the tab while only PDF underlays are selected.
|
||||
pub(in crate::app) fn sync_underlay_tab(&mut self) {
|
||||
let i = self.active_tab;
|
||||
let context = if self.tabs[i].is_start {
|
||||
None
|
||||
} else {
|
||||
self.selected_pdf_underlays(i).first().map(|(_, u)| {
|
||||
UnderlayContext::new(
|
||||
u.contrast,
|
||||
u.fade,
|
||||
u.flags.contains(UnderlayDisplayFlags::MONOCHROME),
|
||||
u.flags.contains(UnderlayDisplayFlags::ON),
|
||||
crate::scene::model::pdf_vector::pdf_osnap(),
|
||||
)
|
||||
})
|
||||
};
|
||||
self.ribbon.set_underlay_context(context);
|
||||
}
|
||||
|
||||
/// One undo step that edits every selected PDF underlay.
|
||||
fn edit_selected_underlays(&mut self, i: usize, label: &str, edit: impl Fn(&mut Underlay)) {
|
||||
let handles: Vec<_> = self
|
||||
.selected_pdf_underlays(i)
|
||||
.into_iter()
|
||||
.map(|(h, _)| h)
|
||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
||||
.collect();
|
||||
if handles.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.push_undo_snapshot(i, label);
|
||||
for handle in &handles {
|
||||
if let Some(acadrust::EntityType::Underlay(u)) =
|
||||
self.tabs[i].scene.document.get_entity_mut(*handle)
|
||||
{
|
||||
edit(u);
|
||||
}
|
||||
self.tabs[i].scene.reseed_derived_caches(*handle);
|
||||
}
|
||||
let changes: Vec<_> = handles
|
||||
.iter()
|
||||
.map(|h| (*h, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
|
||||
pub(in crate::app) fn update_underlay_tab(&mut self, message: UnderlayTabMsg) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let Some(ctx) = self.ribbon.underlay_context_mut() else {
|
||||
return Task::none();
|
||||
};
|
||||
match message {
|
||||
UnderlayTabMsg::Contrast(v) => {
|
||||
ctx.contrast = v;
|
||||
ctx.contrast_text = v.to_string();
|
||||
}
|
||||
UnderlayTabMsg::Fade(v) => {
|
||||
ctx.fade = v;
|
||||
ctx.fade_text = v.to_string();
|
||||
}
|
||||
UnderlayTabMsg::ContrastText(s) => ctx.contrast_text = s,
|
||||
UnderlayTabMsg::FadeText(s) => ctx.fade_text = s,
|
||||
UnderlayTabMsg::Commit => {
|
||||
// A typed value outside 0–100 or not a number keeps the value.
|
||||
let parse = |s: &str, old: u8| {
|
||||
s.trim().parse::<u8>().ok().filter(|v| *v <= 100).unwrap_or(old)
|
||||
};
|
||||
let (contrast, fade) = (
|
||||
parse(&ctx.contrast_text, ctx.contrast),
|
||||
parse(&ctx.fade_text, ctx.fade),
|
||||
);
|
||||
ctx.contrast_text = contrast.to_string();
|
||||
ctx.fade_text = fade.to_string();
|
||||
self.edit_selected_underlays(i, "PDFADJUST", |u| {
|
||||
u.set_contrast(contrast);
|
||||
u.set_fade(fade);
|
||||
});
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// The tab's buttons (host-only command names).
|
||||
pub(super) fn dispatch_pdf_underlay(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||
match cmd {
|
||||
"_PDFULMONO" => {
|
||||
let on = !self
|
||||
.selected_pdf_underlays(i)
|
||||
.first()
|
||||
.is_some_and(|(_, u)| u.flags.contains(UnderlayDisplayFlags::MONOCHROME));
|
||||
self.edit_selected_underlays(i, "PDFADJUST", |u| u.set_monochrome(on));
|
||||
}
|
||||
"_PDFULSHOW" => {
|
||||
let on = !self
|
||||
.selected_pdf_underlays(i)
|
||||
.first()
|
||||
.is_some_and(|(_, u)| u.flags.contains(UnderlayDisplayFlags::ON));
|
||||
self.edit_selected_underlays(i, "PDFUNDERLAY", |u| u.set_on(on));
|
||||
}
|
||||
"_PDFULSNAP" => {
|
||||
let on = !crate::scene::model::pdf_vector::pdf_osnap();
|
||||
crate::scene::model::pdf_vector::set_pdf_osnap(on);
|
||||
self.tabs[i].scene.reseed_underlays();
|
||||
self.sync_underlay_tab();
|
||||
}
|
||||
"_PDFULUNCLIP" => {
|
||||
self.edit_selected_underlays(i, "PDFCLIP", |u| {
|
||||
u.clip_boundary_vertices.clear();
|
||||
u.clip_inverted = false;
|
||||
u.flags -= UnderlayDisplayFlags::CLIPPING;
|
||||
});
|
||||
}
|
||||
"_PDFULCLIP" => {
|
||||
use crate::command::CadCommand;
|
||||
let Some((handle, underlay)) = self.selected_pdf_underlays(i).into_iter().next()
|
||||
else {
|
||||
return Some(Task::none());
|
||||
};
|
||||
let (command, first) =
|
||||
crate::modules::insert::pdf_clip::PdfClipCommand::new_boundary(handle, underlay);
|
||||
if let crate::command::CmdResult::ReportMeasurement(text) = first {
|
||||
for line in text.lines() {
|
||||
self.command_line.push_output(line);
|
||||
}
|
||||
}
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
"_PDFULIMPORT" => {
|
||||
use crate::command::CadCommand;
|
||||
let Some((handle, _)) = self.selected_pdf_underlays(i).into_iter().next() else {
|
||||
return Some(Task::none());
|
||||
};
|
||||
let command = crate::modules::insert::pdf_import::PdfImportCommand::for_underlay(handle);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
Some(self.finish_dispatch(cmd))
|
||||
}
|
||||
}
|
||||
@@ -1015,6 +1015,11 @@ impl OpenCADStudio {
|
||||
| "FRAME"
|
||||
| "IMAGEFRAME"
|
||||
| "PDFFRAME"
|
||||
| "PDFOSNAP"
|
||||
| "UOSNAP"
|
||||
| "PDFIMPORTMODE"
|
||||
| "PDFIMPORTFILTER"
|
||||
| "PDFIMPORTLAYERS"
|
||||
| "POINTCLOUDCLIPFRAME"
|
||||
| "XCLIPFRAME"
|
||||
| "WIPEOUTFRAME"
|
||||
@@ -1106,6 +1111,55 @@ impl OpenCADStudio {
|
||||
}
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
// Snapping to the geometry inside underlays (one switch for
|
||||
// PDF underlays and underlays in general).
|
||||
// The PDF Import Settings' options as bits.
|
||||
if matches!(name.as_str(), "PDFIMPORTMODE" | "PDFIMPORTFILTER" | "PDFIMPORTLAYERS") {
|
||||
use crate::modules::insert::pdf_import::{import_settings, set_import_settings, ImportLayers};
|
||||
let mut settings = import_settings();
|
||||
let (current, max) = match name.as_str() {
|
||||
"PDFIMPORTMODE" => (settings.mode(), 31),
|
||||
"PDFIMPORTFILTER" => (settings.filter(), 15),
|
||||
_ => (settings.layers_value(), 2),
|
||||
};
|
||||
if let Some(value) = &value {
|
||||
match value.parse::<i16>().ok().filter(|v| (0..=max).contains(v)) {
|
||||
Some(v) => {
|
||||
match name.as_str() {
|
||||
"PDFIMPORTMODE" => settings.set_mode(v),
|
||||
"PDFIMPORTFILTER" => settings.set_filter(v),
|
||||
_ => {
|
||||
settings.layers = [ImportLayers::Pdf, ImportLayers::Object, ImportLayers::Current][v as usize]
|
||||
}
|
||||
}
|
||||
set_import_settings(settings);
|
||||
}
|
||||
None => self.command_line.push_error(&format!("Requires an integer between 0 and {max}.")),
|
||||
}
|
||||
} 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(), "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)
|
||||
|
||||
@@ -47,6 +47,11 @@ fn color_value(color: acadrust::types::Color) -> Value {
|
||||
}
|
||||
pub(super) const NAMES: &[&str] = &[
|
||||
"close_modal",
|
||||
"pdf_dialog_ok",
|
||||
"pdf_layer_toggle",
|
||||
"pdf_page_select",
|
||||
"ribbon_tab",
|
||||
"ribbon_dropdown",
|
||||
"dialog_ok",
|
||||
"close_document",
|
||||
"toggle_properties",
|
||||
@@ -664,6 +669,43 @@ impl OpenCADStudio {
|
||||
let name = string(req, "name")?;
|
||||
let msg = match name {
|
||||
"close_modal" => Message::CloseModal,
|
||||
// The open PDF dialog's OK button.
|
||||
// Underlay Layers: switch a layer of the shown underlay.
|
||||
"pdf_layer_toggle" => Message::PdfDialog(
|
||||
crate::ui::window::pdf_dialogs::PdfDialogMsg::LayersToggle(string(req, "value")?.into()),
|
||||
),
|
||||
// Attach dialog: choose pages by index ("0,2").
|
||||
"pdf_page_select" => {
|
||||
let pages: Vec<usize> = string(req, "value")?
|
||||
.split(',')
|
||||
.filter_map(|p| p.trim().parse().ok())
|
||||
.collect();
|
||||
let Some(state) = self.pdf_attach.as_mut() else {
|
||||
return Err(failure("no_dialog", "The Attach dialog is not open"));
|
||||
};
|
||||
state.selected = pages;
|
||||
return Ok(Task::none());
|
||||
}
|
||||
// Bring a ribbon tab forward by module id ("pdf_underlay" for the
|
||||
// contextual underlay tab).
|
||||
"ribbon_tab" => {
|
||||
let id = string(req, "value")?;
|
||||
if !self.ribbon.select_by_id(id) {
|
||||
return Err(failure("no_tab", "No such ribbon tab"));
|
||||
}
|
||||
return Ok(Task::none());
|
||||
}
|
||||
"ribbon_dropdown" => Message::ToggleRibbonDropdown(string(req, "value")?.into()),
|
||||
"pdf_dialog_ok" => {
|
||||
use crate::ui::window::pdf_dialogs::PdfDialogMsg;
|
||||
Message::PdfDialog(match self.active_modal {
|
||||
Some(crate::app::ModalKind::PdfAttach) => PdfDialogMsg::AttachOk,
|
||||
Some(crate::app::ModalKind::UnderlayLayers) => PdfDialogMsg::LayersOk,
|
||||
Some(crate::app::ModalKind::PdfImportSettings) => PdfDialogMsg::SettingsOk,
|
||||
Some(crate::app::ModalKind::PdfImportFile) => PdfDialogMsg::ImportOk,
|
||||
_ => return Err(failure("no_dialog", "No PDF dialog is open")),
|
||||
})
|
||||
}
|
||||
// The open dialog's OK button.
|
||||
"dialog_ok" => match self.active_modal {
|
||||
Some(crate::app::ModalKind::XrefAttach) => Message::XrefAttach(
|
||||
|
||||
@@ -1199,7 +1199,7 @@ impl OpenCADStudio {
|
||||
let Some(entity) = tab.scene.document.get_entity(handle) else {
|
||||
continue;
|
||||
};
|
||||
let (min, max) = crate::scene::convert::tess::entity_bounds(entity);
|
||||
let (min, max) = crate::scene::convert::tess::entity_bounds_in(&tab.scene.document, entity);
|
||||
let metrics = tab
|
||||
.scene
|
||||
.meshes
|
||||
|
||||
@@ -493,6 +493,13 @@ pub(super) struct OpenCADStudio {
|
||||
drawing_units: Option<crate::ui::window::drawing_units::State>,
|
||||
/// Working copy of the Block Definition dialog; `None` while it is closed.
|
||||
block_definition: Option<crate::ui::window::block_definition::BlockDefinitionState>,
|
||||
/// PDF dialogs' working copies; `None` while closed.
|
||||
pdf_attach: Option<crate::ui::window::pdf_dialogs::PdfAttachState>,
|
||||
underlay_layers: Option<crate::ui::window::pdf_dialogs::UnderlayLayersState>,
|
||||
pdf_import_settings: Option<crate::modules::insert::pdf_import::PdfImportSettings>,
|
||||
pdf_import_file: Option<crate::ui::window::pdf_dialogs::PdfImportFileState>,
|
||||
/// The dialog Options was opened from; it comes back when Options closes.
|
||||
options_parent: Option<ModalKind>,
|
||||
/// Working copy of the Attach External Reference dialog; None while closed.
|
||||
xref_attach: Option<crate::ui::window::xref_attach::XrefAttachState>,
|
||||
/// Working copy of the Write Block (WBLOCK) dialog; `None` while it is closed.
|
||||
@@ -1884,6 +1891,10 @@ pub enum ModalKind {
|
||||
LayerTranslator,
|
||||
DrawingUnits,
|
||||
BlockDefinition,
|
||||
PdfAttach,
|
||||
UnderlayLayers,
|
||||
PdfImportSettings,
|
||||
PdfImportFile,
|
||||
XrefAttach,
|
||||
WriteBlock,
|
||||
GeometricTolerance,
|
||||
@@ -3804,6 +3815,14 @@ pub enum Message {
|
||||
// ── PDF Underlay ──────────────────────────────────────────────────────
|
||||
/// Open file-picker dialog for PDFATTACH command (async).
|
||||
PdfAttachPick,
|
||||
/// PDFIMPORT File: pick the PDF to import.
|
||||
PdfImportPick,
|
||||
/// The contextual PDF Underlay tab.
|
||||
RibbonSelectUnderlayTab,
|
||||
/// An edit in one of the PDF dialogs.
|
||||
PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg),
|
||||
UnderlayTab(crate::ui::ribbon::UnderlayTabMsg),
|
||||
PdfImportPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>),
|
||||
/// Result of the PDFATTACH file picker.
|
||||
PdfAttachPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>),
|
||||
|
||||
@@ -3982,6 +4001,11 @@ impl OpenCADStudio {
|
||||
layer_translator: None,
|
||||
drawing_units: None,
|
||||
block_definition: None,
|
||||
pdf_attach: None,
|
||||
underlay_layers: None,
|
||||
pdf_import_settings: None,
|
||||
pdf_import_file: None,
|
||||
options_parent: None,
|
||||
xref_attach: None,
|
||||
wblock: None,
|
||||
geometric_tolerance: None,
|
||||
|
||||
+29
-18
@@ -1498,25 +1498,29 @@ impl OpenCADStudio {
|
||||
xref_rows(&mut sections, ins, &br.xref_path, factor, overrides);
|
||||
}
|
||||
}
|
||||
// Underlay: name + path from the referenced definition.
|
||||
// Underlay: name, page, path and size from the definition.
|
||||
acadrust::EntityType::Underlay(ul) => {
|
||||
if let Some((name, path)) =
|
||||
doc.objects.iter().find_map(|(h, o)| match o {
|
||||
acadrust::objects::ObjectType::UnderlayDefinition(def)
|
||||
if *h == ul.definition_handle =>
|
||||
{
|
||||
let nm = if !def.name.is_empty() {
|
||||
def.name.clone()
|
||||
} else {
|
||||
def.page_name.clone()
|
||||
};
|
||||
Some((nm, def.file_path.clone()))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
set_row(&mut sections, "ul_name", name);
|
||||
set_row(&mut sections, "ul_path", path);
|
||||
use crate::entities::underlay as und;
|
||||
if let Some(def) = und::definition(ul, doc) {
|
||||
set_row(&mut sections, "ul_name", und::definition_display_name(def));
|
||||
set_row(&mut sections, "ul_page", und::page_of(def).to_string());
|
||||
set_row(
|
||||
&mut sections,
|
||||
"ul_path",
|
||||
und::display_path(&def.file_path),
|
||||
);
|
||||
}
|
||||
if let Some((w, h)) = und::shown_size(ul, doc) {
|
||||
set_row_value(
|
||||
&mut sections,
|
||||
"ul_width",
|
||||
crate::scene::model::object::PropValue::EditText(und::plain_number(w)),
|
||||
);
|
||||
set_row_value(
|
||||
&mut sections,
|
||||
"ul_height",
|
||||
crate::scene::model::object::PropValue::EditText(und::plain_number(h)),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Leader: text style / vertical text placement / overall
|
||||
@@ -2355,6 +2359,12 @@ impl OpenCADStudio {
|
||||
acadrust::EntityType::Dimension(
|
||||
acadrust::entities::Dimension::Angular3Pt(_),
|
||||
) => t!("3 Point Angular Dimension").into_owned(),
|
||||
acadrust::EntityType::Underlay(underlay) => match underlay.underlay_type {
|
||||
acadrust::entities::UnderlayType::Pdf => t!("PDF Underlay"),
|
||||
acadrust::entities::UnderlayType::Dwf => t!("DWF Underlay"),
|
||||
acadrust::entities::UnderlayType::Dgn => t!("DGN Underlay"),
|
||||
}
|
||||
.into_owned(),
|
||||
_ => entity_type_label(entity),
|
||||
};
|
||||
// A dynamic dimension shows only its constraint and text
|
||||
@@ -2619,6 +2629,7 @@ filter={:.1} local={:.1} aggregate={:.1} entities={}",
|
||||
let m_panel = t_all.map(|t| t.elapsed().as_secs_f64() * 1000.0);
|
||||
let t_ribbon = crate::perf::enabled().then(iced::time::Instant::now);
|
||||
self.sync_ribbon_from_selection();
|
||||
self.sync_underlay_tab();
|
||||
if let Some(t) = t_all {
|
||||
let ribbon_ms = t_ribbon.map_or(0.0, |r| r.elapsed().as_secs_f64() * 1000.0);
|
||||
let total_ms = t.elapsed().as_secs_f64() * 1000.0;
|
||||
|
||||
@@ -3455,6 +3455,38 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
} else {
|
||||
raw_val
|
||||
};
|
||||
// Underlay rows: reject what the reference rejects, and
|
||||
// write a Width / Height as the scale that produces it.
|
||||
if field.starts_with("ul_") {
|
||||
if let Err(message) =
|
||||
crate::entities::underlay::validate_property(field, &val)
|
||||
{
|
||||
self.command_line.push_error(message);
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
let (field, val) = match field {
|
||||
"ul_width" | "ul_height" => {
|
||||
let scale = handles.iter().find_map(|handle| {
|
||||
match self.tabs[i].scene.document.get_entity(*handle) {
|
||||
Some(acadrust::EntityType::Underlay(underlay)) => {
|
||||
crate::entities::underlay::size_to_scale(
|
||||
underlay,
|
||||
&self.tabs[i].scene.document,
|
||||
field,
|
||||
&val,
|
||||
)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
match scale {
|
||||
Some(scale) => ("ul_scale", scale),
|
||||
None => return Task::none(),
|
||||
}
|
||||
}
|
||||
_ => (field, val),
|
||||
};
|
||||
if matches!(
|
||||
field,
|
||||
"current_fit_point" | "current_control_point" | "pm_current_vertex"
|
||||
|
||||
@@ -990,6 +990,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
Err(msg) => self.command_line.push_error(msg.as_str()),
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.reseed_underlays();
|
||||
}
|
||||
XrefPaletteOp::Reload => {
|
||||
let base_dir: std::path::PathBuf = host
|
||||
@@ -1016,6 +1017,21 @@ 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.
|
||||
for (key, row_name, row_kind, _) in &picked {
|
||||
if row_kind != kind || row_name != name {
|
||||
continue;
|
||||
}
|
||||
let handle = acadrust::types::Handle::new(*key);
|
||||
if let Some(acadrust::objects::ObjectType::UnderlayDefinition(def)) =
|
||||
self.tabs[i].scene.document.objects.get_mut(&handle)
|
||||
{
|
||||
def.unloaded = false;
|
||||
}
|
||||
self.tabs[i].xref_unloaded.remove(key);
|
||||
}
|
||||
self.tabs[i].scene.reseed_underlays();
|
||||
} else if *kind != crate::io::xref_model::RefKind::DwgXref {
|
||||
self.command_line.push_error(crate::tf!(
|
||||
"{}: reload applies to drawing references only.",
|
||||
|
||||
+49
-21
@@ -128,12 +128,19 @@ impl OpenCADStudio {
|
||||
/// Close the active in-canvas modal (Plan B), mirroring what closing the
|
||||
/// old OS window did: a style editor discards its staged (un-applied)
|
||||
/// changes, and the ribbon tool that launched the dialog is de-highlighted.
|
||||
fn close_active_modal(&mut self) {
|
||||
pub(in crate::app) fn close_active_modal(&mut self) {
|
||||
self.mark_startup_modal_shown();
|
||||
use super::ModalKind::*;
|
||||
// Plot Style opened from PLOT behaves as a child modal.
|
||||
// Closing it restores the parent Plot dialog instead of returning
|
||||
// to the drawing.
|
||||
// Options opened from a PDF dialog returns to it.
|
||||
if self.active_modal == Some(Options) {
|
||||
if let Some(parent) = self.options_parent.take() {
|
||||
self.active_modal = Some(parent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if self.active_modal == Some(Plotstyle) {
|
||||
if let Some((plot_offset, plot_resize)) = self.plotstyle_parent_plot_geometry.take() {
|
||||
self.active_modal = Some(Plot);
|
||||
@@ -208,6 +215,10 @@ impl OpenCADStudio {
|
||||
}
|
||||
Some(GeometricTolerance) => self.geometric_tolerance = None,
|
||||
Some(BlockDefinition) => self.block_definition = None,
|
||||
Some(PdfAttach) => self.pdf_attach = None,
|
||||
Some(UnderlayLayers) => self.underlay_layers = None,
|
||||
Some(PdfImportSettings) => self.pdf_import_settings = None,
|
||||
Some(PdfImportFile) => self.pdf_import_file = None,
|
||||
Some(XrefAttach) => self.xref_attach = None,
|
||||
Some(WriteBlock) => self.wblock = None,
|
||||
Some(Hyperlink) => {
|
||||
@@ -1315,32 +1326,43 @@ impl OpenCADStudio {
|
||||
),
|
||||
|
||||
Message::PdfAttachPickResult(Ok((path, bytes))) => {
|
||||
use crate::command::CadCommand;
|
||||
use crate::modules::insert::pdf_attach::PdfAttachCommand;
|
||||
use acadrust::objects::{ObjectType, UnderlayDefinition};
|
||||
|
||||
let i = self.active_tab;
|
||||
let path_str = path.to_string_lossy().into_owned();
|
||||
let _ = i;
|
||||
crate::scene::model::pdf_raster::register_source(&path_str, bytes);
|
||||
|
||||
let definition_handle = self.tabs[i].scene.document.allocate_handle();
|
||||
|
||||
let mut definition = UnderlayDefinition::pdf(&path_str, "1");
|
||||
definition.handle = definition_handle;
|
||||
|
||||
self.tabs[i].scene.document.objects.insert(
|
||||
definition_handle,
|
||||
ObjectType::UnderlayDefinition(definition),
|
||||
);
|
||||
|
||||
let cmd = PdfAttachCommand::new(definition_handle);
|
||||
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
|
||||
// Pages, placement and path type are chosen in the Attach
|
||||
// dialog; the definition is created with the underlay.
|
||||
self.open_pdf_attach_dialog(&path_str);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PdfImportPick => Task::perform(
|
||||
async {
|
||||
let handle = crate::sys::file_dialog()
|
||||
.set_title(crate::t!("Select PDF File").as_ref())
|
||||
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf", "PDF"])
|
||||
.pick_file()
|
||||
.await;
|
||||
match handle {
|
||||
Some(h) => {
|
||||
let path = crate::sys::handle_path(&h);
|
||||
let bytes = std::sync::Arc::new(h.read().await);
|
||||
Ok((path, bytes))
|
||||
}
|
||||
None => Err("Cancelled".to_string()),
|
||||
}
|
||||
},
|
||||
Message::PdfImportPickResult,
|
||||
),
|
||||
Message::PdfImportPickResult(Ok((path, bytes))) => {
|
||||
let i = self.active_tab;
|
||||
let path_str = path.to_string_lossy().into_owned();
|
||||
let _ = i;
|
||||
crate::scene::model::pdf_raster::register_source(&path_str, bytes);
|
||||
self.open_pdf_import_file(&path_str);
|
||||
Task::none()
|
||||
}
|
||||
Message::PdfImportPickResult(Err(_)) => Task::none(),
|
||||
Message::PdfAttachPickResult(Err(e)) => {
|
||||
if e != "Cancelled" {
|
||||
self.command_line
|
||||
@@ -1750,6 +1772,12 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::RibbonSelectUnderlayTab => {
|
||||
self.ribbon.select_underlay_tab();
|
||||
Task::none()
|
||||
}
|
||||
Message::UnderlayTab(message) => self.update_underlay_tab(message),
|
||||
Message::PdfDialog(message) => self.update_pdf_dialog(message),
|
||||
Message::RibbonSelectTab(idx) => {
|
||||
self.ribbon.select(idx);
|
||||
Task::none()
|
||||
|
||||
@@ -24,6 +24,10 @@ impl OpenCADStudio {
|
||||
Some(K::LayerTranslator) => crate::t!("Layer Translator").into_owned(),
|
||||
Some(K::DrawingUnits) => crate::t!("Drawing Units").into_owned(),
|
||||
Some(K::BlockDefinition) => crate::t!("Block Definition").into_owned(),
|
||||
Some(K::PdfAttach) => crate::t!("Attach PDF Underlay").into_owned(),
|
||||
Some(K::UnderlayLayers) => crate::t!("Underlay Layers").into_owned(),
|
||||
Some(K::PdfImportSettings) => crate::t!("PDF Import Settings").into_owned(),
|
||||
Some(K::PdfImportFile) => crate::t!("Import PDF").into_owned(),
|
||||
Some(K::XrefAttach) => crate::t!("Attach External Reference").into_owned(),
|
||||
Some(K::WriteBlock) => crate::t!("Write Block").into_owned(),
|
||||
Some(K::GeometricTolerance) => crate::t!("Geometric Tolerance").into_owned(),
|
||||
@@ -508,6 +512,31 @@ impl OpenCADStudio {
|
||||
crate::ui::window::block_definition::view_window(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::PdfAttach => {
|
||||
let state = self.pdf_attach.as_ref()?;
|
||||
let height = if state.details { 520 } else { 440 };
|
||||
sized_flow(ex, 820, height, |flow| {
|
||||
crate::ui::window::pdf_dialogs::view_attach(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::UnderlayLayers => {
|
||||
let state = self.underlay_layers.as_ref()?;
|
||||
sized_flow(ex, 500, 500, |flow| {
|
||||
crate::ui::window::pdf_dialogs::view_layers(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::PdfImportSettings => {
|
||||
let settings = self.pdf_import_settings.as_ref()?;
|
||||
sized_flow(ex, 600, 360, |flow| {
|
||||
crate::ui::window::pdf_dialogs::view_import_settings(settings, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::PdfImportFile => {
|
||||
let state = self.pdf_import_file.as_ref()?;
|
||||
sized_flow(ex, 900, 560, |flow| {
|
||||
crate::ui::window::pdf_dialogs::view_import_file(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::XrefAttach => {
|
||||
let state = self.xref_attach.as_ref()?;
|
||||
let height = if state.details { 520 } else { 450 };
|
||||
|
||||
Reference in new issue
Block a user