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
@@ -318,7 +318,28 @@ pub(crate) fn placed(
|
||||
(lo.min(z), hi.max(z))
|
||||
})
|
||||
};
|
||||
let color = |i: usize, z: f64| -> [u8; 4] {
|
||||
let scan_color = |i: usize| {
|
||||
if cloud.has_rgb {
|
||||
cloud.colors[i]
|
||||
} else if cloud.has_intensity {
|
||||
[cloud.intensity[i]; 3]
|
||||
} else {
|
||||
object_color
|
||||
}
|
||||
};
|
||||
// A value outside a scheme's range takes the end colour, the scan
|
||||
// colour, or is hidden (`None`), as the cloud's out-of-range choice says.
|
||||
let along = |colors: &[[u8; 3]], t: f64, gradient: bool, out_of_range: i16, fixed: bool, i: usize| {
|
||||
if fixed && !(0.0..=1.0).contains(&t) {
|
||||
return match out_of_range {
|
||||
0 => Some(ramp(colors, t, gradient)),
|
||||
2 => None,
|
||||
_ => Some(scan_color(i)),
|
||||
};
|
||||
}
|
||||
Some(ramp(colors, t, gradient))
|
||||
};
|
||||
let color = |i: usize, z: f64| -> Option<[u8; 4]> {
|
||||
let [r, g, b] = match &style {
|
||||
Stylization::Object(c) => *c,
|
||||
Stylization::Normal => match cloud.normals.get(i) {
|
||||
@@ -329,25 +350,22 @@ pub(crate) fn placed(
|
||||
}
|
||||
None => object_color,
|
||||
},
|
||||
Stylization::Ramp { colors, gradient, intensity: true, range } => {
|
||||
Stylization::Ramp { colors, gradient, intensity: true, range, out_of_range } => {
|
||||
let percent = f64::from(cloud.intensity.get(i).copied().unwrap_or(0)) * 100.0 / 255.0;
|
||||
let (from, to) = (range.0 as f64, range.1 as f64);
|
||||
ramp(colors, (percent - from) / (to - from).max(1e-9), *gradient)
|
||||
}
|
||||
Stylization::Ramp { colors, gradient, .. } => {
|
||||
ramp(colors, (z - low) / (high - low).max(1e-9), *gradient)
|
||||
}
|
||||
Stylization::Scan => {
|
||||
if cloud.has_rgb {
|
||||
cloud.colors[i]
|
||||
} else if cloud.has_intensity {
|
||||
[cloud.intensity[i]; 3]
|
||||
} else {
|
||||
object_color
|
||||
}
|
||||
along(colors, (percent - from) / (to - from).max(1e-9), *gradient, *out_of_range, true, i)?
|
||||
}
|
||||
Stylization::Ramp { colors, gradient, out_of_range, .. } => along(
|
||||
colors,
|
||||
(z - low) / (high - low).max(1e-9),
|
||||
*gradient,
|
||||
*out_of_range,
|
||||
data.elevation_apply_to_fixed_range,
|
||||
i,
|
||||
)?,
|
||||
Stylization::Scan => scan_color(i),
|
||||
};
|
||||
[r, g, b, 255]
|
||||
Some([r, g, b, 255])
|
||||
};
|
||||
let mut instances = Vec::with_capacity(cloud.positions.len() / step + 1);
|
||||
for (i, p) in cloud.positions.iter().enumerate().step_by(step) {
|
||||
@@ -355,6 +373,9 @@ pub(crate) fn placed(
|
||||
continue;
|
||||
}
|
||||
let w = world(p);
|
||||
let Some(color) = color(i, w.z) else {
|
||||
continue;
|
||||
};
|
||||
let pos = [w.x as f32, w.y as f32, w.z as f32];
|
||||
instances.push(PointInstance {
|
||||
pos,
|
||||
@@ -363,7 +384,7 @@ pub(crate) fn placed(
|
||||
(w.y - pos[1] as f64) as f32,
|
||||
(w.z - pos[2] as f64) as f32,
|
||||
],
|
||||
color: color(i, w.z),
|
||||
color,
|
||||
});
|
||||
}
|
||||
crate::perf::record(format_args!(
|
||||
@@ -419,8 +440,9 @@ enum Stylization {
|
||||
/// Each point's world normal as a colour, (n + 1) / 2 per component.
|
||||
Normal,
|
||||
/// A colour scheme over intensity (per cent, in the range given) or
|
||||
/// elevation, blended or in bands.
|
||||
Ramp { colors: Vec<[u8; 3]>, gradient: bool, intensity: bool, range: (i64, i64) },
|
||||
/// elevation, blended or in bands; values out of range per
|
||||
/// `out_of_range` (0 end colours, 1 scan colours, 2 hidden).
|
||||
Ramp { colors: Vec<[u8; 3]>, gradient: bool, intensity: bool, range: (i64, i64), out_of_range: i16 },
|
||||
}
|
||||
|
||||
impl Stylization {
|
||||
@@ -439,7 +461,9 @@ impl Stylization {
|
||||
} else {
|
||||
(0, 100)
|
||||
};
|
||||
Self::Ramp { colors, gradient, intensity, range: (range.0, range.1) }
|
||||
let out_of_range =
|
||||
if intensity { data.intensity_out_of_range_behavior } else { data.elevation_out_of_range_behavior };
|
||||
Self::Ramp { colors, gradient, intensity, range: (range.0, range.1), out_of_range }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,6 +482,58 @@ fn rgb(color: u32) -> [u8; 3] {
|
||||
[(color >> 16) as u8, (color >> 8) as u8, color as u8]
|
||||
}
|
||||
|
||||
/// The colour schemes the drawing offers (identifier, name, colours high end
|
||||
/// first): its colour map's, or the built-in ones.
|
||||
pub(crate) fn schemes(document: &CadDocument) -> Vec<(String, String, Vec<[u8; 3]>)> {
|
||||
for object in document.objects.values() {
|
||||
let ObjectType::ClassObject(object) = object else {
|
||||
continue;
|
||||
};
|
||||
let ClassObjectData::PointCloudColorMap(map) = &object.data else {
|
||||
continue;
|
||||
};
|
||||
if !map.color_ramps.is_empty() {
|
||||
return map
|
||||
.color_ramps
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let colors = r.colors.iter().filter(|c| c.visible).map(|c| rgb(c.color as u32)).collect();
|
||||
(r.id.clone(), r.name.clone(), colors)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
BUILTIN_RAMPS
|
||||
.iter()
|
||||
.map(|(name, colors)| {
|
||||
let id = crate::entities::extended::point_cloud_ramp_id(name).to_string();
|
||||
(id, name.to_string(), colors.iter().map(|c| rgb(*c)).collect())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The schemes new intensity and elevation colouring starts from: the
|
||||
/// colour map's defaults, else Spectrum and Earth.
|
||||
pub(crate) fn default_schemes(document: &CadDocument) -> (String, String) {
|
||||
let map = document.objects.values().find_map(|object| match object {
|
||||
ObjectType::ClassObject(object) => match &object.data {
|
||||
ClassObjectData::PointCloudColorMap(map) => Some(map),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
});
|
||||
let pick = |stored: Option<&String>, name: &str| {
|
||||
stored
|
||||
.filter(|s| !s.is_empty())
|
||||
.cloned()
|
||||
.unwrap_or_else(|| crate::entities::extended::point_cloud_ramp_id(name).to_string())
|
||||
};
|
||||
(
|
||||
pick(map.map(|m| &m.default_intensity_scheme), "Spectrum"),
|
||||
pick(map.map(|m| &m.default_elevation_scheme), "Earth"),
|
||||
)
|
||||
}
|
||||
|
||||
/// A scheme's colours: from the drawing's colour map when it has the scheme,
|
||||
/// else the built-in one of that name (or `fallback` for an unset scheme).
|
||||
fn ramp_colors(document: &CadDocument, id: &str, fallback: &str) -> Vec<[u8; 3]> {
|
||||
|
||||
Reference in new issue
Block a user