From abb8b65b4971532b79d24fc4f45792f2f37ad79d Mon Sep 17 00:00:00 2001 From: Andrea Pozzetti Date: Thu, 12 Feb 2026 21:36:40 +0100 Subject: [PATCH] allow loading of pointclouds --- nodes/_utils/mesh_ops.py | 27 +++++++++++++++++++++++++-- nodes/io/load_mesh.py | 6 +++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/nodes/_utils/mesh_ops.py b/nodes/_utils/mesh_ops.py index c86bbc3a..ca3eac28 100644 --- a/nodes/_utils/mesh_ops.py +++ b/nodes/_utils/mesh_ops.py @@ -199,10 +199,22 @@ def load_mesh_file(file_path: str) -> Tuple[Optional[trimesh.Trimesh], str]: print(f"[load_mesh_file] Loading: {file_path}") # Try to load with trimesh first (supports many formats) - loaded = trimesh.load(file_path, force='mesh') + # Don't force='mesh' so we can also load pointclouds + loaded = trimesh.load(file_path) print(f"[load_mesh_file] Loaded type: {type(loaded).__name__}") + # Handle pointclouds (PLY files with only vertices, no faces) + if isinstance(loaded, trimesh.PointCloud): + print(f"[load_mesh_file] Loaded pointcloud: {len(loaded.vertices)} points") + # Store file metadata + loaded.metadata['file_path'] = file_path + loaded.metadata['file_name'] = os.path.basename(file_path) + loaded.metadata['file_format'] = os.path.splitext(file_path)[1].lower() + loaded.metadata['is_pointcloud'] = True + print(f"[load_mesh_file] ✓ Successfully loaded pointcloud: {len(loaded.vertices)} points") + return loaded, "" + # Handle case where trimesh.load returns a Scene instead of a mesh if isinstance(loaded, trimesh.Scene): print(f"[load_mesh_file] Converting Scene to single mesh (scene has {len(loaded.geometry)} geometries)") @@ -211,9 +223,20 @@ def load_mesh_file(file_path: str) -> Tuple[Optional[trimesh.Trimesh], str]: else: mesh = loaded - if mesh is None or len(mesh.vertices) == 0 or len(mesh.faces) == 0: + if mesh is None or len(mesh.vertices) == 0: return None, f"Failed to read mesh or mesh is empty: {file_path}" + # Check if it's actually a pointcloud (mesh with no faces) + if not hasattr(mesh, 'faces') or mesh.faces is None or len(mesh.faces) == 0: + # Convert to PointCloud + pointcloud = trimesh.PointCloud(vertices=mesh.vertices) + pointcloud.metadata['file_path'] = file_path + pointcloud.metadata['file_name'] = os.path.basename(file_path) + pointcloud.metadata['file_format'] = os.path.splitext(file_path)[1].lower() + pointcloud.metadata['is_pointcloud'] = True + print(f"[load_mesh_file] ✓ Successfully loaded as pointcloud: {len(pointcloud.vertices)} points") + return pointcloud, "" + print(f"[load_mesh_file] Initial mesh: {len(mesh.vertices)} vertices, {len(mesh.faces)} faces") # Ensure mesh is properly triangulated diff --git a/nodes/io/load_mesh.py b/nodes/io/load_mesh.py index 26ba8a54..7c0cb193 100644 --- a/nodes/io/load_mesh.py +++ b/nodes/io/load_mesh.py @@ -195,7 +195,11 @@ class LoadMesh: if loaded_mesh is None: raise ValueError(f"Failed to load mesh: {error}") - print(f"[LoadMesh] Loaded: {len(loaded_mesh.vertices)} vertices, {len(loaded_mesh.faces)} faces") + # Handle both meshes and pointclouds + if hasattr(loaded_mesh, 'faces') and loaded_mesh.faces is not None: + print(f"[LoadMesh] Loaded: {len(loaded_mesh.vertices)} vertices, {len(loaded_mesh.faces)} faces") + else: + print(f"[LoadMesh] Loaded pointcloud: {len(loaded_mesh.vertices)} points") # Extract texture texture = self._extract_texture_image(loaded_mesh)