Jedd/cxpla 94 track workspace id in specklepy connector metrics (#209)
* workspace tracking + black * works
This commit is contained in:
@@ -1,15 +1,16 @@
|
|||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from bpy_speckle.installer import ensure_dependencies
|
from bpy_speckle.installer import ensure_dependencies
|
||||||
|
|
||||||
ensure_dependencies(f"Blender {bpy.app.version[0]}.{bpy.app.version[1]}")
|
ensure_dependencies(f"Blender {bpy.app.version[0]}.{bpy.app.version[1]}")
|
||||||
|
|
||||||
|
from bpy.app.handlers import persistent
|
||||||
from specklepy.logging import metrics
|
from specklepy.logging import metrics
|
||||||
|
|
||||||
from bpy_speckle.ui import *
|
|
||||||
from bpy_speckle.properties import *
|
|
||||||
from bpy_speckle.operators import *
|
|
||||||
from bpy_speckle.callbacks import *
|
from bpy_speckle.callbacks import *
|
||||||
from bpy.app.handlers import persistent
|
from bpy_speckle.operators import *
|
||||||
|
from bpy_speckle.properties import *
|
||||||
|
from bpy_speckle.ui import *
|
||||||
|
|
||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "SpeckleBlender 2.0",
|
"name": "SpeckleBlender 2.0",
|
||||||
@@ -24,7 +25,6 @@ bl_info = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Import SpeckleBlender classes
|
Import SpeckleBlender classes
|
||||||
"""
|
"""
|
||||||
@@ -34,16 +34,18 @@ Add load handler to initialize Speckle when
|
|||||||
loading a Blender file
|
loading a Blender file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def load_handler(dummy):
|
def load_handler(dummy):
|
||||||
pass
|
pass
|
||||||
# Calling users_load is an expensive operation, one that force users to wait a good 10s every time blender loads.
|
# Calling users_load is an expensive operation, one that force users to wait a good 10s every time blender loads.
|
||||||
# Until we can do this non-blocking, we will make the user hit the refresh button each time.
|
# Until we can do this non-blocking, we will make the user hit the refresh button each time.
|
||||||
#bpy.ops.speckle.users_load()
|
# bpy.ops.speckle.users_load()
|
||||||
|
|
||||||
# Instead, we shall just reset the user selection to an uninitiailised state
|
# Instead, we shall just reset the user selection to an uninitiailised state
|
||||||
bpy.ops.speckle.users_reset()
|
bpy.ops.speckle.users_reset()
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Permanent handle on callbacks
|
Permanent handle on callbacks
|
||||||
"""
|
"""
|
||||||
@@ -93,7 +95,6 @@ def register():
|
|||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
|
|
||||||
bpy.app.handlers.load_post.remove(load_handler)
|
bpy.app.handlers.load_post.remove(load_handler)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,24 +1,31 @@
|
|||||||
from typing import Dict, Optional, Tuple, Union
|
from typing import Dict, Optional, Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Object, Collection, ID
|
|
||||||
from specklepy.objects.base import Base
|
|
||||||
from bpy_speckle.functions import _report
|
|
||||||
from specklepy.objects.graph_traversal.commit_object_builder import CommitObjectBuilder, ROOT
|
|
||||||
from specklepy.objects import Base
|
|
||||||
from specklepy.objects.other import Collection as SCollection
|
|
||||||
from attrs import define
|
from attrs import define
|
||||||
|
from bpy.types import ID, Collection, Object
|
||||||
|
from specklepy.objects.base import Base
|
||||||
|
from specklepy.objects.graph_traversal.commit_object_builder import (
|
||||||
|
ROOT, CommitObjectBuilder)
|
||||||
|
from specklepy.objects.other import Collection as SCollection
|
||||||
|
|
||||||
|
from bpy_speckle.functions import _report
|
||||||
|
|
||||||
ELEMENTS = "elements"
|
ELEMENTS = "elements"
|
||||||
|
|
||||||
|
|
||||||
def _id(native_object: ID) -> str:
|
def _id(native_object: ID) -> str:
|
||||||
#NOTE: to avoid naming collisions, we prefix collections and objects differently
|
# NOTE: to avoid naming collisions, we prefix collections and objects differently
|
||||||
return f"{type(native_object).__name__}:{native_object.name_full}"
|
return f"{type(native_object).__name__}:{native_object.name_full}"
|
||||||
|
|
||||||
|
|
||||||
def _try_id(native_object: Optional[Union[Collection, Object]]) -> Optional[str]:
|
def _try_id(native_object: Optional[Union[Collection, Object]]) -> Optional[str]:
|
||||||
return _id(native_object) if native_object else None
|
return _id(native_object) if native_object else None
|
||||||
|
|
||||||
|
|
||||||
def convert_collection_to_speckle(col: Collection) -> SCollection:
|
def convert_collection_to_speckle(col: Collection) -> SCollection:
|
||||||
converted_collection = SCollection(name = col.name_full, collectionType = "Blender Collection", elements = [])
|
converted_collection = SCollection(
|
||||||
|
name=col.name_full, collectionType="Blender Collection", elements=[]
|
||||||
|
)
|
||||||
converted_collection.applicationId = _id(col)
|
converted_collection.applicationId = _id(col)
|
||||||
|
|
||||||
color_tag = col.color_tag
|
color_tag = col.color_tag
|
||||||
@@ -27,9 +34,9 @@ def convert_collection_to_speckle(col: Collection) -> SCollection:
|
|||||||
|
|
||||||
return converted_collection
|
return converted_collection
|
||||||
|
|
||||||
|
|
||||||
@define(slots=True)
|
@define(slots=True)
|
||||||
class BlenderCommitObjectBuilder(CommitObjectBuilder[Object]):
|
class BlenderCommitObjectBuilder(CommitObjectBuilder[Object]):
|
||||||
|
|
||||||
_collections: Dict[str, SCollection]
|
_collections: Dict[str, SCollection]
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -37,35 +44,41 @@ class BlenderCommitObjectBuilder(CommitObjectBuilder[Object]):
|
|||||||
self._collections = {}
|
self._collections = {}
|
||||||
|
|
||||||
def include_object(self, conversion_result: Base, native_object: Object) -> None:
|
def include_object(self, conversion_result: Base, native_object: Object) -> None:
|
||||||
|
|
||||||
# Set the Child -> Parent relationships
|
# Set the Child -> Parent relationships
|
||||||
parent = native_object.parent
|
parent = native_object.parent
|
||||||
|
|
||||||
parent_collections = native_object.users_collection
|
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
|
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)
|
app_id = _id(native_object)
|
||||||
conversion_result.applicationId = app_id
|
conversion_result.applicationId = app_id
|
||||||
self.converted[app_id] = conversion_result
|
self.converted[app_id] = conversion_result
|
||||||
|
|
||||||
# in order or priority, direct parent, direct parent collection, root
|
# in order or priority, direct parent, direct parent collection, root
|
||||||
self.set_relationship(app_id, (_try_id(parent), ELEMENTS), (_try_id(parent_collection), ELEMENTS), (ROOT, ELEMENTS))
|
self.set_relationship(
|
||||||
|
app_id,
|
||||||
|
(_try_id(parent), ELEMENTS),
|
||||||
|
(_try_id(parent_collection), ELEMENTS),
|
||||||
|
(ROOT, ELEMENTS),
|
||||||
|
)
|
||||||
# if parent_collection:
|
# if parent_collection:
|
||||||
# self._include_collection(parent_collection)
|
# self._include_collection(parent_collection)
|
||||||
|
|
||||||
def ensure_collection(self, col: Collection) -> SCollection:
|
def ensure_collection(self, col: Collection) -> SCollection:
|
||||||
id = _id(col)
|
id = _id(col)
|
||||||
if id in self._collections:
|
if id in self._collections:
|
||||||
return self._collections[id] # collection already converted!
|
return self._collections[id] # collection already converted!
|
||||||
|
|
||||||
# Set the Parent -> Children relationships
|
# Set the Parent -> Children relationships
|
||||||
for c in col.children:
|
for c in col.children:
|
||||||
#NOTE: There's no falling back to the grandparent, if the direct parent collection wasn't converted, then we we fallback to the root
|
# NOTE: There's no falling back to the grandparent, if the direct parent collection wasn't converted, then we we fallback to the root
|
||||||
self.set_relationship(_id(c), (id, ELEMENTS), (ROOT, ELEMENTS))
|
self.set_relationship(_id(c), (id, ELEMENTS), (ROOT, ELEMENTS))
|
||||||
|
|
||||||
# Set Child -> Parent relationship
|
# Set Child -> Parent relationship
|
||||||
# parent = self.find_collection_parent(col)
|
# parent = self.find_collection_parent(col)
|
||||||
# self.set_relationship(id, (_try_builder_id(parent), ELEMENTS), (ROOT, ELEMENTS))
|
# self.set_relationship(id, (_try_builder_id(parent), ELEMENTS), (ROOT, ELEMENTS))
|
||||||
|
|
||||||
converted_collection = convert_collection_to_speckle(col)
|
converted_collection = convert_collection_to_speckle(col)
|
||||||
self.converted[id] = converted_collection
|
self.converted[id] = converted_collection
|
||||||
@@ -74,7 +87,7 @@ class BlenderCommitObjectBuilder(CommitObjectBuilder[Object]):
|
|||||||
return converted_collection
|
return converted_collection
|
||||||
|
|
||||||
def build_commit_object(self, root_commit_object: Base) -> None:
|
def build_commit_object(self, root_commit_object: Base) -> None:
|
||||||
assert(root_commit_object.applicationId in self.converted)
|
assert root_commit_object.applicationId in self.converted
|
||||||
|
|
||||||
# Create all collections
|
# Create all collections
|
||||||
root_col = self.ensure_collection(bpy.context.scene.collection)
|
root_col = self.ensure_collection(bpy.context.scene.collection)
|
||||||
@@ -87,19 +100,22 @@ class BlenderCommitObjectBuilder(CommitObjectBuilder[Object]):
|
|||||||
|
|
||||||
self.apply_relationships(objects_to_build, root_commit_object)
|
self.apply_relationships(objects_to_build, root_commit_object)
|
||||||
|
|
||||||
assert(isinstance(root_commit_object, SCollection))
|
assert isinstance(root_commit_object, SCollection)
|
||||||
# Kill unused collections
|
# Kill unused collections
|
||||||
|
|
||||||
def should_remove_unuseful_collection(col: SCollection) -> bool: #TODO: this maybe could be optimised
|
def should_remove_unuseful_collection(
|
||||||
|
col: SCollection,
|
||||||
|
) -> bool: # TODO: this maybe could be optimised
|
||||||
elements = col.elements
|
elements = col.elements
|
||||||
if not elements: return True
|
if not elements:
|
||||||
|
return True
|
||||||
|
|
||||||
should_remove_this_col = True
|
should_remove_this_col = True
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(elements):
|
while i < len(elements):
|
||||||
c = elements[i]
|
c = elements[i]
|
||||||
if not isinstance(c, SCollection):
|
if not isinstance(c, SCollection):
|
||||||
# col has objects (c)
|
# col has objects (c)
|
||||||
should_remove_this_col = False
|
should_remove_this_col = False
|
||||||
i += 1
|
i += 1
|
||||||
@@ -113,8 +129,10 @@ class BlenderCommitObjectBuilder(CommitObjectBuilder[Object]):
|
|||||||
should_remove_this_col = False
|
should_remove_this_col = False
|
||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return should_remove_this_col
|
return should_remove_this_col
|
||||||
|
|
||||||
if should_remove_unuseful_collection(root_commit_object):
|
if should_remove_unuseful_collection(root_commit_object):
|
||||||
_report("WARNING: Only empty collections have been converted!") #TODO: consider raising exception here, to halt the send operation
|
_report(
|
||||||
|
"WARNING: Only empty collections have been converted!"
|
||||||
|
) # TODO: consider raising exception here, to halt the send operation
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
from .on_mesh_edit import scb_on_mesh_edit
|
|
||||||
from .draw_speckle_info import draw_speckle_info
|
from .draw_speckle_info import draw_speckle_info
|
||||||
|
from .on_mesh_edit import scb_on_mesh_edit
|
||||||
|
|||||||
@@ -3,5 +3,4 @@ Permanent handle on all user clients
|
|||||||
"""
|
"""
|
||||||
from specklepy.core.api.client import SpeckleClient
|
from specklepy.core.api.client import SpeckleClient
|
||||||
|
|
||||||
|
|
||||||
speckle_clients: list[SpeckleClient] = []
|
speckle_clients: list[SpeckleClient] = []
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ IGNORED_PROPERTY_KEYS = {
|
|||||||
"vertices",
|
"vertices",
|
||||||
"renderMaterial",
|
"renderMaterial",
|
||||||
"textureCoordinates",
|
"textureCoordinates",
|
||||||
"totalChildrenCount"
|
"totalChildrenCount",
|
||||||
}
|
}
|
||||||
|
|
||||||
DISPLAY_VALUE_PROPERTY_ALIASES = {"displayValue", "@displayValue"}
|
DISPLAY_VALUE_PROPERTY_ALIASES = {"displayValue", "@displayValue"}
|
||||||
@@ -19,4 +19,4 @@ ELEMENTS_PROPERTY_ALIASES = {"elements", "@elements"}
|
|||||||
OBJECT_NAME_MAX_LENGTH = 62
|
OBJECT_NAME_MAX_LENGTH = 62
|
||||||
SPECKLE_ID_LENGTH = 32
|
SPECKLE_ID_LENGTH = 32
|
||||||
OBJECT_NAME_SPECKLE_SEPARATOR = " -- "
|
OBJECT_NAME_SPECKLE_SEPARATOR = " -- "
|
||||||
OBJECT_NAME_NUMERAL_SEPARATOR = '.'
|
OBJECT_NAME_NUMERAL_SEPARATOR = "."
|
||||||
|
|||||||
+333
-194
@@ -1,66 +1,75 @@
|
|||||||
import math
|
import math
|
||||||
from typing import Any, Dict, Iterable, List, Optional, Sequence, Union, Collection, cast
|
from typing import Any, Collection, Dict, Iterable, List, Optional, Union
|
||||||
from bpy_speckle.convert.constants import DISPLAY_VALUE_PROPERTY_ALIASES, ELEMENTS_PROPERTY_ALIASES, OBJECT_NAME_MAX_LENGTH, OBJECT_NAME_NUMERAL_SEPARATOR, OBJECT_NAME_SPECKLE_SEPARATOR, SPECKLE_ID_LENGTH
|
|
||||||
from bpy_speckle.functions import get_default_traversal_func, get_scale_length, _report
|
|
||||||
from bpy_speckle.convert.util import ConversionSkippedException
|
|
||||||
from mathutils import (
|
|
||||||
Matrix as MMatrix,
|
|
||||||
Vector as MVector,
|
|
||||||
Quaternion as MQuaternion,
|
|
||||||
)
|
|
||||||
import bpy, bmesh
|
|
||||||
from specklepy.objects.other import (
|
|
||||||
Collection as SCollection,
|
|
||||||
Instance,
|
|
||||||
Transform,
|
|
||||||
BlockDefinition,
|
|
||||||
)
|
|
||||||
from specklepy.objects.base import Base
|
|
||||||
from specklepy.objects.geometry import Mesh, Line, Polyline, Curve, Arc, Polycurve, Ellipse, Circle, Plane
|
|
||||||
from bpy.types import Object, Collection as BCollection
|
|
||||||
|
|
||||||
from .util import (
|
import bmesh
|
||||||
add_to_hierarchy,
|
import bpy
|
||||||
get_render_material,
|
from bpy.types import Collection as BCollection
|
||||||
get_vertex_color_material,
|
from bpy.types import Object
|
||||||
render_material_to_native,
|
from mathutils import Matrix as MMatrix
|
||||||
add_custom_properties,
|
from mathutils import Quaternion as MQuaternion
|
||||||
add_vertices,
|
from mathutils import Vector as MVector
|
||||||
add_faces,
|
from specklepy.objects.base import Base
|
||||||
add_colors,
|
from specklepy.objects.geometry import (Arc, Circle, Curve, Ellipse, Line,
|
||||||
add_uv_coords,
|
Mesh, Plane, Polycurve, Polyline)
|
||||||
)
|
from specklepy.objects.other import BlockDefinition
|
||||||
|
from specklepy.objects.other import Collection as SCollection
|
||||||
|
from specklepy.objects.other import Instance, Transform
|
||||||
|
|
||||||
|
from bpy_speckle.convert.constants import (DISPLAY_VALUE_PROPERTY_ALIASES,
|
||||||
|
ELEMENTS_PROPERTY_ALIASES,
|
||||||
|
OBJECT_NAME_MAX_LENGTH,
|
||||||
|
OBJECT_NAME_NUMERAL_SEPARATOR,
|
||||||
|
OBJECT_NAME_SPECKLE_SEPARATOR,
|
||||||
|
SPECKLE_ID_LENGTH)
|
||||||
|
from bpy_speckle.convert.util import ConversionSkippedException
|
||||||
|
from bpy_speckle.functions import (_report, get_default_traversal_func,
|
||||||
|
get_scale_length)
|
||||||
|
|
||||||
|
from .util import (add_colors, add_custom_properties, add_faces,
|
||||||
|
add_to_hierarchy, add_uv_coords, add_vertices,
|
||||||
|
get_render_material, get_vertex_color_material,
|
||||||
|
render_material_to_native)
|
||||||
|
|
||||||
SUPPORTED_CURVES = (Line, Polyline, Curve, Arc, Polycurve, Ellipse, Circle)
|
SUPPORTED_CURVES = (Line, Polyline, Curve, Arc, Polycurve, Ellipse, Circle)
|
||||||
CAN_CONVERT_TO_NATIVE = (
|
CAN_CONVERT_TO_NATIVE = (
|
||||||
|
|
||||||
Mesh,
|
Mesh,
|
||||||
*SUPPORTED_CURVES,
|
*SUPPORTED_CURVES,
|
||||||
Instance,
|
Instance,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _has_native_conversion(speckle_object: Base) -> bool:
|
def _has_native_conversion(speckle_object: Base) -> bool:
|
||||||
return any(isinstance(speckle_object, t) for t in CAN_CONVERT_TO_NATIVE) or "View" in speckle_object.speckle_type #hack
|
return (
|
||||||
|
any(isinstance(speckle_object, t) for t in CAN_CONVERT_TO_NATIVE)
|
||||||
|
or "View" in speckle_object.speckle_type
|
||||||
|
) # hack
|
||||||
|
|
||||||
|
|
||||||
|
def _has_fallback_conversion(speckle_object: Base) -> bool:
|
||||||
|
return any(
|
||||||
|
getattr(speckle_object, alias, None) for alias in DISPLAY_VALUE_PROPERTY_ALIASES
|
||||||
|
)
|
||||||
|
|
||||||
def _has_fallback_conversion(speckle_object: Base) -> bool:
|
|
||||||
return any(getattr(speckle_object, alias, None) for alias in DISPLAY_VALUE_PROPERTY_ALIASES)
|
|
||||||
|
|
||||||
def can_convert_to_native(speckle_object: Base) -> bool:
|
def can_convert_to_native(speckle_object: Base) -> bool:
|
||||||
|
if _has_native_conversion(speckle_object) or _has_fallback_conversion(
|
||||||
if(_has_native_conversion(speckle_object) or _has_fallback_conversion(speckle_object)):
|
speckle_object
|
||||||
|
):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
convert_instances_as: str = "" #HACK: This is hacky, we need a better way to pass settings down to the converter
|
|
||||||
|
convert_instances_as: str = "" # HACK: This is hacky, we need a better way to pass settings down to the converter
|
||||||
|
|
||||||
|
|
||||||
def set_convert_instances_as(value: str):
|
def set_convert_instances_as(value: str):
|
||||||
global convert_instances_as
|
global convert_instances_as
|
||||||
convert_instances_as = value
|
convert_instances_as = value
|
||||||
|
|
||||||
#TODO: Check usages handle exceptions
|
# TODO: Check usages handle exceptions
|
||||||
|
|
||||||
|
|
||||||
def convert_to_native(speckle_object: Base) -> Object:
|
def convert_to_native(speckle_object: Base) -> Object:
|
||||||
|
|
||||||
speckle_type = type(speckle_object)
|
speckle_type = type(speckle_object)
|
||||||
|
|
||||||
object_name = _generate_object_name(speckle_object)
|
object_name = _generate_object_name(speckle_object)
|
||||||
@@ -71,9 +80,13 @@ def convert_to_native(speckle_object: Base) -> Object:
|
|||||||
|
|
||||||
# convert elements/breps
|
# convert elements/breps
|
||||||
if not _has_native_conversion(speckle_object):
|
if not _has_native_conversion(speckle_object):
|
||||||
(converted, children) = display_value_to_native(speckle_object, object_name, scale)
|
(converted, children) = display_value_to_native(
|
||||||
|
speckle_object, object_name, scale
|
||||||
|
)
|
||||||
if not converted and not children:
|
if not converted and not children:
|
||||||
raise Exception(f"Zero geometry converted from displayValues for {speckle_object}")
|
raise Exception(
|
||||||
|
f"Zero geometry converted from displayValues for {speckle_object}"
|
||||||
|
)
|
||||||
|
|
||||||
# convert supported geometry
|
# convert supported geometry
|
||||||
elif isinstance(speckle_object, Mesh):
|
elif isinstance(speckle_object, Mesh):
|
||||||
@@ -81,24 +94,25 @@ def convert_to_native(speckle_object: Base) -> Object:
|
|||||||
elif speckle_type in SUPPORTED_CURVES:
|
elif speckle_type in SUPPORTED_CURVES:
|
||||||
converted = icurve_to_native(speckle_object, object_name, scale)
|
converted = icurve_to_native(speckle_object, object_name, scale)
|
||||||
elif "View" in speckle_object.speckle_type:
|
elif "View" in speckle_object.speckle_type:
|
||||||
return view_to_native(speckle_object, object_name, scale)
|
return view_to_native(speckle_object, object_name, scale)
|
||||||
elif isinstance(speckle_object, Instance):
|
elif isinstance(speckle_object, Instance):
|
||||||
if convert_instances_as == "linked_duplicates":
|
if convert_instances_as == "linked_duplicates":
|
||||||
converted = instance_to_native_object(speckle_object, scale)
|
converted = instance_to_native_object(speckle_object, scale)
|
||||||
elif convert_instances_as == "collection_instance":
|
elif convert_instances_as == "collection_instance":
|
||||||
converted = instance_to_native_collection_instance(speckle_object, scale)
|
converted = instance_to_native_collection_instance(speckle_object, scale)
|
||||||
else:
|
else:
|
||||||
_report(f"convert_instances_as = '{convert_instances_as}' is not implemented, Instances will be converted as collection instances!")
|
_report(
|
||||||
|
f"convert_instances_as = '{convert_instances_as}' is not implemented, Instances will be converted as collection instances!"
|
||||||
|
)
|
||||||
converted = instance_to_native_collection_instance(speckle_object, scale)
|
converted = instance_to_native_collection_instance(speckle_object, scale)
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Unsupported type {speckle_type}")
|
raise Exception(f"Unsupported type {speckle_type}")
|
||||||
|
|
||||||
|
|
||||||
if not isinstance(converted, Object):
|
if not isinstance(converted, Object):
|
||||||
converted = create_new_object(converted, object_name)
|
converted = create_new_object(converted, object_name)
|
||||||
|
|
||||||
converted.speckle.object_id = str(speckle_object.id) # type: ignore
|
converted.speckle.object_id = str(speckle_object.id) # type: ignore
|
||||||
converted.speckle.enabled = True # type: ignore
|
converted.speckle.enabled = True # type: ignore
|
||||||
add_custom_properties(speckle_object, converted)
|
add_custom_properties(speckle_object, converted)
|
||||||
|
|
||||||
for c in children:
|
for c in children:
|
||||||
@@ -107,15 +121,30 @@ def convert_to_native(speckle_object: Base) -> Object:
|
|||||||
return converted
|
return converted
|
||||||
|
|
||||||
|
|
||||||
|
def display_value_to_native(
|
||||||
|
speckle_object: Base, name: str, scale: float
|
||||||
|
) -> tuple[Optional[bpy.types.Mesh], list[bpy.types.Object]]:
|
||||||
|
return _members_to_native(
|
||||||
|
speckle_object, name, scale, DISPLAY_VALUE_PROPERTY_ALIASES, True
|
||||||
|
)
|
||||||
|
|
||||||
def display_value_to_native(speckle_object: Base, name: str, scale: float) -> tuple[Optional[bpy.types.Mesh], list[bpy.types.Object]]:
|
|
||||||
return _members_to_native(speckle_object, name, scale, DISPLAY_VALUE_PROPERTY_ALIASES, True)
|
|
||||||
|
|
||||||
def elements_to_native(speckle_object: Base, name: str, scale: float) -> list[bpy.types.Object]:
|
def elements_to_native(
|
||||||
(_, elements) = _members_to_native(speckle_object, name, scale, ELEMENTS_PROPERTY_ALIASES, False)
|
speckle_object: Base, name: str, scale: float
|
||||||
|
) -> list[bpy.types.Object]:
|
||||||
|
(_, elements) = _members_to_native(
|
||||||
|
speckle_object, name, scale, ELEMENTS_PROPERTY_ALIASES, False
|
||||||
|
)
|
||||||
return elements
|
return elements
|
||||||
|
|
||||||
def _members_to_native(speckle_object: Base, name: str, scale: float, members: Iterable[str], combineMeshes: bool) -> tuple[Optional[bpy.types.Mesh], list[bpy.types.Object]]:
|
|
||||||
|
def _members_to_native(
|
||||||
|
speckle_object: Base,
|
||||||
|
name: str,
|
||||||
|
scale: float,
|
||||||
|
members: Iterable[str],
|
||||||
|
combineMeshes: bool,
|
||||||
|
) -> tuple[Optional[bpy.types.Mesh], list[bpy.types.Object]]:
|
||||||
"""
|
"""
|
||||||
Converts a given speckle_object by converting specified members
|
Converts a given speckle_object by converting specified members
|
||||||
|
|
||||||
@@ -133,7 +162,8 @@ def _members_to_native(speckle_object: Base, name: str, scale: float, members: I
|
|||||||
display = getattr(speckle_object, alias, None)
|
display = getattr(speckle_object, alias, None)
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
MAX_DEPTH = 255 # some large value, to prevent infinite recursion
|
MAX_DEPTH = 255 # some large value, to prevent infinite recursion
|
||||||
|
|
||||||
def separate(value: Any) -> bool:
|
def separate(value: Any) -> bool:
|
||||||
nonlocal meshes, others, count, MAX_DEPTH
|
nonlocal meshes, others, count, MAX_DEPTH
|
||||||
|
|
||||||
@@ -143,24 +173,27 @@ def _members_to_native(speckle_object: Base, name: str, scale: float, members: I
|
|||||||
others.append(value)
|
others.append(value)
|
||||||
elif isinstance(value, list):
|
elif isinstance(value, list):
|
||||||
count += 1
|
count += 1
|
||||||
if(count > MAX_DEPTH):
|
if count > MAX_DEPTH:
|
||||||
return True
|
return True
|
||||||
for x in value:
|
for x in value:
|
||||||
separate(x)
|
separate(x)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
did_halt = separate(display)
|
did_halt = separate(display)
|
||||||
|
|
||||||
if did_halt:
|
if did_halt:
|
||||||
_report(f"Traversal of {speckle_object.speckle_type} {speckle_object.id} halted after traversal depth exceeds MAX_DEPTH={MAX_DEPTH}. Are there circular references object structure?")
|
_report(
|
||||||
|
f"Traversal of {speckle_object.speckle_type} {speckle_object.id} halted after traversal depth exceeds MAX_DEPTH={MAX_DEPTH}. Are there circular references object structure?"
|
||||||
|
)
|
||||||
|
|
||||||
children: list[Object] = []
|
children: list[Object] = []
|
||||||
mesh = None
|
mesh = None
|
||||||
|
|
||||||
if meshes:
|
if meshes:
|
||||||
mesh = meshes_to_native(speckle_object, meshes, name, scale) #TODO: reconsider passing scale around...
|
mesh = meshes_to_native(
|
||||||
|
speckle_object, meshes, name, scale
|
||||||
|
) # TODO: reconsider passing scale around...
|
||||||
|
|
||||||
for item in others:
|
for item in others:
|
||||||
try:
|
try:
|
||||||
@@ -172,14 +205,13 @@ def _members_to_native(speckle_object: Base, name: str, scale: float, members: I
|
|||||||
return (mesh, children)
|
return (mesh, children)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def view_to_native(speckle_view, name: str, scale: float) -> bpy.types.Object:
|
def view_to_native(speckle_view, name: str, scale: float) -> bpy.types.Object:
|
||||||
native_cam: bpy.types.Camera
|
native_cam: bpy.types.Camera
|
||||||
if name in bpy.data.cameras.keys():
|
if name in bpy.data.cameras.keys():
|
||||||
native_cam = bpy.data.cameras[name]
|
native_cam = bpy.data.cameras[name]
|
||||||
else:
|
else:
|
||||||
native_cam = bpy.data.cameras.new(name=name)
|
native_cam = bpy.data.cameras.new(name=name)
|
||||||
native_cam.lens = 18 # 90° horizontal fov
|
native_cam.lens = 18 # 90° horizontal fov
|
||||||
|
|
||||||
if not hasattr(speckle_view, "origin"):
|
if not hasattr(speckle_view, "origin"):
|
||||||
raise ConversionSkippedException("2D views not supported")
|
raise ConversionSkippedException("2D views not supported")
|
||||||
@@ -187,28 +219,44 @@ def view_to_native(speckle_view, name: str, scale: float) -> bpy.types.Object:
|
|||||||
cam_obj = create_new_object(native_cam, name)
|
cam_obj = create_new_object(native_cam, name)
|
||||||
|
|
||||||
scale_factor = get_scale_factor(speckle_view, scale)
|
scale_factor = get_scale_factor(speckle_view, scale)
|
||||||
tx = (speckle_view.origin.x * scale_factor)
|
tx = speckle_view.origin.x * scale_factor
|
||||||
ty = (speckle_view.origin.y * scale_factor)
|
ty = speckle_view.origin.y * scale_factor
|
||||||
tz = (speckle_view.origin.z * scale_factor)
|
tz = speckle_view.origin.z * scale_factor
|
||||||
|
|
||||||
forward = MVector((speckle_view.forwardDirection.x, speckle_view.forwardDirection.y, speckle_view.forwardDirection.z))
|
forward = MVector(
|
||||||
up = MVector((speckle_view.upDirection.x, speckle_view.upDirection.y, speckle_view.upDirection.z))
|
(
|
||||||
|
speckle_view.forwardDirection.x,
|
||||||
|
speckle_view.forwardDirection.y,
|
||||||
|
speckle_view.forwardDirection.z,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
up = MVector(
|
||||||
|
(
|
||||||
|
speckle_view.upDirection.x,
|
||||||
|
speckle_view.upDirection.y,
|
||||||
|
speckle_view.upDirection.z,
|
||||||
|
)
|
||||||
|
)
|
||||||
right = forward.cross(up).normalized()
|
right = forward.cross(up).normalized()
|
||||||
|
|
||||||
cam_obj.matrix_world = MMatrix((
|
cam_obj.matrix_world = MMatrix(
|
||||||
(right.x, up.x, -forward.x, tx),
|
(
|
||||||
(right.y, up.y, -forward.y, ty),
|
(right.x, up.x, -forward.x, tx),
|
||||||
(right.z, up.z, -forward.z, tz),
|
(right.y, up.y, -forward.y, ty),
|
||||||
(0, 0, 0, 1 )
|
(right.z, up.z, -forward.z, tz),
|
||||||
))
|
(0, 0, 0, 1),
|
||||||
|
)
|
||||||
|
)
|
||||||
return cam_obj
|
return cam_obj
|
||||||
|
|
||||||
|
|
||||||
def mesh_to_native(speckle_mesh: Mesh, name: str, scale: float) -> bpy.types.Mesh:
|
def mesh_to_native(speckle_mesh: Mesh, name: str, scale: float) -> bpy.types.Mesh:
|
||||||
return meshes_to_native(speckle_mesh, [speckle_mesh], name, scale)
|
return meshes_to_native(speckle_mesh, [speckle_mesh], name, scale)
|
||||||
|
|
||||||
|
|
||||||
|
def meshes_to_native(
|
||||||
def meshes_to_native(element: Base, meshes: Collection[Mesh], name: str, scale: float) -> bpy.types.Mesh:
|
element: Base, meshes: Collection[Mesh], name: str, scale: float
|
||||||
|
) -> bpy.types.Mesh:
|
||||||
if name in bpy.data.meshes.keys():
|
if name in bpy.data.meshes.keys():
|
||||||
return bpy.data.meshes[name]
|
return bpy.data.meshes[name]
|
||||||
blender_mesh = bpy.data.meshes.new(name=name)
|
blender_mesh = bpy.data.meshes.new(name=name)
|
||||||
@@ -227,7 +275,8 @@ def meshes_to_native(element: Base, meshes: Collection[Mesh], name: str, scale:
|
|||||||
# Second pass, add face data
|
# Second pass, add face data
|
||||||
offset = 0
|
offset = 0
|
||||||
for i, mesh in enumerate(meshes):
|
for i, mesh in enumerate(meshes):
|
||||||
if not mesh.vertices: continue
|
if not mesh.vertices:
|
||||||
|
continue
|
||||||
|
|
||||||
add_faces(mesh, bm, offset, i)
|
add_faces(mesh, bm, offset, i)
|
||||||
|
|
||||||
@@ -253,14 +302,14 @@ def meshes_to_native(element: Base, meshes: Collection[Mesh], name: str, scale:
|
|||||||
add_colors(mesh, bm)
|
add_colors(mesh, bm)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
_report(f"Skipping converting vertex colors for {name}: {ex}")
|
_report(f"Skipping converting vertex colors for {name}: {ex}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
add_uv_coords(mesh, bm)
|
add_uv_coords(mesh, bm)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
_report(f"Skipping converting uv coordinates for {name}: {ex}")
|
_report(f"Skipping converting uv coordinates for {name}: {ex}")
|
||||||
|
|
||||||
bm.to_mesh(blender_mesh)
|
bm.to_mesh(blender_mesh)
|
||||||
bm.free()
|
bm.free()
|
||||||
|
|
||||||
return blender_mesh
|
return blender_mesh
|
||||||
|
|
||||||
@@ -269,8 +318,12 @@ def meshes_to_native(element: Base, meshes: Collection[Mesh], name: str, scale:
|
|||||||
Curves
|
Curves
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def line_to_native(speckle_curve: Line, blender_curve: bpy.types.Curve, scale: float) -> List[bpy.types.Spline]:
|
|
||||||
if not speckle_curve.end: return []
|
def line_to_native(
|
||||||
|
speckle_curve: Line, blender_curve: bpy.types.Curve, scale: float
|
||||||
|
) -> List[bpy.types.Spline]:
|
||||||
|
if not speckle_curve.end:
|
||||||
|
return []
|
||||||
|
|
||||||
line = blender_curve.splines.new("POLY")
|
line = blender_curve.splines.new("POLY")
|
||||||
line.points.add(1)
|
line.points.add(1)
|
||||||
@@ -292,8 +345,11 @@ def line_to_native(speckle_curve: Line, blender_curve: bpy.types.Curve, scale: f
|
|||||||
return [line]
|
return [line]
|
||||||
|
|
||||||
|
|
||||||
def polyline_to_native(scurve: Polyline, bcurve: bpy.types.Curve, scale: float) -> List[bpy.types.Spline]:
|
def polyline_to_native(
|
||||||
if not (value := scurve.value): return []
|
scurve: Polyline, bcurve: bpy.types.Curve, scale: float
|
||||||
|
) -> List[bpy.types.Spline]:
|
||||||
|
if not (value := scurve.value):
|
||||||
|
return []
|
||||||
N = len(value) // 3
|
N = len(value) // 3
|
||||||
|
|
||||||
polyline = bcurve.splines.new("POLY")
|
polyline = bcurve.splines.new("POLY")
|
||||||
@@ -311,22 +367,27 @@ def polyline_to_native(scurve: Polyline, bcurve: bpy.types.Curve, scale: float)
|
|||||||
)
|
)
|
||||||
|
|
||||||
return [polyline]
|
return [polyline]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def nurbs_to_native(scurve: Curve, bcurve: bpy.types.Curve, scale: float) -> List[bpy.types.Spline]:
|
def nurbs_to_native(
|
||||||
if not (points := scurve.points): return []
|
scurve: Curve, bcurve: bpy.types.Curve, scale: float
|
||||||
if not scurve.degree: raise Exception("curve is missing degree")
|
) -> List[bpy.types.Spline]:
|
||||||
if not scurve.weights: raise Exception("curve is missing weights")
|
if not (points := scurve.points):
|
||||||
|
return []
|
||||||
|
if not scurve.degree:
|
||||||
|
raise Exception("curve is missing degree")
|
||||||
|
if not scurve.weights:
|
||||||
|
raise Exception("curve is missing weights")
|
||||||
|
|
||||||
# Closed curves from rhino will have n + degree points. We ignore the extras
|
# Closed curves from rhino will have n + degree points. We ignore the extras
|
||||||
num_points = len(points) // 3 - scurve.degree if (scurve.closed) else (
|
num_points = (
|
||||||
len(points) // 3)
|
len(points) // 3 - scurve.degree if (scurve.closed) else (len(points) // 3)
|
||||||
|
)
|
||||||
|
|
||||||
nurbs = bcurve.splines.new("NURBS")
|
nurbs = bcurve.splines.new("NURBS")
|
||||||
nurbs.use_cyclic_u = scurve.closed or False
|
nurbs.use_cyclic_u = scurve.closed or False
|
||||||
nurbs.use_endpoint_u = not scurve.periodic
|
nurbs.use_endpoint_u = not scurve.periodic
|
||||||
|
|
||||||
nurbs.points.add(num_points - 1)
|
nurbs.points.add(num_points - 1)
|
||||||
use_weights = len(scurve.weights) >= num_points
|
use_weights = len(scurve.weights) >= num_points
|
||||||
for i in range(num_points):
|
for i in range(num_points):
|
||||||
@@ -336,7 +397,7 @@ def nurbs_to_native(scurve: Curve, bcurve: bpy.types.Curve, scale: float) -> Lis
|
|||||||
float(points[i * 3 + 2]) * scale,
|
float(points[i * 3 + 2]) * scale,
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
nurbs.points[i].weight = scurve.weights[i] if use_weights else 1
|
nurbs.points[i].weight = scurve.weights[i] if use_weights else 1
|
||||||
|
|
||||||
nurbs.order_u = scurve.degree + 1
|
nurbs.order_u = scurve.degree + 1
|
||||||
@@ -344,11 +405,16 @@ def nurbs_to_native(scurve: Curve, bcurve: bpy.types.Curve, scale: float) -> Lis
|
|||||||
return [nurbs]
|
return [nurbs]
|
||||||
|
|
||||||
|
|
||||||
def arc_to_native(rcurve: Arc, bcurve: bpy.types.Curve, scale: float) -> Optional[bpy.types.Spline]:
|
def arc_to_native(
|
||||||
|
rcurve: Arc, bcurve: bpy.types.Curve, scale: float
|
||||||
|
) -> Optional[bpy.types.Spline]:
|
||||||
# TODO: improve Blender representation of arc - check autocad test stream
|
# TODO: improve Blender representation of arc - check autocad test stream
|
||||||
if not rcurve.radius: raise Exception("curve is missing radius")
|
if not rcurve.radius:
|
||||||
if not rcurve.startAngle: raise Exception("curve is missing startAngle")
|
raise Exception("curve is missing radius")
|
||||||
if not rcurve.endAngle: raise Exception("curve is missing endAngle")
|
if not rcurve.startAngle:
|
||||||
|
raise Exception("curve is missing startAngle")
|
||||||
|
if not rcurve.endAngle:
|
||||||
|
raise Exception("curve is missing endAngle")
|
||||||
|
|
||||||
plane = rcurve.plane
|
plane = rcurve.plane
|
||||||
if not plane:
|
if not plane:
|
||||||
@@ -360,8 +426,8 @@ def arc_to_native(rcurve: Arc, bcurve: bpy.types.Curve, scale: float) -> Optiona
|
|||||||
startAngle = rcurve.startAngle
|
startAngle = rcurve.startAngle
|
||||||
endAngle = rcurve.endAngle
|
endAngle = rcurve.endAngle
|
||||||
|
|
||||||
startQuat = MQuaternion(normal, startAngle) # type: ignore
|
startQuat = MQuaternion(normal, startAngle) # type: ignore
|
||||||
endQuat = MQuaternion(normal, endAngle) # type: ignore
|
endQuat = MQuaternion(normal, endAngle) # type: ignore
|
||||||
|
|
||||||
# Get start and end vectors, centre point, angles, etc.
|
# Get start and end vectors, centre point, angles, etc.
|
||||||
r1 = MVector([plane.xdir.x, plane.xdir.y, plane.xdir.z])
|
r1 = MVector([plane.xdir.x, plane.xdir.y, plane.xdir.z])
|
||||||
@@ -386,7 +452,7 @@ def arc_to_native(rcurve: Arc, bcurve: bpy.types.Curve, scale: float) -> Optiona
|
|||||||
|
|
||||||
Ndiv = max(int(math.floor(angle / 0.3)), 2)
|
Ndiv = max(int(math.floor(angle / 0.3)), 2)
|
||||||
step = angle / float(Ndiv)
|
step = angle / float(Ndiv)
|
||||||
stepQuat = MQuaternion(normal, step) # type: ignore
|
stepQuat = MQuaternion(normal, step) # type: ignore
|
||||||
tan = math.tan(step / 2) * radius
|
tan = math.tan(step / 2) * radius
|
||||||
|
|
||||||
arc.points.add(Ndiv + 1)
|
arc.points.add(Ndiv + 1)
|
||||||
@@ -409,11 +475,14 @@ def arc_to_native(rcurve: Arc, bcurve: bpy.types.Curve, scale: float) -> Optiona
|
|||||||
return arc
|
return arc
|
||||||
|
|
||||||
|
|
||||||
def polycurve_to_native(scurve: Polycurve, bcurve: bpy.types.Curve, scale: float) -> list[bpy.types.Spline]:
|
def polycurve_to_native(
|
||||||
|
scurve: Polycurve, bcurve: bpy.types.Curve, scale: float
|
||||||
|
) -> list[bpy.types.Spline]:
|
||||||
"""
|
"""
|
||||||
Convert Polycurve object
|
Convert Polycurve object
|
||||||
"""
|
"""
|
||||||
if not scurve.segments: raise Exception("curve is missing segments")
|
if not scurve.segments:
|
||||||
|
raise Exception("curve is missing segments")
|
||||||
|
|
||||||
curves = []
|
curves = []
|
||||||
|
|
||||||
@@ -426,46 +495,52 @@ def polycurve_to_native(scurve: Polycurve, bcurve: bpy.types.Curve, scale: float
|
|||||||
_report(f"Unsupported curve type: {speckle_type}")
|
_report(f"Unsupported curve type: {speckle_type}")
|
||||||
|
|
||||||
return curves
|
return curves
|
||||||
|
|
||||||
def ellipse_to_native(ellipse: Union[Ellipse, Circle], bcurve: bpy.types.Curve, units_scale: float) -> List[bpy.types.Spline]:
|
|
||||||
if not ellipse.plane: raise Exception("curve is missing plane")
|
def ellipse_to_native(
|
||||||
|
ellipse: Union[Ellipse, Circle], bcurve: bpy.types.Curve, units_scale: float
|
||||||
|
) -> List[bpy.types.Spline]:
|
||||||
|
if not ellipse.plane:
|
||||||
|
raise Exception("curve is missing plane")
|
||||||
|
|
||||||
radX: float
|
radX: float
|
||||||
radY: float
|
radY: float
|
||||||
if isinstance(ellipse, Ellipse):
|
if isinstance(ellipse, Ellipse):
|
||||||
if not ellipse.firstRadius: raise Exception("curve is missing firstRadius")
|
if not ellipse.firstRadius:
|
||||||
if not ellipse.secondRadius: raise Exception("curve is missing secondRadius")
|
raise Exception("curve is missing firstRadius")
|
||||||
|
if not ellipse.secondRadius:
|
||||||
|
raise Exception("curve is missing secondRadius")
|
||||||
|
|
||||||
radX = ellipse.firstRadius * units_scale
|
radX = ellipse.firstRadius * units_scale
|
||||||
radY = ellipse.secondRadius * units_scale
|
radY = ellipse.secondRadius * units_scale
|
||||||
else:
|
else:
|
||||||
if not ellipse.radius: raise Exception("curve is missing radius")
|
if not ellipse.radius:
|
||||||
|
raise Exception("curve is missing radius")
|
||||||
|
|
||||||
radX = ellipse.radius * units_scale
|
radX = ellipse.radius * units_scale
|
||||||
radY = ellipse.radius * units_scale
|
radY = ellipse.radius * units_scale
|
||||||
|
|
||||||
|
D = 0.5522847498307936 # (4/3)*tan(pi/8)
|
||||||
D = 0.5522847498307936 # (4/3)*tan(pi/8)
|
|
||||||
|
|
||||||
right_handles = [
|
right_handles = [
|
||||||
(+radX, +radY * D, 0.0),
|
(+radX, +radY * D, 0.0),
|
||||||
(-radX * D, +radY, 0.0),
|
(-radX * D, +radY, 0.0),
|
||||||
(-radX, -radY * D, 0.0),
|
(-radX, -radY * D, 0.0),
|
||||||
(+radX * D, -radY, 0.0),
|
(+radX * D, -radY, 0.0),
|
||||||
]
|
]
|
||||||
|
|
||||||
left_handles = [
|
left_handles = [
|
||||||
(+radX, -radY * D, 0.0),
|
(+radX, -radY * D, 0.0),
|
||||||
(+radX * D, +radY, 0.0),
|
(+radX * D, +radY, 0.0),
|
||||||
(-radX, +radY * D, 0.0),
|
(-radX, +radY * D, 0.0),
|
||||||
(-radX * D, -radY, 0.0),
|
(-radX * D, -radY, 0.0),
|
||||||
]
|
]
|
||||||
|
|
||||||
points = [
|
points = [
|
||||||
(+radX, 0.0, 0.0),
|
(+radX, 0.0, 0.0),
|
||||||
(0.0, +radY, 0.0),
|
(0.0, +radY, 0.0),
|
||||||
(-radX, 0.0, 0.0),
|
(-radX, 0.0, 0.0),
|
||||||
(0.0, -radY, 0.0),
|
(0.0, -radY, 0.0),
|
||||||
]
|
]
|
||||||
transform = plane_to_native_transform(ellipse.plane, units_scale)
|
transform = plane_to_native_transform(ellipse.plane, units_scale)
|
||||||
|
|
||||||
@@ -473,17 +548,19 @@ def ellipse_to_native(ellipse: Union[Ellipse, Circle], bcurve: bpy.types.Curve,
|
|||||||
spline.bezier_points.add(len(points) - 1)
|
spline.bezier_points.add(len(points) - 1)
|
||||||
|
|
||||||
for i in range(len(points)):
|
for i in range(len(points)):
|
||||||
spline.bezier_points[i].co = transform @ MVector(points[i]) # type: ignore
|
spline.bezier_points[i].co = transform @ MVector(points[i]) # type: ignore
|
||||||
spline.bezier_points[i].handle_left = transform @ MVector(left_handles[i]) # type: ignore
|
spline.bezier_points[i].handle_left = transform @ MVector(left_handles[i]) # type: ignore
|
||||||
spline.bezier_points[i].handle_right = transform @ MVector(right_handles[i]) # type: ignore
|
spline.bezier_points[i].handle_right = transform @ MVector(right_handles[i]) # type: ignore
|
||||||
|
|
||||||
spline.use_cyclic_u = True
|
spline.use_cyclic_u = True
|
||||||
|
|
||||||
#TODO support trims?
|
# TODO support trims?
|
||||||
return [spline]
|
return [spline]
|
||||||
|
|
||||||
|
|
||||||
def icurve_to_native_spline(speckle_curve: Base, blender_curve: bpy.types.Curve, scale: float) -> List[bpy.types.Spline]:
|
def icurve_to_native_spline(
|
||||||
|
speckle_curve: Base, blender_curve: bpy.types.Curve, scale: float
|
||||||
|
) -> List[bpy.types.Spline]:
|
||||||
# polycurves
|
# polycurves
|
||||||
if isinstance(speckle_curve, Polycurve):
|
if isinstance(speckle_curve, Polycurve):
|
||||||
return polycurve_to_native(speckle_curve, blender_curve, scale)
|
return polycurve_to_native(speckle_curve, blender_curve, scale)
|
||||||
@@ -502,7 +579,9 @@ def icurve_to_native_spline(speckle_curve: Base, blender_curve: bpy.types.Curve,
|
|||||||
elif isinstance(speckle_curve, Ellipse) or isinstance(speckle_curve, Circle):
|
elif isinstance(speckle_curve, Ellipse) or isinstance(speckle_curve, Circle):
|
||||||
splines = ellipse_to_native(speckle_curve, blender_curve, scale)
|
splines = ellipse_to_native(speckle_curve, blender_curve, scale)
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"{speckle_curve} is not a supported curve type. Supported types: {SUPPORTED_CURVES}")
|
raise TypeError(
|
||||||
|
f"{speckle_curve} is not a supported curve type. Supported types: {SUPPORTED_CURVES}"
|
||||||
|
)
|
||||||
|
|
||||||
return splines
|
return splines
|
||||||
|
|
||||||
@@ -518,7 +597,9 @@ def icurve_to_native(speckle_curve: Base, name: str, scale: float) -> bpy.types.
|
|||||||
else bpy.data.curves.new(name, type="CURVE")
|
else bpy.data.curves.new(name, type="CURVE")
|
||||||
)
|
)
|
||||||
blender_curve.dimensions = "3D"
|
blender_curve.dimensions = "3D"
|
||||||
blender_curve.resolution_u = 12 #TODO: We could maybe decern the resolution from the polyline displayValue
|
blender_curve.resolution_u = (
|
||||||
|
12 # TODO: We could maybe decern the resolution from the polyline displayValue
|
||||||
|
)
|
||||||
|
|
||||||
icurve_to_native_spline(speckle_curve, blender_curve, scale)
|
icurve_to_native_spline(speckle_curve, blender_curve, scale)
|
||||||
|
|
||||||
@@ -529,6 +610,7 @@ def icurve_to_native(speckle_curve: Base, name: str, scale: float) -> bpy.types.
|
|||||||
Transforms and Instances
|
Transforms and Instances
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def transform_to_native(transform: Transform, scale: float) -> MMatrix:
|
def transform_to_native(transform: Transform, scale: float) -> MMatrix:
|
||||||
mat = MMatrix(
|
mat = MMatrix(
|
||||||
[
|
[
|
||||||
@@ -543,30 +625,34 @@ def transform_to_native(transform: Transform, scale: float) -> MMatrix:
|
|||||||
mat[i][3] *= scale
|
mat[i][3] *= scale
|
||||||
return mat
|
return mat
|
||||||
|
|
||||||
def plane_to_native_transform(plane: Plane, fallback_scale:float = 1) -> MMatrix:
|
|
||||||
|
def plane_to_native_transform(plane: Plane, fallback_scale: float = 1) -> MMatrix:
|
||||||
scale_factor = get_scale_factor(plane, fallback_scale)
|
scale_factor = get_scale_factor(plane, fallback_scale)
|
||||||
tx = (plane.origin.x * scale_factor)
|
tx = plane.origin.x * scale_factor
|
||||||
ty = (plane.origin.y * scale_factor)
|
ty = plane.origin.y * scale_factor
|
||||||
tz = (plane.origin.z * scale_factor)
|
tz = plane.origin.z * scale_factor
|
||||||
|
|
||||||
|
return MMatrix(
|
||||||
return MMatrix((
|
(
|
||||||
(plane.xdir.x, plane.ydir.x, plane.normal.x, tx),
|
(plane.xdir.x, plane.ydir.x, plane.normal.x, tx),
|
||||||
(plane.xdir.y, plane.ydir.y, plane.normal.y, ty),
|
(plane.xdir.y, plane.ydir.y, plane.normal.y, ty),
|
||||||
(plane.xdir.z, plane.ydir.z, plane.normal.z, tz),
|
(plane.xdir.z, plane.ydir.z, plane.normal.z, tz),
|
||||||
(0, 0, 0, 1 )
|
(0, 0, 0, 1),
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Instances / Blocks
|
Instances / Blocks
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _get_instance_name(instance: Instance) -> str:
|
def _get_instance_name(instance: Instance) -> str:
|
||||||
if not instance.definition: raise Exception("Instance is missing a definition")
|
if not instance.definition:
|
||||||
|
raise Exception("Instance is missing a definition")
|
||||||
name_prefix = (
|
name_prefix = (
|
||||||
_get_friendly_object_name(instance)
|
_get_friendly_object_name(instance)
|
||||||
or _get_friendly_object_name(instance.definition)
|
or _get_friendly_object_name(instance.definition)
|
||||||
or _simplified_speckle_type(instance.speckle_type)
|
or _simplified_speckle_type(instance.speckle_type)
|
||||||
)
|
)
|
||||||
return f"{name_prefix}{OBJECT_NAME_SPECKLE_SEPARATOR}{instance.id}"
|
return f"{name_prefix}{OBJECT_NAME_SPECKLE_SEPARATOR}{instance.id}"
|
||||||
@@ -576,40 +662,45 @@ def instance_to_native_object(instance: Instance, scale: float) -> Object:
|
|||||||
"""
|
"""
|
||||||
Converts Instance to a unique object with (potentially) shared data (linked duplicate)
|
Converts Instance to a unique object with (potentially) shared data (linked duplicate)
|
||||||
"""
|
"""
|
||||||
if not instance.definition: raise Exception("Instance is missing a definition")
|
if not instance.definition:
|
||||||
if not instance.transform: raise Exception("Instance is missing a transform")
|
raise Exception("Instance is missing a definition")
|
||||||
|
if not instance.transform:
|
||||||
|
raise Exception("Instance is missing a transform")
|
||||||
definition = instance.definition
|
definition = instance.definition
|
||||||
if not definition.id: raise Exception("Instance is missing a valid definition")
|
if not definition.id:
|
||||||
|
raise Exception("Instance is missing a valid definition")
|
||||||
|
|
||||||
name = _get_instance_name(instance)
|
name = _get_instance_name(instance)
|
||||||
|
|
||||||
native_instance: Optional[Object] = None
|
native_instance: Optional[Object] = None
|
||||||
converted_objects: Dict[str, Union[Object, BCollection]] = {}
|
converted_objects: Dict[str, Union[Object, BCollection]] = {}
|
||||||
traversal_root: Base = definition
|
traversal_root: Base = definition
|
||||||
|
|
||||||
if not can_convert_to_native(definition):
|
if not can_convert_to_native(definition):
|
||||||
# Non-convertible (like all blocks, and some revit instances) will not be converted as part of the deep_traversal.
|
# Non-convertible (like all blocks, and some revit instances) will not be converted as part of the deep_traversal.
|
||||||
# so we explicitly convert them as empties.
|
# so we explicitly convert them as empties.
|
||||||
native_instance = create_new_object(None, name)
|
native_instance = create_new_object(None, name)
|
||||||
native_instance.empty_display_size = 0
|
native_instance.empty_display_size = 0
|
||||||
|
|
||||||
converted_objects["__ROOT"] = native_instance # we create a dummy root to avoid id conflicts, since revit definitions have displayValues, they are convertible
|
converted_objects[
|
||||||
|
"__ROOT"
|
||||||
|
] = native_instance # we create a dummy root to avoid id conflicts, since revit definitions have displayValues, they are convertible
|
||||||
traversal_root = Base(elements=definition, id="__ROOT")
|
traversal_root = Base(elements=definition, id="__ROOT")
|
||||||
|
|
||||||
#Convert definition + "elements" on definition
|
# Convert definition + "elements" on definition
|
||||||
_deep_conversion(traversal_root, converted_objects, False)
|
_deep_conversion(traversal_root, converted_objects, False)
|
||||||
|
|
||||||
if not native_instance:
|
if not native_instance:
|
||||||
assert(can_convert_to_native(definition))
|
assert can_convert_to_native(definition)
|
||||||
|
|
||||||
if not definition.id in converted_objects:
|
if definition.id not in converted_objects:
|
||||||
raise Exception("Definition was not converted")
|
raise Exception("Definition was not converted")
|
||||||
|
|
||||||
converted = converted_objects[definition.id]
|
converted = converted_objects[definition.id]
|
||||||
|
|
||||||
if not isinstance(converted, Object):
|
if not isinstance(converted, Object):
|
||||||
raise Exception("Definition was not converted to an Object")
|
raise Exception("Definition was not converted to an Object")
|
||||||
|
|
||||||
native_instance = converted
|
native_instance = converted
|
||||||
|
|
||||||
instance_transform = transform_to_native(instance.transform, scale)
|
instance_transform = transform_to_native(instance.transform, scale)
|
||||||
@@ -617,7 +708,10 @@ def instance_to_native_object(instance: Instance, scale: float) -> Object:
|
|||||||
|
|
||||||
return native_instance
|
return native_instance
|
||||||
|
|
||||||
def instance_to_native_collection_instance(instance: Instance, scale: float) -> bpy.types.Object:
|
|
||||||
|
def instance_to_native_collection_instance(
|
||||||
|
instance: Instance, scale: float
|
||||||
|
) -> bpy.types.Object:
|
||||||
"""
|
"""
|
||||||
Convert an Instance as a transformed Object with the `instance_collection` property
|
Convert an Instance as a transformed Object with the `instance_collection` property
|
||||||
set to be the `instance.Definition` converted as a collection
|
set to be the `instance.Definition` converted as a collection
|
||||||
@@ -625,8 +719,10 @@ def instance_to_native_collection_instance(instance: Instance, scale: float) ->
|
|||||||
The definition collection won't be linked to the current scene
|
The definition collection won't be linked to the current scene
|
||||||
Any Elements on the instance object will also be converted (and spacially transformed)
|
Any Elements on the instance object will also be converted (and spacially transformed)
|
||||||
"""
|
"""
|
||||||
if not instance.definition: raise Exception("Instance is missing a definition")
|
if not instance.definition:
|
||||||
if not instance.transform: raise Exception("Instance is missing a transform")
|
raise Exception("Instance is missing a definition")
|
||||||
|
if not instance.transform:
|
||||||
|
raise Exception("Instance is missing a transform")
|
||||||
|
|
||||||
name = _get_instance_name(instance)
|
name = _get_instance_name(instance)
|
||||||
|
|
||||||
@@ -637,16 +733,19 @@ def instance_to_native_collection_instance(instance: Instance, scale: float) ->
|
|||||||
|
|
||||||
native_instance = create_new_object(None, name)
|
native_instance = create_new_object(None, name)
|
||||||
|
|
||||||
#add_custom_properties(instance, native_instance)
|
# add_custom_properties(instance, native_instance)
|
||||||
# hide the instance axes so they don't clutter the viewport
|
# hide the instance axes so they don't clutter the viewport
|
||||||
native_instance.empty_display_size = 0
|
native_instance.empty_display_size = 0
|
||||||
native_instance.instance_collection = collection_def
|
native_instance.instance_collection = collection_def
|
||||||
native_instance.instance_type = "COLLECTION"
|
native_instance.instance_type = "COLLECTION"
|
||||||
native_instance.matrix_world = instance_transform
|
native_instance.matrix_world = instance_transform
|
||||||
|
|
||||||
return native_instance
|
|
||||||
|
|
||||||
def _instance_definition_to_native(definition: Union[Base, BlockDefinition]) -> bpy.types.Collection:
|
return native_instance
|
||||||
|
|
||||||
|
|
||||||
|
def _instance_definition_to_native(
|
||||||
|
definition: Union[Base, BlockDefinition]
|
||||||
|
) -> bpy.types.Collection:
|
||||||
"""
|
"""
|
||||||
Converts a geometry carrying Base as a collection (does not link it to the scene)
|
Converts a geometry carrying Base as a collection (does not link it to the scene)
|
||||||
"""
|
"""
|
||||||
@@ -659,50 +758,72 @@ def _instance_definition_to_native(definition: Union[Base, BlockDefinition]) ->
|
|||||||
native_def["applicationId"] = definition.applicationId
|
native_def["applicationId"] = definition.applicationId
|
||||||
|
|
||||||
converted_objects = {}
|
converted_objects = {}
|
||||||
converted_objects["__ROOT"] = native_def # we create a dummy root to avoid id conflicts, since revit definitions have displayValues, they are convertible
|
converted_objects[
|
||||||
|
"__ROOT"
|
||||||
|
] = native_def # we create a dummy root to avoid id conflicts, since revit definitions have displayValues, they are convertible
|
||||||
dummyRoot = Base(elements=definition, id="__ROOT")
|
dummyRoot = Base(elements=definition, id="__ROOT")
|
||||||
|
|
||||||
_deep_conversion(dummyRoot, converted_objects, True)
|
_deep_conversion(dummyRoot, converted_objects, True)
|
||||||
|
|
||||||
return native_def
|
return native_def
|
||||||
|
|
||||||
def _deep_conversion(root: Base, converted_objects: Dict[str, Union[Object, BCollection]], preserve_transform: bool):
|
|
||||||
|
def _deep_conversion(
|
||||||
|
root: Base,
|
||||||
|
converted_objects: Dict[str, Union[Object, BCollection]],
|
||||||
|
preserve_transform: bool,
|
||||||
|
):
|
||||||
traversal_func = get_default_traversal_func(can_convert_to_native)
|
traversal_func = get_default_traversal_func(can_convert_to_native)
|
||||||
|
|
||||||
for item in traversal_func.traverse(root):
|
for item in traversal_func.traverse(root):
|
||||||
|
|
||||||
current: Base = item.current
|
current: Base = item.current
|
||||||
if can_convert_to_native(current) or isinstance(current, SCollection):
|
if can_convert_to_native(current) or isinstance(current, SCollection):
|
||||||
try:
|
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!
|
# Convert the object!
|
||||||
converted_data_type: str
|
converted_data_type: str
|
||||||
converted: Union[Object, BCollection, None]
|
converted: Union[Object, BCollection, None]
|
||||||
if isinstance(current, SCollection):
|
if isinstance(current, SCollection):
|
||||||
if(current.collectionType == "Scene Collection"): raise ConversionSkippedException()
|
if current.collectionType == "Scene Collection":
|
||||||
|
raise ConversionSkippedException()
|
||||||
converted = collection_to_native(current)
|
converted = collection_to_native(current)
|
||||||
converted_data_type = "COLLECTION"
|
converted_data_type = "COLLECTION"
|
||||||
else:
|
else:
|
||||||
converted = convert_to_native(current)
|
converted = convert_to_native(current)
|
||||||
converted_data_type = "COLLECTION_INSTANCE" if converted.instance_collection else str(converted.type)
|
converted_data_type = (
|
||||||
|
"COLLECTION_INSTANCE"
|
||||||
|
if converted.instance_collection
|
||||||
|
else str(converted.type)
|
||||||
|
)
|
||||||
|
|
||||||
if converted is None:
|
if converted is None:
|
||||||
raise Exception("Conversion returned None")
|
raise Exception("Conversion returned None")
|
||||||
|
|
||||||
converted_objects[current.id] = converted
|
converted_objects[current.id] = converted
|
||||||
|
|
||||||
add_to_hierarchy(converted, item, converted_objects, preserve_transform)
|
add_to_hierarchy(converted, item, converted_objects, preserve_transform)
|
||||||
|
|
||||||
_report(f"Successfully converted {type(current).__name__} {current.id} as '{converted_data_type}'")
|
_report(
|
||||||
|
f"Successfully converted {type(current).__name__} {current.id} as '{converted_data_type}'"
|
||||||
|
)
|
||||||
except ConversionSkippedException as ex:
|
except ConversionSkippedException as ex:
|
||||||
_report(f"Skipped converting {type(current).__name__} {current.id}: {ex}")
|
_report(
|
||||||
|
f"Skipped converting {type(current).__name__} {current.id}: {ex}"
|
||||||
|
)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
_report(f"Failed to converted {type(current).__name__} {current.id}: {ex}")
|
_report(
|
||||||
|
f"Failed to converted {type(current).__name__} {current.id}: {ex}"
|
||||||
|
)
|
||||||
|
|
||||||
def collection_to_native(collection: SCollection) -> BCollection:
|
|
||||||
name = collection.name or f"{collection.collectionType} -- {collection.applicationId or collection.id}" #TODO: consider consolidating name formatting with Rhino
|
def collection_to_native(collection: SCollection) -> BCollection:
|
||||||
ret = get_or_create_collection(name)
|
name = (
|
||||||
|
collection.name
|
||||||
|
or f"{collection.collectionType} -- {collection.applicationId or collection.id}"
|
||||||
|
) # TODO: consider consolidating name formatting with Rhino
|
||||||
|
ret = get_or_create_collection(name)
|
||||||
|
|
||||||
color = getattr(collection, "colorTag", None)
|
color = getattr(collection, "colorTag", None)
|
||||||
if color:
|
if color:
|
||||||
@@ -710,8 +831,9 @@ def collection_to_native(collection: SCollection) -> BCollection:
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_collection(name: str, clear_collection: bool = True) -> BCollection:
|
def get_or_create_collection(name: str, clear_collection: bool = True) -> BCollection:
|
||||||
#Disabled for now, since update mode needs rescoping.
|
# Disabled for now, since update mode needs rescoping.
|
||||||
# existing = cast(Optional[BCollection], bpy.data.collections.get(name))
|
# existing = cast(Optional[BCollection], bpy.data.collections.get(name))
|
||||||
# if existing:
|
# if existing:
|
||||||
# if clear_collection:
|
# if clear_collection:
|
||||||
@@ -721,20 +843,20 @@ def get_or_create_collection(name: str, clear_collection: bool = True) -> BColle
|
|||||||
# else:
|
# else:
|
||||||
new_collection = create_new_collection(name)
|
new_collection = create_new_collection(name)
|
||||||
|
|
||||||
#NOTE: We want to not render revit "Rooms" collections by default.
|
# NOTE: We want to not render revit "Rooms" collections by default.
|
||||||
if name == "Rooms":
|
if name == "Rooms":
|
||||||
new_collection.hide_viewport = True
|
new_collection.hide_viewport = True
|
||||||
new_collection.hide_render = True
|
new_collection.hide_render = True
|
||||||
|
|
||||||
return new_collection
|
return new_collection
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Object Naming and Creation
|
Object Naming and Creation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def create_new_collection( desired_name: str) -> bpy.types.Collection:
|
|
||||||
|
def create_new_collection(desired_name: str) -> bpy.types.Collection:
|
||||||
"""
|
"""
|
||||||
Creates a new blender collection with a unique name
|
Creates a new blender collection with a unique name
|
||||||
If the desired_name is already taken
|
If the desired_name is already taken
|
||||||
@@ -745,7 +867,10 @@ def create_new_collection( desired_name: str) -> bpy.types.Collection:
|
|||||||
blender_collection = bpy.data.collections.new(name)
|
blender_collection = bpy.data.collections.new(name)
|
||||||
return blender_collection
|
return blender_collection
|
||||||
|
|
||||||
def create_new_object(obj_data: Optional[bpy.types.ID], desired_name: str) -> bpy.types.Object:
|
|
||||||
|
def create_new_object(
|
||||||
|
obj_data: Optional[bpy.types.ID], desired_name: str
|
||||||
|
) -> bpy.types.Object:
|
||||||
"""
|
"""
|
||||||
Creates a new blender object with a unique name,
|
Creates a new blender object with a unique name,
|
||||||
If the desired_name is already taken
|
If the desired_name is already taken
|
||||||
@@ -756,26 +881,35 @@ def create_new_object(obj_data: Optional[bpy.types.ID], desired_name: str) -> bp
|
|||||||
blender_object = bpy.data.objects.new(name, obj_data)
|
blender_object = bpy.data.objects.new(name, obj_data)
|
||||||
return blender_object
|
return blender_object
|
||||||
|
|
||||||
def _make_unique_name( desired_name: str, taken_names: Collection[str], counter: int = 0) -> str:
|
|
||||||
|
def _make_unique_name(
|
||||||
|
desired_name: str, taken_names: Collection[str], counter: int = 0
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Using Blenders default naming (append numeral in .xxx format) to avoid name conflicts with taken names
|
Using Blenders default naming (append numeral in .xxx format) to avoid name conflicts with taken names
|
||||||
"""
|
"""
|
||||||
name = desired_name if counter == 0 else f"{desired_name[:OBJECT_NAME_MAX_LENGTH - 4]}{OBJECT_NAME_NUMERAL_SEPARATOR}{counter:03d}" # format counter as name.xxx, truncate to ensure we don't exceed the object name max length
|
name = (
|
||||||
|
desired_name
|
||||||
|
if counter == 0
|
||||||
|
else f"{desired_name[:OBJECT_NAME_MAX_LENGTH - 4]}{OBJECT_NAME_NUMERAL_SEPARATOR}{counter:03d}"
|
||||||
|
) # format counter as name.xxx, truncate to ensure we don't exceed the object name max length
|
||||||
|
|
||||||
#TODO: This is very slow, and gets slower the more objects you receive with the same name...
|
# TODO: This is very slow, and gets slower the more objects you receive with the same name...
|
||||||
# We could use a binary/galloping search, and/or cache the name -> index within a receive.
|
# We could use a binary/galloping search, and/or cache the name -> index within a receive.
|
||||||
if name in taken_names:
|
if name in taken_names:
|
||||||
#Name already taken, increment counter and try again!
|
# Name already taken, increment counter and try again!
|
||||||
return _make_unique_name(desired_name, taken_names, counter + 1)
|
return _make_unique_name(desired_name, taken_names, counter + 1)
|
||||||
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
def _get_friendly_object_name(speckle_object: Base) -> Optional[str]:
|
def _get_friendly_object_name(speckle_object: Base) -> Optional[str]:
|
||||||
return (getattr(speckle_object, "name", None)
|
return (
|
||||||
|
getattr(speckle_object, "name", None)
|
||||||
or getattr(speckle_object, "Name", None)
|
or getattr(speckle_object, "Name", None)
|
||||||
or _get_revit_family_name(speckle_object)
|
or _get_revit_family_name(speckle_object)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_revit_family_name(speckle_object: Base) -> Optional[str]:
|
def _get_revit_family_name(speckle_object: Base) -> Optional[str]:
|
||||||
family = getattr(speckle_object, "family", None)
|
family = getattr(speckle_object, "family", None)
|
||||||
@@ -786,18 +920,23 @@ def _get_revit_family_name(speckle_object: Base) -> Optional[str]:
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Blender object names must not exceed 62 characters
|
# Blender object names must not exceed 62 characters
|
||||||
# We need to ensure the complete ID is included in the name (to prevent identity collisions)
|
# 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
|
# So we if the name is too long, we need to truncate
|
||||||
def _truncate_object_name(name: str) -> str:
|
def _truncate_object_name(name: str) -> str:
|
||||||
|
MAX_NAME_LENGTH = (
|
||||||
MAX_NAME_LENGTH = OBJECT_NAME_MAX_LENGTH - SPECKLE_ID_LENGTH - len(OBJECT_NAME_SPECKLE_SEPARATOR)
|
OBJECT_NAME_MAX_LENGTH - SPECKLE_ID_LENGTH - len(OBJECT_NAME_SPECKLE_SEPARATOR)
|
||||||
|
)
|
||||||
|
|
||||||
return name[:MAX_NAME_LENGTH]
|
return name[:MAX_NAME_LENGTH]
|
||||||
|
|
||||||
|
|
||||||
def _simplified_speckle_type(speckle_type: str) -> str:
|
def _simplified_speckle_type(speckle_type: str) -> str:
|
||||||
return(speckle_type.rsplit('.')[-1]) #Take only the most specific object type name (without namespace)
|
return speckle_type.rsplit(".")[
|
||||||
|
-1
|
||||||
|
] # Take only the most specific object type name (without namespace)
|
||||||
|
|
||||||
|
|
||||||
def _generate_object_name(speckle_object: Base) -> str:
|
def _generate_object_name(speckle_object: Base) -> str:
|
||||||
prefix: str
|
prefix: str
|
||||||
@@ -814,4 +953,4 @@ def get_scale_factor(speckle_object: Base, fallback: float = 1.0) -> float:
|
|||||||
scale = fallback
|
scale = fallback
|
||||||
if units := getattr(speckle_object, "units", None):
|
if units := getattr(speckle_object, "units", None):
|
||||||
scale = get_scale_length(units) / bpy.context.scene.unit_settings.scale_length
|
scale = get_scale_length(units) / bpy.context.scene.unit_settings.scale_length
|
||||||
return scale
|
return scale
|
||||||
|
|||||||
+196
-130
@@ -1,43 +1,42 @@
|
|||||||
from typing import Dict, Iterable, List, Optional, Tuple, Union, cast
|
from typing import Dict, Iterable, List, Optional, Tuple, Union, cast
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.types import (
|
from bpy.types import Camera as NCamera
|
||||||
Depsgraph,
|
from bpy.types import Curve as NCurve
|
||||||
MeshPolygon,
|
from bpy.types import Depsgraph
|
||||||
Object,
|
from bpy.types import Mesh as NMesh
|
||||||
Curve as NCurve,
|
from bpy.types import MeshPolygon, Object
|
||||||
Mesh as NMesh,
|
|
||||||
Camera as NCamera,
|
|
||||||
)
|
|
||||||
from deprecated import deprecated
|
from deprecated import deprecated
|
||||||
|
from mathutils import Matrix as MMatrix
|
||||||
|
from mathutils import Vector as MVector
|
||||||
from mathutils.geometry import interpolate_bezier
|
from mathutils.geometry import interpolate_bezier
|
||||||
from mathutils import (
|
|
||||||
Matrix as MMatrix,
|
|
||||||
Vector as MVector,
|
|
||||||
)
|
|
||||||
from specklepy.objects import Base
|
from specklepy.objects import Base
|
||||||
from specklepy.objects.other import BlockInstance, BlockDefinition, RenderMaterial, Transform
|
from specklepy.objects.geometry import (Box, Curve, Interval, Mesh, Point,
|
||||||
from specklepy.objects.geometry import (
|
Polyline, Vector)
|
||||||
Mesh, Curve, Interval, Box, Point, Vector, Polyline,
|
from specklepy.objects.other import (BlockDefinition, BlockInstance,
|
||||||
)
|
RenderMaterial, Transform)
|
||||||
from bpy_speckle.blender_commit_object_builder import BlenderCommitObjectBuilder
|
|
||||||
from bpy_speckle.convert.constants import OBJECT_NAME_SPECKLE_SEPARATOR, SPECKLE_ID_LENGTH
|
from bpy_speckle.blender_commit_object_builder import \
|
||||||
from bpy_speckle.convert.util import (
|
BlenderCommitObjectBuilder
|
||||||
ConversionSkippedException,
|
from bpy_speckle.convert.constants import (OBJECT_NAME_SPECKLE_SEPARATOR,
|
||||||
get_blender_custom_properties,
|
SPECKLE_ID_LENGTH)
|
||||||
make_knots,
|
from bpy_speckle.convert.util import (ConversionSkippedException,
|
||||||
nurb_make_curve,
|
get_blender_custom_properties,
|
||||||
to_argb_int,
|
make_knots, nurb_make_curve, to_argb_int)
|
||||||
)
|
|
||||||
from bpy_speckle.functions import _report
|
from bpy_speckle.functions import _report
|
||||||
|
|
||||||
|
Units: str = "m" # The desired final units to send
|
||||||
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
|
||||||
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", "FONT", "SURFACE", "META")
|
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:
|
def convert_to_speckle(
|
||||||
|
raw_blender_object: Object,
|
||||||
|
units_scale: float,
|
||||||
|
units: str,
|
||||||
|
depsgraph: Optional[Depsgraph],
|
||||||
|
) -> Base:
|
||||||
"""
|
"""
|
||||||
Converts supported 1 blender objects to 1 speckle object (potentially with children)
|
Converts supported 1 blender objects to 1 speckle object (potentially with children)
|
||||||
:param raw_blender_object: the blender object (unevaluated by a Depsgraph) to convert
|
:param raw_blender_object: the blender object (unevaluated by a Depsgraph) to convert
|
||||||
@@ -49,16 +48,21 @@ def convert_to_speckle(raw_blender_object: Object, units_scale: float, units: st
|
|||||||
global Units, UnitsScale
|
global Units, UnitsScale
|
||||||
Units = units
|
Units = units
|
||||||
UnitsScale = units_scale
|
UnitsScale = units_scale
|
||||||
|
|
||||||
blender_type = raw_blender_object.type
|
blender_type = raw_blender_object.type
|
||||||
if blender_type not in CAN_CONVERT_TO_SPECKLE:
|
if blender_type not in CAN_CONVERT_TO_SPECKLE:
|
||||||
raise ConversionSkippedException(f"Objects of type {blender_type} are not supported")
|
raise ConversionSkippedException(
|
||||||
|
f"Objects of type {blender_type} are not supported"
|
||||||
|
)
|
||||||
|
|
||||||
blender_object = cast(Object, (
|
blender_object = cast(
|
||||||
raw_blender_object.evaluated_get(depsgraph)
|
Object,
|
||||||
if depsgraph
|
(
|
||||||
else raw_blender_object
|
raw_blender_object.evaluated_get(depsgraph)
|
||||||
))
|
if depsgraph
|
||||||
|
else raw_blender_object
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
converted: Optional[Base] = None
|
converted: Optional[Base] = None
|
||||||
if blender_type == "MESH":
|
if blender_type == "MESH":
|
||||||
@@ -68,30 +72,35 @@ def convert_to_speckle(raw_blender_object: Object, units_scale: float, units: st
|
|||||||
elif blender_type == "EMPTY":
|
elif blender_type == "EMPTY":
|
||||||
converted = empty_to_speckle(blender_object)
|
converted = empty_to_speckle(blender_object)
|
||||||
elif blender_type == "CAMERA":
|
elif blender_type == "CAMERA":
|
||||||
converted = camera_to_speckle_view(blender_object, cast(NCamera, blender_object.data))
|
converted = camera_to_speckle_view(
|
||||||
|
blender_object, cast(NCamera, blender_object.data)
|
||||||
|
)
|
||||||
elif blender_type == "FONT" or "SURFACE" or "META":
|
elif blender_type == "FONT" or "SURFACE" or "META":
|
||||||
converted = anything_to_speckle_mesh(blender_object)
|
converted = anything_to_speckle_mesh(blender_object)
|
||||||
if not converted:
|
if not converted:
|
||||||
raise Exception("Conversion returned None")
|
raise Exception("Conversion returned None")
|
||||||
|
|
||||||
converted["properties"] = get_blender_custom_properties(raw_blender_object) #NOTE: Depsgraph copies don't have custom properties so we use the raw version
|
converted["properties"] = get_blender_custom_properties(
|
||||||
|
raw_blender_object
|
||||||
|
) # NOTE: Depsgraph copies don't have custom properties so we use the raw version
|
||||||
|
|
||||||
# Set object transform #TODO: this could be deprecated once we add proper geometry instancing support
|
# Set object transform #TODO: this could be deprecated once we add proper geometry instancing support
|
||||||
if blender_type != "EMPTY":
|
if blender_type != "EMPTY":
|
||||||
converted["properties"]["transform"] = transform_to_speckle(
|
converted["properties"]["transform"] = transform_to_speckle(
|
||||||
blender_object.matrix_world
|
blender_object.matrix_world
|
||||||
)
|
)
|
||||||
|
|
||||||
return converted
|
return converted
|
||||||
|
|
||||||
|
|
||||||
def mesh_to_speckle(blender_object: Object, data: bpy.types.Mesh) -> Base:
|
def mesh_to_speckle(blender_object: Object, data: bpy.types.Mesh) -> Base:
|
||||||
b = Base()
|
b = Base()
|
||||||
b["name"] = to_speckle_name(blender_object)
|
b["name"] = to_speckle_name(blender_object)
|
||||||
b["@displayValue"] = mesh_to_speckle_meshes(blender_object, data)
|
b["@displayValue"] = mesh_to_speckle_meshes(blender_object, data)
|
||||||
return b
|
return b
|
||||||
|
|
||||||
def mesh_to_speckle_meshes(blender_object: Object, data: bpy.types.Mesh) -> List[Mesh]:
|
|
||||||
|
|
||||||
|
def mesh_to_speckle_meshes(blender_object: Object, data: bpy.types.Mesh) -> List[Mesh]:
|
||||||
# Categorise polygons by material index
|
# Categorise polygons by material index
|
||||||
submesh_data: Dict[int, List[MeshPolygon]] = {}
|
submesh_data: Dict[int, List[MeshPolygon]] = {}
|
||||||
|
|
||||||
@@ -101,7 +110,7 @@ def mesh_to_speckle_meshes(blender_object: Object, data: bpy.types.Mesh) -> List
|
|||||||
submesh_data[p.material_index].append(p)
|
submesh_data[p.material_index].append(p)
|
||||||
|
|
||||||
transform = cast(MMatrix, blender_object.matrix_world)
|
transform = cast(MMatrix, blender_object.matrix_world)
|
||||||
scaled_vertices = [tuple(transform @ x.co * UnitsScale) for x in data.vertices]
|
scaled_vertices = [tuple(transform @ x.co * UnitsScale) for x in data.vertices]
|
||||||
|
|
||||||
# Create Speckle meshes for each material
|
# Create Speckle meshes for each material
|
||||||
submeshes = []
|
submeshes = []
|
||||||
@@ -109,8 +118,8 @@ def mesh_to_speckle_meshes(blender_object: Object, data: bpy.types.Mesh) -> List
|
|||||||
for i in submesh_data:
|
for i in submesh_data:
|
||||||
index_mapping: Dict[int, int] = {}
|
index_mapping: Dict[int, int] = {}
|
||||||
|
|
||||||
#Loop through each polygon, and map indices to their new index in m_verts
|
# Loop through each polygon, and map indices to their new index in m_verts
|
||||||
|
|
||||||
mesh_area = 0
|
mesh_area = 0
|
||||||
m_verts: List[float] = []
|
m_verts: List[float] = []
|
||||||
m_faces: List[int] = []
|
m_faces: List[int] = []
|
||||||
@@ -128,7 +137,7 @@ def mesh_to_speckle_meshes(blender_object: Object, data: bpy.types.Mesh) -> List
|
|||||||
m_verts.append(vert[0])
|
m_verts.append(vert[0])
|
||||||
m_verts.append(vert[1])
|
m_verts.append(vert[1])
|
||||||
m_verts.append(vert[2])
|
m_verts.append(vert[2])
|
||||||
|
|
||||||
if data.uv_layers.active:
|
if data.uv_layers.active:
|
||||||
vt = data.uv_layers.active.data[index_counter]
|
vt = data.uv_layers.active.data[index_counter]
|
||||||
uv = cast(MVector, vt.uv)
|
uv = cast(MVector, vt.uv)
|
||||||
@@ -143,43 +152,46 @@ def mesh_to_speckle_meshes(blender_object: Object, data: bpy.types.Mesh) -> List
|
|||||||
colors=[],
|
colors=[],
|
||||||
textureCoordinates=m_texcoords,
|
textureCoordinates=m_texcoords,
|
||||||
units=Units,
|
units=Units,
|
||||||
area = mesh_area,
|
area=mesh_area,
|
||||||
bbox=Box(area=0.0, volume=0.0),
|
bbox=Box(area=0.0, volume=0.0),
|
||||||
)
|
)
|
||||||
|
|
||||||
if i < len(data.materials):
|
if i < len(data.materials):
|
||||||
material = data.materials[i]
|
material = data.materials[i]
|
||||||
if material is not None:
|
if material is not None:
|
||||||
speckle_mesh["renderMaterial"] = material_to_speckle(material)
|
speckle_mesh["renderMaterial"] = material_to_speckle(material)
|
||||||
submeshes.append(speckle_mesh)
|
submeshes.append(speckle_mesh)
|
||||||
|
|
||||||
return submeshes
|
return submeshes
|
||||||
|
|
||||||
|
|
||||||
def bezier_to_speckle(matrix: MMatrix, spline: bpy.types.Spline, name: Optional[str] = None) -> Curve:
|
def bezier_to_speckle(
|
||||||
|
matrix: MMatrix, spline: bpy.types.Spline, name: Optional[str] = None
|
||||||
|
) -> Curve:
|
||||||
degree = 3
|
degree = 3
|
||||||
closed = spline.use_cyclic_u
|
closed = spline.use_cyclic_u
|
||||||
points: List[Tuple[MVector]] = []
|
points: List[Tuple[MVector]] = []
|
||||||
for i, bp in enumerate(spline.bezier_points):
|
for i, bp in enumerate(spline.bezier_points):
|
||||||
if i > 0:
|
if i > 0:
|
||||||
points.append(tuple(matrix @ bp.handle_left * UnitsScale)) # type: ignore
|
points.append(tuple(matrix @ bp.handle_left * UnitsScale)) # type: ignore
|
||||||
points.append(tuple(matrix @ bp.co * UnitsScale)) # type: ignore
|
points.append(tuple(matrix @ bp.co * UnitsScale)) # type: ignore
|
||||||
if i < len(spline.bezier_points) - 1:
|
if i < len(spline.bezier_points) - 1:
|
||||||
points.append(tuple(matrix @ bp.handle_right * UnitsScale)) # type: ignore
|
points.append(tuple(matrix @ bp.handle_right * UnitsScale)) # type: ignore
|
||||||
|
|
||||||
if closed:
|
if closed:
|
||||||
points.extend(
|
points.extend(
|
||||||
(
|
(
|
||||||
tuple(matrix @ spline.bezier_points[-1].handle_right * UnitsScale), # type: ignore
|
tuple(matrix @ spline.bezier_points[-1].handle_right * UnitsScale), # type: ignore
|
||||||
tuple(matrix @ spline.bezier_points[0].handle_left * UnitsScale), # type: ignore
|
tuple(matrix @ spline.bezier_points[0].handle_left * UnitsScale), # type: ignore
|
||||||
tuple(matrix @ spline.bezier_points[0].co * UnitsScale), # type: ignore
|
tuple(matrix @ spline.bezier_points[0].co * UnitsScale), # type: ignore
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
num_points = len(points)
|
num_points = len(points)
|
||||||
|
|
||||||
flattened_points = []
|
flattened_points = []
|
||||||
for row in points: flattened_points.extend(row)
|
for row in points:
|
||||||
|
flattened_points.extend(row)
|
||||||
|
|
||||||
knot_count = num_points + degree - 1
|
knot_count = num_points + degree - 1
|
||||||
knots = [0] * knot_count
|
knots = [0] * knot_count
|
||||||
@@ -193,7 +205,7 @@ def bezier_to_speckle(matrix: MMatrix, spline: bpy.types.Spline, name: Optional[
|
|||||||
name=name,
|
name=name,
|
||||||
degree=degree,
|
degree=degree,
|
||||||
closed=spline.use_cyclic_u,
|
closed=spline.use_cyclic_u,
|
||||||
periodic= not spline.use_endpoint_u,
|
periodic=not spline.use_endpoint_u,
|
||||||
points=flattened_points,
|
points=flattened_points,
|
||||||
weights=[1] * num_points,
|
weights=[1] * num_points,
|
||||||
knots=knots,
|
knots=knots,
|
||||||
@@ -204,12 +216,13 @@ def bezier_to_speckle(matrix: MMatrix, spline: bpy.types.Spline, name: Optional[
|
|||||||
domain=domain,
|
domain=domain,
|
||||||
units=Units,
|
units=Units,
|
||||||
bbox=Box(area=0.0, volume=0.0),
|
bbox=Box(area=0.0, volume=0.0),
|
||||||
displayValue = bezier_to_speckle_polyline(matrix, spline, length),
|
displayValue=bezier_to_speckle_polyline(matrix, spline, length),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def nurbs_to_speckle(matrix: MMatrix, spline: bpy.types.Spline, name: Optional[str] = None) -> Curve:
|
def nurbs_to_speckle(
|
||||||
|
matrix: MMatrix, spline: bpy.types.Spline, name: Optional[str] = None
|
||||||
|
) -> Curve:
|
||||||
degree = spline.order_u - 1
|
degree = spline.order_u - 1
|
||||||
knots = make_knots(spline)
|
knots = make_knots(spline)
|
||||||
|
|
||||||
@@ -219,10 +232,11 @@ def nurbs_to_speckle(matrix: MMatrix, spline: bpy.types.Spline, name: Optional[s
|
|||||||
weights = [pt.weight for pt in spline.points]
|
weights = [pt.weight for pt in spline.points]
|
||||||
is_rational = all(w == weights[0] for w in weights)
|
is_rational = all(w == weights[0] for w in weights)
|
||||||
|
|
||||||
points = [tuple(matrix @ pt.co.xyz * UnitsScale) for pt in spline.points] # type: ignore
|
points = [tuple(matrix @ pt.co.xyz * UnitsScale) for pt in spline.points] # type: ignore
|
||||||
|
|
||||||
flattened_points = []
|
flattened_points = []
|
||||||
for row in points: flattened_points.extend(row)
|
for row in points:
|
||||||
|
flattened_points.extend(row)
|
||||||
|
|
||||||
if spline.use_cyclic_u:
|
if spline.use_cyclic_u:
|
||||||
for i in range(0, degree * 3, 3):
|
for i in range(0, degree * 3, 3):
|
||||||
@@ -230,7 +244,7 @@ def nurbs_to_speckle(matrix: MMatrix, spline: bpy.types.Spline, name: Optional[s
|
|||||||
flattened_points.append(flattened_points[i + 0])
|
flattened_points.append(flattened_points[i + 0])
|
||||||
flattened_points.append(flattened_points[i + 1])
|
flattened_points.append(flattened_points[i + 1])
|
||||||
flattened_points.append(flattened_points[i + 2])
|
flattened_points.append(flattened_points[i + 2])
|
||||||
|
|
||||||
for i in range(0, degree):
|
for i in range(0, degree):
|
||||||
weights.append(weights[i])
|
weights.append(weights[i])
|
||||||
|
|
||||||
@@ -238,11 +252,11 @@ def nurbs_to_speckle(matrix: MMatrix, spline: bpy.types.Spline, name: Optional[s
|
|||||||
name=name,
|
name=name,
|
||||||
degree=degree,
|
degree=degree,
|
||||||
closed=spline.use_cyclic_u,
|
closed=spline.use_cyclic_u,
|
||||||
periodic= not spline.use_endpoint_u,
|
periodic=not spline.use_endpoint_u,
|
||||||
points=flattened_points,
|
points=flattened_points,
|
||||||
weights=weights,
|
weights=weights,
|
||||||
knots=knots,
|
knots=knots,
|
||||||
rational=is_rational,
|
rational=is_rational,
|
||||||
area=0,
|
area=0,
|
||||||
volume=0,
|
volume=0,
|
||||||
length=length,
|
length=length,
|
||||||
@@ -252,41 +266,53 @@ def nurbs_to_speckle(matrix: MMatrix, spline: bpy.types.Spline, name: Optional[s
|
|||||||
displayValue=nurbs_to_speckle_polyline(matrix, spline, length),
|
displayValue=nurbs_to_speckle_polyline(matrix, spline, length),
|
||||||
)
|
)
|
||||||
|
|
||||||
def nurbs_to_speckle_polyline(matrix: MMatrix, spline: bpy.types.Spline, length: Optional[float] = None) -> Polyline:
|
|
||||||
|
def nurbs_to_speckle_polyline(
|
||||||
|
matrix: MMatrix, spline: bpy.types.Spline, length: Optional[float] = None
|
||||||
|
) -> Polyline:
|
||||||
"""
|
"""
|
||||||
Samples a nurbs curve with resolution_u creating a polyline
|
Samples a nurbs curve with resolution_u creating a polyline
|
||||||
"""
|
"""
|
||||||
points: List[float] = []
|
points: List[float] = []
|
||||||
sampled_points = nurb_make_curve(spline, spline.resolution_u, 3)
|
sampled_points = nurb_make_curve(spline, spline.resolution_u, 3)
|
||||||
for i in range(0, len(sampled_points), 3):
|
for i in range(0, len(sampled_points), 3):
|
||||||
scaled_point = cast(Vector, matrix @ MVector((
|
scaled_point = cast(
|
||||||
sampled_points[i + 0],
|
Vector,
|
||||||
sampled_points[i + 1],
|
matrix
|
||||||
sampled_points[i + 2])) * UnitsScale)
|
@ MVector(
|
||||||
|
(sampled_points[i + 0], sampled_points[i + 1], sampled_points[i + 2])
|
||||||
|
)
|
||||||
|
* UnitsScale,
|
||||||
|
)
|
||||||
|
|
||||||
points.append(scaled_point.x)
|
points.append(scaled_point.x)
|
||||||
points.append(scaled_point.y)
|
points.append(scaled_point.y)
|
||||||
points.append(scaled_point.z)
|
points.append(scaled_point.z)
|
||||||
|
|
||||||
length = length or spline.calc_length()
|
length = length or spline.calc_length()
|
||||||
domain = Interval(start=0, end=length, totalChildrenCount=0)
|
domain = Interval(start=0, end=length, totalChildrenCount=0)
|
||||||
return Polyline(value=points, closed = spline.use_cyclic_u, domain=domain, area=0, len=length)
|
return Polyline(
|
||||||
|
value=points, closed=spline.use_cyclic_u, domain=domain, area=0, len=length
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#Inspired by https://blender.stackexchange.com/a/689 (CC BY-SA 3.0)
|
# Inspired by https://blender.stackexchange.com/a/689 (CC BY-SA 3.0)
|
||||||
def bezier_to_speckle_polyline(matrix: MMatrix, spline: bpy.types.Spline, length: Optional[float] = None) -> Optional[Polyline]:
|
def bezier_to_speckle_polyline(
|
||||||
|
matrix: MMatrix, spline: bpy.types.Spline, length: Optional[float] = None
|
||||||
|
) -> Optional[Polyline]:
|
||||||
"""
|
"""
|
||||||
Samples a Bézier curve with resolution_u creating a polyline
|
Samples a Bézier curve with resolution_u creating a polyline
|
||||||
"""
|
"""
|
||||||
segments = len(spline.bezier_points)
|
segments = len(spline.bezier_points)
|
||||||
if segments < 2: return None
|
if segments < 2:
|
||||||
|
return None
|
||||||
|
|
||||||
R = spline.resolution_u + 1
|
R = spline.resolution_u + 1
|
||||||
|
|
||||||
points = []
|
points = []
|
||||||
if not spline.use_cyclic_u:
|
if not spline.use_cyclic_u:
|
||||||
segments -= 1
|
segments -= 1
|
||||||
|
|
||||||
points: List[float] = []
|
points: List[float] = []
|
||||||
for i in range(segments):
|
for i in range(segments):
|
||||||
inext = (i + 1) % len(spline.bezier_points)
|
inext = (i + 1) % len(spline.bezier_points)
|
||||||
@@ -305,22 +331,33 @@ def bezier_to_speckle_polyline(matrix: MMatrix, spline: bpy.types.Spline, length
|
|||||||
|
|
||||||
length = length or spline.calc_length()
|
length = length or spline.calc_length()
|
||||||
domain = Interval(start=0, end=length, totalChildrenCount=0)
|
domain = Interval(start=0, end=length, totalChildrenCount=0)
|
||||||
return Polyline(value=points, closed = spline.use_cyclic_u, domain=domain, area=0, len=length)
|
return Polyline(
|
||||||
|
value=points, closed=spline.use_cyclic_u, domain=domain, area=0, len=length
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
_QUICK_TEST_NAME_LENGTH = SPECKLE_ID_LENGTH + len(OBJECT_NAME_SPECKLE_SEPARATOR)
|
_QUICK_TEST_NAME_LENGTH = SPECKLE_ID_LENGTH + len(OBJECT_NAME_SPECKLE_SEPARATOR)
|
||||||
|
|
||||||
|
|
||||||
def to_speckle_name(blender_object: bpy.types.ID) -> str:
|
def to_speckle_name(blender_object: bpy.types.ID) -> str:
|
||||||
does_name_contain_id = len(blender_object.name) > _QUICK_TEST_NAME_LENGTH and OBJECT_NAME_SPECKLE_SEPARATOR in blender_object.name
|
does_name_contain_id = (
|
||||||
|
len(blender_object.name) > _QUICK_TEST_NAME_LENGTH
|
||||||
|
and OBJECT_NAME_SPECKLE_SEPARATOR in blender_object.name
|
||||||
|
)
|
||||||
if does_name_contain_id:
|
if does_name_contain_id:
|
||||||
return blender_object.name.rsplit(OBJECT_NAME_SPECKLE_SEPARATOR, 1)[0]
|
return blender_object.name.rsplit(OBJECT_NAME_SPECKLE_SEPARATOR, 1)[0]
|
||||||
else:
|
else:
|
||||||
return blender_object.name
|
return blender_object.name
|
||||||
|
|
||||||
def poly_to_speckle(matrix: MMatrix, spline: bpy.types.Spline, name: Optional[str] = None) -> Polyline:
|
|
||||||
points = [tuple(matrix @ pt.co.xyz * UnitsScale) for pt in spline.points] # type: ignore
|
def poly_to_speckle(
|
||||||
|
matrix: MMatrix, spline: bpy.types.Spline, name: Optional[str] = None
|
||||||
|
) -> Polyline:
|
||||||
|
points = [tuple(matrix @ pt.co.xyz * UnitsScale) for pt in spline.points] # type: ignore
|
||||||
|
|
||||||
flattened_points = []
|
flattened_points = []
|
||||||
for row in points: flattened_points.extend(row)
|
for row in points:
|
||||||
|
flattened_points.extend(row)
|
||||||
|
|
||||||
length = spline.calc_length()
|
length = spline.calc_length()
|
||||||
domain = Interval(start=0, end=length, totalChildrenCount=0)
|
domain = Interval(start=0, end=length, totalChildrenCount=0)
|
||||||
@@ -346,40 +383,54 @@ def curve_to_speckle(blender_object: Object, data: bpy.types.Curve) -> Base:
|
|||||||
b["@elements"] = curves
|
b["@elements"] = curves
|
||||||
return b
|
return b
|
||||||
|
|
||||||
def curve_to_speckle_geometry(blender_object: Object, data: bpy.types.Curve) -> Tuple[List[Mesh], List[Base]]:
|
|
||||||
assert(blender_object.type == "CURVE")
|
|
||||||
|
|
||||||
blender_object = cast(Object, blender_object.evaluated_get(bpy.context.view_layer.depsgraph))
|
def curve_to_speckle_geometry(
|
||||||
|
blender_object: Object, data: bpy.types.Curve
|
||||||
|
) -> Tuple[List[Mesh], List[Base]]:
|
||||||
|
assert blender_object.type == "CURVE"
|
||||||
|
|
||||||
|
blender_object = cast(
|
||||||
|
Object, blender_object.evaluated_get(bpy.context.view_layer.depsgraph)
|
||||||
|
)
|
||||||
|
|
||||||
matrix = cast(MMatrix, blender_object.matrix_world)
|
matrix = cast(MMatrix, blender_object.matrix_world)
|
||||||
|
|
||||||
meshes: List[Mesh] = []
|
meshes: List[Mesh] = []
|
||||||
curves: List[Base] = []
|
curves: List[Base] = []
|
||||||
|
|
||||||
#TODO: Could we support this better?
|
# TODO: Could we support this better?
|
||||||
if data.bevel_mode == "OBJECT" and data.bevel_object != None:
|
if data.bevel_mode == "OBJECT" and data.bevel_object is not None:
|
||||||
meshes = mesh_to_speckle_meshes(blender_object, blender_object.to_mesh())
|
meshes = mesh_to_speckle_meshes(blender_object, blender_object.to_mesh())
|
||||||
|
|
||||||
for spline in data.splines:
|
for spline in data.splines:
|
||||||
if spline.type == "BEZIER":
|
if spline.type == "BEZIER":
|
||||||
curves.append(bezier_to_speckle(matrix, spline, to_speckle_name(blender_object)))
|
curves.append(
|
||||||
|
bezier_to_speckle(matrix, spline, to_speckle_name(blender_object))
|
||||||
|
)
|
||||||
|
|
||||||
elif spline.type == "NURBS":
|
elif spline.type == "NURBS":
|
||||||
curves.append(nurbs_to_speckle(matrix, spline, to_speckle_name(blender_object)))
|
curves.append(
|
||||||
|
nurbs_to_speckle(matrix, spline, to_speckle_name(blender_object))
|
||||||
|
)
|
||||||
|
|
||||||
elif spline.type == "POLY":
|
elif spline.type == "POLY":
|
||||||
curves.append(poly_to_speckle(matrix, spline, to_speckle_name(blender_object)))
|
curves.append(
|
||||||
|
poly_to_speckle(matrix, spline, to_speckle_name(blender_object))
|
||||||
|
)
|
||||||
|
|
||||||
return (meshes, curves)
|
return (meshes, curves)
|
||||||
|
|
||||||
def anything_to_speckle_mesh(blender_object: Object) -> Base:
|
|
||||||
|
|
||||||
|
def anything_to_speckle_mesh(blender_object: Object) -> Base:
|
||||||
mesh = mesh_to_speckle(blender_object, blender_object.to_mesh())
|
mesh = mesh_to_speckle(blender_object, blender_object.to_mesh())
|
||||||
blender_object.to_mesh_clear()
|
blender_object.to_mesh_clear()
|
||||||
return mesh
|
return mesh
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
def ngons_to_speckle_polylines(blender_object: Object, data: bpy.types.Mesh) -> Optional[List[Polyline]]:
|
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"
|
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
|
||||||
|
|
||||||
if blender_object.type != "MESH":
|
if blender_object.type != "MESH":
|
||||||
@@ -392,7 +443,7 @@ def ngons_to_speckle_polylines(blender_object: Object, data: bpy.types.Mesh) ->
|
|||||||
for i, poly in enumerate(data.polygons):
|
for i, poly in enumerate(data.polygons):
|
||||||
value = []
|
value = []
|
||||||
for v in poly.vertices:
|
for v in poly.vertices:
|
||||||
value.extend(mat @ verts[v].co * UnitsScale) # type: ignore
|
value.extend(mat @ verts[v].co * UnitsScale) # type: ignore
|
||||||
|
|
||||||
domain = Interval(start=0, end=1)
|
domain = Interval(start=0, end=1)
|
||||||
poly = Polyline(
|
poly = Polyline(
|
||||||
@@ -418,65 +469,75 @@ def material_to_speckle(blender_mat: bpy.types.Material) -> RenderMaterial:
|
|||||||
if blender_mat.use_nodes:
|
if blender_mat.use_nodes:
|
||||||
if blender_mat.node_tree.nodes.get("Principled BSDF"):
|
if blender_mat.node_tree.nodes.get("Principled BSDF"):
|
||||||
inputs = blender_mat.node_tree.nodes["Principled BSDF"].inputs
|
inputs = blender_mat.node_tree.nodes["Principled BSDF"].inputs
|
||||||
emission_color = "Emission" if "Emission" in inputs else "Emission Color" # type: ignore
|
emission_color = "Emission" if "Emission" in inputs else "Emission Color" # type: ignore
|
||||||
|
|
||||||
speckle_mat.diffuse = to_argb_int(inputs["Base Color"].default_value) # type: ignore
|
speckle_mat.diffuse = to_argb_int(inputs["Base Color"].default_value) # type: ignore
|
||||||
speckle_mat.emissive = to_argb_int(inputs[emission_color].default_value) # type: ignore
|
speckle_mat.emissive = to_argb_int(inputs[emission_color].default_value) # type: ignore
|
||||||
speckle_mat.roughness = inputs["Roughness"].default_value # type: ignore
|
speckle_mat.roughness = inputs["Roughness"].default_value # type: ignore
|
||||||
speckle_mat.metalness = inputs["Metallic"].default_value # type: ignore
|
speckle_mat.metalness = inputs["Metallic"].default_value # type: ignore
|
||||||
speckle_mat.opacity = inputs["Alpha"].default_value # type: ignore
|
speckle_mat.opacity = inputs["Alpha"].default_value # type: ignore
|
||||||
return speckle_mat
|
return speckle_mat
|
||||||
elif blender_mat.node_tree.nodes.get("Diffuse BSDF"):
|
elif blender_mat.node_tree.nodes.get("Diffuse BSDF"):
|
||||||
inputs = blender_mat.node_tree.nodes["Diffuse BSDF"].inputs
|
inputs = blender_mat.node_tree.nodes["Diffuse BSDF"].inputs
|
||||||
speckle_mat.diffuse = to_argb_int(inputs["Color"].default_value) # type: ignore
|
speckle_mat.diffuse = to_argb_int(inputs["Color"].default_value) # type: ignore
|
||||||
speckle_mat.roughness = inputs["Roughness"].default_value # type: ignore
|
speckle_mat.roughness = inputs["Roughness"].default_value # type: ignore
|
||||||
return speckle_mat
|
return speckle_mat
|
||||||
#TODO: Support more shaders
|
# TODO: Support more shaders
|
||||||
|
|
||||||
# fallback to standard material props
|
# fallback to standard material props
|
||||||
speckle_mat.diffuse = to_argb_int(blender_mat.diffuse_color) # type: ignore
|
speckle_mat.diffuse = to_argb_int(blender_mat.diffuse_color) # type: ignore
|
||||||
speckle_mat.metalness = blender_mat.metallic
|
speckle_mat.metalness = blender_mat.metallic
|
||||||
speckle_mat.roughness = blender_mat.roughness
|
speckle_mat.roughness = blender_mat.roughness
|
||||||
|
|
||||||
return speckle_mat
|
return speckle_mat
|
||||||
|
|
||||||
|
|
||||||
def camera_to_speckle_view(blender_object: Object, data: NCamera) -> Base:
|
def camera_to_speckle_view(blender_object: Object, data: NCamera) -> Base:
|
||||||
if data.type != 'PERSP':
|
if data.type != "PERSP":
|
||||||
raise Exception(f"Cameras of type {data.type} are not currently supported")
|
raise Exception(f"Cameras of type {data.type} are not currently supported")
|
||||||
|
|
||||||
matrix = cast(MMatrix, blender_object.matrix_world)
|
matrix = cast(MMatrix, blender_object.matrix_world)
|
||||||
up = cast(MVector, matrix.col[1].xyz)
|
up = cast(MVector, matrix.col[1].xyz)
|
||||||
forwards = cast(MVector, -matrix.col[2].xyz)
|
forwards = cast(MVector, -matrix.col[2].xyz)
|
||||||
translation = matrix.translation
|
translation = matrix.translation
|
||||||
|
|
||||||
view = Base.of_type("Objects.BuiltElements.View:Objects.BuiltElements.View3D") #HACK: views are not in specklepy yet!
|
view = Base.of_type(
|
||||||
|
"Objects.BuiltElements.View:Objects.BuiltElements.View3D"
|
||||||
|
) # HACK: views are not in specklepy yet!
|
||||||
view.name = to_speckle_name(blender_object)
|
view.name = to_speckle_name(blender_object)
|
||||||
view.origin = vector_to_speckle_point(translation)
|
view.origin = vector_to_speckle_point(translation)
|
||||||
view.upDirection = vector_to_speckle(up)
|
view.upDirection = vector_to_speckle(up)
|
||||||
view.forwardDirection = vector_to_speckle(forwards)
|
view.forwardDirection = vector_to_speckle(forwards)
|
||||||
view.target = vector_to_speckle_point(forwards) #TODO: do these need to be scaled?
|
view.target = vector_to_speckle_point(forwards) # TODO: do these need to be scaled?
|
||||||
view.units = Units
|
view.units = Units
|
||||||
view.isOrthogonal = False
|
view.isOrthogonal = False
|
||||||
return view
|
return view
|
||||||
|
|
||||||
|
|
||||||
def vector_to_speckle_point(xyz: MVector) -> Point:
|
def vector_to_speckle_point(xyz: MVector) -> Point:
|
||||||
return Point(
|
return Point(
|
||||||
x = xyz.x * UnitsScale,
|
x=xyz.x * UnitsScale,
|
||||||
y = xyz.y * UnitsScale,
|
y=xyz.y * UnitsScale,
|
||||||
z = xyz.z * UnitsScale,
|
z=xyz.z * UnitsScale,
|
||||||
units = Units,
|
units=Units,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def vector_to_speckle(xyz: MVector) -> Vector:
|
def vector_to_speckle(xyz: MVector) -> Vector:
|
||||||
return Vector(
|
return Vector(
|
||||||
x = xyz.x * UnitsScale,
|
x=xyz.x * UnitsScale,
|
||||||
y = xyz.y * UnitsScale,
|
y=xyz.y * UnitsScale,
|
||||||
z = xyz.z * UnitsScale,
|
z=xyz.z * UnitsScale,
|
||||||
units = Units,
|
units=Units,
|
||||||
)
|
)
|
||||||
|
|
||||||
def transform_to_speckle(blender_transform: Union[Iterable[Iterable[float]], MMatrix]) -> Transform:
|
|
||||||
iterable_transform = cast(Iterable[Iterable[float]], blender_transform) #NOTE: Matrix are iterable, even if type hinting says they are not
|
def transform_to_speckle(
|
||||||
|
blender_transform: Union[Iterable[Iterable[float]], MMatrix]
|
||||||
|
) -> Transform:
|
||||||
|
iterable_transform = cast(
|
||||||
|
Iterable[Iterable[float]], blender_transform
|
||||||
|
) # NOTE: Matrix are iterable, even if type hinting says they are not
|
||||||
value = [y for x in iterable_transform for y in x]
|
value = [y for x in iterable_transform for y in x]
|
||||||
# scale the translation
|
# scale the translation
|
||||||
for i in (3, 7, 11):
|
for i in (3, 7, 11):
|
||||||
@@ -492,9 +553,13 @@ def block_def_to_speckle(blender_definition: bpy.types.Collection) -> BlockDefin
|
|||||||
c = convert_to_speckle(geo, UnitsScale, Units, None)
|
c = convert_to_speckle(geo, UnitsScale, Units, None)
|
||||||
geometryBuilder.include_object(c, geo)
|
geometryBuilder.include_object(c, geo)
|
||||||
except ConversionSkippedException as ex:
|
except ConversionSkippedException as ex:
|
||||||
_report(f"Skipped converting '{geo.name_full}' inside collection instance: '{ex}")
|
_report(
|
||||||
|
f"Skipped converting '{geo.name_full}' inside collection instance: '{ex}"
|
||||||
|
)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
_report(f"Failed to converted '{geo.name_full}' inside collection instance: '{ex}'")
|
_report(
|
||||||
|
f"Failed to converted '{geo.name_full}' inside collection instance: '{ex}'"
|
||||||
|
)
|
||||||
|
|
||||||
dummyRoot = Base()
|
dummyRoot = Base()
|
||||||
geometryBuilder.apply_relationships(geometryBuilder.converted.values(), dummyRoot)
|
geometryBuilder.apply_relationships(geometryBuilder.converted.values(), dummyRoot)
|
||||||
@@ -512,9 +577,7 @@ def block_def_to_speckle(blender_definition: bpy.types.Collection) -> BlockDefin
|
|||||||
|
|
||||||
def block_instance_to_speckle(blender_instance: Object) -> BlockInstance:
|
def block_instance_to_speckle(blender_instance: Object) -> BlockInstance:
|
||||||
return BlockInstance(
|
return BlockInstance(
|
||||||
blockDefinition=block_def_to_speckle(
|
blockDefinition=block_def_to_speckle(blender_instance.instance_collection),
|
||||||
blender_instance.instance_collection
|
|
||||||
),
|
|
||||||
transform=transform_to_speckle(blender_instance.matrix_world),
|
transform=transform_to_speckle(blender_instance.matrix_world),
|
||||||
name=to_speckle_name(blender_instance),
|
name=to_speckle_name(blender_instance),
|
||||||
units=Units,
|
units=Units,
|
||||||
@@ -524,18 +587,21 @@ def block_instance_to_speckle(blender_instance: Object) -> BlockInstance:
|
|||||||
def empty_to_speckle(blender_object: Object) -> Union[BlockInstance, Base]:
|
def empty_to_speckle(blender_object: Object) -> Union[BlockInstance, Base]:
|
||||||
# probably an instance collection (block) so let's try it
|
# probably an instance collection (block) so let's try it
|
||||||
|
|
||||||
if blender_object.instance_collection and blender_object.instance_type == "COLLECTION":
|
if (
|
||||||
|
blender_object.instance_collection
|
||||||
|
and blender_object.instance_type == "COLLECTION"
|
||||||
|
):
|
||||||
# Empty -> Block
|
# Empty -> Block
|
||||||
return block_instance_to_speckle(blender_object)
|
return block_instance_to_speckle(blender_object)
|
||||||
else:
|
else:
|
||||||
# Empty -> Point
|
# Empty -> Point
|
||||||
wrapper = Base()
|
wrapper = Base()
|
||||||
wrapper["@displayValue"] = matrix_to_speckle_point(cast(MMatrix, blender_object.matrix_world))
|
wrapper["@displayValue"] = matrix_to_speckle_point(
|
||||||
|
cast(MMatrix, blender_object.matrix_world)
|
||||||
|
)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def matrix_to_speckle_point(matrix: MMatrix, units_scale: float = 1.0) -> Point:
|
def matrix_to_speckle_point(matrix: MMatrix, units_scale: float = 1.0) -> Point:
|
||||||
transformed_pos = cast(MVector, matrix @ MVector((0,0,0)) * units_scale)
|
transformed_pos = cast(MVector, matrix @ MVector((0, 0, 0)) * units_scale)
|
||||||
return Point(x = transformed_pos.x,
|
return Point(x=transformed_pos.x, y=transformed_pos.y, z=transformed_pos.z)
|
||||||
y = transformed_pos.y,
|
|
||||||
z = transformed_pos.z)
|
|
||||||
|
|||||||
+108
-60
@@ -1,20 +1,24 @@
|
|||||||
import math
|
import math
|
||||||
from typing import Any, Dict, Optional, Tuple, Union, cast
|
from typing import Any, Dict, Optional, Tuple, Union, cast
|
||||||
from bmesh.types import BMesh
|
|
||||||
import bpy, idprop
|
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import idprop
|
||||||
|
from bmesh.types import BMesh
|
||||||
|
from bpy.types import Collection as BCollection
|
||||||
|
from bpy.types import Material, Node, Object, ShaderNodeVertexColor
|
||||||
from specklepy.objects.base import Base
|
from specklepy.objects.base import Base
|
||||||
from specklepy.objects.geometry import Mesh
|
from specklepy.objects.geometry import Mesh
|
||||||
|
from specklepy.objects.graph_traversal.traversal import TraversalContext
|
||||||
from specklepy.objects.other import RenderMaterial
|
from specklepy.objects.other import RenderMaterial
|
||||||
|
|
||||||
from bpy_speckle.convert.constants import IGNORED_PROPERTY_KEYS
|
from bpy_speckle.convert.constants import IGNORED_PROPERTY_KEYS
|
||||||
from bpy_speckle.functions import _report
|
from bpy_speckle.functions import _report
|
||||||
from bpy.types import Material, Object, Collection as BCollection, Node, ShaderNodeVertexColor, NodeInputs
|
|
||||||
|
|
||||||
from specklepy.objects.graph_traversal.traversal import TraversalContext
|
|
||||||
|
|
||||||
class ConversionSkippedException(Exception):
|
class ConversionSkippedException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def to_rgba(argb_int: int) -> Tuple[float, float, float, float]:
|
def to_rgba(argb_int: int) -> Tuple[float, float, float, float]:
|
||||||
"""Converts the int representation of a colour into a percent RGBA tuple"""
|
"""Converts the int representation of a colour into a percent RGBA tuple"""
|
||||||
alpha = ((argb_int >> 24) & 255) / 255
|
alpha = ((argb_int >> 24) & 255) / 255
|
||||||
@@ -32,15 +36,21 @@ def to_argb_int(rgba_color: list[float]) -> int:
|
|||||||
|
|
||||||
return int.from_bytes(int_color, byteorder="big", signed=True)
|
return int.from_bytes(int_color, byteorder="big", signed=True)
|
||||||
|
|
||||||
|
|
||||||
def set_custom_property(key: str, value: Any, blender_object: Object) -> None:
|
def set_custom_property(key: str, value: Any, blender_object: Object) -> None:
|
||||||
try:
|
try:
|
||||||
#Expected c types: float, int, string, float[], int[]
|
# Expected c types: float, int, string, float[], int[]
|
||||||
blender_object[key] = value
|
blender_object[key] = value
|
||||||
except (OverflowError, TypeError) as ex:
|
except (OverflowError, TypeError) as ex:
|
||||||
print(f"Skipping setting property ({key}={value}) on {blender_object.name_full}, Reason: {ex}")
|
print(
|
||||||
|
f"Skipping setting property ({key}={value}) on {blender_object.name_full}, Reason: {ex}"
|
||||||
|
)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
#TODO: Log this as it's unexpected!!!
|
# TODO: Log this as it's unexpected!!!
|
||||||
print(f"Skipping setting property ({key}={value}) on {blender_object.name_full}, Reason: {ex}")
|
print(
|
||||||
|
f"Skipping setting property ({key}={value}) on {blender_object.name_full}, Reason: {ex}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_custom_properties(speckle_object: Base, blender_object: Object):
|
def add_custom_properties(speckle_object: Base, blender_object: Object):
|
||||||
if blender_object is None:
|
if blender_object is None:
|
||||||
@@ -51,7 +61,11 @@ def add_custom_properties(speckle_object: Base, blender_object: Object):
|
|||||||
app_id = getattr(speckle_object, "applicationId", None)
|
app_id = getattr(speckle_object, "applicationId", None)
|
||||||
if app_id:
|
if app_id:
|
||||||
blender_object["applicationId"] = speckle_object.applicationId
|
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)
|
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:
|
for key in keys:
|
||||||
val = getattr(speckle_object, key, None)
|
val = getattr(speckle_object, key, None)
|
||||||
if val is None:
|
if val is None:
|
||||||
@@ -66,14 +80,13 @@ def add_custom_properties(speckle_object: Base, blender_object: Object):
|
|||||||
items = [item for item in val if not isinstance(item, Base)]
|
items = [item for item in val if not isinstance(item, Base)]
|
||||||
if items:
|
if items:
|
||||||
set_custom_property(key, items, blender_object)
|
set_custom_property(key, items, blender_object)
|
||||||
elif isinstance(val,dict):
|
elif isinstance(val, dict):
|
||||||
for (k,v) in val.items():
|
for k, v in val.items():
|
||||||
if not isinstance(v, Base):
|
if not isinstance(v, Base):
|
||||||
set_custom_property(k, v, blender_object)
|
set_custom_property(k, v, blender_object)
|
||||||
|
|
||||||
|
|
||||||
def render_material_to_native(speckle_mat: RenderMaterial) -> Material:
|
def render_material_to_native(speckle_mat: RenderMaterial) -> Material:
|
||||||
|
|
||||||
mat_name = speckle_mat.name
|
mat_name = speckle_mat.name
|
||||||
if not mat_name:
|
if not mat_name:
|
||||||
mat_name = speckle_mat.applicationId or speckle_mat.id or speckle_mat.get_id()
|
mat_name = speckle_mat.applicationId or speckle_mat.id or speckle_mat.get_id()
|
||||||
@@ -87,43 +100,48 @@ def render_material_to_native(speckle_mat: RenderMaterial) -> Material:
|
|||||||
blender_mat.use_nodes = True
|
blender_mat.use_nodes = True
|
||||||
inputs = blender_mat.node_tree.nodes["Principled BSDF"].inputs
|
inputs = blender_mat.node_tree.nodes["Principled BSDF"].inputs
|
||||||
|
|
||||||
inputs["Base Color"].default_value = to_rgba(speckle_mat.diffuse) # type: ignore
|
inputs["Base Color"].default_value = to_rgba(speckle_mat.diffuse) # type: ignore
|
||||||
inputs["Roughness"].default_value = speckle_mat.roughness # type: ignore
|
inputs["Roughness"].default_value = speckle_mat.roughness # type: ignore
|
||||||
inputs["Metallic"].default_value = speckle_mat.metalness # type: ignore
|
inputs["Metallic"].default_value = speckle_mat.metalness # type: ignore
|
||||||
inputs["Alpha"].default_value = speckle_mat.opacity # type: ignore
|
inputs["Alpha"].default_value = speckle_mat.opacity # type: ignore
|
||||||
|
|
||||||
# Blender >=4.0 use "Emission Color"
|
# Blender >=4.0 use "Emission Color"
|
||||||
emission_color = "Emission" if "Emission" in inputs else "Emission Color" # type: ignore
|
emission_color = "Emission" if "Emission" in inputs else "Emission Color" # type: ignore
|
||||||
inputs[emission_color].default_value = to_rgba(speckle_mat.emissive) # type: ignore
|
inputs[emission_color].default_value = to_rgba(speckle_mat.emissive) # type: ignore
|
||||||
|
|
||||||
if speckle_mat.opacity < 1.0:
|
if speckle_mat.opacity < 1.0:
|
||||||
blender_mat.blend_method = "BLEND"
|
blender_mat.blend_method = "BLEND"
|
||||||
|
|
||||||
return blender_mat
|
return blender_mat
|
||||||
|
|
||||||
|
|
||||||
_vertex_color_material: Optional[Material] = None
|
_vertex_color_material: Optional[Material] = None
|
||||||
|
|
||||||
|
|
||||||
def get_vertex_color_material() -> Material:
|
def get_vertex_color_material() -> Material:
|
||||||
global _vertex_color_material
|
global _vertex_color_material
|
||||||
|
|
||||||
#see https://stackoverflow.com/a/69807985
|
# see https://stackoverflow.com/a/69807985
|
||||||
if not _vertex_color_material:
|
if not _vertex_color_material:
|
||||||
_vertex_color_material = bpy.data.materials.new("Vertex Color Material")
|
_vertex_color_material = bpy.data.materials.new("Vertex Color Material")
|
||||||
_vertex_color_material.use_nodes = True
|
_vertex_color_material.use_nodes = True
|
||||||
nodes = _vertex_color_material.node_tree.nodes
|
nodes = _vertex_color_material.node_tree.nodes
|
||||||
principled_bsdf_node = cast(Node, nodes.get("Principled BSDF"))
|
principled_bsdf_node = cast(Node, nodes.get("Principled BSDF"))
|
||||||
|
|
||||||
if not "VERTEX_COLOR" in [node.type for node in nodes]:
|
if "VERTEX_COLOR" not in [node.type for node in nodes]:
|
||||||
vertex_color_node = cast(ShaderNodeVertexColor, nodes.new(type = "ShaderNodeVertexColor"))
|
vertex_color_node = cast(
|
||||||
|
ShaderNodeVertexColor, nodes.new(type="ShaderNodeVertexColor")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
vertex_color_node = cast(ShaderNodeVertexColor, nodes.get("Vertex Color"))
|
vertex_color_node = cast(ShaderNodeVertexColor, nodes.get("Vertex Color"))
|
||||||
vertex_color_node.layer_name = "Col"
|
vertex_color_node.layer_name = "Col"
|
||||||
|
|
||||||
links = _vertex_color_material.node_tree.links
|
links = _vertex_color_material.node_tree.links
|
||||||
link = links.new(vertex_color_node.outputs[0], principled_bsdf_node.inputs[0])
|
_ = links.new(vertex_color_node.outputs[0], principled_bsdf_node.inputs[0])
|
||||||
|
|
||||||
return _vertex_color_material
|
return _vertex_color_material
|
||||||
|
|
||||||
|
|
||||||
def get_render_material(speckle_object: Base) -> Optional[RenderMaterial]:
|
def get_render_material(speckle_object: Base) -> Optional[RenderMaterial]:
|
||||||
"""Trys to get a RenderMaterial on given speckle_object"""
|
"""Trys to get a RenderMaterial on given speckle_object"""
|
||||||
|
|
||||||
@@ -137,7 +155,6 @@ def get_render_material(speckle_object: Base) -> Optional[RenderMaterial]:
|
|||||||
return speckle_mat
|
return speckle_mat
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def add_vertices(speckle_mesh: Mesh, blender_mesh: BMesh, scale=1.0):
|
def add_vertices(speckle_mesh: Mesh, blender_mesh: BMesh, scale=1.0):
|
||||||
@@ -154,10 +171,15 @@ def add_vertices(speckle_mesh: Mesh, blender_mesh: BMesh, scale=1.0):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_faces(
|
||||||
def add_faces(speckle_mesh: Mesh, blender_mesh: BMesh, indexOffset: int, materialIndex: int = 0, smooth:bool = True):
|
speckle_mesh: Mesh,
|
||||||
|
blender_mesh: BMesh,
|
||||||
|
indexOffset: int,
|
||||||
|
materialIndex: int = 0,
|
||||||
|
smooth: bool = True,
|
||||||
|
):
|
||||||
sfaces = speckle_mesh.faces
|
sfaces = speckle_mesh.faces
|
||||||
|
|
||||||
if sfaces and len(sfaces) > 0:
|
if sfaces and len(sfaces) > 0:
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(sfaces):
|
while i < len(sfaces):
|
||||||
@@ -178,13 +200,11 @@ def add_faces(speckle_mesh: Mesh, blender_mesh: BMesh, indexOffset: int, materia
|
|||||||
|
|
||||||
|
|
||||||
def add_colors(speckle_mesh: Mesh, blender_mesh: BMesh):
|
def add_colors(speckle_mesh: Mesh, blender_mesh: BMesh):
|
||||||
|
|
||||||
scolors = speckle_mesh.colors
|
scolors = speckle_mesh.colors
|
||||||
|
|
||||||
if scolors:
|
if scolors:
|
||||||
colors = []
|
colors = []
|
||||||
if len(scolors) > 0:
|
if len(scolors) > 0:
|
||||||
|
|
||||||
for i in range(len(scolors)):
|
for i in range(len(scolors)):
|
||||||
argb = int(scolors[i])
|
argb = int(scolors[i])
|
||||||
(a, r, g, b) = argb_split(argb)
|
(a, r, g, b) = argb_split(argb)
|
||||||
@@ -205,6 +225,7 @@ def add_colors(speckle_mesh: Mesh, blender_mesh: BMesh):
|
|||||||
for loop in face.loops:
|
for loop in face.loops:
|
||||||
loop[color_layer] = colors[loop.vert.index]
|
loop[color_layer] = colors[loop.vert.index]
|
||||||
|
|
||||||
|
|
||||||
def argb_split(argb: int) -> Tuple[int, int, int, int]:
|
def argb_split(argb: int) -> Tuple[int, int, int, int]:
|
||||||
alpha = (argb >> 24) & 0xFF
|
alpha = (argb >> 24) & 0xFF
|
||||||
red = (argb >> 16) & 0xFF
|
red = (argb >> 16) & 0xFF
|
||||||
@@ -213,6 +234,7 @@ def argb_split(argb: int) -> Tuple[int, int, int, int]:
|
|||||||
|
|
||||||
return (alpha, red, green, blue)
|
return (alpha, red, green, blue)
|
||||||
|
|
||||||
|
|
||||||
def add_uv_coords(speckle_mesh: Mesh, blender_mesh: BMesh):
|
def add_uv_coords(speckle_mesh: Mesh, blender_mesh: BMesh):
|
||||||
s_uvs = speckle_mesh.textureCoordinates
|
s_uvs = speckle_mesh.textureCoordinates
|
||||||
if not s_uvs:
|
if not s_uvs:
|
||||||
@@ -222,8 +244,7 @@ def add_uv_coords(speckle_mesh: Mesh, blender_mesh: BMesh):
|
|||||||
|
|
||||||
if len(s_uvs) // 2 == len(blender_mesh.verts):
|
if len(s_uvs) // 2 == len(blender_mesh.verts):
|
||||||
uv.extend(
|
uv.extend(
|
||||||
(float(s_uvs[i]), float(s_uvs[i + 1]))
|
(float(s_uvs[i]), float(s_uvs[i + 1])) for i in range(0, len(s_uvs), 2)
|
||||||
for i in range(0, len(s_uvs), 2)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_report(
|
_report(
|
||||||
@@ -235,9 +256,9 @@ def add_uv_coords(speckle_mesh: Mesh, blender_mesh: BMesh):
|
|||||||
uv_layer = blender_mesh.loops.layers.uv.verify()
|
uv_layer = blender_mesh.loops.layers.uv.verify()
|
||||||
|
|
||||||
for f in blender_mesh.faces:
|
for f in blender_mesh.faces:
|
||||||
for l in f.loops:
|
for loop in f.loops:
|
||||||
luv = l[uv_layer]
|
luv = loop[uv_layer]
|
||||||
luv.uv = uv[l.vert.index]
|
luv.uv = uv[loop.vert.index]
|
||||||
except:
|
except:
|
||||||
_report("Failed to decode texture coordinates.")
|
_report("Failed to decode texture coordinates.")
|
||||||
raise
|
raise
|
||||||
@@ -246,8 +267,7 @@ def add_uv_coords(speckle_mesh: Mesh, blender_mesh: BMesh):
|
|||||||
ignored_keys = {
|
ignored_keys = {
|
||||||
"id",
|
"id",
|
||||||
"speckle",
|
"speckle",
|
||||||
"speckle_type"
|
"speckle_type" "_speckle_type",
|
||||||
"_speckle_type",
|
|
||||||
"_speckle_name",
|
"_speckle_name",
|
||||||
"_speckle_transform",
|
"_speckle_transform",
|
||||||
"_RNA_UI",
|
"_RNA_UI",
|
||||||
@@ -257,6 +277,7 @@ ignored_keys = {
|
|||||||
"_chunkable",
|
"_chunkable",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_blender_custom_properties(obj, max_depth: int = 63):
|
def get_blender_custom_properties(obj, max_depth: int = 63):
|
||||||
"""Recursively grabs custom properties on blender objects. Max depth is determined by the max allowed by Newtonsoft.NET, don't exceed unless you know what you're doing"""
|
"""Recursively grabs custom properties on blender objects. Max depth is determined by the max allowed by Newtonsoft.NET, don't exceed unless you know what you're doing"""
|
||||||
if max_depth <= 0:
|
if max_depth <= 0:
|
||||||
@@ -271,22 +292,26 @@ def get_blender_custom_properties(obj, max_depth: int = 63):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isinstance(obj, (list, tuple, idprop.types.IDPropertyArray)):
|
if isinstance(obj, (list, tuple, idprop.types.IDPropertyArray)):
|
||||||
return [get_blender_custom_properties(o, max_depth - 1) for o in obj] # type: ignore
|
return [get_blender_custom_properties(o, max_depth - 1) for o in obj] # type: ignore
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Python implementation of Blender's NURBS curve generation for to Speckle conversion
|
Python implementation of Blender's NURBS curve generation for to Speckle conversion
|
||||||
from: https://blender.stackexchange.com/a/34276
|
from: https://blender.stackexchange.com/a/34276
|
||||||
based on https://projects.blender.org/blender/blender/src/branch/main/source/blender/blenkernel/intern/curve.cc (check old version)
|
based on https://projects.blender.org/blender/blender/src/branch/main/source/blender/blenkernel/intern/curve.cc (check old version)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def macro_knotsu(nu: bpy.types.Spline) -> int:
|
def macro_knotsu(nu: bpy.types.Spline) -> int:
|
||||||
return nu.order_u + nu.point_count_u + (nu.order_u - 1 if nu.use_cyclic_u else 0)
|
return nu.order_u + nu.point_count_u + (nu.order_u - 1 if nu.use_cyclic_u else 0)
|
||||||
|
|
||||||
|
|
||||||
def macro_segmentsu(nu: bpy.types.Spline) -> int:
|
def macro_segmentsu(nu: bpy.types.Spline) -> int:
|
||||||
return nu.point_count_u if nu.use_cyclic_u else nu.point_count_u - 1
|
return nu.point_count_u if nu.use_cyclic_u else nu.point_count_u - 1
|
||||||
|
|
||||||
|
|
||||||
def make_knots(nu: bpy.types.Spline) -> list[float]:
|
def make_knots(nu: bpy.types.Spline) -> list[float]:
|
||||||
knots = [0.0] * macro_knotsu(nu)
|
knots = [0.0] * macro_knotsu(nu)
|
||||||
flag = nu.use_endpoint_u + (nu.use_bezier_u << 1)
|
flag = nu.use_endpoint_u + (nu.use_bezier_u << 1)
|
||||||
@@ -299,13 +324,13 @@ def make_knots(nu: bpy.types.Spline) -> list[float]:
|
|||||||
|
|
||||||
def calc_knots(knots: list[float], point_count: int, order: int, flag: int) -> None:
|
def calc_knots(knots: list[float], point_count: int, order: int, flag: int) -> None:
|
||||||
pts_order = point_count + order
|
pts_order = point_count + order
|
||||||
if flag == 1: # CU_NURB_ENDPOINT
|
if flag == 1: # CU_NURB_ENDPOINT
|
||||||
k = 0.0
|
k = 0.0
|
||||||
for a in range(1, pts_order + 1):
|
for a in range(1, pts_order + 1):
|
||||||
knots[a - 1] = k
|
knots[a - 1] = k
|
||||||
if a >= order and a <= point_count:
|
if a >= order and a <= point_count:
|
||||||
k += 1.0
|
k += 1.0
|
||||||
elif flag == 2: # CU_NURB_BEZIER
|
elif flag == 2: # CU_NURB_BEZIER
|
||||||
if order == 4:
|
if order == 4:
|
||||||
k = 0.34
|
k = 0.34
|
||||||
for a in range(pts_order):
|
for a in range(pts_order):
|
||||||
@@ -323,11 +348,20 @@ def calc_knots(knots: list[float], point_count: int, order: int, flag: int) -> N
|
|||||||
|
|
||||||
knots[-1] = knots[-2]
|
knots[-1] = knots[-2]
|
||||||
|
|
||||||
def basis_nurb(t: float, order: int, point_count: int, knots: list[float], basis: list[float], start: int, end: int) -> Tuple[int, int]:
|
|
||||||
|
def basis_nurb(
|
||||||
|
t: float,
|
||||||
|
order: int,
|
||||||
|
point_count: int,
|
||||||
|
knots: list[float],
|
||||||
|
basis: list[float],
|
||||||
|
start: int,
|
||||||
|
end: int,
|
||||||
|
) -> Tuple[int, int]:
|
||||||
i1 = i2 = 0
|
i1 = i2 = 0
|
||||||
orderpluspnts = order + point_count
|
orderpluspnts = order + point_count
|
||||||
opp2 = orderpluspnts - 1
|
opp2 = orderpluspnts - 1
|
||||||
|
|
||||||
# this is for float inaccuracy
|
# this is for float inaccuracy
|
||||||
if t < knots[0]:
|
if t < knots[0]:
|
||||||
t = knots[0]
|
t = knots[0]
|
||||||
@@ -352,11 +386,10 @@ def basis_nurb(t: float, order: int, point_count: int, knots: list[float], basis
|
|||||||
else:
|
else:
|
||||||
basis[i] = 0.0
|
basis[i] = 0.0
|
||||||
|
|
||||||
basis[i] = 0.0 #type: ignore
|
basis[i] = 0.0 # type: ignore
|
||||||
|
|
||||||
# this is order 2, 3, ...
|
# this is order 2, 3, ...
|
||||||
for j in range(2, order + 1):
|
for j in range(2, order + 1):
|
||||||
|
|
||||||
if i2 + j >= orderpluspnts:
|
if i2 + j >= orderpluspnts:
|
||||||
i2 = opp2 - j
|
i2 = opp2 - j
|
||||||
|
|
||||||
@@ -384,8 +417,9 @@ def basis_nurb(t: float, order: int, point_count: int, knots: list[float], basis
|
|||||||
|
|
||||||
return start, end
|
return start, end
|
||||||
|
|
||||||
|
|
||||||
def nurb_make_curve(nu: bpy.types.Spline, resolu: int, stride: int = 3) -> list[float]:
|
def nurb_make_curve(nu: bpy.types.Spline, resolu: int, stride: int = 3) -> list[float]:
|
||||||
""""BKE_nurb_makeCurve"""
|
""" "BKE_nurb_makeCurve"""
|
||||||
EPS = 1e-6
|
EPS = 1e-6
|
||||||
coord_index = istart = iend = 0
|
coord_index = istart = iend = 0
|
||||||
|
|
||||||
@@ -396,17 +430,22 @@ def nurb_make_curve(nu: bpy.types.Spline, resolu: int, stride: int = 3) -> list[
|
|||||||
|
|
||||||
resolu = resolu * macro_segmentsu(nu)
|
resolu = resolu * macro_segmentsu(nu)
|
||||||
ustart = knots[nu.order_u - 1]
|
ustart = knots[nu.order_u - 1]
|
||||||
uend = knots[nu.point_count_u + nu.order_u - 1] if nu.use_cyclic_u else \
|
uend = (
|
||||||
knots[nu.point_count_u]
|
knots[nu.point_count_u + nu.order_u - 1]
|
||||||
ustep = (uend - ustart) / (resolu - (0 if nu.use_cyclic_u else 1))
|
if nu.use_cyclic_u
|
||||||
|
else knots[nu.point_count_u]
|
||||||
|
)
|
||||||
|
ustep = (uend - ustart) / (resolu - (0 if nu.use_cyclic_u else 1))
|
||||||
cycl = nu.order_u - 1 if nu.use_cyclic_u else 0
|
cycl = nu.order_u - 1 if nu.use_cyclic_u else 0
|
||||||
|
|
||||||
u = ustart
|
u = ustart
|
||||||
while resolu:
|
while resolu:
|
||||||
resolu -= 1
|
resolu -= 1
|
||||||
istart, iend = basis_nurb(u, nu.order_u, nu.point_count_u + cycl, knots, basisu, istart, iend)
|
istart, iend = basis_nurb(
|
||||||
|
u, nu.order_u, nu.point_count_u + cycl, knots, basisu, istart, iend
|
||||||
|
)
|
||||||
|
|
||||||
#/* calc sum */
|
# /* calc sum */
|
||||||
sumdiv = 0.0
|
sumdiv = 0.0
|
||||||
sum_index = 0
|
sum_index = 0
|
||||||
pt_index = istart - 1
|
pt_index = istart - 1
|
||||||
@@ -416,17 +455,17 @@ def nurb_make_curve(nu: bpy.types.Spline, resolu: int, stride: int = 3) -> list[
|
|||||||
else:
|
else:
|
||||||
pt_index += 1
|
pt_index += 1
|
||||||
|
|
||||||
sum_array[sum_index] = basisu[i] * nu.points[pt_index].co[3] #type: ignore
|
sum_array[sum_index] = basisu[i] * nu.points[pt_index].co[3] # type: ignore
|
||||||
sumdiv += sum_array[sum_index]
|
sumdiv += sum_array[sum_index]
|
||||||
sum_index += 1
|
sum_index += 1
|
||||||
|
|
||||||
if (sumdiv != 0.0) and (sumdiv < 1.0 - EPS or sumdiv > 1.0 + EPS):
|
if (sumdiv != 0.0) and (sumdiv < 1.0 - EPS or sumdiv > 1.0 + EPS):
|
||||||
sum_index = 0
|
sum_index = 0
|
||||||
for i in range(istart, iend + 1):
|
for i in range(istart, iend + 1):
|
||||||
sum_array[sum_index] /= sumdiv #type: ignore
|
sum_array[sum_index] /= sumdiv # type: ignore
|
||||||
sum_index += 1
|
sum_index += 1
|
||||||
|
|
||||||
coord_array[coord_index: coord_index + 3] = (0.0, 0.0, 0.0)
|
coord_array[coord_index : coord_index + 3] = (0.0, 0.0, 0.0)
|
||||||
|
|
||||||
sum_index = 0
|
sum_index = 0
|
||||||
pt_index = istart - 1
|
pt_index = istart - 1
|
||||||
@@ -438,7 +477,9 @@ def nurb_make_curve(nu: bpy.types.Spline, resolu: int, stride: int = 3) -> list[
|
|||||||
|
|
||||||
if sum_array[sum_index] != 0.0:
|
if sum_array[sum_index] != 0.0:
|
||||||
for j in range(3):
|
for j in range(3):
|
||||||
coord_array[coord_index + j] += sum_array[sum_index] * nu.points[pt_index].co[j]
|
coord_array[coord_index + j] += (
|
||||||
|
sum_array[sum_index] * nu.points[pt_index].co[j]
|
||||||
|
)
|
||||||
sum_index += 1
|
sum_index += 1
|
||||||
|
|
||||||
coord_index += stride
|
coord_index += stride
|
||||||
@@ -446,14 +487,21 @@ def nurb_make_curve(nu: bpy.types.Spline, resolu: int, stride: int = 3) -> list[
|
|||||||
|
|
||||||
return coord_array
|
return coord_array
|
||||||
|
|
||||||
|
|
||||||
def link_object_to_collection_nested(obj: Object, col: BCollection):
|
def link_object_to_collection_nested(obj: Object, col: BCollection):
|
||||||
if obj.name not in col.objects: #type: ignore
|
if obj.name not in col.objects: # type: ignore
|
||||||
col.objects.link(obj)
|
col.objects.link(obj)
|
||||||
|
|
||||||
for child in obj.children:
|
for child in obj.children:
|
||||||
link_object_to_collection_nested(child, col)
|
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:
|
|
||||||
|
def add_to_hierarchy(
|
||||||
|
converted: Union[Object, BCollection],
|
||||||
|
traversalContext: "TraversalContext",
|
||||||
|
converted_objects: Dict[str, Union[Object, BCollection]],
|
||||||
|
preserve_transform: bool,
|
||||||
|
) -> None:
|
||||||
nextParent = traversalContext.parent
|
nextParent = traversalContext.parent
|
||||||
|
|
||||||
# Traverse up the tree to find a direct parent object, and a containing collection
|
# Traverse up the tree to find a direct parent object, and a containing collection
|
||||||
@@ -467,7 +515,7 @@ def add_to_hierarchy(converted: Union[Object, BCollection], traversalContext : '
|
|||||||
if isinstance(c, BCollection):
|
if isinstance(c, BCollection):
|
||||||
parent_collection = c
|
parent_collection = c
|
||||||
break
|
break
|
||||||
else: #isinstance(c, Object):
|
else: # isinstance(c, Object):
|
||||||
parent_object = parent_object or c
|
parent_object = parent_object or c
|
||||||
|
|
||||||
nextParent = nextParent.parent
|
nextParent = nextParent.parent
|
||||||
@@ -485,8 +533,8 @@ def add_to_hierarchy(converted: Union[Object, BCollection], traversalContext : '
|
|||||||
|
|
||||||
|
|
||||||
def set_parent(child: Object, parent: Object, preserve_transform: bool = False) -> None:
|
def set_parent(child: Object, parent: Object, preserve_transform: bool = False) -> None:
|
||||||
if preserve_transform :
|
if preserve_transform:
|
||||||
previous = child.matrix_world.copy() # type: ignore
|
previous = child.matrix_world.copy() # type: ignore
|
||||||
child.parent = parent
|
child.parent = parent
|
||||||
child.matrix_world = previous
|
child.matrix_world = previous
|
||||||
else:
|
else:
|
||||||
|
|||||||
+22
-16
@@ -1,9 +1,12 @@
|
|||||||
from typing import Callable
|
from typing import Callable
|
||||||
from specklepy.objects.base import Base
|
|
||||||
from bpy_speckle.convert.constants import ELEMENTS_PROPERTY_ALIASES
|
|
||||||
|
|
||||||
from specklepy.objects.graph_traversal.traversal import GraphTraversal, TraversalRule
|
from specklepy.objects.base import Base
|
||||||
from specklepy.objects.units import get_scale_factor_to_meters, get_units_from_string
|
from specklepy.objects.graph_traversal.traversal import (GraphTraversal,
|
||||||
|
TraversalRule)
|
||||||
|
from specklepy.objects.units import (get_scale_factor_to_meters,
|
||||||
|
get_units_from_string)
|
||||||
|
|
||||||
|
from bpy_speckle.convert.constants import ELEMENTS_PROPERTY_ALIASES
|
||||||
|
|
||||||
|
|
||||||
def _report(msg: object) -> None:
|
def _report(msg: object) -> None:
|
||||||
@@ -18,28 +21,31 @@ def get_scale_length(units: str) -> float:
|
|||||||
return get_scale_factor_to_meters(get_units_from_string(units))
|
return get_scale_factor_to_meters(get_units_from_string(units))
|
||||||
|
|
||||||
|
|
||||||
def get_default_traversal_func(can_convert_to_native: Callable[[Base], bool]) -> GraphTraversal:
|
def get_default_traversal_func(
|
||||||
|
can_convert_to_native: Callable[[Base], bool]
|
||||||
|
) -> GraphTraversal:
|
||||||
"""
|
"""
|
||||||
Traversal func for traversing a speckle commit object
|
Traversal func for traversing a speckle commit object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ignore_rule = TraversalRule(
|
ignore_rule = TraversalRule(
|
||||||
[
|
[
|
||||||
lambda o: "Objects.Structural.Results" in o.speckle_type, #Sadly, this one is necessary to avoid double conversion...
|
lambda o: "Objects.Structural.Results"
|
||||||
lambda o: "Objects.BuiltElements.Revit.Parameter" in o.speckle_type, #This one is just for traversal performance of revit commits
|
in o.speckle_type, # Sadly, this one is necessary to avoid double conversion...
|
||||||
],
|
lambda o: "Objects.BuiltElements.Revit.Parameter"
|
||||||
lambda _: [],
|
in o.speckle_type, # This one is just for traversal performance of revit commits
|
||||||
|
],
|
||||||
|
lambda _: [],
|
||||||
)
|
)
|
||||||
|
|
||||||
convertible_rule = TraversalRule(
|
convertible_rule = TraversalRule(
|
||||||
[can_convert_to_native],
|
[can_convert_to_native],
|
||||||
lambda _: ELEMENTS_PROPERTY_ALIASES,
|
lambda _: ELEMENTS_PROPERTY_ALIASES,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
default_rule = TraversalRule(
|
default_rule = TraversalRule(
|
||||||
[lambda _: True],
|
[lambda _: True],
|
||||||
lambda o: o.get_member_names(), #TODO: avoid deprecated members
|
lambda o: o.get_member_names(), # TODO: avoid deprecated members
|
||||||
)
|
)
|
||||||
|
|
||||||
return GraphTraversal([ignore_rule, convertible_rule, default_rule])
|
return GraphTraversal([ignore_rule, convertible_rule, default_rule])
|
||||||
|
|||||||
+18
-23
@@ -3,9 +3,9 @@ Provides uniform and consistent path helpers for `specklepy`
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from importlib import import_module, invalidate_caches
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from importlib import import_module, invalidate_caches
|
|
||||||
|
|
||||||
_user_data_env_var = "SPECKLE_USERDATA_PATH"
|
_user_data_env_var = "SPECKLE_USERDATA_PATH"
|
||||||
|
|
||||||
@@ -55,9 +55,7 @@ def user_application_data_path() -> Path:
|
|||||||
if sys.platform.startswith("win"):
|
if sys.platform.startswith("win"):
|
||||||
app_data_path = os.getenv("APPDATA")
|
app_data_path = os.getenv("APPDATA")
|
||||||
if not app_data_path:
|
if not app_data_path:
|
||||||
raise Exception(
|
raise Exception("Cannot get appdata path from environment.")
|
||||||
"Cannot get appdata path from environment."
|
|
||||||
)
|
|
||||||
return Path(app_data_path)
|
return Path(app_data_path)
|
||||||
else:
|
else:
|
||||||
# try getting the standard XDG_DATA_HOME value
|
# try getting the standard XDG_DATA_HOME value
|
||||||
@@ -68,9 +66,7 @@ def user_application_data_path() -> Path:
|
|||||||
else:
|
else:
|
||||||
return _ensure_folder_exists(Path.home(), ".config")
|
return _ensure_folder_exists(Path.home(), ".config")
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise Exception(
|
raise Exception("Failed to initialize user application data path.", ex)
|
||||||
"Failed to initialize user application data path.", ex
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def user_speckle_folder_path() -> Path:
|
def user_speckle_folder_path() -> Path:
|
||||||
@@ -90,19 +86,16 @@ def user_speckle_connector_installation_path(host_application: str) -> Path:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
print("Starting module dependency installation")
|
print("Starting module dependency installation")
|
||||||
print(sys.executable)
|
print(sys.executable)
|
||||||
|
|
||||||
PYTHON_PATH = sys.executable
|
PYTHON_PATH = sys.executable
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def connector_installation_path(host_application: str) -> Path:
|
def connector_installation_path(host_application: str) -> Path:
|
||||||
connector_installation_path = user_speckle_connector_installation_path(host_application)
|
connector_installation_path = user_speckle_connector_installation_path(
|
||||||
|
host_application
|
||||||
|
)
|
||||||
connector_installation_path.mkdir(exist_ok=True, parents=True)
|
connector_installation_path.mkdir(exist_ok=True, parents=True)
|
||||||
|
|
||||||
# set user modules path at beginning of paths for earlier hit
|
# set user modules path at beginning of paths for earlier hit
|
||||||
@@ -113,7 +106,6 @@ def connector_installation_path(host_application: str) -> Path:
|
|||||||
return connector_installation_path
|
return connector_installation_path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def is_pip_available() -> bool:
|
def is_pip_available() -> bool:
|
||||||
try:
|
try:
|
||||||
import_module("pip") # noqa F401
|
import_module("pip") # noqa F401
|
||||||
@@ -132,7 +124,9 @@ def ensure_pip() -> None:
|
|||||||
if completed_process.returncode == 0:
|
if completed_process.returncode == 0:
|
||||||
print("Successfully installed pip")
|
print("Successfully installed pip")
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Failed to install pip, got {completed_process.returncode} return code")
|
raise Exception(
|
||||||
|
f"Failed to install pip, got {completed_process.returncode} return code"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_requirements_path() -> Path:
|
def get_requirements_path() -> Path:
|
||||||
@@ -151,12 +145,12 @@ def install_requirements(host_application: str) -> None:
|
|||||||
|
|
||||||
def debugger_is_active() -> bool:
|
def debugger_is_active() -> bool:
|
||||||
"""Return if the debugger is currently active"""
|
"""Return if the debugger is currently active"""
|
||||||
return hasattr(sys, 'gettrace') and sys.gettrace() is not None
|
return hasattr(sys, "gettrace") and sys.gettrace() is not None
|
||||||
|
|
||||||
requirements_path = get_requirements_path()
|
requirements_path = get_requirements_path()
|
||||||
|
|
||||||
is_debug = debugger_is_active()
|
is_debug = debugger_is_active()
|
||||||
|
|
||||||
if not is_debug and not requirements_path.exists():
|
if not is_debug and not requirements_path.exists():
|
||||||
print("Skipped installing dependencies")
|
print("Skipped installing dependencies")
|
||||||
return
|
return
|
||||||
@@ -186,7 +180,7 @@ def install_requirements(host_application: str) -> None:
|
|||||||
m = f"Failed to install dependencies through pip, got {completed_process.returncode} return code"
|
m = f"Failed to install dependencies through pip, got {completed_process.returncode} return code"
|
||||||
print(m)
|
print(m)
|
||||||
raise Exception(m)
|
raise Exception(m)
|
||||||
|
|
||||||
print("Successfully installed dependencies")
|
print("Successfully installed dependencies")
|
||||||
|
|
||||||
if not is_debug:
|
if not is_debug:
|
||||||
@@ -205,7 +199,7 @@ def _import_dependencies() -> None:
|
|||||||
# the code above doesn't work for now, it fails on importing graphql-core
|
# the code above doesn't work for now, it fails on importing graphql-core
|
||||||
# despite that, the connector seams to be working as expected
|
# despite that, the connector seams to be working as expected
|
||||||
# But it would be nice to make this solution work
|
# But it would be nice to make this solution work
|
||||||
# it would ensure that all dependencies are fully loaded
|
# it would ensure that all dependencies are fully loaded
|
||||||
# requirements = get_requirements_path().read_text()
|
# requirements = get_requirements_path().read_text()
|
||||||
# reqs = [
|
# reqs = [
|
||||||
# req.split(" ; ")[0].split("==")[0].split("[")[0].replace("-", "_")
|
# req.split(" ; ")[0].split("==")[0].split("[")[0].replace("-", "_")
|
||||||
@@ -216,6 +210,7 @@ def _import_dependencies() -> None:
|
|||||||
# print(req)
|
# print(req)
|
||||||
# import_module("specklepy")
|
# import_module("specklepy")
|
||||||
|
|
||||||
|
|
||||||
def ensure_dependencies(host_application: str) -> None:
|
def ensure_dependencies(host_application: str) -> None:
|
||||||
try:
|
try:
|
||||||
install_dependencies(host_application)
|
install_dependencies(host_application)
|
||||||
@@ -223,6 +218,6 @@ def ensure_dependencies(host_application: str) -> None:
|
|||||||
_import_dependencies()
|
_import_dependencies()
|
||||||
print("Successfully found dependencies")
|
print("Successfully found dependencies")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise Exception(f"Cannot automatically ensure Speckle dependencies. Please try restarting the host application {host_application}!")
|
raise Exception(
|
||||||
|
f"Cannot automatically ensure Speckle dependencies. Please try restarting the host application {host_application}!"
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,29 +1,13 @@
|
|||||||
from .users import LoadUsers, LoadUserStreams, ResetUsers
|
|
||||||
from .object import (
|
|
||||||
UpdateObject,
|
|
||||||
ResetObject,
|
|
||||||
DeleteObject,
|
|
||||||
UploadNgonsAsPolylines,
|
|
||||||
SelectIfSameCustomProperty,
|
|
||||||
SelectIfHasCustomProperty,
|
|
||||||
)
|
|
||||||
from .streams import (
|
|
||||||
ReceiveStreamObjects,
|
|
||||||
SendStreamObjects,
|
|
||||||
ViewStreamDataApi,
|
|
||||||
DeleteStream,
|
|
||||||
SelectOrphanObjects,
|
|
||||||
)
|
|
||||||
from .streams import (
|
|
||||||
AddStreamFromURL,
|
|
||||||
CreateStream,
|
|
||||||
CopyStreamId,
|
|
||||||
CopyCommitId,
|
|
||||||
CopyBranchName,
|
|
||||||
CopyModelId,
|
|
||||||
)
|
|
||||||
from .commit import DeleteCommit
|
from .commit import DeleteCommit
|
||||||
from .misc import OpenSpeckleGuide, OpenSpeckleTutorials, OpenSpeckleForum
|
from .misc import OpenSpeckleForum, OpenSpeckleGuide, OpenSpeckleTutorials
|
||||||
|
from .object import (DeleteObject, ResetObject, SelectIfHasCustomProperty,
|
||||||
|
SelectIfSameCustomProperty, UpdateObject,
|
||||||
|
UploadNgonsAsPolylines)
|
||||||
|
from .streams import (AddStreamFromURL, CopyBranchName, CopyCommitId,
|
||||||
|
CopyModelId, CopyStreamId, CreateStream, DeleteStream,
|
||||||
|
ReceiveStreamObjects, SelectOrphanObjects,
|
||||||
|
SendStreamObjects, ViewStreamDataApi)
|
||||||
|
from .users import LoadUsers, LoadUserStreams, ResetUsers
|
||||||
|
|
||||||
operator_classes = [
|
operator_classes = [
|
||||||
LoadUsers,
|
LoadUsers,
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ Commit operators
|
|||||||
"""
|
"""
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import BoolProperty
|
from bpy.props import BoolProperty
|
||||||
|
from specklepy.logging import metrics
|
||||||
|
|
||||||
from bpy_speckle.clients import speckle_clients
|
from bpy_speckle.clients import speckle_clients
|
||||||
from bpy_speckle.functions import _report
|
from bpy_speckle.functions import _report
|
||||||
from bpy_speckle.properties.scene import get_speckle
|
from bpy_speckle.properties.scene import get_speckle
|
||||||
from specklepy.logging import metrics
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteCommit(bpy.types.Operator):
|
class DeleteCommit(bpy.types.Operator):
|
||||||
@@ -23,7 +24,7 @@ class DeleteCommit(bpy.types.Operator):
|
|||||||
are_you_sure: BoolProperty(
|
are_you_sure: BoolProperty(
|
||||||
name="Confirm",
|
name="Confirm",
|
||||||
default=False,
|
default=False,
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
@@ -48,7 +49,7 @@ class DeleteCommit(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_commit(context: bpy.types.Context) -> None:
|
def delete_commit(context: bpy.types.Context) -> None:
|
||||||
speckle = get_speckle(context)
|
speckle = get_speckle(context)
|
||||||
|
|
||||||
(_, stream, branch, commit) = speckle.validate_commit_selection()
|
(_, stream, branch, commit) = speckle.validate_commit_selection()
|
||||||
@@ -59,14 +60,13 @@ class DeleteCommit(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
client.account,
|
client.account,
|
||||||
custom_props={
|
custom_props={"name": "delete_commit"},
|
||||||
"name": "delete_commit"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not deleted:
|
if not deleted:
|
||||||
raise Exception("Delete operation failed")
|
raise Exception("Delete operation failed")
|
||||||
|
|
||||||
print(f"Version {commit.id} ({commit.message}) of model {branch.id} ({branch.name}) has been deleted from project {stream.id} ({stream.name})")
|
print(
|
||||||
|
f"Version {commit.id} ({commit.message}) of model {branch.id} ({branch.name}) has been deleted from project {stream.id} ({stream.name})"
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import bpy
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from specklepy.logging import metrics
|
|
||||||
|
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from specklepy.logging import metrics
|
||||||
|
|
||||||
|
|
||||||
class OpenSpeckleGuide(bpy.types.Operator):
|
class OpenSpeckleGuide(bpy.types.Operator):
|
||||||
@@ -12,15 +11,13 @@ class OpenSpeckleGuide(bpy.types.Operator):
|
|||||||
bl_label = "Speckle Docs"
|
bl_label = "Speckle Docs"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = f"Browse the documentation on the Speckle Guide ({_guide_url})"
|
bl_description = f"Browse the documentation on the Speckle Guide ({_guide_url})"
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
webbrowser.open(self._guide_url)
|
webbrowser.open(self._guide_url)
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
None,
|
None,
|
||||||
custom_props={
|
custom_props={"name": "OpenSpeckleGuide"},
|
||||||
"name": "OpenSpeckleGuide"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -31,16 +28,16 @@ class OpenSpeckleTutorials(bpy.types.Operator):
|
|||||||
bl_idname = "speckle.open_speckle_tutorials"
|
bl_idname = "speckle.open_speckle_tutorials"
|
||||||
bl_label = "Tutorials Portal"
|
bl_label = "Tutorials Portal"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = f"Visit our tutorials portal for learning resources ({_tutorials_url})"
|
bl_description = (
|
||||||
|
f"Visit our tutorials portal for learning resources ({_tutorials_url})"
|
||||||
|
)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
webbrowser.open(self._tutorials_url)
|
webbrowser.open(self._tutorials_url)
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
None,
|
None,
|
||||||
custom_props={
|
custom_props={"name": "OpenSpeckleTutorials"},
|
||||||
"name": "OpenSpeckleTutorials"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -51,15 +48,15 @@ class OpenSpeckleForum(bpy.types.Operator):
|
|||||||
bl_idname = "speckle.open_speckle_forum"
|
bl_idname = "speckle.open_speckle_forum"
|
||||||
bl_label = "Community Forum"
|
bl_label = "Community Forum"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = f"Ask questions and join the discussion on our community forum ({_forum_url})"
|
bl_description = (
|
||||||
|
f"Ask questions and join the discussion on our community forum ({_forum_url})"
|
||||||
|
)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
webbrowser.open(self._forum_url)
|
webbrowser.open(self._forum_url)
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
None,
|
None,
|
||||||
custom_props={
|
custom_props={"name": "OpenSpeckleForum"},
|
||||||
"name": "OpenSpeckleForum"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ Object operators
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.props import BoolProperty, EnumProperty
|
from bpy.props import BoolProperty, EnumProperty
|
||||||
from deprecated import deprecated
|
from deprecated import deprecated
|
||||||
from bpy_speckle.convert.to_speckle import (
|
|
||||||
convert_to_speckle,
|
|
||||||
ngons_to_speckle_polylines,
|
|
||||||
)
|
|
||||||
from bpy_speckle.functions import get_scale_length, _report
|
|
||||||
from bpy_speckle.clients import speckle_clients
|
|
||||||
from specklepy.logging import metrics
|
from specklepy.logging import metrics
|
||||||
|
|
||||||
|
from bpy_speckle.clients import speckle_clients
|
||||||
|
from bpy_speckle.convert.to_speckle import (convert_to_speckle,
|
||||||
|
ngons_to_speckle_polylines)
|
||||||
|
from bpy_speckle.functions import _report, get_scale_length
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
class UpdateObject(bpy.types.Operator):
|
class UpdateObject(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
@@ -28,7 +28,6 @@ class UpdateObject(bpy.types.Operator):
|
|||||||
client = None
|
client = None
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
|
|
||||||
client = speckle_clients[int(context.scene.speckle.active_user)]
|
client = speckle_clients[int(context.scene.speckle.active_user)]
|
||||||
|
|
||||||
active = context.active_object
|
active = context.active_object
|
||||||
@@ -59,16 +58,15 @@ class UpdateObject(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
None,
|
None,
|
||||||
custom_props={
|
custom_props={"name": "UpdateObject"},
|
||||||
"name": "UpdateObject"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
class ResetObject(bpy.types.Operator):
|
class ResetObject(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
@@ -80,7 +78,6 @@ class ResetObject(bpy.types.Operator):
|
|||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
context.object.speckle.send_or_receive = "send"
|
context.object.speckle.send_or_receive = "send"
|
||||||
context.object.speckle.stream_id = ""
|
context.object.speckle.stream_id = ""
|
||||||
context.object.speckle.object_id = ""
|
context.object.speckle.object_id = ""
|
||||||
@@ -89,14 +86,13 @@ class ResetObject(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
None,
|
None,
|
||||||
custom_props={
|
custom_props={"name": "ResetObject"},
|
||||||
"name": "ResetObject"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
class DeleteObject(bpy.types.Operator):
|
class DeleteObject(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
@@ -143,14 +139,13 @@ class DeleteObject(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
None,
|
None,
|
||||||
custom_props={
|
custom_props={"name": "DeleteObject"},
|
||||||
"name": "DeleteObject"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
class UploadNgonsAsPolylines(bpy.types.Operator):
|
class UploadNgonsAsPolylines(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
@@ -170,7 +165,6 @@ class UploadNgonsAsPolylines(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
active = context.active_object
|
active = context.active_object
|
||||||
if active is not None and active.type == "MESH":
|
if active is not None and active.type == "MESH":
|
||||||
|
|
||||||
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
|
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
|
||||||
client = speckle_clients[int(context.scene.speckle.active_user)]
|
client = speckle_clients[int(context.scene.speckle.active_user)]
|
||||||
stream = user.streams[user.active_stream]
|
stream = user.streams[user.active_stream]
|
||||||
@@ -187,7 +181,6 @@ class UploadNgonsAsPolylines(bpy.types.Operator):
|
|||||||
|
|
||||||
placeholders = []
|
placeholders = []
|
||||||
for polyline in sp:
|
for polyline in sp:
|
||||||
|
|
||||||
res = client.objects.create([polyline])
|
res = client.objects.create([polyline])
|
||||||
|
|
||||||
if res is None:
|
if res is None:
|
||||||
@@ -223,10 +216,8 @@ class UploadNgonsAsPolylines(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
None,
|
None,
|
||||||
custom_props={
|
custom_props={"name": "UploadNgonsAsPolylines"},
|
||||||
"name": "UploadNgonsAsPolylines"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -240,14 +231,13 @@ class UploadNgonsAsPolylines(bpy.types.Operator):
|
|||||||
|
|
||||||
|
|
||||||
def get_custom_speckle_props(self, context):
|
def get_custom_speckle_props(self, context):
|
||||||
ignore = ["speckle", "cycles", "cycles_visibility"]
|
|
||||||
|
|
||||||
active = context.active_object
|
active = context.active_object
|
||||||
if not active:
|
if not active:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return [(x, "{}".format(x), "") for x in active.keys()]
|
return [(x, "{}".format(x), "") for x in active.keys()]
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
class SelectIfSameCustomProperty(bpy.types.Operator):
|
class SelectIfSameCustomProperty(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
@@ -275,7 +265,6 @@ class SelectIfSameCustomProperty(bpy.types.Operator):
|
|||||||
return wm.invoke_props_dialog(self)
|
return wm.invoke_props_dialog(self)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
active = context.active_object
|
active = context.active_object
|
||||||
if not active:
|
if not active:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
@@ -292,7 +281,6 @@ class SelectIfSameCustomProperty(bpy.types.Operator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.data.objects:
|
||||||
|
|
||||||
if self.custom_prop in obj.keys() and obj[self.custom_prop] == value:
|
if self.custom_prop in obj.keys() and obj[self.custom_prop] == value:
|
||||||
obj.select_set(True)
|
obj.select_set(True)
|
||||||
else:
|
else:
|
||||||
@@ -300,14 +288,13 @@ class SelectIfSameCustomProperty(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
None,
|
None,
|
||||||
custom_props={
|
custom_props={"name": "SelectIfSameCustomProperty"},
|
||||||
"name": "SelectIfSameCustomProperty"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
class SelectIfHasCustomProperty(bpy.types.Operator):
|
class SelectIfHasCustomProperty(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
@@ -335,7 +322,6 @@ class SelectIfHasCustomProperty(bpy.types.Operator):
|
|||||||
return wm.invoke_props_dialog(self)
|
return wm.invoke_props_dialog(self)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
active = context.active_object
|
active = context.active_object
|
||||||
if not active:
|
if not active:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
@@ -343,12 +329,9 @@ class SelectIfHasCustomProperty(bpy.types.Operator):
|
|||||||
if self.custom_prop not in active.keys():
|
if self.custom_prop not in active.keys():
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
value = active[self.custom_prop]
|
|
||||||
|
|
||||||
_report("Looking for '{}' property.".format(self.custom_prop))
|
_report("Looking for '{}' property.".format(self.custom_prop))
|
||||||
|
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.data.objects:
|
||||||
|
|
||||||
if self.custom_prop in obj.keys():
|
if self.custom_prop in obj.keys():
|
||||||
obj.select_set(True)
|
obj.select_set(True)
|
||||||
else:
|
else:
|
||||||
@@ -356,11 +339,8 @@ class SelectIfHasCustomProperty(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
None,
|
None,
|
||||||
custom_props={
|
custom_props={"name": "SelectIfHasCustomProperty"},
|
||||||
"name": "SelectIfHasCustomProperty"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
+210
-185
@@ -1,84 +1,101 @@
|
|||||||
"""
|
"""
|
||||||
Stream operators
|
Stream operators
|
||||||
"""
|
"""
|
||||||
|
import webbrowser
|
||||||
from math import radians
|
from math import radians
|
||||||
from typing import Callable, Dict, Optional, Tuple, Union, cast
|
from typing import Callable, Dict, Optional, Tuple, Union, cast
|
||||||
import webbrowser
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import (
|
from bpy.props import BoolProperty, EnumProperty, StringProperty
|
||||||
StringProperty,
|
from bpy.types import Collection, Context, Object
|
||||||
BoolProperty,
|
|
||||||
EnumProperty,
|
|
||||||
)
|
|
||||||
from bpy.types import (
|
|
||||||
Context,
|
|
||||||
Object,
|
|
||||||
Collection
|
|
||||||
)
|
|
||||||
from deprecated import deprecated
|
from deprecated import deprecated
|
||||||
from bpy_speckle.blender_commit_object_builder import BlenderCommitObjectBuilder
|
from specklepy.core.api import host_applications, operations
|
||||||
from bpy_speckle.convert.to_native import (
|
from specklepy.core.api.client import SpeckleClient
|
||||||
can_convert_to_native,
|
from specklepy.core.api.models import Commit, Stream
|
||||||
collection_to_native,
|
|
||||||
convert_to_native,
|
|
||||||
set_convert_instances_as,
|
|
||||||
)
|
|
||||||
from bpy_speckle.convert.to_speckle import (
|
|
||||||
convert_to_speckle,
|
|
||||||
)
|
|
||||||
from bpy_speckle.functions import (
|
|
||||||
get_default_traversal_func,
|
|
||||||
_report,
|
|
||||||
get_scale_length,
|
|
||||||
)
|
|
||||||
from bpy_speckle.clients import speckle_clients
|
|
||||||
from bpy_speckle.operators.users import LoadUserStreams, add_user_stream
|
|
||||||
from bpy_speckle.properties.scene import SpeckleSceneSettings, SpeckleStreamObject, SpeckleUserObject, get_speckle, selection_state
|
|
||||||
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
|
|
||||||
from specklepy.core.api.wrapper import StreamWrapper
|
from specklepy.core.api.wrapper import StreamWrapper
|
||||||
from specklepy.core.api.resources.stream import Stream
|
from specklepy.logging import metrics
|
||||||
from specklepy.transports.server import ServerTransport
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
from specklepy.objects import Base
|
from specklepy.objects import Base
|
||||||
from specklepy.objects.other import Collection as SCollection
|
from specklepy.objects.other import Collection as SCollection
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.transports.server import ServerTransport
|
||||||
from specklepy.logging import metrics
|
|
||||||
|
from bpy_speckle.blender_commit_object_builder import \
|
||||||
|
BlenderCommitObjectBuilder
|
||||||
|
from bpy_speckle.clients import speckle_clients
|
||||||
|
from bpy_speckle.convert.to_native import (can_convert_to_native,
|
||||||
|
collection_to_native,
|
||||||
|
convert_to_native,
|
||||||
|
set_convert_instances_as)
|
||||||
|
from bpy_speckle.convert.to_speckle import convert_to_speckle
|
||||||
|
from bpy_speckle.convert.util import (ConversionSkippedException,
|
||||||
|
add_to_hierarchy)
|
||||||
|
from bpy_speckle.functions import (_report, get_default_traversal_func,
|
||||||
|
get_scale_length)
|
||||||
|
from bpy_speckle.operators.users import LoadUserStreams, add_user_stream
|
||||||
|
from bpy_speckle.properties.scene import (SpeckleSceneSettings,
|
||||||
|
SpeckleStreamObject,
|
||||||
|
SpeckleUserObject, get_speckle,
|
||||||
|
selection_state)
|
||||||
|
|
||||||
ObjectCallback = Optional[Callable[[bpy.types.Context, Object, Base], Object]]
|
ObjectCallback = Optional[Callable[[bpy.types.Context, Object, Base], Object]]
|
||||||
ReceiveCompleteCallback = Optional[Callable[[bpy.types.Context, Dict[str, Union[Object, Collection]]], None]]
|
ReceiveCompleteCallback = Optional[
|
||||||
|
Callable[[bpy.types.Context, Dict[str, Union[Object, Collection]]], None]
|
||||||
|
]
|
||||||
|
|
||||||
def get_receive_funcs(speckle: SpeckleSceneSettings) -> tuple[ObjectCallback, ReceiveCompleteCallback]:
|
|
||||||
|
def get_project_workspace_id(client: SpeckleClient, project_id: str) -> Optional[str]:
|
||||||
|
workspace_id = None
|
||||||
|
server_version = client.project.server_version or client.server.verison()
|
||||||
|
maj = server_version[0]
|
||||||
|
min = server_version[1]
|
||||||
|
if maj > 2 or (maj == 2 and min > 20):
|
||||||
|
workspace_id = client.project.get(project_id).workspaceId
|
||||||
|
return workspace_id
|
||||||
|
|
||||||
|
|
||||||
|
def get_receive_funcs(
|
||||||
|
speckle: SpeckleSceneSettings,
|
||||||
|
) -> tuple[ObjectCallback, ReceiveCompleteCallback]:
|
||||||
"""
|
"""
|
||||||
Fetches the injected callback functions from user specified "Receive Script"
|
Fetches the injected callback functions from user specified "Receive Script"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
objectCallback: ObjectCallback = None
|
objectCallback: ObjectCallback = None
|
||||||
receiveCompleteCallback: ReceiveCompleteCallback = None
|
receiveCompleteCallback: ReceiveCompleteCallback = None
|
||||||
|
|
||||||
if speckle.receive_script in bpy.data.texts:
|
if speckle.receive_script in bpy.data.texts:
|
||||||
mod = bpy.data.texts[speckle.receive_script].as_module()
|
mod = bpy.data.texts[speckle.receive_script].as_module()
|
||||||
if hasattr(mod, "execute_for_each"):
|
if hasattr(mod, "execute_for_each"):
|
||||||
objectCallback = mod.execute_for_each #type: ignore
|
objectCallback = mod.execute_for_each # type: ignore
|
||||||
elif hasattr(mod, "execute"):
|
elif hasattr(mod, "execute"):
|
||||||
objectCallback = lambda c, o, _ : mod.execute(c.scene, o) #type: ignore
|
objectCallback = lambda c, o, _: mod.execute(c.scene, o) # type: ignore
|
||||||
|
|
||||||
if hasattr(mod, "execute_for_all"):
|
if hasattr(mod, "execute_for_all"):
|
||||||
receiveCompleteCallback = mod.execute_for_all #type: ignore
|
receiveCompleteCallback = mod.execute_for_all # type: ignore
|
||||||
|
|
||||||
return (objectCallback, receiveCompleteCallback)
|
return (objectCallback, receiveCompleteCallback)
|
||||||
|
|
||||||
#RECEIVE_MODES = [#TODO: modes
|
|
||||||
|
# RECEIVE_MODES = [#TODO: modes
|
||||||
# ("create", "Create", "Add new geometry, without removing any existing objects"),
|
# ("create", "Create", "Add new geometry, without removing any existing objects"),
|
||||||
# ("replace", "Replace", "Replace objects from previous receive operations from the same stream"),
|
# ("replace", "Replace", "Replace objects from previous receive operations from the same stream"),
|
||||||
# #("update","Update", "") #TODO: update mode!
|
# #("update","Update", "") #TODO: update mode!
|
||||||
#]
|
# ]
|
||||||
|
|
||||||
INSTANCES_SETTINGS = [
|
INSTANCES_SETTINGS = [
|
||||||
("collection_instance", "Collection Instance", "Receive Instances as Collection Instances"),
|
(
|
||||||
("linked_duplicates", "Linked Duplicates", "Receive Instances as Linked Duplicates"),
|
"collection_instance",
|
||||||
|
"Collection Instance",
|
||||||
|
"Receive Instances as Collection Instances",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"linked_duplicates",
|
||||||
|
"Linked Duplicates",
|
||||||
|
"Receive Instances as Linked Duplicates",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ReceiveStreamObjects(bpy.types.Operator):
|
class ReceiveStreamObjects(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
Receive objects from selected model version
|
Receive objects from selected model version
|
||||||
@@ -89,64 +106,63 @@ class ReceiveStreamObjects(bpy.types.Operator):
|
|||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Receive objects from selected model version"
|
bl_description = "Receive objects from selected model version"
|
||||||
|
|
||||||
clean_meshes: BoolProperty(name="Clean Meshes", default=False) # type: ignore
|
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_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") # type: ignore
|
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):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.prop(self, "clean_meshes")
|
col.prop(self, "clean_meshes")
|
||||||
#col.prop(self, "receive_mode")
|
# col.prop(self, "receive_mode")
|
||||||
col.prop(self, "receive_instances_as")
|
col.prop(self, "receive_instances_as")
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def clean_converted_meshes(context: bpy.types.Context, convertedObjects: dict[str, Object]):
|
def clean_converted_meshes(
|
||||||
|
context: bpy.types.Context, convertedObjects: dict[str, Object]
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
):
|
||||||
|
bpy.ops.object.select_all(action="DESELECT")
|
||||||
|
|
||||||
active = None
|
active = None
|
||||||
for obj in convertedObjects.values():
|
for obj in convertedObjects.values():
|
||||||
if obj.type != 'MESH':
|
if obj.type != "MESH":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
obj.select_set(True, view_layer=context.scene.view_layers[0])
|
obj.select_set(True, view_layer=context.scene.view_layers[0])
|
||||||
active = obj
|
active = obj
|
||||||
|
|
||||||
|
|
||||||
if active == None:
|
if active is None:
|
||||||
return
|
return
|
||||||
context.view_layer.objects.active = active
|
context.view_layer.objects.active = active
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
bpy.ops.mesh.select_all(action='SELECT')
|
bpy.ops.mesh.select_all(action="SELECT")
|
||||||
bpy.ops.mesh.dissolve_limited(angle_limit=radians(0.1))
|
bpy.ops.mesh.dissolve_limited(angle_limit=radians(0.1))
|
||||||
|
|
||||||
# Reset state to previous (not quite sure if this is 100% necessary)
|
# Reset state to previous (not quite sure if this is 100% necessary)
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
bpy.ops.object.select_all(action="DESELECT")
|
||||||
bpy.context.view_layer.objects.active = None # type: ignore
|
bpy.context.view_layer.objects.active = None # type: ignore
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.receive(context)
|
self.receive(context)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def receive(self, context: Context) -> None:
|
def receive(self, context: Context) -> None:
|
||||||
bpy.context.view_layer.objects.active = None # type: ignore
|
bpy.context.view_layer.objects.active = None # type: ignore
|
||||||
|
|
||||||
speckle = get_speckle(context)
|
speckle = get_speckle(context)
|
||||||
|
|
||||||
(user, stream, branch, commit) = speckle.validate_commit_selection()
|
(user, stream, branch, commit) = speckle.validate_commit_selection()
|
||||||
|
|
||||||
client = speckle_clients[int(speckle.active_user)]
|
client = speckle_clients[int(speckle.active_user)]
|
||||||
|
|
||||||
transport = ServerTransport(stream.id, client)
|
transport = ServerTransport(stream.id, client)
|
||||||
|
|
||||||
# Fetch commit data
|
# Fetch commit data
|
||||||
commit_object = operations.receive(commit.referenced_object, transport)
|
commit_object = operations.receive(commit.referenced_object, transport)
|
||||||
client.commit.received(
|
client.commit.received(
|
||||||
@@ -158,20 +174,24 @@ class ReceiveStreamObjects(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
metrics.RECEIVE,
|
metrics.RECEIVE,
|
||||||
getattr(transport, "account", None),
|
getattr(transport, "account", None),
|
||||||
custom_props={
|
custom_props={
|
||||||
"sourceHostApp": host_applications.get_host_app_from_string(commit.source_application).slug,
|
"sourceHostApp": host_applications.get_host_app_from_string(
|
||||||
|
commit.source_application
|
||||||
|
).slug,
|
||||||
"sourceHostAppVersion": commit.source_application,
|
"sourceHostAppVersion": commit.source_application,
|
||||||
"isMultiplayer": commit.author_id != user.id,
|
"isMultiplayer": commit.author_id != user.id,
|
||||||
#"connector_version": "unknown", #TODO
|
"workspace_id": get_project_workspace_id(client, stream.id)
|
||||||
|
# "connector_version": "unknown", #TODO
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Convert received data
|
# Convert received data
|
||||||
context.window_manager.progress_begin(0, commit_object.totalChildrenCount or 1)
|
context.window_manager.progress_begin(0, commit_object.totalChildrenCount or 1)
|
||||||
|
|
||||||
set_convert_instances_as(self.receive_instances_as) #HACK: we need a better way to pass settings down to the converter
|
set_convert_instances_as(
|
||||||
|
self.receive_instances_as
|
||||||
|
) # HACK: we need a better way to pass settings down to the converter
|
||||||
|
|
||||||
traversalFunc = get_default_traversal_func(can_convert_to_native)
|
traversalFunc = get_default_traversal_func(can_convert_to_native)
|
||||||
converted_objects: Dict[str, Union[Object, Collection]] = {}
|
converted_objects: Dict[str, Union[Object, Collection]] = {}
|
||||||
@@ -189,10 +209,9 @@ class ReceiveStreamObjects(bpy.types.Operator):
|
|||||||
|
|
||||||
# ensure commit object has a name if not already
|
# ensure commit object has a name if not already
|
||||||
if not commit_object.name:
|
if not commit_object.name:
|
||||||
commit_object.name = f"{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):
|
for item in traversalFunc.traverse(commit_object):
|
||||||
|
|
||||||
current: Base = item.current
|
current: Base = item.current
|
||||||
|
|
||||||
if can_convert_to_native(current) or isinstance(current, SCollection):
|
if can_convert_to_native(current) or isinstance(current, SCollection):
|
||||||
@@ -200,49 +219,64 @@ class ReceiveStreamObjects(bpy.types.Operator):
|
|||||||
if not current or not current.id:
|
if not current or not current.id:
|
||||||
raise Exception(f"{current} was an invalid Speckle object")
|
raise Exception(f"{current} was an invalid Speckle object")
|
||||||
|
|
||||||
#Convert the object!
|
# Convert the object!
|
||||||
converted_data_type: str
|
converted_data_type: str
|
||||||
converted: Union[Object, Collection, None]
|
converted: Union[Object, Collection, None]
|
||||||
if isinstance(current, SCollection):
|
if isinstance(current, SCollection):
|
||||||
if(current.collectionType == "Scene Collection"): raise ConversionSkippedException()
|
if current.collectionType == "Scene Collection":
|
||||||
|
raise ConversionSkippedException()
|
||||||
converted = collection_to_native(current)
|
converted = collection_to_native(current)
|
||||||
converted_data_type = "COLLECTION"
|
converted_data_type = "COLLECTION"
|
||||||
else:
|
else:
|
||||||
converted = convert_to_native(current)
|
converted = convert_to_native(current)
|
||||||
converted_data_type = "COLLECTION_INSTANCE" if converted.instance_collection else str(converted.type)
|
converted_data_type = (
|
||||||
|
"COLLECTION_INSTANCE"
|
||||||
#Run the user specified callback function (AKA receive script)
|
if converted.instance_collection
|
||||||
|
else str(converted.type)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Run the user specified callback function (AKA receive script)
|
||||||
if object_converted_callback:
|
if object_converted_callback:
|
||||||
converted = object_converted_callback(context, converted, current)
|
converted = object_converted_callback(
|
||||||
|
context, converted, current
|
||||||
|
)
|
||||||
|
|
||||||
if converted is None:
|
if converted is None:
|
||||||
raise Exception("Conversion returned None")
|
raise Exception("Conversion returned None")
|
||||||
|
|
||||||
converted_objects[current.id] = converted
|
converted_objects[current.id] = converted
|
||||||
|
|
||||||
add_to_hierarchy(converted, item, converted_objects, True)
|
add_to_hierarchy(converted, item, converted_objects, True)
|
||||||
|
|
||||||
_report(f"Successfully converted {type(current).__name__} {current.id} as '{converted_data_type}'")
|
_report(
|
||||||
|
f"Successfully converted {type(current).__name__} {current.id} as '{converted_data_type}'"
|
||||||
|
)
|
||||||
except ConversionSkippedException as ex:
|
except ConversionSkippedException as ex:
|
||||||
_report(f"Skipped converting {type(current).__name__} {current.id}: {ex}")
|
_report(
|
||||||
|
f"Skipped converting {type(current).__name__} {current.id}: {ex}"
|
||||||
|
)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
_report(f"Failed to converted {type(current).__name__} {current.id}: {ex}")
|
_report(
|
||||||
|
f"Failed to converted {type(current).__name__} {current.id}: {ex}"
|
||||||
|
)
|
||||||
|
|
||||||
converted_count += 1
|
converted_count += 1
|
||||||
context.window_manager.progress_update(converted_count) #NOTE: We don't expect to ever reach 100% since not every object will be traversed
|
context.window_manager.progress_update(
|
||||||
|
converted_count
|
||||||
|
) # NOTE: We don't expect to ever reach 100% since not every object will be traversed
|
||||||
|
|
||||||
context.window_manager.progress_end()
|
context.window_manager.progress_end()
|
||||||
|
|
||||||
if self.clean_meshes:
|
if self.clean_meshes:
|
||||||
objects = {k: v for k, v in converted_objects.items() if isinstance(v, Object)}
|
objects = {
|
||||||
|
k: v for k, v in converted_objects.items() if isinstance(v, Object)
|
||||||
|
}
|
||||||
self.clean_converted_meshes(context, objects)
|
self.clean_converted_meshes(context, objects)
|
||||||
|
|
||||||
if on_complete_callback:
|
if on_complete_callback:
|
||||||
on_complete_callback(context, converted_objects)
|
on_complete_callback(context, converted_objects)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SendStreamObjects(bpy.types.Operator):
|
class SendStreamObjects(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
Send selected objects to selected model
|
Send selected objects to selected model
|
||||||
@@ -253,11 +287,11 @@ class SendStreamObjects(bpy.types.Operator):
|
|||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Send selected objects to selected model"
|
bl_description = "Send selected objects to selected model"
|
||||||
|
|
||||||
apply_modifiers: BoolProperty(name="Apply modifiers", default=True) # type: ignore
|
apply_modifiers: BoolProperty(name="Apply modifiers", default=True) # type: ignore
|
||||||
commit_message: StringProperty(
|
commit_message: StringProperty(
|
||||||
name="Message",
|
name="Message",
|
||||||
default="Sent elements from Blender.",
|
default="Sent elements from Blender.",
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
@@ -271,7 +305,7 @@ class SendStreamObjects(bpy.types.Operator):
|
|||||||
if len(speckle.users) <= 0:
|
if len(speckle.users) <= 0:
|
||||||
_report("No user accounts")
|
_report("No user accounts")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
N = len(context.selected_objects)
|
N = len(context.selected_objects)
|
||||||
if N == 1:
|
if N == 1:
|
||||||
self.commit_message = f"Sent {N} element from Blender."
|
self.commit_message = f"Sent {N} element from Blender."
|
||||||
@@ -279,13 +313,11 @@ class SendStreamObjects(bpy.types.Operator):
|
|||||||
self.commit_message = f"Sent {N} elements from Blender."
|
self.commit_message = f"Sent {N} elements from Blender."
|
||||||
return wm.invoke_props_dialog(self)
|
return wm.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.send(context)
|
self.send(context)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def send(self, context: Context) -> None:
|
def send(self, context: Context) -> None:
|
||||||
|
|
||||||
selected = context.selected_objects
|
selected = context.selected_objects
|
||||||
if len(selected) < 1:
|
if len(selected) < 1:
|
||||||
raise Exception("No objects are selected, sending canceled")
|
raise Exception("No objects are selected, sending canceled")
|
||||||
@@ -304,12 +336,14 @@ class SendStreamObjects(bpy.types.Operator):
|
|||||||
if speckle.send_script in bpy.data.texts:
|
if speckle.send_script in bpy.data.texts:
|
||||||
mod = bpy.data.texts[speckle.send_script].as_module()
|
mod = bpy.data.texts[speckle.send_script].as_module()
|
||||||
if hasattr(mod, "execute"):
|
if hasattr(mod, "execute"):
|
||||||
func = mod.execute #type: ignore
|
func = mod.execute # type: ignore
|
||||||
|
|
||||||
num_converted = 0
|
num_converted = 0
|
||||||
context.window_manager.progress_begin(0, max(len(selected), 1))
|
context.window_manager.progress_begin(0, max(len(selected), 1))
|
||||||
|
|
||||||
depsgraph = bpy.context.evaluated_depsgraph_get() if self.apply_modifiers else None
|
depsgraph = (
|
||||||
|
bpy.context.evaluated_depsgraph_get() if self.apply_modifiers else None
|
||||||
|
)
|
||||||
|
|
||||||
commit_builder = BlenderCommitObjectBuilder()
|
commit_builder = BlenderCommitObjectBuilder()
|
||||||
for obj in selected:
|
for obj in selected:
|
||||||
@@ -319,27 +353,26 @@ class SendStreamObjects(bpy.types.Operator):
|
|||||||
if func:
|
if func:
|
||||||
new_object = func(context.scene, obj)
|
new_object = func(context.scene, obj)
|
||||||
|
|
||||||
if (new_object is None):
|
if new_object is None:
|
||||||
raise ConversionSkippedException(f"Script '{func.__module__}' returned None.")
|
raise ConversionSkippedException(
|
||||||
|
f"Script '{func.__module__}' returned None."
|
||||||
|
)
|
||||||
|
|
||||||
converted = convert_to_speckle(
|
converted = convert_to_speckle(obj, units_scale, units, depsgraph)
|
||||||
obj,
|
|
||||||
units_scale,
|
|
||||||
units,
|
|
||||||
depsgraph
|
|
||||||
)
|
|
||||||
|
|
||||||
if not converted:
|
if not converted:
|
||||||
raise Exception("Converter returned None")
|
raise Exception("Converter returned None")
|
||||||
|
|
||||||
commit_builder.include_object(converted, obj)
|
commit_builder.include_object(converted, obj)
|
||||||
|
|
||||||
_report(f"Successfully converted '{obj.name_full}' as '{converted.speckle_type}'")
|
_report(
|
||||||
|
f"Successfully converted '{obj.name_full}' as '{converted.speckle_type}'"
|
||||||
|
)
|
||||||
except ConversionSkippedException as ex:
|
except ConversionSkippedException as ex:
|
||||||
_report(f"Skipped converting '{obj.name_full}': '{ex}'")
|
_report(f"Skipped converting '{obj.name_full}': '{ex}'")
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
_report(f"Failed to converted '{obj.name_full}': '{ex}'")
|
_report(f"Failed to converted '{obj.name_full}': '{ex}'")
|
||||||
|
|
||||||
num_converted += 1
|
num_converted += 1
|
||||||
context.window_manager.progress_update(num_converted)
|
context.window_manager.progress_update(num_converted)
|
||||||
|
|
||||||
@@ -350,14 +383,15 @@ class SendStreamObjects(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
metrics.SEND,
|
metrics.SEND,
|
||||||
client.account,
|
client.account,
|
||||||
custom_props={
|
custom_props={
|
||||||
"branches": len(stream.branches),
|
"branches": len(stream.branches),
|
||||||
#"collaborators": 0, #TODO:
|
# "collaborators": 0, #TODO:
|
||||||
"isMain": branch.name == "main",
|
"isMain": branch.name == "main",
|
||||||
|
"workspace_id": get_project_workspace_id(stream.id),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
_report(f"Sending data to {stream.name}")
|
_report(f"Sending data to {stream.name}")
|
||||||
transport = ServerTransport(stream.id, client)
|
transport = ServerTransport(stream.id, client)
|
||||||
OBJECT_ID = operations.send(
|
OBJECT_ID = operations.send(
|
||||||
@@ -374,7 +408,9 @@ class SendStreamObjects(bpy.types.Operator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if client.account.serverInfo.frontend2:
|
if client.account.serverInfo.frontend2:
|
||||||
sent_url = f"{user.server_url}/projects/{stream.id}/models/{branch.id}@{COMMIT_ID}"
|
sent_url = (
|
||||||
|
f"{user.server_url}/projects/{stream.id}/models/{branch.id}@{COMMIT_ID}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
sent_url = f"{user.server_url}/streams/{stream.id}/commits/{COMMIT_ID}"
|
sent_url = f"{user.server_url}/streams/{stream.id}/commits/{COMMIT_ID}"
|
||||||
|
|
||||||
@@ -385,14 +421,13 @@ class SendStreamObjects(bpy.types.Operator):
|
|||||||
selection_state.selected_stream_id = stream.id
|
selection_state.selected_stream_id = stream.id
|
||||||
selection_state.selected_user_id = user.id
|
selection_state.selected_user_id = user.id
|
||||||
|
|
||||||
bpy.ops.speckle.load_user_streams() # refresh loaded commits
|
bpy.ops.speckle.load_user_streams() # refresh loaded commits
|
||||||
context.view_layer.update()
|
context.view_layer.update()
|
||||||
|
|
||||||
if context.area:
|
if context.area:
|
||||||
context.area.tag_redraw()
|
context.area.tag_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ViewStreamDataApi(bpy.types.Operator):
|
class ViewStreamDataApi(bpy.types.Operator):
|
||||||
bl_idname = "speckle.view_stream_data_api"
|
bl_idname = "speckle.view_stream_data_api"
|
||||||
bl_label = "Open Model in Web"
|
bl_label = "Open Model in Web"
|
||||||
@@ -409,21 +444,18 @@ class ViewStreamDataApi(bpy.types.Operator):
|
|||||||
url = self._get_url_from_selection(speckle)
|
url = self._get_url_from_selection(speckle)
|
||||||
|
|
||||||
_report(f"Opening {url} in web browser")
|
_report(f"Opening {url} in web browser")
|
||||||
|
|
||||||
if not webbrowser.open(url, new=2):
|
if not webbrowser.open(url, new=2):
|
||||||
raise Exception(f"Failed to open model in browser ({url})")
|
raise Exception(f"Failed to open model in browser ({url})")
|
||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
None,
|
None,
|
||||||
custom_props={
|
custom_props={"name": "view_stream_data_api"},
|
||||||
"name": "view_stream_data_api"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_url_from_selection(speckleScene : SpeckleSceneSettings) -> str:
|
def _get_url_from_selection(speckleScene: SpeckleSceneSettings) -> str:
|
||||||
|
|
||||||
client = speckle_clients[int(speckleScene.active_user)]
|
client = speckle_clients[int(speckleScene.active_user)]
|
||||||
(user, stream) = speckleScene.validate_stream_selection()
|
(user, stream) = speckleScene.validate_stream_selection()
|
||||||
branch = stream.get_active_branch()
|
branch = stream.get_active_branch()
|
||||||
@@ -443,7 +475,8 @@ class ViewStreamDataApi(bpy.types.Operator):
|
|||||||
server_url += f"branches/{branch.name}"
|
server_url += f"branches/{branch.name}"
|
||||||
|
|
||||||
return server_url
|
return server_url
|
||||||
|
|
||||||
|
|
||||||
class AddStreamFromURL(bpy.types.Operator):
|
class AddStreamFromURL(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
Add / select an existing project by providing its URL
|
Add / select an existing project by providing its URL
|
||||||
@@ -453,9 +486,7 @@ class AddStreamFromURL(bpy.types.Operator):
|
|||||||
bl_label = "Add Project From URL"
|
bl_label = "Add Project From URL"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Add / select an existing project by providing its URL"
|
bl_description = "Add / select an existing project by providing its URL"
|
||||||
stream_url: StringProperty(
|
stream_url: StringProperty(name="Project URL", default="") # type: ignore
|
||||||
name="Project URL", default=""
|
|
||||||
) # type: ignore
|
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
@@ -475,21 +506,28 @@ class AddStreamFromURL(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_or_add_stream(user : SpeckleUserObject, stream : Stream) -> Tuple[int, SpeckleStreamObject]:
|
def _get_or_add_stream(
|
||||||
|
user: SpeckleUserObject, stream: Stream
|
||||||
|
) -> Tuple[int, SpeckleStreamObject]:
|
||||||
index, b_stream = next(
|
index, b_stream = next(
|
||||||
((i, cast(SpeckleStreamObject, s)) for i, s in enumerate(user.streams) if s.id == stream.id),
|
(
|
||||||
|
(i, cast(SpeckleStreamObject, s))
|
||||||
|
for i, s in enumerate(user.streams)
|
||||||
|
if s.id == stream.id
|
||||||
|
),
|
||||||
(None, None),
|
(None, None),
|
||||||
)
|
)
|
||||||
|
|
||||||
if index is not None:
|
if index is not None:
|
||||||
assert(b_stream)
|
assert b_stream
|
||||||
return (index, b_stream)
|
return (index, b_stream)
|
||||||
|
|
||||||
add_user_stream(user, stream)
|
add_user_stream(user, stream)
|
||||||
return next(
|
return next(
|
||||||
(i, cast(SpeckleStreamObject, s)) for i, s in enumerate(user.streams) if s.id == stream.id
|
(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:
|
def add_stream_from_url(self, context: Context) -> None:
|
||||||
speckle = get_speckle(context)
|
speckle = get_speckle(context)
|
||||||
@@ -500,15 +538,23 @@ class AddStreamFromURL(bpy.types.Operator):
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
if user_index is None:
|
if user_index is None:
|
||||||
raise Exception(f"No user account credentials for {wrapper.host}, have you added your account in Manager?")
|
raise Exception(
|
||||||
|
f"No user account credentials for {wrapper.host}, have you added your account in Manager?"
|
||||||
|
)
|
||||||
|
|
||||||
speckle.active_user = str(user_index)
|
speckle.active_user = str(user_index)
|
||||||
user = cast(SpeckleUserObject, speckle.users[user_index])
|
user = cast(SpeckleUserObject, speckle.users[user_index])
|
||||||
|
|
||||||
client = speckle_clients[user_index]
|
client = speckle_clients[user_index]
|
||||||
stream = client.stream.get(wrapper.stream_id, branch_limit=LoadUserStreams.branch_limit, commit_limit=LoadUserStreams.commits_limit)
|
stream = client.stream.get(
|
||||||
|
wrapper.stream_id,
|
||||||
|
branch_limit=LoadUserStreams.branch_limit,
|
||||||
|
commit_limit=LoadUserStreams.commits_limit,
|
||||||
|
)
|
||||||
if not isinstance(stream, Stream):
|
if not isinstance(stream, Stream):
|
||||||
raise SpeckleException(f"Could not get the requested project {wrapper.stream_id}")
|
raise SpeckleException(
|
||||||
|
f"Could not get the requested project {wrapper.stream_id}"
|
||||||
|
)
|
||||||
|
|
||||||
(index, b_stream) = self._get_or_add_stream(user, stream)
|
(index, b_stream) = self._get_or_add_stream(user, stream)
|
||||||
user.active_stream = index
|
user.active_stream = index
|
||||||
@@ -533,13 +579,11 @@ class AddStreamFromURL(bpy.types.Operator):
|
|||||||
|
|
||||||
if context.area:
|
if context.area:
|
||||||
context.area.tag_redraw()
|
context.area.tag_redraw()
|
||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
client.account,
|
client.account,
|
||||||
custom_props={
|
custom_props={"name": "add_stream_from_url"},
|
||||||
"name": "add_stream_from_url"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -553,10 +597,10 @@ class CreateStream(bpy.types.Operator):
|
|||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
bl_description = "Create a new Speckle project using the selected user account"
|
bl_description = "Create a new Speckle project using the selected user account"
|
||||||
|
|
||||||
stream_name: StringProperty(name="Project name") # type: ignore
|
stream_name: StringProperty(name="Project name") # type: ignore
|
||||||
stream_description: StringProperty(
|
stream_description: StringProperty(
|
||||||
name="Project description", default="My new project"
|
name="Project description", default="My new project"
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
@@ -575,7 +619,7 @@ class CreateStream(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.create_stream(context)
|
self.create_stream(context)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def create_stream(self, context: Context) -> None:
|
def create_stream(self, context: Context) -> None:
|
||||||
speckle = get_speckle(context)
|
speckle = get_speckle(context)
|
||||||
|
|
||||||
@@ -584,9 +628,7 @@ class CreateStream(bpy.types.Operator):
|
|||||||
client = speckle_clients[int(speckle.active_user)]
|
client = speckle_clients[int(speckle.active_user)]
|
||||||
|
|
||||||
client.stream.create(
|
client.stream.create(
|
||||||
name=self.stream_name,
|
name=self.stream_name, description=self.stream_description, is_public=True
|
||||||
description=self.stream_description,
|
|
||||||
is_public=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
bpy.ops.speckle.load_user_streams()
|
bpy.ops.speckle.load_user_streams()
|
||||||
@@ -597,13 +639,11 @@ class CreateStream(bpy.types.Operator):
|
|||||||
|
|
||||||
if context.area:
|
if context.area:
|
||||||
context.area.tag_redraw()
|
context.area.tag_redraw()
|
||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
client.account,
|
client.account,
|
||||||
custom_props={
|
custom_props={"name": "create_stream"},
|
||||||
"name": "create_stream"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -622,9 +662,9 @@ class DeleteStream(bpy.types.Operator):
|
|||||||
name="Confirm",
|
name="Confirm",
|
||||||
description="⚠ This action will delete your entire stream permanently ⚠",
|
description="⚠ This action will delete your entire stream permanently ⚠",
|
||||||
default=False,
|
default=False,
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
delete_collection: BoolProperty(name="Delete collection", default=False) # type: ignore
|
delete_collection: BoolProperty(name="Delete collection", default=False) # type: ignore
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
@@ -673,12 +713,11 @@ class DeleteStream(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
client.account,
|
client.account,
|
||||||
custom_props={
|
custom_props={"name": "delete_stream"},
|
||||||
"name": "delete_stream"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
class SelectOrphanObjects(bpy.types.Operator):
|
class SelectOrphanObjects(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
@@ -694,7 +733,6 @@ class SelectOrphanObjects(bpy.types.Operator):
|
|||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
for o in context.scene.objects:
|
for o in context.scene.objects:
|
||||||
if (
|
if (
|
||||||
o.speckle.stream_id
|
o.speckle.stream_id
|
||||||
@@ -705,14 +743,13 @@ class SelectOrphanObjects(bpy.types.Operator):
|
|||||||
o.select = False
|
o.select = False
|
||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
custom_props={
|
custom_props={"name": "SelectOrphanObjects"},
|
||||||
"name": "SelectOrphanObjects"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class CopyStreamId(bpy.types.Operator):
|
class CopyStreamId(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
Copy the selected project id to clipboard
|
Copy the selected project id to clipboard
|
||||||
@@ -726,7 +763,7 @@ class CopyStreamId(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.copy_stream_id(context)
|
self.copy_stream_id(context)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def copy_stream_id(self, context) -> None:
|
def copy_stream_id(self, context) -> None:
|
||||||
speckle = get_speckle(context)
|
speckle = get_speckle(context)
|
||||||
|
|
||||||
@@ -735,11 +772,10 @@ class CopyStreamId(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
custom_props={
|
custom_props={"name": "copy_stream_id"},
|
||||||
"name": "copy_stream_id"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CopyCommitId(bpy.types.Operator):
|
class CopyCommitId(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
Copy the selected version id to clipboard
|
Copy the selected version id to clipboard
|
||||||
@@ -754,7 +790,6 @@ class CopyCommitId(bpy.types.Operator):
|
|||||||
self.copy_commit_id(context)
|
self.copy_commit_id(context)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
def copy_commit_id(self, context) -> None:
|
def copy_commit_id(self, context) -> None:
|
||||||
speckle = get_speckle(context)
|
speckle = get_speckle(context)
|
||||||
|
|
||||||
@@ -763,13 +798,10 @@ class CopyCommitId(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
custom_props={
|
custom_props={"name": "copy_commit_id"},
|
||||||
"name": "copy_commit_id"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CopyModelId(bpy.types.Operator):
|
class CopyModelId(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
Copy model id to clipboard
|
Copy model id to clipboard
|
||||||
@@ -784,7 +816,6 @@ class CopyModelId(bpy.types.Operator):
|
|||||||
self.copy_model_id(context)
|
self.copy_model_id(context)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
def copy_model_id(self, context) -> None:
|
def copy_model_id(self, context) -> None:
|
||||||
speckle = get_speckle(context)
|
speckle = get_speckle(context)
|
||||||
|
|
||||||
@@ -794,11 +825,10 @@ class CopyModelId(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
custom_props={
|
custom_props={"name": "copy_branch_id"},
|
||||||
"name": "copy_branch_id"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
class CopyBranchName(bpy.types.Operator):
|
class CopyBranchName(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
@@ -814,7 +844,6 @@ class CopyBranchName(bpy.types.Operator):
|
|||||||
self.copy_branch_id(context)
|
self.copy_branch_id(context)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
def copy_branch_id(self, context) -> None:
|
def copy_branch_id(self, context) -> None:
|
||||||
speckle = get_speckle(context)
|
speckle = get_speckle(context)
|
||||||
|
|
||||||
@@ -824,11 +853,10 @@ class CopyBranchName(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
custom_props={
|
custom_props={"name": "copy_branch_id"},
|
||||||
"name": "copy_branch_id"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
class SelectOrphanObjects(bpy.types.Operator):
|
class SelectOrphanObjects(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
@@ -841,10 +869,9 @@ class SelectOrphanObjects(bpy.types.Operator):
|
|||||||
bl_description = "Select Speckle objects that don't belong to any stream"
|
bl_description = "Select Speckle objects that don't belong to any stream"
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
pass
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
for o in context.scene.objects:
|
for o in context.scene.objects:
|
||||||
if (
|
if (
|
||||||
o.speckle.stream_id
|
o.speckle.stream_id
|
||||||
@@ -855,10 +882,8 @@ class SelectOrphanObjects(bpy.types.Operator):
|
|||||||
o.select = False
|
o.select = False
|
||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
custom_props={
|
custom_props={"name": "SelectOrphanObjects"},
|
||||||
"name": "SelectOrphanObjects"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
"""
|
"""
|
||||||
User account operators
|
User account operators
|
||||||
"""
|
"""
|
||||||
from typing import List, cast
|
from typing import cast
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Context
|
from bpy.types import Context
|
||||||
from bpy_speckle.functions import _report
|
|
||||||
from bpy_speckle.clients import speckle_clients
|
|
||||||
from bpy_speckle.properties.scene import SpeckleBranchObject, SpeckleCommitObject, SpeckleSceneSettings, SpeckleStreamObject, SpeckleUserObject, get_speckle, restore_selection_state
|
|
||||||
from specklepy.core.api.client import SpeckleClient
|
from specklepy.core.api.client import SpeckleClient
|
||||||
|
from specklepy.core.api.credentials import Account, get_local_accounts
|
||||||
from specklepy.core.api.models import Stream
|
from specklepy.core.api.models import Stream
|
||||||
from specklepy.core.api.credentials import get_local_accounts, Account
|
|
||||||
from specklepy.logging import metrics
|
from specklepy.logging import metrics
|
||||||
|
|
||||||
|
from bpy_speckle.clients import speckle_clients
|
||||||
|
from bpy_speckle.functions import _report
|
||||||
|
from bpy_speckle.properties.scene import (SpeckleSceneSettings,
|
||||||
|
SpeckleStreamObject,
|
||||||
|
SpeckleUserObject, get_speckle,
|
||||||
|
restore_selection_state)
|
||||||
|
|
||||||
|
|
||||||
class ResetUsers(bpy.types.Operator):
|
class ResetUsers(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
Reset loaded users
|
Reset loaded users
|
||||||
@@ -26,10 +32,8 @@ class ResetUsers(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
None,
|
None,
|
||||||
custom_props={
|
custom_props={"name": "ResetUsers"},
|
||||||
"name": "ResetUsers"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
bpy.context.view_layer.update()
|
bpy.context.view_layer.update()
|
||||||
@@ -44,6 +48,7 @@ class ResetUsers(bpy.types.Operator):
|
|||||||
speckle.users.clear()
|
speckle.users.clear()
|
||||||
speckle_clients.clear()
|
speckle_clients.clear()
|
||||||
|
|
||||||
|
|
||||||
class LoadUsers(bpy.types.Operator):
|
class LoadUsers(bpy.types.Operator):
|
||||||
"""
|
"""
|
||||||
Loads all user accounts from the credentials in the local database.
|
Loads all user accounts from the credentials in the local database.
|
||||||
@@ -56,7 +61,6 @@ class LoadUsers(bpy.types.Operator):
|
|||||||
bl_description = "Loads all user accounts from the credentials in the local database.\nSee docs to add accounts via Manager"
|
bl_description = "Loads all user accounts from the credentials in the local database.\nSee docs to add accounts via Manager"
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
_report("Loading users...")
|
_report("Loading users...")
|
||||||
|
|
||||||
speckle = get_speckle(context)
|
speckle = get_speckle(context)
|
||||||
@@ -69,20 +73,24 @@ class LoadUsers(bpy.types.Operator):
|
|||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
None,
|
None,
|
||||||
custom_props={
|
custom_props={
|
||||||
"name": "LoadUsers",
|
"name": "LoadUsers",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if not profiles:
|
if not profiles:
|
||||||
raise Exception("Zero accounts were found, please add one through Speckle Manager or a local account")
|
raise Exception(
|
||||||
|
"Zero accounts were found, please add one through Speckle Manager or a local account"
|
||||||
|
)
|
||||||
|
|
||||||
for profile in profiles:
|
for profile in profiles:
|
||||||
try:
|
try:
|
||||||
add_user_account(profile, speckle)
|
add_user_account(profile, speckle)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
_report(f"Failed to authenticate user account {profile.userInfo.email} with server {profile.serverInfo.url}: {ex}")
|
_report(
|
||||||
|
f"Failed to authenticate user account {profile.userInfo.email} with server {profile.serverInfo.url}: {ex}"
|
||||||
|
)
|
||||||
users_list.remove(len(users_list) - 1)
|
users_list.remove(len(users_list) - 1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -100,11 +108,16 @@ class LoadUsers(bpy.types.Operator):
|
|||||||
context.area.tag_redraw()
|
context.area.tag_redraw()
|
||||||
|
|
||||||
if not users_list:
|
if not users_list:
|
||||||
raise Exception("Zero valid user accounts were found, please ensure account is valid and the server is running")
|
raise Exception(
|
||||||
|
"Zero valid user accounts were found, please ensure account is valid and the server is running"
|
||||||
|
)
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def add_user_account(account: Account, speckle: SpeckleSceneSettings) -> SpeckleUserObject:
|
|
||||||
|
def add_user_account(
|
||||||
|
account: Account, speckle: SpeckleSceneSettings
|
||||||
|
) -> SpeckleUserObject:
|
||||||
"""Creates a new new SpeckleUserObject for the provided user Account and adds it to the SpeckleSceneSettings"""
|
"""Creates a new new SpeckleUserObject for the provided user Account and adds it to the SpeckleSceneSettings"""
|
||||||
users_list = speckle.users
|
users_list = speckle.users
|
||||||
|
|
||||||
@@ -118,7 +131,7 @@ def add_user_account(account: Account, speckle: SpeckleSceneSettings) -> Speckle
|
|||||||
user.email = account.userInfo.email
|
user.email = account.userInfo.email
|
||||||
user.company = account.userInfo.company or ""
|
user.company = account.userInfo.company or ""
|
||||||
|
|
||||||
assert(URL)
|
assert URL
|
||||||
client = SpeckleClient(
|
client = SpeckleClient(
|
||||||
host=URL,
|
host=URL,
|
||||||
use_ssl="https" in URL,
|
use_ssl="https" in URL,
|
||||||
@@ -136,7 +149,7 @@ def add_user_stream(user: SpeckleUserObject, stream: Stream):
|
|||||||
s.description = stream.description
|
s.description = stream.description
|
||||||
|
|
||||||
_report(f"Adding stream {s.id} - {s.name}")
|
_report(f"Adding stream {s.id} - {s.name}")
|
||||||
|
|
||||||
if stream.branches:
|
if stream.branches:
|
||||||
s.load_stream_branches(stream)
|
s.load_stream_branches(stream)
|
||||||
|
|
||||||
@@ -159,7 +172,6 @@ class LoadUserStreams(bpy.types.Operator):
|
|||||||
self.load_user_stream(context)
|
self.load_user_stream(context)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
def load_user_stream(self, context: Context) -> None:
|
def load_user_stream(self, context: Context) -> None:
|
||||||
speckle = get_speckle(context)
|
speckle = get_speckle(context)
|
||||||
|
|
||||||
@@ -169,13 +181,12 @@ class LoadUserStreams(bpy.types.Operator):
|
|||||||
try:
|
try:
|
||||||
streams = client.stream.list(stream_limit=self.stream_limit)
|
streams = client.stream.list(stream_limit=self.stream_limit)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise Exception(f"Failed to retrieve projects") from ex
|
raise Exception("Failed to retrieve projects") from ex
|
||||||
|
|
||||||
if not streams:
|
if not streams:
|
||||||
_report("Zero projects found")
|
_report("Zero projects found")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
active_stream_id = None
|
active_stream_id = None
|
||||||
if active_stream := user.get_active_stream():
|
if active_stream := user.get_active_stream():
|
||||||
active_stream_id = active_stream.id
|
active_stream_id = active_stream.id
|
||||||
@@ -185,10 +196,12 @@ class LoadUserStreams(bpy.types.Operator):
|
|||||||
user.streams.clear()
|
user.streams.clear()
|
||||||
|
|
||||||
for i, s in enumerate(streams):
|
for i, s in enumerate(streams):
|
||||||
assert(s.id)
|
assert s.id
|
||||||
load_branches = s.id == active_stream_id if active_stream_id else i == 0
|
load_branches = s.id == active_stream_id if active_stream_id else i == 0
|
||||||
if load_branches:
|
if load_branches:
|
||||||
sstream = client.stream.get(id=s.id, branch_limit=self.branch_limit, commit_limit=10)
|
sstream = client.stream.get(
|
||||||
|
id=s.id, branch_limit=self.branch_limit, commit_limit=10
|
||||||
|
)
|
||||||
add_user_stream(user, sstream)
|
add_user_stream(user, sstream)
|
||||||
else:
|
else:
|
||||||
add_user_stream(user, s)
|
add_user_stream(user, s)
|
||||||
@@ -199,13 +212,9 @@ class LoadUserStreams(bpy.types.Operator):
|
|||||||
|
|
||||||
if context.area:
|
if context.area:
|
||||||
context.area.tag_redraw()
|
context.area.tag_redraw()
|
||||||
|
|
||||||
metrics.track(
|
metrics.track(
|
||||||
"Connector Action",
|
"Connector Action",
|
||||||
client.account,
|
client.account,
|
||||||
custom_props={
|
custom_props={"name": "LoadUserStreams"},
|
||||||
"name": "LoadUserStreams"
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
from .scene import (
|
|
||||||
SpeckleSceneSettings,
|
|
||||||
SpeckleSceneObject,
|
|
||||||
SpeckleUserObject,
|
|
||||||
SpeckleStreamObject,
|
|
||||||
SpeckleBranchObject,
|
|
||||||
SpeckleCommitObject,
|
|
||||||
)
|
|
||||||
from .object import SpeckleObjectSettings
|
|
||||||
from .collection import SpeckleCollectionSettings
|
|
||||||
from .addon import SpeckleAddonPreferences
|
from .addon import SpeckleAddonPreferences
|
||||||
|
from .collection import SpeckleCollectionSettings
|
||||||
|
from .object import SpeckleObjectSettings
|
||||||
|
from .scene import (SpeckleBranchObject, SpeckleCommitObject,
|
||||||
|
SpeckleSceneObject, SpeckleSceneSettings,
|
||||||
|
SpeckleStreamObject, SpeckleUserObject)
|
||||||
|
|
||||||
property_classes = [
|
property_classes = [
|
||||||
SpeckleSceneObject,
|
SpeckleSceneObject,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import bpy
|
|||||||
|
|
||||||
|
|
||||||
class SpeckleCollectionSettings(bpy.types.PropertyGroup):
|
class SpeckleCollectionSettings(bpy.types.PropertyGroup):
|
||||||
enabled: bpy.props.BoolProperty(default=False, name="Enabled") # type: ignore
|
enabled: bpy.props.BoolProperty(default=False, name="Enabled") # type: ignore
|
||||||
|
|
||||||
send_or_receive: bpy.props.EnumProperty(
|
send_or_receive: bpy.props.EnumProperty(
|
||||||
name="Mode",
|
name="Mode",
|
||||||
@@ -13,6 +13,6 @@ class SpeckleCollectionSettings(bpy.types.PropertyGroup):
|
|||||||
("send", "Send", "Send data to Speckle server."),
|
("send", "Send", "Send data to Speckle server."),
|
||||||
("receive", "Receive", "Receive data from Speckle server."),
|
("receive", "Receive", "Receive data from Speckle server."),
|
||||||
),
|
),
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
stream_id: bpy.props.StringProperty(default="") # type: ignore
|
stream_id: bpy.props.StringProperty(default="") # type: ignore
|
||||||
name: bpy.props.StringProperty(default="") # type: ignore
|
name: bpy.props.StringProperty(default="") # type: ignore
|
||||||
|
|||||||
@@ -13,6 +13,6 @@ class SpeckleObjectSettings(bpy.types.PropertyGroup):
|
|||||||
("send", "Send", "Send data to Speckle server."),
|
("send", "Send", "Send data to Speckle server."),
|
||||||
("receive", "Receive", "Receive data from Speckle server."),
|
("receive", "Receive", "Receive data from Speckle server."),
|
||||||
),
|
),
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
stream_id: bpy.props.StringProperty(default="") # type: ignore
|
stream_id: bpy.props.StringProperty(default="") # type: ignore
|
||||||
object_id: bpy.props.StringProperty(default="") # type: ignore
|
object_id: bpy.props.StringProperty(default="") # type: ignore
|
||||||
|
|||||||
+137
-90
@@ -1,66 +1,66 @@
|
|||||||
"""
|
"""
|
||||||
Scene properties
|
Scene properties
|
||||||
"""
|
"""
|
||||||
from typing import Iterable, Optional, Tuple, Union, cast
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import bpy
|
from typing import Iterable, Optional, Tuple, Union, cast
|
||||||
from bpy.props import (
|
|
||||||
StringProperty,
|
|
||||||
FloatProperty,
|
|
||||||
CollectionProperty,
|
|
||||||
EnumProperty,
|
|
||||||
IntProperty,
|
|
||||||
)
|
|
||||||
|
|
||||||
from bpy_speckle.clients import speckle_clients
|
import bpy
|
||||||
|
from bpy.props import (CollectionProperty, EnumProperty, FloatProperty,
|
||||||
|
IntProperty, StringProperty)
|
||||||
from specklepy.core.api.models import Stream
|
from specklepy.core.api.models import Stream
|
||||||
|
|
||||||
|
from bpy_speckle.clients import speckle_clients
|
||||||
|
|
||||||
|
|
||||||
class SpeckleSceneObject(bpy.types.PropertyGroup):
|
class SpeckleSceneObject(bpy.types.PropertyGroup):
|
||||||
name: bpy.props.StringProperty(default="") # type: ignore
|
name: bpy.props.StringProperty(default="") # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class SpeckleCommitObject(bpy.types.PropertyGroup):
|
class SpeckleCommitObject(bpy.types.PropertyGroup):
|
||||||
id: StringProperty(default="") # type: ignore
|
id: StringProperty(default="") # type: ignore
|
||||||
message: StringProperty(default="") # type: ignore
|
message: StringProperty(default="") # type: ignore
|
||||||
author_name: StringProperty(default="") # type: ignore
|
author_name: StringProperty(default="") # type: ignore
|
||||||
author_id: StringProperty(default="") # type: ignore
|
author_id: StringProperty(default="") # type: ignore
|
||||||
created_at: StringProperty(default="") # type: ignore
|
created_at: StringProperty(default="") # type: ignore
|
||||||
source_application: StringProperty(default="") # type: ignore
|
source_application: StringProperty(default="") # type: ignore
|
||||||
referenced_object: StringProperty(default="") # type: ignore
|
referenced_object: StringProperty(default="") # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class SpeckleBranchObject(bpy.types.PropertyGroup):
|
class SpeckleBranchObject(bpy.types.PropertyGroup):
|
||||||
def get_commits(self, context):
|
def get_commits(self, context):
|
||||||
if self.commits != None and len(self.commits) > 0:
|
if self.commits is not None and len(self.commits) > 0:
|
||||||
COMMITS = cast(Iterable[SpeckleCommitObject], self.commits)
|
COMMITS = cast(Iterable[SpeckleCommitObject], self.commits)
|
||||||
return [
|
return [
|
||||||
(str(i), commit.id, commit.message, i)
|
(str(i), commit.id, commit.message, i)
|
||||||
for i, commit in enumerate(COMMITS)
|
for i, commit in enumerate(COMMITS)
|
||||||
]
|
]
|
||||||
return [("0", "<none>", "<none>", 0)]
|
return [("0", "<none>", "<none>", 0)]
|
||||||
|
|
||||||
def commit_update_hook(self, context: bpy.types.Context):
|
def commit_update_hook(self, context: bpy.types.Context):
|
||||||
selection_state.selected_commit_id = SelectionState.get_item_id_by_index(self.commits, self.commit)
|
selection_state.selected_commit_id = SelectionState.get_item_id_by_index(
|
||||||
|
self.commits, self.commit
|
||||||
|
)
|
||||||
selection_state.selected_branch_id = self.id
|
selection_state.selected_branch_id = self.id
|
||||||
# print(f"commit_update_hook: {selection_state.selected_commit_id=}, {selection_state.selected_branch_id=}")
|
# print(f"commit_update_hook: {selection_state.selected_commit_id=}, {selection_state.selected_branch_id=}")
|
||||||
|
|
||||||
name: StringProperty(default="main") # type: ignore
|
name: StringProperty(default="main") # type: ignore
|
||||||
id: StringProperty(default="") # type: ignore
|
id: StringProperty(default="") # type: ignore
|
||||||
description: StringProperty(default="") # type: ignore
|
description: StringProperty(default="") # type: ignore
|
||||||
commits: CollectionProperty(type=SpeckleCommitObject) # type: ignore
|
commits: CollectionProperty(type=SpeckleCommitObject) # type: ignore
|
||||||
commit: EnumProperty(
|
commit: EnumProperty(
|
||||||
name="Version",
|
name="Version",
|
||||||
description="Selected model version",
|
description="Selected model version",
|
||||||
items=get_commits,
|
items=get_commits,
|
||||||
update=commit_update_hook,
|
update=commit_update_hook,
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
def get_active_commit(self) -> Optional[SpeckleCommitObject]:
|
def get_active_commit(self) -> Optional[SpeckleCommitObject]:
|
||||||
selected_index = int(self.commit)
|
selected_index = int(self.commit)
|
||||||
if 0 <= selected_index < len(self.commits):
|
if 0 <= selected_index < len(self.commits):
|
||||||
return self.commits[selected_index]
|
return self.commits[selected_index]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class SpeckleStreamObject(bpy.types.PropertyGroup):
|
class SpeckleStreamObject(bpy.types.PropertyGroup):
|
||||||
def load_stream_branches(self, sstream: Stream):
|
def load_stream_branches(self, sstream: Stream):
|
||||||
self.branches.clear()
|
self.branches.clear()
|
||||||
@@ -80,7 +80,11 @@ class SpeckleStreamObject(bpy.types.PropertyGroup):
|
|||||||
commit.message = c.message or ""
|
commit.message = c.message or ""
|
||||||
commit.author_name = c.authorName
|
commit.author_name = c.authorName
|
||||||
commit.author_id = c.authorId
|
commit.author_id = c.authorId
|
||||||
commit.created_at = c.createdAt.strftime("%Y-%m-%d %H:%M:%S.%f%Z") if c.createdAt else ""
|
commit.created_at = (
|
||||||
|
c.createdAt.strftime("%Y-%m-%d %H:%M:%S.%f%Z")
|
||||||
|
if c.createdAt
|
||||||
|
else ""
|
||||||
|
)
|
||||||
commit.source_application = str(c.sourceApplication)
|
commit.source_application = str(c.sourceApplication)
|
||||||
commit.referenced_object = c.referencedObject
|
commit.referenced_object = c.referencedObject
|
||||||
|
|
||||||
@@ -93,57 +97,66 @@ class SpeckleStreamObject(bpy.types.PropertyGroup):
|
|||||||
if branch.name != "globals"
|
if branch.name != "globals"
|
||||||
]
|
]
|
||||||
return [("0", "<none>", "<none>", 0)]
|
return [("0", "<none>", "<none>", 0)]
|
||||||
|
|
||||||
def branch_update_hook(self, context: bpy.types.Context):
|
def branch_update_hook(self, context: bpy.types.Context):
|
||||||
selection_state.selected_branch_id = SelectionState.get_item_id_by_index(self.branches, self.branch)
|
selection_state.selected_branch_id = SelectionState.get_item_id_by_index(
|
||||||
|
self.branches, self.branch
|
||||||
|
)
|
||||||
# print(f"branch_update_hook: {selection_state.selected_branch_id=}, {selection_state.selected_stream_id=}")
|
# print(f"branch_update_hook: {selection_state.selected_branch_id=}, {selection_state.selected_stream_id=}")
|
||||||
|
|
||||||
name: StringProperty(default="") # type: ignore
|
name: StringProperty(default="") # type: ignore
|
||||||
description: StringProperty(default="") # type: ignore
|
description: StringProperty(default="") # type: ignore
|
||||||
id: StringProperty(default="") # type: ignore
|
id: StringProperty(default="") # type: ignore
|
||||||
branches: CollectionProperty(type=SpeckleBranchObject) # type: ignore
|
branches: CollectionProperty(type=SpeckleBranchObject) # type: ignore
|
||||||
branch: EnumProperty(
|
branch: EnumProperty(
|
||||||
name="Model",
|
name="Model",
|
||||||
description="Selected Model",
|
description="Selected Model",
|
||||||
items=get_branches,
|
items=get_branches,
|
||||||
update=branch_update_hook,
|
update=branch_update_hook,
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
def get_active_branch(self) -> Optional[SpeckleBranchObject]:
|
def get_active_branch(self) -> Optional[SpeckleBranchObject]:
|
||||||
selected_index = int(self.branch)
|
selected_index = int(self.branch)
|
||||||
if 0 <= selected_index < len(self.branches):
|
if 0 <= selected_index < len(self.branches):
|
||||||
return self.branches[selected_index]
|
return self.branches[selected_index]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class SpeckleUserObject(bpy.types.PropertyGroup):
|
class SpeckleUserObject(bpy.types.PropertyGroup):
|
||||||
def fetch_stream_branches(self, context: bpy.types.Context, stream: SpeckleStreamObject):
|
def fetch_stream_branches(
|
||||||
|
self, context: bpy.types.Context, stream: SpeckleStreamObject
|
||||||
|
):
|
||||||
speckle = context.scene.speckle
|
speckle = context.scene.speckle
|
||||||
client = speckle_clients[int(speckle.active_user)]
|
client = speckle_clients[int(speckle.active_user)]
|
||||||
sstream = client.stream.get(id=stream.id, branch_limit=100, commit_limit=10) # TODO: refactor magic numbers
|
sstream = client.stream.get(
|
||||||
|
id=stream.id, branch_limit=100, commit_limit=10
|
||||||
|
) # TODO: refactor magic numbers
|
||||||
stream.load_stream_branches(sstream)
|
stream.load_stream_branches(sstream)
|
||||||
|
|
||||||
def stream_update_hook(self, context: bpy.types.Context):
|
def stream_update_hook(self, context: bpy.types.Context):
|
||||||
stream = SelectionState.get_item_by_index(self.streams, self.active_stream)
|
stream = SelectionState.get_item_by_index(self.streams, self.active_stream)
|
||||||
selection_state.selected_stream_id = stream.id
|
selection_state.selected_stream_id = stream.id
|
||||||
# print(f"stream_update_hook: {selection_state.selected_stream_id=}, {selection_state.selected_user_id=}")
|
# print(f"stream_update_hook: {selection_state.selected_stream_id=}, {selection_state.selected_user_id=}")
|
||||||
if len(stream.branches) == 0: # do not reload on selection, same as the old behavior
|
if (
|
||||||
|
len(stream.branches) == 0
|
||||||
|
): # do not reload on selection, same as the old behavior
|
||||||
self.fetch_stream_branches(context, stream)
|
self.fetch_stream_branches(context, stream)
|
||||||
|
|
||||||
server_name: StringProperty(default="SpeckleXYZ") # type: ignore
|
server_name: StringProperty(default="SpeckleXYZ") # type: ignore
|
||||||
server_url: StringProperty(default="https://speckle.xyz") # type: ignore
|
server_url: StringProperty(default="https://speckle.xyz") # type: ignore
|
||||||
id: StringProperty(default="") # type: ignore
|
id: StringProperty(default="") # type: ignore
|
||||||
name: StringProperty(default="Speckle User") # type: ignore
|
name: StringProperty(default="Speckle User") # type: ignore
|
||||||
email: StringProperty(default="user@speckle.xyz") # type: ignore
|
email: StringProperty(default="user@speckle.xyz") # type: ignore
|
||||||
company: StringProperty(default="SpeckleSystems") # type: ignore
|
company: StringProperty(default="SpeckleSystems") # type: ignore
|
||||||
streams: CollectionProperty(type=SpeckleStreamObject) # type: ignore
|
streams: CollectionProperty(type=SpeckleStreamObject) # type: ignore
|
||||||
active_stream: IntProperty(default=0, update=stream_update_hook) # type: ignore
|
active_stream: IntProperty(default=0, update=stream_update_hook) # type: ignore
|
||||||
|
|
||||||
def get_active_stream(self) -> Optional[SpeckleStreamObject]:
|
def get_active_stream(self) -> Optional[SpeckleStreamObject]:
|
||||||
selected_index = int(self.active_stream)
|
selected_index = int(self.active_stream)
|
||||||
if 0 <= selected_index < len(self.streams):
|
if 0 <= selected_index < len(self.streams):
|
||||||
return self.streams[selected_index]
|
return self.streams[selected_index]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class SpeckleSceneSettings(bpy.types.PropertyGroup):
|
class SpeckleSceneSettings(bpy.types.PropertyGroup):
|
||||||
def get_scripts(self, context):
|
def get_scripts(self, context):
|
||||||
@@ -156,9 +169,9 @@ class SpeckleSceneSettings(bpy.types.PropertyGroup):
|
|||||||
name="Available streams",
|
name="Available streams",
|
||||||
description="Available streams associated with user.",
|
description="Available streams associated with user.",
|
||||||
items=[],
|
items=[],
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
users: CollectionProperty(type=SpeckleUserObject) # type: ignore
|
users: CollectionProperty(type=SpeckleUserObject) # type: ignore
|
||||||
|
|
||||||
def get_users(self, context):
|
def get_users(self, context):
|
||||||
USERS = cast(Iterable[SpeckleUserObject], self.users)
|
USERS = cast(Iterable[SpeckleUserObject], self.users)
|
||||||
@@ -168,8 +181,10 @@ class SpeckleSceneSettings(bpy.types.PropertyGroup):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def user_update_hook(self, context):
|
def user_update_hook(self, context):
|
||||||
bpy.ops.speckle.load_user_streams() # type: ignore
|
bpy.ops.speckle.load_user_streams() # type: ignore
|
||||||
selection_state.selected_user_id = SelectionState.get_item_id_by_index(self.users, self.active_user)
|
selection_state.selected_user_id = SelectionState.get_item_id_by_index(
|
||||||
|
self.users, self.active_user
|
||||||
|
)
|
||||||
|
|
||||||
active_user: EnumProperty(
|
active_user: EnumProperty(
|
||||||
items=get_users,
|
items=get_users,
|
||||||
@@ -178,29 +193,29 @@ class SpeckleSceneSettings(bpy.types.PropertyGroup):
|
|||||||
update=user_update_hook,
|
update=user_update_hook,
|
||||||
get=None,
|
get=None,
|
||||||
set=None,
|
set=None,
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
objects: CollectionProperty(type=SpeckleSceneObject) # type: ignore
|
objects: CollectionProperty(type=SpeckleSceneObject) # type: ignore
|
||||||
|
|
||||||
scale: FloatProperty(default=0.001) # type: ignore
|
scale: FloatProperty(default=0.001) # type: ignore
|
||||||
|
|
||||||
user: StringProperty(
|
user: StringProperty(
|
||||||
name="User",
|
name="User",
|
||||||
description="Current user",
|
description="Current user",
|
||||||
default="Speckle User",
|
default="Speckle User",
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
receive_script: EnumProperty(
|
receive_script: EnumProperty(
|
||||||
name="Receive script",
|
name="Receive script",
|
||||||
description="Custom py script to execute when receiving objects. See docs for function signature.",
|
description="Custom py script to execute when receiving objects. See docs for function signature.",
|
||||||
items=get_scripts,
|
items=get_scripts,
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
send_script: EnumProperty(
|
send_script: EnumProperty(
|
||||||
name="Send script",
|
name="Send script",
|
||||||
description="Custom py script to execute when sending objects. See docs for function signature",
|
description="Custom py script to execute when sending objects. See docs for function signature",
|
||||||
items=get_scripts,
|
items=get_scripts,
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
def get_active_user(self) -> Optional[SpeckleUserObject]:
|
def get_active_user(self) -> Optional[SpeckleUserObject]:
|
||||||
if self.active_user is None:
|
if self.active_user is None:
|
||||||
@@ -209,15 +224,16 @@ class SpeckleSceneSettings(bpy.types.PropertyGroup):
|
|||||||
if 0 <= selected_index < len(self.users):
|
if 0 <= selected_index < len(self.users):
|
||||||
return self.users[selected_index]
|
return self.users[selected_index]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def validate_user_selection(self) -> SpeckleUserObject:
|
def validate_user_selection(self) -> SpeckleUserObject:
|
||||||
user = self.get_active_user()
|
user = self.get_active_user()
|
||||||
if not user:
|
if not user:
|
||||||
raise SelectionException("No user account selected/found")
|
raise SelectionException("No user account selected/found")
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def validate_stream_selection(self) -> Tuple[SpeckleUserObject, SpeckleStreamObject]:
|
def validate_stream_selection(
|
||||||
|
self,
|
||||||
|
) -> Tuple[SpeckleUserObject, SpeckleStreamObject]:
|
||||||
user = self.validate_user_selection()
|
user = self.validate_user_selection()
|
||||||
|
|
||||||
stream = user.get_active_stream()
|
stream = user.get_active_stream()
|
||||||
@@ -225,80 +241,109 @@ class SpeckleSceneSettings(bpy.types.PropertyGroup):
|
|||||||
raise SelectionException("No project selected/found")
|
raise SelectionException("No project selected/found")
|
||||||
|
|
||||||
return (user, stream)
|
return (user, stream)
|
||||||
|
|
||||||
def validate_branch_selection(self) -> Tuple[SpeckleUserObject, SpeckleStreamObject, SpeckleBranchObject]:
|
def validate_branch_selection(
|
||||||
|
self,
|
||||||
|
) -> Tuple[SpeckleUserObject, SpeckleStreamObject, SpeckleBranchObject]:
|
||||||
(user, stream) = self.validate_stream_selection()
|
(user, stream) = self.validate_stream_selection()
|
||||||
|
|
||||||
branch = stream.get_active_branch()
|
branch = stream.get_active_branch()
|
||||||
if not branch:
|
if not branch:
|
||||||
raise SelectionException("No model selected/found")
|
raise SelectionException("No model selected/found")
|
||||||
return (user, stream, branch)
|
return (user, stream, branch)
|
||||||
|
|
||||||
def validate_commit_selection(self) ->Tuple[SpeckleUserObject, SpeckleStreamObject, SpeckleBranchObject, SpeckleCommitObject]:
|
def validate_commit_selection(
|
||||||
|
self,
|
||||||
|
) -> Tuple[
|
||||||
|
SpeckleUserObject, SpeckleStreamObject, SpeckleBranchObject, SpeckleCommitObject
|
||||||
|
]:
|
||||||
(user, stream, branch) = self.validate_branch_selection()
|
(user, stream, branch) = self.validate_branch_selection()
|
||||||
commit = branch.get_active_commit()
|
commit = branch.get_active_commit()
|
||||||
if commit is None:
|
if commit is None:
|
||||||
raise SelectionException("No model version selected/found")
|
raise SelectionException("No model version selected/found")
|
||||||
|
|
||||||
return (user, stream, branch, commit)
|
return (user, stream, branch, commit)
|
||||||
|
|
||||||
|
|
||||||
class SelectionException(Exception):
|
class SelectionException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_speckle(context: bpy.types.Context) -> SpeckleSceneSettings:
|
def get_speckle(context: bpy.types.Context) -> SpeckleSceneSettings:
|
||||||
"""
|
"""
|
||||||
Gets the speckle scene object
|
Gets the speckle scene object
|
||||||
"""
|
"""
|
||||||
return context.scene.speckle #type: ignore
|
return context.scene.speckle # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SelectionState:
|
class SelectionState:
|
||||||
selected_user_id : Optional[str] = None
|
selected_user_id: Optional[str] = None
|
||||||
selected_stream_id : Optional[str] = None
|
selected_stream_id: Optional[str] = None
|
||||||
selected_branch_id : Optional[str] = None
|
selected_branch_id: Optional[str] = None
|
||||||
selected_commit_id : Optional[str] = None
|
selected_commit_id: Optional[str] = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_item_id_by_index(collection: bpy.types.PropertyGroup, index: Union[str, int]) -> Optional[str]:
|
def get_item_id_by_index(
|
||||||
|
collection: bpy.types.PropertyGroup, index: Union[str, int]
|
||||||
|
) -> Optional[str]:
|
||||||
if item := SelectionState.get_item_by_index(collection, index):
|
if item := SelectionState.get_item_by_index(collection, index):
|
||||||
return item.id
|
return item.id
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_item_by_index(collection: bpy.types.PropertyGroup, index: Union[str, int]) -> Optional[bpy.types.PropertyGroup]:
|
def get_item_by_index(
|
||||||
|
collection: bpy.types.PropertyGroup, index: Union[str, int]
|
||||||
|
) -> Optional[bpy.types.PropertyGroup]:
|
||||||
items = collection.values()
|
items = collection.values()
|
||||||
i = int(index)
|
i = int(index)
|
||||||
if 0 <= i <= len(items):
|
if 0 <= i <= len(items):
|
||||||
return items[i]
|
return items[i]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_item_index_by_id(collection: Iterable[SpeckleCommitObject], id: Optional[str]) -> Optional[str]:
|
def get_item_index_by_id(
|
||||||
|
collection: Iterable[SpeckleCommitObject], id: Optional[str]
|
||||||
|
) -> Optional[str]:
|
||||||
for index, item in enumerate(collection):
|
for index, item in enumerate(collection):
|
||||||
if item.id == id:
|
if item.id == id:
|
||||||
return str(index)
|
return str(index)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
selection_state = SelectionState()
|
selection_state = SelectionState()
|
||||||
|
|
||||||
|
|
||||||
def restore_selection_state(speckle: SpeckleSceneSettings) -> None:
|
def restore_selection_state(speckle: SpeckleSceneSettings) -> None:
|
||||||
# Restore branch selection state
|
# Restore branch selection state
|
||||||
if selection_state.selected_branch_id != None:
|
if selection_state.selected_branch_id is not None:
|
||||||
(active_user, active_stream) = speckle.validate_stream_selection()
|
(active_user, active_stream) = speckle.validate_stream_selection()
|
||||||
# print(f"restore_selection_state: {active_user.id=}, {active_stream.id=}")
|
# print(f"restore_selection_state: {active_user.id=}, {active_stream.id=}")
|
||||||
# print(f"restore_selection_state: {selection_state.selected_user_id=}, {selection_state.selected_stream_id=}, {selection_state.selected_branch_id=}, {selection_state.selected_commit_id=}")
|
# print(f"restore_selection_state: {selection_state.selected_user_id=}, {selection_state.selected_stream_id=}, {selection_state.selected_branch_id=}, {selection_state.selected_commit_id=}")
|
||||||
|
|
||||||
is_same_user = active_user.id == selection_state.selected_user_id
|
is_same_user = active_user.id == selection_state.selected_user_id
|
||||||
|
|
||||||
if is_same_user:
|
if is_same_user:
|
||||||
active_user.active_stream = int(SelectionState.get_item_index_by_id(active_user.streams, selection_state.selected_stream_id))
|
active_user.active_stream = int(
|
||||||
active_stream = SelectionState.get_item_by_index(active_user.streams, active_user.active_stream)
|
SelectionState.get_item_index_by_id(
|
||||||
if branch := SelectionState.get_item_index_by_id(active_stream.branches, selection_state.selected_branch_id):
|
active_user.streams, selection_state.selected_stream_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
active_stream = SelectionState.get_item_by_index(
|
||||||
|
active_user.streams, active_user.active_stream
|
||||||
|
)
|
||||||
|
if branch := SelectionState.get_item_index_by_id(
|
||||||
|
active_stream.branches, selection_state.selected_branch_id
|
||||||
|
):
|
||||||
active_stream.branch = branch
|
active_stream.branch = branch
|
||||||
|
|
||||||
# Restore commit selection state
|
# Restore commit selection state
|
||||||
if selection_state.selected_commit_id != None:
|
if selection_state.selected_commit_id is not None:
|
||||||
(active_user, active_stream, active_branch) = speckle.validate_branch_selection()
|
(
|
||||||
|
active_user,
|
||||||
|
active_stream,
|
||||||
|
active_branch,
|
||||||
|
) = speckle.validate_branch_selection()
|
||||||
# print(f"restore_selection_state: {active_user.id=}, {active_stream.id=}, {active_branch.id=}")
|
# print(f"restore_selection_state: {active_user.id=}, {active_stream.id=}, {active_branch.id=}")
|
||||||
# print(f"restore_selection_state: {selection_state.selected_user_id=}, {selection_state.selected_stream_id=}, {selection_state.selected_branch_id=}, {selection_state.selected_commit_id=}")
|
# print(f"restore_selection_state: {selection_state.selected_user_id=}, {selection_state.selected_stream_id=}, {selection_state.selected_branch_id=}, {selection_state.selected_commit_id=}")
|
||||||
|
|
||||||
@@ -307,5 +352,7 @@ def restore_selection_state(speckle: SpeckleSceneSettings) -> None:
|
|||||||
is_same_branch = active_branch.id == selection_state.selected_branch_id
|
is_same_branch = active_branch.id == selection_state.selected_branch_id
|
||||||
|
|
||||||
if is_same_user and is_same_stream and is_same_branch:
|
if is_same_user and is_same_stream and is_same_branch:
|
||||||
if commit := SelectionState.get_item_index_by_id(active_branch.commits, selection_state.selected_commit_id):
|
if commit := SelectionState.get_item_index_by_id(
|
||||||
active_branch.commit = commit
|
active_branch.commits, selection_state.selected_commit_id
|
||||||
|
):
|
||||||
|
active_branch.commit = commit
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
from .object import OBJECT_PT_speckle
|
from .object import OBJECT_PT_speckle
|
||||||
from .view3d import (
|
from .view3d import (VIEW3D_PT_SpeckleActiveStream, VIEW3D_PT_SpeckleHelp,
|
||||||
VIEW3D_UL_SpeckleUsers,
|
VIEW3D_PT_SpeckleStreams, VIEW3D_PT_SpeckleUser,
|
||||||
VIEW3D_UL_SpeckleStreams,
|
VIEW3D_UL_SpeckleStreams, VIEW3D_UL_SpeckleUsers)
|
||||||
VIEW3D_PT_SpeckleUser,
|
|
||||||
VIEW3D_PT_SpeckleStreams,
|
|
||||||
VIEW3D_PT_SpeckleActiveStream,
|
|
||||||
VIEW3D_PT_SpeckleHelp,
|
|
||||||
)
|
|
||||||
|
|
||||||
ui_classes = [
|
ui_classes = [
|
||||||
VIEW3D_PT_SpeckleUser,
|
VIEW3D_PT_SpeckleUser,
|
||||||
|
|||||||
@@ -3,15 +3,9 @@ Object UI elements
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import (
|
|
||||||
StringProperty,
|
|
||||||
BoolProperty,
|
|
||||||
FloatProperty,
|
|
||||||
CollectionProperty,
|
|
||||||
EnumProperty,
|
|
||||||
)
|
|
||||||
from deprecated import deprecated
|
from deprecated import deprecated
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
class OBJECT_PT_speckle(bpy.types.Panel):
|
class OBJECT_PT_speckle(bpy.types.Panel):
|
||||||
bl_space_type = "PROPERTIES"
|
bl_space_type = "PROPERTIES"
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ Speckle UI elements for the 3d viewport
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
from bpy_speckle.properties.scene import get_speckle
|
from bpy_speckle.properties.scene import get_speckle
|
||||||
|
|
||||||
Region = "TOOLS" if bpy.app.version < (2, 80, 0) else "UI"
|
Region = "TOOLS" if bpy.app.version < (2, 80, 0) else "UI"
|
||||||
@@ -114,6 +114,7 @@ class VIEW3D_PT_SpeckleUser(bpy.types.Panel):
|
|||||||
|
|
||||||
col.operator("speckle.users_load", text="", icon="FILE_REFRESH")
|
col.operator("speckle.users_load", text="", icon="FILE_REFRESH")
|
||||||
|
|
||||||
|
|
||||||
class VIEW3D_PT_SpeckleStreams(bpy.types.Panel):
|
class VIEW3D_PT_SpeckleStreams(bpy.types.Panel):
|
||||||
"""
|
"""
|
||||||
Speckle projects UI panel in the 3d viewport
|
Speckle projects UI panel in the 3d viewport
|
||||||
@@ -161,7 +162,7 @@ class VIEW3D_PT_SpeckleActiveStream(bpy.types.Panel):
|
|||||||
col.label(text="No projects")
|
col.label(text="No projects")
|
||||||
else:
|
else:
|
||||||
user = speckle.validate_user_selection()
|
user = speckle.validate_user_selection()
|
||||||
#user = speckle.users[int(speckle.active_user)]
|
# user = speckle.users[int(speckle.active_user)]
|
||||||
if len(user.streams) < 1:
|
if len(user.streams) < 1:
|
||||||
col.label(text="No active project")
|
col.label(text="No active project")
|
||||||
else:
|
else:
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ def get_iddata(base, uuid, name, obdata):
|
|||||||
"""
|
"""
|
||||||
This is taken from the import_3dm add-on:
|
This is taken from the import_3dm add-on:
|
||||||
https://github.com/jesterKing/import_3dm
|
https://github.com/jesterKing/import_3dm
|
||||||
# Copyright (c) 2018-2019 Nathan Letwory, Joel Putnam,
|
# Copyright (c) 2018-2019 Nathan Letwory, Joel Putnam,
|
||||||
Tom Svilans
|
Tom Svilans
|
||||||
|
|
||||||
Get an iddata. If an object with given uuid is found in
|
Get an iddata. If an object with given uuid is found in
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@ def patch_installer(tag: str):
|
|||||||
"""Patches the installer with the correct connector version and specklepy version"""
|
"""Patches the installer with the correct connector version and specklepy version"""
|
||||||
tag = tag.replace("\n", "")
|
tag = tag.replace("\n", "")
|
||||||
iss_file = "speckle-sharp-ci-tools/blender.iss"
|
iss_file = "speckle-sharp-ci-tools/blender.iss"
|
||||||
iss_path = Path(iss_file)
|
iss_path = Path(iss_file)
|
||||||
lines = iss_path.read_text().split("\n")
|
lines = iss_path.read_text().split("\n")
|
||||||
lines.insert(12, f'#define AppVersion "{tag.split("-")[0]}"')
|
lines.insert(12, f'#define AppVersion "{tag.split("-")[0]}"')
|
||||||
lines.insert(13, f'#define AppInfoVersion "{tag}"')
|
lines.insert(13, f'#define AppInfoVersion "{tag}"')
|
||||||
@@ -17,4 +17,4 @@ def patch_installer(tag: str):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
tag = sys.argv[1]
|
tag = sys.argv[1]
|
||||||
patch_installer(tag)
|
patch_installer(tag)
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,7 @@
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def patch_connector(tag):
|
def patch_connector(tag):
|
||||||
"""Patches the connector version within the connector init file"""
|
"""Patches the connector version within the connector init file"""
|
||||||
bpy_file = "bpy_speckle/__init__.py"
|
bpy_file = "bpy_speckle/__init__.py"
|
||||||
@@ -9,7 +10,7 @@ def patch_connector(tag):
|
|||||||
with open(bpy_file, "r") as file:
|
with open(bpy_file, "r") as file:
|
||||||
lines = file.readlines()
|
lines = file.readlines()
|
||||||
|
|
||||||
for (index, line) in enumerate(lines):
|
for index, line in enumerate(lines):
|
||||||
if '"version":' in line:
|
if '"version":' in line:
|
||||||
lines[index] = f' "version": ({tag[0]}, {tag[1]}, {tag[2]}),\n'
|
lines[index] = f' "version": ({tag[0]}, {tag[1]}, {tag[2]}),\n'
|
||||||
print(f"Patched connector version number in {bpy_file}")
|
print(f"Patched connector version number in {bpy_file}")
|
||||||
@@ -18,6 +19,7 @@ def patch_connector(tag):
|
|||||||
with open(bpy_file, "w") as file:
|
with open(bpy_file, "w") as file:
|
||||||
file.writelines(lines)
|
file.writelines(lines)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
tag = sys.argv[1]
|
tag = sys.argv[1]
|
||||||
if not re.match(r"([0-9]+)\.([0-9]+)\.([0-9]+)", tag):
|
if not re.match(r"([0-9]+)\.([0-9]+)\.([0-9]+)", tag):
|
||||||
|
|||||||
Generated
+620
-460
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -7,7 +7,7 @@ license = "Apache-2.0"
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.8, <4.0.0"
|
python = ">=3.8, <4.0.0"
|
||||||
specklepy = "^2.19.1"
|
specklepy = "^2.20.0"
|
||||||
attrs = "^23.1.0"
|
attrs = "^23.1.0"
|
||||||
|
|
||||||
# [tool.poetry.group.local_specklepy.dependencies]
|
# [tool.poetry.group.local_specklepy.dependencies]
|
||||||
@@ -15,7 +15,7 @@ attrs = "^23.1.0"
|
|||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
fake-bpy-module-latest = "^20240524"
|
fake-bpy-module-latest = "^20240524"
|
||||||
black = "23.11.0"
|
black = "24.3.0"
|
||||||
pylint = "^2.15.7"
|
pylint = "^2.15.7"
|
||||||
ruff = "^0.4.4"
|
ruff = "^0.4.4"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user