Compare commits

...

17 Commits

Author SHA1 Message Date
Jedd Morgan 7431b57e0e Merge pull request #198 from specklesystems/jrm/bump-deps
Updated deps
2024-05-27 16:34:39 +01:00
Jedd Morgan 57c19ba3c5 Updated deps 2024-05-27 16:34:07 +01:00
Jedd Morgan 8e3c2ece2f Merge pull request #197 from overengineer/fix/slow_startup
Speedup install dependencies
2024-05-24 16:48:29 +01:00
Soylu cfc58d9456 speedup install dependencies 2024-05-21 19:54:09 +03:00
Jedd Morgan a2f7ab422f Merge pull request #194 from specklesystems/dev
2.19 changes
2024-05-14 17:41:21 +01:00
Jedd Morgan 8c58d9d14c Dev (#192)
* feat(UI): CNX-9070 update connectors to use new fe2 terminology (#186)

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

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

* poetry lock

* Upgraded typing module

* FE2 URL support

* Raised exceptions now display to user

* Fixed unused imports

* Updated terminology to fe2

* merge from stash

* comments

* bl_descriptions

* bl_desc

* new urls

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

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

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

* poetry lock

* Upgraded typing module

* FE2 URL support

* Raised exceptions now display to user

* Fixed unused imports

* Updated terminology to fe2

* merge from stash

* comments

* bl_descriptions

* bl_desc

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

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

* poetry lock

* Upgraded typing module

* FE2 URL support

* Raised exceptions now display to user

* Fixed unused imports

* Update ci signing

* Update config.yml

* Bump Deps

* powershell

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

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

* poetry lock

* Upgraded typing module

* FE2 URL support

* Raised exceptions now display to user

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

* poetry lock
2024-02-07 18:05:00 +00:00
20 changed files with 1128 additions and 979 deletions
+37 -14
View File
@@ -71,24 +71,44 @@ jobs:
build-installer-win:
executor:
name: win/default
shell: cmd.exe
environment:
SSM: 'C:\Program Files\DigiCert\DigiCert One Signing Manager Tools'
steps:
- attach_workspace:
at: ./
- run:
name: Patch installer
shell: powershell.exe
command: python patch_installer.py (Get-Content -Raw SEMVER)
- run:
name: Create Innosetup signing cert
shell: powershell.exe
command: |
echo $env:PFX_B64 > "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt"
certutil -decode "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt" "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.pfx"
- run:
name: Installer
shell: cmd.exe #does not work in powershell
command: speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\blender.iss /Sbyparam=$p
- unless: # Build installers unsigned on non-tagged builds
condition: << pipeline.git.tag >>
steps:
- run:
name: Build Installer
command: speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\blender.iss /Sbyparam=$p
shell: cmd.exe #does not work in powershell
- when: # Setup certificates and build installers signed for tagged builds
condition: << pipeline.git.tag >>
steps:
- run:
name: "Digicert Signing Manager Setup"
command: |
cd C:\
curl.exe -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:$env:SM_API_KEY" -o smtools-windows-x64.msi
msiexec.exe /i smtools-windows-x64.msi /quiet /qn | Wait-Process
- run:
name: Create Auth & OV Signing Cert
command: |
cd C:\
echo $env:SM_CLIENT_CERT_FILE_B64 > certificate.txt
certutil -decode certificate.txt certificate.p12
- run:
name: Sync Certs
command: |
& $env:SSM\smksp_cert_sync.exe
- run:
name: Build Installer
command: speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\blender.iss /Sbyparam=$p /DSIGN_INSTALLER /DCODE_SIGNING_CERT_FINGERPRINT=%SM_CODE_SIGNING_CERT_SHA1_HASH%
shell: cmd.exe #does not work in powershell
- persist_to_workspace:
root: ./
paths:
@@ -109,6 +129,9 @@ jobs:
- checkout
- attach_workspace:
at: ./
- run:
name: Exit if External PR
command: if [ "$CIRCLE_PR_REPONAME" ]; then circleci-agent step halt; fi
- run:
name: Install mono
command: |
@@ -214,7 +237,7 @@ workflows:
filters: *build_filters
- build-installer-win:
context: innosetup
context: digicert-keylocker
name: Windows Installer Build
requires:
- package-connector
@@ -304,4 +327,4 @@ workflows:
- Windows Installer Build
- Mac Intel Build
- Mac ARM Build
filters: *deploy_filters
filters: *deploy_filters
+6 -7
View File
@@ -49,8 +49,7 @@ Head to the [**📚 documentation**](https://speckle.guide/user/blender.html) fo
## Installation
Currently, we are supporting all Blender 3.X versions on Windows and Mac.
We have experimental support for Blender 4.0 and greater.
We officially support Blender 3.3 and newer, on Windows and Mac.
Please follow our installation instructions on our [connector docs](https://speckle.guide/user/blender.html#installation)
@@ -59,10 +58,10 @@ Once enabled in `Preferences -> Addons`,
The Speckle connector UI can be found in the 3d viewport toolbar (N), under the `Speckle` tab.
- Available user accounts are automatically detected and made available. To add user accounts use **Speckle Manager**.
- Select the user from the dropdown list in the `Users` panel. This will populate the `Streams` list with available streams for the selected user.
- Select a branch and commit from the dropdown menus.
- Click on `Receive` to download the objects from the selected stream, branch, and commit. The stream objects will be loaded into a Blender Collection, named `<STREAM_NAME> [ <STREAM_BRANCH> @ <BRANCH_COMMIT> ]`. <!-- You can filter the stream by entering a query into the `Filter` field (i.e. `properties.weight>10` or `type="Mesh"`). -->
- Click on `Open Stream in Web` to view the stream in your web browser.
- Select the user from the dropdown list in the `Users` panel. This will populate the `Projects` list with available projects for the selected user account.
- Select a model and version from the dropdown menus.
- Click on `Receive` to download and convert the objects from the selected model version. The objects will be linked into a Blender Collection, named `<PROJECT_NAME> [ <MODEL_NAME> @ <VERSION_ID> ]`.
- Click on `Open Model in Web` to view the model in your web browser.
## Supported Elements
@@ -77,7 +76,7 @@ The full matrix of supported Blender and Speckle types [can be found here](https
- If a `renderMaterial` property is found, **SpeckleBlender** will create a material named using the sub-property `renderMaterial.name`. If a material with that name already exists in Blender, **SpeckleBlender** will just assign that existing material to the object. This allows geometry to be updated without having to re-assign and re-create materials.
- Receiving vertex colors are supported. The `colors` list from Speckle meshes is translated to a vertex color layer.
- Receiving vertex colors is supported. The `colors` list from Speckle meshes is translated to a vertex color layer.
- Receive/Send scripts. Allow injecting a custom python function to the receive/send process to automate any blender operations
+2 -2
View File
@@ -41,7 +41,7 @@ class BlenderCommitObjectBuilder(CommitObjectBuilder[Object]):
# Set the Child -> Parent relationships
parent = native_object.parent
parent_collections: Tuple[Collection] = native_object.users_collection # type: ignore
parent_collections = native_object.users_collection
parent_collection = parent_collections[0] if len(parent_collections) > 0 else None #NOTE: we don't support objects appearing in more than one collection, for now, we will just take the zeroth one
app_id = _id(native_object)
@@ -79,7 +79,7 @@ class BlenderCommitObjectBuilder(CommitObjectBuilder[Object]):
# Create all collections
root_col = self.ensure_collection(bpy.context.scene.collection)
root_col.collectionType = "Scene Collection"
for col in bpy.context.scene.collection.children_recursive: #type: ignore
for col in bpy.context.scene.collection.children_recursive:
self.ensure_collection(col)
objects_to_build = set(self.converted.values())
+10 -4
View File
@@ -540,7 +540,7 @@ def transform_to_native(transform: Transform, scale: float) -> MMatrix:
)
# scale the translation
for i in range(3):
mat[i][3] *= scale # type: ignore
mat[i][3] *= scale
return mat
def plane_to_native_transform(plane: Plane, fallback_scale:float = 1) -> MMatrix:
@@ -774,15 +774,21 @@ def _make_unique_name( desired_name: str, taken_names: Collection[str], counter:
def _get_friendly_object_name(speckle_object: Base) -> Optional[str]:
return (getattr(speckle_object, "name", None)
or getattr(speckle_object, "Name", None)
or getattr(speckle_object, "family", None)
or _get_revit_family_name(speckle_object)
)
def _get_revit_family_name(speckle_object: Base) -> Optional[str]:
family = getattr(speckle_object, "family", None)
family_type = getattr(speckle_object, "type", None)
if family and family_type:
return f"{family_type}-{family}"
else:
return None
# Blender object names must not exceed 62 characters
# We need to ensure the complete ID is included in the name (to prevent identity collisions)
# So we if the name is too long, we need to truncate
def _truncate_object_name(name: str) -> str:
MAX_NAME_LENGTH = OBJECT_NAME_MAX_LENGTH - SPECKLE_ID_LENGTH - len(OBJECT_NAME_SPECKLE_SEPARATOR)
+12 -4
View File
@@ -34,7 +34,7 @@ from bpy_speckle.functions import _report
Units: str = "m" # The desired final units to send
UnitsScale: float = 1 # The scale factor conversions need to apply to position data to get to the desired units
CAN_CONVERT_TO_SPECKLE = ("MESH", "CURVE", "EMPTY", "CAMERA")
CAN_CONVERT_TO_SPECKLE = ("MESH", "CURVE", "EMPTY", "CAMERA", "FONT", "SURFACE", "META")
def convert_to_speckle(raw_blender_object: Object, units_scale: float, units: str, depsgraph: Optional[Depsgraph]) -> Base:
@@ -69,6 +69,8 @@ def convert_to_speckle(raw_blender_object: Object, units_scale: float, units: st
converted = empty_to_speckle(blender_object)
elif blender_type == "CAMERA":
converted = camera_to_speckle_view(blender_object, cast(NCamera, blender_object.data))
elif blender_type == "FONT" or "SURFACE" or "META":
converted = anything_to_speckle_mesh(blender_object)
if not converted:
raise Exception("Conversion returned None")
@@ -99,7 +101,7 @@ def mesh_to_speckle_meshes(blender_object: Object, data: bpy.types.Mesh) -> List
submesh_data[p.material_index].append(p)
transform = cast(MMatrix, blender_object.matrix_world)
scaled_vertices = [tuple(transform @ x.co * UnitsScale) for x in data.vertices] # type: ignore
scaled_vertices = [tuple(transform @ x.co * UnitsScale) for x in data.vertices]
# Create Speckle meshes for each material
submeshes = []
@@ -370,6 +372,12 @@ def curve_to_speckle_geometry(blender_object: Object, data: bpy.types.Curve) ->
return (meshes, curves)
def anything_to_speckle_mesh(blender_object: Object) -> Base:
mesh = mesh_to_speckle(blender_object, blender_object.to_mesh())
blender_object.to_mesh_clear()
return mesh
@deprecated
def ngons_to_speckle_polylines(blender_object: Object, data: bpy.types.Mesh) -> Optional[List[Polyline]]:
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
@@ -437,8 +445,8 @@ def camera_to_speckle_view(blender_object: Object, data: NCamera) -> Base:
raise Exception(f"Cameras of type {data.type} are not currently supported")
matrix = cast(MMatrix, blender_object.matrix_world)
up = matrix.col[1].xyz # type: ignore
forwards = -matrix.col[2].xyz # type: ignore
up = cast(MVector, matrix.col[1].xyz)
forwards = cast(MVector, -matrix.col[2].xyz)
translation = matrix.translation
view = Base.of_type("Objects.BuiltElements.View:Objects.BuiltElements.View3D") #HACK: views are not in specklepy yet!
+7 -7
View File
@@ -168,7 +168,7 @@ def add_faces(speckle_mesh: Mesh, blender_mesh: BMesh, indexOffset: int, materia
i += 1
try:
f = blender_mesh.faces.new(
[blender_mesh.verts[x + indexOffset] for x in sfaces[i : i + n]] # type: ignore
[blender_mesh.verts[x + indexOffset] for x in sfaces[i : i + n]]
)
f.material_index = materialIndex
f.smooth = smooth
@@ -198,10 +198,10 @@ def add_colors(speckle_mesh: Mesh, blender_mesh: BMesh):
)
# Make vertex colors
if len(scolors) == len(blender_mesh.verts): # type: ignore
if len(scolors) == len(blender_mesh.verts):
color_layer = blender_mesh.loops.layers.color.new("Col")
for face in blender_mesh.faces: # type: ignore
for face in blender_mesh.faces:
for loop in face.loops:
loop[color_layer] = colors[loop.vert.index]
@@ -220,21 +220,21 @@ def add_uv_coords(speckle_mesh: Mesh, blender_mesh: BMesh):
try:
uv = []
if len(s_uvs) // 2 == len(blender_mesh.verts): # type: ignore
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: {len(s_uvs) // 2}" # type: ignore
f"Failed to match UV coordinates to vert data. Blender mesh verts: {len(blender_mesh.verts)}, Speckle UVs: {len(s_uvs) // 2}"
)
return
# Make UVs
uv_layer = blender_mesh.loops.layers.uv.verify()
for f in blender_mesh.faces: # type: ignore
for f in blender_mesh.faces:
for l in f.loops:
luv = l[uv_layer]
luv.uv = uv[l.vert.index]
@@ -450,7 +450,7 @@ def link_object_to_collection_nested(obj: Object, col: BCollection):
if obj.name not in col.objects: #type: ignore
col.objects.link(obj)
for child in obj.children: #type: ignore
for child in obj.children:
link_object_to_collection_nested(child, col)
def add_to_hierarchy(converted: Union[Object, BCollection], traversalContext : 'TraversalContext', converted_objects: Dict[str, Union[Object, BCollection]], preserve_transform: bool) -> None:
+5
View File
@@ -156,7 +156,12 @@ def install_requirements(host_application: str) -> None:
PYTHON_PATH,
"-m",
"pip",
"-q",
"--disable-pip-version-check",
"install",
"--prefer-binary",
"--ignore-installed",
"--no-compile",
"-t",
str(path),
"-r",
+2
View File
@@ -20,6 +20,7 @@ from .streams import (
CopyStreamId,
CopyCommitId,
CopyBranchName,
CopyModelId,
)
from .commit import DeleteCommit
from .misc import OpenSpeckleGuide, OpenSpeckleTutorials, OpenSpeckleForum
@@ -33,6 +34,7 @@ operator_classes = [
CopyStreamId,
CopyCommitId,
CopyBranchName,
CopyModelId,
]
operator_classes.extend([DeleteCommit])
+14 -17
View File
@@ -4,25 +4,26 @@ Commit operators
import bpy
from bpy.props import BoolProperty
from bpy_speckle.clients import speckle_clients
from bpy_speckle.functions import _report
from bpy_speckle.properties.scene import get_speckle
from specklepy.logging import metrics
class DeleteCommit(bpy.types.Operator):
"""
Deletes the selected commit from the selected stream.
Permanently deletes the selected version from the selected model.
To execute from code, call: `bpy.ops.speckle.delete_commit(are_you_sure=True)`
"""
bl_idname = "speckle.delete_commit"
bl_label = "Delete commit"
bl_label = "Delete Version"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Delete active commit permanently"
bl_description = "Permanently Deletes the selected version from the selected model"
are_you_sure: BoolProperty(
name="Confirm",
default=False,
)
) # type: ignore
def draw(self, context):
layout = self.layout
@@ -38,23 +39,19 @@ class DeleteCommit(bpy.types.Operator):
return {"CANCELLED"}
def execute(self, context):
try:
self.delete_commit(context)
return {"FINISHED"}
except Exception as ex:
print(f"{self.bl_idname}: failed: {ex}")
return {"CANCELLED"}
def delete_commit(self, context: bpy.types.Context) -> None:
if not self.are_you_sure:
raise Exception("Cancelled by user")
_report("Cancelled by user")
return {"CANCELLED"}
self.are_you_sure = False
self.delete_commit(context)
return {"FINISHED"}
@staticmethod
def delete_commit(context: bpy.types.Context) -> None:
speckle = get_speckle(context)
(_, stream, _, commit) = speckle.validate_commit_selection()
(_, stream, branch, commit) = speckle.validate_commit_selection()
client = speckle_clients[int(speckle.active_user)]
@@ -71,5 +68,5 @@ class DeleteCommit(bpy.types.Operator):
if not deleted:
raise Exception("Delete operation failed")
print(f"{self.bl_idname}: succeeded - commit {commit.id} ({commit.message}) has been deleted from stream {stream.id}")
print(f"Version {commit.id} ({commit.message}) of model {branch.id} ({branch.name}) has been deleted from project {stream.id} ({stream.name})")
+18 -10
View File
@@ -3,14 +3,18 @@ import webbrowser
from specklepy.logging import metrics
class OpenSpeckleGuide(bpy.types.Operator):
bl_idname = "speckle.open_speckle_guide"
bl_label = "Speckle Guide"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Browse the documentation on the Speckle Guide"
class OpenSpeckleGuide(bpy.types.Operator):
_guide_url = "https://speckle.guide/user/blender.html"
bl_idname = "speckle.open_speckle_guide"
bl_label = "Speckle Docs"
bl_options = {"REGISTER", "UNDO"}
bl_description = f"Browse the documentation on the Speckle Guide ({_guide_url})"
def execute(self, context):
webbrowser.open("https://speckle.guide/user/blender.html")
webbrowser.open(self._guide_url)
metrics.track(
"Connector Action",
None,
@@ -22,13 +26,15 @@ class OpenSpeckleGuide(bpy.types.Operator):
class OpenSpeckleTutorials(bpy.types.Operator):
_tutorials_url = "https://speckle.systems/tutorials/"
bl_idname = "speckle.open_speckle_tutorials"
bl_label = "Tutorials Portal"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Visit our tutorials portal for learning resources"
bl_description = f"Visit our tutorials portal for learning resources ({_tutorials_url})"
def execute(self, context):
webbrowser.open("https://speckle.systems/tutorials/")
webbrowser.open(self._tutorials_url)
metrics.track(
"Connector Action",
None,
@@ -40,13 +46,15 @@ class OpenSpeckleTutorials(bpy.types.Operator):
class OpenSpeckleForum(bpy.types.Operator):
_forum_url = "https://speckle.community/"
bl_idname = "speckle.open_speckle_forum"
bl_label = "Community Forum"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Ask questions and join the discussion on our community forum"
bl_description = f"Ask questions and join the discussion on our community forum ({_forum_url})"
def execute(self, context):
webbrowser.open("https://speckle.community/")
webbrowser.open(self._forum_url)
metrics.track(
"Connector Action",
None,
+11 -10
View File
@@ -13,6 +13,7 @@ from bpy_speckle.functions import get_scale_length, _report
from bpy_speckle.clients import speckle_clients
from specklepy.logging import metrics
@deprecated
class UpdateObject(bpy.types.Operator):
"""
Update local (receive) or remote (send) object depending on
@@ -21,7 +22,7 @@ class UpdateObject(bpy.types.Operator):
"""
bl_idname = "speckle.update_object"
bl_label = "Update Object"
bl_label = "Update Object (DEPRECATED)"
bl_options = {"REGISTER", "UNDO"}
client = None
@@ -68,14 +69,14 @@ class UpdateObject(bpy.types.Operator):
return {"CANCELLED"}
return {"CANCELLED"}
@deprecated
class ResetObject(bpy.types.Operator):
"""
Reset Speckle object settings
"""
bl_idname = "speckle.reset_object"
bl_label = "Reset Object"
bl_label = "Reset Object (DEPRECATED)"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
@@ -96,14 +97,14 @@ class ResetObject(bpy.types.Operator):
return {"FINISHED"}
@deprecated
class DeleteObject(bpy.types.Operator):
"""
Delete object from the server and update relevant stream
"""
bl_idname = "speckle.delete_object"
bl_label = "Delete Object"
bl_label = "Delete Object (DEPRECATED)"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
@@ -158,7 +159,7 @@ class UploadNgonsAsPolylines(bpy.types.Operator):
"""
bl_idname = "speckle.upload_ngons_as_polylines"
bl_label = "Upload Ngons As Polylines"
bl_label = "Upload Ngons As Polylines (DEPRECATED)"
bl_options = {"REGISTER", "UNDO"}
clear_stream: BoolProperty(
@@ -247,7 +248,7 @@ def get_custom_speckle_props(self, context):
return [(x, "{}".format(x), "") for x in active.keys()]
@deprecated
class SelectIfSameCustomProperty(bpy.types.Operator):
"""
Select scene objects if they have the same custom property
@@ -255,7 +256,7 @@ class SelectIfSameCustomProperty(bpy.types.Operator):
"""
bl_idname = "speckle.select_if_same_custom_props"
bl_label = "Select Identical Custom Props"
bl_label = "Select Identical Custom Props (DEPRECATED)"
bl_options = {"REGISTER", "UNDO"}
custom_prop: EnumProperty(
@@ -307,7 +308,7 @@ class SelectIfSameCustomProperty(bpy.types.Operator):
return {"FINISHED"}
@deprecated
class SelectIfHasCustomProperty(bpy.types.Operator):
"""
Select scene objects if they have the same custom property
@@ -315,7 +316,7 @@ class SelectIfHasCustomProperty(bpy.types.Operator):
"""
bl_idname = "speckle.select_if_has_custom_props"
bl_label = "Select Same Custom Prop"
bl_label = "Select Same Custom Prop (DEPRECATED)"
bl_options = {"REGISTER", "UNDO"}
custom_prop: EnumProperty(
+276 -199
View File
@@ -2,7 +2,7 @@
Stream operators
"""
from math import radians
from typing import Callable, Dict, Optional, Union, cast
from typing import Callable, Dict, Optional, Tuple, Union, cast
import webbrowser
import bpy
from bpy.props import (
@@ -15,6 +15,7 @@ from bpy.types import (
Object,
Collection
)
from deprecated import deprecated
from bpy_speckle.blender_commit_object_builder import BlenderCommitObjectBuilder
from bpy_speckle.convert.to_native import (
can_convert_to_native,
@@ -31,8 +32,8 @@ from bpy_speckle.functions import (
get_scale_length,
)
from bpy_speckle.clients import speckle_clients
from bpy_speckle.operators.users import add_user_stream
from bpy_speckle.properties.scene import SpeckleSceneSettings, SpeckleUserObject, get_speckle
from bpy_speckle.operators.users import LoadUserStreams, add_user_stream
from bpy_speckle.properties.scene import SpeckleSceneSettings, SpeckleStreamObject, SpeckleUserObject, get_speckle
from bpy_speckle.convert.util import ConversionSkippedException, add_to_hierarchy
from specklepy.core.api.models import Commit
from specklepy.core.api import operations, host_applications
@@ -80,19 +81,18 @@ INSTANCES_SETTINGS = [
class ReceiveStreamObjects(bpy.types.Operator):
"""
Receive stream objects
Receive objects from selected model version
"""
bl_idname = "speckle.receive_stream_objects"
bl_label = "Download Stream Objects"
bl_label = "Receive"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Receive objects from active stream"
bl_description = "Receive objects from selected model version"
clean_meshes: BoolProperty(name="Clean Meshes", default=False)
clean_meshes: BoolProperty(name="Clean Meshes", default=False) # type: ignore
#receive_mode: EnumProperty(items=RECEIVE_MODES, name="Receive Type", default="replace", description="The behaviour of the receive operation")
receive_instances_as: EnumProperty(items=INSTANCES_SETTINGS, name="Receive Instances As", default="collection_instance", description="How to receive speckle Instances")
receive_instances_as: EnumProperty(items=INSTANCES_SETTINGS, name="Receive Instances As", default="collection_instance", description="How to receive speckle Instances") # type: ignore
def draw(self, context):
@@ -130,23 +130,19 @@ class ReceiveStreamObjects(bpy.types.Operator):
# 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
bpy.context.view_layer.objects.active = None # type: ignore
def execute(self, context):
try:
self.receive(context)
return {"FINISHED"}
except Exception as ex:
_report(f"Failed to receive objects: {type(ex)} {ex}")
return {"CANCELLED"}
self.receive(context)
return {"FINISHED"}
def receive(self, context: Context) -> None:
bpy.context.view_layer.objects.active = None
bpy.context.view_layer.objects.active = None # type: ignore
speckle = get_speckle(context)
(user, stream, branch, commit) = speckle.validate_commit_selection()
client = speckle_clients[int(speckle.active_user)]
transport = ServerTransport(stream.id, client)
@@ -157,7 +153,7 @@ class ReceiveStreamObjects(bpy.types.Operator):
stream.id,
commit.id,
source_application="blender",
message="received commit from Speckle Blender",
message="Received model version from Speckle Blender",
)
metrics.track(
@@ -193,7 +189,7 @@ class ReceiveStreamObjects(bpy.types.Operator):
# ensure commit object has a name if not already
if not commit_object.name:
commit_object.name = "{} [ {} @ {} ]".format(stream.name, branch.name, commit.id) # Matches Rhino "Create" naming
commit_object.name = f"{stream.name} [ {branch.name} @ {commit.id} ]" # Matches Rhino "Create" naming
for item in traversalFunc.traverse(commit_object):
@@ -201,7 +197,8 @@ class ReceiveStreamObjects(bpy.types.Operator):
if can_convert_to_native(current) or isinstance(current, SCollection):
try:
if not current or not current.id: raise Exception(f"{current} was an invalid speckle object")
if not current or not current.id:
raise Exception(f"{current} was an invalid Speckle object")
#Convert the object!
converted_data_type: str
@@ -248,19 +245,19 @@ class ReceiveStreamObjects(bpy.types.Operator):
class SendStreamObjects(bpy.types.Operator):
"""
Send stream objects
Send selected objects to selected model
"""
bl_idname = "speckle.send_stream_objects"
bl_label = "Send stream objects"
bl_label = "Send"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Send selected objects to active stream"
bl_description = "Send selected objects to selected model"
apply_modifiers: BoolProperty(name="Apply modifiers", default=True)
apply_modifiers: BoolProperty(name="Apply modifiers", default=True) # type: ignore
commit_message: StringProperty(
name="Message",
default="Pushed elements from Blender.",
)
default="Sent elements from Blender.",
) # type: ignore
def draw(self, context):
layout = self.layout
@@ -270,23 +267,22 @@ class SendStreamObjects(bpy.types.Operator):
def invoke(self, context, event):
wm = context.window_manager
if len(context.scene.speckle.users) <= 0: return {"CANCELLED"}
speckle = get_speckle(context)
if len(speckle.users) <= 0:
_report("No user accounts")
return {"CANCELLED"}
N = len(context.selected_objects)
if N == 1:
self.commit_message = f"Pushed {N} element from Blender."
self.commit_message = f"Sent {N} element from Blender."
else:
self.commit_message = f"Pushed {N} elements from Blender."
self.commit_message = f"Sent {N} elements from Blender."
return wm.invoke_props_dialog(self)
def execute(self, context):
try:
self.send(context)
return {"FINISHED"}
except Exception as ex:
_report(f"Send failed: {ex}")
return {"CANCELLED"}
self.send(context)
return {"FINISHED"}
def send(self, context: Context) -> None:
@@ -376,7 +372,13 @@ class SendStreamObjects(bpy.types.Operator):
message=self.commit_message,
source_application="blender",
)
_report(f"Commit Created {user.server_url}/streams/{stream.id}/commits/{COMMIT_ID}")
if client.account.serverInfo.frontend2:
sent_url = f"{user.server_url}/projects/{stream.id}/models/{branch.id}@{COMMIT_ID}"
else:
sent_url = f"{user.server_url}/streams/{stream.id}/commits/{COMMIT_ID}"
_report(f"Commit Created {sent_url}")
bpy.ops.speckle.load_user_streams() # refresh loaded commits
context.view_layer.update()
@@ -388,25 +390,23 @@ class SendStreamObjects(bpy.types.Operator):
class ViewStreamDataApi(bpy.types.Operator):
bl_idname = "speckle.view_stream_data_api"
bl_label = "Open Stream in Web"
bl_label = "Open Model in Web"
bl_options = {"REGISTER", "UNDO"}
bl_description = "View the stream in the web browser"
bl_description = "View the selected model in the web browser"
def execute(self, context):
try:
self.view_stream_data_api(context)
return {"FINISHED"}
except Exception as ex:
_report(f"{self.bl_idname} failed: {ex}")
return {"CANCELLED"}
self.view_stream_data_api(context)
return {"FINISHED"}
def view_stream_data_api(self, context: Context) -> None:
speckle = get_speckle(context)
(user, stream) = speckle.validate_stream_selection()
url = self._get_url_from_selection(speckle)
_report(f"Opening {url} in web browser")
if not webbrowser.open("%s/streams/%s" % (user.server_url, stream.id), new=2):
raise Exception("Failed to open stream in browser")
if not webbrowser.open(url, new=2):
raise Exception(f"Failed to open model in browser ({url})")
metrics.track(
"Connector Action",
@@ -416,19 +416,41 @@ class ViewStreamDataApi(bpy.types.Operator):
},
)
@staticmethod
def _get_url_from_selection(speckleScene : SpeckleSceneSettings) -> str:
client = speckle_clients[int(speckleScene.active_user)]
(user, stream) = speckleScene.validate_stream_selection()
branch = stream.get_active_branch()
commit = branch.get_active_commit() if branch else None
if client.account.serverInfo.frontend2:
server_url = f"{user.server_url}/projects/{stream.id}/"
if branch:
server_url += f"models/{branch.id}"
if commit:
server_url += f"@{commit.id}"
else:
server_url = f"{user.server_url}/streams/{stream.id}/"
if commit:
server_url += f"commits/{commit.id}"
elif branch:
server_url += f"branches/{branch.name}"
return server_url
class AddStreamFromURL(bpy.types.Operator):
"""
Add / select a stream using its url
Add / select an existing project by providing its URL
"""
bl_idname = "speckle.add_stream_from_url"
bl_label = "Add stream from URL"
bl_label = "Add Project From URL"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Add an existing stream by providing its URL"
bl_description = "Add / select an existing project by providing its URL"
stream_url: StringProperty(
name="Stream URL", default="https://speckle.xyz/streams/3073b96e86"
)
name="Project URL", default=""
) # type: ignore
def draw(self, context):
layout = self.layout
@@ -444,13 +466,26 @@ class AddStreamFromURL(bpy.types.Operator):
return {"CANCELLED"}
def execute(self, context):
try:
self.add_stream_from_url(context)
return {"FINISHED"}
except Exception as ex:
_report(f"{self.bl_idname} failed: {ex}")
return {"CANCELLED"}
self.add_stream_from_url(context)
return {"FINISHED"}
@staticmethod
def _get_or_add_stream(user : SpeckleUserObject, stream : Stream) -> Tuple[int, SpeckleStreamObject]:
index, b_stream = next(
((i, cast(SpeckleStreamObject, s)) for i, s in enumerate(user.streams) if s.id == stream.id),
(None, None),
)
if index is not None:
assert(b_stream)
return (index, b_stream)
add_user_stream(user, stream)
return next(
(i, cast(SpeckleStreamObject, s)) for i, s in enumerate(user.streams) if s.id == stream.id
)
def add_stream_from_url(self, context: Context) -> None:
speckle = get_speckle(context)
@@ -460,28 +495,20 @@ class AddStreamFromURL(bpy.types.Operator):
None,
)
if user_index is None:
raise Exception("Unable to find user stream server")
raise Exception(f"No user account credentials for {wrapper.host}, have you added your account in Manager?")
speckle.active_user = str(user_index)
user = cast(SpeckleUserObject, speckle.users[user_index])
client = speckle_clients[user_index]
stream = client.stream.get(wrapper.stream_id, branch_limit=20)
stream = client.stream.get(wrapper.stream_id, branch_limit=LoadUserStreams.branch_limit, commit_limit=LoadUserStreams.commits_limit)
if not isinstance(stream, Stream):
raise SpeckleException("Could not get the requested stream")
raise SpeckleException(f"Could not get the requested project {wrapper.stream_id}")
index, b_stream = next(
((i, s) for i, s in enumerate(user.streams) if s.id == stream.id),
(None, None),
)
(index, b_stream) = self._get_or_add_stream(user, stream)
user.active_stream = index
if index is None:
add_user_stream(user, stream)
user.active_stream, b_stream = next(
(i, s) for i, s in enumerate(user.streams) if s.id == stream.id
)
else:
user.active_stream = index
_report(f"Selecting project at index {index} ({b_stream.id} - {b_stream.name})")
if wrapper.branch_name:
b_index = b_stream.branches.find(wrapper.branch_name)
@@ -513,18 +540,18 @@ class AddStreamFromURL(bpy.types.Operator):
class CreateStream(bpy.types.Operator):
"""
Create new stream
Create a new Speckle project using the selected user account
"""
bl_idname = "speckle.create_stream"
bl_label = "Create stream"
bl_label = "Create Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create new stream"
bl_description = "Create a new Speckle project using the selected user account"
stream_name: StringProperty(name="Stream name")
stream_name: StringProperty(name="Project name") # type: ignore
stream_description: StringProperty(
name="Stream description", default="This is a Blender stream."
)
name="Project description", default="My new project"
) # type: ignore
def draw(self, context):
layout = self.layout
@@ -541,12 +568,8 @@ class CreateStream(bpy.types.Operator):
return {"CANCELLED"}
def execute(self, context):
try:
self.create_stream(context)
return {"FINISHED"}
except Exception as ex:
_report(f"{self.bl_idname} failed: {ex}")
return {"CANCELLED"}
self.create_stream(context)
return {"FINISHED"}
def create_stream(self, context: Context) -> None:
speckle = get_speckle(context)
@@ -579,22 +602,24 @@ class CreateStream(bpy.types.Operator):
)
@deprecated
class DeleteStream(bpy.types.Operator):
"""
Delete stream
Permanently delete the selected project
"""
bl_idname = "speckle.delete_stream"
bl_label = "Delete stream"
bl_label = "Delete Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Delete selected stream permanently"
bl_description = "Permanently delete the selected project"
are_you_sure: BoolProperty(
name="Confirm",
description="⚠ This action will delete your entire stream permanently ⚠",
default=False,
)
) # type: ignore
delete_collection: BoolProperty(name="Delete collection", default=False)
delete_collection: BoolProperty(name="Delete collection", default=False) # type: ignore
def draw(self, context):
layout = self.layout
@@ -611,19 +636,16 @@ class DeleteStream(bpy.types.Operator):
return {"CANCELLED"}
def execute(self, context):
try:
self.delete_stream(context)
return {"FINISHED"}
except Exception as ex:
_report(f"{self.bl_idname} failed: {ex}")
return {"CANCELLED"}
def delete_stream(self, context: Context) -> None:
if not self.are_you_sure:
raise Exception("Cancelled by user")
_report(f"Cancelled by user - are_you_sure was {self.are_you_sure}")
return {"CANCELLED"}
self.are_you_sure = False
self.delete_stream(context, self.delete_collection)
return {"FINISHED"}
@staticmethod
def delete_stream(context: Context, delete_collection: bool) -> None:
speckle = get_speckle(context)
(_, stream) = speckle.validate_stream_selection()
@@ -631,7 +653,8 @@ class DeleteStream(bpy.types.Operator):
client.stream.delete(id=stream.id)
if self.delete_collection:
if delete_collection:
# This may not work anymore since we changed the collection naming...
col_name = "SpeckleStream_{}_{}".format(stream.name, stream.id)
if col_name in bpy.data.collections:
collection = bpy.data.collections[col_name]
@@ -651,7 +674,157 @@ class DeleteStream(bpy.types.Operator):
},
)
@deprecated
class SelectOrphanObjects(bpy.types.Operator):
"""
Select Speckle objects that don't belong to any stream
"""
bl_idname = "speckle.select_orphans"
bl_label = "Select Orphaned Objects (DEPRECATED)"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Select Speckle objects that don't belong to any stream"
def draw(self, context):
layout = self.layout
def execute(self, context):
for o in context.scene.objects:
if (
o.speckle.stream_id
and o.speckle.stream_id not in context.scene["speckle_streams"]
):
o.select = True
else:
o.select = False
metrics.track(
"Connector Action",
custom_props={
"name": "SelectOrphanObjects"
},
)
return {"FINISHED"}
class CopyStreamId(bpy.types.Operator):
"""
Copy the selected project id to clipboard
"""
bl_idname = "speckle.stream_copy_id"
bl_label = "Copy Project Id"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Copy the selected project id to clipboard"
def execute(self, context):
self.copy_stream_id(context)
return {"FINISHED"}
def copy_stream_id(self, context) -> None:
speckle = get_speckle(context)
(_, stream) = speckle.validate_stream_selection()
bpy.context.window_manager.clipboard = stream.id
metrics.track(
"Connector Action",
custom_props={
"name": "copy_stream_id"
},
)
class CopyCommitId(bpy.types.Operator):
"""
Copy the selected version id to clipboard
"""
bl_idname = "speckle.commit_copy_id"
bl_label = "Copy Version Id"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Copy the selected version id to clipboard"
def execute(self, context):
self.copy_commit_id(context)
return {"FINISHED"}
def copy_commit_id(self, context) -> None:
speckle = get_speckle(context)
(_, _, _, commit) = speckle.validate_commit_selection()
bpy.context.window_manager.clipboard = commit.id
metrics.track(
"Connector Action",
custom_props={
"name": "copy_commit_id"
},
)
class CopyModelId(bpy.types.Operator):
"""
Copy model id to clipboard
"""
bl_idname = "speckle.model_copy_id"
bl_label = "Copy model id"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Copy model id to clipboard"
def execute(self, context):
self.copy_model_id(context)
return {"FINISHED"}
def copy_model_id(self, context) -> None:
speckle = get_speckle(context)
(_, _, branch) = speckle.validate_branch_selection()
bpy.context.window_manager.clipboard = branch.id
metrics.track(
"Connector Action",
custom_props={
"name": "copy_branch_id"
},
)
@deprecated
class CopyBranchName(bpy.types.Operator):
"""
Copy branch name to clipboard
"""
bl_idname = "speckle.branch_copy_name"
bl_label = "Copy branch name"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Copy branch name to clipboard"
def execute(self, context):
self.copy_branch_id(context)
return {"FINISHED"}
def copy_branch_id(self, context) -> None:
speckle = get_speckle(context)
(_, _, branch) = speckle.validate_branch_selection()
bpy.context.window_manager.clipboard = branch.name
metrics.track(
"Connector Action",
custom_props={
"name": "copy_branch_id"
},
)
@deprecated
class SelectOrphanObjects(bpy.types.Operator):
"""
Select Speckle objects that don't belong to any stream
@@ -683,100 +856,4 @@ class SelectOrphanObjects(bpy.types.Operator):
},
)
return {"FINISHED"}
class CopyStreamId(bpy.types.Operator):
"""
Copy stream ID to clipboard
"""
bl_idname = "speckle.stream_copy_id"
bl_label = "Copy stream ID"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Copy stream ID to clipboard"
def execute(self, context):
try:
self.copy_stream_id(context)
return {"FINISHED"}
except Exception as ex:
_report(f"{self.bl_idname} failed: {ex}")
return {"CANCELLED"}
def copy_stream_id(self, context) -> None:
speckle = get_speckle(context)
(_, stream) = speckle.validate_stream_selection()
bpy.context.window_manager.clipboard = stream.id
metrics.track(
"Connector Action",
custom_props={
"name": "copy_stream_id"
},
)
class CopyCommitId(bpy.types.Operator):
"""
Copy commit ID to clipboard
"""
bl_idname = "speckle.commit_copy_id"
bl_label = "Copy commit ID"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Copy commit ID to clipboard"
def execute(self, context):
try:
self.copy_commit_id(context)
return {"FINISHED"}
except Exception as ex:
_report(f"{self.bl_idname} failed: {ex}")
return {"CANCELLED"}
def copy_commit_id(self, context) -> None:
speckle = get_speckle(context)
(_, _, _, commit) = speckle.validate_commit_selection()
bpy.context.window_manager.clipboard = commit.id
metrics.track(
"Connector Action",
custom_props={
"name": "copy_commit_id"
},
)
class CopyBranchName(bpy.types.Operator):
"""
Copy branch name to clipboard
"""
bl_idname = "speckle.branch_copy_name"
bl_label = "Copy branch name"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Copy branch name to clipboard"
def execute(self, context):
try:
self.copy_branch_id(context)
return {"FINISHED"}
except Exception as ex:
_report(f"{self.bl_idname} failed: {ex}")
return {"CANCELLED"}
def copy_branch_id(self, context) -> None:
speckle = get_speckle(context)
(_, _, branch) = speckle.validate_branch_selection()
bpy.context.window_manager.clipboard = branch.name
metrics.track(
"Connector Action",
custom_props={
"name": "copy_branch_id"
},
)
return {"FINISHED"}
+24 -25
View File
@@ -1,12 +1,12 @@
"""
User account operators
"""
from typing import cast
from typing import List, cast
import bpy
from bpy.types import Context
from bpy_speckle.functions import _report
from bpy_speckle.clients import speckle_clients
from bpy_speckle.properties.scene import SpeckleCommitObject, SpeckleSceneSettings, SpeckleStreamObject, SpeckleUserObject, get_speckle
from bpy_speckle.properties.scene import SpeckleBranchObject, SpeckleCommitObject, SpeckleSceneSettings, SpeckleStreamObject, SpeckleUserObject, get_speckle
from specklepy.core.api.client import SpeckleClient
from specklepy.core.api.models import Stream
from specklepy.core.api.credentials import get_local_accounts, Account
@@ -18,7 +18,7 @@ class ResetUsers(bpy.types.Operator):
"""
bl_idname = "speckle.users_reset"
bl_label = "Reset users"
bl_label = "Reset Users"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
@@ -46,18 +46,20 @@ class ResetUsers(bpy.types.Operator):
class LoadUsers(bpy.types.Operator):
"""
Load all users from local user database
Loads all user accounts from the credentials in the local database.
See docs to add accounts via Manager
"""
bl_idname = "speckle.users_load"
bl_label = "Load users"
bl_label = "Load Users"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Loads all user accounts from the credentials in the local database.\nSee docs to add accounts via Manager"
def execute(self, context):
_report("Loading users...")
speckle = cast(SpeckleSceneSettings, context.scene.speckle) #type: ignore
speckle = get_speckle(context)
users_list = speckle.users
ResetUsers.reset_ui(context)
@@ -133,13 +135,17 @@ def add_user_stream(user: SpeckleUserObject, stream: Stream):
s.id = stream.id
s.description = stream.description
_report(f"Adding stream {s.id} - {s.name}")
if not stream.branches:
return
# branches = [branch for branch in stream.branches.items if branch.name != "globals"]
for b in stream.branches.items:
branch = s.branches.add()
branch = cast(SpeckleBranchObject, s.branches.add())
branch.name = b.name
branch.id = b.id
branch.description = b.description or ""
if not b.commits:
continue
@@ -154,32 +160,25 @@ def add_user_stream(user: SpeckleUserObject, stream: Stream):
commit.source_application = str(c.sourceApplication)
commit.referenced_object = c.referencedObject
if hasattr(s, "baseProperties"):
s.units = stream.baseProperties.units # type: ignore
else:
s.units = "Meters"
class LoadUserStreams(bpy.types.Operator):
"""
Load all available streams for active user
(Re)Load all available projects for active user
"""
bl_idname = "speckle.load_user_streams"
bl_label = "Load user streams"
bl_label = "Load User's Projects"
bl_options = {"REGISTER", "UNDO"}
bl_description = "(Re)load all available user streams"
bl_description = "(Re)Load all available projects for active user"
stream_limit: int = 20
branch_limit: int = 20
branch_limit: int = 100
commits_limit: int = 10
def execute(self, context):
try:
self.load_user_stream(context)
return {"FINISHED"}
except Exception as ex:
_report(f"{self.bl_idname} failed: {ex}")
return {"CANCELLED"}
self.load_user_stream(context)
return {"FINISHED"}
def load_user_stream(self, context: Context) -> None:
speckle = get_speckle(context)
@@ -190,17 +189,17 @@ class LoadUserStreams(bpy.types.Operator):
try:
streams = client.stream.list(stream_limit=self.stream_limit)
except Exception as ex:
raise Exception(f"Failed to retrieve streams") from ex
raise Exception(f"Failed to retrieve projects") from ex
if not streams:
raise Exception("Zero streams found")
_report("Zero projects found")
return
user.streams.clear()
for s in streams:
assert(s.id)
sstream = client.stream.get(id=s.id, branch_limit=self.branch_limit)
sstream = client.stream.get(id=s.id, branch_limit=self.branch_limit, commit_limit=10)
add_user_stream(user, sstream)
bpy.context.view_layer.update()
+4 -5
View File
@@ -5,7 +5,7 @@ import bpy
class SpeckleCollectionSettings(bpy.types.PropertyGroup):
enabled: bpy.props.BoolProperty(default=False, name="Enabled")
enabled: bpy.props.BoolProperty(default=False, name="Enabled") # type: ignore
send_or_receive: bpy.props.EnumProperty(
name="Mode",
@@ -13,7 +13,6 @@ class SpeckleCollectionSettings(bpy.types.PropertyGroup):
("send", "Send", "Send data to Speckle server."),
("receive", "Receive", "Receive data from Speckle server."),
),
)
stream_id: bpy.props.StringProperty(default="")
name: bpy.props.StringProperty(default="")
units: bpy.props.StringProperty(default="")
) # type: ignore
stream_id: bpy.props.StringProperty(default="") # type: ignore
name: bpy.props.StringProperty(default="") # type: ignore
+3 -3
View File
@@ -13,6 +13,6 @@ class SpeckleObjectSettings(bpy.types.PropertyGroup):
("send", "Send", "Send data to Speckle server."),
("receive", "Receive", "Receive data from Speckle server."),
),
)
stream_id: bpy.props.StringProperty(default="")
object_id: bpy.props.StringProperty(default="")
) # type: ignore
stream_id: bpy.props.StringProperty(default="") # type: ignore
object_id: bpy.props.StringProperty(default="") # type: ignore
+59 -55
View File
@@ -1,48 +1,49 @@
"""
Scene properties
"""
from typing import Optional, Tuple
from typing import Iterable, Optional, Tuple, cast
import bpy
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
IntProperty,
PointerProperty,
)
class SpeckleSceneObject(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(default="")
name: bpy.props.StringProperty(default="") # type: ignore
class SpeckleCommitObject(bpy.types.PropertyGroup):
id: StringProperty(default="")
message: StringProperty(default="")
author_name: StringProperty(default="")
author_id: StringProperty(default="")
created_at: StringProperty(default="")
source_application: StringProperty(default="")
referenced_object: StringProperty(default="")
id: StringProperty(default="") # type: ignore
message: StringProperty(default="") # type: ignore
author_name: StringProperty(default="") # type: ignore
author_id: StringProperty(default="") # type: ignore
created_at: StringProperty(default="") # type: ignore
source_application: StringProperty(default="") # type: ignore
referenced_object: StringProperty(default="") # type: ignore
class SpeckleBranchObject(bpy.types.PropertyGroup):
def get_commits(self, context):
if self.commits != None and len(self.commits) > 0:
COMMITS = cast(Iterable[SpeckleCommitObject], self.commits)
return [
(str(i), commit.id, commit.message, i)
for i, commit in enumerate(self.commits)
for i, commit in enumerate(COMMITS)
]
return [("0", "<none>", "<none>", 0)]
name: StringProperty(default="main")
commits: CollectionProperty(type=SpeckleCommitObject)
name: StringProperty(default="main") # type: ignore
id: StringProperty(default="") # type: ignore
description: StringProperty(default="") # type: ignore
commits: CollectionProperty(type=SpeckleCommitObject) # type: ignore
commit: EnumProperty(
name="Commit",
description="Active commit",
name="Version",
description="Selected model version",
items=get_commits,
)
) # type: ignore
def get_active_commit(self) -> Optional[SpeckleCommitObject]:
selected_index = int(self.commit)
@@ -54,24 +55,23 @@ class SpeckleBranchObject(bpy.types.PropertyGroup):
class SpeckleStreamObject(bpy.types.PropertyGroup):
def get_branches(self, context):
if self.branches:
BRANCHES = cast(Iterable[SpeckleBranchObject], self.branches)
return [
(str(i), branch.name, branch.name, i)
for i, branch in enumerate(self.branches)
(str(i), branch.name, branch.description, i)
for i, branch in enumerate(BRANCHES)
if branch.name != "globals"
]
return [("0", "<none>", "<none>", 0)]
name: StringProperty(default="SpeckleStream")
description: StringProperty(default="No description provided.")
id: StringProperty(default="")
units: StringProperty(default="Meters")
query: StringProperty(default="")
branches: CollectionProperty(type=SpeckleBranchObject)
name: StringProperty(default="") # type: ignore
description: StringProperty(default="") # type: ignore
id: StringProperty(default="") # type: ignore
branches: CollectionProperty(type=SpeckleBranchObject) # type: ignore
branch: EnumProperty(
name="Branch",
description="Active branch",
name="Model",
description="Selected Model",
items=get_branches,
)
) # type: ignore
def get_active_branch(self) -> Optional[SpeckleBranchObject]:
selected_index = int(self.branch)
@@ -81,14 +81,14 @@ class SpeckleStreamObject(bpy.types.PropertyGroup):
class SpeckleUserObject(bpy.types.PropertyGroup):
server_name: StringProperty(default="SpeckleXYZ")
server_url: StringProperty(default="https://speckle.xyz")
id: StringProperty(default="")
name: StringProperty(default="Speckle User")
email: StringProperty(default="user@speckle.xyz")
company: StringProperty(default="SpeckleSystems")
streams: CollectionProperty(type=SpeckleStreamObject)
active_stream: IntProperty(default=0)
server_name: StringProperty(default="SpeckleXYZ") # type: ignore
server_url: StringProperty(default="https://speckle.xyz") # type: ignore
id: StringProperty(default="") # type: ignore
name: StringProperty(default="Speckle User") # type: ignore
email: StringProperty(default="user@speckle.xyz") # type: ignore
company: StringProperty(default="SpeckleSystems") # type: ignore
streams: CollectionProperty(type=SpeckleStreamObject) # type: ignore
active_stream: IntProperty(default=0) # type: ignore
def get_active_stream(self) -> Optional[SpeckleStreamObject]:
selected_index = int(self.active_stream)
@@ -107,18 +107,19 @@ class SpeckleSceneSettings(bpy.types.PropertyGroup):
name="Available streams",
description="Available streams associated with user.",
items=[],
)
) # type: ignore
users: CollectionProperty(type=SpeckleUserObject)
users: CollectionProperty(type=SpeckleUserObject) # type: ignore
def get_users(self, context):
USERS = cast(Iterable[SpeckleUserObject], self.users)
return [
(str(i), "{} ({})".format(user.email, user.server_name), user.server_url, i)
for i, user in enumerate(self.users)
(str(i), f"{user.email} ({user.server_name})", user.server_url, i)
for i, user in enumerate(USERS)
]
def set_user(self, context):
bpy.ops.speckle.load_user_streams()
bpy.ops.speckle.load_user_streams() # type: ignore
active_user: EnumProperty(
items=get_users,
@@ -127,29 +128,29 @@ class SpeckleSceneSettings(bpy.types.PropertyGroup):
update=set_user,
get=None,
set=None,
)
) # type: ignore
objects: CollectionProperty(type=SpeckleSceneObject)
objects: CollectionProperty(type=SpeckleSceneObject) # type: ignore
scale: FloatProperty(default=0.001)
scale: FloatProperty(default=0.001) # type: ignore
user: StringProperty(
name="User",
description="Current user.",
description="Current user",
default="Speckle User",
)
) # type: ignore
receive_script: EnumProperty(
name="Receive script",
description="Script to run when receiving stream objects.",
description="Custom py script to execute when receiving objects. See docs for function signature.",
items=get_scripts,
)
) # type: ignore
send_script: EnumProperty(
name="Send script",
description="Script to run when sending stream objects.",
description="Custom py script to execute when sending objects. See docs for function signature",
items=get_scripts,
)
) # type: ignore
def get_active_user(self) -> Optional[SpeckleUserObject]:
selected_index = int(self.active_user)
@@ -161,7 +162,7 @@ class SpeckleSceneSettings(bpy.types.PropertyGroup):
def validate_user_selection(self) -> SpeckleUserObject:
user = self.get_active_user()
if not user:
raise SelectionException("No user selected/found")
raise SelectionException("No user account selected/found")
return user
def validate_stream_selection(self) -> Tuple[SpeckleUserObject, SpeckleStreamObject]:
@@ -169,7 +170,7 @@ class SpeckleSceneSettings(bpy.types.PropertyGroup):
stream = user.get_active_stream()
if not stream:
raise SelectionException("No stream selected/found")
raise SelectionException("No project selected/found")
return (user, stream)
@@ -178,14 +179,14 @@ class SpeckleSceneSettings(bpy.types.PropertyGroup):
branch = stream.get_active_branch()
if not branch:
raise SelectionException("No branch selected/found")
raise SelectionException("No model selected/found")
return (user, stream, branch)
def validate_commit_selection(self) ->Tuple[SpeckleUserObject, SpeckleStreamObject, SpeckleBranchObject, SpeckleCommitObject]:
(user, stream, branch) = self.validate_branch_selection()
commit = branch.get_active_commit()
if commit is None:
raise SelectionException("No commit selected/found")
raise SelectionException("No model version selected/found")
return (user, stream, branch, commit)
@@ -193,4 +194,7 @@ class SelectionException(Exception):
pass
def get_speckle(context: bpy.types.Context) -> SpeckleSceneSettings:
"""
Gets the speckle scene object
"""
return context.scene.speckle #type: ignore
+3 -2
View File
@@ -10,8 +10,9 @@ from bpy.props import (
CollectionProperty,
EnumProperty,
)
from deprecated import deprecated
@deprecated
class OBJECT_PT_speckle(bpy.types.Panel):
bl_space_type = "PROPERTIES"
# bl_idname = 'OBJECT_PT_speckle'
@@ -28,7 +29,7 @@ class OBJECT_PT_speckle(bpy.types.Panel):
layout.active = ob.speckle.enabled
col = layout.column()
col.prop(ob.speckle, "send_or_receive", expand=True)
col.prop(ob.speckle, "stream_id", text="Stream ID")
col.prop(ob.speckle, "stream_id", text="Project ID")
col.prop(ob.speckle, "object_id", text="Object ID")
col.operator("speckle.update_object", text="Update")
col.operator("speckle.reset_object", text="Reset")
+24 -35
View File
@@ -4,20 +4,10 @@ Speckle UI elements for the 3d viewport
import bpy
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
CollectionProperty,
EnumProperty,
)
from datetime import datetime
"""
Compatibility
TODO: evaluate if we should still support Blender <2.80
"""
from bpy_speckle.properties.scene import get_speckle
Region = "TOOLS" if bpy.app.version < (2, 80, 0) else "UI"
@@ -78,7 +68,7 @@ class VIEW3D_UL_SpeckleUsers(bpy.types.UIList):
class VIEW3D_UL_SpeckleStreams(bpy.types.UIList):
"""
Speckle stream list
Speckle projects list
"""
def draw_item(self, context, layout, data, stream, active_data, active_propname):
@@ -94,7 +84,7 @@ class VIEW3D_UL_SpeckleStreams(bpy.types.UIList):
elif self.layout_type in {"GRID"}:
layout.alignment = "CENTER"
layout.label(text="Streams", icon_value=0)
layout.label(text="Projects", icon_value=0)
class VIEW3D_PT_SpeckleUser(bpy.types.Panel):
@@ -106,10 +96,10 @@ class VIEW3D_PT_SpeckleUser(bpy.types.Panel):
bl_region_type = Region
bl_category = "Speckle"
bl_context = "objectmode"
bl_label = "User"
bl_label = "User Account"
def draw(self, context):
speckle = context.scene.speckle
speckle = get_speckle(context)
layout = self.layout
col = layout.column()
@@ -119,28 +109,28 @@ class VIEW3D_PT_SpeckleUser(bpy.types.Panel):
else:
col.prop(speckle, "active_user", text="")
user = speckle.users[int(speckle.active_user)]
col.label(text="{} ({})".format(user.server_name, user.server_url))
col.label(text="{} ({})".format(user.name, user.email))
col.label(text=f"{user.server_name} ({user.server_url})")
col.label(text=f"{user.name} ({user.email})")
col.operator("speckle.users_load", text="", icon="FILE_REFRESH")
class VIEW3D_PT_SpeckleStreams(bpy.types.Panel):
"""
Speckle Streams UI panel in the 3d viewport
Speckle projects UI panel in the 3d viewport
"""
bl_space_type = "VIEW_3D"
bl_region_type = Region
bl_category = "Speckle"
bl_context = "objectmode"
bl_label = "Streams"
bl_label = "Projects"
def draw(self, context):
speckle = context.scene.speckle
speckle = get_speckle(context)
col = self.layout.column()
if len(speckle.users) < 1:
col.label(text="No stream data.")
col.label(text="No Projects")
else:
user = speckle.users[int(speckle.active_user)]
col.template_list(
@@ -149,31 +139,31 @@ class VIEW3D_PT_SpeckleStreams(bpy.types.Panel):
row = col.row(align=True)
row.operator("speckle.add_stream_from_url", text="", icon="URL")
row.operator("speckle.create_stream", text="", icon="ADD")
row.operator("speckle.delete_stream", text="", icon="REMOVE")
row.operator("speckle.load_user_streams", text="", icon="FILE_REFRESH")
class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
"""
Speckle Active Streams UI panel in the 3d viewport
Speckle Active Projects UI panel in the 3d viewport
"""
bl_space_type = "VIEW_3D"
bl_region_type = Region
bl_category = "Speckle"
bl_context = "objectmode"
bl_label = "Active stream"
bl_label = "Active Project"
def draw(self, context):
speckle = context.scene.speckle
speckle = get_speckle(context)
col = self.layout.column()
if len(speckle.users) < 1:
col.label(text="No stream data.")
col.label(text="No projects")
else:
user = speckle.users[int(speckle.active_user)]
user = speckle.validate_user_selection()
#user = speckle.users[int(speckle.active_user)]
if len(user.streams) < 1:
col.label(text="No active stream.")
col.label(text="No active project")
else:
stream = user.streams[user.active_stream]
# user.active_stream = min(user.active_stream, len(user.streams) - 1)
@@ -183,14 +173,14 @@ class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
col.separator()
row = col.row()
row.prop(stream, "branch", text="")
row.operator("speckle.branch_copy_name", text="", icon="COPY_ID")
row.prop(stream, "branch", text="Model")
row.operator("speckle.model_copy_id", text="", icon="COPY_ID")
if len(stream.branches) > 0:
branch = stream.branches[int(stream.branch)]
row = col.row()
row.prop(branch, "commit", text="")
row.prop(branch, "commit", text="Version")
row.operator("speckle.commit_copy_id", text="", icon="COPY_ID")
if len(branch.commits) > 0:
@@ -213,7 +203,7 @@ class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
col.label(text=f"{commit.author_name} ({commit.author_id})")
col.label(text=commit.source_application)
else:
col.label(text="No branches found!")
col.label(text="No models found!")
col.separator()
@@ -225,7 +215,6 @@ class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
subcol = row.column()
subcol.operator("speckle.send_stream_objects", text="Send")
subcol.prop(speckle, "send_script", text="")
area.prop(stream, "query", text="Filter")
col.separator()
@@ -246,7 +235,7 @@ class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
area.separator()
col.separator()
col.operator("speckle.view_stream_data_api", text="Open Stream in Web")
col.operator("speckle.view_stream_data_api", text="Open Model in Web")
class VIEW3D_PT_SpeckleHelp(bpy.types.Panel):
Generated
+607 -576
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -7,17 +7,17 @@ license = "Apache-2.0"
[tool.poetry.dependencies]
python = ">=3.8, <4.0.0"
specklepy = "^2.17.14"
specklepy = "^2.19.1"
attrs = "^23.1.0"
# [tool.poetry.group.local_specklepy.dependencies]
# specklepy = {path = "../specklepy", develop = true}
[tool.poetry.group.dev.dependencies]
fake-bpy-module-latest = "^20230117"
black = "^22.10.0"
fake-bpy-module-latest = "^20240524"
black = "23.11.0"
pylint = "^2.15.7"
ruff = "^0.0.187"
ruff = "^0.4.4"
[build-system]
requires = ["poetry-core>=1.0.0"]