diff options
author | Alexander Gavrilov <angavrilov@gmail.com> | 2019-02-16 13:57:57 +0300 |
---|---|---|
committer | Alexander Gavrilov <angavrilov@gmail.com> | 2019-03-14 14:39:16 +0300 |
commit | 36e8d00aec705b06008a0bc334fe266448b4f2c2 (patch) | |
tree | 02bf0290ec6423c611e3cd4ad49c69cb77acb67e /rigify/utils/widgets.py | |
parent | eabb5cddf79e5fae3ca429242cf2c6f5a272920e (diff) |
Rigify: add support for user-defined rig packages and related utilities.
As suggested by @icappielo, and after discussion with @meta-androcto,
I start a public request to commit third-party contributions already
accepted to https://github.com/eigen-value/rigify/tree/rigify_0.6_beta
Specifically, this includes:
* User-defined rig package (feature set) support by @pioverfour.
This allows users to install pre-packaged rig sets via zip
files, which become accessible together with built-in rigs,
as discussed in T52758.
https://github.com/eigen-value/rigify/pull/1
* Modularization of python script generation, allowing rigs to
add their own utility functions and operators to the generated
script. This is critical to make custom rig support really
useful.
https://github.com/eigen-value/rigify/pull/5
* The utils.py file is split into multiple modules with a backward
compatibility proxy for old functions.
* Automatic verification that different rigs don't try to create
different rig settings with the same name to alleviate increased
risk of namespace conflicts with custom rigs.
https://github.com/eigen-value/rigify/pull/7
* New utility class that implements bone layer selection UI.
https://github.com/eigen-value/rigify/pull/6
* New utilities to replace copy & pasted boilerplate code for
creating custom properties, constraints and drivers.
https://github.com/eigen-value/rigify/pull/11
Some other random changes by MAD have likely slipped through.
These changes have already been extensively discussed and accepted
into the branch by @luciorossi, so I see no reason not to commit
them to the official repository to be tested during 2.8 beta.
Reviewers: icappiello
Differential Revision: https://developer.blender.org/D4364
Diffstat (limited to 'rigify/utils/widgets.py')
-rw-r--r-- | rigify/utils/widgets.py | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/rigify/utils/widgets.py b/rigify/utils/widgets.py new file mode 100644 index 00000000..184429a3 --- /dev/null +++ b/rigify/utils/widgets.py @@ -0,0 +1,168 @@ +#====================== BEGIN GPL LICENSE BLOCK ====================== +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +#======================= END GPL LICENSE BLOCK ======================== + +# <pep8 compliant> + +import bpy +import math + +from .errors import MetarigError + +WGT_PREFIX = "WGT-" # Prefix for widget objects + +#============================================= +# Widget creation +#============================================= + + +def obj_to_bone(obj, rig, bone_name): + """ Places an object at the location/rotation/scale of the given bone. + """ + if bpy.context.mode == 'EDIT_ARMATURE': + raise MetarigError("obj_to_bone(): does not work while in edit mode") + + bone = rig.data.bones[bone_name] + + mat = rig.matrix_world @ bone.matrix_local + + obj.location = mat.to_translation() + + obj.rotation_mode = 'XYZ' + obj.rotation_euler = mat.to_euler() + + scl = mat.to_scale() + scl_avg = (scl[0] + scl[1] + scl[2]) / 3 + obj.scale = (bone.length * scl_avg), (bone.length * scl_avg), (bone.length * scl_avg) + + +def create_widget(rig, bone_name, bone_transform_name=None): + """ Creates an empty widget object for a bone, and returns the object. + """ + if bone_transform_name is None: + bone_transform_name = bone_name + + obj_name = WGT_PREFIX + rig.name + '_' + bone_name + scene = bpy.context.scene + collection = bpy.context.collection + + # Check if it already exists in the scene + if obj_name in scene.objects: + # Move object to bone position, in case it changed + obj = scene.objects[obj_name] + obj_to_bone(obj, rig, bone_transform_name) + + return None + else: + # Delete object if it exists in blend data but not scene data. + # This is necessary so we can then create the object without + # name conflicts. + if obj_name in bpy.data.objects: + bpy.data.objects[obj_name].user_clear() + bpy.data.objects.remove(bpy.data.objects[obj_name]) + + # Create mesh object + mesh = bpy.data.meshes.new(obj_name) + obj = bpy.data.objects.new(obj_name, mesh) + collection.objects.link(obj) + + # Move object to bone position and set layers + obj_to_bone(obj, rig, bone_transform_name) + wgts_group_name = 'WGTS_' + rig.name + if wgts_group_name in bpy.data.objects.keys(): + obj.parent = bpy.data.objects[wgts_group_name] + + return obj + + +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 poligon + axis: axis normal to the circle + radius: the radius of the circle + head_tail: where along the length of the bone the circle is (0.0=head, 1.0=tail) + """ + verts = [] + edges = [] + angle = 2 * math.pi / number_verts + i = 0 + + assert(axis in 'XYZ') + + while i < (number_verts): + a = math.cos(i * angle) + b = math.sin(i * angle) + + if axis == 'X': + verts.append((head_tail, a * radius, b * radius)) + elif axis == 'Y': + verts.append((a * radius, head_tail, b * radius)) + elif axis == 'Z': + verts.append((a * radius, b * radius, head_tail)) + + if i < (number_verts - 1): + edges.append((i , i + 1)) + + i += 1 + + edges.append((0, number_verts - 1)) + + return verts, edges + + +def write_widget(obj): + """ Write a mesh object as a python script for widget use. + """ + script = "" + script += "def create_thing_widget(rig, bone_name, size=1.0, bone_transform_name=None):\n" + script += " obj = create_widget(rig, bone_name, bone_transform_name)\n" + script += " if obj != None:\n" + + # Vertices + if len(obj.data.vertices) > 0: + script += " verts = [" + for v in obj.data.vertices: + script += "(" + str(v.co[0]) + "*size, " + str(v.co[1]) + "*size, " + str(v.co[2]) + "*size), " + script += "]\n" + + # Edges + if len(obj.data.edges) > 0: + script += " edges = [" + for e in obj.data.edges: + script += "(" + str(e.vertices[0]) + ", " + str(e.vertices[1]) + "), " + script += "]\n" + + # Faces + if len(obj.data.polygons) > 0: + script += " faces = [" + for f in obj.data.polygons: + script += "(" + for v in f.vertices: + script += str(v) + ", " + script += "), " + script += "]\n" + + # Build mesh + script += "\n mesh = obj.data\n" + script += " mesh.from_pydata(verts, edges, faces)\n" + script += " mesh.update()\n" + script += " mesh.update()\n" + script += " return obj\n" + script += " else:\n" + script += " return None\n" + + return script |