Skip to content

Commit 75830eb

Browse files
committed
Adds ability to define polygons with a circumscribed circle
This adds the option to define polygons by a circumscribed circle in addition to the previous of inscribed circles. Inscribing remains the default. Circumscribing is useful in certain circumstances such as when the distance from the center of the polygon to the midpoint of one of the segments needs to be defined, e.g. when tiling the polygons along the x-axis.
1 parent 3581904 commit 75830eb

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)