import bpy
from typing import Dict, List, Union, Tuple, Text
from compas_blender.utilities import create_collection
from compas.geometry import centroid_points
from compas.geometry import distance_point_point
from compas.geometry import subtract_vectors
__all__ = [
'draw_points',
'draw_pointcloud',
'draw_lines',
'draw_polylines',
'draw_cylinders',
'draw_spheres',
'draw_cubes',
'draw_pipes',
'draw_faces',
'draw_texts',
'draw_mesh',
]
def _link_object(obj, collection=None, layer=None):
if not collection:
collection = bpy.context.collection
if not isinstance(collection, bpy.types.Collection):
collection = create_collection(collection)
# if not layer:
# layer = bpy.context.view_layer
# layer_collection = layer.active_layer_collection.collection
for c in obj.users_collection:
c.objects.unlink(obj)
collection.objects.link(obj)
# layer_collection.objects.link(obj)
def _link_objects(objects, collection=None, layer=None):
if not collection:
collection = bpy.context.collection
if not isinstance(collection, bpy.types.Collection):
collection = create_collection(collection)
# if not layer:
# layer = bpy.context.view_layer
# layer_collection = layer.active_layer_collection.collection
for o in objects:
for c in o.users_collection:
c.objects.unlink(o)
collection.objects.link(o)
# layer_collection.objects.link(o)
def _create_material(rgb, alpha=1.0):
rgba = list(rgb) + [alpha]
name = '-'.join(['{0:.2f}'.format(i) for i in rgba])
material = bpy.data.materials.get(name) or bpy.data.materials.new(name)
material.diffuse_color = rgba
return material
def _set_object_color(obj, rgb, alpha=1.0):
rgba = list(rgb) + [alpha]
material = _create_material(rgb, alpha)
obj.color = rgba
if obj.data.materials:
obj.data.materials[0] = material
else:
obj.data.materials.append(material)
obj.active_material = material
# ==============================================================================
# Annotations
# ==============================================================================
[docs]def draw_texts(texts: List[Dict],
collection: Union[Text, bpy.types.Collection] = None) -> List[bpy.types.Object]:
"""Draw text objects."""
bpy.ops.object.text_add()
empty = bpy.context.object
_link_object(empty, collection)
_set_object_color(empty, [1.0, 1.0, 1.0])
objects = [0] * len(texts)
for index, data in enumerate(texts):
obj = empty.copy()
obj.location = data['pos']
obj.data.body = data['text']
obj.scale *= data.get('size', 1)
obj.name = data.get('name', 'text')
objects[index] = obj
_link_objects(objects, collection)
empty.hide_set(True)
return objects
# ==============================================================================
# Primitives
# ==============================================================================
# replace this by a custom point shader
# https://docs.blender.org/api/current/gpu.html#custom-shader-for-dotted-3d-line
# https://docs.blender.org/api/current/gpu.html#triangle-with-custom-shader
[docs]def draw_points(points: List[Dict],
collection: Union[Text, bpy.types.Collection] = None) -> List[bpy.types.Object]:
"""Draw point objects."""
P = len(points)
N = len(str(P))
add_point = bpy.ops.mesh.primitive_uv_sphere_add
objects = [0] * P
for index, point in enumerate(points):
xyz = point['pos']
radius = point.get('radius', 1.0)
name = point.get('name', f'P.{index:0{N}d}')
color = list(point.get('color', [1.0, 1.0, 1.0]))
add_point(location=xyz, radius=radius, segments=10, ring_count=10)
obj = bpy.context.object
obj.name = name
# values = [True] * len(obj.data.polygons)
# obj.data.polygons.foreach_set("use_smooth", values)
_set_object_color(obj, color)
objects[index] = obj
_link_objects(objects, collection)
return objects
# replace this by a custom pointcloud shader
# https://docs.blender.org/api/current/gpu.html#custom-shader-for-dotted-3d-line
# https://docs.blender.org/api/current/gpu.html#triangle-with-custom-shader
[docs]def draw_pointcloud(points: List[Dict],
collection: Union[Text, bpy.types.Collection] = None) -> bpy.types.Object:
"""Draw point objects as a single cloud."""
P = len(points)
N = len(str(P))
bpy.ops.mesh.primitive_uv_sphere_add(location=[0, 0, 0], radius=1.0, segments=10, ring_count=10)
empty = bpy.context.object
_link_object(empty, collection)
_set_object_color(empty, [1.0, 1.0, 1.0])
objects = [0] * P
for index, data in enumerate(points):
obj = empty.copy()
obj.location = data['pos']
obj.scale *= data.get('radius', 1.0)
obj.name = data.get('name', f'P.{index:0{N}d}')
# obj.data.polygons.foreach_set("use_smooth", [True] * len(obj.data.polygons))
objects[index] = obj
_link_objects(objects, collection)
empty.hide_set(True)
return objects
# replace this by a custom line shader
# https://docs.blender.org/api/current/gpu.html#custom-shader-for-dotted-3d-line
# https://docs.blender.org/api/current/gpu.html#triangle-with-custom-shader
[docs]def draw_lines(lines: List[Dict],
collection: Union[Text, bpy.types.Collection] = None,
centroid: bool = True) -> List[bpy.types.Object]:
"""Draw line objects."""
L = len(lines)
N = len(str(L))
objects = [0] * L
for index, data in enumerate(lines):
sp = data['start']
ep = data['end']
origin = centroid_points([sp, ep]) if centroid else [0, 0, 0]
name = data.get('name', f'L.{index:0{N}d}')
curve = bpy.data.curves.new(name, type='CURVE')
curve.dimensions = '3D'
spline = curve.splines.new('POLY')
spline.points.add(1)
spline.points[0].co = subtract_vectors(sp, origin) + [1.0]
spline.points[1].co = subtract_vectors(ep, origin) + [1.0]
spline.order_u = 1
obj = bpy.data.objects.new(name, curve)
obj.location = origin
obj.data.fill_mode = 'FULL'
obj.data.bevel_depth = data.get('width', 0.05)
obj.data.bevel_resolution = 0
obj.data.resolution_u = 20
rgb = data.get('color', [1.0, 1.0, 1.0])
_set_object_color(obj, rgb)
objects[index] = obj
_link_objects(objects, collection)
return objects
# replace this by a custom polyline shader
# https://docs.blender.org/api/current/gpu.html#custom-shader-for-dotted-3d-line
# https://docs.blender.org/api/current/gpu.html#triangle-with-custom-shader
def draw_polylines(polylines: List[Dict],
collection: Union[Text, bpy.types.Collection] = None,
centroid: bool = True) -> List[bpy.types.Object]:
"""Draw polyline objects."""
P = len(polylines)
N = len(str(P))
objects = [0] * P
for index, data in enumerate(polylines):
points = data['points']
origin = centroid_points(points) if centroid else [0, 0, 0]
name = data.get('name', f'POLY.{index:0{N}d}')
curve = bpy.data.curves.new(name, type='CURVE')
curve.dimensions = '3D'
spline = curve.splines.new('POLY')
spline.points.add(len(points) - 1)
for i, point in enumerate(points):
spline.points[i].co = subtract_vectors(point, origin) + [1.0]
spline.order_u = 1
obj = bpy.data.objects.new(name, curve)
obj.location = origin
obj.data.fill_mode = 'FULL'
obj.data.bevel_depth = data.get('width', 0.05)
obj.data.bevel_resolution = 0
obj.data.resolution_u = 20
rgb = data.get('color', [1.0, 1.0, 1.0])
_set_object_color(obj, rgb)
objects[index] = obj
_link_objects(objects, collection)
return objects
def draw_polygons(polygons: List[Dict],
collection: Union[Text, bpy.types.Collection] = None,
centroid: bool = True) -> List[bpy.types.Object]:
"""Draw polyline objects."""
raise NotImplementedError
def draw_curves(curves: List[Dict],
collection: Union[Text, bpy.types.Collection] = None,
centroid: bool = True) -> List[bpy.types.Object]:
"""Draw curve objects."""
raise NotImplementedError
[docs]def draw_faces(faces: List[Dict],
collection: Union[Text, bpy.types.Collection] = None) -> List[bpy.types.Object]:
"""Draw polygonal faces."""
F = len(faces)
N = len(str(F))
objects = [0] * F
for index, face in enumerate(faces):
points = face['points']
indices = [list(range(len(points)))]
name = face.get('name', f'FACE.{index:0{N}d}')
color = face.get('color', [1.0, 1.0, 1.0])
obj = draw_mesh(name=name, vertices=points, faces=indices, color=color, collection=collection)
objects[index] = obj
return objects
# ==============================================================================
# Shapes
# ==============================================================================
[docs]def draw_cylinders(cylinders: List[Dict],
collection: Union[Text, bpy.types.Collection] = None,
uv: int = 10) -> List[bpy.types.Object]:
"""Draw cylinder objects as mesh primitives."""
from math import acos
from math import atan2
bpy.ops.mesh.primitive_cylinder_add(location=[0, 0, 0], radius=1, depth=1, vertices=uv)
empty = bpy.context.object
_link_object(empty, collection)
objects = [0] * len(cylinders)
for index, data in enumerate(cylinders):
sp = data['start']
ep = data['end']
mp = centroid_points([sp, ep])
radius = data.get('radius', 1.0)
length = distance_point_point(sp, ep)
obj = empty.copy()
obj.name = data.get('name', 'cylinder')
obj.rotation_euler[1] = acos((ep[2] - sp[2]) / length)
obj.rotation_euler[2] = atan2(ep[1] - sp[1], ep[0] - sp[0])
obj.location = mp
obj.scale = ((radius, radius, length))
rgb = data.get('color', [1.0, 1.0, 1.0])
_set_object_color(obj, rgb)
objects[index] = obj
_link_objects(objects, collection)
empty.hide_set(True)
return objects
# these objects are all linked.
# therefore they cannot have different colors
# also, if the linked mesh data block is chaged, it will affect all objects
[docs]def draw_spheres(spheres: List[Dict],
collection: Union[Text, bpy.types.Collection] = None,
uv: int = 10) -> List[bpy.types.Object]:
"""Draw sphere objects as mesh primitives."""
bpy.ops.mesh.primitive_uv_sphere_add(location=[0, 0, 0], radius=1.0, segments=uv, ring_count=uv)
empty = bpy.context.object
_link_object(empty, collection)
objects = [0] * len(spheres)
for index, data in enumerate(spheres):
obj = empty.copy()
obj.location = data['pos']
obj.scale *= data.get('radius', 1.0)
obj.name = data.get('name', 'sphere')
# values = [True] * len(obj.data.polygons)
# obj.data.polygons.foreach_set("use_smooth", values)
rgb = data.get('color', [1.0, 1.0, 1.0])
_set_object_color(obj, rgb)
objects[index] = obj
_link_objects(objects, collection)
empty.hide_set(True)
return objects
# def draw_spheres(spheres, collection):
# add_sphere = compas_blender.bpy.ops.mesh.primitive_uv_sphere_add
# objects = []
# for sphere in spheres:
# add_sphere(location=[0, 0, 0], radius=1.0, segments=10, ring_count=10)
# pos = sphere['pos']
# radius = sphere['radius']
# name = sphere['name']
# color = sphere['color']
# obj = compas_blender.bpy.context.active_object
# obj.location = pos
# obj.scale = radius
# obj.name = name
# compas_blender.drawing.set_object_color(obj, color)
# objects.apend(obj)
# for o in objects_vertices:
# for c in o.user_collection:
# c.objects.unlink(o)
# collection.objects.link(o)
[docs]def draw_cubes(cubes: List[Dict],
collection: Union[Text, bpy.types.Collection] = None) -> List[bpy.types.Object]:
"""Draw cube objects as mesh primitives."""
bpy.ops.mesh.primitive_cube_add(size=1, location=[0, 0, 0])
empty = bpy.context.object
_link_object(empty, collection)
objects = [0] * len(cubes)
for index, data in enumerate(cubes):
obj = empty.copy()
obj.location = data['pos']
obj.scale *= data.get('size', 1)
obj.name = data.get('name', 'cube')
rgb = data.get('color', [1.0, 1.0, 1.0])
_set_object_color(obj, rgb)
objects[index] = obj
_link_objects(objects, collection)
empty.hide_set(True)
return objects
# replace this by a custom polyline shader
# https://docs.blender.org/api/current/gpu.html#custom-shader-for-dotted-3d-line
# https://docs.blender.org/api/current/gpu.html#triangle-with-custom-shader
def draw_pipes(pipes: List[Dict],
collection: Union[Text, bpy.types.Collection] = None,
centroid: bool = True,
smooth: bool = True) -> List[bpy.types.Object]:
"""Draw polyline objects."""
P = len(pipes)
N = len(str(P))
objects = [0] * P
for index, data in enumerate(pipes):
points = data['points']
origin = centroid_points(points) if centroid else [0, 0, 0]
name = data.get('name', f'POLY.{index:0{N}d}')
curve = bpy.data.curves.new(name, type='CURVE')
curve.dimensions = '3D'
curve.fill_mode = 'FULL'
curve.bevel_depth = data.get('width', 0.05)
curve.bevel_resolution = 0
curve.resolution_u = 20
curve.use_fill_caps = True
if smooth:
spline = curve.splines.new('NURBS')
else:
spline = curve.splines.new('POLY')
spline.points.add(len(points) - 1)
for i, point in enumerate(points):
spline.points[i].co = subtract_vectors(point, origin) + [1.0]
spline.order_u = 1
obj = bpy.data.objects.new(name, curve)
obj.location = origin
rgb = data.get('color', [1.0, 1.0, 1.0])
_set_object_color(obj, rgb)
objects[index] = obj
_link_objects(objects, collection)
return objects
# ==============================================================================
# Data Structures
# ==============================================================================
[docs]def draw_mesh(vertices: List[List[float]],
faces: List[List[int]],
name: str = 'mesh',
color: Tuple[float, float, float] = (1.0, 1.0, 1.0),
centroid: bool = True,
collection: Union[Text, bpy.types.Collection] = None, **kwargs) -> bpy.types.Object:
"""Draw a mesh object."""
mp = centroid_points(vertices) if centroid else [0, 0, 0]
vertices = [subtract_vectors(vertex, mp) for vertex in vertices]
mesh = bpy.data.meshes.new(name)
mesh.from_pydata(vertices, [], faces)
mesh.update(calc_edges=True)
obj = bpy.data.objects.new(name, mesh)
obj.show_wire = True
obj.location = mp
_set_object_color(obj, color)
_link_objects([obj], collection=collection)
return obj
# ==============================================================================
# Main
# ==============================================================================
if __name__ == '__main__':
pass