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 MeshMesh#
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.
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.
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.
(0.0, 0.0, 0.0)World position of the local origin.
1.0Per-axis scale, applied before rotation.
NoneAs for every geometry: quaternion (w, x, y, z) or Euler (roll, pitch, yaw) in radians.
TrueShift the vertices so that their bounding-box centre is the local origin before scaling. The stored vertices are not changed.
'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.
NoneHalf-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.
NoneWhere the mesh came from. Stored as an absolute path string; Core does not read it.
NoneTrue 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.
NoneDevice 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,
) -> MeshLoads 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#
| Property | Type | Meaning |
|---|---|---|
vertices | Tensor (N, 3) | The authored vertex tensor, unchanged. |
faces | Tensor (M, 3) | The authored face tensor, unchanged. |
world_vertices | Tensor (N, 3) | (local * scale) @ R.T + position, with recentering applied. Keeps autograd. |
bounds_world | Tensor (2, 3) | Row 0 is the minimum corner, row 1 the maximum, of world_vertices. |
vertex_count, face_count | int | N and M. |
scale | Tensor (3,) | The scale. |
recenter, fill_mode, surface_thickness, source_path, topology_diagnostics | As passed (resolved). topology_diagnostics tells whether diagnostics ran. |
Diagnostics#
Set when diagnostics ran, otherwise None.
| Attribute | Meaning |
|---|---|
boundary_edge_count | Edges used by exactly one triangle. |
non_manifold_edge_count | Edges used by more than two triangles. |
degenerate_face_count | Triangles with a repeated vertex or near-zero area. |
inconsistent_edge_orientation_count | Shared edges traversed in the same direction by both triangles. |
signed_volume | Enclosed volume in m³; negative when faces are wound inward. |
enclosed_volume | abs(signed_volume). |
is_watertight | True when all four counts are 0 and the volume is not negligible. |
Mesh.signed_distance#
signed_distance(x: Tensor, y: Tensor, z: Tensor) -> TensorSigned 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#
| Method | Description |
|---|---|
has_trainable_geometry() -> bool | True when position, rotation, scale, vertices or a tensor surface_thickness requires a gradient. |
geometry_state_key() -> tuple | A 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.