Skip to content

Commit ae58538

Browse files
angavriloveigen-value
authored andcommitted
#11 partial pull Utilities for creating the rig mechanism (constraints, drivers)
1 parent 7f869d1 commit ae58538

File tree

2 files changed

+323
-0
lines changed

2 files changed

+323
-0
lines changed

utils/bones.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,67 @@
2626
from .errors import MetarigError
2727
from .naming import strip_org, make_mechanism_name, insert_before_lr
2828

29+
#=======================
30+
# Bone collection
31+
#=======================
32+
33+
class BoneDict(dict):
34+
"""
35+
Special dictionary for holding bone names in a structured way.
36+
37+
Allows access to contained items as attributes, and only
38+
accepts certain types of values.
39+
"""
40+
41+
@staticmethod
42+
def __sanitize_attr(key, value):
43+
if hasattr(BoneDict, key):
44+
raise KeyError("Invalid BoneDict key: %s" % (key))
45+
46+
if (value is None or
47+
isinstance(value, str) or
48+
isinstance(value, list) or
49+
isinstance(value, BoneDict)):
50+
return value
51+
52+
if isinstance(value, dict):
53+
return BoneDict(value)
54+
55+
raise ValueError("Invalid BoneDict value: %r" % (value))
56+
57+
def __init__(self, *args, **kwargs):
58+
super(BoneDict, self).__init__()
59+
60+
for key, value in dict(*args, **kwargs).items():
61+
dict.__setitem__(self, key, BoneDict.__sanitize_attr(key, value))
62+
63+
self.__dict__ = self
64+
65+
def __repr__(self):
66+
return "BoneDict(%s)" % (dict.__repr__(self))
67+
68+
def __setitem__(self, key, value):
69+
dict.__setitem__(self, key, BoneDict.__sanitize_attr(key, value))
70+
71+
def update(self, *args, **kwargs):
72+
for key, value in dict(*args, **kwargs).items():
73+
dict.__setitem__(self, key, BoneDict.__sanitize_attr(key, value))
74+
75+
def flatten(self):
76+
"""Return all contained bones as a list."""
77+
78+
all_bones = []
79+
80+
for item in self.values():
81+
if isinstance(item, BoneDict):
82+
all_bones.extend(item.flatten())
83+
elif isinstance(item, list):
84+
all_bones.extend(item)
85+
elif item is not None:
86+
all_bones.append(item)
87+
88+
return all_bones
89+
2990
#=======================
3091
# Bone manipulation
3192
#=======================

