Skip to content

Commit a267500

Browse files
authored
Merge pull request #898 from compas-dev/blndr_norm
Blender MeshArtist
2 parents 6a42f21 + 3ea1729 commit a267500

File tree

4 files changed

+301
-5
lines changed

4 files changed

+301
-5
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
* Added `draw_vertexlabels`, `draw_edgelabels`, `draw_facelabels`, `draw_vertexnormals`, and `draw_facenormals` to `compas_blender.artists.MeshArtist`.
13+
1214
### Changed
1315

16+
* Fixed bug in `compas_blender.draw_texts`.
17+
1418
### Removed
1519

1620

src/compas_blender/artists/meshartist.py

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
from functools import partial
44

55
import compas_blender
6+
from compas.geometry import add_vectors
7+
from compas.geometry import centroid_points
8+
from compas.geometry import scale_vector
69

710
from compas_blender.artists._artist import BaseArtist
811
from compas.utilities import color_to_colordict
@@ -50,9 +53,19 @@ def __init__(self, mesh):
5053
self._vertexcollection = None
5154
self._edgecollection = None
5255
self._facecollection = None
56+
self._vertexnormalcollection = None
57+
self._facenormalcollection = None
58+
self._vertexlabelcollection = None
59+
self._edgelabelcollection = None
60+
self._facelabelcollection = None
5361
self._object_vertex = {}
5462
self._object_edge = {}
5563
self._object_face = {}
64+
self._object_vertexnormal = {}
65+
self._object_facenormal = {}
66+
self._object_vertexlabel = {}
67+
self._object_edgelabel = {}
68+
self._object_facelabel = {}
5669
self.color_vertices = (1.0, 1.0, 1.0)
5770
self.color_edges = (0.0, 0.0, 0.0)
5871
self.color_faces = (0.7, 0.7, 0.7)
@@ -88,6 +101,41 @@ def facecollection(self):
88101
self._facecollection = compas_blender.create_collections_from_path(path)[1]
89102
return self._facecollection
90103

104+
@property
105+
def vertexnormalcollection(self):
106+
path = f"{self.mesh.name}::VertexNormals"
107+
if not self._vertexnormalcollection:
108+
self._vertexnormalcollection = compas_blender.create_collections_from_path(path)[1]
109+
return self._vertexnormalcollection
110+
111+
@property
112+
def facenormalcollection(self):
113+
path = f"{self.mesh.name}::FaceNormals"
114+
if not self._facenormalcollection:
115+
self._facenormalcollection = compas_blender.create_collections_from_path(path)[1]
116+
return self._facenormalcollection
117+
118+
@property
119+
def vertexlabelcollection(self):
120+
path = f"{self.mesh.name}::VertexLabels"
121+
if not self._vertexlabelcollection:
122+
self._vertexlabelcollection = compas_blender.create_collections_from_path(path)[1]
123+
return self._vertexlabelcollection
124+
125+
@property
126+
def edgelabelcollection(self):
127+
path = f"{self.mesh.name}::EdgeLabels"
128+
if not self._edgelabelcollection:
129+
self._edgelabelcollection = compas_blender.create_collections_from_path(path)[1]
130+
return self._edgelabelcollection
131+
132+
@property
133+
def facelabelcollection(self):
134+
path = f"{self.mesh.name}::FaceLabels"
135+
if not self._facelabelcollection:
136+
self._facelabelcollection = compas_blender.create_collections_from_path(path)[1]
137+
return self._facelabelcollection
138+
91139
@property
92140
def object_vertex(self):
93141
"""Map between Blender object objects and mesh vertex identifiers."""
@@ -115,6 +163,51 @@ def object_face(self):
115163
def object_face(self, values):
116164
self._object_face = dict(values)
117165

