forked from 1b-agent/opencadstudio-upstream
Point clouds: attach dialog, crop toolbar, stylize, palette rows
POINTCLOUDATTACH opens the Attach Point Cloud dialog after the file is chosen; its Attach button lives in the Attach Underlay list instead of a separate Insert panel. A selected cloud brings the crop tools to the right-edge toolbar. POINTCLOUDSTYLIZE sets the stylization with the reference's prompts, the External References palette lists point clouds (unload, reload, detach, path), and definitions keep the header extents. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
3afd547e28
commit
c2d9ac5ae2
48 files changed
+1711
-83
No files matched your search
@@ -6,7 +6,7 @@ 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,
|
||||
RotationChoice, UnderlayLayersState,
|
||||
PointCloudAttachState, RotationChoice, UnderlayLayersState,
|
||||
};
|
||||
|
||||
/// "7.8740 × 3.9369" (inches) for a page.
|
||||
@@ -102,6 +102,139 @@ impl OpenCADStudio {
|
||||
crate::entities::underlay::display_path(&self.pdf_stored_path(path, state.path_type.0));
|
||||
}
|
||||
|
||||
/// POINTCLOUDATTACH after the file is chosen: the Attach Point Cloud
|
||||
/// dialog, placed on screen by default.
|
||||
pub(in crate::app) fn open_point_cloud_attach_dialog(&mut self, path: &std::path::Path) {
|
||||
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 data = format!(
|
||||
"RGB: {} · {}: {} · {}: {}",
|
||||
yes_no(cloud.has_rgb),
|
||||
crate::t!("Intensity"),
|
||||
yes_no(cloud.has_intensity),
|
||||
crate::t!("Normals"),
|
||||
yes_no(cloud.has_normals),
|
||||
);
|
||||
let data_more = format!(
|
||||
"{}: {} · {}: {}",
|
||||
crate::t!("Classification"),
|
||||
yes_no(false),
|
||||
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 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 mut state = PointCloudAttachState {
|
||||
path: full.clone(),
|
||||
name: file_stem(&full),
|
||||
preview: cloud.preview.clone().map(iced::widget::image::Handle::from_bytes),
|
||||
summary: format!(
|
||||
"{} · {}",
|
||||
crate::tf!("{count} scans", count = cloud.scans),
|
||||
crate::tf!("{count} points", count = cloud.positions.len())
|
||||
),
|
||||
data,
|
||||
data_more,
|
||||
size: cloud
|
||||
.bounds
|
||||
.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)),
|
||||
found_in: crate::entities::underlay::display_path(&full),
|
||||
saved_path: String::new(),
|
||||
path_type: memory.path_type,
|
||||
insert_on_screen: true,
|
||||
insert: ["0.0000".into(), "0.0000".into(), "0.0000".into()],
|
||||
scale_on_screen: true,
|
||||
scale: "1.0000".into(),
|
||||
rotation_on_screen: true,
|
||||
rotation: "0".into(),
|
||||
lock: false,
|
||||
zoom: true,
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
/// The dialog's Attach: what it set goes to the command, which asks for
|
||||
/// the rest.
|
||||
fn point_cloud_attach_ok(&mut self, i: usize) -> Task<Message> {
|
||||
use crate::command::CadCommand;
|
||||
let Some(state) = self.point_cloud_attach.as_ref() else {
|
||||
return Task::none();
|
||||
};
|
||||
let number = |s: &str| s.trim().parse::<f64>().ok();
|
||||
let insertion = if state.insert_on_screen {
|
||||
None
|
||||
} else {
|
||||
match (number(&state.insert[0]), number(&state.insert[1]), number(&state.insert[2])) {
|
||||
(Some(x), Some(y), Some(z)) => Some(glam::DVec3::new(x, y, z)),
|
||||
_ => {
|
||||
self.command_line.push_error("Requires numeric value.");
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
};
|
||||
let scale = if state.scale_on_screen {
|
||||
None
|
||||
} else {
|
||||
match number(&state.scale).filter(|v| *v > 0.0) {
|
||||
Some(v) => Some(v),
|
||||
None => {
|
||||
self.command_line.push_error("Value must be positive and nonzero.");
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
};
|
||||
let rotation = if state.rotation_on_screen {
|
||||
None
|
||||
} else {
|
||||
match number(&state.rotation) {
|
||||
Some(v) => Some(v),
|
||||
None => {
|
||||
self.command_line.push_error("Requires numeric value.");
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
};
|
||||
let state = self.point_cloud_attach.take().expect("checked above");
|
||||
self.close_active_modal();
|
||||
let options = crate::modules::insert::pc_attach::AttachOptions {
|
||||
stored: (state.path_type.0 == Pathtype::None).then(|| {
|
||||
std::path::Path::new(&state.path)
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
.unwrap_or_default()
|
||||
}),
|
||||
relative: state.path_type.0 == Pathtype::Relative,
|
||||
locked: Some(state.lock),
|
||||
zoom: state.zoom,
|
||||
scale,
|
||||
rotation,
|
||||
};
|
||||
let mut command = crate::modules::insert::pc_attach::PointCloudAttachCommand::from_dialog(
|
||||
std::path::PathBuf::from(&state.path),
|
||||
options,
|
||||
);
|
||||
match insertion {
|
||||
Some(point) => {
|
||||
let result = command.on_point(point);
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
self.apply_cmd_result(result)
|
||||
}
|
||||
None => {
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
self.focus_cmd_input()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ULAYERS: every underlay of the drawing, the selected one first shown.
|
||||
pub(in crate::app) fn open_underlay_layers_dialog(&mut self, i: usize) {
|
||||
let document = &self.tabs[i].scene.document;
|
||||
@@ -244,6 +377,7 @@ impl OpenCADStudio {
|
||||
crate::t!("Attaches a DGN file as an underlay.")
|
||||
}
|
||||
"attach" => crate::t!("Attaches a PDF file as an underlay."),
|
||||
"pointcloud" => crate::t!("Attaches a point cloud scan or project."),
|
||||
"layers" => crate::t!("Turns the layers of a PDF underlay on or off."),
|
||||
_ => crate::t!("Imports the geometry, fills, raster images and text of a PDF file as drawing objects."),
|
||||
};
|
||||
@@ -343,6 +477,57 @@ impl OpenCADStudio {
|
||||
}
|
||||
PdfDialogMsg::AttachOk => return self.pdf_attach_ok(i),
|
||||
|
||||
// ── Attach Point Cloud ────────────────────────────────────────
|
||||
PdfDialogMsg::CloudBrowse => return Task::done(Message::PointCloudAttachPick),
|
||||
PdfDialogMsg::CloudOk => return self.point_cloud_attach_ok(i),
|
||||
PdfDialogMsg::CloudPathType(choice) => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::CloudInsertOnScreen(on) => {
|
||||
if let Some(s) = self.point_cloud_attach.as_mut() {
|
||||
s.insert_on_screen = on;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::CloudInsert(k, value) => {
|
||||
if let Some(s) = self.point_cloud_attach.as_mut() {
|
||||
s.insert[k.min(2)] = value;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::CloudScaleOnScreen(on) => {
|
||||
if let Some(s) = self.point_cloud_attach.as_mut() {
|
||||
s.scale_on_screen = on;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::CloudScale(value) => {
|
||||
if let Some(s) = self.point_cloud_attach.as_mut() {
|
||||
s.scale = value;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::CloudRotationOnScreen(on) => {
|
||||
if let Some(s) = self.point_cloud_attach.as_mut() {
|
||||
s.rotation_on_screen = on;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::CloudRotation(value) => {
|
||||
if let Some(s) = self.point_cloud_attach.as_mut() {
|
||||
s.rotation = value;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::CloudLock(on) => {
|
||||
if let Some(s) = self.point_cloud_attach.as_mut() {
|
||||
s.lock = on;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::CloudZoom(on) => {
|
||||
if let Some(s) = self.point_cloud_attach.as_mut() {
|
||||
s.zoom = on;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Underlay Layers ───────────────────────────────────────────
|
||||
PdfDialogMsg::LayersUnderlay(name) => {
|
||||
if let Some(s) = self.underlay_layers.as_mut() {
|
||||
|
||||
@@ -62,6 +62,104 @@ impl OpenCADStudio {
|
||||
})
|
||||
};
|
||||
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));
|
||||
}
|
||||
|
||||
/// The selected point clouds, or nothing when anything else is selected.
|
||||
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())),
|
||||
_ => return Vec::new(),
|
||||
},
|
||||
_ => return Vec::new(),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// POINTCLOUDSTYLIZE's result: the clouds that carry the data take the
|
||||
/// stylization (one undo step); the rest are named.
|
||||
fn stylize_point_clouds(&mut self, i: usize, handles: &[codec::Handle], stylization: i16) {
|
||||
let mut done = Vec::new();
|
||||
for &handle in handles {
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
continue;
|
||||
}
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let Some(codec::EntityType::Extended(extended)) = document.get_entity(handle) else {
|
||||
continue;
|
||||
};
|
||||
let codec::entities::ExtendedEntityData::PointCloudEx(data) = &extended.data else {
|
||||
continue;
|
||||
};
|
||||
let cloud = crate::scene::model::point_cloud::resolve_source(document, data)
|
||||
.and_then(|path| crate::scene::model::point_cloud::load(&path));
|
||||
let supported = match (stylization, &cloud) {
|
||||
(6, _) => false,
|
||||
(_, None) => true,
|
||||
(1, Some(c)) => c.has_rgb,
|
||||
(5, Some(c)) => c.has_intensity,
|
||||
(3, Some(c)) => c.has_normals,
|
||||
_ => true,
|
||||
};
|
||||
if supported {
|
||||
done.push(handle);
|
||||
} else {
|
||||
self.command_line.push_info(&format!(
|
||||
"The selected point cloud({}) does not support this stylization type.",
|
||||
data.name
|
||||
));
|
||||
}
|
||||
}
|
||||
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 {
|
||||
crate::entities::extended::set_point_cloud_stylization(data, stylization);
|
||||
}
|
||||
}
|
||||
}
|
||||
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()));
|
||||
}
|
||||
|
||||
/// One undo step that edits every selected point cloud not locked.
|
||||
fn edit_selected_point_clouds(
|
||||
&mut self,
|
||||
i: usize,
|
||||
label: &str,
|
||||
edit: impl Fn(&mut codec::entities::PointCloudExData),
|
||||
) {
|
||||
let handles: Vec<_> = self
|
||||
.selected_point_clouds(i)
|
||||
.into_iter()
|
||||
.map(|(h, _)| h)
|
||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
||||
.collect();
|
||||
if handles.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.push_undo_snapshot(i, label);
|
||||
for handle in &handles {
|
||||
if let Some(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();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
|
||||
/// One undo step that edits every selected PDF underlay.
|
||||
@@ -156,6 +254,72 @@ impl OpenCADStudio {
|
||||
self.apply_xclip(i, inserts, crate::modules::insert::xclip::XclipAction::Delete);
|
||||
}
|
||||
}
|
||||
// Point cloud tools: a crop of the first selected cloud, or the
|
||||
// crops of every selected one.
|
||||
"_PCCROPRECT" | "_PCCROPPOLY" | "_PCCROPCIRC" => {
|
||||
use crate::command::CadCommand;
|
||||
let Some(&(handle, _)) = self.selected_point_clouds(i).first() else {
|
||||
return Some(Task::none());
|
||||
};
|
||||
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 {
|
||||
return Some(Task::none());
|
||||
};
|
||||
let option = match cmd {
|
||||
"_PCCROPPOLY" => "P",
|
||||
"_PCCROPCIRC" => "C",
|
||||
_ => "",
|
||||
};
|
||||
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));
|
||||
}
|
||||
"POINTCLOUDSTYLIZE" => {
|
||||
use crate::command::CadCommand;
|
||||
let command = crate::modules::draw::select::SelectObjectsCommand::with_prompt(
|
||||
"POINTCLOUDSTYLIZE",
|
||||
"_PCSTYLIZE",
|
||||
"Select point cloud objects:",
|
||||
);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
// The chosen objects' point clouds, then the option.
|
||||
"_PCSTYLIZE" => {
|
||||
use crate::command::CadCommand;
|
||||
let handles: Vec<codec::Handle> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.into_iter()
|
||||
.filter(|(_, entity)| crate::scene::is_point_cloud(entity))
|
||||
.map(|(h, _)| h)
|
||||
.collect();
|
||||
if handles.is_empty() {
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
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();
|
||||
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);
|
||||
}
|
||||
"_PCCROPINVERT" => self.edit_selected_point_clouds(i, "POINTCLOUDCROP", |data| {
|
||||
for crop in &mut data.croppings {
|
||||
crop.inverted = !crop.inverted;
|
||||
}
|
||||
}),
|
||||
"_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();
|
||||
|
||||
@@ -49,18 +49,8 @@ fn ensure_definition(
|
||||
name: &str,
|
||||
points: &crate::scene::model::point_cloud::CloudPoints,
|
||||
) -> (Handle, Vector3, Vector3) {
|
||||
let (mut lo, mut hi) = ([f64::MAX; 3], [f64::MIN; 3]);
|
||||
for p in &points.positions {
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(p[k]);
|
||||
hi[k] = hi[k].max(p[k]);
|
||||
}
|
||||
}
|
||||
let (min, max) = if points.positions.is_empty() {
|
||||
(Vector3::ZERO, Vector3::ZERO)
|
||||
} else {
|
||||
(Vector3::new(lo[0], lo[1], lo[2]), Vector3::new(hi[0], hi[1], hi[2]))
|
||||
};
|
||||
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 {
|
||||
@@ -107,6 +97,25 @@ impl OpenCADStudio {
|
||||
return;
|
||||
};
|
||||
let path = placement.path.to_string_lossy().into_owned();
|
||||
let options = placement.options.clone();
|
||||
// The path stored: as chosen; relative to the drawing once it has a
|
||||
// file (until then the full path, made relative on the first save).
|
||||
let host = self.tabs[i].current_path.clone();
|
||||
let stored = match &options.stored {
|
||||
Some(stored) => stored.clone(),
|
||||
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()
|
||||
})
|
||||
.map(|relative| relative.replace('/', "\\"))
|
||||
.unwrap_or_else(|| path.clone()),
|
||||
None => path.clone(),
|
||||
};
|
||||
if stored != path {
|
||||
crate::scene::model::point_cloud::register_source(&stored, placement.path.clone());
|
||||
}
|
||||
let relative_later = options.relative && options.stored.is_none() && host.is_none();
|
||||
let name = placement
|
||||
.path
|
||||
.file_stem()
|
||||
@@ -115,9 +124,11 @@ impl OpenCADStudio {
|
||||
let pending = self.begin_undo(i, label, 1, false);
|
||||
let document = &mut self.tabs[i].scene.document;
|
||||
// POINTCLOUDLOCK 1 attaches clouds locked.
|
||||
let locked = 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, &path, &name, &points);
|
||||
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)
|
||||
});
|
||||
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.
|
||||
@@ -175,6 +186,12 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
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()));
|
||||
}
|
||||
if options.zoom {
|
||||
self.tabs[i].scene.zoom_to_entities(&[cloud]);
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
if let Some(pd) = pending {
|
||||
|
||||
@@ -452,19 +452,14 @@ impl OpenCADStudio {
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
// A point cloud goes on to its placement prompts.
|
||||
// A point cloud goes on to the Attach Point Cloud dialog.
|
||||
"rcp" | "rcs" => {
|
||||
use crate::command::CadCommand;
|
||||
let i = self.active_tab;
|
||||
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("Attach point cloud failed");
|
||||
return Task::none();
|
||||
}
|
||||
let command =
|
||||
crate::modules::insert::pc_attach::PointCloudAttachCommand::with_file(path);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
self.open_point_cloud_attach_dialog(&path);
|
||||
Task::none()
|
||||
}
|
||||
"png" | "jpg" | "jpeg" | "bmp" | "tif" | "tiff" => {
|
||||
|
||||
Reference in new issue
Block a user