diff --git a/CHANGELOG.md b/CHANGELOG.md index 8138c09e7515..0e8962656feb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed * Replaced use of `Rhino.Geometry.VertexColors.SetColors` with a for loop and `SetColor` in `compas_ghpyton` since the former requires a `System.Array`. + +### Removed + + +## [2.4.1] 2024-08-25 + +### Added + +### Changed + +* Changed supported Blender versions to latest LTS versions (3.3, 3.6, 4.2). +* Fixed bug in `compas_rhino.conversions.cone_to_compas`. +* Fixed bug in `compas_rhino.conversions.cylinder_to_compas`. +* Fixed bug in `compas_rhino.scene.RhinoMeshObject.draw_vertexnormals` (scale not used). +* Fixed bug in `compas_rhino.scene.RhinoMeshObject.draw_facenormals` (scale not used). +* Changed scene object registration to stop printing messages. + +### Removed + +## [2.4.0] 2024-08-22 + +### Added + +* Added `compas.scene.Scene.redraw`. +* Added `compas.scene.Scene.context_objects` representing all objects drawn in the visualisation context by the scene. +* Added `compas.scene.Scene.clear_context` with optional `guids` to clear some or all objects from the visualisation context. +* Added `clear_scene` and `clear_context` parameters to `compas.scene.Scene.clear` to differentiate between removing objects from the scene internally or removing corresponding objects from the viz context, or both (default). +* Added `compas_rhino.conversions.extrusion_to_compas_box` as direct conversion of extrusion breps. + +### Changed + * Changed the `__str__` of `compas.geometry.Frame`, `compas.geometry.Plane`, `compas.geometry.Polygon`, `compas.geometry.Polyhedron`, `compas.geometry.Quaternion` to use a limited number of decimals (determined by `Tolerance.PRECISION`). Note: `__repr__` will instead maintain full precision. * Changed the `__str__` of `compas.geometry.Pointcloud` to print total number of points instead of the long list of points. Note: `__repr__` will still print all the points with full precision. * Fixed bug in `Pointcloud.from_box()`. +* Changed `compas.scene.MeshObject` to not use vertex coordinate caching because it is too fragile. +* Changed `compas_rhino.scene.RhinoMeshObject` to keep track of element-guid pairs in dicts. +* Changed `compas.scene.Scene._guids` to a default value of `[]`. +* Fixed bug due to missing import in `compas_rhino.scene.graphobject`. +* Changed `compas_rhino.scene.RhinoMeshObject.draw_vertexnormals` to use the same selection of vertices as `draw_vertices`. +* Changed `compas_rhino.scene.RhinoMeshObject.draw_vertexnormals` to use the corresponding vertex color if no color is specified. +* Changed `compas_rhino.scene.RhinoMeshObject.draw_facenormals` to use the same selection of vertices as `draw_faces`. +* Changed `compas_rhino.scene.RhinoMeshObject.draw_facenormals` to use the corresponding face color if no color is specified. ### Removed diff --git a/docs/userguide/cad.blender.rst b/docs/userguide/cad.blender.rst index 057803051928..1e0470f35454 100644 --- a/docs/userguide/cad.blender.rst +++ b/docs/userguide/cad.blender.rst @@ -10,7 +10,8 @@ and :mod:`compas_blender` provides functionality for converting COMPAS objects t .. note:: - These instructions are for the latest version of Blender (4.0) and for the current LTS versions (3.3 and 3.6). + These instructions are for the current LTS versions of Blender (3.3, 3.6 and 4.2). + Other versions are currently not supported. .. warning:: @@ -34,9 +35,9 @@ This procedure simply uses that Python installation and associated ``pip`` to in The location of the executable is different on different platforms. The default locations are: -* Windows: ``C:\Program Files\Blender Foundation\Blender 4.0\4.0\python\bin\python.exe`` -* macOS: ``/Applications/Blender.app/Contents/Resources/4.0/python/bin/python3.10`` -* Linux: ``/usr/share/blender/4.0/python/bin/python3.10`` (i think :) +* Windows: ``C:\Program Files\Blender Foundation\Blender 4.2\4.2\python\bin\python.exe`` +* macOS: ``/Applications/Blender.app/Contents/Resources/4.2/python/bin/python3.10`` +* Linux: ``/usr/share/blender/4.2/python/bin/python3.10`` (i think :) .. note:: @@ -53,7 +54,7 @@ Before installing `compas` with `pip`, it is highly recommended that you update .. code-block:: bash - $ /Applications/Blender.app/Contents/Resources/4.0/python/bin/python3.10 -m pip install --upgrade pip + $ /Applications/Blender.app/Contents/Resources/4.2/python/bin/python3.10 -m pip install --upgrade pip Install from PyPI @@ -63,7 +64,7 @@ For example on Mac: .. code-block:: bash - $ /Applications/Blender.app/Contents/Resources/4.0/python/bin/python3.10 -m pip install compas + $ /Applications/Blender.app/Contents/Resources/4.2/python/bin/python3.10 -m pip install compas Install from Source @@ -72,7 +73,7 @@ Install from Source .. code-block:: bash $ cd path/to/compas - $ /Applications/Blender.app/Contents/Resources/4.0/python/bin/python3.10 -m pip install -e . + $ /Applications/Blender.app/Contents/Resources/4.2/python/bin/python3.10 -m pip install -e . Using ``compas_blender.install`` @@ -95,7 +96,7 @@ Optionally, you can specify the version of Blender using the ``-v`` flag. .. code-block:: bash - $ python -m compas_blender.install -v 4.0 + $ python -m compas_blender.install -v 4.2 To remove all previously installed symlinks before installing new ones, use the ``--clean`` flag. @@ -111,7 +112,7 @@ To test if the installation was successful, you can run the following on the Ble >>> import compas >>> print(compas.__version__) -'2.0.0' +'2.4.0' Visualisation diff --git a/pyproject.toml b/pyproject.toml index 1252af9bb63e..0581a072f442 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,7 @@ doctest_optionflags = [ # ============================================================================ [tool.bumpversion] -current_version = "2.3.0" +current_version = "2.4.1" message = "Bump version to {new_version}" commit = true tag = true diff --git a/src/compas/__init__.py b/src/compas/__init__.py index 1d88bd58ccda..0b1839974685 100644 --- a/src/compas/__init__.py +++ b/src/compas/__init__.py @@ -20,7 +20,7 @@ __copyright__ = "Copyright 2014-2022 - ETH Zurich, Copyright 2023 - COMPAS Association" __license__ = "MIT License" __email__ = "tom.v.mele@gmail.com" -__version__ = "2.3.0" +__version__ = "2.4.1" HERE = compas._os.realpath(os.path.dirname(__file__)) diff --git a/src/compas/scene/meshobject.py b/src/compas/scene/meshobject.py index 33069b8edd29..c6904ef3cd3a 100644 --- a/src/compas/scene/meshobject.py +++ b/src/compas/scene/meshobject.py @@ -5,7 +5,6 @@ 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 from .sceneobject import SceneObject @@ -71,7 +70,6 @@ def __init__( ): # fmt: skip # type: (...) -> None super(MeshObject, self).__init__(**kwargs) # type: ignore - self._vertex_xyz = None self.show_vertices = show_vertices self.show_edges = show_edges self.show_faces = show_faces @@ -107,7 +105,6 @@ def mesh(self, mesh): # type: (compas.datastructures.Mesh) -> None self._item = mesh self._transformation = None - self._vertex_xyz = None @property def transformation(self): @@ -117,24 +114,8 @@ def transformation(self): @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.mesh: - if self._vertex_xyz is None: - points = self.mesh.vertices_attributes("xyz") - points = transform_points(points, self.worldtransformation) - self._vertex_xyz = dict(zip(self.mesh.vertices(), points)) - return self._vertex_xyz # type: ignore - - @vertex_xyz.setter - def vertex_xyz(self, vertex_xyz): - # type: (dict[int, list[float]]) -> None - self._vertex_xyz = vertex_xyz - def draw_vertices(self): """Draw the vertices of the mesh. diff --git a/src/compas/scene/scene.py b/src/compas/scene/scene.py index f9b0861a2b67..2e4dc253086f 100644 --- a/src/compas/scene/scene.py +++ b/src/compas/scene/scene.py @@ -76,6 +76,14 @@ def objects(self): # type: () -> list[SceneObject] return [node for node in self.nodes if not node.is_root] # type: ignore + @property + def context_objects(self): + # type: () -> list + guids = [] + for obj in self.objects: + guids += obj.guids + return guids + def add(self, item, parent=None, **kwargs): # type: (compas.geometry.Geometry | compas.datastructures.Datastructure, SceneObject | TreeNode | None, dict) -> SceneObject """Add an item to the scene. @@ -108,32 +116,82 @@ def add(self, item, parent=None, **kwargs): super(Scene, self).add(sceneobject, parent=parent) return sceneobject - def clear(self): - # type: () -> None - """Clear everything from the current context of the scene.""" + def clear_context(self, guids=None): + # type: (list | None) -> None + """Clear the visualisation context. + + Parameters + ---------- + guids : list, optional + The identifiers of the objects in the visualisation context. + + Returns + ------- + None + + Notes + ----- + If `guids=None`, this will clear all objects from the visualisation context. + For example, when used in Rhino, it will remove everything from the Rhino model. + This is equivalent to `compas_rhino.clear()`. + + If `guids` is a list, only those objects in the list will be removed. + + The method is used by `Scene.clear` to remove all objects previously drawn by the scene, + without removing other model objects. + + """ + clear(guids) + + def clear(self, clear_scene=True, clear_context=True): + # type: (bool, bool) -> None + """Clear the scene. + + Parameters + ---------- + clear_scene : bool, optional + If True, all scene objects will be removed from the scene tree. + clear_context : bool, optional + If True, all objects drawn by the scene in the visualisation context will be removed. + + Returns + ------- + None - clear() + Notes + ----- + To redraw the scene, without modifying any of the other objects in the visualisation context: - def clear_objects(self): - # type: () -> None - """Clear all objects inside the scene.""" + >>> scene.clear(clear_scene=False, clear_context=True) + >>> scene.draw() + """ guids = [] + for sceneobject in self.objects: guids += sceneobject.guids sceneobject._guids = None - clear(guids=guids) + + if clear_scene: + self.remove(sceneobject) + + if clear_context: + self.clear_context(guids) def draw(self): - """Draw the scene.""" + """Draw the scene. + + This will just draw all scene objects in the scene tree, + without making any modifications to the visualisation context. + For example, it will not remove any of the previously drawn objects. + + """ if not self.context: raise ValueError("No context detected.") before_draw() - self.clear_objects() - drawn_objects = [] for sceneobject in self.objects: if sceneobject.show: @@ -142,3 +200,14 @@ def draw(self): after_draw(drawn_objects) return drawn_objects + + def redraw(self): + """Redraw the scene. + + This removes all previously drawn objects from the visualisation context, + before drawing all scene objects in the scene tree. + + """ + + self.clear(clear_scene=False, clear_context=True) + self.draw() diff --git a/src/compas/scene/sceneobject.py b/src/compas/scene/sceneobject.py index 2a0f4c303833..3ca5f081da2e 100644 --- a/src/compas/scene/sceneobject.py +++ b/src/compas/scene/sceneobject.py @@ -111,7 +111,7 @@ def __init__( # which means that adding child objects will be added in context "None" self.context = context self._item = item - self._guids = None + self._guids = [] self._node = None self._frame = frame self._transformation = transformation diff --git a/src/compas_blender/__init__.py b/src/compas_blender/__init__.py index a93ef4fcfc73..6985154c0a6d 100644 --- a/src/compas_blender/__init__.py +++ b/src/compas_blender/__init__.py @@ -11,12 +11,12 @@ pass -__version__ = "2.3.0" +__version__ = "2.4.1" INSTALLABLE_PACKAGES = ["compas", "compas_blender"] -SUPPORTED_VERSIONS = ["3.3", "3.6", "4.0"] -DEFAULT_VERSION = "4.0" +SUPPORTED_VERSIONS = ["3.3", "3.6", "4.2"] +DEFAULT_VERSION = "4.2" INSTALLATION_ARGUMENTS = None @@ -200,6 +200,8 @@ def _get_default_blender_python_path(version): def _get_default_blender_python_path_mac(version): + if version == "4.2": + return "/Applications/Blender.app/Contents/Resources/{}/python/bin/python3.11".format(version) return "/Applications/Blender.app/Contents/Resources/{}/python/bin/python3.10".format(version) diff --git a/src/compas_blender/scene/__init__.py b/src/compas_blender/scene/__init__.py index 021a7716939f..4d6e4d315245 100644 --- a/src/compas_blender/scene/__init__.py +++ b/src/compas_blender/scene/__init__.py @@ -83,16 +83,16 @@ def register_scene_objects(): register(Torus, ShapeObject, context="Blender") register(Vector, VectorObject, context="Blender") register(VolMesh, VolMeshObject, context="Blender") - print("Blender Objects registered.") + + # print("Blender Objects registered.") __all__ = [ "BlenderSceneObject", - "shapeObject", + "ShapeObject", "BoxObject", "CircleObject", "CurveObject", - "CylinderObject", "FrameObject", "LineObject", "MeshObject", @@ -103,9 +103,7 @@ def register_scene_objects(): "PolygonObject", "PolyhedronObject", "PolylineObject", - "SphereObject", "SurfaceObject", - "TorusObject", "VectorObject", "VolMeshObject", ] diff --git a/src/compas_ghpython/__init__.py b/src/compas_ghpython/__init__.py index e3d9e3a93ba0..d75892f769a3 100644 --- a/src/compas_ghpython/__init__.py +++ b/src/compas_ghpython/__init__.py @@ -9,7 +9,7 @@ from compas_rhino import unload_modules # noqa: F401 -__version__ = "2.3.0" +__version__ = "2.4.1" if compas.is_rhino(): from .utilities import * # noqa: F401 F403 diff --git a/src/compas_ghpython/scene/__init__.py b/src/compas_ghpython/scene/__init__.py index 822d5220a37b..384804cd69a8 100644 --- a/src/compas_ghpython/scene/__init__.py +++ b/src/compas_ghpython/scene/__init__.py @@ -86,7 +86,8 @@ def register_scene_objects(): register(Vector, VectorObject, context="Grasshopper") register(VolMesh, VolMeshObject, context="Grasshopper") register(Brep, BrepObject, context="Grasshopper") - print("GH SceneObjects registered.") + + # print("GH SceneObjects registered.") __all__ = [ diff --git a/src/compas_ghpython/scene/meshobject.py b/src/compas_ghpython/scene/meshobject.py index 46f4aada7949..5988912511d2 100644 --- a/src/compas_ghpython/scene/meshobject.py +++ b/src/compas_ghpython/scene/meshobject.py @@ -51,44 +51,36 @@ def draw(self): if self.show_faces is True: vertexcolors = [] - if len(self.vertexcolor): - vertexcolors = [self.vertexcolor[vertex] for vertex in self.mesh.vertices()] + if len(self.vertexcolor): # type: ignore + vertexcolors = [self.vertexcolor[vertex] for vertex in self.mesh.vertices()] # type: ignore facecolors = [] - if len(self.facecolor): - facecolors = [self.facecolor[face] for face in self.mesh.faces()] - - color = None - if not vertexcolors and not facecolors: - color = self.color + if len(self.facecolor): # type: ignore + facecolors = [self.facecolor[face] for face in self.mesh.faces()] # type: ignore vertex_index = self.mesh.vertex_index() - vertex_xyz = self.vertex_xyz - vertices = [vertex_xyz[vertex] for vertex in self.mesh.vertices()] + vertices = [self.mesh.vertex_attributes(vertex, "xyz") 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, + color=self.color, vertexcolors=vertexcolors, facecolors=facecolors, disjoint=self.disjoint, ) - # geometry.Transform(conversions.transformation_to_rhino(self.worldtransformation)) + 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() + self.draw_faces() - if self.show_edges: - self._guids += self.draw_edges() + self.draw_vertices() + self.draw_edges() return self.guids @@ -104,9 +96,13 @@ def draw_vertices(self): vertices = list(self.mesh.vertices()) if self.show_vertices is True else self.show_vertices or [] + transformation = conversions.transformation_to_rhino(self.worldtransformation) + if vertices: for vertex in vertices: - points.append(conversions.point_to_rhino(self.vertex_xyz[vertex])) + geometry = conversions.point_to_rhino(self.mesh.vertex_attributes(vertex, "xyz")) + geometry.Transform(transformation) + points.append(geometry) return points @@ -122,9 +118,14 @@ def draw_edges(self): edges = list(self.mesh.edges()) if self.show_edges is True else self.show_edges or [] + transformation = conversions.transformation_to_rhino(self.worldtransformation) + if edges: for edge in edges: - lines.append(conversions.line_to_rhino((self.vertex_xyz[edge[0]], self.vertex_xyz[edge[1]]))) + line = self.mesh.edge_line(edge) + geometry = conversions.line_to_rhino(line) + geometry.Transform(transformation) + lines.append(geometry) return lines @@ -140,12 +141,17 @@ def draw_faces(self): faces = list(self.mesh.faces()) if self.show_faces is True else self.show_faces or [] + transformation = conversions.transformation_to_rhino(self.worldtransformation) + if faces: for face in faces: - color = self.facecolor[face] - vertices = [self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)] + color = self.facecolor[face] # type: ignore + vertices = [self.mesh.vertex_attributes(vertex, "xyz") 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], color=color)) + geometry = conversions.vertices_and_faces_to_rhino(vertices, [facet], color=color) + geometry.Transform(transformation) + meshes.append(geometry) return meshes diff --git a/src/compas_rhino/__init__.py b/src/compas_rhino/__init__.py index 769922013a6a..c52f1cb41be1 100644 --- a/src/compas_rhino/__init__.py +++ b/src/compas_rhino/__init__.py @@ -7,7 +7,7 @@ import compas import compas._os -__version__ = "2.3.0" +__version__ = "2.4.1" PURGE_ON_DELETE = True diff --git a/src/compas_rhino/conversions/breps.py b/src/compas_rhino/conversions/breps.py index 51ee2714e4f4..3be86027198b 100644 --- a/src/compas_rhino/conversions/breps.py +++ b/src/compas_rhino/conversions/breps.py @@ -12,6 +12,7 @@ from compas.tolerance import TOL from .exceptions import ConversionError +from .extrusions import extrusion_to_compas_box from .geometry import point_to_compas from .shapes import cone_to_compas from .shapes import cylinder_to_compas @@ -82,7 +83,7 @@ def brep_to_compas_box(brep): :class:`compas.geometry.Box` """ - raise NotImplementedError + return extrusion_to_compas_box(brep.Geometry) def brep_to_compas_cone(brep): diff --git a/src/compas_rhino/conversions/meshes.py b/src/compas_rhino/conversions/meshes.py index 82c89ed4ea56..deff5233ae03 100644 --- a/src/compas_rhino/conversions/meshes.py +++ b/src/compas_rhino/conversions/meshes.py @@ -231,7 +231,7 @@ def face_callback(face): def mesh_to_compas(rhinomesh, cls=None): - """Convert a Rhino mesh object to a COMPAS mesh. + """Convert a Rhino mesh to a COMPAS mesh. Parameters ---------- diff --git a/src/compas_rhino/conversions/shapes.py b/src/compas_rhino/conversions/shapes.py index 955568fbf6c5..320db85a7eec 100644 --- a/src/compas_rhino/conversions/shapes.py +++ b/src/compas_rhino/conversions/shapes.py @@ -6,11 +6,9 @@ import scriptcontext as sc # type: ignore from compas.geometry import Box -from compas.geometry import Circle from compas.geometry import Cone from compas.geometry import Cylinder from compas.geometry import Frame -from compas.geometry import Plane from compas.geometry import Sphere from compas.geometry import Torus @@ -19,11 +17,9 @@ # from .geometry import plane_to_rhino from .geometry import frame_to_rhino -from .geometry import plane_to_compas from .geometry import plane_to_compas_frame from .geometry import point_to_compas from .geometry import point_to_rhino -from .geometry import vector_to_compas # ============================================================================= # To Rhino @@ -253,8 +249,9 @@ def cone_to_compas(cone): :class:`compas.geometry.Cone` """ - plane = Plane(cone.BasePoint, vector_to_compas(cone.Plane.Normal).inverted()) - return Cone(Circle(plane, cone.Radius), cone.Height) + frame = plane_to_compas_frame(cone.Plane) + frame.point = point_to_compas(cone.BasePoint) # invert the z-axis? + return Cone(radius=cone.Radius, height=cone.Height, frame=frame) def cylinder_to_compas(cylinder): @@ -269,10 +266,10 @@ def cylinder_to_compas(cylinder): :class:`compas.geometry.Cylinder` """ - plane = plane_to_compas(cylinder.BasePlane) + frame = plane_to_compas_frame(cylinder.BasePlane) height = cylinder.TotalHeight - plane.point += plane.normal * (0.5 * height) - return Cylinder(Circle(plane, cylinder.Radius), height) + frame.point += frame.normal * (0.5 * height) + return Cylinder(radius=cylinder.Radius, height=height, frame=frame) def torus_to_compas(torus): diff --git a/src/compas_rhino/scene/__init__.py b/src/compas_rhino/scene/__init__.py index e5bc8a7b475d..cc243b8022fd 100644 --- a/src/compas_rhino/scene/__init__.py +++ b/src/compas_rhino/scene/__init__.py @@ -93,7 +93,8 @@ def register_scene_objects(): register(Curve, RhinoCurveObject, context="Rhino") register(Surface, RhinoSurfaceObject, context="Rhino") register(Brep, RhinoBrepObject, context="Rhino") - print("Rhino SceneObjects registered.") + + # print("Rhino SceneObjects registered.") __all__ = [ diff --git a/src/compas_rhino/scene/graphobject.py b/src/compas_rhino/scene/graphobject.py index 88f1e409966f..a761a559e36e 100644 --- a/src/compas_rhino/scene/graphobject.py +++ b/src/compas_rhino/scene/graphobject.py @@ -6,6 +6,7 @@ import scriptcontext as sc # type: ignore import compas_rhino +import compas_rhino.objects from compas.geometry import Line from compas.scene import GraphObject from compas_rhino.conversions import line_to_rhino diff --git a/src/compas_rhino/scene/meshobject.py b/src/compas_rhino/scene/meshobject.py index db04e6c9eb27..195b878013f9 100644 --- a/src/compas_rhino/scene/meshobject.py +++ b/src/compas_rhino/scene/meshobject.py @@ -8,13 +8,11 @@ import compas_rhino.objects from compas.colors import Color from compas.geometry import Line -from compas.geometry import Point from compas.geometry import centroid_points from compas.scene import MeshObject from compas_rhino.conversions import line_to_rhino from compas_rhino.conversions import point_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 @@ -56,16 +54,9 @@ def __init__(self, disjoint=False, vertexgroup=None, edgegroup=None, facegroup=N super(RhinoMeshObject, self).__init__(**kwargs) self.disjoint = disjoint self._guid_mesh = None - self._guids_faces = None - self._guids_edges = None - self._guids_vertices = None - self._guids_vertexnormals = None - self._guids_facenormals = None - self._guids_vertexlabels = None - self._guids_edgelabels = None - self._guids_facelabels = None - self._guids_spheres = None - self._guids_pipes = None + self._guid_face = {} + self._guid_edge = {} + self._guid_vertex = {} self.vertexgroup = vertexgroup self.edgegroup = edgegroup self.facegroup = facegroup @@ -92,7 +83,7 @@ def clear_vertices(self): None """ - compas_rhino.objects.delete_objects(self._guids_vertices, purge=True) + compas_rhino.objects.delete_objects(self._guid_vertex, purge=True) def clear_edges(self): """Delete all edges drawn by this scene object. @@ -102,7 +93,7 @@ def clear_edges(self): None """ - compas_rhino.objects.delete_objects(self._guids_edges, purge=True) + compas_rhino.objects.delete_objects(self._guid_edge, purge=True) def clear_faces(self): """Delete all faces drawn by this scene object. @@ -112,57 +103,7 @@ def clear_faces(self): None """ - compas_rhino.objects.delete_objects(self._guids_faces, purge=True) - - def clear_vertexnormals(self): - """Delete all vertex normals drawn by this scene object. - - Returns - ------- - None - - """ - compas_rhino.objects.delete_objects(self._guids_vertexnormals, purge=True) - - def clear_facenormals(self): - """Delete all face normals drawn by this scene object. - - Returns - ------- - None - - """ - compas_rhino.objects.delete_objects(self._guids_facenormals, purge=True) - - def clear_vertexlabels(self): - """Delete all vertex labels drawn by this scene object. - - Returns - ------- - None - - """ - compas_rhino.objects.delete_objects(self._guids_vertexlabels, purge=True) - - def clear_edgelabels(self): - """Delete all edge labels drawn by this scene object. - - Returns - ------- - None - - """ - compas_rhino.objects.delete_objects(self._guids_edgelabels, purge=True) - - def clear_facelabels(self): - """Delete all face labels drawn by this scene object. - - Returns - ------- - None - - """ - compas_rhino.objects.delete_objects(self._guids_facelabels, purge=True) + compas_rhino.objects.delete_objects(self._guid_face, purge=True) # ========================================================================== # draw @@ -193,17 +134,16 @@ def draw(self): attr = self.compile_attributes() vertexcolors = [] - if len(self.vertexcolor): - vertexcolors = [self.vertexcolor[vertex] for vertex in self.mesh.vertices()] + if len(self.vertexcolor): # type: ignore + vertexcolors = [self.vertexcolor[vertex] for vertex in self.mesh.vertices()] # type: ignore facecolors = [] - if len(self.facecolor): - facecolors = [self.facecolor[face] for face in self.mesh.faces()] + if len(self.facecolor): # type: ignore + facecolors = [self.facecolor[face] for face in self.mesh.faces()] # type: ignore vertex_index = self.mesh.vertex_index() - vertex_xyz = self.vertex_xyz - vertices = [vertex_xyz[vertex] for vertex in self.mesh.vertices()] + vertices = [self.mesh.vertex_attributes(vertex, "xyz") 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( @@ -215,7 +155,7 @@ def draw(self): 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: @@ -224,13 +164,10 @@ def draw(self): self._guids.append(self._guid_mesh) elif self.show_faces: - self._guids += self.draw_faces() + self.draw_faces() - if self.show_vertices: - self._guids += self.draw_vertices() - - if self.show_edges: - self._guids += self.draw_edges() + self.draw_vertices() + self.draw_edges() return self.guids @@ -247,15 +184,18 @@ def draw_vertices(self): vertices = list(self.mesh.vertices()) if self.show_vertices is True else self.show_vertices or [] + transformation = transformation_to_rhino(self.worldtransformation) + if vertices: for vertex in vertices: name = "{}.vertex.{}".format(self.mesh.name, vertex) - color = self.vertexcolor[vertex] + color = self.vertexcolor[vertex] # type: ignore attr = self.compile_attributes(name=name, color=color) - point = point_to_rhino(self.vertex_xyz[vertex]) + geometry = point_to_rhino(self.mesh.vertex_attributes(vertex, "xyz")) + geometry.Transform(transformation) - guid = sc.doc.Objects.AddPoint(point, attr) + guid = sc.doc.Objects.AddPoint(geometry, attr) guids.append(guid) if guids: @@ -264,7 +204,9 @@ def draw_vertices(self): elif self.group: self.add_to_group(self.group, guids) - self._guids_vertices = guids + self._guid_vertex = dict(zip(guids, vertices)) + + self._guids += guids return guids def draw_edges(self): @@ -280,15 +222,20 @@ def draw_edges(self): edges = list(self.mesh.edges()) if self.show_edges is True else self.show_edges or [] + transformation = transformation_to_rhino(self.worldtransformation) + if edges: for edge in edges: name = "{}.edge.{}-{}".format(self.mesh.name, *edge) - color = self.edgecolor[edge] + color = self.edgecolor[edge] # type: ignore attr = self.compile_attributes(name=name, color=color) - line = Line(self.vertex_xyz[edge[0]], self.vertex_xyz[edge[1]]) + line = self.mesh.edge_line(edge) - guid = sc.doc.Objects.AddLine(line_to_rhino(line), attr) + geometry = line_to_rhino(line) + geometry.Transform(transformation) + + guid = sc.doc.Objects.AddLine(geometry, attr) guids.append(guid) if guids: @@ -297,7 +244,9 @@ def draw_edges(self): elif self.group: self.add_to_group(self.group, guids) - self._guids_edges = guids + self._guid_edge = dict(zip(guids, edges)) + + self._guids += guids return guids def draw_faces(self): @@ -313,17 +262,22 @@ def draw_faces(self): faces = list(self.mesh.faces()) if self.show_faces is True else self.show_faces or [] + transformation = transformation_to_rhino(self.worldtransformation) + if faces: for face in faces: name = "{}.face.{}".format(self.mesh.name, face) - color = self.facecolor[face] + color = self.facecolor[face] # type: ignore attr = self.compile_attributes(name=name, color=color) - vertices = [self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)] # type: ignore + vertices = [self.mesh.vertex_attributes(vertex, "xyz") 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) + geometry = vertices_and_faces_to_rhino(vertices, [facet]) + geometry.Transform(transformation) + + guid = sc.doc.Objects.AddMesh(geometry, attr) guids.append(guid) if guids: @@ -332,7 +286,9 @@ def draw_faces(self): elif self.group: self.add_to_group(self.group, guids) - self._guids_faces = guids + self._guid_face = dict(zip(guids, faces)) + + self._guids += guids return guids # ========================================================================== @@ -365,14 +321,17 @@ def draw_vertexlabels(self, text, color=None, group=None, fontheight=10, fontfac self.vertexcolor = color + transformation = transformation_to_rhino(self.worldtransformation) + for vertex in text: name = "{}.vertex.{}.label".format(self.mesh.name, vertex) # type: ignore - color = self.vertexcolor[vertex] + color = self.vertexcolor[vertex] # type: ignore attr = self.compile_attributes(name=name, color=color) - point = point_to_rhino(self.vertex_xyz[vertex]) + location = point_to_rhino(self.mesh.vertex_attributes(vertex, "xyz")) + location.Transform(transformation) - dot = Rhino.Geometry.TextDot(str(text[vertex]), point) # type: ignore + dot = Rhino.Geometry.TextDot(str(text[vertex]), location) # type: ignore dot.FontHeight = fontheight dot.FontFace = fontface @@ -383,7 +342,7 @@ def draw_vertexlabels(self, text, color=None, group=None, fontheight=10, fontfac self.add_to_group(group, guids) self._guids_vertexlabels = guids - + self._guids += guids return guids def draw_edgelabels(self, text, color=None, group=None, fontheight=10, fontface="Arial Regular"): @@ -412,15 +371,18 @@ def draw_edgelabels(self, text, color=None, group=None, fontheight=10, fontface= self.edgecolor = color + transformation = transformation_to_rhino(self.worldtransformation) + for edge in text: name = "{}.edge.{}-{}".format(self.mesh.name, *edge) # type: ignore - color = self.edgecolor[edge] + color = self.edgecolor[edge] # type: ignore attr = self.compile_attributes(name="{}.label".format(name), color=color) - line = Line(self.vertex_xyz[edge[0]], self.vertex_xyz[edge[1]]) - point = point_to_rhino(line.midpoint) + line = self.mesh.edge_line(edge) + location = point_to_rhino(line.midpoint) + location.Transform(transformation) - dot = Rhino.Geometry.TextDot(str(text[edge]), point) # type: ignore + dot = Rhino.Geometry.TextDot(str(text[edge]), location) # type: ignore dot.FontHeight = fontheight dot.FontFace = fontface @@ -431,7 +393,7 @@ def draw_edgelabels(self, text, color=None, group=None, fontheight=10, fontface= self.add_to_group(group, guids) self._guids_edgelabels = guids - + self._guids += guids return guids def draw_facelabels(self, text, color=None, group=None, fontheight=10, fontface="Arial Regular"): @@ -458,15 +420,18 @@ def draw_facelabels(self, text, color=None, group=None, fontheight=10, fontface= """ guids = [] + transformation = transformation_to_rhino(self.worldtransformation) + for face in text: name = "{}.face.{}.label".format(self.mesh.name, face) # type: ignore - color = self.facecolor[face] + color = self.facecolor[face] # type: ignore attr = self.compile_attributes(name=name, color=color) - points = [self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)] # type: ignore - point = point_to_rhino(centroid_points(points)) + points = [self.mesh.vertex_attributes(vertex, "xyz") for vertex in self.mesh.face_vertices(face)] # type: ignore + location = point_to_rhino(centroid_points(points)) + location.Transform(transformation) - dot = Rhino.Geometry.TextDot(str(text[face]), point) # type: ignore + dot = Rhino.Geometry.TextDot(str(text[face]), location) # type: ignore dot.FontHeight = fontheight dot.FontFace = fontface @@ -477,23 +442,21 @@ def draw_facelabels(self, text, color=None, group=None, fontheight=10, fontface= self.add_to_group(group, guids) self._guids_facelabels = guids - + self._guids += guids return guids # ========================================================================== # draw normals # ========================================================================== - def draw_vertexnormals(self, vertices=None, color=(0, 255, 0), scale=1.0, group=None): + def draw_vertexnormals(self, color=None, scale=1.0, group=None): """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 : tuple[int, int, int] | tuple[float, float, float] | :class:`compas.colors.Color`, optional The color specification of the normal vectors. + If no color is specified, the color of the corresponding vertex is used. scale : float, optional Scale factor for the vertex normals. group : str, optional @@ -507,35 +470,40 @@ def draw_vertexnormals(self, vertices=None, color=(0, 255, 0), scale=1.0, group= """ guids = [] + vertices = list(self.mesh.vertices()) if self.show_vertices is True else self.show_vertices or [] + transformation = transformation_to_rhino(self.worldtransformation) + color = Color.coerce(color) - for vertex in vertices or self.mesh.vertices(): # type: ignore + for vertex in vertices: name = "{}.vertex.{}.normal".format(self.mesh.name, vertex) # type: ignore - attr = self.compile_attributes(name=name, color=color) + attr = self.compile_attributes(name=name, color=color or self.vertexcolor[vertex]) # type: ignore - point = Point(*self.vertex_xyz[vertex]) + point = self.mesh.vertex_point(vertex) normal = self.mesh.vertex_normal(vertex) # type: ignore + line = Line.from_point_and_vector(point, normal * scale) + + geometry = line_to_rhino(line) + geometry.Transform(transformation) - guid = sc.doc.Objects.AddLine(point_to_rhino(point), point_to_rhino(point + normal * scale), attr) + guid = sc.doc.Objects.AddLine(geometry, attr) guids.append(guid) if group: self.add_to_group(group, guids) self._guids_vertexnormals = guids - + self._guids += guids return guids - def draw_facenormals(self, faces=None, color=(0, 255, 255), scale=1.0, group=None): + def draw_facenormals(self, color=None, scale=1.0, group=None): """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 : tuple[int, int, int] | tuple[float, float, float] | :class:`compas.colors.Color`, optional The color specification of the normal vectors. + If no color is specified, the color of the corresponding face is used. scale : float, optional Scale factor for the face normals. group : str, optional @@ -549,16 +517,23 @@ def draw_facenormals(self, faces=None, color=(0, 255, 255), scale=1.0, group=Non """ guids = [] + faces = list(self.mesh.faces()) if self.show_faces is True else self.show_faces or [] + transformation = transformation_to_rhino(self.worldtransformation) + color = Color.coerce(color) - for face in faces or self.mesh.faces(): # type: ignore + for face in faces: name = "{}.face.{}.normal".format(self.mesh.name, face) # type: ignore - attr = self.compile_attributes(name=name, color=color) + attr = self.compile_attributes(name=name, color=color or self.facecolor[face]) # type: ignore + + point = self.mesh.face_centroid(face) + normal = self.mesh.face_normal(face) + line = Line.from_point_and_vector(point, normal * scale) - point = Point(*centroid_points([self.vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)])) # type: ignore - normal = self.mesh.face_normal(face) # type: ignore + geometry = line_to_rhino(line) + geometry.Transform(transformation) - guid = sc.doc.Objects.AddLine(point_to_rhino(point), point_to_rhino(point + normal * scale), attr) + guid = sc.doc.Objects.AddLine(geometry, attr) guids.append(guid) if group: diff --git a/tests/compas/geometry/test_polyhedron.py b/tests/compas/geometry/test_polyhedron.py index 18d098b88f81..8b673012421a 100644 --- a/tests/compas/geometry/test_polyhedron.py +++ b/tests/compas/geometry/test_polyhedron.py @@ -1,8 +1,3 @@ -import pytest -import json -import compas -from random import random -from compas.geometry import Point from compas.geometry import Polyhedron from compas.itertools import pairwise diff --git a/tests/compas/scene/test_scene.py b/tests/compas/scene/test_scene.py index 678e4c3f235c..29016749c36f 100644 --- a/tests/compas/scene/test_scene.py +++ b/tests/compas/scene/test_scene.py @@ -99,3 +99,15 @@ def test_sceneobject_transform(): sceneobj3.worldtransformation == sceneobj1.frame.to_transformation() * sceneobj2.frame.to_transformation() * sceneobj3.frame.to_transformation() * sceneobj3.transformation ) + + def test_scene_clear(): + scene = Scene() + sceneobj1 = scene.add(Box()) + sceneobj2 = scene.add(Box(), parent=sceneobj1) + sceneobj3 = scene.add(Box(), parent=sceneobj2) # noqa: F841 + + assert len(scene.objects) == 3 + + scene.clear(clear_context=False, clear_scene=True) + + assert len(scene.objects) == 0