# SPDX-License-Identifier: GPL-2.0-or-later import bpy import importlib import re import vse_toolbox from bpy_extras.io_utils import ImportHelper from bpy.props import ( CollectionProperty, BoolProperty, EnumProperty, IntProperty, StringProperty, ) from bpy.types import ( Operator, ) from pathlib import Path from vse_toolbox.constants import ( ASSETS, EDITS, MOVIES, SOUNDS, EDIT_SUFFIXES, MOVIE_SUFFIXES, SOUND_SUFFIXES, ) from vse_toolbox.sequencer_utils import ( clean_sequencer, get_shot_sequence, get_strips, get_active_strip, import_edit, import_movie, import_sound, rename_strips, render_strips, set_channels, ) from vse_toolbox.bl_utils import get_addon_prefs, get_settings from vse_toolbox.file_utils import install_module, norm_name, norm_str class VSETB_OT_export_csv(Operator): bl_idname = "sequencer.export_csv" bl_label = "Set Scene" bl_description = "Set Scene for Breakdown" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return True def execute(self, context): self.report({'ERROR'}, f'Export not implemented yet.') return {"CANCELLED"} class VSETB_OT_auto_select_files(Operator): bl_idname = "import.auto_select_files" bl_label = "Auto Select" bl_description = "Auto Select Files" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return True def get_items(self, items=[]): if not items: return [('NONE', 'None', '', 0)] return [(e, e, '', i) for i, e in enumerate(sorted(items))] def execute(self, context): params = context.space_data.params directory = Path(params.directory.decode()) EDITS.clear() MOVIES.clear() SOUNDS.clear() edits = [] movies = [] sounds = [] for file_entry in directory.glob('*'): if file_entry.is_dir(): continue if file_entry.suffix in EDIT_SUFFIXES: edits.append(file_entry.name) elif file_entry.suffix in MOVIE_SUFFIXES: movies.append(file_entry.name) elif file_entry.suffix in SOUND_SUFFIXES: sounds.append(file_entry.name) EDITS.extend(self.get_items(items=edits)) MOVIES.extend(self.get_items(items=movies)) SOUNDS.extend(self.get_items(items=sounds)) return {'FINISHED'} # class VSETB_OT_select_file(Operator): # bl_idname = "import.select_file" # bl_label = "Select File" # bl_description = "Select Active as File" # bl_options = {"REGISTER", "UNDO"} # type : StringProperty('') # @classmethod # def poll(cls, context): # return True # def execute(self, context): # params = context.space_data.params # print(params.filename) # if self.type == 'edit': # bpy.ops.sequencer.import_files(edit=params.filename) # elif self.type == 'movie': # bpy.ops.sequencer.import_files(movie=params.filename) # elif self.type == 'sound': # bpy.ops.sequencer.import_files(sound=params.filename) # return {'FINISHED'} class VSETB_OT_import_files(Operator): bl_idname = "sequencer.import_files" bl_label = "Import" bl_description = "Import Edit" bl_options = {"REGISTER", "UNDO"} directory : StringProperty(subtype='DIR_PATH') filepath: StringProperty( name="File Path", description="Filepath used for importing the file", maxlen=1024, subtype='FILE_PATH', ) files : CollectionProperty(type=bpy.types.OperatorFileListElement) clean_sequencer : BoolProperty( name="Clean Sequencer", default=False, description="Clean all existing strips in sequencer", ) import_edit : BoolProperty(name='', default=True) edit: EnumProperty(name='', items=lambda s, c: EDITS) import_movie : BoolProperty(name='', default=False) movie: EnumProperty(name='', items=lambda s, c: MOVIES) import_sound : BoolProperty(name='', default=False) sound: EnumProperty(name='', items=lambda s, c: SOUNDS) @classmethod def poll(cls, context): return True def draw(self, context): scn = context.scene settings = get_settings() layout = self.layout layout.use_property_split = True layout.use_property_decorate = False col = layout.column(align=True) col.operator('import.auto_select_files', text='Auto Select') row = self.layout.row(heading="Import Edit", align=True) row.prop(self, 'import_edit') sub = row.row(align=True) sub.active = self.import_edit sub.prop(self, 'edit') # op = sub.operator('import.select_file', text="", icon='EYEDROPPER') # op.type = 'edit' row = self.layout.row(heading="Import Movie", align=True) row.prop(self, 'import_movie') sub = row.row() sub.active = self.import_movie sub.prop(self, 'movie') # op = sub.operator('import.select_file', text="", icon='EYEDROPPER') # op.type = 'movie' row = self.layout.row(heading="Import Sound", align=True) row.prop(self, 'import_sound') sub = row.row() sub.active = self.import_sound sub.prop(self, 'sound') # op = sub.operator('import.select_file', text="", icon='EYEDROPPER') # op.type = 'sound' col = layout.column() col.separator() col.prop(self, 'clean_sequencer') def invoke(self, context, event): context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} def execute(self, context): otio = install_module('opentimelineio') edit_filepath = Path(self.directory, self.edit) if not edit_filepath.exists(): self.import_edit = False movie_filepath = Path(self.directory, self.movie) if not movie_filepath.exists(): self.import_movie = False sound_filepath = Path(self.directory, self.sound) if not sound_filepath.exists(): self.import_sound = False if self.clean_sequencer: clean_sequencer( edit=self.import_edit, movie=self.import_movie, sound=self.import_sound, ) if self.import_edit: print(f'[>.] Loading Edit from: {str(edit_filepath)}') import_edit(edit_filepath, adapter="cmx_3600") if self.import_movie: print(f'[>.] Loading Movie from: {str(movie_filepath)}') import_movie(movie_filepath) if self.import_sound: print(f'[>.] Loading Audio from: {str(sound_filepath)}') import_sound(sound_filepath) elif not self.import_sound and self.import_movie: print(f'[>.] Loading Audio from Movie: {str(movie_filepath)}') import_sound(movie_filepath) context.scene.sequence_editor.sequences.update() return {"FINISHED"} class VSETB_OT_load_assets(Operator): bl_idname = "vse_toolbox.load_assets" bl_label = "Load Assets for current projects" bl_description = "Load Assets" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): settings = get_settings() if settings.active_project: return True def get_items(self, items=[]): if not items: return [('NONE', 'None', '', 0)] icons = { 'camera':'CAMERA_DATA', 'chars':'COMMUNITY', 'props':'OBJECT_DATAMODE', 'sets':'SMOOTHCURVE', } # return [(e.name, e.norm_name, '', i) for i, e in enumerate(items)] return [(e.name, e.name, '', icons[e.asset_type], i) for i, e in enumerate(items)] def execute(self, context): settings = get_settings() prefs = get_addon_prefs() tracker = prefs.tracker ASSETS.clear() settings.active_project.assets.clear() project = settings.active_project assets = tracker.get_assets(project['id']) if not assets: self.report({'ERROR'}, f'No Assets found for {project.name}.') for asset_data in assets: asset = project.assets.add() asset.name = asset_data['name'] # asset.norm_name = norm_name(asset_data['name'], separator=' ', format=str.title) asset.id = asset_data['id'] asset.asset_type = asset_data['asset_type'] ASSETS.extend(self.get_items(items=project.assets)) self.report({'INFO'}, f'Assets for {project.name} successfully loaded') return {"FINISHED"} class VSETB_OT_load_projects(Operator): bl_idname = "vse_toolbox.load_projects" bl_label = "Load Projects" bl_description = "Load Projects" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return True def execute(self, context): settings = get_settings() prefs = get_addon_prefs() tracker = prefs.tracker settings.projects.clear() tracker.connect() for project_data in tracker.get_projects(): project = settings.projects.add() project.name = project_data['name'] project.id = project_data['id'] for episode_data in tracker.get_episodes(project_data): episode = project.episodes.add() episode.name = episode_data['name'] episode.id = episode_data['id'] # for asset_data in tracker.get_assets(project_data): # asset = project.assets.add() # asset.name = asset_data['name'] # asset.id = asset_data['id'] # asset.asset_type = asset_data['asset_type'] return {'FINISHED'} class VSETB_OT_new_episode(Operator): bl_idname = "vse_toolbox.new_episode" bl_label = "New Epispde" bl_description = "Add new Episode to Project" bl_options = {"REGISTER", "UNDO"} episode_name : StringProperty(name="Episode Name", default="") @classmethod def poll(cls, context): return True def invoke(self, context, event): scn = context.scene settings = get_settings() return context.window_manager.invoke_props_dialog(self) def execute(self, context): settings = get_settings() prefs = get_addon_prefs() tracker = prefs.tracker episode_name = settings.episode_template.format(index=int(self.episode_name)) print(self.episode_name) print('episode_name: ', episode_name) episode = tracker.get_episode(episode_name) if episode: self.report({'ERROR'}, f'Episode {episode_name} already exists') return {"CANCELLED"} tracker.new_episode(episode_name) # tracker.get_episodes tracker.update_project() self.report({'INFO'}, f'Episode {episode_name} successfully created') return {'FINISHED'} class VSETB_OT_reload_addon(Operator): bl_idname = "vse_toolbox.reload_addon" bl_options = {"UNDO"} bl_label = 'Reload VSE ToolBox Addon' bl_description = 'Reload The VSE ToolBox Addon' def execute(self, context): print('Execute reload') vse_toolbox.unregister() importlib.reload(vse_toolbox) vse_toolbox.register() return {'FINISHED'} class VSETB_OT_rename(Operator): bl_idname = "sequencer.strips_rename" bl_label = "Rename Strips" bl_description = "Rename Strips" bl_options = {"REGISTER", "UNDO"} template : StringProperty(name="Strip Template Name", default="") increment : IntProperty(name="Name Increment", default=0) channel_name : StringProperty(name="Channel Name", default="") selected_only : BoolProperty(name="Selected Only", default=False) start_number : IntProperty(name="Start Number", default=0, min=0) by_sequence : BoolProperty( name="Reset By Sequence", description="Reset Start Number for each sequence", default=False ) @classmethod def poll(cls, context): return True def invoke(self, context, event): scn = context.scene settings = get_settings() return context.window_manager.invoke_props_dialog(self) def draw(self, context): scn = context.scene settings = get_settings() layout = self.layout col = layout.column() col.use_property_split = True col.prop(self, 'template') col.prop(self, 'start_number') if self.channel_name == 'Shots': col.prop(self, 'by_sequence') col.prop(self, 'selected_only') def execute(self, context): scn = context.scene strips = get_strips(channel=self.channel_name, selected_only=self.selected_only) rename_strips( strips, self.template, increment=self.increment, start_number=self.start_number, by_sequence=self.by_sequence ) return {"FINISHED"} class VSETB_OT_render(Operator): bl_idname = "sequencer.strips_render" bl_label = "Render Shots Strips" bl_description = "Render Shots Strips" bl_options = {"REGISTER", "UNDO"} selected_only : BoolProperty(name="Selected Only", default=False) @classmethod def poll(cls, context): return True def invoke(self, context, event): scn = context.scene settings = get_settings() return context.window_manager.invoke_props_dialog(self) def draw(self, context): scn = context.scene settings = get_settings() layout = self.layout col = layout.column() col.use_property_split = True col.use_property_decorate = False col.prop(settings, 'channel', text='Channel') col.prop(self, 'selected_only') def execute(self, context): scn = context.scene settings = get_settings() strips = get_strips(channel=settings.channel, selected_only=self.selected_only) render_strips(strips) return {"FINISHED"} class VSETB_OT_set_scene(Operator): bl_idname = "vse_toolbox.set_scene" bl_label = "Set Scene" bl_description = "Set Scene for Breakdown" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return True def execute(self, context): scn = context.scene set_channels() movie = get_strips(channel='Movie') if movie: movie = movie[0] movie.transform.scale_x = movie.transform.scale_y = 1 elem = movie.strip_elem_from_frame(scn.frame_current) scn.render.resolution_x = elem.orig_width scn.render.resolution_y = elem.orig_height else: self.report({'INFO'}, f'Cannot set Resolution. No Movie Found.') return {"FINISHED"} class VSETB_OT_casting_add(Operator): bl_idname = "vse_toolbox.casting_add" bl_label = "Casting Add" bl_description = "Add Asset to Castin" bl_options = {"REGISTER", "UNDO"} bl_property = "asset_name" asset_name : EnumProperty(name='', items=lambda s, c: ASSETS) @classmethod def poll(cls, context): active_strip = context.scene.sequence_editor.active_strip if active_strip: return True def invoke(self, context, event): context.window_manager.invoke_search_popup(self) return {"FINISHED"} def execute(self, context): scn = context.scene settings = get_settings() active_strip = scn.sequence_editor.active_strip project = settings.active_project if active_strip.casting.get(self.asset_name): self.report({'WARNING'}, f"Asset {self.asset_name} already casted.") return {"CANCELLED"} item = active_strip.casting.add() item.name = self.asset_name item.asset_type = project.assets[self.asset_name].asset_type active_strip.casting.update() return {"FINISHED"} class VSETB_OT_casting_actions(Operator): bl_idname = "vse_toolbox.casting_actions" bl_label = "Casting Actions" bl_description = "Actions to Add, Remove, Move casting items" bl_options = {"REGISTER", "UNDO"} action: EnumProperty( items=( ('UP', "Up", ""), ('DOWN', "Down", ""), ('REMOVE', "Remove", ""), ) ) asset_name : StringProperty() @classmethod def poll(cls, context): active_strip = context.scene.sequence_editor.active_strip if active_strip: return True def invoke(self, context, event): scn = context.scene active_strip = scn.sequence_editor.active_strip idx = active_strip.casting_index try: item = active_strip.casting[idx] except IndexError: pass else: if self.action == 'DOWN' and idx < len(active_strip.casting) - 1: item_next = active_strip.casting[idx+1].name active_strip.casting.move(idx, idx+1) active_strip.casting_index += 1 info = f"Item {item.name} moved to position {(item.name, active_strip.casting_index + 1)}" self.report({'INFO'}, info) elif self.action == 'UP' and idx >= 1: item_prev = active_strip.casting[idx-1].name active_strip.casting.move(idx, idx-1) active_strip.casting_index -= 1 info = f"Item {item.name} moved to position {(item.name, active_strip.casting_index + 1)}" self.report({'INFO'}, info) elif self.action == 'REMOVE': item = active_strip.casting[active_strip.casting_index] active_strip.casting.remove(idx) if active_strip.casting_index == 0: active_strip.casting_index = 0 else: active_strip.casting_index -= 1 info = f"Item {item.name} removed from casting" self.report({'INFO'}, info) return {"FINISHED"} classes=( VSETB_OT_auto_select_files, VSETB_OT_casting_add, VSETB_OT_casting_actions, VSETB_OT_export_csv, VSETB_OT_import_files, VSETB_OT_load_assets, VSETB_OT_load_projects, VSETB_OT_new_episode, VSETB_OT_reload_addon, VSETB_OT_rename, VSETB_OT_render, # VSETB_OT_select_file, VSETB_OT_set_scene, ) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls)