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, facefilter=None, cleanup=True): """Convert the surface b-rep loops to a COMPAS mesh. Parameters ---------- cls : :class:`compas.datastructures.Mesh`, optional The type of COMPAS mesh. facefilter : callable, optional A filter for selection which Brep faces to include. If provided, the filter should return ``True`` or ``False`` per face. A very simple filter that includes all faces is ``def facefilter(face): return True``. Default parameter value is ``None`` in which case all faces are included. cleanup : bool, optional Flag indicating to clean up the result. Cleaning up means to remove isolated faces and unused vertices. Default is ``True``. Returns ------- :class:`compas.datastructures.Mesh` The resulting mesh. Examples -------- >>> import compas_rhino >>> from compas_rhino.geometry import RhinoSurface >>> from compas_rhino.artists import MeshArtist >>> def facefilter(face): ... success, w, h = face.GetSurfaceSize() ... if success: ... if w > 10 and h > 10: ... return True ... return False ... >>> guid = compas_rhino.select_surface() >>> surf = RhinoSurface.from_guid(guid) >>> mesh = surf.to_compas(facefilter=facefilter) >>> artist = MeshArtist(mesh, layer="Blocks") >>> artist.clear_layer() >>> artist.draw() """ if not self.geometry.HasBrepForm: return brep = Rhino.Geometry.Brep.TryConvertBrep(self.geometry) if facefilter and callable(facefilter): brepfaces = [face for face in brep.Faces if facefilter(face)] else: brepfaces = brep.Faces # vertex maps and face lists gkey_xyz = {} faces = [] for face in brepfaces: loop = face.OuterLoop curve = loop.To3dCurve() segments = curve.Explode() a = segments[0].PointAtStart b = segments[0].PointAtEnd a_gkey = geometric_key(a) b_gkey = geometric_key(b) gkey_xyz[a_gkey] = a gkey_xyz[b_gkey] = b face = [a_gkey, b_gkey] for segment in segments[1:-1]: b = segment.PointAtEnd b_gkey = geometric_key(b) face.append(b_gkey) gkey_xyz[b_gkey] = b faces.append(face) # vertices and faces 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 face] for face in faces] # remove duplicates from vertexlist polygons = [] for temp in faces: face = [] for vertex in temp: if vertex not in face: face.append(vertex) polygons.append(face) # define mesh type cls = cls or Mesh # create mesh mesh = cls.from_vertices_and_faces(vertices, polygons) mesh.name = self.name # remove isolated faces if cleanup: for face in list(mesh.faces()): if not mesh.face_neighbors(face): mesh.delete_face(face) mesh.remove_unused_vertices() return mesh
# ========================================================================== # # ========================================================================== 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