Planar Shape Construction

Basic representations of common two-dimensional shapes:

Rectangle :
An axis-aligned rectangle.
Circle :
A planar circle with a defined origin and radius.
Text :
The shapes formed from a line of text.

Also contains convenience methods for creating complex polygons:

arc() :
a semi-circular arc of points
bezier() :
a bezier curve with specified control points
fillet() :
a filleted corner with a specified radius from three points
class petrify.shape.Circle(origin, radius, segments=10)[source]

Bases: petrify.plane.Polygon2

Approximates a perfect circle with a finite number of line segments:

>>> circle = Circle(Point(0, 0), 1, 5)
PointsConstructor

alias of petrify.plane.Polygon2

centered(point)

Center this polygon at a given point:

>>> from petrify.shape import Rectangle
>>> Rectangle(Point(0, 0), Vector(2, 2)).centered(Point(3, 3))
Polygon([Point(2.0, 2.0), Point(2.0, 4.0), Point(4.0, 4.0), Point(4.0, 2.0)])
clockwise()

Returns True if the points in this polygon are in clockwise order:

>>> Polygon([Point(2, 0), Point(0, 0), Point(1, 1)]).clockwise()
True
>>> Polygon([Point(1, 1), Point(0, 0), Point(2, 0)]).clockwise()
False
contains(p)

Tests whether a point lies within this polygon:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> tri.contains(Point(1.0, 0.5))
True
>>> tri.contains(Point(0.5, 1.5))
False
envelope()

Returns the bounding Rectangle around this polygon:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> tri.envelope()
Rectangle(Point(0, 0), Vector(2, 1))
index_of(point)

Finds index of given point:

>>> Polygon([Point(0, 0), Point(1, 0), Point(1, 1)]).index_of(Point(1, 1))
2
inverted()

Reverse the points in this polygon:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> tri.inverted()
Polygon([Point(1, 1), Point(0, 0), Point(2, 0)])
inwards(edge)

Finds the normalized Ray2 facing inwards for a given edge:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> tri.inwards(tri.segments()[0])
Vector(0.0, 1.0)
is_convex()

Return True if the polynomial defined by the sequence of 2D points is ‘strictly convex’:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> tri.is_convex()
True
>>> indent = Polygon([
...     Point(0, 0),
...     Point(10, 0),
...     Point(5, 5),
...     Point(10, 10),
...     Point(0, 10)
... ])
>>> indent.is_convex()
False

Note

“strictly convex” is defined as follows:

  • points are valid
  • side lengths are non-zero
  • interior angles are strictly between zero and a straight angle
  • the polygon does not intersect itself
offset(amount)

Finds the dynamic offset of a polygon by moving all edges by a given amount perpendicular to their direction:

>>> square = Polygon([Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)])
>>> square.offset(-0.1).exterior[0]
Polygon([Point(0.1, 0.1), Point(0.1, 0.9), Point(0.9, 0.9), Point(0.9, 0.1)])
>>> square.offset(0.1).exterior[0]
Polygon([Point(-0.1, -0.1), Point(-0.1, 1.1), Point(1.1, 1.1), Point(1.1, -0.1)])
>>> square.offset(-10)
ComplexPolygon([])

Note

This always returns a ComplexPolygon. Collisions during the offset process can subdivide the polygon.

shift(n)

Shift the points in this polygon by n:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> tri.shift(1)
Polygon([Point(0, 0), Point(1, 1), Point(2, 0)])
simplify(tolerance=0.0001)

Remove any duplicate points, within a certain tolerance:

>>> Polygon([Point(1, 1), Point(2, 0), Point(0, 0), Point(1, 1)]).simplify()
Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])

Returns None if the resulting simplification would create a point:

>>> Polygon([
...     Point(1, 1),
...     Point(2, 0),
...     Point(0, 0),
...     Point(1, 1)
... ]).simplify(100) is None
True
to_clockwise()

Converts this polygon to a clockwise one if necessary:

>>> Polygon([Point(2, 0), Point(0, 0), Point(1, 1)]).to_clockwise()
Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> Polygon([Point(1, 1), Point(0, 0), Point(2, 0)]).to_clockwise()
Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
to_counterclockwise()

Converts this polygon to a clockwise one if necessary:

