Modules | PY

Reindex

Extract, filter, and reorganize geometric data with referential integrity.

The Reindex module provides efficient tools for extracting, filtering, and reorganizing geometric data while maintaining referential integrity. It operates on arrays and forms, supporting both simple data extraction and complex multi-level reindexing with automatic index map generation.

Core Concepts

Reindexing in trueform revolves around two key concepts:

  1. Index Maps: Structures that map old indices to new indices, enabling efficient data reorganization
  2. Referential Integrity: Ensuring that relationships between different data structures (e.g., faces and points) remain consistent after reindexing

The module provides both immediate reindexing operations and optional index map returns for downstream processing.

Reindexing by Mask

Filter data using boolean masks:

import trueform as tf
import numpy as np

# Create mesh with mask
faces = np.array([[0, 1, 2], [1, 3, 2], [2, 3, 4]], dtype=np.int32)
points = np.array([[0, 0], [1, 0], [0.5, 1], [1.5, 1], [1, 2]], dtype=np.float32)
face_mask = np.array([True, False, True], dtype=bool)

# Reindex by mask - automatically filters unused points
filtered_faces, filtered_points = tf.reindex_by_mask((faces, points), face_mask)

# With Mesh object
mesh = tf.Mesh(faces, points)
filtered_faces, filtered_points = tf.reindex_by_mask(mesh, face_mask)

# Filter by geometric criteria
areas = np.array([compute_triangle_area(points[f]) for f in faces])
large_faces = areas > 0.1
filtered_mesh = tf.reindex_by_mask(mesh, large_faces)

With index maps:

# Get index maps for downstream processing
(filtered_faces, filtered_points), (f_faces, kept_faces), (f_points, kept_points) = tf.reindex_by_mask(
    (faces, points), face_mask, return_index_map=True
)

# Use maps to reindex associated data
filtered_face_normals = face_normals[kept_faces]
filtered_point_attributes = point_attributes[kept_points]

Reindexing by IDs

Extract specific elements by their IDs:

# Extract specific faces by ID
face_ids = np.array([0, 5, 10, 15], dtype=np.int32)
subset_faces, subset_points = tf.reindex_by_ids((faces, points), face_ids)

# With Mesh object
subset_mesh = tf.reindex_by_ids(mesh, face_ids)

# Extract specific edges
edge_ids = np.array([2, 4, 6], dtype=np.int32)
subset_edges = tf.reindex_by_ids(edge_mesh, edge_ids)

# Extract specific points
point_ids = np.array([10, 25, 30, 45], dtype=np.int32)
extracted_points = tf.reindex_by_ids(point_cloud, point_ids)

With index maps:

# Get index maps for consistency
(subset_faces, subset_points), (f_faces, kept_faces), (f_points, kept_points) = tf.reindex_by_ids(
    (faces, points), face_ids, return_index_map=True
)

# Reindex associated attributes
subset_face_attrs = face_attrs[kept_faces]
subset_point_attrs = point_attrs[kept_points]

Point-Based Filtering

Filter faces/edges based on point criteria rather than face/edge criteria.

Mask on Points

# Create point mask based on spatial criteria
point_mask = points[:, 2] > height_threshold  # Keep points above certain height

# Filter polygons - keep only those with ALL vertices in the mask
filtered_faces, filtered_points = tf.reindex_by_mask_on_points((faces, points), point_mask)

# With Mesh object
filtered_faces, filtered_points = tf.reindex_by_mask_on_points(mesh, point_mask)

# Filter segments similarly
filtered_edges, filtered_points = tf.reindex_by_mask_on_points(edge_mesh, point_mask)

With index maps:

(filtered_faces, filtered_points), (f_faces, kept_faces), (f_points, kept_points) = tf.reindex_by_mask_on_points(
    (faces, points), point_mask, return_index_map=True
)

# Reindex associated attributes
filtered_face_normals = face_normals[kept_faces]
filtered_point_attrs = point_attrs[kept_points]

IDs on Points

# Select specific point IDs (e.g., feature points)
selected_point_ids = np.array([10, 25, 30, 45], dtype=np.int32)

# Extract faces that use only the selected points
filtered_faces, filtered_points = tf.reindex_by_ids_on_points((faces, points), selected_point_ids)

