2025-12-01
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
if "bpy" not in locals():
|
||||
import bpy
|
||||
from . import main
|
||||
from . import objects
|
||||
from . import command_api
|
||||
from . import retargeting
|
||||
from . import updater
|
||||
from . import info
|
||||
from . import login
|
||||
else:
|
||||
import importlib
|
||||
|
||||
importlib.reload(main)
|
||||
importlib.reload(objects)
|
||||
importlib.reload(command_api)
|
||||
importlib.reload(retargeting)
|
||||
importlib.reload(updater)
|
||||
importlib.reload(info)
|
||||
importlib.reload(login)
|
||||
@@ -0,0 +1,37 @@
|
||||
import bpy
|
||||
|
||||
from .main import ToolPanel
|
||||
from ..operators import command_api
|
||||
from ..core.icon_manager import Icons
|
||||
|
||||
|
||||
# Main panel of the Rokoko panel
|
||||
class CommandPanel(ToolPanel, bpy.types.Panel):
|
||||
bl_idname = 'VIEW3D_PT_rsl_command_api_v2'
|
||||
bl_label = 'Studio Command API'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = False
|
||||
|
||||
col = layout.column()
|
||||
|
||||
row = col.row(align=True)
|
||||
row.label(text='Address:')
|
||||
row.prop(context.scene, 'rsl_command_ip_address', text='')
|
||||
|
||||
row = col.row(align=True)
|
||||
row.label(text='Port:')
|
||||
row.prop(context.scene, 'rsl_command_ip_port', text='')
|
||||
|
||||
row = col.row(align=True)
|
||||
row.label(text='Key:')
|
||||
row.prop(context.scene, 'rsl_command_api_key', text='')
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 1.5
|
||||
row.scale_x = 3
|
||||
row.operator(command_api.StartCalibration.bl_idname, text='', icon_value=Icons.CALIBRATE.get_icon())
|
||||
row.operator(command_api.Restart.bl_idname, text='', icon_value=Icons.RESTART.get_icon())
|
||||
row.operator(command_api.StartRecording.bl_idname, text='', icon_value=Icons.START_RECORDING.get_icon())
|
||||
row.operator(command_api.StopRecording.bl_idname, text='', icon='SNAP_FACE')
|
||||
@@ -0,0 +1,64 @@
|
||||
import bpy
|
||||
|
||||
from .main import ToolPanel, separator
|
||||
from .. import updater
|
||||
from ..operators import info
|
||||
from ..core.icon_manager import Icons
|
||||
from ..operators.login import LogoutButton
|
||||
from ..core import login_manager as lm
|
||||
|
||||
|
||||
class InfoPanel(ToolPanel, bpy.types.Panel):
|
||||
bl_idname = 'VIEW3D_PT_rsl_info_v2'
|
||||
bl_label = 'Info'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Rokoko Studio Live', icon_value=Icons.STUDIO_LIVE_LOGO.get_icon())
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 0.1
|
||||
row.label(text='for Blender (v' + updater.current_version_str + ')', icon='BLANK1')
|
||||
|
||||
separator(layout, 0.01)
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Developed by ', icon='BLANK1')
|
||||
row.scale_y = 0.6
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 0.3
|
||||
row.label(text='Rokoko Electronics ApS', icon='BLANK1')
|
||||
|
||||
separator(layout, 0.1)
|
||||
|
||||
col = layout.column(align=True)
|
||||
|
||||
row = col.row(align=True)
|
||||
row.operator(info.LicenseButton.bl_idname)
|
||||
row.operator(info.RokokoButton.bl_idname)
|
||||
row = col.row(align=True)
|
||||
row.operator(info.DocumentationButton.bl_idname)
|
||||
# row = col.row(align=True)
|
||||
# row.operator(info.ForumButton.bl_idname) # TODO: Add forums back with correct link
|
||||
|
||||
# If there is no email, the user is not logged in yet
|
||||
if not lm.user.email:
|
||||
return
|
||||
|
||||
separator(layout, 0.1)
|
||||
|
||||
subrow = layout.row(align=True)
|
||||
row = subrow.row(align=True)
|
||||
row.scale_y = 0.7
|
||||
row.label(text='Rokoko ID:')
|
||||
row = subrow.row(align=True)
|
||||
row.scale_y = 0.7
|
||||
row.alignment = 'RIGHT'
|
||||
row.operator(info.ToggleRokokoIDButton.bl_idname, text='', icon='HIDE_OFF' if lm.user.display_email else 'HIDE_ON')
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 0.3
|
||||
row.label(text=lm.user.email if lm.user.display_email else "***********")
|
||||
row = layout.row(align=True)
|
||||
row.operator(LogoutButton.bl_idname)
|
||||
@@ -0,0 +1,44 @@
|
||||
import bpy
|
||||
from .main import ToolPanel, separator
|
||||
from ..operators.login import LoginButton, InstallLibsButton
|
||||
from ..core.icon_manager import Icons
|
||||
from .. import updater, updater_ops
|
||||
from ..core import login_manager as lm
|
||||
|
||||
|
||||
class LoginPanel(ToolPanel, bpy.types.Panel):
|
||||
bl_idname = 'VIEW3D_PT_rsl_login_v2'
|
||||
bl_label = 'Rokoko ID'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
updater.check_for_update_background(check_on_startup=True)
|
||||
updater_ops.draw_update_notification_panel(layout)
|
||||
|
||||
if not lm.loaded_all_libs:
|
||||
row = layout.row(align=True)
|
||||
row.label(text="First time setup:", icon="INFO")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 2
|
||||
row.operator(InstallLibsButton.bl_idname, icon="TRIA_DOWN_BAR")
|
||||
return
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 2
|
||||
row.operator(LoginButton.bl_idname, text="Sign in to Rokoko" if not lm.user.logging_in else "Waiting for sign in..", icon_value=Icons.STUDIO_LIVE_LOGO.get_icon())
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 0.5
|
||||
row.label(text='*Opens your browser')
|
||||
|
||||
errors = lm.user.display_error
|
||||
if not errors:
|
||||
return
|
||||
|
||||
separator(layout, scale=0.2)
|
||||
for i, error in enumerate(errors):
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 0.5
|
||||
row.label(text=error, icon="ERROR" if i == 0 else "BLANK1")
|
||||
@@ -0,0 +1,329 @@
|
||||
import bpy
|
||||
import datetime
|
||||
|
||||
from .. import updater, updater_ops
|
||||
from ..core import animations
|
||||
from ..core import recorder as recorder_manager
|
||||
from ..core import receiver as receiver_cls
|
||||
from ..core.icon_manager import Icons
|
||||
from ..operators import receiver, recorder
|
||||
|
||||
row_scale = 0.75
|
||||
paired_inputs = {}
|
||||
|
||||
|
||||
# Initializes the Rokoko panel in the toolbar
|
||||
class ToolPanel(object):
|
||||
bl_label = 'Rokoko'
|
||||
bl_idname = 'VIEW3D_TS_rokoko'
|
||||
bl_category = 'Rokoko'
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
|
||||
|
||||
def separator(layout, scale=1):
|
||||
# Add small separator
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = scale
|
||||
row.label(text='')
|
||||
|
||||
|
||||
# Main panel of the Rokoko panel
|
||||
class ReceiverPanel(ToolPanel, bpy.types.Panel):
|
||||
bl_idname = 'VIEW3D_PT_rsl_receiver_v2'
|
||||
bl_label = 'Rokoko Studio Live'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = False
|
||||
|
||||
# box = layout.box()
|
||||
updater.check_for_update_background(check_on_startup=True)
|
||||
updater_ops.draw_update_notification_panel(layout)
|
||||
|
||||
col = layout.column()
|
||||
|
||||
row = col.row(align=True)
|
||||
row.label(text='Port:')
|
||||
row.enabled = not receiver.receiver_enabled
|
||||
row.prop(context.scene, 'rsl_receiver_port', text='')
|
||||
|
||||
# row = col.row(align=True)
|
||||
# row.label(text='FPS:')
|
||||
# row.enabled = not receiver.receiver_enabled
|
||||
# row.prop(context.scene, 'rsl_receiver_fps', text='')
|
||||
|
||||
row = col.row(align=True)
|
||||
row.label(text='Scene Scale:')
|
||||
row.prop(context.scene, 'rsl_scene_scaling', text='')
|
||||
|
||||
layout.separator()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.scene, 'rsl_reset_scene_on_stop')
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.scene, 'rsl_hide_mesh_during_play')
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 1.3
|
||||
if receiver.receiver_enabled:
|
||||
row.operator(receiver.ReceiverStop.bl_idname, icon='PAUSE', depress=True)
|
||||
else:
|
||||
row.operator(receiver.ReceiverStart.bl_idname, icon='PLAY')
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 1.3
|
||||
row.enabled = receiver.receiver_enabled
|
||||
if not context.scene.rsl_recording:
|
||||
row.operator(recorder.RecorderStart.bl_idname, icon_value=Icons.START_RECORDING.get_icon())
|
||||
else:
|
||||
row.operator(recorder.RecorderStop.bl_idname, icon='SNAP_FACE', depress=True)
|
||||
|
||||
# Calculate recording time
|
||||
timestamps = list(recorder_manager.recorded_timestamps.keys())
|
||||
if timestamps:
|
||||
time_recorded = int(timestamps[-1] - timestamps[0])
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Recording time: ' + str(datetime.timedelta(seconds=time_recorded)))
|
||||
|
||||
if receiver.receiver_enabled and receiver_cls.show_error:
|
||||
for i, error in enumerate(receiver_cls.show_error):
|
||||
if i == 0:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=error, icon='ERROR')
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 0.3
|
||||
row.label(text=error, icon='BLANK1')
|
||||
return
|
||||
|
||||
if animations.live_data.version <= 2:
|
||||
show_connetions_v2(layout)
|
||||
else:
|
||||
show_connetions_v3(layout)
|
||||
|
||||
|
||||
def show_connetions_v2(layout):
|
||||
# Show all inputs
|
||||
global paired_inputs
|
||||
paired_inputs = {}
|
||||
used_trackers = []
|
||||
used_faces = []
|
||||
|
||||
# Get all paired inputs. Paired inputs are paired to an object in the scene
|
||||
for obj in bpy.data.objects:
|
||||
# Get paired props and trackers
|
||||
if animations.live_data.props or animations.live_data.trackers:
|
||||
if obj.rsl_animations_props_trackers and obj.rsl_animations_props_trackers != 'None':
|
||||
paired = paired_inputs.get(obj.rsl_animations_props_trackers.split('|')[1])
|
||||
if not paired:
|
||||
paired_inputs[obj.rsl_animations_props_trackers.split('|')[1]] = [obj.name]
|
||||
else:
|
||||
paired.append(obj.name)
|
||||
|
||||
# Get paired faces
|
||||
if animations.live_data.faces and obj.rsl_animations_faces and obj.rsl_animations_faces != 'None':
|
||||
paired = paired_inputs.get(obj.rsl_animations_faces)
|
||||
if not paired:
|
||||
paired_inputs[obj.rsl_animations_faces] = [obj.name]
|
||||
else:
|
||||
paired.append(obj.name)
|
||||
|
||||
# Get paired actors
|
||||
if animations.live_data.actors and obj.rsl_animations_actors and obj.rsl_animations_actors != 'None':
|
||||
paired = paired_inputs.get(obj.rsl_animations_actors)
|
||||
if not paired:
|
||||
paired_inputs[obj.rsl_animations_actors] = [obj.name]
|
||||
else:
|
||||
paired.append(obj.name)
|
||||
|
||||
# This is used as a small spacer
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 0.01
|
||||
row.label(text=' ')
|
||||
|
||||
# Display all paired and unpaired inputs
|
||||
for actor in animations.live_data.actors:
|
||||
if actor['profileName']:
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = row_scale
|
||||
row.label(text=actor['profileName'], icon='ANTIALIASED')
|
||||
|
||||
split = layout.row(align=True)
|
||||
split.scale_y = row_scale
|
||||
add_indent(split)
|
||||
show_actor(split, actor)
|
||||
|
||||
for tracker in animations.live_data.trackers:
|
||||
if tracker['connectionId'] == actor['name']:
|
||||
split = layout.row(align=True)
|
||||
split.scale_y = row_scale
|
||||
add_indent(split, empty=True)
|
||||
add_indent(split)
|
||||
show_tracker(split, tracker)
|
||||
used_trackers.append(tracker['name'])
|
||||
|
||||
for face in animations.live_data.faces:
|
||||
if face.get('profileName') and face.get('profileName') == actor['profileName']:
|
||||
split = layout.row(align=True)
|
||||
split.scale_y = row_scale
|
||||
add_indent(split)
|
||||
show_face(split, face)
|
||||
used_faces.append(face['faceId'])
|
||||
|
||||
# split = layout.row(align=True)
|
||||
# add_indent(split)
|
||||
# row = split.row(align=True)
|
||||
# row.label(text='faceId', icon_value=Icons.FACE.get_icon())
|
||||
|
||||
for prop in animations.live_data.props:
|
||||
show_prop(layout, prop, scale=True)
|
||||
|
||||
for tracker in animations.live_data.trackers:
|
||||
if tracker['connectionId'] == prop['id']:
|
||||
split = layout.row(align=True)
|
||||
split.scale_y = row_scale
|
||||
add_indent(split)
|
||||
show_tracker(split, tracker)
|
||||
used_trackers.append(tracker['name'])
|
||||
|
||||
for tracker in animations.live_data.trackers:
|
||||
if tracker['name'] not in used_trackers:
|
||||
show_tracker(layout, tracker, scale=True)
|
||||
|
||||
# row = layout.row(align=True)
|
||||
# row.label(text='5', icon_value=Icons.VP.get_icon())
|
||||
|
||||
for face in animations.live_data.faces:
|
||||
if face['faceId'] not in used_faces:
|
||||
show_face(layout, face, scale=True)
|
||||
|
||||
|
||||
def show_connetions_v3(layout):
|
||||
# Show all inputs
|
||||
global paired_inputs
|
||||
paired_inputs = {}
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
# Get props
|
||||
if obj.rsl_animations_props_trackers and obj.rsl_animations_props_trackers != 'None':
|
||||
if animations.live_data.props:
|
||||
prop = animations.live_data.get_prop_by_obj(obj)
|
||||
if prop:
|
||||
prop_id = animations.live_data.get_prop_id(prop)
|
||||
if not paired_inputs.get(prop_id):
|
||||
paired_inputs[prop_id] = [obj.name]
|
||||
else:
|
||||
paired_inputs[prop_id].append(obj.name)
|
||||
|
||||
# Get faces
|
||||
if animations.live_data.faces and obj.rsl_animations_faces and obj.rsl_animations_faces != 'None':
|
||||
face = animations.live_data.get_face_by_obj(obj)
|
||||
if face:
|
||||
face_id = animations.live_data.get_face_id(face)
|
||||
if not paired_inputs.get(face_id):
|
||||
paired_inputs[face_id] = [obj.name]
|
||||
else:
|
||||
paired_inputs[face_id].append(obj.name)
|
||||
|
||||
# Get actors
|
||||
if animations.live_data.actors and obj.rsl_animations_actors and obj.rsl_animations_actors != 'None':
|
||||
actor = animations.live_data.get_actor_by_obj(obj)
|
||||
if actor:
|
||||
actor_id = animations.live_data.get_actor_id(actor)
|
||||
if not paired_inputs.get(actor_id):
|
||||
paired_inputs[actor_id] = [obj.name]
|
||||
else:
|
||||
paired_inputs[actor_id].append(obj.name)
|
||||
|
||||
# This is used as a small spacer
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 0.01
|
||||
row.label(text=' ')
|
||||
|
||||
# Display all paired and unpaired inputs
|
||||
for actor in animations.live_data.actors:
|
||||
show_actor(layout, actor)
|
||||
|
||||
for face in animations.live_data.faces:
|
||||
if animations.live_data.get_face_parent_id(face) == animations.live_data.get_actor_id(actor):
|
||||
split = layout.row(align=True)
|
||||
split.scale_y = row_scale
|
||||
add_indent(split)
|
||||
show_face(split, face)
|
||||
|
||||
for prop in animations.live_data.props:
|
||||
show_prop(layout, prop, scale=True)
|
||||
|
||||
|
||||
def add_indent(split, empty=False):
|
||||
row = split.row(align=True)
|
||||
row.alignment = 'LEFT'
|
||||
if empty:
|
||||
row.label(text="", icon='BLANK1')
|
||||
else:
|
||||
row.label(text="", icon_value=Icons.PAIRED.get_icon())
|
||||
|
||||
|
||||
def show_actor(layout, actor, scale=False):
|
||||
row = layout.row(align=True)
|
||||
if scale:
|
||||
row.scale_y = row_scale
|
||||
|
||||
actor_id = animations.live_data.get_actor_id(actor)
|
||||
if paired_inputs.get(actor_id):
|
||||
row.label(text=actor_id + ' --> ' + ', '.join(paired_inputs.get(actor_id)), icon_value=Icons.SUIT.get_icon())
|
||||
else:
|
||||
row.enabled = False
|
||||
row.label(text=actor_id, icon_value=Icons.SUIT.get_icon())
|
||||
|
||||
|
||||
def show_glove(layout, glove, scale=False):
|
||||
row = layout.row(align=True)
|
||||
if scale:
|
||||
row.scale_y = row_scale
|
||||
|
||||
if paired_inputs.get(glove['gloveID']):
|
||||
row.label(text=glove['gloveID'] + ' --> ' + ', '.join(paired_inputs.get(glove['gloveID'])), icon='VIEW_PAN')
|
||||
else:
|
||||
row.enabled = False
|
||||
row.label(text=glove['gloveID'], icon='VIEW_PAN')
|
||||
|
||||
|
||||
def show_face(layout, face, scale=False):
|
||||
row = layout.row(align=True)
|
||||
if scale:
|
||||
row.scale_y = row_scale
|
||||
|
||||
face_id = animations.live_data.get_face_id(face)
|
||||
if paired_inputs.get(face_id):
|
||||
row.label(text=face_id + ' --> ' + ', '.join(paired_inputs.get(face_id)), icon_value=Icons.FACE.get_icon())
|
||||
else:
|
||||
row.enabled = False
|
||||
row.label(text=face_id, icon_value=Icons.FACE.get_icon())
|
||||
|
||||
|
||||
def show_tracker(layout, tracker, scale=False):
|
||||
row = layout.row(align=True)
|
||||
if scale:
|
||||
row.scale_y = row_scale
|
||||
|
||||
if paired_inputs.get(tracker['name']):
|
||||
row.label(text=tracker['name'] + ' --> ' + ', '.join(paired_inputs.get(tracker['name'])), icon_value=Icons.VP.get_icon())
|
||||
else:
|
||||
row.enabled = False
|
||||
row.label(text=tracker['name'], icon_value=Icons.VP.get_icon())
|
||||
|
||||
|
||||
def show_prop(layout, prop, scale=False):
|
||||
row = layout.row(align=True)
|
||||
if scale:
|
||||
row.scale_y = row_scale
|
||||
|
||||
prop_id = animations.live_data.get_prop_name_raw(prop)
|
||||
if paired_inputs.get(prop_id):
|
||||
row.label(text=prop_id + ' --> ' + ', '.join(paired_inputs.get(prop_id)), icon='FILE_3D')
|
||||
else:
|
||||
row.enabled = False
|
||||
row.label(text=prop_id, icon='FILE_3D')
|
||||
@@ -0,0 +1,167 @@
|
||||
import bpy
|
||||
|
||||
from ..core import animations, animation_lists
|
||||
from ..operators.actor import InitTPose, ResetTPose
|
||||
from ..operators import detector
|
||||
|
||||
|
||||
# Create a panel in the Object category of all objects
|
||||
class ObjectsPanel(bpy.types.Panel):
|
||||
bl_label = "Rokoko Studio Live Setup"
|
||||
bl_idname = "OBJECT_PT_rsl_objects_v2"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
obj = context.object
|
||||
|
||||
self.draw_tracker(context, layout)
|
||||
|
||||
if obj.type == 'MESH':
|
||||
self.draw_face(context, layout)
|
||||
|
||||
elif obj.type == 'ARMATURE':
|
||||
self.draw_actor(context, layout)
|
||||
|
||||
@staticmethod
|
||||
def draw_tracker(context, layout):
|
||||
obj = context.object
|
||||
|
||||
props_string = 'Prop or Tracker' if animations.live_data.version <= 2 else 'prop'
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f'Attach to {props_string}:')
|
||||
|
||||
if not animations.live_data.trackers and not animations.live_data.props:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f'No {props_string.lower()} data available.', icon='INFO')
|
||||
return
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.object, 'rsl_animations_props_trackers')
|
||||
|
||||
if obj.rsl_animations_props_trackers and obj.rsl_animations_props_trackers != 'None':
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.object, 'rsl_use_custom_scale')
|
||||
if obj.rsl_use_custom_scale:
|
||||
row.prop(context.object, 'rsl_custom_scene_scale', text='')
|
||||
|
||||
@staticmethod
|
||||
def draw_face(context, layout):
|
||||
obj = context.object
|
||||
|
||||
layout.separator()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Attach to Face:')
|
||||
|
||||
if not animations.live_data.faces:
|
||||
row = layout.row(align=True)
|
||||
row.label(text='No face data available.', icon='INFO')
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 0.1
|
||||
return
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj, 'rsl_animations_faces')
|
||||
|
||||
if obj.rsl_animations_faces and obj.rsl_animations_faces != 'None':
|
||||
layout.separator()
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Select Shapekeys:')
|
||||
row.operator(detector.DetectFaceShapes.bl_idname)
|
||||
|
||||
if not hasattr(obj.data, 'shape_keys') or not hasattr(obj.data.shape_keys, 'key_blocks'):
|
||||
row = layout.row(align=True)
|
||||
row.label(text='This mesh has no shapekeys!', icon='INFO')
|
||||
return
|
||||
|
||||
draw_import_export(layout, shapes=True)
|
||||
|
||||
for shape in animation_lists.face_shapes:
|
||||
row = layout.row(align=True)
|
||||
row.prop_search(obj, 'rsl_face_' + shape, obj.data.shape_keys, "key_blocks", text=shape)
|
||||
|
||||
@staticmethod
|
||||
def draw_actor(context, layout):
|
||||
obj = context.object
|
||||
|
||||
layout.separator()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Attach to Actor:')
|
||||
|
||||
if not animations.live_data.actors:
|
||||
row = layout.row(align=True)
|
||||
row.label(text='No actor data available.', icon='INFO')
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.object, 'rsl_animations_actors')
|
||||
|
||||
if obj.rsl_animations_actors and obj.rsl_animations_actors != 'None':
|
||||
layout.separator()
|
||||
|
||||
split = layout.row(align=True)
|
||||
row = split.split(factor=0.16, align=True)
|
||||
row.label(text='Bones:')
|
||||
row.operator(detector.DetectActorBones.bl_idname)
|
||||
row.operator(InitTPose.bl_idname)
|
||||
row.operator(ResetTPose.bl_idname)
|
||||
|
||||
# if obj.rsl_animations_actors and obj.rsl_animations_actors != 'None':
|
||||
if not obj.get('CUSTOM') or not obj.get('CUSTOM').get('rsl_tpose_bones'):
|
||||
row = layout.row(align=True)
|
||||
row.label(text='T-Pose is not set yet!', icon='ERROR')
|
||||
|
||||
draw_import_export(layout)
|
||||
|
||||
col = layout.column()
|
||||
show_gloves = True
|
||||
for actor_bone in animation_lists.get_bones().keys():
|
||||
if not show_gloves:
|
||||
continue
|
||||
|
||||
split = col.row(align=True)
|
||||
row = split.split(factor=0.32, align=True)
|
||||
row.label(text=actor_bone + ':')
|
||||
row.prop_search(obj, 'rsl_actor_' + actor_bone, obj.pose, "bones", text='')
|
||||
|
||||
# Make a split after right toe to separate hands
|
||||
if actor_bone == 'rightToe':
|
||||
if not animations.live_data.has_gloves(animations.live_data.get_actor_by_obj(obj)): # Stop showing glove bones if they are not supported by the JSON version
|
||||
show_gloves = False
|
||||
continue
|
||||
col.separator()
|
||||
row = col.row(align=True)
|
||||
row.label(text='Gloves:', icon='VIEW_PAN')
|
||||
|
||||
if actor_bone == 'leftLittleDistal':
|
||||
col.separator()
|
||||
|
||||
|
||||
def draw_import_export(layout, shapes=False):
|
||||
layout.separator()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Custom Naming Schemes:')
|
||||
if shapes:
|
||||
row.operator(detector.SaveCustomShapes.bl_idname, text='Save Current Naming Scheme')
|
||||
else:
|
||||
row.operator(detector.SaveCustomBones.bl_idname, text='Save Current Naming Scheme')
|
||||
|
||||
subrow = layout.row(align=True)
|
||||
row = subrow.row(align=True)
|
||||
row.scale_y = 0.9
|
||||
row.operator(detector.ImportCustomBones.bl_idname, text='Import')
|
||||
row.operator(detector.ExportCustomBones.bl_idname, text='Export')
|
||||
row = subrow.row(align=True)
|
||||
row.scale_y = 0.9
|
||||
row.alignment = 'RIGHT'
|
||||
if shapes:
|
||||
row.operator(detector.ClearCustomShapes.bl_idname, text='', icon='X')
|
||||
else:
|
||||
row.operator(detector.ClearCustomBones.bl_idname, text='', icon='X')
|
||||
|
||||
layout.separator()
|
||||
@@ -0,0 +1,137 @@
|
||||
import bpy
|
||||
|
||||
from .main import ToolPanel
|
||||
from ..operators import retargeting, detector
|
||||
from ..core.icon_manager import Icons
|
||||
from ..core.retargeting import get_target_armature, get_source_armature
|
||||
|
||||
from bpy.types import PropertyGroup, UIList
|
||||
from bpy.props import StringProperty, BoolProperty
|
||||
|
||||
|
||||
# Retargeting panel
|
||||
class RetargetingPanel(ToolPanel, bpy.types.Panel):
|
||||
bl_idname = 'VIEW3D_PT_rsl_retargeting_v2'
|
||||
bl_label = 'Retargeting'
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = False
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Select the armatures:')
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.scene, 'rsl_retargeting_armature_source', icon='ARMATURE_DATA')
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.scene, 'rsl_retargeting_armature_target', icon='ARMATURE_DATA')
|
||||
|
||||
anim_exists = False
|
||||
for obj in bpy.data.objects:
|
||||
if obj.animation_data and obj.animation_data.action:
|
||||
anim_exists = True
|
||||
|
||||
if not anim_exists:
|
||||
row = layout.row(align=True)
|
||||
row.label(text='No animated armature found!', icon='INFO')
|
||||
return
|
||||
|
||||
if not context.scene.rsl_retargeting_armature_source or not context.scene.rsl_retargeting_armature_target:
|
||||
self.draw_import_export(layout)
|
||||
return
|
||||
|
||||
if not context.scene.rsl_retargeting_bone_list:
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 1.2
|
||||
row.operator(retargeting.BuildBoneList.bl_idname, icon_value=Icons.CALIBRATE.get_icon())
|
||||
self.draw_import_export(layout)
|
||||
return
|
||||
|
||||
subrow = layout.row(align=True)
|
||||
row = subrow.row(align=True)
|
||||
row.scale_y = 1.2
|
||||
row.operator(retargeting.BuildBoneList.bl_idname, text='Rebuild Bone List', icon_value=Icons.CALIBRATE.get_icon())
|
||||
row = subrow.row(align=True)
|
||||
row.scale_y = 1.2
|
||||
row.alignment = 'RIGHT'
|
||||
row.operator(retargeting.ClearBoneList.bl_idname, text="", icon='X')
|
||||
|
||||
layout.separator()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.template_list("RSL_UL_BoneList", "Bone List", context.scene, "rsl_retargeting_bone_list", context.scene, "rsl_retargeting_bone_list_index", rows=1, maxrows=10)
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator(retargeting.AddBoneListItem.bl_idname, text="Add Custom Entry", icon='ADD')
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.scene, 'rsl_retargeting_auto_scaling')
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Use Pose:')
|
||||
row.prop(context.scene, 'rsl_retargeting_use_pose', expand=True)
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 1.4
|
||||
row.operator(retargeting.RetargetAnimation.bl_idname, icon_value=Icons.CALIBRATE.get_icon())
|
||||
|
||||
self.draw_import_export(layout)
|
||||
|
||||
def draw_import_export(self, layout):
|
||||
layout.separator()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Custom Naming Schemes:')
|
||||
|
||||
subrow = layout.row(align=True)
|
||||
row = subrow.row(align=True)
|
||||
row.scale_y = 0.9
|
||||
row.operator(detector.SaveCustomBonesRetargeting.bl_idname, text='Save')
|
||||
row.operator(detector.ImportCustomBones.bl_idname, text='Import')
|
||||
row.operator(detector.ExportCustomBones.bl_idname, text='Export')
|
||||
row = subrow.row(align=True)
|
||||
row.scale_y = 0.9
|
||||
row.alignment = 'RIGHT'
|
||||
row.operator(detector.ClearCustomBones.bl_idname, text='', icon='X')
|
||||
|
||||
|
||||
class BoneListItem(PropertyGroup):
|
||||
"""Properties of the bone list items"""
|
||||
bone_name_source: StringProperty(
|
||||
name="Source Bone",
|
||||
description="The source bone name",
|
||||
default="")
|
||||
|
||||
bone_name_target: StringProperty(
|
||||
name="Target Bone",
|
||||
description="The target bone name",
|
||||
default="")
|
||||
|
||||
bone_name_key: StringProperty(
|
||||
name="Auto Detection Key",
|
||||
description="The automatically detected bone key",
|
||||
default="")
|
||||
|
||||
is_custom: BoolProperty(
|
||||
description="This determines if the field is a custom one source bone one",
|
||||
default=False)
|
||||
|
||||
|
||||
class RSL_UL_BoneList(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
armature_target = get_target_armature()
|
||||
armature_source = get_source_armature()
|
||||
|
||||
layout = layout.split(factor=0.36, align=True)
|
||||
|
||||
# Displays source bone
|
||||
if item.is_custom:
|
||||
layout.prop_search(item, 'bone_name_source', armature_source.pose, "bones", text='')
|
||||
else:
|
||||
layout.label(text=item.bone_name_source)
|
||||
|
||||
# Displays target bone
|
||||
if armature_target:
|
||||
layout.prop_search(item, 'bone_name_target', armature_target.pose, "bones", text='')
|
||||
@@ -0,0 +1,13 @@
|
||||
import bpy
|
||||
|
||||
from .main import ToolPanel
|
||||
from .. import updater_ops
|
||||
|
||||
|
||||
class UpdaterPanel(ToolPanel, bpy.types.Panel):
|
||||
bl_idname = 'VIEW3D_PT_rsl_updater_v2'
|
||||
bl_label = 'Updater'
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context):
|
||||
updater_ops.draw_updater_panel(context, self.layout)
|
||||
Reference in New Issue
Block a user