forked from 1b-agent/opencadstudio-upstream
Point clouds: drawn from their scans, placed, cropped and attached as the reference does
Scans (.rcs) and projects (.rcp) are read and their points drawn as screen-sized dots, coloured by the cloud's stylization. The cloud's placement carries its scale and rotation; its extents and crops stay in the scan's coordinates. A missing scan shows the extents with the saved path. -POINTCLOUDATTACH / POINTCLOUDATTACH, POINTCLOUDCROP and the POINTCLOUD* settings follow the reference's prompts and messages, the Properties list is the reference's, and a locked cloud is left alone by edits. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
07f25f4bca
commit
e9c4e501db
58 files changed
+2711
-237
No files matched your search
@@ -0,0 +1,184 @@
|
||||
//! POINTCLOUDATTACH: the point cloud, its definition (one per file, in
|
||||
//! ACAD_POINTCLOUD_EX_DICT) and the reactor linking them, added as one undo
|
||||
//! step.
|
||||
|
||||
use super::*;
|
||||
use codec::objects::{
|
||||
ClassObject, ClassObjectData, Dictionary, ObjectType, PointCloudDefinition,
|
||||
PointCloudDefinitionReactor,
|
||||
};
|
||||
use codec::types::{Handle, Vector3};
|
||||
|
||||
use crate::modules::insert::pc_attach::PointCloudPlacement;
|
||||
|
||||
const DEFINITIONS: &str = "ACAD_POINTCLOUD_EX_DICT";
|
||||
|
||||
fn vector(v: glam::DVec3) -> Vector3 {
|
||||
Vector3::new(v.x, v.y, v.z)
|
||||
}
|
||||
|
||||
/// The point cloud definitions dictionary under the named-objects root,
|
||||
/// created when the drawing has none.
|
||||
fn definitions_dictionary(document: &mut codec::CadDocument) -> Option<Handle> {
|
||||
let root = document.header.named_objects_dict_handle;
|
||||
let Some(ObjectType::Dictionary(root_dictionary)) = document.objects.get(&root) else {
|
||||
return None;
|
||||
};
|
||||
if let Some(existing) = root_dictionary.get(DEFINITIONS) {
|
||||
if matches!(document.objects.get(&existing), Some(ObjectType::Dictionary(_))) {
|
||||
return Some(existing);
|
||||
}
|
||||
}
|
||||
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(DEFINITIONS, handle);
|
||||
}
|
||||
Some(handle)
|
||||
}
|
||||
|
||||
/// The definition for a file: the existing one, or a new one registered
|
||||
/// under the file's name ("Chair", "Chair(2)" …). Its point count and
|
||||
/// extents come from the file.
|
||||
fn ensure_definition(
|
||||
document: &mut codec::CadDocument,
|
||||
path: &str,
|
||||
name: &str,
|
||||
points: &crate::scene::model::point_cloud::CloudPoints,
|
||||
) -> (Handle, Vector3, Vector3) {
|
||||
let (mut lo, mut hi) = ([f64::MAX; 3], [f64::MIN; 3]);
|
||||
for p in &points.positions {
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(p[k]);
|
||||
hi[k] = hi[k].max(p[k]);
|
||||
}
|
||||
}
|
||||
let (min, max) = if points.positions.is_empty() {
|
||||
(Vector3::ZERO, Vector3::ZERO)
|
||||
} else {
|
||||
(Vector3::new(lo[0], lo[1], lo[2]), Vector3::new(hi[0], hi[1], hi[2]))
|
||||
};
|
||||
let same = |stored: &str| stored.replace('/', "\\").eq_ignore_ascii_case(&path.replace('/', "\\"));
|
||||
let existing = document.objects.iter().find_map(|(handle, object)| match object {
|
||||
ObjectType::ClassObject(object) => match &object.data {
|
||||
ClassObjectData::PointCloudDefinitionEx(def) if same(&def.source_filename) => Some(*handle),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
});
|
||||
if let Some(handle) = existing {
|
||||
return (handle, min, max);
|
||||
}
|
||||
let handle = document.allocate_handle();
|
||||
let mut definition = ClassObject::new(ClassObjectData::PointCloudDefinitionEx(PointCloudDefinition {
|
||||
class_version: 1,
|
||||
source_filename: path.to_string(),
|
||||
is_loaded: true,
|
||||
point_count: points.positions.len() as i64,
|
||||
extents_min: min,
|
||||
extents_max: max,
|
||||
}));
|
||||
definition.handle = handle;
|
||||
if let Some(dictionary) = definitions_dictionary(document) {
|
||||
definition.owner = dictionary;
|
||||
definition.reactors.push(dictionary);
|
||||
if let Some(ObjectType::Dictionary(entries)) = document.objects.get_mut(&dictionary) {
|
||||
let mut key = name.to_string();
|
||||
let mut suffix = 1;
|
||||
while entries.get(&key).is_some() {
|
||||
suffix += 1;
|
||||
key = format!("{name}({suffix})");
|
||||
}
|
||||
entries.add_entry(key, handle);
|
||||
}
|
||||
}
|
||||
document.objects.insert(handle, ObjectType::ClassObject(definition));
|
||||
(handle, min, max)
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
pub(crate) fn attach_point_cloud(&mut self, i: usize, label: String, placement: PointCloudPlacement) {
|
||||
let Some(points) = crate::scene::model::point_cloud::load(&placement.path) else {
|
||||
self.command_line.push_error("All scans are not found or invalid.");
|
||||
self.command_line.push_error("Attach point cloud failed");
|
||||
return;
|
||||
};
|
||||
let path = placement.path.to_string_lossy().into_owned();
|
||||
let name = placement
|
||||
.path
|
||||
.file_stem()
|
||||
.map(|stem| stem.to_string_lossy().into_owned())
|
||||
.unwrap_or_default();
|
||||
let pending = self.begin_undo(i, label, 1, false);
|
||||
let document = &mut self.tabs[i].scene.document;
|
||||
// POINTCLOUDLOCK 1 attaches clouds locked.
|
||||
let locked = crate::scene::model::point_cloud::setting("POINTCLOUDLOCK")
|
||||
.is_some_and(|setting| crate::scene::model::point_cloud::setting_value(document, setting) == 1);
|
||||
let (definition, extents_min, extents_max) = ensure_definition(document, &path, &name, &points);
|
||||
let [x, y, z] = placement.axes;
|
||||
// As the reference creates a cloud: scan colours, the intensity
|
||||
// range 0–100 as a gradient, crops shown.
|
||||
let data = codec::entities::PointCloudExData {
|
||||
class_version: 1,
|
||||
extents_min,
|
||||
extents_max,
|
||||
ucs_origin: vector(placement.insertion),
|
||||
ucs_x_direction: vector(x),
|
||||
ucs_y_direction: vector(y),
|
||||
ucs_z_direction: vector(z),
|
||||
locked,
|
||||
definition_handle: definition,
|
||||
reactor_handle: Handle::NULL,
|
||||
name,
|
||||
show_intensity: false,
|
||||
show_cropping: true,
|
||||
unknown_bl0: 0,
|
||||
unknown_bl1: 0,
|
||||
stylization_type: 1,
|
||||
intensity_color_scheme: String::new(),
|
||||
current_color_scheme: String::new(),
|
||||
classification_color_scheme: String::new(),
|
||||
elevation_min: 0.0,
|
||||
elevation_max: 0.0,
|
||||
intensity_min: 0,
|
||||
intensity_max: 100,
|
||||
intensity_out_of_range_behavior: 1,
|
||||
elevation_out_of_range_behavior: 1,
|
||||
elevation_apply_to_fixed_range: false,
|
||||
intensity_as_gradient: true,
|
||||
elevation_as_gradient: false,
|
||||
croppings: Vec::new(),
|
||||
};
|
||||
let entity = codec::EntityType::Extended(codec::entities::ExtendedEntity {
|
||||
common: codec::entities::EntityCommon::new(),
|
||||
data: codec::entities::ExtendedEntityData::PointCloudEx(data),
|
||||
});
|
||||
if let Some(cloud) = self.commit_entity_handle(entity) {
|
||||
// The reactor, owned by the cloud, tells the definition who uses it.
|
||||
let document = &mut self.tabs[i].scene.document;
|
||||
let reactor = document.allocate_handle();
|
||||
let mut object = ClassObject::new(ClassObjectData::PointCloudDefinitionReactorEx(
|
||||
PointCloudDefinitionReactor { class_version: 1 },
|
||||
));
|
||||
object.handle = reactor;
|
||||
object.owner = cloud;
|
||||
document.objects.insert(reactor, ObjectType::ClassObject(object));
|
||||
if let Some(ObjectType::ClassObject(definition)) = document.objects.get_mut(&definition) {
|
||||
definition.reactors.push(reactor);
|
||||
}
|
||||
if let Some(codec::EntityType::Extended(extended)) = document.get_entity_mut(cloud) {
|
||||
if let codec::entities::ExtendedEntityData::PointCloudEx(data) = &mut extended.data {
|
||||
data.reactor_handle = reactor;
|
||||
}
|
||||
}
|
||||
self.command_line.push_output("1 point cloud attached");
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user