2025-12-01

This commit is contained in:
2026-03-17 14:58:51 -06:00
parent 183e865f8b
commit 4b82b57113
6846 changed files with 954887 additions and 162606 deletions
@@ -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)