Plane (2D) Geometry¶
Math utility library for common two-dimensional constructs:
The pint library can be used to specify dimensions:
>>> from petrify import u
>>> p = Point(24, 12) * u.inches
>>> p.to(u.ft)
<Quantity(Point(2.0, 1.0), 'foot')>
Many methods are nominally supported when wrapped with pint. We recommend you only use units when exporting and importing data, and pick a canonical unit for all petrify operations.
Big thanks to pyeuclid, the source of most of the code here.
Note
These examples and this library make heavy use of the tau constant for rotational math instead of Pi. Read why at the Tau Manifesto.
-
petrify.plane.
ComplexPolygon
¶ alias of
petrify.plane.ComplexPolygon2
-
class
petrify.plane.
ComplexPolygon2
(polygons=None, interior=None, exterior=None)[source]¶ Bases:
object
Represents a complex polygon. A complex polygon is composed of one or more separate simple polygons, and may contain holes:
>>> from petrify.shape import Rectangle >>> square = Rectangle(Point(0, 0), Vector(1, 1)) >>> complex = ComplexPolygon([square + Vector(1, 1), square * 3]) >>> len(complex) 8
Supports common built-in methods:
>>> square = Rectangle(Point(0, 0), Vector(1, 1)) >>> ComplexPolygon([square]) + Vector(1, 1) ComplexPolygon([Polygon([Point(1, 1), Point(1, 2), Point(2, 2), Point(2, 1)])]) >>> ComplexPolygon([square]) - Vector(1, 1) ComplexPolygon([Polygon([Point(-1, -1), Point(-1, 0), Point(0, 0), Point(0, -1)])])
-
centered
(point)[source]¶ 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
()[source]¶ 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)[source]¶ 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
()[source]¶ 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
()[source]¶ 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.plane.
Line
¶ alias of
petrify.plane.line.Line2
-
class
petrify.plane.
Line2
(*args)[source]¶ Bases:
petrify.geometry.Geometry
,petrify.plane.planar.Planar
Represents an infinite line:
>>> Line(Point(0, 0), Vector(1, 1)) Line(Point(0, 0), Vector(1, 1)) >>> Line(Point(0, 0), Point(1, 1)) Line(Point(0, 0), Vector(1, 1))
Implements many built-in methods:
>>> Line(Point(1, 1), Point(2, 1)) + Vector(1, 1) Line(Point(2, 2), Vector(1, 0))
-
connect
(other)[source]¶ Finds the closest connecting line segment between this object and another:
>>> l = Line(Point(0, 2), Vector(0, -2)) >>> l.connect(Point(1, 0)) LineSegment(Point(0.0, 0.0), Point(1.0, 0.0)) >>> from petrify.plane import Circle >>> l.connect(Circle(Point(2, 0), 1)) LineSegment(Point(0.0, 0.0), Point(1.0, 0.0))
-
intersect
(other)[source]¶ Finds the intersection of this object and another:
>>> l = Line(Point(0, 2), Vector(0, -2)) >>> l.intersect(Line(Point(2, 0), Vector(-2, 0))) Point(0.0, 0.0) >>> l.intersect(Line(Point(1, 2), Vector(0, -2))) is None True >>> from petrify.plane import Circle >>> l.intersect(Circle(Point(0, 0), 1)) LineSegment(Point(0.0, -1.0), Point(0.0, 1.0))
-
-
petrify.plane.
LineSegment
¶ alias of
petrify.plane.line.LineSegment2
-
class
petrify.plane.
LineSegment2
(*args)[source]¶ Bases:
petrify.plane.line.Line2
Represents a line segment:
>>> LineSegment(Point(0, 0), Vector(1, 1)) LineSegment(Point(0, 0), Point(1, 1))
-
connect
(other)¶ Finds the closest connecting line segment between this object and another:
>>> l = Line(Point(0, 2), Vector(0, -2)) >>> l.connect(Point(1, 0)) LineSegment(Point(0.0, 0.0), Point(1.0, 0.0)) >>> from petrify.plane import Circle >>> l.connect(Circle(Point(2, 0), 1)) LineSegment(Point(0.0, 0.0), Point(1.0, 0.0))
-
intersect
(other)¶ Finds the intersection of this object and another:
>>> l = Line(Point(0, 2), Vector(0, -2)) >>> l.intersect(Line(Point(2, 0), Vector(-2, 0))) Point(0.0, 0.0) >>> l.intersect(Line(Point(1, 2), Vector(0, -2))) is None True >>> from petrify.plane import Circle >>> l.intersect(Circle(Point(0, 0), 1)) LineSegment(Point(0.0, -1.0), Point(0.0, 1.0))
-
normalized
()¶ Normalizes this line:
>>> Line(Point(0, 0), Point(2, 0)).normalized() Line(Point(0, 0), Vector(1.0, 0.0))
-
-
petrify.plane.
Matrix
¶ alias of
petrify.plane.Matrix2
-
class
petrify.plane.
Matrix2
[source]¶ Bases:
object
A matrix that can be used to transform two-dimensional vectors and points.
-
classmethod
rotate
(angle)[source]¶ Counter-clockwise rotational transform:
>>> (Point(1, 0) * Matrix2.rotate(tau / 4)).round(2) Point(0.0, 1.0)
-
classmethod
-
petrify.plane.
Point
¶ alias of
petrify.plane.point.Point2
-
class
petrify.plane.
Point2
(x=0, y=0)[source]¶ Bases:
petrify.plane.point.Vector2
,petrify.geometry.Geometry
,petrify.generic.Point
A close cousin of
Vector2
used to represent points:>>> Point(1, 1) + Vector(2, 3) Point(3, 4) >>> Point(1, 2) * 2 Point(2, 4)
-
angle
(other)¶ Return the angle to the vector other:
>>> Vector(1, 0).angle(Vector(0, 1)) == tau / 4 True
-
connect
(other)[source]¶ Connects this point to the other given geometry:
>>> from petrify.plane import Circle, Line >>> Point(1, 1).connect(Line(Point(0, 0), Vector(1, 0))) LineSegment(Point(1, 1), Point(1.0, 0.0)) >>> Point(1, 1).connect(Point(0, 0)) LineSegment(Point(1, 1), Point(0, 0)) >>> Point(0, 2).connect(Circle(Point(0, 0), 1)) LineSegment(Point(0, 2), Point(0.0, 1.0))
-
dot
(other)¶ The dot product of this vector and other:
>>> Vector(2, 1).dot(Vector(2, 3)) 7
-
intersect
(other)[source]¶ Used to determine if this point is within a circle:
>>> from petrify.plane import Circle >>> Point(1, 1).intersect(Circle(Point(0, 0), 2)) True >>> Point(3, 3).intersect(Circle(Point(0, 0), 2)) False
-
normalized
()¶ Return a new vector normalized to unit length:
>>> Vector(0, 5).normalized() Vector(0.0, 1.0)
-
project
(other)¶ Return one vector projected on the vector other
-
reflected
(normal)¶ Reflects this vector across a line with the given perpendicular normal:
>>> Vector(1, 1).reflected(Vector(0, 1)) Vector(1, -1)
Warning
Assumes normal is normalized (has unit length).
-
round
(places)¶ Rounds this vector to the given decimal place:
>>> Vector(1.00001, 1.00001).round(2) Vector(1.0, 1.0)
-
snap
(grid)¶ Snaps this vector to a grid:
>>> Vector(1.15, 1.15).snap(0.25) Vector(1.25, 1.25)
-
-
petrify.plane.
Polygon
¶ alias of
petrify.plane.Polygon2
-
class
petrify.plane.
Polygon2
(points)[source]¶ Bases:
petrify.geometry.AbstractPolygon
,petrify.plane.planar.Planar
A two-dimensional polygon:
>>> Polygon([Point(2, 0), Point(0, 0), Point(1, 1)]) Polygon([Point(2, 0), Point(0, 0), Point(1, 1)])
Supports scaling and translation:
>>> tri = Polygon([Point(2, 0), Point(0, 0), Point(1, 1)]) >>> tri * Vector(2, 3) Polygon([Point(4, 0), Point(0, 0), Point(2, 3)]) >>> tri * Matrix2.scale(2, 3) Polygon([Point(4, 0), Point(0, 0), Point(2, 3)]) >>> tri + Vector(2, 1) Polygon([Point(4, 1), Point(2, 1), Point(3, 2)]) >>> tri - Vector(1, 1) Polygon([Point(1, -1), Point(-1, -1), Point(0, 0)]) >>> len(tri) 3
-
centered
(point)[source]¶ 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
()[source]¶ 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)[source]¶ 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
()[source]¶ 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)[source]¶ Finds index of given point:
>>> Polygon([Point(0, 0), Point(1, 0), Point(1, 1)]).index_of(Point(1, 1)) 2
-
inverted
()[source]¶ 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)[source]¶ 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
()[source]¶ 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)[source]¶ 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)[source]¶ 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)[source]¶ 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
()[source]¶ 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
()[source]¶ 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)])
-
-
petrify.plane.
Ray
¶ alias of
petrify.plane.line.Ray2
-
class
petrify.plane.
Ray2
(*args)[source]¶ Bases:
petrify.plane.line.Line2
Represents a line with an origin point that extends forever:
>>> Ray(Point(0, 0), Vector(1, 1)) Ray(Point(0, 0), Vector(1, 1))
-
connect
(other)¶ Finds the closest connecting line segment between this object and another:
>>> l = Line(Point(0, 2), Vector(0, -2)) >>> l.connect(Point(1, 0)) LineSegment(Point(0.0, 0.0), Point(1.0, 0.0)) >>> from petrify.plane import Circle >>> l.connect(Circle(Point(2, 0), 1)) LineSegment(Point(0.0, 0.0), Point(1.0, 0.0))
-
intersect
(other)¶ Finds the intersection of this object and another:
>>> l = Line(Point(0, 2), Vector(0, -2)) >>> l.intersect(Line(Point(2, 0), Vector(-2, 0))) Point(0.0, 0.0) >>> l.intersect(Line(Point(1, 2), Vector(0, -2))) is None True >>> from petrify.plane import Circle >>> l.intersect(Circle(Point(0, 0), 1)) LineSegment(Point(0.0, -1.0), Point(0.0, 1.0))
-
normalized
()¶ Normalizes this line:
>>> Line(Point(0, 0), Point(2, 0)).normalized() Line(Point(0, 0), Vector(1.0, 0.0))
-
-
petrify.plane.
Vector
¶ alias of
petrify.plane.point.Vector2
-
class
petrify.plane.
Vector2
(x=0, y=0)[source]¶ Bases:
petrify.generic.Concrete
,petrify.generic.Vector
,petrify.plane.planar.Planar
A two-dimensional vector supporting all corresponding built-in math operators:
>>> Vector(1, 2) + Vector(2, 2) Vector(3, 4) >>> Vector(1, 2) - Vector(2, 2) Vector(-1, 0) >>> Vector(1, 1) * 5 Vector(5, 5) >>> Vector(1, 1) / 5 Vector(0.2, 0.2) >>> Vector(1, 1) == Vector(1, 1) True
In addition to many other specialized vector operations.
-
angle
(other)[source]¶ Return the angle to the vector other:
>>> Vector(1, 0).angle(Vector(0, 1)) == tau / 4 True
-
normalized
()[source]¶ Return a new vector normalized to unit length:
>>> Vector(0, 5).normalized() Vector(0.0, 1.0)
-
reflected
(normal)[source]¶ Reflects this vector across a line with the given perpendicular normal:
>>> Vector(1, 1).reflected(Vector(0, 1)) Vector(1, -1)
Warning
Assumes normal is normalized (has unit length).
-