Compare commits
11 Commits
2.12.2
...
2.13.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
| 52136d3ef6 | |||
| fe764d7f0c | |||
| 669dd67521 | |||
| f74b2c37f0 | |||
| ebb4e32fff | |||
| 25903baf83 | |||
| cb6d6d7ad8 | |||
| fd2687aa3c | |||
| f5c65068de | |||
| a1ec137c67 | |||
| b95f621272 |
@@ -37,8 +37,12 @@ loading a Blender file
|
||||
@persistent
|
||||
def load_handler(dummy):
|
||||
pass
|
||||
#bpy.ops.speckle.users_load() #this is an expensive operation, one that forces the user to wait every time blender loads. Until we can do this non-blocking, we will make the user hit the refresh button each time.
|
||||
|
||||
# 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.
|
||||
#bpy.ops.speckle.users_load()
|
||||
|
||||
# Instead, we shall just reset the user selection to an uninitiailised state
|
||||
bpy.ops.speckle.users_reset()
|
||||
|
||||
"""
|
||||
Permanent handle on callbacks
|
||||
|
||||
+366
-174
@@ -1,14 +1,22 @@
|
||||
import math
|
||||
from typing import Iterable, Union, Collection
|
||||
from bpy_speckle.convert.to_speckle import transform_to_speckle
|
||||
from typing import Tuple, Union, Collection
|
||||
from bpy_speckle.functions import get_scale_length, _report
|
||||
import mathutils
|
||||
import bpy, bmesh, bpy_types
|
||||
from specklepy.objects.other import *
|
||||
from mathutils import (
|
||||
Matrix as MMatrix,
|
||||
Vector as MVector,
|
||||
Quaternion as MQuaternion,
|
||||
)
|
||||
import bpy, bmesh
|
||||
from specklepy.objects.other import (
|
||||
Instance,
|
||||
Transform,
|
||||
BlockDefinition,
|
||||
)
|
||||
from specklepy.objects.geometry import *
|
||||
from bpy.types import Object
|
||||
from .util import (
|
||||
get_render_material,
|
||||
link_object_to_collection_nested,
|
||||
render_material_to_native,
|
||||
add_custom_properties,
|
||||
add_vertices,
|
||||
@@ -17,53 +25,76 @@ from .util import (
|
||||
add_uv_coords,
|
||||
)
|
||||
|
||||
SUPPORTED_CURVES = (Line, Polyline, Curve, Arc, Polycurve)
|
||||
SUPPORTED_CURVES = (Line, Polyline, Curve, Arc, Polycurve, Ellipse, Circle)
|
||||
|
||||
CAN_CONVERT_TO_NATIVE = (
|
||||
Mesh,
|
||||
*SUPPORTED_CURVES,
|
||||
transform_to_speckle,
|
||||
BlockDefinition,
|
||||
BlockInstance,
|
||||
Instance,
|
||||
)
|
||||
|
||||
|
||||
def can_convert_to_native(speckle_object: Base) -> bool:
|
||||
if type(speckle_object) in CAN_CONVERT_TO_NATIVE:
|
||||
return True
|
||||
def _has_native_convesion(speckle_object: Base) -> bool:
|
||||
return any(isinstance(speckle_object, t) for t in CAN_CONVERT_TO_NATIVE)
|
||||
|
||||
for alias in DISPLAY_VALUE_PROPERTY_ALIASES:
|
||||
if getattr(speckle_object, alias, None):
|
||||
return True
|
||||
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:
|
||||
|
||||
if(_has_native_convesion(speckle_object) or _has_fallback_conversion(speckle_object)):
|
||||
return True
|
||||
|
||||
_report(f"Could not convert unsupported Speckle object: {speckle_object}")
|
||||
return False
|
||||
|
||||
|
||||
def create_new_object(obj_data: Optional[bpy.types.ID], desired_name: str, counter: int = 0) -> bpy.types.Object:
|
||||
"""
|
||||
Creates a new blender object with a unique name,
|
||||
if the desired_name is already taken
|
||||
we'll append a number, with the format .xxx to the desired_name to ensure the name is unique.
|
||||
"""
|
||||
name = desired_name if counter == 0 else f"{desired_name[:OBJECT_NAME_MAX_LENGTH - 4]}.{counter:03d}" # format counter as name.xxx, truncate to ensure we don't exceed the object name max length
|
||||
|
||||
if name in bpy.data.objects.keys():
|
||||
#Object already exists, increment counter and try again!
|
||||
return create_new_object(obj_data, desired_name, counter + 1)
|
||||
|
||||
blender_object = bpy.data.objects.new(name, obj_data)
|
||||
return blender_object
|
||||
|
||||
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):
|
||||
global convert_instances_as
|
||||
convert_instances_as = value
|
||||
|
||||
def convert_to_native(speckle_object: Base) -> list[Object]:
|
||||
|
||||
speckle_type = type(speckle_object)
|
||||
speckle_name = generate_object_name(speckle_object)
|
||||
try:
|
||||
object_name = _generate_object_name(speckle_object)
|
||||
scale = get_scale_factor(speckle_object)
|
||||
|
||||
obj_data: Optional[Union[bpy.types.ID, bpy.types.Object, mathutils.Matrix]] = None
|
||||
obj_data: Optional[Union[bpy.types.ID, bpy.types.Object]] = None
|
||||
converted: list[Object] = []
|
||||
|
||||
# convert elements/breps
|
||||
if speckle_type not in CAN_CONVERT_TO_NATIVE:
|
||||
(obj_data, converted) = display_value_to_native(speckle_object, speckle_name, scale)
|
||||
if not _has_native_convesion(speckle_object):
|
||||
(obj_data, converted) = element_to_native(speckle_object, object_name, scale)
|
||||
if not obj_data and not converted:
|
||||
_report(f"Unsupported type {speckle_object.speckle_type}")
|
||||
|
||||
# convert supported geometry
|
||||
elif isinstance(speckle_object, Mesh):
|
||||
obj_data = mesh_to_native(speckle_object, speckle_name, scale)
|
||||
obj_data = mesh_to_native(speckle_object, object_name, scale)
|
||||
elif speckle_type in SUPPORTED_CURVES:
|
||||
obj_data = icurve_to_native(speckle_object, speckle_name, scale)
|
||||
elif isinstance(speckle_object, Transform):
|
||||
obj_data = transform_to_native(speckle_object, scale)
|
||||
elif isinstance(speckle_object, BlockDefinition):
|
||||
obj_data = block_def_to_native(speckle_object)
|
||||
elif isinstance(speckle_object, BlockInstance):
|
||||
obj_data = block_instance_to_native(speckle_object, scale)
|
||||
obj_data = icurve_to_native(speckle_object, object_name, scale)
|
||||
elif isinstance(speckle_object, Instance):
|
||||
if convert_instances_as == "linked_duplicates":
|
||||
(obj_data, converted) = instance_to_native_object(speckle_object, scale)
|
||||
else: # convert_instances_as == collection_instance
|
||||
obj_data = instance_to_native_collection_instance(speckle_object, scale)
|
||||
else:
|
||||
_report(f"Unsupported type {speckle_type}")
|
||||
return []
|
||||
@@ -71,25 +102,9 @@ def convert_to_native(speckle_object: Base) -> list[Object]:
|
||||
_report(f"Error converting {speckle_object} \n{ex}")
|
||||
return []
|
||||
|
||||
if speckle_name in bpy.data.objects.keys():
|
||||
blender_object = bpy.data.objects[speckle_name]
|
||||
blender_object.data = (
|
||||
obj_data.data if isinstance(obj_data, Object) else obj_data
|
||||
)
|
||||
blender_object.matrix_world = (
|
||||
blender_object.matrix_world
|
||||
if speckle_type is BlockInstance
|
||||
else mathutils.Matrix()
|
||||
)
|
||||
if hasattr(obj_data, "materials"):
|
||||
blender_object.data.materials.clear()
|
||||
else:
|
||||
blender_object = (
|
||||
obj_data
|
||||
if isinstance(obj_data, Object)
|
||||
else bpy.data.objects.new(speckle_name, obj_data)
|
||||
)
|
||||
|
||||
blender_object = obj_data if isinstance(obj_data, Object) else create_new_object(obj_data, object_name)
|
||||
|
||||
blender_object.speckle.object_id = str(speckle_object.id)
|
||||
blender_object.speckle.enabled = True
|
||||
add_custom_properties(speckle_object, blender_object)
|
||||
@@ -98,57 +113,51 @@ def convert_to_native(speckle_object: Base) -> list[Object]:
|
||||
child.parent = blender_object
|
||||
|
||||
converted.append(blender_object)
|
||||
_report(f"Successfully converted {object_name} as {blender_object.type}")
|
||||
return converted
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def generate_object_name(speckle_object: Base) -> str:
|
||||
prefix = (getattr(speckle_object, "name", None)
|
||||
or getattr(speckle_object, "Name", None)
|
||||
or speckle_object.speckle_type.rsplit(':')[-1])
|
||||
|
||||
return f"{prefix} -- {speckle_object.id}"
|
||||
|
||||
def get_scale_factor(speckle_object: Base, fallback: float = 1.0) -> float:
|
||||
scale = fallback
|
||||
if units := getattr(speckle_object, "units", None):
|
||||
scale = get_scale_length(units) / bpy.context.scene.unit_settings.scale_length
|
||||
return scale
|
||||
|
||||
|
||||
DISPLAY_VALUE_PROPERTY_ALIASES = ["displayValue", "@displayValue", "displayMesh", "@displayMesh", "elements", "@elements"]
|
||||
|
||||
def display_value_to_native(speckle_object: Base, name: str, scale: float) -> tuple[Optional[bpy.types.Mesh], list[bpy.types.Object]]:
|
||||
def element_to_native(speckle_object: Base, name: str, scale: float, combineMeshes: bool = True) -> tuple[Optional[bpy.types.Mesh], list[bpy.types.Object]]:
|
||||
"""
|
||||
Converts mesh displayValues as one mesh
|
||||
Converts non-mesh displayValues as child Objects
|
||||
Converts a given speckle_object by converting displayValue properties (elements treated the same as displayValues)
|
||||
|
||||
if combineMeshes == True
|
||||
Converts mesh displayValues as one mesh
|
||||
Converts non-mesh displayValues as child Objects
|
||||
if combineMeshes == False
|
||||
Converts all displayValues as child objects (first item of the returned tuple will be None)
|
||||
"""
|
||||
meshes: list[Mesh] = []
|
||||
elements: list[Base] = []
|
||||
|
||||
#NOTE: raw Mesh elements will be treated like displayValues, which is not ideal, but no connector sends raw Mesh elements so its fine
|
||||
#NOTE: raw Mesh elements will be treated like displayValues, which is not ideal, but no connector sends raw Mesh elements so it's fine
|
||||
for alias in DISPLAY_VALUE_PROPERTY_ALIASES:
|
||||
display = getattr(speckle_object, alias, None)
|
||||
|
||||
count = 0
|
||||
max_depth = 255
|
||||
def seperate(value: Any) -> None:
|
||||
nonlocal meshes, elements, count, max_depth
|
||||
MAX_DEPTH = 255 # some large value, to prevent infinite reccursion
|
||||
def seperate(value: Any) -> bool:
|
||||
nonlocal meshes, elements, count, MAX_DEPTH
|
||||
|
||||
if isinstance(value, Mesh):
|
||||
if combineMeshes and isinstance(value, Mesh):
|
||||
meshes.append(value)
|
||||
elif isinstance(value, Base):
|
||||
elements.append(value)
|
||||
elif isinstance(value, list):
|
||||
count += 1
|
||||
if(count > max_depth):
|
||||
return
|
||||
if(count > MAX_DEPTH):
|
||||
return True
|
||||
for x in value:
|
||||
seperate(x)
|
||||
|
||||
seperate(display)
|
||||
return False
|
||||
|
||||
did_halt = seperate(display)
|
||||
|
||||
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?")
|
||||
|
||||
|
||||
converted: list[Object] = []
|
||||
@@ -157,10 +166,10 @@ def display_value_to_native(speckle_object: Base, name: str, scale: float) -> tu
|
||||
if meshes:
|
||||
mesh = meshes_to_native(speckle_object, meshes, name, scale)
|
||||
|
||||
# add parent type here so we can use it as a blender custom prop
|
||||
# not making it hidden, so it will get added on send as i think it might be helpful? can reconsider
|
||||
for item in elements:
|
||||
item.parent_speckle_type = speckle_object.speckle_type
|
||||
# add parent type here so we can use it as a blender custom prop
|
||||
# not making it hidden, so it will get added on send as i think it might be helpful? can reconsider
|
||||
item.parent_speckle_type = speckle_object.speckle_type #TODO: consider if this is still useful, as we now properly structure object parenting
|
||||
blender_object = convert_to_native(item)
|
||||
if isinstance(blender_object, list):
|
||||
converted.extend(blender_object)
|
||||
@@ -168,9 +177,6 @@ def display_value_to_native(speckle_object: Base, name: str, scale: float) -> tu
|
||||
add_custom_properties(speckle_object, blender_object)
|
||||
converted.append(blender_object)
|
||||
|
||||
if not elements and not meshes:
|
||||
_report(f"Unsupported type {speckle_object.speckle_type}")
|
||||
|
||||
return (mesh, converted)
|
||||
|
||||
|
||||
@@ -179,9 +185,9 @@ def mesh_to_native(speckle_mesh: Mesh, name: str, scale: float) -> bpy.types.Mes
|
||||
|
||||
def meshes_to_native(element: Base, meshes: Collection[Mesh], name: str, scale: float) -> bpy.types.Mesh:
|
||||
if name in bpy.data.meshes.keys():
|
||||
blender_mesh = bpy.data.meshes[name]
|
||||
else:
|
||||
blender_mesh = bpy.data.meshes.new(name=name)
|
||||
return bpy.data.meshes[name]
|
||||
|
||||
blender_mesh = bpy.data.meshes.new(name=name)
|
||||
|
||||
fallback_material = get_render_material(element)
|
||||
|
||||
@@ -222,7 +228,13 @@ def meshes_to_native(element: Base, meshes: Collection[Mesh], name: str, scale:
|
||||
return blender_mesh
|
||||
|
||||
|
||||
"""
|
||||
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 []
|
||||
|
||||
line = blender_curve.splines.new("POLY")
|
||||
line.points.add(1)
|
||||
|
||||
@@ -233,97 +245,93 @@ def line_to_native(speckle_curve: Line, blender_curve: bpy.types.Curve, scale: f
|
||||
1,
|
||||
)
|
||||
|
||||
if speckle_curve.end:
|
||||
line.points[1].co = (
|
||||
float(speckle_curve.end.x) * scale,
|
||||
float(speckle_curve.end.y) * scale,
|
||||
float(speckle_curve.end.z) * scale,
|
||||
1,
|
||||
)
|
||||
|
||||
line.points[1].co = (
|
||||
float(speckle_curve.end.x) * scale,
|
||||
float(speckle_curve.end.y) * scale,
|
||||
float(speckle_curve.end.z) * scale,
|
||||
return [line]
|
||||
|
||||
|
||||
def polyline_to_native(scurve: Polyline, bcurve: bpy.types.Curve, scale: float) -> list[bpy.types.Spline]:
|
||||
if not (value := scurve.value): return []
|
||||
N = len(value) // 3
|
||||
|
||||
polyline = bcurve.splines.new("POLY")
|
||||
|
||||
if hasattr(scurve, "closed"):
|
||||
polyline.use_cyclic_u = scurve.closed
|
||||
|
||||
# if "closed" in scurve.keys():
|
||||
# polyline.use_cyclic_u = scurve["closed"]
|
||||
|
||||
polyline.points.add(N - 1)
|
||||
for i in range(N):
|
||||
polyline.points[i].co = (
|
||||
float(value[i * 3]) * scale,
|
||||
float(value[i * 3 + 1]) * scale,
|
||||
float(value[i * 3 + 2]) * scale,
|
||||
1,
|
||||
)
|
||||
|
||||
return [line]
|
||||
return []
|
||||
|
||||
def polyline_to_native(scurve: Polyline, bcurve: bpy.types.Curve, scale: float) -> list[bpy.types.Spline]:
|
||||
if value := scurve.value:
|
||||
N = len(value) // 3
|
||||
|
||||
polyline = bcurve.splines.new("POLY")
|
||||
|
||||
if hasattr(scurve, "closed"):
|
||||
polyline.use_cyclic_u = scurve.closed
|
||||
|
||||
# if "closed" in scurve.keys():
|
||||
# polyline.use_cyclic_u = scurve["closed"]
|
||||
|
||||
polyline.points.add(N - 1)
|
||||
for i in range(N):
|
||||
polyline.points[i].co = (
|
||||
float(value[i * 3]) * scale,
|
||||
float(value[i * 3 + 1]) * scale,
|
||||
float(value[i * 3 + 2]) * scale,
|
||||
1,
|
||||
)
|
||||
|
||||
return [polyline]
|
||||
return []
|
||||
return [polyline]
|
||||
|
||||
|
||||
|
||||
def nurbs_to_native(scurve: Curve, bcurve: bpy.types.Curve, scale: float) -> list[bpy.types.Spline]:
|
||||
if points := scurve.points:
|
||||
N = len(points) // 3
|
||||
if not (points := scurve.points): return []
|
||||
|
||||
# Closed curves from rhino will have n + degree points. We ignore the extras
|
||||
num_points = len(points) // 3 - scurve.degree if (scurve.closed) else (
|
||||
len(points) // 3)
|
||||
|
||||
nurbs = bcurve.splines.new("NURBS")
|
||||
nurbs.use_cyclic_u = scurve.closed
|
||||
nurbs.use_endpoint_u = not scurve.periodic
|
||||
|
||||
nurbs.points.add(num_points - 1)
|
||||
use_weights = len(scurve.weights) >= num_points
|
||||
for i in range(num_points):
|
||||
nurbs.points[i].co = (
|
||||
float(points[i * 3]) * scale,
|
||||
float(points[i * 3 + 1]) * scale,
|
||||
float(points[i * 3 + 2]) * scale,
|
||||
1,
|
||||
)
|
||||
|
||||
nurbs = bcurve.splines.new("NURBS")
|
||||
nurbs.points[i].weight = scurve.weights[i] if use_weights else 1
|
||||
|
||||
if hasattr(scurve, "closed"):
|
||||
nurbs.use_cyclic_u = scurve.closed != 0
|
||||
nurbs.order_u = scurve.degree + 1
|
||||
|
||||
nurbs.points.add(N - 1)
|
||||
for i in range(N):
|
||||
nurbs.points[i].co = (
|
||||
float(points[i * 3]) * scale,
|
||||
float(points[i * 3 + 1]) * scale,
|
||||
float(points[i * 3 + 2]) * scale,
|
||||
1,
|
||||
)
|
||||
|
||||
if len(scurve.weights) == len(nurbs.points):
|
||||
for i, w in enumerate(scurve.weights):
|
||||
nurbs.points[i].weight = w
|
||||
|
||||
# TODO: anaylize curve knots to decide if use_endpoint_u or use_bezier_u should be enabled
|
||||
# nurbs.use_endpoint_u = True
|
||||
nurbs.order_u = scurve.degree + 1
|
||||
|
||||
return [nurbs]
|
||||
return []
|
||||
return [nurbs]
|
||||
|
||||
|
||||
def arc_to_native(rcurve: Arc, bcurve: bpy.types.Curve, scale: float) -> Optional[bpy.types.Spline]:
|
||||
# TODO: improve Blender representation of arc
|
||||
# TODO: improve Blender representation of arc - check autocad test stream
|
||||
|
||||
plane = rcurve.plane
|
||||
if not plane:
|
||||
return None
|
||||
|
||||
normal = mathutils.Vector([plane.normal.x, plane.normal.y, plane.normal.z])
|
||||
normal = MVector([plane.normal.x, plane.normal.y, plane.normal.z])
|
||||
|
||||
radius = rcurve.radius * scale
|
||||
startAngle = rcurve.startAngle
|
||||
endAngle = rcurve.endAngle
|
||||
|
||||
startQuat = mathutils.Quaternion(normal, startAngle)
|
||||
endQuat = mathutils.Quaternion(normal, endAngle)
|
||||
startQuat = MQuaternion(normal, startAngle)
|
||||
endQuat = MQuaternion(normal, endAngle)
|
||||
|
||||
# Get start and end vectors, centre point, angles, etc.
|
||||
r1 = mathutils.Vector([plane.xdir.x, plane.xdir.y, plane.xdir.z])
|
||||
r1 = MVector([plane.xdir.x, plane.xdir.y, plane.xdir.z])
|
||||
r1.rotate(startQuat)
|
||||
|
||||
r2 = mathutils.Vector([plane.xdir.x, plane.xdir.y, plane.xdir.z])
|
||||
r2 = MVector([plane.xdir.x, plane.xdir.y, plane.xdir.z])
|
||||
r2.rotate(endQuat)
|
||||
|
||||
c = mathutils.Vector([plane.origin.x, plane.origin.y, plane.origin.z]) * scale
|
||||
c = MVector([plane.origin.x, plane.origin.y, plane.origin.z]) * scale
|
||||
|
||||
spt = c + r1 * radius
|
||||
ept = c + r2 * radius
|
||||
@@ -339,7 +347,7 @@ def arc_to_native(rcurve: Arc, bcurve: bpy.types.Curve, scale: float) -> Optiona
|
||||
|
||||
Ndiv = max(int(math.floor(angle / 0.3)), 2)
|
||||
step = angle / float(Ndiv)
|
||||
stepQuat = mathutils.Quaternion(normal, step)
|
||||
stepQuat = MQuaternion(normal, step)
|
||||
tan = math.tan(step / 2) * radius
|
||||
|
||||
arc.points.add(Ndiv + 1)
|
||||
@@ -380,6 +388,55 @@ def polycurve_to_native(scurve: Polycurve, bcurve: bpy.types.Curve, scale: float
|
||||
|
||||
return curves
|
||||
|
||||
def circle_to_native(circle: Circle, bcurve: bpy.types.Curve, units_scale: float) -> list[bpy.types.Spline]:
|
||||
#HACK: violates typing, but it works...
|
||||
circle["firstRadius"] = circle.radius
|
||||
circle["secondRadius"] = circle.radius
|
||||
return ellipse_to_native(circle, bcurve, units_scale)
|
||||
|
||||
def ellipse_to_native(ellipse: Ellipse, bcurve: bpy.types.Curve, units_scale: float) -> list[bpy.types.Spline]:
|
||||
plane = ellipse.plane
|
||||
|
||||
radX = ellipse.firstRadius * units_scale
|
||||
radY = ellipse.secondRadius * units_scale
|
||||
|
||||
D = 0.5522847498307936 # (4/3)*tan(pi/8)
|
||||
|
||||
right_handles = [
|
||||
(+radX, +radY * D, 0.0),
|
||||
(-radX * D, +radY, 0.0),
|
||||
(-radX, -radY * D, 0.0),
|
||||
(+radX * D, -radY, 0.0),
|
||||
]
|
||||
|
||||
left_handles = [
|
||||
(+radX, -radY * D, 0.0),
|
||||
(+radX * D, +radY, 0.0),
|
||||
(-radX, +radY * D, 0.0),
|
||||
(-radX * D, -radY, 0.0),
|
||||
]
|
||||
|
||||
points = [
|
||||
(+radX, 0.0, 0.0),
|
||||
(0.0, +radY, 0.0),
|
||||
(-radX, 0.0, 0.0),
|
||||
(0.0, -radY, 0.0),
|
||||
]
|
||||
transform = plane_to_native_transform(plane, units_scale)
|
||||
|
||||
spline = bcurve.splines.new("BEZIER")
|
||||
spline.bezier_points.add(len(points) - 1)
|
||||
|
||||
for i in range(len(points)):
|
||||
spline.bezier_points[i].co = transform @ MVector(points[i])
|
||||
spline.bezier_points[i].handle_left = transform @ MVector(left_handles[i])
|
||||
spline.bezier_points[i].handle_right = transform @ MVector(right_handles[i])
|
||||
|
||||
spline.use_cyclic_u = True
|
||||
|
||||
#TODO support trims?
|
||||
return [spline]
|
||||
|
||||
|
||||
def icurve_to_native_spline(speckle_curve: Base, blender_curve: bpy.types.Curve, scale: float) -> list[bpy.types.Spline]:
|
||||
# polycurves
|
||||
@@ -391,10 +448,14 @@ def icurve_to_native_spline(speckle_curve: Base, blender_curve: bpy.types.Curve,
|
||||
spline = line_to_native(speckle_curve, blender_curve, scale)
|
||||
elif isinstance(speckle_curve, Curve):
|
||||
spline = nurbs_to_native(speckle_curve, blender_curve, scale)
|
||||
elif isinstance(speckle_curve,Polyline):
|
||||
elif isinstance(speckle_curve, Polyline):
|
||||
spline = polyline_to_native(speckle_curve, blender_curve, scale)
|
||||
elif isinstance(speckle_curve, Arc):
|
||||
spline = arc_to_native(speckle_curve, blender_curve, scale)
|
||||
elif isinstance(speckle_curve, Ellipse):
|
||||
spline = ellipse_to_native(speckle_curve, blender_curve, scale)
|
||||
elif isinstance(speckle_curve, Circle):
|
||||
spline = circle_to_native(speckle_curve, blender_curve, scale)
|
||||
else:
|
||||
raise TypeError(f"{speckle_curve} is not a supported curve type. Supported types: {SUPPORTED_CURVES}")
|
||||
|
||||
@@ -412,15 +473,19 @@ def icurve_to_native(speckle_curve: Base, name: str, scale: float) -> Optional[b
|
||||
else bpy.data.curves.new(name, type="CURVE")
|
||||
)
|
||||
blender_curve.dimensions = "3D"
|
||||
blender_curve.resolution_u = 12
|
||||
blender_curve.resolution_u = 12 #TODO: We could maybe decern the resolution from the ployline displayValue
|
||||
|
||||
icurve_to_native_spline(speckle_curve, blender_curve, scale)
|
||||
|
||||
return blender_curve
|
||||
|
||||
|
||||
def transform_to_native(transform: Transform, scale: float) -> mathutils.Matrix:
|
||||
mat = mathutils.Matrix(
|
||||
"""
|
||||
Transforms and Intances
|
||||
"""
|
||||
|
||||
def transform_to_native(transform: Transform, scale: float) -> MMatrix:
|
||||
mat = MMatrix(
|
||||
[
|
||||
transform.value[:4],
|
||||
transform.value[4:8],
|
||||
@@ -433,38 +498,165 @@ def transform_to_native(transform: Transform, scale: float) -> mathutils.Matrix:
|
||||
mat[i][3] *= scale
|
||||
return mat
|
||||
|
||||
def plane_to_native_transform(plane: Plane, fallback_scale:float = 1) -> MMatrix:
|
||||
scale_factor = get_scale_factor(plane, fallback_scale)
|
||||
tx = (plane.origin.x * scale_factor)
|
||||
ty = (plane.origin.y * scale_factor)
|
||||
tz = (plane.origin.z * scale_factor)
|
||||
|
||||
def block_def_to_native(definition: BlockDefinition) -> bpy.types.Collection:
|
||||
native_def = bpy.data.collections.get(definition.name)
|
||||
return MMatrix((
|
||||
(plane.xdir.x, plane.xdir.y, plane.xdir.z , 0),
|
||||
(plane.ydir.x, plane.ydir.y, plane.ydir.z , 0),
|
||||
(plane.normal.x, plane.normal.y, plane.normal.z , 0),
|
||||
(tx, ty, tz, 1)
|
||||
)).transposed()
|
||||
|
||||
|
||||
"""
|
||||
Instances / Blocks
|
||||
"""
|
||||
|
||||
def _get_instance_name(instance: Instance) -> str:
|
||||
name_prefix = _speckle_object_name(instance) or _speckle_object_name(instance.definition) or _simplified_speckle_name(instance.speckle_type)
|
||||
return f"{name_prefix}{OBJECT_NAME_SEPERATOR}{instance.id}"
|
||||
|
||||
|
||||
def instance_to_native_object(instance: Instance, scale: float) -> Tuple[bpy.types.Object, List[bpy.types.Object]]:
|
||||
"""
|
||||
Converts Instance to a unique object with (potentially) shared data (linked duplicate)
|
||||
"""
|
||||
if not instance.definition: raise Exception(f"Instance is missing a definition")
|
||||
if not instance.transform: raise Exception(f"Instance is missing a transform")
|
||||
|
||||
name = _get_instance_name(instance)
|
||||
definition = instance.definition
|
||||
|
||||
native_instance: Object
|
||||
native_elements: List[Object] = []
|
||||
elements_on_instance: List[Object] = []
|
||||
|
||||
if isinstance(definition, BlockDefinition): #NOTE: We have to handle BlockDefinitions specially here, since they don't follow normal traversal rules
|
||||
native_instance = create_new_object(None, name) #Instance will be empty
|
||||
native_instance.empty_display_size = 0
|
||||
for geo in definition.geometry:
|
||||
native_elements.append(convert_to_native(geo)[-1])
|
||||
else:
|
||||
native_instance = convert_to_native(instance.definition)[-1] # Convert assuming that definition is convertable
|
||||
|
||||
instance_transform = transform_to_native(instance.transform, scale)
|
||||
instance_transform_inverted = instance_transform.inverted()
|
||||
native_instance.matrix_world = instance_transform
|
||||
|
||||
(_, elements_on_instance) = element_to_native(instance, name, scale)
|
||||
for c in elements_on_instance:
|
||||
c.matrix_world = instance_transform_inverted @ c.matrix_world #Undo the instance transform on elements
|
||||
|
||||
native_elements.extend(elements_on_instance)
|
||||
|
||||
return (native_instance, native_elements) #TODO: need to double check that all child objects have custom props attached correctly
|
||||
|
||||
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
|
||||
set to be the `instance.Definition` converted as a collection
|
||||
|
||||
The definition collection won't be linked to the current scene
|
||||
Any Elements on the instance object will also be converted (and spacially transformed)
|
||||
"""
|
||||
if not instance.definition: raise Exception(f"Instance is missing a definition")
|
||||
if not instance.transform: raise Exception(f"Instance is missing a transform")
|
||||
|
||||
name = _get_instance_name(instance)
|
||||
|
||||
# Get/Convert definition collection
|
||||
collection_def = _instance_definition_to_native(instance.definition)
|
||||
|
||||
# Convert elements as children of collection instance object
|
||||
(_, elements) = element_to_native(instance, name, scale, False)
|
||||
|
||||
instance_transform = transform_to_native(instance.transform, scale)
|
||||
instance_transform_inverted = instance_transform.inverted()
|
||||
|
||||
native_instance = bpy.data.objects.new(name, None)
|
||||
|
||||
#add_custom_properties(instance, native_instance)
|
||||
# hide the instance axes so they don't clutter the viewport
|
||||
native_instance.empty_display_size = 0
|
||||
native_instance.instance_collection = collection_def
|
||||
native_instance.instance_type = "COLLECTION"
|
||||
native_instance.matrix_world =instance_transform
|
||||
|
||||
for c in elements:
|
||||
c.matrix_world = instance_transform_inverted @ c.matrix_world #Undo the instance transform on elements
|
||||
c.parent = native_instance #TODO: need to double check that all child objects have custom props attached correctly
|
||||
|
||||
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)
|
||||
"""
|
||||
name = _generate_object_name(definition)
|
||||
native_def = bpy.data.collections.get(name)
|
||||
if native_def:
|
||||
return native_def
|
||||
|
||||
native_def = bpy.data.collections.new(definition.name)
|
||||
native_def = bpy.data.collections.new(name)
|
||||
native_def["applicationId"] = definition.applicationId
|
||||
for geo in definition.geometry:
|
||||
if b_obj := convert_to_native(geo):
|
||||
native_def.objects.link(
|
||||
b_obj
|
||||
if isinstance(b_obj, bpy_types.Object)
|
||||
else bpy.data.objects.new(b_obj.name, b_obj)
|
||||
)
|
||||
|
||||
#TODO could maybe replace BlockDefinition awareness with a single traverse member call
|
||||
geometry = definition.geometry if isinstance(definition, BlockDefinition) else [definition]
|
||||
|
||||
for geo in geometry:
|
||||
if not geo: continue
|
||||
converted = convert_to_native(geo)[-1] #NOTE: we assume the last item is the root converted item
|
||||
link_object_to_collection_nested(converted, native_def)
|
||||
|
||||
|
||||
return native_def
|
||||
|
||||
|
||||
def block_instance_to_native(instance: BlockInstance, scale: float) -> bpy.types.Object:
|
||||
"""
|
||||
Convert BlockInstance to native
|
||||
"""
|
||||
name = f"{getattr(instance, 'name', None) or instance.blockDefinition.name} -- {instance.id}"
|
||||
native_def = block_def_to_native(instance.blockDefinition)
|
||||
"""
|
||||
Object Naming
|
||||
"""
|
||||
|
||||
native_instance = bpy.data.objects.new(name, None)
|
||||
add_custom_properties(instance, native_instance)
|
||||
native_instance["name"] = getattr(instance, 'name', None) or instance.blockDefinition.name
|
||||
# hide the instance axes so they don't clutter the viewport
|
||||
native_instance.empty_display_size = 0
|
||||
native_instance.instance_collection = native_def
|
||||
native_instance.instance_type = "COLLECTION"
|
||||
native_instance.matrix_world = transform_to_native(instance.transform, scale)
|
||||
return native_instance
|
||||
def _speckle_object_name(speckle_object: Base) -> Optional[str]:
|
||||
return (getattr(speckle_object, "name", None)
|
||||
or getattr(speckle_object, "Name", None)
|
||||
or getattr(speckle_object, "family", None)
|
||||
)
|
||||
|
||||
|
||||
# Blender object names must not exceed 62 characters
|
||||
# We need to ensure the complete ID is included in the name (to prevent identity collisions)
|
||||
# So we if the name is too long, we need to truncate
|
||||
OBJECT_NAME_MAX_LENGTH = 62
|
||||
SPECKLE_ID_LENGTH = 32
|
||||
OBJECT_NAME_SEPERATOR = " -- "
|
||||
|
||||
def _truncate_name(name: str) -> str:
|
||||
|
||||
MAX_NAME_LENGTH = OBJECT_NAME_MAX_LENGTH - SPECKLE_ID_LENGTH - len(OBJECT_NAME_SEPERATOR)
|
||||
|
||||
return name[:MAX_NAME_LENGTH]
|
||||
|
||||
|
||||
def _simplified_speckle_name(speckle_type: str) -> str:
|
||||
return(speckle_type.rsplit('.')[-1]) #Take only the most specific object type name (without namespace)
|
||||
|
||||
def _generate_object_name(speckle_object: Base) -> str:
|
||||
prefix: str
|
||||
name = _speckle_object_name(speckle_object)
|
||||
if name:
|
||||
prefix = _truncate_name(name)
|
||||
else:
|
||||
prefix = _simplified_speckle_name(speckle_object.speckle_type)
|
||||
|
||||
return f"{prefix}{OBJECT_NAME_SEPERATOR}{speckle_object.id}"
|
||||
|
||||
|
||||
def get_scale_factor(speckle_object: Base, fallback: float = 1.0) -> float:
|
||||
scale = fallback
|
||||
if units := getattr(speckle_object, "units", None):
|
||||
scale = get_scale_length(units) / bpy.context.scene.unit_settings.scale_length
|
||||
return scale
|
||||
@@ -1,12 +1,19 @@
|
||||
from typing import Dict, Iterable, Optional, Tuple
|
||||
import bpy
|
||||
from bpy.types import Depsgraph, Material, MeshPolygon, Object
|
||||
from deprecated import deprecated
|
||||
from mathutils.geometry import interpolate_bezier
|
||||
from mathutils import (
|
||||
Matrix as MMatrix,
|
||||
Vector as MVector,
|
||||
)
|
||||
from specklepy.objects.geometry import Mesh, Curve, Interval, Box, Point, Polyline
|
||||
from specklepy.objects.other import *
|
||||
from bpy_speckle.functions import _report
|
||||
from bpy_speckle.convert.util import (
|
||||
get_blender_custom_properties,
|
||||
make_knots,
|
||||
nurb_make_curve,
|
||||
to_argb_int,
|
||||
)
|
||||
|
||||
@@ -117,7 +124,7 @@ def mesh_to_speckle(blender_object: Object, data: bpy.types.Mesh, scale: float =
|
||||
return submeshes
|
||||
|
||||
|
||||
def bezier_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: float, name: Optional[str] = None) -> Curve:
|
||||
def bezier_to_speckle(matrix: MMatrix, spline: bpy.types.Spline, scale: float, name: Optional[str] = None) -> Curve:
|
||||
degree = 3
|
||||
closed = spline.use_cyclic_u
|
||||
|
||||
@@ -155,50 +162,121 @@ def bezier_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: floa
|
||||
name=name,
|
||||
degree=degree,
|
||||
closed=spline.use_cyclic_u,
|
||||
periodic=spline.use_cyclic_u,
|
||||
periodic= not spline.use_endpoint_u,
|
||||
points=flattend_points,
|
||||
weights=[1] * num_points,
|
||||
knots=knots,
|
||||
rational=False,
|
||||
rational=True,
|
||||
area=0,
|
||||
volume=0,
|
||||
length=length,
|
||||
domain=domain,
|
||||
units=UNITS,
|
||||
bbox=Box(area=0.0, volume=0.0),
|
||||
displayValue = bezier_to_speckle_polyline(matrix, spline, scale, length),
|
||||
)
|
||||
|
||||
|
||||
def nurbs_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: float, name: Optional[str] = None) -> Curve:
|
||||
knots = make_knots(spline)
|
||||
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
|
||||
def nurbs_to_speckle(matrix: MMatrix, spline: bpy.types.Spline, scale: float, name: Optional[str] = None) -> Curve:
|
||||
|
||||
degree = spline.order_u - 1
|
||||
knots = make_knots(spline)
|
||||
|
||||
length = spline.calc_length()
|
||||
domain = Interval(start=0, end=length, totalChildrenCount=0)
|
||||
|
||||
weights = [pt.weight for pt in spline.points]
|
||||
is_rational = all(w == weights[0] for w in weights)
|
||||
|
||||
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
|
||||
|
||||
flattend_points = []
|
||||
for row in points: flattend_points.extend(row)
|
||||
|
||||
if spline.use_cyclic_u:
|
||||
for i in range(0, degree * 3, 3):
|
||||
# Rhino expects n + degree number of points (for closed curves). So we need to add an extra point for each degree
|
||||
flattend_points.append(flattend_points[i + 0])
|
||||
flattend_points.append(flattend_points[i + 1])
|
||||
flattend_points.append(flattend_points[i + 2])
|
||||
|
||||
for i in range(0, degree):
|
||||
weights.append(weights[i])
|
||||
|
||||
return Curve(
|
||||
name=name,
|
||||
degree=degree,
|
||||
closed=spline.use_cyclic_u,
|
||||
periodic=spline.use_cyclic_u,
|
||||
periodic= not spline.use_endpoint_u,
|
||||
points=flattend_points,
|
||||
weights=[pt.weight for pt in spline.points],
|
||||
weights=weights,
|
||||
knots=knots,
|
||||
rational=False,
|
||||
rational=is_rational,
|
||||
area=0,
|
||||
volume=0,
|
||||
length=length,
|
||||
domain=domain,
|
||||
units=UNITS,
|
||||
bbox=Box(area=0.0, volume=0.0),
|
||||
displayValue=nurbs_to_speckle_polyline(matrix, spline, scale, length),
|
||||
)
|
||||
|
||||
def nurbs_to_speckle_polyline(matrix: MMatrix, spline: bpy.types.Spline, scale: float, length: Optional[float] = None) -> Polyline:
|
||||
"""
|
||||
Samples a nurbs curve with resolution_u creating a polyline
|
||||
"""
|
||||
points = []
|
||||
sampled_points = nurb_make_curve(spline, spline.resolution_u, 3)
|
||||
for i in range(0, len(sampled_points), 3):
|
||||
scaled_point = matrix @ MVector((
|
||||
sampled_points[i + 0],
|
||||
sampled_points[i + 1],
|
||||
sampled_points[i + 2])) * scale
|
||||
|
||||
def poly_to_speckle(matrix: List[float], spline: bpy.types.Spline, scale: float, name: Optional[str] = None) -> Polyline:
|
||||
points.append(scaled_point.x)
|
||||
points.append(scaled_point.y)
|
||||
points.append(scaled_point.z)
|
||||
|
||||
length = length or spline.calc_length()
|
||||
domain = Interval(start=0, end=length, totalChildrenCount=0)
|
||||
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)
|
||||
def bezier_to_speckle_polyline(matrix: MMatrix, spline: bpy.types.Spline, scale: float, length: Optional[float] = None) -> Optional[Polyline]:
|
||||
"""
|
||||
Samples a Bézier curve with resolution_u creating a polyline
|
||||
"""
|
||||
segments = len(spline.bezier_points)
|
||||
if segments < 2: return None
|
||||
|
||||
R = spline.resolution_u + 1
|
||||
|
||||
points = []
|
||||
if not spline.use_cyclic_u:
|
||||
segments -= 1
|
||||
|
||||
points: List[float] = []
|
||||
for i in range(segments):
|
||||
inext = (i + 1) % len(spline.bezier_points)
|
||||
|
||||
knot1 = spline.bezier_points[i].co
|
||||
handle1 = spline.bezier_points[i].handle_right
|
||||
handle2 = spline.bezier_points[inext].handle_left
|
||||
knot2 = spline.bezier_points[inext].co
|
||||
|
||||
_points = interpolate_bezier(knot1, handle1, handle2, knot2, R)
|
||||
for p in _points:
|
||||
scaled_point = matrix @ p * scale
|
||||
points.append(scaled_point.x)
|
||||
points.append(scaled_point.y)
|
||||
points.append(scaled_point.z)
|
||||
|
||||
length = length or spline.calc_length()
|
||||
domain = Interval(start=0, end=length, totalChildrenCount=0)
|
||||
return Polyline(value=points, closed = spline.use_cyclic_u, domain=domain, area=0, len=length)
|
||||
|
||||
def poly_to_speckle(matrix: MMatrix, spline: bpy.types.Spline, scale: float, name: Optional[str] = None) -> Polyline:
|
||||
points = [tuple(matrix @ pt.co.xyz * scale) for pt in spline.points]
|
||||
|
||||
flattend_points = []
|
||||
@@ -246,7 +324,7 @@ def icurve_to_speckle(blender_object: Object, data: bpy.types.Curve, scale=1.0)
|
||||
|
||||
return curves
|
||||
|
||||
|
||||
@deprecated
|
||||
def ngons_to_speckle_polylines(blender_object: Object, data: bpy.types.Mesh, scale=1.0) -> Optional[List[Polyline]]:
|
||||
UNITS = "m" if bpy.context.scene.unit_settings.system == "METRIC" else "ft"
|
||||
|
||||
|
||||
+135
-26
@@ -4,7 +4,7 @@ from bmesh.types import BMesh
|
||||
import bpy, struct, idprop
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry import Mesh
|
||||
from specklepy.objects.geometry import Circle, Mesh, Ellipse
|
||||
from specklepy.objects.other import RenderMaterial
|
||||
from bpy_speckle.functions import _report
|
||||
from bpy.types import Material, Object
|
||||
@@ -138,7 +138,7 @@ def add_vertices(speckle_mesh: Mesh, blender_mesh: BMesh, scale=1.0):
|
||||
|
||||
|
||||
|
||||
def add_faces(speckle_mesh: Mesh, blender_mesh: BMesh, indexOffset: int, materialIndex: int = 0, smooth:bool = False):
|
||||
def add_faces(speckle_mesh: Mesh, blender_mesh: BMesh, indexOffset: int, materialIndex: int = 0, smooth:bool = True):
|
||||
sfaces = speckle_mesh.faces
|
||||
|
||||
if sfaces and len(sfaces) > 0:
|
||||
@@ -252,27 +252,23 @@ def get_blender_custom_properties(obj, max_depth=1000):
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
"""
|
||||
Python implementation of Blender's NURBS curve generation for to Speckle conversion
|
||||
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)
|
||||
"""
|
||||
|
||||
|
||||
def macro_knotsu(nu: bpy.types.Spline) -> int:
|
||||
return nu.order_u + nu.point_count_u + (nu.order_u - 1 if nu.use_cyclic_u else 0)
|
||||
|
||||
|
||||
def macro_segmentsu(nu: bpy.types.Spline) -> int:
|
||||
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]:
|
||||
knots = [0.0] * (4 + macro_knotsu(nu))
|
||||
knots = [0.0] * macro_knotsu(nu)
|
||||
flag = nu.use_endpoint_u + (nu.use_bezier_u << 1)
|
||||
if nu.use_cyclic_u:
|
||||
calc_knots(knots, nu.point_count_u, nu.order_u, 0)
|
||||
makecyclicknots(knots, nu.point_count_u, nu.order_u)
|
||||
else:
|
||||
calc_knots(knots, nu.point_count_u, nu.order_u, flag)
|
||||
return knots
|
||||
@@ -280,13 +276,13 @@ def make_knots(nu: bpy.types.Spline) -> list[float]:
|
||||
|
||||
def calc_knots(knots: list[float], point_count: int, order: int, flag: int) -> None:
|
||||
pts_order = point_count + order
|
||||
if flag == 1:
|
||||
if flag == 1: # CU_NURB_ENDPOINT
|
||||
k = 0.0
|
||||
for a in range(1, pts_order + 1):
|
||||
knots[a - 1] = k
|
||||
if a >= order and a <= point_count:
|
||||
k += 1.0
|
||||
elif flag == 2:
|
||||
elif flag == 2: # CU_NURB_BEZIER
|
||||
if order == 4:
|
||||
k = 0.34
|
||||
for a in range(pts_order):
|
||||
@@ -299,24 +295,137 @@ def calc_knots(knots: list[float], point_count: int, order: int, flag: int) -> N
|
||||
k += 0.5
|
||||
knots[a] = math.floor(k)
|
||||
else:
|
||||
for a in range(pts_order):
|
||||
knots[a] = a
|
||||
for a in range(1, len(knots) - 1):
|
||||
knots[a] = a - 1
|
||||
|
||||
knots[-1] = knots[-2]
|
||||
|
||||
def makecyclicknots(knots: list[float], point_count: int, order: int) -> None:
|
||||
order2 = order - 1
|
||||
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
|
||||
orderpluspnts = order + point_count
|
||||
opp2 = orderpluspnts - 1
|
||||
|
||||
if order > 2:
|
||||
b = point_count + order2
|
||||
for a in range(1, order2):
|
||||
if knots[b] != knots[b - a]:
|
||||
break
|
||||
# this is for float inaccuracy
|
||||
if t < knots[0]:
|
||||
t = knots[0]
|
||||
elif t > knots[opp2]:
|
||||
t = knots[opp2]
|
||||
|
||||
if a == order2:
|
||||
knots[point_count + order - 2] += 1.0
|
||||
# this part is order '1'
|
||||
o2 = order + 1
|
||||
for i in range(opp2):
|
||||
if knots[i] != knots[i + 1] and t >= knots[i] and t <= knots[i + 1]:
|
||||
basis[i] = 1.0
|
||||
i1 = i - o2
|
||||
if i1 < 0:
|
||||
i1 = 0
|
||||
i2 = i
|
||||
i += 1
|
||||
while i < opp2:
|
||||
basis[i] = 0.0
|
||||
i += 1
|
||||
break
|
||||
|
||||
b = order
|
||||
c = point_count + order + order2
|
||||
for a in range(point_count + order2, c):
|
||||
knots[a] = knots[a - 1] + (knots[b] - knots[b - 1])
|
||||
b -= 1
|
||||
else:
|
||||
basis[i] = 0.0
|
||||
|
||||
basis[i] = 0.0
|
||||
|
||||
# this is order 2, 3, ...
|
||||
for j in range(2, order + 1):
|
||||
|
||||
if i2 + j >= orderpluspnts:
|
||||
i2 = opp2 - j
|
||||
|
||||
for i in range(i1, i2 + 1):
|
||||
if basis[i] != 0.0:
|
||||
d = ((t - knots[i]) * basis[i]) / (knots[i + j - 1] - knots[i])
|
||||
else:
|
||||
d = 0.0
|
||||
|
||||
if basis[i + 1] != 0.0:
|
||||
e = ((knots[i + j] - t) * basis[i + 1]) / (knots[i + j] - knots[i + 1])
|
||||
else:
|
||||
e = 0.0
|
||||
|
||||
basis[i] = d + e
|
||||
|
||||
start = 1000
|
||||
end = 0
|
||||
|
||||
for i in range(i1, i2 + 1):
|
||||
if basis[i] > 0.0:
|
||||
end = i
|
||||
if start == 1000:
|
||||
start = i
|
||||
|
||||
return start, end
|
||||
|
||||
def nurb_make_curve(nu: bpy.types.Spline, resolu: int, stride: int = 3) -> list[float]:
|
||||
""""BKE_nurb_makeCurve"""
|
||||
EPS = 1e-6
|
||||
coord_index = istart = iend = 0
|
||||
|
||||
coord_array = [0.0] * (3 * nu.resolution_u * macro_segmentsu(nu))
|
||||
sum_array = [0] * nu.point_count_u
|
||||
basisu = [0.0] * macro_knotsu(nu)
|
||||
knots = make_knots(nu)
|
||||
|
||||
resolu = resolu * macro_segmentsu(nu)
|
||||
ustart = knots[nu.order_u - 1]
|
||||
uend = knots[nu.point_count_u + nu.order_u - 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
|
||||
|
||||
u = ustart
|
||||
while resolu:
|
||||
resolu -= 1
|
||||
istart, iend = basis_nurb(u, nu.order_u, nu.point_count_u + cycl, knots, basisu, istart, iend)
|
||||
|
||||
#/* calc sum */
|
||||
sumdiv = 0.0
|
||||
sum_index = 0
|
||||
pt_index = istart - 1
|
||||
for i in range(istart, iend + 1):
|
||||
if i >= nu.point_count_u:
|
||||
pt_index = i - nu.point_count_u
|
||||
else:
|
||||
pt_index += 1
|
||||
|
||||
sum_array[sum_index] = basisu[i] * nu.points[pt_index].co[3]
|
||||
sumdiv += sum_array[sum_index]
|
||||
sum_index += 1
|
||||
|
||||
if (sumdiv != 0.0) and (sumdiv < 1.0 - EPS or sumdiv > 1.0 + EPS):
|
||||
sum_index = 0
|
||||
for i in range(istart, iend + 1):
|
||||
sum_array[sum_index] /= sumdiv
|
||||
sum_index += 1
|
||||
|
||||
coord_array[coord_index: coord_index + 3] = (0.0, 0.0, 0.0)
|
||||
|
||||
sum_index = 0
|
||||
pt_index = istart - 1
|
||||
for i in range(istart, iend + 1):
|
||||
if i >= nu.point_count_u:
|
||||
pt_index = i - nu.point_count_u
|
||||
else:
|
||||
pt_index += 1
|
||||
|
||||
if sum_array[sum_index] != 0.0:
|
||||
for j in range(3):
|
||||
coord_array[coord_index + j] += sum_array[sum_index] * nu.points[pt_index].co[j]
|
||||
sum_index += 1
|
||||
|
||||
coord_index += stride
|
||||
u += ustep
|
||||
|
||||
return coord_array
|
||||
|
||||
def link_object_to_collection_nested(obj: bpy.types.Object, col: bpy.types.Collection):
|
||||
if obj.name not in col.objects:
|
||||
col.objects.link(obj)
|
||||
|
||||
for child in obj.children:
|
||||
link_object_to_collection_nested(child, col)
|
||||
@@ -1,4 +1,4 @@
|
||||
from .users import LoadUsers, LoadUserStreams
|
||||
from .users import LoadUsers, LoadUserStreams, ResetUsers
|
||||
from .object import (
|
||||
UpdateObject,
|
||||
ResetObject,
|
||||
@@ -27,6 +27,7 @@ from .misc import OpenSpeckleGuide, OpenSpeckleTutorials, OpenSpeckleForum
|
||||
|
||||
operator_classes = [
|
||||
LoadUsers,
|
||||
ResetUsers,
|
||||
ReceiveStreamObjects,
|
||||
SendStreamObjects,
|
||||
LoadUserStreams,
|
||||
|
||||
@@ -3,13 +3,15 @@ Commit operators
|
||||
"""
|
||||
import bpy
|
||||
from bpy.props import BoolProperty
|
||||
from bpy_speckle.functions import _check_speckle_client_user_stream
|
||||
from bpy_speckle.functions import _check_speckle_client_user_stream, _report
|
||||
from bpy_speckle.clients import speckle_clients
|
||||
from bpy_speckle.properties.scene import SpeckleSceneSettings
|
||||
|
||||
|
||||
class DeleteCommit(bpy.types.Operator):
|
||||
"""
|
||||
Delete stream
|
||||
Deletes the selected commit from the selected stream.
|
||||
To execute from code, call: `bpy.ops.speckle.delete_commit(are_you_sure=True)`
|
||||
"""
|
||||
|
||||
bl_idname = "speckle.delete_commit"
|
||||
@@ -37,27 +39,39 @@ class DeleteCommit(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
|
||||
if not self.are_you_sure:
|
||||
_report(f"{self.bl_idname}: cancelled by user")
|
||||
return {"CANCELLED"}
|
||||
|
||||
self.are_you_sure = False
|
||||
|
||||
speckle = context.scene.speckle
|
||||
speckle: SpeckleSceneSettings = context.scene.speckle
|
||||
|
||||
check = _check_speckle_client_user_stream(context.scene)
|
||||
if check is None:
|
||||
user = speckle.get_active_user()
|
||||
if user is None:
|
||||
print(f"{self.bl_idname}: failed - No user selected/found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
user, stream = check
|
||||
client = speckle_clients[int(context.scene.speckle.active_user)]
|
||||
stream = user.get_active_stream()
|
||||
if stream is None:
|
||||
print(f"{self.bl_idname}: failed - No stream selected/found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
stream = user.streams[user.active_stream]
|
||||
if len(stream.branches) < 1:
|
||||
branch = stream.get_active_branch()
|
||||
if branch is None:
|
||||
print(f"{self.bl_idname}: failed - No branch selected/found")
|
||||
return {"CANCELLED"}
|
||||
branch = stream.branches[int(stream.branch)]
|
||||
if len(branch.commits) < 1:
|
||||
|
||||
commit = branch.get_active_commit()
|
||||
if commit is None:
|
||||
print(f"{self.bl_idname}: failed - No commit selected/found")
|
||||
return {"CANCELLED"}
|
||||
commit = branch.commits[int(branch.commit)]
|
||||
|
||||
client = speckle_clients[int(speckle.active_user)]
|
||||
|
||||
deleted = client.commit.delete(stream_id=stream.id, commit_id=commit.id)
|
||||
if not deleted:
|
||||
print(f"{self.bl_idname}: failed - Delete operation failed")
|
||||
return {"CANCELLED"}
|
||||
|
||||
print(f"{self.bl_idname}: succeeded - commit {commit.id} ({commit.message}) has been deleted from stream {stream.id}")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -28,7 +28,6 @@ class UpdateObject(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
user = context.scene.speckle.users[int(context.scene.speckle.active_user)]
|
||||
client = speckle_clients[int(context.scene.speckle.active_user)]
|
||||
stream = user.streams[user.active_stream]
|
||||
|
||||
active = context.active_object
|
||||
_report(active)
|
||||
|
||||
@@ -5,6 +5,8 @@ from itertools import chain
|
||||
from math import radians
|
||||
from typing import Callable, Dict, Iterable, Optional
|
||||
import bpy
|
||||
from bpy_speckle.convert.util import link_object_to_collection_nested
|
||||
from bpy_speckle.properties.scene import SpeckleSceneSettings
|
||||
from specklepy.api.models import Commit
|
||||
import webbrowser
|
||||
from bpy.props import (
|
||||
@@ -13,7 +15,11 @@ from bpy.props import (
|
||||
EnumProperty,
|
||||
)
|
||||
from bpy.types import Context, Object
|
||||
from bpy_speckle.convert.to_native import can_convert_to_native, convert_to_native
|
||||
from bpy_speckle.convert.to_native import (
|
||||
can_convert_to_native,
|
||||
convert_to_native,
|
||||
set_convert_instances_as,
|
||||
)
|
||||
from bpy_speckle.convert.to_speckle import (
|
||||
convert_to_speckle,
|
||||
)
|
||||
@@ -187,7 +193,7 @@ def base_to_native(context: bpy.types.Context,
|
||||
# new_objects.extend(
|
||||
# get_speckle_subobjects(base["properties"], scale, base["id"])
|
||||
# )
|
||||
|
||||
|
||||
"""
|
||||
Set object Speckle settings
|
||||
"""
|
||||
@@ -216,8 +222,9 @@ def base_to_native(context: bpy.types.Context,
|
||||
new_object.name = name
|
||||
col.objects.unlink(existing[new_object.speckle.object_id])
|
||||
|
||||
if new_object.name not in col.objects:
|
||||
col.objects.link(new_object)
|
||||
link_object_to_collection_nested(new_object, col)
|
||||
#if new_object.name not in col.objects:
|
||||
#col.objects.link(new_object)
|
||||
|
||||
|
||||
def create_collection(name: str, clear_collection=True) -> bpy.types.Collection:
|
||||
@@ -272,19 +279,23 @@ def create_nested_hierarchy(base: Base, hierarchy: List[str], objects: Any):
|
||||
child.add_detachable_attrs({name})
|
||||
child = child[name]
|
||||
|
||||
# TODO: what do we call this attribute?
|
||||
if not hasattr(child, "@objects"):
|
||||
child["@objects"] = []
|
||||
child["@objects"].extend(objects)
|
||||
if not hasattr(child, "@elements"):
|
||||
child["@elements"] = []
|
||||
child["@elements"].extend(objects)
|
||||
|
||||
return base
|
||||
|
||||
#RECEIVE_MODES = [#TODO: modes
|
||||
# ("create", "Create", "Add new geometry, without removing any existing objects"),
|
||||
# ("replace", "Replace", "Replace objects from previous receive operations from the same stream"),
|
||||
# #("update","Update") #TODO: update mode!
|
||||
# #("update","Update", "") #TODO: update mode!
|
||||
#]
|
||||
|
||||
INSTANCES_SETTINGS = [
|
||||
("collection_instance", "Collection Instace", "Receive Instances as Collection Instances"),
|
||||
("linked_duplicates", "Linked Duplicates", "Receive Instances as Linked Duplicates"),
|
||||
]
|
||||
|
||||
class ReceiveStreamObjects(bpy.types.Operator):
|
||||
"""
|
||||
Receive stream objects
|
||||
@@ -299,13 +310,15 @@ class ReceiveStreamObjects(bpy.types.Operator):
|
||||
clean_meshes: BoolProperty(name="Clean Meshes", default=False)
|
||||
|
||||
#receive_mode: EnumProperty(items=RECEIVE_MODES, name="Receive Type", default="replace", description="The behaviour of the recieve operation")
|
||||
|
||||
receive_instances_as: EnumProperty(items=INSTANCES_SETTINGS, name="Receive Instances As", default="collection_instance", description="How to receive speckle Instances")
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
col.prop(self, "clean_meshes")
|
||||
#col.prop(self, "receive_mode")
|
||||
col.prop(self, "receive_instances_as")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
@@ -341,30 +354,31 @@ class ReceiveStreamObjects(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
bpy.context.view_layer.objects.active = None
|
||||
|
||||
check = _check_speckle_client_user_stream(context.scene)
|
||||
if check is None:
|
||||
speckle: SpeckleSceneSettings = context.scene.speckle
|
||||
|
||||
#Get UI Selection
|
||||
user = speckle.get_active_user()
|
||||
if not user:
|
||||
print("No user selected/found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
user, bstream = check
|
||||
|
||||
client = speckle_clients[int(context.scene.speckle.active_user)]
|
||||
|
||||
stream = client.stream.get(id=bstream.id, branch_limit=20)
|
||||
if stream.branches.totalCount < 1:
|
||||
stream = user.get_active_stream()
|
||||
if not stream:
|
||||
print("No stream selected/found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if not stream.branches:
|
||||
branch = stream.get_active_branch()
|
||||
if not branch:
|
||||
print("No branch selected/found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
branch = stream.branches.items[int(bstream.branch)]
|
||||
|
||||
bbranch = bstream.branches[int(bstream.branch)]
|
||||
|
||||
if branch.commits.totalCount < 1:
|
||||
_report("No commits found. Probably an empty stream.")
|
||||
commit = branch.get_active_commit()
|
||||
if commit is None:
|
||||
print("No commit selected/found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
commit: Commit = branch.commits.items[int(bbranch.commit)]
|
||||
#Get actual stream data
|
||||
client = speckle_clients[int(speckle.active_user)]
|
||||
|
||||
transport = ServerTransport(stream.id, client)
|
||||
|
||||
@@ -372,27 +386,29 @@ class ReceiveStreamObjects(bpy.types.Operator):
|
||||
metrics.RECEIVE,
|
||||
getattr(transport, "account", None),
|
||||
custom_props={
|
||||
"sourceHostApp": host_applications.get_host_app_from_string(commit.sourceApplication).slug,
|
||||
"sourceHostAppVersion": commit.sourceApplication
|
||||
"sourceHostApp": host_applications.get_host_app_from_string(commit.source_application).slug,
|
||||
"sourceHostAppVersion": commit.source_application
|
||||
},
|
||||
)
|
||||
stream_data = operations._untracked_receive(commit.referencedObject, transport)
|
||||
commit_object = operations._untracked_receive(commit.referenced_object, transport)
|
||||
client.commit.received(
|
||||
bstream.id,
|
||||
stream.id,
|
||||
commit.id,
|
||||
source_application="blender",
|
||||
message="received commit from Speckle Blender",
|
||||
)
|
||||
|
||||
context.window_manager.progress_begin(0, stream_data.totalChildrenCount)
|
||||
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
|
||||
|
||||
"""
|
||||
Create or get Collection for stream objects
|
||||
"""
|
||||
collections = get_objects_collections(stream_data)
|
||||
collections = get_objects_collections(commit_object)
|
||||
|
||||
if not collections:
|
||||
print("Unusual commit structure - did not correctly create collections")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# name = ""
|
||||
@@ -403,7 +419,7 @@ class ReceiveStreamObjects(bpy.types.Operator):
|
||||
|
||||
col = create_collection(name)
|
||||
col.speckle.stream_id = stream.id
|
||||
col.speckle.units = stream_data.units or "m"
|
||||
col.speckle.units = commit_object.units or "m"
|
||||
|
||||
if col.name not in bpy.context.scene.collection.children:
|
||||
bpy.context.scene.collection.children.link(col)
|
||||
@@ -490,10 +506,11 @@ class SendStreamObjects(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
check = _check_speckle_client_user_stream(context.scene)
|
||||
if check is None:
|
||||
user, bstream = check
|
||||
|
||||
if user is None:
|
||||
return {"CANCELLED"}
|
||||
|
||||
user, bstream = check
|
||||
stream = user.streams[user.active_stream]
|
||||
branch = stream.branches[int(stream.branch)]
|
||||
|
||||
@@ -572,17 +589,20 @@ class SendStreamObjects(bpy.types.Operator):
|
||||
|
||||
transport = ServerTransport(stream.id, client)
|
||||
|
||||
_report(f"Sending to {stream}")
|
||||
obj_id = operations.send(
|
||||
base,
|
||||
[transport],
|
||||
)
|
||||
client.commit.create(
|
||||
|
||||
commitId = client.commit.create(
|
||||
stream.id,
|
||||
obj_id,
|
||||
branch.name,
|
||||
message=self.commit_message,
|
||||
source_application="blender",
|
||||
)
|
||||
_report(f"Commit Created {user.server_url}/streams/{stream.id}/commits/{commitId}")
|
||||
|
||||
bpy.ops.speckle.load_user_streams()
|
||||
|
||||
|
||||
@@ -4,12 +4,35 @@ User account operators
|
||||
import bpy
|
||||
from bpy_speckle.functions import _report
|
||||
from bpy_speckle.clients import speckle_clients
|
||||
from bpy_speckle.properties.scene import SpeckleSceneSettings
|
||||
from bpy_speckle.properties.scene import SpeckleCommitObject, SpeckleSceneSettings
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.api.models import Stream, User
|
||||
from specklepy.api.credentials import get_local_accounts
|
||||
from datetime import datetime
|
||||
|
||||
class ResetUsers(bpy.types.Operator):
|
||||
"""
|
||||
Reset loaded users
|
||||
"""
|
||||
|
||||
bl_idname = "speckle.users_reset"
|
||||
bl_label = "Reset users"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.reset_ui(context)
|
||||
|
||||
bpy.context.view_layer.update()
|
||||
if context.area:
|
||||
context.area.tag_redraw()
|
||||
return {"FINISHED"}
|
||||
|
||||
@staticmethod
|
||||
def reset_ui(context: bpy.types.Context):
|
||||
speckle: SpeckleSceneSettings = context.scene.speckle
|
||||
|
||||
speckle.users.clear()
|
||||
speckle_clients.clear()
|
||||
|
||||
class LoadUsers(bpy.types.Operator):
|
||||
"""
|
||||
@@ -24,11 +47,10 @@ class LoadUsers(bpy.types.Operator):
|
||||
|
||||
_report("Loading users...")
|
||||
|
||||
speckle : SpeckleSceneSettings = context.scene.speckle
|
||||
speckle: SpeckleSceneSettings = context.scene.speckle
|
||||
users = speckle.users
|
||||
|
||||
speckle.users.clear()
|
||||
speckle_clients.clear()
|
||||
ResetUsers.reset_ui(context)
|
||||
|
||||
profiles = get_local_accounts()
|
||||
active_user_index = 0
|
||||
@@ -53,7 +75,7 @@ class LoadUsers(bpy.types.Operator):
|
||||
if profile.isDefault:
|
||||
active_user_index = len(users) - 1
|
||||
|
||||
speckle.active_user_index = int(speckle.active_user)
|
||||
#speckle.active_user_index = int(speckle.active_user) #TODO Wtf is this?
|
||||
speckle.active_user = str(active_user_index)
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
@@ -80,13 +102,14 @@ def add_user_stream(user: User, stream: Stream):
|
||||
continue
|
||||
|
||||
for c in b.commits.items:
|
||||
commit = branch.commits.add()
|
||||
commit: SpeckleCommitObject = branch.commits.add()
|
||||
commit.id = commit.name = c.id
|
||||
commit.message = c.message or ""
|
||||
commit.author_name = c.authorName
|
||||
commit.author_id = c.authorId
|
||||
commit.created_at = datetime.strftime(c.createdAt, "%Y-%m-%d %H:%M:%S.%f%Z")
|
||||
commit.source_application = str(c.sourceApplication)
|
||||
commit.referenced_object = c.referencedObject
|
||||
|
||||
if hasattr(s, "baseProperties"):
|
||||
s.units = stream.baseProperties.units
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Scene properties
|
||||
"""
|
||||
from typing import Optional
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
StringProperty,
|
||||
@@ -18,12 +19,13 @@ class SpeckleSceneObject(bpy.types.PropertyGroup):
|
||||
|
||||
|
||||
class SpeckleCommitObject(bpy.types.PropertyGroup):
|
||||
id: StringProperty(default="abc")
|
||||
message: StringProperty(default="A simple commit")
|
||||
author_name: StringProperty(default="Author name")
|
||||
author_id: StringProperty(default="Author ID")
|
||||
created_at: StringProperty(default="Today")
|
||||
source_application: StringProperty(default="Unknown")
|
||||
id: StringProperty(default="")
|
||||
message: StringProperty(default="")
|
||||
author_name: StringProperty(default="")
|
||||
author_id: StringProperty(default="")
|
||||
created_at: StringProperty(default="")
|
||||
source_application: StringProperty(default="")
|
||||
referenced_object: StringProperty(default="")
|
||||
|
||||
|
||||
class SpeckleBranchObject(bpy.types.PropertyGroup):
|
||||
@@ -42,6 +44,12 @@ class SpeckleBranchObject(bpy.types.PropertyGroup):
|
||||
description="Active commit",
|
||||
items=get_commits,
|
||||
)
|
||||
|
||||
def get_active_commit(self) -> Optional[SpeckleCommitObject]:
|
||||
selected_index = int(self.commit)
|
||||
if 0 <= selected_index < len(self.commits):
|
||||
return self.commits[selected_index]
|
||||
return None
|
||||
|
||||
|
||||
class SpeckleStreamObject(bpy.types.PropertyGroup):
|
||||
@@ -66,6 +74,12 @@ class SpeckleStreamObject(bpy.types.PropertyGroup):
|
||||
items=get_branches,
|
||||
)
|
||||
|
||||
def get_active_branch(self) -> Optional[SpeckleBranchObject]:
|
||||
selected_index = int(self.branch)
|
||||
if 0 <= selected_index < len(self.branches):
|
||||
return self.branches[selected_index]
|
||||
return None
|
||||
|
||||
|
||||
class SpeckleUserObject(bpy.types.PropertyGroup):
|
||||
server_name: StringProperty(default="SpeckleXYZ")
|
||||
@@ -76,6 +90,11 @@ class SpeckleUserObject(bpy.types.PropertyGroup):
|
||||
streams: CollectionProperty(type=SpeckleStreamObject)
|
||||
active_stream: IntProperty(default=0)
|
||||
|
||||
def get_active_stream(self) -> Optional[SpeckleStreamObject]:
|
||||
selected_index = int(self.active_stream)
|
||||
if 0 <= selected_index < len(self.streams):
|
||||
return self.streams[selected_index]
|
||||
return None
|
||||
|
||||
class SpeckleSceneSettings(bpy.types.PropertyGroup):
|
||||
def get_scripts(self, context):
|
||||
@@ -103,8 +122,8 @@ class SpeckleSceneSettings(bpy.types.PropertyGroup):
|
||||
|
||||
active_user: EnumProperty(
|
||||
items=get_users,
|
||||
name="User",
|
||||
description="Select user",
|
||||
name="Account",
|
||||
description="Select account",
|
||||
update=set_user,
|
||||
get=None,
|
||||
set=None,
|
||||
@@ -131,3 +150,9 @@ class SpeckleSceneSettings(bpy.types.PropertyGroup):
|
||||
description="Script to run when sending stream objects.",
|
||||
items=get_scripts,
|
||||
)
|
||||
|
||||
def get_active_user(self) -> Optional[SpeckleUserObject]:
|
||||
selected_index = int(self.active_user)
|
||||
if 0 < selected_index < len(self.users):
|
||||
return self.users[selected_index]
|
||||
return None
|
||||
|
||||
Generated
+556
-558
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -7,14 +7,14 @@ license = "Apache-2.0"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.8, <4.0.0"
|
||||
specklepy = "^2.12.0"
|
||||
specklepy = "^2.13.0"
|
||||
|
||||
# [tool.poetry.group.local_specklepy.dependencies]
|
||||
# specklepy = {path = "../specklepy", develop = true}
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
numpy = "^1.23.5"
|
||||
fake-bpy-module-latest = "^20221006"
|
||||
fake-bpy-module-latest = "^20230117"
|
||||
black = "^22.10.0"
|
||||
pylint = "^2.15.7"
|
||||
ruff = "^0.0.166"
|
||||
|
||||
Reference in New Issue
Block a user