Skip to content

Commit 5284523

Browse files
authored
Merge pull request #1111 from compas-dev/surface_types
Surface types
2 parents b03002c + ba7b6ad commit 5284523

File tree

15 files changed

+444
-112
lines changed

15 files changed

+444
-112
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
* Added conversion function `frame_to_rhino_plane` to `compas_rhino.conversions`.
1313
* Added `RhinoSurface.from_frame` to `compas_rhino.geometry`.
1414
* Added representation for trims with `compas.geometry.BrepTrim`.
15+
* Added `Arc` to `compas.geometry`.
16+
* Added `Arc` conversion functions to `compas_rhino.conversions`.
17+
* Added `from_sphere` alternative constructor to `RhinoBrep`.
18+
* Added support for singular trims to `RhinoBrep`.
1519

1620
### Changed
1721

1822
* Updated workflows to v2.
23+
* Fixed attribute error in `compas_rhino.conversions.ellipse_to_compas`.
1924
* Changed deepcopy of `RhinoBrep` to use the native `Rhino.Geometry` mechanism.
2025
* The normal of the cutting plane is no longer flipped in `compas_rhino.geometry.RhinoBrep`.
26+
* Planar holes caused by `RhinoBrep.trim` are now automatically capped.
2127
* Fixed `Polygon` constructor to not modify the input list of points.
28+
* Fixed serialization of sphere and cylinder Breps in `RhinoBrep`.
29+
* Fixed serialization of some trimmed shapes in `RhinoBrep`.
2230

2331
### Removed
2432

