Mesh

Reference for Mesh - constructor arguments, from_obj, world-space properties, topology diagnostics, signed distance and cache control.

Mesh lives in witwin.core.geometry.mesh and is exported from witwin.core. It subclasses GeometryBase, so position, rotation, device and to_mask work as described in Geometry primitives. For a walkthrough, see Load a mesh.

from witwin.core import Mesh

Mesh#

class Mesh(
    vertices,
    faces,
    *,
    position=(0.0, 0.0, 0.0),
    scale=1.0,
    rotation=None,
    recenter: bool = True,
    fill_mode: str = "auto",
    surface_thickness: float | None = None,
    source_path: str | PathLike[str] | None = None,
    topology_diagnostics: bool | None = None,
    device=None,
)

An explicit triangle mesh.

verticesarray-like | Tensor (N, 3)required

Local vertex positions. A tensor is stored as is (it must be floating point); anything else becomes a float32 tensor. N must be greater than 0.

facesarray-like | Tensor (M, 3)required

Triangle vertex indices, int32 or int64. A tensor is stored as is; a list becomes int64. Float indices raise TypeError. For non-tensor input, indices outside [0, N) raise ValueError.

position3 floats | Tensor (3,)default (0.0, 0.0, 0.0)

World position of the local origin.

scalefloat | 3 floats | Tensor (3,)default 1.0

Per-axis scale, applied before rotation.

rotationNone | 3 or 4 values | Tensordefault None

As for every geometry: quaternion (w, x, y, z) or Euler (roll, pitch, yaw) in radians.

recenterbooldefault True

Shift the vertices so that their bounding-box centre is the local origin before scaling. The stored vertices are not changed.

fill_modestrdefault 'auto'

"solid" (closed volume, winding-number sign), "surface" (thin shell of half-width surface_thickness) or "auto" (solid if watertight, else surface). Case-insensitive. Anything else raises ValueError.

surface_thicknessfloat | Tensor () | Nonedefault None

Half-width of the shell for surface queries, in metres. Must be positive. None uses a quarter of the smallest grid spacing of the query, or 1 mm.

source_pathstr | PathLike | Nonedefault None

Where the mesh came from. Stored as an absolute path string; Core does not read it.

topology_diagnosticsbool | Nonedefault None

True forces diagnostics (CPU tensors only). None runs them for non-tensor input, and for CPU tensors when fill_mode="auto". False skips them for tensor input; non-tensor input is always checked.

devicestr | torch.device | Nonedefault None

Device for values created from Python input. Tensor arguments on another device raise ValueError.

Raises ValueError if diagnostics are requested for CUDA tensors (Tensor topology diagnostics require CPU-authored vertices and faces.).

Mesh.from_obj#

@classmethod
from_obj(
    path: str | PathLike[str],
    *,
    position=(0.0, 0.0, 0.0),
    scale=1.0,
    rotation=None,
    recenter: bool = True,
    fill_mode: str = "auto",
    surface_thickness: float | None = None,
    device=None,
) -> Mesh

Loads a Wavefront OBJ file. Reads v and f records (1-based and negative indices, v/vt/vn tokens), fan-triangulates polygons and drops degenerate fan triangles. Everything else is ignored. Always runs topology diagnostics and sets source_path.

Raises FileNotFoundError for a missing file, and ValueError for malformed records, out-of-range indices, or a file without vertices or triangles.

Properties#

PropertyTypeMeaning
verticesTensor (N, 3)The authored vertex tensor, unchanged.
facesTensor (M, 3)The authored face tensor, unchanged.
world_verticesTensor (N, 3)(local * scale) @ R.T + position, with recentering applied. Keeps autograd.
bounds_worldTensor (2, 3)Row 0 is the minimum corner, row 1 the maximum, of world_vertices.
vertex_count, face_countintN and M.
scaleTensor (3,)The scale.
recenter, fill_mode, surface_thickness, source_path, topology_diagnosticsAs passed (resolved). topology_diagnostics tells whether diagnostics ran.

Diagnostics#

Set when diagnostics ran, otherwise None.

AttributeMeaning
boundary_edge_countEdges used by exactly one triangle.
non_manifold_edge_countEdges used by more than two triangles.
degenerate_face_countTriangles with a repeated vertex or near-zero area.
inconsistent_edge_orientation_countShared edges traversed in the same direction by both triangles.
signed_volumeEnclosed volume in m³; negative when faces are wound inward.
enclosed_volumeabs(signed_volume).
is_watertightTrue when all four counts are 0 and the volume is not negligible.

Mesh.signed_distance#

signed_distance(x: Tensor, y: Tensor, z: Tensor) -> Tensor

Signed distance in metres. If x, y and z are all 1-D, they are treated as grid axes and the result has shape (len(x), len(y), len(z)). Otherwise they are used point by point and the result has their shape.

Raises RuntimeError when fill_mode="auto" and diagnostics did not run (Mesh topology is unknown; ...), and when the vertex or face tensor changed in place after diagnostics ran (Mesh topology diagnostics are stale ...).

Mesh.to_mesh#

to_mesh(segments=16, *, device=None) -> tuple[Tensor, Tensor]

Returns (world_vertices, faces). segments is ignored. device moves the result to another device; by default it stays on the device of vertices. The authored faces tensor is returned itself when no move is needed.

Other methods#

MethodDescription
has_trainable_geometry() -> boolTrue when position, rotation, scale, vertices or a tensor surface_thickness requires a gradient.
geometry_state_key() -> tupleA key that changes when any defining tensor or setting changes. Solvers use it for caching.
clear_sdf_cache()Empties the cached distance-query data.
clear_voxel_cache()Empties the voxel cache that solvers may fill.

Example#

import torch
 
from witwin.core import Mesh
 
vertices = torch.tensor(
    [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]],
    dtype=torch.float32,
)
faces = torch.tensor([
    [0, 2, 1], [0, 3, 2], [4, 5, 6], [4, 6, 7], [0, 4, 7], [0, 7, 3],
    [1, 2, 6], [1, 6, 5], [0, 1, 5], [0, 5, 4], [3, 7, 6], [3, 6, 2],
])
cube = Mesh(vertices, faces, scale=2.0)          # CPU tensors + fill_mode="auto": diagnostics run
print(cube.is_watertight, cube.signed_volume, cube.bounds_world.tolist())
print(cube.signed_distance(torch.tensor([0.0, 1.5]), torch.tensor([0.0]), torch.tensor([0.0])).flatten())
True 1.0 [[-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]]
tensor([-1.0000,  0.5000])

The diagnostics report the authored unit cube (volume 1 m³). The scaled cube spans 2 m, so the origin is 1 m inside and x = 1.5 m is 0.5 m outside.