166+
@property
167+
def object_vertexnormal(self):
168+
"""Map between Blender object objects and mesh vertex normal identifiers."""
169+
return self._object_vertexnormal
170+
171+
@object_vertexnormal.setter
172+
def object_vertexnormal(self, values):
173+
self._object_vertexnormal = dict(values)
174+
175+
@property
176+
def object_facenormal(self):
177+
"""Map between Blender object objects and mesh face normal identifiers."""
178+
return self._object_facenormal
179+
180+
@object_facenormal.setter
181+
def object_facenormal(self, values):
182+
self._object_facenormal = dict(values)
183+
184+
@property
185+
def object_vertexlabel(self):
186+
"""Map between Blender object objects and mesh vertex label identifiers."""
187+
return self._object_vertexlabel
188+
189+
@object_vertexlabel.setter
190+
def object_vertexlabel(self, values):
191+
self._object_vertexlabel = dict(values)
192+
193+
@property
194+
def object_edgelabel(self):
195+
"""Map between Blender object objects and mesh edge label identifiers."""
196+
return self._object_edgelabel
197+
198+
@object_edgelabel.setter
199+
def object_edgelabel(self, values):
200+
self._object_edgelabel = dict(values)
201+
202+
@property
203+
def object_facelabel(self):
204+
"""Map between Blender object objects and mesh face label identifiers."""
205+
return self._object_facelabel
206+
207+
@object_facelabel.setter
208+
def object_facelabel(self, values):
209+
self._object_facelabel = dict(values)
210+
118211
# ==========================================================================
119212
# clear
120213
# ==========================================================================
@@ -126,10 +219,20 @@ def clear(self):
126219
objects += list(self.object_vertex)
127220
objects += list(self.object_edge)
128221
objects += list(self.object_face)
222+
objects += list(self.object_vertexnormal)
223+
objects += list(self.object_facenormal)
224+
objects += list(self.object_vertexlabel)
225+
objects += list(self.object_edgelabel)
226+
objects += list(self.object_facelabel)
129227
compas_blender.delete_objects(objects, purge_data=True)
130228
self._object_vertex = {}
131229
self._object_edge = {}
132230
self._object_face = {}
231+
self._object_vertexnormal = {}
232+
self._object_facenormal = {}
233+
self._object_vertexlabel = {}
234+
self._object_edgelabel = {}
235+
self._object_facelabel = {}
133236

