Compare commits

...

2 Commits

Author SHA1 Message Date
JR-Morgan ea2b6dfb0e feat(connector): Added clean mesh option to recieve geometry with limited disolve + merge verts 2022-09-23 12:36:55 +01:00
JR-Morgan 83610cec38 Added type hinting to codebase 2022-09-21 22:22:07 +01:00
8 changed files with 144 additions and 53 deletions
+4 -1
View File
@@ -1,4 +1,7 @@
"""
Permanent handle on all user clients
"""
speckle_clients = []
from specklepy.api.client import SpeckleClient
speckle_clients: list[SpeckleClient] = []
+2 -2
View File
@@ -1,7 +1,7 @@
from bpy_speckle.convert.to_native import convert_to_native
from specklepy.objects.base import Base
def get_speckle_subobjects(attr, scale, name):
def get_speckle_subobjects(attr: dict | Base, scale: float, name: str) -> list:
subobjects = []
keys = attr.keys() if isinstance(attr, dict) else attr.get_dynamic_member_names()
for key in keys:
+16 -15
View File
@@ -4,6 +4,7 @@ import mathutils
import bpy, bmesh, bpy_types
from specklepy.objects.other import *
from specklepy.objects.geometry import *
from bpy.types import Object
from .util import (
add_blender_material,
add_custom_properties,
@@ -25,7 +26,7 @@ CAN_CONVERT_TO_NATIVE = (
)
def can_convert_to_native(speckle_object):
def can_convert_to_native(speckle_object: Base) -> bool:
if type(speckle_object) in CAN_CONVERT_TO_NATIVE:
return True
if getattr(
@@ -37,7 +38,7 @@ def can_convert_to_native(speckle_object):
return False
def convert_to_native(speckle_object, name=None):
def convert_to_native(speckle_object: Base, name: str = None) -> list | Object:
speckle_type = type(speckle_object)
speckle_name = (
name
@@ -52,7 +53,7 @@ def convert_to_native(speckle_object, name=None):
)
if not elements and not display:
_report(f"Could not convert unsupported Speckle object: {speckle_object}")
return
return None
if isinstance(display, list):
elements.extend(display)
else:
@@ -130,7 +131,7 @@ def convert_to_native(speckle_object, name=None):
return blender_object
def mesh_to_native(speckle_mesh: Mesh, name, scale=1.0):
def mesh_to_native(speckle_mesh: Mesh, name: str, scale=1.0) -> bpy.types.Mesh:
if name in bpy.data.meshes.keys():
blender_mesh = bpy.data.meshes[name]
@@ -150,7 +151,7 @@ def mesh_to_native(speckle_mesh: Mesh, name, scale=1.0):
return blender_mesh
def line_to_native(speckle_curve, blender_curve, scale):
def line_to_native(speckle_curve: Line, blender_curve: bpy.types.Curve, scale: float) -> bpy.types.Spline | None:
line = blender_curve.splines.new("POLY")
line.points.add(1)
@@ -173,7 +174,7 @@ def line_to_native(speckle_curve, blender_curve, scale):
return line
def polyline_to_native(scurve, bcurve, scale):
def polyline_to_native(scurve: Polyline, bcurve: bpy.types.Curve, scale: float) -> bpy.types.Spline | None:
if value := scurve.value:
N = len(value) // 3
@@ -197,7 +198,7 @@ def polyline_to_native(scurve, bcurve, scale):
return polyline
def nurbs_to_native(scurve, bcurve, scale):
def nurbs_to_native(scurve: Curve, bcurve: bpy.types.Curve, scale: float) -> bpy.types.Spline | None:
if points := scurve.points:
N = len(points) // 3
@@ -226,12 +227,12 @@ def nurbs_to_native(scurve, bcurve, scale):
return nurbs
def arc_to_native(rcurve, bcurve, scale):
def arc_to_native(rcurve: Arc, bcurve: bpy.types.Curve, scale: float) -> bpy.types.Spline | None:
# TODO: improve Blender representation of arc
plane = rcurve.plane
if not plane:
return
return None
normal = mathutils.Vector([plane.normal.x, plane.normal.y, plane.normal.z])
@@ -288,7 +289,7 @@ def arc_to_native(rcurve, bcurve, scale):
return arc
def polycurve_to_native(scurve, bcurve, scale):
def polycurve_to_native(scurve: Polycurve, bcurve: bpy.types.Curve, scale: float):
"""
Convert Polycurve object
"""
@@ -307,7 +308,7 @@ def polycurve_to_native(scurve, bcurve, scale):
return curves
def icurve_to_native_spline(speckle_curve, blender_curve, scale=1.0):
def icurve_to_native_spline(speckle_curve: Base, blender_curve: bpy.types.Curve, scale=1.0):
curve_type = type(speckle_curve)
if curve_type is Line:
return line_to_native(speckle_curve, blender_curve, scale)
@@ -321,7 +322,7 @@ def icurve_to_native_spline(speckle_curve, blender_curve, scale=1.0):
return arc_to_native(speckle_curve, blender_curve, scale)
def icurve_to_native(speckle_curve, name=None, scale=1.0):
def icurve_to_native(speckle_curve: Base, name=None, scale=1.0) -> Curve | None:
curve_type = type(speckle_curve)
if curve_type not in SUPPORTED_CURVES:
_report(f"Unsupported curve type: {curve_type}")
@@ -340,7 +341,7 @@ def icurve_to_native(speckle_curve, name=None, scale=1.0):
return blender_curve
def transform_to_native(transform: Transform, scale=1.0):
def transform_to_native(transform: Transform, scale=1.0) -> mathutils.Matrix:
mat = mathutils.Matrix(
[
transform.value[:4],
@@ -355,7 +356,7 @@ def transform_to_native(transform: Transform, scale=1.0):
return mat
def block_def_to_native(definition: BlockDefinition, scale=1.0):
def block_def_to_native(definition: BlockDefinition, scale=1.0) -> bpy.types.Collection:
native_def = bpy.data.collections.get(definition.name)
if native_def:
return native_def
@@ -373,7 +374,7 @@ def block_def_to_native(definition: BlockDefinition, scale=1.0):
return native_def
def block_instance_to_native(instance: BlockInstance, scale=1.0):
def block_instance_to_native(instance: BlockInstance, scale=1.0) -> bpy.types.Object:
"""
Convert BlockInstance to native
"""
+24 -16
View File
@@ -1,4 +1,5 @@
import bpy
from bpy.types import MeshVertColor, MeshVertex, Object
from specklepy.objects.geometry import Mesh, Curve, Interval, Box, Point, Polyline
from specklepy.objects.other import *
from bpy_speckle.functions import _report
@@ -13,12 +14,12 @@ UNITS = "m"
CAN_CONVERT_TO_SPECKLE = ("MESH", "CURVE", "EMPTY")
def convert_to_speckle(blender_object, scale, units, desgraph=None):
def convert_to_speckle(blender_object: Object, scale: float, units: str, desgraph=None) -> list | None:
global UNITS
UNITS = units
blender_type = blender_object.type
if blender_type not in CAN_CONVERT_TO_SPECKLE:
return
return None
speckle_objects = []
speckle_material = material_to_speckle(blender_object)
@@ -54,10 +55,11 @@ def convert_to_speckle(blender_object, scale, units, desgraph=None):
return speckle_objects
def mesh_to_speckle(blender_object, data, scale=1.0):
def mesh_to_speckle(blender_object: Object, data: bpy.types.Mesh, scale=1.0) -> List[Mesh]:
if data.loop_triangles is None or len(data.loop_triangles) < 1:
data.calc_loop_triangles()
mat = blender_object.matrix_world
verts = [tuple(mat @ x.co * scale) for x in data.vertices]
@@ -89,10 +91,16 @@ def mesh_to_speckle(blender_object, data, scale=1.0):
sm.faces.append(n)
sm.faces.extend(f)
# TODO: figure out how to align vertex colors and vertices consistantly in receiving applications
# we are seeing the same issue as with texture coordinate alignment
#if data.color_attributes.active_color:
# sm.colors = [to_argb_int(x.color) for x in data.color_attributes.active_color.data]
return [sm]
def bezier_to_speckle(matrix, spline, scale, name=None):
def bezier_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: float, name:str = None) -> Curve:
degree = 3
closed = spline.use_cyclic_u
@@ -141,7 +149,7 @@ def bezier_to_speckle(matrix, spline, scale, name=None):
)
def nurbs_to_speckle(matrix, spline, scale, name=None):
def nurbs_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: float, name:str = None) -> Curve:
knots = make_knots(spline)
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
degree = spline.order_u - 1
@@ -167,7 +175,7 @@ def nurbs_to_speckle(matrix, spline, scale, name=None):
)
def poly_to_speckle(matrix, spline, scale, name=None):
def poly_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: float, name: str = None) -> Polyline:
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
length = spline.calc_length()
@@ -184,7 +192,7 @@ def poly_to_speckle(matrix, spline, scale, name=None):
)
def icurve_to_speckle(blender_object, data, scale=1.0):
def icurve_to_speckle(blender_object: Object, data: bpy.types.Curve, scale=1.0) -> List[Base] | None:
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
if blender_object.type != "CURVE":
@@ -213,7 +221,7 @@ def icurve_to_speckle(blender_object, data, scale=1.0):
return curves
def ngons_to_speckle_polylines(blender_object, data, scale=1.0):
def ngons_to_speckle_polylines(blender_object: Object, data: bpy.types.Mesh, scale=1.0) -> List[Polyline] | None:
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
if blender_object.type != "MESH":
@@ -245,14 +253,14 @@ def ngons_to_speckle_polylines(blender_object, data, scale=1.0):
return polylines
def material_to_speckle(blender_object) -> RenderMaterial:
def material_to_speckle(blender_object: Object) -> RenderMaterial | None:
"""Create and return a render material from a blender object"""
if not getattr(blender_object.data, "materials", None):
return
return None
blender_mat = blender_object.data.materials[0]
blender_mat: bpy.types.Material = blender_object.data.materials[0]
if not blender_mat:
return
return None
speckle_mat = RenderMaterial()
speckle_mat.name = blender_mat.name
@@ -275,7 +283,7 @@ def material_to_speckle(blender_object) -> RenderMaterial:
return speckle_mat
def transform_to_speckle(blender_transform, scale=1.0):
def transform_to_speckle(blender_transform: List[float], scale=1.0) -> Transform:
value = [y for x in blender_transform for y in x]
# scale the translation
for i in (3, 7, 11):
@@ -284,7 +292,7 @@ def transform_to_speckle(blender_transform, scale=1.0):
return Transform(value=value, units=UNITS)
def block_def_to_speckle(blender_definition, scale=1.0):
def block_def_to_speckle(blender_definition: bpy.types.Collection, scale=1.0) -> BlockDefinition:
geometry = []
for geo in blender_definition.objects:
geometry.extend(convert_to_speckle(geo, scale, UNITS))
@@ -299,7 +307,7 @@ def block_def_to_speckle(blender_definition, scale=1.0):
return block_def
def block_instance_to_speckle(blender_instance, scale=1.0):
def block_instance_to_speckle(blender_instance: Object, scale=1.0):
return BlockInstance(
blockDefinition=block_def_to_speckle(
blender_instance.instance_collection, scale
@@ -310,7 +318,7 @@ def block_instance_to_speckle(blender_instance, scale=1.0):
)
def empty_to_speckle(blender_object, scale=1.0):
def empty_to_speckle(blender_object: Object, scale=1.0) -> BlockInstance | None:
# probably an instance collection (block) so let's try it
try:
geo = blender_object.instance_collection.objects.items()
+10 -6
View File
@@ -1,9 +1,13 @@
import math
from typing import Tuple
from bmesh.types import BMesh
import bpy, struct, idprop
from specklepy.objects.base import Base
from specklepy.objects.geometry import Mesh
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
from bpy_speckle.functions import _report
from bpy.types import Object
IGNORED_PROPERTY_KEYS = {
"id",
@@ -38,7 +42,7 @@ def to_argb_int(diffuse_colour) -> int:
return int.from_bytes(diffuse_colour, byteorder="big", signed=True)
def add_custom_properties(speckle_object, blender_object):
def add_custom_properties(speckle_object: Base, blender_object: Object):
if blender_object is None:
return
@@ -69,7 +73,7 @@ def add_custom_properties(speckle_object, blender_object):
blender_object[k] = v
def add_blender_material(speckle_object, blender_object) -> None:
def add_blender_material(speckle_object: Base, blender_object: Object) -> None:
"""Add material to a blender object if the corresponding speckle object has a render material"""
if blender_object.data is None:
return
@@ -107,7 +111,7 @@ def add_blender_material(speckle_object, blender_object) -> None:
blender_object.data.materials.append(blender_mat)
def add_vertices(speckle_mesh, blender_mesh, scale=1.0):
def add_vertices(speckle_mesh: Mesh, blender_mesh: BMesh, scale=1.0):
sverts = speckle_mesh.vertices
if sverts and len(sverts) > 0:
@@ -123,7 +127,7 @@ def add_vertices(speckle_mesh, blender_mesh, scale=1.0):
blender_mesh.verts.ensure_lookup_table()
def add_faces(speckle_mesh, blender_mesh, smooth=False):
def add_faces(speckle_mesh: Mesh, blender_mesh: BMesh, smooth=False):
sfaces = speckle_mesh.faces
if sfaces and len(sfaces) > 0:
@@ -147,7 +151,7 @@ def add_faces(speckle_mesh, blender_mesh, smooth=False):
blender_mesh.verts.index_update()
def add_colors(speckle_mesh, blender_mesh):
def add_colors(speckle_mesh: Mesh, blender_mesh: BMesh):
scolors = speckle_mesh.colors
@@ -178,7 +182,7 @@ def add_colors(speckle_mesh, blender_mesh):
loop[color_layer] = colors[loop.vert.index]
def add_uv_coords(speckle_mesh, blender_mesh):
def add_uv_coords(speckle_mesh: Mesh, blender_mesh: BMesh):
s_uvs = speckle_mesh.textureCoordinates
if not s_uvs:
return
+1 -1
View File
@@ -33,7 +33,7 @@ def _report(msg):
print("SpeckleBlender: {}".format(msg))
def get_scale_length(units):
def get_scale_length(units: str) -> float:
if units.lower() in unit_scale.keys():
return unit_scale[units]
_report("Units <{}> are not supported.".format(units))
+85 -11
View File
@@ -2,7 +2,8 @@
Stream operators
"""
from itertools import chain
from typing import Dict
from math import radians
from typing import Callable, Dict, Iterable
import bpy
from specklepy.api.models import Commit
import webbrowser
@@ -10,6 +11,7 @@ from bpy.props import (
StringProperty,
BoolProperty,
)
from bpy.types import Object
from bpy_speckle.convert.to_native import can_convert_to_native, convert_to_native
from bpy_speckle.convert.to_speckle import (
convert_to_speckle,
@@ -32,7 +34,7 @@ from specklepy.objects.geometry import *
from specklepy.logging.exceptions import SpeckleException
def get_objects_collections(base) -> Dict:
def get_objects_collections(base: Base) -> Dict[str, list]:
"""Create collections based on the dynamic members on a root commit object"""
collections = {}
for name in base.get_dynamic_member_names():
@@ -47,7 +49,7 @@ def get_objects_collections(base) -> Dict:
return collections
def get_objects_nested_lists(items, parent_col=None) -> List:
def get_objects_nested_lists(items: list, parent_col: bpy.types.Collection = None) -> List:
"""For handling the weird nested lists that come from Grasshopper"""
objects = []
@@ -64,7 +66,7 @@ def get_objects_nested_lists(items, parent_col=None) -> List:
return objects
def get_objects_collections_recursive(base, parent_col=None) -> List:
def get_objects_collections_recursive(base: Base, parent_col: bpy.types.Collection = None) -> List:
"""Recursively create collections based on the dynamic members on nested `Base` objects within the root commit object"""
# if it's a convertable (registered) class and not just a plain `Base`, return the object itself
if can_convert_to_native(base):
@@ -94,7 +96,7 @@ def get_objects_collections_recursive(base, parent_col=None) -> List:
return objects
def bases_to_native(context, collections, scale, stream_id, func=None):
def bases_to_native(context: bpy.types.Context, collections: Dict[str, list], scale: float, stream_id: str, func: Callable = None):
for col_name, objects in collections.items():
col = bpy.data.collections[col_name]
existing = get_existing_collection_objs(col)
@@ -126,7 +128,8 @@ def bases_to_native(context, collections, scale, stream_id, func=None):
context.area.tag_redraw()
def base_to_native(context, base, scale, stream_id, col, existing, func=None):
def base_to_native(context: bpy.types.Context, base: Base, scale: float, stream_id: str, col: bpy.types.Collection, existing: Dict[str, Object], func: Callable = None):
new_objects = convert_to_native(base)
if not isinstance(new_objects, list):
new_objects = [new_objects]
@@ -170,7 +173,7 @@ def base_to_native(context, base, scale, stream_id, col, existing, func=None):
col.objects.link(new_object)
def create_collection(name, clear_collection=True):
def create_collection(name: str, clear_collection=True) -> bpy.types.Collection:
if name in bpy.data.collections:
col = bpy.data.collections[name]
if clear_collection:
@@ -182,13 +185,13 @@ def create_collection(name, clear_collection=True):
return col
def create_child_collections(parent_col, children_names):
def create_child_collections(parent_col: bpy.types.Collection, children_names: Iterable[str]):
for name in children_names:
col = create_collection(name)
parent_col.children.link(col)
def get_existing_collection_objs(col):
def get_existing_collection_objs(col: bpy.types.Collection) -> Dict[str, bpy.types.Object]:
return {
obj.speckle.object_id: obj for obj in col.objects if obj.speckle.object_id != ""
}
@@ -240,6 +243,49 @@ class ReceiveStreamObjects(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Receive objects from active stream"
clean_meshes: BoolProperty(name="Clean Meshes", default=False)
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, "clean_meshes")
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
@staticmethod
def clean_converted_meshes(context: bpy.types.Context, convertedObjects: dict[str, Object]):
bpy.ops.object.select_all(action='DESELECT')
active = None
for obj in convertedObjects.values():
if obj.type != 'MESH':
continue
# This seems to be required inorder to select the object here
if obj.name not in context.scene.collection.objects:
context.scene.collection.objects.link(obj)
obj.select_set(True, view_layer=context.scene.view_layers[0])
active = obj
if active == None:
return
context.view_layer.objects.active = active
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles()
bpy.ops.mesh.dissolve_limited(angle_limit=radians(0.1))
# Reset state to previous (not quite sure if this is 100% necessary)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = None
def execute(self, context):
bpy.context.view_layer.objects.active = None
@@ -277,6 +323,9 @@ class ReceiveStreamObjects(bpy.types.Operator):
message="received commit from Speckle Blender",
)
context.window_manager.progress_begin(0, stream_data.totalChildrenCount)
"""
Create or get Collection for stream objects
"""
@@ -309,20 +358,45 @@ class ReceiveStreamObjects(bpy.types.Operator):
"""
Get script from text editor for injection
"""
func = None
userFunc = None
if context.scene.speckle.receive_script in bpy.data.texts:
mod = bpy.data.texts[context.scene.speckle.receive_script].as_module()
if hasattr(mod, "execute"):
func = mod.execute
userFunc = mod.execute
createdObjects:Dict[str, Object] = {}
progress = 0
def func(scene, obj: Object):
nonlocal progress
nonlocal context
nonlocal createdObjects
progress += 1 #TODO: Progress bar neverreaches 100 because func is only called for convertable objects
context.window_manager.progress_update(progress)
createdObjects[obj.name] = obj
if userFunc:
return userFunc(scene, obj)
else:
return obj
"""
Iterate through retrieved resources
"""
bases_to_native(context, collections, scale, stream.id, func)
context.window_manager.progress_end()
if self.clean_meshes:
self.clean_converted_meshes(context, createdObjects)
return {"FINISHED"}
class SendStreamObjects(bpy.types.Operator):
"""
Send stream objects
+2 -1
View File
@@ -5,6 +5,7 @@ import bpy
from bpy_speckle.functions import _report
from bpy_speckle.clients import speckle_clients
from specklepy.api.client import SpeckleClient
from specklepy.api.models import Stream, User
from specklepy.api.credentials import get_local_accounts
from datetime import datetime
@@ -59,7 +60,7 @@ class LoadUsers(bpy.types.Operator):
return {"FINISHED"}
def add_user_stream(user, stream):
def add_user_stream(user: User, stream: Stream):
s = user.streams.add()
s.name = stream.name
s.id = stream.id