Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 235b49d8c6 | |||
| a1fcdad0e3 | |||
| 584e543964 | |||
| ef20c5240c | |||
| 9fe12a018a | |||
| 8411c01f1b | |||
| 63b82a30cb | |||
| 0b6e39cf38 | |||
| b7efcec517 | |||
| 0ab0096aac | |||
| cbd8fc99bb | |||
| 2a9287e762 | |||
| 98c70f237c | |||
| 048047cf05 | |||
| 6118215cae | |||
| 8e06432fe6 | |||
| cb8620ff8a | |||
| e0eddea8ab | |||
| 8497d0c195 | |||
| 1ce394c08f | |||
| aabbf87dda | |||
| 7e31787d37 | |||
| f1259587fd | |||
| 7f913e3af0 | |||
| 5433c34a4d | |||
| 2c52e93660 | |||
| 3c3b24cf98 | |||
| 3012b0ebcb | |||
| 35b96eaa4e | |||
| c229bb2414 | |||
| 318bd086c0 | |||
| e3eb29daa4 | |||
| 3359c8f275 | |||
| cfc5007d00 | |||
| 0a630457be | |||
| fa124c2312 | |||
| 977994e141 | |||
| d9cbc80ee7 | |||
| 45038fad79 | |||
| cda621d735 | |||
| 2d052d1379 | |||
| 46ce2bc0df | |||
| 80e2216aa0 | |||
| 10d69aa44b | |||
| b1481bd259 | |||
| e19fc9ef4e | |||
| 448eb856b2 | |||
| 06a7d416c7 | |||
| 0753436899 | |||
| 40e0a49b11 | |||
| b17b9c9de4 | |||
| f035111ffa | |||
| fa073f754f | |||
| 83919375f9 | |||
| defb11bc89 | |||
| 548b3ad352 | |||
| d2deecf099 | |||
| fbda0110cd | |||
| ba9ad9ac07 | |||
| d13db2b44a | |||
| 2c127c85f3 | |||
| cc099c0ff1 | |||
| 448ab70c3b | |||
| 020eba2727 | |||
| d8117e2c30 | |||
| 57a6d88e6b | |||
| 74cb3e5f85 | |||
| d0aeecc863 | |||
| 2803308c5e | |||
| 326f04f67d | |||
| 68972ba8f9 | |||
| 73a028b56f | |||
| 33890ef0ee | |||
| 53fe676ab6 | |||
| f027c7eca4 | |||
| ea2b6dfb0e | |||
| 83610cec38 | |||
| 2ed0685b10 | |||
| 877e616188 | |||
| 2e3e258a9a |
+239
-217
@@ -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,181 +117,191 @@ 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-connector:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/dotnet/sdk:6.0
|
||||
parameters:
|
||||
file_slug:
|
||||
type: string
|
||||
os:
|
||||
type: string
|
||||
extension:
|
||||
type: string
|
||||
arch:
|
||||
type: string
|
||||
default: Any
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: ./
|
||||
- run:
|
||||
name: Install Manager Feed CLI
|
||||
command: dotnet tool install --global Speckle.Manager.Feed
|
||||
- run: *restore_semver
|
||||
- run:
|
||||
name: Upload new version
|
||||
# this is where the installer gets the semver baked into the file name
|
||||
command: |
|
||||
SEMVER=$(cat ./SEMVER)
|
||||
echo $SEMVER
|
||||
/root/.dotnet/tools/Speckle.Manager.Feed deploy \
|
||||
-s blender \
|
||||
-v ${SEMVER} \
|
||||
-u https://releases.speckle.dev/installers/blender/<< parameters.file_slug >>-${SEMVER}.<< parameters.extension >> \
|
||||
-o << parameters.os >> \
|
||||
-a << parameters.arch >> \
|
||||
-f speckle-sharp-ci-tools/Installers/blender/<< parameters.file_slug >>-${SEMVER}.<< parameters.extension >>
|
||||
|
||||
workflows:
|
||||
build: # build the installers, but don't persist to workspace for deployment
|
||||
jobs:
|
||||
- get-ci-tools:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- /ci\/.*/
|
||||
- build-connector-win:
|
||||
requires:
|
||||
- get-ci-tools
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- /ci\/.*/
|
||||
- build-connector-mac:
|
||||
requires:
|
||||
- get-ci-tools
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- /ci\/.*/
|
||||
|
||||
deploy: # build installers and deploy
|
||||
jobs:
|
||||
- get-ci-tools:
|
||||
filters:
|
||||
- package-connector:
|
||||
filters: &build_filters
|
||||
tags:
|
||||
only: /.*/
|
||||
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?(?:\.[0-9]+)?/
|
||||
|
||||
- build-connector-zip:
|
||||
requires:
|
||||
- package-connector
|
||||
filters: *build_filters
|
||||
|
||||
- get-ci-tools:
|
||||
filters: *build_filters
|
||||
|
||||
- build-installer-win:
|
||||
context: innosetup
|
||||
name: Windows Installer Build
|
||||
requires:
|
||||
- package-connector
|
||||
- get-ci-tools
|
||||
filters: *build_filters
|
||||
|
||||
- deploy-connector:
|
||||
context: do-spaces-speckle-releases
|
||||
name: deploy-windows
|
||||
file_slug: blender
|
||||
os: WIN
|
||||
arch: Any
|
||||
extension: exe
|
||||
requires:
|
||||
- Manual Installer Build
|
||||
- Windows Installer Build
|
||||
- Mac Intel Build
|
||||
- Mac ARM Build
|
||||
filters: &deploy_filters
|
||||
branches:
|
||||
ignore: /.*/
|
||||
|
||||
- build-connector-win:
|
||||
name: build-deploy-connector-win
|
||||
slug: blender
|
||||
installer: true
|
||||
requires:
|
||||
- get-ci-tools
|
||||
filters:
|
||||
tags:
|
||||
only: /^(all|win)\/([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?(?:\.[0-9]+)?/
|
||||
|
||||
- build-connector-mac:
|
||||
name: build-deploy-connector-mac-arm
|
||||
- build-installer-mac:
|
||||
name: Mac ARM Build
|
||||
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-zip
|
||||
filters: *build_filters
|
||||
|
||||
- build-connector-mac:
|
||||
name: build-deploy-connector-mac-intel
|
||||
- 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
|
||||
installer: true
|
||||
requires:
|
||||
- get-ci-tools
|
||||
filters:
|
||||
tags:
|
||||
only: /^(all|mac)\/([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
- build-connector-zip
|
||||
filters: *build_filters
|
||||
|
||||
- deploy:
|
||||
- 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-deploy-connector-win
|
||||
- build-deploy-connector-mac-arm
|
||||
- build-deploy-connector-mac-intel
|
||||
filters:
|
||||
tags:
|
||||
only: /^(all|win|mac)\/([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
- 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
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
@@ -10,4 +10,7 @@ __pycache__/
|
||||
# dev
|
||||
.venv
|
||||
Installers/
|
||||
modules/
|
||||
modules/
|
||||
.tool-versions
|
||||
requirements.txt
|
||||
SEMVER
|
||||
@@ -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
@@ -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.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
"""
|
||||
Permanent handle on all user clients
|
||||
"""
|
||||
speckle_clients = []
|
||||
from specklepy.api.client import SpeckleClient
|
||||
|
||||
|
||||
speckle_clients: list[SpeckleClient] = []
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
+58
-45
@@ -1,9 +1,13 @@
|
||||
from typing import Tuple
|
||||
import math
|
||||
from typing import Any, 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,27 @@ 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 set_custom_property(key: str, value: Any, blender_object: Object) -> None:
|
||||
try:
|
||||
#Expected c types: float, int, string, float[], int[]
|
||||
blender_object[key] = value
|
||||
except (OverflowError, TypeError) as ex:
|
||||
print(f"Skipping setting property ({key}={value}) on {blender_object.name_full}, Reason: {ex}")
|
||||
except Exception as ex:
|
||||
#TODO: Log this as it's unexpected!!!
|
||||
print(f"Skipping setting property ({key}={value}) on {blender_object.name_full}, Reason: {ex}")
|
||||
|
||||
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)
|
||||
@@ -55,39 +68,28 @@ def add_custom_properties(speckle_object, blender_object):
|
||||
continue
|
||||
|
||||
if isinstance(val, (int, str, float)):
|
||||
blender_object[key] = val
|
||||
set_custom_property(key, val, blender_object)
|
||||
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
|
||||
set_custom_property(key, items, blender_object)
|
||||
elif isinstance(val,dict):
|
||||
for (k,v) in val.items():
|
||||
if not isinstance(v, Base):
|
||||
blender_object[k] = v
|
||||
set_custom_property(k, v, blender_object)
|
||||
|
||||
|
||||
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 +103,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 +136,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 +151,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 +191,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 +205,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 +259,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 +278,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 +303,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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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, str(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()
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
@@ -36,22 +40,21 @@ class LoadUsers(bpy.types.Operator):
|
||||
user.name = profile.userInfo.name
|
||||
user.email = profile.userInfo.email
|
||||
user.company = profile.userInfo.company or ""
|
||||
user.authToken = profile.token
|
||||
try:
|
||||
client = SpeckleClient(
|
||||
host=profile.serverInfo.url,
|
||||
use_ssl="https" in profile.serverInfo.url,
|
||||
)
|
||||
client.authenticate(user.authToken)
|
||||
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 +62,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
|
||||
|
||||
@@ -73,7 +73,6 @@ class SpeckleUserObject(bpy.types.PropertyGroup):
|
||||
name: StringProperty(default="Speckle User")
|
||||
email: StringProperty(default="user@speckle.xyz")
|
||||
company: StringProperty(default="SpeckleSystems")
|
||||
authToken: StringProperty(default="")
|
||||
streams: CollectionProperty(type=SpeckleStreamObject)
|
||||
active_stream: IntProperty(default=0)
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e -o pipefail
|
||||
|
||||
poetry export --only main -o bpy_speckle/requirements.txt
|
||||
@@ -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)
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+11
-8
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user