Skip to content

Commit 89b1350

Browse files
authored
Merge pull request #17 from compas-dev/maintenance-v0.1.x
Maintenance v0.1.x
2 parents d93376d + f89a603 commit 89b1350

File tree

6 files changed

+196
-41
lines changed

6 files changed

+196
-41
lines changed

src/compas/geometry/algorithms/hull_numpy.py

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,56 @@ def convex_hull_numpy(points):
3737
Indices of the points on the hull.
3838
Faces of the hull.
3939
40-
Warning
41-
-------
42-
This function requires Numpy ands Scipy.
40+
Notes
41+
-----
42+
The faces of the hull returned by this function do not necessarily have consistent
43+
cycle directions. To obtain a mesh with consistent cycle directions, construct
44+
a mesh from the returned vertices, this function should be used in combination
45+
with :func:`compas.topology.unify_cycles`.
4346
4447
Examples
4548
--------
4649
.. code-block:: python
4750
48-
#
51+
import random
52+
53+
from compas.datastructures import Mesh
54+
55+
from compas.geometry import distance_point_point
56+
from compas.geometry import convex_hull_numpy
57+
from compas.topology import unify_cycles
58+
59+
from compas.viewers import MeshViewer
60+
61+
radius = 5
62+
origin = (0., 0., 0.)
63+
count = 0
64+
points = []
65+
66+
while count < 10:
67+
x = (random.random() - 0.5) * radius * 2
68+
y = (random.random() - 0.5) * radius * 2
69+
z = (random.random() - 0.5) * radius * 2
70+
pt = x, y, z
71+
72+
if distance_point_point(origin, pt) <= radius:
73+
points.append(pt)
74+
count += 1
75+
76+
vertices, faces = convex_hull_numpy(points)
77+
78+
i_index = {i: index for index, i in enumerate(vertices)}
79+
80+
vertices = [points[index] for index in vertices]
81+
faces = [[i_index[i] for i in face] for face in faces]
82+
faces = unify_cycles(vertices, faces)
83+
84+
mesh = Mesh.from_vertices_and_faces(vertices, faces)
85+
86+
viewer = MeshViewer(mesh)
87+
88+
viewer.setup()
89+
viewer.show()
4990
5091
"""
5192
points = asarray(points)
@@ -73,10 +114,18 @@ def convex_hull_xy_numpy(points):
73114
74115
Returns
75116
-------
76-
tuple
117+
list
77118
Indices of the points on the hull.
119+
list
78120
Faces of the hull.
79121
122+
Notes
123+
-----
124+
The faces of the hull returned by this function do not necessarily have consistent
125+
cycle directions. To obtain a mesh with consistent cycle directions, construct
126+
a mesh from the returned vertices, this function should be used in combination
127+
with :func:`compas.topology.unify_cycles`.
128+
80129
Examples
81130
--------
82131
.. code-block:: python
@@ -91,9 +140,6 @@ def convex_hull_xy_numpy(points):
91140

92141
points = points[:, :2]
93142
hull = ConvexHull(points)
94-
# temp = zeros((hull.vertices.shape[0], 1))
95-
# temp[:, :-1] = points[hull.vertices]
96-
# return temp
97143
return hull.vertices, hull.simplices
98144

99145

@@ -108,19 +154,19 @@ def convex_hull_xy_numpy(points):
108154

109155
import random
110156

111-
from compas.geometry.distance import distance_point_point
157+
from compas.geometry import distance_point_point
112158

113159
from compas.datastructures import Mesh
114160
from compas.viewers import MeshViewer
115161

116-
from compas.topology import mesh_unify_cycles
162+
from compas.topology import unify_cycles
117163

118164
radius = 5
119165
origin = (0., 0., 0.)
120166
count = 0
121167
points = []
122168

123-
while count < 10:
169+
while count < 1000:
124170
x = (random.random() - 0.5) * radius * 2
125171
y = (random.random() - 0.5) * radius * 2
126172
z = (random.random() - 0.5) * radius * 2
@@ -134,14 +180,16 @@ def convex_hull_xy_numpy(points):
134180

135181
i_index = {i: index for index, i in enumerate(vertices)}
136182

137-
mesh = Mesh.from_vertices_and_faces(
138-
[points[index] for index in vertices],
139-
[[i_index[i] for i in face] for face in faces[1:]]
140-
)
183+
vertices = [points[index] for index in vertices]
184+
faces = [[i_index[i] for i in face] for face in faces]
185+
faces = unify_cycles(vertices, faces)
141186

142-
mesh_unify_cycles(mesh)
187+
mesh = Mesh.from_vertices_and_faces(vertices, faces)
143188

144189
viewer = MeshViewer(mesh)
145190

191+
viewer.axes_on = False
192+
viewer.grid_on = False
193+
146194
viewer.setup()
147195
viewer.show()

src/compas/plotters/plotter.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,6 @@
3131
__all__ = ['Plotter', ]
3232

3333

34-
# https://matplotlib.org/faq/usage_faq.html#what-is-interactive-mode
35-
# https://matplotlib.org/api/pyplot_summary.html
36-
# https://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure
37-
# https://matplotlib.org/api/axes_api.html
38-
# https://matplotlib.org/api/index.html
39-
40-
4134
class Plotter(object):
4235
"""Definition of a plotter object based on matplotlib.
4336
@@ -76,7 +69,6 @@ class Plotter(object):
7669
Computing In Science & Engineering (9) 3, p.90-95.
7770
Available at: http://ieeexplore.ieee.org/document/4160265/citations.
7871
79-
8072
"""
8173
def __init__(self, figsize=(16.0, 12.0), dpi=100.0, interactive=False, tight=False, **kwargs):
8274
"""Initialises a plotter object"""

src/compas/topology/orientation.py

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from __future__ import absolute_import
33
from __future__ import division
44

5+
from compas.utilities import pairwise
6+
from compas.geometry import centroid_points
57
from compas.topology import breadth_first_traverse
68

79

@@ -12,12 +14,89 @@
1214

1315

1416
__all__ = [
17+
'face_adjacency',
18+
'mesh_face_adjacency',
19+
'unify_cycles',
1520
'mesh_unify_cycles',
1621
'mesh_flip_cycles',
1722
]
1823

1924

20-
def face_adjacency(mesh):
25+
def face_adjacency(xyz, faces):
26+
""""""
27+
points = [centroid_points([xyz[index] for index in face]) for face in faces]
28+
29+
try:
30+
from scipy.spatial import cKDTree
31+
32+
tree = cKDTree(points)
33+
_, closest = tree.query(points, k=10, n_jobs=-1)
34+
35+
except Exception:
36+
try:
37+
import Rhino
38+
39+
except Exception:
40+
from compas.geometry import KDTree
41+
42+
tree = KDTree(points)
43+
closest = [tree.nearest_neighbours(point, 10) for point in points]
44+
closest = [[index for _, index, _ in nnbrs] for nnbrs in closest]
45+
46+
else:
47+
from Rhino.Geometry import RTree
48+
from Rhino.Geometry import Sphere
49+
from Rhino.Geometry import Point3d
50+
51+
tree = RTree()
52+
for i, point in enumerate(points):
53+
tree.Insert(Point3d(* point), i)
54+
55+
def callback(sender, e):
56+
data = e.Tag
57+
data.append(e.Id)
58+
59+
closest = []
60+
for i, point in enumerate(points):
61+
sphere = Sphere(Point3d(* point), 2.0)
62+
data = []
63+
tree.Search(sphere, callback, data)
64+
closest.append(data)
65+
66+
adjacency = {}
67+
68+
for face, vertices in enumerate(faces):
69+
nbrs = []
70+
found = set()
71+
72+
nnbrs = set(closest[face])
73+
74+
for u, v in pairwise(vertices + vertices[0:1]):
75+
for nbr in nnbrs:
76+
77+
if nbr == face:
78+
continue
79+
if nbr in found:
80+
continue
81+
82+
for a, b in pairwise(faces[nbr] + faces[nbr][0:1]):
83+
if v == a and u == b:
84+
nbrs.append(nbr)
85+
found.add(nbr)
86+
break
87+
88+
for a, b in pairwise(faces[nbr] + faces[nbr][0:1]):
89+
if u == a and v == b:
90+
nbrs.append(nbr)
91+
found.add(nbr)
92+
break
93+
94+
adjacency[face] = nbrs
95+
96+
return adjacency
97+
98+
99+
def mesh_face_adjacency(mesh):
21100
"""Build a face adjacency dict.
22101
23102
Parameters
@@ -86,7 +165,7 @@ def callback(sender, e):
86165
index = fkey_index[fkey]
87166
found = set()
88167

