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 = Point3(50, 25, 50) * u.mm
>>> p.to('m')
<Quantity(Point3(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]

Embeds a two-dimensional space into a three-dimensional space:

>>> basis = Basis(Point3(1, 0, 0), Vector3.basis.y, Vector3.basis.z)
>>> basis.project(Point2(2, 3))
Point3(1, 2, 3)
>>> basis.project(Vector2(-2, -3))
Vector3(1, -2, -3)

Can be translated:

>>> translated = basis.xy + Vector3(0, 0, 2)
>>> translated
Basis(Point3(0, 0, 2), Vector3(1, 0, 0), Vector3(0, 1, 0))
>>> translated.project(Point2(2, 3))
Point3(2, 3, 2)

Note

Any given Plane has an infinite number of associated Basis constructions.

There are special Basis objects for commonly used bases:

>>> Basis.unit
Basis(Point3(0, 0, 0), Vector3(1, 0, 0), Vector3(0, 1, 0))
>>> Basis.xy
Basis(Point3(0, 0, 0), Vector3(1, 0, 0), Vector3(0, 1, 0))
>>> Basis.yz
Basis(Point3(0, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1))
>>> Basis.xz
Basis(Point3(0, 0, 0), Vector3(1, 0, 0), Vector3(0, 0, 1))
class petrify.space.Face(basis, direction, polygon)[source]

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]

An infinite line:

>>> Line3(Point3(0, 0, 0), Vector3(1, 1, 1))
Line3(Point3(0, 0, 0), Vector3(1, 1, 1))
>>> Line3(Point3(0, 0, 0), Point3(1, 1, 1))
Line3(Point3(0, 0, 0), Vector3(1, 1, 1))
connect(other)[source]

Find the shortest line segment connecting this object to the other object.

intersect(other)[source]

Find the point where this line intersects the other plane or sphere:

>>> l = Line3(Point3(0, 0, 0), Vector3(1, 1, 1));
>>> p = Plane(Vector3(0, 0, 1), 2);
>>> l.intersect(p)
Point3(2.0, 2.0, 2.0)
petrify.space.LineSegment

alias of petrify.space.LineSegment3

class petrify.space.LineSegment3(*args)[source]
petrify.space.Matrix

alias of petrify.space.Matrix3

class petrify.space.Matrix3[source]

A matrix that can be used to perform common transformations on three-dimensional points and vectors:

>>> Matrix3.scale(*Vector3(1, 2, 1).xyz) * Point3(1, 1, 1)
Point3(1, 2, 1)
get_quaternion()[source]

Returns a quaternion representing the rotation part of the matrix.

classmethod identity()[source]

The identity transform:

>>> Matrix3.identity() * Point3(1, 1, 1)
Point3(1.0, 1.0, 1.0)
classmethod new(*values)[source]

Create a new matrix from the provided values array.

classmethod rotate_at(origin, axis, angle)[source]

A rotational transform:

>>> rotation = Matrix3.rotate_at(Point3(1, 1, 1), Vector3.basis.z, tau / 4)
>>> (rotation * Point3(2, 1, 1)).rounded()
Point3(1, 2, 1)
classmethod rotate_axis(axis, angle)[source]

A rotational transform:

>>> (Matrix3.rotate_axis(Vector3.basis.z, tau / 4) * Point3(1, 0, 0)).rounded()
Point3(0, 1, 0)
classmethod scale(x, y, z)[source]

A scale transform:

>>> Matrix3.scale(*Vector3(1, 2, 1).xyz) * Point3(1, 1, 1)
Point3(1, 2, 1)
classmethod translate(x, y, z)[source]

A translation transform:

>>> Matrix3.translate(*Vector3(1, 2, 1).xyz) * Point3(1, 1, 1)
Point3(2.0, 3.0, 2.0)
class petrify.space.PlanarPolygon(basis, polygon)[source]

A two-dimensional petrify.plane.Polygon2 or petrify.plane.ComplexPolygon2 embedded in three-dimensional space via a Basis:

>>> tri = plane.Polygon2([           plane.Point2(0, 0),              plane.Point2(0, 2),              plane.Point2(1, 1)           ])
>>> triangle = PlanarPolygon(Basis.xy, tri)
>>> triangle.project()
[Polygon3([Point3(0, 0, 0), Point3(0, 2, 0), Point3(1, 1, 0)])]
render()[source]

Visualize this polygon in a Jupyter notebook.

class petrify.space.Plane(*args)[source]

A three-dimensional plane.

Can be constructed with three coplanar points:

>>> Plane(Point3(0, 0, 0), Point3(1, 0, 0), Point3(0, 1, 0))
Plane(Vector3(0.0, 0.0, 1.0), 0.0)

Or an origin point and two basis vectors: >>> Plane(Point3(0, 0, 0), Vector3.basis.x, Vector3.basis.y) Plane(Vector3(0.0, 0.0, 1.0), 0.0)

Or a normal and solution scalar / point:

>>> Plane(Vector3.basis.z, 0)
Plane(Vector3(0.0, 0.0, 1.0), 0)
>>> Plane(Vector3.basis.z, Point3.origin)
Plane(Vector3(0.0, 0.0, 1.0), 0.0)

Plane also defines convenience methods for commonly used origin planes:

