Coverage for trimesh/permutate.py: 96%
49 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-07-31 23:55 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-07-31 23:55 +0000
1"""
2permutate.py
3-------------
5Randomly deform meshes in different ways.
6"""
8import numpy as np
10from . import transformations, util
11from . import triangles as triangles_module
12from .typed import Number, Seed
15def transform(mesh, translation_scale: Number = 1000.0, seed: Seed = None):
16 """
17 Return a permutated variant of a mesh by randomly reordering faces
18 and rotatating + translating a mesh by a random matrix.
20 Parameters
21 ----------
22 mesh : trimesh.Trimesh
23 Mesh, will not be altered by this function
24 seed : None or int
25 Seed for deterministic results, otherwise OS entropy.
27 Returns
28 ----------
29 permutated : trimesh.Trimesh
30 Mesh with same faces as input mesh but reordered
31 and rigidly transformed in space.
32 """
33 # thread one generator through so the rotation and the reordering
34 # below aren't each handed an identically re-seeded stream
35 random = util.random_generator(seed)
36 matrix = transformations.random_rotation_matrix(
37 translate=translation_scale, seed=random
38 )
40 # randomly re-order triangles
41 triangles = random.permutation(mesh.triangles).reshape((-1, 3))
42 # apply rigid transform
43 triangles = transformations.transform_points(triangles, matrix)
45 # extract the class from the input object
46 mesh_type = util.type_named(mesh, "Trimesh")
47 # generate a new mesh from the permutated data
48 permutated = mesh_type(**triangles_module.to_kwargs(triangles.reshape((-1, 3, 3))))
50 return permutated
53def noise(mesh, magnitude=None, seed: Seed = None):
54 """
55 Add gaussian noise to every vertex of a mesh, making
56 no effort to maintain topology or sanity.
58 Parameters
59 ----------
60 mesh : trimesh.Trimesh
61 Input geometry, will not be altered
62 magnitude : float
63 What is the maximum distance per axis we can displace a vertex.
64 If None, value defaults to (mesh.scale / 100.0)
65 seed : None or int
66 Seed for deterministic results, otherwise OS entropy.
68 Returns
69 ----------
70 permutated : trimesh.Trimesh
71 Input mesh with noise applied
72 """
73 if magnitude is None:
74 magnitude = mesh.scale / 100.0
76 random = util.random_generator(seed)
77 offset = (random.random(mesh.vertices.shape) - 0.5) * magnitude
78 vertices_noise = mesh.vertices.copy() + offset
80 # make sure we've re- ordered faces randomly
81 triangles = random.permutation(vertices_noise[mesh.faces])
83 mesh_type = util.type_named(mesh, "Trimesh")
84 permutated = mesh_type(**triangles_module.to_kwargs(triangles))
86 return permutated
89def tessellation(mesh, seed: Seed = None):
90 """
91 Subdivide each face of a mesh into three faces with the new vertex
92 randomly placed inside the old face.
94 This produces a mesh with exactly the same surface area and volume
95 but with different tessellation.
97 Parameters
98 ------------
99 mesh : trimesh.Trimesh
100 Input geometry
101 seed : None or int
102 Seed for deterministic results, otherwise OS entropy.
104 Returns
105 ----------
106 permutated : trimesh.Trimesh
107 Mesh with remeshed facets
108 """
109 random = util.random_generator(seed)
111 # create random barycentric coordinates for each face
112 # pad all coordinates by a small amount to bias new vertex towards center
113 barycentric = random.random(mesh.faces.shape) + 0.05
114 barycentric /= barycentric.sum(axis=1).reshape((-1, 1))
116 # create one new vertex somewhere in a face
117 vertex_face = (barycentric.reshape((-1, 3, 1)) * mesh.triangles).sum(axis=1)
118 vertex_face_id = np.arange(len(vertex_face)) + len(mesh.vertices)
120 # new vertices are the old vertices stacked on the vertices in the faces
121 vertices = np.vstack((mesh.vertices, vertex_face))
122 # there are three new faces per old face, and we maintain correct winding
123 faces = np.vstack(
124 (
125 np.column_stack((mesh.faces[:, [0, 1]], vertex_face_id)),
126 np.column_stack((mesh.faces[:, [1, 2]], vertex_face_id)),
127 np.column_stack((mesh.faces[:, [2, 0]], vertex_face_id)),
128 )
129 )
130 # make sure the order of the faces is permutated
131 faces = random.permutation(faces)
133 mesh_type = util.type_named(mesh, "Trimesh")
134 permutated = mesh_type(vertices=vertices, faces=faces)
135 return permutated
138class Permutator:
139 def __init__(self, mesh):
140 """
141 A convenience object to get permutated versions of a mesh.
142 """
143 self._mesh = mesh
145 def transform(self, translation_scale=1000, seed: Seed = None):
146 return transform(self._mesh, translation_scale=translation_scale, seed=seed)
148 def noise(self, magnitude=None, seed: Seed = None):
149 return noise(self._mesh, magnitude, seed=seed)
151 def tessellation(self, seed: Seed = None):
152 return tessellation(self._mesh, seed=seed)
155try:
156 # copy the function docstrings to the helper object
157 Permutator.noise.__doc__ = noise.__doc__
158 Permutator.transform.__doc__ = transform.__doc__
159 Permutator.tessellation.__doc__ = tessellation.__doc__
160except AttributeError:
161 # no docstrings in Python2
162 pass