Coverage for trimesh/permutate.py: 96%

49 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-07-31 23:55 +0000

1""" 

2permutate.py 

3------------- 

4 

5Randomly deform meshes in different ways. 

6""" 

7 

8import numpy as np 

9 

10from . import transformations, util 

11from . import triangles as triangles_module 

12from .typed import Number, Seed 

13 

14 

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. 

19 

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. 

26 

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 ) 

39 

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) 

44 

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)))) 

49 

50 return permutated 

51 

52 

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. 

57 

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. 

67 

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 

75 

76 random = util.random_generator(seed) 

77 offset = (random.random(mesh.vertices.shape) - 0.5) * magnitude 

78 vertices_noise = mesh.vertices.copy() + offset 

79 

80 # make sure we've re- ordered faces randomly 

81 triangles = random.permutation(vertices_noise[mesh.faces]) 

82 

83 mesh_type = util.type_named(mesh, "Trimesh") 

84 permutated = mesh_type(**triangles_module.to_kwargs(triangles)) 

85 

86 return permutated 

87 

88 

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. 

93 

94 This produces a mesh with exactly the same surface area and volume 

95 but with different tessellation. 

96 

97 Parameters 

98 ------------ 

99 mesh : trimesh.Trimesh 

100 Input geometry 

101 seed : None or int 

102 Seed for deterministic results, otherwise OS entropy. 

103 

104 Returns 

105 ---------- 

106 permutated : trimesh.Trimesh 

107 Mesh with remeshed facets 

108 """ 

109 random = util.random_generator(seed) 

110 

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)) 

115 

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) 

119 

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) 

132 

133 mesh_type = util.type_named(mesh, "Trimesh") 

134 permutated = mesh_type(vertices=vertices, faces=faces) 

135 return permutated 

136 

137 

138class Permutator: 

139 def __init__(self, mesh): 

140 """ 

141 A convenience object to get permutated versions of a mesh. 

142 """ 

143 self._mesh = mesh 

144 

145 def transform(self, translation_scale=1000, seed: Seed = None): 

146 return transform(self._mesh, translation_scale=translation_scale, seed=seed) 

147 

148 def noise(self, magnitude=None, seed: Seed = None): 

149 return noise(self._mesh, magnitude, seed=seed) 

150 

151 def tessellation(self, seed: Seed = None): 

152 return tessellation(self._mesh, seed=seed) 

153 

154 

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