Source code for compas_rhino.geometry.surface


from __future__ import print_function
from __future__ import absolute_import
from __future__ import division

import Rhino
import compas_rhino

from compas.datastructures import Mesh
from compas.geometry import angle_vectors
from compas.geometry import distance_point_point
from compas.utilities import geometric_key

from ._geometry import BaseRhinoGeometry


__all__ = ['RhinoSurface']


[docs]class RhinoSurface(BaseRhinoGeometry): """Wrapper for Rhino surface objects.""" def __init__(self): super(RhinoSurface, self).__init__()
[docs] @classmethod def from_geometry(cls): raise NotImplementedError
[docs] @classmethod def from_selection(cls): guid = compas_rhino.select_surface() return cls.from_guid(guid)
[docs] def to_compas(self, cls=None): """Convert the surface b-rep loops to a COMPAS mesh. Parameters ---------- cls : :class:`compas.datastructures.Mesh`, optional The type of COMPAS mesh. Returns ------- :class:`compas.datastructures.Mesh` The resulting mesh. """ if not self.geometry.HasBrepForm: return brep = Rhino.Geometry.Brep.TryConvertBrep(self.geometry) gkey_xyz = {} faces = [] for loop in brep.Loops: curve = loop.To3dCurve() segments = curve.Explode() face = [] sp = segments[0].PointAtStart ep = segments[0].PointAtEnd sp_gkey = geometric_key(sp) ep_gkey = geometric_key(ep) gkey_xyz[sp_gkey] = sp gkey_xyz[ep_gkey] = ep face.append(sp_gkey) face.append(ep_gkey) for segment in segments[1:-1]: ep = segment.PointAtEnd ep_gkey = geometric_key(ep) face.append(ep_gkey) gkey_xyz[ep_gkey] = ep faces.append(face) gkey_index = {gkey: index for index, gkey in enumerate(gkey_xyz)} vertices = [list(xyz) for gkey, xyz in gkey_xyz.items()] faces = [[gkey_index[gkey] for gkey in f] for f in faces] polygons = [] for temp in faces: face = [] for vertex in temp: if vertex not in face: face.append(vertex) polygons.append(face) cls = cls or Mesh mesh = cls.from_vertices_and_faces(vertices, polygons) mesh.name = self.name return mesh
# def uv_to_compas(self, cls=None, density=(10, 10)): # """Convert the surface UV space to a COMPAS mesh. # Parameters # ---------- # cls : :class:`compas.datastructures.Mesh`, optional # The type of mesh. # density : tuple of int, optional # The density in the U and V directions. # Default is ``u = 10`` and ``v = 10``. # Returns # ------- # :class:`compas.datastructures.Mesh` # The COMPAS mesh. # """ # return self.heightfield_to_compas(cls=cls, density=density, over_space=True) # def heightfield_to_compas(self, cls=None, density=(10, 10), over_space=False): # """Convert a heightfiled of the surface to a COMPAS mesh. # Parameters # ---------- # cls : :class:`compas.datastructures.Mesh`, optional # The type of mesh. # density : tuple of int, optional # The density in the two grid directions. # Default is ``u = 10`` and ``v = 10``. # over_space : bool, optional # Construct teh grid over the surface UV space instead of the XY axes. # Default is ``False``. # Returns # ------- # :class:`compas.datastructures.Mesh` # The COMPAS mesh. # """ # try: # u, v = density # except Exception: # u, v = density, density # vertices = self.heightfield(density=(u, v), over_space=over_space) # faces = [] # for i in range(u - 1): # for j in range(v - 1): # face = [(i + 0) * v + j, # (i + 1) * v + j, # (i + 1) * v + j + 1, # (i + 0) * v + j + 1] # faces.append(face) # cls = cls or Mesh # return cls.from_vertices_and_faces(vertices, faces) # ========================================================================== # # ========================================================================== def space(self, density=(10, 10)): """Construct a parameter grid overt the UV space of the surface. Parameters ---------- density : tuple, optional The density in the U and V directions of the parameter space. Default is ``10`` in both directions. Returns ------- list A list of UV parameter tuples. """ rs = compas_rhino.rs rs.EnableRedraw(False) try: du, dv = density except TypeError: du = density dv = density density_u = int(du) density_v = int(dv) if rs.IsPolysurface(self.guid): faces = rs.ExplodePolysurfaces(self.guid) elif rs.IsSurface(self.guid): faces = [self.guid] else: raise Exception('Object is not a surface.') uv = [] for face in faces: domain_u = rs.SurfaceDomain(face, 0) domain_v = rs.SurfaceDomain(face, 1) du = (domain_u[1] - domain_u[0]) / (density_u - 1) dv = (domain_v[1] - domain_v[0]) / (density_v - 1) # move to meshgrid function for i in range(density_u): for j in range(density_v): uv.append((domain_u[0] + i * du, domain_v[0] + j * dv)) if len(faces) > 1: rs.DeleteObjects(faces) rs.EnableRedraw(True) return uv def heightfield(self, density=(10, 10), over_space=True): """Construct a point grid over the surface. Parameters ---------- density : tuple, optional The density in the U and V directions of the grid. Default is ``10`` in both directions. over_space : bool, optional Construct the grid over the UV space of the surface. Default is ``True``. Returns ------- list List of grid points. """ rs = compas_rhino.rs rs.EnableRedraw(False) try: du, dv = density except TypeError: du = density dv = density du = int(du) dv = int(dv) if rs.IsPolysurface(self.guid): faces = rs.ExplodePolysurfaces(self.guid) elif rs.IsSurface(self.guid): faces = [self.guid] else: raise Exception('Object is not a surface.') xyz = [] if over_space: for guid in faces: face = RhinoSurface.from_guid(guid) uv = face.space(density) for u, v in uv: xyz.append(list(rs.EvaluateSurface(face.guid, u, v))) else: for guid in faces: bbox = rs.BoundingBox(guid) xmin = bbox[0][0] xmax = bbox[1][0] ymin = bbox[0][1] ymax = bbox[3][1] xstep = 1.0 * (xmax - xmin) / (du - 1) ystep = 1.0 * (ymax - ymin) / (dv - 1) seeds = [] for i in range(du): for j in range(dv): seed = xmin + i * xstep, ymin + j * ystep, 0 seeds.append(seed) points = map(list, rs.ProjectPointToSurface(seeds, guid, [0, 0, 1])) xyz += points if len(faces) > 1: rs.DeleteObjects(faces) rs.EnableRedraw(True) return xyz def descent(self, points=None): """""" rs = compas_rhino.rs if not points: points = self.heightfield() tol = rs.UnitAbsoluteTolerance() descent = [] if rs.IsPolysurface(self.guid): rs.EnableRedraw(False) faces = {} for p0 in points: p = p0[:] p[2] -= 2 * tol bcp = rs.BrepClosestPoint(self.guid, p) uv = bcp[1] index = bcp[2][1] try: face = faces[index] except (TypeError, IndexError): face = rs.ExtractSurface(self.guid, index, True) faces[index] = face p1 = rs.EvaluateSurface(face, uv[0], uv[1]) vector = [p1[_] - p0[_] for _ in range(3)] descent.append((p0, vector)) rs.DeleteObjects(faces.values()) rs.EnableRedraw(True) elif rs.IsSurface(self.guid): for p0 in points: p = p0[:] p[2] -= 2 * tol bcp = rs.BrepClosestPoint(self.guid, p) uv = bcp[1] p1 = rs.EvaluateSurface(self.guid, uv[0], uv[1]) vector = [p1[_] - p0[_] for _ in range(3)] descent.append((p0, vector)) else: raise Exception('Object is not a surface.') return descent def curvature(self, points=None): """""" rs = compas_rhino.rs if not points: points = self.heightfield() curvature = [] if rs.IsPolysurface(self.guid): rs.EnableRedraw(False) faces = {} for point in points: bcp = rs.BrepClosestPoint(self.guid, point) uv = bcp[1] index = bcp[2][1] try: face = faces[index] except (TypeError, IndexError): face = rs.ExtractSurface(self.guid, index, True) faces[index] = face props = rs.SurfaceCurvature(face, uv) curvature.append((point, (props[1], props[3], props[5]))) rs.DeleteObjects(faces.values()) rs.EnableRedraw(False) elif rs.IsSurface(self.guid): for point in points: bcp = rs.BrepClosestPoint(self.guid, point) uv = bcp[1] props = rs.SurfaceCurvature(self.guid, uv) curvature.append((point, (props[1], props[3], props[5]))) else: raise Exception('Object is not a surface.') return curvature def borders(self, border_type=1): """Duplicate the borders of the surface. Parameters ---------- border_type : {0, 1, 2} The type of border. * 0: All borders * 1: The exterior borders. * 2: The interior borders. Returns ------- list The GUIDs of the extracted border curves. """ rs = compas_rhino.rs border = rs.DuplicateSurfaceBorder(self.guid, type=border_type) curves = rs.ExplodeCurves(border, delete_input=True) return curves def kinks(self, threshold=1e-3): """Return the XYZ coordinates of kinks, i.e. tangency discontinuities, along the surface's boundaries. Returns ------- list The list of XYZ coordinates of surface boundary kinks. """ from .curve import RhinoCurve rs = compas_rhino.rs kinks = [] borders = self.borders(border_type=0) for border in borders: border = RhinoCurve(border) extremities = map(lambda x: rs.EvaluateCurve(border.guid, rs.CurveParameter(border.guid, x)), [0., 1.]) if border.is_closed(): start_tgt, end_tgt = border.tangents(extremities) if angle_vectors(start_tgt, end_tgt) > threshold: kinks += extremities else: kinks += extremities return list(set(kinks)) def closest_point(self, xyz): """Return the XYZ coordinates of the closest point on the surface from input XYZ-coordinates. Parameters ---------- xyz : list XYZ coordinates. Returns ------- list The XYZ coordinates of the closest point on the surface. """ rs = compas_rhino.rs return rs.EvaluateSurface(self.guid, * rs.SurfaceClosestPoint(self.guid, xyz)) def closest_points(self, points): return [self.closest_point(point) for point in points] def closest_point_on_boundaries(self, xyz): """Return the XYZ coordinates of the closest point on the boundaries of the surface from input XYZ-coordinates. Parameters ---------- xyz : list XYZ coordinates. Returns ------- list The XYZ coordinates of the closest point on the boundaries of the surface. """ from .curve import RhinoCurve borders = self.borders(type=0) proj_dist = {tuple(proj_xyz): distance_point_point(xyz, proj_xyz) for proj_xyz in [RhinoCurve(border).closest_point(xyz) for border in borders]} compas_rhino.delete_objects(borders) return min(proj_dist, key=proj_dist.get) def closest_points_on_boundaries(self, points): return [self.closest_point_on_boundaries(point) for point in points]
# ============================================================================== # Main # ============================================================================== if __name__ == '__main__': pass