forked from 1b-agent/opencadstudio-upstream
Point clouds: POINTCLOUDCOLORMAP dialog, out-of-range colouring
POINTCLOUDCOLORMAP asks for a cloud (or None for the drawing's default schemes) and opens the Point Cloud Color Map dialog: intensity and elevation tabs with the scheme list, New / Delete / Rename, number of colours (2-25, resampled along the scheme), blend between the ends, reverse, gradient or bands, the colorized range, the out-of-range choice and, for elevation, the cloud's extents or a fixed range with its band height. OK / Apply write the drawing's colour map (created under ACAD_POINTCLOUD_COLORMAP_DICT when missing) and the cloud's colouring in one undo step; Make stylization current sets the tab's stylization. Values outside a scheme's range take the end colours, the scan colours or are hidden. Automation action pc_colormap edits the dialog. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
7761652a9f
commit
2eaae1ecab
32 files changed
+1449
-21
No files matched your search
@@ -15,6 +15,7 @@ mod layerprops;
|
||||
mod layers;
|
||||
pub(crate) mod pdf_import;
|
||||
mod point_cloud;
|
||||
mod pc_colormap;
|
||||
mod pc_extract;
|
||||
mod pdf_underlay;
|
||||
mod pdf_dialogs;
|
||||
@@ -271,6 +272,9 @@ impl OpenCADStudio {
|
||||
if let Some(t) = self.dispatch_pdf_underlay(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
if let Some(t) = self.dispatch_pc_colormap(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
if let Some(t) = self.dispatch_pc_extract(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
//! POINTCLOUDCOLORMAP: the Point Cloud Color Map dialog for a chosen cloud
|
||||
//! (or the drawing's default schemes), and what OK / Apply write — the
|
||||
//! drawing's colour map (its schemes) and the cloud's intensity and
|
||||
//! elevation colouring — as one undo step.
|
||||
|
||||
use super::*;
|
||||
use codec::entities::{ExtendedEntityData, PointCloudExData};
|
||||
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};
|
||||
|
||||
const MAP_DICTIONARY: &str = "ACAD_POINTCLOUD_COLORMAP_DICT";
|
||||
const MAP_ENTRY: &str = "ACAD_POINTCLOUD_COLORMAP";
|
||||
/// The classification scheme the colour map names by default.
|
||||
const CLASSIFICATION_SCHEME: &str = "8AF59545-D22F-46B7-923C-912F71ED57DE";
|
||||
|
||||
fn cloud_data(document: &codec::CadDocument, handle: Handle) -> Option<&PointCloudExData> {
|
||||
match document.get_entity(handle)? {
|
||||
codec::EntityType::Extended(extended) => match &extended.data {
|
||||
ExtendedEntityData::PointCloudEx(data) => Some(data),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
});
|
||||
if existing.is_some() {
|
||||
return existing;
|
||||
}
|
||||
let root = document.header.named_objects_dict_handle;
|
||||
let ObjectType::Dictionary(root_dictionary) = document.objects.get(&root)? else {
|
||||
return None;
|
||||
};
|
||||
let dictionary = match root_dictionary.get(MAP_DICTIONARY) {
|
||||
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));
|
||||
if let Some(ObjectType::Dictionary(root_dictionary)) = document.objects.get_mut(&root) {
|
||||
root_dictionary.add_entry(MAP_DICTIONARY, handle);
|
||||
}
|
||||
handle
|
||||
}
|
||||
};
|
||||
let handle = document.allocate_handle();
|
||||
let ramps = crate::scene::model::point_cloud::schemes(document)
|
||||
.into_iter()
|
||||
.map(|(id, name, colors)| to_codec_ramp(&MapRamp { id, name, colors }))
|
||||
.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_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));
|
||||
if let Some(ObjectType::Dictionary(dictionary)) = document.objects.get_mut(&dictionary) {
|
||||
dictionary.add_entry(MAP_ENTRY, handle);
|
||||
}
|
||||
Some(handle)
|
||||
}
|
||||
|
||||
/// A scheme as the colour map stores it: true colours, all shown.
|
||||
fn to_codec_ramp(ramp: &MapRamp) -> PointCloudColorRamp {
|
||||
PointCloudColorRamp {
|
||||
id: ramp.id.clone(),
|
||||
class_version: 1,
|
||||
colors: ramp
|
||||
.colors
|
||||
.iter()
|
||||
.map(|[r, g, b]| PointCloudRampColor {
|
||||
color: (0xC2u32 << 24 | u32::from(*r) << 16 | u32::from(*g) << 8 | u32::from(*b)) as i32,
|
||||
visible: true,
|
||||
})
|
||||
.collect(),
|
||||
name: ramp.name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A new scheme identifier: a random GUID in the stored form.
|
||||
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])
|
||||
}
|
||||
|
||||
/// `count` colours taken evenly along a scheme's blend.
|
||||
fn resample(colors: &[[u8; 3]], count: usize) -> Vec<[u8; 3]> {
|
||||
let count = count.clamp(2, 25);
|
||||
(0..count)
|
||||
.map(|k| crate::ui::window::pdf_dialogs::blend(colors, k as f64 / (count - 1) as f64))
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// POINTCLOUDCOLORMAP after the prompt: the dialog for `cloud`, or for
|
||||
/// the drawing's default schemes.
|
||||
pub(in crate::app) fn open_point_cloud_color_map(&mut self, i: usize, cloud: Option<Handle>) {
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let ramps: Vec<MapRamp> = crate::scene::model::point_cloud::schemes(document)
|
||||
.into_iter()
|
||||
.map(|(id, name, colors)| MapRamp { id, name, colors })
|
||||
.collect();
|
||||
let defaults = crate::scene::model::point_cloud::default_schemes(document);
|
||||
let data = cloud.and_then(|handle| cloud_data(document, handle));
|
||||
let height = data
|
||||
.map(|data| {
|
||||
let corners = codec::entities::point_cloud_ex_corners(data);
|
||||
let low = corners.iter().map(|c| c.z).fold(f64::MAX, f64::min);
|
||||
let high = corners.iter().map(|c| c.z).fold(f64::MIN, f64::max);
|
||||
(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 (intensity, elevation, extents, elevation_tab) = match data {
|
||||
Some(data) => (
|
||||
slot(
|
||||
&data.intensity_color_scheme,
|
||||
&defaults.0,
|
||||
data.intensity_as_gradient,
|
||||
data.intensity_max.to_string(),
|
||||
data.intensity_min.to_string(),
|
||||
data.intensity_out_of_range_behavior,
|
||||
),
|
||||
slot(
|
||||
&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 }),
|
||||
data.elevation_out_of_range_behavior,
|
||||
),
|
||||
!data.elevation_apply_to_fixed_range,
|
||||
data.stylization_type == 4,
|
||||
),
|
||||
None => (
|
||||
slot("", &defaults.0, true, String::new(), String::new(), 1),
|
||||
slot("", &defaults.1, false, String::new(), String::new(), 1),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
};
|
||||
let mut state = PointCloudColorMapState {
|
||||
cloud: cloud.filter(|_| data.is_some()),
|
||||
elevation_tab,
|
||||
ramps,
|
||||
intensity,
|
||||
elevation,
|
||||
extents,
|
||||
height: (clean(height.0), clean(height.1)),
|
||||
make_current: true,
|
||||
interval: String::new(),
|
||||
naming: None,
|
||||
};
|
||||
refresh_interval(&mut state);
|
||||
self.point_cloud_color_map = Some(state);
|
||||
self.active_modal = Some(crate::app::ModalKind::PointCloudColorMap);
|
||||
}
|
||||
|
||||
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();
|
||||
};
|
||||
match message {
|
||||
PdfDialogMsg::MapTab(elevation) => {
|
||||
state.elevation_tab = elevation;
|
||||
state.naming = None;
|
||||
refresh_interval(state);
|
||||
}
|
||||
PdfDialogMsg::MapScheme(name) => {
|
||||
if let Some(ramp) = state.ramps.iter().find(|r| r.name == name) {
|
||||
state.slot_mut().scheme = ramp.id.clone();
|
||||
refresh_interval(state);
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::MapCount(count) => {
|
||||
if let Some(ramp) = state.ramp_mut() {
|
||||
ramp.colors = resample(&ramp.colors, count);
|
||||
}
|
||||
refresh_interval(state);
|
||||
}
|
||||
// The colours between the two ends, blended evenly.
|
||||
PdfDialogMsg::MapEven => {
|
||||
if let Some(ramp) = state.ramp_mut() {
|
||||
let (first, last) = (ramp.colors[0], ramp.colors[ramp.colors.len() - 1]);
|
||||
ramp.colors = resample(&[first, last], ramp.colors.len());
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::MapReverse => {
|
||||
if let Some(ramp) = state.ramp_mut() {
|
||||
ramp.colors.reverse();
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::MapGradient(on) => state.slot_mut().gradient = on,
|
||||
PdfDialogMsg::MapNew => {
|
||||
let name = state.ramp().map(|r| format!("{} (2)", r.name)).unwrap_or_default();
|
||||
state.naming = Some((true, name));
|
||||
}
|
||||
PdfDialogMsg::MapRename => {
|
||||
let name = state.ramp().map(|r| r.name.clone()).unwrap_or_default();
|
||||
state.naming = Some((false, name));
|
||||
}
|
||||
PdfDialogMsg::MapNameInput(value) => {
|
||||
if let Some((_, name)) = state.naming.as_mut() {
|
||||
*name = value;
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::MapNameCancel => state.naming = None,
|
||||
PdfDialogMsg::MapNameOk => {
|
||||
let Some((new, name)) = state.naming.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
let name = name.trim().to_string();
|
||||
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)));
|
||||
return Task::none();
|
||||
}
|
||||
if new {
|
||||
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.slot_mut().scheme = id;
|
||||
} else if let Some(ramp) = state.ramp_mut() {
|
||||
ramp.name = name;
|
||||
}
|
||||
}
|
||||
// The last scheme stays; a slot naming a deleted one takes the first.
|
||||
PdfDialogMsg::MapDelete => {
|
||||
if state.ramps.len() > 1 {
|
||||
let id = state.ramp().map(|r| r.id.clone()).unwrap_or_default();
|
||||
state.ramps.retain(|r| r.id != id);
|
||||
let first = state.ramps[0].id.clone();
|
||||
for slot in [&mut state.intensity, &mut state.elevation] {
|
||||
if slot.scheme.eq_ignore_ascii_case(&id) {
|
||||
slot.scheme = first.clone();
|
||||
}
|
||||
}
|
||||
refresh_interval(state);
|
||||
}
|
||||
}
|
||||
PdfDialogMsg::MapMax(value) => {
|
||||
state.slot_mut().max = value;
|
||||
refresh_interval(state);
|
||||
}
|
||||
PdfDialogMsg::MapMin(value) => {
|
||||
state.slot_mut().min = value;
|
||||
refresh_interval(state);
|
||||
}
|
||||
// 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>()) {
|
||||
state.elevation.max = short(min + step * count);
|
||||
}
|
||||
state.interval = value;
|
||||
}
|
||||
PdfDialogMsg::MapExtents(on) => {
|
||||
state.extents = on;
|
||||
if on {
|
||||
state.elevation.max = short(state.height.1);
|
||||
state.elevation.min = short(state.height.0);
|
||||
}
|
||||
refresh_interval(state);
|
||||
}
|
||||
PdfDialogMsg::MapOutOfRange(choice) => state.slot_mut().out_of_range = choice,
|
||||
PdfDialogMsg::MapCurrent(on) => state.make_current = on,
|
||||
PdfDialogMsg::MapApply => {
|
||||
self.apply_point_cloud_color_map(i);
|
||||
}
|
||||
PdfDialogMsg::MapOk => {
|
||||
if self.apply_point_cloud_color_map(i) {
|
||||
self.point_cloud_color_map = None;
|
||||
self.active_modal = None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Writes the dialog's schemes and the cloud's colouring as one undo
|
||||
/// step; `false` (with a message) when a range value is not a number.
|
||||
fn apply_point_cloud_color_map(&mut self, i: usize) -> bool {
|
||||
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 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());
|
||||
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);
|
||||
self.push_undo_snapshot(i, "POINTCLOUDCOLORMAP");
|
||||
let document = &mut self.tabs[i].scene.document;
|
||||
if let Some(handle) = ensure_color_map(document) {
|
||||
if let Some(ObjectType::ClassObject(object)) = document.objects.get_mut(&handle) {
|
||||
if let ClassObjectData::PointCloudColorMap(map) = &mut object.data {
|
||||
map.color_ramps = ramps;
|
||||
if cloud.is_none() {
|
||||
map.default_intensity_scheme = intensity_slot.scheme.clone();
|
||||
map.default_elevation_scheme = elevation_slot.scheme.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(handle) = cloud {
|
||||
if let Some(codec::EntityType::Extended(extended)) = document.get_entity_mut(handle) {
|
||||
if let ExtendedEntityData::PointCloudEx(data) = &mut extended.data {
|
||||
data.intensity_color_scheme = intensity_slot.scheme;
|
||||
data.intensity_as_gradient = intensity_slot.gradient;
|
||||
data.intensity_out_of_range_behavior = intensity_slot.out_of_range.0;
|
||||
if let (Ok(lo), Ok(hi)) = intensity {
|
||||
(data.intensity_min, data.intensity_max) = (lo, hi);
|
||||
}
|
||||
data.current_color_scheme = elevation_slot.scheme;
|
||||
data.elevation_as_gradient = elevation_slot.gradient;
|
||||
data.elevation_out_of_range_behavior = elevation_slot.out_of_range.0;
|
||||
data.elevation_apply_to_fixed_range = !extents;
|
||||
if let (false, Ok(lo), Ok(hi)) = (extents, elevation.0, elevation.1) {
|
||||
(data.elevation_min, data.elevation_max) = (lo, hi);
|
||||
}
|
||||
if make_current {
|
||||
data.stylization_type = if elevation_tab { 4 } else { 5 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Every cloud colours from the schemes: redraw them all.
|
||||
let changes: Vec<_> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.filter(|entity| crate::scene::is_point_cloud(entity))
|
||||
.map(|entity| (entity.common().handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn dispatch_pc_colormap(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||
use crate::command::CadCommand;
|
||||
if cmd == "POINTCLOUDCOLORMAP" {
|
||||
let command = crate::modules::insert::pc_stylize::PointCloudColorMapCommand::new();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
let rest = cmd.strip_prefix("_PCCOLORMAP")?;
|
||||
let cloud = u64::from_str_radix(rest.trim(), 16).ok().map(Handle::new);
|
||||
let task = self.finish_dispatch(cmd);
|
||||
self.open_point_cloud_color_map(i, cloud);
|
||||
Some(task)
|
||||
}
|
||||
}
|
||||
|
||||
/// A height with rounding noise dropped (the extents box's -0.0016 reads 0).
|
||||
fn clean(value: f64) -> f64 {
|
||||
(value * 100.0).round() / 100.0
|
||||
}
|
||||
|
||||
fn refresh_interval(state: &mut PointCloudColorMapState) {
|
||||
let count = state.ramp().map_or(1, |r| r.colors.len()).max(1) as f64;
|
||||
let lo = state.elevation.min.trim().parse::<f64>();
|
||||
let hi = state.elevation.max.trim().parse::<f64>();
|
||||
state.interval = match (lo, hi) {
|
||||
(Ok(lo), Ok(hi)) => short((hi - lo) / count),
|
||||
_ => String::new(),
|
||||
};
|
||||
}
|
||||
@@ -378,6 +378,9 @@ impl OpenCADStudio {
|
||||
}
|
||||
"attach" => crate::t!("Attaches a PDF file as an underlay."),
|
||||
"pointcloud" => crate::t!("Attaches a point cloud scan or project."),
|
||||
"pointcloudcolormap" => {
|
||||
crate::t!("Sets the color schemes and ranges a point cloud is colored by intensity or elevation.")
|
||||
}
|
||||
"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."),
|
||||
};
|
||||
@@ -480,6 +483,26 @@ impl OpenCADStudio {
|
||||
// ── Attach Point Cloud ────────────────────────────────────────
|
||||
PdfDialogMsg::CloudBrowse => return Task::done(Message::PointCloudAttachPick),
|
||||
PdfDialogMsg::CloudOk => return self.point_cloud_attach_ok(i),
|
||||
message @ (PdfDialogMsg::MapTab(_)
|
||||
| PdfDialogMsg::MapScheme(_)
|
||||
| PdfDialogMsg::MapCount(_)
|
||||
| PdfDialogMsg::MapEven
|
||||
| PdfDialogMsg::MapReverse
|
||||
| PdfDialogMsg::MapGradient(_)
|
||||
| PdfDialogMsg::MapNew
|
||||
| PdfDialogMsg::MapDelete
|
||||
| PdfDialogMsg::MapRename
|
||||
| PdfDialogMsg::MapNameInput(_)
|
||||
| PdfDialogMsg::MapNameOk
|
||||
| PdfDialogMsg::MapNameCancel
|
||||
| PdfDialogMsg::MapMax(_)
|
||||
| PdfDialogMsg::MapMin(_)
|
||||
| PdfDialogMsg::MapInterval(_)
|
||||
| PdfDialogMsg::MapExtents(_)
|
||||
| PdfDialogMsg::MapOutOfRange(_)
|
||||
| PdfDialogMsg::MapCurrent(_)
|
||||
| PdfDialogMsg::MapApply
|
||||
| PdfDialogMsg::MapOk) => return self.update_point_cloud_color_map(message),
|
||||
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) {
|
||||
|
||||
@@ -72,6 +72,7 @@ pub(super) const NAMES: &[&str] = &[
|
||||
"pdf_dialog_ok",
|
||||
"pdf_layer_toggle",
|
||||
"pdf_page_select",
|
||||
"pc_colormap",
|
||||
"ribbon_tab",
|
||||
"ribbon_dropdown",
|
||||
"dialog_ok",
|
||||
@@ -688,6 +689,36 @@ impl OpenCADStudio {
|
||||
"pdf_layer_toggle" => Message::PdfDialog(
|
||||
crate::ui::window::pdf_dialogs::PdfDialogMsg::LayersToggle(string(req, "value")?.into()),
|
||||
),
|
||||
// Point Cloud Color Map: one edit, "tab=elevation", "count=3",
|
||||
// "even", "reverse", "scheme=Earth", "gradient=0", "max=90",
|
||||
// "min=10", "interval=0.1", "extents=0", "range=2", "current=0".
|
||||
"pc_colormap" => {
|
||||
use crate::ui::window::pdf_dialogs::{OutOfRange, PdfDialogMsg as M};
|
||||
let value = string(req, "value")?;
|
||||
let (key, arg) = value.split_once('=').unwrap_or((value, ""));
|
||||
let on = arg != "0";
|
||||
Message::PdfDialog(match key {
|
||||
"tab" => M::MapTab(arg == "elevation"),
|
||||
"scheme" => M::MapScheme(arg.into()),
|
||||
"count" => M::MapCount(arg.parse().map_err(|_| failure("invalid_value", "count"))?),
|
||||
"even" => M::MapEven,
|
||||
"reverse" => M::MapReverse,
|
||||
"gradient" => M::MapGradient(on),
|
||||
"max" => M::MapMax(arg.into()),
|
||||
"min" => M::MapMin(arg.into()),
|
||||
"interval" => M::MapInterval(arg.into()),
|
||||
"extents" => M::MapExtents(on),
|
||||
"range" => M::MapOutOfRange(OutOfRange(arg.parse().unwrap_or(1))),
|
||||
"current" => M::MapCurrent(on),
|
||||
"new" => M::MapNew,
|
||||
"rename" => M::MapRename,
|
||||
"name" => M::MapNameInput(arg.into()),
|
||||
"name_ok" => M::MapNameOk,
|
||||
"delete" => M::MapDelete,
|
||||
"apply" => M::MapApply,
|
||||
_ => return Err(failure("invalid_value", "Unknown color map edit")),
|
||||
})
|
||||
}
|
||||
// Attach dialog: choose pages by index ("0,2").
|
||||
"pdf_page_select" => {
|
||||
let pages: Vec<usize> = string(req, "value")?
|
||||
@@ -714,6 +745,7 @@ impl OpenCADStudio {
|
||||
Message::PdfDialog(match self.active_modal {
|
||||
Some(crate::app::ModalKind::PdfAttach) => PdfDialogMsg::AttachOk,
|
||||
Some(crate::app::ModalKind::PointCloudAttach) => PdfDialogMsg::CloudOk,
|
||||
Some(crate::app::ModalKind::PointCloudColorMap) => PdfDialogMsg::MapOk,
|
||||
Some(crate::app::ModalKind::UnderlayLayers) => PdfDialogMsg::LayersOk,
|
||||
Some(crate::app::ModalKind::PdfImportSettings) => PdfDialogMsg::SettingsOk,
|
||||
Some(crate::app::ModalKind::PdfImportFile) => PdfDialogMsg::ImportOk,
|
||||
|
||||
@@ -498,6 +498,7 @@ pub(super) struct OpenCADStudio {
|
||||
/// PDF dialogs' working copies; `None` while closed.
|
||||
pdf_attach: Option<crate::ui::window::pdf_dialogs::PdfAttachState>,
|
||||
point_cloud_attach: Option<crate::ui::window::pdf_dialogs::PointCloudAttachState>,
|
||||
point_cloud_color_map: Option<crate::ui::window::pdf_dialogs::PointCloudColorMapState>,
|
||||
underlay_layers: Option<crate::ui::window::pdf_dialogs::UnderlayLayersState>,
|
||||
pdf_import_settings: Option<crate::modules::insert::pdf_import::PdfImportSettings>,
|
||||
pdf_import_file: Option<crate::ui::window::pdf_dialogs::PdfImportFileState>,
|
||||
@@ -1903,6 +1904,7 @@ pub enum ModalKind {
|
||||
BlockDefinition,
|
||||
PdfAttach,
|
||||
PointCloudAttach,
|
||||
PointCloudColorMap,
|
||||
UnderlayLayers,
|
||||
PdfImportSettings,
|
||||
PdfImportFile,
|
||||
@@ -4034,6 +4036,7 @@ impl OpenCADStudio {
|
||||
block_definition: None,
|
||||
pdf_attach: None,
|
||||
point_cloud_attach: None,
|
||||
point_cloud_color_map: None,
|
||||
underlay_layers: None,
|
||||
pdf_import_settings: None,
|
||||
pdf_import_file: None,
|
||||
|
||||
@@ -30,6 +30,7 @@ impl OpenCADStudio {
|
||||
_ => crate::t!("Attach PDF Underlay").into_owned(),
|
||||
},
|
||||
Some(K::PointCloudAttach) => crate::t!("Attach Point Cloud").into_owned(),
|
||||
Some(K::PointCloudColorMap) => crate::t!("Point Cloud Color Map").into_owned(),
|
||||
Some(K::UnderlayLayers) => crate::t!("Underlay Layers").into_owned(),
|
||||
Some(K::PdfImportSettings) => crate::t!("PDF Import Settings").into_owned(),
|
||||
Some(K::PdfImportFile) => crate::t!("Import PDF").into_owned(),
|
||||
@@ -529,6 +530,12 @@ impl OpenCADStudio {
|
||||
crate::ui::window::pdf_dialogs::view_point_cloud_attach(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::PointCloudColorMap => {
|
||||
let state = self.point_cloud_color_map.as_ref()?;
|
||||
sized_flow(ex, 760, 560, |flow| {
|
||||
crate::ui::window::pdf_dialogs::view_point_cloud_color_map(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::UnderlayLayers => {
|
||||
let state = self.underlay_layers.as_ref()?;
|
||||
sized_flow(ex, 460, 520, |flow| {
|
||||
|
||||
Reference in new issue
Block a user