189 lines
6.4 KiB
Python
189 lines
6.4 KiB
Python
from typing import Tuple, List, Optional
|
|
import bpy
|
|
import mathutils
|
|
from specklepy.objects import Base
|
|
from specklepy.objects.graph_traversal.default_traversal import (
|
|
create_default_traversal_function,
|
|
)
|
|
|
|
|
|
def to_rgba(argb_int: int) -> Tuple[float, float, float, float]:
|
|
"""
|
|
converts the int representation of a colour into a RGBA tuple
|
|
"""
|
|
alpha = ((argb_int >> 24) & 255) / 255
|
|
red = ((argb_int >> 16) & 255) / 255
|
|
green = ((argb_int >> 8) & 255) / 255
|
|
blue = (argb_int & 255) / 255
|
|
return (red, green, blue, alpha)
|
|
|
|
|
|
def to_argb_int(rgba_color: List[float]) -> int:
|
|
"""
|
|
converts an RGBA array to an ARGB integer
|
|
"""
|
|
argb_color = rgba_color[-1:] + rgba_color[:3]
|
|
int_color = [int(val * 255) for val in argb_color]
|
|
return int.from_bytes(int_color, byteorder="big", signed=True)
|
|
|
|
|
|
def create_material_from_proxy(
|
|
render_material, material_name: str
|
|
) -> bpy.types.Material:
|
|
"""
|
|
creates a Blender material from a Speckle RenderMaterial
|
|
"""
|
|
if material_name in bpy.data.materials:
|
|
return bpy.data.materials[material_name]
|
|
|
|
# create new material
|
|
material = bpy.data.materials.new(name=material_name)
|
|
material.use_nodes = True
|
|
node_tree = material.node_tree
|
|
nodes = node_tree.nodes
|
|
|
|
for node in nodes:
|
|
nodes.remove(node)
|
|
|
|
bsdf = nodes.new(type="ShaderNodeBsdfPrincipled")
|
|
output = nodes.new(type="ShaderNodeOutputMaterial")
|
|
|
|
node_tree.links.new(bsdf.outputs["BSDF"], output.inputs["Surface"])
|
|
|
|
if hasattr(render_material, "diffuse"):
|
|
diffuse_rgba = to_rgba(render_material.diffuse)
|
|
bsdf.inputs["Base Color"].default_value = (
|
|
diffuse_rgba[0],
|
|
diffuse_rgba[1],
|
|
diffuse_rgba[2],
|
|
1.0,
|
|
)
|
|
|
|
if hasattr(render_material, "opacity"):
|
|
opacity = float(render_material.opacity)
|
|
if opacity < 1.0:
|
|
material.blend_method = "BLEND"
|
|
bsdf.inputs["Alpha"].default_value = opacity
|
|
|
|
if hasattr(render_material, "metalness"):
|
|
metalness = float(render_material.metalness)
|
|
bsdf.inputs["Metallic"].default_value = metalness
|
|
|
|
if hasattr(render_material, "roughness"):
|
|
roughness = float(render_material.roughness)
|
|
bsdf.inputs["Roughness"].default_value = roughness
|
|
|
|
if (
|
|
hasattr(render_material, "emissive") and render_material.emissive != -16777216
|
|
): # default black
|
|
emissive_rgba = to_rgba(render_material.emissive)
|
|
# only add emission if it's not black (default)
|
|
if any(val > 0.01 for val in emissive_rgba[:3]):
|
|
bsdf.inputs["Emission Color"].default_value = (
|
|
emissive_rgba[0],
|
|
emissive_rgba[1],
|
|
emissive_rgba[2],
|
|
1.0,
|
|
)
|
|
bsdf.inputs["Emission Strength"].default_value = 1.0
|
|
|
|
# set viewport display color
|
|
if hasattr(render_material, "diffuse") and hasattr(render_material, "opacity"):
|
|
material.diffuse_color = (
|
|
diffuse_rgba[0],
|
|
diffuse_rgba[1],
|
|
diffuse_rgba[2],
|
|
opacity,
|
|
)
|
|
|
|
return material
|
|
|
|
|
|
def transform_matrix(transform: List[float]) -> mathutils.Matrix:
|
|
"""
|
|
converts a speckle transform array to a 4x4 matrix (blender needs it)
|
|
"""
|
|
|
|
if len(transform) != 16:
|
|
raise ValueError(f"Expected transform with 16 values, got {len(transform)}")
|
|
|
|
return mathutils.Matrix(
|
|
(
|
|
(transform[0], transform[4], transform[8], transform[12]),
|
|
(transform[1], transform[5], transform[9], transform[13]),
|
|
(transform[2], transform[6], transform[10], transform[14]),
|
|
(transform[3], transform[7], transform[11], transform[15]),
|
|
)
|
|
)
|
|
|
|
|
|
def find_object_by_id(root_object: Base, target_id: str) -> Optional[Base]:
|
|
"""
|
|
finds an object using traversal, checking both id and applicationId
|
|
"""
|
|
if hasattr(root_object, "__closure") and root_object.__closure:
|
|
if target_id in root_object.__closure:
|
|
if hasattr(root_object, "elements"):
|
|
for element in root_object.elements:
|
|
if hasattr(element, "id") and element.id == target_id:
|
|
return element
|
|
if (
|
|
hasattr(element, "referencedId")
|
|
and element.referencedId == target_id
|
|
):
|
|
return find_object_by_id(root_object, element.referencedId)
|
|
|
|
if hasattr(root_object, "@elements"):
|
|
for element in root_object["@elements"]:
|
|
if hasattr(element, "id") and element.id == target_id:
|
|
return element
|
|
if (
|
|
hasattr(element, "referencedId")
|
|
and element.referencedId == target_id
|
|
):
|
|
return find_object_by_id(root_object, element.referencedId)
|
|
|
|
traversal_function = create_default_traversal_function()
|
|
|
|
for traversal_item in traversal_function.traverse(root_object):
|
|
obj = traversal_item.current
|
|
|
|
if not hasattr(obj, "id"):
|
|
continue
|
|
|
|
if obj.id == target_id:
|
|
return obj
|
|
|
|
if hasattr(obj, "applicationId"):
|
|
app_id = obj.applicationId
|
|
if app_id == target_id:
|
|
return obj
|
|
|
|
def deep_search(search_obj):
|
|
if hasattr(search_obj, "id") and search_obj.id == target_id:
|
|
return search_obj
|
|
|
|
elements_attrs = ["elements", "@elements"]
|
|
for attr in elements_attrs:
|
|
if hasattr(search_obj, attr):
|
|
elements = getattr(search_obj, attr)
|
|
if elements and isinstance(elements, list):
|
|
for element in elements:
|
|
if hasattr(element, "id") and element.id == target_id:
|
|
return element
|
|
if (
|
|
hasattr(element, "referencedId")
|
|
and element.referencedId == target_id
|
|
):
|
|
ref_obj = find_object_by_id(
|
|
root_object, element.referencedId
|
|
)
|
|
if ref_obj:
|
|
return ref_obj
|
|
result = deep_search(element)
|
|
if result:
|
|
return result
|
|
return None
|
|
|
|
return deep_search(root_object)
|