/
plus.py
211 lines (168 loc) · 7.86 KB
/
plus.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
"""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
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))
)
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])
def polygon(point_list):
"""Return a polygon from points."""
return rc.Geometry.Polyline(point_list).ToNurbsCurve()
# ------------------------- End of honeybee[+] methods -----------------------------
# ------------------------------ Utilities -----------------------------------------
def is_planar(geometry, tol=1e-3):
"""Check if a surface in planar."""
return geometry.Faces[0].IsPlanar(tol)
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,)
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
)
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
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
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)
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))
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