Skip to content

Commit 0997040

Browse files
committed
surface and polyhedron fixes
1 parent 8a783a4 commit 0997040

6 files changed

Lines changed: 135 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
* Added `compas.scene.Scene.add_group()` for adding group.
1313
* Added `compas.scene.Group.add_from_list()` for adding a list of items to a group.
14+
* Added implementation for `compas.geometry.SphericalSurface.isocurve_u`.
15+
* Added implementation for `compas.geometry.SphericalSurface.isocurve_v`.
16+
* Added implementation for `compas.geometry.CylindricalSurface.isocurve_u`.
17+
* Added implementation for `compas.geometry.CylindricalSurface.isocurve_v`.
1418

1519
### Changed
1620

1721
* Fixed error in `circle_to_compas` from Rhino.
1822
* Fixed Rhino to Rhino brep serialization.
1923
* Upated `compas.scene.Group.add()` to pass on group kwargs as default for child items.
2024
* Fixed bug in context detection, which wrongly defaults to `Viewer` instead of `None`.
25+
* Fixed bug in calculation of `compas.geometry.Polyhedron.edges` if geometry is computed using numpy.
26+
* Fixed bug in `Grpah.from_pointcloud` which uses degree parameter wrongly.
2127

2228
### Removed
2329

src/compas/datastructures/graph/graph.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from ast import literal_eval
66
from itertools import combinations
77
from random import sample
8+
from random import choice
9+
from random import shuffle
810

911
import compas
1012

@@ -132,15 +134,21 @@ def __data__(self):
132134

133135
def __before_json_dump__(self, data):
134136
data["node"] = {repr(key): attr for key, attr in data["node"].items()}
135-
data["edge"] = {repr(u): {repr(v): attr for v, attr in nbrs.items()} for u, nbrs in data["edge"].items()}
137+
data["edge"] = {
138+
repr(u): {repr(v): attr for v, attr in nbrs.items()}
139+
for u, nbrs in data["edge"].items()
140+
}
136141
return data
137142

138143
def __after_json_load__(self, data):
139144
l_e = literal_eval
140145
nodes = data["node"] or {}
141146
edges = data["edge"] or {}
142147
data["node"] = {l_e(node): attr for node, attr in nodes.items()}
143-
data["edge"] = {l_e(u): {l_e(v): attr for v, attr in nbrs.items()} for u, nbrs in edges.items()}
148+
data["edge"] = {
149+
l_e(u): {l_e(v): attr for v, attr in nbrs.items()}
150+
for u, nbrs in edges.items()
151+
}
144152
return data
145153

146154
@classmethod
@@ -159,7 +167,13 @@ def __from_data__(cls, data):
159167
graph._max_node = data.get("max_node", graph._max_node)
160168
return graph
161169

162-
def __init__(self, default_node_attributes=None, default_edge_attributes=None, name=None, **kwargs):
170+
def __init__(
171+
self,
172+
default_node_attributes=None,
173+
default_edge_attributes=None,
174+
name=None,
175+
**kwargs,
176+
):
163177
super(Graph, self).__init__(kwargs, name=name)
164178
self._max_node = -1
165179
self.node = {}
@@ -375,8 +389,16 @@ def from_pointcloud(cls, cloud, degree=3):
375389
graph = cls()
376390
for x, y, z in cloud:
377391
graph.add_node(x=x, y=y, z=z)
392+
nodes = list(graph.nodes())
378393
for u in graph.nodes():
379-
for v in graph.node_sample(size=degree):
394+
shuffle(nodes)
395+
for v in nodes:
396+
if v == u:
397+
continue
398+
if graph.degree(v) == degree:
399+
continue
400+
if graph.degree(u) == degree:
401+
break
380402
graph.add_edge(u, v)
381403
return graph
382404

