From 279a919e5c0faf5260f5b78d7965b984aa4f306f Mon Sep 17 00:00:00 2001 From: tomvanmele Date: Wed, 3 Jul 2024 22:04:53 +0200 Subject: [PATCH 1/9] use direct new overwrite with type check instead of pluggable --- src/compas/geometry/surfaces/surface.py | 57 +++++++++---------------- 1 file changed, 20 insertions(+), 37 deletions(-) diff --git a/src/compas/geometry/surfaces/surface.py b/src/compas/geometry/surfaces/surface.py index b8b77ac75a5..22250bfbe60 100644 --- a/src/compas/geometry/surfaces/surface.py +++ b/src/compas/geometry/surfaces/surface.py @@ -9,19 +9,13 @@ from compas.geometry import Point from compas.geometry import Transformation from compas.itertools import linspace +from compas.plugins import PluginNotInstalledError 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_plane(cls, *args, **kwargs): - raise NotImplementedError +def surface_from_native(cls, *args, **kwargs): + raise PluginNotInstalledError class Surface(Geometry): @@ -51,7 +45,9 @@ class Surface(Geometry): """ def __new__(cls, *args, **kwargs): - return new_surface(cls, *args, **kwargs) + if cls is Surface: + raise TypeError("Instantiating the base Surface class directly is not allowed.") + return object.__new__(cls) def __init__(self, frame=None, name=None): super(Surface, self).__init__(name=name) @@ -149,21 +145,25 @@ def is_periodic_v(self): # Constructors # ============================================================================== + # these probably need to be moved to Nurbs + # i don't think you can store a "general" parametric surface in a file + @classmethod - def from_step(cls, filepath): - """Load a surface from a STP file. + def from_native(cls, surface): + """Construct a parametric surface from a native surface geometry. Parameters ---------- - filepath : str - The path to the file. + surface + A CAD native surface object. Returns ------- :class:`compas.geometry.Surface` + A COMPAS surface. """ - raise NotImplementedError + return surface_from_native(cls, surface) @classmethod def from_obj(cls, filepath): @@ -182,20 +182,20 @@ def from_obj(cls, filepath): raise NotImplementedError @classmethod - def from_plane(cls, plane, *args, **kwargs): - """Construct a surface from a plane. + def from_step(cls, filepath): + """Load a surface from a STP file. Parameters ---------- - plane : :class:`compas.geometry.Plane` - The plane. + filepath : str + The path to the file. Returns ------- :class:`compas.geometry.Surface` """ - return new_surface_from_plane(cls, plane, *args, **kwargs) + raise NotImplementedError # ============================================================================== # Conversions @@ -658,20 +658,3 @@ def intersections_with_plane(self, plane): """ raise NotImplementedError - - # def patch(self, u, v, du=1, dv=1): - # """Construct a NURBS surface patch from the surface at the given UV parameters. - - # Parameters - # ---------- - # u : float - # v : float - # du : int, optional - # dv : int, optional - - # Returns - # ------- - # :class:`compas.geometry.NurbsSurface` - - # """ - # raise NotImplementedError From 404ce795bb7b1d00cb34be13ae68e12a61508372 Mon Sep 17 00:00:00 2001 From: tomvanmele Date: Wed, 3 Jul 2024 22:05:18 +0200 Subject: [PATCH 2/9] remove unneeded new overwrites --- src/compas/geometry/surfaces/conical.py | 7 ------- src/compas/geometry/surfaces/cylindrical.py | 7 ------- src/compas/geometry/surfaces/planar.py | 7 ------- src/compas/geometry/surfaces/spherical.py | 7 ------- src/compas/geometry/surfaces/toroidal.py | 7 ------- 5 files changed, 35 deletions(-) diff --git a/src/compas/geometry/surfaces/conical.py b/src/compas/geometry/surfaces/conical.py index d4c4ce41457..0bcda369aad 100644 --- a/src/compas/geometry/surfaces/conical.py +++ b/src/compas/geometry/surfaces/conical.py @@ -28,13 +28,6 @@ class ConicalSurface(Surface): """ - # overwriting the __new__ method is necessary - # to avoid triggering the plugin mechanism of the base surface class - def __new__(cls, *args, **kwargs): - surface = object.__new__(cls) - surface.__init__(*args, **kwargs) - return surface - DATASCHEMA = { "type": "object", "properties": { diff --git a/src/compas/geometry/surfaces/cylindrical.py b/src/compas/geometry/surfaces/cylindrical.py index 0ed358a58c3..662e9b7e3c1 100644 --- a/src/compas/geometry/surfaces/cylindrical.py +++ b/src/compas/geometry/surfaces/cylindrical.py @@ -30,13 +30,6 @@ class CylindricalSurface(Surface): """ - # overwriting the __new__ method is necessary - # to avoid triggering the plugin mechanism of the base surface class - def __new__(cls, *args, **kwargs): - surface = object.__new__(cls) - surface.__init__(*args, **kwargs) - return surface - DATASCHEMA = { "type": "object", "properties": { diff --git a/src/compas/geometry/surfaces/planar.py b/src/compas/geometry/surfaces/planar.py index 66f636f1904..6e9e25f2fca 100644 --- a/src/compas/geometry/surfaces/planar.py +++ b/src/compas/geometry/surfaces/planar.py @@ -27,13 +27,6 @@ class PlanarSurface(Surface): """ - # overwriting the __new__ method is necessary - # to avoid triggering the plugin mechanism of the base surface class - def __new__(cls, *args, **kwargs): - surface = object.__new__(cls) - surface.__init__(*args, **kwargs) - return surface - DATASCHEMA = { "type": "object", "properties": { diff --git a/src/compas/geometry/surfaces/spherical.py b/src/compas/geometry/surfaces/spherical.py index 1530b776f3a..4f3f92acd73 100644 --- a/src/compas/geometry/surfaces/spherical.py +++ b/src/compas/geometry/surfaces/spherical.py @@ -37,13 +37,6 @@ class SphericalSurface(Surface): """ - # overwriting the __new__ method is necessary - # to avoid triggering the plugin mechanism of the base surface class - def __new__(cls, *args, **kwargs): - surface = object.__new__(cls) - surface.__init__(*args, **kwargs) - return surface - DATASCHEMA = { "type": "object", "properties": { diff --git a/src/compas/geometry/surfaces/toroidal.py b/src/compas/geometry/surfaces/toroidal.py index f955c33b78a..d239749a539 100644 --- a/src/compas/geometry/surfaces/toroidal.py +++ b/src/compas/geometry/surfaces/toroidal.py @@ -35,13 +35,6 @@ class ToroidalSurface(Surface): """ - # overwriting the __new__ method is necessary - # to avoid triggering the plugin mechanism of the base surface class - def __new__(cls, *args, **kwargs): - surface = object.__new__(cls) - surface.__init__(*args, **kwargs) - return surface - DATASCHEMA = { "type": "object", "properties": { From b2c69f6089d8c9c2e370a1e76fd186ca70defb5e Mon Sep 17 00:00:00 2001 From: tomvanmele Date: Wed, 3 Jul 2024 22:05:42 +0200 Subject: [PATCH 3/9] reorder and restructure factory methods --- src/compas/geometry/surfaces/nurbs.py | 282 ++++++++++++++++++++------ 1 file changed, 223 insertions(+), 59 deletions(-) diff --git a/src/compas/geometry/surfaces/nurbs.py b/src/compas/geometry/surfaces/nurbs.py index 23f7d9a8f86..68640ad80bb 100644 --- a/src/compas/geometry/surfaces/nurbs.py +++ b/src/compas/geometry/surfaces/nurbs.py @@ -17,27 +17,62 @@ def new_nurbssurface(cls, *args, **kwargs): @pluggable(category="factories") -def new_nurbssurface_from_native(cls, *args, **kwargs): +def nurbssurface_from_cylinder(cls, *args, **kwargs): raise PluginNotInstalledError @pluggable(category="factories") -def new_nurbssurface_from_parameters(cls, *args, **kwargs): +def nurbssurface_from_extrusion(cls, *args, **kwargs): raise PluginNotInstalledError @pluggable(category="factories") -def new_nurbssurface_from_points(cls, *args, **kwargs): +def nurbssurface_from_fill(cls, *args, **kwargs): raise PluginNotInstalledError @pluggable(category="factories") -def new_nurbssurface_from_fill(cls, *args, **kwargs): +def nurbssurface_from_frame(cls, *args, **kwargs): raise PluginNotInstalledError @pluggable(category="factories") -def new_nurbssurface_from_step(cls, *args, **kwargs): +def nurbssurface_from_interpolation(cls, *args, **kwargs): + raise PluginNotInstalledError + + +@pluggable(category="factories") +def nurbssurface_from_parameters(cls, *args, **kwargs): + raise PluginNotInstalledError + + +@pluggable(category="factories") +def nurbssurface_from_plane(cls, *args, **kwargs): + raise PluginNotInstalledError + + +@pluggable(category="factories") +def nurbssurface_from_points(cls, *args, **kwargs): + raise PluginNotInstalledError + + +@pluggable(category="factories") +def nurbssurface_from_revolution(cls, *args, **kwargs): + raise PluginNotInstalledError + + +@pluggable(category="factories") +def nurbssurface_from_sphere(cls, *args, **kwargs): + raise PluginNotInstalledError + + +@pluggable(category="factories") +def nurbssurface_from_step(cls, *args, **kwargs): + raise PluginNotInstalledError + + +@pluggable(category="factories") +def nurbssurface_from_torus(cls, *args, **kwargs): raise PluginNotInstalledError @@ -95,7 +130,7 @@ def __dtype__(self): @property def __data__(self): return { - "points": [point.__data__ for point in self.points], + "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, @@ -109,25 +144,46 @@ def __data__(self): @classmethod def __from_data__(cls, data): + """Construct a Nurbs surface from its data representation. + + Parameters + ---------- + data : dict + The data dictionary. + + Returns + ------- + :class:`compas.geometry.NurbsSurface` + 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( - 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"], + points, + weights, + knots_u, + knots_v, + mults_u, + mults_v, + degree_u, + degree_v, + is_periodic_u, + is_periodic_v, ) + # this may not really be necessary def __new__(cls, *args, **kwargs): return new_nurbssurface(cls, *args, **kwargs) - def __init__(self, name=None): - super(NurbsSurface, self).__init__(name=name) - def __repr__(self): return "{0}(points={1!r}, weigths={2}, knots_u={3}, knots_v={4}, mults_u={5}, mults_v={6}, degree_u={7}, degree_v={8}, is_periodic_u={9}, is_periodic_v={10})".format( type(self).__name__, @@ -187,26 +243,139 @@ def degree_u(self): def degree_v(self): raise NotImplementedError + @property + def domain_u(self): + raise NotImplementedError + + @property + def domain_v(self): + raise NotImplementedError + # ============================================================================== # Constructors # ============================================================================== @classmethod - def from_native(cls, surface): - """Construct a NURBS surface from a surface object. + def from_cylinder(cls, cylinder, *args, **kwargs): + """Construct a surface from a sphere. Parameters ---------- - surface : :class:`Rhino.Geometry.NurbsSurface` - A CAD native surface object. + cylinder : :class:`compas.geometry.Cylinder` + The cylinder. Returns ------- :class:`compas.geometry.NurbsSurface` - A COMPAS NURBS surface. """ - return new_nurbssurface_from_native(cls, surface) + return nurbssurface_from_cylinder(cls, cylinder, *args, **kwargs) + + @classmethod + def from_extrusion(cls, curve, vector, *args, **kwargs): + """Construct a NURBS surface from an extrusion of a basis curve. + + Note that the extrusion surface is constructed by generating an infill + between the basis curve and a translated copy with :meth:`from_fill`. + + Parameters + ---------- + curve : :class:`compas.geometry.Curve` + The basis curve for the extrusion. + vector : :class:`compas.geometry.Vector` + The extrusion vector, which serves as a translation vector for the basis curve. + + Returns + ------- + :class:`compas.geometry.NurbsSurface` + + """ + return nurbssurface_from_extrusion(cls, curve, vector, *args, **kwargs) + + @classmethod + def from_fill(cls, curve1, curve2, curve3=None, curve4=None, style="stretch"): + """Construct a NURBS surface from the infill between two, three or four contiguous NURBS curves. + + Parameters + ---------- + curve1 : :class:`compas.geometry.NurbsCurve` + curve2 : :class:`compas.geometry.NurbsCurve` + curve3 : :class:`compas.geometry.NurbsCurve`, optional. + curve4 : :class:`compas.geometry.NurbsCurve`, optional. + style : Literal['stretch', 'coons', 'curved'], optional. + + * ``'stretch'`` produces the flattest patch. + * ``'curved'`` produces a rounded patch. + * ``'coons'`` is between stretch and coons. + + Raises + ------ + ValueError + If the fill style is not supported. + + Returns + ------- + :class:`compas.geometry.NurbsSurface` + + """ + return nurbssurface_from_fill(cls, curve1, curve2, curve3, curve4, style) + + @classmethod + def from_frame(cls, frame, *args, **kwargs): + """Construct a surface from a frame. + + Parameters + ---------- + frame : :class:`compas.geometry.Frame` + The plane. + + Returns + ------- + :class:`compas.geometry.NurbsSurface` + + """ + return nurbssurface_from_frame(cls, frame, *args, **kwargs) + + @classmethod + def from_interpolation(cls, points, *args, **kwargs): + """Construct a surface from a frame. + + Parameters + ---------- + points : list[:class:`compas.geometry.Point`] + The interpolation points. + + Returns + ------- + :class:`compas.geometry.NurbsSurface` + + """ + return nurbssurface_from_interpolation(cls, points, *args, **kwargs) + + @classmethod + def from_meshgrid(cls, nu=10, nv=10): + """Construct a NURBS surface from a mesh grid. + + Parameters + ---------- + nu : int, optional + Number of control points in the U direction. + nv : int, optional + Number of control points in the V direction. + + Returns + ------- + :class:`compas.geometry.NurbsSurface` + + """ + UU, VV = meshgrid(linspace(0, nu, nu + 1), linspace(0, nv, nv + 1)) + points = [] + for U, V in zip(UU, VV): + row = [] + for u, v in zip(U, V): + row.append(Point(u, v, 0.0)) + points.append(row) + return cls.from_points(points=points) @classmethod def from_parameters( @@ -248,7 +417,7 @@ def from_parameters( :class:`compas.geometry.NurbsSurface` """ - return new_nurbssurface_from_parameters( + return nurbssurface_from_parameters( cls, points, weights, @@ -262,6 +431,22 @@ def from_parameters( is_periodic_v=is_periodic_v, ) + @classmethod + def from_plane(cls, plane, *args, **kwargs): + """Construct a surface from a plane. + + Parameters + ---------- + plane : :class:`compas.geometry.Plane` + The plane. + + Returns + ------- + :class:`compas.geometry.NurbsSurface` + + """ + return nurbssurface_from_plane(cls, plane, *args, **kwargs) + @classmethod def from_points(cls, points, degree_u=3, degree_v=3): """Construct a NURBS surface from control points. @@ -280,32 +465,23 @@ def from_points(cls, points, degree_u=3, degree_v=3): :class:`compas.geometry.NurbsSurface` """ - return new_nurbssurface_from_points(cls, points, degree_u=degree_u, degree_v=degree_v) + return nurbssurface_from_points(cls, points, degree_u=degree_u, degree_v=degree_v) @classmethod - def from_meshgrid(cls, nu=10, nv=10): - """Construct a NURBS surface from a mesh grid. + def from_sphere(cls, sphere, *args, **kwargs): + """Construct a surface from a sphere. Parameters ---------- - nu : int, optional - Number of control points in the U direction. - nv : int, optional - Number of control points in the V direction. + sphere : :class:`compas.geometry.Sphere` + The sphere. Returns ------- :class:`compas.geometry.NurbsSurface` """ - UU, VV = meshgrid(linspace(0, nu, nu + 1), linspace(0, nv, nv + 1)) - points = [] - for U, V in zip(UU, VV): - row = [] - for u, v in zip(U, V): - row.append(Point(u, v, 0.0)) - points.append(row) - return cls.from_points(points=points) + return nurbssurface_from_sphere(cls, sphere, *args, **kwargs) @classmethod def from_step(cls, filepath): @@ -320,35 +496,23 @@ def from_step(cls, filepath): :class:`compas.geometry.NurbsSurface` """ - return new_nurbssurface_from_step(cls, filepath) + return nurbssurface_from_step(cls, filepath) @classmethod - def from_fill(cls, curve1, curve2, curve3=None, curve4=None, style="stretch"): - """Construct a NURBS surface from the infill between two, three or four contiguous NURBS curves. + def from_torus(cls, torus, *args, **kwargs): + """Construct a surface from a torus. Parameters ---------- - curve1 : :class:`compas.geometry.NurbsCurve` - curve2 : :class:`compas.geometry.NurbsCurve` - curve3 : :class:`compas.geometry.NurbsCurve`, optional. - curve4 : :class:`compas.geometry.NurbsCurve`, optional. - style : Literal['stretch', 'coons', 'curved'], optional. - - * ``'stretch'`` produces the flattest patch. - * ``'curved'`` produces a rounded patch. - * ``'coons'`` is between stretch and coons. - - Raises - ------ - ValueError - If the fill style is not supported. + torus : :class:`compas.geometry.Torus` + The torus. Returns ------- :class:`compas.geometry.NurbsSurface` """ - return new_nurbssurface_from_fill(cls, curve1, curve2, curve3, curve4, style) + return nurbssurface_from_torus(cls, torus, *args, **kwargs) # ============================================================================== # Conversions From e5cfc234f589fb3a3d48b2f75cd0f67e3a6bc076 Mon Sep 17 00:00:00 2001 From: tomvanmele Date: Wed, 3 Jul 2024 22:06:04 +0200 Subject: [PATCH 4/9] use new_ only when it is a new overwrite --- .../geometry/surfaces/__init__.py | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/__init__.py b/src/compas_rhino/geometry/surfaces/__init__.py index 30081543e44..fed416471a8 100644 --- a/src/compas_rhino/geometry/surfaces/__init__.py +++ b/src/compas_rhino/geometry/surfaces/__init__.py @@ -1,50 +1,61 @@ 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 @plugin(category="factories", requires=["Rhino"]) -def new_surface(cls, *args, **kwargs): - surface = super(Surface, cls).__new__(cls) - surface.__init__(*args, **kwargs) - return surface +def surface_from_native(cls, *args, **kwargs): + return RhinoSurface.from_native(*args, **kwargs) +# this may not really be necessary @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) +def nurbssurface_from_cylinder(cls, *args, **kwargs): + return RhinoNurbsSurface.from_cylinder(*args, **kwargs) @plugin(category="factories", requires=["Rhino"]) -def new_nurbssurface_from_parameters(cls, *args, **kwargs): +def nurbssurface_from_fill(cls, *args, **kwargs): + return RhinoNurbsSurface.from_fill(*args, **kwargs) + + +@plugin(category="factories", requires=["Rhino"]) +def nurbssurface_from_frame(cls, *args, **kwargs): + return RhinoNurbsSurface.from_frame(*args, **kwargs) + + +@plugin(category="factories", requires=["Rhino"]) +def nurbssurface_from_parameters(cls, *args, **kwargs): return RhinoNurbsSurface.from_parameters(*args, **kwargs) @plugin(category="factories", requires=["Rhino"]) -def new_nurbssurface_from_plane(cls, *args, **kwargs): +def nurbssurface_from_plane(cls, *args, **kwargs): return RhinoNurbsSurface.from_plane(*args, **kwargs) @plugin(category="factories", requires=["Rhino"]) -def new_nurbssurface_from_points(cls, *args, **kwargs): +def nurbssurface_from_points(cls, *args, **kwargs): return RhinoNurbsSurface.from_points(*args, **kwargs) @plugin(category="factories", requires=["Rhino"]) -def new_nurbssurface_from_fill(cls, *args, **kwargs): - return RhinoNurbsSurface.from_fill(*args, **kwargs) +def nurbssurface_from_sphere(cls, *args, **kwargs): + return RhinoNurbsSurface.from_sphere(*args, **kwargs) @plugin(category="factories", requires=["Rhino"]) -def new_nurbssurface_from_step(cls, *args, **kwargs): +def nurbssurface_from_step(cls, *args, **kwargs): return RhinoNurbsSurface.from_step(*args, **kwargs) + + +@plugin(category="factories", requires=["Rhino"]) +def nurbssurface_from_torus(cls, *args, **kwargs): + return RhinoNurbsSurface.from_torus(*args, **kwargs) From b54ad28adadec84c8ea4214082f3e307a4e8d276 Mon Sep 17 00:00:00 2001 From: tomvanmele Date: Wed, 3 Jul 2024 22:06:32 +0200 Subject: [PATCH 5/9] fix bugs, use native_, restructure --- src/compas_rhino/geometry/surfaces/nurbs.py | 201 +++++++++++++----- src/compas_rhino/geometry/surfaces/surface.py | 186 ++++------------ 2 files changed, 191 insertions(+), 196 deletions(-) diff --git a/src/compas_rhino/geometry/surfaces/nurbs.py b/src/compas_rhino/geometry/surfaces/nurbs.py index 037263cff99..f682982fa69 100644 --- a/src/compas_rhino/geometry/surfaces/nurbs.py +++ b/src/compas_rhino/geometry/surfaces/nurbs.py @@ -7,11 +7,14 @@ 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 cylinder_to_rhino +from compas_rhino.conversions import frame_to_rhino_plane +from compas_rhino.conversions import plane_to_rhino from compas_rhino.conversions import point_to_compas from compas_rhino.conversions import point_to_rhino +from compas_rhino.conversions import sphere_to_rhino from .surface import RhinoSurface @@ -133,10 +136,6 @@ class RhinoNurbsSurface(RhinoSurface, NurbsSurface): """ - def __init__(self, name=None): - super(RhinoNurbsSurface, self).__init__(name=name) - self._points = None - # ============================================================================== # Data # ============================================================================== @@ -165,44 +164,6 @@ def __data__(self): "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, - ) - # ============================================================================== # Properties # ============================================================================== @@ -210,7 +171,7 @@ def __from_data__(cls, data): @property def points(self): if self.rhino_surface: - if not self._points: + if not hasattr(self, "_points"): self._points = ControlPoints(self.rhino_surface) return self._points @@ -269,6 +230,95 @@ def degree_v(self): # Constructors # ============================================================================== + @classmethod + def from_corners(cls, corners): + """Creates a NURBS surface using the given 4 corners. + + The order of the given points determins the normal direction of the generated surface. + + Parameters + ---------- + corners : list(:class:`compas.geometry.Point`) + 4 points in 3d space to represent the corners of the planar surface. + + Returns + ------- + :class:`compas_rhino.geometry.RhinoNurbsSurface` + + """ + rhino_points = [Rhino.Geometry.Point3d(corner.x, corner.y, corner.z) for corner in corners] + return cls.from_native(Rhino.Geometry.NurbsSurface.CreateFromCorners(*rhino_points)) + + @classmethod + def from_cylinder(cls, cylinder): + """Create a NURBS surface from a cylinder. + + Parameters + ---------- + cylinder : :class:`compas.geometry.Cylinder` + The surface's geometry. + + Returns + ------- + :class:`compas_rhino.geometry.RhinoNurbsSurface` + + """ + cylinder = cylinder_to_rhino(cylinder) + surface = Rhino.Geometry.NurbsSurface.CreateFromCylinder(cylinder) + return cls.from_native(surface) + + @classmethod + def from_fill(cls, curve1, curve2): + """Construct a NURBS surface from the infill between two NURBS curves. + + Parameters + ---------- + curve1 : :class:`compas.geometry.NurbsCurve` + curve2 : :class:`compas.geometry.NurbsCurve` + + Returns + ------- + :class:`compas_rhino.geometry.RhinoNurbsSurface` + + """ + surface = cls() + # these curves probably need to be processed first + surface.rhino_surface = Rhino.Geometry.NurbsSurface.CreateRuledSurface(curve1, curve2) + return surface + + @classmethod + def from_frame(cls, frame, domain_u=(0, 1), domain_v=(0, 1), degree_u=1, degree_v=1, pointcount_u=2, pointcount_v=2): + """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. + domain_u : tuple[int, int], optional + The domain of the U parameter. + domain_v : tuple[int, int], optional + The domain of the V parameter. + degree_u : int, optional + Degree in the U direction. + degree_v : int, optional + Degree in the V direction. + pointcount_u : int, optional + Number of control points in the U direction. + pointcount_v : int, optional + Number of control points in the V direction. + + Returns + ------- + :class:`compas_rhino.geometry.RhinoNurbsSurface` + + """ + plane = frame_to_rhino_plane(frame) + du = Rhino.Geometry.Interval(*domain_u) + dv = Rhino.Geometry.Interval(*domain_v) + rhino_surface = Rhino.Geometry.NurbsSurface.CreateFromPlane(plane, du, dv, degree_u, degree_v, pointcount_u, pointcount_v) + return cls.from_native(rhino_surface) + @classmethod def from_parameters( cls, @@ -313,6 +363,38 @@ def from_parameters( surface.rhino_surface = rhino_surface_from_parameters(points, weights, knots_u, knots_v, mults_u, mults_v, degree_u, degree_v) return surface + @classmethod + def from_plane(cls, plane, domain_u=(0, 1), domain_v=(0, 1), degree_u=1, degree_v=1, pointcount_u=2, pointcount_v=2): + """Construct a surface from a plane. + + Parameters + ---------- + plane : :class:`compas.geometry.Plane` + The plane. + domain_u : tuple[int, int], optional + The domain of the U parameter. + domain_v : tuple[int, int], optional + The domain of the V parameter. + degree_u : int, optional + Degree in the U direction. + degree_v : int, optional + Degree in the V direction. + pointcount_u : int, optional + Number of control points in the U direction. + pointcount_v : int, optional + Number of control points in the V direction. + + Returns + ------- + :class:`compas_rhino.geometry.RhinoNurbsSurface` + + """ + plane = plane_to_rhino(plane) + du = Rhino.Geometry.Interval(*domain_u) + dv = Rhino.Geometry.Interval(*domain_v) + rhino_surface = Rhino.Geometry.NurbsSurface.CreateFromPlane(plane, du, dv, degree_u, degree_v, pointcount_u, pointcount_v) + return cls.from_native(rhino_surface) + @classmethod def from_points(cls, points, degree_u=3, degree_v=3): """Construct a NURBS surface from control points. @@ -343,23 +425,38 @@ def from_points(cls, points, degree_u=3, degree_v=3): return surface @classmethod - def from_fill(cls, curve1, curve2): - """Construct a NURBS surface from the infill between two NURBS curves. + def from_sphere(cls, sphere): + """Creates a NURBS surface from a sphere. Parameters ---------- - curve1 : :class:`compas.geometry.NurbsCurve` - curve2 : :class:`compas.geometry.NurbsCurve` + sphere : :class:`compas.geometry.Sphere` + The surface's geometry. Returns ------- :class:`compas_rhino.geometry.RhinoNurbsSurface` """ - surface = cls() - # these curves probably need to be processed first - surface.rhino_surface = Rhino.Geometry.NurbsSurface.CreateRuledSurface(curve1, curve2) - return surface + sphere = sphere_to_rhino(sphere) + surface = Rhino.Geometry.NurbsSurface.CreateFromSphere(sphere) + return cls.from_native(surface) + + @classmethod + def from_torus(cls, torus): + """Create a NURBS surface from a torus. + + Parameters + ---------- + torus : :class:`compas.geometry.Torus` + The surface's geometry. + + Returns + ------- + :class:`compas_rhino.geometry.RhinoNurbsSurface` + + """ + raise NotImplementedError # ============================================================================== # Conversions diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index 36fa127a0fa..7a69c72461b 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -6,13 +6,9 @@ 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 compas_rhino.conversions import point_to_rhino -from compas_rhino.conversions import sphere_to_rhino from compas_rhino.conversions import transformation_to_rhino from compas_rhino.conversions import vector_to_compas from compas_rhino.geometry.curves import RhinoCurve @@ -34,21 +30,25 @@ class RhinoSurface(Surface): """ - def __init__(self, name=None): + def __init__(self, rhino_surface, name=None): super(RhinoSurface, self).__init__(name=name) - self._rhino_surface = None + self._native_surface = rhino_surface @property def rhino_surface(self): - return self._rhino_surface + return self._native_surface @rhino_surface.setter def rhino_surface(self, surface): - self._rhino_surface = surface + self._native_surface = surface - # ============================================================================== - # Data - # ============================================================================== + @property + def native_surface(self): + return self._native_surface + + @native_surface.setter + def native_surface(self, surface): + self._native_surface = surface # ============================================================================== # Properties @@ -56,101 +56,30 @@ def rhino_surface(self, surface): @property def domain_u(self): - if self.rhino_surface: - return self.rhino_surface.Domain(0) + if self.native_surface: + return self.native_surface.Domain(0) @property def domain_v(self): - if self.rhino_surface: - return self.rhino_surface.Domain(1) + if self.native_surface: + return self.native_surface.Domain(1) @property def is_periodic_u(self): - if self.rhino_surface: - return self.rhino_surface.IsPeriodic(0) + if self.native_surface: + return self.native_surface.IsPeriodic(0) @property def is_periodic_v(self): - if self.rhino_surface: - return self.rhino_surface.IsPeriodic(1) + if self.native_surface: + return self.native_surface.IsPeriodic(1) # ============================================================================== # Constructors # ============================================================================== @classmethod - def from_corners(cls, corners): - """Creates a NURBS surface using the given 4 corners. - - The order of the given points determins the normal direction of the generated surface. - - Parameters - ---------- - corners : list(:class:`compas.geometry.Point`) - 4 points in 3d space to represent the corners of the planar surface. - - Returns - ------- - :class:`compas_rhino.geometry.RhinoNurbsSurface` - - """ - 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)) - - @classmethod - def from_sphere(cls, sphere): - """Creates a NURBS surface from a sphere. - - Parameters - ---------- - sphere : :class:`compas.geometry.Sphere` - The surface's geometry. - - Returns - ------- - :class:`compas_rhino.geometry.RhinoNurbsSurface` - - """ - sphere = sphere_to_rhino(sphere) - surface = Rhino.Geometry.NurbsSurface.CreateFromSphere(sphere) - return cls.from_rhino(surface) - - @classmethod - def from_cylinder(cls, cylinder): - """Create a NURBS surface from a cylinder. - - Parameters - ---------- - cylinder : :class:`compas.geometry.Cylinder` - The surface's geometry. - - Returns - ------- - :class:`compas_rhino.geometry.RhinoNurbsSurface` - - """ - cylinder = cylinder_to_rhino(cylinder) - surface = Rhino.Geometry.NurbsSurface.CreateFromCylinder(cylinder) - return cls.from_rhino(surface) - - @classmethod - def from_torus(cls, torus): - """Create a NURBS surface from a torus. - - Parameters - ---------- - torus : :class:`compas.geometry.Torus` - The surface's geometry. - - Returns - ------- - :class:`compas_rhino.geometry.RhinoNurbsSurface` - - """ - raise NotImplementedError - - @classmethod - def from_rhino(cls, rhino_surface): + def from_native(cls, rhino_surface): """Construct a NURBS surface from an existing Rhino surface. Parameters @@ -163,57 +92,28 @@ def from_rhino(cls, rhino_surface): :class:`compas_rhino.geometry.RhinoSurface` """ - curve = cls() - curve.rhino_surface = rhino_surface - return curve + return cls(rhino_surface) @classmethod - def from_plane(cls, plane, box): - """Construct a surface from a plane. + def from_rhino(cls, rhino_surface): + """Construct a NURBS surface from an existing Rhino surface. Parameters ---------- - plane : :class:`compas.geometry.Plane` - The plane. + rhino_surface : :rhino:`Rhino.Geometry.Surface` + A Rhino surface. Returns ------- :class:`compas_rhino.geometry.RhinoSurface` - """ - 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) - return cls.from_rhino(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` + Warnings + -------- + .. deprecated:: 2.3 + Use `from_native` instead. """ - 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_rhino(surface) + return cls(rhino_surface) # ============================================================================== # Conversions @@ -232,9 +132,7 @@ def copy(self): """ cls = type(self) - surface = cls() - surface.rhino_surface = self.rhino_surface.Duplicate() # type: ignore - return surface + return cls(self.native_surface.Duplicate()) def transform(self, T): """Transform this surface. @@ -249,9 +147,9 @@ def transform(self, T): None """ - self.rhino_surface.Transform(transformation_to_rhino(T)) # type: ignore + self.native_surface.Transform(transformation_to_rhino(T)) # type: ignore - def u_isocurve(self, u): + def isocurve_u(self, u): """Compute the isoparametric curve at parameter u. Parameters @@ -263,10 +161,10 @@ def u_isocurve(self, u): :class:`compas_rhino.geometry.RhinoCurve` """ - curve = self.rhino_surface.IsoCurve(1, u) # type: ignore + curve = self.native_surface.IsoCurve(1, u) # type: ignore return RhinoCurve.from_rhino(curve) - def v_isocurve(self, v): + def isocurve_v(self, v): """Compute the isoparametric curve at parameter v. Parameters @@ -278,7 +176,7 @@ def v_isocurve(self, v): :class:`compas_rhino.geometry.RhinoCurve` """ - curve = self.rhino_surface.IsoCurve(0, v) # type: ignore + curve = self.native_surface.IsoCurve(0, v) # type: ignore return RhinoCurve.from_rhino(curve) def point_at(self, u, v): @@ -294,7 +192,7 @@ def point_at(self, u, v): :class:`compas.geometry.Point` """ - point = self.rhino_surface.PointAt(u, v) # type: ignore + point = self.native_surface.PointAt(u, v) # type: ignore return point_to_compas(point) def curvature_at(self, u, v): @@ -313,7 +211,7 @@ def curvature_at(self, u, v): value for the point at UV. None at failure. """ - surface_curvature = self.rhino_surface.CurvatureAt(u, v) # type: ignore + surface_curvature = self.native_surface.CurvatureAt(u, v) # type: ignore if surface_curvature: point, normal, kappa_u, direction_u, kappa_v, direction_v, gaussian, mean = surface_curvature cpoint = point_to_compas(point) @@ -335,7 +233,7 @@ def frame_at(self, u, v): :class:`compas.geometry.Frame` """ - result, plane = self.rhino_surface.FrameAt(u, v) # type: ignore + result, plane = self.native_surface.FrameAt(u, v) # type: ignore if result: return plane_to_compas_frame(plane) @@ -361,7 +259,7 @@ def closest_point(self, point, return_parameters=False): If `return_parameters` is True. """ - result, u, v = self.rhino_surface.ClosestPoint(point_to_rhino(point)) # type: ignore + result, u, v = self.native_surface.ClosestPoint(point_to_rhino(point)) # type: ignore if not result: return point = self.point_at(u, v) @@ -383,7 +281,7 @@ def aabb(self, precision=0.0, optimal=False): :class:`compas.geometry.Box` """ - box = self.rhino_surface.GetBoundingBox(optimal) # type: ignore + box = self.native_surface.GetBoundingBox(optimal) # type: ignore return box_to_compas(Rhino.Geometry.Box(box)) def intersections_with_curve(self, curve, tolerance=1e-3, overlap=1e-3): @@ -398,7 +296,7 @@ def intersections_with_curve(self, curve, tolerance=1e-3, overlap=1e-3): list[:class:`compas.geometry.Point`] """ - intersections = Rhino.Geometry.Intersect.Intersection.CurveSurface(curve.rhino_curve, self.rhino_surface, tolerance, overlap) + intersections = Rhino.Geometry.Intersect.Intersection.CurveSurface(curve.rhino_curve, self.native_surface, tolerance, overlap) points = [] for event in intersections: if event.IsPoint: From 006c274b9d33ff7c5ef5536de9662cf57f867721 Mon Sep 17 00:00:00 2001 From: tomvanmele Date: Wed, 3 Jul 2024 22:39:55 +0200 Subject: [PATCH 6/9] log --- CHANGELOG.md | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72bfae95425..6c8f20a46e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,13 +9,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Added `compas.geometry.surfaces.surface.Surface.from_native`. +* Added `compas.geometry.surfaces.nurbs.NurbsSurface.from_plane`. +* Added `compas.geometry.surfaces.nurbs.NurbsSurface.from_cylinder`. +* Added `compas.geometry.surfaces.nurbs.NurbsSurface.from_extrusion`. +* Added `compas.geometry.surfaces.nurbs.NurbsSurface.from_frame`. +* Added `compas.geometry.surfaces.nurbs.NurbsSurface.from_interpolation`. +* Added `compas.geometry.surfaces.nurbs.NurbsSurface.from_revolution`. +* Added `compas.geometry.surfaces.nurbs.NurbsSurface.from_sphere`. +* Added `compas.geometry.surfaces.nurbs.NurbsSurface.from_torus`. +* Added `compas_rhino.geometry.surfaces.surface_from_native`. +* Added `compas_rhino.geometry.surfaces.nurbssurface_from_cylinder`. +* Added `compas_rhino.geometry.surfaces.nurbssurface_from_fill`. +* Added `compas_rhino.geometry.surfaces.nurbssurface_from_torus`. +* Added `compas_rhino.geometry.surfaces.nurbs.NurbsSurface.from_corners`. +* Added `compas_rhino.geometry.surfaces.nurbs.NurbsSurface.from_cylinder`. +* Added `compas_rhino.geometry.surfaces.nurbs.NurbsSurface.from_frame`. +* Added `compas_rhino.geometry.surfaces.nurbs.NurbsSurface.from_sphere`. +* Added `compas_rhino.geometry.surfaces.nurbs.NurbsSurface.from_torus`. + ### Changed * Fixed bug in `compas.geometry.curves.curve.Curve.reversed` by adding missing parenthesis. * Fixed all doctests so we can run `invoke test --doctest`. - -### Removed - +* Changed `compas.geometry.surfaces.surface.Surface.__new__` to prevent instantiation of `compas.geometry.surfaces.surface.Surface` only. +* Fixed bug in `compas.geometry.surfaces.nurbs.NurbsSurface.__data__`. +* Changed `compas.geometry.surfaces.nurbs.new_nurbssurface_from_...` to `compas.geometry.surfaces.nurbs.nurbssurface_from_...`. + +### Removed + +* Removed pluggable `compas.geometry.surfaces.surface.new_surface`. +* Removed pluggable `compas.geometry.surfaces.surface.new_surface_from_plane`. +* Removed `compas.geometry.surfaces.surface.Surface.from_plane`. +* Removed `compas.geometry.surfaces.surface.ConicalSurface.__new__`. +* Removed `compas.geometry.surfaces.surface.CylindricalSurface.__new__`. +* Removed `compas.geometry.surfaces.surface.PlanarSurface.__new__`. +* Removed `compas.geometry.surfaces.surface.SphericalSurface.__new__`. +* Removed `compas.geometry.surfaces.surface.ToroidalSurface.__new__`. +* Removed `compas.geometry.surfaces.nurbs.NurbsSurface.__init__`. +* Removed `compas_rhino.geometry.surfaces.new_surface`. +* Removed `compas_rhino.geometry.surfaces.nurbs.NurbsSurface.__from_data__`. +* Removed `compas_rhino.geometry.surfaces.surface.Surface.from_corners`. +* Removed `compas_rhino.geometry.surfaces.surface.Surface.from_cylinder`. +* Removed `compas_rhino.geometry.surfaces.surface.Surface.from_frame`. +* Removed `compas_rhino.geometry.surfaces.surface.Surface.from_sphere`. +* Removed `compas_rhino.geometry.surfaces.surface.Surface.from_torus`. ## [2.2.1] 2024-06-25 From 8edb83dbbc350b26640a01472df9c764f2a6ed2f Mon Sep 17 00:00:00 2001 From: tomvanmele Date: Thu, 4 Jul 2024 11:22:29 +0200 Subject: [PATCH 7/9] last fixes --- CHANGELOG.md | 5 +- src/compas/geometry/surfaces/nurbs.py | 33 ++++-- src/compas/geometry/surfaces/surface.py | 5 +- .../geometry/surfaces/__init__.py | 12 +- src/compas_rhino/geometry/surfaces/nurbs.py | 112 ++++++++++-------- src/compas_rhino/geometry/surfaces/surface.py | 31 ++--- 6 files changed, 114 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78d63a0d4e4..16409feb29e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added `compas.geometry.surfaces.nurbs.NurbsSurface.from_sphere`. * Added `compas.geometry.surfaces.nurbs.NurbsSurface.from_torus`. * Added `compas_rhino.geometry.surfaces.surface_from_native`. +* Added `compas_rhino.geometry.surfaces.nurbssurface_from_native`. * Added `compas_rhino.geometry.surfaces.nurbssurface_from_cylinder`. * Added `compas_rhino.geometry.surfaces.nurbssurface_from_fill`. * Added `compas_rhino.geometry.surfaces.nurbssurface_from_torus`. @@ -33,7 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed bug in `compas.geometry.curves.curve.Curve.reversed` by adding missing parenthesis. * Fixed all doctests so we can run `invoke test --doctest`. -* Changed `compas.geometry.surfaces.surface.Surface.__new__` to prevent instantiation of `compas.geometry.surfaces.surface.Surface` only. +* Changed `compas.geometry.surfaces.surface.Surface.__new__` to prevent instantiation of `compas.geometry.surfaces.surface.Surface` directly. +* Changed `compas.geometry.surfaces.nurbs.NurbsSurface.__new__` to prevent instantiation of `compas.geometry.surfaces.nurbs.NurbsSurface` directly. * Fixed bug in `compas.geometry.surfaces.nurbs.NurbsSurface.__data__`. * Changed `compas.geometry.surfaces.nurbs.new_nurbssurface_from_...` to `compas.geometry.surfaces.nurbs.nurbssurface_from_...`. @@ -49,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Removed `compas.geometry.surfaces.surface.ToroidalSurface.__new__`. * Removed `compas.geometry.surfaces.nurbs.NurbsSurface.__init__`. * Removed `compas_rhino.geometry.surfaces.new_surface`. +* Removed `compas_rhino.geometry.surfaces.new_nurbssurface`. * Removed `compas_rhino.geometry.surfaces.nurbs.NurbsSurface.__from_data__`. * Removed `compas_rhino.geometry.surfaces.surface.Surface.from_corners`. * Removed `compas_rhino.geometry.surfaces.surface.Surface.from_cylinder`. diff --git a/src/compas/geometry/surfaces/nurbs.py b/src/compas/geometry/surfaces/nurbs.py index 68640ad80bb..eb69538a947 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 nurbssurface_from_cylinder(cls, *args, **kwargs): raise PluginNotInstalledError @@ -41,6 +36,11 @@ def nurbssurface_from_interpolation(cls, *args, **kwargs): raise PluginNotInstalledError +@pluggable(category="factories") +def nurbssurface_from_native(cls, *args, **kwargs): + raise PluginNotInstalledError + + @pluggable(category="factories") def nurbssurface_from_parameters(cls, *args, **kwargs): raise PluginNotInstalledError @@ -157,7 +157,7 @@ def __from_data__(cls, data): The constructed surface. """ - points = [[Point.__from_data__(point) for point in row] for row in data["points"]] + points = data["points"] # conversion is not needed because point data can be provided in their raw form as well weights = data["weights"] knots_u = data["knots_u"] knots_v = data["knots_v"] @@ -180,9 +180,10 @@ def __from_data__(cls, data): is_periodic_v, ) - # this may not really be necessary def __new__(cls, *args, **kwargs): - return new_nurbssurface(cls, *args, **kwargs) + if cls is NurbsSurface: + raise TypeError("Instantiating the base NURBS Surface class directly is not allowed.") + return object.__new__(cls) def __repr__(self): return "{0}(points={1!r}, weigths={2}, knots_u={3}, knots_v={4}, mults_u={5}, mults_v={6}, degree_u={7}, degree_v={8}, is_periodic_u={9}, is_periodic_v={10})".format( @@ -377,6 +378,22 @@ def from_meshgrid(cls, nu=10, nv=10): points.append(row) return cls.from_points(points=points) + @classmethod + def from_native(cls, surface): + """Construct a NURBS surface from a native surface geometry. + + Parameters + ---------- + surface + A CAD native surface object. + + Returns + ------- + :class:`compas.geometry.NurbsSurface` + + """ + raise NotImplementedError + @classmethod def from_parameters( cls, diff --git a/src/compas/geometry/surfaces/surface.py b/src/compas/geometry/surfaces/surface.py index 22250bfbe60..d83f1fb370a 100644 --- a/src/compas/geometry/surfaces/surface.py +++ b/src/compas/geometry/surfaces/surface.py @@ -145,9 +145,6 @@ def is_periodic_v(self): # Constructors # ============================================================================== - # these probably need to be moved to Nurbs - # i don't think you can store a "general" parametric surface in a file - @classmethod def from_native(cls, surface): """Construct a parametric surface from a native surface geometry. @@ -163,7 +160,7 @@ def from_native(cls, surface): A COMPAS surface. """ - return surface_from_native(cls, surface) + raise NotImplementedError @classmethod def from_obj(cls, filepath): diff --git a/src/compas_rhino/geometry/surfaces/__init__.py b/src/compas_rhino/geometry/surfaces/__init__.py index fed416471a8..026f8a5ccc1 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 NurbsSurface from compas.plugins import plugin @@ -10,12 +9,6 @@ def surface_from_native(cls, *args, **kwargs): return RhinoSurface.from_native(*args, **kwargs) -# this may not really be necessary -@plugin(category="factories", requires=["Rhino"]) -def new_nurbssurface(cls, *args, **kwargs): - return super(NurbsSurface, cls).__new__(cls) - - @plugin(category="factories", requires=["Rhino"]) def nurbssurface_from_cylinder(cls, *args, **kwargs): return RhinoNurbsSurface.from_cylinder(*args, **kwargs) @@ -31,6 +24,11 @@ def nurbssurface_from_frame(cls, *args, **kwargs): return RhinoNurbsSurface.from_frame(*args, **kwargs) +@plugin(category="factories", requires=["Rhino"]) +def nurbssurface_from_native(cls, *args, **kwargs): + return RhinoNurbsSurface.from_native(*args, **kwargs) + + @plugin(category="factories", requires=["Rhino"]) def nurbssurface_from_parameters(cls, *args, **kwargs): return RhinoNurbsSurface.from_parameters(*args, **kwargs) diff --git a/src/compas_rhino/geometry/surfaces/nurbs.py b/src/compas_rhino/geometry/surfaces/nurbs.py index f682982fa69..8fd7210c635 100644 --- a/src/compas_rhino/geometry/surfaces/nurbs.py +++ b/src/compas_rhino/geometry/surfaces/nurbs.py @@ -21,15 +21,15 @@ class ControlPoints(object): def __init__(self, surface): - self.rhino_surface = surface + self.native_surface = surface @property def points(self): points = [] - for i in range(self.rhino_surface.Points.CountU): + for i in range(self.native_surface.Points.CountU): row = [] - for j in range(self.rhino_surface.Points.CountV): - row.append(point_to_compas(self.rhino_surface.Points.GetControlPoint(i, j).Location)) + for j in range(self.native_surface.Points.CountV): + row.append(point_to_compas(self.native_surface.Points.GetControlPoint(i, j).Location)) points.append(row) return points @@ -39,21 +39,21 @@ def __getitem__(self, index): except TypeError: return self.points[index] else: - point = self.rhino_surface.Points.GetControlPoint(u, v).Location + point = self.native_surface.Points.GetControlPoint(u, v).Location return point_to_compas(point) def __setitem__(self, index, point): u, v = index - self.rhino_surface.Points.SetControlPoint(u, v, Rhino.Geometry.ControlPoint(point_to_rhino(point))) + self.native_surface.Points.SetControlPoint(u, v, Rhino.Geometry.ControlPoint(point_to_rhino(point))) def __len__(self): - return self.rhino_surface.Points.CountU + return self.native_surface.Points.CountU def __iter__(self): return iter(self.points) -def rhino_surface_from_parameters( +def native_surface_from_parameters( points, weights, knots_u, @@ -72,7 +72,7 @@ def rhino_surface_from_parameters( is_rational = any(weight != 1.0 for weight in flatten(weights)) dimensions = 3 - rhino_surface = Rhino.Geometry.NurbsSurface.Create( + native_surface = Rhino.Geometry.NurbsSurface.Create( dimensions, is_rational, order_u, @@ -81,7 +81,7 @@ def rhino_surface_from_parameters( pointcount_v, ) - if not rhino_surface: + if not native_surface: message = "dimensions: {} is_rational: {} order_u: {} order_v: {} u_points: {} v_points: {}".format( dimensions, is_rational, @@ -102,14 +102,14 @@ def rhino_surface_from_parameters( knotvector_v[:] = knotvector_v[1:-1] # add knots for index, knot in enumerate(knotvector_u): - rhino_surface.KnotsU[index] = knot + native_surface.KnotsU[index] = knot for index, knot in enumerate(knotvector_v): - rhino_surface.KnotsV[index] = knot + native_surface.KnotsV[index] = knot # add control points for i in range(pointcount_u): for j in range(pointcount_v): - rhino_surface.Points.SetPoint(i, j, point_to_rhino(points[i][j]), weights[i][j]) - return rhino_surface + native_surface.Points.SetPoint(i, j, point_to_rhino(points[i][j]), weights[i][j]) + return native_surface class RhinoNurbsSurface(RhinoSurface, NurbsSurface): @@ -170,61 +170,61 @@ def __data__(self): @property def points(self): - if self.rhino_surface: + if self.native_surface: if not hasattr(self, "_points"): - self._points = ControlPoints(self.rhino_surface) + self._points = ControlPoints(self.native_surface) return self._points @property def weights(self): - if self.rhino_surface: + if self.native_surface: weights = [] - for i in range(self.rhino_surface.Points.CountU): + for i in range(self.native_surface.Points.CountU): row = [] - for j in range(self.rhino_surface.Points.CountV): - row.append(self.rhino_surface.Points.GetWeight(i, j)) + for j in range(self.native_surface.Points.CountV): + row.append(self.native_surface.Points.GetWeight(i, j)) weights.append(row) return weights @property def knots_u(self): - if self.rhino_surface: - return [key for key, _ in groupby(self.rhino_surface.KnotsU)] + if self.native_surface: + return [key for key, _ in groupby(self.native_surface.KnotsU)] @property def mults_u(self): - if self.rhino_surface: - return [len(list(group)) for _, group in groupby(self.rhino_surface.KnotsU)] + if self.native_surface: + return [len(list(group)) for _, group in groupby(self.native_surface.KnotsU)] @property def knotvector_u(self): - if self.rhino_surface: - return list(self.rhino_surface.KnotsU) + if self.native_surface: + return list(self.native_surface.KnotsU) @property def knots_v(self): - if self.rhino_surface: - return [key for key, _ in groupby(self.rhino_surface.KnotsV)] + if self.native_surface: + return [key for key, _ in groupby(self.native_surface.KnotsV)] @property def mults_v(self): - if self.rhino_surface: - return [len(list(group)) for _, group in groupby(self.rhino_surface.KnotsV)] + if self.native_surface: + return [len(list(group)) for _, group in groupby(self.native_surface.KnotsV)] @property def knotvector_v(self): - if self.rhino_surface: - return list(self.rhino_surface.KnotsV) + if self.native_surface: + return list(self.native_surface.KnotsV) @property def degree_u(self): - if self.rhino_surface: - return self.rhino_surface.Degree(0) + if self.native_surface: + return self.native_surface.Degree(0) @property def degree_v(self): - if self.rhino_surface: - return self.rhino_surface.Degree(1) + if self.native_surface: + return self.native_surface.Degree(1) # ============================================================================== # Constructors @@ -281,10 +281,8 @@ def from_fill(cls, curve1, curve2): :class:`compas_rhino.geometry.RhinoNurbsSurface` """ - surface = cls() - # these curves probably need to be processed first - surface.rhino_surface = Rhino.Geometry.NurbsSurface.CreateRuledSurface(curve1, curve2) - return surface + native_surface = Rhino.Geometry.NurbsSurface.CreateRuledSurface(curve1, curve2) + return cls.from_native(native_surface) @classmethod def from_frame(cls, frame, domain_u=(0, 1), domain_v=(0, 1), degree_u=1, degree_v=1, pointcount_u=2, pointcount_v=2): @@ -316,8 +314,24 @@ def from_frame(cls, frame, domain_u=(0, 1), domain_v=(0, 1), degree_u=1, degree_ plane = frame_to_rhino_plane(frame) du = Rhino.Geometry.Interval(*domain_u) dv = Rhino.Geometry.Interval(*domain_v) - rhino_surface = Rhino.Geometry.NurbsSurface.CreateFromPlane(plane, du, dv, degree_u, degree_v, pointcount_u, pointcount_v) - return cls.from_native(rhino_surface) + native_surface = Rhino.Geometry.NurbsSurface.CreateFromPlane(plane, du, dv, degree_u, degree_v, pointcount_u, pointcount_v) + return cls.from_native(native_surface) + + @classmethod + def from_native(cls, native_surface): + """Construct a NURBS surface from an existing Rhino surface. + + Parameters + ---------- + native_surface : :rhino:`Rhino.Geometry.Surface` + A Rhino surface. + + Returns + ------- + :class:`compas_rhino.geometry.RhinoNurbsSurface` + + """ + return cls(native_surface) @classmethod def from_parameters( @@ -359,9 +373,8 @@ def from_parameters( :class:`compas_rhino.geometry.RhinoNurbsSurface` """ - surface = cls() - surface.rhino_surface = rhino_surface_from_parameters(points, weights, knots_u, knots_v, mults_u, mults_v, degree_u, degree_v) - return surface + native_surface = native_surface_from_parameters(points, weights, knots_u, knots_v, mults_u, mults_v, degree_u, degree_v) + return cls.from_native(native_surface) @classmethod def from_plane(cls, plane, domain_u=(0, 1), domain_v=(0, 1), degree_u=1, degree_v=1, pointcount_u=2, pointcount_v=2): @@ -392,8 +405,8 @@ def from_plane(cls, plane, domain_u=(0, 1), domain_v=(0, 1), degree_u=1, degree_ plane = plane_to_rhino(plane) du = Rhino.Geometry.Interval(*domain_u) dv = Rhino.Geometry.Interval(*domain_v) - rhino_surface = Rhino.Geometry.NurbsSurface.CreateFromPlane(plane, du, dv, degree_u, degree_v, pointcount_u, pointcount_v) - return cls.from_native(rhino_surface) + native_surface = Rhino.Geometry.NurbsSurface.CreateFromPlane(plane, du, dv, degree_u, degree_v, pointcount_u, pointcount_v) + return cls.from_native(native_surface) @classmethod def from_points(cls, points, degree_u=3, degree_v=3): @@ -420,9 +433,8 @@ def from_points(cls, points, degree_u=3, degree_v=3): pointcount_u = len(points) pointcount_v = len(points[0]) points[:] = [point_to_rhino(point) for row in points for point in row] - surface = cls() - surface.rhino_surface = Rhino.Geometry.NurbsSurface.CreateFromPoints(points, pointcount_u, pointcount_v, degree_u, degree_v) - return surface + native_surface = Rhino.Geometry.NurbsSurface.CreateFromPoints(points, pointcount_u, pointcount_v, degree_u, degree_v) + return cls.from_native(native_surface) @classmethod def from_sphere(cls, sphere): diff --git a/src/compas_rhino/geometry/surfaces/surface.py b/src/compas_rhino/geometry/surfaces/surface.py index 7a69c72461b..eb66ca6e836 100644 --- a/src/compas_rhino/geometry/surfaces/surface.py +++ b/src/compas_rhino/geometry/surfaces/surface.py @@ -17,6 +17,13 @@ class RhinoSurface(Surface): """Class representing a general surface object. + Parameters + ---------- + native_surface : :rhino:`Surface` + A Rhino surface. + name : str, optional + The name of the surface. + Attributes ---------- domain_u: tuple[float, float] @@ -30,18 +37,14 @@ class RhinoSurface(Surface): """ - def __init__(self, rhino_surface, name=None): + def __init__(self, native_surface, name=None): super(RhinoSurface, self).__init__(name=name) - self._native_surface = rhino_surface + self._native_surface = native_surface @property def rhino_surface(self): return self._native_surface - @rhino_surface.setter - def rhino_surface(self, surface): - self._native_surface = surface - @property def native_surface(self): return self._native_surface @@ -79,12 +82,12 @@ def is_periodic_v(self): # ============================================================================== @classmethod - def from_native(cls, rhino_surface): - """Construct a NURBS surface from an existing Rhino surface. + def from_native(cls, native_surface): + """Construct a surface from an existing Rhino surface. Parameters ---------- - rhino_surface : :rhino:`Rhino.Geometry.Surface` + native_surface : :rhino:`Rhino.Geometry.Surface` A Rhino surface. Returns @@ -92,15 +95,15 @@ def from_native(cls, rhino_surface): :class:`compas_rhino.geometry.RhinoSurface` """ - return cls(rhino_surface) + return cls(native_surface) @classmethod - def from_rhino(cls, rhino_surface): - """Construct a NURBS surface from an existing Rhino surface. + def from_rhino(cls, native_surface): + """Construct a surface from an existing Rhino surface. Parameters ---------- - rhino_surface : :rhino:`Rhino.Geometry.Surface` + native_surface : :rhino:`Rhino.Geometry.Surface` A Rhino surface. Returns @@ -113,7 +116,7 @@ def from_rhino(cls, rhino_surface): Use `from_native` instead. """ - return cls(rhino_surface) + return cls(native_surface) # ============================================================================== # Conversions From bb31e61941883677121b61b08cd7c46033eb7e66 Mon Sep 17 00:00:00 2001 From: Tom Van Mele Date: Thu, 4 Jul 2024 11:25:14 +0200 Subject: [PATCH 8/9] Update src/compas/geometry/surfaces/nurbs.py Co-authored-by: Chen Kasirer --- src/compas/geometry/surfaces/nurbs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas/geometry/surfaces/nurbs.py b/src/compas/geometry/surfaces/nurbs.py index eb69538a947..b57d6569c36 100644 --- a/src/compas/geometry/surfaces/nurbs.py +++ b/src/compas/geometry/surfaces/nurbs.py @@ -258,7 +258,7 @@ def domain_v(self): @classmethod def from_cylinder(cls, cylinder, *args, **kwargs): - """Construct a surface from a sphere. + """Construct a surface from a cylinder. Parameters ---------- From 0f69306d77612c3dd3ca102081474011122f7726 Mon Sep 17 00:00:00 2001 From: tomvanmele Date: Thu, 4 Jul 2024 12:30:06 +0200 Subject: [PATCH 9/9] better error message --- src/compas/geometry/surfaces/nurbs.py | 2 +- src/compas/geometry/surfaces/surface.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compas/geometry/surfaces/nurbs.py b/src/compas/geometry/surfaces/nurbs.py index eb69538a947..acb2ec225ad 100644 --- a/src/compas/geometry/surfaces/nurbs.py +++ b/src/compas/geometry/surfaces/nurbs.py @@ -182,7 +182,7 @@ def __from_data__(cls, data): def __new__(cls, *args, **kwargs): if cls is NurbsSurface: - raise TypeError("Instantiating the base NURBS Surface class directly is not allowed.") + raise TypeError("Making an instance of `NurbsSurface` using `NurbsSurface()` is not allowed. Please use one of the factory methods instead (`NurbsSurface.from_...`)") return object.__new__(cls) def __repr__(self): diff --git a/src/compas/geometry/surfaces/surface.py b/src/compas/geometry/surfaces/surface.py index d83f1fb370a..e669eddba8c 100644 --- a/src/compas/geometry/surfaces/surface.py +++ b/src/compas/geometry/surfaces/surface.py @@ -46,7 +46,7 @@ class Surface(Geometry): def __new__(cls, *args, **kwargs): if cls is Surface: - raise TypeError("Instantiating the base Surface class directly is not allowed.") + raise TypeError("Making an instance of `Surface` using `Surface()` is not allowed. Please use one of the factory methods instead (`Surface.from_...`)") return object.__new__(cls) def __init__(self, frame=None, name=None):