diff --git a/scripts/debugging_panel.py b/scripts/debugging_panel.py index 5ad8f6c..4681545 100644 --- a/scripts/debugging_panel.py +++ b/scripts/debugging_panel.py @@ -5,8 +5,8 @@ import json import os try: - from speckle.converter.layers.CRS import CRS - from speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer + from speckle.speckle.converter.layers.CRS import CRS + from speckle.speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer except: from speckle_toolbox.esri.toolboxes.speckle.converter.layers.CRS import CRS from speckle_toolbox.esri.toolboxes.speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer @@ -73,7 +73,7 @@ for k, grp in enumerate(sym.renderer.groups): -from speckle.converter.layers.symbology import get_polygon_simpleRenderer +from speckle.speckle.converter.layers.symbology import get_polygon_simpleRenderer from arcpy._mp import ArcGISProject aprx = ArcGISProject('CURRENT') diff --git a/speckle_arcgis_installer/conda_clone_activate.py b/speckle_arcgis_installer/conda_clone_activate.py index cf73db3..d03cf11 100644 --- a/speckle_arcgis_installer/conda_clone_activate.py +++ b/speckle_arcgis_installer/conda_clone_activate.py @@ -10,38 +10,42 @@ from subprocess_call import subprocess_call import sys +ENV_NEW_NAME = "arcgispro-py3-speckle" + def setup(): pythonExec = get_python_path() # import numpy; import os; print(os.path.abspath(numpy.__file__)) return pythonExec # None if not successful -def get_python_path(): # create a full copy of default env - #print("Get Python path") - # or: import site; site.getsitepackages()[0] - # import specklepy; import os; print(os.path.abspath(specklepy.__file__)) ##currentPythonExec = sysconfig.get_paths()['data'] + r"\python.exe" - +def get_default_python(): pythonExec = os.environ["ProgramFiles"] + r'\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\python.exe' #(r"%PROGRAMFILES%\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\python.exe") #os.path.dirname(sys.executable) + "\\python.exe" # default python.exe #print(pythonExec) if not os.path.exists(pythonExec): pythonExec = os.getenv('APPDATA').replace("Roaming", "Local") + r'\Programs\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\python.exe' if not os.path.exists(pythonExec): return None + return pythonExec + +def get_python_path(): # create a full copy of default env + #print("Get Python path") + # or: import site; site.getsitepackages()[0] + # import specklepy; import os; print(os.path.abspath(specklepy.__file__)) ##currentPythonExec = sysconfig.get_paths()['data'] + r"\python.exe" + def_exec = get_default_python() #print(os.getenv('APPDATA') + r'\Programs\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\python.exe') if sys.platform == "win32": - env_new_name = "arcgispro-py3-speckle" - newExec = clone_env(pythonExec, env_new_name) # only if doesn't exist yet + newExec = clone_env(def_exec) # only if doesn't exist yet if not os.path.exists(newExec): return None - activate_env(env_new_name) + activate_env() return newExec else: return None -def clone_env(pythonExec_old: str, env_new_name: str): +def clone_env(pythonExec_old: str): install_folder = os.getenv('APPDATA').replace("\\Roaming","") + r"\Local\ESRI\conda\envs" #r"%LOCALAPPDATA%\ESRI\conda\envs" if not os.path.exists(install_folder): os.makedirs(install_folder) default_env = pythonExec_old.replace("Pro\\bin\\Python\\envs\\arcgispro-py3\\python.exe","Pro\\bin\\Python\\envs\\arcgispro-py3") # + "\\" + 'arcgispro-py3' conda_exe = pythonExec_old.replace("Pro\\bin\\Python\\envs\\arcgispro-py3\\python.exe","Pro\\bin\\Python\\Scripts\\conda.exe") #%PROGRAMFILES%\ArcGIS\Pro\bin\Python\Scripts\conda.exe #base: %PROGRAMDATA%\Anaconda3\condabin\conda.bat - new_env = install_folder + "\\" + env_new_name # %LOCALAPPDATA%\ESRI\conda\envs\... + new_env = install_folder + "\\" + ENV_NEW_NAME # %LOCALAPPDATA%\ESRI\conda\envs\... if os.path.exists(conda_exe) and os.path.exists(default_env) and os.path.exists(new_env) and not os.path.exists(new_env + "\\python.exe"): # conda environment invalid: delete it's folder @@ -60,9 +64,9 @@ def clone_env(pythonExec_old: str, env_new_name: str): print(new_env + "\\python.exe") return new_env + "\\python.exe" -def activate_env(env_new_name: str): +def activate_env(): # using Popen, because process does not return result; subprocess.run will hang indefinitely - variable = subprocess.Popen((f'proswap {env_new_name}'),stdout = subprocess.PIPE,stderr = subprocess.PIPE,text = True,shell = True) + variable = subprocess.Popen((f'proswap {ENV_NEW_NAME}'),stdout = subprocess.PIPE,stderr = subprocess.PIPE,text = True,shell = True) # activate new env : https://support.esri.com/en/technical-article/000024206 @@ -152,10 +156,16 @@ def installDependencies(pythonExec: str, pkgName: str, pkgVersion: str): return True pythonPath = setup() +print(pythonPath) if pythonPath is not None: + + #def_exec = get_default_python() + #conda_exe = def_exec.replace("Pro\\bin\\Python\\envs\\arcgispro-py3\\python.exe","Pro\\bin\\Python\\Scripts\\conda.exe") #%PROGRAMFILES%\ArcGIS\Pro\bin\Python\Scripts\conda.exe #base: %PROGRAMDATA%\Anaconda3\condabin\conda.bat + #subprocess_call([conda_exe, 'proup','-n', ENV_NEW_NAME]) + clearToolbox(pythonPath) installToolbox(pythonPath) - installDependencies(pythonPath, "specklepy", "2.9.0" ) + installDependencies(pythonPath, "specklepy", "2.17.12" ) installDependencies(pythonPath, "panda3d", "1.10.11" ) installDependencies(pythonPath, "PyQt5", "5.15.9" ) diff --git a/speckle_toolbox/esri/toolboxes/Speckle.pyt.xml b/speckle_toolbox/esri/toolboxes/Speckle.pyt.xml index 12662eb..4d0341b 100644 --- a/speckle_toolbox/esri/toolboxes/Speckle.pyt.xml +++ b/speckle_toolbox/esri/toolboxes/Speckle.pyt.xml @@ -1,77 +1,2 @@ - -20220718135001001.0TRUE202309121503181500000005000ItemDescriptionc:\program files\arcgis\pro\Resources\Help\gpSpeckleSpeckle connector for ArcGISspeckle3dArcToolbox Toolbox/9j/4AAQSkZJRgABAQEAYABgAAD/4gxYSUNDX1BST0ZJTEUAAQEAAAxITGlubwIQAABtbnRyUkdC -IFhZWiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAA -AADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFj -cHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAACGAAA -ABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAAAIh2dWVkAAAD -TAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJD -AAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5 -OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEA -AAAAAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAA -AAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAA -AA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo -dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAt -IHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAt -IHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcg -Q29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENv -bmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAA -ABOk/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAA -AAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAK -AA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQAJUA -mgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMBGQEfASUBKwEy -ATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMC -DAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMh -Ay0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4E -jASaBKgEtgTEBNME4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3 -BkgGWQZqBnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDII -RghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqY -Cq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN -Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJECYQQxBh -EH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT -5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReu -F9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9oc -AhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCY -IMQg8CEcIUghdSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZcl -xyX3JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2 -K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIx -SjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3YDec -N9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1hPaE94D4gPmA+ -oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXe -RiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN -3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYP -VlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1f -D19hX7NgBWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/ -aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfBy -S3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB -fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6GcobXhzuH -n4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBukNaRP5GokhGSepLj -k02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6f -HZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1 -q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm4 -0blKucK6O7q1uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZG -xsPHQce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnU -y9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj -4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/8ozz -GfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t////2wBDAAMCAgMC -AgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIU -FRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU -FBQUFBQUFBQUFBQUFBT/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAEC -AwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0Kx -wRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1 -dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ -2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QA -tREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYk -NOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaH -iImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq -8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9Kfif8SNJ+EngjUPFeui4OlWLwrObWMPIolmSINtJGQDI -Ccc4BwCcA8rbftG+F7y3iuLe31CeCVBJHLGkTK6kZDAiTBBHeuZ/bq/5NW8b/wDbj/6XW9fmzpP/ -ACC7P/rin/oIr9T4V4XwueYKVetJqSk18rRfl3Z8NxLnlfJfZypK/MfpTq37XHhmxvpbeFLVhGdj -C71KKGRWHDKUG7GD716V8OfihoHxP0l7vRr+2uZ4Nq3drDOsj27HOA209Dg4bvg9CCB+S1fZf/BO -v/moH/cP/wDbmvX4j4OwOV5XUxlBvmhy/O8ktfvvofO5DxVjMxzKGFrJcs7/ACsm/wBD1H9ur/k1 -bxv/ANuP/pdb1+bOk/8AILs/+uKf+giv2Y1DT7XVrC5sb62hvLK5iaGe2uIxJHLGwIZGU8MpBIIP -BBrw/Rf2JPhRpLXQk0e81GGWTdDDdahMFtU7RxmNlJUDAy5ZuOWNeTwlxVg8jwlShioybcuZctne -6Stq1ta+/wDwfoOKMjxOcqlHDNK173dvyTPzhr7L/wCCdf8AzUD/ALh//tzXsv8Awx58If8AoUf/ -ACpXn/x6uz+HXwd8IfCf+0P+EV0j+yv7Q8v7T/pM03mbN2z/AFjtjG9umOtepxDxpl+bZZVwVCE1 -KXLa6jbSSfST7djwMi4Sx2WZjSxdacHGN72bvrFrrFd+5//Z20220725 + +20231206233915001.0TRUE20231206233915c:\program files\arcgis\pro\Resources\Help\gpSpeckleArcToolbox Toolbox diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/speckle_arcgis.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/speckle_arcgis.py index 9708c48..2169e80 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/speckle_arcgis.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/speckle_arcgis.py @@ -33,13 +33,14 @@ try: from speckle.speckle.converter.layers.Layer import (Layer, VectorLayer, RasterLayer) from speckle.speckle.converter.layers import convertSelectedLayers, getLayers from speckle.speckle.converter.layers.utils import findAndClearLayerGroup - from speckle.speckle.ui.validation import tryGetStream, validateBranch, validateCommit, validateStream, validateTransport + from speckle.speckle.ui.validation import tryGetStream, tryGetClient, validateBranch, validateCommit, validateStream, validateTransport from speckle.speckle.ui.add_stream_modal import AddStreamModalDialog from speckle.speckle.ui.create_stream import CreateStreamModalDialog from speckle.speckle.ui.create_branch import CreateBranchModalDialog from speckle.speckle.ui.speckle_qgis_dialog import SpeckleGISDialog from speckle.speckle.ui.logger import logToUser, logToUserWithAction from speckle.speckle.plugin_utils.helpers import removeSpecialCharacters, getAppName + from speckle.specklepy_qt_ui.qt_ui.DataStorage import DataStorage except: from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.object_utils import callback, traverseObject @@ -47,13 +48,14 @@ except: from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers import convertSelectedLayers, getLayers from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.emptyLayerTemplates import createGroupLayer from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.utils import findAndClearLayerGroup - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.validation import tryGetStream, validateBranch, validateCommit, validateStream, validateTransport + from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.validation import tryGetStream, tryGetClient, validateBranch, validateCommit, validateStream, validateTransport from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.add_stream_modal import AddStreamModalDialog from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.create_stream import CreateStreamModalDialog from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.create_branch import CreateBranchModalDialog from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.speckle_qgis_dialog import SpeckleGISDialog from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser, logToUserWithAction from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.helpers import removeSpecialCharacters, getAppName + from speckle_toolbox.esri.toolboxes.speckle.specklepy_qt_ui.qt_ui.DataStorage import DataStorage # Import the code for the dialog @@ -138,6 +140,7 @@ class SpeckleGIS: version: str gis_version: str + dataStorage: DataStorage dockwidget: Optional[SpeckleGISDialog] add_stream_modal: AddStreamModalDialog create_stream_modal: CreateStreamModalDialog @@ -168,6 +171,7 @@ class SpeckleGIS: self.gis_version = full_version # Save reference to the QGIS interface + self.dataStorage = None self.dockwidget = None #self.iface = None self.gis_project = ArcGISProject('CURRENT') #QgsProject.instance() @@ -385,9 +389,13 @@ class SpeckleGIS: streamWrapper = self.active_stream[0] streamName = self.active_stream[1].name streamId = streamWrapper.stream_id - client = streamWrapper.get_client() + + # client = streamWrapper.get_client() + client, stream = tryGetClient( + streamWrapper, self.dataStorage, False, self.dockwidget + ) - stream = validateStream(streamWrapper) + stream = validateStream(stream, self.dockwidget) if stream == None: return branchName = str(self.dockwidget.streamBranchDropdown.currentText()) @@ -472,14 +480,19 @@ class SpeckleGIS: # Get the stream wrapper streamWrapper = self.active_stream[0] streamId = streamWrapper.stream_id - client = streamWrapper.get_client() + #client = streamWrapper.get_client() + + client, stream = tryGetClient( + streamWrapper, self.dataStorage, False, self.dockwidget + ) # Ensure the stream actually exists print("ON RECEIVE 2") except Exception as e: logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin = self.dockwidget) return try: - stream = validateStream(streamWrapper) + + stream = validateStream(stream, self.dockwidget) if stream == None: return branchName = str(self.dockwidget.streamBranchDropdown.currentText()) @@ -559,9 +572,12 @@ class SpeckleGIS: try: from speckle.ui.project_vars import get_project_streams, get_survey_point, get_project_layer_selection except: - from speckle_toolbox.esri.toolboxes.speckle.ui.project_vars import get_project_streams, get_survey_point, get_project_layer_selection + from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.project_vars import get_project_streams, get_survey_point, get_project_layer_selection - self.is_setup = self.check_for_accounts() + self.dataStorage = DataStorage() + self.dataStorage.plugin_version = self.version + + self.is_setup = self.dataStorage.check_for_accounts() if self.dockwidget is not None: self.active_stream = None get_project_streams(self) @@ -593,11 +609,11 @@ class SpeckleGIS: """Run method that performs all the real work""" print("run plugin") try: - from speckle.ui.speckle_qgis_dialog import SpeckleGISDialog - from speckle.ui.project_vars import get_project_streams, get_survey_point, get_project_layer_selection + from speckle.speckle.ui.speckle_qgis_dialog import SpeckleGISDialog + from speckle.speckle.ui.project_vars import get_project_streams, get_survey_point, get_project_layer_selection except: - from speckle_toolbox.esri.toolboxes.speckle.ui.speckle_qgis_dialog import SpeckleGISDialog - from speckle_toolbox.esri.toolboxes.speckle.ui.project_vars import get_project_streams, get_survey_point, get_project_layer_selection + from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.speckle_qgis_dialog import SpeckleGISDialog + from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.project_vars import get_project_streams, get_survey_point, get_project_layer_selection try: # Create the dialog with elements (after translation) and keep reference # Only create GUI ONCE in callback, so that it will only load when the plugin is started @@ -606,6 +622,11 @@ class SpeckleGIS: if self.pluginIsActive: self.reloadUI() else: + + self.dataStorage = DataStorage() + self.dataStorage.plugin_version = self.version + self.is_setup = self.dataStorage.check_for_accounts() + self.pluginIsActive = True if self.dockwidget is None: self.dockwidget = SpeckleGISDialog() @@ -639,7 +660,7 @@ class SpeckleGIS: try: from speckle.ui.project_vars import set_survey_point except: - from speckle_toolbox.esri.toolboxes.speckle.ui.project_vars import set_survey_point + from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.project_vars import set_survey_point set_survey_point(self) def onStreamCreateClicked(self): @@ -693,7 +714,7 @@ class SpeckleGIS: if isinstance(br_id, GraphQLException): logToUser(br_id.message, level=2, func = inspect.stack()[0][3]) - self.active_stream = (sw, tryGetStream(sw)) + self.active_stream = (sw, tryGetStream(sw, self.dataStorage)) self.current_streams[0] = self.active_stream self.dockwidget.populateActiveStreamBranchDropdown(self) @@ -706,14 +727,14 @@ class SpeckleGIS: def handleStreamAdd(self, sw: StreamWrapper): try: - from speckle.ui.project_vars import set_project_streams + from speckle.speckle.ui.project_vars import set_project_streams except: - from speckle_toolbox.esri.toolboxes.speckle.ui.project_vars import set_project_streams + from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.project_vars import set_project_streams streamExists = 0 index = 0 try: - stream = tryGetStream(sw) + stream = tryGetStream(sw, self.dataStorage) for st in self.current_streams: if isinstance(stream, Stream) and st[0].stream_id == stream.id: diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/project_vars.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/project_vars.py index 2bc2b86..75f3cf7 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/project_vars.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/project_vars.py @@ -33,50 +33,51 @@ except: FIELDS = ["project_streams","project_layer_selection", "lat_lon"] -def get_project_streams(self: SpeckleGIS, content: str = None): +def get_project_streams(plugin: SpeckleGIS, content: str = None): try: - print("get proj streams") - print("GET proj streams") - project = self.gis_project + project = plugin.gis_project table = findOrCreateSpeckleTable(project) - if table is None: return + logToUser(table, level=0, func = inspect.stack()[0][3]) rows = arcpy.da.SearchCursor(table, "project_streams") saved_streams = [] for x in rows: + logToUser(x, level=0, func = inspect.stack()[0][3]) saved_streams.append(x[0]) temp = [] ######### need to check whether saved streams are available (account reachable) if len(saved_streams) > 0: for url in saved_streams: + if url=="": continue try: sw = StreamWrapper(url) try: - stream = tryGetStream(sw) + stream = tryGetStream(sw, plugin.dataStorage) except SpeckleException as e: logToUser(e.message, level=2, func = inspect.stack()[0][3]) stream = None #strId = stream.id # will cause exception if invalid temp.append((sw, stream)) except SpeckleException as e: - logToUser(e.message, 2) + logToUser(e.message, level=2, func = inspect.stack()[0][3]) #except GraphQLException as e: # logger.logToUser(e.message, Qgis.Warning) - self.current_streams = temp + plugin.current_streams = temp except Exception as e: logToUser(str(e), level=2, func = inspect.stack()[0][3]) -def set_project_streams(self: SpeckleGIS): +def set_project_streams(plugin: SpeckleGIS): try: print("SET proj streams") - project = self.gis_project + project = plugin.gis_project table = findOrCreateSpeckleTable(project) print("SET proj streams 2") - value = [stream[0].stream_url for stream in self.current_streams] #",".join() + value = [stream[0].stream_url for stream in plugin.current_streams] #",".join() print(value) + logToUser(value, level=0, func = inspect.stack()[0][3]) if table is not None: proj_layers = [] @@ -106,10 +107,10 @@ def set_project_streams(self: SpeckleGIS): except Exception as e: logToUser(str(e), level=2, func = inspect.stack()[0][3]) -def get_project_layer_selection(self: SpeckleGIS): +def get_project_layer_selection(plugin: SpeckleGIS): try: print("GET project layer selection from the table") - project = self.gis_project + project = plugin.gis_project table = findOrCreateSpeckleTable(project) if table is None: return @@ -134,7 +135,7 @@ def get_project_layer_selection(self: SpeckleGIS): break if found == 0: logToUser(f'Saved layer not found: "{layerPath}"', level=1, func = inspect.stack()[0][3]) - self.current_layers = temp + plugin.current_layers = temp except Exception as e: logToUser(str(e), level=2, func = inspect.stack()[0][3]) @@ -287,12 +288,12 @@ def findOrCreateSpeckleTable(project: ArcGISProject) -> Union[str, None]: arcpy.management.AddField(table, "lat_lon", "TEXT") cursor = arcpy.da.InsertCursor(table, FIELDS ) - cursor.insertRow(["",""]) + cursor.insertRow(["","",""]) del cursor except Exception as e: logToUser("Error creating a table: " + str(e), level=1, func = inspect.stack()[0][3]) - return None + raise e else: #print("table already exists") # make sure fileds exist @@ -354,266 +355,3 @@ def findOrCreateRow(table:str, fields: List[str]): except Exception as e: logToUser(str(e), level=2, func = inspect.stack()[0][3]) - -r''' -class speckleInputsClass: - #def __init__(self): - print("CREATING speckle inputs first time________") - instances = [] - accounts: List[Account] = get_local_accounts() - account = None - streams_default: Optional[List[Stream]] = None - - project = None - active_map = None - saved_streams: List[Optional[Tuple[StreamWrapper, Stream]]] = [] - stream_file_path: str = "" - all_layers: List[arcLayer] = [] - clients: List[SpeckleClient] = [] - - for acc in accounts: - if acc.isDefault: account = acc - new_client = SpeckleClient( - acc.serverInfo.url, - acc.serverInfo.url.startswith("https") - ) - new_client.authenticate_with_token(token=acc.token) - clients.append(new_client) - - speckle_client = None - if account: - speckle_client = SpeckleClient( - account.serverInfo.url, - account.serverInfo.url.startswith("https") - ) - speckle_client.authenticate_with_token(token=account.token) - streams_default = speckle_client.stream.search("") - - def __init__(self) -> None: - print("___start speckle inputs________") - self.all_layers = [] - try: - aprx = ArcGISProject('CURRENT') - self.project = aprx - # following will fail if no project found - self.active_map = aprx.activeMap - - if self.active_map is not None and isinstance(self.active_map, Map): # if project loaded - for layer in self.active_map.listLayers(): - try: geomType = arcpy.Describe(layer.dataSource).shapeType.lower() - except: geomType = '' #print(arcpy.Describe(layer.dataSource)) #and arcpy.Describe(layer.dataSource).shapeType.lower() != "multipatch") - if (layer.isFeatureLayer and geomType != "multipatch") or layer.isRasterLayer: self.all_layers.append(layer) #type: 'arcpy._mp.Layer' - self.stream_file_path: str = aprx.filePath.replace("aprx","gdb") + "\\speckle_streams.txt" - - if os.path.exists(self.stream_file_path): - try: - f = open(self.stream_file_path, "r") - content = f.read() - self.saved_streams = self.getProjectStreams(content) - f.close() - except: pass - - elif len(self.stream_file_path) >10: - f = open(self.stream_file_path, "x") - f.close() - f = open(self.stream_file_path, "w") - content = "" - f.write(content) - f.close() - except: self.project = None; print("Project not found") - self.instances.append(self) - - def getProjectStreams(self, content: str = None): - print("get proj streams") - if not content: - content = self.stream_file_path - try: - f = open(self.stream_file_path, "r") - content = f.read() - f.close() - except: pass - - ######### need to check whether saved streams are available (account reachable) - if content: - streamsTuples = [] - for i, url in enumerate(content.split(",")): - - streamExists = 0 - index = 0 - try: - #print(url) - sw = StreamWrapper(url) - stream = self.tryGetStream(sw) - - for st in streamsTuples: - if isinstance(stream, Stream) and st[0].stream_id == stream.id: - streamExists = 1; - break - index += 1 - if streamExists == 1: del streamsTuples[index] - streamsTuples.insert(0,(sw, stream)) - - except SpeckleException as e: - arcpy.AddMessage(str(e.args)) - return streamsTuples - else: return [] - - def tryGetStream (self,sw: StreamWrapper) -> Stream: - if isinstance(sw, StreamWrapper): - steamId = sw.stream_id - try: steamId = sw.stream_id.split("/streams/")[1].split("/")[0] - except: pass - - client = sw.get_client() - stream = client.stream.get(id = steamId, branch_limit = 100, commit_limit = 100) - if isinstance(stream, GraphQLException): - raise SpeckleException(stream.errors[0]['message']) - return stream - else: - raise SpeckleException('Invalid StreamWrapper provided') - -class toolboxInputsClass: - - print("CREATING UI inputs first time________") - instances = [] - lat: float = 0.0 - lon: float = 0.0 - active_stream: Optional[Stream] = None - active_stream_wrapper: Optional[StreamWrapper] = None - active_branch: Optional[Branch] = None - active_commit = None - selected_layers: List[Any] = [] - messageSpeckle: str = "" - action: int = 1 #send - project = None - stream_file_path: str = "" - # Get the target item's Metadata object - - def __init__(self) -> None: - print("___start UI inputs________") - try: - aprx = ArcGISProject('CURRENT') - project = aprx - self.stream_file_path: str = aprx.filePath.replace("aprx","gdb") + "\\speckle_streams.txt" - if os.path.exists(self.stream_file_path): - try: - f = open(self.stream_file_path, "r") - content = f.read() - self.lat, self.lon = self.get_survey_point(content) - f.close() - except: pass - except: print("Project not found") - try: - aprx = ArcGISProject('CURRENT') - self.project = aprx - except: self.project = None; print("Project not found"); arcpy.AddWarning("Project not found") - self.instances.append(self) - - def setProjectStreams(self, wr: StreamWrapper, add = True): - # ERROR 032659 Error queueing metrics request: - print("SET proj streams") - - if os.path.exists(self.stream_file_path) and ".gdb\\speckle_streams.txt" in self.stream_file_path: - - new_content = "" - - f = open(self.stream_file_path, "r") - existing_content = f.read() - f.close() - - f = open(self.stream_file_path, "w") - if str(wr.stream_url) in existing_content: - new_content = existing_content.replace(str(wr.stream_url) + "," , "") - else: - new_content = existing_content - - if add == True: new_content += str(wr.stream_url) + "," # add stream - else: pass # remove stream - - f.write(new_content) - f.close() - elif ".gdb\\speckle_streams.txt" in self.stream_file_path: - f = open(self.stream_file_path, "x") - f.close() - f = open(self.stream_file_path, "w") - f.write(str(wr.stream_url) + ",") - f.close() - - def get_survey_point(self, content = None) -> Tuple[float]: - # get from saved project - print("get survey point") - x = y = 0 - if not content: - content = None - if os.path.exists(self.stream_file_path) and ".gdb\\speckle_streams.txt" in self.stream_file_path: - try: - f = open(self.stream_file_path, "r") - content = f.read() - f.close() - except: pass - if content: - for i, coords in enumerate(content.split(",")): - if "speckle_sr_origin_" in coords: - try: - x, y = [float(c) for c in coords.replace("speckle_sr_origin_","").split(";")] - except: pass - return (x, y) - - def set_survey_point(self, coords: List[float]): - # from widget (2 strings) to local vars + update SR of the map - print("SET survey point") - - if len(coords) == 2: - pt = "speckle_sr_origin_" + str(coords[0]) + ";" + str(coords[1]) - if os.path.exists(self.stream_file_path) and ".gdb\\speckle_streams.txt" in self.stream_file_path: - - new_content = "" - f = open(self.stream_file_path, "r") - existing_content = f.read() - f.close() - - f = open(self.stream_file_path, "w") - if pt in existing_content: - new_content = existing_content.replace( pt , "") - else: - new_content = existing_content - - new_content += pt + "," # add point - f.write(new_content) - f.close() - elif ".gdb\\speckle_streams.txt" in self.stream_file_path: - f = open(self.stream_file_path, "x") - f.close() - f = open(self.stream_file_path, "w") - f.write(pt + ",") - f.close() - - # save to project; crearte SR - self.lat, self.lon = coords[0], coords[1] - newCrsString = "+proj=tmerc +ellps=WGS84 +datum=WGS84 +units=m +no_defs +lon_0=" + str(self.lon) + " lat_0=" + str(self.lat) + " +x_0=0 +y_0=0 +k_0=1" - newCrs = osr.SpatialReference() - newCrs.ImportFromProj4(newCrsString) - newCrs.MorphToESRI() # converts the WKT to an ESRI-compatible format - - - validate = True if len(newCrs.ExportToWkt())>10 else False - - if validate: - newProjSR = arcpy.SpatialReference() - newProjSR.loadFromString(newCrs.ExportToWkt()) - - #source = osr.SpatialReference() - #source.ImportFromWkt(self.project.activeMap.spatialReference.exportToString()) - #transform = osr.CoordinateTransformation(source, newCrs) - - self.project.activeMap.spatialReference = newProjSR - arcpy.AddMessage("Custom project CRS successfully applied") - else: - arcpy.AddWarning("Custom CRS could not be created") - - else: - arcpy.AddWarning("Custom CRS could not be created: not enough coordinates provided") - - return True -''' - \ No newline at end of file diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/speckle_qgis_dialog.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/speckle_qgis_dialog.py index e10eb9d..5b1edf4 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/speckle_qgis_dialog.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/speckle_qgis_dialog.py @@ -429,9 +429,9 @@ class SpeckleGISDialog(QMainWindow): print("populate layer dropdown / clicked save selection") if not self: return try: - from speckle.ui.project_vars import set_project_layer_selection + from speckle.speckle.ui.project_vars import set_project_layer_selection except: - from speckle_toolbox.esri.toolboxes.speckle.ui.project_vars import set_project_layer_selection + from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.project_vars import set_project_layer_selection try: self.layersWidget.clear() @@ -531,9 +531,9 @@ class SpeckleGISDialog(QMainWindow): def populateProjectStreams(self, plugin): try: - from speckle.ui.project_vars import set_project_streams + from speckle.speckle.ui.project_vars import set_project_streams except: - from speckle_toolbox.esri.toolboxes.speckle.ui.project_vars import set_project_streams + from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.project_vars import set_project_streams try: if not self: return diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/validation.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/validation.py index c5794fa..9dc2a1b 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/validation.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/validation.py @@ -13,30 +13,101 @@ try: from speckle.speckle.ui.logger import logToUser except: from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser - -def tryGetStream (sw: StreamWrapper) -> Union[Stream, None]: - try: - client = sw.get_client() - stream = client.stream.get(id = sw.stream_id, branch_limit = 100, commit_limit = 100) - if isinstance(stream, GraphQLException): - raise SpeckleException(stream.errors[0]['message']) - return stream - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None -def validateStream(streamWrapper: StreamWrapper) -> Union[Stream, None]: +def tryGetStream( + sw: StreamWrapper, dataStorage, write=False, dockwidget=None +) -> Union[Stream, None]: + try: + # print("tryGetStream") + client, stream = tryGetClient(sw, dataStorage, write, dockwidget) + return stream + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=dockwidget) + return None + + +def tryGetClient(sw: StreamWrapper, dataStorage, write=False, dockwidget=None): + # only streams with write access + try: + client = None + savedRole = None + savedStreamId = None + for acc in dataStorage.accounts: + # only check accounts on selected server + if acc.serverInfo.url in sw.server_url: + client = SpeckleClient( + acc.serverInfo.url, acc.serverInfo.url.startswith("https") + ) + try: + client.authenticate_with_account(acc) + if client.account.token is not None: + break + except SpeckleException as ex: + if "already connected" in ex.message: + logToUser( + "Dependencies versioning error.\nClick here for details.", + url="dependencies_error", + level=2, + plugin=dockwidget, + ) + return + else: + raise ex + + # if token still not found + if client is None or client.account.token is None: + for acc in dataStorage.accounts: + client = sw.get_client() + if client is not None: + break + + if client is not None: + stream = client.stream.get( + id=sw.stream_id, branch_limit=100, commit_limit=100 + ) + if isinstance(stream, Stream): + # print(stream.role) + if write == False: + # try get stream, only read access needed + # print("only read access needed") + return client, stream + else: + # check write access + # print("write access needed") + if stream.role is None or ( + isinstance(stream.role, str) and "reviewer" in stream.role + ): + savedRole = stream.role + savedStreamId = stream.id + else: + return client, stream + + if savedRole is not None and savedStreamId is not None: + logToUser( + f"You don't have write access to the stream '{savedStreamId}'. You role is '{savedRole}'", + level=2, + func=inspect.stack()[0][3], + plugin=dockwidget, + ) + + return None, None + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=dockwidget) + return None, None + + +def validateStream(stream: Stream, dockwidget) -> Union[Stream, None]: try: - stream = tryGetStream(streamWrapper) - if isinstance(stream, SpeckleException): return None + if isinstance(stream, SpeckleException): + return None if stream.branches is None: - logToUser("Stream has no branches", level=2, func = inspect.stack()[0][3]) + logToUser("Stream has no branches", level=1, plugin=dockwidget) return None return stream except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None + logToUser(e, level=2, plugin=dockwidget) + return def validateBranch(stream: Stream, branchName: str, checkCommits: bool) -> Union[Branch, None]: diff --git a/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/ConnectorBindings.py b/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/ConnectorBindings.py new file mode 100644 index 0000000..8896faa --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/ConnectorBindings.py @@ -0,0 +1,111 @@ + +from typing import Dict, List + +from numpy import double +from import UpdateSavedStreams +from import UpdateSelectedStream + +from specklepy_qt_ui.qt_ui.ConnectorBindings import ConnectorBindings +from specklepy_qt_ui.qt_ui.Models.StreamState import StreamState + +class QGISBindings(ConnectorBindings): + + def __init__(self): + pass + + def UpdateSavedStreams(self, streams: List[StreamState]): + UpdateSavedStreams(streams) + + def UpdateSelectedStream(self): + UpdateSelectedStream() + + def Open3DView(self, viewCoordinates: List[double], viewName: str = ""): + '''Opens a 3D view in the host application + viewCoordinates: First three values are the camera position, second three the target. + viewName: Id or Name of the view''' + return + + def GetHostAppNameVersion(self)-> str: + '''Gets the current host application name with version.''' + return + + def GetHostAppName(self) -> str: + '''Gets the current host application name.''' + return + + def GetFileName(self) -> str: + '''Gets the current opened/focused file's name. + Make sure to check regarding unsaved/temporary files.''' + return + + def GetDocumentId(self) -> str: + '''Gets the current opened/focused file's id. + Generate one in here if the host app does not provide one.''' + return + + def GetDocumentLocation(self) -> str: + '''Gets the current opened/focused file's locations. + Make sure to check regarding unsaved/temporary files.''' + return + + def ResetDocument(self): + '''Clears the document state of selections and previews''' + return + + def GetActiveViewName(self) -> str: + '''Gets the current opened/focused file's view, if applicable.''' + return + + def GetStreamsInFile(self) -> List[StreamState]: + '''Returns the serialised clients present in the current open host file.''' + return + + def WriteStreamsToFile(self, streams: List[StreamState]): + '''Writes serialised clients to the current open host file.''' + return + + def AddNewStream(self, state: StreamState): + '''Adds a new client and persists the info to the host file''' + return + + def PersistAndUpdateStreamInFile(self, state: StreamState): + '''Persists the stream info to the host file; if maintaining a local in memory copy, make sure to update it too.''' + return + + def SendStream(self, state: StreamState, progress: ProgressViewModel) -> str: + '''Pushes a client's stream''' + return + + def PreviewSend(self, state: StreamState, progress: ProgressViewModel): + '''Previews a send operation''' + + def ReceiveStream(self, state: StreamState, progress: ProgressViewModel) -> StreamState: + '''Receives stream data from the server''' + + def PreviewReceive(self, state: StreamState, progress: ProgressViewModel) -> StreamState: + '''Previews a receive operation''' + + def GetSelectedObjects(self) -> List[str]: + '''Adds the current selection to the provided client.''' + + def GetObjectsInView(self) -> List[str]: + '''Gets a list of objects in the currently active view''' + + def SelectClientObjects(self, objs: List[str], deselect: bool = False): + '''clients should be able to select/preview/hover one way or another their associated objects''' + + def GetSelectionFilters(self) -> List[ISelectionFilter]: + '''Should return a list of filters that the application supports.''' + + def GetReceiveModes(self) -> List[ReceiveMode]: + '''Should return a list of receive modes that the application supports.''' + + def GetCustomStreamMenuItems(self) -> List[MenuItem]: + '''Return a list of custom menu items for stream cards.''' + + def GetSettings(self) -> List[ISetting]: + return + + def ImportFamilyCommand(self, Mapping: Dict[str, List[MappingValue]] ) -> Dict[str, List[MappingValue]] : + '''Imports family symbols in Revit''' + return diff --git a/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/__init__.py b/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/dockwidget_main.py b/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/dockwidget_main.py new file mode 100644 index 0000000..bb2a400 --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/dockwidget_main.py @@ -0,0 +1,104 @@ +import threading +from specklepy_qt_ui.qt_ui.dockwidget_main import SpeckleQGISDialog as SpeckleQGISDialog_UI +import specklepy_qt_ui.qt_ui + +from speckle.ui_widgets.widget_transforms import MappingSendDialogQGIS + +from PyQt5 import QtWidgets, uic +import os +import inspect +from specklepy.logging.exceptions import (SpeckleException, GraphQLException) +from specklepy.logging import metrics + + +from PyQt5 import QtWidgets, uic +from PyQt5.QtWidgets import QCheckBox, QListWidgetItem, QHBoxLayout, QWidget +from PyQt5.QtCore import pyqtSignal + + +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.DataStorage import DataStorage + +FORM_CLASS, _ = uic.loadUiType( + os.path.join(os.path.dirname(specklepy_qt_ui.qt_ui.__file__), os.path.join("ui", "dockwidget_main.ui") ) +) + +class SpeckleQGISDialog(SpeckleQGISDialog_UI, FORM_CLASS): + + def __init__(self, parent=None): + """Constructor.""" + super(SpeckleQGISDialog_UI, self).__init__(parent) + + self.setupUi(self) + self.runAllSetup() + + def createMappingDialog(self): + + if self.mappingSendDialog is None: + self.mappingSendDialog = MappingSendDialogQGIS(None) + self.mappingSendDialog.dataStorage = self.dataStorage + + self.mappingSendDialog.runSetup() + + def completeStreamSection(self, plugin): + try: + self.streams_remove_button.clicked.connect( lambda: self.onStreamRemoveButtonClicked(plugin) ) + self.streamList.currentIndexChanged.connect( lambda: self.onActiveStreamChanged(plugin) ) + self.streamBranchDropdown.currentIndexChanged.connect( lambda: self.populateActiveCommitDropdown(plugin) ) + return + except Exception as e: + logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + return + + def onStreamRemoveButtonClicked(self, plugin): + try: + from speckle.utils.project_vars import set_project_streams + if not self: return + index = self.streamList.currentIndex() + if len(plugin.current_streams) > 0: plugin.current_streams.pop(index) + plugin.active_stream = None + self.streamBranchDropdown.clear() + self.commitDropdown.clear() + #self.streamIdField.setText("") + + set_project_streams(plugin) + self.populateProjectStreams(plugin) + except Exception as e: + logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + return + + def populateProjectStreams(self, plugin): + try: + from speckle.utils.project_vars import set_project_streams + if not self: return + self.streamList.clear() + for stream in plugin.current_streams: + self.streamList.addItems( + [f"Stream not accessible - {stream[0].stream_id}" if stream[1] is None or isinstance(stream[1], SpeckleException) else f"{stream[1].name}, {stream[1].id} | {stream[0].stream_url.split('/streams')[0].split('/projects')[0]}"] + ) + if len(plugin.current_streams)==0: self.streamList.addItems([""]) + self.streamList.addItems(["Create New Stream"]) + set_project_streams(plugin) + index = self.streamList.currentIndex() + if index == -1: self.streams_remove_button.setEnabled(False) + else: self.streams_remove_button.setEnabled(True) + + if len(plugin.current_streams)>0: plugin.active_stream = plugin.current_streams[0] + except Exception as e: + logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + return + + def cancelOperations(self): + #print("____cancelOperations______") + for t in threading.enumerate(): + #print(t.name) + if 'speckle_' in t.name: + #print(f"thread to kill: {t}") + t.kill() + t.join() + # not printed if same thread + #print("Remaining threads: ") + #print(threading.enumerate()) + \ No newline at end of file diff --git a/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/widget_transforms.py b/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/widget_transforms.py new file mode 100644 index 0000000..d25c891 --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/widget_transforms.py @@ -0,0 +1,404 @@ +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.qt_ui.widget_transforms import MappingSendDialog +from specklepy_qt_ui.qt_ui.utils.logger import displayUserMsg +from specklepy_qt_ui.qt_ui.DataStorage import DataStorage + +from speckle.utils.panel_logging import 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 +import specklepy_qt_ui.qt_ui + +FORM_CLASS, _ = uic.loadUiType( + os.path.join( + os.path.join( + os.path.dirname(specklepy_qt_ui.qt_ui.__file__), "ui", "transforms.ui" + ) + ) +) + + +class MappingSendDialogQGIS(MappingSendDialog, FORM_CLASS): + def __init__(self, parent=None): + super(MappingSendDialog, self).__init__(parent, QtCore.Qt.WindowStaysOnTopHint) + self.setupUi(self) + self.runAllSetup() + + def runSetup(self): + self.attr_label.setEnabled(False) + self.attrDropdown.setEnabled(False) + self.dialog_button.setText("Apply") + + self.populateTransforms() + self.populateLayersByTransform() + self.populateSavedTransforms(self.dataStorage) + self.populateSavedElevationLayer(self.dataStorage) + + # self.elevationLayerDropdown.currentIndexChanged.connect(self.saveElevationLayer) + + def populateSavedTransforms( + self, dataStorage + ): # , 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: + logToUser( + 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.\nTransformation 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.\nTransformation is removed.", + 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 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(self.dataStorage) + + try: + metrics.track( + "Connector Action", + self.dataStorage.active_account, + { + "name": "Transformation on Send Add", + "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) + + try: + metrics.track( + "Connector Action", + self.dataStorage.active_account, + { + "name": "Transformation on Send Remove", + "Transformation": listItem.split(" -> ")[1], + "connector_version": str(self.dataStorage.plugin_version), + }, + ) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + + self.populateSavedTransforms(self.dataStorage) + set_transformations(self.dataStorage) + + def onOkClicked(self): + try: + self.saveElevationLayer() + 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 + + # for satellites + if "texture" in transform.lower(): + listItem = layer.name() + # for elevation to mesh + elif "mesh" in transform.lower(): + try: + if layer.bandCount() == 1: + listItem = layer.name() + except: + pass + + 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 populateSavedElevationLayer( + self, dataStorage + ): # , 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 + elif layer.bandCount() != 1: + 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): + # print("saveElevationLayer") + 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()) + try: + if self.dataStorage.elevationLayer.name() == layerName: + return + except: + pass + + 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] + # print(all_l_names) + + for l in self.dataStorage.all_layers: + if layerName == l.name(): + layer = l + try: + # print(layerName) + 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 + else: + self.dataStorage.elevationLayer = layer + set_elevationLayer(self.dataStorage) + logToUser( + f"Elevation layer '{layerName}' successfully set", + level=0, + ) + break + except: + displayUserMsg( + f"Layer '{layer.name()}' is not found in the project", + level=1, + ) + layer = None + break + + 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")