forked from 1b-agent/opencadstudio-upstream
Merge commit 'ac9a6b07' into codex/pointcloud-attach
# Conflicts: # locales/ar-SA/opencadstudio.ftl # locales/bg-BG/opencadstudio.ftl # locales/cs-CZ/opencadstudio.ftl # locales/de-DE/opencadstudio.ftl # locales/el-GR/opencadstudio.ftl # locales/en-US/opencadstudio.ftl # locales/es-ES/opencadstudio.ftl # locales/fi-FI/opencadstudio.ftl # locales/fr-FR/opencadstudio.ftl # locales/hi-IN/opencadstudio.ftl # locales/hu-HU/opencadstudio.ftl # locales/it-IT/opencadstudio.ftl # locales/ja-JP/opencadstudio.ftl # locales/ko-KR/opencadstudio.ftl # locales/nl-NL/opencadstudio.ftl # locales/pl-PL/opencadstudio.ftl # locales/pt-BR/opencadstudio.ftl # locales/ru-RU/opencadstudio.ftl # locales/tr-TR/opencadstudio.ftl # locales/zh-CN/opencadstudio.ftl # locales/zh-TW/opencadstudio.ftl # src/app/control/actions.rs # src/locale_catalog.rs
This commit is contained in:
commit
ee0d4aedb5
35 files changed
+673
-13
No files matched your search
@@ -4003,6 +4003,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = إنشاء
|
||||
.boolean = عمليات منطقية
|
||||
|
||||
@@ -3990,6 +3990,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Създаване
|
||||
.boolean = Булеви операции
|
||||
|
||||
@@ -3988,6 +3988,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Vytvořit
|
||||
.boolean = Booleovské operace
|
||||
|
||||
@@ -3989,6 +3989,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Erstellen
|
||||
.boolean = Boolesche Operationen
|
||||
|
||||
@@ -3991,6 +3991,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Δημιουργία
|
||||
.boolean = Δυαδική πράξη
|
||||
|
||||
@@ -3992,6 +3992,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Create
|
||||
.boolean = Boolean
|
||||
|
||||
@@ -3991,6 +3991,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Crear
|
||||
.boolean = Booleanas
|
||||
|
||||
@@ -3990,6 +3990,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Luo
|
||||
.boolean = Boolen operaatiot
|
||||
|
||||
@@ -3991,6 +3991,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Créer
|
||||
.boolean = Opérations booléennes
|
||||
|
||||
@@ -3979,6 +3979,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = बनाएँ
|
||||
.boolean = बूलियन संक्रियाएँ
|
||||
|
||||
@@ -3990,6 +3990,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Létrehozás
|
||||
.boolean = Logikai műveletek
|
||||
|
||||
@@ -3990,6 +3990,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Crea
|
||||
.boolean = Operazioni booleane
|
||||
|
||||
@@ -3979,6 +3979,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = 作成
|
||||
.boolean = ブール演算
|
||||
|
||||
@@ -3990,6 +3990,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = 생성
|
||||
.boolean = 불리언 연산
|
||||
|
||||
@@ -3989,6 +3989,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Maken
|
||||
.boolean = Booleaanse bewerkingen
|
||||
|
||||
@@ -3990,6 +3990,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Utwórz
|
||||
.boolean = Operacje boolowskie
|
||||
|
||||
@@ -3991,6 +3991,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Criar
|
||||
.boolean = Operações booleanas
|
||||
|
||||
@@ -3992,6 +3992,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Создание
|
||||
.boolean = Булевы операции
|
||||
|
||||
@@ -3951,6 +3951,14 @@ insert =
|
||||
.pcm-name-taken = Bu adda bir renk şeması zaten var.
|
||||
.pcm-bad-range = Renklendirilen nokta aralığı geçersiz.
|
||||
.pcm-help = Nokta bulutunun yoğunluğa veya yüksekliğe göre renklendirildiği renk şemalarını ve aralıkları ayarlar.
|
||||
.pc-manager = Nokta Bulutu Yöneticisi
|
||||
.pc-regions-scans = BÖLGELER VE TARAMALAR
|
||||
.pc-regions = Bölgeler
|
||||
.pc-unassigned = Atanmamış Noktalar
|
||||
.pc-scans = Taramalar
|
||||
.pc-search = Ara
|
||||
.pc-collapse-all = Tümünü daralt
|
||||
.pc-expand-all = Tümünü genişlet
|
||||
model =
|
||||
.create = Oluştur
|
||||
.boolean = Boole işlemleri
|
||||
|
||||
@@ -3974,6 +3974,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = 创建
|
||||
.boolean = 布尔运算
|
||||
|
||||
@@ -3986,6 +3986,14 @@ insert =
|
||||
.pcm-name-taken = A color scheme with this name already exists.
|
||||
.pcm-bad-range = Invalid range of colorized points.
|
||||
.pcm-help = Sets the color schemes and ranges a point cloud is colored by intensity or elevation.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = 建立
|
||||
.boolean = 布林運算
|
||||
|
||||
@@ -740,6 +740,8 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"EXPORTSTEP",
|
||||
"EXPORTSTL",
|
||||
"EXTERNALREFERENCES",
|
||||
"POINTCLOUDMANAGER",
|
||||
"POINTCLOUDMANAGERCLOSE",
|
||||
"EXTRIM",
|
||||
"FILETAB",
|
||||
"FIND",
|
||||
|
||||
@@ -34,7 +34,13 @@ impl OpenCADStudio {
|
||||
common.color.clone()
|
||||
};
|
||||
let color = color.rgb().map_or([255; 3], |(r, g, b)| [r, g, b]);
|
||||
if let Some(placed) = crate::scene::model::point_cloud::placed(document, data, color, None) {
|
||||
if let Some(placed) = crate::scene::model::point_cloud::placed(
|
||||
document,
|
||||
data,
|
||||
color,
|
||||
None,
|
||||
&crate::scene::model::point_cloud::hidden(common),
|
||||
) {
|
||||
clouds.push((common.handle, placed));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,59 @@ impl OpenCADStudio {
|
||||
self.refresh_properties();
|
||||
}
|
||||
|
||||
/// A Point Cloud Manager click: a switch writes the cloud's visibility
|
||||
/// extended data 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> {
|
||||
use crate::scene::model::point_cloud::VISIBILITY_APP;
|
||||
use crate::ui::window::pc_manager::{self, PcManagerMsg};
|
||||
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)
|
||||
else {
|
||||
return Task::none();
|
||||
};
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
return Task::none();
|
||||
}
|
||||
let hidden = pc_manager::toggled(&cloud.hidden, &cloud.scans, &row);
|
||||
self.push_undo_snapshot(i, "POINTCLOUDMANAGER");
|
||||
self.tabs[i].scene.ensure_app_id(VISIBILITY_APP);
|
||||
let app_handle = self.tabs[i].scene.document.app_ids.get(VISIBILITY_APP).map(|a| a.handle.value());
|
||||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
let data = &mut entity.common_mut().extended_data;
|
||||
if let Some(app) = app_handle {
|
||||
data.raw_dwg_eed.retain(|(a, _)| *a != app);
|
||||
}
|
||||
data.remove_record(VISIBILITY_APP);
|
||||
if !hidden.is_empty() {
|
||||
let mut record = codec::xdata::ExtendedDataRecord::new(VISIBILITY_APP);
|
||||
for name in hidden {
|
||||
record.add_value(codec::xdata::XDataValue::String(name));
|
||||
}
|
||||
data.add_record(record);
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
PcManagerMsg::Expand(key) => {
|
||||
if !self.pc_manager.collapsed.remove(&key) {
|
||||
self.pc_manager.collapsed.insert(key);
|
||||
}
|
||||
}
|
||||
PcManagerMsg::Select(key) => self.pc_manager.selected = Some(key),
|
||||
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));
|
||||
}
|
||||
PcManagerMsg::ExpandAll => self.pc_manager.collapsed.clear(),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// One undo step that edits every selected PDF underlay.
|
||||
fn edit_selected_underlays(&mut self, i: usize, label: &str, edit: impl Fn(&mut Underlay)) {
|
||||
let handles: Vec<_> = self
|
||||
@@ -319,7 +372,23 @@ impl OpenCADStudio {
|
||||
crop.inverted = !crop.inverted;
|
||||
}
|
||||
}),
|
||||
"_PCUNCROP" => self.edit_selected_point_clouds(i, "POINTCLOUDUNCROP", |data| data.croppings.clear()),
|
||||
// The Point Cloud Manager docks on the right like External
|
||||
// References and opens expanded.
|
||||
"POINTCLOUDMANAGER" => {
|
||||
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_expanded = Some(id);
|
||||
}
|
||||
"POINTCLOUDMANAGERCLOSE" => {
|
||||
self.pc_manager.show = false;
|
||||
if self.dock_expanded == Some(crate::ui::dock::PanelId::PointCloudManager) {
|
||||
self.dock_expanded = None;
|
||||
}
|
||||
}
|
||||
"_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();
|
||||
|
||||
@@ -71,6 +71,7 @@ pub(super) const NAMES: &[&str] = &[
|
||||
"close_modal",
|
||||
"pdf_dialog_ok",
|
||||
"pdf_layer_toggle",
|
||||
"pc_manager_toggle",
|
||||
"pdf_page_select",
|
||||
"pc_colormap",
|
||||
"ribbon_tab",
|
||||
@@ -719,6 +720,19 @@ impl OpenCADStudio {
|
||||
_ => return Err(failure("invalid_value", "Unknown color map edit")),
|
||||
})
|
||||
}
|
||||
// Point Cloud Manager: flip a row's switch, "<handle>:<row>" with
|
||||
// row cloud, unassigned, scans or scan:<name>.
|
||||
"pc_manager_toggle" => {
|
||||
use crate::ui::window::pc_manager::{PcManagerMsg, Row};
|
||||
let value = string(req, "value")?;
|
||||
let parsed = value.split_once(':').and_then(|(handle, row)| {
|
||||
Some((codec::Handle::new(u64::from_str_radix(handle, 16).ok()?), Row::parse(row)?))
|
||||
});
|
||||
let Some((handle, row)) = parsed else {
|
||||
return Err(failure("bad_value", "Expected <handle>:<row>"));
|
||||
};
|
||||
Message::PcManager(PcManagerMsg::Toggle(handle, row))
|
||||
}
|
||||
// Attach dialog: choose pages by index ("0,2").
|
||||
"pdf_page_select" => {
|
||||
let pages: Vec<usize> = string(req, "value")?
|
||||
|
||||
@@ -793,6 +793,8 @@ pub(super) struct OpenCADStudio {
|
||||
/// Whether the Browser panel is shown. Off until BROWSER opens it, so
|
||||
/// the default layout is unchanged for existing users.
|
||||
pub(crate) show_browser: bool,
|
||||
/// Point Cloud Manager palette (POINTCLOUDMANAGER).
|
||||
pub(crate) pc_manager: crate::ui::window::pc_manager::PcManager,
|
||||
/// Which viewport background the colour wheel is editing, or `None` when
|
||||
/// it is closed. One slot, because only one wheel can be open at a time.
|
||||
pub(crate) bg_picker: Option<BgTarget>,
|
||||
@@ -3845,6 +3847,8 @@ pub enum Message {
|
||||
PdfImportPick,
|
||||
/// An edit in one of the PDF dialogs.
|
||||
PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg),
|
||||
/// A click or search in the Point Cloud Manager.
|
||||
PcManager(crate::ui::window::pc_manager::PcManagerMsg),
|
||||
PdfImportPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>),
|
||||
/// Result of the PDFATTACH file picker.
|
||||
PdfAttachPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>),
|
||||
@@ -4160,6 +4164,7 @@ impl OpenCADStudio {
|
||||
graph_undo_open: false,
|
||||
show_external_references: false,
|
||||
show_browser: false,
|
||||
pc_manager: Default::default(),
|
||||
bg_picker: None,
|
||||
block_palette: Default::default(),
|
||||
xref_manager: Default::default(),
|
||||
|
||||
@@ -608,6 +608,9 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
PanelId::NodeGraph => {
|
||||
self.show_node_graph = false;
|
||||
}
|
||||
PanelId::PointCloudManager => {
|
||||
self.pc_manager.show = false;
|
||||
}
|
||||
PanelId::Properties => {
|
||||
self.show_properties = false;
|
||||
self.ribbon.set_properties(false);
|
||||
@@ -712,6 +715,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
PanelId::ExternalReferences => self.show_external_references,
|
||||
PanelId::Browser => self.show_browser,
|
||||
PanelId::NodeGraph => self.show_node_graph,
|
||||
PanelId::PointCloudManager => self.pc_manager.show,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1806,6 +1806,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
Message::PdfDialog(message) => self.update_pdf_dialog(message),
|
||||
Message::PcManager(message) => self.update_pc_manager(message),
|
||||
Message::RibbonSelectTab(idx) => {
|
||||
self.ribbon.select(idx);
|
||||
Task::none()
|
||||
|
||||
@@ -1870,6 +1870,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
crate::ui::dock::PanelId::ExternalReferences => self.show_external_references,
|
||||
crate::ui::dock::PanelId::Browser => self.show_browser,
|
||||
crate::ui::dock::PanelId::NodeGraph => self.show_node_graph,
|
||||
crate::ui::dock::PanelId::PointCloudManager => self.pc_manager.show,
|
||||
}
|
||||
};
|
||||
let edge_stack = |side: crate::app::config::DockSide| -> Option<Element<'_, Message>> {
|
||||
@@ -2994,6 +2995,13 @@ impl OpenCADStudio {
|
||||
auto_collapse,
|
||||
),
|
||||
crate::ui::dock::PanelId::NodeGraph => tab.graph.panel(width, auto_collapse),
|
||||
crate::ui::dock::PanelId::PointCloudManager => crate::ui::window::pc_manager::view(
|
||||
&self.pc_manager,
|
||||
&tab.scene.document,
|
||||
&tab.scene.selected_handles_in_order(),
|
||||
width,
|
||||
auto_collapse,
|
||||
),
|
||||
};
|
||||
let divider = dock_divider(id);
|
||||
match side {
|
||||
|
||||
@@ -134,6 +134,14 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"A color scheme with this name already exists." => Some(("insert", "pcm-name-taken")),
|
||||
"Invalid range of colorized points." => Some(("insert", "pcm-bad-range")),
|
||||
"Sets the color schemes and ranges a point cloud is colored by intensity or elevation." => Some(("insert", "pcm-help")),
|
||||
"Point Cloud Manager" => Some(("insert", "pc-manager")),
|
||||
"REGIONS AND SCANS" => Some(("insert", "pc-regions-scans")),
|
||||
"Regions" => Some(("insert", "pc-regions")),
|
||||
"Unassigned Points" => Some(("insert", "pc-unassigned")),
|
||||
"Scans" => Some(("insert", "pc-scans")),
|
||||
"Search" => Some(("insert", "pc-search")),
|
||||
"Collapse all" => Some(("insert", "pc-collapse-all")),
|
||||
"Expand all" => Some(("insert", "pc-expand-all")),
|
||||
"Stylization" => Some(("properties", "stylization")),
|
||||
"Color scheme" => Some(("properties", "color-scheme")),
|
||||
"Scan Colors" => Some(("properties", "scan-colors")),
|
||||
|
||||
+7
-1
@@ -8403,7 +8403,13 @@ impl Scene {
|
||||
common.color.clone()
|
||||
};
|
||||
let object_color = color.rgb().map_or([255; 3], |(r, g, b)| [r, g, b]);
|
||||
clouds.extend(point_cloud::placed(&self.document, data, object_color, transform));
|
||||
clouds.extend(point_cloud::placed(
|
||||
&self.document,
|
||||
data,
|
||||
object_color,
|
||||
transform,
|
||||
&point_cloud::hidden(common),
|
||||
));
|
||||
},
|
||||
);
|
||||
if clouds.is_empty() {
|
||||
|
||||
@@ -31,6 +31,8 @@ pub struct CloudPoints {
|
||||
pub has_normals: bool,
|
||||
/// Scans read (one for a scan file).
|
||||
pub scans: usize,
|
||||
/// Each scan's name (its file stem) and its points' index range.
|
||||
pub scan_ranges: Vec<(String, std::ops::Range<usize>)>,
|
||||
/// A project's preview picture (JPEG).
|
||||
pub preview: Option<Vec<u8>>,
|
||||
/// Bounds from the scans' headers, placed like their points: the
|
||||
@@ -70,18 +72,19 @@ fn decode(path: &Path) -> Option<CloudPoints> {
|
||||
.is_some_and(|e| e.eq_ignore_ascii_case("rcp"));
|
||||
// A project lists its scans with their own transforms; a scan file
|
||||
// carries the same transform in its header.
|
||||
let scans: Vec<(rcs::Scan, [[f64; 3]; 3])> = if is_project {
|
||||
let stem = |p: &Path| p.file_stem().map_or_else(String::new, |s| s.to_string_lossy().into_owned());
|
||||
let scans: Vec<(rcs::Scan, [[f64; 3]; 3], String)> = if is_project {
|
||||
rcp::scans(&bytes, path)?
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
let scan = rcs::decode(&std::fs::read(&entry.path).ok()?)?;
|
||||
Some((scan, [entry.translation, entry.rotation, entry.scale]))
|
||||
Some((scan, [entry.translation, entry.rotation, entry.scale], stem(&entry.path)))
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
let scan = rcs::decode(&bytes)?;
|
||||
let transform = [scan.translation, scan.rotation, scan.scale];
|
||||
vec![(scan, transform)]
|
||||
vec![(scan, transform, stem(path))]
|
||||
};
|
||||
let mut cloud = CloudPoints {
|
||||
id: NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
|
||||
@@ -89,21 +92,24 @@ fn decode(path: &Path) -> Option<CloudPoints> {
|
||||
colors: Vec::new(),
|
||||
intensity: Vec::new(),
|
||||
normals: Vec::new(),
|
||||
has_rgb: scans.iter().any(|(scan, _)| scan.has_rgb),
|
||||
has_intensity: scans.iter().any(|(scan, _)| scan.has_intensity),
|
||||
has_normals: scans.iter().any(|(scan, _)| scan.has_normals),
|
||||
has_rgb: scans.iter().any(|(scan, ..)| scan.has_rgb),
|
||||
has_intensity: scans.iter().any(|(scan, ..)| scan.has_intensity),
|
||||
has_normals: scans.iter().any(|(scan, ..)| scan.has_normals),
|
||||
scans: scans.len(),
|
||||
scan_ranges: Vec::new(),
|
||||
preview: is_project.then(|| rcp::preview(&bytes)).flatten(),
|
||||
bounds: None,
|
||||
};
|
||||
for (scan, [translation, rotation, scale]) in scans {
|
||||
for (scan, [translation, rotation, scale], name) in scans {
|
||||
let rotate = euler(rotation.map(f64::to_radians));
|
||||
let place = |p: &[f64; 3]| {
|
||||
let s = [p[0] * scale[0], p[1] * scale[1], p[2] * scale[2]];
|
||||
let r = rotate.map(|row| row[0] * s[0] + row[1] * s[1] + row[2] * s[2]);
|
||||
[r[0] + translation[0], r[1] + translation[1], r[2] + translation[2]]
|
||||
};
|
||||
let first = cloud.positions.len();
|
||||
cloud.positions.extend(scan.local.iter().map(place));
|
||||
cloud.scan_ranges.push((name, first..cloud.positions.len()));
|
||||
let [lo, hi] = scan.bounds;
|
||||
for corner in 0..8 {
|
||||
let p = place(&[
|
||||
@@ -249,16 +255,46 @@ pub(crate) fn any_definition(document: &CadDocument) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
/// What the Point Cloud Manager turned off on a cloud: extended data under
|
||||
/// this application, one string per hidden scan plus the markers below.
|
||||
pub const VISIBILITY_APP: &str = "OCS_POINTCLOUD_VISIBILITY";
|
||||
/// The whole cloud is off.
|
||||
pub const CLOUD_OFF: &str = "*CLOUD_OFF";
|
||||
/// Its unassigned points (every point of a cloud without regions) are off.
|
||||
pub const UNASSIGNED_OFF: &str = "*UNASSIGNED_OFF";
|
||||
|
||||
/// The scans and markers a cloud entity hides.
|
||||
pub(crate) fn hidden(common: &codec::entities::EntityCommon) -> Vec<String> {
|
||||
common
|
||||
.extended_data
|
||||
.records()
|
||||
.iter()
|
||||
.filter(|r| r.application_name.eq_ignore_ascii_case(VISIBILITY_APP))
|
||||
.flat_map(|r| r.values.iter())
|
||||
.filter_map(|v| match v {
|
||||
codec::xdata::XDataValue::String(s) => Some(s.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The points `data` shows, placed through its origin and axes and then
|
||||
/// `transform` (a block instance), with its crops applied. `None` when the
|
||||
/// scan file is missing or unreadable.
|
||||
/// `transform` (a block instance), with its crops applied and the `hidden`
|
||||
/// scans left out. `None` when the scan file is missing or unreadable.
|
||||
pub(crate) fn placed(
|
||||
document: &CadDocument,
|
||||
data: &PointCloudExData,
|
||||
object_color: [u8; 3],
|
||||
transform: Option<&Transform>,
|
||||
hidden: &[String],
|
||||
) -> Option<Arc<PlacedCloud>> {
|
||||
let cloud = load(&resolve_source(document, data)?)?;
|
||||
let skipped: Vec<std::ops::Range<usize>> =
|
||||
if hidden.iter().any(|h| h == CLOUD_OFF || h == UNASSIGNED_OFF) {
|
||||
vec![0..cloud.positions.len()]
|
||||
} else {
|
||||
cloud.scan_ranges.iter().filter(|(name, _)| hidden.contains(name)).map(|(_, r)| r.clone()).collect()
|
||||
};
|
||||
// The points shown: POINTCLOUDDENSITY per cent of POINTCLOUDPOINTMAX,
|
||||
// in tenths by POINTCLOUDLOD, every n-th point of a larger cloud.
|
||||
let shown = |name| setting(name).map_or(0, |s| setting_value(document, s));
|
||||
@@ -298,6 +334,7 @@ pub(crate) fn placed(
|
||||
}
|
||||
step.hash(&mut h);
|
||||
style.hash(&mut h);
|
||||
skipped.hash(&mut h);
|
||||
let key = h.finish();
|
||||
|
||||
type Placed = Mutex<FxHashMap<u64, Weak<PlacedCloud>>>;
|
||||
@@ -369,7 +406,7 @@ pub(crate) fn placed(
|
||||
};
|
||||
let mut instances = Vec::with_capacity(cloud.positions.len() / step + 1);
|
||||
for (i, p) in cloud.positions.iter().enumerate().step_by(step) {
|
||||
if !crops.iter().all(|crop| keeps(crop, p)) {
|
||||
if skipped.iter().any(|r| r.contains(&i)) || !crops.iter().all(|crop| keeps(crop, p)) {
|
||||
continue;
|
||||
}
|
||||
let w = world(p);
|
||||
|
||||
@@ -46,6 +46,8 @@ pub enum PanelId {
|
||||
Browser,
|
||||
/// Node library of the node graph overlay.
|
||||
NodeGraph,
|
||||
/// Regions and scans of the drawing's point clouds.
|
||||
PointCloudManager,
|
||||
}
|
||||
|
||||
impl PanelId {
|
||||
@@ -57,6 +59,7 @@ impl PanelId {
|
||||
PanelId::ExternalReferences => "External References",
|
||||
PanelId::Browser => "Browser",
|
||||
PanelId::NodeGraph => "Node Graph",
|
||||
PanelId::PointCloudManager => "Point Cloud Manager",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +71,7 @@ impl PanelId {
|
||||
PanelId::ExternalReferences => 460.0,
|
||||
PanelId::Browser => 230.0,
|
||||
PanelId::NodeGraph => 220.0,
|
||||
PanelId::PointCloudManager => 280.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +161,7 @@ impl DockState {
|
||||
PanelId::ExternalReferences,
|
||||
PanelId::Browser,
|
||||
PanelId::NodeGraph,
|
||||
PanelId::PointCloudManager,
|
||||
] {
|
||||
self.panels.entry(id).or_insert_with(|| DockPanel::for_id(id));
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ pub mod annotation_data;
|
||||
pub mod alias_editor;
|
||||
pub mod find_replace;
|
||||
pub mod named_parameters;
|
||||
pub mod pc_manager;
|
||||
pub mod pdf_dialogs;
|
||||
pub mod xref_attach;
|
||||
pub mod xref_help;
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
//! Point Cloud Manager (POINTCLOUDMANAGER): the regions and scans of the
|
||||
//! drawing's point clouds as a tree, each with a switch that shows or hides
|
||||
//! its points. The switches are the cloud's own visibility extended data, so
|
||||
//! the tree is read straight from the document on every view.
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::scene::model::point_cloud::{self, CLOUD_OFF, UNASSIGNED_OFF};
|
||||
use crate::ui::dock::PanelId;
|
||||
use codec::entities::ExtendedEntityData;
|
||||
use codec::{CadDocument, EntityType, Handle};
|
||||
use iced::widget::{button, checkbox, column, container, mouse_area, row, scrollable, text, text_input, tooltip, Space};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
use std::collections::HashSet;
|
||||
|
||||
const CLOUD_ICON: &[u8] = include_bytes!("../../../assets/icons/pc_attach.svg");
|
||||
const REGION_ICON: &[u8] = include_bytes!("../../../assets/icons/region.svg");
|
||||
const UNASSIGNED_ICON: &[u8] = include_bytes!("../../../assets/icons/revcloud.svg");
|
||||
|
||||
/// Palette state that is not in the drawing: shown, folded nodes, the
|
||||
/// highlighted row and the search text.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PcManager {
|
||||
pub show: bool,
|
||||
pub collapsed: HashSet<String>,
|
||||
pub selected: Option<String>,
|
||||
pub search: String,
|
||||
}
|
||||
|
||||
/// A row with a switch.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Row {
|
||||
Cloud,
|
||||
Unassigned,
|
||||
Scans,
|
||||
Scan(String),
|
||||
}
|
||||
|
||||
impl Row {
|
||||
/// `cloud`, `unassigned`, `scans` or `scan:<name>` (automation).
|
||||
pub fn parse(text: &str) -> Option<Self> {
|
||||
match text {
|
||||
"cloud" => Some(Self::Cloud),
|
||||
"unassigned" => Some(Self::Unassigned),
|
||||
"scans" => Some(Self::Scans),
|
||||
_ => text.strip_prefix("scan:").map(|name| Self::Scan(name.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PcManagerMsg {
|
||||
Toggle(Handle, Row),
|
||||
Expand(String),
|
||||
Select(String),
|
||||
Search(String),
|
||||
CollapseAll,
|
||||
ExpandAll,
|
||||
}
|
||||
|
||||
/// One cloud of the tree.
|
||||
pub struct Cloud {
|
||||
pub handle: Handle,
|
||||
pub name: String,
|
||||
pub hidden: Vec<String>,
|
||||
pub scans: Vec<String>,
|
||||
}
|
||||
|
||||
/// The drawing's point clouds; only the selected ones when any is selected.
|
||||
pub fn clouds(document: &CadDocument, selected: &[Handle]) -> Vec<Cloud> {
|
||||
let all: Vec<Cloud> = document
|
||||
.entities()
|
||||
.filter_map(|entity| {
|
||||
let EntityType::Extended(extended) = entity else {
|
||||
return None;
|
||||
};
|
||||
let ExtendedEntityData::PointCloudEx(data) = &extended.data else {
|
||||
return None;
|
||||
};
|
||||
let common = entity.common();
|
||||
let scans = point_cloud::resolve_source(document, data)
|
||||
.and_then(|path| point_cloud::load(&path))
|
||||
.map(|cloud| cloud.scan_ranges.iter().map(|(name, _)| name.clone()).collect())
|
||||
.unwrap_or_default();
|
||||
Some(Cloud {
|
||||
handle: common.handle,
|
||||
name: if data.name.is_empty() { format!("{:X}", common.handle.value()) } else { data.name.clone() },
|
||||
hidden: point_cloud::hidden(common),
|
||||
scans,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
if all.iter().any(|cloud| selected.contains(&cloud.handle)) {
|
||||
all.into_iter().filter(|cloud| selected.contains(&cloud.handle)).collect()
|
||||
} else {
|
||||
all
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a row's switch is on. A parent is on while any child is.
|
||||
pub fn is_on(hidden: &[String], scans: &[String], row: &Row) -> bool {
|
||||
let off = |name: &str| hidden.iter().any(|h| h == name);
|
||||
let any_scan = scans.is_empty() || scans.iter().any(|s| !off(s));
|
||||
match row {
|
||||
Row::Scan(name) => !off(name),
|
||||
Row::Scans => any_scan,
|
||||
Row::Unassigned => !off(UNASSIGNED_OFF),
|
||||
Row::Cloud => !off(CLOUD_OFF) && (!off(UNASSIGNED_OFF) || any_scan),
|
||||
}
|
||||
}
|
||||
|
||||
/// The hidden list after flipping `row`. Switching the cloud back on shows
|
||||
/// everything under it.
|
||||
pub fn toggled(hidden: &[String], scans: &[String], row: &Row) -> Vec<String> {
|
||||
fn set(out: &mut Vec<String>, name: &str, off: bool) {
|
||||
out.retain(|h| h != name);
|
||||
if off {
|
||||
out.push(name.to_string());
|
||||
}
|
||||
}
|
||||
let on = is_on(hidden, scans, row);
|
||||
let mut out = hidden.to_vec();
|
||||
match row {
|
||||
Row::Cloud if on => set(&mut out, CLOUD_OFF, true),
|
||||
Row::Cloud => out.clear(),
|
||||
Row::Unassigned => set(&mut out, UNASSIGNED_OFF, on),
|
||||
Row::Scans => scans.iter().for_each(|s| set(&mut out, s, on)),
|
||||
Row::Scan(name) => set(&mut out, name, on),
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Keys of the rows that fold: each cloud and its Scans.
|
||||
pub fn folding_keys(clouds: &[Cloud]) -> Vec<String> {
|
||||
clouds.iter().flat_map(|c| [key(c.handle), format!("{}/scans", key(c.handle))]).collect()
|
||||
}
|
||||
|
||||
fn key(handle: Handle) -> String {
|
||||
format!("{:X}", handle.value())
|
||||
}
|
||||
|
||||
fn msg(m: PcManagerMsg) -> Message {
|
||||
Message::PcManager(m)
|
||||
}
|
||||
|
||||
/// One tree row: indent, fold arrow, icon, label and switch (`None` for a
|
||||
/// row that has none to give, drawn off and disabled).
|
||||
fn tree_row<'a>(
|
||||
state: &PcManager,
|
||||
key: String,
|
||||
depth: u16,
|
||||
fold: Option<bool>,
|
||||
icon: Element<'a, Message>,
|
||||
label: String,
|
||||
switch: Option<(bool, Message)>,
|
||||
) -> Element<'a, Message> {
|
||||
let arrow: Element<'a, Message> = match fold {
|
||||
Some(open) => button(if open {
|
||||
crate::ui::icons::themed_arrow_down(10.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_arrow_right(10.0)
|
||||
})
|
||||
.on_press(msg(PcManagerMsg::Expand(key.clone())))
|
||||
.style(button::text)
|
||||
.padding(2)
|
||||
.into(),
|
||||
None => Space::new().width(14).into(),
|
||||
};
|
||||
let enabled = switch.is_some();
|
||||
let label = text(label).size(12).width(Fill);
|
||||
let label = if enabled { label } else { label.style(crate::ui::style::common::muted_style) };
|
||||
let switch: Element<'a, Message> = match switch {
|
||||
Some((on, message)) => checkbox(on).on_toggle(move |_| message.clone()).size(14).into(),
|
||||
None => checkbox(false).size(14).into(),
|
||||
};
|
||||
let selected = state.selected.as_deref() == Some(key.as_str());
|
||||
let cells = row![Space::new().width(Length::Fixed(f32::from(depth) * 16.0)), arrow, icon, label, switch]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center);
|
||||
mouse_area(
|
||||
container(cells)
|
||||
.width(Fill)
|
||||
.padding([3, 6])
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: selected.then(|| Background::Color(theme.palette().primary.weak.color)),
|
||||
text_color: selected.then(|| theme.palette().primary.weak.text),
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
.on_press(msg(PcManagerMsg::Select(key)))
|
||||
.into()
|
||||
}
|
||||
|
||||
fn fold_button<'a>(icon: &'static [u8], tip: String, message: PcManagerMsg) -> Element<'a, Message> {
|
||||
let b = button(crate::ui::icons::themed_secondary(icon, 12.0))
|
||||
.on_press(msg(message))
|
||||
.style(button::subtle)
|
||||
.padding([2, 4]);
|
||||
tooltip(b, text(tip).size(10), tooltip::Position::Bottom).gap(4).into()
|
||||
}
|
||||
|
||||
pub fn view<'a>(
|
||||
state: &'a PcManager,
|
||||
document: &'a CadDocument,
|
||||
selected: &[Handle],
|
||||
width: f32,
|
||||
auto_collapse: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let title_bar =
|
||||
crate::ui::dock::title_bar(PanelId::PointCloudManager, crate::t!("Point Cloud Manager").into_owned(), auto_collapse);
|
||||
let query = state.search.trim().to_lowercase();
|
||||
let matches = |label: &str| query.is_empty() || label.to_lowercase().contains(&query);
|
||||
// Searching opens every node so the matches show.
|
||||
let open = |key: &str| !query.is_empty() || !state.collapsed.contains(key);
|
||||
let regions = crate::t!("Regions").into_owned();
|
||||
let unassigned = crate::t!("Unassigned Points").into_owned();
|
||||
let scans_label = crate::t!("Scans").into_owned();
|
||||
|
||||
let mut tree = column![].spacing(1);
|
||||
for cloud in clouds(document, selected) {
|
||||
let root = key(cloud.handle);
|
||||
let scans: Vec<&String> = cloud.scans.iter().filter(|s| matches(s)).collect();
|
||||
let show_scans = matches(&scans_label) || !scans.is_empty();
|
||||
let children = matches(®ions) || matches(&unassigned) || show_scans;
|
||||
if !matches(&cloud.name) && !children {
|
||||
continue;
|
||||
}
|
||||
let switch = |row: Row| {
|
||||
let on = is_on(&cloud.hidden, &cloud.scans, &row);
|
||||
Some((on, msg(PcManagerMsg::Toggle(cloud.handle, row))))
|
||||
};
|
||||
let cloud_icon = || crate::ui::icons::semantic(CLOUD_ICON, 14.0);
|
||||
tree = tree.push(tree_row(
|
||||
state,
|
||||
root.clone(),
|
||||
0,
|
||||
Some(open(&root)),
|
||||
cloud_icon(),
|
||||
cloud.name.clone(),
|
||||
switch(Row::Cloud),
|
||||
));
|
||||
if !open(&root) {
|
||||
continue;
|
||||
}
|
||||
if matches(®ions) {
|
||||
// Region data is not read: the row is there, off and disabled.
|
||||
tree = tree.push(tree_row(
|
||||
state,
|
||||
format!("{root}/regions"),
|
||||
1,
|
||||
None,
|
||||
crate::ui::icons::semantic_disabled(REGION_ICON, 14.0),
|
||||
regions.clone(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
if matches(&unassigned) {
|
||||
tree = tree.push(tree_row(
|
||||
state,
|
||||
format!("{root}/unassigned"),
|
||||
1,
|
||||
None,
|
||||
crate::ui::icons::semantic(UNASSIGNED_ICON, 14.0),
|
||||
unassigned.clone(),
|
||||
switch(Row::Unassigned),
|
||||
));
|
||||
}
|
||||
if show_scans {
|
||||
let scans_key = format!("{root}/scans");
|
||||
let scans_open = open(&scans_key);
|
||||
tree = tree.push(tree_row(
|
||||
state,
|
||||
scans_key.clone(),
|
||||
1,
|
||||
Some(scans_open),
|
||||
cloud_icon(),
|
||||
scans_label.clone(),
|
||||
switch(Row::Scans),
|
||||
));
|
||||
if scans_open {
|
||||
for name in scans {
|
||||
tree = tree.push(tree_row(
|
||||
state,
|
||||
format!("{scans_key}/{name}"),
|
||||
2,
|
||||
None,
|
||||
cloud_icon(),
|
||||
name.clone(),
|
||||
switch(Row::Scan(name.clone())),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let heading = text(crate::t!("REGIONS AND SCANS").into_owned())
|
||||
.size(11)
|
||||
.font(iced::Font {
|
||||
weight: iced::font::Weight::Bold,
|
||||
style: iced::font::Style::Italic,
|
||||
..iced::Font::DEFAULT
|
||||
})
|
||||
.style(|theme: &Theme| text::Style { color: Some(theme.palette().primary.base.color) })
|
||||
.width(Fill);
|
||||
let header = row![
|
||||
heading,
|
||||
fold_button(crate::ui::icons::MINUS, crate::t!("Collapse all").into_owned(), PcManagerMsg::CollapseAll),
|
||||
fold_button(crate::ui::icons::PLUS, crate::t!("Expand all").into_owned(), PcManagerMsg::ExpandAll),
|
||||
]
|
||||
.spacing(2)
|
||||
.align_y(iced::Center);
|
||||
let card = container(column![header, scrollable(tree).height(Fill)].spacing(6))
|
||||
.padding(6)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
..Default::default()
|
||||
});
|
||||
let search = text_input(&crate::t!("Search"), &state.search)
|
||||
.on_input(|v| msg(PcManagerMsg::Search(v)))
|
||||
.padding([4, 8])
|
||||
.size(12);
|
||||
|
||||
crate::ui::dock::frame(column![title_bar, card, search].spacing(6), width)
|
||||
}
|
||||
Reference in new issue
Block a user