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 pyqt_ui.logger import displayUserMsg, logToUser import pyqt_ui.dockwidget_main from qgis.core import Qgis, QgsProject, QgsVectorLayer, QgsRasterLayer, QgsIconUtils from speckle.utils.panel_logging import logger from qgis.PyQt import QtWidgets, uic, QtCore from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtWidgets import QCheckBox, QListWidgetItem from qgis.PyQt.QtCore import pyqtSignal from specklepy.api.models import Stream from specklepy.api.client import SpeckleClient from specklepy.logging.exceptions import SpeckleException from speckle.utils.utils import logger from specklepy.api.credentials import Account, get_local_accounts #, StreamWrapper from specklepy.api.wrapper import StreamWrapper from gql import gql 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")