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 = Point2(24, 12) * u.inches
>>> p.to(u.ft)
<Quantity(Point2(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:
objectRepresents a complex polygon. A complex polygon is composed of one or more separate simple polygons, and may contain holes.
-
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 = Polygon2([Point2(0, 0), Point2(0, 1), Point2(1, 1), Point2(1, 0)]) >>> complex = ComplexPolygon2([square + Vector2(1, 1), square * 3]) >>> complex.offset(-0.1).interior [Polygon2([Point2(0.9, 0.9), Point2(0.9, 2.1), Point2(2.1, 2.1), Point2(2.1, 0.9)])] >>> complex.offset(-0.1).exterior [Polygon2([Point2(0.1, 0.1), Point2(0.1, 2.9), Point2(2.9, 2.9), Point2(2.9, 0.1)])]
-
to_clockwise()[source]¶ Converts all sub-polygons to clockwise:
>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]) >>> ComplexPolygon2([tri]).to_clockwise() ComplexPolygon2([Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])]) >>> ComplexPolygon2([tri.inverted()]).to_clockwise() ComplexPolygon2([Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])])
-
to_counterclockwise()[source]¶ Converts all sub-polygons to counter-clockwise:
>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]) >>> ComplexPolygon2([tri]).to_counterclockwise() ComplexPolygon2([Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)])]) >>> ComplexPolygon2([tri.inverted()]).to_counterclockwise() ComplexPolygon2([Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)])])
-
-
petrify.plane.Line¶ alias of
petrify.plane.Line2
-
class
petrify.plane.Line2(*args)[source]¶ Bases:
petrify.geometry.Geometry,petrify.plane.PlanarRepresents an infinite line:
>>> Line2(Point2(0, 0), Vector2(1, 1)) Line2(Point2(0, 0), Vector2(1, 1)) >>> Line2(Point2(0, 0), Point2(1, 1)) Line2(Point2(0, 0), Vector2(1, 1))
-
connect(other)[source]¶ Finds the closest connecting line segment between this object and another:
>>> l = Line2(Point2(0, 2), Vector2(0, -2)) >>> l.connect(Point2(1, 0)) LineSegment2(Point2(0.0, 0.0), Point2(1.0, 0.0)) >>> l.connect(Circle(Point2(2, 0), 1)) LineSegment2(Point2(0.0, 0.0), Point2(1.0, 0.0))
-
intersect(other)[source]¶ Finds the intersection of this object and another:
>>> l = Line2(Point2(0, 2), Vector2(0, -2)) >>> l.intersect(Line2(Point2(2, 0), Vector2(-2, 0))) Point2(0.0, 0.0) >>> l.intersect(Line2(Point2(1, 2), Vector2(0, -2))) is None True >>> l.intersect(Circle(Point2(0, 0), 1)) LineSegment2(Point2(0.0, -1.0), Point2(0.0, 1.0))
-
-
petrify.plane.LineSegment¶ alias of
petrify.plane.LineSegment2
-
class
petrify.plane.LineSegment2(*args)[source]¶ Bases:
petrify.plane.Line2Represents a line segment:
>>> LineSegment2(Point2(0, 0), Vector2(1, 1)) LineSegment2(Point2(0, 0), Point2(1, 1))
-
connect(other)¶ Finds the closest connecting line segment between this object and another:
>>> l = Line2(Point2(0, 2), Vector2(0, -2)) >>> l.connect(Point2(1, 0)) LineSegment2(Point2(0.0, 0.0), Point2(1.0, 0.0)) >>> l.connect(Circle(Point2(2, 0), 1)) LineSegment2(Point2(0.0, 0.0), Point2(1.0, 0.0))
-
intersect(other)¶ Finds the intersection of this object and another:
>>> l = Line2(Point2(0, 2), Vector2(0, -2)) >>> l.intersect(Line2(Point2(2, 0), Vector2(-2, 0))) Point2(0.0, 0.0) >>> l.intersect(Line2(Point2(1, 2), Vector2(0, -2))) is None True >>> l.intersect(Circle(Point2(0, 0), 1)) LineSegment2(Point2(0.0, -1.0), Point2(0.0, 1.0))
-
normalized()¶ Normalizes this line:
>>> Line2(Point2(0, 0), Point2(2, 0)).normalized() Line2(Point2(0, 0), Vector2(1.0, 0.0))
-
-
petrify.plane.Matrix¶ alias of
petrify.plane.Matrix2
-
class
petrify.plane.Matrix2[source]¶ Bases:
objectA matrix that can be used to transform two-dimensional vectors and points.
-
classmethod
rotate(angle)[source]¶ Counter-clockwise rotational transform:
>>> (Point2(1, 0) * Matrix2.rotate(tau / 4)).round(2) Point2(0.0, 1.0)
-
classmethod
-
petrify.plane.Point¶ alias of
petrify.plane.Point2
-
class
petrify.plane.Point2(x=0, y=0)[source]¶ Bases:
petrify.plane.Vector2,petrify.geometry.GeometryA close cousin of
Vector2used to represent points:>>> Point2(1, 1) + Vector2(2, 3) Point2(3, 4) >>> Point2(1, 2) * 2 Point2(2, 4)
-
angle(other)¶ Return the angle to the vector other:
>>> Vector2(1, 0).angle(Vector2(0, 1)) == tau / 4 True
-
connect(other)[source]¶ Connects this point to the other given geometry:
>>> Point2(1, 1).connect(Line2(Point2(0, 0), Vector2(1, 0))) LineSegment2(Point2(1, 1), Point2(1.0, 0.0)) >>> Point2(1, 1).connect(Point2(0, 0)) LineSegment2(Point2(1, 1), Point2(0, 0)) >>> Point2(0, 2).connect(Circle(Point2(0, 0), 1)) LineSegment2(Point2(0, 2), Point2(0.0, 1.0))
-
dot(other)¶ The dot product of this vector and other:
>>> Vector2(2, 1).dot(Vector2(2, 3)) 7
-
intersect(other)[source]¶ Used to determine if this point is within a circle:
>>> Point2(1, 1).intersect(Circle(Point2(0, 0), 2)) True >>> Point2(3, 3).intersect(Circle(Point2(0, 0), 2)) False
-
normalized()¶ Return a new vector normalized to unit length:
>>> Vector2(0, 5).normalized() Vector2(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:
>>> Vector2(1, 1).reflected(Vector2(0, 1)) Vector2(1, -1)
Warning
Assumes normal is normalized (has unit length).
-
round(places)¶ Rounds this vector to the given decimal place:
>>> Vector2(1.00001, 1.00001).round(2) Vector2(1.0, 1.0)
-
snap(grid)¶ Snaps this vector to a grid:
>>> Vector2(1.15, 1.15).snap(0.25) Vector2(1.25, 1.25)
-
-
petrify.plane.Polygon¶ alias of
petrify.plane.Polygon2
-
class
petrify.plane.Polygon2(points)[source]¶ Bases:
petrify.plane.PlanarA two-dimensional polygon:
>>> Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]) Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
Supports scaling and translation:
>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]) >>> tri * Vector2(2, 3) Polygon2([Point2(4, 0), Point2(0, 0), Point2(2, 3)]) >>> tri * Matrix2.scale(2, 3) Polygon2([Point2(4, 0), Point2(0, 0), Point2(2, 3)]) >>> tri + Vector2(2, 1) Polygon2([Point2(4, 1), Point2(2, 1), Point2(3, 2)])
-
clockwise()[source]¶ Returns True if the points in this polygon are in clockwise order:
>>> Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]).clockwise() True >>> Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)]).clockwise() False
-
contains(p)[source]¶ Tests whether a point lies within this polygon:
>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]) >>> tri.contains(Point2(1.0, 0.5)) True >>> tri.contains(Point2(0.5, 1.5)) False
-
inverted()[source]¶ Reverse the points in this polygon:
>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]) >>> tri.inverted() Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)])
-
inwards(edge)[source]¶ Finds the normalized
Ray2facing inwards for a given edge:>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]) >>> tri.inwards(tri.segments()[0]) Vector2(0.0, 1.0)
-
is_convex()[source]¶ Return True if the polynomial defined by the sequence of 2D points is ‘strictly convex’:
>>> tri = Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]) >>> tri.is_convex() True >>> indent = Polygon2([ Point2(0, 0), Point2(10, 0), Point2(5, 5), Point2(10, 10), Point2(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, tolerance=0.0001)[source]¶ Finds the dynamic offset of a polygon by moving all edges by a given amount perpendicular to their direction:
>>> square = Polygon2([Point2(0, 0), Point2(0, 1), Point2(1, 1), Point2(1, 0)]) >>> square.offset(-0.1) Polygon2([Point2(0.1, 0.1), Point2(0.1, 0.9), Point2(0.9, 0.9), Point2(0.9, 0.1)]) >>> square.offset(0.1) Polygon2([Point2(-0.1, -0.1), Point2(-0.1, 1.1), Point2(1.1, 1.1), Point2(1.1, -0.1)]) >>> square.offset(10) is None True
Note
This is currently a naive implementation that does not properly handle non-local intersections that can split the polygon.
-
simplify(tolerance=0.0001)[source]¶ Remove any duplicate points, within a certain tolerance:
>>> Polygon2([Point2(1, 1), Point2(2, 0), Point2(0, 0), Point2(1, 1)]).simplify() Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
Returns None if the resulting simplification would create a point:
>>> Polygon2([Point2(1, 1), Point2(2, 0), Point2(0, 0), Point2(1, 1)]).simplify(100) is None True
-
to_clockwise()[source]¶ Converts this polygon to a clockwise one if necessary:
>>> Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]).to_clockwise() Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]) >>> Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)]).to_clockwise() Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)])
-
to_counterclockwise()[source]¶ Converts this polygon to a clockwise one if necessary:
>>> Polygon2([Point2(2, 0), Point2(0, 0), Point2(1, 1)]).to_counterclockwise() Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)]) >>> Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)]).to_counterclockwise() Polygon2([Point2(1, 1), Point2(0, 0), Point2(2, 0)])
-
-
petrify.plane.Ray¶ alias of
petrify.plane.Ray2
-
class
petrify.plane.Ray2(*args)[source]¶ Bases:
petrify.plane.Line2Represents a line with an origin point that extends forever:
>>> Ray2(Point2(0, 0), Vector2(1, 1)) Ray2(Point2(0, 0), Vector2(1, 1))
-
connect(other)¶ Finds the closest connecting line segment between this object and another:
>>> l = Line2(Point2(0, 2), Vector2(0, -2)) >>> l.connect(Point2(1, 0)) LineSegment2(Point2(0.0, 0.0), Point2(1.0, 0.0)) >>> l.connect(Circle(Point2(2, 0), 1)) LineSegment2(Point2(0.0, 0.0), Point2(1.0, 0.0))
-
intersect(other)¶ Finds the intersection of this object and another:
>>> l = Line2(Point2(0, 2), Vector2(0, -2)) >>> l.intersect(Line2(Point2(2, 0), Vector2(-2, 0))) Point2(0.0, 0.0) >>> l.intersect(Line2(Point2(1, 2), Vector2(0, -2))) is None True >>> l.intersect(Circle(Point2(0, 0), 1)) LineSegment2(Point2(0.0, -1.0), Point2(0.0, 1.0))
-
normalized()¶ Normalizes this line:
>>> Line2(Point2(0, 0), Point2(2, 0)).normalized() Line2(Point2(0, 0), Vector2(1.0, 0.0))
-
-
petrify.plane.Vector¶ alias of
petrify.plane.Vector2
-
class
petrify.plane.Vector2(x=0, y=0)[source]¶ Bases:
petrify.plane.PlanarA two-dimensional vector supporting all corresponding built-in math operators:
>>> Vector2(1, 2) + Vector2(2, 2) Vector2(3, 4) >>> Vector2(1, 2) - Vector2(2, 2) Vector2(-1, 0) >>> Vector2(1, 1) * 5 Vector2(5, 5) >>> Vector2(1, 1) / 5 Vector2(0.2, 0.2) >>> Vector2(1, 1) == Vector2(1, 1) True
In addition to many other specialized vector operations.
-
angle(other)[source]¶ Return the angle to the vector other:
>>> Vector2(1, 0).angle(Vector2(0, 1)) == tau / 4 True
-
dot(other)[source]¶ The dot product of this vector and other:
>>> Vector2(2, 1).dot(Vector2(2, 3)) 7
-
normalized()[source]¶ Return a new vector normalized to unit length:
>>> Vector2(0, 5).normalized() Vector2(0.0, 1.0)
-
reflected(normal)[source]¶ Reflects this vector across a line with the given perpendicular normal:
>>> Vector2(1, 1).reflected(Vector2(0, 1)) Vector2(1, -1)
Warning
Assumes normal is normalized (has unit length).
-