src/compas/geometry/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
Ellipse
5959
Polygon
6060
NurbsSurface
61+
Arc
6162
6263
3-dimensional
6364
-------------
@@ -811,6 +812,7 @@
811812
Polyline,
812813
Quaternion,
813814
Vector,
815+
Arc,
814816
)
815817
from .shapes import ( # noqa: E402
816818
Shape,
@@ -1170,6 +1172,7 @@
11701172
"Geometry",
11711173
"Primitive",
11721174
"Bezier",
1175+
"Arc",
11731176
"Circle",
11741177
"Ellipse",
11751178
"Frame",

src/compas/geometry/primitives/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from .circle import Circle # noqa: F401
1616
from .ellipse import Ellipse # noqa: F401
1717
from .curve import Bezier # noqa: F401
18+
from .arc import Arc # noqa: F401
1819

1920

2021
__all__ = [name for name in dir() if not name.startswith("_")]
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import math
2+
3+
from compas.geometry import close
4+
from compas.geometry.primitives import Frame
5+
from compas.geometry.primitives import Primitive
6+
7+
8+
class Arc(Primitive):
9+
"""Represents a portion of a circle's arc.
10+
11+
Parameters
12+
----------
13+
frame : :class:`~compas.geometry.Frame`
14+
Coordinate frame at the center of the arc's circle.
15+
radius : float
16+
Radius of the arc's circle.
17+
end_angle : float
18+
The angle in radians of the end of this Arc.
19+
start_angle :
20+
The angle in radians of the start of this Arc.
21+
22+
Attributes
23+
----------
24+
length : float
25+
The length of the arc (radius * angle)
26+
angle : float
27+
The sweep angle in radians (between incrementing end angle and start angle).
28+
domain : tuple(float, float)
29+
A tuple containing the start and end angles of this arc, in radians.
30+
center : :class:`~compas.geometry.Point`
31+
The center point of the circle which coincides with this Arc.
32+
circumference : float
33+
The circumference of the circle which coincides with this Arc.
34+
diameter : float
35+
The diameter of the circle which coincides with this Arc.
36+
37+
"""
38+
39+
def __init__(self, frame=None, radius=None, end_angle=None, start_angle=None, **kwargs):
40+
super(Arc, self).__init__(**kwargs)
41+
42+
self._frame = frame
43+
self._radius = radius
44+
self._start_angle = start_angle if start_angle is not None else 0.0
45+
self._end_angle = end_angle
46+
47+
if self._radius is not None and self._end_angle is not None:
48+
self._verify()
49+
50+
@property
51+
def data(self):
52+
return {
53+
"frame": self._frame.data,
54+
"radius": self._radius,
55+
"start": self._start_angle,
56+
"end": self._end_angle,
57+
}
58+
59+
@data.setter
60+
def data(self, value):
61+
self._frame = Frame.from_data(value["frame"])
62+
self._radius = value["radius"]
63+
self._start_angle = value["start"]
64+
self._end_angle = value["end"]
65+
66+
@property
67+
def frame(self):
68+
return self._frame
69+
70+
@frame.setter
71+
def frame(self, value):
72+
self._frame = value
73+
74+
@property
75+
def radius(self):
76+
return self._radius
77+
78+
@radius.setter
79+
def radius(self, value):
80+
self._radius = value
81+
82+
@property
83+
def start_angle(self):
84+
return self._start_angle
85+
86+
@start_angle.setter
87+
def start_angle(self, value):
88+
self._start_angle = value
89+
self._verify()
90+
91+
@property
92+
def end_angle(self):
93+
return self._end_angle
94+
95+
@end_angle.setter
96+
def end_angle(self, value):
97+
self._end_angle = value
98+
self._verify
99+
100+
@property
101+
def length(self):
102+
return self.radius * self.angle
103+
104+
@property
105+
def angle(self):
106+
return self._end_angle - self._start_angle
107+
108+
@property
109+
def domain(self):
110+
return self._start_angle, self._end_angle
111+
112+
@property
113+
def center(self):
114+
return self.frame.point
115+
116+
@property
117+
def circumference(self):
118+
return 2.0 * math.pi * self.radius
119+
120+
@property
121+
def diameter(self):
122+
return 2.0 * self.radius
123+
124+
@property
125+
def is_circle(self):
126+
return close(abs(abs(self.angle) - 2.0 * math.pi), 0.0)
127+
128+
@classmethod
129+
def from_circle(cls, circle, start_angle, end_angle):
130+
"""Creates an Arc from a circle and start and end angles.
131+
132+
Parameters
133+
----------
134+
circle : :class:`~compas.geometry.Circle`
135+
The center point and radius of this circle will be used to create an Arc.
136+
start_angle : float
137+
The start angle in radians.
138+
end_angle : float
139+
The end angle in radians.
140+
141+
Returns
142+
-------
143+
:class:`~compas.geometry.Arc`
144+
145+
"""
146+
frame = Frame.worldXY()
147+
# TODO: take circle.frame once it has one
148+
frame.point = circle.center.copy()
149+
return cls(frame, circle.radius, end_angle, start_angle)
150+
151+
def _verify(self):
152+
if self.angle < 0.0 or self.angle > 2.0 * math.pi:
153+
raise ValueError("Sweep angle must satisfy 0 < angle < 2 * Pi. Currently:{}".format(self.angle))

src/compas_rhino/conversions/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
ellipse_to_rhino
6464
polyline_to_rhino
6565
polygon_to_rhino
66+
arc_to_rhino
6667
point_to_compas
6768
vector_to_compas
6869
line_to_compas
@@ -72,6 +73,7 @@
7273
ellipse_to_compas
7374
polyline_to_compas
7475
polygon_to_compas
76+
arc_to_compas
7577
7678
7779
Shapes
@@ -140,6 +142,7 @@
140142
ellipse_to_rhino,
141143
polyline_to_rhino,
142144
polygon_to_rhino,
145+
arc_to_rhino,
143146
point_to_compas,
144147
vector_to_compas,
145148
line_to_compas,
@@ -149,6 +152,7 @@
149152
ellipse_to_compas,
150153
polyline_to_compas,
151154
polygon_to_compas,
155+
arc_to_compas,
152156
)
153157
from ._shapes import (
154158
box_to_rhino,
@@ -216,6 +220,8 @@
216220
"sphere_to_rhino",
217221
"cone_to_rhino",
218222
"cylinder_to_rhino",
223+
"arc_to_rhino",
224+
"arc_to_compas",
219225
"box_to_compas",
220226
"sphere_to_compas",
221227
"cone_to_compas",

src/compas_rhino/conversions/_primitives.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@
1111
from compas.geometry import Ellipse
1212
from compas.geometry import Polyline
1313
from compas.geometry import Polygon
14+
from compas.geometry import Arc
1415

1516
from Rhino.Geometry import Point3d
1617
from Rhino.Geometry import Vector3d
18+
from Rhino.Geometry import Interval
1719
from Rhino.Geometry import Line as RhinoLine
1820
from Rhino.Geometry import Plane as RhinoPlane
1921
from Rhino.Geometry import Circle as RhinoCircle
2022
from Rhino.Geometry import Ellipse as RhinoEllipse
2123
from Rhino.Geometry import Polyline as RhinoPolyline
24+
from Rhino.Geometry import Arc as RhinoArc
2225

2326

2427
def point_to_compas(point):
@@ -232,7 +235,7 @@ def ellipse_to_compas(ellipse):
232235
:class:`~compas.geometry.Ellipse`
233236
234237
"""
235-
return Ellipse(plane_to_compas(ellipse.Plane), ellipse.Major, ellipse.Minor)
238+
return Ellipse(plane_to_compas(ellipse.Plane), ellipse.Radius1, ellipse.Radius2)
236239

237240

238241
def ellipse_to_rhino(ellipse):
@@ -308,3 +311,42 @@ def polygon_to_rhino(polygon):
308311
309312
"""
310313
raise NotImplementedError
314+
315+
316+
def arc_to_rhino(arc):
317+
"""Convert a COMPAS Arc to a Rhino one.
318+
319+
Parameters
320+
----------
321+
arc : :class:`~compas.geometry.Arc`
322+
The COMPAS Arc to convert.
323+
324+
Returns
325+
-------
326+
:rhino:`Rhino.Geometry.Arc`
327+
328+
"""
329+
plane = frame_to_rhino_plane(arc.frame)
330+
circle = RhinoCircle(plane, arc.radius)
331+
angle_interval = Interval(arc.start_angle, arc.end_angle)
332+
return RhinoArc(circle, angle_interval)
333+
334+
335+
def arc_to_compas(arc):
336+
"""Convert a Rhino Arc Structure to a COMPAS Arc.
337+
338+
Parameters
339+
----------
340+
arc : :rhino:`Rhino.Geometry.Arc`
341+
The Rhino Arc to convert.
342+
343+
Returns
344+
-------
345+
:class:`~compas.geometry.Arc`
346+
347+
"""
348+
frame = plane_to_compas_frame(arc.Plane)
349+
# Arc center point can be set independently of its plane's origin
350+
center = point_to_compas(arc.Center)
351+
frame.point = center
352+
return Arc(frame, arc.Radius, start_angle=arc.StartAngle, end_angle=arc.EndAngle)

src/compas_rhino/geometry/__init__.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@
2222
RhinoBrepFace
2323
RhinoBrepLoop
2424
RhinoBrepTrim
25-
RhinoBrepBuilder
26-
RhinoFaceBuilder
27-
RhinoLoopBuilder
2825
2926
Plugins
3027
=======
@@ -95,9 +92,6 @@
9592
from .brep import RhinoBrepFace
9693
from .brep import RhinoBrepEdge
9794
from .brep import RhinoBrepTrim
98-
from .brep import RhinoBrepBuilder
99-
from .brep import RhinoFaceBuilder
100-
from .brep import RhinoLoopBuilder
10195

10296
__all__ = [
10397
"RhinoGeometry",
@@ -123,7 +117,4 @@
123117
"RhinoBrepFace",
124118
"RhinoBrepLoop",
125119
"RhinoBrepTrim",
126-
"RhinoBrepBuilder",
127-
"RhinoFaceBuilder",
128-
"RhinoLoopBuilder",
129120
]

src/compas_rhino/geometry/brep/__init__.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
from .vertex import RhinoBrepVertex
77
from .loop import RhinoBrepLoop
88
from .trim import RhinoBrepTrim
9-
from .builder import RhinoBrepBuilder
10-
from .builder import RhinoFaceBuilder
11-
from .builder import RhinoLoopBuilder
129

1310
import Rhino
1411

@@ -20,9 +17,6 @@
2017
"RhinoBrepLoop",
2118
"RhinoBrepFace",
2219
"RhinoBrepTrim",
23-
"RhinoBrepBuilder",
24-
"RhinoFaceBuilder",
25-
"RhinoLoopBuilder",
2620
"new_brep",
2721
"from_native",
2822
"from_box",
@@ -49,3 +43,8 @@ def from_box(*args, **kwargs):
4943
@plugin(category="factories", requires=["Rhino"])
5044
def from_cylinder(*args, **kwargs):
5145
return RhinoBrep.from_cylinder(*args, **kwargs)
46+
47+
48+
@plugin(category="factories", requires=["Rhino"])
49+
def from_sphere(*args, **kwargs):
50+
return RhinoBrep.from_sphere(*args, **kwargs)

0 commit comments

Comments
 (0)