134237
# ==========================================================================
135238
# components
@@ -246,3 +349,190 @@ def draw_edges(self, edges=None, color=None):
246349
objects = compas_blender.draw_lines(lines, self.edgecollection)
247350
self.object_edge = zip(objects, edges)
248351
return objects
352+
353+
# ==========================================================================
354+
# draw normals
355+
# ==========================================================================
356+
357+
def draw_vertexnormals(self, vertices=None, color=(0., 1., 0.), scale=1.0):
358+
"""Draw the normals at the vertices of the mesh.
359+
360+
Parameters
361+
----------
362+
vertices : list, optional
363+
A selection of vertex normals to draw.
364+
Default is to draw all vertex normals.
365+
color : tuple, optional
366+
The color specification of the normal vectors.
367+
The default color is green, ``(0., 1., 0.)``.
368+
scale : float, optional
369+
Scale factor for the vertex normals.
370+
Default is ``1.0``.
371+
372+
Returns
373+
-------
374+
list of :class:`bpy.types.Object`
375+
"""
376+
vertices = vertices or list(self.mesh.vertices())
377+
lines = []
378+
for vertex in vertices:
379+
a = self.mesh.vertex_coordinates(vertex)
380+
n = self.mesh.vertex_normal(vertex)
381+
b = add_vectors(a, scale_vector(n, scale))
382+
lines.append({
383+
'start': a,
384+
'end': b,
385+
'color': color,
386+
'name': "{}.vertexnormal.{}".format(self.mesh.name, vertex)
387+
})
388+
objects = compas_blender.draw_lines(lines, collection=self.vertexnormalcollection)
389+
self.object_vertexnormal = zip(objects, vertices)
390+
return objects
391+
392+
def draw_facenormals(self, faces=None, color=(0., 1., 1.), scale=1.0):
393+
"""Draw the normals of the faces.
394+
395+
Parameters
396+
----------
397+
faces : list, optional
398+
A selection of face normals to draw.
399+
Default is to draw all face normals.
400+
color : tuple, optional
401+
The color specification of the normal vectors.
402+
The default color is cyan, ``(0., 1., 1.)``.
403+
scale : float, optional
404+
Scale factor for the face normals.
405+
Default is ``1.0``.
406+
407+
Returns
408+
-------
409+
list of :class:`bpy.types.Object`
410+
"""
411+
faces = faces or list(self.mesh.faces())
412+
lines = []
413+
for face in faces:
414+
a = centroid_points(
415+
[self.mesh.vertex_coordinates(vertex) for vertex in self.mesh.face_vertices(face)]
416+
)
417+
n = self.mesh.face_normal(face)
418+
b = add_vectors(a, scale_vector(n, scale))
419+
lines.append({
420+
'start': a,
421+
'end': b,
422+
'name': "{}.facenormal.{}".format(self.mesh.name, face),
423+
'color': color
424+
})
425+
objects = compas_blender.draw_lines(lines, collection=self.facenormalcollection)
426+
self.object_facenormal = zip(objects, faces)
427+
return objects
428+
429+
# ==========================================================================
430+
# draw labels
431+
# ==========================================================================
432+
433+
def draw_vertexlabels(self, text=None, color=None):
434+
"""Draw labels for a selection vertices.
435+
436+
Parameters
437+
----------
438+
text : dict, optional
439+
A dictionary of vertex labels as vertex-text pairs.
440+
The default value is ``None``, in which case every vertex will be labelled with its key.
441+
color : tuple or dict of tuple, optional
442+
The color specification of the labels.
443+
The default color is the same as the default vertex color.
444+
445+
Returns
446+
-------
447+
list of :class:`bpy.types.Object`
448+
"""
449+
if not text or text == 'key':
450+
vertex_text = {vertex: str(vertex) for vertex in self.mesh.vertices()}
451+
elif text == 'index':
452+
vertex_text = {vertex: str(index) for index, vertex in enumerate(self.mesh.vertices())}
453+
elif isinstance(text, dict):
454+
vertex_text = text
455+
else:
456+
raise NotImplementedError
457+
vertex_color = color or self.color_vertices
458+
labels = []
459+
for vertex in vertex_text:
460+
labels.append({
461+
'pos': self.mesh.vertex_coordinates(vertex),
462+
'name': "{}.vertexlabel.{}".format(self.mesh.name, vertex),
463+
'text': vertex_text[vertex]})
464+
objects = compas_blender.draw_texts(labels, collection=self.vertexlabelcollection, color=vertex_color)
465+
self.object_vertexlabel = zip(objects, vertex_text)
466+
return objects
467+
468+
def draw_edgelabels(self, text=None, color=None):
469+
"""Draw labels for a selection of edges.
470+
471+
Parameters
472+
----------
473+
text : dict, optional
474+
A dictionary of edge labels as edge-text pairs.
475+
The default value is ``None``, in which case every edge will be labelled with its key.
476+
color : tuple or dict of tuple, optional
477+
The color specification of the labels.
478+
The default color is the same as the default color for edges.
479+
480+
Returns
481+
-------
482+
list of :class:`bpy.types.Object`
483+
"""
484+
if text is None:
485+
edge_text = {(u, v): "{}-{}".format(u, v) for u, v in self.mesh.edges()}
486+
elif isinstance(text, dict):
487+
edge_text = text
488+
else:
489+
raise NotImplementedError
490+
edge_color = color or self.color_edges
491+
labels = []
492+
for edge in edge_text:
493+
labels.append({
494+
'pos': centroid_points(
495+
[self.mesh.vertex_coordinates(edge[0]), self.mesh.vertex_coordinates(edge[1])]
496+
),
497+
'name': "{}.edgelabel.{}-{}".format(self.mesh.name, *edge),
498+
'text': edge_text[edge]})
499+
objects = compas_blender.draw_texts(labels, collection=self.edgelabelcollection, color=edge_color)
500+
self.object_edgelabel = zip(objects, edge_text)
501+
return objects
502+
503+
def draw_facelabels(self, text=None, color=None):
504+
"""Draw labels for a selection of faces.
505+
506+
Parameters
507+
----------
508+
text : dict, optional
509+
A dictionary of face labels as face-text pairs.
510+
The default value is ``None``, in which case every face will be labelled with its key.
511+
color : tuple or dict of tuple, optional
512+
The color specification of the labels.
513+
The default color is the same as the default face color.
514+
515+
Returns
516+
-------
517+
list of :class:`bpy.types.Object`
518+
"""
519+
if not text or text == 'key':
520+
face_text = {face: str(face) for face in self.mesh.faces()}
521+
elif text == 'index':
522+
face_text = {face: str(index) for index, face in enumerate(self.mesh.faces())}
523+
elif isinstance(text, dict):
524+
face_text = text
525+
else:
526+
raise NotImplementedError
527+
face_color = color or self.color_faces
528+
labels = []
529+
for face in face_text:
530+
labels.append({
531+
'pos': centroid_points(
532+
[self.mesh.vertex_coordinates(vertex) for vertex in self.mesh.face_vertices(face)]
533+
),
534+
'name': "{}.facelabel.{}".format(self.mesh.name, face),
535+
'text': face_text[face]})
536+
objects = compas_blender.draw_texts(labels, collection=self.collection, color=face_color)
537+
self.object_facelabel = zip(objects, face_text)
538+
return objects

