import bpy
import bmesh
import ddg
import ddg.datastructures.halfedge.get as get
import ddg.datastructures.halfedge.set as setutils
import ddg.math.euclidean
from ddg.datastructures.halfedge.surface_generator import cylinder, icosphere
from ddg.visualization.blender.mesh import from_bmesh, shade_smooth, add_root
import numpy as np
[docs]def hes_to_bmesh(hes, co_attr="co", bpy_data=None):
"""
Converts a given halfedge surface into a bmesh.
The returned bmesh can then be used for handing over the surface to blender.
Parameters
----------
hes : ddg.halfedge.Surface
The halfedge surface that shall be converted to a bmesh.
co_attr : coordinate attribute
The type of coordinates that will be taken for conversion.
bpy_data : bmesh (optional, default=None)
When given, bmesh to save the data into.
Returns
-------
bmesh
The bmesh corresponding to the input halfedge surface.
"""
if bpy_data == None:
hes_bmesh = bmesh.new()
else:
hes_bmesh = bpy_data
hes_bmesh.clear()
co = getattr(hes.verts, co_attr)
# create vertices
# by adding new vertices to the bmesh
for _ in range(len(hes.verts)):
hes_bmesh.verts.new()
# and handing over the coordinates
hes_bmesh.verts.ensure_lookup_table()
tmp_dict = dict() # for rebuilding the faces: save the correspondence of
# the bmesh-vertex and its corresponding hes-vertex-id
for i, v in enumerate(hes.verts):
hes_bmesh.verts[i].co = co[v]
tmp_dict[id(v)] = hes_bmesh.verts[i]
# create faces
for f in hes.faces:
face_verts = list(get.get_vertices(f))
hes_bmesh.faces.new([tmp_dict[id(v)] for v in face_verts])
# catch edges not belonging to a face
for e in get.single_edges(hes):
if e.face is None and e.opp.face is None:
hes_bmesh.edges.new([tmp_dict[id(e.opp.head)], tmp_dict[id(e.head)]])
return hes_bmesh
[docs]def hes_to_tubes_and_spheres_blender_object(hes, co_attr='co',
tube_resolution=20, fill_tube_caps=True, tube_radius=.05,
sphere_subdivision_steps=2, sphere_radius=.1,
parent_kwargs={}, kwargs_generator=None):
"""
Converts a given half-edge surface to a blender object consisting of tubes and spheres.
The tubes represent the edges and the spheres represent the vertices of the surface.
Their respective blender objects are linked to a common parent object.
Smooth shading is always enabled.
Parameters
----------
hes : ddg.halfedge.Surface
The half-edge surface that shall be converted to a Blender object.
co_attr : str (default='co')
The name of vertex attribute storing the euclidean coordinates.
tube_resolution: int (default=20)
Resolution of the edge tube, i.e., number of faces of a tube.
fill_tube_caps: bool (default=False)
Decides whether the ends of the tubes are closed by faces (or left open).
tube_radius: float (default=.05)
Radius of the edge tube.
sphere_subdivision_steps: int (default=2)
Number of subdivisions of the icospheres at the vertices.
sphere_radius: float (default=.1)
Radius of the icospheres at the vertices.
parent_kwargs: dict (default={})
Dictionary containing the keyword arguments that are handed to ddg.to_blender_object_helper when initializing
the parent blender object.
kwargs_generator: function (default=None)
Function of signature kwargs_generator(cell) returning a kwargs dictionary where a cell can either be a vertex
or an edge. If such a function is given, the returned dictionary will be handed over to
ddg.to_blender_object_helper when creating the tubes and spheres. For example, it simply could return different
material names for vertex spheres and edge tubes, respectively.
Returns
-------
blender.types.Object
Blender object that stores tubes and spheres as children.
"""
setutils.set_euclidean_length_attr(hes, co_attr=co_attr, attr_name='length')
# create parent blender object
parent_bobj = add_root(**parent_kwargs)
# create hds sphere and tube
sphere = icosphere(subdivision_steps=sphere_subdivision_steps, radius=sphere_radius)
tube = cylinder(resolution=tube_resolution, fill_caps=fill_tube_caps)
for v in hes.verts:
sphere_kwargs = {} if kwargs_generator is None else kwargs_generator(v)
location = np.array(sphere_kwargs.get('attributes', {}).get('location', np.array([0, 0, 0]))) + np.array(
getattr(v, co_attr))
# update attributes by new parent
sphere_kwargs['attributes'] = sphere_kwargs.get('attributes', {})
if sphere_kwargs['attributes'].get('parent', None) is not None:
raise Warning('Given attributes for vertex sphere contain "parent" that will be overwritten!')
sphere_kwargs['attributes'].update({'location': location, 'parent': parent_bobj})
# update kwargs by shade_smooth mesh transformation
sphere_kwargs['mesh_transformations'] = sphere_kwargs.get('mesh_transformations', [])
sphere_kwargs['mesh_transformations'] = sphere_kwargs['mesh_transformations'] if type(
sphere_kwargs['mesh_transformations']) is list else [sphere_kwargs['mesh_transformations']]
sphere_kwargs['mesh_transformations'].extend([shade_smooth])
ddg.to_blender_object_helper(sphere, **sphere_kwargs)
for e in get.single_edges(hes):
matrix_world = ddg.math.euclidean.rotation_from_to(getattr(e.head, co_attr) - getattr(e.tail, co_attr),
(0, 1, 0), 4)
tube_kwargs = {} if kwargs_generator is None else kwargs_generator(e)
# update attributes by matrix_world, parent and scale
attributes = tube_kwargs.get('attributes', {})
location = attributes.get('location', np.array([0, 0, 0])) + np.array(getattr(e.tail, co_attr))
if attributes.get('location'):
del attributes['location']
if np.any([attributes.get('matrix_world', None), attributes.get('parent', None), attributes.get('scale', None)]):
raise Warning('Given attributes for edge tube contain either "matrix_world", "parent", or "scale" that '
'will be overwritten!')
attributes.update({'matrix_world': matrix_world, 'location': location, 'parent': parent_bobj,
'scale': (tube_radius, e.length, tube_radius)})
tube_kwargs['attributes'] = attributes
# update kwargs by shade_smooth mesh transformation
tube_kwargs['mesh_transformations'] = tube_kwargs.get('mesh_transformations', [])
tube_kwargs['mesh_transformations'] = tube_kwargs['mesh_transformations'] if type(
tube_kwargs['mesh_transformations']) is list else [tube_kwargs['mesh_transformations']]
tube_kwargs['mesh_transformations'].extend([shade_smooth])
ddg.to_blender_object_helper(tube, **tube_kwargs)
delattr(hes.edges, 'length')
return parent_bobj
[docs]def hes_to_mesh(hes, name="Halfedge mesh", co_attr="co"):
hes_bmesh = hes_to_bmesh(hes, co_attr=co_attr)
return from_bmesh(hes_bmesh, name, free=True)
[docs]def hes_to_blender_object(hes, name="Halfedge object", co_attr="co", parent=None,
matrix=np.eye(4)):
hes_mesh = hes_to_mesh(hes, name=name, co_attr=co_attr)
obj = bpy.data.objects.new(name, hes_mesh)
obj.parent = parent
obj.matrix_local = matrix
bpy.context.scene.collection.objects.link(obj)
return obj