First Commit
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
|
||||
from asset_library.action import (
|
||||
gui,
|
||||
keymaps,
|
||||
clear_asset,
|
||||
concat_preview,
|
||||
operators,
|
||||
properties,
|
||||
rename_pose,
|
||||
render_preview,)
|
||||
|
||||
if 'bpy' in locals():
|
||||
import importlib
|
||||
|
||||
importlib.reload(gui)
|
||||
importlib.reload(keymaps)
|
||||
importlib.reload(clear_asset)
|
||||
importlib.reload(concat_preview)
|
||||
importlib.reload(operators)
|
||||
importlib.reload(properties)
|
||||
importlib.reload(rename_pose)
|
||||
importlib.reload(render_preview)
|
||||
|
||||
def register():
|
||||
operators.register()
|
||||
keymaps.register()
|
||||
|
||||
def unregister():
|
||||
operators.unregister()
|
||||
keymaps.unregister()
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
import argparse
|
||||
import bpy
|
||||
import json
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
sys.path.append(Path(__file__).parents[3])
|
||||
from asset_library.common.bl_utils import (
|
||||
get_preview,
|
||||
)
|
||||
|
||||
def clear_asset(action_name='', use_fake_user=False):
|
||||
|
||||
scn = bpy.context.scene
|
||||
|
||||
action = bpy.data.actions.get(action_name)
|
||||
if not action:
|
||||
print(f'No {action_name} not found.')
|
||||
bpy.ops.wm.quit_blender()
|
||||
|
||||
action.asset_clear()
|
||||
if use_fake_user:
|
||||
action.use_fake_user = True
|
||||
else:
|
||||
preview = get_preview(asset_path=bpy.data.filepath, asset_name=action_name)
|
||||
if preview:
|
||||
preview.unlink()
|
||||
bpy.data.actions.remove(action)
|
||||
|
||||
bpy.ops.wm.save_mainfile(
|
||||
filepath=bpy.data.filepath, compress=True, exit=True
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__' :
|
||||
parser = argparse.ArgumentParser(description='Add Comment To the tracker',
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument('--action-name')
|
||||
parser.add_argument('--use-fake-user', type=json.loads, default='false')
|
||||
|
||||
if '--' in sys.argv :
|
||||
index = sys.argv.index('--')
|
||||
sys.argv = [sys.argv[index-1], *sys.argv[index+1:]]
|
||||
|
||||
args = parser.parse_args()
|
||||
clear_asset(**vars(args))
|
||||
@@ -0,0 +1,146 @@
|
||||
|
||||
import bpy
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def alpha_to_color(pixels_data, color):
|
||||
"""Convert Alpha to WhiteBG"""
|
||||
new_pixels_data = []
|
||||
for i in pixels_data:
|
||||
height, width, array_d = i.shape
|
||||
mask = i[:,:,3:]
|
||||
background = np.array([color[0], color[1], color[2] ,1], dtype=np.float32)
|
||||
background = np.tile(background, (height*width))
|
||||
background = np.reshape(background, (height,width,4))
|
||||
new_pixels_data.append(i * mask + background * (1 - mask))
|
||||
# print(new_pixels_data)#Dbg
|
||||
return new_pixels_data
|
||||
|
||||
def create_array(height, width):
|
||||
return np.zeros((height*width*4), dtype=np.float32)
|
||||
|
||||
def read_pixels_data(img, source_height, source_width):
|
||||
img_w, img_h = img.size
|
||||
|
||||
if img_w != source_width :
|
||||
scale = abs(img_w/source_width)
|
||||
img.scale(int(img_w/scale), int(img_h/scale))
|
||||
img_w, img_h = img.size
|
||||
|
||||
array = create_array(img_h, img_w)
|
||||
img.pixels.foreach_get(array)
|
||||
array = array.reshape(img_h, img_w, 4)
|
||||
|
||||
if array.shape[0] != source_height:
|
||||
#print('ARRAY SHAPE', array.shape[:], source_height)
|
||||
missing_height = int(abs(source_height-img_h)/2)
|
||||
empty_array = create_array(missing_height, source_width)
|
||||
empty_array = empty_array.reshape(missing_height, source_width, 4)
|
||||
array = np.vstack((empty_array, array, empty_array))
|
||||
|
||||
return array.reshape(source_height, source_width, 4)
|
||||
|
||||
def create_final(output_name, pixels_data, final_height, final_width):
|
||||
#print('output_name: ', output_name)
|
||||
|
||||
new_img = bpy.data.images.get(output_name)
|
||||
if new_img:
|
||||
bpy.data.images.remove(new_img)
|
||||
|
||||
new_img = bpy.data.images.new(output_name, final_width, final_height)
|
||||
new_img.generated_color=(0,0,0,0)
|
||||
|
||||
#print('pixels_data: ', pixels_data)
|
||||
new_img.pixels.foreach_set(pixels_data)
|
||||
|
||||
return new_img
|
||||
|
||||
def guess_input_format(img_list):
|
||||
for i in img_list:
|
||||
if i.size[0] == i.size[1]:
|
||||
return i.size
|
||||
|
||||
def format_files(files, catalog_data):
|
||||
img_dict = {}
|
||||
for k, v in catalog_data.items():
|
||||
if '/' not in k:
|
||||
continue
|
||||
img_dict[v['name']] = [f for f in files if v['name'] in f]
|
||||
|
||||
return img_dict
|
||||
|
||||
def mosaic_export(
|
||||
files, catalog_data, row=2, columns=2, auto_calculate=True,
|
||||
bg_color=(0.18, 0.18, 0.18,), resize_output=100,
|
||||
):
|
||||
|
||||
img_dict = format_files(files, catalog_data)
|
||||
|
||||
for cat, files_list in img_dict.items():
|
||||
|
||||
if not files_list:
|
||||
continue
|
||||
|
||||
for i in bpy.data.images:
|
||||
bpy.data.images.remove(i)
|
||||
|
||||
img_list = []
|
||||
|
||||
chars = Path(files_list[0]).parts[-4]
|
||||
output_dir = str(Path(files_list[0]).parent.parent)
|
||||
|
||||
ext = 'jpg'
|
||||
output_name = f'{chars}_{cat}.{ext}'
|
||||
|
||||
for img in files_list:
|
||||
img_list.append(bpy.data.images.load(img, check_existing=True))
|
||||
|
||||
for i in img_list:
|
||||
i.colorspace_settings.name = 'Raw'
|
||||
|
||||
if auto_calculate:
|
||||
rows = int(math.sqrt(len(img_list)))
|
||||
columns = math.ceil(len(img_list)/rows)
|
||||
|
||||
if rows*columns < len(img_list):
|
||||
raise AttributeError('Grid too small for number of images')
|
||||
|
||||
src_w, src_h = img_list[0].size
|
||||
final_w = src_w * columns
|
||||
final_h = src_h * rows
|
||||
|
||||
img_pixels = [read_pixels_data(img, src_h, src_w) for img in img_list]
|
||||
|
||||
#Check if there is enough "data" to create an horizontal stack
|
||||
##It not, create empty array
|
||||
h_stack = []
|
||||
total_len = rows*columns
|
||||
if len(img_pixels) < total_len:
|
||||
for i in range(total_len-len(img_pixels)):
|
||||
img_pixels.append(create_array(src_h, src_w).reshape(src_h, src_w, 4))
|
||||
|
||||
img_pixels = alpha_to_color(img_pixels, bg_color)
|
||||
for i in range(0,len(img_pixels),columns):
|
||||
h_stack.append(np.hstack(img_pixels[i:i+columns]))
|
||||
if rows > 1:
|
||||
combined_stack = np.vstack(h_stack[::-1])
|
||||
else:
|
||||
combined_stack = np.hstack((h_stack[:]))
|
||||
|
||||
combined_img = create_final(output_name, combined_stack.flatten(), final_h, final_w)
|
||||
|
||||
if resize_output != 100:
|
||||
w, h = combined_img.size
|
||||
combined_img.scale(w*(resize_output*.01), h*(resize_output*.01))
|
||||
|
||||
|
||||
combined_img.filepath_raw = '/'.join([output_dir, output_name])
|
||||
combined_img.file_format = 'JPEG'
|
||||
combined_img.save()
|
||||
|
||||
print(f"""
|
||||
Image saved: {combined_img.filepath_raw}
|
||||
""")
|
||||
@@ -0,0 +1,207 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
"""
|
||||
Functions related to anim and pose.
|
||||
"""
|
||||
|
||||
from collections.abc import Collection
|
||||
|
||||
from typing import Optional, FrozenSet, Set, Union, Iterable, cast
|
||||
import dataclasses
|
||||
import functools
|
||||
import re
|
||||
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
Action,
|
||||
Bone,
|
||||
Context,
|
||||
FCurve,
|
||||
Keyframe,
|
||||
Object,
|
||||
TimelineMarker
|
||||
)
|
||||
|
||||
from asset_library.common.bl_utils import active_catalog_id, split_path
|
||||
|
||||
FCurveValue = Union[float, int]
|
||||
|
||||
pose_bone_re = re.compile(r'pose.bones\["([^"]+)"\]')
|
||||
"""RegExp for matching FCurve data paths."""
|
||||
|
||||
def is_pose(action):
|
||||
for fc in action.fcurves:
|
||||
if len(fc.keyframe_points) > 1:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_bone_visibility(data_path):
|
||||
bone, prop = split_path(data_path)
|
||||
|
||||
ob = bpy.context.object
|
||||
b_layers = [i for i, val in enumerate(ob.pose.bones[bone].bone.layers) if val]
|
||||
|
||||
rig_layers = [(i, val) for i, val in enumerate(ob.data.layers)]
|
||||
|
||||
return ob.data.layers[b_layers[0]]
|
||||
|
||||
def get_keyframes(action, selected=False, includes=[]):
|
||||
if selected:
|
||||
# keyframes = sorted([int(k.co[0]) for f in action.fcurves for k in f.keyframe_points if k.select_control_point and get_bone_visibility(f.data_path)])
|
||||
keyframes = []
|
||||
for f in action.fcurves:
|
||||
bone, prop = split_path(f.data_path)
|
||||
for k in f.keyframe_points:
|
||||
if bone not in includes:
|
||||
continue
|
||||
if not k.select_control_point:
|
||||
continue
|
||||
if not get_bone_visibility(f.data_path):
|
||||
continue
|
||||
|
||||
keyframes += [int(k.co[0])]
|
||||
if len(keyframes) <= 1:
|
||||
keyframes = [bpy.context.scene.frame_current]
|
||||
else:
|
||||
keyframes = sorted([int(k.co[0]) for f in action.fcurves for k in f.keyframe_points])
|
||||
|
||||
return keyframes
|
||||
|
||||
def get_marker(action):
|
||||
if action.pose_markers:
|
||||
markers = action.pose_markers
|
||||
return next((m.name for m in markers if m.frame == bpy.context.scene.frame_current), None)
|
||||
|
||||
def reset_bone(bone, transform=True, custom_props=True):
|
||||
if transform:
|
||||
bone.location = (0, 0, 0)
|
||||
if bone.rotation_mode == "QUATERNION":
|
||||
bone.rotation_quaternion = (0, 0, 0, 0)
|
||||
elif bone.rotation_mode == "AXIS_ANGLE":
|
||||
bone.rotation_axis_angle = (0, 0, 0, 0)
|
||||
else:
|
||||
bone.rotation_euler = (0, 0, 0)
|
||||
|
||||
bone.scale = (1, 1, 1)
|
||||
|
||||
if custom_props:
|
||||
for key, value in bone.items():
|
||||
try:
|
||||
id_prop = bone.id_properties_ui(key)
|
||||
except TypeError:
|
||||
continue
|
||||
|
||||
if not isinstance(value, (int, float)) or not id_prop:
|
||||
continue
|
||||
bone[key] = id_prop.as_dict()['default']
|
||||
|
||||
def is_asset_action(action):
|
||||
return action.asset_data and action.asset_data.catalog_id != str(uuid.UUID(int=0))
|
||||
|
||||
def conform_action(action):
|
||||
tags = ('pose', 'anim')
|
||||
|
||||
if any(tag in action.asset_data.tags.keys() for tag in tags):
|
||||
return
|
||||
|
||||
for fc in action.fcurves:
|
||||
action.asset_data['is_single_frame'] = True
|
||||
if len(fc.keyframe_points) > 1:
|
||||
action.asset_data['is_single_frame'] = False
|
||||
break
|
||||
|
||||
if action.asset_data['is_single_frame']:
|
||||
action.asset_data.tags.new('pose')
|
||||
else:
|
||||
action.asset_data.tags.new('anim')
|
||||
|
||||
def clean_action(action='', frame_start=0, frame_end=0, excludes=[], includes=[]):
|
||||
## Clean Keyframe Before/After Range
|
||||
for fc in action.fcurves:
|
||||
bone, prop = split_path(fc.data_path)
|
||||
# !! Mush Mush dependent. Need to be fix
|
||||
if bone in excludes or bone not in includes:
|
||||
action.fcurves.remove(fc)
|
||||
continue
|
||||
|
||||
# Add Keyframe At Start/End Range
|
||||
for fr in (frame_start, frame_end):
|
||||
fc_val = fc.evaluate(fr)
|
||||
fc.keyframe_points.insert(frame=fr, value=fc_val)
|
||||
fc.update()
|
||||
|
||||
# Remove Keyframe out of range
|
||||
for k in reversed(fc.keyframe_points):
|
||||
if int(k.co[0]) not in range(frame_start, frame_end+1):
|
||||
fc.keyframe_points.remove(k)
|
||||
fc.update()
|
||||
|
||||
def append_action(action_path='', action_name=''):
|
||||
print(f'Loading {action_name} from: {action_path}')
|
||||
|
||||
with bpy.data.libraries.load(str(action_path), link=False) as (data_from, data_to):
|
||||
data_to.actions = [action_name]
|
||||
|
||||
return data_to.actions[0]
|
||||
|
||||
def apply_anim(action_lib, ob, bones=[]):
|
||||
from mathutils import Vector
|
||||
|
||||
scn = bpy.context.scene
|
||||
|
||||
if not ob.animation_data:
|
||||
ob.animation_data_create()
|
||||
|
||||
action = ob.animation_data.action
|
||||
|
||||
if not action:
|
||||
action = bpy.data.actions.new(ob.name)
|
||||
ob.animation_data.action = action
|
||||
|
||||
keys = sorted([k.co[0] for f in action_lib.fcurves for k in f.keyframe_points])
|
||||
if not keys:
|
||||
print(f'The action {action_lib.name} has no keyframes')
|
||||
return
|
||||
|
||||
first_key = keys[0]
|
||||
key_offset = scn.frame_current - first_key
|
||||
|
||||
key_attr = ('type', 'interpolation', 'handle_left_type', 'handle_right_type',
|
||||
'amplitude', 'back', 'easing', 'period', 'handle_right', 'handle_left'
|
||||
)
|
||||
for fc in action_lib.fcurves:
|
||||
bone_name, prop_name = split_path(fc.data_path)
|
||||
|
||||
if bones and bone_name not in bones:
|
||||
continue
|
||||
|
||||
action_fc = action.fcurves.find(fc.data_path, index=fc.array_index)
|
||||
if not action_fc:
|
||||
action_fc = action.fcurves.new(
|
||||
fc.data_path,
|
||||
index=fc.array_index,
|
||||
action_group=fc.group.name if fc.group else fc.data_path.split('"')[1]
|
||||
)
|
||||
|
||||
for kf_lib in fc.keyframe_points:
|
||||
kf = action_fc.keyframe_points.insert(
|
||||
frame=kf_lib.co[0] + key_offset,
|
||||
value=kf_lib.co[1]
|
||||
)
|
||||
for attr in key_attr:
|
||||
src_val = getattr(kf_lib, attr)
|
||||
if attr.startswith('handle') and 'type' not in attr:
|
||||
src_val += Vector((key_offset, 0))
|
||||
|
||||
setattr(kf, attr, src_val)
|
||||
|
||||
fc.update()
|
||||
|
||||
# redraw graph areas
|
||||
for window in bpy.context.window_manager.windows:
|
||||
screen = window.screen
|
||||
for area in screen.areas:
|
||||
if area.type == 'GRAPH_EDITOR':
|
||||
area.tag_redraw()
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def draw_context_menu(layout):
|
||||
params = bpy.context.space_data.params
|
||||
asset = bpy.context.asset_file_handle
|
||||
|
||||
layout.operator("assetlib.open_blend", text="Open blend file")#.asset = asset.name
|
||||
layout.operator("assetlib.play_preview", text="Play Preview")
|
||||
|
||||
layout.separator()
|
||||
|
||||
layout.operator_context = 'INVOKE_DEFAULT'
|
||||
|
||||
#layout.operator("assetlib.rename_asset", text="Rename Action")
|
||||
layout.operator("assetlib.clear_asset", text="Remove Asset")
|
||||
layout.operator("assetlib.edit_data", text="Edit Asset data")
|
||||
|
||||
#layout.operator("actionlib.clear_asset", text="Clear Asset (Fake User)").use_fake_user = True
|
||||
|
||||
layout.separator()
|
||||
|
||||
layout.operator("actionlib.apply_selected_action", text="Apply Pose").flipped = False
|
||||
layout.operator("actionlib.apply_selected_action", text="Apply Pose (Flipped)").flipped = True
|
||||
|
||||
layout.separator()
|
||||
|
||||
layout.operator("poselib.blend_pose_asset_for_keymap", text="Blend Pose").flipped = False
|
||||
layout.operator("poselib.blend_pose_asset_for_keymap", text="Blend Pose (Flipped)").flipped = True
|
||||
|
||||
layout.separator()
|
||||
|
||||
layout.operator("poselib.pose_asset_select_bones", text="Select Bones").selected_side = 'CURRENT'
|
||||
layout.operator("poselib.pose_asset_select_bones", text="Select Bones (Flipped)").selected_side = 'FLIPPED'
|
||||
layout.operator("poselib.pose_asset_select_bones", text="Select Bones (Both)").selected_side = 'BOTH'
|
||||
|
||||
layout.separator()
|
||||
#layout.operator("asset.library_refresh")
|
||||
if params.display_type == 'THUMBNAIL':
|
||||
layout.prop_menu_enum(params, "display_size")
|
||||
|
||||
|
||||
def draw_header(layout):
|
||||
'''Draw the header of the Asset Browser Window'''
|
||||
|
||||
layout.separator()
|
||||
layout.operator("actionlib.store_anim_pose", text='Add Action', icon='FILE_NEW')
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
|
||||
from typing import List, Tuple
|
||||
|
||||
import bpy
|
||||
|
||||
addon_keymaps: List[Tuple[bpy.types.KeyMap, bpy.types.KeyMapItem]] = []
|
||||
|
||||
def register():
|
||||
wm = bpy.context.window_manager
|
||||
addon = wm.keyconfigs.addon
|
||||
if not addon:
|
||||
return
|
||||
|
||||
km = addon.keymaps.new(name="File Browser Main", space_type="FILE_BROWSER")
|
||||
|
||||
# DblClick to apply pose.
|
||||
kmi = km.keymap_items.new("actionlib.apply_selected_action", "LEFTMOUSE", "DOUBLE_CLICK")
|
||||
kmi.properties.flipped = False
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
kmi = km.keymap_items.new("actionlib.apply_selected_action", "LEFTMOUSE", "DOUBLE_CLICK", alt=True)
|
||||
kmi.properties.flipped = True
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
kmi = km.keymap_items.new("poselib.blend_pose_asset_for_keymap", "LEFTMOUSE", "DOUBLE_CLICK", shift=True)
|
||||
kmi.properties.flipped = False
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
kmi = km.keymap_items.new("poselib.blend_pose_asset_for_keymap", "LEFTMOUSE", "DOUBLE_CLICK", alt=True, shift=True)
|
||||
kmi.properties.flipped = True
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
kmi = km.keymap_items.new("poselib.pose_asset_select_bones", "S", "PRESS")
|
||||
kmi.properties.selected_side = 'CURRENT'
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
kmi = km.keymap_items.new("poselib.pose_asset_select_bones", "S", "PRESS", alt=True)
|
||||
kmi.properties.selected_side = 'FLIPPED'
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
kmi = km.keymap_items.new("poselib.pose_asset_select_bones", "S", "PRESS", alt=True, ctrl=True)
|
||||
kmi.properties.selected_side = 'BOTH'
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
def unregister():
|
||||
for km, kmi in addon_keymaps:
|
||||
km.keymap_items.remove(kmi)
|
||||
addon_keymaps.clear()
|
||||
+1075
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
import bpy
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import PointerProperty, StringProperty, BoolProperty
|
||||
|
||||
class ACTIONLIB_PG_scene(PropertyGroup):
|
||||
flipped : BoolProperty(
|
||||
name="Flip Pose",
|
||||
default=False,
|
||||
)
|
||||
previous_action : PointerProperty(type=bpy.types.Action)
|
||||
publish_path : StringProperty(subtype='FILE_PATH')
|
||||
camera : PointerProperty(type=bpy.types.Object, poll=lambda s, o: o.type == 'CAMERA')
|
||||
rest_pose : PointerProperty(type=bpy.types.Action, poll=lambda s, a: a.asset_data)
|
||||
|
||||
|
||||
classes = (
|
||||
ACTIONLIB_PG_scene,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
bpy.types.Scene.actionlib = PointerProperty(type=ACTIONLIB_PG_scene)
|
||||
|
||||
def unregister():
|
||||
try:
|
||||
del bpy.types.Scene.actionlib
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
import argparse
|
||||
import bpy
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
sys.path.append(Path(__file__).parents[3])
|
||||
from asset_library.common.bl_utils import (
|
||||
get_preview,
|
||||
)
|
||||
|
||||
def rename_pose(src_name='', dst_name=''):
|
||||
|
||||
scn = bpy.context.scene
|
||||
action = bpy.data.actions.get(src_name)
|
||||
if not action:
|
||||
print(f'No {src_name} not found.')
|
||||
bpy.ops.wm.quit_blender()
|
||||
|
||||
action.name = dst_name
|
||||
preview = get_preview(asset_path=bpy.data.filepath, asset_name=src_name)
|
||||
if preview:
|
||||
preview.rename(re.sub(src_name, dst_name, str(preview)))
|
||||
|
||||
bpy.ops.wm.save_mainfile(
|
||||
filepath=bpy.data.filepath, compress=True, exit=True
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__' :
|
||||
parser = argparse.ArgumentParser(description='Add Comment To the tracker',
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument('--src-name')
|
||||
parser.add_argument('--dst-name')
|
||||
|
||||
if '--' in sys.argv :
|
||||
index = sys.argv.index('--')
|
||||
sys.argv = [sys.argv[index-1], *sys.argv[index+1:]]
|
||||
|
||||
args = parser.parse_args()
|
||||
rename_pose(**vars(args))
|
||||
@@ -0,0 +1,292 @@
|
||||
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
sys.path.append(str(Path(__file__).parents[3]))
|
||||
|
||||
from asset_library.action.concat_preview import mosaic_export
|
||||
from asset_library.common.file_utils import open_file
|
||||
from asset_library.action.functions import reset_bone, get_keyframes
|
||||
from asset_library.common.functions import read_catalog
|
||||
|
||||
import bpy
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from tempfile import gettempdir
|
||||
|
||||
|
||||
|
||||
def rm_tree(pth):
|
||||
pth = Path(pth)
|
||||
for child in pth.glob('*'):
|
||||
if child.is_file():
|
||||
child.unlink()
|
||||
else:
|
||||
rm_tree(child)
|
||||
pth.rmdir()
|
||||
|
||||
def render_preview(directory, asset_catalog, render_actions, publish_actions, remove_folder):
|
||||
|
||||
scn = bpy.context.scene
|
||||
rnd = bpy.context.scene.render
|
||||
rnd.resolution_x = rnd.resolution_y = 512
|
||||
|
||||
report = []
|
||||
blendfile = Path(bpy.data.filepath)
|
||||
asset_catalog_data = read_catalog(asset_catalog)
|
||||
|
||||
anim_render_dir = Path(gettempdir()) / 'actionlib_render' #/tmp/actionlib_render. Removed at the end
|
||||
anim_render_dir.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
preview_render_dir = Path(directory) / 'preview'
|
||||
|
||||
if preview_render_dir.exists() and remove_folder:
|
||||
rm_tree(preview_render_dir)
|
||||
|
||||
preview_render_dir.mkdir(exist_ok=True, parents=True)
|
||||
for i in ('anim', 'pose'):
|
||||
Path(preview_render_dir / i).mkdir(exist_ok=True, parents=True)
|
||||
|
||||
for f in preview_render_dir.rglob('*'):
|
||||
if f.is_dir():
|
||||
print(f'{f} is dir. Skipped.')
|
||||
continue
|
||||
if all(i not in f.parts for i in ('anim', 'pose')) and f.parent.parts[-1] != 'preview':
|
||||
print(f'{f} is out of pipe. Approved or Rtk pictures. Skipped.')
|
||||
continue
|
||||
if not any(f.stem.endswith(a) for a in publish_actions):
|
||||
print(f'{str(f)} not in publish actions anymore. Removing...')
|
||||
f.unlink()
|
||||
|
||||
# Set Scene
|
||||
# ----------
|
||||
# Scene Setting
|
||||
scn.use_preview_range = True
|
||||
scn.eevee.use_gtao = True
|
||||
scn.tool_settings.use_keyframe_insert_auto = False
|
||||
|
||||
# Render Setting
|
||||
rnd.engine = 'BLENDER_EEVEE'
|
||||
rnd.use_simplify = False
|
||||
rnd.use_stamp_date = True
|
||||
rnd.use_stamp_time = True
|
||||
rnd.use_stamp_render_time = False
|
||||
rnd.use_stamp_frame = True
|
||||
rnd.use_stamp_frame_range = False
|
||||
rnd.use_stamp_memory = False
|
||||
rnd.use_stamp_hostname = False
|
||||
rnd.use_stamp_camera = True
|
||||
rnd.use_stamp_lens = False
|
||||
rnd.use_stamp_scene = False
|
||||
rnd.use_stamp_marker = False
|
||||
rnd.use_stamp_filename = False
|
||||
rnd.use_stamp_sequencer_strip = False
|
||||
rnd.use_stamp_note = True
|
||||
rnd.use_stamp = True
|
||||
rnd.stamp_font_size = 16
|
||||
rnd.use_stamp_labels = False
|
||||
rnd.image_settings.file_format = 'JPEG'
|
||||
|
||||
# Viewport Look
|
||||
# ----------
|
||||
"""
|
||||
# Eevee
|
||||
for screen in bpy.data.screens:
|
||||
for area in screen.areas:
|
||||
for space in area.spaces:
|
||||
if space.type == 'VIEW_3D':
|
||||
space.overlay.show_overlays = False
|
||||
space.shading.type = 'RENDERED'
|
||||
space.shading.use_scene_lights_render = False
|
||||
space.shading.use_scene_world_render = False
|
||||
space.region_3d.view_perspective = 'CAMERA'
|
||||
|
||||
"""
|
||||
# Cycles Mat Shading
|
||||
for a in bpy.context.screen.areas:
|
||||
if a.type == 'VIEW_3D':
|
||||
a.spaces[0].overlay.show_overlays = False
|
||||
a.spaces[0].region_3d.view_perspective = 'CAMERA'
|
||||
a.spaces[0].shading.show_cavity = True
|
||||
a.spaces[0].shading.cavity_type = 'WORLD'
|
||||
a.spaces[0].shading.cavity_ridge_factor = 0.75
|
||||
a.spaces[0].shading.cavity_valley_factor = 1.0
|
||||
|
||||
|
||||
# Add Subsurf
|
||||
# -----------
|
||||
deform_ob = [m.object for o in scn.objects \
|
||||
for m in o.modifiers if m.type == 'MESH_DEFORM'
|
||||
]
|
||||
deform_ob += [m.target for o in scn.objects \
|
||||
for m in o.modifiers if m.type == 'SURFACE_DEFORM'
|
||||
]
|
||||
|
||||
objects = [o for o in bpy.context.scene.objects if (o.type == 'MESH'
|
||||
and o not in deform_ob and o not in bpy.context.scene.collection.objects[:])
|
||||
]
|
||||
|
||||
for o in objects:
|
||||
subsurf = False
|
||||
for m in o.modifiers:
|
||||
if m.type == 'SUBSURF':
|
||||
m.show_viewport = m.show_render
|
||||
m.levels = m.render_levels
|
||||
subsurf = True
|
||||
break
|
||||
|
||||
if not subsurf:
|
||||
subsurf = o.modifiers.new('', 'SUBSURF')
|
||||
subsurf.show_viewport = subsurf.show_render
|
||||
subsurf.levels = subsurf.render_levels
|
||||
|
||||
|
||||
# Loop through action and render
|
||||
# ------------------------------
|
||||
rig = next((o for o in scn.objects if o.type == 'ARMATURE'), None)
|
||||
# actions = [a for a in bpy.data.actions if a.asset_data]
|
||||
|
||||
|
||||
rig.animation_data_create()
|
||||
for action_name in render_actions:
|
||||
action = bpy.data.actions.get(action_name)
|
||||
|
||||
if not action:
|
||||
print(f'\'{action_name}\' not found.')
|
||||
continue
|
||||
|
||||
print(f"-- Current --: {action.name}")
|
||||
|
||||
rnd.stamp_note_text = '{type} : {pose_name}'
|
||||
action_data = action.asset_data
|
||||
|
||||
if 'camera' not in action_data.keys():
|
||||
report.append(f"'{action.name}' has no CameraData.")
|
||||
continue
|
||||
|
||||
catalog_name = next((v['name'] for v in asset_catalog_data.values() if action_data.catalog_id == v['id']), None)
|
||||
pose_name = '/'.join([*catalog_name.split('-'), action.name])
|
||||
filename = bpy.path.clean_name(f'{catalog_name}_{action.name}')
|
||||
ext = 'jpg'
|
||||
|
||||
rig.animation_data.action = None
|
||||
bpy.context.view_layer.update()
|
||||
for b in rig.pose.bones:
|
||||
if re.match('^[A-Z]+\.', b.name):
|
||||
continue
|
||||
reset_bone(b)
|
||||
|
||||
rest_pose = None
|
||||
if isinstance(action.asset_data.get('rest_pose'), str):
|
||||
rest_pose = bpy.data.actions.get(action.asset_data['rest_pose'])
|
||||
|
||||
rig.animation_data.action = rest_pose
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
rig.animation_data.action = action
|
||||
|
||||
if 'camera' in action.asset_data.keys():
|
||||
action_cam = bpy.data.objects.get(action.asset_data['camera'], '')
|
||||
if action_cam:
|
||||
scn.camera = action_cam
|
||||
|
||||
# Is Anim
|
||||
if not action_data['is_single_frame'] or 'anim' in action_data.tags.keys():
|
||||
keyframes = get_keyframes(action)
|
||||
if not keyframes:
|
||||
continue
|
||||
anim_start = keyframes[0]
|
||||
anim_end = keyframes[-1]
|
||||
|
||||
if anim_start < scn.frame_start:
|
||||
report.append(f"Issue found for '{action.name}'. Has keyframes before 'Start Frame'.")
|
||||
continue
|
||||
|
||||
scn.frame_preview_start = anim_start
|
||||
scn.frame_preview_end = anim_end
|
||||
|
||||
rnd.stamp_note_text = rnd.stamp_note_text.format(
|
||||
type='ANIM',
|
||||
pose_name=pose_name,
|
||||
)
|
||||
|
||||
rnd.filepath = f'{str(anim_render_dir)}/{filename}_####.{ext}'
|
||||
|
||||
bpy.ops.render.opengl(animation=True)
|
||||
|
||||
ffmpeg_cmd = [
|
||||
'ffmpeg', '-y',
|
||||
'-start_number', f'{anim_start:04d}',
|
||||
'-i', rnd.filepath.replace('####', '%04d'),
|
||||
'-c:v', 'libx264',
|
||||
str((preview_render_dir/'anim'/filename).with_suffix('.mov')),
|
||||
]
|
||||
subprocess.call(ffmpeg_cmd)
|
||||
|
||||
# Is Pose
|
||||
elif action_data['is_single_frame'] or 'pose' in action_data.tags.keys():
|
||||
scn.frame_preview_start = scn.frame_preview_end = scn.frame_start
|
||||
|
||||
rnd.stamp_note_text = rnd.stamp_note_text.format(
|
||||
type='POSE',
|
||||
pose_name=pose_name,
|
||||
)
|
||||
|
||||
rnd.filepath = f'{str(preview_render_dir)}/pose/{filename}_####.{ext}'
|
||||
|
||||
bpy.ops.render.opengl(animation=True)
|
||||
|
||||
filename = rnd.filepath.replace('####', f'{scn.frame_preview_end:04d}')
|
||||
Path(filename).rename(re.sub('_[0-9]{4}.', '.', filename))
|
||||
|
||||
shutil.rmtree(anim_render_dir)
|
||||
|
||||
# Report
|
||||
# ------
|
||||
if report:
|
||||
report_file = blendfile.parent / Path(f'{blendfile.stem}report').with_suffix('.txt')
|
||||
if not report_file.exists():
|
||||
report_file.touch(exist_ok=False)
|
||||
|
||||
report_file.write_text('-')
|
||||
report_file.write_text('\n'.join(report))
|
||||
|
||||
result = report_file
|
||||
|
||||
else:
|
||||
result = preview_render_dir
|
||||
|
||||
open_file(result)
|
||||
|
||||
files = [str(f) for f in sorted((preview_render_dir/'pose').glob('*.jpg'))]
|
||||
|
||||
mosaic_export(
|
||||
files=files, catalog_data=asset_catalog_data,
|
||||
row=2, columns=2, auto_calculate=True,
|
||||
bg_color=(0.18, 0.18, 0.18,), resize_output=100
|
||||
)
|
||||
|
||||
bpy.ops.wm.quit_blender()
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__' :
|
||||
parser = argparse.ArgumentParser(description='Add Comment To the tracker',
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument('--directory')
|
||||
parser.add_argument('--asset-catalog')
|
||||
parser.add_argument('--render-actions', nargs='+')
|
||||
parser.add_argument('--publish-actions', nargs='+')
|
||||
parser.add_argument('--remove-folder', type=json.loads, default='false')
|
||||
|
||||
if '--' in sys.argv :
|
||||
index = sys.argv.index('--')
|
||||
sys.argv = [sys.argv[index-1], *sys.argv[index+1:]]
|
||||
|
||||
args = parser.parse_args()
|
||||
render_preview(**vars(args))
|
||||
Reference in New Issue
Block a user