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): scn = context.scene if scn.rig_picker.use_pick_bone: 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 = "rigpicker.reload_picker" bl_label = 'Reload Picker' #@classmethod #def poll(cls, context): # if not is_picker_space(context): # return def execute(self, context): #PICKERS.clear() if context.object.type == 'ARMATURE': rig = context.object else: collection = get_picker_collection(context.object) rig = collection.rig_picker.rig load_picker_data(rig) ''' for area in context.screen.areas: #print(area.type, is_picker_space(area.spaces.active)) if is_picker_space(area.spaces.active): print('Tag Redraw Region', area.type) area.regions[0].tag_redraw() area.tag_redraw() ''' return {"FINISHED"} class RP_OT_toogle_bone_layer(Operator): """Toogle bone layer visibility when double clicking on a bone""" bl_idname = "rigpicker.toogle_bone_layer" bl_label = '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.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 if bone: for key in bone.keys(): layout.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", 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)