Files
specklepy_qt_ui/widget_transforms.py
T
2023-06-15 00:27:19 +01:00

349 lines
15 KiB
Python

import inspect
import os
from typing import Any, List, Tuple, Union
from speckle.converter.layers import getAllLayers
from speckle.converter.layers.utils import getElevationLayer, getLayerGeomType
from specklepy_qt_ui.logger import displayUserMsg, logToUser
from qgis.core import QgsVectorLayer, QgsRasterLayer, QgsIconUtils
from PyQt5 import QtWidgets, uic, QtCore
from PyQt5.QtWidgets import QListWidgetItem
from specklepy.logging import metrics
from osgeo import gdal
import webbrowser
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(
os.path.join(os.path.join(os.path.dirname(__file__), "ui", "transforms.ui") )
)
class MappingSendDialog(QtWidgets.QWidget, FORM_CLASS):
dialog_button_box: QtWidgets.QPushButton = None
more_info: QtWidgets.QPushButton = None
layerDropdown: QtWidgets.QComboBox
transformDropdown: QtWidgets.QComboBox
addTransform: QtWidgets.QPushButton
removeTransform: QtWidgets.QPushButton
transformationsList: QtWidgets.QListWidget
elevationLayerDropdown: QtWidgets.QComboBox
attrDropdown: QtWidgets.QComboBox
dataStorage: Any = None
def __init__(self, parent=None):
super(MappingSendDialog,self).__init__(parent,QtCore.Qt.WindowStaysOnTopHint)
self.setupUi(self)
self.setMinimumWidth(600)
self.addTransform.setStyleSheet("QPushButton {color: black; padding:3px;padding-left:5px;border: none; } QPushButton:hover { background-color: lightgrey}")
self.removeTransform.setStyleSheet("QPushButton {color: black; padding:3px;padding-left:5px;border: none; } QPushButton:hover { background-color: lightgrey}")
#self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.onOkClicked)
self.addTransform.clicked.connect(self.onAddTransform)
self.removeTransform.clicked.connect(self.onRemoveTransform)
self.transformDropdown.currentIndexChanged.connect(self.populateLayersByTransform)
self.transformDropdown.currentIndexChanged.connect(self.populateAttributesByLayer)
self.layerDropdown.currentIndexChanged.connect(self.populateAttributesByLayer)
self.dialog_button_box.clicked.connect(self.saveElevationLayer)
self.dialog_button_box.clicked.connect(self.onOkClicked)
self.more_info.clicked.connect(self.onMoreInfo)
return
def runSetup(self):
self.attr_label.setEnabled(False)
self.attrDropdown.setEnabled(False)
self.populateTransforms()
self.populateLayersByTransform()
self.populateSavedTransforms()
self.populateSavedElevationLayer()
def populateSavedTransforms(self, dataStorage = None): #, savedTransforms: Union[List, None] = None, getLayer: Union[str, None] = None, getTransform: Union[str, None] = None):
if dataStorage is not None:
self.dataStorage = dataStorage # making sure lists are synced
self.transformationsList.clear()
vals = self.dataStorage.savedTransforms
all_l_names = [l.name() for l in self.dataStorage.all_layers]
for item in vals:
layer_name = item.split(" -> ")[0].split(" (\'")[0]
transform_name = item.split(" -> ")[1]
layer = None
for l in self.dataStorage.all_layers:
if layer_name == l.name():
layer = l
if layer is None:
displayUserMsg(f"Layer \'{layer_name}\' not found in the project.\nTransformation is removed.", level=2)
self.dataStorage.savedTransforms.remove(item)
else:
if transform_name not in self.dataStorage.transformsCatalog:
displayUserMsg(f"Saved transformation \'{transform_name}\' is not valid.\n. Transformation is removed.", level=1)
self.dataStorage.savedTransforms.remove(item)
elif all_l_names.count(layer.name()) > 1:
displayUserMsg(f"Layer name \'{layer.name()}\' is used for more than 1 layer in the project", level=1)
self.dataStorage.savedTransforms.remove(item)
else:
listItem = QListWidgetItem(item)
icon = QgsIconUtils().iconForLayer(layer)
listItem.setIcon(icon)
self.transformationsList.addItem(listItem)
def onAddTransform(self):
from speckle.utils.project_vars import set_transformations
root = self.dataStorage.project.layerTreeRoot()
self.dataStorage.all_layers = getAllLayers(root)
if len(self.layerDropdown.currentText())>1 and len(self.transformDropdown.currentText())>1:
listItem = str(self.layerDropdown.currentText()) + " -> " + str(self.transformDropdown.currentText())
layer_name = listItem.split(" -> ")[0].split(" (\'")[0]
transform_name = listItem.split(" -> ")[1].lower()
exists = 0
for record in self.dataStorage.savedTransforms:
current_layer_name = record.split(" -> ")[0].split(" (\'")[0]
current_transf_name = record.split(" -> ")[1].lower()
if layer_name == current_layer_name: # in layers
exists +=1
displayUserMsg("Selected layer already has a transformation applied", level=1)
break
if ("elevation" in transform_name and "mesh" in transform_name and "texture" not in transform_name) and transform_name == current_transf_name: # in transforms
exists +=1
displayUserMsg(f"Layer '{current_layer_name}' is already assigned as a 3d elevation", level=1)
break
if exists == 0:
layer = None
for l in self.dataStorage.all_layers:
if layer_name == l.name():
layer = l
if layer is not None:
if "attribute" in transform_name and self.attrDropdown.currentText() != '':
listItem = str(self.layerDropdown.currentText()) + " (\'" + str(self.attrDropdown.currentText()) + "\') -> " + str(self.transformDropdown.currentText())
self.dataStorage.savedTransforms.append(listItem)
self.populateSavedTransforms()
try:
metrics.track("Connector Action", self.dataStorage.active_account, {"name": "Add transformation on Send", "Transformation": listItem.split(" -> ")[1], "connector_version": str(self.dataStorage.plugin_version)})
except Exception as e:
logToUser(e, level = 2, func = inspect.stack()[0][3] )
set_transformations(self.dataStorage)
def onRemoveTransform(self):
from speckle.utils.project_vars import set_transformations
if self.transformationsList.currentItem() is not None:
listItem = self.transformationsList.currentItem().text()
print(listItem)
if listItem in self.dataStorage.savedTransforms:
self.dataStorage.savedTransforms.remove(listItem)
self.populateSavedTransforms()
set_transformations(self.dataStorage)
def onOkClicked(self):
try:
self.close()
except Exception as e:
logToUser(e, level = 2, func = inspect.stack()[0][3])
return
def populateLayers(self):
try:
self.layerDropdown.clear()
root = self.dataStorage.project.layerTreeRoot()
self.dataStorage.all_layers = getAllLayers(root)
for i, layer in enumerate(self.dataStorage.all_layers):
listItem = layer.name()
self.layerDropdown.addItem(listItem)
icon = QgsIconUtils().iconForLayer(layer)
self.layerDropdown.setItemIcon(i, icon)
except Exception as e:
logToUser(e, level = 2, func = inspect.stack()[0][3])
return
def populateLayersByTransform(self):
try:
self.layerDropdown.clear()
root = self.dataStorage.project.layerTreeRoot()
self.dataStorage.all_layers = getAllLayers(root)
transform = str(self.transformDropdown.currentText())
layers_dropdown = []
for i, layer in enumerate(self.dataStorage.all_layers):
listItem = None
if "extrude" in transform.lower():
if isinstance(layer, QgsVectorLayer):
geom_type = getLayerGeomType(layer)
if "polygon" in geom_type.lower():
listItem = layer.name()
elif "elevation" in transform.lower():
if isinstance(layer, QgsRasterLayer):
# avoiding tiling layers
ds = gdal.Open(layer.source(), gdal.GA_ReadOnly)
if ds is None:
continue
listItem = layer.name()
if listItem is not None:
layers_dropdown.append(listItem)
self.layerDropdown.addItem(listItem)
icon = QgsIconUtils().iconForLayer(layer)
self.layerDropdown.setItemIcon(len(layers_dropdown)-1, icon)
except Exception as e:
logToUser(e, level = 2, func = inspect.stack()[0][3])
return
def populateAttributesByLayer(self):
try:
self.attrDropdown.clear()
root = self.dataStorage.project.layerTreeRoot()
self.dataStorage.all_layers = getAllLayers(root)
layer_name = str(self.layerDropdown.currentText())
transform_name = self.transformDropdown.currentText()
layerForAttributes = None
for i, layer in enumerate(self.dataStorage.all_layers):
if layer_name == layer.name():
if isinstance(layer, QgsVectorLayer):
geom_type = getLayerGeomType(layer)
if "polygon" in geom_type.lower():
layerForAttributes = layer
break
if layerForAttributes is not None and 'attribute' in transform_name:
self.attr_label.setEnabled(True)
self.attrDropdown.setEnabled(True)
if 'ignore' not in transform_name:
self.attrDropdown.addItem('Random height')
for field in layerForAttributes.fields():
field_type = field.type()
if field_type in [2,6,10]:
self.attrDropdown.addItem(str(field.name()))
else:
self.attr_label.setEnabled(False)
self.attrDropdown.setEnabled(False)
except Exception as e:
logToUser(e, level = 2, func = inspect.stack()[0][3])
return
def populateTransforms(self):
try:
self.transformDropdown.clear()
for item in self.dataStorage.transformsCatalog:
self.transformDropdown.addItem(item)
except Exception as e:
logToUser(e, level = 2, func = inspect.stack()[0][3])
return
def nameCheck(self):
return
try:
if len(self.name_field.text()) == 0 or len(self.name_field.text()) >= 3:
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
else:
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)
return
except Exception as e:
logToUser(e, level = 2, func = inspect.stack()[0][3])
return
def populateSavedElevationLayer(self, dataStorage = None): #, savedTransforms: Union[List, None] = None, getLayer: Union[str, None] = None, getTransform: Union[str, None] = None):
try:
if dataStorage is not None:
self.dataStorage = dataStorage # making sure lists are synced
elevationLayer = getElevationLayer(self.dataStorage)
self.elevationLayerDropdown.clear()
root = self.dataStorage.project.layerTreeRoot()
self.dataStorage.all_layers = getAllLayers(root)
self.elevationLayerDropdown.addItem("")
setAsindex = 0
countRaster = 1
for i, layer in enumerate(self.dataStorage.all_layers):
if isinstance(layer, QgsRasterLayer):
# avoiding tiling layers
ds = gdal.Open(layer.source(), gdal.GA_ReadOnly)
if ds is None:
continue
listItem = layer.name()
self.elevationLayerDropdown.addItem(listItem)
icon = QgsIconUtils().iconForLayer(layer)
self.elevationLayerDropdown.setItemIcon(countRaster, icon)
if elevationLayer is not None:
if listItem == elevationLayer.name():
setAsindex = countRaster
countRaster += 1
self.elevationLayerDropdown.setCurrentIndex(setAsindex)
except Exception as e:
logToUser(e, level = 2, func = inspect.stack()[0][3])
return
def saveElevationLayer(self):
from speckle.utils.project_vars import set_elevationLayer
root = self.dataStorage.project.layerTreeRoot()
layer = None
if self.dataStorage is None: return
layerName = str(self.elevationLayerDropdown.currentText())
if len(layerName) < 1:
layer = None
else:
self.dataStorage.all_layers = getAllLayers(root)
all_l_names = [l.name() for l in self.dataStorage.all_layers]
for l in self.dataStorage.all_layers:
if layerName == l.name():
layer = l
try:
if all_l_names.count(layer.name()) > 1:
displayUserMsg(f"Layer name \'{layer.name()}\' is used for more than 1 layer in the project", level=1)
layer = None
break
except:
displayUserMsg(f"Layer \'{layer.name()}\' is not found in the project", level=1)
layer = None
break
self.dataStorage.elevationLayer = layer
set_elevationLayer(self.dataStorage)
try:
metrics.track("Connector Action", self.dataStorage.active_account, {"name": "Add transformation on Send", "Transformation": "Set Layer as Elevation", "connector_version": str(self.dataStorage.plugin_version)})
except Exception as e:
logToUser(e, level = 2, func = inspect.stack()[0][3] )
def onMoreInfo(self):
webbrowser.open("https://speckle.guide/user/qgis.html#transformations")