diff --git a/area.py b/area.py index 3b5a358..b510c4a 100644 --- a/area.py +++ b/area.py @@ -18,6 +18,8 @@ def draw_header(self, context): if not context.space_data.tree_type == 'RigPickerTree': self._draw(context) return + + scn = context.scene layout = self.layout layout.template_header() @@ -42,6 +44,11 @@ def draw_header(self, context): if not picker_group: return + if scn.rig_picker.use_pick_bone: + layout.alert = True + layout.label(text='Auto Bone Assign') + layout.prop(scn.rig_picker, 'use_pick_bone', icon='PANEL_CLOSE', text='', emboss=False) + layout.separator_spacer() layout.label(text=ob.name) diff --git a/core/picker.py b/core/picker.py index e6b3659..4a5f1b6 100644 --- a/core/picker.py +++ b/core/picker.py @@ -3,7 +3,7 @@ import bpy import gpu from gpu_extras.batch import batch_for_shader import blf -from mathutils import bvhtree, Vector +from mathutils import bvhtree, Vector, Color from mathutils.geometry import (intersect_point_quad_2d, intersect_point_tri_2d, intersect_tri_tri_2d) from ..constants import PICKERS @@ -30,13 +30,15 @@ class Shape: self.press = False self.shader = gpu.shader.from_builtin('UNIFORM_COLOR') + self.shader.bind() #self.hover_shader = gpu.shader.from_builtin('UNIFORM_COLOR') #self.hover_shader.uniform_float("color", [1, 1, 1, 0.1]) - self.hover_color = [1, 1, 1, 0.1] + + self.color = color + self.hover_color = self.brighten_color(self.color) self.tooltip = tooltip - self.color = color self.points = points self.polygons = polygons or [] self.edges = edges or [] @@ -50,6 +52,14 @@ class Shape: def color(self): return self._color + def brighten_color(self, color): + brighten_color = Color(color[:3]) + brighten_color.v += 0.05 + brighten_color.v *= 1.1 + brighten_color.v = max(brighten_color.v, 0.15) + + return [*brighten_color, color[3]] + @color.setter def color(self, color=None): if not color: @@ -69,11 +79,12 @@ class Shape: def draw(self): - self.shader.bind() + #self.shader.bind() self.shader.uniform_float("color", self.color) if self.polygons: self.p_batch.draw(self.shader) + if self.edges: self.e_batch.draw(self.shader) @@ -105,6 +116,7 @@ class BoneShape(Shape): self.type = 'bone' self.bone = bone self.active_color = [1, 1, 1, 0.1] + self.select_color = self.brighten_color(self.hover_color) self.bone_colors = self.get_bone_colors() @@ -174,49 +186,71 @@ class BoneShape(Shape): return bone_colors - def draw(self): + def draw_hided(self): gpu.state.blend_set('ALPHA') + gpu.state.line_width_set(1.0) - if self.hide: - self.shader.uniform_float("color", (*self.color[:3], 0.4)) - self.p_batch.draw(self.shader) - #elif self.select: - # self.shader.uniform_float("color", self.bone_color) - # self.p_batch.draw(self.shader) - else: - super().draw() + line_color = (1, 1, 1, 0.1) + color = Color(self.color[:3]) + if self.hover: + color.v += 0.05 + color.v *= 1.1 - # Overlay the fill slightly with the bone color - #self.shader.uniform_float("color", (*self.bone_color[:3], 0.1)) - #self.p_batch.draw(self.shader) + line_color = (1, 1, 1, 0.3) - if self.select: - color = self.hover_color - - if self.select or self.hover: - color = self.hover_color - - if self.select and self.hover: - color = self.active_color - - self.shader.uniform_float("color", color) - self.p_batch.draw(self.shader) - - #Overlay the fill slightly with the bone color - self.shader.uniform_float("color", (*self.bone_colors['normal'][:3], 0.1)) + self.shader.uniform_float("color", (*color[:3], 0.5)) self.p_batch.draw(self.shader) + + self.shader.uniform_float("color", line_color) + self.e_batch.draw(self.shader) + gpu.state.blend_set('NONE') + def draw_zero_scaled(self): + gpu.state.blend_set('ALPHA') + gpu.state.line_width_set(1.0) - #self.contour_shader.bind() + line_color = (*self.bone_colors['normal'][:3], 0.2) + color = Color(self.color[:3]) + if self.hover or self.select: + color.v += 0.05 + color.v *= 1.1 + line_color = (*self.bone_color[:3], 0.66) - #print(self.bone_color) + self.shader.uniform_float("color", (*color[:3], 0.5)) + self.p_batch.draw(self.shader) + + self.shader.uniform_float("color", line_color) + self.e_batch.draw(self.shader) + + gpu.state.blend_set('NONE') + + def draw(self): + gpu.state.blend_set('ALPHA') + gpu.state.line_width_set(1.0) + + if self.hide: + return self.draw_hided() + + elif self.bone.custom_shape_scale_xyz.length < 0.00001: + return self.draw_zero_scaled() + + # Draw Fill + color = self.color + if self.select: + color = self.select_color + elif self.hover: + color = self.hover_color + + self.shader.uniform_float("color", color) + self.p_batch.draw(self.shader) + + # Draw Outline + gpu.state.blend_set('NONE') if self.select or self.active: gpu.state.line_width_set(2.0) - #if not self.hide: self.shader.uniform_float("color", self.bone_color) - #for b in self.contour_batches: self.e_batch.draw(self.shader) gpu.state.line_width_set(1.0) @@ -359,8 +393,8 @@ class OperatorShape(Shape): class Picker: - def __init__(self, rig, shapes): - + def __init__(self, parent, rig, shapes): + self.parent = parent self.region = bpy.context.region self.rig = rig @@ -421,7 +455,7 @@ class Picker: if shape.type=='bone' and shape.hover: shape.assign_bone_event() - bpy.ops.rigpicker.save_picker() + bpy.ops.rigpicker.save_picker(index=self.index) def press_event(self, mode='SET'): for shape in self.shapes: @@ -475,9 +509,9 @@ class Picker: def border_select(self, border, mode): border = [Vector(b) - Vector(self.translation) for b in border] - - for shape in self.shapes: + shape.press = False + shape.hover = False if shape.type != 'bone': continue @@ -519,16 +553,21 @@ class Picker: relative_center = (self.rect[1] + self.rect[3]) * 0.5 return relative_center + self.translation + @property + def index(self): + return self.parent.pickers.index(self) + class PickerGroup: def __init__(self, rig, pickers): - self.view_location = Vector((0, 0)) + #self.view_location = Vector((0, 0)) self.region = bpy.context.region self.rig = rig self.pickers = [] self.mouse = Vector((0, 0)) + self.location = Vector((0, 0)) self.hover_shape = None @@ -542,7 +581,7 @@ class PickerGroup: self.add_picker(picker) def add_picker(self, picker): - self.pickers.append(Picker(self.rig, picker)) + self.pickers.append(Picker(self, self.rig, picker)) def draw(self): y = 0 @@ -558,22 +597,23 @@ class PickerGroup: def move_event(self, mouse): self.mouse = mouse - location = self.region.view2d.region_to_view(*mouse) + self.location = self.region.view2d.region_to_view(*mouse) - view_location = self.region.view2d.region_to_view(0, 0) - if view_location != self.view_location: - self.view_location = view_location - self.tooltip = '' - if self.timer: - self.timer.cancel() - return + # Try to detect view pan to remove tooltip + # view_location = self.region.view2d.region_to_view(0, 0) + # if view_location != self.view_location: + # self.view_location = view_location + # self.tooltip = '' + # if self.timer: + # self.timer.cancel() + # return hover_shape = None for picker in self.pickers: - if not picker.under_mouse(location): + if not picker.under_mouse(self.location): continue - picker.move_event(location) + picker.move_event(self.location) if picker.hover_shape: hover_shape = picker.hover_shape @@ -593,16 +633,28 @@ class PickerGroup: self.clear_tooltip() for picker in self.pickers: - picker.press_event(mode) + if picker.under_mouse(self.location): + picker.press_event(mode) + else: + for shape in picker.shapes: + shape.press = False def release_event(self, mode='SET'): - if mode == 'SET': for bone in self.rig.data.bones: bone.select = False for picker in self.pickers: - picker.release_event(mode) + if picker.under_mouse(self.location): + picker.release_event(mode) + else: + for shape in picker.shapes: + shape.press = False + + def assign_bone_event(self): + for picker in self.pickers: + if picker.under_mouse(self.location): + picker.assign_bone_event() def border_select(self, border, mode): border = [self.region.view2d.region_to_view(*b) for b in border] diff --git a/draw_handlers.py b/draw_handlers.py index 42a070a..938cb7c 100644 --- a/draw_handlers.py +++ b/draw_handlers.py @@ -98,6 +98,7 @@ def draw_callback_px(): if not picker_group or not picker_group.tooltip: return + region = bpy.context.region text = picker_group.tooltip mouse = picker_group.tooltip_mouse ui_scale = bpy.context.preferences.system.ui_scale @@ -106,15 +107,16 @@ def draw_callback_px(): font_id = 0 blf.size(font_id, int(13 * ui_scale)) + margins = [12, 4] text_size = blf.dimensions(font_id, text) - text_pos = (mouse[0] - text_size[0]*0.33, mouse[1] - (34*ui_scale)) + #text_pos = (mouse[0] - text_size[0]*0.33, mouse[1] - (34*ui_scale)) + text_pos = (region.width - text_size[0]-margins[0], margins[1]) - bg_margins = [8, 4] - bg_pos = (text_pos[0] - bg_margins[0], text_pos[1] - bg_margins[1]-1) - bg_size = (text_size[0] + 2*bg_margins[0], text_size[1] + 2*bg_margins[1]) + bg_pos = (text_pos[0] - margins[0], text_pos[1] - margins[1]-1) + bg_size = (text_size[0] + 2*margins[0], text_size[1] + 2*margins[1]) gpu.state.blend_set('ALPHA') - draw_rect_2d(bg_pos, *bg_size, (0, 0, 0, 0.75)) + draw_rect_2d(bg_pos, *bg_size, (0.1, 0.1, 0.1, 0.75)) gpu.state.blend_set('NONE') diff --git a/operators/picker.py b/operators/picker.py index db5b443..0a94619 100644 --- a/operators/picker.py +++ b/operators/picker.py @@ -1,5 +1,5 @@ import bpy -from bpy.props import EnumProperty, IntProperty +from bpy.props import EnumProperty, IntProperty, BoolProperty from ..constants import PICKERS from ..core.bl_utils import get_view_3d_override from ..core.geometry_utils import bounding_rect @@ -9,7 +9,7 @@ from ..core.picker import get_picker_path, pack_picker, unpack_picker #from core.picker import * #from .utils import is_over_region import gpu -from mathutils import Vector +from mathutils import Vector, Euler, Matrix from gpu_extras.batch import batch_for_shader from pathlib import Path import json @@ -196,13 +196,14 @@ class RP_OT_picker_transform(bpy.types.Operator): """Tooltip""" bl_idname = "node.picker_transform" bl_label = "Move Bone in Picker View" - + mode : EnumProperty(items=[(m, m.title(), '') for m in ('TRANSLATE', 'ROTATE', 'SCALE')]) @classmethod def poll(cls, context): return is_picker_space(context) + ''' def execute(self, context): with context.temp_override(**get_view_3d_override()): if self.mode == 'TRANSLATE': @@ -213,36 +214,105 @@ class RP_OT_picker_transform(bpy.types.Operator): bpy.ops.transform.resize("INVOKE_DEFAULT") return {"FINISHED"} + ''' - # def invoke(self, context, event): - # self.mouse = event.mouse_region_x, event.mouse_region_y + def invoke(self, context, event): + self.override = get_view_3d_override() - # self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones} + if self.mode == 'TRANSLATE': + with context.temp_override(**self.override): + if event.alt: + bpy.ops.pose.loc_clear() + else: + bpy.ops.transform.translate("INVOKE_DEFAULT") + return {"FINISHED"} - # context.window_manager.modal_handler_add(self) - # return {'RUNNING_MODAL'} + elif self.mode == 'ROTATE' and event.alt: + with context.temp_override(**self.override): + bpy.ops.pose.rot_clear() + return {"FINISHED"} - # def modal(self, context, event): + elif self.mode == 'SCALE' and event.alt: + with context.temp_override(**self.override): + bpy.ops.pose.scale_clear() + return {"FINISHED"} - # delta_x = (event.mouse_region_x - self.mouse[0]) / 1000 - # delta_y = (event.mouse_region_y - self.mouse[1]) / 1000 + self.mouse = event.mouse_region_x, event.mouse_region_y - # for bone, matrix in self.bone_data.items(): - # bone.matrix.translation = matrix.translation + Vector((delta_x, 0, delta_y)) + self.center = context.active_pose_bone.matrix.translation.copy() + self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones} - # #print(delta_x, delta_y) + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} - # if event.type=="LEFTMOUSE" and event.value == 'RELEASE': - # return {'FINISHED'} + def modal(self, context, event): - # if event.type=="RIGHTMOUSE": - # for bone, matrix in self.bone_data.items(): - # bone.matrix = matrix + delta_x = (event.mouse_region_x - self.mouse[0]) / 100 + #delta_y = (event.mouse_region_y - self.mouse[1]) / 1000 - # return {'CANCELLED'} + #for bone, matrix in self.bone_data.items(): + # bone.matrix.#translation = matrix.translation + Vector((delta_x, 0, delta_y)) + center = context.active_pose_bone.tail - # return {'RUNNING_MODAL'} + scn = context.scene + cam = scn.camera + + + + #rotation_axis = cam.matrix_world.to_3x3().transposed().col[2] + #rotation_matrix = Matrix.Rotation(delta_x, 4, Vector((0, 0, -1)) @ cam.matrix_world) + view_vec = Vector((0, 0, 1)) + view_vec.rotate(cam.matrix_world) + rotation_matrix = Matrix.Rotation(delta_x, 4, view_vec) + + #rot_mat = Matrix.Rotation(delta_x, 4, "Z") + #rot_mat = scn.camera.matrix_world.inverted() @ rot_mat + #rot_mat = Matrix.LocRotScale(center, Euler((0, 0, delta_x)), (1, 1, 1)) + + if event.type == "MOUSEMOVE": + for bone, matrix in self.bone_data.items(): + #center = + + mat = matrix.copy() + mat.translation -= self.center + mat = rotation_matrix @ mat + mat.translation += self.center + + bone.matrix = mat + #rotation_matrix.translation = matrix.translation + + #mat = matrix.copy().to_3x3() + #mat.rotate(rotation_matrix) + #mat = mat.to_4x4() + #mat.translation = matrix.translation + + #bone.matrix = mat + + #bone.matrix = matrix.inverted() @ (rotation_matrix @ bone_mat) + #bone.matrix.translation = 0, 0, 0 + #bone.matrix = rotation_matrix @ matrix + #bone.matrix = matrix @ (matrix.inverted() @ rotation_matrix) + #bone.matrix.translation = matrix.translation + + #print('Rotate', delta_x) + #with context.temp_override(**get_view_3d_override()): + #for bone, matrix in self.bone_data.items(): + # bone.rotation_euler.x = delta_x + #bpy.ops.transform.rotate(value=delta_x) + + #print(delta_x, delta_y) + + if event.type=="LEFTMOUSE" and event.value == 'RELEASE': + return {'FINISHED'} + + if event.type == "RIGHTMOUSE": + for bone, matrix in self.bone_data.items(): + bone.matrix = matrix + + return {'CANCELLED'} + + return {'RUNNING_MODAL'} class RP_OT_function_execute(bpy.types.Operator): @@ -534,18 +604,28 @@ def register_keymaps(): kmi.properties.mode = 'TRANSLATE' keymaps.append((km, kmi)) + kmi = km.keymap_items.new("node.picker_transform", type="G", value="PRESS", alt=True) + kmi.properties.mode = 'TRANSLATE' + 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("node.picker_transform", type="R", value="PRESS", alt=True) + kmi.properties.mode = 'ROTATE' + 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("node.context_menu_picker", type="RIGHTMOUSE", value="PRESS") + kmi = km.keymap_items.new("node.picker_transform", type="S", value="PRESS", alt=True) + kmi.properties.mode = 'SCALE' 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' diff --git a/operators/shape.py b/operators/shape.py index cafb196..8687b58 100644 --- a/operators/shape.py +++ b/operators/shape.py @@ -1,9 +1,11 @@ import json +from os.path import abspath from pathlib import Path -from mathutils import Matrix import bpy from bpy.types import Operator +from bpy.props import IntProperty +from mathutils import Matrix from ..core.shape import get_picker_data from ..core.addon_utils import is_shape @@ -184,10 +186,27 @@ class RP_OT_save_picker(Operator): bl_label = 'Store UI Data' bl_idname = 'rigpicker.save_picker' + index : IntProperty(default=-1) + def execute(self, context): scn = context.scene ob = context.object - collection = next((c for c in ob.users_collection if c.rig_picker.enabled), None) + + print('SAve Picker', self.index) + + if self.index == -1: + collection = next((c for c in ob.users_collection if c.rig_picker.enabled), None) + else: + source = context.active_object.data.rig_picker.sources[self.index].source + source = Path(bpy.path.abspath(source, library=ob.data.library)).resolve() + collection = next((c for c in set(scn.collection.children_recursive) if c.rig_picker.enabled + and Path(bpy.path.abspath(c.rig_picker.destination)).resolve() == source), None) + + print('source', source) + print('colleciton', collection) + if not collection: + self.report({"ERROR"}, 'No Picker found') + return {'CANCELLED'} canvas = collection.rig_picker.canvas rig = collection.rig_picker.rig