utils/mechanism.py

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
#====================== BEGIN GPL LICENSE BLOCK ======================
2+
#
3+
# This program is free software; you can redistribute it and/or
4+
# modify it under the terms of the GNU General Public License
5+
# as published by the Free Software Foundation; either version 2
6+
# of the License, or (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program; if not, write to the Free Software Foundation,
15+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16+
#
17+
#======================= END GPL LICENSE BLOCK ========================
18+
19+
# <pep8 compliant>
20+
21+
import bpy
22+
23+
from rna_prop_ui import rna_idprop_ui_prop_get
24+
25+
#=============================================
26+
# Constraint creation utilities
27+
#=============================================
28+
29+
_TRACK_AXIS_MAP = {
30+
'X': 'TRACK_X', '-X': 'TRACK_NEGATIVE_X',
31+
'Y': 'TRACK_Y', '-Y': 'TRACK_NEGATIVE_Y',
32+
'Z': 'TRACK_Z', '-Z': 'TRACK_NEGATIVE_Z',
33+
}
34+
35+
def make_constraint(
36+
owner, type, target=None, subtarget=None,
37+
space=None, track_axis=None, use_xyz=None,
38+
**options):
39+
"""
40+
Creates and initializes constraint of the specified type for the owner bone.
41+
42+
Specially handled keyword arguments:
43+
44+
target, subtarget: if both not None, passed through to the constraint
45+
space : assigned to both owner_space and target_space
46+
track_axis : allows shorter X, Y, Z, -X, -Y, -Z notation
47+
use_xyz : list of 3 items is assigned to use_x, use_y and use_z options
48+
min/max_x/y/z : a corresponding use_min/max_x/y/z option is set to True
49+
50+
Other keyword arguments are directly assigned to the constraint options.
51+
Returns the newly created constraint.
52+
"""
53+
con = owner.constraints.new(type)
54+
55+
if target is not None and subtarget is not None:
56+
con.target = target
57+
con.subtarget = subtarget
58+
59+
if space is not None:
60+
con.owner_space = con.target_space = space
61+
62+
if track_axis is not None:
63+
con.track_axis = _TRACK_AXIS_MAP.get(track_axis, track_axis)
64+
65+
if use_xyz is not None:
66+
con.use_x, con.use_y, con.use_z = use_xyz[0:3]
67+
68+
for key in ['min_x', 'max_x', 'min_y', 'max_y', 'min_z', 'max_z']:
69+
if key in options and 'use_'+key not in options:
70+
options['use_'+key] = True
71+
72+
for p, v in options.items():
73+
setattr(con, p, v)
74+
75+
return con
76+
77+
#=============================================
78+
# Custom property creation utilities
79+
#=============================================
80+
81+
def make_property(owner, name, default=0.0, min=0.0, max=1.0, soft_min=None, soft_max=None):
82+
"""
83+
Creates and initializes a custom property of owner.
84+
85+
The soft_min and soft_max parameters default to min and max.
86+
"""
87+
owner[name] = default
88+
89+
prop = rna_idprop_ui_prop_get(owner, name, create=True)
90+
prop["min"] = min
91+
prop["soft_min"] = soft_min if soft_min is not None else min
92+
prop["max"] = max
93+
prop["soft_max"] = soft_max if soft_max is not None else max
94+
95+
return prop
96+
97+
#=============================================
98+
# Driver creation utilities
99+
#=============================================
100+
101+
def _init_driver_target(drv_target, var_info, target_id):
102+
"""Initialize a driver variable target from a specification."""
103+
104+
# Parse the simple list format for the common case.
105+
if isinstance(var_info, list):
106+
# [ (target_id,) subtarget, ...path ]
107+
108+
# If target_id is supplied as parameter, allow omitting it
109+
if target_id is None or isinstance(var_info[0], bpy.types.ID):
110+
target_id,subtarget,*refs = var_info
111+
else:
112+
subtarget,*refs = var_info
113+
114+
# Simple path string case.
115+
if len(refs) == 0:
116+
# [ (target_id,) path_str ]
117+
path = subtarget
118+
else:
119+
# If subtarget is a string, look up a bone in the target
120+
if isinstance(subtarget, str):
121+
subtarget = target_id.pose.bones[subtarget]
122+
123+
# Use ".foo" type path items verbatim, otherwise quote
124+
path = subtarget.path_from_id()
125+
for item in refs:
126+
path += item if item[0] == '.' else '["'+item+'"]'
127+
128+
drv_target.id = target_id
129+
drv_target.data_path = path
130+
131+
else:
132+
# { 'id': ..., ... }
133+
if target_id is not None:
134+
drv_target.id = target_id
135+
136+
for tp, tv in tdata.items():
137+
setattr(drv_target, tp, tv)
138+
139+
140+
def _add_driver_variable(drv, var_name, var_info, target_id):
141+
"""Add and initialize a driver variable."""
142+
143+
var = drv.variables.new()
144+
var.name = var_name
145+
146+
# Parse the simple list format for the common case.
147+
if isinstance(var_info, list):
148+
# [ (target_id,) subtarget, ...path ]
149+
var.type = "SINGLE_PROP"
150+
151+
_init_driver_target(var.targets[0], var_info, target_id)
152+
153+
else:
154+
# Variable info as generic dictionary - assign properties.
155+
# { 'type': 'SINGLE_PROP', 'targets':[...] }
156+
var.type = var_info['type']
157+
158+
for p, v in var_info.items():
159+
if p == 'targets':
160+
for i, tdata in enumerate(v):
161+
_init_driver_target(var.targets[i], tdata, target_id)
162+
elif p != 'type':
163+
setattr(var, p, v)
164+
165+
def make_driver(owner, prop, index=-1, type='SUM', expression=None, variables={}, polynomial=None, target_id=None):
166+
"""
167+
Creates and initializes a driver for the 'prop' property of owner.
168+
169+
Arguments:
170+
index : item index for vector properties
171+
type, expression: mutually exclusive options to set core driver mode.
172+
variables : either a list or dictionary of variable specifications.
173+
polynomial : coefficients of the POLYNOMIAL driver modifier
174+
target_id : specifies the target ID of variables implicitly
175+
176+
Specification format:
177+
If the variables argument is a dictionary, keys specify variable names.
178+
Otherwise names are set to var0, var1... etc:
179+
180+
variables = [ ..., ..., ... ]
181+
variables = { 'var0': ..., 'var1': ..., 'var2': ... }
182+
183+
Variable specifications are constructed as nested dictionaries and lists that
184+
follow the property structure of the original Blender objects, but the most
185+
common case can be abbreviated as a simple list.
186+
187+
The following specifications are equivalent:
188+
189+
[ target, subtarget, '.foo', 'bar' ]
190+
191+
{ 'type': 'SINGLE_PROP', 'targets':[[ target, subtarget, '.foo', 'bar' ]] }
192+
193+
{ 'type': 'SINGLE_PROP',
194+
'targets':[{ 'id': target, 'data_path': subtarget.path_from_id() + '.foo["bar"]' }] }
195+
196+
If subtarget is as string, it is automatically looked up within target as a bone.
197+
198+
It is possible to specify path directly as a simple string without following items:
199+
200+
[ target, 'path' ]
201+
202+
{ 'type': 'SINGLE_PROP', 'targets':[{ 'id': target, 'data_path': 'path' }] }
203+
204+
If the target_id parameter is not None, it is possible to omit target:
205+
206+
[ subtarget, '.foo', 'bar' ]
207+
208+
{ 'type': 'SINGLE_PROP',
209+
'targets':[{ 'id': target_id, 'data_path': subtarget.path_from_id() + '.foo["bar"]' }] }
210+
211+
Returns the newly created driver FCurve.
212+
"""
213+
fcu = owner.driver_add(prop, index)
214+
drv = fcu.driver
215+
216+
if expression is not None:
217+
drv.type = 'SCRIPTED'
218+
drv.expression = expression
219+
else:
220+
drv.type = type
221+
222+
if isinstance(variables, list):
223+
# variables = [ info, ... ]
224+
for i, var_info in enumerate(variables):
225+
_add_driver_variable(drv, 'var'+str(i), var_info, target_id)
226+
else:
227+
# variables = { 'varname': info, ... }
228+
for var_name, var_info in variables.items():
229+
_add_driver_variable(drv, var_name, var_info, target_id)
230+
231+
if polynomial is not None:
232+
drv_modifier = fcu.modifiers[0]
233+
drv_modifier.mode = 'POLYNOMIAL'
234+
drv_modifier.poly_order = len(polynomial)-1
235+
for i,v in enumerate(polynomial):
236+
drv_modifier.coefficients[i] = v
237+
238+
return fcu
239+
240+
#=============================================
241+
# Utility mixin
242+
#=============================================
243+
244+
class MechanismUtilityMixin:
245+
"""
246+
Provides methods for more convenient creation of constraints, properties
247+
and drivers within an armature (by implicitly providing context).
248+
249+
Requires self.obj to be the armature object being worked on.
250+
"""
251+
252+
def make_constraint(self, bone, type, subtarget=None, **args):
253+
assert(self.obj.mode == 'OBJECT')
254+
return make_constraint(self.obj.pose.bones[bone], type, self.obj, subtarget, **args)
255+
256+
def make_property(self, bone, name, **args):
257+
assert(self.obj.mode == 'OBJECT')
258+
return make_property(self.obj.pose.bones[bone], name, **args)
259+
260+
def make_driver(self, owner, prop, **args):
261+
assert(self.obj.mode == 'OBJECT')
262+
return make_driver(owner, prop, target_id=self.obj, **args)

0 commit comments

Comments
 (0)