forked from 1b-agent/opencadstudio-upstream
Point clouds: crops along the view, POINTCLOUDUNCROP, POINTCLOUDLOD
A crop is drawn on the view plane: it keeps the view's right and up directions in the cloud's coordinates and its points in 3D, and cuts along the view. POINTCLOUDUNCROP removes a picked cloud's crops. POINTCLOUDLOD scales the points shown in tenths. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
da25c45fdd
commit
2e23a903a4
4 files changed
+81
-36
No files matched your search
@@ -720,9 +720,11 @@ impl OpenCADStudio {
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
"POINTCLOUDCROP" => {
|
||||
"POINTCLOUDCROP" | "POINTCLOUDUNCROP" => {
|
||||
use crate::command::CadCommand;
|
||||
let command = crate::modules::insert::pc_crop::PointCloudCropCommand::new();
|
||||
use crate::modules::insert::pc_crop::PointCloudCropCommand;
|
||||
let command =
|
||||
if cmd == "POINTCLOUDUNCROP" { PointCloudCropCommand::uncrop() } else { PointCloudCropCommand::new() };
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
|
||||
@@ -535,22 +535,20 @@ fn point_cloud_ex_clip_lines(data: &PointCloudExData) -> Vec<[f64; 3]> {
|
||||
if crop.points.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
// Crop boundaries are kept in the cloud's own coordinates, on
|
||||
// the crop plane through `plane` spanned by its two directions.
|
||||
let normal = crop.x_direction.cross(&crop.y_direction);
|
||||
// Crop points are kept in the cloud's own coordinates; the crop
|
||||
// runs along the normal of the plane its two directions span.
|
||||
let place = |p: Vector3| {
|
||||
let local = crop.plane + crop.x_direction * p.x + crop.y_direction * p.y + normal * p.z;
|
||||
let w = codec::entities::point_cloud_ex_to_world(data, local);
|
||||
let w = codec::entities::point_cloud_ex_to_world(data, p);
|
||||
[w.x, w.y, w.z]
|
||||
};
|
||||
// A circle is stored as its centre and a point on it.
|
||||
let outline: Vec<Vector3> = if crop.crop_type == 3 {
|
||||
let (c, e) = (crop.points[0], crop.points[1]);
|
||||
let r = ((e.x - c.x).powi(2) + (e.y - c.y).powi(2)).sqrt();
|
||||
let (c, d) = (crop.points[0], crop.points[1] - crop.points[0]);
|
||||
let r = d.dot(&crop.x_direction).hypot(d.dot(&crop.y_direction));
|
||||
(0..64)
|
||||
.map(|k| {
|
||||
let a = k as f64 / 64.0 * std::f64::consts::TAU;
|
||||
Vector3::new(c.x + r * a.cos(), c.y + r * a.sin(), c.z)
|
||||
c + crop.x_direction * (r * a.cos()) + crop.y_direction * (r * a.sin())
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// POINTCLOUDCROP — crop a point cloud to a rectangle, polygon or circle.
|
||||
// POINTCLOUDUNCROP — `Select point cloud:` and all its crops go.
|
||||
//
|
||||
// Select point cloud:
|
||||
// Specify first corner point or [Polygon/Circular]:
|
||||
@@ -8,9 +9,10 @@
|
||||
// Specify center point: / Specify radius: (Circular)
|
||||
// Keep points inside or outside? [Inside/Outside] <Inside>:
|
||||
//
|
||||
// Crops are kept in the cloud's own coordinates, on the plane of its X and
|
||||
// Y axes: a rectangle as its four corners, a polygon as its points, a
|
||||
// circle as its centre and a point on it. They add up; Invert flips them,
|
||||
// Crops are kept in the cloud's own coordinates, on the view plane (its
|
||||
// right and up directions) and running along the view: a rectangle as its
|
||||
// four corners, a polygon as its points, a circle as its centre and a point
|
||||
// on it. They add up; Invert flips them,
|
||||
// Remove last drops the newest, ON / OFF shows or hides them.
|
||||
|
||||
use std::sync::Mutex;
|
||||
@@ -59,6 +61,10 @@ pub struct PointCloudCropCommand {
|
||||
picked: Option<EntityType>,
|
||||
shape: Shape,
|
||||
points: Vec<DVec3>,
|
||||
/// POINTCLOUDUNCROP: the picked cloud loses its crops.
|
||||
uncrop: bool,
|
||||
/// The view's right and up directions in the drawing.
|
||||
view: (DVec3, DVec3),
|
||||
}
|
||||
|
||||
impl PointCloudCropCommand {
|
||||
@@ -70,9 +76,15 @@ impl PointCloudCropCommand {
|
||||
picked: None,
|
||||
shape: Shape::Rectangle,
|
||||
points: Vec::new(),
|
||||
uncrop: false,
|
||||
view: (DVec3::X, DVec3::Y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uncrop() -> Self {
|
||||
Self { uncrop: true, ..Self::new() }
|
||||
}
|
||||
|
||||
/// The toolbar's crops: the cloud already chosen, the shape too
|
||||
/// ("P" polygon, "C" circle, "" rectangle).
|
||||
pub fn for_cloud(handle: Handle, cloud: ExtendedEntity, option: &str) -> Self {
|
||||
@@ -108,13 +120,21 @@ impl PointCloudCropCommand {
|
||||
CmdResult::UpdateEntityAndFinish { handle: self.handle, entity: EntityType::Extended(cloud) }
|
||||
}
|
||||
|
||||
/// A point of the drawing in the cloud's own coordinates.
|
||||
fn to_local(data: &PointCloudExData, p: DVec3) -> Vector3 {
|
||||
fn axes(data: &PointCloudExData) -> glam::DMat3 {
|
||||
let v = |a: Vector3| DVec3::new(a.x, a.y, a.z);
|
||||
let axes = glam::DMat3::from_cols(v(data.ucs_x_direction), v(data.ucs_y_direction), v(data.ucs_z_direction));
|
||||
let local = axes.inverse() * (p - v(data.ucs_origin));
|
||||
// On the plane of the cloud's X and Y axes.
|
||||
Vector3::new(local.x, local.y, 0.0)
|
||||
glam::DMat3::from_cols(v(data.ucs_x_direction), v(data.ucs_y_direction), v(data.ucs_z_direction))
|
||||
}
|
||||
|
||||
/// A point of the drawing in the cloud's own coordinates.
|
||||
fn to_local(data: &PointCloudExData, p: DVec3) -> DVec3 {
|
||||
let origin = data.ucs_origin;
|
||||
Self::axes(data).inverse() * (p - DVec3::new(origin.x, origin.y, origin.z))
|
||||
}
|
||||
|
||||
/// The view's right and up directions in the cloud's own coordinates.
|
||||
fn view_local(&self, data: &PointCloudExData) -> (DVec3, DVec3) {
|
||||
let inverse = Self::axes(data).inverse();
|
||||
((inverse * self.view.0).normalize_or(DVec3::X), (inverse * self.view.1).normalize_or(DVec3::Y))
|
||||
}
|
||||
|
||||
fn option(&mut self, text: &str) -> CmdResult {
|
||||
@@ -157,24 +177,26 @@ impl PointCloudCropCommand {
|
||||
let Some(data) = self.data() else {
|
||||
return CmdResult::Cancel;
|
||||
};
|
||||
let points: Vec<Vector3> = match self.shape {
|
||||
let (right, up) = self.view_local(data);
|
||||
let local: Vec<DVec3> = self.points.iter().map(|p| Self::to_local(data, *p)).collect();
|
||||
let local = match self.shape {
|
||||
// The corners along the view's right and up directions.
|
||||
Shape::Rectangle => {
|
||||
let (a, b) = (self.points[0], self.points[1]);
|
||||
[DVec3::new(a.x, a.y, a.z), DVec3::new(b.x, a.y, a.z), DVec3::new(b.x, b.y, a.z), DVec3::new(a.x, b.y, a.z)]
|
||||
.into_iter()
|
||||
.map(|p| Self::to_local(data, p))
|
||||
.collect()
|
||||
let (a, d) = (local[0], local[1] - local[0]);
|
||||
let (dx, dy) = (right * d.dot(right), up * d.dot(up));
|
||||
vec![a, a + dx, a + dx + dy, a + dy]
|
||||
}
|
||||
_ => self.points.iter().map(|p| Self::to_local(data, *p)).collect(),
|
||||
_ => local,
|
||||
};
|
||||
let vector = |v: DVec3| Vector3::new(v.x, v.y, v.z);
|
||||
let crop = PointCloudExCrop {
|
||||
crop_type: self.shape as i16,
|
||||
inside,
|
||||
inverted: false,
|
||||
plane: Vector3::ZERO,
|
||||
x_direction: Vector3::UNIT_X,
|
||||
y_direction: Vector3::UNIT_Y,
|
||||
points,
|
||||
x_direction: vector(right),
|
||||
y_direction: vector(up),
|
||||
points: local.into_iter().map(vector).collect(),
|
||||
};
|
||||
self.finish(move |data| {
|
||||
data.croppings.push(crop);
|
||||
@@ -190,7 +212,7 @@ impl PointCloudCropCommand {
|
||||
|
||||
impl CadCommand for PointCloudCropCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"POINTCLOUDCROP"
|
||||
if self.uncrop { "POINTCLOUDUNCROP" } else { "POINTCLOUDCROP" }
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
@@ -247,6 +269,21 @@ impl CadCommand for PointCloudCropCommand {
|
||||
self.step == Step::Select
|
||||
}
|
||||
|
||||
fn wants_point_pick_context(&self) -> bool {
|
||||
!matches!(self.step, Step::Select | Step::Keep)
|
||||
}
|
||||
|
||||
fn set_point_pick_context(&mut self, context: Option<crate::command::PointPickContext>) {
|
||||
// The projection's first two rows run along the view's right and up.
|
||||
if let Some(context) = context {
|
||||
let row = |i: usize| context.view.row(i).truncate().as_dvec3().normalize_or_zero();
|
||||
let (right, up) = (row(0), row(1));
|
||||
if right != DVec3::ZERO && up != DVec3::ZERO {
|
||||
self.view = (right, up);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_before_entity_pick(&self) -> bool {
|
||||
true
|
||||
}
|
||||
@@ -260,6 +297,9 @@ impl CadCommand for PointCloudCropCommand {
|
||||
Some(EntityType::Extended(cloud)) if matches!(cloud.data, ExtendedEntityData::PointCloudEx(_)) => {
|
||||
self.handle = handle;
|
||||
self.cloud = Some(cloud);
|
||||
if self.uncrop {
|
||||
return self.finish(|data| data.croppings.clear());
|
||||
}
|
||||
self.step = Step::First;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
@@ -366,4 +406,4 @@ impl CadCommand for PointCloudCropCommand {
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["POINTCLOUDCROP"] });
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["POINTCLOUDCROP", "POINTCLOUDUNCROP"] });
|
||||
@@ -260,9 +260,10 @@ pub(crate) fn placed(
|
||||
) -> Option<Arc<PlacedCloud>> {
|
||||
let cloud = load(&resolve_source(document, data)?)?;
|
||||
// The points shown: POINTCLOUDDENSITY per cent of POINTCLOUDPOINTMAX,
|
||||
// every n-th point of a larger cloud.
|
||||
// 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));
|
||||
let limit = (shown("POINTCLOUDPOINTMAX") * shown("POINTCLOUDDENSITY") / 100).max(1) as usize;
|
||||
let limit =
|
||||
(shown("POINTCLOUDPOINTMAX") * shown("POINTCLOUDDENSITY") / 100 * shown("POINTCLOUDLOD") / 10).max(1) as usize;
|
||||
let step = cloud.positions.len().div_ceil(limit).max(1);
|
||||
let style = Stylization::of(document, data, object_color);
|
||||
let mut origin = data.ucs_origin;
|
||||
@@ -382,9 +383,13 @@ pub(crate) fn placed(
|
||||
/// is in that plane's coordinates. `inside` keeps the prism's interior and
|
||||
/// `inverted` flips it.
|
||||
fn keeps(crop: &PointCloudExCrop, p: &[f64; 3]) -> bool {
|
||||
let d = Vector3::new(p[0], p[1], p[2]) - crop.plane;
|
||||
let (x, y) = (d.dot(&crop.x_direction), d.dot(&crop.y_direction));
|
||||
let points = &crop.points;
|
||||
// Points and crop outline on the crop plane, along its normal.
|
||||
let on_plane = |v: Vector3| {
|
||||
let d = v - crop.plane;
|
||||
Vector3::new(d.dot(&crop.x_direction), d.dot(&crop.y_direction), 0.0)
|
||||
};
|
||||
let Vector3 { x, y, .. } = on_plane(Vector3::new(p[0], p[1], p[2]));
|
||||
let points: Vec<Vector3> = crop.points.iter().copied().map(on_plane).collect();
|
||||
// A circle: its centre and a point on it.
|
||||
if crop.crop_type == 3 {
|
||||
let (c, e) = (points[0], points[1]);
|
||||
|
||||
Reference in new issue
Block a user