Compare commits

...

50 Commits

Author SHA1 Message Date
Matteo Cominetti 326f04f67d ci: adds signtool param 2022-09-27 10:07:01 +01:00
JR-Morgan 68972ba8f9 Merge remote-tracking branch 'origin' into jrm/clean-mesh 2022-09-23 13:58:07 +01:00
Alan Rynne 73a028b56f Merge pull request #112 from specklesystems/ci/force-run
ci: Activated both mac runs on build
2022-09-23 14:56:02 +02:00
Alan Rynne 33890ef0ee fix: went too far 2022-09-23 14:28:51 +02:00
Alan Rynne 53fe676ab6 ci: Prettified steps 2022-09-23 14:27:50 +02:00
Alan Rynne f027c7eca4 ci: Activated both mac runs on build 2022-09-23 14:11:19 +02:00
JR-Morgan ea2b6dfb0e feat(connector): Added clean mesh option to recieve geometry with limited disolve + merge verts 2022-09-23 12:36:55 +01:00
JR-Morgan 83610cec38 Added type hinting to codebase 2022-09-21 22:22:07 +01:00
izzy lyseggen 2ed0685b10 fix(ci): soz just me clowning w regex (#108) 2022-08-26 12:09:51 +01:00
izzy lyseggen 877e616188 ci: temp remove mac steps and clean tags (#107) 2022-08-26 12:04:14 +01:00
izzy lyseggen 2e3e258a9a ci: deploy to manager 2 (#106)
* ci: add manager 2 feed

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

* ci: fix conditional logic?

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

* ci: split out pip install win job

* ci: fix modules path

* ci: try reusable command instead

* ci: fix shell specify syntax

* ci: try only mior ver for upgrade py

* ci: specify full py version

* ci: separate mac and build w tags

* ci: fix branch filter

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

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

* refactor(ui): remove some superficial units stuff

* fix(converter): pass brep materials to displayvals

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

* ci: blender 3.1 mac support

* ci: update brew

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

* ci(mac): retry py3.10 symlink

* ci: fix installer paths

* ci: tags and patch ver

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

* chore: update deps

* chore: update specklepy

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

* feat(convert): wip attached props

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

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

* ci: how about now?

* ci: we did it! 💃
2022-02-23 16:42:01 +00:00
izzy lyseggen 8296e48c28 Merge pull request #81 from specklesystems/izzy/displayvals
fix(convert): new `displayValue` to native & specklepy update
2022-02-23 11:56:15 +00:00
izzy lyseggen ca81ac6fd6 feat(convert): list displayvals to native 2022-02-23 11:50:33 +00:00
izzy lyseggen 88212b94b6 fix(users): none commit bug 2022-02-23 11:50:21 +00:00
izzy lyseggen 3375a04007 chore: update specklepy 2022-02-23 11:06:56 +00:00
izzy lyseggen c62538fc8f Merge pull request #78 from specklesystems/izzy/blocks
feat(convert): big refactor !! and blocks 🧊
2022-01-10 17:41:07 +00:00
izzylys fd83e80bbd refactor: final cleanup 2022-01-10 09:40:44 -08:00
izzylys 0a2c1f9f32 refactor(convert): remove unused methods 2022-01-10 09:26:22 -08:00
izzylys 2975737377 feat(convert): col fixes & can_convert_to_native 2022-01-10 09:18:16 -08:00
izzylys 72dea53eee feat(convert): block to speckle 2022-01-10 09:17:51 -08:00
izzylys a1aa267a30 fix(convert): custom props and cols fixes 2022-01-07 10:17:42 -08:00
izzylys df30b65fdb refactor: deleting code is my love language 2022-01-07 09:09:29 -08:00
izzylys 6176abb4a3 refactor(converter): more cleanup 2022-01-06 13:41:43 -08:00
izzylys 5c2d2d195f refactor(converter): to_speckle refactor!! 2022-01-06 11:42:46 -08:00
izzylys 7a784a4a8d fix(convert): unique names for all instances
add the id to the end of the name to ensure this
2022-01-04 13:09:58 -08:00
izzylys 2c8f2c96a9 feat(convert): BIG to native refactor + blocks 2022-01-04 12:42:14 -08:00
izzylys bc7400cb36 Merge branch 'izzy/blocks' of https://github.com/specklesystems/speckle-blender into izzy/blocks 2022-01-03 10:46:35 -08:00
izzy lyseggen 421c68a143 wip: block nonsense from other pc 2022-01-03 18:37:00 +00:00
izzy lyseggen 409adda784 Merge pull request #77 from specklesystems/izzy/nulls-fix
fix(convert): nulls on receive
2022-01-03 17:11:40 +00:00
izzylys f5f1c6a8d0 fix(convert): handle list display val 2022-01-03 17:09:57 +00:00
izzylys 1eca91a030 chore: bump specklepy 2022-01-03 17:09:29 +00:00
izzy lyseggen 6b872f11d9 Merge pull request #75 from specklesystems/izzy/add-parent-type
feat(convert): add parent type to display meshes
2021-12-09 10:59:28 +00:00
izzy lyseggen fd3742b800 feat(convert): add parent type to display meshes
makes it easier to work w geo from places like revit
2021-12-09 10:58:22 +00:00
33 changed files with 2283 additions and 1828 deletions
+245 -62
View File
@@ -7,21 +7,25 @@ orbs:
# Upload artifacts to s3
aws-s3: circleci/aws-s3@2.0.0
jobs:
build-connector: # Reusable job for basic connectors
executor:
name: win/default # comes with python 3.7.3
shell: cmd.exe
commands:
install-specklepy-windows: # Reusable job for installing `specklepy` for windows machines
parameters:
slug:
python-version:
type: string
default: ""
default: "" # leave blank for blender v2.93
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: Install specklepy with python 3.7
- 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('.')
@@ -29,57 +33,143 @@ jobs:
echo "using python version:" $pyver
$specklepy=(python patch_version.py)
python -m pip install --target=./modules-$pyver specklepy==$specklepy
- run:
name: Install python 3.9 and specklepy
shell: powershell.exe
command: |
choco install python --version=3.9.2
$pyarr=(C:\Python39\python.exe --version).split(' ')[1].split('.')
$pyver=($pyarr[0..1] -join '.')
echo "using python version:" $pyver
$specklepy=(python patch_version.py)
C:\Python39\python.exe -m pip install --target=./modules-$pyver specklepy==$specklepy
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
steps:
- checkout
- attach_workspace:
at: ./
- install-specklepy-windows
- install-specklepy-windows:
python-version: "3.9.10"
- install-specklepy-windows:
python-version: "3.10.2"
- run:
name: Patch
shell: powershell.exe
command:
| # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
$tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.1" } else { $env:CIRCLE_TAG }
$semver = $tag.replace("-beta","")
$version = "$($semver).$($env:CIRCLE_BUILD_NUM)"
$channel = "latest"
if($tag -like "*-beta") { $channel = "beta" }
# only create the yml if we have a tag
New-Item -Force "speckle-sharp-ci-tools/Installers/blender/$channel.yml" -ItemType File -Value "version: $version"
echo $version
ls
python patch_version.py $version
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\blender.iss
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers
$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 /Sbyparam=$p
- when:
condition: << parameters.installer >>
steps:
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers
install-specklepy: # due to ujson dep, we need to match the py version to the install
docker:
- image: "cimg/python:<<parameters.tag>>"
build-connector-mac:
macos:
xcode: 12.5.1
parameters:
tag:
default: "3.9"
slug:
type: string
default: "blender-mac"
installername:
type: string
default: "SpeckleBlenderInstall"
runtime:
type: string
default: "osx-x64"
installer:
type: boolean
default: false
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: upgrade pip and install specklepy
name: Install mono
command: |
specklepyver=$(python patch_version.py)
echo installing specklepy $specklepyver
python -m pip install --target=./modules-<<parameters.tag>> specklepy==$specklepyver
- persist_to_workspace:
root: ./
paths:
- modules-*
HOMEBREW_NO_AUTO_UPDATE=1 brew install mono
# Compress build files
- run:
name: Install dotnet
command: curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin
- run:
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:
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
- run:
name: Build Mac installer
command: ~/.dotnet/dotnet publish speckle-sharp-ci-tools/Mac/<<parameters.installername>>/<<parameters.installername>>.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
get-ci-tools: # Clones our ci tools and persists them to the workspace
docker:
- image: cimg/base:2021.01
@@ -109,34 +199,127 @@ jobs:
from: '"speckle-sharp-ci-tools/Installers/"'
to: s3://speckle-releases/installers/
deploy-manager2:
docker:
- image: mcr.microsoft.com/dotnet/sdk:6.0
parameters:
slug:
type: string
os:
type: string
extension:
type: string
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: Install Manager Feed CLI
command: dotnet tool install --global Speckle.Manager.Feed
- run:
name: Upload new 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-]*//')
/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 >>
workflows:
main:
build: # build the installers, but don't persist to workspace for deployment
jobs:
- get-ci-tools:
filters:
branches:
only:
- main
- /ci\/.*/
- build-connector-win:
name: Windows Build
requires:
- get-ci-tools
filters:
branches:
only:
- main
- /ci\/.*/
- build-connector-mac:
name: Mac ARM Build
slug: blender-mac-arm
runtime: osx-arm64
requires:
- get-ci-tools
- build-connector-mac:
name: Mac Intel Build
slug: blender-mac-intel
runtime: osx-x64
requires:
- get-ci-tools
deploy: # build installers and deploy
jobs:
- get-ci-tools:
filters:
tags:
only: /.*/
branches:
only:
- main
- /ci\/.*/
- build-connector:
ignore: /.*/
- build-connector-win:
name: Windows Build&Deploy
slug: blender
installer: true
requires:
- get-ci-tools
filters:
tags:
only: /.*/
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
only:
- main
- /ci\/.*/
ignore: /.*/
- build-connector-mac:
name: Mac ARM Build&Deploy
slug: blender-mac-arm
runtime: osx-arm64
installer: true
requires:
- get-ci-tools
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/
- build-connector-mac:
name: Mac Intel Build&Deploy
slug: blender-mac-intel
runtime: osx-x64
installer: true
requires:
- get-ci-tools
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/
- deploy:
requires:
- get-ci-tools
- build-connector
- Windows Build&Deploy
- Mac ARM Build&Deploy
- Mac Intel Build&Deploy
filters:
tags:
only: /[0-9]+(\.[0-9]+)*/
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/
- deploy-manager2:
slug: blender
os: Win
extension: exe
requires:
- deploy
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
+2 -38
View File
@@ -1,25 +1,4 @@
# MIT License
# Copyright (c) 2018-2021 Tom Svilans
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import bpy
bl_info = {
"name": "SpeckleBlender 2.0",
@@ -33,21 +12,7 @@ bl_info = {
"category": "Scene",
}
import bpy
"""
Import PySpeckle and attempt install if not found
"""
try:
import specklepy
except ModuleNotFoundError as error:
print("Speckle not found.")
# TODO: Implement automatic installation of speckle and dependencies
# to the local Blender module folder
# from .install_dependencies import install_dependencies
# install_dependencies()
"""
Import SpeckleBlender classes
@@ -67,7 +32,6 @@ Add load handler to initialize Speckle when
loading a Blender file
"""
@persistent
def load_handler(dummy):
bpy.ops.speckle.users_load()
@@ -95,7 +59,7 @@ def register():
for cls in speckle_classes:
register_class(cls)
metrics.set_host_app("Blender")
metrics.set_host_app("blender", f"blender {bpy.app.version_string}")
"""
Register all new properties
-1
View File
@@ -11,4 +11,3 @@ def scb_on_mesh_edit(context):
edit_obj = bpy.context.edit_object
if edit_obj is not None and edit_obj.is_updated_data is True:
print("Mesh edited: {}".format(edit_obj))
# print('>>> Update')
+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] = []
+8 -293
View File
@@ -1,241 +1,15 @@
import bpy, idprop
from mathutils import Matrix
from .from_speckle import *
from .to_speckle import *
from .util import *
from bpy_speckle.functions import _report, get_scale_length
from specklepy.objects.geometry import *
from specklepy.objects.other import RenderMaterial
FROM_SPECKLE_SCHEMAS = {
Mesh: import_mesh,
Brep: import_brep,
Curve: import_curve,
Line: import_curve,
Polyline: import_curve,
Polycurve: import_curve,
Arc: import_curve,
}
TO_SPECKLE = {
"MESH": export_mesh,
"CURVE": export_curve,
"EMPTY": export_empty,
}
def set_transform(speckle_object, blender_object):
transform = None
if hasattr(speckle_object, "transform"):
transform = speckle_object.transform
elif (
hasattr(speckle_object, "properties") and speckle_object.properties is not None
):
transform = speckle_object.properties.get("transform", None)
if transform and len(transform) == 16:
mat = Matrix(
[transform[0:4], transform[4:8], transform[8:12], transform[12:16]]
)
blender_object.matrix_world = mat
def add_blender_material(smesh, blender_object) -> None:
"""Add material to a blender object if the corresponding speckle object has a render material"""
if blender_object.data is None:
return
if not hasattr(smesh, "renderMaterial") and not hasattr(smesh, "@renderMaterial"):
return
speckle_mat = getattr(smesh, "renderMaterial", None) or smesh["@renderMaterial"]
mat_name = getattr(speckle_mat, "name", None) or speckle_mat.__dict__.get("@name")
if not mat_name:
mat_name = speckle_mat.applicationId or speckle_mat.id or speckle_mat.get_id()
blender_mat = bpy.data.materials.get(mat_name)
if not blender_mat:
blender_mat = bpy.data.materials.new(mat_name)
# for now, we're not updating these materials. as per tom's suggestion, we should have a toggle
# that enables this as the blender mats will prob be much more complex than whatever is coming in
blender_mat.use_nodes = True
inputs = blender_mat.node_tree.nodes["Principled BSDF"].inputs
inputs["Base Color"].default_value = to_rgba(speckle_mat.diffuse)
inputs["Emission"].default_value = to_rgba(speckle_mat.emissive)
inputs["Roughness"].default_value = speckle_mat.roughness
inputs["Metallic"].default_value = speckle_mat.metalness
inputs["Alpha"].default_value = speckle_mat.opacity
if speckle_mat.opacity < 1:
blender_mat.blend_method = "BLEND"
blender_object.data.materials.append(blender_mat)
def material_to_speckle(blender_object) -> RenderMaterial:
"""Create and return a render material from a blender object"""
if not getattr(blender_object.data, "materials", None):
return
blender_mat = blender_object.data.materials[0]
speckle_mat = RenderMaterial()
speckle_mat.name = blender_mat.name
if blender_mat.use_nodes is True:
inputs = blender_mat.node_tree.nodes["Principled BSDF"].inputs
speckle_mat.diffuse = to_argb_int(inputs["Base Color"].default_value)
speckle_mat.emissive = to_argb_int(inputs["Emission"].default_value)
speckle_mat.roughness = inputs["Roughness"].default_value
speckle_mat.metalness = inputs["Metallic"].default_value
speckle_mat.opacity = inputs["Alpha"].default_value
else:
speckle_mat.diffuse = to_argb_int(blender_mat.diffuse_color)
speckle_mat.metalness = blender_mat.metallic
speckle_mat.roughness = blender_mat.roughness
return speckle_mat
def try_add_property(speckle_object, blender_object, prop, prop_name):
if prop in speckle_object.keys() and speckle_object[prop] is not None:
blender_object[prop_name] = speckle_object[prop]
# def add_dictionary(prop, blender_object, superkey=None):
# for key in prop.keys():
# key_name = "{}.{}".format(superkey, key) if superkey else "{}".format(key)
# if isinstance(prop[key], dict):
# subtype = prop[key].get("type", None)
# if subtype and subtype in FROM_SPECKLE.keys():
# continue
# else:
# add_dictionary(prop[key], blender_object, key_name)
# elif hasattr(prop[key], "type"):
# subtype = prop[key].type
# if subtype and subtype in FROM_SPECKLE.keys():
# continue
# else:
# try:
# blender_object[key_name] = prop[key]
# except KeyError:
# pass
def add_custom_properties(speckle_object, blender_object):
if blender_object is None:
return
blender_object["_speckle_type"] = type(speckle_object).__name__
# blender_object['_speckle_name'] = "SpeckleObject"
ignore = ["_chunkable", "_units", "units"]
if hasattr(speckle_object, "applicationId"):
blender_object["applicationId"] = speckle_object.applicationId
for key in speckle_object.get_dynamic_member_names():
if key in ignore:
continue
if isinstance(speckle_object[key], (int, str, float, dict)):
blender_object[key] = speckle_object[key]
def dict_to_speckle_object(data):
if "type" in data.keys() and data["type"] in SCHEMAS.keys():
obj = SCHEMAS[data["type"]].parse_obj(data)
for key in obj.properties.keys():
if isinstance(obj.properties[key], dict):
obj.properties[key] = dict_to_speckle_object(obj.properties[key])
elif isinstance(obj.properties[key], list):
for i in range(len(obj.properties[key])):
if isinstance(obj.properties[key][i], dict):
obj.properties[key][i] = dict_to_speckle_object(
obj.properties[key][i]
)
return obj
else:
for key in data.keys():
if isinstance(data[key], dict):
data[key] = dict_to_speckle_object(data[key])
elif isinstance(data[key], list):
for i in range(len(data[key])):
if isinstance(data[key][i], dict):
data[key][i] = dict_to_speckle_object(data[key][i])
return data
def from_speckle_object(speckle_object, scale, name=None):
speckle_name = (
name
or getattr(speckle_object, "name", None)
or speckle_object.speckle_type + f" -- {speckle_object.id}"
)
units = getattr(speckle_object, "units", None)
if units:
scale = get_scale_length(units) / bpy.context.scene.unit_settings.scale_length
# try native conversion
if type(speckle_object) in FROM_SPECKLE_SCHEMAS.keys():
print("Got object type: {}".format(type(speckle_object)))
try:
obdata = FROM_SPECKLE_SCHEMAS[type(speckle_object)](
speckle_object, scale, speckle_name
)
except Exception as e: # conversion error
_report(f"Error converting {speckle_object} \n{e}")
return None
if speckle_name in bpy.data.objects.keys():
blender_object = bpy.data.objects[speckle_name]
blender_object.data = obdata
blender_object.matrix_world = Matrix()
if hasattr(obdata, "materials"):
blender_object.data.materials.clear()
else:
blender_object = bpy.data.objects.new(speckle_name, obdata)
blender_object.speckle.object_id = str(speckle_object.id)
blender_object.speckle.enabled = True
add_custom_properties(speckle_object, blender_object)
add_blender_material(speckle_object, blender_object)
# TODO: chat with tom re transforms
# set_transform(speckle_object, blender_object)
return blender_object
# try display mesh
mesh = getattr(
speckle_object, "displayMesh", getattr(speckle_object, "displayValue", None)
)
if mesh:
return from_speckle_object(mesh, scale, speckle_name)
# return none if fail
_report("Invalid input: {}".format(speckle_object))
return None
def get_speckle_subobjects(attr, scale, name):
from bpy_speckle.convert.to_native import convert_to_native
from specklepy.objects.base import Base
def get_speckle_subobjects(attr: dict | Base, scale: float, name: str) -> list:
subobjects = []
for key in attr.keys():
keys = attr.keys() if isinstance(attr, dict) else attr.get_dynamic_member_names()
for key in keys:
if isinstance(attr[key], dict):
subtype = attr[key].get("type", None)
if subtype:
name = "{}.{}".format(name, key)
# print("{} :: {}".format(name, subtype))
subobject = from_speckle_object(attr[key], scale, name)
add_custom_properties(attr[key], subobject)
name = f"{name}.{key}"
subobject = convert_to_native(attr[key], name)
subobjects.append(subobject)
props = attr[key].get("properties", None)
@@ -245,69 +19,10 @@ def get_speckle_subobjects(attr, scale, name):
subtype = attr[key].type
if subtype:
name = "{}.{}".format(name, key)
# print("{} :: {}".format(name, subtype))
subobject = from_speckle_object(attr[key], scale, name)
add_custom_properties(attr[key], subobject)
subobject = convert_to_native(attr[key], name)
subobjects.append(subobject)
props = attr[key].get("properties", None)
if props:
subobjects.extend(get_speckle_subobjects(props, scale, name))
return subobjects
ignored_keys = [
"speckle",
"_speckle_type",
"_speckle_name",
"_speckle_transform",
"_RNA_UI",
"transform",
"_units",
"_chunkable",
]
def get_blender_custom_properties(obj, max_depth=1000):
global ignored_keys
if max_depth < 0:
return obj
if hasattr(obj, "keys"):
return {
key: get_blender_custom_properties(obj[key], max_depth - 1)
for key in obj.keys()
if key not in ignored_keys and not key.startswith("_")
}
elif isinstance(obj, (list, tuple, idprop.types.IDPropertyArray)):
return [get_blender_custom_properties(o, max_depth - 1) for o in obj]
else:
return obj
def to_speckle_object(blender_object, scale, desgraph=None):
blender_type = blender_object.type
speckle_objects = []
speckle_material = material_to_speckle(blender_object)
if blender_type in TO_SPECKLE.keys():
if desgraph:
blender_object = blender_object.evaluated_get(desgraph)
converted = TO_SPECKLE[blender_type](blender_object, blender_object.data, scale)
if isinstance(converted, list):
speckle_objects.extend([c for c in converted if c != None])
for so in speckle_objects:
so.properties = get_blender_custom_properties(blender_object)
so.applicationId = so.properties.pop("applicationId", None)
if speckle_material:
so["renderMaterial"] = speckle_material
# Set object transform
so.properties["transform"] = [y for x in blender_object.matrix_world for y in x]
# _report(speckle_objects)
return speckle_objects
@@ -1,3 +0,0 @@
from .mesh import import_mesh
from .curve import import_curve
from .brep import import_brep
-24
View File
@@ -1,24 +0,0 @@
import bpy
from .mesh import to_bmesh
from bpy_speckle.util import find_key_case_insensitive
def import_brep(speckle_brep, scale, name=None):
if not name:
name = speckle_brep.geometryHash or speckle_brep.id
display = getattr(
speckle_brep, "displayMesh", getattr(speckle_brep, "displayValue", None)
)
if display:
if name in bpy.data.meshes.keys():
mesh = bpy.data.meshes[name]
else:
mesh = bpy.data.meshes.new(name=name)
to_bmesh(display, mesh, name, scale)
# add_custom_properties(speckle_brep[dvKey], mesh)
else:
mesh = None
return mesh
-237
View File
@@ -1,237 +0,0 @@
import bpy, math
from bpy_speckle.util import find_key_case_insensitive
import mathutils
from specklepy.objects.geometry import *
CONVERT = {}
def import_line(scurve, bcurve, scale):
line = bcurve.splines.new("POLY")
line.points.add(1)
line.points[0].co = (
float(scurve.start.x) * scale,
float(scurve.start.y) * scale,
float(scurve.start.z) * scale,
1,
)
if scurve.end:
line.points[1].co = (
float(scurve.end.x) * scale,
float(scurve.end.y) * scale,
float(scurve.end.z) * scale,
1,
)
return line
CONVERT[Line] = import_line
def import_polyline(scurve, bcurve, scale):
# value = find_key_case_insensitive(scurve, "value")
value = scurve.value
if value:
N = int(len(value) / 3)
polyline = bcurve.splines.new("POLY")
if hasattr(scurve, "closed"):
polyline.use_cyclic_u = scurve.closed
# if "closed" in scurve.keys():
# polyline.use_cyclic_u = scurve["closed"]
polyline.points.add(N - 1)
for i in range(N):
polyline.points[i].co = (
float(value[i * 3]) * scale,
float(value[i * 3 + 1]) * scale,
float(value[i * 3 + 2]) * scale,
1,
)
return polyline
CONVERT[Polyline] = import_polyline
def import_nurbs_curve(scurve, bcurve, scale):
# points = find_key_case_insensitive(scurve, "points")
points = scurve.points
if points:
N = int(len(points) / 3)
nurbs = bcurve.splines.new("NURBS")
if hasattr(scurve, "closed"):
nurbs.use_cyclic_u = scurve.closed != 0
nurbs.points.add(N - 1)
for i in range(N):
nurbs.points[i].co = (
float(points[i * 3]) * scale,
float(points[i * 3 + 1]) * scale,
float(points[i * 3 + 2]) * scale,
1,
)
if len(scurve.weights) == len(nurbs.points):
for i, w in enumerate(scurve.weights):
nurbs.points[i].weight = w
# TODO: anaylize curve knots to decide if use_endpoint_u or use_bezier_u should be enabled
# nurbs.use_endpoint_u = True
nurbs.order_u = scurve.degree + 1
return nurbs
CONVERT[Curve] = import_nurbs_curve
def import_arc(rcurve, bcurve, scale):
"""
Convert Arc object
TODO: improve Blender representation of arc
"""
plane = rcurve.plane
if not plane:
return
origin = plane.origin
normal = mathutils.Vector([plane.normal.x, plane.normal.y, plane.normal.z])
xaxis = plane.xdir
yaxis = plane.ydir
radius = rcurve.radius * scale
startAngle = rcurve.startAngle
endAngle = rcurve.endAngle
startQuat = mathutils.Quaternion(normal, startAngle)
endQuat = mathutils.Quaternion(normal, endAngle)
"""
Get start and end vectors, centre point, angles, etc.
"""
r1 = mathutils.Vector([plane.xdir.x, plane.xdir.y, plane.xdir.z])
r1.rotate(startQuat)
r2 = mathutils.Vector([plane.xdir.x, plane.xdir.y, plane.xdir.z])
r2.rotate(endQuat)
c = mathutils.Vector([plane.origin.x, plane.origin.y, plane.origin.z]) * scale
spt = c + r1 * radius
ept = c + r2 * radius
angle = endAngle - startAngle
t1 = normal.cross(r1)
"""
Initialize arc data and calculate subdivisions
"""
arc = bcurve.splines.new("NURBS")
arc.use_cyclic_u = False
Ndiv = max(int(math.floor(angle / 0.3)), 2)
step = angle / float(Ndiv)
stepQuat = mathutils.Quaternion(normal, step)
tan = math.tan(step / 2) * radius
arc.points.add(Ndiv + 1)
"""
Set start and end points
"""
arc.points[0].co = (spt.x, spt.y, spt.z, 1)
arc.points[Ndiv + 1].co = (ept.x, ept.y, ept.z, 1)
"""
Set intermediate points
"""
for i in range(Ndiv):
t1 = normal.cross(r1)
pt = c + r1 * radius + t1 * tan
arc.points[i + 1].co = (pt.x, pt.y, pt.z, 1)
r1.rotate(stepQuat)
"""
Set curve settings
"""
arc.use_endpoint_u = True
arc.order_u = 3
return arc
CONVERT[Arc] = import_arc
def import_null(speckle_object, bcurve, scale):
"""
Handle unsupported types
"""
print("Failed to convert type", speckle_object["type"])
return None
def import_polycurve(scurve, bcurve, scale):
"""
Convert Polycurve object
"""
segments = scurve.segments
curves = []
for seg in segments:
speckle_type = type(seg)
if speckle_type in CONVERT.keys():
# segcurve = SCHEMAS[speckle_type].parse_obj(seg)
curves.append(CONVERT[speckle_type](seg, bcurve, scale))
else:
print("Unsupported curve type: {}".format(speckle_type))
return curves
CONVERT[Polycurve] = import_polycurve
def import_curve(speckle_curve, scale, name=None):
"""
Convert Curve object
"""
if not name:
name = speckle_curve.geometryHash or speckle_curve.id or "SpeckleCurve"
if name in bpy.data.curves.keys():
curve_data = bpy.data.curves[name]
else:
curve_data = bpy.data.curves.new(name, type="CURVE")
curve_data.dimensions = "3D"
curve_data.resolution_u = 12
if type(speckle_curve) not in CONVERT.keys():
print("Unsupported curve type: {}".format(speckle_curve.type))
return None
CONVERT[type(speckle_curve)](speckle_curve, curve_data, scale)
return curve_data
-145
View File
@@ -1,145 +0,0 @@
import bpy, bmesh, struct
import base64
from bpy_speckle.functions import _report
def add_vertices(smesh, bmesh, scale=1.0):
sverts = smesh.vertices
if sverts and len(sverts) > 0:
for i in range(0, len(sverts), 3):
bmesh.verts.new(
(
float(sverts[i]) * scale,
float(sverts[i + 1]) * scale,
float(sverts[i + 2]) * scale,
)
)
bmesh.verts.ensure_lookup_table()
def add_faces(smesh, bmesh, smooth=False):
sfaces = smesh.faces
if sfaces and len(sfaces) > 0:
i = 0
while i < len(sfaces):
n = sfaces[i]
if n < 3:
n += 3 # 0 -> 3, 1 -> 4
i += 1
try:
f = bmesh.faces.new([bmesh.verts[int(x)] for x in sfaces[i : i + n]])
f.smooth = smooth
except Exception as e:
_report(f"Failed to create face for mesh {smesh.id} \n{e}")
i += n
bmesh.faces.ensure_lookup_table()
bmesh.verts.index_update()
def add_colors(smesh, bmesh):
scolors = smesh.colors
if scolors:
colors = []
if len(scolors) > 0:
for i in range(len(scolors)):
col = int(scolors[i])
(a, r, g, b) = [
int(x) for x in struct.unpack("!BBBB", struct.pack("!i", col))
]
colors.append(
(
float(r) / 255.0,
float(g) / 255.0,
float(b) / 255.0,
float(a) / 255.0,
)
)
# Make vertex colors
if len(scolors) == len(bmesh.verts):
color_layer = bmesh.loops.layers.color.new("Col")
for face in bmesh.faces:
for loop in face.loops:
loop[color_layer] = colors[loop.vert.index]
def add_uv_coords(smesh, bmesh):
if not hasattr(smesh, "properties"):
return
sprops = smesh.properties
if sprops:
texKey = ""
if "texture_coordinates" in sprops.keys():
texKey = "texture_coordinates"
elif "TextureCoordinates" in sprops.keys():
texKey = "TextureCoordinates"
if texKey != "":
try:
decoded = base64.b64decode(sprops[texKey]).decode("utf-8")
s_uvs = decoded.split()
uv = []
if int(len(s_uvs) / 2) == len(bmesh.verts):
for i in range(0, len(s_uvs), 2):
uv.append((float(s_uvs[i]), float(s_uvs[i + 1])))
else:
print(len(s_uvs) * 2)
print(len(bmesh.verts))
print("Failed to match UV coordinates to vert data.")
# Make UVs
uv_layer = bmesh.loops.layers.uv.verify()
for f in bmesh.faces:
for l in f.loops:
luv = l[uv_layer]
luv.uv = uv[l.vert.index]
except:
print("Failed to decode texture coordinates.")
raise
del smesh.properties[texKey]
def to_bmesh(speckle_mesh, blender_mesh, name="SpeckleMesh", scale=1.0):
bm = bmesh.new()
add_vertices(speckle_mesh, bm, scale)
add_faces(speckle_mesh, bm)
add_colors(speckle_mesh, bm)
add_uv_coords(speckle_mesh, bm)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bm.to_mesh(blender_mesh)
bm.free()
return blender_mesh
def import_mesh(speckle_mesh, scale=1.0, name=None):
"""
Convert Mesh object
"""
if not name:
name = speckle_mesh.geometryHash or speckle_mesh.id
if name in bpy.data.meshes.keys():
mesh = bpy.data.meshes[name]
else:
mesh = bpy.data.meshes.new(name=name)
to_bmesh(speckle_mesh, mesh, name, scale)
return mesh
@@ -1,6 +0,0 @@
import bpy, bmesh, struct
import base64
def import_plane(speckle_plane, scale=1.0, name=None):
return None
+392
View File
@@ -0,0 +1,392 @@
import math
from bpy_speckle.functions import get_scale_length, _report
import mathutils
import bpy, bmesh, bpy_types
from specklepy.objects.other import *
from specklepy.objects.geometry import *
from bpy.types import Object
from .util import (
add_blender_material,
add_custom_properties,
add_vertices,
add_faces,
add_colors,
add_uv_coords,
)
SUPPORTED_CURVES = (Line, Polyline, Curve, Arc, Polycurve)
CAN_CONVERT_TO_NATIVE = (
Mesh,
Brep,
*SUPPORTED_CURVES,
Transform,
BlockDefinition,
BlockInstance,
)
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
_report(f"Could not convert unsupported Speckle object: {speckle_object}")
return False
def convert_to_native(speckle_object: Base, name: str = None) -> 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 None
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
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
return [convert_to_native(mesh) for mesh in meshes]
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 speckle_type in SUPPORTED_CURVES:
obj_data = icurve_to_native(speckle_object, name=speckle_name, scale=scale)
elif speckle_type is Transform:
obj_data = transform_to_native(speckle_object, scale=scale)
elif speckle_type is BlockDefinition:
obj_data = block_def_to_native(speckle_object, scale=scale)
elif speckle_type is BlockInstance:
obj_data = block_instance_to_native(speckle_object, scale=scale)
else:
_report(f"Unsupported type {speckle_type}")
return None
except Exception as ex: # conversion error
_report(f"Error converting {speckle_object} \n{ex}")
return None
if speckle_name in bpy.data.objects.keys():
blender_object = bpy.data.objects[speckle_name]
blender_object.data = (
obj_data.data if isinstance(obj_data, bpy_types.Object) else obj_data
)
blender_object.matrix_world = (
blender_object.matrix_world
if speckle_type is BlockInstance
else mathutils.Matrix()
)
if hasattr(obj_data, "materials"):
blender_object.data.materials.clear()
else:
blender_object = (
obj_data
if isinstance(obj_data, bpy_types.Object)
else bpy.data.objects.new(speckle_name, obj_data)
)
blender_object.speckle.object_id = str(speckle_object.id)
blender_object.speckle.enabled = True
add_custom_properties(speckle_object, blender_object)
add_blender_material(speckle_object, blender_object)
return blender_object
def mesh_to_native(speckle_mesh: Mesh, name: str, scale=1.0) -> 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)
bm = bmesh.new()
add_vertices(speckle_mesh, bm, scale)
add_faces(speckle_mesh, bm)
add_colors(speckle_mesh, bm)
add_uv_coords(speckle_mesh, bm)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bm.to_mesh(blender_mesh)
bm.free()
return blender_mesh
def line_to_native(speckle_curve: Line, blender_curve: bpy.types.Curve, scale: float) -> bpy.types.Spline | None:
line = blender_curve.splines.new("POLY")
line.points.add(1)
line.points[0].co = (
float(speckle_curve.start.x) * scale,
float(speckle_curve.start.y) * scale,
float(speckle_curve.start.z) * scale,
1,
)
if speckle_curve.end:
line.points[1].co = (
float(speckle_curve.end.x) * scale,
float(speckle_curve.end.y) * scale,
float(speckle_curve.end.z) * scale,
1,
)
return line
def polyline_to_native(scurve: Polyline, bcurve: bpy.types.Curve, scale: float) -> bpy.types.Spline | None:
if value := scurve.value:
N = len(value) // 3
polyline = bcurve.splines.new("POLY")
if hasattr(scurve, "closed"):
polyline.use_cyclic_u = scurve.closed
# if "closed" in scurve.keys():
# polyline.use_cyclic_u = scurve["closed"]
polyline.points.add(N - 1)
for i in range(N):
polyline.points[i].co = (
float(value[i * 3]) * scale,
float(value[i * 3 + 1]) * scale,
float(value[i * 3 + 2]) * scale,
1,
)
return polyline
def nurbs_to_native(scurve: Curve, bcurve: bpy.types.Curve, scale: float) -> bpy.types.Spline | None:
if points := scurve.points:
N = len(points) // 3
nurbs = bcurve.splines.new("NURBS")
if hasattr(scurve, "closed"):
nurbs.use_cyclic_u = scurve.closed != 0
nurbs.points.add(N - 1)
for i in range(N):
nurbs.points[i].co = (
float(points[i * 3]) * scale,
float(points[i * 3 + 1]) * scale,
float(points[i * 3 + 2]) * scale,
1,
)
if len(scurve.weights) == len(nurbs.points):
for i, w in enumerate(scurve.weights):
nurbs.points[i].weight = w
# TODO: anaylize curve knots to decide if use_endpoint_u or use_bezier_u should be enabled
# nurbs.use_endpoint_u = True
nurbs.order_u = scurve.degree + 1
return nurbs
def arc_to_native(rcurve: Arc, bcurve: bpy.types.Curve, scale: float) -> bpy.types.Spline | None:
# TODO: improve Blender representation of arc
plane = rcurve.plane
if not plane:
return None
normal = mathutils.Vector([plane.normal.x, plane.normal.y, plane.normal.z])
radius = rcurve.radius * scale
startAngle = rcurve.startAngle
endAngle = rcurve.endAngle
startQuat = mathutils.Quaternion(normal, startAngle)
endQuat = mathutils.Quaternion(normal, endAngle)
# Get start and end vectors, centre point, angles, etc.
r1 = mathutils.Vector([plane.xdir.x, plane.xdir.y, plane.xdir.z])
r1.rotate(startQuat)
r2 = mathutils.Vector([plane.xdir.x, plane.xdir.y, plane.xdir.z])
r2.rotate(endQuat)
c = mathutils.Vector([plane.origin.x, plane.origin.y, plane.origin.z]) * scale
spt = c + r1 * radius
ept = c + r2 * radius
angle = endAngle - startAngle
t1 = normal.cross(r1)
# Initialize arc data and calculate subdivisions
arc = bcurve.splines.new("NURBS")
arc.use_cyclic_u = False
Ndiv = max(int(math.floor(angle / 0.3)), 2)
step = angle / float(Ndiv)
stepQuat = mathutils.Quaternion(normal, step)
tan = math.tan(step / 2) * radius
arc.points.add(Ndiv + 1)
# Set start and end points
arc.points[0].co = (spt.x, spt.y, spt.z, 1)
arc.points[Ndiv + 1].co = (ept.x, ept.y, ept.z, 1)
# Set intermediate points
for i in range(Ndiv):
t1 = normal.cross(r1)
pt = c + r1 * radius + t1 * tan
arc.points[i + 1].co = (pt.x, pt.y, pt.z, 1)
r1.rotate(stepQuat)
# Set curve settings
arc.use_endpoint_u = True
arc.order_u = 3
return arc
def polycurve_to_native(scurve: Polycurve, bcurve: bpy.types.Curve, scale: float):
"""
Convert Polycurve object
"""
segments = scurve.segments
curves = []
for seg in segments:
speckle_type = type(seg)
if speckle_type in SUPPORTED_CURVES:
curves.append(icurve_to_native_spline(seg, bcurve, scale=scale))
else:
_report(f"Unsupported curve type: {speckle_type}")
return curves
def icurve_to_native_spline(speckle_curve: Base, blender_curve: bpy.types.Curve, scale=1.0):
curve_type = type(speckle_curve)
if curve_type is Line:
return line_to_native(speckle_curve, blender_curve, scale)
if curve_type is Polyline:
return polyline_to_native(speckle_curve, blender_curve, scale)
if curve_type is Curve:
return nurbs_to_native(speckle_curve, blender_curve, scale)
if curve_type is Polycurve:
return polycurve_to_native(speckle_curve, blender_curve, scale)
if curve_type is Arc:
return arc_to_native(speckle_curve, blender_curve, scale)
def icurve_to_native(speckle_curve: Base, name=None, scale=1.0) -> Curve | None:
curve_type = type(speckle_curve)
if curve_type not in SUPPORTED_CURVES:
_report(f"Unsupported curve type: {curve_type}")
return None
name = name or f"{curve_type} -- {speckle_curve.id}"
blender_curve = (
bpy.data.curves[name]
if name in bpy.data.curves.keys()
else bpy.data.curves.new(name, type="CURVE")
)
blender_curve.dimensions = "3D"
blender_curve.resolution_u = 12
icurve_to_native_spline(speckle_curve, blender_curve, scale)
return blender_curve
def transform_to_native(transform: Transform, scale=1.0) -> mathutils.Matrix:
mat = mathutils.Matrix(
[
transform.value[:4],
transform.value[4:8],
transform.value[8:12],
transform.value[12:16],
]
)
# scale the translation
for i in range(3):
mat[i][3] *= scale
return mat
def block_def_to_native(definition: BlockDefinition, scale=1.0) -> bpy.types.Collection:
native_def = bpy.data.collections.get(definition.name)
if native_def:
return native_def
native_def = bpy.data.collections.new(definition.name)
native_def["applicationId"] = definition.applicationId
for geo in definition.geometry:
if b_obj := convert_to_native(geo):
native_def.objects.link(
b_obj
if isinstance(b_obj, bpy_types.Object)
else bpy.data.objects.new(b_obj.name, b_obj)
)
return native_def
def block_instance_to_native(instance: BlockInstance, scale=1.0) -> 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_instance = bpy.data.objects.new(name, None)
add_custom_properties(instance, native_instance)
native_instance["name"] = getattr(instance, 'name', None) or instance.blockDefinition.name
# hide the instance axes so they don't clutter the viewport
native_instance.empty_display_size = 0
native_instance.instance_collection = native_def
native_instance.instance_type = "COLLECTION"
native_instance.matrix_world = transform_to_native(instance.transform, scale)
return native_instance
+330
View File
@@ -0,0 +1,330 @@
import bpy
from bpy.types import MeshVertColor, MeshVertex, Object
from specklepy.objects.geometry import Mesh, Curve, Interval, Box, Point, Polyline
from specklepy.objects.other import *
from bpy_speckle.functions import _report
from bpy_speckle.convert.util import (
get_blender_custom_properties,
make_knots,
to_argb_int,
)
UNITS = "m"
CAN_CONVERT_TO_SPECKLE = ("MESH", "CURVE", "EMPTY")
def convert_to_speckle(blender_object: Object, scale: float, units: str, desgraph=None) -> list | None:
global UNITS
UNITS = units
blender_type = blender_object.type
if blender_type not in CAN_CONVERT_TO_SPECKLE:
return None
speckle_objects = []
speckle_material = material_to_speckle(blender_object)
if desgraph:
blender_object = blender_object.evaluated_get(desgraph)
converted = None
if blender_type == "MESH":
converted = mesh_to_speckle(blender_object, blender_object.data, scale)
elif blender_type == "CURVE":
converted = icurve_to_speckle(blender_object, blender_object.data, scale)
elif blender_type == "EMPTY":
converted = empty_to_speckle(blender_object, scale)
if not converted:
return None
if isinstance(converted, list):
speckle_objects.extend([c for c in converted if c != None])
else:
speckle_objects.append(converted)
for so in speckle_objects:
so.properties = get_blender_custom_properties(blender_object)
so.applicationId = so.properties.pop("applicationId", None)
if speckle_material:
so["renderMaterial"] = speckle_material
# Set object transform
if blender_type != "EMPTY":
so.properties["transform"] = transform_to_speckle(
blender_object.matrix_world
)
return speckle_objects
def mesh_to_speckle(blender_object: Object, data: bpy.types.Mesh, scale=1.0) -> List[Mesh]:
if data.loop_triangles is None or len(data.loop_triangles) < 1:
data.calc_loop_triangles()
mat = blender_object.matrix_world
verts = [tuple(mat @ x.co * scale) for x in data.vertices]
faces = [p.vertices for p in data.polygons]
unit_system = bpy.context.scene.unit_settings.system
sm = Mesh(
name=blender_object.name,
vertices=list(sum(verts, ())),
faces=[],
colors=[],
textureCoordinates=[],
units=UNITS,
bbox=Box(area=0.0, volume=0.0),
)
if data.uv_layers.active:
for vt in data.uv_layers.active.data:
sm.textureCoordinates.extend([vt.uv.x, vt.uv.y])
for f in faces:
n = len(f)
if n == 3:
sm.faces.append(0)
elif n == 4:
sm.faces.append(1)
else:
sm.faces.append(n)
sm.faces.extend(f)
# TODO: figure out how to align vertex colors and vertices consistantly in receiving applications
# we are seeing the same issue as with texture coordinate alignment
#if data.color_attributes.active_color:
# sm.colors = [to_argb_int(x.color) for x in data.color_attributes.active_color.data]
return [sm]
def bezier_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: float, name:str = None) -> Curve:
degree = 3
closed = spline.use_cyclic_u
points = []
for i, bp in enumerate(spline.bezier_points):
if i > 0:
points.append(tuple(matrix @ bp.handle_left * scale))
points.append(tuple(matrix @ bp.co * scale))
if i < len(spline.bezier_points) - 1:
points.append(tuple(matrix @ bp.handle_right * scale))
if closed:
points.extend(
(
tuple(matrix @ spline.bezier_points[-1].handle_right * scale),
tuple(matrix @ spline.bezier_points[0].handle_left * scale),
tuple(matrix @ spline.bezier_points[0].co * scale),
)
)
num_points = len(points)
knot_count = num_points + degree - 1
knots = [0] * knot_count
for i in range(1, len(knots)):
knots[i] = i // 3
length = spline.calc_length()
domain = Interval(start=0, end=length, totalChildrenCount=0)
return Curve(
name=name,
degree=degree,
closed=spline.use_cyclic_u,
periodic=spline.use_cyclic_u,
points=list(sum(points, ())), # magic (flatten list of tuples)
weights=[1] * num_points,
knots=knots,
rational=False,
area=0,
volume=0,
length=length,
domain=domain,
units=UNITS,
bbox=Box(area=0.0, volume=0.0),
)
def nurbs_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: float, name:str = None) -> Curve:
knots = make_knots(spline)
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
degree = spline.order_u - 1
length = spline.calc_length()
domain = Interval(start=0, end=length, totalChildrenCount=0)
return Curve(
name=name,
degree=degree,
closed=spline.use_cyclic_u,
periodic=spline.use_cyclic_u,
points=list(sum(points, ())), # magic (flatten list of tuples)
weights=[pt.weight for pt in spline.points],
knots=knots,
rational=False,
area=0,
volume=0,
length=length,
domain=domain,
units=UNITS,
bbox=Box(area=0.0, volume=0.0),
)
def poly_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: float, name: str = None) -> Polyline:
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
length = spline.calc_length()
domain = Interval(start=0, end=length, totalChildrenCount=0)
return Polyline(
name=name,
closed=bool(spline.use_cyclic_u),
value=list(sum(points, ())), # magic (flatten list of tuples)
length=length,
domain=domain,
bbox=Box(area=0.0, volume=0.0),
area=0,
units=UNITS,
)
def icurve_to_speckle(blender_object: Object, data: bpy.types.Curve, scale=1.0) -> List[Base] | None:
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
if blender_object.type != "CURVE":
return None
blender_object = blender_object.evaluated_get(bpy.context.view_layer.depsgraph)
mat = blender_object.matrix_world
curves = []
if data.bevel_mode == "OBJECT" and data.bevel_object != None:
mesh = mesh_to_speckle(blender_object, blender_object.to_mesh(), scale)
curves.extend(mesh)
for spline in data.splines:
if spline.type == "BEZIER":
curves.append(bezier_to_speckle(mat, spline, scale, blender_object.name))
elif spline.type == "NURBS":
curves.append(nurbs_to_speckle(mat, spline, scale, blender_object.name))
elif spline.type == "POLY":
curves.append(poly_to_speckle(mat, spline, scale, blender_object.name))
return curves
def ngons_to_speckle_polylines(blender_object: Object, data: bpy.types.Mesh, scale=1.0) -> List[Polyline] | None:
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
if blender_object.type != "MESH":
return None
mat = blender_object.matrix_world
verts = data.vertices
polylines = []
for i, poly in enumerate(data.polygons):
value = []
for v in poly.vertices:
value.extend(mat @ verts[v].co * scale)
domain = Interval(start=0, end=1)
poly = Polyline(
name="{}_{}".format(blender_object.name, i),
closed=True,
value=value, # magic (flatten list of tuples)
length=0,
domain=domain,
bbox=Box(area=0.0, volume=0.0),
area=0,
units=UNITS,
)
polylines.append(poly)
return polylines
def material_to_speckle(blender_object: Object) -> RenderMaterial | None:
"""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
speckle_mat = RenderMaterial()
speckle_mat.name = blender_mat.name
if blender_mat.use_nodes is True and blender_mat.node_tree.nodes.get(
"Principled BSDF"
):
inputs = blender_mat.node_tree.nodes["Principled BSDF"].inputs
speckle_mat.diffuse = to_argb_int(inputs["Base Color"].default_value)
speckle_mat.emissive = to_argb_int(inputs["Emission"].default_value)
speckle_mat.roughness = inputs["Roughness"].default_value
speckle_mat.metalness = inputs["Metallic"].default_value
speckle_mat.opacity = inputs["Alpha"].default_value
else:
speckle_mat.diffuse = to_argb_int(blender_mat.diffuse_color)
speckle_mat.metalness = blender_mat.metallic
speckle_mat.roughness = blender_mat.roughness
return speckle_mat
def transform_to_speckle(blender_transform: List[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):
value[i] *= scale
return Transform(value=value, units=UNITS)
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))
block_def = BlockDefinition(
units=UNITS,
name=blender_definition.name,
geometry=geometry,
basePoint=Point(units=UNITS),
)
blender_props = get_blender_custom_properties(blender_definition)
block_def.applicationId = blender_props.pop("applicationId", None)
return block_def
def block_instance_to_speckle(blender_instance: Object, scale=1.0):
return BlockInstance(
blockDefinition=block_def_to_speckle(
blender_instance.instance_collection, scale
),
transform=transform_to_speckle(blender_instance.matrix_world),
name=blender_instance.name,
units=UNITS,
)
def empty_to_speckle(blender_object: Object, scale=1.0) -> BlockInstance | None:
# probably an instance collection (block) so let's try it
try:
geo = blender_object.instance_collection.objects.items()
return block_instance_to_speckle(blender_object, scale)
except AttributeError as err:
_report(
f"No instance collection found in empty. Skipping object {blender_object.name}"
)
return None
@@ -1,3 +0,0 @@
from .mesh import export_mesh
from .curve import export_curve, export_ngons_as_polylines
from .empty import export_empty
-224
View File
@@ -1,224 +0,0 @@
import bpy, bmesh, struct
from specklepy.objects.geometry import Curve, Interval, Box, Polyline
from bpy_speckle.convert.to_speckle.mesh import export_mesh
UNITS = "m"
def bezier_to_speckle(matrix, spline, scale, name=None):
degree = 3
closed = spline.use_cyclic_u
points = []
for i, bp in enumerate(spline.bezier_points):
if i > 0:
points.append(tuple(matrix @ bp.handle_left * scale))
points.append(tuple(matrix @ bp.co * scale))
if i < len(spline.bezier_points) - 1:
points.append(tuple(matrix @ bp.handle_right * scale))
if closed:
points.append(tuple(matrix @ spline.bezier_points[-1].handle_right * scale))
points.append(tuple(matrix @ spline.bezier_points[0].handle_left * scale))
points.append(tuple(matrix @ spline.bezier_points[0].co * scale))
num_points = len(points)
knot_count = num_points + degree - 1
knots = [0] * knot_count
for i in range(1, len(knots)):
knots[i] = i // 3
length = spline.calc_length()
domain = Interval(start=0, end=length, totalChildrenCount=0)
return Curve(
name=name,
degree=degree,
closed=spline.use_cyclic_u,
periodic=spline.use_cyclic_u,
points=list(sum(points, ())), # magic (flatten list of tuples)
weights=[1] * num_points,
knots=knots,
rational=False,
area=0,
volume=0,
length=length,
domain=domain,
units=UNITS,
bbox=Box(area=0.0, volume=0.0),
)
def nurbs_to_speckle(matrix, spline, scale, name=None):
knots = makeknots(spline)
# print("knots: {}".format(knots))
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
degree = spline.order_u - 1
length = spline.calc_length()
domain = Interval(start=0, end=length, totalChildrenCount=0)
return Curve(
name=name,
degree=degree,
closed=spline.use_cyclic_u,
periodic=spline.use_cyclic_u,
points=list(sum(points, ())), # magic (flatten list of tuples)
weights=[pt.weight for pt in spline.points],
knots=knots,
rational=False,
area=0,
volume=0,
length=length,
domain=domain,
units=UNITS,
bbox=Box(area=0.0, volume=0.0),
)
def poly_to_speckle(matrix, spline, scale, name=None):
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
length = spline.calc_length()
domain = Interval(start=0, end=length, totalChildrenCount=0)
return Polyline(
name=name,
closed=spline.use_cyclic_u,
value=list(sum(points, ())), # magic (flatten list of tuples)
length=length,
domain=domain,
bbox=Box(area=0.0, volume=0.0),
area=0,
units=UNITS,
)
def export_curve(blender_object, data, scale=1.0):
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
if blender_object.type != "CURVE":
return None
blender_object = blender_object.evaluated_get(bpy.context.view_layer.depsgraph)
mat = blender_object.matrix_world
curves = []
if data.bevel_mode == "OBJECT" and data.bevel_object != None:
mesh = export_mesh(blender_object, blender_object.to_mesh(), scale)
curves.extend(mesh)
for spline in data.splines:
if spline.type == "BEZIER":
curves.append(bezier_to_speckle(mat, spline, scale, blender_object.name))
elif spline.type == "NURBS":
curves.append(nurbs_to_speckle(mat, spline, scale, blender_object.name))
elif spline.type == "POLY":
curves.append(poly_to_speckle(mat, spline, scale, blender_object.name))
return curves
def export_ngons_as_polylines(blender_object, data, scale=1.0):
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
if blender_object.type != "MESH":
return None
mat = blender_object.matrix_world
verts = data.vertices
polylines = []
for i, poly in enumerate(data.polygons):
value = []
for v in poly.vertices:
value.extend(mat @ verts[v].co * scale)
domain = Interval(start=0, end=1)
poly = Polyline(
name="{}_{}".format(blender_object.name, i),
closed=True,
value=value, # magic (flatten list of tuples)
length=0,
domain=domain,
bbox=Box(area=0.0, volume=0.0),
area=0,
units=UNITS,
)
polylines.append(poly)
return polylines
"""
Python implementation of Blender's NURBS curve generation
from: https://blender.stackexchange.com/a/34276
"""
def macro_knotsu(nu):
return nu.order_u + nu.point_count_u + (nu.order_u - 1 if nu.use_cyclic_u else 0)
def macro_segmentsu(nu):
return nu.point_count_u if nu.use_cyclic_u else nu.point_count_u - 1
def makeknots(nu):
knots = [0.0] * (4 + macro_knotsu(nu))
flag = nu.use_endpoint_u + (nu.use_bezier_u << 1)
if nu.use_cyclic_u:
calcknots(knots, nu.point_count_u, nu.order_u, 0)
makecyclicknots(knots, nu.point_count_u, nu.order_u)
else:
calcknots(knots, nu.point_count_u, nu.order_u, flag)
return knots
def calcknots(knots, pnts, order, flag):
pnts_order = pnts + order
if flag == 1:
k = 0.0
for a in range(1, pnts_order + 1):
knots[a - 1] = k
if a >= order and a <= pnts:
k += 1.0
elif flag == 2:
if order == 4:
k = 0.34
for a in range(pnts_order):
knots[a] = math.floor(k)
k += 1.0 / 3.0
elif order == 3:
k = 0.6
for a in range(pnts_order):
if a >= order and a <= pnts:
k += 0.5
knots[a] = math.floor(k)
else:
for a in range(pnts_order):
knots[a] = a
def makecyclicknots(knots, pnts, order):
order2 = order - 1
if order > 2:
b = pnts + order2
for a in range(1, order2):
if knots[b] != knots[b - a]:
break
if a == order2:
knots[pnts + order - 2] += 1.0
b = order
c = pnts + order + order2
for a in range(pnts + order2, c):
knots[a] = knots[a - 1] + (knots[b] - knots[b - 1])
b -= 1
@@ -1,5 +0,0 @@
import bpy, bmesh, struct
def export_default(blender_object, scale=1.0):
return None
-5
View File
@@ -1,5 +0,0 @@
import bpy, bmesh, struct
def export_empty(blender_object, data, scale=1.0):
return None
-44
View File
@@ -1,44 +0,0 @@
import bpy, bmesh, struct
import base64, hashlib
from time import strftime, gmtime
from specklepy.objects.geometry import Mesh, Interval, Box
def export_mesh(blender_object, data, scale=1.0):
if data.loop_triangles is None or len(data.loop_triangles) < 1:
data.calc_loop_triangles()
mat = blender_object.matrix_world
verts = [tuple(mat @ x.co * scale) for x in data.vertices]
faces = [p.vertices for p in data.polygons]
unit_system = bpy.context.scene.unit_settings.system
sm = Mesh(
name=blender_object.name,
vertices=list(sum(verts, ())),
faces=[],
colors=[],
textureCoordinates=[],
units="m" if unit_system == "METRIC" else "ft",
bbox=Box(area=0.0, volume=0.0),
)
if data.uv_layers.active:
for vt in data.uv_layers.active.data:
sm.textureCoordinates.extend([vt.uv.x, vt.uv.y])
for f in faces:
n = len(f)
if n == 3:
sm.faces.append(0)
elif n == 4:
sm.faces.append(1)
else:
sm.faces.append(n)
sm.faces.extend(f)
return [sm]
+295 -1
View File
@@ -1,4 +1,28 @@
import math
from typing import Tuple
from bmesh.types import BMesh
import bpy, struct, idprop
from specklepy.objects.base import Base
from specklepy.objects.geometry import Mesh
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
from bpy_speckle.functions import _report
from bpy.types import Object
IGNORED_PROPERTY_KEYS = {
"id",
"elements",
"displayMesh",
"displayValue",
"speckle_type",
"parameters",
"faces",
"colors",
"vertices",
"renderMaterial",
"textureCoordinates",
"totalChildrenCount"
}
def to_rgba(argb_int: int) -> Tuple[float]:
@@ -13,7 +37,277 @@ def to_rgba(argb_int: int) -> Tuple[float]:
def to_argb_int(diffuse_colour) -> int:
"""Converts an RGBA array to an ARGB integer"""
diffuse_colour = diffuse_colour[-1:] + diffuse_colour[0:3]
diffuse_colour = diffuse_colour[-1:] + diffuse_colour[:3]
diffuse_colour = [int(val * 255) for val in diffuse_colour]
return int.from_bytes(diffuse_colour, byteorder="big", signed=True)
def add_custom_properties(speckle_object: 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)
if app_id:
blender_object["applicationId"] = speckle_object.applicationId
keys = speckle_object.get_dynamic_member_names() if "Geometry" in speckle_object.speckle_type else (set(speckle_object.get_member_names()) - IGNORED_PROPERTY_KEYS)
for key in keys:
val = getattr(speckle_object, key, None)
if val is None:
continue
if isinstance(val, (int, str, float)):
blender_object[key] = val
elif key == "properties" and isinstance(val, Base):
val["applicationId"] = None
add_custom_properties(val, blender_object)
elif isinstance(val, list):
items = [item for item in val if not isinstance(item, Base)]
if items:
blender_object[key] = items
elif isinstance(val,dict):
for (k,v) in val.items():
if not isinstance(v, Base):
blender_object[k] = v
def add_blender_material(speckle_object: Base, blender_object: Object) -> None:
"""Add material to a blender object if the corresponding speckle object has a render material"""
if blender_object.data is None:
return
speckle_mat = getattr(
speckle_object,
"renderMaterial",
getattr(speckle_object, "@renderMaterial", None),
)
if not speckle_mat:
return
mat_name = getattr(speckle_mat, "name", None) or speckle_mat.__dict__.get("@name")
if not mat_name:
mat_name = speckle_mat.applicationId or speckle_mat.id or speckle_mat.get_id()
blender_mat = bpy.data.materials.get(mat_name)
if not blender_mat:
blender_mat = bpy.data.materials.new(mat_name)
# for now, we're not updating these materials. as per tom's suggestion, we should have a toggle
# that enables this as the blender mats will prob be much more complex than whatever is coming in
blender_mat.use_nodes = True
inputs = blender_mat.node_tree.nodes["Principled BSDF"].inputs
inputs["Base Color"].default_value = to_rgba(speckle_mat.diffuse)
inputs["Emission"].default_value = to_rgba(speckle_mat.emissive)
inputs["Roughness"].default_value = speckle_mat.roughness
inputs["Metallic"].default_value = speckle_mat.metalness
inputs["Alpha"].default_value = speckle_mat.opacity
if speckle_mat.opacity < 1:
blender_mat.blend_method = "BLEND"
blender_object.data.materials.append(blender_mat)
def add_vertices(speckle_mesh: Mesh, blender_mesh: BMesh, scale=1.0):
sverts = speckle_mesh.vertices
if sverts and len(sverts) > 0:
for i in range(0, len(sverts), 3):
blender_mesh.verts.new(
(
float(sverts[i]) * scale,
float(sverts[i + 1]) * scale,
float(sverts[i + 2]) * scale,
)
)
blender_mesh.verts.ensure_lookup_table()
def add_faces(speckle_mesh: Mesh, blender_mesh: BMesh, smooth=False):
sfaces = speckle_mesh.faces
if sfaces and len(sfaces) > 0:
i = 0
while i < len(sfaces):
n = sfaces[i]
if n < 3:
n += 3 # 0 -> 3, 1 -> 4
i += 1
try:
f = blender_mesh.faces.new(
[blender_mesh.verts[int(x)] for x in sfaces[i : i + n]]
)
f.smooth = smooth
except Exception as e:
_report(f"Failed to create face for mesh {speckle_mesh.id} \n{e}")
i += n
blender_mesh.faces.ensure_lookup_table()
blender_mesh.verts.index_update()
def add_colors(speckle_mesh: Mesh, blender_mesh: BMesh):
scolors = speckle_mesh.colors
if scolors:
colors = []
if len(scolors) > 0:
for i in range(len(scolors)):
col = int(scolors[i])
(a, r, g, b) = [
int(x) for x in struct.unpack("!BBBB", struct.pack("!i", col))
]
colors.append(
(
float(r) / 255.0,
float(g) / 255.0,
float(b) / 255.0,
float(a) / 255.0,
)
)
# Make vertex colors
if len(scolors) == len(blender_mesh.verts):
color_layer = blender_mesh.loops.layers.color.new("Col")
for face in blender_mesh.faces:
for loop in face.loops:
loop[color_layer] = colors[loop.vert.index]
def add_uv_coords(speckle_mesh: Mesh, blender_mesh: BMesh):
s_uvs = speckle_mesh.textureCoordinates
if not s_uvs:
return
try:
uv = []
if len(s_uvs) // 2 == len(blender_mesh.verts):
uv.extend(
(float(s_uvs[i]), float(s_uvs[i + 1]))
for i in range(0, len(s_uvs), 2)
)
else:
_report(
f"Failed to match UV coordinates to vert data. Blender mesh verts: {len(blender_mesh.verts)}, Speckle UVs * 2: {len(s_uvs) * 2}"
)
return
# Make UVs
uv_layer = blender_mesh.loops.layers.uv.verify()
for f in blender_mesh.faces:
for l in f.loops:
luv = l[uv_layer]
luv.uv = uv[l.vert.index]
except:
_report("Failed to decode texture coordinates.")
raise
ignored_keys = {
"id",
"speckle",
"speckle_type"
"_speckle_type",
"_speckle_name",
"_speckle_transform",
"_RNA_UI",
"elements",
"transform",
"_units",
"_chunkable",
}
def get_blender_custom_properties(obj, max_depth=1000):
if max_depth < 0:
return obj
if hasattr(obj, "keys"):
keys = set(obj.keys()) - ignored_keys
return {
key: get_blender_custom_properties(obj[key], max_depth - 1)
for key in keys
if not key.startswith("_")
}
if isinstance(obj, (list, tuple, idprop.types.IDPropertyArray)):
return [get_blender_custom_properties(o, max_depth - 1) for o in obj]
return obj
"""
Python implementation of Blender's NURBS curve generation for to Speckle conversion
from: https://blender.stackexchange.com/a/34276
"""
def macro_knotsu(nu):
return nu.order_u + nu.point_count_u + (nu.order_u - 1 if nu.use_cyclic_u else 0)
def macro_segmentsu(nu):
return nu.point_count_u if nu.use_cyclic_u else nu.point_count_u - 1
def make_knots(nu):
knots = [0.0] * (4 + macro_knotsu(nu))
flag = nu.use_endpoint_u + (nu.use_bezier_u << 1)
if nu.use_cyclic_u:
calc_knots(knots, nu.point_count_u, nu.order_u, 0)
makecyclicknots(knots, nu.point_count_u, nu.order_u)
else:
calc_knots(knots, nu.point_count_u, nu.order_u, flag)
return knots
def calc_knots(knots, point_count, order, flag):
pts_order = point_count + order
if flag == 1:
k = 0.0
for a in range(1, pts_order + 1):
knots[a - 1] = k
if a >= order and a <= point_count:
k += 1.0
elif flag == 2:
if order == 4:
k = 0.34
for a in range(pts_order):
knots[a] = math.floor(k)
k += 1.0 / 3.0
elif order == 3:
k = 0.6
for a in range(pts_order):
if a >= order and a <= point_count:
k += 0.5
knots[a] = math.floor(k)
else:
for a in range(pts_order):
knots[a] = a
def makecyclicknots(knots, point_count, order):
order2 = order - 1
if order > 2:
b = point_count + order2
for a in range(1, order2):
if knots[b] != knots[b - a]:
break
if a == order2:
knots[point_count + order - 2] += 1.0
b = order
c = point_count + order + order2
for a in range(point_count + order2, c):
knots[a] = knots[a - 1] + (knots[b] - knots[b - 1])
b -= 1
+1 -33
View File
@@ -1,4 +1,3 @@
from specklepy.api.client import SpeckleClient
from bpy_speckle.clients import speckle_clients
"""
@@ -34,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))
@@ -71,34 +70,3 @@ def _check_speckle_client_user_stream(scene):
print("Account contains no streams.")
return (user, stream)
def _create_stream(user, stream_name, units="Millimeters"):
"""
Create a new stream
"""
# TODO: double-check, but this should not be accessible through the UI if
# there aren't any active users anyway
# user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
client = speckle_clients[int(bpy.context.scene.speckle.active_user)]
return client.stream.create(
name=stream_name, description="This is a Blender stream.", is_public=True
)
# TODO: Update stream with properties such as units, etc.
def _delete_stream(client, user, stream):
"""
Delete the active stream
TODO: probably doesn't need to be a separate function and can be
folded into the operator
"""
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
client = speckle_clients[int(context.scene.speckle.active_user)]
if stream:
res = client.streams.delete(stream.id)
_report(res["message"])
-104
View File
@@ -1,104 +0,0 @@
import os, sys, bpy
import ctypes, sys
import os, sys
def modules_path():
# set up addons/modules under the user
# script path. Here we'll install the
# dependencies
modulespath = os.path.normpath(
os.path.join(bpy.utils.script_path_user(), "addons", "modules")
)
if not os.path.exists(modulespath):
os.makedirs(modulespath)
# set user modules path at beginning of paths for earlier hit
if sys.path[1] != modulespath:
sys.path.insert(1, modulespath)
return modulespath
def install_dependencies():
import sys
import os
try:
try:
import pip
except:
print("Installing pip... "),
from subprocess import run as sprun
res = sprun([bpy.app.binary_path_python, "-m", "ensurepip"])
if res.returncode == 0:
import pip
else:
raise Exception("Failed to install pip.")
modulespath = modules_path()
if not os.path.exists(modulespath):
os.makedirs(modulespath)
print("Installing speckle to {}... ".format(modulespath)),
from subprocess import run as sprun
res = sprun(
[
bpy.app.binary_path_python,
"-m",
"pip",
"install",
"-q",
"-t",
"{}".format(modulespath),
"--no-deps",
"pydantic",
]
)
res = sprun(
[
bpy.app.binary_path_python,
"-m",
"pip",
"install",
"-q",
"-t",
"{}".format(modulespath),
"specklepy",
]
)
except:
raise Exception(
"Failed to install dependencies. Please make sure you have pip installed."
)
if __name__ == "__main__":
try:
import specklepy
except:
print("Failed to load speckle.")
from sys import platform
if platform == "win32":
if ctypes.windll.shell32.IsUserAnAdmin():
install_dependencies()
import specklepy
else:
ctypes.windll.shell32.ShellExecuteW(
None, "runas", sys.executable, __file__, None, 1
)
else:
print(
"Platform {} cannot automatically install dependencies.".format(
platform
)
)
raise
-2
View File
@@ -3,7 +3,6 @@ from .object import (
UpdateObject,
ResetObject,
DeleteObject,
UploadObject,
UploadNgonsAsPolylines,
SelectIfSameCustomProperty,
SelectIfHasCustomProperty,
@@ -43,7 +42,6 @@ operator_classes.extend(
UpdateObject,
ResetObject,
DeleteObject,
UploadObject,
UploadNgonsAsPolylines,
SelectIfSameCustomProperty,
SelectIfHasCustomProperty,
+10 -32
View File
@@ -1,23 +1,9 @@
"""
Commit operators
"""
import bpy, os
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
)
from bpy_speckle.functions import (
_check_speckle_client_user_stream,
_create_stream,
get_scale_length,
_report,
)
from bpy_speckle.convert import from_speckle_object
import bpy
from bpy.props import BoolProperty
from bpy_speckle.functions import _check_speckle_client_user_stream
from bpy_speckle.clients import speckle_clients
@@ -32,7 +18,8 @@ class DeleteCommit(bpy.types.Operator):
bl_description = "Delete active commit permanently"
are_you_sure: BoolProperty(
name="Confirm", default=False,
name="Confirm",
default=False,
)
def draw(self, context):
@@ -66,20 +53,11 @@ class DeleteCommit(bpy.types.Operator):
stream = user.streams[user.active_stream]
if len(stream.branches) < 1:
return {"CANCELLED"}
else:
branch = stream.branches[int(stream.branch)]
if len(branch.commits) < 1:
return {"CANCELLED"}
else:
commit = branch.commits[int(branch.commit)]
branch = stream.branches[int(stream.branch)]
if len(branch.commits) < 1:
return {"CANCELLED"}
commit = branch.commits[int(branch.commit)]
deleted = client.commit.delete(stream_id=stream.id, commit_id=commit.id)
deleted = client.commit.delete(stream_id=stream.id, commit_id=commit.id)
return {"FINISHED"}
bpy.ops.speckle.load_user_streams()
context.view_layer.update()
if context.area:
context.area.tag_redraw()
return {"FINISHED"}
+11 -77
View File
@@ -2,19 +2,12 @@
Object operators
"""
import bpy, bmesh, os
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
import bpy
from bpy.props import BoolProperty, EnumProperty
from bpy_speckle.convert.to_speckle import (
convert_to_speckle,
ngons_to_speckle_polylines,
)
from specklepy.api.client import SpeckleClient
from bpy_speckle.convert import to_speckle_object
from bpy_speckle.convert.to_speckle import export_ngons_as_polylines
from bpy_speckle.functions import get_scale_length, _report
from bpy_speckle.clients import speckle_clients
@@ -58,7 +51,7 @@ class UpdateObject(bpy.types.Operator):
stream_units
)
sm = to_speckle_object(active, scale)
sm = convert_to_speckle(active, scale)
_report("Updating object {}".format(sm["_id"]))
client.objects.update(active.speckle.object_id, sm)
@@ -99,8 +92,6 @@ class DeleteObject(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
client = speckle_clients[int(context.scene.speckle.active_user)]
active = context.object
if active.speckle.enabled:
@@ -112,13 +103,11 @@ class DeleteObject(bpy.types.Operator):
]
if existing is None:
return {"CANCELLED"}
# print("Existing: %s" % SpeckleResource.to_json_pretty(existing))
new_objects = [
x
for x in res["resource"]["objects"]
if x["_id"] != active.speckle.object_id
]
# print (SpeckleResource.to_json_pretty(new_objects))
res = client.GetLayers(active.speckle.stream_id)
new_layers = res["resource"]["layers"]
@@ -162,11 +151,12 @@ class UploadNgonsAsPolylines(bpy.types.Operator):
client = speckle_clients[int(context.scene.speckle.active_user)]
stream = user.streams[user.active_stream]
scale = context.scene.unit_settings.scale_length / get_scale_length(
stream.units
)
# scale = context.scene.unit_settings.scale_length / get_scale_length(
# stream.units
# )
scale = 1.0
sp = export_ngons_as_polylines(active, scale)
sp = ngons_to_speckle_polylines(active, scale)
if sp is None:
return {"CANCELLED"}
@@ -175,16 +165,12 @@ class UploadNgonsAsPolylines(bpy.types.Operator):
for polyline in sp:
res = client.objects.create([polyline])
print(res)
if res is None:
_report(client.me)
continue
placeholders.extend(res)
# polyline['_id'] = res['_id']
# placeholders.append({'type':'Placeholder', '_id':res['_id']})
if not placeholders:
return {"CANCELLED"}
@@ -222,58 +208,6 @@ class UploadNgonsAsPolylines(bpy.types.Operator):
layout.prop(self, "clear_stream")
class UploadObject(bpy.types.Operator):
"""
DEPRECATED
Upload an individual object
"""
bl_idname = "speckle.upload_object"
bl_label = "Upload Object"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
active = context.active_object
if active is not None:
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
client = speckle_clients[int(context.scene.speckle.active_user)]
stream = user.streams[user.active_stream]
scale = context.scene.unit_settings.scale_length / get_scale_length(
stream.units
)
sm = to_speckle_object(active, scale)
placeholders = client.objects.create([sm])
if placeholders is None:
return {"CANCELLED"}
sstream = client.streams.get(stream.id)
sstream.objects.extend(placeholders)
N = sstream.layers[-1].objectCount
sstream.layers[-1].objectCount = N + 1
sstream.layers[-1].topology = "0-%s" % (N + 1)
_report("Updating stream %s" % stream.id)
res = client.streams.update(stream["id"], sstream)
_report(res)
active.speckle.enabled = True
active.speckle.object_id = sm.id
active.speckle.stream_id = stream.id
active.speckle.send_or_receive = "send"
context.view_layer.update()
_report("Done.")
return {"FINISHED"}
def get_custom_speckle_props(self, context):
ignore = ["speckle", "cycles", "cycles_visibility"]
+137 -58
View File
@@ -2,40 +2,39 @@
Stream operators
"""
from itertools import chain
from typing import Dict
import bpy, bmesh, os
from math import radians
from typing import Callable, Dict, Iterable
import bpy
from specklepy.api.models import Commit
import webbrowser
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
)
from bpy.types import 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,
_create_stream,
get_scale_length,
_report,
)
from bpy_speckle.convert import to_speckle_object, get_speckle_subobjects
from bpy_speckle.convert.to_speckle import export_ngons_as_polylines
from bpy_speckle.convert import from_speckle_object
from bpy_speckle.convert import get_speckle_subobjects
from bpy_speckle.clients import speckle_clients
from bpy_speckle.operators.users import add_user_stream
from specklepy.api import operations
from specklepy.api.credentials import StreamWrapper
from specklepy.api.wrapper import StreamWrapper
from specklepy.api.resources.stream import Stream
from specklepy.transports.server import ServerTransport
from specklepy.objects import Base
from specklepy.objects.geometry import *
from specklepy.logging.exceptions import SpeckleException
def get_objects_collections(base) -> 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():
@@ -50,7 +49,7 @@ 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: bpy.types.Collection = None) -> List:
"""For handling the weird nested lists that come from Grasshopper"""
objects = []
@@ -67,15 +66,10 @@ 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: 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
object_type = Base.get_registered_type(base.speckle_type)
if (
(object_type and object_type != Base)
or hasattr(base, "displayMesh")
or hasattr(base, "displayValue")
):
if can_convert_to_native(base):
return [base]
# if it's an unknown type, try to drill further down to find convertable objects
@@ -83,21 +77,26 @@ 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):
for item in value:
if isinstance(item, Base):
objects.append(item)
objects.extend(item for item in value if isinstance(item, Base))
if isinstance(value, Base):
col = parent_col.children.get(name)
if not parent_col.children.get(name):
if not col:
col = create_collection(name)
parent_col.children.link(col)
try:
parent_col.children.link(col)
except:
_report(
f"Problem linking collection {col.name} to parent {parent_col.name}; skipping"
)
objects.append({name: get_objects_collections_recursive(value, col)})
return objects
def bases_to_native(context, collections, scale, stream_id, func=None):
def bases_to_native(context: bpy.types.Context, collections: Dict[str, list], scale: float, stream_id: str, func: Callable = None):
for col_name, objects in collections.items():
col = bpy.data.collections[col_name]
existing = get_existing_collection_objs(col)
@@ -117,6 +116,7 @@ def bases_to_native(context, collections, scale, stream_id, func=None):
)
elif isinstance(obj, Base):
base_to_native(context, obj, scale, stream_id, col, existing, func)
else:
_report(
f"Something went wrong when receiving collection: {col_name}"
@@ -128,8 +128,11 @@ 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 = [from_speckle_object(base, scale)]
def base_to_native(context: bpy.types.Context, base: Base, scale: float, stream_id: str, col: bpy.types.Collection, existing: Dict[str, Object], func: Callable = 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))
@@ -154,8 +157,7 @@ def base_to_native(context, base, scale, stream_id, col, existing, func=None):
if (
new_object is None
): # Make sure that the injected function returned an object
new_obj = new_object
_report("Script '{}' returned None.".format(func.__module__))
_report(f"Script '{func.__module__}' returned None.")
continue
new_object.speckle.stream_id = stream_id
@@ -163,7 +165,7 @@ def base_to_native(context, base, scale, stream_id, col, existing, func=None):
if new_object.speckle.object_id in existing.keys():
name = existing[new_object.speckle.object_id].name
existing[new_object.speckle.object_id].name = name + "__deleted"
existing[new_object.speckle.object_id].name = f"{name}__deleted"
new_object.name = name
col.objects.unlink(existing[new_object.speckle.object_id])
@@ -171,7 +173,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:
@@ -183,13 +185,13 @@ 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 != ""
}
@@ -224,9 +226,9 @@ def create_nested_hierarchy(base, hierarchy, objects):
child = child[name]
# TODO: what do we call this attribute?
if not hasattr(child, "objects"):
child["objects"] = []
child["objects"].extend(objects)
if not hasattr(child, "@objects"):
child["@objects"] = []
child["@objects"].extend(objects)
return base
@@ -241,6 +243,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)
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, "clean_meshes")
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
# This seems to be required inorder to select the object here
if obj.name not in context.scene.collection.objects:
context.scene.collection.objects.link(obj)
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
@@ -252,7 +297,7 @@ class ReceiveStreamObjects(bpy.types.Operator):
client = speckle_clients[int(context.scene.speckle.active_user)]
stream = client.stream.get(id=bstream.id)
stream = client.stream.get(id=bstream.id, branch_limit=20)
if stream.branches.totalCount < 1:
return {"CANCELLED"}
@@ -264,7 +309,7 @@ class ReceiveStreamObjects(bpy.types.Operator):
bbranch = bstream.branches[int(bstream.branch)]
if branch.commits.totalCount < 1:
print("No commits found. Probably an empty stream.")
_report("No commits found. Probably an empty stream.")
return {"CANCELLED"}
commit = branch.commits.items[int(bbranch.commit)]
@@ -278,6 +323,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
"""
@@ -310,20 +358,45 @@ class ReceiveStreamObjects(bpy.types.Operator):
"""
Get script from text editor for injection
"""
func = None
userFunc = 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
userFunc = mod.execute
createdObjects:Dict[str, Object] = {}
progress = 0
def func(scene, obj: Object):
nonlocal progress
nonlocal context
nonlocal createdObjects
progress += 1 #TODO: Progress bar neverreaches 100 because func is only called for convertable objects
context.window_manager.progress_update(progress)
createdObjects[obj.name] = obj
if userFunc:
return userFunc(scene, obj)
else:
return obj
"""
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, createdObjects)
return {"FINISHED"}
class SendStreamObjects(bpy.types.Operator):
"""
Send stream objects
@@ -375,9 +448,14 @@ class SendStreamObjects(bpy.types.Operator):
client = speckle_clients[int(context.scene.speckle.active_user)]
scale = context.scene.unit_settings.scale_length / get_scale_length(
stream.units.lower()
)
# scale = context.scene.unit_settings.scale_length / get_scale_length(
# stream.units.lower()
# )
scale = 1.0
units = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
"""
Get script from text editor for injection
@@ -412,18 +490,19 @@ class SendStreamObjects(bpy.types.Operator):
_report("Converting {}".format(obj.name))
ngons = obj.get("speckle_ngons_as_polylines", False)
# ngons = obj.get("speckle_ngons_as_polylines", False)
if ngons:
converted = export_ngons_as_polylines(obj, scale)
else:
converted = to_speckle_object(
obj,
scale,
bpy.context.evaluated_depsgraph_get()
if self.apply_modifiers
else None,
)
# if ngons:
# converted = ngons_to_speckle_polylines(obj, scale)
# else:
converted = convert_to_speckle(
obj,
scale,
units,
bpy.context.evaluated_depsgraph_get()
if self.apply_modifiers
else None,
)
if not converted:
continue
@@ -520,7 +599,7 @@ class AddStreamFromURL(bpy.types.Operator):
user = speckle.users[user_index]
client = speckle_clients[user_index]
stream = client.stream.get(wrapper.stream_id)
stream = client.stream.get(wrapper.stream_id, branch_limit=20)
if not isinstance(stream, Stream):
raise SpeckleException("Could not get the requested stream")
@@ -569,7 +648,7 @@ class CreateStream(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create new stream"
stream_name: StringProperty(name="Stream name", default="SpeckleStream")
stream_name: StringProperty(name="Stream name")
stream_description: StringProperty(
name="Stream description", default="This is a Blender stream."
)
+9 -19
View File
@@ -1,23 +1,13 @@
"""
User account operators
"""
from typing import cast
import bpy, bmesh, os
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
)
from bpy_speckle.properties.scene import SpeckleUserObject
import bpy
from bpy_speckle.functions import _report
from bpy_speckle.clients import speckle_clients
from specklepy.api.client import SpeckleClient
from specklepy.api.credentials import get_default_account, get_local_accounts
from specklepy.api.models import Stream, User
from specklepy.api.credentials import get_local_accounts
from datetime import datetime
class LoadUsers(bpy.types.Operator):
@@ -70,7 +60,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
@@ -90,10 +80,10 @@ def add_user_stream(user, stream):
for c in b.commits.items:
commit = branch.commits.add()
commit.id = commit.name = c.id
commit.message = c.message
commit.message = c.message or ""
commit.author_name = c.authorName
commit.author_id = c.authorId
commit.created_at = c.createdAt
commit.created_at = datetime.strftime(c.createdAt, "%Y-%m-%d %H:%M:%S.%f%Z")
commit.source_application = str(c.sourceApplication)
if hasattr(s, "baseProperties"):
@@ -122,7 +112,7 @@ class LoadUserStreams(bpy.types.Operator):
try:
streams = client.stream.list(stream_limit=20)
except Exception as e:
_report("Failed to retrieve streams: {}".format(e))
_report(f"Failed to retrieve streams: {e}")
return
if not streams:
_report("Failed to retrieve streams.")
@@ -133,7 +123,7 @@ class LoadUserStreams(bpy.types.Operator):
default_units = "Meters"
for s in streams:
sstream = client.stream.get(id=s.id)
sstream = client.stream.get(id=s.id, branch_limit=20)
add_user_stream(user, sstream)
bpy.context.view_layer.update()
-2
View File
@@ -1,9 +1,7 @@
"""
Addon properties
"""
import bpy
from bpy.props import BoolProperty
class SpeckleAddonPreferences(bpy.types.AddonPreferences):
-2
View File
@@ -1,9 +1,7 @@
"""
Collection properties
"""
import bpy
from bpy.props import StringProperty, BoolProperty, EnumProperty
class SpeckleCollectionSettings(bpy.types.PropertyGroup):
-2
View File
@@ -1,9 +1,7 @@
"""
Object properties
"""
import bpy
from bpy.props import StringProperty, BoolProperty, EnumProperty
class SpeckleObjectSettings(bpy.types.PropertyGroup):
+4 -5
View File
@@ -1,7 +1,6 @@
"""
Scene properties
"""
import bpy
from bpy.props import (
StringProperty,
@@ -12,7 +11,6 @@ from bpy.props import (
IntProperty,
PointerProperty,
)
from specklepy.api.client import SpeckleClient
class SpeckleSceneObject(bpy.types.PropertyGroup):
@@ -82,9 +80,10 @@ class SpeckleUserObject(bpy.types.PropertyGroup):
class SpeckleSceneSettings(bpy.types.PropertyGroup):
def get_scripts(self, context):
seq = [("<none>", "<none>", "<none>")]
seq.extend([(t.name, t.name, t.name) for t in bpy.data.texts])
return seq
return [
("<none>", "<none>", "<none>"),
*[(t.name, t.name, t.name) for t in bpy.data.texts],
]
streams: EnumProperty(
name="Available streams",
+7 -14
View File
@@ -12,7 +12,7 @@ from bpy.props import (
EnumProperty,
)
import datetime
from datetime import datetime
"""
Compatibility
@@ -84,9 +84,8 @@ class VIEW3D_UL_SpeckleStreams(bpy.types.UIList):
def draw_item(self, context, layout, data, stream, active_data, active_propname):
if self.layout_type in {"DEFAULT", "COMPACT"}:
if stream:
# layout.prop(user, "name", text=user.name, emboss=False, icon_value=0)
layout.label(
text="{} ({})".format(stream.name, stream.id),
text=f"{stream.name} ({stream.id})",
translate=False,
icon_value=0,
)
@@ -118,7 +117,6 @@ class VIEW3D_PT_SpeckleUser(bpy.types.Panel):
if len(speckle.users) < 1:
col.label(text="No users found.")
else:
# col.label(text="User")
col.prop(speckle, "active_user", text="")
user = speckle.users[int(speckle.active_user)]
col.label(text="{} ({})".format(user.server_name, user.server_url))
@@ -179,7 +177,7 @@ class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
stream = user.streams[user.active_stream]
# user.active_stream = min(user.active_stream, len(user.streams) - 1)
row = col.row()
row.label(text="{} ({})".format(stream.name, stream.id))
row.label(text=f"{stream.name} ({stream.id})")
row.operator("speckle.stream_copy_id", text="", icon="COPY_ID")
col.separator()
@@ -207,13 +205,11 @@ class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
row.label(text=line)
area.separator()
dt = datetime.datetime.strptime(
commit.created_at, "%Y-%m-%dT%H:%M:%S.%fZ"
)
col.label(text="{}".format(dt.ctime()))
col.label(
text="{} ({})".format(commit.author_name, commit.author_id)
dt = datetime.strptime(
commit.created_at, "%Y-%m-%d %H:%M:%S.%f%Z"
)
col.label(text=f"{dt.ctime()}")
col.label(text=f"{commit.author_name} ({commit.author_id})")
col.label(text=commit.source_application)
else:
col.label(text="No branches found!")
@@ -234,9 +230,6 @@ class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
row = col.row(align=True)
subcol = row.column()
subcol.label(text="Units:")
subcol = row.column()
subcol.label(text=stream.units)
col.label(text="Description:")
area = col.box()
+4 -3
View File
@@ -28,7 +28,8 @@ def patch_installer(tag):
with open(iss_file, "r") as file:
lines = file.readlines()
lines.insert(11, f'#define SpecklepyVersion "{py_tag}"\n')
lines.insert(11, f'#define AppVersion "{tag}"\n')
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)
@@ -54,11 +55,11 @@ def main():
return
tag = sys.argv[1]
if not re.match(r"[0-9]+(\.[0-9]+)*$", tag):
if not re.match(r"([0-9]+)\.([0-9]+)\.([0-9]+)", tag):
raise ValueError(f"Invalid tag provided: {tag}")
print(f"Patching version: {tag}")
patch_connector(tag)
patch_connector(tag.split("-")[0])
patch_installer(tag)
Generated
+819 -379
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -6,14 +6,15 @@ authors = ["izzy lyseggen <izzy.lyseggen@gmail.com>"]
license = "Apache-2.0"
[tool.poetry.dependencies]
python = ">=3.7,<3.8"
specklepy = "2.4.2"
python = ">=3.8, <4.0.0"
specklepy = "^2.6.6"
[tool.poetry.dev-dependencies]
devtools = "^0.6.1"
numpy = "^1.20.2"
bpy = "^2.82.1"
bpy-build = "^2.1.0"
fake-bpy-module-latest = "^20220401"
black = "^21.12b0"
pylint = "^2.12.2"
[build-system]
requires = ["poetry-core>=1.0.0"]