Skip to content

Commit 5896f35

Browse files
authored
Merge pull request #1424 from compas-dev/fix-polygon-surface
Polygon fixes and Brep from step for Rhino
2 parents 2c7ed4e + 03ddbe0 commit 5896f35

File tree

9 files changed

+261
-187
lines changed

9 files changed

+261
-187
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
* Added implementation for `compas_rhino.geometry.RhinoBrep.from_step`.
13+
1214
### Changed
1315

1416
* Changed `requirements.txt` to allow `numpy>=2`.
17+
* Fixed bug in `compas.geometry.Polygon.points` setter by removing duplicate points if they exist.
18+
* Fixed bug in `compas.geometry.Polygon.plane` by aligning the normal of the bestfit plane with the approximate normal of the polygon faces.
19+
* Changed the order of face vertices in `compas.geometry.Surface.to_vertices_and_faces` to a counter clockwise cycling direction and outward facing normals for curved surfaces.
1520

1621
### Removed
1722

src/compas/geometry/polygon.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from compas.geometry import earclip_polygon
1717
from compas.geometry import is_coplanar
1818
from compas.geometry import is_polygon_convex
19+
from compas.geometry import normal_triangle
1920
from compas.geometry import transform_points
2021
from compas.itertools import pairwise
2122
from compas.tolerance import TOL
@@ -135,9 +136,15 @@ def points(self):
135136

136137
@points.setter
137138
def points(self, points):
138-
if points[-1] == points[0]:
139-
points = points[:-1]
140-
self._points = [Point(*xyz) for xyz in points]
139+
previous = Point(*points[0])
140+
self._points = [previous]
141+
for xyz in points[1:]:
142+
if previous == xyz:
143+
continue
144+
previous = Point(*xyz)
145+
self._points.append(previous)
146+
if self._points[0] == self._points[-1]:
147+
del self._points[-1]
141148
self._lines = None
142149

143150
@property
@@ -173,7 +180,19 @@ def normal(self):
173180

174181
@property
175182
def plane(self):
176-
return Plane.from_points(self.points)
183+
# by just taking the bestfit plane,
184+
# the normal might not be aligned with the winding direciton of the polygon
185+
# this can be solved by comparing the plane normal with the normal of one of the triangles of the polygon
186+
# for convex polygons this is always correct
187+
# in the case of concave polygons it may not be
188+
# to be entirely correct, the check should be done with one of the polygon ears after earclipping
189+
# however, this is costly
190+
# and even then it is only correct if we assume th polygon is plat enough to have a consistent direction
191+
normal = normal_triangle([self.centroid] + self.points[:2])
192+
plane = Plane.from_points(self.points)
193+
if plane.normal.dot(normal) < 0:
194+
plane.normal.flip()
195+
return plane
177196

178197
@property
179198
def frame(self):

src/compas/geometry/shapes/sphere.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def from_point_and_radius(cls, point, radius): # type: (...) -> Sphere
176176
# Discretisation
177177
# ==========================================================================
178178

179-
def compute_vertices(self): # type: () -> list[float]
179+
def compute_vertices(self): # type: () -> list[list[float]]
180180
"""Compute the vertices of the discrete representation of the sphere.
181181
182182
Returns

src/compas/geometry/surfaces/surface.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,9 @@ def to_vertices_and_faces(self, nu=16, nv=16, du=None, dv=None):
252252
faces = [
253253
[
254254
i * (nv + 1) + j,
255-
(i + 1) * (nv + 1) + j,
256-
(i + 1) * (nv + 1) + j + 1,
257255
i * (nv + 1) + j + 1,
256+
(i + 1) * (nv + 1) + j + 1,
257+
(i + 1) * (nv + 1) + j,
258258
]
259259
for i, j in product(range(nu), range(nv))
260260
]

src/compas/geometry/triangulation_earclip.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Ear(object):
1818
Coordinates of the vertex of the Ear triangle.
1919
next : int
2020
Index of the next vertex of the Ear triangle.
21-
prew : int
21+
prev : int
2222
Index of the previous vertex of the Ear triangle.
2323
neighbour_coords : list
2424
Coordinates of the next and previous vertices of the Ear triangle.
@@ -32,10 +32,10 @@ def __init__(self, points, indexes, ind):
3232
index_in_indexes_arr = indexes.index(ind)
3333
self.next = indexes[(index_in_indexes_arr + 1) % length]
3434
if index_in_indexes_arr == 0:
35-
self.prew = indexes[length - 1]
35+
self.prev = indexes[length - 1]
3636
else:
37-
self.prew = indexes[index_in_indexes_arr - 1]
38-
self.neighbour_coords = [points[self.prew], points[self.next]]
37+
self.prev = indexes[index_in_indexes_arr - 1]
38+
self.neighbour_coords = [points[self.prev], points[self.next]]
3939

4040
def is_inside(self, point):
4141
"""Check if a given point is inside the triangle formed by the Ear.
@@ -120,7 +120,7 @@ def get_triangle(self):
120120
List of vertex indices forming the Ear triangle.
121121
122122
"""
123-
return [self.prew, self.index, self.next]
123+
return [self.prev, self.index, self.next]
124124

125125

126126
class Earcut(object):
@@ -168,7 +168,7 @@ def add_ear(self, new_ear):
168168
169169
"""
170170
self.ears.append(new_ear)
171-
self.neighbours.append(new_ear.prew)
171+
self.neighbours.append(new_ear.prev)
172172
self.neighbours.append(new_ear.next)
173173

