almost final version of the code:
# mcjObjSeqRender.py# by mCasualJacques# released under whichever permissive license is compatible with Blender's licenseimport bpy, mathimport structfrom bpy.props import StringPropertyimport osimport io_scene_obj.import_objfrom bpy_extras.io_utils import axis_conversionimport reimport string#------------------------------------------------------------------------------- #note line_value is derived from from io_scene_objdef line_value(line_split): ''' Returns 1 string represneting the value for this line None will be returned if theres only 1 word ''' length = len(line_split) if length == 1: return None elif length == 2: return line_split[1] elif length > 2: return ' '.join(line_split[1:]) #------------------------------------------------------------------------------- # Begin Section - fix paths in mtl files - by mCasualJacques#---------- getMtlFromObj ----------def getMtlFromObj( filepath ): filepath = os.fsencode( filepath ) file = open( filepath, 'r' ) for line in file: line = line.lstrip() if line.startswith('mtllib'): file.close() l = len( line ) return( line[7:l-1] ) file.close() return( 0 )#-------------- outMap ----------------- # line is a of the form : map-filePath# if first char of the filePath is '/' then this is a relative path in a format# that is not handled well by the importer so we remove the leading '/'def outMap( out, line, key ): img_filepath = line_value(line.split()) if img_filepath: if img_filepath[0] == '/': out.write( key + ' ' + img_filepath[1:] + '\n' ) else: out.write( key + ' ' + img_filepath + '\n' )#-------------- mapNeedsFix ----------------- # line is a of the form : map-filePath# if first char of the filePath is '/' then this is a relative path in a format# that is not handled well by the importer so we remove the leading '/'def mapNeedsFix( line, key ): img_filepath = line_value(line.split()) if img_filepath: if img_filepath[0] == '/': return( True ) else: return( False ) #---------- mtlNeedsFix ----------def mtlNeedsFix(filepath,mtllib): DIR = os.path.dirname(filepath) mtlpath = os.path.join( DIR, mtllib ) if not os.path.exists(mtlpath): return( False ) mtl = open(mtlpath, 'r') for line in mtl: line = line.strip() line_lower = line.lower().lstrip() if not line: pass elif line_lower.startswith('km'): #we'll remove the Kms mtl.close() return( True ) elif line_lower.startswith('map_ka'): if( mapNeedsFix( line, 'map_ka' ) ): mtl.close() return( True ) elif line_lower.startswith('map_ks'): if( mapNeedsFix( line, 'map_ks' ) ): mtl.close() return( True ) elif line_lower.startswith('map_kd'): if( mapNeedsFix( line, 'map_kd' ) ): mtl.close() return( True ) elif line_lower.startswith('map_bump'): if( mapNeedsFix( line, 'map_bump' ) ): mtl.close() return( True ) elif line_lower.startswith( 'bump'): if( mapNeedsFix( line, 'bump' ) ): mtl.close() return( True ) elif line_lower.startswith('map_d'): if( mapNeedsFix( line, 'map_d' ) ): mtl.close() return( True ) elif line_lower.startswith('map_tr'): if( mapNeedsFix( line, 'map_tr' ) ): mtl.close() return( True ) elif line_lower.startswith('refl'): if( mapNeedsFix( line, 'refl' ) ): mtl.close() return( True ) mtl.close() return( False ) #---------- fixMtlPaths ----------def fixMtlPaths( filepath ): mtllib = getMtlFromObj( filepath ); if not mtllib: return if not mtlNeedsFix(filepath,mtllib): return print( "fixing .mtl paths" ) DIR = os.path.dirname(filepath) mtlpath = os.path.join( DIR, mtllib ) if not os.path.exists(mtlpath): return mtl = open(mtlpath, 'r') tempPath = os.path.join( DIR, 'temp.txt' ); out = open( tempPath , 'w') out.write( '# Leading front slashes in map paths were removed' + '\n' ) for line in mtl: line = line.strip() line_lower = line.lower().lstrip() if not line: pass if line.startswith('#'): out.write( line + '\n' ) elif line_lower.startswith('km'): #the importer reports Km as errors out.write( '#' + line + '\n' ) elif line_lower.startswith('map_ka'): outMap( out, line, 'map_ka' ) elif line_lower.startswith('map_ks'): outMap( out, line, 'map_ks' ) elif line_lower.startswith('map_kd'): outMap( out, line, 'map_kd' ) elif line_lower.startswith('map_bump'): outMap( out, line, 'map_bump' ) elif line_lower.startswith( 'bump'): outMap( out, line, 'bump' ) elif line_lower.startswith('map_d'): outMap( out, line, 'map_d' ) elif line_lower.startswith('map_tr'): outMap( out, line, 'map_tr' ) elif line_lower.startswith('refl'): outMap( out, line, 'refl' ) else: out.write( line + '\n' ) mtl.close() out.close() os.remove( mtlpath ) os.renames( tempPath, mtlpath ); # End Section - fix mtl paths by mCasualJacques#------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Begin Section - by mCasualJacques# switch materials of newly loaded objects# to the materials with the same root-names# those materials were created/modified when# the first .obj in the sequence of .objs was loaded#------------- findMaterial -----------------def findMaterial(name): allmats = bpy.data.materials for material in allmats: # material root-name truncation may # occur during obj-import # ex: a material named 'antidisestablishmen' # the first time the obj-importer encounters it # it will be stored as 'antidisestablishmen' # the second time the obj-importer encounters it # it will be stored as 'antidisestablishm.001' n = len( name ) oldname = material.name leno = len( oldname ) if( leno >= n ): if oldname[0:n] == name: if( leno == n ): return material else: if( oldname[n] != '.' ): return material#--------------- switchMatsToBase ------------def switchMatsToBase( obj_names ): for obj_name in obj_names: obj = bpy.data.objects[obj_name] for matslot in obj.material_slots: mat = matslot.material ( root, ext ) = os.path.splitext( mat.name ) if( len( ext ) > 0 ): baseMap = findMaterial( root ) if baseMap: matslot.material = baseMap# end Section - by mCasualJacques #------------------------------------------------------------------------------- # Begin Section : mesh loader-renderer-unloader code by TomHarding# http://blenderartists.org/forum/archive/index.php/t-228040.html?s=3fbeeee6aedf70853b58826dcf82b5e1def meshStreamer( frameNo, objPath, outPath, bMats, bFixMapPaths, bSimulate ): print( 'frame: %d'%frameNo + ', in: ' + objPath + ', out: ' + outPath ) if( bSimulate ) : return if bFixMapPaths: fixMtlPaths( objPath ) #get list of current objects and meshes before_obj_list = [] before_mesh_list = [] for object in bpy.data.objects: before_obj_list.append(object.name) for mesh in bpy.data.meshes: before_mesh_list.append(mesh.name) #load the appropriate obj file gm = axis_conversion( from_forward='-Z', from_up='Y' ).to_4x4() io_scene_obj.import_obj.load(bpy.ops,bpy.context,objPath,global_matrix=gm) #get new list of objects and meshes after_obj_list = [] after_mesh_list = [] for object in bpy.data.objects: after_obj_list.append(object.name) for mesh in bpy.data.meshes: after_mesh_list.append(mesh.name) #get new object & mesh names new_obj_names = [item for item in after_obj_list if not item in before_obj_list] new_mesh_names = [item for item in after_mesh_list if not item in before_mesh_list] # re-use materials from the first .obj if bMats: switchMatsToBase( new_obj_names ) #set render current frame bpy.context.scene.frame_set( frameNo ) bpy.context.scene.render.filepath = outPath bpy.ops.render.render( animation = False, write_still =True, layer="", scene="" ) #remove added data from project & memory for name in new_obj_names: ob = bpy.data.objects[name] bpy.context.scene.objects.unlink(ob) bpy.data.objects.remove(ob) for name in new_mesh_names: mesh = bpy.data.meshes[name] bpy.data.meshes.remove(mesh)# End Section : mesh loader-renderer-unloader code by TomHarding#------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # begin numbered file list building section# derived from code by# Chip Chapin, possibly Luc-Eric Rousseau, and John Cromiedef seq_file_list( a_seq_file ): (work_folder, sequence_proto) = os.path.split(a_seq_file) sm = re.match("(.*?)[0-9]+\.", sequence_proto) sq_name = sequence_proto if not sm: sorted_sq_files = [[a_seq_file, '-1']] ( sq_name, ext ) = os.path.splitext( sequence_proto ) return( sorted_sq_files, sq_name, work_folder ) sq_name = sm.group(1) sq_re = re.compile("^" + sq_name + "[0-9]+\." ); sq_files = [] dir = os.listdir(work_folder) chopoff = len( sq_name ) for f in dir: if sq_re.match(f): ext = os.path.splitext( f )[1] if( ext == ".obj" ): numandext = f[chopoff:] (num, ext) = os.path.splitext(numandext) sq_files.append([f,'%06d'% int(num)]) #zero-padded for sorting sorted_sq_files = sorted(sq_files, key=lambda col: col[1]) return( sorted_sq_files, sq_name, work_folder )#end numbered file list building section#------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Begin Section : render sequence of numbered objs, code by mCasual/Jacques#-------------------- doit --------------------def doit( filepath, bMats, bFixMapPaths, fObjStart, fObjStep, bSimulate ): print( "\n------- mcjObjSeqRender begins -------\n") scene = bpy.context.scene render = scene.render ext = render.image_settings.file_format ( sq_files, sq_name, work_folder ) = seq_file_list( filepath ) sq_count = len( sq_files ) frame_start = bpy.context.scene.frame_start frame_end = bpy.context.scene.frame_end frame_step = bpy.context.scene.frame_step sep = '\\' numObj = len( sq_files ) if fObjStart < 0 : fObjStart = 0 if fObjStart >= numObj: fObjStart = numObj - 1 idxObj = fObjStart if( sq_files[0][1] == '-1' ): #not a numbered file objPath = sq_files[0][0] outPath = work_folder + sep + sq_name + '.' + ext meshStreamer( frame_start, objPath, outPath, bMats, bFixMapPaths, bSimulate ) return {'FINISHED'} for frame in range( frame_start, frame_end + 1, frame_step ): objPath = work_folder + sep + sq_files[idxObj][0] outPath = work_folder + sep + sq_name + '%d'%frame + '.' + ext meshStreamer( frame, objPath, outPath, bMats, bFixMapPaths, bSimulate ) idxObj = ( idxObj + fObjStep ) % numObj return {'FINISHED'} # End Section : mCasual/Jacques#------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Begin Section : file selection code derived from code by Jacob Valenta# http://blenderapi.wordpress.com/2011/09/26/file-selection-with-python/class McjBatchObjRenderer(bpy.types.Operator): bl_idname = "object.mcj_batch_obj_render" bl_label = "Import" filename_ext = ".obj" filter_glob = StringProperty(default="*.obj", options={'HIDDEN'}) filepath = StringProperty(subtype="FILE_PATH") bMats = bpy.props.BoolProperty(name="Re-Use initial materials") bFixMapPaths = bpy.props.BoolProperty(name = "Fix .mtl files") bSimulate = bpy.props.BoolProperty(name="simulate/log") fObjStart = bpy.props.IntProperty(name=".obj start index [0...]") fObjStep = bpy.props.IntProperty(name=".obj step") def execute(self, context): doit( self.filepath, self.bMats, self.bFixMapPaths, self.fObjStart, self.fObjStep, self.bSimulate ) return {'FINISHED'} def invoke(self, context, event): context.window_manager.fileselect_add(self) self.bMats = True self.bFixMapPaths = True self.fObjStart = 0 self.fObjStep = 1 self.bSimulate = False return {'RUNNING_MODAL'} def draw(self, context): layout = self.layout col = layout.column() col.label(text="Warning: By pressing Ctrl-C in the System" ) col.label(text="Console, you will be able to interrupt this" ) col.label(text="script, otherwise your only option will be" ) col.label(text="to close Blender. You can open the console" ) col.label(text="from Blender's Help menu. You can also" ) col.label(text="monitor the render progress in the console." ) row = col.row() col.prop(self, "bMats" ) col.prop(self, "bFixMapPaths" ) col.prop(self, "fObjStart" ) col.prop(self, "fObjStep" ) col.prop(self, "bSimulate" )bpy.utils.register_class(McjBatchObjRenderer)bpy.ops.object.mcj_batch_obj_render('INVOKE_DEFAULT')# End Section : file selection code by Jacob Valenta#-------------------------------------------------------------------------------