Geometry
Reference for the shapes, triangle meshes, extruded polygons and the Structure type that place materials in a Maxwell scene.
This page lists the geometry types you use to place materials in a Scene. You need it whenever you add a Structure, a MaterialRegion, or a MeshOverrideStructure.
Most classes here come from the shared base package witwin.core (PyPI witwin 0.4.x, which witwin-maxwell 0.5.1 requires) and are re-exported by witwin.maxwell: Box, Sphere, Cylinder, Ellipsoid, Cone, Pyramid, Prism, Torus, HollowBox, Mesh and Structure. mw.Box and witwin.core.Box are the same class. PolySlab and ComplexPolySlab are Maxwell's own and live in witwin.maxwell.geometry.
import witwin.maxwell as mw
scene.add_structure(
mw.Structure(
geometry=mw.Box(position=(0.0, 0.0, 0.0), size=(0.24, 0.24, 0.24)),
material=mw.Material(eps_r=4.0),
name="dielectric_box",
)
)Common conventions#
These rules hold for every geometry on this page.
- Units. All lengths are in metres.
- Storage. Lengths, positions and rotations are stored as
float32torch.Tensorvalues. You can pass plain numbers or tensors. A tensor withrequires_grad=Truestays in the autograd graph, so you can optimize shape parameters. See Differentiability. position. A length-3 value. For most shapes it is the centre.ConeandPyramidput their apex there.rotation.None(no rotation), three Euler angles(roll, pitch, yaw)in radians, or a quaternion(w, x, y, z). A tensor of shape(3,)or(4,)is kept as is.axis."x","y"or"z", for shapes that have one. Other values raiseValueError.device. Keyword-only. Places the parameter tensors on that device. Tensor arguments must agree with it, or the constructor raisesValueError.- Rasterization. Maxwell's material compiler samples each geometry on the Yee grid through
to_mask, which is built onsigned_distance.
Warning
with_material(material, **kwargs) is defined on the witwin.core geometry base class, but in witwin 0.4.0 it raises ImportError. Build the structure with mw.Structure(geometry=..., material=...) instead.
Shared geometry methods#
Every geometry class below has these members.
signed_distance#
geometry.signed_distance(xx, yy, zz) -> torch.TensorSigned distance in metres from each query point to the surface: negative inside, positive outside. xx, yy, zz are coordinate tensors of the same shape, for example from torch.meshgrid(..., indexing="ij").
to_mask#
geometry.to_mask(xx, yy, zz, offset=0.0, beta=None) -> torch.TensorA smooth occupancy in the range 0 to 1: 0.5 * (1 - tanh((d - offset) / beta)) where d is the signed distance. offset grows (negative) or shrinks (positive) the shape, in metres. beta is the transition width in metres; None uses 5% of the smallest coordinate spacing found in the inputs.
to_mesh#
geometry.to_mesh(segments=16) -> tuple[torch.Tensor, torch.Tensor]A triangle mesh (vertices, faces) in world coordinates: vertices of shape (N, 3) and int64 faces of shape (M, 3). segments sets the tessellation of curved shapes and is ignored by flat-faced ones.
kind and device#
kind is a class-level string such as "box" or "poly_slab". device is a property that returns the device of position.
Box#
class Box(position=(0, 0, 0), size=(1, 1, 1), rotation=None, *, device=None)A rectangular box centred at position.
(0, 0, 0)Centre in metres.
(1, 1, 1)Edge lengths (sx, sy, sz) in metres. A single number gives a cube.
NoneEuler angles in radians or a (w, x, y, z) quaternion.
slab = mw.Box(position=(0.0, 0.0, 0.0), size=(0.52, 1.28, 0.12))Sphere#
class Sphere(position=(0, 0, 0), radius=1.0, rotation=None, *, device=None)A sphere centred at position. rotation is stored but has no effect on the shape.
(0, 0, 0)Centre in metres.
1.0Radius in metres.
Cylinder#
class Cylinder(position=(0, 0, 0), radius=1.0, height=1.0, axis="z", rotation=None, *, device=None)A circular cylinder centred at position, with its axis along axis before rotation.
1.0Radius in metres.
1.0Full length along the axis in metres. The cylinder spans height / 2 on each side of position.
'z'Axis direction: "x", "y" or "z".
rod = mw.Cylinder(position=(0.0, 0.0, 0.0), radius=0.05, height=0.4, axis="x")Ellipsoid#
class Ellipsoid(position=(0, 0, 0), radii=(1, 1, 1), rotation=None, *, device=None)An ellipsoid centred at position.
(1, 1, 1)Semi-axes (rx, ry, rz) in metres, before rotation.
Cone#
class Cone(position=(0, 0, 0), radius=1.0, height=1.0, axis="z", rotation=None, *, device=None)A solid circular cone. The apex is at position, and the cone opens along the positive axis direction to a flat base at distance height.
1.0Base radius in metres.
1.0Apex-to-base distance in metres.
'z'Axis direction: "x", "y" or "z".
Pyramid#
class Pyramid(position=(0, 0, 0), base_size=1.0, height=1.0, axis="z", rotation=None, *, device=None)A pyramid with a square base. The apex is at position, and the base lies at distance height along the positive axis direction.
1.0Side length of the square base in metres.
1.0Apex-to-base distance in metres.
'z'Axis direction: "x", "y" or "z".
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 at position.
1.0Circumradius of the polygon (centre to vertex) in metres.
1.0Full length along the axis in metres.
6Number of polygon sides. Must be 3 or more, otherwise ValueError.
'z'Axis direction: "x", "y" or "z".
Torus#
class Torus(position=(0, 0, 0), major_radius=1.0, minor_radius=0.25, axis="z", rotation=None, *, device=None)A torus centred at position, with its symmetry axis along axis.
1.0Distance from the centre to the centre of the tube, in metres.
0.25Tube radius in metres.
'z'Symmetry axis: "x", "y" or "z".
ring = mw.Torus(position=(0.0, 0.0, 0.0), major_radius=1.0, minor_radius=0.3)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 at position. The shape is the outer box minus the inner box.
(1, 1, 1)Outer edge lengths in metres.
(0.8, 0.8, 0.8)Cavity edge lengths in metres.
HollowBox.size is a property that returns outer_size as a tuple of floats.
shell = mw.HollowBox(position=(0.0, 0.0, 0.0), outer_size=(0.6, 0.6, 0.6), inner_size=(0.4, 0.4, 0.4))Mesh#
class Mesh(
vertices, faces, *,
position=(0.0, 0.0, 0.0), scale=1.0, rotation=None, recenter=True,
fill_mode="auto", surface_thickness=None, source_path=None,
topology_diagnostics=None, device=None,
)An explicit triangle mesh. World vertices are (local * scale) @ R.T + position, where local is the authored vertices (recentred when recenter=True) and R is the rotation matrix.
Floating-point vertex positions of shape (N, 3) with N greater than 0, in metres before scale.
Triangle vertex indices of shape (M, 3) with M greater than 0, as int32 or int64. Indices must be in range.
(0.0, 0.0, 0.0)Translation applied last, in metres.
1.0Scale factor, uniform or per axis.
NoneEuler angles in radians or a (w, x, y, z) quaternion.
TrueWhen True, the authored vertices are shifted so their bounding-box centre is at the origin before scale, rotation and translation.
'auto'"solid" fills the enclosed volume. "surface" fills a thin shell around the triangles. "auto" uses solid for a watertight mesh and surface otherwise; it needs topology diagnostics.
NoneFor surface fill: points within this distance of the triangles count as inside, in metres. None uses a quarter of the smallest grid spacing. Must be positive when set.
NoneOrigin file, stored as an absolute path.
NoneWhether to compute watertightness statistics. They always run for non-tensor input. For tensor input they run when this is True, or when it is None, fill_mode="auto" and both tensors are on the CPU.
Raises: ValueError for wrong shapes, out-of-range indices, an unknown fill_mode or a non-positive surface_thickness; TypeError for wrong dtypes. At query time, signed_distance raises RuntimeError when fill_mode="auto" and no diagnostics were computed, or when the vertex or face tensors were changed in place after diagnostics ran.
import torch
vertices = torch.tensor([[0.0, 0.0, 0.0], [0.1, 0.0, 0.0], [0.0, 0.1, 0.0], [0.0, 0.0, 0.1]])
faces = torch.tensor([[0, 2, 1], [0, 1, 3], [0, 3, 2], [1, 2, 3]])
tetra = mw.Mesh(vertices, faces, fill_mode="solid")Mesh.from_obj#
Mesh.from_obj(
path, *, position=(0.0, 0.0, 0.0), scale=1.0, rotation=None, recenter=True,
fill_mode="auto", surface_thickness=None, device=None,
) -> MeshLoads a Wavefront OBJ file. Polygon faces are triangulated, source_path is set to path, and topology diagnostics always run.
Raises: FileNotFoundError for a missing file; ValueError for malformed records or a file without vertices or triangles.
mesh = mw.Mesh.from_obj("cube.obj", position=(1.0, 2.0, 3.0), scale=2.0)
print(mesh.vertex_count, mesh.face_count, mesh.is_watertight)Mesh properties#
| Property | Returns | Notes |
|---|---|---|
vertices, faces | torch.Tensor | The authored tensors, not transformed. |
world_vertices | torch.Tensor | Vertices after recentring, scale, rotation and translation. |
bounds_world | torch.Tensor | Shape (2, 3): world minimum and maximum corners. |
vertex_count, face_count | int | Sizes of the authored arrays. |
is_watertight | bool or None | None when diagnostics did not run. |
boundary_edge_count, non_manifold_edge_count | int or None | Topology statistics. |
degenerate_face_count, inconsistent_edge_orientation_count | int or None | Topology statistics. |
signed_volume, enclosed_volume | float or None | Volume of the authored mesh; enclosed_volume is the absolute value. |
Mesh methods#
| Method | Returns | Notes |
|---|---|---|
has_trainable_geometry() | bool | True when position, rotation, scale, vertices or surface thickness requires grad. |
geometry_state_key() | tuple | Cache key that changes when the mesh state changes. |
clear_voxel_cache(), clear_sdf_cache() | None | Drop cached rasterization data. |
to_mesh(segments=16, *, device=None) | tuple | World vertices and faces, optionally on another device. |
PolySlab#
class PolySlab(
vertices, bounds, axis="z", sidewall_angle=0.0, reference_plane="middle",
position=(0, 0, 0), rotation=None, *, device=None,
)A 2D polygon extruded along an axis, with an optional linear sidewall taper. Defined in witwin.maxwell.geometry. The inside test uses the even-odd rule, so non-convex polygons work and vertex winding does not matter.
Polygon vertices of shape (N, 2) with N of 3 or more, in metres. The two columns are the in-plane coordinates: (x, y) for axis "z", (x, z) for axis "y", (y, z) for axis "x".
(min, max) extent along the extrusion axis, in metres.
'z'Extrusion axis: "x", "y", "z", or 0, 1, 2.
0.0Taper angle in radians. A positive angle shrinks the cross-section as the axis coordinate increases.
'middle'Where along the axis the drawn polygon is exact: "bottom", "middle" or "top".
(0, 0, 0)Translation of the local frame in metres. vertices and bounds are relative to it.
NoneEuler angles in radians or a (w, x, y, z) quaternion.
Raises: ValueError for vertices not shaped (N, 2) with N of 3 or more, bounds without two values, an integer axis outside 0 to 2, or an unknown reference_plane. to_mesh raises ValueError when the polygon is not simple enough to triangulate.
UM = 1e-6
waveguide = mw.PolySlab(
vertices=[(0.0, 0.0), (2.0 * UM, 0.0), (2.0 * UM, 0.5 * UM), (0.0, 0.5 * UM)],
bounds=(0.0, 0.22 * UM),
sidewall_angle=0.1,
)ComplexPolySlab#
class ComplexPolySlab(
loops, bounds, axis="z", sidewall_angle=0.0, reference_plane="middle",
position=(0, 0, 0), rotation=None, *, device=None,
)A PolySlab whose cross-section is built from several vertex loops. Self-intersecting loops (such as a bowtie) and multiple loops are allowed. The inside test is the even-odd rule over all loops together, so an inner loop carves a hole. Defined in witwin.maxwell.geometry.
One or more polygon loops, each with 3 or more vertices, in metres.
The other parameters match PolySlab. loop_sizes holds the vertex count of each loop.
Raises: ValueError for an empty loops list or a malformed loop. to_mesh raises NotImplementedError, so a ComplexPolySlab cannot be exported as a mesh.
UM = 1e-6
outer = [(0.0, 0.0), (2.0 * UM, 0.0), (2.0 * UM, 1.0 * UM), (0.0, 1.0 * UM)]
hole = [(0.5 * UM, 0.25 * UM), (1.5 * UM, 0.25 * UM), (1.5 * UM, 0.75 * UM), (0.5 * UM, 0.75 * UM)]
holed = mw.ComplexPolySlab(loops=[outer, hole], bounds=(0.0, 0.22 * UM))Structure#
class Structure(
geometry, material, name=None, priority=0, enabled=True, tags=(), metadata=None, *,
structure_id=None, material_id=None, assignment_id=None, surface_id=None,
primitive_ids=(), phase_screen=None, uv=None, face_uv=None,
)Binds one geometry to one material. Immutable. Re-exported from witwin.core; add it to a scene with Scene.add_structure.
Any object with a kind attribute and a to_mesh() method, such as the classes on this page. Otherwise TypeError.
A material that implements the witwin.core MaterialSpec contract, such as mw.Material. Otherwise TypeError. See Materials.
NoneLabel, converted to str.
0Overlap order. Where structures overlap, Maxwell uses the one with the higher priority; at equal priority, the one added later.
TrueWhen False, Maxwell's material compiler skips the structure.
()Free-form tags, stored as a tuple of strings.
NoneFree-form metadata, stored as a read-only mapping.
The keyword-only arguments belong to the shared witwin.core scene contract:
| Argument | Meaning |
|---|---|
structure_id, material_id, assignment_id, surface_id | Stable integer identities. When omitted, new ones are allocated; material_id is taken from the material when it has one. |
primitive_ids | Explicit per-primitive identities. An empty sequence means none. |
phase_screen | A witwin.core PhaseScreen, or None. |
uv, face_uv | Texture coordinates (T, 2) and per-face UV indices (F, 3). Pass both or neither; they require mesh geometry with one face_uv row per face. |
Maxwell's solver does not read phase_screen, uv or face_uv.
Raises: TypeError for an unsupported geometry, material, phase screen or UV dtype; ValueError for mismatched UV shapes or devices.
Structure.primitive_id#
Structure.primitive_id(local_index) -> PrimitiveIdReturns the identity of primitive local_index. With primitive_ids set it indexes that tuple; otherwise it combines structure_id and the index. Raises ValueError for a negative index or one that does not fit in 32 bits, and IndexError when the index is outside primitive_ids.
structure = mw.Structure(
geometry=mw.Box(position=(0.0, 0.0, 0.0), size=(1.0, 1.0, 1.0)),
material=mw.Material(eps_r=3.0, sigma_e=0.25),
name="target",
priority=7,
)