From c41b6389fd08959599e0c914e2e7a4834b46f599 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 2 May 2024 12:01:40 +0200 Subject: [PATCH 01/28] added plugin for Surface.from_plane --- src/compas_rhino/geometry/surfaces/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/compas_rhino/geometry/surfaces/__init__.py b/src/compas_rhino/geometry/surfaces/__init__.py index 30081543e44..d82b944de18 100644 --- a/src/compas_rhino/geometry/surfaces/__init__.py +++ b/src/compas_rhino/geometry/surfaces/__init__.py @@ -13,6 +13,11 @@ def new_surface(cls, *args, **kwargs): return surface +@plugin(category="factories", requires=["Rhino"]) +def new_surface_from_plane(cls, *args, **kwargs): + return RhinoSurface.from_plane(*args, **kwargs) + + @plugin(category="factories", requires=["Rhino"]) def new_nurbssurface(cls, *args, **kwargs): surface = super(NurbsSurface, cls).__new__(cls) From 1d0b2c6ed98936a034acda9ec9b34a5c2d4f9d6a Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 2 May 2024 12:03:02 +0200 Subject: [PATCH 02/28] made argument `box` optional in `RhinoSurface.from_plane` --- src/compas_rhino/geometry/surfaces/surface.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index 36fa127a0fa..b6636cdebc2 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -168,13 +168,15 @@ def from_rhino(cls, rhino_surface): return curve @classmethod - def from_plane(cls, plane, box): + def from_plane(cls, plane, box=None): """Construct a surface from a plane. Parameters ---------- plane : :class:`compas.geometry.Plane` The plane. + box : :class:`compas.geometry.Box`, optional + A box that bounds the surface. Returns ------- @@ -182,8 +184,13 @@ def from_plane(cls, plane, box): """ plane = plane_to_rhino(plane) - box = Rhino.Geometry.BoundingBox(box.xmin, box.ymin, box.zmin, box.xmax, box.ymax, box.zmax) - rhino_surface = Rhino.Geometry.PlaneSurface.CreateThroughBox(plane, box) + if box: + box = Rhino.Geometry.BoundingBox(box.xmin, box.ymin, box.zmin, box.xmax, box.ymax, box.zmax) + rhino_surface = Rhino.Geometry.PlaneSurface.CreateThroughBox(plane, box) + else: + rhino_surface = Rhino.Geometry.PlaneSurface( + plane, Rhino.Geometry.Interval(0, 1), Rhino.Geometry.Interval(0, 1) + ) return cls.from_rhino(rhino_surface) @classmethod From b2a18e3c5ebc54f75706538a77598c3d691ec979 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 2 May 2024 15:13:50 +0200 Subject: [PATCH 03/28] added `NurbsSurface.from_plane` --- src/compas/geometry/surfaces/nurbs.py | 9 +++++++ src/compas_rhino/geometry/surfaces/nurbs.py | 27 +++++++++++++++++++ src/compas_rhino/geometry/surfaces/surface.py | 4 +-- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/compas/geometry/surfaces/nurbs.py b/src/compas/geometry/surfaces/nurbs.py index 23f7d9a8f86..bb447176914 100644 --- a/src/compas/geometry/surfaces/nurbs.py +++ b/src/compas/geometry/surfaces/nurbs.py @@ -26,6 +26,11 @@ def new_nurbssurface_from_parameters(cls, *args, **kwargs): raise PluginNotInstalledError +@pluggable(category="factories") +def new_nurbssurface_from_plane(cls, *args, **kwargs): + raise PluginNotInstalledError + + @pluggable(category="factories") def new_nurbssurface_from_points(cls, *args, **kwargs): raise PluginNotInstalledError @@ -350,6 +355,10 @@ def from_fill(cls, curve1, curve2, curve3=None, curve4=None, style="stretch"): """ return new_nurbssurface_from_fill(cls, curve1, curve2, curve3, curve4, style) + @classmethod + def from_plane(cls, plane, u_degree=1, v_degree=1): + return new_nurbssurface_from_plane(cls, plane, u_degree, v_degree) + # ============================================================================== # Conversions # ============================================================================== diff --git a/src/compas_rhino/geometry/surfaces/nurbs.py b/src/compas_rhino/geometry/surfaces/nurbs.py index 037263cff99..1498e7e45a9 100644 --- a/src/compas_rhino/geometry/surfaces/nurbs.py +++ b/src/compas_rhino/geometry/surfaces/nurbs.py @@ -10,6 +10,7 @@ from compas.geometry import Point from compas.geometry import knots_and_mults_to_knotvector from compas.itertools import flatten +from compas_rhino.conversions import plane_to_rhino from compas_rhino.conversions import point_to_compas from compas_rhino.conversions import point_to_rhino @@ -361,6 +362,32 @@ def from_fill(cls, curve1, curve2): surface.rhino_surface = Rhino.Geometry.NurbsSurface.CreateRuledSurface(curve1, curve2) return surface + @classmethod + def from_plane(cls, plane, u_degree=1, v_degree=1): + """Construct a NURBS surface from a plane. + + Parameters + ---------- + plane : :class:`compas.geometry.Plane` + + Returns + ------- + :class:`compas_rhino.geometry.RhinoNurbsSurface` + + """ + plane = plane_to_rhino(plane) + surface = cls() + surface.rhino_surface = Rhino.Geometry.NurbsSurface.CreateFromPlane( + plane, + Rhino.Geometry.Interval(0, 1), + Rhino.Geometry.Interval(0, 1), + u_degree, + v_degree, + u_degree + 1, + v_degree + 1, + ) + return surface + # ============================================================================== # Conversions # ============================================================================== diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index b6636cdebc2..ee3b54919db 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -188,9 +188,7 @@ def from_plane(cls, plane, box=None): box = Rhino.Geometry.BoundingBox(box.xmin, box.ymin, box.zmin, box.xmax, box.ymax, box.zmax) rhino_surface = Rhino.Geometry.PlaneSurface.CreateThroughBox(plane, box) else: - rhino_surface = Rhino.Geometry.PlaneSurface( - plane, Rhino.Geometry.Interval(0, 1), Rhino.Geometry.Interval(0, 1) - ) + rhino_surface = Rhino.Geometry.PlaneSurface(plane, Rhino.Geometry.Interval(0, 1), Rhino.Geometry.Interval(0, 1)) return cls.from_rhino(rhino_surface) @classmethod From 4dd4294e9b143565818565d80a121a8f1cd8a4d3 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Fri, 21 Jun 2024 23:10:39 +0200 Subject: [PATCH 04/28] surface domain instead of box --- src/compas_rhino/geometry/surfaces/surface.py | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index ee3b54919db..3ef9714c317 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -4,7 +4,13 @@ import Rhino.Geometry # type: ignore +import compas + +if not compas.IPY: + from typing import Tuple + from compas.geometry import Surface +from compas.geometry import Plane from compas_rhino.conversions import box_to_compas from compas_rhino.conversions import cylinder_to_rhino from compas_rhino.conversions import frame_to_rhino_plane @@ -168,15 +174,24 @@ def from_rhino(cls, rhino_surface): return curve @classmethod - def from_plane(cls, plane, box=None): + def from_plane(cls, plane, u_domain, v_domain): + # type: (Plane, Tuple[float, float], Tuple[float, float]) -> RhinoSurface """Construct a surface from a plane. Parameters ---------- plane : :class:`compas.geometry.Plane` The plane. - box : :class:`compas.geometry.Box`, optional - A box that bounds the surface. + u_domain : tuple(float, float) + The parametric domain of the U parameter. u_domain[0] => u_domain[1]. + v_domain : tuple(float, float) + The parametric domain of the V parameter. v_domain[0] => v_domain[1]. + + Note + ---- + While the plane's origin is its center, the surface's parametric origin is at the surface's corner. + For the plane to overlap with the surface, the plane's origin should be first shifted by half it's domain. + Alternatively, the surface's domain can be adjusted to match the plane's origin. Returns ------- @@ -184,11 +199,7 @@ def from_plane(cls, plane, box=None): """ plane = plane_to_rhino(plane) - if box: - box = Rhino.Geometry.BoundingBox(box.xmin, box.ymin, box.zmin, box.xmax, box.ymax, box.zmax) - rhino_surface = Rhino.Geometry.PlaneSurface.CreateThroughBox(plane, box) - else: - rhino_surface = Rhino.Geometry.PlaneSurface(plane, Rhino.Geometry.Interval(0, 1), Rhino.Geometry.Interval(0, 1)) + rhino_surface = Rhino.Geometry.PlaneSurface(plane, Rhino.Geometry.Interval(*u_domain), Rhino.Geometry.Interval(*v_domain)) return cls.from_rhino(rhino_surface) @classmethod From 4354d35bb18e2e610d2b3875a0c71d4ccbf77494 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Fri, 21 Jun 2024 23:13:47 +0200 Subject: [PATCH 05/28] user specifies domain in RhinoNurbsSurface.from_plane --- src/compas_rhino/geometry/surfaces/nurbs.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/nurbs.py b/src/compas_rhino/geometry/surfaces/nurbs.py index 1498e7e45a9..15244d81c88 100644 --- a/src/compas_rhino/geometry/surfaces/nurbs.py +++ b/src/compas_rhino/geometry/surfaces/nurbs.py @@ -363,12 +363,25 @@ def from_fill(cls, curve1, curve2): return surface @classmethod - def from_plane(cls, plane, u_degree=1, v_degree=1): + def from_plane(cls, plane, u_degree=1, v_degree=1, u_domain=(0, 1), v_domain=(0, 1)): """Construct a NURBS surface from a plane. Parameters ---------- plane : :class:`compas.geometry.Plane` + The plane from which to construct the surface. + u_degree : int, optional + The degree of the surface in the U direction. + Default is ``1``. + v_degree : int, optional + The degree of the surface in the V direction. + Default is ``1``. + u_domain : tuple(float), optional + The domain of the surface in the U direction. + Default is ``(0, 1)``. + v_domain : tuple(float), optional + The domain of the surface in the V direction. + Default is ``(0, 1)``. Returns ------- @@ -379,8 +392,8 @@ def from_plane(cls, plane, u_degree=1, v_degree=1): surface = cls() surface.rhino_surface = Rhino.Geometry.NurbsSurface.CreateFromPlane( plane, - Rhino.Geometry.Interval(0, 1), - Rhino.Geometry.Interval(0, 1), + Rhino.Geometry.Interval(*u_domain), + Rhino.Geometry.Interval(*v_domain), u_degree, v_degree, u_degree + 1, From 605a95e02aca122de45f4c1caa6f3b2fa8db8bc0 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Fri, 21 Jun 2024 23:15:16 +0200 Subject: [PATCH 06/28] default domains --- src/compas_rhino/geometry/surfaces/nurbs.py | 2 +- src/compas_rhino/geometry/surfaces/surface.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/nurbs.py b/src/compas_rhino/geometry/surfaces/nurbs.py index 15244d81c88..d4e3dfef646 100644 --- a/src/compas_rhino/geometry/surfaces/nurbs.py +++ b/src/compas_rhino/geometry/surfaces/nurbs.py @@ -363,7 +363,7 @@ def from_fill(cls, curve1, curve2): return surface @classmethod - def from_plane(cls, plane, u_degree=1, v_degree=1, u_domain=(0, 1), v_domain=(0, 1)): + def from_plane(cls, plane, u_degree=1, v_degree=1, u_domain=(0.0, 1.0), v_domain=(0.0, 1.0)): """Construct a NURBS surface from a plane. Parameters diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index 3ef9714c317..d08fa58aede 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -174,7 +174,7 @@ def from_rhino(cls, rhino_surface): return curve @classmethod - def from_plane(cls, plane, u_domain, v_domain): + def from_plane(cls, plane, u_domain=(0.0, 1.0), v_domain=(0.0, 1.0)): # type: (Plane, Tuple[float, float], Tuple[float, float]) -> RhinoSurface """Construct a surface from a plane. @@ -182,10 +182,12 @@ def from_plane(cls, plane, u_domain, v_domain): ---------- plane : :class:`compas.geometry.Plane` The plane. - u_domain : tuple(float, float) + u_domain : tuple(float, float), optional The parametric domain of the U parameter. u_domain[0] => u_domain[1]. - v_domain : tuple(float, float) + Default is ``(0.0, 1.0)``. + v_domain : tuple(float, float), optional The parametric domain of the V parameter. v_domain[0] => v_domain[1]. + Default is ``(0.0, 1.0)``. Note ---- From 7249f1ef41fd8ef04765bb8cd306dce53805a172 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Fri, 21 Jun 2024 23:17:24 +0200 Subject: [PATCH 07/28] noqa on typing imports --- src/compas_rhino/geometry/surfaces/surface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index d08fa58aede..de221a9104b 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -7,10 +7,10 @@ import compas if not compas.IPY: - from typing import Tuple + from typing import Tuple # noqa: F401 +from compas.geometry import Plane # noqa: F401 from compas.geometry import Surface -from compas.geometry import Plane from compas_rhino.conversions import box_to_compas from compas_rhino.conversions import cylinder_to_rhino from compas_rhino.conversions import frame_to_rhino_plane From 4242ee31cf6fe1d7c028dd32147912608309254d Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Sat, 22 Jun 2024 00:42:06 +0200 Subject: [PATCH 08/28] added from_native and native_surface to Surface --- src/compas/geometry/surfaces/surface.py | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/compas/geometry/surfaces/surface.py b/src/compas/geometry/surfaces/surface.py index b8b77ac75a5..a5d0e52fb75 100644 --- a/src/compas/geometry/surfaces/surface.py +++ b/src/compas/geometry/surfaces/surface.py @@ -19,6 +19,11 @@ def new_surface(cls, *args, **kwargs): return surface +@pluggable(category="factories") +def from_native(cls, *args, **kwargs): + raise NotImplementedError + + @pluggable(category="factories") def new_surface_from_plane(cls, *args, **kwargs): raise NotImplementedError @@ -47,6 +52,8 @@ class Surface(Geometry): Flag indicating if the surface is periodic in the U direction. is_periodic_v : bool, read-only Flag indicating if the surface is periodic in the V direction. + native_surface : Any, read-only + The CAD native surface object. """ @@ -95,6 +102,10 @@ def transformation(self): self._transformation = Transformation.from_frame(self.frame) return self._transformation + @property + def native_surface(self): + raise NotImplementedError + @property def point(self): if not self._point: @@ -197,6 +208,22 @@ def from_plane(cls, plane, *args, **kwargs): """ return new_surface_from_plane(cls, plane, *args, **kwargs) + @classmethod + def from_native(cls, surface): + """Construct a surface from a CAD native surface object. + + Parameters + ---------- + surface : Any + A native surface object. + + Returns + ------- + :class:`compas.geometry.Surface` + + """ + return from_native(cls, surface) + # ============================================================================== # Conversions # ============================================================================== From b78432a4c0a238f716e123b41c27fb3a82bb9084 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Sat, 22 Jun 2024 00:43:21 +0200 Subject: [PATCH 09/28] Surface() create a RhinoSurface object in Rhino --- src/compas_rhino/geometry/surfaces/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/__init__.py b/src/compas_rhino/geometry/surfaces/__init__.py index d82b944de18..d6033513220 100644 --- a/src/compas_rhino/geometry/surfaces/__init__.py +++ b/src/compas_rhino/geometry/surfaces/__init__.py @@ -8,9 +8,7 @@ @plugin(category="factories", requires=["Rhino"]) def new_surface(cls, *args, **kwargs): - surface = super(Surface, cls).__new__(cls) - surface.__init__(*args, **kwargs) - return surface + return object.__new__(RhinoSurface) @plugin(category="factories", requires=["Rhino"]) From 5ed87910ab0a6f932e3b583620691b186eaf1bbb Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Sat, 22 Jun 2024 00:44:22 +0200 Subject: [PATCH 10/28] added RhinoSurface.from_native plugin with intention to replace from_rhino --- src/compas_rhino/geometry/surfaces/surface.py | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index de221a9104b..f15222a9e91 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -37,12 +37,19 @@ class RhinoSurface(Surface): True if the surface is periodic in the U direction. is_periodic_v: bool True if the surface is periodic in the V direction. + native_surface: :class:`Rhino.Geometry.Surface` + The underlying Rhino surface object. """ def __init__(self, name=None): super(RhinoSurface, self).__init__(name=name) - self._rhino_surface = None + # empty surface is not really a valid RhinoSurface object. + self._rhino_surface = Rhino.Geometry.PlaneSurface(Rhino.Geometry.Plane.WorldXY, Rhino.Geometry.Interval(0, 1), Rhino.Geometry.Interval(0, 1)) + + @property + def native_surface(self): + return self._rhino_surface @property def rhino_surface(self): @@ -101,7 +108,7 @@ def from_corners(cls, corners): """ rhino_points = [Rhino.Geometry.Point3d(corner.x, corner.y, corner.z) for corner in corners] - return cls.from_rhino(Rhino.Geometry.NurbsSurface.CreateFromCorners(*rhino_points)) + return cls.from_native(Rhino.Geometry.NurbsSurface.CreateFromCorners(*rhino_points)) @classmethod def from_sphere(cls, sphere): @@ -119,7 +126,7 @@ def from_sphere(cls, sphere): """ sphere = sphere_to_rhino(sphere) surface = Rhino.Geometry.NurbsSurface.CreateFromSphere(sphere) - return cls.from_rhino(surface) + return cls.from_native(surface) @classmethod def from_cylinder(cls, cylinder): @@ -137,7 +144,7 @@ def from_cylinder(cls, cylinder): """ cylinder = cylinder_to_rhino(cylinder) surface = Rhino.Geometry.NurbsSurface.CreateFromCylinder(cylinder) - return cls.from_rhino(surface) + return cls.from_native(surface) @classmethod def from_torus(cls, torus): @@ -159,6 +166,9 @@ def from_torus(cls, torus): def from_rhino(cls, rhino_surface): """Construct a NURBS surface from an existing Rhino surface. + .. deprecated:: 2.1.1 + ``from_rhino`` will be removed in the future. Use ``from_native`` instead. + Parameters ---------- rhino_surface : :rhino:`Rhino.Geometry.Surface` @@ -168,9 +178,28 @@ def from_rhino(cls, rhino_surface): ------- :class:`compas_rhino.geometry.RhinoSurface` + """ + from warnings import warn + + warn("RhinoSurface.from_rhino will be removed in the future. Use RhinoSurface.from_native instead.", DeprecationWarning) + return cls.from_native(rhino_surface) + + @classmethod + def from_native(cls, native_surface): + """Construct a surface from an existing Rhino surface. + + Parameters + ---------- + native_surface : :rhino:`Rhino.Geometry.Surface` + A Rhino surface. + + Returns + ------- + :class:`compas_rhino.geometry.RhinoSurface` + """ curve = cls() - curve.rhino_surface = rhino_surface + curve.rhino_surface = native_surface return curve @classmethod @@ -202,7 +231,7 @@ def from_plane(cls, plane, u_domain=(0.0, 1.0), v_domain=(0.0, 1.0)): """ plane = plane_to_rhino(plane) rhino_surface = Rhino.Geometry.PlaneSurface(plane, Rhino.Geometry.Interval(*u_domain), Rhino.Geometry.Interval(*v_domain)) - return cls.from_rhino(rhino_surface) + return cls.from_native(rhino_surface) @classmethod def from_frame(cls, frame, u_interval, v_interval): @@ -231,7 +260,7 @@ def from_frame(cls, frame, u_interval, v_interval): if not surface: msg = "Failed creating PlaneSurface from frame:{} u_interval:{} v_interval:{}" raise ValueError(msg.format(frame, u_interval, v_interval)) - return cls.from_rhino(surface) + return cls.from_native(surface) # ============================================================================== # Conversions @@ -282,7 +311,7 @@ def u_isocurve(self, u): """ curve = self.rhino_surface.IsoCurve(1, u) # type: ignore - return RhinoCurve.from_rhino(curve) + return RhinoCurve.from_native(curve) def v_isocurve(self, v): """Compute the isoparametric curve at parameter v. @@ -297,7 +326,7 @@ def v_isocurve(self, v): """ curve = self.rhino_surface.IsoCurve(0, v) # type: ignore - return RhinoCurve.from_rhino(curve) + return RhinoCurve.from_native(curve) def point_at(self, u, v): """Compute a point on the surface. From 3e5017c31b1b8cbc219a1fe19f93e9d8189d2052 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Sat, 22 Jun 2024 00:47:29 +0200 Subject: [PATCH 11/28] updated changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32bdd6d2044..3073599e590 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added `VolMesh.vertex_edges`. * Added `VolMesh.from_meshes`. * Added `VolMesh.from_polyhedrons`. +* Added pluggable `Surface.from_native`. +* Added deprecation warning on `RhinoSurface.from_rhino`. +* Added `Surface.native_surface`. +* Added `RhinoSurface.native_surface`. +* Added plugin `RhinoSurface.from_native`. ### Changed @@ -54,6 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Updated `compas.geometry.vector.__truediv__` to allow element-wise division with another vector. * Fixed bug in registration `shapely` boolean plugins. * Temporarily restrict `numpy` to versions lower than `2.x`. +* Changed `RhinoSurface` plugin `new_surface` to create `RhinoSurface` object when `compas.geometry.Surface()` is called. ### Removed From 5566f950435d40c2a72d34839cd414e07243cd7a Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Sat, 22 Jun 2024 00:48:04 +0200 Subject: [PATCH 12/28] unused import --- src/compas_rhino/geometry/surfaces/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compas_rhino/geometry/surfaces/__init__.py b/src/compas_rhino/geometry/surfaces/__init__.py index d6033513220..be98beac8fb 100644 --- a/src/compas_rhino/geometry/surfaces/__init__.py +++ b/src/compas_rhino/geometry/surfaces/__init__.py @@ -1,7 +1,6 @@ from .surface import RhinoSurface # noqa : F401 from .nurbs import RhinoNurbsSurface -from compas.geometry import Surface from compas.geometry import NurbsSurface from compas.plugins import plugin From de97233228ae3b5b47c33e72cf9af0872b79f04c Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Mon, 24 Jun 2024 20:30:20 +0200 Subject: [PATCH 13/28] WIP --- src/compas/geometry/surfaces/surface.py | 13 +++++++++ src/compas_rhino/geometry/__init__.py | 2 ++ .../geometry/surfaces/__init__.py | 10 +++---- src/compas_rhino/geometry/surfaces/surface.py | 27 ++++++++++++++++--- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/compas/geometry/surfaces/surface.py b/src/compas/geometry/surfaces/surface.py index a5d0e52fb75..de935bc2d50 100644 --- a/src/compas/geometry/surfaces/surface.py +++ b/src/compas/geometry/surfaces/surface.py @@ -57,6 +57,19 @@ class Surface(Geometry): """ + @property + def __data__(self): + return { + "frame": self._frame.__data__, + "domain_u": list(self.domain_u), + "domain_v": list(self.domain_v), + + + @property + def __dtype__(self): + # make serialization CAD agnostic + return "compas.geometry/Surface" + def __new__(cls, *args, **kwargs): return new_surface(cls, *args, **kwargs) diff --git a/src/compas_rhino/geometry/__init__.py b/src/compas_rhino/geometry/__init__.py index 2481a1bfb8f..90b64f1abab 100644 --- a/src/compas_rhino/geometry/__init__.py +++ b/src/compas_rhino/geometry/__init__.py @@ -5,6 +5,7 @@ from __future__ import absolute_import from .curves.nurbs import RhinoNurbsCurve +from .surfaces import RhinoSurface from .surfaces.nurbs import RhinoNurbsSurface from .brep.brep import RhinoBrep @@ -34,6 +35,7 @@ "trimesh_principal_curvature", "trimesh_slice", "RhinoNurbsCurve", + "RhinoSurface", "RhinoNurbsSurface", "RhinoBrep", "RhinoBrepVertex", diff --git a/src/compas_rhino/geometry/surfaces/__init__.py b/src/compas_rhino/geometry/surfaces/__init__.py index be98beac8fb..378600ce116 100644 --- a/src/compas_rhino/geometry/surfaces/__init__.py +++ b/src/compas_rhino/geometry/surfaces/__init__.py @@ -2,12 +2,14 @@ from .nurbs import RhinoNurbsSurface from compas.geometry import NurbsSurface +from compas.geometry import Surface from compas.plugins import plugin @plugin(category="factories", requires=["Rhino"]) def new_surface(cls, *args, **kwargs): - return object.__new__(RhinoSurface) + # to make instance of `subclass`: super(superclass, subclass).__new__(subclass, *args, **kwargs) + return super(Surface, RhinoSurface).__new__(RhinoSurface, *args, **kwargs) @plugin(category="factories", requires=["Rhino"]) @@ -17,14 +19,12 @@ def new_surface_from_plane(cls, *args, **kwargs): @plugin(category="factories", requires=["Rhino"]) def new_nurbssurface(cls, *args, **kwargs): - surface = super(NurbsSurface, cls).__new__(cls) - surface.__init__(*args, **kwargs) - return surface + return super(NurbsSurface, cls).__new__(cls) @plugin(category="factories", requires=["Rhino"]) def new_nurbssurface_from_native(cls, *args, **kwargs): - return RhinoNurbsSurface.from_rhino(*args, **kwargs) + return RhinoNurbsSurface.from_native(*args, **kwargs) @plugin(category="factories", requires=["Rhino"]) diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index f15222a9e91..8840b78213a 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -9,6 +9,7 @@ if not compas.IPY: from typing import Tuple # noqa: F401 +from compas.geometry import Frame from compas.geometry import Plane # noqa: F401 from compas.geometry import Surface from compas_rhino.conversions import box_to_compas @@ -42,10 +43,29 @@ class RhinoSurface(Surface): """ + @classmethod + def __from_data__(cls, data): + frame = Frame.__from_data__(data["frame"]) + u_interval = tuple(data["u_interval"]) + v_interval = tuple(data["v_interval"]) + return cls.from_frame(frame, u_interval, v_interval) + def __init__(self, name=None): super(RhinoSurface, self).__init__(name=name) # empty surface is not really a valid RhinoSurface object. - self._rhino_surface = Rhino.Geometry.PlaneSurface(Rhino.Geometry.Plane.WorldXY, Rhino.Geometry.Interval(0, 1), Rhino.Geometry.Interval(0, 1)) + # if not isinstance(native_surface, Rhino.Geometry.Surface): + # raise ValueError("RhinoSurface cannot be directly instantiated without a native ``Rhino.Geometry.Surface`` object.") + # self._rhino_surface = native_surface + self._rhino_surface = None + self.frame = self._get_frame_from_planesurface() + + def _get_frame_from_planesurface(self): + u_start = self.domain_u[0] + v_start = self.domain_v[0] + success, frame = self.native_surface.FrameAt(u_start, v_start) + if not success: + raise ValueError("Failed to get frame at u={} v={}".format(u_start, v_start)) + return plane_to_compas_frame(frame) @property def native_surface(self): @@ -198,9 +218,8 @@ def from_native(cls, native_surface): :class:`compas_rhino.geometry.RhinoSurface` """ - curve = cls() - curve.rhino_surface = native_surface - return curve + print("RhinoSurface.from_native: cls={}".format(cls)) + return cls(native_surface) @classmethod def from_plane(cls, plane, u_domain=(0.0, 1.0), v_domain=(0.0, 1.0)): From 0d72fe5ad90a7d76b11495e34e3f56371ea2e1a9 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Tue, 2 Jul 2024 12:41:07 +0200 Subject: [PATCH 14/28] protect Surface(). Surface object can only be created via alternative constructors --- src/compas/geometry/surfaces/surface.py | 22 ++++++++++------ .../geometry/surfaces/__init__.py | 11 +++++--- src/compas_rhino/geometry/surfaces/surface.py | 26 +++++++++++-------- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/compas/geometry/surfaces/surface.py b/src/compas/geometry/surfaces/surface.py index de935bc2d50..c7724348f01 100644 --- a/src/compas/geometry/surfaces/surface.py +++ b/src/compas/geometry/surfaces/surface.py @@ -6,21 +6,22 @@ from compas.geometry import Frame from compas.geometry import Geometry +from compas.geometry import Plane from compas.geometry import Point from compas.geometry import Transformation from compas.itertools import linspace from compas.plugins import pluggable -@pluggable(category="factories") -def new_surface(cls, *args, **kwargs): - surface = object.__new__(cls) - surface.__init__(*args, **kwargs) - return surface +# @pluggable(category="factories") +# def new_surface(cls, *args, **kwargs): +# surface = object.__new__(cls) +# surface.__init__(*args, **kwargs) +# return surface @pluggable(category="factories") -def from_native(cls, *args, **kwargs): +def new_surface_from_native(cls, *args, **kwargs): raise NotImplementedError @@ -63,7 +64,12 @@ def __data__(self): "frame": self._frame.__data__, "domain_u": list(self.domain_u), "domain_v": list(self.domain_v), + } + @classmethod + def __from_data__(cls, data): + plane = Plane.from_frame(Frame.__from_data__(data["frame"])) + return cls.from_plane(plane, data["domain_u"], data["domain_v"]) @property def __dtype__(self): @@ -71,7 +77,7 @@ def __dtype__(self): return "compas.geometry/Surface" def __new__(cls, *args, **kwargs): - return new_surface(cls, *args, **kwargs) + raise AssertionError("Surface() is protected. Use Surface.from_...() to construct a Surface object.") def __init__(self, frame=None, name=None): super(Surface, self).__init__(name=name) @@ -235,7 +241,7 @@ def from_native(cls, surface): :class:`compas.geometry.Surface` """ - return from_native(cls, surface) + return new_surface_from_native(cls, surface) # ============================================================================== # Conversions diff --git a/src/compas_rhino/geometry/surfaces/__init__.py b/src/compas_rhino/geometry/surfaces/__init__.py index 378600ce116..0cb73966eba 100644 --- a/src/compas_rhino/geometry/surfaces/__init__.py +++ b/src/compas_rhino/geometry/surfaces/__init__.py @@ -6,10 +6,15 @@ from compas.plugins import plugin +# @plugin(category="factories", requires=["Rhino"]) +# def new_surface(cls, *args, **kwargs): +# # to make instance of `subclass`: super(superclass, subclass).__new__(subclass, *args, **kwargs) +# return super(Surface, RhinoSurface).__new__(RhinoSurface, *args, **kwargs) + + @plugin(category="factories", requires=["Rhino"]) -def new_surface(cls, *args, **kwargs): - # to make instance of `subclass`: super(superclass, subclass).__new__(subclass, *args, **kwargs) - return super(Surface, RhinoSurface).__new__(RhinoSurface, *args, **kwargs) +def new_surface_from_native(cls, *args, **kwargs): + return RhinoSurface.from_native(*args, **kwargs) @plugin(category="factories", requires=["Rhino"]) diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index 8840b78213a..86c3f1c3821 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -34,6 +34,8 @@ class RhinoSurface(Surface): The parameter domain in the U direction. domain_v: tuple[float, float] The parameter domain in the V direction. + frame: :class:`compas.geometry.Frame` + The frame of the surface at the parametric origin. is_periodic_u: bool True if the surface is periodic in the U direction. is_periodic_v: bool @@ -50,14 +52,13 @@ def __from_data__(cls, data): v_interval = tuple(data["v_interval"]) return cls.from_frame(frame, u_interval, v_interval) + def __new__(cls, *args, **kwargs): + # needed because the Surface.__new__ now enforces creation via alternative constructors only + return super(Surface, RhinoSurface).__new__(RhinoSurface, *args, **kwargs) + def __init__(self, name=None): super(RhinoSurface, self).__init__(name=name) - # empty surface is not really a valid RhinoSurface object. - # if not isinstance(native_surface, Rhino.Geometry.Surface): - # raise ValueError("RhinoSurface cannot be directly instantiated without a native ``Rhino.Geometry.Surface`` object.") - # self._rhino_surface = native_surface self._rhino_surface = None - self.frame = self._get_frame_from_planesurface() def _get_frame_from_planesurface(self): u_start = self.domain_u[0] @@ -79,10 +80,6 @@ def rhino_surface(self): def rhino_surface(self, surface): self._rhino_surface = surface - # ============================================================================== - # Data - # ============================================================================== - # ============================================================================== # Properties # ============================================================================== @@ -97,6 +94,12 @@ def domain_v(self): if self.rhino_surface: return self.rhino_surface.Domain(1) + @property + def frame(self): + if not self._frame: + self._frame = self._get_frame_from_planesurface() + return self._frame + @property def is_periodic_u(self): if self.rhino_surface: @@ -218,8 +221,9 @@ def from_native(cls, native_surface): :class:`compas_rhino.geometry.RhinoSurface` """ - print("RhinoSurface.from_native: cls={}".format(cls)) - return cls(native_surface) + instance = cls() + instance._rhino_surface = native_surface + return instance @classmethod def from_plane(cls, plane, u_domain=(0.0, 1.0), v_domain=(0.0, 1.0)): From 0ea3ede416ba8ca059e31e3ede25ffa169c834ea Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Tue, 2 Jul 2024 12:59:57 +0200 Subject: [PATCH 15/28] unused import --- src/compas/geometry/surfaces/surface.py | 1 - src/compas_rhino/geometry/surfaces/__init__.py | 1 - 2 files changed, 2 deletions(-) diff --git a/src/compas/geometry/surfaces/surface.py b/src/compas/geometry/surfaces/surface.py index c7724348f01..427a22756ee 100644 --- a/src/compas/geometry/surfaces/surface.py +++ b/src/compas/geometry/surfaces/surface.py @@ -12,7 +12,6 @@ from compas.itertools import linspace from compas.plugins import pluggable - # @pluggable(category="factories") # def new_surface(cls, *args, **kwargs): # surface = object.__new__(cls) diff --git a/src/compas_rhino/geometry/surfaces/__init__.py b/src/compas_rhino/geometry/surfaces/__init__.py index 0cb73966eba..2b36d20d0d1 100644 --- a/src/compas_rhino/geometry/surfaces/__init__.py +++ b/src/compas_rhino/geometry/surfaces/__init__.py @@ -2,7 +2,6 @@ from .nurbs import RhinoNurbsSurface from compas.geometry import NurbsSurface -from compas.geometry import Surface from compas.plugins import plugin From 6a9c2862cabc486d12440cbb23e8122474a5972e Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 11:32:34 +0200 Subject: [PATCH 16/28] merge from_frame/plane. only implement abstract properties --- src/compas_rhino/geometry/surfaces/surface.py | 71 ++++++------------- 1 file changed, 21 insertions(+), 50 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index 86c3f1c3821..12fde16af4a 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -68,37 +68,32 @@ def _get_frame_from_planesurface(self): raise ValueError("Failed to get frame at u={} v={}".format(u_start, v_start)) return plane_to_compas_frame(frame) + # ============================================================================== + # Implementation of abstract properties + # ============================================================================== + @property def native_surface(self): return self._rhino_surface + @native_surface.setter + def native_surface(self, surface): + self._rhino_surface = surface + @property def rhino_surface(self): + # TODO: Deprecate. replace with native_surface return self._rhino_surface @rhino_surface.setter def rhino_surface(self, surface): + # TODO: Deprecate. replace with native_surface self._rhino_surface = surface - # ============================================================================== - # Properties - # ============================================================================== - - @property - def domain_u(self): - if self.rhino_surface: - return self.rhino_surface.Domain(0) - - @property - def domain_v(self): - if self.rhino_surface: - return self.rhino_surface.Domain(1) - @property - def frame(self): - if not self._frame: - self._frame = self._get_frame_from_planesurface() - return self._frame + def is_closed(self): + # TODO: implement this properly + raise NotImplementedError @property def is_periodic_u(self): @@ -222,7 +217,10 @@ def from_native(cls, native_surface): """ instance = cls() - instance._rhino_surface = native_surface + instance.native_surface = native_surface + instance.frame = instance._get_frame_from_planesurface() + instance._domain_u = native_surface.Domain(0)[0], native_surface.Domain(0)[1] + instance._domain_v = native_surface.Domain(1)[0], native_surface.Domain(1)[1] return instance @classmethod @@ -232,8 +230,8 @@ def from_plane(cls, plane, u_domain=(0.0, 1.0), v_domain=(0.0, 1.0)): Parameters ---------- - plane : :class:`compas.geometry.Plane` - The plane. + plane : :class:`compas.geometry.Plane` or :class:`compas.geometry.Frame` + The plane or frame to use as origin and orientation of the surface. u_domain : tuple(float, float), optional The parametric domain of the U parameter. u_domain[0] => u_domain[1]. Default is ``(0.0, 1.0)``. @@ -252,39 +250,12 @@ def from_plane(cls, plane, u_domain=(0.0, 1.0), v_domain=(0.0, 1.0)): :class:`compas_rhino.geometry.RhinoSurface` """ + if isinstance(plane, Frame): + plane = Plane.from_frame(plane) plane = plane_to_rhino(plane) rhino_surface = Rhino.Geometry.PlaneSurface(plane, Rhino.Geometry.Interval(*u_domain), Rhino.Geometry.Interval(*v_domain)) return cls.from_native(rhino_surface) - @classmethod - def from_frame(cls, frame, u_interval, v_interval): - """Creates a planar surface from a frame and parametric domain information. - - Parameters - ---------- - frame : :class:`compas.geometry.Frame` - A frame with point at the center of the wanted plannar surface and - x and y axes the direction of u and v respectively. - u_interval : tuple(float, float) - The parametric domain of the U parameter. u_interval[0] => u_interval[1]. - v_interval : tuple(float, float) - The parametric domain of the V parameter. v_interval[0] => v_interval[1]. - - Returns - ------- - :class:`compas_rhino.geometry.surface.RhinoSurface` - - """ - surface = Rhino.Geometry.PlaneSurface( - frame_to_rhino_plane(frame), - Rhino.Geometry.Interval(*u_interval), - Rhino.Geometry.Interval(*v_interval), - ) - if not surface: - msg = "Failed creating PlaneSurface from frame:{} u_interval:{} v_interval:{}" - raise ValueError(msg.format(frame, u_interval, v_interval)) - return cls.from_native(surface) - # ============================================================================== # Conversions # ============================================================================== From d4f3ee0f854e0f3dbb61df8a586aeb3271343cdf Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 11:35:10 +0200 Subject: [PATCH 17/28] fix search and replace accident --- src/compas_rhino/geometry/surfaces/surface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index 12fde16af4a..dd97f6a0386 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -305,7 +305,7 @@ def u_isocurve(self, u): """ curve = self.rhino_surface.IsoCurve(1, u) # type: ignore - return RhinoCurve.from_native(curve) + return RhinoCurve.from_rhino(curve) def v_isocurve(self, v): """Compute the isoparametric curve at parameter v. @@ -320,7 +320,7 @@ def v_isocurve(self, v): """ curve = self.rhino_surface.IsoCurve(0, v) # type: ignore - return RhinoCurve.from_native(curve) + return RhinoCurve.from_rhino(curve) def point_at(self, u, v): """Compute a point on the surface. From a3d6eccc492833672819da31017788089d6ae5d2 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 12:31:24 +0200 Subject: [PATCH 18/28] removed Surface.__from_data__ --- src/compas/geometry/surfaces/surface.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/compas/geometry/surfaces/surface.py b/src/compas/geometry/surfaces/surface.py index 427a22756ee..239e5214719 100644 --- a/src/compas/geometry/surfaces/surface.py +++ b/src/compas/geometry/surfaces/surface.py @@ -65,11 +65,6 @@ def __data__(self): "domain_v": list(self.domain_v), } - @classmethod - def __from_data__(cls, data): - plane = Plane.from_frame(Frame.__from_data__(data["frame"])) - return cls.from_plane(plane, data["domain_u"], data["domain_v"]) - @property def __dtype__(self): # make serialization CAD agnostic From 186ba78dbebc06f27976cf648d974c5db2434505 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 13:29:02 +0200 Subject: [PATCH 19/28] instantiating nurbs surface via from_native --- src/compas/geometry/surfaces/nurbs.py | 7 +------ src/compas/geometry/surfaces/surface.py | 7 +++++++ src/compas_rhino/geometry/surfaces/__init__.py | 5 ----- src/compas_rhino/geometry/surfaces/nurbs.py | 6 ++++++ src/compas_rhino/geometry/surfaces/surface.py | 11 +++-------- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/compas/geometry/surfaces/nurbs.py b/src/compas/geometry/surfaces/nurbs.py index bb447176914..3c48ca3e51b 100644 --- a/src/compas/geometry/surfaces/nurbs.py +++ b/src/compas/geometry/surfaces/nurbs.py @@ -11,11 +11,6 @@ from .surface import Surface -@pluggable(category="factories") -def new_nurbssurface(cls, *args, **kwargs): - raise PluginNotInstalledError - - @pluggable(category="factories") def new_nurbssurface_from_native(cls, *args, **kwargs): raise PluginNotInstalledError @@ -128,7 +123,7 @@ def __from_data__(cls, data): ) def __new__(cls, *args, **kwargs): - return new_nurbssurface(cls, *args, **kwargs) + raise AssertionError("NurbsSurface() is protected. Use NurbsSurface.from_...() to construct a NurbsSurface object.") def __init__(self, name=None): super(NurbsSurface, self).__init__(name=name) diff --git a/src/compas/geometry/surfaces/surface.py b/src/compas/geometry/surfaces/surface.py index 239e5214719..177961010ca 100644 --- a/src/compas/geometry/surfaces/surface.py +++ b/src/compas/geometry/surfaces/surface.py @@ -65,6 +65,13 @@ def __data__(self): "domain_v": list(self.domain_v), } + @classmethod + def __from_data__(cls, data): + frame = Frame.__from_data__(data["frame"]) + domain_u = tuple(data["domain_u"]) + domain_v = tuple(data["domain_v"]) + return cls.from_plane(frame, domain_u, domain_v) + @property def __dtype__(self): # make serialization CAD agnostic diff --git a/src/compas_rhino/geometry/surfaces/__init__.py b/src/compas_rhino/geometry/surfaces/__init__.py index 2b36d20d0d1..1758053bec1 100644 --- a/src/compas_rhino/geometry/surfaces/__init__.py +++ b/src/compas_rhino/geometry/surfaces/__init__.py @@ -21,11 +21,6 @@ def new_surface_from_plane(cls, *args, **kwargs): return RhinoSurface.from_plane(*args, **kwargs) -@plugin(category="factories", requires=["Rhino"]) -def new_nurbssurface(cls, *args, **kwargs): - return super(NurbsSurface, cls).__new__(cls) - - @plugin(category="factories", requires=["Rhino"]) def new_nurbssurface_from_native(cls, *args, **kwargs): return RhinoNurbsSurface.from_native(*args, **kwargs) diff --git a/src/compas_rhino/geometry/surfaces/nurbs.py b/src/compas_rhino/geometry/surfaces/nurbs.py index d4e3dfef646..eced32cf894 100644 --- a/src/compas_rhino/geometry/surfaces/nurbs.py +++ b/src/compas_rhino/geometry/surfaces/nurbs.py @@ -401,6 +401,12 @@ def from_plane(cls, plane, u_degree=1, v_degree=1, u_domain=(0.0, 1.0), v_domain ) return surface + @classmethod + def from_native(cls, native_surface): + surface = cls() + surface.rhino_surface = native_surface + return surface + # ============================================================================== # Conversions # ============================================================================== diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index dd97f6a0386..5796ee134b4 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -45,16 +45,10 @@ class RhinoSurface(Surface): """ - @classmethod - def __from_data__(cls, data): - frame = Frame.__from_data__(data["frame"]) - u_interval = tuple(data["u_interval"]) - v_interval = tuple(data["v_interval"]) - return cls.from_frame(frame, u_interval, v_interval) - def __new__(cls, *args, **kwargs): # needed because the Surface.__new__ now enforces creation via alternative constructors only - return super(Surface, RhinoSurface).__new__(RhinoSurface, *args, **kwargs) + # cls is the class that is being instantiated + return super(Surface, cls).__new__(cls, *args, **kwargs) def __init__(self, name=None): super(RhinoSurface, self).__init__(name=name) @@ -217,6 +211,7 @@ def from_native(cls, native_surface): """ instance = cls() + print("RhinoSurface.from_native: cls:{}".format(cls)) instance.native_surface = native_surface instance.frame = instance._get_frame_from_planesurface() instance._domain_u = native_surface.Domain(0)[0], native_surface.Domain(0)[1] From dafabfa1bb4b075df8685b6cb7238552a3731504 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 13:32:12 +0200 Subject: [PATCH 20/28] same from_native in nurbs and normal surface --- src/compas_rhino/geometry/surfaces/nurbs.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/nurbs.py b/src/compas_rhino/geometry/surfaces/nurbs.py index eced32cf894..d4e3dfef646 100644 --- a/src/compas_rhino/geometry/surfaces/nurbs.py +++ b/src/compas_rhino/geometry/surfaces/nurbs.py @@ -401,12 +401,6 @@ def from_plane(cls, plane, u_degree=1, v_degree=1, u_domain=(0.0, 1.0), v_domain ) return surface - @classmethod - def from_native(cls, native_surface): - surface = cls() - surface.rhino_surface = native_surface - return surface - # ============================================================================== # Conversions # ============================================================================== From 5d8672114db8860f1421a4272d1968291ad41be3 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 14:30:13 +0200 Subject: [PATCH 21/28] added domain and frame to nurbs params. __from_data__ is generic but RhinoNurbsSurface.__data__ has pre-processing --- src/compas/geometry/surfaces/nurbs.py | 81 +++++++++++++++------ src/compas_rhino/geometry/surfaces/nurbs.py | 80 ++++++++------------ 2 files changed, 89 insertions(+), 72 deletions(-) diff --git a/src/compas/geometry/surfaces/nurbs.py b/src/compas/geometry/surfaces/nurbs.py index 3c48ca3e51b..a399763a4b7 100644 --- a/src/compas/geometry/surfaces/nurbs.py +++ b/src/compas/geometry/surfaces/nurbs.py @@ -2,6 +2,7 @@ from __future__ import division from __future__ import print_function +from compas.geometry import Frame from compas.geometry import Point from compas.itertools import linspace from compas.itertools import meshgrid @@ -94,32 +95,62 @@ def __dtype__(self): @property def __data__(self): - return { - "points": [point.__data__ for point in self.points], - "weights": self.weights, - "knots_u": self.knots_u, - "knots_v": self.knots_v, - "mults_u": self.mults_u, - "mults_v": self.mults_v, - "degree_u": self.degree_u, - "degree_v": self.degree_v, - "is_periodic_u": self.is_periodic_u, - "is_periodic_v": self.is_periodic_v, - } + data = super(NurbsSurface, self).__data__ + data["points"] = [[point.__data__ for point in row] for row in self.points] # type: ignore + data["weights"] = self.weights + data["knots_u"] = self.knots_u + data["knots_v"] = self.knots_v + data["mults_u"] = self.mults_u + data["mults_v"] = self.mults_v + data["degree_u"] = self.degree_u + data["degree_v"] = self.degree_v + data["is_periodic_u"] = self.is_periodic_u + data["is_periodic_v"] = self.is_periodic_v + return data @classmethod def __from_data__(cls, data): + """Construct a BSpline surface from its data representation. + + Parameters + ---------- + data : dict + The data dictionary. + + Returns + ------- + :class:`compas_rhino.geometry.RhinoNurbsSurface` + The constructed surface. + + """ + frame = Frame.__from_data__(data["frame"]) + domain_u = data["domain_u"] + domain_v = data["domain_v"] + points = [[Point.__from_data__(point) for point in row] for row in data["points"]] + weights = data["weights"] + knots_u = data["knots_u"] + knots_v = data["knots_v"] + mults_u = data["mults_u"] + mults_v = data["mults_v"] + degree_u = data["degree_u"] + degree_v = data["degree_v"] + is_periodic_u = data["is_periodic_u"] + is_periodic_v = data["is_periodic_v"] + return cls.from_parameters( - data["points"], - data["weights"], - data["knots_u"], - data["knots_v"], - data["mults_u"], - data["mults_v"], - data["degree_u"], - data["degree_v"], - data["is_periodic_u"], - data["is_periodic_v"], + frame, + domain_u, + domain_v, + points, + weights, + knots_u, + knots_v, + mults_u, + mults_v, + degree_u, + degree_v, + is_periodic_u, + is_periodic_v, ) def __new__(cls, *args, **kwargs): @@ -211,6 +242,9 @@ def from_native(cls, surface): @classmethod def from_parameters( cls, + frame, + domain_u, + domain_v, points, weights, knots_u, @@ -250,6 +284,9 @@ def from_parameters( """ return new_nurbssurface_from_parameters( cls, + frame, + domain_u, + domain_v, points, weights, knots_u, diff --git a/src/compas_rhino/geometry/surfaces/nurbs.py b/src/compas_rhino/geometry/surfaces/nurbs.py index d4e3dfef646..5865e4a639d 100644 --- a/src/compas_rhino/geometry/surfaces/nurbs.py +++ b/src/compas_rhino/geometry/surfaces/nurbs.py @@ -52,6 +52,8 @@ def __iter__(self): def rhino_surface_from_parameters( + domain_u, + domain_v, points, weights, knots_u, @@ -90,6 +92,9 @@ def rhino_surface_from_parameters( ) raise ValueError("Failed to create NurbsSurface with params:\n{}".format(message)) + rhino_surface.SetDomain(0, Rhino.Geometry.Interval(domain_u[0], domain_u[1])) + rhino_surface.SetDomain(1, Rhino.Geometry.Interval(domain_v[0], domain_v[1])) + knotvector_u = knots_and_mults_to_knotvector(knots_u, mults_u) knotvector_v = knots_and_mults_to_knotvector(knots_v, mults_v) # account for superfluous knots @@ -153,56 +158,19 @@ def __data__(self): mults_u[-1] += 1 mults_v[0] += 1 mults_v[-1] += 1 - return { - "points": [[point.__data__ for point in row] for row in self.points], # type: ignore - "weights": self.weights, - "knots_u": self.knots_u, - "knots_v": self.knots_v, - "mults_u": mults_u, - "mults_v": mults_v, - "degree_u": self.degree_u, - "degree_v": self.degree_v, - "is_periodic_u": self.is_periodic_u, - "is_periodic_v": self.is_periodic_v, - } - - @classmethod - def __from_data__(cls, data): - """Construct a BSpline surface from its data representation. - - Parameters - ---------- - data : dict - The data dictionary. - - Returns - ------- - :class:`compas_rhino.geometry.RhinoNurbsSurface` - The constructed surface. - """ - points = [[Point.__from_data__(point) for point in row] for row in data["points"]] - weights = data["weights"] - knots_u = data["knots_u"] - knots_v = data["knots_v"] - mults_u = data["mults_u"] - mults_v = data["mults_v"] - degree_u = data["degree_u"] - degree_v = data["degree_v"] - is_periodic_u = data["is_periodic_u"] - is_periodic_v = data["is_periodic_v"] - return cls.from_parameters( - points, - weights, - knots_u, - knots_v, - mults_u, - mults_v, - degree_u, - degree_v, - is_periodic_u, - is_periodic_v, - ) + data = super(NurbsSurface, self).__data__ + data["points"] = [[point.__data__ for point in row] for row in self.points] # type: ignore + data["weights"] = self.weights + data["knots_u"] = self.knots_u + data["knots_v"] = self.knots_v + data["mults_u"] = mults_u + data["mults_v"] = mults_v + data["degree_u"] = self.degree_u + data["degree_v"] = self.degree_v + data["is_periodic_u"] = self.is_periodic_u + data["is_periodic_v"] = self.is_periodic_v + return data # ============================================================================== # Properties @@ -273,6 +241,9 @@ def degree_v(self): @classmethod def from_parameters( cls, + frame, + domain_u, + domain_v, points, weights, knots_u, @@ -288,6 +259,12 @@ def from_parameters( Parameters ---------- + frame : :class:`compas.geometry.Frame` + The frame of the surface. + domain_u : tuple(float) + The domain of the surface in the U direction. + domain_v : tuple(float) + The domain of the surface in the V direction. points : list[list[:class:`compas.geometry.Point`]] The control points. weights : list[list[float]] @@ -311,7 +288,10 @@ def from_parameters( """ surface = cls() - surface.rhino_surface = rhino_surface_from_parameters(points, weights, knots_u, knots_v, mults_u, mults_v, degree_u, degree_v) + surface.rhino_surface = rhino_surface_from_parameters(domain_u, domain_v, points, weights, knots_u, knots_v, mults_u, mults_v, degree_u, degree_v) + surface.frame = frame + surface._domain_u = domain_u + surface._domain_v = domain_v return surface @classmethod From 33c388f7827a062044f83d9b8202822758820da9 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 14:31:05 +0200 Subject: [PATCH 22/28] removed dead code and unused imports --- src/compas/geometry/surfaces/surface.py | 1 - src/compas_rhino/geometry/surfaces/__init__.py | 7 ------- src/compas_rhino/geometry/surfaces/nurbs.py | 1 - src/compas_rhino/geometry/surfaces/surface.py | 1 - 4 files changed, 10 deletions(-) diff --git a/src/compas/geometry/surfaces/surface.py b/src/compas/geometry/surfaces/surface.py index 177961010ca..17e45b2860f 100644 --- a/src/compas/geometry/surfaces/surface.py +++ b/src/compas/geometry/surfaces/surface.py @@ -6,7 +6,6 @@ from compas.geometry import Frame from compas.geometry import Geometry -from compas.geometry import Plane from compas.geometry import Point from compas.geometry import Transformation from compas.itertools import linspace diff --git a/src/compas_rhino/geometry/surfaces/__init__.py b/src/compas_rhino/geometry/surfaces/__init__.py index 1758053bec1..54c7e453070 100644 --- a/src/compas_rhino/geometry/surfaces/__init__.py +++ b/src/compas_rhino/geometry/surfaces/__init__.py @@ -1,16 +1,9 @@ from .surface import RhinoSurface # noqa : F401 from .nurbs import RhinoNurbsSurface -from compas.geometry import NurbsSurface from compas.plugins import plugin -# @plugin(category="factories", requires=["Rhino"]) -# def new_surface(cls, *args, **kwargs): -# # to make instance of `subclass`: super(superclass, subclass).__new__(subclass, *args, **kwargs) -# return super(Surface, RhinoSurface).__new__(RhinoSurface, *args, **kwargs) - - @plugin(category="factories", requires=["Rhino"]) def new_surface_from_native(cls, *args, **kwargs): return RhinoSurface.from_native(*args, **kwargs) diff --git a/src/compas_rhino/geometry/surfaces/nurbs.py b/src/compas_rhino/geometry/surfaces/nurbs.py index 5865e4a639d..fa407b446d1 100644 --- a/src/compas_rhino/geometry/surfaces/nurbs.py +++ b/src/compas_rhino/geometry/surfaces/nurbs.py @@ -7,7 +7,6 @@ import Rhino.Geometry # type: ignore from compas.geometry import NurbsSurface -from compas.geometry import Point from compas.geometry import knots_and_mults_to_knotvector from compas.itertools import flatten from compas_rhino.conversions import plane_to_rhino diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index 5796ee134b4..e6d8f583d2e 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -14,7 +14,6 @@ from compas.geometry import Surface from compas_rhino.conversions import box_to_compas from compas_rhino.conversions import cylinder_to_rhino -from compas_rhino.conversions import frame_to_rhino_plane from compas_rhino.conversions import plane_to_compas_frame from compas_rhino.conversions import plane_to_rhino from compas_rhino.conversions import point_to_compas From 9f6631d32f403d3981ea0e72e3d720757d1fa32a Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 14:48:23 +0200 Subject: [PATCH 23/28] added nurbssurface_to_compas --- src/compas_rhino/conversions/__init__.py | 2 ++ src/compas_rhino/conversions/surfaces.py | 40 +++++++++++++++++------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/compas_rhino/conversions/__init__.py b/src/compas_rhino/conversions/__init__.py index ee610f2a17d..076a7ee39ba 100644 --- a/src/compas_rhino/conversions/__init__.py +++ b/src/compas_rhino/conversions/__init__.py @@ -43,6 +43,7 @@ ) from .surfaces import ( surface_to_rhino, + nurbssurface_to_compas, data_to_rhino_surface, surface_to_compas_data, surface_to_compas, @@ -127,6 +128,7 @@ "curve_to_compas", # surfaces "surface_to_rhino", + "nurbssurface_to_compas", "surface_to_compas_data", "data_to_rhino_surface", "surface_to_compas", diff --git a/src/compas_rhino/conversions/surfaces.py b/src/compas_rhino/conversions/surfaces.py index fbb84010aed..9022efc852e 100644 --- a/src/compas_rhino/conversions/surfaces.py +++ b/src/compas_rhino/conversions/surfaces.py @@ -6,6 +6,7 @@ from compas.datastructures import Mesh from compas.geometry import NurbsSurface +from compas.geometry import Surface from compas.geometry import Point from compas.tolerance import TOL from compas.utilities import memoize @@ -32,12 +33,14 @@ def surface_to_rhino(surface): Rhino.Geometry.Surface """ - return surface.rhino_surface + return surface.native_surface def data_to_rhino_surface(data): """Convert a COMPAS surface to a Rhino surface. + TODO: still needed? this is basically RhinoSurface.from_parameters() + Parameters ---------- data: dict @@ -85,6 +88,8 @@ def data_to_rhino_surface(data): def surface_to_compas_data(surface): """Convert a Rhino surface to a COMPAS surface. + TODO: still needed? this is basically RhinoSurface.__data__ + Parameters ---------- surface: :rhino:`Rhino.Geometry.Surface` @@ -147,24 +152,37 @@ def surface_to_compas(surface): Parameters ---------- surface: :rhino:`Rhino.Geometry.Surface` + A Rhino surface object. Returns ------- - :class:`compas.geometry.Surface` + :class:`compas_rhino.geometry.RhinoSurface` """ - if isinstance(surface, Rhino.DocObjects.RhinoObject): - surface = surface.Geometry + if not isinstance(surface, Rhino.Geometry.Surface): + raise ConversionError("Expected a Rhino Surface object but got: {}".format(type(surface))) + + return Surface.from_native(surface) - if not isinstance(surface, Rhino.Geometry.Brep): - brep = Rhino.Geometry.Brep.TryConvertBrep(surface) - else: - brep = surface - if brep.Surfaces.Count > 1: # type: ignore - raise ConversionError("Conversion of a Brep with multiple underlying surface is currently not supported.") +def nurbssurface_to_compas(nurbssurface): + """Convert a Rhino NurbsSurface to a COMPAS NurbsSurface. + + Parameters + ---------- + nurbssurface : :class:`Rhino.Geometry.NurbsSurface` + A Rhino NurbsSurface object. + + Returns + ------- + :class:`compas_rhino.geometry.RhinoNurbsSurface` + A COMPAS NurbsSurface object. + + """ + if not isinstance(nurbssurface, Rhino.Geometry.NurbsSurface): + raise ConversionError("Expected a Rhino NurbsSurface object but got: {}".format(type(nurbssurface))) - return NurbsSurface.from_native(brep.Surfaces[0]) + return NurbsSurface.from_native(nurbssurface) def surface_to_compas_mesh(surface, facefilter=None, cleanup=False, cls=None): From de25d041a7fb2f9f66fd7cf53458d971b751912a Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 15:23:18 +0200 Subject: [PATCH 24/28] native_surface is readonly --- src/compas_rhino/geometry/surfaces/surface.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index e6d8f583d2e..df96b430098 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -69,10 +69,6 @@ def _get_frame_from_planesurface(self): def native_surface(self): return self._rhino_surface - @native_surface.setter - def native_surface(self, surface): - self._rhino_surface = surface - @property def rhino_surface(self): # TODO: Deprecate. replace with native_surface @@ -80,7 +76,7 @@ def rhino_surface(self): @rhino_surface.setter def rhino_surface(self, surface): - # TODO: Deprecate. replace with native_surface + # TODO: Deprecate. should be read-only self._rhino_surface = surface @property @@ -211,7 +207,7 @@ def from_native(cls, native_surface): """ instance = cls() print("RhinoSurface.from_native: cls:{}".format(cls)) - instance.native_surface = native_surface + instance._rhino_surface = native_surface instance.frame = instance._get_frame_from_planesurface() instance._domain_u = native_surface.Domain(0)[0], native_surface.Domain(0)[1] instance._domain_v = native_surface.Domain(1)[0], native_surface.Domain(1)[1] From 370ec8f3b7ac670e504a46faf4586442c46e40f2 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 15:23:33 +0200 Subject: [PATCH 25/28] is_closed_u/v instead of is_closed --- src/compas/geometry/surfaces/surface.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/compas/geometry/surfaces/surface.py b/src/compas/geometry/surfaces/surface.py index 17e45b2860f..8bbb5edad5e 100644 --- a/src/compas/geometry/surfaces/surface.py +++ b/src/compas/geometry/surfaces/surface.py @@ -11,12 +11,6 @@ from compas.itertools import linspace from compas.plugins import pluggable -# @pluggable(category="factories") -# def new_surface(cls, *args, **kwargs): -# surface = object.__new__(cls) -# surface.__init__(*args, **kwargs) -# return surface - @pluggable(category="factories") def new_surface_from_native(cls, *args, **kwargs): @@ -47,6 +41,10 @@ class Surface(Geometry): The parameter domain of the surface in the U direction. domain_v : tuple[float, float], read-only The parameter domain of the surface in the V direction. + is_closed_u : bool, read-only + Flag indicating if the surface is closed in the U direction. + is_closed_v : bool, read-only + Flag indicating if the surface is closed in the V direction. is_periodic_u : bool, read-only Flag indicating if the surface is periodic in the U direction. is_periodic_v : bool, read-only @@ -164,7 +162,11 @@ def domain_v(self): return self._domain_v @property - def is_closed(self): + def is_closed_u(self): + raise NotImplementedError + + @property + def is_closed_v(self): raise NotImplementedError @property From af70a6d877f81363fbbcd8087ce850d9083e2b2f Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 15:25:41 +0200 Subject: [PATCH 26/28] removed debug print --- src/compas_rhino/geometry/surfaces/surface.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index df96b430098..f5ee87cb96c 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -206,7 +206,6 @@ def from_native(cls, native_surface): """ instance = cls() - print("RhinoSurface.from_native: cls:{}".format(cls)) instance._rhino_surface = native_surface instance.frame = instance._get_frame_from_planesurface() instance._domain_u = native_surface.Domain(0)[0], native_surface.Domain(0)[1] From f8db6c9d2a1d06163a3ed5840f3f98e75b980a23 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 15:25:54 +0200 Subject: [PATCH 27/28] updated changelog --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3073599e590..230fe58f6ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added `Surface.native_surface`. * Added `RhinoSurface.native_surface`. * Added plugin `RhinoSurface.from_native`. +* Added parameters `frame`, `domain_u` and `domain_v` to `RhinoNurbs.from_parameters`. +* Added `nurbssurface_to_compas` to `compas_rhino.conversions`. +* Added `is_closed_u` and `is_closed_v` to `compas.geometry.Surface`. ### Changed @@ -59,12 +62,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Updated `compas.geometry.vector.__truediv__` to allow element-wise division with another vector. * Fixed bug in registration `shapely` boolean plugins. * Temporarily restrict `numpy` to versions lower than `2.x`. -* Changed `RhinoSurface` plugin `new_surface` to create `RhinoSurface` object when `compas.geometry.Surface()` is called. +* Protected call to `compas.geometry.Surface()`. `Surface` should be instantiated using one of the alternative constructors. +* Protected call to `compas.geometry.NurbsSurface()`. `NurbsSurface` should be instantiated using one of the alternative constructors. ### Removed * Removed `System` dependency in `compas_ghpython/utilities/drawing.py`. * Removed GH plugin for `compas.scene.clear` since it clashed with the Rhino version. +* Removed pluggable `new_nurbssurface`. +* Removed pluggable `new_surface`. +* Removed `compas.geometry.Surface.is_closed`. ## [2.1.1] 2024-05-14 From 5b5e1acde55b11f3c445a0d752fd9ae3c4c7aacb Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 3 Jul 2024 15:26:57 +0200 Subject: [PATCH 28/28] linting --- src/compas_rhino/conversions/surfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_rhino/conversions/surfaces.py b/src/compas_rhino/conversions/surfaces.py index 9022efc852e..c869a928700 100644 --- a/src/compas_rhino/conversions/surfaces.py +++ b/src/compas_rhino/conversions/surfaces.py @@ -6,8 +6,8 @@ from compas.datastructures import Mesh from compas.geometry import NurbsSurface -from compas.geometry import Surface from compas.geometry import Point +from compas.geometry import Surface from compas.tolerance import TOL from compas.utilities import memoize