Coverage for trimesh/permutate.py: 96%
46 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +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
15def transform(mesh, translation_scale: Number = 1000.0):
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
25 Returns
26 ----------
27 permutated : trimesh.Trimesh
28 Mesh with same faces as input mesh but reordered
29 and rigidly transformed in space.
30 """
31 # rotate and translate randomly
32 matrix = transformations.random_rotation_matrix(translate=translation_scale)
34 # randomly re-order triangles
35 triangles = np.random.permutation(mesh.triangles).reshape((-1, 3))
36 # apply rigid transform
37 triangles = transformations.transform_points(triangles, matrix)
39 # extract the class from the input object
40 mesh_type = util.type_named(mesh, "Trimesh")
41 # generate a new mesh from the permutated data
42 permutated = mesh_type(**triangles_module.to_kwargs(triangles.reshape((-1, 3, 3))))
44 return permutated
47def noise(mesh, magnitude=None):
48 """
49 Add gaussian noise to every vertex of a mesh, making
50 no effort to maintain topology or sanity.
52 Parameters
53 ----------
54 mesh : trimesh.Trimesh
55 Input geometry, will not be altered
56 magnitude : float
57 What is the maximum distance per axis we can displace a vertex.
58 If None, value defaults to (mesh.scale / 100.0)
60 Returns
61 ----------
62 permutated : trimesh.Trimesh
63 Input mesh with noise applied
64 """
65 if magnitude is None:
66 magnitude = mesh.scale / 100.0
68 random = (np.random.random(mesh.vertices.shape) - 0.5) * magnitude
69 vertices_noise = mesh.vertices.copy() + random
71 # make sure we've re- ordered faces randomly
72 triangles = np.random.permutation(vertices_noise[mesh.faces])
74 mesh_type = util.type_named(mesh, "Trimesh")
75 permutated = mesh_type(**triangles_module.to_kwargs(triangles))
77 return permutated
80def tessellation(mesh):
81 """
82 Subdivide each face of a mesh into three faces with the new vertex
83 randomly placed inside the old face.
85 This produces a mesh with exactly the same surface area and volume
86 but with different tessellation.
88 Parameters
89 ------------
90 mesh : trimesh.Trimesh
91 Input geometry
93 Returns
94 ----------
95 permutated : trimesh.Trimesh
96 Mesh with remeshed facets
97 """
98 # create random barycentric coordinates for each face
99 # pad all coordinates by a small amount to bias new vertex towards center
100 barycentric = np.random.random(mesh.faces.shape) + 0.05
101 barycentric /= barycentric.sum(axis=1).reshape((-1, 1))
103 # create one new vertex somewhere in a face
104 vertex_face = (barycentric.reshape((-1, 3, 1)) * mesh.triangles).sum(axis=1)
105 vertex_face_id = np.arange(len(vertex_face)) + len(mesh.vertices)
107 # new vertices are the old vertices stacked on the vertices in the faces
108 vertices = np.vstack((mesh.vertices, vertex_face))
109 # there are three new faces per old face, and we maintain correct winding
110 faces = np.vstack(
111 (
112 np.column_stack((mesh.faces[:, [0, 1]], vertex_face_id)),
113 np.column_stack((mesh.faces[:, [1, 2]], vertex_face_id)),
114 np.column_stack((mesh.faces[:, [2, 0]], vertex_face_id)),
115 )
116 )
117 # make sure the order of the faces is permutated
118 faces = np.random.permutation(faces)
120 mesh_type = util.type_named(mesh, "Trimesh")
121 permutated = mesh_type(vertices=vertices, faces=faces)
122 return permutated
125class Permutator:
126 def __init__(self, mesh):
127 """
128 A convenience object to get permutated versions of a mesh.
129 """
130 self._mesh = mesh
132 def transform(self, translation_scale=1000):
133 return transform(self._mesh, translation_scale=translation_scale)
135 def noise(self, magnitude=None):
136 return noise(self._mesh, magnitude)
138 def tessellation(self):
139 return tessellation(self._mesh)
142try:
143 # copy the function docstrings to the helper object
144 Permutator.noise.__doc__ = noise.__doc__
145 Permutator.transform.__doc__ = transform.__doc__
146 Permutator.tessellation.__doc__ = tessellation.__doc__
147except AttributeError:
148 # no docstrings in Python2
149 pass