2 Commits

Author SHA1 Message Date
KatKatKateryna 7b99bedd8d working dashboards 2023-11-10 15:18:45 +00:00
KatKatKateryna d71fd4becf add dashboard ui 2023-11-03 16:29:27 +00:00
41 changed files with 1184 additions and 1255 deletions
+36 -57
View File
@@ -1,17 +1,13 @@
from datetime import datetime
import inspect
from typing import List, Optional, Tuple, Union, Any
import webbrowser
try:
from specklepy_qt_ui.qt_ui.utils.logger import logToUser
except ModuleNotFoundError:
from speckle.specklepy_qt_ui.qt_ui.utils.logger import logToUser
from speckle.utils.panel_logging import logToUser
from specklepy.core.api.credentials import get_local_accounts
class DataStorage:
plugin_version = "0.0.99"
project = None
@@ -22,85 +18,68 @@ class DataStorage:
currentCRS = None
currentUnits = "m"
currentOriginalUnits = ""
workspace = ""
custom_lat: Optional[float] = None
custom_lon: Optional[float] = None
crs_offset_x: Optional[float] = 0
crs_offset_y: Optional[float] = 0
crs_rotation: Optional[float] = 0
crs_offset_x: Optional[float] = 0
crs_offset_y: Optional[float] = 0
crs_rotation: Optional[float] = 0
current_layer_crs_offset_x: Optional[float] = None
current_layer_crs_offset_y: Optional[float] = None
current_layer_crs_rotation: Optional[float] = None
current_layer_crs_offset_x: Optional[float] = None
current_layer_crs_offset_y: Optional[float] = None
current_layer_crs_rotation: Optional[float] = None
current_layers: Union[List[Tuple[Any, str, str]], None] = None
saved_layers: Union[List, None] = None
all_layers: Union[List, None] = None
current_layers: Union[List[Tuple[Any, str, str]], None] = None
saved_layers: Union[List, None] = None
all_layers: Union[List, None] = None
elevationLayer: None
elevationLayer: None
savedTransforms: Union[List, None] = None
transformsCatalog: Union[List, None] = None
matrix = None # if receiving instance with transform
matrix = None # if receiving instance with transform
latestHostApp: str = ""
latestActionReport: Optional[list] = None
latestActionFeaturesReport: Optional[list] = None
latestActionReport: Optional[list] = None
latestActionFeaturesReport: Optional[list] = None
latestActionTime: str = ""
latestTransferTime: datetime = None
latestConversionTime: datetime = None
latestActionLayers: Optional[list] = None
latestActionLayers: Optional[list] = None
latestActionUnits: str = ""
flat_report_receive: dict = {}
flat_report_latest: dict = {}
def __init__(self):
# print("hello")
# self.streamsToFollow.append(("https://speckle.xyz/streams/17b0b76d13/branches/random_tests", "", "09a0f3e41a"))
self.transformsCatalog = [
"Convert Raster Elevation to a 3d Mesh",
"Set Raster as a Texture for the Elevation Layer",
"Extrude polygons by selected attribute (randomly populate NULL values)",
"Extrude polygons by selected attribute (ignore NULL values)",
"Extrude polygons by selected attribute (randomly populate NULL values) and project on 3d elevation",
"Extrude polygons by selected attribute (ignore NULL values) and project on 3d elevation",
]
#print("hello")
#self.streamsToFollow.append(("https://speckle.xyz/streams/17b0b76d13/branches/random_tests", "", "09a0f3e41a"))
self.transformsCatalog = ["Convert Raster Elevation to a 3d Mesh",
"Set Raster as a Texture for the Elevation Layer",
"Extrude polygons by selected attribute (randomly populate NULL values)",
"Extrude polygons by selected attribute (ignore NULL values)",
"Extrude polygons by selected attribute (randomly populate NULL values) and project on 3d elevation",
"Extrude polygons by selected attribute (ignore NULL values) and project on 3d elevation"
]
self.savedTransforms = []
self.all_layers = []
self.current_layers = []
self.saved_layers = []
self.accounts = []
self.elevationLayer = None
self.latestActionReport = []
self.latestActionFeaturesReport = []
self.latestActionLayers = []
self.accounts = []
self.elevationLayer = None
def check_for_accounts(self):
try:
def go_to_manager():
webbrowser.open("https://speckle-releases.netlify.app/")
accounts = get_local_accounts()
self.accounts = accounts
if len(accounts) == 0:
logToUser(
"No accounts were found. Please remember to install the Speckle Manager and setup at least one account",
level=1,
url="https://speckle-releases.netlify.app/",
func=inspect.stack()[0][3],
plugin=self.dockwidget,
) # , action_text="Download Manager", callback=go_to_manager)
logToUser("No accounts were found. Please remember to install the Speckle Manager and setup at least one account", level = 1, url="https://speckle-releases.netlify.app/", func = inspect.stack()[0][3], plugin = self.dockwidget) #, action_text="Download Manager", callback=go_to_manager)
return False
for acc in accounts:
if acc.isDefault:
self.default_account = acc
self.active_account = acc
break
if acc.isDefault:
self.default_account = acc
self.active_account = acc
break
return True
except Exception as e:
logToUser(e, level=2, func=inspect.stack()[0][3])
logToUser(e, level = 2, func = inspect.stack()[0][3])
return
+21 -41
View File
@@ -9,38 +9,19 @@ import webbrowser
from specklepy.logging import metrics
from specklepy.core.api.credentials import Account
try:
from specklepy_qt_ui.qt_ui.utils.global_resources import (
BACKGR_COLOR,
BACKGR_COLOR_LIGHT,
BACKGR_COLOR_GREY,
BACKGR_COLOR_TRANSPARENT,
BACKGR_COLOR_HIGHLIGHT,
NEW_GREY,
NEW_GREY_HIGHLIGHT,
BACKGR_ERROR_COLOR,
BACKGR_ERROR_COLOR_LIGHT,
)
from specklepy_qt_ui.qt_ui.widget_dependencies_upgrade import (
DependenciesUpgradeDialog,
)
from specklepy_qt_ui.qt_ui.widget_report import ReportDialog
except ModuleNotFoundError:
from speckle.specklepy_qt_ui.qt_ui.utils.global_resources import (
BACKGR_COLOR,
BACKGR_COLOR_LIGHT,
BACKGR_COLOR_GREY,
BACKGR_COLOR_TRANSPARENT,
BACKGR_COLOR_HIGHLIGHT,
NEW_GREY,
NEW_GREY_HIGHLIGHT,
BACKGR_ERROR_COLOR,
BACKGR_ERROR_COLOR_LIGHT,
)
from speckle.specklepy_qt_ui.qt_ui.widget_dependencies_upgrade import (
DependenciesUpgradeDialog,
)
from speckle.specklepy_qt_ui.qt_ui.widget_report import ReportDialog
from specklepy_qt_ui.qt_ui.global_resources import (
BACKGR_COLOR,
BACKGR_COLOR_LIGHT,
BACKGR_COLOR_GREY,
BACKGR_COLOR_TRANSPARENT,
BACKGR_COLOR_HIGHLIGHT,
NEW_GREY,
NEW_GREY_HIGHLIGHT,
BACKGR_ERROR_COLOR,
BACKGR_ERROR_COLOR_LIGHT,
)
from specklepy_qt_ui.qt_ui.widget_dependencies_upgrade import DependenciesUpgradeDialog
from specklepy_qt_ui.qt_ui.widget_report import ReportDialog
class LogWidget(QWidget):
@@ -50,6 +31,7 @@ class LogWidget(QWidget):
btns: List[QPushButton]
max_msg: int
sendMessage = pyqtSignal(object)
dataStorage = None
reportBtn = None
active_account: Account
@@ -287,22 +269,20 @@ class LogWidget(QWidget):
def getNextBtn(self):
index = len(self.used_btns) # get the next "free" button
# print(index)
# print(self.btns)
if index >= len(self.btns):
# remove first button
print(self.layout.itemAt(0).widget())
self.layout.itemAt(0).widget().setParent(None)
# self.used_btns.clear()
self.createBtns()
index = 0
btn = self.btns[index]
return btn, index
def getLastBtn(self):
index = len(self.used_btns) - 1 # get the next "free" button
btn = None
if index > 0:
btn = self.btns[index]
# print(btn)
return btn, index
def getBtnByKeyword(self, keyword: str):

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 345 B

