# ***** BEGIN GPL LICENSE BLOCK ***** # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** bl_info = { "name": "Render Shots", "author": "Aaron Symons", "version": (0, 3, 2), "blender": (2, 76, 0), "location": "Properties > Render > Render Shots", "description": "Render an image or animation from different camera views", "warning": "", "wiki_url": "http://wiki.blender.org/index.php?title=Extensions:2.6/Py"\ "/Scripts/Render/Render_Shots", "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", "category": "Render"} import bpy from bpy.props import BoolProperty, IntProperty, StringProperty from bpy.app.handlers import persistent import os, shutil ##################################### # Update Functions ##################################### def shape_nav(self, context): nav = self.rs_shotshape_nav if self.rs_shotshape_shape != "": shapeVerts = bpy.data.objects[self.rs_shotshape_shape].data.vertices max = len(shapeVerts)-1 min = max - (max+max) if nav > max or nav < min: nav = 0 v = shapeVerts[nav].co self.location = (v[0], v[1], v[2]) return None def is_new_object(ob): try: isNew = ob["rs_shotshape_use_frames"] except: isNew = None return True if isNew is None else False def update_shot_list(scn): scn.rs_is_updating = True if hasattr(scn, 'rs_create_folders'): scn.rs_create_folders = False for ob in bpy.data.objects: if ob.type == 'CAMERA': if is_new_object(ob): ob["rs_shot_include"] = True ob["rs_shot_start"] = scn.frame_start ob["rs_shot_end"] = scn.frame_end ob["rs_shot_output"] = "" ob["rs_toggle_panel"] = True ob["rs_settings_use"] = False ob["rs_resolution_x"] = scn.render.resolution_x ob["rs_resolution_y"] = scn.render.resolution_y ob["rs_cycles_samples"] = 10 ob["rs_shotshape_use"] = False ob["rs_shotshape_shape"] = "" ob["rs_shotshape_nav"] = 0 ob["rs_shotshape_nav_start"] = False ob["rs_shotshape_offset"] = 1 ob["rs_shotshape_use_frames"] = False else: ob["rs_shot_include"] ob["rs_shot_start"] ob["rs_shot_end"] ob["rs_shot_output"] ob["rs_toggle_panel"] ob["rs_settings_use"] ob["rs_resolution_x"] ob["rs_resolution_y"] ob["rs_cycles_samples"] ob["rs_shotshape_use"] ob["rs_shotshape_shape"] ob["rs_shotshape_nav"] ob["rs_shotshape_nav_start"] ob["rs_shotshape_offset"] ob["rs_shotshape_use_frames"] scn.rs_is_updating = False ##################################### # Initialisation ##################################### def init_props(): object = bpy.types.Object scene = bpy.types.Scene # Camera properties object.rs_shot_include = BoolProperty(name="", description="Include this shot during render", default=True) object.rs_shot_start = IntProperty(name="Start", description="First frame in this shot", default=0, min=0, max=300000) object.rs_shot_end = IntProperty(name="End", description="Last frame in this shot", default=0, min=0, max=300000) object.rs_shot_output = StringProperty(name="", description="Directory/name to save to", subtype='DIR_PATH') object.rs_toggle_panel = BoolProperty(name="", description="Show/hide options for this shot", default=True) # Render settings object.rs_settings_use = BoolProperty(name = "", default=False, description = "Use specific render settings for this shot") object.rs_resolution_x = IntProperty(name="X", description="Number of horizontal pixels in the rendered image", default=2000, min=4, max=10000) object.rs_resolution_y = IntProperty(name="Y", description = "Number of vertical pixels in the rendered image", default=2000, min=4, max=10000) object.rs_cycles_samples = IntProperty(name="Samples", description = "Number of samples to render for each pixel", default=10, min=1, max=2147483647) # Shot shapes object.rs_shotshape_use = BoolProperty(name="", default=False, description="Use a shape to set a series of shots for this camera") object.rs_shotshape_shape = StringProperty(name="Shape:", description="Select an object") object.rs_shotshape_nav = IntProperty(name="Navigate", description="Navigate through this shape's vertices (0 = first vertex)", default=0, update=shape_nav) object.rs_shotshape_nav_start = BoolProperty(name="Start from here", default=False, description="Start from this vertex (skips previous vertices)") object.rs_shotshape_offset = IntProperty(name="Offset", description="Offset between frames (defines animation length)", default=1, min=1, max=200) object.rs_shotshape_use_frames = BoolProperty(name="Use frame range", description="Use the shot's frame range instead of the object's vertex"\ " count", default=False) # Internal scene.rs_is_updating = BoolProperty(name="", description="", default=False) scene.rs_create_folders = BoolProperty(name="", description="", default=False) scene.rs_main_folder = StringProperty(name="Main Folder", subtype='DIR_PATH', default="", description="Main folder in which to create the sub folders") scene.rs_overwrite_folders = BoolProperty(name="Overwrite", default=False, description="Overwrite existing folders (this will delete all"\ " files inside any existing folders)") ##################################### # Operators and Functions ##################################### RENDER_DONE = True RENDER_SETTINGS_HELP = False TIMELINE = {"start": 1, "end": 250, "current": 1} RENDER_SETTINGS = {"cycles_samples": 10, "res_x": 1920, "res_y": 1080} @persistent def render_finished(unused): global RENDER_DONE RENDER_DONE = True def using_cycles(scn): return True if scn.render.engine == 'CYCLES' else False def timeline_handler(scn, mode): global TIMELINE if mode == 'GET': TIMELINE["start"] = scn.frame_start TIMELINE["end"] = scn.frame_end TIMELINE["current"] = scn.frame_current elif mode == 'SET': scn.frame_start = TIMELINE["start"] scn.frame_end = TIMELINE["end"] scn.frame_current = TIMELINE["current"] def render_settings_handler(scn, mode, cycles_on, ob): global RENDER_SETTINGS if mode == 'GET': RENDER_SETTINGS["cycles_samples"] = scn.cycles.samples RENDER_SETTINGS["res_x"] = scn.render.resolution_x RENDER_SETTINGS["res_y"] = scn.render.resolution_y elif mode == 'SET': if cycles_on: scn.cycles.samples = ob["rs_cycles_samples"] scn.render.resolution_x = ob["rs_resolution_x"] scn.render.resolution_y = ob["rs_resolution_y"] elif mode == 'REVERT': if cycles_on: scn.cycles.samples = RENDER_SETTINGS["cycles_samples"] scn.render.resolution_x = RENDER_SETTINGS["res_x"] scn.render.resolution_y = RENDER_SETTINGS["res_y"] def frames_from_verts(ob, end, shape, mode): start = ob.rs_shot_start frame_range = (end - start)+1 verts = len(shape.data.vertices) if frame_range % verts != 0: end += 1 return create_frames_from_verts(ob, end, shape, mode) else: if mode == 'OFFSET': return frame_range / verts elif mode == 'END': return end def keyframes_handler(scn, ob, shape, mode): bpy.ops.object.select_all(action='DESELECT') ob.select_set(True) start = ob.rs_shotshape_nav if ob.rs_shotshape_nav_start else 0 if ob.rs_shotshape_use_frames and shape is not None: firstframe = ob.rs_shot_start offset = frames_from_verts(ob, ob.rs_shot_end, shape, 'OFFSET') else: firstframe = 1 offset = ob.rs_shotshape_offset if mode == 'SET': scn.frame_current = firstframe for vert in shape.data.vertices: if vert.index >= start: ob.location = vert.co bpy.ops.anim.keyframe_insert_menu(type='Location') scn.frame_current += offset return (len(shape.data.vertices) - start) * offset elif mode == 'WIPE': ob.animation_data_clear() class RENDER_OT_RenderShots_create_folders(bpy.types.Operator): ''' Create the output folders for all cameras ''' bl_idname = "render.rendershots_create_folders" bl_label = "Create Folders" mode = IntProperty() def execute(self, context): scn = context.scene if self.mode == 1: # Display options scn.rs_create_folders = True elif self.mode == 2: # Create folders if scn.rs_main_folder != "" and not scn.rs_main_folder.isspace(): for ob in bpy.data.objects: if ob.type == 'CAMERA' and not is_new_object(ob): # Name cleaning if "." in ob.name: name = ob.name.split(".") camName = name[0]+name[1] else: camName = ob.name mainFolder = scn.rs_main_folder destination = os.path.join(mainFolder, camName) # Folder creation if scn.rs_overwrite_folders: if os.path.isdir(destination): shutil.rmtree(destination) os.mkdir(destination) ob.rs_shot_output = destination+"\\" else: if not os.path.isdir(destination): ob.rs_shot_output = destination+"\\" os.makedirs(destination_path) self.report({'INFO'}, "Output folders created") scn.rs_overwrite_folders = False scn.rs_create_folders = False else: self.report({'ERROR'}, "No main folder selected") elif self.mode == 3: # Cancelled scn.rs_overwrite_folders = False scn.rs_create_folders = False return {'FINISHED'} class RENDER_OT_RenderShots_settingshelp(bpy.types.Operator): ''' \ Edit the resolutions and see the changes in 3D View ('ESC' to finish)\ ''' bl_idname = "render.rendershots_settingshelp" bl_label = "Render Settings Help" cam = StringProperty() def execute(self, context): global RENDER_SETTINGS_HELP RENDER_SETTINGS_HELP = True scn = context.scene render_settings_handler(scn, 'GET', using_cycles(scn), None) context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} def modal(self, context, event): scn = context.scene ob = bpy.data.objects[self.cam] if event.type in {'ESC'}: global RENDER_SETTINGS_HELP RENDER_SETTINGS_HELP = False render_settings_handler(scn, 'REVERT', using_cycles(scn), None) return {'FINISHED'} scn.render.resolution_x = ob["rs_resolution_x"] scn.render.resolution_y = ob["rs_resolution_y"] return {'PASS_THROUGH'} class RENDER_OT_RenderShots_constraints_add(bpy.types.Operator): ''' Add the tracking constraints and Empty for this camera ''' bl_idname = "render.rendershots_constraints_add" bl_label = "Create Constraints" cam: StringProperty() def execute(self, context): ob = bpy.data.objects[self.cam] ssName = "LookAt_for_"+ob.name bpy.ops.object.add(type="EMPTY") context.active_object.name = ssName target = bpy.data.objects[ssName] ob.constraints.new(type="DAMPED_TRACK").name="SS_Damped" damped_track = ob.constraints["SS_Damped"] damped_track.target = target damped_track.track_axis = 'TRACK_NEGATIVE_Z' damped_track.influence = 0.994 ob.constraints.new(type="LOCKED_TRACK").name="SS_Locked" locked_track = ob.constraints["SS_Locked"] locked_track.target = target locked_track.track_axis = 'TRACK_Y' locked_track.lock_axis = 'LOCK_Z' locked_track.influence = 1.0 return {'FINISHED'} class RENTER_OT_rendershots_refresh(bpy.types.Operator): ''' Adds newly created cameras to the list ''' bl_idname = "render.rendershots_refresh" bl_label = "Refresh" def execute(self, context): update_shot_list(context.scene) return {'FINISHED'} class RENDER_OT_RenderShots_render(bpy.types.Operator): ''' Render shots ''' bl_idname = "render.rendershots_render" bl_label = "Render" animation: BoolProperty(default=False) _timer = None _usingShape = False def execute(self, context): global RENDER_DONE RENDER_DONE = True scn = context.scene self.camList = [] self.vertTrack = -1 self.cam = "" for ob in bpy.data.objects: if ob.type == 'CAMERA' and not is_new_object(ob): if ob["rs_shot_include"]: output = ob["rs_shot_output"] addToList = False if output != "" and not output.isspace(): addToList = True else: message = "\"%s\" has no output destination" % ob.name self.report({'WARNING'}, message) if ob["rs_shotshape_use"]: shotShape = ob["rs_shotshape_shape"] if shotShape == "": addToList = False self.report({'WARNING'}, "\"%s\" has no shot shape" % ob.name) elif bpy.data.objects[shotShape].type != 'MESH': errObj = bpy.data.objects[shotShape].name addToList = False self.report({'ERROR'}, "\"%s\" is not a mesh object" % errObj) #else: # bpy.data.objects[shotShape].hide_render = True if addToList: self.camList.append(ob.name) self.camList.reverse() timeline_handler(scn, 'GET') render_settings_handler(scn, 'GET', using_cycles(scn), None) context.window_manager.modal_handler_add(self) self._timer = context.window_manager.event_timer_add(3, context.window) return {'RUNNING_MODAL'} def modal(self, context, event): global RENDER_DONE scn = context.scene if event.type in {'ESC'}: context.window_manager.event_timer_remove(self._timer) keyframes_handler(scn, bpy.data.objects[self.cam], None, 'WIPE') render_settings_handler(scn, 'REVERT', using_cycles(scn), None) timeline_handler(scn, 'SET') return {'CANCELLED'} if RENDER_DONE and self.camList: RENDER_DONE = False objs = bpy.data.objects range = 0 if self._usingShape: keyframes_handler(scn, objs[self.cam], None, 'WIPE') self._usingShape = False if not self._usingShape and self.camList: self.cam = self.camList.pop() ob = objs[self.cam] # Output and name cleaning scn.camera = ob output = ob["rs_shot_output"] if output[-1] == "/" or output[-1] == "\\": if "." in self.cam: camName = self.cam.split(".") output += camName[0]+camName[1] else: output += self.cam # Shot shapes if ob["rs_shotshape_use"]: self._usingShape = True shape = ob["rs_shotshape_shape"] range = keyframes_handler(scn, ob, objs[shape], 'SET') # Render settings if ob["rs_settings_use"]: render_settings_handler(scn, 'SET', using_cycles(scn), ob) else: render_settings_handler(scn, 'REVERT', using_cycles(scn), None) context.scene.render.filepath = output # Render ssUsing = ob["rs_shotshape_use"] if self.animation and not ssUsing and not self._usingShape: scn.frame_start = ob["rs_shot_start"] scn.frame_end = ob["rs_shot_end"] bpy.ops.render.render('INVOKE_DEFAULT', animation=True) elif self.animation and ssUsing and self._usingShape: if ob["rs_shotshape_use_frames"]: scn.frame_start = ob.rs_shot_start scn.frame_end = frames_from_verts(ob, ob.rs_shot_end, objs[shape], 'END') else: scn.frame_start = 1 scn.frame_end = range bpy.ops.render.render('INVOKE_DEFAULT', animation=True) elif not self.animation and not ssUsing and not self._usingShape: bpy.ops.render.render('INVOKE_DEFAULT', write_still=True) elif RENDER_DONE and not self.camList: context.window_manager.event_timer_remove(self._timer) keyframes_handler(scn, bpy.data.objects[self.cam], None, 'WIPE') render_settings_handler(scn, 'REVERT', using_cycles(scn), None) timeline_handler(scn, 'SET') return {'FINISHED'} return {'PASS_THROUGH'} class RENDER_OT_RenderShots_previewcamera(bpy.types.Operator): ''' Preview this shot (makes this the active camera in 3D View) ''' bl_idname = "render.rendershots_preview_camera" bl_label = "Preview Camera" camera = bpy.props.StringProperty() def execute(self, context): scn = context.scene cam = bpy.data.objects[self.camera] scn.objects.active = cam scn.camera = cam return {'FINISHED'} ##################################### # UI ##################################### class RENDER_PT_RenderShots(bpy.types.Panel): bl_label = "Render Shots" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" def draw(self, context): global RENDER_SETTINGS_HELP layout = self.layout scn = context.scene ANI_ICO, STILL_ICO = "RENDER_ANIMATION", "RENDER_STILL" INCL_ICO = "RESTRICT_RENDER_OFF" RENDER_OP = "render.rendershots_render" row = layout.row() row.operator(RENDER_OP, text="Image", icon=STILL_ICO) row.operator(RENDER_OP, text="Animation", icon=ANI_ICO).animation=True row = layout.row() if scn.rs_create_folders: row.operator("render.rendershots_create_folders", icon="FILE_TICK").mode=2 else: row.operator("render.rendershots_create_folders", icon="NEWFOLDER").mode=1 row.operator("render.rendershots_refresh", icon="FILE_REFRESH") if scn.rs_create_folders: row = layout.row() col = row.column(align=True) colrow = col.row() colrow.label(text="Main Folder:") colrow = col.row() colrow.prop(scn, "rs_main_folder", text="") colrow = col.row() colrow.prop(scn, "rs_overwrite_folders") colrow.operator("render.rendershots_create_folders", text="Cancel", icon="X").mode=3 if not scn.rs_is_updating: for ob in bpy.data.objects: if ob.type == 'CAMERA' and not is_new_object(ob): TOGL_ICO = "TRIA_DOWN" if ob["rs_toggle_panel"] else "TRIA_LEFT" box = layout.box() box.active = ob["rs_shot_include"] col = box.column() row = col.row() row.label(text="\""+ob.name+"\"") row.operator("render.rendershots_preview_camera", text="", icon="OUTLINER_OB_CAMERA").camera=ob.name row.prop(ob, "rs_shotshape_use", icon="MESH_DATA") row.prop(ob, "rs_settings_use", icon="SETTINGS") row.prop(ob, "rs_shot_include", icon=INCL_ICO) row.prop(ob, "rs_toggle_panel", icon=TOGL_ICO, emboss=False) if ob["rs_toggle_panel"]: col.separator() row = col.row() rowbox = row.box() col = rowbox.column() if ob["rs_shotshape_use"]: row = col.row() row.label(text="Shot Shape:") row = col.row() row.prop_search(ob, "rs_shotshape_shape", scn, "objects", text="", icon="OBJECT_DATA") row = col.row(align=True) row.prop(ob, "rs_shotshape_nav") row.prop(ob, "rs_shotshape_nav_start") row = col.row(align=True) row.prop(ob, "rs_shotshape_offset") row.prop(ob, "rs_shotshape_use_frames") row = col.row() row.operator("render.rendershots_constraints_add", icon="CONSTRAINT_DATA").cam=ob.name col.separator() if ob["rs_settings_use"]: row = col.row() row.label(text="Render Settings:") row = col.row() rowcol = row.column(align=True) rowcol.prop(ob, "rs_resolution_x") rowcol.prop(ob, "rs_resolution_y") rowcol = row.column() if not RENDER_SETTINGS_HELP: rowcol.operator("render.rendershots_settingshelp", text="", icon="HELP").cam=ob.name else: rowcol.label(icon="TIME") if using_cycles(scn): rowcol.prop(ob, "rs_cycles_samples") else: rowcol.label() col.separator() row = col.row() row.label(text="Shot Settings:") row = col.row(align=True) row.prop(ob, "rs_shot_start") row.prop(ob, "rs_shot_end") row = col.row() out = ob["rs_shot_output"] row.alert = False if out != "" and not out.isspace() else True row.prop(ob, "rs_shot_output") def register(): bpy.utils.register_module(__name__) init_props() bpy.app.handlers.render_complete.append(render_finished) def unregister(): bpy.app.handlers.render_complete.remove(render_finished) bpy.utils.unregister_module(__name__) object = bpy.types.Object scene = bpy.types.Scene # Camera properties del object.rs_shot_include del object.rs_shot_start del object.rs_shot_end del object.rs_shot_output del object.rs_toggle_panel # Render settings del object.rs_settings_use del object.rs_resolution_x del object.rs_resolution_y del object.rs_cycles_samples # Shot shapes del object.rs_shotshape_use del object.rs_shotshape_shape del object.rs_shotshape_nav del object.rs_shotshape_nav_start del object.rs_shotshape_offset del object.rs_shotshape_use_frames # Internal del scene.rs_is_updating del scene.rs_create_folders del scene.rs_main_folder del scene.rs_overwrite_folders if __name__ == '__main__': register()