From 8d505db4fdfad8ed9dcddd2100636ab885708a10 Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Wed, 7 Feb 2024 18:07:42 +0000 Subject: [PATCH 1/6] btn active --- qt_ui/mainWindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qt_ui/mainWindow.py b/qt_ui/mainWindow.py index afafcb1..8947298 100644 --- a/qt_ui/mainWindow.py +++ b/qt_ui/mainWindow.py @@ -617,7 +617,7 @@ class SpeckleGISDialog(QMainWindow): self.runButton.setEnabled(False) elif ( self.layerSendModeDropdown.currentIndex() == 1 - and len(plugin.dataStorage.current_layers) == 0 + and len(plugin.dataStorage.saved_layers) == 0 ): # saved layers; but the list is empty self.runButton.setEnabled(False) else: @@ -673,7 +673,7 @@ class SpeckleGISDialog(QMainWindow): all_layers = getAllProjLayers(plugin) if all_layers is None: return - + all_layers_ids = [l.dataSource for l in all_layers] for layer_tuple in plugin.dataStorage.saved_layers: if layer_tuple[1].dataSource in all_layers_ids: From 66f45e8147093911e97e74941c0da806e0eaee3a Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Sat, 10 Feb 2024 19:00:28 +0000 Subject: [PATCH 2/6] arcgis support: crs format; pin window --- qt_ui/mainWindow.py | 35 ++++++++++++++++++++++++----- qt_ui/utils/assets/pin-outline.png | Bin 0 -> 3670 bytes qt_ui/utils/assets/pin.png | Bin 0 -> 3430 bytes qt_ui/utils/global_resources.py | 7 ++++++ qt_ui/widget_report.py | 23 ++++++++++++++----- 5 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 qt_ui/utils/assets/pin-outline.png create mode 100644 qt_ui/utils/assets/pin.png diff --git a/qt_ui/mainWindow.py b/qt_ui/mainWindow.py index 8947298..4ad8c55 100644 --- a/qt_ui/mainWindow.py +++ b/qt_ui/mainWindow.py @@ -57,6 +57,8 @@ from speckle.specklepy_qt_ui.qt_ui.utils.global_resources import ( ICON_SEARCH, ICON_DELETE, ICON_DELETE_BLUE, + ICON_PIN_ACTIVE, + ICON_PIN_DISABLED, ICON_SEND, ICON_RECEIVE, ICON_SEND_BLACK, @@ -84,6 +86,8 @@ ui_file_path = os.path.join( class SpeckleGISDialog(QMainWindow): + on_top: bool = True + pin_label: QtWidgets.QPushButton closingPlugin = pyqtSignal() streamList: QtWidgets.QComboBox sendModeButton: QtWidgets.QPushButton @@ -111,9 +115,7 @@ class SpeckleGISDialog(QMainWindow): def __init__(self, parent=None): """Constructor.""" print("START MAIN WINDOW") - super(SpeckleGISDialog, self).__init__( - parent - ) # , QtCore.Qt.WindowStaysOnTopHint) + super(SpeckleGISDialog, self).__init__(parent, QtCore.Qt.WindowStaysOnTopHint) uic.loadUi(ui_file_path, self) # Load the .ui file # self.show() self.runAllSetup() @@ -217,7 +219,7 @@ class SpeckleGISDialog(QMainWindow): self.runButton.setIcon(QIcon(ICON_SEND)) # insert checkbox - l = self.verticalLayout + # l = self.verticalLayout except Exception as e: logToUser(str(e), level=2, func=inspect.stack()[0][3], plugin=self) @@ -300,16 +302,39 @@ class SpeckleGISDialog(QMainWindow): boxLayout = QHBoxLayout(widget) boxLayout.addWidget(text_label) # , alignment=Qt.AlignCenter) boxLayout.addWidget(version_label) - boxLayout.setContentsMargins(0, 0, 0, 0) + boxLayout.setContentsMargins(0, 0, 30, 0) self.setWindowTitle("SpeckleArcGIS") self.gridLayoutTitleBar.addWidget(widget) # fro QMainWindow # self.setTitleBarWidget(widget) # for dockwidget self.labelWidget = text_label self.labelWidget.setCursor(QCursor(QtCore.Qt.PointingHandCursor)) self.labelWidget.clicked.connect(self.onClickLogo) + + pin_label = QtWidgets.QPushButton("") + pin_label.setIcon(QIcon(ICON_PIN_ACTIVE)) + pin_label.setMaximumWidth(25) + # pin_label.setFlat(True) + pin_label.setStyleSheet("QPushButton {border: none;}") + boxLayout.addWidget(pin_label) + pin_label.setCursor(QCursor(QtCore.Qt.PointingHandCursor)) + self.pin_label = pin_label + self.pin_label.clicked.connect(self.pinWindow) + except Exception as e: logToUser(e) + def pinWindow(self): + if self.on_top == True: + self.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint, False) + self.pin_label.setIcon(QIcon(ICON_PIN_DISABLED)) + self.on_top = False + self.show() + else: + self.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint, True) + self.pin_label.setIcon(QIcon(ICON_PIN_ACTIVE)) + self.on_top = True + self.show() + def addDataStorage(self, plugin): self.dataStorage = plugin.dataStorage try: diff --git a/qt_ui/utils/assets/pin-outline.png b/qt_ui/utils/assets/pin-outline.png new file mode 100644 index 0000000000000000000000000000000000000000..682d64a89d0194f5a0c10d86466663c564e87a45 GIT binary patch literal 3670 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 Date: Sat, 10 Feb 2024 23:17:35 +0000 Subject: [PATCH 3/6] fix arcgis report on receive --- qt_ui/utils/utils.py | 2 ++ qt_ui/widget_report.py | 30 ++++++++++++++++-------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/qt_ui/utils/utils.py b/qt_ui/utils/utils.py index 8cc73b6..41be9b1 100644 --- a/qt_ui/utils/utils.py +++ b/qt_ui/utils/utils.py @@ -2,6 +2,8 @@ from textwrap import wrap from typing import Union import requests +SYMBOL = "_x_x_" + def splitTextIntoLines(text: str = "", number: int = 40) -> str: msg = "" diff --git a/qt_ui/widget_report.py b/qt_ui/widget_report.py index 47aa11a..a5d0a59 100644 --- a/qt_ui/widget_report.py +++ b/qt_ui/widget_report.py @@ -5,9 +5,11 @@ from typing import List, Tuple, Union try: from specklepy_qt_ui.qt_ui.DataStorage import DataStorage from specklepy_qt_ui.qt_ui.utils.global_resources import COLOR + from specklepy_qt_ui.qt_ui.utils.utils import SYMBOL except ModuleNotFoundError: from speckle.specklepy_qt_ui.qt_ui.DataStorage import DataStorage from speckle.specklepy_qt_ui.qt_ui.utils.global_resources import COLOR + from speckle.specklepy_qt_ui.qt_ui.utils.utils import SYMBOL # from specklepy_qt_ui.qt_ui.utils.logger import logToUser @@ -51,7 +53,7 @@ class ReportDialog(QtWidgets.QWidget, FORM_CLASS): try: if self.dataStorage is None: return - reportList = self.dataStorage.latestActionReport + reportList: List[dict] = self.dataStorage.latestActionReport if reportList is None: return @@ -67,11 +69,13 @@ class ReportDialog(QtWidgets.QWidget, FORM_CLASS): for item in reportList: line = "✅ " try: # if sending - line += f'{item["feature_id"]}: {item["obj_type"]}' + some_id = item["feature_id"].replace(SYMBOL, "\\") + line += f'{some_id}: {item["obj_type"]}' operation = f"Sent at {self.dataStorage.latestActionTime}" except: # if receiving sending = False - line += f'{item["speckle_id"]}: {item["obj_type"]}' + some_id = item[list(item.keys())[0]].replace(SYMBOL, "\\") + line += f'{some_id}: {item["obj_type"]}' # f'{item["speckle_id"]}: {item["obj_type"]}' operation = f"Received at {self.dataStorage.latestActionTime}" # edit based on the type @@ -123,14 +127,12 @@ class ReportDialog(QtWidgets.QWidget, FORM_CLASS): # add info about the offsets try: - text += "Project CRS: " + self.dataStorage.project.crs().authid() + "\n" + crs = self.dataStorage.project.crs() + text += "Project CRS: " + crs.authid() + "\n" except AttributeError: + crs = self.dataStorage.project.activeMap.spatialReference CRS_KEYWORD = "Spatial Reference" - text += ( - f"Project {CRS_KEYWORD}: " - + self.dataStorage.project.activeMap.spatialReference.name - + "\n" - ) + text += f"Project {CRS_KEYWORD}: " + crs.name + "\n" units = self.dataStorage.latestActionUnits text += ( f"Project {CRS_KEYWORD} units: " @@ -138,11 +140,11 @@ class ReportDialog(QtWidgets.QWidget, FORM_CLASS): + f"{' (not supported, treated as Meters)' if 'degrees' in units else ''}" + "\n" ) - text += ( - f"Project {CRS_KEYWORD} WKT: \n" - + self.dataStorage.project.crs().toWkt() - + "\n\n" - ) + try: + text += f"Project {CRS_KEYWORD} WKT: \n" + crs.toWkt() + "\n\n" + except: + text += f"Project {CRS_KEYWORD} WKT: \n" + crs.exportToString() + "\n\n" + text += ( f"{CRS_KEYWORD} offsets: x={self.dataStorage.crs_offset_x}, y={self.dataStorage.crs_offset_y}" + "\n" From 30c3514bded8dbb42fa8a2c52d1a4368e1db777d Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Sun, 11 Feb 2024 18:15:45 +0000 Subject: [PATCH 4/6] get layers with structure --- qt_ui/mainWindow.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/qt_ui/mainWindow.py b/qt_ui/mainWindow.py index 4ad8c55..bfcff1b 100644 --- a/qt_ui/mainWindow.py +++ b/qt_ui/mainWindow.py @@ -40,7 +40,7 @@ import arcpy import inspect # from speckle.speckle_arcgis_new import Speckle -from speckle.speckle.converter.layers import getLayers, getAllProjLayers +from speckle.speckle.converter.layers import getLayersWithStructure, getAllProjLayers from speckle.speckle.utils.panel_logging import logToUser from speckle.specklepy_qt_ui.qt_ui.LogWidget import LogWidget from speckle.specklepy_qt_ui.qt_ui.utils.utils import constructCommitURL @@ -710,16 +710,18 @@ class SpeckleGISDialog(QMainWindow): print("populate layers from selection") plugin.dataStorage.current_layers = [] - layers = getLayers(plugin, bySelection=True) # List[QgsLayerTreeNode] - print(layers) + layers, structure = getLayersWithStructure( + plugin, bySelection=True + ) # List[QgsLayerTreeNode] + #print(layers) if layers is not None: for i, layer in enumerate(layers): plugin.dataStorage.current_layers.append((layer.name, layer)) listItem = self.fillLayerList(layer) self.layersWidget.addItem(listItem) - print("populate layers from selection 2") + #print("populate layers from selection 2") set_project_layer_selection(plugin) - print("populate layers from selection 3") + #print("populate layers from selection 3") self.layersWidget.setIconSize(QSize(20, 20)) self.runBtnStatusChanged(plugin) @@ -729,7 +731,7 @@ class SpeckleGISDialog(QMainWindow): logToUser(str(e), level=2, func=inspect.stack()[0][3], plugin=self) def fillLayerList(self, layer): - print("Fill layer list") + #print("Fill layer list") try: listItem = QListWidgetItem(layer.name) From 0b099810e50a628e65bd2f362f2b3b22c543d22e Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Mon, 12 Feb 2024 00:03:43 +0000 Subject: [PATCH 5/6] don't pin window on launch --- qt_ui/mainWindow.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/qt_ui/mainWindow.py b/qt_ui/mainWindow.py index bfcff1b..72c6f8b 100644 --- a/qt_ui/mainWindow.py +++ b/qt_ui/mainWindow.py @@ -86,7 +86,7 @@ ui_file_path = os.path.join( class SpeckleGISDialog(QMainWindow): - on_top: bool = True + on_top: bool = False pin_label: QtWidgets.QPushButton closingPlugin = pyqtSignal() streamList: QtWidgets.QComboBox @@ -115,7 +115,9 @@ class SpeckleGISDialog(QMainWindow): def __init__(self, parent=None): """Constructor.""" print("START MAIN WINDOW") - super(SpeckleGISDialog, self).__init__(parent, QtCore.Qt.WindowStaysOnTopHint) + super(SpeckleGISDialog, self).__init__( + parent + ) # , QtCore.Qt.WindowStaysOnTopHint) uic.loadUi(ui_file_path, self) # Load the .ui file # self.show() self.runAllSetup() @@ -311,7 +313,7 @@ class SpeckleGISDialog(QMainWindow): self.labelWidget.clicked.connect(self.onClickLogo) pin_label = QtWidgets.QPushButton("") - pin_label.setIcon(QIcon(ICON_PIN_ACTIVE)) + pin_label.setIcon(QIcon(ICON_PIN_DISABLED)) pin_label.setMaximumWidth(25) # pin_label.setFlat(True) pin_label.setStyleSheet("QPushButton {border: none;}") @@ -713,15 +715,15 @@ class SpeckleGISDialog(QMainWindow): layers, structure = getLayersWithStructure( plugin, bySelection=True ) # List[QgsLayerTreeNode] - #print(layers) + # print(layers) if layers is not None: for i, layer in enumerate(layers): plugin.dataStorage.current_layers.append((layer.name, layer)) listItem = self.fillLayerList(layer) self.layersWidget.addItem(listItem) - #print("populate layers from selection 2") + # print("populate layers from selection 2") set_project_layer_selection(plugin) - #print("populate layers from selection 3") + # print("populate layers from selection 3") self.layersWidget.setIconSize(QSize(20, 20)) self.runBtnStatusChanged(plugin) @@ -731,7 +733,7 @@ class SpeckleGISDialog(QMainWindow): logToUser(str(e), level=2, func=inspect.stack()[0][3], plugin=self) def fillLayerList(self, layer): - #print("Fill layer list") + # print("Fill layer list") try: listItem = QListWidgetItem(layer.name) From 563b54aa600be81e96e652e9e89ac9838c3486c3 Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Mon, 12 Feb 2024 13:28:26 +0000 Subject: [PATCH 6/6] add workspace property --- qt_ui/DataStorage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qt_ui/DataStorage.py b/qt_ui/DataStorage.py index 5615f0d..70ac8e2 100644 --- a/qt_ui/DataStorage.py +++ b/qt_ui/DataStorage.py @@ -5,7 +5,7 @@ import webbrowser try: from specklepy_qt_ui.qt_ui.utils.logger import logToUser -except ModuleNotFoundError: +except ModuleNotFoundError: from speckle.specklepy_qt_ui.qt_ui.utils.logger import logToUser from specklepy.core.api.credentials import get_local_accounts @@ -22,6 +22,7 @@ class DataStorage: currentCRS = None currentUnits = "m" currentOriginalUnits = "" + workspace = "" custom_lat: Optional[float] = None custom_lon: Optional[float] = None