89-
nnbrs = closest[index]
168+
nnbrs = set(closest[index])
90169

91170
for u, v in mesh.face_halfedges(fkey):
92171
for index in nnbrs:
@@ -114,6 +193,30 @@ def callback(sender, e):
114193
return adjacency
115194

116195

196+
def unify_cycles(vertices, faces, root=0):
197+
""""""
198+
def unify(node, nbr):
199+
# find the common edge
200+
for u, v in pairwise(faces[nbr] + faces[nbr][0:1]):
201+
if u in faces[node] and v in faces[node]:
202+
# node and nbr have edge u-v in common
203+
i = faces[node].index(u)
204+
j = faces[node].index(v)
205+
if i == j - 1 or (j == 0 and u == faces[node][-1]):
206+
# if the traversal of a neighbouring halfedge
207+
# is in the same direction
208+
# flip the neighbour
209+
faces[nbr][:] = faces[nbr][::-1]
210+
return
211+
212+
adj = face_adjacency(vertices, faces)
213+
214+
visited = breadth_first_traverse(adj, root, unify)
215+
216+
assert len(list(visited)) == len(faces), 'Not all faces were visited'
217+
return faces
218+
219+
117220
def mesh_unify_cycles(mesh, root=None):
118221
"""Unify the cycle directions of all faces.
119222
@@ -135,7 +238,7 @@ def unify(node, nbr):
135238
# node and nbr have edge u-v in common
136239
i = mesh.face[node].index(u)
137240
j = mesh.face[node].index(v)
138-
if i == j - 1:
241+
if i == j - 1 or (j == 0 and u == mesh.face[node][-1]):
139242
# if the traversal of a neighbouring halfedge
140243
# is in the same direction
141244
# flip the neighbour
@@ -145,7 +248,7 @@ def unify(node, nbr):
145248
if root is None:
146249
root = mesh.get_any_face()
147250

148-
adj = face_adjacency(mesh)
251+
adj = mesh_face_adjacency(mesh)
149252

150253
visited = breadth_first_traverse(adj, root, unify)
151254

src/compas/topology/traversal.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ def breadth_first_ordering(adjacency, root):
256256

257257

258258
def breadth_first_traverse(adjacency, root, callback=None):
259+
""""""
259260
tovisit = deque([root])
260261
visited = set([root])
261262

src/compas/viewers/meshviewer.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,22 +71,22 @@ def display(self):
7171
'color' : (0.4, 0.4, 0.4),
7272
'size' : 5.0})
7373

74-
normals = []
75-
for fkey in self.mesh.faces():
76-
n = self.mesh.face_normal(fkey, unitized=True)
77-
sp = self.mesh.face_centroid(fkey)
78-
ep = [sp[axis] + n[axis] for axis in (0, 1, 2)]
79-
normals.append({
80-
'start' : sp,
81-
'end' : ep,
82-
'color' : (0.0, 1.0, 0.0),
83-
'width' : 2.0
84-
})
74+
# normals = []
75+
# for fkey in self.mesh.faces():
76+
# n = self.mesh.face_normal(fkey, unitized=True)
77+
# sp = self.mesh.face_centroid(fkey)
78+
# ep = [sp[axis] + n[axis] for axis in (0, 1, 2)]
79+
# normals.append({
80+
# 'start' : sp,
81+
# 'end' : ep,
82+
# 'color' : (0.0, 1.0, 0.0),
83+
# 'width' : 2.0
84+
# })
8585

8686
xdraw_polygons(polygons)
8787
xdraw_lines(lines)
8888
xdraw_points(points)
89-
xdraw_lines(normals)
89+
# xdraw_lines(normals)
9090

9191
def keypress(self, key, x, y):
9292
"""

src/compas/viewers/viewer.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,27 @@ class Viewer(object):
4848
The width of the viewer window.
4949
height : int
5050
The height of the viewer window.
51-
clear_color : sequence(4)
51+
near : float
52+
Distance of the near clipping plane. Default is `0.1`.
53+
far : float
54+
Distance of the far clipping plane. Default is `1000.0`.
55+
fov : float
56+
Field of view. Default is `50.0`.
57+
clear_color : 4-tuple of float
5258
A sequence of 4 floats defining the background color of the scene.
59+
Default is `(0.9, 0.9, 0.9, 1.0)`.
5360
grid_on : bool
5461
Grid on or off.
62+
axes_on : bool
63+
Grid on or off.
5564
mouse : Mouse
5665
A ``Mouse`` object.
5766
camera : Camera
5867
A ``Camera`` object.
5968
grid : Grid
6069
A ``Grid`` object.
70+
displayfuncs : list of callable
71+
A list of functions called by the display callback to render the scene.
6172
6273
Notes
6374
-----

0 commit comments

Comments
 (0)