@@ -1952,7 +1974,9 @@ def has_edge(self, edge, directed=True):
19521974
u, v = edge
19531975
if directed:
19541976
return u in self.edge and v in self.edge[u]
1955-
return (u in self.edge and v in self.edge[u]) or (v in self.edge and u in self.edge[v])
1977+
return (u in self.edge and v in self.edge[u]) or (
1978+
v in self.edge and u in self.edge[v]
1979+
)
19561980

19571981
# --------------------------------------------------------------------------
19581982
# Node geometry
@@ -2040,7 +2064,11 @@ def node_neighborhood_centroid(self, key):
20402064
:meth:`node_coordinates`, :meth:`node_point`, :meth:`node_laplacian`
20412065
20422066
"""
2043-
return Point(*centroid_points([self.node_coordinates(nbr) for nbr in self.neighbors(key)]))
2067+
return Point(
2068+
*centroid_points(
2069+
[self.node_coordinates(nbr) for nbr in self.neighbors(key)]
2070+
)
2071+
)
20442072

20452073
# --------------------------------------------------------------------------
20462074
# Edge geometry
@@ -2320,7 +2348,10 @@ def connected_edges(self):
23202348
:meth:`connected_nodes`
23212349
23222350
"""
2323-
return [[(u, v) for u in nodes for v in self.neighbors(u) if u < v] for nodes in self.connected_nodes()]
2351+
return [
2352+
[(u, v) for u in nodes for v in self.neighbors(u) if u < v]
2353+
for nodes in self.connected_nodes()
2354+
]
23242355

23252356
def exploded(self):
23262357
"""Explode the graph into its connected components.
@@ -2407,7 +2438,9 @@ def adjacency_matrix(self, rtype="array"):
24072438
from compas.matrices import adjacency_matrix
24082439

24092440
node_index = self.node_index()
2410-
adjacency = [[node_index[nbr] for nbr in self.neighbors(key)] for key in self.nodes()]
2441+
adjacency = [
2442+
[node_index[nbr] for nbr in self.neighbors(key)] for key in self.nodes()
2443+
]
24112444
return adjacency_matrix(adjacency, rtype=rtype)
24122445

24132446
def connectivity_matrix(self, rtype="array"):
@@ -2447,7 +2480,9 @@ def degree_matrix(self, rtype="array"):
24472480
from compas.matrices import degree_matrix
24482481

24492482
node_index = self.node_index()
2450-
adjacency = [[node_index[nbr] for nbr in self.neighbors(key)] for key in self.nodes()]
2483+
adjacency = [
2484+
[node_index[nbr] for nbr in self.neighbors(key)] for key in self.nodes()
2485+
]
24512486
return degree_matrix(adjacency, rtype=rtype)
24522487

24532488
def laplacian_matrix(self, normalize=False, rtype="array"):

src/compas/geometry/polyhedron.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,9 @@ def faces(self, faces):
301301
def edges(self):
302302
seen = set()
303303
for face in self.faces:
304-
for u, v in pairwise(face + face[:1]):
304+
for i in range(-1, len(face) - 1):
305+
u = face[i]
306+
v = face[i + 1]
305307
if (u, v) not in seen:
306308
seen.add((u, v))
307309
seen.add((v, u))
@@ -317,7 +319,9 @@ def lines(self):
317319

318320
@property
319321
def polygons(self):
320-
return [Polygon([self.vertices[index] for index in face]) for face in self.faces]
322+
return [
323+
Polygon([self.vertices[index] for index in face]) for face in self.faces
324+
]
321325

322326
@property
323327
def planes(self):
@@ -386,7 +390,9 @@ def from_platonicsolid(cls, f):
386390
elif f == 20:
387391
vertices, faces = icosahedron()
388392
else:
389-
raise ValueError("The number of sides of a platonic solid must be one of: 4, 6, 8, 12, 20.")
393+
raise ValueError(
394+
"The number of sides of a platonic solid must be one of: 4, 6, 8, 12, 20."
395+
)
390396
solid = cls(vertices, faces)
391397
return solid
392398

@@ -437,7 +443,9 @@ def from_halfspaces(cls, halfspaces, interior_point):
437443
interior_point = asarray(interior_point, dtype=float)
438444
hsi = HalfspaceIntersection(halfspaces, interior_point)
439445
hull = ConvexHull(hsi.intersections)
440-
mesh = Mesh.from_vertices_and_faces([hsi.intersections[i] for i in hull.vertices], hull.simplices)
446+
mesh = Mesh.from_vertices_and_faces(
447+
[hsi.intersections[i] for i in hull.vertices], hull.simplices
448+
)
441449
mesh.unify_cycles()
442450
to_merge = []
443451
for a, b in combinations(mesh.faces(), 2):

src/compas/geometry/surfaces/cylindrical.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from compas.geometry import Circle
1010
from compas.geometry import Frame
11+
from compas.geometry import Line
1112
from compas.geometry import Point
1213
from compas.geometry import Vector
1314

@@ -162,6 +163,39 @@ def from_three_points(cls, a, b, c):
162163
# Methods
163164
# =============================================================================
164165

166+
def isocurve_u(self, u):
167+
"""Compute the isoparametric curve at parameter u.
168+
169+
Parameters
170+
----------
171+
u : float
172+
173+
Returns
174+
-------
175+
:class:`compas.geometry.Line`
176+
177+
"""
178+
base = self.point_at(u=u, v=0.5)
179+
return Line.from_point_direction_length(base, self.frame.zaxis, 1.0)
180+
181+
def isocurve_v(self, v):
182+
"""Compute the isoparametric curve at parameter v.
183+
184+
Parameters
185+
----------
186+
v : float
187+
188+
Returns
189+
-------
190+
:class:`compas.geometry.Circle`
191+
192+
"""
193+
point = self.center + self.frame.zaxis * v
194+
xaxis = self.frame.xaxis
195+
yaxis = self.frame.yaxis
196+
frame = Frame(point, xaxis, yaxis)
197+
return Circle(radius=self.radius, frame=frame)
198+
165199
def point_at(self, u, v, world=True):
166200
"""Compute a point on the surface at the given parameters.
167201

