Skip to content

Commit 50cec1b

Browse files
committed
fix: add layer from qgis to visualisation
1 parent 08a50db commit 50cec1b

File tree

1 file changed

+114
-1
lines changed

1 file changed

+114
-1
lines changed

loopstructural/gui/visualisation/object_list_widget.py

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ def show_add_object_menu(self):
345345

346346
addFeatureAction = menu.addAction("Surface from model")
347347
loadFeatureAction = menu.addAction("Load from file")
348+
addQgsLayerAction = menu.addAction("Add from QGIS layer")
348349

349350
buttonPosition = self.sender().mapToGlobal(self.sender().rect().bottomLeft())
350351
action = menu.exec_(buttonPosition)
@@ -353,10 +354,122 @@ def show_add_object_menu(self):
353354
self.add_feature_from_geological_model()
354355
elif action == loadFeatureAction:
355356
self.load_feature_from_file()
356-
357+
elif action == addQgsLayerAction:
358+
self.add_object_from_qgis_layer()
357359
def add_feature_from_geological_model(self):
358360
# Logic to add a feature from the geological model
359361
print("Adding feature from geological model")
362+
def add_object_from_qgis_layer(self):
363+
"""Show a dialog to pick a QGIS point vector layer, convert it to a VTK/PyVista
364+
point cloud and copy numeric attributes as point scalars.
365+
"""
366+
# Local imports so the module can still be imported when QGIS GUI isn't available
367+
try:
368+
from qgis.gui import QgsMapLayerComboBox
369+
from qgis.core import QgsMapLayerProxyModel, QgsWkbTypes
370+
except Exception as e:
371+
print("QGIS GUI components are not available:", e)
372+
return
373+
374+
try:
375+
from loopstructural.main.vectorLayerWrapper import qgsLayerToGeoDataFrame
376+
except Exception as e:
377+
print("Could not import qgsLayerToGeoDataFrame:", e)
378+
return
379+
from loopstructural.main.model_manager import AllSampler
380+
381+
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QMessageBox
382+
import numpy as np
383+
import pandas as pd
384+
385+
dialog = QDialog(self)
386+
dialog.setWindowTitle("Add from QGIS layer")
387+
layout = QVBoxLayout(dialog)
388+
389+
layout.addWidget(QLabel("Select point layer:"))
390+
layer_combo = QgsMapLayerComboBox(dialog)
391+
# Restrict to point layers only
392+
layer_combo.setFilters(QgsMapLayerProxyModel.PointLayer)
393+
layout.addWidget(layer_combo)
394+
395+
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
396+
buttons.accepted.connect(dialog.accept)
397+
buttons.rejected.connect(dialog.reject)
398+
layout.addWidget(buttons)
399+
400+
if dialog.exec_() != QDialog.Accepted:
401+
return
402+
403+
layer = layer_combo.currentLayer()
404+
if layer is None or not layer.isValid():
405+
QMessageBox.warning(self, "Invalid layer", "No valid layer selected.")
406+
return
407+
408+
# Basic geometry check - ensure the layer contains point geometry
409+
try:
410+
if layer.wkbType() != QgsWkbTypes.Point and QgsWkbTypes.geometryType(layer.wkbType()) != QgsWkbTypes.PointGeometry:
411+
# Some QGIS versions use different enums; allow via proxy filter primarily
412+
# If the check fails, continue but warn
413+
print("Selected layer does not appear to be a point layer. Proceeding anyway.")
414+
except Exception:
415+
# ignore strict checks - rely on conversion result
416+
pass
417+
418+
# Convert layer to a DataFrame (no DTM)
419+
gdf = qgsLayerToGeoDataFrame(layer)
420+
sampler = AllSampler()
421+
# sample the points from the gdf with no DTM and include Z if present
422+
df = sampler(gdf,None,True)
423+
if df is None or df.empty:
424+
QMessageBox.warning(self, "No data", "Selected layer contains no points.")
425+
return
426+
427+
# Ensure X,Y,Z columns present
428+
if not set(["X", "Y", "Z"]).issubset(df.columns):
429+
QMessageBox.warning(self, "Invalid data", "Layer conversion did not produce X/Y/Z columns.")
430+
return
431+
432+
# Build points array
433+
try:
434+
pts = np.vstack([df["X"].to_numpy(), df["Y"].to_numpy(), df["Z"].to_numpy()]).T.astype(float)
435+
except Exception as e:
436+
QMessageBox.warning(self, "Error", f"Failed to build point coordinates: {e}")
437+
return
438+
439+
# Create PyVista point cloud / PolyData
440+
try:
441+
mesh = pv.PolyData(pts)
442+
except Exception as e:
443+
QMessageBox.warning(self, "Error", f"Failed to create mesh: {e}")
444+
return
445+
446+
# Add numeric attributes as point scalars
447+
for col in df.columns:
448+
if col in ("X", "Y", "Z"):
449+
continue
450+
try:
451+
ser = pd.to_numeric(df[col], errors='coerce')
452+
if ser.isnull().all():
453+
# no numeric values present
454+
continue
455+
arr = ser.to_numpy().astype(float)
456+
# Ensure length matches points
457+
if len(arr) != mesh.n_points:
458+
# skip columns that don't match
459+
continue
460+
mesh.point_data[col] = arr
461+
except Exception:
462+
# skip non-numeric or problematic fields
463+
continue
464+
465+
# Add to viewer
466+
if self.viewer and hasattr(self.viewer, 'add_mesh_object'):
467+
try:
468+
self.viewer.add_mesh_object(mesh, name=layer.name())
469+
except Exception as e:
470+
print("Failed to add mesh to viewer:", e)
471+
else:
472+
print("Error: Viewer is not initialized or does not support adding meshes.")
360473

361474
def load_feature_from_file(self):
362475
file_path, _ = QFileDialog.getOpenFileName(

0 commit comments

Comments
 (0)