Compare commits

...

72 Commits

Author SHA1 Message Date
JR-Morgan 8411c01f1b Merge branch 'main' of https://github.com/specklesystems/speckle-blender 2023-02-02 15:06:25 +00:00
JR-Morgan 63b82a30cb bumped specklepy 2.12.0 2023-02-02 15:05:59 +00:00
Jedd Morgan 0b6e39cf38 Merge pull request #147 from specklesystems/jrm/converter/multi-mesh
Fixed misalgined uv error message
2023-02-02 13:56:21 +00:00
JR-Morgan b7efcec517 uv experimentation 2023-02-02 13:45:57 +00:00
Jedd Morgan 0ab0096aac Merge pull request #142 from specklesystems/jrm/converter/multi-mesh
Jrm/converter/multi mesh
2023-02-01 14:00:11 +00:00
JR-Morgan cbd8fc99bb Merge remote-tracking branch 'origin' into jrm/converter/multi-mesh 2023-02-01 14:00:00 +00:00
JR-Morgan 2a9287e762 final polish 2023-02-01 13:52:20 +00:00
Jedd Morgan 98c70f237c Merge pull request #146 from specklesystems/gergo/alwaysInstall
fix(installer): make sure to always install specklepy dependencies
2023-02-01 13:10:22 +00:00
Gergő Jedlicska 048047cf05 fix(installer): make sure to always install specklepy dependencies
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2023-01-31 10:57:05 +02:00
Jedd Morgan 6118215cae Experimentation with receive modes 2023-01-26 14:58:53 +00:00
Jedd Morgan 8e06432fe6 relock poetry 2023-01-23 23:50:42 +00:00
Jedd Morgan cb8620ff8a Merge branch 'main' into jrm/converter/multi-mesh
Signed-off-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2023-01-23 23:49:12 +00:00
Jedd Morgan e0eddea8ab Fixed regression of null elements 2023-01-23 19:59:49 +00:00
Jedd Morgan 8497d0c195 Merge pull request #144 from specklesystems/jrm/ifc/fix
fixed IFC commits with empty lists
2023-01-20 17:00:04 +00:00
Jedd Morgan 1ce394c08f fixed issue with IFC commits with empty lists 2023-01-20 15:31:44 +00:00
Jedd Morgan aabbf87dda fix(ci): signing cert for iss 2023-01-16 17:12:39 +00:00
Jedd Morgan 7e31787d37 Updated config.yml 2023-01-16 16:29:13 +00:00
Jedd Morgan f1259587fd Bumped speckle py to 2.11.4 2023-01-16 15:00:10 +00:00
Jedd Morgan 7f913e3af0 Merge pull request #141 from specklesystems/gergo/ciContextUpdate
clone ci tools with ssh, use circleci context for env vars
2023-01-16 13:42:24 +00:00
Jedd Morgan 5433c34a4d Merge pull request #140 from specklesystems/jrm/manual-installer
Manual Installer
2023-01-16 13:39:50 +00:00
Alan Rynne 2c52e93660 ci: Updated github actions to use new actions repo 2023-01-09 20:41:22 +01:00
Gergő Jedlicska 3c3b24cf98 use innosetup context in the windows build 2023-01-06 14:01:56 +01:00
Gergő Jedlicska 3012b0ebcb clone ci tools with ssh, use circleci context for env vars 2023-01-06 12:17:19 +01:00
Jedd Morgan 35b96eaa4e feat(converter): meshes with multiple materials ToSpeckle 2022-12-19 13:42:35 +00:00
Jedd Morgan c229bb2414 materials 2022-12-15 18:04:31 +00:00
Jedd Morgan 318bd086c0 feat(converter): convert displayValues ToNative as a single mesh 2022-12-15 16:00:44 +00:00
Jedd Morgan e3eb29daa4 fiX(ci): fixed mistake in manual installer ci 2022-12-15 14:17:29 +00:00
Jedd Morgan 3359c8f275 feat(ci): Added manual instaler deploy 2022-12-14 17:01:45 +00:00
Gergő Jedlicska cfc5007d00 update tag fiter for numbered alpha and beta 2022-12-14 11:20:00 +01:00
Jedd Morgan 0a630457be Jrm/accounts reload (#138)
* add new installer mechanism

* ci(circleci): add new package connector step

* ci(circleci): install dependencies before packaging

* chore(deps): remove local specklepy dep

* chore(deps): relock again

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

* Zero restart installation

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

* ci(circleci): add new package connector step

* ci(circleci): install dependencies before packaging

* chore(deps): remove local specklepy dep

* chore(deps): relock again

* ci(circleci): rewrite full ci WIP

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

* ci(circleci): updates

* ci(circleci): fix naming

* ci(circleci): update filters

* ci(circleci): update semver location

* ci(circleci): update dependency graph

* getting the specific installer branch

* get proper ci tools

* force remove new lines from installer tags

* fix installer patch pathing

* properly saving packaged zip

* add python to dotnet installer

* add zip again

* only zip the published isntaller

* publish only usefull stuff

* ci(circleci): update details in deploy

* ci(circleci): fix filters

* ci(cirleci): fix mac  build triggers

* make sure semver is set

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

* ci(circleci): fix missing semver in files

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

* ci(circleci): fix persist to workspace pathing

* fix zipping dir

* mkdir for the zip folder

* mkdir -p

* force copy

* copy 101

* mkdir 101

* top level cp operation

* pipe the zip

* no CD

* only parameter

* build for osx 13 for arm

* moving back to a mac build machine

* fixc arm mac runtime

* remove old linux installer build

* allow alpha numbered version

* remove ci tools branch
2022-12-14 10:55:44 +01:00
Jedd Morgan 977994e141 feat(converter): initial refactor of mesh traversal 2022-12-13 19:02:43 +00:00
Jedd Morgan d9cbc80ee7 Zero restart installation 2022-12-09 15:00:44 +00:00
Jedd Morgan 45038fad79 feat(connector): Added reload operation to user account selection 2022-12-09 03:26:21 +00:00
Gergő Jedlicska cda621d735 chore(deps): relock again 2022-12-07 14:46:09 +01:00
Gergő Jedlicska 2d052d1379 chore(deps): remove local specklepy dep 2022-12-07 14:44:20 +01:00
Gergő Jedlicska 46ce2bc0df ci(circleci): install dependencies before packaging 2022-12-07 14:38:06 +01:00
Gergő Jedlicska 80e2216aa0 ci(circleci): add new package connector step 2022-12-07 14:32:48 +01:00
Gergő Jedlicska 10d69aa44b add new installer mechanism 2022-12-07 13:52:22 +01:00
Jedd Morgan b1481bd259 Merge pull request #135 from specklesystems/jrm/2.10-fixes
Jrm/2.10 fixes
2022-11-30 20:40:01 +00:00
Jedd Morgan e19fc9ef4e fix(deployment): potential fix for mac releases 2022-11-30 20:38:43 +00:00
Jedd Morgan 448eb856b2 Bumped dependencies 2022-11-30 20:36:07 +00:00
Jedd Morgan 06a7d416c7 Merge pull request #134 from specklesystems/jrm/2.10-fixes
fix(converter): fixed issue with meshes with very high number of vert…
2022-11-30 20:32:42 +00:00
Jedd Morgan 0753436899 fix(converter): fixed issue with meshes with very high number of verts taking an eternity to send
Changed from using sum to extend list functions
2022-11-29 23:54:28 +00:00
Jedd Morgan 40e0a49b11 Merge pull request #130 from specklesystems/jrm/clean-mesh-fix
fix(receive): Fixed issue with Clean Mesh creating duplicated collection
2022-11-17 16:05:56 +00:00
JR-Morgan b17b9c9de4 fix(receive): Fixed issue with Clean Mesh creating duplicated collection 2022-11-17 14:45:17 +00:00
Jedd Morgan f035111ffa Merge pull request #128 from specklesystems/fix-py39
Fixed issue with connector failing to load on Blender 2.93
2022-11-11 12:09:55 +00:00
JR-Morgan fa073f754f Merge remote-tracking branch 'origin' into fix-py39 2022-11-11 02:23:50 +00:00
Jedd Morgan 83919375f9 Merge pull request #129 from specklesystems/jrm/ci/macos
MacOS CI
2022-11-11 02:22:49 +00:00
JR-Morgan defb11bc89 fix: Fixed issue with connector on Blender 2.93 failing due to accidental use of unsupported python features 2022-11-11 02:13:19 +00:00
JR-Morgan 548b3ad352 CI fix for macos slugs 2022-10-24 21:48:25 +01:00
JR-Morgan d2deecf099 added deploy steps for macos 2022-10-24 21:27:38 +01:00
JR-Morgan fbda0110cd fix(connector): 2.9.0 update 2022-10-11 18:43:36 +01:00
Jedd Morgan ba9ad9ac07 Merge pull request #118 from specklesystems/gergo/workflowMetrics
add workflow tracking to receive operations
2022-10-11 17:56:40 +01:00
Jedd Morgan d13db2b44a Merge pull request #117 from specklesystems/jrm/receive-script-update
Jrm/receive script update
2022-10-11 17:54:33 +01:00
JR-Morgan 2c127c85f3 feat: Updated recieve scripts to use context rather than scene 2022-10-07 16:03:10 +01:00
Gergő Jedlicska cc099c0ff1 add workflow tracking to receive operations 2022-10-07 16:08:27 +02:00
JR-Morgan 448ab70c3b Merge remote-tracking branch 'origin/jrm/clean-mesh' into jrm/receive-script-update 2022-10-04 18:28:57 +01:00
JR-Morgan 020eba2727 Merge remote-tracking branch 'origin/main' into jrm/receive-script-update 2022-10-04 17:53:01 +01:00
JR-Morgan d8117e2c30 feat(converter): Updated Recieve Script callback functions to allow for access to Base object, and for callback on ToNative completion 2022-10-04 17:50:57 +01:00
Matteo Cominetti 57a6d88e6b ci 2022-09-27 10:27:50 +01:00
Matteo Cominetti 74cb3e5f85 ci: change shell 2022-09-27 10:15:58 +01:00
Matteo Cominetti d0aeecc863 Update README.md 2022-09-27 10:08:55 +01:00
Matteo Cominetti 2803308c5e Update README.md 2022-09-27 10:08:06 +01:00
Matteo Cominetti 326f04f67d ci: adds signtool param 2022-09-27 10:07:01 +01:00
JR-Morgan 68972ba8f9 Merge remote-tracking branch 'origin' into jrm/clean-mesh 2022-09-23 13:58:07 +01:00
Alan Rynne 73a028b56f Merge pull request #112 from specklesystems/ci/force-run
ci: Activated both mac runs on build
2022-09-23 14:56:02 +02:00
Alan Rynne 33890ef0ee fix: went too far 2022-09-23 14:28:51 +02:00
Alan Rynne 53fe676ab6 ci: Prettified steps 2022-09-23 14:27:50 +02:00
Alan Rynne f027c7eca4 ci: Activated both mac runs on build 2022-09-23 14:11:19 +02:00
JR-Morgan ea2b6dfb0e feat(connector): Added clean mesh option to recieve geometry with limited disolve + merge verts 2022-09-23 12:36:55 +01:00
JR-Morgan 83610cec38 Added type hinting to codebase 2022-09-21 22:22:07 +01:00
22 changed files with 1738 additions and 1633 deletions
+230 -243
View File
@@ -1,98 +1,110 @@
version: 2.1
orbs:
python: circleci/python@1.3.2
# Using windows for builds
win: circleci/windows@2.4.0
# Upload artifacts to s3
aws-s3: circleci/aws-s3@2.0.0
commands:
install-specklepy-windows: # Reusable job for installing `specklepy` for windows machines
parameters:
python-version:
type: string
default: "" # leave blank for blender v2.93
steps:
- when:
condition: << parameters.python-version >>
steps:
- run:
name: Upgrade python version << parameters.python-version >>
shell: powershell.exe
command: |
choco upgrade python --version=<< parameters.python-version >>
refreshenv
python --version
- run:
name: Install specklepy into modules directory
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
win: circleci/windows@5.0.0
jobs:
build-connector-win: # Reusable job for basic connectors
executor:
name: win/default # comes with python 3.7.3
shell: cmd.exe
parameters:
slug:
type: string
default: "blender"
installer:
type: boolean
default: false
package-connector:
docker:
- image: cimg/python:3.11.0
steps:
- checkout
- run:
name: Setup SEMVER value
command: |
SEMVER=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "0.0.0"; fi;)
echo $SEMVER > ./SEMVER
python3 patch_version.py $SEMVER
- run:
name: install dependencies
command: poetry install --only main
- run:
name: export package dependencies
command: ./export_dependencies.sh
- persist_to_workspace:
root: ./
paths:
- bpy_speckle
- patch_installer.py
- SEMVER
build-connector-zip:
docker:
- image: cimg/python:3.11.0
steps:
- attach_workspace:
at: ./
- install-specklepy-windows
- install-specklepy-windows:
python-version: "3.9.10"
- install-specklepy-windows:
python-version: "3.10.2"
- run: &restore_semver
name: Restore Semver
command: SEMVER=$(cat ./SEMVER) && echo $SEMVER
- run:
name: Patch
shell: powershell.exe
command:
| # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
$tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.0" } else { $env:CIRCLE_TAG }
$semver = if($tag.Contains('/')) {$tag.Split("/")[1] } else { $tag }
$ver = if($semver.Contains('-')) {$semver.Split("-")[0] } else { $semver }
$version = "$($ver).$($env:CIRCLE_BUILD_NUM)"
$channel = if($semver.Contains('-')) { "prerelease" } else { "latest" }
New-Item -Force "speckle-sharp-ci-tools/Installers/blender/$channel.yml" -ItemType File -Value "version: $semver"
echo $semver
python patch_version.py $semver
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\blender.iss
- when:
condition: << parameters.installer >>
steps:
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers
name: Package to Zip
command: zip -r bpy_speckle.zip bpy_speckle
- persist_to_workspace:
root: ./
paths:
- bpy_speckle.zip
get-ci-tools: # Clones our ci tools and persists them to the workspace
docker:
- image: cimg/base:2021.01
steps:
- add_ssh_keys:
fingerprints:
- "77:64:03:93:c5:f3:1d:a6:fd:bd:fb:d1:05:56:ca:e9"
- run:
name: I know Github as a host
command: |
mkdir ~/.ssh
touch ~/.ssh/known_hosts
ssh-keyscan github.com >> ~/.ssh/known_hosts
- run:
name: Clone
command: git clone git@github.com:specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
- run:
command: cd speckle-sharp-ci-tools
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools
build-connector-mac:
build-installer-win:
executor:
name: win/default
shell: cmd.exe
steps:
- attach_workspace:
at: ./
- run:
name: Patch installer
shell: powershell.exe
command: python patch_installer.py (Get-Content -Raw SEMVER)
- run:
name: Create Innosetup signing cert
shell: powershell.exe
command: |
echo $env:PFX_B64 > "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt"
certutil -decode "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt" "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.pfx"
- run:
name: Installer
shell: cmd.exe #does not work in powershell
command: speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\blender.iss /Sbyparam=$p
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers/blender/blender-*.exe
build-installer-mac:
macos:
xcode: 12.5.1
parameters:
slug:
type: string
default: "blender-mac"
installername:
type: string
default: "SpeckleBlenderInstall"
runtime:
type: string
default: "osx-x64"
installer:
type: boolean
default: false
slug:
type: string
installer_path:
type: string
default: speckle-sharp-ci-tools/Mac/SpeckleBlenderInstall
steps:
- checkout
- attach_workspace:
@@ -105,110 +117,64 @@ jobs:
- run:
name: Install dotnet
command: curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin
- run:
name: Build & Patch Version
command: |
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "0.0.0"; fi;)
SEMVER=$(echo "$TAG" | sed -e 's/[a-zA-Z-]*\///')
VER=$(echo "$SEMVER" | sed -e 's/-beta//')
VERSION=$(echo $VER.$CIRCLE_BUILD_NUM)
CHANNEL=$(if [[ "$VERSION" == *"-"* ]]; then echo $(cut -d "-" -f2 \<\<\< $VERSION); else echo latest; fi)
mkdir -p speckle-sharp-ci-tools/Installers/<< parameters.slug >>
if [ "${CIRCLE_TAG}" ]; then echo "version: $SEMVER" > "speckle-sharp-ci-tools/Installers/<< parameters.slug >>/$CHANNEL.yml"; fi
python3 patch_version.py $SEMVER
# update python and package dependencies
- when:
condition:
and:
- << parameters.installer >>
- equal: [osx-x64, << parameters.runtime >>]
steps:
- run:
name: Install python 3.10
command: |
brew install python@3.10
brew link --overwrite python@3.10
- run:
name: Package specklepy dependencies for blender 3.1 & 3.2
command: |
python3 --version
python3 -m pip install --target=./modules-intel-3.10 specklepy==$(python3 -m patch_version)
zip -r modules-intel-3.10.zip modules-intel-3.10/
cp modules-intel-3.10.zip speckle-sharp-ci-tools/Mac/<<parameters.installername>>/.installationFiles
- run:
name: Zip Connector files
command: |
zip -r << parameters.slug >>.zip bpy_speckle/
- run: *restore_semver
- run:
name: Copy connector files to installer
command: |
mkdir -p speckle-sharp-ci-tools/Mac/<<parameters.installername>>/.installationFiles/
cp << parameters.slug >>.zip speckle-sharp-ci-tools/Mac/<<parameters.installername>>/.installationFiles
python3 patch_version.py > speckle-sharp-ci-tools/Mac/<<parameters.installername>>/.installationFiles/specklepy_ver.yml
mkdir -p <<parameters.installer_path >>/.installationFiles/
cp bpy_speckle.zip << parameters.installer_path >>/.installationFiles
- run:
name: Build Mac installer
command: ~/.dotnet/dotnet publish speckle-sharp-ci-tools/Mac/<<parameters.installername>>/<<parameters.installername>>.sln -r << parameters.runtime >> -c Release
command: ~/.dotnet/dotnet publish << parameters.installer_path >>/SpeckleBlenderInstall.sln -r << parameters.runtime >> -c Release
- run:
name: Zip installer
command: |
cd speckle-sharp-ci-tools/Mac/<<parameters.installername>>/bin/Release/net6.0/<< parameters.runtime >>/
zip -r << parameters.slug >>.zip ./
- store_artifacts:
path: speckle-sharp-ci-tools/Mac/<<parameters.installername>>/bin/Release/net6.0/<< parameters.runtime >>/<< parameters.slug >>.zip
- run:
name: Copy to installer location
command: |
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "0.0.0"; fi;)
SEMVER=$(echo "$TAG" | sed -e 's/[a-zA-Z-]*\///')
cp speckle-sharp-ci-tools/Mac/<<parameters.installername>>/bin/Release/net6.0/<< parameters.runtime >>/<< parameters.slug >>.zip speckle-sharp-ci-tools/Installers/<< parameters.slug >>/<< parameters.slug >>-$SEMVER.zip
- when:
condition: << parameters.installer >>
steps:
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers
SEMVER=$(cat ./SEMVER)
echo $SEMVER
mkdir -p speckle-sharp-ci-tools/Installers/blender
(cd <<parameters.installer_path>>/bin/Release/net6.0/<< parameters.runtime >>/publish/ && zip -r - ./) > << parameters.slug >>-${SEMVER}.zip
cp << parameters.slug >>-${SEMVER}.zip speckle-sharp-ci-tools/Installers/blender/
get-ci-tools: # Clones our ci tools and persists them to the workspace
docker:
- image: cimg/base:2021.01
steps:
- run: # Could not get ssh to work, so using a personal token
name: Clone
command: git clone https://$GITHUB_TOKEN@github.com/specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools
- speckle-sharp-ci-tools/Installers/blender/<< parameters.slug >>*.zip
deploy: # Uploads all installers found to S3
build-installer-manual:
docker:
- image: cimg/base:2021.01
parameters:
slug:
type: string
default: bpy_speckle
steps:
- attach_workspace:
at: ./
- run: *restore_semver
- run:
name: List contents
command: ls -R speckle-sharp-ci-tools/Installers
- aws-s3/copy:
arguments: "--recursive --endpoint=https://$SPACES_REGION.digitaloceanspaces.com --acl public-read"
aws-access-key-id: SPACES_KEY
aws-region: SPACES_REGION
aws-secret-access-key: SPACES_SECRET
from: '"speckle-sharp-ci-tools/Installers/"'
to: s3://speckle-releases/installers/
name: Copy zip with semver
command: |
SEMVER=$(cat ./SEMVER)
mkdir -p speckle-sharp-ci-tools/Installers/blender
cp bpy_speckle.zip speckle-sharp-ci-tools/Installers/blender/<< parameters.slug >>-${SEMVER}.zip
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers/blender/<< parameters.slug >>*.zip
deploy-manager2:
deploy-connector:
docker:
- image: mcr.microsoft.com/dotnet/sdk:6.0
parameters:
slug:
file_slug:
type: string
os:
type: string
extension:
type: string
arch:
type: string
default: Any
steps:
- checkout
- attach_workspace:
@@ -216,105 +182,126 @@ jobs:
- run:
name: Install Manager Feed CLI
command: dotnet tool install --global Speckle.Manager.Feed
- run: *restore_semver
- run:
name: Upload new version
# this is where the installer gets the semver baked into the file name
command: |
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "0.0.0"; fi;)
SEMVER=$(echo "$TAG" | sed -e 's/\/[a-zA-Z-]*//')
/root/.dotnet/tools/Speckle.Manager.Feed deploy -s << parameters.slug >> -v ${SEMVER} -u https://releases.speckle.dev/installers/<< parameters.slug >>/<< parameters.slug >>-${SEMVER}.<< parameters.extension >> -o << parameters.os >> -f speckle-sharp-ci-tools/Installers/<< parameters.slug >>/<< parameters.slug >>-${SEMVER}.<< parameters.extension >>
SEMVER=$(cat ./SEMVER)
echo $SEMVER
/root/.dotnet/tools/Speckle.Manager.Feed deploy \
-s blender \
-v ${SEMVER} \
-u https://releases.speckle.dev/installers/blender/<< parameters.file_slug >>-${SEMVER}.<< parameters.extension >> \
-o << parameters.os >> \
-a << parameters.arch >> \
-f speckle-sharp-ci-tools/Installers/blender/<< parameters.file_slug >>-${SEMVER}.<< parameters.extension >>
workflows:
build: # build the installers, but don't persist to workspace for deployment
jobs:
- package-connector:
filters: &build_filters
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?(?:\.[0-9]+)?/
- build-connector-zip:
requires:
- package-connector
filters: *build_filters
- get-ci-tools:
filters:
branches:
only:
- main
- /ci\/.*/
- build-connector-win:
filters: *build_filters
- build-installer-win:
context: innosetup
name: Windows Installer Build
requires:
- package-connector
- get-ci-tools
filters:
branches:
only:
- main
- /ci\/.*/
- build-connector-mac:
requires:
- get-ci-tools
filters:
branches:
only:
- main
- /ci\/.*/
filters: *build_filters
deploy: # build installers and deploy
jobs:
- get-ci-tools:
filters:
tags:
only: /.*/
branches:
ignore: /.*/
- build-connector-win:
name: build-deploy-connector-win
slug: blender
installer: true
requires:
- get-ci-tools
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/
# - build-connector-mac:
# name: build-deploy-connector-mac-arm
# slug: blender-mac-arm
# runtime: osx-arm64
# installer: true
# requires:
# - get-ci-tools
# filters:
# tags:
# only: /^(all|mac)\/([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
# branches:
# ignore: /.*/
# - build-connector-mac:
# name: build-deploy-connector-mac-intel
# slug: blender-mac-intel
# runtime: osx-x64
# installer: true
# requires:
# - get-ci-tools
# filters:
# tags:
# only: /^(all|mac)\/([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
# branches:
# ignore: /.*/
- deploy:
requires:
- get-ci-tools
- build-deploy-connector-win
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/
- deploy-manager2:
slug: blender
os: Win
- deploy-connector:
context: do-spaces-speckle-releases
name: deploy-windows
file_slug: blender
os: WIN
arch: Any
extension: exe
requires:
- get-ci-tools
- deploy
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
- Manual Installer Build
- Windows Installer Build
- Mac Intel Build
- Mac ARM Build
filters: &deploy_filters
branches:
ignore: /.*/ # For testing only! /ci\/.*/
ignore: /.*/
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?(?:\.[0-9]+)?/
- build-installer-mac:
name: Mac ARM Build
slug: blender-mac-arm
runtime: osx-arm64
requires:
- get-ci-tools
- build-connector-zip
filters: *build_filters
- deploy-connector:
context: do-spaces-speckle-releases
name: deploy-mac-arm
file_slug: blender-mac-arm
os: OSX
arch: Arm
extension: zip
requires:
- Manual Installer Build
- Windows Installer Build
- Mac Intel Build
- Mac ARM Build
filters: *deploy_filters
- build-installer-mac:
name: Mac Intel Build
slug: blender-mac-intel
runtime: osx-x64
requires:
- get-ci-tools
- build-connector-zip
filters: *build_filters
- deploy-connector:
context: do-spaces-speckle-releases
name: deploy-mac-intel
file_slug: blender-mac-intel
os: OSX
arch: Intel
extension: zip
requires:
- Manual Installer Build
- Windows Installer Build
- Mac Intel Build
- Mac ARM Build
filters: *deploy_filters
- build-installer-manual:
name: Manual Installer Build
requires:
- get-ci-tools
- build-connector-zip
filters: *build_filters
- deploy-connector:
context: do-spaces-speckle-releases
name: deploy-manual
file_slug: bpy_speckle
os: Any
arch: Any
extension: zip
requires:
- Manual Installer Build
- Windows Installer Build
- Mac Intel Build
- Mac ARM Build
filters: *deploy_filters
+4 -70
View File
@@ -6,73 +6,7 @@ on:
jobs:
update_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo "$PROJECT_ID"
echo "$STATUS_FIELD_ID"
echo 'DONE_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .settings | fromjson | .options[] | select(.name== "Done") | .id' project_data.json) >> $GITHUB_ENV
echo "$DONE_ID"
- name: Add Issue to project #it's already in the project, but we do this to get its node id!
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
- name: Update Status
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $status:ID!, $id:ID!, $value:String!) {
set_status: updateProjectNextItemField(
input: {
projectId: $project
itemId: $id
fieldId: $status
value: $value
}
) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f status=$STATUS_FIELD_ID -f id=$ITEM_ID -f value=${{ env.DONE_ID }}
uses: specklesystems/github-actions/.github/workflows/project-add-issue.yml@main
secrets: inherit
with:
issue-id: ${{ github.event.issue.node_id }}
+4 -42
View File
@@ -6,45 +6,7 @@ on:
jobs:
track_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
- name: Add Issue to project
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
uses: specklesystems/github-actions/.github/workflows/project-add-issue.yml@main
secrets: inherit
with:
issue-id: ${{ github.event.issue.node_id }}
+4 -1
View File
@@ -10,4 +10,7 @@ __pycache__/
# dev
.venv
Installers/
modules/
modules/
.tool-versions
requirements.txt
SEMVER
+2 -2
View File
@@ -3,7 +3,7 @@
Speckle | Blender
</h1>
<h3 align="center">
Connector for Blender 2.92 & 2.93
Connector for Blender
</h3>
<p align="center"><b>Speckle</b> is the data infrastructure for the AEC industry.</p><br/>
@@ -91,4 +91,4 @@ The Speckle Community hangs out on [the forum](https://discourse.speckle.works),
Unless otherwise described, the code in this repository is licensed under the Apache-2.0 License. Please note that some modules, extensions or code herein might be otherwise licensed. This is indicated either in the root of the containing folder under a different license file, or in the respective file's header. If you have any questions, don't hesitate to get in touch with us via [email](mailto:hello@speckle.systems).
## Notes
SpeckleBlender is written and maintained by [Tom Svilans](http://tomsvilans.com) ([Github](https://github.com/tsvilans)).
Thanks to [Tom Svilans](http://tomsvilans.com) ([Github](https://github.com/tsvilans)) for the original v1 contribution!
+13 -10
View File
@@ -1,4 +1,15 @@
import bpy
from bpy_speckle.installer import ensure_dependencies
ensure_dependencies()
from specklepy.logging import metrics
from bpy_speckle.ui import *
from bpy_speckle.properties import *
from bpy_speckle.operators import *
from bpy_speckle.callbacks import *
from bpy.app.handlers import persistent
bl_info = {
"name": "SpeckleBlender 2.0",
@@ -18,15 +29,6 @@ bl_info = {
Import SpeckleBlender classes
"""
from specklepy.api.client import SpeckleClient # , SpeckleCache
from specklepy.logging import metrics
from bpy_speckle.ui import *
from bpy_speckle.properties import *
from bpy_speckle.operators import *
from bpy_speckle.callbacks import *
from bpy.app.handlers import persistent
"""
Add load handler to initialize Speckle when
loading a Blender file
@@ -34,7 +36,8 @@ loading a Blender file
@persistent
def load_handler(dummy):
bpy.ops.speckle.users_load()
pass
#bpy.ops.speckle.users_load() #this is an expensive operation, one that forces the user to wait every time blender loads. Until we can do this non-blocking, we will make the user hit the refresh button each time.
"""
+4 -1
View File
@@ -1,4 +1,7 @@
"""
Permanent handle on all user clients
"""
speckle_clients = []
from specklepy.api.client import SpeckleClient
speckle_clients: list[SpeckleClient] = []
+3 -2
View File
@@ -1,7 +1,8 @@
from typing import Union
from bpy_speckle.convert.to_native import convert_to_native
from specklepy.objects.base import Base
def get_speckle_subobjects(attr, scale, name):
def get_speckle_subobjects(attr: Union[dict, Base], scale: float, name: str) -> list:
subobjects = []
keys = attr.keys() if isinstance(attr, dict) else attr.get_dynamic_member_names()
for key in keys:
+181 -102
View File
@@ -1,11 +1,15 @@
import math
from typing import Iterable, Union, Collection
from bpy_speckle.convert.to_speckle import transform_to_speckle
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 bpy.types import Object
from .util import (
add_blender_material,
get_render_material,
render_material_to_native,
add_custom_properties,
add_vertices,
add_faces,
@@ -17,96 +21,60 @@ SUPPORTED_CURVES = (Line, Polyline, Curve, Arc, Polycurve)
CAN_CONVERT_TO_NATIVE = (
Mesh,
Brep,
*SUPPORTED_CURVES,
Transform,
transform_to_speckle,
BlockDefinition,
BlockInstance,
)
def can_convert_to_native(speckle_object):
def can_convert_to_native(speckle_object: Base) -> bool:
if type(speckle_object) in CAN_CONVERT_TO_NATIVE:
return True
if getattr(
speckle_object, "displayValue", getattr(speckle_object, "displayMesh", None)
):
return True
for alias in DISPLAY_VALUE_PROPERTY_ALIASES:
if getattr(speckle_object, alias, None):
return True
_report(f"Could not convert unsupported Speckle object: {speckle_object}")
return False
def convert_to_native(speckle_object, name=None):
def convert_to_native(speckle_object: Base) -> list[Object]:
speckle_type = type(speckle_object)
speckle_name = (
name
or getattr(speckle_object, "name", None)
or f"{speckle_object.speckle_type} -- {speckle_object.id}"
)
# convert unsupported types with display values
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
speckle_name = generate_object_name(speckle_object)
try:
# convert breps
if speckle_type is Brep:
meshes = getattr(
speckle_object, "displayValue", getattr(speckle_object, "displayMesh", None)
)
if material := getattr(speckle_object, "renderMaterial", getattr(speckle_object, "@renderMaterial", None),):
for mesh in meshes:
mesh["renderMaterial"] = material
scale = get_scale_factor(speckle_object)
return [convert_to_native(mesh) for mesh in meshes]
obj_data: Optional[Union[bpy.types.ID, bpy.types.Object, mathutils.Matrix]] = None
converted: list[Object] = []
# convert elements/breps
if speckle_type not in CAN_CONVERT_TO_NATIVE:
(obj_data, converted) = display_value_to_native(speckle_object, speckle_name, scale)
if units := getattr(speckle_object, "units", None):
scale = get_scale_length(units) / bpy.context.scene.unit_settings.scale_length
# convert supported geometry
if speckle_type is Mesh:
obj_data = mesh_to_native(speckle_object, name=speckle_name, scale=scale)
elif isinstance(speckle_object, Mesh):
obj_data = mesh_to_native(speckle_object, speckle_name, 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)
obj_data = icurve_to_native(speckle_object, speckle_name, scale)
elif isinstance(speckle_object, Transform):
obj_data = transform_to_native(speckle_object, scale)
elif isinstance(speckle_object, BlockDefinition):
obj_data = block_def_to_native(speckle_object)
elif isinstance(speckle_object, BlockInstance):
obj_data = block_instance_to_native(speckle_object, scale)
else:
_report(f"Unsupported type {speckle_type}")
return None
return []
except Exception as ex: # conversion error
_report(f"Error converting {speckle_object} \n{ex}")
return None
return []
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
obj_data.data if isinstance(obj_data, Object) else obj_data
)
blender_object.matrix_world = (
blender_object.matrix_world
@@ -118,39 +86,143 @@ def convert_to_native(speckle_object, name=None):
else:
blender_object = (
obj_data
if isinstance(obj_data, bpy_types.Object)
if isinstance(obj_data, 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
for child in converted:
child.parent = blender_object
converted.append(blender_object)
return converted
def mesh_to_native(speckle_mesh: Mesh, name, scale=1.0):
def generate_object_name(speckle_object: Base) -> str:
prefix = (getattr(speckle_object, "name", None)
or getattr(speckle_object, "Name", None)
or speckle_object.speckle_type.rsplit(':')[-1])
return f"{prefix} -- {speckle_object.id}"
def get_scale_factor(speckle_object: Base, fallback: float = 1.0) -> float:
scale = fallback
if units := getattr(speckle_object, "units", None):
scale = get_scale_length(units) / bpy.context.scene.unit_settings.scale_length
return scale
DISPLAY_VALUE_PROPERTY_ALIASES = ["displayValue", "@displayValue", "displayMesh", "@displayMesh", "elements", "@elements"]
def display_value_to_native(speckle_object: Base, name: str, scale: float) -> tuple[Optional[bpy.types.Mesh], list[bpy.types.Object]]:
"""
Converts mesh displayValues as one mesh
Converts non-mesh displayValues as child Objects
"""
meshes: list[Mesh] = []
elements: list[Base] = []
#NOTE: raw Mesh elements will be treated like displayValues, which is not ideal, but no connector sends raw Mesh elements so its fine
for alias in DISPLAY_VALUE_PROPERTY_ALIASES:
display = getattr(speckle_object, alias, None)
count = 0
max_depth = 255
def seperate(value: Any) -> None:
nonlocal meshes, elements, count, max_depth
if isinstance(value, Mesh):
meshes.append(value)
elif isinstance(value, Base):
elements.append(value)
elif isinstance(value, list):
count += 1
if(count > max_depth):
return
for x in value:
seperate(x)
seperate(display)
converted: list[Object] = []
mesh = None
if meshes:
mesh = meshes_to_native(speckle_object, meshes, name, scale)
# 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
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)
if not elements and not meshes:
_report(f"Unsupported type {speckle_object.speckle_type}")
return (mesh, converted)
def mesh_to_native(speckle_mesh: Mesh, name: str, scale: float) -> bpy.types.Mesh:
return meshes_to_native(speckle_mesh, [speckle_mesh], name, scale)
def meshes_to_native(element: Base, meshes: Collection[Mesh], name: str, scale: float) -> bpy.types.Mesh:
if name in bpy.data.meshes.keys():
blender_mesh = bpy.data.meshes[name]
else:
blender_mesh = bpy.data.meshes.new(name=name)
fallback_material = get_render_material(element)
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)
# First pass, add vertex data
for mesh in meshes:
scale = get_scale_factor(mesh, scale)
add_vertices(mesh, bm, scale)
bm.verts.ensure_lookup_table()
# Second pass, add face data
offset = 0
for i, mesh in enumerate(meshes):
add_faces(mesh, bm, offset, i)
render_material = get_render_material(mesh) or fallback_material
if render_material is not None:
native_material = render_material_to_native(render_material)
blender_mesh.materials.append(native_material)
offset += len(mesh.vertices) // 3
bm.faces.ensure_lookup_table()
bm.verts.index_update()
# Third pass, add vertex instance data
for mesh in meshes:
add_colors(mesh, bm)
add_uv_coords(mesh, bm)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bm.to_mesh(blender_mesh)
bm.free()
bm.free()
return blender_mesh
def line_to_native(speckle_curve, blender_curve, scale):
def line_to_native(speckle_curve: Line, blender_curve: bpy.types.Curve, scale: float) -> list[bpy.types.Spline]:
line = blender_curve.splines.new("POLY")
line.points.add(1)
@@ -170,10 +242,10 @@ def line_to_native(speckle_curve, blender_curve, scale):
1,
)
return line
return [line]
return []
def polyline_to_native(scurve, bcurve, scale):
def polyline_to_native(scurve: Polyline, bcurve: bpy.types.Curve, scale: float) -> list[bpy.types.Spline]:
if value := scurve.value:
N = len(value) // 3
@@ -194,13 +266,14 @@ def polyline_to_native(scurve, bcurve, scale):
1,
)
return polyline
return [polyline]
return []
def nurbs_to_native(scurve, bcurve, scale):
def nurbs_to_native(scurve: Curve, bcurve: bpy.types.Curve, scale: float) -> list[bpy.types.Spline]:
if points := scurve.points:
N = len(points) // 3
nurbs = bcurve.splines.new("NURBS")
if hasattr(scurve, "closed"):
@@ -223,15 +296,16 @@ def nurbs_to_native(scurve, bcurve, scale):
# nurbs.use_endpoint_u = True
nurbs.order_u = scurve.degree + 1
return nurbs
return [nurbs]
return []
def arc_to_native(rcurve, bcurve, scale):
def arc_to_native(rcurve: Arc, bcurve: bpy.types.Curve, scale: float) -> Optional[bpy.types.Spline]:
# TODO: improve Blender representation of arc
plane = rcurve.plane
if not plane:
return
return None
normal = mathutils.Vector([plane.normal.x, plane.normal.y, plane.normal.z])
@@ -288,7 +362,7 @@ def arc_to_native(rcurve, bcurve, scale):
return arc
def polycurve_to_native(scurve, bcurve, scale):
def polycurve_to_native(scurve: Polycurve, bcurve: bpy.types.Curve, scale: float) -> list[bpy.types.Spline]:
"""
Convert Polycurve object
"""
@@ -300,33 +374,38 @@ def polycurve_to_native(scurve, bcurve, scale):
speckle_type = type(seg)
if speckle_type in SUPPORTED_CURVES:
curves.append(icurve_to_native_spline(seg, bcurve, scale=scale))
curves.append(icurve_to_native_spline(seg, bcurve, 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:
def icurve_to_native_spline(speckle_curve: Base, blender_curve: bpy.types.Curve, scale: float) -> list[bpy.types.Spline]:
# polycurves
if isinstance(speckle_curve, Polycurve):
return polycurve_to_native(speckle_curve, blender_curve, scale)
if curve_type is Arc:
return arc_to_native(speckle_curve, blender_curve, scale)
# single curves
if isinstance(speckle_curve, Line):
spline = line_to_native(speckle_curve, blender_curve, scale)
elif isinstance(speckle_curve, Curve):
spline = nurbs_to_native(speckle_curve, blender_curve, scale)
elif isinstance(speckle_curve,Polyline):
spline = polyline_to_native(speckle_curve, blender_curve, scale)
elif isinstance(speckle_curve, Arc):
spline = arc_to_native(speckle_curve, blender_curve, scale)
else:
raise TypeError(f"{speckle_curve} is not a supported curve type. Supported types: {SUPPORTED_CURVES}")
return [spline] if spline is not None else []
def icurve_to_native(speckle_curve, name=None, scale=1.0):
def icurve_to_native(speckle_curve: Base, name: str, scale: float) -> Optional[bpy.types.Curve]:
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()
@@ -340,7 +419,7 @@ def icurve_to_native(speckle_curve, name=None, scale=1.0):
return blender_curve
def transform_to_native(transform: Transform, scale=1.0):
def transform_to_native(transform: Transform, scale: float) -> mathutils.Matrix:
mat = mathutils.Matrix(
[
transform.value[:4],
@@ -355,7 +434,7 @@ def transform_to_native(transform: Transform, scale=1.0):
return mat
def block_def_to_native(definition: BlockDefinition, scale=1.0):
def block_def_to_native(definition: BlockDefinition) -> bpy.types.Collection:
native_def = bpy.data.collections.get(definition.name)
if native_def:
return native_def
@@ -373,12 +452,12 @@ def block_def_to_native(definition: BlockDefinition, scale=1.0):
return native_def
def block_instance_to_native(instance: BlockInstance, scale=1.0):
def block_instance_to_native(instance: BlockInstance, scale: float) -> bpy.types.Object:
"""
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_def = block_def_to_native(instance.blockDefinition)
native_instance = bpy.data.objects.new(name, None)
add_custom_properties(instance, native_instance)
+95 -57
View File
@@ -1,4 +1,6 @@
from typing import Dict, Iterable, Optional, Tuple
import bpy
from bpy.types import Depsgraph, Material, MeshPolygon, Object
from specklepy.objects.geometry import Mesh, Curve, Interval, Box, Point, Polyline
from specklepy.objects.other import *
from bpy_speckle.functions import _report
@@ -13,15 +15,15 @@ UNITS = "m"
CAN_CONVERT_TO_SPECKLE = ("MESH", "CURVE", "EMPTY")
def convert_to_speckle(blender_object, scale, units, desgraph=None):
def convert_to_speckle(blender_object: Object, scale: float, units: str, desgraph: Optional[Depsgraph]) -> Optional[list]:
global UNITS
UNITS = units
blender_type = blender_object.type
if blender_type not in CAN_CONVERT_TO_SPECKLE:
return
return None
speckle_objects = []
speckle_material = material_to_speckle(blender_object)
# speckle_material = material_to_speckle_old(blender_object) #TODO: What about curves with materials...
if desgraph:
blender_object = blender_object.evaluated_get(desgraph)
converted = None
@@ -38,12 +40,11 @@ def convert_to_speckle(blender_object, scale, units, desgraph=None):
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":
@@ -53,46 +54,70 @@ def convert_to_speckle(blender_object, scale, units, desgraph=None):
return speckle_objects
def mesh_to_speckle(blender_object: Object, data: bpy.types.Mesh, scale: float = 1.0) -> List[Mesh]:
#if data.loop_triangles is None or len(data.loop_triangles) < 1:
# data.calc_loop_triangles()
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()
# Categorise polygons by material index
submesh_data: Dict[int, List[MeshPolygon]] = {}
mat = blender_object.matrix_world
for p in data.polygons:
if p.material_index not in submesh_data:
submesh_data[p.material_index] = []
submesh_data[p.material_index].append(p)
verts = [tuple(mat @ x.co * scale) for x in data.vertices]
transform = blender_object.matrix_world
scaled_vertices = [tuple(transform @ x.co * scale) for x in data.vertices]
faces = [p.vertices for p in data.polygons]
unit_system = bpy.context.scene.unit_settings.system
# Create Speckle meshes for each material
submeshes = []
index_counter = 0
for i in submesh_data:
index_mapping: Dict[int, int] = {}
sm = Mesh(
name=blender_object.name,
vertices=list(sum(verts, ())),
faces=[],
colors=[],
textureCoordinates=[],
units=UNITS,
bbox=Box(area=0.0, volume=0.0),
)
#Loop through each polygon, and map indicies to their new index in m_verts
m_verts: List[float] = []
m_faces: List[int] = []
m_texcoords: List[float] = []
for face in submesh_data[i]:
u_indices = face.vertices
m_faces.append(len(u_indices))
for u_index in u_indices:
if u_index not in index_mapping:
# Create mapping between index in blender mesh, and new index in speckle submesh
index_mapping[u_index] = len(m_verts) // 3
vert = scaled_vertices[u_index]
m_verts.append(vert[0])
m_verts.append(vert[1])
m_verts.append(vert[2])
if data.uv_layers.active:
vt = data.uv_layers.active.data[index_counter]
m_texcoords.extend([vt.uv.x, vt.uv.y])
if data.uv_layers.active:
for vt in data.uv_layers.active.data:
sm.textureCoordinates.extend([vt.uv.x, vt.uv.y])
m_faces.append(index_mapping[u_index])
index_counter += 1
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)
speckle_mesh = Mesh(
vertices=m_verts,
faces=m_faces,
colors=[],
textureCoordinates=m_texcoords,
units=UNITS,
bbox=Box(area=0.0, volume=0.0),
)
if i < len(data.materials):
material = data.materials[i]
if material is not None:
speckle_mesh["renderMaterial"] = material_to_speckle(material)
submeshes.append(speckle_mesh)
return [sm]
return submeshes
def bezier_to_speckle(matrix, spline, scale, name=None):
def bezier_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: float, name: Optional[str] = None) -> Curve:
degree = 3
closed = spline.use_cyclic_u
@@ -112,9 +137,12 @@ def bezier_to_speckle(matrix, spline, scale, name=None):
tuple(matrix @ spline.bezier_points[0].co * scale),
)
)
num_points = len(points)
flattend_points = []
for row in points: flattend_points.extend(row)
knot_count = num_points + degree - 1
knots = [0] * knot_count
@@ -128,7 +156,7 @@ def bezier_to_speckle(matrix, spline, scale, name=None):
degree=degree,
closed=spline.use_cyclic_u,
periodic=spline.use_cyclic_u,
points=list(sum(points, ())), # magic (flatten list of tuples)
points=flattend_points,
weights=[1] * num_points,
knots=knots,
rational=False,
@@ -141,7 +169,7 @@ def bezier_to_speckle(matrix, spline, scale, name=None):
)
def nurbs_to_speckle(matrix, spline, scale, name=None):
def nurbs_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: float, name: Optional[str] = None) -> Curve:
knots = make_knots(spline)
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
degree = spline.order_u - 1
@@ -149,12 +177,15 @@ def nurbs_to_speckle(matrix, spline, scale, name=None):
length = spline.calc_length()
domain = Interval(start=0, end=length, totalChildrenCount=0)
flattend_points = []
for row in points: flattend_points.extend(row)
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)
points=flattend_points,
weights=[pt.weight for pt in spline.points],
knots=knots,
rational=False,
@@ -167,15 +198,18 @@ def nurbs_to_speckle(matrix, spline, scale, name=None):
)
def poly_to_speckle(matrix, spline, scale, name=None):
def poly_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: float, name: Optional[str] = None) -> Polyline:
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
flattend_points = []
for row in points: flattend_points.extend(row)
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)
value=list(flattend_points),
length=length,
domain=domain,
bbox=Box(area=0.0, volume=0.0),
@@ -184,7 +218,7 @@ def poly_to_speckle(matrix, spline, scale, name=None):
)
def icurve_to_speckle(blender_object, data, scale=1.0):
def icurve_to_speckle(blender_object: Object, data: bpy.types.Curve, scale=1.0) -> Optional[List[Base]]:
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
if blender_object.type != "CURVE":
@@ -213,7 +247,7 @@ def icurve_to_speckle(blender_object, data, scale=1.0):
return curves
def ngons_to_speckle_polylines(blender_object, data, scale=1.0):
def ngons_to_speckle_polylines(blender_object: Object, data: bpy.types.Mesh, scale=1.0) -> Optional[List[Polyline]]:
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
if blender_object.type != "MESH":
@@ -245,15 +279,7 @@ def ngons_to_speckle_polylines(blender_object, data, scale=1.0):
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
def material_to_speckle(blender_mat: bpy.types.Material) -> RenderMaterial:
speckle_mat = RenderMaterial()
speckle_mat.name = blender_mat.name
@@ -275,7 +301,19 @@ def material_to_speckle(blender_object) -> RenderMaterial:
return speckle_mat
def transform_to_speckle(blender_transform, scale=1.0):
def material_to_speckle_old(blender_object: Object) -> Optional[RenderMaterial]:
"""Create and return a render material from a blender object"""
if not getattr(blender_object.data, "materials", None):
return None
blender_mat: bpy.types.Material = blender_object.data.materials[0]
if not blender_mat:
return None
return material_to_speckle(blender_mat)
def transform_to_speckle(blender_transform: Iterable[Iterable[float]], scale=1.0) -> Transform:
value = [y for x in blender_transform for y in x]
# scale the translation
for i in (3, 7, 11):
@@ -284,10 +322,10 @@ def transform_to_speckle(blender_transform, scale=1.0):
return Transform(value=value, units=UNITS)
def block_def_to_speckle(blender_definition, scale=1.0):
def block_def_to_speckle(blender_definition: bpy.types.Collection, scale=1.0) -> BlockDefinition:
geometry = []
for geo in blender_definition.objects:
geometry.extend(convert_to_speckle(geo, scale, UNITS))
geometry.extend(convert_to_speckle(geo, scale, UNITS, None))
block_def = BlockDefinition(
units=UNITS,
name=blender_definition.name,
@@ -299,7 +337,7 @@ def block_def_to_speckle(blender_definition, scale=1.0):
return block_def
def block_instance_to_speckle(blender_instance, scale=1.0):
def block_instance_to_speckle(blender_instance: Object, scale=1.0) -> BlockInstance:
return BlockInstance(
blockDefinition=block_def_to_speckle(
blender_instance.instance_collection, scale
@@ -310,7 +348,7 @@ def block_instance_to_speckle(blender_instance, scale=1.0):
)
def empty_to_speckle(blender_object, scale=1.0):
def empty_to_speckle(blender_object: Object, scale=1.0) -> Optional[BlockInstance]:
# probably an instance collection (block) so let's try it
try:
geo = blender_object.instance_collection.objects.items()
+46 -43
View File
@@ -1,9 +1,13 @@
from typing import Tuple
import math
from typing import Optional, Tuple
from bmesh.types import BMesh
import bpy, struct, idprop
from specklepy.objects.base import Base
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
from specklepy.objects.geometry import Mesh
from specklepy.objects.other import RenderMaterial
from bpy_speckle.functions import _report
from bpy.types import Material, Object
IGNORED_PROPERTY_KEYS = {
"id",
@@ -21,7 +25,7 @@ IGNORED_PROPERTY_KEYS = {
}
def to_rgba(argb_int: int) -> Tuple[float]:
def to_rgba(argb_int: int) -> Tuple[float, float, float, float]:
"""Converts the int representation of a colour into a percent RGBA tuple"""
alpha = ((argb_int >> 24) & 255) / 255
red = ((argb_int >> 16) & 255) / 255
@@ -31,18 +35,17 @@ def to_rgba(argb_int: int) -> Tuple[float]:
return (red, green, blue, alpha)
def to_argb_int(diffuse_colour) -> int:
def to_argb_int(rgba_color: list[float]) -> int:
"""Converts an RGBA array to an ARGB integer"""
diffuse_colour = diffuse_colour[-1:] + diffuse_colour[:3]
diffuse_colour = [int(val * 255) for val in diffuse_colour]
argb_color = rgba_color[-1:] + rgba_color[:3]
int_color = [int(val * 255) for val in argb_color]
return int.from_bytes(diffuse_colour, byteorder="big", signed=True)
return int.from_bytes(int_color, byteorder="big", signed=True)
def add_custom_properties(speckle_object, blender_object):
def add_custom_properties(speckle_object: Base, blender_object: Object):
if blender_object is None:
return
serializer = BaseObjectSerializer()
blender_object["_speckle_type"] = type(speckle_object).__name__
app_id = getattr(speckle_object, "applicationId", None)
@@ -66,28 +69,17 @@ def add_custom_properties(speckle_object, blender_object):
elif isinstance(val,dict):
for (k,v) in val.items():
if not isinstance(v, Base):
blender_object[k] = v
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")
def render_material_to_native(speckle_mat: RenderMaterial) -> Material:
mat_name = speckle_mat.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:
if blender_mat is None:
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
@@ -101,13 +93,27 @@ def add_blender_material(speckle_object, blender_object) -> None:
inputs["Metallic"].default_value = speckle_mat.metalness
inputs["Alpha"].default_value = speckle_mat.opacity
if speckle_mat.opacity < 1:
if speckle_mat.opacity < 1.0:
blender_mat.blend_method = "BLEND"
blender_object.data.materials.append(blender_mat)
return blender_mat
def get_render_material(speckle_object: Base) -> Optional[RenderMaterial]:
"""Trys to get a RenderMaterial on given speckle_object and convert it to a blender material"""
speckle_mat = getattr(
speckle_object,
"renderMaterial",
getattr(speckle_object, "@renderMaterial", None),
)
if not isinstance(speckle_mat, RenderMaterial):
return None
return speckle_mat
def add_vertices(speckle_mesh, blender_mesh, scale=1.0):
def add_vertices(speckle_mesh: Mesh, blender_mesh: BMesh, scale=1.0):
sverts = speckle_mesh.vertices
if sverts and len(sverts) > 0:
@@ -120,12 +126,11 @@ def add_vertices(speckle_mesh, blender_mesh, scale=1.0):
)
)
blender_mesh.verts.ensure_lookup_table()
def add_faces(speckle_mesh, blender_mesh, smooth=False):
def add_faces(speckle_mesh: Mesh, blender_mesh: BMesh, indexOffset: int, materialIndex: int = 0, smooth:bool = False):
sfaces = speckle_mesh.faces
if sfaces and len(sfaces) > 0:
i = 0
while i < len(sfaces):
@@ -136,18 +141,16 @@ def add_faces(speckle_mesh, blender_mesh, smooth=False):
i += 1
try:
f = blender_mesh.faces.new(
[blender_mesh.verts[int(x)] for x in sfaces[i : i + n]]
[blender_mesh.verts[x + indexOffset] for x in sfaces[i : i + n]]
)
f.material_index = materialIndex
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):
def add_colors(speckle_mesh: Mesh, blender_mesh: BMesh):
scolors = speckle_mesh.colors
@@ -178,7 +181,7 @@ def add_colors(speckle_mesh, blender_mesh):
loop[color_layer] = colors[loop.vert.index]
def add_uv_coords(speckle_mesh, blender_mesh):
def add_uv_coords(speckle_mesh: Mesh, blender_mesh: BMesh):
s_uvs = speckle_mesh.textureCoordinates
if not s_uvs:
return
@@ -192,7 +195,7 @@ def add_uv_coords(speckle_mesh, blender_mesh):
)
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}"
f"Failed to match UV coordinates to vert data. Blender mesh verts: {len(blender_mesh.verts)}, Speckle UVs: {len(s_uvs) // 2}"
)
return
@@ -246,15 +249,15 @@ from: https://blender.stackexchange.com/a/34276
"""
def macro_knotsu(nu):
def macro_knotsu(nu: bpy.types.Spline) -> int:
return nu.order_u + nu.point_count_u + (nu.order_u - 1 if nu.use_cyclic_u else 0)
def macro_segmentsu(nu):
def macro_segmentsu(nu: bpy.types.Spline) -> int:
return nu.point_count_u if nu.use_cyclic_u else nu.point_count_u - 1
def make_knots(nu):
def make_knots(nu: bpy.types.Spline) -> list[float]:
knots = [0.0] * (4 + macro_knotsu(nu))
flag = nu.use_endpoint_u + (nu.use_bezier_u << 1)
if nu.use_cyclic_u:
@@ -265,7 +268,7 @@ def make_knots(nu):
return knots
def calc_knots(knots, point_count, order, flag):
def calc_knots(knots: list[float], point_count: int, order: int, flag: int) -> None:
pts_order = point_count + order
if flag == 1:
k = 0.0
@@ -290,7 +293,7 @@ def calc_knots(knots, point_count, order, flag):
knots[a] = a
def makecyclicknots(knots, point_count, order):
def makecyclicknots(knots: list[float], point_count: int, order: int) -> None:
order2 = order - 1
if order > 2:
+1 -1
View File
@@ -33,7 +33,7 @@ def _report(msg):
print("SpeckleBlender: {}".format(msg))
def get_scale_length(units):
def get_scale_length(units: str) -> float:
if units.lower() in unit_scale.keys():
return unit_scale[units]
_report("Units <{}> are not supported.".format(units))
+128
View File
@@ -0,0 +1,128 @@
from pathlib import Path
from importlib import import_module, invalidate_caches
import bpy
import sys
print("Starting Speckle Blender installation")
print(sys.executable)
PYTHON_PATH = sys.executable
def modules_path() -> Path:
modules_path = Path(bpy.utils.script_path_user(), "addons", "modules")
modules_path.mkdir(exist_ok=True, parents=True)
# set user modules path at beginning of paths for earlier hit
if sys.path[1] != modules_path:
sys.path.insert(1, modules_path)
return modules_path
print(f"Found blender modules path {modules_path()}")
def is_pip_available() -> bool:
try:
import_module("pip") # noqa F401
return True
except ImportError:
return False
def ensure_pip() -> None:
print("Installing pip... "),
from subprocess import run
completed_process = run([PYTHON_PATH, "-m", "ensurepip"])
if completed_process.returncode == 0:
print("Successfully installed pip")
else:
raise Exception("Failed to install pip.")
def get_requirements_path() -> Path:
# we assume that a requirements.txt exists next to the __init__.py file
path = Path(Path(__file__).parent, "requirements.txt")
assert path.exists()
return path
def install_requirements() -> None:
# set up addons/modules under the user
# script path. Here we'll install the
# dependencies
path = modules_path()
print(f"Installing Speckle dependencies to {path}")
from subprocess import run
completed_process = run(
[
PYTHON_PATH,
"-m",
"pip",
"install",
"-t",
str(path),
"-r",
str(get_requirements_path()),
],
capture_output=True,
text=True,
)
if completed_process.returncode != 0:
print("Please try manually installing speckle-blender")
raise Exception(
"""
Failed to install speckle-blender.
See console for manual install instruction.
"""
)
def install_dependencies() -> None:
if not is_pip_available():
ensure_pip()
install_requirements()
def _import_dependencies() -> None:
import_module("specklepy")
# the code above doesn't work for now, it fails on importing graphql-core
# despite that, the connector seams to be working as expected
# But it would be nice to make this solution work
# it would ensure that all dependencies are fully loaded
# requirements = get_requirements_path().read_text()
# reqs = [
# req.split(" ; ")[0].split("==")[0].split("[")[0].replace("-", "_")
# for req in requirements.split("\n")
# if req and not req.startswith(" ")
# ]
# for req in reqs:
# print(req)
# import_module("specklepy")
def ensure_dependencies() -> None:
try:
install_dependencies()
invalidate_caches()
_import_dependencies()
print("Found all dependencies, proceed with loading")
except ImportError:
raise Exception(
"Cannot automatically ensure Speckle dependencies. Please restart Blender!"
)
if __name__ == "__main__":
ensure_dependencies()
+166 -41
View File
@@ -2,37 +2,39 @@
Stream operators
"""
from itertools import chain
from typing import Dict
from math import radians
from typing import Callable, Dict, Iterable, Optional
import bpy
from specklepy.api.models import Commit
import webbrowser
from bpy.props import (
StringProperty,
BoolProperty,
EnumProperty,
)
from bpy.types import Context, Object
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,
get_scale_length,
_report,
)
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 import operations, host_applications
from specklepy.api.wrapper import StreamWrapper
from specklepy.api.resources.stream import Stream
from specklepy.transports.server import ServerTransport
from specklepy.objects.geometry import *
from specklepy.logging.exceptions import SpeckleException
from specklepy.logging import metrics
def get_objects_collections(base) -> Dict:
def get_objects_collections(base: Base) -> Dict[str, list]:
"""Create collections based on the dynamic members on a root commit object"""
collections = {}
for name in base.get_dynamic_member_names():
@@ -47,9 +49,11 @@ def get_objects_collections(base) -> Dict:
return collections
def get_objects_nested_lists(items, parent_col=None) -> List:
def get_objects_nested_lists(items: list, parent_col: Optional[bpy.types.Collection] = None) -> List:
"""For handling the weird nested lists that come from Grasshopper"""
objects = []
if not items:
return objects
if isinstance(items[0], list):
items = list(chain.from_iterable(items))
@@ -64,7 +68,7 @@ def get_objects_nested_lists(items, parent_col=None) -> List:
return objects
def get_objects_collections_recursive(base, parent_col=None) -> List:
def get_objects_collections_recursive(base: Base, parent_col: Optional[bpy.types.Collection] = 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):
@@ -75,8 +79,6 @@ def get_objects_collections_recursive(base, parent_col=None) -> List:
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):
objects.extend(item for item in value if isinstance(item, Base))
if isinstance(value, Base):
@@ -94,7 +96,47 @@ def get_objects_collections_recursive(base, parent_col=None) -> List:
return objects
def bases_to_native(context, collections, scale, stream_id, func=None):
ObjectCallback = Optional[Callable[[bpy.types.Context, Object, Base], Object]]
ReceiveCompleteCallback = Optional[Callable[[bpy.types.Context, Dict[str, Object]], None]]
def get_receive_funcs(context: Context, created_objects: Dict[str, Object]) -> tuple[ObjectCallback, ReceiveCompleteCallback]:
"""
Fetches the injected callback functions from user specified "Receive Script"
"""
objectCallback: ObjectCallback = None
receiveCompleteCallback: ReceiveCompleteCallback = None
if context.scene.speckle.receive_script in bpy.data.texts:
mod = bpy.data.texts[context.scene.speckle.receive_script].as_module()
if hasattr(mod, "execute_for_each"):
objectCallback = mod.execute_for_each
elif hasattr(mod, "execute"):
objectCallback = lambda c, o, _ : mod.execute(c.scene, o)
if hasattr(mod, "execute_for_all"):
receiveCompleteCallback = mod.execute_for_all
progress = 0
def for_each_object(context: bpy.types.Context, obj: Object, base: Base) -> Object:
nonlocal progress
nonlocal created_objects
nonlocal objectCallback
progress += 1 #NOTE:XXX Progress bar never reaches 100 because func is only called for convertible objects
context.window_manager.progress_update(progress)
created_objects[obj.name] = obj
if objectCallback:
return objectCallback(context, obj, base)
else:
return obj
return (for_each_object, receiveCompleteCallback)
def bases_to_native(context: bpy.types.Context, collections: Dict[str, list], scale: float, stream_id: str, func: ObjectCallback = None):
for col_name, objects in collections.items():
col = bpy.data.collections[col_name]
existing = get_existing_collection_objs(col)
@@ -104,7 +146,7 @@ def bases_to_native(context, collections, scale, stream_id, func=None):
for obj in objects:
if isinstance(obj, dict):
bases_to_native(context, obj, scale, stream_id, func)
elif isinstance(obj, list):
elif isinstance(obj, list): #FIXME: wtf are these nested if statement, can this not be a recursive call?
for item in obj:
if isinstance(item, dict):
bases_to_native(context, item, scale, stream_id, func)
@@ -117,7 +159,7 @@ def bases_to_native(context, collections, scale, stream_id, func=None):
else:
_report(
f"Something went wrong when receiving collection: {col_name}"
f"Something went wrong when receiving collection: {col_name}" #FIXME: undescript report message
)
bpy.context.view_layer.update()
@@ -126,17 +168,25 @@ def bases_to_native(context, collections, scale, stream_id, func=None):
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"])
)
def base_to_native(context: bpy.types.Context,
base: Base,
scale: float,
stream_id: str,
col: bpy.types.Collection,
existing: Dict[str, Object],
func: ObjectCallback = None
):
new_objects = convert_to_native(base)
#NOTE: this code is ancient, and in testing does nothing, so we are removing it.
# 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
@@ -149,11 +199,11 @@ def base_to_native(context, base, scale, stream_id, col, existing, func=None):
Run injected function
"""
if func:
new_object = func(context.scene, new_object)
new_object = func(context, new_object, base) #this base object isn't always the right one for hosted elements! #TODO: may be it now, need to double check!
if (
new_object is None
): # Make sure that the injected function returned an object
): # If the injected function returned None, then we should ignore this object.
_report(f"Script '{func.__module__}' returned None.")
continue
@@ -170,7 +220,7 @@ def base_to_native(context, base, scale, stream_id, col, existing, func=None):
col.objects.link(new_object)
def create_collection(name, clear_collection=True):
def create_collection(name: str, clear_collection=True) -> bpy.types.Collection:
if name in bpy.data.collections:
col = bpy.data.collections[name]
if clear_collection:
@@ -182,19 +232,19 @@ def create_collection(name, clear_collection=True):
return col
def create_child_collections(parent_col, children_names):
def create_child_collections(parent_col: bpy.types.Collection, children_names: Iterable[str]):
for name in children_names:
col = create_collection(name)
parent_col.children.link(col)
def get_existing_collection_objs(col):
def get_existing_collection_objs(col: bpy.types.Collection) -> Dict[str, bpy.types.Object]:
return {
obj.speckle.object_id: obj for obj in col.objects if obj.speckle.object_id != ""
}
def get_collection_parents(collection, names):
def get_collection_parents(collection: bpy.types.Collection, names: list[str]) -> None:
for parent in bpy.data.collections:
if collection.name in parent.children.keys():
# TODO: this should be rethought to make it clear when this is an IFC delim so we know to replace it
@@ -203,7 +253,7 @@ def get_collection_parents(collection, names):
get_collection_parents(parent, names)
def get_collection_hierarchy(collection):
def get_collection_hierarchy(collection: Optional[bpy.types.Collection]) -> list[str]:
if not collection:
return []
names = [collection.name.replace("/", "::").replace(".", "::")]
@@ -212,7 +262,7 @@ def get_collection_hierarchy(collection):
return names
def create_nested_hierarchy(base, hierarchy, objects):
def create_nested_hierarchy(base: Base, hierarchy: List[str], objects: Any):
child = base
while hierarchy:
@@ -229,6 +279,11 @@ def create_nested_hierarchy(base, hierarchy, objects):
return base
#RECEIVE_MODES = [#TODO: modes
# ("create", "Create", "Add new geometry, without removing any existing objects"),
# ("replace", "Replace", "Replace objects from previous receive operations from the same stream"),
# #("update","Update") #TODO: update mode!
#]
class ReceiveStreamObjects(bpy.types.Operator):
"""
@@ -240,6 +295,49 @@ class ReceiveStreamObjects(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Receive objects from active stream"
clean_meshes: BoolProperty(name="Clean Meshes", default=False)
#receive_mode: EnumProperty(items=RECEIVE_MODES, name="Receive Type", default="replace", description="The behaviour of the recieve operation")
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, "clean_meshes")
#col.prop(self, "receive_mode")
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
@staticmethod
def clean_converted_meshes(context: bpy.types.Context, convertedObjects: dict[str, Object]):
bpy.ops.object.select_all(action='DESELECT')
active = None
for obj in convertedObjects.values():
if obj.type != 'MESH':
continue
obj.select_set(True, view_layer=context.scene.view_layers[0])
active = obj
if active == None:
return
context.view_layer.objects.active = active
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles()
bpy.ops.mesh.dissolve_limited(angle_limit=radians(0.1))
# Reset state to previous (not quite sure if this is 100% necessary)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = None
def execute(self, context):
bpy.context.view_layer.objects.active = None
@@ -266,10 +364,19 @@ class ReceiveStreamObjects(bpy.types.Operator):
_report("No commits found. Probably an empty stream.")
return {"CANCELLED"}
commit = branch.commits.items[int(bbranch.commit)]
commit: Commit = branch.commits.items[int(bbranch.commit)]
transport = ServerTransport(stream.id, client)
stream_data = operations.receive(commit.referencedObject, transport)
metrics.track(
metrics.RECEIVE,
getattr(transport, "account", None),
custom_props={
"sourceHostApp": host_applications.get_host_app_from_string(commit.sourceApplication).slug,
"sourceHostAppVersion": commit.sourceApplication
},
)
stream_data = operations._untracked_receive(commit.referencedObject, transport)
client.commit.received(
bstream.id,
commit.id,
@@ -277,6 +384,9 @@ class ReceiveStreamObjects(bpy.types.Operator):
message="received commit from Speckle Blender",
)
context.window_manager.progress_begin(0, stream_data.totalChildrenCount)
"""
Create or get Collection for stream objects
"""
@@ -285,11 +395,16 @@ class ReceiveStreamObjects(bpy.types.Operator):
if not collections:
return {"CANCELLED"}
name = "{} [ {} @ {} ]".format(stream.name, branch.name, commit.id)
# name = ""
# if self.receive_mode == "create":
name = "{} [ {} @ {} ]".format(stream.name, branch.name, commit.id) # Matches Rhino "Create" naming
# else:
# name = stream.name # Doesn't quite match rhino's Update layer naming, but is close enough no?
col = create_collection(name)
col.speckle.stream_id = stream.id
col.speckle.name = stream.name
col.speckle.units = stream_data.units
col.speckle.units = stream_data.units or "m"
if col.name not in bpy.context.scene.collection.children:
bpy.context.scene.collection.children.link(col)
@@ -302,25 +417,35 @@ class ReceiveStreamObjects(bpy.types.Operator):
Set conversion scale from stream units
"""
scale = (
get_scale_length(stream_data.units)
get_scale_length(col.speckle.units)
/ context.scene.unit_settings.scale_length
)
"""
Get script from text editor for injection
"""
func = None
if context.scene.speckle.receive_script in bpy.data.texts:
mod = bpy.data.texts[context.scene.speckle.receive_script].as_module()
if hasattr(mod, "execute"):
func = mod.execute
created_objects = {}
(func, on_complete) = get_receive_funcs(context, created_objects)
"""
Iterate through retrieved resources
"""
bases_to_native(context, collections, scale, stream.id, func)
context.window_manager.progress_end()
if self.clean_meshes:
self.clean_converted_meshes(context, created_objects)
if on_complete:
on_complete(context, created_objects)
return {"FINISHED"}
class SendStreamObjects(bpy.types.Operator):
+11 -7
View File
@@ -4,7 +4,9 @@ User account operators
import bpy
from bpy_speckle.functions import _report
from bpy_speckle.clients import speckle_clients
from bpy_speckle.properties.scene import SpeckleSceneSettings
from specklepy.api.client import SpeckleClient
from specklepy.api.models import Stream, User
from specklepy.api.credentials import get_local_accounts
from datetime import datetime
@@ -22,12 +24,14 @@ class LoadUsers(bpy.types.Operator):
_report("Loading users...")
users = context.scene.speckle.users
speckle : SpeckleSceneSettings = context.scene.speckle
users = speckle.users
context.scene.speckle.users.clear()
speckle.users.clear()
speckle_clients.clear()
profiles = get_local_accounts()
active_user_index = 0
for profile in profiles:
user = users.add()
@@ -42,16 +46,16 @@ class LoadUsers(bpy.types.Operator):
host=profile.serverInfo.url,
use_ssl="https" in profile.serverInfo.url,
)
client.authenticate(user.authToken)
client.authenticate_with_account(profile)
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)
active_user_index = len(users) - 1
context.scene.speckle.active_user_index = int(context.scene.speckle.active_user)
bpy.ops.speckle.load_user_streams()
speckle.active_user_index = int(speckle.active_user)
speckle.active_user = str(active_user_index)
bpy.context.view_layer.update()
if context.area:
@@ -59,7 +63,7 @@ class LoadUsers(bpy.types.Operator):
return {"FINISHED"}
def add_user_stream(user, stream):
def add_user_stream(user: User, stream: Stream):
s = user.streams.add()
s.name = stream.name
s.id = stream.id
+1 -1
View File
@@ -73,7 +73,7 @@ class SpeckleUserObject(bpy.types.PropertyGroup):
name: StringProperty(default="Speckle User")
email: StringProperty(default="user@speckle.xyz")
company: StringProperty(default="SpeckleSystems")
authToken: StringProperty(default="")
authToken: StringProperty(default="", subtype='PASSWORD')
streams: CollectionProperty(type=SpeckleStreamObject)
active_stream: IntProperty(default=0)
+3 -2
View File
@@ -115,13 +115,14 @@ class VIEW3D_PT_SpeckleUser(bpy.types.Panel):
col = layout.column()
if len(speckle.users) < 1:
col.label(text="No users found.")
col.label(text="Refresh to initialise")
else:
col.prop(speckle, "active_user", text="")
user = speckle.users[int(speckle.active_user)]
col.label(text="{} ({})".format(user.server_name, user.server_url))
col.label(text="{} ({})".format(user.name, user.email))
col.operator("speckle.users_load", text="", icon="FILE_REFRESH")
class VIEW3D_PT_SpeckleStreams(bpy.types.Panel):
"""
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -e -o pipefail
poetry export --only main -o bpy_speckle/requirements.txt
+20
View File
@@ -0,0 +1,20 @@
import sys
from pathlib import Path
def patch_installer(tag: str):
"""Patches the installer with the correct connector version and specklepy version"""
tag = tag.replace("\n", "")
iss_file = "speckle-sharp-ci-tools/blender.iss"
iss_path = Path(iss_file)
lines = iss_path.read_text().split("\n")
lines.insert(12, f'#define AppVersion "{tag.split("-")[0]}"')
lines.insert(13, f'#define AppInfoVersion "{tag}"')
iss_path.write_text("\n".join(lines))
print(f"Patched installer with connector v{tag}")
if __name__ == "__main__":
tag = sys.argv[1]
patch_installer(tag)
-36
View File
@@ -1,7 +1,6 @@
import re
import sys
def patch_connector(tag):
"""Patches the connector version within the connector init file"""
bpy_file = "bpy_speckle/__init__.py"
@@ -19,48 +18,13 @@ def patch_connector(tag):
with open(bpy_file, "w") as file:
file.writelines(lines)
def patch_installer(tag):
"""Patches the installer with the correct connector version and specklepy version"""
iss_file = "speckle-sharp-ci-tools/blender.iss"
py_tag = get_specklepy_version()
with open(iss_file, "r") as file:
lines = file.readlines()
lines.insert(11, f'#define SpecklepyVersion "{py_tag}"\n')
lines.insert(12, f'#define AppVersion "{tag.split("-")[0]}"\n')
lines.insert(13, f'#define AppInfoVersion "{tag}"\n')
with open(iss_file, "w") as file:
file.writelines(lines)
print(f"Patched installer with connector v{tag} and specklepy 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]+)\.([0-9]+)", tag):
raise ValueError(f"Invalid tag provided: {tag}")
print(f"Patching version: {tag}")
patch_connector(tag.split("-")[0])
patch_installer(tag)
if __name__ == "__main__":
Generated
+807 -964
View File
File diff suppressed because it is too large Load Diff
+11 -8
View File
@@ -2,19 +2,22 @@
name = "speckle-blender"
version = "2.0.0"
description = "the Speckle 2.0 connector for Blender!"
authors = ["izzy lyseggen <izzy.lyseggen@gmail.com>"]
authors = ["izzy lyseggen <izzy.lyseggen@gmail.com>", "Gergő Jedlicska <gergo@jedlicska.com>"]
license = "Apache-2.0"
[tool.poetry.dependencies]
python = ">=3.8, <4.0.0"
specklepy = "^2.6.6"
specklepy = "^2.12.0"
[tool.poetry.dev-dependencies]
devtools = "^0.6.1"
numpy = "^1.20.2"
fake-bpy-module-latest = "^20220401"
black = "^21.12b0"
pylint = "^2.12.2"
# [tool.poetry.group.local_specklepy.dependencies]
# specklepy = {path = "../specklepy", develop = true}
[tool.poetry.group.dev.dependencies]
numpy = "^1.23.5"
fake-bpy-module-latest = "^20221006"
black = "^22.10.0"
pylint = "^2.15.7"
ruff = "^0.0.166"
[build-system]
requires = ["poetry-core>=1.0.0"]