Skip to content

Commit 347e5ec

Browse files
authored
Merge pull request #1413 from compas-dev/fix_unify_cyc
update unify cycles
2 parents d77f706 + fc9e736 commit 347e5ec

File tree

5 files changed

+108
-27
lines changed

5 files changed

+108
-27
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3232
### Changed
3333

3434
* Fixed `PluginNotInstalledError` when using `Brep.from_boolean_*` in Rhino.
35+
* Expose the parameters `radius` and `nmax` from `compas.topology._face_adjacency` to `compas.topology.face_adjacency` and further propagate them to `unify_cycles` and `Mesh.unify_cycles`.
36+
* Modify `face_adjacency` to avoid using `compas.topology._face_adjacency` by default when there are more than 100 faces, unless one of the parameters `radius`, `nmax` is passed
3537
* Added support for `Polyline` as input for `compas_rhino.Brep.from_extrusion`.
3638

3739
### Removed

src/compas/datastructures/mesh/mesh.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2932,9 +2932,18 @@ def quads_to_triangles(self, check_angles=False):
29322932
del self.facedata[face]
29332933

29342934
# only reason this is here and not on the halfedge is because of the spatial tree
2935-
def unify_cycles(self, root=None):
2935+
def unify_cycles(self, root=None, nmax=None, max_distance=None):
29362936
"""Unify the cycles of the mesh.
29372937
2938+
Parameters
2939+
----------
2940+
root : int, optional
2941+
The key of the root face.
2942+
nmax : int, optional
2943+
The maximum number of neighboring faces to consider. If neither nmax nor max_distance is specified, all faces will be considered.
2944+
max_distance : float, optional
2945+
The max_distance of the search sphere for neighboring faces. If neither nmax nor max_distance is specified, all faces will be considered.
2946+
29382947
Returns
29392948
-------
29402949
None
@@ -2951,7 +2960,7 @@ def unify_cycles(self, root=None):
29512960
vertices = self.vertices_attributes("xyz")
29522961
faces = [[vertex_index[vertex] for vertex in self.face_vertices(face)] for face in self.faces()]
29532962

2954-
unify_cycles(vertices, faces)
2963+
unify_cycles(vertices, faces, root=root, nmax=nmax, max_distance=max_distance)
29552964

29562965
self.halfedge = {key: {} for key in self.vertices()}
29572966
for index, vertices in enumerate(faces):

src/compas/topology/orientation.py

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
from compas.topology import breadth_first_traverse
1010

1111

12-
def _closest_faces(vertices, faces, nmax=10, radius=10.0):
12+
def _closest_faces(vertices, faces, nmax, max_distance):
1313
points = [centroid_points([vertices[index] for index in face]) for face in faces]
1414

15-
k = min(len(faces), nmax)
15+
k = len(faces) if nmax is None else min(len(faces), nmax)
1616

1717
# determine the k closest faces for each face
1818
# each item in "closest" is
@@ -21,22 +21,26 @@ def _closest_faces(vertices, faces, nmax=10, radius=10.0):
2121
# [2] the distance between the test point and the face centroid
2222

2323
try:
24+
import numpy as np
2425
from scipy.spatial import cKDTree
2526

27+
tree = cKDTree(points)
28+
distances, closest = tree.query(points, k=k, workers=-1)
29+
if max_distance is None:
30+
return closest
31+
32+
closest_within_distance = []
33+
for i, closest_row in enumerate(closest):
34+
idx = np.where(distances[i] < max_distance)[0]
35+
closest_within_distance.append(closest_row[idx].tolist())
36+
return closest_within_distance
37+
2638
except Exception:
2739
try:
2840
from Rhino.Geometry import Point3d # type: ignore
2941
from Rhino.Geometry import RTree # type: ignore
3042
from Rhino.Geometry import Sphere # type: ignore
3143

32-
except Exception:
33-
from compas.geometry import KDTree
34-
35-
tree = KDTree(points)
36-
closest = [tree.nearest_neighbors(point, k) for point in points]
37-
closest = [[index for xyz, index, d in nnbrs] for nnbrs in closest]
38-
39-
else:
4044
tree = RTree()
4145

4246
for i, point in enumerate(points):
@@ -48,20 +52,26 @@ def callback(sender, e):
4852

4953
closest = []
5054
for i, point in enumerate(points):
51-
sphere = Sphere(Point3d(*point), radius)
55+
sphere = Sphere(Point3d(*point), max_distance)
5256
data = []
5357
tree.Search(sphere, callback, data)
5458
closest.append(data)
59+
return closest
5560

56-
else:
57-
tree = cKDTree(points)
58-
_, closest = tree.query(points, k=k, workers=-1)
61+
except Exception:
62+
from compas.geometry import KDTree
5963

60-
return closest
64+
tree = KDTree(points)
65+
closest = [tree.nearest_neighbors(point, k) for point in points]
66+
if max_distance is None:
67+
return closest
68+
return [[index for xyz, index, d in nnbrs if d < max_distance] for nnbrs in closest]
6169

6270

63-
def _face_adjacency(vertices, faces, nmax=10, radius=10.0):
64-
closest = _closest_faces(vertices, faces, nmax=nmax, radius=radius)
71+
def _face_adjacency(vertices, faces, nmax=None, max_distance=None):
72+
if nmax is None and max_distance is None:
73+
raise ValueError("Either nmax or max_distance should be specified.")
74+
closest = _closest_faces(vertices, faces, nmax=nmax, max_distance=max_distance)
6575

6676
adjacency = {}
6777

@@ -94,7 +104,7 @@ def _face_adjacency(vertices, faces, nmax=10, radius=10.0):
94104
return adjacency
95105

96106

97-
def face_adjacency(points, faces):
107+
def face_adjacency(points, faces, nmax=None, max_distance=None):
98108
"""Build a face adjacency dict.
99109
100110
Parameters
@@ -103,6 +113,10 @@ def face_adjacency(points, faces):
103113
The vertex locations of the faces.
104114
faces : list[list[int]]
105115
The faces defined as list of indices in the points list.
116+
nmax : int, optional
117+
The maximum number of neighboring faces to consider. If neither nmax nor max_distance is specified, all faces will be considered.
118+
max_distance : float, optional
119+
The max_distance of the search sphere for neighboring faces. If neither nmax nor max_distance is specified, all faces will be considered.
106120
107121
Returns
108122
-------
@@ -117,10 +131,8 @@ def face_adjacency(points, faces):
117131
purely geometrical, but uses a spatial indexing tree to speed up the search.
118132
119133
"""
120-
f = len(faces)
121-
122-
if f > 100:
123-
return _face_adjacency(points, faces)
134+
if nmax or max_distance:
135+
return _face_adjacency(points, faces, nmax=nmax, max_distance=max_distance)
124136

125137
adjacency = {}
126138

@@ -152,7 +164,7 @@ def face_adjacency(points, faces):
152164
return adjacency
153165

154166

155-
def unify_cycles(vertices, faces, root=None):
167+
def unify_cycles(vertices, faces, root=None, nmax=None, max_distance=None):
156168
"""Unify the cycle directions of all faces.
157169
158170
Unified cycle directions is a necessary condition for the data structure to
@@ -164,8 +176,12 @@ def unify_cycles(vertices, faces, root=None):
164176
The vertex coordinates of the mesh.
165177
faces : list[list[int]]
166178
The faces of the mesh defined as lists of vertex indices.
167-
root : str, optional
179+
root : int, optional
168180
The key of the root face.
181+
nmax : int, optional
182+
The maximum number of neighboring faces to consider. If neither nmax nor max_distance is specified, all faces will be considered.
183+
max_distance : float, optional
184+
The max_distance of the search sphere for neighboring faces. If neither nmax nor max_distance is specified, all faces will be considered.
169185
170186
Returns
171187
-------
@@ -196,7 +212,7 @@ def unify(node, nbr):
196212
if root is None:
197213
root = random.choice(list(range(len(faces))))
198214

199-
adj = face_adjacency(vertices, faces) # this is the only place where the vertex coordinates are used
215+
adj = face_adjacency(vertices, faces, nmax=nmax, max_distance=max_distance) # this is the only place where the vertex coordinates are used
200216

201217
visited = breadth_first_traverse(adj, root, unify)
202218

0 commit comments

Comments
 (0)