def get_keys(self, context): scn_frame = context.scene.frame_current_final fcurves = dict() quats = dict() updates = dict() break_sync = False if hasattr(context.scene, 'sync'): break_sync = context.scene.sync.sync_3d and context.scene.sync.sync_between_3d if context.mode == 'POSE': # objects = [o for o in Get.objects(context) if o.mode == 'POSE'] objects = list() for ob in Get.objects(context): if ob.mode != 'POSE': continue bones = tuple([f"pose.bones[\"{b.name}\"]" for b in Get.selected_pose_bones(context, ob)]) if bones: objects.append((ob, bones)) if not objects: for ob in Get.objects(context): if ob.mode != 'POSE': continue bones = tuple([f"pose.bones[\"{b.name}\"]" for b in ob.pose.bones if Is.visible(context, b)]) if bones: objects.append((ob, bones)) else: objects = [(o, list()) for o in Get.selected_objects(context)] for (ob, bones) in objects: for anim in Get.animation_datas(ob): action = anim.action if not action: continue if anim.use_tweak_mode: frame = Get.frame_to_strip(context, anim, scn_frame) for s in Get.strips(anim.id_data): if s.action == action: strip = s if s.active: break blend = strip.blend_type else: frame = scn_frame blend = anim.action_blend_type offset = abs(frame - scn_frame) if (scn_frame < frame): offset *= -1 for fc in anim.action.fcurves: path = fc.data_path index = fc.array_index if len(fc.keyframe_points) < 2: continue if bones: src = None for bone in bones: if path.startswith(bone): try: eval(repr(ob) + '.' + path) # Validate path src = eval(repr(ob) + '.' + bone) attr = path.replace(bone, '', 1) if attr.startswith('.'): attr = attr.replace('.', '', 1) is_custom = False else: is_custom = True except: # curve points to a removed bone or something src = None break else: # Pose mode but bone not selected continue if src is None: # Missing bone continue else: attr = path src = ob if attr in transforms: is_custom = False elif attr.startswith('["'): is_custom = True else: # if attr.startswith(('pose.bones', 'bones')): continue # Find the property to be able to manipulate, and its current value if is_custom: prop = src split = attr.rsplit('"]["', 1) if len(split) == 2: prop = eval(repr(src) + split[0] + '"]') attr = '["' + split[1] prop_value = getattr(prop, attr) elif hasattr(src, attr): prop = getattr(src, attr) if Is.iterable(prop): # elif attr in transforms: # prop = src.path_resolve(attr) prop_value = prop[index] else: prop = src prop_value = getattr(prop, attr) else: # maybe a constraint: # pose.bones[bone.name].constraints[con.name].influence continue # Function to apply values to the bone/object, later if Is.iterable(prop): def apply(self, val): "Function to apply values to (array) in bone/object, later" self.prop[self.index] = val prop = src.path_resolve(attr) prop_value = prop[index] is_array = True else: def apply(self, val): setattr(self.prop, self.attr, val) is_array = False cache = dict( attr=attr, apply=apply, index=index, is_array=is_array, key=None, quat=None, prop=prop, src=src, value=fc.evaluate(frame), ) left = type('', (), cache) current = type('', (), cache) current.found = False right = type('', (), cache) pre_key = None # Find the current keyframe, and keys left and right if break_sync: # types = context.scene.keyframe_navigation_types types = ('KEYFRAME', 'MOVING_HOLD') for key in fc.keyframe_points: if key.co.x < frame: if (left.key is None) or (key.type in types): left.key = key left.value = key.co.y right.key = key right.value = key.co.y elif key.co.x == frame: if left.key is None: left.key = key left.value = key.co.y current.key = key current.found = True right.key = key right.value = key.co.y elif key.co.x > frame: if left.key is None: left.key = key left.value = key.co.y if (key.type in types) or key == fc.keyframe_points[-1]: right.key = key right.value = key.co.y break if not (left.key and right.key): for key in fc.keyframe_points: if key.co.x < frame: left.key = key left.value = key.co.y right.key = key right.value = key.co.y elif key.co.x == frame: if left.key is None: left.key = key left.value = key.co.y current.key = key current.found = True right.key = key right.value = key.co.y elif key.co.x > frame: if left.key is None: left.key = key left.value = key.co.y right.key = key right.value = key.co.y break if not (left.key and right.key): # Nothing to tween continue # Get info for current keyframe's defaults sVal = left.key.co.x + offset eVal = right.key.co.x + offset current.w1 = frame - sVal current.w2 = eVal - frame left.frame = left.key.co.x current.frame = frame right.frame = right.key.co.x current.in_range = False if frame < left.frame: left.value = prop_value elif right.frame < frame: right.value = prop_value else: current.in_range = True if blend == 'REPLACE': current.value = prop_value else: if not self.has_additive: self.has_additive = True if current.key: value = current.key.co.y else: value = fc.evaluate(frame) left.value = prop_value + (left.value - value) current.value = prop_value # + (value - value) right.value = prop_value + (right.value - value) if tween.update: if sVal not in updates: updates[sVal] = list() if eVal not in updates: updates[eVal] = list() updates[sVal].append(left) updates[eVal].append(right) # Add classes to memory fcurves[fc] = [left, current, right] if attr == 'rotation_quaternion': # Do math for quaternions if (action, path) not in quats: quats[(action, path)] = dict() if (src.lock_rotations_4d or not src.lock_rotation_w) \ and True not in src.lock_rotation[:]: quats[(action, path)][index] = (left, current, right) if updates: for frame in updates: context.scene.frame_set(frame) for (cls) in updates[frame]: if Is.iterable(prop): cls.value = cls.prop[cls.index] else: cls.value = getattr(cls.prop, cls.attr) context.scene.frame_set(scn_frame) for (action, path) in quats: if len(quats[action, path]) < 4: continue (w_left, w_current, w_right) = quats[action, path][0] (x_left, x_current, x_right) = quats[action, path][1] (y_left, y_current, y_right) = quats[action, path][2] (z_left, z_current, z_right) = quats[action, path][3] left_quat = [x.value for x in (w_left, x_left, y_left, z_left)] current_quat = [x.value for x in (w_current, x_current, y_current, z_current)] right_quat = [x.value for x in (w_right, x_right, y_right, z_right)] cpp.normalize_qt(left_quat) cpp.normalize_qt(current_quat) cpp.normalize_qt(right_quat) for x in (w_left, x_left, y_left, z_left): x.quat = left_quat for x in (w_current, x_current, y_current, z_current): x.quat = current_quat for x in (w_right, x_right, y_right, z_right): x.quat = right_quat return fcurves
def manual(context, src, path, **kargs): "Insert a keyframe manually. Sub is for sub targets, like constraints" # kargs: # sub=None, index=-1, frame=None, value=None, # group=None, insert_key=None, action=None, context=None insert_key = kargs.get('insert_key', None) sub = kargs.get('sub', None) index = kargs.get('index', -1) frame = kargs.get('frame', None) value = kargs.get('value', None) group = kargs.get('group', None) action = kargs.get('action', None) results = kargs.get('results', list()) options = kargs.get('options', set()) delete = kargs.get('delete', False) keyframe_type = kargs.get('type', context.scene.tool_settings.keyframe_type) if not keyframe.poll_insert(context, insert_key, src=src): return results # if frame is None: # frame = context.scene.frame_current # if group is None: # group = keyframe.group_name(src) # src.keyframe_insert(path, index=index, frame=frame, group=group) # return if group is None: group = keyframe.group_name(src) obj = src.id_data anim = obj.animation_data_create() if action is None: key_in_action = True action = anim.action if action is None: action = anim.action = bpy.data.actions.new("Action") else: # When using a specified action, don't insert # keyframes later to determine the property's value key_in_action = False if frame is None: frame = context.scene.frame_current_final strip = Get.active_strip(anim) if anim.use_tweak_mode else None if strip: if not (strip.frame_start <= frame <= strip.frame_end): # frame outside of the strip's bounds key_in_action = False tweak_frame = Get.frame_to_strip(context, anim, frame=frame) else: tweak_frame = frame if Is.posebone(src) and not path.startswith('pose.bones'): keypath = utils.string('pose.bones[\"', src.name, '\"]', '' if path.startswith('[') else '.', path) elif not Is.object(src) and hasattr(src, 'path_from_id'): keypath = src.path_from_id(path) else: keypath = path # Find the value(s) to insert keyframes for if hasattr(sub, path): base = getattr(sub, path) elif hasattr(src, path): base = getattr(src, path) else: base = eval(f'{obj!r}.{keypath}') if value is None: prop = base else: if Is.iterable(base) and not Is.iterable(value): prop = [value for i in base] elif not Is.iterable(base) and Is.iterable(value): prop = value[(index, 0)[index == -1]] else: prop = value if (not Is.iterable(prop)): if index != -1: index = -1 if (not Is.iterable(prop)): props = [(index, prop)] else: props = [(index, prop[index])] elif (index == -1): props = list(enumerate(prop)) else: props = [(index, prop[index])] def save_quats(): quats = [0, 0, 0, 0] for index in range(4): fc = Get.fcurve(action, keypath, index=index) if fc: quats[index] = len(fc.keyframe_points) return quats def restore_quats(skip): nonlocal quats for index in range(4): if index == skip: continue fc = Get.fcurve(action, keypath, index=index) if fc and len(fc.keyframe_points) > quats[index]: for key in fc.keyframe_points: if key.co.x == tweak_frame: fc.keyframe_points.remove(key) break else: # Shouldn't happen but backup in case fail to find key key_args = dict(index=index, frame=frame, group=group) if sub is None: src.keyframe_delete(path, **key_args) else: sub.keyframe_delete(path, **key_args) if path.endswith('rotation_quaternion') and ( (strip and strip.blend_type == 'COMBINE') or (not strip and anim.action_blend_type == 'COMBINE')): # Combine forces keyframe insertion on all 4 channels, so reset them quats = save_quats() else: quats = None # Create curve(s) (if needed) and keyframe(s) for (i, v) in props: fc = Get.fcurve(action, keypath, i) new_fc = not bool(fc) if new_fc: fc = New.fcurve(action, keypath, index=i, group=group) if fc.lock: # Internal ops don't allow keyframing locked channels, so :p results.append((fc, None)) continue if delete: results.append((fc, src.keyframe_delete(path, frame=frame))) continue count = len(fc.keyframe_points) if (value is None) and key_in_action: key_args = dict(index=i, frame=frame, group=group, options=options) if sub is None: src.keyframe_insert(path, **key_args) else: sub.keyframe_insert(path, **key_args) v = fc.evaluate(tweak_frame) key = fc.keyframe_points.insert(tweak_frame, v, options={'FAST'}) # options=set({'REPLACE', 'NEEDED', 'FAST'}) # src.keyframe_insert(path, index=i, frame=frame, group=group) if quats: restore_quats(skip=i) quats[i] = len(fc.keyframe_points) # Update key count for current index, to not remove it later # Update keyframe to use default preferences values edit = utils.prefs().edit key.handle_left_type = key.handle_right_type = \ edit.keyframe_new_handle_type if new_fc: # When inserting keyframes, only change their interpolation type if the fcurve is new key.interpolation = edit.keyframe_new_interpolation_type if len(fc.keyframe_points) > count: # New keyframe was added key.type = keyframe_type results.append((fc, key)) if kargs.get('index', -1) == -1: return results else: return results[0]