>>> Plane.xy
Plane(Vector3(0.0, 0.0, 1.0), 0.0)
>>> Plane.xz
Plane(Vector3(0.0, 1.0, 0.0), 0.0)
>>> Plane.yz
Plane(Vector3(1.0, 0.0, 0.0), 0.0)
connect(other)[source]

Find the shortest line segment connecting this object to the other object.

intersect(other)[source]

Find the point where this plane intersects the other line or plane:

>>> Plane(Vector3(0, 1, 0), 1).intersect(Plane(Vector3(1, 0, 0), 2))
Line3(Point3(2.0, 1.0, 0.0), Vector3(0.0, 0.0, 1.0))
>>> Plane(Vector3(0, 0, 1), 2).intersect(Line3(Point3(0, 0, 0), Vector3(1, 1, 1)))
Point3(2.0, 2.0, 2.0)
petrify.space.Point

alias of petrify.space.Point3

class petrify.space.Point3(x=0, y=0, z=0)[source]

A close cousin of petrify.space.Vector3, used to represent a point instead of a transform.

Defines a convenience .origin attribute for this commonly-used point:

>>> Point3.origin
Point3(0, 0, 0)
connect(other)[source]

Find the shortest line segment connecting this object to the other object.

intersect(other)[source]

Returns whether the point lies within the given other sphere:

vector()[source]

The vector formed from the origin to this point.

petrify.space.Polygon

alias of petrify.space.Polygon3

class petrify.space.Polygon3(points)[source]

A linear cycle of coplanar convex points:

>>> triangle = Polygon3([Point3(0, 0, 0), Point3(0, 2, 0), Point3(1, 1, 0)])
>>> triangle.plane
Plane(Vector3(0.0, 0.0, -1.0), 0.0)
has_edge(edge)[source]

Returns true if this polygon contains the given edge.

render()[source]

Visualize this polygon in a Jupyter notebook.

segments()[source]

Returns all line segments composing this polygon’s edges.

simplify(tolerance=0.0001)[source]

Remove any duplicate points, within a certain tolerance:

>>> Polygon3([Point3(1, 1, 0), Point3(2, 0, 0), Point3(0, 0, 0), Point3(1, 1, 0)]).simplify()
Polygon3([Point3(2, 0, 0), Point3(0, 0, 0), Point3(1, 1, 0)])

Returns None if the resulting simplification would create a point:

>>> Polygon3([Point3(1, 1, 0), Point3(2, 0, 0), Point3(0, 0, 0)]).simplify(100) is None
True
class petrify.space.Quaternion(w=1, x=0, y=0, z=0)[source]

Quaternions are composable representations of three-dimensional rotation operations.

Multiplication can be performed on Vector3 instances to get the transformed vector or point:

>>> r = Quaternion.rotate_axis(Vector3.basis.x, tau / 4);
>>> (r * Vector3(0, 1, 0)).rounded()
Vector3(0, 0, 1)
petrify.space.Ray

alias of petrify.space.Ray3

class petrify.space.Ray3(*args)[source]

A Line3 with a fixed origin that continues indefinitely in the given direction.

class petrify.space.Sphere(center, radius)[source]

A perfect sphere with the provided center and radius:

>>> Sphere(Point3(0, 0, 0), 1.0)
Sphere(Point3(0, 0, 0), 1.0)
connect(other)[source]

Find the shortest line segment connecting this object to the other object.

intersect(other)[source]

Checks whether the other point lies within this sphere.

petrify.space.Vector

alias of petrify.space.Vector3

class petrify.space.Vector3(x=0, y=0, z=0)[source]

A three-dimensional vector supporting all corresponding built-in math operators:

>>> Vector3(1, 2, 3) + Vector3(2, 2, 2)
Vector3(3, 4, 5)
>>> Vector3(1, 2, 3) - Vector3(2, 2, 2)
Vector3(-1, 0, 1)
>>> Vector3(1, 0, 1) * 5
Vector3(5, 0, 5)
>>> Vector3(1, 0, 1) / 5
Vector3(0.2, 0.0, 0.2)
>>> Vector3(1, 1, 1) == Vector3(1, 1, 1)
True

In addition to many other specialized vector operations.

Defines convenience .basis members for commonly used basis vectors:

>>> Vector3.basis.x; Vector3.bx
Vector3(1, 0, 0)
Vector3(1, 0, 0)
>>> Vector3.basis.y; Vector3.by
Vector3(0, 1, 0)
Vector3(0, 1, 0)
>>> Vector3.basis.z; Vector3.bz
Vector3(0, 0, 1)
Vector3(0, 0, 1)
angle(other)[source]

Return the angle to the vector other.

cross(other)[source]

The cross product of this vector and the other.

dot(other)[source]

The dot product of this vector and the other.

normalized()[source]

Returns a vector with the same direction but unit (1) length.

point()[source]

Convert this vector into a point.

project(other)[source]

Return one vector projected on the vector other.

reflect(normal)[source]

Reflect this vector across a plane with the given normal

Note

Assumes the given normal has unit (1) length.

rotate(axis, theta)[source]

Return a new vector rotated around axis by angle theta. Right hand rule applies.

rounded(place=None)[source]

Rounds all elements to place decimals.

snap(grid)[source]

Snaps this vector to a grid:

>>> Vector3(1.15, 1.15, 0.9).snap(0.25)
Vector3(1.25, 1.25, 1.0)