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:
ramox81andClaude Opus 5.5 committed 2026-09-30 10:14:44 +03:00
1 parent 3afd547e28
commit c2d9ac5ae2
48 files changed
+1711 -83

No files matched your search

+27
View File
@@ -59,6 +59,24 @@ pub fn pdf_underlay_tools(kind: codec::entities::UnderlayType) -> Vec<ToolDef> {
tools
}
/// Point cloud tools: the three crops, showing, inverting and removing
/// them, and the palette.
pub fn point_cloud_tools() -> Vec<ToolDef> {
vec![
tool("_PCCROPRECT", "Rectangular\nCrop", CLIP),
tool("_PCCROPPOLY", "Polygonal\nCrop", CLIP),
tool("_PCCROPCIRC", "Circular\nCrop", CLIP),
tool(
"_PCCROPSHOW",
"Show\nCropping",
include_bytes!("../../../assets/icons/underlay_frames.svg"),
),
tool("_PCCROPINVERT", "Invert\nCropping", CLIP),
tool("_PCUNCROP", "Remove All\nCropping", UNCLIP),
tool("EXTERNALREFERENCES", "External\nReferences", crate::ui::icons::FOLDER_OPEN),
]
}
/// External reference tools: edit, open, clipping and the palette.
pub fn xref_tools() -> Vec<ToolDef> {
vec![
@@ -90,6 +108,15 @@ impl Ribbon {
self.xref_ctx
}
/// Only point clouds are selected: whether the first shows its crops.
pub fn set_point_cloud_context(&mut self, context: Option<bool>) {
self.point_cloud_ctx = context;
}
pub fn point_cloud_context(&self) -> Option<bool> {
self.point_cloud_ctx
}
/// Select a tab by its module id.
pub fn select_by_id(&mut self, id: &str) -> bool {
match self.modules.iter().position(|m| m.id() == id) {
+4 -1
View File
@@ -26,7 +26,7 @@ pub(crate) use draw_panel::tools as panel_tools;
mod modify_panel;
mod color_dropdown;
mod context_tools;
pub use context_tools::{pdf_underlay_tools, xref_tools, UnderlayContext};
pub use context_tools::{pdf_underlay_tools, point_cloud_tools, xref_tools, UnderlayContext};
use widgets::{StyleContext, *};
pub(crate) use widgets::{REDO_HISTORY_ID, UNDO_HISTORY_ID};
mod collapse;
@@ -109,6 +109,8 @@ pub struct Ribbon {
underlay_ctx: Option<UnderlayContext>,
/// Only xrefs are selected.
xref_ctx: bool,
/// Only point clouds are selected: whether the first shows its crops.
point_cloud_ctx: Option<bool>,
/// XDWGFADECTL as the Reference slide-out shows it (negative = off).
pub xref_fade: i32,
}
@@ -206,6 +208,7 @@ impl Ribbon {
collapse_tight: Arc::new(AtomicBool::new(false)),
underlay_ctx: None,
xref_ctx: false,
point_cloud_ctx: None,
xref_fade: 50,
}
}
+146
View File
@@ -35,6 +35,18 @@ pub enum PdfDialogMsg {
AttachRotation(String),
AttachDetails(bool),
AttachOk,
// Attach Point Cloud
CloudBrowse,
CloudPathType(PathTypeChoice),
CloudInsertOnScreen(bool),
CloudInsert(usize, String),
CloudScaleOnScreen(bool),
CloudScale(String),
CloudRotationOnScreen(bool),
CloudRotation(String),
CloudLock(bool),
CloudZoom(bool),
CloudOk,
// Underlay Layers
LayersUnderlay(String),
LayersSearch(String),
@@ -665,6 +677,140 @@ pub fn view_attach<'a>(
.into()
}
// ── Attach Point Cloud ─────────────────────────────────────────────────────
/// The Attach Point Cloud dialog: the scan or project chosen, what it
/// holds, and how it is placed.
pub struct PointCloudAttachState {
/// The file read (absolute).
pub path: String,
pub name: String,
pub preview: Option<image::Handle>,
/// "1 scan · 365281 points".
pub summary: String,
/// Which data the scans carry (colour, intensity, normals; then
/// classification and segmentation).
pub data: String,
pub data_more: String,
/// Width × length × height.
pub size: String,
pub unit: String,
pub found_in: String,
pub saved_path: String,
pub path_type: PathTypeChoice,
pub insert_on_screen: bool,
pub insert: [String; 3],
pub scale_on_screen: bool,
pub scale: String,
pub rotation_on_screen: bool,
pub rotation: String,
pub lock: bool,
pub zoom: bool,
}
/// A switch that cannot be used here: dimmed, not pressable.
fn off_chip<'a>(label: String) -> Element<'a, Message> {
container(text(label).size(11).style(muted_style)).padding([4, 10]).into()
}
pub fn view_point_cloud_attach<'a>(
state: &'a PointCloudAttachState,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let file = row![
text_input("", &state.name).size(12).padding([5, 8]).width(Fill).style(field_style),
container(text(state.summary.clone()).size(11).style(accent_text)).padding([4, 10]).style(well_style),
button(text(t!("Browse...")).size(11))
.on_press(msg(PdfDialogMsg::CloudBrowse))
.style(button_style(false))
.padding([5, 12]),
]
.spacing(8)
.align_y(iced::Center);
let picture: Element<'a, Message> = match &state.preview {
Some(handle) => image(handle.clone()).width(Fill).height(Fill).into(),
None => Space::new().width(Fill).height(Fill).into(),
};
let preview = card(
t!("Preview").into_owned(),
container(picture).padding(6).height(Length::Fixed(226.0)).width(Fill).style(well_style),
);
let details = card(
t!("File details").into_owned(),
column![
info_line(t!("Found in:").into_owned(), &state.found_in),
info_line(t!("Saved path:").into_owned(), &state.saved_path),
info_line(t!("Data:").into_owned(), &state.data),
info_line(String::new(), &state.data_more),
info_line(t!("Size:").into_owned(), &state.size),
info_line(t!("Unit:").into_owned(), &state.unit),
]
.spacing(4),
);
let on_screen = t!("On screen").into_owned();
let heading = |label: std::borrow::Cow<'static, str>, on: bool, message: Message| {
row![text(label).size(11).width(Fill), chip(on_screen.clone(), on, message)].align_y(iced::Center)
};
let insert_enabled = !state.insert_on_screen;
let xyz = row![
field("X".into(), &state.insert[0], insert_enabled, 12.0, |v| msg(PdfDialogMsg::CloudInsert(0, v))),
field("Y".into(), &state.insert[1], insert_enabled, 12.0, |v| msg(PdfDialogMsg::CloudInsert(1, v))),
field("Z".into(), &state.insert[2], insert_enabled, 12.0, |v| msg(PdfDialogMsg::CloudInsert(2, v))),
]
.spacing(8);
let placement = card(
t!("Placement").into_owned(),
column![
heading(
t!("Insertion point"),
state.insert_on_screen,
msg(PdfDialogMsg::CloudInsertOnScreen(!state.insert_on_screen))
),
xyz,
heading(t!("Scale"), state.scale_on_screen, msg(PdfDialogMsg::CloudScaleOnScreen(!state.scale_on_screen))),
field(String::new(), &state.scale, !state.scale_on_screen, 0.0, |v| msg(PdfDialogMsg::CloudScale(v))),
heading(
t!("Rotation"),
state.rotation_on_screen,
msg(PdfDialogMsg::CloudRotationOnScreen(!state.rotation_on_screen))
),
field(String::new(), &state.rotation, !state.rotation_on_screen, 0.0, |v| msg(PdfDialogMsg::CloudRotation(v))),
]
.spacing(6),
);
let path = card(
t!("Path type").into_owned(),
segmented(
PathTypeChoice::ALL.iter().map(|c| (*c, c.to_string())).collect(),
state.path_type,
PdfDialogMsg::CloudPathType,
),
);
let options = card(
t!("Options").into_owned(),
column![
off_chip(t!("Use geographic location").into_owned()),
chip(t!("Lock point cloud").into_owned(), state.lock, msg(PdfDialogMsg::CloudLock(!state.lock))),
chip(t!("Zoom to point cloud").into_owned(), state.zoom, msg(PdfDialogMsg::CloudZoom(!state.zoom))),
]
.spacing(6),
);
let body = row![
column![preview, details].spacing(8).width(Length::FillPortion(3)),
column![placement, path, options].spacing(8).width(Length::FillPortion(2)),
]
.spacing(10);
column![file, body, footer("pointcloud", None, t!("Attach"), PdfDialogMsg::CloudOk)]
.spacing(10)
.padding([10, 12])
.width(sizing.width)
.into()
}
// ── Underlay Layers ────────────────────────────────────────────────────────
/// One PDF underlay the dialog can show: its name, its file's layers, and
+14 -1
View File
@@ -1103,6 +1103,11 @@ fn direct_identities(doc: &CadDocument) -> HashSet<(u64, String)> {
ObjectType::UnderlayDefinition(def) => {
ids.insert((handle.value(), def.file_path.clone()));
}
ObjectType::ClassObject(object) => {
if let codec::objects::ClassObjectData::PointCloudDefinitionEx(def) = &object.data {
ids.insert((handle.value(), def.source_filename.clone()));
}
}
_ => {}
}
}
@@ -1193,6 +1198,14 @@ fn type_text(entry: &ReferenceEntry) -> std::borrow::Cow<'static, str> {
_ => crate::t!("PDF"),
}
}
// The file's own kind: RCP (project) or RCS (scan).
RefKind::PointCloud => {
let ext = std::path::Path::new(&entry.saved_path.replace('\\', "/"))
.extension()
.map(|e| e.to_string_lossy().to_ascii_uppercase())
.unwrap_or_default();
if ext == "RCS" { "RCS".into() } else { "RCP".into() }
}
}
}
@@ -2125,7 +2138,7 @@ fn reference_preview(entry: &ReferenceEntry) -> Option<image::RgbaImage> {
let img = image::open(found).ok()?;
Some(img.thumbnail(PREVIEW_MAX, PREVIEW_MAX).to_rgba8())
}
RefKind::Underlay => None,
RefKind::Underlay | RefKind::PointCloud => None,
}
}