forked from 1b-agent/opencadstudio-upstream
style: apply cargo fmt across workspace
This commit is contained in:
1 parent
85710e5454
commit
9cf9ff2c5c
531 files changed
+57740
-33906
No files matched your search
@@ -28,9 +28,16 @@ impl OpenCADStudio {
|
||||
Some(Task::none())
|
||||
}
|
||||
"-ATTDEF" => {
|
||||
let defaults =
|
||||
crate::scene::creation_style::current_text_defaults(&self.tabs[i].scene.document);
|
||||
let styles: Vec<_> = self.tabs[i].scene.document.text_styles.iter().cloned().collect();
|
||||
let defaults = crate::scene::creation_style::current_text_defaults(
|
||||
&self.tabs[i].scene.document,
|
||||
);
|
||||
let styles: Vec<_> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.text_styles
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let command = AttdefCommand::new(
|
||||
"-ATTDEF",
|
||||
defaults.style_name,
|
||||
@@ -97,7 +104,8 @@ impl OpenCADStudio {
|
||||
Some(v) => match v.parse::<u8>().ok().filter(|v| *v <= 63) {
|
||||
Some(v) => attdef::session().aflags = v,
|
||||
None => {
|
||||
self.command_line.push_error("Requires an integer between 0 and 63.");
|
||||
self.command_line
|
||||
.push_error("Requires an integer between 0 and 63.");
|
||||
self.command_line
|
||||
.push_output(&format!("Enter new value for AFLAGS <{current}>:"));
|
||||
self.pending_setvar = Some("AFLAGS".to_string());
|
||||
@@ -145,7 +153,8 @@ impl OpenCADStudio {
|
||||
/// The Edit Attribute Definition dialog for `handle`.
|
||||
pub(in crate::app) fn open_attdef_edit(&mut self, handle: codec::Handle) {
|
||||
let i = self.active_tab;
|
||||
let Some(codec::EntityType::AttributeDefinition(a)) = self.tabs[i].scene.document.get_entity(handle)
|
||||
let Some(codec::EntityType::AttributeDefinition(a)) =
|
||||
self.tabs[i].scene.document.get_entity(handle)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -175,11 +184,15 @@ impl OpenCADStudio {
|
||||
}
|
||||
AttdefDialogMsg::EditInsertField => {
|
||||
self.active_modal = None;
|
||||
self.open_field_dialog(crate::ui::window::field_dialog::FieldTarget::AttdefEdit);
|
||||
self.open_field_dialog(
|
||||
crate::ui::window::field_dialog::FieldTarget::AttdefEdit,
|
||||
);
|
||||
}
|
||||
AttdefDialogMsg::Help => self.command_line.push_info(
|
||||
crate::t!("Changes the tag, prompt and default value of an attribute definition.")
|
||||
.as_ref(),
|
||||
crate::t!(
|
||||
"Changes the tag, prompt and default value of an attribute definition."
|
||||
)
|
||||
.as_ref(),
|
||||
),
|
||||
AttdefDialogMsg::EditOk => return self.attdef_edit_ok(i),
|
||||
AttdefDialogMsg::DismissError => edit.error = None,
|
||||
|
||||
+49
-46
@@ -38,8 +38,10 @@ impl OpenCADStudio {
|
||||
base: glam::DVec3,
|
||||
) -> usize {
|
||||
// Clone + dep-capture live in the shared kernel; storage stays here.
|
||||
let (entities, deps) =
|
||||
super::super::command_driver::copy_to_clipboard_kernel(&self.tabs[i].scene.document, handles);
|
||||
let (entities, deps) = super::super::command_driver::copy_to_clipboard_kernel(
|
||||
&self.tabs[i].scene.document,
|
||||
handles,
|
||||
);
|
||||
let count = entities.len();
|
||||
self.clipboard_base = base;
|
||||
self.clipboard = entities;
|
||||
@@ -492,7 +494,8 @@ impl OpenCADStudio {
|
||||
// INSERT opens the Blocks palette on the drawing's blocks;
|
||||
// -INSERT runs on the command line.
|
||||
"INSERT" => {
|
||||
self.command_line.push_output("*Insert a block from the Blocks palette");
|
||||
self.command_line
|
||||
.push_output("*Insert a block from the Blocks palette");
|
||||
self.open_blocks_palette(Some(crate::ui::window::block_palette::Tab::Current));
|
||||
}
|
||||
|
||||
@@ -519,7 +522,8 @@ impl OpenCADStudio {
|
||||
cmd if cmd.starts_with("_-INSERTFILE ") => {
|
||||
use crate::command::CadCommand;
|
||||
use crate::modules::insert::insert_block::InsertBlockCommand;
|
||||
let path = std::path::PathBuf::from(cmd["_-INSERTFILE ".len()..].trim().trim_matches('"'));
|
||||
let path =
|
||||
std::path::PathBuf::from(cmd["_-INSERTFILE ".len()..].trim().trim_matches('"'));
|
||||
match self.import_file_as_block(path) {
|
||||
Ok(name) => {
|
||||
let units = self
|
||||
@@ -645,11 +649,10 @@ impl OpenCADStudio {
|
||||
.iter()
|
||||
.any(|a| a.tag.eq_ignore_ascii_case(tag))
|
||||
{
|
||||
ins.attributes
|
||||
.push(codec::entities::AttributeEntity::new(
|
||||
tag.clone(),
|
||||
default.clone(),
|
||||
));
|
||||
ins.attributes.push(codec::entities::AttributeEntity::new(
|
||||
tag.clone(),
|
||||
default.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
synced += 1;
|
||||
@@ -702,7 +705,12 @@ impl OpenCADStudio {
|
||||
// A block picked in the ribbon gallery.
|
||||
cmd if cmd.starts_with("_BLOCKINSERT ") => {
|
||||
let name = cmd["_BLOCKINSERT ".len()..].trim().to_string();
|
||||
return Some(self.palette_insert(crate::ui::window::block_palette::Item::Current(name), None));
|
||||
return Some(
|
||||
self.palette_insert(
|
||||
crate::ui::window::block_palette::Item::Current(name),
|
||||
None,
|
||||
),
|
||||
);
|
||||
}
|
||||
cmd if cmd.starts_with("_BLOCKSPALETTE ") => {
|
||||
use crate::ui::window::block_palette::Tab;
|
||||
@@ -737,10 +745,16 @@ impl OpenCADStudio {
|
||||
}
|
||||
// A file named after it skips the picker.
|
||||
cmd if cmd.starts_with("_PDFIMPORTFILE ") => {
|
||||
let path = cmd["_PDFIMPORTFILE ".len()..].trim().trim_matches('"').to_string();
|
||||
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));
|
||||
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.")),
|
||||
@@ -760,8 +774,11 @@ impl OpenCADStudio {
|
||||
"POINTCLOUDCROP" | "POINTCLOUDUNCROP" => {
|
||||
use crate::command::CadCommand;
|
||||
use crate::modules::insert::pc_crop::PointCloudCropCommand;
|
||||
let command =
|
||||
if cmd == "POINTCLOUDUNCROP" { PointCloudCropCommand::uncrop() } else { PointCloudCropCommand::new() };
|
||||
let command = if cmd == "POINTCLOUDUNCROP" {
|
||||
PointCloudCropCommand::uncrop()
|
||||
} else {
|
||||
PointCloudCropCommand::new()
|
||||
};
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
@@ -810,7 +827,10 @@ impl OpenCADStudio {
|
||||
// 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(),
|
||||
cmd.split_whitespace()
|
||||
.next()
|
||||
.map(|v| v.to_ascii_uppercase())
|
||||
.as_deref(),
|
||||
Some("PDFATTACH") | Some("-PDFATTACH")
|
||||
) =>
|
||||
{
|
||||
@@ -859,14 +879,12 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
format!("{}/new_block.dwg", default_folder.replace('\\', "/"))
|
||||
};
|
||||
self.wblock = Some(
|
||||
crate::ui::window::wblock::WblockState::new(
|
||||
existing_blocks,
|
||||
handles,
|
||||
default_unit,
|
||||
default_path,
|
||||
),
|
||||
);
|
||||
self.wblock = Some(crate::ui::window::wblock::WblockState::new(
|
||||
existing_blocks,
|
||||
handles,
|
||||
default_unit,
|
||||
default_path,
|
||||
));
|
||||
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
|
||||
}
|
||||
|
||||
@@ -1079,8 +1097,7 @@ impl OpenCADStudio {
|
||||
self.tabs[i].xref_unloaded.remove(key);
|
||||
self.tabs[i].xref_stat_cache.remove(key);
|
||||
}
|
||||
let handles: rustc_hash::FxHashSet<codec::types::Handle> = self.tabs
|
||||
[i]
|
||||
let handles: rustc_hash::FxHashSet<codec::types::Handle> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.block_records
|
||||
@@ -1928,12 +1945,8 @@ mod tests {
|
||||
.objects
|
||||
.insert(h, ObjectType::ImageDefinition(def));
|
||||
// Reference it so collect_entries lists it.
|
||||
let mut img = codec::entities::RasterImage::new(
|
||||
"img.png",
|
||||
codec::types::Vector3::ZERO,
|
||||
8.0,
|
||||
8.0,
|
||||
);
|
||||
let mut img =
|
||||
codec::entities::RasterImage::new("img.png", codec::types::Vector3::ZERO, 8.0, 8.0);
|
||||
img.definition_handle = Some(h);
|
||||
app.tabs[i]
|
||||
.scene
|
||||
@@ -1963,12 +1976,8 @@ mod tests {
|
||||
.document
|
||||
.objects
|
||||
.insert(h, ObjectType::ImageDefinition(def));
|
||||
let mut img = codec::entities::RasterImage::new(
|
||||
"img.png",
|
||||
codec::types::Vector3::ZERO,
|
||||
8.0,
|
||||
8.0,
|
||||
);
|
||||
let mut img =
|
||||
codec::entities::RasterImage::new("img.png", codec::types::Vector3::ZERO, 8.0, 8.0);
|
||||
img.definition_handle = Some(h);
|
||||
app.tabs[i]
|
||||
.scene
|
||||
@@ -2069,9 +2078,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let mut line = codec::entities::Line::new();
|
||||
line.common.layer = "WALLS".to_string();
|
||||
xref_doc
|
||||
.add_entity(codec::EntityType::Line(line))
|
||||
.unwrap();
|
||||
xref_doc.add_entity(codec::EntityType::Line(line)).unwrap();
|
||||
let bytes = crate::io::save_to_bytes(&xref_doc, "dwg", xref_doc.version).unwrap();
|
||||
let xref_path = dir.join("plan.dwg");
|
||||
std::fs::write(&xref_path, &bytes).unwrap();
|
||||
@@ -2104,9 +2111,7 @@ mod tests {
|
||||
let mut line = codec::entities::Line::new();
|
||||
line.common.plotstyle_flags = 0b11;
|
||||
line.common.plotstyle_handle = Some(xref_doc.allocate_handle());
|
||||
xref_doc
|
||||
.add_entity(codec::EntityType::Line(line))
|
||||
.unwrap();
|
||||
xref_doc.add_entity(codec::EntityType::Line(line)).unwrap();
|
||||
let bytes = crate::io::save_to_bytes(&xref_doc, "dwg", xref_doc.version).unwrap();
|
||||
let xref_path = dir.join("plan.dwg");
|
||||
std::fs::write(&xref_path, &bytes).unwrap();
|
||||
@@ -2347,9 +2352,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let mut line = codec::entities::Line::new();
|
||||
line.common.layer = "WALLS".to_string();
|
||||
xref_doc
|
||||
.add_entity(codec::EntityType::Line(line))
|
||||
.unwrap();
|
||||
xref_doc.add_entity(codec::EntityType::Line(line)).unwrap();
|
||||
let bytes = crate::io::save_to_bytes(&xref_doc, "dwg", xref_doc.version).unwrap();
|
||||
let xref_path = dir.join("plan.dwg");
|
||||
std::fs::write(&xref_path, &bytes).unwrap();
|
||||
|
||||
@@ -42,7 +42,8 @@ impl OpenCADStudio {
|
||||
match name.as_str() {
|
||||
"BLOCKSTATE" => {
|
||||
let state = self.tabs[i].refedit_session.is_some() as u8;
|
||||
self.command_line.push_output(&format!("BLOCKSTATE = {state} (read only)"));
|
||||
self.command_line
|
||||
.push_output(&format!("BLOCKSTATE = {state} (read only)"));
|
||||
}
|
||||
"INSNAME" | "BLOCKNAVIGATE" => {
|
||||
let current = if name == "INSNAME" {
|
||||
@@ -52,7 +53,11 @@ impl OpenCADStudio {
|
||||
};
|
||||
match value {
|
||||
Some(v) => {
|
||||
let v = if v == "." { String::new() } else { v.trim_matches('"').to_string() };
|
||||
let v = if v == "." {
|
||||
String::new()
|
||||
} else {
|
||||
v.trim_matches('"').to_string()
|
||||
};
|
||||
if name == "INSNAME" {
|
||||
self.insname = v;
|
||||
} else {
|
||||
|
||||
+209
-67
@@ -6,14 +6,21 @@ use crate::app::{Message, OpenCADStudio};
|
||||
use crate::command::CadCommand;
|
||||
use crate::modules::annotate::count_cmd::{CountCommand, CountTableCommand, UpdateFieldCommand};
|
||||
use crate::modules::annotate::field_cmd::FieldPlaceCommand;
|
||||
use crate::ui::window::count_palette::{aci_rgba, CountMode, CountMsg, CountTarget, RowAction, AREA_LAYER};
|
||||
use crate::ui::window::count_palette::{
|
||||
aci_rgba, CountMode, CountMsg, CountTarget, RowAction, AREA_LAYER,
|
||||
};
|
||||
use codec::count::{area_json, block_json, single_json, CountKey};
|
||||
use codec::{EntityType, Handle};
|
||||
use iced::Task;
|
||||
|
||||
/// The variables this family answers for.
|
||||
pub(super) const COUNT_SYSVARS: &[&str] =
|
||||
&["COUNTNUMBER", "COUNTSERVICE", "COUNTPALETTESTATE", "COUNTCOLOR", "COUNTERRORCOLOR"];
|
||||
pub(super) const COUNT_SYSVARS: &[&str] = &[
|
||||
"COUNTNUMBER",
|
||||
"COUNTSERVICE",
|
||||
"COUNTPALETTESTATE",
|
||||
"COUNTCOLOR",
|
||||
"COUNTERRORCOLOR",
|
||||
];
|
||||
|
||||
fn hex(h: Handle) -> String {
|
||||
format!("{:X}", h.value())
|
||||
@@ -56,18 +63,27 @@ impl OpenCADStudio {
|
||||
match verb.as_str() {
|
||||
"COUNT" => {
|
||||
let preselected = self.tabs[i].scene.selected_handles_in_order();
|
||||
let current = matches!(rest.trim_start_matches('_').to_ascii_uppercase().as_str(), "C");
|
||||
let current = matches!(
|
||||
rest.trim_start_matches('_').to_ascii_uppercase().as_str(),
|
||||
"C"
|
||||
);
|
||||
if current && !preselected.is_empty() {
|
||||
// Count Selection: the selection is the target.
|
||||
let found = preselected.len();
|
||||
self.command_line.push_output(&crate::tf!("{found} found"));
|
||||
let handles: Vec<String> = preselected.iter().map(|h| hex(*h)).collect();
|
||||
self.tabs[i].scene.deselect_all();
|
||||
return Some(self.dispatch_command(&format!("_COUNTRUN C T {}", handles.join(","))));
|
||||
return Some(
|
||||
self.dispatch_command(&format!("_COUNTRUN C T {}", handles.join(","))),
|
||||
);
|
||||
}
|
||||
// Plain COUNT asks for its targets itself.
|
||||
self.tabs[i].scene.deselect_all();
|
||||
let command = if current { CountCommand::with_area("C") } else { CountCommand::new(false) };
|
||||
let command = if current {
|
||||
CountCommand::with_area("C")
|
||||
} else {
|
||||
CountCommand::new(false)
|
||||
};
|
||||
Some(self.start_count_command(i, Box::new(command)))
|
||||
}
|
||||
"COUNTAREA" => Some(self.start_count_command(i, Box::new(CountCommand::new(true)))),
|
||||
@@ -92,9 +108,9 @@ impl OpenCADStudio {
|
||||
match self.count_field_code(i) {
|
||||
Some(code) => return Some(self.start_count_field(i, code)),
|
||||
None if self.tabs[i].count.is_some() => {}
|
||||
None => self
|
||||
.command_line
|
||||
.push_error(&crate::t!("** COUNTFIELD command only available during Count. **")),
|
||||
None => self.command_line.push_error(&crate::t!(
|
||||
"** COUNTFIELD command only available during Count. **"
|
||||
)),
|
||||
}
|
||||
Some(Task::none())
|
||||
}
|
||||
@@ -150,7 +166,8 @@ impl OpenCADStudio {
|
||||
mode.cursor = None;
|
||||
self.tabs[i].count = Some(mode);
|
||||
self.apply_count_display(i);
|
||||
self.command_line.push_output(&crate::t!("count area is active."));
|
||||
self.command_line
|
||||
.push_output(&crate::t!("count area is active."));
|
||||
}
|
||||
Some(Task::none())
|
||||
}
|
||||
@@ -158,7 +175,11 @@ impl OpenCADStudio {
|
||||
let (point, names) = rest.split_once(' ').unwrap_or((rest, ""));
|
||||
let p: Vec<f64> = point.split(',').filter_map(|v| v.parse().ok()).collect();
|
||||
if p.len() == 3 {
|
||||
let names: Vec<String> = names.split('|').filter(|n| !n.is_empty()).map(str::to_string).collect();
|
||||
let names: Vec<String> = names
|
||||
.split('|')
|
||||
.filter(|n| !n.is_empty())
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
self.place_count_table(i, [p[0], p[1], p[2]], names);
|
||||
}
|
||||
Some(Task::none())
|
||||
@@ -193,7 +214,8 @@ impl OpenCADStudio {
|
||||
_ => None,
|
||||
};
|
||||
if let Some(v) = read_only {
|
||||
self.command_line.push_output(&crate::tf!("{name} = {v} (read only)"));
|
||||
self.command_line
|
||||
.push_output(&crate::tf!("{name} = {v} (read only)"));
|
||||
return;
|
||||
}
|
||||
let (current, min, max) = match name {
|
||||
@@ -202,7 +224,8 @@ impl OpenCADStudio {
|
||||
_ => (self.count_palette.error_color as i32, 1, 255),
|
||||
};
|
||||
let ask = |app: &mut Self| {
|
||||
app.command_line.push_output(&crate::tf!("Enter new value for {name} <{current}>:"));
|
||||
app.command_line
|
||||
.push_output(&crate::tf!("Enter new value for {name} <{current}>:"));
|
||||
app.pending_setvar = Some(name.to_string());
|
||||
};
|
||||
let Some(value) = value else {
|
||||
@@ -237,7 +260,8 @@ impl OpenCADStudio {
|
||||
self.ribbon.set_count_palette(open);
|
||||
if open {
|
||||
if self.dock.location(id).is_none() {
|
||||
self.dock.dock(id, crate::app::config::DockSide::Right, usize::MAX);
|
||||
self.dock
|
||||
.dock(id, crate::app::config::DockSide::Right, usize::MAX);
|
||||
}
|
||||
self.dock_expanded = Some(id);
|
||||
} else if self.dock_expanded == Some(id) {
|
||||
@@ -248,19 +272,26 @@ impl OpenCADStudio {
|
||||
/// Recolour the drawing for count mode: counted references, duplicates,
|
||||
/// the rest faded (nothing changes without a target).
|
||||
pub(in crate::app) fn apply_count_display(&mut self, i: usize) {
|
||||
let computed = self.tabs[i].count.as_ref().map(|m| m.compute(&self.tabs[i].scene.document));
|
||||
let computed = self.tabs[i]
|
||||
.count
|
||||
.as_ref()
|
||||
.map(|m| m.compute(&self.tabs[i].scene.document));
|
||||
if let (Some(mode), Some(c)) = (self.tabs[i].count.as_mut(), computed) {
|
||||
mode.cache = Some(c);
|
||||
}
|
||||
let display = self.tabs[i].count.as_ref().filter(|m| m.target.is_some()).map(|m| {
|
||||
let r = m.result(&self.tabs[i].scene.document);
|
||||
crate::scene::CountDisplay {
|
||||
counted: r.counted.iter().copied().collect(),
|
||||
errors: r.errors.iter().chain(&r.overlapped).copied().collect(),
|
||||
color: aci_rgba(self.count_palette.color),
|
||||
error_color: aci_rgba(self.count_palette.error_color),
|
||||
}
|
||||
});
|
||||
let display = self.tabs[i]
|
||||
.count
|
||||
.as_ref()
|
||||
.filter(|m| m.target.is_some())
|
||||
.map(|m| {
|
||||
let r = m.result(&self.tabs[i].scene.document);
|
||||
crate::scene::CountDisplay {
|
||||
counted: r.counted.iter().copied().collect(),
|
||||
errors: r.errors.iter().chain(&r.overlapped).copied().collect(),
|
||||
color: aci_rgba(self.count_palette.color),
|
||||
error_color: aci_rgba(self.count_palette.error_color),
|
||||
}
|
||||
});
|
||||
self.tabs[i].scene.set_count_display(display);
|
||||
let epoch = self.tabs[i].scene.geometry_epoch;
|
||||
if let Some(mode) = self.tabs[i].count.as_mut() {
|
||||
@@ -271,7 +302,11 @@ impl OpenCADStudio {
|
||||
/// After the drawing changes, count mode colours its references again.
|
||||
pub(in crate::app) fn refresh_count_if_stale(&mut self) {
|
||||
let i = self.active_tab;
|
||||
let Some(mode) = self.tabs[i].count.as_ref().filter(|m| m.epoch != self.tabs[i].scene.geometry_epoch) else {
|
||||
let Some(mode) = self.tabs[i]
|
||||
.count
|
||||
.as_ref()
|
||||
.filter(|m| m.epoch != self.tabs[i].scene.geometry_epoch)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if let Some((h, _)) = mode.boundary {
|
||||
@@ -294,7 +329,11 @@ impl OpenCADStudio {
|
||||
}
|
||||
// An edited boundary moves the area with it.
|
||||
Some(EntityType::LwPolyline(pl)) => {
|
||||
let ring: Vec<[f64; 2]> = pl.vertices.iter().map(|v| [v.location.x, v.location.y]).collect();
|
||||
let ring: Vec<[f64; 2]> = pl
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|v| [v.location.x, v.location.y])
|
||||
.collect();
|
||||
if let Some(mode) = self.tabs[i].count.as_mut() {
|
||||
mode.area = Some(ring);
|
||||
}
|
||||
@@ -388,12 +427,20 @@ impl OpenCADStudio {
|
||||
tokens: &[&str],
|
||||
) -> Option<(Option<Vec<[f64; 2]>>, Option<(Handle, bool)>)> {
|
||||
let kind = tokens.first().copied().unwrap_or("E");
|
||||
let points = || tokens[1..].iter().map_while(|t| parse_xy(t)).collect::<Vec<_>>();
|
||||
let points = || {
|
||||
tokens[1..]
|
||||
.iter()
|
||||
.map_while(|t| parse_xy(t))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
match kind {
|
||||
"E" => Some((None, None)),
|
||||
"K" => {
|
||||
let mode = self.tabs[i].count.as_ref();
|
||||
Some((mode.and_then(|m| m.area.clone()), mode.and_then(|m| m.boundary)))
|
||||
Some((
|
||||
mode.and_then(|m| m.area.clone()),
|
||||
mode.and_then(|m| m.boundary),
|
||||
))
|
||||
}
|
||||
// Current area counts all of model space (the view and an area
|
||||
// in use take no part).
|
||||
@@ -450,7 +497,10 @@ impl OpenCADStudio {
|
||||
/// counts the targets and enters count mode.
|
||||
fn count_run(&mut self, i: usize, rest: &str) {
|
||||
let tokens: Vec<&str> = rest.split_whitespace().collect();
|
||||
let split = tokens.iter().position(|t| *t == "L" || *t == "T").unwrap_or(tokens.len());
|
||||
let split = tokens
|
||||
.iter()
|
||||
.position(|t| *t == "L" || *t == "T")
|
||||
.unwrap_or(tokens.len());
|
||||
let (area_tokens, tail) = tokens.split_at(split);
|
||||
let targets: Vec<Handle> = match tail {
|
||||
["T", handles, ..] => handles
|
||||
@@ -479,9 +529,21 @@ impl OpenCADStudio {
|
||||
})
|
||||
.collect();
|
||||
let target = match blocks.first() {
|
||||
Some(first) if blocks.len() == targets.len() && blocks.iter().all(|b| b.eq_ignore_ascii_case(first)) => {
|
||||
let name = doc.block_records.get(first).map(|b| b.name.clone()).unwrap_or_else(|| first.to_string());
|
||||
Some(CountTarget::Block { name, reference: targets.first().copied(), matching: [false; 3], picked: true })
|
||||
Some(first)
|
||||
if blocks.len() == targets.len()
|
||||
&& blocks.iter().all(|b| b.eq_ignore_ascii_case(first)) =>
|
||||
{
|
||||
let name = doc
|
||||
.block_records
|
||||
.get(first)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_else(|| first.to_string());
|
||||
Some(CountTarget::Block {
|
||||
name,
|
||||
reference: targets.first().copied(),
|
||||
matching: [false; 3],
|
||||
picked: true,
|
||||
})
|
||||
}
|
||||
None if targets.is_empty() => None,
|
||||
_ => Some(CountTarget::Group(targets.clone())),
|
||||
@@ -492,10 +554,16 @@ impl OpenCADStudio {
|
||||
if let Some(old) = self.tabs[i].count.take().filter(|o| o.boundary != boundary) {
|
||||
self.drop_count_boundary(i, old.boundary);
|
||||
}
|
||||
let mode = CountMode { area, boundary, target, ..CountMode::default() };
|
||||
let mode = CountMode {
|
||||
area,
|
||||
boundary,
|
||||
target,
|
||||
..CountMode::default()
|
||||
};
|
||||
if let Some(name) = mode.target_name() {
|
||||
let n = mode.count(&self.tabs[i].scene.document);
|
||||
self.command_line.push_output(&crate::tf!("{name} ...... {n}"));
|
||||
self.command_line
|
||||
.push_output(&crate::tf!("{name} ...... {n}"));
|
||||
}
|
||||
self.tabs[i].count = Some(mode);
|
||||
self.apply_count_display(i);
|
||||
@@ -508,11 +576,12 @@ impl OpenCADStudio {
|
||||
let mode = self.tabs[i].count.as_ref()?;
|
||||
match mode.target.as_ref()? {
|
||||
CountTarget::Group(handles) => Some(format!("\\AcCount {}", single_json(handles))),
|
||||
CountTarget::Block { reference: Some(r), matching: [false, false, false], picked: true, .. }
|
||||
if mode.boundary.is_none() =>
|
||||
{
|
||||
Some(format!("\\AcCount {}", single_json(&[*r])))
|
||||
}
|
||||
CountTarget::Block {
|
||||
reference: Some(r),
|
||||
matching: [false, false, false],
|
||||
picked: true,
|
||||
..
|
||||
} if mode.boundary.is_none() => Some(format!("\\AcCount {}", single_json(&[*r]))),
|
||||
CountTarget::Block { name, .. } => {
|
||||
let instances = mode.instances(&self.tabs[i].scene.document);
|
||||
Some(self.block_field_code(i, name, &mode.key(&instances)))
|
||||
@@ -531,13 +600,21 @@ impl OpenCADStudio {
|
||||
/// Place a count field as multi-line text (`Specify start point or [Height/Justify]:`).
|
||||
pub(in crate::app) fn start_count_field(&mut self, i: usize, code: String) -> Task<Message> {
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let value = crate::entities::field::evaluate(doc, &code, &[], None).unwrap_or_else(|| "####".into());
|
||||
let value = crate::entities::field::evaluate(doc, &code, &[], None)
|
||||
.unwrap_or_else(|| "####".into());
|
||||
let defaults = crate::scene::creation_style::current_text_defaults(doc);
|
||||
self.command_line.push_output(&crate::tf!(
|
||||
"MTEXT Current text style: \"{}\" Text height: {:.4}",
|
||||
defaults.style_name, defaults.height
|
||||
defaults.style_name,
|
||||
defaults.height
|
||||
));
|
||||
let command = FieldPlaceCommand::new(value, (code, Vec::new()), defaults.style_name, defaults.height).named("MTEXT");
|
||||
let command = FieldPlaceCommand::new(
|
||||
value,
|
||||
(code, Vec::new()),
|
||||
defaults.style_name,
|
||||
defaults.height,
|
||||
)
|
||||
.named("MTEXT");
|
||||
self.start_count_command(i, Box::new(command))
|
||||
}
|
||||
|
||||
@@ -563,7 +640,11 @@ impl OpenCADStudio {
|
||||
Some(EntityType::Table(t)) => t
|
||||
.block_record_handle
|
||||
.and_then(|r| doc.block_records.iter().find(|b| b.handle == r))
|
||||
.or_else(|| doc.block_records.get(&t.block_name).filter(|_| !t.block_name.is_empty())),
|
||||
.or_else(|| {
|
||||
doc.block_records
|
||||
.get(&t.block_name)
|
||||
.filter(|_| !t.block_name.is_empty())
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(record) = record {
|
||||
@@ -573,7 +654,9 @@ impl OpenCADStudio {
|
||||
let attributes: Vec<&codec::entities::EntityCommon> = handles
|
||||
.iter()
|
||||
.filter_map(|h| match doc.get_entity(*h) {
|
||||
Some(EntityType::Insert(insert)) => Some(insert.attributes.iter().map(|a| &a.common)),
|
||||
Some(EntityType::Insert(insert)) => {
|
||||
Some(insert.attributes.iter().map(|a| &a.common))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
@@ -585,8 +668,10 @@ impl OpenCADStudio {
|
||||
}),
|
||||
});
|
||||
if !hosting {
|
||||
self.command_line.push_output(&crate::tf!("{found} field(s) found.", found = 0));
|
||||
self.command_line.push_output(&crate::tf!("{updated} field(s) updated.", updated = 0));
|
||||
self.command_line
|
||||
.push_output(&crate::tf!("{found} field(s) found.", found = 0));
|
||||
self.command_line
|
||||
.push_output(&crate::tf!("{updated} field(s) updated.", updated = 0));
|
||||
return;
|
||||
}
|
||||
// The reference counts fields, not the texts holding them, and
|
||||
@@ -595,8 +680,10 @@ impl OpenCADStudio {
|
||||
if changed > 0 {
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
self.command_line.push_output(&crate::tf!("{found} field(s) found."));
|
||||
self.command_line.push_output(&crate::tf!("{updated} field(s) updated.", updated = found));
|
||||
self.command_line
|
||||
.push_output(&crate::tf!("{found} field(s) found."));
|
||||
self.command_line
|
||||
.push_output(&crate::tf!("{updated} field(s) updated.", updated = found));
|
||||
}
|
||||
|
||||
/// A count table at `point`: `Item` | `Count`, one row per block (all
|
||||
@@ -607,11 +694,19 @@ impl OpenCADStudio {
|
||||
let area = self.tabs[i].count.as_ref().and_then(|m| m.area.clone());
|
||||
let instances = codec::count::block_instances(doc, area.as_deref());
|
||||
let mut names: Vec<String> = if names.is_empty() {
|
||||
codec::count::block_counts(&instances).into_iter().map(|c| c.name).collect()
|
||||
codec::count::block_counts(&instances)
|
||||
.into_iter()
|
||||
.map(|c| c.name)
|
||||
.collect()
|
||||
} else {
|
||||
names
|
||||
.iter()
|
||||
.map(|n| doc.block_records.get(n).map(|b| b.name.clone()).unwrap_or_else(|| n.clone()))
|
||||
.map(|n| {
|
||||
doc.block_records
|
||||
.get(n)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_else(|| n.clone())
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
names.sort_by_key(|n| n.to_ascii_uppercase());
|
||||
@@ -659,8 +754,10 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
}
|
||||
let codes: Vec<String> =
|
||||
names.iter().map(|n| self.block_field_code(i, n, &CountKey::default())).collect();
|
||||
let codes: Vec<String> = names
|
||||
.iter()
|
||||
.map(|n| self.block_field_code(i, n, &CountKey::default()))
|
||||
.collect();
|
||||
self.push_undo_snapshot(i, "COUNTTABLE");
|
||||
let Some(handle) = self.commit_entity_handle(EntityType::Table(Box::new(table))) else {
|
||||
return;
|
||||
@@ -672,19 +769,29 @@ impl OpenCADStudio {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(r, code)| {
|
||||
let value = crate::entities::field::evaluate(doc, code, &[], None).unwrap_or_else(|| "####".into());
|
||||
(r + 1, 1, r"%<\_FldIdx 0>%".to_string(), vec![codec::fields::NewField::new(code.clone(), value)])
|
||||
let value = crate::entities::field::evaluate(doc, code, &[], None)
|
||||
.unwrap_or_else(|| "####".into());
|
||||
(
|
||||
r + 1,
|
||||
1,
|
||||
r"%<\_FldIdx 0>%".to_string(),
|
||||
vec![codec::fields::NewField::new(code.clone(), value)],
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
doc.build_table_block(handle, fields);
|
||||
self.tabs[i].scene.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
|
||||
/// Zoom to a counted object (← / →, an error of the report).
|
||||
fn zoom_to_insert(&mut self, i: usize, h: Handle) {
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let Some(entity) = doc.get_entity(h) else { return };
|
||||
let Some(entity) = doc.get_entity(h) else {
|
||||
return;
|
||||
};
|
||||
let outline = codec::count::entity_outline(doc, entity);
|
||||
if outline.is_empty() {
|
||||
return;
|
||||
@@ -704,12 +811,20 @@ impl OpenCADStudio {
|
||||
let instances = mode.instances(&self.tabs[i].scene.document);
|
||||
let reference = instances
|
||||
.iter()
|
||||
.find(|inst| inst.duplicate_of.is_none() && inst.name.eq_ignore_ascii_case(&name) && key.matches(inst))
|
||||
.find(|inst| {
|
||||
inst.duplicate_of.is_none()
|
||||
&& inst.name.eq_ignore_ascii_case(&name)
|
||||
&& key.matches(inst)
|
||||
})
|
||||
.map(|inst| inst.handle);
|
||||
mode.target = Some(CountTarget::Block {
|
||||
name,
|
||||
reference,
|
||||
matching: [key.layer.is_some(), key.scale.is_some(), key.mirror_state.is_some()],
|
||||
matching: [
|
||||
key.layer.is_some(),
|
||||
key.scale.is_some(),
|
||||
key.mirror_state.is_some(),
|
||||
],
|
||||
picked: false,
|
||||
});
|
||||
mode.cursor = None;
|
||||
@@ -721,9 +836,15 @@ impl OpenCADStudio {
|
||||
/// expression counts.
|
||||
pub(in crate::app) fn show_count_instances(&mut self, i: usize, json: &str) {
|
||||
match codec::count::parse_query(json) {
|
||||
Some(codec::count::CountQuery::Block { name, key, boundary }) => {
|
||||
Some(codec::count::CountQuery::Block {
|
||||
name,
|
||||
key,
|
||||
boundary,
|
||||
}) => {
|
||||
if let Some(b) = boundary {
|
||||
if let Some(ring) = codec::count::boundary_polygon(&self.tabs[i].scene.document, b) {
|
||||
if let Some(ring) =
|
||||
codec::count::boundary_polygon(&self.tabs[i].scene.document, b)
|
||||
{
|
||||
let mut mode = self.tabs[i].count.take().unwrap_or_default();
|
||||
if mode.boundary.map(|(h, _)| h) != Some(b) {
|
||||
self.drop_count_boundary(i, mode.boundary);
|
||||
@@ -737,7 +858,11 @@ impl OpenCADStudio {
|
||||
}
|
||||
Some(codec::count::CountQuery::Single(handles)) if !handles.is_empty() => {
|
||||
let hexes: Vec<String> = handles.iter().map(|h| hex(*h)).collect();
|
||||
let area = if self.tabs[i].count.is_some() { "K" } else { "E" };
|
||||
let area = if self.tabs[i].count.is_some() {
|
||||
"K"
|
||||
} else {
|
||||
"E"
|
||||
};
|
||||
self.count_run(i, &format!("{area} T {}", hexes.join(",")));
|
||||
}
|
||||
_ => {}
|
||||
@@ -793,7 +918,10 @@ impl OpenCADStudio {
|
||||
CountMsg::TableAll(on) => {
|
||||
let names: Vec<String> = if on {
|
||||
let area = self.tabs[i].count.as_ref().and_then(|m| m.area.clone());
|
||||
let instances = codec::count::block_instances(&self.tabs[i].scene.document, area.as_deref());
|
||||
let instances = codec::count::block_instances(
|
||||
&self.tabs[i].scene.document,
|
||||
area.as_deref(),
|
||||
);
|
||||
crate::ui::window::count_palette::rows(&instances, &self.count_palette)
|
||||
.into_iter()
|
||||
.map(|r| r.name.to_ascii_uppercase())
|
||||
@@ -813,16 +941,26 @@ impl OpenCADStudio {
|
||||
}
|
||||
CountMsg::Back | CountMsg::Close => self.close_count(i),
|
||||
CountMsg::Match(k, on) => {
|
||||
if let Some(CountMode { target: Some(CountTarget::Block { matching, .. }), .. }) = self.tabs[i].count.as_mut() {
|
||||
if let Some(CountMode {
|
||||
target: Some(CountTarget::Block { matching, .. }),
|
||||
..
|
||||
}) = self.tabs[i].count.as_mut()
|
||||
{
|
||||
matching[k] = on;
|
||||
}
|
||||
self.apply_count_display(i);
|
||||
}
|
||||
CountMsg::ToggleDetails => self.count_palette.details_closed = !self.count_palette.details_closed,
|
||||
CountMsg::ToggleErrors => self.count_palette.errors_closed = !self.count_palette.errors_closed,
|
||||
CountMsg::ToggleDetails => {
|
||||
self.count_palette.details_closed = !self.count_palette.details_closed
|
||||
}
|
||||
CountMsg::ToggleErrors => {
|
||||
self.count_palette.errors_closed = !self.count_palette.errors_closed
|
||||
}
|
||||
CountMsg::ShowError(h) => self.zoom_to_insert(i, h),
|
||||
CountMsg::Prev | CountMsg::Next => {
|
||||
let Some(mode) = self.tabs[i].count.as_ref() else { return Task::none() };
|
||||
let Some(mode) = self.tabs[i].count.as_ref() else {
|
||||
return Task::none();
|
||||
};
|
||||
let result = mode.result(&self.tabs[i].scene.document);
|
||||
let n = result.counted.len();
|
||||
if n == 0 {
|
||||
@@ -843,7 +981,11 @@ impl OpenCADStudio {
|
||||
CountMsg::Area => return self.dispatch_command("COUNTAREA"),
|
||||
CountMsg::Select => {
|
||||
self.tabs[i].scene.deselect_all();
|
||||
let command = CountCommand::with_area(if self.tabs[i].count.is_some() { "K" } else { "C" });
|
||||
let command = CountCommand::with_area(if self.tabs[i].count.is_some() {
|
||||
"K"
|
||||
} else {
|
||||
"C"
|
||||
});
|
||||
return self.start_count_command(i, Box::new(command));
|
||||
}
|
||||
CountMsg::Field => return self.dispatch_command("COUNTFIELD"),
|
||||
|
||||
+98
-95
@@ -30,7 +30,7 @@ fn solid_edge_sources(
|
||||
entity,
|
||||
codec::EntityType::Solid3D(_) | codec::EntityType::Surface(_)
|
||||
)
|
||||
.then_some(entity.common().handle)
|
||||
.then_some(entity.common().handle)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
app.tabs[tab].scene.restore_solid_models(&handles);
|
||||
@@ -199,8 +199,9 @@ impl OpenCADStudio {
|
||||
_ => None,
|
||||
};
|
||||
if text_align.is_none() && mtext_ap.is_none() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("JUSTIFYTEXT: unknown justification option.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("JUSTIFYTEXT: unknown justification option.").as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
self.push_undo_snapshot(i, "JUSTIFYTEXT");
|
||||
@@ -358,10 +359,7 @@ impl OpenCADStudio {
|
||||
.selected_entities()
|
||||
.iter()
|
||||
.filter(|(_, e)| {
|
||||
matches!(
|
||||
e,
|
||||
codec::EntityType::Text(_) | codec::EntityType::MText(_)
|
||||
)
|
||||
matches!(e, codec::EntityType::Text(_) | codec::EntityType::MText(_))
|
||||
})
|
||||
.map(|(h, _)| *h)
|
||||
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||
@@ -421,8 +419,7 @@ impl OpenCADStudio {
|
||||
"TEXTFIT" => {
|
||||
use crate::command::SelectThenValueCommand;
|
||||
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
||||
let c =
|
||||
SelectThenValueCommand::new("TEXTFIT", "TEXTFIT target width:", has_sel);
|
||||
let c = SelectThenValueCommand::new("TEXTFIT", "TEXTFIT target width:", has_sel);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
@@ -485,9 +482,9 @@ impl OpenCADStudio {
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"TEXTFIT: fitted {n} text object(s) to width {target}."
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!("TEXTFIT: fitted {n} text object(s) to width {target}.").as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
// (text sequential numbering)
|
||||
@@ -505,20 +502,11 @@ impl OpenCADStudio {
|
||||
cmd if cmd.starts_with("TCOUNT ") => {
|
||||
let mut args = cmd.split_whitespace().skip(1);
|
||||
|
||||
let start: i64 = args
|
||||
.next()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(1);
|
||||
let start: i64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(1);
|
||||
|
||||
let increment: i64 = args
|
||||
.next()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(1);
|
||||
let increment: i64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(1);
|
||||
|
||||
let placement = args
|
||||
.next()
|
||||
.unwrap_or("O")
|
||||
.to_uppercase();
|
||||
let placement = args.next().unwrap_or("O").to_uppercase();
|
||||
|
||||
let placement = match placement.as_str() {
|
||||
"O" | "OVERWRITE" => "O",
|
||||
@@ -538,9 +526,7 @@ impl OpenCADStudio {
|
||||
.selected_entities()
|
||||
.iter()
|
||||
.filter_map(|(h, e)| match e {
|
||||
codec::EntityType::Text(t)
|
||||
if !self.tabs[i].scene.is_layer_locked(*h) =>
|
||||
{
|
||||
codec::EntityType::Text(t) if !self.tabs[i].scene.is_layer_locked(*h) => {
|
||||
Some((*h, t.insertion_point.x, t.insertion_point.y))
|
||||
}
|
||||
_ => None,
|
||||
@@ -558,10 +544,7 @@ impl OpenCADStudio {
|
||||
texts.sort_by(|a, b| {
|
||||
b.2.partial_cmp(&a.2)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
.then(
|
||||
a.1.partial_cmp(&b.1)
|
||||
.unwrap_or(std::cmp::Ordering::Equal),
|
||||
)
|
||||
.then(a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
|
||||
});
|
||||
|
||||
self.push_undo_snapshot(i, "TCOUNT");
|
||||
@@ -589,9 +572,7 @@ impl OpenCADStudio {
|
||||
|
||||
let changes: Vec<_> = texts
|
||||
.iter()
|
||||
.map(|(handle, _, _)| {
|
||||
(*handle, crate::scene::ChangeKind::Modified)
|
||||
})
|
||||
.map(|(handle, _, _)| (*handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
@@ -618,21 +599,22 @@ impl OpenCADStudio {
|
||||
.header
|
||||
.current_mleader_style_name
|
||||
.clone();
|
||||
let style = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.objects
|
||||
.iter()
|
||||
.find_map(|(handle, object)| match object {
|
||||
codec::objects::ObjectType::MultiLeaderStyle(style)
|
||||
if style.name.eq_ignore_ascii_case(&name) =>
|
||||
{
|
||||
let mut style = style.clone();
|
||||
style.handle = *handle;
|
||||
Some(style)
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let style =
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.objects
|
||||
.iter()
|
||||
.find_map(|(handle, object)| match object {
|
||||
codec::objects::ObjectType::MultiLeaderStyle(style)
|
||||
if style.name.eq_ignore_ascii_case(&name) =>
|
||||
{
|
||||
let mut style = style.clone();
|
||||
style.handle = *handle;
|
||||
Some(style)
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let multiplier = self.tabs[i].scene.creation_annotation_multiplier();
|
||||
let block_sources = self.tabs[i]
|
||||
.scene
|
||||
@@ -656,9 +638,11 @@ impl OpenCADStudio {
|
||||
.iter()
|
||||
.map(|style| (style.name.clone(), style.handle))
|
||||
.collect();
|
||||
let new_cmd = style.map_or_else(MLeaderCommand::new, |style| {
|
||||
MLeaderCommand::with_style(style, multiplier)
|
||||
}).with_drawing_resources(block_sources, layers, text_styles);
|
||||
let new_cmd = style
|
||||
.map_or_else(MLeaderCommand::new, |style| {
|
||||
MLeaderCommand::with_style(style, multiplier)
|
||||
})
|
||||
.with_drawing_resources(block_sources, layers, text_styles);
|
||||
self.command_line.push_info(&new_cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||
}
|
||||
@@ -686,10 +670,7 @@ impl OpenCADStudio {
|
||||
.filter(|handle| scene.entity_belongs_to_active_space(*handle))
|
||||
.and_then(|handle| scene.document.get_entity(handle))
|
||||
.cloned();
|
||||
let cmd = DimContinueCommand::new(
|
||||
recent,
|
||||
self.dimension_continue_mode == 1,
|
||||
);
|
||||
let cmd = DimContinueCommand::new(recent, self.dimension_continue_mode == 1);
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
}
|
||||
@@ -754,11 +735,8 @@ impl OpenCADStudio {
|
||||
surface_area: None,
|
||||
})
|
||||
.collect();
|
||||
let cmd = QdimCommand::new(
|
||||
selection,
|
||||
dim_spacing,
|
||||
self.quick_dimension_snap_priority,
|
||||
);
|
||||
let cmd =
|
||||
QdimCommand::new(selection, dim_spacing, self.quick_dimension_snap_priority);
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
}
|
||||
@@ -829,13 +807,15 @@ impl OpenCADStudio {
|
||||
"ZOOM EXTENTS ALL" | "ZOOM EXTENTS ALL VIEWPORTS" | "ZOOM EA" | "ZEA" => {
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.tabs[i].scene.fit_all_model_viewports();
|
||||
self.command_line.push_output(crate::t!("Zoom Extents — All Viewports").as_ref());
|
||||
self.command_line
|
||||
.push_output(crate::t!("Zoom Extents — All Viewports").as_ref());
|
||||
}
|
||||
|
||||
"ZOOM EXTENTS" | "ZOOM E" | "ZOOMEXTENTS" | "ZE" => {
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.tabs[i].scene.fit_all();
|
||||
self.command_line.push_output(crate::t!("Zoom Extents").as_ref());
|
||||
self.command_line
|
||||
.push_output(crate::t!("Zoom Extents").as_ref());
|
||||
}
|
||||
|
||||
"ZOOM IN" | "ZOOM I" | "ZI" => {
|
||||
@@ -847,19 +827,22 @@ impl OpenCADStudio {
|
||||
"ZOOM OUT" | "ZO" => {
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.tabs[i].scene.zoom_camera(1.5);
|
||||
self.command_line.push_output(crate::t!("Zoom Out").as_ref());
|
||||
self.command_line
|
||||
.push_output(crate::t!("Zoom Out").as_ref());
|
||||
}
|
||||
|
||||
// ZOOM ALL — fit the configured drawing limits.
|
||||
"ZOOM ALL" | "ZOOM A" | "ZA" => {
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.tabs[i].scene.fit_all_with_limits();
|
||||
self.command_line.push_output(crate::t!("Zoom All").as_ref());
|
||||
self.command_line
|
||||
.push_output(crate::t!("Zoom All").as_ref());
|
||||
}
|
||||
|
||||
"ZOOM PREVIOUS" | "ZOOM P" | "ZP" => {
|
||||
if self.tabs[i].scene.restore_previous_view() {
|
||||
self.command_line.push_output(crate::t!("Zoom Previous").as_ref());
|
||||
self.command_line
|
||||
.push_output(crate::t!("Zoom Previous").as_ref());
|
||||
} else {
|
||||
self.command_line
|
||||
.push_error(crate::t!("ZOOM: no previous view.").as_ref());
|
||||
@@ -881,7 +864,8 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
if self.tabs[i].scene.zoom_to_entities(&handles) {
|
||||
self.command_line.push_output(crate::t!("Zoom Object").as_ref());
|
||||
self.command_line
|
||||
.push_output(crate::t!("Zoom Object").as_ref());
|
||||
} else {
|
||||
self.command_line.push_error(
|
||||
crate::t!("ZOOM: selected objects have no visible bounds.").as_ref(),
|
||||
@@ -986,7 +970,11 @@ impl OpenCADStudio {
|
||||
};
|
||||
(!handles.is_empty()
|
||||
&& handles.iter().all(|h| {
|
||||
self.tabs[i].scene.wire_models_for(&[*h]).iter().any(touches)
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.wire_models_for(&[*h])
|
||||
.iter()
|
||||
.any(touches)
|
||||
}))
|
||||
.then(|| {
|
||||
(
|
||||
@@ -1257,25 +1245,30 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
// EXPLMODE 0 keeps references that are not uniformly and
|
||||
// positively scaled whole.
|
||||
let selected: Vec<_> = if super::blockvars::explmode(&self.tabs[i].scene.document) == 0 {
|
||||
selected
|
||||
.into_iter()
|
||||
.filter(|(_, entity)| match entity {
|
||||
codec::EntityType::Insert(ins) => {
|
||||
let (x, y, z) = (ins.x_scale(), ins.y_scale(), ins.z_scale());
|
||||
let uniform = x > 0.0 && (x - y).abs() <= 1e-10 && (x - z).abs() <= 1e-10;
|
||||
if !uniform {
|
||||
self.command_line
|
||||
.push_output("The object is not uniformly and positively scaled.");
|
||||
let selected: Vec<_> =
|
||||
if super::blockvars::explmode(&self.tabs[i].scene.document) == 0 {
|
||||
selected
|
||||
.into_iter()
|
||||
.filter(|(_, entity)| match entity {
|
||||
codec::EntityType::Insert(ins) => {
|
||||
let (x, y, z) =
|
||||
(ins.x_scale(), ins.y_scale(), ins.z_scale());
|
||||
let uniform = x > 0.0
|
||||
&& (x - y).abs() <= 1e-10
|
||||
&& (x - z).abs() <= 1e-10;
|
||||
if !uniform {
|
||||
self.command_line.push_output(
|
||||
"The object is not uniformly and positively scaled.",
|
||||
);
|
||||
}
|
||||
uniform
|
||||
}
|
||||
uniform
|
||||
}
|
||||
_ => true,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
selected
|
||||
};
|
||||
_ => true,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
selected
|
||||
};
|
||||
let replacements = plan_explode(&selected, &self.tabs[i].scene.document);
|
||||
let exploded = replacements.len();
|
||||
if exploded > 0 {
|
||||
@@ -1288,8 +1281,9 @@ impl OpenCADStudio {
|
||||
self.command_line
|
||||
.push_output(crate::tf!("{exploded} object(s) exploded.").as_ref());
|
||||
} else {
|
||||
self.command_line
|
||||
.push_info(crate::t!("EXPLODE: no explodable objects selected.").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("EXPLODE: no explodable objects selected.").as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1350,8 +1344,7 @@ impl OpenCADStudio {
|
||||
})
|
||||
.collect();
|
||||
let all_entities: Vec<_> = entities.into_iter().map(|(_, e)| e).collect();
|
||||
let new_cmd =
|
||||
TrimCommand::with_cutting_edges(all_entities, initial_edges);
|
||||
let new_cmd = TrimCommand::with_cutting_edges(all_entities, initial_edges);
|
||||
self.command_line.push_info(&new_cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||
}
|
||||
@@ -1364,7 +1357,12 @@ impl OpenCADStudio {
|
||||
.iter()
|
||||
.filter_map(|w| {
|
||||
let h = Scene::handle_from_wire_name(&w.name)?;
|
||||
self.tabs[i].scene.document.get_entity(h).cloned().map(|e| (h, e))
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(h)
|
||||
.cloned()
|
||||
.map(|e| (h, e))
|
||||
})
|
||||
.collect();
|
||||
let new_cmd = ExtrimCommand::new(all);
|
||||
@@ -1413,7 +1411,10 @@ impl OpenCADStudio {
|
||||
let text = cmd.strip_prefix("ARCTEXT").unwrap_or("").trim().to_string();
|
||||
if text.is_empty() {
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: ARCTEXT <text> (select an arc first; the text follows it)").as_ref(),
|
||||
crate::t!(
|
||||
"Usage: ARCTEXT <text> (select an arc first; the text follows it)"
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
@@ -1433,7 +1434,8 @@ impl OpenCADStudio {
|
||||
};
|
||||
let chars: Vec<char> = text.chars().filter(|c| !c.is_control()).collect();
|
||||
if chars.is_empty() {
|
||||
self.command_line.push_error(crate::t!("ARCTEXT: no printable text.").as_ref());
|
||||
self.command_line
|
||||
.push_error(crate::t!("ARCTEXT: no printable text.").as_ref());
|
||||
return None;
|
||||
}
|
||||
let n = chars.len();
|
||||
@@ -1456,8 +1458,9 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.add_entity(codec::EntityType::Text(t));
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
.push_output(crate::tf!("ARCTEXT: placed {n} character(s) along the arc.").as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!("ARCTEXT: placed {n} character(s) along the arc.").as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
_ => return None,
|
||||
|
||||
+364
-205
@@ -8,8 +8,9 @@ impl OpenCADStudio {
|
||||
"PAN" => {
|
||||
self.tabs[i].pan_mode = true;
|
||||
self.clear_navigation_hover(i);
|
||||
self.command_line
|
||||
.push_output(crate::t!("PAN: drag with the left mouse button. Press Esc to exit.").as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::t!("PAN: drag with the left mouse button. Press Esc to exit.").as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
// ── TABLE structure and cell editing ───────────────────────────────
|
||||
@@ -33,12 +34,17 @@ impl OpenCADStudio {
|
||||
}
|
||||
let words: Vec<&str> = rest.split_whitespace().collect();
|
||||
let integer = |index: usize| {
|
||||
words.get(index).and_then(|value| value.parse::<usize>().ok())
|
||||
words
|
||||
.get(index)
|
||||
.and_then(|value| value.parse::<usize>().ok())
|
||||
};
|
||||
let real = |index: usize| {
|
||||
words.get(index).and_then(|value| value.parse::<f64>().ok())
|
||||
};
|
||||
let action = match words.first().map(|word| word.to_ascii_uppercase()).as_deref() {
|
||||
let real =
|
||||
|index: usize| words.get(index).and_then(|value| value.parse::<f64>().ok());
|
||||
let action = match words
|
||||
.first()
|
||||
.map(|word| word.to_ascii_uppercase())
|
||||
.as_deref()
|
||||
{
|
||||
Some("CELL") => {
|
||||
let parts: Vec<&str> = rest.splitn(4, char::is_whitespace).collect();
|
||||
match (integer(1), integer(2)) {
|
||||
@@ -117,11 +123,7 @@ impl OpenCADStudio {
|
||||
u64::from_str_radix(value.trim_start_matches("0x"), 16)
|
||||
.ok()
|
||||
.map(|handle| {
|
||||
TableAction::Field(
|
||||
row,
|
||||
column,
|
||||
codec::Handle::new(handle),
|
||||
)
|
||||
TableAction::Field(row, column, codec::Handle::new(handle))
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
@@ -158,16 +160,19 @@ impl OpenCADStudio {
|
||||
None
|
||||
};
|
||||
if matches!(action, TableAction::Block(_, _, _)) && block_handle.is_none() {
|
||||
self.command_line.push_error(
|
||||
crate::t!("TABLE: block definition not found.").as_ref(),
|
||||
);
|
||||
self.command_line
|
||||
.push_error(crate::t!("TABLE: block definition not found.").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
if let TableAction::Field(_, _, field_handle) = &action {
|
||||
if !self.tabs[i].scene.document.fields.contains_key(field_handle) {
|
||||
self.command_line.push_error(
|
||||
crate::t!("TABLE: field handle not found.").as_ref(),
|
||||
);
|
||||
if !self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.fields
|
||||
.contains_key(field_handle)
|
||||
{
|
||||
self.command_line
|
||||
.push_error(crate::t!("TABLE: field handle not found.").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
}
|
||||
@@ -337,10 +342,10 @@ impl OpenCADStudio {
|
||||
self.command_line
|
||||
.push_output(crate::t!("TABLE: edit applied.").as_ref());
|
||||
} else {
|
||||
self.command_line
|
||||
.push_error(crate::t!(
|
||||
"TABLE: row/column is out of range or the cell is locked."
|
||||
).as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("TABLE: row/column is out of range or the cell is locked.")
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,9 +393,10 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"UCSICON {sub}: updated {count} viewport(s) + model space."
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!("UCSICON {sub}: updated {count} viewport(s) + model space.")
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
"" => {
|
||||
// Bare UCSICON toggles visibility.
|
||||
@@ -405,11 +411,13 @@ impl OpenCADStudio {
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
let state = if visible { "ON" } else { "OFF" };
|
||||
self.command_line.push_output(crate::tf!("UCSICON {state}").as_ref());
|
||||
self.command_line
|
||||
.push_output(crate::tf!("UCSICON {state}").as_ref());
|
||||
}
|
||||
_ => {
|
||||
self.command_line
|
||||
.push_info(crate::t!("Usage: UCSICON ON | OFF | NOORIGIN | ORIGIN").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: UCSICON ON | OFF | NOORIGIN | ORIGIN").as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -437,40 +445,52 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.set_drawing_limit_check(enabled);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
self.command_line.push_output(crate::t!(if enabled {
|
||||
"Limits checking ON."
|
||||
} else {
|
||||
"Limits checking OFF."
|
||||
}).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::t!(if enabled {
|
||||
"Limits checking ON."
|
||||
} else {
|
||||
"Limits checking OFF."
|
||||
})
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
cmd if cmd.starts_with("LIMITS SET ") => {
|
||||
let tokens: Vec<&str> = cmd["LIMITS SET ".len()..].split_whitespace().collect();
|
||||
let values: Result<Vec<f64>, _> =
|
||||
tokens.iter().map(|value| value.parse()).collect();
|
||||
let Ok(values) = values else {
|
||||
self.command_line
|
||||
.push_error(crate::t!("LIMITS: four numeric coordinates required.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("LIMITS: four numeric coordinates required.").as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
};
|
||||
if tokens.len() != 4 || !values.iter().all(|value| value.is_finite()) {
|
||||
self.command_line
|
||||
.push_error(crate::t!("LIMITS: four finite numeric coordinates required.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("LIMITS: four finite numeric coordinates required.").as_ref(),
|
||||
);
|
||||
} else {
|
||||
let first = glam::DVec2::new(values[0], values[1]);
|
||||
let opposite = glam::DVec2::new(values[2], values[3]);
|
||||
let min = first.min(opposite);
|
||||
let max = first.max(opposite);
|
||||
if min.x == max.x || min.y == max.y {
|
||||
self.command_line
|
||||
.push_error(crate::t!("LIMITS: corners must define a non-zero area.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("LIMITS: corners must define a non-zero area.").as_ref(),
|
||||
);
|
||||
} else {
|
||||
self.push_undo_snapshot(i, "LIMITS");
|
||||
self.tabs[i].scene.set_current_drawing_limits(min, max);
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"Drawing limits: {:.4},{:.4} to {:.4},{:.4}.",
|
||||
min.x, min.y, max.x, max.y
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!(
|
||||
"Drawing limits: {:.4},{:.4} to {:.4},{:.4}.",
|
||||
min.x,
|
||||
min.y,
|
||||
max.x,
|
||||
max.y
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,15 +519,21 @@ impl OpenCADStudio {
|
||||
// it only queues a per-viewport cache invalidation.
|
||||
"REDRAW" => {
|
||||
use crate::scene::ViewportRefreshScope;
|
||||
self.tabs[i].scene.request_refresh(ViewportRefreshScope::Active);
|
||||
self.command_line.push_output(crate::t!("REDRAW: viewport refreshed.").as_ref());
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.request_refresh(ViewportRefreshScope::Active);
|
||||
self.command_line
|
||||
.push_output(crate::t!("REDRAW: viewport refreshed.").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
// REDRAWALL — force re-rasterize of every generated viewport.
|
||||
"REDRAWALL" => {
|
||||
use crate::scene::ViewportRefreshScope;
|
||||
self.tabs[i].scene.request_refresh(ViewportRefreshScope::All);
|
||||
self.command_line.push_output(crate::t!("REDRAWALL: viewports refreshed.").as_ref());
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.request_refresh(ViewportRefreshScope::All);
|
||||
self.command_line
|
||||
.push_output(crate::t!("REDRAWALL: viewports refreshed.").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
// Rebuild tessellation caches without modifying the document.
|
||||
@@ -515,7 +541,8 @@ impl OpenCADStudio {
|
||||
// A regen is a field evaluation event (FIELDEVAL bit 16).
|
||||
self.tabs[i].scene.update_fields(16, None);
|
||||
self.tabs[i].scene.populate_meshes_from_document();
|
||||
self.command_line.push_output(crate::t!("REGEN: regenerated model.").as_ref());
|
||||
self.command_line
|
||||
.push_output(crate::t!("REGEN: regenerated model.").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
|
||||
@@ -593,8 +620,9 @@ impl OpenCADStudio {
|
||||
|
||||
// ── TOOLPALETTES — not yet implemented ───────────────────────────────
|
||||
"TOOLPALETTES" => {
|
||||
self.command_line
|
||||
.push_info(crate::t!("TOOLPALETTES: Tool Palettes not yet implemented.").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("TOOLPALETTES: Tool Palettes not yet implemented.").as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
// ── XDATA — read/write extended entity data ──────────────────────────
|
||||
@@ -635,19 +663,24 @@ impl OpenCADStudio {
|
||||
if let Some(entity) = self.tabs[i].scene.document.get_entity(*sh) {
|
||||
let xd = &entity.common().extended_data;
|
||||
if xd.is_empty() {
|
||||
self.command_line
|
||||
.push_output(crate::tf!(" {:x}: no xdata.", sh.value()).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!(" {:x}: no xdata.", sh.value()).as_ref(),
|
||||
);
|
||||
} else {
|
||||
for rec in xd.records() {
|
||||
self.command_line.push_output(crate::tf!(
|
||||
" {:x} [{}]: {} value(s)",
|
||||
sh.value(),
|
||||
rec.application_name,
|
||||
rec.values.len()
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!(
|
||||
" {:x} [{}]: {} value(s)",
|
||||
sh.value(),
|
||||
rec.application_name,
|
||||
rec.values.len()
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
for v in &rec.values {
|
||||
self.command_line
|
||||
.push_output(crate::tf!(" {:?}", v).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!(" {:?}", v).as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -680,10 +713,13 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"XDATA: set [{app}] = \"{val}\" on {} entity/entities.",
|
||||
editable.len()
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!(
|
||||
"XDATA: set [{app}] = \"{val}\" on {} entity/entities.",
|
||||
editable.len()
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
"CLEAR" => {
|
||||
let app_filter = parts.get(1).copied();
|
||||
@@ -723,11 +759,14 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(crate::t!("XDATA: cleared.").as_ref());
|
||||
self.command_line
|
||||
.push_output(crate::t!("XDATA: cleared.").as_ref());
|
||||
}
|
||||
_ => {
|
||||
self.command_line
|
||||
.push_info(crate::t!("Usage: XDATA LIST | SET <app> <value> | CLEAR [app]").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: XDATA LIST | SET <app> <value> | CLEAR [app]")
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -786,7 +825,9 @@ impl OpenCADStudio {
|
||||
use crate::modules::insert::solid3d_cmds::PresspullCommand;
|
||||
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
|
||||
let mut command = PresspullCommand::new(color);
|
||||
command.set_isolines(crate::entities::solid3d::clamp_header_isolines(self.tabs[i].scene.document.header.isolines));
|
||||
command.set_isolines(crate::entities::solid3d::clamp_header_isolines(
|
||||
self.tabs[i].scene.document.header.isolines,
|
||||
));
|
||||
command.set_preselection(self.presspull_preselection());
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
@@ -797,7 +838,9 @@ impl OpenCADStudio {
|
||||
use crate::modules::insert::solid3d_cmds::RevolveCommand;
|
||||
let selected: Vec<_> = self.tabs[i].scene.selected_entities().into_iter().collect();
|
||||
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
|
||||
let isolines = crate::entities::solid3d::clamp_header_isolines(self.tabs[i].scene.document.header.isolines);
|
||||
let isolines = crate::entities::solid3d::clamp_header_isolines(
|
||||
self.tabs[i].scene.document.header.isolines,
|
||||
);
|
||||
let mut cmd = RevolveCommand::new(color, isolines);
|
||||
if !selected.is_empty() {
|
||||
cmd.set_preselection(
|
||||
@@ -815,13 +858,23 @@ impl OpenCADStudio {
|
||||
"SWEEP" => {
|
||||
use crate::modules::insert::solid3d_cmds::SweepCommand;
|
||||
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
|
||||
let isolines = crate::entities::solid3d::clamp_header_isolines(self.tabs[i].scene.document.header.isolines);
|
||||
let isolines = crate::entities::solid3d::clamp_header_isolines(
|
||||
self.tabs[i].scene.document.header.isolines,
|
||||
);
|
||||
let mut cmd = SweepCommand::new(color, isolines);
|
||||
cmd.set_parameters(self.tabs[i].scene.named_parameters().clone());
|
||||
let selected = self.tabs[i].scene.selected_handles_in_order()
|
||||
let selected = self.tabs[i]
|
||||
.scene
|
||||
.selected_handles_in_order()
|
||||
.into_iter()
|
||||
.filter_map(|handle| self.tabs[i].scene.document.get_entity(handle)
|
||||
.cloned().map(|entity| (handle, entity)))
|
||||
.filter_map(|handle| {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.cloned()
|
||||
.map(|entity| (handle, entity))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if !selected.is_empty() {
|
||||
cmd.set_preselection(selected);
|
||||
@@ -834,13 +887,35 @@ impl OpenCADStudio {
|
||||
"LOFT" => {
|
||||
use crate::modules::insert::solid3d_cmds::LoftCommand;
|
||||
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
|
||||
let isolines = crate::entities::solid3d::clamp_header_isolines(self.tabs[i].scene.document.header.isolines);
|
||||
let selected = self.tabs[i].scene.selected_handles_in_order().into_iter()
|
||||
.filter_map(|handle| self.tabs[i].scene.document.get_entity(handle)
|
||||
.cloned().map(|entity| (handle, entity))).collect();
|
||||
let available = self.tabs[i].scene.document.entities()
|
||||
.map(|entity| (entity.common().handle, entity.clone())).collect();
|
||||
let cmd = LoftCommand::new(color, isolines, crate::command::ExtrudeMode::Solid, selected, available);
|
||||
let isolines = crate::entities::solid3d::clamp_header_isolines(
|
||||
self.tabs[i].scene.document.header.isolines,
|
||||
);
|
||||
let selected = self.tabs[i]
|
||||
.scene
|
||||
.selected_handles_in_order()
|
||||
.into_iter()
|
||||
.filter_map(|handle| {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.cloned()
|
||||
.map(|entity| (handle, entity))
|
||||
})
|
||||
.collect();
|
||||
let available = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.map(|entity| (entity.common().handle, entity.clone()))
|
||||
.collect();
|
||||
let cmd = LoftCommand::new(
|
||||
color,
|
||||
isolines,
|
||||
crate::command::ExtrudeMode::Solid,
|
||||
selected,
|
||||
available,
|
||||
);
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
}
|
||||
@@ -923,14 +998,18 @@ impl OpenCADStudio {
|
||||
"Plot style: {} ({} color overrides)",
|
||||
t.name,
|
||||
t.aci_entries.iter().filter(|e| e.color.is_some()).count()
|
||||
).into_owned()
|
||||
)
|
||||
.into_owned()
|
||||
})
|
||||
.unwrap_or_else(|| crate::t!("No plot style table loaded.").into_owned());
|
||||
.unwrap_or_else(|| {
|
||||
crate::t!("No plot style table loaded.").into_owned()
|
||||
});
|
||||
self.command_line.push_output(&msg);
|
||||
}
|
||||
_ => {
|
||||
self.command_line
|
||||
.push_error(crate::t!("Usage: PLOTSTYLE [LOAD | CLEAR | STATUS]").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("Usage: PLOTSTYLE [LOAD | CLEAR | STATUS]").as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -948,17 +1027,21 @@ impl OpenCADStudio {
|
||||
.selected_entities()
|
||||
.iter()
|
||||
.filter(|(handle, _)| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||
.filter_map(|(handle, _)| match self.tabs[i].scene.document.get_entity(*handle) {
|
||||
Some(codec::EntityType::RasterImage(image)) => {
|
||||
Some((*handle, image.flags.contains(ImageDisplayFlags::TRANSPARENCY_ON)))
|
||||
.filter_map(|(handle, _)| {
|
||||
match self.tabs[i].scene.document.get_entity(*handle) {
|
||||
Some(codec::EntityType::RasterImage(image)) => Some((
|
||||
*handle,
|
||||
image.flags.contains(ImageDisplayFlags::TRANSPARENCY_ON),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let Some(&(_, first_on)) = images.first() else {
|
||||
return Some(Task::none());
|
||||
};
|
||||
let handles: Vec<codec::Handle> = images.iter().map(|(handle, _)| *handle).collect();
|
||||
let handles: Vec<codec::Handle> =
|
||||
images.iter().map(|(handle, _)| *handle).collect();
|
||||
if cmd == "TRANSPARENCY MODE" {
|
||||
let c = crate::modules::insert::image_transparency::TransparencyCommand::mode(
|
||||
handles, first_on,
|
||||
@@ -1022,8 +1105,9 @@ impl OpenCADStudio {
|
||||
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||
.collect();
|
||||
if handles.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("UNDERLAY: select underlay entities first.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("UNDERLAY: select underlay entities first.").as_ref(),
|
||||
);
|
||||
} else {
|
||||
let parts: Vec<&str> = sub.splitn(2, char::is_whitespace).collect();
|
||||
let action = parts.first().copied().unwrap_or("");
|
||||
@@ -1060,8 +1144,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
"CLIP" => match arg {
|
||||
"ON" => {
|
||||
ul.flags |=
|
||||
codec::entities::UnderlayDisplayFlags::CLIPPING;
|
||||
ul.flags |= codec::entities::UnderlayDisplayFlags::CLIPPING;
|
||||
changed += 1;
|
||||
}
|
||||
"OFF" => {
|
||||
@@ -1140,8 +1223,10 @@ impl OpenCADStudio {
|
||||
return Some(Task::none());
|
||||
}
|
||||
let Some(scale) = self.tabs[i].scene.creation_annotation_scale_handle() else {
|
||||
self.command_line
|
||||
.push_error(crate::t!("OBJECTSCALE: the active annotation scale is unavailable.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("OBJECTSCALE: the active annotation scale is unavailable.")
|
||||
.as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
};
|
||||
|
||||
@@ -1156,11 +1241,9 @@ impl OpenCADStudio {
|
||||
let member = memberships.iter().any(|(_, current)| *current == scale);
|
||||
if adding {
|
||||
!member
|
||||
&& self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(*handle)
|
||||
.is_some_and(crate::scene::annotative::supports_annotation_context)
|
||||
&& self.tabs[i].scene.document.get_entity(*handle).is_some_and(
|
||||
crate::scene::annotative::supports_annotation_context,
|
||||
)
|
||||
} else {
|
||||
member
|
||||
}
|
||||
@@ -1213,11 +1296,14 @@ impl OpenCADStudio {
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"OBJECTSCALE: {} the current scale on {} object(s).",
|
||||
if adding { "added" } else { "removed" },
|
||||
changed.len()
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!(
|
||||
"OBJECTSCALE: {} the current scale on {} object(s).",
|
||||
if adding { "added" } else { "removed" },
|
||||
changed.len()
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
|
||||
@@ -1226,19 +1312,22 @@ impl OpenCADStudio {
|
||||
"HYPERLINK" => {
|
||||
use crate::command::SelectThenValueCommand;
|
||||
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
||||
let c = SelectThenValueCommand::new(
|
||||
"HYPERLINK",
|
||||
"HYPERLINK URL to attach:",
|
||||
has_sel,
|
||||
);
|
||||
let c =
|
||||
SelectThenValueCommand::new("HYPERLINK", "HYPERLINK URL to attach:", has_sel);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("HYPERLINK ") => {
|
||||
use codec::xdata::XDataValue;
|
||||
let url = cmd.strip_prefix("HYPERLINK").unwrap_or("").trim().to_string();
|
||||
let url = cmd
|
||||
.strip_prefix("HYPERLINK")
|
||||
.unwrap_or("")
|
||||
.trim()
|
||||
.to_string();
|
||||
if url.is_empty() {
|
||||
self.command_line.push_info(crate::t!("Usage: HYPERLINK <url> (select objects first)").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: HYPERLINK <url> (select objects first)").as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
let handles: Vec<codec::Handle> = self.tabs[i]
|
||||
@@ -1249,7 +1338,8 @@ impl OpenCADStudio {
|
||||
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||
.collect();
|
||||
if handles.is_empty() {
|
||||
self.command_line.push_error(crate::t!("HYPERLINK: select objects first.").as_ref());
|
||||
self.command_line
|
||||
.push_error(crate::t!("HYPERLINK: select objects first.").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
self.push_undo_snapshot(i, "HYPERLINK");
|
||||
@@ -1282,7 +1372,11 @@ impl OpenCADStudio {
|
||||
"ADJUST",
|
||||
"ADJUST [Brightness / Contrast / Fade]:",
|
||||
vec![
|
||||
("Brightness", "BRIGHTNESS", Some("ADJUST brightness 0-100:")),
|
||||
(
|
||||
"Brightness",
|
||||
"BRIGHTNESS",
|
||||
Some("ADJUST brightness 0-100:"),
|
||||
),
|
||||
("Contrast", "CONTRAST", Some("ADJUST contrast 0-100:")),
|
||||
("Fade", "FADE", Some("ADJUST fade 0-100:")),
|
||||
],
|
||||
@@ -1307,8 +1401,9 @@ impl OpenCADStudio {
|
||||
self.command_line
|
||||
.push_error(crate::t!("ADJUST: select raster image(s) first.").as_ref());
|
||||
} else if action.is_empty() {
|
||||
self.command_line
|
||||
.push_info(crate::t!("Usage: ADJUST BRIGHTNESS|CONTRAST|FADE <0-100>").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: ADJUST BRIGHTNESS|CONTRAST|FADE <0-100>").as_ref(),
|
||||
);
|
||||
} else if let Ok(v) = arg.parse::<u8>() {
|
||||
let v = v.min(100);
|
||||
self.push_undo_snapshot(i, "ADJUST");
|
||||
@@ -1351,15 +1446,17 @@ impl OpenCADStudio {
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.command_line
|
||||
.push_output(crate::tf!("ADJUST: {action} = {v} on {changed} image(s).").as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!("ADJUST: {action} = {v} on {changed} image(s).").as_ref(),
|
||||
);
|
||||
} else {
|
||||
self.command_line.push_error(
|
||||
crate::t!("ADJUST: no raster images selected, or unknown property (use BRIGHTNESS|CONTRAST|FADE).").as_ref(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.command_line.push_error(crate::t!("ADJUST: value must be 0-100.").as_ref());
|
||||
self.command_line
|
||||
.push_error(crate::t!("ADJUST: value must be 0-100.").as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1377,10 +1474,8 @@ impl OpenCADStudio {
|
||||
}
|
||||
"ANNOALLVISIBLE" => {
|
||||
use crate::command::ValuePromptCommand;
|
||||
let c = ValuePromptCommand::new(
|
||||
"ANNOALLVISIBLE",
|
||||
"ANNOALLVISIBLE new value [0/1]:",
|
||||
);
|
||||
let c =
|
||||
ValuePromptCommand::new("ANNOALLVISIBLE", "ANNOALLVISIBLE new value [0/1]:");
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
@@ -1402,10 +1497,8 @@ impl OpenCADStudio {
|
||||
}
|
||||
"ANNOAUTOSCALE" => {
|
||||
use crate::command::ValuePromptCommand;
|
||||
let c = ValuePromptCommand::new(
|
||||
"ANNOAUTOSCALE",
|
||||
"ANNOAUTOSCALE new value [-4..4]:",
|
||||
);
|
||||
let c =
|
||||
ValuePromptCommand::new("ANNOAUTOSCALE", "ANNOAUTOSCALE new value [-4..4]:");
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
@@ -1427,8 +1520,9 @@ impl OpenCADStudio {
|
||||
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||
.collect();
|
||||
if handles.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("ANNOUPDATE: select annotation objects first.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("ANNOUPDATE: select annotation objects first.").as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
self.push_undo_snapshot(i, "ANNOUPDATE");
|
||||
@@ -1487,9 +1581,9 @@ impl OpenCADStudio {
|
||||
self.command_line
|
||||
.push_output(crate::tf!("Annotation scale: {arg}").as_ref());
|
||||
}
|
||||
None => self
|
||||
.command_line
|
||||
.push_error(crate::t!("Usage: ANNOSCALE <ratio> e.g. 1:50, 2:1, or a factor").as_ref()),
|
||||
None => self.command_line.push_error(
|
||||
crate::t!("Usage: ANNOSCALE <ratio> e.g. 1:50, 2:1, or a factor").as_ref(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1503,8 +1597,16 @@ impl OpenCADStudio {
|
||||
"SCALELISTEDIT",
|
||||
"SCALELISTEDIT [Add / Delete]:",
|
||||
vec![
|
||||
("Add", "ADD", Some("SCALELISTEDIT ADD new scale ratio (e.g. 1:50):")),
|
||||
("Delete", "DELETE", Some("SCALELISTEDIT DELETE scale ratio to remove:")),
|
||||
(
|
||||
"Add",
|
||||
"ADD",
|
||||
Some("SCALELISTEDIT ADD new scale ratio (e.g. 1:50):"),
|
||||
),
|
||||
(
|
||||
"Delete",
|
||||
"DELETE",
|
||||
Some("SCALELISTEDIT DELETE scale ratio to remove:"),
|
||||
),
|
||||
],
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
@@ -1522,20 +1624,22 @@ impl OpenCADStudio {
|
||||
self.push_undo_snapshot(i, "SCALELISTEDIT");
|
||||
if self.tabs[i].scene.add_scale(arg, paper, drawing) {
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
.push_output(crate::tf!("Added annotation scale {arg}.").as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!("Added annotation scale {arg}.").as_ref(),
|
||||
);
|
||||
} else {
|
||||
self.command_line
|
||||
.push_info(crate::tf!("Scale {arg} already exists.").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::tf!("Scale {arg} already exists.").as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => self
|
||||
.command_line
|
||||
.push_error(crate::t!("SCALELISTEDIT ADD: use a ratio like 1:50.").as_ref()),
|
||||
_ => self.command_line.push_error(
|
||||
crate::t!("SCALELISTEDIT ADD: use a ratio like 1:50.").as_ref(),
|
||||
),
|
||||
},
|
||||
None => self
|
||||
.command_line
|
||||
.push_error(crate::t!("SCALELISTEDIT ADD: use a ratio like 1:50.").as_ref()),
|
||||
None => self.command_line.push_error(
|
||||
crate::t!("SCALELISTEDIT ADD: use a ratio like 1:50.").as_ref(),
|
||||
),
|
||||
},
|
||||
"DELETE" | "REMOVE" => {
|
||||
let current = self.tabs[i]
|
||||
@@ -1545,20 +1649,25 @@ impl OpenCADStudio {
|
||||
.current_annotation_scale
|
||||
.clone();
|
||||
if arg.is_empty() {
|
||||
self.command_line.push_info(crate::t!("Usage: SCALELISTEDIT DELETE <name>").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: SCALELISTEDIT DELETE <name>").as_ref(),
|
||||
);
|
||||
} else if arg.eq_ignore_ascii_case(¤t) {
|
||||
self.command_line.push_error(crate::tf!(
|
||||
"Cannot delete the current annotation scale ({arg})."
|
||||
).as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::tf!("Cannot delete the current annotation scale ({arg}).")
|
||||
.as_ref(),
|
||||
);
|
||||
} else {
|
||||
self.push_undo_snapshot(i, "SCALELISTEDIT");
|
||||
if self.tabs[i].scene.remove_scale(arg) {
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
.push_output(crate::tf!("Removed annotation scale {arg}.").as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!("Removed annotation scale {arg}.").as_ref(),
|
||||
);
|
||||
} else {
|
||||
self.command_line
|
||||
.push_info(crate::tf!("No annotation scale named {arg}.").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::tf!("No annotation scale named {arg}.").as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1570,15 +1679,17 @@ impl OpenCADStudio {
|
||||
.map(|(n, _, _)| n)
|
||||
.collect();
|
||||
if names.is_empty() {
|
||||
self.command_line.push_info(crate::t!("No annotation scales defined.").as_ref());
|
||||
} else {
|
||||
self.command_line
|
||||
.push_output(crate::tf!("Annotation scales: {}", names.join(", ")).as_ref());
|
||||
.push_info(crate::t!("No annotation scales defined.").as_ref());
|
||||
} else {
|
||||
self.command_line.push_output(
|
||||
crate::tf!("Annotation scales: {}", names.join(", ")).as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => self
|
||||
.command_line
|
||||
.push_info(crate::t!("Usage: SCALELISTEDIT [ADD 1:50 | DELETE 1:50]").as_ref()),
|
||||
_ => self.command_line.push_info(
|
||||
crate::t!("Usage: SCALELISTEDIT [ADD 1:50 | DELETE 1:50]").as_ref(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1624,8 +1735,10 @@ impl OpenCADStudio {
|
||||
.collect();
|
||||
if candidates.is_empty() {
|
||||
self.command_line.push_info(
|
||||
crate::t!("ANNORESET: no selected object has alternate scale representations.")
|
||||
.as_ref(),
|
||||
crate::t!(
|
||||
"ANNORESET: no selected object has alternate scale representations."
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
@@ -1666,9 +1779,8 @@ impl OpenCADStudio {
|
||||
cmd if cmd.starts_with("DATALINK ") => {
|
||||
let path = cmd.trim_start_matches("DATALINK").trim();
|
||||
if path.is_empty() {
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: DATALINK <path-to-.csv>").as_ref(),
|
||||
);
|
||||
self.command_line
|
||||
.push_info(crate::t!("Usage: DATALINK <path-to-.csv>").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
match std::fs::read_to_string(path) {
|
||||
@@ -1695,29 +1807,32 @@ impl OpenCADStudio {
|
||||
}
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let current_style = doc.header.current_table_style_name.clone();
|
||||
table.table_style_handle = doc.objects.iter().find_map(|(handle, object)| {
|
||||
match object {
|
||||
codec::objects::ObjectType::TableStyle(style)
|
||||
if style.name.eq_ignore_ascii_case(¤t_style) =>
|
||||
{
|
||||
Some(*handle)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
table.table_style_handle =
|
||||
doc.objects
|
||||
.iter()
|
||||
.find_map(|(handle, object)| match object {
|
||||
codec::objects::ObjectType::TableStyle(style)
|
||||
if style.name.eq_ignore_ascii_case(¤t_style) =>
|
||||
{
|
||||
Some(*handle)
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let link_path = std::fs::canonicalize(path)
|
||||
.map(|value| value.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|_| path.to_string());
|
||||
let command = crate::modules::annotate::data_link::DataLinkPlaceCommand::new(
|
||||
table, &link_path,
|
||||
);
|
||||
let command =
|
||||
crate::modules::annotate::data_link::DataLinkPlaceCommand::new(
|
||||
table, &link_path,
|
||||
);
|
||||
self.command_line
|
||||
.push_info(&crate::command::CadCommand::prompt(&command));
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
Err(e) => {
|
||||
self.command_line
|
||||
.push_error(crate::tf!("DATALINK: cannot read \"{path}\": {e}").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::tf!("DATALINK: cannot read \"{path}\": {e}").as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1784,15 +1899,17 @@ impl OpenCADStudio {
|
||||
&self.tabs[i].scene.document,
|
||||
*link_handle,
|
||||
)
|
||||
.and_then(|path| std::fs::write(path, csv).map_err(|error| error.to_string()))
|
||||
{
|
||||
.and_then(|path| {
|
||||
std::fs::write(path, csv).map_err(|error| error.to_string())
|
||||
}) {
|
||||
Ok(()) => written += 1,
|
||||
Err(error) => errors.push(error),
|
||||
}
|
||||
}
|
||||
if written > 0 {
|
||||
self.command_line.push_output(
|
||||
crate::tf!("DATALINKUPDATE: wrote {} linked source(s).", written).as_ref(),
|
||||
crate::tf!("DATALINKUPDATE: wrote {} linked source(s).", written)
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
for error in errors {
|
||||
@@ -1813,8 +1930,9 @@ impl OpenCADStudio {
|
||||
.filter(|(_, _, rows)| !rows.is_empty())
|
||||
.collect();
|
||||
if updates.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("DATALINKUPDATE: linked sources could not be read.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("DATALINKUPDATE: linked sources could not be read.").as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
self.push_undo_snapshot(i, "DATALINKUPDATE");
|
||||
@@ -1834,7 +1952,11 @@ impl OpenCADStudio {
|
||||
table.remove_row(table.row_count() - 1);
|
||||
}
|
||||
while table.column_count() < columns {
|
||||
let width = table.columns.last().map(|column| column.width).unwrap_or(2.0);
|
||||
let width = table
|
||||
.columns
|
||||
.last()
|
||||
.map(|column| column.width)
|
||||
.unwrap_or(2.0);
|
||||
table.add_column(width);
|
||||
}
|
||||
while table.column_count() > columns {
|
||||
@@ -1864,8 +1986,11 @@ impl OpenCADStudio {
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
self.command_line.push_output(
|
||||
crate::tf!("DATALINKUPDATE: updated {} linked table(s).", changed_handles.len())
|
||||
.as_ref(),
|
||||
crate::tf!(
|
||||
"DATALINKUPDATE: updated {} linked table(s).",
|
||||
changed_handles.len()
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1874,8 +1999,10 @@ impl OpenCADStudio {
|
||||
// (northing easting elevation) → Point at (easting, northing, elev).
|
||||
"LANDXMLIMPORT" => {
|
||||
use crate::command::ValuePromptCommand;
|
||||
let c =
|
||||
ValuePromptCommand::new("LANDXMLIMPORT", "LANDXMLIMPORT path to the .xml file:");
|
||||
let c = ValuePromptCommand::new(
|
||||
"LANDXMLIMPORT",
|
||||
"LANDXMLIMPORT path to the .xml file:",
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
@@ -1883,7 +2010,10 @@ impl OpenCADStudio {
|
||||
let path = cmd.trim_start_matches("LANDXMLIMPORT").trim();
|
||||
if path.is_empty() {
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: LANDXMLIMPORT <path-to-.xml> (imports CgPoint survey points)").as_ref(),
|
||||
crate::t!(
|
||||
"Usage: LANDXMLIMPORT <path-to-.xml> (imports CgPoint survey points)"
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
@@ -1891,8 +2021,10 @@ impl OpenCADStudio {
|
||||
Ok(xml) => {
|
||||
let pts = parse_landxml_cgpoints(&xml);
|
||||
if pts.is_empty() {
|
||||
self.command_line
|
||||
.push_info(crate::t!("LANDXMLIMPORT: no <CgPoint> survey points found.").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("LANDXMLIMPORT: no <CgPoint> survey points found.")
|
||||
.as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
self.push_undo_snapshot(i, "LANDXMLIMPORT");
|
||||
@@ -1904,14 +2036,17 @@ impl OpenCADStudio {
|
||||
.add_entity_clone(codec::EntityType::Point(p));
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(crate::tf!(
|
||||
self.command_line.push_output(
|
||||
crate::tf!(
|
||||
"LANDXMLIMPORT: imported {} survey point(s). Use ZOOM EXTENTS to view.",
|
||||
pts.len()
|
||||
).as_ref());
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
Err(e) => self
|
||||
.command_line
|
||||
.push_error(crate::tf!("LANDXMLIMPORT: cannot read \"{path}\": {e}").as_ref()),
|
||||
Err(e) => self.command_line.push_error(
|
||||
crate::tf!("LANDXMLIMPORT: cannot read \"{path}\": {e}").as_ref(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2046,8 +2181,14 @@ mod tests {
|
||||
app.tabs[i].scene.refresh_pending_any(),
|
||||
"REDRAW must leave a pending force request"
|
||||
);
|
||||
assert_eq!(app.tabs[i].scene.geometry_epoch, geom_before, "REDRAW must not regen");
|
||||
assert_eq!(app.tabs[i].scene.block_epoch, block_before, "REDRAW must not regen blocks");
|
||||
assert_eq!(
|
||||
app.tabs[i].scene.geometry_epoch, geom_before,
|
||||
"REDRAW must not regen"
|
||||
);
|
||||
assert_eq!(
|
||||
app.tabs[i].scene.block_epoch, block_before,
|
||||
"REDRAW must not regen blocks"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2057,7 +2198,10 @@ mod tests {
|
||||
let _ = full.run_command_line("REDRAW");
|
||||
let _ = short.run_command_line("R");
|
||||
let i = full.active_tab;
|
||||
assert!(short.tabs[i].scene.refresh_pending_any(), "'R' must trigger REDRAW");
|
||||
assert!(
|
||||
short.tabs[i].scene.refresh_pending_any(),
|
||||
"'R' must trigger REDRAW"
|
||||
);
|
||||
assert_eq!(
|
||||
short.tabs[i].scene.refresh_pending_any(),
|
||||
full.tabs[i].scene.refresh_pending_any(),
|
||||
@@ -2070,7 +2214,10 @@ mod tests {
|
||||
let mut app = fresh_app();
|
||||
let i = app.active_tab;
|
||||
let _ = app.run_command_line("REDRAWALL");
|
||||
assert!(app.tabs[i].scene.refresh_pending_any(), "REDRAWALL must leave a force request");
|
||||
assert!(
|
||||
app.tabs[i].scene.refresh_pending_any(),
|
||||
"REDRAWALL must leave a force request"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2081,11 +2228,23 @@ mod tests {
|
||||
let block_before = app.tabs[i].scene.block_epoch;
|
||||
app.tabs[i].dirty = false;
|
||||
let _ = app.run_command_line("REGEN");
|
||||
assert_ne!(app.tabs[i].scene.geometry_epoch, geom_before, "REGEN must regenerate geometry");
|
||||
assert_ne!(app.tabs[i].scene.block_epoch, block_before, "REGEN must regenerate block epoch");
|
||||
assert!(!app.tabs[i].dirty, "REGEN must NOT mark the document as modified (no DB change)");
|
||||
assert_ne!(
|
||||
app.tabs[i].scene.geometry_epoch, geom_before,
|
||||
"REGEN must regenerate geometry"
|
||||
);
|
||||
assert_ne!(
|
||||
app.tabs[i].scene.block_epoch, block_before,
|
||||
"REGEN must regenerate block epoch"
|
||||
);
|
||||
assert!(
|
||||
!app.tabs[i].dirty,
|
||||
"REGEN must NOT mark the document as modified (no DB change)"
|
||||
);
|
||||
let _ = app.run_command_line("REGENALL");
|
||||
assert!(!app.tabs[i].dirty, "REGENALL must not dirty the document either");
|
||||
assert!(
|
||||
!app.tabs[i].dirty,
|
||||
"REGENALL must not dirty the document either"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+67
-67
@@ -438,9 +438,7 @@ impl OpenCADStudio {
|
||||
.document
|
||||
.entities()
|
||||
.filter_map(|entity| match entity {
|
||||
codec::EntityType::Face3D(face) => {
|
||||
Some((face.common.handle, face.clone()))
|
||||
}
|
||||
codec::EntityType::Face3D(face) => Some((face.common.handle, face.clone())),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
@@ -1030,9 +1028,8 @@ impl OpenCADStudio {
|
||||
false,
|
||||
true,
|
||||
);
|
||||
self.command_line.push_output(
|
||||
format!("{} constraint bar(s) reset.", count).as_str(),
|
||||
);
|
||||
self.command_line
|
||||
.push_output(format!("{} constraint bar(s) reset.", count).as_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1145,20 +1142,15 @@ impl OpenCADStudio {
|
||||
self.tabs[i].active_cmd = Some(Box::new(sel));
|
||||
} else {
|
||||
let scope = self.tabs[i].current_parametric_scope();
|
||||
let inferred = self.tabs[i]
|
||||
.scene
|
||||
.inferred_parametric_constraints(
|
||||
scope,
|
||||
&handles,
|
||||
&self.auto_constrain_settings,
|
||||
);
|
||||
let inferred = self.tabs[i].scene.inferred_parametric_constraints(
|
||||
scope,
|
||||
&handles,
|
||||
&self.auto_constrain_settings,
|
||||
);
|
||||
if inferred.is_empty() {
|
||||
self.command_line.push_output(
|
||||
format!(
|
||||
"0 constraint(s) applied to {} object(s).",
|
||||
handles.len()
|
||||
)
|
||||
.as_str(),
|
||||
format!("0 constraint(s) applied to {} object(s).", handles.len())
|
||||
.as_str(),
|
||||
);
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.refresh_selected_grips();
|
||||
@@ -1281,8 +1273,7 @@ impl OpenCADStudio {
|
||||
use crate::scene::named_parameters::DrivingValue;
|
||||
use crate::scene::parametric_constraints::{
|
||||
dynamic_dimension_text, next_angular_parameter_name,
|
||||
next_dimensional_parameter_name, next_radial_parameter_name,
|
||||
ConstraintKind,
|
||||
next_dimensional_parameter_name, next_radial_parameter_name, ConstraintKind,
|
||||
};
|
||||
|
||||
let scope = self.tabs[i].current_parametric_scope();
|
||||
@@ -1322,7 +1313,11 @@ impl OpenCADStudio {
|
||||
};
|
||||
if self.tabs[i]
|
||||
.scene
|
||||
.validate_parametric_constraint(kind, &refs, Some(&DrivingValue::Literal(measured)))
|
||||
.validate_parametric_constraint(
|
||||
kind,
|
||||
&refs,
|
||||
Some(&DrivingValue::Literal(measured)),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
refused += 1;
|
||||
@@ -1451,10 +1446,12 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
self.command_line
|
||||
.push_output(crate::tf!("{converted} associative dimensions converted").as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!("{refused} associative dimension(s) could not be converted").as_ref(),
|
||||
crate::tf!("{converted} associative dimensions converted").as_ref(),
|
||||
);
|
||||
self.command_line.push_output(
|
||||
crate::tf!("{refused} associative dimension(s) could not be converted")
|
||||
.as_ref(),
|
||||
);
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
@@ -1472,7 +1469,11 @@ impl OpenCADStudio {
|
||||
let (kind, axis, label) = if vertical {
|
||||
(ConstraintKind::Vertical, "Vertical", "Vertical constraint")
|
||||
} else {
|
||||
(ConstraintKind::Horizontal, "Horizontal", "Horizontal constraint")
|
||||
(
|
||||
ConstraintKind::Horizontal,
|
||||
"Horizontal",
|
||||
"Horizontal constraint",
|
||||
)
|
||||
};
|
||||
let new_command = || {
|
||||
if vertical {
|
||||
@@ -1491,13 +1492,14 @@ impl OpenCADStudio {
|
||||
.push_error(&format!("{axis}: select exactly one compatible object."));
|
||||
} else {
|
||||
let handle = handles[0];
|
||||
let reference = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.and_then(|entity| {
|
||||
HorizontalConstraintCommand::preselected_reference(entity, handle)
|
||||
});
|
||||
let reference =
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.and_then(|entity| {
|
||||
HorizontalConstraintCommand::preselected_reference(entity, handle)
|
||||
});
|
||||
if let Some(reference) = reference {
|
||||
let plane = self.tabs[i].ucs_xform().working_plane();
|
||||
let direction = if vertical { plane.y } else { plane.x };
|
||||
@@ -1532,13 +1534,14 @@ impl OpenCADStudio {
|
||||
// the reference's own point-or-object prompt.
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
if let [handle] = handles.as_slice() {
|
||||
let reference = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(*handle)
|
||||
.and_then(|entity| {
|
||||
FixConstraintCommand::preselected_reference(entity, *handle)
|
||||
});
|
||||
let reference =
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(*handle)
|
||||
.and_then(|entity| {
|
||||
FixConstraintCommand::preselected_reference(entity, *handle)
|
||||
});
|
||||
if let Some(reference) = reference {
|
||||
return Some(self.apply_cmd_result(CmdResult::AddParametricConstraint {
|
||||
kind: ConstraintKind::Fixed,
|
||||
@@ -1718,13 +1721,15 @@ impl OpenCADStudio {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if picks.len() == 2 && picks[0].reference != picks[1].reference {
|
||||
return Some(self.apply_cmd_result(CmdResult::AddPerpendicularConstraint {
|
||||
first: picks[0].reference,
|
||||
second: picks[1].reference,
|
||||
first_fixed: picks[0].fixed_reference,
|
||||
second_start: picks[1].start_reference,
|
||||
label: "Perpendicular constraint",
|
||||
}));
|
||||
return Some(self.apply_cmd_result(
|
||||
CmdResult::AddPerpendicularConstraint {
|
||||
first: picks[0].reference,
|
||||
second: picks[1].reference,
|
||||
first_fixed: picks[0].fixed_reference,
|
||||
second_start: picks[1].start_reference,
|
||||
label: "Perpendicular constraint",
|
||||
},
|
||||
));
|
||||
}
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.command_line.push_error(
|
||||
@@ -1860,21 +1865,21 @@ impl OpenCADStudio {
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
} else {
|
||||
use crate::command::{
|
||||
CmdResult, SymmetricConstraintSelection,
|
||||
};
|
||||
use crate::command::{CmdResult, SymmetricConstraintSelection};
|
||||
use crate::modules::parametric::SymmetricConstraintCommand;
|
||||
|
||||
let refs = (handles.len() == 3).then(|| {
|
||||
let first = self.tabs[i].scene.document.get_entity(handles[0])?;
|
||||
let second = self.tabs[i].scene.document.get_entity(handles[1])?;
|
||||
let axis = self.tabs[i].scene.document.get_entity(handles[2])?;
|
||||
SymmetricConstraintCommand::preselected_refs(
|
||||
(first, handles[0]),
|
||||
(second, handles[1]),
|
||||
(axis, handles[2]),
|
||||
)
|
||||
}).flatten();
|
||||
let refs = (handles.len() == 3)
|
||||
.then(|| {
|
||||
let first = self.tabs[i].scene.document.get_entity(handles[0])?;
|
||||
let second = self.tabs[i].scene.document.get_entity(handles[1])?;
|
||||
let axis = self.tabs[i].scene.document.get_entity(handles[2])?;
|
||||
SymmetricConstraintCommand::preselected_refs(
|
||||
(first, handles[0]),
|
||||
(second, handles[1]),
|
||||
(axis, handles[2]),
|
||||
)
|
||||
})
|
||||
.flatten();
|
||||
if let Some([first, second, axis]) = refs {
|
||||
return Some(self.apply_cmd_result(CmdResult::AddSymmetricConstraint {
|
||||
selection: SymmetricConstraintSelection::Objects(first, second),
|
||||
@@ -1962,10 +1967,8 @@ impl OpenCADStudio {
|
||||
// asked for this command.
|
||||
// The reference asks for the circle itself, whatever is
|
||||
// selected, so a pick-first set is left alone.
|
||||
let name = next_radial_parameter_name(
|
||||
self.tabs[i].scene.named_parameters(),
|
||||
diameter,
|
||||
);
|
||||
let name =
|
||||
next_radial_parameter_name(self.tabs[i].scene.named_parameters(), diameter);
|
||||
let command = DimConstraintCommand::new(
|
||||
if diameter {
|
||||
DimConstraintAxis::Diameter
|
||||
@@ -2537,10 +2540,7 @@ impl OpenCADStudio {
|
||||
.filter(|handle| {
|
||||
matches!(
|
||||
scene.document.get_entity(*handle),
|
||||
Some(
|
||||
codec::EntityType::Solid3D(_)
|
||||
| codec::EntityType::Surface(_)
|
||||
)
|
||||
Some(codec::EntityType::Solid3D(_) | codec::EntityType::Surface(_))
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
+82
-24
@@ -3,10 +3,12 @@
|
||||
|
||||
use crate::app::{Message, OpenCADStudio};
|
||||
use crate::command::CadCommand;
|
||||
use crate::modules::annotate::field_cmd::{FieldObjectPickCommand, FieldPlaceCommand, FieldTablePickCommand};
|
||||
use crate::modules::annotate::field_cmd::{
|
||||
FieldObjectPickCommand, FieldPlaceCommand, FieldTablePickCommand,
|
||||
};
|
||||
use crate::ui::window::field_dialog::{
|
||||
fields_of, nav_props, object_properties, FieldDialogMsg, FieldDialogState, FieldTarget, DATE_FORMATS,
|
||||
NAMED_TYPES,
|
||||
fields_of, nav_props, object_properties, FieldDialogMsg, FieldDialogState, FieldTarget,
|
||||
DATE_FORMATS, NAMED_TYPES,
|
||||
};
|
||||
use iced::Task;
|
||||
|
||||
@@ -18,7 +20,10 @@ impl OpenCADStudio {
|
||||
Some(Task::none())
|
||||
}
|
||||
// FIELDEVAL — the events that update fields, kept in the drawing.
|
||||
cmd if cmd == "FIELDEVAL" || cmd.starts_with("FIELDEVAL ") || cmd.starts_with("SETVAR FIELDEVAL") => {
|
||||
cmd if cmd == "FIELDEVAL"
|
||||
|| cmd.starts_with("FIELDEVAL ")
|
||||
|| cmd.starts_with("SETVAR FIELDEVAL") =>
|
||||
{
|
||||
let value = cmd
|
||||
.trim_start_matches("SETVAR ")
|
||||
.trim_start_matches("FIELDEVAL")
|
||||
@@ -44,8 +49,9 @@ impl OpenCADStudio {
|
||||
}
|
||||
_ => {
|
||||
let (min, max) = (0, 31);
|
||||
self.command_line
|
||||
.push_error(&crate::tf!("Requires an integer between {min} and {max}."));
|
||||
self.command_line.push_error(&crate::tf!(
|
||||
"Requires an integer between {min} and {max}."
|
||||
));
|
||||
ask(self);
|
||||
}
|
||||
}
|
||||
@@ -58,9 +64,14 @@ impl OpenCADStudio {
|
||||
.and_then(|h| u64::from_str_radix(h, 16).ok())
|
||||
.map(codec::Handle::new);
|
||||
if let Some(state) = self.field_dialog.as_mut() {
|
||||
if let Some(entity) = handle.and_then(|h| self.tabs[i].scene.document.get_entity(h)) {
|
||||
if let Some(entity) =
|
||||
handle.and_then(|h| self.tabs[i].scene.document.get_entity(h))
|
||||
{
|
||||
let props = object_properties(entity);
|
||||
state.object = Some((entity.common().handle, crate::t!(crate::entities::names::ui_name(entity)).into_owned()));
|
||||
state.object = Some((
|
||||
entity.common().handle,
|
||||
crate::t!(crate::entities::names::ui_name(entity)).into_owned(),
|
||||
));
|
||||
state.object_prop = props.first().copied();
|
||||
state.object_props = props;
|
||||
}
|
||||
@@ -100,7 +111,11 @@ impl OpenCADStudio {
|
||||
let function = *parts.first()?;
|
||||
let cell = |at: &[&str]| -> Option<(codec::Handle, usize, usize)> {
|
||||
let handle = codec::Handle::new(u64::from_str_radix(at.first()?, 16).ok()?);
|
||||
let point = glam::DVec3::new(at.get(1)?.parse().ok()?, at.get(2)?.parse().ok()?, at.get(3)?.parse().ok()?);
|
||||
let point = glam::DVec3::new(
|
||||
at.get(1)?.parse().ok()?,
|
||||
at.get(2)?.parse().ok()?,
|
||||
at.get(3)?.parse().ok()?,
|
||||
);
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let codec::EntityType::Table(table) = document.get_entity(handle)? else {
|
||||
return None;
|
||||
@@ -111,7 +126,11 @@ impl OpenCADStudio {
|
||||
let name = |row: usize, column: usize| format!("{}{}", column_letters(column), row + 1);
|
||||
let (table, row, column) = cell(parts.get(1..5)?)?;
|
||||
if function == "Cell" {
|
||||
return Some(format!("Table(%<\\_ObjId {:X}>%).{}", table.value(), name(row, column)));
|
||||
return Some(format!(
|
||||
"Table(%<\\_ObjId {:X}>%).{}",
|
||||
table.value(),
|
||||
name(row, column)
|
||||
));
|
||||
}
|
||||
let (second_table, row2, column2) = cell(parts.get(5..9)?)?;
|
||||
if second_table != table {
|
||||
@@ -139,7 +158,13 @@ impl OpenCADStudio {
|
||||
.collect();
|
||||
if let Some(db) = self.sheet_set.db() {
|
||||
let custom = codec::sheet_set::custom_properties(db.sheet_set());
|
||||
let names = |flag: i32| custom.iter().filter(|(_, _, f)| f & flag != 0).map(|(n, _, _)| n.clone()).collect();
|
||||
let names = |flag: i32| {
|
||||
custom
|
||||
.iter()
|
||||
.filter(|(_, _, f)| f & flag != 0)
|
||||
.map(|(n, _, _)| n.clone())
|
||||
.collect()
|
||||
};
|
||||
state.ss_sheet_custom = names(codec::sheet_set::CUSTOM_SHEET_PROP);
|
||||
state.ss_set_custom = names(codec::sheet_set::CUSTOM_SHEET_SET_PROP);
|
||||
state.ss_set_name = db.name().to_string();
|
||||
@@ -153,8 +178,13 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
fn field_value(&self, code: &str, objects: &[codec::Handle]) -> String {
|
||||
crate::entities::field::evaluate(&self.tabs[self.active_tab].scene.document, code, objects, None)
|
||||
.unwrap_or_else(|| "----".to_string())
|
||||
crate::entities::field::evaluate(
|
||||
&self.tabs[self.active_tab].scene.document,
|
||||
code,
|
||||
objects,
|
||||
None,
|
||||
)
|
||||
.unwrap_or_else(|| "----".to_string())
|
||||
}
|
||||
|
||||
fn refresh_field_preview(&mut self) {
|
||||
@@ -165,7 +195,8 @@ impl OpenCADStudio {
|
||||
let preview = if state.complete() {
|
||||
if state.name == "BlockPlaceholder" {
|
||||
// A placeholder shows its property name until a block reference resolves it.
|
||||
let (label, _, _) = codec::fields::BLOCK_PLACEHOLDER_PROPERTIES[state.placeholder_property];
|
||||
let (label, _, _) =
|
||||
codec::fields::BLOCK_PLACEHOLDER_PROPERTIES[state.placeholder_property];
|
||||
text_case(label, state.text_case)
|
||||
} else {
|
||||
self.field_value(&code, &objects)
|
||||
@@ -191,16 +222,32 @@ impl OpenCADStudio {
|
||||
.filter(|b| !b.name.starts_with('*'))
|
||||
.map(|b| (b.name.clone(), b.handle))
|
||||
.collect(),
|
||||
"Dimstyle" => document.dim_styles.iter().map(|s| (s.name.clone(), s.handle)).collect(),
|
||||
"Layer" => document.layers.iter().map(|l| (l.name.clone(), l.handle)).collect(),
|
||||
"Linetype" => document.line_types.iter().map(|l| (l.name.clone(), l.handle)).collect(),
|
||||
"Dimstyle" => document
|
||||
.dim_styles
|
||||
.iter()
|
||||
.map(|s| (s.name.clone(), s.handle))
|
||||
.collect(),
|
||||
"Layer" => document
|
||||
.layers
|
||||
.iter()
|
||||
.map(|l| (l.name.clone(), l.handle))
|
||||
.collect(),
|
||||
"Linetype" => document
|
||||
.line_types
|
||||
.iter()
|
||||
.map(|l| (l.name.clone(), l.handle))
|
||||
.collect(),
|
||||
"Textstyle" => document
|
||||
.text_styles
|
||||
.iter()
|
||||
.filter(|s| !s.name.is_empty())
|
||||
.map(|s| (s.name.clone(), s.handle))
|
||||
.collect(),
|
||||
"View" => document.views.iter().map(|v| (v.name.clone(), v.handle)).collect(),
|
||||
"View" => document
|
||||
.views
|
||||
.iter()
|
||||
.map(|v| (v.name.clone(), v.handle))
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
names.sort_by_key(|(n, _)| n.to_lowercase());
|
||||
@@ -378,8 +425,9 @@ impl OpenCADStudio {
|
||||
match state.target {
|
||||
FieldTarget::NewText => {
|
||||
self.close_active_modal();
|
||||
let defaults =
|
||||
crate::scene::creation_style::current_text_defaults(&self.tabs[i].scene.document);
|
||||
let defaults = crate::scene::creation_style::current_text_defaults(
|
||||
&self.tabs[i].scene.document,
|
||||
);
|
||||
let command = FieldPlaceCommand::new(
|
||||
value,
|
||||
field.expect("set above"),
|
||||
@@ -416,8 +464,12 @@ impl OpenCADStudio {
|
||||
let target = self.field_dialog.take().map(|s| s.target);
|
||||
self.close_active_modal();
|
||||
self.active_modal = match target {
|
||||
Some(FieldTarget::AttdefDefault) if self.attdef_dialog.is_some() => Some(crate::app::ModalKind::AttDef),
|
||||
Some(FieldTarget::AttdefEdit) if self.attdef_edit.is_some() => Some(crate::app::ModalKind::AttDefEdit),
|
||||
Some(FieldTarget::AttdefDefault) if self.attdef_dialog.is_some() => {
|
||||
Some(crate::app::ModalKind::AttDef)
|
||||
}
|
||||
Some(FieldTarget::AttdefEdit) if self.attdef_edit.is_some() => {
|
||||
Some(crate::app::ModalKind::AttDefEdit)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@@ -445,14 +497,20 @@ fn text_case(value: &str, case: usize) -> String {
|
||||
3 => {
|
||||
let lower = value.to_lowercase();
|
||||
let mut chars = lower.chars();
|
||||
chars.next().map(|c| c.to_uppercase().chain(chars).collect()).unwrap_or_default()
|
||||
chars
|
||||
.next()
|
||||
.map(|c| c.to_uppercase().chain(chars).collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
4 => value
|
||||
.split(' ')
|
||||
.map(|word| {
|
||||
let lower = word.to_lowercase();
|
||||
let mut chars = lower.chars();
|
||||
chars.next().map(|c| c.to_uppercase().chain(chars).collect::<String>()).unwrap_or_default()
|
||||
chars
|
||||
.next()
|
||||
.map(|c| c.to_uppercase().chain(chars).collect::<String>())
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
|
||||
+148
-80
@@ -28,8 +28,9 @@ impl OpenCADStudio {
|
||||
// Tabs without a path (never saved) are skipped with a note.
|
||||
"SAVEALL" => {
|
||||
if self.read_only {
|
||||
self.command_line
|
||||
.push_error(crate::t!("Read-only session (--read-only): saving is disabled.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("Read-only session (--read-only): saving is disabled.").as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -41,47 +42,47 @@ impl OpenCADStudio {
|
||||
continue;
|
||||
}
|
||||
if let Some(path) = self.tabs[t].current_path.clone() {
|
||||
if self
|
||||
.active_save_jobs
|
||||
.contains_key(&self.tabs[t].id)
|
||||
{
|
||||
self.command_line.push_info(crate::tf!(
|
||||
"SAVEALL: {} already has a save running",
|
||||
path.display()
|
||||
).as_ref());
|
||||
if self.active_save_jobs.contains_key(&self.tabs[t].id) {
|
||||
self.command_line.push_info(
|
||||
crate::tf!(
|
||||
"SAVEALL: {} already has a save running",
|
||||
path.display()
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
match self.save_tab_synchronously_protected(
|
||||
t,
|
||||
path.clone(),
|
||||
false,
|
||||
) {
|
||||
match self.save_tab_synchronously_protected(t, path.clone(), false) {
|
||||
Ok(()) => {
|
||||
saved += 1;
|
||||
}
|
||||
Err(e) => self.command_line.push_error(crate::tf!(
|
||||
"SAVEALL: {} failed: {e}",
|
||||
path.display()
|
||||
).as_ref()),
|
||||
Err(e) => self.command_line.push_error(
|
||||
crate::tf!("SAVEALL: {} failed: {e}", path.display()).as_ref(),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
skipped += 1;
|
||||
}
|
||||
}
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"SAVEALL: saved {saved} drawing(s){}.",
|
||||
if skipped > 0 {
|
||||
crate::tf!("; {skipped} need SAVEAS (no file path yet)").into_owned()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!(
|
||||
"SAVEALL: saved {saved} drawing(s){}.",
|
||||
if skipped > 0 {
|
||||
crate::tf!("; {skipped} need SAVEAS (no file path yet)")
|
||||
.into_owned()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
self.command_line
|
||||
.push_info(crate::t!("SAVEALL: save each tab individually in the web build.").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("SAVEALL: save each tab individually in the web build.").as_ref(),
|
||||
);
|
||||
}
|
||||
return Some(Task::none());
|
||||
}
|
||||
@@ -105,7 +106,8 @@ impl OpenCADStudio {
|
||||
// without undoing any work done since.
|
||||
"OOPS" => {
|
||||
if self.oops_cache.is_empty() {
|
||||
self.command_line.push_info(crate::t!("OOPS: nothing to restore.").as_ref());
|
||||
self.command_line
|
||||
.push_info(crate::t!("OOPS: nothing to restore.").as_ref());
|
||||
} else {
|
||||
let restored = std::mem::take(&mut self.oops_cache);
|
||||
let pending = self.begin_undo(i, "OOPS", restored.len(), true);
|
||||
@@ -202,7 +204,11 @@ impl OpenCADStudio {
|
||||
}
|
||||
for d in deps {
|
||||
let dp = PathBuf::from(&d);
|
||||
let resolved = if dp.is_absolute() { dp } else { parent.join(&dp) };
|
||||
let resolved = if dp.is_absolute() {
|
||||
dp
|
||||
} else {
|
||||
parent.join(&dp)
|
||||
};
|
||||
if resolved.exists() {
|
||||
if let Some(fname) = resolved.file_name() {
|
||||
if std::fs::copy(&resolved, folder.join(fname)).is_ok() {
|
||||
@@ -211,18 +217,23 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"{cmd}: packaged {copied} file(s) into {}",
|
||||
folder.display()
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!(
|
||||
"{cmd}: packaged {copied} file(s) into {}",
|
||||
folder.display()
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
Err(e) => self
|
||||
.command_line
|
||||
.push_error(crate::tf!("{cmd}: cannot create folder ({e}).").as_ref()),
|
||||
}
|
||||
} else {
|
||||
self.command_line
|
||||
.push_error(crate::t!("ARCHIVE: save the drawing first (it has no file path yet).").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("ARCHIVE: save the drawing first (it has no file path yet).")
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,11 +252,14 @@ impl OpenCADStudio {
|
||||
"PERF" => {
|
||||
self.perf_hud = !self.perf_hud;
|
||||
crate::perf::set_ui_enabled(self.perf_hud);
|
||||
self.command_line.push_info(crate::t!(if self.perf_hud {
|
||||
"PERF panel on — tracing render and interaction costs"
|
||||
} else {
|
||||
"PERF panel off"
|
||||
}).as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!(if self.perf_hud {
|
||||
"PERF panel on — tracing render and interaction costs"
|
||||
} else {
|
||||
"PERF panel off"
|
||||
})
|
||||
.as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
|
||||
@@ -277,14 +291,16 @@ impl OpenCADStudio {
|
||||
self.model_space.mode = crate::app::config::ModelSpaceMode::MatchTheme;
|
||||
self.sync_model_space_theme(true);
|
||||
self.persist_settings_if_changed();
|
||||
self.command_line
|
||||
.push_output(crate::t!("Model space background set to Match Theme.").as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::t!("Model space background set to Match Theme.").as_ref(),
|
||||
);
|
||||
} else if first_arg == "CLASSIC" {
|
||||
self.model_space.mode = crate::app::config::ModelSpaceMode::ClassicDark;
|
||||
self.sync_model_space_theme(true);
|
||||
self.persist_settings_if_changed();
|
||||
self.command_line
|
||||
.push_output(crate::t!("Model space background set to Classic CAD Dark.").as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::t!("Model space background set to Classic CAD Dark.").as_ref(),
|
||||
);
|
||||
} else if first_arg == "DEFAULT" || first_arg == "RESET" {
|
||||
if is_paper {
|
||||
self.model_space.custom_paper_bg = None;
|
||||
@@ -300,31 +316,48 @@ impl OpenCADStudio {
|
||||
.push_output(crate::t!("Background reset to default.").as_ref());
|
||||
} else if first_arg == "DESK" {
|
||||
let sub_args = &args[1..];
|
||||
let sub_first = sub_args.first().map(|s| s.to_uppercase()).unwrap_or_default();
|
||||
let sub_first = sub_args
|
||||
.first()
|
||||
.map(|s| s.to_uppercase())
|
||||
.unwrap_or_default();
|
||||
if sub_first == "DEFAULT" || sub_first == "RESET" {
|
||||
self.model_space.custom_desk_bg = None;
|
||||
self.sync_model_space_theme(false);
|
||||
self.persist_settings_if_changed();
|
||||
self.command_line
|
||||
.push_output(crate::t!("Paper desk surround background reset to default.").as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::t!("Paper desk surround background reset to default.").as_ref(),
|
||||
);
|
||||
} else if let Some(rgba) = parse_background_color(sub_args) {
|
||||
let [r, g, b, _] = rgba;
|
||||
let rgb = [(r * 255.0).round() as u8, (g * 255.0).round() as u8, (b * 255.0).round() as u8];
|
||||
let rgb = [
|
||||
(r * 255.0).round() as u8,
|
||||
(g * 255.0).round() as u8,
|
||||
(b * 255.0).round() as u8,
|
||||
];
|
||||
self.model_space.custom_desk_bg = Some(rgb);
|
||||
self.sync_model_space_theme(false);
|
||||
self.persist_settings_if_changed();
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"Desk surround background: rgb({}, {}, {})",
|
||||
rgb[0],
|
||||
rgb[1],
|
||||
rgb[2]
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!(
|
||||
"Desk surround background: rgb({}, {}, {})",
|
||||
rgb[0],
|
||||
rgb[1],
|
||||
rgb[2]
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
} else {
|
||||
self.command_line.push_info(crate::t!("Usage: BACKGROUND DESK <r> <g> <b> | DEFAULT").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: BACKGROUND DESK <r> <g> <b> | DEFAULT").as_ref(),
|
||||
);
|
||||
}
|
||||
} else if let Some(rgba) = parse_background_color(&args) {
|
||||
let [r, g, b, _] = rgba;
|
||||
let rgb = [(r * 255.0).round() as u8, (g * 255.0).round() as u8, (b * 255.0).round() as u8];
|
||||
let rgb = [
|
||||
(r * 255.0).round() as u8,
|
||||
(g * 255.0).round() as u8,
|
||||
(b * 255.0).round() as u8,
|
||||
];
|
||||
if is_paper {
|
||||
self.model_space.custom_paper_bg = Some(rgb);
|
||||
self.paper_bg_input = crate::app::config::rgb_to_hex(rgb);
|
||||
@@ -335,12 +368,9 @@ impl OpenCADStudio {
|
||||
}
|
||||
self.sync_model_space_theme(true);
|
||||
self.persist_settings_if_changed();
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"Background: rgb({}, {}, {})",
|
||||
rgb[0],
|
||||
rgb[1],
|
||||
rgb[2]
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!("Background: rgb({}, {}, {})", rgb[0], rgb[1], rgb[2]).as_ref(),
|
||||
);
|
||||
} else {
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: BACKGROUND <r> <g> <b> (0–255) | THEME|CLASSIC|DEFAULT|DESK|BLACK|DARKGRAY|GRAY|LIGHTGRAY|WHITE").as_ref(),
|
||||
@@ -397,15 +427,19 @@ impl OpenCADStudio {
|
||||
.filter(|l| !l.starts_with('#') && !l.starts_with(';'))
|
||||
.map(|l| Task::done(Message::ScriptLine(l.to_string())))
|
||||
.collect();
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"SCRIPT: running {} command(s) from {clean_path}.",
|
||||
cmds.len()
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!(
|
||||
"SCRIPT: running {} command(s) from {clean_path}.",
|
||||
cmds.len()
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
return Some(Task::batch(cmds));
|
||||
}
|
||||
Err(e) => {
|
||||
self.command_line
|
||||
.push_error(crate::tf!("SCRIPT: cannot read {clean_path}: {e}").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::tf!("SCRIPT: cannot read {clean_path}: {e}").as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -480,17 +514,30 @@ mod tests {
|
||||
|
||||
// Feed standard multi-line CAD script sequence
|
||||
let _ = app.update(Message::ScriptLine("LINE".into()));
|
||||
assert!(app.tabs[i].active_cmd.is_some(), "LINE command should be active");
|
||||
assert!(
|
||||
app.tabs[i].active_cmd.is_some(),
|
||||
"LINE command should be active"
|
||||
);
|
||||
|
||||
let _ = app.update(Message::ScriptLine("0,0".into()));
|
||||
assert!(app.tabs[i].active_cmd.is_some(), "LINE command should stay active after first point");
|
||||
assert!(
|
||||
app.tabs[i].active_cmd.is_some(),
|
||||
"LINE command should stay active after first point"
|
||||
);
|
||||
|
||||
let _ = app.update(Message::ScriptLine("10,10".into()));
|
||||
let _ = app.update(Message::ScriptLine("".into())); // Enter to finish
|
||||
|
||||
let after_count = app.tabs[i].scene.document.entities().count();
|
||||
assert_eq!(after_count, before_count + 1, "Line entity should have been created");
|
||||
assert!(app.tabs[i].active_cmd.is_none(), "LINE command should be finished");
|
||||
assert_eq!(
|
||||
after_count,
|
||||
before_count + 1,
|
||||
"Line entity should have been created"
|
||||
);
|
||||
assert!(
|
||||
app.tabs[i].active_cmd.is_none(),
|
||||
"LINE command should be finished"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -502,8 +549,15 @@ mod tests {
|
||||
let _ = app.update(Message::ScriptLine("CIRCLE 5,5 2".into()));
|
||||
|
||||
let after_count = app.tabs[i].scene.document.entities().count();
|
||||
assert_eq!(after_count, before_count + 1, "Circle entity should have been created");
|
||||
assert!(app.tabs[i].active_cmd.is_none(), "CIRCLE command should be finished");
|
||||
assert_eq!(
|
||||
after_count,
|
||||
before_count + 1,
|
||||
"Circle entity should have been created"
|
||||
);
|
||||
assert!(
|
||||
app.tabs[i].active_cmd.is_none(),
|
||||
"CIRCLE command should be finished"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -515,7 +569,11 @@ mod tests {
|
||||
let _ = app.update(Message::ScriptLine("_CIRCLE 10,10 3".into()));
|
||||
|
||||
let after_count = app.tabs[i].scene.document.entities().count();
|
||||
assert_eq!(after_count, before_count + 1, "_CIRCLE should resolve to CIRCLE and create entity");
|
||||
assert_eq!(
|
||||
after_count,
|
||||
before_count + 1,
|
||||
"_CIRCLE should resolve to CIRCLE and create entity"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -546,7 +604,11 @@ CIRCLE 50,50 15
|
||||
|
||||
ZOOM EXTENTS
|
||||
"#;
|
||||
for line in SCRIPT.lines().map(str::trim).filter(|l| !l.starts_with('#') && !l.starts_with(';')) {
|
||||
for line in SCRIPT
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.filter(|l| !l.starts_with('#') && !l.starts_with(';'))
|
||||
{
|
||||
let _ = app.update(Message::ScriptLine(line.to_string()));
|
||||
}
|
||||
// 4 square edges + 2 diagonal lines + 2 circles = 8 entities
|
||||
@@ -572,7 +634,10 @@ ZOOM EXTENTS
|
||||
|
||||
// 2. Blank line when idle repeats LINE
|
||||
let _ = app.update(Message::ScriptLine("".into()));
|
||||
assert!(app.tabs[i].active_cmd.is_some(), "Blank line should repeat LINE");
|
||||
assert!(
|
||||
app.tabs[i].active_cmd.is_some(),
|
||||
"Blank line should repeat LINE"
|
||||
);
|
||||
let _ = app.update(Message::ScriptLine("20,20".into()));
|
||||
let _ = app.update(Message::ScriptLine("30,30".into()));
|
||||
let _ = app.update(Message::ScriptLine("".into())); // finishes second LINE
|
||||
@@ -586,7 +651,10 @@ ZOOM EXTENTS
|
||||
|
||||
// Blank line repeats CIRCLE
|
||||
let _ = app.update(Message::ScriptLine("".into()));
|
||||
assert!(app.tabs[i].active_cmd.is_some(), "Blank line should repeat CIRCLE");
|
||||
assert!(
|
||||
app.tabs[i].active_cmd.is_some(),
|
||||
"Blank line should repeat CIRCLE"
|
||||
);
|
||||
let _ = app.update(Message::ScriptLine("15,15".into()));
|
||||
let _ = app.update(Message::ScriptLine("3".into()));
|
||||
assert_eq!(app.tabs[i].scene.document.entities().count(), 4);
|
||||
|
||||
+424
-252
File diff suppressed because it is too large.
Load diff
+373
-252
File diff suppressed because it is too large.
Load diff
+110
-105
@@ -37,7 +37,8 @@ impl OpenCADStudio {
|
||||
self.tabs[i].dirty = true;
|
||||
self.commit_layer_undo(i, undo);
|
||||
self.refresh_layer_panel();
|
||||
self.command_line.push_info(crate::t!("Layer(s) turned off.").as_ref());
|
||||
self.command_line
|
||||
.push_info(crate::t!("Layer(s) turned off.").as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +75,8 @@ impl OpenCADStudio {
|
||||
self.tabs[i].dirty = true;
|
||||
self.commit_layer_undo(i, undo);
|
||||
self.refresh_layer_panel();
|
||||
self.command_line.push_info(crate::t!("Layer(s) frozen.").as_ref());
|
||||
self.command_line
|
||||
.push_info(crate::t!("Layer(s) frozen.").as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +93,9 @@ impl OpenCADStudio {
|
||||
.filter(|&(code, _)| code != 0)
|
||||
.map(|(code, label)| (label, units::short(code), None))
|
||||
.collect();
|
||||
let current = crate::t!(units::label(self.tabs[i].scene.document.header.insertion_units));
|
||||
let current = crate::t!(units::label(
|
||||
self.tabs[i].scene.document.header.insertion_units
|
||||
));
|
||||
self.command_line
|
||||
.push_info(crate::tf!("DWGUNITS current unit: {current}").as_ref());
|
||||
let c = KeywordCommand::new("DWGUNITS", "DWGUNITS Convert drawing to:", choices);
|
||||
@@ -124,7 +128,8 @@ impl OpenCADStudio {
|
||||
cmd if cmd.starts_with("LAYDEL ") => {
|
||||
let name = cmd.trim_start_matches("LAYDEL").trim();
|
||||
if name.is_empty() {
|
||||
self.command_line.push_info(crate::t!("Usage: LAYDEL <layer name>").as_ref());
|
||||
self.command_line
|
||||
.push_info(crate::t!("Usage: LAYDEL <layer name>").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
let resolved = self.tabs[i]
|
||||
@@ -166,9 +171,9 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_layer_panel();
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"LAYDEL: deleted layer \"{layer}\" and {n} object(s)."
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!("LAYDEL: deleted layer \"{layer}\" and {n} object(s).").as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
// LAYMRG <source> <target> — move every object from <source> onto
|
||||
@@ -187,8 +192,9 @@ impl OpenCADStudio {
|
||||
let rest = cmd.trim_start_matches("LAYMRG").trim();
|
||||
let parts: Vec<&str> = rest.split_whitespace().collect();
|
||||
if parts.len() != 2 {
|
||||
self.command_line
|
||||
.push_info(crate::t!("Usage: LAYMRG <source layer> <target layer>").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: LAYMRG <source layer> <target layer>").as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
let keys: Vec<String> = self.tabs[i]
|
||||
@@ -207,18 +213,21 @@ impl OpenCADStudio {
|
||||
.find(|k| k.eq_ignore_ascii_case(parts[1]))
|
||||
.cloned();
|
||||
let (Some(src), Some(dst)) = (src, dst) else {
|
||||
self.command_line
|
||||
.push_error(crate::t!("LAYMRG: source and target layers must both exist.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("LAYMRG: source and target layers must both exist.").as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
};
|
||||
if src == dst {
|
||||
self.command_line
|
||||
.push_error(crate::t!("LAYMRG: source and target are the same layer.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("LAYMRG: source and target are the same layer.").as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
if src == "0" {
|
||||
self.command_line
|
||||
.push_error(crate::t!("LAYMRG: layer \"0\" cannot be merged away.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("LAYMRG: layer \"0\" cannot be merged away.").as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
if src.eq_ignore_ascii_case(&self.tabs[i].active_layer) {
|
||||
@@ -241,9 +250,10 @@ impl OpenCADStudio {
|
||||
.invalidate_layer_dependencies(std::slice::from_ref(&dst));
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_layer_panel();
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"LAYMRG: merged \"{src}\" into \"{dst}\" ({moved} object(s))."
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!("LAYMRG: merged \"{src}\" into \"{dst}\" ({moved} object(s)).")
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
// LAYERSTATE — save / restore named snapshots of all layer states
|
||||
@@ -269,19 +279,24 @@ impl OpenCADStudio {
|
||||
let states = self.tabs[i].scene.document.layer_states();
|
||||
if states.is_empty() {
|
||||
self.command_line.push_info(
|
||||
crate::t!("LAYERSTATE: no saved states. Use LAYERSTATE SAVE <name>.").as_ref(),
|
||||
crate::t!(
|
||||
"LAYERSTATE: no saved states. Use LAYERSTATE SAVE <name>."
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
} else {
|
||||
let mut names: Vec<&str> =
|
||||
states.iter().map(|state| state.name.as_str()).collect();
|
||||
names.sort_unstable();
|
||||
self.command_line
|
||||
.push_output(crate::tf!("Saved layer states: {}", names.join(", ")).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!("Saved layer states: {}", names.join(", ")).as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
"SAVE" | "S" => {
|
||||
if arg.is_empty() {
|
||||
self.command_line.push_info(crate::t!("Usage: LAYERSTATE SAVE <name>").as_ref());
|
||||
self.command_line
|
||||
.push_info(crate::t!("Usage: LAYERSTATE SAVE <name>").as_ref());
|
||||
} else {
|
||||
let description = self.tabs[i]
|
||||
.scene
|
||||
@@ -304,9 +319,9 @@ impl OpenCADStudio {
|
||||
self.command_line
|
||||
.push_info(crate::t!("Usage: LAYERSTATE RESTORE <name>").as_ref());
|
||||
} else if self.tabs[i].scene.document.layer_state(arg).is_none() {
|
||||
self.command_line.push_error(crate::tf!(
|
||||
"LAYERSTATE: no saved state named \"{arg}\"."
|
||||
).as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::tf!("LAYERSTATE: no saved state named \"{arg}\".").as_ref(),
|
||||
);
|
||||
} else {
|
||||
let names: Vec<String> = self.tabs[i]
|
||||
.scene
|
||||
@@ -330,9 +345,10 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.invalidate_layer_dependencies(&names);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_layer_panel();
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"LAYERSTATE: restored \"{arg}\" ({n} layer(s))."
|
||||
).as_ref());
|
||||
self.command_line.push_output(
|
||||
crate::tf!("LAYERSTATE: restored \"{arg}\" ({n} layer(s)).")
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
"DELETE" | "D" => {
|
||||
@@ -340,9 +356,9 @@ impl OpenCADStudio {
|
||||
self.command_line
|
||||
.push_info(crate::t!("Usage: LAYERSTATE DELETE <name>").as_ref());
|
||||
} else if self.tabs[i].scene.document.layer_state(arg).is_none() {
|
||||
self.command_line.push_error(crate::tf!(
|
||||
"LAYERSTATE: no saved state named \"{arg}\"."
|
||||
).as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::tf!("LAYERSTATE: no saved state named \"{arg}\".").as_ref(),
|
||||
);
|
||||
} else {
|
||||
self.push_undo_snapshot(i, "LAYERSTATE DELETE");
|
||||
self.tabs[i].scene.document.delete_layer_state(arg);
|
||||
@@ -352,8 +368,10 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.command_line
|
||||
.push_info(crate::t!("Usage: LAYERSTATE SAVE|RESTORE|DELETE <name> | ? (list)").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("Usage: LAYERSTATE SAVE|RESTORE|DELETE <name> | ? (list)")
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -389,7 +407,8 @@ impl OpenCADStudio {
|
||||
self.commit_layer_undo(i, undo);
|
||||
self.refresh_layer_panel();
|
||||
self.refresh_properties();
|
||||
self.command_line.push_info(crate::t!("Layer(s) locked.").as_ref());
|
||||
self.command_line
|
||||
.push_info(crate::t!("Layer(s) locked.").as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,7 +463,8 @@ impl OpenCADStudio {
|
||||
self.tabs[i].dirty = true;
|
||||
self.commit_layer_undo(i, undo);
|
||||
self.refresh_layer_panel();
|
||||
self.command_line.push_info(crate::t!("All layers turned on.").as_ref());
|
||||
self.command_line
|
||||
.push_info(crate::t!("All layers turned on.").as_ref());
|
||||
}
|
||||
|
||||
"LAYTHW" => {
|
||||
@@ -465,7 +485,8 @@ impl OpenCADStudio {
|
||||
self.tabs[i].dirty = true;
|
||||
self.commit_layer_undo(i, undo);
|
||||
self.refresh_layer_panel();
|
||||
self.command_line.push_info(crate::t!("All layers thawed.").as_ref());
|
||||
self.command_line
|
||||
.push_info(crate::t!("All layers thawed.").as_ref());
|
||||
}
|
||||
|
||||
"LAYULK" => {
|
||||
@@ -499,7 +520,8 @@ impl OpenCADStudio {
|
||||
self.commit_layer_undo(i, undo);
|
||||
self.refresh_layer_panel();
|
||||
self.refresh_properties();
|
||||
self.command_line.push_info(crate::t!("Layer(s) unlocked.").as_ref());
|
||||
self.command_line
|
||||
.push_info(crate::t!("Layer(s) unlocked.").as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,8 +534,10 @@ impl OpenCADStudio {
|
||||
.map(|(_, e)| e.common().layer.clone())
|
||||
.collect();
|
||||
if sel_layers.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("LAYISO: select entities on the layers to isolate first.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("LAYISO: select entities on the layers to isolate first.")
|
||||
.as_ref(),
|
||||
);
|
||||
} else {
|
||||
let names: Vec<String> = self.tabs[i]
|
||||
.scene
|
||||
@@ -534,21 +558,22 @@ impl OpenCADStudio {
|
||||
self.tabs[i].dirty = true;
|
||||
self.commit_layer_undo(i, undo);
|
||||
self.refresh_layer_panel();
|
||||
self.command_line
|
||||
.push_info(crate::tf!("LAYISO: isolated {} layer(s).", sel_layers.len()).as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::tf!("LAYISO: isolated {} layer(s).", sel_layers.len()).as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ISOLATEOBJECTS — hide every object except the current selection
|
||||
"ISOLATEOBJECTS" => {
|
||||
if self.tabs[i].scene.selected.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("ISOLATEOBJECTS: select the objects to isolate first.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("ISOLATEOBJECTS: select the objects to isolate first.").as_ref(),
|
||||
);
|
||||
} else {
|
||||
let n = self.tabs[i].scene.selected.len();
|
||||
let before = self.tabs[i].scene.object_isolation.clone();
|
||||
let selected_before =
|
||||
self.tabs[i].scene.selected.iter().copied().collect();
|
||||
let selected_before = self.tabs[i].scene.selected.iter().copied().collect();
|
||||
self.tabs[i].scene.isolate_selected();
|
||||
self.push_object_visibility_history(
|
||||
i,
|
||||
@@ -556,32 +581,28 @@ impl OpenCADStudio {
|
||||
before,
|
||||
selected_before,
|
||||
);
|
||||
self.command_line.push_info(crate::tf!(
|
||||
"Isolated {n} object(s). UNISOLATEOBJECTS to restore."
|
||||
).as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::tf!("Isolated {n} object(s). UNISOLATEOBJECTS to restore.").as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// HIDEOBJECTS — hide the current selection
|
||||
"HIDEOBJECTS" => {
|
||||
if self.tabs[i].scene.selected.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("HIDEOBJECTS: select the objects to hide first.").as_ref());
|
||||
self.command_line.push_error(
|
||||
crate::t!("HIDEOBJECTS: select the objects to hide first.").as_ref(),
|
||||
);
|
||||
} else {
|
||||
let n = self.tabs[i].scene.selected.len();
|
||||
let before = self.tabs[i].scene.object_isolation.clone();
|
||||
let selected_before =
|
||||
self.tabs[i].scene.selected.iter().copied().collect();
|
||||
let selected_before = self.tabs[i].scene.selected.iter().copied().collect();
|
||||
self.tabs[i].scene.hide_selected();
|
||||
self.push_object_visibility_history(
|
||||
i,
|
||||
"HIDEOBJECTS",
|
||||
before,
|
||||
selected_before,
|
||||
);
|
||||
self.push_object_visibility_history(i, "HIDEOBJECTS", before, selected_before);
|
||||
self.refresh_properties();
|
||||
self.command_line
|
||||
.push_info(crate::tf!("Hid {n} object(s). UNISOLATEOBJECTS to restore.").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::tf!("Hid {n} object(s). UNISOLATEOBJECTS to restore.").as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,8 +610,7 @@ impl OpenCADStudio {
|
||||
"UNISOLATEOBJECTS" => {
|
||||
if self.tabs[i].scene.is_isolation_active() {
|
||||
let before = self.tabs[i].scene.object_isolation.clone();
|
||||
let selected_before =
|
||||
self.tabs[i].scene.selected.iter().copied().collect();
|
||||
let selected_before = self.tabs[i].scene.selected.iter().copied().collect();
|
||||
self.tabs[i].scene.end_isolation();
|
||||
self.push_object_visibility_history(
|
||||
i,
|
||||
@@ -601,7 +621,8 @@ impl OpenCADStudio {
|
||||
self.command_line
|
||||
.push_info(crate::t!("Isolation ended — all objects shown.").as_ref());
|
||||
} else {
|
||||
self.command_line.push_info(crate::t!("No hidden objects.").as_ref());
|
||||
self.command_line
|
||||
.push_info(crate::t!("No hidden objects.").as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,8 +651,7 @@ impl OpenCADStudio {
|
||||
|
||||
"QLAYER" => {
|
||||
use crate::modules::draw::layers::quick_layer::{
|
||||
QuickLayerCommand,
|
||||
QuickLayerEntry,
|
||||
QuickLayerCommand, QuickLayerEntry,
|
||||
};
|
||||
|
||||
let layers: Vec<QuickLayerEntry> = self.tabs[i]
|
||||
@@ -668,9 +688,8 @@ impl OpenCADStudio {
|
||||
let encoded = cmd.trim_start_matches("QLAYER_APPLY_HEX").trim();
|
||||
|
||||
let Some(requested) = QuickLayerCommand::decode_layer_name(encoded) else {
|
||||
self.command_line.push_error(
|
||||
crate::t!("QLAYER: invalid encoded layer name.").as_ref(),
|
||||
);
|
||||
self.command_line
|
||||
.push_error(crate::t!("QLAYER: invalid encoded layer name.").as_ref());
|
||||
return Some(Task::none());
|
||||
};
|
||||
|
||||
@@ -683,9 +702,8 @@ impl OpenCADStudio {
|
||||
.map(|name| name.to_string());
|
||||
|
||||
let Some(layer) = resolved else {
|
||||
self.command_line.push_error(
|
||||
crate::tf!("QLAYER: no layer named \"{requested}\".").as_ref(),
|
||||
);
|
||||
self.command_line
|
||||
.push_error(crate::tf!("QLAYER: no layer named \"{requested}\".").as_ref());
|
||||
return Some(Task::none());
|
||||
};
|
||||
|
||||
@@ -700,10 +718,7 @@ impl OpenCADStudio {
|
||||
match self.set_current_layer_name(i, &layer) {
|
||||
Ok(()) => {
|
||||
self.command_line.push_info(
|
||||
crate::tf!(
|
||||
"QLAYER: current layer set to \"{layer}\"."
|
||||
)
|
||||
.as_ref(),
|
||||
crate::tf!("QLAYER: current layer set to \"{layer}\".").as_ref(),
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
@@ -713,30 +728,19 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
let count = handles.len();
|
||||
|
||||
self.apply_property_op(
|
||||
i,
|
||||
"QLAYER",
|
||||
&handles,
|
||||
|app, handle| {
|
||||
if let Some(entity) =
|
||||
app.tabs[i].scene.document.get_entity_mut(handle)
|
||||
{
|
||||
crate::scene::view::dispatch::apply_common_prop(
|
||||
entity,
|
||||
"layer",
|
||||
&layer,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
self.apply_property_op(i, "QLAYER", &handles, |app, handle| {
|
||||
if let Some(entity) = app.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
crate::scene::view::dispatch::apply_common_prop(
|
||||
entity, "layer", &layer,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
self.ribbon.active_layer = layer.clone();
|
||||
|
||||
self.command_line.push_info(
|
||||
crate::tf!(
|
||||
"QLAYER: moved {count} object(s) to layer \"{layer}\"."
|
||||
)
|
||||
.as_ref(),
|
||||
crate::tf!("QLAYER: moved {count} object(s) to layer \"{layer}\".")
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -840,8 +844,10 @@ impl OpenCADStudio {
|
||||
let mappings = laytrans::map_same(&sources, &targets);
|
||||
if mappings.is_empty() {
|
||||
self.command_line.push_info(
|
||||
crate::t!("LAYTRANS: no layer names in common — use LAYTRANS with no file to map them.")
|
||||
.as_ref(),
|
||||
crate::t!(
|
||||
"LAYTRANS: no layer names in common — use LAYTRANS with no file to map them."
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
@@ -968,11 +974,7 @@ impl OpenCADStudio {
|
||||
.abs()
|
||||
> 1.0e-12
|
||||
{
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.header
|
||||
.annotation_scale_value /= factor;
|
||||
self.tabs[i].scene.document.header.annotation_scale_value /= factor;
|
||||
}
|
||||
// Sizes the drawing keeps as settings rather than as geometry: dash
|
||||
// lengths, default heights and widths, the radii the fillet and chamfer
|
||||
@@ -1027,8 +1029,7 @@ impl OpenCADStudio {
|
||||
&mut self,
|
||||
preload: Option<std::path::PathBuf>,
|
||||
) -> Task<Message> {
|
||||
self.layer_translator
|
||||
.get_or_insert_with(Default::default);
|
||||
self.layer_translator.get_or_insert_with(Default::default);
|
||||
self.active_modal = Some(crate::app::ModalKind::LayerTranslator);
|
||||
match preload {
|
||||
Some(path) => Task::done(Message::LayerTranslatorLoaded(path)),
|
||||
@@ -1042,7 +1043,11 @@ impl OpenCADStudio {
|
||||
/// there would mean inventing a place inside the DWG for something no other
|
||||
/// application reads back. A small text file beside the drawing carries the
|
||||
/// same information without touching the drawing's own structure.
|
||||
pub(in crate::app) fn layer_translator_mappings_file(&mut self, path: &std::path::Path, save: bool) {
|
||||
pub(in crate::app) fn layer_translator_mappings_file(
|
||||
&mut self,
|
||||
path: &std::path::Path,
|
||||
save: bool,
|
||||
) {
|
||||
use crate::modules::draw::layers::laytrans::Mapping;
|
||||
if save {
|
||||
let Some(state) = self.layer_translator.as_ref() else {
|
||||
|
||||
+29
-26
@@ -5,29 +5,29 @@ use iced::Task;
|
||||
use std::path::PathBuf;
|
||||
|
||||
mod attdef;
|
||||
mod field;
|
||||
mod count;
|
||||
mod blocks;
|
||||
mod xref_attach;
|
||||
mod blockvars;
|
||||
mod count;
|
||||
mod dim;
|
||||
pub(crate) mod display;
|
||||
mod draw;
|
||||
mod field;
|
||||
mod fileops;
|
||||
mod inquiry;
|
||||
mod layerprops;
|
||||
mod layers;
|
||||
pub(crate) mod pdf_import;
|
||||
mod point_cloud;
|
||||
mod pc_colormap;
|
||||
mod pc_extract;
|
||||
mod pdf_underlay;
|
||||
mod pdf_dialogs;
|
||||
pub(crate) mod sheet_set;
|
||||
mod xclip;
|
||||
mod blockvars;
|
||||
pub(crate) mod pdf_import;
|
||||
mod pdf_underlay;
|
||||
mod plotvars;
|
||||
mod point_cloud;
|
||||
pub(crate) mod sheet_set;
|
||||
mod styleprops;
|
||||
mod view;
|
||||
mod xclip;
|
||||
mod xref_attach;
|
||||
|
||||
// `DrawOrderRefCommand` lives in the `view` family file but is referenced by
|
||||
// path (`commands::DrawOrderRefCommand`) from `update.rs`, so re-export it at
|
||||
@@ -80,7 +80,8 @@ impl OpenCADStudio {
|
||||
.scene
|
||||
.selection
|
||||
.borrow_mut()
|
||||
.input.right_click_entered = false;
|
||||
.input
|
||||
.right_click_entered = false;
|
||||
// A fresh command starts at the polar/cartesian default — clear
|
||||
// any `,`-driven reshape and locked dynamic-input values from a
|
||||
// previous command. Otherwise a bare Enter on the first point prompt
|
||||
@@ -212,8 +213,9 @@ impl OpenCADStudio {
|
||||
// the single place that decides; `on_ribbon_tool_click` defers to it
|
||||
// rather than keeping a second, blunter copy (#388, #389).
|
||||
if self.tabs[i].is_start && !start_allowed(strip_command_prefixes(cmd).unwrap_or(cmd)) {
|
||||
self.command_line
|
||||
.push_info(crate::t!("No drawing open. Use NEW or OPEN to start a drawing.").as_ref());
|
||||
self.command_line.push_info(
|
||||
crate::t!("No drawing open. Use NEW or OPEN to start a drawing.").as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
@@ -377,7 +379,13 @@ impl OpenCADStudio {
|
||||
/// Commands that may run transparently (`'PAN`, `'ZOOM …`): the navigation
|
||||
/// set, which never edits the drawing.
|
||||
fn is_transparent_capable(cmd: &str) -> bool {
|
||||
cmd == "PAN" || cmd == "ZOOM" || cmd.starts_with("ZOOM ") || matches!(cmd, "ZW" | "ZE" | "ZA" | "ZP" | "ZI" | "ZO" | "ZD" | "ZEA" | "ZOBJ")
|
||||
cmd == "PAN"
|
||||
|| cmd == "ZOOM"
|
||||
|| cmd.starts_with("ZOOM ")
|
||||
|| matches!(
|
||||
cmd,
|
||||
"ZW" | "ZE" | "ZA" | "ZP" | "ZI" | "ZO" | "ZD" | "ZEA" | "ZOBJ"
|
||||
)
|
||||
}
|
||||
|
||||
/// ZOOM forms that install an interactive prompt (window corners, object
|
||||
@@ -414,9 +422,10 @@ fn is_spacemouse_command(cmd: &str) -> bool {
|
||||
/// source of truth: the dispatch gate refuses everything else, and the ribbon
|
||||
/// dims the tools this rejects.
|
||||
pub fn start_allowed(cmd: &str) -> bool {
|
||||
is_spacemouse_command(cmd) || matches!(
|
||||
cmd,
|
||||
"NEW"
|
||||
is_spacemouse_command(cmd)
|
||||
|| matches!(
|
||||
cmd,
|
||||
"NEW"
|
||||
| "OPEN"
|
||||
| "EXIT"
|
||||
| "QUIT"
|
||||
@@ -437,7 +446,7 @@ pub fn start_allowed(cmd: &str) -> bool {
|
||||
// command that opens the same dialog belongs here too.
|
||||
| "OPTIONS"
|
||||
| "OP"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ── Autocomplete registry — one-shot commands ──────────────────────────────
|
||||
@@ -971,10 +980,7 @@ mod marquee_cancel_tests {
|
||||
sel.input.left_press_time = Some(Instant::now());
|
||||
sel.input.left_dragging = true;
|
||||
sel.gesture.poly_active = true;
|
||||
sel.gesture.poly_points = vec![
|
||||
iced::Point::new(10.0, 10.0),
|
||||
iced::Point::new(20.0, 30.0),
|
||||
];
|
||||
sel.gesture.poly_points = vec![iced::Point::new(10.0, 10.0), iced::Point::new(20.0, 30.0)];
|
||||
sel.gesture.poly_crossing = true;
|
||||
}
|
||||
|
||||
@@ -1058,11 +1064,8 @@ mod marquee_cancel_tests {
|
||||
let mut app = fresh();
|
||||
let i = app.active_tab;
|
||||
let handle = Handle::new(42);
|
||||
app.tabs[i].active_grip = Some(GripEdit::radius(
|
||||
handle,
|
||||
1,
|
||||
glam::DVec3::new(2.0, 0.0, 0.0),
|
||||
));
|
||||
app.tabs[i].active_grip =
|
||||
Some(GripEdit::radius(handle, 1, glam::DVec3::new(2.0, 0.0, 0.0)));
|
||||
app.grip_pending = Some(GripPendingValue {
|
||||
handle,
|
||||
grip_id: 1,
|
||||
|
||||
+115
-34
@@ -5,10 +5,15 @@
|
||||
|
||||
use super::*;
|
||||
use codec::entities::{ExtendedEntityData, PointCloudExData};
|
||||
use codec::objects::{ClassObject, ClassObjectData, Dictionary, ObjectType, PointCloudColorMap, PointCloudColorRamp, PointCloudRampColor};
|
||||
use codec::objects::{
|
||||
ClassObject, ClassObjectData, Dictionary, ObjectType, PointCloudColorMap, PointCloudColorRamp,
|
||||
PointCloudRampColor,
|
||||
};
|
||||
use codec::types::Handle;
|
||||
|
||||
use crate::ui::window::pdf_dialogs::{short, MapRamp, MapSlot, OutOfRange, PdfDialogMsg, PointCloudColorMapState};
|
||||
use crate::ui::window::pdf_dialogs::{
|
||||
short, MapRamp, MapSlot, OutOfRange, PdfDialogMsg, PointCloudColorMapState,
|
||||
};
|
||||
|
||||
const MAP_DICTIONARY: &str = "ACAD_POINTCLOUD_COLORMAP_DICT";
|
||||
const MAP_ENTRY: &str = "ACAD_POINTCLOUD_COLORMAP";
|
||||
@@ -28,10 +33,17 @@ fn cloud_data(document: &codec::CadDocument, handle: Handle) -> Option<&PointClo
|
||||
/// The drawing's colour map object, created (with the built-in schemes)
|
||||
/// under its dictionary when the drawing has none.
|
||||
fn ensure_color_map(document: &mut codec::CadDocument) -> Option<Handle> {
|
||||
let existing = document.objects.iter().find_map(|(handle, object)| match object {
|
||||
ObjectType::ClassObject(object) if matches!(object.data, ClassObjectData::PointCloudColorMap(_)) => Some(*handle),
|
||||
_ => None,
|
||||
});
|
||||
let existing = document
|
||||
.objects
|
||||
.iter()
|
||||
.find_map(|(handle, object)| match object {
|
||||
ObjectType::ClassObject(object)
|
||||
if matches!(object.data, ClassObjectData::PointCloudColorMap(_)) =>
|
||||
{
|
||||
Some(*handle)
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
if existing.is_some() {
|
||||
return existing;
|
||||
}
|
||||
@@ -40,13 +52,22 @@ fn ensure_color_map(document: &mut codec::CadDocument) -> Option<Handle> {
|
||||
return None;
|
||||
};
|
||||
let dictionary = match root_dictionary.get(MAP_DICTIONARY) {
|
||||
Some(handle) if matches!(document.objects.get(&handle), Some(ObjectType::Dictionary(_))) => handle,
|
||||
Some(handle)
|
||||
if matches!(
|
||||
document.objects.get(&handle),
|
||||
Some(ObjectType::Dictionary(_))
|
||||
) =>
|
||||
{
|
||||
handle
|
||||
}
|
||||
_ => {
|
||||
let handle = document.allocate_handle();
|
||||
let mut dictionary = Dictionary::new();
|
||||
dictionary.handle = handle;
|
||||
dictionary.owner = root;
|
||||
document.objects.insert(handle, ObjectType::Dictionary(dictionary));
|
||||
document
|
||||
.objects
|
||||
.insert(handle, ObjectType::Dictionary(dictionary));
|
||||
if let Some(ObjectType::Dictionary(root_dictionary)) = document.objects.get_mut(&root) {
|
||||
root_dictionary.add_entry(MAP_DICTIONARY, handle);
|
||||
}
|
||||
@@ -60,15 +81,19 @@ fn ensure_color_map(document: &mut codec::CadDocument) -> Option<Handle> {
|
||||
.collect();
|
||||
let mut map = ClassObject::new(ClassObjectData::PointCloudColorMap(PointCloudColorMap {
|
||||
class_version: 4,
|
||||
default_intensity_scheme: crate::entities::extended::point_cloud_ramp_id("Spectrum").to_string(),
|
||||
default_elevation_scheme: crate::entities::extended::point_cloud_ramp_id("Earth").to_string(),
|
||||
default_intensity_scheme: crate::entities::extended::point_cloud_ramp_id("Spectrum")
|
||||
.to_string(),
|
||||
default_elevation_scheme: crate::entities::extended::point_cloud_ramp_id("Earth")
|
||||
.to_string(),
|
||||
default_classification_scheme: CLASSIFICATION_SCHEME.to_string(),
|
||||
color_ramps: ramps,
|
||||
classification_color_ramps: Vec::new(),
|
||||
}));
|
||||
map.handle = handle;
|
||||
map.owner = dictionary;
|
||||
document.objects.insert(handle, ObjectType::ClassObject(map));
|
||||
document
|
||||
.objects
|
||||
.insert(handle, ObjectType::ClassObject(map));
|
||||
if let Some(ObjectType::Dictionary(dictionary)) = document.objects.get_mut(&dictionary) {
|
||||
dictionary.add_entry(MAP_ENTRY, handle);
|
||||
}
|
||||
@@ -84,7 +109,8 @@ fn to_codec_ramp(ramp: &MapRamp) -> PointCloudColorRamp {
|
||||
.colors
|
||||
.iter()
|
||||
.map(|[r, g, b]| PointCloudRampColor {
|
||||
color: (0xC2u32 << 24 | u32::from(*r) << 16 | u32::from(*g) << 8 | u32::from(*b)) as i32,
|
||||
color: (0xC2u32 << 24 | u32::from(*r) << 16 | u32::from(*g) << 8 | u32::from(*b))
|
||||
as i32,
|
||||
visible: true,
|
||||
})
|
||||
.collect(),
|
||||
@@ -97,7 +123,14 @@ fn new_scheme_id() -> String {
|
||||
let mut bytes = [0u8; 16];
|
||||
getrandom::fill(&mut bytes).expect("OS random source");
|
||||
let hex: String = bytes.iter().map(|b| format!("{b:02X}")).collect();
|
||||
format!("{}-{}-{}-{}-{}", &hex[0..8], &hex[8..12], &hex[12..16], &hex[16..20], &hex[20..32])
|
||||
format!(
|
||||
"{}-{}-{}-{}-{}",
|
||||
&hex[0..8],
|
||||
&hex[8..12],
|
||||
&hex[12..16],
|
||||
&hex[16..20],
|
||||
&hex[20..32]
|
||||
)
|
||||
}
|
||||
|
||||
/// `count` colours taken evenly along a scheme's blend.
|
||||
@@ -127,13 +160,20 @@ impl OpenCADStudio {
|
||||
(low, high)
|
||||
})
|
||||
.unwrap_or((0.0, 0.0));
|
||||
let slot = |scheme: &str, fallback: &str, gradient: bool, max: String, min: String, out: i16| MapSlot {
|
||||
scheme: if scheme.is_empty() { fallback.to_string() } else { scheme.to_string() },
|
||||
gradient,
|
||||
max,
|
||||
min,
|
||||
out_of_range: OutOfRange(out),
|
||||
};
|
||||
let slot =
|
||||
|scheme: &str, fallback: &str, gradient: bool, max: String, min: String, out: i16| {
|
||||
MapSlot {
|
||||
scheme: if scheme.is_empty() {
|
||||
fallback.to_string()
|
||||
} else {
|
||||
scheme.to_string()
|
||||
},
|
||||
gradient,
|
||||
max,
|
||||
min,
|
||||
out_of_range: OutOfRange(out),
|
||||
}
|
||||
};
|
||||
let (intensity, elevation, extents, elevation_tab) = match data {
|
||||
Some(data) => (
|
||||
slot(
|
||||
@@ -148,8 +188,16 @@ impl OpenCADStudio {
|
||||
&data.current_color_scheme,
|
||||
&defaults.1,
|
||||
data.elevation_as_gradient,
|
||||
short(if data.elevation_apply_to_fixed_range { data.elevation_max } else { height.1 }),
|
||||
short(if data.elevation_apply_to_fixed_range { data.elevation_min } else { height.0 }),
|
||||
short(if data.elevation_apply_to_fixed_range {
|
||||
data.elevation_max
|
||||
} else {
|
||||
height.1
|
||||
}),
|
||||
short(if data.elevation_apply_to_fixed_range {
|
||||
data.elevation_min
|
||||
} else {
|
||||
height.0
|
||||
}),
|
||||
data.elevation_out_of_range_behavior,
|
||||
),
|
||||
!data.elevation_apply_to_fixed_range,
|
||||
@@ -179,7 +227,10 @@ impl OpenCADStudio {
|
||||
self.active_modal = Some(crate::app::ModalKind::PointCloudColorMap);
|
||||
}
|
||||
|
||||
pub(in crate::app) fn update_point_cloud_color_map(&mut self, message: PdfDialogMsg) -> Task<Message> {
|
||||
pub(in crate::app) fn update_point_cloud_color_map(
|
||||
&mut self,
|
||||
message: PdfDialogMsg,
|
||||
) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let Some(state) = self.point_cloud_color_map.as_mut() else {
|
||||
return Task::none();
|
||||
@@ -219,7 +270,12 @@ impl OpenCADStudio {
|
||||
// "Color Scheme 0", or the first number not taken.
|
||||
let name = (0..)
|
||||
.map(|n| format!("Color Scheme {n}"))
|
||||
.find(|name| !state.ramps.iter().any(|r| r.name.eq_ignore_ascii_case(name)))
|
||||
.find(|name| {
|
||||
!state
|
||||
.ramps
|
||||
.iter()
|
||||
.any(|r| r.name.eq_ignore_ascii_case(name))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
state.naming = Some((true, name));
|
||||
}
|
||||
@@ -238,17 +294,31 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
};
|
||||
let name = name.trim().to_string();
|
||||
let taken = state.ramps.iter().any(|r| r.name.eq_ignore_ascii_case(&name))
|
||||
let taken = state
|
||||
.ramps
|
||||
.iter()
|
||||
.any(|r| r.name.eq_ignore_ascii_case(&name))
|
||||
&& !(!new && state.ramp().is_some_and(|r| r.name == name));
|
||||
if name.is_empty() || taken {
|
||||
self.command_line.push_error(crate::t!("A color scheme with this name already exists.").as_ref());
|
||||
self.point_cloud_color_map.as_mut().map(|s| s.naming = Some((new, name)));
|
||||
self.command_line.push_error(
|
||||
crate::t!("A color scheme with this name already exists.").as_ref(),
|
||||
);
|
||||
self.point_cloud_color_map
|
||||
.as_mut()
|
||||
.map(|s| s.naming = Some((new, name)));
|
||||
return Task::none();
|
||||
}
|
||||
if new {
|
||||
let colors = state.ramp().map(|r| r.colors.clone()).unwrap_or_else(|| vec![[255; 3], [0; 3]]);
|
||||
let colors = state
|
||||
.ramp()
|
||||
.map(|r| r.colors.clone())
|
||||
.unwrap_or_else(|| vec![[255; 3], [0; 3]]);
|
||||
let id = new_scheme_id();
|
||||
state.ramps.push(MapRamp { id: id.clone(), name, colors });
|
||||
state.ramps.push(MapRamp {
|
||||
id: id.clone(),
|
||||
name,
|
||||
colors,
|
||||
});
|
||||
state.slot_mut().scheme = id;
|
||||
} else if let Some(ramp) = state.ramp_mut() {
|
||||
ramp.name = name;
|
||||
@@ -279,7 +349,10 @@ impl OpenCADStudio {
|
||||
// A band height sets the maximum from the minimum.
|
||||
PdfDialogMsg::MapInterval(value) => {
|
||||
let count = state.ramp().map_or(1, |r| r.colors.len()) as f64;
|
||||
if let (Ok(step), Ok(min)) = (value.trim().parse::<f64>(), state.elevation.min.trim().parse::<f64>()) {
|
||||
if let (Ok(step), Ok(min)) = (
|
||||
value.trim().parse::<f64>(),
|
||||
state.elevation.min.trim().parse::<f64>(),
|
||||
) {
|
||||
state.elevation.max = short(min + step * count);
|
||||
}
|
||||
state.interval = value;
|
||||
@@ -314,20 +387,28 @@ impl OpenCADStudio {
|
||||
let Some(state) = self.point_cloud_color_map.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
let intensity = (state.intensity.min.trim().parse::<i32>(), state.intensity.max.trim().parse::<i32>());
|
||||
let elevation = (state.elevation.min.trim().parse::<f64>(), state.elevation.max.trim().parse::<f64>());
|
||||
let intensity = (
|
||||
state.intensity.min.trim().parse::<i32>(),
|
||||
state.intensity.max.trim().parse::<i32>(),
|
||||
);
|
||||
let elevation = (
|
||||
state.elevation.min.trim().parse::<f64>(),
|
||||
state.elevation.max.trim().parse::<f64>(),
|
||||
);
|
||||
let cloud = state.cloud;
|
||||
if cloud.is_some() {
|
||||
let ok_intensity = matches!(intensity, (Ok(lo), Ok(hi)) if (0..=100).contains(&lo) && (0..=100).contains(&hi) && lo < hi);
|
||||
let ok_elevation = state.extents || matches!(elevation, (Ok(lo), Ok(hi)) if lo < hi);
|
||||
if !ok_intensity || !ok_elevation {
|
||||
self.command_line.push_error(crate::t!("Invalid range of colorized points.").as_ref());
|
||||
self.command_line
|
||||
.push_error(crate::t!("Invalid range of colorized points.").as_ref());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let ramps: Vec<PointCloudColorRamp> = state.ramps.iter().map(to_codec_ramp).collect();
|
||||
let (intensity_slot, elevation_slot) = (state.intensity.clone(), state.elevation.clone());
|
||||
let (extents, make_current, elevation_tab) = (state.extents, state.make_current, state.elevation_tab);
|
||||
let (extents, make_current, elevation_tab) =
|
||||
(state.extents, state.make_current, state.elevation_tab);
|
||||
self.push_undo_snapshot(i, "POINTCLOUDCOLORMAP");
|
||||
let document = &mut self.tabs[i].scene.document;
|
||||
if let Some(handle) = ensure_color_map(document) {
|
||||
|
||||
@@ -30,7 +30,11 @@ impl OpenCADStudio {
|
||||
let common = entity.common();
|
||||
// The colour the renderer places with, so its cached points are reused.
|
||||
let color = if matches!(common.color, codec::types::Color::ByLayer) {
|
||||
document.layers.get(&common.layer).map(|layer| layer.color.clone()).unwrap_or(common.color.clone())
|
||||
document
|
||||
.layers
|
||||
.get(&common.layer)
|
||||
.map(|layer| layer.color.clone())
|
||||
.unwrap_or(common.color.clone())
|
||||
} else {
|
||||
common.color.clone()
|
||||
};
|
||||
@@ -65,7 +69,12 @@ impl OpenCADStudio {
|
||||
let vertical = dvec(data.vertical_direction).normalize_or(DVec3::Z);
|
||||
let viewing = vertical.cross(tangent).normalize_or(-DVec3::Z)
|
||||
* if data.flags & 4 != 0 { 1.0 } else { -1.0 };
|
||||
Some(Section { origin: first, viewing, tangent, live: data.flags & 1 != 0 })
|
||||
Some(Section {
|
||||
origin: first,
|
||||
viewing,
|
||||
tangent,
|
||||
live: data.flags & 1 != 0,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -104,7 +113,13 @@ impl OpenCADStudio {
|
||||
let s = settings();
|
||||
let current = crate::t!("Use Current").into_owned();
|
||||
let mut layers = vec![current.clone()];
|
||||
let mut names: Vec<String> = self.tabs[i].scene.document.layers.iter().map(|l| l.name.clone()).collect();
|
||||
let mut names: Vec<String> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.layers
|
||||
.iter()
|
||||
.map(|l| l.name.clone())
|
||||
.collect();
|
||||
names.sort_by_key(|n| n.to_lowercase());
|
||||
layers.extend(names);
|
||||
let short = |v: f64| {
|
||||
@@ -128,8 +143,10 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
pub(in crate::app) fn update_pc_section(&mut self, message: PdfDialogMsg) -> Task<Message> {
|
||||
use crate::modules::insert::pc_extract::{set_settings, settings, SectionCommand, SectionDistanceCommand};
|
||||
use crate::command::CadCommand;
|
||||
use crate::modules::insert::pc_extract::{
|
||||
set_settings, settings, SectionCommand, SectionDistanceCommand,
|
||||
};
|
||||
let i = self.active_tab;
|
||||
let Some(state) = self.pc_section.as_mut() else {
|
||||
return Task::none();
|
||||
@@ -155,18 +172,39 @@ impl OpenCADStudio {
|
||||
}
|
||||
PdfDialogMsg::SecCreate => {
|
||||
let number = |t: &str| crate::entities::common::parse_f64(t.trim());
|
||||
let (min_length, connect, width) = (number(&state.min_length), number(&state.connect), number(&state.width));
|
||||
let (Some(min_length), Some(connect)) = (min_length.filter(|v| *v > 0.0), connect.filter(|v| *v > 0.0)) else {
|
||||
self.command_line.push_error(crate::t!("Requires a positive number.").as_ref());
|
||||
let (min_length, connect, width) = (
|
||||
number(&state.min_length),
|
||||
number(&state.connect),
|
||||
number(&state.width),
|
||||
);
|
||||
let (Some(min_length), Some(connect)) = (
|
||||
min_length.filter(|v| *v > 0.0),
|
||||
connect.filter(|v| *v > 0.0),
|
||||
) else {
|
||||
self.command_line
|
||||
.push_error(crate::t!("Requires a positive number.").as_ref());
|
||||
return Task::none();
|
||||
};
|
||||
let Some(angle) = state.angle.trim().parse::<i64>().ok().filter(|v| (0..=10).contains(v)) else {
|
||||
let Some(angle) = state
|
||||
.angle
|
||||
.trim()
|
||||
.parse::<i64>()
|
||||
.ok()
|
||||
.filter(|v| (0..=10).contains(v))
|
||||
else {
|
||||
self.command_line
|
||||
.push_error("Colinear angle tolerance only accepts integer from 0 to 10.");
|
||||
return Task::none();
|
||||
};
|
||||
let Some(max_points) = state.max_points.trim().parse::<usize>().ok().filter(|v| *v > 0) else {
|
||||
self.command_line.push_error(crate::t!("Requires a positive integer.").as_ref());
|
||||
let Some(max_points) = state
|
||||
.max_points
|
||||
.trim()
|
||||
.parse::<usize>()
|
||||
.ok()
|
||||
.filter(|v| *v > 0)
|
||||
else {
|
||||
self.command_line
|
||||
.push_error(crate::t!("Requires a positive integer.").as_ref());
|
||||
return Task::none();
|
||||
};
|
||||
let current = crate::t!("Use Current").into_owned();
|
||||
|
||||
+121
-31
@@ -5,8 +5,8 @@ use super::*;
|
||||
use crate::io::xref_model::Pathtype;
|
||||
use crate::modules::insert::pdf_import::{self, PdfFileImport};
|
||||
use crate::ui::window::pdf_dialogs::{
|
||||
click_page, item_thumbs, page_thumbs, LayerTarget, PdfAttachState, PdfDialogMsg, PdfImportFileState,
|
||||
PointCloudAttachState, RotationChoice, UnderlayLayersState,
|
||||
click_page, item_thumbs, page_thumbs, LayerTarget, PdfAttachState, PdfDialogMsg,
|
||||
PdfImportFileState, PointCloudAttachState, RotationChoice, UnderlayLayersState,
|
||||
};
|
||||
|
||||
/// "7.8740 × 3.9369" (inches) for a page.
|
||||
@@ -22,7 +22,13 @@ fn item_size_text(kind: codec::entities::UnderlayType, path: &str, item: &str) -
|
||||
return page_size_text(path, item);
|
||||
}
|
||||
crate::scene::model::underlay_vector::sheet(kind, path, item)
|
||||
.map(|s| format!("{:.4} × {:.4}", s.rect[2] - s.rect[0], s.rect[3] - s.rect[1]))
|
||||
.map(|s| {
|
||||
format!(
|
||||
"{:.4} × {:.4}",
|
||||
s.rect[2] - s.rect[0],
|
||||
s.rect[3] - s.rect[1]
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -66,7 +72,11 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
/// PDFATTACH / DWFATTACH / DGNATTACH after the file is chosen.
|
||||
pub(in crate::app) fn open_underlay_attach_dialog(&mut self, kind: codec::entities::UnderlayType, path: &str) {
|
||||
pub(in crate::app) fn open_underlay_attach_dialog(
|
||||
&mut self,
|
||||
kind: codec::entities::UnderlayType,
|
||||
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() {
|
||||
@@ -94,7 +104,11 @@ impl OpenCADStudio {
|
||||
state.selected = vec![0];
|
||||
state.anchor = 0;
|
||||
state.found_in = crate::entities::underlay::display_path(path);
|
||||
let first = state.page_labels().into_iter().next().unwrap_or_else(|| "1".to_string());
|
||||
let first = state
|
||||
.page_labels()
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_else(|| "1".to_string());
|
||||
state.page_size = item_size_text(state.kind, path, &first);
|
||||
state.sub_per_master = sub_per_master(state.kind, path, &first);
|
||||
state.sub_units = false;
|
||||
@@ -108,7 +122,14 @@ impl OpenCADStudio {
|
||||
let Some(cloud) = crate::scene::model::point_cloud::load(path) else {
|
||||
return;
|
||||
};
|
||||
let yes_no = |on: bool| if on { crate::t!("Yes") } else { crate::t!("No") }.into_owned();
|
||||
let yes_no = |on: bool| {
|
||||
if on {
|
||||
crate::t!("Yes")
|
||||
} else {
|
||||
crate::t!("No")
|
||||
}
|
||||
.into_owned()
|
||||
};
|
||||
let data = format!(
|
||||
"RGB: {} · {}: {} · {}: {}",
|
||||
yes_no(cloud.has_rgb),
|
||||
@@ -124,14 +145,28 @@ impl OpenCADStudio {
|
||||
crate::t!("Segmentation"),
|
||||
yes_no(false),
|
||||
);
|
||||
let factor = crate::app::properties::insert_unit_scale(self.tabs[self.active_tab].scene.document.header.insertion_units, 6)
|
||||
.unwrap_or(1.0);
|
||||
let factor = crate::app::properties::insert_unit_scale(
|
||||
self.tabs[self.active_tab]
|
||||
.scene
|
||||
.document
|
||||
.header
|
||||
.insertion_units,
|
||||
6,
|
||||
)
|
||||
.unwrap_or(1.0);
|
||||
let full = path.to_string_lossy().into_owned();
|
||||
let memory = crate::ui::window::pdf_dialogs::ATTACH_MEMORY.lock().ok().and_then(|m| m.clone()).unwrap_or_default();
|
||||
let memory = crate::ui::window::pdf_dialogs::ATTACH_MEMORY
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|m| m.clone())
|
||||
.unwrap_or_default();
|
||||
let mut state = PointCloudAttachState {
|
||||
path: full.clone(),
|
||||
name: file_stem(&full),
|
||||
preview: cloud.preview.clone().map(iced::widget::image::Handle::from_bytes),
|
||||
preview: cloud
|
||||
.preview
|
||||
.clone()
|
||||
.map(iced::widget::image::Handle::from_bytes),
|
||||
summary: format!(
|
||||
"{} · {}",
|
||||
crate::tf!("{count} scans", count = cloud.scans),
|
||||
@@ -141,9 +176,21 @@ impl OpenCADStudio {
|
||||
data_more,
|
||||
size: cloud
|
||||
.bounds
|
||||
.map(|[lo, hi]| format!("{:.4} × {:.4} × {:.4}", hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]))
|
||||
.map(|[lo, hi]| {
|
||||
format!(
|
||||
"{:.4} × {:.4} × {:.4}",
|
||||
hi[0] - lo[0],
|
||||
hi[1] - lo[1],
|
||||
hi[2] - lo[2]
|
||||
)
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
unit: format!("{} · {} {}", crate::t!("Meters"), crate::t!("Unit factor"), crate::app::properties::format_unit_factor(factor)),
|
||||
unit: format!(
|
||||
"{} · {} {}",
|
||||
crate::t!("Meters"),
|
||||
crate::t!("Unit factor"),
|
||||
crate::app::properties::format_unit_factor(factor)
|
||||
),
|
||||
found_in: crate::entities::underlay::display_path(&full),
|
||||
saved_path: String::new(),
|
||||
path_type: memory.path_type,
|
||||
@@ -156,7 +203,9 @@ impl OpenCADStudio {
|
||||
lock: false,
|
||||
zoom: true,
|
||||
};
|
||||
state.saved_path = crate::entities::underlay::display_path(&self.pdf_stored_path(&full, state.path_type.0));
|
||||
state.saved_path = crate::entities::underlay::display_path(
|
||||
&self.pdf_stored_path(&full, state.path_type.0),
|
||||
);
|
||||
self.point_cloud_attach = Some(state);
|
||||
self.active_modal = Some(crate::app::ModalKind::PointCloudAttach);
|
||||
}
|
||||
@@ -172,7 +221,11 @@ impl OpenCADStudio {
|
||||
let insertion = if state.insert_on_screen {
|
||||
None
|
||||
} else {
|
||||
match (number(&state.insert[0]), number(&state.insert[1]), number(&state.insert[2])) {
|
||||
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.");
|
||||
@@ -186,7 +239,8 @@ impl OpenCADStudio {
|
||||
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.");
|
||||
self.command_line
|
||||
.push_error("Value must be positive and nonzero.");
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
@@ -258,10 +312,12 @@ impl OpenCADStudio {
|
||||
name = format!("{name} ({})", same + 1);
|
||||
}
|
||||
let mut layers: Vec<String> = match u.underlay_type {
|
||||
codec::entities::UnderlayType::Pdf => crate::scene::model::pdf_layers::layers(&def.file_path)
|
||||
.into_iter()
|
||||
.map(|l| l.name)
|
||||
.collect(),
|
||||
codec::entities::UnderlayType::Pdf => {
|
||||
crate::scene::model::pdf_layers::layers(&def.file_path)
|
||||
.into_iter()
|
||||
.map(|l| l.name)
|
||||
.collect()
|
||||
}
|
||||
kind => crate::scene::model::underlay_vector::layer_names(kind, &def.file_path),
|
||||
};
|
||||
layers.sort_by_key(|n| n.to_lowercase());
|
||||
@@ -313,7 +369,9 @@ impl OpenCADStudio {
|
||||
|
||||
/// 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, DGN_OVERRIDE_PLACEHOLDER, LAYER_OVERRIDE_APP};
|
||||
use crate::scene::model::pdf_layers::{
|
||||
hidden_layers, DGN_OVERRIDE_PLACEHOLDER, LAYER_OVERRIDE_APP,
|
||||
};
|
||||
let changed: Vec<LayerTarget> = targets
|
||||
.into_iter()
|
||||
.filter(|t| match self.tabs[i].scene.document.get_entity(t.handle) {
|
||||
@@ -345,7 +403,9 @@ impl OpenCADStudio {
|
||||
let mut record = codec::xdata::ExtendedDataRecord::new(LAYER_OVERRIDE_APP);
|
||||
// A DGN underlay's names follow a placeholder string.
|
||||
if u.underlay_type == codec::entities::UnderlayType::Dgn {
|
||||
record.add_value(codec::xdata::XDataValue::String(DGN_OVERRIDE_PLACEHOLDER.to_string()));
|
||||
record.add_value(codec::xdata::XDataValue::String(
|
||||
DGN_OVERRIDE_PLACEHOLDER.to_string(),
|
||||
));
|
||||
}
|
||||
for name in &target.hidden {
|
||||
record.add_value(codec::xdata::XDataValue::String(name.clone()));
|
||||
@@ -394,7 +454,11 @@ impl OpenCADStudio {
|
||||
|
||||
// ── Attach ────────────────────────────────────────────────────
|
||||
PdfDialogMsg::AttachBrowse => {
|
||||
let kind = self.pdf_attach.as_ref().map(|s| s.kind).unwrap_or(codec::entities::UnderlayType::Pdf);
|
||||
let kind = self
|
||||
.pdf_attach
|
||||
.as_ref()
|
||||
.map(|s| s.kind)
|
||||
.unwrap_or(codec::entities::UnderlayType::Pdf);
|
||||
if let Some(state) = &self.pdf_attach {
|
||||
state.remember();
|
||||
}
|
||||
@@ -407,7 +471,11 @@ impl OpenCADStudio {
|
||||
if let Some(s) = self.pdf_attach.as_mut() {
|
||||
s.sub_units = sub;
|
||||
// The scale offered follows the units, as on the command line.
|
||||
s.scale = if sub { format!("{}", 1.0 / s.sub_per_master) } else { "1.0000".to_string() };
|
||||
s.scale = if sub {
|
||||
format!("{}", 1.0 / s.sub_per_master)
|
||||
} else {
|
||||
"1.0000".to_string()
|
||||
};
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::AttachName(name) => {
|
||||
@@ -517,7 +585,10 @@ impl OpenCADStudio {
|
||||
| PdfDialogMsg::SecPreview(_)
|
||||
| PdfDialogMsg::SecCreate) => return self.update_pc_section(message),
|
||||
PdfDialogMsg::CloudPathType(choice) => {
|
||||
let stored = self.point_cloud_attach.as_ref().map(|s| self.pdf_stored_path(&s.path, choice.0));
|
||||
let stored = self
|
||||
.point_cloud_attach
|
||||
.as_ref()
|
||||
.map(|s| self.pdf_stored_path(&s.path, choice.0));
|
||||
if let (Some(s), Some(stored)) = (self.point_cloud_attach.as_mut(), stored) {
|
||||
s.path_type = choice;
|
||||
s.saved_path = crate::entities::underlay::display_path(&stored);
|
||||
@@ -648,7 +719,12 @@ impl OpenCADStudio {
|
||||
}
|
||||
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)) {
|
||||
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());
|
||||
}
|
||||
@@ -674,8 +750,10 @@ impl OpenCADStudio {
|
||||
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.");
|
||||
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");
|
||||
@@ -695,7 +773,10 @@ impl OpenCADStudio {
|
||||
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));
|
||||
self.run_pdf_import(
|
||||
i,
|
||||
crate::app::commands::pdf_import::PdfImportSource::File(import),
|
||||
);
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
@@ -710,7 +791,11 @@ impl OpenCADStudio {
|
||||
let insertion = if state.insert_on_screen {
|
||||
None
|
||||
} else {
|
||||
match (number(&state.insert[0]), number(&state.insert[1]), number(&state.insert[2])) {
|
||||
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.");
|
||||
@@ -724,7 +809,8 @@ impl OpenCADStudio {
|
||||
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.");
|
||||
self.command_line
|
||||
.push_error("Value must be positive and nonzero.");
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
@@ -766,7 +852,11 @@ impl OpenCADStudio {
|
||||
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(),
|
||||
&self.tabs[i]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.map(|c| c.prompt())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
return self.focus_cmd_input();
|
||||
}
|
||||
|
||||
@@ -86,7 +86,12 @@ impl OpenCADStudio {
|
||||
|
||||
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) {
|
||||
if result.dashed
|
||||
&& !scene
|
||||
.document
|
||||
.line_types
|
||||
.contains(pdf_import::DASH_LINETYPE)
|
||||
{
|
||||
let mut lt = codec::tables::LineType::new(pdf_import::DASH_LINETYPE);
|
||||
lt.handle = scene.document.allocate_handle();
|
||||
lt.add_element(codec::tables::LineTypeElement::dash(0.8));
|
||||
@@ -106,8 +111,14 @@ impl OpenCADStudio {
|
||||
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()
|
||||
if image::save_buffer(
|
||||
&file,
|
||||
&image.rgba,
|
||||
image.width,
|
||||
image.height,
|
||||
image::ColorType::Rgba8,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -163,10 +174,8 @@ impl OpenCADStudio {
|
||||
.define_block_from_owned_entities(result.entities, &name, glam::DVec3::ZERO)
|
||||
.is_ok()
|
||||
{
|
||||
let mut insert = codec::entities::Insert::new(
|
||||
name,
|
||||
codec::types::Vector3::new(0.0, 0.0, 0.0),
|
||||
);
|
||||
let mut insert =
|
||||
codec::entities::Insert::new(name, codec::types::Vector3::new(0.0, 0.0, 0.0));
|
||||
insert.common.layer = current_layer;
|
||||
scene.add_entity(codec::EntityType::Insert(insert));
|
||||
}
|
||||
@@ -198,7 +207,9 @@ impl OpenCADStudio {
|
||||
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);
|
||||
dictionary
|
||||
.entries
|
||||
.retain(|(_, target)| *target != definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//! selection, their buttons edit it.
|
||||
|
||||
use super::*;
|
||||
use codec::entities::{Underlay, UnderlayDisplayFlags};
|
||||
use crate::ui::ribbon::UnderlayContext;
|
||||
use codec::entities::{Underlay, UnderlayDisplayFlags};
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// The selected underlays when they are all of one kind, or nothing
|
||||
@@ -13,7 +13,9 @@ impl OpenCADStudio {
|
||||
for (handle, entity) in self.tabs[i].scene.selected_entities() {
|
||||
match entity {
|
||||
codec::EntityType::Underlay(u)
|
||||
if out.first().is_none_or(|(_, first)| first.underlay_type == u.underlay_type) =>
|
||||
if out
|
||||
.first()
|
||||
.is_none_or(|(_, first)| first.underlay_type == u.underlay_type) =>
|
||||
{
|
||||
out.push((handle, u.clone()));
|
||||
}
|
||||
@@ -52,27 +54,37 @@ impl OpenCADStudio {
|
||||
let context = if self.tabs[i].is_start {
|
||||
None
|
||||
} else {
|
||||
self.selected_pdf_underlays(i).first().map(|(_, u)| {
|
||||
UnderlayContext {
|
||||
self.selected_pdf_underlays(i)
|
||||
.first()
|
||||
.map(|(_, u)| UnderlayContext {
|
||||
kind: u.underlay_type,
|
||||
monochrome: u.flags.contains(UnderlayDisplayFlags::MONOCHROME),
|
||||
shown: u.flags.contains(UnderlayDisplayFlags::ON),
|
||||
snap: crate::scene::model::pdf_vector::underlay_osnap(u.underlay_type),
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
self.ribbon.set_underlay_context(context, xref);
|
||||
let clouds = if self.tabs[i].is_start { Vec::new() } else { self.selected_point_clouds(i) };
|
||||
self.ribbon.set_point_cloud_context(clouds.first().map(|(_, data)| data.show_cropping));
|
||||
let clouds = if self.tabs[i].is_start {
|
||||
Vec::new()
|
||||
} else {
|
||||
self.selected_point_clouds(i)
|
||||
};
|
||||
self.ribbon
|
||||
.set_point_cloud_context(clouds.first().map(|(_, data)| data.show_cropping));
|
||||
}
|
||||
|
||||
/// The selected point clouds, or nothing when anything else is selected.
|
||||
fn selected_point_clouds(&self, i: usize) -> Vec<(codec::Handle, codec::entities::PointCloudExData)> {
|
||||
fn selected_point_clouds(
|
||||
&self,
|
||||
i: usize,
|
||||
) -> Vec<(codec::Handle, codec::entities::PointCloudExData)> {
|
||||
let mut out = Vec::new();
|
||||
for (handle, entity) in self.tabs[i].scene.selected_entities() {
|
||||
match entity {
|
||||
codec::EntityType::Extended(extended) => match &extended.data {
|
||||
codec::entities::ExtendedEntityData::PointCloudEx(data) => out.push((handle, data.clone())),
|
||||
codec::entities::ExtendedEntityData::PointCloudEx(data) => {
|
||||
out.push((handle, data.clone()))
|
||||
}
|
||||
_ => return Vec::new(),
|
||||
},
|
||||
_ => return Vec::new(),
|
||||
@@ -118,18 +130,26 @@ impl OpenCADStudio {
|
||||
if !done.is_empty() {
|
||||
self.push_undo_snapshot(i, "POINTCLOUDSTYLIZE");
|
||||
for handle in &done {
|
||||
if let Some(codec::EntityType::Extended(extended)) = self.tabs[i].scene.document.get_entity_mut(*handle) {
|
||||
if let codec::entities::ExtendedEntityData::PointCloudEx(data) = &mut extended.data {
|
||||
if let Some(codec::EntityType::Extended(extended)) =
|
||||
self.tabs[i].scene.document.get_entity_mut(*handle)
|
||||
{
|
||||
if let codec::entities::ExtendedEntityData::PointCloudEx(data) =
|
||||
&mut extended.data
|
||||
{
|
||||
crate::entities::extended::set_point_cloud_stylization(data, stylization);
|
||||
}
|
||||
}
|
||||
}
|
||||
let changes: Vec<_> = done.iter().map(|h| (*h, crate::scene::ChangeKind::Modified)).collect();
|
||||
let changes: Vec<_> = done
|
||||
.iter()
|
||||
.map(|h| (*h, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
self.command_line.push_output(&format!("{} point cloud(s) stylized", done.len()));
|
||||
self.command_line
|
||||
.push_output(&format!("{} point cloud(s) stylized", done.len()));
|
||||
}
|
||||
|
||||
/// One undo step that edits every selected point cloud not locked.
|
||||
@@ -150,13 +170,19 @@ impl OpenCADStudio {
|
||||
}
|
||||
self.push_undo_snapshot(i, label);
|
||||
for handle in &handles {
|
||||
if let Some(codec::EntityType::Extended(extended)) = self.tabs[i].scene.document.get_entity_mut(*handle) {
|
||||
if let codec::entities::ExtendedEntityData::PointCloudEx(data) = &mut extended.data {
|
||||
if let Some(codec::EntityType::Extended(extended)) =
|
||||
self.tabs[i].scene.document.get_entity_mut(*handle)
|
||||
{
|
||||
if let codec::entities::ExtendedEntityData::PointCloudEx(data) = &mut extended.data
|
||||
{
|
||||
edit(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
let changes: Vec<_> = handles.iter().map(|h| (*h, crate::scene::ChangeKind::Modified)).collect();
|
||||
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();
|
||||
@@ -164,14 +190,18 @@ impl OpenCADStudio {
|
||||
|
||||
/// A Point Cloud Manager click: a switch writes the cloud's hidden scans
|
||||
/// and regions as one undo step; the rest is palette state.
|
||||
pub(in crate::app) fn update_pc_manager(&mut self, message: crate::ui::window::pc_manager::PcManagerMsg) -> Task<Message> {
|
||||
pub(in crate::app) fn update_pc_manager(
|
||||
&mut self,
|
||||
message: crate::ui::window::pc_manager::PcManagerMsg,
|
||||
) -> Task<Message> {
|
||||
use crate::scene::model::point_cloud::UNASSIGNED_OFF;
|
||||
use crate::ui::window::pc_manager::{self, PcManagerMsg, Row};
|
||||
let i = self.active_tab;
|
||||
match message {
|
||||
PcManagerMsg::Toggle(handle, row) => {
|
||||
let Some(cloud) =
|
||||
pc_manager::clouds(&self.tabs[i].scene.document, &[]).into_iter().find(|c| c.handle == handle)
|
||||
let Some(cloud) = pc_manager::clouds(&self.tabs[i].scene.document, &[])
|
||||
.into_iter()
|
||||
.find(|c| c.handle == handle)
|
||||
else {
|
||||
return Task::none();
|
||||
};
|
||||
@@ -181,19 +211,34 @@ impl OpenCADStudio {
|
||||
// A scan named by its file name (automation) is the scan with that name.
|
||||
let row = match row {
|
||||
Row::Scan(key) => Row::Scan(
|
||||
cloud.scans.iter().find(|(name, _)| *name == key).map_or(key, |(_, id)| id.clone()),
|
||||
cloud
|
||||
.scans
|
||||
.iter()
|
||||
.find(|(name, _)| *name == key)
|
||||
.map_or(key, |(_, id)| id.clone()),
|
||||
),
|
||||
row => row,
|
||||
};
|
||||
let hidden = pc_manager::toggled(&cloud.hidden, &cloud.scans, &row);
|
||||
self.push_undo_snapshot(i, "POINTCLOUDMANAGER");
|
||||
if let Some(codec::EntityType::Extended(extended)) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
if let codec::entities::ExtendedEntityData::PointCloudEx(data) = &mut extended.data {
|
||||
data.hidden_regions = if hidden.iter().any(|h| h == UNASSIGNED_OFF) { vec![0] } else { Vec::new() };
|
||||
data.hidden_scans = hidden.into_iter().filter(|h| h != UNASSIGNED_OFF).collect();
|
||||
if let Some(codec::EntityType::Extended(extended)) =
|
||||
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||
{
|
||||
if let codec::entities::ExtendedEntityData::PointCloudEx(data) =
|
||||
&mut extended.data
|
||||
{
|
||||
data.hidden_regions = if hidden.iter().any(|h| h == UNASSIGNED_OFF) {
|
||||
vec![0]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
data.hidden_scans =
|
||||
hidden.into_iter().filter(|h| h != UNASSIGNED_OFF).collect();
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
PcManagerMsg::Expand(key) => {
|
||||
@@ -205,7 +250,9 @@ impl OpenCADStudio {
|
||||
PcManagerMsg::Search(text) => self.pc_manager.search = text,
|
||||
PcManagerMsg::CollapseAll => {
|
||||
let clouds = pc_manager::clouds(&self.tabs[i].scene.document, &[]);
|
||||
self.pc_manager.collapsed.extend(pc_manager::folding_keys(&clouds));
|
||||
self.pc_manager
|
||||
.collapsed
|
||||
.extend(pc_manager::folding_keys(&clouds));
|
||||
}
|
||||
PcManagerMsg::ExpandAll => self.pc_manager.collapsed.clear(),
|
||||
}
|
||||
@@ -276,7 +323,11 @@ impl OpenCADStudio {
|
||||
};
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.tabs[i].scene.select_entity(handle, true);
|
||||
let command = if cmd == "_XREFEDIT" { "REFEDIT" } else { "XOPEN" };
|
||||
let command = if cmd == "_XREFEDIT" {
|
||||
"REFEDIT"
|
||||
} else {
|
||||
"XOPEN"
|
||||
};
|
||||
return Some(self.dispatch_command(command));
|
||||
}
|
||||
"_XREFCLIP" => {
|
||||
@@ -286,10 +337,13 @@ impl OpenCADStudio {
|
||||
return Some(Task::none());
|
||||
}
|
||||
let clipped = inserts.iter().any(|h| {
|
||||
crate::scene::pick::xclip::filter_handle(&self.tabs[i].scene.document, *h).is_some()
|
||||
crate::scene::pick::xclip::filter_handle(&self.tabs[i].scene.document, *h)
|
||||
.is_some()
|
||||
});
|
||||
let (command, first) =
|
||||
crate::modules::insert::xclip::XclipCommand::start_new_boundary(inserts, clipped);
|
||||
crate::modules::insert::xclip::XclipCommand::start_new_boundary(
|
||||
inserts, clipped,
|
||||
);
|
||||
if let crate::command::CmdResult::ReportMeasurement(text) = first {
|
||||
for line in text.lines() {
|
||||
self.command_line.push_output(line);
|
||||
@@ -301,7 +355,11 @@ impl OpenCADStudio {
|
||||
"_XREFUNCLIP" => {
|
||||
let inserts = self.selected_xrefs(i);
|
||||
if !inserts.is_empty() {
|
||||
self.apply_xclip(i, inserts, crate::modules::insert::xclip::XclipAction::Delete);
|
||||
self.apply_xclip(
|
||||
i,
|
||||
inserts,
|
||||
crate::modules::insert::xclip::XclipAction::Delete,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Point cloud tools: a crop of the first selected cloud, or the
|
||||
@@ -314,7 +372,9 @@ impl OpenCADStudio {
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
return Some(Task::none());
|
||||
}
|
||||
let Some(codec::EntityType::Extended(cloud)) = self.tabs[i].scene.document.get_entity(handle).cloned() else {
|
||||
let Some(codec::EntityType::Extended(cloud)) =
|
||||
self.tabs[i].scene.document.get_entity(handle).cloned()
|
||||
else {
|
||||
return Some(Task::none());
|
||||
};
|
||||
let option = match cmd {
|
||||
@@ -322,7 +382,9 @@ impl OpenCADStudio {
|
||||
"_PCCROPCIRC" => "C",
|
||||
_ => "",
|
||||
};
|
||||
let command = crate::modules::insert::pc_crop::PointCloudCropCommand::for_cloud(handle, *cloud, option);
|
||||
let command = crate::modules::insert::pc_crop::PointCloudCropCommand::for_cloud(
|
||||
handle, *cloud, option,
|
||||
);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
@@ -349,20 +411,28 @@ impl OpenCADStudio {
|
||||
if handles.is_empty() {
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
let command = crate::modules::insert::pc_stylize::PointCloudStylizeCommand::new(handles);
|
||||
let command =
|
||||
crate::modules::insert::pc_stylize::PointCloudStylizeCommand::new(handles);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
c if c.starts_with("_PCSTYLIZEAPPLY ") => {
|
||||
let mut parts = c.split_whitespace().skip(1);
|
||||
let stylization: i16 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
|
||||
let handles: Vec<codec::Handle> =
|
||||
parts.filter_map(|h| u64::from_str_radix(h, 16).ok()).map(codec::Handle::new).collect();
|
||||
let handles: Vec<codec::Handle> = parts
|
||||
.filter_map(|h| u64::from_str_radix(h, 16).ok())
|
||||
.map(codec::Handle::new)
|
||||
.collect();
|
||||
self.stylize_point_clouds(i, &handles, stylization);
|
||||
}
|
||||
"_PCCROPSHOW" => {
|
||||
let on = !self.selected_point_clouds(i).first().is_some_and(|(_, data)| data.show_cropping);
|
||||
self.edit_selected_point_clouds(i, "POINTCLOUDCROP", |data| data.show_cropping = on);
|
||||
let on = !self
|
||||
.selected_point_clouds(i)
|
||||
.first()
|
||||
.is_some_and(|(_, data)| data.show_cropping);
|
||||
self.edit_selected_point_clouds(i, "POINTCLOUDCROP", |data| {
|
||||
data.show_cropping = on
|
||||
});
|
||||
}
|
||||
"_PCCROPINVERT" => self.edit_selected_point_clouds(i, "POINTCLOUDCROP", |data| {
|
||||
for crop in &mut data.croppings {
|
||||
@@ -375,7 +445,8 @@ impl OpenCADStudio {
|
||||
let id = crate::ui::dock::PanelId::PointCloudManager;
|
||||
self.pc_manager.show = true;
|
||||
if self.dock.location(id).is_none() {
|
||||
self.dock.dock(id, crate::app::config::DockSide::Right, usize::MAX);
|
||||
self.dock
|
||||
.dock(id, crate::app::config::DockSide::Right, usize::MAX);
|
||||
}
|
||||
self.dock_expanded = Some(id);
|
||||
}
|
||||
@@ -385,7 +456,8 @@ impl OpenCADStudio {
|
||||
self.dock_expanded = None;
|
||||
}
|
||||
}
|
||||
"_PCUNCROP" =>self.edit_selected_point_clouds(i, "POINTCLOUDUNCROP", |data| data.croppings.clear()),
|
||||
"_PCUNCROP" => self
|
||||
.edit_selected_point_clouds(i, "POINTCLOUDUNCROP", |data| data.croppings.clear()),
|
||||
"_PDFULUNCLIP" => {
|
||||
self.edit_selected_underlays(i, "PDFCLIP", |u| {
|
||||
u.clip_boundary_vertices.clear();
|
||||
@@ -400,7 +472,9 @@ impl OpenCADStudio {
|
||||
return Some(Task::none());
|
||||
};
|
||||
let (command, first) =
|
||||
crate::modules::insert::pdf_clip::PdfClipCommand::new_boundary(handle, underlay);
|
||||
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);
|
||||
@@ -414,7 +488,8 @@ impl OpenCADStudio {
|
||||
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);
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -69,9 +69,8 @@ impl OpenCADStudio {
|
||||
task
|
||||
}
|
||||
Ok(Outcome::Current(current)) => {
|
||||
self.command_line.push_output(
|
||||
crate::tf!("Enter new value for {name} <{current}>:").as_ref(),
|
||||
);
|
||||
self.command_line
|
||||
.push_output(crate::tf!("Enter new value for {name} <{current}>:").as_ref());
|
||||
self.pending_setvar = Some(name);
|
||||
Task::none()
|
||||
}
|
||||
@@ -199,7 +198,11 @@ mod tests {
|
||||
}
|
||||
|
||||
fn last_line(app: &OpenCADStudio) -> String {
|
||||
app.command_line.history.last().map(|line| line.text.clone()).unwrap_or_default()
|
||||
app.command_line
|
||||
.history
|
||||
.last()
|
||||
.map(|line| line.text.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -25,7 +25,10 @@ fn definitions_dictionary(document: &mut codec::CadDocument) -> Option<Handle> {
|
||||
return None;
|
||||
};
|
||||
if let Some(existing) = root_dictionary.get(DEFINITIONS) {
|
||||
if matches!(document.objects.get(&existing), Some(ObjectType::Dictionary(_))) {
|
||||
if matches!(
|
||||
document.objects.get(&existing),
|
||||
Some(ObjectType::Dictionary(_))
|
||||
) {
|
||||
return Some(existing);
|
||||
}
|
||||
}
|
||||
@@ -33,7 +36,9 @@ fn definitions_dictionary(document: &mut codec::CadDocument) -> Option<Handle> {
|
||||
let mut dictionary = Dictionary::new();
|
||||
dictionary.handle = handle;
|
||||
dictionary.owner = root;
|
||||
document.objects.insert(handle, ObjectType::Dictionary(dictionary));
|
||||
document
|
||||
.objects
|
||||
.insert(handle, ObjectType::Dictionary(dictionary));
|
||||
if let Some(ObjectType::Dictionary(root_dictionary)) = document.objects.get_mut(&root) {
|
||||
root_dictionary.add_entry(DEFINITIONS, handle);
|
||||
}
|
||||
@@ -50,27 +55,41 @@ fn ensure_definition(
|
||||
points: &crate::scene::model::point_cloud::CloudPoints,
|
||||
) -> (Handle, Vector3, Vector3) {
|
||||
let [lo, hi] = points.bounds.unwrap_or_default();
|
||||
let (min, max) = (Vector3::new(lo[0], lo[1], lo[2]), Vector3::new(hi[0], hi[1], hi[2]));
|
||||
let same = |stored: &str| stored.replace('/', "\\").eq_ignore_ascii_case(&path.replace('/', "\\"));
|
||||
let existing = document.objects.iter().find_map(|(handle, object)| match object {
|
||||
ObjectType::ClassObject(object) => match &object.data {
|
||||
ClassObjectData::PointCloudDefinitionEx(def) if same(&def.source_filename) => Some(*handle),
|
||||
let (min, max) = (
|
||||
Vector3::new(lo[0], lo[1], lo[2]),
|
||||
Vector3::new(hi[0], hi[1], hi[2]),
|
||||
);
|
||||
let same = |stored: &str| {
|
||||
stored
|
||||
.replace('/', "\\")
|
||||
.eq_ignore_ascii_case(&path.replace('/', "\\"))
|
||||
};
|
||||
let existing = document
|
||||
.objects
|
||||
.iter()
|
||||
.find_map(|(handle, object)| match object {
|
||||
ObjectType::ClassObject(object) => match &object.data {
|
||||
ClassObjectData::PointCloudDefinitionEx(def) if same(&def.source_filename) => {
|
||||
Some(*handle)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
});
|
||||
});
|
||||
if let Some(handle) = existing {
|
||||
return (handle, min, max);
|
||||
}
|
||||
let handle = document.allocate_handle();
|
||||
let mut definition = ClassObject::new(ClassObjectData::PointCloudDefinitionEx(PointCloudDefinition {
|
||||
class_version: 1,
|
||||
source_filename: path.to_string(),
|
||||
is_loaded: true,
|
||||
point_count: points.positions.len() as i64,
|
||||
extents_min: min,
|
||||
extents_max: max,
|
||||
}));
|
||||
let mut definition = ClassObject::new(ClassObjectData::PointCloudDefinitionEx(
|
||||
PointCloudDefinition {
|
||||
class_version: 1,
|
||||
source_filename: path.to_string(),
|
||||
is_loaded: true,
|
||||
point_count: points.positions.len() as i64,
|
||||
extents_min: min,
|
||||
extents_max: max,
|
||||
},
|
||||
));
|
||||
definition.handle = handle;
|
||||
if let Some(dictionary) = definitions_dictionary(document) {
|
||||
definition.owner = dictionary;
|
||||
@@ -85,14 +104,22 @@ fn ensure_definition(
|
||||
entries.add_entry(key, handle);
|
||||
}
|
||||
}
|
||||
document.objects.insert(handle, ObjectType::ClassObject(definition));
|
||||
document
|
||||
.objects
|
||||
.insert(handle, ObjectType::ClassObject(definition));
|
||||
(handle, min, max)
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
pub(crate) fn attach_point_cloud(&mut self, i: usize, label: String, placement: PointCloudPlacement) {
|
||||
pub(crate) fn attach_point_cloud(
|
||||
&mut self,
|
||||
i: usize,
|
||||
label: String,
|
||||
placement: PointCloudPlacement,
|
||||
) {
|
||||
let Some(points) = crate::scene::model::point_cloud::load(&placement.path) else {
|
||||
self.command_line.push_error("All scans are not found or invalid.");
|
||||
self.command_line
|
||||
.push_error("All scans are not found or invalid.");
|
||||
self.command_line.push_error("Attach point cloud failed");
|
||||
return;
|
||||
};
|
||||
@@ -106,7 +133,12 @@ impl OpenCADStudio {
|
||||
None if options.relative => host
|
||||
.as_deref()
|
||||
.and_then(|host| {
|
||||
crate::io::xref_model::to_pathtype_result(&path, host, crate::io::xref_model::Pathtype::Relative).ok()
|
||||
crate::io::xref_model::to_pathtype_result(
|
||||
&path,
|
||||
host,
|
||||
crate::io::xref_model::Pathtype::Relative,
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
.map(|relative| relative.replace('/', "\\"))
|
||||
.unwrap_or_else(|| path.clone()),
|
||||
@@ -125,10 +157,12 @@ impl OpenCADStudio {
|
||||
let document = &mut self.tabs[i].scene.document;
|
||||
// POINTCLOUDLOCK 1 attaches clouds locked.
|
||||
let locked = options.locked.unwrap_or_else(|| {
|
||||
crate::scene::model::point_cloud::setting("POINTCLOUDLOCK")
|
||||
.is_some_and(|setting| crate::scene::model::point_cloud::setting_value(document, setting) == 1)
|
||||
crate::scene::model::point_cloud::setting("POINTCLOUDLOCK").is_some_and(|setting| {
|
||||
crate::scene::model::point_cloud::setting_value(document, setting) == 1
|
||||
})
|
||||
});
|
||||
let (definition, extents_min, extents_max) = ensure_definition(document, &stored, &name, &points);
|
||||
let (definition, extents_min, extents_max) =
|
||||
ensure_definition(document, &stored, &name, &points);
|
||||
let [x, y, z] = placement.axes;
|
||||
// As the reference creates a cloud: scan colours, the intensity
|
||||
// range 0–100 as a gradient, crops shown.
|
||||
@@ -176,18 +210,26 @@ impl OpenCADStudio {
|
||||
));
|
||||
object.handle = reactor;
|
||||
object.owner = cloud;
|
||||
document.objects.insert(reactor, ObjectType::ClassObject(object));
|
||||
if let Some(ObjectType::ClassObject(definition)) = document.objects.get_mut(&definition) {
|
||||
document
|
||||
.objects
|
||||
.insert(reactor, ObjectType::ClassObject(object));
|
||||
if let Some(ObjectType::ClassObject(definition)) = document.objects.get_mut(&definition)
|
||||
{
|
||||
definition.reactors.push(reactor);
|
||||
}
|
||||
if let Some(codec::EntityType::Extended(extended)) = document.get_entity_mut(cloud) {
|
||||
if let codec::entities::ExtendedEntityData::PointCloudEx(data) = &mut extended.data {
|
||||
if let codec::entities::ExtendedEntityData::PointCloudEx(data) = &mut extended.data
|
||||
{
|
||||
data.reactor_handle = reactor;
|
||||
}
|
||||
}
|
||||
self.command_line.push_output("1 point cloud attached");
|
||||
if relative_later {
|
||||
self.tabs[i].xref_relative_on_save.insert(format!("{}{:X}", crate::io::xref::POINT_CLOUD_KEY, definition.value()));
|
||||
self.tabs[i].xref_relative_on_save.insert(format!(
|
||||
"{}{:X}",
|
||||
crate::io::xref::POINT_CLOUD_KEY,
|
||||
definition.value()
|
||||
));
|
||||
}
|
||||
if options.zoom {
|
||||
self.tabs[i].scene.zoom_to_entities(&[cloud]);
|
||||
|
||||
+769
-182
File diff suppressed because it is too large.
Load diff
+887
-469
File diff suppressed because it is too large.
Load diff
+1071
-448
File diff suppressed because it is too large.
Load diff
@@ -38,11 +38,17 @@ impl OpenCADStudio {
|
||||
action: XclipAction,
|
||||
) {
|
||||
if let XclipAction::Depth { rejected: true, .. } = action {
|
||||
self.command_line.push_error(crate::modules::insert::xclip::DEPTH_REJECTED);
|
||||
self.command_line
|
||||
.push_error(crate::modules::insert::xclip::DEPTH_REJECTED);
|
||||
}
|
||||
self.push_undo_snapshot(i, "XCLIP");
|
||||
let extents: Vec<_> = inserts.iter().map(|h| self.insert_extents(i, *h)).collect();
|
||||
let current_layer = self.tabs[i].scene.document.header.current_layer_name.clone();
|
||||
let current_layer = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.header
|
||||
.current_layer_name
|
||||
.clone();
|
||||
let scene = &mut self.tabs[i].scene;
|
||||
let mut polylines = Vec::new();
|
||||
for (n, insert) in inserts.iter().enumerate() {
|
||||
@@ -64,8 +70,10 @@ impl OpenCADStudio {
|
||||
let Some(spatial) = clip::filter_handle(doc, *insert) else {
|
||||
continue;
|
||||
};
|
||||
let (Some(codec::objects::ObjectType::SpatialFilter(filter)), Some(codec::EntityType::Insert(ins))) =
|
||||
(doc.objects.get(&spatial), doc.get_entity(*insert))
|
||||
let (
|
||||
Some(codec::objects::ObjectType::SpatialFilter(filter)),
|
||||
Some(codec::EntityType::Insert(ins)),
|
||||
) = (doc.objects.get(&spatial), doc.get_entity(*insert))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
use super::*;
|
||||
use crate::io::xref_model::Pathtype;
|
||||
use crate::modules::insert::xattach::{
|
||||
existing_reference, path_to_block_name, prepare_xref_definition, stored_path,
|
||||
XAttachCommand, XrefAttachRequest, XrefPlacement,
|
||||
existing_reference, path_to_block_name, prepare_xref_definition, stored_path, XAttachCommand,
|
||||
XrefAttachRequest, XrefPlacement,
|
||||
};
|
||||
use crate::modules::insert::xref_cmd::display_path;
|
||||
use crate::ui::window::xref_attach::{XrefAttachMsg, XrefAttachState};
|
||||
@@ -22,9 +22,12 @@ impl OpenCADStudio {
|
||||
) {
|
||||
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);
|
||||
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) {
|
||||
if let Some(crate::command::CmdResult::ReportError(message)) =
|
||||
command.on_text_input(file)
|
||||
{
|
||||
self.command_line.push_error(&message);
|
||||
}
|
||||
}
|
||||
@@ -66,7 +69,9 @@ impl OpenCADStudio {
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
if !rest.is_empty() {
|
||||
return Some(self.feed_command(crate::command::StepInput::Text(rest.to_string())));
|
||||
return Some(
|
||||
self.feed_command(crate::command::StepInput::Text(rest.to_string())),
|
||||
);
|
||||
}
|
||||
Some(Task::none())
|
||||
}
|
||||
@@ -131,26 +136,31 @@ impl OpenCADStudio {
|
||||
.block_records
|
||||
.iter()
|
||||
.filter(|br| br.flags.is_xref || br.flags.is_xref_overlay)
|
||||
.filter(|br| {
|
||||
pattern == "*" || crate::io::xref_model::wildcard_match(&br.name, pattern)
|
||||
})
|
||||
.filter(|br| pattern == "*" || crate::io::xref_model::wildcard_match(&br.name, pattern))
|
||||
.map(|br| {
|
||||
let kind = if br.flags.is_xref_overlay { "Overlay" } else { "Attach" };
|
||||
let kind = if br.flags.is_xref_overlay {
|
||||
"Overlay"
|
||||
} else {
|
||||
"Attach"
|
||||
};
|
||||
(br.name.clone(), kind, display_path(&br.xref_path))
|
||||
})
|
||||
.collect();
|
||||
let out = &mut self.command_line;
|
||||
out.push_output("");
|
||||
out.push_output(&format!("{:<34}{:<13}{}", "Xref name", "Xref Type", "Path"));
|
||||
out.push_output(&format!(
|
||||
"{:<34}{:<13}{}",
|
||||
"Xref name",
|
||||
"Xref Type",
|
||||
"Path"
|
||||
"----------------------", "---------", "----------"
|
||||
));
|
||||
out.push_output(&format!("{:<34}{:<13}{}", "----------------------", "---------", "----------"));
|
||||
out.push_output("");
|
||||
for (name, kind, path) in &rows {
|
||||
out.push_output(&format!("{:<34}{:<13}{}", format!("\"{name}\""), *kind, path));
|
||||
out.push_output(&format!(
|
||||
"{:<34}{:<13}{}",
|
||||
format!("\"{name}\""),
|
||||
*kind,
|
||||
path
|
||||
));
|
||||
}
|
||||
out.push_output("");
|
||||
out.push_output(&format!("Total Xref(s): {}", rows.len()));
|
||||
@@ -170,9 +180,8 @@ impl OpenCADStudio {
|
||||
let host = self.tabs[i].current_path.clone();
|
||||
if let Some(host) = host.as_deref() {
|
||||
if crate::modules::insert::xattach::is_self_attach(host, &full, host.parent()) {
|
||||
self.command_line.push_error(
|
||||
"Error: Possible circular reference to current drawing. *Invalid*",
|
||||
);
|
||||
self.command_line
|
||||
.push_error("Error: Possible circular reference to current drawing. *Invalid*");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -180,8 +189,7 @@ impl OpenCADStudio {
|
||||
if let Some(existing) = existing.as_ref() {
|
||||
self.command_line
|
||||
.push_output(&format!("Xref \"{}\" has already been defined.", existing));
|
||||
self.command_line
|
||||
.push_output("Using existing definition.");
|
||||
self.command_line.push_output("Using existing definition.");
|
||||
} else {
|
||||
let verb = if request.overlay {
|
||||
format!("Overlay Xref \"{}\": {}", name, display_path(&full))
|
||||
@@ -209,7 +217,8 @@ impl OpenCADStudio {
|
||||
}
|
||||
let (wires, source_units) = self.xref_preview(i, existing.as_deref(), &full);
|
||||
let host_units = self.tabs[i].scene.document.header.insertion_units;
|
||||
let unit = crate::app::properties::insert_unit_scale(host_units, source_units).unwrap_or(1.0);
|
||||
let unit =
|
||||
crate::app::properties::insert_unit_scale(host_units, source_units).unwrap_or(1.0);
|
||||
let command = command.with_preview(wires, unit);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
@@ -326,8 +335,8 @@ impl OpenCADStudio {
|
||||
let source_units = crate::io::load_file(std::path::Path::new(path))
|
||||
.map(|doc| doc.header.insertion_units)
|
||||
.unwrap_or(0);
|
||||
state.unit_label = crate::t!(crate::app::properties::insunits_name(source_units))
|
||||
.into_owned();
|
||||
state.unit_label =
|
||||
crate::t!(crate::app::properties::insunits_name(source_units)).into_owned();
|
||||
let factor =
|
||||
crate::app::properties::insert_unit_scale(host_units, source_units).unwrap_or(1.0);
|
||||
state.unit_factor = crate::app::properties::format_unit_factor(factor);
|
||||
@@ -434,9 +443,14 @@ impl OpenCADStudio {
|
||||
let path = path.to_string_lossy().into_owned();
|
||||
match std::fs::read(&path) {
|
||||
Ok(bytes) => {
|
||||
crate::scene::model::pdf_raster::register_source(&path, std::sync::Arc::new(bytes));
|
||||
crate::scene::model::pdf_raster::register_source(
|
||||
&path,
|
||||
std::sync::Arc::new(bytes),
|
||||
);
|
||||
crate::scene::model::underlay_vector::forget(&path);
|
||||
if crate::scene::model::underlay_vector::item_names(kind, &path).is_some_and(|n| !n.is_empty()) {
|
||||
if crate::scene::model::underlay_vector::item_names(kind, &path)
|
||||
.is_some_and(|n| !n.is_empty())
|
||||
{
|
||||
self.open_underlay_attach_dialog(kind, &path);
|
||||
} else {
|
||||
self.command_line.push_error(&format!(
|
||||
@@ -455,7 +469,8 @@ impl OpenCADStudio {
|
||||
// A point cloud goes on to the Attach Point Cloud dialog.
|
||||
"rcp" | "rcs" => {
|
||||
if crate::scene::model::point_cloud::load(&path).is_none() {
|
||||
self.command_line.push_error("All scans are not found or invalid.");
|
||||
self.command_line
|
||||
.push_error("All scans are not found or invalid.");
|
||||
self.command_line.push_error("Attach point cloud failed");
|
||||
return Task::none();
|
||||
}
|
||||
@@ -509,7 +524,8 @@ impl OpenCADStudio {
|
||||
{
|
||||
let host = self.tabs[self.active_tab].current_path.clone();
|
||||
let base = host.as_deref().and_then(|h| h.parent());
|
||||
let full = crate::modules::insert::xattach::resolve_xref_store_path(&saved, base);
|
||||
let full =
|
||||
crate::modules::insert::xattach::resolve_xref_store_path(&saved, base);
|
||||
self.load_xref_attach_file(&mut state, &full);
|
||||
}
|
||||
}
|
||||
@@ -569,7 +585,9 @@ impl OpenCADStudio {
|
||||
insert: (!state.insert_on_screen)
|
||||
.then(|| state.insert_point())
|
||||
.flatten(),
|
||||
scale: (!state.scale_on_screen).then(|| state.scale_values()).flatten(),
|
||||
scale: (!state.scale_on_screen)
|
||||
.then(|| state.scale_values())
|
||||
.flatten(),
|
||||
rotation: (!state.rotation_on_screen)
|
||||
.then(|| crate::entities::common::parse_typed_angle(&state.rotation))
|
||||
.flatten()
|
||||
@@ -577,9 +595,7 @@ impl OpenCADStudio {
|
||||
};
|
||||
let invalid = (!state.insert_on_screen && placement.insert.is_none())
|
||||
|| (!state.scale_on_screen
|
||||
&& placement
|
||||
.scale
|
||||
.is_none_or(|s| s.iter().any(|v| *v == 0.0)))
|
||||
&& placement.scale.is_none_or(|s| s.iter().any(|v| *v == 0.0)))
|
||||
|| (!state.rotation_on_screen && placement.rotation.is_none());
|
||||
if invalid || state.path.is_empty() {
|
||||
self.command_line
|
||||
|
||||
Reference in new issue
Block a user