rig_picker/operators/picker.py
2025-12-05 17:59:06 +01:00

873 lines
27 KiB
Python

import bpy
from bpy.props import EnumProperty, IntProperty, BoolProperty, StringProperty
from bpy.types import Operator, Menu
from ..constants import PICKERS, SHADERS
from ..core.addon_utils import get_picker_collection
from ..core.bl_utils import get_view_3d_override, split_path, eval_attr
from ..core.geometry_utils import bounding_rect
from ..core.picker import get_picker_path, pack_picker, unpack_picker, load_picker_data
# from .func_bgl import draw_callback_px
# from .func_bgl import select_bone
# from core.picker import *
# from .utils import is_over_region
import gpu
from mathutils import Vector, Euler, Matrix
from gpu_extras.batch import batch_for_shader
from pathlib import Path
import json
import os
def is_picker_space(space_data=None):
if not space_data:
space_data = bpy.context.space_data
if space_data and (
space_data.type == "NODE_EDITOR" and space_data.tree_type == "RigPickerTree"
):
return True
return False
class RP_OT_box_select(Operator):
"""Box Select bones in the picker view"""
bl_idname = "node.rp_box_select"
bl_label = "Picker Box Select"
mode: EnumProperty(
items=[(i, i.title(), "") for i in ("SET", "EXTEND", "SUBSTRACT")]
)
@classmethod
def poll(cls, context):
if not is_picker_space(context.space_data):
return
ob = context.object
return ob and ob in PICKERS
"""
def mode_from_event(self, event):
if event.alt:
return 'SUBSTRACT'
elif event.ctrl or event.shift:
return 'EXTEND'
else:
return 'SET'
"""
def draw_callback(self):
# print('draw callback border')
if not self.draw_border:
return
gpu.state.blend_set("ALPHA")
# print('DRAW BORDER')
self.color_shader.bind()
self.color_shader.uniform_float("color", self.bg_color)
self.bg_batch.draw(self.color_shader)
self.dash_shader.bind()
matrix = gpu.matrix.get_projection_matrix()
self.dash_shader.uniform_float("color", self.border_color)
self.dash_shader.uniform_float("viewMatrix", matrix)
self.dash_shader.uniform_float("dashSize", 5)
self.dash_shader.uniform_float("gapSize", 4)
self.contour_batch.draw(self.dash_shader)
gpu.state.blend_set("NONE")
def invoke(self, context, event):
# print(f'invoke: type={event.type}, value={event.value}, ctrl={event.ctrl}, shift={event.shift}, alt={event.alt}')
if context.object.mode != "POSE":
bpy.ops.object.posemode_toggle()
self.timer = None
# self.mode = self.mode_from_event(event)
# self.invoke_event = event.copy()
self.region = context.region
self.draw_border = False
self.picker = PICKERS[context.object]
self.start_mouse = event.mouse_region_x, event.mouse_region_y
# self.shader = line_strip_shader
self.border_color = [1, 1, 1, 1]
self.bg_color = [1, 1, 1, 0.05]
# args = (self, context)
self.color_shader = gpu.shader.from_builtin("UNIFORM_COLOR")
self.dash_shader = SHADERS["dashed_line"]
self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(
self.draw_callback, (), "WINDOW", "POST_PIXEL"
)
context.window_manager.modal_handler_add(self)
self.picker.press_event(self.mode)
self.region.tag_redraw()
return {"RUNNING_MODAL"}
def modal(self, context, event):
self.mouse = event.mouse_region_x, event.mouse_region_y
self.border = bounding_rect((self.start_mouse, self.mouse))
self.bg_batch = batch_for_shader(
self.color_shader, "TRI_FAN", {"pos": self.border}
)
self.contour_batch = batch_for_shader(
self.dash_shader, "LINE_LOOP", {"pos": self.border}
)
self.draw_border = True
self.region.tag_redraw()
if event.value == "RELEASE":
return self.release_event(context)
return {"RUNNING_MODAL"}
def release_event(self, context):
if get_picker_collection():
self.picker.assign_bone_event()
elif (
self.start_mouse[0] != self.mouse[0] or self.start_mouse[1] != self.mouse[1]
):
self.picker.border_select(self.border, self.mode)
else:
self.picker.move_event(self.mouse)
self.picker.release_event(self.mode)
bpy.ops.ed.undo_push(message="Box Select")
return self.exit(context)
def exit(self, context):
bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, "WINDOW")
context.region.tag_redraw()
return {"FINISHED"}
class RP_OT_picker_transform(Operator):
"""Transform Bones in the picker view"""
bl_idname = "node.picker_transform"
bl_label = "Transform Bone in Picker View"
mode: EnumProperty(items=[(m, m.title(), "") for m in ("ROTATE", "SCALE")])
@classmethod
def poll(cls, context):
return context.selected_pose_bones and is_picker_space(context.space_data)
"""
def draw_callback(self):
gpu.state.blend_set('ALPHA')
gpu.state.line_width_set(2)
self.dash_shader.bind()
matrix = gpu.matrix.get_projection_matrix()
self.dash_shader.uniform_float("color", [0, 0, 0, 1])
self.dash_shader.uniform_float("viewMatrix", matrix)
self.dash_shader.uniform_float("dashSize", 5)
self.dash_shader.uniform_float("gapSize", 4)
self.batch = batch_for_shader(self.dash_shader, 'LINE_LOOP', {"pos": [self.view_center, self.mouse]})
self.batch.draw(self.dash_shader)
gpu.state.line_width_set(1)
gpu.state.blend_set('NONE')
"""
def invoke(self, context, event):
self.override = get_view_3d_override()
self.view_center = Vector(
(int(context.region.width / 2), int(context.region.height / 2))
)
self.mouse_start = Vector((event.mouse_region_x, event.mouse_region_y))
self.mouse = Vector((0, 0))
transform_type = context.scene.tool_settings.transform_pivot_point
self.center = context.active_pose_bone.matrix.to_translation()
if transform_type == "MEDIAN_POINT":
origins = [b.matrix.to_translation() for b in context.selected_pose_bones]
self.center = sum(origins, Vector()) / len(context.selected_pose_bones)
# self.bone_data = {}
# for bone in context.selected_pose_bones:
# self.bone_data[bone] = bone.matrix.copy()
self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones}
space = self.override["area"].spaces.active
view_matrix = space.region_3d.view_matrix
if space.region_3d.view_perspective == "CAMERA":
view_matrix = context.scene.camera.matrix_world
context.window.cursor_modal_set("MOVE_X")
self.view_vector = Vector((0, 0, 1))
if self.mode == "ROTATE":
self.view_vector.rotate(view_matrix)
elif self.mode == "SCALE":
self.view_vector = Vector((0, 0, 0))
self.transform_type = "VIEW"
self.transform_types = ["VIEW", "GLOBAL", "LOCAL"]
if context.scene.transform_orientation_slots[0].type == "LOCAL":
self.transform_types = ["VIEW", "LOCAL", "GLOBAL"]
self.transform_orientation = context.scene.transform_orientation_slots[0].type
# self.dash_shader = SHADERS['dashed_line']
# self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(self.draw_callback, (), 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self)
return {"RUNNING_MODAL"}
def change_transform_type(self):
indices = {0: 1, 1: 2, 2: 1}
index = self.transform_types.index(self.transform_type)
new_index = indices[index]
self.transform_type = self.transform_types[new_index]
def release_event(self, context):
scn = bpy.context.scene
# Insert keyframe
# bpy.ops.ed.undo_push(message="Transform")
if scn.tool_settings.use_keyframe_insert_auto:
try:
bpy.ops.animtoolbox.insert_keyframe()
except Exception:
self.report(
{"WARNING"}, "You need animtoolbox addon for inserting keyframe"
)
self.exit(context)
def exit(self, context):
# bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
context.window.cursor_modal_restore()
# context.region.tag_redraw()
def modal(self, context, event):
scn = context.scene
self.mouse = Vector((event.mouse_region_x, event.mouse_region_y))
if self.mode == "ROTATE":
delta = (event.mouse_region_x - self.mouse_start[0]) / 100
elif self.mode == "SCALE":
delta = 1 + (event.mouse_region_x - self.mouse_start[0]) / 100
# delta = (self.mouse - self.view_center).length / (self.mouse_start - self.view_center).length
transform_type = self.transform_type
# self.batch = batch_for_shader(self.dash_shader, 'LINE_STRIP', {"pos": [self.mouse_start, self.mouse]})
# context.area.tag_redraw()
# print(event.type, event.value)
if self.mode == "ROTATE" and event.type == "R" and event.value == "PRESS":
with context.temp_override(**self.override):
self.exit(context)
bpy.ops.transform.trackball("INVOKE_DEFAULT")
return {"FINISHED"}
if event.type == "LEFTMOUSE" and event.value == "RELEASE":
self.release_event(context)
return {"FINISHED"}
if event.type in {"RIGHTMOUSE", "ESC"}:
for bone, matrix in self.bone_data.items():
bone.matrix = matrix
self.exit(context)
return {"CANCELLED"}
elif event.type == "X" and event.value == "PRESS":
self.change_transform_type()
self.view_vector = Vector((1, 0, 0))
# transform_type = self.transform_orientation
elif event.type == "Y" and event.value == "PRESS":
self.change_transform_type()
self.view_vector = Vector((0, 1, 0))
# transform_type = self.transform_orientation
elif event.type == "Z" and event.value == "PRESS":
self.change_transform_type()
self.view_vector = Vector((0, 0, 1))
# transform_type = self.transform_orientation
elif event.type == "MOUSEMOVE":
if self.mode == "ROTATE":
transform_matrix = Matrix.Rotation(delta, 4, self.view_vector)
elif self.mode == "SCALE":
if self.view_vector.length:
transform_matrix = Matrix.Scale(delta, 4, self.view_vector)
else:
transform_matrix = Matrix.Scale(delta, 4)
for bone, matrix in self.bone_data.items():
center = self.center
if scn.tool_settings.transform_pivot_point == "INDIVIDUAL_ORIGINS":
center = matrix.to_translation()
if self.mode == "ROTATE":
if transform_type == "LOCAL":
view_vector = self.view_vector.copy()
view_vector.rotate(matrix)
transform_matrix = Matrix.Rotation(delta, 4, view_vector)
elif self.mode == "SCALE":
if transform_type == "LOCAL":
view_vector = self.view_vector.copy()
view_vector.rotate(matrix)
transform_matrix = Matrix.Scale(delta, 4, view_vector)
mat = matrix.copy()
mat.translation -= center
mat = transform_matrix @ mat
mat.translation += center
bone.matrix = mat
return {"RUNNING_MODAL"}
class RP_OT_toogle_property(Operator):
"""Invert a bone custom property"""
bl_idname = "rigpicker.toogle_property"
bl_label = "Toogle Property"
data_path: StringProperty()
@classmethod
def poll(cls, context):
return is_picker_space(context.space_data)
def execute(self, context):
ob = context.object
try:
value = ob.path_resolve(self.data_path)
except Exception:
return {"CANCELLED"}
data = ob
prop_name = self.data_path
# if '.' in self.data_path:
# data, prop = prop_name.rsplit('.', 1)
value = type(value)(not value)
exec(f"{repr(ob)}.{self.data_path} = {value}")
# setattr(data, prop_name, value)
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="POSE")
if context.scene.tool_settings.use_keyframe_insert_auto:
bone_name, _ = split_path(self.data_path)
ob.keyframe_insert(data_path=self.data_path, group=bone_name)
context.area.tag_redraw()
return {"FINISHED"}
class RP_OT_reload_picker(Operator):
"""Reload the picker shapes"""
bl_idname: str = "rigpicker.reload_picker"
bl_label: str = "Reload Picker"
# @classmethod
# def poll(cls, context):
# if not is_picker_space(context):
# return
def execute(self, context: bpy.types.Context):
if not context.object:
return
if context.object.type == "ARMATURE":
rig = context.object
else:
collection = get_picker_collection(context.object)
rig = collection.rig_picker.rig
# print('Reload', rig)
load_picker_data(rig)
if context.screen:
for area in context.screen.areas:
if is_picker_space(area.spaces.active):
area.tag_redraw()
return {"FINISHED"}
class RP_OT_toogle_bone_layer(Operator):
"""Toogle bone layer visibility when double clicking on a bone"""
bl_idname: str = "rigpicker.toogle_bone_layer"
bl_label: str = "Toogle Bone Layer"
@classmethod
def poll(cls, context):
if not is_picker_space(context.space_data):
return
ob = context.object
picker = PICKERS.get(ob)
if picker.hover_shape and picker.hover_shape.type == "bone":
return True
def execute(self, context):
ob = context.object
picker = PICKERS.get(ob)
bone = picker.hover_shape.bone
hide = picker.hover_shape.hide
if bone:
for layer in bone.bone.collections:
layer.is_visible = hide
context.region.tag_redraw()
return {"FINISHED"}
class RP_OT_context_menu_picker(Operator):
"""Display Menu with the custom properties of the hovered bone if any or the active bone"""
bl_idname = "node.context_menu_picker"
bl_label = "Context Menu Picker"
@classmethod
def poll(cls, context):
return is_picker_space(context.space_data)
def execute(self, context):
bpy.ops.wm.call_menu(name="RP_MT_context_menu")
return {"FINISHED"}
class RP_OT_pack_picker(Operator):
"""Pack Unpack the picker on the rig"""
bl_idname = "rigpicker.pack_picker"
bl_label = "Pack Picker"
@classmethod
def poll(cls, context):
ob = context.object
return ob and ob.type == "ARMATURE" and ob.data.rig_picker.sources
def execute(self, context):
print("Pack Pickers...")
rig = context.object
if "pickers" in rig.data.rig_picker.keys():
unpack_picker(rig)
self.report({"INFO"}, f"The picker is unpacked")
return {"FINISHED"}
pack_picker(rig)
if not rig.data.rig_picker["pickers"]:
self.report({"ERROR"}, f"No picker have been packed")
return {"CANCELLED"}
elif len(rig.data.rig_picker["pickers"]) < len(rig.data.rig_picker.sources):
self.report({"WARNING"}, f"No all pickers have been packed")
return {"FINISHED"}
self.report({"INFO"}, f"The picker is packed")
return {"FINISHED"}
class RP_OT_call_operator(Operator):
bl_idname = "rigpicker.call_operator"
bl_label = "Call operator"
operator: StringProperty()
arguments: StringProperty()
invoke: BoolProperty()
view_3d: BoolProperty()
@classmethod
def poll(cls, context):
return is_picker_space(context.space_data)
@classmethod
def description(cls, context, properties):
try:
op = eval_attr(bpy.ops, properties.operator).get_rna_type()
return op.description
except AttributeError:
return "Call operator"
def execute(self, context):
# context.area.tag_redraw()
override = {}
if self.view_3d:
override = get_view_3d_override()
arguments = json.loads(self.arguments or "{}")
with context.temp_override(**override):
try:
ops = eval_attr(bpy.ops, self.operator)
if self.invoke:
ops("INVOKE_DEFAULT", **arguments)
else:
ops(**arguments)
except Exception as e:
print(e)
self.report({"ERROR"}, f"The operator {self.operator} cannot be called")
return {"CANCELLED"}
context.area.tag_redraw()
return {"FINISHED"}
class RP_MT_context_menu(Menu):
bl_label = "Context Menu"
# Set the menu operators and draw functions
def draw(self, context):
layout = self.layout
col = layout.column()
col.operator_context = "INVOKE_DEFAULT"
# col.use_property_split = True
ob = context.object
picker = PICKERS.get(ob)
if picker.hover_shape and picker.hover_shape.type == "bone":
bone = picker.hover_shape.bone
else:
bone = context.active_pose_bone
# Draw Space Switch Operator
if getattr(ob.data, "space_switch"):
space_switch = ob.data.space_switch
data_paths = [f'pose.bones["{bone.name}"]["{k}"]' for k in bone.keys()]
space_bone = next(
(s for s in space_switch.bones if s.data_path in data_paths), None
)
if space_bone:
index = list(space_switch.bones).index(space_bone)
value = ob.path_resolve(space_bone.data_path)
space = next(
(s.name for s in space_bone.spaces if s.value == value), None
)
op = col.operator(
"spaceswitch.change_space", text=f"({space})", icon="PINNED"
)
op.index = index
col.separator()
if bone:
for key in bone.keys():
col.prop(bone, f'["{key}"]', slider=True)
# layout.operator("rigpicker.show_bone_layer", text="Show Bone Layer", ).type = 'ACTIVE'
# layout.operator("rigidbody.objects_add", text="B Add Passive").type = 'PASSIVE'
class RP_OT_add_picker_collection(Operator):
bl_idname = "rigpicker.add_picker_collection"
bl_label = "Add a Picker Collection"
bl_description = "Add a Picker Collection"
bl_options = {"UNDO", "REGISTER"}
@classmethod
def poll(cls, context):
ob = context.object
return ob and ob.type == "ARMATURE"
def execute(self, context):
scn = context.scene
ob = context.object
name = ob.name
if name.endswith("_rig"):
name = name[:-4]
canvas_name = f"canevas_{name}"
canvas_points = [(-0.5, 0.5, 0), (0.5, 0.5, 0), (0.5, -0.5, 0), (-0.5, -0.5, 0)]
canvas_faces = [(0, 1, 2, 3)]
canvas_mesh = bpy.data.meshes.new(canvas_name)
canvas_mesh.from_pydata(canvas_points, [], canvas_faces)
canvas_mesh.update(calc_edges=True)
canvas_ob = bpy.data.objects.new(canvas_name, canvas_mesh)
canvas_ob.rig_picker.shape_type = "DISPLAY"
col = bpy.data.collections.new(f"Picker {name}")
col.rig_picker.enabled = True
col.rig_picker.rig = ob
col.rig_picker.canvas = canvas_ob
col.objects.link(canvas_ob)
scn.collection.children.link(col)
self.report({"INFO"}, f"New Picker Collection {col.name} Created")
return {"FINISHED"}
class RP_OT_add_picker_source(Operator):
bl_idname = "rigpicker.add_picker_source"
bl_label = "Add a Picker source"
bl_description = "Add a Picker source"
bl_options = {"UNDO", "REGISTER"}
def execute(self, context):
arm = context.object.data
arm.rig_picker.sources.add()
return {"FINISHED"}
class RP_OT_remove_picker_source(Operator):
bl_idname = "rigpicker.remove_picker_source"
bl_label = "Delete a Picker source"
bl_description = "Delete a Picker source"
bl_options = {"UNDO", "REGISTER"}
index: IntProperty(default=-1)
def execute(self, context):
arm = context.object.data
arm.rig_picker.sources.remove(self.index)
return {"FINISHED"}
class RP_OT_fit_picker(Operator):
bl_idname = "rigpicker.fit_picker"
bl_label = "Fit Picker"
bl_description = "Fit Picker in 2d view"
bl_options = {"UNDO", "REGISTER"}
index: IntProperty()
@classmethod
def poll(cls, context):
return is_picker_space(context.space_data) and PICKERS.get(context.object)
def execute(self, context):
ob = context.object
picker_group = PICKERS[ob]
if self.index >= len(picker_group.pickers):
return {"CANCELLED"}
picker = picker_group.pickers[self.index]
view2d = context.region.view2d
if self.index == -1:
# print('Picker Group')
picker = picker_group
view_rect = [view2d.view_to_region(*co, clip=False) for co in picker.rect]
bpy.ops.view2d.zoom_border(
xmin=round(view_rect[3][0]),
xmax=round(view_rect[1][0]),
ymin=round(view_rect[3][1]),
ymax=round(view_rect[1][1]),
wait_for_input=False,
zoom_out=False,
)
region_center = Vector(
(context.region.width * 0.5, context.region.height * 0.5)
)
view_center = view2d.region_to_view(*region_center)
view_offset = Vector(view_center) + (picker.center - Vector(view_center))
region_offset = view2d.view_to_region(*view_offset, clip=False)
delta = Vector(region_offset) - region_center
bpy.ops.view2d.pan(deltax=round(delta[0]), deltay=round(delta[1]))
return {"FINISHED"}
keymaps = []
def register_keymaps():
wm = bpy.context.window_manager
km = wm.keyconfigs.addon.keymaps.new(name="Node Editor", space_type="NODE_EDITOR")
for i in range(10):
kmi = km.keymap_items.new(
"rigpicker.fit_picker", type=f"NUMPAD_{i}", value="PRESS"
)
kmi.properties.index = i - 1
keymaps.append((km, kmi))
kmi = km.keymap_items.new(
"rigpicker.call_operator", type="NUMPAD_PERIOD", value="PRESS"
)
kmi.properties.operator = "view3d.view_selected"
kmi.properties.view_3d = True
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="A", value="PRESS")
kmi.properties.operator = "pose.select_all"
kmi.properties.arguments = '{"action": "SELECT"}'
keymaps.append((km, kmi))
kmi = km.keymap_items.new(
"rigpicker.call_operator", type="A", value="PRESS", alt=True
)
kmi.properties.operator = "pose.select_all"
kmi.properties.arguments = '{"action": "DESELECT"}'
keymaps.append((km, kmi))
kmi = km.keymap_items.new(
"rigpicker.toogle_bone_layer", type="LEFTMOUSE", value="DOUBLE_CLICK"
)
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="X", value="PRESS")
kmi.properties.operator = "animtoolbox.reset_bone"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="K", value="PRESS")
kmi.properties.operator = "animtoolbox.insert_keyframe"
keymaps.append((km, kmi))
kmi = km.keymap_items.new(
"anim.keyframe_delete_v3d", type="K", value="PRESS", alt=True
)
keymaps.append((km, kmi))
kmi = km.keymap_items.new("rigpicker.call_operator", type="G", value="PRESS")
kmi.properties.operator = "transform.translate"
kmi.properties.view_3d = True
kmi.properties.invoke = True
keymaps.append((km, kmi))
kmi = km.keymap_items.new(
"rigpicker.call_operator", type="G", value="PRESS", alt=True
)
kmi.properties.operator = "pose.loc_clear"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.picker_transform", type="R", value="PRESS")
kmi.properties.mode = "ROTATE"
keymaps.append((km, kmi))
kmi = km.keymap_items.new(
"rigpicker.call_operator", type="R", value="PRESS", alt=True
)
kmi.properties.operator = "pose.rot_clear"
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.picker_transform", type="S", value="PRESS")
kmi.properties.mode = "SCALE"
keymaps.append((km, kmi))
kmi = km.keymap_items.new(
"rigpicker.call_operator", type="S", value="PRESS", alt=True
)
kmi.properties.operator = "pose.scale_clear"
keymaps.append((km, kmi))
kmi = km.keymap_items.new(
"node.context_menu_picker", type="RIGHTMOUSE", value="PRESS"
)
keymaps.append((km, kmi))
kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS")
kmi.properties.mode = "SET"
keymaps.append((km, kmi))
kmi = km.keymap_items.new(
"node.rp_box_select", type="LEFTMOUSE", value="PRESS", shift=True
)
kmi.properties.mode = "EXTEND"
keymaps.append((km, kmi))
# kmi = km.keymap_items.new("node.rp_box_select", type="LEFTMOUSE", value="PRESS", ctrl=True)
kmi = km.keymap_items.new(
"node.rp_box_select", type="LEFTMOUSE", value="PRESS", alt=True
)
kmi.properties.mode = "SUBSTRACT"
keymaps.append((km, kmi))
# km = wm.keyconfigs.addon.keymaps.new(name="View2D")
# kmi = km.keymap_items.new("rigpicker.call_operator", type="MIDDLEMOUSE", value="PRESS")
# kmi.properties.operator = "bpy.ops.view2d.pan('INVOKE_DEFAULT')"
# keymaps.append((km, kmi))
def unregister_keymaps():
for km, kmi in keymaps:
km.keymap_items.remove(kmi)
keymaps.clear()
classes = (
RP_OT_box_select,
RP_OT_toogle_property,
RP_OT_reload_picker,
RP_OT_toogle_bone_layer,
RP_OT_call_operator,
RP_MT_context_menu,
RP_OT_picker_transform,
RP_OT_context_menu_picker,
RP_OT_pack_picker,
RP_OT_add_picker_source,
RP_OT_remove_picker_source,
RP_OT_fit_picker,
RP_OT_add_picker_collection,
# RP_OT_ui_draw
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
register_keymaps()
def unregister():
unregister_keymaps()
for cls in reversed(classes):
bpy.utils.unregister_class(cls)