11import os
2+ import sys
23import subprocess
34import pymeshlab as ml
45import numpy as np
56from threading import Lock
6- import threading
77from multiprocessing .pool import ThreadPool
88import Sequence_Metadata
99from PIL import Image
1010
1111class SequenceConverter :
1212
13- isPointcloud = False
1413 terminateProcessing = False
15-
14+ debugMode = False
1615 metaData = Sequence_Metadata .MetaData ()
1716
1817 modelPaths = []
@@ -25,6 +24,9 @@ class SequenceConverter:
2524 outputPath = ""
2625 resourcePath = ""
2726
27+ isPointcloud = False
28+ hasUVs = False
29+ textureDimensions = []
2830 convertToDDS = False
2931 convertToASTC = False
3032 convertToSRGB = False
@@ -35,10 +37,8 @@ class SequenceConverter:
3537 maxThreads = 8
3638 loadMeshLock = Lock ()
3739 activeThreads = 0
38-
3940
4041 def start_conversion (self , model_paths_list , image_paths_list , input_path , output_path , resource_Path , processFinishedCB , threadCount , convertDDS , convertASTC , convertSRGB , decimatePointcloud , decimatePercentage ):
41-
4242 self .metaData = Sequence_Metadata .MetaData ()
4343 self .terminateProcessing = False
4444 self .modelPaths = model_paths_list
@@ -52,6 +52,7 @@ def start_conversion(self, model_paths_list, image_paths_list, input_path, outpu
5252 self .convertToSRGB = convertSRGB
5353 self .decimatePointcloud = decimatePointcloud
5454 self .decimatePercentage = decimatePercentage
55+ self .debugMode = False # hasattr(sys, 'gettrace') and sys.gettrace() is not None
5556
5657 modelCount = len (model_paths_list )
5758 self .metaData .headerSizes = [None ] * modelCount
@@ -87,7 +88,7 @@ def finish_conversion(self, writeMetaData):
8788 self .texturePool .close ()
8889 except :
8990 waitOnClose = True
90- self .texturePool .join ()
91+ self .texturePool .close ()
9192
9293 if (writeMetaData ):
9394 self .write_metadata ()
@@ -102,15 +103,20 @@ def process_models(self):
102103 else :
103104 threads = self .maxThreads
104105
105- self .modelPool = ThreadPool (processes = threads )
106- self .modelPool .map_async (self .convert_model , self .modelPaths )
106+ if not self .debugMode :
107+ self .modelPool = ThreadPool (processes = threads )
108+ self .modelPool .map_async (self .convert_model , self .modelPaths )
109+
110+ else :
111+ for model in self .modelPaths :
112+ self .convert_model (model )
107113
108114 def convert_model (self , file ):
109115
110116 listIndex = self .modelPaths .index (file )
111117
112118 if (self .terminateProcessing ):
113- self .convert_model_finished (False , "" )
119+ self .processFinishedCB (False , "" )
114120 return
115121
116122 splitted_file = file .split ("." )
@@ -122,41 +128,65 @@ def convert_model(self, file):
122128
123129 ms = ml .MeshSet ()
124130
125- self .loadMeshLock .acquire () # If we don't lock the mesh loading process, crashes might occur
131+ if not self .debugMode :
132+ self .loadMeshLock .acquire () # If we don't lock the mesh loading process, crashes might occur
126133
127134 try :
128135 ms .load_new_mesh (inputfile )
129136 except :
130137 self .loadMeshLock .release ()
131- self .convert_model_finished (True , "Error opening file: " + inputfile )
138+ self .processFinishedCB (True , "Error opening file: " + inputfile )
132139 return
133140
134- self .loadMeshLock .release ()
135-
136141 if (self .terminateProcessing ):
137- self .convert_model_finished (False , "" )
142+ self .processFinishedCB (False , "" )
143+ self .loadMeshLock .release ()
138144 return
139145
140146 faceCount = len (ms .current_mesh ().face_matrix ())
141- is_pointcloud = True
142- has_UVs = False
143147
144148 #Is the file a mesh or pointcloud?
145149 if (faceCount > 0 ):
146- is_pointcloud = False
150+ pointcloud = False
151+ else :
152+ pointcloud = True
147153
148154 if (ms .current_mesh ().has_wedge_tex_coord () == True or ms .current_mesh ().has_vertex_tex_coord () == True ):
149- has_UVs = True
155+ uvs = True
156+ else :
157+ uvs = False
158+
159+ if (listIndex == 0 ):
160+ self .isPointcloud = pointcloud
161+ self .hasUVs = uvs
162+ else :
163+ if (self .hasUVs != uvs ):
164+ # The sequence has different attributes, which is not allowed
165+ self .processFinishedCB (True , "Error: Some frames with UVs, some without. All frames need to be consistent with this attribute!" )
166+ self .loadMeshLock .release ()
167+ return
168+ if (self .isPointcloud != pointcloud ):
169+ self .processFinishedCB (True , "Error: Some frames are Pointclouds, some are meshes. Mixed sequences are not allowed!" )
170+ self .loadMeshLock .release ()
171+ return
150172
151173 #There is a chance that the file might have wedge tex
152174 #coordinates which are unsupported in Unity, so we convert them
153175 #Also we need to ensure that our mesh contains only triangles!
154- if (is_pointcloud == False and ms .current_mesh ().has_wedge_tex_coord () == True ):
155- ms .compute_texcoord_transfer_wedge_to_vertex ()
176+ if (self . isPointcloud == False and ms .current_mesh ().has_wedge_tex_coord () == True ):
177+ ms .compute_texcoord_transfer_wedge_to_vertex ()
156178
179+ # Unity mirrors the X-Axis on import of meshes, so we need to mirror it as well
180+ # so that the axis stays consistent
181+ ms .apply_matrix_flip_or_swap_axis (flipx = True )
182+
183+ # This somehow also flips the faces, so we flip them again
184+ if (self .isPointcloud == False ):
185+ ms .meshing_invert_face_orientation (forceflip = True )
157186
158187 if (self .terminateProcessing ):
159- self .convert_model_finished (False , "" )
188+ self .processFinishedCB (False , "" )
189+ self .loadMeshLock .release ()
160190 return
161191
162192 vertices = None
@@ -165,15 +195,15 @@ def convert_model(self, file):
165195 uvs = None
166196
167197 #Load type specific attributes
168- if (is_pointcloud == True ):
198+ if (self . isPointcloud == True ):
169199 vertices = ms .current_mesh ().vertex_matrix ().astype (np .float32 )
170200 vertice_colors = ms .current_mesh ().vertex_color_array ()
171201
172202 else :
173203 vertices = ms .current_mesh ().vertex_matrix ().astype (np .float32 )
174204 faces = ms .current_mesh ().face_matrix ()
175205
176- if (has_UVs == True ):
206+ if (self . hasUVs == True ):
177207 uvs = ms .current_mesh ().vertex_tex_coord_matrix ().astype (np .float32 )
178208
179209 vertexCount = len (vertices )
@@ -185,18 +215,21 @@ def convert_model(self, file):
185215
186216 bounds = ms .current_mesh ().bounding_box ()
187217
188- if (is_pointcloud == True ):
218+ if (self . isPointcloud == True ):
189219 geoType = Sequence_Metadata .GeometryType .point
190220 else :
191- if (has_UVs == False ):
221+ if (self . hasUVs == False ):
192222 geoType = Sequence_Metadata .GeometryType .mesh
193223 else :
194224 geoType = Sequence_Metadata .GeometryType .texturedMesh
195225
196226 if (self .terminateProcessing ):
197- self .convert_model_finished (False , "" )
227+ self .processFinishedCB (False , "" )
228+ self .loadMeshLock .release ()
198229 return
199230
231+ if not self .debugMode :
232+ self .loadMeshLock .release ()
200233
201234 #The meshlab exporter doesn't support all the features we need, so we export the files manually
202235 #to PLY with our very stringent structure. This is needed because we want to keep the
@@ -218,14 +251,14 @@ def convert_model(self, file):
218251 header += "property float y" + "\n "
219252 header += "property float z" + "\n "
220253
221- if (is_pointcloud == True ):
254+ if (self . isPointcloud == True ):
222255 header += "property uchar red" + "\n "
223256 header += "property uchar green" + "\n "
224257 header += "property uchar blue" + "\n "
225258 header += "property uchar alpha" + "\n "
226259
227260 else :
228- if (has_UVs == True ):
261+ if (self . hasUVs == True ):
229262 header += "property float s" + "\n "
230263 header += "property float t" + "\n "
231264 header += "element face " + str (len (faces )) + "\n "
@@ -239,7 +272,7 @@ def convert_model(self, file):
239272 f .write (headerASCII )
240273
241274 #Constructing the mesh data, as binary array
242- if (is_pointcloud == True ):
275+ if (self . isPointcloud == True ):
243276
244277 verticePositionsBytes = np .frombuffer (vertices .tobytes (), dtype = np .uint8 )
245278 verticeColorsBytes = np .frombuffer (vertice_colors .tobytes (), dtype = np .uint8 )
@@ -267,7 +300,7 @@ def convert_model(self, file):
267300 #Vertices and UVS
268301 verticePositionsBytes = np .frombuffer (vertices .tobytes (), dtype = np .uint8 )
269302
270- if (has_UVs == True ):
303+ if (self . hasUVs == True ):
271304 uvsBytes = np .frombuffer (uvs .tobytes (), dtype = np .uint8 )
272305
273306 verticePositionsBytes = np .reshape (verticePositionsBytes , (- 1 , 12 ))
@@ -293,13 +326,12 @@ def convert_model(self, file):
293326
294327 f .write (bytes (body ))
295328
296- self .metaData .set_metadata_Model (vertexCount , indiceCount , headerSize , bounds , geoType , has_UVs , listIndex )
329+ self .metaData .set_metadata_Model (vertexCount , indiceCount , headerSize , bounds , geoType , self . hasUVs , listIndex )
297330
298- self .convert_model_finished (False , "" )
331+ self .processFinishedCB (False , "" )
299332
300-
301- def convert_model_finished (self , error , errorText ):
302- self .processFinishedCB (error , errorText )
333+ if self .debugMode :
334+ print ("Processed file: " + str (listIndex ))
303335
304336 def process_images (self ):
305337
@@ -309,6 +341,11 @@ def process_images(self):
309341 threads = self .maxThreads
310342
311343 self .texturePool = ThreadPool (processes = threads )
344+
345+ #Read the first image to get the dimensions
346+ self .convert_image (self .imagePaths [0 ])
347+ self .imagePaths .pop (0 )
348+
312349 self .texturePool .map_async (self .convert_image , self .imagePaths )
313350
314351 def convert_image (self , file ):
@@ -321,24 +358,28 @@ def convert_image(self, file):
321358
322359 splitted_file = file .split ("." )
323360 file_name = splitted_file [0 ]
361+ for x in range (1 , len (splitted_file ) - 1 ):
362+ file_name += "." + splitted_file [x ]
324363 inputfile = self .inputPath + "\\ " + file
325364
326365 sizeDDS = 0
327366 sizeASTC = 0
328367
329368 if (self .convertToDDS ):
330369 outputfileDDS = self .outputPath + "\\ " + file_name + ".dds"
331- cmd = self .resourcePath + "texconv " + inputfile + " -o " + self .outputPath + " -m 1 -f DXT1 -y -nologo"
370+ cmd = self .resourcePath + "texconv " + " \" " + inputfile + "\" " + " -o " + " \" " + self .outputPath + " \" " + " -m 1 -f DXT1 -y -nologo"
332371 if (self .convertToSRGB ):
333372 cmd += " -srgbo"
334373 if (subprocess .run (cmd ).returncode != 0 ):
335374 self .processFinishedCB (True , "Error converting DDS texture: " + inputfile )
375+ return
336376
337377 if (self .convertToASTC ):
338378 outputfileASCT = self .outputPath + "\\ " + file_name + ".astc"
339- cmd = self .resourcePath + "astcenc -cl " + inputfile + " " + outputfileASCT + " 6x6 -medium -silent"
379+ cmd = self .resourcePath + "astcenc -cl " + " \" " + inputfile + "\" " + " " + " \" " + outputfileASCT + " \" " + " 6x6 -medium -silent"
340380 if (subprocess .run (cmd ).returncode != 0 ):
341381 self .processFinishedCB (True , "Error converting ASTC texture: " + inputfile )
382+ return
342383
343384 # Write the metadata once per sequence
344385 if (listIndex == 0 ):
@@ -352,10 +393,17 @@ def convert_image(self, file):
352393 if (len (self .imagePaths ) > 1 ):
353394 textureMode = Sequence_Metadata .TextureMode .perFrame
354395
396+ self .textureDimensions = self .get_image_dimensions (inputfile )
397+ self .metaData .set_metadata_texture (self .convertToDDS , self .convertToASTC , self .textureDimensions [0 ], self .textureDimensions [1 ], sizeDDS , sizeASTC , textureMode )
398+ else :
355399 dimensions = self .get_image_dimensions (inputfile )
356- self .metaData .set_metadata_texture (self .convertToDDS , self .convertToASTC , dimensions [0 ], dimensions [1 ], sizeDDS , sizeASTC , textureMode )
357-
358-
400+ if len (dimensions ) < 2 :
401+ self .processFinishedCB (True , "Could not get image dimensions!" )
402+ return
403+ if (dimensions [0 ] != self .textureDimensions [0 ] or dimensions [1 ] != self .textureDimensions [1 ]):
404+ self .processFinishedCB (True , "All textures need to have the same resolution! Frame " + str (listIndex ))
405+ return
406+
359407 self .processFinishedCB (False , "" )
360408
361409 def get_image_dimensions (self , filePath ):
0 commit comments