diff --git a/CHANGELOG.md b/CHANGELOG.md index ef4a68703ecc..b12451afb117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +* Fixed support for `compas_gpython` in Rhino 8 Grasshopper CPython components. + ### Removed +* Removed deprecated module `compas_ghpython.utilities`. For drawing functions, use `compas_ghpython.drawing` directly. ## [2.4.2] 2024-09-17 diff --git a/src/compas/plugins.py b/src/compas/plugins.py index 131fc9a3bc4f..96210c9482cd 100644 --- a/src/compas/plugins.py +++ b/src/compas/plugins.py @@ -22,6 +22,8 @@ import pkgutil import threading +import compas + __all__ = [ "pluggable", "plugin", @@ -33,6 +35,17 @@ "PluginDefaultNotAvailableError", ] +if compas.RHINO: + import System # type: ignore + + DotNetException = System.Exception +else: + + class DotNetException(BaseException): + """Ensures DotNetException is always a valid exception class. Even when not in IPY.""" + + pass + class PluginNotInstalledError(Exception): """Exception raised when an extension point is invoked but no plugin is available.""" @@ -401,7 +414,6 @@ def try_import(self, module_name): If importable, it returns the imported module, otherwise ``None``. """ module = None - try: module = __import__(module_name, fromlist=["__name__"], level=0) self._cache[module_name] = True @@ -409,7 +421,8 @@ def try_import(self, module_name): # There are two types of possible failure modes: # 1) cannot be imported, or # 2) is a python 3 module and we're in IPY, which causes a SyntaxError - except (ImportError, SyntaxError): + # 3) in Rhino8 we may get a nasty DotNet Exception when trying to import a module + except (ImportError, SyntaxError, DotNetException): self._cache[module_name] = False return module diff --git a/src/compas_ghpython/__init__.py b/src/compas_ghpython/__init__.py index cbb14b4fe133..2f3cc7d29a2b 100644 --- a/src/compas_ghpython/__init__.py +++ b/src/compas_ghpython/__init__.py @@ -11,9 +11,6 @@ __version__ = "2.4.2" -if compas.is_rhino(): - from .utilities import * # noqa: F401 F403 - __all__ = [ "get_grasshopper_managedplugin_path", "get_grasshopper_library_path", diff --git a/src/compas_ghpython/drawing.py b/src/compas_ghpython/drawing.py index 5bf971a251f4..5a8d43a078e9 100644 --- a/src/compas_ghpython/drawing.py +++ b/src/compas_ghpython/drawing.py @@ -20,8 +20,8 @@ from Rhino.Geometry import Vector3f from compas.geometry import centroid_points +from compas.geometry import centroid_polygon from compas.itertools import pairwise -from compas_rhino.drawing import _face_to_max_quad try: from Rhino.Geometry import MeshNgon @@ -31,6 +31,17 @@ TOL = sc.doc.ModelAbsoluteTolerance +def _face_to_max_quad(points, face): + faces = [] + c = len(points) + points.append(centroid_polygon(points)) + for i in range(-1, len(face) - 1): + a = face[i] + b = face[i + 1] + faces.append([c, a, b, b]) + return faces + + def draw_frame(frame): """Draw a frame.""" pt = Point3d(*iter(frame.point)) diff --git a/src/compas_ghpython/scene/__init__.py b/src/compas_ghpython/scene/__init__.py index 384804cd69a8..536a675c47b5 100644 --- a/src/compas_ghpython/scene/__init__.py +++ b/src/compas_ghpython/scene/__init__.py @@ -87,7 +87,7 @@ def register_scene_objects(): 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 5988912511d2..b6f82ff88e00 100644 --- a/src/compas_ghpython/scene/meshobject.py +++ b/src/compas_ghpython/scene/meshobject.py @@ -4,11 +4,20 @@ from compas.scene import MeshObject as BaseMeshObject from compas_rhino import conversions -from compas_rhino.scene.helpers import ngon from .sceneobject import GHSceneObject +def _create_ngon(vertex_count): + if vertex_count < 3: + return + if vertex_count == 3: + return [0, 1, 2] + if vertex_count == 4: + return [0, 1, 2, 3] + return list(range(vertex_count)) + + class MeshObject(GHSceneObject, BaseMeshObject): """Scene object for drawing mesh data structures. @@ -147,7 +156,7 @@ def draw_faces(self): for face in faces: 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)) + facet = _create_ngon(len(vertices)) if facet: geometry = conversions.vertices_and_faces_to_rhino(vertices, [facet], color=color) diff --git a/src/compas_ghpython/scene/volmeshobject.py b/src/compas_ghpython/scene/volmeshobject.py index fdc30a2e04b3..2393e2155de6 100644 --- a/src/compas_ghpython/scene/volmeshobject.py +++ b/src/compas_ghpython/scene/volmeshobject.py @@ -4,11 +4,20 @@ from compas.scene import VolMeshObject as BaseVolMeshObject from compas_rhino import conversions -from compas_rhino.scene.helpers import ngon from .sceneobject import GHSceneObject +def _create_ngon(vertex_count): + if vertex_count < 3: + return + if vertex_count == 3: + return [0, 1, 2] + if vertex_count == 4: + return [0, 1, 2, 3] + return list(range(vertex_count)) + + class VolMeshObject(GHSceneObject, BaseVolMeshObject): """Scene object for drawing volmesh data structures.""" @@ -84,7 +93,7 @@ def draw_faces(self): for face in faces: color = self.facecolor[face] vertices = [self.vertex_xyz[vertex] for vertex in self.volmesh.face_vertices(face)] - facet = ngon(len(vertices)) + facet = _create_ngon(len(vertices)) if facet: meshes.append(conversions.vertices_and_faces_to_rhino(vertices, [facet], color=color)) diff --git a/src/compas_ghpython/utilities/__init__.py b/src/compas_ghpython/utilities/__init__.py deleted file mode 100644 index 3096b7bc87ac..000000000000 --- a/src/compas_ghpython/utilities/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import absolute_import -from warnings import warn - -from compas_ghpython.drawing import ( - draw_frame, - draw_points, - draw_lines, - draw_geodesics, - draw_polylines, - draw_faces, - draw_cylinders, - draw_pipes, - draw_spheres, - draw_mesh, - draw_graph, - draw_circles, - draw_brep, -) - -__all__ = [ - "draw_frame", - "draw_points", - "draw_lines", - "draw_geodesics", - "draw_polylines", - "draw_faces", - "draw_cylinders", - "draw_pipes", - "draw_spheres", - "draw_mesh", - "draw_graph", - "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/tests/compas/test_plugins.py b/tests/compas/test_plugins.py index e0949b4c2aa6..1b6ae4170d02 100644 --- a/tests/compas/test_plugins.py +++ b/tests/compas/test_plugins.py @@ -1,7 +1,7 @@ from abc import abstractmethod import pytest - +import compas from compas.plugins import IncompletePluginImplError from compas.plugins import PluginValidator @@ -36,3 +36,38 @@ def test_ensure_implementations_fails_with_incomplete_impl(): def test_ensure_implementations_with_valid_impl(): PluginValidator.ensure_implementations(CompleteImpl) + + +def test_dot_net_exception_with_rhino(): + if not compas.RHINO: + return + + from compas.plugins import DotNetException + + assert DotNetException is not None + + import System + + assert DotNetException == System.Exception + + +def test_dot_net_exception_without_rhino(): + if compas.RHINO: + return + + from compas.plugins import DotNetException + + assert DotNetException is not None + assert issubclass(DotNetException, BaseException) + + +def test_importer_fail_silently(): + from compas.plugins import Importer + + importer = Importer() + + is_importable = importer.check_importable("compas") + assert is_importable + + is_importable = importer.check_importable("module_which_does_not_exist") + assert not is_importable