forked from 1b-agent/opencadstudio-upstream
Merge pull request #1553 from ramox81/codex/pointcloud-attach
Point clouds: drawn from their scans, attached, cropped and stylized. The fork's codec patch is resolved to the upstream pins: the point cloud records landed in opencadcodec 5ef9376, carried through opencadkernel 68eeda1 and opencadgraph e204966. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
3dcebf0dc6
81 files changed
+9162
-305
No files matched your search
+160
-1
@@ -322,6 +322,21 @@ pub fn register_underlay_sources(doc: &CadDocument, base_dir: &Path) {
|
||||
crate::scene::model::pdf_raster::register_source(stored, std::sync::Arc::new(bytes));
|
||||
}
|
||||
}
|
||||
// Point cloud scans are large and read lazily: register where they are.
|
||||
for object in doc.objects.values() {
|
||||
let codec::objects::ObjectType::ClassObject(object) = object else {
|
||||
continue;
|
||||
};
|
||||
let (codec::objects::ClassObjectData::PointCloudDefinitionEx(def)
|
||||
| codec::objects::ClassObjectData::PointCloudDefinition(def)) = &object.data
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let stored = def.source_filename.trim();
|
||||
if let Some(found) = (!stored.is_empty()).then(|| resolve_path(stored, base_dir)).flatten() {
|
||||
crate::scene::model::point_cloud::register_source(stored, found);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Layer properties an override can change, as compared for the
|
||||
@@ -1277,6 +1292,55 @@ pub fn collect_entries_with_prev(
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
// ── Point clouds ──
|
||||
// Listed under their definitions' names; a definition no cloud uses is
|
||||
// Unreferenced, as for images and underlays.
|
||||
let mut referenced_clouds: HashSet<Handle> = HashSet::default();
|
||||
for e in doc.entities() {
|
||||
if let EntityType::Extended(extended) = e {
|
||||
if let codec::entities::ExtendedEntityData::PointCloudEx(data) = &extended.data {
|
||||
referenced_clouds.insert(data.definition_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
let cloud_names: HashMap<Handle, String> = doc
|
||||
.objects
|
||||
.values()
|
||||
.filter_map(|object| match object {
|
||||
ObjectType::Dictionary(dictionary) => Some(dictionary),
|
||||
_ => None,
|
||||
})
|
||||
.flat_map(|dictionary| dictionary.entries.iter().map(|(name, handle)| (*handle, name.clone())))
|
||||
.collect();
|
||||
for (handle, obj) in doc.objects.iter() {
|
||||
let ObjectType::ClassObject(object) = obj else {
|
||||
continue;
|
||||
};
|
||||
let codec::objects::ClassObjectData::PointCloudDefinitionEx(def) = &object.data else {
|
||||
continue;
|
||||
};
|
||||
let key = handle.value();
|
||||
let name = cloud_names.get(handle).cloned().unwrap_or_else(|| {
|
||||
Path::new(&def.source_filename.replace('\\', "/"))
|
||||
.file_stem()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_default()
|
||||
});
|
||||
let mut entry = ReferenceEntry::new(key, name, RefKind::PointCloud);
|
||||
entry.saved_path = def.source_filename.clone();
|
||||
if !def.is_loaded {
|
||||
entry.status = RefStatus::Unloaded;
|
||||
resolve_only(&mut entry, &def.source_filename, base_dir);
|
||||
} else {
|
||||
stat_into(&mut entry, &def.source_filename, base_dir);
|
||||
entry.status = decide_status(entry.status, entry.modified, prev.get(&key).copied(), false);
|
||||
if !referenced_clouds.contains(handle) {
|
||||
entry.status = RefStatus::Unreferenced;
|
||||
}
|
||||
}
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
// ── Nested enumeration: full transitive closure, read-only, no merge ──
|
||||
// Paths stored in a nested drawing are relative to *that drawing*, not to
|
||||
// the host. The work queue carries each parent's resolved file so every
|
||||
@@ -1369,6 +1433,7 @@ enum RefTarget {
|
||||
DwgXref { handle: Handle, name: String },
|
||||
Image { handle: Handle, name: String },
|
||||
Pdf { handle: Handle, name: String },
|
||||
PointCloud { handle: Handle, name: String },
|
||||
}
|
||||
|
||||
fn find_target(doc: &CadDocument, key: u64) -> Option<RefTarget> {
|
||||
@@ -1389,6 +1454,11 @@ fn find_target(doc: &CadDocument, key: u64) -> Option<RefTarget> {
|
||||
name: def.file_name.clone(),
|
||||
});
|
||||
}
|
||||
ObjectType::ClassObject(object) if handle.value() == key => {
|
||||
if let codec::objects::ClassObjectData::PointCloudDefinitionEx(def) = &object.data {
|
||||
return Some(RefTarget::PointCloud { handle: *handle, name: def.source_filename.clone() });
|
||||
}
|
||||
}
|
||||
ObjectType::UnderlayDefinition(def) if handle.value() == key => {
|
||||
return Some(RefTarget::Pdf {
|
||||
handle: *handle,
|
||||
@@ -1462,6 +1532,20 @@ pub fn unload_reference(doc: &mut CadDocument, key: u64) -> Result<String, Strin
|
||||
}
|
||||
Ok(name)
|
||||
}
|
||||
// So does a point cloud (its definition's loaded flag).
|
||||
RefTarget::PointCloud { handle, name } => {
|
||||
set_point_cloud_loaded(doc, handle, false);
|
||||
Ok(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A point cloud definition's loaded flag (Unload / Reload).
|
||||
pub fn set_point_cloud_loaded(doc: &mut CadDocument, handle: Handle, loaded: bool) {
|
||||
if let Some(codec::objects::ObjectType::ClassObject(object)) = doc.objects.get_mut(&handle) {
|
||||
if let codec::objects::ClassObjectData::PointCloudDefinitionEx(def) = &mut object.data {
|
||||
def.is_loaded = loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1563,6 +1647,34 @@ pub fn detach_reference(doc: &mut CadDocument, key: u64) -> Result<String, Strin
|
||||
doc.objects.remove(&handle);
|
||||
Ok(name)
|
||||
}
|
||||
// The clouds, their reactors, the definition and its dictionary
|
||||
// entry.
|
||||
RefTarget::PointCloud { handle, name } => {
|
||||
use codec::entities::ExtendedEntityData;
|
||||
let clouds: Vec<(Handle, Handle)> = doc
|
||||
.entities()
|
||||
.filter_map(|e| match e {
|
||||
EntityType::Extended(extended) => match &extended.data {
|
||||
ExtendedEntityData::PointCloudEx(data) if data.definition_handle == handle => {
|
||||
Some((e.common().handle, data.reactor_handle))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
for (cloud, reactor) in clouds {
|
||||
doc.remove_entity(cloud);
|
||||
doc.objects.remove(&reactor);
|
||||
}
|
||||
for object in doc.objects.values_mut() {
|
||||
if let codec::objects::ObjectType::Dictionary(dictionary) = object {
|
||||
dictionary.entries.retain(|(_, h)| *h != handle);
|
||||
}
|
||||
}
|
||||
doc.objects.remove(&handle);
|
||||
Ok(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1611,6 +1723,9 @@ pub fn bind_reference(
|
||||
"XREF: PDF bind (vector import) is not available in this version."
|
||||
)
|
||||
.to_string()),
|
||||
RefTarget::PointCloud { .. } => {
|
||||
Err(crate::t!("XREF: bind applies to drawing references only.").to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1881,6 +1996,10 @@ fn same_file(a: &Path, b: &Path) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks a point cloud definition (by handle) among the references made
|
||||
/// relative on the drawing's first save.
|
||||
pub const POINT_CLOUD_KEY: &str = "pointcloud:";
|
||||
|
||||
/// Make the named references' paths relative to `host` (their drawing's
|
||||
/// first save); a reference that cannot be relative keeps its full path.
|
||||
pub fn make_relative(
|
||||
@@ -1889,6 +2008,12 @@ pub fn make_relative(
|
||||
host: &Path,
|
||||
) {
|
||||
for name in names {
|
||||
if let Some(handle) = name.strip_prefix(POINT_CLOUD_KEY) {
|
||||
if let Ok(handle) = u64::from_str_radix(handle, 16) {
|
||||
relative_point_cloud_path(doc, Handle::new(handle), host);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let Some(key) = doc.block_records.get(name).map(|br| br.handle.value()) else {
|
||||
continue;
|
||||
};
|
||||
@@ -2146,6 +2271,19 @@ pub fn set_ref_path(
|
||||
_ => Err(crate::t!("XREF: no loaded reference with that key.").to_string()),
|
||||
}
|
||||
}
|
||||
RefTarget::PointCloud { handle, name } => {
|
||||
use codec::objects::{ClassObjectData, ObjectType};
|
||||
match doc.objects.get_mut(&handle) {
|
||||
Some(ObjectType::ClassObject(object)) => match &mut object.data {
|
||||
ClassObjectData::PointCloudDefinitionEx(def) => {
|
||||
def.source_filename = new_raw.to_string();
|
||||
Ok(name)
|
||||
}
|
||||
_ => Err(crate::t!("XREF: no loaded reference with that key.").to_string()),
|
||||
},
|
||||
_ => Err(crate::t!("XREF: no loaded reference with that key.").to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2177,6 +2315,7 @@ pub fn apply_pathtype(
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
Some(RefTarget::PointCloud { name, .. }) => name,
|
||||
None => return Err(crate::t!("XREF: no loaded reference with that key.").to_string()),
|
||||
};
|
||||
// A stored relative path first has to be resolved against its current host
|
||||
@@ -2305,7 +2444,7 @@ pub fn set_ref_type(
|
||||
}
|
||||
Err(crate::t!("XREF: no loaded reference with that key.").to_string())
|
||||
}
|
||||
RefTarget::Image { .. } | RefTarget::Pdf { .. } => {
|
||||
RefTarget::Image { .. } | RefTarget::Pdf { .. } | RefTarget::PointCloud { .. } => {
|
||||
Err(crate::t!("XREF: overlays apply to drawing references only.").to_string())
|
||||
}
|
||||
}
|
||||
@@ -3106,3 +3245,23 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A point cloud definition's full path made relative to the drawing
|
||||
/// (".\rcs\scan.rcs", as the reference writes it); on another drive it
|
||||
/// stays full.
|
||||
fn relative_point_cloud_path(doc: &mut CadDocument, handle: Handle, host: &Path) {
|
||||
use crate::io::xref_model::{to_pathtype_result, Pathtype};
|
||||
let Some(codec::objects::ObjectType::ClassObject(object)) = doc.objects.get_mut(&handle) else {
|
||||
return;
|
||||
};
|
||||
let codec::objects::ClassObjectData::PointCloudDefinitionEx(def) = &mut object.data else {
|
||||
return;
|
||||
};
|
||||
if let Ok(relative) = to_pathtype_result(&def.source_filename, host, Pathtype::Relative) {
|
||||
let relative = relative.replace('/', "\\");
|
||||
if let Some(found) = std::path::Path::new(&def.source_filename).is_file().then(|| def.source_filename.clone()) {
|
||||
crate::scene::model::point_cloud::register_source(&relative, found.into());
|
||||
}
|
||||
def.source_filename = relative;
|
||||
}
|
||||
}
|
||||
@@ -305,6 +305,8 @@ pub enum RefKind {
|
||||
Image,
|
||||
/// A PDF, DWF or DGN underlay.
|
||||
Underlay,
|
||||
/// A point cloud scan (.rcs) or project (.rcp).
|
||||
PointCloud,
|
||||
}
|
||||
|
||||
/// Lifecycle state of a [`ReferenceEntry`].
|
||||
|
||||
Reference in new issue
Block a user