Skip to content

Commit 3a1cd5a

Browse files
Copilotlachlangrose
andcommitted
Add OBJ, PLY, and ASCII voxel export options
- Fix bug: Changed `object` to `mesh` in format detection (lines 265-269) - Add detection for grid/voxel mesh types (UniformGrid, ImageData, StructuredGrid, RectilinearGrid) - Add ASCII export option for grid meshes (x, y, z, value format) - Implement _export_grid_ascii helper method - Add logging support and replace print statements with logger calls - OBJ and PLY formats already supported for surface meshes Co-authored-by: lachlangrose <7371904+lachlangrose@users.noreply.github.com>
1 parent ed5a73d commit 3a1cd5a

File tree

1 file changed

+67
-4
lines changed

1 file changed

+67
-4
lines changed

loopstructural/gui/visualisation/object_list_widget.py

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import logging
2+
13
import pyvista as pv
24
from PyQt5.QtCore import Qt
35
from PyQt5.QtWidgets import (
@@ -13,6 +15,8 @@
1315
QWidget,
1416
)
1517

18+
logger = logging.getLogger(__name__)
19+
1620

1721
class ObjectListWidget(QWidget):
1822
def __init__(self, parent=None, *, viewer=None, properties_widget=None):
@@ -262,11 +266,19 @@ def export_selected_object(self):
262266
except ImportError:
263267
has_geoh5py = False
264268

265-
if hasattr(object, "faces"): # Likely a surface/mesh
269+
# Check if this is a grid/voxel type (UniformGrid, ImageData, StructuredGrid, RectilinearGrid)
270+
is_grid = type(mesh).__name__ in ['UniformGrid', 'ImageData', 'StructuredGrid', 'RectilinearGrid']
271+
272+
if is_grid:
273+
# Grid/voxel meshes support ASCII export
274+
formats = ["vtk", "ascii"]
275+
if has_geoh5py:
276+
formats.append("geoh5")
277+
elif hasattr(mesh, "faces"): # Likely a surface/mesh
266278
formats = ["obj", "vtk", "ply"]
267279
if has_geoh5py:
268280
formats.append("geoh5")
269-
elif hasattr(object, "points"): # Likely a point cloud
281+
elif hasattr(mesh, "points"): # Likely a point cloud
270282
formats = ["vtp"]
271283
if has_geoh5py:
272284
formats.append("geoh5")
@@ -279,6 +291,7 @@ def export_selected_object(self):
279291
"vtk": "VTK (*.vtk)",
280292
"ply": "PLY (*.ply)",
281293
"vtp": "VTP (*.vtp)",
294+
"ascii": "ASCII Grid (*.txt)",
282295
"geoh5": "Geoh5 (*.geoh5)",
283296
}
284297
filters = ";;".join([filter_map[f] for f in formats])
@@ -312,6 +325,9 @@ def export_selected_object(self):
312325
if hasattr(mesh, "save")
313326
else pv.save_meshio(file_path, mesh)
314327
)
328+
elif selected_format == "ascii":
329+
# Export grid/voxel as ASCII: x, y, z, value format
330+
self._export_grid_ascii(mesh, file_path, object_label)
315331
elif selected_format == "geoh5":
316332
with geoh5py.Geoh5(file_path, overwrite=True) as geoh5:
317333
if hasattr(mesh, "faces"):
@@ -320,11 +336,58 @@ def export_selected_object(self):
320336
)
321337
else:
322338
geoh5.add_points(name=object_label, vertices=mesh.points)
323-
print(f"Exported {object_label} to {file_path} as {selected_format}")
339+
logger.info(f"Exported {object_label} to {file_path} as {selected_format}")
324340
except Exception as e:
325-
print(f"Failed to export object: {e}")
341+
logger.error(f"Failed to export object: {e}")
326342
# Logic for exporting the object
327343

344+
def _export_grid_ascii(self, mesh, file_path, object_label):
345+
"""Export a grid/voxel mesh to ASCII format.
346+
347+
Format: x, y, z, value (one line per cell center)
348+
349+
Parameters
350+
----------
351+
mesh : pyvista grid mesh
352+
The grid mesh to export
353+
file_path : str
354+
Path to the output file
355+
object_label : str
356+
Name of the object (used to determine which scalar array to export)
357+
"""
358+
import numpy as np
359+
360+
# Get cell centers
361+
cell_centers = mesh.cell_centers()
362+
centers = cell_centers.points
363+
364+
# Get scalar values - try to use the active scalars or the first available array
365+
scalar_name = mesh.active_scalars_name
366+
if scalar_name is None:
367+
# Try to find any cell data array
368+
if mesh.cell_data:
369+
scalar_name = list(mesh.cell_data.keys())[0]
370+
371+
if scalar_name is not None:
372+
values = mesh.cell_data[scalar_name]
373+
else:
374+
# If no scalar data, use zeros
375+
values = np.zeros(mesh.n_cells)
376+
377+
# Write to file
378+
with open(file_path, 'w') as f:
379+
f.write(f"# ASCII Grid Export: {object_label}\n")
380+
f.write(f"# Format: x y z value\n")
381+
f.write(f"# Number of cells: {mesh.n_cells}\n")
382+
if scalar_name:
383+
f.write(f"# Scalar field: {scalar_name}\n")
384+
f.write("#\n")
385+
386+
for i in range(len(centers)):
387+
x, y, z = centers[i]
388+
value = values[i]
389+
f.write(f"{x:.6f} {y:.6f} {z:.6f} {value:.6f}\n")
390+
328391
def remove_selected_object(self):
329392
selected_items = self.treeWidget.selectedItems()
330393
if not selected_items:

0 commit comments

Comments
 (0)