Compare commits

...

115 Commits

Author SHA1 Message Date
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
izzy lyseggen 376a90c5cf Merge pull request #59 from specklesystems/izzy/texture-coords
feat(convert): texture coords out and material receive fix
2021-10-14 18:03:52 +02:00
izzy lyseggen b0f8dbd63e fix(ui): diff fix for hiding globals
this is acting kinda weird, but it's taking up too much time for me
and it works as is so whatever
2021-10-14 17:03:19 +01:00
izzy lyseggen 89a8232a47 chore: update specklepy 2021-10-14 16:23:15 +01:00
izzy lyseggen 985f23b015 fix(convert): temp fix for materials w no name/id 2021-10-14 16:22:35 +01:00
izzy lyseggen d15a480e64 feat(uvs): try adding active uv layer 2021-10-14 16:22:35 +01:00
izzy lyseggen b88af9ac08 chore: update specklepy to 2.3.4 2021-10-13 10:37:14 +01:00
izzy lyseggen b649e29bdc Merge pull request #58 from specklesystems/izzy/chores
chore: smol fixes
2021-10-12 17:24:44 +02:00
izzy lyseggen bf907519a8 fix(streams): hide globals branch
you can't receive it anyway so let's just not show it
closes 🌿 Reorder branches in default selection menu #55
2021-10-12 16:23:07 +01:00
izzy lyseggen aa8cab2f27 fix(users): ssl check 2021-10-12 15:53:43 +01:00
izzy lyseggen 5157dbcfde feat(metrics): set host app 2021-10-12 15:28:20 +01:00
izzy lyseggen d03f11c57d Merge pull request #57 from specklesystems/izzy/update-ci
adds patcher script to patch connector and specklepy versions in installer and in connector init
2021-10-12 16:20:57 +02:00
izzy lyseggen b1deb1bb1c chore: update specklepy 2021-10-12 15:18:48 +01:00
izzy lyseggen bdd2c5d0ea ci: move patching to py script 2021-10-12 15:17:13 +01:00
izzy lyseggen 6783d82e48 ci: patch installer as well 2021-10-12 15:11:03 +01:00
izzy lyseggen f1db69fce5 ci: patch version script 2021-10-12 12:54:20 +01:00
Matteo Cominetti d09908412d Create close-issue.yml 2021-10-02 17:09:39 +01:00
Matteo Cominetti e84163a600 Create open-issue.yml 2021-10-02 17:09:19 +01:00
izzy lyseggen 03a77759a1 docs: update readme to align with new format 2021-09-03 16:26:15 +01:00
izzy lyseggen 47258cb7a6 Merge pull request #51 from specklesystems/izzy/toggle-modifiers
feat(send): toggle applying modifiers
2021-08-13 12:28:25 +01:00
izzy lyseggen b216f95187 feat(send): toggle applying moddifiers 2021-08-13 12:27:05 +01:00
izzy lyseggen 695135b655 Merge pull request #50 from specklesystems/izzy/stream-by-url
feat(ui): add stream, branch, or commit by url
2021-08-11 09:59:26 +01:00
izzy lyseggen 5f7e47221c feat(ui): get commit and branch from url 2021-08-11 09:51:03 +01:00
izzy lyseggen 38ee1c6ef9 chore: update specklepy again 2021-08-11 09:50:19 +01:00
izzy lyseggen d8b3c49dee chore: upgrade specklepy version 2021-08-11 08:59:11 +01:00
izzy lyseggen dceec1b061 feat(ui): add stream from url 2021-08-10 17:44:14 +01:00
izzy lyseggen 2071c3f3b4 chore: update deps (for specklepy ^2.2.6) 2021-08-10 11:43:45 +01:00
izzy lyseggen d45419ba5e Merge pull request #49 from specklesystems/izzy/help-panel
feat(ui): help panel w links to docs, tuts, forum
2021-08-10 11:38:30 +01:00
izzy lyseggen 2c640b7272 feat(ui): help panel w links to docs, tuts, forum 2021-08-10 11:37:16 +01:00
izzy lyseggen c00635d093 Merge pull request #48 from specklesystems/izzy/receive-fixes
fix(cols): try catch linking children
2021-08-04 10:26:34 +01:00
izzy lyseggen a07d4f0a8e fix(cols): try catch linking children 2021-08-04 10:25:33 +01:00
izzy lyseggen 61e721716f Merge pull request #47 from specklesystems/izzy/receive-fixes
fix(receive): clear matrix and get mat name
2021-08-04 10:10:15 +01:00
izzy lyseggen 91d12b5a6c fix: remove devtools ref 2021-08-04 10:09:52 +01:00
izzy lyseggen f331846138 fix(receive): clear matrix and get mat name 2021-08-04 10:08:59 +01:00
izzy lyseggen d350887860 Merge pull request #46 from specklesystems/izzy/materials-again
fix(receive): adding and assigning materials
2021-07-30 11:17:48 +01:00
izzy lyseggen 8ad607a8e0 docs(readme): update info on materials 2021-07-30 11:12:46 +01:00
izzy lyseggen 13f242e47f fix(user): make sure default is first active 2021-07-29 16:35:32 +01:00
izzy lyseggen 9f55acd02d fix(mats): get both detached and attached by name 2021-07-28 18:03:59 +01:00
izzy lyseggen 476f947b68 Merge pull request #45 from specklesystems/izzy/gh-receive-bug
fix(receive): resolve bug when receiveing 🦗 CSO
2021-07-19 18:20:55 +01:00
izzy lyseggen f56dea0a35 fix(receive): resolve bug when receiveing 🦗 CSO 2021-07-19 18:19:15 +01:00
izzy lyseggen c8d6b3ebea Merge pull request #41 from specklesystems/izzy/grasshopper-fix
fix(receive): handle nested lists from grasshopper
2021-07-01 17:20:28 +01:00
izzy lyseggen b2e7a899f6 fix(receive): handle nested lists from grasshopper 2021-07-01 17:03:45 +01:00
izzy lyseggen ef0f7ef46f Merge pull request #39 from specklesystems/izzy/units-and-conversions
General fixes regarding units, curves, and props
2021-07-01 16:02:51 +01:00
izzy lyseggen 8799f52dfb refactor: bit more cleanup 2021-07-01 16:00:00 +01:00
izzy lyseggen 22c1fb4fb2 refactor(curve): clean up curve conversions 2021-07-01 15:59:37 +01:00
izzy lyseggen bce03f9e38 feat(convert): adjust unit and prop handling 2021-07-01 15:59:12 +01:00
izzy lyseggen dc1d1adefc Merge pull request #34 from specklesystems/izzy/conversions
feat(conversions): improvements for `from_speckle` conversions
2021-06-18 18:23:27 +01:00
izzy lyseggen 4de1f6ecc1 feat(conversions): clen up from speckle 2021-06-18 18:10:36 +01:00
izzy lyseggen 4eaea0cc51 fix(send): add blender as commit source app 2021-06-18 15:34:47 +01:00
izzy lyseggen ccf2996096 feat(converter): get mesh for unsupported objs 2021-06-17 18:49:19 +01:00
izzy lyseggen aa0a4d2f3b fix(conversions): arc and nurbs fixes 2021-06-17 18:30:48 +01:00
izzy lyseggen 48ef9420de fix(conversion): try catch for fails 2021-06-17 18:29:50 +01:00
izzy lyseggen 6c223dba33 fix(user): increase stream limit 2021-06-17 18:29:36 +01:00
izzy lyseggen b028b93da2 Merge pull request #32 from specklesystems/izzy/refactor
fix(receive): some prelim geometry fixes
2021-06-17 16:54:35 +01:00
izzy lyseggen f6132179b1 fix(receive): some temp geometry fixes
will need to investigate this a bit further... but want to put something out for now
2021-06-17 16:52:21 +01:00
izzy lyseggen 52f40400bf chore: lock file 2021-06-17 16:51:21 +01:00
37 changed files with 2679 additions and 1705 deletions
+63 -31
View File
@@ -21,8 +21,36 @@ jobs:
- 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: Install specklepy with python 3.7 for Blender v2.93
shell: powershell.exe
command: |
$pyarr=(python --version).split(' ')[1].split('.')
$pyver=($pyarr[0..1] -join '.')
echo "using python version:" $pyver
$specklepy=(python patch_version.py)
python -m pip install --target=./modules-$pyver specklepy==$specklepy
- run:
name: Install python 3.9 and specklepy for Blender v3.0
shell: powershell.exe
command: |
choco upgrade python --version=3.9.7
refreshenv
$pyarr=(py --version).split(' ')[1].split('.')
$pyver=($pyarr[0..1] -join '.')
echo "using python version:" $pyver
$specklepy=(python patch_version.py)
py -m pip install --target=./modules-$pyver specklepy==$specklepy
- run:
name: Install python 3.10 and specklepy for Blender v3.1
shell: powershell.exe
command: |
choco upgrade python --version=3.10.2
refreshenv
$pyarr=(py --version).split(' ')[1].split('.')
$pyver=($pyarr[0..1] -join '.')
echo "using python version:" $pyver
$specklepy=(python patch_version.py)
py -m pip install --target=./modules-$pyver specklepy==$specklepy
- run:
name: Patch
shell: powershell.exe
@@ -36,12 +64,34 @@ jobs:
# 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
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\blender.iss /dAppVersion=$version
ls
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
install-specklepy: # due to ujson dep, we need to match the py version to the install
docker:
- image: "cimg/python:<<parameters.tag>>"
parameters:
tag:
default: "3.9"
type: string
steps:
- checkout
- run:
name: upgrade pip and install specklepy
command: |
specklepyver=$(python patch_version.py)
echo installing specklepy $specklepyver
python -m pip install --target=./modules-<<parameters.tag>> specklepy==$specklepyver
- persist_to_workspace:
root: ./
paths:
- modules-*
get-ci-tools: # Clones our ci tools and persists them to the workspace
docker:
- image: cimg/base:2021.01
@@ -53,10 +103,6 @@ jobs:
root: ./
paths:
- speckle-sharp-ci-tools
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools
deploy: # Uploads all installers found to S3
docker:
@@ -76,41 +122,27 @@ jobs:
to: s3://speckle-releases/installers/
workflows:
build:
jobs:
- get-ci-tools:
filters:
branches:
only:
- main
- /ci\/.*/
- build-connector:
slug: blender
requires:
- get-ci-tools
filters:
branches:
only:
- main
- /ci\/.*/
deploy:
main:
jobs:
- get-ci-tools:
filters:
tags:
only: /[0-9]+(\.[0-9]+)*/
only: /.*/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
only:
- main
- /ci\/.*/
- build-connector:
slug: blender
requires:
- get-ci-tools
filters:
tags:
only: /[0-9]+(\.[0-9]+)*/
only: /.*/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
only:
- main
- /ci\/.*/
- deploy:
requires:
- get-ci-tools
+78
View File
@@ -0,0 +1,78 @@
name: Update issue Status
on:
issues:
types: [closed]
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 }}
+50
View File
@@ -0,0 +1,50 @@
name: Move new issues into Project
on:
issues:
types: [opened]
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
+1
View File
@@ -5,6 +5,7 @@ __pycache__/
# editor
.vscode
.idea
# dev
.venv
+44 -16
View File
@@ -1,23 +1,51 @@
# SpeckleBlender 2.0
Speckle add-on for Blender 2.92
<h1 align="center">
<img src="https://user-images.githubusercontent.com/2679513/131189167-18ea5fe1-c578-47f6-9785-3748178e4312.png" width="150px"/><br/>
Speckle | Blender
</h1>
<h3 align="center">
Connector for Blender 2.92 & 2.93
</h3>
<p align="center"><b>Speckle</b> is the data infrastructure for the AEC industry.</p><br/>
[![Twitter Follow](https://img.shields.io/twitter/follow/SpeckleSystems?style=social)](https://twitter.com/SpeckleSystems) [![Community forum users](https://img.shields.io/discourse/users?server=https%3A%2F%2Fdiscourse.speckle.works&style=flat-square&logo=discourse&logoColor=white)](https://discourse.speckle.works) [![website](https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square)](https://speckle.systems) [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&logo=read-the-docs&logoColor=white)](https://speckle.guide/dev/)
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&amp;style=flat-square&amp;logo=discourse&amp;logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://speckle.guide/dev/"><img src="https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&amp;logo=read-the-docs&amp;logoColor=white" alt="docs"></a></p>
<p align="center"><a href="https://github.com/specklesystems/speckle-blender/"><img src="https://circleci.com/gh/specklesystems/speckle-blender.svg?style=svg&amp;circle-token=76eabd350ea243575cbb258b746ed3f471f7ac29" alt="Speckle-Next"></a> </p>
## Introduction
# About Speckle
What is Speckle? Check our ![YouTube Video Views](https://img.shields.io/youtube/views/B9humiSpHzM?label=Speckle%20in%201%20minute%20video&style=social)
### Features
- **Object-based:** say goodbye to files! Speckle is the first object based platform for the AEC industry
- **Version control:** Speckle is the Git & Hub for geometry and BIM data
- **Collaboration:** share your designs collaborate with others
- **3D Viewer:** see your CAD and BIM models online, share and embed them anywhere
- **Interoperability:** get your CAD and BIM models into other software without exporting or importing
- **Real time:** get real time updates and notifications and changes
- **GraphQL API:** get what you need anywhere you want it
- **Webhooks:** the base for a automation and next-gen pipelines
- **Built for developers:** we are building Speckle with developers in mind and got tools for every stack
- **Built for the AEC industry:** Speckle connectors are plugins for the most common software used in the industry such as Revit, Rhino, Grasshopper, AutoCAD, Civil 3D, Excel, Unreal Engine, Unity, QGIS, Blender and more!
### Try Speckle now!
Give Speckle a try in no time by:
- [![speckle XYZ](https://img.shields.io/badge/https://-speckle.xyz-0069ff?style=flat-square&logo=hackthebox&logoColor=white)](https://speckle.xyz) ⇒ creating an account at our public server
- [![create a droplet](https://img.shields.io/badge/Create%20a%20Droplet-0069ff?style=flat-square&logo=digitalocean&logoColor=white)](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
### Resources
- [![Community forum users](https://img.shields.io/badge/community-forum-green?style=for-the-badge&logo=discourse&logoColor=white)](https://speckle.community) for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
- [![website](https://img.shields.io/badge/tutorials-speckle.systems-royalblue?style=for-the-badge&logo=youtube)](https://speckle.systems) our tutorials portal is full of resources to get you started using Speckle
- [![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
The Speckle UI can be found in the 3d viewport toolbar (N), under the Speckle tab.
<!--
This repo holds Speckle's:
- Default [Code of Conduct](.github/CODE_OF_CONDUCT.md),
- Default [Contribution Guidelines](.github/CONTRIBUTING.md),
- README template (you're reading it now),
- Default [Issue Template](.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md),
- Default [Pull Request Template](.github/PULL_REQUEST_TEMPLATE/PR_TEMPLATE.md),
- OSS License (Apache 2.0)
Either copy paste the parts that are useful in existing repos, or use this as a base when creating a new repository.
-->
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.
@@ -45,7 +73,7 @@ This code is WIP and as such should be used with extreme caution on non-sensitiv
## Custom properties
- **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 `material` property is found, **SpeckleBlender** will create a material named using the sub-property `material.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.
- 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"`.
+3
View File
@@ -54,6 +54,7 @@ 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 *
@@ -94,6 +95,8 @@ def register():
for cls in speckle_classes:
register_class(cls)
metrics.set_host_app("Blender")
"""
Register all new properties
"""
-1
View File
@@ -11,4 +11,3 @@ def scb_on_mesh_edit(context):
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')
+6 -277
View File
@@ -1,230 +1,15 @@
import bpy, idprop
from mathutils import Matrix
from .from_speckle import *
from .to_speckle import *
from .util import *
from bpy_speckle.util import find_key_case_insensitive
from bpy_speckle.functions import _report
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,
}
# FROM_SPECKLE = {
# "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"):
return
speckle_mat = smesh.renderMaterial
mat_name = getattr(speckle_mat, "name", None)
if not mat_name:
mat_name = speckle_mat.applicationId or speckle_mat.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"]
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):
if type(speckle_object) in FROM_SPECKLE_SCHEMAS.keys():
print("Got object type: {}".format(type(speckle_object)))
speckle_name = (
name
or getattr(speckle_object, "name", None)
or speckle_object.speckle_type + f" -- {speckle_object.id}"
)
obdata = FROM_SPECKLE_SCHEMAS[type(speckle_object)](
speckle_object, scale, speckle_name
)
if speckle_name in bpy.data.objects.keys():
blender_object = bpy.data.objects[speckle_name]
blender_object.data = obdata
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)
set_transform(speckle_object, blender_object)
return blender_object
else:
_report("Invalid input: {}".format(speckle_object))
return None
from bpy_speckle.convert.to_native import convert_to_native
def get_speckle_subobjects(attr, scale, name):
subobjects = []
for key in attr.keys():
keys = attr.keys() if isinstance(attr, dict) else attr.get_dynamic_member_names()
for key in 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)
name = f"{name}.{key}"
subobject = convert_to_native(attr[key], name)
subobjects.append(subobject)
props = attr[key].get("properties", None)
@@ -234,66 +19,10 @@ def get_speckle_subobjects(attr, scale, name):
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)
subobject = convert_to_native(attr[key], name)
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"):
d = {}
for key in obj.keys():
if key in ignored_keys or key.startswith("_"):
continue
d[key] = get_blender_custom_properties(obj[key], max_depth - 1)
return d
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):
blender_type = blender_object.type
speckle_objects = []
speckle_material = material_to_speckle(blender_object)
if blender_type in TO_SPECKLE.keys():
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)
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
-22
View File
@@ -1,22 +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 = speckle_brep.displayMesh or speckle_brep.displayValue
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
from mathutils import Vector, Quaternion
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 = Vector(plane.normal.value)
xaxis = plane.xdir
yaxis = plane.ydir
radius = rcurve.radius * scale
startAngle = rcurve.startAngle
endAngle = rcurve.endAngle
startQuat = Quaternion(normal, startAngle)
endQuat = Quaternion(normal, endAngle)
"""
Get start and end vectors, centre point, angles, etc.
"""
r1 = Vector(plane.xdir.value)
r1.rotate(startQuat)
r2 = Vector(plane.xdir.value)
r2.rotate(endQuat)
c = Vector(plane.origin.value) * 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 = 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
-159
View File
@@ -1,159 +0,0 @@
import bpy, bmesh, struct
import base64
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
while i < len(sfaces):
if sfaces[i] == 0:
i += 1
f = bmesh.faces.new(
(
bmesh.verts[int(sfaces[i])],
bmesh.verts[int(sfaces[i + 1])],
bmesh.verts[int(sfaces[i + 2])],
)
)
f.smooth = smooth
i += 3
elif sfaces[i] == 1:
i += 1
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
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() and False:
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
+390
View File
@@ -0,0 +1,390 @@
import math
from bpy_speckle.functions import get_scale_length, _report
import mathutils
import bpy, bmesh, bpy_types
from specklepy.objects.other import *
from specklepy.objects.geometry import *
from .util import (
add_blender_material,
add_custom_properties,
add_vertices,
add_faces,
add_colors,
add_uv_coords,
)
SUPPORTED_CURVES = (Line, Polyline, Curve, Arc, Polycurve)
CAN_CONVERT_TO_NATIVE = (
Mesh,
*SUPPORTED_CURVES,
Transform,
BlockDefinition,
BlockInstance,
)
def can_convert_to_native(speckle_object):
if type(speckle_object) in CAN_CONVERT_TO_NATIVE:
return True
if getattr(
speckle_object, "displayValue", getattr(speckle_object, "displayMesh", None)
):
return True
_report(f"Could not convert unsupported Speckle object: {speckle_object}")
return False
def convert_to_native(speckle_object, name=None):
speckle_type = type(speckle_object)
speckle_name = (
name
or getattr(speckle_object, "name", None)
or f"{speckle_object.speckle_type} -- {speckle_object.id}"
)
if speckle_type not in CAN_CONVERT_TO_NATIVE:
elements = getattr(speckle_object, "elements", []) or []
display = getattr(
speckle_object, "displayValue", getattr(speckle_object, "displayMesh", None)
)
if not elements and not display:
_report(f"Could not convert unsupported Speckle object: {speckle_object}")
return
if isinstance(display, list):
elements.extend(display)
else:
elements.append(display)
# TODO: depreciate the parent type
# add parent type here so we can use it as a blender custom prop
# not making it hidden, so it will get added on send as i think it might be helpful? can reconsider
converted = []
for item in elements:
item.parent_speckle_type = speckle_object.speckle_type
blender_object = convert_to_native(item)
if isinstance(blender_object, list):
converted.extend(blender_object)
else:
add_custom_properties(speckle_object, blender_object)
converted.append(blender_object)
return converted
units = getattr(speckle_object, "units", None)
if units:
scale = get_scale_length(units) / bpy.context.scene.unit_settings.scale_length
try:
if speckle_type is Mesh:
obj_data = mesh_to_native(speckle_object, name=speckle_name, scale=scale)
elif speckle_type in SUPPORTED_CURVES:
obj_data = icurve_to_native(speckle_object, name=speckle_name, scale=scale)
elif speckle_type is Transform:
obj_data = transform_to_native(speckle_object, scale=scale)
elif speckle_type is BlockDefinition:
obj_data = block_def_to_native(speckle_object, scale=scale)
elif speckle_type is BlockInstance:
obj_data = block_instance_to_native(speckle_object, scale=scale)
else:
_report(f"Unsupported type {speckle_type}")
return None
except Exception as ex: # conversion error
_report(f"Error converting {speckle_object} \n{ex}")
return None
if speckle_name in bpy.data.objects.keys():
blender_object = bpy.data.objects[speckle_name]
blender_object.data = (
obj_data.data if isinstance(obj_data, bpy_types.Object) else obj_data
)
blender_object.matrix_world = (
blender_object.matrix_world
if speckle_type is BlockInstance
else mathutils.Matrix()
)
if hasattr(obj_data, "materials"):
blender_object.data.materials.clear()
else:
blender_object = (
obj_data
if isinstance(obj_data, bpy_types.Object)
else bpy.data.objects.new(speckle_name, obj_data)
)
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)
return blender_object
def mesh_to_native(speckle_mesh, name, scale=1.0):
if name in bpy.data.meshes.keys():
blender_mesh = bpy.data.meshes[name]
else:
blender_mesh = bpy.data.meshes.new(name=name)
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 line_to_native(speckle_curve, blender_curve, scale):
line = blender_curve.splines.new("POLY")
line.points.add(1)
line.points[0].co = (
float(speckle_curve.start.x) * scale,
float(speckle_curve.start.y) * scale,
float(speckle_curve.start.z) * scale,
1,
)
if speckle_curve.end:
line.points[1].co = (
float(speckle_curve.end.x) * scale,
float(speckle_curve.end.y) * scale,
float(speckle_curve.end.z) * scale,
1,
)
return line
def polyline_to_native(scurve, bcurve, scale):
# value = find_key_case_insensitive(scurve, "value")
value = scurve.value
if value:
N = 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
def nurbs_to_native(scurve, bcurve, scale):
# points = find_key_case_insensitive(scurve, "points")
points = scurve.points
if points:
N = 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
def arc_to_native(rcurve, bcurve, scale):
# TODO: improve Blender representation of arc
plane = rcurve.plane
if not plane:
return
normal = mathutils.Vector([plane.normal.x, plane.normal.y, plane.normal.z])
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
def polycurve_to_native(scurve, bcurve, scale):
"""
Convert Polycurve object
"""
segments = scurve.segments
curves = []
for seg in segments:
speckle_type = type(seg)
if speckle_type in SUPPORTED_CURVES:
curves.append(icurve_to_native_spline(seg, bcurve, scale=scale))
else:
_report(f"Unsupported curve type: {speckle_type}")
return curves
def icurve_to_native_spline(speckle_curve, blender_curve, scale=1.0):
curve_type = type(speckle_curve)
if curve_type is Line:
return line_to_native(speckle_curve, blender_curve, scale)
if curve_type is Polyline:
return polyline_to_native(speckle_curve, blender_curve, scale)
if curve_type is Curve:
return nurbs_to_native(speckle_curve, blender_curve, scale)
if curve_type is Polycurve:
return polycurve_to_native(speckle_curve, blender_curve, scale)
if curve_type is Arc:
return arc_to_native(speckle_curve, blender_curve, scale)
def icurve_to_native(speckle_curve, name=None, scale=1.0):
curve_type = type(speckle_curve)
if curve_type not in SUPPORTED_CURVES:
_report(f"Unsupported curve type: {curve_type}")
return None
name = name or f"{curve_type} -- {speckle_curve.id}"
blender_curve = (
bpy.data.curves[name]
if name in bpy.data.curves.keys()
else bpy.data.curves.new(name, type="CURVE")
)
blender_curve.dimensions = "3D"
blender_curve.resolution_u = 12
icurve_to_native_spline(speckle_curve, blender_curve, scale)
return blender_curve
def transform_to_native(transform: Transform, scale=1.0):
mat = mathutils.Matrix(
[
transform.value[:4],
transform.value[4:8],
transform.value[8:12],
transform.value[12:16],
]
)
# scale the translation
for i in range(3):
mat[i][3] *= scale
return mat
def block_def_to_native(definition: BlockDefinition, scale=1.0):
native_def = bpy.data.collections.get(definition.name)
if native_def:
return native_def
native_def = bpy.data.collections.new(definition.name)
native_def["applicationId"] = definition.applicationId
for geo in definition.geometry:
b_obj = convert_to_native(geo)
if b_obj:
native_def.objects.link(
b_obj
if isinstance(b_obj, bpy_types.Object)
else bpy.data.objects.new(b_obj.name, b_obj)
)
return native_def
def block_instance_to_native(instance: BlockInstance, scale=1.0):
"""
Convert BlockInstance to native
"""
name = f"{getattr(instance, 'name', None) or instance.blockDefinition.name} -- {instance.id}"
native_def = block_def_to_native(instance.blockDefinition, scale)
native_instance = bpy.data.objects.new(name, None)
add_custom_properties(instance, native_instance)
native_instance["name"] = getattr(instance, 'name', None) or instance.blockDefinition.name
# hide the instance axes so they don't clutter the viewport
native_instance.empty_display_size = 0
native_instance.instance_collection = native_def
native_instance.instance_type = "COLLECTION"
native_instance.matrix_world = transform_to_native(instance.transform, scale)
return native_instance
+321
View File
@@ -0,0 +1,321 @@
import bpy
from specklepy.objects.geometry import Mesh, Curve, Interval, Box, Point, Polyline
from specklepy.objects.other import *
from bpy_speckle.functions import _report
from bpy_speckle.convert.util import (
get_blender_custom_properties,
make_knots,
to_argb_int,
)
UNITS = "m"
CAN_CONVERT_TO_SPECKLE = ("MESH", "CURVE", "EMPTY")
def convert_to_speckle(blender_object, scale, desgraph=None):
blender_type = blender_object.type
if blender_type not in CAN_CONVERT_TO_SPECKLE:
return
speckle_objects = []
speckle_material = material_to_speckle(blender_object)
if desgraph:
blender_object = blender_object.evaluated_get(desgraph)
converted = None
if blender_type == "MESH":
converted = mesh_to_speckle(blender_object, blender_object.data, scale)
elif blender_type == "CURVE":
converted = icurve_to_speckle(blender_object, blender_object.data, scale)
elif blender_type == "EMPTY":
converted = empty_to_speckle(blender_object, scale)
if not converted:
return None
if isinstance(converted, list):
speckle_objects.extend([c for c in converted if c != None])
else:
speckle_objects.append(converted)
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
if blender_type != "EMPTY":
so.properties["transform"] = transform_to_speckle(
blender_object.matrix_world
)
return speckle_objects
def mesh_to_speckle(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]
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:
n = len(f)
if n == 3:
sm.faces.append(0)
elif n == 4:
sm.faces.append(1)
else:
sm.faces.append(n)
sm.faces.extend(f)
return [sm]
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.extend(
(
tuple(matrix @ spline.bezier_points[-1].handle_right * scale),
tuple(matrix @ spline.bezier_points[0].handle_left * scale),
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 = make_knots(spline)
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=bool(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 icurve_to_speckle(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 = mesh_to_speckle(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 ngons_to_speckle_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
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]
if not blender_mat:
return
speckle_mat = RenderMaterial()
speckle_mat.name = blender_mat.name
if blender_mat.use_nodes is True and blender_mat.node_tree.nodes.get(
"Principled BSDF"
):
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 transform_to_speckle(blender_transform, scale=1.0):
units = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
value = [y for x in blender_transform for y in x]
# scale the translation
for i in (3, 7, 11):
value[i] *= scale
return Transform(value=value, units=units)
def block_def_to_speckle(blender_definition, scale=1.0):
geometry = []
for geo in blender_definition.objects:
geometry.extend(convert_to_speckle(geo, scale))
block_def = BlockDefinition(
units=UNITS,
name=blender_definition.name,
geometry=geometry,
basePoint=Point(units=UNITS),
)
blender_props = get_blender_custom_properties(blender_definition)
block_def.applicationId = blender_props.pop("applicationId", None)
return block_def
def block_instance_to_speckle(blender_instance, scale=1.0):
return BlockInstance(
blockDefinition=block_def_to_speckle(
blender_instance.instance_collection, scale
),
transform=transform_to_speckle(blender_instance.matrix_world),
name=blender_instance.name,
units=UNITS,
)
def empty_to_speckle(blender_object, scale=1.0):
# probably an instance collection (block) so let's try it
try:
geo = blender_object.instance_collection.objects.items()
return block_instance_to_speckle(blender_object, scale)
except AttributeError as err:
_report(
f"No instance collection found in empty. Skipping object {blender_object.name}"
)
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
-226
View File
@@ -1,226 +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
def export_curve(blender_object, data, scale=1.0):
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)
unit_system = bpy.context.scene.unit_settings.system
for spline in data.splines:
if spline.type == "BEZIER":
degree = 3
closed = spline.use_cyclic_u
points = []
for i, bp in enumerate(spline.bezier_points):
if i > 0:
points.append(tuple(mat @ bp.handle_left * scale))
points.append(tuple(mat @ bp.co * scale))
if i < len(spline.bezier_points) - 1:
points.append(tuple(mat @ bp.handle_right * scale))
if closed:
points.append(
tuple(mat @ spline.bezier_points[-1].handle_right * scale)
)
points.append(tuple(mat @ spline.bezier_points[0].handle_left * scale))
points.append(tuple(mat @ 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), 1):
knots[i] = i // 3
length = spline.calc_length()
domain = Interval(
start=0, end=length, totalChildrenCount=0, applicationId="Blender"
)
bezier = Curve(
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="m" if unit_system == "METRIC" else "ft",
bbox=Box(area=0.0, volume=0.0),
applicationId="Blender",
)
curves.append(bezier)
elif spline.type == "NURBS":
knots = makeknots(spline)
# print("knots: {}".format(knots))
points = [tuple(mat @ 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, applicationId="Blender"
)
nurbs = Curve(
name=blender_object.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="m" if unit_system == "METRIC" else "ft",
bbox=Box(area=0.0, volume=0.0),
applicationId="Blender",
)
curves.append(nurbs)
elif spline.type == "POLY":
points = [tuple(mat @ pt.co.xyz * scale) for pt in spline.points]
length = spline.calc_length()
domain = Interval(
start=0, end=length, totalChildrenCount=0, applicationId="Blender"
)
poly = Polyline(
name=blender_object.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="m" if unit_system == "METRIC" else "ft",
applicationId="Blender",
)
curves.append(poly)
return curves
def export_ngons_as_polylines(blender_object, data, scale=1.0):
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, applicationId="Blender")
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="m" if unit_system == "METRIC" else "ft",
applicationId="Blender",
)
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
-41
View File
@@ -1,41 +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=[],
units="m" if unit_system == "METRIC" else "ft",
bbox=Box(area=0.0, volume=0.0),
applicationId="Blender",
)
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]
+291 -1
View File
@@ -1,4 +1,24 @@
from typing import Tuple
import bpy, struct, idprop
from specklepy.objects.base import Base
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
from bpy_speckle.functions import _report
IGNORED_PROPERTY_KEYS = {
"id",
"elements",
"displayMesh",
"displayValue",
"speckle_type",
"parameters",
"faces",
"colors",
"vertices",
"renderMaterial",
"textureCoordinates",
"totalChildrenCount"
}
def to_rgba(argb_int: int) -> Tuple[float]:
@@ -13,7 +33,277 @@ def to_rgba(argb_int: int) -> Tuple[float]:
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 = diffuse_colour[-1:] + diffuse_colour[:3]
diffuse_colour = [int(val * 255) for val in diffuse_colour]
return int.from_bytes(diffuse_colour, byteorder="big", signed=True)
def add_custom_properties(speckle_object, blender_object):
if blender_object is None:
return
serializer = BaseObjectSerializer()
blender_object["_speckle_type"] = type(speckle_object).__name__
app_id = getattr(speckle_object, "applicationId", None)
if app_id:
blender_object["applicationId"] = speckle_object.applicationId
keys = speckle_object.get_dynamic_member_names() if "Geometry" in speckle_object.speckle_type else (set(speckle_object.get_member_names()) - IGNORED_PROPERTY_KEYS)
for key in keys:
val = getattr(speckle_object, key, None)
if val is None:
continue
if isinstance(val, (int, str, float)):
blender_object[key] = val
elif key == "properties" and isinstance(val, Base):
val["applicationId"] = None
add_custom_properties(val, blender_object)
elif isinstance(val, list):
items = [item for item in val if not isinstance(item, Base)]
if items:
blender_object[key] = items
elif isinstance(val,dict):
for (k,v) in val.items():
if not isinstance(v, Base):
blender_object[k] = v
def add_blender_material(speckle_object, 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
speckle_mat = getattr(
speckle_object,
"renderMaterial",
getattr(speckle_object, "@renderMaterial", None),
)
if not speckle_mat:
return
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 add_vertices(speckle_mesh, blender_mesh, scale=1.0):
sverts = speckle_mesh.vertices
if sverts and len(sverts) > 0:
for i in range(0, len(sverts), 3):
blender_mesh.verts.new(
(
float(sverts[i]) * scale,
float(sverts[i + 1]) * scale,
float(sverts[i + 2]) * scale,
)
)
blender_mesh.verts.ensure_lookup_table()
def add_faces(speckle_mesh, blender_mesh, smooth=False):
sfaces = speckle_mesh.faces
if sfaces and len(sfaces) > 0:
i = 0
while i < len(sfaces):
n = sfaces[i]
if n < 3:
n += 3 # 0 -> 3, 1 -> 4
i += 1
try:
f = blender_mesh.faces.new(
[blender_mesh.verts[int(x)] for x in sfaces[i : i + n]]
)
f.smooth = smooth
except Exception as e:
_report(f"Failed to create face for mesh {speckle_mesh.id} \n{e}")
i += n
blender_mesh.faces.ensure_lookup_table()
blender_mesh.verts.index_update()
def add_colors(speckle_mesh, blender_mesh):
scolors = speckle_mesh.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(blender_mesh.verts):
color_layer = blender_mesh.loops.layers.color.new("Col")
for face in blender_mesh.faces:
for loop in face.loops:
loop[color_layer] = colors[loop.vert.index]
def add_uv_coords(speckle_mesh, blender_mesh):
s_uvs = speckle_mesh.textureCoordinates
if not s_uvs:
return
try:
uv = []
if len(s_uvs) // 2 == len(blender_mesh.verts):
uv.extend(
(float(s_uvs[i]), float(s_uvs[i + 1]))
for i in range(0, len(s_uvs), 2)
)
else:
_report(
f"Failed to match UV coordinates to vert data. Blender mesh verts: {len(blender_mesh.verts)}, Speckle UVs * 2: {len(s_uvs) * 2}"
)
return
# Make UVs
uv_layer = blender_mesh.loops.layers.uv.verify()
for f in blender_mesh.faces:
for l in f.loops:
luv = l[uv_layer]
luv.uv = uv[l.vert.index]
except:
_report("Failed to decode texture coordinates.")
raise
ignored_keys = {
"id",
"speckle",
"speckle_type"
"_speckle_type",
"_speckle_name",
"_speckle_transform",
"_RNA_UI",
"elements",
"transform",
"_units",
"_chunkable",
}
def get_blender_custom_properties(obj, max_depth=1000):
if max_depth < 0:
return obj
if hasattr(obj, "keys"):
keys = set(obj.keys()) - ignored_keys
return {
key: get_blender_custom_properties(obj[key], max_depth - 1)
for key in keys
if not key.startswith("_")
}
if isinstance(obj, (list, tuple, idprop.types.IDPropertyArray)):
return [get_blender_custom_properties(o, max_depth - 1) for o in obj]
return obj
"""
Python implementation of Blender's NURBS curve generation for to Speckle conversion
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 make_knots(nu):
knots = [0.0] * (4 + macro_knotsu(nu))
flag = nu.use_endpoint_u + (nu.use_bezier_u << 1)
if nu.use_cyclic_u:
calc_knots(knots, nu.point_count_u, nu.order_u, 0)
makecyclicknots(knots, nu.point_count_u, nu.order_u)
else:
calc_knots(knots, nu.point_count_u, nu.order_u, flag)
return knots
def calc_knots(knots, point_count, order, flag):
pts_order = point_count + order
if flag == 1:
k = 0.0
for a in range(1, pts_order + 1):
knots[a - 1] = k
if a >= order and a <= point_count:
k += 1.0
elif flag == 2:
if order == 4:
k = 0.34
for a in range(pts_order):
knots[a] = math.floor(k)
k += 1.0 / 3.0
elif order == 3:
k = 0.6
for a in range(pts_order):
if a >= order and a <= point_count:
k += 0.5
knots[a] = math.floor(k)
else:
for a in range(pts_order):
knots[a] = a
def makecyclicknots(knots, point_count, order):
order2 = order - 1
if order > 2:
b = point_count + order2
for a in range(1, order2):
if knots[b] != knots[b - a]:
break
if a == order2:
knots[point_count + order - 2] += 1.0
b = order
c = point_count + order + order2
for a in range(point_count + order2, c):
knots[a] = knots[a - 1] + (knots[b] - knots[b - 1])
b -= 1
-34
View File
@@ -1,6 +1,3 @@
from specklepy.api.client import SpeckleClient
import requests
from bpy_speckle.clients import speckle_clients
"""
@@ -73,34 +70,3 @@ def _check_speckle_client_user_stream(scene):
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"])
+13 -3
View File
@@ -3,7 +3,6 @@ from .object import (
UpdateObject,
ResetObject,
DeleteObject,
UploadObject,
UploadNgonsAsPolylines,
SelectIfSameCustomProperty,
SelectIfHasCustomProperty,
@@ -17,12 +16,14 @@ from .streams import (
)
from .streams import (
UpdateGlobal,
AddStreamFromURL,
CreateStream,
CopyStreamId,
CopyCommitId,
CopyBranchName,
)
from .commit import DeleteCommit
from .misc import OpenSpeckleGuide, OpenSpeckleTutorials, OpenSpeckleForum
operator_classes = [
LoadUsers,
@@ -41,7 +42,6 @@ operator_classes.extend(
UpdateObject,
ResetObject,
DeleteObject,
UploadObject,
UploadNgonsAsPolylines,
SelectIfSameCustomProperty,
SelectIfHasCustomProperty,
@@ -49,5 +49,15 @@ operator_classes.extend(
)
operator_classes.extend(
[ViewStreamDataApi, DeleteStream, SelectOrphanObjects, UpdateGlobal, CreateStream,]
[
ViewStreamDataApi,
DeleteStream,
SelectOrphanObjects,
UpdateGlobal,
AddStreamFromURL,
CreateStream,
OpenSpeckleGuide,
OpenSpeckleTutorials,
OpenSpeckleForum,
]
)
+10 -32
View File
@@ -1,23 +1,9 @@
"""
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
import bpy
from bpy.props import BoolProperty
from bpy_speckle.functions import _check_speckle_client_user_stream
from bpy_speckle.clients import speckle_clients
@@ -32,7 +18,8 @@ class DeleteCommit(bpy.types.Operator):
bl_description = "Delete active commit permanently"
are_you_sure: BoolProperty(
name="Confirm", default=False,
name="Confirm",
default=False,
)
def draw(self, context):
@@ -66,20 +53,11 @@ class DeleteCommit(bpy.types.Operator):
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)]
branch = stream.branches[int(stream.branch)]
if len(branch.commits) < 1:
return {"CANCELLED"}
commit = branch.commits[int(branch.commit)]
deleted = client.commit.delete(stream_id=stream.id, commit_id=commit.id)
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"}
+35
View File
@@ -0,0 +1,35 @@
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"}
+10 -77
View File
@@ -2,19 +2,12 @@
Object operators
"""
import bpy, bmesh, os
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
import bpy
from bpy.props import BoolProperty, EnumProperty
from bpy_speckle.convert.to_speckle import (
convert_to_speckle,
ngons_to_speckle_polylines,
)
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
@@ -58,7 +51,7 @@ class UpdateObject(bpy.types.Operator):
stream_units
)
sm = to_speckle_object(active, scale)
sm = convert_to_speckle(active, scale)
_report("Updating object {}".format(sm["_id"]))
client.objects.update(active.speckle.object_id, sm)
@@ -99,8 +92,6 @@ class DeleteObject(bpy.types.Operator):
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:
@@ -110,15 +101,13 @@ class DeleteObject(bpy.types.Operator):
for x in res["resource"]["objects"]
if x["_id"] == active.speckle.object_id
]
if existing == None:
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"]
@@ -166,7 +155,7 @@ class UploadNgonsAsPolylines(bpy.types.Operator):
stream.units
)
sp = export_ngons_as_polylines(active, scale)
sp = ngons_to_speckle_polylines(active, scale)
if sp is None:
return {"CANCELLED"}
@@ -175,17 +164,13 @@ class UploadNgonsAsPolylines(bpy.types.Operator):
for polyline in sp:
res = client.objects.create([polyline])
print(res)
if res == None:
if res is None:
_report(client.me)
continue
placeholders.extend(res)
# polyline['_id'] = res['_id']
# placeholders.append({'type':'Placeholder', '_id':res['_id']})
if len(placeholders) < 1:
if not placeholders:
return {"CANCELLED"}
# Get list of existing objects in stream and append new object to list
@@ -222,58 +207,6 @@ class UploadNgonsAsPolylines(bpy.types.Operator):
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 == 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"]
+223 -80
View File
@@ -1,43 +1,45 @@
"""
Stream operators
"""
import bpy, bmesh, os
from itertools import chain
from typing import Dict
import bpy
from specklepy.api.models import Commit
import webbrowser
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
)
from bpy_speckle.convert.to_native import can_convert_to_native, convert_to_native
from bpy_speckle.convert.to_speckle import (
convert_to_speckle,
ngons_to_speckle_polylines,
)
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.convert import get_speckle_subobjects
from bpy_speckle.clients import speckle_clients
from bpy_speckle.operators.users import add_user_stream
from specklepy.api import operations
from specklepy.api.wrapper 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):
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] = [item for item in value if isinstance(item, Base)]
collections[name] = get_objects_nested_lists(value, col)
if isinstance(value, Base):
col = create_collection(name)
collections[name] = get_objects_collections_recursive(value, col)
@@ -45,17 +47,48 @@ def get_objects_collections(base):
return collections
def get_objects_collections_recursive(base, parent_col=None):
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
if can_convert_to_native(base):
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 name == "parameters" and "Revit" in base.speckle_type:
continue
if isinstance(value, list):
for item in value:
if isinstance(item, Base):
objects.append(item)
objects.extend(item for item in value if isinstance(item, Base))
if isinstance(value, Base):
col = create_collection(name)
parent_col.children.link(col)
col = parent_col.children.get(name)
if not col:
col = create_collection(name)
try:
parent_col.children.link(col)
except:
_report(
f"Problem linking collection {col.name} to parent {parent_col.name}; skipping"
)
objects.append({name: get_objects_collections_recursive(value, col)})
return objects
@@ -70,60 +103,73 @@ def bases_to_native(context, collections, scale, stream_id, func=None):
elif isinstance(objects, list):
for obj in objects:
if isinstance(obj, dict):
bases_to_native(context, obj, scale, stream_id)
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:
new_objects = [from_speckle_object(obj, scale)]
_report(
f"Something went wrong when receiving collection: {col_name}"
)
if hasattr(obj, "properties") and obj.properties is not None:
new_objects.extend(
get_speckle_subobjects(obj.properties, scale, obj.id)
)
elif isinstance(obj, dict) and "properties" in obj.keys():
new_objects.extend(
get_speckle_subobjects(obj["properties"], scale, obj["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)
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 = convert_to_native(base)
if not isinstance(new_objects, list):
new_objects = [new_objects]
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
_report(f"Script '{func.__module__}' returned None.")
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 = f"{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]
@@ -177,9 +223,9 @@ def create_nested_hierarchy(base, hierarchy, objects):
child = child[name]
# TODO: what do we call this attribute?
if not hasattr(child, "objects"):
child["objects"] = []
child["objects"].extend(objects)
if not hasattr(child, "@objects"):
child["@objects"] = []
child["@objects"].extend(objects)
return base
@@ -205,7 +251,7 @@ class ReceiveStreamObjects(bpy.types.Operator):
client = speckle_clients[int(context.scene.speckle.active_user)]
stream = client.stream.get(id=bstream.id)
stream = client.stream.get(id=bstream.id, branch_limit=20)
if stream.branches.totalCount < 1:
return {"CANCELLED"}
@@ -217,13 +263,19 @@ class ReceiveStreamObjects(bpy.types.Operator):
bbranch = bstream.branches[int(bstream.branch)]
if branch.commits.totalCount < 1:
print("No commits found. Probably an empty stream.")
_report("No commits found. Probably an empty stream.")
return {"CANCELLED"}
commit = branch.commits.items[int(bbranch.commit)]
transport = ServerTransport(client, stream.id)
transport = ServerTransport(stream.id, client)
stream_data = operations.receive(commit.referencedObject, transport)
client.commit.received(
bstream.id,
commit.id,
source_application="blender",
message="received commit from Speckle Blender",
)
"""
Create or get Collection for stream objects
@@ -234,7 +286,6 @@ class ReceiveStreamObjects(bpy.types.Operator):
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
@@ -243,8 +294,10 @@ class ReceiveStreamObjects(bpy.types.Operator):
bpy.context.scene.collection.children.link(col)
for child_col in collections.keys():
col.children.link(bpy.data.collections[child_col])
try:
col.children.link(bpy.data.collections[child_col])
except:
pass
"""
Set conversion scale from stream units
"""
@@ -280,6 +333,7 @@ class SendStreamObjects(bpy.types.Operator):
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.",
@@ -289,6 +343,7 @@ class SendStreamObjects(bpy.types.Operator):
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
@@ -359,9 +414,15 @@ class SendStreamObjects(bpy.types.Operator):
ngons = obj.get("speckle_ngons_as_polylines", False)
if ngons:
converted = export_ngons_as_polylines(obj, scale)
converted = ngons_to_speckle_polylines(obj, scale)
else:
converted = to_speckle_object(obj, scale)
converted = convert_to_speckle(
obj,
scale,
bpy.context.evaluated_depsgraph_get()
if self.apply_modifiers
else None,
)
if not converted:
continue
@@ -378,14 +439,18 @@ class SendStreamObjects(bpy.types.Operator):
hierarchy = get_collection_hierarchy(collection)
create_nested_hierarchy(base, hierarchy, objects)
transport = ServerTransport(client, stream.id)
transport = ServerTransport(stream.id, client)
obj_id = operations.send(base, [transport])
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()
@@ -415,6 +480,84 @@ class ViewStreamDataApi(bpy.types.Operator):
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, branch_limit=20)
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
@@ -425,7 +568,7 @@ class CreateStream(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create new stream"
stream_name: StringProperty(name="Stream name", default="SpeckleStream")
stream_name: StringProperty(name="Stream name")
stream_description: StringProperty(
name="Stream description", default="This is a Blender stream."
)
+45 -48
View File
@@ -1,23 +1,12 @@
"""
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
import bpy
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
from specklepy.api.credentials import get_local_accounts
from datetime import datetime
class LoadUsers(bpy.types.Operator):
@@ -49,12 +38,17 @@ class LoadUsers(bpy.types.Operator):
user.company = profile.userInfo.company or ""
user.authToken = profile.token
try:
client = SpeckleClient(host=profile.serverInfo.url, use_ssl=True)
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()
@@ -65,6 +59,38 @@ class LoadUsers(bpy.types.Operator):
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 or ""
commit.author_name = c.authorName
commit.author_id = c.authorId
commit.created_at = datetime.strftime(c.createdAt, "%Y-%m-%d %H:%M:%S.%f%Z")
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
@@ -83,9 +109,9 @@ class LoadUserStreams(bpy.types.Operator):
client = speckle_clients[int(context.scene.speckle.active_user)]
try:
streams = client.stream.list()
streams = client.stream.list(stream_limit=20)
except Exception as e:
_report("Failed to retrieve streams: {}".format(e))
_report(f"Failed to retrieve streams: {e}")
return
if not streams:
_report("Failed to retrieve streams.")
@@ -93,40 +119,11 @@ class LoadUserStreams(bpy.types.Operator):
user.streams.clear()
streams = sorted(streams, key=lambda x: x.name, reverse=False)
default_units = "Meters"
for s in streams:
stream = user.streams.add()
stream.name = s.name
stream.id = s.id
stream.description = s.description
sstream = client.stream.get(id=s.id)
if not sstream.branches:
continue
for b in sstream.branches.items:
branch = stream.branches.add()
branch.name = b.name
if not b.commits:
continue
for c in b.commits.items:
commit = branch.commits.add()
commit.id = 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"):
stream.units = s.baseProperties.units
else:
stream.units = default_units
sstream = client.stream.get(id=s.id, branch_limit=20)
add_user_stream(user, sstream)
bpy.context.view_layer.update()
-2
View File
@@ -1,9 +1,7 @@
"""
Addon properties
"""
import bpy
from bpy.props import BoolProperty
class SpeckleAddonPreferences(bpy.types.AddonPreferences):
-2
View File
@@ -1,9 +1,7 @@
"""
Collection properties
"""
import bpy
from bpy.props import StringProperty, BoolProperty, EnumProperty
class SpeckleCollectionSettings(bpy.types.PropertyGroup):
-2
View File
@@ -1,9 +1,7 @@
"""
Object properties
"""
import bpy
from bpy.props import StringProperty, BoolProperty, EnumProperty
class SpeckleObjectSettings(bpy.types.PropertyGroup):
+6 -6
View File
@@ -1,7 +1,6 @@
"""
Scene properties
"""
import bpy
from bpy.props import (
StringProperty,
@@ -12,7 +11,6 @@ from bpy.props import (
IntProperty,
PointerProperty,
)
from specklepy.api.client import SpeckleClient
class SpeckleSceneObject(bpy.types.PropertyGroup):
@@ -48,10 +46,11 @@ class SpeckleBranchObject(bpy.types.PropertyGroup):
class SpeckleStreamObject(bpy.types.PropertyGroup):
def get_branches(self, context):
if len(self.branches) > 0:
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)]
@@ -81,9 +80,10 @@ class SpeckleUserObject(bpy.types.PropertyGroup):
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
return [
("<none>", "<none>", "<none>"),
*[(t.name, t.name, t.name) for t in bpy.data.texts],
]
streams: EnumProperty(
name="Available streams",
+2 -1
View File
@@ -5,6 +5,7 @@ from .view3d import (
VIEW3D_PT_SpeckleUser,
VIEW3D_PT_SpeckleStreams,
VIEW3D_PT_SpeckleActiveStream,
VIEW3D_PT_SpeckleHelp,
)
ui_classes = [
@@ -13,5 +14,5 @@ ui_classes = [
VIEW3D_PT_SpeckleActiveStream,
VIEW3D_UL_SpeckleUsers,
VIEW3D_UL_SpeckleStreams,
# OBJECT_PT_speckle,
VIEW3D_PT_SpeckleHelp,
]
+30 -12
View File
@@ -12,7 +12,7 @@ from bpy.props import (
EnumProperty,
)
import datetime
from datetime import datetime
"""
Compatibility
@@ -84,9 +84,8 @@ class VIEW3D_UL_SpeckleStreams(bpy.types.UIList):
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),
text=f"{stream.name} ({stream.id})",
translate=False,
icon_value=0,
)
@@ -118,7 +117,6 @@ class VIEW3D_PT_SpeckleUser(bpy.types.Panel):
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))
@@ -144,11 +142,11 @@ class VIEW3D_PT_SpeckleStreams(bpy.types.Panel):
col.label(text="No stream data.")
else:
user = speckle.users[int(speckle.active_user)]
# col.label(text="Streams")
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")
@@ -179,7 +177,7 @@ class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
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.label(text=f"{stream.name} ({stream.id})")
row.operator("speckle.stream_copy_id", text="", icon="COPY_ID")
col.separator()
@@ -207,13 +205,11 @@ class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
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)
dt = datetime.strptime(
commit.created_at, "%Y-%m-%d %H:%M:%S.%f%Z"
)
col.label(text=f"{dt.ctime()}")
col.label(text=f"{commit.author_name} ({commit.author_id})")
col.label(text=commit.source_application)
else:
col.label(text="No branches found!")
@@ -253,3 +249,25 @@ class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
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")
+66
View File
@@ -0,0 +1,66 @@
import re
import sys
def patch_connector(tag):
"""Patches the connector version within the connector init file"""
bpy_file = "bpy_speckle/__init__.py"
tag = tag.split(".")
with open(bpy_file, "r") as file:
lines = file.readlines()
for (index, line) in enumerate(lines):
if '"version":' in line:
lines[index] = f' "version": ({tag[0]}, {tag[1]}, {tag[2]}),\n'
print(f"Patched connector version number in {bpy_file}")
break
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:
print(get_specklepy_version())
return
tag = sys.argv[1]
if not re.match(r"[0-9]+(\.[0-9]+)*$", tag):
raise ValueError(f"Invalid tag provided: {tag}")
print(f"Patching version: {tag}")
patch_connector(tag)
patch_installer(tag)
if __name__ == "__main__":
main()
Generated
+987 -369
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -6,14 +6,15 @@ authors = ["izzy lyseggen <izzy.lyseggen@gmail.com>"]
license = "Apache-2.0"
[tool.poetry.dependencies]
python = ">=3.7,<3.8"
specklepy = "^2.2.2"
python = ">=3.7,<3.10"
specklepy = "^2.6.4"
[tool.poetry.dev-dependencies]
devtools = "^0.6.1"
numpy = "^1.20.2"
bpy = "^2.82.1"
bpy-build = "^2.1.0"
fake-bpy-module-latest = "^20220401"
black = "^21.12b0"
pylint = "^2.12.2"
[build-system]
requires = ["poetry-core>=1.0.0"]