src/compas_blender/utilities/drawing.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,17 @@ def _set_object_color(obj, rgb, alpha=1.0):
7979

8080

8181
def draw_texts(texts: List[Dict],
82-
collection: Union[Text, bpy.types.Collection] = None) -> List[bpy.types.Object]:
82+
collection: Union[Text, bpy.types.Collection] = None,
83+
color: Tuple = (1.0, 1.0, 1.0)) -> List[bpy.types.Object]:
8384
"""Draw text objects."""
8485
bpy.ops.object.text_add()
8586
empty = bpy.context.object
8687
_link_object(empty, collection)
87-
_set_object_color(empty, [1.0, 1.0, 1.0])
88+
_set_object_color(empty, color)
8889
objects = [0] * len(texts)
8990
for index, data in enumerate(texts):
9091
obj = empty.copy()
92+
obj.data = empty.data.copy()
9193
obj.location = data['pos']
9294
obj.data.body = data['text']
9395
obj.scale *= data.get('size', 1)

src/compas_rhino/artists/meshartist.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ def draw_vertexlabels(self, text=None, color=None):
361361
A dictionary of vertex labels as vertex-text pairs.
362362
The default value is ``None``, in which case every vertex will be labelled with its key.
363363
color : tuple or dict of tuple, optional
364-
The color sepcification of the labels.
364+
The color specification of the labels.
365365
The default color is the same as the default vertex color.
366366
367367
Returns
@@ -398,7 +398,7 @@ def draw_facelabels(self, text=None, color=None):
398398
A dictionary of face labels as face-text pairs.
399399
The default value is ``None``, in which case every face will be labelled with its key.
400400
color : tuple or dict of tuple, optional
401-
The color sepcification of the labels.
401+
The color specification of the labels.
402402
The default color is the same as the default face color.
403403
404404
Returns
@@ -435,7 +435,7 @@ def draw_edgelabels(self, text=None, color=None):
435435
A dictionary of edge labels as edge-text pairs.
436436
The default value is ``None``, in which case every edge will be labelled with its key.
437437
color : tuple or dict of tuple, optional
438-
The color sepcification of the labels.
438+
The color specification of the labels.
439439
The default color is the same as the default color for edges.
440440
441441
Returns

0 commit comments

Comments
 (0)