Geometry primitives
Reference for GeometrySpec, GeometryBase, the Geometry union and the nine analytic primitives from Box to HollowBox, with their parameters and placement.
The geometry classes live in witwin.core.geometry and are exported from witwin.core. Mesh has its own page: Mesh. For a walkthrough, see Create geometry.
from witwin.core import Box, Cone, Cylinder, Ellipsoid, HollowBox, Prism, Pyramid, Sphere, TorusAll lengths are in metres. Any parameter can be a Python number or sequence, which Core converts to a float32 tensor, or a tensor, which Core stores as is.
GeometrySpec#
@runtime_checkable
class GeometrySpec(Protocol):
kind: str
def to_mesh(self, segments: int = 16) -> tuple: ...The minimum contract Structure accepts: a kind string and a to_mesh method that returns (vertices, faces). Use isinstance(obj, GeometrySpec) to check an object. See Write your own geometry or material.
Geometry#
Geometry = Box | Sphere | Cylinder | Cone | Ellipsoid | Pyramid | Prism | Torus | HollowBox | MeshA type alias for the built-in geometry classes, for type annotations.
GeometryBase#
class GeometryBase(position=(0, 0, 0), rotation=None, *, device=None)Base class of all built-in geometry. Subclass it to get position, rotation, device handling and to_mask.
(0, 0, 0)World position of the local origin.
NoneNone is the identity. Four values are a quaternion (w, x, y, z). Three values are Euler angles (roll, pitch, yaw) in radians, applied as Rz(yaw) @ Ry(pitch) @ Rx(roll). A Python sequence of three is stored as a quaternion; a (3,) tensor is stored as is. Any other length raises ValueError.
NoneDevice for values created from Python numbers. None uses the device of the first tensor argument, or the CPU. A tensor on another device raises ValueError.
| Member | Description |
|---|---|
kind | Class attribute naming the shape: "box", "sphere", "cylinder", "cone", "ellipsoid", "pyramid", "prism", "torus", "hollow_box" or "mesh". |
position | (3,) tensor. |
rotation | (4,) quaternion tensor, or the (3,) Euler tensor you passed. |
device | Property: the device of position. |
signed_distance(xx, yy, zz) | Signed distance in metres at world points: negative inside, positive outside. Element-wise over broadcastable tensors. GeometryBase itself raises NotImplementedError. |
to_mask(xx, yy, zz, offset=0.0, beta=None) | Soft occupancy 0.5 * (1 - tanh((d - offset) / beta)), clamped to [0, 1]. beta=None uses 5 % of the smallest spacing found along dimension 0 of xx, 1 of yy and 2 of zz, or 1 mm. |
to_mesh(segments=16) | World-space (vertices, faces): a float tensor (N, 3) and an int64 tensor (M, 3). |
Note
with_material(material, **kwargs) is defined on GeometryBase but raises ImportError in 0.4.0. Use Structure(geometry, material, ...).
Box#
class Box(position=(0, 0, 0), size=(1, 1, 1), rotation=None, *, device=None)An axis-aligned box in its local frame, centred on position.
(1, 1, 1)Full edge lengths along local x, y and z. A single number gives a cube.
to_mesh ignores segments and returns 8 vertices and 12 triangles.
Sphere#
class Sphere(position=(0, 0, 0), radius=1.0, rotation=None, *, device=None)A sphere centred on position. rotation affects only the mesh orientation.
1.0Radius.
to_mesh(segments) uses segments rings and 2 * segments sectors.
Ellipsoid#
class Ellipsoid(position=(0, 0, 0), radii=(1, 1, 1), rotation=None, *, device=None)An ellipsoid centred on position.
(1, 1, 1)Semi-axes along local x, y and z.
The signed distance is a first-order approximation: exact on the surface and for a sphere, approximate elsewhere.
Cylinder#
class Cylinder(position=(0, 0, 0), radius=1.0, height=1.0, axis="z", rotation=None, *, device=None)A solid circular cylinder centred on position.
1.0Radius.
1.0Full length along axis, from -height/2 to +height/2.
'z'"x", "y" or "z" (case-insensitive). Anything else raises ValueError.
to_mesh(segments) uses segments points around the circumference.
Cone#
class Cone(position=(0, 0, 0), radius=1.0, height=1.0, axis="z", rotation=None, *, device=None)A solid circular cone with its apex at position and its base, of radius radius, at +height along axis.
Pyramid#
class Pyramid(position=(0, 0, 0), base_size=1.0, height=1.0, axis="z", rotation=None, *, device=None)A square pyramid with its apex at position and a square base of edge base_size at +height along axis. to_mesh ignores segments.
Prism#
class Prism(position=(0, 0, 0), radius=1.0, height=1.0, num_sides=6, axis="z", rotation=None, *, device=None)A right prism with a regular polygon cross-section, centred on position.
1.0Circumradius of the polygon. The first vertex lies on the local x axis (for axis="z").
1.0Full length along axis.
6Number of sides. Must be at least 3, otherwise ValueError.
to_mesh ignores segments.
Torus#
class Torus(position=(0, 0, 0), major_radius=1.0, minor_radius=0.25, axis="z", rotation=None, *, device=None)A ring torus centred on position, with axis normal to the ring plane.
1.0Distance from the centre to the centre of the tube.
0.25Radius of the tube.
to_mesh(segments) uses 2 * segments steps around the ring and segments around the tube.
HollowBox#
class HollowBox(position=(0, 0, 0), outer_size=(1, 1, 1), inner_size=(0.8, 0.8, 0.8), rotation=None, *, device=None)A box with a box-shaped cavity, both centred on position. The shell is the outer box minus the inner box.
(1, 1, 1)Full outer edge lengths.
(0.8, 0.8, 0.8)Full cavity edge lengths. Core does not check that the cavity fits inside.
size (property) returns outer_size as a tuple of floats. to_mesh returns the outer box and the inner box with inverted faces.
Mesh winding for axis x and y#
Warning
In 0.4.0, to_mesh of Cylinder, Cone, Pyramid, Prism and Torus with axis="x" or axis="y" returns triangles wound inward (negative signed volume). The vertices are correct. Reverse the faces with faces.flip(1) if you need outward normals. Signed distances and masks are not affected.
Example#
import torch
from witwin.core import Cone
cone = Cone(position=(0.0, 0.0, 0.0), radius=0.5, height=1.0)
z = torch.tensor([-0.1, 0.5, 1.1])
print(cone.signed_distance(torch.zeros(3), torch.zeros(3), z))
vertices, faces = cone.to_mesh(segments=8)
print(vertices[:, 2].min().item(), vertices[:, 2].max().item())tensor([ 0.1000, -0.2236, 0.1000])
0.0 1.0The apex is at the origin and the base at z = 1. The point at z = 0.5 on the axis is 0.22 m inside the slanted side.