Compare commits

...

270 Commits

Author SHA1 Message Date
Oguzhan Koral 028099219b TODOs 2024-10-01 20:07:42 +03:00
Mucahit Bilal GOKER a5824702ab Merge branch 'bilal/dui3' of https://github.com/specklesystems/speckle-blender into bilal/dui3 2024-09-29 23:40:17 +03:00
Mucahit Bilal GOKER bb8486c94a keep props dialog at the same place 2024-09-29 23:37:31 +03:00
Mucahit Bilal GOKER d32fc23e14 saving model cards to file (initial implementation) 2024-09-28 14:16:09 +03:00
Mucahit Bilal GOKER 3e85a018fc renamed selection dialog to selection filter dialog 2024-09-25 10:23:47 +03:00
Mucahit Bilal GOKER dd2e222c84 Added Speckle logo in the main panel. 2024-09-24 23:49:44 +03:00
Mucahit Bilal GOKER bcdabb1226 added project by url button. 2024-09-24 22:47:51 +03:00
Mucahit Bilal GOKER 8c1a5b4463 added type hinting 2024-09-24 22:36:42 +03:00
Mucahit Bilal GOKER 4811329d9e Add docstrings to some classes. 2024-09-24 22:26:40 +03:00
Mucahit Bilal GOKER 6c3ab4baef adjusted column widths in uilists 2024-09-24 22:04:20 +03:00
Mucahit Bilal GOKER a7295e7b25 adjusted the layout of buttons in the model card 2024-09-24 21:51:33 +03:00
Mucahit Bilal GOKER fb8fda27c5 added some todo comments to model card settings dialog. 2024-09-24 21:33:14 +03:00
Mucahit Bilal GOKER 32b114274c Added options to model card settings 2024-09-24 21:31:26 +03:00
Mucahit Bilal GOKER 02a9da050f show greeting message only when no model card added. 2024-09-24 21:16:59 +03:00
Mucahit Bilal GOKER c6ba0ff86d model cards 1st pass 2024-09-24 18:03:58 +03:00
Mucahit Bilal GOKER 0d386aa93d adjustments to init py 2024-09-24 17:03:20 +03:00
Mucahit Bilal GOKER d439f65463 Project and Model names visible across dialogs. 2024-09-23 22:47:27 +03:00
Mucahit Bilal GOKER 345bae9463 beautified the selection summary. 2024-09-23 22:05:48 +03:00
Mucahit Bilal GOKER cb1f9c0480 first pass on selection dialog. 2024-09-23 21:46:49 +03:00
Mucahit Bilal GOKER 2be74ce617 Update .gitignore 2024-09-23 16:07:58 +03:00
Mucahit Bilal GOKER 56675ef88d initial commit 2024-09-23 16:02:26 +03:00
Alper S. Soylu 4c381bd809 fix send stream object cases (#207)
* fix send stream object cases

* clean logs

* revert workaround

* revert unnecessary logic

* revert unnecessary logic

* rename set_user

---------

Co-authored-by: Soylu <alper.soylu@siemens.com>
2024-08-05 13:36:06 +01:00
Alper S. Soylu 8c3885ece8 Lazy loading for stream branches & commits (#200)
* only fetch branches on reload

* initial load

* load on stream selection

* refactor get_item_by_index

* fix resetting commit selection on default branch

* refactor load_stream_branches

* clean logs

* explicit return

---------

Co-authored-by: Soylu <alper.soylu@siemens.com>
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2024-07-29 12:15:33 +01:00
Alper S. Soylu 15bd3f5070 fix resetting commit selection on default branch (#206)
Co-authored-by: Soylu <alper.soylu@siemens.com>
2024-07-28 23:22:07 +01:00
Jedd Morgan 6fd4571d34 update macos images (#203) 2024-07-16 19:42:34 +02:00
Jedd Morgan 5081177653 Small tweaks to installer (#202)
* Skip installing dependencies after initial install on production

* remove print

* remove requirements.txt on install

* small tweaks

---------

Co-authored-by: Soylu <alper.soylu@siemens.com>
2024-07-12 12:23:36 +01:00
Alper S. Soylu 0d0ca2c811 Skip installing dependencies after initial install (#199)
* Skip installing dependencies after initial install on production

* remove print

* remove requirements.txt on install

---------

Co-authored-by: Soylu <alper.soylu@siemens.com>
2024-07-12 12:18:40 +01:00
Jedd Morgan 230e27a162 Merge pull request #196 from overengineer/fix/resetting_selected_branch_workaround
Recover branch selection
2024-06-05 14:54:21 +01:00
Jedd Morgan 669fd19c2e Type hint polish 2024-06-05 14:52:32 +01:00
Soylu 139f8ccb33 type hints 2024-06-03 19:10:54 +03:00
Soylu 7f625bd468 check None item 2024-05-30 18:11:29 +03:00
Soylu 4d07ba7637 use object ids 2024-05-30 16:53:53 +03:00
Jedd Morgan 7431b57e0e Merge pull request #198 from specklesystems/jrm/bump-deps
Updated deps
2024-05-27 16:34:39 +01:00
Jedd Morgan 57c19ba3c5 Updated deps 2024-05-27 16:34:07 +01:00
Jedd Morgan 8e3c2ece2f Merge pull request #197 from overengineer/fix/slow_startup
Speedup install dependencies
2024-05-24 16:48:29 +01:00
Soylu cfc58d9456 speedup install dependencies 2024-05-21 19:54:09 +03:00
Soylu e74a6cebb1 boundary check 2024-05-19 23:24:13 +03:00
Soylu 5e01d5a976 Recover branch selection 2024-05-16 18:19:36 +03:00
Jedd Morgan a2f7ab422f Merge pull request #194 from specklesystems/dev
2.19 changes
2024-05-14 17:41:21 +01:00
Jedd Morgan 8c58d9d14c Dev (#192)
* feat(UI): CNX-9070 update connectors to use new fe2 terminology (#186)

* feat: CNX-8705 fe2 ur ls in blender (#182)

* Increased default branch get to 100 limit, and added as mesh conversion for text + surfaces + metaball

* poetry lock

* Upgraded typing module

* FE2 URL support

* Raised exceptions now display to user

* Fixed unused imports

* Updated terminology to fe2

* merge from stash

* comments

* bl_descriptions

* bl_desc

* new urls

* Updated naming of revit elements to include family type (#193)
2024-05-14 17:38:50 +01:00
Jedd Morgan 90e61b6dc1 Updated naming of revit elements to include family type (#193) 2024-05-01 21:14:52 +01:00
Jedd Morgan 5c479e4c0e Merge branch 'main' into dev 2024-04-11 13:32:06 +01:00
Jedd Morgan 97d20ad7b1 Ci tweaks (#191) 2024-03-14 11:09:48 +00:00
Jedd Morgan 2800b84747 Merge pull request #190 from specklesystems/main
BACK MERGE MAIN -> DEV
2024-03-11 16:18:25 +00:00
Jedd Morgan 511d69314e feat(ci): [CNX-9125] Update to digicert-keylocker (#189)
* feat(ci): Update to digicert-keylocker

* removed pem
2024-03-11 17:14:36 +01:00
Jedd Morgan 24e7f02213 feat(UI): CNX-9070 update connectors to use new fe2 terminology (#186)
* feat: CNX-8705 fe2 ur ls in blender (#182)

* Increased default branch get to 100 limit, and added as mesh conversion for text + surfaces + metaball

* poetry lock

* Upgraded typing module

* FE2 URL support

* Raised exceptions now display to user

* Fixed unused imports

* Updated terminology to fe2

* merge from stash

* comments

* bl_descriptions

* bl_desc

* new urls
2024-02-28 18:53:11 +00:00
Jedd Morgan c1d7947085 Merge pull request #188 from specklesystems/main
deprecated delete stream (#187)
2024-02-28 16:46:37 +00:00
Jedd Morgan 21281e5d77 deprecated delete stream (#187) 2024-02-28 16:46:08 +00:00
Jedd Morgan 29bbdc69a2 chore(ci): CNX-9051 Update ci signing (#185)
* feat: CNX-8705 fe2 ur ls in blender (#182)

* Increased default branch get to 100 limit, and added as mesh conversion for text + surfaces + metaball

* poetry lock

* Upgraded typing module

* FE2 URL support

* Raised exceptions now display to user

* Fixed unused imports

* Update ci signing

* Update config.yml

* Bump Deps

* powershell

* More powershell
2024-02-27 11:29:33 +00:00
Jedd Morgan efe6e6a4a0 2.18 Update (#183)
* feat: CNX-8705 fe2 ur ls in blender (#182)

* Increased default branch get to 100 limit, and added as mesh conversion for text + surfaces + metaball

* poetry lock

* Upgraded typing module

* FE2 URL support

* Raised exceptions now display to user

* Fixed unused imports
2024-02-26 17:32:38 +00:00
Jedd Morgan f036109020 Increased default branch get to 100 limit, and added as mesh conversi… (#181)
* Increased default branch get to 100 limit, and added as mesh conversion for text + surfaces + metaball

* poetry lock
2024-02-07 18:05:00 +00:00
Jedd Morgan 86bc2dc590 Merge pull request #180 from specklesystems/jrm/update/2.17
chore(deps): lock deps
2023-11-29 11:57:35 +00:00
Jedd Morgan a34b6ad0c2 lock + readme 2023-11-29 11:56:50 +00:00
Jedd Morgan e436949ef9 Merge pull request #179 from specklesystems/jrm/4.0/material-support
feat(4.X): Added support for Blender 4.X material nodes
2023-11-11 20:18:37 +00:00
Jedd Morgan 6d8f4a4a80 Added support for Blender 4.X BSDF materials 2023-11-11 20:16:49 +00:00
Jedd Morgan dabb65427a Added defaults to converter settings so converter can be used without connector 2023-10-18 15:48:31 +01:00
Jedd Morgan 57ece17e8b Merge pull request #177 from specklesystems/jrm/chore/comments
chore: fixed some mistakes in code comments
2023-10-16 11:43:58 +01:00
Jedd Morgan 4362f737d0 chore: fixed some mistakes in code comments 2023-10-16 11:43:36 +01:00
Jedd Morgan b55df58313 Merge pull request #176 from specklesystems/jrm/blender/automate
Extracted some functions for automate
2023-10-13 13:02:28 +01:00
Jedd Morgan afa6722253 Extracted some functions for automate 2023-10-13 12:56:30 +01:00
Jedd Morgan a3d4881578 Merge pull request #175 from specklesystems/jrm/properties/depth
fix(custom_properties): Set max depth of properties to 64 to align with newtonsoft limit
2023-10-08 18:45:50 +01:00
JR-Morgan 1af158a5e0 poetry lock 2023-10-08 18:44:18 +01:00
JR-Morgan 47857a9db0 Aligned json depth cap with newtonsofts reader at 64 levels of depth 2023-10-08 18:40:43 +01:00
Jedd Morgan 3b026e6027 Merge pull request #173 from specklesystems/jrm/fix-py-indent-error
Update users.py
2023-09-22 13:03:32 +01:00
Jedd Morgan d572609f75 Update users.py 2023-09-22 13:03:06 +01:00
Jedd Morgan 37032cc7aa Merge pull request #172 from specklesystems/jrm/deps/specklepy216
better error message when user has no valid accounts
2023-09-12 12:35:03 +01:00
Jedd Morgan 6027325878 better error message when user has no valid accounts 2023-09-12 11:59:39 +01:00
Jedd Morgan 5ddb2aa052 Merge pull request #171 from specklesystems/jrm/deps/specklepy216
fix(receive)!: Fixed issue with collection name conflicts
2023-09-12 10:50:15 +01:00
Jedd Morgan 67a18821cc fix(receive)!: Collections will no longer update 2023-09-12 10:36:22 +01:00
Jedd Morgan 2688a69286 Merge pull request #169 from specklesystems/jrm/deps/specklepy216
Jrm/deps/specklepy216
2023-09-08 16:04:08 +01:00
Jedd Morgan 56216a6137 bump specklepy 2023-09-08 15:39:25 +01:00
Jedd Morgan 319cbf8960 Specklepy 2.16 2023-09-08 13:41:14 +01:00
Jedd Morgan d7ac6c0b95 Updated to new core metrics 2023-09-04 19:51:39 +01:00
Jedd Morgan 201ca5f26e Update blender_commit_object_builder.py 2023-07-22 01:02:23 +01:00
Jedd Morgan 89528437b1 Merge pull request #167 from specklesystems/vertex-color-uint
Fixed issue with uint32 vertex colours not parsing
2023-07-17 21:51:18 +01:00
Jedd Morgan 91bde24fe9 Fixed issue with uint32 vertex colors not parsing 2023-07-17 21:21:28 +01:00
Jedd Morgan 991b0f9ff1 Merge pull request #166 from specklesystems/2.15-deps-update
Fixed issue with hosted elements on nested instances not receiving or receiving twice
2023-07-06 15:20:58 +01:00
Jedd Morgan ee1715ff8a fixed circular import 2023-07-06 15:12:34 +01:00
Jedd Morgan 70ee09b9bb Fixed issue with non-convertable revit definitions 2023-07-06 13:57:44 +01:00
Jedd Morgan 83dd62d03f deps update 2023-07-06 13:16:50 +01:00
Jedd Morgan 94cc0ac3f7 fix(instance): Fixed issues with hosted and nested instances 2023-07-06 13:14:20 +01:00
Jedd Morgan 36cb94d3d7 fix(converter): ToSpeckle instances 2023-07-03 16:59:53 +01:00
Jedd Morgan c60baf78c5 deps: updated to specklepy 2.15.0 2023-06-27 16:09:04 +01:00
Jedd Morgan d72cfd3522 feat(view): Added support for receiving/sending view objects 2023-05-31 01:12:17 +01:00
Jedd Morgan a26618a4f7 Merge pull request #164 from specklesystems/2.14/bug-fixes
2.14/bug fixes
2023-05-29 13:39:06 +01:00
Jedd Morgan eaf370407d Updated lock 2023-05-29 13:37:49 +01:00
Jedd Morgan a2b50fe5a1 Added support for sending diffuse BSDF shader materials 2023-05-29 13:33:53 +01:00
Jedd Morgan 7e62f76841 fix: fixed bug with imperial units scaling on send 2023-05-29 12:40:54 +01:00
Jedd Morgan fc804f16d3 Fixed bug with circular referenced custom props 2023-05-29 12:40:34 +01:00
Jedd Morgan 6c7da24595 Merge pull request #163 from specklesystems/collections
Collections
2023-05-27 13:44:19 +01:00
Jedd Morgan b284d39328 removed normal re-calculation 2023-05-27 13:43:42 +01:00
Jedd Morgan 907185c9bb Object naming tweaks 2023-05-26 18:58:42 +01:00
Jedd Morgan a189a2e1c0 Various cleanup and bug fixes 2023-05-26 18:18:10 +01:00
Jedd Morgan 1fad926275 Remove empty collections from send 2023-05-25 21:01:34 +01:00
Jedd Morgan 99c147fe2f Sending collections (all collections regardless of contents) 2023-05-25 17:45:31 +01:00
Jedd Morgan e2adf710b3 commit object builder 2023-05-25 00:22:09 +01:00
Jedd Morgan 9509344533 Added traversal refactor and support for receiving collections 2023-05-18 22:15:35 +01:00
Jedd Morgan 6fabc6cae6 feat(converter): implemented view to native 2023-05-10 17:31:18 +01:00
Jedd Morgan c39298687d Merge pull request #160 from specklesystems/jrm/ismultiplayer
Added `isMultiplayer` property
2023-04-13 14:26:29 +01:00
Jedd Morgan bcdddbf930 Added isMultiplayer property 2023-04-13 14:24:17 +01:00
Jedd Morgan b5684e34f6 Merge pull request #159 from specklesystems/jrm/curve-fix
Removed merge vertices by distance from clean mesh
2023-04-05 12:51:27 +01:00
Jedd Morgan 2203fe98f8 Removed merge vertices by distance from clean mesh 2023-04-05 12:47:03 +01:00
Jedd Morgan bbfdf2863b Merge pull request #158 from specklesystems/jrm/curve-fix
Using new installer.py
2023-04-04 20:22:48 +01:00
Jedd Morgan f25f6cb16c Fixed some misc issues 2023-04-04 20:21:20 +01:00
Jedd Morgan 9e4e533ba8 Using new installer.py 2023-03-28 17:06:40 +01:00
Jedd Morgan 8db12ca9b9 Merge pull request #157 from specklesystems/jrm/curve-fix
Mesh area calc + minor cleanup
2023-03-28 16:47:59 +01:00
Jedd Morgan 366c864247 Mesh area calc + minor cleanup 2023-03-28 16:47:07 +01:00
Jedd Morgan 52136d3ef6 Merge pull request #155 from specklesystems/jrm/instances
Implemented support for new instances
2023-03-21 22:01:46 +00:00
Jedd Morgan fe764d7f0c removed old comment 2023-03-21 21:58:07 +00:00
Jedd Morgan 669dd67521 Added option to receive instances as linked duplicates 2023-03-21 21:45:53 +00:00
Jedd Morgan f74b2c37f0 Implemented support for new instances 2023-03-19 03:08:57 +00:00
Jedd Morgan ebb4e32fff Merge pull request #151 from specklesystems/jrm/curve-fix
Fixes many bugs with curves
2023-03-09 14:52:28 +00:00
Jedd Morgan 25903baf83 Set default mesh smoothing to True 2023-02-21 19:19:31 +00:00
Jedd Morgan cb6d6d7ad8 feat(converter): Added support for Ellipse and Circles 2023-02-21 19:17:14 +00:00
Jedd Morgan fd2687aa3c All non-bezier nurbs curves now work perfectly rhino->blend->rhino 2023-02-20 22:26:11 +00:00
Jedd Morgan f5c65068de feat(converter): Better curve support 2023-02-18 02:23:41 +00:00
Jedd Morgan 235b49d8c6 Merge pull request #150 from specklesystems/hotfix-authtoken
fix(auth): hotfix for serializing authTokens in blender file
2023-02-15 18:00:53 +00:00
Jedd Morgan a1ec137c67 minor cleanup 2023-02-15 16:40:49 +00:00
Jedd Morgan b95f621272 Aligned nurbs knot multiplicities with rhino curves 2023-02-15 16:37:48 +00:00
Jedd Morgan a1fcdad0e3 fix(auth): hotfix for serializing authTokens in blender file 2023-02-15 15:41:36 +00:00
Gergő Jedlicska 584e543964 makes sure to insert a path string into the sys path not a path object (#149) 2023-02-08 12:45:29 +01:00
Jedd Morgan ef20c5240c Merge pull request #148 from specklesystems/jrm/converter/fix
Custom Properties overflow fix
2023-02-05 17:20:17 +00:00
Jedd Morgan 9fe12a018a fix(ToNative): fixed issue with attaching custom properties that exceed int32 max value 2023-02-05 15:30:55 +00:00
JR-Morgan 8411c01f1b Merge branch 'main' of https://github.com/specklesystems/speckle-blender 2023-02-02 15:06:25 +00:00
JR-Morgan 63b82a30cb bumped specklepy 2.12.0 2023-02-02 15:05:59 +00:00
Jedd Morgan 0b6e39cf38 Merge pull request #147 from specklesystems/jrm/converter/multi-mesh
Fixed misalgined uv error message
2023-02-02 13:56:21 +00:00
JR-Morgan b7efcec517 uv experimentation 2023-02-02 13:45:57 +00:00
Jedd Morgan 0ab0096aac Merge pull request #142 from specklesystems/jrm/converter/multi-mesh
Jrm/converter/multi mesh
2023-02-01 14:00:11 +00:00
JR-Morgan cbd8fc99bb Merge remote-tracking branch 'origin' into jrm/converter/multi-mesh 2023-02-01 14:00:00 +00:00
JR-Morgan 2a9287e762 final polish 2023-02-01 13:52:20 +00:00
Jedd Morgan 98c70f237c Merge pull request #146 from specklesystems/gergo/alwaysInstall
fix(installer): make sure to always install specklepy dependencies
2023-02-01 13:10:22 +00:00
Gergő Jedlicska 048047cf05 fix(installer): make sure to always install specklepy dependencies
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2023-01-31 10:57:05 +02:00
Jedd Morgan 6118215cae Experimentation with receive modes 2023-01-26 14:58:53 +00:00
Jedd Morgan 8e06432fe6 relock poetry 2023-01-23 23:50:42 +00:00
Jedd Morgan cb8620ff8a Merge branch 'main' into jrm/converter/multi-mesh
Signed-off-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2023-01-23 23:49:12 +00:00
Jedd Morgan e0eddea8ab Fixed regression of null elements 2023-01-23 19:59:49 +00:00
Jedd Morgan 8497d0c195 Merge pull request #144 from specklesystems/jrm/ifc/fix
fixed IFC commits with empty lists
2023-01-20 17:00:04 +00:00
Jedd Morgan 1ce394c08f fixed issue with IFC commits with empty lists 2023-01-20 15:31:44 +00:00
Jedd Morgan aabbf87dda fix(ci): signing cert for iss 2023-01-16 17:12:39 +00:00
Jedd Morgan 7e31787d37 Updated config.yml 2023-01-16 16:29:13 +00:00
Jedd Morgan f1259587fd Bumped speckle py to 2.11.4 2023-01-16 15:00:10 +00:00
Jedd Morgan 7f913e3af0 Merge pull request #141 from specklesystems/gergo/ciContextUpdate
clone ci tools with ssh, use circleci context for env vars
2023-01-16 13:42:24 +00:00
Jedd Morgan 5433c34a4d Merge pull request #140 from specklesystems/jrm/manual-installer
Manual Installer
2023-01-16 13:39:50 +00:00
Alan Rynne 2c52e93660 ci: Updated github actions to use new actions repo 2023-01-09 20:41:22 +01:00
Gergő Jedlicska 3c3b24cf98 use innosetup context in the windows build 2023-01-06 14:01:56 +01:00
Gergő Jedlicska 3012b0ebcb clone ci tools with ssh, use circleci context for env vars 2023-01-06 12:17:19 +01:00
Jedd Morgan 35b96eaa4e feat(converter): meshes with multiple materials ToSpeckle 2022-12-19 13:42:35 +00:00
Jedd Morgan c229bb2414 materials 2022-12-15 18:04:31 +00:00
Jedd Morgan 318bd086c0 feat(converter): convert displayValues ToNative as a single mesh 2022-12-15 16:00:44 +00:00
Jedd Morgan e3eb29daa4 fiX(ci): fixed mistake in manual installer ci 2022-12-15 14:17:29 +00:00
Jedd Morgan 3359c8f275 feat(ci): Added manual instaler deploy 2022-12-14 17:01:45 +00:00
Gergő Jedlicska cfc5007d00 update tag fiter for numbered alpha and beta 2022-12-14 11:20:00 +01:00
Jedd Morgan 0a630457be Jrm/accounts reload (#138)
* add new installer mechanism

* ci(circleci): add new package connector step

* ci(circleci): install dependencies before packaging

* chore(deps): remove local specklepy dep

* chore(deps): relock again

* feat(connector): Added reload operation to user account selection

* Zero restart installation

Co-authored-by: Gergő Jedlicska <gergo@jedlicska.com>
2022-12-14 11:09:57 +01:00
Gergő Jedlicska fa124c2312 gergo/newInstaller (#136)
* add new installer mechanism

* ci(circleci): add new package connector step

* ci(circleci): install dependencies before packaging

* chore(deps): remove local specklepy dep

* chore(deps): relock again

* ci(circleci): rewrite full ci WIP

* fix(installer): ensure pip call was commented outy

* ci(circleci): updates

* ci(circleci): fix naming

* ci(circleci): update filters

* ci(circleci): update semver location

* ci(circleci): update dependency graph

* getting the specific installer branch

* get proper ci tools

* force remove new lines from installer tags

* fix installer patch pathing

* properly saving packaged zip

* add python to dotnet installer

* add zip again

* only zip the published isntaller

* publish only usefull stuff

* ci(circleci): update details in deploy

* ci(circleci): fix filters

* ci(cirleci): fix mac  build triggers

* make sure semver is set

* ci(circleci): fix env var restore in deploy

* ci(circleci): fix missing semver in files

* ci(circleci): wtf is microsoft doing with env vars in net6 containers?

* ci(circleci): fix persist to workspace pathing

* fix zipping dir

* mkdir for the zip folder

* mkdir -p

* force copy

* copy 101

* mkdir 101

* top level cp operation

* pipe the zip

* no CD

* only parameter

* build for osx 13 for arm

* moving back to a mac build machine

* fixc arm mac runtime

* remove old linux installer build

* allow alpha numbered version

* remove ci tools branch
2022-12-14 10:55:44 +01:00
Jedd Morgan 977994e141 feat(converter): initial refactor of mesh traversal 2022-12-13 19:02:43 +00:00
Jedd Morgan d9cbc80ee7 Zero restart installation 2022-12-09 15:00:44 +00:00
Jedd Morgan 45038fad79 feat(connector): Added reload operation to user account selection 2022-12-09 03:26:21 +00:00
Gergő Jedlicska cda621d735 chore(deps): relock again 2022-12-07 14:46:09 +01:00
Gergő Jedlicska 2d052d1379 chore(deps): remove local specklepy dep 2022-12-07 14:44:20 +01:00
Gergő Jedlicska 46ce2bc0df ci(circleci): install dependencies before packaging 2022-12-07 14:38:06 +01:00
Gergő Jedlicska 80e2216aa0 ci(circleci): add new package connector step 2022-12-07 14:32:48 +01:00
Gergő Jedlicska 10d69aa44b add new installer mechanism 2022-12-07 13:52:22 +01:00
Jedd Morgan b1481bd259 Merge pull request #135 from specklesystems/jrm/2.10-fixes
Jrm/2.10 fixes
2022-11-30 20:40:01 +00:00
Jedd Morgan e19fc9ef4e fix(deployment): potential fix for mac releases 2022-11-30 20:38:43 +00:00
Jedd Morgan 448eb856b2 Bumped dependencies 2022-11-30 20:36:07 +00:00
Jedd Morgan 06a7d416c7 Merge pull request #134 from specklesystems/jrm/2.10-fixes
fix(converter): fixed issue with meshes with very high number of vert…
2022-11-30 20:32:42 +00:00
Jedd Morgan 0753436899 fix(converter): fixed issue with meshes with very high number of verts taking an eternity to send
Changed from using sum to extend list functions
2022-11-29 23:54:28 +00:00
Jedd Morgan 40e0a49b11 Merge pull request #130 from specklesystems/jrm/clean-mesh-fix
fix(receive): Fixed issue with Clean Mesh creating duplicated collection
2022-11-17 16:05:56 +00:00
JR-Morgan b17b9c9de4 fix(receive): Fixed issue with Clean Mesh creating duplicated collection 2022-11-17 14:45:17 +00:00
Jedd Morgan f035111ffa Merge pull request #128 from specklesystems/fix-py39
Fixed issue with connector failing to load on Blender 2.93
2022-11-11 12:09:55 +00:00
JR-Morgan fa073f754f Merge remote-tracking branch 'origin' into fix-py39 2022-11-11 02:23:50 +00:00
Jedd Morgan 83919375f9 Merge pull request #129 from specklesystems/jrm/ci/macos
MacOS CI
2022-11-11 02:22:49 +00:00
JR-Morgan defb11bc89 fix: Fixed issue with connector on Blender 2.93 failing due to accidental use of unsupported python features 2022-11-11 02:13:19 +00:00
JR-Morgan 548b3ad352 CI fix for macos slugs 2022-10-24 21:48:25 +01:00
JR-Morgan d2deecf099 added deploy steps for macos 2022-10-24 21:27:38 +01:00
JR-Morgan fbda0110cd fix(connector): 2.9.0 update 2022-10-11 18:43:36 +01:00
Jedd Morgan ba9ad9ac07 Merge pull request #118 from specklesystems/gergo/workflowMetrics
add workflow tracking to receive operations
2022-10-11 17:56:40 +01:00
Jedd Morgan d13db2b44a Merge pull request #117 from specklesystems/jrm/receive-script-update
Jrm/receive script update
2022-10-11 17:54:33 +01:00
JR-Morgan 2c127c85f3 feat: Updated recieve scripts to use context rather than scene 2022-10-07 16:03:10 +01:00
Gergő Jedlicska cc099c0ff1 add workflow tracking to receive operations 2022-10-07 16:08:27 +02:00
JR-Morgan 448ab70c3b Merge remote-tracking branch 'origin/jrm/clean-mesh' into jrm/receive-script-update 2022-10-04 18:28:57 +01:00
JR-Morgan 020eba2727 Merge remote-tracking branch 'origin/main' into jrm/receive-script-update 2022-10-04 17:53:01 +01:00
JR-Morgan d8117e2c30 feat(converter): Updated Recieve Script callback functions to allow for access to Base object, and for callback on ToNative completion 2022-10-04 17:50:57 +01:00
Matteo Cominetti 57a6d88e6b ci 2022-09-27 10:27:50 +01:00
Matteo Cominetti 74cb3e5f85 ci: change shell 2022-09-27 10:15:58 +01:00
Matteo Cominetti d0aeecc863 Update README.md 2022-09-27 10:08:55 +01:00
Matteo Cominetti 2803308c5e Update README.md 2022-09-27 10:08:06 +01:00
Matteo Cominetti 326f04f67d ci: adds signtool param 2022-09-27 10:07:01 +01:00
JR-Morgan 68972ba8f9 Merge remote-tracking branch 'origin' into jrm/clean-mesh 2022-09-23 13:58:07 +01:00
Alan Rynne 73a028b56f Merge pull request #112 from specklesystems/ci/force-run
ci: Activated both mac runs on build
2022-09-23 14:56:02 +02:00
Alan Rynne 33890ef0ee fix: went too far 2022-09-23 14:28:51 +02:00
Alan Rynne 53fe676ab6 ci: Prettified steps 2022-09-23 14:27:50 +02:00
Alan Rynne f027c7eca4 ci: Activated both mac runs on build 2022-09-23 14:11:19 +02:00
JR-Morgan ea2b6dfb0e feat(connector): Added clean mesh option to recieve geometry with limited disolve + merge verts 2022-09-23 12:36:55 +01:00
JR-Morgan 83610cec38 Added type hinting to codebase 2022-09-21 22:22:07 +01:00
izzy lyseggen 2ed0685b10 fix(ci): soz just me clowning w regex (#108) 2022-08-26 12:09:51 +01:00
izzy lyseggen 877e616188 ci: temp remove mac steps and clean tags (#107) 2022-08-26 12:04:14 +01:00
izzy lyseggen 2e3e258a9a ci: deploy to manager 2 (#106)
* ci: add manager 2 feed

* ci: update manager2 requirements
2022-08-26 11:47:50 +01:00
izzy lyseggen 1e1c790eb4 ci: blender mac pt 2 electric boogaloo (#100)
* ci: build mac installers for both intel & arm

* ci: fix conditional logic?

* ci: final cleanup
2022-06-29 17:04:49 +01:00
izzy lyseggen 2148fe8dee ci: fix deploy job names 2022-06-29 15:07:47 +01:00
izzy lyseggen 41c87a8661 ci: clean up config and add blender 3.2 (#99)
* chore: remove unused stuff

* ci: split out pip install win job

* ci: fix modules path

* ci: try reusable command instead

* ci: fix shell specify syntax

* ci: try only mior ver for upgrade py

* ci: specify full py version

* ci: separate mac and build w tags

* ci: fix branch filter

* ci: rename deploy jobs
2022-06-29 15:05:40 +01:00
izzy lyseggen 96c9add526 ci: update version patch script 2022-06-21 16:20:25 +01:00
izzy lyseggen f425316e60 fix(converter): pass brep materials to displayvals (#96)
* chore: bump py version to >=3.8

no need for old versions, and gives us some nice syntax

* refactor(ui): remove some superficial units stuff

* fix(converter): pass brep materials to displayvals

this only passes the `renderMaterial` and it assumes all items in the brep's `displayValue` are meshes
2022-06-17 12:40:52 +01:00
izzy lyseggen abf363894e fix(ci): relax regex on the script 2022-06-15 14:46:44 +01:00
izzy lyseggen 8fc8b97b7a fix(ci): another patching fix oop 2022-06-15 14:38:04 +01:00
izzylys 64b4175585 fix(ci): patch version script 2022-06-15 12:02:56 +01:00
izzy lyseggen 4eefda3305 ci: tag deploy trigger 2022-06-15 11:47:04 +01:00
izzy lyseggen f7275140d5 ci: initial mac install ci (#95)
* ci: initial mac install ci

* ci: blender 3.1 mac support

* ci: update brew

so this worked on ssh let's see if it's ok now o.o

* ci(mac): retry py3.10 symlink

* ci: fix installer paths

* ci: tags and patch ver

* ci: mac syntax for python3! argh
2022-06-15 11:43:41 +01:00
izzy lyseggen 17a36f4fc2 ci: fix version tags again oops 2022-04-22 16:34:05 +01:00
izzy lyseggen ba931e8205 fix(installer/metrics): updates and cleanup (#92)
* ci: use semver tag instead of full circle tag

* chore: update deps

* chore: update specklepy

* feat(metrics): add blender version
2022-04-22 11:58:47 +01:00
izzy lyseggen 572925cfbb feat(convert): temps custom props patch (#89)
* chore: bump specklepy and fix deps

* feat(convert): wip attached props

* feat(convert): wip custom props
2022-04-04 12:10:57 +01:00
izzy lyseggen 03f94d6371 fix(convert): breps and dates (#87) 2022-03-28 15:03:27 +01:00
izzy lyseggen c220337aec chore: upgrade specklepy and add Blender v3.1 to ci (#86)
* chore(specklepy): upgrade to 2.6.3

* ci: add py 3.10 for blender 3.1
2022-03-28 11:47:56 +01:00
izzy lyseggen 1b67304cfc fix(converter): skip if not Pincipled BSDF (#85) 2022-03-09 11:07:35 +00:00
izzy lyseggen 25a1ec1cd1 ci: fix py 3.9 package install (#83)
* ci: test upgrade py

* ci: how about now?

* ci: we did it! 💃
2022-02-23 16:42:01 +00:00
izzy lyseggen 8296e48c28 Merge pull request #81 from specklesystems/izzy/displayvals
fix(convert): new `displayValue` to native & specklepy update
2022-02-23 11:56:15 +00:00
izzy lyseggen ca81ac6fd6 feat(convert): list displayvals to native 2022-02-23 11:50:33 +00:00
izzy lyseggen 88212b94b6 fix(users): none commit bug 2022-02-23 11:50:21 +00:00
izzy lyseggen 3375a04007 chore: update specklepy 2022-02-23 11:06:56 +00:00
izzy lyseggen c62538fc8f Merge pull request #78 from specklesystems/izzy/blocks
feat(convert): big refactor !! and blocks 🧊
2022-01-10 17:41:07 +00:00
izzylys fd83e80bbd refactor: final cleanup 2022-01-10 09:40:44 -08:00
izzylys 0a2c1f9f32 refactor(convert): remove unused methods 2022-01-10 09:26:22 -08:00
izzylys 2975737377 feat(convert): col fixes & can_convert_to_native 2022-01-10 09:18:16 -08:00
izzylys 72dea53eee feat(convert): block to speckle 2022-01-10 09:17:51 -08:00
izzylys a1aa267a30 fix(convert): custom props and cols fixes 2022-01-07 10:17:42 -08:00
izzylys df30b65fdb refactor: deleting code is my love language 2022-01-07 09:09:29 -08:00
izzylys 6176abb4a3 refactor(converter): more cleanup 2022-01-06 13:41:43 -08:00
izzylys 5c2d2d195f refactor(converter): to_speckle refactor!! 2022-01-06 11:42:46 -08:00
izzylys 7a784a4a8d fix(convert): unique names for all instances
add the id to the end of the name to ensure this
2022-01-04 13:09:58 -08:00
izzylys 2c8f2c96a9 feat(convert): BIG to native refactor + blocks 2022-01-04 12:42:14 -08:00
izzylys bc7400cb36 Merge branch 'izzy/blocks' of https://github.com/specklesystems/speckle-blender into izzy/blocks 2022-01-03 10:46:35 -08:00
izzy lyseggen 421c68a143 wip: block nonsense from other pc 2022-01-03 18:37:00 +00:00
izzy lyseggen 409adda784 Merge pull request #77 from specklesystems/izzy/nulls-fix
fix(convert): nulls on receive
2022-01-03 17:11:40 +00:00
izzylys f5f1c6a8d0 fix(convert): handle list display val 2022-01-03 17:09:57 +00:00
izzylys 1eca91a030 chore: bump specklepy 2022-01-03 17:09:29 +00:00
izzy lyseggen 6b872f11d9 Merge pull request #75 from specklesystems/izzy/add-parent-type
feat(convert): add parent type to display meshes
2021-12-09 10:59:28 +00:00
izzy lyseggen fd3742b800 feat(convert): add parent type to display meshes
makes it easier to work w geo from places like revit
2021-12-09 10:58:22 +00:00
izzy lyseggen 870174a969 Merge pull request #73 from specklesystems/ci/fix-install-platform
ci: fix install platform
2021-12-07 16:26:58 +00:00
izzy lyseggen 2c6dfcb340 ci: grab py version to ensure all is working 2021-12-07 16:22:42 +00:00
izzy lyseggen 4d3eacbe52 ci: no need to persist now i think 2021-12-07 16:05:51 +00:00
izzy lyseggen df7d631b54 ci: bump to 3.9.2 2021-12-07 16:05:23 +00:00
izzy lyseggen 07ceb07058 ci: try ths install again! 2021-12-07 16:01:16 +00:00
izzy lyseggen 112bc56ded ci: remove install specklepy step 2021-12-07 15:54:59 +00:00
izzy lyseggen 88e2744e93 ci: test in win exe 2021-12-07 15:53:52 +00:00
izzy lyseggen ad1c201c79 Merge pull request #72 from specklesystems/ci/install-changes
ci: preinstall specklepy
2021-12-07 14:54:26 +00:00
Gergő Jedlicska e18569f738 (ci) wait for install 2021-12-07 15:18:03 +01:00
Gergő Jedlicska f7e563f500 (ci) new glob 2021-12-07 15:15:13 +01:00
Gergő Jedlicska 7e56c21395 (ci) what is attached? 2021-12-07 15:01:47 +01:00
Gergő Jedlicska 30b701ee27 Merge branch 'ci/install-changes' of github.com:specklesystems/speckle-blender into ci/install-changes 2021-12-07 14:48:50 +01:00
Gergő Jedlicska 3cdbc09fc0 (ci) update job order 2021-12-07 14:48:43 +01:00
Gergő Jedlicska 656e41f03c (ci) update job order 2021-12-07 14:41:09 +01:00
Gergő Jedlicska 7da26e44b6 Update config.yml
try with glob pattern
2021-12-07 14:27:30 +01:00
izzy lyseggen 830ba842e0 ci: remove unecessary steps 2021-12-07 13:01:31 +00:00
izzy lyseggen d38f990e75 ci: fix modules path 2021-12-07 12:42:51 +00:00
izzy lyseggen c01836806c ci: persist to workspace 2021-12-07 12:38:20 +00:00
izzy lyseggen bf416dd228 ci: remove pip upgrade step? 2021-12-07 12:28:48 +00:00
izzy lyseggen a2e2c489b4 ci: change branch filter 2021-12-07 12:17:12 +00:00
izzy lyseggen c40c2d7955 ci: add specklepy install step 2021-12-07 12:08:37 +00:00
izzy lyseggen b45aa0d6c1 chore: return specklepy V in patch script w no arg
should allow us to grab the version for manual specklepy install in the ci env
2021-12-07 12:07:27 +00:00
izzy lyseggen 667616b1fb Merge pull request #68 from specklesystems/izzy/bump-deps
chore: bump to specklepy 2.4.2
2021-11-30 11:56:52 +00:00
izzy lyseggen 87cb69090a chore: bump to specklepy 2.4.2
attempting to resolve Plugin activation error due to `typing_extensions` #67
2021-11-30 11:56:10 +00:00
izzy lyseggen 4a8a0ca6c7 Merge pull request #66 from specklesystems/jm/ngons
Ngon Support
2021-11-29 10:54:20 +00:00
izzy lyseggen 3c0d1eba65 style: remove old comment re face error
(was resolved in #35)
2021-11-29 10:52:02 +00:00
JR-Morgan 14aaf4f064 N-gon ToSpeckle should now work! 2021-11-25 16:54:35 +00:00
JR-Morgan 10bf3e3af5 Experimenting with n-gon sending 2021-11-25 13:55:18 +00:00
izzy lyseggen 936d573510 Merge pull request #63 from specklesystems/izzy/read-receipts
feat(streams): add read reciepts + update to specklepy 2.4
2021-11-15 11:17:33 +00:00
izzy lyseggen 1f886202ec feat(streams): add commit received call 2021-11-15 11:14:51 +00:00
izzy lyseggen 479e5b5b98 fix(streams): update stream transport constructor 2021-11-15 11:14:29 +00:00
izzy lyseggen bed1ecf2cc chore: update specklepy to 2.4 2021-11-15 11:02:35 +00:00
izzy lyseggen a6cbce977f ci: remove pip install specklepy step 2021-10-14 17:07:08 +01:00
61 changed files with 2409 additions and 4472 deletions
+299 -91
View File
@@ -1,123 +1,331 @@
version: 2.1
orbs:
python: circleci/python@1.3.2
# Using windows for builds
win: circleci/windows@2.4.0
# Upload artifacts to s3
aws-s3: circleci/aws-s3@2.0.0
win: circleci/windows@5.0.0
jobs:
build-connector: # Reusable job for basic connectors
package-connector:
docker:
- image: cimg/python:3.11.0
steps:
- checkout
- run:
name: Setup SEMVER value
command: |
SEMVER=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "0.0.0"; fi;)
echo $SEMVER > ./SEMVER
python3 patch_version.py $SEMVER
- run:
name: install dependencies
command: poetry install --only main
- run:
name: export package dependencies
command: ./export_dependencies.sh
- persist_to_workspace:
root: ./
paths:
- bpy_speckle
- patch_installer.py
- SEMVER
build-connector-zip:
docker:
- image: cimg/python:3.11.0
steps:
- attach_workspace:
at: ./
- run: &restore_semver
name: Restore Semver
command: SEMVER=$(cat ./SEMVER) && echo $SEMVER
- run:
name: Package to Zip
command: zip -r bpy_speckle.zip bpy_speckle
- persist_to_workspace:
root: ./
paths:
- bpy_speckle.zip
get-ci-tools: # Clones our ci tools and persists them to the workspace
docker:
- image: cimg/base:2021.01
steps:
- 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 git@github.com:specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
- run:
command: cd speckle-sharp-ci-tools
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools
build-installer-win:
executor:
name: win/default # comes with python 3.7.3
shell: cmd.exe
name: win/default
environment:
SSM: 'C:\Program Files\DigiCert\DigiCert One Signing Manager Tools'
steps:
- attach_workspace:
at: ./
- run:
name: Patch installer
command: python patch_installer.py (Get-Content -Raw SEMVER)
- unless: # Build installers unsigned on non-tagged builds
condition: << pipeline.git.tag >>
steps:
- run:
name: Build Installer
command: speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\blender.iss /Sbyparam=$p
shell: cmd.exe #does not work in powershell
- when: # Setup certificates and build installers signed for tagged builds
condition: << pipeline.git.tag >>
steps:
- run:
name: "Digicert Signing Manager Setup"
command: |
cd C:\
curl.exe -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:$env:SM_API_KEY" -o smtools-windows-x64.msi
msiexec.exe /i smtools-windows-x64.msi /quiet /qn | Wait-Process
- run:
name: Create Auth & OV Signing Cert
command: |
cd C:\
echo $env:SM_CLIENT_CERT_FILE_B64 > certificate.txt
certutil -decode certificate.txt certificate.p12
- run:
name: Sync Certs
command: |
& $env:SSM\smksp_cert_sync.exe
- run:
name: Build Installer
command: speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\blender.iss /Sbyparam=$p /DSIGN_INSTALLER /DCODE_SIGNING_CERT_FINGERPRINT=%SM_CODE_SIGNING_CERT_SHA1_HASH%
shell: cmd.exe #does not work in powershell
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers/blender/blender-*.exe
build-installer-mac:
macos:
xcode: 13.4.1
resource_class: macos.m1.medium.gen1
parameters:
runtime:
type: string
slug:
type: string
default: ""
installer_path:
type: string
default: speckle-sharp-ci-tools/Mac/SpeckleBlenderInstall
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: upgrade pip and install specklepy
command: python -m pip install --upgrade pip & python -m pip install --target=.\modules specklepy
name: Exit if External PR
command: if [ "$CIRCLE_PR_REPONAME" ]; then circleci-agent step halt; fi
- run:
name: Patch
shell: powershell.exe
command:
| # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
$tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.1" } else { $env:CIRCLE_TAG }
$semver = $tag.replace("-beta","")
$version = "$($semver).$($env:CIRCLE_BUILD_NUM)"
$channel = "latest"
if($tag -like "*-beta") { $channel = "beta" }
# only create the yml if we have a tag
New-Item -Force "speckle-sharp-ci-tools/Installers/blender/$channel.yml" -ItemType File -Value "version: $version"
echo $version
python patch_version.py $version
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\blender.iss
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers
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
name: Clone
command: git clone https://$GITHUB_TOKEN@github.com/specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools
name: Install mono
command: |
HOMEBREW_NO_AUTO_UPDATE=1 brew install mono
# Compress build files
- run:
name: Install dotnet
command: curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin
- run: *restore_semver
- run:
name: Copy connector files to installer
command: |
mkdir -p <<parameters.installer_path >>/.installationFiles/
cp bpy_speckle.zip << parameters.installer_path >>/.installationFiles
- run:
name: Build Mac installer
command: ~/.dotnet/dotnet publish << parameters.installer_path >>/SpeckleBlenderInstall.sln -r << parameters.runtime >> -c Release
- run:
name: Zip installer
command: |
SEMVER=$(cat ./SEMVER)
echo $SEMVER
mkdir -p speckle-sharp-ci-tools/Installers/blender
(cd <<parameters.installer_path>>/bin/Release/net6.0/<< parameters.runtime >>/publish/ && zip -r - ./) > << parameters.slug >>-${SEMVER}.zip
cp << parameters.slug >>-${SEMVER}.zip speckle-sharp-ci-tools/Installers/blender/
deploy: # Uploads all installers found to S3
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers/blender/<< parameters.slug >>*.zip
build-installer-manual:
docker:
- image: cimg/base:2021.01
parameters:
slug:
type: string
default: bpy_speckle
steps:
- attach_workspace:
at: ./
- run: *restore_semver
- run:
name: List contents
command: ls -R speckle-sharp-ci-tools/Installers
- aws-s3/copy:
arguments: "--recursive --endpoint=https://$SPACES_REGION.digitaloceanspaces.com --acl public-read"
aws-access-key-id: SPACES_KEY
aws-region: SPACES_REGION
aws-secret-access-key: SPACES_SECRET
from: '"speckle-sharp-ci-tools/Installers/"'
to: s3://speckle-releases/installers/
name: Copy zip with semver
command: |
SEMVER=$(cat ./SEMVER)
mkdir -p speckle-sharp-ci-tools/Installers/blender
cp bpy_speckle.zip speckle-sharp-ci-tools/Installers/blender/<< parameters.slug >>-${SEMVER}.zip
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers/blender/<< parameters.slug >>*.zip
deploy-connector:
docker:
- image: mcr.microsoft.com/dotnet/sdk:6.0
parameters:
file_slug:
type: string
os:
type: string
extension:
type: string
arch:
type: string
default: Any
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: Install Manager Feed CLI
command: dotnet tool install --global Speckle.Manager.Feed
- run: *restore_semver
- run:
name: Upload new version
# this is where the installer gets the semver baked into the file name
command: |
SEMVER=$(cat ./SEMVER)
echo $SEMVER
/root/.dotnet/tools/Speckle.Manager.Feed deploy \
-s blender \
-v ${SEMVER} \
-u https://releases.speckle.dev/installers/blender/<< parameters.file_slug >>-${SEMVER}.<< parameters.extension >> \
-o << parameters.os >> \
-a << parameters.arch >> \
-f speckle-sharp-ci-tools/Installers/blender/<< parameters.file_slug >>-${SEMVER}.<< parameters.extension >>
workflows:
build:
build: # build the installers, but don't persist to workspace for deployment
jobs:
- get-ci-tools:
filters:
branches:
only:
- main
- /ci\/.*/
- package-connector:
filters: &build_filters
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?(?:\.[0-9]+)?/
- build-connector-zip:
requires:
- package-connector
filters: *build_filters
- build-connector:
slug: blender
requires:
- get-ci-tools
filters:
branches:
only:
- main
- /ci\/.*/
deploy:
jobs:
- get-ci-tools:
filters:
tags:
only: /[0-9]+(\.[0-9]+)*/
filters: *build_filters
- build-installer-win:
context: digicert-keylocker
name: Windows Installer Build
requires:
- package-connector
- get-ci-tools
filters: *build_filters
- deploy-connector:
context: do-spaces-speckle-releases
name: deploy-windows
file_slug: blender
os: WIN
arch: Any
extension: exe
requires:
- Manual Installer Build
- Windows Installer Build
- Mac Intel Build
- Mac ARM Build
filters: &deploy_filters
branches:
ignore: /.*/ # For testing only! /ci\/.*/
- build-connector:
slug: blender
ignore: /.*/
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?(?:\.[0-9]+)?/
- build-installer-mac:
name: Mac ARM Build
slug: blender-mac-arm
runtime: osx-arm64
requires:
- get-ci-tools
filters:
tags:
only: /[0-9]+(\.[0-9]+)*/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
- deploy:
- build-connector-zip
filters: *build_filters
- deploy-connector:
context: do-spaces-speckle-releases
name: deploy-mac-arm
file_slug: blender-mac-arm
os: OSX
arch: Arm
extension: zip
requires:
- Manual Installer Build
- Windows Installer Build
- Mac Intel Build
- Mac ARM Build
filters: *deploy_filters
- build-installer-mac:
name: Mac Intel Build
slug: blender-mac-intel
runtime: osx-x64
requires:
- get-ci-tools
- build-connector
filters:
tags:
only: /[0-9]+(\.[0-9]+)*/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
- build-connector-zip
filters: *build_filters
- deploy-connector:
context: do-spaces-speckle-releases
name: deploy-mac-intel
file_slug: blender-mac-intel
os: OSX
arch: Intel
extension: zip
requires:
- Manual Installer Build
- Windows Installer Build
- Mac Intel Build
- Mac ARM Build
filters: *deploy_filters
- build-installer-manual:
name: Manual Installer Build
requires:
- get-ci-tools
- build-connector-zip
filters: *build_filters
- deploy-connector:
context: do-spaces-speckle-releases
name: deploy-manual
file_slug: bpy_speckle
os: Any
arch: Any
extension: zip
requires:
- Manual Installer Build
- Windows Installer Build
- Mac Intel Build
- Mac ARM Build
filters: *deploy_filters
+4 -70
View File
@@ -6,73 +6,7 @@ on:
jobs:
update_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo "$PROJECT_ID"
echo "$STATUS_FIELD_ID"
echo 'DONE_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .settings | fromjson | .options[] | select(.name== "Done") | .id' project_data.json) >> $GITHUB_ENV
echo "$DONE_ID"
- name: Add Issue to project #it's already in the project, but we do this to get its node id!
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
- name: Update Status
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $status:ID!, $id:ID!, $value:String!) {
set_status: updateProjectNextItemField(
input: {
projectId: $project
itemId: $id
fieldId: $status
value: $value
}
) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f status=$STATUS_FIELD_ID -f id=$ITEM_ID -f value=${{ env.DONE_ID }}
uses: specklesystems/github-actions/.github/workflows/project-add-issue.yml@main
secrets: inherit
with:
issue-id: ${{ github.event.issue.node_id }}
+4 -42
View File
@@ -6,45 +6,7 @@ on:
jobs:
track_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
- name: Add Issue to project
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
uses: specklesystems/github-actions/.github/workflows/project-add-issue.yml@main
secrets: inherit
with:
issue-id: ${{ github.event.issue.node_id }}
+6 -1
View File
@@ -5,8 +5,13 @@ __pycache__/
# editor
.vscode
.idea
# dev
.venv
Installers/
modules/
modules/
.tool-versions
requirements.txt
SEMVER
dui3/
+37 -22
View File
@@ -3,7 +3,7 @@
Speckle | Blender
</h1>
<h3 align="center">
Connector for Blender 2.92 & 2.93
Connector for Blender
</h3>
<p align="center"><b>Speckle</b> is the data infrastructure for the AEC industry.</p><br/>
@@ -41,42 +41,57 @@ Give Speckle a try in no time by:
- [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=for-the-badge&logo=read-the-docs&logoColor=white)](https://speckle.guide/user/blender.html) reference on almost any end-user and developer functionality
# Repo structure
# Blender Connector
The Speckle UI can be found in the 3d viewport toolbar (N), under the Speckle tab.
Head to the [**📚 documentation**](https://speckle.guide/user/blender.html) for more information.
## Disclaimer
This code is WIP and as such should be used with extreme caution on non-sensitive projects.
## Installation
1. Place `bpy_speckle` folder in your `addons` folder. On Windows this is typically `%APPDATA%/Blender Foundation/Blender/2.80/scripts/addons`.
2. Go to `Edit->Preferences` (Ctrl + Alt + U)
3. Go to the `Add-ons` tab
4. Find and enable `SpeckleBlender 2.0` in the `Scene` category. <!-- **If enabling for the first time, expect the UI to freeze for bit while it silently installs all the dependencies.** -->
5. The Speckle UI can be found in the 3d viewport toolbar (N), under the `Speckle` tab.
We officially support Blender 3.3 and newer, on Windows and Mac.
Please follow our installation instructions on our [connector docs](https://speckle.guide/user/blender.html#installation)
## Usage
Once enabled in `Preferences -> Addons`,
The Speckle connector UI can be found in the 3d viewport toolbar (N), under the `Speckle` tab.
- Available user accounts are automatically detected and made available. To add user accounts use **Speckle Manager**.
- Select the user from the dropdown list in the `Users` panel. This will populate the `Streams` list with available streams for the selected user.
- Select a branch and commit from the dropdown menus.
- Click on `Receive` to download the objects from the selected stream, branch, and commit. The stream objects will be loaded into a Blender Collection, named `<STREAM_NAME> [ <STREAM_BRANCH> @ <BRANCH_COMMIT> ]`. <!-- You can filter the stream by entering a query into the `Filter` field (i.e. `properties.weight>10` or `type="Mesh"`). -->
- Click on `Open Stream in Web` to view the stream in your web browser.
- Select the user from the dropdown list in the `Users` panel. This will populate the `Projects` list with available projects for the selected user account.
- Select a model and version from the dropdown menus.
- Click on `Receive` to download and convert the objects from the selected model version. The objects will be linked into a Blender Collection, named `<PROJECT_NAME> [ <MODEL_NAME> @ <VERSION_ID> ]`.
- Click on `Open Model in Web` to view the model in your web browser.
## Caveats
## Supported Elements
- Mesh objects are supported. Breps are imported as meshes using their `displayValue` data.
- Curves have limited support: `Polylines` are supported; `NurbsCurves` are supported, though they are not guaranteed to look the same; `Lines` are supported; `Arcs` are not supported, though they are very roughly approximated; `PolyCurves` are supported for linear / polyline segments and very approximate arc segments. These conversions are a point of focus for further development.
The Blender Connector is still a work in progress and, as such, data sent from the Blender connector is a highly lossy exchange. Our connectors are ever evolving to facilitate more and more Speckle usecases. We welcome feedback, requests, edge cases, and contributions!
## Custom properties
The full matrix of supported Blender and Speckle types [can be found here](https://speckle.guide/user/support-tables.html#blender)
## Additional Features
- **SpeckleBlender** will look for a `texture_coordinates` property and use that to create a UV layer for the imported object. These texture coordinates are a space-separated list of floats (`[u v u v u v etc...]`) that is encoded as a base64 blob. This is subject to change as **SpeckleBlender** develops.
- If a `renderMaterial` property is found, **SpeckleBlender** will create a material named using the sub-property `renderMaterial.name`. If a material with that name already exists in Blender, **SpeckleBlender** will just assign that existing material to the object. This allows geometry to be updated without having to re-assign and re-create materials.
- Vertex colors are supported. The `colors` list from Speckle meshes is translated to a vertex color layer.
- Speckle properties will be imported as custom properties on Blender objects. Nested dictionaries are expanded to individual properties by flattening their key hierarchy. I.e. `propA:{'propB': {'propC':10, 'propD':'foobar'}}` is flattened to `propA.propB.propC = 10` and `propA.propB.propD = "foobar"`.
- If a `renderMaterial` property is found, **SpeckleBlender** will create a material named using the sub-property `renderMaterial.name`. If a material with that name already exists in Blender, **SpeckleBlender** will just assign that existing material to the object. This allows geometry to be updated without having to re-assign and re-create materials.
- Receiving vertex colors is supported. The `colors` list from Speckle meshes is translated to a vertex color layer.
- Receive/Send scripts. Allow injecting a custom python function to the receive/send process to automate any blender operations
## Dependency Installation and Compatibility with Other Blender Addons
Upon first launch of the addon, the Speckle connector installs its SpecklePy dependencies in `%appdata%/Speckle/connector_installations` on Windows and `~/.config/Speckle/connector_installations` on Mac.
This is done through our [`installer.py`](https://github.com/specklesystems/speckle-blender/blob/main/bpy_speckle/installer.py). Through pip, we install the correct version of each dependency for your blender python version, host OS, and system architecture.
As such, an internet connection is required for first launch of the connector.
Other blender addons may require dependencies that conflict with specklepy. In these cases, one or both addons may fail to load.
If you suspect you're seeing a conflict, Please uninstall other third party addons one at a time to identify which addon is conflicting.
If you find an addon that conflicts, please try using a different version of that addon (newer or older).
If you can't find a version of an addon that works, please let us know on [our forums](https://speckle.community/) the name of the addon, the versions you've tried, the version of the Speckle connector you've tried, and your OS (win/mac/linux).
## Contributing
@@ -91,4 +106,4 @@ The Speckle Community hangs out on [the forum](https://discourse.speckle.works),
Unless otherwise described, the code in this repository is licensed under the Apache-2.0 License. Please note that some modules, extensions or code herein might be otherwise licensed. This is indicated either in the root of the containing folder under a different license file, or in the respective file's header. If you have any questions, don't hesitate to get in touch with us via [email](mailto:hello@speckle.systems).
## Notes
SpeckleBlender is written and maintained by [Tom Svilans](http://tomsvilans.com) ([Github](https://github.com/tsvilans)).
Thanks to [Tom Svilans](http://tomsvilans.com) ([Github](https://github.com/tsvilans)) for the original v1 contribution!
+82 -123
View File
@@ -1,142 +1,101 @@
# MIT License
# Copyright (c) 2018-2021 Tom Svilans
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
bl_info = {
"name": "SpeckleBlender 2.0",
"author": "Speckle Systems",
"version": (0, 2, 0),
"blender": (2, 92, 0),
"location": "3d viewport toolbar (N), under the Speckle tab.",
"description": "The Speckle Connector using specklepy 2.0!",
"warning": "This add-on is WIP and should be used with caution",
"wiki_url": "https://github.com/specklesystems/speckle-blender",
"category": "Scene",
}
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
from .ui import icons
import json
"""
Import PySpeckle and attempt install if not found
"""
try:
import specklepy
except ModuleNotFoundError as error:
print("Speckle not found.")
# TODO: Implement automatic installation of speckle and dependencies
# to the local Blender module folder
# from .install_dependencies import install_dependencies
# install_dependencies()
"""
Import SpeckleBlender classes
"""
from specklepy.api.client import SpeckleClient # , SpeckleCache
from specklepy.logging import metrics
from bpy_speckle.ui import *
from bpy_speckle.properties import *
from bpy_speckle.operators import *
from bpy_speckle.callbacks import *
from bpy.app.handlers import persistent
"""
Add load handler to initialize Speckle when
loading a Blender file
"""
# UI
from .ui.main_panel import SPECKLE_PT_main_panel
from .ui.project_selection_dialog import SPECKLE_OT_project_selection_dialog, speckle_project, SPECKLE_UL_projects_list, SPECKLE_OT_add_project_by_url
from .ui.model_selection_dialog import SPECKLE_OT_model_selection_dialog, speckle_model, SPECKLE_UL_models_list
from .ui.version_selection_dialog import SPECKLE_OT_version_selection_dialog, speckle_version, SPECKLE_UL_versions_list
from .ui.selection_filter_dialog import SPECKLE_OT_selection_filter_dialog
from .ui.model_card import speckle_model_card
# Operators
from .operators.publish import SPECKLE_OT_publish
from .operators.load import SPECKLE_OT_load
from .operators.model_card_settings import SPECKLE_OT_model_card_settings, SPECKLE_OT_view_in_browser, SPECKLE_OT_view_model_versions
# Bindings
from .bindings.account_binding import AccountBinding
@persistent
def save_model_cards(scene):
model_cards_data = [card.to_dict() for card in scene.speckle_model_cards]
scene["speckle_model_cards_data"] = json.dumps(model_cards_data)
def load_model_cards(scene):
if "speckle_model_cards_data" in scene:
model_cards_data = json.loads(scene["speckle_model_cards_data"])
scene.speckle_model_cards.clear()
for card_data in model_cards_data:
card = speckle_model_card.from_dict(card_data)
scene.speckle_model_cards.add().update(card)
# Classes to load
classes = (
SPECKLE_PT_main_panel,
SPECKLE_OT_publish,
SPECKLE_OT_load,
SPECKLE_OT_project_selection_dialog, speckle_project, SPECKLE_UL_projects_list, SPECKLE_OT_add_project_by_url,
SPECKLE_OT_model_selection_dialog, speckle_model, SPECKLE_UL_models_list,
SPECKLE_OT_version_selection_dialog, speckle_version, SPECKLE_UL_versions_list,
SPECKLE_OT_selection_filter_dialog,
speckle_model_card, SPECKLE_OT_model_card_settings, SPECKLE_OT_view_in_browser, SPECKLE_OT_view_model_versions,
AccountBinding)
@bpy.app.handlers.persistent
def load_handler(dummy):
bpy.ops.speckle.users_load()
"""
Permanent handle on callbacks
"""
callbacks = {}
"""
Add Speckle classes for registering
"""
speckle_classes = []
speckle_classes.extend(operator_classes)
speckle_classes.extend(property_classes)
speckle_classes.extend(ui_classes)
load_model_cards(bpy.context.scene)
@bpy.app.handlers.persistent
def save_handler(dummy):
save_model_cards(bpy.context.scene)
# Register and Unregister
def register():
from bpy.utils import register_class
icons.load_icons()
for cls in speckle_classes:
register_class(cls)
metrics.set_host_app("Blender")
"""
Register all new properties
"""
bpy.types.Scene.speckle = bpy.props.PointerProperty(type=SpeckleSceneSettings)
bpy.types.Collection.speckle = bpy.props.PointerProperty(
type=SpeckleCollectionSettings
)
bpy.types.Object.speckle = bpy.props.PointerProperty(type=SpeckleObjectSettings)
"""
Add callbacks
"""
# Callback for displaying the current user account on top of the 3d view
# callbacks['view3d_status'] = ((
# bpy.types.SpaceView3D.draw_handler_remove, # Function pointer for removal
# bpy.types.SpaceView3D.draw_handler_add(draw_speckle_info, (None, None), 'WINDOW', 'POST_PIXEL'), # Add handler
# 'WINDOW' # Callback space for removal
# ))
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.speckle_projects = bpy.props.CollectionProperty(type=speckle_project)
bpy.types.Scene.speckle_models = bpy.props.CollectionProperty(type=speckle_model)
bpy.types.Scene.speckle_versions = bpy.props.CollectionProperty(type=speckle_version)
bpy.types.Scene.speckle_ui_mode = bpy.props.StringProperty(name="UI Mode", default="NONE")
bpy.types.Scene.speckle_model_cards = bpy.props.CollectionProperty(type=speckle_model_card)
bpy.types.Scene.speckle_model_card_index = bpy.props.IntProperty(name="Model Card Index", default=0)
bpy.types.Scene.speckle_mouse_position = bpy.props.IntVectorProperty(size=2)
bpy.app.handlers.load_post.append(load_handler)
bpy.app.handlers.save_post.append(save_handler)
def unregister():
icons.unload_icons()
for cls in classes:
bpy.utils.unregister_class(cls)
del bpy.types.Scene.speckle_projects
del bpy.types.Scene.speckle_models
del bpy.types.Scene.speckle_versions
del bpy.types.Scene.speckle_ui_mode
del bpy.types.Scene.speckle_model_cards
del bpy.types.Scene.speckle_model_card_index
del bpy.types.Scene.speckle_mouse_position
bpy.app.handlers.load_post.remove(load_handler)
bpy.app.handlers.save_post.remove(save_handler)
"""
Remove callbacks
"""
for cb in callbacks.values():
cb[0](cb[1], cb[2])
from bpy.utils import unregister_class
for cls in reversed(speckle_classes):
unregister_class(cls)
# Run the register function when the script is executed
if __name__ == "__main__":
register()
View File
+6
View File
@@ -0,0 +1,6 @@
class AccountBinding:
def get_accounts(self): #-> Account[]:
# call sqlite to get accounts from Accounts.db, return
return
+11
View File
@@ -0,0 +1,11 @@
class SendBinding:
def send(self, model_card_id: str):
# TODO:
# 1- find model card from context(or whatever state)
# 2- find objects to send
# 3- call converter bla bla
# .....
return
+75
View File
@@ -0,0 +1,75 @@
schema_version = "1.0.0"
# Example of manifest file for a Blender extension
# Change the values according to your extension
id = "speckle_blender_addon"
version = "3.0.0"
name = "Speckle for Blender BETA"
tagline = "Speckle connector for Blender"
maintainer = "Speckle Systems"
# Supported types: "add-on", "theme"
type = "add-on"
# Optional link to documentation, support, source files, etc
website = "https://speckle.guide/user/blender.html"
# Optional list defined by Blender and server, see:
# https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html
tags = ["Scene"]
blender_version_min = "4.2.0"
# # Optional: Blender version that the extension does not support, earlier versions are supported.
# # This can be omitted and defined later on the extensions platform if an issue is found.
# blender_version_max = "5.1.0"
# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix)
# https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html
license = [
"SPDX:Apache-2.0",
]
# Optional: required by some licenses.
# copyright = [
# "2002-2024 Developer Name",
# "1998 Company Name",
# ]
# Optional list of supported platforms. If omitted, the extension will be available in all operating systems.
# platforms = ["windows-x64", "macos-arm64", "linux-x64"]
# Other supported platforms: "windows-arm64", "macos-x64"
# Optional: bundle 3rd party Python modules.
# https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html
# wheels = [
# "./wheels/hexdump-3.3-py3-none-any.whl",
# "./wheels/jsmin-3.0.1-py3-none-any.whl",
# ]
# Optional: add-ons can list which resources they will require:
# * files (for access of any filesystem operations)
# * network (for internet access)
# * clipboard (to read and/or write the system clipboard)
# * camera (to capture photos and videos)
# * microphone (to capture audio)
# permissions = ["network"]
#
# If using network, remember to also check `bpy.app.online_access`
# https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access
#
# For each permission it is important to also specify the reason why it is required.
# Keep this a single short sentence without a period (.) at the end.
# For longer explanations use the documentation or detail page.
#
# [permissions]
# network = "Need to sync motion-capture data to server"
# files = "Import/export FBX from/to disk"
# clipboard = "Copy and paste bone transforms"
# Optional: build settings.
# https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build
# [build]
# paths_exclude_pattern = [
# "__pycache__/",
# "/.git/",
# "/*.zip",
# ]
-2
View File
@@ -1,2 +0,0 @@
from .on_mesh_edit import scb_on_mesh_edit
from .draw_speckle_info import draw_speckle_info
@@ -1,23 +0,0 @@
"""
Drawing callback to display active Speckle user
"""
import blf
import bpy
def draw_speckle_info(self, context):
"""
Draw active user info on the 3d viewport
"""
scn = bpy.context.scene
if len(scn.speckle.users) > 0:
user = scn.speckle.users[int(scn.speckle.active_user)]
dpi = bpy.context.preferences.system.dpi
blf.position(0, 100, 50, 0)
blf.size(0, 20, dpi)
blf.draw(0, "Active Speckle user: {} ({})".format(user.name, user.email))
blf.position(0, 100, 20, 0)
blf.size(0, 16, dpi)
blf.draw(0, "Server: {}".format(user.server))
-14
View File
@@ -1,14 +0,0 @@
import bpy
from bpy.app.handlers import persistent
@persistent
def scb_on_mesh_edit(context):
"""
DEPRECATED
Do something whenever a mesh is updated
"""
edit_obj = bpy.context.edit_object
if edit_obj is not None and edit_obj.is_updated_data is True:
print("Mesh edited: {}".format(edit_obj))
# print('>>> Update')
-4
View File
@@ -1,4 +0,0 @@
"""
Permanent handle on all user clients
"""
speckle_clients = []
-313
View File
@@ -1,313 +0,0 @@
import bpy, idprop
from mathutils import Matrix
from .from_speckle import *
from .to_speckle import *
from .util import *
from bpy_speckle.functions import _report, get_scale_length
from specklepy.objects.geometry import *
from specklepy.objects.other import RenderMaterial
FROM_SPECKLE_SCHEMAS = {
Mesh: import_mesh,
Brep: import_brep,
Curve: import_curve,
Line: import_curve,
Polyline: import_curve,
Polycurve: import_curve,
Arc: import_curve,
}
TO_SPECKLE = {
"MESH": export_mesh,
"CURVE": export_curve,
"EMPTY": export_empty,
}
def set_transform(speckle_object, blender_object):
transform = None
if hasattr(speckle_object, "transform"):
transform = speckle_object.transform
elif (
hasattr(speckle_object, "properties") and speckle_object.properties is not None
):
transform = speckle_object.properties.get("transform", None)
if transform and len(transform) == 16:
mat = Matrix(
[transform[0:4], transform[4:8], transform[8:12], transform[12:16]]
)
blender_object.matrix_world = mat
def add_blender_material(smesh, blender_object) -> None:
"""Add material to a blender object if the corresponding speckle object has a render material"""
if blender_object.data is None:
return
if not hasattr(smesh, "renderMaterial") and not hasattr(smesh, "@renderMaterial"):
return
speckle_mat = getattr(smesh, "renderMaterial", None) or smesh["@renderMaterial"]
mat_name = getattr(speckle_mat, "name", None) or speckle_mat.__dict__.get("@name")
if not mat_name:
mat_name = speckle_mat.applicationId or speckle_mat.id or speckle_mat.get_id()
blender_mat = bpy.data.materials.get(mat_name)
if not blender_mat:
blender_mat = bpy.data.materials.new(mat_name)
# for now, we're not updating these materials. as per tom's suggestion, we should have a toggle
# that enables this as the blender mats will prob be much more complex than whatever is coming in
blender_mat.use_nodes = True
inputs = blender_mat.node_tree.nodes["Principled BSDF"].inputs
inputs["Base Color"].default_value = to_rgba(speckle_mat.diffuse)
inputs["Emission"].default_value = to_rgba(speckle_mat.emissive)
inputs["Roughness"].default_value = speckle_mat.roughness
inputs["Metallic"].default_value = speckle_mat.metalness
inputs["Alpha"].default_value = speckle_mat.opacity
if speckle_mat.opacity < 1:
blender_mat.blend_method = "BLEND"
blender_object.data.materials.append(blender_mat)
def material_to_speckle(blender_object) -> RenderMaterial:
"""Create and return a render material from a blender object"""
if not getattr(blender_object.data, "materials", None):
return
blender_mat = blender_object.data.materials[0]
speckle_mat = RenderMaterial()
speckle_mat.name = blender_mat.name
if blender_mat.use_nodes is True:
inputs = blender_mat.node_tree.nodes["Principled BSDF"].inputs
speckle_mat.diffuse = to_argb_int(inputs["Base Color"].default_value)
speckle_mat.emissive = to_argb_int(inputs["Emission"].default_value)
speckle_mat.roughness = inputs["Roughness"].default_value
speckle_mat.metalness = inputs["Metallic"].default_value
speckle_mat.opacity = inputs["Alpha"].default_value
else:
speckle_mat.diffuse = to_argb_int(blender_mat.diffuse_color)
speckle_mat.metalness = blender_mat.metallic
speckle_mat.roughness = blender_mat.roughness
return speckle_mat
def try_add_property(speckle_object, blender_object, prop, prop_name):
if prop in speckle_object.keys() and speckle_object[prop] is not None:
blender_object[prop_name] = speckle_object[prop]
# def add_dictionary(prop, blender_object, superkey=None):
# for key in prop.keys():
# key_name = "{}.{}".format(superkey, key) if superkey else "{}".format(key)
# if isinstance(prop[key], dict):
# subtype = prop[key].get("type", None)
# if subtype and subtype in FROM_SPECKLE.keys():
# continue
# else:
# add_dictionary(prop[key], blender_object, key_name)
# elif hasattr(prop[key], "type"):
# subtype = prop[key].type
# if subtype and subtype in FROM_SPECKLE.keys():
# continue
# else:
# try:
# blender_object[key_name] = prop[key]
# except KeyError:
# pass
def add_custom_properties(speckle_object, blender_object):
if blender_object is None:
return
blender_object["_speckle_type"] = type(speckle_object).__name__
# blender_object['_speckle_name'] = "SpeckleObject"
ignore = ["_chunkable", "_units", "units"]
if hasattr(speckle_object, "applicationId"):
blender_object["applicationId"] = speckle_object.applicationId
for key in speckle_object.get_dynamic_member_names():
if key in ignore:
continue
if isinstance(speckle_object[key], (int, str, float, dict)):
blender_object[key] = speckle_object[key]
def dict_to_speckle_object(data):
if "type" in data.keys() and data["type"] in SCHEMAS.keys():
obj = SCHEMAS[data["type"]].parse_obj(data)
for key in obj.properties.keys():
if isinstance(obj.properties[key], dict):
obj.properties[key] = dict_to_speckle_object(obj.properties[key])
elif isinstance(obj.properties[key], list):
for i in range(len(obj.properties[key])):
if isinstance(obj.properties[key][i], dict):
obj.properties[key][i] = dict_to_speckle_object(
obj.properties[key][i]
)
return obj
else:
for key in data.keys():
if isinstance(data[key], dict):
data[key] = dict_to_speckle_object(data[key])
elif isinstance(data[key], list):
for i in range(len(data[key])):
if isinstance(data[key][i], dict):
data[key][i] = dict_to_speckle_object(data[key][i])
return data
def from_speckle_object(speckle_object, scale, name=None):
speckle_name = (
name
or getattr(speckle_object, "name", None)
or speckle_object.speckle_type + f" -- {speckle_object.id}"
)
units = getattr(speckle_object, "units", None)
if units:
scale = get_scale_length(units) / bpy.context.scene.unit_settings.scale_length
# try native conversion
if type(speckle_object) in FROM_SPECKLE_SCHEMAS.keys():
print("Got object type: {}".format(type(speckle_object)))
try:
obdata = FROM_SPECKLE_SCHEMAS[type(speckle_object)](
speckle_object, scale, speckle_name
)
except Exception as e: # conversion error
_report(f"Error converting {speckle_object} \n{e}")
return None
if speckle_name in bpy.data.objects.keys():
blender_object = bpy.data.objects[speckle_name]
blender_object.data = obdata
blender_object.matrix_world = Matrix()
if hasattr(obdata, "materials"):
blender_object.data.materials.clear()
else:
blender_object = bpy.data.objects.new(speckle_name, obdata)
blender_object.speckle.object_id = str(speckle_object.id)
blender_object.speckle.enabled = True
add_custom_properties(speckle_object, blender_object)
add_blender_material(speckle_object, blender_object)
# TODO: chat with tom re transforms
# set_transform(speckle_object, blender_object)
return blender_object
# try display mesh
mesh = getattr(
speckle_object, "displayMesh", getattr(speckle_object, "displayValue", None)
)
if mesh:
return from_speckle_object(mesh, scale, speckle_name)
# return none if fail
_report("Invalid input: {}".format(speckle_object))
return None
def get_speckle_subobjects(attr, scale, name):
subobjects = []
for key in attr.keys():
if isinstance(attr[key], dict):
subtype = attr[key].get("type", None)
if subtype:
name = "{}.{}".format(name, key)
# print("{} :: {}".format(name, subtype))
subobject = from_speckle_object(attr[key], scale, name)
add_custom_properties(attr[key], subobject)
subobjects.append(subobject)
props = attr[key].get("properties", None)
if props:
subobjects.extend(get_speckle_subobjects(props, scale, name))
elif hasattr(attr[key], "type"):
subtype = attr[key].type
if subtype:
name = "{}.{}".format(name, key)
# print("{} :: {}".format(name, subtype))
subobject = from_speckle_object(attr[key], scale, name)
add_custom_properties(attr[key], subobject)
subobjects.append(subobject)
props = attr[key].get("properties", None)
if props:
subobjects.extend(get_speckle_subobjects(props, scale, name))
return subobjects
ignored_keys = [
"speckle",
"_speckle_type",
"_speckle_name",
"_speckle_transform",
"_RNA_UI",
"transform",
"_units",
"_chunkable",
]
def get_blender_custom_properties(obj, max_depth=1000):
global ignored_keys
if max_depth < 0:
return obj
if hasattr(obj, "keys"):
return {
key: get_blender_custom_properties(obj[key], max_depth - 1)
for key in obj.keys()
if key not in ignored_keys and not key.startswith("_")
}
elif isinstance(obj, (list, tuple, idprop.types.IDPropertyArray)):
return [get_blender_custom_properties(o, max_depth - 1) for o in obj]
else:
return obj
def to_speckle_object(blender_object, scale, desgraph=None):
blender_type = blender_object.type
speckle_objects = []
speckle_material = material_to_speckle(blender_object)
if blender_type in TO_SPECKLE.keys():
if desgraph:
blender_object = blender_object.evaluated_get(desgraph)
converted = TO_SPECKLE[blender_type](blender_object, blender_object.data, scale)
if isinstance(converted, list):
speckle_objects.extend([c for c in converted if c != None])
for so in speckle_objects:
so.properties = get_blender_custom_properties(blender_object)
so.applicationId = so.properties.pop("applicationId", None)
if speckle_material:
so["renderMaterial"] = speckle_material
# Set object transform
so.properties["transform"] = [y for x in blender_object.matrix_world for y in x]
# _report(speckle_objects)
return speckle_objects
@@ -1,3 +0,0 @@
from .mesh import import_mesh
from .curve import import_curve
from .brep import import_brep
-24
View File
@@ -1,24 +0,0 @@
import bpy
from .mesh import to_bmesh
from bpy_speckle.util import find_key_case_insensitive
def import_brep(speckle_brep, scale, name=None):
if not name:
name = speckle_brep.geometryHash or speckle_brep.id
display = getattr(
speckle_brep, "displayMesh", getattr(speckle_brep, "displayValue", None)
)
if display:
if name in bpy.data.meshes.keys():
mesh = bpy.data.meshes[name]
else:
mesh = bpy.data.meshes.new(name=name)
to_bmesh(display, mesh, name, scale)
# add_custom_properties(speckle_brep[dvKey], mesh)
else:
mesh = None
return mesh
-237
View File
@@ -1,237 +0,0 @@
import bpy, math
from bpy_speckle.util import find_key_case_insensitive
import mathutils
from specklepy.objects.geometry import *
CONVERT = {}
def import_line(scurve, bcurve, scale):
line = bcurve.splines.new("POLY")
line.points.add(1)
line.points[0].co = (
float(scurve.start.x) * scale,
float(scurve.start.y) * scale,
float(scurve.start.z) * scale,
1,
)
if scurve.end:
line.points[1].co = (
float(scurve.end.x) * scale,
float(scurve.end.y) * scale,
float(scurve.end.z) * scale,
1,
)
return line
CONVERT[Line] = import_line
def import_polyline(scurve, bcurve, scale):
# value = find_key_case_insensitive(scurve, "value")
value = scurve.value
if value:
N = int(len(value) / 3)
polyline = bcurve.splines.new("POLY")
if hasattr(scurve, "closed"):
polyline.use_cyclic_u = scurve.closed
# if "closed" in scurve.keys():
# polyline.use_cyclic_u = scurve["closed"]
polyline.points.add(N - 1)
for i in range(N):
polyline.points[i].co = (
float(value[i * 3]) * scale,
float(value[i * 3 + 1]) * scale,
float(value[i * 3 + 2]) * scale,
1,
)
return polyline
CONVERT[Polyline] = import_polyline
def import_nurbs_curve(scurve, bcurve, scale):
# points = find_key_case_insensitive(scurve, "points")
points = scurve.points
if points:
N = int(len(points) / 3)
nurbs = bcurve.splines.new("NURBS")
if hasattr(scurve, "closed"):
nurbs.use_cyclic_u = scurve.closed != 0
nurbs.points.add(N - 1)
for i in range(N):
nurbs.points[i].co = (
float(points[i * 3]) * scale,
float(points[i * 3 + 1]) * scale,
float(points[i * 3 + 2]) * scale,
1,
)
if len(scurve.weights) == len(nurbs.points):
for i, w in enumerate(scurve.weights):
nurbs.points[i].weight = w
# TODO: anaylize curve knots to decide if use_endpoint_u or use_bezier_u should be enabled
# nurbs.use_endpoint_u = True
nurbs.order_u = scurve.degree + 1
return nurbs
CONVERT[Curve] = import_nurbs_curve
def import_arc(rcurve, bcurve, scale):
"""
Convert Arc object
TODO: improve Blender representation of arc
"""
plane = rcurve.plane
if not plane:
return
origin = plane.origin
normal = mathutils.Vector([plane.normal.x, plane.normal.y, plane.normal.z])
xaxis = plane.xdir
yaxis = plane.ydir
radius = rcurve.radius * scale
startAngle = rcurve.startAngle
endAngle = rcurve.endAngle
startQuat = mathutils.Quaternion(normal, startAngle)
endQuat = mathutils.Quaternion(normal, endAngle)
"""
Get start and end vectors, centre point, angles, etc.
"""
r1 = mathutils.Vector([plane.xdir.x, plane.xdir.y, plane.xdir.z])
r1.rotate(startQuat)
r2 = mathutils.Vector([plane.xdir.x, plane.xdir.y, plane.xdir.z])
r2.rotate(endQuat)
c = mathutils.Vector([plane.origin.x, plane.origin.y, plane.origin.z]) * scale
spt = c + r1 * radius
ept = c + r2 * radius
angle = endAngle - startAngle
t1 = normal.cross(r1)
"""
Initialize arc data and calculate subdivisions
"""
arc = bcurve.splines.new("NURBS")
arc.use_cyclic_u = False
Ndiv = max(int(math.floor(angle / 0.3)), 2)
step = angle / float(Ndiv)
stepQuat = mathutils.Quaternion(normal, step)
tan = math.tan(step / 2) * radius
arc.points.add(Ndiv + 1)
"""
Set start and end points
"""
arc.points[0].co = (spt.x, spt.y, spt.z, 1)
arc.points[Ndiv + 1].co = (ept.x, ept.y, ept.z, 1)
"""
Set intermediate points
"""
for i in range(Ndiv):
t1 = normal.cross(r1)
pt = c + r1 * radius + t1 * tan
arc.points[i + 1].co = (pt.x, pt.y, pt.z, 1)
r1.rotate(stepQuat)
"""
Set curve settings
"""
arc.use_endpoint_u = True
arc.order_u = 3
return arc
CONVERT[Arc] = import_arc
def import_null(speckle_object, bcurve, scale):
"""
Handle unsupported types
"""
print("Failed to convert type", speckle_object["type"])
return None
def import_polycurve(scurve, bcurve, scale):
"""
Convert Polycurve object
"""
segments = scurve.segments
curves = []
for seg in segments:
speckle_type = type(seg)
if speckle_type in CONVERT.keys():
# segcurve = SCHEMAS[speckle_type].parse_obj(seg)
curves.append(CONVERT[speckle_type](seg, bcurve, scale))
else:
print("Unsupported curve type: {}".format(speckle_type))
return curves
CONVERT[Polycurve] = import_polycurve
def import_curve(speckle_curve, scale, name=None):
"""
Convert Curve object
"""
if not name:
name = speckle_curve.geometryHash or speckle_curve.id or "SpeckleCurve"
if name in bpy.data.curves.keys():
curve_data = bpy.data.curves[name]
else:
curve_data = bpy.data.curves.new(name, type="CURVE")
curve_data.dimensions = "3D"
curve_data.resolution_u = 12
if type(speckle_curve) not in CONVERT.keys():
print("Unsupported curve type: {}".format(speckle_curve.type))
return None
CONVERT[type(speckle_curve)](speckle_curve, curve_data, scale)
return curve_data
-167
View File
@@ -1,167 +0,0 @@
import bpy, bmesh, struct
import base64
from bpy_speckle.functions import _report
def add_vertices(smesh, bmesh, scale=1.0):
sverts = smesh.vertices
if sverts and len(sverts) > 0:
for i in range(0, len(sverts), 3):
bmesh.verts.new(
(
float(sverts[i]) * scale,
float(sverts[i + 1]) * scale,
float(sverts[i + 2]) * scale,
)
)
bmesh.verts.ensure_lookup_table()
def add_faces(smesh, bmesh, smooth=False):
sfaces = smesh.faces
if sfaces and len(sfaces) > 0:
i = 0
# TODO: why does `faces.new()` seem to fail so often?
while i < len(sfaces):
if sfaces[i] == 0:
i += 1
try:
f = bmesh.faces.new(
(
bmesh.verts[int(sfaces[i])],
bmesh.verts[int(sfaces[i + 1])],
bmesh.verts[int(sfaces[i + 2])],
)
)
f.smooth = smooth
except Exception as e:
_report(f"Failed to create face for mesh {smesh.id} \n{e}")
i += 3
elif sfaces[i] == 1:
i += 1
try:
f = bmesh.faces.new(
(
bmesh.verts[int(sfaces[i])],
bmesh.verts[int(sfaces[i + 1])],
bmesh.verts[int(sfaces[i + 2])],
bmesh.verts[int(sfaces[i + 3])],
)
)
f.smooth = smooth
except Exception as e:
_report(f"Failed to create face for mesh {smesh.id} \n{e}")
i += 4
else:
print("Invalid face length.\n" + str(sfaces[i]))
break
bmesh.faces.ensure_lookup_table()
bmesh.verts.index_update()
def add_colors(smesh, bmesh):
scolors = smesh.colors
if scolors:
colors = []
if len(scolors) > 0:
for i in range(len(scolors)):
col = int(scolors[i])
(a, r, g, b) = [
int(x) for x in struct.unpack("!BBBB", struct.pack("!i", col))
]
colors.append(
(
float(r) / 255.0,
float(g) / 255.0,
float(b) / 255.0,
float(a) / 255.0,
)
)
# Make vertex colors
if len(scolors) == len(bmesh.verts):
color_layer = bmesh.loops.layers.color.new("Col")
for face in bmesh.faces:
for loop in face.loops:
loop[color_layer] = colors[loop.vert.index]
def add_uv_coords(smesh, bmesh):
if not hasattr(smesh, "properties"):
return
sprops = smesh.properties
if sprops:
texKey = ""
if "texture_coordinates" in sprops.keys():
texKey = "texture_coordinates"
elif "TextureCoordinates" in sprops.keys():
texKey = "TextureCoordinates"
if texKey != "":
try:
decoded = base64.b64decode(sprops[texKey]).decode("utf-8")
s_uvs = decoded.split()
uv = []
if int(len(s_uvs) / 2) == len(bmesh.verts):
for i in range(0, len(s_uvs), 2):
uv.append((float(s_uvs[i]), float(s_uvs[i + 1])))
else:
print(len(s_uvs) * 2)
print(len(bmesh.verts))
print("Failed to match UV coordinates to vert data.")
# Make UVs
uv_layer = bmesh.loops.layers.uv.verify()
for f in bmesh.faces:
for l in f.loops:
luv = l[uv_layer]
luv.uv = uv[l.vert.index]
except:
print("Failed to decode texture coordinates.")
raise
del smesh.properties[texKey]
def to_bmesh(speckle_mesh, blender_mesh, name="SpeckleMesh", scale=1.0):
bm = bmesh.new()
add_vertices(speckle_mesh, bm, scale)
add_faces(speckle_mesh, bm)
add_colors(speckle_mesh, bm)
add_uv_coords(speckle_mesh, bm)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bm.to_mesh(blender_mesh)
bm.free()
return blender_mesh
def import_mesh(speckle_mesh, scale=1.0, name=None):
"""
Convert Mesh object
"""
if not name:
name = speckle_mesh.geometryHash or speckle_mesh.id
if name in bpy.data.meshes.keys():
mesh = bpy.data.meshes[name]
else:
mesh = bpy.data.meshes.new(name=name)
to_bmesh(speckle_mesh, mesh, name, scale)
return mesh
@@ -1,6 +0,0 @@
import bpy, bmesh, struct
import base64
def import_plane(speckle_plane, scale=1.0, name=None):
return None
@@ -1,3 +0,0 @@
from .mesh import export_mesh
from .curve import export_curve, export_ngons_as_polylines
from .empty import export_empty
-224
View File
@@ -1,224 +0,0 @@
import bpy, bmesh, struct
from specklepy.objects.geometry import Curve, Interval, Box, Polyline
from bpy_speckle.convert.to_speckle.mesh import export_mesh
UNITS = "m"
def bezier_to_speckle(matrix, spline, scale, name=None):
degree = 3
closed = spline.use_cyclic_u
points = []
for i, bp in enumerate(spline.bezier_points):
if i > 0:
points.append(tuple(matrix @ bp.handle_left * scale))
points.append(tuple(matrix @ bp.co * scale))
if i < len(spline.bezier_points) - 1:
points.append(tuple(matrix @ bp.handle_right * scale))
if closed:
points.append(tuple(matrix @ spline.bezier_points[-1].handle_right * scale))
points.append(tuple(matrix @ spline.bezier_points[0].handle_left * scale))
points.append(tuple(matrix @ spline.bezier_points[0].co * scale))
num_points = len(points)
knot_count = num_points + degree - 1
knots = [0] * knot_count
for i in range(1, len(knots)):
knots[i] = i // 3
length = spline.calc_length()
domain = Interval(start=0, end=length, totalChildrenCount=0)
return Curve(
name=name,
degree=degree,
closed=spline.use_cyclic_u,
periodic=spline.use_cyclic_u,
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=UNITS,
bbox=Box(area=0.0, volume=0.0),
)
def nurbs_to_speckle(matrix, spline, scale, name=None):
knots = makeknots(spline)
# print("knots: {}".format(knots))
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
degree = spline.order_u - 1
length = spline.calc_length()
domain = Interval(start=0, end=length, totalChildrenCount=0)
return Curve(
name=name,
degree=degree,
closed=spline.use_cyclic_u,
periodic=spline.use_cyclic_u,
points=list(sum(points, ())), # magic (flatten list of tuples)
weights=[pt.weight for pt in spline.points],
knots=knots,
rational=False,
area=0,
volume=0,
length=length,
domain=domain,
units=UNITS,
bbox=Box(area=0.0, volume=0.0),
)
def poly_to_speckle(matrix, spline, scale, name=None):
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
length = spline.calc_length()
domain = Interval(start=0, end=length, totalChildrenCount=0)
return Polyline(
name=name,
closed=spline.use_cyclic_u,
value=list(sum(points, ())), # magic (flatten list of tuples)
length=length,
domain=domain,
bbox=Box(area=0.0, volume=0.0),
area=0,
units=UNITS,
)
def export_curve(blender_object, data, scale=1.0):
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
if blender_object.type != "CURVE":
return None
blender_object = blender_object.evaluated_get(bpy.context.view_layer.depsgraph)
mat = blender_object.matrix_world
curves = []
if data.bevel_mode == "OBJECT" and data.bevel_object != None:
mesh = export_mesh(blender_object, blender_object.to_mesh(), scale)
curves.extend(mesh)
for spline in data.splines:
if spline.type == "BEZIER":
curves.append(bezier_to_speckle(mat, spline, scale, blender_object.name))
elif spline.type == "NURBS":
curves.append(nurbs_to_speckle(mat, spline, scale, blender_object.name))
elif spline.type == "POLY":
curves.append(poly_to_speckle(mat, spline, scale, blender_object.name))
return curves
def export_ngons_as_polylines(blender_object, data, scale=1.0):
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
if blender_object.type != "MESH":
return None
mat = blender_object.matrix_world
verts = data.vertices
polylines = []
for i, poly in enumerate(data.polygons):
value = []
for v in poly.vertices:
value.extend(mat @ verts[v].co * scale)
domain = Interval(start=0, end=1)
poly = Polyline(
name="{}_{}".format(blender_object.name, i),
closed=True,
value=value, # magic (flatten list of tuples)
length=0,
domain=domain,
bbox=Box(area=0.0, volume=0.0),
area=0,
units=UNITS,
)
polylines.append(poly)
return polylines
"""
Python implementation of Blender's NURBS curve generation
from: https://blender.stackexchange.com/a/34276
"""
def macro_knotsu(nu):
return nu.order_u + nu.point_count_u + (nu.order_u - 1 if nu.use_cyclic_u else 0)
def macro_segmentsu(nu):
return nu.point_count_u if nu.use_cyclic_u else nu.point_count_u - 1
def makeknots(nu):
knots = [0.0] * (4 + macro_knotsu(nu))
flag = nu.use_endpoint_u + (nu.use_bezier_u << 1)
if nu.use_cyclic_u:
calcknots(knots, nu.point_count_u, nu.order_u, 0)
makecyclicknots(knots, nu.point_count_u, nu.order_u)
else:
calcknots(knots, nu.point_count_u, nu.order_u, flag)
return knots
def calcknots(knots, pnts, order, flag):
pnts_order = pnts + order
if flag == 1:
k = 0.0
for a in range(1, pnts_order + 1):
knots[a - 1] = k
if a >= order and a <= pnts:
k += 1.0
elif flag == 2:
if order == 4:
k = 0.34
for a in range(pnts_order):
knots[a] = math.floor(k)
k += 1.0 / 3.0
elif order == 3:
k = 0.6
for a in range(pnts_order):
if a >= order and a <= pnts:
k += 0.5
knots[a] = math.floor(k)
else:
for a in range(pnts_order):
knots[a] = a
def makecyclicknots(knots, pnts, order):
order2 = order - 1
if order > 2:
b = pnts + order2
for a in range(1, order2):
if knots[b] != knots[b - a]:
break
if a == order2:
knots[pnts + order - 2] += 1.0
b = order
c = pnts + order + order2
for a in range(pnts + order2, c):
knots[a] = knots[a - 1] + (knots[b] - knots[b - 1])
b -= 1
@@ -1,5 +0,0 @@
import bpy, bmesh, struct
def export_default(blender_object, scale=1.0):
return None
-5
View File
@@ -1,5 +0,0 @@
import bpy, bmesh, struct
def export_empty(blender_object, data, scale=1.0):
return None
-45
View File
@@ -1,45 +0,0 @@
import bpy, bmesh, struct
import base64, hashlib
from time import strftime, gmtime
from specklepy.objects.geometry import Mesh, Interval, Box
def export_mesh(blender_object, data, scale=1.0):
if data.loop_triangles is None or len(data.loop_triangles) < 1:
data.calc_loop_triangles()
mat = blender_object.matrix_world
verts = [tuple(mat @ x.co * scale) for x in data.vertices]
# TODO: add n-gon support, using tessfaces for now
# faces = [x.vertices for x in data.loop_triangles]
faces = [p.vertices for p in data.polygons]
unit_system = bpy.context.scene.unit_settings.system
sm = Mesh(
name=blender_object.name,
vertices=list(sum(verts, ())),
faces=[],
colors=[],
textureCoordinates=[],
units="m" if unit_system == "METRIC" else "ft",
bbox=Box(area=0.0, volume=0.0),
)
if data.uv_layers.active:
for vt in data.uv_layers.active.data:
sm.textureCoordinates.extend([vt.uv.x, vt.uv.y])
for f in faces:
if len(f) == 3:
sm.faces.append(0)
elif len(f) == 4:
sm.faces.append(1)
else:
continue
sm.faces.extend(f)
return [sm]
-19
View File
@@ -1,19 +0,0 @@
from typing import Tuple
def to_rgba(argb_int: int) -> Tuple[float]:
"""Converts the int representation of a colour into a percent RGBA tuple"""
alpha = ((argb_int >> 24) & 255) / 255
red = ((argb_int >> 16) & 255) / 255
green = ((argb_int >> 8) & 255) / 255
blue = (argb_int & 255) / 255
return (red, green, blue, alpha)
def to_argb_int(diffuse_colour) -> int:
"""Converts an RGBA array to an ARGB integer"""
diffuse_colour = diffuse_colour[-1:] + diffuse_colour[0:3]
diffuse_colour = [int(val * 255) for val in diffuse_colour]
return int.from_bytes(diffuse_colour, byteorder="big", signed=True)
-104
View File
@@ -1,104 +0,0 @@
from specklepy.api.client import SpeckleClient
from bpy_speckle.clients import speckle_clients
"""
Speckle functions
"""
unit_scale = {
"meters": 1.0,
"centimeters": 0.01,
"millimeters": 0.001,
"inches": 0.0254,
"feet": 0.3048,
"kilometers": 1000.0,
"mm": 0.001,
"cm": 0.01,
"m": 1.0,
"km": 1000.0,
"in": 0.0254,
"ft": 0.3048,
"yd": 0.9144,
"mi": 1609.340,
}
"""
Utility functions
"""
def _report(msg):
"""
Function for printing messages to the console
"""
print("SpeckleBlender: {}".format(msg))
def get_scale_length(units):
if units.lower() in unit_scale.keys():
return unit_scale[units]
_report("Units <{}> are not supported.".format(units))
return 1.0
"""
Client, user, and stream functions
"""
def _check_speckle_client_user_stream(scene):
"""
Verify that there is a valid user and stream
"""
speckle = scene.speckle
user = (
speckle.users[int(speckle.active_user)]
if len(speckle.users) > int(speckle.active_user)
else None
)
if user is None:
print("No users loaded.")
stream = (
user.streams[user.active_stream]
if len(user.streams) > user.active_stream
else None
)
if stream is None:
print("Account contains no streams.")
return (user, stream)
def _create_stream(user, stream_name, units="Millimeters"):
"""
Create a new stream
"""
# TODO: double-check, but this should not be accessible through the UI if
# there aren't any active users anyway
# user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
client = speckle_clients[int(bpy.context.scene.speckle.active_user)]
return client.stream.create(
name=stream_name, description="This is a Blender stream.", is_public=True
)
# TODO: Update stream with properties such as units, etc.
def _delete_stream(client, user, stream):
"""
Delete the active stream
TODO: probably doesn't need to be a separate function and can be
folded into the operator
"""
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
client = speckle_clients[int(context.scene.speckle.active_user)]
if stream:
res = client.streams.delete(stream.id)
_report(res["message"])
-104
View File
@@ -1,104 +0,0 @@
import os, sys, bpy
import ctypes, sys
import os, sys
def modules_path():
# set up addons/modules under the user
# script path. Here we'll install the
# dependencies
modulespath = os.path.normpath(
os.path.join(bpy.utils.script_path_user(), "addons", "modules")
)
if not os.path.exists(modulespath):
os.makedirs(modulespath)
# set user modules path at beginning of paths for earlier hit
if sys.path[1] != modulespath:
sys.path.insert(1, modulespath)
return modulespath
def install_dependencies():
import sys
import os
try:
try:
import pip
except:
print("Installing pip... "),
from subprocess import run as sprun
res = sprun([bpy.app.binary_path_python, "-m", "ensurepip"])
if res.returncode == 0:
import pip
else:
raise Exception("Failed to install pip.")
modulespath = modules_path()
if not os.path.exists(modulespath):
os.makedirs(modulespath)
print("Installing speckle to {}... ".format(modulespath)),
from subprocess import run as sprun
res = sprun(
[
bpy.app.binary_path_python,
"-m",
"pip",
"install",
"-q",
"-t",
"{}".format(modulespath),
"--no-deps",
"pydantic",
]
)
res = sprun(
[
bpy.app.binary_path_python,
"-m",
"pip",
"install",
"-q",
"-t",
"{}".format(modulespath),
"specklepy",
]
)
except:
raise Exception(
"Failed to install dependencies. Please make sure you have pip installed."
)
if __name__ == "__main__":
try:
import specklepy
except:
print("Failed to load speckle.")
from sys import platform
if platform == "win32":
if ctypes.windll.shell32.IsUserAnAdmin():
install_dependencies()
import specklepy
else:
ctypes.windll.shell32.ShellExecuteW(
None, "runas", sys.executable, __file__, None, 1
)
else:
print(
"Platform {} cannot automatically install dependencies.".format(
platform
)
)
raise
+2 -65
View File
@@ -1,65 +1,2 @@
from .users import LoadUsers, LoadUserStreams
from .object import (
UpdateObject,
ResetObject,
DeleteObject,
UploadObject,
UploadNgonsAsPolylines,
SelectIfSameCustomProperty,
SelectIfHasCustomProperty,
)
from .streams import (
ReceiveStreamObjects,
SendStreamObjects,
ViewStreamDataApi,
DeleteStream,
SelectOrphanObjects,
)
from .streams import (
UpdateGlobal,
AddStreamFromURL,
CreateStream,
CopyStreamId,
CopyCommitId,
CopyBranchName,
)
from .commit import DeleteCommit
from .misc import OpenSpeckleGuide, OpenSpeckleTutorials, OpenSpeckleForum
operator_classes = [
LoadUsers,
ReceiveStreamObjects,
SendStreamObjects,
LoadUserStreams,
CopyStreamId,
CopyCommitId,
CopyBranchName,
]
operator_classes.extend([DeleteCommit])
operator_classes.extend(
[
UpdateObject,
ResetObject,
DeleteObject,
UploadObject,
UploadNgonsAsPolylines,
SelectIfSameCustomProperty,
SelectIfHasCustomProperty,
]
)
operator_classes.extend(
[
ViewStreamDataApi,
DeleteStream,
SelectOrphanObjects,
UpdateGlobal,
AddStreamFromURL,
CreateStream,
OpenSpeckleGuide,
OpenSpeckleTutorials,
OpenSpeckleForum,
]
)
from .load import SPECKLE_OT_load
from .publish import SPECKLE_OT_publish
-85
View File
@@ -1,85 +0,0 @@
"""
Commit operators
"""
import bpy, os
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
)
from bpy_speckle.functions import (
_check_speckle_client_user_stream,
_create_stream,
get_scale_length,
_report,
)
from bpy_speckle.convert import from_speckle_object
from bpy_speckle.clients import speckle_clients
class DeleteCommit(bpy.types.Operator):
"""
Delete stream
"""
bl_idname = "speckle.delete_commit"
bl_label = "Delete commit"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Delete active commit permanently"
are_you_sure: BoolProperty(
name="Confirm", default=False,
)
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, "are_you_sure")
def invoke(self, context, event):
wm = context.window_manager
if len(context.scene.speckle.users) > 0:
return wm.invoke_props_dialog(self)
return {"CANCELLED"}
def execute(self, context):
if not self.are_you_sure:
return {"CANCELLED"}
self.are_you_sure = False
speckle = context.scene.speckle
check = _check_speckle_client_user_stream(context.scene)
if check is None:
return {"CANCELLED"}
user, stream = check
client = speckle_clients[int(context.scene.speckle.active_user)]
stream = user.streams[user.active_stream]
if len(stream.branches) < 1:
return {"CANCELLED"}
else:
branch = stream.branches[int(stream.branch)]
if len(branch.commits) < 1:
return {"CANCELLED"}
else:
commit = branch.commits[int(branch.commit)]
deleted = client.commit.delete(stream_id=stream.id, commit_id=commit.id)
return {"FINISHED"}
bpy.ops.speckle.load_user_streams()
context.view_layer.update()
if context.area:
context.area.tag_redraw()
return {"FINISHED"}
+17
View File
@@ -0,0 +1,17 @@
import bpy
# Load Operator
class SPECKLE_OT_load(bpy.types.Operator):
bl_idname = "speckle.load"
bl_label = "Load from Speckle"
bl_description = "Load objects from Speckle"
def invoke(self, context, event):
context.scene.speckle_mouse_position = (event.mouse_x, event.mouse_y)
return self.execute(context)
def execute(self, context):
context.scene.speckle_ui_mode = "LOAD"
self.report({'INFO'}, f"Load button clicked at {context.scene.speckle_mouse_position[0], context.scene.speckle_mouse_position[1]}")
bpy.ops.speckle.project_selection_dialog("INVOKE_DEFAULT")
return {'FINISHED'}
-35
View File
@@ -1,35 +0,0 @@
import bpy
import webbrowser
class OpenSpeckleGuide(bpy.types.Operator):
bl_idname = "speckle.open_speckle_guide"
bl_label = "Speckle Guide"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Browse the documentation on the Speckle Guide"
def execute(self, context):
webbrowser.open("https://speckle.guide/user/blender.html")
return {"FINISHED"}
class OpenSpeckleTutorials(bpy.types.Operator):
bl_idname = "speckle.open_speckle_tutorials"
bl_label = "Tutorials Portal"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Visit our tutorials portal for learning resources"
def execute(self, context):
webbrowser.open("https://speckle.systems/tutorials/")
return {"FINISHED"}
class OpenSpeckleForum(bpy.types.Operator):
bl_idname = "speckle.open_speckle_forum"
bl_label = "Community Forum"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Ask questions and join the discussion on our community forum"
def execute(self, context):
webbrowser.open("https://speckle.community/")
return {"FINISHED"}
@@ -0,0 +1,58 @@
import bpy
import webbrowser
class SPECKLE_OT_model_card_settings(bpy.types.Operator):
"""
Operator for managing model card settings.
This operator provides functionality to view and modify settings
for a specific model card.
"""
bl_idname = "speckle.model_card_settings"
bl_label = "Model Card Settings"
bl_description = "Settings for the model card"
model_name: bpy.props.StringProperty()
def execute(self, context):
self.report({'INFO'}, f"Settings for {self.model_name}")
return {'FINISHED'}
def draw(self, context):
layout = self.layout
# Add a button for viewing 3d model in the browser
layout.operator("speckle.view_in_browser", text="View in Browser")
# Add a button for viewing model versions in the browser
layout.operator("speckle.view_model_versions", text="View Model Versions")
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
# Operator for viewing the model in the browser
class SPECKLE_OT_view_in_browser(bpy.types.Operator):
"""
Operator for viewing the model in the browser.
"""
bl_idname = "speckle.view_in_browser"
bl_label = "View in Browser"
bl_description = "View the model in the browser"
def execute(self, context):
# TODO: Update this to model URL
webbrowser.open(f"https://speckle.guide")
self.report({'INFO'}, f"Viewing in the browser")
return {'FINISHED'}
# Operator for viewing the model versions in the browser
class SPECKLE_OT_view_model_versions(bpy.types.Operator):
"""
Operator for viewing the model versions in the browser.
"""
bl_idname = "speckle.view_model_versions"
bl_label = "View Model Versions"
bl_description = "View the model versions in the browser"
def execute(self, context):
# TODO: Update this to model versions URL
webbrowser.open(f"https://speckle.guide")
self.report({'INFO'}, f"Viewing model's versions in the browser")
return {'FINISHED'}
-384
View File
@@ -1,384 +0,0 @@
"""
Object operators
"""
import bpy, bmesh, os
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
)
from specklepy.api.client import SpeckleClient
from bpy_speckle.convert import to_speckle_object
from bpy_speckle.convert.to_speckle import export_ngons_as_polylines
from bpy_speckle.functions import get_scale_length, _report
from bpy_speckle.clients import speckle_clients
class UpdateObject(bpy.types.Operator):
"""
Update local (receive) or remote (send) object depending on
the update direction. If sending, updates the object on the
server in-place.
"""
bl_idname = "speckle.update_object"
bl_label = "Update Object"
bl_options = {"REGISTER", "UNDO"}
client = None
def execute(self, context):
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
client = speckle_clients[int(context.scene.speckle.active_user)]
stream = user.streams[user.active_stream]
active = context.active_object
_report(active)
if active is not None and active.speckle.enabled:
if active.speckle.send_or_receive == "send" and active.speckle.stream_id:
sstream = client.streams.get(active.speckle.stream_id)
# res = client.StreamGetAsync(active.speckle.stream_id)['resource']
# res = client.streams.get(active.speckle.stream_id)
if sstream is None:
_report("Getting stream failed.")
return {"CANCELLED"}
stream_units = "Meters"
if sstream.baseProperties:
stream_units = sstream.baseProperties.units
scale = context.scene.unit_settings.scale_length / get_scale_length(
stream_units
)
sm = to_speckle_object(active, scale)
_report("Updating object {}".format(sm["_id"]))
client.objects.update(active.speckle.object_id, sm)
return {"FINISHED"}
return {"CANCELLED"}
return {"CANCELLED"}
class ResetObject(bpy.types.Operator):
"""
Reset Speckle object settings
"""
bl_idname = "speckle.reset_object"
bl_label = "Reset Object"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.object.speckle.send_or_receive = "send"
context.object.speckle.stream_id = ""
context.object.speckle.object_id = ""
context.object.speckle.enabled = False
context.view_layer.update()
return {"FINISHED"}
class DeleteObject(bpy.types.Operator):
"""
Delete object from the server and update relevant stream
"""
bl_idname = "speckle.delete_object"
bl_label = "Delete Object"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
client = speckle_clients[int(context.scene.speckle.active_user)]
active = context.object
if active.speckle.enabled:
res = client.StreamGetAsync(active.speckle.stream_id)
existing = [
x
for x in res["resource"]["objects"]
if x["_id"] == active.speckle.object_id
]
if existing is None:
return {"CANCELLED"}
# print("Existing: %s" % SpeckleResource.to_json_pretty(existing))
new_objects = [
x
for x in res["resource"]["objects"]
if x["_id"] != active.speckle.object_id
]
# print (SpeckleResource.to_json_pretty(new_objects))
res = client.GetLayers(active.speckle.stream_id)
new_layers = res["resource"]["layers"]
new_layers[-1]["objectCount"] = new_layers[-1]["objectCount"] - 1
new_layers[-1]["topology"] = "0-%s" % new_layers[-1]["objectCount"]
res = client.StreamUpdateAsync(
{"objects": new_objects, "layers": new_layers}, active.speckle.stream_id
)
res = client.ObjectDeleteAsync(active.speckle.object_id)
active.speckle.send_or_receive = "send"
active.speckle.stream_id = ""
active.speckle.object_id = ""
active.speckle.enabled = False
context.view_layer.update()
return {"FINISHED"}
class UploadNgonsAsPolylines(bpy.types.Operator):
"""
Upload mesh ngon faces as polyline outlines
TODO: move to another category of specialized operators and fix to work with API 2.0
"""
bl_idname = "speckle.upload_ngons_as_polylines"
bl_label = "Upload Ngons As Polylines"
bl_options = {"REGISTER", "UNDO"}
clear_stream: BoolProperty(
name="Clear stream",
default=False,
)
def execute(self, context):
active = context.active_object
if active is not None and active.type == "MESH":
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
client = speckle_clients[int(context.scene.speckle.active_user)]
stream = user.streams[user.active_stream]
scale = context.scene.unit_settings.scale_length / get_scale_length(
stream.units
)
sp = export_ngons_as_polylines(active, scale)
if sp is None:
return {"CANCELLED"}
placeholders = []
for polyline in sp:
res = client.objects.create([polyline])
print(res)
if res is None:
_report(client.me)
continue
placeholders.extend(res)
# polyline['_id'] = res['_id']
# placeholders.append({'type':'Placeholder', '_id':res['_id']})
if not placeholders:
return {"CANCELLED"}
# Get list of existing objects in stream and append new object to list
_report("Fetching stream...")
sstream = client.streams.get(stream.id)
if self.clear_stream:
_report("Clearing stream...")
sstream.objects = placeholders
N = 0
else:
sstream.objects.extend(placeholders)
N = sstream.layers[-1].objectCount
if self.clear_stream:
N = 0
sstream.layers[-1].objectCount = N + len(placeholders)
sstream.layers[-1].topology = "0-%s" % (N + len(placeholders))
res = client.streams.update(sstream.id, sstream)
# Update view layer
context.view_layer.update()
_report("Done.")
return {"FINISHED"}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.prop(self, "clear_stream")
class UploadObject(bpy.types.Operator):
"""
DEPRECATED
Upload an individual object
"""
bl_idname = "speckle.upload_object"
bl_label = "Upload Object"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
active = context.active_object
if active is not None:
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
client = speckle_clients[int(context.scene.speckle.active_user)]
stream = user.streams[user.active_stream]
scale = context.scene.unit_settings.scale_length / get_scale_length(
stream.units
)
sm = to_speckle_object(active, scale)
placeholders = client.objects.create([sm])
if placeholders is None:
return {"CANCELLED"}
sstream = client.streams.get(stream.id)
sstream.objects.extend(placeholders)
N = sstream.layers[-1].objectCount
sstream.layers[-1].objectCount = N + 1
sstream.layers[-1].topology = "0-%s" % (N + 1)
_report("Updating stream %s" % stream.id)
res = client.streams.update(stream["id"], sstream)
_report(res)
active.speckle.enabled = True
active.speckle.object_id = sm.id
active.speckle.stream_id = stream.id
active.speckle.send_or_receive = "send"
context.view_layer.update()
_report("Done.")
return {"FINISHED"}
def get_custom_speckle_props(self, context):
ignore = ["speckle", "cycles", "cycles_visibility"]
active = context.active_object
if not active:
return []
return [(x, "{}".format(x), "") for x in active.keys()]
class SelectIfSameCustomProperty(bpy.types.Operator):
"""
Select scene objects if they have the same custom property
value as the active object
"""
bl_idname = "speckle.select_if_same_custom_props"
bl_label = "Select Identical Custom Props"
bl_options = {"REGISTER", "UNDO"}
custom_prop: EnumProperty(
name="Custom properties",
description="Available streams associated with user.",
items=get_custom_speckle_props,
)
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, "custom_prop")
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def execute(self, context):
active = context.active_object
if not active:
return {"CANCELLED"}
if self.custom_prop not in active.keys():
return {"CANCELLED"}
value = active[self.custom_prop]
_report(
"Looking for '{}' property with a value of '{}'.".format(
self.custom_prop, value
)
)
for obj in bpy.data.objects:
if self.custom_prop in obj.keys() and obj[self.custom_prop] == value:
obj.select_set(True)
else:
obj.select_set(False)
return {"FINISHED"}
class SelectIfHasCustomProperty(bpy.types.Operator):
"""
Select scene objects if they have the same custom property
as the active object, regardless of the value
"""
bl_idname = "speckle.select_if_has_custom_props"
bl_label = "Select Same Custom Prop"
bl_options = {"REGISTER", "UNDO"}
custom_prop: EnumProperty(
name="Custom properties",
description="Custom properties yo",
items=get_custom_speckle_props,
)
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, "custom_prop")
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def execute(self, context):
active = context.active_object
if not active:
return {"CANCELLED"}
if self.custom_prop not in active.keys():
return {"CANCELLED"}
value = active[self.custom_prop]
_report("Looking for '{}' property.".format(self.custom_prop))
for obj in bpy.data.objects:
if self.custom_prop in obj.keys():
obj.select_set(True)
else:
obj.select_set(False)
return {"FINISHED"}
+18
View File
@@ -0,0 +1,18 @@
import bpy
# Publish Operator
class SPECKLE_OT_publish(bpy.types.Operator):
bl_idname = "speckle.publish"
bl_label = "Publish to Speckle"
bl_description = "Publish selected objects to Speckle"
def invoke(self, context, event):
context.scene.speckle_mouse_position = (event.mouse_x, event.mouse_y)
return self.execute(context)
def execute(self, context):
context.scene.speckle_ui_mode = "PUBLISH"
self.report({'INFO'}, f"Publish button clicked at {context.scene.speckle_mouse_position[0], context.scene.speckle_mouse_position[1]}")
bpy.ops.speckle.project_selection_dialog("INVOKE_DEFAULT")
return {'FINISHED'}
-808
View File
@@ -1,808 +0,0 @@
"""
Stream operators
"""
from itertools import chain
from typing import Dict
import bpy, bmesh, os
from specklepy.api.models import Commit
import webbrowser
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
)
from bpy_speckle.functions import (
_check_speckle_client_user_stream,
_create_stream,
get_scale_length,
_report,
)
from bpy_speckle.convert import to_speckle_object, get_speckle_subobjects
from bpy_speckle.convert.to_speckle import export_ngons_as_polylines
from bpy_speckle.convert import from_speckle_object
from bpy_speckle.clients import speckle_clients
from bpy_speckle.operators.users import add_user_stream
from specklepy.api import operations
from specklepy.api.credentials import StreamWrapper
from specklepy.api.resources.stream import Stream
from specklepy.transports.server import ServerTransport
from specklepy.objects import Base
from specklepy.objects.geometry import *
from specklepy.logging.exceptions import SpeckleException
def get_objects_collections(base) -> Dict:
"""Create collections based on the dynamic members on a root commit object"""
collections = {}
for name in base.get_dynamic_member_names():
value = base[name]
if isinstance(value, list):
col = create_collection(name)
collections[name] = get_objects_nested_lists(value, col)
if isinstance(value, Base):
col = create_collection(name)
collections[name] = get_objects_collections_recursive(value, col)
return collections
def get_objects_nested_lists(items, parent_col=None) -> List:
"""For handling the weird nested lists that come from Grasshopper"""
objects = []
if isinstance(items[0], list):
items = list(chain.from_iterable(items))
objects.extend(get_objects_nested_lists(items, parent_col))
else:
objects = [
get_objects_collections_recursive(item, parent_col)
for item in items
if isinstance(item, Base)
]
return objects
def get_objects_collections_recursive(base, parent_col=None) -> List:
"""Recursively create collections based on the dynamic members on nested `Base` objects within the root commit object"""
# if it's a convertable (registered) class and not just a plain `Base`, return the object itself
object_type = Base.get_registered_type(base.speckle_type)
if (
(object_type and object_type != Base)
or hasattr(base, "displayMesh")
or hasattr(base, "displayValue")
):
return [base]
# if it's an unknown type, try to drill further down to find convertable objects
objects = []
for name in base.get_dynamic_member_names():
value = base[name]
if isinstance(value, list):
for item in value:
if isinstance(item, Base):
objects.append(item)
if isinstance(value, Base):
col = parent_col.children.get(name)
if not parent_col.children.get(name):
col = create_collection(name)
parent_col.children.link(col)
objects.append({name: get_objects_collections_recursive(value, col)})
return objects
def bases_to_native(context, collections, scale, stream_id, func=None):
for col_name, objects in collections.items():
col = bpy.data.collections[col_name]
existing = get_existing_collection_objs(col)
if isinstance(objects, dict):
bases_to_native(context, objects, scale, stream_id)
elif isinstance(objects, list):
for obj in objects:
if isinstance(obj, dict):
bases_to_native(context, obj, scale, stream_id, func)
elif isinstance(obj, list):
for item in obj:
if isinstance(item, dict):
bases_to_native(context, item, scale, stream_id, func)
elif isinstance(item, Base):
base_to_native(
context, item, scale, stream_id, col, existing, func
)
elif isinstance(obj, Base):
base_to_native(context, obj, scale, stream_id, col, existing, func)
else:
_report(
f"Something went wrong when receiving collection: {col_name}"
)
bpy.context.view_layer.update()
if context.area:
context.area.tag_redraw()
def base_to_native(context, base, scale, stream_id, col, existing, func=None):
new_objects = [from_speckle_object(base, scale)]
if hasattr(base, "properties") and base.properties is not None:
new_objects.extend(get_speckle_subobjects(base.properties, scale, base.id))
elif isinstance(base, dict) and "properties" in base.keys():
new_objects.extend(
get_speckle_subobjects(base["properties"], scale, base["id"])
)
"""
Set object Speckle settings
"""
for new_object in new_objects:
if new_object is None:
continue
"""
Run injected function
"""
if func:
new_object = func(context.scene, new_object)
if (
new_object is None
): # Make sure that the injected function returned an object
new_obj = new_object
_report("Script '{}' returned None.".format(func.__module__))
continue
new_object.speckle.stream_id = stream_id
new_object.speckle.send_or_receive = "receive"
if new_object.speckle.object_id in existing.keys():
name = existing[new_object.speckle.object_id].name
existing[new_object.speckle.object_id].name = name + "__deleted"
new_object.name = name
col.objects.unlink(existing[new_object.speckle.object_id])
if new_object.name not in col.objects:
col.objects.link(new_object)
def create_collection(name, clear_collection=True):
if name in bpy.data.collections:
col = bpy.data.collections[name]
if clear_collection:
for obj in col.objects:
col.objects.unlink(obj)
else:
col = bpy.data.collections.new(name)
return col
def create_child_collections(parent_col, children_names):
for name in children_names:
col = create_collection(name)
parent_col.children.link(col)
def get_existing_collection_objs(col):
return {
obj.speckle.object_id: obj for obj in col.objects if obj.speckle.object_id != ""
}
def get_collection_parents(collection, names):
for parent in bpy.data.collections:
if collection.name in parent.children.keys():
# TODO: this should be rethought to make it clear when this is an IFC delim so we know to replace it
# with `/` again on receive
names.append(parent.name.replace("/", "::").replace(".", "::"))
get_collection_parents(parent, names)
def get_collection_hierarchy(collection):
if not collection:
return []
names = [collection.name.replace("/", "::").replace(".", "::")]
get_collection_parents(collection, names)
return names
def create_nested_hierarchy(base, hierarchy, objects):
child = base
while hierarchy:
name = hierarchy.pop()
if not hasattr(child, name):
child[name] = Base()
child.add_detachable_attrs({name})
child = child[name]
# TODO: what do we call this attribute?
if not hasattr(child, "objects"):
child["objects"] = []
child["objects"].extend(objects)
return base
class ReceiveStreamObjects(bpy.types.Operator):
"""
Receive stream objects
"""
bl_idname = "speckle.receive_stream_objects"
bl_label = "Download Stream Objects"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Receive objects from active stream"
def execute(self, context):
bpy.context.view_layer.objects.active = None
check = _check_speckle_client_user_stream(context.scene)
if check is None:
return {"CANCELLED"}
user, bstream = check
client = speckle_clients[int(context.scene.speckle.active_user)]
stream = client.stream.get(id=bstream.id)
if stream.branches.totalCount < 1:
return {"CANCELLED"}
if not stream.branches:
return {"CANCELLED"}
branch = stream.branches.items[int(bstream.branch)]
bbranch = bstream.branches[int(bstream.branch)]
if branch.commits.totalCount < 1:
print("No commits found. Probably an empty stream.")
return {"CANCELLED"}
commit = branch.commits.items[int(bbranch.commit)]
transport = ServerTransport(client, stream.id)
stream_data = operations.receive(commit.referencedObject, transport)
"""
Create or get Collection for stream objects
"""
collections = get_objects_collections(stream_data)
if not collections:
return {"CANCELLED"}
name = "{} [ {} @ {} ]".format(stream.name, branch.name, commit.id)
col = create_collection(name)
col.speckle.stream_id = stream.id
col.speckle.name = stream.name
col.speckle.units = stream_data.units
if col.name not in bpy.context.scene.collection.children:
bpy.context.scene.collection.children.link(col)
for child_col in collections.keys():
try:
col.children.link(bpy.data.collections[child_col])
except:
pass
"""
Set conversion scale from stream units
"""
scale = (
get_scale_length(stream_data.units)
/ context.scene.unit_settings.scale_length
)
"""
Get script from text editor for injection
"""
func = None
if context.scene.speckle.receive_script in bpy.data.texts:
mod = bpy.data.texts[context.scene.speckle.receive_script].as_module()
if hasattr(mod, "execute"):
func = mod.execute
"""
Iterate through retrieved resources
"""
bases_to_native(context, collections, scale, stream.id, func)
return {"FINISHED"}
class SendStreamObjects(bpy.types.Operator):
"""
Send stream objects
"""
bl_idname = "speckle.send_stream_objects"
bl_label = "Send stream objects"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Send selected objects to active stream"
apply_modifiers: BoolProperty(name="Apply modifiers", default=True)
commit_message: StringProperty(
name="Message",
default="Pushed elements from Blender.",
)
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, "commit_message")
col.prop(self, "apply_modifiers")
def invoke(self, context, event):
wm = context.window_manager
if len(context.scene.speckle.users) > 0:
N = len(context.selected_objects)
if N == 1:
self.commit_message = "Pushed {} element from Blender.".format(N)
else:
self.commit_message = "Pushed {} elements from Blender.".format(N)
return wm.invoke_props_dialog(self)
return {"CANCELLED"}
def execute(self, context):
selected = context.selected_objects
if len(selected) < 1:
return {"CANCELLED"}
check = _check_speckle_client_user_stream(context.scene)
if check is None:
return {"CANCELLED"}
user, bstream = check
stream = user.streams[user.active_stream]
branch = stream.branches[int(stream.branch)]
client = speckle_clients[int(context.scene.speckle.active_user)]
scale = context.scene.unit_settings.scale_length / get_scale_length(
stream.units.lower()
)
"""
Get script from text editor for injection
"""
func = None
if context.scene.speckle.send_script in bpy.data.texts:
mod = bpy.data.texts[context.scene.speckle.send_script].as_module()
if hasattr(mod, "execute"):
func = mod.execute
export = {}
for obj in selected:
# if obj.type != 'MESH':
# continue
new_object = obj
"""
Run injected function
"""
if func:
new_object = func(context.scene, obj)
if (
new_object is None
): # Make sure that the injected function returned an object
new_obj = obj
_report("Script '{}' returned None.".format(func.__module__))
continue
_report("Converting {}".format(obj.name))
ngons = obj.get("speckle_ngons_as_polylines", False)
if ngons:
converted = export_ngons_as_polylines(obj, scale)
else:
converted = to_speckle_object(
obj,
scale,
bpy.context.evaluated_depsgraph_get()
if self.apply_modifiers
else None,
)
if not converted:
continue
collection_name = obj.users_collection[0].name
if not export.get(collection_name):
export[collection_name] = []
export[collection_name].extend(converted)
base = Base()
for name, objects in export.items():
collection = bpy.data.collections.get(name)
hierarchy = get_collection_hierarchy(collection)
create_nested_hierarchy(base, hierarchy, objects)
transport = ServerTransport(client, stream.id)
obj_id = operations.send(
base,
[transport],
)
client.commit.create(
stream.id,
obj_id,
branch.name,
message=self.commit_message,
source_application="blender",
)
bpy.ops.speckle.load_user_streams()
context.view_layer.update()
if context.area:
context.area.tag_redraw()
return {"FINISHED"}
class ViewStreamDataApi(bpy.types.Operator):
bl_idname = "speckle.view_stream_data_api"
bl_label = "Open Stream in Web"
bl_options = {"REGISTER", "UNDO"}
bl_description = "View the stream in the web browser"
def execute(self, context):
if len(context.scene.speckle.users) > 0:
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
if len(user.streams) > 0:
stream = user.streams[user.active_stream]
webbrowser.open("%s/streams/%s" % (user.server_url, stream.id), new=2)
return {"FINISHED"}
return {"CANCELLED"}
class AddStreamFromURL(bpy.types.Operator):
"""
Add / select a stream using its url
"""
bl_idname = "speckle.add_stream_from_url"
bl_label = "Add stream from URL"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Add an existing stream by providing its URL"
stream_url: StringProperty(
name="Stream URL", default="https://speckle.xyz/streams/3073b96e86"
)
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, "stream_url")
def invoke(self, context, event):
wm = context.window_manager
if len(context.scene.speckle.users) > 0:
return wm.invoke_props_dialog(self)
return {"CANCELLED"}
def execute(self, context):
speckle = context.scene.speckle
wrapper = StreamWrapper(self.stream_url)
user_index = next(
(i for i, u in enumerate(speckle.users) if wrapper.host in u.server_url),
None,
)
if user_index is None:
return {"CANCELLED"}
speckle.active_user = str(user_index)
user = speckle.users[user_index]
client = speckle_clients[user_index]
stream = client.stream.get(wrapper.stream_id)
if not isinstance(stream, Stream):
raise SpeckleException("Could not get the requested stream")
index, b_stream = next(
((i, s) for i, s in enumerate(user.streams) if s.id == stream.id),
(None, None),
)
if index is None:
add_user_stream(user, stream)
user.active_stream, b_stream = next(
(i, s) for i, s in enumerate(user.streams) if s.id == stream.id
)
else:
user.active_stream = index
if wrapper.branch_name:
b_index = b_stream.branches.find(wrapper.branch_name)
b_stream.branch = str(b_index if b_index != -1 else 0)
elif wrapper.commit_id:
commit = client.commit.get(wrapper.stream_id, wrapper.commit_id)
if isinstance(commit, Commit):
b_index = b_stream.branches.find(commit.branchName)
if b_index == -1:
b_index = 0
b_stream.branch = str(b_index)
c_index = b_stream.branches[b_index].commits.find(commit.id)
b_stream.branches[b_index].commit = str(c_index if c_index != -1 else 0)
# Update view layer
context.view_layer.update()
if context.area:
context.area.tag_redraw()
return {"FINISHED"}
class CreateStream(bpy.types.Operator):
"""
Create new stream
"""
bl_idname = "speckle.create_stream"
bl_label = "Create stream"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create new stream"
stream_name: StringProperty(name="Stream name", default="SpeckleStream")
stream_description: StringProperty(
name="Stream description", default="This is a Blender stream."
)
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, "stream_name")
col.prop(self, "stream_description")
def invoke(self, context, event):
wm = context.window_manager
if len(context.scene.speckle.users) > 0:
return wm.invoke_props_dialog(self)
return {"CANCELLED"}
def execute(self, context):
check = _check_speckle_client_user_stream(context.scene)
if check is None:
return {"CANCELLED"}
user, bstream = check
client = speckle_clients[int(context.scene.speckle.active_user)]
client.stream.create(
name=self.stream_name, description=self.stream_description, is_public=True
)
bpy.ops.speckle.load_user_streams()
user.active_stream = user.streams.find(self.stream_name)
# Update view layer
context.view_layer.update()
if context.area:
context.area.tag_redraw()
return {"FINISHED"}
class DeleteStream(bpy.types.Operator):
"""
Delete stream
"""
bl_idname = "speckle.delete_stream"
bl_label = "Delete stream"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Delete selected stream permanently"
are_you_sure: BoolProperty(
name="Confirm",
default=False,
)
delete_collection: BoolProperty(name="Delete collection", default=False)
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, "are_you_sure")
col.prop(self, "delete_collection")
def invoke(self, context, event):
wm = context.window_manager
if len(context.scene.speckle.users) > 0:
return wm.invoke_props_dialog(self)
return {"CANCELLED"}
def execute(self, context):
if not self.are_you_sure:
return {"CANCELLED"}
self.are_you_sure = False
check = _check_speckle_client_user_stream(context.scene)
if check is None:
return {"CANCELLED"}
user, stream = check
client = speckle_clients[int(context.scene.speckle.active_user)]
client.stream.delete(id=stream.id)
if self.delete_collection:
col_name = "SpeckleStream_{}_{}".format(stream.name, stream.id)
if col_name in bpy.data.collections:
collection = bpy.data.collections[col_name]
bpy.data.collections.remove(collection)
bpy.ops.speckle.load_user_streams()
context.view_layer.update()
if context.area:
context.area.tag_redraw()
return {"FINISHED"}
class SelectOrphanObjects(bpy.types.Operator):
"""
Select Speckle objects that don't belong to any stream
"""
bl_idname = "speckle.select_orphans"
bl_label = "Select orphaned objects"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Select Speckle objects that don't belong to any stream"
def draw(self, context):
layout = self.layout
def execute(self, context):
for o in context.scene.objects:
if (
o.speckle.stream_id
and o.speckle.stream_id not in context.scene["speckle_streams"]
):
o.select = True
else:
o.select = False
return {"FINISHED"}
class UpdateGlobal(bpy.types.Operator):
"""
DEPRECATED
Update all Speckle objects
"""
bl_idname = "speckle.update_global"
bl_label = "Update Global"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Update all Speckle objects"
client = None
def draw(self, context):
layout = self.layout
row = layout.row()
label = row.label(text="Update everything.")
def execute(self, context):
client = context.scene.speckle.client
profiles = client.load_local_profiles()
if len(profiles) < 1:
raise ValueError("No profiles found.")
client.use_existing_profile(sorted(profiles.keys())[0])
context.scene.speckle.user = sorted(profiles.keys())[0]
for obj in context.scene.objects:
if obj.speckle.enabled:
UpdateObject(context.scene.speckle_client, obj)
context.scene.update()
return {"FINISHED"}
class CopyStreamId(bpy.types.Operator):
"""
Copy stream ID to clipboard
"""
bl_idname = "speckle.stream_copy_id"
bl_label = "Copy stream ID"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Copy stream ID to clipboard"
def execute(self, context):
speckle = context.scene.speckle
if len(speckle.users) < 1:
return {"CANCELLED"}
user = speckle.users[int(speckle.active_user)]
if len(user.streams) < 1:
return {"CANCELLED"}
stream = user.streams[user.active_stream]
bpy.context.window_manager.clipboard = stream.id
return {"FINISHED"}
class CopyCommitId(bpy.types.Operator):
"""
Copy commit ID to clipboard
"""
bl_idname = "speckle.commit_copy_id"
bl_label = "Copy commit ID"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Copy commit ID to clipboard"
def execute(self, context):
speckle = context.scene.speckle
if len(speckle.users) < 1:
return {"CANCELLED"}
user = speckle.users[int(speckle.active_user)]
if len(user.streams) < 1:
return {"CANCELLED"}
stream = user.streams[user.active_stream]
if len(stream.branches) < 1:
return {"CANCELLED"}
branch = stream.branches[int(stream.branch)]
if len(branch.commits) < 1:
return {"CANCELLED"}
commit = branch.commits[int(branch.commit)]
bpy.context.window_manager.clipboard = commit.id
return {"FINISHED"}
class CopyBranchName(bpy.types.Operator):
"""
Copy branch name to clipboard
"""
bl_idname = "speckle.branch_copy_name"
bl_label = "Copy branch name"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Copy branch name to clipboard"
def execute(self, context):
speckle = context.scene.speckle
if len(speckle.users) < 1:
return {"CANCELLED"}
user = speckle.users[int(speckle.active_user)]
if len(user.streams) < 1:
return {"CANCELLED"}
stream = user.streams[user.active_stream]
if len(stream.branches) < 1:
return {"CANCELLED"}
branch = stream.branches[int(stream.branch)]
bpy.context.window_manager.clipboard = branch.name
return {"FINISHED"}
-145
View File
@@ -1,145 +0,0 @@
"""
User account operators
"""
from typing import cast
import bpy, bmesh, os
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
)
from bpy_speckle.properties.scene import SpeckleUserObject
from bpy_speckle.functions import _report
from bpy_speckle.clients import speckle_clients
from specklepy.api.client import SpeckleClient
from specklepy.api.credentials import get_default_account, get_local_accounts
class LoadUsers(bpy.types.Operator):
"""
Load all users from local user database
"""
bl_idname = "speckle.users_load"
bl_label = "Load users"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
_report("Loading users...")
users = context.scene.speckle.users
context.scene.speckle.users.clear()
speckle_clients.clear()
profiles = get_local_accounts()
for profile in profiles:
user = users.add()
user.server_name = profile.serverInfo.name or "Speckle Server"
user.server_url = profile.serverInfo.url
user.name = profile.userInfo.name
user.email = profile.userInfo.email
user.company = profile.userInfo.company or ""
user.authToken = profile.token
try:
client = SpeckleClient(
host=profile.serverInfo.url,
use_ssl="https" in profile.serverInfo.url,
)
client.authenticate(user.authToken)
speckle_clients.append(client)
except Exception as ex:
_report(ex)
users.remove(len(users) - 1)
if profile.isDefault:
context.scene.speckle.active_user = str(len(users) - 1)
context.scene.speckle.active_user_index = int(context.scene.speckle.active_user)
bpy.ops.speckle.load_user_streams()
bpy.context.view_layer.update()
if context.area:
context.area.tag_redraw()
return {"FINISHED"}
def add_user_stream(user, stream):
s = user.streams.add()
s.name = stream.name
s.id = stream.id
s.description = stream.description
if not stream.branches:
return
# branches = [branch for branch in stream.branches.items if branch.name != "globals"]
for b in stream.branches.items:
branch = s.branches.add()
branch.name = b.name
if not b.commits:
continue
for c in b.commits.items:
commit = branch.commits.add()
commit.id = commit.name = c.id
commit.message = c.message
commit.author_name = c.authorName
commit.author_id = c.authorId
commit.created_at = c.createdAt
commit.source_application = str(c.sourceApplication)
if hasattr(s, "baseProperties"):
s.units = stream.baseProperties.units
else:
s.units = "Meters"
class LoadUserStreams(bpy.types.Operator):
"""
Load all available streams for active user user
"""
bl_idname = "speckle.load_user_streams"
bl_label = "Load user streams"
bl_options = {"REGISTER", "UNDO"}
bl_description = "(Re)load all available user streams"
def execute(self, context):
speckle = context.scene.speckle
if len(speckle.users) > 0:
user = speckle.users[int(context.scene.speckle.active_user)]
client = speckle_clients[int(context.scene.speckle.active_user)]
try:
streams = client.stream.list(stream_limit=20)
except Exception as e:
_report("Failed to retrieve streams: {}".format(e))
return
if not streams:
_report("Failed to retrieve streams.")
return
user.streams.clear()
default_units = "Meters"
for s in streams:
sstream = client.stream.get(id=s.id)
add_user_stream(user, sstream)
bpy.context.view_layer.update()
return {"FINISHED"}
if context.area:
context.area.tag_redraw()
return {"CANCELLED"}
-23
View File
@@ -1,23 +0,0 @@
from .scene import (
SpeckleSceneSettings,
SpeckleSceneObject,
SpeckleUserObject,
SpeckleStreamObject,
SpeckleBranchObject,
SpeckleCommitObject,
)
from .object import SpeckleObjectSettings
from .collection import SpeckleCollectionSettings
from .addon import SpeckleAddonPreferences
property_classes = [
SpeckleSceneObject,
SpeckleCommitObject,
SpeckleBranchObject,
SpeckleStreamObject,
SpeckleUserObject,
SpeckleSceneSettings,
SpeckleObjectSettings,
SpeckleCollectionSettings,
SpeckleAddonPreferences,
]
-19
View File
@@ -1,19 +0,0 @@
"""
Addon properties
"""
import bpy
from bpy.props import BoolProperty
class SpeckleAddonPreferences(bpy.types.AddonPreferences):
"""
Add-on preferences
TODO: add any preferences that might be relevant here
"""
bl_idname = __package__
def draw(self, context):
layout = self.layout
layout.label(text="SpeckleBlender preferences")
-21
View File
@@ -1,21 +0,0 @@
"""
Collection properties
"""
import bpy
from bpy.props import StringProperty, BoolProperty, EnumProperty
class SpeckleCollectionSettings(bpy.types.PropertyGroup):
enabled: bpy.props.BoolProperty(default=False, name="Enabled")
send_or_receive: bpy.props.EnumProperty(
name="Mode",
items=(
("send", "Send", "Send data to Speckle server."),
("receive", "Receive", "Receive data from Speckle server."),
),
)
stream_id: bpy.props.StringProperty(default="")
name: bpy.props.StringProperty(default="")
units: bpy.props.StringProperty(default="")
-20
View File
@@ -1,20 +0,0 @@
"""
Object properties
"""
import bpy
from bpy.props import StringProperty, BoolProperty, EnumProperty
class SpeckleObjectSettings(bpy.types.PropertyGroup):
enabled: bpy.props.BoolProperty(default=False, name="Enabled")
send_or_receive: bpy.props.EnumProperty(
name="Mode",
items=(
("send", "Send", "Send data to Speckle server."),
("receive", "Receive", "Receive data from Speckle server."),
),
)
stream_id: bpy.props.StringProperty(default="")
object_id: bpy.props.StringProperty(default="")
-135
View File
@@ -1,135 +0,0 @@
"""
Scene properties
"""
import bpy
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
IntProperty,
PointerProperty,
)
from specklepy.api.client import SpeckleClient
class SpeckleSceneObject(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(default="")
class SpeckleCommitObject(bpy.types.PropertyGroup):
id: StringProperty(default="abc")
message: StringProperty(default="A simple commit")
author_name: StringProperty(default="Author name")
author_id: StringProperty(default="Author ID")
created_at: StringProperty(default="Today")
source_application: StringProperty(default="Unknown")
class SpeckleBranchObject(bpy.types.PropertyGroup):
def get_commits(self, context):
if self.commits != None and len(self.commits) > 0:
return [
(str(i), commit.id, commit.message, i)
for i, commit in enumerate(self.commits)
]
return [("0", "<none>", "<none>", 0)]
name: StringProperty(default="main")
commits: CollectionProperty(type=SpeckleCommitObject)
commit: EnumProperty(
name="Commit",
description="Active commit",
items=get_commits,
)
class SpeckleStreamObject(bpy.types.PropertyGroup):
def get_branches(self, context):
if self.branches:
return [
(str(i), branch.name, branch.name, i)
for i, branch in enumerate(self.branches)
if branch.name != "globals"
]
return [("0", "<none>", "<none>", 0)]
name: StringProperty(default="SpeckleStream")
description: StringProperty(default="No description provided.")
id: StringProperty(default="")
units: StringProperty(default="Meters")
query: StringProperty(default="")
branches: CollectionProperty(type=SpeckleBranchObject)
branch: EnumProperty(
name="Branch",
description="Active branch",
items=get_branches,
)
class SpeckleUserObject(bpy.types.PropertyGroup):
server_name: StringProperty(default="SpeckleXYZ")
server_url: StringProperty(default="https://speckle.xyz")
name: StringProperty(default="Speckle User")
email: StringProperty(default="user@speckle.xyz")
company: StringProperty(default="SpeckleSystems")
authToken: StringProperty(default="")
streams: CollectionProperty(type=SpeckleStreamObject)
active_stream: IntProperty(default=0)
class SpeckleSceneSettings(bpy.types.PropertyGroup):
def get_scripts(self, context):
seq = [("<none>", "<none>", "<none>")]
seq.extend([(t.name, t.name, t.name) for t in bpy.data.texts])
return seq
streams: EnumProperty(
name="Available streams",
description="Available streams associated with user.",
items=[],
)
users: CollectionProperty(type=SpeckleUserObject)
def get_users(self, context):
return [
(str(i), "{} ({})".format(user.email, user.server_name), user.server_url, i)
for i, user in enumerate(self.users)
]
def set_user(self, context):
bpy.ops.speckle.load_user_streams()
active_user: EnumProperty(
items=get_users,
name="User",
description="Select user",
update=set_user,
get=None,
set=None,
)
objects: CollectionProperty(type=SpeckleSceneObject)
scale: FloatProperty(default=0.001)
user: StringProperty(
name="User",
description="Current user.",
default="Speckle User",
)
receive_script: EnumProperty(
name="Receive script",
description="Script to run when receiving stream objects.",
items=get_scripts,
)
send_script: EnumProperty(
name="Send script",
description="Script to run when sending stream objects.",
items=get_scripts,
)
@@ -0,0 +1,15 @@
{
"folders": [
{
"path": ".."
},
{
"name": "speckle_blender_addon",
"path": "."
}
],
"settings": {
"blender.addon.loadDirectory": "auto",
"blender.executables": []
}
}
+1 -18
View File
@@ -1,18 +1 @@
from .object import OBJECT_PT_speckle
from .view3d import (
VIEW3D_UL_SpeckleUsers,
VIEW3D_UL_SpeckleStreams,
VIEW3D_PT_SpeckleUser,
VIEW3D_PT_SpeckleStreams,
VIEW3D_PT_SpeckleActiveStream,
VIEW3D_PT_SpeckleHelp,
)
ui_classes = [
VIEW3D_PT_SpeckleUser,
VIEW3D_PT_SpeckleStreams,
VIEW3D_PT_SpeckleActiveStream,
VIEW3D_UL_SpeckleUsers,
VIEW3D_UL_SpeckleStreams,
VIEW3D_PT_SpeckleHelp,
]
from .main_panel import SPECKLE_PT_main_panel
+19
View File
@@ -0,0 +1,19 @@
import bpy
import os
import bpy.utils.previews
speckle_icons = None
def load_icons():
global speckle_icons
speckle_icons = bpy.utils.previews.new()
icons_dir = os.path.dirname(__file__)
speckle_icons.load("speckle_logo", os.path.join(icons_dir, "speckle-logo.png"), 'IMAGE')
def unload_icons():
global speckle_icons
bpy.utils.previews.remove(speckle_icons)
def get_icon(icon_name):
global speckle_icons
return speckle_icons[icon_name].icon_id
+67
View File
@@ -0,0 +1,67 @@
import bpy
from bpy.types import UILayout, Context
from .icons import get_icon
from bindings.account_binding import AccountBinding
# Main Panel
class SPECKLE_PT_main_panel(bpy.types.Panel):
"""
Main panel for the Speckle addon in Blender.
This panel provides the primary user interface such as buttons for publishing and loading models, and model cards for each model added to the file.
"""
bl_label = "Speckle"
bl_idname = "SPECKLE_PT_main_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Speckle'
bindings = {"accountBinding": AccountBinding()}
def draw(self, context: Context) -> None:
layout : UILayout = self.layout
layout.label(text="Speckle Connector BETA", icon_value=get_icon("speckle_logo"))
# for (binding_name, binding) in self.bindings:
# context[binding_name] = binding
# Check to see if there are any speckle models in the file
if not context.scene.speckle_model_cards:
layout.label(text="Hello!")
layout.label(text="There are no Speckle models in this file yet.")
# Add some space
layout.separator()
# Publish and Load buttons
row = layout.row()
row.operator("speckle.publish", text="Publish", icon='EXPORT')
row.operator("speckle.load", text="Load", icon='IMPORT')
layout.separator()
for model_card in context.scene.speckle_model_cards:
box = layout.box()
row = box.row()
icon = 'EXPORT' if model_card.is_publish else 'IMPORT'
row.operator("speckle.publish", text="", icon=icon)
row.label(text=f"{model_card.model_name} - {model_card.project_name}")
row.operator("speckle.model_card_settings", text="", icon='PREFERENCES').model_name = model_card.model_name
row = box.row()
# Display selection summary or version ID
if model_card.is_publish:
# This adjusts the layout of the row (button 1/3, label 2/3 )
split = row.split(factor=0.33)
# TODO: Connect to selection operator
split.operator("speckle.publish", text="Selection")
split.label(text=f"{model_card.selection_summary}")
else:
# This adjusts the layout of the row (button 1/3, label 2/3 )
split = row.split(factor=0.33)
# TODO: Connect to version operator
split.operator("speckle.load", text=f"{model_card.version_id}")
# TODO: Get last updated time
split.label(text="Last updated: 2 days ago")
+27
View File
@@ -0,0 +1,27 @@
import bpy
class speckle_model_card(bpy.types.PropertyGroup):
project_name: bpy.props.StringProperty(name="Project Name", description="Name of the project", default="")
model_name: bpy.props.StringProperty(name="Model Name", description="Name of the model", default="")
is_publish: bpy.props.BoolProperty(name="Publish/Load", description="If the model is published or loaded", default=False)
selection_summary: bpy.props.StringProperty(name="Selection Summary", description="Summary of the selection", default="")
version_id: bpy.props.StringProperty(name="Version ID", description="ID of the selected version", default="")
def to_dict(self):
return{
"project_name" : self.project_name,
"model_name" : self.model_name,
"is_publish" : self.is_publish,
"selection_summary" : self.selection_summary,
"version_id" : self.version_id,
}
@classmethod
def from_dict(cls, data):
item = cls()
item.project_name = data["project_name"]
item.model_name = data["model_name"]
item.is_publish = data["is_publish"]
item.selection_summary = data["selection_summary"]
item.version_id = data["version_id"]
+102
View File
@@ -0,0 +1,102 @@
import bpy
from bpy.types import UILayout, Context, UIList, PropertyGroup, Operator, Event
from .mouse_position_mixin import MousePositionMixin
class speckle_model(bpy.types.PropertyGroup):
"""
PropertyGroup for storing models.
This PropertyGroup is used to store information about a model,
such as its name, source application, and update time.
These are then used in the model selection dialog.
"""
name: bpy.props.StringProperty()
source_app: bpy.props.StringProperty(name="Source")
updated: bpy.props.StringProperty(name="Updated")
class SPECKLE_UL_models_list(bpy.types.UIList):
"""
UIList for displaying a list of models.
This UIList is used to display a list of models in model selection dialog.
"""
#TODO: Adjust column widths so name has the most space.
def draw_item(self, context: Context, layout: UILayout, data: PropertyGroup, item: PropertyGroup, icon: str, active_data: PropertyGroup, active_propname: str) -> None:
if self.layout_type in {'DEFAULT', 'COMPACT'}:
row = layout.row(align=True)
split = row.split(factor=0.5)
split.label(text=item.name)
right_split = split.split(factor=0.25)
right_split.label(text=item.source_app)
right_split.label(text=item.updated)
# This handles when the list is in a grid layout
elif self.layout_type == 'GRID':
layout.alignment = 'CENTER'
layout.label(text=item.name)
class SPECKLE_OT_model_selection_dialog(MousePositionMixin, bpy.types.Operator):
"""
Operator for displaying a dialog for selecting a model.
"""
bl_idname = "speckle.model_selection_dialog"
bl_label = "Select Model"
search_query: bpy.props.StringProperty(
name="Search",
description="Search a project",
default=""
)
project_name: bpy.props.StringProperty(
name="Project Name",
description="The name of the project to select",
default=""
)
models: list[tuple[str, str, str]] = [
("94-workset name", "RVT", "1 day ago"),
("296/skp2skp3", "SKP", "16 days ago"),
("49/rhn2viewer", "RHN", "21 days ago"),
]
model_index: bpy.props.IntProperty(name="Model Index", default=0)
def execute(self, context: Context) -> set[str]:
selected_model = context.scene.speckle_models[self.model_index]
if context.scene.speckle_ui_mode == "PUBLISH":
bpy.ops.speckle.selection_filter_dialog("INVOKE_DEFAULT", project_name=self.project_name, model_name=selected_model.name)
elif context.scene.speckle_ui_mode == "LOAD":
bpy.ops.speckle.version_selection_dialog("INVOKE_DEFAULT", project_name=self.project_name, model_name=selected_model.name)
return {'FINISHED'}
def invoke(self, context: Context, event: Event) -> set[str]:
# Clear existing models
context.scene.speckle_models.clear()
# Populate with new projects
for name, source_app, updated in self.models:
model = context.scene.speckle_models.add()
model.name = name
model.source_app = source_app
model.updated = updated
# Store the original mouse position
self.init_mouse_position(context, event)
return context.window_manager.invoke_props_dialog(self)
def draw(self, context: Context) -> None:
layout : UILayout = self.layout
layout.label(text=f"Project: {self.project_name}")
# Search field
row = layout.row(align=True)
row.prop(self, "search_query", icon='VIEWZOOM', text="")
# Models UIList
layout.template_list("SPECKLE_UL_models_list", "", context.scene, "speckle_models", self, "model_index")
layout.separator()
# Move cursor to original position
self.restore_mouse_position(context)
+15
View File
@@ -0,0 +1,15 @@
import bpy
class MousePositionMixin:
original_mouse_position: bpy.props.IntVectorProperty(size=2)
mouse_snap: bpy.props.BoolProperty(name="Mouse Snap", default=False)
def init_mouse_position(self, context, event):
self.original_mouse_position = (event.mouse_x, event.mouse_y)
self.mouse_snap = False
context.window.cursor_warp(context.scene.speckle_mouse_position[0], context.scene.speckle_mouse_position[1])
def restore_mouse_position(self, context):
if not self.mouse_snap:
self.mouse_snap = True
context.window.cursor_warp(self.original_mouse_position[0], self.original_mouse_position[1])
-35
View File
@@ -1,35 +0,0 @@
"""
Object UI elements
"""
import bpy
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
)
class OBJECT_PT_speckle(bpy.types.Panel):
bl_space_type = "PROPERTIES"
# bl_idname = 'OBJECT_PT_speckle'
bl_region_type = "WINDOW"
bl_context = "object"
bl_label = "Speckle"
def draw_header(self, context):
self.layout.prop(context.object.speckle, "enabled", text="")
def draw(self, context):
ob = context.object
layout = self.layout
layout.active = ob.speckle.enabled
col = layout.column()
col.prop(ob.speckle, "send_or_receive", expand=True)
col.prop(ob.speckle, "stream_id", text="Stream ID")
col.prop(ob.speckle, "object_id", text="Object ID")
col.operator("speckle.update_object", text="Update")
col.operator("speckle.reset_object", text="Reset")
col.operator("speckle.delete_object", text="Delete")
+128
View File
@@ -0,0 +1,128 @@
import bpy
from bpy.types import UILayout, Context, UIList, PropertyGroup, Operator, Event
class speckle_project(bpy.types.PropertyGroup):
"""
PropertyGroup for storing projects.
This PropertyGroup is used to store information about a project,
such as its name, role, and update time.
This is used in the project selection dialog.
"""
name: bpy.props.StringProperty()
role: bpy.props.StringProperty(name="Role")
updated: bpy.props.StringProperty(name="Updated")
class SPECKLE_UL_projects_list(bpy.types.UIList):
"""
UIList for displaying a list of projects.
This UIList is used to display a list of projects in a Blender dialog.
This is used in the project selection dialog.
"""
def draw_item(self, context: Context, layout: UILayout, data: PropertyGroup, item: PropertyGroup, icon: str, active_data: PropertyGroup, active_propname: str) -> None:
if self.layout_type in {'DEFAULT', 'COMPACT'}:
row = layout.row(align=True)
split = row.split(factor=0.5) # This gives project name 1/2
split.label(text=item.name)
right_split = split.split(factor=0.5) # This gives project role and updated the other 1/2 of the row
right_split.label(text=item.role)
right_split.label(text=item.updated)
# This handles when the list is in a grid layout
elif self.layout_type == 'GRID':
layout.alignment = 'CENTER'
layout.label(text=item.name)
class SPECKLE_OT_project_selection_dialog(bpy.types.Operator):
"""
Operator for project selection dialog.
"""
bl_idname = "speckle.project_selection_dialog"
bl_label = "Select Project"
account: bpy.props.EnumProperty(
name="Account",
description="Select the account to filter projects by",
items=[("account1", "Account 1", "Account 1"), ("account2", "Account 2", "Account 2")],
default="account1"
)
search_query: bpy.props.StringProperty(
name="Search",
description="Search a project",
default=""
)
projects: list[tuple[str, str, str]] = [
("RICK'S PORTAL", "contributor", "6 hours ago"),
("[BETA] Revit Tests", "owner", "6 hours ago"),
("Community Tickets", "owner", "a day ago"),
("Bilal's CNX Testing Space", "owner", "a day ago"),
("ArcGIS testing", "contributor", "3 days ago"),
]
project_index: bpy.props.IntProperty(name="Project Index", default=0)
def execute(self, context: Context) -> set[str]:
selected_project = context.scene.speckle_projects[self.project_index]
bpy.ops.speckle.model_selection_dialog("INVOKE_DEFAULT", project_name=selected_project.name)
return {'FINISHED'}
def invoke(self, context: Context, event: Event) -> set[str]:
# Clear existing projects
context.scene.speckle_projects.clear()
# Populate with new projects
for name, role, updated in self.projects:
project = context.scene.speckle_projects.add()
project.name = name
project.role = role
project.updated = updated
return context.window_manager.invoke_props_dialog(self)
def draw(self, context: Context) -> None:
# TODO: Add UI elements here
layout : UILayout = self.layout
accounts = context.accountBinding.get_accounts()
# Account selection
# TODO: Connect to Speckle API to get accounts
layout.prop(self, "account", text="")
# Search field
row = layout.row(align=True)
row.prop(self, "search_query", icon='VIEWZOOM', text="")
row.operator("speckle.add_project_by_url", icon='URL', text="")
# Projects UIList
layout.template_list("SPECKLE_UL_projects_list", "", context.scene, "speckle_projects", self, "project_index")
layout.separator()
class SPECKLE_OT_add_project_by_url(bpy.types.Operator):
"""
Operator for adding a project by URL.
"""
bl_idname = "speckle.add_project_by_url"
bl_label = "Add Project by URL"
bl_description = "Add a project from a URL"
url: bpy.props.StringProperty(
name="Project URL",
description="Enter the Speckle project URL",
default=""
)
def execute(self, context: Context) -> set[str]:
# TODO: Implement logic to add project using the URL
self.report({'INFO'}, f"Adding project from URL: {self.url}")
return {'FINISHED'}
def invoke(self, context: Context, event: Event) -> set[str]:
return context.window_manager.invoke_props_dialog(self)
def draw(self, context: Context) -> None:
layout: UILayout = self.layout
layout.prop(self, "url")
+119
View File
@@ -0,0 +1,119 @@
import bpy
from .mouse_position_mixin import MousePositionMixin
class SPECKLE_OT_selection_filter_dialog(MousePositionMixin, bpy.types.Operator):
"""
Operator for selecting objects.
"""
bl_idname = "speckle.selection_filter_dialog"
bl_label = "Select Objects"
selection_type: bpy.props.EnumProperty(
name="Selection",
items=[
("SELECTION", "Selection", "Select objects manually"),
],
default="SELECTION"
)
project_name: bpy.props.StringProperty(
name="Project Name",
description="Name of the selected project",
default=""
)
model_name: bpy.props.StringProperty(
name="Model Name",
description="Name of the selected model",
default=""
)
def execute(self, context):
model_card = context.scene.speckle_model_cards.add()
model_card.project_name = self.project_name
model_card.model_name = self.model_name
model_card.is_publish = True
# Create the selection summary
selected_objects = context.selected_objects
total_selected = len(selected_objects)
object_types = {}
for obj in selected_objects:
if obj.type not in object_types:
object_types[obj.type] = 1
else:
object_types[obj.type] += 1
summary = f"{total_selected} objects - "
for obj_type, count in object_types.items():
summary += f"{obj_type}: {count}, "
model_card.selection_summary = summary.strip()
return {'FINISHED'}
def invoke(self, context, event):
# Initialize mouse position
self.init_mouse_position(context, event)
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.label(text=f"Project: {self.project_name}")
layout.label(text=f"Model: {self.model_name}")
# Selection dropdown
layout.prop(self, "selection_type")
layout.separator()
# Get selected objects
selected_objects = context.selected_objects
total_selected = len(selected_objects)
# Create a box for the selection summary
box = layout.box()
row = box.row()
row.label(text="Selection Summary", icon='OUTLINER_OB_GROUP_INSTANCE')
row.label(text=f"Total: {total_selected}", icon='OBJECT_DATA')
# Display object types and counts
object_types = {}
for obj in selected_objects:
if obj.type not in object_types:
object_types[obj.type] = 1
else:
object_types[obj.type] += 1
col = box.column(align=True)
for obj_type, count in object_types.items():
row = col.row()
row.label(text=f"{obj_type}:", icon=self.get_icon_for_type(obj_type))
row.label(text=str(count))
layout.separator()
# Restore mouse position
self.restore_mouse_position(context)
def get_icon_for_type(self, obj_type):
icon_map = {
'MESH': 'OUTLINER_OB_MESH',
'CURVE': 'OUTLINER_OB_CURVE',
'SURFACE': 'OUTLINER_OB_SURFACE',
'META': 'OUTLINER_OB_META',
'FONT': 'OUTLINER_OB_FONT',
'ARMATURE': 'OUTLINER_OB_ARMATURE',
'LATTICE': 'OUTLINER_OB_LATTICE',
'EMPTY': 'OUTLINER_OB_EMPTY',
'GPENCIL': 'OUTLINER_OB_GREASEPENCIL',
'CAMERA': 'OUTLINER_OB_CAMERA',
'LIGHT': 'OUTLINER_OB_LIGHT',
'SPEAKER': 'OUTLINER_OB_SPEAKER',
'LIGHT_PROBE': 'OUTLINER_OB_LIGHTPROBE',
}
return icon_map.get(obj_type, 'OBJECT_DATA')
def check(self, context):
return True # This forces the dialog to redraw
Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

+110
View File
@@ -0,0 +1,110 @@
import bpy
from .mouse_position_mixin import MousePositionMixin
class speckle_version(bpy.types.PropertyGroup):
"""
PropertyGroup for storing versions.
This PropertyGroup is used to store information about a version,
such as its ID, message, and updated time.
These are then used in the version selection dialog.
"""
id: bpy.props.StringProperty(name="ID")
message: bpy.props.StringProperty(name="Message")
updated: bpy.props.StringProperty(name="Updated")
source_app: bpy.props.StringProperty(name="Source")
class SPECKLE_UL_versions_list(bpy.types.UIList):
"""
UIList for displaying a list of versions.
This UIList is used to display a list of versions in the version selection dialog.
"""
#TODO: Adjust column widths so message has the most space.
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if self.layout_type in {'DEFAULT', 'COMPACT'}:
row = layout.row(align=True)
split = row.split(factor=0.166)
split.label(text=item.id)
right_split = split.split(factor=0.7)
right_split.label(text=item.message)
right_split.label(text=item.updated)
# This handles when the list is in a grid layout
elif self.layout_type == 'GRID':
layout.alignment = 'CENTER'
layout.label(text=item.id)
class SPECKLE_OT_version_selection_dialog(MousePositionMixin, bpy.types.Operator):
"""
Operator for selecting a version.
"""
bl_idname = "speckle.version_selection_dialog"
bl_label = "Select Version"
search_query: bpy.props.StringProperty(
name="Search",
description="Search a project",
default=""
)
project_name: bpy.props.StringProperty(
name="Project Name",
description="Name of the selected project",
default=""
)
model_name: bpy.props.StringProperty(
name="Model Name",
description="Name of the selected model",
default=""
)
versions = [
("648896", "Message 1", "12 day ago"),
("658465", "Message 2", "15 days ago"),
("154651", "Message 3", "20 days ago"),
]
version_index: bpy.props.IntProperty(name="Model Index", default=0)
def execute(self, context):
model_card = context.scene.speckle_model_cards.add()
model_card.project_name = self.project_name
model_card.model_name = self.model_name
model_card.is_publish = False
# Store the selected version ID
selected_version = context.scene.speckle_versions[self.version_index]
model_card.version_id = selected_version.id
return {'FINISHED'}
def invoke(self, context, event):
# Clear existing versions
context.scene.speckle_versions.clear()
# Populate with new versions
for id, message, updated in self.versions:
version = context.scene.speckle_versions.add()
version.id = id
version.message = message
version.updated = updated
# Initialize mouse position
self.init_mouse_position(context, event)
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.label(text=f"Project: {self.project_name}")
layout.label(text=f"Model: {self.model_name}")
# TODO: Add more UI elements here.
# Search field
row = layout.row(align=True)
row.prop(self, "search_query", icon='VIEWZOOM', text="")
# Versions UIList
layout.template_list("SPECKLE_UL_versions_list", "", context.scene, "speckle_versions", self, "version_index")
layout.separator()
# Restore mouse position
self.restore_mouse_position(context)
-277
View File
@@ -1,277 +0,0 @@
"""
Speckle UI elements for the 3d viewport
"""
import bpy
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
)
import datetime
"""
Compatibility
TODO: evaluate if we should still support Blender <2.80
"""
Region = "TOOLS" if bpy.app.version < (2, 80, 0) else "UI"
def wrap(width, text):
"""
Split strings into width for
wrapping
"""
lines = []
arr = text.split()
lengthSum = 0
line = []
for var in arr:
lengthSum += len(var) + 1
if lengthSum <= width:
line.append(var)
else:
lines.append(" ".join(line))
line = [var]
lengthSum = len(var)
lines.append(" ".join(line))
return lines
def get_available_users(self, context):
"""
Function to populate users list
"""
return [(a, a, a.name) for a in context.scene.speckle.users]
class VIEW3D_UL_SpeckleUsers(bpy.types.UIList):
"""
Speckle user list
"""
def draw_item(self, context, layout, data, user, active_data, active_propname):
if self.layout_type in {"DEFAULT", "COMPACT"}:
if user:
# layout.prop(user, "name", text=user.name, emboss=False, icon_value=0)
layout.label(
text=user.name + " (" + user.email + ")",
translate=False,
icon_value=0,
)
else:
layout.label(text="", translate=False, icon_value=0)
elif self.layout_type in {"GRID"}:
layout.alignment = "CENTER"
layout.label(text="Users", icon_value=0)
class VIEW3D_UL_SpeckleStreams(bpy.types.UIList):
"""
Speckle stream list
"""
def draw_item(self, context, layout, data, stream, active_data, active_propname):
if self.layout_type in {"DEFAULT", "COMPACT"}:
if stream:
# layout.prop(user, "name", text=user.name, emboss=False, icon_value=0)
layout.label(
text="{} ({})".format(stream.name, stream.id),
translate=False,
icon_value=0,
)
else:
layout.label(text=" ", translate=False, icon_value=0)
elif self.layout_type in {"GRID"}:
layout.alignment = "CENTER"
layout.label(text="Streams", icon_value=0)
class VIEW3D_PT_SpeckleUser(bpy.types.Panel):
"""
Speckle Users UI panel in the 3d viewport
"""
bl_space_type = "VIEW_3D"
bl_region_type = Region
bl_category = "Speckle"
bl_context = "objectmode"
bl_label = "User"
def draw(self, context):
speckle = context.scene.speckle
layout = self.layout
col = layout.column()
if len(speckle.users) < 1:
col.label(text="No users found.")
else:
# col.label(text="User")
col.prop(speckle, "active_user", text="")
user = speckle.users[int(speckle.active_user)]
col.label(text="{} ({})".format(user.server_name, user.server_url))
col.label(text="{} ({})".format(user.name, user.email))
class VIEW3D_PT_SpeckleStreams(bpy.types.Panel):
"""
Speckle Streams UI panel in the 3d viewport
"""
bl_space_type = "VIEW_3D"
bl_region_type = Region
bl_category = "Speckle"
bl_context = "objectmode"
bl_label = "Streams"
def draw(self, context):
speckle = context.scene.speckle
col = self.layout.column()
if len(speckle.users) < 1:
col.label(text="No stream data.")
else:
user = speckle.users[int(speckle.active_user)]
col.template_list(
"VIEW3D_UL_SpeckleStreams", "", user, "streams", user, "active_stream"
)
row = col.row(align=True)
row.operator("speckle.add_stream_from_url", text="", icon="URL")
row.operator("speckle.create_stream", text="", icon="ADD")
row.operator("speckle.delete_stream", text="", icon="REMOVE")
row.operator("speckle.load_user_streams", text="", icon="FILE_REFRESH")
class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
"""
Speckle Active Streams UI panel in the 3d viewport
"""
bl_space_type = "VIEW_3D"
bl_region_type = Region
bl_category = "Speckle"
bl_context = "objectmode"
bl_label = "Active stream"
def draw(self, context):
speckle = context.scene.speckle
col = self.layout.column()
if len(speckle.users) < 1:
col.label(text="No stream data.")
else:
user = speckle.users[int(speckle.active_user)]
if len(user.streams) < 1:
col.label(text="No active stream.")
else:
stream = user.streams[user.active_stream]
# user.active_stream = min(user.active_stream, len(user.streams) - 1)
row = col.row()
row.label(text="{} ({})".format(stream.name, stream.id))
row.operator("speckle.stream_copy_id", text="", icon="COPY_ID")
col.separator()
row = col.row()
row.prop(stream, "branch", text="")
row.operator("speckle.branch_copy_name", text="", icon="COPY_ID")
if len(stream.branches) > 0:
branch = stream.branches[int(stream.branch)]
row = col.row()
row.prop(branch, "commit", text="")
row.operator("speckle.commit_copy_id", text="", icon="COPY_ID")
if len(branch.commits) > 0:
commit = branch.commits[int(branch.commit)]
area = col.box()
area.separator()
lines = wrap(32, commit.message)
for line in lines:
row = area.row(align=True)
row.alignment = "EXPAND"
row.scale_y = 0.4
row.label(text=line)
area.separator()
dt = datetime.datetime.strptime(
commit.created_at, "%Y-%m-%dT%H:%M:%S.%fZ"
)
col.label(text="{}".format(dt.ctime()))
col.label(
text="{} ({})".format(commit.author_name, commit.author_id)
)
col.label(text=commit.source_application)
else:
col.label(text="No branches found!")
col.separator()
area = col.box()
row = area.row()
subcol = row.column()
subcol.operator("speckle.receive_stream_objects", text="Receive")
subcol.prop(speckle, "receive_script", text="")
subcol = row.column()
subcol.operator("speckle.send_stream_objects", text="Send")
subcol.prop(speckle, "send_script", text="")
area.prop(stream, "query", text="Filter")
col.separator()
row = col.row(align=True)
subcol = row.column()
subcol.label(text="Units:")
subcol = row.column()
subcol.label(text=stream.units)
col.label(text="Description:")
area = col.box()
area.separator()
lines = wrap(32, stream.description)
for line in lines:
row = area.row(align=True)
row.alignment = "EXPAND"
row.scale_y = 0.4
row.label(text=line)
area.separator()
col.separator()
col.operator("speckle.view_stream_data_api", text="Open Stream in Web")
class VIEW3D_PT_SpeckleHelp(bpy.types.Panel):
"""
Speckle Help UI panel in the 3d viewport
"""
bl_space_type = "VIEW_3D"
bl_region_type = Region
bl_category = "Speckle"
bl_context = "objectmode"
bl_label = "Help"
def draw(self, context):
layout = self.layout
col = layout.column()
col.operator("speckle.open_speckle_guide")
col.separator()
col.operator("speckle.open_speckle_tutorials")
col.separator()
col.operator("speckle.open_speckle_forum")
-54
View File
@@ -1,54 +0,0 @@
def find_key_case_insensitive(data, key, default=None):
value = data.get(key)
if value:
return value
"""
Necessary to find keys where the first character
is capitalized
"""
value = data.get(key[0].upper() + key[1:])
if value:
return value
value = data.get(key.upper())
if value:
return value
return default
def get_iddata(base, uuid, name, obdata):
"""
This is taken from the import_3dm add-on:
https://github.com/jesterKing/import_3dm
# Copyright (c) 2018-2019 Nathan Letwory, Joel Putnam,
Tom Svilans
Get an iddata. If an object with given uuid is found in
this .blend use that. Otherwise new up one with base.new,
potentially with obdata if that is set
"""
founditem = None
if uuid is not None:
for item in base:
if item.get("speckle_id", None) == str(uuid):
founditem = item
break
elif name:
for item in base:
if item.get("name", None) == name:
founditem = item
break
if founditem:
theitem = founditem
theitem["name"] = name
if obdata:
theitem.data = obdata
else:
if obdata:
theitem = base.new(name=name, object_data=obdata)
else:
theitem = base.new(name=name)
tag_data(theitem, uuid, name)
return theitem
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -e -o pipefail
poetry export --only main -o bpy_speckle/requirements.txt
+20
View File
@@ -0,0 +1,20 @@
import sys
from pathlib import Path
def patch_installer(tag: str):
"""Patches the installer with the correct connector version and specklepy version"""
tag = tag.replace("\n", "")
iss_file = "speckle-sharp-ci-tools/blender.iss"
iss_path = Path(iss_file)
lines = iss_path.read_text().split("\n")
lines.insert(12, f'#define AppVersion "{tag.split("-")[0]}"')
lines.insert(13, f'#define AppInfoVersion "{tag}"')
iss_path.write_text("\n".join(lines))
print(f"Patched installer with connector v{tag}")
if __name__ == "__main__":
tag = sys.argv[1]
patch_installer(tag)
+2 -36
View File
@@ -1,7 +1,6 @@
import re
import sys
def patch_connector(tag):
"""Patches the connector version within the connector init file"""
bpy_file = "bpy_speckle/__init__.py"
@@ -19,46 +18,13 @@ def patch_connector(tag):
with open(bpy_file, "w") as file:
file.writelines(lines)
def patch_installer(tag):
"""Patches the installer with the correct connector version and specklepy version"""
iss_file = "speckle-sharp-ci-tools/blender.iss"
py_tag = get_specklepy_version()
with open(iss_file, "r") as file:
lines = file.readlines()
lines.insert(11, f'#define SpecklepyVersion "{py_tag}"\n')
lines.insert(11, f'#define AppVersion "{tag}"\n')
with open(iss_file, "w") as file:
file.writelines(lines)
print(f"Patched installer with connector v{tag} and specklepy v{py_tag}")
def get_specklepy_version():
"""Get version of specklepy to install from the pyproject.toml"""
version = "2.3.3"
with open("pyproject.toml", "r") as f:
lines = [line for line in f if line.startswith("specklepy = ")]
if not lines:
raise Exception("Could not find specklepy in pyproject.toml")
match = re.search(r"[0-9]+(\.[0-9]+)*", lines[0])
if match:
version = match[0]
return version
def main():
if len(sys.argv) < 2:
return
tag = sys.argv[1]
if not re.match(r"[0-9]+(\.[0-9]+)*$", tag):
if not re.match(r"([0-9]+)\.([0-9]+)\.([0-9]+)", tag):
raise ValueError(f"Invalid tag provided: {tag}")
print(f"Patching version: {tag}")
patch_connector(tag)
patch_installer(tag)
patch_connector(tag.split("-")[0])
if __name__ == "__main__":
Generated
+1149 -653
View File
File diff suppressed because it is too large Load Diff
+12 -8
View File
@@ -2,18 +2,22 @@
name = "speckle-blender"
version = "2.0.0"
description = "the Speckle 2.0 connector for Blender!"
authors = ["izzy lyseggen <izzy.lyseggen@gmail.com>"]
authors = ["izzy lyseggen <izzy.lyseggen@gmail.com>", "Gergő Jedlicska <gergo@jedlicska.com>"]
license = "Apache-2.0"
[tool.poetry.dependencies]
python = ">=3.7,<3.8"
specklepy = "2.3.5"
python = ">=3.8, <4.0.0"
specklepy = "^2.19.1"
attrs = "^23.1.0"
[tool.poetry.dev-dependencies]
devtools = "^0.6.1"
numpy = "^1.20.2"
bpy = "^2.82.1"
bpy-build = "^2.1.0"
# [tool.poetry.group.local_specklepy.dependencies]
# specklepy = {path = "../specklepy", develop = true}
[tool.poetry.group.dev.dependencies]
fake-bpy-module-latest = "^20240524"
black = "23.11.0"
pylint = "^2.15.7"
ruff = "^0.4.4"
[build-system]
requires = ["poetry-core>=1.0.0"]