Source code for honeybee.plus
"""Collection of methods for Honeybee geometry operations in Grasshopper."""
from collections import namedtuple
try:
import Rhino as rc
except ImportError as e:
pass
# ------------------------- Required functions -----------------------------------------
# In order to create a plus library you must overwrite these three methods.
# The structure of arguments and returns should stay the same.
# --------------------------------------------------------------------------------------
# TODO(someone!): Implement triangulate
[docs]def extract_geometry_points(geometries, meshing_parameters=None):
"""Calculate list of points for a Grasshopper geometry.
For planar surfaces the length of the list will be only 1. For non-planar
surfaces or surfaces with internal edges it will be a number of lists.
Args:
geometries: List of meshes or Breps
meshing_parameters: Optional Rhino meshing_parameters. This will only be used if
the surface is non-planar or has an internal edge and needs to be meshed.
Default:
Rhino.Geometry.meshing_parameters.Coarse; SimplePlanes = True for planar
surfaces; Rhino.Geometry.meshing_parameters.Smooth for non-planar
surfaces
Returns:
A Collection of (geometry, points) in which each geometry is coupled by points.
For planar surfaces the length of the points list will be only 1. For
non-planar surfaces, meshes or surfaces with internal edges it will be multiple
lists.
"""
if not hasattr(geometries, '__iter__'):
geometries = (geometries,)
for geometry in geometries:
if isinstance(geometry, rc.Geometry.Mesh):
yield extract_mesh_points((geometry,))
elif isinstance(geometry, rc.Geometry.Brep):
yield extract_brep_points(geometry, meshing_parameters)
else:
raise TypeError(
'Input surface should be a Mesh or a Brep not {}.'.format(type(geometry))
)
[docs]def xyz_to_geometrical_points(xyz_points):
"""convert a sequence of (x, y, z) values to Grasshopper points.
Input should be list of lists of points.
"""
for xyz_list in xyz_points:
for xyz in xyz_list:
yield rc.Geometry.Point3d(xyz[0], xyz[1], xyz[2])
[docs]def polygon(point_list):
"""Return a polygon from points."""
return rc.Geometry.Polyline(point_list).ToNurbsCurve()
# ------------------------- End of honeybee[+] methods -----------------------------
# ------------------------------ Utilities -----------------------------------------
[docs]def is_planar(geometry, tol=1e-3):
"""Check if a surface in planar."""
return geometry.Faces[0].IsPlanar(tol)
[docs]def extract_brep_points(brep, meshing_parameters=None, tol=1e-3):
"""Extract points from Brep."""
meshing_parameters = meshing_parameters or rc.Geometry.MeshingParameters.Coarse
for fid in xrange(brep.Faces.Count):
geometry = brep.Faces[fid].DuplicateFace(False)
if not brep.Faces[fid].IsPlanar(tol):
meshes = rc.Geometry.Mesh.CreateFromBrep(geometry, meshing_parameters)
yield next(extract_mesh_points(meshes))
else:
# planar surface
pts = geometry.DuplicateVertices()
# sort points anti clockwise
# this is only important for energy simulation and won't make a difference
# for Radiance
# To sort the points we find border of the surface and evaluate points
# on border and use the parameter value to sort them
border = rc.Geometry.Curve.JoinCurves(geometry.DuplicateEdgeCurves(True))
if len(border) > 1:
# mesh the surface
meshing_parameters.SimplePlanes = True
meshes = rc.Geometry.Mesh.CreateFromBrep(geometry, meshing_parameters)
yield next(extract_mesh_points(meshes))
else:
# In some strange cases Rhino returns a single point for the surface
if len(pts) == 1:
pts = (p.Location for p in border[0].Points)
points_sorted = sorted(pts, key=lambda pt: border[0].ClosestPoint(pt)[1])
# make sure points are anti clockwise
if not is_points_sorted_anticlockwise(
points_sorted,
get_surface_center_pt_and_normal(geometry).normal_vector):
points_sorted.reverse()
# return sorted points
# Wrap in a list as Honeybee accepts list of list of points
yield geometry, (points_sorted,)
[docs]def extract_mesh_points(meshes):
"""Extract points from a mesh."""
for mesh in meshes:
yield mesh, tuple(
tuple(mesh.Vertices[face[i]] for i in range(4))
if face.IsQuad else
tuple(mesh.Vertices[face[i]] for i in range(3))
for face in mesh.Faces
)
[docs]def vectors_cross_product(vector1, vector2):
"""Calculate cross product of two vectors."""
return vector1.X * vector2.X + \
vector1.Y * vector2.Y + vector1.Z * vector2.Z
[docs]def is_points_sorted_anticlockwise(sorted_points, normal):
"""Check if an ordered list of points are anti-clockwise."""
vector0 = rc.Geometry.Vector3d(sorted_points[1] - sorted_points[0])
vector1 = rc.Geometry.Vector3d(sorted_points[-1] - sorted_points[0])
pts_normal = rc.Geometry.Vector3d.CrossProduct(vector0, vector1)
# in case points are anti-clockwise then normals should be parallel
if vectors_cross_product(pts_normal, normal) > 0:
return True
else:
return False
[docs]def get_surface_center_pt_and_normal(geometry):
"""Calculate center point and normal for a hb_surface.
Args:
hb_surface: A Honeybee surface
Returns:
Returns a tuple as (center_pt, normal_vector)
"""
brep_face = geometry.Faces[0]
if brep_face.IsPlanar and brep_face.IsSurface:
u_domain = brep_face.Domain(0)
v_domain = brep_face.Domain(1)
center_u = (u_domain.Min + u_domain.Max) / 2
center_v = (v_domain.Min + v_domain.Max) / 2
center_pt = brep_face.PointAt(center_u, center_v)
normal_vector = brep_face.NormalAt(center_u, center_v)
else:
centroid = rc.Geometry.AreaMassProperties.Compute(brep_face).Centroid
uv = brep_face.ClosestPoint(centroid)
center_pt = brep_face.PointAt(uv[1], uv[2])
normal_vector = brep_face.NormalAt(uv[1], uv[2])
SurfaceData = namedtuple('SurfaceData', 'center_pt normal_vector')
return SurfaceData(center_pt, normal_vector)
[docs]def check_planarity(hb_surface, tolerance=1e-3):
"""Check planarity of a hb_surface.
Args:
hb_surface: A Honeybee surface
tolerance: A float number as tolerance (Default: 1e-3)
Returns:
True is the surface is planar, otherwise return False.
"""
try:
return hb_surface.geometry.Faces[0].is_planar(tolerance)
except AttributeError as e:
raise TypeError("Input is not a hb_surface: %s" % str(e))
[docs]def check_for_internal_edge(hb_surface):
"""Check if the surface has an internal edge.
For surfaces with internal edge surfaces needs to be meshed to extract the points.
Args:
hb_surface: A Honeybee surface
Returns:
True is the surface has an internal edge, otherwise return False.
"""
# I believe there should be a direct method in RhinoCommon to indicate if a
# surface is an open brep but since I couldn't find it I'm using this method
# if Surface has no intenal edges the length of joined border is 1
try:
edges = hb_surface.geometry.DuplicateEdgeCurves(True)
except AttributeError as e:
raise TypeError("Input is not a hb_surface: %s" % str(e))
else:
border = rc.Geometry.Curve.JoinCurves(edges)
if len(border) > 1:
return True
else:
return False