Compare commits

...

29 Commits

Author SHA1 Message Date
KatKatKateryna 18234800dd 2.10.3 whl to release 2022-11-30 01:27:30 +08:00
KatKatKateryna 2949142140 Merge pull request #32 from specklesystems/kate/2.10.3
Kate/2.10.2-beta
2022-11-24 10:58:29 +08:00
KatKatKateryna 5c9138238a whl file 2.10.2-beta 2022-11-24 10:55:59 +08:00
KatKatKateryna 276c78603b fixing customCRS location (negative values read properly)) 2022-11-24 10:54:12 +08:00
KatKatKateryna 323d23cfaf use custom SR via WKT (not name) 2022-11-23 01:44:59 +08:00
KatKatKateryna ab8805d5f2 whl for manual install 2022-11-22 04:34:02 +08:00
KatKatKateryna 8b0ee41ed7 Merge pull request #31 from specklesystems/kate/2.10_fix
msg
2022-11-22 02:15:04 +08:00
KatKatKateryna 92ce4c6abb catch wrong account exception 2022-11-22 02:11:32 +08:00
KatKatKateryna 9596e14c2d msg 2022-11-22 01:57:16 +08:00
KatKatKateryna cb5240c4c0 Merge pull request #30 from specklesystems/kate/2.10_fix
installer_copying file; authors data
2022-11-22 01:50:48 +08:00
KatKatKateryna edc686526a installer_copying file; authors data 2022-11-22 01:49:55 +08:00
KatKatKateryna ed97d83468 Merge pull request #29 from specklesystems/kate/2.10
Kate/2.10
2022-11-22 01:08:45 +08:00
KatKatKateryna 5b86638749 no multipatch layers in UI 2022-11-22 01:07:02 +08:00
KatKatKateryna 1e308bf1ab cleaning 2022-11-22 00:37:38 +08:00
KatKatKateryna 971d71fc7e yml copying files; flattening Base attributes; fix message on send 2022-11-22 00:26:42 +08:00
KatKatKateryna 821acf93d8 clean print statements 2022-11-21 11:05:15 +08:00
KatKatKateryna 0f4494936e Receiving BIM with attributes (some need to be flattened) 2022-11-18 15:33:55 +08:00
KatKatKateryna 5791667bfe receive BIM meshes (no properties yet) 2022-11-18 04:07:54 +08:00
KatKatKateryna 364c8fe507 added quick test code for arcgis panel 2022-11-17 19:52:10 +08:00
KatKatKateryna 791dd045c3 DO NOT send BIM layers from ArcGIS 2022-11-16 00:02:08 +08:00
KatKatKateryna 999c67ea8d Update Readme.md 2022-11-02 14:47:42 +08:00
KatKatKateryna 772ed3334d Update Readme.md 2022-11-02 02:58:13 +08:00
KatKatKateryna 3b9188dabe Merge pull request #24 from specklesystems/kate/2.9_debugging
Kate/2.9 debugging
2022-11-02 02:46:53 +08:00
KatKatKateryna 895eded593 path to .exe corrected 2022-11-02 02:21:49 +08:00
KatKatKateryna fa4908ed1a fixed bug with dynamically changing received polycurve 2022-11-02 02:11:37 +08:00
KatKatKateryna 3cf079acaf add .pyt after user clicks Install; clone conda env by condition(if exists/if invalid/if doesn't exist); package import directly to new env. clean lists on Refresh; add units on Send; raster stretch-coloring on send 2022-11-01 20:34:12 +08:00
KatKatKateryna 6d6df92d42 Manage conda envs and packages all from 1 file (conda_clone_activate); checked Manual install instructions 2022-11-01 06:29:59 +08:00
KatKatKateryna 4fcd408759 patching file adapted 2022-11-01 02:18:17 +08:00
KatKatKateryna 7bf8957989 Installing dependencies properly; .pyt back to debugging mode; creatingGroupLayer adapted to ArcGIS 3.0 2022-11-01 00:31:04 +08:00
27 changed files with 1011 additions and 1106 deletions
+2
View File
@@ -34,6 +34,8 @@ jobs:
$version = "$($ver).$($env:CIRCLE_BUILD_NUM)"
echo $semver
python patch_version.py $semver
python setup.py sdist bdist_wheel
Copy-Item -Path "dist\speckle_toolbox-$($ver)-py3-none-any.whl" -Destination "speckle_arcgis_installer"
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\arcgis.iss
- when:
condition: << parameters.installer >>
+1 -1
View File
@@ -74,7 +74,7 @@ Setup is adapted from [this tutorial](https://pro.arcgis.com/en/pro-app/2.8/arcp
#### Dev Environment
For a better development experience in your editor, we recommend creating a [virtual environment in ArcGIS](https://pro.arcgis.com/en/pro-app/2.8/arcpy/get-started/work-with-python-environments.htm). In the venv, you'll just need to install `specklepy`.
For a better development experience in your editor, we recommend creating a [virtual conda environment in ArcGIS](https://pro.arcgis.com/en/pro-app/2.8/arcpy/get-started/work-with-python-environments.htm). In the new conda environment, you'll just need to install `specklepy` and `panda3d`.
### Debugging
+29 -22
View File
@@ -5,17 +5,18 @@ def patch_installer(tag):
"""Patches the installer with the correct connector version and specklepy version"""
iss_file = "speckle-sharp-ci-tools/arcgis.iss"
setup_whl_file = "setup.py"
conda_file = "speckle_arcgis_installer/conda_clone_activate.py"
toolbox_install_file = "speckle_arcgis_installer/toolbox_install.py"
toolbox_manual_install_file = "speckle_arcgis_installer/toolbox_install_manual.py"
#py_tag = get_specklepy_version()
with open(iss_file, "r") as file:
lines = file.readlines()
if "#define AppVersion" in lines[12]: lines[12] = f'#define AppVersion "{tag.split("-")[0]}"\n'
else: lines.insert(12, f'#define AppVersion "{tag.split("-")[0]}"\n')
if "#define AppInfoVersion" in lines[13]: lines[13] = f'#define AppInfoVersion "{tag}"\n'
else: lines.insert(13, f'#define AppInfoVersion "{tag}"\n')
for i, line in enumerate(lines):
if "#define AppVersion " in line:
lines[i] = f'#define AppVersion "{tag.split("-")[0]}"\n'
if "#define AppInfoVersion " in line:
lines[i] = f'#define AppInfoVersion "{tag}"\n'
with open(iss_file, "w") as file:
file.writelines(lines)
print(f"Patched installer with connector v{tag} and specklepy ")
@@ -23,27 +24,33 @@ def patch_installer(tag):
with open(setup_whl_file, "r") as file:
lines = file.readlines()
if "version=" in lines[17]: lines[17] = f'\t\t\tversion="{tag.split("-")[0]}",\n'
else: lines.insert(17, f'\t\t\tversion="{tag.split("-")[0]}",\n')
for i, line in enumerate(lines):
if "version=" in line:
lines[i] = f'\t\t\tversion="{tag.split("-")[0]}",\n'
break
with open(setup_whl_file, "w") as file:
file.writelines(lines)
print(f"Patched whl setup with connector v{tag} and specklepy ")
file.close()
with open(toolbox_install_file, "r") as file:
lines = file.readlines()
for i, line in enumerate(lines):
if "-py3-none-any.whl" in line:
p1 = line.split("-py3-none-any.whl")[0].split("-")[0]
p2 = f'{tag.split("-")[0]}'
p3 = line.split("-py3-none-any.whl")[1]
lines[i] = p1+"-"+p2+"-py3-none-any.whl"+p3
with open(toolbox_install_file, "w") as file:
file.writelines(lines)
print(f"Patched toolbox_installer with connector v{tag} and specklepy ")
file.close()
def whlFileRename(fileName: str):
with open(fileName, "r") as file:
lines = file.readlines()
for i, line in enumerate(lines):
if "-py3-none-any.whl" in line:
p1 = line.split("-py3-none-any.whl")[0].split("-")[0]
p2 = f'{tag.split("-")[0]}'
p3 = line.split("-py3-none-any.whl")[1]
lines[i] = p1+"-"+p2+"-py3-none-any.whl"+p3
with open(fileName, "w") as file:
file.writelines(lines)
print(f"Patched toolbox_installer with connector v{tag} and specklepy ")
file.close()
whlFileRename(conda_file)
whlFileRename(toolbox_install_file)
whlFileRename(toolbox_manual_install_file)
def main():
if len(sys.argv) < 2:
+4 -2
View File
@@ -1,7 +1,7 @@
# to build an installer: run cmd from this folder or use terminal: "%PROGRAMFILES%\\ArcGIS\\Pro\\bin\\Python\\envs\\arcgispro-py3\\python.exe"
#
# 1) python patch_version.py 2.x.x
# 2) (if needed: pip install wheel) python setup.py sdist bdist_wheel #C:\\Users\\username\\Documents\\00_Speckle\\GitHub\\speckle-arcgis\\setup.py sdist bdist_wheel
# 2) python setup.py sdist bdist_wheel #C:\\Users\\username\\Documents\\00_Speckle\\GitHub\\speckle-arcgis\\setup.py sdist bdist_wheel
# copy .whl from "dist" to "speckle_arcgis_installer"
# ref: https://pro.arcgis.com/en/pro-app/2.8/arcpy/geoprocessing_and_python/distributing-python-modules.htm
@@ -15,7 +15,9 @@ def read(fname):
setup(name='speckle_toolbox',
author='SpeckleSystems',
version="2.9.3",
version="2.9.4",
author_email="connectors@speckle.systems",
url="https://speckle.systems/",
description=("Example for extending geoprocessing through Python modules"),
long_description=read('Readme.md'),
python_requires='~=3.3',
+13
View File
@@ -0,0 +1,13 @@
### Manual installation
1. Download present "speckle_arcgis_installer" folder
2. Clone the default ArcGIS Pro conda environment and restart ArcGIS Pro
- for 2.9.0: Project-> Python-> Manage Environments-> Clone Default
- for 3.0.0: Project-> Package Manager-> Active Environment (Environment Manager)-> Clone arcgispro-py3
3. Change the path to your new environemnt Python.exe if necessary (variable "pythonPath" in "toolbox_install_manual.py")
4. Enter the location of 'toolbox_install_manual.py' in the following command and run this command in ArcGIS Python console (View -> Python Window)
```python
import sysconfig; import subprocess; x = sysconfig.get_paths()['data'] + r"\python.exe"; subprocess.run((x, 'C:\\Users\\pathToFolder\\speckle_arcgis_installer\\toolbox_install_manual.py'), capture_output=True, text=True, shell=True, timeout=1000 )
```
+1
View File
@@ -0,0 +1 @@
from speckle.speckle_arcgis import *
@@ -8,50 +8,128 @@ import subprocess
from subprocess import CalledProcessError
from subprocess_call import subprocess_call
from msilib.schema import Error
import sys
import arcpy
def setup():
#print(plugin_dir)
pythonExec = get_python_path() # import numpy; import os; print(os.path.abspath(numpy.__file__))
#print(pythonExec)
if pythonExec is None: # env is default, need to restart ArcGIS
return False
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"
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
#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"
#clone_env(pythonExec, env_new_name) # only if doesn't exist yet
newExec = clone_env(pythonExec, env_new_name) # only if doesn't exist yet
if not os.path.exists(newExec): return None
activate_env(env_new_name)
return pythonExec
else: pass
return newExec
else: return None
def clone_env(pythonExec_old: str, env_new_name: str):
install_folder = os.getenv('APPDATA').replace("\\Roaming","") + r"\Local\ESRI\conda\envs" #r"%LOCALAPPDATA%\ESRI\conda\envs"
#print("Clone default ArcGIS Pro conda env")
#print(install_folder)
#if not os.path.exists(install_folder): os.makedirs(install_folder)
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\...
subprocess_call( [ conda_exe, 'create', '--clone', default_env, '-p', new_env] ) # will not execute if already exists
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
print(f"Removing invalid environment {new_env}")
os.remove(new_env)
if os.path.exists(conda_exe) and os.path.exists(default_env) and not os.path.exists(new_env):
print("Wait for the default ArcGIS Pro conda environment to be cloned")
subprocess_call( [ conda_exe, 'config', '--set', 'ssl_verify', 'False'] )
subprocess_call( [ conda_exe, 'create', '--clone', default_env, '-p', new_env] ) # will not execute if already exists
subprocess_call( [ conda_exe, 'config', '--set', 'ssl_verify', 'True'] )
elif os.path.exists(new_env) and os.path.exists(new_env + "\\python.exe"):
print(f"Environment {new_env} already exists, preparing to install packages..")
print(new_env + "\\python.exe")
return new_env + "\\python.exe"
def activate_env(env_new_name: str):
# 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)
#print(variable)
# activate new env : https://support.esri.com/en/technical-article/000024206
setup()
def installToolbox(newExec: str):
print("Installing Speckle Toolbox")
whl_file = os.path.join(os.path.dirname(__file__), "speckle_toolbox-2.9.4-py3-none-any.whl" )
print(whl_file)
subprocess_call([newExec, '-m','pip','install','--upgrade', '--force-reinstall', whl_file])
# to uninstall: cmd.exe "C:\\Users\\username\\AppData\\Local\\ESRI\\conda\\envs\\arcgispro-2.9.4-py3-none-any.whl
return
def installDependencies(pythonExec: str, pkgName: str, pkgVersion: str):
# install pip
print(pythonExec)
try:
import pip
except:
getPipFilePath = os.path.join(os.path.dirname(__file__), "get_pip.py") #TODO: give actual folder path
exec(open(getPipFilePath).read())
# just in case the included version is old
subprocess_call([pythonExec, "-m", "pip", "install", "--upgrade", "pip"])
# install package
try:
#import importlib #importlib.import_module(pkgName)
if pkgName == "specklepy":
import specklepy
if pythonExec.replace("\\python.exe","") not in (os.path.abspath(specklepy.__file__)):
print(f"Installing {pkgName} to {pythonExec}")
#subprocess_call( [pythonExec, "-m", "pip", "uninstall", f"{pkgName}"])
subprocess_call( [pythonExec, "-m", "pip", "install", f"{pkgName}=={pkgVersion}"])
elif pkgName == "panda3d":
import panda3d
if pythonExec.replace("\\python.exe","") not in (os.path.abspath(panda3d.__file__)):
print(f"Installing {pkgName} to {pythonExec}")
subprocess_call( [pythonExec, "-m", "pip", "install", f"{pkgName}=={pkgVersion}"])
except Exception as e:
print(f"{pkgName} not installed")
subprocess_call( [pythonExec, "-m", "pip", "install", f"{pkgName}=={pkgVersion}"])
# Check if package needs updating
r'''
try:
print(f"Attempting to update {pkgName} to {pkgVersion}")
result = subprocess_call(
[
pythonExec,
"-m",
"pip",
"install",
"--upgrade",
f"{pkgName}=={pkgVersion}",
]
)
if result == True:
print(f"{pkgName} upgraded")
return True
else:
return False
except Exception as e:
print(e)
print(e.with_traceback)
'''
return True
pythonPath = setup()
if pythonPath is not None:
installToolbox(pythonPath)
installDependencies(pythonPath, "specklepy", "2.9.0" )
installDependencies(pythonPath, "panda3d", "1.10.11" )
@@ -1,73 +0,0 @@
# MANUAL INSTALLATION:
# 1. enter correct path to Python exe of your new environemnt in line 10
# 2. enter the location of 'manual_toolbox_install.py' in the following command and run this command in ArcGIS Python console (View -> Python Window)
# import sysconfig; import subprocess; x = sysconfig.get_paths()['data'] + r"\python.exe"; subprocess.run((x, 'C:\\Users\\Kateryna\\Documents\\00_Speckle\\GitHub\\speckle-arcgis\\speckle_arcgis_installer\\manual_toolbox_install.py'), capture_output=True, text=True, shell=True, timeout=1000 )
# then restart
from subprocess_call import subprocess_call
import os
pythonPath = "C:\\ Users\\Kateryna\\AppData\\Local\\ESRI\\conda\\envs\\arcgispro-py3-speckle\\python.exe"
def installToolbox(newExec: str):
print("Installing Speckle Toolbox")
whl_file = os.path.join(os.path.dirname(__file__), "speckle_toolbox-0.1-py3-none-any.whl" )
subprocess_call([newExec, '-m','pip','install','--upgrade', '--force-reinstall', whl_file])
return
def installDependencies(pythonExec: str):
print("Installing dependencies")
print(pythonExec)
try:
import pip
except:
getPipFilePath = os.path.join(os.path.dirname(__file__), "get_pip.py")
exec(open(getPipFilePath).read())
# just in case the included version is old
subprocess_call([pythonExec, "-m", "pip", "install", "--upgrade", "pip"])
pkgVersion = "2.7.4"
pkgName = "specklepy"
try:
import specklepy
except Exception as e:
subprocess_call([ pythonExec, "-m", "pip", "install", f"{pkgName}=={pkgVersion}"])
pkgVersion = "1.10.11"
pkgName = "panda3d"
try:
import panda3d
except Exception as e:
print("panda3d not installed")
subprocess_call( [pythonExec, "-m", "pip", "install", f"{pkgName}=={pkgVersion}"])
# Check if specklpy needs updating
try:
print(f"Attempting to update specklepy to {pkgVersion}")
# pip.main(['install', "specklepy==2.7.4"])
result = subprocess_call(
[
pythonExec,
"-m",
"pip",
"install",
"--upgrade",
f"{pkgName}=={pkgVersion}",
]
)
if result == True:
print("specklepy upgraded")
return True
else:
return False
except Exception as e:
print(e)
print(e.with_traceback)
return True
installToolbox(pythonPath)
installDependencies(pythonPath)
+2 -2
View File
@@ -19,7 +19,7 @@ def subprocess_call(*args, **kwargs):
try:
# if manually: cmd.exe -> conda activate [env folder] -> pip install specklepy
result = subprocess.run(*args, capture_output=True, text=True, shell=True, timeout=1000)
#print(result)
print(result)
#result = subprocess.Popen( arg, shell=True, stdout=subprocess.PIPE) #, stderr=subprocess.STDOUT)
#retcode = subprocess.check_call(*args, **kwargs) # Creates infinite loop, known issue: https://github.com/python/cpython/issues/87512
except CalledProcessError as e:
@@ -32,7 +32,7 @@ def subprocess_call(*args, **kwargs):
#print(str(e))
return False
except: print("unknown error")
print("end")
#print("end")
return True
+16 -46
View File
@@ -6,65 +6,34 @@ pythonPath = os.getenv('APPDATA').replace("\\Roaming","") + r"\Local\ESRI\conda\
def installToolbox(newExec: str):
print("Installing Speckle Toolbox")
whl_file = os.path.join(os.path.dirname(__file__), "speckle_toolbox-2.9.3-py3-none-any.whl" )
whl_file = os.path.join(os.path.dirname(__file__), "speckle_toolbox-2.9.4-py3-none-any.whl" )
print(whl_file)
subprocess_call([newExec, '-m','pip','install','--upgrade', '--force-reinstall', whl_file])
# to uninstall: cmd.exe "C:\\Users\\username\\AppData\\Local\\ESRI\\conda\\envs\\arcgispro-2.9.3-py3-none-any.whl
# to uninstall: cmd.exe "C:\\Users\\username\\AppData\\Local\\ESRI\\conda\\envs\\arcgispro-2.9.4-py3-none-any.whl
return
def installDependencies(pythonExec: str):
#print("Installing dependencies")
def installDependencies(pythonExec: str, pkgName: str, pkgVersion: str):
# install pip
print(pythonExec)
try:
import pip
except:
getPipFilePath = os.path.join(os.path.dirname(__file__), "get_pip.py") #TODO: give actual folder path
exec(open(getPipFilePath).read())
# just in case the included version is old
subprocess_call([pythonExec, "-m", "pip", "install", "--upgrade", "pip"])
pkgVersion = "2.9.0"
pkgName = "specklepy"
# install package
try:
import specklepy # C:\Users\username\AppData\Roaming\Python\Python37\site-packages\specklepy\__init__.py
import importlib
importlib.import_module(pkgName)
except Exception as e:
subprocess_call([ pythonExec, "-m", "pip", "install", f"{pkgName}=={pkgVersion}"])
# Check if specklpy needs updating
try:
print(f"Attempting to update specklepy to {pkgVersion}")
result = subprocess_call(
[
pythonExec,
"-m",
"pip",
"install",
"--upgrade",
f"{pkgName}=={pkgVersion}",
]
)
if result == True:
print("specklepy upgraded")
return True
else:
return False
except Exception as e:
print(e)
print(e.with_traceback)
pkgVersion = "1.10.11"
pkgName = "panda3d"
try:
import panda3d
except Exception as e:
print("panda3d not installed")
print(f"{pkgName} not installed")
subprocess_call( [pythonExec, "-m", "pip", "install", f"{pkgName}=={pkgVersion}"])
# Check if specklpy needs updating
# Check if package needs updating
try:
print(f"Attempting to update panda3d to {pkgVersion}")
print(f"Attempting to update {pkgName} to {pkgVersion}")
result = subprocess_call(
[
pythonExec,
@@ -76,16 +45,17 @@ def installDependencies(pythonExec: str):
]
)
if result == True:
print("dependencies upgraded")
print(f"{pkgName} upgraded")
return True
else:
return False
except Exception as e:
print(e)
print(e.with_traceback)
return True
installToolbox(pythonPath)
installDependencies(pythonPath)
installToolbox(pythonPath)
installDependencies(pythonPath, "specklepy", "2.9.0" )
installDependencies(pythonPath, "panda3d", "1.10.11" )
@@ -0,0 +1,57 @@
# MANUAL INSTALLATION:
# 1. Clone the default ArcGIS Pro conda environment
# for 2.9.0: Project-> Python-> Manage Environments-> Clone Default
# for 3.0.0: Project-> Package Manager-> Active Environment (Environment Manager)-> Clone arcgispro-py3
# 2. Change the path to your new environemnt Python.exe if necessary (in variable "pythonPath" below, line 13)
# 3. Enter the location of 'toolbox_install_manual.py' in the following command and run this command in ArcGIS Python console (View -> Python Window)
# import sysconfig; import subprocess; x = sysconfig.get_paths()['data'] + r"\python.exe"; subprocess.run((x, 'C:\\Users\\myusername\\Documents\\manual_toolbox_install.py'), capture_output=True, text=True, shell=True, timeout=1000 )
# 4. Restart ArcGIS Pro
from subprocess_call import subprocess_call
import os
pythonPath = os.getenv('APPDATA').replace("\\Roaming","") + r"\Local\ESRI\conda\envs\arcgispro-py3-speckle\python.exe"
def installToolbox(newExec: str):
print("Installing Speckle Toolbox")
whl_file = os.path.join(os.path.dirname(__file__), "speckle_toolbox-2.9.4-py3-none-any.whl" )
subprocess_call([newExec, '-m','pip','install','--upgrade', '--force-reinstall', whl_file])
return
def installDependencies(pythonExec: str, pkgName: str, pkgVersion: str):
# install package
try:
#import importlib #importlib.import_module(pkgName)
if pkgName == "specklepy": import specklepy
elif pkgName == "panda3d": import panda3d
except Exception as e:
print(f"{pkgName} not installed")
subprocess_call( [pythonExec, "-m", "pip", "install", f"{pkgName}=={pkgVersion}"])
# Check if package needs updating
try:
print(f"Attempting to update {pkgName} to {pkgVersion}")
result = subprocess_call(
[
pythonExec,
"-m",
"pip",
"install",
"--upgrade",
f"{pkgName}=={pkgVersion}",
]
)
if result == True:
print(f"{pkgName} upgraded")
return True
else:
return False
except Exception as e:
print(e)
print(e.with_traceback)
return True
installToolbox(pythonPath)
installDependencies(pythonPath, "specklepy", "2.9.0" )
installDependencies(pythonPath, "panda3d", "1.10.11" )
+1 -764
View File
@@ -1,764 +1 @@
#from speckle.speckle_arcgis import *
# -*- coding: utf-8 -*-
from typing import Any, Callable, List, Optional, Tuple
#r'''
from collections import defaultdict
import arcpy
#from arcpy import toolbox
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
from arcpy import metadata as md
from specklepy.api.models import Branch, Stream, Streams
from speckle.converter.layers.Layer import Layer, RasterLayer
from speckle.converter.layers._init_ import convertSelectedLayers, layerToNative, cadLayerToNative
from arcgis.features import FeatureLayer
import os
import os.path
import specklepy
from specklepy.transports.server.server import ServerTransport
from specklepy.api.credentials import get_local_accounts
from specklepy.api.client import SpeckleClient
from specklepy.api import operations
from specklepy.logging.exceptions import (
GraphQLException,
SpeckleException,
SpeckleWarning,
)
#from specklepy.api.credentials import StreamWrapper
from specklepy.api.wrapper import StreamWrapper
from specklepy.objects import Base
from specklepy.logging import metrics
from speckle.ui.project_vars import toolboxInputsClass, speckleInputsClass
from speckle.converter.layers.emptyLayerTemplates import createGroupLayer
from speckle.converter.layers.Layer import VectorLayer
#'''
def traverseObject(
base: Base,
callback: Optional[Callable[[Base], bool]],
check: Optional[Callable[[Base], bool]],
):
if check and check(base):
res = callback(base) if callback else False
if res:
return
memberNames = base.get_member_names()
for name in memberNames:
try:
if ["id", "applicationId", "units", "speckle_type"].index(name):
continue
except:
pass
traverseValue(base[name], callback, check)
def traverseValue(
value: Any,
callback: Optional[Callable[[Base], bool]],
check: Optional[Callable[[Base], bool]],
):
if isinstance(value, Base):
traverseObject(value, callback, check)
if isinstance(value, List):
for item in value:
traverseValue(item, callback, check)
class Toolbox:
def __init__(self):
"""Define the toolbox (the name of the toolbox is the name of the
.pyt file)."""
print("___ping_Toolbox")
self.label = "Speckle Tools"
self.alias = "speckle_toolbox_"
# List of tool classes associated with this toolbox
self.tools = [Speckle]
metrics.set_host_app("ArcGIS")
# https://pro.arcgis.com/en/pro-app/2.8/arcpy/mapping/alphabeticallistofclasses.htm#except: print("something happened")
class Speckle:
def __init__(self):
#print("________________reset_______________")
self.label = "Speckle"
self.description = "Allows you to send and receive your layers " + \
"to/from other software using Speckle server."
self.toRefresh = False
self.speckleInputs = None
self.toolboxInputs = None
#print("ping_Speckle1")
#print(speckleInputsClass.instances)
total = len(speckleInputsClass.instances)
#print(total)
for i in range(total):
#print(i)
#print(speckleInputsClass.instances[total-i-1])
if speckleInputsClass.instances[total-i-1] is not None:
try:
#print(speckleInputsClass.instances[total-i-1].streams_default)
y = speckleInputsClass.instances[total-i-1].streams_default # in case not initialized properly
self.speckleInputs = speckleInputsClass.instances[total-i-1] # take latest (first in reverted list)
#print("FOUND INSTANCE")
break
except: pass
#print(self.speckleInputs)
if self.speckleInputs is None: self.speckleInputs = speckleInputsClass()
#print(toolboxInputsClass.instances)
#print("TOTAL = ...................")
total = len(toolboxInputsClass.instances)
#print(total)
for i in range(total):
if toolboxInputsClass.instances[total-i-1] is not None:
self.toolboxInputs = toolboxInputsClass.instances[total-i-1] # take latest (first in reverted list)
#print("FOUND INSTANCE")
break
#print(self.toolboxInputs)
if self.toolboxInputs is None: self.toolboxInputs = toolboxInputsClass()
#print("ping_Speckle2")
def getParameterInfo(self):
#data types: https://pro.arcgis.com/en/pro-app/2.8/arcpy/geoprocessing_and_python/defining-parameter-data-types-in-a-python-toolbox.htm
# parameter details: https://pro.arcgis.com/en/pro-app/latest/arcpy/geoprocessing_and_python/customizing-tool-behavior-in-a-python-toolbox.htm
print("Get parameter values")
cat1 = "Add Streams"
cat2 = "Send/Receive"
cat3 = "Create custom Spatial Reference"
streamsDefalut = arcpy.Parameter(
displayName="Add stream from default account",
name="streamsDefalut",
datatype="GPString",
parameterType="Optional",
#category="Sending data",
direction="Input",
category=cat1
)
streamsDefalut.filter.type = 'ValueList'
streamsDefalut.filter.list = [ (st.name + " - " + st.id) for st in self.speckleInputs.streams_default ]
addDefStreams = arcpy.Parameter(
displayName="Add",
name="addDefStreams",
datatype="GPBoolean",
parameterType="Optional",
#category="Sending data",
direction="Input",
category=cat1
)
addDefStreams.value = False
streamUrl = arcpy.Parameter(
displayName="Add stream by URL",
name="streamUrl",
datatype="GPString",
parameterType="Optional",
direction="Input",
category=cat1
)
streamUrl.value = ""
addUrlStreams = arcpy.Parameter(
displayName="Add",
name="addUrlStreams",
datatype="GPBoolean",
parameterType="Optional",
direction="Input",
category=cat1
)
addUrlStreams.value = False
############################################################################
lat = arcpy.Parameter(
displayName="Origin point LAT",
name="lat",
datatype="GPString",
parameterType="Optional",
direction="Input",
category=cat3
)
lat.value = str(self.toolboxInputs.lat)
lon = arcpy.Parameter(
displayName="Origin point LON",
name="lon",
datatype="GPString",
parameterType="Optional",
direction="Input",
category=cat3
)
lon.value = str(self.toolboxInputs.lon)
setLatLon = arcpy.Parameter(
displayName="Create and apply",
name="setLatLon",
datatype="GPBoolean",
parameterType="Optional",
direction="Input",
category=cat3
)
setLatLon.value = False
####################################################################################################
savedStreams = arcpy.Parameter(
displayName="Select Stream",
name="savedStreams",
datatype="GPString",
parameterType="Required",
direction="Input",
multiValue=False,
#category=cat2
)
savedStreams.filter.list = [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}" for i,stream in enumerate(self.speckleInputs.saved_streams)]
removeStream = arcpy.Parameter(
displayName="Remove",
name="removeStream",
datatype="GPBoolean",
parameterType="Optional",
direction="Input"
)
removeStream.value = False
branch = arcpy.Parameter(
displayName="Branch",
name="branch",
datatype="GPString",
parameterType="Required",
#category="Sending data",
direction="Input",
#category=cat2
)
branch.value = ""
branch.filter.type = 'ValueList'
commit = arcpy.Parameter(
displayName="Commit",
name="commit",
datatype="GPString",
parameterType="Optional",
#category="Sending data",
direction="Input",
#category=cat2
)
commit.value = ""
commit.filter.type = 'ValueList'
msg = arcpy.Parameter(
displayName="Message",
name="message",
datatype="GPString",
parameterType="Optional",
direction="Input",
multiValue=False,
#category=cat2
)
msg.value = ""
selectedLayers = arcpy.Parameter(
displayName="Selected Layers",
name="selectedLayers",
datatype="GPString",
parameterType="Optional",
direction="Input",
multiValue=True,
#category=cat2
)
selectedLayers.filter.list = [str(i) + "-" + l.longName for i,l in enumerate(self.speckleInputs.all_layers)] #"Polyline"
action = arcpy.Parameter(
displayName="",
name="action",
datatype="GPString",
parameterType="Required",
#category="Sending data",
direction="Input",
multiValue=False,
#category=cat2
)
action.value = "Send"
#action.filter.type = 'ValueList'
action.filter.list = ["Send", "Receive"]
refresh = arcpy.Parameter(
displayName="Refresh",
name="refresh",
datatype="GPBoolean",
parameterType="Optional",
direction="Input"
)
#refresh.filter.type = "ValueList"
refresh.value = False
parameters = [streamsDefalut, addDefStreams, streamUrl, addUrlStreams, lat, lon, setLatLon, savedStreams, removeStream, branch, commit, selectedLayers, msg, action, refresh]
return parameters
def isLicensed(self): #optional
return True
def updateParameters(self, parameters: List, toRefresh = False): #optional
print("UPDATING PARAMETERS")
for i, par in enumerate(parameters):
if par.name == "addDefStreams" and par.altered and par.value == True:
for p in parameters:
if p.name == "streamsDefalut" and p.valueAsText is not None:
# add value from streamsDefault to saved streams
selected_stream_name = p.valueAsText[:]
#print(selected_stream_name)
for stream in self.speckleInputs.streams_default:
#print(stream)
if stream.name == selected_stream_name.split(" - ")[0]:
print("_____Add from list___")
wr = StreamWrapper(f"{self.speckleInputs.account.serverInfo.url}/streams/{stream.id}?u={self.speckleInputs.account.userInfo.id}")
self.toolboxInputs.setProjectStreams(wr)
for p_saved in parameters:
if p_saved.name == "savedStreams":
saved_streams = self.speckleInputs.getProjectStreams()
self.speckleInputs.saved_streams = saved_streams
p_saved.filter.list = [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}" for i,stream in enumerate(saved_streams)]
if len(p_saved.filter.list)>0: print(p_saved.filter.list); p_saved.value = p_saved.filter.list[0]
break
p.value = None
par.value = False
if par.name == "addUrlStreams" and par.altered and par.value == True:
for p in parameters:
if p.name == "streamUrl" and p.valueAsText is not None:
# add value from streamsDefault to saved streams
query = p.valueAsText[:]
if "http" in query and len(query.split("/")) >= 3: # URL
steamId = query
try: steamId = query.split("/streams/")[1].split("/")[0]
except: pass
# quesry stream, add to saved
stream = self.speckleInputs.speckle_client.stream.get(steamId)
if isinstance(stream, Stream):
print("_____Add by URL___")
wr = StreamWrapper(f"{self.speckleInputs.account.serverInfo.url}/streams/{stream.id}?u={self.speckleInputs.account.userInfo.id}")
self.toolboxInputs.setProjectStreams(wr)
for p_saved in parameters:
if p_saved.name == "savedStreams":
saved_streams = self.speckleInputs.getProjectStreams()
self.speckleInputs.saved_streams = saved_streams
p_saved.filter.list = [f"Stream not accessible - {st[0].stream_id}" if st[1] is None or isinstance(st[1], SpeckleException) else f"{st[1].name} - {st[1].id}" for i,st in enumerate(saved_streams)]
if len(p_saved.filter.list)>0: print(p_saved.filter.list); p_saved.value = p_saved.filter.list[0]
else: pass
p.value = None
break
par.value = False
if par.name == "removeStream" and par.altered and par.value == True:
for p in parameters:
if p.name == "savedStreams" and p.valueAsText is not None:
# get value from savedStreams
selected_stream_name = p.valueAsText[:]
#print(selected_stream_name)
for streamTup in self.speckleInputs.saved_streams:
#print(stream)
stream = streamTup[1]
if stream.name == selected_stream_name.split(" - ")[0]:
print("_____Remove stream___")
wr = StreamWrapper(f"{self.speckleInputs.account.serverInfo.url}/streams/{stream.id}?u={self.speckleInputs.account.userInfo.id}")
self.toolboxInputs.setProjectStreams(wr, False)
for p_saved in parameters:
if p_saved.name == "savedStreams":
saved_streams = self.speckleInputs.getProjectStreams()
self.speckleInputs.saved_streams = saved_streams
p_saved.filter.list = [f"Stream not accessible - {st[0].stream_id}" if st[1] is None or isinstance(st[1], SpeckleException) else f"{st[1].name} - {st[1].id}" for i,st in enumerate(saved_streams)]
p_saved.value = None
break
p.value = None
par.value = False
#######################################################################
if par.name == "setLatLon" and par.altered and par.value == True:
lat = lon = 0
for p in parameters:
if p.name == "lat" and p.valueAsText is not None:
# add value from the UI to saved lat
lat = p.valueAsText[:].replace(",","").replace(" ","").replace(";","").replace("-","").replace("_","")
try: lat = float(lat)
except: lat = 0; p.value = "0.0"
if p.name == "lon" and p.valueAsText is not None:
# add value from the UI to saved lat
lon = p.valueAsText[:].replace(",","").replace(" ","").replace(";","").replace("-","").replace("_","")
try: lon = float(lon)
except: lon = 0; p.value = "0.0"
coords = [lat, lon]
self.toolboxInputs.set_survey_point(coords)
par.value = False
#######################################################################
if par.name == "savedStreams" and par.altered:
# Search for the stream by name
if par.value is not None and "Stream not accessible" not in par.valueAsText[:]:
#print("SAVED STREAMS - selection")
selected_stream_name = par.valueAsText[:]
self.toolboxInputs.active_stream = None
for st in self.speckleInputs.saved_streams:
if st[1].name == selected_stream_name.split(" - ")[0]:
self.toolboxInputs.active_stream = st[1]
break
# edit branches: globals and UI
branch_list = [branch.name for branch in self.toolboxInputs.active_stream.branches.items]
for p in parameters:
if p.name == "branch":
p.filter.list = branch_list
if p.valueAsText not in branch_list:
p.value = "main"
for b in self.toolboxInputs.active_stream.branches.items:
if b.name == p.value:
self.toolboxInputs.active_branch = b
break
# setting commit value and list
for p in parameters:
if p.name == "commit":
try:
p.filter.list = [f"{commit.id}"+ " - " + f"{commit.message}" for commit in self.toolboxInputs.active_branch.commits.items]
if p.valueAsText not in p.filter.list:
p.value = self.toolboxInputs.active_branch.commits.items[0].id + " - " + self.toolboxInputs.active_branch.commits.items[0].message
self.toolboxInputs.active_commit = self.toolboxInputs.active_branch.commits.items[0]
except:
p.filter.list = []
p.value = None
self.toolboxInputs.active_commit = None
else: par.value = None
#print(self.toolboxInputs.action)
if par.name == "branch" and par.altered: # branches
if par.value is not None:
selected_branch_name = par.valueAsText[:]
self.toolboxInputs.active_branch = None
if self.toolboxInputs.active_stream is not None:
for br in self.toolboxInputs.active_stream.branches.items:
if br.name == selected_branch_name:
self.toolboxInputs.active_branch = br
break
# edit commit values
if self.toolboxInputs.active_branch is not None:
for p in parameters:
if p.name == "commit":
try:
p.filter.list = [f"{commit.id}"+ " - " + f"{commit.message}" for commit in self.toolboxInputs.active_branch.commits.items]
if p.valueAsText not in p.filter.list:
p.value = self.toolboxInputs.active_branch.commits.items[0].id + " - " + self.toolboxInputs.active_branch.commits.items[0].message
self.toolboxInputs.active_commit = self.toolboxInputs.active_branch.commits.items[0]
except:
p.filter.list = []
p.value = None
self.toolboxInputs.active_commit = None
if par.name == "commit" and par.altered: # commits
if par.value is not None:
selected_commit_id = par.valueAsText[:].split(" - ")[0]
self.toolboxInputs.active_commit = None
if self.toolboxInputs.active_branch is not None:
for c in self.toolboxInputs.active_branch.commits.items:
if c.id == selected_commit_id:
self.toolboxInputs.active_commit = c
break
if par.name == "selectedLayers" and par.altered: # selected layers
if par.value is not None:
self.toolboxInputs.selected_layers = par.values
#print("selected layers changed")
#print(self.toolboxInputs.action)
#print(self.toolboxInputs.selected_layers)
if par.name == "msg" and par.altered and par.valueAsText is not None:
self.toolboxInputs.messageSpeckle = par.valueAsText
if par.name == "action" and par.altered:
#print("action changed")
#print(par.valueAsText)
if par.valueAsText == "Send": self.toolboxInputs.action = 1
else: self.toolboxInputs.action = 0
#print(self.toolboxInputs.action)
#print(self.toolboxInputs.selected_layers)
if par.name == "refresh" and par.altered: # refresh btn
if par.value == True:
self.refresh(parameters)
if self.toRefresh == True:
self.refresh(parameters)
self.toRefresh = False
print("____________________________parameters___________________________")
#[print(str(x.name) + " - " + str(x.valueAsText)) for x in parameters]
#[x.clearMessage() for x in parameters] # https://pro.arcgis.com/en/pro-app/latest/arcpy/geoprocessing_and_python/programming-a-toolvalidator-class.htm
#[print(x.valueAsText) for x in parameters]
return
def refresh(self, parameters: List[Any]):
print("Refresh______")
self.speckleInputs: speckleInputsClass = speckleInputsClass()
self.toolboxInputs: toolboxInputsClass = toolboxInputsClass()
for par in parameters:
if par.name == "streamUrl": par.value = None
if par.name == "streamsDefalut": par.value = None
if par.name == "savedStreams": par.value = None
if par.name == "branch": par.value = ""
if par.name == "commit": par.value = None
if par.name == "selectedLayers": par.value = None
if par.name == "msg": par.value = ""
if par.name == "action": par.value = "Send"
if par.name == "refresh": par.value = False
if par.name == "lat": par.value = str(self.toolboxInputs.get_survey_point()[0])
if par.name == "lon": par.value = str(self.toolboxInputs.get_survey_point()[1])
if par.name == "streamsDefalut": par.filter.list = [ (st.name + " - " + st.id) for st in self.speckleInputs.streams_default ]
if par.name == "savedStreams":
#print("par.name")
saved_streams = self.speckleInputs.getProjectStreams()
#print(saved_streams)
par.filter.list = [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}" for i,stream in enumerate(saved_streams)]
if par.name == "selectedLayers": par.filter.list = [str(i) + "-" + l.longName for i,l in enumerate(self.speckleInputs.all_layers)]
return parameters
def updateMessages(self, parameters): #optional
return
def execute(self, parameters: List, messages):
# https://pro.arcgis.com/en/pro-app/latest/arcpy/get-started/what-is-arcpy-.htm
#Warning if any of the fields is invalid/empty
print("___________________________Run___________________________")
check = self.validateStreamBranch(parameters) # apparently pdate needed to assign proper self.values
print(self.toolboxInputs.selected_layers)
print(self.toolboxInputs.action)
if self.toolboxInputs.action == 1 and check is True: self.onSend(parameters)
elif self.toolboxInputs.action == 0 and check is True: self.onReceive(parameters)
print("__________________________Run_end___________________________")
def validateStreamBranch(self, parameters: List):
self.updateParameters(parameters)
if self.toolboxInputs.active_stream is None:
arcpy.AddError("Choose a valid stream")
return False
if self.toolboxInputs.active_branch is None:
arcpy.AddError("Choose a valid branch")
return False
return True
def onSend(self, parameters: List):
print("______________SEND_______________")
#if self.validateStreamBranch(parameters) == False: return
if len(self.toolboxInputs.selected_layers) == 0:
arcpy.AddError("No layers selected for sending")
return
streamId = self.toolboxInputs.active_stream.id #stream_id
client = self.speckleInputs.speckle_client # ?
# Get the stream wrapper
#streamWrapper = StreamWrapper(None)
#client = streamWrapper.get_client()
# Ensure the stream actually exists
#try:
# client.stream.get(streamId)
#except SpeckleException as error:
# print(str(error))
# return
# next create a server transport - this is the vehicle through which you will send and receive
transport = ServerTransport(client=client, stream_id=streamId)
##################################### conversions ################################################
base_obj = Base(units = "m")
base_obj.layers = convertSelectedLayers(self.speckleInputs.all_layers, self.toolboxInputs.selected_layers, self.speckleInputs.project)
try:
# this serialises the block and sends it to the transport
objId = operations.send(base=base_obj, transports=[transport])
except SpeckleException as error:
arcpy.AddError("Error sending data")
#print("Error sending data")
return
except SpeckleWarning as warning:
arcpy.AddMessage("SpeckleWarning: " + str(warning.args[0]))
message = self.toolboxInputs.messageSpeckle
if message is None or ( isinstance(message, str) and len(message) == 0): message = "Sent from ArcGIS"
try:
# you can now create a commit on your stream with this object
client.commit.create(
stream_id=streamId,
object_id=objId,
branch_name=self.toolboxInputs.active_branch.name,
message=message,
source_application="ArcGIS",
)
arcpy.AddMessage("Successfully sent data to stream: " + streamId)
except:
arcpy.AddError("Error creating commit")
def onReceive(self, parameters: List[Any]):
print("______________RECEIVE_______________")
#if self.validateStreamBranch(parameters) == False: return
try:
streamId = self.toolboxInputs.active_stream.id #stream_id
client = self.speckleInputs.speckle_client #
except SpeckleWarning as warning:
arcpy.AddWarning(str(warning.args[0]))
# get commit
commit = None
try:
#commit = self.toolboxInputs.active_branch.commits.items[0]
commit = self.toolboxInputs.active_commit
commitId = commit.id # text to make sure commit exists
except:
try:
commit = self.toolboxInputs.active_branch.commits.items[0]
commitId = commit.id
arcpy.AddWarning("Failed to find a commit. Getting the last commit of the branch")
except:
arcpy.AddError("Failed to find a commit")
return
# next create a server transport - this is the vehicle through which you will send and receive
try:
transport = ServerTransport(client=client, stream_id=streamId)
client.commit.received(
streamId,
commit.id,
source_application="ArcGIS",
message="Received commit in ArcGIS",
)
except:
arcpy.AddError("Make sure your account has access to the chosen stream")
return
try:
#print(commit)
objId = commit.referencedObject
commitDetailed = client.commit.get(streamId, commit.id)
app = commitDetailed.sourceApplication
if objId is None:
return
commitObj = operations.receive(objId, transport, None)
if app != "QGIS" and app != "ArcGIS":
if self.speckleInputs.project.activeMap.spatialReference.type == "Geographic" or self.speckleInputs.project.activeMap.spatialReference is None: #TODO test with invalid CRS
arcpy.AddMessage("It is advisable to set the project Spatial reference to Projected type before receiving CAD geometry (e.g. EPSG:32631), or create a custom one from geographic coordinates")
print("It is advisable to set the project Spatial reference to Projected type before receiving CAD geometry (e.g. EPSG:32631), or create a custom one from geographic coordinates")
print(f"Succesfully received {objId}")
# Clear 'latest' group
streamBranch = streamId + "_" + self.toolboxInputs.active_branch.name + "_" + str(commit.id)
newGroupName = f'{streamBranch}'
groupExists = 0
#print(newGroupName)
for l in self.speckleInputs.project.activeMap.listLayers():
#print(l.longName)
if l.longName.startswith(newGroupName + "\\"):
#print(l.longName)
self.speckleInputs.project.activeMap.removeLayer(l)
groupExists+=1
elif l.longName == newGroupName:
groupExists+=1
if groupExists == 0:
# create empty group layer file
path = self.speckleInputs.project.filePath.replace("aprx","gdb") #"\\".join(self.toolboxInputs.project.filePath.split("\\")[:-1]) + "\\speckle_layers\\"
#print(path)
f = open(path + "\\" + newGroupName + ".lyrx", "w")
content = createGroupLayer().replace("TestGroupLayer", newGroupName)
f.write(content)
f.close()
smth = arcpy.mp.LayerFile(path + "\\" + newGroupName + ".lyrx")
#print(smth)
layerGroup = self.speckleInputs.project.activeMap.addLayer(smth)[0]
layerGroup.name = newGroupName
if app == "QGIS" or app == "ArcGIS": check: Callable[[Base], bool] = lambda base: isinstance(base, Layer) or isinstance(base, VectorLayer) or isinstance(base, RasterLayer)
else: check: Callable[[Base], bool] = lambda base: isinstance(base, Base)
def callback(base: Base) -> bool:
print("callback")
#print(base)
if isinstance(base, Layer) or isinstance(base, VectorLayer) or isinstance(base, RasterLayer):
layer = layerToNative(base, streamBranch, self.speckleInputs.project)
if layer is not None:
print("Layer created: " + layer.name)
else:
loopObj(base, "")
return True
def loopObj(base: Base, baseName: str):
memberNames = base.get_member_names()
for name in memberNames:
if name in ["id", "applicationId", "units", "speckle_type"]: continue
try: loopVal(base[name], baseName + "/" + name) # loop properties not included above
except: pass
def loopVal(value: Any, name: str): # "name" is the parent object/property/layer name
if isinstance(value, Base):
try: # dont go through parts of Speckle Geometry object
print("objects to loop through: " + value.speckle_type)
if value.speckle_type.startswith("Objects.Geometry."): pass #.Brep") or value.speckle_type.startswith("Objects.Geometry.Mesh") or value.speckle_type.startswith("Objects.Geometry.Surface") or value.speckle_type.startswith("Objects.Geometry.Extrusion"): pass
else: loopObj(value, name)
except: loopObj(value, name)
if isinstance(value, List):
for item in value:
loopVal(item, name)
#print(item)
if item.speckle_type and item.speckle_type.startswith("Objects.Geometry."):
pt, pl = cadLayerToNative(value, name, streamBranch, self.speckleInputs.project)
if pt is not None: print("Layer group created: " + pt.name())
if pl is not None: print("Layer group created: " + pl.name())
break
traverseObject(commitObj, callback, check)
except SpeckleException as e:
print("Receive failed")
return
print("received")
#self.updateParameters(parameters, True)
#self.refresh(parameters)
#__all__ = ["Toolbox", "Speckle"]
from speckle.speckle_arcgis import *
@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<metadata xml:lang="en"><Esri><CreaDate>20220718</CreaDate><CreaTime>13500100</CreaTime><ArcGISFormat>1.0</ArcGISFormat><SyncOnce>TRUE</SyncOnce><ModDate>20221025</ModDate><ModTime>190532</ModTime><scaleRange><minScale>150000000</minScale><maxScale>5000</maxScale></scaleRange><ArcGISProfile>ItemDescription</ArcGISProfile></Esri><toolbox name="Speckle" alias="speckle_toolbox_"><arcToolboxHelpPath>c:\program files\arcgis\pro\Resources\Help\gp</arcToolboxHelpPath><toolsets/></toolbox><dataIdInfo><idCitation><resTitle>Speckle</resTitle></idCitation><idPurp>Speckle connector for ArcGIS</idPurp><searchKeys><keyword>speckle3d</keyword></searchKeys></dataIdInfo><distInfo><distributor><distorFormat><formatName>ArcToolbox Toolbox</formatName></distorFormat></distributor></distInfo><mdHrLv><ScopeCd value="005"></ScopeCd></mdHrLv><Binary><Thumbnail><Data EsriPropertyType="PictureX">/9j/4AAQSkZJRgABAQEAYABgAAD/4gxYSUNDX1BST0ZJTEUAAQEAAAxITGlubwIQAABtbnRyUkdC
<metadata xml:lang="en"><Esri><CreaDate>20220718</CreaDate><CreaTime>13500100</CreaTime><ArcGISFormat>1.0</ArcGISFormat><SyncOnce>TRUE</SyncOnce><ModDate>20221031</ModDate><ModTime>212303</ModTime><scaleRange><minScale>150000000</minScale><maxScale>5000</maxScale></scaleRange><ArcGISProfile>ItemDescription</ArcGISProfile></Esri><toolbox name="Speckle" alias="speckle_toolbox_"><arcToolboxHelpPath>c:\program files\arcgis\pro\Resources\Help\gp</arcToolboxHelpPath><toolsets/></toolbox><dataIdInfo><idCitation><resTitle>Speckle</resTitle></idCitation><idPurp>Speckle connector for ArcGIS</idPurp><searchKeys><keyword>speckle3d</keyword></searchKeys></dataIdInfo><distInfo><distributor><distorFormat><formatName>ArcToolbox Toolbox</formatName></distorFormat></distributor></distInfo><mdHrLv><ScopeCd value="005"></ScopeCd></mdHrLv><Binary><Thumbnail><Data EsriPropertyType="PictureX">/9j/4AAQSkZJRgABAQEAYABgAAD/4gxYSUNDX1BST0ZJTEUAAQEAAAxITGlubwIQAABtbnRyUkdC
IFhZWiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAA
AADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFj
cHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAACGAAA
@@ -9,6 +9,7 @@ from speckle.converter.geometry.polygon import polygonToNative, polygonToSpeckle
from speckle.converter.geometry.polyline import arcToNative, ellipseToNative, circleToNative, curveToNative, lineToNative, polycurveToNative, polylineFromVerticesToSpeckle, polylineToNative, polylineToSpeckle
from speckle.converter.geometry.point import pointToCoord, pointToNative, pointToSpeckle, multiPointToSpeckle
from speckle.converter.geometry.polyline import speckleArcCircleToPoints, specklePolycurveToPoints, multiPolylineToSpeckle
from speckle.converter.geometry.mesh import meshToNative
import numpy as np
def convertToSpeckle(feature, layer, geomType, featureType) -> Union[Base, Sequence[Base], None]:
@@ -1,10 +1,58 @@
from typing import List
import arcpy
from specklepy.objects.geometry import Mesh
import shapefile
from shapefile import TRIANGLE_STRIP, TRIANGLE_FAN
from speckle.converter.layers.utils import get_scale_factor
def meshToNative(mesh: Mesh):
"""Converts a Speckle Mesh to QgsGeometry. Currently UNSUPPORTED"""
return None
def meshToNative(meshes: List[Mesh], path: str):
"""Converts a Speckle Mesh to MultiPatch"""
print("06___________________Mesh to Native")
#print(meshes)
#print(mesh.units)
w = shapefile.Writer(path)
w.field('speckleTyp', 'C')
shapes = []
for mesh_full in meshes:
#print(mesh_full)
#print(mesh_full.get_dynamic_member_names())
mesh = mesh_full.displayMesh
#print(mesh)
w = fill_mesh_parts(w, mesh)
w.close()
return path
def fill_mesh_parts(w: shapefile.Writer, mesh: Mesh):
scale = get_scale_factor(mesh.units)
parts_list = []
types_list = []
count = 0 # sequence of vertex (not of flat coord list)
try:
#print(len(mesh.faces))
if len(mesh.faces) % 4 == 0 and mesh.faces[0] == 0:
for f in mesh.faces:
try:
if mesh.faces[count] == 0 or mesh.faces[count] == 3: # only handle triangles
f1 = [ scale*mesh.vertices[mesh.faces[count+1]*3], scale*mesh.vertices[mesh.faces[count+1]*3+1], scale*mesh.vertices[mesh.faces[count+1]*3+2] ]
f2 = [ scale*mesh.vertices[mesh.faces[(count+2)]*3], scale*mesh.vertices[mesh.faces[(count+2)]*3+1], scale*mesh.vertices[mesh.faces[(count+2)]*3+2] ]
f3 = [ scale*mesh.vertices[mesh.faces[(count+3)]*3], scale*mesh.vertices[mesh.faces[(count+3)]*3+1], scale*mesh.vertices[mesh.faces[(count+3)]*3+2] ]
parts_list.append([ f1, f2, f3 ])
types_list.append(TRIANGLE_FAN)
count += 4
else:
count += mesh.faces[count+1]
except: break
w.multipatch(parts_list, partTypes=types_list ) # one type for each part
w.record('displayMesh')
else: print("not triangulated mesh")
except Exception as e: pass #; print(e)
return w
def rasterToMesh(vertices, faces, colors):
mesh = Mesh.create(vertices, faces, colors)
mesh.units = "m"
@@ -55,19 +55,20 @@ def pointToNative(pt: Point, sr: arcpy.SpatialReference) -> arcpy.PointGeometry:
#print(geom)
return geom
def pointToCoord(pt: Point) -> List[float]:
def pointToCoord(point: Point) -> List[float]:
"""Converts a Speckle Point to QgsPoint"""
pt = scalePointToNative(pt, pt.units)
pt = scalePointToNative(point, point.units)
coords = [pt.x, pt.y, pt.z]
#print(coords)
return coords
def scalePointToNative(pt: Point, units: str) -> Point:
def scalePointToNative(point: Point, units: str) -> Point:
"""Scale point coordinates to meters"""
scaleFactor = get_scale_factor(units)
pt.x = pt.x * scaleFactor
pt.y = pt.y * scaleFactor
pt.z = 0 if math.isnan(pt.z) else pt.z * scaleFactor
pt = Point(units = "m")
pt.x = point.x * scaleFactor
pt.y = point.y * scaleFactor
pt.z = 0 if math.isnan(point.z) else point.z * scaleFactor
return pt
def addZtoPoint(coords: List):
@@ -26,6 +26,7 @@ def circleToSpeckle(center, point):
#print(args)
c = Circle.from_list(args)
c.plane.origin.units = "m"
c.units = "m"
#print(c)
return c
@@ -107,6 +108,7 @@ def arc3ptToSpeckle(p0: List, p1: List, p2: List, feature, layer) -> Arc:
arc.plane = Plane() #.from_list(Point(), Vector(Point(0, 0, 1)), Vector(Point(0,1,0)), Vector(Point(-1,0,0)))
arc.plane.origin = Point.from_list(center)
arc.plane.origin.units = "m"
arc.units = "m"
arc.angleRadians, startAngle, endAngle = getArcRadianAngle(arc)
arc.radius = radius
@@ -173,7 +175,7 @@ def curveToSpeckle(geom, geomType, feature, layer) -> Union[Circle, Arc, Polylin
# a - elliptical arc (endPt, centralPt) e.g. for circle: [[[631307.05960000027,5803698.4477999993,0],{"a":[[631307.05960000027,5803698.4477999993,0],[631307.05960000027,5803414.92656173],0,1]}]]
# c - circular arc (endPt, throughPt) e.g. [[[633242.45179999992,5803058.0354999993,0],{"c":[[633718.26040000003,5803496.4210000001,0],[633337.75764975848,5803431.9997026781]]},[633242.45179999992,5803058.0354999993,0]]]
boundary = Polycurve()
boundary = Polycurve(units = "m")
if geomType == "Polyline": boundary.closed = False
else: boundary.closed = True
segments = []
@@ -326,7 +328,7 @@ def lineFrom2pt(pt1: List[float], pt2: List[float]):
dist = math.sqrt( math.pow((pt2[0] - pt1[0]), 2) + math.pow((pt2[1] - pt1[1]), 2) + math.pow((pt2[2] - pt1[2]), 2) )
print(dist)
domain = [0, dist, 0, 0]
line = Line()#.from_list([*pt1, *pt2, *domain])
line = Line(units = "m" )#.from_list([*pt1, *pt2, *domain])
line.start = Point.from_list(pt1)
line.end = Point.from_list(pt2)
line.start.units = line.end.units = "m"
@@ -377,7 +379,7 @@ def ellipseToNative():
def circleToNative(poly: Circle, sr: arcpy.SpatialReference) -> arcpy.Polyline:
"""Converts a Speckle Circle to QgsLineString"""
print("___Convert Circle from Native___")
print("___Convert Circle to Native___")
points = []
angle1 = math.pi/2
@@ -393,7 +395,8 @@ def circleToNative(poly: Circle, sr: arcpy.SpatialReference) -> arcpy.Polyline:
if poly.plane.normal.z == 0: normal = 1
else: normal = poly.plane.normal.z
angle = angle1 + k * math.pi*2 * normal
pt = Point( x = poly.plane.origin.x + radScaled * cos(angle), y = poly.plane.origin.y + radScaled * sin(angle), z = 0)
pt = Point( x = poly.plane.origin.x * get_scale_factor(poly.units) + radScaled * cos(angle), y = poly.plane.origin.y * get_scale_factor(poly.units) + radScaled * sin(angle), z = 0)
print(pt)
pt.units = "m"
points.append(pointToCoord(pt))
points.append(points[0])
@@ -404,10 +407,10 @@ def polycurveToNative(poly: Polycurve, sr: arcpy.SpatialReference) -> arcpy.Poly
points = []
curve = None
print("___Polycurve to native___")
try:
for segm in poly.segments: # Line, Polyline, Curve, Arc, Circle
#print(segm)
try:
for i, segm in enumerate(poly.segments): # Line, Polyline, Curve, Arc, Circle
print("___start segment")
if isinstance(segm,Line): converted = lineToNative(segm, sr) # QgsLineString
elif isinstance(segm,Polyline): converted = polylineToNative(segm, sr) # QgsLineString
elif isinstance(segm,Curve): converted = curveToNative(segm, sr) # QgsLineString
@@ -420,6 +423,7 @@ def polycurveToNative(poly: Polycurve, sr: arcpy.SpatialReference) -> arcpy.Poly
if converted is not None:
#print(converted) # <geoprocessing describe geometry object object at 0x000002B2D3E338D0>
for part in converted:
#print("Part: ")
#print(part) # <geoprocessing array object object at 0x000002B2D2E09530>
for pt in part:
#print(pt) # 64.4584221540162 5.5 NaN NaN
@@ -428,8 +432,7 @@ def polycurveToNative(poly: Polycurve, sr: arcpy.SpatialReference) -> arcpy.Poly
#print(pt_z)
#print(len(points))
if len(points)>0 and pt.X == points[len(points)-1][0] and pt.Y == points[len(points)-1][1] and pt_z == points[len(points)-1][2]: pass
else: points.append(pointToCoord(Point(x=pt.X, y = pt.Y, z = pt_z)))
#print(points)
else: points.append(pointToCoord(Point(x=pt.X, y = pt.Y, z = pt_z, units = "m"))) # e.g. [[64.4584221540162, 5.499999999999999, 0.0], [64.45461685210796, 5.587155742747657, 0.0]]
else:
arcpy.AddWarning(f"Part of the polycurve cannot be converted")
curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True )
@@ -441,7 +444,7 @@ def polycurveToNative(poly: Polycurve, sr: arcpy.SpatialReference) -> arcpy.Poly
return curve
def arcToNativePolyline(poly: Union[Arc, Circle], sr: arcpy.SpatialReference):
print("__Arc/Circle to native__")
print("__Arc/Circle to native polyline__")
pointsSpeckle = speckleArcCircleToPoints(poly)
points = [pointToCoord(p) for p in pointsSpeckle]
curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True )
@@ -454,6 +457,7 @@ def specklePolycurveToPoints(poly: Polycurve) -> List[Point]:
points = []
for segm in poly.segments:
print(segm)
pts = []
if isinstance(segm, Arc) or isinstance(segm, Circle): # or isinstance(segm, Curve):
print("Arc or Curve")
pts: List[Point] = speckleArcCircleToPoints(segm)
@@ -470,8 +474,8 @@ def specklePolycurveToPoints(poly: Polycurve) -> List[Point]:
def speckleArcCircleToPoints(poly: Union[Arc, Circle]) -> List[Point]:
print("__Arc or Circle to Points___")
points = []
print(poly.plane)
print(poly.plane.normal)
#print(poly.plane)
#print(poly.plane.normal)
if poly.plane is None or poly.plane.normal.z == 0: normal = 1
else: normal = poly.plane.normal.z
#print(poly.plane.origin)
@@ -511,7 +515,6 @@ def speckleArcCircleToPoints(poly: Union[Arc, Circle]) -> List[Point]:
pt.units = poly.plane.origin.units
points.append(pt)
if isinstance(poly, Arc): points.append(poly.endPoint)
return points
@@ -7,8 +7,10 @@ from typing import Any, List, Tuple, Union
from regex import D
from speckle.converter.layers.CRS import CRS
from speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer
from speckle.converter.layers.feature import featureToNative, featureToSpeckle, cadFeatureToNative, rasterFeatureToSpeckle
from speckle.converter.layers.feature import featureToNative, featureToSpeckle, cadFeatureToNative, bimFeatureToNative, rasterFeatureToSpeckle
from specklepy.objects import Base
from specklepy.objects.geometry import Mesh
from speckle.converter.geometry.mesh import rasterToMesh, meshToNative
import arcgisscripting
import pandas as pd
@@ -38,8 +40,8 @@ def convertSelectedLayers(all_layers: List[arcLayer], selected_layers: List[str]
#if layerToSend.isFeatureLayer:
newBaseLayer = layerToSpeckle(layerToSend, project)
if newBaseLayer is not None: result.append(newBaseLayer)
elif layerToSend.isRasterLayer: pass
print(result)
return result
@@ -121,6 +123,8 @@ def layerToSpeckle(layer: arcLayer, project: ArcGISProject) -> Union[VectorLayer
speckleLayer.features=layerObjs
speckleLayer.geomType = data.shapeType
if len(speckleLayer.features) == 0: return None
#layerBase.renderer = layerRenderer
#layerBase.applicationId = selectedLayer.id()
@@ -160,7 +164,184 @@ def layerToNative(layer: Union[Layer, VectorLayer, RasterLayer], streamBranch: s
return rasterLayerToNative(layer, streamBranch, project)
return None
def cadLayerToNative(layerContentList:Base, layerName: str, streamBranch: str, project: ArcGISProject) :
def bimLayerToNative(layerContentList: List[Base], layerName: str, streamBranch: str, project: ArcGISProject) :
print("01______BIM layer to native")
print(layerName)
geom_meshes = []
layer_meshes = None
#filter speckle objects by type within each layer, create sub-layer for each type (points, lines, polygons, mesh?)
for geom in layerContentList:
#print(geom)
if geom.displayMesh and isinstance(geom.displayMesh, Mesh):
geom_meshes.append(geom)
if len(geom_meshes)>0: layer_meshes = bimVectorLayerToNative(geom_meshes, layerName, "Mesh", streamBranch, project)
return True
def bimVectorLayerToNative(geomList, layerName: str, geomType: str, streamBranch: str, project: ArcGISProject):
# no support for mltipatches, maybe in 3.1: https://community.esri.com/t5/arcgis-pro-ideas/better-support-for-multipatches-in-arcpy/idi-p/953614/page/2#comments
print("02_________BIM vector layer to native_____")
#get Project CRS, use it by default for the new received layer
vl = None
layerName = layerName.replace("[","_").replace("]","_").replace(" ","_").replace("-","_").replace("(","_").replace(")","_").replace(":","_").replace("\\","_").replace("/","_").replace("\"","_").replace("&","_").replace("@","_").replace("$","_").replace("%","_").replace("^","_")
layerName = layerName + "_" + geomType
#if not "__Structural_Foundations_Mesh" in layerName: return None
sr = arcpy.SpatialReference(text = project.activeMap.spatialReference.exportToString())
active_map = project.activeMap
path = project.filePath.replace("aprx","gdb") #
path_bim = "\\".join(project.filePath.split("\\")[:-1]) + "\\BIM_layers_speckle\\" + streamBranch+ "\\" + layerName + "\\" #arcpy.env.workspace + "\\" #
print(path_bim)
if not os.path.exists(path_bim): os.makedirs(path_bim)
print(path)
if sr.type == "Geographic":
arcpy.AddMessage(f"Project CRS is set to Geographic type, and objects in linear units might not be received correctly")
#CREATE A GROUP "received blabla" with sublayers
layerGroup = None
newGroupName = f'{streamBranch}'
#print(newGroupName)
for l in active_map.listLayers():
if l.longName == newGroupName: layerGroup = l; break
#find ID of the layer with a matching name in the "latest" group
newName = f'{streamBranch.split("_")[len(streamBranch.split("_"))-1]}_{layerName}'
print(newName)
all_layer_names = []
layerExists = 0
for l in project.activeMap.listLayers():
if l.longName.startswith(newGroupName + "\\"):
all_layer_names.append(l.longName)
#print(all_layer_names)
longName = streamBranch + "\\" + newName
if longName in all_layer_names:
for index, letter in enumerate('234567890abcdefghijklmnopqrstuvwxyz'):
if (longName + "_" + letter) not in all_layer_names: newName += "_"+letter; layerExists +=1; break
# particularly if the layer comes from ArcGIS
if "mesh" in geomType.lower(): geomType = "Multipatch"
#print("Create feature class (cad): ")
# should be created inside the workspace to be a proper Feature class (not .shp) with Nullable Fields
class_name = ("f_class_" + newName)
#f_class = CreateFeatureclass(path, class_name, geomType, has_z="ENABLED", spatial_reference = sr)
shp = meshToNative(geomList, path_bim + newName)
print("____ meshes saved___")
print(shp)
#print(path)
#print(class_name)
validated_class_path = validate_path(class_name)
print(validated_class_path)
validated_class_name = validated_class_path.split("\\")[len(validated_class_path.split("\\"))-1]
print(validated_class_name)
f_class = arcpy.conversion.FeatureClassToFeatureClass(shp, path, validated_class_name)
# , spatial_reference = sr
#arcpy.management.Project(in_dataset, f_class, sr, in_coor_system=sr)
print(f_class)
#print(geomList)
# get and set Layer attribute fields
# example: https://resource.esriuk.com/blog/an-introductory-slice-of-arcpy-in-arcgis-pro/
newFields = getLayerAttributes(geomList)
fields_to_ignore = ["arcgisgeomfromspeckle", "shape", "objectid", "displayMesh"]
matrix = []
all_keys = []
all_key_types = []
max_len = 52
print("___ after layer attributes: ___________")
print(newFields.items())
#try:
for key, value in newFields.items():
existingFields = [fl.name for fl in arcpy.ListFields(validated_class_name)]
#print(existingFields)
if key not in existingFields and key.lower() not in fields_to_ignore: # exclude geometry and default existing fields
#print(key)
# signs that should not be used as field names and table names: https://support.esri.com/en/technical-article/000005588
key = key.replace(" ","_").replace("-","_").replace("(","_").replace(")","_").replace(":","_").replace("\\","_").replace("/","_").replace("\"","_").replace("&","_").replace("@","_").replace("$","_").replace("%","_").replace("^","_")
if key[0] in ['0','1','2','3','4','5','6','7','8','9']: key = "_"+key
if len(key)>max_len: key = key[:max_len]
#print(all_keys)
if key in all_keys:
for index, letter in enumerate('1234567890abcdefghijklmnopqrstuvwxyz'):
if len(key)<max_len and (key+letter) not in all_keys: key+=letter; break
if len(key) == max_len and (key[:9] + letter) not in all_keys: key=key[:9] + letter; break
if key not in all_keys:
all_keys.append(key)
all_key_types.append(value)
#print(all_keys)
matrix.append([key, value, key, 255])
print(all_keys)
print(len(all_keys))
if len(matrix)>0: AddFields(str(f_class), matrix)
fets = []
print("_________BIM FeatureS To Native___________")
for f in geomList[:]:
new_feat = bimFeatureToNative(f, newFields, sr, path_bim)
if new_feat != "" and new_feat != None:
fets.append(new_feat)
print(len(fets))
if len(fets) == 0: return None
count = 0
rowValues = []
for i, feat in enumerate(fets):
row = []
heads = []
for key in all_keys:
try:
row.append(feat[key])
heads.append(key)
except Exception as e:
row.append(None)
heads.append(key)
rowValues.append(row)
count += 1
with arcpy.da.UpdateCursor(f_class, heads) as cur:
# For each row, evaluate the WELL_YIELD value (index position
# of 0), and update WELL_CLASS (index position of 1)
shp_num = 0
try:
for rowShape in cur:
for i,r in enumerate(rowShape):
rowShape[i] = rowValues[shp_num][i]
if isinstance(rowValues[shp_num][i], str): rowShape[i] = rowValues[shp_num][i][:255]
cur.updateRow(rowShape)
shp_num += 1
except Exception as e:
print(e)
print(i)
print(shp_num)
print(len(rowValues))
print(rowValues[i-1])
print(len(rowValues[i-1]))
del cur
print("create layer:")
vl = MakeFeatureLayer(str(f_class), newName).getOutput(0)
active_map.addLayerToGroup(layerGroup, vl)
print("created2")
#os.remove(path_bim)
return True #last one
def cadLayerToNative(layerContentList: List[Base], layerName: str, streamBranch: str, project: ArcGISProject) :
print("01______Cad vector layer to native")
print(layerName)
geom_points = []
@@ -187,7 +368,7 @@ def cadVectorLayerToNative(geomList, layerName: str, geomType: str, streamBranch
layerName = layerName.replace("[","_").replace("]","_").replace(" ","_").replace("-","_").replace("(","_").replace(")","_").replace(":","_").replace("\\","_").replace("/","_").replace("\"","_").replace("&","_").replace("@","_").replace("$","_").replace("%","_").replace("^","_")
layerName = layerName + "_" + geomType
sr = arcpy.SpatialReference(project.activeMap.spatialReference.name)
sr = arcpy.SpatialReference(text = project.activeMap.spatialReference.exportToString())
active_map = project.activeMap
path = project.filePath.replace("aprx","gdb") #"\\".join(project.filePath.split("\\")[:-1]) + "\\speckle_layers\\" #arcpy.env.workspace + "\\" #
@@ -231,7 +412,7 @@ def cadVectorLayerToNative(geomList, layerName: str, geomType: str, streamBranch
# should be created inside the workspace to be a proper Feature class (not .shp) with Nullable Fields
class_name = ("f_class_" + newName)
f_class = CreateFeatureclass(path, class_name, geomType, has_z="ENABLED", spatial_reference = sr)
#print(f_class)
print(f_class)
#print(geomList)
# get and set Layer attribute fields
@@ -269,7 +450,7 @@ def cadVectorLayerToNative(geomList, layerName: str, geomType: str, streamBranch
if new_feat != "" and new_feat != None:
fets.append(new_feat)
#print("features created")
#print(fets)
print(len(fets))
if len(fets) == 0: return None
count = 0
@@ -287,19 +468,16 @@ def cadVectorLayerToNative(geomList, layerName: str, geomType: str, streamBranch
row.append(value)
rowValues.append(row)
count += 1
#print(heads)
cur = arcpy.da.InsertCursor(str(f_class), tuple(heads) )
for row in rowValues:
#print(tuple(heads))
#print(tuple(row))
cur.insertRow(tuple(row))
del cur
#print(f_class)
vl = MakeFeatureLayer(str(f_class), newName).getOutput(0)
#adding layers from code solved: https://gis.stackexchange.com/questions/344343/arcpy-makefeaturelayer-management-function-not-creating-feature-layer-in-arcgis
#active_map.addLayer(new_layer)
active_map.addLayerToGroup(layerGroup, vl)
print("Layer created")
return vl
@@ -14,7 +14,7 @@ from speckle.converter.layers.utils import (findTransformation, getVariantFromVa
traverseDictByKey, hsv_to_rgb)
from speckle.converter.geometry.point import pointToSpeckle
from speckle.converter.geometry.mesh import rasterToMesh
from speckle.converter.geometry.mesh import rasterToMesh, meshToNative
import numpy as np
import colorsys
@@ -27,10 +27,19 @@ def featureToSpeckle(fieldnames, attr_list, f_shape, projectCRS: arcpy.SpatialRe
layer_sr = data.spatialReference # if sr.type == "Projected":
geomType = data.shapeType #Polygon, Point, Polyline, Multipoint, MultiPatch
featureType = data.featureType # Simple,SimpleJunction,SimpleJunction,ComplexEdge,Annotation,CoverageAnnotation,Dimension,RasterCatalogItem
print(geomType)
print(hasattr(data, "isRevit"))
print(hasattr(data, "isIFC"))
print(hasattr(data, "bimLevels"))
print(hasattr(data, "hasSpatialIndex"))
if geomType == "MultiPatch" or hasattr(data, "isRevit") or hasattr(data, "isIFC") or hasattr(data, "bimLevels"):
print(f"Layer {selectedLayer.name} has unsupported data type")
arcpy.AddWarning(f"Layer {selectedLayer.name} has unsupported data type")
return None
#print(layer_sr.name)
#print(projectCRS.name)
f_shape = findTransformation(f_shape, geomType, layer_sr, projectCRS, selectedLayer)
if f_shape is None: return None
######################################### Convert geometry ##########################################
try:
@@ -82,9 +91,26 @@ def featureToNative(feature: Base, fields: dict, geomType: str, sr: arcpy.Spatia
if variant == "LONG": feat.update({key: None})
if variant == "SHORT": feat.update({key: None})
return feat
def bimFeatureToNative(feature: Base, fields: dict, sr: arcpy.SpatialReference, path: str):
#print("04_________BIM Feature To Native____________")
feat = {}
try: speckle_geom = feature["geometry"] # for created in QGIS Layer type
except: speckle_geom = feature # for created in other software
feat.update({"arcGisGeomFromSpeckle": ""})
try:
if "Speckle_ID" not in fields.keys() and feature["id"]: feat.update("Speckle_ID", "TEXT")
except: pass
feat_updated = updateFeat(feat, fields, feature)
return feat_updated
def cadFeatureToNative(feature: Base, fields: dict, sr: arcpy.SpatialReference):
print("04_________CAD Feature To Native____________")
#print("04_________CAD Feature To Native____________")
feat = {}
try: speckle_geom = feature["geometry"] # for created in QGIS Layer type
except: speckle_geom = feature # for created in other software
@@ -94,43 +120,73 @@ def cadFeatureToNative(feature: Base, fields: dict, sr: arcpy.SpatialReference):
else: arcGisGeom = convertToNative(speckle_geom[0], sr)
else:
arcGisGeom = convertToNative(speckle_geom, sr)
print(feat)
if arcGisGeom is not None:
feat.update({"arcGisGeomFromSpeckle": arcGisGeom})
else: return None
print(feat)
try:
if "Speckle_ID" not in fields.keys() and feature["id"]: feat.update("Speckle_ID", "TEXT")
except: pass
print(feat)
#### setting attributes to feature
for key, variant in fields.items():
#value = feature[key]
#print()
if key == "Speckle_ID":
value = str(feature["id"])
feat[key] = value
else:
try: value = feature[key]
except:
rootName = key.split("_")[0]
newF, newVals = traverseDict({}, {}, rootName, feature[rootName][0])
for i, (k,v) in enumerate(newVals.items()):
if k == key: value = v; break
# for all values:
if variant == "TEXT": value = str(value)
if variant == getVariantFromValue(value) and value != "NULL" and value != "None":
feat.update({key: value})
else:
if variant == "TEXT": feat.update({key: None})
if variant == "FLOAT": feat.update({key: None})
if variant == "LONG": feat.update({key: None})
if variant == "SHORT": feat.update({key: None})
print(feat)
return feat
#### setting attributes to feature
feat_updated = updateFeat(feat, fields, feature)
return feat_updated
def updateFeat(feat:dict, fields: dict, feature: Base) -> dict[str, Any]:
for key, variant in fields.items():
try:
if key == "Speckle_ID":
value = str(feature["id"])
if key != "parameters": print(value)
feat[key] = value
if variant == "TEXT": value = str(value)
if variant == getVariantFromValue(value) and value != "NULL" and value != "None": feat.update({key: value})
elif variant == "TEXT" or variant == "FLOAT" or variant == "LONG" or variant == "SHORT": feat.update({key: None})
else:
try:
value = feature[key]
if variant == "TEXT": value = str(value)
if variant == getVariantFromValue(value) and value != "NULL" and value != "None": feat.update({key: value})
elif variant == "TEXT" or variant == "FLOAT" or variant == "LONG" or variant == "SHORT": feat.update({key: None})
except:
value = None
rootName = key.split("_")[0]
#try: # if the root category exists
# if its'a list
if isinstance(feature[rootName], list):
for i in range(len(feature[rootName])):
try:
newF, newVals = traverseDict({}, {}, rootName + "_" + str(i), feature[rootName][i])
for i, (key,value) in enumerate(newVals.items()):
for k, (x,y) in enumerate(newF.items()):
if key == x: variant = y; break
if variant == "TEXT": value = str(value)
if variant == getVariantFromValue(value) and value != "NULL" and value != "None": feat.update({key: value})
elif variant == "TEXT" or variant == "FLOAT" or variant == "LONG" or variant == "SHORT": feat.update({key: None})
except Exception as e: print(e)
#except: # if not a list
else:
try:
newF, newVals = traverseDict({}, {}, rootName, feature[rootName])
for i, (key,value) in enumerate(newVals.items()):
for k, (x,y) in enumerate(newF.items()):
if key == x: variant = y; break
#print(variant)
if variant == "TEXT": value = str(value)
if variant == getVariantFromValue(value) and value != "NULL" and value != "None": feat.update({key: value})
elif variant == "TEXT" or variant == "FLOAT" or variant == "LONG" or variant == "SHORT": feat.update({key: None})
except Exception as e: feat.update({key: None})
except Exception as e:
feat.update({key: None})
feat_sorted = {k: v for k, v in sorted(feat.items(), key=lambda item: item[0])}
#print("_________________end of updating a feature_________________________")
return feat_sorted
def rasterFeatureToSpeckle(selectedLayer: arcLayer, projectCRS: arcpy.SpatialReference, project: ArcGISProject) -> Base:
print("_________ Raster feature to speckle______")
# https://pro.arcgis.com/en/pro-app/latest/arcpy/classes/raster-object.htm
@@ -181,6 +237,8 @@ def rasterFeatureToSpeckle(selectedLayer: arcLayer, projectCRS: arcpy.SpatialRef
reprojectedPt = rasterOriginPoint
if my_raster.spatialReference.name != projectCRS.name:
reprojectedPt = findTransformation(reprojectedPt, "Point", my_raster.spatialReference, projectCRS, selectedLayer)
if reprojectedPt is None:
reprojectedPt = rasterOriginPoint
geom = pointToSpeckle(reprojectedPt.getPart(), None, None)
if (geom != None):
b['displayValue'] = [geom]
@@ -324,7 +382,7 @@ def rasterFeatureToSpeckle(selectedLayer: arcLayer, projectCRS: arcpy.SpatialRef
pt3 = arcpy.PointGeometry(arcpy.Point(extent.XMin+(h+1)*rasterResXY[0], extent.YMax-(v+1)*rasterResXY[1]), my_raster.spatialReference, has_z = True)
pt4 = arcpy.PointGeometry(arcpy.Point(extent.XMin+(h+1)*rasterResXY[0], extent.YMax-v*rasterResXY[1]), my_raster.spatialReference, has_z = True)
# first, get point coordinates with correct position and resolution, then reproject each:
if my_raster.spatialReference.name != projectCRS.name:
if my_raster.spatialReference.exportToString() != projectCRS.exportToString():
pt1 = findTransformation(pt1, "Point", my_raster.spatialReference, projectCRS, selectedLayer)
pt2 = findTransformation(pt2, "Point", my_raster.spatialReference, projectCRS, selectedLayer)
pt3 = findTransformation(pt3, "Point", my_raster.spatialReference, projectCRS, selectedLayer)
@@ -386,11 +444,17 @@ def rasterFeatureToSpeckle(selectedLayer: arcLayer, projectCRS: arcpy.SpatialRef
if colorizer:
try: bandIndex = int(colorizer.band)
except: pass
if rasterBandVals[bandIndex][int(count/4)] >= float(colorizer.minLabel) and rasterBandVals[bandIndex][int(count/4)] <= float(colorizer.maxLabel) : #rasterBandMinVal[bandIndex]:
try:
if rasterBandVals[bandIndex][int(count/4)] >= float(colorizer.minLabel) and rasterBandVals[bandIndex][int(count/4)] <= float(colorizer.maxLabel) : #rasterBandMinVal[bandIndex]:
# REMAP band values to (0,255) range
valRange = float(colorizer.maxLabel) - float(colorizer.minLabel) #(rasterBandMaxVal[bandIndex] - rasterBandMinVal[bandIndex])
colorVal = int( (rasterBandVals[bandIndex][int(count/4)] - float(colorizer.minLabel)) / valRange * 255 )
if colorizer.invertColorRamp is True: colorVal = int( (-rasterBandVals[bandIndex][int(count/4)] + float(colorizer.maxLabel)) / valRange * 255 )
color = (colorVal<<16) + (colorVal<<8) + colorVal
except: # if no Min Max labels:
# REMAP band values to (0,255) range
valRange = float(colorizer.maxLabel) - float(colorizer.minLabel) #(rasterBandMaxVal[bandIndex] - rasterBandMinVal[bandIndex])
colorVal = int( (rasterBandVals[bandIndex][int(count/4)] - float(colorizer.minLabel)) / valRange * 255 )
if colorizer.invertColorRamp is True: colorVal = int( (-rasterBandVals[bandIndex][int(count/4)] + float(colorizer.maxLabel)) / valRange * 255 )
valRange = max(rasterBandVals[bandIndex]) - min(rasterBandVals[bandIndex]) #(rasterBandMaxVal[bandIndex] - rasterBandMinVal[bandIndex])
colorVal = int( (rasterBandVals[bandIndex][int(count/4)] - min(rasterBandVals[bandIndex])) / valRange * 255 )
color = (colorVal<<16) + (colorVal<<8) + colorVal
else:
# REMAP band values to (0,255) range
@@ -6,8 +6,10 @@ import arcpy
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
import os
ATTRS_REMOVE = ['geometry','applicationId','bbox','displayStyle', 'id', 'renderMaterial', 'displayMesh']
def getVariantFromValue(value: Any) -> Union[str, None]:
#print("_________get variant from value_______")
# TODO add Base object
pairs = [
(str, "TEXT"), # 10
@@ -28,73 +30,93 @@ def getVariantFromValue(value: Any) -> Union[str, None]:
return res
def getLayerAttributes(features: List[Base], attrsToRemove: List[str] =['geometry','applicationId','bbox','displayStyle', 'id', 'renderMaterial', 'geometry'] ) -> dict:
def getLayerAttributes(featuresList: List[Base], attrsToRemove: List[str] = ATTRS_REMOVE ) -> dict[str, str]:
print("03________ get layer attributes")
#print(featuresList)
if not isinstance(featuresList, List): features = [featuresList]
else: features = featuresList[:]
fields = {}
all_props = []
for feature in features:
#get object properties to add as attributes
dynamicProps = feature.get_dynamic_member_names()
#attrsToRemove = ['geometry','applicationId','bbox','displayStyle', 'id', 'renderMaterial', 'geometry']
for att in attrsToRemove:
try: dynamicProps.remove(att)
except: pass
dynamicProps.sort()
print(dynamicProps)
# add field names and variands
for name in dynamicProps:
if name not in all_props: all_props.append(name)
#if name not in all_props: all_props.append(name)
value = feature[name]
variant = getVariantFromValue(value)
if not variant: variant = None #LongLong #4
# go thought the dictionary object
print("go thought the dictionary object")
if value and isinstance(value, list) and isinstance(value[0], dict) :
all_props.remove(name) # remove generic dict name
newF, newVals = traverseDict( {}, {}, name, value[0])
#print(newF)
#print(newF.items())
if value and isinstance(value, list):
#all_props.remove(name) # remove generic dict name
for i, val_item in enumerate(value):
newF, newVals = traverseDict( {}, {}, name+"_"+str(i), val_item)
for i, (k,v) in enumerate(newF.items()):
fields.update({k: v})
if k not in all_props: all_props.append(k)
#print(fields)
# add a field if not existing yet
else: # if str, Base, etc
newF, newVals = traverseDict( {}, {}, name, value)
for i, (k,v) in enumerate(newF.items()):
fields.update({k: v})
if k not in all_props: all_props.append(k)
if k not in fields.keys(): fields.update({k: v}) #if variant is known
elif k in fields.keys(): #check if the field was empty previously:
oldVariant = fields[k]
# replace if new one is NOT LongLong or IS String
if oldVariant != "TEXT" and variant == "TEXT":
fields.update({k: variant})
#print(fields)
# add a field if not existing yet AND if variant is known
elif variant and (name not in fields.keys()):
fields.update({name: variant})
elif name in fields.keys(): #check if the field was empty previously:
oldVariant = fields[name]
# replace if new one is NOT LongLong or IS String
if oldVariant != "TEXT" and variant == "TEXT":
fields.update({name: variant})
#print(all_props)
# replace all empty ones wit String
for name in all_props:
if name not in fields.keys():
fields.update({name: "TEXT"})
print(fields)
return fields
fields.update({name: 'TEXT'})
#print(fields)
fields_sorted = {k: v for k, v in sorted(fields.items(), key=lambda item: item[0])}
return fields_sorted
def traverseDict(newF: dict, newVals: dict, nam: str, val: Any):
#print("Traverse Dict")
#print("______05___Traverse Dict")
#print(nam)
#print(val)
if isinstance(val, dict):
#print("DICT")
for i, (k,v) in enumerate(val.items()):
traverseDict( newF, newVals, nam+"_"+k, v)
newF, newVals = traverseDict( newF, newVals, nam+"_"+k, v)
elif isinstance(val, Base):
#print("BASE")
dynamicProps = val.get_dynamic_member_names()
for att in ATTRS_REMOVE:
try: dynamicProps.remove(att)
except: pass
dynamicProps.sort()
item_dict = {}
for prop in dynamicProps:
item_dict.update({prop: val[prop]})
for i, (k,v) in enumerate(item_dict.items()):
newF, newVals = traverseDict( newF, newVals, nam+"_"+k, v)
else:
#print("ELSE")
var = getVariantFromValue(val)
if not var: var = None #LongLong #4
else:
newF.update({nam: var})
newVals.update({nam: val})
#print(newF)
#print(newVals)
#print("traverse end")
if var is None:
var = 'TEXT'
val = str(val)
newF.update({nam: var})
newVals.update({nam: val})
return newF, newVals
def get_scale_factor(units: str) -> float:
@@ -123,46 +145,52 @@ def findTransformation(f_shape, geomType, layer_sr: arcpy.SpatialReference, proj
#apply transformation if needed
if layer_sr.name != projectCRS.name:
tr0 = tr1 = tr2 = tr_custom = None
transformations = arcpy.ListTransformations(layer_sr, projectCRS)
customTransformName = "layer_sr.name"+"_To_"+ projectCRS.name
if len(transformations) == 0:
midSr = arcpy.SpatialReference("WGS 1984") # GCS_WGS_1984
try:
tr1 = arcpy.ListTransformations(layer_sr, midSr)[0]
tr2 = arcpy.ListTransformations(midSr, projectCRS)[0]
except:
#customGeoTransfm = "GEOGTRAN[METHOD['Geocentric_Translation'],PARAMETER['X_Axis_Translation',''],PARAMETER['Y_Axis_Translation',''],PARAMETER['Z_Axis_Translation','']]"
#CreateCustomGeoTransformation(customTransformName, layer_sr, projectCRS)
tr_custom = customTransformName
else:
#print("else")
# choose equation based instead of file-based/grid-based method,
# to be consistent with QGIS: https://desktop.arcgis.com/en/arcmap/latest/map/projections/choosing-an-appropriate-transformation.htm
selecterTr = {}
for tr in transformations:
if "NTv2" not in tr and "NADCON" not in tr:
set1 = set( layer_sr.name.split("_") + projectCRS.name.split("_") )
set2 = set( tr.split("_") )
diff = len( set(set1).symmetric_difference(set2) )
selecterTr.update({tr: diff})
selecterTr = dict(sorted(selecterTr.items(), key=lambda item: item[1]))
tr0 = list(selecterTr.keys())[0]
print(layer_sr)
try:
transformations = arcpy.ListTransformations(layer_sr, projectCRS)
customTransformName = "layer_sr.name"+"_To_"+ projectCRS.name
if len(transformations) == 0:
midSr = arcpy.SpatialReference("WGS 1984") # GCS_WGS_1984
try:
tr1 = arcpy.ListTransformations(layer_sr, midSr)[0]
tr2 = arcpy.ListTransformations(midSr, projectCRS)[0]
except:
#customGeoTransfm = "GEOGTRAN[METHOD['Geocentric_Translation'],PARAMETER['X_Axis_Translation',''],PARAMETER['Y_Axis_Translation',''],PARAMETER['Z_Axis_Translation','']]"
#CreateCustomGeoTransformation(customTransformName, layer_sr, projectCRS)
tr_custom = customTransformName
else:
#print("else")
# choose equation based instead of file-based/grid-based method,
# to be consistent with QGIS: https://desktop.arcgis.com/en/arcmap/latest/map/projections/choosing-an-appropriate-transformation.htm
selecterTr = {}
for tr in transformations:
if "NTv2" not in tr and "NADCON" not in tr:
set1 = set( layer_sr.name.split("_") + projectCRS.name.split("_") )
set2 = set( tr.split("_") )
diff = len( set(set1).symmetric_difference(set2) )
selecterTr.update({tr: diff})
selecterTr = dict(sorted(selecterTr.items(), key=lambda item: item[1]))
tr0 = list(selecterTr.keys())[0]
if geomType != "Point" and geomType != "Polyline" and geomType != "Polygon" and geomType != "Multipoint":
try: arcpy.AddWarning("Unsupported or invalid geometry in layer " + selectedLayer.name)
except: arcpy.AddWarning("Unsupported or invalid geometry")
if geomType != "Point" and geomType != "Polyline" and geomType != "Polygon" and geomType != "Multipoint":
try: arcpy.AddWarning("Unsupported or invalid geometry in layer " + selectedLayer.name)
except: arcpy.AddWarning("Unsupported or invalid geometry")
# reproject geometry using chosen transformstion(s)
if tr0 is not None:
ptgeo1 = f_shape.projectAs(projectCRS, tr0)
f_shape = ptgeo1
elif tr1 is not None and tr2 is not None:
ptgeo1 = f_shape.projectAs(midSr, tr1)
ptgeo2 = ptgeo1.projectAs(projectCRS, tr2)
f_shape = ptgeo2
else:
ptgeo1 = f_shape.projectAs(projectCRS)
f_shape = ptgeo1
# reproject geometry using chosen transformstion(s)
if tr0 is not None:
ptgeo1 = f_shape.projectAs(projectCRS, tr0)
f_shape = ptgeo1
elif tr1 is not None and tr2 is not None:
ptgeo1 = f_shape.projectAs(midSr, tr1)
ptgeo2 = ptgeo1.projectAs(projectCRS, tr2)
f_shape = ptgeo2
else:
ptgeo1 = f_shape.projectAs(projectCRS)
f_shape = ptgeo1
except:
arcpy.AddWarning(f"Spatial Transformation not found for layer {selectedLayer.name}")
return None
return f_shape
@@ -252,12 +280,12 @@ def curvedFeatureClassToSegments(layer) -> str:
print(newPath)
return newPath
def validate_path(path):
def validate_path(path: str):
# https://github.com/EsriOceans/btm/commit/a9c0529485c9b0baa78c1f094372c0f9d83c0aaf
"""If our path contains a DB name, make sure we have a valid DB name and not a standard file name."""
dirname, file_name = os.path.split(path)
print(dirname)
print(file_name)
#print(dirname)
#print(file_name)
file_base = os.path.splitext(file_name)[0]
if dirname == '':
# a relative path only, relying on the workspace
@@ -0,0 +1,246 @@
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
import arcpy
from speckle.converter.layers.CRS import CRS
from speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
from arcpy.management import (CreateFeatureclass, MakeFeatureLayer,
AddFields, AlterField, DefineProjection )
from specklepy.objects import Base
project = ArcGISProject('CURRENT')
active_map = project.activeMap
all_layers = []
#get layer of interest
for layer in active_map.listLayers():
if layer.isFeatureLayer or layer.isRasterLayer: all_layers.append(layer)
if layer.name == "ExteriorShell": break
if isinstance(layer, arcLayer):
projectCRS = project.activeMap.spatialReference
try: data = arcpy.Describe(layer.dataSource)
except OSError as e: print(e)
layerName = layer.name
crs = data.SpatialReference
units = "m"
layerObjs = []
# Convert CRS to speckle, use the projectCRS
speckleReprojectedCrs = CRS(name = projectCRS.name, wkt = projectCRS.exportToString(), units = units)
layerCRS = CRS(name=crs.name, wkt=crs.exportToString(), units = units)
if layer.isFeatureLayer:
print("VECTOR LAYER HERE")
speckleLayer = VectorLayer(units = "m")
speckleLayer.type="VectorLayer"
speckleLayer.name = layerName
speckleLayer.crs = speckleReprojectedCrs
if data.datasetType == "FeatureClass": #FeatureClass, ?Table Properties, ?Datasets
# write feature attributes
fieldnames = [field.name for field in data.fields]
rows_shapes = arcpy.da.SearchCursor(layer.longName, "Shape@") # arcpy.da.SearchCursor(in_table, field_names, {where_clause}, {spatial_reference}, {explode_to_points}, {sql_clause})
print("__ start iterating features")
row_shapes_list = [x for k, x in enumerate(rows_shapes)]
for i, features in enumerate(row_shapes_list):
print("____error Feature # " + str(i+1)) # + " / " + str(sum(1 for _ in enumerate(rows_shapes))))
if features[0] is None: continue
feat = features[0]
if feat is not None:
print(feat)
rows_attributes = arcpy.da.SearchCursor(layer.longName, fieldnames)
row_attr = []
for k, attrs in enumerate(rows_attributes):
if i == k: row_attr = attrs; break
if feat.hasCurves: feat = feat.densify("ANGLE", 1000, 0.12)
print("___________Feature to Speckle____________")
b = Base(units = "m")
data = arcpy.Describe(layer.dataSource)
layer_sr = data.spatialReference # if sr.type == "Projected":
geomType = data.shapeType #Polygon, Point, Polyline, Multipoint, MultiPatch
featureType = data.featureType # Simple,SimpleJunction,SimpleJunction,ComplexEdge,Annotation,CoverageAnnotation,Dimension,RasterCatalogItem
print(geomType)
print(hasattr(data, "isRevit"))
print(hasattr(data, "isIFC"))
print(hasattr(data, "bimLevels"))
print(hasattr(data, "hasSpatialIndex"))
if geomType == "MultiPatch" or hasattr(data, "isRevit") or hasattr(data, "isIFC") or hasattr(data, "bimLevels"):
print(f"Layer {layer.name} has unsupported data type")
print("___convertToSpeckle____________")
geom = feat
print(geom.isMultipart) # e.g. False
print(geom.hasCurves)
print(geom.partCount)
geomMultiType = geom.isMultipart
hasCurves = feat.hasCurves
geomPart = []
for i,x in enumerate(feat): # [[x,x,x]
if i==0:
print("Part # " + str(i+1))
print(x)
inner_arr = []
for k,ptn in enumerate(x):
if k<10: print(ptn) # e.g. 6.25128173828125 -9.42138671875 22.2768999999971 NaN
inner_arr.append(ptn)
#inner_arr.append(inner_arr[0]) #add first in the end
geomPart.append(arcpy.Array(inner_arr))
geomPartArray = arcpy.Array(inner_arr)
sr = project.activeMap.spatialReference
multipatch = arcpy.Multipatch(arcpy.Array(x), sr, has_z=True) # error
print(multipatch)
else:
print("___convertToSpeckle____________")
geom = feat
print(geom.isMultipart) # e.g. False
print(geom.hasCurves)
print(geom.partCount)
geomMultiType = geom.isMultipart
hasCurves = feat.hasCurves
for i,x in enumerate(feat): # [[x,x,x]
print("Part # " + str(i+1))
print(x)
for k,ptn in enumerate(x):
if k<10: print(ptn) # e.g. 6.25128173828125 -9.42138671875 22.2768999999971 NaN
path: str = project.filePath.replace("aprx","gdb")
sr = project.activeMap.spatialReference
print(sr)
f_class = CreateFeatureclass(path, "NewTestLayer", "Multipatch", has_z="ENABLED", spatial_reference = sr)
fets = []
print("04_____Feature To Native____________")
new_feat = {}
new_feat.update({"arcGisGeomFromSpeckle": multipatch})
fets.append(new_feat)
vl = MakeFeatureLayer(str(f_class), "NewTestLayer").getOutput(0)
############################# write shapefile ##################################
import shapefile
from shapefile import TRIANGLE_STRIP, TRIANGLE_FAN
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
from arcpy.management import (CreateFeatureclass, MakeFeatureLayer,
AddFields, AlterField, DefineProjection )
from specklepy.objects import Base
project = ArcGISProject('CURRENT')
path: str = project.filePath.replace("aprx","gdb")
#with shapefile.Writer(path + "\contextwriter") as w:
# w.field('field1', 'C')
# pass
w = shapefile.Writer(path + '\\dtype')
w.field('TEXT', 'C')
w.field('SHORT_TEXT', 'C', size=5)
w.field('LONG_TEXT', 'C', size=250)
w.null()
w.record('Hello', 'World', 'World'*50)
w.close()
r = shapefile.Reader(path + '\\dtype')
assert r.record(0) == ['Hello', 'World', 'World'*50]
################################################################### WORKS #################################
w = shapefile.Writer(path + '\\dtype')
w.field('INT', 'N')
w.field('LOWPREC', 'N', decimal=2)
w.field('MEDPREC', 'N', decimal=10)
w.field('HIGHPREC', 'N', decimal=30)
w.field('FTYPE', 'F', decimal=10)
w.field('LARGENR', 'N', 101)
w.field('FIRST_FLD','C','40')
w.field('SECOND_FLD','C','40')
nr = 1.3217328
w.null()
w.null()
w.record(INT=nr, LOWPREC=nr, MEDPREC=nr, HIGHPREC=-3.2302e-25, FTYPE=nr, LARGENR=int(nr)*10**100, FIRST_FLD='First', SECOND_FLD='Line')
w.record(None, None, None, None, None, None, '', '')
w.close()
r = shapefile.Reader(path + '\\dtype')
assert r.record(0) == [1, 1.32, 1.3217328, -3.2302e-25, 1.3217328, 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, 'First', 'Line']
assert r.record(1) == [None, None, None, None, None, None, '', '']
################################################################# Add point ####################
w = shapefile.Writer(path + '\\dtypeShapes')
w.field('name', 'C')
w.point(122, 37)
w.record('point1')
w.close()
################################################################# Add Multipatch ####################
w = shapefile.Writer(path + '\\MultipatchTest2')
w.field('name', 'C')
w.multipatch([
[[0,0,0],[0,0,3],[5,0,0],[5,0,3],[5,5,0],[5,5,3],[0,5,0],[0,5,3],[0,0,0],[0,0,3]], # TRIANGLE_STRIP for house walls
[[2.5,2.5,5],[0,0,3],[5,0,3],[5,5,3],[0,5,3],[0,0,3]], # TRIANGLE_FAN for pointed house roof
],
partTypes=[TRIANGLE_STRIP, TRIANGLE_FAN]) # one type for each part
w.record('house1')
w.close()
r = shapefile.Reader(path + '\\MultipatchTest2')
assert r.record(0) == ['house1']
active_map.addDataFromPath(path + '\\MultipatchTest2.shp')
########################################################################## reader
sf = shapefile.Reader(path + '\\MultipatchTest2.shp')
sf.shapeType # e.g. 31 - multipatch
sf.bbox # e.g. [0.0, 0.0, 5.0, 5.0]
shapefile.Shape
##################################################### cerate multipatch layer #################################
result = arcpy.management.CreateFeatureclass(arcpy.env.scratchGDB, "test_multipatch", "MULTIPATCH", has_z="ENABLED", spatial_reference=4326)
feature_class = result[0]
################################# reading shapefile - works ####################
fc = r'C:\Users\katri\Documents\ArcGIS\Projects\MyProject\BIM_layers_speckle\00f70159b9104180f622cca87f5dd2cb.shp'
rows = arcpy.da.SearchCursor(fc, 'Shape@')
for r in rows:
if r is not None: shape = r
print(shape)
cl = arcpy.conversion.FeatureClassToFeatureClass(r'C:\Users\katri\Documents\ArcGIS\Projects\MyProject\BIM_layers_speckle\16d73b756a_main_2f8cfa8644\__Floors_Mesh\00c7696966e4cfda2bd8c03860a414a6', r'C:\Users\katri\Documents\ArcGIS\tests', 'copyclass')
##################################### update rows in feature class - working #############
with arcpy.da.UpdateCursor('f_class_2f8cfa8644___Structural_Framing_Mesh', 'name') as cursor:
# For each row, evaluate the WELL_YIELD value (index position
# of 0), and update WELL_CLASS (index position of 1)
for row in cursor:
row[0] = "newName"
cursor.updateRow(row)
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
from typing import Any, Callable, List, Optional, Tuple
r'''
#r'''
from collections import defaultdict
import arcpy
@@ -12,7 +13,7 @@ from arcpy import metadata as md
from specklepy.api.models import Branch, Stream, Streams
from speckle.converter.layers.Layer import Layer, RasterLayer
from speckle.converter.layers._init_ import convertSelectedLayers, layerToNative, cadLayerToNative
from speckle.converter.layers._init_ import convertSelectedLayers, layerToNative, cadLayerToNative, bimLayerToNative
from arcgis.features import FeatureLayer
import os
import os.path
@@ -36,7 +37,7 @@ from speckle.ui.project_vars import toolboxInputsClass, speckleInputsClass
from speckle.converter.layers.emptyLayerTemplates import createGroupLayer
from speckle.converter.layers.Layer import VectorLayer
#'''
def traverseObject(
base: Base,
@@ -74,7 +75,7 @@ class Toolbox:
.pyt file)."""
print("___ping_Toolbox")
self.label = "Speckle Tools"
self.alias = "speckle_toolbox"
self.alias = "speckle_toolbox_"
# List of tool classes associated with this toolbox
self.tools = [Speckle]
metrics.set_host_app("ArcGIS")
@@ -256,7 +257,7 @@ class Speckle:
msg = arcpy.Parameter(
displayName="Message",
name="message",
name="msg",
datatype="GPString",
parameterType="Optional",
direction="Input",
@@ -331,7 +332,7 @@ class Speckle:
saved_streams = self.speckleInputs.getProjectStreams()
self.speckleInputs.saved_streams = saved_streams
p_saved.filter.list = [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}" for i,stream in enumerate(saved_streams)]
p_saved.value = p_saved.filter.list[0]
if len(p_saved.filter.list)>0: print(p_saved.filter.list); p_saved.value = p_saved.filter.list[0]
break
p.value = None
par.value = False
@@ -358,7 +359,7 @@ class Speckle:
saved_streams = self.speckleInputs.getProjectStreams()
self.speckleInputs.saved_streams = saved_streams
p_saved.filter.list = [f"Stream not accessible - {st[0].stream_id}" if st[1] is None or isinstance(st[1], SpeckleException) else f"{st[1].name} - {st[1].id}" for i,st in enumerate(saved_streams)]
p_saved.value = p_saved.filter.list[0]
if len(p_saved.filter.list)>0: print(p_saved.filter.list); p_saved.value = p_saved.filter.list[0]
else: pass
p.value = None
@@ -397,12 +398,12 @@ class Speckle:
for p in parameters:
if p.name == "lat" and p.valueAsText is not None:
# add value from the UI to saved lat
lat = p.valueAsText[:].replace(",","").replace(" ","").replace(";","").replace("-","").replace("_","")
lat = p.valueAsText[:].replace(",","").replace(" ","").replace(";","").replace("_","")
try: lat = float(lat)
except: lat = 0; p.value = "0.0"
if p.name == "lon" and p.valueAsText is not None:
# add value from the UI to saved lat
lon = p.valueAsText[:].replace(",","").replace(" ","").replace(";","").replace("-","").replace("_","")
lon = p.valueAsText[:].replace(",","").replace(" ","").replace(";","").replace("_","")
try: lon = float(lon)
except: lon = 0; p.value = "0.0"
coords = [lat, lon]
@@ -493,6 +494,7 @@ class Speckle:
if par.name == "msg" and par.altered and par.valueAsText is not None:
self.toolboxInputs.messageSpeckle = par.valueAsText
print(self.toolboxInputs.messageSpeckle)
if par.name == "action" and par.altered:
#print("action changed")
@@ -525,8 +527,8 @@ class Speckle:
if par.name == "streamUrl": par.value = None
if par.name == "streamsDefalut": par.value = None
if par.name == "savedStreams": par.value = None
if par.name == "branch": par.value = ""
if par.name == "commit": par.value = None
if par.name == "branch": par.value = ""; par.filter.list = []
if par.name == "commit": par.value = None; par.filter.list = []
if par.name == "selectedLayers": par.value = None
if par.name == "msg": par.value = ""
if par.name == "action": par.value = "Send"
@@ -601,6 +603,9 @@ class Speckle:
base_obj = Base(units = "m")
base_obj.layers = convertSelectedLayers(self.speckleInputs.all_layers, self.toolboxInputs.selected_layers, self.speckleInputs.project)
if len(base_obj.layers) == 0:
arcpy.AddMessage("No data sent to stream " + streamId)
return
try:
# this serialises the block and sends it to the transport
objId = operations.send(base=base_obj, transports=[transport])
@@ -613,7 +618,9 @@ class Speckle:
message = self.toolboxInputs.messageSpeckle
print(message)
if message is None or ( isinstance(message, str) and len(message) == 0): message = "Sent from ArcGIS"
print(message)
try:
# you can now create a commit on your stream with this object
client.commit.create(
@@ -681,14 +688,14 @@ class Speckle:
if self.speckleInputs.project.activeMap.spatialReference.type == "Geographic" or self.speckleInputs.project.activeMap.spatialReference is None: #TODO test with invalid CRS
arcpy.AddMessage("It is advisable to set the project Spatial reference to Projected type before receiving CAD geometry (e.g. EPSG:32631), or create a custom one from geographic coordinates")
print("It is advisable to set the project Spatial reference to Projected type before receiving CAD geometry (e.g. EPSG:32631), or create a custom one from geographic coordinates")
print(f"Succesfully received {objId}")
print(f"Successfully received {objId}")
# Clear 'latest' group
streamBranch = streamId + "_" + self.toolboxInputs.active_branch.name + "_" + str(commit.id)
newGroupName = f'{streamBranch}'
groupExists = 0
#print(newGroupName)
print(newGroupName)
for l in self.speckleInputs.project.activeMap.listLayers():
#print(l.longName)
if l.longName.startswith(newGroupName + "\\"):
@@ -697,18 +704,29 @@ class Speckle:
groupExists+=1
elif l.longName == newGroupName:
groupExists+=1
print(newGroupName)
if groupExists == 0:
# create empty group layer file
path = self.speckleInputs.project.filePath.replace("aprx","gdb") #"\\".join(self.toolboxInputs.project.filePath.split("\\")[:-1]) + "\\speckle_layers\\"
#print(path)
f = open(path + "\\" + newGroupName + ".lyrx", "w")
content = createGroupLayer().replace("TestGroupLayer", newGroupName)
f.write(content)
f.close()
smth = arcpy.mp.LayerFile(path + "\\" + newGroupName + ".lyrx")
#print(smth)
layerGroup = self.speckleInputs.project.activeMap.addLayer(smth)[0]
print(path)
try:
f = open(path + "\\" + newGroupName + ".lyrx", "w")
content = createGroupLayer().replace("TestGroupLayer", newGroupName)
f.write(content)
f.close()
newGroupLayer = arcpy.mp.LayerFile(path + "\\" + newGroupName + ".lyrx")
layerGroup = self.speckleInputs.project.activeMap.addLayer(newGroupLayer)[0]
except: # for 3.0.0
if self.speckleInputs.active_map is not None:
layerGroup = self.speckleInputs.active_map.createGroupLayer(newGroupName)
else:
arcpy.AddWarning("The map didn't fully load, try refreshing the plugin.")
return
print(layerGroup)
print("layer added")
layerGroup.name = newGroupName
print(newGroupName)
if app == "QGIS" or app == "ArcGIS": check: Callable[[Base], bool] = lambda base: isinstance(base, Layer) or isinstance(base, VectorLayer) or isinstance(base, RasterLayer)
else: check: Callable[[Base], bool] = lambda base: isinstance(base, Base)
@@ -743,21 +761,30 @@ class Speckle:
for item in value:
loopVal(item, name)
#print(item)
pt = None
if item.speckle_type and item.speckle_type.startswith("Objects.Geometry."):
pt, pl = cadLayerToNative(value, name, streamBranch, self.speckleInputs.project)
if pt is not None: print("Layer group created: " + pt.name())
if pl is not None: print("Layer group created: " + pl.name())
break
if item.speckle_type and "Revit" in item.speckle_type and item.speckle_type.startswith("Objects.BuiltElements."):
msh_bool = bimLayerToNative(value, name, streamBranch, self.speckleInputs.project)
#if msh is not None: print("Layer group created: " + msh.name())
break
traverseObject(commitObj, callback, check)
except SpeckleException as e:
print("Receive failed")
except (SpeckleException, GraphQLException) as e:
print("Receive failed: " + str(e))
arcpy.AddError("Receive failed: " + str(e))
return
print("received")
#self.updateParameters(parameters, True)
#self.refresh(parameters)
#__all__ = ["Toolbox", "Speckle"]
'''
#__all__ = ["Toolbox", "Speckle"]
@@ -28,11 +28,19 @@ class speckleInputsClass:
saved_streams: List[Optional[Tuple[StreamWrapper, Stream]]] = []
stream_file_path: str = ""
all_layers: List[arcLayer] = []
clients = []
for acc in accounts:
if acc.isDefault:
account = acc
break
#break
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(
@@ -52,8 +60,9 @@ class speckleInputsClass:
if self.active_map is not None and isinstance(self.active_map, Map): # if project loaded
for layer in self.active_map.listLayers():
#print(layer)
if layer.isFeatureLayer or layer.isRasterLayer: self.all_layers.append(layer) #type: 'arcpy._mp.Layer'
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):
@@ -157,7 +166,7 @@ class toolboxInputsClass:
try:
aprx = ArcGISProject('CURRENT')
self.project = aprx
except: self.project = None; print("Project not found")
except: self.project = None; print("Project not found"); arcpy.AddWarning("Project not found")
self.instances.append(self)
def setProjectStreams(self, wr: StreamWrapper, add = True):
@@ -244,15 +253,21 @@ class toolboxInputsClass:
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)
print(newCrs.ExportToWkt())
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.AddWarning("Custom project CRS successfully applied")
arcpy.AddMessage("Custom project CRS successfully applied")
else:
arcpy.AddWarning("Custom CRS could not be created")