# SPDX-License-Identifier: GPL-2.0-or-later """ Pose Library - operators. """ from math import radians from mathutils import Vector from pathlib import Path from tempfile import gettempdir from typing import Optional, Set import bpy import json import os import re import shutil import subprocess import uuid import time from pathlib import Path from functools import partial from asset_library.pose.pose_creation import( create_pose_asset_from_context, assign_from_asset_browser ) from asset_library.pose.pose_usage import( select_bones, flip_side_name ) from asset_library.action.functions import( apply_anim, append_action, clean_action, reset_bone, is_asset_action, conform_action ) from bpy.props import ( BoolProperty, CollectionProperty, EnumProperty, PointerProperty, StringProperty, IntProperty ) from bpy.types import ( Action, Context, Event, FileSelectEntry, Object, Operator, PropertyGroup, ) from bpy_extras import asset_utils from bpy_extras.io_utils import ExportHelper, ImportHelper from asset_library.action.functions import ( is_pose, get_marker, get_keyframes, ) from asset_library.common.functions import ( #get_actionlib_dir, #get_asset_source, #get_catalog_path, #read_catalog, #set_actionlib_dir, #resync_lib, get_active_library, get_active_catalog, asset_warning_callback ) from asset_library.common.bl_utils import ( area_from_context, attr_set, get_addon_prefs, copy_frames, split_path, get_preview, get_view3d_persp, load_assets_from, get_asset_space_params, get_bl_cmd, get_overriden_col ) from asset_library.common.file_utils import ( remove_version, synchronize, open_file, copy_dir, ) class ACTIONLIB_OT_restore_previous_action(Operator): bl_idname = "actionlib.restore_previous_action" bl_label = "Restore Previous Action" bl_description = "Switch back to the previous Action, after creating a pose asset" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context: Context) -> bool: return bool( context.scene.actionlib.previous_action and context.object and context.object.animation_data and context.object.animation_data.action and context.object.animation_data.action.asset_data is not None ) def execute(self, context: Context) -> Set[str]: # This is the Action that was just created with "Create Pose Asset". # It has to be re-applied after switching to the previous action, # to ensure the character keeps the same pose. self.pose_action = context.object.animation_data.action prev_action = context.scene.actionlib.previous_action context.object.animation_data.action = prev_action context.scene.actionlib.previous_action = None # Wait a bit for the action assignment to be handled, before applying the pose. wm = context.window_manager self._timer = wm.event_timer_add(0.001, window=context.window) wm.modal_handler_add(self) return {'RUNNING_MODAL'} def modal(self, context, event): if event.type != 'TIMER': return {'RUNNING_MODAL'} wm = context.window_manager wm.event_timer_remove(self._timer) context.object.pose.apply_pose_from_action(self.pose_action) return {'FINISHED'} class ACTIONLIB_OT_assign_action(Operator): bl_idname = "actionlib.assign_action" bl_label = "Assign Action" bl_description = "Set this pose Action as active Action on the active Object" bl_options = {"REGISTER", "UNDO"} assign: BoolProperty(name="Assign", default=True) # type: ignore def execute(self, context: Context) -> Set[str]: if self.assign: context.object.animation_data_create().action = context.id else: if context.object.animation_data.action: context.object.animation_data.action = None return {"FINISHED"} class ACTIONLIB_OT_replace_pose(Operator): bl_idname = "actionlib.replace_pose" bl_options = {"REGISTER", "UNDO", "INTERNAL"} bl_label = 'Update Pose' bl_description = 'Update selected Pose. ! Works only on Pose, not Anim !' @classmethod def poll(cls, context: Context) -> bool: # wm = context.window_manager # if context.active_file and wm.edit_pose: # if context.active_file.name == wm.edit_pose_action: # return True # else: # cls.poll_message_set(f"Current Action {context.id.name} different than Edit Action {wm.edit_pose_action}") # return False if context.mode == 'POSE' and context.area.ui_type == 'ASSETS' and context.active_file: return True def execute(self, context: Context) -> Set[str]: wm = context.window_manager active = context.active_file #print('active: ', active) select_bones( context.object, context.asset_file_handle.local_id, selected_side='BOTH', toggle=False) data = { 'name':active.name, 'catalog_id':active.asset_data.catalog_id, } data.update(dict(active.asset_data)) # if 'camera' in asset_data.keys(): # data.update({'camera':active.asset_data['camera']}) # if 'is_single_frame' in asset_data.keys(): # data.update({'is_single_frame' : active.asset_data['is_single_frame']}) #print('data: ', data) action = create_pose_asset_from_context( context, data['name'], ) if not action: self.report( # type: ignore {"ERROR"}, f"Can't Update Current Pose", ) return {"CANCELLED"} bpy.ops.asset.clear() _old_action = bpy.data.actions[active.name] _old_action.use_fake_user = False bpy.data.actions.remove(_old_action) action.name = data['name'] action.asset_data.catalog_id = data['catalog_id'] for k, v in data.items(): if k in ('camera', 'is_single_frame'): action.asset_data[k] = v if not is_pose(action) and 'pose' in action.asset_data.tags.keys() and 'anim' not in action.asset_data.tags.keys(): for tag in action.asset_data.tags: if tag != 'pose': continue action.asset_data.tags.remove(tag) action.asset_data.tags.new('anim') return {'FINISHED'} class ACTIONLIB_OT_apply_anim(Operator): bl_idname = "actionlib.apply_anim" bl_options = {"REGISTER", "UNDO", "INTERNAL"} bl_label = 'Apply Anim' bl_description = 'Apply selected Anim to selected bones' @classmethod def poll(cls, context: Context) -> bool: return context.mode == 'POSE' def execute(self, context: Context) -> Set[str]: ob = context.object active_action = context.active_file to_remove = False prefs = get_addon_prefs() #params = get_asset_space_params(context.area) asset_library_ref = context.asset_library_ref if asset_library_ref == 'LOCAL': action = bpy.data.actions[active_action.name] to_remove = False else: asset_file_handle = bpy.context.asset_file_handle if asset_file_handle is None: return {'CANCELLED'} if asset_file_handle.local_id: return {'CANCELLED'} lib = get_active_library() if 'filepath' in asset_file_handle.asset_data: action_path = asset_file_handle.asset_data['filepath'] action_path = lib.adapter.format_path(action_path) else: action_path = bpy.types.AssetHandle.get_full_library_path( asset_file_handle, asset_library_ref ) if action_path and Path(action_path).exists(): action = append_action( action_path=action_path, action_name=active_action.name, ) to_remove = True else: self.report({"WARNING"}, f"Could not load action path {action_path} not exist") return {"CANCELLED"} bones = [b.name for b in context.selected_pose_bones_from_active_object] apply_anim(action, context.active_object, bones=bones) if to_remove: bpy.data.actions.remove(action) return {'FINISHED'} """ class ACTIONLIB_OT_publish(Operator): bl_idname = "actionlib.publish" bl_label = "Publish Library" bl_description = "Publish Pose Lib" #ExportHelper mixin class uses this filename_ext = "" use_filter_folder = True check_extension = None # filter_glob = StringProperty( # default="*.blend", # ) filepath: StringProperty( name="File Path", description="Filepath used for exporting the file", maxlen=1024, subtype='DIR_PATH', ) selected_actions : CollectionProperty(type=PropertyGroup) render_preview : BoolProperty(default=False) clean : BoolProperty(default=False) def invoke(self, context, _event): scn = context.scene filename = remove_version(bpy.data.filepath) filename = re.sub('_actionlib', '', filename) if scn.actionlib.publish_path: action_dir = Path(os.path.expandvars(scn.actionlib.publish_path)) if action_dir.is_file(): action_dir = action_dir.parent self.filepath = str(action_dir) else: self.filepath = str(Path(get_actionlib_dir()) / Path(filename).stem) context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} def execute(self, context: Context) -> Set[str]: scn = context.scene scn.actionlib.publish_path = self.filepath.replace( get_actionlib_dir(), '$ACTIONLIB_DIR' ) action_dir = Path(self.filepath) try: bpy.ops.asset.catalogs_save() except RuntimeError: pass actionlib_dir = get_actionlib_dir() current_catalog = get_catalog_path() current_catalog_data = read_catalog(current_catalog) catalog_global = get_catalog_path(actionlib_dir) catalog_data = read_catalog(catalog_global) for d in catalog_global.parent.rglob('*'): if not d.is_dir(): continue reldir = d.relative_to(actionlib_dir) if reldir not in catalog_data: catalog_data[reldir.as_posix()] = { 'id':str(uuid.uuid4()), 'name':'-'.join(reldir.parts) } catalog_path = action_dir.relative_to(actionlib_dir) for k, v in current_catalog_data.items(): v['name'] = '-'.join([*catalog_path.parts, v['name']]) catalog_data[(catalog_path / k).as_posix()] = v catalog_global_lines = ['VERSION 1', ''] for k, v in sorted(catalog_data.items()): if any(i in k for i in ('anim', 'pose', 'preview')): continue catalog_global_lines.append(':'.join([v['id'], k, v['name']])) catalog_global.write_text('\n'.join(catalog_global_lines)) if not self.selected_actions: render_actions_name = [a.name for a in bpy.data.actions if is_asset_action(a)] else: render_actions_name = [a.name for a in self.selected_actions] publish_actions_name = [] publish_actions = {} for a in bpy.data.actions: if is_asset_action(a): if not a.asset_data.catalog_id in publish_actions: publish_actions[a.asset_data.catalog_id] = set() publish_actions[a.asset_data.catalog_id].add(a) publish_actions_name.append(a.name) for cat_id, actions in publish_actions.items(): # Cleanup actions store_actions = actions.copy() for a in actions: conform_action(a) action_rel_path = next(k for k, v in current_catalog_data.items() if v['id']==cat_id) action_path = action_dir / action_rel_path action_path = action_path.parent / f'{action_dir.name}_{action_path.name}.blend' action_path.parent.mkdir(parents=True, exist_ok=True) print(f'Saving File to: {action_path}') bpy.data.libraries.write(str(action_path), store_actions, compress=True) # Render Previews _tmp_filepath = Path(gettempdir()) / Path(bpy.data.filepath).name print('_tmp_filepath: ', _tmp_filepath) bpy.ops.wm.save_as_mainfile(filepath=str(_tmp_filepath), copy=True, compress=True) if self.render_preview: print('[>-] Rendering Preview..') cmd = [ bpy.app.binary_path, '--no-window-focus', '--window-geometry', '5000', '0', '10', '10', str(_tmp_filepath), '--python-use-system-env', '--python', str(Path(__file__).parent / 'render_preview.py'), '--', '--directory', str(action_dir), '--asset-catalog', str(current_catalog), '--render-actions', *render_actions_name, '--publish-actions', *publish_actions_name, '--remove-folder', json.dumps(self.clean) ] print('cmd: ', cmd) subprocess.Popen(cmd) print('[>-] Publish Done.') return {"FINISHED"} """ class ACTIONLIB_OT_create_anim_asset(Operator): bl_idname = "actionlib.create_anim_asset" bl_label = "Create Anim Asset" bl_description = ( "Create a new Action that contains the anim of the selected bones, and mark it as Asset. " "The asset will be stored in the current blend file" ) bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context: Context) -> bool: if not context.object: return if not context.object.animation_data: cls.poll_message_set("Object has no Keyframes") return if not context.object.animation_data.action: cls.poll_message_set("Object has no Action") return # Make sure that if there is an asset browser open, the artist can see the newly created pose asset. asset_browse_area: Optional[bpy.types.Area] = area_from_context(context) if not asset_browse_area: # No asset browser is visible, so there also aren't any expectations # that this asset will be visible. return True params = get_asset_space_params(asset_browse_area) if params.asset_library_ref != 'LOCAL': cls.poll_message_set("Asset Browser must be set to the Current File library") return False return True def execute(self, context: Context) -> Set[str]: ### ADD ADM action = context.object.animation_data.action action.asset_mark() action.asset_generate_preview() data = action.asset_data #data.catalog_id = str(uuid.UUID(int=0)) asset_browse_area: Optional[bpy.types.Area] = area_from_context(context) asset_space_params = params(asset_browse_area) data.catalog_id = asset_space_params.catalog_id data_dict = dict( is_single_frame=False, camera= context.scene.camera.name if context.scene.camera else '', ) data.tags.new('anim') for k, v in data_dict.items(): data[k] = v ### return {'FINISHED'} class ACTIONLIB_OT_apply_selected_action(Operator): bl_idname = "actionlib.apply_selected_action" bl_options = {"REGISTER", "UNDO", "INTERNAL"} bl_label = 'Apply Pose/Anim' bl_description = 'Apply selected Action to selected bones' flipped: BoolProperty(name="Flipped", default=False) # type: ignore @classmethod def poll(cls, context: Context) -> bool: if context.mode == 'POSE' and context.area.ui_type == 'ASSETS': return True def execute(self, context: Context) -> Set[str]: active_action = context.active_file if 'pose' in active_action.asset_data.tags.keys(): bpy.ops.poselib.apply_pose_asset_for_keymap(flipped=self.flipped) else: bpy.ops.actionlib.apply_anim() return {'FINISHED'} class ACTIONLIB_OT_edit_action(Operator): bl_idname = "actionlib.edit_action" bl_options = {"REGISTER", "UNDO", "INTERNAL"} bl_label = 'Edit Action' bl_description = 'Assign active action and set Camera' @classmethod def poll(cls, context: Context) -> bool: if context.mode == 'POSE' and context.area.ui_type == 'ASSETS' and context.active_file: return True def execute(self, context: Context) -> Set[str]: scn = context.scene rest_pose = bpy.data.actions.get(context.id.asset_data.get('rest_pose', '')) context.object.animation_data_create() keyframes = get_keyframes(context.id) if not keyframes: self.report({'ERROR'}, f'No Keyframes found for {context.id.name}.') return scn.frame_set(keyframes[0]) context.object.animation_data.action = None for b in context.object.pose.bones: if re.match('^[A-Z]+\.', b.name): continue reset_bone(b) context.object.animation_data.action = rest_pose context.view_layer.update() context.object.animation_data.action = context.id if 'camera' in context.id.asset_data.keys(): scn.camera = bpy.data.objects[context.id.asset_data['camera']] return {"FINISHED"} class ACTIONLIB_OT_clear_action(Operator): bl_idname = "actionlib.clear_action" bl_options = {"REGISTER", "UNDO", "INTERNAL"} bl_label = 'Clear Action' bl_description = 'Assign active action and set Camera' @classmethod def poll(cls, context: Context) -> bool: if context.mode == 'POSE' and context.area.ui_type == 'ASSETS' and context.active_file: return True def execute(self, context: Context) -> Set[str]: wm = context.window_manager context.object.animation_data_create() context.object.animation_data.action = None context.id.asset_generate_preview() for a in context.screen.areas: if a.type == 'DOPESHEET_EDITOR' and a.ui_type == 'DOPESHEET': a.tag_redraw() return {"FINISHED"} class ACTIONLIB_OT_generate_preview(Operator): bl_idname = "actionlib.generate_preview" bl_options = {"REGISTER", "UNDO", "INTERNAL"} bl_label = 'Generate Preview' bl_description = 'Genreate Preview for Active Action' @classmethod def poll(cls, context: Context) -> bool: if context.object.type == 'ARMATURE' and context.mode == 'POSE': return True def execute(self, context: Context) -> Set[str]: _action = context.object.animation_data.action _camera = context.scene.camera context.object.animation_data_create() actions = context.selected_files + [_action] for a in context.selected_files: rest_pose = bpy.data.actions.get(a.asset_data.get('rest_pose', '')) if rest_pose: context.object.animation_data.action = rest_pose bpy.context.view_layer.update() else: context.object.animation_data.action = None for b in context.object.pose.bones: if re.match('^[A-Z]+\.', b.name): continue reset_bone(b) a_cam = bpy.data.objects.get(a.asset_data['camera']) if a_cam: context.scene.camera = a_cam bpy.context.view_layer.update() a.local_id.asset_generate_preview() context.object.animation_data.action = _action context.scene.camera = _camera bpy.context.view_layer.update() return {"FINISHED"} class ACTIONLIB_OT_update_action_data(Operator): bl_idname = "actionlib.update_action_data" bl_options = {"REGISTER", "INTERNAL"} bl_label = 'Udpate Action Data' bl_description = 'Update Action Metadata' tags: EnumProperty( name='Tags', items=( ('POSE', "pose", ""), ('ANIM', "anim", ""), ) ) use_tags: BoolProperty(default=False) use_camera: BoolProperty(default=False) use_rest_pose: BoolProperty(default=False) @classmethod def poll(cls, context: Context) -> bool: if context.id: return True def invoke(self, context, event): scn = context.scene scn.actionlib.camera = scn.camera return context.window_manager.invoke_props_dialog(self) def draw(self, context): layout = self.layout layout.use_property_split = True scn = context.scene heading = layout.column(align=True, heading="Tags") row = heading.row(align=True) row.prop(self, "use_tags", text="") sub = row.row() sub.active = self.use_tags sub.prop(self, "tags", text="") heading = layout.column(align=True, heading="Camera") row = heading.row(align=True) row.prop(self, "use_camera", text="") sub = row.row() sub.active = self.use_camera sub.prop(scn.actionlib, "camera", text="", icon='CAMERA_DATA') heading = layout.column(align=True, heading="Rest Pose") row = heading.row(align=True) row.prop(self, "use_rest_pose", text="") sub = row.row() sub.active = self.use_rest_pose sub.prop(scn.actionlib, "rest_pose", text="") def execute(self, context): scn = context.scene for action in context.selected_files: if self.use_camera: action.asset_data['camera'] = context.scene.actionlib.camera.name if self.use_tags: tag = self.tags.lower() not_tag = 'anim' if tag == 'pose' else 'pose' if tag not in action.asset_data.tags.keys(): if not_tag in action.asset_data.tags.keys(): for t in action.asset_data.tags: if t != not_tag: continue action.asset_data.tags.remove(t) action.asset_data.tags.new(tag) if 'pose' in action.asset_data.tags.keys(): action.asset_data['is_single_frame'] = True else: action.asset_data['is_single_frame'] = False if self.use_rest_pose: name = scn.actionlib.rest_pose.name if scn.actionlib.rest_pose else '' action.asset_data['rest_pose'] = name return {'FINISHED'} class ACTIONLIB_OT_assign_rest_pose(Operator): bl_idname = "actionlib.mark_as_rest_pose" bl_options = {"REGISTER", "UNDO", "INTERNAL"} bl_label = 'Mark As Rest Pose' bl_description = 'Mark Pose as Rest Pose' @classmethod def poll(cls, context: Context) -> bool: if context.area.ui_type == 'ASSETS': return True def execute(self, context: Context) -> Set[str]: active_action = context.active_file context.scene.actionlib.rest_pose = context.id print(f"'{active_action.name.title()}' marked as Rest Pose.") return {'FINISHED'} class ACTIONLIB_OT_open_blendfile(Operator): bl_idname = "actionlib.open_blendfile" bl_options = {"REGISTER", "UNDO", "INTERNAL"} bl_label = 'Set Paths' bl_description = 'Open Containing File' replace_local : BoolProperty(default=False) @classmethod def poll(cls, context): if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data): return False sp = context.space_data if sp.params.asset_library_ref == 'LOCAL': return False return True def execute(self, context: Context) -> Set[str]: asset_path = get_asset_source(replace_local=self.replace_local) cmd = get_bl_cmd( blender=bpy.app.binary_path, blendfile=str(asset_path), ) subprocess.Popen(cmd) return {'FINISHED'} #LIBRARY_ITEMS = [] class ACTIONLIB_OT_store_anim_pose(Operator): bl_idname = "actionlib.store_anim_pose" bl_label = "Add Action to the current library" bl_description = "Store current pose/anim to local library" #use_new_folder: BoolProperty(default=False) warning: StringProperty(name='') path: StringProperty(name='Path') catalog: StringProperty(name='Catalog', update=asset_warning_callback, options={'TEXTEDIT_UPDATE'}) name: StringProperty(name='Name', update=asset_warning_callback, options={'TEXTEDIT_UPDATE'}) action_type: EnumProperty(name='Type', items=[('POSE', 'Pose', ''), ('ANIMATION', 'Animation', '')]) frame_start: IntProperty(name="Frame Start") frame_end: IntProperty(name="Frame End") tags: StringProperty(name='Tags', description='Tags need to separate with a comma (,)') description: StringProperty(name='Description') #library: EnumProperty(items=lambda s, c: s.library_items, name="Library") #library: EnumProperty(items=lambda s, c: LIBRARY_ITEMS, name="Library") #CLIPBOARD_ASSET_MARKER = "ASSET-BLEND=" @classmethod def poll(cls, context: Context) -> bool: ob = context.object if not ob: cls.poll_message_set(f'You have no active object') return False if not ob.type == 'ARMATURE': cls.poll_message_set(f'Active object {ob.name} is not of type ARMATURE') return False if not ob.animation_data or not ob.animation_data.action: cls.poll_message_set(f'Active object {ob.name} has no action or keyframes') return False return True # def draw_tags(self, asset, layout): # row = layout.row() # row.template_list("ASSETBROWSER_UL_metadata_tags", "asset_tags", asset.asset_data, "tags", # asset.asset_data, "active_tag", rows=4) # col = row.column(align=True) # col.operator("asset.tag_add", icon='ADD', text="") # col.operator("asset.tag_remove", icon='REMOVE', text="") def draw(self, context): layout = self.layout layout.separator() prefs = get_addon_prefs() #row = layout.row(align=True) layout.use_property_split = True #layout.alignment = 'LEFT' #layout.ui_units_x = 50 if self.current_library.merge_libraries: layout.prop(self.current_library, 'store_library', expand=False) #row.label(text='Action Name') layout.prop(self, "action_type", text="Type", expand=True) if self.action_type == 'ANIMATION': col = layout.column(align=True) col.prop(self, "frame_start") col.prop(self, "frame_end", text='End') layout.prop(self, "catalog", text="Catalog") layout.prop(self, "name", text="Name") #layout.separator() #self.draw_tags(self.asset_action, layout) layout.prop(self, 'tags') layout.prop(self, 'description', text='Description') #layout.prop(prefs, 'author', text='Author') #layout.prop() layout.separator() col = layout.column() col.use_property_split = False #row.enabled = False if self.path: col.label(text=self.path) if self.warning: col.label(icon='ERROR', text=self.warning) def set_action_type(self): ob = bpy.context.object action = ob.animation_data.action bones = [b.name for b in bpy.context.selected_pose_bones_from_active_object] keyframes_selected = get_keyframes(action, selected=True, includes=bones) self.frame_start = min(keyframes_selected) self.frame_end = max(keyframes_selected) self.action_type = 'POSE' if (self.frame_start != self.frame_end): self.action_type = 'ANIMATION' def invoke(self, context, event): prefs = get_addon_prefs() ob = context.object self.asset_action = ob.animation_data.action.copy() self.asset_action.asset_mark() self.area = context.area self.current_library = get_active_library() #self.sce #lib = self.current_library self.tags = '' #print(self, self.library_items) self.catalog = get_active_catalog() self.set_action_type() return context.window_manager.invoke_props_dialog(self, width=450) def action_to_asset(self, action): #action.asset_mark() prefs = get_addon_prefs() action.name = self.name action.asset_generate_preview() # Remove Keyframes bones = [b.name for b in bpy.context.selected_pose_bones_from_active_object] clean_action( action=action, frame_start=self.frame_start, frame_end=self.frame_end, excludes=['world', 'walk'], includes=bones, ) ## Define Tags tags = [t.strip() for t in self.tags.split(',')] tag_range = f'f{self.frame_start}' is_single_frame = True if self.action_type == 'ANIM': is_single_frame = False tag_range = f'f{self.frame_start}-f{self.frame_end}' tags.append(self.action_type) tags.append(tag_range) for tag in tags: action.asset_data.tags.new(tag) action.asset_data['is_single_frame'] = is_single_frame action.asset_data['rig'] = bpy.context.object.name action.asset_data.description = self.description action.asset_data.author = prefs.author col = get_overriden_col(bpy.context.object) if col: action.asset_data['col'] = col.name return action def render_preview(self, image_path, video_path): ctx = bpy.context scn = ctx.scene vl = ctx.view_layer area = get_view3d_persp() space = area.spaces.active preview_attrs = [ (scn, 'use_preview_range', True), (scn, 'frame_preview_start', self.frame_start), (scn, 'frame_preview_end', self.frame_end), (scn.render, 'resolution_percentage', 100), (space.overlay, 'show_overlays', False), (space.region_3d, 'view_perspective', 'CAMERA'), ] image_attrs = [ (scn.render, 'resolution_x', 512), (scn.render, 'resolution_y', 512), (scn.render, 'film_transparent', True), (scn.render.image_settings, 'file_format', 'PNG'), (scn.render.image_settings, 'color_mode', 'RGBA'), (scn.render.image_settings, 'color_depth', 8), (scn.render, 'use_overwrite', True), (scn.render, 'filepath', str(image_path)) ] video_attrs = [ (scn.render, 'resolution_x', 1280), (scn.render, 'resolution_y', 720), (scn.render.image_settings, 'file_format', 'FFMPEG'), (scn.render.ffmpeg, 'format', 'QUICKTIME'), (scn.render.ffmpeg, 'codec', 'H264'), (scn.render.ffmpeg, 'ffmpeg_preset', 'GOOD'), (scn.render.ffmpeg, 'constant_rate_factor', 'HIGH'), (scn.render.ffmpeg, 'gopsize', 12), (scn.render, 'filepath', str(video_path)), ] with attr_set(preview_attrs+image_attrs): with ctx.temp_override(area=area): bpy.ops.render.opengl(write_still=True) if self.action_type == "ANIMATION": with attr_set(preview_attrs+video_attrs): with ctx.temp_override(area=area): bpy.ops.render.opengl(animation=True) def save_pose_preview(self): pass def refresh(self, area): bpy.ops.asset.library_refresh({"area": area, 'region': area.regions[3]}) #space_data.activate_asset_by_id(asset, deferred=deferred) def execute(self, context: Context): scn = context.scene vl = context.view_layer ob = context.object prefs = get_addon_prefs() lib = self.current_library if lib.merge_libraries: lib = prefs.libraries[self.current_library.store_library] #lib_path = lib.library_path #name = lib.adapter.norm_file_name(self.name) asset_path = lib.adapter.get_asset_path(name=self.name, catalog=self.catalog) img_path = lib.adapter.get_image_path(name=self.name, catalog=self.catalog, filepath=asset_path) video_path = lib.adapter.get_video_path(name=self.name, catalog=self.catalog, filepath=asset_path) ## Copy Action current_action = ob.animation_data.action asset_action = self.asset_action ob.animation_data.action = asset_action vl.update() self.action_to_asset(asset_action) #Saving the preview self.render_preview(img_path, video_path) with context.temp_override(id=asset_action): bpy.ops.ed.lib_id_load_custom_preview( filepath=str(img_path) ) lib.adapter.write_asset(asset=asset_action, asset_path=asset_path) # asset_description = lib.adapter.get_asset_description(dict( # author=prefs.author, # assets=[dict( # name=asset_action.name, # description=self.description, # catalog=self.catalog, # tags=self.tags.split(',') # )]), asset_path # ) # lib.adapter.write_description_file(asset_description, asset_path) # Restore action and cleanup ob.animation_data.action = current_action asset_action.asset_clear() asset_action.use_fake_user = False bpy.data.actions.remove(asset_action) # TODO Write a proper method for this diff_path = Path(bpy.app.tempdir, 'diff.json') diff = [dict(a, operation='ADD') for a in lib.adapter.norm_cache([asset_description])] diff_path.write_text(json.dumps(diff, indent=4)) bpy.ops.assetlib.bundle(name=lib.name, diff=str(diff_path), blocking=True) #self.area.tag_redraw() self.report({'INFO'}, f'"{self.name}" has been added to the library.') return {"FINISHED"} classes = ( ACTIONLIB_OT_assign_action, ACTIONLIB_OT_restore_previous_action, #ACTIONLIB_OT_publish, ACTIONLIB_OT_apply_anim, ACTIONLIB_OT_replace_pose, ACTIONLIB_OT_create_anim_asset, ACTIONLIB_OT_apply_selected_action, ACTIONLIB_OT_edit_action, ACTIONLIB_OT_clear_action, ACTIONLIB_OT_generate_preview, ACTIONLIB_OT_update_action_data, ACTIONLIB_OT_assign_rest_pose, ACTIONLIB_OT_store_anim_pose, ) register, unregister = bpy.utils.register_classes_factory(classes)