Spatial (3D) Math¶
Math utility library for common three-dimensional constructs:
The pint library can be used to specify dimensions:
>>> from petrify import u
>>> p = Point(50, 25, 50) * u.mm
>>> p.to('m')
<Quantity(Point(0.05, 0.025, 0.05), 'meter')>
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.
-
class
petrify.space.
Basis
(origin, bx, by)[source]¶ Bases:
object
Embeds a two-dimensional space into a three-dimensional space:
>>> basis = Basis(Point(1, 0, 0), Vector.basis.y, Vector.basis.z) >>> basis.project(Point2(2, 3)) Point(1, 2, 3) >>> basis.project(Vector2(-2, -3)) Vector(1, -2, -3)
Can be translated:
>>> translated = basis.xy + Vector(0, 0, 2) >>> translated Basis(Point(0, 0, 2), Vector(1, 0, 0), Vector(0, 1, 0)) >>> translated.project(Point2(2, 3)) Point(2, 3, 2)
There are special Basis objects for commonly used bases:
>>> Basis.unit Basis(Point(0, 0, 0), Vector(1, 0, 0), Vector(0, 1, 0)) >>> Basis.xy Basis(Point(0, 0, 0), Vector(1, 0, 0), Vector(0, 1, 0)) >>> Basis.yz Basis(Point(0, 0, 0), Vector(0, 1, 0), Vector(0, 0, 1)) >>> Basis.xz Basis(Point(0, 0, 0), Vector(1, 0, 0), Vector(0, 0, 1))
-
class
petrify.space.
Face
(basis, direction, polygon)[source]¶ Bases:
petrify.space.PlanarPolygon
A
PlanarPolygon
with an associated polarity. Face.Positive polarity follows the right hand rule, Face.Negative is inverted.>>> tri= Polygon2([Point2(0, 0), Point2(0, 2), Point2(1, 1)]) >>> triangle = Face(Basis.xy, Face.Positive, tri)
-
petrify.space.
Line
¶ alias of
petrify.space.Line3
-
class
petrify.space.
Line3
(*args)[source]¶ Bases:
petrify.space.util.Spatial
An infinite line:
>>> Line(Point(0, 0, 0), Vector(1, 1, 1)) Line(Point(0, 0, 0), Vector(1, 1, 1)) >>> Line(Point(0, 0, 0), Point(1, 1, 1)) Line(Point(0, 0, 0), Vector(1, 1, 1))
-
petrify.space.
LineSegment
¶ alias of
petrify.space.LineSegment3
-
class
petrify.space.
LineSegment3
(*args)[source]¶ Bases:
petrify.space.Line3
-
petrify.space.
Matrix
¶ alias of
petrify.space.transform.Matrix3
-
class
petrify.space.
Matrix3
[source]¶ Bases:
object
A matrix that can be used to perform common transformations on three-dimensional points and vectors:
>>> from . import Point, Vector >>> Matrix3.scale(*Vector(1, 2, 1).xyz) * Point(1, 1, 1) Point(1, 2, 1)
-
classmethod
identity
()[source]¶ The identity transform:
>>> from . import Point >>> Matrix3.identity() * Point(1, 1, 1) Point(1.0, 1.0, 1.0)
-
classmethod
rotate_at
(origin, axis, angle)[source]¶ A rotational transform:
>>> from . import Point, Vector >>> rotation = Matrix3.rotate_at(Point(1, 1, 1), Vector.basis.z, tau / 4) >>> (rotation * Point(2, 1, 1)).rounded() Point(1, 2, 1)
-
classmethod
rotate_axis
(axis, angle)[source]¶ A rotational transform:
>>> from . import Point, Vector >>> (Matrix3.rotate_axis(Vector.basis.z, tau / 4) * Point(1, 0, 0)).rounded() Point(0, 1, 0)
-
classmethod
-
class
petrify.space.
PlanarPolygon
(basis, polygon)[source]¶ Bases:
object
A two-dimensional
Polygon2
orComplexPolygon2
embedded in three-dimensional space via aBasis
:>>> tri = plane.Polygon2([ ... plane.Point2(0, 0), ... plane.Point2(0, 2), ... plane.Point2(1, 1) ... ]) >>> triangle = PlanarPolygon(Basis.xy, tri) >>> triangle.project() [Polygon([Point(0, 0, 0), Point(0, 2, 0), Point(1, 1, 0)])]
-
class
petrify.space.
Plane
(*args)[source]¶ Bases:
object
A three-dimensional plane.
Can be constructed with three coplanar points:
>>> Plane(Point(0, 0, 0), Point(1, 0, 0), Point(0, 1, 0)) Plane(Vector(0.0, 0.0, 1.0), 0.0)
Or an origin point and two basis vectors: >>> Plane(Point(0, 0, 0), Vector.basis.x, Vector.basis.y) Plane(Vector(0.0, 0.0, 1.0), 0.0)
Or a normal and solution scalar / point:
>>> Plane(Vector.basis.z, 0) Plane(Vector(0.0, 0.0, 1.0), 0) >>> Plane(Vector.basis.z, Point.origin) Plane(Vector(0.0, 0.0, 1.0), 0.0)
Plane also defines convenience methods for commonly used origin planes:
>>> Plane.xy Plane(Vector(0.0, 0.0, 1.0), 0.0) >>> Plane.xz Plane(Vector(0.0, 1.0, 0.0), 0.0) >>> Plane.yz Plane(Vector(1.0, 0.0, 0.0), 0.0)
-
intersect
(other)[source]¶ Find the point where this plane intersects the other line or plane:
>>> Plane(Vector(0, 1, 0), 1).intersect(Plane(Vector(1, 0, 0), 2)) Line(Point(2.0, 1.0, 0.0), Vector(0.0, 0.0, 1.0)) >>> Plane(Vector(0, 0, 1), 2).intersect(Line(Point(0, 0, 0), Vector(1, 1, 1))) Point(2.0, 2.0, 2.0)
-
-
petrify.space.
Point
¶ alias of
petrify.space.point.Point3
-
class
petrify.space.
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)
-
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))
-
-
class
petrify.space.
Point3
(x=0, y=0, z=0)[source]¶ Bases:
petrify.space.point.Vector3
,petrify.generic.Point
,petrify.geometry.Geometry
A close cousin of
Vector
, used to represent a point instead of a transform:>>> Point(1, 2, 3) + Vector(1, 1, 1) Point(2, 3, 4)
Defines a convenience .origin attribute for this commonly-used point:
>>> Point.origin Point(0, 0, 0)
-
petrify.space.
Polygon
¶ alias of
petrify.space.Polygon3
-
class
petrify.space.
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)])
-
-
class
petrify.space.
Polygon3
(points)[source]¶ Bases:
petrify.geometry.AbstractPolygon
,petrify.space.util.Spatial
A linear cycle of coplanar convex points:
>>> tri= Polygon([Point(0, 0, 0), Point(0, 2, 0), Point(1, 1, 0)]) >>> tri.plane Plane(Vector(0.0, 0.0, -1.0), 0.0) >>> tri * Vector(1, 2, 3) Polygon([Point(0, 0, 0), Point(0, 4, 0), Point(1, 2, 0)]) >>> tri * Matrix3.scale(1, 2, 3) Polygon([Point(0, 0, 0), Point(0, 4, 0), Point(1, 2, 0)]) >>> tri + Vector(1, 2, 1) Polygon([Point(1.0, 2.0, 1.0), Point(1.0, 4.0, 1.0), Point(2.0, 3.0, 1.0)]) >>> len(tri) 3
-
index_of
(point)[source]¶ Finds index of given point:
>>> p = Polygon([Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0)]) >>> p.index_of(Point(1, 1)) 2
-
inverted
()[source]¶ Reverses the points on a given polygon:
>>> p = Polygon([Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0)]) >>> p.inverted() Polygon([Point(1, 1, 0), Point(1, 0, 0), Point(0, 0, 0)])
-
simplify
(tolerance=0.0001)[source]¶ Remove any duplicate points, within a certain tolerance:
>>> Polygon([Point(1, 1, 0), Point(2, 0, 0), Point(0, 0, 0), Point(1, 1, 0)]).simplify() Polygon([Point(2, 0, 0), Point(0, 0, 0), Point(1, 1, 0)])
Returns None if the resulting simplification would create a point:
>>> Polygon([Point(1, 1, 0), Point(2, 0, 0), Point(0, 0, 0)]).simplify(100) is None True
-
-
class
petrify.space.
Quaternion
(w=1, x=0, y=0, z=0)[source]¶ Bases:
object
Quaternions are composable representations of three-dimensional rotation operations.
Multiplication can be performed on Vector instances to get the transformed vector or point:
>>> from . import Vector >>> r = Quaternion.rotate_axis(Vector.basis.x, tau / 4); >>> (r * Vector(0, 1, 0)).rounded() Vector(0, 0, 1)
-
petrify.space.
Ray
¶ alias of
petrify.space.Ray3
-
class
petrify.space.
Ray3
(*args)[source]¶ Bases:
petrify.space.Line3
A
Line
with a fixed origin that continues indefinitely in the given direction.
-
class
petrify.space.
Sphere
(center, radius)[source]¶ Bases:
object
A perfect sphere with the provided center and radius:
>>> Sphere(Point(0, 0, 0), 1.0) Sphere(Point(0, 0, 0), 1.0)
-
petrify.space.
Vector
¶ alias of
petrify.space.point.Vector3
-
class
petrify.space.
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).
-
-
class
petrify.space.
Vector3
(x=0, y=0, z=0)[source]¶ Bases:
petrify.generic.Concrete
,petrify.generic.Vector
,petrify.space.util.Spatial
A three-dimensional vector supporting all corresponding built-in math operators:
>>> Vector(1, 2, 3) + Vector(2, 2, 2) Vector(3, 4, 5) >>> Vector(1, 2, 3) - Vector(2, 2, 2) Vector(-1, 0, 1) >>> Vector(1, 0, 1) * 5 Vector(5, 0, 5) >>> Vector(1, 0, 1) / 5 Vector(0.2, 0.0, 0.2) >>> Vector(1, 1, 1) == Vector(1, 1, 1) True
In addition to many other specialized vector operations.
Defines convenience .basis members for commonly used basis vectors:
>>> Vector.basis.x; Vector.bx Vector(1, 0, 0) Vector(1, 0, 0) >>> Vector.basis.y; Vector.by Vector(0, 1, 0) Vector(0, 1, 0) >>> Vector.basis.z; Vector.bz Vector(0, 0, 1) Vector(0, 0, 1)
-
normalized
()[source]¶ Returns a vector with the same direction but unit (1) length:
>>> Vector(0, 0, 5).normalized() Vector(0.0, 0.0, 1.0)
-
reflect
(normal)[source]¶ Reflect this vector across a plane with the given normal
Note
Assumes the given normal has unit (1) length.
-