Compare commits

...

54 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
KatKatKateryna e409e6d578 Merge pull request #22 from specklesystems/kate/2.9_debugging
Kate/2.9 debugging
2022-10-25 18:30:24 +01:00
KatKatKateryna 98cfc35cbc add conflicted file 2022-10-25 19:29:58 +02:00
KatKatKateryna 1ba8aaaa15 Revert "add conflicted file"
This reverts commit 1f95fbd169.
2022-10-25 19:29:43 +02:00
KatKatKateryna 1f95fbd169 add conflicted file 2022-10-25 19:26:12 +02:00
KatKatKateryna 42f9f8c815 2.9.3; don't duplicate layers of Refresh 2022-10-25 19:10:20 +02:00
KatKatKateryna 019f67ffbc 2.9.2 naming 2022-10-25 18:50:16 +02:00
KatKatKateryna 3f6a21f65e Toolbox naming 2022-10-25 18:03:00 +02:00
KatKatKateryna 3ce685f77a exception for unrefresed UI; defining types properly 2022-10-25 18:00:17 +02:00
KatKatKateryna be8996ebaa file reading failproof; move plugin to PYT, raster mesh sent correctly 2022-10-25 17:49:24 +02:00
KatKatKateryna 03d8a20a44 patch to update whl version; installer to update specklepy 2022-10-25 16:36:03 +02:00
KatKatKateryna 057cbb8cc5 Raster flipped; extra precautions writing txt file; installer instructions updated 2022-10-25 15:38:12 +02:00
KatKatKateryna a68fdbc999 whl package fix, added missing plugin folder 2022-10-25 14:13:13 +02:00
KatKatKateryna be565cd1fa Merge pull request #21 from specklesystems/kate/2.9_debugging
Kate/2.9 debugging
2022-10-25 12:29:09 +01:00
KatKatKateryna ebde9a77ca Multiand raster support; multipart polygons and polylines; UI classes separated and speed up 2022-10-25 13:24:13 +02:00
KatKatKateryna 0d2e62786d resolved major failing issues; sending curved geometry; receiving multipart multipolygons; todo: receive raster in proper SR 2022-10-24 22:55:38 +02:00
KatKatKateryna f6b478c978 receiving curved boundaries; sending anything with curved (to segments), Setting custom SR; receiving rasters; receiving multipart polygons 2022-10-21 14:31:12 +02:00
KatKatKateryna c774ea167c raster sending in BW, by the first band 2022-10-19 01:02:32 +02:00
KatKatKateryna d9f3bfe143 Separating UI class; Saved streams (from def acc and from url); reading project rasters 2022-10-18 10:24:17 +02:00
KatKatKateryna 5371788b46 specklepy upgraded; some arc fixes 2022-10-17 08:21:52 +02:00
KatKatKateryna 69b771924a Arcs & polycurves are sent properly 2022-10-11 08:11:14 +02:00
KatKatKateryna 06a30beda5 read receipt; reconstructing arcs; sending and receiving curved polygons (transforming to polylines) 2022-10-07 19:10:36 +02:00
KatKatKateryna 6ec5594d1c dictionary attributes flattened; receiving arcs properly; VectorLayerClass 2022-09-09 00:25:42 +02:00
KatKatKateryna b216fc31b3 metrics app name 2022-09-01 15:34:06 +08:00
KatKatKateryna c73db1f0e3 Merge pull request #11 from specklesystems/kate/development
readme
2022-08-31 06:53:21 +01:00
KatKatKateryna 9364ccc1e0 readme 2022-08-31 13:51:40 +08:00
33 changed files with 3139 additions and 839 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 >>
+4 -4
View File
@@ -25,7 +25,7 @@ What is Speckle? Check our ![YouTube Video Views](https://img.shields.io/youtube
- **GraphQL API:** get what you need anywhere you want it
- **Webhooks:** the base for a automation and next-gen pipelines
- **Built for developers:** we are building Speckle with developers in mind and got tools for every stack
- **Built for the AEC industry:** Speckle connectors are plugins for the most common software used in the industry such as Revit, Rhino, Grasshopper, AutoCAD, Civil 3D, Excel, Unreal Engine, Unity, QGIS (you are here), Blender and more!
- **Built for the AEC industry:** Speckle connectors are plugins for the most common software used in the industry such as Revit, Rhino, Grasshopper, AutoCAD, Civil 3D, Excel, Unreal Engine, Unity, QGIS, ArcGIS (you are here), Blender and more!
### Try Speckle now!
@@ -44,7 +44,7 @@ Give Speckle a try in no time by:
## Repo Structure
This repo contains the QGIS plugin for Speckle 2.0. It is written in `python` and uses our fantastic [Python SDK](https://github.com/specklesystems/speckle-py). The [Speckle Server](https://github.com/specklesystems/Server) is providing all the web-facing functionality and can be found [here](https://github.com/specklesystems/Server).
This repo contains the ArcGIS plugin for Speckle 2.0. It is written in `python` and uses our fantastic [Python SDK](https://github.com/specklesystems/speckle-py). The [Speckle Server](https://github.com/specklesystems/Server) is providing all the web-facing functionality and can be found [here](https://github.com/specklesystems/Server).
> **Try it out!!**
> Although we're still in early development stages, we encourage you to try out the latest stable release.
@@ -52,7 +52,7 @@ This repo contains the QGIS plugin for Speckle 2.0. It is written in `python` an
>
> **What can it do?**
>
> Currently, the plugin allows to send data from a single layer to a Speckle server using one of the accounts configured on your computer. It will extract all the features of that layer along side their properties and, when possible, geometry too.
> Currently, the plugin allows to receive the data from Speckle and send data from a AcrGIS Pro layers to a Speckle server using one of the accounts configured on your computer. It will extract all the features of that layer along side their properties.
> The following geometry types are supported for now:
>
> - Point
@@ -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
+39 -3
View File
@@ -4,16 +4,52 @@ import sys
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()
lines.insert(12, f'#define AppVersion "{tag.split("-")[0]}"\n')
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 ")
file.close()
with open(setup_whl_file, "r") as file:
lines = file.readlines()
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()
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():
+14 -6
View File
@@ -1,27 +1,35 @@
# to build an installer: run cmd from this folder:
# "%PROGRAMFILES%\\ArcGIS\\Pro\\bin\\Python\\envs\\arcgispro-py3\\python.exe" C:\\Users\\username\\Documents\\00_Speckle\\GitHub\\speckle-arcgis\\setup.py sdist bdist_wheel
# 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) 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
import os
from setuptools import setup
# https://pro.arcgis.com/en/pro-app/2.8/arcpy/geoprocessing_and_python/distributing-python-modules.htm
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
setup(name='speckle_toolbox',
version='0.1',
author='SpeckleSystems',
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',
setup_requires=['wheel'],
packages=['speckle_toolbox'],
package_data={'speckle_toolbox':['esri/toolboxes/*',
package_data={'speckle_toolbox':[
'esri/arcpy/*',
'esri/help/gp/*', 'esri/help/gp/toolboxes/*', 'esri/help/gp/messages/*',
'esri/toolboxes/*','esri/toolboxes/speckle/*',
'esri/toolboxes/speckle/converter/*', 'esri/toolboxes/speckle/converter/geometry/*', 'esri/toolboxes/speckle/converter/layers/*',
'esri/toolboxes/speckle/plugin_utils/*']
'esri/toolboxes/speckle/plugin_utils/*',
'esri/toolboxes/speckle/ui/*']
},
)
+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" )
Binary file not shown.
@@ -1,73 +0,0 @@
# MANUAL INSTALLATION:
# 1. enter correct path to your new environemnt in line 10
# 2. enter the location of 'manual_toolbox_install.py' 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:\\...\\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:\\ ...\\custom_environment_name\\python.exe"
def installToolbox(newExec: str):
print("Installing Speckle Toolbox")
whl_file = os.path.join(os.path.dirname(__file__), "foo-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)
+3 -3
View File
@@ -14,12 +14,12 @@ def subprocess_call(*args, **kwargs):
startupinfo.dwFlags = subprocess.CREATE_NEW_CONSOLE | subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
kwargs['startupinfo'] = startupinfo
print("start")
#print("start")
#print(*args)
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
+17 -25
View File
@@ -6,43 +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-0.1-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-py3-speckle\\python.exe" -m pip uninstall C:\\Users\\username\\Downloads\\speckle-arcgis\\foo-0.1-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.7.4"
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}"])
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 specklepy to {pkgVersion}")
# pip.main(['install', "specklepy==2.7.4"])
print(f"Attempting to update {pkgName} to {pkgVersion}")
result = subprocess_call(
[
pythonExec,
@@ -54,16 +45,17 @@ def installDependencies(pythonExec: str):
]
)
if result == True:
print("specklepy 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 -1
View File
@@ -1 +1 @@
from speckle.speckle_arcgis import *
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>20220826</ModDate><ModTime>150722</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
@@ -1,23 +1,28 @@
from regex import F
from specklepy.objects import Base
from specklepy.objects.geometry import Line, Mesh, Point, Polyline, Curve, Arc, Circle, Polycurve
from specklepy.objects.geometry import Line, Mesh, Point, Polyline, Curve, Arc, Circle, Polycurve, Ellipse
import arcpy
from typing import Any, List, Union, Sequence
from speckle.converter.geometry.polygon import polygonToNative, polygonToSpeckle
from speckle.converter.geometry.polyline import arcToNative, circleToNative, curveToNative, lineToNative, polycurveToNative, polylineFromVerticesToSpeckle, polylineToNative, polylineToSpeckle
from speckle.converter.geometry.polygon import polygonToNative, polygonToSpeckle, multiPolygonToSpeckle
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]:
"""Converts the provided layer feature to Speckle objects"""
print("___convertToSpeckle____________")
geom = feature
#print(geom.isMultipart) # e.g. False
print(geom.isMultipart) # e.g. False
print(geom.partCount)
geomMultiType = geom.isMultipart
hasCurves = feature.hasCurves
# feature is <geoprocessing describe geometry object object at 0x000002A75D6A4BD0>
#print(featureType) # e.g. Simple
#print(geomType) # e.g. Polygon
#geomSingleType = (featureType=="Simple") # Simple,SimpleJunction,SimpleJunction,ComplexEdge,Annotation,CoverageAnnotation,Dimension,RasterCatalogItem
@@ -26,9 +31,16 @@ def convertToSpeckle(feature, layer, geomType, featureType) -> Union[Base, Seque
for pt in geom:
return pointToSpeckle(pt, feature, layer)
elif geomType == "Polyline":
return polylineToSpeckle(geom, feature, layer, geomMultiType)
#if geom.hasCurves:
# geom, feature = curvesToSegments(geom, feature, layer, geomMultiType)
# geomMultiType = geom.isMultipart
# return polylineToSpeckle(geom, feature, layer, geomMultiType)
#else:
if geom.partCount > 1: return multiPolylineToSpeckle(geom, feature, layer, geomMultiType)
else: return polylineToSpeckle(geom, feature, layer, geomMultiType)
elif geomType == "Polygon":
return polygonToSpeckle(geom, feature, layer, geomMultiType)
if geom.partCount > 1: return multiPolygonToSpeckle(geom, feature, layer, geomMultiType)
else: return polygonToSpeckle(geom, feature, layer, geomMultiType)
elif geomType == "Multipoint":
return multiPointToSpeckle(geom, feature, layer, geomMultiType)
else:
@@ -48,6 +60,7 @@ def convertToNative(base: Base, sr: arcpy.SpatialReference) -> Union[Any, None]:
(Curve, curveToNative),
(Arc, arcToNative),
(Circle, circleToNative),
(Ellipse, ellipseToNative),
#(Mesh, meshToNative),
(Polycurve, polycurveToNative),
(Base, polygonToNative), # temporary solution for polygons (Speckle has no type Polygon yet)
@@ -77,33 +90,75 @@ def multiPolylineToNative(items: List[Polyline], sr: arcpy.SpatialReference):
print("_______Drawing Multipolylines____")
#print(items)
poly = None
full_array_list = []
for item in items: # will be 1 item
pointsSpeckle = []
try: pointsSpeckle = item.as_points()
except: continue
pts = [pointToCoord(pt) for pt in pointsSpeckle]
if item.closed is True:
pts.append( pointToCoord(item.as_points()[0]) )
arr = [arcpy.Point(*coords) for coords in pts]
full_array_list.append(arr)
poly = arcpy.Polyline( arcpy.Array(full_array_list), sr, has_z=True )
return poly
def multiPolygonToNative(items: List[Base], sr: arcpy.SpatialReference): #TODO fix multi features
print("_______Drawing Multipolygons____")
#print(items)
full_array_list = []
for item in items: # will be 1 item
#print(item)
pts = [pointToCoord(pt) for pt in item["boundary"].as_points()]
#pts = [pointToCoord(pt) for pt in item["boundary"].as_points()]
pointsSpeckle = []
if isinstance(item["boundary"], Circle) or isinstance(item["boundary"], Arc):
pointsSpeckle = speckleArcCircleToPoints(item["boundary"])
elif isinstance(item["boundary"], Polycurve):
pointsSpeckle = specklePolycurveToPoints(item["boundary"])
elif isinstance(item["boundary"], Line): pass
else:
try: pointsSpeckle = item["boundary"].as_points()
except: pass # if Line
pts = [pointToCoord(pt) for pt in pointsSpeckle]
print(pts)
outer_arr = [arcpy.Point(*coords) for coords in pts]
outer_arr.append(outer_arr[0])
list_of_arrs = []
geomPart = []
try:
for void in item["voids"]:
#print(void)
pts = [pointToCoord(pt) for pt in void.as_points()]
#print(pts)
#pts = [pointToCoord(pt) for pt in void.as_points()]
pointsSpeckle = []
if isinstance(void, Circle) or isinstance(void, Arc):
pointsSpeckle = speckleArcCircleToPoints(void)
elif isinstance(void, Polycurve):
pointsSpeckle = specklePolycurveToPoints(void)
elif isinstance(void, Line): pass
else:
try: pointsSpeckle = void.as_points()
except: pass # if Line
pts = [pointToCoord(pt) for pt in pointsSpeckle]
inner_arr = [arcpy.Point(*coords) for coords in pts]
inner_arr.append(inner_arr[0])
list_of_arrs.append(arcpy.Array(inner_arr))
geomPart.append(arcpy.Array(inner_arr))
except:pass
list_of_arrs.insert(0, arcpy.Array(outer_arr))
array = arcpy.Array(list_of_arrs)
polygon = arcpy.Polygon(array, sr, has_z=True)
geomPart.insert(0, arcpy.Array(outer_arr))
full_array_list.extend(geomPart) # outlines are written one by one, with no separation to "parts"
geomPartArray = arcpy.Array(full_array_list)
polygon = arcpy.Polygon(geomPartArray, sr, has_z=True)
print(polygon)
return polygon
def convertToNativeMulti(items: List[Base], sr: arcpy.SpatialReference):
@@ -113,5 +168,8 @@ def convertToNativeMulti(items: List[Base], sr: arcpy.SpatialReference):
return multiPointToNative(items, sr)
elif isinstance(first, Line) or isinstance(first, Polyline):
return multiPolylineToNative(items, sr)
elif first["boundary"] is not None and first["voids"] is not None:
return multiPolygonToNative(items, sr)
elif isinstance(first, Base):
try:
if first["boundary"] is not None and first["voids"] is not None:
return multiPolygonToNative(items, sr)
except: return 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"
@@ -38,33 +38,39 @@ def pointToSpeckle(pt, feature, layer):
specklePoint.y = y
specklePoint.z = z
'''
col = featureColorfromNativeRenderer(feature, layer)
specklePoint['displayStyle'] = {}
specklePoint['displayStyle']['color'] = col
if feature is not None and layer is not None: # can be if it's a point from raster layer
col = featureColorfromNativeRenderer(feature, layer)
specklePoint['displayStyle'] = {}
specklePoint['displayStyle']['color'] = col
'''
#print(specklePoint)
return specklePoint
def pointToNative(pt: Point, sr: arcpy.SpatialReference) -> arcpy.PointGeometry:
"""Converts a Speckle Point to QgsPoint"""
print("___pointToNative__")
#print("___pointToNative__")
#print(pt)
pt = scalePointToNative(pt, pt.units)
geom = arcpy.PointGeometry(arcpy.Point(pt.x, pt.y, pt.z), sr, has_z = True)
#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):
if len(coords) == 2: coords.append(0)
return coords
@@ -1,79 +1,93 @@
from typing import Sequence
import arcpy
import json
import json
from arcpy.arcobjects.arcobjects import SpatialReference
from specklepy.objects import Base
from specklepy.objects.geometry import Point
from specklepy.objects.geometry import Point, Arc, Circle, Polycurve, Polyline, Line
from speckle.converter.geometry.mesh import rasterToMesh
from speckle.converter.geometry.point import pointToCoord
from speckle.converter.geometry.polyline import polylineFromVerticesToSpeckle, circleToSpeckle
from speckle.converter.geometry.point import pointToCoord, pointToNative
from speckle.converter.geometry.polyline import (polylineFromVerticesToSpeckle,
circleToSpeckle,
speckleArcCircleToPoints,
curveToSpeckle,
specklePolycurveToPoints
)
import math
from panda3d.core import Triangulator
def multiPolygonToSpeckle(geom, feature, layer, multiType: bool):
print("___MultiPolygon to Speckle____")
polygon = []
#print(enumerate(geom.getPart())) # this method ignores curvature and voids
#print(json.loads(geom.JSON))
#js = json.loads(geom.JSON)['rings']
#https://desktop.arcgis.com/en/arcmap/latest/analyze/python/reading-geometries.htm
for i,x in enumerate(feature): # [[x,x,x]
print("Part # " + str(i+1))
print(x)
boundaryFinished = 0
arrBoundary = []
arrInnerRings = []
for ptn in x: # arcpy.Point
if ptn is None:
boundaryFinished += 1
arrInnerRings.append([]) # start of new Inner Ring
elif boundaryFinished == 0 and ptn is not None:
arrBoundary.append(ptn)
elif boundaryFinished == 1 and ptn is not None:
arrInnerRings[len(arrInnerRings)-1].append(ptn)
full_arr = [arrBoundary] + arrInnerRings
print(full_arr)
poly = arcpy.Polygon(arcpy.Array(full_arr), arcpy.Describe(layer.dataSource).SpatialReference, has_z = True)
print(poly) #<geoprocessing describe geometry object object at 0x000002B2D3E338D0>
polygon.append(polygonToSpeckle(poly, feature, layer, poly.isMultipart))
return polygon
def polygonToSpeckle(geom, feature, layer, multiType: bool):
"""Converts a Polygon to Speckle"""
#try:
print("___Polygon to Speckle____")
print(geom)
polygon = Base(units = "m")
pointList = []
voidPointList = []
voids = []
boundary = None
data = arcpy.Describe(layer.dataSource)
sr = data.spatialReference
print(multiType)
partsBoundaries = []
partsVoids = []
if geom.hasCurves:
# geometry SHAPE@ tokens: https://pro.arcgis.com/en/pro-app/latest/arcpy/get-started/reading-geometries.htm
print(geom.JSON)
# look for "curvePaths" or "curveRings"[[ (startPt, {arcs, beziers etc}, optional(endPt))],[],...], "rings"
# examples: https://developers.arcgis.com/documentation/common-data-types/geometry-objects.htm
# e.g. {"hasZ":true,"curveRings":[[[631307.05960000027,5803698.4477999993,0],{"a":[[631307.05960000027,5803698.4477999993,0],[631307.05960000027,5803414.92656173],0,1]}]],"spatialReference":{"wkid":32631,"latestWkid":32631}}
# b - bezier curve (endPt, controlPts)
# a - elliptical arc (endPt, centralPt)
# c - circular arc (endPt, throughPt)
#else: # no curves
if multiType is False: # Multipolygon
if geom.hasCurves:
print("has curves")
# geometry SHAPE@ tokens: https://pro.arcgis.com/en/pro-app/latest/arcpy/get-started/reading-geometries.htm
print(geom.JSON)
boundary = curveToSpeckle(geom, "Polygon", feature, layer)
else:
print("no curves")
for p in geom:
for pt in p:
if pt != None: pointList.append(pt)
boundary = polylineFromVerticesToSpeckle(pointList, True, feature, layer)
partsBoundaries.append(boundary)
partsVoids.append([])
#startPtCoords = geom.JSON.curveRings[0][0]
r'''
segments = []
for key, val in json.loads(geom.JSON).items():
if key == "curveRings":
for segm in val:
print(segm)
segmStartCoord = segm[0]
print(segmStartCoord)
segmData = segm[1]
for key2, val2 in segmData.items():
if key2 == "a":
segmToCoord = val2[0] # elliptical arc
segmCenter = val2[1]
print(segmToCoord)
print(segmCenter)
if segmStartCoord == segmToCoord:
print("full circle")
boundary = circleToSpeckle(segmCenter, segmToCoord, layer)
try:
segmToCoord = segm[1].c # circular arc
except:
try:
segmToCoord = segm[1].b # bezier curve
except: pass
segmEndCoord = None
if len(segm)>2:
segmEndCoord = segm[2]
'''
if multiType is False:
print("single type")
for p in geom:
for pt in p:
if pt != None: pointList.append(pt)
boundary = polylineFromVerticesToSpeckle(pointList, True, feature, layer)
else:
print("multi type")
for i, p in enumerate(geom):
print(p)
for pt in p:
#print(pt) # 284394.58100903 5710688.11602606 NaN NaN
if pt == None and boundary == None: # first break
@@ -90,24 +104,39 @@ def polygonToSpeckle(geom, feature, layer, multiType: bool):
void = polylineFromVerticesToSpeckle(pointList, True, feature, layer)
voids.append(void)
#partsBoundaries.append(boundary)
#partsVoids.append(local_voids)
if boundary is None: return None
polygon.boundary = boundary
polygon.voids = voids
polygon.displayValue = [ boundary ] + voids
#print(boundary)
############# mesh
vertices = []
polyBorder = []
total_vertices = 0
polyBorder = boundary.as_points()
if isinstance(boundary, Circle) or isinstance(boundary, Arc):
polyBorder = speckleArcCircleToPoints(boundary)
elif isinstance(boundary, Polycurve):
polyBorder = specklePolycurveToPoints(boundary)
#polygon.boundary.displayValue.closed = True
elif isinstance(boundary, Line): pass
elif isinstance(boundary, Polyline):
try: polyBorder = boundary.as_points()
except: pass # if Line
#print(polyBorder)
if len(polyBorder)>2:
if len(polyBorder)>2: # at least 3 points
print("make meshes from polygons")
if len(voids) == 0: # if there is a mesh with no voids
for pt in polyBorder:
x = pt.x
y = pt.y
z = 0 if math.isnan(pt.z) else pt.z
if isinstance(pt, Point): pt = pointToNative(pt, sr).getPart() # SR unknown
x = pt.X
y = pt.Y
z = 0 if math.isnan(pt.Z) else pt.Z
vertices.extend([x, y, z])
total_vertices += 1
#print(vertices)
@@ -131,15 +160,34 @@ def polygonToSpeckle(geom, feature, layer, multiType: bool):
trianglator.addPolygonVertex(trianglator.addVertex(pt.x, pt.y))
vertices.extend([pt.x, pt.y, pt.z])
#trianglator.addPolygonVertex(trianglator.addVertex((pt.x+pt2.x)/4*3, (pt.y+pt2.y)/4*3))
#vertices.extend([(pt.x+pt2.x)/4*3, (pt.y+pt2.y)/4*3, (pt.z+pt2.z)/4*3])
trianglator.addPolygonVertex(trianglator.addVertex((pt.x+pt2.x)/2, (pt.y+pt2.y)/2))
vertices.extend([(pt.x+pt2.x)/2, (pt.y+pt2.y)/2, (pt.z+pt2.z)/2])
#trianglator.addPolygonVertex(trianglator.addVertex((pt.x+pt2.x)/4, (pt.y+pt2.y)/4))
#vertices.extend([(pt.x+pt2.x)/4, (pt.y+pt2.y)/4, (pt.z+pt2.z)/4])
total_vertices += 2
pt_count += 1
#add void points
for i in range(len(voids)):
trianglator.beginHole()
pts = voids[i].as_points()
pts = []
if isinstance(voids[i], Circle) or isinstance(voids[i], Arc):
pts = speckleArcCircleToPoints(voids[i])
elif isinstance(voids[i], Polycurve):
pts = specklePolycurveToPoints(voids[i])
elif isinstance(voids[i], Line): pass
else:
try: pts = voids[i].as_points()
except: pass # if Line
#pts = voids[i].as_points()
for pt in pts:
trianglator.addHoleVertex(trianglator.addVertex(pt.x, pt.y))
vertices.extend([pt.x, pt.y, pt.z])
@@ -168,21 +216,44 @@ def polygonToNative(poly: Base, sr: arcpy.SpatialReference) -> arcpy.Polygon:
Each being a Speckle Polyline and List of polylines respectively."""
print("_______Drawing polygons____")
pts = [pointToCoord(pt) for pt in poly["boundary"].as_points()]
#pts = [pointToCoord(pt) for pt in poly["boundary"].as_points()]
pointsSpeckle = []
if isinstance(poly["boundary"], Circle) or isinstance(poly["boundary"], Arc):
pointsSpeckle = speckleArcCircleToPoints(poly["boundary"])
elif isinstance(poly["boundary"], Polycurve):
pointsSpeckle = specklePolycurveToPoints(poly["boundary"])
elif isinstance(poly["boundary"], Line): pass
else:
try: pointsSpeckle = poly["boundary"].as_points()
except: pass # if Line
pts = [pointToCoord(pt) for pt in pointsSpeckle]
print(pts)
outer_arr = [arcpy.Point(*coords) for coords in pts]
outer_arr.append(outer_arr[0])
list_of_arrs = []
geomPart = []
try:
for void in poly["voids"]:
#print(void)
pts = [pointToCoord(pt) for pt in void.as_points()]
#print(pts)
#pts = [pointToCoord(pt) for pt in void.as_points()]
pointsSpeckle = []
if isinstance(void, Circle) or isinstance(void, Arc):
pointsSpeckle = speckleArcCircleToPoints(void)
elif isinstance(void, Polycurve):
pointsSpeckle = specklePolycurveToPoints(void)
elif isinstance(void, Line): pass
else:
try: pointsSpeckle = void.as_points()
except: pass # if Line
pts = [pointToCoord(pt) for pt in pointsSpeckle]
inner_arr = [arcpy.Point(*coords) for coords in pts]
inner_arr.append(inner_arr[0])
list_of_arrs.append(arcpy.Array(inner_arr))
geomPart.append(arcpy.Array(inner_arr))
except:pass
list_of_arrs.insert(0, outer_arr)
array = arcpy.Array(list_of_arrs)
polygon = arcpy.Polygon(array, sr, has_z=True)
geomPart.insert(0, outer_arr)
geomPartArray = arcpy.Array(geomPart)
polygon = arcpy.Polygon(geomPartArray, sr, has_z=True)
return polygon
@@ -1,14 +1,17 @@
from math import atan, cos, sin
import math
from typing import List, Union
from specklepy.objects.geometry import Point, Line, Polyline, Curve, Arc, Circle, Polycurve, Plane, Interval
import json
from typing import List, Union, Tuple
from specklepy.objects import Base
from specklepy.objects.geometry import Box, Vector, Point, Line, Polyline, Curve, Ellipse, Arc, Circle, Polycurve, Plane, Interval
import arcpy
import numpy as np
from speckle.converter.geometry.point import pointToCoord, pointToNative, pointToSpeckle
from speckle.converter.geometry.point import pointToCoord, pointToSpeckle, addZtoPoint
from speckle.converter.layers.utils import get_scale_factor
def circleToSpeckle(center, point, layer):
def circleToSpeckle(center, point):
print("___Circle to Speckle____")
rad = math.sqrt(math.pow((center[0] - point[0]),2) + math.pow((center[1] - point[1]),2) )
#print(rad)
@@ -21,32 +24,60 @@ def circleToSpeckle(center, point, layer):
args = [0] + [rad] + domain + plane + [units]
#print(args)
c = Circle().from_list(args)
c = Circle.from_list(args)
c.plane.origin.units = "m"
c.units = "m"
#print(c)
return c
def multiPolylineToSpeckle(geom, feature, layer, multiType: bool):
print("___MultiPolyline to Speckle____")
polyline = []
print(enumerate(geom.getPart()))
for i,x in enumerate(geom.getPart()):
poly = arcpy.Polyline(x, arcpy.Describe(layer.dataSource).SpatialReference, has_z = True)
print(poly)
polyline.append(polylineToSpeckle(poly, feature, layer, poly.isMultipart))
return polyline
def polylineToSpeckle(geom, feature, layer, multiType: bool):
print("___Polyline to Speckle____")
polyline = None
pointList = []
#print(geom.hasCurves)
print(geom.hasCurves)
if multiType is False:
for p in geom:
for pt in p:
if pt != None: pointList.append(pt)#; print(pt.Z)
closed = False
if pointList[0] == pointList[len(pointList)-1]:
closed = True
pointList = pointList[:-1]
polyline = polylineFromVerticesToSpeckle(pointList, closed, feature, layer)
if geom.hasCurves:
print("has curves")
# geometry SHAPE@ tokens: https://pro.arcgis.com/en/pro-app/latest/arcpy/get-started/reading-geometries.htm
print(geom.JSON)
polyline = curveToSpeckle(geom, "Polyline", feature, layer)
else:
for p in geom:
for pt in p:
if pt != None: pointList.append(pt)#; print(pt.Z)
closed = False
if pointList[0] == pointList[len(pointList)-1]:
closed = True
pointList = pointList[:-1]
polyline = polylineFromVerticesToSpeckle(pointList, closed, feature, layer)
return polyline
def polylineFromVerticesToSpeckle(vertices, closed, feature, layer):
def polylineFromVerticesToSpeckle(vertices: List[Point], closed: bool, feature, layer) -> Polyline:
"""Converts a Polyline to Speckle"""
print("___Polyline from vertices to Speckle____")
if isinstance(vertices, list):
if len(vertices) > 0 and isinstance(vertices[0], Point):
specklePts = vertices
else: specklePts = [pointToSpeckle(pt, feature, layer) for pt in vertices] #breaks unexplainably
#elif isinstance(vertices, QgsVertexIterator):
# specklePts = [pointToSpeckle(pt, feature, layer) for pt in vertices]
else: return None
specklePts = []
for pt in vertices:
newPt = pointToSpeckle(pt, feature, layer)
@@ -58,17 +89,264 @@ def polylineFromVerticesToSpeckle(vertices, closed, feature, layer):
polyline.closed = closed
polyline.units = specklePts[0].units
for i, point in enumerate(specklePts):
if closed and i == len(specklePts) - 1:
continue
if closed and i == len(specklePts) - 1 and specklePts[0] == point:
continue # if we consider the last pt, do not add is coincides with the first (and type is Closed)
polyline.value.extend([point.x, point.y, point.z])
return polyline
def arc3ptToSpeckle(p0: List, p1: List, p2: List, feature, layer) -> Arc:
print("____arc 3pt to Speckle___")
p0 = addZtoPoint(p0)
p1 = addZtoPoint(p1)
p2 = addZtoPoint(p2)
arc = Arc()
arc.startPoint = pointToSpeckle(arcpy.Point(*p0), feature, layer)
arc.midPoint = pointToSpeckle(arcpy.Point(*p1), feature, layer)
arc.endPoint = pointToSpeckle(arcpy.Point(*p2), feature, layer)
center, radius = getArcCenter(Point.from_list(p0), Point.from_list(p1), Point.from_list(p2))
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
arc.plane.normal = getArcNormal(arc, arc.midPoint)
#arc.angleRadians = abs(angle1 + angle2)
#print(arc.angleRadians)
#col = featureColorfromNativeRenderer(feature, layer)
#arc['displayStyle'] = {}
#arc['displayStyle']['color'] = col
return arc
def curveBezierToSpeckle(segmStartCoord, segmEndCoord, knots, feature, layer):
print("____bezier curve to Speckle____")
degree = 3
points = [
tuple(knots[0]), tuple(segmStartCoord), tuple(knots[1]), tuple(segmEndCoord)
] #[segmStartCoord, *coords]
print(points)
num_points = len(points) #2
knot_count = num_points + degree - 1 #4
knots = [0] * knot_count
print(knots)
for i in range(1, len(knots)):
knots[i] = i // 3
print(knots[i])
length = 1 #spline.calc_length()
domain = Interval(start=0, end=length, totalChildrenCount=0)
points = [tuple(pt) for pt in points]
curve = Curve(
degree = degree,
closed = False,
periodic= True if (segmStartCoord == segmEndCoord) else False,
points= list(sum(points, ())), # magic (flatten list of tuples)
weights=[1] * num_points,
knots=knots,
rational=False,
area=0,
volume=0,
length=length,
domain=domain,
units="m",
bbox=Box(area=0.0, volume=0.0),
)
print(curve)
return curve
def curveToSpeckle(geom, geomType, feature, layer) -> Union[Circle, Arc, Polyline, Polycurve]:
print("____curve to Speckle____")
print(geomType)
# look for "curvePaths" or "curveRings"[[ (startPt, {arcs, beziers etc}, optional(endPt))],[],...], "rings"
# examples: https://developers.arcgis.com/documentation/common-data-types/geometry-objects.htm
# e.g. {"hasZ":true,
# "curveRings":[[[631307.05960000027,5803698.4477999993,0],{"a":[[631307.05960000027,5803698.4477999993,0],[631307.05960000027,5803414.92656173],0,1]}]],
# "spatialReference":{"wkid":32631,"latestWkid":32631}}
# b - bezier curve (endPt, controlPts)
# 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(units = "m")
if geomType == "Polyline": boundary.closed = False
else: boundary.closed = True
segments = []
for key, val in json.loads(geom.JSON).items():
print(key)
if key == "curveRings" or key == "curvePaths":
#boundary.closed = True
includesLines = 0
for segm in val: # segm: List
print(segm) #e.g. [[631307.05960000027,5803698.4477999993,0], {"a":[[631307.05960000027,5803698.4477999993,0],[631307.05960000027,5803414.92656173],0,1]}]
segmStartCoord: List = addZtoPoint(segm[0])
# go through all elements (points, a, c, ...)
for k in range(1, len(segm)):
# e.g. one from the list: "curveRings":[[[631750.87200000044,5803159.6126000006,0],
# {"c":[[632429.8348000003,5803507.1132999994,0],[631988.22772700491,5803532.9008129537]]},
# {"c":[[632590.21970000025,5803127.5355999991,0],[633018.51899157302,5803532.1801161235]]},
# [631750.87200000044,5803159.6126000006,0]]]
# if previous segments exist
if len(segments) > 0:
segmOldData = segm[k-1]
if isinstance(segmOldData, dict): # get "end point" of previous segment
for key3, val3 in segmOldData.items():
segmStartCoord: List = addZtoPoint(val2[0])
elif isinstance(segmOldData, list) and isinstance(segmOldData[0], float):
segmStartCoord: List = segmOldData
segmStartCoord = addZtoPoint(segmStartCoord)
if isinstance(segm[k], dict):
for key2, val2 in segm[k].items():
if key2 == "a": # elliptical arc (endPt, centralPt)
# e.g. {'a': [[633883.1035000002, 5802972.5812, 0], [634028.3379278888, 5802908.342895357], 0, 1, 1.1543577096027686, 473.59966687227444, 0.33531864204900685]}
segmEndCoord = addZtoPoint(val2[0]) # [631307.05960000027,5803698.4477999993,0]
segmCenter = addZtoPoint(val2[1]) # [631307.05960000027,5803414.92656173]
if segmStartCoord == segmEndCoord:
if len(val2) == 4:
print("full circle")
#boundary.closed = True
segmentLocal = circleToSpeckle(segmCenter, segmEndCoord)
segments.append(segmentLocal)
lastPt = segmEndCoord
print("segmentLocal:")
print(segmentLocal)
print(segmStartCoord)
print(segmEndCoord)
else: # ellipse
arcpy.AddMessage("SpeckleWarning: ellipse geometry not supported yet")
segments = []
break
else: # elliptical curve
arcpy.AddMessage("SpeckleWarning: ellipse geometry not supported yet")
segments = []
break
if key2 == "c": # circular arc (endPt, throughPt)
segmEndCoord: List = addZtoPoint(val2[0]) # [633718.26040000003,5803496.4210000001,0]
segmThrough: List = addZtoPoint(val2[1]) # [633337.7576497585, 5803431.999702678]
segmentLocal = arc3ptToSpeckle(segmStartCoord, segmThrough, segmEndCoord, feature, layer)
segments.append(segmentLocal)
print("segmentLocal:")
print(segmentLocal)
print(segmStartCoord)
print(segmEndCoord)
lastPt = segmEndCoord
if key2 == "b": # bezier curve (endPt, controlPts)
arcpy.AddMessage("SpeckleWarning: bezier curve geometry not supported yet")
segments = []
break
r'''
segmEndCoord: List = addZtoPoint(val2[0]) # [633718.26040000003,5803496.4210000001,0]
#segmThrough: List = val2[1] # [633337.7576497585, 5803431.999702678]
coords = val2[1:]
segmentLocal = curveBezierToSpeckle(segmStartCoord, segmEndCoord, coords, feature, layer)
segments.append(segmentLocal)
print("segmentLocal:")
print(segmentLocal)
print(segmStartCoord)
print(segmEndCoord)
lastPt = segmEndCoord
'''
elif isinstance(segm[k], list) and isinstance(segm[k][0], float): # add line to point
print("add line")
segm[k] = addZtoPoint(segm[k])
segmentLocal = lineFrom2pt(segmStartCoord, segm[k])
includesLines = 1
segments.append(segmentLocal)
lastPt = segm[k]
print("segmentLocal:")
print(segmentLocal)
print(segmentLocal.start)
print(segmentLocal.end)
# for the last point
if k == len(segm)-1 and isinstance(segm[k], list):
print("last element is a point (adding line)")
lastPt = addZtoPoint(lastPt)
if lastPt != segm[0]:
#segmentLocal = lineFrom2pt(lastPt, segm[0])
#segments.append(segmentLocal)
#includesLines = 1
#print("segmentLocal:")
#print(segmentLocal)
#print(segmentLocal.start)
#print(segmentLocal.end)
boundary.closed = True
#pts = speckleArcCircleToPoints(segmentLocal)
#pts.append(segm[k])
#arcgisPts = [arcpy.Point(pt[0], pt[1], pt[2]) for pt in pts]
#segmentLocal = polylineFromVerticesToSpeckle(arcgisPts, True, feature, layer)
boundary.segments = segments
print(segments)
if len(segments) == 1:
boundary = segments[0]
#if isinstance(boundary, Arc) or isinstance(boundary, Circle):
# boundary.displayValue = Polyline.from_points(speckleArcCircleToPoints(boundary))
elif len(segments) > 1: # and includesLines == 0:
#boundary.displayValue = Polyline.from_points(specklePolycurveToPoints(boundary))
pass
#boundary.closed = True
#elif len(segments) > 1 and includesLines == 1:
# print("includes lines!")
# points = specklePolycurveToPoints(boundary)
# boundary = Polyline.from_points(points)
else: return None
#boundary.displayValue = Polyline.from_points(specklePolycurveToPoints(boundary))
print(boundary)
return boundary
def lineFrom2pt(pt1: List[float], pt2: List[float]):
pt1 = addZtoPoint(pt1)
pt2 = addZtoPoint(pt2)
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(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"
return line
def polylineToNative(poly: Polyline, sr: arcpy.SpatialReference) -> arcpy.Polyline:
"""Converts a Speckle Polyline to QgsLineString"""
print("__ convert poly to native __")
pts = [pointToCoord(pt) for pt in poly.as_points()]
if isinstance(poly, Polycurve):
poly = specklePolycurveToPoints(poly)
if isinstance(poly, Arc) or isinstance(poly, Circle):
try: poly = poly["displayValue"]
except: poly = speckleArcCircleToPoints(poly)
if isinstance(poly, list): pts = [pointToCoord(pt) for pt in poly]
else: pts = [pointToCoord(pt) for pt in poly.as_points()]
if poly.closed is True:
pts.append( pointToCoord(poly.as_points()[0]) )
@@ -79,26 +357,29 @@ def polylineToNative(poly: Polyline, sr: arcpy.SpatialReference) -> arcpy.Polyli
def lineToNative(line: Line, sr: arcpy.SpatialReference) -> arcpy.Polyline:
"""Converts a Speckle Line to QgsLineString"""
"""Converts a Speckle Line to Native"""
print("___Line to Native___")
pts = [pointToCoord(pt) for pt in [line.start, line.end]]
line = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in pts]), sr , has_z=True)
return line
def curveToNative(poly: Curve, sr: arcpy.SpatialReference) -> arcpy.Polyline:
"""Converts a Speckle Curve to QgsLineString"""
"""Converts a Speckle Curve to Native"""
display = poly.displayValue
curve = polylineToNative(display, sr)
return curve
def arcToNative(poly: Arc, sr: arcpy.SpatialReference) -> arcpy.Polyline:
"""Converts a Speckle Arc to QgsCircularString"""
arc = arcToNativePoints(poly, sr) #QgsCircularString(pointToNative(poly.startPoint), pointToNative(poly.midPoint), pointToNative(poly.endPoint))
"""Converts a Speckle Arc to Native"""
arc = arcToNativePolyline(poly, sr) #QgsCircularString(pointToNative(poly.startPoint), pointToNative(poly.midPoint), pointToNative(poly.endPoint))
return arc
def ellipseToNative():
return
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
@@ -114,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])
@@ -125,15 +407,15 @@ 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
elif isinstance(segm,Circle): converted = circleToNative(segm, sr) # QgsLineString
elif isinstance(segm,Arc): converted = arcToNativePoints(segm, sr) # QgsLineString
elif isinstance(segm,Arc): converted = arcToNativePolyline(segm, sr) # QgsLineString
else: # either return a part of the curve, of skip this segment and try next
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)
@@ -141,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
@@ -149,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 )
@@ -161,45 +443,158 @@ def polycurveToNative(poly: Polycurve, sr: arcpy.SpatialReference) -> arcpy.Poly
curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True )
return curve
def arcToNativePoints(poly: Arc, sr: arcpy.SpatialReference):
print("__Arc to native__")
points = []
if poly.startPoint.x == poly.plane.origin.x: angle1 = math.pi/2
else: angle1 = atan( abs ((poly.startPoint.y - poly.plane.origin.y) / (poly.startPoint.x - poly.plane.origin.x) )) # between 0 and pi/2
#print(angle1)
if poly.plane.origin.x < poly.startPoint.x and poly.plane.origin.y > poly.startPoint.y: angle1 = 2*math.pi - angle1
if poly.plane.origin.x > poly.startPoint.x and poly.plane.origin.y > poly.startPoint.y: angle1 = math.pi + angle1
if poly.plane.origin.x > poly.startPoint.x and poly.plane.origin.y < poly.startPoint.y: angle1 = math.pi - angle1
print(angle1)
if poly.endPoint.x == poly.plane.origin.x: angle2 = math.pi/2
else: angle2 = atan( abs ((poly.endPoint.y - poly.plane.origin.y) / (poly.endPoint.x - poly.plane.origin.x) )) # between 0 and pi/2
#print(angle2)
if poly.plane.origin.x < poly.endPoint.x and poly.plane.origin.y > poly.endPoint.y: angle2 = 2*math.pi - angle2
if poly.plane.origin.x > poly.endPoint.x and poly.plane.origin.y > poly.endPoint.y: angle2 = math.pi + angle2
if poly.plane.origin.x > poly.endPoint.x and poly.plane.origin.y < poly.endPoint.y: angle2 = math.pi - angle2
print(angle2)
try: interval = math.floor(poly.endAngle - poly.startAngle)
except: interval = math.floor(angle2-angle1)
pointsNum = math.floor( abs(interval)) * 12
if pointsNum <4: pointsNum = 4
points.append(pointToCoord(poly.startPoint))
print(points)
print(interval)
print(pointsNum)
for i in range(1, pointsNum + 1):
k = i/pointsNum # to reset values from 1/10 to 1
if poly.plane.normal.z == 0: normal = 1
else: normal = poly.plane.normal.z
angle = angle1 + k * interval * normal
print(f"k: {str(i)} multiplied: {str(k*interval)} angle: {str(angle1 + k * interval)}")
#print(cos(angle))
pt = Point( x = poly.plane.origin.x + poly.radius * cos(angle), y = poly.plane.origin.y + poly.radius * sin(angle), z = 0)
pt.units = poly.startPoint.units
points.append(pointToCoord(pt))
#print(pointToCoord(pt))
points.append(pointToCoord(poly.endPoint))
def arcToNativePolyline(poly: Union[Arc, Circle], sr: arcpy.SpatialReference):
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 )
return curve
def specklePolycurveToPoints(poly: Polycurve) -> List[Point]:
print("_____Speckle Polycurve to points____")
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)
elif isinstance(segm, Line):
print("Line")
pts: List[Point] = [segm.start, segm.end]
elif isinstance(segm, Polyline):
print("Polyline")
pts: List[Point] = segm.as_points()
points.extend(pts)
return points
def speckleArcCircleToPoints(poly: Union[Arc, Circle]) -> List[Point]:
print("__Arc or Circle to Points___")
points = []
#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)
if isinstance(poly, Circle):
interval = 2*math.pi
range_start = 0
angle1 = 0
else: # if Arc
points.append(poly.startPoint)
range_start = 0
#angle1, angle2 = getArcAngles(poly)
interval, angle1, angle2 = getArcRadianAngle(poly)
interval = abs(angle2 - angle1)
#print(angle1)
#print(angle2)
if (angle1 > angle2 and normal == -1) or (angle2 > angle1 and normal == 1): pass
if angle1 > angle2 and normal == 1: interval = abs( (2*math.pi-angle1) + angle2)
if angle2 > angle1 and normal == -1: interval = abs( (2*math.pi-angle2) + angle1)
#print(interval)
#print(normal)
pointsNum = math.floor( abs(interval)) * 12
if pointsNum <4: pointsNum = 4
for i in range(range_start, pointsNum + 1):
k = i/pointsNum # to reset values from 1/10 to 1
angle = angle1 + k * interval * normal
#print(k)
#print(angle)
pt = Point( x = poly.plane.origin.x + poly.radius * cos(angle), y = poly.plane.origin.y + poly.radius * sin(angle), z = 0)
pt.units = poly.plane.origin.units
points.append(pt)
if isinstance(poly, Arc): points.append(poly.endPoint)
return points
def getArcRadianAngle(arc: Arc) -> List[float]:
interval = None
normal = arc.plane.normal.z
angle1, angle2 = getArcAngles(arc)
if angle1 is None or angle2 is None: return None
interval = abs(angle2 - angle1)
if (angle1 > angle2 and normal == -1) or (angle2 > angle1 and normal == 1): pass
if angle1 > angle2 and normal == 1: interval = abs( (2*math.pi-angle1) + angle2)
if angle2 > angle1 and normal == -1: interval = abs( (2*math.pi-angle2) + angle1)
return interval, angle1, angle2
def getArcAngles(poly: Arc) -> Tuple[float]:
if poly.startPoint.x == poly.plane.origin.x: angle1 = math.pi/2
else: angle1 = atan( abs ((poly.startPoint.y - poly.plane.origin.y) / (poly.startPoint.x - poly.plane.origin.x) )) # between 0 and pi/2
if poly.plane.origin.x < poly.startPoint.x and poly.plane.origin.y > poly.startPoint.y: angle1 = 2*math.pi - angle1
if poly.plane.origin.x > poly.startPoint.x and poly.plane.origin.y > poly.startPoint.y: angle1 = math.pi + angle1
if poly.plane.origin.x > poly.startPoint.x and poly.plane.origin.y < poly.startPoint.y: angle1 = math.pi - angle1
if poly.endPoint.x == poly.plane.origin.x: angle2 = math.pi/2
else: angle2 = atan( abs ((poly.endPoint.y - poly.plane.origin.y) / (poly.endPoint.x - poly.plane.origin.x) )) # between 0 and pi/2
if poly.plane.origin.x < poly.endPoint.x and poly.plane.origin.y > poly.endPoint.y: angle2 = 2*math.pi - angle2
if poly.plane.origin.x > poly.endPoint.x and poly.plane.origin.y > poly.endPoint.y: angle2 = math.pi + angle2
if poly.plane.origin.x > poly.endPoint.x and poly.plane.origin.y < poly.endPoint.y: angle2 = math.pi - angle2
return angle1, angle2
def getArcNormal(poly: Arc, midPt: Point):
print("____getArcNormal___")
angle1, angle2 = getArcAngles(poly)
if midPt.x == poly.plane.origin.x: angle = math.pi/2
else: angle = atan( abs ((midPt.y - poly.plane.origin.y) / (midPt.x - poly.plane.origin.x) )) # between 0 and pi/2
if poly.plane.origin.x < midPt.x and poly.plane.origin.y > midPt.y: angle = 2*math.pi - angle
if poly.plane.origin.x > midPt.x and poly.plane.origin.y > midPt.y: angle = math.pi + angle
if poly.plane.origin.x > midPt.x and poly.plane.origin.y < midPt.y: angle = math.pi - angle
normal = Vector()
normal.x = normal.y = 0
if angle1 > angle > angle2: normal.z = -1
if angle1 > angle2 > angle: normal.z = 1
if angle2 > angle1 > angle: normal.z = -1
if angle > angle1 > angle2: normal.z = 1
if angle2 > angle > angle1: normal.z = 1
if angle > angle2 > angle1: normal.z = -1
print(angle1)
print(angle)
print(angle2)
print(normal)
return normal
def getArcCenter(p1: Point, p2: Point, p3: Point) -> Tuple[List, float]:
#print(p1)
p1 = np.array(p1.to_list())
p2 = np.array(p2.to_list())
p3 = np.array(p3.to_list())
a = np.linalg.norm(p3 - p2)
b = np.linalg.norm(p3 - p1)
c = np.linalg.norm(p2 - p1)
s = (a + b + c) / 2
radius = a*b*c / 4 / np.sqrt(s * (s - a) * (s - b) * (s - c))
b1 = a*a * (b*b + c*c - a*a)
b2 = b*b * (a*a + c*c - b*b)
b3 = c*c * (a*a + b*b - c*c)
center = np.column_stack((p1, p2, p3)).dot(np.hstack((b1, b2, b3)))
center /= b1 + b2 + b3
center = center.tolist()
return center, radius
@@ -4,6 +4,7 @@ from specklepy.objects.base import Base
from speckle.converter.layers.CRS import CRS
class Layer(Base, chunkable={"features": 100}):
"""A GIS Layer"""
@@ -27,14 +28,37 @@ class Layer(Base, chunkable={"features": 100}):
self.geomType = geomType
self.renderer = renderer
class RasterLayer(Base, chunkable={"features": 100}):
"""A GIS Layer"""
class VectorLayer(Base, chunkable={"features": 100}):
"""A GIS Vector Layer"""
def __init__(
self,
name=None,
crs=None,
rasterCrs=None,
name: Optional[str] = None,
crs: Optional[CRS] = None,
datum: Optional[CRS] = None,
features: List[Base] = [],
layerType: str = "None",
geomType: str = "None",
renderer: dict = {},
**kwargs
) -> None:
super().__init__(**kwargs)
self.name = name
self.crs = crs
self.datum = datum
self.type = layerType
self.features = features
self.geomType = geomType
self.renderer = renderer
class RasterLayer(Base, chunkable={"features": 100}):
"""A GIS Raster Layer"""
def __init__(
self,
name: Optional[str] = None,
crs: Optional[CRS] =None,
rasterCrs: Optional[CRS] = None,
features: List[Base] = [],
layerType: str = "None",
geomType: str = "None",
@@ -2,13 +2,15 @@
Contains all Layer related classes and methods.
"""
import os
from typing import Any, List, Union
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, RasterLayer
from speckle.converter.layers.feature import featureToNative, featureToSpeckle, cadFeatureToNative
from speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer
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
@@ -17,10 +19,13 @@ from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
from arcpy.management import (CreateFeatureclass, MakeFeatureLayer,
AddFields, AlterField, DefineProjection )
from speckle.converter.layers.utils import getLayerAttributes
from speckle.converter.layers.utils import getLayerAttributes, newLayerGroupAndName, validate_path
import numpy as np
from speckle.converter.layers.utils import findTransformation
def convertSelectedLayers(all_layers: List[arcpy._mp.Layer], selected_layers: List[str], project: arcpy.mp.ArcGISProject) -> List[Layer]:
def convertSelectedLayers(all_layers: List[arcLayer], selected_layers: List[str], project: ArcGISProject) -> List[Union[VectorLayer,Layer]]:
"""Converts the current selected layers to Speckle"""
print("________Convert Layers_________")
result = []
@@ -32,82 +37,123 @@ def convertSelectedLayers(all_layers: List[arcpy._mp.Layer], selected_layers: Li
break
if layerToSend is not None:
ds = layerToSend.dataSource #file path
if layerToSend.isFeatureLayer:
newBaseLayer = layerToSpeckle(layerToSend, project)
if newBaseLayer is not None: result.append(newBaseLayer)
#if layerToSend.isFeatureLayer:
newBaseLayer = layerToSpeckle(layerToSend, project)
if newBaseLayer is not None: result.append(newBaseLayer)
elif layerToSend.isRasterLayer: pass
'''
if layer.name() in selectedLayerNames:
result.append(layerToSpeckle(layer, projectCRS, project))
'''
#print(result)
print(result)
return result
def layerToSpeckle(layer: arcLayer, project: ArcGISProject) -> Layer: #now the input is QgsVectorLayer instead of qgis._core.QgsLayerTreeLayer
def layerToSpeckle(layer: arcLayer, project: ArcGISProject) -> Union[VectorLayer, RasterLayer]: #now the input is QgsVectorLayer instead of qgis._core.QgsLayerTreeLayer
"""Converts a given QGIS Layer to Speckle"""
print("________Convert Feature Layer_________")
speckleLayer = None
projectCRS = project.activeMap.spatialReference
try: data = arcpy.Describe(layer.dataSource)
except OSError as e: arcpy.AddWarning(str(e.args[0])); return
#print(projectCRS)
#print(projectCRS.name)
crs = CRS(name = projectCRS.name, wkt = projectCRS.exportToString(), units = "m")
layer_geo_crs = None
datum = None
#if data.spatialReference.type == "Projected":
# #layer_geo_crs =
# datum = CRS(name = layer_geo_crs.name, wkt = layer_geo_crs.exportToString(), units = "m")
speckleLayer = Layer()
speckleLayer.type="VectorLayer"
speckleLayer.name = layer.name
speckleLayer.crs = crs
speckleLayer.datum = datum
try: # https://pro.arcgis.com/en/pro-app/2.8/arcpy/get-started/the-spatial-reference-object.htm
layerObjs = []
print(data.datasetType)
if data.datasetType == "FeatureClass": #FeatureClass, ?Table Properties, ?Datasets
# write feature attributes
fieldnames = [field.name for field in data.fields]
#print(layer.longName) # e.g. 17b0b76d13_custom_crs_04dcfaa936\04dcfaa936_Vector_lineGeom
#print(fieldnames) # e.g. ['OBJECTID', 'Shape', 'Shape_Length', 'Speckle_ID', 'number', 'area']
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(rows_shapes) # <da.SearchCursor object at 0x00000172565E6C10>
print("__ start iterating features")
# write feature attributes
for i, features in enumerate(rows_shapes):
print("____Feature # " + str(i+1))
if features[0] == None: continue
#print(features[0].hasCurves)
if features[0].hasCurves: continue
rows_attributes = arcpy.da.SearchCursor(layer.longName, fieldnames)
row_attr = []
for k, attrs in enumerate(rows_attributes):
if i == k: row_attr = attrs; break
#print(features) #(<Polygon object at 0x172592ae8c8[0x17258d2a600]>,)
#print(features[0].pointCount)
#print(features[0].partCount)
if features[0]:
b = featureToSpeckle(fieldnames, row_attr, features[0], projectCRS, project, layer)
layerObjs.append(b)
#print(layerObjs)
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)
#renderer = selectedLayer.renderer()
#layerRenderer = rendererToSpeckle(renderer)
if layer.isFeatureLayer:
print("VECTOR LAYER HERE")
speckleLayer = VectorLayer(units = "m")
speckleLayer.type="VectorLayer"
speckleLayer.name = layerName
speckleLayer.crs = speckleReprojectedCrs
#speckleLayer.datum = datum
try: # https://pro.arcgis.com/en/pro-app/2.8/arcpy/get-started/the-spatial-reference-object.htm
#print(data.datasetType) # FeatureClass
if data.datasetType == "FeatureClass": #FeatureClass, ?Table Properties, ?Datasets
print("__ finish iterating features")
speckleLayer.features=layerObjs
speckleLayer.geomType = data.shapeType
# 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):
except OSError as e:
arcpy.AddWarning(str(e))
return
print("____error Feature # " + str(i+1)) # + " / " + str(sum(1 for _ in enumerate(rows_shapes))))
if features[0] is None: continue
feat = features[0]
#print(feat) # <geoprocessing describe geometry object object at 0x000002A75D6A4BD0>
#print(feat.hasCurves)
#print(feat.partCount)
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 curves detected, createa new feature class, turn to segments and get the same feature but in straigt lines
#print(feat.hasCurves)
if feat.hasCurves:
#f_class_modified = curvedFeatureClassToSegments(layer)
#rows_shapes_modified = arcpy.da.SearchCursor(f_class_modified, "Shape@")
#row_shapes_list_modified = [x for k, x in enumerate(rows_shapes_modified)]
feat = feat.densify("ANGLE", 1000, 0.12)
#print(feat)
b = featureToSpeckle(fieldnames, row_attr, feat, projectCRS, project, layer)
if b is not None: layerObjs.append(b)
print("____End of Feature # " + str(i+1))
print("__ finish iterating features")
speckleLayer.features=layerObjs
speckleLayer.geomType = data.shapeType
if len(speckleLayer.features) == 0: return None
#layerBase.renderer = layerRenderer
#layerBase.applicationId = selectedLayer.id()
except OSError as e:
arcpy.AddWarning(str(e))
return
elif layer.isRasterLayer:
print("RASTER IN DA HOUSE")
print(layer.name) # London_square.tif
print(arcpy.Describe(layer.dataSource)) # <geoprocessing describe data object object at 0x000002507C7F3BB0>
print(arcpy.Describe(layer.dataSource).datasetType) # RasterDataset
b = rasterFeatureToSpeckle(layer, projectCRS, project)
if b is not None: layerObjs.append(b)
speckleLayer = RasterLayer(units = "m", type="RasterLayer")
speckleLayer.name = layerName
speckleLayer.crs = speckleReprojectedCrs
speckleLayer.rasterCrs = layerCRS
speckleLayer.type="RasterLayer"
#speckleLayer.geomType="Raster"
speckleLayer.features = layerObjs
#speckleLayer.renderer = layerRenderer
#speckleLayer.applicationId = selectedLayer.id()
return speckleLayer
def layerToNative(layer: Union[Layer, RasterLayer], streamBranch: str, project: ArcGISProject):
def layerToNative(layer: Union[Layer, VectorLayer, RasterLayer], streamBranch: str, project: ArcGISProject):
if layer.type is None:
# Handle this case
@@ -118,8 +164,186 @@ def layerToNative(layer: Union[Layer, RasterLayer], streamBranch: str, project:
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 = []
geom_polylines = []
geom_polygones = []
@@ -129,7 +353,7 @@ def cadLayerToNative(layerContentList:Base, layerName: str, streamBranch: str, p
#print(geom)
if geom.speckle_type == "Objects.Geometry.Point":
geom_points.append(geom)
if geom.speckle_type == "Objects.Geometry.Line" or geom.speckle_type == "Objects.Geometry.Polyline" or geom.speckle_type == "Objects.Geometry.Curve" or geom.speckle_type == "Objects.Geometry.Arc" or geom.speckle_type == "Objects.Geometry.Circle" or geom.speckle_type == "Objects.Geometry.Polycurve":
if geom.speckle_type == "Objects.Geometry.Line" or geom.speckle_type == "Objects.Geometry.Polyline" or geom.speckle_type == "Objects.Geometry.Curve" or geom.speckle_type == "Objects.Geometry.Arc" or geom.speckle_type == "Objects.Geometry.Circle" or geom.speckle_type == "Objects.Geometry.Ellipse" or geom.speckle_type == "Objects.Geometry.Polycurve":
geom_polylines.append(geom)
if len(geom_points)>0: layer_points = cadVectorLayerToNative(geom_points, layerName, "Points", streamBranch, project)
@@ -138,13 +362,13 @@ def cadLayerToNative(layerContentList:Base, layerName: str, streamBranch: str, p
return [layer_points, layer_polylines]
def cadVectorLayerToNative(geomList, layerName: str, geomType: str, streamBranch: str, project: ArcGISProject):
print("_________CAD vector layer to native_____")
print("02_________CAD 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
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 + "\\" #
@@ -184,11 +408,11 @@ def cadVectorLayerToNative(geomList, layerName: str, geomType: str, streamBranch
#path = r"C:\Users\username\Documents\ArcGIS\Projects\MyProject-test\MyProject-test.gdb\\"
#https://community.esri.com/t5/arcgis-pro-questions/is-it-possible-to-create-a-new-group-layer-with/td-p/1068607
print("_________create feature class (cad)___________________________________")
#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)
#print(f_class)
print(f_class)
#print(geomList)
# get and set Layer attribute fields
@@ -226,8 +450,9 @@ 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
rowValues = []
for feat in fets:
@@ -243,23 +468,20 @@ 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
def vectorLayerToNative(layer: Layer, streamBranch: str, project: ArcGISProject):
def vectorLayerToNative(layer: Union[Layer, VectorLayer], streamBranch: str, project: ArcGISProject):
print("_________Vector Layer to Native_________")
vl = None
layerName = layer.name.replace(" ","_").replace("-","_").replace("(","_").replace(")","_").replace(":","_").replace("\\","_").replace("/","_").replace("\"","_").replace("&","_").replace("@","_").replace("$","_").replace("%","_").replace("^","_")
@@ -271,27 +493,7 @@ def vectorLayerToNative(layer: Layer, streamBranch: str, project: ArcGISProject)
#if not os.path.exists(path): os.makedirs(path)
#print(path)
#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}'
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
newName, layerGroup = newLayerGroupAndName(layerName, streamBranch, project)
# particularly if the layer comes from ArcGIS
geomType = layer.geomType # for ArcGIS: Polygon, Point, Polyline, Multipoint, MultiPatch
@@ -347,8 +549,10 @@ def vectorLayerToNative(layer: Layer, streamBranch: str, project: ArcGISProject)
if new_feat != "" and new_feat!= None: fets.append(new_feat)
print(fets)
if len(fets) == 0: return None
count = 0
rowValues = []
heads = None
for feat in fets:
#print(feat)
try: feat['applicationId']
@@ -405,88 +609,145 @@ def vectorLayerToNative(layer: Layer, streamBranch: str, project: ArcGISProject)
def rasterLayerToNative(layer: RasterLayer, streamBranch: str, project: ArcGISProject):
raster_layer = None
'''
crs = QgsCoordinateReferenceSystem.fromWkt(layer.crs.wkt) #moved up, because CRS of existing layer needs to be rewritten
# try, in case of older version "rasterCrs" will not exist
try: crsRaster = QgsCoordinateReferenceSystem.fromWkt(layer.rasterCrs.wkt) #moved up, because CRS of existing layer needs to be rewritten
except:
crsRaster = crs
logger.logToUser(f"Raster layer {layer.name} might have been sent from the older version of plugin. Try sending it again for more accurate results.", Qgis.Warning)
rasterLayer = None
layerName = layer.name.replace(" ","_").replace("-","_").replace("(","_").replace(")","_").replace(":","_").replace("\\","_").replace("/","_").replace("\"","_").replace("&","_").replace("@","_").replace("$","_").replace("%","_").replace("^","_")
#CREATE A GROUP "received blabla" with sublayers
newGroupName = f'{streamBranch}'
root = QgsProject.instance().layerTreeRoot()
layerGroup = QgsLayerTreeGroup(newGroupName)
print(layerName)
sr = arcpy.SpatialReference(text=layer.crs.wkt)
print(layer.crs.wkt)
active_map = project.activeMap
path = project.filePath.replace("aprx","gdb")
#path = '.'.join(path.split("\\")[:-1])
rasterHasSr = False
print(path)
if root.findGroup(newGroupName) is not None:
layerGroup = root.findGroup(newGroupName)
else:
root.addChildNode(layerGroup)
layerGroup.setExpanded(True)
layerGroup.setItemVisibilityChecked(True)
#find ID of the layer with a matching name in the "latest" group
newName = f'{streamBranch}/{layer.name}'
######################## testing, only for receiving layers #################
source_folder = QgsProject.instance().absolutePath()
if(source_folder == ""):
logger.logToUser(f"Raster layers can only be received in an existing saved project. Layer {layer.name} will be ignored", Qgis.Warning)
return None
project = QgsProject.instance()
projectCRS = QgsCoordinateReferenceSystem.fromWkt(layer.crs.wkt)
crsid = crsRaster.authid()
try: epsg = int(crsid.split(":")[1])
try:
srRasterWkt = str(layer.rasterCrs.wkt)
print(layer.rasterCrs.wkt)
srRaster = arcpy.SpatialReference(text=srRasterWkt) # by native raster SR
rasterHasSr = True
except:
epsg = int(str(projectCRS).split(":")[len(str(projectCRS).split(":"))-1].split(">")[0])
logger.logToUser(f"CRS of the received raster cannot be identified. Project CRS will be used.", Qgis.Warning)
srRasterWkt = str(layer.crs.wkt)
srRaster: arcpy.SpatialReference = sr # by layer
#print(layer.rasterCrs.wkt)
print(srRaster)
newName, layerGroup = newLayerGroupAndName(layerName, streamBranch, project)
print(newName)
if "." in newName: newName = '.'.join(newName.split(".")[:-1])
print(newName)
feat = layer.features[0]
bandNames = feat["Band names"]
bandValues = [feat["@(10000)" + name + "_values"] for name in bandNames]
#newName = f'{streamBranch}_latest_{layer.name}'
xsize= int(feat["X pixels"])
ysize= int(feat["Y pixels"])
xres = float(feat["X resolution"])
yres = float(feat["Y resolution"])
bandsCount=int(feat["Band count"])
originPt = arcpy.Point(feat['displayValue'][0].x, feat['displayValue'][0].y, feat['displayValue'][0].z)
print(originPt)
#if source projection is different from layer display projection, convert display OriginPt to raster source projection
if rasterHasSr is True and srRaster.exportToString() != sr.exportToString():
originPt = findTransformation(arcpy.PointGeometry(originPt, sr, has_z = True), "Point", sr, srRaster, None).getPart()
print(originPt)
###########################################################################
bandDatasets = ""
rastersToMerge = []
rasterPathsToMerge = []
## https://opensourceoptions.com/blog/pyqgis-create-raster/
# creating file in temporary folder: https://stackoverflow.com/questions/56038742/creating-in-memory-qgsrasterlayer-from-the-rasterization-of-a-qgsvectorlayer-wit
fn = source_folder + '/' + newName.replace("/","_") + '.tif' #'_received_raster.tif'
driver = gdal.GetDriverByName('GTiff')
# create raster dataset
ds = driver.Create(fn, xsize=feat["X pixels"], ysize=feat["Y pixels"], bands=feat["Band count"], eType=gdal.GDT_Float32)
# Write data to raster band
for i in range(feat["Band count"]):
arcpy.env.workspace = path
arcpy.env.overwriteOutput = True
# https://pro.arcgis.com/en/pro-app/latest/tool-reference/data-management/composite-bands.htm
for i in range(bandsCount):
print(i)
print(bandNames[i])
rasterbandPath = path + "\\" + newName + "_Band_" + str(i+1) #+ ".tif"
bandDatasets += rasterbandPath + ";"
rasterband = np.array(bandValues[i])
rasterband = np.reshape(rasterband,(feat["Y pixels"], feat["X pixels"]))
ds.GetRasterBand(i+1).WriteArray(rasterband) # or "rasterband.T"
rasterband = np.reshape(rasterband,(ysize, xsize))
print(rasterband)
print(np.shape(rasterband))
print(xsize)
print(xres)
print(ysize)
print(yres)
leftLowerCorner = arcpy.Point(originPt.X, originPt.Y + (ysize*yres), originPt.Z)
#upperRightCorner = arcpy.Point(originPt.X + (xsize*xres), originPt.Y, originPt.Z)
print(leftLowerCorner)
#print(upperRightCorner)
# create GDAL transformation in format [top-left x coord, cell width, 0, top-left y coord, 0, cell height]
pt = pointToNative(feat["displayValue"][0])
xform = QgsCoordinateTransform(crs, crsRaster, project)
pt.transform(xform)
ds.SetGeoTransform([pt.x(), feat["X resolution"], 0, pt.y(), 0, feat["Y resolution"]])
# create a spatial reference object
srs = osr.SpatialReference()
# For the Universal Transverse Mercator the SetUTM(Zone, North=1 or South=2)
srs.ImportFromEPSG(epsg) # from https://gis.stackexchange.com/questions/34082/creating-raster-layer-from-numpy-array-using-pyqgis
ds.SetProjection(srs.ExportToWkt())
# close the rater datasource by setting it equal to None
ds = None
# # Convert array to a geodatabase raster, add to layers
try: myRaster = arcpy.NumPyArrayToRaster(rasterband, leftLowerCorner, abs(xres), abs(yres), float(feat["NoDataVal"][i]) )
except: myRaster = arcpy.NumPyArrayToRaster(rasterband, leftLowerCorner, abs(xres), abs(yres))
raster_layer = QgsRasterLayer(fn, newName, 'gdal')
QgsProject.instance().addMapLayer(raster_layer, False)
layerGroup.addLayer(raster_layer)
rasterbandPath = validate_path(rasterbandPath) #solved file saving issue
print(rasterbandPath)
#mergedRaster = arcpy.ia.Merge(rastersToMerge) # glues all bands together
myRaster.save(rasterbandPath)
dataProvider = raster_layer.dataProvider()
rendererNew = rasterRendererToNative(layer, dataProvider)
print(myRaster.width)
print(myRaster.height)
try: raster_layer.setRenderer(rendererNew)
except: pass
rastersToMerge.append(myRaster)
rasterPathsToMerge.append(rasterbandPath)
print(rasterbandPath)
#mergedRaster.setProperty("spatialReference", crsRaster)
full_path = validate_path(path + "\\" + newName) #solved file saving issue
if os.path.exists(full_path):
for index, letter in enumerate('1234567890abcdefghijklmnopqrstuvwxyz'):
if os.path.exists(full_path + letter): pass
else: full_path += letter; break
print(full_path)
#mergedRaster = arcpy.ia.Merge(rastersToMerge) # glues all bands together
#mergedRaster.save(full_path) # similar errors: https://community.esri.com/t5/python-questions/error-010240-could-not-save-raster-dataset/td-p/321690
arcpy.management.CompositeBands(rasterPathsToMerge, full_path)
print(path + "\\" + newName)
arcpy.management.DefineProjection(full_path, srRaster)
rasterLayer = arcpy.management.MakeRasterLayer(full_path, newName + "_").getOutput(0)
print(layerGroup)
active_map.addLayerToGroup(layerGroup, rasterLayer)
r'''
if arcpy.Exists(fileout):
arcpy.management.Delete(fileout)
arcpy.management.Rename(filelist[0], fileout)
# Remove temporary files
for fileitem in filelist:
if arcpy.Exists(fileitem):
arcpy.management.Delete(fileitem)
# Release raster objects from memory
del myRasterBlock
del myRaster
'''
return raster_layer
r'''
rasterComposite = arcpy.management.CompositeBands(bandDatasets, path + "\\" + newName) # "band1.tif;band2.tif;band3.tif", "compbands.tif"
# https://pro.arcgis.com/en/pro-app/latest/tool-reference/data-management/make-raster-layer.htm
rasterLayer = arcpy.MakeRasterLayer_management(rasterComposite, newName)
'''
r'''
# WORKS:
arcpy.CreateRasterDataset_management(r"C:\Users\Kateryna\Documents\ArcGIS\Projects\MyProject-test",
"EmptyTIFF.tif",
"2",
"8_BIT_UNSIGNED",
"PROJCS['DHDN_3_Degree_Gauss_Zone_3',GEOGCS['GCS_Deutsches_Hauptdreiecksnetz',DATUM['D_Deutsches_Hauptdreiecksnetz',SPHEROID['Bessel_1841',6377397.155,299.1528128]],PRIMEM['Greenwich',0.0],UNIT['Degree',0.0174532925199433]],PROJECTION['Gauss_Kruger'],PARAMETER['False_Easting',3500000.0],PARAMETER['False_Northing',0.0],PARAMETER['Central_Meridian',9.0],PARAMETER['Scale_Factor',1.0],PARAMETER['Latitude_Of_Origin',0.0],UNIT['Meter',1.0]]", "3", "", "PYRAMIDS -1 NEAREST JPEG", "128 128", "NONE", "")
'''
return rasterLayer
@@ -1,66 +1,45 @@
import json
import math
import os
from typing import Dict, Any, Callable, List, Optional, Tuple
from specklepy.objects import Base
import arcpy
from arcpy.management import CreateCustomGeoTransformation
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
from speckle.converter.geometry._init_ import convertToSpeckle, convertToNative, convertToNativeMulti
from speckle.converter.layers.utils import getVariantFromValue
from speckle.converter.layers.utils import (findTransformation, getVariantFromValue, traverseDict,
traverseDictByKey, hsv_to_rgb)
def featureToSpeckle(fieldnames, attr_list, f_shape, projectCRS: arcpy.SpatialReference, project: arcpy.mp.ArcGISProject, selectedLayer):
from speckle.converter.geometry.point import pointToSpeckle
from speckle.converter.geometry.mesh import rasterToMesh, meshToNative
import numpy as np
import colorsys
def featureToSpeckle(fieldnames, attr_list, f_shape, projectCRS: arcpy.SpatialReference, project: ArcGISProject, selectedLayer: arcLayer):
print("___________Feature to Speckle____________")
b = Base(units = "m")
data = arcpy.Describe(selectedLayer.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 {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)
#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(tr0)
if geomType != "Point" and geomType != "Polyline" and geomType != "Polygon" and geomType != "Multipoint":
#print(geomType)
arcpy.AddWarning("Unsupported or invalid geometry in layer " + selectedLayer.name)
# 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
f_shape = findTransformation(f_shape, geomType, layer_sr, projectCRS, selectedLayer)
if f_shape is None: return None
######################################### Convert geometry ##########################################
try:
@@ -84,7 +63,7 @@ def featureToSpeckle(fieldnames, attr_list, f_shape, projectCRS: arcpy.SpatialRe
return b
def featureToNative(feature: Base, fields: dict, geomType: str, sr: arcpy.SpatialReference):
print("Feature To Native____________")
print("04_____Feature To Native____________")
feat = {}
try: speckle_geom = feature["geometry"] # for created in QGIS / ArcGIS Layer type
except: speckle_geom = feature # for created in other software
@@ -99,7 +78,7 @@ def featureToNative(feature: Base, fields: dict, geomType: str, sr: arcpy.Spatia
feat.update({"arcGisGeomFromSpeckle": arcGisGeom})
else:
return None
print(feat)
for key, variant in fields.items():
value = feature[key]
@@ -112,33 +91,428 @@ 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____________")
def cadFeatureToNative(feature: Base, fields: dict, sr: arcpy.SpatialReference):
print("_________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
#print(feature)
#print(speckle_geom)
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____________")
feat = {}
try: speckle_geom = feature["geometry"] # for created in QGIS Layer type
except: speckle_geom = feature # for created in other software
if isinstance(speckle_geom, list):
if len(speckle_geom)>1: arcGisGeom = convertToNativeMulti(speckle_geom, sr)
else: arcGisGeom = convertToNative(speckle_geom[0], sr)
else:
arcGisGeom = convertToNative(speckle_geom, sr)
if arcGisGeom is not None:
feat.update({"arcGisGeomFromSpeckle": arcGisGeom})
else: return None
for key, variant in fields.items():
value = feature[key]
if variant == "TEXT": value = str(feature[key])
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
try:
if "Speckle_ID" not in fields.keys() and feature["id"]: feat.update("Speckle_ID", "TEXT")
except: pass
#### 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
r'''
# Save layer file to read symbology
# https://pro.arcgis.com/en/pro-app/latest/tool-reference/data-management/save-to-layer-file.htm
layerFile = project.homeFolder + "\\" + selectedLayer.name.split(".")[0]
arcpy.management.SaveToLayerFile(selectedLayer.name, layerFile, "ABSOLUTE")
# read the file and then delete
f = open(layerFile + ".lyrx", "r")
layerFileContent = json.loads(f.read())
print(layerFileContent)
f.close()
os.remove(layerFile + ".lyrx")
'''
# get Raster object of entire raster dataset
my_raster = arcpy.Raster(selectedLayer.dataSource)
print(my_raster.mdinfo) # None
rasterBandCount = my_raster.bandCount
rasterBandNames = my_raster.bandNames
rasterDimensions = [my_raster.width, my_raster.height]
if rasterDimensions[0]*rasterDimensions[1] > 1000000 :
arcpy.AddWarning("Large layer: ")
#ds = gdal.Open(selectedLayer.source(), gdal.GA_ReadOnly)
extent = my_raster.extent
print(extent.XMin)
print(extent.YMin)
rasterOriginPoint = arcpy.PointGeometry(arcpy.Point(extent.XMin, extent.YMax, extent.ZMin), my_raster.spatialReference, has_z = True)
#if extent.YMin>0: rasterOriginPoint = arcpy.PointGeometry(arcpy.Point(extent.XMin, extent.YMax, extent.ZMin), my_raster.spatialReference, has_z = True)
print(rasterOriginPoint)
rasterResXY = [my_raster.meanCellWidth, my_raster.meanCellHeight] #[float(ds.GetGeoTransform()[1]), float(ds.GetGeoTransform()[5])]
rasterBandNoDataVal = [] #list(my_raster.noDataValues)
rasterBandMinVal = []
rasterBandMaxVal = []
rasterBandVals = []
b = Base(units = "m")
# Try to extract geometry
reprojectedPt = None
try:
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]
print(geom)
except Exception as error:
arcpy.AddError("Error converting point geometry: " + str(error))
for i, item in enumerate(rasterBandNames):
print(item)
rb = my_raster.getRasterBands(item)
print(rb)
print(np.shape(rb.read()))
valMin = rb.minimum
valMax = rb.maximum
bandVals = np.swapaxes(rb.read(), 1, 2).flatten() #.tolist() np.flip( , 0)
bandValsFlat = []
bandValsFlat.extend(bandVals.tolist())
#print(bandValsFlat)
const = float(-1* math.pow(10,30))
defaultNoData = rb.noDataValue
# check whether NA value is too small or raster has too small values
# assign min value of an actual list; re-assign NA val; replace list items to new NA val
try:
# create "safe" fake NA value; replace extreme values with it
fakeNA = max(bandValsFlat) + 1
bandValsFlatFake = [fakeNA if val<=const else val for val in bandValsFlat] # replace all values corresponding to NoData value
#if default NA value is too small
if (isinstance(defaultNoData, float) or isinstance(defaultNoData, int)) and defaultNoData < const:
# find and rewrite min of actual band values; create new NA value
valMin = min(bandValsFlatFake)
noDataValNew = valMin - 1000 # use new adequate value
rasterBandNoDataVal.append(noDataValNew)
# replace fake NA with new NA
bandValsFlat = [noDataValNew if val == fakeNA else val for val in bandValsFlatFake] # replace all values corresponding to NoData value
# if default val unaccessible and minimum val is too small
elif (isinstance(defaultNoData, str) or defaultNoData is None) and valMin < const: # if there are extremely small values but default NA unaccessible
noDataValNew = valMin
rasterBandNoDataVal.append(noDataValNew)
# replace fake NA with new NA
bandValsFlat = [noDataValNew if val == fakeNA else val for val in bandValsFlatFake] # replace all values corresponding to NoData value
# last, change minValto actual one
valMin = min(bandValsFlatFake)
else: rasterBandNoDataVal.append(rb.noDataValue)
except: rasterBandNoDataVal.append(rb.noDataValue)
rasterBandVals.append(bandValsFlat)
rasterBandMinVal.append(valMin)
rasterBandMaxVal.append(valMax)
#print(rb.getColormap()) #None
b["@(10000)" + item + "_values"] = bandValsFlat #[0:int(max_values/rasterBandCount)]
b["X resolution"] = rasterResXY[0]
b["Y resolution"] = -1* rasterResXY[1]
b["X pixels"] = rasterDimensions[0]
b["Y pixels"] = rasterDimensions[1]
b["Band count"] = rasterBandCount
b["Band names"] = rasterBandNames
b["NoDataVal"] = rasterBandNoDataVal
# creating a mesh
vertices = []
faces = []
colors = []
count = 0
print(my_raster.variables)
print(selectedLayer.symbology) #None
colorizer = None
#renderer = selectedLayer.symbology.renderer
if hasattr(selectedLayer.symbology, 'colorizer'):
colorizer = selectedLayer.symbology.colorizer
print(colorizer) # <arcpy._colorizer.RasterStretchColorizer object at 0x000001780497FBC8>
print(colorizer.type) # RasterStretchColorizer
rendererType = ""
if hasattr(selectedLayer.symbology, 'renderer'): rendererType = selectedLayer.symbology.renderer.type #e.g. SimpleRenderer
# custom color ramp {"type": "algorithmic", "fromColor": [115, 76, 0, 255],"toColor": [255, 25, 86, 255], "algorithm": "esriHSVAlgorithm"}.
# custom color map {'values': [0, 1, 2, 3, 4, 5], 'colors': ['#000000', '#DCFFDF', '#B8FFBE', '#85FF90', '#50FF60','#00AB10']}
bandIndex = 0
r'''
if colorizer.type == "RasterStretchColorizer":
print("___Color cell: RasterStretchColorizer___")
print(colorizer.band)
#colorRamps = project.listColorRamps()
bandIndex = colorizer.band
colorizerData = None
colorRamp = None
colorizerData = traverseDictByKey(layerFileContent, "colorizer", None)
print(colorizerData) # {'type': 'CIMRasterStretchColorizer', 'resamplingType':
#noDataColor: List[float] = traverseDictByKey(colorizerData, "noDataColor")['values']
colorRamp = traverseDictByKey(colorizerData, "colorRamp", None)
colorsFromRamp: List[List[float]] = []
colorsFromRampType = []
try:
for i, item in enumerate(colorRamp['colorRamps']):
colorsFromRamp.append(item['fromColor']['values'])
colorsFromRampType.append(item['fromColor']['type'])
if i == len(colorRamp['colorRamps'])-1 :
colorsFromRamp.append(item['toColor']['values'])
colorsFromRampType.append(item['toColor']['type'])
except: pass
print(colorsFromRamp) # [[220, 100, 45, 100], [214.12, 100, 100, 100], [201, 25, 100, 100]]
rangesNumber = len(colorsFromRamp) - 1 # 2 (if 3 colors)
colorsFromRampRGB = []
for i, item in enumerate(colorsFromRamp):
if ("CIMHSVColor" in colorsFromRampType[i]):
# https://pro.arcgis.com/en/pro-app/latest/arcpy/mapping/rasterclassbreak-class.htm
newR, newG, newB = colorsys.hsv_to_rgb(item[0],item[1],item[2])
colorsFromRampRGB.append( ( int(newR*255), int( newG*255), int(newB*255) ) )
elif ("HSL" in colorsFromRampType[i]):
# https://pro.arcgis.com/en/pro-app/latest/arcpy/mapping/rasterclassbreak-class.htm
newR, newG, newB = colorsys.hsl_to_rgb(item[0],item[1],item[2])
colorsFromRampRGB.append( ( int(newR*255), int( newG*255), int(newB*255) ) )
else:
colorsFromRampRGB.append(item)
rs = [float(x[0]) for x in colorsFromRampRGB]
gs = [float(x[1]) for x in colorsFromRampRGB]
bs = [float(x[2]) for x in colorsFromRampRGB]
'''
# identify symbology type and if Multiband, which band is which color
for v in range(rasterDimensions[1] ): #each row, Y
for h in range(rasterDimensions[0] ): #item in a row, X
pt1 = arcpy.PointGeometry(arcpy.Point(extent.XMin+h*rasterResXY[0],extent.YMax-v*rasterResXY[1]), my_raster.spatialReference, has_z = True)
pt2 = arcpy.PointGeometry(arcpy.Point(extent.XMin+h*rasterResXY[0], extent.YMax-(v+1)*rasterResXY[1]), my_raster.spatialReference, has_z = True)
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.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)
pt4 = findTransformation(pt4, "Point", my_raster.spatialReference, projectCRS, selectedLayer)
vertices.extend([pt1.getPart().X, pt1.getPart().Y, pt1.getPart().Z, pt2.getPart().X, pt2.getPart().Y, pt2.getPart().Z, pt3.getPart().X, pt3.getPart().Y, pt3.getPart().Z, pt4.getPart().X, pt4.getPart().Y, pt4.getPart().Z]) ## add 4 points
faces.extend([4, count, count+1, count+2, count+3])
# color vertices according to QGIS renderer
color = (0<<16) + (0<<8) + 0
noValColor = [0,0,0] #selectedLayer.renderer().nodataColor().getRgb()
r'''
if colorizer.type == "RasterStretchColorizer":
# find position of the alue on the range
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])
position = (rasterBandVals[bandIndex][int(count/4)] - float(colorizer.minLabel)) / valRange
print(position) # 0.8461538461538461
print("calc range")
localPosition = 0
for n in range(rangesNumber): # 0, 1
print(n)
start = n/rangesNumber
end = (n+1)/rangesNumber
if position <= end and position >= start:
localRange = end-start
localPosition = position - start
break # n - is the range we need, number bentween n and (n+1)
print(localPosition) # 0.34615384615384615
print(n)
localColor = []
for c in [rs,gs,bs]:
print(c)
# go through each color:
localColor.append( int( (c[n+1] - c[n]) * localPosition + c[n] ) )
print(localColor)
color = (localColor[0]<<16) + (localColor[1]<<8) + localColor[2]
print(color)
elif colorizer.type == "RasterClassifyColorizer":
print(colorizer.noDataColor)
print(colorizer.breakCount) # number of classes
print(colorizer.classBreaks)
print(colorizer.classificationField)
print(colorizer.classificationMethod)
print(colorizer.colorRamp)
elif colorizer.type == "RasterUniqueValueColorizer":
print(colorizer.noDataColor)
print(colorizer.colorRamp)
print(colorizer.field)
print(colorizer.groups)
'''
#else:
if colorizer:
try: bandIndex = int(colorizer.band)
except: pass
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 = 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
rbVals = my_raster.getRasterBands(rasterBandNames[0])
try:
rbvalMin = rbVals.minimum
rbvalMax = rbVals.maximum
except:
rbvalMin = min(rbVals)
rbvalMax = max(rbVals)
valRange = float(rbvalMax) - float(rbvalMin) #(rasterBandMaxVal[bandIndex] - rasterBandMinVal[bandIndex])
colorVal = int( (rasterBandVals[bandIndex][int(count/4)] - float(rbvalMin)) / valRange * 255 )
color = (colorVal<<16) + (colorVal<<8) + colorVal
colors.extend([color,color,color,color])
count += 4
mesh = rasterToMesh(vertices, faces, colors)
if(b['displayValue'] is None):
b['displayValue'] = []
b['displayValue'].append(mesh)
return b
r'''
# example raster stretch colorizer
{'type': 'CIMRasterStretchColorizer', 'resamplingType': 'NearestNeighbor',
'noDataColor': {'type': 'CIMRGBColor', 'values': [255, 255, 255, 0]},
'backgroundColor': {'type': 'CIMRGBColor', 'values': [255, 255, 255, 0]},
'colorRamp':
{
'type': 'CIMMultipartColorRamp',
'colorRamps':
[{
'type': 'CIMPolarContinuousColorRamp',
'colorSpace': {'type': 'CIMICCColorSpace', 'url': 'Default RGB'},
'fromColor': {'type': 'CIMHSVColor', 'values': [220, 100, 45, 100]},
'toColor': {'type': 'CIMHSVColor', 'values': [214, 100, 100, 100]},
'interpolationSpace': 'HSV', 'polarDirection': 'Counterclockwise'
},
{
'type': 'CIMPolarContinuousColorRamp',
'colorSpace': {'type': 'CIMICCColorSpace', 'url': 'Default RGB'},
'fromColor': {'type': 'CIMHSVColor', 'values': [214.12, 100, 100, 100]},
'toColor': {'type': 'CIMHSVColor', 'values': [201, 25, 100, 100]},
'interpolationSpace': 'HSV', 'polarDirection': 'Counterclockwise'
}],
'weights': [1, 1]},
'colorScheme': 'Bathymetry #3', 'customStretchMax': 1, 'gammaValue': 1, 'hillshadeZFactor': 1,
'maxPercent': 2, 'minPercent': 2, 'standardDeviationParam': 2, 'statsType': 'Dataset',
'stretchClasses': [
{'type': 'CIMRasterStretchClass', 'label': '3', 'value': 3},
{'type': 'CIMRasterStretchClass', 'value': 22.5},
{'type': 'CIMRasterStretchClass', 'label': '42', 'value': 42}
],
'stretchStats': {'type': 'StatsHistogram', 'min': 3, 'max': 42, 'mean': 21.761538461538, 'stddev': 11.387670241563, 'resolution': 0.15294117647058825},
'stretchType': 'StandardDeviations'}
'''
@@ -1,9 +1,15 @@
from typing import Any, List, Union
from typing import Dict, Any, List, Union
import json
from specklepy.objects import Base
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
@@ -24,51 +30,94 @@ def getVariantFromValue(value: Any) -> Union[str, None]:
return res
def getLayerAttributes(features: List[Base]) -> dict:
print("________ get layer attributes___")
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', 'userDictionary', 'userStrings','geometry']
for att in attrsToRemove:
try: dynamicProps.remove(att)
except: pass
dynamicProps.sort()
#print(dynamicProps)
# add field names and variands
#variants = []
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)
#print(variant)
if not variant: variant = None #LongLong #4
# add a field if not existing yet AND if variant is known
if variant and (name not in fields.keys()):
fields.update({name: variant})
# go thought the dictionary object
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)
elif name in fields.keys(): #check if the field was empty previously:
#nameIndex = fields.indexFromName(name)
oldVariant = fields[name]
#print(oldVariant)
# replace if new one is NOT LongLong or IS String
if oldVariant != "TEXT" and variant == "TEXT":
fields.update({name: variant})
print(all_props)
# 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()):
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)
# 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("______05___Traverse Dict")
#print(nam)
#print(val)
if isinstance(val, dict):
#print("DICT")
for i, (k,v) in enumerate(val.items()):
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 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:
unit_scale = {
@@ -92,3 +141,159 @@ def get_scale_factor(units: str) -> float:
arcpy.AddWarning(f"Units {units} are not supported. Meters will be applied by default.")
return 1.0
def findTransformation(f_shape, geomType, layer_sr: arcpy.SpatialReference, projectCRS: arcpy.SpatialReference, selectedLayer: arcLayer):
#apply transformation if needed
if layer_sr.name != projectCRS.name:
tr0 = tr1 = tr2 = tr_custom = None
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")
# 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
def traverseDictByKey(d: Dict, key:str ="", result = None) -> Dict:
print("__traverse")
result = None
#print(d)
for k, v in d.items():
try: v = json.loads(v)
except: pass
if isinstance(v, dict):
#print("__dict__")
if k == key: print("__break loop"); result = v; return result
else:
result = traverseDictByKey(v, key, result)
if result is not None: return result
if isinstance(v, list):
for item in v:
#print(item)
if isinstance(item, dict):
result = traverseDictByKey(item, key, result)
if result is not None: return result
#print("__result is: ____________")
#return result
def hsv_to_rgb(listHSV):
h, s, v = listHSV[0], listHSV[1], listHSV[2]
if s == 0.0: v*=255; return (v, v, v)
i = int(h*6.) # XXX assume int() truncates!
f = (h*6.)-i; p,q,t = int(255*(v*(1.-s))), int(255*(v*(1.-s*f))), int(255*(v*(1.-s*(1.-f)))); v*=255; i%=6
if i == 0: return (v, t, p)
if i == 1: return (q, v, p)
if i == 2: return (p, v, t)
if i == 3: return (p, q, v)
if i == 4: return (t, p, v)
if i == 5: return (v, p, q)
def cmyk_to_rgb(c, m, y, k, cmyk_scale, rgb_scale=255):
r = rgb_scale * (1.0 - c / float(cmyk_scale)) * (1.0 - k / float(cmyk_scale))
g = rgb_scale * (1.0 - m / float(cmyk_scale)) * (1.0 - k / float(cmyk_scale))
b = rgb_scale * (1.0 - y / float(cmyk_scale)) * (1.0 - k / float(cmyk_scale))
return r, g, b
def newLayerGroupAndName(layerName: str, streamBranch: str, project: ArcGISProject) -> str:
print("___new Layer Group and Name")
#CREATE A GROUP "received blabla" with sublayers
layerGroup = None
newGroupName = f'{streamBranch}'
print(newGroupName)
for l in project.activeMap.listLayers():
if l.longName == newGroupName: layerGroup = l; break
#find a layer with a matching name in the "latest" group
newName = f'{streamBranch.split("_")[len(streamBranch.split("_"))-1]}_{layerName}'
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)
print(newName)
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
print(newName)
return newName, layerGroup
def curvedFeatureClassToSegments(layer) -> str:
print("___densify___")
data = arcpy.Describe(layer.dataSource)
dataPath = data.catalogPath
print(dataPath)
newPath = dataPath+"_backup"
arcpy.management.CopyFeatures(dataPath, newPath) # features copied like this do not preserve curved segments
arcpy.edit.Densify(in_features = newPath, densification_method = "ANGLE", max_angle = 0.01, max_vertex_per_segment = 100) # https://pro.arcgis.com/en/pro-app/latest/tool-reference/editing/densify.htm
print(newPath)
return newPath
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)
file_base = os.path.splitext(file_name)[0]
if dirname == '':
# a relative path only, relying on the workspace
dirname = arcpy.env.workspace
path_ext = os.path.splitext(dirname)[1].lower()
if path_ext in ['.mdb', '.gdb', '.sde']:
# we're working in a database
file_name = arcpy.ValidateTableName(file_base) # e.g. add a letter in front of the name
validated_path = os.path.join(dirname, file_name)
#msg("validated path: %s; (from %s)" % (validated_path, path))
return validated_path
@@ -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,13 +1,13 @@
# -*- coding: utf-8 -*-
r'''
import arcpy
class Toolbox(object):
def __init__(self):
"""Define the toolbox (the name of the toolbox is the name of the
.pyt file)."""
self.label = "Speckle Toolbox"
self.alias = "speckle_toolbox_"
self.label = "Speckle something"
self.alias = "speckle_toolbox"
self.tools = [Speckle]
class Speckle(object):
@@ -45,4 +45,4 @@ class Speckle(object):
def execute(self, parameters):
return
'''
@@ -1,16 +1,21 @@
# -*- coding: utf-8 -*-
from typing import Any, Callable, List, Optional, Tuple
#r'''
from collections import defaultdict
from typing import Any, Callable, List, Optional
from xmlrpc.client import Boolean
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 speckle.converter.layers._init_ import convertSelectedLayers, layerToNative, cadLayerToNative, bimLayerToNative
from arcgis.features import FeatureLayer
import os
import os.path
import specklepy
@@ -19,14 +24,20 @@ 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.objects import Base
#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,
@@ -58,120 +69,178 @@ def traverseValue(
traverseValue(item, callback, check)
class Toolbox(object):
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]
#self.toolboxInputs = uiInputs() # initialize once together with a toolbox
self.tools = [Speckle]
metrics.set_host_app("ArcGIS")
#print(self.toolboxInputs.selected_layers)
#try:
# https://pro.arcgis.com/en/pro-app/2.8/arcpy/mapping/alphabeticallistofclasses.htm#except: print("something happened")
class uiInputs(object):
speckle_client: Any
streams: Optional[Streams]
active_stream: Optional[Stream]
active_branch: Optional[Branch]
all_layers: List[arcLayer]
selected_layers: List[Any]
messageSpeckle: str
project: ArcGISProject
action: int
instances = []
def __init__(self):
#print("start UI inputs________")
self.instances.append(self)
accounts = get_local_accounts()
account = None
for acc in accounts:
if acc.isDefault: account = acc; break
#account.userInfo.name, account.serverInfo.url
self.speckle_client = SpeckleClient(account.serverInfo.url, account.serverInfo.url.startswith("https"))
self.speckle_client.authenticate_with_token(token=account.token)
#print("ping")
#print(self.speckle_client)
self.streams = self.speckle_client.stream.search("")
#print("ping")
self.active_stream = None
self.active_branch = None
self.active_commit = None
self.all_layers = []
self.selected_layers = []
self.messageSpeckle = ""
self.project = aprx = None
self.action = 1 #send
#print(self.streams)
try: aprx = ArcGISProject('CURRENT')
except:
#print(arcpy.env.workspace) # None
#arcpy.env.workspace = ""
#proj_path = "\\".join(arcpy.env.workspace.split("\\")[:-1]) + "\\"
#aprx = ArcGISProject(proj_path)
#print(aprx)
print("Project not found")
self.project = aprx
active_map = aprx.activeMap
if active_map is not None and isinstance(active_map, Map): # if project loaded
for layer in active_map.listLayers():
#print(layer)
if layer.isFeatureLayer: self.all_layers.append(layer) #type: 'arcpy._mp.Layer'
class Speckle(object):
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.toolboxInputs = None
self.toRefresh = False
for instance in uiInputs.instances:
#print(instance)
if instance is not None:
try:
x = instance.streams # in case not initialized properly
self.toolboxInputs = instance # take latest
except: pass
if self.toolboxInputs is None:
#print("Instance is None")
self.toolboxInputs = uiInputs() #in case Toolbox class was not initialized
# TODO react on project changes
self.toRefresh = False
self.speckleInputs = None
self.toolboxInputs = None
#print("ping_Speckle1")
#print("______continue reset_______")
#print(self.toolboxInputs.all_layers)
#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"
stream = arcpy.Parameter(
displayName="Stream",
name="stream",
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",
#category="Sending data",
direction="Input")
stream.filter.type = 'ValueList'
stream.filter.list = [ (st.name + " | " + st.id) for st in self.toolboxInputs.streams ]
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")
branch.value = "main"
direction="Input",
#category=cat2
)
branch.value = ""
branch.filter.type = 'ValueList'
commit = arcpy.Parameter(
@@ -180,40 +249,34 @@ class Speckle(object):
datatype="GPString",
parameterType="Optional",
#category="Sending data",
direction="Input")
direction="Input",
#category=cat2
)
commit.value = ""
commit.filter.type = 'ValueList'
################################################################
msg = arcpy.Parameter(
displayName="Message",
name="message",
name="msg",
datatype="GPString",
parameterType="Optional",
direction="Input",
multiValue=False)
multiValue=False,
#category=cat2
)
msg.value = ""
selected_layers = arcpy.Parameter(
selectedLayers = arcpy.Parameter(
displayName="Selected Layers",
name="selected_layers",
name="selectedLayers",
datatype="GPString",
parameterType="Optional",
direction="Input",
multiValue=True
multiValue=True,
#category=cat2
)
selected_layers.filter.list = [str(i) + "-" + l.longName for i,l in enumerate(self.toolboxInputs.all_layers)] #"Polyline"
selectedLayers.filter.list = [str(i) + "-" + l.longName for i,l in enumerate(self.speckleInputs.all_layers)] #"Polyline"
refresh = arcpy.Parameter(
displayName="Refresh",
name="refresh",
datatype="GPBoolean",
parameterType="Optional",
#category="Sending data",
direction="Input"
)
#refresh.filter.type = "ValueList"
refresh.value = False
action = arcpy.Parameter(
displayName="",
@@ -222,13 +285,24 @@ class Speckle(object):
parameterType="Required",
#category="Sending data",
direction="Input",
multiValue=False
multiValue=False,
#category=cat2
)
action.value = "Send"
#action.filter.type = 'ValueList'
action.filter.list = ["Send", "Receive"]
parameters = [stream, branch, commit, selected_layers, msg, action, refresh]
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
@@ -237,130 +311,238 @@ class Speckle(object):
def updateParameters(self, parameters: List, toRefresh = False): #optional
print("UPDATING PARAMETERS")
if parameters[0].altered:
# Search for the stream by name
if parameters[0].valueAsText is not None:
selected_stream_name = parameters[0].valueAsText[:]
self.toolboxInputs.active_stream = None
#print(self.toolboxInputs.active_stream)
#print(self.toolboxInputs.streams)
for st in self.toolboxInputs.streams:
if st.name == selected_stream_name.split(" | ")[0]:
self.toolboxInputs.active_stream = st
break
# edit branches: globals and UI
branch_list = [branch.name for branch in self.toolboxInputs.active_stream.branches.items]
parameters[1].filter.list = branch_list
#print(parameters[1].filter.list)
if parameters[1].valueAsText not in branch_list:
parameters[1].value = "main"
for b in self.toolboxInputs.active_stream.branches.items:
if b.name == parameters[1].value:
self.toolboxInputs.active_branch = b
break
# setting commit value and list
try:
#print("___editing the stream input")
#print(self.toolboxInputs.active_branch.commits.items)
parameters[2].filter.list = [f"{commit.id}"+ " | " + f"{commit.message}" for commit in self.toolboxInputs.active_branch.commits.items]
#print(parameters[2].filter.list)
#print(parameters[2].valueAsText)
if parameters[2].valueAsText not in parameters[2].filter.list:
parameters[2].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:
parameters[2].filter.list = []
parameters[2].value = None
self.toolboxInputs.active_commit = None
if parameters[1].altered: # branches
if parameters[1].valueAsText is not None:
selected_branch_name = parameters[1].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: #.split(" | ")[0]:
self.toolboxInputs.active_branch = br
break
# edit commit values
if self.toolboxInputs.active_branch is not None:
try:
#print("___editing the branch input")
#print(self.toolboxInputs.active_branch)
parameters[2].filter.list = [f"{commit.id}"+ " | " + f"{commit.message}" for commit in self.toolboxInputs.active_branch.commits.items]
#print(parameters[2].filter.list)
#print(parameters[2].valueAsText)
if parameters[2].valueAsText not in parameters[2].filter.list:
parameters[2].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:
parameters[2].filter.list = []
parameters[2].value = None
self.toolboxInputs.active_commit = None
if parameters[2].altered: # commits
if parameters[2].valueAsText is not None:
selected_commit_id = parameters[2].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 parameters[3].altered: # selected layers
if parameters[3].valueAsText is not None:
self.toolboxInputs.selected_layers = parameters[3].values
if parameters[4].altered:
self.toolboxInputs.messageSpeckle = parameters[4].valueAsText
if parameters[5].altered:
if parameters[5].valueAsText == "Send": self.toolboxInputs.action = 1
else: self.toolboxInputs.action = 0
if parameters[6].altered: # refresh btn
if parameters[6].value == True:
self.refresh(parameters)
if self.toRefresh == True:
self.refresh(parameters)
self.toRefresh = False
#if newParams: # apply fresh values
# print(newParams[2].filter.list)
# parameters = newParams
for i, par in enumerate(parameters):
##parameters = newParams
#print("continue UPDATING PARAMETERS")
#print(parameters[2].filter.list)
#print(parameters[4].valueAsText)
return
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("_","")
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("_","")
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
print(self.toolboxInputs.messageSpeckle)
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______")
uiInputs()
for instance in uiInputs.instances:
if instance is not None: self.toolboxInputs = instance # take latest
#self.__init__()
#self.streams = self.speckle_client.stream.search("")
#params_new = []
#for i,p in enumerate(parameters):
# params_new.append(p)
parameters[0].value = None
parameters[1].value = "main"
parameters[2].value = None
parameters[3].value = None
parameters[4].value = ""
parameters[5].value = "Send"
parameters[6].value = False
parameters[0].filter.list = [ (st.name + " | " + st.id) for st in self.toolboxInputs.streams ]
parameters[3].filter.list = [str(i) + "-" + l.longName for i,l in enumerate(self.toolboxInputs.all_layers)]
#print("___continue_refresh______")
#print(parameters[2].filter.list)
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 = ""; 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"
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
@@ -370,10 +552,15 @@ class Speckle(object):
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__________________________")
#print(self.toolboxInputs.action)
if self.toolboxInputs.action == 1: self.onSend(parameters)
elif self.toolboxInputs.action == 0: self.onReceive(parameters)
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):
@@ -388,14 +575,16 @@ class Speckle(object):
def onSend(self, parameters: List):
if self.validateStreamBranch(parameters) == False: return
print("______________SEND_______________")
#if self.validateStreamBranch(parameters) == False: return
if len(self.toolboxInputs.selected_layers) == 0:
arcpy.AddError("No layers selected")
arcpy.AddError("No layers selected for sending")
return
streamId = self.toolboxInputs.active_stream.id #stream_id
client = self.toolboxInputs.speckle_client # ?
client = self.speckleInputs.speckle_client # ?
# Get the stream wrapper
#streamWrapper = StreamWrapper(None)
@@ -411,9 +600,12 @@ class Speckle(object):
transport = ServerTransport(client=client, stream_id=streamId)
##################################### conversions ################################################
base_obj = Base()
base_obj.layers = convertSelectedLayers(self.toolboxInputs.all_layers, self.toolboxInputs.selected_layers, self.toolboxInputs.project)
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])
@@ -426,7 +618,9 @@ class Speckle(object):
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(
@@ -437,22 +631,18 @@ class Speckle(object):
source_application="ArcGIS",
)
arcpy.AddMessage("Successfully sent data to stream: " + streamId)
#print("Successfully sent data to stream: " + streamId)
#parameters[2].value = ""
except:
arcpy.AddError("Error creating commit")
#print("sent")
#self.updateParameters(parameters, True)
#self.refresh(parameters)
def onReceive(self, parameters: List[Any]):
if self.validateStreamBranch(parameters) == False: return
print("______________RECEIVE_______________")
#if self.validateStreamBranch(parameters) == False: return
try:
streamId = self.toolboxInputs.active_stream.id #stream_id
client = self.toolboxInputs.speckle_client #
client = self.speckleInputs.speckle_client #
except SpeckleWarning as warning:
arcpy.AddWarning(str(warning.args[0]))
@@ -472,7 +662,15 @@ class Speckle(object):
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)
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
@@ -487,46 +685,57 @@ class Speckle(object):
commitObj = operations.receive(objId, transport, None)
if app != "QGIS" and app != "ArcGIS":
if self.toolboxInputs.project.activeMap.spatialReference.type == "Geographic" or self.toolboxInputs.project.activeMap.spatialReference is None: #TODO test with invalid CRS
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)
for l in self.toolboxInputs.project.activeMap.listLayers():
print(newGroupName)
for l in self.speckleInputs.project.activeMap.listLayers():
#print(l.longName)
if l.longName.startswith(newGroupName + "\\"):
#print(l.longName)
self.toolboxInputs.project.activeMap.removeLayer(l)
self.speckleInputs.project.activeMap.removeLayer(l)
groupExists+=1
elif l.longName == newGroupName:
groupExists+=1
print(newGroupName)
if groupExists == 0:
# create empty group layer file
path = self.toolboxInputs.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.toolboxInputs.project.activeMap.addLayer(smth)[0]
layerGroup.name = newGroupName
path = self.speckleInputs.project.filePath.replace("aprx","gdb") #"\\".join(self.toolboxInputs.project.filePath.split("\\")[:-1]) + "\\speckle_layers\\"
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
if app == "QGIS" or app == "ArcGIS": check: Callable[[Base], bool] = lambda base: isinstance(base, Layer) or isinstance(base, RasterLayer)
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)
def callback(base: Base) -> bool:
print("callback")
#print(base)
if isinstance(base, Layer) or isinstance(base, RasterLayer):
layer = layerToNative(base, streamBranch, self.toolboxInputs.project)
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:
@@ -543,6 +752,7 @@ class Speckle(object):
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)
@@ -550,19 +760,31 @@ class Speckle(object):
if isinstance(value, List):
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.toolboxInputs.project)
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"]
@@ -0,0 +1,276 @@
from typing import Any, List, Optional, Tuple, Union
import arcpy
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
from specklepy.api.models import Branch, Stream, Streams
import os.path
from specklepy.api.credentials import get_local_accounts
from specklepy.api.client import SpeckleClient
from specklepy.logging.exceptions import (
GraphQLException,
SpeckleException,
)
#from specklepy.api.credentials import StreamWrapper
from specklepy.api.wrapper import StreamWrapper
from osgeo import osr
class speckleInputsClass:
#def __init__(self):
print("CREATING speckle inputs first time________")
instances = []
accounts = get_local_accounts()
account = None
streams_default: None or Streams = None
project = None
active_map = None
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
new_client = SpeckleClient(
acc.serverInfo.url,
acc.serverInfo.url.startswith("https")
)
new_client.authenticate_with_token(token=acc.token)
clients.append(new_client)
speckle_client = None
if account:
speckle_client = SpeckleClient(
account.serverInfo.url,
account.serverInfo.url.startswith("https")
)
speckle_client.authenticate_with_token(token=account.token)
streams_default = speckle_client.stream.search("")
def __init__(self) -> None:
print("___start speckle inputs________")
self.all_layers = []
try:
aprx = ArcGISProject('CURRENT')
self.project = aprx
self.active_map = aprx.activeMap
if self.active_map is not None and isinstance(self.active_map, Map): # if project loaded
for layer in self.active_map.listLayers():
try: geomType = arcpy.Describe(layer.dataSource).shapeType.lower()
except: geomType = '' #print(arcpy.Describe(layer.dataSource)) #and arcpy.Describe(layer.dataSource).shapeType.lower() != "multipatch")
if (layer.isFeatureLayer and geomType != "multipatch") or layer.isRasterLayer: self.all_layers.append(layer) #type: 'arcpy._mp.Layer'
self.stream_file_path: str = aprx.filePath.replace("aprx","gdb") + "\\speckle_streams.txt"
if os.path.exists(self.stream_file_path):
try:
f = open(self.stream_file_path, "r")
content = f.read()
self.saved_streams = self.getProjectStreams(content)
f.close()
except: pass
elif len(self.stream_file_path) >10:
f = open(self.stream_file_path, "x")
f.close()
f = open(self.stream_file_path, "w")
content = ""
f.write(content)
f.close()
except: self.project = None; print("Project not found")
self.instances.append(self)
def getProjectStreams(self, content: str = None):
print("get proj streams")
if not content:
content = self.stream_file_path
try:
f = open(self.stream_file_path, "r")
content = f.read()
f.close()
except: pass
######### need to check whether saved streams are available (account reachable)
if content:
streamsTuples = []
for i, url in enumerate(content.split(",")):
streamExists = 0
index = 0
try:
print(url)
sw = StreamWrapper(url)
stream = self.tryGetStream(sw)
for st in streamsTuples:
if isinstance(stream, Stream) and st[0].stream_id == stream.id:
streamExists = 1;
break
index += 1
if streamExists == 1: del streamsTuples[index]
streamsTuples.insert(0,(sw, stream))
except SpeckleException as e:
arcpy.AddMessage(str(e.args))
return streamsTuples
else: return []
def tryGetStream (self,sw: StreamWrapper) -> Stream:
#print("Try get streams")
steamId = sw.stream_id
try: steamId = sw.stream_id.split("/streams/")[1].split("/")[0]
except: pass
client = sw.get_client()
stream = client.stream.get(steamId)
if isinstance(stream, GraphQLException):
raise SpeckleException(stream.errors[0]['message'])
return stream
class toolboxInputsClass:
#def __init__(self):
print("CREATING UI inputs first time________")
# self.instances.append(self)
instances = []
lat: float = 0.0
lon: float = 0.0
active_stream: Optional[Stream] = None
active_branch: Optional[Branch] = None
active_commit = None
selected_layers: List[Any] = []
messageSpeckle: str = ""
action: int = 1 #send
project = None
stream_file_path: str = ""
# Get the target item's Metadata object
def __init__(self) -> None:
print("___start UI inputs________")
try:
aprx = ArcGISProject('CURRENT')
project = aprx
self.stream_file_path: str = aprx.filePath.replace("aprx","gdb") + "\\speckle_streams.txt"
if os.path.exists(self.stream_file_path):
try:
f = open(self.stream_file_path, "r")
content = f.read()
self.lat, self.lon = self.get_survey_point(content)
f.close()
except: pass
except: print("Project not found")
try:
aprx = ArcGISProject('CURRENT')
self.project = aprx
except: self.project = None; print("Project not found"); arcpy.AddWarning("Project not found")
self.instances.append(self)
def setProjectStreams(self, wr: StreamWrapper, add = True):
# ERROR 032659 Error queueing metrics request:
# Cannot parse into a stream wrapper class - invalid URL provided.
print("SET proj streams")
if os.path.exists(self.stream_file_path):
new_content = ""
f = open(self.stream_file_path, "r")
existing_content = f.read()
f.close()
f = open(self.stream_file_path, "w")
if str(wr.stream_url) in existing_content:
new_content = existing_content.replace(str(wr.stream_url) + "," , "")
else:
new_content = existing_content
if add == True: new_content += str(wr.stream_url) + "," # add stream
else: pass # remove stream
f.write(new_content)
f.close()
elif len(self.stream_file_path) >10:
f = open(self.stream_file_path, "x")
f.close()
f = open(self.stream_file_path, "w")
f.write(str(wr.stream_url) + ",")
f.close()
def get_survey_point(self, content = None) -> Tuple[float]:
# get from saved project
print("get survey point")
x = y = 0
if not content:
content = self.stream_file_path
try:
f = open(self.stream_file_path, "r")
content = f.read()
f.close()
except: pass
if content:
temp = []
for i, coords in enumerate(content.split(",")):
if "speckle_sr_origin_" in coords:
try:
x, y = [float(c) for c in coords.replace("speckle_sr_origin_","").split(";")]
except: pass
return (x, y)
def set_survey_point(self, coords: List[float]):
# from widget (2 strings) to local vars + update SR of the map
print("SET survey point")
pt = "speckle_sr_origin_" + str(coords[0]) + ";" + str(coords[1])
if os.path.exists(self.stream_file_path):
new_content = ""
f = open(self.stream_file_path, "r")
existing_content = f.read()
f.close()
f = open(self.stream_file_path, "w")
if pt in existing_content:
new_content = existing_content.replace( pt , "")
else:
new_content = existing_content
new_content += pt + "," # add point
f.write(new_content)
f.close()
elif len(self.stream_file_path) >10:
f = open(self.stream_file_path, "x")
f.close()
f = open(self.stream_file_path, "w")
f.write(pt + ",")
f.close()
# save to project; crearte SR
self.lat, self.lon = coords[0], coords[1]
newCrsString = "+proj=tmerc +ellps=WGS84 +datum=WGS84 +units=m +no_defs +lon_0=" + str(self.lon) + " lat_0=" + str(self.lat) + " +x_0=0 +y_0=0 +k_0=1"
newCrs = osr.SpatialReference()
newCrs.ImportFromProj4(newCrsString)
newCrs.MorphToESRI() # converts the WKT to an ESRI-compatible format
validate = True if len(newCrs.ExportToWkt())>10 else False
if validate:
newProjSR = arcpy.SpatialReference()
newProjSR.loadFromString(newCrs.ExportToWkt())
#source = osr.SpatialReference()
#source.ImportFromWkt(self.project.activeMap.spatialReference.exportToString())
#transform = osr.CoordinateTransformation(source, newCrs)
self.project.activeMap.spatialReference = newProjSR
arcpy.AddMessage("Custom project CRS successfully applied")
else:
arcpy.AddWarning("Custom CRS could not be created")
return True