Compare commits

...

173 Commits

Author SHA1 Message Date
KatKatKateryna 34e0d1cacf Merge pull request #60 from specklesystems/2.13.0-release
2.13.0 release
2023-03-19 03:12:56 +08:00
KatKatKateryna 3538891247 user messages panel added 2023-03-18 19:11:24 +00:00
KatKatKateryna f0fcf4e20f fix 2023-03-18 18:06:49 +00:00
KatKatKateryna ad26a5bbfc msg split into lines 2023-03-16 17:59:09 +00:00
KatKatKateryna 26c0b14d84 more refactoring and user messages 2023-03-14 18:56:48 +00:00
KatKatKateryna 362f44983e remove diffuse material from raster meshes on send 2023-03-14 18:56:33 +00:00
KatKatKateryna ae282bb02b on receive check speckle_type only for Base (not Int etc); traverse ModelCurve; add function name to userMessages 2023-03-13 21:22:15 +00:00
KatKatKateryna 2ea3b64801 installer: clear plugin folder before installing whl (if permissions allow) 2023-03-13 19:22:30 +00:00
KatKatKateryna fe170c9094 move toolbox section away from .pyt 2023-03-13 18:29:21 +00:00
KatKatKateryna c1727e9048 Merge pull request #58 from specklesystems/2.13.0-new-ui
2.13.0 new UI
2023-03-10 03:11:51 +08:00
KatKatKateryna fda7462577 fixed Link widget; sending VISIBLE layers (not selected) 2023-03-09 19:06:11 +00:00
KatKatKateryna 2071515db0 geometry try/except 2023-03-09 16:54:28 +00:00
KatKatKateryna 50f1aa3d79 layers try/except 2023-03-09 16:35:04 +00:00
KatKatKateryna 5a83cb333d plugin utils try/except 2023-03-09 16:06:24 +00:00
KatKatKateryna 54c466ebcf ui try/except 2023-03-09 16:01:16 +00:00
KatKatKateryna 472eb1967c main plugin try/except 2023-03-09 12:54:01 +00:00
KatKatKateryna 3df8f0ea87 implement user messages of 3 levels of importance 2023-03-09 12:19:09 +00:00
KatKatKateryna 618f529636 sending Meshes and IFC in colors (but slow) 2023-03-08 00:16:03 +00:00
KatKatKateryna 3f838e579c ignore None layer geom on Send (if it happens) 2023-03-07 15:19:15 +00:00
KatKatKateryna 52e0f19f0e assign SR to received BIM/CAD; receive multipart mesh; receive color from pure meshes; receive symbology from BIM&IFC; naming of duplicate feature classes on receive 2023-03-06 18:10:39 +00:00
KatKatKateryna 7bfd81e121 meshes received(issues: too long; not all objects; center dislocated) 2023-03-03 21:22:41 +00:00
KatKatKateryna 8930984e41 icons in layer list 2023-03-02 22:32:01 +00:00
KatKatKateryna 459ca3fdc9 remove link on click; adjust position 2023-03-02 21:39:06 +00:00
KatKatKateryna a357904247 sending works 2023-03-02 16:55:54 +00:00
KatKatKateryna 05aef10054 receiving cad works 2023-03-02 13:47:33 +00:00
KatKatKateryna 8c7aad350f typos 2023-03-01 19:35:37 +00:00
KatKatKateryna 4f16b8e38c UI operates (not receive) 2023-03-01 19:27:07 +00:00
KatKatKateryna 350df33719 launching pyt with no errors 2023-03-01 18:05:06 +00:00
KatKatKateryna 49915857dd major UI change; todo: layers, object_utils, Dialog 2023-03-01 01:22:46 +00:00
KatKatKateryna 8f6d399281 remove threading and instances check 2023-02-27 18:05:04 +00:00
KatKatKateryna da85f29089 remove threading - run in the background instead; move window to front 2023-02-27 14:30:43 +00:00
KatKatKateryna 04e9740f15 launch tool with Run button 2023-02-27 13:16:35 +00:00
KatKatKateryna 9639fb00ba add images for UI 2023-02-27 13:16:17 +00:00
KatKatKateryna 941cd42ee2 Styles applied; TODO: crashing if Toolscript clicked & sometimes on close 2023-02-22 18:48:08 +00:00
KatKatKateryna 9312364dc9 PyQt UI window 2023-02-21 23:02:49 +00:00
KatKatKateryna a89e6e398c path for manual install 2023-02-14 11:21:22 +08:00
KatKatKateryna fceeb6a9d7 remove unnecessary files 2023-02-13 21:40:35 +08:00
KatKatKateryna 8a27a3a8e2 Manual install instructions 2023-02-13 21:35:32 +08:00
KatKatKateryna 2540d05181 typo 2023-02-13 20:36:46 +08:00
KatKatKateryna 4cb57d9631 list directories 2023-02-13 20:33:32 +08:00
KatKatKateryna 007e6263a6 typo 2023-02-13 20:29:53 +08:00
KatKatKateryna 4a93b40e8e attach installer folder to workspace 2023-02-13 20:28:56 +08:00
KatKatKateryna 95c1495977 split tag version 2023-02-13 20:22:18 +08:00
KatKatKateryna fc07cbf60e add a context with github token 2023-02-13 20:15:09 +08:00
KatKatKateryna 07029675e4 remove token 2023-02-13 20:00:15 +08:00
KatKatKateryna f24ef49450 quote version 2023-02-13 19:52:44 +08:00
KatKatKateryna 52a531e040 debug 2023-02-13 19:46:40 +08:00
KatKatKateryna d3be4f0377 version syntax 2023-02-13 19:43:57 +08:00
KatKatKateryna 865964249c latest 2023-02-13 19:35:31 +08:00
KatKatKateryna 1d11e702dc go install 2023-02-13 19:33:47 +08:00
KatKatKateryna b454ac543c try another docker image 2023-02-13 19:31:47 +08:00
KatKatKateryna fcbdc9a200 remove whl step completely 2023-02-13 19:14:53 +08:00
KatKatKateryna 7cbdab0471 remove --64 2023-02-13 19:04:49 +08:00
KatKatKateryna 464bcf0f61 test another ghr installer 2023-02-13 19:03:11 +08:00
KatKatKateryna 97c8cebdb4 sudo 2023-02-13 18:58:37 +08:00
KatKatKateryna cd3a05103b add $ 2023-02-13 18:56:54 +08:00
KatKatKateryna cc11402470 remove powershell 2023-02-13 18:53:05 +08:00
KatKatKateryna ae0f15023c Update config.yml 2023-02-13 07:53:44 +08:00
KatKatKateryna 0eed167715 Merge pull request #52 from specklesystems/kate/2.12
tags fix, run on cmd
2023-02-13 07:48:44 +08:00
KatKatKateryna f60cd064f3 tags fix, run on cmd 2023-02-13 07:47:40 +08:00
KatKatKateryna b86799856e Merge pull request #51 from specklesystems/kate/2.12
Kate/2.12
2023-02-13 07:38:22 +08:00
KatKatKateryna 14e805cf99 typo 2023-02-13 07:37:19 +08:00
KatKatKateryna 6d35d13f99 ghr for adding files to release; separate job 2023-02-13 07:35:46 +08:00
KatKatKateryna 5e0ff316f0 add whl via powershell 2023-02-13 07:07:19 +08:00
KatKatKateryna b22ac2ef17 don't add whl to installer 2023-02-13 06:59:11 +08:00
KatKatKateryna da1ebb04e4 Merge branch 'main' into kate/2.12 2023-02-09 02:28:17 +08:00
KatKatKateryna 29c4fde0c5 handling access error to the account 2023-02-08 20:24:27 +08:00
KatKatKateryna e132b16878 refactoring unittests 2023-02-08 20:16:15 +08:00
KatKatKateryna 2e1dc329b3 add 2.11.3 whl 2023-02-02 16:52:02 +08:00
KatKatKateryna ee3cc81391 Merge branch 'main' into kate/2.12 2023-02-02 16:31:28 +08:00
KatKatKateryna 78063bc976 ci: add whl file to release 2023-02-02 16:24:38 +08:00
KatKatKateryna 8dcdfbbfc1 version name for metrics 2023-02-02 00:49:08 +08:00
KatKatKateryna 221a050df4 typings fix for python 3.7 2023-02-01 22:58:43 +08:00
KatKatKateryna 67480f2f70 CI: return version 2023-01-31 23:24:53 +08:00
KatKatKateryna 0a24379984 typo 2023-01-31 23:15:56 +08:00
KatKatKateryna 6437be9d25 CI signing 2023-01-31 23:15:05 +08:00
KatKatKateryna 644b64bbb3 ci contexts 2023-01-31 22:42:19 +08:00
KatKatKateryna 037469966b Merge pull request #50 from specklesystems/2.11-fix
typing fixed for older python versions
2023-01-31 21:10:53 +08:00
KatKatKateryna e8d4b8035b typing fixed for older python versions 2023-01-31 21:09:53 +08:00
KatKatKateryna 8721ae246a search limit for branches and commits increased from 10 to 100 2023-01-28 07:14:23 +08:00
KatKatKateryna 261d324ed4 folder naming (to save BIM mesh and raster bands) 2023-01-28 06:29:19 +08:00
KatKatKateryna 07fe49a1f8 Merge pull request #48 from specklesystems/kate/2.11-fixes
missing value of 'Speckle_ID' for received GIS features
2023-01-11 20:34:12 +08:00
KatKatKateryna 6f90081af7 linting 2023-01-11 20:29:40 +08:00
KatKatKateryna 67911fdb5d typo 2023-01-11 19:32:50 +08:00
KatKatKateryna a9e48db570 manual install 2.11.0; patching error fixed 2023-01-11 19:21:40 +08:00
KatKatKateryna d284c5415a missing value of 'Speckle_ID' for received GIS features 2023-01-11 19:11:20 +08:00
Alan Rynne a6ec7b4a0b ci: Added issue automation actions 2023-01-09 20:39:59 +01:00
KatKatKateryna e70debc606 Merge pull request #47 from specklesystems/ci/use-contexts
CI: Use contexts and ssh
2023-01-09 15:21:55 +08:00
Alan Rynne a651fbd732 fix(ci): Use correct fingerprint 2023-01-05 18:14:31 +01:00
Alan Rynne 7eba1ba98b ci: Changes in CI config for get-ci-tools and deploy jobs 2023-01-05 18:10:01 +01:00
KatKatKateryna 4828f5b55e extra fix for changed attribute types 2023-01-05 22:45:14 +08:00
KatKatKateryna 0a5e49fb07 SpeckleID prop for CAD geometry on receive; fixed weird bug where object properties changed type before assigning to attribute table 2023-01-05 20:38:23 +08:00
KatKatKateryna b651bfd401 Merge pull request #46 from specklesystems/kate/2.12
2.11.0-fixed
2023-01-05 17:18:39 +08:00
KatKatKateryna 52a28eae3f Merging Main with old commits 2023-01-05 17:15:29 +08:00
KatKatKateryna 50c37315f8 Merge branch 'main' into kate/2.12 2023-01-05 16:14:10 +08:00
KatKatKateryna d9706b4f5f Merge branch 'main' of https://github.com/specklesystems/speckle-arcgis 2023-01-05 16:02:03 +08:00
KatKatKateryna aad3be3962 setting app version, basic unit tests, some error-preventing fixes 2023-01-05 15:53:20 +08:00
KatKatKateryna 013e9e27d6 adjust imports for unittest 2023-01-04 18:11:51 +08:00
KatKatKateryna 3131ba8950 Merge pull request #45 from specklesystems/2.11.0-beta
identify large integers, save into "float" type attribute; override "…
2023-01-04 02:43:59 +08:00
KatKatKateryna 9f8637d670 identify large integers, save into "float" type attribute; override "Long" type if already existed 2023-01-04 01:50:01 +08:00
KatKatKateryna f9f2628d3a Merge pull request #42 from specklesystems/kate/2.11
Kate/2.11
2022-12-25 01:35:37 +08:00
KatKatKateryna 22b4672b4a 2.10.3 whl to release 2022-12-25 01:34:27 +08:00
KatKatKateryna b963228da0 Merge pull request #32 from specklesystems/kate/2.10.3
Kate/2.10.2-beta
2022-12-25 01:34:27 +08:00
KatKatKateryna faa8fb9cfa whl file 2.10.2-beta 2022-12-25 01:34:27 +08:00
KatKatKateryna a1de8be5b0 fixing customCRS location (negative values read properly)) 2022-12-25 01:34:26 +08:00
KatKatKateryna bf10c4f00b use custom SR via WKT (not name) 2022-12-25 01:34:05 +08:00
KatKatKateryna f88a9e3966 attach renderers separately for vector/raster layers 2022-12-25 01:24:44 +08:00
KatKatKateryna b4149fa5f7 calculate raster mesh and symbology colors correctly on send; Vector renderer on send 2022-12-25 01:23:57 +08:00
KatKatKateryna 68d8c1f8f3 temporarily removed unhandled specklepy None value exception; 2022-12-23 21:14:12 +08:00
KatKatKateryna 8b4fbe88ef raster renderer on receive and send; mesh coloring on send 2022-12-20 19:35:13 +08:00
KatKatKateryna 67564631d2 fixed SR on receive cad/bim; raster mesh color on send (WIP); raster renderer send (WIP) 2022-12-20 04:28:01 +08:00
KatKatKateryna b3ffdd9d0b receiving vector gis-layer symbology 2022-12-15 23:55:41 +08:00
KatKatKateryna d7f467c6a6 symbology added ()not applied yet) 2022-12-08 01:15:21 +08:00
KatKatKateryna a14550d7ad send/receive from multiple registered accounts 2022-12-07 15:17:10 +08:00
KatKatKateryna 79b8b363f0 Merge pull request #33 from specklesystems/kate/2.11
Kate/2.10.4
2022-12-01 22:45:33 +08:00
KatKatKateryna d9b1b11036 fix 2022-12-01 22:45:10 +08:00
KatKatKateryna e47d89ce57 fix path by removing "/"; add other types than DisplayMesh; 2022-12-01 02:51:02 +08:00
KatKatKateryna faadba8e73 manual install taking the latest available 2.x whl 2022-11-30 02:36:46 +08:00
KatKatKateryna 18234800dd 2.10.3 whl to release 2022-11-30 01:27:30 +08:00
KatKatKateryna f0213b7186 stream list with no account 2022-11-30 01:26:50 +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
73 changed files with 9252 additions and 1850 deletions
+59 -10
View File
@@ -23,6 +23,12 @@ jobs:
- checkout
- attach_workspace:
at: ./
- run:
name: Create Innosetup signing cert
shell: powershell.exe
command: |
echo $env:PFX_B64 > "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt"
certutil -decode "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt" "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.pfx"
- run:
name: Patch
shell: powershell.exe
@@ -34,7 +40,14 @@ jobs:
$version = "$($ver).$($env:CIRCLE_BUILD_NUM)"
echo $semver
python patch_version.py $semver
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\arcgis.iss
python setup.py sdist bdist_wheel
Copy-Item -Path "dist\speckle_toolbox-$($ver)-py3-none-any.whl" -Destination "speckle_arcgis_installer"
- run:
name: Build Installer
shell: cmd.exe
command:
| # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\arcgis.iss /Sbyparam=$p
- when:
condition: << parameters.installer >>
steps:
@@ -42,14 +55,38 @@ jobs:
root: ./
paths:
- speckle-sharp-ci-tools/Installers
- speckle_arcgis_installer
publish-github-release:
docker:
- image: cimg/go:1.20.0
steps:
- attach_workspace:
at: ./
- run:
name: "Publish Release on GitHub"
command: |
set -x
go install github.com/tcnksm/ghr@v0.16.0
VERSION="${CIRCLE_TAG:-0.0.0}"
VERSION_SHORT=$(echo "${VERSION}" | cut -d- -f1)
ghr -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete "${VERSION}" "./speckle_arcgis_installer/speckle_toolbox-${VERSION_SHORT}-py3-none-any.whl"
get-ci-tools: # Clones our ci tools and persists them to the workspace
docker:
- image: cimg/base:2021.01
steps:
- run: # Could not get ssh to work, so using a personal token
- add_ssh_keys:
fingerprints:
- "77:64:03:93:c5:f3:1d:a6:fd:bd:fb:d1:05:56:ca:e9"
- run:
name: I know Github as a host
command: |
mkdir ~/.ssh
touch ~/.ssh/known_hosts
ssh-keyscan github.com >> ~/.ssh/known_hosts
- run:
name: Clone
command: git clone https://$GITHUB_TOKEN@github.com/specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
command: git clone git@github.com:specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
- persist_to_workspace:
root: ./
paths:
@@ -82,6 +119,7 @@ workflows: #happens with every PR to main
build: # build the installers, but don't persist to workspace for deployment
jobs:
- get-ci-tools:
context: github-dev-bot
filters:
branches:
only:
@@ -95,10 +133,12 @@ workflows: #happens with every PR to main
only:
- main
- /ci\/.*/
context: innosetup
deploy: # build installers and deploy
jobs:
- get-ci-tools:
context: github-dev-bot
filters:
tags:
only: /.*/
@@ -116,16 +156,25 @@ workflows: #happens with every PR to main
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/
- deploy-manager2:
slug: arcgis
os: Win
extension: exe
context: innosetup
- publish-github-release:
requires:
- get-ci-tools
- build-deploy-connector-win
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
ignore: /.*/
context: arcgis-github-release
- deploy-manager2:
slug: arcgis
os: Win
extension: exe
requires:
- build-deploy-connector-win
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
context: do-spaces-speckle-releases
+12
View File
@@ -0,0 +1,12 @@
name: Update issue Status
on:
issues:
types: [closed]
jobs:
update_issue:
uses: specklesystems/github-actions/.github/workflows/project-add-issue.yml@main
secrets: inherit
with:
issue-id: ${{ github.event.issue.node_id }}
+12
View File
@@ -0,0 +1,12 @@
name: Move new issues into Project
on:
issues:
types: [opened]
jobs:
track_issue:
uses: specklesystems/github-actions/.github/workflows/project-add-issue.yml@main
secrets: inherit
with:
issue-id: ${{ github.event.issue.node_id }}
+3 -1
View File
@@ -118,4 +118,6 @@ scratch.py
settings.json
**/.DS_Store
zip_build
.qt_for_python
.qt_for_python
*.pyt.xml
+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 and '.sort' not 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():
+3
View File
@@ -0,0 +1,3 @@
specklepy==2.9.1
panda3d==1.10.11
+2
View File
@@ -0,0 +1,2 @@
print("Hello")
+331
View File
@@ -0,0 +1,331 @@
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
import arcpy
import json
import os
try:
from speckle.converter.layers.CRS import CRS
from speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer
except:
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.CRS import CRS
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
from arcpy.management import (CreateFeatureclass, MakeFeatureLayer,
AddFields, AlterField, DefineProjection, SelectLayerByAttribute, GetCount )
from specklepy.objects import Base
##################################################### get example layers from the project #######
project = ArcGISProject('CURRENT')
active_map = project.activeMap
all_layers = []
layerPolygon = None
layerPolyline = None
layerPoint = None
layerMultiPoint = None
layerRaster = None
#get layer of interest
for layer in active_map.listLayers():
if layer.isFeatureLayer or layer.isRasterLayer:
all_layers.append(layer)
data = arcpy.Describe(layer.dataSource)
if layer.isRasterLayer and layerRaster is None: layerRaster = layer
if layer.isFeatureLayer:
geomType = data.shapeType
if geomType == "Polygon" and layerPolygon is None: layerPolygon = layer
if geomType == "Polyline" and layerPolyline is None: layerPolyline = layer
if geomType == "Point" and layerPoint is None: layerPoint = layer
if geomType == "Multipoint" and layerMultiPoint is None: layerMultiPoint = layer
################################ select/ clear selection ###########################
for layer in project.activeMap.listLayers():
if (layer.isFeatureLayer) or layer.isRasterLayer:
arcpy.SelectLayerByAttribute_management(layer.longName,"ADD_TO_SELECTION",'"OBJECTID" = 1')
print(arcpy.GetCount_management(layer.longName).getOutput(0))
arcpy.SelectLayerByAttribute_management(layer.longName, "CLEAR_SELECTION")
################## reset symbology if needed:
sym = layerPolygon.symbology
print(sym.renderer.type)
sym.updateRenderer('UniqueValueRenderer')
layerPolygon.symbology = sym
print(sym.updateRenderer('UniqueValueRenderer'))
print(layerPolygon.symbology.renderer.type)
# SimpleRenderer, GraduatedColorsRenderer, GraduatedSymbolsRenderer, UnclassedColorsRenderer, UniqueValueRenderer
######################################### change symbology #################################
for k, grp in enumerate(sym.renderer.groups):
for itm in grp.items:
print(itm)
print(itm.values)
print(itm.symbol.color)
transVal = itm.values[0][0] #Grab the first "percent" value in the list of potential values
print(transVal)
for i in range(len(cats)):
label = cats[i]['value']
print(label)
if label is None or label=="": label = "<Null>"
print(label)
from speckle.converter.layers.symbology import get_polygon_simpleRenderer
from arcpy._mp import ArcGISProject
aprx = ArcGISProject('CURRENT')
root_path = "\\".join(aprx.filePath.split("\\")[:-1])
path_style = root_path + '\\layer_speckle_symbology.lyrx'
path_style2 = root_path + '\\layer_speckle_symbology2.lyrx'
#arcpy.management.SaveToLayerFile(layerPolygon, path_style, False)
print(layerPolygon.dataSource)
arcpy.management.ApplySymbologyFromLayer(
in_layer=layerPolygon.dataSource,
in_symbology_layer=path_style2,
update_symbology='UPDATE')
f = open(path_style, "r")
renderer = json.loads(f.read())
renderer["layerDefinitions"][0]["renderer"] = get_polygon_simpleRenderer(1,2,150)
f = open(path_style2, "w")
f.write(json.dumps(renderer, indent=4))
f.close()
arcpy.management.ApplySymbologyFromLayer(str(layerPolygon), path_style2)
os.remove(path_style)
os.remove(path_style2)
###########################################################################
layer = all_layers[0]
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\Layers_Speckle\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\Layers_Speckle\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)
+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. From the [latest release](https://github.com/specklesystems/speckle-arcgis/releases) download the whl file and the source code zip, unzip and locate the subfolder "speckle_arcgis_installer" on your machine and place whl file in it.
2. Clone the default ArcGIS Pro conda environment (or set the one you use, except the default one) 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. Adjust the path to your new environment python executable (variable "pythonPath" in "speckle_arcgis_installer/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 )
```
+2
View File
@@ -0,0 +1,2 @@
try: from speckle.speckle_arcgis import *
except: from speckle_toolbox.esri.toolboxes.speckle.speckle_arcgis import *
+122 -17
View File
@@ -8,50 +8,155 @@ 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 "X:\\xxx.whl
return
def clearToolbox(pythonExec: str):
# install pip
print("CLEAR toolbox")
print(pythonExec)
try:
speckle_path = pythonExec.replace("python.exe","Lib\\site-packages\\")
print(speckle_path)
paths = os.listdir(speckle_path)
for p in paths:
if "speckle_toolbox" in p:
print("remove: " + str(p))
os.remove(p)
except Exception as e:
print(e)
pass
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}"])
elif pkgName == "PyQt5":
import PyQt5
if pythonExec.replace("\\python.exe","") not in (os.path.abspath(PyQt5.__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:
clearToolbox(pythonPath)
installToolbox(pythonPath)
installDependencies(pythonPath, "specklepy", "2.9.0" )
installDependencies(pythonPath, "panda3d", "1.10.11" )
installDependencies(pythonPath, "PyQt5", "5.15.9" )
# manual: import sysconfig; import subprocess; x = sysconfig.get_paths()['data'] + r"\python.exe"; subprocess.run((x, "-m", "pip", "install", "PyQt5==5.15.9"), capture_output=True, text=True, shell=True, timeout=1000 )
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)
@@ -1,3 +0,0 @@
@echo off
"%PROGRAMFILES%\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\python.exe" "conda_clone_activate.py"
"%LOCALAPPDATA%\ESRI\conda\envs\arcgispro-py3-speckle\python.exe" "toolbox_install.py"
+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
@@ -1,69 +0,0 @@
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-0.1-py3-none-any.whl" )
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
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") #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"
try:
import specklepy # C:\Users\username\AppData\Roaming\Python\Python37\site-packages\specklepy\__init__.py
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)
@@ -0,0 +1,63 @@
# 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\\toolbox_install_manual.py'), capture_output=True, text=True, shell=True, timeout=1000 )
# 4. Restart ArcGIS Pro
from subprocess_call import subprocess_call
import os
from os import listdir
from os.path import isfile, join
pythonPath = os.getenv('APPDATA').replace("\\Roaming","") + r"\Local\ESRI\conda\envs\arcgispro-py3-speckle\python.exe"
def installToolbox(newExec: str):
print("Installing Speckle Toolbox")
mypath = os.path.dirname(__file__)
onlyfiles = [f for f in listdir(mypath) if (isfile(join(mypath, f)) and "py3-none-any.whl" in str(f))]
onlyfiles.sort(key = lambda x: int(x.replace("speckle_toolbox-","").replace("-py3-none-any.whl","").split(".")[1]) )
whl_file = mypath + "\\" + onlyfiles[len(onlyfiles)-1]
#whl_file = os.path.join(os.path.dirname(__file__), "speckle_toolbox-2.11.3-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" )
+2 -1
View File
@@ -1 +1,2 @@
from speckle.speckle_arcgis import *
try: from speckle.speckle_arcgis import *
except: from speckle_toolbox.esri.toolboxes.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>20230303</ModDate><ModTime>104937</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
@@ -0,0 +1,214 @@
from regex import F
from specklepy.objects import Base
from specklepy.objects.geometry import Line, Mesh, Point, Polyline, Curve, Arc, Circle, Polycurve, Ellipse
import arcpy
from typing import Any, List, Union, Sequence
import inspect
try:
from speckle.converter.geometry.polygon import polygonToNative, polygonToSpeckle, multiPolygonToSpeckle, polygonToSpeckleMesh
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.ui.logger import logToUser
from speckle.converter.geometry.mesh import meshToNative
except:
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.polygon import polygonToNative, polygonToSpeckle, multiPolygonToSpeckle, polygonToSpeckleMesh
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.polyline import arcToNative, ellipseToNative, circleToNative, curveToNative, lineToNative, polycurveToNative, polylineFromVerticesToSpeckle, polylineToNative, polylineToSpeckle
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.point import pointToCoord, pointToNative, pointToSpeckle, multiPointToSpeckle
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.polyline import speckleArcCircleToPoints, specklePolycurveToPoints, multiPolylineToSpeckle
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.mesh import meshToNative
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
import numpy as np
def convertToSpeckle(feature, index: str, layer, geomType, featureType) -> Union[Base, Sequence[Base], None]:
"""Converts the provided layer feature to Speckle objects"""
print("___convertToSpeckle____________")
try:
geom = feature
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
if geomType == "Point": #Polygon, Point, Polyline, Multipoint, MultiPatch
for pt in geom:
return pointToSpeckle(pt, feature, layer)
elif geomType == "Polyline":
#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":
if geom.partCount > 1: return multiPolygonToSpeckle(geom, index, layer, geomMultiType)
else: return polygonToSpeckle(geom, index, layer, geomMultiType)
elif geomType == "Multipoint":
return multiPointToSpeckle(geom, feature, layer, geomMultiType)
elif geomType == "MultiPatch":
return polygonToSpeckleMesh(geom, index, layer, False)
else:
logToUser("Unsupported or invalid geometry in layer " + layer.name, level=1, func = inspect.stack()[0][3])
return None
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def convertToNative(base: Base, sr: arcpy.SpatialReference) -> Union[Any, None]:
"""Converts any given base object to QgsGeometry."""
print("___Convert to Native SingleType___")
converted = None
try:
#print(base)
conversions = [
(Point, pointToNative),
(Line, lineToNative),
(Polyline, polylineToNative),
(Curve, curveToNative),
(Arc, arcToNative),
(Circle, circleToNative),
(Ellipse, ellipseToNative),
#(Mesh, meshToNative),
(Polycurve, polycurveToNative),
(Base, polygonToNative), # temporary solution for polygons (Speckle has no type Polygon yet)
]
for conversion in conversions:
if isinstance(base, conversion[0]):
#print(conversion[0])
converted = conversion[1](base, sr)
break
#print(converted)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return converted
def multiPointToNative(items: List[Point], sr: arcpy.SpatialReference):
print("___Create MultiPoint")
features = None
try:
all_pts = []
# example https://pro.arcgis.com/en/pro-app/2.8/arcpy/classes/multipoint.htm
for item in items:
pt = pointToCoord(item) # [x, y, z]
all_pts.append( arcpy.Point(pt[0], pt[1], pt[2]) )
#print(all_pts)
features = arcpy.Multipoint( arcpy.Array(all_pts) )
#if len(features)==0: features = None
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return features
def multiPolylineToNative(items: List[Polyline], sr: arcpy.SpatialReference):
print("_______Drawing Multipolylines____")
poly = None
try:
#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 )
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return poly
def multiPolygonToNative(items: List[Base], sr: arcpy.SpatialReference): #TODO fix multi features
print("_______Drawing Multipolygons____")
polygon = None
try:
#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()]
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])
geomPart = []
try:
for void in item["voids"]:
#print(void)
#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])
geomPart.append(arcpy.Array(inner_arr))
except:pass
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)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return polygon
def convertToNativeMulti(items: List[Base], sr: arcpy.SpatialReference):
print("___Convert to Native MultiType___")
try:
first = items[0]
if isinstance(first, Point):
return multiPointToNative(items, sr)
elif isinstance(first, Line) or isinstance(first, Polyline):
return multiPolylineToNative(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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
@@ -1,117 +0,0 @@
from regex import F
from specklepy.objects import Base
from specklepy.objects.geometry import Line, Mesh, Point, Polyline, Curve, Arc, Circle, Polycurve
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.point import pointToCoord, pointToNative, pointToSpeckle, multiPointToSpeckle
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
geomMultiType = geom.isMultipart
hasCurves = feature.hasCurves
#print(featureType) # e.g. Simple
#print(geomType) # e.g. Polygon
#geomSingleType = (featureType=="Simple") # Simple,SimpleJunction,SimpleJunction,ComplexEdge,Annotation,CoverageAnnotation,Dimension,RasterCatalogItem
if geomType == "Point": #Polygon, Point, Polyline, Multipoint, MultiPatch
for pt in geom:
return pointToSpeckle(pt, feature, layer)
elif geomType == "Polyline":
return polylineToSpeckle(geom, feature, layer, geomMultiType)
elif geomType == "Polygon":
return polygonToSpeckle(geom, feature, layer, geomMultiType)
elif geomType == "Multipoint":
return multiPointToSpeckle(geom, feature, layer, geomMultiType)
else:
arcpy.AddWarning("Unsupported or invalid geometry in layer " + layer.name)
return None
def convertToNative(base: Base, sr: arcpy.SpatialReference) -> Union[Any, None]:
"""Converts any given base object to QgsGeometry."""
print("___Convert to Native SingleType___")
#print(base)
converted = None
conversions = [
(Point, pointToNative),
(Line, lineToNative),
(Polyline, polylineToNative),
(Curve, curveToNative),
(Arc, arcToNative),
(Circle, circleToNative),
#(Mesh, meshToNative),
(Polycurve, polycurveToNative),
(Base, polygonToNative), # temporary solution for polygons (Speckle has no type Polygon yet)
]
for conversion in conversions:
if isinstance(base, conversion[0]):
#print(conversion[0])
converted = conversion[1](base, sr)
break
#print(converted)
return converted
def multiPointToNative(items: List[Point], sr: arcpy.SpatialReference):
print("___Create MultiPoint")
all_pts = []
# example https://pro.arcgis.com/en/pro-app/2.8/arcpy/classes/multipoint.htm
for item in items:
pt = pointToCoord(item) # [x, y, z]
all_pts.append( arcpy.Point(pt[0], pt[1], pt[2]) )
#print(all_pts)
features = arcpy.Multipoint( arcpy.Array(all_pts) )
#if len(features)==0: features = None
return features
def multiPolylineToNative(items: List[Polyline], sr: arcpy.SpatialReference):
print("_______Drawing Multipolylines____")
#print(items)
poly = None
return poly
def multiPolygonToNative(items: List[Base], sr: arcpy.SpatialReference): #TODO fix multi features
print("_______Drawing Multipolygons____")
#print(items)
for item in items: # will be 1 item
#print(item)
pts = [pointToCoord(pt) for pt in item["boundary"].as_points()]
outer_arr = [arcpy.Point(*coords) for coords in pts]
outer_arr.append(outer_arr[0])
list_of_arrs = []
try:
for void in item["voids"]:
#print(void)
pts = [pointToCoord(pt) for pt in void.as_points()]
#print(pts)
inner_arr = [arcpy.Point(*coords) for coords in pts]
inner_arr.append(inner_arr[0])
list_of_arrs.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)
return polygon
def convertToNativeMulti(items: List[Base], sr: arcpy.SpatialReference):
print("___Convert to Native MultiType___")
first = items[0]
if isinstance(first, Point):
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)
@@ -1,11 +1,281 @@
from specklepy.objects.geometry import Mesh
from typing import List
import arcpy
import math
from specklepy.objects.geometry import Mesh, Point
from specklepy.objects.other import RenderMaterial
import inspect
import shapefile
from shapefile import TRIANGLE_STRIP, TRIANGLE_FAN, OUTER_RING
try:
from speckle.converter.layers.utils import get_scale_factor
from speckle.converter.geometry.point import pointToNative
from speckle.converter.layers.symbology import featureColorfromNativeRenderer
from speckle.converter.layers.utils import get_scale_factor
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.utils import get_scale_factor
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.point import pointToNative
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.symbology import featureColorfromNativeRenderer
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.utils import get_scale_factor
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
from panda3d.core import Triangulator
def meshToNative(meshes: List[Mesh], path: str):
"""Converts a Speckle Mesh to MultiPatch"""
print("06___________________Mesh to Native")
try:
#print(meshes)
#print(mesh.units)
w = shapefile.Writer(path)
w.field('speckleTyp', 'C')
shapes = []
for geom in meshes:
try:
if geom.displayValue and isinstance(geom.displayValue, Mesh):
mesh = geom.displayValue
w = fill_mesh_parts(w, mesh)
elif geom.displayValue and isinstance(geom.displayValue, List):
for part in geom.displayValue:
if isinstance(part, Mesh):
mesh = part
w = fill_mesh_parts(w, mesh)
except:
try:
if geom.displayMesh and isinstance(geom.displayMesh, Mesh):
mesh = geom.displayMesh
w = fill_mesh_parts(w, mesh)
elif geom.displayMesh and isinstance(geom.displayMesh, List):
for part in geom.displayMesh:
if isinstance(part, Mesh):
mesh = part
w = fill_mesh_parts(w, mesh)
except: pass
w.close()
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return path
def meshToNative(mesh: Mesh):
"""Converts a Speckle Mesh to QgsGeometry. Currently UNSUPPORTED"""
return None
def writeMeshToShp(meshes: List[Mesh], path: str):
"""Converts a Speckle Mesh to native geometry"""
print("06___________________Mesh to Native")
try:
#print(meshes)
#print(mesh.units)
w = shapefile.Writer(path)
w.field('speckle_id', 'C')
def rasterToMesh(vertices, faces, colors):
mesh = Mesh.create(vertices, faces, colors)
mesh.units = "m"
shapes = []
for geom in meshes:
if geom.speckle_type =='Objects.Geometry.Mesh' and isinstance(geom, Mesh):
mesh = geom
w = fill_mesh_parts(w, mesh, geom.id)
else:
try:
if geom.displayValue and isinstance(geom.displayValue, Mesh):
mesh = geom.displayValue
w = fill_mesh_parts(w, mesh, geom.id)
elif geom.displayValue and isinstance(geom.displayValue, List):
w = fill_multi_mesh_parts(w, geom.displayValue, geom.id)
except:
try:
if geom["@displayValue"] and isinstance(geom["@displayValue"], Mesh):
mesh = geom["@displayValue"]
w = fill_mesh_parts(w, mesh, geom.id)
elif geom["@displayValue"] and isinstance(geom["@displayValue"], List):
w = fill_multi_mesh_parts(w, geom["@displayValue"], geom.id)
except:
try:
if geom.displayMesh and isinstance(geom.displayMesh, Mesh):
mesh = geom.displayMesh
w = fill_mesh_parts(w, mesh, geom.id)
elif geom.displayMesh and isinstance(geom.displayMesh, List):
w = fill_multi_mesh_parts(w, geom.displayMesh, geom.id)
except: pass
w.close()
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return path
def fill_multi_mesh_parts(w: shapefile.Writer, meshes: List[Mesh], geom_id: str):
try:
parts_list = []
types_list = []
for mesh in meshes:
if not isinstance(mesh, Mesh): continue
try:
#print(f"Fill multi-mesh parts # {geom_id}")
parts_list_x, types_list_x = deconstructSpeckleMesh(mesh)
parts_list.extend(parts_list_x)
types_list.extend(types_list_x)
except Exception as e: pass
w.multipatch(parts_list, partTypes=types_list ) # one type for each part
w.record(geom_id)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return w
def fill_mesh_parts(w: shapefile.Writer, mesh: Mesh, geom_id: str):
try:
#print(f"Fill mesh parts # {geom_id}")
parts_list, types_list = deconstructSpeckleMesh(mesh)
w.multipatch(parts_list, partTypes=types_list ) # one type for each part
w.record(geom_id)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return w
def deconstructSpeckleMesh(mesh: Mesh):
parts_list = []
types_list = []
try:
scale = get_scale_factor(mesh.units)
count = 0 # sequence of vertex (not of flat coord list)
for f in mesh.faces: # real number of loops will be at least 3 times less
try:
vertices = mesh.faces[count]
if mesh.faces[count] == 0: vertices = 3
if mesh.faces[count] == 1: vertices = 4
face = []
for i in range(vertices):
index_faces = count + 1 + i
index_vertices = mesh.faces[index_faces]*3
face.append([ scale * mesh.vertices[index_vertices], scale * mesh.vertices[index_vertices+1], scale * mesh.vertices[index_vertices+2] ])
parts_list.append(face)
types_list.append(OUTER_RING)
count += vertices + 1
except: break # when out of range
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return parts_list, types_list
def constructMeshFromRaster(vertices, faces, colors):
mesh = None
try:
mesh = Mesh.create(vertices, faces, colors)
mesh.units = "m"
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return mesh
def constructMesh(vertices, faces, colors):
mesh = None
try:
mesh = Mesh.create(vertices, faces, colors)
mesh.units = "m"
material = RenderMaterial()
material.diffuse = colors[0]
mesh.renderMaterial = material
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return mesh
def meshPartsFromPolygon(polyBorder: List[Point], voidsAsPts: List[List[Point]], existing_vert: int, index: int, layer):
try:
#print("__meshPartsFromPolygon__")
vertices = []
total_vertices = 0
#print(layer)
try: sr = arcpy.Describe(layer.dataSource).spatialReference
except Exception as e:
print(e)
sr = None
#print(sr)
coef = 1
maxPoints = 5000
if len(polyBorder) >= maxPoints: coef = int(len(polyBorder)/maxPoints)
if len(voidsAsPts) == 0: # only if there is a mesh with no voids and large amount of points
#print("mesh with no voids")
for k, ptt in enumerate(polyBorder): #pointList:
pt = polyBorder[k*coef]
if k < maxPoints:
#if isinstance(pt, QgsPointXY):
# pt = QgsPoint(pt)
#print(pt)
if isinstance(pt,Point):
pt = pointToNative(pt, sr).getPart()
x = pt.X
y = pt.Y
z = 0 if math.isnan(pt.Z) else pt.Z
vertices.extend([x, y, z])
total_vertices += 1
else: break
ran = range(0, total_vertices)
faces = [total_vertices]
faces.extend([i + existing_vert for i in ran])
# else: https://docs.panda3d.org/1.10/python/reference/panda3d.core.Triangulator
else: # if there are voids
print("mesh with voids")
# if its a large polygon with voids to be triangualted, lower the coef even more:
maxPoints = 100
if len(polyBorder) >= maxPoints: coef = int(len(polyBorder)/maxPoints)
trianglator = Triangulator()
faces = []
pt_count = 0
# add extra middle point for border
for k, ptt in enumerate(polyBorder): #pointList:
pt = polyBorder[k*coef]
if k < maxPoints:
if pt_count < len(polyBorder)-1 and k < (maxPoints-1):
pt2 = polyBorder[(k+1)*coef]
else: pt2 = polyBorder[0]
trianglator.addPolygonVertex(trianglator.addVertex(pt.x, pt.y))
vertices.extend([pt.x, pt.y, pt.z])
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])
total_vertices += 2
pt_count += 1
else: break
#add void points
for pts in voidsAsPts:
trianglator.beginHole()
coefVoid = 1
if len(pts) >= maxPoints: coefVoid = int(len(pts)/maxPoints)
for k, ptt in enumerate(pts):
pt = pts[k*coefVoid]
if k < maxPoints:
trianglator.addHoleVertex(trianglator.addVertex(pt.x, pt.y))
vertices.extend([pt.x, pt.y, pt.z])
total_vertices += 1
else: break
trianglator.triangulate()
i = 0
#print(trianglator.getNumTriangles())
while i < trianglator.getNumTriangles():
tr = [trianglator.getTriangleV0(i),trianglator.getTriangleV1(i),trianglator.getTriangleV2(i)]
faces.extend([3, tr[0] + existing_vert, tr[1] + existing_vert, tr[2] + existing_vert])
i+=1
ran = range(0, total_vertices)
#print("color")
col = featureColorfromNativeRenderer(index, layer) #(100<<16) + (100<<8) + 100
colors = [col for i in ran] # apply same color for all vertices
return total_vertices, vertices, faces, colors
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None, None, None, None
@@ -3,23 +3,29 @@ from typing import List
from specklepy.objects.geometry import Point
import arcpy
from speckle.converter.layers.utils import get_scale_factor
import inspect
try:
from speckle.converter.layers.utils import get_scale_factor
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.utils import get_scale_factor
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
def multiPointToSpeckle(geom, feature, layer, multiType: bool):
"""Converts a Point to Speckle"""
#try:
#print("___Point to Speckle____")
#point = Point(units = "m")
pointList = []
#print(geom) # <geoprocessing describe geometry object object at 0x0000020F1D94AB10>
#print(multiType)
if multiType is False:
for pt in geom:
#print(pt) # 284394.58100903 5710688.11602606 NaN NaN <class 'arcpy.arcobjects.arcobjects.Point'>
#print(type(pt))
if pt != None: pointList.append(pointToSpeckle(pt, feature, layer))
try:
if multiType is False:
for pt in geom:
#print(pt) # 284394.58100903 5710688.11602606 NaN NaN <class 'arcpy.arcobjects.arcobjects.Point'>
#print(type(pt))
if pt != None: pointList.append(pointToSpeckle(pt, feature, layer))
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return pointList
def pointToSpeckle(pt, feature, layer):
@@ -29,42 +35,67 @@ def pointToSpeckle(pt, feature, layer):
# when unset, z() returns "nan"
#print(pt) # 4.9046319 52.3592043 NaN NaN
#print("____Point to Speckle___")
x = pt.X
y = pt.Y
if pt.Z: z = pt.Z
else: z = 0
specklePoint = Point(units = "m")
specklePoint.x = x
specklePoint.y = y
specklePoint.z = z
'''
col = featureColorfromNativeRenderer(feature, layer)
specklePoint['displayStyle'] = {}
specklePoint['displayStyle']['color'] = col
'''
#print(specklePoint)
return specklePoint
try:
x = pt.X
y = pt.Y
if pt.Z: z = pt.Z
else: z = 0
specklePoint = Point(units = "m")
specklePoint.x = x
specklePoint.y = y
specklePoint.z = z
'''
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def pointToNative(pt: Point, sr: arcpy.SpatialReference) -> arcpy.PointGeometry:
"""Converts a Speckle Point to QgsPoint"""
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
try:
pt = scalePointToNative(pt, pt.units)
geom = arcpy.PointGeometry(arcpy.Point(pt.x, pt.y, pt.z), sr, has_z = True)
#print(geom)
return geom
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def pointToCoord(pt: Point) -> List[float]:
def pointToCoord(point: Point) -> List[float]:
"""Converts a Speckle Point to QgsPoint"""
pt = scalePointToNative(pt, pt.units)
coords = [pt.x, pt.y, pt.z]
#print(coords)
return coords
try:
pt = scalePointToNative(point, point.units)
coords = [pt.x, pt.y, pt.z]
#print(coords)
return coords
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return [None, None, None]
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
return pt
try:
scaleFactor = get_scale_factor(units)
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def addZtoPoint(coords: List):
try:
if len(coords) == 2: coords.append(0)
return coords
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
@@ -1,165 +1,294 @@
from typing import Sequence
from typing import List, Sequence, Union
import arcpy
import json
import json
from arcpy.arcobjects.arcobjects import SpatialReference
from specklepy.objects import Base
from specklepy.objects.geometry import Point
from speckle.converter.geometry.mesh import rasterToMesh
from speckle.converter.geometry.point import pointToCoord
from speckle.converter.geometry.polyline import polylineFromVerticesToSpeckle, circleToSpeckle
from specklepy.objects.geometry import Point, Arc, Circle, Polycurve, Polyline, Line
import inspect
try:
from speckle.converter.geometry.mesh import constructMesh, constructMeshFromRaster, meshPartsFromPolygon
from speckle.converter.geometry.point import pointToCoord, pointToNative
from speckle.converter.layers.symbology import featureColorfromNativeRenderer
from speckle.converter.geometry.polyline import (polylineFromVerticesToSpeckle,
circleToSpeckle,
speckleArcCircleToPoints,
curveToSpeckle,
specklePolycurveToPoints
)
from speckle.converter.geometry.utils import speckleBoundaryToSpecklePts
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.mesh import constructMeshFromRaster, constructMesh, meshPartsFromPolygon
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.point import pointToCoord, pointToNative
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.symbology import featureColorfromNativeRenderer
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.polyline import (polylineFromVerticesToSpeckle,
circleToSpeckle,
speckleArcCircleToPoints,
curveToSpeckle,
specklePolycurveToPoints
)
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.utils import speckleBoundaryToSpecklePts
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
import math
from panda3d.core import Triangulator
def polygonToSpeckle(geom, feature, layer, multiType: bool):
"""Converts a Polygon to Speckle"""
#try:
print("___Polygon to Speckle____")
def polygonToSpeckleMesh(feature, index: int, layer, multitype: bool):
print("________polygonToSpeckleMesh_____")
print(feature)
polygon = Base(units = "m")
pointList = []
voidPointList = []
voids = []
boundary = None
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)
try:
#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):
for pt in p:
#print(pt) # 284394.58100903 5710688.11602606 NaN NaN
if pt == None and boundary == None: # first break
boundary = polylineFromVerticesToSpeckle(pointList, True, feature, layer)
pointList = []
elif pt == None and boundary != None: # breaks btw voids
void = polylineFromVerticesToSpeckle(pointList, True, feature, layer)
voids.append(void)
pointList = []
elif pt != None: # add points to whatever list (boundary or void)
pointList.append(pt)
if boundary != None and len(pointList)>0: # remaining polyline
void = polylineFromVerticesToSpeckle(pointList, True, feature, layer)
voids.append(void)
polygon.boundary = boundary
polygon.voids = voids
polygon.displayValue = [ boundary ] + voids
############# mesh
vertices = []
total_vertices = 0
polyBorder = boundary.as_points()
#print(polyBorder)
if len(polyBorder)>2:
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
vertices.extend([x, y, z])
total_vertices += 1
#print(vertices)
ran = range(0, total_vertices)
faces = [total_vertices]
faces.extend([i for i in ran])
#print(faces)
# else: https://docs.panda3d.org/1.10/python/reference/panda3d.core.Triangulator
else:
trianglator = Triangulator()
faces = []
# add boundary points
#polyBorder = boundary.as_points()
pt_count = 0
# add extra middle point for border
for pt in polyBorder:
if pt_count < len(polyBorder)-1:
pt2 = polyBorder[pt_count+1]
else: pt2 = polyBorder[0]
trianglator.addPolygonVertex(trianglator.addVertex(pt.x, pt.y))
vertices.extend([pt.x, pt.y, pt.z])
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])
total_vertices += 2
pt_count += 1
#add void points
for i in range(len(voids)):
trianglator.beginHole()
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])
total_vertices += 1
trianglator.triangulate()
i = 0
while i < trianglator.getNumTriangles():
tr = [trianglator.getTriangleV0(i),trianglator.getTriangleV1(i),trianglator.getTriangleV2(i)]
faces.extend([3, tr[0], tr[1], tr[2]])
i+=1
ran = range(0, total_vertices)
vertices = []
faces = []
colors = []
existing_vert = 0
for i, p in enumerate(feature):
#print("____start enumerate feature")
#print(p) #<geoprocessing array object object at 0x0000026796C77110>
boundary, voids = getPolyBoundaryVoids(p, layer, multitype)
#print(boundary)
#print(voids)
polyBorder = speckleBoundaryToSpecklePts(boundary)
#print(polyBorder)
voidsAsPts = []
for v in voids:
pts = speckleBoundaryToSpecklePts(v)
voidsAsPts.append(pts)
#print(voidsAsPts)
#print("__to start meshPartsFromPolygon")
total_vert, vertices_x, faces_x, colors_x = meshPartsFromPolygon(polyBorder, voidsAsPts, existing_vert, index, layer)
existing_vert += total_vert
vertices.extend(vertices_x)
faces.extend(faces_x)
colors.extend(colors_x)
#print("Colors: ")
#print(colors)
mesh = constructMesh(vertices, faces, colors)
polygon.displayValue = [ mesh ]
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return polygon
def getPolyBoundaryVoids(geom, layer, multiType: bool):
#print("__getPolyBoundaryVoids__")
voids: List[Union[None, Polyline, Arc, Line, Polycurve]] = []
#print(voids)
boundary = None
pointList = []
try:
partsBoundaries = []
partsVoids = []
if multiType is False: # Multipolygon
try: # might be no property "has curves"
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", geom, layer)
else:
print("no curves")
for p in geom:
for pt in p:
#print(pt)
if pt != None: pointList.append(pt)
boundary = polylineFromVerticesToSpeckle(pointList, True, geom, layer)
print(boundary)
except: # for multipatches, no property "has curves"
#print(geom)
for pt in geom:
#print(pt)
if pt != None: pointList.append(pt)
boundary = polylineFromVerticesToSpeckle(pointList, True, geom, layer)
partsBoundaries.append(boundary)
partsVoids.append([])
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
boundary = polylineFromVerticesToSpeckle(pointList, True, geom, layer)
pointList = []
elif pt == None and boundary != None: # breaks btw voids
void = polylineFromVerticesToSpeckle(pointList, True, geom, layer)
voids.append(void)
pointList = []
elif pt != None: # add points to whatever list (boundary or void)
pointList.append(pt)
if boundary != None and len(pointList)>0: # remaining polyline
void = polylineFromVerticesToSpeckle(pointList, True, geom, layer)
voids.append(void)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return boundary, voids
def multiPolygonToSpeckle(geom, index: str, layer, multiType: bool):
print("___MultiPolygon to Speckle____")
polygon = []
try:
#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(geom): # [[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, index, layer, poly.isMultipart))
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return polygon
def polygonToSpeckle(geom, index: int, layer, multitype: bool):
"""Converts a Polygon to Speckle"""
polygon = Base(units = "m")
try:
print("___Polygon to Speckle____")
print(geom)
boundary, voids = getPolyBoundaryVoids(geom, layer, multitype)
data = arcpy.Describe(layer.dataSource)
sr = data.spatialReference
if boundary is None: return None
polygon.boundary = boundary
polygon.voids = voids
polygon.displayValue = [ boundary ] + voids
#print(boundary)
############# mesh
vertices = []
polyBorder = []
total_vertices = 0
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: # 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:
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)
ran = range(0, total_vertices)
faces = [total_vertices]
faces.extend([i for i in ran])
#print(faces)
# else: https://docs.panda3d.org/1.10/python/reference/panda3d.core.Triangulator
else:
trianglator = Triangulator()
faces = []
# add boundary points
#polyBorder = boundary.as_points()
pt_count = 0
# add extra middle point for border
for pt in polyBorder:
if pt_count < len(polyBorder)-1:
pt2 = polyBorder[pt_count+1]
else: pt2 = polyBorder[0]
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 = []
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])
total_vertices += 1
trianglator.triangulate()
i = 0
while i < trianglator.getNumTriangles():
tr = [trianglator.getTriangleV0(i),trianglator.getTriangleV1(i),trianglator.getTriangleV2(i)]
faces.extend([3, tr[0], tr[1], tr[2]])
i+=1
ran = range(0, total_vertices)
#print(polygon)
col = featureColorfromNativeRenderer(index, layer)
colors = [col for i in ran] # apply same color for all vertices
mesh = constructMesh(vertices, faces, colors)
polygon.displayValue = mesh
#print("print resulted polygon")
#print(polygon)
col = (100<<16) + (100<<8) + 100 #featureColorfromNativeRenderer(feature, layer)
colors = [col for i in ran] # apply same color for all vertices
mesh = rasterToMesh(vertices, faces, colors)
polygon.displayValue = mesh
#print("print resulted polygon")
#print(polygon)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return polygon
def polygonToNative(poly: Base, sr: arcpy.SpatialReference) -> arcpy.Polygon:
@@ -168,21 +297,48 @@ 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()]
outer_arr = [arcpy.Point(*coords) for coords in pts]
outer_arr.append(outer_arr[0])
list_of_arrs = []
try:
for void in poly["voids"]:
#print(void)
pts = [pointToCoord(pt) for pt in void.as_points()]
#print(pts)
inner_arr = [arcpy.Point(*coords) for coords in pts]
inner_arr.append(inner_arr[0])
list_of_arrs.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)
polygon = None
try:
#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])
geomPart = []
try:
for void in poly["voids"]:
#print(void)
#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])
geomPart.append(arcpy.Array(inner_arr))
except:pass
geomPart.insert(0, outer_arr)
geomPartArray = arcpy.Array(geomPart)
polygon = arcpy.Polygon(geomPartArray, sr, has_z=True)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return polygon
@@ -1,146 +1,485 @@
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.layers.utils import get_scale_factor
import inspect
def circleToSpeckle(center, point, layer):
try:
from speckle.converter.geometry.point import pointToCoord, pointToSpeckle, addZtoPoint
from speckle.converter.layers.utils import get_scale_factor
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.point import pointToCoord, pointToSpeckle, addZtoPoint
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.utils import get_scale_factor
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
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)
if len(center)>2: center_z = center[2]
else: center_z = 0
length = rad*2*math.pi
domain = [0, length]
plane = [center[0], center[1], center_z, 0,0,1, 1,0,0, 0,1,0]
units = 3 #"m"
try:
rad = math.sqrt(math.pow((center[0] - point[0]),2) + math.pow((center[1] - point[1]),2) )
#print(rad)
if len(center)>2: center_z = center[2]
else: center_z = 0
length = rad*2*math.pi
domain = [0, length]
plane = [center[0], center[1], center_z, 0,0,1, 1,0,0, 0,1,0]
units = 3 #"m"
args = [0] + [rad] + domain + plane + [units]
#print(args)
c = Circle().from_list(args)
#print(c)
return c
args = [0] + [rad] + domain + plane + [units]
#print(args)
c = Circle.from_list(args)
c.plane.origin.units = "m"
c.units = "m"
#print(c)
return c
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def multiPolylineToSpeckle(geom, feature, layer, multiType: bool):
print("___MultiPolyline to Speckle____")
polyline = []
try:
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))
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return polyline
def polylineToSpeckle(geom, feature, layer, multiType: bool):
print("___Polyline to Speckle____")
polyline = None
pointList = []
#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)
try:
pointList = []
print(geom.hasCurves)
if multiType is False:
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)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
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____")
specklePts = []
for pt in vertices:
newPt = pointToSpeckle(pt, feature, layer)
specklePts.append(newPt)
# TODO: Replace with `from_points` function when fix is pushed.
polyline = Polyline(units = "m")
polyline.value = []
polyline.closed = closed
polyline.units = specklePts[0].units
for i, point in enumerate(specklePts):
if closed and i == len(specklePts) - 1:
continue
polyline.value.extend([point.x, point.y, point.z])
try:
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)
specklePts.append(newPt)
# TODO: Replace with `from_points` function when fix is pushed.
polyline.value = []
polyline.closed = closed
polyline.units = specklePts[0].units
for i, point in enumerate(specklePts):
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])
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return polyline
def arc3ptToSpeckle(p0: List, p1: List, p2: List, feature, layer) -> Arc:
print("____arc 3pt to Speckle___")
arc = Arc()
try:
p0 = addZtoPoint(p0)
p1 = addZtoPoint(p1)
p2 = addZtoPoint(p2)
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return arc
def curveBezierToSpeckle(segmStartCoord, segmEndCoord, knots, feature, layer):
print("____bezier curve to Speckle____")
try:
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def curveToSpeckle(geom, geomType, feature, layer) -> Union[Circle, Arc, Polyline, Polycurve]:
print("____curve to Speckle____")
boundary = Polycurve(units = "m")
print(geomType)
try:
# 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]]]
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
logToUser("SpeckleWarning: ellipse geometry not supported yet", level=1, func = inspect.stack()[0][3])
segments = []
break
else: # elliptical curve
logToUser("SpeckleWarning: ellipse geometry not supported yet", level=1, func = inspect.stack()[0][3])
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, geom, layer)
segments.append(segmentLocal)
print("segmentLocal:")
print(segmentLocal)
print(segmStartCoord)
print(segmEndCoord)
lastPt = segmEndCoord
if key2 == "b": # bezier curve (endPt, controlPts)
logToUser("SpeckleWarning: bezier curve geometry not supported yet", level=1, func = inspect.stack()[0][3])
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)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return boundary
def lineFrom2pt(pt1: List[float], pt2: List[float]):
line = Line(units = "m" )#.from_list([*pt1, *pt2, *domain])
try:
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.start = Point.from_list(pt1)
line.end = Point.from_list(pt2)
line.start.units = line.end.units = "m"
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
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 poly.closed is True:
pts.append( pointToCoord(poly.as_points()[0]) )
print("__ convert polyline to native __")
polyline = None
try:
pts_coord_list = [arcpy.Point(*coords) for coords in pts]
polyline = arcpy.Polyline( arcpy.Array(pts_coord_list), sr, has_z=True )
#print(polyline.JSON)
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]) )
pts_coord_list = [arcpy.Point(*coords) for coords in pts]
polyline = arcpy.Polyline( arcpy.Array(pts_coord_list), sr, has_z=True )
#print(polyline.JSON)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return polyline
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
try:
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def curveToNative(poly: Curve, sr: arcpy.SpatialReference) -> arcpy.Polyline:
"""Converts a Speckle Curve to QgsLineString"""
display = poly.displayValue
curve = polylineToNative(display, sr)
return curve
"""Converts a Speckle Curve to Native"""
try:
display = poly.displayValue
curve = polylineToNative(display, sr)
return curve
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
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))
return arc
"""Converts a Speckle Arc to Native"""
try:
arc = arcToNativePolyline(poly, sr) #QgsCircularString(pointToNative(poly.startPoint), pointToNative(poly.midPoint), pointToNative(poly.endPoint))
return arc
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def ellipseToNative(poly: Ellipse, sr: arcpy.SpatialReference):
logToUser("Ellipse geometry is not supported yet", level=1)
return
def circleToNative(poly: Circle, sr: arcpy.SpatialReference) -> arcpy.Polyline:
"""Converts a Speckle Circle to QgsLineString"""
print("___Convert Circle from Native___")
points = []
angle1 = math.pi/2
print("___Convert Circle to Native___")
curve = None
try:
points = []
angle1 = math.pi/2
pointsNum = math.floor(math.pi*2) * 12
if pointsNum <4: pointsNum = 4
points.append(pointToCoord(poly.plane.origin))
radScaled = poly.radius * get_scale_factor(poly.units)
points[0][1] += radScaled
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 * math.pi*2 * normal
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])
curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr , has_z=True)
pointsNum = math.floor(math.pi*2) * 12
if pointsNum <4: pointsNum = 4
points.append(pointToCoord(poly.plane.origin))
radScaled = poly.radius * get_scale_factor(poly.units)
points[0][1] += radScaled
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 * 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.units = "m"
points.append(pointToCoord(pt))
points.append(points[0])
curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr , has_z=True)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return curve
def polycurveToNative(poly: Polycurve, sr: arcpy.SpatialReference) -> arcpy.Polyline:
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")
logToUser(f"Part of the polycurve cannot be converted", level=1, func = inspect.stack()[0][3])
curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr , has_z=True)
return curve
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,57 +488,194 @@ 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")
logToUser(f"Part of the polycurve cannot be converted", level=1, func = inspect.stack()[0][3])
curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True )
return curve
except: curve = None
#print(curve)
curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True )
#print(curve)
curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True )
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return curve
def arcToNativePoints(poly: Arc, sr: arcpy.SpatialReference):
print("__Arc to native__")
def arcToNativePolyline(poly: Union[Arc, Circle], sr: arcpy.SpatialReference):
print("__Arc/Circle to native polyline__")
curve = None
try:
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 )
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return curve
def specklePolycurveToPoints(poly: Polycurve) -> List[Point]:
print("_____Speckle Polycurve to points____")
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)
try:
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()
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)
points.extend(pts)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return points
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))
curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True )
return curve
def speckleArcCircleToPoints(poly: Union[Arc, Circle]) -> List[Point]:
print("__Arc or Circle to Points___")
points = []
try:
#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)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return points
def getArcRadianAngle(arc: Arc) -> List[float]:
try:
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None, None, None
def getArcAngles(poly: Arc) -> Tuple[float]:
try:
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None, None
def getArcNormal(poly: Arc, midPt: Point):
print("____getArcNormal___")
try:
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def getArcCenter(p1: Point, p2: Point, p3: Point) -> Tuple[List, float]:
#print(p1)
try:
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None, None
@@ -0,0 +1,31 @@
from specklepy.objects.geometry import Point, Line, Polyline, Circle, Arc, Polycurve
from specklepy.objects import Base
from typing import List, Union
import inspect
try:
from speckle.converter.geometry.polyline import speckleArcCircleToPoints, specklePolycurveToPoints
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.polyline import speckleArcCircleToPoints, specklePolycurveToPoints
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
def speckleBoundaryToSpecklePts(boundary: Union[None, Polyline, Arc, Line, Polycurve]) -> List[Point]:
#print("__speckleBoundaryToSpecklePts__")
# add boundary points
polyBorder = []
try:
if isinstance(boundary, Circle) or isinstance(boundary, Arc):
polyBorder = speckleArcCircleToPoints(boundary)
elif isinstance(boundary, Polycurve):
polyBorder = specklePolycurveToPoints(boundary)
elif isinstance(boundary, Line): pass
else:
try: polyBorder = boundary.as_points()
except: pass # if Line or None
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return polyBorder
@@ -1,7 +1,11 @@
from typing import List, Optional
from specklepy.objects.base import Base
from speckle.converter.layers.CRS import CRS
try:
from speckle.converter.layers.CRS import CRS
except:
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.CRS import CRS
class Layer(Base, chunkable={"features": 100}):
@@ -27,14 +31,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",
File diff suppressed because it is too large Load Diff
@@ -1,492 +0,0 @@
"""
Contains all Layer related classes and methods.
"""
import os
from typing import Any, List, 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 specklepy.objects import Base
import arcgisscripting
import pandas as pd
import arcpy
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
def convertSelectedLayers(all_layers: List[arcpy._mp.Layer], selected_layers: List[str], project: arcpy.mp.ArcGISProject) -> List[Layer]:
"""Converts the current selected layers to Speckle"""
print("________Convert Layers_________")
result = []
for layer in selected_layers:
layerToSend = None
for c in range(len(all_layers)):
if int(layer.split("-",1)[0]) == c:
layerToSend = all_layers[c]
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)
elif layerToSend.isRasterLayer: pass
'''
if layer.name() in selectedLayerNames:
result.append(layerToSpeckle(layer, projectCRS, project))
'''
#print(result)
return result
def layerToSpeckle(layer: arcLayer, project: ArcGISProject) -> Layer: #now the input is QgsVectorLayer instead of qgis._core.QgsLayerTreeLayer
"""Converts a given QGIS Layer to Speckle"""
print("________Convert Feature Layer_________")
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)
print("__ finish iterating features")
speckleLayer.features=layerObjs
speckleLayer.geomType = data.shapeType
except OSError as e:
arcpy.AddWarning(str(e))
return
return speckleLayer
def layerToNative(layer: Union[Layer, RasterLayer], streamBranch: str, project: ArcGISProject):
if layer.type is None:
# Handle this case
return
elif layer.type.endswith("VectorLayer"):
return vectorLayerToNative(layer, streamBranch, project)
elif layer.type.endswith("RasterLayer"):
return rasterLayerToNative(layer, streamBranch, project)
return None
def cadLayerToNative(layerContentList:Base, layerName: str, streamBranch: str, project: ArcGISProject) :
geom_points = []
geom_polylines = []
geom_polygones = []
geom_meshes = []
#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.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":
geom_polylines.append(geom)
if len(geom_points)>0: layer_points = cadVectorLayerToNative(geom_points, layerName, "Points", streamBranch, project)
if len(geom_polylines)>0: layer_polylines = cadVectorLayerToNative(geom_polylines, layerName, "Polylines", streamBranch, project)
return [layer_points, layer_polylines]
def cadVectorLayerToNative(geomList, layerName: str, geomType: str, streamBranch: str, project: ArcGISProject):
print("_________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)
active_map = project.activeMap
path = project.filePath.replace("aprx","gdb") #"\\".join(project.filePath.split("\\")[:-1]) + "\\speckle_layers\\" #arcpy.env.workspace + "\\" #
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}'
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 "polygon" in geomType.lower(): geomType = "Polygon"
if "line" in geomType.lower(): geomType = "Polyline"
if "multipoint" in geomType.lower(): geomType = "Multipoint"
elif "point" in geomType.lower(): geomType = "Point"
#print(geomType)
#print(newName)
#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)___________________________________")
# 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(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"]
matrix = []
all_keys = []
all_key_types = []
max_len = 52
for key, value in newFields.items():
existingFields = [fl.name for fl in arcpy.ListFields(class_name)]
if key not in existingFields and key.lower() not in fields_to_ignore: # exclude geometry and default existing fields
# 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(matrix)
if len(matrix)>0: AddFields(str(f_class), matrix)
fets = []
for f in geomList[:]:
new_feat = cadFeatureToNative(f, newFields, sr)
if new_feat != "" and new_feat != None:
fets.append(new_feat)
#print("features created")
#print(fets)
count = 0
rowValues = []
for feat in fets:
try: feat['applicationId']
except: feat.update({'applicationId': count})
row = [feat['arcGisGeomFromSpeckle'], feat['applicationId']]
heads = [ 'Shape@', 'OBJECTID']
for key,value in feat.items():
if key in all_keys and key.lower() not in fields_to_ignore:
heads.append(key)
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)
return vl
def vectorLayerToNative(layer: Layer, 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("^","_")
print(layerName)
sr = arcpy.SpatialReference(text=layer.crs.wkt)
active_map = project.activeMap
path = project.filePath.replace("aprx","gdb") #"\\".join(project.filePath.split("\\")[:-1]) + "\\speckle_layers\\" #arcpy.env.workspace + "\\" #
#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
# particularly if the layer comes from ArcGIS
geomType = layer.geomType # for ArcGIS: Polygon, Point, Polyline, Multipoint, MultiPatch
print(geomType)
if "polygon" in geomType.lower(): geomType = "Polygon"
if "line" in geomType.lower(): geomType = "Polyline"
if "multipoint" in geomType.lower(): geomType = "Multipoint"
elif "point" in geomType.lower(): geomType = "Point"
#print(geomType)
#print(newName)
#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(project.filePath.replace("aprx","gdb"))
#print("_________create feature class___________________________________")
# should be created inside the workspace to be a proper Feature class (not .shp) with Nullable Fields
class_name = "f_class_" + newName
#print(class_name)
try: f_class = CreateFeatureclass(path, class_name, geomType, has_z="ENABLED", spatial_reference = sr)
except arcgisscripting.ExecuteError: class_name+="_"; f_class = CreateFeatureclass(path, class_name, geomType, has_z="ENABLED", spatial_reference = sr)
# get and set Layer attribute fields
# example: https://resource.esriuk.com/blog/an-introductory-slice-of-arcpy-in-arcgis-pro/
newFields = getLayerAttributes(layer.features)
fields_to_ignore = ["arcgisgeomfromspeckle", "shape", "objectid"]
matrix = []
all_keys = []
all_key_types = []
max_len = 52
for key, value in newFields.items():
existingFields = [fl.name for fl in arcpy.ListFields(class_name)]
if key not in existingFields and key.lower() not in fields_to_ignore: # exclude geometry and default existing fields
# 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(matrix)
if len(matrix)>0: AddFields(str(f_class), matrix)
fets = []
for f in layer.features:
new_feat = featureToNative(f, newFields, geomType, sr)
if new_feat != "" and new_feat!= None: fets.append(new_feat)
print(fets)
count = 0
rowValues = []
for feat in fets:
#print(feat)
try: feat['applicationId']
except: feat.update({'applicationId': count})
row = [feat['arcGisGeomFromSpeckle'], feat['applicationId']]
heads = [ 'Shape@', 'OBJECTID']
for key,value in feat.items():
if key in all_keys and key.lower() not in fields_to_ignore:
heads.append(key)
row.append(value)
rowValues.append(row)
count += 1
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
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)
r'''
# rename back the layer if was renamed due to existing duplicate
if layerExists:
vl.name = newName[:len(newName)-2]
for lyr in project.activeMap.listLayers():
print(lyr.longName)
if (streamBranch + "\\" + newName) == lyr.longName:
lyr.name = lyr.name.replace( lyr.name, lyr.name[:len(lyr.name)-2] )
lyr.longName = lyr.longName.replace( lyr.longName, lyr.longName[:len(newName)-2] )
break
'''
r'''
pr.addFeatures(fets)
vl.updateExtents()
vl.commitChanges()
#layerGroup.addLayer(vl)
rendererNew = vectorRendererToNative(layer)
if rendererNew is None:
symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType)))
rendererNew = QgsSingleSymbolRenderer(symbol)
try: vl.setRenderer(rendererNew)
except: pass
'''
return vl
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)
#CREATE A GROUP "received blabla" with sublayers
newGroupName = f'{streamBranch}'
root = QgsProject.instance().layerTreeRoot()
layerGroup = QgsLayerTreeGroup(newGroupName)
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])
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)
feat = layer.features[0]
bandNames = feat["Band names"]
bandValues = [feat["@(10000)" + name + "_values"] for name in bandNames]
#newName = f'{streamBranch}_latest_{layer.name}'
###########################################################################
## 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"]):
rasterband = np.array(bandValues[i])
rasterband = np.reshape(rasterband,(feat["Y pixels"], feat["X pixels"]))
ds.GetRasterBand(i+1).WriteArray(rasterband) # or "rasterband.T"
# 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
raster_layer = QgsRasterLayer(fn, newName, 'gdal')
QgsProject.instance().addMapLayer(raster_layer, False)
layerGroup.addLayer(raster_layer)
dataProvider = raster_layer.dataProvider()
rendererNew = rasterRendererToNative(layer, dataProvider)
try: raster_layer.setRenderer(rendererNew)
except: pass
'''
return raster_layer
@@ -1,144 +1,640 @@
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
import inspect
def featureToSpeckle(fieldnames, attr_list, f_shape, projectCRS: arcpy.SpatialReference, project: arcpy.mp.ArcGISProject, selectedLayer):
try:
from speckle.converter.geometry import convertToSpeckle, convertToNative, convertToNativeMulti
from speckle.converter.layers.utils import (findTransformation, getVariantFromValue, traverseDict,
traverseDictByKey, hsv_to_rgb)
from speckle.converter.geometry.point import pointToSpeckle
from speckle.converter.geometry.mesh import constructMeshFromRaster, meshToNative
from speckle.converter.layers.symbology import jsonFromLayerStyle
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry import convertToSpeckle, convertToNative, convertToNativeMulti
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.utils import (findTransformation, getVariantFromValue, traverseDict,
traverseDictByKey, hsv_to_rgb)
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.point import pointToSpeckle
from speckle_toolbox.esri.toolboxes.speckle.converter.geometry.mesh import constructMeshFromRaster, meshToNative
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.symbology import jsonFromLayerStyle
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
import numpy as np
import colorsys
def featureToSpeckle(fieldnames, attr_list, index: int, 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(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
######################################### Convert geometry ##########################################
try:
geom = convertToSpeckle(f_shape, selectedLayer, geomType, featureType)
if geom is not None: print(geom); b["geometry"] = geom
except Exception as error:
print("Error converting geometry: " + str(error))
print(selectedLayer)
arcpy.AddError("Error converting geometry: " + str(error))
#print(geomType)
#print(featureType)
for i, name in enumerate(fieldnames):
corrected = name.replace("/", "_").replace(".", "-")
if corrected != "Shape" and corrected != "Shape@":
# different ID behaviors: https://support.esri.com/en/technical-article/000010834
# save all attribute, duplicate one into applicationId
b[corrected] = attr_list[i]
if corrected == "FID" or corrected == "OID" or corrected == "OBJECTID": b["applicationId"] = str(attr_list[i])
#print(b)
print("______end of __Feature to Speckle____________________")
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 hasattr(data, "isRevit") or hasattr(data, "isIFC") or hasattr(data, "bimLevels"):
print(f"Layer {selectedLayer.name} has unsupported data type")
logToUser(f"Layer {selectedLayer.name} has unsupported data type", level=1, func = inspect.stack()[0][3])
return None
#print(layer_sr.name)
#print(projectCRS.name)
f_shape = findTransformation(f_shape, geomType, layer_sr, projectCRS, selectedLayer)
if f_shape is None: return None
######################################### Convert geometry ##########################################
try:
geom = convertToSpeckle(f_shape, index, selectedLayer, geomType, featureType)
if geom is not None: print(geom); b["geometry"] = geom
else: b["geometry"] = []
except Exception as error:
print("Error converting geometry: " + str(error))
print(selectedLayer)
logToUser("Error converting geometry: " + str(error), level=2, func = inspect.stack()[0][3])
print(geom)
#print(featureType)
for i, name in enumerate(fieldnames):
corrected = name.replace("/", "_").replace(".", "-")
if corrected != "Shape" and corrected != "Shape@":
# different ID behaviors: https://support.esri.com/en/technical-article/000010834
# save all attribute, duplicate one into applicationId
b[corrected] = attr_list[i]
if corrected == "FID" or corrected == "OID" or corrected == "OBJECTID": b["applicationId"] = str(attr_list[i])
#print(b)
print("______end of __Feature to Speckle____________________")
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
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
print(speckle_geom)
if isinstance(speckle_geom, list):
if len(speckle_geom)>1 or geomType == "Multipoint": arcGisGeom = convertToNativeMulti(speckle_geom, sr)
else: arcGisGeom = convertToNative(speckle_geom[0], sr)
else:
arcGisGeom = convertToNative(speckle_geom, sr)
try:
try: speckle_geom = feature["geometry"] # for created in QGIS / ArcGIS Layer type
except: speckle_geom = feature # for created in other software
#print(speckle_geom)
if isinstance(speckle_geom, list):
if len(speckle_geom)>1 or geomType == "Multipoint": 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
print(feat)
for key, variant in fields.items():
if arcGisGeom is not None:
feat.update({"arcGisGeomFromSpeckle": arcGisGeom})
else:
return None
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})
for key, variant in fields.items():
try: value = feature[key]
except:
if key == 'Speckle_ID': value = feature['id']
else:
arcpy.AddWarning(f'Field {key} not found')
return None
if variant == "TEXT": value = str(value)
if variant == getVariantFromValue(value) and value != "NULL" and value != "None":
feat.update({key: value})
else:
if variant == "TEXT": feat.update({key: None})
if variant == "FLOAT": feat.update({key: None})
if variant == "LONG": feat.update({key: None})
if variant == "SHORT": feat.update({key: None})
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return feat
def bimFeatureToNative(feature: Base, fields: dict, sr: arcpy.SpatialReference, path: str):
#print("04_________BIM Feature To Native____________")
feat_updated = {}
try:
feat = {}
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)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return feat_updated
def cadFeatureToNative(feature: Base, fields: dict, sr: arcpy.SpatialReference):
print("_________CAD Feature To Native____________")
print("04_________CAD Feature To Native____________")
feat = {}
try: speckle_geom = feature["geometry"] # for created in QGIS Layer type
except: speckle_geom = feature # for created in other software
#print(feature)
#print(speckle_geom)
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)
try:
try: speckle_geom = feature["geometry"] # for created in QGIS Layer type
except: speckle_geom = feature # for created in other software
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})
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
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)
print(feat)
print(fields)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return feat_updated
def addFeatVariant(key, variant, value, f):
#print("Add feat variant")
feat = f
try:
if variant == "TEXT": value = str(value)
if value != "NULL" and value != "None":
#if key == 'area': print(value); print(type(value)); print(getVariantFromValue(value))
if variant == getVariantFromValue(value): # or (variant=="FLOAT" and isinstance(value, int)):
feat.update({key: value})
elif variant == "LONG" and isinstance(value, float): # if object has been modified
feat.update({key: int(value)})
elif variant == "FLOAT" and isinstance(value, int): # if object has been modified
feat.update({key: float(value)})
else: feat.update({key: None})
elif variant == "TEXT" or variant == "FLOAT" or variant == "LONG" or variant == "SHORT": feat.update({key: None})
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return feat
def updateFeat(feat:dict, fields: dict, feature: Base) -> Dict[str, Any]:
#print("Update feat")
feat_sorted = {}
try:
for key, variant in fields.items():
try:
if key == "Speckle_ID":
value = str(feature["id"])
feat[key] = value
feat = addFeatVariant(key, variant, value, feat)
else:
try:
value = feature[key]
#if key == "area": print(feature[key]); print(type(feature[key]))
feat = addFeatVariant(key, variant, value, feat)
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
feat = addFeatVariant(key, variant, value, feat)
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
feat = addFeatVariant(key, variant, value, feat)
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_________________________")
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return feat_sorted
def rasterFeatureToSpeckle(selectedLayer: arcLayer, projectCRS: arcpy.SpatialReference, project: ArcGISProject) -> Base:
print("_________ Raster feature to speckle______")
b = Base(units = "m")
try:
# https://pro.arcgis.com/en/pro-app/latest/arcpy/classes/raster-object.htm
# 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 = []
# 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:
logToUser("Error converting point geometry: " + str(error), level=2, func = inspect.stack()[0][3])
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)
if rasterBandNoDataVal[len(rasterBandNoDataVal)-1] is None:
rasterBandNoDataVal[len(rasterBandNoDataVal)-1] = 'None'
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
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
#RGB colorizer
root_path = "\\".join(project.filePath.split("\\")[:-1])
if not os.path.exists(root_path + '\\Layers_Speckle\\raster_bands'): os.makedirs(root_path + '\\Layers_Speckle\\raster_bands')
path_style = root_path + '\\Layers_Speckle\\raster_bands\\' + selectedLayer.name + '_temp.lyrx'
symJson = jsonFromLayerStyle(selectedLayer, path_style)
# read from Json
print(symJson["layerDefinitions"][0]["colorizer"])
try: greenBand = symJson["layerDefinitions"][0]["colorizer"]["greenBandIndex"]
except: greenBand = None
try: blueBand = symJson["layerDefinitions"][0]["colorizer"]["blueBandIndex"]
except: blueBand = None
try: redBand = symJson["layerDefinitions"][0]["colorizer"]["redBandIndex"]
except:
if blueBand!=0 and greenBand!=0: redBand= 0
else: redBand = None
print("bands")
print(redBand)
print(greenBand)
print(blueBand)
try:
rbVals = rasterBandVals[redBand] #my_raster.getRasterBands(rasterBandNames[redBand])
rbvalMin = min(rbVals)
rbvalMax = max(rbVals)
rvalRange = float(rbvalMax) - float(rbvalMin)
print(rbvalMin)
print(rbvalMax)
print(rvalRange)
except Exception as e: print(e); rvalRange = None
try:
gbVals = rasterBandVals[greenBand]
gbvalMin = min(gbVals)
gbvalMax = max(gbVals)
gvalRange = float(gbvalMax) - float(gbvalMin)
print(gbvalMin)
print(gbvalMax)
print(gvalRange)
except: gvalRange = None
try:
bbVals = rasterBandVals[blueBand]
bbvalMin = min(bbVals)
bbvalMax = max(bbVals)
bvalRange = float(bbvalMax) - float(bbvalMin)
print(bbvalMin)
print(bbvalMax)
print(bvalRange)
except: bvalRange = None
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 hasattr(selectedLayer.symbology, 'colorizer'): # only 1 band
try: bandIndex = int(colorizer.band) # if stretched
except: pass
if colorizer.type =='RasterUniqueValueColorizer':
# REDO !!!!!!!!!!!!
colorRVal = colorGVal = colorBVal = 0
try:
for br in colorizer.groups:
print(br.heading) #"Value"
# go through all values classified
if br.heading != 'Value': print(int('x')) #call exception
for k, itm in enumerate(br.items):
print(itm.values)
if itm.values[0] == rasterBandVals[bandIndex][int(count/4)]:
print(itm.values[0])
colorRVal, colorGVal, colorBVal = itm.color['RGB'][0], itm.color['RGB'][1], itm.color['RGB'][2]
break
# if string covering float
try:
if float(itm.values[0]) == float(rasterBandVals[bandIndex][int(count/4)]):
print(itm.values[0])
colorRVal, colorGVal, colorBVal = itm.color['RGB'][0], itm.color['RGB'][1], itm.color['RGB'][2]
break
except Exception as e: print(e); pass
except Exception as e: # if no Min Max labels:
# REMAP band values to (0,255) range
print(e)
valRange = max(rasterBandVals[bandIndex]) - min(rasterBandVals[bandIndex]) #(rasterBandMaxVal[bandIndex] - rasterBandMinVal[bandIndex])
colorRVal = colorGVal = colorBVal = int( (rasterBandVals[bandIndex][int(count/4)] - min(rasterBandVals[bandIndex])) / valRange * 255 )
print("__pixel color_")
print(colorRVal)
print(colorGVal)
print(colorBVal)
color = (colorRVal<<16) + (colorGVal<<8) + colorBVal
else:
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: # rgb
# REMAP band values to (0,255) range
if rvalRange is not None and redBand is not None: colorRVal = int( (rasterBandVals[redBand][int(count/4)] - float(rbvalMin)) / rvalRange * 255 )
else: colorRVal = 0
if gvalRange is not None and greenBand is not None: colorGVal = int( (rasterBandVals[greenBand][int(count/4)] - float(gbvalMin)) / gvalRange * 255 )
else: colorGVal = 0
if bvalRange is not None and blueBand is not None: colorBVal = int( (rasterBandVals[blueBand][int(count/4)] - float(bbvalMin)) / bvalRange * 255 )
else: colorBVal = 0
print("__pixel color_")
print(colorRVal)
print(colorGVal)
print(colorBVal)
color = (colorRVal<<16) + (colorGVal<<8) + colorBVal
colors.extend([color,color,color,color])
count += 4
mesh = constructMeshFromRaster(vertices, faces, colors)
if(b['displayValue'] is None):
b['displayValue'] = []
b['displayValue'].append(mesh)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
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'}
'''
@@ -0,0 +1,704 @@
import json
from typing import Any, List, Tuple, Union
import copy
import os
from typing import Dict
import inspect
import arcpy
from arcpy._mp import ArcGISProject, Layer as arcLayer
from arcpy.management import (CreateFeatureclass, MakeFeatureLayer,
AddFields, AlterField, DefineProjection )
from specklepy.objects import Base
from specklepy.objects.other import RenderMaterial
try:
from speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
def jsonFromLayerStyle(layerArcgis, path_style):
# write updated renderer to file and get layerStyle variable
try:
arcpy.management.SaveToLayerFile(layerArcgis, path_style, False)
f = open(path_style, "r")
layerStyle = json.loads(f.read())
f.close()
os.remove(path_style)
return layerStyle
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def symbol_color_to_speckle(color: dict):
newColor = (0<<16) + (0<<8) + 0
try:
r = int(color['RGB'][0])
g = int(color['RGB'][1])
b = int(color['RGB'][2])
newColor = (r<<16) + (g<<8) + b
except Exception as e:
logToUser(str(e), level=1, func = inspect.stack()[0][3])
return newColor
def colorFromRenderMaterial(material):
color = {'RGB': [245, 245, 245, 100]} #Objects.Other.RenderMaterial
if material is not None:
try:
rgb = material.diffuse
r = (rgb & 0xFF0000) >> 16
g = (rgb & 0xFF00) >> 8
b = rgb & 0xFF
color = {'RGB': [r, g, b, 100]}
#print(color)
except Exception as e:
logToUser(str(e), level=1, func = inspect.stack()[0][3])
return color
def cadBimRendererToNative(project: ArcGISProject, active_map, layerGroup, fetColors: List[RenderMaterial], layerArcgis, f_class, existingAttrs: List) -> Union[None, Dict[str, Any]] :
print("___________APPLY VECTOR RENDERER______________")
print(layerArcgis)
print(f_class)
print(fetColors)
attribute = "Speckle_ID"
try:
root_path = "\\".join(project.filePath.split("\\")[:-1])
#path_style = root_path + '\\' + str(f_class).split('\\')[-1] + '_old.lyrx'
data = arcpy.Describe(layerArcgis.dataSource)
if layerArcgis.isFeatureLayer:
geomType = data.shapeType
sym = layerArcgis.symbology
cursor = arcpy.da.SearchCursor(f_class, attribute)
class_shapes = [shp_id[0] for n, shp_id in enumerate(cursor)]
del cursor
sym.updateRenderer('UniqueValueRenderer')
print(sym.renderer.type)
print(existingAttrs)
print(attribute)
sym.renderer.fields = [attribute]
for k, grp in enumerate(sym.renderer.groups):
for itm in grp.items:
transVal = itm.values[0][0] #Grab the first "percent" value in the list of potential values
#print(transVal)
for i in range(len(class_shapes)):
label = class_shapes[i]
#print(label)
if label is None or label=="" or str(label)=="": label = "<Null>"
if str(transVal) == label:
#print("found label")
material = fetColors[i]
#print(material)
itm.symbol.color = colorFromRenderMaterial(material)
itm.label = label
break
layerArcgis.symbology = sym
#print(layerArcgis)
return layerArcgis
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def vectorRendererToNative(project: ArcGISProject, active_map, layerGroup, layerSpeckle: Union[Layer, VectorLayer], layerArcgis, f_class, existingAttrs: List) -> Union[None, Dict[str, Any]] :
print("___________APPLY VECTOR RENDERER______________")
print(layerArcgis)
print(f_class)
try:
renderer = layerSpeckle.renderer
if renderer and renderer['type']:
print(renderer['type'])
root_path = "\\".join(project.filePath.split("\\")[:-1])
#path_style = root_path + '\\' + str(f_class).split('\\')[-1] + '_old.lyrx'
data = arcpy.Describe(layerArcgis.dataSource)
if layerArcgis.isFeatureLayer:
geomType = data.shapeType
sym = layerArcgis.symbology
if renderer['type'] == 'singleSymbol':
print("RENDERER SINGLE")
print(renderer)
r,g,b = get_rgb_from_speckle(renderer['properties']['symbol']['symbColor'])
#print(r,g,b)
#print(sym.renderer.symbol.color)
sym.renderer.symbol.color = {'RGB': [r, g, b, 100]}
#print(sym.renderer.symbol.color)
layerArcgis.symbology = sym # SimpleRenderer
#print(layerArcgis)
return layerArcgis
elif renderer['type'] == 'categorizedSymbol':
print("RENDERER CATEGORIZED")
print(renderer)
cats = renderer['properties']['categories']
attribute = renderer['properties']['attribute']
if attribute not in existingAttrs: return layerArcgis
#vl2 = active_map.addLayer(layerArcgis)[0]
#sym = layerArcgis.symbology
sym.updateRenderer('UniqueValueRenderer')
print(sym.renderer.type)
print(existingAttrs)
print(attribute)
sym.renderer.fields = [attribute]
for k, grp in enumerate(sym.renderer.groups):
for itm in grp.items:
transVal = itm.values[0][0] #Grab the first "percent" value in the list of potential values
for i in range(len(cats)):
label = cats[i]['value']
if label is None or label=="" or str(label)=="": label = "<Null>"
r,g,b = get_rgb_from_speckle(cats[i]['symbColor'])
if str(transVal) == label:
itm.symbol.color = {'RGB': [r, g, b, 100]}
itm.label = label
break
layerArcgis.symbology = sym
return layerArcgis
elif renderer['type'] == 'graduatedSymbol':
print("RENDERER GRADUATED")
print(renderer)
attribute = renderer['properties']['attribute']
gradMetod = renderer['properties']['gradMethod'] # by color or by size
if gradMetod != 0:
r,g,b = get_rgb_from_speckle(renderer['properties']['sourceSymbColor'] )
sym.renderer.symbol.color = {'RGB': [r, g, b, 100]}
layerArcgis.symbology = sym # SimpleRenderer
return layerArcgis
if attribute not in existingAttrs or gradMetod != 0: return layerArcgis # by color, not line width
sym.updateRenderer('GraduatedColorsRenderer')
print(sym.renderer.type)
r,g,b = get_rgb_from_speckle(renderer['properties']['sourceSymbColor'])
ramp = renderer['properties']['ramp'] # {discrete, rampType, stops}
ranges = renderer['properties']['ranges'] # []
# get all existing values
all_values = []
with arcpy.da.UpdateCursor(f_class, attribute) as cur:
for rowShape in cur:
all_values.append(rowShape[0])
print(all_values)
del cur
print(len(ranges))
sym.renderer.classificationField = attribute
print(sym.renderer.breakCount)
sym.renderer.breakCount = len(ranges)
print(sym.renderer.breakCount)
if len(sym.renderer.classBreaks) > 0:
totalClasses = 0
for k, br in enumerate(ranges):
print(totalClasses)
if sym.renderer.breakCount < len(ranges):
valFits = 0
# check if any existing value fits in this range:
for val in all_values:
if val <= ranges[k]["upper"] and (totalClasses==0 or (totalClasses>0 and sym.renderer.classBreaks[totalClasses-1].upperBound<val)):
valFits+=1
break
if valFits == 0: continue
r,g,b = get_rgb_from_speckle(ranges[k]['symbColor'])
#classBreak.upperBound = ranges[k]["upper"]
sym.renderer.classBreaks[totalClasses].upperBound = ranges[k]["upper"]
sym.renderer.classBreaks[totalClasses].label = ranges[k]["label"]
sym.renderer.classBreaks[totalClasses].symbol.color = {'RGB': [r, g, b, 100]}
totalClasses += 1
#layerArcgis.symbology = sym
print(ranges[k]["label"])
print(ranges[k]["upper"])
sym.renderer.classBreaks[0].upperBound = ranges[0]["upper"] # otherwise its assigned maximum value
layerArcgis.symbology = sym
return layerArcgis
else: return None
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def get_rgb_from_speckle(rgb: int) -> Tuple[int, int, int]:
r = g = b = 0
try:
r = (rgb & 0xFF0000) >> 16
g = (rgb & 0xFF00) >> 8
b = rgb & 0xFF
except: r = g = b = 0
r,g,b = check_rgb(r,g,b)
return r,g,b
def check_rgb(r:int, g:int, b:int) -> Tuple[int, int, int]:
try:
if not isinstance(r, int) or r<0 or r>255: r=g=b=0
if not isinstance(g, int) or g<0 or g>255: r=g=b=0
if not isinstance(b, int) or b<0 or b>255: r=g=b=0
return r,g,b
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return 0, 0, 0
def rasterRendererToNative(project: ArcGISProject, active_map, layerGroup, layer: RasterLayer, arcLayer, rasterPathsToMerge, newName):
print("_____rasterRenderer ToNative______")
try:
renderer = layer.renderer
rendererNew = None
print(renderer)
feat = layer.features[0]
print(feat)
bandNames = feat["Band names"]
print(bandNames)
sym = arcLayer.symbology
symJson = None
path_style = ""
path_style2 = ""
print(sym)
if renderer and renderer['type']:
if not hasattr(arcLayer.symbology, 'colorizer'):
# multiband raster, CIMRasterRGBColorizer
# arcpy doesnt support multiband raster symbology: https://community.esri.com/t5/arcgis-api-for-python-questions/why-does-arcpy-mp-arcgis-pro-2-6-mosaic-dataset/td-p/1016312
root_path = "\\".join(project.filePath.split("\\")[:-1])
if not os.path.exists(root_path + '\\Layers_Speckle\\raster_bands'): os.makedirs(root_path + '\\Layers_Speckle\\raster_bands')
path_style = root_path + '\\Layers_Speckle\\raster_bands\\' + newName + '_old.lyrx'
path_style2 = root_path + '\\Layers_Speckle\\raster_bands\\' + newName + '_new.lyrx'
symJson = jsonFromLayerStyle(arcLayer, path_style)
if renderer['type'] == 'singlebandgray':
print("Singleband grey")
band_index = renderer['properties']['band']-1
if symJson is None:
sym.updateColorizer('RasterStretchColorizer')
sym.colorizer.band = band_index
arcLayer.symbology = sym
else:
temp = arcpy.management.MakeRasterLayer(rasterPathsToMerge[band_index], newName + "_temp").getOutput(0)
active_map.addLayerToGroup(layerGroup, temp)
temp_layer = None
for l in active_map.listLayers():
if l.longName == layerGroup.longName + "\\" + newName + "_temp":
print(l.longName)
temp_layer = l
break
sym = temp_layer.symbology
sym.updateColorizer('RasterStretchColorizer')
sym.colorizer.band = band_index
arcLayer.symbology = sym
active_map.removeLayer(temp_layer)
elif renderer['type'] == 'multibandcolor':
print("Multiband")
if symJson is None:
sym.updateColorizer('RasterStretchColorizer')
arcLayer.symbology = sym
else:
redSt = copy.deepcopy(symJson["layerDefinitions"][0]["colorizer"]["stretchStatsRed"])
greenSt = copy.deepcopy(symJson["layerDefinitions"][0]["colorizer"]["stretchStatsGreen"])
blueSt = copy.deepcopy(symJson["layerDefinitions"][0]["colorizer"]["stretchStatsBlue"])
redBand = renderer['properties']['redBand']
greenBand = renderer['properties']['greenBand']
blueBand = renderer['properties']['blueBand']
try: symJson["layerDefinitions"][0]["colorizer"]["greenBandIndex"] = greenBand-1
except: symJson["layerDefinitions"][0]["colorizer"]["greenBandIndex"] = 0
try: symJson["layerDefinitions"][0]["colorizer"]["redBandIndex"] = redBand-1
except: symJson["layerDefinitions"][0]["colorizer"]["redBandIndex"] = 0
try: symJson["layerDefinitions"][0]["colorizer"]["blueBandIndex"] = blueBand-1
except: symJson["layerDefinitions"][0]["colorizer"]["blueBandIndex"] = 0
print(symJson)
f = open(path_style2, "w")
f.write(json.dumps(symJson, indent=2))
f.close()
active_map.removeLayer(arcLayer)
lyrFile = arcpy.mp.LayerFile(path_style2)
active_map.addLayerToGroup(layerGroup, lyrFile )
os.remove(path_style2)
elif renderer['type'] == 'paletted':
print("Paletted")
band_index = renderer['properties']['band']-1
if symJson is None:
for br in sym.colorizer.groups:
print(br.heading) #"Value"
# go through all values classified
for k, itm in enumerate(br.items):
if k< len(renderer['properties']['classes']):
#go through saved renderer classes
for n, cl in enumerate(renderer['properties']['classes']):
if k == n:
r,g,b = get_rgb_from_speckle(cl['color'])
itm.color = {'RGB': [r,g,b, 100]}
itm.label = cl['label']
itm.values = cl['value']
else: pass
arcLayer.symbology = sym
else:
sym.updateColorizer('RasterStretchColorizer')
arcLayer.symbology = sym
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return arcLayer
def rendererToSpeckle(project: ArcGISProject, active_map, arcLayer, rasterFeat: Base):
print("_____renderer To Speckle______")
try:
if arcLayer.isRasterLayer:
try:
rType = arcLayer.symbology.colorizer.type # 'singleSymbol','categorizedSymbol','graduatedSymbol',
if rType =='RasterStretchColorizer': rType = 'singlebandgray'
elif rType =='RasterUniqueValueColorizer': rType = 'paletted' # only for 1-band raster
else: rType = 'singlebandgray'
except:
rType = "multibandcolor"
root_path = "\\".join(project.filePath.split("\\")[:-1])
if not os.path.exists(root_path + '\\Layers_Speckle\\raster_bands'): os.makedirs(root_path + '\\Layers_Speckle\\raster_bands')
path_style = root_path + '\\Layers_Speckle\\raster_bands\\' + arcLayer.name + '_temp.lyrx'
#path_style2 = root_path + '\\' + newName + '_new.lyrx'
symJson = jsonFromLayerStyle(arcLayer, path_style)
layerRenderer: Dict[str, Any] = {}
layerRenderer['type'] = rType
print(rType)
my_raster = arcpy.Raster(arcLayer.dataSource)
rasterBandNames = my_raster.bandNames
#bandNames = rasterFeat["Band names"]
bandValues = [rasterFeat["@(10000)" + name + "_values"] for name in rasterBandNames]
if rType == "singlebandgray":
try: band = arcLayer.symbology.colorizer.band
except: band = 0
try:
bVals = bandValues[band]
bvalMin = min(bVals)
bvalMax = max(bVals)
except:
bvalMin = 0
bvalMax = 255
layerRenderer.update({'properties': {'max':bvalMax,'min':bvalMin,'band':band+1,'contrast':1}})
elif rType == "multibandcolor":
try: greenBand = symJson["layerDefinitions"][0]["colorizer"]["greenBandIndex"] +1
except: greenBand = None
try: blueBand = symJson["layerDefinitions"][0]["colorizer"]["blueBandIndex"] +1
except: blueBand = None
try: redBand = symJson["layerDefinitions"][0]["colorizer"]["redBandIndex"] +1
except:
print(greenBand)
print(blueBand)
if blueBand!=1 and greenBand!=1: redBand= 1
else: redBand = None
print(redBand)
try:
rbVals = bandValues[redBand-1]
rbvalMin = min(rbVals)
rbvalMax = max(rbVals)
print(rbvalMin)
print(rbvalMax)
except:
rbvalMin = 0
rbvalMax = 255
try:
gbVals = bandValues[greenBand-1]
gbvalMin = min(gbVals)
gbvalMax = max(gbVals)
except:
gbvalMin = 0
gbvalMax = 255
try:
bbVals = bandValues[blueBand-1]
bbvalMin = min(bbVals)
bbvalMax = max(bbVals)
except:
bbvalMin = 0
bbvalMax = 255
layerRenderer.update({'properties': {'greenBand':greenBand,'blueBand':blueBand,'redBand':redBand}})
layerRenderer['properties'].update({'redContrast':1,'redMin':rbvalMin,'redMax':rbvalMax})
layerRenderer['properties'].update({'greenContrast':1,'greenMin':gbvalMin,'greenMax':gbvalMax})
layerRenderer['properties'].update({'blueContrast':1,'blueMin':bbvalMin,'blueMax':bbvalMax})
elif rType == "paletted":
band = 0
rendererClasses = arcLayer.symbology.colorizer.groups
classes = []
sourceRamp = {}
for i, cl in enumerate(rendererClasses):
if cl.heading == 'Value':
for k, itm in enumerate(cl.items):
value = itm.values[0]
label = itm.label
try:
r,g,b = itm.color['RGB'][0], itm.color['RGB'][1], itm.color['RGB'][2]
sColor = (r<<16) + (g<<8) + b
classes.append({'color':sColor,'value':value,'label':label})
except: pass
layerRenderer.update({'properties': {'classes':classes,'ramp':sourceRamp,'band':band+1}})
return layerRenderer
elif arcLayer.isFeatureLayer:
layerRenderer: Dict[str, Any] = {}
sym = arcLayer.symbology
print(sym.renderer.type)
if sym.renderer.type == 'SimpleRenderer':
layerRenderer['type'] = 'singleSymbol'
layerRenderer['properties'] = {'symbol':{}, 'symbType':""}
symbolColor = symbol_color_to_speckle(sym.renderer.symbol.color)
layerRenderer['properties'].update({'symbol':{'symbColor': symbolColor}, 'symbType':''})
elif sym.renderer.type == 'UniqueValueRenderer':
layerRenderer['type'] = 'categorizedSymbol'
layerRenderer['properties'] = {'attribute': '', 'symbType': ''} #{'symbol':{}, 'ramp':{}, 'ranges':{}, 'gradMethod':"", 'symbType':"", 'legendClassificationAttribute': ""}
attribute = sym.renderer.fields[0]
layerRenderer['properties']['attribute'] = attribute
sourceSymbColor = symbol_color_to_speckle(sym.renderer.defaultSymbol.color)
layerRenderer['properties'].update( {'sourceSymbColor': sourceSymbColor} )
categories = sym.renderer.groups
layerRenderer['properties']['categories'] = []
for i, grp in enumerate(categories):
for itm in grp.items:
value = itm.values[0][0]
symbColor = symbol_color_to_speckle(itm.symbol.color)
label = itm.label
layerRenderer['properties']['categories'].append({'value':value,'symbColor':symbColor,'symbOpacity':1, 'sourceSymbColor': sourceSymbColor,'label':label})
elif sym.renderer.type == 'GraduatedColorsRenderer' or sym.renderer.type == 'GraduatedSymbolsRenderer':
layerRenderer['type'] = 'graduatedSymbol'
layerRenderer['properties'] = {'symbol':{}, 'ramp':{}, 'ranges':{}, 'gradMethod':"", 'symbType':""}
attribute = sym.renderer.classificationField
sourceSymbColor = (0<<16) + (0<<8) + 0
layerRenderer['properties'].update( {'attribute': attribute, 'symbType': '', 'gradMethod': 0, 'sourceSymbColor': sourceSymbColor} )
rRamp = sym.renderer.colorRamp # QgsGradientColorRamp
layerRenderer['properties']['ramp'] = {} # gradientColorRampToSpeckle(rRamp)
rRanges = sym.renderer.classBreaks
layerRenderer['properties']['ranges'] = []
for itm in rRanges:
try: lower = float(itm.label.split(" - ")[0]) if (" - " in rRanges.label) else float(rRanges.label[0])
except: lower = 0
upper = itm.upperBound
symbColor = symbol_color_to_speckle(itm.symbol.color)
label = itm.label
width = 0.26
# {'label': '1 - 1.4', 'lower': 1.0, 'symbColor': <PyQt5.QtGui.QColor ...BD9B9D4A0>, 'symbOpacity': 1.0, 'upper': 1.4}
layerRenderer['properties']['ranges'].append({'lower':lower,'upper':upper,'symbColor':symbColor,'symbOpacity':1,'label':label,'width':width})
elif sym.renderer.type == 'UnclassedColorsRenderer':
layerRenderer['type'] = 'graduatedSymbol'
layerRenderer['properties'] = {'symbol':{}, 'ramp':{}, 'ranges':{}, 'gradMethod':"", 'symbType':""}
attribute = sym.renderer.field
sourceSymbColor = (0<<16) + (0<<8) + 0
layerRenderer['properties'].update( {'attribute': attribute, 'symbType': '', 'gradMethod': 0, 'sourceSymbColor': sourceSymbColor} )
layerRenderer['properties']['ramp'] = {} # gradientColorRampToSpeckle(rRamp)
lowest = sym.renderer.lowerLabel
highest = sym.renderer.upperLabel
# trick to get colors
rRamp = sym.renderer.colorRamp # QgsGradientColorRamp
arcRamp = project.listColorRamps('White to Black')[0]
sym.updateRenderer('GraduatedColorsRenderer')
sym.renderer.colorRamp = arcRamp
sym.renderer.classificationField = attribute
rows_attributes = arcpy.da.SearchCursor(arcLayer.dataSource, attribute)
row_attrs = []
row_max = -1000000000
row_min = 1000000000
for k, attrs in enumerate(rows_attributes):
row_attrs.append(attrs[0])
if attrs[0] < row_min: row_min = attrs[0]
if attrs[0] > row_max: row_max = attrs[0]
row_range = row_max - row_min
breakCount = len(list(set(row_attrs))) # only unique values
sym.renderer.breakCount = breakCount
# run as gradient colors
rRanges = sym.renderer.classBreaks
layerRenderer['properties']['ranges'] = []
for itm in rRanges:
try: lower = float(itm.label.split(" - ")[0]) if (" - " in rRanges.label) else float(rRanges.label[0])
except: lower = 0
upper = itm.upperBound
if row_range==0: rgb = 0
else: rgb = 255 - int((itm.upperBound - row_min) / row_range * 255 )
symbColor = (rgb<<16) + (rgb<<8) + rgb
label = itm.label
width = 0.26
# {'label': '1 - 1.4', 'lower': 1.0, 'symbColor': <PyQt5.QtGui.QColor ...BD9B9D4A0>, 'symbOpacity': 1.0, 'upper': 1.4}
layerRenderer['properties']['ranges'].append({'lower':lower,'upper':upper,'symbColor':symbColor,'symbOpacity':1,'label':label,'width':width})
else: return None
return layerRenderer
else: return None
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def featureColorfromNativeRenderer(index: int, arcLayer: arcLayer) -> int:
# case with one color for the entire layer
#try:
color = {'RGB': [100,100,100,100]}
try:
sym = arcLayer.symbology
if sym.renderer.type == 'SimpleRenderer':
print('SimpleRenderer')
color = sym.renderer.symbol.color
elif sym.renderer.type == 'UniqueValueRenderer':
#print('Unique Value Renderer')
#print(index)
#print(arcLayer)
attribute = sym.renderer.fields[0]
color = sym.renderer.defaultSymbol.color
categories = sym.renderer.groups
rows_attributes = arcpy.da.SearchCursor(arcLayer.dataSource, attribute)
row_shapes_list = [x for k, x in enumerate(rows_attributes)]
color_found = 0
for i, grp in enumerate(categories):
if color_found == 1: break
for n, itm in enumerate(grp.items):
for k, attrs in enumerate(row_shapes_list):
if str(itm.values[0][0]) == "<Null>": itm.values[0][0] = None
if k == index and ( str(attrs[0]) == str(itm.values[0][0]) or (attrs[0] is None and str(itm.values[0][0]) == "<Null>") ):
color = itm.symbol.color
#print("symbol color: ")
#print(color)
color_found = 1
break
elif sym.renderer.type == 'GraduatedColorsRenderer' or sym.renderer.type == 'GraduatedSymbolsRenderer':
print('Graduated Colors / Sybmols Renderer')
attribute = sym.renderer.classificationField
rows_attributes = arcpy.da.SearchCursor(arcLayer.dataSource, attribute)
row_shapes_list = [x for k, x in enumerate(rows_attributes)]
rRanges = sym.renderer.classBreaks
upperBounds = [-10000000000000000000]
color_found = 0
for itm in rRanges:
print(itm)
if color_found == 1: break
for k, attrs in enumerate(row_shapes_list):
try:
if k == index and float(attrs[0]) <= float(itm.upperBound) and (k==0 or float(attrs[0]) > float(upperBounds[-1]) ):
color = itm.symbol.color
color_found = 1
break
except: pass
upperBounds.append(itm.upperBound)
elif sym.renderer.type == 'UnclassedColorsRenderer':
print('UnclassedColorsRenderer')
attribute = sym.renderer.field
sym.updateRenderer('GraduatedColorsRenderer')
sym.renderer.classificationField = attribute
rows_attributes = arcpy.da.SearchCursor(arcLayer.dataSource, attribute)
row_shapes_list = [x for k, x in enumerate(rows_attributes)]
row_attrs = []
row_max = -10000000000000000000
row_min = 10000000000000000000
feat_value = None
for k, attrs in enumerate(row_shapes_list):
row_attrs.append(attrs[0])
if attrs[0] < row_min: row_min = attrs[0]
if attrs[0] > row_max: row_max = attrs[0]
if k == index: feat_value = attrs[0]
row_range = row_max - row_min
breakCount = len(list(set(row_attrs))) # only unique values
sym.renderer.breakCount = breakCount
# run as gradient colors
upperBounds = [-10000000000000000000]
rRanges = sym.renderer.classBreaks
for itm in rRanges:
print(itm)
try:
if row_range!=0 and float(feat_value) <= float(itm.upperBound) and (len(upperBounds)==0 or float(feat_value) > float(upperBounds[-1])):
rgb = 255 - int((itm.upperBound - row_min) / row_range * 255 )
color = {'RGB':[rgb,rgb,rgb,100]}
print(color)
break
except: pass
upperBounds.append(itm.upperBound)
else:
print('Else')
return (100<<16) + (100<<8) + 100
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
#print("final color: ")
#print(color)
# construct RGB color
col = symbol_color_to_speckle(color)
#print(col)
return col
@@ -1,75 +1,195 @@
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
import inspect
try:
from speckle.converter.layers.emptyLayerTemplates import createGroupLayer
from speckle.plugin_utils.helpers import findOrCreatePath
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.emptyLayerTemplates import createGroupLayer
from speckle_toolbox.esri.toolboxes.speckle.plugin_utils.helpers import findOrCreatePath
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
#ATTRS_REMOVE = ['geometry','applicationId','bbox','displayStyle', 'id', 'renderMaterial', 'displayMesh', 'displayValue']
ATTRS_REMOVE = ['speckleTyp','speckle_id','geometry','applicationId','bbox','displayStyle', 'id', 'renderMaterial', 'displayMesh', 'displayValue']
def findAndClearLayerGroup(gis_project: ArcGISProject, newGroupName: str = ""):
print("find And Clear LayerGroup")
try:
groupExists = 0
print(newGroupName)
for l in gis_project.activeMap.listLayers():
#print(l.longName)
if l.longName.startswith(newGroupName + "\\"):
#print(l.longName)
gis_project.activeMap.removeLayer(l)
groupExists+=1
elif l.longName == newGroupName:
groupExists+=1
print(newGroupName)
if groupExists == 0:
# create empty group layer file "\\Layers_Speckle\\
path = "\\".join(gis_project.filePath.split("\\")[:-1]) + "\\Layers_Speckle\\"
findOrCreatePath(path)
lyr_path = path + newGroupName + ".lyrx"
print(lyr_path)
try:
f = open(lyr_path, "w")
content = createGroupLayer().replace("TestGroupLayer", newGroupName)
f.write(content)
f.close()
newGroupLayer = arcpy.mp.LayerFile(lyr_path)
layerGroup = gis_project.activeMap.addLayer(newGroupLayer)[0]
print(layerGroup)
except: # for 3.0.0
if gis_project.active_map is not None:
print("try creating the group")
layerGroup = gis_project.activeMap.createGroupLayer(newGroupName)
print(layerGroup)
else:
logToUser("The map didn't fully load, try selecting the project Map or/and refreshing the plugin.", level=1, func = inspect.stack()[0][3])
return
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def getVariantFromValue(value: Any) -> Union[str, None]:
#print("_________get variant from value_______")
# TODO add Base object
pairs = [
(str, "TEXT"), # 10
(float, "FLOAT"),
(int, "LONG"),
(bool, "SHORT")
#date: "SHORT"
]
res = None
for p in pairs:
if isinstance(value, p[0]): res = p[1]; break
#t = type(value)
#try: res = pairs[t]
#except: pass
#if isinstance(value, str) and "PyQt5.QtCore.QDate(" in value: res = QVariant.Date #14
#elif isinstance(value, str) and "PyQt5.QtCore.QDateTime(" in value: res = QVariant.DateTime #16
try:
pairs = [
(str, "TEXT"), # 10
(float, "FLOAT"),
(int, "LONG"),
(bool, "SHORT")
]
for p in pairs:
if isinstance(value, p[0]):
res = p[1]
try:
if res == "LONG" and (value>= 2147483647 or value<= -2147483647):
#https://pro.arcgis.com/en/pro-app/latest/help/data/geodatabases/overview/arcgis-field-data-types.htm
res = "FLOAT"
except Exception as e: print(e)
break
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
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")
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
try:
if not isinstance(featuresList, list): features = [featuresList]
else: features = featuresList[:]
all_props = []
for feature in features:
#get object properties to add as attributes
dynamicProps = feature.get_dynamic_member_names()
for att in ATTRS_REMOVE:
try: dynamicProps.remove(att)
except: pass
dynamicProps.sort()
dynamicProps.sort()
#print(dynamicProps)
# add field names and variands
for name in dynamicProps:
#if name not in all_props: all_props.append(name)
# add field names and variands
#variants = []
for name in dynamicProps:
if name not in all_props: all_props.append(name)
value = feature[name]
variant = getVariantFromValue(value)
#if name == 'area': print(value); print(variant)
if not variant: variant = None #LongLong #4
value = feature[name]
variant = getVariantFromValue(value)
#print(variant)
if not variant: variant = None #LongLong #4
# 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)
# add a field if not existing yet AND if variant is known
if variant and (name not in fields.keys()):
fields.update({name: variant})
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)
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})
else: #check if the field was empty previously:
oldVariant = fields[k]
# replace if new one is NOT Float (too large integers)
#if oldVariant != "FLOAT" and v == "FLOAT":
# fields.update({k: v})
# replace if new one is NOT LongLong or IS String
if oldVariant != "TEXT" and v == "TEXT":
fields.update({k: v})
# 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
else: #check if the field was empty previously:
oldVariant = fields[k]
# replace if new one is NOT Float (too large integers)
#print(oldVariant, v)
#if oldVariant == "LONG" and v == "FLOAT":
# fields.update({k: v})
# replace if new one is NOT LongLong or IS String
if oldVariant != "TEXT" and v == "TEXT":
fields.update({k: v})
#print(fields)
# replace all empty ones wit String
all_props.append("Speckle_ID")
for name in all_props:
if name not in fields.keys():
fields.update({name: 'TEXT'})
# replace all empty ones wit String
for name in all_props:
if name not in fields.keys():
fields.update({name: "TEXT"})
print(fields)
#fields_sorted = {k: v for k, v in sorted(fields.items(), key=lambda item: item[0])}
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return fields
def traverseDict(newF: dict, newVals: dict, nam: str, val: Any):
try:
if isinstance(val, dict):
for i, (k,v) in enumerate(val.items()):
newF, newVals = traverseDict( newF, newVals, nam+"_"+k, v)
elif isinstance(val, 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:
var = getVariantFromValue(val)
if var is None:
var = 'TEXT'
val = str(val)
#print(var)
newF.update({nam: var})
newVals.update({nam: val})
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return newF, newVals
def get_scale_factor(units: str) -> float:
unit_scale = {
"meters": 1.0,
@@ -89,6 +209,190 @@ def get_scale_factor(units: str) -> float:
}
if units is not None and units.lower() in unit_scale.keys():
return unit_scale[units]
arcpy.AddWarning(f"Units {units} are not supported. Meters will be applied by default.")
logToUser(f"Units {units} are not supported. Meters will be applied by default.", level=0, func = inspect.stack()[0][3])
return 1.0
def findTransformation(f_shape, geomType, layer_sr: arcpy.SpatialReference, projectCRS: arcpy.SpatialReference, selectedLayer: arcLayer):
#apply transformation if needed
try:
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: logToUser("Unsupported or invalid geometry in layer " + selectedLayer.name, level=2, func = inspect.stack()[0][3])
except: logToUser("Unsupported or invalid geometry", level=2, func = inspect.stack()[0][3])
# 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:
logToUser(f"Spatial Transformation not found for layer {selectedLayer.name}", level=2, func = inspect.stack()[0][3])
return None
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return f_shape
def traverseDictByKey(d: Dict, key:str ="", result = None) -> Dict:
print("__traverse")
try:
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
#print("__result is: ____________")
#return result
def hsv_to_rgb(listHSV):
try:
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)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return (0,0,0)
def cmyk_to_rgb(c, m, y, k, cmyk_scale, rgb_scale=255):
try:
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return 0,0,0
def newLayerGroupAndName(layerName: str, streamBranch: str, project: ArcGISProject) -> str:
print("___new Layer Group and Name")
layerGroup = None
newGroupName = f'{streamBranch}'
try:
#CREATE A GROUP "received blabla" with sublayers
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None, None
def curvedFeatureClassToSegments(layer) -> str:
print("___densify___")
try:
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def validate_path(path: str):
"""If our path contains a DB name, make sure we have a valid DB name and not a standard file name."""
try:
# https://github.com/EsriOceans/btm/commit/a9c0529485c9b0baa78c1f094372c0f9d83c0aaf
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
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
@@ -0,0 +1,54 @@
import os
from typing import List
import inspect
def findOrCreatePath(path: str):
if not os.path.exists(path):
os.makedirs(path)
def splitTextIntoLines(text: str = "", number: int= 40) -> str:
print("__splitTextIntoLines")
print(text)
msg = ""
try:
if len(text)>number:
try:
for i, x in enumerate(text):
msg += x
if i!=0 and i%number == 0: msg += "\n"
except Exception as e: print(e)
else:
msg = text
except Exception as e:
print(e)
print(text)
return msg
def validateNewFclassName(newName: str, prefix: str, all_layer_names: List[str]) -> str:
fixed_name = newName
if (prefix + fixed_name) in all_layer_names:
layerNameCreated = 0
for index, letter in enumerate('234567890abcdefghijklmnopqrstuvwxyz'):
if ((prefix + fixed_name) + "_" + letter) not in all_layer_names:
fixed_name += "_"+letter
layerNameCreated +=1
break
if layerNameCreated == 0:
for index, letter in enumerate('234567890abcdefghijklmnopqrstuvwxyz'):
test_fixed_name = validateNewFclassName((fixed_name + "_" + letter), prefix, all_layer_names)
if (prefix + test_fixed_name) not in all_layer_names:
fixed_name = test_fixed_name
layerNameCreated +=1
break
#else: layerNameCreated +=1
if layerNameCreated == 0:
pass #logToUser('Feature class name already exists', level=2, func = inspect.stack()[0][3])
#return fixed_name
return fixed_name
@@ -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
'''
@@ -0,0 +1,142 @@
from typing import Any, Callable, List, Optional
import inspect
from specklepy.objects import Base
try:
from speckle.ui.logger import logToUser
from speckle.converter.layers.Layer import VectorLayer, RasterLayer, Layer
from speckle.converter.layers import bimLayerToNative, cadLayerToNative, layerToNative
except:
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.Layer import VectorLayer, RasterLayer, Layer
from speckle_toolbox.esri.toolboxes.speckle.converter.layers import bimLayerToNative, cadLayerToNative, layerToNative
import arcpy
SPECKLE_TYPES_TO_READ = ["Objects.Geometry.", "Objects.BuiltElements.", "IFC"] # will properly traverse and check for displayValue
def traverseObject(
base: Base,
callback: Optional[Callable[[Base, str], bool]],
check: Optional[Callable[[Base], bool]],
streamBranch: str,
):
try:
#print("traverse Object")
#print(base)
if check and check(base):
res = callback(base, streamBranch) if callback else False
#print(res)
if res:
return
memberNames = base.get_member_names()
#print(base)
#print(memberNames)
for name in memberNames:
try:
if ["id", "applicationId", "units", "speckle_type"].index(name):
continue
except:
pass
#print(name)
traverseValue(base[name], callback, check, streamBranch)
logToUser("Data received", level=0)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def traverseValue(
value: Any,
callback: Optional[Callable[[Base, str], bool]],
check: Optional[Callable[[Base], bool]],
streamBranch: str,
):
try:
#print("traverse Value")
#print(value)
if isinstance(value, Base):
traverseObject(value, callback, check, streamBranch)
if isinstance(value, List):
for item in value:
traverseValue(item, callback, check, streamBranch)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def callback(base: Base, streamBranch: str) -> bool:
try:
#print("callback")
if isinstance(base, VectorLayer) or isinstance(base, Layer) or isinstance(base, RasterLayer):
if isinstance(base, Layer):
logToUser(f"Speckle class \"Layer\" will be deprecated in future updates in favour of \"VectorLayer\" or \"RasterLayer\"", level=0, func = inspect.stack()[0][3])
layer = layerToNative(base, streamBranch)
#print(layer)
#if layer is not None:
# logToUser("Layer created: " + layer.name(), level=0)
else:
loopObj(base, "", streamBranch)
return True
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return False
def loopObj(base: Base, baseName: str, streamBranch: str):
try:
memberNames = base.get_member_names()
for name in memberNames:
if name in ["id", "applicationId", "units", "speckle_type"]: continue
# skip if traversal goes to displayValue of an object, that will be readable anyway:
if not isinstance(base, Base): logToUser("NOT BASE: "+type(base), level=1, func = inspect.stack()[0][3]); continue
if (name == "displayValue" or name == "@displayValue") and base.speckle_type.startswith(tuple(SPECKLE_TYPES_TO_READ)): continue
try: loopVal(base[name], baseName + "/" + name, streamBranch)
except: pass
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def loopVal(value: Any, name: str, streamBranch: str): # "name" is the parent object/property/layer name
try:
if isinstance(value, Base):
try: # loop through objects with Speckletype prop, but don't go through parts of Speckle Geometry object
if not value.speckle_type.startswith("Objects.Geometry."):
loopObj(value, name, streamBranch)
except:
loopObj(value, name, streamBranch)
elif isinstance(value, List):
streamBranch = streamBranch.replace("[","_").replace("]","_").replace(" ","_").replace("-","_").replace("(","_").replace(")","_").replace(":","_").replace("\\","_").replace("/","_").replace("\"","_").replace("&","_").replace("@","_").replace("$","_").replace("%","_").replace("^","_")
objectListConverted = 0
#print("loop val - List")
for i, item in enumerate(value):
loopVal(item, name, streamBranch)
if not isinstance(item, Base): continue
if item.speckle_type and item.speckle_type.startswith("IFC"):
# keep traversing infinitely, just don't run repeated conversion for the same list of objects
try:
if item["displayValue"] is not None and objectListConverted == 0:
bimLayerToNative(value, name, streamBranch)
objectListConverted += 1
except:
try:
if item["@displayValue"] is not None and objectListConverted == 0:
bimLayerToNative(value, name, streamBranch)
objectListConverted += 1
except: pass
elif item.speckle_type and item.speckle_type.endswith(".ModelCurve"):
if item["baseCurve"] is not None:
cadLayerToNative(value, name, streamBranch)
break
elif item.speckle_type and (item.speckle_type == "Objects.Geometry.Mesh" or item.speckle_type == "Objects.Geometry.Brep" or item.speckle_type.startswith("Objects.BuiltElements.")):
bimLayerToNative(value, name, streamBranch)
break
elif item.speckle_type and item.speckle_type != "Objects.Geometry.Mesh" and item.speckle_type != "Objects.Geometry.Brep" and item.speckle_type.startswith("Objects.Geometry."): # or item.speckle_type == 'Objects.BuiltElements.Alignment'):
pt, pl = cadLayerToNative(value, name, streamBranch)
#if pt is not None: arcpy.AddMessage("Layer group created: " + str(pt.name))
#if pl is not None: arcpy.AddMessage("Layer group created: " + str(pl.name))
break
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
@@ -0,0 +1,73 @@
#python speckle_toolbox\esri\toolboxes\speckle\plugin_utils\testing_from_file.py
import arcpy
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
import json
import os
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
from arcpy.management import (CreateFeatureclass, MakeFeatureLayer,
AddFields, AlterField, DefineProjection )
##################################################### get example layers from the project #######
path = r'C:\Users\katri\Documents\ArcGIS\Projects\MyProject\MyProject.gdb'
arcpy.env.workspace = path
project = ArcGISProject(path.replace("gdb","aprx"))
active_map = project.listMaps()[0] #.activeMap
all_layers = []
layerPolygon = None
layerPolyline = None
layerPoint = None
layerMultiPoint = None
#get layer of interest
for layer in active_map.listLayers():
if layer.isFeatureLayer or layer.isRasterLayer:
all_layers.append(layer)
data = arcpy.Describe(layer.dataSource)
if layer.isFeatureLayer:
geomType = data.shapeType
if geomType == "Polygon" and layerPolygon is None: layerPolygon = layer
if geomType == "Polyline" and layerPolyline is None: layerPolyline = layer
if geomType == "Point" and layerPoint is None: layerPoint = layer
if geomType == "Multipoint" and layerMultiPoint is None: layerMultiPoint = layer
root_path = "\\".join(project.filePath.split("\\")[:-1])
#path_style = root_path + '\\layer_speckle_symbology.lyrx'
path_style2 = root_path + '\\layer_speckle_symbology2.lyrx'
for layer in active_map.listLayers():
if layer.longName == layerPolygon.longName:
layerPolygon = layer
break
print(layerPolygon.dataSource)
r'''
source = str(layerPolygon.dataSource).split('\\')
layerPolygon = arcpy.ApplySymbologyFromLayer_management(
in_layer= str(layerPolygon.dataSource),
in_symbology_layer=path_style2,
update_symbology='UPDATE')[0]
'''
#vl2 = MakeFeatureLayer(layerPolygon.dataSource, 'someName').getOutput(0)
#active_map.addLayer(arcpy.mp.LayerFile(path_style2))
################## reset symbology if needed:
sym = layerPolygon.symbology
print(sym.renderer.type)
sym.updateRenderer('UniqueValueRenderer')
print(sym.renderer.type)
layerPolygon.symbology = sym
print(sym.renderer.type)
r'''
sym = layerPolygon.symbology
print(sym.renderer.type)
sym.updateRenderer('UniqueValueRenderer')
layerPolygon.symbology = sym
print(sym.updateRenderer('UniqueValueRenderer'))
print(layerPolygon.symbology.renderer.type)
# SimpleRenderer, GraduatedColorsRenderer, GraduatedSymbolsRenderer, UnclassedColorsRenderer, UniqueValueRenderer
'''
project.save()
@@ -0,0 +1,13 @@
import arcpy
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
import json
import os
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
from arcpy.management import (CreateFeatureclass, MakeFeatureLayer,
AddFields, AlterField, DefineProjection )
import unittest
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,787 @@
# -*- coding: utf-8 -*-
from typing import Any, Callable, List, Optional, Tuple
#r'''
from collections import defaultdict
import arcpy
#from arcpy import toolbox
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
from arcpy import metadata as md
try:
from speckle.converter.layers.Layer import Layer, RasterLayer
from speckle.converter.layers._init_ import convertSelectedLayers, layerToNative, cadLayerToNative, bimLayerToNative
from speckle.ui.project_vars import toolboxInputsClass, speckleInputsClass
from speckle.converter.layers.emptyLayerTemplates import createGroupLayer
from speckle.converter.layers.Layer import VectorLayer
except:
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.Layer import Layer, RasterLayer
from speckle_toolbox.esri.toolboxes.speckle.converter.layers import convertSelectedLayers, layerToNative, cadLayerToNative, bimLayerToNative
from speckle_toolbox.esri.toolboxes.speckle.ui.project_vars import toolboxInputsClass, speckleInputsClass
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.emptyLayerTemplates import createGroupLayer
from speckle_toolbox.esri.toolboxes.speckle.converter.layers.Layer import VectorLayer
from arcgis.features import FeatureLayer
import os
import os.path
import sys
import specklepy
from specklepy.api.models import Branch, Stream, Streams
from specklepy.transports.server.server import ServerTransport
from specklepy.api.credentials import get_local_accounts
from specklepy.api.client import SpeckleClient
from specklepy.api import operations
from specklepy.logging.exceptions import (
GraphQLException,
SpeckleException,
SpeckleWarning,
)
#from specklepy.api.credentials import StreamWrapper
from specklepy.api.wrapper import StreamWrapper
from specklepy.objects import Base
from specklepy.logging import metrics
#'''
def traverseObject(
base: Base,
callback: Optional[Callable[[Base], bool]],
check: Optional[Callable[[Base], bool]],
):
if check and check(base):
res = callback(base) if callback else False
if res:
return
memberNames = base.get_member_names()
for name in memberNames:
try:
if ["id", "applicationId", "units", "speckle_type"].index(name):
continue
except:
pass
traverseValue(base[name], callback, check)
def traverseValue(
value: Any,
callback: Optional[Callable[[Base], bool]],
check: Optional[Callable[[Base], bool]],
):
if isinstance(value, Base):
traverseObject(value, callback, check)
if isinstance(value, List):
for item in value:
traverseValue(item, callback, check)
class Toolbox:
def __init__(self):
"""Define the toolbox (the name of the toolbox is the name of the
.pyt file)."""
print("___ping_Toolbox")
self.label = "Speckle Tools"
self.alias = "speckle_toolbox_"
# List of tool classes associated with this toolbox
self.tools = [Speckle]
try:
version = arcpy.GetInstallInfo()['Version']
python_version = f"python {'.'.join(map(str, sys.version_info[:2]))}"
metrics.set_host_app("ArcGIS", ', '.join([f"ArcGIS {version}", python_version]))
except:
metrics.set_host_app("ArcGIS")
# https://pro.arcgis.com/en/pro-app/2.8/arcpy/mapping/alphabeticallistofclasses.htm#except: print("something happened")
class Speckle:
def __init__(self):
print("__________________INIT SPECKLE TOOL_________")
self.label = "Speckle"
self.description = "Allows you to send and receive your layers " + \
"to/from other software using Speckle server."
self.toRefresh = False
self.speckleInputs = None
self.toolboxInputs = None
total = len(speckleInputsClass.instances)
print(total)
for i in range(total):
if speckleInputsClass.instances[total-i-1] is not None:
try:
y = speckleInputsClass.instances[total-i-1].streams_default
#if not isinstance(speckleInputsClass.instances[total-i-1].streams_default, SpeckleException): # also will throw exception in case not initialized properly
self.speckleInputs = speckleInputsClass.instances[total-i-1] # take latest (first in reverted list)
break
except: pass
if self.speckleInputs is None or isinstance(self.speckleInputs.streams_default, SpeckleException): self.speckleInputs = speckleInputsClass()
#print(self.speckleInputs.streams_default)
print(len(speckleInputsClass.instances))
total = len(toolboxInputsClass.instances)
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)
break
if self.toolboxInputs is None: self.toolboxInputs = toolboxInputsClass()
#print(self.speckleInputs.accounts)
if len(self.speckleInputs.accounts) == 0:
arcpy.AddError("Speckle accounts not found")
def getParameterInfo(self):
#data types: https://pro.arcgis.com/en/pro-app/2.8/arcpy/geoprocessing_and_python/defining-parameter-data-types-in-a-python-toolbox.htm
# parameter details: https://pro.arcgis.com/en/pro-app/latest/arcpy/geoprocessing_and_python/customizing-tool-behavior-in-a-python-toolbox.htm
print("Get parameter values")
cat1 = "Add Streams"
cat2 = "Send/Receive"
cat3 = "Create custom Spatial Reference"
streamsDefalut = arcpy.Parameter(
displayName="Add stream from default account",
name="streamsDefalut",
datatype="GPString",
parameterType="Optional",
direction="Input",
category=cat1
)
streamsDefalut.filter.type = 'ValueList'
if isinstance(self.speckleInputs.streams_default, SpeckleException):
arcpy.AddError("Speckle account not accessible")
streamsDefalut.filter.list = []
elif self.speckleInputs.streams_default is not None:
streamsDefalut.filter.list = [ (str(st.name) + " - " + str(st.id)) for st in self.speckleInputs.streams_default ]
else:
streamsDefalut.filter.list = []
arcpy.AddError("Error connecting to default Speckle account")
addDefStreams = arcpy.Parameter(
displayName="Add",
name="addDefStreams",
datatype="GPBoolean",
parameterType="Optional",
direction="Input",
category=cat1
)
addDefStreams.value = False
streamUrl = arcpy.Parameter(
displayName="Add stream by URL",
name="streamUrl",
datatype="GPString",
parameterType="Optional",
direction="Input",
category=cat1
)
streamUrl.value = ""
addUrlStreams = arcpy.Parameter(
displayName="Add",
name="addUrlStreams",
datatype="GPBoolean",
parameterType="Optional",
direction="Input",
category=cat1
)
addUrlStreams.value = False
############################################################################
lat = arcpy.Parameter(
displayName="Origin point LAT",
name="lat",
datatype="GPString",
parameterType="Optional",
direction="Input",
category=cat3
)
lat.value = str(self.toolboxInputs.lat)
lon = arcpy.Parameter(
displayName="Origin point LON",
name="lon",
datatype="GPString",
parameterType="Optional",
direction="Input",
category=cat3
)
lon.value = str(self.toolboxInputs.lon)
setLatLon = arcpy.Parameter(
displayName="Create and apply",
name="setLatLon",
datatype="GPBoolean",
parameterType="Optional",
direction="Input",
category=cat3
)
setLatLon.value = False
####################################################################################################
savedStreams = arcpy.Parameter(
displayName="Select Stream",
name="savedStreams",
datatype="GPString",
parameterType="Required",
direction="Input",
multiValue=False,
)
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",
direction="Input",
)
branch.value = ""
branch.filter.type = 'ValueList'
commit = arcpy.Parameter(
displayName="Commit",
name="commit",
datatype="GPString",
parameterType="Optional",
direction="Input",
)
commit.value = ""
commit.filter.type = 'ValueList'
msg = arcpy.Parameter(
displayName="Message",
name="msg",
datatype="GPString",
parameterType="Optional",
direction="Input",
multiValue=False,
)
msg.value = ""
selectedLayers = arcpy.Parameter(
displayName="Selected Layers",
name="selectedLayers",
datatype="GPString",
parameterType="Optional",
direction="Input",
multiValue=True,
)
selectedLayers.filter.list = [str(i) + "-" + l.longName for i,l in enumerate(self.speckleInputs.all_layers)] #"Polyline"
action = arcpy.Parameter(
displayName="",
name="action",
datatype="GPString",
parameterType="Required",
direction="Input",
multiValue=False,
)
action.value = "Send"
action.filter.list = ["Send", "Receive"]
refresh = arcpy.Parameter(
displayName="Refresh",
name="refresh",
datatype="GPBoolean",
parameterType="Optional",
direction="Input"
)
refresh.value = False
parameters = [streamsDefalut, addDefStreams, streamUrl, addUrlStreams, lat, lon, setLatLon, savedStreams, removeStream, branch, commit, selectedLayers, msg, action, refresh]
return parameters
def isLicensed(self): #optional
return True
def updateParameters(self, parameters: List, toRefresh = False): #optional
print("UPDATING PARAMETERS")
for i, par in enumerate(parameters):
if par.name == "addDefStreams" and par.altered and par.value == True:
for p in parameters:
if p.name == "streamsDefalut" and p.valueAsText is not None:
# add value from streamsDefault to saved streams
selected_stream_name = p.valueAsText[:]
for stream in self.speckleInputs.streams_default:
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
# query stream, add to saved
stream = self.speckleInputs.speckle_client.stream.get(id = steamId, branch_limit = 100, commit_limit = 100)
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[:]
for streamTup in self.speckleInputs.saved_streams:
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[:]:
selected_stream_name = par.valueAsText[:]
self.toolboxInputs.active_stream = None
self.toolboxInputs.active_stream_wrapper = None
for st in self.speckleInputs.saved_streams:
if st[1].name == selected_stream_name.split(" - ")[0]:
self.toolboxInputs.active_stream = st[1]
self.toolboxInputs.active_stream_wrapper = st[0]
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
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
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:
if par.valueAsText == "Send": self.toolboxInputs.action = 1
else: self.toolboxInputs.action = 0
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___________________________")
return
def refresh(self, parameters: List[Any]):
print("Refresh______")
self.speckleInputs: speckleInputsClass = speckleInputsClass()
self.toolboxInputs: toolboxInputsClass = toolboxInputsClass()
for par in parameters:
if par.name == "streamUrl": par.value = None
if par.name == "streamsDefalut": par.value = None
if par.name == "savedStreams": par.value = None
if par.name == "branch": par.value = ""; 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":
if isinstance(self.speckleInputs.streams_default, SpeckleException):
arcpy.AddError("Speckle account not accessible")
par.filter.list = []
else:
par.filter.list = [ (st.name + " - " + st.id) for st in self.speckleInputs.streams_default ]
if par.name == "savedStreams":
saved_streams = self.speckleInputs.getProjectStreams()
par.filter.list = [f"Stream not accessible - {stream[0].stream_id}" if stream[1] is None or isinstance(stream[1], SpeckleException) else f"{stream[1].name} - {stream[1].id}" for i,stream in enumerate(saved_streams)]
if par.name == "selectedLayers": par.filter.list = [str(i) + "-" + l.longName for i,l in enumerate(self.speckleInputs.all_layers)]
return parameters
def updateMessages(self, parameters): #optional
return
def execute(self, parameters: List, messages):
# https://pro.arcgis.com/en/pro-app/latest/arcpy/get-started/what-is-arcpy-.htm
#Warning if any of the fields is invalid/empty
print("___________________________Run___________________________")
check = self.validateStreamBranch(parameters) # apparently pdate needed to assign proper self.values
print(self.toolboxInputs.selected_layers)
print(self.toolboxInputs.action)
if self.toolboxInputs.action == 1 and check is True: self.onSend(parameters)
elif self.toolboxInputs.action == 0 and check is True: self.onReceive(parameters)
print("__________________________Run_end___________________________")
def validateStreamBranch(self, parameters: List):
self.updateParameters(parameters)
if self.toolboxInputs.active_stream is None:
arcpy.AddError("Choose a valid stream")
return False
if self.toolboxInputs.active_branch is None:
arcpy.AddError("Choose a valid branch")
return False
return True
def onSend(self, parameters: List):
print("______________SEND_______________")
if len(self.toolboxInputs.selected_layers) == 0:
arcpy.AddError("No layers selected for sending")
return
streamId = self.toolboxInputs.active_stream.id #stream_id
client = self.toolboxInputs.active_stream_wrapper.get_client()
#client = self.speckleInputs.speckle_client # ?
# Get the stream wrapper
#streamWrapper = StreamWrapper(None)
#client = streamWrapper.get_client()
# Ensure the stream actually exists
#try:
# client.stream.get(streamId)
#except SpeckleException as error:
# print(str(error))
# return
# next create a server transport - this is the vehicle through which you will send and receive
transport = ServerTransport(client=client, stream_id=streamId)
##################################### conversions ################################################
base_obj = Base(units = "m")
base_obj.layers = convertSelectedLayers(self.speckleInputs.all_layers, self.toolboxInputs.selected_layers, self.speckleInputs.project)
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])
except SpeckleException as error:
arcpy.AddError("Error sending data")
#print("Error sending data")
return
except SpeckleWarning as warning:
arcpy.AddMessage("SpeckleWarning: " + str(warning.args[0]))
message = self.toolboxInputs.messageSpeckle
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(
stream_id=streamId,
object_id=objId,
branch_name=self.toolboxInputs.active_branch.name,
message=message,
source_application="ArcGIS",
)
arcpy.AddMessage("Successfully sent data to stream: " + streamId)
except:
arcpy.AddError("Error creating commit")
def onReceive(self, parameters: List[Any]):
print("______________RECEIVE_______________")
#if self.validateStreamBranch(parameters) == False: return
try:
streamId = self.toolboxInputs.active_stream.id #stream_id
client = self.toolboxInputs.active_stream_wrapper.get_client()
#client = self.speckleInputs.speckle_client #
except SpeckleWarning as warning:
arcpy.AddWarning(str(warning.args[0]))
# get commit
commit = None
try:
#commit = self.toolboxInputs.active_branch.commits.items[0]
commit = self.toolboxInputs.active_commit
commitId = commit.id # text to make sure commit exists
except:
try:
commit = self.toolboxInputs.active_branch.commits.items[0]
commitId = commit.id
arcpy.AddWarning("Failed to find a commit. Getting the last commit of the branch")
except:
arcpy.AddError("Failed to find a commit")
return
# next create a server transport - this is the vehicle through which you will send and receive
try:
transport = ServerTransport(client=client, stream_id=streamId)
client.commit.received(
streamId,
commit.id,
source_application="ArcGIS",
message="Received commit in ArcGIS",
)
except:
arcpy.AddError("Make sure your account has access to the chosen stream")
return
try:
#print(commit)
objId = commit.referencedObject
commitDetailed = client.commit.get(streamId, commit.id)
if isinstance(commitDetailed, GraphQLException):
arcpy.AddError("Access error")
return
app = commitDetailed.sourceApplication
if objId is None:
return
commitObj = operations.receive(objId, transport, None)
if app != "QGIS" and app != "ArcGIS":
if self.speckleInputs.project.activeMap.spatialReference.type == "Geographic" or self.speckleInputs.project.activeMap.spatialReference is None: #TODO test with invalid CRS
arcpy.AddMessage("It is advisable to set the project Spatial reference to Projected type before receiving CAD geometry (e.g. EPSG:32631), or create a custom one from geographic coordinates")
print("It is advisable to set the project Spatial reference to Projected type before receiving CAD geometry (e.g. EPSG:32631), or create a custom one from geographic coordinates")
print(f"Successfully received {objId}")
# Clear 'latest' group
streamBranch = streamId + "_" + self.toolboxInputs.active_branch.name + "_" + str(commit.id)
streamBranch = streamBranch.replace("[","_").replace("]","_").replace(" ","_").replace("-","_").replace("(","_").replace(")","_").replace(":","_").replace("\\","_").replace("/","_").replace("\"","_").replace("&","_").replace("@","_").replace("$","_").replace("%","_").replace("^","_")
newGroupName = f'{streamBranch}'
groupExists = 0
print(newGroupName)
for l in self.speckleInputs.project.activeMap.listLayers():
#print(l.longName)
if l.longName.startswith(newGroupName + "\\"):
#print(l.longName)
self.speckleInputs.project.activeMap.removeLayer(l)
groupExists+=1
elif l.longName == newGroupName:
groupExists+=1
print(newGroupName)
if groupExists == 0:
# create empty group layer file
path = self.speckleInputs.project.filePath.replace("aprx","gdb") #"\\".join(self.toolboxInputs.project.filePath.split("\\")[:-1]) + "\\speckle_layers\\"
print(path)
try:
f = open(path + "\\" + newGroupName + ".lyrx", "w")
content = createGroupLayer().replace("TestGroupLayer", newGroupName)
f.write(content)
f.close()
newGroupLayer = arcpy.mp.LayerFile(path + "\\" + newGroupName + ".lyrx")
layerGroup = self.speckleInputs.project.activeMap.addLayer(newGroupLayer)[0]
except: # for 3.0.0
if self.speckleInputs.active_map is not None:
layerGroup = self.speckleInputs.active_map.createGroupLayer(newGroupName)
else:
arcpy.AddWarning("The map didn't fully load, try refreshing the plugin.")
return
print(layerGroup)
print("layer added")
layerGroup.name = newGroupName
print(newGroupName)
if app == "QGIS" or app == "ArcGIS": check: Callable[[Base], bool] = lambda base: isinstance(base, Layer) or isinstance(base, VectorLayer) or isinstance(base, RasterLayer)
else: check: Callable[[Base], bool] = lambda base: isinstance(base, Base)
def callback(base: Base) -> bool:
print("callback")
#print(base)
if isinstance(base, Layer) or isinstance(base, VectorLayer) or isinstance(base, RasterLayer):
layer = layerToNative(base, streamBranch, self.speckleInputs.project)
if layer is not None:
print("Layer created: " + layer.name)
else:
loopObj(base, "")
return True
def loopObj(base: Base, baseName: str):
memberNames = base.get_member_names()
for name in memberNames:
if name in ["id", "applicationId", "units", "speckle_type"]: continue
try: loopVal(base[name], baseName + "/" + name) # loop properties not included above
except: pass
def loopVal(value: Any, name: str): # "name" is the parent object/property/layer name
if name.endswith('/displayValue'): return
if isinstance(value, Base):
try: # dont go through parts of Speckle Geometry object
print("objects to loop through: " + value.speckle_type)
if value.speckle_type.startswith("Objects.Geometry."): pass #.Brep") or value.speckle_type.startswith("Objects.Geometry.Mesh") or value.speckle_type.startswith("Objects.Geometry.Surface") or value.speckle_type.startswith("Objects.Geometry.Extrusion"): pass
else: loopObj(value, name)
except: loopObj(value, name)
if isinstance(value, List):
for item in value:
loopVal(item, name)
print(item)
pt = None
if item.speckle_type and item.speckle_type.startswith("Objects.Geometry."):
pt, pl = cadLayerToNative(value, name, streamBranch, self.speckleInputs.project)
if pt is not None: print("Layer group created: " + pt.name())
if pl is not None: print("Layer group created: " + pl.name())
break
if item.speckle_type and (item.speckle_type.startswith("Objects.BuiltElements.") or item.speckle_type.startswith("Objects.Structural.Geometry")): # and "Revit" in item.speckle_type
print("__receiving structures__")
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, 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,160 @@
import time
from typing import List
from PyQt5 import QtCore
from PyQt5.QtCore import QCoreApplication, QSettings, Qt, QTranslator, QRect, QObject
from PyQt5.QtWidgets import QAction, QDockWidget, QVBoxLayout, QWidget, QPushButton
from PyQt5 import QtWidgets
import webbrowser
import inspect
try:
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
SPECKLE_COLOR = (59,130,246)
SPECKLE_COLOR_LIGHT = (69,140,255)
BACKGR_COLOR = f"background-color: rgb{str(SPECKLE_COLOR)};"
BACKGR_COLOR_LIGHT = f"background-color: rgb{str(SPECKLE_COLOR_LIGHT)};"
BACKGR_COLOR_GREY = f"background-color: Gainsboro;"
class LogWidget(QWidget):
msgs: List[str] = []
used_btns: List[int] = []
btns: List[QPushButton]
# constructor
def __init__(self, parent=None):
super(LogWidget, self).__init__(parent)
print("start LogWidget")
self.parentWidget = parent
print(self.parentWidget)
# create a temporary floating button
width = 0 #parent.frameSize().width()
height = 0# parent.frameSize().height()
self.setAttribute(QtCore.Qt.WA_StyledBackground, True)
self.setStyleSheet("background-color: rgba(250,250,250,80);")
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(10, 60, 10, 40)
self.layout.setAlignment(Qt.AlignBottom)
self.setGeometry(0, 0, width, height)
# generate 100 buttons to use later
self.btns = []
for i in range(10):
button = QPushButton(f"👌 Error") # to '{streamName}' Sent , v
button.setStyleSheet("QPushButton {color: black; border: 0px;border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR_GREY}" + "}")
button.clicked.connect(lambda: self.hide())
self.btns.append(button)
self.hide()
# overriding the mouseReleaseEvent method
def mouseReleaseEvent(self, event):
print("Mouse Release Event")
self.hide()
#self.parentWidget.hideError()
def hide(self):
self.setGeometry(0, 0, 0, 0)
# remove all buttons
for i in reversed(range(self.layout.count())):
self.layout.itemAt(i).widget().setParent(None)
# remove list of used btns
self.used_btns.clear()
self.msgs.clear()
def addButton(self, text: str = "something went wrong", level: int = 2):
print("Add button")
self.setGeometry(0, 0, self.parentWidget.frameSize().width(), self.parentWidget.frameSize().height())
# find index of the first unused button
btn = self.getNextBtn()
btn.setStyleSheet("QPushButton {color: black; border: 0px;border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR_GREY}" + "}")
btn.setText(text)
self.resizeToText(btn)
#btn.resize(btn.sizeHint())
self.layout.addWidget(btn) #, alignment=Qt.AlignCenter)
self.msgs.append(text)
self.used_btns.append(1)
def addInfoButton(self, text: str = "link here", level: int = 2, url = ""):
print("Add blue button")
self.setGeometry(0, 0, self.parentWidget.frameSize().width(), self.parentWidget.frameSize().height())
# find index of the first unused button
btn: QPushButton = self.getNextBtn()
# style the button
btn.setStyleSheet("QPushButton {color: white;border: 0px;border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR}" + "}")
btn.setText(text)
btn = self.resizeToText(btn)
self.layout.addWidget(btn) #, alignment=Qt.AlignCenter)
self.msgs.append(text)
self.used_btns.append(1)
def addLinkButton(self, text: str = "link here", level: int = 2, url = ""):
print("Add link button")
self.setGeometry(0, 0, self.parentWidget.frameSize().width(), self.parentWidget.frameSize().height())
# find index of the first unused button
btn = self.getNextBtn()
# style the button
btn.setStyleSheet("QPushButton {color: white;border: 0px;border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR}" + "} QPushButton:hover { "+ f"{BACKGR_COLOR_LIGHT}" + " }")
btn.setText(text)
self.resizeToText(btn)
btn.clicked.connect(lambda: self.openLink(url))
self.layout.addWidget(btn) #, alignment=Qt.AlignCenter)
self.msgs.append(text)
self.used_btns.append(1)
def openLink(self, url = ""):
try:
webbrowser.open(url, new=0, autoraise=True)
self.hide()
except Exception as e:
pass #logger.logToUser(str(e), level=2, func = inspect.stack()[0][3])
def getNextBtn(self) -> QPushButton:
index = len(self.used_btns)
if index >= len(self.btns):
self.used_btns.clear()
index = 0
btn = self.btns[index] # get the next "free" button
return btn
def resizeToText(self, btn):
try:
text = btn.text()
if len(text.split("\n"))>=2:
height = len(text.split("\n"))*25
print(height)
btn.setMinimumHeight(height)
return btn
except Exception as e:
print(e)
return btn
@@ -0,0 +1,163 @@
import os
from typing import List, Union
#import ui.speckle_qgis_dialog
from PyQt5 import QtWidgets, uic, QtCore
from PyQt5.QtCore import pyqtSignal
from specklepy.api.models import Stream
from specklepy.api.client import SpeckleClient
from specklepy.logging.exceptions import SpeckleException
from specklepy.api.credentials import get_local_accounts #, StreamWrapper
from specklepy.api.wrapper import StreamWrapper
from gql import gql
import inspect
try:
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
import arcpy
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
ui_class = os.path.dirname(os.path.abspath(__file__)) + "/add_stream_modal.ui"
class AddStreamModalDialog(QtWidgets.QWidget):
search_button: QtWidgets.QPushButton = None
search_text_field: QtWidgets.QLineEdit = None
search_results_list: QtWidgets.QListWidget = None
dialog_button_box: QtWidgets.QDialogButtonBox = None
accounts_dropdown: QtWidgets.QComboBox
stream_results: List[Stream] = []
speckle_client: Union[SpeckleClient, None] = None
#Events
handleStreamAdd = pyqtSignal(StreamWrapper)
def __init__(self, parent=None, speckle_client: SpeckleClient = None):
super(AddStreamModalDialog,self).__init__(parent,QtCore.Qt.WindowStaysOnTopHint)
uic.loadUi(ui_class, self) # Load the .ui file
self.show()
try:
self.speckle_client = speckle_client
self.setWindowTitle("Add Speckle stream")
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)
self.search_button.clicked.connect(self.onSearchClicked)
self.search_results_list.currentItemChanged.connect( self.searchResultChanged )
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.onOkClicked)
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.onCancelClicked)
self.accounts_dropdown.currentIndexChanged.connect(self.onAccountSelected)
self.populate_accounts_dropdown()
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def searchResultChanged(self):
try:
index = self.search_results_list.currentIndex().row()
if index == -1: self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)
else: self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def onSearchClicked(self):
try:
query = self.search_text_field.text()
sw = None
results = []
if "http" in query and len(query.split("/")) >= 3: # URL
sw = StreamWrapper(query)
stream = sw.get_client().stream.get(sw.stream_id)
if isinstance(stream, Stream): results = [stream]
else: results = []
elif self.speckle_client is not None:
results = self.speckle_client.stream.search(query)
elif self.speckle_client is None:
logToUser(f"Account cannot be authenticated: {self.accounts_dropdown.currentText()}", level=2, func = inspect.stack()[0][3])
self.stream_results = results
self.populateResultsList(sw)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def populateResultsList(self, sw):
try:
self.search_results_list.clear()
if isinstance(self.stream_results, SpeckleException):
logToUser("Some streams cannot be accessed", level=1, func = inspect.stack()[0][3])
return
for stream in self.stream_results:
host = ""
if sw is not None:
host = sw.get_account().serverInfo.url
else:
host = self.speckle_client.account.serverInfo.url
if isinstance(stream, SpeckleException):
logToUser("Some streams cannot be accessed", level=1, func = inspect.stack()[0][3])
else:
self.search_results_list.addItems([
f"{stream.name}, {stream.id} | {host}" #for stream in self.stream_results
])
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def onOkClicked(self):
try:
if isinstance(self.stream_results, SpeckleException):
logToUser("Selected stream cannot be accessed", level=1, func = inspect.stack()[0][3])
return
#elif index == -1 or len(self.stream_results) == 0:
# logger.logToUser("Select stream from \"Search Results\". No stream selected", Qgis.Warning)
# return
else:
try:
index = self.search_results_list.currentIndex().row()
stream = self.stream_results[index]
item = self.search_results_list.item(index)
url = item.text().split(" | ")[1] + "/streams/" + item.text().split(", ")[1].split(" | ")[0]
sw = StreamWrapper(url)
#acc = sw.get_account() #get_local_accounts()[self.accounts_dropdown.currentIndex()]
self.handleStreamAdd.emit(sw) #StreamWrapper(f"{acc.serverInfo.url}/streams/{stream.id}?u={acc.userInfo.id}"))
self.close()
except Exception as e:
logToUser("Some streams cannot be accessed: " + str(e), level=1, func = inspect.stack()[0][3])
return
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def onCancelClicked(self):
self.close()
def onAccountSelected(self, index):
try:
account = self.speckle_accounts[index]
self.speckle_client = SpeckleClient(account.serverInfo.url, account.serverInfo.url.startswith("https"))
self.speckle_client.authenticate_with_token(token=account.token)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def populate_accounts_dropdown(self):
# Populate the accounts comboBox
try:
self.speckle_accounts = get_local_accounts()
self.accounts_dropdown.clear()
self.accounts_dropdown.addItems(
[
f"{acc.userInfo.name}, {acc.userInfo.email} | {acc.serverInfo.url}"
for acc in self.speckle_accounts
]
)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddStreamDialog</class>
<widget class="QWidget" name="AddStreamDialog">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<item>
<layout class="QFormLayout" name="search_form">
<item row="1" column="0">
<widget class="QLabel" name="search_label">
<property name="text">
<string>Search Stream by name or URL</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="search_text_field"/>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="search_button">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="accounts_dropdown"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="accounts_label">
<property name="text">
<string>Account</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="search_results_label">
<property name="text">
<string>Search Results</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="search_results_list">
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="dialog_button_box">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,83 @@
import os
from typing import List, Tuple, Union
#import ui.speckle_qgis_dialog
from PyQt5 import QtWidgets, uic, QtCore
from PyQt5.QtCore import pyqtSignal
from specklepy.api.models import Stream
from specklepy.api.client import SpeckleClient
from specklepy.logging.exceptions import SpeckleException
from specklepy.api.credentials import Account, get_local_accounts #, StreamWrapper
from specklepy.api.wrapper import StreamWrapper
from gql import gql
import inspect
import arcpy
try:
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
ui_class = os.path.dirname(os.path.abspath(__file__)) + "/create_branch.ui"
class CreateBranchModalDialog(QtWidgets.QWidget):
name_field: QtWidgets.QLineEdit = None
description_field: QtWidgets.QLineEdit = None
dialog_button_box: QtWidgets.QDialogButtonBox = None
speckle_client: Union[SpeckleClient, None] = None
#Events
handleBranchCreate = pyqtSignal(str,str)
def __init__(self, parent=None, speckle_client: SpeckleClient = None):
super(CreateBranchModalDialog,self).__init__(parent,QtCore.Qt.WindowStaysOnTopHint)
uic.loadUi(ui_class, self) # Load the .ui file
self.show()
try:
self.speckle_client = speckle_client
self.setWindowTitle("Create New Branch")
self.name_field.textChanged.connect(self.nameCheck)
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.onOkClicked)
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.onCancelClicked)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def nameCheck(self):
try:
if len(self.name_field.text()) >= 3:
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
else:
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)
return
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def onOkClicked(self):
try:
name = self.name_field.text()
description = self.description_field.text()
self.handleBranchCreate.emit(name, description)
self.close()
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return
def onCancelClicked(self):
self.close()
def onAccountSelected(self, index):
try:
account = self.speckle_accounts[index]
self.speckle_client = SpeckleClient(account.serverInfo.url, account.serverInfo.url.startswith("https"))
self.speckle_client.authenticate_with_token(token=account.token)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CreateStreamDialog</class>
<widget class="QWidget" name="AddBranchDialog">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<item>
<layout class="QFormLayout" name="search_form">
<item row="0" column="0">
<widget class="QLabel" name="name_label">
<property name="text">
<string>Branch Name</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="name_label">
<property name="text">
<string>Description</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="name_field"/>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="description_field"/>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="dialog_button_box">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,108 @@
import os
from typing import List, Tuple, Union
#import ui.speckle_qgis_dialog
from PyQt5 import QtWidgets, uic, QtCore
from PyQt5.QtCore import pyqtSignal
import arcpy
from specklepy.api.models import Stream
from specklepy.api.client import SpeckleClient
from specklepy.logging.exceptions import SpeckleException
from specklepy.api.credentials import Account, get_local_accounts #, StreamWrapper
from specklepy.api.wrapper import StreamWrapper
from gql import gql
import inspect
try:
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
ui_class = os.path.dirname(os.path.abspath(__file__)) + "/create_stream.ui"
class CreateStreamModalDialog(QtWidgets.QWidget):
name_field: QtWidgets.QLineEdit = None
description_field: QtWidgets.QLineEdit = None
dialog_button_box: QtWidgets.QDialogButtonBox = None
accounts_dropdown: QtWidgets.QComboBox
public_toggle: QtWidgets.QCheckBox
speckle_client: Union[SpeckleClient, None] = None
#Events
handleStreamCreate = pyqtSignal(Account, str, str, bool)
def __init__(self, parent=None, speckle_client: SpeckleClient = None):
super(CreateStreamModalDialog,self).__init__(parent,QtCore.Qt.WindowStaysOnTopHint)
uic.loadUi(ui_class, self) # Load the .ui file
self.show()
try:
self.speckle_client = speckle_client
self.setWindowTitle("Create New Stream")
self.name_field.textChanged.connect(self.nameCheck)
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.onOkClicked)
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.onCancelClicked)
self.accounts_dropdown.currentIndexChanged.connect(self.onAccountSelected)
self.populate_accounts_dropdown()
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def nameCheck(self):
try:
if len(self.name_field.text()) == 0 or len(self.name_field.text()) >= 3:
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
else:
self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)
return
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def onOkClicked(self):
try:
acc = get_local_accounts()[self.accounts_dropdown.currentIndex()]
name = self.name_field.text()
description = self.description_field.text()
public = self.public_toggle.isChecked()
self.handleStreamCreate.emit(acc,name,description,public)
self.close()
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return
def onCancelClicked(self):
#self.handleCancelStreamCreate.emit()
self.close()
def onAccountSelected(self, index):
try:
account = self.speckle_accounts[index]
self.speckle_client = SpeckleClient(account.serverInfo.url, account.serverInfo.url.startswith("https"))
self.speckle_client.authenticate_with_token(token=account.token)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def populate_accounts_dropdown(self):
try:
# Populate the accounts comboBox
self.speckle_accounts = get_local_accounts()
self.accounts_dropdown.clear()
self.accounts_dropdown.addItems(
[
f"{acc.userInfo.name}, {acc.userInfo.email} | {acc.serverInfo.url}"
for acc in self.speckle_accounts
]
)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CreateStreamDialog</class>
<widget class="QWidget" name="AddStreamDialog">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<item>
<layout class="QFormLayout" name="search_form">
<item row="0" column="0">
<widget class="QLabel" name="accounts_label">
<property name="text">
<string>Account</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="name_label">
<property name="text">
<string>Stream Name</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="name_label">
<property name="text">
<string>Description</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="name_label">
<property name="text">
<string>Public</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="accounts_dropdown"/>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="name_field"/>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="description_field"/>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="public_toggle"/>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="dialog_button_box">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,55 @@
from PyQt5.QtWidgets import QMessageBox
from PyQt5 import QtCore
import arcpy
try:
from speckle.plugin_utils.helpers import splitTextIntoLines
except:
from speckle_toolbox.esri.toolboxes.speckle.plugin_utils.helpers import splitTextIntoLines
import inspect
def logToUser(msg: str, func=None, level: int = 2, plugin = None, blue = False):
print("Log to user")
msg = str(msg)
dockwidget = plugin
try:
if func is not None: msg += "::" + str(func)
writeToLog(msg, level)
if dockwidget is None: return
new_msg = splitTextIntoLines(msg, 70)
if blue is True:
dockwidget.msgLog.addInfoButton(new_msg, level=level)
else:
new_msg = addLevelSymbol(new_msg, level)
dockwidget.msgLog.addButton(new_msg, level=level)
except Exception as e: print(e); return
def logToUserWithAction(msg: str, level: int = 0, plugin = None, url = ""):
print("Log to user with action")
msg = str(msg)
dockwidget = plugin
if dockwidget is None: return
try:
new_msg = splitTextIntoLines(msg, 70)
dockwidget.msgLog.addLinkButton(new_msg, level=level, url=url)
writeToLog(new_msg, level)
except Exception as e: print(e); return
def addLevelSymbol(msg: str, level: int):
if level == 0: msg = "🛈 " + msg
if level == 1: msg = "⚠️ " + msg
if level == 2: msg = "" + msg
return msg
def writeToLog(msg: str = "", level: int = 2):
print("write log")
if level == 0: arcpy.AddMessage(msg)
if level == 1: arcpy.AddWarning(msg)
if level == 2: arcpy.AddError(msg)
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

@@ -0,0 +1,601 @@
from typing import Any, List, Optional, Tuple, Union
import arcpy
from arcpy._mp import ArcGISProject, Map, Layer as arcLayer
from arcpy.management import CreateTable
import os.path
from specklepy.api.credentials import Account, get_local_accounts
from specklepy.api.client import SpeckleClient
from specklepy.logging.exceptions import (
GraphQLException,
SpeckleException,
)
from specklepy.api.wrapper import StreamWrapper
from specklepy.api.models import Branch, Stream, Streams
from osgeo import osr
import inspect
try:
from speckle.ui.validation import tryGetStream
from speckle.speckle_arcgis import SpeckleGIS
from speckle.converter.layers import getAllProjLayers
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.ui.validation import tryGetStream
from speckle_toolbox.esri.toolboxes.speckle.speckle_arcgis import SpeckleGIS
from speckle_toolbox.esri.toolboxes.speckle.converter.layers import getAllProjLayers
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
FIELDS = ["project_streams","project_layer_selection", "lat_lon"]
def get_project_streams(self: SpeckleGIS, content: str = None):
try:
print("get proj streams")
print("GET proj streams")
project = self.gis_project
table = findOrCreateSpeckleTable(project)
if table is None: return
rows = arcpy.da.SearchCursor(table, "project_streams")
saved_streams = []
for x in rows:
saved_streams.append(x[0])
temp = []
######### need to check whether saved streams are available (account reachable)
if len(saved_streams) > 0:
for url in saved_streams:
try:
sw = StreamWrapper(url)
try:
stream = tryGetStream(sw)
except SpeckleException as e:
logToUser(e.message, level=2, func = inspect.stack()[0][3])
stream = None
#strId = stream.id # will cause exception if invalid
temp.append((sw, stream))
except SpeckleException as e:
logToUser(e.message, 2)
#except GraphQLException as e:
# logger.logToUser(e.message, Qgis.Warning)
self.current_streams = temp
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def set_project_streams(self: SpeckleGIS):
try:
print("SET proj streams")
project = self.gis_project
table = findOrCreateSpeckleTable(project)
print("SET proj streams 2")
value = [stream[0].stream_url for stream in self.current_streams] #",".join()
print(value)
if table is not None:
proj_layers = []
lan_lot = ""
with arcpy.da.UpdateCursor(table, FIELDS) as cursor:
for row in cursor: # just one row
if row[1] is not None and row[1] != "": proj_layers.append(row[1])
if row[2] is not None and row[2] != "": lan_lot = row[2]
cursor.deleteRow()
del cursor
if len(proj_layers) == 0: proj_layers.append("")
if len(value) == 0: value.append("")
cursor = arcpy.da.InsertCursor(table, FIELDS )
length = max(len(proj_layers), len(value))
for i in range(length):
if i==0:
cursor.insertRow([value[i], proj_layers[i] , lan_lot])
else:
try:
cursor.insertRow([value[i], proj_layers[i] , ""])
except:
if len(value) <= i: cursor.insertRow(["", proj_layers[i] , ""])
if len(proj_layers) <= i: cursor.insertRow([value[i], "" , ""])
del cursor
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def get_project_layer_selection(self: SpeckleGIS):
try:
print("GET project layer selection from the table")
project = self.gis_project
table = findOrCreateSpeckleTable(project)
if table is None: return
rows = arcpy.da.SearchCursor(table, "project_layer_selection")
saved_layers = []
for x in rows:
saved_layers.append(x[0])
temp = []
proj_layers = getAllProjLayers(project)
######### need to check whether saved streams are available (account reachable)
if len(saved_layers) > 0:
for layerPath in saved_layers:
if layerPath == "": continue
found = 0
for layer in proj_layers:
print(layer.dataSource)
if layer.dataSource == layerPath:
temp.append((layer.name, layer))
found += 1
break
if found == 0:
logToUser(f'Saved layer not found: "{layerPath}"', level=1, func = inspect.stack()[0][3])
self.current_layers = temp
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def set_project_layer_selection(self: SpeckleGIS):
try:
print("SET project layer selection function")
project = self.gis_project
value: List[str] = [layer[1].dataSource for layer in self.current_layers] #",".join([layer[1].dataSource for layer in self.current_layers])
print(value)
table = findOrCreateSpeckleTable(project)
#print(table)
if table is not None:
lan_lot = ""
proj_streams = []
with arcpy.da.UpdateCursor(table, FIELDS) as cursor:
for row in cursor: # just one row
if row[0] is not None and row[0] != "": proj_streams.append(row[0])
if row[2] is not None and row[2] != "": lan_lot = row[2]
cursor.deleteRow()
del cursor
if len(proj_streams) == 0: proj_streams.append("")
if len(value) == 0: value.append("")
#print(proj_streams)
cursor = arcpy.da.InsertCursor(table, FIELDS )
length = max(len(proj_streams), len(value))
#print(length)
for i in range(length):
#print(i)
if i==0:
cursor.insertRow([proj_streams[i], value[i] , lan_lot])
print(i)
else:
try:
cursor.insertRow([proj_streams[i], value[i] , ""])
except:
if len(proj_streams) <= i: cursor.insertRow(["", value[i] , ""])
if len(value) <= i: cursor.insertRow([proj_streams[i], "" , ""])
#print(i)
del cursor
#print(table)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
print("SET project layer selection 2")
def get_survey_point(self: SpeckleGIS, content = None):
try:
print("get survey point")
project = self.gis_project
table = findOrCreateSpeckleTable(project)
if table is None: return
rows = arcpy.da.SearchCursor(table, "lat_lon")
points = ""
for x in rows:
points = x[0]
break
if points != "":
vals: List[str] = points.replace(" ","").split(";")[:2]
self.lat, self.lon = [float(i) for i in vals]
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def set_survey_point(self: SpeckleGIS):
try:
# from widget (2 strings) to local vars + update SR of the map
print("SET survey point")
project = self.gis_project
vals =[ str(self.dockwidget.surveyPointLat.text()), str(self.dockwidget.surveyPointLon.text()) ]
self.lat, self.lon = [float(i.replace(" ","")) for i in vals]
pt = str(self.lat) + ";" + str(self.lon)
table = findOrCreateSpeckleTable(project)
if table is not None:
with arcpy.da.UpdateCursor(table, ["lat_lon"]) as cursor:
for row in cursor: # just one row
cursor.updateRow([pt])
break
del cursor
setProjectReferenceSystem(self)
return True
except Exception as e:
self.dockwidget.surveyPointLat.setText(str(self.lat))
self.dockwidget.surveyPointLon.setText(str(self.lon))
logToUser("Lat, Lon values invalid: " + str(e), level=2, func = inspect.stack()[0][3])
return False
def setProjectReferenceSystem(self: SpeckleGIS):
try:
# save to project; create SR
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.gis_project.activeMap.spatialReference = newProjSR
logToUser("Custom project Spatial Reference successfully applied", level=0, func = inspect.stack()[0][3])
else:
logToUser("Custom Spatial Reference could not be created", level=1, func = inspect.stack()[0][3])
return True
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return False
def findOrCreateSpeckleTable(project: ArcGISProject) -> Union[str, None]:
try:
path = project.filePath.replace("aprx","gdb") #"\\".join(project.filePath.split("\\")[:-1]) + "\\speckle_layers\\" #arcpy.env.workspace + "\\" #
if 'speckle_gis' not in arcpy.ListTables():
try:
table = CreateTable(path, "speckle_gis")
arcpy.management.AddField(table, "project_streams", "TEXT")
arcpy.management.AddField(table, "project_layer_selection", "TEXT")
arcpy.management.AddField(table, "lat_lon", "TEXT")
cursor = arcpy.da.InsertCursor(table, FIELDS )
cursor.insertRow(["",""])
del cursor
except Exception as e:
logToUser("Error creating a table: " + str(e), level=1, func = inspect.stack()[0][3])
return None
else:
#print("table already exists")
# make sure fileds exist
table = path + "\\speckle_gis"
findOrCreateTableField(table, FIELDS[0])
findOrCreateTableField(table, FIELDS[1])
findOrCreateTableField(table, FIELDS[2])
findOrCreateRow(table, FIELDS)
return table
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def findOrCreateTableField(table: str, field: str):
try:
with arcpy.da.UpdateCursor(table, [field]) as cursor:
value = None
for row in cursor:
value = row # tuple(val,)
if value[0] is None: cursor.updateRow("")
break # look at the 1st row only
del cursor
#if value is None: # if there are no rows
# cursor = arcpy.da.InsertCursor(table, [field])
# cursor.insertRow([""])
# del cursor
except: # if field doesn't exist
arcpy.management.AddField(table, field, "TEXT")
#cursor = arcpy.da.InsertCursor(table, [field] )
#cursor.insertRow([""])
del cursor
def findOrCreateRow(table:str, fields: List[str]):
try:
# check if the row exists
cursor = arcpy.da.SearchCursor(table, fields)
k=-1
for k, row in enumerate(cursor):
#print(row)
break
del cursor
# if no rows
if k == -1:
cursor = arcpy.da.InsertCursor(table, fields)
cursor.insertRow(["", "", ""])
del cursor
else:
with arcpy.da.UpdateCursor(table, fields) as cursor:
for row in cursor:
if None in row: cursor.updateRow(["","",""])
break # look at the 1st row only
del cursor
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
r'''
class speckleInputsClass:
#def __init__(self):
print("CREATING speckle inputs first time________")
instances = []
accounts: List[Account] = get_local_accounts()
account = None
streams_default: Optional[List[Stream]] = None
project = None
active_map = None
saved_streams: List[Optional[Tuple[StreamWrapper, Stream]]] = []
stream_file_path: str = ""
all_layers: List[arcLayer] = []
clients: List[SpeckleClient] = []
for acc in accounts:
if acc.isDefault: account = acc
new_client = SpeckleClient(
acc.serverInfo.url,
acc.serverInfo.url.startswith("https")
)
new_client.authenticate_with_token(token=acc.token)
clients.append(new_client)
speckle_client = None
if account:
speckle_client = SpeckleClient(
account.serverInfo.url,
account.serverInfo.url.startswith("https")
)
speckle_client.authenticate_with_token(token=account.token)
streams_default = speckle_client.stream.search("")
def __init__(self) -> None:
print("___start speckle inputs________")
self.all_layers = []
try:
aprx = ArcGISProject('CURRENT')
self.project = aprx
# following will fail if no project found
self.active_map = aprx.activeMap
if self.active_map is not None and isinstance(self.active_map, Map): # if project loaded
for layer in self.active_map.listLayers():
try: geomType = arcpy.Describe(layer.dataSource).shapeType.lower()
except: geomType = '' #print(arcpy.Describe(layer.dataSource)) #and arcpy.Describe(layer.dataSource).shapeType.lower() != "multipatch")
if (layer.isFeatureLayer and geomType != "multipatch") or layer.isRasterLayer: self.all_layers.append(layer) #type: 'arcpy._mp.Layer'
self.stream_file_path: str = aprx.filePath.replace("aprx","gdb") + "\\speckle_streams.txt"
if os.path.exists(self.stream_file_path):
try:
f = open(self.stream_file_path, "r")
content = f.read()
self.saved_streams = self.getProjectStreams(content)
f.close()
except: pass
elif len(self.stream_file_path) >10:
f = open(self.stream_file_path, "x")
f.close()
f = open(self.stream_file_path, "w")
content = ""
f.write(content)
f.close()
except: self.project = None; print("Project not found")
self.instances.append(self)
def getProjectStreams(self, content: str = None):
print("get proj streams")
if not content:
content = self.stream_file_path
try:
f = open(self.stream_file_path, "r")
content = f.read()
f.close()
except: pass
######### need to check whether saved streams are available (account reachable)
if content:
streamsTuples = []
for i, url in enumerate(content.split(",")):
streamExists = 0
index = 0
try:
#print(url)
sw = StreamWrapper(url)
stream = self.tryGetStream(sw)
for st in streamsTuples:
if isinstance(stream, Stream) and st[0].stream_id == stream.id:
streamExists = 1;
break
index += 1
if streamExists == 1: del streamsTuples[index]
streamsTuples.insert(0,(sw, stream))
except SpeckleException as e:
arcpy.AddMessage(str(e.args))
return streamsTuples
else: return []
def tryGetStream (self,sw: StreamWrapper) -> Stream:
if isinstance(sw, StreamWrapper):
steamId = sw.stream_id
try: steamId = sw.stream_id.split("/streams/")[1].split("/")[0]
except: pass
client = sw.get_client()
stream = client.stream.get(id = steamId, branch_limit = 100, commit_limit = 100)
if isinstance(stream, GraphQLException):
raise SpeckleException(stream.errors[0]['message'])
return stream
else:
raise SpeckleException('Invalid StreamWrapper provided')
class toolboxInputsClass:
print("CREATING UI inputs first time________")
instances = []
lat: float = 0.0
lon: float = 0.0
active_stream: Optional[Stream] = None
active_stream_wrapper: Optional[StreamWrapper] = None
active_branch: Optional[Branch] = None
active_commit = None
selected_layers: List[Any] = []
messageSpeckle: str = ""
action: int = 1 #send
project = None
stream_file_path: str = ""
# Get the target item's Metadata object
def __init__(self) -> None:
print("___start UI inputs________")
try:
aprx = ArcGISProject('CURRENT')
project = aprx
self.stream_file_path: str = aprx.filePath.replace("aprx","gdb") + "\\speckle_streams.txt"
if os.path.exists(self.stream_file_path):
try:
f = open(self.stream_file_path, "r")
content = f.read()
self.lat, self.lon = self.get_survey_point(content)
f.close()
except: pass
except: print("Project not found")
try:
aprx = ArcGISProject('CURRENT')
self.project = aprx
except: self.project = None; print("Project not found"); arcpy.AddWarning("Project not found")
self.instances.append(self)
def setProjectStreams(self, wr: StreamWrapper, add = True):
# ERROR 032659 Error queueing metrics request:
print("SET proj streams")
if os.path.exists(self.stream_file_path) and ".gdb\\speckle_streams.txt" in self.stream_file_path:
new_content = ""
f = open(self.stream_file_path, "r")
existing_content = f.read()
f.close()
f = open(self.stream_file_path, "w")
if str(wr.stream_url) in existing_content:
new_content = existing_content.replace(str(wr.stream_url) + "," , "")
else:
new_content = existing_content
if add == True: new_content += str(wr.stream_url) + "," # add stream
else: pass # remove stream
f.write(new_content)
f.close()
elif ".gdb\\speckle_streams.txt" in self.stream_file_path:
f = open(self.stream_file_path, "x")
f.close()
f = open(self.stream_file_path, "w")
f.write(str(wr.stream_url) + ",")
f.close()
def get_survey_point(self, content = None) -> Tuple[float]:
# get from saved project
print("get survey point")
x = y = 0
if not content:
content = None
if os.path.exists(self.stream_file_path) and ".gdb\\speckle_streams.txt" in self.stream_file_path:
try:
f = open(self.stream_file_path, "r")
content = f.read()
f.close()
except: pass
if content:
for i, coords in enumerate(content.split(",")):
if "speckle_sr_origin_" in coords:
try:
x, y = [float(c) for c in coords.replace("speckle_sr_origin_","").split(";")]
except: pass
return (x, y)
def set_survey_point(self, coords: List[float]):
# from widget (2 strings) to local vars + update SR of the map
print("SET survey point")
if len(coords) == 2:
pt = "speckle_sr_origin_" + str(coords[0]) + ";" + str(coords[1])
if os.path.exists(self.stream_file_path) and ".gdb\\speckle_streams.txt" in self.stream_file_path:
new_content = ""
f = open(self.stream_file_path, "r")
existing_content = f.read()
f.close()
f = open(self.stream_file_path, "w")
if pt in existing_content:
new_content = existing_content.replace( pt , "")
else:
new_content = existing_content
new_content += pt + "," # add point
f.write(new_content)
f.close()
elif ".gdb\\speckle_streams.txt" in self.stream_file_path:
f = open(self.stream_file_path, "x")
f.close()
f = open(self.stream_file_path, "w")
f.write(pt + ",")
f.close()
# save to project; crearte SR
self.lat, self.lon = coords[0], coords[1]
newCrsString = "+proj=tmerc +ellps=WGS84 +datum=WGS84 +units=m +no_defs +lon_0=" + str(self.lon) + " lat_0=" + str(self.lat) + " +x_0=0 +y_0=0 +k_0=1"
newCrs = osr.SpatialReference()
newCrs.ImportFromProj4(newCrsString)
newCrs.MorphToESRI() # converts the WKT to an ESRI-compatible format
validate = True if len(newCrs.ExportToWkt())>10 else False
if validate:
newProjSR = arcpy.SpatialReference()
newProjSR.loadFromString(newCrs.ExportToWkt())
#source = osr.SpatialReference()
#source.ImportFromWkt(self.project.activeMap.spatialReference.exportToString())
#transform = osr.CoordinateTransformation(source, newCrs)
self.project.activeMap.spatialReference = newProjSR
arcpy.AddMessage("Custom project CRS successfully applied")
else:
arcpy.AddWarning("Custom CRS could not be created")
else:
arcpy.AddWarning("Custom CRS could not be created: not enough coordinates provided")
return True
'''
Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

@@ -0,0 +1,620 @@
import os
import sys
from typing import List
#from speckle.converter.layers import getLayers
#import ui.speckle_qgis_dialog
from specklepy.logging.exceptions import (SpeckleException, GraphQLException)
from PyQt5 import QtWidgets, uic
from PyQt5 import QtGui
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import (QMainWindow, QApplication, QWidget,
QListWidgetItem, QAction, QDockWidget, QVBoxLayout,
QHBoxLayout, QWidget, QLabel)
from PyQt5 import QtCore
from PyQt5.QtCore import pyqtSignal, Qt, QSize, QEvent
from PyQt5 import QtGui, uic
from specklepy.api.credentials import get_local_accounts
import importlib
from specklepy.api.wrapper import StreamWrapper
from specklepy.api.client import SpeckleClient
import arcpy
import inspect
try:
#from speckle.speckle_arcgis_new import Speckle
from speckle.converter.layers import getLayers
from speckle.converter.layers import getAllProjLayers
from speckle.ui.logger import logToUser
from speckle.ui.LogWidget import LogWidget
except:
#from speckle_toolbox.esri.toolboxes.speckle.speckle_arcgis_new import Speckle
from speckle_toolbox.esri.toolboxes.speckle.converter.layers import getLayers
from speckle_toolbox.esri.toolboxes.speckle.converter.layers import getAllProjLayers
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
from speckle_toolbox.esri.toolboxes.speckle.ui.LogWidget import LogWidget
#from ui.validation import tryGetStream
# Create module-like object
#pytPath = os.path.dirname(os.path.abspath(__file__)).replace("/speckle/ui","/Speckle.pyt")
#print(pytPath)
#pytModule = importlib.machinery.SourceFileLoader("specklePyt", pytPath )
#specklePyt = pytModule.load_module("specklePyt")
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
COLOR_HIGHLIGHT = (210,210,210)
SPECKLE_COLOR = (59,130,246)
SPECKLE_COLOR_LIGHT = (69,140,255)
ICON_LOGO = os.path.dirname(os.path.abspath(__file__)) + "/logo-slab-white@0.5x.png"
ICON_SEARCH = os.path.dirname(os.path.abspath(__file__)) + "/magnify.png"
ICON_DELETE = os.path.dirname(os.path.abspath(__file__)) + "/delete.png"
ICON_DELETE_BLUE = os.path.dirname(os.path.abspath(__file__)) + "/delete-blue.png"
ICON_SEND = os.path.dirname(os.path.abspath(__file__)) + "/cube-send.png"
ICON_RECEIVE = os.path.dirname(os.path.abspath(__file__)) + "/cube-receive.png"
ICON_SEND_BLACK = os.path.dirname(os.path.abspath(__file__)) + "/cube-send-black.png"
ICON_RECEIVE_BLACK = os.path.dirname(os.path.abspath(__file__)) + "/cube-receive-black.png"
ICON_SEND_BLUE = os.path.dirname(os.path.abspath(__file__)) + "/cube-send-blue.png"
ICON_RECEIVE_BLUE = os.path.dirname(os.path.abspath(__file__)) + "/cube-receive-blue.png"
ui_class = os.path.dirname(os.path.abspath(__file__)) + "/speckle_qgis_dialog_base.ui"
print(os.path.dirname(__file__))
class SpeckleGISDialog(QMainWindow):
closingPlugin = pyqtSignal()
streamList: QtWidgets.QComboBox
sendModeButton: QtWidgets.QPushButton
receiveModeButton: QtWidgets.QPushButton
streamBranchDropdown: QtWidgets.QComboBox
layerSendModeDropdown: QtWidgets.QComboBox
commitDropdown: QtWidgets.QComboBox
layersWidget: QtWidgets.QListWidget
saveLayerSelection: QtWidgets.QPushButton
runButton: QtWidgets.QPushButton
msgLog: LogWidget = None
gridLayoutTitleBar = QtWidgets.QGridLayout
def __init__(self):
"""Constructor."""
print("START MAIN WINDOW")
super(SpeckleGISDialog, self).__init__(None)#, QtCore.Qt.WindowStaysOnTopHint)
uic.loadUi(ui_class, self) # Load the .ui file
#self.installEventFilter(self)
self.show()
#self.instances.append(1)
try:
self.streamBranchDropdown.setMaxCount(100)
self.commitDropdown.setMaxCount(100)
self.streams_add_button.setFlat(True)
self.streams_remove_button.setFlat(True)
self.saveSurveyPoint.setFlat(True)
self.saveLayerSelection.setFlat(True)
self.reloadButton.setFlat(True)
self.closeButton.setFlat(True)
color = f"color: rgb{str(SPECKLE_COLOR)};"
backgr_color = f"background-color: rgb{str(SPECKLE_COLOR)};"
backgr_color_light = f"background-color: rgb{str(SPECKLE_COLOR_LIGHT)};"
backgr_image_del = f"border-image: url({ICON_DELETE_BLUE});"
self.streams_add_button.setIcon(QIcon(ICON_SEARCH))
self.streams_add_button.setMaximumWidth(25)
self.streams_add_button.setStyleSheet("QPushButton {padding:3px;padding-left:5px;border: none; text-align: left;} QPushButton:hover { " + f"background-color: rgb{str(COLOR_HIGHLIGHT)};" + f"{color}" + " }")
self.streams_remove_button.setIcon(QIcon(ICON_DELETE))
self.streams_remove_button.setMaximumWidth(25)
self.streams_remove_button.setStyleSheet("QPushButton {padding:3px;padding-left:5px;border: none; text-align: left; image-position:right} QPushButton:hover { " + f"background-color: rgb{str(COLOR_HIGHLIGHT)};" + f"{color}" + " }") #+ f"{backgr_image_del}"
self.saveLayerSelection.setStyleSheet("QPushButton {text-align: right;} QPushButton:hover { " + f"{color}" + " }")
self.saveSurveyPoint.setStyleSheet("QPushButton {text-align: right;} QPushButton:hover { " + f"{color}" + " }")
self.reloadButton.setStyleSheet("QPushButton {text-align: left;} QPushButton:hover { " + f"{color}" + " }")
self.closeButton.setStyleSheet("QPushButton {text-align: right;} QPushButton:hover { " + f"{color}" + " }")
exitIcon = QPixmap(ICON_LOGO)
exitActIcon = QIcon(exitIcon)
backgr_color = f"background-color: rgb{str(SPECKLE_COLOR)};"
backgr_color_light = f"background-color: rgb{str(SPECKLE_COLOR_LIGHT)};"
# create a label
text_label = QtWidgets.QPushButton(" for ArcGIS")
text_label.setStyleSheet("border: 0px;"
"color: white;"
f"{backgr_color}"
"top-margin: 40 px;"
"padding: 10px;"
"padding-left: 20px;"
"font-size: 15px;"
"height: 30px;"
"text-align: left;"
)
text_label.setIcon(exitActIcon)
text_label.setIconSize(QSize(300, 93))
text_label.setMinimumSize(QSize(100, 40))
text_label.setMaximumWidth(220)
version = ""
try:
metadata_file = os.path.dirname(__file__)[:-2] + "metadata.txt"
with open(metadata_file, "r") as file:
lines = file.readlines()
for i, line in enumerate(lines):
if "version=" in line:
version = "v " + line.replace("version=", "")
break
except: pass
version_label = QtWidgets.QPushButton(f"{version}")
version_label.setStyleSheet("border: 0px;"
"color: white;"
f"{backgr_color}"
"padding-top: 15px;"
"padding-left: 0px;"
"margin-left: 0px;"
"font-size: 10px;"
"height: 30px;"
"text-align: left;"
)
widget = QWidget()
widget.setStyleSheet(f"{backgr_color}")
connect_box = QHBoxLayout(widget)
connect_box.addWidget(text_label) #, alignment=Qt.AlignCenter)
connect_box.addWidget(version_label)
connect_box.setContentsMargins(0, 0, 0, 0)
self.gridLayoutTitleBar.addWidget(widget) # fro QMainWindow
#self.setTitleBarWidget(widget) # for QDockWidget
self.sendModeButton.setStyleSheet("QPushButton {padding: 10px; border: 0px; " + f"color: rgb{str(SPECKLE_COLOR)};"+ "} QPushButton:hover { " + "}" )
self.sendModeButton.setIcon(QIcon(ICON_SEND_BLUE))
self.receiveModeButton.setFlat(True)
self.receiveModeButton.setStyleSheet("QPushButton {padding: 10px; border: 0px;}"+ "QPushButton:hover { " + f"background-color: rgb{str(COLOR_HIGHLIGHT)};" + "}" )
self.receiveModeButton.setIcon(QIcon(ICON_RECEIVE_BLACK))
self.runButton.setStyleSheet("QPushButton {color: white;border: 0px;border-radius: 17px;padding: 10px;"+ f"{backgr_color}" + "} QPushButton:hover { "+ f"{backgr_color_light}" + " }")
self.runButton.setMaximumWidth(200)
self.runButton.setIcon(QIcon(ICON_SEND))
# add widgets that will only show on event trigger
logWidget = LogWidget(parent=self)
self.layout().addWidget(logWidget)
self.msgLog = logWidget
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def resizeEvent(self, event):
try:
print("resize")
QtWidgets.QMainWindow.resizeEvent(self, event)
if self.msgLog.size().height() != 0: # visible
self.msgLog.setGeometry(0, 0, self.msgLog.parentWidget.frameSize().width(), self.msgLog.parentWidget.frameSize().height()) #.resize(self.frameSize().width(), self.frameSize().height())
except Exception as e:
#logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self)
return
def closeEvent(self, event):
try:
#import threading
print("Close event")
#threads = threading.enumerate()
#print(f"Threads total: {str(len(threads))}: {str(threads)}")
#print(self.instances)
self.closingPlugin.emit()
event.accept()
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def clearDropdown(self):
try:
#self.streamIdField.clear()
self.streamBranchDropdown.clear()
self.commitDropdown.clear()
#self.layerSendModeDropdown.clear()
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def reloadDialogUI(self, plugin):
try:
self.clearDropdown()
self.populateUI(plugin)
self.enableElements(plugin)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def run(self, plugin):
try:
print("dockwidget run")
# Setup events on first load only!
self.setupOnFirstLoad(plugin)
# Connect streams section events
self.completeStreamSection(plugin)
# Populate the UI dropdowns
self.populateUI(plugin)
print("dockwidget run end")
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def setupOnFirstLoad(self, plugin):
try:
self.runButton.clicked.connect(plugin.onRunButtonClicked)
self.streams_add_button.clicked.connect( plugin.onStreamAddButtonClicked )
self.reloadButton.clicked.connect(plugin.reloadUI)
self.closeButton.clicked.connect(plugin.onClosePlugin)
self.saveSurveyPoint.clicked.connect(plugin.set_survey_point)
self.saveLayerSelection.clicked.connect(lambda: self.populateLayerDropdown(plugin))
self.sendModeButton.clicked.connect(lambda: self.setSendMode(plugin))
self.layerSendModeDropdown.currentIndexChanged.connect( lambda: self.layerSendModeChange(plugin) )
self.receiveModeButton.clicked.connect(lambda: self.setReceiveMode(plugin))
self.streamBranchDropdown.currentIndexChanged.connect( lambda: self.runBtnStatusChanged(plugin) )
self.commitDropdown.currentIndexChanged.connect( lambda: self.runBtnStatusChanged(plugin) )
self.closingPlugin.connect(plugin.onClosePlugin)
return
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def setSendMode(self, plugin):
try:
plugin.btnAction = 0 # send
color = f"color: rgb{str(SPECKLE_COLOR)};"
self.sendModeButton.setStyleSheet("border: 0px;"
f"color: rgb{str(SPECKLE_COLOR)};"
"padding: 10px;")
self.sendModeButton.setIcon(QIcon(ICON_SEND_BLUE))
self.sendModeButton.setFlat(False)
self.receiveModeButton.setFlat(True)
self.receiveModeButton.setStyleSheet("QPushButton {border: 0px; color: black; padding: 10px; } QPushButton:hover { " + f"background-color: rgb{str(COLOR_HIGHLIGHT)};" + " };")
self.receiveModeButton.setIcon(QIcon(ICON_RECEIVE_BLACK))
#self.receiveModeButton.setFlat(True)
self.runButton.setProperty("text", " SEND")
self.runButton.setIcon(QIcon(ICON_SEND))
# enable sections only if in "saved streams" mode
if self.layerSendModeDropdown.currentIndex() == 1: self.layersWidget.setEnabled(True)
if self.layerSendModeDropdown.currentIndex() == 1: self.saveLayerSelection.setEnabled(True)
self.commitDropdown.setEnabled(False)
self.messageInput.setEnabled(True)
self.layerSendModeDropdown.setEnabled(True)
self.runBtnStatusChanged(plugin)
return
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def setReceiveMode(self, plugin):
try:
plugin.btnAction = 1 # receive
color = f"color: rgb{str(SPECKLE_COLOR)};"
self.receiveModeButton.setStyleSheet("border: 0px;"
f"color: rgb{str(SPECKLE_COLOR)};"
"padding: 10px;")
self.sendModeButton.setIcon(QIcon(ICON_SEND_BLACK))
self.sendModeButton.setStyleSheet("QPushButton {border: 0px; color: black; padding: 10px;} QPushButton:hover { " + f"background-color: rgb{str(COLOR_HIGHLIGHT)};" + " };")
self.receiveModeButton.setIcon(QIcon(ICON_RECEIVE_BLUE))
self.sendModeButton.setFlat(True)
self.receiveModeButton.setFlat(False)
#self.sendModeButton.setFlat(True)
self.runButton.setProperty("text", " RECEIVE")
self.runButton.setIcon(QIcon(ICON_RECEIVE))
#self.layerSendModeChange(plugin, 1)
self.commitDropdown.setEnabled(True)
self.layersWidget.setEnabled(False)
self.messageInput.setEnabled(False)
self.saveLayerSelection.setEnabled(False)
self.layerSendModeDropdown.setEnabled(False)
self.runBtnStatusChanged(plugin)
return
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def completeStreamSection(self, plugin):
self.streams_remove_button.clicked.connect( lambda: self.onStreamRemoveButtonClicked(plugin) )
self.streamList.currentIndexChanged.connect( lambda: self.onActiveStreamChanged(plugin) )
self.streamBranchDropdown.currentIndexChanged.connect( lambda: self.populateActiveCommitDropdown(plugin) )
return
def populateUI(self, plugin):
try:
self.populateLayerSendModeDropdown()
self.populateLayerDropdown(plugin, False)
#items = [self.layersWidget.item(x).text() for x in range(self.layersWidget.count())]
self.populateProjectStreams(plugin)
self.populateSurveyPoint(plugin)
self.runBtnStatusChanged(plugin)
self.runButton.setEnabled(False)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def runBtnStatusChanged(self, plugin):
try:
commitStr = str(self.commitDropdown.currentText())
branchStr = str(self.streamBranchDropdown.currentText())
if plugin.btnAction == 1: # on receive
if commitStr == "":
self.runButton.setEnabled(False)
else:
self.runButton.setEnabled(True)
if plugin.btnAction == 0: # on send
if branchStr == "":
self.runButton.setEnabled(False)
elif branchStr != "" and self.layerSendModeDropdown.currentIndex() == 1 and len(plugin.current_layers) == 0: # saved layers; but the list is empty
self.runButton.setEnabled(False)
else:
self.runButton.setEnabled(True)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def layerSendModeChange(self, plugin, runMode = None):
try:
print("Send mode changed")
if self.layerSendModeDropdown.currentIndex() == 0 or runMode == 1: # by manual selection OR receive mode
self.current_layers = []
self.layersWidget.setEnabled(False)
self.saveLayerSelection.setEnabled(False)
elif self.layerSendModeDropdown.currentIndex() == 1 and (runMode == 0 or runMode is None): # by saved AND when Send mode
self.layersWidget.setEnabled(True)
self.saveLayerSelection.setEnabled(True)
branchStr = str(self.streamBranchDropdown.currentText())
if self.layerSendModeDropdown.currentIndex() == 0:
if branchStr == "": self.runButton.setEnabled(False) # by manual selection
else: self.runButton.setEnabled(True) # by manual selection
elif self.layerSendModeDropdown.currentIndex() == 1: self.runBtnStatusChanged(plugin) # by saved
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def populateLayerDropdown(self, plugin, bySelection: bool = True):
print("populate layer dropdown / clicked save selection")
if not self: return
try:
from speckle.ui.project_vars import set_project_layer_selection
except:
from speckle_toolbox.esri.toolboxes.speckle.ui.project_vars import set_project_layer_selection
try:
self.layersWidget.clear()
nameDisplay = []
project = plugin.gis_project
if bySelection is False: # read from project data
print("populate layers from saved data")
#print(project)
#print(project.activeMap)
all_layers_ids = [l.dataSource for l in getAllProjLayers(project)]
for layer_tuple in plugin.current_layers:
if layer_tuple[1].dataSource in all_layers_ids:
listItem = self.fillLayerList(layer_tuple[1])
self.layersWidget.addItem(listItem)
else: # read selected layers
# Fetch selected layers
print("populate layers from selection")
plugin.current_layers = []
layers = getLayers(plugin, bySelection) # List[QgsLayerTreeNode]
print(layers)
for i, layer in enumerate(layers):
plugin.current_layers.append((layer.name, layer))
listItem = self.fillLayerList(layer)
self.layersWidget.addItem(listItem)
print("populate layers from selection 2")
set_project_layer_selection(plugin)
print("populate layers from selection 3")
self.layersWidget.setIconSize(QSize(20, 20))
self.runBtnStatusChanged(plugin)
return
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def fillLayerList(self, layer):
print("Fill layer list")
try:
ICON_XXL = os.path.dirname(os.path.abspath(__file__)) + "/size-xxl.png"
ICON_RASTER = os.path.dirname(os.path.abspath(__file__)) + "/legend_raster.png"
ICON_POLYGON = os.path.dirname(os.path.abspath(__file__)) + "/legend_polygon.png"
ICON_LINE = os.path.dirname(os.path.abspath(__file__)) + "/legend_line.png"
ICON_POINT = os.path.dirname(os.path.abspath(__file__)) + "/legend_point.png"
listItem = QListWidgetItem(layer.name)
#print(listItem)
if layer.isRasterLayer: # and layer.width()*layer.height() > 1000000:
listItem.setIcon(QIcon(ICON_RASTER))
elif layer.isFeatureLayer: # and layer.featureCount() > 20000:
geomType = arcpy.Describe(layer.dataSource).shapeType
if geomType == "Polygon": listItem.setIcon(QIcon(ICON_POLYGON))
elif geomType == "Polyline": listItem.setIcon(QIcon(ICON_LINE))
elif geomType == "Point" or geomType == "Multipoint": listItem.setIcon(QIcon(ICON_POINT))
else:
listItem.setIcon(QIcon(ICON_XXL))
#else:
# icon = QgsIconUtils().iconForLayer(layer)
# listItem.setIcon(icon)
return listItem
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def populateSurveyPoint(self, plugin):
if not self:
return
try:
self.surveyPointLat.clear()
self.surveyPointLat.setText(str(plugin.lat))
self.surveyPointLon.clear()
self.surveyPointLon.setText(str(plugin.lon))
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def enableElements(self, plugin):
try:
self.sendModeButton.setEnabled(plugin.is_setup)
self.receiveModeButton.setEnabled(plugin.is_setup)
self.runButton.setEnabled(plugin.is_setup)
self.streams_add_button.setEnabled(plugin.is_setup)
if plugin.is_setup is False: self.streams_remove_button.setEnabled(plugin.is_setup)
self.streamBranchDropdown.setEnabled(plugin.is_setup)
self.layerSendModeDropdown.setEnabled(plugin.is_setup)
self.commitDropdown.setEnabled(False)
self.show()
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def populateProjectStreams(self, plugin):
try:
from speckle.ui.project_vars import set_project_streams
except:
from speckle_toolbox.esri.toolboxes.speckle.ui.project_vars import set_project_streams
try:
if not self: return
self.streamList.clear()
for stream in plugin.current_streams:
self.streamList.addItems(
[f"Stream not accessible - {stream[0].stream_id}" if stream[1] is None or isinstance(stream[1], SpeckleException) else f"{stream[1].name}, {stream[1].id} | {stream[0].stream_url.split('/streams')[0]}"]
)
if len(plugin.current_streams)==0: self.streamList.addItems([""])
self.streamList.addItems(["Create New Stream"])
set_project_streams(plugin)
index = self.streamList.currentIndex()
if index == -1: self.streams_remove_button.setEnabled(False)
else: self.streams_remove_button.setEnabled(True)
if len(plugin.current_streams)>0: plugin.active_stream = plugin.current_streams[0]
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def onActiveStreamChanged(self, plugin):
if not self: return
try:
index = self.streamList.currentIndex()
if (len(plugin.current_streams) == 0 and index ==1) or (len(plugin.current_streams)>0 and index == len(plugin.current_streams)):
self.populateProjectStreams(plugin)
plugin.onStreamCreateClicked()
return
if len(plugin.current_streams) == 0: return
if index == -1: return
try: plugin.active_stream = plugin.current_streams[index]
except: plugin.active_stream = None
self.populateActiveStreamBranchDropdown(plugin)
self.populateActiveCommitDropdown(plugin)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def populateLayerSendModeDropdown(self):
if not self: return
try:
self.layerSendModeDropdown.clear()
self.layerSendModeDropdown.addItems(
["Send visible layers", "Send saved layers"]
)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def populateActiveStreamBranchDropdown(self, plugin):
if not self: return
if plugin.active_stream is None: return
try:
self.streamBranchDropdown.clear()
if isinstance(plugin.active_stream[1], SpeckleException):
#logger.logToUser("Some streams cannot be accessed", Qgis.Warning)
return
elif plugin.active_stream is None or plugin.active_stream[1] is None or plugin.active_stream[1].branches is None:
return
self.streamBranchDropdown.addItems(
[f"{branch.name}" for branch in plugin.active_stream[1].branches.items]
)
self.streamBranchDropdown.addItems(["Create New Branch"])
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def populateActiveCommitDropdown(self, plugin):
if not self: return
try:
self.commitDropdown.clear()
if plugin.active_stream is None: return
branchName = self.streamBranchDropdown.currentText()
if branchName == "": return
if branchName == "Create New Branch":
self.streamBranchDropdown.setCurrentText("main")
plugin.onBranchCreateClicked()
return
branch = None
if isinstance(plugin.active_stream[1], SpeckleException):
#logger.logToUser("Some streams cannot be accessed", Qgis.Warning)
return
elif plugin.active_stream[1]:
for b in plugin.active_stream[1].branches.items:
if b.name == branchName:
branch = b
break
try:
self.commitDropdown.addItems(
[f"{commit.id}"+ " | " + f"{commit.message}" for commit in branch.commits.items]
)
except: pass
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
def onStreamRemoveButtonClicked(self, plugin):
try:
#from ui.project_vars import set_project_streams
if not self: return
index = self.streamList.currentIndex()
if len(plugin.current_streams) > 0: plugin.current_streams.pop(index)
plugin.active_stream = None
self.streamBranchDropdown.clear()
self.commitDropdown.clear()
#self.streamIdField.setText("")
#set_project_streams(plugin)
self.populateProjectStreams(plugin)
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self)
@@ -0,0 +1,316 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SpeckleQArcGISDialog</class>
<widget class="QMainWindow" name="SpeckleQArcGISDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>600</height>
</rect>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayoutTitleBar">
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="leftMargin">
<number>20</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>30</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item row="0" column="1">
<layout class="QHBoxLayout" name="streamListButtons">
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="streamListLabel">
<property name="text">
<string>Stream</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="streamListButtons" stretch="20,1">
<item>
<widget class="QComboBox" name="streamList"/>
</item>
<item>
<widget class="QPushButton" name="streams_add_button">
<property name="text">
<string> </string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="streams_remove_button">
<property name="text">
<string> </string>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>10</width>
<height>10</height>
</rect>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="streamBranchLabel">
<property name="text">
<string>Branch</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="streamBranchDropdown"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="commitLabel">
<property name="text">
<string>Commit</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="commitDropdown"/>
</item>
<item row="4" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
</layout>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="sendModeButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Send</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="receiveModeButton">
<property name="text">
<string>Receive</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="layerSendModeDropdown"/>
</item>
<item row="7" column="1">
<widget class="QListWidget" name="layersWidget">
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="resizeMode">
<enum>QListView::Fixed</enum>
</property>
<property name="viewMode">
<enum>QListView::ListMode</enum>
</property>
</widget>
</item>
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="saveLayerSelection">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Set visible layers as selection</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="9" column="0">
<widget class="QLabel" name="messageLabel">
<property name="text">
<string>Message</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLineEdit" name="messageInput">
<property name="placeholderText">
<string>Sent XXX objects from ArcGIS</string>
</property>
</widget>
</item>
<item row="10" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="runButton">
<property name="text">
<string> SEND</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
</spacer>
</item>
</layout>
</item>
<item row="11" column="0">
<widget class="QLabel" name="surveyPointLabel">
<property name="text">
<string>Lat, Lon</string>
</property>
</widget>
</item>
<item row="11" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="surveyPointLat">
<property name="placeholderText">
<string>0.0</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="surveyPointLon">
<property name="placeholderText">
<string>0.0</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="saveSurveyPoint">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Set as a project center</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="12" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="reloadButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Refresh</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="closeButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,19 @@
import os
from PyQt5 import QtWidgets, uic
from PyQt5.QtCore import pyqtSignal
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
ui_class = os.path.dirname(os.path.abspath(__file__)) + "/streamlist_dialog.ui"
class StreamListDialog(QtWidgets.QWidget):
streams_add_button: QtWidgets.QPushButton
streams_reload_button: QtWidgets.QPushButton
streams_remove_button: QtWidgets.QPushButton
def __init__(self, parent=None):
super(StreamListDialog, self).__init__(parent)
uic.loadUi(ui_class, self) # Load the .ui file
self.show()
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>StreamListDialog</class>
<widget class="QWidget" name="StreamListDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>70</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="pushButton_3">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,94 @@
from typing import Union
from specklepy.api.wrapper import StreamWrapper
from specklepy.api.models import Stream, Branch, Commit
from specklepy.transports.server import ServerTransport
from specklepy.api.client import SpeckleClient
from specklepy.logging.exceptions import SpeckleException, GraphQLException
import inspect
import arcpy
try:
from speckle.ui.logger import logToUser
except:
from speckle_toolbox.esri.toolboxes.speckle.ui.logger import logToUser
def tryGetStream (sw: StreamWrapper) -> Union[Stream, None]:
try:
client = sw.get_client()
stream = client.stream.get(id = sw.stream_id, branch_limit = 100, commit_limit = 100)
if isinstance(stream, GraphQLException):
raise SpeckleException(stream.errors[0]['message'])
return stream
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def validateStream(streamWrapper: StreamWrapper) -> Union[Stream, None]:
try:
stream = tryGetStream(streamWrapper)
if isinstance(stream, SpeckleException): return None
if stream.branches is None:
logToUser("Stream has no branches", level=2, func = inspect.stack()[0][3])
return None
return stream
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def validateBranch(stream: Stream, branchName: str, checkCommits: bool) -> Union[Branch, None]:
try:
branch = None
if not stream.branches or not stream.branches.items:
return None
for b in stream.branches.items:
if b.name == branchName:
branch = b
break
if branch is None:
logToUser("Failed to find a branch", level=2, func = inspect.stack()[0][3])
return None
if checkCommits == True:
if branch.commits is None:
logToUser("Failed to find a branch", level=2, func = inspect.stack()[0][3])
return None
if len(branch.commits.items)==0:
logToUser("Branch contains no commits", level=2, func = inspect.stack()[0][3])
return None
return branch
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
return None
def validateCommit(branch: Branch, commitId: str) -> Union[Commit, None]:
try:
commit = None
try: commitId = commitId.split(" | ")[0]
except: logToUser("Commit ID is not valid", level=2, func = inspect.stack()[0][3])
for i in branch.commits.items:
if i.id == commitId:
commit = i
break
if commit is None:
try:
commit = branch.commits.items[0]
logToUser("Failed to find a commit. Receiving Latest", level=2, func = inspect.stack()[0][3])
except:
logToUser("Failed to find a commit", level=2, func = inspect.stack()[0][3])
return None
return commit
except Exception as e:
logToUser(str(e), level=2, func = inspect.stack()[0][3])
def validateTransport(client: SpeckleClient, streamId: str) -> Union[ServerTransport, None]:
try:
transport = ServerTransport(client=client, stream_id=streamId)
return transport
except Exception as e:
logToUser("Make sure you have sufficient permissions: " + str(e), level=2, func = inspect.stack()[0][3])
return None
+104
View File
@@ -0,0 +1,104 @@
from speckle_toolbox.esri.toolboxes.speckle.speckle_arcgis import Toolbox, Speckle # The code to test
from speckle_toolbox.esri.toolboxes.speckle.ui.project_vars import speckleInputsClass, toolboxInputsClass
import arcpy
import os
from specklepy.api.wrapper import StreamWrapper
from specklepy.api.models import Branch, Stream, Streams
from specklepy.logging.exceptions import GraphQLException, SpeckleException
from specklepy.api.credentials import Account
import unittest # The test framework
# remove SetUp
# add different scenqrios for Streqm Wrapper including wrng ones
# tree of all options for input or class outcome
# use dict for types chech
# issue with untestable class init
# "mocking objects" for tests or "faking"
# get functions ut of INIT
class Test_InitializingClasses(unittest.TestCase):
def setUp(self) -> None:
self.toolbox_input = toolboxInputsClass()
self.speckle_input = speckleInputsClass()
self.toolbox = Toolbox()
self.speckleTool = Speckle()
self.test_stream = "https://speckle.xyz/streams////17b0b76d13"
def text_all_toolbox(self):
self.assertTrue(isinstance(self.toolbox.tools[0], Speckle))
def test_toolbox_inputs(self):
self.assertEqual(self.toolbox_input.lat, 0)
self.assertEqual(self.toolbox_input.lon, 0)
self.assertIsNone(self.toolbox_input.active_stream)
self.assertIsNone(self.toolbox_input.active_branch)
self.assertIsNone(self.toolbox_input.active_commit)
self.assertEqual(len(self.toolbox_input.selected_layers), 0)
self.assertEqual(self.toolbox_input.messageSpeckle, "")
self.assertEqual(self.toolbox_input.action, 1)
self.assertIsNone(self.toolbox_input.project)
self.assertEqual(self.toolbox_input.stream_file_path, "")
def test_something(self):
# Arrange
toolbox_input: toolboxInputsClass = toolboxInputsClass()
# Act
toolbox_input.setProjectStreams(StreamWrapper(self.test_stream))
# Assert
os.path.exists(self.toolbox_input.stream_file_path)
def test_toolbox_inputs_functions(self):
self.toolbox_input.setProjectStreams(StreamWrapper(self.test_stream))
if os.path.exists(self.toolbox_input.stream_file_path):
f = open(self.toolbox_input.stream_file_path, "r")
existing_content = f.read()
f.close()
self.assertTrue(isinstance(existing_content, str))
self.toolbox_input.setProjectStreams(None)
if os.path.exists(self.toolbox_input.stream_file_path):
f = open(self.toolbox_input.stream_file_path, "r")
existing_content = f.read()
f.close()
self.assertTrue(isinstance(existing_content, str))
self.assertIsInstance()
self.assertTrue( isinstance(self.toolbox_input.get_survey_point(), tuple))
self.assertTrue( isinstance(self.toolbox_input.get_survey_point()[0], float) or isinstance(self.toolbox_input.get_survey_point()[0], int))
self.assertTrue( isinstance(self.toolbox_input.get_survey_point()[1], float) or isinstance(self.toolbox_input.get_survey_point()[1], int))
self.assertTrue( self.toolbox_input.set_survey_point )
def test_speckle_inputs(self):
self.assertTrue(isinstance(self.speckle_input.accounts, list))
self.assertTrue(self.speckle_input.account is None or isinstance(self.speckle_input.account, Account))
self.assertTrue(self.speckle_input.streams_default is None or isinstance(self.speckle_input.streams_default, list))
self.assertIsNone(self.speckle_input.project)
self.assertIsNone(self.speckle_input.active_map)
self.assertEqual(self.speckle_input.stream_file_path, "")
self.assertTrue(isinstance(self.speckle_input.saved_streams, list))
self.assertTrue(isinstance(self.speckle_input.all_layers, list))
self.assertTrue(isinstance(self.speckle_input.clients, list))
def test_speckle_inputs_functions(self):
self.assertTrue(isinstance(self.speckle_input.getProjectStreams(), list))
getStreams = self.speckle_input.getProjectStreams(self.test_stream)
self.assertTrue(isinstance(getStreams[0][0], StreamWrapper) and isinstance(getStreams[0][1], Stream))
self.assertTrue(isinstance(self.speckle_input.tryGetStream(StreamWrapper(self.test_stream)), Stream))
self.assertRaises(SpeckleException, lambda: self.speckle_input.tryGetStream(None))
def test_parameters(self):
actual = len(self.speckleTool.getParameterInfo())
expected = 15
self.assertEqual(actual, expected)
if __name__ == '__main__':
unittest.main()