diff options
author | Alexander Gavrilov <angavrilov@gmail.com> | 2021-07-11 16:13:28 +0300 |
---|---|---|
committer | Alexander Gavrilov <angavrilov@gmail.com> | 2021-07-11 19:21:57 +0300 |
commit | e1f331e0af4461dbf80e03e76d9576fa748a0460 (patch) | |
tree | 57b4f05b59cce0372c09b227ec80afdf48e7fddb /rigify | |
parent | 7884a7bbf9e2c9358609129ad8f4fca351d7f278 (diff) |
Rigify: add more utility code for the upcoming face rig.
Move widget and driver utilities from the feature set and rewrite
create_circle_widget. Also increase the line length for autopep8.
Diffstat (limited to 'rigify')
-rw-r--r-- | rigify/.pep8 | 2 | ||||
-rw-r--r-- | rigify/utils/mechanism.py | 37 | ||||
-rw-r--r-- | rigify/utils/misc.py | 24 | ||||
-rw-r--r-- | rigify/utils/widgets.py | 112 | ||||
-rw-r--r-- | rigify/utils/widgets_basic.py | 24 |
5 files changed, 189 insertions, 10 deletions
diff --git a/rigify/.pep8 b/rigify/.pep8 new file mode 100644 index 00000000..a2d556f4 --- /dev/null +++ b/rigify/.pep8 @@ -0,0 +1,2 @@ +[pycodestyle] +max_line_length = 99 diff --git a/rigify/utils/mechanism.py b/rigify/utils/mechanism.py index b4b2d389..413d8a00 100644 --- a/rigify/utils/mechanism.py +++ b/rigify/utils/mechanism.py @@ -312,6 +312,11 @@ def make_driver(owner, prop, *, index=-1, type='SUM', expression=None, variables return fcu +#============================================= +# Driver variable utilities +#============================================= + + def driver_var_transform(target, bone=None, *, type='LOC_X', space='WORLD', rotation_mode='AUTO'): """ Create a Transform Channel driver variable specification. @@ -337,6 +342,38 @@ def driver_var_transform(target, bone=None, *, type='LOC_X', space='WORLD', rota return { 'type': 'TRANSFORMS', 'targets': [ target_map ] } +def driver_var_distance(target, *, bone1=None, target2=None, bone2=None, space1='WORLD', space2='WORLD'): + """ + Create a Distance driver variable specification. + + Usage: + make_driver(..., variables=[driver_var_distance(...)]) + + Target bone name can be provided via a 'lazy' callable closure without arguments. + """ + + assert space1 in {'WORLD', 'TRANSFORM', 'LOCAL'} + assert space2 in {'WORLD', 'TRANSFORM', 'LOCAL'} + + target1_map = { + 'id': target, + 'transform_space': space1 + '_SPACE', + } + + if bone1 is not None: + target1_map['bone_target'] = bone1 + + target2_map = { + 'id': target2 or target, + 'transform_space': space2 + '_SPACE', + } + + if bone2 is not None: + target2_map['bone_target'] = bone2 + + return {'type': 'LOC_DIFF', 'targets': [target1_map, target2_map]} + + #============================================= # Constraint management #============================================= diff --git a/rigify/utils/misc.py b/rigify/utils/misc.py index e4ba55f2..9d4307c0 100644 --- a/rigify/utils/misc.py +++ b/rigify/utils/misc.py @@ -22,7 +22,7 @@ import bpy import math import collections -from itertools import tee, chain, islice, repeat +from itertools import tee, chain, islice, repeat, permutations from mathutils import Vector, Matrix, Color from rna_prop_ui import rna_idprop_value_to_python @@ -32,6 +32,28 @@ from rna_prop_ui import rna_idprop_value_to_python #============================================= +axis_vectors = { + 'x': (1,0,0), + 'y': (0,1,0), + 'z': (0,0,1), + '-x': (-1,0,0), + '-y': (0,-1,0), + '-z': (0,0,-1), +} + + +# Matrices that reshuffle axis order and/or invert them +shuffle_matrix = { + sx+x+sy+y+sz+z: Matrix(( + axis_vectors[sx+x], axis_vectors[sy+y], axis_vectors[sz+z] + )).transposed().freeze() + for x, y, z in permutations(['x', 'y', 'z']) + for sx in ('', '-') + for sy in ('', '-') + for sz in ('', '-') +} + + def angle_on_plane(plane, vec1, vec2): """ Return the angle between two vectors projected onto a plane. """ diff --git a/rigify/utils/widgets.py b/rigify/utils/widgets.py index 970904c1..a8691349 100644 --- a/rigify/utils/widgets.py +++ b/rigify/utils/widgets.py @@ -24,6 +24,7 @@ import inspect import functools from mathutils import Matrix, Vector, Euler +from itertools import count from .errors import MetarigError from .collections import ensure_widget_collection @@ -210,6 +211,113 @@ def widget_generator(generate_func=None, *, register=None, subsurf=0): return wrapper +def generate_lines_geometry(geom, points, *, matrix=None, closed_loop=False): + """ + Generates a polyline using given points, optionally closing the loop. + """ + assert len(points) >= 2 + + base = len(geom.verts) + + for i, raw_point in enumerate(points): + point = Vector(raw_point).to_3d() + + if matrix: + point = matrix @ point + + geom.verts.append(point) + + if i > 0: + geom.edges.append((base + i - 1, base + i)) + + if closed_loop: + geom.edges.append((len(geom.verts) - 1, base)) + + +def generate_circle_geometry(geom, center, radius, *, matrix=None, angle_range=None, + steps=24, radius_x=None, depth_x=0): + """ + Generates a circle, adding vertices and edges to the lists. + center, radius: parameters of the circle + matrix: transformation matrix (by default the circle is in the XY plane) + angle_range: pair of angles to generate an arc of the circle + steps: number of edges to cover the whole circle (reduced for arcs) + """ + assert steps >= 3 + + start = 0 + delta = math.pi * 2 / steps + + if angle_range: + start, end = angle_range + if start == end: + steps = 1 + else: + steps = max(3, math.ceil(abs(end - start) / delta) + 1) + delta = (end - start) / (steps - 1) + + if radius_x is None: + radius_x = radius + + center = Vector(center).to_3d() # allow 2d center + points = [] + + for i in range(steps): + angle = start + delta * i + x = math.cos(angle) + y = math.sin(angle) + points.append(center + Vector((x * radius_x, y * radius, x * x * depth_x))) + + generate_lines_geometry(geom, points, matrix=matrix, closed_loop=not angle_range) + + +def generate_circle_hull_geometry(geom, points, radius, gap, *, matrix=None, steps=24): + """ + Given a list of 2D points forming a convex hull, generate a contour around + it, with each point being circumscribed with a circle arc of given radius, + and keeping the given distance gap from the lines connecting the circles. + """ + assert radius >= gap + + if len(points) <= 1: + if points: + generate_circle_geometry( + geom, points[0], radius, + matrix=matrix, steps=steps + ) + return + + base = len(geom.verts) + points_ex = [points[-1], *points, points[0]] + agap = math.asin(gap / radius) + + for i, pprev, pcur, pnext in zip(count(0), points_ex[0:], points_ex[1:], points_ex[2:]): + vprev = pprev - pcur + vnext = pnext - pcur + + # Compute bearings to adjacent points + aprev = math.atan2(vprev.y, vprev.x) + anext = math.atan2(vnext.y, vnext.x) + if anext <= aprev: + anext += math.pi * 2 + + # Adjust gap for circles that are too close + aprev += max(agap, math.acos(min(1, vprev.length/radius/2))) + anext -= max(agap, math.acos(min(1, vnext.length/radius/2))) + + if anext > aprev: + if len(geom.verts) > base: + geom.edges.append((len(geom.verts)-1, len(geom.verts))) + + generate_circle_geometry( + geom, pcur, radius, angle_range=(aprev, anext), + matrix=matrix, steps=steps + ) + + if len(geom.verts) > base: + geom.edges.append((len(geom.verts)-1, base)) + + def create_circle_polygon(number_verts, axis, radius=1.0, head_tail=0.0): """ Creates a basic circle around of an axis selected. number_verts: number of vertices of the polygon @@ -245,6 +353,10 @@ def create_circle_polygon(number_verts, axis, radius=1.0, head_tail=0.0): return verts, edges +#============================================= +# Widget transformation +#============================================= + def adjust_widget_axis(obj, axis='y', offset=0.0): mesh = obj.data diff --git a/rigify/utils/widgets_basic.py b/rigify/utils/widgets_basic.py index abc27263..543457eb 100644 --- a/rigify/utils/widgets_basic.py +++ b/rigify/utils/widgets_basic.py @@ -18,7 +18,9 @@ # <pep8 compliant> -from .widgets import create_widget, widget_generator, register_widget +from .misc import shuffle_matrix +from .widgets import (create_widget, widget_generator, register_widget, + generate_circle_geometry) # Common Widgets @@ -30,19 +32,23 @@ def create_line_widget(geom): @widget_generator -def create_circle_widget(geom, *, radius=1.0, head_tail=0.0, head_tail_x=None, with_line=False): +def create_circle_widget(geom, *, radius=1.0, head_tail=0.0, radius_x=None, head_tail_x=None, with_line=False): """ Creates a basic circle widget, a circle around the y-axis. radius: the radius of the circle head_tail: where along the length of the bone the circle is (0.0=head, 1.0=tail) - head_tail_x: if not None, specifies a different value along the X axis to create a deformed circle + radius_x: if not None, specifies a different radius along the X axis, creating an ellipse + head_tail_x: if not None, specifies a different value along the X axis to create + a circle deformed in the Y direction. """ - v = [(0.7071068286895752, 2.980232238769531e-07, -0.7071065306663513), (0.8314696550369263, 2.980232238769531e-07, -0.5555699467658997), (0.9238795042037964, 2.682209014892578e-07, -0.3826831877231598), (0.9807852506637573, 2.5331974029541016e-07, -0.19509011507034302), (1.0, 2.365559055306221e-07, 1.6105803979371558e-07), (0.9807853698730469, 2.2351741790771484e-07, 0.19509044289588928), (0.9238796234130859, 2.086162567138672e-07, 0.38268351554870605), (0.8314696550369263, 1.7881393432617188e-07, 0.5555704236030579), (0.7071068286895752, 1.7881393432617188e-07, 0.7071070075035095), (0.5555702447891235, 1.7881393432617188e-07, 0.8314698934555054), (0.38268327713012695, 1.7881393432617188e-07, 0.923879861831665), (0.19509008526802063, 1.7881393432617188e-07, 0.9807855486869812), (-3.2584136988589307e-07, 1.1920928955078125e-07, 1.000000238418579), (-0.19509072601795197, 1.7881393432617188e-07, 0.9807854294776917), (-0.3826838731765747, 1.7881393432617188e-07, 0.9238795638084412), (-0.5555707216262817, 1.7881393432617188e-07, 0.8314695358276367), (-0.7071071863174438, 1.7881393432617188e-07, 0.7071065902709961), (-0.8314700126647949, 1.7881393432617188e-07, 0.5555698871612549), (-0.923879861831665, 2.086162567138672e-07, 0.3826829195022583), (-0.9807853698730469, 2.2351741790771484e-07, 0.1950896978378296), (-1.0, 2.365559907957504e-07, -7.290432222362142e-07), (-0.9807850122451782, 2.5331974029541016e-07, -0.195091113448143), (-0.9238790273666382, 2.682209014892578e-07, -0.38268423080444336), (-0.831468939781189, 2.980232238769531e-07, -0.5555710196495056), (-0.7071058750152588, 2.980232238769531e-07, -0.707107424736023), (-0.555569052696228, 2.980232238769531e-07, -0.8314701318740845), (-0.38268208503723145, 2.980232238769531e-07, -0.923879861831665), (-0.19508881866931915, 2.980232238769531e-07, -0.9807853102684021), (1.6053570561780361e-06, 2.980232238769531e-07, -0.9999997615814209), (0.19509197771549225, 2.980232238769531e-07, -0.9807847142219543), (0.3826850652694702, 2.980232238769531e-07, -0.9238786101341248), (0.5555717945098877, 2.980232238769531e-07, -0.8314683437347412)] - delta = (head_tail_x - head_tail) if head_tail_x is not None else 0 - geom.verts = [(a[0] * radius, head_tail + delta * a[0] * a[0], a[2] * radius) for a in v] + radius_x = radius_x if radius_x is not None else radius + ht_delta = head_tail_x - head_tail if head_tail_x is not None else 0 + generate_circle_geometry( + geom, (0, 0, head_tail), radius, + radius_x=radius_x, depth_x=ht_delta, + matrix=shuffle_matrix['xzy'], steps=32 + ) if with_line: - geom.edges = [(28, 12), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (0, 31)] - else: - geom.edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (0, 31)] + geom.edges.append((8, 24)) # Z axis line register_widget("circle", create_circle_widget, radius=0.5) |