174174
def find_ears(self):
@@ -229,16 +229,16 @@ def triangulate(self):
229229
current = self.ears.pop(0)
230230

231231
indexes.remove(current.index)
232-
self.neighbours.remove(current.prew)
232+
self.neighbours.remove(current.prev)
233233
self.neighbours.remove(current.next)
234234

235235
self.triangles.append(current.get_triangle())
236236

237-
# Check if prew and next vertices form new ears
238-
prew_ear_new = Ear(self.vertices, indexes, current.prew)
237+
# Check if prev and next vertices form new ears
238+
prev_ear_new = Ear(self.vertices, indexes, current.prev)
239239
next_ear_new = Ear(self.vertices, indexes, current.next)
240-
if prew_ear_new.validate(self.vertices, indexes, self.ears) and prew_ear_new.index not in self.neighbours:
241-
self.add_ear(prew_ear_new)
240+
if prev_ear_new.validate(self.vertices, indexes, self.ears) and prev_ear_new.index not in self.neighbours:
241+
self.add_ear(prev_ear_new)
242242
continue
243243
if next_ear_new.validate(self.vertices, indexes, self.ears) and next_ear_new.index not in self.neighbours:
244244
self.add_ear(next_ear_new)
@@ -270,8 +270,6 @@ def earclip_polygon(polygon):
270270
If no more ears were found for triangulation.
271271
272272
"""
273-
274-
# Orient the copy of polygon points to XY plane.
275273
from compas.geometry import Frame # Avoid circular import.
276274
from compas.geometry import Plane # Avoid circular import.
277275
from compas.geometry import Transformation # Avoid circular import.
Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
import Rhino # noqa: F401
1+
import Rhino # type: ignore # noqa: F401
22

33
from compas.plugins import plugin
44

55
from .brep import RhinoBrep
66

77

88
@plugin(category="factories", requires=["Rhino"])
9-
def new_brep(*args, **kwargs):
10-
return object.__new__(RhinoBrep)
9+
def from_boolean_difference(*args, **kwargs):
10+
return RhinoBrep.from_boolean_difference(*args, **kwargs)
1111

1212

1313
@plugin(category="factories", requires=["Rhino"])
14-
def from_extrusion(*args, **kwargs):
15-
return RhinoBrep.from_extrusion(*args, **kwargs)
14+
def from_boolean_intersection(*args, **kwargs):
15+
return RhinoBrep.from_boolean_intersection(*args, **kwargs)
1616

1717

1818
@plugin(category="factories", requires=["Rhino"])
19-
def from_native(*args, **kwargs):
20-
return RhinoBrep.from_native(*args, **kwargs)
19+
def from_boolean_union(*args, **kwargs):
20+
return RhinoBrep.from_boolean_union(*args, **kwargs)
2121

2222

2323
@plugin(category="factories", requires=["Rhino"])
@@ -31,8 +31,13 @@ def from_cylinder(*args, **kwargs):
3131

3232

3333
@plugin(category="factories", requires=["Rhino"])
34-
def from_sphere(*args, **kwargs):
35-
return RhinoBrep.from_sphere(*args, **kwargs)
34+
def from_extrusion(*args, **kwargs):
35+
return RhinoBrep.from_extrusion(*args, **kwargs)
36+
37+
38+
@plugin(category="factories", requires=["Rhino"])
39+
def from_loft(*args, **kwargs):
40+
return RhinoBrep.from_loft(*args, **kwargs)
3641

3742

3843
@plugin(category="factories", requires=["Rhino"])
@@ -41,20 +46,20 @@ def from_mesh(*args, **kwargs):
4146

4247

4348
@plugin(category="factories", requires=["Rhino"])
44-
def from_loft(*args, **kwargs):
45-
return RhinoBrep.from_loft(*args, **kwargs)
49+
def from_native(*args, **kwargs):
50+
return RhinoBrep.from_native(*args, **kwargs)
4651

4752

4853
@plugin(category="factories", requires=["Rhino"])
49-
def from_boolean_difference(*args, **kwargs):
50-
return RhinoBrep.from_boolean_difference(*args, **kwargs)
54+
def from_sphere(*args, **kwargs):
55+
return RhinoBrep.from_sphere(*args, **kwargs)
5156

5257

5358
@plugin(category="factories", requires=["Rhino"])
54-
def from_boolean_intersection(*args, **kwargs):
55-
return RhinoBrep.from_boolean_intersection(*args, **kwargs)
59+
def from_step(*args, **kwargs):
60+
return RhinoBrep.from_step(*args, **kwargs)
5661

5762

5863
@plugin(category="factories", requires=["Rhino"])
59-
def from_boolean_union(*args, **kwargs):
60-
return RhinoBrep.from_boolean_union(*args, **kwargs)
64+
def new_brep(*args, **kwargs):
65+
return object.__new__(RhinoBrep)

0 commit comments

Comments
 (0)