src/compas/geometry/surfaces/spherical.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,44 @@ def from_points(cls, points):
191191
# Methods
192192
# =============================================================================
193193

194+
def isocurve_u(self, u):
195+
"""Compute the isoparametric curve at parameter u.
196+
197+
Parameters
198+
----------
199+
u : float
200+
201+
Returns
202+
-------
203+
:class:`compas.geometry.Circle`
204+
205+
"""
206+
origin = self.center
207+
xaxis = self.point_at(u=u, v=0) - origin
208+
yaxis = self.point_at(u=u, v=0.25) - origin
209+
frame = Frame(origin, xaxis, yaxis)
210+
return Circle(radius=xaxis.length, frame=frame)
211+
212+
def isocurve_v(self, v):
213+
"""Compute the isoparametric curve at parameter v.
214+
215+
Parameters
216+
----------
217+
v : float
218+
219+
Returns
220+
-------
221+
:class:`compas.geometry.Circle`
222+
223+
"""
224+
x = self.point_at(u=0, v=v)
225+
y = self.point_at(u=0.25, v=v)
226+
origin = self.center + self.frame.zaxis * (x - self.center).dot(self.frame.zaxis)
227+
xaxis = x - origin
228+
yaxis = y - origin
229+
frame = Frame(origin, xaxis, yaxis)
230+
return Circle(radius=xaxis.length, frame=frame)
231+
194232
def point_at(self, u, v, world=True):
195233
"""Construct a point on the sphere.
196234

src/compas/geometry/surfaces/surface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ def isocurve_v(self, v):
474474
475475
Returns
476476
-------
477-
:class:`compas_occ.geometry.Curve`
477+
:class:`compas.geometry.Curve`
478478
479479
"""
480480
raise NotImplementedError

0 commit comments

Comments
 (0)