ABX/abx/abx_ui.py

573 lines
17 KiB
Python

# Anansi Studio Extensions for Blender 'ABX'
"""
Collection of Blender extension tools to make our jobs easier.
This is not really meant to be an integrated plugin, but rather
a collection of useful scripts we can run to solve problems we
run into.
"""
#
#Copyright (C) 2019 Terry Hancock
#
# 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.
#
import os
import bpy, bpy.utils, bpy.types, bpy.props
from bpy.app.handlers import persistent
from . import file_context
# if bpy.data.filepath:
# BlendfileContext = file_context.FileContext(bpy.data.filepath)
# else:
# BlendfileContext = file_context.FileContext()
#
# abx_data = BlendfileContext.abx_data
from . import copy_anim
from abx import ink_paint
from . import render_profile
#configfile = os.path.join(os.path.dirname(__file__), 'config.yaml')
#print("Configuration file path: ", os.path.abspath(configfile))
# Lunatics Scene Panel
# Lunatics file/scene properties:
# TODO: This hard-coded table is a temporary solution until I have figured
# out a good way to look these up from the project files (maybe YAML?):
seq_id_table = {
('S1', 0): {'':'', 'mt':'Main Title'},
('S1', 1): {'':'',
'TR':'Train',
'SR':'Soyuz Rollout',
'TB':'Touring Baikonur',
'PC':'Press Conference',
'SU':'Suiting Up',
'LA':'Launch',
'SF':'Soyuz Flight',
'mt':'Main Title',
'ad':'Ad Spot',
'pv':'Preview',
'et':'Episode Titles',
'cr':'Credits'
},
('S1', 2): {'':'',
'MM':'Media Montage',
'mt':'Main Title',
'et':'Episode Titles',
'SS':'Space Station',
'LC':'Loading Cargo',
'TL':'Trans Lunar Injection',
'BT':'Bed Time',
'ad':'Ad Spot',
'pv':'Preview',
'cr':'Credits'
},
('S1', 3): {'':'',
'mt':'Main Title',
'et':'Episode Titles',
'ZG':'Zero G',
'LI':'Lunar Injection',
'LO':'Lunar Orbit',
'ML':'Moon Landing',
'IR':'Iridium',
'TC':'Touring Colony',
'FD':'Family Dinner',
'ad':'Ad Spot',
'pv':'Preview',
'cr':'Credits'
},
('S2', 0): {'':'', 'mt':'Main Title'},
('L', 0): {'':'',
'demo':'Demonstration',
'prop':'Property',
'set': 'Set',
'ext': 'Exterior Set',
'int': 'Interior Set',
'prac':'Practical',
'char':'Character',
'fx': 'Special Effect',
'stock': 'Stock Animation'
},
None: ['']
}
def get_seq_ids(self, context):
#
# Note: To avoid the reference bug mentioned in the Blender documentation,
# we only return values held in the global seq_id_table, which
# should remain defined and therefore hold a reference to the strings.
#
if not context:
seq_ids = seq_id_table[None]
else:
scene = context.scene
series = scene.lunaprops.series_id
episode = scene.lunaprops.episode_id
if (series, episode) in seq_id_table:
seq_ids = seq_id_table[(series, episode)]
else:
seq_ids = seq_id_table[None]
seq_enum_items = [(s, s, seq_id_table[series,episode][s]) for s in seq_ids]
return seq_enum_items
# Another hard-coded table -- for render profiles
render_profile_table = {
'previz': {
'name': 'PreViz',
'desc': 'GL/AVI Previz Render for Animatics',
'engine':'gl',
'version':'any',
'fps': 30,
'fps_div': 1000,
'fps_skip': 1,
'suffix': 'GL',
'format': 'AVI',
'freestyle': False
},
'paint6': {
'name': '6fps Paint',
'desc': '6fps Simplified Paint-Only Render',
'engine':'bi',
'fps': 30,
'fps_skip': 5,
'suffix': 'P6',
'format': 'AVI',
'freestyle': False,
'antialias': False,
'motionblur': False
},
'paint3': {
'name': '3fps Paint',
'desc': '3fps Simplified Paint-Only Render',
'engine': 'bi',
'fps': 30,
'fps_skip': 10,
'suffix': 'P3',
'format': 'AVI',
'freestyle': False,
'antialias': False,
'motionblur': False,
},
'paint': {
'name': '30fps Paint',
'desc': '30fps Simplified Paint-Only Render',
'engine': 'bi',
'fps': 30,
'fps_skip': 1,
'suffix': 'PT',
'format': 'AVI',
'freestyle': False,
'antialias': False,
'motionblur': False
},
'check': {
'name': '1fps Check',
'desc': '1fps Full-Features Check Renders',
'engine': 'bi',
'fps': 30,
'fps_skip': 30,
'suffix': 'CH',
'format': 'JPG',
'framedigits': 5,
'freestyle': True,
'antialias': 8
},
'full': {
'name': '30fps Full',
'desc': 'Full Render with all Features Turned On',
'engine': 'bi',
'fps': 30,
'fps_skip': 1,
'suffix': '',
'format': 'PNG',
'framedigits': 5,
'freestyle': True,
'antialias': 8
},
}
class LunaticsSceneProperties(bpy.types.PropertyGroup):
"""
Properties of the current scene.
"""
series_id = bpy.props.EnumProperty(
items=[
('S1', 'S1', 'Series One'),
('S2', 'S2', 'Series Two'),
('S3', 'S3', 'Series Three'),
('A1', 'Aud','Audiodrama'),
('L', 'Lib','Library')
],
name="Series",
default='S1',
description="Series/Season of Animated Series, Audiodrama, or Library"
)
episode_id = bpy.props.IntProperty(
name="Episode",
default=0,
description="Episode number (0 means multi-use), ignored for Library",
min=0,
max=1000,
soft_max=18
)
seq_id = bpy.props.EnumProperty(
name='',
items=get_seq_ids,
description="Sequence ID"
)
block_id = bpy.props.IntProperty(
name='',
default=1,
min=0,
max=20,
soft_max=10,
description="Block number"
)
use_multicam = bpy.props.BoolProperty(
name="Multicam",
default=False,
description="Use multicam camera/shot numbering?"
)
cam_id = bpy.props.IntProperty(
name="Cam",
default=0,
min=0,
max=20,
soft_max=10,
description="Camera number"
)
shot_id = bpy.props.EnumProperty(
name='Shot',
#items=[('NONE', '', 'Single')]+[(c,c,'Shot '+c) for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'],
items=[(c,c,'Shot '+c) for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'],
default='A',
description="Shot ID, normally a single capital letter, can be empty, two letters for transitions"
)
shot_name = bpy.props.StringProperty(
name='Name',
description='Short descriptive codename',
maxlen=0
)
class LunaticsScenePanel(bpy.types.Panel):
"""
Add a panel to the Properties-Scene screen
"""
bl_idname = 'SCENE_PT_lunatics'
bl_label = 'Lunatics Project'
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = 'scene'
def draw(self, context):
lunaprops = bpy.context.scene.lunaprops
self.layout.label(text='Lunatics! Project Properties')
row = self.layout.row()
row.prop(lunaprops, 'series_id')
row.prop(lunaprops, 'episode_id')
row = self.layout.row()
row.prop(lunaprops, 'use_multicam')
row = self.layout.row()
row.prop(lunaprops, 'seq_id')
row.prop(lunaprops, 'block_id')
if lunaprops.use_multicam:
row.prop(lunaprops, 'cam_id')
row.prop(lunaprops, 'shot_id')
row.prop(lunaprops, 'shot_name')
# Buttons
class RenderProfileSettings(bpy.types.PropertyGroup):
"""
Settings for Render Profiles control.
"""
render_profile = bpy.props.EnumProperty(
name='Profile',
items=[(k, v['name'], v['desc'])
for k,v in render_profile_table.items()],
description="Select from pre-defined profiles of render settings",
default='full')
class RenderProfilesOperator(bpy.types.Operator):
"""
Operator invoked implicitly when render profile is changed.
"""
bl_idname = 'render.render_profiles'
bl_label = 'Apply Render Profile'
bl_options = {'UNDO'}
def invoke(self, context, event):
scene = context.scene
profile = render_profile_table[scene.render_profile_settings.render_profile]
render_profile.set_render_from_profile(scene, profile)
return {'FINISHED'}
class RenderProfilesPanel(bpy.types.Panel):
"""
Add simple drop-down selector for generating common render settings with
destination set according to project defaults.
"""
bl_idname = 'SCENE_PT_render_profiles'
bl_label = 'Render Profiles'
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = 'render'
def draw(self, context):
rps = bpy.context.scene.render_profile_settings
row = self.layout.row()
row.prop(rps, 'render_profile')
row = self.layout.row()
row.operator('render.render_profiles')
class copy_animation(bpy.types.Operator):
"""
Copy animation from active object to selected objects (select source last!).
Useful for fixing broken proxy rigs (create a new proxy, and used this tool
to copy all animation from the original -- avoids tedious/error-prone NLA work).
Can also migrate to a re-scaled rig.
"""
bl_idname = 'object.copy_anim'
bl_label = 'Copy Animation'
bl_options = {'UNDO'}
def invoke(self, context, event):
#print("Copy NLA from selected armature to active armatures.")
src_ob = context.active_object
tgt_obs = [ob for ob in context.selected_objects if ob != context.active_object]
# TODO
# Are these type checks necessary?
# Is there any reason to restrict this operator to armature objects?
# I think there isn't.
if src_ob.type != 'ARMATURE':
self.report({'WARNING'}, 'Cannot copy NLA data from object that is not an ARMATURE.')
return {'CANCELLED'}
tgt_arm_obs = []
for ob in tgt_obs:
if ob.type == 'ARMATURE':
tgt_arm_obs.append(ob)
if not tgt_arm_obs:
self.report({'WARNING'}, 'No armature objects selected to copy animation data to.')
return {'CANCELLED'}
copy_anim.copy_object_animation(src_ob, tgt_arm_obs,
dopesheet=context.scene.copy_anim_settings.dopesheet,
nla=context.scene.copy_anim_settings.nla,
rescale=context.scene.copy_anim_settings.rescale,
scale_factor=context.scene.copy_anim_settings.scale_factor,
report=self.report)
return {'FINISHED'}
class copy_animation_settings(bpy.types.PropertyGroup):
"""
Settings for the 'copy_animation' operator.
"""
dopesheet = bpy.props.BoolProperty(
name = "Dope Sheet",
description = "Copy animation from Dope Sheet",
default=True)
nla = bpy.props.BoolProperty(
name = "NLA Strips",
description = "Copy all strips from NLA Editor",
default=True)
rescale = bpy.props.BoolProperty(
name = "Re-Scale/Copy",
description = "Make rescaled COPY of actions instead of LINK to original",
default = False)
scale_factor = bpy.props.FloatProperty(
name = "Scale",
description = "Scale factor for scaling animation (Re-Scale w/ 1.0 copies actions)",
default = 1.0)
class CharacterPanel(bpy.types.Panel):
bl_space_type = "VIEW_3D" # window type panel is displayed in
bl_context = "objectmode"
bl_region_type = "TOOLS" # region of window panel is displayed in
bl_label = "Character"
bl_category = "ABX"
def draw(self, context):
settings = bpy.context.scene.copy_anim_settings
layout = self.layout.column(align = True)
layout.label("Animation Data")
layout.operator('object.copy_anim')
layout.prop(settings, 'dopesheet')
layout.prop(settings, 'nla')
layout.prop(settings, 'rescale')
layout.prop(settings, 'scale_factor')
class lunatics_compositing_settings(bpy.types.PropertyGroup):
"""
Settings for the LX compositor tool.
"""
inkthru = bpy.props.BoolProperty(
name = "Ink-Thru",
description = "Support transparent Freestyle ink effect",
default=True)
billboards = bpy.props.BoolProperty(
name = "Billboards",
description = "Support material pass for correct billboard inking",
default = False)
sepsky = bpy.props.BoolProperty(
name = "Separate Sky",
description = "Render sky separately with compositing support (better shadows)",
default = True)
class lunatics_compositing(bpy.types.Operator):
"""
Set up standard Lunatics scene compositing.
"""
bl_idname = "scene.lunatics_compos"
bl_label = "Ink/Paint Config"
bl_options = {'UNDO'}
bl_description = "Set up standard Lunatics Ink/Paint compositing in scene"
def invoke(self, context, event):
"""
Add standard 'Lunatics!' shot compositing to the currently-selected scene.
"""
scene = context.scene
shot = ink_paint.LunaticsShot(scene,
inkthru=context.scene.lx_compos_settings.inkthru,
billboards=context.scene.lx_compos_settings.billboards,
sepsky=context.scene.lx_compos_settings.sepsky )
shot.cfg_scene()
return {'FINISHED'}
# def draw(self, context):
# settings = context.scene.lx_compos_settings
# self.col = self.layout.col()
# col.prop(settings, "inkthru", text="Ink Thru")
# col.prop(settings, "billboards", text="Ink Thru")
class LunaticsPanel(bpy.types.Panel):
bl_space_type = "VIEW_3D"
bl_context = "objectmode"
bl_region_type = "TOOLS"
bl_label = "Lunatics"
bl_category = "ABX"
def draw(self, context):
settings = bpy.context.scene.lx_compos_settings
layout = self.layout.column(align = True)
layout.label("Compositing")
layout.operator('scene.lunatics_compos')
layout.prop(settings, 'inkthru', text="Ink-Thru")
layout.prop(settings, 'billboards', text="Billboards")
layout.prop(settings, 'sepsky', text="Separate Sky")
BlendFile = file_context.FileContext()
@persistent
def update_handler(ctxt):
BlendFile.update(bpy.data.filepath)
def register():
bpy.utils.register_class(LunaticsSceneProperties)
bpy.types.Scene.lunaprops = bpy.props.PointerProperty(type=LunaticsSceneProperties)
bpy.utils.register_class(LunaticsScenePanel)
bpy.utils.register_class(RenderProfileSettings)
bpy.types.Scene.render_profile_settings = bpy.props.PointerProperty(
type=RenderProfileSettings)
bpy.utils.register_class(RenderProfilesOperator)
bpy.utils.register_class(RenderProfilesPanel)
bpy.utils.register_class(copy_animation)
bpy.utils.register_class(copy_animation_settings)
bpy.types.Scene.copy_anim_settings = bpy.props.PointerProperty(type=copy_animation_settings)
bpy.utils.register_class(CharacterPanel)
bpy.utils.register_class(lunatics_compositing_settings)
bpy.types.Scene.lx_compos_settings = bpy.props.PointerProperty(type=lunatics_compositing_settings)
bpy.utils.register_class(lunatics_compositing)
bpy.utils.register_class(LunaticsPanel)
bpy.app.handlers.save_post.append(update_handler)
bpy.app.handlers.load_post.append(update_handler)
bpy.app.handlers.scene_update_post.append(update_handler)
def unregister():
bpy.utils.unregister_class(LunaticsSceneProperties)
bpy.utils.unregister_class(LunaticsScenePanel)
bpy.utils.unregister_class(RenderProfileSettings)
bpy.utils.unregister_class(RenderProfilesOperator)
bpy.utils.unregister_class(RenderProfilesPanel)
bpy.utils.unregister_class(copy_animation)
bpy.utils.unregister_class(copy_animation_settings)
bpy.utils.unregister_class(CharacterPanel)
bpy.utils.unregister_class(lunatics_compositing_settings)
bpy.utils.unregister_class(lunatics_compositing)
bpy.utils.unregister_class(LunaticsPanel)