Coverage for trimesh/exchange/misc.py: 76%

67 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-24 04:40 +0000

1import json 

2from tempfile import NamedTemporaryFile 

3 

4from .. import util 

5from ..exceptions import ExceptionWrapper 

6 

7 

8def load_dict(file_obj, **kwargs): 

9 """ 

10 Load multiple input types into kwargs for a Trimesh constructor. 

11 Tries to extract keys: 

12 'faces' 

13 'vertices' 

14 'face_normals' 

15 'vertex_normals' 

16 

17 Parameters 

18 ---------- 

19 file_obj : dict 

20 accepts multiple forms 

21 -dict: has keys for vertices and faces as (n,3) numpy arrays 

22 -dict: has keys for vertices/faces (n,3) arrays encoded as dicts/base64 

23 with trimesh.util.array_to_encoded/trimesh.util.encoded_to_array 

24 -str: json blob as dict with either straight array or base64 values 

25 -file object: json blob of dict 

26 file_type: not used 

27 

28 Returns 

29 ----------- 

30 loaded: dict with keys 

31 -vertices: (n,3) float 

32 -faces: (n,3) int 

33 -face_normals: (n,3) float (optional) 

34 """ 

35 if file_obj is None: 

36 raise ValueError("file_obj passed to load_dict was None!") 

37 if util.is_instance_named(file_obj, "Trimesh"): 

38 return file_obj 

39 if isinstance(file_obj, str): 

40 if "{" not in file_obj: 

41 raise ValueError("Object is not a JSON encoded dictionary!") 

42 file_obj = json.loads(file_obj.decode("utf-8")) 

43 elif util.is_file(file_obj): 

44 file_obj = json.load(file_obj) 

45 

46 # what shape should the file_obj be to be usable 

47 mesh_file_obj = { 

48 "vertices": (-1, 3), 

49 "faces": (-1, (3, 4)), 

50 "face_normals": (-1, 3), 

51 "face_colors": (-1, (3, 4)), 

52 "vertex_normals": (-1, 3), 

53 "vertex_colors": (-1, (3, 4)), 

54 } 

55 

56 # now go through file_obj structure and if anything is encoded as base64 

57 # pull it back into numpy arrays 

58 if not isinstance(file_obj, dict): 

59 raise ValueError(f"`{type(file_obj)}` object passed to dict loader!") 

60 

61 loaded = {} 

62 file_obj = util.decode_keys(file_obj, "utf-8") 

63 for key, shape in mesh_file_obj.items(): 

64 if key in file_obj: 

65 loaded[key] = util.encoded_to_array(file_obj[key]) 

66 if not util.is_shape(loaded[key], shape): 

67 raise ValueError( 

68 "Shape of %s is %s, not %s!", 

69 key, 

70 str(loaded[key].shape), 

71 str(shape), 

72 ) 

73 if len(loaded) == 0: 

74 raise ValueError("Unable to extract a mesh from the dict!") 

75 

76 return loaded 

77 

78 

79def load_meshio(file_obj, file_type: str, **kwargs): 

80 """ 

81 Load a meshio-supported file into the kwargs for a Trimesh 

82 constructor. 

83 

84 

85 Parameters 

86 ---------- 

87 file_obj : file object 

88 Contains a meshio file 

89 file_type : str 

90 File extension, aka 'vtk' 

91 

92 Returns 

93 ---------- 

94 loaded : dict 

95 kwargs for Trimesh constructor 

96 """ 

97 import warnings 

98 

99 warnings.warn( 

100 "`meshio` loading is deprecated and will be removed after June 2027; " 

101 + "use `meshio` directly if you need these formats.", 

102 category=DeprecationWarning, 

103 stacklevel=2, 

104 ) 

105 

106 # trimesh "file types" are really filename extensions 

107 # meshio may return multiple answers for each file extension 

108 file_formats = meshio.extension_to_filetypes["." + file_type] 

109 

110 mesh = None 

111 exceptions = [] 

112 

113 # meshio appears to only support loading by file name so use a tempfile 

114 with NamedTemporaryFile(suffix=f".{file_type}") as temp: 

115 temp.write(file_obj.read()) 

116 temp.flush() 

117 # try the loaders in order 

118 for file_format in file_formats: 

119 try: 

120 mesh = meshio.read(temp.name, file_format=file_format) 

121 break 

122 except BaseException as E: 

123 exceptions.append(str(E)) 

124 

125 if mesh is None: 

126 raise ValueError("Failed to load file:" + "\n".join(exceptions)) 

127 

128 # save file_obj as kwargs for a trimesh.Trimesh 

129 result = {} 

130 # pass kwargs to mesh constructor 

131 result.update(kwargs) 

132 # add vertices 

133 result["vertices"] = mesh.points 

134 try: 

135 # add faces 

136 result["faces"] = mesh.get_cells_type("triangle") 

137 except BaseException: 

138 util.log.warning("unable to get faces", exc_info=True) 

139 result["faces"] = [] 

140 

141 return result 

142 

143 

144_misc_loaders = {"dict": load_dict, "dict64": load_dict} 

145_misc_loaders = {} 

146 

147 

148try: 

149 import meshio 

150 

151 # add meshio loaders here 

152 _meshio_loaders = {k[1:]: load_meshio for k in meshio.extension_to_filetypes.keys()} 

153 _misc_loaders.update(_meshio_loaders) 

154except BaseException: 

155 _meshio_loaders = {} 

156 

157try: 

158 import openctm 

159 

160 _misc_loaders["ctm"] = openctm.load_ctm 

161except BaseException as E: 

162 _misc_loaders["ctm"] = ExceptionWrapper(E)