From b28e80d6d56f314d0be11c0e105539c3c4527bf4 Mon Sep 17 00:00:00 2001 From: ChristopheSeux Date: Thu, 9 Nov 2023 10:29:56 +0100 Subject: [PATCH] refacto --- .vscode/settings.json | 4 + __init__.py | 19 +- _draw_handler.py | 289 ------------------------ core/__init__.py | 0 core/addon_utils.py | 43 ++++ core/bl_utils.py | 119 ++++++++++ core/geometry_utils.py | 49 ++++ picker.py => core/picker.py | 118 +++------- func_shape.py => core/shape.py | 58 ++++- draw_handlers.py | 91 ++++++++ func_picker.py | 248 -------------------- gizmo.py | 2 +- operators/__init__.py | 20 ++ op_material.py => operators/material.py | 0 op_picker.py => operators/picker.py | 134 +++++++---- op_shape.py => operators/shape.py | 6 +- panels.py | 3 +- snapping_utils.py | 209 ----------------- utils.py | 215 ------------------ 19 files changed, 515 insertions(+), 1112 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 _draw_handler.py create mode 100644 core/__init__.py create mode 100644 core/addon_utils.py create mode 100644 core/bl_utils.py create mode 100644 core/geometry_utils.py rename picker.py => core/picker.py (87%) rename func_shape.py => core/shape.py (66%) create mode 100644 draw_handlers.py delete mode 100644 func_picker.py create mode 100644 operators/__init__.py rename op_material.py => operators/material.py (100%) rename op_picker.py => operators/picker.py (80%) rename op_shape.py => operators/shape.py (97%) delete mode 100644 snapping_utils.py delete mode 100644 utils.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cc67606 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.linting.pylintEnabled": true, + "python.linting.enabled": true +} \ No newline at end of file diff --git a/__init__.py b/__init__.py index e507598..34e9890 100644 --- a/__init__.py +++ b/__init__.py @@ -12,29 +12,20 @@ bl_info = { import importlib -modules = [ - '.op_material', - '.op_picker', - '.op_shape', +modules = ( + '.operators', '.properties', '.panels', '.area', '.gizmo', - '.picker' -] - -functions = [ - #'.func_picker', - '.func_shape', - '.snapping_utils', - '.utils' -] + '.draw_handlers' +) import bpy if "bpy" in locals(): if not bpy.app.background: - for name in modules + functions: + for name in modules: module = importlib.import_module(name, __name__) importlib.reload(module) diff --git a/_draw_handler.py b/_draw_handler.py deleted file mode 100644 index cd4ebe9..0000000 --- a/_draw_handler.py +++ /dev/null @@ -1,289 +0,0 @@ -import bpy -import blf -import gpu - -from mathutils import Vector -from .func_picker import * -from .utils import intersect_rectangles, point_inside_rectangle,\ -point_over_shape, border_over_shape, canvas_space - -from gpu_extras.batch import batch_for_shader -from .constants import PICKERS -from .picker import Picker - -""" -def draw_polygon_2d(verts, indices, loops, color, contour, width=None): - dpi = int(bpy.context.user_preferences.system.pixel_size) - - bgl.glColor4f(*color) - gpu.state.blend_set('ALPHA') - #bgl.glEnable(bgl.GL_LINE_SMOOTH) - - bgl.glColor4f(*color) - for face in faces: - bgl.glBegin(bgl.GL_POLYGON) - for v_index in face: - coord = verts[v_index] - bgl.glVertex2f(coord[0],coord[1]) - bgl.glEnd() - - if width: - gpu.state.line_width_set(width*dpi) - bgl.glColor4f(*contour) - - for loop in loops: - if faces: - bgl.glBegin(bgl.GL_LINE_LOOP) - else: - bgl.glBegin(bgl.GL_LINE_STRIP) - for v_index in loop: - coord = verts[v_index] - bgl.glVertex2f(coord[0],coord[1]) - bgl.glEnd() - - gpu.state.blend_set('NONE') - #bgl.glDisable(bgl.GL_LINE_SMOOTH) - bgl.glEnd() - - return - -def draw_border(border): - gpu.state.blend_set('ALPHA') - bgl.glColor4f(1,1,1,0.2) - bgl.glBegin(bgl.GL_POLYGON) - for v in border: - bgl.glVertex2f(v[0],v[1]) - - bgl.glEnd() - - bgl.glColor4f(0.0, 0.0, 0.0, 0.5) - gpu.state.line_width_set(2.0) - bgl.glLineStipple(3, 0xAAAA) - bgl.glEnable(bgl.GL_LINE_STIPPLE) - - bgl.glBegin(bgl.GL_LINE_LOOP) - - for v in border: - bgl.glVertex2f(v[0],v[1]) - - bgl.glEnd() - - bgl.glColor4f(1.0, 1.0, 1.0, 1) - gpu.state.line_width_set(1.0) - bgl.glBegin(bgl.GL_LINE_LOOP) - for v in border: - bgl.glVertex2f(v[0],v[1]) - - bgl.glEnd() - bgl.glDisable(bgl.GL_LINE_STIPPLE) - gpu.state.blend_set('NONE') - -def draw_text(mouse,text,color): - dpi = int(bpy.context.user_preferences.system.pixel_size) - - gpu.state.blend_set('ALPHA') - font_id =0 # XXX, need to find out how best to get this. - # draw some text - bgl.glColor4f(0,0,0,0.75) - blf.blur(font_id,5) - blf.position(font_id, mouse[0]+10*dpi, mouse[1]-20*dpi, 0) - blf.size(font_id, 9*dpi, 96) - blf.draw(font_id, text) - - bgl.glEnd() - bgl.glColor4f(*color) - blf.blur(font_id,0) - blf.draw(font_id, text) - gpu.state.blend_set('NONE') - - -def select_bone(self,context,event): - ob = context.object - if ob and ob.type =='ARMATURE' and ob.data.rig_picker: - shape_data = ob.data.rig_picker - selected_bones = [b for b in ob.pose.bones if b.bone.select] - - if not event.shift and not event.alt: - for b in ob.pose.bones: - b.bone.select= False - - for shape in [s for s in shape_data['shapes'] if not s['shape_type']==["DISPLAY"]]: - points = [canvas_space(p,self.scale,self.offset) for p in shape['points']] - bound = [canvas_space(p,self.scale,self.offset) for p in shape['bound']] - loops = shape['loops'] - - ## Colision check - over = False - if self.is_border: - if intersect_rectangles(self.border,bound): #start check on over bound_box - over = border_over_shape(self.border,points,loops) - else: - if point_inside_rectangle(self.end,bound): - over = point_over_shape(self.end,points,loops) - - if over: - if shape['shape_type'] == 'BONE': - bone = context.object.pose.bones.get(shape['bone']) - if bone: - if event.alt: - bone.bone.select = False - else: - bone.bone.select = True - context.object.data.bones.active = bone.bone - - if shape['shape_type'] == 'FUNCTION' and event.value== 'RELEASE' and not self.is_border: - # restore selection - for b in selected_bones: - b.bone.select = True - - function = shape['function'] - if shape.get('variables'): - variables=shape['variables'].to_dict() - - else: - variables={} - - variables['event']=event - - print(variables) - - globals()[function](variables) - -""" - - - -def draw_callback_view(): - ob = bpy.context.object - - if not ob or ob.type !='ARMATURE' or 'shapes' not in ob.data.rig_picker.keys(): - return - - if ob not in PICKERS: - shapes = [s.to_dict() for s in ob.data.rig_picker['shapes']] - PICKERS[ob] = Picker(ob, shapes=shapes) - - picker = PICKERS.get(ob) - picker.draw() - -handle_view = None -handle_pixel = None - -def register(): - global handle_view, handle_pixel - - handle_view = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_view, (), 'WINDOW', 'POST_VIEW') - handle_pixel = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_px, (), 'WINDOW', 'POST_PIXEL') - -def unregister(): - global handle_view, handle_pixel - - bpy.types.SpaceNodeEditor.draw_handler_remove(handle_view, 'WINDOW') - bpy.types.SpaceNodeEditor.draw_handler_remove(handle_pixel, 'WINDOW') - - handle_view = None - handle_pixel = None - -''' - return - - shape_data = ob.data.rig_picker - rig_layers = [i for i,l in enumerate(ob.data.layers) if l] - - r = context.region - #self.scale = region.height - - #draw BG - canvas = shape_data['shapes'][0] - #bg_point = [(0, r.height), (r.width, r.height), (r.width, 0),(0, 0)] - bg_color = [*canvas['color'], 1] - draw_polygon_2d(bg_point,[[0,1,2,3]],[[0,1,2,3]], bg_color,(0,0,0,1),0) - - show_tooltip = False - for shape in shape_data['shapes']: - - color = shape['color'] - points = [canvas_space(p,self.scale,self.offset) for p in shape['points']] - bound = [canvas_space(p,self.scale,self.offset) for p in shape['bound']] - loops = shape['loops'] - faces = shape['faces'] - - select=None - contour_color = [0,0,0] - contour_alpha = 1 - width = 0 - shape_color = [c for c in color] - shape_alpha = 1 - - - if shape['shape_type'] == 'DISPLAY' and not faces: - width = 1 - - if shape['shape_type'] != 'DISPLAY': - if shape['shape_type'] == 'BONE': - bone = ob.pose.bones.get(shape['bone']) - if bone: - b_layers = [i for i,l in enumerate(bone.bone.layers) if l] - - if bone.bone_group: - group_color = list(bone.bone_group.colors.normal) - contour_color = [c*0.85 for c in group_color] - width = 1 - - if bone.bone.select : - shape_color = [c*1.2+0.1 for c in color] - if bone.bone_group: - contour_color = [0.05,0.95,0.95] - - if ob.data.bones.active and shape['bone'] == ob.data.bones.active.name: - if bone.bone_group: - if bone.bone.select : - shape_color = [c*1.2+0.2 for c in color] - contour_color = [1,1,1] - width = 1.5 - else: - shape_color = [c*1.2+0.15 for c in color] - contour_color = [0.9,0.9,0.9] - width = 1 - - - if bone.bone.hide or not len(set(b_layers).intersection(rig_layers)): - shape_alpha = 0.33 - contour_alpha = 0.33 - - elif shape['shape_type'] == 'FUNCTION': - if shape['function'] == 'boolean': - path = shape['variables']['data_path'] - - if ob.path_resolve(path): - shape_color = [c*1.4+0.08 for c in color] - else: - shape_color = [color[0],color[1],color[2]] - - - ## On mouse over checking - over = False - if self.is_border: - if intersect_rectangles(self.border,bound): #start check on over bound_box - over = border_over_shape(self.border,points,loops) - else: - if point_inside_rectangle(self.end,bound): - over = point_over_shape(self.end,points,loops) - - if over: - show_tooltip = True - tooltip = shape['tooltip'] - if not self.press: - shape_color = [c*1.02+0.05 for c in shape_color] - contour_color = [c*1.03+0.05 for c in contour_color] - - shape_color.append(shape_alpha) - contour_color.append(contour_alpha) - draw_polygon_2d(points,faces,loops,shape_color,contour_color,width) - - if show_tooltip: - draw_text(self.end,tooltip,(1,1,1,1)) - - if self.is_border: - draw_border(self.border) -''' \ No newline at end of file diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/addon_utils.py b/core/addon_utils.py new file mode 100644 index 0000000..9470719 --- /dev/null +++ b/core/addon_utils.py @@ -0,0 +1,43 @@ + +import bpy +from .bl_utils import get_mat + +def is_shape(ob): + scn = bpy.context.scene + canvas = scn.rig_picker.canvas + if not canvas or ob.hide_render: + return False + + shapes = {ob for col in canvas.users_collection for ob in col.all_objects} + + if ob.type in ('MESH', 'CURVE', 'FONT') and ob in shapes: + return True + + return False + +def get_object_color(ob): + if not ob.data.materials: + return + + mat = get_mat(ob) + if not mat or not mat.node_tree or not mat.node_tree.nodes: + return + + emit_node = mat.node_tree.nodes.get('Emission') + if not emit_node: + return + + return emit_node.inputs['Color'].default_value + +def get_operator_from_id(idname): + if not '.' in idname: + return + + m, o = idname.split(".") + try: + op = getattr(getattr(bpy.ops, m), o) + op.get_rna_type() + except Exception: + return + + return op \ No newline at end of file diff --git a/core/bl_utils.py b/core/bl_utils.py new file mode 100644 index 0000000..4a08126 --- /dev/null +++ b/core/bl_utils.py @@ -0,0 +1,119 @@ + +import bpy + + +def get_view_3d_override(): + windows = bpy.context.window_manager.windows + areas = [a for w in windows for a in w.screen.areas if a.type == 'VIEW_3D'] + + if not areas: + print('No view 3d found') + return + + view_3d = None + for area in areas: + if area.spaces.active.camera: + view_3d = area + + if not view_3d: + view_3d = max(areas, key=lambda x :x.width) + + return {'area': view_3d, 'region': view_3d.regions[-1]} + +def get_mat(ob): + for sl in ob.material_slots: + if sl.material: + return sl.material + +def link_mat_to_object(ob): + for sl in ob.material_slots: + m = sl.material + sl.link = 'OBJECT' + sl.material = m + +def find_mirror(name): + mirror = None + prop= False + + if name: + + if name.startswith('[')and name.endswith(']'): + prop = True + name= name[:-2][2:] + + match={ + 'R': 'L', + 'r': 'l', + 'L': 'R', + 'l': 'r', + } + + separator=['.','_'] + + if name.startswith(tuple(match.keys())): + if name[1] in separator: + mirror = match[name[0]]+name[1:] + + if name.endswith(tuple(match.keys())): + if name[-2] in separator: + mirror = name[:-1]+match[name[-1]] + + if mirror and prop == True: + mirror='["%s"]'%mirror + + return mirror + + else: + return None + +def hide_layers(args): + """ """ + ob = bpy.context.object + + layers = [] + for bone in [b for b in ob.pose.bones if b.bone.select]: + for i,l in enumerate(bone.bone.layers): + if l and i not in layers: + layers.append(i) + + for i in layers: + ob.data.layers[i] = not ob.data.layers[i] + +def select_layer(args): + ob = bpy.context.object + + layers =[] + for bone in [b for b in ob.pose.bones if b.bone.select]: + bone_layers = [i for i,l in enumerate(bone.bone.layers) if l] + + for l in bone_layers: + if l not in layers: + layers.append(l) + + for bone in ob.pose.bones: + bone_layers = [i for i,l in enumerate(bone.bone.layers) if l] + + if len(set(bone_layers).intersection(layers)): + bone.bone.select = True + +def hide_bones(args): + ob = bpy.context.object + selected_bone = [b for b in ob.pose.bones if b.bone.select] + + hide = [b.bone.hide for b in selected_bone if not b.bone.hide] + + visibility = True if len(hide) else False + + for bone in selected_bone: + bone.bone.hide = visibility + +def select_all(args): + ob = bpy.context.object + shapes = ob.data.rig_picker['shapes'] + bones = [s['bone'] for s in shapes if s['shape_type']=='BONE'] + + for bone_name in bones: + bone = ob.pose.bones.get(bone_name) + + if bone: + bone.bone.select = True diff --git a/core/geometry_utils.py b/core/geometry_utils.py new file mode 100644 index 0000000..f061898 --- /dev/null +++ b/core/geometry_utils.py @@ -0,0 +1,49 @@ + +import bpy +from mathutils import Vector +from mathutils.geometry import intersect_line_line_2d + + +def is_over_region(self,context,event): + inside = 2 < event.mouse_region_x < context.region.width -2 and \ + 2 < event.mouse_region_y < context.region.height -2 and \ + [a for a in context.screen.areas if a.as_pointer()==self.adress] and \ + not context.screen.show_fullscreen + + return inside + +def bound_box_center(ob): + points = [ob.matrix_world@Vector(p) for p in ob.bound_box] + + x = [v[0] for v in points] + y = [v[1] for v in points] + z = [v[2] for v in points] + + return (sum(x) / len(points), sum(y) / len(points),sum(z) / len(points)) + + +def intersect_rectangles(bound, border): # returns None if rectangles don't intersect + dx = min(border[1][0],bound[1][0]) - max(border[0][0],bound[0][0]) + dy = min(border[0][1],bound[0][1]) - max(border[2][1],bound[2][1]) + + if (dx>=0) and (dy>=0): + return dx*dy + +def point_inside_rectangle(point, rect): + return rect[0][0]< point[0]< rect[1][0] and rect[2][1]< point[1]< rect[0][1] + +def point_over_shape(point,verts,loops,outside_point=(-1,-1)): + out = Vector(outside_point) + pt = Vector(point) + + intersections = 0 + for loop in loops: + for i,p in enumerate(loop): + a = Vector(verts[loop[i-1]]) + b = Vector(verts[p]) + + if intersect_line_line_2d(pt,out,a,b): + intersections += 1 + + if intersections%2 == 1: #chek if the nb of intersection is odd + return True \ No newline at end of file diff --git a/picker.py b/core/picker.py similarity index 87% rename from picker.py rename to core/picker.py index 1e3aa94..6c2e71c 100644 --- a/picker.py +++ b/core/picker.py @@ -2,15 +2,16 @@ import bpy import gpu from gpu_extras.batch import batch_for_shader -import gpu +import blf from mathutils import bvhtree, Vector from mathutils.geometry import intersect_point_quad_2d, intersect_point_tri_2d, intersect_tri_tri_2d -from .constants import PICKERS -from .utils import get_operator_from_id +from ..constants import PICKERS +from .addon_utils import get_operator_from_id from pathlib import Path import re import json +import os import threading @@ -383,8 +384,6 @@ class OperatorShape(Shape): gpu.state.blend_set('NONE') - - class Picker: def __init__(self, rig, shapes): @@ -474,6 +473,8 @@ class Picker: s.release_event(mode) s.press = False + + #bpy.context.area.tag_redraw() def tooltip_event(self): @@ -538,9 +539,12 @@ class Picker: self.mouse = location location = self.region.view2d.region_to_view(*location) - for s in self.shapes: - if s.move_event(location): - self.hover_shape = s + self.hover_shape = None + for shape in reversed(self.shapes): + if self.hover_shape: + shape.hover = False + elif shape.move_event(location): + self.hover_shape = shape #if point_inside_rectangle(self.end, bound): # over = point_over_shape(self.end,points, edges) @@ -581,86 +585,26 @@ class Picker: gpu.state.blend_set('NONE') ''' - -def draw_callback_view(): - sp = bpy.context.space_data - - if not sp or not sp.tree_type == 'RigPickerTree': +def get_picker_path(rig, start=None): + picker_path = rig.data.get('rig_picker', {}).get('source') + if not picker_path: return - ob = bpy.context.object - if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.source: + picker_path = bpy.path.abspath(picker_path, library=rig.data.library, start=start) + + return Path(os.path.abspath(picker_path)) + +def pack_picker(rig, start=None): + picker_path = get_picker_path(rig, start=start) + if picker_path and picker_path.exists(): + if 'rig_picker' not in rig.data.keys(): + rig.data['rig_picker'] = {} + + rig.data['rig_picker']['picker'] = json.loads(picker_path.read_text()) + +def unpack_picker(rig): + if 'rig_picker' not in rig.data.keys(): return - if ob not in PICKERS: - if 'picker' in ob.data.rig_picker: - picker_datas = [s.to_dict() for s in ob.data.rig_picker['picker']] - else: - picker_path = Path(bpy.path.abspath(ob.data.rig_picker.source, library=ob.data.library)) - - if not picker_path.exists(): - print(f'Picker path not exists: {picker_path.resolve()}') - return - - print('Load picker from', picker_path.resolve()) - picker_datas = json.loads(picker_path.read_text()) - #shapes = [s.to_dict() for s in ob.data.rig_picker['shapes']] - PICKERS[ob] = Picker(ob, shapes=picker_datas) - - picker = PICKERS.get(ob) - picker.draw() - -def draw_callback_px(): - sp = bpy.context.space_data - if not sp.tree_type == 'RigPickerTree': - return - - ob = bpy.context.object - - picker = PICKERS.get(ob) - if not picker or not picker.tooltip: - return - - text = picker.tooltip - - #print('Draw text', text) - - font_id = 0 - #blf.dimensions(font_id, text) - blf.enable(font_id, blf.SHADOW) - #gpu.state.blend_set('ALPHA') - - - # BLF drawing routine - blf.position(font_id, picker.tooltip_mouse[0]-5, picker.tooltip_mouse[1]+5, 0) - blf.size(font_id, 14) - blf.color(font_id, 1, 1, 1, 1) - - blf.shadow(font_id , 5, 0.0, 0.0, 0.0, 1) - blf.shadow_offset(font_id, 2, -2) - - blf.draw(font_id, text) - - blf.disable(font_id, blf.SHADOW) - #gpu.state.blend_set('NONE') - -handle_view = None -handle_pixel = None - - -def register(): - global handle_view - global handle_pixel - - handle_view = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_view, (), 'WINDOW', 'POST_VIEW') - handle_pixel = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_px, (), 'WINDOW', 'POST_PIXEL') - -def unregister(): - global handle_view - global handle_pixel - - bpy.types.SpaceNodeEditor.draw_handler_remove(handle_view, 'WINDOW') - bpy.types.SpaceNodeEditor.draw_handler_remove(handle_pixel, 'WINDOW') - - handle_view = None - handle_pixel = None \ No newline at end of file + if 'picker' in rig.data['rig_picker'].keys(): + del rig.data['rig_picker']['picker'] \ No newline at end of file diff --git a/func_shape.py b/core/shape.py similarity index 66% rename from func_shape.py rename to core/shape.py index 1cf39a1..dcc15ce 100644 --- a/func_shape.py +++ b/core/shape.py @@ -2,9 +2,65 @@ import bpy import bmesh from mathutils import Vector, Matrix from bpy_extras import mesh_utils -from .utils import bound_box_center, contour_loops, get_object_color +from .geometry_utils import bound_box_center +from .addon_utils import get_object_color + +def border_over_shape(border,verts,loops): + for loop in loops: + for i,p in enumerate(loop): + a = Vector(verts[loop[i-1]]) + b = Vector(verts[p]) + + for j in range(0,4): + c = border[j-1] + d = border[j] + if intersect_line_line_2d(a,b,c,d): + return True + + for point in verts: + if point_inside_rectangle(point,border): + return True + + for point in border: + if point_over_shape(point,verts,loops): + return True + + +def border_loop(vert, loop): + border_edge =[e for e in vert.link_edges if e.is_boundary] + + if border_edge: + for edge in border_edge: + other_vert = edge.other_vert(vert) + + if not other_vert in loop: + loop.append(other_vert) + border_loop(other_vert, loop) + + return loop + else: + return [vert] + +def contour_loops(bm, vert_index=0, loops=None, vert_indices=None): + loops = loops or [] + vert_indices = vert_indices or [v.index for v in bm.verts] + + bm.verts.ensure_lookup_table() + + loop = border_loop(bm.verts[vert_index], [bm.verts[vert_index]]) + if len(loop) >1: + loops.append(loop) + + for v in loop: + vert_indices.remove(v.index) + + if len(vert_indices): + contour_loops(bm, vert_indices[0], loops, vert_indices) + + return loops + def get_picker_datas(objects, canvas, rig): picker_datas = [] gamma = 1 / 2.2 diff --git a/draw_handlers.py b/draw_handlers.py new file mode 100644 index 0000000..901b62c --- /dev/null +++ b/draw_handlers.py @@ -0,0 +1,91 @@ + +import bpy +import blf +from pathlib import Path +from .constants import PICKERS +from .core.picker import Picker +import json + + +def draw_callback_view(): + sp = bpy.context.space_data + + if not sp or not sp.tree_type == 'RigPickerTree': + return + + ob = bpy.context.object + if not ob or ob.type !='ARMATURE' or not ob.data.rig_picker.source: + return + + if ob not in PICKERS: + if 'picker' in ob.data.rig_picker: + picker_datas = [s.to_dict() for s in ob.data.rig_picker['picker']] + else: + picker_path = Path(bpy.path.abspath(ob.data.rig_picker.source, library=ob.data.library)) + + if not picker_path.exists(): + print(f'Picker path not exists: {picker_path.resolve()}') + return + + print('Load picker from', picker_path.resolve()) + picker_datas = json.loads(picker_path.read_text()) + #shapes = [s.to_dict() for s in ob.data.rig_picker['shapes']] + PICKERS[ob] = Picker(ob, shapes=picker_datas) + + picker = PICKERS.get(ob) + picker.draw() + +def draw_callback_px(): + sp = bpy.context.space_data + if not sp.tree_type == 'RigPickerTree': + return + + ob = bpy.context.object + + picker = PICKERS.get(ob) + if not picker or not picker.tooltip: + return + + text = picker.tooltip + + #print('Draw text', text) + + font_id = 0 + #blf.dimensions(font_id, text) + blf.enable(font_id, blf.SHADOW) + #gpu.state.blend_set('ALPHA') + + + # BLF drawing routine + blf.position(font_id, picker.tooltip_mouse[0]-5, picker.tooltip_mouse[1]+5, 0) + blf.size(font_id, 14) + blf.color(font_id, 1, 1, 1, 1) + + blf.shadow(font_id , 5, 0.0, 0.0, 0.0, 1) + blf.shadow_offset(font_id, 2, -2) + + blf.draw(font_id, text) + + blf.disable(font_id, blf.SHADOW) + #gpu.state.blend_set('NONE') + +handle_view = None +handle_pixel = None + + +def register(): + global handle_view + global handle_pixel + + handle_view = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_view, (), 'WINDOW', 'POST_VIEW') + handle_pixel = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_px, (), 'WINDOW', 'POST_PIXEL') + +def unregister(): + global handle_view + global handle_pixel + + bpy.types.SpaceNodeEditor.draw_handler_remove(handle_view, 'WINDOW') + bpy.types.SpaceNodeEditor.draw_handler_remove(handle_pixel, 'WINDOW') + + handle_view = None + handle_pixel = None \ No newline at end of file diff --git a/func_picker.py b/func_picker.py deleted file mode 100644 index f1c383c..0000000 --- a/func_picker.py +++ /dev/null @@ -1,248 +0,0 @@ -import bpy - -#from. insert_keyframe import insert_keyframe -from. snapping_utils import * -from .utils import get_IK_bones - -try: - from rigutils.driver_utils import split_path - from rigutils.insert_keyframe import insert_keyframe - from rigutils.snap_ik_fk import snap_ik_fk - from rigutils.utils import find_mirror -except: - print('You need to install the rigutils module in your blender modules path') - -def hide_layers(args): - """ """ - ob = bpy.context.object - - layers=[] - for bone in [b for b in ob.pose.bones if b.bone.select]: - for i,l in enumerate(bone.bone.layers): - if l and i not in layers: - layers.append(i) - - for i in layers: - ob.data.layers[i] = not ob.data.layers[i] - - -def boolean(args): - """ data_path, keyable """ - ob = bpy.context.object - data_path = args['data_path'] - keyable = args['keyable'] - - #bone,prop = split_path(data_path) - - try: - value = ob.path_resolve(data_path) - #setattr(ob.pose.bones.get(bone),'["%s"]'%prop,not value) - try: - exec("ob.%s = %s"%(data_path,not value)) - except: - exec("ob%s= %s"%(data_path,not value)) - - if keyable and bpy.context.scene.tool_settings.use_keyframe_insert_auto: - - if not ob.animation_data: - ob.animation_data_create() - - ob.keyframe_insert(data_path = data_path ,group = bone) - - except ValueError: - print("Property don't exist") - - -def select_layer(args): - ob = bpy.context.object - - layers =[] - for bone in [b for b in ob.pose.bones if b.bone.select]: - bone_layers = [i for i,l in enumerate(bone.bone.layers) if l] - - for l in bone_layers: - if l not in layers: - layers.append(l) - - for bone in ob.pose.bones: - bone_layers = [i for i,l in enumerate(bone.bone.layers) if l] - - if len(set(bone_layers).intersection(layers)): - bone.bone.select = True - -def hide_bones(args): - ob = bpy.context.object - selected_bone = [b for b in ob.pose.bones if b.bone.select] - - hide = [b.bone.hide for b in selected_bone if not b.bone.hide] - - visibility = True if len(hide) else False - - for bone in selected_bone: - bone.bone.hide = visibility - - -def select_all(args): - ob = bpy.context.object - shapes = ob.data.rig_picker['shapes'] - bones = [s['bone'] for s in shapes if s['shape_type']=='BONE'] - - for bone_name in bones: - bone = ob.pose.bones.get(bone_name) - - if bone: - bone.bone.select = True - -def select_bones(args): - """bones (name list)""" - ob = bpy.context.object - pBones = ob.pose.bones - bones_name =args['bones'] - event = args['event'] - if not event.shift: - for bone in bpy.context.object.pose.bones: - bone.bone.select = False - - bones = [pBones.get(b) for b in bones_name] - - select = False - for bone in bones: - if bone.bone.select == False: - select =True - break - - for bone in bones: - bone.bone.select = select - ob.data.bones.active = bones[-1].bone - -def keyframe_bones(args): - print(args) - event=args['event'] - bones=[] - - for bone in bpy.context.object.pose.bones: - if not bone.name.startswith(('DEF','ORG','MCH')) and not bone.get('_unkeyable_') ==1: - if event.shift: - bones.append(bone) - elif not event.shift and bone.bone.select : - bones.append(bone) - - - for bone in bones: - insert_keyframe(bone) - -def reset_bones(args): - event=args['event'] - avoid_value =args['avoid_value'] - - ob = bpy.context.object - - bones=[] - for bone in bpy.context.object.pose.bones: - if not bone.name.startswith(('DEF','ORG','MCH')) and not bone.get('_unkeyable_') ==1: - if event.shift: - bones.append(bone) - elif not event.shift and bone.bone.select : - bones.append(bone) - - - for bone in bones: - if bone.rotation_mode =='QUATERNION': - bone.rotation_quaternion = 1, 0, 0, 0 - - if bone.rotation_mode == 'AXIS_ANGLE': - bone.rotation_axis_angle = 0, 0, 1, 0 - - else: - bone.rotation_euler = 0, 0, 0 - - bone.location = 0, 0, 0 - bone.scale = 1, 1, 1 - - for key,value in bone.items(): - if key not in avoid_value and type(value) in (int,float): - if ob.data.get("DefaultValues") and ob.data.DefaultValues['bones'].get(bone.name): - - if key in ob.data.DefaultValues['bones'][bone.name]: - bone[key] = ob.data.DefaultValues['bones'][bone.name][key] - - else: - if type(value)== int: - bone[key]=0 - else: - bone[key]=0.0 - else: - if type(value)== int: - bone[key]=0 - else: - bone[key]=0.0 - - if bpy.context.scene.tool_settings.use_keyframe_insert_auto: - insert_keyframe(bone) - - -def flip_bones(args): - event=args['event'] - - ob = bpy.context.object - arm = bpy.context.object.pose.bones - - selected_bones = [bone for bone in ob.pose.bones if bone.bone.select==True ] - mirrorActive = None - - for bone in selected_bones: - boneName = bone.name - mirrorBoneName= find_mirror(boneName) - - mirrorBone = ob.pose.bones.get(mirrorBoneName) if mirrorBoneName else None - - if bpy.context.active_pose_bone == bone: - mirrorActive = mirrorBone - - #print(mirrorBone) - if not event.shift and mirrorBone: - bone.bone.select = False - - if mirrorBone: - mirrorBone.bone.select = True - if mirrorActive: - ob.data.bones.active = mirrorActive.bone - -def snap_ikfk(args): - """ way, chain_index """ - - way =args['way'] - chain_index = args['chain_index'] - #auto_switch = self.auto_switch - - ob = bpy.context.object - armature = ob.data - - SnappingChain = armature.get('SnappingChain') - - poseBone = ob.pose.bones - dataBone = ob.data.bones - - IKFK_chain = SnappingChain['IKFK_bones'][chain_index] - switch_prop = IKFK_chain['switch_prop'] - - FK_root = poseBone.get(IKFK_chain['FK_root']) - FK_mid = [poseBone.get(b['name']) for b in IKFK_chain['FK_mid']] - FK_tip = poseBone.get(IKFK_chain['FK_tip']) - - IK_last = poseBone.get(IKFK_chain['IK_last']) - IK_tip = poseBone.get(IKFK_chain['IK_tip']) - IK_pole = poseBone.get(IKFK_chain['IK_pole']) - - invert = IKFK_chain['invert_switch'] - - ik_fk_layer = (IKFK_chain['FK_layer'],IKFK_chain['IK_layer']) - - - for lock in ('lock_ik_x','lock_ik_y','lock_ik_z'): - if getattr(IK_last,lock): - full_snapping = False - break - - - snap_ik_fk(ob,way,switch_prop,FK_root,FK_tip,IK_last,IK_tip,IK_pole,FK_mid,full_snapping,invert,ik_fk_layer,auto_switch=True) diff --git a/gizmo.py b/gizmo.py index 504c95a..f0a16b2 100644 --- a/gizmo.py +++ b/gizmo.py @@ -6,7 +6,7 @@ from bpy.types import (AddonPreferences, GizmoGroup, Operator, Gizmo) from mathutils import Vector, Matrix, Euler from .constants import PICKERS -from .picker import Picker +from .core.picker import Picker diff --git a/operators/__init__.py b/operators/__init__.py new file mode 100644 index 0000000..0e34465 --- /dev/null +++ b/operators/__init__.py @@ -0,0 +1,20 @@ + + +from . import material +from . import picker +from . import shape + + +modules = ( + material, + picker, + shape +) + +def register(): + for mod in modules: + mod.register() + +def unregister(): + for mod in reversed(modules): + mod.unregister() \ No newline at end of file diff --git a/op_material.py b/operators/material.py similarity index 100% rename from op_material.py rename to operators/material.py diff --git a/op_picker.py b/operators/picker.py similarity index 80% rename from op_picker.py rename to operators/picker.py index e7bd41b..a6de955 100644 --- a/op_picker.py +++ b/operators/picker.py @@ -1,14 +1,18 @@ import bpy -from .constants import PICKERS +from bpy.props import EnumProperty +from ..constants import PICKERS +from ..core.bl_utils import get_view_3d_override +from ..core.picker import get_picker_path, pack_picker, unpack_picker #from .func_bgl import draw_callback_px #from .func_bgl import select_bone -#from .func_picker import * +#from core.picker import * #from .utils import is_over_region import gpu from mathutils import Vector from gpu_extras.batch import batch_for_shader from pathlib import Path import json +import os vertex_shader = ''' @@ -92,7 +96,7 @@ class RP_OT_box_select(bpy.types.Operator): bl_idname = "node.rp_box_select" bl_label = "Picker Box Select" - mode: bpy.props.EnumProperty(items=[(i, i.title(), '') for i in ('SET', 'EXTEND', 'SUBSTRACT')]) + mode: EnumProperty(items=[(i, i.title(), '') for i in ('SET', 'EXTEND', 'SUBSTRACT')]) color_shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') dash_shader = gpu.types.GPUShader(vertex_shader, fragment_shader) @@ -194,47 +198,57 @@ class RP_OT_box_select(bpy.types.Operator): -class RP_OT_move_bone(bpy.types.Operator): +class RP_OT_picker_transform(bpy.types.Operator): """Tooltip""" - bl_idname = "node.move_bone" + 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): - if not is_picker_space(context): - return - - def invoke(self, context, event): - self.mouse = event.mouse_region_x, event.mouse_region_y + return is_picker_space(context) - self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones} + def execute(self, context): + with context.temp_override(**get_view_3d_override()): + if self.mode == 'TRANSLATE': + bpy.ops.transform.translate("INVOKE_DEFAULT") + elif self.mode == 'ROTATE': + bpy.ops.transform.rotate("INVOKE_DEFAULT") + elif self.mode == 'SCALE': + bpy.ops.transform.resize("INVOKE_DEFAULT") - context.window_manager.modal_handler_add(self) - return {'RUNNING_MODAL'} + return {"FINISHED"} - def modal(self, context, event): + # def invoke(self, context, event): + # self.mouse = event.mouse_region_x, event.mouse_region_y - delta_x = (event.mouse_region_x - self.mouse[0]) / 1000 - delta_y = (event.mouse_region_y - self.mouse[1]) / 1000 + # self.bone_data = {b: b.matrix.copy() for b in context.selected_pose_bones} - + # context.window_manager.modal_handler_add(self) + # return {'RUNNING_MODAL'} - for bone, matrix in self.bone_data.items(): - bone.matrix.translation = matrix.translation + Vector((delta_x, 0, delta_y)) + # def modal(self, context, event): + + # delta_x = (event.mouse_region_x - self.mouse[0]) / 1000 + # delta_y = (event.mouse_region_y - self.mouse[1]) / 1000 + + # for bone, matrix in self.bone_data.items(): + # bone.matrix.translation = matrix.translation + Vector((delta_x, 0, delta_y)) - #print(delta_x, delta_y) + # #print(delta_x, delta_y) - if event.type=="LEFTMOUSE" and event.value == 'RELEASE': - return {'FINISHED'} + # 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 + # if event.type=="RIGHTMOUSE": + # for bone, matrix in self.bone_data.items(): + # bone.matrix = matrix - return {'CANCELLED'} + # return {'CANCELLED'} - return {'RUNNING_MODAL'} + # return {'RUNNING_MODAL'} class RP_OT_function_execute(bpy.types.Operator): @@ -309,20 +323,30 @@ class RP_OT_toogle_bone_layer(bpy.types.Operator): bone = picker.hover_shape.bone hide = picker.hover_shape.hide - if bone: for i, l in enumerate(bone.bone.layers): if l: ob.data.layers[i] = hide - print('Bone Layer toogle') - - #if picker.hover_bone: context.region.tag_redraw() return {"FINISHED"} +class RP_OT_context_menu_picker(bpy.types.Operator): + bl_idname = "node.context_menu_picker" + bl_label = 'Context Menu Picker' + + @classmethod + def poll(cls, context): + return is_picker_space(context) + + def execute(self, context): + bpy.ops.wm.call_menu(name='RP_MT_context_menu') + + return {"FINISHED"} + + class RP_OT_pack_picker(bpy.types.Operator): """Pack Unpack the picker on the rig""" bl_idname = "rigpicker.pack_picker" @@ -335,19 +359,21 @@ class RP_OT_pack_picker(bpy.types.Operator): def execute(self, context): print('Pack Picker') + + rig = context.object + picker_path = get_picker_path(rig) - ob = context.object - picker_src = Path(bpy.path.abspath(ob.data.rig_picker.source, library=ob.data.library)) - - if 'picker' in ob.data.rig_picker.keys(): - del ob.data.rig_picker['picker'] + if 'picker' in rig.data.rig_picker.keys(): + unpack_picker(rig) + self.report({"INFO"}, f'The picker is unpacked') return {"FINISHED"} - if not picker_src.exists(): - self.report({"ERROR"}, f'The path of the picker not exist: {picker_src}') + if not picker_path.exists(): + self.report({"ERROR"}, f'The path of the picker not exist: {picker_path}') return {"CANCELLED"} - ob.data.rig_picker['picker'] = json.loads(picker_src.read_text()) + pack_picker(rig) + self.report({"INFO"}, f'The picker is packed') return {"FINISHED"} @@ -383,8 +409,19 @@ class RP_MT_context_menu(bpy.types.Menu): # Set the menu operators and draw functions def draw(self, context): layout = self.layout + col = layout.column() + col.use_property_split = True - layout.operator("rigpicker.show_bone_layer", text="Show Bone Layer", ).type = 'ACTIVE' + ob = context.object + picker = PICKERS.get(ob) + + if picker.hover_shape and picker.hover_shape.type == 'bone': + bone = picker.hover_shape.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' ''' @@ -530,11 +567,19 @@ def register_keymaps(): kmi = km.keymap_items.new("rigpicker.toogle_bone_layer", type="LEFTMOUSE", value="DOUBLE_CLICK") keymaps.append((km, kmi)) - kmi = km.keymap_items.new("node.move_bone", type="G", value="PRESS") + kmi = km.keymap_items.new("node.picker_transform", type="G", value="PRESS") + kmi.properties.mode = 'TRANSLATE' keymaps.append((km, kmi)) - kmi = km.keymap_items.new("wm.call_menu", type="RIGHTMOUSE", value="PRESS") - kmi.properties.name = "RP_MT_context_menu" + 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="S", value="PRESS") + 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") @@ -563,7 +608,8 @@ classes = ( RP_OT_toogle_bone_layer, RP_OT_call_operator, RP_MT_context_menu, - RP_OT_move_bone, + RP_OT_picker_transform, + RP_OT_context_menu_picker, RP_OT_pack_picker #RP_OT_ui_draw ) diff --git a/op_shape.py b/operators/shape.py similarity index 97% rename from op_shape.py rename to operators/shape.py index 3a4d548..b9fa8ef 100644 --- a/op_shape.py +++ b/operators/shape.py @@ -1,8 +1,8 @@ import bpy -from .func_shape import get_picker_datas -from .utils import is_shape, find_mirror, link_mat_to_object -#import os +from ..core.shape import get_picker_datas +from ..core.addon_utils import is_shape +from ..core.bl_utils import link_mat_to_object, find_mirror from pathlib import Path import json diff --git a/panels.py b/panels.py index 75759c1..32635ed 100644 --- a/panels.py +++ b/panels.py @@ -1,7 +1,8 @@ import bpy #import collections #import inspect -from .utils import get_operator_from_id, get_mat +from .core.addon_utils import get_operator_from_id +from .core.bl_utils import get_mat import re diff --git a/snapping_utils.py b/snapping_utils.py deleted file mode 100644 index 7d4c217..0000000 --- a/snapping_utils.py +++ /dev/null @@ -1,209 +0,0 @@ -import bpy -from mathutils import Vector,Matrix -from math import acos, pi -#from .insert_keyframe import insert_keyframe - -############################ -## Math utility functions ## -############################ - -def perpendicular_vector(v): - """ Returns a vector that is perpendicular to the one given. - The returned vector is _not_ guaranteed to be normalized. - """ - # Create a vector that is not aligned with v. - # It doesn't matter what vector. Just any vector - # that's guaranteed to not be pointing in the same - # direction. - if abs(v[0]) < abs(v[1]): - tv = Vector((1,0,0)) - else: - tv = Vector((0,1,0)) - - # Use cross prouct to generate a vector perpendicular to - # both tv and (more importantly) v. - return v.cross(tv) - - -def rotation_difference(mat1, mat2): - """ Returns the shortest-path rotational difference between two - matrices. - """ - q1 = mat1.to_quaternion() - q2 = mat2.to_quaternion() - angle = acos(min(1,max(-1,q1.dot(q2)))) * 2 - if angle > pi: - angle = -angle + (2*pi) - return angle - - -######################################### -## "Visual Transform" helper functions ## -######################################### - -def get_pose_matrix_in_other_space(mat, pose_bone): - """ Returns the transform matrix relative to pose_bone's current - transform space. In other words, presuming that mat is in - armature space, slapping the returned matrix onto pose_bone - should give it the armature-space transforms of mat. - TODO: try to handle cases with axis-scaled parents better. - """ - rest = pose_bone.bone.matrix_local.copy() - rest_inv = rest.inverted() - if pose_bone.parent: - par_mat = pose_bone.parent.matrix.copy() - par_inv = par_mat.inverted() - par_rest = pose_bone.parent.bone.matrix_local.copy() - else: - par_mat = Matrix() - par_inv = Matrix() - par_rest = Matrix() - - # Get matrix in bone's current transform space - smat = rest_inv * (par_rest * (par_inv * mat)) - - # Compensate for non-local location - #if not pose_bone.bone.use_local_location: - # loc = smat.to_translation() * (par_rest.inverted() * rest).to_quaternion() - # smat.translation = loc - - return smat - - -def get_local_pose_matrix(pose_bone): - """ Returns the local transform matrix of the given pose bone. - """ - return get_pose_matrix_in_other_space(pose_bone.matrix, pose_bone) - - -def set_pose_translation(pose_bone, mat): - """ Sets the pose bone's translation to the same translation as the given matrix. - Matrix should be given in bone's local space. - """ - if pose_bone.bone.use_local_location is True: - pose_bone.location = mat.to_translation() - else: - loc = mat.to_translation() - - rest = pose_bone.bone.matrix_local.copy() - if pose_bone.bone.parent: - par_rest = pose_bone.bone.parent.matrix_local.copy() - else: - par_rest = Matrix() - - q = (par_rest.inverted() * rest).to_quaternion() - pose_bone.location = q * loc - - -def set_pose_rotation(pose_bone, mat): - """ Sets the pose bone's rotation to the same rotation as the given matrix. - Matrix should be given in bone's local space. - """ - q = mat.to_quaternion() - - if pose_bone.rotation_mode == 'QUATERNION': - pose_bone.rotation_quaternion = q - elif pose_bone.rotation_mode == 'AXIS_ANGLE': - pose_bone.rotation_axis_angle[0] = q.angle - pose_bone.rotation_axis_angle[1] = q.axis[0] - pose_bone.rotation_axis_angle[2] = q.axis[1] - pose_bone.rotation_axis_angle[3] = q.axis[2] - else: - pose_bone.rotation_euler = q.to_euler(pose_bone.rotation_mode) - - -def set_pose_scale(pose_bone, mat): - """ Sets the pose bone's scale to the same scale as the given matrix. - Matrix should be given in bone's local space. - """ - pose_bone.scale = mat.to_scale() - - -def match_pose_translation(pose_bone, target_bone): - """ Matches pose_bone's visual translation to target_bone's visual - translation. - This function assumes you are in pose mode on the relevant armature. - """ - mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone) - set_pose_translation(pose_bone, mat) - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='POSE') - - -def match_pose_rotation(pose_bone, target_bone): - """ Matches pose_bone's visual rotation to target_bone's visual - rotation. - This function assumes you are in pose mode on the relevant armature. - """ - mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone) - set_pose_rotation(pose_bone, mat) - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='POSE') - - -def match_pose_scale(pose_bone, target_bone): - """ Matches pose_bone's visual scale to target_bone's visual - scale. - This function assumes you are in pose mode on the relevant armature. - """ - mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone) - set_pose_scale(pose_bone, mat) - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='POSE') - - -############################## -## IK/FK snapping functions ## -############################## - -def match_pole_target(ik_first, ik_last, pole, match_bone, length): - """ Places an IK chain's pole target to match ik_first's - transforms to match_bone. All bones should be given as pose bones. - You need to be in pose mode on the relevant armature object. - ik_first: first bone in the IK chain - ik_last: last bone in the IK chain - pole: pole target bone for the IK chain - match_bone: bone to match ik_first to (probably first bone in a matching FK chain) - length: distance pole target should be placed from the chain center - """ - a = ik_first.matrix.to_translation() - b = ik_last.matrix.to_translation() + ik_last.vector - - # Vector from the head of ik_first to the - # tip of ik_last - ikv = b - a - - # Get a vector perpendicular to ikv - pv = perpendicular_vector(ikv).normalized() * length - - def set_pole(pvi): - """ Set pole target's position based on a vector - from the arm center line. - """ - # Translate pvi into armature space - ploc = a + (ikv/2) + pvi - - # Set pole target to location - mat = get_pose_matrix_in_other_space(Matrix.Translation(ploc), pole) - set_pose_translation(pole, mat) - - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='POSE') - - set_pole(pv) - - # Get the rotation difference between ik_first and match_bone - angle = rotation_difference(ik_first.matrix, match_bone.matrix) - - # Try compensating for the rotation difference in both directions - pv1 = Matrix.Rotation(angle, 4, ikv) * pv - set_pole(pv1) - ang1 = rotation_difference(ik_first.matrix, match_bone.matrix) - - pv2 = Matrix.Rotation(-angle, 4, ikv) * pv - set_pole(pv2) - ang2 = rotation_difference(ik_first.matrix, match_bone.matrix) - - # Do the one with the smaller angle - if ang1 < ang2: - set_pole(pv1) diff --git a/utils.py b/utils.py deleted file mode 100644 index c430216..0000000 --- a/utils.py +++ /dev/null @@ -1,215 +0,0 @@ - -import bpy -from mathutils import Vector -from mathutils.geometry import intersect_line_line_2d - - -def get_mat(ob): - for sl in ob.material_slots: - if sl.material: - return sl.material - -def link_mat_to_object(ob): - for sl in ob.material_slots: - m = sl.material - sl.link = 'OBJECT' - sl.material = m - -def get_operator_from_id(idname): - if not '.' in idname: - return - - m, o = idname.split(".") - try: - op = getattr(getattr(bpy.ops, m), o) - op.get_rna_type() - except Exception: - return - - return op -''' -def canvas_space(point,scale,offset): - return scale*Vector(point)+Vector(offset) -''' - -def get_object_color(ob): - if not ob.data.materials: - return - - mat = get_mat(ob) - if not mat or not mat.node_tree or not mat.node_tree.nodes: - return - - emit_node = mat.node_tree.nodes.get('Emission') - if not emit_node: - return - - return emit_node.inputs['Color'].default_value - -def intersect_rectangles(bound, border): # returns None if rectangles don't intersect - dx = min(border[1][0],bound[1][0]) - max(border[0][0],bound[0][0]) - dy = min(border[0][1],bound[0][1]) - max(border[2][1],bound[2][1]) - - if (dx>=0) and (dy>=0): - return dx*dy - -def point_inside_rectangle(point, rect): - return rect[0][0]< point[0]< rect[1][0] and rect[2][1]< point[1]< rect[0][1] - -def point_over_shape(point,verts,loops,outside_point=(-1,-1)): - out = Vector(outside_point) - pt = Vector(point) - - intersections = 0 - for loop in loops: - for i,p in enumerate(loop): - a = Vector(verts[loop[i-1]]) - b = Vector(verts[p]) - - if intersect_line_line_2d(pt,out,a,b): - intersections += 1 - - if intersections%2 == 1: #chek if the nb of intersection is odd - return True - - -def border_over_shape(border,verts,loops): - for loop in loops: - for i,p in enumerate(loop): - a = Vector(verts[loop[i-1]]) - b = Vector(verts[p]) - - for j in range(0,4): - c = border[j-1] - d = border[j] - if intersect_line_line_2d(a,b,c,d): - return True - - for point in verts: - if point_inside_rectangle(point,border): - return True - - for point in border: - if point_over_shape(point,verts,loops): - return True - - - -def border_loop(vert, loop): - border_edge =[e for e in vert.link_edges if e.is_boundary] - - if border_edge: - for edge in border_edge: - other_vert = edge.other_vert(vert) - - if not other_vert in loop: - loop.append(other_vert) - border_loop(other_vert, loop) - - return loop - else: - return [vert] - - -def contour_loops(bm, vert_index=0, loops=None, vert_indices=None): - loops = loops or [] - vert_indices = vert_indices or [v.index for v in bm.verts] - - bm.verts.ensure_lookup_table() - - loop = border_loop(bm.verts[vert_index], [bm.verts[vert_index]]) - if len(loop) >1: - loops.append(loop) - - for v in loop: - vert_indices.remove(v.index) - - if len(vert_indices): - contour_loops(bm, vert_indices[0], loops, vert_indices) - - return loops - - -def get_IK_bones(IK_last): - ik_chain = IK_last.parent_recursive - ik_len = 0 - - #Get IK len: - for c in IK_last.constraints: - if c.type == 'IK': - ik_len = c.chain_count -2 - break - - IK_root = ik_chain[ik_len] - - IK_mid= ik_chain[:ik_len] - - IK_mid.reverse() - IK_mid.append(IK_last) - - return IK_root,IK_mid - -def find_mirror(name): - mirror = None - prop= False - - if name: - - if name.startswith('[')and name.endswith(']'): - prop = True - name= name[:-2][2:] - - match={ - 'R': 'L', - 'r': 'l', - 'L': 'R', - 'l': 'r', - } - - separator=['.','_'] - - if name.startswith(tuple(match.keys())): - if name[1] in separator: - mirror = match[name[0]]+name[1:] - - if name.endswith(tuple(match.keys())): - if name[-2] in separator: - mirror = name[:-1]+match[name[-1]] - - if mirror and prop == True: - mirror='["%s"]'%mirror - - return mirror - - else: - return None - -def is_shape(ob): - scn = bpy.context.scene - canvas = scn.rig_picker.canvas - if not canvas or ob.hide_render: - return False - - shapes = {ob for col in canvas.users_collection for ob in col.all_objects} - - if ob.type in ('MESH', 'CURVE', 'FONT') and ob in shapes: - return True - - return False - -def is_over_region(self,context,event): - inside = 2 < event.mouse_region_x < context.region.width -2 and \ - 2 < event.mouse_region_y < context.region.height -2 and \ - [a for a in context.screen.areas if a.as_pointer()==self.adress] and \ - not context.screen.show_fullscreen - - return inside - -def bound_box_center(ob): - points = [ob.matrix_world@Vector(p) for p in ob.bound_box] - - x = [v[0] for v in points] - y = [v[1] for v in points] - z = [v[2] for v in points] - - return (sum(x) / len(points), sum(y) / len(points),sum(z) / len(points))