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))