>>> Polygon([Point(2, 0), Point(0, 0), Point(1, 1)]).to_counterclockwise()
Polygon([Point(1, 1), Point(0, 0), Point(2, 0)])
>>> Polygon([Point(1, 1), Point(0, 0), Point(2, 0)]).to_counterclockwise()
Polygon([Point(1, 1), Point(0, 0), Point(2, 0)])
class petrify.shape.Rectangle(origin, extent)[source]

Bases: petrify.plane.Polygon2

An axis-aligned rectangle with a point of origin and a vector or point extent:

>>> Rectangle(Point(0, 0), Vector(1, 1))
Rectangle(Point(0, 0), Vector(1, 1))
>>> Rectangle(Point(1, 1), Point(2, 3))
Rectangle(Point(1, 1), Vector(1, 2))
PointsConstructor

alias of petrify.plane.Polygon2

centered(point)

Center this polygon at a given point:

>>> from petrify.shape import Rectangle
>>> Rectangle(Point(0, 0), Vector(2, 2)).centered(Point(3, 3))
Polygon([Point(2.0, 2.0), Point(2.0, 4.0), Point(4.0, 4.0), Point(4.0, 2.0)])
clockwise()

Returns True if the points in this polygon are in clockwise order:

>>> Polygon([Point(2, 0), Point(0, 0), Point(1, 1)]).clockwise()
True
>>> Polygon([Point(1, 1), Point(0, 0), Point(2, 0)]).clockwise()
False
contains(p)

Tests whether a point lies within this polygon:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> tri.contains(Point(1.0, 0.5))
True
>>> tri.contains(Point(0.5, 1.5))
False
envelope()

Returns the bounding Rectangle around this polygon:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> tri.envelope()
Rectangle(Point(0, 0), Vector(2, 1))
index_of(point)

Finds index of given point:

>>> Polygon([Point(0, 0), Point(1, 0), Point(1, 1)]).index_of(Point(1, 1))
2
inverted()

Reverse the points in this polygon:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> tri.inverted()
Polygon([Point(1, 1), Point(0, 0), Point(2, 0)])
inwards(edge)

Finds the normalized Ray2 facing inwards for a given edge:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> tri.inwards(tri.segments()[0])
Vector(0.0, 1.0)
is_convex()

Return True if the polynomial defined by the sequence of 2D points is ‘strictly convex’:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> tri.is_convex()
True
>>> indent = Polygon([
...     Point(0, 0),
...     Point(10, 0),
...     Point(5, 5),
...     Point(10, 10),
...     Point(0, 10)
... ])
>>> indent.is_convex()
False

Note

“strictly convex” is defined as follows:

  • points are valid
  • side lengths are non-zero
  • interior angles are strictly between zero and a straight angle
  • the polygon does not intersect itself
offset(amount)

Finds the dynamic offset of a polygon by moving all edges by a given amount perpendicular to their direction:

>>> square = Polygon([Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)])
>>> square.offset(-0.1).exterior[0]
Polygon([Point(0.1, 0.1), Point(0.1, 0.9), Point(0.9, 0.9), Point(0.9, 0.1)])
>>> square.offset(0.1).exterior[0]
Polygon([Point(-0.1, -0.1), Point(-0.1, 1.1), Point(1.1, 1.1), Point(1.1, -0.1)])
>>> square.offset(-10)
ComplexPolygon([])

Note

This always returns a ComplexPolygon. Collisions during the offset process can subdivide the polygon.

shift(n)

Shift the points in this polygon by n:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> tri.shift(1)
Polygon([Point(0, 0), Point(1, 1), Point(2, 0)])
simplify(tolerance=0.0001)

Remove any duplicate points, within a certain tolerance:

>>> Polygon([Point(1, 1), Point(2, 0), Point(0, 0), Point(1, 1)]).simplify()
Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])

Returns None if the resulting simplification would create a point:

>>> Polygon([
...     Point(1, 1),
...     Point(2, 0),
...     Point(0, 0),
...     Point(1, 1)
... ]).simplify(100) is None
True
to_clockwise()

Converts this polygon to a clockwise one if necessary:

>>> Polygon([Point(2, 0), Point(0, 0), Point(1, 1)]).to_clockwise()
Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> Polygon([Point(1, 1), Point(0, 0), Point(2, 0)]).to_clockwise()
Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
to_counterclockwise()

Converts this polygon to a clockwise one if necessary:

