From 08e61fd1e684a9cd2c9e8e77b9710136269d1abe Mon Sep 17 00:00:00 2001 From: tomvanmele Date: Sun, 17 Sep 2023 21:07:21 +0200 Subject: [PATCH] upgraded blender artists --- src/compas_blender/artists/__init__.py | 4 + src/compas_blender/artists/artist.py | 216 +++++-- src/compas_blender/artists/boxartist.py | 63 +-- src/compas_blender/artists/capsuleartist.py | 60 +- src/compas_blender/artists/circleartist.py | 64 +-- src/compas_blender/artists/coneartist.py | 65 +-- src/compas_blender/artists/curveartist.py | 63 +-- src/compas_blender/artists/cylinderartist.py | 64 +-- src/compas_blender/artists/frameartist.py | 128 ++--- src/compas_blender/artists/lineartist.py | 65 +-- src/compas_blender/artists/meshartist.py | 525 +++++++++--------- src/compas_blender/artists/networkartist.py | 313 +++++------ src/compas_blender/artists/pointartist.py | 63 +-- .../artists/pointcloudartist.py | 68 +++ src/compas_blender/artists/polygonartist.py | 79 +-- .../artists/polyhedronartist.py | 66 +-- src/compas_blender/artists/polylineartist.py | 84 +-- src/compas_blender/artists/sphereartist.py | 66 +-- src/compas_blender/artists/surfaceartist.py | 57 +- src/compas_blender/artists/torusartist.py | 69 +-- src/compas_blender/artists/vectorartist.py | 93 +--- 21 files changed, 948 insertions(+), 1327 deletions(-) create mode 100644 src/compas_blender/artists/pointcloudartist.py diff --git a/src/compas_blender/artists/__init__.py b/src/compas_blender/artists/__init__.py index 4642abd1ca1..6199ef4e5a9 100644 --- a/src/compas_blender/artists/__init__.py +++ b/src/compas_blender/artists/__init__.py @@ -12,6 +12,7 @@ from compas.geometry import Frame from compas.geometry import Line from compas.geometry import Point +from compas.geometry import Pointcloud from compas.geometry import Polygon from compas.geometry import Polyhedron from compas.geometry import Polyline @@ -36,6 +37,7 @@ from .meshartist import MeshArtist from .networkartist import NetworkArtist from .pointartist import PointArtist +from .pointcloudartist import PointcloudArtist from .polygonartist import PolygonArtist from .polyhedronartist import PolyhedronArtist from .polylineartist import PolylineArtist @@ -70,6 +72,7 @@ def register_artists(): Artist.register(Mesh, MeshArtist, context="Blender") Artist.register(Network, NetworkArtist, context="Blender") Artist.register(Point, PointArtist, context="Blender") + Artist.register(Pointcloud, PointcloudArtist, context="Blender") Artist.register(Polygon, PolygonArtist, context="Blender") Artist.register(Polyhedron, PolyhedronArtist, context="Blender") Artist.register(Polyline, PolylineArtist, context="Blender") @@ -95,6 +98,7 @@ def register_artists(): "MeshArtist", "NetworkArtist", "PointArtist", + "PointcloudArtist", "PolygonArtist", "PolyhedronArtist", "PolylineArtist", diff --git a/src/compas_blender/artists/artist.py b/src/compas_blender/artists/artist.py index 943fdab82bd..b9a340c8bbc 100644 --- a/src/compas_blender/artists/artist.py +++ b/src/compas_blender/artists/artist.py @@ -1,5 +1,3 @@ -from typing import Union -from typing import Optional from typing import Any import bpy # type: ignore @@ -7,7 +5,8 @@ from compas.colors import Color from compas.artists import Artist -from compas_blender.conversions import color_to_blender_material + +from compas_blender import conversions class BlenderArtist(Artist): @@ -28,70 +27,215 @@ class BlenderArtist(Artist): """ - def __init__(self, collection: Optional[Union[str, bpy.types.Collection]] = None, **kwargs: Any): + def __init__(self, **kwargs: Any): # Initialize collection before even calling super because other classes depend on that - self._collection = None - self.collection = collection super().__init__(**kwargs) + self.objects = [] - @property - def collection(self) -> bpy.types.Collection: - return self._collection + # many of the methods below will be added to a general scene object in the future + # to make them universaly accessible they are added here for now - @collection.setter - def collection(self, value: Union[str, bpy.types.Collection]): - if isinstance(value, bpy.types.Collection): - self._collection = value - elif isinstance(value, str): - self._collection = compas_blender.create_collection(value) - else: - raise Exception("Collection must be of type `str` or `bpy.types.Collection`.") + # ============================================================================= + # Objects + # ============================================================================= - def link_object(self, obj: bpy.types.Object): - """Link an object to the collection of this artist. + def create_object(self, geometry, name=None): + """Add an object to the Blender scene. Parameters ---------- - obj : :class:`bpy.types.Object` - The Blender object to link. + geometry : :blender:`bpy.types.Mesh` | :blender:`bpy.types.Curve` + The Blender object data. + name : str, optional + The name of the object. + + Returns + ------- + :blender:`bpy.types.Object` + The Blender object. """ - self.unlink_object(obj) - self.collection.objects.link(obj) + obj = bpy.data.objects.new(name, geometry) + self.objects.append(obj) + return obj - def unlink_object(self, obj: bpy.types.Object): - """Unlink an object from the collection of this artist. + def update_object(self, obj, name=None, color=None, collection=None, transformation=None, show_wire=False): + """Update an object in the Blender scene. Parameters ---------- - obj : :class:`bpy.types.Object` - The Blender object to unlink. + obj : :blender:`bpy.types.Object` + The Blender object data. + name : str, optional + The name of the object. + color : rgb1 | rgb255 | :class:`compas.colors.Color`, optional + The color specification. + collection : str | :blender:`bpy.types.Collection`, optional + The collection to which the object should be added. + transformation : :class:`compas.geometry.Transformation`, optional + The transformation to apply to the object. + show_wire : bool, optional + Show the wireframe of the object. + + Returns + ------- + None """ - for c in obj.users_collection: - c.objects.unlink(obj) + if show_wire: + obj.show_wire = True + + if name: + obj.name = name - def assign_object_color(self, obj: bpy.types.Object, color: Color): - """Assign a color to a Blender object. + self.set_object_tranformation(obj, transformation) + self.add_object_to_collection(obj, collection) + self.set_object_color(obj, color) + + def add_object_to_collection(self, obj, name=None, do_unlink=True): + """Add an object to a collection. + + Parameters + ---------- + obj : :blender:`bpy.types.Object` + The Blender object. + name : str, optional + The name of the collection to which the object should be added. + + Returns + ------- + :blender:`bpy.types.Collection` + + """ + if name: + collection = self.create_collection(name) + else: + collection = bpy.context.scene.collection + + if do_unlink: + for c in obj.users_collection: + c.objects.unlink(obj) + + collection.objects.link(obj) + return collection + + def set_object_color(self, obj, color): + """Set the color of a Blender object. Parameters ---------- obj : :class:`bpy.types.Object` The Blender object. - color : str | tuple, optional + color : rgb1 | rgb255 | :class:`compas.colors.Color` The color specification. Returns ------- - :class:`bpy.types.Material` - The Blender color material assigned to the object. + None """ - material = color_to_blender_material(color) + color = Color.coerce(color) + if not color: + return + + material = conversions.color_to_blender_material(color) obj.color = color.rgba if obj.data.materials: obj.data.materials[0] = material else: obj.data.materials.append(material) obj.active_material = material - return material + + def set_object_tranformation(self, obj, transformation): + """Set the transformation of a Blender object. + + Parameters + ---------- + obj : :class:`bpy.types.Object` + The Blender object. + transformation : :class:`compas.geometry.Transformation` + The transformation. + + Returns + ------- + None + + """ + if transformation: + if self.transformation: + transformation = self.transformation * transformation + obj.matrix_world = conversions.transformation_to_blender(transformation) + elif self.transformation: + obj.matrix_world = conversions.transformation_to_blender(self.transformation) + + # ============================================================================= + # Collections + # ============================================================================= + + def create_collection(self, name: str) -> bpy.types.Collection: + """Create a collection with the given name. + + Parameters + ---------- + name : str + The name of the collection. + parent : bpy.types.Collection, optional + A parent collection. + + Returns + ------- + :blender:`bpy.types.Collection` + + """ + parts = name.split("::") + parent = bpy.context.scene.collection + collection = None + for index, name in enumerate(parts): + if index > 0: + name = f"{parent.name}::{name}" + if name not in bpy.data.collections: + collection = bpy.data.collections.new(name) + parent.children.link(collection) + else: + collection = bpy.data.collections[name] + parent = collection + return collection + + def clear_collection(self, name: str, include_children: bool = True): + """Clear the objects in a collection. + + Parameters + ---------- + name : str + The name of the collection to clear. + include_children : bool, optional + Clear the children collections as well. + + Returns + ------- + None + + """ + if name not in bpy.data.collections: + return + compas_blender.clear_collection(name) + if include_children: + collection = bpy.data.collections.get(name) + for child in collection.children: + self.clear_collection(child.name) + + def delete_collection(self, name: str): + """Delete a collection. + + Parameters + ---------- + name : str + The name of the collection to delete. + + Returns + ------- + None + + """ + self.clear_collection(name) + bpy.data.collections.remove(bpy.data.collections[name]) diff --git a/src/compas_blender/artists/boxartist.py b/src/compas_blender/artists/boxartist.py index 1e7a4753e43..55d37bf77f4 100644 --- a/src/compas_blender/artists/boxartist.py +++ b/src/compas_blender/artists/boxartist.py @@ -7,7 +7,7 @@ from compas.geometry import Box from compas.artists import GeometryArtist from compas.colors import Color -from compas_blender.conversions import vertices_and_faces_to_blender +from compas_blender import conversions from .artist import BlenderArtist @@ -18,61 +18,27 @@ class BoxArtist(BlenderArtist, GeometryArtist): ---------- box : :class:`~compas.geometry.Box` A COMPAS box. - collection : str | :blender:`bpy.types.Collection`, optional - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import Box - from compas_blender.artists import BoxArtist - - box = Box.from_width_height_depth(1, 1, 1) - - artist = BoxArtist(box) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import Box - from compas.artists import Artist - - box = Box.from_width_height_depth(1, 1, 1) - - artist = Artist(box) - artist.draw() - """ - def __init__( - self, - box: Box, - collection: Optional[Union[str, bpy.types.Collection]] = None, - **kwargs: Any, - ): - super().__init__( - geometry=box, - collection=collection or box.name, - **kwargs, - ) + def __init__(self, box: Box, **kwargs: Any): + super().__init__(geometry=box, **kwargs) - def draw(self, color: Optional[Color] = None) -> bpy.types.Object: + def draw( + self, color: Optional[Color] = None, collection: Optional[Union[str, bpy.types.Collection]] = None + ) -> bpy.types.Object: """Draw the box associated with the artist. Parameters ---------- color : tuple[int, int, int] | tuple[float, float, float] | :class:`~compas.colors.Color`, optional The RGB color of the box. - The default color is :attr:`compas.artists.GeometryArtist.color`. + collection : str | :blender:`bpy.types.Collection`, optional + The Blender scene collection containing the created objects. Returns ------- @@ -81,22 +47,17 @@ def draw(self, color: Optional[Color] = None) -> bpy.types.Object: """ color = Color.coerce(color) or self.color + name = self.geometry.name # add option for local coordinates vertices, faces = self.geometry.to_vertices_and_faces() - mesh = vertices_and_faces_to_blender(vertices, faces, name=self.geometry.name) - - obj = bpy.data.objects.new(self.geometry.name, mesh) - obj.show_wire = True + mesh = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) # mp = centroid_points(vertices) if centroid else [0, 0, 0] # vertices = [subtract_vectors(vertex, mp) for vertex in vertices] # obj.location = self.geometry.frame.point - # obj.matrix_world = self.geometry.transformation.matrix - - self.link_object(obj) - if color: - self.assign_object_color(obj, color) + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=collection, show_wire=True) return obj diff --git a/src/compas_blender/artists/capsuleartist.py b/src/compas_blender/artists/capsuleartist.py index b1c0acbaf93..f0b392d5f51 100644 --- a/src/compas_blender/artists/capsuleartist.py +++ b/src/compas_blender/artists/capsuleartist.py @@ -1,13 +1,12 @@ from typing import Any from typing import Optional -from typing import Union import bpy # type: ignore from compas.geometry import Capsule from compas.artists import GeometryArtist from compas.colors import Color -from compas_blender.conversions import vertices_and_faces_to_blender +from compas_blender import conversions from .artist import BlenderArtist @@ -18,56 +17,20 @@ class CapsuleArtist(BlenderArtist, GeometryArtist): ---------- capsule : :class:`~compas.geometry.Capsule` A COMPAS capsule. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import Capsule - from compas_blender.artists import CapsuleArtist - - capsule = Capsule(([0, 0, 0], [1, 0, 0]), 0.3) - - artist = CapsuleArtist(capsule) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import Capsule - from compas.artists import Artist - - capsule = Capsule(([0, 0, 0], [1, 0, 0]), 0.3) - - artist = Artist(capsule) - artist.draw() - """ - def __init__( - self, - capsule: Capsule, - collection: Optional[Union[str, bpy.types.Collection]] = None, - **kwargs: Any, - ): - super().__init__( - geometry=capsule, - collection=collection or capsule.name, - **kwargs, - ) + def __init__(self, capsule: Capsule, **kwargs: Any): + super().__init__(geometry=capsule, **kwargs) def draw( self, color: Optional[Color] = None, + collection: Optional[str] = None, u: int = 16, v: int = 16, ) -> bpy.types.Object: @@ -77,7 +40,8 @@ def draw( ---------- color : tuple[int, int, int] | tuple[float, float, float] | :class:`~compas.colors.Color`, optional The RGB color of the capsule. - The default color is :attr:`compas.artists.GeometryArtist.color`. + collection : str, optional + The Blender scene collection containing the created objects. u : int, optional Number of faces in the "u" direction. v : int, optional @@ -89,15 +53,13 @@ def draw( The objects created in Blender. """ + name = self.geometry.name color = Color.coerce(color) or self.color - vertices, faces = self.geometry.to_vertices_and_faces(u=u, v=v) - mesh = vertices_and_faces_to_blender(vertices, faces, name=self.geometry.name) - obj = bpy.data.objects.new(self.geometry.name, mesh) - obj.show_wire = True + vertices, faces = self.geometry.to_vertices_and_faces(u=u, v=v) + mesh = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) - self.link_object(obj) - if color: - self.assign_object_color(obj, color) + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=collection, show_wire=True) return obj diff --git a/src/compas_blender/artists/circleartist.py b/src/compas_blender/artists/circleartist.py index 4395951ffe7..d565a225849 100644 --- a/src/compas_blender/artists/circleartist.py +++ b/src/compas_blender/artists/circleartist.py @@ -1,6 +1,5 @@ from typing import Any from typing import Optional -from typing import Union import bpy # type: ignore @@ -17,70 +16,25 @@ class CircleArtist(BlenderArtist, GeometryArtist): ---------- circle : :class:`~compas.geometry.Circle` A COMPAS circle. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import Plane, Circle - from compas_blender.artists import CircleArtist - - circle = Circle(Plane([0, 0, 0], [0,, 0, 1]), 1.0) - - artist = CircleArtist(circle) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import Plane, Circle - from compas.artists import Artist - - circle = Circle(Plane([0, 0, 0], [0,, 0, 1]), 1.0) - - artist = Artist(circle) - artist.draw() - """ - def __init__( - self, - circle: Circle, - collection: Optional[Union[str, bpy.types.Collection]] = None, - **kwargs: Any, - ): - super().__init__( - geometry=circle, - collection=collection or circle.name, - **kwargs, - ) + def __init__(self, circle: Circle, **kwargs: Any): + super().__init__(geometry=circle, **kwargs) - def draw( - self, - color: Optional[Color] = None, - show_point: bool = False, - show_normal: bool = False, - ) -> bpy.types.Object: + def draw(self, color: Optional[Color] = None, collection: Optional[str] = None) -> bpy.types.Object: """Draw the circle. Parameters ---------- color : tuple[int, int, int] | tuple[float, float, float] | :class:`~compas.colors.Color`, optional The RGB color of the capsule. - The default color is :attr:`compas.artists.GeometryArtist.color`. - show_point : bool, optional - If True, also draw the center point of the circle. - show_normal : bool, optional - If True, also draw the normal vector of the circle. + collection : str, optional + The Blender scene collection containing the created objects. Returns ------- @@ -93,11 +47,7 @@ def draw( bpy.ops.curve.primitive_bezier_circle_add(radius=self.geometry.radius) obj = bpy.context.object - obj.name = self.geometry.name - obj.matrix_world = self.geometry.transformation.matrix - - self.link_object(obj) - if color: - self.assign_object_color(obj, color) + self.objects.append(obj) + self.update_object(obj, color=color, collection=collection, transformation=self.geometry.transformation) return obj diff --git a/src/compas_blender/artists/coneartist.py b/src/compas_blender/artists/coneartist.py index e874cc732f5..3d8632a253c 100644 --- a/src/compas_blender/artists/coneartist.py +++ b/src/compas_blender/artists/coneartist.py @@ -1,13 +1,12 @@ from typing import Any from typing import Optional -from typing import Union import bpy # type: ignore from compas.geometry import Cone from compas.artists import GeometryArtist from compas.colors import Color -from compas_blender.conversions import vertices_and_faces_to_blender +from compas_blender import conversions from .artist import BlenderArtist @@ -18,61 +17,30 @@ class ConeArtist(BlenderArtist, GeometryArtist): ---------- cone : :class:`~compas.geometry.Cone` A COMPAS cone. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import Plane, Circle, Cone - from compas_blender.artists import ConeArtist - - cone = Cone(Circle(Plane([0, 0, 0], [0, 0, 1]), 0.3), 1.0) - - artist = ConeArtist(cone) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import Plane, Circle, Cone - from compas.artists import Artist - - cone = Cone(Circle(Plane([0, 0, 0], [0, 0, 1]), 0.3), 1.0) - - artist = Artist(cone) - artist.draw() - """ - def __init__( - self, - cone: Cone, - collection: Optional[Union[str, bpy.types.Collection]] = None, - **kwargs: Any, - ): - super().__init__( - geometry=cone, - collection=collection or cone.name, - **kwargs, - ) + def __init__(self, cone: Cone, **kwargs: Any): + super().__init__(geometry=cone, **kwargs) - def draw(self, color: Optional[Color] = None, u: int = 16) -> bpy.types.Object: + def draw( + self, + color: Optional[Color] = None, + collection: Optional[str] = None, + u: int = 16, + ) -> bpy.types.Object: """Draw the cone associated with the artist. Parameters ---------- color : tuple[int, int, int] | tuple[float, float, float] | :class:`~compas.colors.Color`, optional The RGB color of the cone. - The default color is :attr:`compas.artists.GeometryArtist.color`. + collection : str, optional + The Blender scene collection containing the created objects. u : int, optional Number of faces in the "u" direction. @@ -82,16 +50,13 @@ def draw(self, color: Optional[Color] = None, u: int = 16) -> bpy.types.Object: The objects created in Blender. """ + name = self.geometry.name color = Color.coerce(color) or self.color vertices, faces = self.geometry.to_vertices_and_faces(u=u) - mesh = vertices_and_faces_to_blender(vertices, faces, name=self.geometry.name) - - obj = bpy.data.objects.new(self.geometry.name, mesh) - obj.show_wire = True + mesh = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) - self.link_object(obj) - if color: - self.assign_object_color(obj, color) + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=collection, show_wire=True) return obj diff --git a/src/compas_blender/artists/curveartist.py b/src/compas_blender/artists/curveartist.py index 23b60feb2b3..e2e1d234092 100644 --- a/src/compas_blender/artists/curveartist.py +++ b/src/compas_blender/artists/curveartist.py @@ -1,13 +1,12 @@ from typing import Any from typing import Optional -from typing import Union import bpy # type: ignore from compas.artists import GeometryArtist from compas.geometry import Curve from compas.colors import Color -from compas_blender.conversions import nurbscurve_to_blender_curve +from compas_blender import conversions from compas_blender.artists import BlenderArtist @@ -18,74 +17,40 @@ class CurveArtist(BlenderArtist, GeometryArtist): ---------- curve : :class:`~compas.geometry.Curve` A COMPAS curve. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import NurbsCurve - from compas_blender.artists import CurveArtist - - curve = NurbsCurve([[0, 0, 0], [1, 0, 0], [1, 1, 0], [1, 1, 1]]) - - artist = CurveArtist(curve) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import NurbsCurve - from compas.artists import Artist - - curve = NurbsCurve([[0, 0, 0], [1, 0, 0], [1, 1, 0], [1, 1, 1]]) - - artist = Artist(curve) - artist.draw() - """ - def __init__( - self, - curve: Curve, - collection: Optional[Union[str, bpy.types.Collection]] = None, - **kwargs: Any, - ): - super().__init__( - geometry=curve, - collection=collection or curve.name, - **kwargs, - ) + def __init__(self, curve: Curve, **kwargs: Any): + super().__init__(geometry=curve, **kwargs) - def draw(self, color: Optional[Color] = None) -> bpy.types.Object: + def draw( + self, + color: Optional[Color] = None, + collection: Optional[str] = None, + ) -> bpy.types.Object: """Draw the curve. Parameters ---------- color : tuple[int, int, int] | tuple[float, float, float] | :class:`~compas.colors.Color`, optional The RGB color of the curve. - The default color is :attr:`compas.artists.CurveArtist.color`. + collection : str, optional + The Blender scene collection containing the created objects. Returns ------- :blender:`bpy.types.Object` """ + name = self.geometry.name color = Color.coerce(color) or self.color - curve = nurbscurve_to_blender_curve(self.geometry) - - obj = bpy.data.objects.new(self.geometry.name, curve) + curve = conversions.nurbscurve_to_blender_curve(self.geometry) - self.link_object(obj) - if color: - self.assign_object_color(obj, color) + obj = self.create_object(curve, name=name) + self.update_object(obj, color=color, collection=collection) return obj diff --git a/src/compas_blender/artists/cylinderartist.py b/src/compas_blender/artists/cylinderartist.py index 98d41747710..c6a2a807091 100644 --- a/src/compas_blender/artists/cylinderartist.py +++ b/src/compas_blender/artists/cylinderartist.py @@ -1,13 +1,12 @@ from typing import Any from typing import Optional -from typing import Union import bpy # type: ignore from compas.geometry import Cylinder from compas.artists import GeometryArtist from compas.colors import Color -from compas_blender.conversions import vertices_and_faces_to_blender +from compas_blender import conversions from .artist import BlenderArtist @@ -18,60 +17,30 @@ class CylinderArtist(BlenderArtist, GeometryArtist): ---------- cylinder : :class:`~compas.geometry.Cylinder` A COMPAS cylinder. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import Plane, Circle, Cylinder - from compas_blender.artists import CylinderArtist - - cylinder = Cylinder(Circle(Plane([0, 0, 0], [0, 0, 1]), 0.3), 1.0) - - artist = CylinderArtist(cylinder) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import Plane, Circle, Cylinder - from compas.artists import Artist - - cylinder = Cylinder(Circle(Plane([0, 0, 0], [0, 0, 1]), 0.3), 1.0) - - artist = Artist(cylinder) - artist.draw() - """ - def __init__( - self, - cylinder: Cylinder, - collection: Optional[Union[str, bpy.types.Collection]] = None, - **kwargs: Any, - ): - super().__init__( - geometry=cylinder, - collection=collection or cylinder.name, - **kwargs, - ) + def __init__(self, cylinder: Cylinder, **kwargs: Any): + super().__init__(geometry=cylinder, **kwargs) - def draw(self, color: Optional[Color] = None, u: int = 16) -> bpy.types.Object: + def draw( + self, + color: Optional[Color] = None, + collection: Optional[str] = None, + u: int = 16, + ) -> bpy.types.Object: """Draw the cylinder associated with the artist. Parameters ---------- color : tuple[int, int, int] | tuple[float, float, float] | :class:`~compas.colors.Color`, optional The RGB color of the cylinder. + collection : str, optional + The Blender scene collection containing the created objects. u : int, optional Number of faces in the "u" direction. @@ -81,16 +50,13 @@ def draw(self, color: Optional[Color] = None, u: int = 16) -> bpy.types.Object: The objects created in Blender. """ + name = self.geometry.name color = Color.coerce(color) or self.color vertices, faces = self.geometry.to_vertices_and_faces(u=u) - mesh = vertices_and_faces_to_blender(vertices, faces, name=self.geometry.name) - - obj = bpy.data.objects.new(self.geometry.name, mesh) - obj.show_wire = True + mesh = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) - self.link_object(obj) - if color: - self.assign_object_color(obj, color) + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=collection, show_wire=True) return obj diff --git a/src/compas_blender/artists/frameartist.py b/src/compas_blender/artists/frameartist.py index 5ee120eb597..6ea72dcf2ab 100644 --- a/src/compas_blender/artists/frameartist.py +++ b/src/compas_blender/artists/frameartist.py @@ -1,13 +1,11 @@ from typing import Any from typing import List from typing import Optional -from typing import Union import bpy # type: ignore from compas.geometry import Frame -import compas_blender from compas.artists import GeometryArtist from compas.colors import Color from .artist import BlenderArtist @@ -20,10 +18,6 @@ class FrameArtist(BlenderArtist, GeometryArtist): ---------- frame: :class:`~compas.geometry.Frame` A COMPAS frame. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. - scale: float, optional - Scale factor that controls the length of the axes. **kwargs : dict, optional Additional keyword arguments. For more info, @@ -31,9 +25,6 @@ class FrameArtist(BlenderArtist, GeometryArtist): Attributes ---------- - scale : float - Scale factor that controls the length of the axes. - Default is ``1.0``. color_origin : :class:`~compas.colors.Color` Color for the point at the frame origin. Default is ``Color.black()``. @@ -46,83 +37,72 @@ class FrameArtist(BlenderArtist, GeometryArtist): """ - def __init__( - self, - frame: Frame, - collection: Optional[Union[str, bpy.types.Collection]] = None, - scale: float = 1.0, - **kwargs: Any, - ): - super().__init__(geometry=frame, collection=collection or frame.name, **kwargs) - - self.scale = scale or 1.0 + def __init__(self, frame: Frame, **kwargs: Any): + super().__init__(geometry=frame, **kwargs) self.color_origin = Color.black() self.color_xaxis = Color.red() self.color_yaxis = Color.green() self.color_zaxis = Color.blue() - def draw(self) -> List[bpy.types.Object]: + def draw( + self, + scale=1.0, + collection: Optional[str] = None, + ) -> List[bpy.types.Object]: """Draw the frame. - Returns - ------- - list[:blender:`bpy.types.Object`] - - """ - self.clear() - objects = [] - objects += self.draw_origin() - objects += self.draw_axes() - return objects - - def draw_origin(self) -> List[bpy.types.Object]: - """Draw the origin of the frame. + Parameters + ---------- + scale : float, optional + Scale of the frame axes. + collection : str, optional + The Blender scene collection containing the created objects. Returns ------- list[:blender:`bpy.types.Object`] """ - points = [ - { - "pos": self.geometry.point, - "name": f"{self.geometry.name}.origin", - "color": self.color_origin, - "radius": 0.01, - } - ] - return compas_blender.draw_points(points, self.collection) - - def draw_axes(self) -> List[bpy.types.Object]: - """Draw the axes of the frame. + objects = [] - Returns - ------- - list[:blender:`bpy.types.Object`] + name = self.geometry.name + collection = collection or name + + bpy.ops.mesh.primitive_uv_sphere_add( + location=self.geometry, + radius=0.01, + segments=16, + ring_count=16, + ) + obj = bpy.context.object + objects.append(obj) + + self.update_object(obj, color=self.color_origin, collection=collection) + + # origin = self.geometry.point + # X = self.geometry.point + self.geometry.xaxis.scaled(self.scale) + # Y = self.geometry.point + self.geometry.yaxis.scaled(self.scale) + # Z = self.geometry.point + self.geometry.zaxis.scaled(self.scale) + # lines = [ + # { + # "start": origin, + # "end": X, + # "color": self.color_xaxis, + # "name": f"{self.geometry.name}.xaxis", + # }, + # { + # "start": origin, + # "end": Y, + # "color": self.color_yaxis, + # "name": f"{self.geometry.name}.yaxis", + # }, + # { + # "start": origin, + # "end": Z, + # "color": self.color_zaxis, + # "name": f"{self.geometry.name}.zaxis", + # }, + # ] + # return compas_blender.draw_lines(lines, self.collection) - """ - origin = self.geometry.point - X = self.geometry.point + self.geometry.xaxis.scaled(self.scale) - Y = self.geometry.point + self.geometry.yaxis.scaled(self.scale) - Z = self.geometry.point + self.geometry.zaxis.scaled(self.scale) - lines = [ - { - "start": origin, - "end": X, - "color": self.color_xaxis, - "name": f"{self.geometry.name}.xaxis", - }, - { - "start": origin, - "end": Y, - "color": self.color_yaxis, - "name": f"{self.geometry.name}.yaxis", - }, - { - "start": origin, - "end": Z, - "color": self.color_zaxis, - "name": f"{self.geometry.name}.zaxis", - }, - ] - return compas_blender.draw_lines(lines, self.collection) + return objects diff --git a/src/compas_blender/artists/lineartist.py b/src/compas_blender/artists/lineartist.py index 5d086482ac7..15dea3430e8 100644 --- a/src/compas_blender/artists/lineartist.py +++ b/src/compas_blender/artists/lineartist.py @@ -1,16 +1,16 @@ from typing import Any from typing import List from typing import Optional -from typing import Union import bpy # type: ignore -from compas.artists import GeometryArtist from compas.geometry import Line from compas.colors import Color + +from compas.artists import GeometryArtist from compas_blender.artists import BlenderArtist -from compas_blender.conversions import line_to_blender_curve +from compas_blender import conversions class LineArtist(BlenderArtist, GeometryArtist): @@ -20,57 +20,20 @@ class LineArtist(BlenderArtist, GeometryArtist): ---------- line : :class:`~compas.geometry.Line` A COMPAS line. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import Line - from compas_blender.artists import LineArtist - - line = Line([0, 0, 0], [1, 1, 1]) - - artist = LineArtist(line) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import Line - from compas.artists import Artist - - line = Line([0, 0, 0], [1, 1, 1]) - - artist = Artist(line) - artist.draw() - """ - def __init__( - self, - line: Line, - collection: Optional[Union[str, bpy.types.Collection]] = None, - **kwargs: Any, - ): - super().__init__( - geometry=line, - collection=collection or line.name, - **kwargs, - ) + def __init__(self, line: Line, **kwargs: Any): + super().__init__(geometry=line, **kwargs) def draw( self, color: Optional[Color] = None, - show_points: bool = False, + collection: Optional[str] = None, ) -> List[bpy.types.Object]: """Draw the line. @@ -78,22 +41,20 @@ def draw( ---------- color : tuple[int, int, int] | tuple[float, float, float] | :class:`~compas.colors.Color`, optional The RGB color of the box. - The default color is :attr:`compas.artists.GeometryArtist.color`. - show_points : bool, optional - If True, show the start and end point in addition to the line. + collection : str, optional + The Blender scene collection containing the created objects. Returns ------- - list[:blender:`bpy.types.Object`] + :blender:`bpy.types.Object` """ + name = self.geometry.name color = Color.coerce(color) or self.color - curve = line_to_blender_curve(self.geometry) - obj = bpy.data.objects.new(self.geometry.name, curve) + curve = conversions.line_to_blender_curve(self.geometry) - self.link_object(obj) - if color: - self.assign_object_color(obj, color) + obj = self.create_object(curve, name=name) + self.update_object(obj, color=color, collection=collection, show_wire=True) return obj diff --git a/src/compas_blender/artists/meshartist.py b/src/compas_blender/artists/meshartist.py index b1bbe57ceef..26cfc6c2ac4 100644 --- a/src/compas_blender/artists/meshartist.py +++ b/src/compas_blender/artists/meshartist.py @@ -9,15 +9,19 @@ import compas_blender from compas.datastructures import Mesh +from compas.geometry import Line +from compas.geometry import Sphere +from compas.geometry import Cylinder from compas.geometry import add_vectors from compas.geometry import centroid_points from compas.geometry import scale_vector - from compas.colors import Color + from compas.artists import MeshArtist as BaseArtist -from compas_blender.conversions import mesh_to_blender from .artist import BlenderArtist +from compas_blender import conversions + class MeshArtist(BlenderArtist, BaseArtist): """Artist for drawing mesh data structures in Blender. @@ -26,176 +30,88 @@ class MeshArtist(BlenderArtist, BaseArtist): ---------- mesh : :class:`~compas.datastructures.Mesh` A COMPAS mesh. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. - - Attributes - ---------- - vertexcollection : :blender:`bpy.types.Collection` - The collection containing the vertices. - edgecollection : :blender:`bpy.types.Collection` - The collection containing the edges. - facecollection : :blender:`bpy.types.Collection` - The collection containing the faces. - vertexlabelcollection : :blender:`bpy.types.Collection` - The collection containing the vertex labels. - edgelabelcollection : :blender:`bpy.types.Collection` - The collection containing the edge labels. - facelabelcollection : :blender:`bpy.types.Collection` - The collection containing the face labels. - - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.datastructures import Mesh - from compas_blender.artists import MeshArtist - - mesh = Mesh.from_meshgrid(10, 10) - - artist = MeshArtist(mesh) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.datastructures import Mesh - from compas.artists import Artist - - mesh = Mesh.from_meshgrid(10, 10) - - artist = Artist(mesh) - artist.draw() """ - def __init__( - self, - mesh: Mesh, - collection: Optional[Union[str, bpy.types.Collection]] = None, - **kwargs: Any, - ): - super().__init__(mesh=mesh, collection=collection or mesh.name, **kwargs) - - @property - def vertexcollection(self) -> bpy.types.Collection: - if not self._vertexcollection: - self._vertexcollection = compas_blender.create_collection("Vertices", parent=self.collection) - return self._vertexcollection - - @property - def edgecollection(self) -> bpy.types.Collection: - if not self._edgecollection: - self._edgecollection = compas_blender.create_collection("Edges", parent=self.collection) - return self._edgecollection - - @property - def facecollection(self) -> bpy.types.Collection: - if not self._facecollection: - self._facecollection = compas_blender.create_collection("Faces", parent=self.collection) - return self._facecollection - - @property - def vertexnormalcollection(self) -> bpy.types.Collection: - if not self._vertexnormalcollection: - self._vertexnormalcollection = compas_blender.create_collection("VertexNormals", parent=self.collection) - return self._vertexnormalcollection - - @property - def facenormalcollection(self) -> bpy.types.Collection: - if not self._facenormalcollection: - self._facenormalcollection = compas_blender.create_collection("FaceNormals", parent=self.collection) - return self._facenormalcollection - - @property - def vertexlabelcollection(self) -> bpy.types.Collection: - if not self._vertexlabelcollection: - self._vertexlabelcollection = compas_blender.create_collection("VertexLabels", parent=self.collection) - return self._vertexlabelcollection - - @property - def edgelabelcollection(self) -> bpy.types.Collection: - if not self._edgelabelcollection: - self._edgelabelcollection = compas_blender.create_collection("EdgeLabels", parent=self.collection) - return self._edgelabelcollection - - @property - def facelabelcollection(self) -> bpy.types.Collection: - if not self._facelabelcollection: - self._facelabelcollection = compas_blender.create_collection("FaceLabels", parent=self.collection) - return self._facelabelcollection + def __init__(self, mesh: Mesh, **kwargs: Any): + super().__init__(mesh=mesh, **kwargs) + self.vertexobjects = [] + self.edgeobjects = [] + self.faceobjects = [] # ========================================================================== # clear # ========================================================================== def clear(self): - compas_blender.delete_objects(self.collection.objects) + compas_blender.delete_objects(self.objects) def clear_vertices(self): - """Clear the objects contained in the vertex collection (``self.vertexcollection``). + """Clear the vertex objects. Returns ------- None """ - compas_blender.delete_objects(self.vertexcollection.objects) + compas_blender.delete_objects(self.vertexobjects) def clear_edges(self): - """Clear the objects contained in the edge collection (``self.edgecollection``). + """Clear the edge objects. Returns ------- None """ - compas_blender.delete_objects(self.edgecollection.objects) + compas_blender.delete_objects(self.edgeobjects) def clear_faces(self): - """Clear the objects contained in the face collection (``self.facecollection``). + """Clear the face objects. Returns ------- None """ - compas_blender.delete_objects(self.facecollection.objects) + compas_blender.delete_objects(self.faceobjects) # ========================================================================== # draw # ========================================================================== - def draw(self, color: Optional[Color] = None) -> bpy.types.Object: + def draw(self, color: Optional[Color] = None, collection: Optional[str] = None) -> bpy.types.Object: """Draw the mesh. Parameters ---------- color : :class:`~compas.colors.Color`, optional The color of the mesh. - The default value is :attr:`color`. + collection : str, optional + The name of the collection that should contain the mesh. Returns ------- list[:blender:`bpy.types.Object`] """ + name = self.mesh.name # type: ignore color = Color.coerce(color) or self.color - mesh = mesh_to_blender(self.mesh) # type: ignore - obj = bpy.data.objects.new(self.mesh.name, mesh) # type: ignore - obj.show_wire = True - self.link_object(obj) - if color: - self.assign_object_color(obj, color) + mesh = conversions.mesh_to_blender(self.mesh) # type: ignore + + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=collection, show_wire=True) + return obj def draw_vertices( self, vertices: Optional[List[int]] = None, color: Optional[Union[Color, Dict[int, Color]]] = None, + collection: Optional[str] = None, + radius: float = 0.01, + u: int = 16, + v: int = 16, ) -> List[bpy.types.Object]: """Draw a selection of vertices. @@ -206,38 +122,46 @@ def draw_vertices( Default is None, in which case all vertices are drawn. color : :class:`~compas.colors.Color` | dict[int, :class:`~compas.colors.Color`], optional The color specification for the vertices. - The default color of vertices is :attr:`default_vertexcolor`. + collection : str, optional + The name of the collection that should contain the vertices. + + Other Parameters + ---------------- + radius : float, optional + The radius of the vertex spheres. + u : int, optional + Number of faces in the "u" direction. + v : int, optional + Number of faces in the "v" direction. Returns ------- list[:blender:`bpy.types.Object`] """ - # self.vertex_color = color - # vertices = vertices or self.vertices - # points = [] - # for vertex in vertices: - # points.append( - # { - # "pos": self.vertex_xyz[vertex], - # "name": f"{self.mesh.name}.vertex.{vertex}", - # "color": self.vertex_color[vertex], - # "radius": 0.01, - # } - # ) - # return compas_blender.draw_points(points, self.vertexcollection) - vertices = vertices or self.mesh.vertices() # type: ignore + objects = [] self.vertex_color = color - self.vertex_text = text - vertex_xyz = self.vertex_xyz - vertex_color = self.vertex_color - vertex_text = self.vertex_text + + for vertex in vertices or self.mesh.vertices(): # type: ignore + name = f"{self.mesh.name}.vertex.{vertex}" # type: ignore + color = self.vertex_color[vertex] # type: ignore + point = self.vertex_xyz[vertex] + + # there is no such thing as a sphere data block + bpy.ops.mesh.primitive_uv_sphere_add(location=point, radius=radius, segments=u, ring_count=v) + obj = bpy.context.object + self.objects.append(obj) + self.update_object(obj, name=name, color=color, collection=collection) + objects.append(obj) + + return objects def draw_edges( self, edges: Optional[List[Tuple[int, int]]] = None, - color: Optional[Union[Color, Dict[int, Color]]] = None, + color: Optional[Union[Color, Dict[Tuple[int, int], Color]]] = None, + collection: Optional[str] = None, ) -> List[bpy.types.Object]: """Draw a selection of edges. @@ -248,32 +172,34 @@ def draw_edges( The default is None, in which case all edges are drawn. color : :class:`~compas.colors.Color` | dict[tuple[int, int], :class:`~compas.colors.Color`], optional The color specification for the edges. - The default color of edges is :attr:`default_edgecolor`. + collection : str, optional + The name of the collection that should contain the edges. Returns ------- list[:blender:`bpy.types.Object`] """ + objects = [] + self.edge_color = color - edges = edges or self.edges - lines = [] - for edge in edges: - u, v = edge - lines.append( - { - "start": self.vertex_xyz[u], - "end": self.vertex_xyz[v], - "color": self.edge_color[edge], - "name": f"{self.mesh.name}.edge.{u}-{v}", - } - ) - return compas_blender.draw_lines(lines, self.edgecollection) + + for u, v in edges or self.mesh.edges(): # type: ignore + name = f"{self.mesh.name}.edge.{u}-{v}" # type: ignore + color = self.edge_color[u, v] # type: ignore + curve = conversions.line_to_blender_curve(Line(self.vertex_xyz[u], self.vertex_xyz[v])) + + obj = self.create_object(curve, name=name) + self.update_object(obj, color=color, collection=collection) + objects.append(obj) + + return objects def draw_faces( self, faces: Optional[List[int]] = None, color: Optional[Union[Color, Dict[int, Color]]] = None, + collection: Optional[str] = None, ) -> List[bpy.types.Object]: """Draw a selection of faces. @@ -284,25 +210,29 @@ def draw_faces( The default is None, in which case all faces are drawn. color : :class:`~compas.colors.Color` | dict[int, :class:`~compas.colors.Color`], optional The color specification for the faces. - Th default color of faces is :attr:`default_facecolor`. + collection : str, optional + The name of the collection that should contain the faces. Returns ------- list[:blender:`bpy.types.Object`] """ + objects = [] + self.face_color = color - faces = faces or self.faces - facets = [] - for face in faces: - facets.append( - { - "points": [self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)], - "name": f"{self.mesh.name}.face.{face}", - "color": self.face_color[face], - } - ) - return compas_blender.draw_faces(facets, self.facecollection) + + for face in faces or self.mesh.faces(): # type: ignore + name = f"{self.mesh.name}.face.{face}" # type: ignore + color = self.face_color[face] # type: ignore + points = [self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)] # type: ignore + mesh = conversions.polygon_to_blender_mesh(points, name=name) # type: ignore + + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=collection, show_wire=False) + objects.append(obj) + + return objects # ========================================================================== # draw normals @@ -313,6 +243,7 @@ def draw_vertexnormals( vertices: Optional[List[int]] = None, color: Color = Color.green(), scale: float = 1.0, + collection: Optional[str] = None, ) -> List[bpy.types.Object]: """Draw the normals at the vertices of the mesh. @@ -325,33 +256,40 @@ def draw_vertexnormals( The color specification of the normal vectors. scale : float, optional Scale factor for the vertex normals. + collection : str, optional + The name of the collection that should contain the normals. Returns ------- list[:blender:`bpy.types.Object`] """ - vertices = vertices or self.vertices - lines = [] - for vertex in vertices: + objects = [] + + color = Color.coerce(color) # type: ignore + + for vertex in vertices or self.mesh.vertices(): # type: ignore + name = f"{self.mesh.name}.vertex.{vertex}.normal" # type: ignore + a = self.vertex_xyz[vertex] - n = self.mesh.vertex_normal(vertex) + n = self.mesh.vertex_normal(vertex) # type: ignore b = add_vectors(a, scale_vector(n, scale)) - lines.append( - { - "start": a, - "end": b, - "color": color, - "name": f"{self.mesh.name}.vertexnormal.{vertex}", - } - ) - return compas_blender.draw_lines(lines, collection=self.vertexnormalcollection) + + curve = conversions.line_to_blender_curve(Line(a, b)) + + obj = self.create_object(curve, name=name) + self.update_object(obj, color=color, collection=collection) + + objects.append(obj) + + return objects def draw_facenormals( self, faces: Optional[List[List[int]]] = None, color: Color = Color.cyan(), scale: float = 1.0, + collection: Optional[str] = None, ) -> List[bpy.types.Object]: """Draw the normals of the faces. @@ -364,110 +302,201 @@ def draw_facenormals( The color specification of the normal vectors. scale : float, optional Scale factor for the face normals. + collection : str, optional + The name of the collection that should contain the normals. Returns ------- list[:blender:`bpy.types.Object`] """ - faces = faces or self.faces - lines = [] - for face in faces: - a = centroid_points([self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)]) - n = self.mesh.face_normal(face) + objects = [] + + color = Color.coerce(color) # type: ignore + + for face in faces or self.mesh.faces(): # type: ignore + name = f"{self.mesh.name}.face.{face}.normal" # type: ignore + + a = centroid_points([self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)]) # type: ignore + n = self.mesh.face_normal(face) # type: ignore b = add_vectors(a, scale_vector(n, scale)) - lines.append( - { - "start": a, - "end": b, - "name": f"{self.mesh.name}.facenormal.{face}", - "color": color, - } - ) - return compas_blender.draw_lines(lines, collection=self.facenormalcollection) + + curve = conversions.line_to_blender_curve(Line(a, b)) + + obj = self.create_object(curve, name=name) + self.update_object(obj, color=color, collection=collection) + + objects.append(obj) + + return objects # ========================================================================== # draw labels # ========================================================================== - def draw_vertexlabels(self, text: Optional[Dict[int, str]] = None) -> List[bpy.types.Object]: - """Draw labels for a selection vertices. + # def draw_vertexlabels(self, text: Optional[Dict[int, str]] = None) -> List[bpy.types.Object]: + # """Draw labels for a selection vertices. + + # Parameters + # ---------- + # text : dict[int, str], optional + # A dictionary of vertex labels as vertex-text pairs. + # The default value is None, in which case every vertex will be labeled with its identifier. + + # Returns + # ------- + # list[:blender:`bpy.types.Object`] + + # """ + # self.vertex_text = text + # labels = [] + # for vertex in self.vertex_text: + # labels.append( + # { + # "pos": self.vertex_xyz[vertex], + # "name": f"{self.mesh.name}.vertexlabel.{vertex}", + # "text": self.vertex_text[vertex], + # "color": self.vertex_color[vertex], + # } + # ) + # return compas_blender.draw_texts(labels, collection=self.vertexlabelcollection) + + # def draw_edgelabels(self, text: Optional[Dict[Tuple[int, int], str]] = None) -> List[bpy.types.Object]: + # """Draw labels for a selection of edges. + + # Parameters + # ---------- + # text : dict[tuple[int, int], str], optional + # A dictionary of edge labels as edge-text pairs. + # The default value is None, in which case every edge will be labeled with its identifier. + + # Returns + # ------- + # list[:blender:`bpy.types.Object`] + + # """ + # self.edge_text = text + # labels = [] + # for edge in self.edge_text: + # u, v = edge + # labels.append( + # { + # "pos": centroid_points([self.vertex_xyz[u], self.vertex_xyz[v]]), + # "name": f"{self.mesh.name}.edgelabel.{u}-{v}", + # "text": self.edge_text[edge], + # "color": self.edge_color[edge], + # } + # ) + # return compas_blender.draw_texts(labels, collection=self.edgelabelcollection) + + # def draw_facelabels(self, text: Optional[Dict[int, str]] = None) -> List[bpy.types.Object]: + # """Draw labels for a selection of faces. + + # Parameters + # ---------- + # text : dict[int, str], optional + # A dictionary of face labels as face-text pairs. + # The default value is None, in which case every face will be labeled with its identifier. + + # Returns + # ------- + # list[:blender:`bpy.types.Object`] + + # """ + # self.face_text = text + # labels = [] + # for face in self.face_text: + # labels.append( + # { + # "pos": centroid_points([self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)]), + # "name": "{}.facelabel.{}".format(self.mesh.name, face), + # "text": self.face_text[face], + # "color": self.face_color[face], + # } + # ) + # return compas_blender.draw_texts(labels, collection=self.collection) + + # ============================================================================= + # draw miscellaneous + # ============================================================================= + + def draw_spheres( + self, + radius: Dict[int, float], + color: Optional[Union[Color, Dict[int, Color]]] = None, + collection: Optional[str] = None, + ) -> list[bpy.types.Object]: + """Draw spheres at the vertices of the mesh. Parameters ---------- - text : dict[int, str], optional - A dictionary of vertex labels as vertex-text pairs. - The default value is None, in which case every vertex will be labeled with its identifier. + radius : dict[int, float], optional + The radius of the spheres. + color : tuple[int, int, int] | tuple[float, float, float] | :class:`~compas.colors.Color` | dict[int, :class:`~compas.colors.Color`], optional + The color of the spheres. + collection : str, optional + The name of the collection the should contain the objects. Returns ------- list[:blender:`bpy.types.Object`] """ - self.vertex_text = text - labels = [] - for vertex in self.vertex_text: - labels.append( - { - "pos": self.vertex_xyz[vertex], - "name": f"{self.mesh.name}.vertexlabel.{vertex}", - "text": self.vertex_text[vertex], - "color": self.vertex_color[vertex], - } - ) - return compas_blender.draw_texts(labels, collection=self.vertexlabelcollection) - - def draw_edgelabels(self, text: Optional[Dict[Tuple[int, int], str]] = None) -> List[bpy.types.Object]: - """Draw labels for a selection of edges. + objects = [] - Parameters - ---------- - text : dict[tuple[int, int], str], optional - A dictionary of edge labels as edge-text pairs. - The default value is None, in which case every edge will be labeled with its identifier. + self.vertex_color = color - Returns - ------- - list[:blender:`bpy.types.Object`] + for vertex in radius: + name = "{}.vertex.{}.sphere".format(self.mesh.name, vertex) # type: ignore + color = self.vertex_color[vertex] # type: ignore - """ - self.edge_text = text - labels = [] - for edge in self.edge_text: - u, v = edge - labels.append( - { - "pos": centroid_points([self.vertex_xyz[u], self.vertex_xyz[v]]), - "name": f"{self.mesh.name}.edgelabel.{u}-{v}", - "text": self.edge_text[edge], - "color": self.edge_color[edge], - } - ) - return compas_blender.draw_texts(labels, collection=self.edgelabelcollection) - - def draw_facelabels(self, text: Optional[Dict[int, str]] = None) -> List[bpy.types.Object]: - """Draw labels for a selection of faces. + sphere = Sphere.from_point_and_radius(self.vertex_xyz[vertex], radius[vertex]) + geometry = conversions.sphere_to_blender_mesh(sphere, name=name) + + obj = self.create_object(geometry, name=name) + self.update_object(obj, color=color, collection=collection) + + objects.append(obj) + + return objects + + def draw_pipes( + self, + radius: Dict[Tuple[int, int], float], + color: Optional[Union[Color, Dict[int, Color]]] = None, + collection: Optional[str] = None, + ) -> list[bpy.types.Object]: + """Draw pipes around the edges of the mesh. Parameters ---------- - text : dict[int, str], optional - A dictionary of face labels as face-text pairs. - The default value is None, in which case every face will be labeled with its identifier. + radius : dict[tuple[int, int], float] + The radius per edge. + color : tuple[int, int, int] | tuple[float, float, float] | :class:`~compas.colors.Color` | dict[tuple[int, int], :class:`~compas.colors.Color`], optional + The color of the pipes. + collection : str, optional + The name of the collection to store the objects in. Returns ------- list[:blender:`bpy.types.Object`] """ - self.face_text = text - labels = [] - for face in self.face_text: - labels.append( - { - "pos": centroid_points([self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)]), - "name": "{}.facelabel.{}".format(self.mesh.name, face), - "text": self.face_text[face], - "color": self.face_color[face], - } - ) - return compas_blender.draw_texts(labels, collection=self.collection) + objects = [] + + self.edge_color = color + + for u, v in radius: + name = "{}.edge.{}-{}.pipe".format(self.mesh.name, u, v) # type: ignore + color = self.edge_color[u, v] # type: ignore + + line = Line(self.vertex_xyz[u], self.vertex_xyz[v]) + cylinder = Cylinder.from_line_and_radius(line, radius[u, v]) # type: ignore + geometry = conversions.cylinder_to_blender_mesh(cylinder) + + obj = self.create_object(geometry, name=name) + self.update_object(obj, color=color, collection=collection) + + objects.append(obj) + + return objects diff --git a/src/compas_blender/artists/networkartist.py b/src/compas_blender/artists/networkartist.py index d66e76a7d90..1331921d4ad 100644 --- a/src/compas_blender/artists/networkartist.py +++ b/src/compas_blender/artists/networkartist.py @@ -6,101 +6,32 @@ from typing import Union import bpy # type: ignore -from functools import partial import compas_blender from compas.datastructures import Network -from compas.geometry import centroid_points -from compas.utilities import color_to_colordict -from compas.artists import NetworkArtist from compas.colors import Color +from compas.geometry import Line + +from compas.artists import NetworkArtist as BaseArtist from .artist import BlenderArtist -colordict = partial(color_to_colordict, colorformat="rgb", normalize=True) +from compas_blender import conversions -class NetworkArtist(BlenderArtist, NetworkArtist): +class NetworkArtist(BlenderArtist, BaseArtist): """Artist for drawing network data structures in Blender. Parameters ---------- network : :class:`~compas.datastructures.Network` A COMPAS network. - collection : str | :blender:`bpy.types.Collection` - The name of the collection the object belongs to. - - Attributes - ---------- - nodecollection : :blender:`bpy.types.Collection` - The collection containing the nodes. - edgecollection : :blender:`bpy.types.Collection` - The collection containing the edges. - nodelabelcollection : :blender:`bpy.types.Collection` - The collection containing the node labels. - edgelabelcollection : :blender:`bpy.types.Collection` - The collection containing the edge labels. - - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - import compas - from compas.datastructures import Network - from compas_blender.artists import NetworkArtist - - network = Network.from_obj(compas.get('lines.obj')) - - artist = NetworkArtist(network) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - import compas - from compas.datastructures import Network - from compas.artists import Artist - - network = Network.from_obj(compas.get('lines.obj')) - - artist = Artist(network) - artist.draw() """ - def __init__( - self, - network: Network, - collection: Optional[Union[str, bpy.types.Collection]] = None, - **kwargs: Any, - ): - super().__init__(network=network, collection=collection or network.name, **kwargs) - - @property - def nodecollection(self) -> bpy.types.Collection: - if not self._nodecollection: - self._nodecollection = compas_blender.create_collection("Nodes", parent=self.collection) - return self._nodecollection - - @property - def edgecollection(self) -> bpy.types.Collection: - if not self._edgecollection: - self._edgecollection = compas_blender.create_collection("Edges", parent=self.collection) - return self._edgecollection - - @property - def nodelabelcollection(self) -> bpy.types.Collection: - if not self._nodelabelcollection: - self._nodelabelcollection = compas_blender.create_collection("NodeLabels", parent=self.collection) - return self._nodelabelcollection - - @property - def edgelabelcollection(self) -> bpy.types.Collection: - if not self._edgelabelcollection: - self._edgelabelcollection = compas_blender.create_collection("EdgeLabels", parent=self.collection) - return self._edgelabelcollection + def __init__(self, network: Network, **kwargs: Any): + super().__init__(network=network, **kwargs) + self.nodeobjects = [] + self.edgeobjects = [] # ========================================================================== # clear @@ -114,7 +45,7 @@ def clear_nodes(self): None """ - compas_blender.delete_objects(self.nodecollection.objects) + compas_blender.delete_objects(self.nodeobjects) def clear_edges(self): """Clear all objects contained in the edge collection. @@ -124,27 +55,27 @@ def clear_edges(self): None """ - compas_blender.delete_objects(self.edgecollection.objects) + compas_blender.delete_objects(self.edgeobjects) - def clear_nodelabels(self): - """Clear all objects contained in the nodelabel collection. + # def clear_nodelabels(self): + # """Clear all objects contained in the nodelabel collection. - Returns - ------- - None + # Returns + # ------- + # None - """ - compas_blender.delete_objects(self.nodelabelcollection.objects) + # """ + # compas_blender.delete_objects(self.nodelabelcollection.objects) - def clear_edgelabels(self): - """Clear all objects contained in the edgelabel collection. + # def clear_edgelabels(self): + # """Clear all objects contained in the edgelabel collection. - Returns - ------- - None + # Returns + # ------- + # None - """ - compas_blender.delete_objects(self.edgelabelcollection.objects) + # """ + # compas_blender.delete_objects(self.edgelabelcollection.objects) # ========================================================================== # draw @@ -154,8 +85,8 @@ def draw( self, nodes: Optional[List[int]] = None, edges: Optional[Tuple[int, int]] = None, - nodecolor: Optional[Union[str, Color, Dict[int, Color]]] = None, - edgecolor: Optional[Union[str, Color, Dict[int, Color]]] = None, + nodecolor: Optional[Union[Color, Dict[int, Color]]] = None, + edgecolor: Optional[Union[Color, Dict[Tuple[int, int], Color]]] = None, ) -> None: """Draw the network. @@ -178,15 +109,17 @@ def draw( """ self.clear() - if self.show_nodes: - self.draw_nodes(nodes=nodes, color=nodecolor) - if self.show_edges: - self.draw_edges(edges=edges, color=edgecolor) + self.draw_nodes(nodes=nodes, color=nodecolor) + self.draw_edges(edges=edges, color=edgecolor) def draw_nodes( self, nodes: Optional[List[int]] = None, color: Optional[Union[Color, Dict[int, Color]]] = None, + collection: Optional[str] = None, + radius: float = 0.05, + u: int = 16, + v: int = 16, ) -> List[bpy.types.Object]: """Draw a selection of nodes. @@ -197,31 +130,37 @@ def draw_nodes( Default is None, in which case all nodes are drawn. color : :class:`~compas.colors.Color` | dict[hashable, :class:`~compas.colors.Color`], optional The color specification for the nodes. - The default color of nodes is :attr:`default_nodecolor`. + collection : str, optional + The name of the collection to draw in. Returns ------- list[:blender:`bpy.types.Object`] """ + objects = [] + self.node_color = color - nodes = nodes or self.nodes - points = [] - for node in nodes: - points.append( - { - "pos": self.node_xyz[node], - "name": f"{self.network.name}.node.{node}", - "color": self.node_color[node], - "radius": 0.05, - } - ) - return compas_blender.draw_points(points, self.nodecollection) + + for node in nodes or self.network.nodes(): # type: ignore + name = f"{self.network.name}.node.{node}" # type: ignore + color = self.node_color[node] # type: ignore + point = self.node_xyz[node] # type: ignore + + # there is no such thing as a sphere data block + bpy.ops.mesh.primitive_uv_sphere_add(location=point, radius=radius, segments=u, ring_count=v) + obj = bpy.context.object + self.objects.append(obj) + self.update_object(obj, name=name, color=color, collection=collection) + objects.append(obj) + + return objects def draw_edges( self, edges: Optional[Tuple[int, int]] = None, - color: Optional[Union[Color, Dict[int, Color]]] = None, + color: Optional[Union[Color, Dict[Tuple[int, int], Color]]] = None, + collection: Optional[str] = None, ) -> List[bpy.types.Object]: """Draw a selection of edges. @@ -232,80 +171,88 @@ def draw_edges( The default is None, in which case all edges are drawn. color : :class:`~compas.colors.Color` | dict[tuple[hashable, hashable], :class:`~compas.colors.Color`], optional The color specification for the edges. - The default color of edges is :attr:`default_edgecolor`. - - Returns - ------- - list[:blender:`bpy.types.Object`] - - """ - self.edge_color = color - edges = edges or self.edges - lines = [] - for edge in edges: - u, v = edge - lines.append( - { - "start": self.node_xyz[u], - "end": self.node_xyz[v], - "color": self.edge_color[edge], - "name": f"{self.network.name}.edge.{u}-{v}", - "width": self.edge_width[edge], - } - ) - return compas_blender.draw_lines(lines, self.edgecollection) - - def draw_nodelabels(self, text: Optional[Dict[int, str]] = None) -> List[bpy.types.Object]: - """Draw labels for a selection nodes. - - Parameters - ---------- - text : dict[hashable, str], optional - A dictionary of vertex labels as vertex-text pairs. - The default value is None, in which case every vertex will be labeled with its key. + collection : str, optional + The name of the collection to draw in. Returns ------- list[:blender:`bpy.types.Object`] """ - self.node_text = text - labels = [] - for node in self.node_text: - labels.append( - { - "pos": self.node_xyz[node], - "name": f"{self.network.name}.nodelabel.{node}", - "text": self.node_text[node], - "color": self.node_color[node], - } - ) - return compas_blender.draw_texts(labels, collection=self.nodelabelcollection) - - def draw_edgelabels(self, text: Optional[Dict[Tuple[int, int], str]] = None) -> List[bpy.types.Object]: - """Draw labels for a selection of edges. - - Parameters - ---------- - text : dict[tuple[hashable, hashable], str], optional - A dictionary of edge labels as edge-text pairs. - The default value is None, in which case every edge will be labeled with its key. + objects = [] - Returns - ------- - list[:blender:`bpy.types.Object`] + self.edge_color = color - """ - self.edge_text = text - labels = [] - for edge in self.edge_text: - u, v = edge - labels.append( - { - "pos": centroid_points([self.node_xyz[u], self.node_xyz[v]]), - "name": f"{self.network.name}.edgelabel.{u}-{v}", - "text": self.edge_text[edge], - "color": self.edge_color[edge], - } - ) - return compas_blender.draw_texts(labels, collection=self.edgelabelcollection) + for u, v in edges or self.network.edges(): # type: ignore + name = f"{self.network.name}.edge.{u}-{v}" # type: ignore + color = self.edge_color[u, v] # type: ignore + curve = conversions.line_to_blender_curve(Line(self.node_xyz[u], self.node_xyz[v])) + + obj = self.create_object(curve, name=name) + self.update_object(obj, color=color, collection=collection) + objects.append(obj) + + return objects + + # ============================================================================= + # draw labels + # ============================================================================= + + # def draw_nodelabels(self, text: Optional[Dict[int, str]] = None) -> List[bpy.types.Object]: + # """Draw labels for a selection nodes. + + # Parameters + # ---------- + # text : dict[hashable, str], optional + # A dictionary of vertex labels as vertex-text pairs. + # The default value is None, in which case every vertex will be labeled with its key. + + # Returns + # ------- + # list[:blender:`bpy.types.Object`] + + # """ + # self.node_text = text + # labels = [] + # for node in self.node_text: + # labels.append( + # { + # "pos": self.node_xyz[node], + # "name": f"{self.network.name}.nodelabel.{node}", + # "text": self.node_text[node], + # "color": self.node_color[node], + # } + # ) + # return compas_blender.draw_texts(labels, collection=self.nodelabelcollection) + + # def draw_edgelabels(self, text: Optional[Dict[Tuple[int, int], str]] = None) -> List[bpy.types.Object]: + # """Draw labels for a selection of edges. + + # Parameters + # ---------- + # text : dict[tuple[hashable, hashable], str], optional + # A dictionary of edge labels as edge-text pairs. + # The default value is None, in which case every edge will be labeled with its key. + + # Returns + # ------- + # list[:blender:`bpy.types.Object`] + + # """ + # self.edge_text = text + # labels = [] + # for edge in self.edge_text: + # u, v = edge + # labels.append( + # { + # "pos": centroid_points([self.node_xyz[u], self.node_xyz[v]]), + # "name": f"{self.network.name}.edgelabel.{u}-{v}", + # "text": self.edge_text[edge], + # "color": self.edge_color[edge], + # } + # ) + # return compas_blender.draw_texts(labels, collection=self.edgelabelcollection) + + # ============================================================================= + # draw miscellaneous + # ============================================================================= diff --git a/src/compas_blender/artists/pointartist.py b/src/compas_blender/artists/pointartist.py index 1dfba35d365..8213074567d 100644 --- a/src/compas_blender/artists/pointartist.py +++ b/src/compas_blender/artists/pointartist.py @@ -1,12 +1,12 @@ from typing import Any from typing import Optional -from typing import Union import bpy # type: ignore -from compas.artists import GeometryArtist from compas.geometry import Point from compas.colors import Color + +from compas.artists import GeometryArtist from .artist import BlenderArtist @@ -17,56 +17,20 @@ class PointArtist(BlenderArtist, GeometryArtist): ---------- point : :class:`~compas.geometry.Point` A COMPAS point. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import Point - from compas_blender.artists import PointArtist - - point = Point(0, 0, 0) - - artist = PointArtist(point) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import Point - from compas.artists import Artist - - point = Point(0, 0, 0) - - artist = Artist(point) - artist.draw() - """ - def __init__( - self, - point: Point, - collection: Optional[Union[str, bpy.types.Collection]] = None, - **kwargs: Any, - ): - super().__init__( - geometry=point, - collection=collection or point.name, - **kwargs, - ) + def __init__(self, point: Point, **kwargs: Any): + super().__init__(geometry=point, **kwargs) def draw( self, color: Optional[Color] = None, + collection: Optional[str] = None, size: float = 0.01, u: int = 16, v: int = 16, @@ -77,13 +41,21 @@ def draw( ---------- color : tuple[float, float, float] | tuple[int, int, int] | :class:`~compas.colors.Color`, optional Color of the point object. - The default color is :attr:`compas.artists.GeometryArtist.color`. + collection : str, optional + The name of the Blender scene collection containing the created object(s). + size : float, optional + Radius of the point object. + u : int, optional + Number of faces in the "u" direction. + v : int, optional + Number of faces in the "v" direction. Returns ------- :blender:`bpy.types.Object` """ + name = self.geometry.name color = Color.coerce(color) or self.color bpy.ops.mesh.primitive_uv_sphere_add( @@ -94,10 +66,7 @@ def draw( ) obj = bpy.context.object - obj.name = self.geometry.name - - self.link_object(obj) - if color: - self.assign_object_color(obj, color) + self.objects.append(obj) + self.update_object(obj, name=name, color=color, collection=collection) return obj diff --git a/src/compas_blender/artists/pointcloudartist.py b/src/compas_blender/artists/pointcloudartist.py new file mode 100644 index 00000000000..53cae51143f --- /dev/null +++ b/src/compas_blender/artists/pointcloudartist.py @@ -0,0 +1,68 @@ +from typing import Any +from typing import Optional + +import bpy # type: ignore + +from compas.geometry import Point +from compas.colors import Color + +from compas.artists import GeometryArtist +from .artist import BlenderArtist + +from compas_blender import conversions + + +class PointcloudArtist(BlenderArtist, GeometryArtist): + """Artist for drawing pointclouds in Blender. + + Parameters + ---------- + pointcloud : :class:`~compas.geometry.Pointcloud` + A COMPAS point. + **kwargs : dict, optional + Additional keyword arguments. + For more info, + see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. + + """ + + def __init__(self, point: Point, **kwargs: Any): + super().__init__(geometry=point, **kwargs) + + def draw( + self, + color: Optional[Color] = None, + collection: Optional[str] = None, + size: float = 0.01, + u: int = 16, + v: int = 16, + ) -> bpy.types.Object: + """Draw the pointcloud. + + Parameters + ---------- + color : tuple[float, float, float] | tuple[int, int, int] | :class:`~compas.colors.Color`, optional + Color of the point object. + collection : str, optional + The name of the Blender scene collection containing the created object(s). + size : float, optional + Radius of the point object. + u : int, optional + Number of faces in the "u" direction. + v : int, optional + Number of faces in the "v" direction. + + Returns + ------- + :blender:`bpy.types.Object` + + """ + name = self.geometry.name + color = Color.coerce(color) or self.color + + mesh = conversions.pointcloud_to_blender(self.geometry, name=self.geometry.name) + + obj = self.create_object(mesh, name=name) + self.update_object(obj, name=name, color=color, collection=collection) + + return obj diff --git a/src/compas_blender/artists/polygonartist.py b/src/compas_blender/artists/polygonartist.py index db0877d1447..a731d78e878 100644 --- a/src/compas_blender/artists/polygonartist.py +++ b/src/compas_blender/artists/polygonartist.py @@ -1,14 +1,13 @@ from typing import Any from typing import List from typing import Optional -from typing import Union import bpy # type: ignore -import compas_blender from compas.artists import GeometryArtist from compas.geometry import Polygon from compas.colors import Color +from compas_blender import conversions from .artist import BlenderArtist @@ -19,46 +18,20 @@ class PolygonArtist(BlenderArtist, GeometryArtist): ---------- polygon : :class:`~compas.geometry.Polygon` A COMPAS polygon. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - .. code-block:: python - - from compas.geometry import Polygon - from compas_blender.artists import PolygonArtist - - polygon = Polygon.from_sides_and_radius_xy(5, 1) - - artist = PolygonArtist(polygon) - artist.draw() - - .. code-block:: python - - from compas.geometry import Polygon - from compas.artists import Artist - - polygon = Polygon.from_sides_and_radius_xy(5, 1) - - artist = Artist(polygon) - artist.draw() - """ - def __init__(self, polygon: Polygon, collection: Optional[Union[str, bpy.types.Collection]] = None, **kwargs: Any): - super().__init__(geometry=polygon, collection=collection or polygon.name, **kwargs) + def __init__(self, polygon: Polygon, **kwargs: Any): + super().__init__(geometry=polygon, **kwargs) def draw( self, color: Optional[Color] = None, - show_points: bool = False, - show_edges: bool = False, - show_face: bool = True, + collection: Optional[str] = None, ) -> List[bpy.types.Object]: """Draw the polygon. @@ -66,42 +39,20 @@ def draw( ---------- color : tuple[float, float, float] | tuple[int, int, int] | :class:`~compas.colors.Color`, optional The RGB color of the polygon. - The default color is :attr:`compas.artists.GeometryArtist.color`. - show_points : bool, optional - If True, draw the corner points of the polygon. - show_edges : bool, optional - If True, draw the edges of the polygon. - show_face : bool, optional - If True, draw the face of the polygon. + collection : str, optional + The Blender scene collection containing the created objects. Returns ------- - list[:blender:`bpy.types.Object`] + :blender:`bpy.types.Object` """ + name = self.geometry.name color = Color.coerce(color) or self.color - objects = [] - if show_points: - points = [ - { - "pos": point, - "color": color, - "name": self.geometry.name, - "radius": 0.01, - } - for point in self.geometry.points - ] - objects += compas_blender.draw_points(points, collection=self.collection) - if show_edges: - lines = [{"start": a, "end": b, "color": color, "name": self.geometry.name} for a, b in self.geometry.lines] - objects += compas_blender.draw_lines(lines, collection=self.collection) - if show_face: - polygons = [ - { - "points": self.geometry.points, - "color": color, - "name": self.geometry.name, - } - ] - objects += compas_blender.draw_faces(polygons, collection=self.collection) - return objects + + vertices, faces = self.geometry.to_vertices_and_faces() + mesh = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=collection) + + return obj diff --git a/src/compas_blender/artists/polyhedronartist.py b/src/compas_blender/artists/polyhedronartist.py index ffbbc465fdf..cd0e0f65b82 100644 --- a/src/compas_blender/artists/polyhedronartist.py +++ b/src/compas_blender/artists/polyhedronartist.py @@ -1,15 +1,16 @@ from typing import Any from typing import List from typing import Optional -from typing import Union import bpy # type: ignore -import compas_blender from compas.geometry import Polyhedron -from compas.artists import GeometryArtist from compas.colors import Color + +from compas.artists import GeometryArtist from .artist import BlenderArtist +from compas_blender import conversions + class PolyhedronArtist(BlenderArtist, GeometryArtist): """Artist for drawing polyhedron shapes in Blender. @@ -18,68 +19,39 @@ class PolyhedronArtist(BlenderArtist, GeometryArtist): ---------- polyhedron : :class:`~compas.geometry.Polyhedron` A COMPAS polyhedron. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import Polyhedron - from compas_blender.artists import PolyhedronArtist - - polyhedron = Polyhedron.from_platonicsolid(12) - - artist = PolyhedronArtist(polyhedron) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import Polyhedron - from compas.artists import Artist - - polyhedron = Polyhedron.from_platonicsolid(12) - - artist = Artist(polyhedron) - artist.draw() - """ - def __init__( - self, polyhedron: Polyhedron, collection: Optional[Union[str, bpy.types.Collection]] = None, **kwargs: Any - ): - super().__init__(geometry=polyhedron, collection=collection or polyhedron.name, **kwargs) + def __init__(self, polyhedron: Polyhedron, **kwargs: Any): + super().__init__(geometry=polyhedron, **kwargs) - def draw(self, color: Optional[Color] = None) -> List[bpy.types.Object]: + def draw(self, color: Optional[Color] = None, collection: Optional[str] = None) -> List[bpy.types.Object]: """Draw the polyhedron associated with the artist. Parameters ---------- color : tuple[float, float, float] | tuple[int, int, int] | :class:`~compas.colors.Color`, optional The RGB color of the polyhedron. - The default color is :attr:`compas.artists.GeometryArtist.color`. + collection : str, optional + The Blender scene collection containing the created object. Returns ------- - list[:blender:`bpy.types.Object`] - The objects created in Blender. + :blender:`bpy.types.Object` + The object created in Blender. """ + name = self.geometry.name color = Color.coerce(color) or self.color + vertices, faces = self.geometry.to_vertices_and_faces() - obj = compas_blender.draw_mesh( - vertices, - faces, - name=self.geometry.name, - color=color, - collection=self.collection, - ) - return [obj] + mesh = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) + + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=collection, show_wire=True) + + return obj diff --git a/src/compas_blender/artists/polylineartist.py b/src/compas_blender/artists/polylineartist.py index 70b826eaddf..56947f9c4a8 100644 --- a/src/compas_blender/artists/polylineartist.py +++ b/src/compas_blender/artists/polylineartist.py @@ -1,16 +1,16 @@ from typing import Any -from typing import List from typing import Optional -from typing import Union import bpy # type: ignore -import compas_blender -from compas.artists import GeometryArtist from compas.geometry import Polyline from compas.colors import Color + +from compas.artists import GeometryArtist from .artist import BlenderArtist +from compas_blender import conversions + class PolylineArtist(BlenderArtist, GeometryArtist): """Artist for drawing polylines in Blender. @@ -19,87 +19,37 @@ class PolylineArtist(BlenderArtist, GeometryArtist): ---------- polyline : :class:`~compas.geometry.Polyline` A COMPAS polyline. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import Polyline - from compas_blender.artists import PolylineArtist - - polyline = Polyline([[0, 0, 0], [1, 0, 0], [1, 1, 0], [1, 1, 1]]) - - artist = PolylineArtist(polyline) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import Polyline - from compas.artists import Artist - - polyline = Polyline([[0, 0, 0], [1, 0, 0], [1, 1, 0], [1, 1, 1]]) - - artist = Artist(polyline) - artist.draw() - """ - def __init__( - self, - polyline: Polyline, - collection: Optional[Union[str, bpy.types.Collection]] = None, - **kwargs: Any, - ): - super().__init__(geometry=polyline, collection=collection or polyline.name, **kwargs) + def __init__(self, polyline: Polyline, **kwargs: Any): + super().__init__(geometry=polyline, **kwargs) - def draw(self, color: Optional[Color] = None, show_points: Optional[bool] = False) -> List[bpy.types.Object]: + def draw(self, color: Optional[Color] = None, collection: Optional[str] = None) -> bpy.types.Object: """Draw the line. Parameters ---------- color : tuple[float, float, float] | tuple[int, int, int] | :class:`~compas.colors.Color`, optional The RGB color of the polyline. - The default color is :attr:`compas.artists.GeometryArtist.color`. - show_points : bool, optional - If True, draw the corner points of the polyline. + collection : str, optional + The name of the Blender scene collection containing the created object(s). Returns ------- - list[:blender:`bpy.types.Object`] + :blender:`bpy.types.Object` """ + name = self.geometry.name color = Color.coerce(color) or self.color - lines = [ - { - "start": start, - "end": end, - "color": self.color, - "name": f"{self.geometry.name}", - } - for start, end in self.geometry.lines - ] - objects = compas_blender.draw_lines(lines, collection=self.collection) - - if show_points: - points = [ - { - "pos": point, - "name": f"{self.geometry.name}.point", - "color": color, - "radius": 0.01, - } - for point in self.geometry.points - ] - objects += compas_blender.draw_points(points, collection=self.collection) - return objects + curve = conversions.polyline_to_blender_curve(self.geometry, name=name) + + obj = self.create_object(curve, name=name) + self.update_object(obj, color=color, collection=collection) + + return obj diff --git a/src/compas_blender/artists/sphereartist.py b/src/compas_blender/artists/sphereartist.py index 2bb468c92bc..d902d643011 100644 --- a/src/compas_blender/artists/sphereartist.py +++ b/src/compas_blender/artists/sphereartist.py @@ -1,16 +1,17 @@ from typing import Optional from typing import Any from typing import List -from typing import Union import bpy # type: ignore -import compas_blender from compas.geometry import Sphere -from compas.artists import GeometryArtist from compas.colors import Color + +from compas.artists import GeometryArtist from .artist import BlenderArtist +from compas_blender import conversions + class SphereArtist(BlenderArtist, GeometryArtist): """Artist for drawing sphere shapes in Blender. @@ -19,73 +20,44 @@ class SphereArtist(BlenderArtist, GeometryArtist): ---------- sphere : :class:`~compas.geometry.Sphere` A COMPAS sphere. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import Sphere - from compas_blender.artists import SphereArtist - - sphere = Sphere([0, 0, 0], 1) - - artist = SphereArtist(sphere) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import Sphere - from compas.artists import Artist - - sphere = Sphere([0, 0, 0], 1) - - artist = Artist(sphere) - artist.draw() - """ - def __init__(self, sphere: Sphere, collection: Optional[Union[str, bpy.types.Collection]] = None, **kwargs: Any): - super().__init__(geometry=sphere, collection=collection or sphere.name, **kwargs) + def __init__(self, sphere: Sphere, **kwargs: Any): + super().__init__(geometry=sphere, **kwargs) - def draw(self, color: Optional[Color] = None, u: int = None, v: int = None) -> List[bpy.types.Object]: + def draw( + self, color: Optional[Color] = None, collection: Optional[str] = None, u: int = 16, v: int = 16 + ) -> List[bpy.types.Object]: """Draw the sphere associated with the artist. Parameters ---------- color : tuple[float, float, float] | tuple[int, int, int] | :class:`~compas.colors.Color`, optional The RGB color of the sphere. - The default color is :attr:`compas.artists.GeometryArtist.color`. + collection : str, optional + The name of the Blender scene collection containing the created object(s). u : int, optional Number of faces in the "u" direction. - Default is ``SphereArtist.u``. v : int, optional Number of faces in the "v" direction. - Default is ``SphereArtist.v``. Returns ------- list The objects created in Blender. """ - u = u or self.u - v = v or self.v + name = self.geometry.name color = Color.coerce(color) or self.color + vertices, faces = self.geometry.to_vertices_and_faces(u=u, v=v) - obj = compas_blender.draw_mesh( - vertices, - faces, - name=self.geometry.name, - color=color, - collection=self.collection, - ) - return [obj] + mesh = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) + + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=collection, show_wire=True) + + return obj diff --git a/src/compas_blender/artists/surfaceartist.py b/src/compas_blender/artists/surfaceartist.py index 056b1c3b22b..4955981f34e 100644 --- a/src/compas_blender/artists/surfaceartist.py +++ b/src/compas_blender/artists/surfaceartist.py @@ -1,16 +1,16 @@ from typing import Any -from typing import List from typing import Optional -from typing import Union import bpy # type: ignore -import compas_blender -from compas.artists import GeometryArtist from compas.geometry import Surface from compas.colors import Color + +from compas.artists import GeometryArtist from compas_blender.artists import BlenderArtist +from compas_blender import conversions + class SurfaceArtist(BlenderArtist, GeometryArtist): """Artist for drawing surfaces in Blender. @@ -19,58 +19,37 @@ class SurfaceArtist(BlenderArtist, GeometryArtist): ---------- surface : :class:`~compas.geometry.Surface` A COMPAS surface. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import NurbsSurface - from compas_blender.artists import SurfaceArtist - - surface = NurbsSurface([[0, 0, 0], [1, 0, 0], [1, 1, 0], [1, 1, 1]]) - - artist = SurfaceArtist(surface) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import NurbsSurface - from compas.artists import Artist - - surface = NurbsSurface([[0, 0, 0], [1, 0, 0], [1, 1, 0], [1, 1, 1]]) - - artist = Artist(surface) - artist.draw() - """ - def __init__(self, surface: Surface, collection: Optional[Union[str, bpy.types.Collection]] = None, **kwargs: Any): - super().__init__(geometry=surface, collection=collection or surface.name, **kwargs) + def __init__(self, surface: Surface, **kwargs: Any): + super().__init__(geometry=surface, **kwargs) - def draw(self, color: Optional[Color] = None) -> List[bpy.types.Object]: + def draw(self, color: Optional[Color] = None, collection: Optional[str] = None) -> bpy.types.Object: """Draw the surface. Parameters ---------- color : tuple[float, float, float] | tuple[int, int, int] | :class:`~compas.colors.Color`, optional The RGB color of the surface. - The default color is :attr:`compas.artists.SurfaceArtist.color`. + collection : str, optional + The name of the Blender scene collection containing the created object(s). Returns ------- - list[:blender:`bpy.types.Object`] + :blender:`bpy.types.Object` """ + name = self.geometry.name color = Color.coerce(color) or self.color - surfaces = [{"surface": self.geometry, "color": color, "name": self.geometry.name}] - return compas_blender.draw_surfaces(surfaces, collection=self.collection) + + surface = conversions.nurbssurface_to_blender_surface(self.geometry) + + obj = self.create_object(surface, name=name) + self.update_object(obj, color=color, collection=collection) + + return obj diff --git a/src/compas_blender/artists/torusartist.py b/src/compas_blender/artists/torusartist.py index 603caf1a7c6..3bdb48a77b9 100644 --- a/src/compas_blender/artists/torusartist.py +++ b/src/compas_blender/artists/torusartist.py @@ -1,16 +1,16 @@ from typing import Optional from typing import Any -from typing import List -from typing import Union import bpy # type: ignore -import compas_blender from compas.geometry import Torus -from compas.artists import GeometryArtist from compas.colors import Color + +from compas.artists import GeometryArtist from .artist import BlenderArtist +from compas_blender import conversions + class TorusArtist(BlenderArtist, GeometryArtist): """Artist for drawing torus shapes in Blender. @@ -19,79 +19,48 @@ class TorusArtist(BlenderArtist, GeometryArtist): ---------- torus : :class:`~compas.geometry.Torus` A COMPAS torus. - collection: str | :blender:`bpy.types.Collection` - The name of the collection the object belongs to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import Plane, Torus - from compas_blender.artists import TorusArtist - - torus = Torus(Plane([0, 0, 0], [0, 0, 1]), 1.0, 0.3) - - artist = TorusArtist(torus) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import Plane, Torus - from compas.artists import Artist - - torus = Torus(Plane([0, 0, 0], [0, 0, 1]), 1.0, 0.3) - - artist = Artist(torus) - artist.draw() - """ - def __init__(self, torus: Torus, collection: Optional[Union[str, bpy.types.Collection]] = None, **kwargs: Any): - super().__init__(geometry=torus, collection=collection or torus.name, **kwargs) + def __init__(self, torus: Torus, **kwargs: Any): + super().__init__(geometry=torus, **kwargs) def draw( self, color: Optional[Color] = None, + collection: Optional[str] = None, u: Optional[int] = None, v: Optional[int] = None, - ) -> List[bpy.types.Object]: + ) -> bpy.types.Object: """Draw the torus associated with the artist. Parameters ---------- color : tuple[float, float, float] | tuple[int, int, int] | :class:`~compas.colors.Color`, optional The RGB color of the torus. - The default color is :attr:`compas.artists.GeometryArtist.color`. + collection : str, optional + The name of the Blender scene collection containing the created object(s). u : int, optional Number of faces in the "u" direction. - Default is :attr:`TorusArtist.u`. v : int, optional Number of faces in the "v" direction. - Default is :attr:`TorusArtist.v`. Returns ------- - list - The objects created in Blender. + :blender:`bpy.types.Curve` """ - u = u or self.u - v = v or self.v + name = self.geometry.name color = Color.coerce(color) or self.color + vertices, faces = self.geometry.to_vertices_and_faces(u=u, v=v) - obj = compas_blender.draw_mesh( - vertices, - faces, - name=self.geometry.name, - color=color, - collection=self.collection, - ) - return [obj] + mesh = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) + + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=collection, show_wire=True) + + return obj diff --git a/src/compas_blender/artists/vectorartist.py b/src/compas_blender/artists/vectorartist.py index 7eb18f59f65..194a607b16a 100644 --- a/src/compas_blender/artists/vectorartist.py +++ b/src/compas_blender/artists/vectorartist.py @@ -1,17 +1,18 @@ from typing import Any -from typing import List from typing import Optional -from typing import Union import bpy # type: ignore -import compas_blender -from compas.artists import GeometryArtist from compas.geometry import Point from compas.geometry import Vector +from compas.geometry import Line from compas.colors import Color + +from compas.artists import GeometryArtist from .artist import BlenderArtist +from compas_blender import conversions + class VectorArtist(BlenderArtist, GeometryArtist): """Artist for drawing vectors in Blender. @@ -20,94 +21,50 @@ class VectorArtist(BlenderArtist, GeometryArtist): ---------- primitive : :class:`~compas.geometry.Vector` A COMPAS vector. - collection : str | :blender:`bpy.types.Collection` - The Blender scene collection the object(s) created by this artist belong to. **kwargs : dict, optional Additional keyword arguments. For more info, see :class:`~compas_blender.artists.BlenderArtist` and :class:`~compas.artists.GeometryArtist`. - Examples - -------- - Use the Blender artist explicitly. - - .. code-block:: python - - from compas.geometry import Vector - from compas_blender.artists import VectorArtist - - vector = Vector(1, 1, 1) - - artist = VectorArtist(vector) - artist.draw() - - Or, use the artist through the plugin mechanism. - - .. code-block:: python - - from compas.geometry import Vector - from compas.artists import Artist - - vector = Vector(1, 1, 1) - - artist = Artist(vector) - artist.draw() - """ - def __init__( - self, - vector: Vector, - collection: Optional[Union[str, bpy.types.Collection]] = None, - **kwargs: Any, - ): - super().__init__(geometry=vector, collection=collection or vector.name, **kwargs) + def __init__(self, vector: Vector, **kwargs: Any): + super().__init__(geometry=vector, **kwargs) def draw( self, color: Optional[Color] = None, + collection: Optional[str] = None, point: Optional[Point] = None, - show_point: Optional[bool] = False, - ) -> List[bpy.types.Object]: + ) -> bpy.types.Object: """Draw the vector. Parameters ---------- color : tuple[float, float, float] | tuple[int, int, int] | :class:`~compas.colors.Color`, optional The RGB color of the vector. - The default color is :attr:`compas.artists.GeometryArtist.color`. + collection : str, optional + The name of the Blender scene collection containing the created object(s). point : [float, float, float] | :class:`~compas.geometry.Point`, optional Point of application of the vector. Default is ``Point(0, 0, 0)``. - show_point : bool, optional - If True, draw the point of application of the vector. Returns ------- - list[:blender:`bpy.types.Object`] + :blender:`bpy.types.Object` """ - point = point or (0.0, 0.0, 0.0) - start = Point(*point) - end = start + self.geometry + name = self.geometry.name color = Color.coerce(color) or self.color - lines = [ - { - "start": start, - "end": end, - "color": color, - "name": f"{self.geometry.name}", - }, - ] - objects = compas_blender.draw_lines(lines, self.collection) - if show_point: - points = [ - { - "pos": start, - "name": f"{self.geometry.name}.origin", - "color": (1.0, 1.0, 1.0), - "radius": 0.01, - } - ] - objects += compas_blender.draw_points(points, self.collection) - return objects + + point = point or (0.0, 0.0, 0.0) # type: ignore + start = Point(*point) # type: ignore + end = start + self.geometry + line = Line(start, end) + + curve = conversions.line_to_blender_curve(line) + + obj = self.create_object(curve, name=name) + self.update_object(obj, color=color, collection=collection) + + return obj