Coverage for trimesh/permutate.py: 96%

46 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-24 04:40 +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 

13 

14 

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. 

19 

20 Parameters 

21 ---------- 

22 mesh : trimesh.Trimesh 

23 Mesh, will not be altered by this function 

24 

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) 

33 

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) 

38 

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

43 

44 return permutated 

45 

46 

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. 

51 

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) 

59 

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 

67 

68 random = (np.random.random(mesh.vertices.shape) - 0.5) * magnitude 

69 vertices_noise = mesh.vertices.copy() + random 

70 

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

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

73 

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

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

76 

77 return permutated 

78 

79 

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. 

84 

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

86 but with different tessellation. 

87 

88 Parameters 

89 ------------ 

90 mesh : trimesh.Trimesh 

91 Input geometry 

92 

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

102 

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) 

106 

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) 

119 

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

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

122 return permutated 

123 

124 

125class Permutator: 

126 def __init__(self, mesh): 

127 """ 

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

129 """ 

130 self._mesh = mesh 

131 

132 def transform(self, translation_scale=1000): 

133 return transform(self._mesh, translation_scale=translation_scale) 

134 

135 def noise(self, magnitude=None): 

136 return noise(self._mesh, magnitude) 

137 

138 def tessellation(self): 

139 return tessellation(self._mesh) 

140 

141 

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