# With Mesh object
filtered_faces, filtered_points = tf.reindex_by_ids_on_points(mesh, selected_point_ids)

With index maps:

(filtered_faces, filtered_points), (f_faces, kept_faces), (f_points, kept_points) = tf.reindex_by_ids_on_points(
    (faces, points), selected_point_ids, return_index_map=True
)

Concatenation

Merge multiple geometric structures into a single unified structure:

# Concatenate meshes
mesh1 = tf.Mesh(faces1, points1)
mesh2 = tf.Mesh(faces2, points2)
mesh3 = tf.Mesh(faces3, points3)

combined_faces, combined_points = tf.concatenated([mesh1, mesh2, mesh3])
combined_mesh = tf.Mesh(combined_faces, combined_points)

# Concatenate edge meshes
edges1 = tf.EdgeMesh(e1, p1)
edges2 = tf.EdgeMesh(e2, p2)
combined_edges, combined_points = tf.concatenated([edges1, edges2])

# Also works with tuples
data = [(faces1, points1), (faces2, points2)]
combined_faces, combined_points = tf.concatenated(data)

Index offsetting is handled automatically. If edges1 references points 0-99 and edges2 references points 0-49, the result will have edges1 as-is and edges2 offset to reference 100-149.

Transformations: When concatenating Mesh or EdgeMesh objects that have transformations set, the transformations are automatically applied to the points before concatenation. This enables assembly workflows where parts are positioned independently.
All input geometries must have the same point dimensions (all 2D or all 3D). Mixing fixed-size with dynamic (e.g., triangles + dynamic) produces dynamic output (OffsetBlockedArray).

Split into Components

Split a mesh with per-element labels into multiple separate geometries:

# Create labels (one per face)
labels = np.array([0, 0, 1, 1, 2, 2], dtype=np.int32)

# Split into separate components
components, comp_labels = tf.split_into_components(mesh, labels)

# components: list of (faces, points) tuples
# comp_labels: array of unique label values (sorted)

print(f"Split into {len(components)} components")
print(f"Component labels: {comp_labels}")  # e.g., [0, 1, 2]

# Process each component independently
for (comp_faces, comp_points), label in zip(components, comp_labels):
    print(f"Component {label}: {len(comp_faces)} faces, {len(comp_points)} points")
    # Create Mesh from component if needed
    comp_mesh = tf.Mesh(comp_faces, comp_points)

# Works with edge meshes too
edge_components, edge_labels = tf.split_into_components(edge_mesh, edge_labels)
for (comp_edges, comp_points), label in zip(edge_components, edge_labels):
    print(f"Edge component {label}: {len(comp_edges)} edges")
Relationship with Concatenation: These operations are inverses:
  • concatenated: Multiple meshes → Single mesh
  • split_into_components: Single mesh + labels → Multiple meshes
This allows flexible workflows: concatenate for batch processing, then split back into components for individual analysis.

Dynamic Mesh Support

All reindex functions work with dynamic meshes (n-gons via OffsetBlockedArray):

Input TypeReturns connectivity as
Fixed-size indices (N, V)np.ndarray (M, V)
Dynamic indices OffsetBlockedArrayOffsetBlockedArray
# Reindex dynamic mesh by IDs
quads = tf.as_offset_blocked(np.array([[0,1,2,3], [4,5,6,7]], dtype=np.int32))
ids = np.array([0], dtype=np.int32)
new_faces, new_points = tf.reindex_by_ids((quads, points), ids)
# new_faces is OffsetBlockedArray

# Split dynamic mesh into components
components, labels = tf.split_into_components((quads, points), face_labels)
# Each component's faces is OffsetBlockedArray

Index Maps

When return_index_map=True, reindexing operations return index maps (f, kept_ids) that track element transformations. These maps enable reindexing of associated attributes to match the reindexed geometry:

# Example with reindex_by_mask
(new_faces, new_points), (f_faces, kept_faces), (f_points, kept_points) = tf.reindex_by_mask(
    mesh, face_mask, return_index_map=True
)

# Reindex all associated data
new_face_normals = face_normals[kept_faces]
new_point_colors = point_colors[kept_points]
For detailed explanation of index map structure (f and kept_ids), see Understanding Index Maps in the Clean module documentation.
For implementation details, see the C++ Reindex documentation.