diff --git a/object_facemap_auto/__init__.py b/object_facemap_auto/__init__.py deleted file mode 100644 index 1085633f9787ee7130863a4f20bef2d0e0cf3b46..0000000000000000000000000000000000000000 --- a/object_facemap_auto/__init__.py +++ /dev/null @@ -1,45 +0,0 @@ -# ##### 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 ##### - -bl_info = { - "name": "Auto Face Map Widgets", - "author": "Campbell Barton", - "version": (1, 0), - "blender": (2, 80, 0), - "location": "View3D", - "description": "Use face-maps in the 3D view when rigged meshes are selected.", - "warning": "This is currently a proof of concept.", - "doc_url": "", - "category": "Rigging", -} - -submodules = ( - "auto_fmap_widgets", - "auto_fmap_ops", -) - -# reload at runtime, for development. -USE_RELOAD = False -USE_VERBOSE = False - -from bpy.utils import register_submodule_factory - -register, unregister = register_submodule_factory(__name__, submodules) - -if __name__ == "__main__": - register() diff --git a/object_facemap_auto/auto_fmap_ops.py b/object_facemap_auto/auto_fmap_ops.py deleted file mode 100644 index 4ed3aef0c8099eb5a7884182217764bdcaa2e492..0000000000000000000000000000000000000000 --- a/object_facemap_auto/auto_fmap_ops.py +++ /dev/null @@ -1,103 +0,0 @@ -# ##### 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 ##### - -import bpy -from bpy.types import ( - Operator, -) -from bpy.props import ( - EnumProperty, -) - -from . import USE_RELOAD - - -class MyFaceMapClear(Operator): - """Clear face-map transform""" - bl_idname = "my_facemap.transform_clear" - bl_label = "My Face Map Clear Transform" - bl_options = {'REGISTER', 'UNDO'} - - clear_types: EnumProperty( - name="Clear Types", - options={'ENUM_FLAG'}, - items=( - ('LOCATION', "Location", ""), - ('ROTATION', "Rotation", ""), - ('SCALE', "Scale", ""), - ), - description="Clear transform", - # default=set(), - ) - - @classmethod - def poll(cls, context): - return context.active_object is not None - - def invoke(self, context, _event): - self._group = context.manipulator_group - return self.execute(context) - - def execute(self, context): - # trick since redo wont have manipulator_group - group = self._group - - from .auto_fmap_utils import import_reload_or_none - auto_fmap_widgets_xform = import_reload_or_none( - __package__ + "." + "auto_fmap_widgets_xform", reload=USE_RELOAD, - ) - - if auto_fmap_widgets_xform is None: - return {'CANCELED'} - - for mpr in group.manipulators: - ob = mpr.fmap_mesh_object - fmap_target = mpr.fmap_target - fmap = mpr.fmap - - if mpr.select: - if 'LOCATION' in self.clear_types: - auto_fmap_widgets_xform.widget_clear_location( - context, mpr, ob, fmap, fmap_target, - ) - if 'ROTATION' in self.clear_types: - auto_fmap_widgets_xform.widget_clear_rotation( - context, mpr, ob, fmap, fmap_target, - ) - if 'SCALE' in self.clear_types: - auto_fmap_widgets_xform.widget_clear_scale( - context, mpr, ob, fmap, fmap_target, - ) - return {'FINISHED'} - - -classes = ( - MyFaceMapClear, -) - - -def register(): - from bpy.utils import register_class - for cls in classes: - register_class(cls) - - -def unregister(): - from bpy.utils import unregister_class - for cls in classes: - unregister_class(cls) diff --git a/object_facemap_auto/auto_fmap_utils.py b/object_facemap_auto/auto_fmap_utils.py deleted file mode 100644 index ba825c0972528570de5b7fc8145180ee8a56331c..0000000000000000000000000000000000000000 --- a/object_facemap_auto/auto_fmap_utils.py +++ /dev/null @@ -1,36 +0,0 @@ -# ##### 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 ##### - - -# Use so we can develop modules without reloading the add-on. - -def import_reload_or_none(name, reload=True): - """ - Import and reload a module. - """ - try: - mod = __import__(name) - if reload: - import importlib - mod = importlib.reload(mod) - import sys - return sys.modules[name] - except Exception: - import traceback - traceback.print_exc() - return None diff --git a/object_facemap_auto/auto_fmap_widgets.py b/object_facemap_auto/auto_fmap_widgets.py deleted file mode 100644 index 24ddf103229d8c25faaf6a98fd1c2456596c144e..0000000000000000000000000000000000000000 --- a/object_facemap_auto/auto_fmap_widgets.py +++ /dev/null @@ -1,446 +0,0 @@ -# ##### 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 ##### - -''' -Face map manipulator: - -- Automatic face-map to bone/shape mapping, so tweaking a face-map edits the underlying bone or shape - by matching names. -- Bones will: Translates, Rotates, Scales (in that order based on locks) -- Face-map selection can be used. -- Transforming multiple face maps at once works (with selection). -- Alt-G/R/S can be used to clear face-map transformation. -- Dot-key can be used for view selected face-maps. -- Face-map names can store key-values, formatted: - "some_name;foo=bar;baz=spam". - Currently this is used to set the face-map action if it cant be usefully guessed from locks. - eg "Bone;action=scale". -- Shapes simply change their influence. -- Face-map colors from bone groups are used when available. -- Supports precision (Shift) and increment snap (Ctrl). -- Keyframes after each edit, regardless of auto-key setting (unless its canceled). - Note: I did this because there is no easy way to manually key - if you don't want the keyframe you can undo :) -''' - -import bpy - -from bpy.types import ( - GizmoGroup, - Gizmo, - - PoseBone, - ShapeKey, -) - - -# USE_VERBOSE = True -from . import ( - USE_VERBOSE, - USE_RELOAD, -) - - -# ----------------------------------------------------------------------------- -# Utility functions - -def object_armatures(ob): - for mod in ob.modifiers: - if mod.type == 'ARMATURE': - if mod.show_viewport: - ob_arm = mod.object - if ob_arm is not None: - yield ob_arm - - -def face_map_find_target(ob, fmap_name): - """ - Returns pose-bone or shape-key. - """ - # first pose-bone - for ob_arm in object_armatures(ob): - pbone = ob_arm.pose.bones.get(fmap_name) - if pbone is not None: - return pbone - - # second shape-keys - if ob.type == 'MESH': - ob_data = ob.data - shape_keys = ob_data.shape_keys - if shape_keys is not None: - shape_key = ob_data.shape_keys.key_blocks.get(fmap_name) - if shape_key is not None: - return shape_key - - # can't find target - return None - - -def pose_bone_get_color(pose_bone): - bone_group = pose_bone.bone_group - if bone_group is not None: - return bone_group.colors.active - else: - return None - - -# ----------------------------------------------------------------------------- -# Face-map gizmos - -class AutoFaceMapWidget(Gizmo): - bl_idname = "VIEW3D_WT_auto_facemap" - - __slots__ = ( - # Face-map this widget displays. - "fmap", - # Face-Map index is used for drawing. - "fmap_index", - # Mesh object the face map is attached to. - "fmap_mesh_object", - # Item this widget controls: - # PoseBone or ShapeKey. - "fmap_target", - # list of rules, eg: ["action=rotate", ...] - "fmap_target_rules", - # Iterator to use while interacting. - "_iter", - ) - - def draw(self, context): - if USE_VERBOSE: - print("(draw)") - self.draw_preset_facemap(self.fmap_mesh_object, self.fmap_index) - - def select_refresh(self): - fmap = getattr(self, "fmap", None) - if fmap is not None: - fmap.select = self.select - - def setup(self): - if USE_VERBOSE: - print("(setup)", self) - - def draw_select(self, context, select_id): - if USE_VERBOSE: - print("(draw_select)", self, context, select_id >> 8) - self.draw_preset_facemap(self.fmap_mesh_object, self.fmap_index, select_id=select_id) - - def invoke(self, context, event): - if USE_VERBOSE: - print("(invoke)", self, event) - - # Avoid having to re-register to test logic - from .auto_fmap_utils import import_reload_or_none - auto_fmap_widgets_xform = import_reload_or_none( - __package__ + "." + "auto_fmap_widgets_xform", reload=USE_RELOAD, - ) - - auto_fmap_widgets_xform.USE_VERBOSE = USE_VERBOSE - - # ob = context.object - # fmap = ob.fmaps[self.fmap_index] - # fmap_target = fmap_find_target(ob, fmap) - - mpr_list = [self] - for mpr in self.group.gizmos: - if mpr is not self: - if mpr.select: - mpr_list.append(mpr) - - self._iter = [] - - self.group.is_modal = True - - for mpr in mpr_list: - ob = mpr.fmap_mesh_object - fmap_target = mpr.fmap_target - fmap = mpr.fmap - - if isinstance(fmap_target, PoseBone): - # try get from rules first - action = mpr.fmap_target_rules.get("action") - if action is None: - bone = fmap_target.bone - if (not (bone.use_connect and bone.parent)) and (not all(fmap_target.lock_location)): - action = "translate" - elif not all(fmap_target.lock_rotation): - action = "rotate" - elif not all(fmap_target.lock_scale): - action = "scale" - del bone - - # guess from pose - if action == "translate": - mpr_iter = iter(auto_fmap_widgets_xform.widget_iter_pose_translate( - context, mpr, ob, fmap, fmap_target)) - elif action == "rotate": - mpr_iter = iter(auto_fmap_widgets_xform.widget_iter_pose_rotate( - context, mpr, ob, fmap, fmap_target)) - elif action == "scale": - mpr_iter = iter(auto_fmap_widgets_xform.widget_iter_pose_scale( - context, mpr, ob, fmap, fmap_target)) - elif action: - print("Warning: action %r not known!" % action) - - elif isinstance(fmap_target, ShapeKey): - mpr_iter = iter(auto_fmap_widgets_xform.widget_iter_shapekey( - context, mpr, ob, fmap, fmap_target)) - - if mpr_iter is None: - mpr_iter = iter(auto_fmap_widgets_xform.widget_iter_template( - context, mpr, ob, fmap, fmap_target)) - - # initialize - mpr_iter.send(None) - # invoke() - mpr_iter.send(event) - - self._iter.append(mpr_iter) - return {'RUNNING_MODAL'} - - def exit(self, context, cancel): - self.group.is_modal = False - # failed case - if not self._iter: - return - - if USE_VERBOSE: - print("(exit)", self, cancel) - - # last event, StopIteration is expected! - for mpr_iter in self._iter: - try: - mpr_iter.send((cancel, None)) - except StopIteration: - continue # expected state - raise Exception("for some reason the iterator lives on!") - if not cancel: - bpy.ops.ed.undo_push(message="Tweak Gizmo") - - def modal(self, context, event, tweak): - # failed case - if not self._iter: - return {'CANCELLED'} - - # iter prints - for mpr_iter in self._iter: - try: - mpr_iter.send((event, tweak)) - except Exception: - import traceback - traceback.print_exc() - # avoid flooding output - # We might want to remove this from the list! - # self._iter = None - return {'RUNNING_MODAL'} - - -class AutoFaceMapWidgetGroup(GizmoGroup): - bl_idname = "OBJECT_WGT_auto_facemap" - bl_label = "Auto Face Map" - bl_space_type = 'VIEW_3D' - bl_region_type = 'WINDOW' - bl_options = {'3D', 'DEPTH_3D', 'SELECT', 'PERSISTENT', 'SHOW_MODAL_ALL'} - - __slots__ = ( - # "widgets", - # need some comparison - "last_active_object", - "is_modal", - ) - - @classmethod - def poll(cls, context): - if context.mode != 'OBJECT': - return False - ob = context.object - return (ob and ob.type == 'MESH' and ob.face_maps) - - @staticmethod - def mesh_objects_from_armature(ob, visible_mesh_objects): - """ - Take a context and return all mesh objects we can face-map. - """ - for ob_iter in visible_mesh_objects: - if ob in object_armatures(ob_iter): - yield ob_iter - - @staticmethod - def mesh_objects_from_context(context): - """ - Take a context and return all mesh objects we can face-map. - """ - ob = context.object - if ob is None: - pass - ob_type = ob.type - visible_mesh_objects = [ob_iter for ob_iter in context.visible_objects if ob_iter.type == 'MESH'] - if ob_type == 'ARMATURE': - yield from AutoFaceMapWidgetGroup.mesh_objects_from_armature(ob, visible_mesh_objects) - elif ob_type == 'MESH': - # Unlikely, but possible 2x armatures control the same meshes - # so double check we dont add the same meshes twice. - unique_objects = set() - for ob_iter in object_armatures(ob): - if ob_iter not in unique_objects: - yield from AutoFaceMapWidgetGroup.mesh_objects_from_armature(ob_iter, visible_mesh_objects) - unique_objects.add(ob_iter) - - def setup_manipulator_from_facemap(self, fmap_mesh_object, fmap, i): - color_fallback = 0.15, 0.62, 1.0 - - # foo;bar;baz --> ("foo", "bar;baz") - fmap_name = fmap.name - fmap_name_strip, fmap_rules = fmap_name.partition(";")[::2] - fmap_target = face_map_find_target(fmap_mesh_object, fmap_name_strip) - - if fmap_target is None: - return None - - mpr = self.gizmos.new(AutoFaceMapWidget.bl_idname) - mpr.fmap_index = i - mpr.fmap = fmap - mpr.fmap_mesh_object = fmap_mesh_object - mpr.fmap_target = fmap_target - - # See 'select_refresh' which syncs back in the other direction. - mpr.select = fmap.select - - # foo;bar=baz;bonzo=bingo --> {"bar": baz", "bonzo": bingo} - mpr.fmap_target_rules = dict( - item.partition("=")[::2] for item in fmap_rules - ) - - # XXX, we might want to have some way to extract a 'center' from a face-map - # for now use the pose-bones location. - if isinstance(fmap_target, PoseBone): - mpr.matrix_basis = (fmap_target.id_data.matrix_world @ fmap_target.matrix).normalized() - - mpr.use_draw_hover = True - mpr.use_draw_modal = True - - if isinstance(fmap_target, PoseBone): - mpr.color = pose_bone_get_color(fmap_target) or color_fallback - else: # We could color shapes, for now not. - mpr.color = color_fallback - - mpr.alpha = 0.5 - - mpr.color_highlight = mpr.color - mpr.alpha_highlight = 0.5 - return mpr - - def setup(self, context): - self.is_modal = False - - # we could remove this, - # for now ensure keymaps are added on setup - self.evil_keymap_setup(context) - - is_update = hasattr(self, "last_active_object") - - # For weak sanity check - detects undo - if is_update and (self.last_active_object != context.active_object): - is_update = False - self.gizmos.clear() - - self.last_active_object = context.active_object - - if not is_update: - for fmap_mesh_object in self.mesh_objects_from_context(context): - for (i, fmap) in enumerate(fmap_mesh_object.face_maps): - self.setup_manipulator_from_facemap(fmap_mesh_object, fmap, i) - else: - # first attempt simple update - force_full_update = False - mpr_iter_old = iter(self.gizmos) - for fmap_mesh_object in self.mesh_objects_from_context(context): - for (i, fmap) in enumerate(fmap_mesh_object.face_maps): - mpr_old = next(mpr_iter_old, None) - if ( - (mpr_old is None) or - (mpr_old.fmap_mesh_object != fmap_mesh_object) or - (mpr_old.fmap != fmap) - ): - force_full_update = True - break - # else we will want to update the base matrix at least - # possibly colors - # but its not so important - del mpr_iter_old - - if force_full_update: - self.gizmos.clear() - # same as above - for fmap_mesh_object in self.mesh_objects_from_context(context): - for (i, fmap) in enumerate(fmap_mesh_object.face_maps): - self.setup_manipulator_from_facemap(fmap_mesh_object, fmap, i) - - def refresh(self, context): - if self.is_modal: - return - # WEAK! - self.setup(context) - - @classmethod - def evil_keymap_setup(cls, context): - # only once! - if hasattr(cls, "_keymap_init"): - return - cls._keymap_init = True - - # TODO, lookup existing keys and use those. - - # in-place of bpy.ops.object.location_clear and friends. - km = context.window_manager.keyconfigs.active.keymaps.get(cls.bl_idname) - if km is None: - return - kmi = km.keymap_items.new('my_facemap.transform_clear', 'G', 'PRESS', alt=True) - kmi.properties.clear_types = {'LOCATION'} - kmi = km.keymap_items.new('my_facemap.transform_clear', 'R', 'PRESS', alt=True) - kmi.properties.clear_types = {'ROTATION'} - kmi = km.keymap_items.new('my_facemap.transform_clear', 'S', 'PRESS', alt=True) - kmi.properties.clear_types = {'SCALE'} - - # in-place of bpy.ops.view3d.view_selected - # km.keymap_items.new('my_facemap.view_selected', 'NUMPAD_PERIOD', 'PRESS') - - ''' - @classmethod - def setup_keymap(cls, keyconfig): - km = keyconfig.keymaps.new(name=cls.bl_label, space_type='VIEW_3D', region_type='WINDOW') - # XXX add keymaps - return km - ''' - - -classes = ( - AutoFaceMapWidget, - AutoFaceMapWidgetGroup, -) - - -def register(): - from bpy.utils import register_class - for cls in classes: - register_class(cls) - - -def unregister(): - from bpy.utils import unregister_class - for cls in classes: - unregister_class(cls) diff --git a/object_facemap_auto/auto_fmap_widgets_xform.py b/object_facemap_auto/auto_fmap_widgets_xform.py deleted file mode 100644 index 5b4ae1fecb128941d407f8bcd6c09e628b2ea669..0000000000000000000000000000000000000000 --- a/object_facemap_auto/auto_fmap_widgets_xform.py +++ /dev/null @@ -1,514 +0,0 @@ -# ##### 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 ##### - -import bpy -import math - -from bpy.types import ( - PoseBone, - ShapeKey, -) - -# may be overwritten by user! -from . import USE_VERBOSE - - -# ----------------------------------------------------------------------------- -# Utilities - -def coords_to_loc_3d(context, coords_2d_seq, depth_location): - from bpy_extras.view3d_utils import region_2d_to_location_3d - region = context.region - rv3d = context.region_data - - return tuple(( - region_2d_to_location_3d(region, rv3d, mval, depth_location) - for mval in coords_2d_seq - )) - - -def calc_view_vector(context): - rv3d = context.region_data - viewinv = rv3d.view_matrix.inverted() - return -viewinv.col[2].xyz.normalized() - - -def pose_bone_calc_transform_orientation(pose_bone): - ob_pose = pose_bone.id_data - return (ob_pose.matrix_world @ pose_bone.matrix).inverted().to_3x3() - - -def pose_bone_rotation_attr_from_mode(pose_bone): - return { - # XYZ or any re-ordering maps to euler - 'AXIS_ANGLE': 'rotation_axis_angle', - 'QUATERNION': 'rotation_quaternion', - }.get(pose_bone.rotation_mode, 'rotation_euler') - - -def pose_bone_autokey(pose_bone, attr_key, attr_lock): - ob_pose = pose_bone.id_data - value = getattr(pose_bone, attr_key) - locks = getattr(pose_bone, attr_lock) - data_path = pose_bone.path_from_id() + "." + attr_key - for i in range(len(value)): - # needed because quaternion has only 3 locks - try: - is_lock = locks[i] - except IndexError: - is_lock = False - - ob_pose.keyframe_insert(data_path, frame=i) - - -def pose_bone_set_attr_with_locks(pose_bone, attr_key, attr_lock, value): - ob_pose = pose_bone.id_data - locks = getattr(pose_bone, attr_lock) - if not any(locks): - setattr(pose_bone, attr_key, value) - else: - # keep wrapped - value_wrap = getattr(pose_bone, attr_key) - # update from 'value' for unlocked axis - value_new = value_wrap.copy() - for i in range(len(value)): - # needed because quaternion has only 3 locks - try: - is_lock = locks[i] - except IndexError: - is_lock = False - - if not is_lock: - value_new[i] = value[i] - # Set all at once instead of per-axis to avoid multiple RNA updates. - value_wrap[:] = value_new - - -# ----------------------------------------------------------------------------- -# Interactivity class -# -# Note, this could use asyncio, we may want to investigate that! -# for now generators work fine. - -def widget_iter_template(context, mpr, ob, fmap, fmap_target): - # generic initialize - if USE_VERBOSE: - print("(iter-init)") - - # invoke() - # ... - if USE_VERBOSE: - print("(iter-invoke)") - - context.area.header_text_set("No operation found for: {}".format(fmap.name)) - - event = yield - tweak = set() - - # modal(), first step - # Keep this loop fast! runs on mouse-movement. - while True: - event, tweak_next = yield - if event in {True, False}: - break - if event.type == 'INBETWEEN_MOUSEMOVE': - continue - tweak = tweak_next - - if USE_VERBOSE: - print("(iter-modal)", event, tweak) - - # exit() - if USE_VERBOSE: - print("(iter-exit)", event) - - context.area.header_text_set(None) - - -def widget_iter_pose_translate(context, mpr, ob, fmap, fmap_target): - from mathutils import ( - Vector, - ) - # generic initialize - if USE_VERBOSE: - print("(iter-init)") - - context.area.header_text_set("Translating face-map: {}".format(fmap.name)) - - # invoke() - # ... - if USE_VERBOSE: - print("(iter-invoke)") - event = yield - tweak = set() - - # modal(), first step - mval_init = Vector((event.mouse_region_x, event.mouse_region_y)) - mval = mval_init.copy() - - # impl vars - pose_bone = fmap_target - del fmap_target - - tweak_attr = "location" - tweak_attr_lock = "lock_location" - - # Could use face-map center too - # Don't update these while interacting - bone_matrix_init = pose_bone.matrix.copy() - depth_location = bone_matrix_init.to_translation() - - world_to_local_3x3 = pose_bone_calc_transform_orientation(pose_bone) - - loc_init = pose_bone.location.copy() - - # Keep this loop fast! runs on mouse-movement. - while True: - event, tweak_next = yield - if event in {True, False}: - break - if event.type == 'INBETWEEN_MOUSEMOVE': - continue - tweak = tweak_next - - if USE_VERBOSE: - print("(iter-modal)", event, tweak) - - mval = Vector((event.mouse_region_x, event.mouse_region_y)) - - co_init, co = coords_to_loc_3d(context, (mval_init, mval), depth_location) - loc_delta = world_to_local_3x3 @ (co - co_init) - - input_scale = 1.0 - is_precise = 'PRECISE' in tweak - if is_precise: - input_scale /= 10.0 - - loc_delta *= input_scale - # relative snap - if 'SNAP' in tweak: - loc_delta[:] = [round(v, 2 if is_precise else 1) for v in loc_delta] - - final_value = loc_init + loc_delta - pose_bone_set_attr_with_locks(pose_bone, tweak_attr, tweak_attr_lock, final_value) - - # exit() - if USE_VERBOSE: - print("(iter-exit)", event) - if event is True: # cancel - pose_bone.location = loc_init - else: - pose_bone_autokey(pose_bone, tweak_attr, tweak_attr_lock) - - context.area.header_text_set(None) - - -def widget_iter_pose_rotate(context, mpr, ob, fmap, fmap_target): - from mathutils import ( - Vector, - Quaternion, - ) - # generic initialize - if USE_VERBOSE: - print("(iter-init)") - - tweak_attr = pose_bone_rotation_attr_from_mode(fmap_target) - context.area.header_text_set("Rotating ({}) face-map: {}".format(tweak_attr, fmap.name)) - tweak_attr_lock = "lock_rotation" - - # invoke() - # ... - if USE_VERBOSE: - print("(iter-invoke)") - event = yield - tweak = set() - - # modal(), first step - mval_init = Vector((event.mouse_region_x, event.mouse_region_y)) - mval = mval_init.copy() - - # impl vars - pose_bone = fmap_target - del fmap_target - - # Could use face-map center too - # Don't update these while interacting - bone_matrix_init = pose_bone.matrix.copy() - depth_location = bone_matrix_init.to_translation() - rot_center = bone_matrix_init.to_translation() - - world_to_local_3x3 = pose_bone_calc_transform_orientation(pose_bone) - - # for rotation - local_view_vector = (calc_view_vector(context) @ world_to_local_3x3).normalized() - - rot_init = getattr(pose_bone, tweak_attr).copy() - - # Keep this loop fast! runs on mouse-movement. - while True: - event, tweak_next = yield - if event in {True, False}: - break - if event.type == 'INBETWEEN_MOUSEMOVE': - continue - tweak = tweak_next - - if USE_VERBOSE: - print("(iter-modal)", event, tweak) - - mval = Vector((event.mouse_region_x, event.mouse_region_y)) - - # calculate rotation matrix from input - co_init, co = coords_to_loc_3d(context, (mval_init, mval), depth_location) - # co_delta = world_to_local_3x3 @ (co - co_init) - - input_scale = 1.0 - is_precise = 'PRECISE' in tweak - if is_precise: - input_scale /= 10.0 - - if False: - # Dial logic, not obvious enough unless we show graphical line to center... - # but this is typically too close to the center of the face-map. - rot_delta = (co_init - rot_center).rotation_difference(co - rot_center).to_matrix() - else: - # Steering wheel logic, left to rotate left, right to rotate right :) - # use X-axis only - - # Calculate imaginary point as if mouse was moved 100px to the right - # then transform to local orientation and use to see where the cursor is - - rotate_angle = ((mval.x - mval_init.x) / 100.0) - rotate_angle *= input_scale - - if 'SNAP' in tweak: - v = math.radians(1.0 if is_precise else 15.0) - rotate_angle = round(rotate_angle / v) * v - del v - - rot_delta = Quaternion(local_view_vector, rotate_angle).to_matrix() - - # rot_delta = (co_init - rot_center).rotation_difference(co - rot_center).to_matrix() - - rot_matrix = rot_init.to_matrix() - rot_matrix = rot_matrix @ rot_delta - - if tweak_attr == "rotation_quaternion": - final_value = rot_matrix.to_quaternion() - elif tweak_attr == "rotation_euler": - final_value = rot_matrix.to_euler(pose_bone.rotation_mode, rot_init) - else: - assert(tweak_attr == "rotation_axis_angle") - final_value = rot_matrix.to_quaternion().to_axis_angle() - final_value = (*final_value[0], final_value[1]) # flatten - - pose_bone_set_attr_with_locks(pose_bone, tweak_attr, tweak_attr_lock, final_value) - - # exit() - if USE_VERBOSE: - print("(iter-exit)", event) - if event is True: # cancel - setattr(pose_bone, tweak_attr, rot_init) - else: - pose_bone_autokey(pose_bone, tweak_attr, tweak_attr_lock) - - context.area.header_text_set(None) - - -def widget_iter_pose_scale(context, mpr, ob, fmap, fmap_target): - from mathutils import ( - Vector, - ) - # generic initialize - if USE_VERBOSE: - print("(iter-init)") - - context.area.header_text_set("Scale face-map: {}".format(fmap.name)) - - # invoke() - # ... - if USE_VERBOSE: - print("(iter-invoke)") - event = yield - tweak = set() - - # modal(), first step - mval_init = Vector((event.mouse_region_x, event.mouse_region_y)) - mval = mval_init.copy() - - # impl vars - pose_bone = fmap_target - del fmap_target - tweak_attr = "scale" - tweak_attr_lock = "lock_scale" - - scale_init = pose_bone.scale.copy() - - # Keep this loop fast! runs on mouse-movement. - while True: - event, tweak_next = yield - if event in {True, False}: - break - if event.type == 'INBETWEEN_MOUSEMOVE': - continue - tweak = tweak_next - - if USE_VERBOSE: - print("(iter-modal)", event, tweak) - - mval = Vector((event.mouse_region_x, event.mouse_region_y)) - - input_scale = 1.0 - is_precise = 'PRECISE' in tweak - if is_precise: - input_scale /= 10.0 - - scale_factor = ((mval.y - mval_init.y) / 200.0) * input_scale - if 'SNAP' in tweak: - # relative - scale_factor = round(scale_factor, 2 if is_precise else 1) - final_value = scale_init * (1.0 + scale_factor) - pose_bone_set_attr_with_locks(pose_bone, tweak_attr, tweak_attr_lock, final_value) - - # exit() - if USE_VERBOSE: - print("(iter-exit)", event) - if event is True: # cancel - pose_bone.scale = scale_init - else: - pose_bone_autokey(pose_bone, tweak_attr, tweak_attr_lock) - - context.area.header_text_set(None) - - -def widget_iter_shapekey(context, mpr, ob, fmap, fmap_target): - from mathutils import ( - Vector, - ) - # generic initialize - if USE_VERBOSE: - print("(iter-init)") - - context.area.header_text_set("ShapeKey face-map: {}".format(fmap.name)) - - # invoke() - # ... - if USE_VERBOSE: - print("(iter-invoke)") - event = yield - tweak = set() - - # modal(), first step - mval_init = Vector((event.mouse_region_x, event.mouse_region_y)) - mval = mval_init.copy() - - # impl vars - shape = fmap_target - del fmap_target - - value_init = shape.value - - # Keep this loop fast! runs on mouse-movement. - while True: - event, tweak_next = yield - if event in {True, False}: - break - if event.type == 'INBETWEEN_MOUSEMOVE': - continue - tweak = tweak_next - - if USE_VERBOSE: - print("(iter-modal)", event, tweak) - - mval = Vector((event.mouse_region_x, event.mouse_region_y)) - - input_scale = 1.0 - is_precise = 'PRECISE' in tweak - if is_precise: - input_scale /= 10.0 - - final_value = value_init + ((mval.y - mval_init.y) / 200.0) * input_scale - if 'SNAP' in tweak: - final_value = round(final_value, 2 if is_precise else 1) - - shape.value = final_value - - # exit() - if USE_VERBOSE: - print("(iter-exit)", event) - if event is True: # cancel - shape.value = scale_init - else: - shape.id_data.keyframe_insert(shape.path_from_id() + ".value") - - context.area.header_text_set(None) - - -# ------------------------- -# Non Interactive Functions - -def widget_clear_location(context, mpr, ob, fmap, fmap_target): - - if isinstance(fmap_target, ShapeKey): - shape = fmap_target - del fmap_target - # gets clamped - shape.value = 0.0 - elif isinstance(fmap_target, PoseBone): - pose_bone = fmap_target - del fmap_target - - tweak_attr = "location" - tweak_attr_lock = "lock_location" - - pose_bone_set_attr_with_locks(pose_bone, tweak_attr, tweak_attr_lock, (0.0, 0.0, 0.0)) - pose_bone_autokey(pose_bone, tweak_attr, tweak_attr_lock) - - -def widget_clear_rotation(context, mpr, ob, fmap, fmap_target): - - if isinstance(fmap_target, PoseBone): - pose_bone = fmap_target - del fmap_target - tweak_attr = pose_bone_rotation_attr_from_mode(pose_bone) - tweak_attr_lock = "lock_rotation" - - value = getattr(pose_bone, tweak_attr).copy() - if tweak_attr == 'rotation_axis_angle': - # keep the axis - value[3] = 0.0 - elif tweak_attr == 'rotation_quaternion': - value.identity() - elif tweak_attr == 'rotation_euler': - value.zero() - - pose_bone_set_attr_with_locks(pose_bone, tweak_attr, tweak_attr_lock, value) - pose_bone_autokey(pose_bone, tweak_attr, tweak_attr_lock) - - -def widget_clear_scale(context, mpr, ob, fmap, fmap_target): - - if isinstance(fmap_target, PoseBone): - pose_bone = fmap_target - del fmap_target - - tweak_attr = "scale" - tweak_attr_lock = "lock_scale" - - pose_bone_set_attr_with_locks(pose_bone, tweak_attr, tweak_attr_lock, (1.0, 1.0, 1.0)) - pose_bone_autokey(pose_bone, tweak_attr, tweak_attr_lock)