diff --git a/CHANGELOG.md b/CHANGELOG.md index a73a050f2b8..d81aa4b27c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added implementation of `compute_vertices`, `compute_edges`, `compute_faces` to `compas.geometry.Cylinder`. * Added implementation of `compute_vertices`, `compute_edges`, `compute_faces` to `compas.geometry.Sphere`. * Added implementation of `compute_vertices`, `compute_edges`, `compute_faces` to `compas.geometry.Torus`. +* Added `compas_blender.scene.ShapeObject`. * Added `compas.geometry.vector.__radd__`. * Added `compas.geometry.vector.__rsub__`. * Added `compas.geometry.vector.__rmul__`. @@ -32,12 +33,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Changed check for empty vertices and faces to use `is None` to add support for `numpy` arrays. * Changed order of `u` and `v` of `compas.geometry.SphericalSurface` to the match the excpected parametrisation. * Changed `compas.geometry.Shape.to_vertices_and_faces` to use `Shape.vertices` and `Shape.faces` or `Shape.triangles`. +* Changed default of `compas.scene.descriptors.color.ColorAttribute` to `None` to support native coloring in CAD contexts. +* Changed `compas.colors.ColorDict.__data__` and `compas.colors.ColorDict.__from_data__` to properly support serialisation. +* Moved `compas_blender.utilities.drawing` to `compas_blender.drawing` with backward compatible imports and deprecation warning. +* Moved `compas_ghpython.utilities.drawing` to `compas_ghpython.drawing` with backward compatible imports and deprecation warning. +* Moved `compas_rhino.utilities.drawing` to `compas_rhino.drawing` with backward compatible imports and deprecation warning. +* Changed `draw_nodes` and `draw_edges` of `compas_blender.scene.GraphObject`, `compas_ghpython.scene.GraphObject`, and `compas_rhino.scene.GraphObject` to use only attributes instead of parameters. +* Changed `draw_vertices`, `draw_edges` and `draw_faces` of `compas_blender.scene.MeshObject`, `compas_ghpython.scene.MeshObject`, and `compas_rhino.scene.MeshObject` to use only attributes instead of parameters. +* Changed `draw_vertices`, `draw_edges` and `draw_faces` of `compas_blender.scene.VolMeshObject`, `compas_ghpython.scene.VolMeshObject`, and `compas_rhino.scene.VolMeshObject` to use only attributes instead of parameters. +* Changed registration of `Capsule`, `Cone`, `Cylinder`, `Sphere`, `Torus` to `ShapeObject` in `compas_blender.scene`. * Updated `compas.geometry.vector.__mul__` to allow element-wise multiplication with another vector. * Updated `compas.geometry.vector.__truediv__` to allow element-wise division with another vector. ### Removed -* Removed `System`dependency in `compas_ghpython/utilities/drawing.py`. +* Removed `System` dependency in `compas_ghpython/utilities/drawing.py`. +* Removed GH plugin for `compas.scene.clear` since it clashed with the Rhino version. ## [2.1.1] 2024-05-14 diff --git a/src/compas/colors/colordict.py b/src/compas/colors/colordict.py index 5a86b8fa46a..916da688de7 100644 --- a/src/compas/colors/colordict.py +++ b/src/compas/colors/colordict.py @@ -26,7 +26,15 @@ class ColorDict(Data): @property def __data__(self): - return {"default": self.default.__data__, "dict": self._dict} + return {"default": self.default, "dict": self._dict} + + @classmethod + def __from_data__(cls, data): + colordict = cls(data["default"]) + # note: this is specific to color dicts for vertices of meshes + # perhaps the color dict needs to be subclassed per scene object type + colordict.update({int(key): value for key, value in data["dict"].items()}) + return colordict def __init__(self, default, name=None): super(ColorDict, self).__init__(name=name) diff --git a/src/compas/scene/assemblyobject.py b/src/compas/scene/assemblyobject.py deleted file mode 100644 index 878841c1840..00000000000 --- a/src/compas/scene/assemblyobject.py +++ /dev/null @@ -1,3 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function diff --git a/src/compas/scene/descriptors/color.py b/src/compas/scene/descriptors/color.py index 3a3e1014e88..aac747057fe 100644 --- a/src/compas/scene/descriptors/color.py +++ b/src/compas/scene/descriptors/color.py @@ -14,7 +14,6 @@ class ColorAttribute(object): def __init__(self, default=None, **kwargs): super(ColorAttribute, self).__init__(**kwargs) - default = default or Color.black() self.default = Color.coerce(default) def __set_name__(self, owner, name): diff --git a/src/compas/scene/geometryobject.py b/src/compas/scene/geometryobject.py index 45ffbd8336e..952df6be11c 100644 --- a/src/compas/scene/geometryobject.py +++ b/src/compas/scene/geometryobject.py @@ -52,7 +52,5 @@ def __init__(self, geometry, **kwargs): self.show_points = kwargs.get("show_points", False) self.show_lines = kwargs.get("show_lines", True) self.show_surfaces = kwargs.get("show_surfaces", True) - - def draw(self): - """Draw the geometry. Implemented by child classes.""" - raise NotImplementedError + # note: either lines should be renamed to curves + # or surfaces should be renamed to faces? diff --git a/src/compas/scene/graphobject.py b/src/compas/scene/graphobject.py index bc1651dfc3d..15f28e51b13 100644 --- a/src/compas/scene/graphobject.py +++ b/src/compas/scene/graphobject.py @@ -2,6 +2,9 @@ from __future__ import division from __future__ import print_function +import compas.colors # noqa: F401 +import compas.datastructures # noqa: F401 +import compas.geometry # noqa: F401 from compas.geometry import transform_points from .descriptors.colordict import ColorDictAttribute @@ -31,7 +34,7 @@ class GraphObject(SceneObject): The size of the nodes. Default is ``1.0``. edgewidth : float The width of the edges. Default is ``1.0``. - show_nodes : Union[bool, sequence[float]] + show_nodes : Union[bool, sequence[hashable]] Flag for showing or hiding the nodes. Default is ``True``. show_edges : Union[bool, sequence[tuple[hashable, hashable]]] Flag for showing or hiding the edges. Default is ``True``. @@ -46,64 +49,76 @@ class GraphObject(SceneObject): nodecolor = ColorDictAttribute() edgecolor = ColorDictAttribute() - def __init__(self, graph, **kwargs): + def __init__(self, graph, show_nodes=True, show_edges=True, nodecolor=None, edgecolor=None, nodesize=1.0, edgewidth=1.0, **kwargs): + # type: (compas.datastructures.Graph, bool | list, bool | list, compas.colors.Color | dict | None, compas.colors.Color | dict | None, float | dict, float | dict, dict) -> None super(GraphObject, self).__init__(item=graph, **kwargs) self._graph = None self._node_xyz = None self.graph = graph - self.nodecolor = kwargs.get("nodecolor", self.color) - self.edgecolor = kwargs.get("edgecolor", self.color) - self.nodesize = kwargs.get("nodesize", 1.0) - self.edgewidth = kwargs.get("edgewidth", 1.0) - self.show_nodes = kwargs.get("show_nodes", True) - self.show_edges = kwargs.get("show_edges", True) + self.show_nodes = show_nodes + self.show_edges = show_edges + self.nodecolor = nodecolor or self.color + self.edgecolor = edgecolor or self.color + self.nodesize = nodesize + self.edgewidth = edgewidth + + @property + def settings(self): + # type: () -> dict + settings = super(GraphObject, self).settings + settings["show_nodes"] = self.show_nodes + settings["show_edges"] = self.show_edges + settings["nodecolor"] = self.nodecolor + settings["edgecolor"] = self.edgecolor + settings["nodesize"] = self.nodesize + settings["edgewidth"] = self.edgewidth + return settings @property def graph(self): + # type: () -> compas.datastructures.Graph return self._graph @graph.setter def graph(self, graph): + # type: (compas.datastructures.Graph) -> None self._graph = graph self._transformation = None self._node_xyz = None @property def transformation(self): + # type: () -> compas.geometry.Transformation | None return self._transformation @transformation.setter def transformation(self, transformation): + # type: (compas.geometry.Transformation) -> None self._node_xyz = None self._transformation = transformation @property def node_xyz(self): + # type: () -> dict[int | str | tuple, list[float]] if self._node_xyz is None: - points = self.graph.nodes_attributes("xyz") # type: ignore + points = self.graph.nodes_attributes("xyz") points = transform_points(points, self.worldtransformation) - self._node_xyz = dict(zip(self.graph.nodes(), points)) # type: ignore + self._node_xyz = dict(zip(self.graph.nodes(), points)) return self._node_xyz @node_xyz.setter def node_xyz(self, node_xyz): + # type: (dict[int | str | tuple, list[float]]) -> None self._node_xyz = node_xyz - def draw_nodes(self, nodes=None, color=None, text=None): + def draw_nodes(self): """Draw the nodes of the graph. - Parameters - ---------- - nodes : list[int], optional - The nodes to include in the drawing. - Default is all nodes. - color : tuple[float, float, float] | :class:`compas.colors.Color` | dict[int, tuple[float, float, float] | :class:`compas.colors.Color`], optional - The color of the nodes, - as either a single color to be applied to all nodes, - or a color dict, mapping specific nodes to specific colors. - text : dict[int, str], optional - The text labels for the nodes - as a text dict, mapping specific nodes to specific text labels. + Nodes are drawn based on the values of + + * `self.show_nodes` + * `self.nodecolor` + * `self.nodesize` Returns ------- @@ -113,21 +128,14 @@ def draw_nodes(self, nodes=None, color=None, text=None): """ raise NotImplementedError - def draw_edges(self, edges=None, color=None, text=None): + def draw_edges(self): """Draw the edges of the graph. - Parameters - ---------- - edges : list[tuple[int, int]], optional - The edges to include in the drawing. - Default is all edges. - color : tuple[float, float, float] | :class:`compas.colors.Color` | dict[tuple[int, int], tuple[float, float, float] | :class:`compas.colors.Color`], optional - The color of the edges, - as either a single color to be applied to all edges, - or a color dict, mapping specific edges to specific colors. - text : dict[tuple[int, int]], optional - The text labels for the edges - as a text dict, mapping specific edges to specific text labels. + Edges are drawn based on the values of + + * `self.show_edges` + * `self.edgecolor` + * `self.edgewidth` Returns ------- @@ -137,10 +145,6 @@ def draw_edges(self, edges=None, color=None, text=None): """ raise NotImplementedError - def draw(self): - """Draw the network.""" - raise NotImplementedError - def clear_nodes(self): """Clear the nodes of the graph. diff --git a/src/compas/scene/meshobject.py b/src/compas/scene/meshobject.py index 715e24477f8..75cf222fac4 100644 --- a/src/compas/scene/meshobject.py +++ b/src/compas/scene/meshobject.py @@ -2,6 +2,9 @@ from __future__ import division from __future__ import print_function +import compas.colors # noqa: F401 +import compas.datastructures # noqa: F401 +import compas.geometry # noqa: F401 from compas.geometry import transform_points from .descriptors.colordict import ColorDictAttribute @@ -56,66 +59,83 @@ class MeshObject(SceneObject): edgecolor = ColorDictAttribute() facecolor = ColorDictAttribute() - def __init__(self, mesh, **kwargs): + def __init__(self, mesh, show_vertices=False, show_edges=False, show_faces=True, vertexcolor=None, edgecolor=None, facecolor=None, vertexsize=1.0, edgewidth=1.0, **kwargs): + # type: (compas.datastructures.Mesh, bool | list, bool | list, bool | list, compas.colors.Color | dict, compas.colors.Color | dict, compas.colors.Color | dict, float | dict, float | dict, dict) -> None super(MeshObject, self).__init__(item=mesh, **kwargs) self._mesh = None self._vertex_xyz = None self.mesh = mesh - self.vertexcolor = kwargs.get("vertexcolor", self.contrastcolor) - self.edgecolor = kwargs.get("edgecolor", self.contrastcolor) - self.facecolor = kwargs.get("facecolor", self.color) - self.vertexsize = kwargs.get("vertexsize", 1.0) - self.edgewidth = kwargs.get("edgewidth", 1.0) - self.show_vertices = kwargs.get("show_vertices", False) - self.show_edges = kwargs.get("show_edges", False) - self.show_faces = kwargs.get("show_faces", True) + self.show_vertices = show_vertices + self.show_edges = show_edges + self.show_faces = show_faces + self.vertexcolor = vertexcolor or self.contrastcolor + self.edgecolor = edgecolor or self.contrastcolor + self.facecolor = facecolor or self.color + self.vertexsize = vertexsize + self.edgewidth = edgewidth + + @property + def settings(self): + # type: () -> dict + settings = super(MeshObject, self).settings + # perhaps this should be renamed to just "vertices", "edges", "faces" + settings["show_vertices"] = self.show_vertices + settings["show_edges"] = self.show_edges + settings["show_faces"] = self.show_faces + # end of perhaps + settings["vertexcolor"] = self.vertexcolor + settings["edgecolor"] = self.edgecolor + settings["facecolor"] = self.facecolor + settings["vertexsize"] = self.vertexsize + settings["edgewidth"] = self.edgewidth + return settings @property def mesh(self): + # type: () -> compas.datastructures.Mesh return self._mesh @mesh.setter def mesh(self, mesh): + # type: (compas.datastructures.Mesh) -> None self._mesh = mesh self._transformation = None self._vertex_xyz = None @property def transformation(self): + # type: () -> compas.geometry.Transformation | None return self._transformation @transformation.setter def transformation(self, transformation): + # type: (compas.geometry.Transformation) -> None self._vertex_xyz = None self._transformation = transformation @property def vertex_xyz(self): + # type: () -> dict[int, list[float]] if self._vertex_xyz is None: - points = self.mesh.vertices_attributes("xyz") # type: ignore + points = self.mesh.vertices_attributes("xyz") points = transform_points(points, self.worldtransformation) - self._vertex_xyz = dict(zip(self.mesh.vertices(), points)) # type: ignore + self._vertex_xyz = dict(zip(self.mesh.vertices(), points)) return self._vertex_xyz @vertex_xyz.setter def vertex_xyz(self, vertex_xyz): + # type: (dict[int, list[float]]) -> None self._vertex_xyz = vertex_xyz - def draw_vertices(self, vertices=None, color=None, text=None): + def draw_vertices(self): """Draw the vertices of the mesh. - Parameters - ---------- - vertices : list[int], optional - The vertices to include in the drawing. - Default is all vertices. - color : tuple[float, float, float] | :class:`compas.colors.Color` | dict[int, tuple[float, float, float] | :class:`compas.colors.Color`], optional - The color of the vertices, - as either a single color to be applied to all vertices, - or a color dict, mapping specific vertices to specific colors. - text : dict[int, str], optional - The text labels for the vertices - as a text dict, mapping specific vertices to specific text labels. + Vertices are drawn based on the values of + + * `self.show_vertices` + * `self.vertexcolor` + * `self.vertextext` + * `self.vertexsize` Returns ------- @@ -125,21 +145,15 @@ def draw_vertices(self, vertices=None, color=None, text=None): """ raise NotImplementedError - def draw_edges(self, edges=None, color=None, text=None): + def draw_edges(self): """Draw the edges of the mesh. - Parameters - ---------- - edges : list[tuple[int, int]], optional - The edges to include in the drawing. - Default is all edges. - color : tuple[float, float, float] | :class:`compas.colors.Color` | dict[tuple[int, int], tuple[float, float, float] | :class:`compas.colors.Color`], optional - The color of the edges, - as either a single color to be applied to all edges, - or a color dict, mapping specific edges to specific colors. - text : dict[tuple[int, int], str], optional - The text labels for the edges - as a text dict, mapping specific edges to specific text labels. + Edges are drawn based on the values of + + * `self.show_edges` + * `self.edgecolor` + * `self.edgetext` + * `self.edgewidth` Returns ------- @@ -149,21 +163,14 @@ def draw_edges(self, edges=None, color=None, text=None): """ raise NotImplementedError - def draw_faces(self, faces=None, color=None, text=None): + def draw_faces(self): """Draw the faces of the mesh. - Parameters - ---------- - faces : list[int], optional - The faces to include in the drawing. - Default is all faces. - color : tuple[float, float, float] | :class:`compas.colors.Color` | dict[int, tuple[float, float, float] | :class:`compas.colors.Color`], optional - The color of the faces, - as either a single color to be applied to all faces, - or a color dict, mapping specific faces to specific colors. - text : dict[int, str], optional - The text labels for the faces - as a text dict, mapping specific faces to specific text labels. + Faces are drawn based on the values of + + * `self.show_faces` + * `self.facecolor` + * `self.facetext` Returns ------- @@ -187,26 +194,6 @@ def draw_mesh(self, *args, **kwargs): """ return self.draw(*args, **kwargs) - def draw(self): - """draw the mesh. - - Returns - ------- - None - - """ - raise NotImplementedError - - def clear(self): - """Clear all components of the mesh. - - Returns - ------- - None - - """ - raise NotImplementedError - def clear_vertices(self): """Clear the vertices of the mesh. diff --git a/src/compas/scene/scene.py b/src/compas/scene/scene.py index b181691740d..a6d95966be2 100644 --- a/src/compas/scene/scene.py +++ b/src/compas/scene/scene.py @@ -112,7 +112,7 @@ def add(self, item, parent=None, **kwargs): def clear(self): # type: () -> None - """Clear the current context of the scene.""" + """Clear everything from the current context of the scene.""" clear() def clear_objects(self): @@ -127,11 +127,11 @@ def clear_objects(self): def draw(self): """Draw the scene.""" - before_draw() - if not self.context: raise ValueError("No context detected.") + before_draw() + self.clear_objects() drawn_objects = [] diff --git a/src/compas/scene/sceneobject.py b/src/compas/scene/sceneobject.py index a3ea109818b..51988a7bcea 100644 --- a/src/compas/scene/sceneobject.py +++ b/src/compas/scene/sceneobject.py @@ -2,11 +2,11 @@ from __future__ import division from __future__ import print_function -from abc import abstractmethod from functools import reduce from operator import mul import compas.colors # noqa: F401 +import compas.data # noqa: F401 import compas.datastructures # noqa: F401 import compas.geometry # noqa: F401 from compas.colors import Color @@ -88,8 +88,7 @@ def __new__(cls, item, **kwargs): def __init__(self, item, name=None, color=None, opacity=1.0, show=True, frame=None, transformation=None, context=None, **kwargs): # fmt: skip # type: (compas.geometry.Geometry | compas.datastructures.Datastructure, str | None, compas.colors.Color | None, float, bool, compas.geometry.Frame | None, compas.geometry.Transformation | None, str | None, dict) -> None - name = name or item.name - super(SceneObject, self).__init__(name=name, **kwargs) + super(SceneObject, self).__init__(name=name or item.name, **kwargs) # the scene object needs to store the context # because it has no access to the tree and/or the scene before it is added # which means that adding child objects will be added in context "None" @@ -181,10 +180,11 @@ def worldtransformation(self): def contrastcolor(self): # type: () -> compas.colors.Color if not self._contrastcolor: - if self.color.is_light: - self._contrastcolor = self.color.darkened(50) - else: - self._contrastcolor = self.color.lightened(50) + if self.color: + if self.color.is_light: + self._contrastcolor = self.color.darkened(50) + else: + self._contrastcolor = self.color.lightened(50) return self._contrastcolor @contrastcolor.setter @@ -195,7 +195,6 @@ def contrastcolor(self, color): @property def settings(self): # type: () -> dict - # The settings are all the nessessary attributes to reconstruct the scene object besides the Data item. settings = { "name": self.name, "color": self.color, @@ -211,6 +210,7 @@ def settings(self): return settings def add(self, item, **kwargs): + # type: (compas.data.Data, dict) -> SceneObject """Add a child item to the scene object. Parameters @@ -242,7 +242,6 @@ def add(self, item, **kwargs): super(SceneObject, self).add(sceneobject) return sceneobject - @abstractmethod def draw(self): """The main drawing method.""" raise NotImplementedError diff --git a/src/compas/scene/volmeshobject.py b/src/compas/scene/volmeshobject.py index 6ca1135b651..ecc8fcc0a67 100644 --- a/src/compas/scene/volmeshobject.py +++ b/src/compas/scene/volmeshobject.py @@ -2,6 +2,8 @@ from __future__ import division from __future__ import print_function +import compas.datastructures # noqa: F401 +import compas.geometry # noqa: F401 from compas.geometry import transform_points from .descriptors.colordict import ColorDictAttribute @@ -64,43 +66,77 @@ class VolMeshObject(SceneObject): facecolor = ColorDictAttribute() cellcolor = ColorDictAttribute() - def __init__(self, volmesh, **kwargs): + def __init__( + self, + volmesh, + show_vertices=False, + show_edges=True, + show_faces=False, + show_cells=True, + vertexcolor=None, + edgecolor=None, + facecolor=None, + cellcolor=None, + vertexsize=1.0, + edgewidth=1.0, + **kwargs, + ): + # type: (compas.datastructures.VolMesh, bool | list, bool | list, bool | list, bool | list, compas.colors.Color | dict, compas.colors.Color | dict, compas.colors.Color | dict, compas.colors.Color | dict, float | dict, float | dict, dict) -> None super(VolMeshObject, self).__init__(item=volmesh, **kwargs) self._volmesh = None self._vertex_xyz = None self.volmesh = volmesh - self.vertexcolor = kwargs.get("vertexcolor", self.color) - self.edgecolor = kwargs.get("edgecolor", self.color) - self.facecolor = kwargs.get("facecolor", self.color) - self.cellcolor = kwargs.get("cellcolor", self.color) - self.vertexsize = kwargs.get("vertexsize", 1.0) - self.edgewidth = kwargs.get("edgewidth", 1.0) - self.show_vertices = kwargs.get("show_vertices", False) - self.show_edges = kwargs.get("show_edges", True) - self.show_faces = kwargs.get("show_faces", False) - self.show_cells = kwargs.get("show_cells", True) + self.show_vertices = show_vertices + self.show_edges = show_edges + self.show_faces = show_faces + self.show_cells = show_cells + self.vertexcolor = vertexcolor or self.color + self.edgecolor = edgecolor or self.color + self.facecolor = facecolor or self.color + self.cellcolor = cellcolor or self.color + self.vertexsize = vertexsize + self.edgewidth = edgewidth + + @property + def settings(self): + # type: () -> dict + settings = super(VolMeshObject, self).settings + settings["show_vertices"] = self.show_vertices + settings["show_edges"] = self.show_edges + settings["show_faces"] = self.show_faces + settings["show_cells"] = self.show_cells + settings["vertexcolor"] = self.vertexcolor + settings["edgecolor"] = self.edgecolor + settings["facecolor"] = self.facecolor + settings["cellcolor"] = self.cellcolor + return settings @property def volmesh(self): + # type: () -> compas.datastructures.VolMesh return self._volmesh @volmesh.setter def volmesh(self, volmesh): + # type: (compas.datastructures.VolMesh) -> None self._volmesh = volmesh self._transformation = None self._vertex_xyz = None @property def transformation(self): + # type: () -> compas.geometry.Transformation | None return self._transformation @transformation.setter def transformation(self, transformation): + # type: (compas.geometry.Transformation) -> None self._vertex_xyz = None self._transformation = transformation @property def vertex_xyz(self): + # type: () -> dict[int, list[float]] if self._vertex_xyz is None: points = self.volmesh.vertices_attributes("xyz") # type: ignore points = transform_points(points, self.worldtransformation) @@ -109,23 +145,17 @@ def vertex_xyz(self): @vertex_xyz.setter def vertex_xyz(self, vertex_xyz): + # type: (dict[int, list[float]]) -> None self._vertex_xyz = vertex_xyz - def draw_vertices(self, vertices=None, color=None, text=None): + def draw_vertices(self): """Draw the vertices of the mesh. - Parameters - ---------- - vertices : list[int], optional - The vertices to include in the drawing. - Default is all vertices. - color : tuple[float, float, float] | :class:`compas.colors.Color` | dict[int, tuple[float, float, float] | :class:`compas.colors.Color`], optional - The color of the vertices, - as either a single color to be applied to all vertices, - or a color dict, mapping specific vertices to specific colors. - text : dict[int, str], optional - The text labels for the vertices as a text dict, - mapping specific vertices to specific text labels. + The vertices are drawn based on the values of + + * `self.show_vertices` + * `self.vertexcolor` + * `self.vertexsize` Returns ------- @@ -135,21 +165,14 @@ def draw_vertices(self, vertices=None, color=None, text=None): """ raise NotImplementedError - def draw_edges(self, edges=None, color=None, text=None): + def draw_edges(self): """Draw the edges of the mesh. - Parameters - ---------- - edges : list[tuple[int, int]], optional - The edges to include in the drawing. - Default is all edges. - color : tuple[float, float, float] | :class:`compas.colors.Color` | dict[tuple[int, int], tuple[float, float, float] | :class:`compas.colors.Color`], optional - The color of the edges, - as either a single color to be applied to all edges, - or a color dict, mapping specific edges to specific colors. - text : dict[tuple[int, int], str], optional - The text labels for the edges as a text dict, - mapping specific edges to specific text labels. + The edges are drawn based on the values of + + * `self.show_edges` + * `self.edgecolor` + * `self.edgewidth` Returns ------- @@ -159,21 +182,13 @@ def draw_edges(self, edges=None, color=None, text=None): """ raise NotImplementedError - def draw_faces(self, faces=None, color=None, text=None): + def draw_faces(self): """Draw the faces of the mesh. - Parameters - ---------- - faces : list[int], optional - The faces to include in the drawing. - Default is all faces. - color : tuple[float, float, float] | :class:`compas.colors.Color` | dict[int, tuple[float, float, float] | :class:`compas.colors.Color`], optional - The color of the faces, - as either a single color to be applied to all faces, - or a color dict, mapping specific faces to specific colors. - text : dict[int, str], optional - The text labels for the faces as a text dict, - mapping specific faces to specific text labels. + The faces are drawn based on the values of + + * `self.show_faces` + * `self.facecolor` Returns ------- @@ -183,21 +198,13 @@ def draw_faces(self, faces=None, color=None, text=None): """ raise NotImplementedError - def draw_cells(self, cells=None, color=None, text=None): + def draw_cells(self): """Draw the cells of the mesh. - Parameters - ---------- - cells : list[int], optional - The cells to include in the drawing. - Default is all cells. - color : tuple[float, float, float] | :class:`compas.colors.Color` | dict[int, tuple[float, float, float] | :class:`compas.colors.Color`], optional - The color of the cells, - as either a single color to be applied to all cells, - or a color dict, mapping specific cells to specific colors. - text : dict[int, str], optional - The text labels for the cells as a text dict, - mapping specific cells to specific text labels. + The cells are drawn based on the values of + + * `self.show_cells` + * `self.cellcolor` Returns ------- @@ -207,10 +214,6 @@ def draw_cells(self, cells=None, color=None, text=None): """ raise NotImplementedError - def draw(self): - """Draw the volmesh.""" - raise NotImplementedError - def clear_vertices(self): """Clear the vertices of the mesh. diff --git a/src/compas_blender/utilities/drawing.py b/src/compas_blender/drawing.py similarity index 100% rename from src/compas_blender/utilities/drawing.py rename to src/compas_blender/drawing.py diff --git a/src/compas_blender/scene/__init__.py b/src/compas_blender/scene/__init__.py index 414cef7dfea..021a7716939 100644 --- a/src/compas_blender/scene/__init__.py +++ b/src/compas_blender/scene/__init__.py @@ -31,12 +31,10 @@ from compas.datastructures import VolMesh from .sceneobject import BlenderSceneObject +from .shapeobject import ShapeObject from .boxobject import BoxObject -from .capsuleobject import CapsuleObject from .circleobject import CircleObject -from .coneobject import ConeObject from .curveobject import CurveObject -from .cylinderobject import CylinderObject from .frameobject import FrameObject from .lineobject import LineObject from .meshobject import MeshObject @@ -47,9 +45,7 @@ from .polygonobject import PolygonObject from .polyhedronobject import PolyhedronObject from .polylineobject import PolylineObject -from .sphereobject import SphereObject from .surfaceobject import SurfaceObject -from .torusobject import TorusObject from .vectorobject import VectorObject from .volmeshobject import VolMeshObject @@ -67,11 +63,11 @@ def after_draw_blender(drawn_objects): @plugin(category="factories", requires=["bpy"]) def register_scene_objects(): register(Box, BoxObject, context="Blender") - register(Capsule, CapsuleObject, context="Blender") + register(Capsule, ShapeObject, context="Blender") register(Circle, CircleObject, context="Blender") - register(Cone, ConeObject, context="Blender") + register(Cone, ShapeObject, context="Blender") register(Curve, CurveObject, context="Blender") - register(Cylinder, CylinderObject, context="Blender") + register(Cylinder, ShapeObject, context="Blender") register(Frame, FrameObject, context="Blender") register(Line, LineObject, context="Blender") register(Mesh, MeshObject, context="Blender") @@ -82,9 +78,9 @@ def register_scene_objects(): register(Polygon, PolygonObject, context="Blender") register(Polyhedron, PolyhedronObject, context="Blender") register(Polyline, PolylineObject, context="Blender") - register(Sphere, SphereObject, context="Blender") + register(Sphere, ShapeObject, context="Blender") register(Surface, SurfaceObject, context="Blender") - register(Torus, TorusObject, context="Blender") + register(Torus, ShapeObject, context="Blender") register(Vector, VectorObject, context="Blender") register(VolMesh, VolMeshObject, context="Blender") print("Blender Objects registered.") @@ -92,10 +88,9 @@ def register_scene_objects(): __all__ = [ "BlenderSceneObject", + "shapeObject", "BoxObject", - "CapsuleObject", "CircleObject", - "ConeObject", "CurveObject", "CylinderObject", "FrameObject", diff --git a/src/compas_blender/scene/boxobject.py b/src/compas_blender/scene/boxobject.py index 05f0da9b9fd..38952936fce 100644 --- a/src/compas_blender/scene/boxobject.py +++ b/src/compas_blender/scene/boxobject.py @@ -1,10 +1,7 @@ from typing import Any -from typing import Optional -from typing import Union import bpy # type: ignore -from compas.colors import Color from compas.geometry import Box from compas.scene import GeometryObject from compas_blender import conversions @@ -26,39 +23,21 @@ class BoxObject(BlenderSceneObject, GeometryObject): def __init__(self, box: Box, **kwargs: Any): super().__init__(geometry=box, **kwargs) + self.geometry: Box - def draw( - self, - color: Optional[Color] = None, - collection: Optional[Union[str, bpy.types.Collection]] = None, - show_wire: bool = True, - ) -> list[bpy.types.Object]: + def draw(self) -> list[bpy.types.Object]: """Draw the box associated with the scene object. - Parameters - ---------- - color : tuple[int, int, int] | tuple[float, float, float] | :class:`compas.colors.Color`, optional - The RGB color of the box. - collection : str | :blender:`bpy.types.Collection`, optional - The name of the Blender scene collection containing the created object(s). - show_wire : bool, optional - Display the wireframe of the box. - Returns ------- list[:blender:`bpy.types.Object`] The object(s) created in Blender to represent the box. """ - 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 = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) + mesh = conversions.vertices_and_faces_to_blender_mesh(self.geometry.vertices, self.geometry.faces, name=self.geometry.name) - obj = self.create_object(mesh, name=name) - self.update_object(obj, color=color, collection=collection, show_wire=show_wire) + obj = self.create_object(mesh, name=self.geometry.name) + self.update_object(obj, color=self.color, collection=self.collection, show_wire=self.show_wire) self._guids = [obj] return self.guids diff --git a/src/compas_blender/scene/capsuleobject.py b/src/compas_blender/scene/capsuleobject.py deleted file mode 100644 index da8f157d93e..00000000000 --- a/src/compas_blender/scene/capsuleobject.py +++ /dev/null @@ -1,73 +0,0 @@ -from typing import Any -from typing import Optional - -import bpy # type: ignore - -from compas.colors import Color -from compas.geometry import Capsule -from compas.scene import GeometryObject -from compas_blender import conversions - -from .sceneobject import BlenderSceneObject - - -class CapsuleObject(BlenderSceneObject, GeometryObject): - """Scene object for drawing capsule shapes in Blender. - - Parameters - ---------- - capsule : :class:`compas.geometry.Capsule` - A COMPAS capsule. - **kwargs : dict, optional - Additional keyword arguments. - - """ - - 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, - show_wire: bool = False, - shade_smooth: bool = True, - ) -> list[bpy.types.Object]: - """Draw the capsule associated with the scene object. - - Parameters - ---------- - color : tuple[int, int, int] | tuple[float, float, float] | :class:`compas.colors.Color`, optional - The RGB color of the capsule. - 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. - v : int, optional - Number of faces in the "v" direction. - show_wire : bool, optional - Display the wireframe of the capsule. - shade_smooth : bool, optional - Display smooth shading on the capsule. - - Returns - ------- - list[:blender:`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, v=v) - mesh = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) - if shade_smooth: - mesh.shade_smooth() - - obj = self.create_object(mesh, name=name) - self.update_object(obj, color=color, collection=collection, show_wire=show_wire) - - self._guids = [obj] - return self.guids diff --git a/src/compas_blender/scene/coneobject.py b/src/compas_blender/scene/coneobject.py deleted file mode 100644 index b7b6ea7aeca..00000000000 --- a/src/compas_blender/scene/coneobject.py +++ /dev/null @@ -1,70 +0,0 @@ -from typing import Any -from typing import Optional - -import bpy # type: ignore - -from compas.colors import Color -from compas.geometry import Cone -from compas.scene import GeometryObject -from compas_blender import conversions - -from .sceneobject import BlenderSceneObject - - -class ConeObject(BlenderSceneObject, GeometryObject): - """Scene object for drawing cone shapes in Blender. - - Parameters - ---------- - cone : :class:`compas.geometry.Cone` - A COMPAS cone. - **kwargs : dict, optional - Additional keyword arguments. - - """ - - def __init__(self, cone: Cone, **kwargs: Any): - super().__init__(geometry=cone, **kwargs) - - def draw( - self, - color: Optional[Color] = None, - collection: Optional[str] = None, - u: int = 16, - show_wire: bool = False, - shade_smooth: bool = False, - ) -> list[bpy.types.Object]: - """Draw the cone associated with the scene object. - - Parameters - ---------- - color : tuple[int, int, int] | tuple[float, float, float] | :class:`compas.colors.Color`, optional - The RGB color of the cone. - 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. - show_wire : bool, optional - Display the wireframe of the cone. - shade_smooth : bool, optional - Display smooth shading on the cone. - - Returns - ------- - list[:blender:`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 = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) - if shade_smooth: - mesh.shade_smooth() - - obj = self.create_object(mesh, name=name) - self.update_object(obj, color=color, collection=collection, show_wire=show_wire) - - self._guids = [obj] - return self.guids diff --git a/src/compas_blender/scene/cylinderobject.py b/src/compas_blender/scene/cylinderobject.py deleted file mode 100644 index ae7e198d3a9..00000000000 --- a/src/compas_blender/scene/cylinderobject.py +++ /dev/null @@ -1,70 +0,0 @@ -from typing import Any -from typing import Optional - -import bpy # type: ignore - -from compas.colors import Color -from compas.geometry import Cylinder -from compas.scene import GeometryObject -from compas_blender import conversions - -from .sceneobject import BlenderSceneObject - - -class CylinderObject(BlenderSceneObject, GeometryObject): - """Scene object for drawing cylinder shapes in Blender. - - Parameters - ---------- - cylinder : :class:`compas.geometry.Cylinder` - A COMPAS cylinder. - **kwargs : dict, optional - Additional keyword arguments. - - """ - - def __init__(self, cylinder: Cylinder, **kwargs: Any): - super().__init__(geometry=cylinder, **kwargs) - - def draw( - self, - color: Optional[Color] = None, - collection: Optional[str] = None, - u: int = 16, - show_wire: bool = False, - shade_smooth: bool = False, - ) -> list[bpy.types.Object]: - """Draw the cylinder associated with the scene object. - - 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 name of the Blender scene collection containing the created object(s). - u : int, optional - Number of faces in the "u" direction. - show_wire : bool, optional - Display the wireframe of the cylinder. - shade_smooth : bool, optional - Display smooth shading on the cylinder. - - Returns - ------- - list[:blender:`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 = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) - if shade_smooth: - mesh.shade_smooth() - - obj = self.create_object(mesh, name=name) - self.update_object(obj, color=color, collection=collection, show_wire=show_wire) - - self._guids = [obj] - return self.guids diff --git a/src/compas_blender/scene/graphobject.py b/src/compas_blender/scene/graphobject.py index f33a4145ab5..d5593e03332 100644 --- a/src/compas_blender/scene/graphobject.py +++ b/src/compas_blender/scene/graphobject.py @@ -1,23 +1,19 @@ -from typing import Any -from typing import Dict from typing import List -from typing import Optional -from typing import Tuple -from typing import Union import bpy # type: ignore import compas_blender -from compas.colors import Color +import compas_blender.objects from compas.datastructures import Graph from compas.geometry import Line -from compas.scene import GraphObject as BaseSceneObject +from compas.geometry import Sphere +from compas.scene import GraphObject as BaseGraphObject from compas_blender import conversions from .sceneobject import BlenderSceneObject -class GraphObject(BlenderSceneObject, BaseSceneObject): +class GraphObject(BlenderSceneObject, BaseGraphObject): """Scene object for drawing graph data structures in Blender. Parameters @@ -27,10 +23,12 @@ class GraphObject(BlenderSceneObject, BaseSceneObject): """ - def __init__(self, graph: Graph, **kwargs: Any): + def __init__(self, graph: Graph, node_u=16, node_v=16, **kwargs: dict): super().__init__(graph=graph, **kwargs) self.nodeobjects = [] self.edgeobjects = [] + self.node_u = node_u + self.node_v = node_v # ========================================================================== # clear @@ -44,7 +42,7 @@ def clear_nodes(self): None """ - compas_blender.delete_objects(self.nodeobjects) + compas_blender.objects.delete_objects(self.nodeobjects) def clear_edges(self): """Clear all objects contained in the edge collection. @@ -54,84 +52,27 @@ def clear_edges(self): None """ - compas_blender.delete_objects(self.edgeobjects) - - # def clear_nodelabels(self): - # """Clear all objects contained in the nodelabel collection. - - # Returns - # ------- - # None - - # """ - # compas_blender.delete_objects(self.nodelabelcollection.objects) - - # def clear_edgelabels(self): - # """Clear all objects contained in the edgelabel collection. - - # Returns - # ------- - # None - - # """ - # compas_blender.delete_objects(self.edgelabelcollection.objects) + compas_blender.objects.delete_objects(self.edgeobjects) # ========================================================================== # draw # ========================================================================== - def draw( - self, - nodes: Optional[List[int]] = None, - edges: Optional[Tuple[int, int]] = None, - nodecolor: Optional[Union[Color, Dict[int, Color]]] = None, - edgecolor: Optional[Union[Color, Dict[Tuple[int, int], Color]]] = None, - ) -> list[bpy.types.Object]: + def draw(self) -> list[bpy.types.Object]: """Draw the graph. - Parameters - ---------- - nodes : list[hashable], optional - A list of node identifiers. - Default is None, in which case all nodes are drawn. - edges : list[tuple[hashable, hashable]], optional - A list of edge keys (as uv pairs) identifying which edges to draw. - The default is None, in which case all edges are drawn. - nodecolor : :class:`compas.colors.Color` | dict[hashable, :class:`compas.colors.Color`], optional - The color specification for the nodes. - edgecolor : :class:`compas.colors.Color` | dict[tuple[hashable, hashable], :class:`compas.colors.Color`], optional - The color specification for the edges. - Returns ------- list[:blender:`bpy.types.Object`] The objects created in Blender. """ - self._guids = self.draw_nodes(nodes=nodes, color=nodecolor) + self.draw_edges(edges=edges, color=edgecolor) + self._guids = self.draw_nodes() + self.draw_edges() return self.guids - 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]: + def draw_nodes(self) -> List[bpy.types.Object]: """Draw a selection of nodes. - Parameters - ---------- - nodes : list[hashable], optional - A list of node identifiers. - 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. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - Returns ------- list[:blender:`bpy.types.Object`] @@ -139,40 +80,33 @@ def draw_nodes( """ objects = [] - self.nodecolor = color - - for node in nodes or self.graph.nodes(): # type: ignore - name = f"{self.graph.name}.node.{node}" # type: ignore - color = self.nodecolor[node] # type: ignore - point = self.graph.nodes_attributes("xyz")[node] # type: ignore + nodes = list(self.graph.nodes()) if self.show_nodes is True else self.show_nodes or [] + + for node in nodes: + name = f"{self.graph.name}.node.{node}" + color = self.nodecolor[node] + point = self.node_xyz[node] + + # # there is no such thing as a sphere data block + # # this doesn't work with application of worl transformation matrix + # bpy.ops.mesh.primitive_uv_sphere_add(location=point, radius=self.nodesize, segments=self.node_u, ring_count=self.node_v) + # obj = bpy.context.object + # self.update_object(obj, name=name, color=color, collection=self.collection) + sphere = Sphere(radius=self.nodesize, point=point) + sphere.resolution_u = self.node_u + sphere.resolution_v = self.node_v + mesh = conversions.vertices_and_faces_to_blender_mesh(sphere.vertices, sphere.faces, name=name) + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=self.collection) - # 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) + self.nodeobjects = objects return objects - def draw_edges( - self, - edges: Optional[Tuple[int, int]] = None, - color: Optional[Union[Color, Dict[Tuple[int, int], Color]]] = None, - collection: Optional[str] = None, - ) -> List[bpy.types.Object]: + def draw_edges(self) -> List[bpy.types.Object]: """Draw a selection of edges. - Parameters - ---------- - edges : list[tuple[hashable, hashable]], optional - A list of edge keys (as uv pairs) identifying which edges to draw. - 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. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - Returns ------- list[:blender:`bpy.types.Object`] @@ -180,78 +114,16 @@ def draw_edges( """ objects = [] - self.edgecolor = color + edges = list(self.graph.edges()) if self.show_edges is True else self.show_edges or [] - for u, v in edges or self.graph.edges(): # type: ignore - name = f"{self.graph.name}.edge.{u}-{v}" # type: ignore - color = self.edgecolor[u, v] # type: ignore - curve = conversions.line_to_blender_curve(Line(self.graph.nodes_attributes("xyz")[u], self.graph.nodes_attributes("xyz")[v])) + for u, v in edges: + name = f"{self.graph.name}.edge.{u}-{v}" + color = self.edgecolor[u, v] + 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) + self.update_object(obj, color=color, collection=self.collection) objects.append(obj) + self.edgeobjects = objects 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.graph.nodes_attributes("xyz")[node], - # "name": f"{self.graph.name}.nodelabel.{node}", - # "text": self.node_text[node], - # "color": self.nodecolor[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.graph.nodes_attributes("xyz")[u], self.graph.nodes_attributes("xyz")[v]]), - # "name": f"{self.graph.name}.edgelabel.{u}-{v}", - # "text": self.edge_text[edge], - # "color": self.edgecolor[edge], - # } - # ) - # return compas_blender.draw_texts(labels, collection=self.edgelabelcollection) - - # ============================================================================= - # draw miscellaneous - # ============================================================================= diff --git a/src/compas_blender/scene/meshobject.py b/src/compas_blender/scene/meshobject.py index 22ba36beebd..ed963722d03 100644 --- a/src/compas_blender/scene/meshobject.py +++ b/src/compas_blender/scene/meshobject.py @@ -1,21 +1,13 @@ from typing import Any -from typing import Dict from typing import List -from typing import Optional -from typing import Tuple -from typing import Union import bpy # type: ignore import compas_blender -from compas.colors import Color +import compas_blender.objects from compas.datastructures import Mesh -from compas.geometry import Cylinder from compas.geometry import Line from compas.geometry import Sphere -from compas.geometry import add_vectors -from compas.geometry import centroid_points -from compas.geometry import scale_vector from compas.scene import MeshObject as BaseMeshObject from compas_blender import conversions @@ -32,18 +24,20 @@ class MeshObject(BlenderSceneObject, BaseMeshObject): """ - def __init__(self, mesh: Mesh, **kwargs: Any): + def __init__(self, mesh: Mesh, vertex_u=16, vertex_v=16, **kwargs: Any): super().__init__(mesh=mesh, **kwargs) self.vertexobjects = [] self.edgeobjects = [] self.faceobjects = [] + self.vertex_u = vertex_u + self.vertex_v = vertex_v # ========================================================================== # clear # ========================================================================== def clear(self): - compas_blender.delete_objects(self.objects) + compas_blender.objects.delete_objects(self.objects) def clear_vertices(self): """Clear the vertex objects. @@ -53,7 +47,7 @@ def clear_vertices(self): None """ - compas_blender.delete_objects(self.vertexobjects) + compas_blender.objects.delete_objects(self.vertexobjects) def clear_edges(self): """Clear the edge objects. @@ -63,7 +57,7 @@ def clear_edges(self): None """ - compas_blender.delete_objects(self.edgeobjects) + compas_blender.objects.delete_objects(self.edgeobjects) def clear_faces(self): """Clear the face objects. @@ -73,70 +67,47 @@ def clear_faces(self): None """ - compas_blender.delete_objects(self.faceobjects) + compas_blender.objects.delete_objects(self.faceobjects) # ========================================================================== # draw # ========================================================================== - def draw(self, color: Optional[Color] = None, collection: Optional[str] = None, show_wire: bool = True) -> list[bpy.types.Object]: + def draw(self) -> list[bpy.types.Object]: """Draw the mesh. - Parameters - ---------- - color : :class:`compas.colors.Color`, optional - The color of the mesh. - collection : str, optional - The name of the collection that should contain the mesh. - show_wire : bool, optional - Display the wireframe of the mesh. - Returns ------- list[:blender:`bpy.types.Object`] The objects created in Blender. """ - name = self.mesh.name # type: ignore - color = Color.coerce(color) or self.color - mesh = conversions.mesh_to_blender(self.mesh) # type: ignore + self._guids = [] + + if self.show_faces is True: + name = self.mesh.name # type: ignore + color = self.color + mesh = conversions.mesh_to_blender(self.mesh) # type: ignore + + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=self.collection, show_wire=self.show_wire) + + self._guids.append(obj) + + elif self.show_faces: + self._guids += self.draw_faces() - obj = self.create_object(mesh, name=name) - self.update_object(obj, color=color, collection=collection, show_wire=show_wire) + if self.show_vertices: + self._guids += self.draw_vertices() + + if self.show_edges: + self._guids += self.draw_edges() - self._guids = [obj] return self.guids - 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]: + def draw_vertices(self) -> List[bpy.types.Object]: """Draw a selection of vertices. - Parameters - ---------- - vertices : list[int], optional - A list of vertex keys identifying which vertices to draw. - 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. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - - Other Parameters - ---------------- - radius : float, optional - The radius of the vertex spheres. - u : int, optional - Number of faces in the "u" direction of the vertex spheres. - v : int, optional - Number of faces in the "v" direction of the vertex spheres. - Returns ------- list[:blender:`bpy.types.Object`] @@ -144,40 +115,32 @@ def draw_vertices( """ objects = [] - self.vertexcolor = color + vertices = list(self.mesh.vertices()) if self.show_vertices is True else self.show_vertices or [] - for vertex in vertices or self.mesh.vertices(): # type: ignore - name = f"{self.mesh.name}.vertex.{vertex}" # type: ignore - color = self.vertexcolor[vertex] # type: ignore + for vertex in vertices: + name = f"{self.mesh.name}.vertex.{vertex}" + color = self.vertexcolor[vertex] 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) # 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.update_object(obj, name=name, color=color, collection=collection) # type: ignore + + sphere = Sphere(radius=self.vertexsize, point=point) + sphere.resolution_u = self.vertex_u + sphere.resolution_v = self.vertex_v + mesh = conversions.vertices_and_faces_to_blender_mesh(sphere.vertices, sphere.faces, name=name) + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=self.collection) objects.append(obj) + self.vertexobjects = objects return objects - def draw_edges( - self, - edges: Optional[List[Tuple[int, int]]] = None, - color: Optional[Union[Color, Dict[Tuple[int, int], Color]]] = None, - collection: Optional[str] = None, - ) -> List[bpy.types.Object]: + def draw_edges(self) -> List[bpy.types.Object]: """Draw a selection of edges. - Parameters - ---------- - edges : list[tuple[int, int]], optional - A list of edge keys (as uv pairs) identifying which edges to draw. - 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. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - Returns ------- list[:blender:`bpy.types.Object`] @@ -185,40 +148,23 @@ def draw_edges( """ objects = [] - self.edgecolor = color + edges = list(self.mesh.edges()) if self.show_edges is True else self.show_edges or [] - for u, v in edges or self.mesh.edges(): # type: ignore - name = f"{self.mesh.name}.edge.{u}-{v}" # type: ignore - color = self.edgecolor[u, v] # type: ignore + for u, v in edges: + name = f"{self.mesh.name}.edge.{u}-{v}" + color = self.edgecolor[u, v] 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) # type: ignore + self.update_object(obj, color=color, collection=self.collection) objects.append(obj) + self.edgeobjects = objects return objects - def draw_faces( - self, - faces: Optional[List[int]] = None, - color: Optional[Union[Color, Dict[int, Color]]] = None, - collection: Optional[str] = None, - show_wire: bool = True, - ) -> List[bpy.types.Object]: + def draw_faces(self) -> List[bpy.types.Object]: """Draw a selection of faces. - Parameters - ---------- - faces : list[int], optional - A list of face keys identifying which faces to draw. - 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. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - show_wire : bool, optional - Display the wireframe of the faces. - Returns ------- list[:blender:`bpy.types.Object`] @@ -226,283 +172,113 @@ def draw_faces( """ objects = [] - self.facecolor = color + faces = list(self.mesh.faces()) if self.show_faces is True else self.show_faces or [] - for face in faces or self.mesh.faces(): # type: ignore - name = f"{self.mesh.name}.face.{face}" # type: ignore - color = self.facecolor[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 + for face in faces: + name = f"{self.mesh.name}.face.{face}" + color = self.facecolor[face] + points = [self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)] + mesh = conversions.polygon_to_blender_mesh(points, name=name) obj = self.create_object(mesh, name=name) - self.update_object(obj, color=color, collection=collection, show_wire=show_wire) # type: ignore + self.update_object(obj, color=color, collection=self.collection) objects.append(obj) + self.faceobjects = objects return objects # ========================================================================== # draw normals # ========================================================================== - def draw_vertexnormals( - self, - 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. - - Parameters - ---------- - vertices : list[int], optional - A selection of vertex normals to draw. - Default is to draw all vertex normals. - color : :class:`compas.colors.Color`, optional - The color specification of the normal vectors. - scale : float, optional - Scale factor for the vertex normals. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - - Returns - ------- - list[:blender:`bpy.types.Object`] - - """ - 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) # type: ignore - b = add_vectors(a, scale_vector(n, scale)) - - 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. - - Parameters - ---------- - faces : list[int], optional - A selection of face normals to draw. - Default is to draw all face normals. - color : :class:`compas.colors.Color`, optional - The color specification of the normal vectors. - scale : float, optional - Scale factor for the face normals. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - - Returns - ------- - list[:blender:`bpy.types.Object`] - - """ - 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)) - - 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_vertexnormals( + # self, + # 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. # 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. + # vertices : list[int], optional + # A selection of vertex normals to draw. + # Default is to draw all vertex normals. + # color : :class:`compas.colors.Color`, optional + # The color specification of the normal vectors. + # scale : float, optional + # Scale factor for the vertex normals. + # collection : str, optional + # The name of the Blender scene collection containing the created object(s). # 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.vertexcolor[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. + # color = Color.coerce(color) # type: ignore - # Returns - # ------- - # list[:blender:`bpy.types.Object`] + # for vertex in vertices or self.mesh.vertices(): # type: ignore + # name = f"{self.mesh.name}.vertex.{vertex}.normal" # 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.edgecolor[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. + # a = self.vertex_xyz[vertex] + # n = self.mesh.vertex_normal(vertex) # type: ignore + # b = add_vectors(a, scale_vector(n, scale)) + + # 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. # 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. + # faces : list[int], optional + # A selection of face normals to draw. + # Default is to draw all face normals. + # color : :class:`compas.colors.Color`, optional + # The color specification of the normal vectors. + # scale : float, optional + # Scale factor for the face normals. + # collection : str, optional + # The name of the Blender scene collection containing the created object(s). # 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.facecolor[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 - ---------- - 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 Blender scene collection containing the created object(s). + # objects = [] - Returns - ------- - list[:blender:`bpy.types.Object`] + # color = Color.coerce(color) # type: ignore - """ - objects = [] - - self.vertexcolor = color + # for face in faces or self.mesh.faces(): # type: ignore + # name = f"{self.mesh.name}.face.{face}.normal" # type: ignore - for vertex in radius: - name = "{}.vertex.{}.sphere".format(self.mesh.name, vertex) # type: ignore - color = self.vertexcolor[vertex] # 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)) - sphere = Sphere.from_point_and_radius(self.vertex_xyz[vertex], radius[vertex]) - geometry = conversions.sphere_to_blender_mesh(sphere, name=name) + # curve = conversions.line_to_blender_curve(Line(a, b)) - obj = self.create_object(geometry, name=name) - self.update_object(obj, color=color, collection=collection) # type: ignore + # obj = self.create_object(curve, 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 - ---------- - 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 Blender scene collection containing the created object(s). - - Returns - ------- - list[:blender:`bpy.types.Object`] + # objects.append(obj) - """ - objects = [] - - self.edgecolor = color - - for u, v in radius: - name = "{}.edge.{}-{}.pipe".format(self.mesh.name, u, v) # type: ignore - color = self.edgecolor[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) # type: ignore - - objects.append(obj) - - return objects + # return objects diff --git a/src/compas_blender/scene/sceneobject.py b/src/compas_blender/scene/sceneobject.py index 6edf60dfe64..5808d1ae806 100644 --- a/src/compas_blender/scene/sceneobject.py +++ b/src/compas_blender/scene/sceneobject.py @@ -28,9 +28,11 @@ class BlenderSceneObject(SceneObject): """ - def __init__(self, **kwargs: Any): + def __init__(self, collection: Union[str, bpy.types.Collection] = None, show_wire: bool = True, **kwargs: Any): super().__init__(**kwargs) self.objects = [] + self.collection = collection + self.show_wire = show_wire # 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 diff --git a/src/compas_blender/scene/shapeobject.py b/src/compas_blender/scene/shapeobject.py new file mode 100644 index 00000000000..ac8aec91b82 --- /dev/null +++ b/src/compas_blender/scene/shapeobject.py @@ -0,0 +1,78 @@ +from typing import Any +from typing import Optional + +import bpy # type: ignore + +from compas.geometry import Shape +from compas.scene import GeometryObject +from compas_blender import conversions + +from .sceneobject import BlenderSceneObject + + +class ShapeObject(BlenderSceneObject, GeometryObject): + """Scene object for drawing capsule shapes in Blender. + + Parameters + ---------- + shape : :class:`compas.geometry.shape` + A COMPAS shape. + v : int, optional + The number of vertices in the u-direction of non-OCC geometries. + u : int, optional + The number of vertices in the v-direction of non-OCC geometries. + **kwargs : dict, optional + Additional keyword arguments. + + """ + + def __init__( + self, + geometry: Shape, + u: Optional[int] = 16, + v: Optional[int] = 16, + shade_smooth: bool = True, + **kwargs: Any, + ): + super().__init__(geometry=geometry, **kwargs) + self.geometry: Shape + self.u = u + self.v = v + self.shade_smooth = shade_smooth + + @property + def u(self) -> int: + return self.geometry.resolution_u + + @u.setter + def u(self, u: int) -> None: + self.geometry.resolution_u = u + + @property + def v(self) -> int: + return self.geometry.resolution_v + + @v.setter + def v(self, v: int) -> None: + self.geometry.resolution_v = v + + def draw(self) -> list[bpy.types.Object]: + """Draw the cone associated with the scene object. + + Returns + ------- + list[:blender:`bpy.types.Object`] + The objects created in Blender. + + """ + mesh = conversions.vertices_and_faces_to_blender_mesh(self.geometry.vertices, self.geometry.faces, name=self.geometry.name) + if self.shade_smooth: + mesh.shade_smooth() + else: + mesh.shade_flat() + + obj = self.create_object(mesh, name=self.geometry.name) + self.update_object(obj, color=self.color, collection=self.collection, show_wire=self.show_wire) + + self._guids = [obj] + return self.guids diff --git a/src/compas_blender/scene/sphereobject.py b/src/compas_blender/scene/sphereobject.py deleted file mode 100644 index 23faba0974f..00000000000 --- a/src/compas_blender/scene/sphereobject.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import Any -from typing import Optional - -import bpy # type: ignore - -from compas.colors import Color -from compas.geometry import Sphere -from compas.scene import GeometryObject -from compas_blender import conversions - -from .sceneobject import BlenderSceneObject - - -class SphereObject(BlenderSceneObject, GeometryObject): - """Scene object for drawing sphere shapes in Blender. - - Parameters - ---------- - sphere : :class:`compas.geometry.Sphere` - A COMPAS sphere. - **kwargs : dict, optional - Additional keyword arguments. - - """ - - def __init__(self, sphere: Sphere, **kwargs: Any): - super().__init__(geometry=sphere, **kwargs) - - def draw( - self, - color: Optional[Color] = None, - collection: Optional[str] = None, - u: int = 16, - v: int = 16, - show_wire: bool = False, - shade_smooth: bool = True, - ) -> list[bpy.types.Object]: - """Draw the sphere associated with the scene object. - - Parameters - ---------- - color : tuple[float, float, float] | tuple[int, int, int] | :class:`compas.colors.Color`, optional - The RGB color of the sphere. - 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. - v : int, optional - Number of faces in the "v" direction. - show_wire : bool, optional - Display the wireframe of the sphere. - shade_smooth : bool, optional - Display smooth shading on the sphere. - - Returns - ------- - list[:blender:`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, v=v) - mesh = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) - if shade_smooth: - mesh.shade_smooth() - - obj = self.create_object(mesh, name=name) - self.update_object(obj, color=color, collection=collection, show_wire=show_wire) - - self._guids = [obj] - return self.guids diff --git a/src/compas_blender/scene/torusobject.py b/src/compas_blender/scene/torusobject.py deleted file mode 100644 index afffad83505..00000000000 --- a/src/compas_blender/scene/torusobject.py +++ /dev/null @@ -1,73 +0,0 @@ -from typing import Any -from typing import Optional - -import bpy # type: ignore - -from compas.colors import Color -from compas.geometry import Torus -from compas.scene import GeometryObject -from compas_blender import conversions - -from .sceneobject import BlenderSceneObject - - -class TorusObject(BlenderSceneObject, GeometryObject): - """Scene object for drawing torus shapes in Blender. - - Parameters - ---------- - torus : :class:`compas.geometry.Torus` - A COMPAS torus. - **kwargs : dict, optional - Additional keyword arguments. - - """ - - 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] = 16, - v: Optional[int] = 16, - show_wire: bool = False, - shade_smooth: bool = True, - ) -> list[bpy.types.Object]: - """Draw the torus associated with the scene object. - - Parameters - ---------- - color : tuple[float, float, float] | tuple[int, int, int] | :class:`compas.colors.Color`, optional - The RGB color of the torus. - 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. - v : int, optional - Number of faces in the "v" direction. - show_wire : bool, optional - Display the wireframe of the torus. - shade_smooth : bool, optional - Display smooth shading on the torus. - - Returns - ------- - list[:blender:`bpy.types.Curve`] - 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 = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=self.geometry.name) - if shade_smooth: - mesh.shade_smooth() - - obj = self.create_object(mesh, name=name) - self.update_object(obj, color=color, collection=collection, show_wire=show_wire) - - self._guids = [obj] - return self.guids diff --git a/src/compas_blender/scene/volmeshobject.py b/src/compas_blender/scene/volmeshobject.py index 545a253c132..4012bbcefda 100644 --- a/src/compas_blender/scene/volmeshobject.py +++ b/src/compas_blender/scene/volmeshobject.py @@ -1,18 +1,12 @@ from typing import Any -from typing import Dict from typing import List -from typing import Optional -from typing import Tuple -from typing import Union import bpy # type: ignore import compas_blender -from compas.colors import Color +import compas_blender.objects from compas.geometry import Line -from compas.geometry import add_vectors -from compas.geometry import centroid_points -from compas.geometry import scale_vector +from compas.geometry import Sphere from compas.scene import VolMeshObject as BaseVolMeshObject from compas_blender import conversions @@ -31,19 +25,21 @@ class VolMeshObject(BlenderSceneObject, BaseVolMeshObject): """ - def __init__(self, volmesh, **kwargs: Any): + def __init__(self, volmesh, vertex_u=16, vertex_v=16, **kwargs: Any): super().__init__(volmesh=volmesh, **kwargs) self.vertexobjects = [] self.edgeobjects = [] self.faceobjects = [] self.cellobjects = [] + self.vertex_u = vertex_u + self.vertex_v = vertex_v # ========================================================================== # clear # ========================================================================== def clear(self): - compas_blender.delete_objects(self.objects) + compas_blender.objects.delete_objects(self.objects) def clear_vertices(self): """Clear the objects contained in the vertex collection (``self.vertexcollection``). @@ -53,7 +49,7 @@ def clear_vertices(self): None """ - compas_blender.delete_objects(self.vertexobjects) + compas_blender.objects.delete_objects(self.vertexobjects) def clear_edges(self): """Clear the objects contained in the edge collection (``self.edgecollection``). @@ -63,7 +59,7 @@ def clear_edges(self): None """ - compas_blender.delete_objects(self.edgeobjects) + compas_blender.objects.delete_objects(self.edgeobjects) def clear_faces(self): """Clear the objects contained in the face collection (``self.facecollection``). @@ -73,7 +69,7 @@ def clear_faces(self): None """ - compas_blender.delete_objects(self.faceobjects) + compas_blender.objects.delete_objects(self.faceobjects) def clear_cells(self): """Clear the objects contained in the cell collection (``self.cellcollection``). @@ -83,62 +79,39 @@ def clear_cells(self): None """ - compas_blender.delete_objects(self.cellobjects) + compas_blender.objects.delete_objects(self.cellobjects) # ========================================================================== # draw # ========================================================================== - def draw(self, cells: Optional[List[int]] = None, color: Optional[Color] = None, collection: Optional[str] = None) -> list[bpy.types.Object]: + def draw(self) -> list[bpy.types.Object]: """Draw a selection of cells. - Parameters - ---------- - cells : list[int], optional - A list of cells to draw. - The default is None, in which case all cells are drawn. - color : :class:`compas.colors.Color` | dict[int, :class:`compas.colors.Color`], optional - The color of the cells. - The default color is :attr:`VolMeshObject.default_cellcolor`. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - Returns ------- list[:blender:`bpy.types.Object`] The objects created in Blender. """ - self._guids = self.draw_cells(cells=cells, color=color, collection=collection) + guids = [] + + if self.show_vertices: + guids += self.draw_vertices() + if self.show_edges: + guids += self.draw_edges() + if self.show_faces: + guids += self.draw_faces() + if self.show_cells: + guids += self.draw_cells() + + self._guids = guids + return self.guids - 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]: + def draw_vertices(self) -> List[bpy.types.Object]: """Draw a selection of vertices. - Parameters - ---------- - vertices : list[int], optional - A list of vertex keys identifying which vertices to draw. - 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. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - radius : float, optional - The radius of the spheres representing the vertices. - u : int, optional - Number of faces in the "u" direction of the spheres representing the vertices. - v : int, optional - Number of faces in the "v" direction of the spheres representing the vertices. - Returns ------- list[:blender:`bpy.types.Object`] @@ -146,40 +119,27 @@ def draw_vertices( """ objects = [] - self.vertexcolor = color + vertices = list(self.volmesh.vertices()) if self.show_vertices is True else self.show_vertices or [] - for vertex in vertices or self.volmesh.vertices(): # type: ignore - name = f"{self.volmesh.name}.vertex.{vertex}" # type: ignore - color = self.vertexcolor[vertex] # type: ignore - point = self.volmesh.vertices_attributes("xyz")[vertex] + for vertex in vertices: + name = f"{self.volmesh.name}.vertex.{vertex}" + color = self.vertexcolor[vertex] + 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) # type: ignore + sphere = Sphere(radius=self.vertexsize, point=point) + sphere.resolution_u = self.vertex_u + sphere.resolution_v = self.vertex_v + mesh = conversions.vertices_and_faces_to_blender_mesh(sphere.vertices, sphere.faces, name=name) + obj = self.create_object(mesh, name=name) + self.update_object(obj, color=color, collection=self.collection) objects.append(obj) + self.vertexobjects = objects return objects - def draw_edges( - self, - edges: Optional[List[Tuple[int, int]]] = None, - color: Optional[Union[Color, Dict[int, Color]]] = None, - collection: Optional[str] = None, - ) -> List[bpy.types.Object]: + def draw_edges(self) -> List[bpy.types.Object]: """Draw a selection of edges. - Parameters - ---------- - edges : list[tuple[int, int]], optional - A list of edge keys (as uv pairs) identifying which edges to draw. - 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. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - Returns ------- list[:blender:`bpy.types.Object`] @@ -187,40 +147,23 @@ def draw_edges( """ objects = [] - self.edgecolor = color + edges = list(self.volmesh.edges()) if self.show_edges is True else self.show_edges or [] - for u, v in edges or self.volmesh.edges(): # type: ignore - name = f"{self.volmesh.name}.edge.{u}-{v}" # type: ignore - color = self.edgecolor[u, v] # type: ignore - curve = conversions.line_to_blender_curve(Line(self.volmesh.vertices_attributes("xyz")[u], self.volmesh.vertices_attributes("xyz")[v])) + for u, v in edges: + name = f"{self.volmesh.name}.edge.{u}-{v}" + color = self.edgecolor[u, v] + 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) # type: ignore + self.update_object(obj, color=color, collection=self.collection) objects.append(obj) + self.edgeobjects = objects return objects - def draw_faces( - self, - faces: Optional[List[int]] = None, - color: Optional[Union[Color, Dict[int, Color]]] = None, - collection: Optional[str] = None, - show_wire: bool = True, - ) -> List[bpy.types.Object]: + def draw_faces(self) -> List[bpy.types.Object]: """Draw a selection of faces. - Parameters - ---------- - faces : list[int], optional - A list of face keys identifying which faces to draw. - 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. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - show_wire : bool, optional - Display the wireframe of the faces. - Returns ------- list[:blender:`bpy.types.Object`] @@ -228,41 +171,24 @@ def draw_faces( """ objects = [] - self.facecolor = color + faces = list(self.volmesh.faces()) if self.show_faces is True else self.show_faces or [] - for face in faces or self.volmesh.faces(): # type: ignore - name = f"{self.volmesh.name}.face.{face}" # type: ignore - color = self.facecolor[face] # type: ignore - points = [self.volmesh.vertices_attributes("xyz")[vertex] for vertex in self.volmesh.face_vertices(face)] # type: ignore - mesh = conversions.polygon_to_blender_mesh(points, name=name) # type: ignore + for face in faces: + name = f"{self.volmesh.name}.face.{face}" + color = self.facecolor[face] + points = [self.vertex_xyz[vertex] for vertex in self.volmesh.face_vertices(face)] + mesh = conversions.polygon_to_blender_mesh(points, name=name) obj = self.create_object(mesh, name=name) - self.update_object(obj, color=color, collection=collection, show_wire=show_wire) # type: ignore + self.update_object(obj, color=color, collection=self.collection) objects.append(obj) + self.faceobjects = objects return objects - def draw_cells( - self, - cells: Optional[List[int]] = None, - color: Optional[Union[Color, Dict[int, Color]]] = None, - collection: Optional[str] = None, - show_wire: bool = True, - ) -> List[bpy.types.Object]: + def draw_cells(self) -> List[bpy.types.Object]: """Draw a selection of cells. - Parameters - ---------- - cells : list[int], optional - A list of cells to draw. - The default is None, in which case all cells are drawn. - color : :class:`compas.colors.Color` | dict[int, :class:`compas.colors.Color`], optional - The color of the cells. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - show_wire : bool, optional - Display the wireframe of the cells. - Returns ------- list[:blender:`bpy.types.Object`] @@ -270,23 +196,23 @@ def draw_cells( """ objects = [] - self.cellcolor = color + cells = list(self.volmesh.cells()) if self.show_cells is True else self.show_cells or [] - for cell in cells or self.volmesh.cells(): # type: ignore - name = f"{self.volmesh.name}.cell.{cell}" # type: ignore - color = self.cellcolor[cell] # type: ignore + for cell in cells: + name = "{}.cell.{}".format(self.volmesh.name, cell) + color = self.cellcolor[cell] - vertices = self.volmesh.cell_vertices(cell) # type: ignore - faces = self.volmesh.cell_faces(cell) # type: ignore + vertices = self.volmesh.cell_vertices(cell) + faces = self.volmesh.cell_faces(cell) vertex_index = dict((vertex, index) for index, vertex in enumerate(vertices)) - vertices = [self.volmesh.vertices_attributes("xyz")[vertex] for vertex in vertices] - faces = [[vertex_index[vertex] for vertex in self.volmesh.halfface_vertices(face)] for face in faces] # type: ignore + vertices = [self.vertex_xyz[vertex] for vertex in vertices] + faces = [[vertex_index[vertex] for vertex in self.volmesh.halfface_vertices(face)] for face in faces] - mesh = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=name) # type: ignore + mesh = conversions.vertices_and_faces_to_blender_mesh(vertices, faces, name=name) obj = self.create_object(mesh, name=name) - self.update_object(obj, color=color, collection=collection, show_wire=show_wire) # type: ignore + self.update_object(obj, color=color, collection=self.collection, show_wire=self.show_wire) objects.append(obj) @@ -296,180 +222,94 @@ def draw_cells( # draw normals # ========================================================================== - def draw_vertexnormals( - self, - 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. - - Parameters - ---------- - vertices : list[int], optional - A selection of vertex normals to draw. - Default is to draw all vertex normals. - color : :class:`compas.colors.Color`, optional - The color specification of the normal vectors. - scale : float, optional - Scale factor for the vertex normals. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - - Returns - ------- - list[:blender:`bpy.types.Object`] - - """ - objects = [] - - color = Color.coerce(color) # type: ignore - - for vertex in vertices or self.volmesh.vertices(): # type: ignore - name = f"{self.volmesh.name}.vertex.{vertex}.normal" # type: ignore - - a = self.volmesh.vertices_attributes("xyz")[vertex] - n = self.volmesh.vertex_normal(vertex) # type: ignore - b = add_vectors(a, scale_vector(n, scale)) - - 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) + # def draw_vertexnormals( + # self, + # 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. - 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. - - Parameters - ---------- - faces : list[int], optional - A selection of face normals to draw. - Default is to draw all face normals. - color : :class:`compas.colors.Color`, optional - The color specification of the normal vectors. - scale : float, optional - Scale factor for the face normals. - collection : str, optional - The name of the Blender scene collection containing the created object(s). - - Returns - ------- - list[:blender:`bpy.types.Object`] + # Parameters + # ---------- + # vertices : list[int], optional + # A selection of vertex normals to draw. + # Default is to draw all vertex normals. + # color : :class:`compas.colors.Color`, optional + # The color specification of the normal vectors. + # scale : float, optional + # Scale factor for the vertex normals. + # collection : str, optional + # The name of the Blender scene collection containing the created object(s). - """ - objects = [] + # Returns + # ------- + # list[:blender:`bpy.types.Object`] - color = Color.coerce(color) # type: ignore + # """ + # objects = [] - for face in faces or self.volmesh.faces(): # type: ignore - name = f"{self.volmesh.name}.face.{face}.normal" # type: ignore + # color = Color.coerce(color) # type: ignore - a = centroid_points([self.volmesh.vertices_attributes("xyz")[vertex] for vertex in self.volmesh.face_vertices(face)]) # type: ignore - n = self.volmesh.face_normal(face) # type: ignore - b = add_vectors(a, scale_vector(n, scale)) + # for vertex in vertices or self.volmesh.vertices(): # type: ignore + # name = f"{self.volmesh.name}.vertex.{vertex}.normal" # type: ignore - curve = conversions.line_to_blender_curve(Line(a, b)) + # a = self.volmesh.vertices_attributes("xyz")[vertex] + # n = self.volmesh.vertex_normal(vertex) # type: ignore + # b = add_vectors(a, scale_vector(n, scale)) - obj = self.create_object(curve, name=name) - self.update_object(obj, color=color, collection=collection) + # curve = conversions.line_to_blender_curve(Line(a, b)) - objects.append(obj) + # obj = self.create_object(curve, name=name) + # self.update_object(obj, color=color, collection=collection) - return objects + # objects.append(obj) - # ========================================================================== - # draw labels - # ========================================================================== + # return objects - # def draw_vertexlabels(self, text: Optional[Dict[int, str]] = None) -> List[bpy.types.Object]: - # """Draw labels for a selection vertices. + # 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. # 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. + # faces : list[int], optional + # A selection of face normals to draw. + # Default is to draw all face normals. + # color : :class:`compas.colors.Color`, optional + # The color specification of the normal vectors. + # scale : float, optional + # Scale factor for the face normals. + # collection : str, optional + # The name of the Blender scene collection containing the created object(s). # Returns # ------- # list[:blender:`bpy.types.Object`] # """ - # self.vertex_text = text - # labels = [] - # for vertex in self.vertex_text: - # labels.append( - # { - # "pos": self.volmesh.vertices_attributes("xyz")[vertex], - # "name": f"{self.volmesh.name}.vertexlabel.{vertex}", - # "text": self.vertex_text[vertex], - # "color": self.vertexcolor[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. + # color = Color.coerce(color) # type: ignore - # Returns - # ------- - # list[:blender:`bpy.types.Object`] + # for face in faces or self.volmesh.faces(): # type: ignore + # name = f"{self.volmesh.name}.face.{face}.normal" # type: ignore - # """ - # self.edge_text = text - # labels = [] - # for edge in self.edge_text: - # u, v = edge - # labels.append( - # { - # "pos": centroid_points([self.volmesh.vertices_attributes("xyz")[u], self.volmesh.vertices_attributes("xyz")[v]]), - # "name": f"{self.volmesh.name}.edgelabel.{u}-{v}", - # "text": self.edge_text[edge], - # "color": self.edgecolor[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. + # a = centroid_points([self.volmesh.vertices_attributes("xyz")[vertex] for vertex in self.volmesh.face_vertices(face)]) # type: ignore + # n = self.volmesh.face_normal(face) # type: ignore + # b = add_vectors(a, scale_vector(n, scale)) - # 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. + # curve = conversions.line_to_blender_curve(Line(a, b)) - # Returns - # ------- - # list[:blender:`bpy.types.Object`] + # obj = self.create_object(curve, name=name) + # self.update_object(obj, color=color, collection=collection) - # """ - # self.face_text = text - # labels = [] - # for face in self.face_text: - # labels.append( - # { - # "pos": centroid_points([self.volmesh.vertices_attributes("xyz")[vertex] for vertex in self.volmesh.face_vertices(face)]), - # "name": "{}.facelabel.{}".format(self.volmesh.name, face), - # "text": self.face_text[face], - # "color": self.facecolor[face], - # } - # ) - # return compas_blender.draw_texts(labels, collection=self.collection) + # objects.append(obj) + + # return objects diff --git a/src/compas_blender/utilities/__init__.py b/src/compas_blender/utilities/__init__.py index c018d710e46..10dd64eb287 100644 --- a/src/compas_blender/utilities/__init__.py +++ b/src/compas_blender/utilities/__init__.py @@ -1,4 +1,6 @@ -from .drawing import ( +from warnings import warn + +from ..drawing import ( draw_circles, draw_cylinders, draw_cubes, @@ -36,3 +38,5 @@ "draw_surfaces", "RGBColor", ] + +warn("compas_blender.utilities will be removed in version 2.3. Please use compas_blender.drawing instead.", DeprecationWarning, stacklevel=2) diff --git a/src/compas_ghpython/utilities/drawing.py b/src/compas_ghpython/drawing.py similarity index 99% rename from src/compas_ghpython/utilities/drawing.py rename to src/compas_ghpython/drawing.py index 78d91bfdaa5..001fa4e3346 100644 --- a/src/compas_ghpython/utilities/drawing.py +++ b/src/compas_ghpython/drawing.py @@ -21,7 +21,7 @@ from compas.geometry import centroid_points from compas.itertools import pairwise -from compas_rhino.utilities.drawing import _face_to_max_quad +from compas_rhino.drawing import _face_to_max_quad try: from Rhino.Geometry import MeshNgon diff --git a/src/compas_ghpython/scene/__init__.py b/src/compas_ghpython/scene/__init__.py index 1dceb254b72..822d5220a37 100644 --- a/src/compas_ghpython/scene/__init__.py +++ b/src/compas_ghpython/scene/__init__.py @@ -57,9 +57,9 @@ from .brepobject import BrepObject -@plugin(category="drawing-utils", pluggable_name="clear", requires=["Grasshopper"]) -def clear_GH(guids=None): - pass +# @plugin(category="drawing-utils", pluggable_name="clear", requires=["Grasshopper"]) +# def clear_GH(guids=None): +# pass @plugin(category="factories", requires=["Rhino"]) diff --git a/src/compas_ghpython/scene/graphobject.py b/src/compas_ghpython/scene/graphobject.py index ac9396b651e..ee30a20db35 100644 --- a/src/compas_ghpython/scene/graphobject.py +++ b/src/compas_ghpython/scene/graphobject.py @@ -34,15 +34,9 @@ def draw(self): self._guids = self.draw_edges() + self.draw_nodes() return self.guids - def draw_nodes(self, nodes=None): + def draw_nodes(self): """Draw a selection of nodes. - Parameters - ---------- - nodes: list[hashable], optional - The selection of nodes that should be drawn. - Default is None, in which case all nodes are drawn. - Returns ------- list[:rhino:`Rhino.Geometry.Point3d`] @@ -50,7 +44,9 @@ def draw_nodes(self, nodes=None): """ points = [] - for node in nodes or self.graph.nodes(): # type: ignore + nodes = list(self.graph.nodes()) if self.show_nodes is True else self.show_nodes or [] + + for node in nodes: points.append(conversions.point_to_rhino(self.node_xyz[node])) return points @@ -58,12 +54,6 @@ def draw_nodes(self, nodes=None): def draw_edges(self, edges=None): """Draw a selection of edges. - Parameters - ---------- - edges : list[tuple[hashable, hashable]], optional - A list of edges to draw. - The default is None, in which case all edges are drawn. - Returns ------- list[:rhino:`Rhino.Geometry.Line`] @@ -71,7 +61,9 @@ def draw_edges(self, edges=None): """ lines = [] - for edge in edges or self.graph.edges(): # type: ignore + edges = list(self.graph.edges()) if self.show_edges is True else self.show_edges or [] + + for edge in edges: lines.append(conversions.line_to_rhino((self.node_xyz[edge[0]], self.node_xyz[edge[1]]))) return lines diff --git a/src/compas_ghpython/scene/meshobject.py b/src/compas_ghpython/scene/meshobject.py index d4bcd6a0182..12c7e6cc4f4 100644 --- a/src/compas_ghpython/scene/meshobject.py +++ b/src/compas_ghpython/scene/meshobject.py @@ -2,7 +2,6 @@ from __future__ import division from __future__ import print_function -from compas.colors import Color from compas.scene import MeshObject as BaseMeshObject from compas_rhino import conversions from compas_rhino.scene.helpers import ngon @@ -22,10 +21,11 @@ class MeshObject(GHSceneObject, BaseMeshObject): """ - def __init__(self, mesh, **kwargs): + def __init__(self, mesh, disjoint=False, **kwargs): super(MeshObject, self).__init__(mesh=mesh, **kwargs) + self.disjoint = disjoint - def draw(self, color=None, vertexcolors=None, facecolors=None, disjoint=False): + def draw(self): """Draw the mesh. Parameters @@ -42,36 +42,54 @@ def draw(self, color=None, vertexcolors=None, facecolors=None, disjoint=False): # because it can set an overall color on the mesh object attributes # this is not possible in GH (since there is no such object) # either we set an overall color or we set component colors - if not vertexcolors and not facecolors: - color = Color.coerce(color) or self.color + self._guids = [] - vertex_index = self.mesh.vertex_index() # type: ignore - vertex_xyz = self.vertex_xyz + if self.show_faces is True: + vertexcolors = [] + if len(self.vertexcolor): + vertexcolors = [self.vertexcolor[vertex] for vertex in self.mesh.vertices()] - vertices = [vertex_xyz[vertex] for vertex in self.mesh.vertices()] # type: ignore - faces = [[vertex_index[vertex] for vertex in self.mesh.face_vertices(face)] for face in self.mesh.faces()] # type: ignore + facecolors = [] + if len(self.facecolor): + facecolors = [self.facecolor[face] for face in self.mesh.faces()] - geometry = conversions.vertices_and_faces_to_rhino( - vertices, - faces, - color=color, - vertexcolors=vertexcolors, - facecolors=facecolors, - disjoint=disjoint, - ) + color = None + if not vertexcolors and not facecolors: + color = self.color + + vertex_index = self.mesh.vertex_index() + vertex_xyz = self.vertex_xyz + + vertices = [vertex_xyz[vertex] for vertex in self.mesh.vertices()] + faces = [[vertex_index[vertex] for vertex in self.mesh.face_vertices(face)] for face in self.mesh.faces()] + + geometry = conversions.vertices_and_faces_to_rhino( + vertices, + faces, + color=color, + vertexcolors=vertexcolors, + facecolors=facecolors, + disjoint=self.disjoint, + ) + + # geometry.Transform(conversions.transformation_to_rhino(self.worldtransformation)) + + self._guids.append(geometry) + + elif self.show_faces: + self._guids += self.draw_faces() + + if self.show_vertices: + self._guids += self.draw_vertices() + + if self.show_edges: + self._guids += self.draw_edges() - self._guids = [geometry] return self.guids - def draw_vertices(self, vertices=None): + def draw_vertices(self): """Draw a selection of vertices. - Parameters - ---------- - vertices : list[int], optional - A selection of vertices to draw. - Default is None, in which case all vertices are drawn. - Returns ------- list[:rhino:`Rhino.Geometry.Point3d`] @@ -79,20 +97,17 @@ def draw_vertices(self, vertices=None): """ points = [] - for vertex in vertices or self.mesh.vertices(): # type: ignore - points.append(conversions.point_to_rhino(self.vertex_xyz[vertex])) + vertices = list(self.mesh.vertices()) if self.show_vertices is True else self.show_vertices or [] + + if vertices: + for vertex in vertices: + points.append(conversions.point_to_rhino(self.vertex_xyz[vertex])) return points - def draw_edges(self, edges=None): + def draw_edges(self): """Draw a selection of edges. - Parameters - ---------- - edges : list[tuple[int, int]], optional - A selection of edges to draw. - The default is None, in which case all edges are drawn. - Returns ------- list[:rhino:`Rhino.Geometry.Line`] @@ -100,38 +115,32 @@ def draw_edges(self, edges=None): """ lines = [] - for edge in edges or self.mesh.edges(): # type: ignore - lines.append(conversions.line_to_rhino((self.vertex_xyz[edge[0]], self.vertex_xyz[edge[1]]))) + edges = list(self.mesh.edges()) if self.show_edges is True else self.show_edges or [] + + if edges: + for edge in edges: + lines.append(conversions.line_to_rhino((self.vertex_xyz[edge[0]], self.vertex_xyz[edge[1]]))) return lines - def draw_faces(self, faces=None, color=None): + def draw_faces(self): """Draw a selection of faces. - Parameters - ---------- - faces : list[int], optional - A selection of faces to draw. - 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. - Returns ------- list[:rhino:`Rhino.Geometry.Mesh`] """ - faces = faces or self.mesh.faces() # type: ignore - - self.facecolor = color - meshes = [] - for face in faces: - color = self.facecolor[face] # type: ignore - vertices = [self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)] # type: ignore - facet = ngon(len(vertices)) - if facet: - meshes.append(conversions.vertices_and_faces_to_rhino(vertices, [facet])) + faces = list(self.mesh.faces()) if self.show_faces is True else self.show_faces or [] + + if faces: + for face in faces: + color = self.facecolor[face] + vertices = [self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)] + facet = ngon(len(vertices)) + if facet: + meshes.append(conversions.vertices_and_faces_to_rhino(vertices, [facet], color=color)) return meshes diff --git a/src/compas_ghpython/scene/volmeshobject.py b/src/compas_ghpython/scene/volmeshobject.py index adebfa1e8d1..9ad97145ae3 100644 --- a/src/compas_ghpython/scene/volmeshobject.py +++ b/src/compas_ghpython/scene/volmeshobject.py @@ -24,36 +24,32 @@ class VolMeshObject(GHSceneObject, BaseVolMeshObject): def __init__(self, volmesh, **kwargs): super(VolMeshObject, self).__init__(volmesh=volmesh, **kwargs) - def draw(self, cells=None, color=None): + def draw(self): """Draw a selection of cells. - Parameters - ---------- - cells : list[int], optional - A list of cells to draw. - The default is None, in which case all cells are drawn. - color : :class:`compas.colors.Color` | dict[int, :class:`compas.colors.Color`], optional - The color of the cells. - The default color is :attr:`VolMeshObject.default_cellcolor`. - Returns ------- list[:rhino:`Rhino.Geometry.Mesh`] List of created Rhino meshes. """ - self._guids = self.draw_cells(cells=cells, color=color) + guids = [] + + if self.show_vertices: + guids += self.draw_vertices() + if self.show_edges: + guids += self.draw_edges() + if self.show_faces: + guids += self.draw_faces() + if self.show_cells: + guids += self.draw_cells() + + self._guids = guids return self.guids - def draw_vertices(self, vertices=None): + def draw_vertices(self): """Draw a selection of vertices. - Parameters - ---------- - vertices : list - A list of vertices to draw. - Default is None, in which case all vertices are drawn. - Returns ------- list[:rhino:`Rhino.Geometry.Point3d`] @@ -61,20 +57,16 @@ def draw_vertices(self, vertices=None): """ points = [] - for vertex in vertices or self.volmesh.vertices(): # type: ignore + vertices = list(self.volmesh.vertices()) if self.show_vertices is True else self.show_vertices or [] + + for vertex in vertices: points.append(conversions.point_to_rhino(self.vertex_xyz[vertex])) return points - def draw_edges(self, edges=None): + def draw_edges(self): """Draw a selection of edges. - Parameters - ---------- - edges : list[tuple[int, int]], optional - A list of edges to draw. - The default is None, in which case all edges are drawn. - Returns ------- list[:rhino:`Rhino.Geometry.Line`] @@ -82,73 +74,56 @@ def draw_edges(self, edges=None): """ lines = [] - for edge in edges or self.volmesh.edges(): # type: ignore + edges = list(self.volmesh.edges()) if self.show_edges is True else self.show_edges or [] + + for edge in edges: lines.append(conversions.line_to_rhino((self.vertex_xyz[edge[0]], self.vertex_xyz[edge[1]]))) return lines - def draw_faces(self, faces=None, color=None): + def draw_faces(self): """Draw a selection of faces. - Parameters - ---------- - faces : list[list[int]], optional - A list of faces to draw. - 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. - The default color is :attr:`VolMeshObject.default_facecolor`. - Returns ------- list[:rhino:`Rhino.Geometry.Mesh`] """ - faces = faces or self.volmesh.faces() # type: ignore - - self.facecolor = color - meshes = [] + faces = list(self.volmesh.faces()) if self.show_faces is True else self.show_faces or [] + for face in faces: - color = self.facecolor[face] # type: ignore - vertices = [self.vertex_xyz[vertex] for vertex in self.volmesh.face_vertices(face)] # type: ignore + color = self.facecolor[face] + vertices = [self.vertex_xyz[vertex] for vertex in self.volmesh.face_vertices(face)] facet = ngon(len(vertices)) if facet: - meshes.append(conversions.vertices_and_faces_to_rhino(vertices, [facet])) + meshes.append(conversions.vertices_and_faces_to_rhino(vertices, [facet], color=color)) return meshes - def draw_cells(self, cells=None, color=None): + def draw_cells(self): """Draw a selection of cells. - Parameters - ---------- - cells : list[int], optional - A list of cells to draw. - The default is None, in which case all cells are drawn. - color : :class:`compas.colors.Color` | dict[int, :class:`compas.colors.Color`], optional - The color of the cells. - Returns ------- list[:rhino:`Rhino.Geometry.Mesh`] """ - self.cellcolor = color - meshes = [] - for cell in cells or self.volmesh.cells(): # type: ignore - color = self.cellcolor[cell] # type: ignore + cells = list(self.volmesh.cells()) if self.show_cells is True else self.show_cells or [] + + for cell in cells: + color = self.cellcolor[cell] - vertices = self.volmesh.cell_vertices(cell) # type: ignore - faces = self.volmesh.cell_faces(cell) # type: ignore + vertices = self.volmesh.cell_vertices(cell) + faces = self.volmesh.cell_faces(cell) vertex_index = dict((vertex, index) for index, vertex in enumerate(vertices)) vertices = [self.vertex_xyz[vertex] for vertex in vertices] - faces = [[vertex_index[vertex] for vertex in self.volmesh.halfface_vertices(face)] for face in faces] # type: ignore + faces = [[vertex_index[vertex] for vertex in self.volmesh.halfface_vertices(face)] for face in faces] - mesh = conversions.vertices_and_faces_to_rhino(vertices, faces, disjoint=True) + mesh = conversions.vertices_and_faces_to_rhino(vertices, faces, disjoint=True, color=color) meshes.append(mesh) return meshes diff --git a/src/compas_ghpython/utilities/__init__.py b/src/compas_ghpython/utilities/__init__.py index d52c4f7e5c9..4a816968760 100644 --- a/src/compas_ghpython/utilities/__init__.py +++ b/src/compas_ghpython/utilities/__init__.py @@ -1,6 +1,7 @@ from __future__ import absolute_import +from warnings import warn -from .drawing import ( +from ..drawing import ( draw_frame, draw_points, draw_lines, @@ -31,3 +32,5 @@ "draw_circles", "draw_brep", ] + +warn("compas_ghpython.utilities will be removed in version 2.3. Please use compas_ghpython.drawing instead.", DeprecationWarning, stacklevel=2) diff --git a/src/compas_rhino/utilities/drawing.py b/src/compas_rhino/drawing.py similarity index 100% rename from src/compas_rhino/utilities/drawing.py rename to src/compas_rhino/drawing.py diff --git a/src/compas_rhino/scene/graphobject.py b/src/compas_rhino/scene/graphobject.py index b9a043b5dbc..6f2725de118 100644 --- a/src/compas_rhino/scene/graphobject.py +++ b/src/compas_rhino/scene/graphobject.py @@ -6,14 +6,10 @@ import scriptcontext as sc # type: ignore import compas_rhino -from compas.geometry import Cylinder from compas.geometry import Line -from compas.geometry import Sphere from compas.scene import GraphObject -from compas_rhino.conversions import cylinder_to_rhino_brep from compas_rhino.conversions import line_to_rhino from compas_rhino.conversions import point_to_rhino -from compas_rhino.conversions import sphere_to_rhino from .sceneobject import RhinoSceneObject @@ -30,7 +26,7 @@ class RhinoGraphObject(RhinoSceneObject, GraphObject): """ - def __init__(self, graph, **kwargs): + def __init__(self, graph, nodegroup=None, edgegroup=None, edgedirection=False, **kwargs): super(RhinoGraphObject, self).__init__(graph=graph, **kwargs) self._guids_nodes = None self._guids_edges = None @@ -38,6 +34,9 @@ def __init__(self, graph, **kwargs): self._guids_edgelabels = None self._guids_spheres = None self._guids_pipes = None + self.nodegroup = nodegroup + self.edgegroup = edgegroup + self.edgedirection = edgedirection # ========================================================================== # clear @@ -86,23 +85,15 @@ def draw(self): The GUIDs of the created Rhino objects. """ - self.clear() - guids = self.draw_nodes(nodes=self.show_nodes, color=self.nodecolor, group=self.group) - guids += self.draw_edges(edges=self.show_edges, color=self.edgecolor, group=self.group) + guids = self.draw_nodes() + guids += self.draw_edges() + self._guids = guids return self.guids - def draw_nodes(self, nodes=None, color=None, group=None): + def draw_nodes(self): """Draw a selection of nodes. - Parameters - ---------- - nodes : list[int], optional - A list of nodes to draw. - Default is None, in which case all nodes are drawn. - color : :class:`compas.colors.Color` | dict[int, :class:`compas.colors.Color`], optional - Color of the nodes. - Returns ------- list[System.Guid] @@ -111,41 +102,29 @@ def draw_nodes(self, nodes=None, color=None, group=None): """ guids = [] - self.nodecolor = color + nodes = list(self.graph.nodes()) if self.show_nodes is True else self.show_nodes or [] - if nodes is True: - nodes = list(self.graph.nodes()) + if nodes: + for node in nodes: + name = "{}.node.{}".format(self.graph.name, node) + attr = self.compile_attributes(name=name, color=self.nodecolor[node]) + geometry = point_to_rhino(self.node_xyz[node]) - for node in nodes or self.graph.nodes(): # type: ignore - name = "{}.node.{}".format(self.graph.name, node) # type: ignore - attr = self.compile_attributes(name=name, color=self.nodecolor[node]) - geometry = point_to_rhino(self.node_xyz[node]) - - guid = sc.doc.Objects.AddPoint(geometry, attr) - guids.append(guid) + guid = sc.doc.Objects.AddPoint(geometry, attr) + guids.append(guid) - if group: - self.add_to_group(group, guids) + if guids: + if self.nodegroup: + self.add_to_group(self.nodegroup, guids) + elif self.group: + self.add_to_group(self.group, guids) self._guids_nodes = guids - return guids - def draw_edges(self, edges=None, color=None, group=None, show_direction=False): + def draw_edges(self): """Draw a selection of edges. - Parameters - ---------- - edges : list[tuple[int, int]], optional - A list of edges to draw. - 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 - Color of the edges. - group : str, optional - The name of a group to add the edges to. - show_direction : bool, optional - Show the direction of the edges. - Returns ------- list[System.Guid] @@ -154,28 +133,28 @@ def draw_edges(self, edges=None, color=None, group=None, show_direction=False): """ guids = [] - arrow = "end" if show_direction else None - self.edgecolor = color + edges = list(self.graph.edges()) if self.show_edges is True else self.show_edges or [] + edgedirection = "end" if self.edgedirection else False - if edges is True: - edges = list(self.graph.edges()) + if edges: + for edge in edges: + u, v = edge - for edge in edges or self.graph.edges(): # type: ignore - u, v = edge - - color = self.edgecolor[edge] - name = "{}.edge.{}-{}".format(self.graph.name, u, v) # type: ignore - attr = self.compile_attributes(name=name, color=color, arrow=arrow) - geometry = line_to_rhino((self.node_xyz[u], self.node_xyz[v])) + color = self.edgecolor[edge] + name = "{}.edge.{}-{}".format(self.graph.name, u, v) + attr = self.compile_attributes(name=name, color=color, arrow=edgedirection) + geometry = line_to_rhino((self.node_xyz[u], self.node_xyz[v])) - guid = sc.doc.Objects.AddLine(geometry, attr) - guids.append(guid) + guid = sc.doc.Objects.AddLine(geometry, attr) + guids.append(guid) - if group: - self.add_to_group(group, guids) + if guids: + if self.edgegroup: + self.add_to_group(self.edgegroup, guids) + elif self.group: + self.add_to_group(self.group, guids) self._guids_edges = guids - return guids # ============================================================================= @@ -277,88 +256,3 @@ def draw_edgelabels(self, text, color=None, group=None, fontheight=10, fontface= self._guids_edgelabels = guids return guids - - # ============================================================================= - # draw miscellaneous - # ============================================================================= - - def draw_spheres(self, radius, color=None, group=None): - """Draw spheres at the vertices of the graph. - - Parameters - ---------- - radius : dict[int, float], optional - The radius of the spheres. - color : :class:`compas.colors.Color` | dict[int, :class:`compas.colors.Color`], optional - The color of the spheres. - group : str, optional - The name of a group to join the created Rhino objects in. - - Returns - ------- - list[System.Guid] - The GUIDs of the created Rhino objects. - - """ - guids = [] - - self.nodecolor = color - - for node in radius: - name = "{}.node.{}.sphere".format(self.graph.name, node) # type: ignore - color = self.nodecolor[node] - attr = self.compile_attributes(name=name, color=color) - - sphere = Sphere.from_point_and_radius(self.node_xyz[node], radius[node]) - geometry = sphere_to_rhino(sphere) - - guid = sc.doc.Objects.AddSphere(geometry, attr) - guids.append(guid) - - if group: - self.add_to_group(group, guids) - - self._guids_spheres = guids - - return guids - - def draw_pipes(self, radius, color=None, group=None): - """Draw pipes around the edges of the graph. - - Parameters - ---------- - radius : dict[tuple[int, int], float] - The radius per edge. - color : :class:`compas.colors.Color` | dict[tuple[int, int], :class:`compas.colors.Color`], optional - The color of the pipes. - group : str, optional - The name of a group to join the created Rhino objects in. - - Returns - ------- - list[System.Guid] - The GUIDs of the created Rhino objects. - - """ - guids = [] - - self.edgecolor = color - - for edge in radius: - name = "{}.edge.{}-{}.pipe".format(self.graph.name, *edge) # type: ignore - color = self.edgecolor[edge] - attr = self.compile_attributes(name=name, color=color) - - line = Line(self.node_xyz[edge[0]], self.node_xyz[edge[1]]) - cylinder = Cylinder.from_line_and_radius(line, radius[edge]) - geometry = cylinder_to_rhino_brep(cylinder) - - guid = sc.doc.Objects.AddBrep(geometry, attr) - guids.append(guid) - - if group: - self.add_to_group(group, guids) - - self._guids_pipes = guids - - return guids diff --git a/src/compas_rhino/scene/meshobject.py b/src/compas_rhino/scene/meshobject.py index 799eca74ed3..19ce6f0fa69 100644 --- a/src/compas_rhino/scene/meshobject.py +++ b/src/compas_rhino/scene/meshobject.py @@ -7,18 +7,14 @@ import compas_rhino.objects from compas.colors import Color -from compas.geometry import Cylinder from compas.geometry import Line from compas.geometry import Point -from compas.geometry import Sphere from compas.geometry import centroid_points from compas.scene import MeshObject -from compas_rhino.conversions import cylinder_to_rhino_brep from compas_rhino.conversions import line_to_rhino -from compas_rhino.conversions import mesh_to_rhino from compas_rhino.conversions import point_to_rhino -from compas_rhino.conversions import sphere_to_rhino -from compas_rhino.conversions import transformation_to_rhino + +# from compas_rhino.conversions import transformation_to_rhino from compas_rhino.conversions import vertices_and_faces_to_rhino from .helpers import ngon @@ -43,7 +39,7 @@ class RhinoMeshObject(RhinoSceneObject, MeshObject): """ - def __init__(self, mesh, disjoint=False, **kwargs): + def __init__(self, mesh, disjoint=False, vertexgroup=None, edgegroup=None, facegroup=None, **kwargs): super(RhinoMeshObject, self).__init__(mesh=mesh, **kwargs) self.disjoint = disjoint self._guid_mesh = None @@ -57,6 +53,9 @@ def __init__(self, mesh, disjoint=False, **kwargs): self._guids_facelabels = None self._guids_spheres = None self._guids_pipes = None + self.vertexgroup = vertexgroup + self.edgegroup = edgegroup + self.facegroup = facegroup # ========================================================================== # clear @@ -188,15 +187,22 @@ def draw(self): if len(self.facecolor): facecolors = [self.facecolor[face] for face in self.mesh.faces()] - geometry = mesh_to_rhino( - self.mesh, + vertex_index = self.mesh.vertex_index() + vertex_xyz = self.vertex_xyz + + vertices = [vertex_xyz[vertex] for vertex in self.mesh.vertices()] + faces = [[vertex_index[vertex] for vertex in self.mesh.face_vertices(face)] for face in self.mesh.faces()] + + geometry = vertices_and_faces_to_rhino( + vertices, + faces, color=self.color, vertexcolors=vertexcolors, facecolors=facecolors, disjoint=self.disjoint, ) - geometry.Transform(transformation_to_rhino(self.worldtransformation)) + # geometry.Transform(transformation_to_rhino(self.worldtransformation)) self._guid_mesh = sc.doc.Objects.AddMesh(geometry, attr) if self.group: @@ -205,29 +211,19 @@ def draw(self): self._guids.append(self._guid_mesh) elif self.show_faces: - self._guids += self.draw_faces(faces=self.show_faces, color=self.facecolor, group=self.group) + self._guids += self.draw_faces() if self.show_vertices: - self._guids += self.draw_vertices(vertices=self.show_vertices, color=self.vertexcolor, group=self.group) + self._guids += self.draw_vertices() if self.show_edges: - self._guids += self.draw_edges(edges=self.show_edges, color=self.edgecolor, group=self.group) + self._guids += self.draw_edges() return self.guids - def draw_vertices(self, vertices=None, color=None, group=None): + def draw_vertices(self): """Draw a selection of vertices. - Parameters - ---------- - vertices : list[int], optional - A selection of vertices to draw. - Default is None, in which case all vertices are drawn. - color : :class:`compas.colors.Color` | dict[int, :class:`compas.colors.Color`], optional - The color of the vertices. - group : str, optional - The name of a group to join the created Rhino objects in. - Returns ------- list[System.Guid] @@ -236,45 +232,31 @@ def draw_vertices(self, vertices=None, color=None, group=None): """ guids = [] - self.vertexcolor = color - - if vertices is True: - vertices = list(self.mesh.vertices()) + vertices = list(self.mesh.vertices()) if self.show_vertices is True else self.show_vertices or [] - for vertex in vertices or self.mesh.vertices(): # type: ignore - name = "{}.vertex.{}".format(self.mesh.name, vertex) # type: ignore - color = self.vertexcolor[vertex] - attr = self.compile_attributes(name=name, color=color) + if vertices: + for vertex in vertices: + name = "{}.vertex.{}".format(self.mesh.name, vertex) + color = self.vertexcolor[vertex] + attr = self.compile_attributes(name=name, color=color) - point = point_to_rhino(self.vertex_xyz[vertex]) + point = point_to_rhino(self.vertex_xyz[vertex]) - guid = sc.doc.Objects.AddPoint(point, attr) - guids.append(guid) + guid = sc.doc.Objects.AddPoint(point, attr) + guids.append(guid) - if group: - self.add_to_group(group, guids) + if guids: + if self.vertexgroup: + self.add_to_group(self.vertexgroup, guids) + elif self.group: + self.add_to_group(self.group, guids) self._guids_vertices = guids - return guids - def draw_edges(self, edges=None, color=None, text=None, fontheight=10, fontface="Arial Regular", group=None): + def draw_edges(self): """Draw a selection of edges. - Parameters - ---------- - edges : list[tuple[int, int]], optional - A selection of edges to draw. - 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 of the edges. - text : dict[tuple[int, int], str], optional - A dictionary of edge labels as edge-text pairs. - fontheight : int, optional - Font height of the edge labels. - fontface : str, optional - Font face of the edge labels. - Returns ------- list[System.Guid] @@ -283,45 +265,31 @@ def draw_edges(self, edges=None, color=None, text=None, fontheight=10, fontface= """ guids = [] - self.edgecolor = color + edges = list(self.mesh.edges()) if self.show_edges is True else self.show_edges or [] - if edges is True: - edges = list(self.mesh.edges()) + if edges: + for edge in edges: + name = "{}.edge.{}-{}".format(self.mesh.name, *edge) + color = self.edgecolor[edge] + attr = self.compile_attributes(name=name, color=color) - for edge in edges or self.mesh.edges(): # type: ignore - name = "{}.edge.{}-{}".format(self.mesh.name, *edge) # type: ignore - color = self.edgecolor[edge] - attr = self.compile_attributes(name=name, color=color) + line = Line(self.vertex_xyz[edge[0]], self.vertex_xyz[edge[1]]) - line = Line(self.vertex_xyz[edge[0]], self.vertex_xyz[edge[1]]) + guid = sc.doc.Objects.AddLine(line_to_rhino(line), attr) + guids.append(guid) - guid = sc.doc.Objects.AddLine(line_to_rhino(line), attr) - guids.append(guid) - - if group: - self.add_to_group(group, guids) + if guids: + if self.edgegroup: + self.add_to_group(self.edgegroup, guids) + elif self.group: + self.add_to_group(self.group, guids) self._guids_edges = guids - return guids - def draw_faces(self, faces=None, color=None, group=None): + def draw_faces(self): """Draw a selection of faces. - Parameters - ---------- - faces : list[int], optional - A selection of faces to draw. - 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 of the faces. - text : dict[int, str], optional - A dictionary of face labels as face-text pairs. - fontheight : int, optional - Font height of the face labels. - fontface : str, optional - Font face of the face labels. - Returns ------- list[System.Guid] @@ -330,28 +298,28 @@ def draw_faces(self, faces=None, color=None, group=None): """ guids = [] - self.facecolor = color + faces = list(self.mesh.faces()) if self.show_faces is True else self.show_faces or [] - if faces is True: - faces = list(self.mesh.faces()) + if faces: + for face in faces: + name = "{}.face.{}".format(self.mesh.name, face) + color = self.facecolor[face] + attr = self.compile_attributes(name=name, color=color) - for face in faces or self.mesh.faces(): # type: ignore - name = "{}.face.{}".format(self.mesh.name, face) # type: ignore - color = self.facecolor[face] - attr = self.compile_attributes(name=name, color=color) + vertices = [self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)] # type: ignore + facet = ngon(len(vertices)) - vertices = [self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)] # type: ignore - facet = ngon(len(vertices)) + if facet: + guid = sc.doc.Objects.AddMesh(vertices_and_faces_to_rhino(vertices, [facet]), attr) + guids.append(guid) - if facet: - guid = sc.doc.Objects.AddMesh(vertices_and_faces_to_rhino(vertices, [facet]), attr) - guids.append(guid) - - if group: - self.add_to_group(group, guids) + if guids: + if self.facegroup: + self.add_to_group(self.facegroup, guids) + elif self.group: + self.add_to_group(self.group, guids) self._guids_faces = guids - return guids # ========================================================================== @@ -586,88 +554,3 @@ def draw_facenormals(self, faces=None, color=(0, 255, 255), scale=1.0, group=Non self._guids_facenormals = guids return guids - - # ========================================================================== - # draw miscellaneous - # ========================================================================== - - def draw_spheres(self, radius, color=None, group=None): - """Draw spheres at the vertices of the mesh. - - Parameters - ---------- - 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. - group : str, optional - The name of a group to join the created Rhino objects in. - - Returns - ------- - list[System.Guid] - The GUIDs of the created Rhino objects. - - """ - guids = [] - - self.vertexcolor = color - - for vertex in radius: - name = "{}.vertex.{}.sphere".format(self.mesh.name, vertex) # type: ignore - color = self.vertexcolor[vertex] - attr = self.compile_attributes(name=name, color=color) - - sphere = Sphere.from_point_and_radius(self.vertex_xyz[vertex], radius[vertex]) - geometry = sphere_to_rhino(sphere) - - guid = sc.doc.Objects.AddSphere(geometry, attr) - guids.append(guid) - - if group: - self.add_to_group(group, guids) - - self._guids_spheres = guids - - return guids - - def draw_pipes(self, radius, color=None, group=None): - """Draw pipes around the edges of the mesh. - - Parameters - ---------- - 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. - group : str, optional - The name of a group to join the created Rhino objects in. - - Returns - ------- - list[System.Guid] - The GUIDs of the created Rhino objects. - - """ - guids = [] - - self.edgecolor = color - - for edge in radius: - name = "{}.edge.{}-{}.pipe".format(self.mesh.name, *edge) # type: ignore - color = self.edgecolor[edge] - attr = self.compile_attributes(name=name, color=color) - - line = Line(self.vertex_xyz[edge[0]], self.vertex_xyz[edge[1]]) - cylinder = Cylinder.from_line_and_radius(line, radius[edge]) - brep = cylinder_to_rhino_brep(cylinder) - - guid = sc.doc.Objects.AddBrep(brep, attr) - guids.append(guid) - - if group: - self.add_to_group(group, guids) - - self._guids_pipes = guids - - return guids diff --git a/src/compas_rhino/scene/volmeshobject.py b/src/compas_rhino/scene/volmeshobject.py index 3be88d5312c..1b56be5c09b 100644 --- a/src/compas_rhino/scene/volmeshobject.py +++ b/src/compas_rhino/scene/volmeshobject.py @@ -32,7 +32,7 @@ class RhinoVolMeshObject(RhinoSceneObject, VolMeshObject): """ - def __init__(self, volmesh, disjoint=True, **kwargs): + def __init__(self, volmesh, disjoint=True, vertexgroup=None, edgegroup=None, facegroup=None, cellgroup=None, **kwargs): super(RhinoVolMeshObject, self).__init__(volmesh=volmesh, **kwargs) self.disjoint = disjoint self._guids_vertices = None @@ -43,6 +43,10 @@ def __init__(self, volmesh, disjoint=True, **kwargs): self._guids_edgelabels = None self._guids_facelabels = None self._guids_celllabels = None + self.vertexgroup = vertexgroup + self.edgegroup = edgegroup + self.facegroup = facegroup + self.cellgroup = cellgroup # ========================================================================== # clear @@ -145,153 +149,122 @@ def draw(self): guids = [] if self.show_vertices: - guids += self.draw_vertices(vertices=self.show_vertices, color=self.vertexcolor, group=self.group) + guids += self.draw_vertices() if self.show_edges: - guids += self.draw_edges(edges=self.show_edges, color=self.edgecolor, group=self.group) + guids += self.draw_edges() if self.show_faces: - guids += self.draw_faces(faces=self.show_faces, color=self.facecolor, group=self.group) + guids += self.draw_faces() if self.show_cells: - guids += self.draw_cells(cells=self.show_cells, color=self.cellcolor, group=self.group) + guids += self.draw_cells() self._guids = guids return self.guids - def draw_vertices(self, vertices=None, color=None, group=None): + def draw_vertices(self): """Draw a selection of vertices. - Parameters - ---------- - vertices : list[int], optional - A list of vertices to draw. - Default is None, in which case all vertices are drawn. - color : :class:`compas.colors.Color` | dict[int, :class:`compas.colors.Color`], optional - The color of the vertices. - group : str, optional - The name of the group in which the vertices are combined. - Returns ------- list[System.Guid] The GUIDs of the created Rhino point objects. """ - self.vertexcolor = color - guids = [] - if vertices is True: - vertices = list(self.volmesh.vertices()) + vertices = list(self.volmesh.vertices()) if self.show_vertices is True else self.show_vertices or [] - for vertex in vertices or self.volmesh.vertices(): # type: ignore - name = "{}.vertex.{}".format(self.volmesh.name, vertex) # type: ignore - color = self.vertexcolor[vertex] - attr = self.compile_attributes(name=name, color=color) + if vertices: + for vertex in vertices: + name = "{}.vertex.{}".format(self.volmesh.name, vertex) + color = self.vertexcolor[vertex] + attr = self.compile_attributes(name=name, color=color) - point = self.vertex_xyz[vertex] + point = self.vertex_xyz[vertex] - guid = sc.doc.Objects.AddPoint(point_to_rhino(point), attr) - guids.append(guid) + guid = sc.doc.Objects.AddPoint(point_to_rhino(point), attr) + guids.append(guid) + + if guids: + if self.vertexgroup: + self.add_to_group(self.vertexgroup, guids) + elif self.group: + self.add_to_group(self.group, guids) + self._guids_vertices = guids return guids - def draw_edges(self, edges=None, color=None, group=None): + def draw_edges(self): """Draw a selection of edges. - Parameters - ---------- - edges : list[tuple[int, int]], optional - A list of edges to draw. - 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 of the edges. - group : str, optional - The name of the group in which the edges are combined. - Returns ------- list[System.Guid] The GUIDs of the created Rhino line objects. """ - self.edgecolor = color - guids = [] - if edges is True: - edges = list(self.volmesh.edges()) + edges = list(self.volmesh.edges()) if self.show_edges is True else self.show_edges or [] - for edge in edges or self.volmesh.edges(): # type: ignore - name = "{}.edge.{}-{}".format(self.volmesh.name, *edge) # type: ignore - color = self.edgecolor[edge] - attr = self.compile_attributes(name=name, color=color) + if edges: + for edge in edges: + name = "{}.edge.{}-{}".format(self.volmesh.name, *edge) + color = self.edgecolor[edge] + attr = self.compile_attributes(name=name, color=color) - line = Line(self.vertex_xyz[edge[0]], self.vertex_xyz[edge[1]]) + line = Line(self.vertex_xyz[edge[0]], self.vertex_xyz[edge[1]]) - guid = sc.doc.Objects.AddLine(line_to_rhino(line), attr) - guids.append(guid) + guid = sc.doc.Objects.AddLine(line_to_rhino(line), attr) + guids.append(guid) - if group: - self.add_to_group(group, guids) + if guids: + if self.edgegroup: + self.add_to_group(self.edgegroup, guids) + elif self.group: + self.add_to_group(self.group, guids) + self._guids_edges = guids return guids - def draw_faces(self, faces=None, color=None, group=None): + def draw_faces(self): """Draw a selection of faces. - Parameters - ---------- - faces : list[int], optional - A list of faces to draw. - 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 of the faces. - group : str, optional - The name of the group in which the faces are combined. - Returns ------- list[System.Guid] The GUIDs of the created Rhino objects. """ - self.facecolor = color - guids = [] - if faces is True: - faces = list(self.volmesh.faces()) + faces = list(self.volmesh.faces()) if self.show_faces is True else self.show_faces or [] - for face in faces or self.volmesh.faces(): # type: ignore - name = "{}.face.{}".format(self.volmesh.name, face) # type: ignore - color = self.facecolor[face] - attr = self.compile_attributes(name=name, color=color) + if faces: + for face in faces: + name = "{}.face.{}".format(self.volmesh.name, face) + color = self.facecolor[face] + attr = self.compile_attributes(name=name, color=color) - vertices = [self.vertex_xyz[vertex] for vertex in self.volmesh.face_vertices(face)] # type: ignore - facet = ngon(len(vertices)) + vertices = [self.vertex_xyz[vertex] for vertex in self.volmesh.face_vertices(face)] + facet = ngon(len(vertices)) - if facet: - guid = sc.doc.Objects.AddMesh(vertices_and_faces_to_rhino(vertices, [facet]), attr) - guids.append(guid) + if facet: + guid = sc.doc.Objects.AddMesh(vertices_and_faces_to_rhino(vertices, [facet]), attr) + guids.append(guid) - if group: - self.add_to_group(group, guids) + if guids: + if self.facegroup: + self.add_to_group(self.facegroup, guids) + elif self.group: + self.add_to_group(self.group, guids) + self._guids_faces = guids return guids - def draw_cells(self, cells=None, color=None, group=None): + def draw_cells(self): """Draw a selection of cells. - Parameters - ---------- - cells : list[int], optional - A list of cells to draw. - The default is None, in which case all cells are drawn. - color : :class:`compas.colors.Color` | dict[int, :class:`compas.colors.Color`], optional - The color of the cells. - group : str, optional - The name of the group in which the cells are combined. - Returns ------- list[System.Guid] @@ -299,27 +272,33 @@ def draw_cells(self, cells=None, color=None, group=None): Every cell is drawn as an individual mesh. """ - self.cellcolor = color - guids = [] - if cells is True: - cells = list(self.volmesh.cells()) + cells = list(self.volmesh.cells()) if self.show_cells is True else self.show_cells or [] - for cell in cells or self.volmesh.cells(): # type: ignore - name = "{}.cell.{}".format(self.volmesh.name, cell) # type: ignore - color = self.cellcolor[cell] - attr = self.compile_attributes(name=name, color=color) + if cells: + for cell in cells: + name = "{}.cell.{}".format(self.volmesh.name, cell) + color = self.cellcolor[cell] + attr = self.compile_attributes(name=name, color=color) - vertices = self.volmesh.cell_vertices(cell) # type: ignore - faces = self.volmesh.cell_faces(cell) # type: ignore - vertex_index = dict((vertex, index) for index, vertex in enumerate(vertices)) - vertices = [self.vertex_xyz[vertex] for vertex in vertices] - faces = [[vertex_index[vertex] for vertex in self.volmesh.halfface_vertices(face)] for face in faces] # type: ignore + vertices = self.volmesh.cell_vertices(cell) + faces = self.volmesh.cell_faces(cell) + vertex_index = dict((vertex, index) for index, vertex in enumerate(vertices)) - guid = sc.doc.Objects.AddMesh(vertices_and_faces_to_rhino(vertices, faces, disjoint=self.disjoint), attr) - guids.append(guid) + vertices = [self.vertex_xyz[vertex] for vertex in vertices] + faces = [[vertex_index[vertex] for vertex in self.volmesh.halfface_vertices(face)] for face in faces] + + guid = sc.doc.Objects.AddMesh(vertices_and_faces_to_rhino(vertices, faces, disjoint=self.disjoint), attr) + guids.append(guid) + if guids: + if self.cellgroup: + self.add_to_group(self.cellgroup, guids) + elif self.group: + self.add_to_group(self.group, guids) + + self._guids_cells = guids return guids # ============================================================================= @@ -508,11 +487,3 @@ def draw_celllabels(self, text, color=None, group=None, fontheight=10, fontface= self.add_to_group(group, guids) return guids - - # ============================================================================= - # draw normals - # ============================================================================= - - # ============================================================================= - # draw miscellaneous - # ============================================================================= diff --git a/src/compas_rhino/utilities/__init__.py b/src/compas_rhino/utilities/__init__.py index 70efdc72bf5..51790bb787e 100644 --- a/src/compas_rhino/utilities/__init__.py +++ b/src/compas_rhino/utilities/__init__.py @@ -1,6 +1,7 @@ from __future__ import absolute_import +from warnings import warn -from .drawing import ( +from ..drawing import ( draw_labels, draw_points, draw_lines, @@ -35,3 +36,5 @@ "draw_surfaces", "draw_brep", ] + +warn("compas_rhino.utilities will be removed in version 2.3. Please use compas_rhino.drawing instead.", DeprecationWarning, stacklevel=2) diff --git a/src/compas_rhino/utilities/constructors.py b/src/compas_rhino/utilities/constructors.py deleted file mode 100644 index aa86af86ba5..00000000000 --- a/src/compas_rhino/utilities/constructors.py +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import Rhino # type: ignore -import scriptcontext as sc # type: ignore - -from compas.tolerance import TOL - - -def volmesh_from_polysurfaces(cls, guids, precision=None): - """Construct a volumetric mesh from given polysurfaces. - - Parameters - ---------- - cls : :class:`compas.datastructures.VolMesh` - The class of volmesh. - guids : sequence[str | System.Guid] - The *globally unique identifiers* of the polysurfaces. - precision: int, optional - Precision for converting numbers to strings. - Default is :attr:`TOL.precision`. - - Returns - ------- - :class:`compas.datastructures.Volmesh` - The volumetric mesh object. - - Notes - ----- - Essentially, this function does the following: - - * find each of the polysurfaces and check if they have a boundary representation (b-rep) - * convert to b-rep and extract the edge loops - * make a face of each loop by referring to vertices using their geometric keys - * add a cell per brep - * and add the faces of a brep to the cell - * create a volmesh from the found vertices and cells - - """ - gkey_xyz = {} - cells = [] - for guid in guids: - cell = [] - obj = sc.doc.Objects.Find(guid) - if not obj.Geometry.HasBrepForm: - continue - brep = Rhino.Geometry.Brep.TryConvertBrep(obj.Geometry) - for loop in brep.Loops: - curve = loop.To3dCurve() - segments = curve.Explode() - face = [] - sp = segments[0].PointAtStart - ep = segments[0].PointAtEnd - sp_gkey = TOL.geometric_key(sp, precision) - ep_gkey = TOL.geometric_key(ep, precision) - gkey_xyz[sp_gkey] = sp - gkey_xyz[ep_gkey] = ep - face.append(sp_gkey) - face.append(ep_gkey) - for segment in segments[1:-1]: - ep = segment.PointAtEnd - ep_gkey = TOL.geometric_key(ep, precision) - face.append(ep_gkey) - gkey_xyz[ep_gkey] = ep - cell.append(face) - cells.append(cell) - gkey_index = dict((gkey, index) for index, gkey in enumerate(gkey_xyz)) - vertices = [list(xyz) for gkey, xyz in gkey_xyz.items()] - cells = [[[gkey_index[gkey] for gkey in face] for face in cell] for cell in cells] - return cls.from_vertices_and_cells(vertices, cells) diff --git a/tests/compas/scene/test_scene.py b/tests/compas/scene/test_scene.py index ba1a1c6c565..52da3d72129 100644 --- a/tests/compas/scene/test_scene.py +++ b/tests/compas/scene/test_scene.py @@ -1,14 +1,12 @@ -import pytest # noqa: F401 - import compas -from compas.scene import context -from compas.scene import register -from compas.scene import SceneObject -from compas.scene import SceneObjectNotRegisteredError -from compas.data import Data - if not compas.IPY: + import pytest # noqa: F401 + from compas.scene import context + from compas.scene import register + from compas.scene import SceneObject + from compas.scene import SceneObjectNotRegisteredError + from compas.data import Data @pytest.fixture(autouse=True) def reset_sceneobjects_registration(): @@ -17,73 +15,63 @@ def reset_sceneobjects_registration(): # after each test, reset scene objects context.ITEM_SCENEOBJECT.clear() + def register_fake_context(): + register(FakeItem, FakeSceneObject, context="fake") -def register_fake_context(): - register(FakeItem, FakeSceneObject, context="fake") + class FakeSceneObject(SceneObject): + def draw(self): + pass + def clear(self): + pass -class FakeSceneObject(SceneObject): - def draw(self): - pass - - def clear(self): - pass + class FakeSubSceneObject(SceneObject): + def draw(self): + pass + def clear(self): + pass -class FakeSubSceneObject(SceneObject): - def draw(self): + class FakeItem(Data): pass - def clear(self): + class FakeSubItem(FakeItem): pass + def test_get_sceneobject_cls_with_orderly_registration(): + register(FakeItem, FakeSceneObject, context="fake") + register(FakeSubItem, FakeSubSceneObject, context="fake") + item = FakeItem() + sceneobject = SceneObject(item, context="fake") + assert isinstance(sceneobject, FakeSceneObject) -class FakeItem(Data): - pass - - -class FakeSubItem(FakeItem): - pass - - -def test_get_sceneobject_cls_with_orderly_registration(): - register(FakeItem, FakeSceneObject, context="fake") - register(FakeSubItem, FakeSubSceneObject, context="fake") - item = FakeItem() - sceneobject = SceneObject(item, context="fake") - assert isinstance(sceneobject, FakeSceneObject) - - item = FakeSubItem() - sceneobject = SceneObject(item, context="fake") - assert isinstance(sceneobject, FakeSubSceneObject) - - -def test_get_sceneobject_cls_with_out_of_order_registration(): - register(FakeSubItem, FakeSubSceneObject, context="fake") - register(FakeItem, FakeSceneObject, context="fake") - item = FakeItem() - sceneobject = SceneObject(item, context="fake") - assert isinstance(sceneobject, FakeSceneObject) - - item = FakeSubItem() - sceneobject = SceneObject(item, context="fake") - assert isinstance(sceneobject, FakeSubSceneObject) + item = FakeSubItem() + sceneobject = SceneObject(item, context="fake") + assert isinstance(sceneobject, FakeSubSceneObject) + def test_get_sceneobject_cls_with_out_of_order_registration(): + register(FakeSubItem, FakeSubSceneObject, context="fake") + register(FakeItem, FakeSceneObject, context="fake") + item = FakeItem() + sceneobject = SceneObject(item, context="fake") + assert isinstance(sceneobject, FakeSceneObject) -if not compas.IPY: + item = FakeSubItem() + sceneobject = SceneObject(item, context="fake") + assert isinstance(sceneobject, FakeSubSceneObject) - def test_sceneobject_auto_context_discovery(mocker): - register_fake_context() + def test_sceneobject_auto_context_discovery(mocker): + register_fake_context() - item = FakeItem() - sceneobject = SceneObject(item) + item = FakeItem() + sceneobject = SceneObject(item) - assert isinstance(sceneobject, FakeSceneObject) + assert isinstance(sceneobject, FakeSceneObject) - def test_sceneobject_auto_context_discovery_no_context(mocker): - mocker.patch("compas.scene.context.compas.is_grasshopper", return_value=False) - mocker.patch("compas.scene.context.compas.is_rhino", return_value=False) + def test_sceneobject_auto_context_discovery_no_context(mocker): + mocker.patch("compas.scene.context.compas.is_grasshopper", return_value=False) + mocker.patch("compas.scene.context.compas.is_rhino", return_value=False) - with pytest.raises(SceneObjectNotRegisteredError): - item = FakeSubItem() - _ = SceneObject(item) + with pytest.raises(SceneObjectNotRegisteredError): + item = FakeSubItem() + _ = SceneObject(item) diff --git a/tests/compas/scene/test_scene_serialisation.py b/tests/compas/scene/test_scene_serialisation.py index 2fd12c3bca1..26fb03f7e9f 100644 --- a/tests/compas/scene/test_scene_serialisation.py +++ b/tests/compas/scene/test_scene_serialisation.py @@ -1,119 +1,117 @@ -import pytest # noqa: F401 import compas -from compas.data import Data -from compas.scene import Scene -from compas.geometry import Box -from compas.geometry import Capsule -from compas.geometry import Circle -from compas.geometry import Cone -from compas.geometry import Cylinder -from compas.geometry import Ellipse -from compas.geometry import Frame -from compas.geometry import Line -from compas.geometry import Point -from compas.geometry import Polygon -from compas.geometry import Polyhedron -from compas.geometry import Polyline -from compas.geometry import Sphere -from compas.geometry import Torus -from compas.geometry import Vector -from compas.geometry import Plane -from compas.datastructures import Mesh -from compas.datastructures import Graph -from compas.datastructures import VolMesh - - -@pytest.fixture -def items(): - box = Box.from_width_height_depth(1, 1, 1) - capsule = Capsule(0.5, 1, Frame.worldXY()) - circle = Circle(1, Frame.worldXY()) - cone = Cone(1, 1, Frame.worldXY()) - cylinder = Cylinder(1, 1, Frame.worldXY()) - line = Line(Point(0, 0, 0), Point(1, 1, 1)) - point = Point(0, 0, 0) - polygon = Polygon.from_sides_and_radius_xy(5, 1) - polyhedron = Polyhedron.from_platonicsolid(4) - polyline = Polyline([[0, 0, 0], [1, 0, 0], [1, 0, 1]]) - sphere = Sphere(1) - torus = Torus(1, 0.3, Frame.worldXY()) - vector = Vector(0, 0, 1) - ellipse = Ellipse(1, 0.5, Frame.worldXY()) - frame = Frame.worldXY() - plane = Plane(Point(0, 0, 0), Vector(0, 0, 1)) - mesh = Mesh.from_polyhedron(8) - graph = Graph.from_nodes_and_edges([(0, 0, 0), (0, -1.5, 0), (-1, 1, 0), (1, 1, 0)], [(0, 1), (0, 2), (0, 3)]) - volmesh = VolMesh.from_meshgrid(1, 1, 1, 2, 2, 2) - - return [ - box, - capsule, - circle, - cone, - cylinder, - line, - point, - polygon, - polyhedron, - polyline, - sphere, - torus, - vector, - ellipse, - frame, - plane, - mesh, - graph, - volmesh, - ] - - -def assert_is_data_equal(obj1, obj2, path=""): - if type(obj1) is not type(obj2): - print("Type mismatch: {} != {} for {}:{} and {}:{}".format(type(obj1), type(obj2), path, obj1, path, obj2)) - return False - - if isinstance(obj1, (list, tuple)): - if len(obj1) != len(obj2): - print("Length mismatch: {} != {} for {} and {}".format(len(obj1), len(obj2), path, path)) +if not compas.IPY: + import pytest # noqa: F401 + from compas.data import Data + from compas.scene import Scene + from compas.geometry import Box + from compas.geometry import Capsule + from compas.geometry import Circle + from compas.geometry import Cone + from compas.geometry import Cylinder + from compas.geometry import Ellipse + from compas.geometry import Frame + from compas.geometry import Line + from compas.geometry import Point + from compas.geometry import Polygon + from compas.geometry import Polyhedron + from compas.geometry import Polyline + from compas.geometry import Sphere + from compas.geometry import Torus + from compas.geometry import Vector + from compas.geometry import Plane + from compas.datastructures import Mesh + from compas.datastructures import Graph + from compas.datastructures import VolMesh + + @pytest.fixture + def items(): + box = Box.from_width_height_depth(1, 1, 1) + capsule = Capsule(0.5, 1, Frame.worldXY()) + circle = Circle(1, Frame.worldXY()) + cone = Cone(1, 1, Frame.worldXY()) + cylinder = Cylinder(1, 1, Frame.worldXY()) + line = Line(Point(0, 0, 0), Point(1, 1, 1)) + point = Point(0, 0, 0) + polygon = Polygon.from_sides_and_radius_xy(5, 1) + polyhedron = Polyhedron.from_platonicsolid(4) + polyline = Polyline([[0, 0, 0], [1, 0, 0], [1, 0, 1]]) + sphere = Sphere(1) + torus = Torus(1, 0.3, Frame.worldXY()) + vector = Vector(0, 0, 1) + ellipse = Ellipse(1, 0.5, Frame.worldXY()) + frame = Frame.worldXY() + plane = Plane(Point(0, 0, 0), Vector(0, 0, 1)) + mesh = Mesh.from_polyhedron(8) + graph = Graph.from_nodes_and_edges([(0, 0, 0), (0, -1.5, 0), (-1, 1, 0), (1, 1, 0)], [(0, 1), (0, 2), (0, 3)]) + volmesh = VolMesh.from_meshgrid(1, 1, 1, 2, 2, 2) + + return [ + box, + capsule, + circle, + cone, + cylinder, + line, + point, + polygon, + polyhedron, + polyline, + sphere, + torus, + vector, + ellipse, + frame, + plane, + mesh, + graph, + volmesh, + ] + + def assert_is_data_equal(obj1, obj2, path=""): + if type(obj1) is not type(obj2): + print("Type mismatch: {} != {} for {}:{} and {}:{}".format(type(obj1), type(obj2), path, obj1, path, obj2)) return False - for i, (item1, item2) in enumerate(zip(obj1, obj2)): - if not assert_is_data_equal(item1, item2, path="{}[{}]".format(path, i)): + if isinstance(obj1, (list, tuple)): + if len(obj1) != len(obj2): + print("Length mismatch: {} != {} for {} and {}".format(len(obj1), len(obj2), path, path)) return False - return True + for i, (item1, item2) in enumerate(zip(obj1, obj2)): + if not assert_is_data_equal(item1, item2, path="{}[{}]".format(path, i)): + return False - elif isinstance(obj1, dict): - if set(obj1.keys()) != set(obj2.keys()): - print("Key mismatch: {} != {} for {} and {}".format(set(obj1.keys()), set(obj2.keys()), path, path)) - return False + return True - for key in obj1: - if not assert_is_data_equal(obj1[key], obj2[key], path='{}["{}"]'.format(path, key)): + elif isinstance(obj1, dict): + if set(obj1.keys()) != set(obj2.keys()): + print("Key mismatch: {} != {} for {} and {}".format(set(obj1.keys()), set(obj2.keys()), path, path)) return False - return True - - elif isinstance(obj1, Data): - return assert_is_data_equal(obj1.__data__, obj2.__data__, path="{}.__data__".format(path)) + for key in obj1: + if not assert_is_data_equal(obj1[key], obj2[key], path='{}["{}"]'.format(path, key)): + return False - else: - if obj1 != obj2: - print("Value mismatch: {} != {} for {}:{} and {}:{}".format(obj1, obj2, path, obj1, path, obj2)) - return False - else: return True + elif isinstance(obj1, Data): + return assert_is_data_equal(obj1.__data__, obj2.__data__, path="{}.__data__".format(path)) + + else: + if obj1 != obj2: + print("Value mismatch: {} != {} for {}:{} and {}:{}".format(obj1, obj2, path, obj1, path, obj2)) + return False + else: + return True -def test_scene_serialisation(items, mocker): - if compas.IPY: - mocker.patch("compas.is_rhino", return_value=False) + def test_scene_serialisation(items, mocker): + if compas.IPY: + mocker.patch("compas.is_rhino", return_value=False) - scene1 = Scene() - for item in items: - scene1.add(item) + scene1 = Scene() + for item in items: + scene1.add(item) - scene2 = Scene.from_jsonstring(scene1.to_jsonstring()) - assert assert_is_data_equal(scene1, scene2) + scene2 = Scene.from_jsonstring(scene1.to_jsonstring()) + assert assert_is_data_equal(scene1, scene2)