>>> Polygon([Point(2, 0), Point(0, 0), Point(1, 1)]).to_counterclockwise()
Polygon([Point(1, 1), Point(0, 0), Point(2, 0)])
>>> Polygon([Point(1, 1), Point(0, 0), Point(2, 0)]).to_counterclockwise()
Polygon([Point(1, 1), Point(0, 0), Point(2, 0)])
class petrify.shape.Text(face, text)[source]

Bases: petrify.plane.ComplexPolygon2

Using a Face provided by the freetype-py bindings, generate a ComplexPolygon2 for the corresponding text:

>>> from freetype import Face
>>> face = Face('./tests/fixtures/RussoOne.ttf')
>>> polygon = Text(face, 'petrify')

Uses the face’s pre-defined line height for the size of the text, scaled to 1.0 units on the y axis.

centered(point)

Center this polygon at a given point:

>>> from petrify.shape import Rectangle
>>> ComplexPolygon([
...   Rectangle(Point(0, 0), Vector(2, 2)),
...   Rectangle(Point(1, 1), Vector(1, 1)),
... ]).centered(Point(3, 3)).envelope()
Rectangle(Point(2.0, 2.0), Vector(2.0, 2.0))
envelope()

Returns the bounding Rectangle around this polygon:

>>> square = Polygon([Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)])
>>> complex = ComplexPolygon([square + Vector(1, 1), square * 3])
>>> complex.envelope()
Rectangle(Point(0, 0), Vector(3, 3))
offset(amount)

Finds the dynamic offset of this complex polygon by moving all edges by a given amount perpendicular to their direction.

Any inner polygons are offset in the reverse direction to the outer polygons:

>>> square = Polygon([Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)])
>>> complex = ComplexPolygon([square + Vector(1, 1), square * 3])
>>> complex.offset(-0.1).interior
[Polygon([Point(0.9, 0.9), Point(0.9, 2.1), Point(2.1, 2.1), Point(2.1, 0.9)])]
>>> complex.offset(-0.1).exterior
[Polygon([Point(0.1, 0.1), Point(0.1, 2.9), Point(2.9, 2.9), Point(2.9, 0.1)])]
to_clockwise()

Converts all sub-polygons to clockwise:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> ComplexPolygon([tri]).to_clockwise()
ComplexPolygon([Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])])
>>> ComplexPolygon([tri.inverted()]).to_clockwise()
ComplexPolygon([Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])])
to_counterclockwise()

Converts all sub-polygons to counter-clockwise:

>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
>>> ComplexPolygon([tri]).to_counterclockwise()
ComplexPolygon([Polygon([Point(1, 1), Point(0, 0), Point(2, 0)])])
>>> ComplexPolygon([tri.inverted()]).to_counterclockwise()
ComplexPolygon([Polygon([Point(1, 1), Point(0, 0), Point(2, 0)])])
petrify.shape.arc(center, radius, start, end, segments=10)[source]

Creates an arc of points around the given center with a specified radius and start and end angles, approximated with a fixed number of segments:

>>> points = arc(Point(5, 0), 5, 0, tau / 2, segments = 3)
>>> [p.snap(0.1) for p in points]
[Point(10.0, 0.0), Point(5.0, 5.0), Point(0.0, 0.0)]
petrify.shape.bezier(a, b, c, d, segments=10)[source]

Creates a bezier curve between the given control points, approximated with a given number of segments:

>>> points = bezier(Point(0, 0), Point(5, 0), Point(5, 5), Point(10, 5), 4)
>>> [p.snap(1.0) for p in points]
[Point(0.0, 0.0), Point(4.0, 1.0), Point(6.0, 4.0), Point(10.0, 5.0)]

You can also use relative Vector controls instead of absolute points:

>>> points = bezier(Point(0, 0), Vector(5, 0), Vector(-5, 0), Point(10, 5), 4)
>>> [p.snap(1.0) for p in points]
[Point(0.0, 0.0), Point(4.0, 1.0), Point(6.0, 4.0), Point(10.0, 5.0)]
petrify.shape.fillet(a, b, c, r, segments=10)[source]

Creates a smooth circular fillet from an ordered triplet of points that define a corner:

>>> points = fillet(Point(1, 0), Point(0, 0), Point(0, 1), 0.5, segments = 3)
>>> [p.snap(0.1) for p in points]
[Point(0.5, 0.0), Point(0.1, 0.1), Point(0.0, 0.5)]