Skip to content

Commit e4b6a04

Browse files
authored
Merge pull request #892 from jthacker/polygon_circumscribed
Adds ability to define polygons with a circumscribed circle
2 parents 3581904 + 75830eb commit e4b6a04

File tree

2 files changed

+64
-12
lines changed

2 files changed

+64
-12
lines changed

cadquery/cq.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2611,30 +2611,44 @@ def ellipse(
26112611
return self.eachpoint(lambda loc: e.moved(loc), True)
26122612

26132613
def polygon(
2614-
self: T, nSides: int, diameter: float, forConstruction: bool = False
2614+
self: T,
2615+
nSides: int,
2616+
diameter: float,
2617+
forConstruction: bool = False,
2618+
circumscribed: bool = False,
26152619
) -> T:
26162620
"""
2617-
Creates a polygon inscribed in a circle of the specified diameter for each point on
2618-
the stack
2621+
Make a polygon for each item on the stack.
26192622
2620-
The first vertex is always oriented in the x direction.
2623+
By default, each polygon is created by inscribing it in a circle of the
2624+
specified diameter, such that the first vertex is oriented in the x direction.
2625+
Alternatively, each polygon can be created by circumscribing it around
2626+
a circle of the specified diameter, such that the midpoint of the first edge
2627+
is oriented in the x direction. Circumscribed polygons are thus rotated by
2628+
pi/nSides radians relative to the inscribed polygon. This ensures the extent
2629+
of the polygon along the positive x-axis is always known.
2630+
This has the advantage of not requiring additional formulae for purposes such as
2631+
tiling on the x-axis (at least for even sided polygons).
26212632
26222633
:param nSides: number of sides, must be >= 3
2623-
:param diameter: the size of the circle the polygon is inscribed into
2634+
:param diameter: the diameter of the circle for constructing the polygon
2635+
:param circumscribed: circumscribe the polygon about a circle
2636+
:type circumscribed: true to create the polygon by circumscribing it about a circle,
2637+
false to create the polygon by inscribing it in a circle
26242638
:return: a polygon wire
26252639
"""
26262640

26272641
# pnt is a vector in local coordinates
26282642
angle = 2.0 * math.pi / nSides
2643+
radius = diameter / 2.0
2644+
if circumscribed:
2645+
radius /= math.cos(angle / 2.0)
26292646
pnts = []
26302647
for i in range(nSides + 1):
2631-
pnts.append(
2632-
Vector(
2633-
(diameter / 2.0 * math.cos(angle * i)),
2634-
(diameter / 2.0 * math.sin(angle * i)),
2635-
0,
2636-
)
2637-
)
2648+
o = angle * i
2649+
if circumscribed:
2650+
o += angle / 2.0
2651+
pnts.append(Vector(radius * math.cos(o), radius * math.sin(o), 0,))
26382652
p = Wire.makePolygon(pnts, forConstruction)
26392653

26402654
return self.eachpoint(lambda loc: p.moved(loc), True)

tests/test_cadquery.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4941,3 +4941,41 @@ def testFaceToPln(self):
49414941
p2 = f2.toPln()
49424942
self.assertTrue(p2.Contains(f2.Center().toPnt(), 0.1))
49434943
self.assertTrue(Vector(p2.Axis().Direction()) == f2.normalAt())
4944+
4945+
def testCircumscribedPolygon(self):
4946+
"""
4947+
Test that circumscribed polygons result in the correct shapes
4948+
"""
4949+
4950+
def circumradius(n, a):
4951+
return a / math.cos(math.pi / n)
4952+
4953+
a = 1
4954+
# Test triangle
4955+
vs = Workplane("XY").polygon(3, 2 * a, circumscribed=True).vertices().vals()
4956+
self.assertEqual(3, len(vs))
4957+
R = circumradius(3, a)
4958+
self.assertEqual(
4959+
vs[0].toTuple(), approx((a, a * math.tan(math.radians(60)), 0))
4960+
)
4961+
self.assertEqual(vs[1].toTuple(), approx((-R, 0, 0)))
4962+
self.assertEqual(
4963+
vs[2].toTuple(), approx((a, -a * math.tan(math.radians(60)), 0))
4964+
)
4965+
4966+
# Test square
4967+
vs = Workplane("XY").polygon(4, 2 * a, circumscribed=True).vertices().vals()
4968+
self.assertEqual(4, len(vs))
4969+
R = circumradius(4, a)
4970+
self.assertEqual(
4971+
vs[0].toTuple(), approx((a, a * math.tan(math.radians(45)), 0))
4972+
)
4973+
self.assertEqual(
4974+
vs[1].toTuple(), approx((-a, a * math.tan(math.radians(45)), 0))
4975+
)
4976+
self.assertEqual(
4977+
vs[2].toTuple(), approx((-a, -a * math.tan(math.radians(45)), 0))
4978+
)
4979+
self.assertEqual(
4980+
vs[3].toTuple(), approx((a, -a * math.tan(math.radians(45)), 0))
4981+
)

0 commit comments

Comments
 (0)