After

Width:  |  Height:  |  Size: 345 B

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Before

Width:  |  Height:  |  Size: 400 B

After

Width:  |  Height:  |  Size: 400 B

+68 -77
View File
@@ -3,72 +3,39 @@
from copy import copy
import inspect
import os
from specklepy_qt_ui.qt_ui.widget_dashboard import SpeckleDashboard
try:
from specklepy_qt_ui.qt_ui.widget_transforms import MappingSendDialog
from specklepy_qt_ui.qt_ui.LogWidget import LogWidget
from specklepy_qt_ui.qt_ui.utils.logger import logToUser
from specklepy_qt_ui.qt_ui.utils.utils import constructCommitURL
from specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from specklepy_qt_ui.qt_ui.utils.global_resources import (
COLOR_HIGHLIGHT,
SPECKLE_COLOR,
SPECKLE_COLOR_LIGHT,
ICON_OPEN_WEB,
ICON_REPORT,
ICON_LOGO,
ICON_SEARCH,
ICON_DELETE,
ICON_DELETE_BLUE,
ICON_SEND,
ICON_RECEIVE,
ICON_SEND_BLACK,
ICON_RECEIVE_BLACK,
ICON_SEND_BLUE,
ICON_RECEIVE_BLUE,
COLOR,
BACKGR_COLOR,
BACKGR_COLOR_LIGHT,
ICON_XXL,
ICON_RASTER,
ICON_POLYGON,
ICON_LINE,
ICON_POINT,
ICON_GENERIC,
)
except ModuleNotFoundError:
from speckle.specklepy_qt_ui.qt_ui.widget_transforms import MappingSendDialog
from speckle.specklepy_qt_ui.qt_ui.LogWidget import LogWidget
from speckle.specklepy_qt_ui.qt_ui.utils.logger import logToUser
from speckle.specklepy_qt_ui.qt_ui.utils.utils import constructCommitURL
from speckle.specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from speckle.specklepy_qt_ui.qt_ui.utils.global_resources import (
COLOR_HIGHLIGHT,
SPECKLE_COLOR,
SPECKLE_COLOR_LIGHT,
ICON_OPEN_WEB,
ICON_REPORT,
ICON_LOGO,
ICON_SEARCH,
ICON_DELETE,
ICON_DELETE_BLUE,
ICON_SEND,
ICON_RECEIVE,
ICON_SEND_BLACK,
ICON_RECEIVE_BLACK,
ICON_SEND_BLUE,
ICON_RECEIVE_BLUE,
COLOR,
BACKGR_COLOR,
BACKGR_COLOR_LIGHT,
ICON_XXL,
ICON_RASTER,
ICON_POLYGON,
ICON_LINE,
ICON_POINT,
ICON_GENERIC,
)
from specklepy_qt_ui.qt_ui.widget_transforms import MappingSendDialog
from specklepy_qt_ui.qt_ui.LogWidget import LogWidget
from specklepy_qt_ui.qt_ui.logger import logToUser
from specklepy_qt_ui.qt_ui.utils import constructCommitURL
from specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from specklepy_qt_ui.qt_ui.global_resources import (
COLOR_HIGHLIGHT,
SPECKLE_COLOR,
SPECKLE_COLOR_LIGHT,
ICON_OPEN_WEB,
ICON_REPORT,
ICON_LOGO,
ICON_SEARCH,
ICON_DELETE,
ICON_DELETE_BLUE,
ICON_SEND,
ICON_RECEIVE,
ICON_SEND_BLACK,
ICON_RECEIVE_BLACK,
ICON_SEND_BLUE,
ICON_RECEIVE_BLUE,
COLOR,
BACKGR_COLOR,
BACKGR_COLOR_LIGHT,
ICON_XXL,
ICON_RASTER,
ICON_POLYGON,
ICON_LINE,
ICON_POINT,
ICON_GENERIC,
)
from specklepy.logging.exceptions import SpeckleException, GraphQLException
from specklepy.logging import metrics
@@ -77,7 +44,7 @@ from PyQt5 import QtWidgets, uic
from PyQt5.QtGui import QIcon, QPixmap, QCursor
from PyQt5.QtWidgets import QCheckBox, QListWidgetItem, QHBoxLayout, QWidget
from PyQt5 import QtCore
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import pyqtSignal, Qt
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
@@ -103,6 +70,8 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS):
dataStorage: DataStorage = None
mappingSendDialog = None
custom_crs_modal = None
dashboard = None
iface = None
signal_1 = pyqtSignal(object)
signal_2 = pyqtSignal(object)
@@ -110,8 +79,7 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS):
signal_4 = pyqtSignal(object)
signal_5 = pyqtSignal(object)
signal_6 = pyqtSignal(object)
signal_remove_btn_url = pyqtSignal(str)
signal_cancel_operation = pyqtSignal()
signal_cancel_operation = pyqtSignal(str)
def __init__(self, parent=None):
"""Constructor."""
@@ -380,7 +348,19 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS):
# print("setupOnFirstLoad")
self.msgLog.sendMessage.connect(self.addMsg)
self.setMapping.clicked.connect(self.showMappingDialog)
# print("before")
# print(self.reportBtn)
# print(self.msgLog)
self.reportBtn.clicked.connect(self.msgLog.showReport)
self.open_dashboard.clicked.connect(self.showDashboard)
self.open_dashboard.setFlat(True)
self.open_dashboard.setStyleSheet(
"QPushButton {border: 0px; color: black; padding: 10px; } QPushButton:hover { "
+ f"background-color: rgba{str(COLOR_HIGHLIGHT)};"
+ " };"
)
# print("after")
self.streams_add_button.clicked.connect(plugin.onStreamAddButtonClicked)
self.commit_web_view.clicked.connect(
@@ -691,6 +671,9 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS):
def fillLayerList(self, layer, layerType="generic"):
try:
icon_xxl = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", " size-xxl.png"
)
listItem = QListWidgetItem(layer.name())
try: # if QGIS
@@ -700,10 +683,10 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS):
isinstance(layer, QgsRasterLayer)
and layer.width() * layer.height() > 1000000
):
listItem.setIcon(QIcon(ICON_XXL))
listItem.setIcon(QIcon(icon_xxl))
elif isinstance(layer, QgsVectorLayer) and layer.featureCount() > 20000:
listItem.setIcon(QIcon(ICON_XXL))
listItem.setIcon(QIcon(icon_xxl))
else:
from qgis.core import QgsIconUtils
@@ -801,7 +784,9 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS):
active_branch = copy(plugin.active_branch)
active_commit = copy(plugin.active_commit)
keep_branch = True # case of search by URL
if active_branch is None: # case of populating from Saved Streams
if (
active_branch is None or active_commit is None
): # case of populating from Saved Streams
keep_branch = False
# print(active_branch)
@@ -829,12 +814,7 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS):
# print(5)
if keep_branch is True:
plugin.active_branch = active_branch
if active_commit is not None:
plugin.active_commit = active_commit
elif len(plugin.active_branch.commits.items) > 0:
plugin.active_commit = plugin.active_branch.commits.items[0]
# else:
# plugin.active_commit = plugin.active_branch.commits.items[0]
plugin.active_commit = active_commit
# print(plugin.active_branch)
# set index to current (if added from URL)
@@ -952,3 +932,14 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS):
def cancelOperations(self):
return
def showDashboard(self):
if self.dashboard is None:
self.dashboard = SpeckleDashboard()
self.dashboard.dataStorage = self.dataStorage
self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.dashboard)
self.dashboard.update()
self.dashboard.show()
else:
self.dashboard.update()
self.dashboard.show()
+47
View File
@@ -0,0 +1,47 @@
import os
# colors
COLOR_HIGHLIGHT = (210,210,210,1)
SPECKLE_COLOR = (59,130,246,1)
SPECKLE_COLOR_LIGHT = (69,140,255,1)
ICON_LOGO = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "logo-slab-white@0.5x.png")
ERROR_COLOR = (255,150,150,1)
ERROR_COLOR_LIGHT = (255,210,210,1)
COLOR = f"color: rgba{str(SPECKLE_COLOR)};"
BACKGR_COLOR_TRANSPARENT = f"background-color: rgba(0,0,0,0);"
BACKGR_COLOR_HIGHLIGHT = f"background-color: rgba{str(COLOR_HIGHLIGHT)};"
BACKGR_COLOR = f"background-color: rgba{str(SPECKLE_COLOR)};"
BACKGR_COLOR_LIGHT = f"background-color: rgba{str(SPECKLE_COLOR_LIGHT)};"
BACKGR_COLOR_GREY = f"background-color: rgba(220,220,220,1);"
BACKGR_ERROR_COLOR = f"background-color: rgba{str(ERROR_COLOR)};"
BACKGR_ERROR_COLOR_LIGHT = f"background-color: rgba{str(ERROR_COLOR_LIGHT)};"
NEW_GREY = BACKGR_COLOR_GREY.replace("1);","0.2);")
NEW_GREY_HIGHLIGHT = BACKGR_COLOR_HIGHLIGHT.replace("1);","0.3);")
# images
ICON_SEARCH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "magnify.png")
ICON_OPEN_WEB = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "open-in-new.png")
ICON_REPORT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "chart-line.png")
ICON_DELETE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "delete.png")
ICON_DELETE_BLUE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "delete-blue.png")
ICON_SEND = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "cube-send.png")
ICON_RECEIVE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "cube-receive.png")
ICON_SEND_BLACK = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "cube-send-black.png")
ICON_RECEIVE_BLACK = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "cube-receive-black.png")
ICON_SEND_BLUE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "cube-send-blue.png")
ICON_RECEIVE_BLUE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "cube-receive-blue.png")
ICON_XXL = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "/size-xxl.png")
ICON_RASTER = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "/legend_raster.png")
ICON_POLYGON = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "/legend_polygon.png")
ICON_LINE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "/legend_line.png")
ICON_POINT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "/legend_point.png")
ICON_GENERIC = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "/legend_generic.png")
+4 -7
View File
@@ -1,7 +1,7 @@
try:
from specklepy_qt_ui.qt_ui.utils.utils import splitTextIntoLines
except ModuleNotFoundError:
from speckle.specklepy_qt_ui.qt_ui.utils.utils import splitTextIntoLines
from PyQt5.QtWidgets import QMessageBox
from PyQt5 import QtCore
from specklepy_qt_ui.qt_ui.utils import splitTextIntoLines
def logToUser(
@@ -55,9 +55,6 @@ def displayUserMsg(msg: str, func=None, level: int = 2):
def createWindow(msg_old: str, func=None, level: int = 2):
# print("Create window")
from PyQt5.QtWidgets import QMessageBox
from PyQt5 import QtCore
window = None
try:
# https://www.techwithtim.net/tutorials/pyqt5-tutorial/messageboxes/
+355 -700
View File
File diff suppressed because it is too large Load Diff
+110
View File
@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SpeckleDashboard</class>
<widget class="QDockWidget" name="SpeckleDashboard">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>575</width>
<height>651</height>
</rect>
</property>
<property name="windowTitle">
<string>Speckle Dashboard</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout" stretch="5,1">
<item>
<layout class="QFormLayout" name="formLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="1" column="0">
<widget class="QWidget" name="chart">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="1">
<layout class="QHBoxLayout" name="streamListButtons">
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="streamBranchLabel">
<property name="text">
<string>Model properties</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="selectionDropdown"/>
</item>
<item row="3" column="1">
<widget class="QListWidget" name="dataWidget">
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="resizeMode">
<enum>QListView::Fixed</enum>
</property>
<property name="viewMode">
<enum>QListView::ListMode</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
+17
View File
@@ -334,6 +334,23 @@
</layout>
</item>
<item row="14" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="open_dashboard">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Open Dashboard</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
+47 -58
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SpeckleGISDialog</class>
<widget class="QMainWindow" name="SpeckleGISDialog">
<class>SpeckleQArcGISDialog</class>
<widget class="QMainWindow" name="SpeckleQArcGISDialog">
<property name="geometry">
<rect>
<x>0</x>
@@ -98,21 +98,9 @@
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="commitListButtons" stretch="20,1">
<item>
<widget class="QComboBox" name="commitDropdown"/>
</item>
<item>
<widget class="QPushButton" name="commit_web_view">
<property name="text">
<string> </string>
</property>
</widget>
</item>
</layout>
<widget class="QComboBox" name="commitDropdown"/>
</item>
<item row="4" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
</layout>
@@ -138,15 +126,6 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="reportBtn">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
@@ -199,49 +178,21 @@
<bool>true</bool>
</property>
<property name="text">
<string>Save visible layers as selection</string>
<string>Set visible layers as selection</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="10" column="1">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="crsSettings">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Set project center on Send/Receive</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="11" column="0">
<item row="9" column="0">
<widget class="QLabel" name="messageLabel">
<property name="text">
<string>Message</string>
</property>
</widget>
</item>
<item row="11" column="1">
<item row="9" column="1">
<widget class="QLineEdit" name="messageInput">
<property name="placeholderText">
<string>Sent XXX objects from ArcGIS</string>
@@ -251,7 +202,7 @@
<item row="12" column="1">
<item row="10" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
@@ -285,8 +236,46 @@
</layout>
</item>
<item row="11" column="0">
<widget class="QLabel" name="surveyPointLabel">
<property name="text">
<string>Lat, Lon</string>
</property>
</widget>
</item>
<item row="11" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="surveyPointLat">
<property name="placeholderText">
<string>0.0</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="surveyPointLon">
<property name="placeholderText">
<string>0.0</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="saveSurveyPoint">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Set as a project center</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="13" column="1">
<item row="12" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
+17 -16
View File
@@ -1,11 +1,9 @@
from textwrap import wrap
from typing import Union
import requests
SYMBOL = "_x_x_"
def splitTextIntoLines(text: str = "", number: int = 40) -> str:
# print("__splitTextIntoLines")
# print(text)
msg = ""
try:
if len(text) > number:
@@ -29,10 +27,8 @@ def splitTextIntoLines(text: str = "", number: int = 40) -> str:
def constructCommitURL(
streamWrapper,
branch_id: Union[str, None] = None,
commit_id: Union[str, None] = None,
) -> Union[str, None]:
streamWrapper, branch_id: str = None, commit_id: str = None
) -> str:
import requests
try:
@@ -44,7 +40,7 @@ def constructCommitURL(
url = streamUrl
# check for frontend2
try:
header = r.headers["x-speckle-frontend-2"] # will throw Exception in FE1
header = r.headers["x-speckle-frontend-2"]
url = (
streamUrl.replace("streams", "projects")
+ "/models/"
@@ -56,21 +52,26 @@ def constructCommitURL(
url = streamUrl.replace("projects", "streams") + "/commits/" + commit_id
return url
except:
return None
pass
def constructCommitURLfromServerCommit(serverURL: str, stream_id: str) -> str:
import requests
r = requests.get(serverURL)
# check for frontend2
# only check the url string
try:
header = r.headers["x-speckle-frontend-2"]
# url = streamUrl.replace("streams", "projects") + "/models/" + branch_id + "@" + commit_id
url = (
serverURL
+ "/projects/"
+ stream_id # + "/models/" + branch_id + "@" + commit_id
)
except KeyError:
serverURL + "/projects/" + stream_id
) # replace with 'projects' after it's implemented in Specklepy
except:
url = serverURL + "/streams/" + stream_id
return url
# def removeSpecialCharacters(text: str) -> str:
# new_text = text.replace("[","_").replace("]","_").replace(" ","_").replace("-","_").replace("(","_").replace(")","_").replace(":","_").replace("\\","_").replace("/","_").replace("\"","_").replace("&","_").replace("@","_").replace("$","_").replace("%","_").replace("^","_")
# return new_text
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

-90
View File
@@ -1,90 +0,0 @@
import os
# colors
COLOR_HIGHLIGHT = (210, 210, 210, 1)
SPECKLE_COLOR = (59, 130, 246, 1)
SPECKLE_COLOR_LIGHT = (69, 140, 255, 1)
ICON_LOGO = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "logo-slab-white@0.5x.png"
)
ERROR_COLOR = (255, 150, 150, 1)
ERROR_COLOR_LIGHT = (255, 210, 210, 1)
COLOR = f"color: rgba{str(SPECKLE_COLOR)};"
BACKGR_COLOR_TRANSPARENT = f"background-color: rgba(0,0,0,0);"
BACKGR_COLOR_HIGHLIGHT = f"background-color: rgba{str(COLOR_HIGHLIGHT)};"
BACKGR_COLOR = f"background-color: rgba{str(SPECKLE_COLOR)};"
BACKGR_COLOR_LIGHT = f"background-color: rgba{str(SPECKLE_COLOR_LIGHT)};"
BACKGR_COLOR_GREY = f"background-color: rgba(220,220,220,1);"
BACKGR_ERROR_COLOR = f"background-color: rgba{str(ERROR_COLOR)};"
BACKGR_ERROR_COLOR_LIGHT = f"background-color: rgba{str(ERROR_COLOR_LIGHT)};"
NEW_GREY = BACKGR_COLOR_GREY.replace("1);", "0.2);")
NEW_GREY_HIGHLIGHT = BACKGR_COLOR_HIGHLIGHT.replace("1);", "0.3);")
# images
ICON_SEARCH = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "magnify.png"
)
ICON_OPEN_WEB = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "open-in-new.png"
)
ICON_REPORT = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "chart-line.png"
)
ICON_DELETE = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "delete.png"
)
ICON_DELETE_BLUE = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "delete-blue.png"
)
ICON_SEND = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "cube-send.png"
)
ICON_RECEIVE = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "cube-receive.png"
)
ICON_SEND_BLACK = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "cube-send-black.png"
)
ICON_RECEIVE_BLACK = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "cube-receive-black.png"
)
ICON_SEND_BLUE = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "cube-send-blue.png"
)
ICON_RECEIVE_BLUE = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "cube-receive-blue.png"
)
ICON_XXL = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "size-xxl.png"
)
ICON_RASTER = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "legend_raster.png"
)
ICON_POLYGON = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "legend_polygon.png"
)
ICON_LINE = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "legend_line.png"
)
ICON_POINT = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "legend_point.png"
)
ICON_GENERIC = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "legend_generic.png"
)
ICON_PIN_ACTIVE = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "pin.png"
)
ICON_PIN_DISABLED = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "assets", "pin-outline.png"
)
+41 -25
View File
@@ -3,16 +3,8 @@ import os
from typing import List, Union
import urllib.parse
try:
from specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from specklepy_qt_ui.qt_ui.utils.logger import logToUser
from specklepy_qt_ui.qt_ui.utils.utils import constructCommitURLfromServerCommit
from specklepy_qt_ui.qt_ui.utils.logger import displayUserMsg
except ModuleNotFoundError:
from speckle.specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from speckle.specklepy_qt_ui.qt_ui.utils.logger import logToUser
from speckle.specklepy_qt_ui.qt_ui.utils.utils import constructCommitURLfromServerCommit
from speckle.specklepy_qt_ui.qt_ui.utils.logger import displayUserMsg
from specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from specklepy_qt_ui.qt_ui.logger import logToUser
from PyQt5 import QtWidgets, uic, QtCore
from PyQt5.QtCore import pyqtSignal
@@ -24,6 +16,7 @@ from specklepy.core.api.credentials import get_local_accounts # , StreamWrapper
from specklepy.core.api.wrapper import StreamWrapper
from specklepy.logging import metrics
from specklepy_qt_ui.qt_ui.utils import constructCommitURLfromServerCommit
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(
@@ -124,33 +117,61 @@ class AddStreamModalDialog(QtWidgets.QWidget, FORM_CLASS):
streams = []
branch = None
commit = None
# print("_____ onSearchClicked___")
if "http" in query and len(query.split("/")) >= 3: # URL
sw = StreamWrapper(
query,
)
sw = StreamWrapper(query)
stream = sw.get_client().stream.get(
id=sw.stream_id, branch_limit=100, commit_limit=100
)
if isinstance(stream, Stream):
streams = [stream]
if sw.type == "branch":
branch_name = sw.branch_name
if "/branches/" in query:
# print("branches")
branch_name = (
query.split("/branches/")[
len(query.split("/branches/")) - 1
]
.split("/")[0]
.split("?")[0]
.split("&")[0]
.split("@")[0]
)
# print(branch_name)
# print(stream)
# print(len(stream.branches.items))
for br in stream.branches.items:
name = urllib.parse.quote(br.name)
if br.name == branch_name:
# print(name)
if name == branch_name:
branch = br
break
if sw.type == "commit":
commit_id = sw.commit_id
elif "/commits/" in query:
# print("commits")
commit_id = (
query.split("/commits/")[len(query.split("/commits/")) - 1]
.split("/")[0]
.split("?")[0]
.split("&")[0]
.split("@")[0]
)
for br in stream.branches.items:
for com in br.commits.items:
if com.id == commit_id:
branch = br
commit = com
# print(branch)
# print(commit)
break
elif isinstance(stream, Exception):
print(stream)
# if "/commits/" in query:
# branch_id = query.split("/commits/")[len(query.split("/commits/"))-1].split("/")[0].split("?")[0].split("&")[0].split("@")[0]
# for com in stream.
# if br.id == branch_id:
# branch = br
# break
try:
metrics.track(
"Connector Action",
@@ -191,10 +212,6 @@ class AddStreamModalDialog(QtWidgets.QWidget, FORM_CLASS):
except Exception as e:
logToUser(e, level=2, func=inspect.stack()[0][3])
if isinstance(e, SpeckleException):
displayUserMsg(e.message, level=2)
else:
displayUserMsg(str(e), level=2)
return
def populateResultsList(self, sw=None):
@@ -243,12 +260,13 @@ class AddStreamModalDialog(QtWidgets.QWidget, FORM_CLASS):
else:
try:
index = self.search_results_list.currentIndex().row()
# stream = self.stream_results[index]
stream = self.stream_results[index]
item = self.search_results_list.item(index)
url = constructCommitURLfromServerCommit(
item.text().split(" | ")[1],
item.text().split(", ")[1].split(" | ")[0],
)
# url = item.text().split(" | ")[1] + "/streams/" + item.text().split(", ")[1].split(" | ")[0]
sw = StreamWrapper(url)
try:
@@ -276,11 +294,9 @@ class AddStreamModalDialog(QtWidgets.QWidget, FORM_CLASS):
level=1,
func=inspect.stack()[0][3],
)
print(e)
return
except Exception as e:
logToUser(e, level=2, func=inspect.stack()[0][3])
print(e)
return
def onCancelClicked(self):
+1 -5
View File
@@ -1,11 +1,7 @@
import inspect
import os
from typing import List, Tuple, Union
try:
from specklepy_qt_ui.qt_ui.utils.logger import logToUser
except ModuleNotFoundError:
from speckle.specklepy_qt_ui.qt_ui.utils.logger import logToUser
from specklepy_qt_ui.qt_ui.logger import logToUser
from PyQt5 import QtWidgets, uic, QtCore
from PyQt5.QtCore import pyqtSignal
+1 -5
View File
@@ -1,11 +1,7 @@
import inspect
import os
from typing import List, Tuple, Union
try:
from specklepy_qt_ui.qt_ui.utils.logger import logToUser
except ModuleNotFoundError:
from speckle.specklepy_qt_ui.qt_ui.utils.logger import logToUser
from specklepy_qt_ui.qt_ui.logger import logToUser
from PyQt5 import QtWidgets, uic, QtCore
from PyQt5.QtCore import pyqtSignal
+46 -75
View File
@@ -1,91 +1,80 @@
import inspect
import os
from typing import List, Tuple, Union
try:
from specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from specklepy_qt_ui.qt_ui.utils.logger import logToUser
from specklepy_qt_ui.qt_ui.utils.global_resources import COLOR
except ModuleNotFoundError:
from speckle.specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from speckle.specklepy_qt_ui.qt_ui.utils.logger import logToUser
from speckle.specklepy_qt_ui.qt_ui.utils.global_resources import COLOR
from specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from specklepy_qt_ui.qt_ui.logger import logToUser
from PyQt5 import QtWidgets, uic, QtCore
from PyQt5.QtWidgets import QCheckBox, QListWidgetItem, QHBoxLayout, QWidget
from PyQt5.QtWidgets import QCheckBox, QListWidgetItem, QHBoxLayout, QWidget
from PyQt5.QtCore import pyqtSignal
from specklepy.core.api.client import SpeckleClient
from specklepy_qt_ui.qt_ui.global_resources import COLOR
# 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", "custom_crs.ui"))
os.path.join(os.path.join(os.path.dirname(__file__), "ui", "custom_crs.ui") )
)
class CustomCRSDialog(QtWidgets.QWidget, FORM_CLASS):
name_field: QtWidgets.QLineEdit = None
description: QtWidgets.QLineEdit = None
description_field: QtWidgets.QLineEdit = None
dialog_button_box: QtWidgets.QDialogButtonBox = None
saveSurveyPoint: QtWidgets.QPushButton = None
speckle_client: Union[SpeckleClient, None] = None
dataStorage: DataStorage = None
dataStorage: DataStorage = None
# Events
# handleCRSCreate = pyqtSignal(str,str)
#Events
#handleCRSCreate = pyqtSignal(str,str)
def __init__(self, parent=None):
super(CustomCRSDialog, self).__init__(parent, QtCore.Qt.WindowStaysOnTopHint)
super(CustomCRSDialog,self).__init__(parent, QtCore.Qt.WindowStaysOnTopHint)
self.setupUi(self)
self.setWindowTitle("Set project center on Send/Receive")
# self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Close).clicked.connect(self.onCancelClicked)
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Cancel).setText(
"More Info"
)
self.setWindowTitle("Set project center on Send/Receive")
#self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Close).clicked.connect(self.onCancelClicked)
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Cancel).setText("More Info")
self.modeDropdown.currentIndexChanged.connect(self.onModeChanged)
def onModeChanged(self):
try:
if not self:
return
if not self: return
index = self.modeDropdown.currentIndex()
if index == 1: # custom crs
if index == 1: # custom crs
self.surveyPointLat.show()
self.surveyPointLon.show()
self.degreeSignX.show()
self.degreeSignY.show()
self.label_survey.show()
self.offsetX.hide()
self.offsetY.hide()
self.label_offsets.hide()
self.offsetXDegreeSign.hide()
self.offsetYDegreeSign.hide()
self.description.setText(
"Use this option when you don't have to use a specific CRS.\
self.description.setText("Use this option when you don't have to use a specific CRS.\
\n\nThis will change your Project CRS to a new custom CRS.\
\n\nHint: right-click on the canvas -> Copy Coordinate -> EPSG:4326. "
)
\n\nHint: right-click on the canvas -> Copy Coordinate -> EPSG:4326. ")
elif index == 0: # offsets
elif index == 0: # offsets
self.surveyPointLat.hide()
self.surveyPointLon.hide()
self.degreeSignX.hide()
self.degreeSignY.hide()
self.label_survey.hide()
self.offsetX.show()
self.offsetY.show()
self.label_offsets.show()
# if self.dataStorage.currentOriginalUnits == 'degrees':
#if self.dataStorage.currentOriginalUnits == 'degrees':
self.offsetXDegreeSign.show()
self.offsetYDegreeSign.show()
units = self.dataStorage.currentOriginalUnits
print(units)
if units == "degrees":
if units == 'degrees':
self.offsetXDegreeSign.setText("°")
self.offsetYDegreeSign.setText("°")
elif units is not None:
@@ -94,64 +83,49 @@ class CustomCRSDialog(QtWidgets.QWidget, FORM_CLASS):
else:
self.offsetXDegreeSign.hide()
self.offsetYDegreeSign.hide()
try:
authid = self.dataStorage.currentCRS.authid()
except:
try:
authid = self.dataStorage.currentCRS.name
except:
authid = str(self.dataStorage.currentCRS)
text = f"Use this option when your project requires a use of a specific CRS. \
\n\nThis will only affect Speckle data properties, not your Project CRS.\
\n\nHint: your current project CRS is '{authid}' and using units '{self.dataStorage.currentOriginalUnits}'."
\n\nHint: your current project CRS is '{self.dataStorage.currentCRS.authid()}' and using units '{self.dataStorage.currentOriginalUnits}'."
if units == "degrees":
if units == 'degrees':
text += "\nThis CRS is not recommended if data was sent or needs to be \
\nreceived in a non-GIS application."
self.description.setText(text)
self.populateSurveyPoint()
self.populateOffsets()
self.populateRotation()
except Exception as e:
logToUser(e, level=2, func=inspect.stack()[0][3])
logToUser(e, level = 2, func = inspect.stack()[0][3])
return
def populateModeDropdown(self):
if not self:
return
if not self: return
try:
self.modeDropdown.clear()
self.modeDropdown.addItems(
[
"Add offsets / rotation to the current Project CRS",
"Create custom CRS",
]
["Add offsets / rotation to the current Project CRS", "Create custom CRS"]
)
except Exception as e:
logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self)
logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self)
return
def populateSurveyPoint(self):
if not self:
return
try:
self.surveyPointLat.clear()
self.surveyPointLon.clear()
if (
self.dataStorage.custom_lat is not None
and self.dataStorage.custom_lon is not None
):
if self.dataStorage.custom_lat is not None and self.dataStorage.custom_lon is not None:
self.surveyPointLat.setText(str(self.dataStorage.custom_lat))
self.surveyPointLon.setText(str(self.dataStorage.custom_lon))
except Exception as e:
logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self)
logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self)
return
def populateRotation(self):
if not self:
return
@@ -160,22 +134,19 @@ class CustomCRSDialog(QtWidgets.QWidget, FORM_CLASS):
if self.dataStorage.crs_rotation is not None:
self.rotation.setText(str(self.dataStorage.crs_rotation))
except Exception as e:
logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self)
logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self)
return
def populateOffsets(self):
try:
self.offsetX.clear()
self.offsetY.clear()
if (
self.dataStorage.crs_offset_x is not None
and self.dataStorage.crs_offset_y is not None
):
if self.dataStorage.crs_offset_x is not None and self.dataStorage.crs_offset_y is not None:
self.offsetX.setText(str(self.dataStorage.crs_offset_x))
self.offsetY.setText(str(self.dataStorage.crs_offset_y))
except Exception as e:
logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self)
logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self)
return
def onOkClicked(self):
@@ -183,5 +154,5 @@ class CustomCRSDialog(QtWidgets.QWidget, FORM_CLASS):
try:
self.close()
except Exception as e:
logToUser(e, level=2, func=inspect.stack()[0][3])
logToUser(e, level = 2, func = inspect.stack()[0][3])
return
+316
View File
@@ -0,0 +1,316 @@
import random
from qgis.PyQt import QtWidgets, uic
from qgis.PyQt import QtGui
from qgis.PyQt.QtGui import QIcon, QPixmap
from qgis.PyQt.QtWidgets import (
QCheckBox,
QListWidgetItem,
QAction,
QDockWidget,
QVBoxLayout,
QHBoxLayout,
QWidget,
QLabel,
)
from qgis.PyQt import QtCore
from qgis.PyQt.QtCore import pyqtSignal, Qt
# from qgis.PyQt import QtCore, QtWidgets #, QtWebEngineWidgets
from PyQt5 import *
from PyQt5.QtCore import QUrl
import plotly.express as px
from PyQt5.QtWebKitWidgets import QWebView
import pandas as pd
import os
FORM_CLASS, _ = uic.loadUiType(
os.path.join(os.path.join(os.path.dirname(__file__), "ui", "dashboard.ui"))
)
class SpeckleDashboard(QtWidgets.QDockWidget, FORM_CLASS):
closingPlugin = pyqtSignal()
dataStorage = None
dataNumeric: dict = {}
dataText: dict = {}
current_filter: str = ""
current_index: int = -1
selectionDropdown: QtWidgets.QComboBox
dataWidget: QtWidgets.QListWidget
chart: QWidget
browser = None
existing_web: int = 0
def __init__(self, parent=None):
"""Constructor."""
super(SpeckleDashboard, self).__init__(parent)
# Set up the user interface from Designer through FORM_CLASS.
# After self.setupUi() you can access any designer object by doing
# self.<objectname>, and you can use autoconnect slots - see
# http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
# #widgets-and-dialogs-with-auto-connect
self.browser = QWebView(self) # https://github.com/qgis/QGIS/issues/26048
self.setupUi(self)
self.selectionDropdown.clear()
self.selectionDropdown.addItems(["area", "property value"])
self.selectionDropdown.setCurrentIndex(0)
self.selectionDropdown.currentIndexChanged.connect(self.populateUI)
def setup(self):
self.dataWidget.clear()
for i, (key, val) in enumerate(self.dataNumeric.items()):
# property_filter = self.selectionDropdown.currentText()
if self.current_filter in key or key in self.current_filter:
listItem = QListWidgetItem(f"{key}: {val}")
self.dataWidget.addItem(listItem)
# self.dataWidget.setMaximumHeight(50)
def populateUI(self, force=0):
print(self.selectionDropdown.currentIndex())
print(self.current_filter)
if self.selectionDropdown.currentText() == "":
self.current_filter = "area"
self.current_index = 0
self.setup()
self.selectionDropdown.setIndex(0)
self.createChart()
elif self.selectionDropdown.currentText() != self.current_filter:
self.current_filter = self.selectionDropdown.currentText()
self.current_index = self.selectionDropdown.currentIndex()
self.setup()
self.createChart()
if force == 1:
self.setup()
self.createChart()
def update(self):
self.dataNumeric = {}
self.dataText = {}
for i in range(10):
# keys = ["key1", "key2", "key3", "key4", "key5"]
# key = keys[random.randint(0, 4)]
value = random.randint(10, 100)
self.dataNumeric.update({"index": i, f"area {random.randint(1, 4)}": value})
for i in range(10):
# keys = ["key1", "key2", "key3", "key4", "key5"]
# key = keys[random.randint(0, 4)]
value = random.randint(10, 100)
self.dataNumeric.update(
{
"index": i + 10,
f"property value {random.randint(1, 4)}": [
value,
value * 0.5,
value * 2,
],
}
)
r"""
layer = getLayerByName(self.dataStorage.project, "Speckle_dashboard")
fields = layer.fields()
for i, key in enumerate(fields.names()):
if key in ["Branch URL", "commit_id", "updated"]:
continue
value = None
variant = fields.at(i).type()
if "value" in key:
value = []
if "area" in key:
value = 0
for feat in layer.getFeatures():
if isinstance(value, List):
if feat[key] is not None:
list_vals = (
feat[key]
.replace("[", "")
.replace("]", "")
.replace("'", "")
.split(",")
)
value.extend([x for x in list_vals if x != ""])
print(value)
elif isinstance(value, float) or isinstance(value, int):
if feat[key] is not None:
value += feat[key]
self.dataNumeric.update({key: value})
"""
self.populateUI(force=1)
def createChart(self):
# https://stackoverflow.com/questions/60522103/how-to-have-plotly-graph-as-pyqt5-widget
r"""
print("PRINT DATAFRAME")
df = pd.DataFrame.from_dict(self.dataNumeric, orient="index", columns=["value"])
df2 = df.reset_index()
print(df2)
property_filter = str(self.selectionDropdown.currentText())
if len(property_filter) <= 1:
property_filter = "area"
df2 = df2[df2["index"].str.lower().str.contains(property_filter)]
print(df2)
if "area" in property_filter:
fig = px.pie(
df2,
values="value",
names="index",
title="Land use distribution",
hole=0.5,
)
elif "value" in property_filter:
all_column_vals = df2["value"].to_list()
all_column_vals_separated = []
[all_column_vals_separated.extend(x) for x in all_column_vals]
all_vals = [float(x) for x in all_column_vals_separated]
df2 = pd.DataFrame([{property_filter: val} for val in all_vals])
fig = px.histogram(df2, x=property_filter, title="Property values")
else:
df2 = df2.loc["area" in df2["index"]]
fig = px.pie(
df2, values="value", names="index", title="Land use distribution"
)
print(df2)
# remove all buttons
# try:
# for i in reversed(range(self.chart.layout.count())):
# self.chart.layout.itemAt(i).widget().setParent(None)
# except: pass
"""
fig = self.assembleFigure()
if self.existing_web == 0:
self.browser = QWebView(self)
self.browser.setHtml(
fig.to_html(include_plotlyjs="cdn", config={"displayModeBar": False})
)
# self.browser.setUrl(QUrl("https://speckle.xyz/streams/e2effcfa27/commits/f76cedd9a6"))
self.chart.layout = QHBoxLayout(self.chart)
# self.browser.setMaximumHeight(400)
if self.existing_web == 0:
self.chart.layout.addWidget(self.browser)
self.existing_web = 1
return
# https://www.pythonguis.com/tutorials/plotting-pyqtgraph/
graphWidget = pg.PlotWidget()
hour = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
temperature = [30, 32, 34, 32, 33, 31, 29, 32, 35, 45]
# plot data: x, y values
graphWidget.plot(hour, temperature)
# remove all buttons
try:
for i in reversed(range(self.chart.layout.count())):
self.chart.layout.itemAt(i).widget().setParent(None)
except:
pass
self.chart.layout = QHBoxLayout(self.chart)
self.chart.layout.addWidget(graphWidget)
# set the QWebEngineView instance as main widget
# self.setCentralWidget(plot_widget)
def assembleFigure(self):
import plotly
from plotly.subplots import make_subplots
cols = 1
rows = 2 # len(TOTAL_SERVER_LIST) #math.ceil(len(TOTAL_SERVER_LIST)/cols)
# specs_col = [{"type": "bar"}, {"type": "sunburst"}]
specs = [[{"type": "sunburst"}], [{"type": "bar"}]]
# specs = [ specs_col for r in range(rows)]
# df = pd.DataFrame(columns=['Country', '', 'count'])
# for i in range(5): df = df.append({'Name' : 'Ankit', 'Articles' : 97, 'Improved' : 2200},ignore_index = True)
fig = make_subplots(rows=rows, cols=cols, specs=specs)
# dataframe
df = pd.DataFrame.from_dict(self.dataNumeric, orient="index", columns=["value"])
df = df.reset_index()
print(df)
# plot - pie chart
# df2 = df2.loc["area" in df2["index"]]
property_filter = "area"
df2 = df[df["index"].str.lower().str.contains(property_filter)].copy()
fig2 = px.pie(
df2,
values="value",
names="index",
title="Land use distribution",
hole=0.5,
)
fig.add_trace(fig2.data[0], row=1, col=1)
# plot - histogram
property_filter = "value"
# df2 = df[df["index"].str.lower().str.contains(property_filter)].copy()
all_column_vals = df[df["index"].str.lower().str.contains(property_filter)][
"value"
].to_list()
all_column_vals_separated = []
[
all_column_vals_separated.extend(x)
for x in all_column_vals
if isinstance(x, list)
]
all_vals = [float(x) for x in all_column_vals_separated]
df2 = pd.DataFrame([{property_filter: val} for val in all_vals])
print(df2)
fig2 = px.histogram(df2, x=property_filter, title="Property values")
fig.add_trace(fig2.data[0], row=2, col=1)
# plot - sunburst
# fig2 = px.sunburst(
# df2,
# path=["server_id", "isWebhook", "isMultiplayer"],
# color="isMultiplayer",
# values="count",
# color_discrete_map={True: "blue", False: "red"},
# )
# fig.add_trace(fig2.data[0], row=2, col=1)
fig.update_layout(
title="Some title",
)
width = 300
fig.update_layout(
autosize=False,
width=width,
height=width / cols * rows,
)
return fig
# fig.update_yaxes(range = [0, 30], col=1) #, title_text=f"Total receives: {total_receives} by {people} people")
# fig.update_xaxes(range = [0, 31*4], col=1)
# fig.show()
# plotly.offline.plot(fig, filename='Graphs/stats_perServer_pie.html')
+8 -10
View File
@@ -3,11 +3,7 @@ import urllib3
import requests
import requests_toolbelt
from specklepy.logging import metrics
try:
from specklepy_qt_ui.qt_ui.DataStorage import DataStorage
except ModuleNotFoundError:
from speckle.specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from PyQt5 import QtWidgets, uic, QtCore
@@ -91,25 +87,27 @@ To do it manually, you can run 2 following commands from QGIS Plugins panel->Pyt
def runSubprocess(self):
import subprocess
try:
from speckle.utils.utils import get_qgis_python_path as path
except ModuleNotFoundError:
from speckle.speckle.utils.utils import get_qgis_python_path as path
from speckle.utils.utils import get_qgis_python_path as path
result1 = subprocess.run(
[path(), "-m", "pip", "install", "requests==2.31.0"],
shell=True,
timeout=1000,
capture_output=True,
text=True,
)
result2 = subprocess.run(
[path(), "-m", "pip", "install", "urllib3==1.26.16"],
shell=True,
timeout=1000,
capture_output=True,
text=True,
)
result3 = subprocess.run(
[path(), "-m", "pip", "install", "requests_toolbelt==0.10.1"],
shell=True,
timeout=1000,
capture_output=True,
text=True,
)
return result1, result2, result3
+48 -84
View File
@@ -1,83 +1,70 @@
import inspect
import os
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
from specklepy_qt_ui.qt_ui.DataStorage import DataStorage
#from specklepy_qt_ui.qt_ui.logger import logToUser
from PyQt5 import QtWidgets, uic, QtCore
from PyQt5.QtWidgets import QCheckBox, QListWidgetItem, QHBoxLayout, QWidget
from PyQt5.QtWidgets import QCheckBox, QListWidgetItem, QHBoxLayout, QWidget
from PyQt5.QtCore import pyqtSignal
from specklepy.core.api.client import SpeckleClient
from specklepy_qt_ui.qt_ui.global_resources import COLOR
# 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", "report.ui"))
os.path.join(os.path.join(os.path.dirname(__file__), "ui", "report.ui") )
)
class ReportDialog(QtWidgets.QWidget, FORM_CLASS):
report_label: QtWidgets.QLabel = None
report_text: QtWidgets.QTextEdit = None
dataStorage: DataStorage = None
def __init__(self, parent=None):
super(ReportDialog, self).__init__(parent, QtCore.Qt.WindowStaysOnTopHint)
super(ReportDialog,self).__init__(parent, QtCore.Qt.WindowStaysOnTopHint)
self.setupUi(self)
self.runAllSetup()
def runAllSetup(self):
self.setWindowTitle("Report (Speckle)")
self.setWindowTitle("Report (Speckle)")
self.setMinimumWidth(500)
self.setMinimumHeight(600)
self.report_label.setWordWrap(True)
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(
self.onOkClicked
)
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.onOkClicked)
return
# self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Cancel).setText("More Info")
#self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Cancel).setText("More Info")
def assembleReport(self):
try:
if self.dataStorage is None:
return
reportList: List[dict] = self.dataStorage.latestActionReport
if reportList is None:
return
if self.dataStorage is None: return
reportList = self.dataStorage.latestActionReport
if reportList is None: return
operation = ""
total_layers = 0
total_objects = 0
text = ""
sending = True
# details
# details
last_report = ""
last_report += "Details:" + "\n"
for item in reportList:
line = ""
try: # if sending
some_id = item["feature_id"].replace(SYMBOL, "\\")
line += f'{some_id}: {item["obj_type"]}'
try: # if sending
line += f'{item["feature_id"]}: {item["obj_type"]}'
operation = f"Sent at {self.dataStorage.latestActionTime}"
except: # if receiving
except: # if receiving
sending = False
some_id = item[list(item.keys())[0]].replace(SYMBOL, "\\")
line += f'{some_id}: {item["obj_type"]}' # f'{item["speckle_id"]}: {item["obj_type"]}'
line += f'{item["speckle_id"]}: {item["obj_type"]}'
operation = f"Received at {self.dataStorage.latestActionTime}"
# edit based on the type
# edit based on the type
if "Layer" in item["obj_type"]:
total_layers += 1
if item["errors"] != "":
@@ -88,84 +75,61 @@ class ReportDialog(QtWidgets.QWidget, FORM_CLASS):
if item["errors"] != "":
line += f', errors: {item["errors"]}'
line = "" + line[1:]
else:
total_objects += 1
else: total_objects += 1
line = "__ " + line
last_report += line + "\n"
text += f"Operation: {operation}\n"
text += f"Total: {total_layers} layer{'' if str(total_layers).endswith('1') else 's'}, {total_objects} feature{'' if str(total_objects).endswith('1') else 's'}\n\n"
if sending is False:
if sending is False:
try:
text += f"Host application: {self.dataStorage.latestHostApp}\n\n"
except:
pass
except: pass
# layers and transformations (if applicable)
text += "Layers and transformations (if applicable):" + "\n"
for i, layer in enumerate(self.dataStorage.latestActionLayers):
# print(self.dataStorage.latestActionLayers)
name = layer # if isinstance(layer, str) else layer.name()
try:
#print(self.dataStorage.latestActionLayers)
name = layer #if isinstance(layer, str) else layer.name()
try:
transformExists = 0
for item in self.dataStorage.savedTransforms:
layer_name = item.split(" -> ")[0].split(" ('")[0]
layer_name = item.split(" -> ")[0].split(" (\'")[0]
transform_name = item.split(" -> ")[1]
if layer_name == name:
text += (
f"{i+1}. {layer_name} -> '{transform_name}'" + "\n"
)
text += f"{i+1}. {layer_name} -> '{transform_name}'" + "\n"
transformExists += 1
break
if transformExists == 0:
break
if transformExists==0:
text += f"{i+1}. {name} \n"
except Exception as e:
print(e)
text += "\n"
except Exception as e: print(e)
text += "\n"
# add info about the offsets
try:
crs = self.dataStorage.project.crs()
text += "Project CRS: " + crs.authid() + "\n"
crs_keyword = "CRS"
except AttributeError:
crs = self.dataStorage.project.activeMap.spatialReference
crs_keyword = "Spatial Reference"
text += f"Project {crs_keyword}: " + crs.name + "\n"
text += "Project CRS: " + self.dataStorage.project.crs().authid() + "\n"
units = self.dataStorage.latestActionUnits
text += (
f"Project {crs_keyword} units: "
+ units
+ f"{' (not supported, treated as Meters)' if 'degrees' in units else ''}"
+ "\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"
)
text += f"{crs_keyword} rotation: {self.dataStorage.crs_rotation}°" + "\n\n"
text += "Project CRS units: " + units + f"{' (not supported, treated as Meters)' if 'degrees' in units else ''}" + "\n"
text += "Project CRS WKT: \n" + self.dataStorage.project.crs().toWkt() + "\n\n"
text += f"CRS offsets: x={self.dataStorage.crs_offset_x}, y={self.dataStorage.crs_offset_y}" + "\n"
text += f"CRS rotation: {self.dataStorage.crs_rotation}°" + "\n\n"
text += last_report
return operation, total_layers, total_objects, text
return operation, total_layers, total_objects, text
except Exception as e:
print(e)
return
return
def applyReport(self):
def applyReport(self):
result = self.assembleReport()
if result is None:
print("no report generated")
return
return
operation, total_layers, total_objects, report = result
# self.report_label.setText(f"Operation: {operation}\nTotal: {total_layers} layer{'' if str(total_layers).endswith('1') else 's'}, {total_objects} feature{'' if str(total_objects).endswith('1') else 's'}")
#self.report_label.setText(f"Operation: {operation}\nTotal: {total_layers} layer{'' if str(total_layers).endswith('1') else 's'}, {total_objects} feature{'' if str(total_objects).endswith('1') else 's'}")
self.report_text.setText(report)
def onOkClicked(self):
self.close()
+1 -5
View File
@@ -1,10 +1,6 @@
import os
try:
from specklepy_qt_ui.qt_ui.DataStorage import DataStorage
except ModuleNotFoundError:
from speckle.specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from specklepy_qt_ui.qt_ui.DataStorage import DataStorage
from PyQt5 import QtWidgets, uic, QtCore