def draw(self, context): layout = self.layout src = context.src mp = context.mp mpath = src.motion_path layout.prop(mp, 'show_frame_numbers') # , text="Frame Numbers") layout.prop(mp, 'show_keyframe_highlight') # , text="Keyframes") if mp.show_keyframe_highlight: if Is.posebone(src): layout.prop(mp, 'show_keyframe_action_all', text="+ Non-Grouped Keyframes") layout.prop(mp, 'show_keyframe_numbers', text="Keyframe Numbers") layout.separator() if Is.posebone(src): layout.label(text=src.name, icon='BONE_DATA') else: layout.label(text=src.name, icon='OBJECT_DATA') if mpath: layout.prop(mpath, 'lines') # , text="Lines") layout.prop(mpath, 'use_custom_color') # , text="Custom Color") else: op = 'zpy.update_motion_paths' layout.label(text="Add Motion Paths") layout.operator(op, text="At Heads").use_tails = False layout.operator(op, text="At Tails").use_tails = True layout.separator() mot = utils.prefs(__package__).motion layout.prop(mot, 'frame_before') layout.prop(mot, 'frame_after')
def execute(self, context): do_all = (self.mode == {'loc', 'rot', 'scale'}) selected = Get.selected(context, mirror=True) for src in selected: base = get_base(src) if 'loc' in self.mode: src.location = base.location if 'rot' in self.mode: if src.rotation_mode == 'QUATERNION': src.rotation_quaternion = base.rotation_quaternion elif src.rotation_mode == 'AXIS_ANGLE': src.rotation_axis_angle = base.rotation_axis_angle else: src.rotation_euler = base.rotation_euler if Is.posebone(src): src.bbone_curveinx = src.bbone_curveoutx = src.bbone_curveiny = src.bbone_curveouty = src.bbone_rollin = src.bbone_rollout = 0 if 'scale' in self.mode: src.scale = base.scale if Is.posebone(src): src.bbone_scaleinx = src.bbone_scaleinx = src.bbone_scaleiny = src.bbone_scaleoutx = src.bbone_scaleouty = 1 src.bbone_easein = src.bbone_easeout = 0 utils.clean_custom(src) keyframe.keyingset(context, selected=selected) return {'FINISHED'}
def tail(context, bone): # ------ Bones if any((Is.posebone(bone), Is.bone(bone), Is.editbone(bone))): if (Is.posebone(bone)): obj = bone.id_data else: obj = Get.rig(context, bone) if obj is None: return else: mat = Get.matrix(obj) return utils.multiply_matrix(mat, bone.tail)
def get_bone_child(self, _bone): if Is.posebone(_bone): bone = _bone.bone if _bone.child: child = _bone.child.bone else: # No bone connected, so try to find a substitute child = self.get_closest_child(bone) else: bone = _bone child = None if len(bone.children) == 1: child = bone.children[0] elif bone.children: for bone_child in bone.children: if bone_child.use_connect: if (child is not None): child_dist = Get.distance(child.tail, bone.tail) active_dist = Get.distance(bone_child.tail, bone.tail) if (child_dist == active_dist): # both children tails same distance from bone # Just sort alphabetically child = sorted( (b.name, b) for b in [child, bone_child])[0][1] continue elif (child_dist < active_dist): # Previous is closes continue child = bone_child if child is None: # No bone connected, so try to find a substitute child = self.get_closest_child(bone) return (bone, child)
def execute(self, context): if (context.mode == 'POSE'): if self.selected: bones = context.selected_pose_bones else: bones = [context.active_pose_bone] else: if self.selected: bones = context.selected_bones else: bones = [context.active_bone] for _bone in bones: (bone, child) = self.get_bone_child(_bone) self.set_bbone(bone, child) if Is.posebone(_bone): mirror_x = (_bone.id_data.pose.use_mirror_x or bone.id_data.use_mirror_x) else: mirror_x = bone.id_data.use_mirror_x if mirror_x: m_bone = Get.mirror_bone(_bone) if m_bone and (m_bone not in bones): (mbone, mchild) = self.get_bone_child(m_bone) self.set_bbone(mbone, mchild) return {'FINISHED'}
def sorted_bones(selected): "Scan through selected bones to find order of bones in a a tree" selected = [ b for b in selected if (Is.posebone(b) or Is.bone(b) or Is.editbone(b)) ] bones = list() while selected: for bone in list(selected): if (bone not in selected): # a previous bone was one of this bone's parents continue for parent in bone.parent_recursive: if parent in selected: break else: bones.append(bone) selected.remove(bone) # Loop over children, which "may" result in less looping for child in bone.children_recursive: if child in selected: bones.append(child) selected.remove(child) return bones
def bone_group(rig, name='Group', color=None): "" if Is.posebone(rig): rig = rig.id_data group = rig.pose.bone_groups.new(name=name) if Is.string(color): group.color_set = color # DEFAULT = regular bone color (not unique) # THEME01 - THEME15 = Builtin color palettes # THEME16 - THEME20 = Black for user assigned templates # CUSTOM = manually set elif color is False: group.color_set = 'DEFAULT' elif color is True: from random import randrange as random rand = f"{random(1, 15):02}" group.color_set = f'THEME{rand}' elif color: group.color_set = 'CUSTOM' gc = group.colors if not Is.iterable(color): if Is.digit(color): color = [[color] * 3] * 3 else: color = [color] * 3 (gc.normal, gc.select, gc.active) = color return group
def visible_armature_layers(bone, arm): """List of layer indexes that is visible and the Bone is on""" if Is.posebone(bone): bone = bone.bone if Is.armature(arm): arm = arm.data return [i for i, j in zip(bone.layers, arm.layers) if i and j]
def keyingset(context, default=True, **kargs): """ Use bpy.ops to insert keyframe. default means to only use the keying set if the keying set button is enabled """ insert_key = kargs.get('insert_key') if not keyframe.poll_insert(context, insert_key): return ks = context.scene.keying_sets_all.active if (default and not keyframe.use_keyingset(context)) or \ ks is None: ks = 'LocRotScale' else: ks = ks.bl_idname bones = kargs.get('bones', list()) objects = kargs.get('objects', list()) selected = kargs.get('selected', list()) # if bones is None: bones = Get.selected_pose_bones(context) # if objects is None: objects = Get.selected_objects(context) for src in selected: if Is.posebone(src): bones.append(src) elif Is.object(src): objects.append(src) else: assert None, ("This is not a bone or object", src) if kargs.get('skip_bones', False): # This step removes bones from badBonePrefixes for src in bones.copy(): if not keyframe.poll_insert(context, insert_key, src=src): bones.remove(src) # if keyframe.use_keyingset(): # bpy.ops.anim.keyframe_insert(type=ks, confirm_success=False) # # else: # for src in KSI.iter(): # keyframe.all(src) if (bones or objects): try: return bpy.ops.anim.keyframe_insert(dict(selected_pose_bones=bones, selected_objects=objects), type=ks, confirm_success=False) except Exception as ex: pass else: "Nothing to keyframe"
def mirror_bone(bone): name = utils.flip_name(bone.name) if name == bone.name: return if Is.posebone(bone): return bone.id_data.pose.bones.get(name) elif Is.editbone(bone): return bone.id_data.edit_bones.get(name) elif Is.bone(bone): return bone.id_data.bones.get(name)
def matrix(src, local=False, basis=False, tail=False, copy=True): # editbone. matrix # bone. matrix, matrix_local # posebone. matrix, matrix_basis, matrix_channel # object. matrix_world, matrix_basis, matrix_local, matrix_parent_inverse if Is.posebone(src): if copy: matrix = utils.multiply_matrix(src.id_data.matrix_world, src.matrix) else: matrix = src.matrix if basis: matrix_local = src.matrix_basis else: matrix_local = src.bone.matrix_local elif Is.bone(src): matrix = src.matrix matrix_local = src.matrix_basis elif Is.editbone(src): matrix = matrix_local = src.matrix else: matrix = src.matrix_world if basis: matrix_local = src.matrix_basis else: matrix_local = src.matrix_local if copy: matrix = matrix.copy() matrix_local = matrix_local.copy() # scaled = False # if len(set(matrix.to_scale())) == 1: # matrix *= matrix.to_scale()[0] # scaled = True if (tail and hasattr(src, 'tail')): # matrix.translation += (src.tail - src.head) matrix.translation = matrix.Translation(src.tail).translation matrix_local.translation = \ matrix_local.Translation(src.tail).translation # for (i, t) in enumerate(matrix.translation): # v = (src.tail[i] - src.head[i]) * ( # 1 if scaled else matrix.to_scale()[i]) # matrix.translation[i] += v if (basis or local): return matrix_local else: return matrix
def execute(self, context): selection = Get.selected(context) if not selection: selection = [Get.active(context)] for src in selection: if Is.posebone(src): mp = src.id_data.pose.animation_visualization.motion_path else: mp = src.animation_visualization.motion_path mp.type = self.type return {'FINISHED'}
def icon_from_type(src): "return an Icon id for the specified item's type" if Is.object(src): if src.type == 'LIGHT_PROBE': icon = 'LIGHTPROBE_' + src.data.type else: icon = src.type + '_DATA' elif Is.bone(src) or Is.editbone(src) or Is.posebone(src): icon = 'BONE_DATA' else: icon = 'ERROR' utils.debug("Can't find icon type for ", src, type(src)) return icon
def hook(curve, target, index): mod = curve.modifiers.new("Modifier Name", 'HOOK') mod.object = target.id_data if Is.posebone(target): mod.subtarget = target.name # modifier sets indices (which are the points + their two handles) mod_points = (index * 3 + 0, index * 3 + 1, index * 3 + 2) mod.vertex_indices_set(mod_points) mod.matrix_inverse = Get.matrix(target).inverted_safe() # Set the matrix, to allow the hook to stay with the "posed" bones # Don't get target's matrix, because it hasn't been updated yet mod.show_expanded = False return mod
def bone_group(src, name=""): """ Returns a named group inside rig. if src is a posebone, will return the bone's group instead. If the bone doesn't have a group, will try to return the named group instead. """ group = None if Is.posebone(src): group = src.bone_group if not group: group = src.id_data.pose.bone_groups.get(name) return group
def matrix_local(src, mat=None): """Get and convert a matrix to local space for later re-use""" if mat is None: mat = Get.matrix(src) if Is.posebone(src): # from zpy import Set # Set.matrix(src, Get.matrix(src)) # mat = Get.matrix(src, basis=True) # Setting the matrix updates it to the visual pose # Basis gets the applied pose # This would do that as well, without needing "apply": mat = src.id_data.convert_space(pose_bone=src, matrix=mat, from_space='POSE', to_space='LOCAL') "or so my notes said" # From Baker: # if visual keying: # # Get the final transform of the bone in its own local space... # matrix[name] = obj.convert_space( # pose_bone=bone, matrix=bone.matrix, # from_space=self.bone_matrix, to_space='LOCAL') # else: # matrix = bone.matrix_basis.copy() else: mat = src.id_data.convert_space(pose_bone=None, matrix=mat, from_space='WORLD', to_space='LOCAL') # From Baker: # If deleting object parents, do matrix world # if (Global.do_parents_clear): # trans['matrix'] = obj.matrix_world.copy() # else: # parent = obj.parent # matrix = obj.matrix_world # trans['matrix'] = multiply_matrix( # parent.matrix_world.inverted_safe(), matrix # ) if parent else matrix.copy() return mat
def select(target, value=True): """Select or Deselect an item""" if Is.object(target): target.select_set(value) elif Is.posebone(target): target.bone.select = value elif Is.bone(target) or Is.editbone(target): target.select = value elif target is None: pass else: # Give error assert None, ( "Error: zpy\\Set.select() can't use the provided target \n", target, )
def name(src): # orig = Constraint.get.original(src) # if orig: # src = orig if Is.posebone(src): name = f"{src.id_data.name}.{src.name}" else: name = f"{src.name}" # if src.constraints_relative.is_new: # name = name.rsplit('-', 1)[0] # if name.endswith('-'): # name = name.rsplit('-', 1)[0] return name
def rig(context, src): """ bone/editbone don't directly link to objects, just the armature.\\ So, try to find an object by using their armature instead """ is_armature = isinstance(src, bpy.types.Armature) if Is.posebone(src) or Is.armature(src.id_data): return src.id_data elif (is_armature, Is.bone(src) or Is.editbone(src)): for object in Get.objects(context): if (object.data == src.id_data): break else: object = None return object else: assert None, ("Could not find rig for", src)
def active(context, target): """Set target as active scene object or bone""" objects = Get.objects(context) # Remember previous active previous = Get.active(context) selected = Is.selected(target) # Set the active if Is.object(target): obj = target elif Is.posebone(target): obj = target.id_data obj.data.bones.active = obj.data.bones.get(target.name) elif isinstance(target, bpy.types.Armature): obj = Get.rig(context, target) elif Is.bone(target): obj = Get.rig(context, target) bones = target.id_data.bones bones.active = bones.get(target.name) elif Is.editbone(target): obj = Get.rig(context, target) if obj: in_edit = (obj.mode == 'EDIT') else: in_edit = (context.mode == 'EDIT_ARMATURE') if in_edit: bones = target.id_data.edit_bones bones.active = bones.get(target.name) elif target is None: obj = None # debug("Set.active() has None as the target") else: assert None, ("Set.active() can't use the provided target", target) if (target and Is.selected(target) != selected): # When setting a bone as active in a rig, it gets selected as well. Set.select(target, selected) objects.active = obj return previous
def sorted_chains(selected): "Scan through selected bones to find order of bones for spline_ik chains" chains = [] for bone in selected: if not any((Is.posebone(bone), Is.bone(bone), Is.editbone(bone))): continue chain = [] end = True for child in bone.children_recursive: if child in selected: end = False continue if end: for par in reversed(bone.parent_recursive): if par in selected: chain.append(par) chain.append(bone) chains.append(chain) return chains
def ik_chain(bone, constraint=None): "return list of parent bones connected to the bone through an IK chain" parents = list() if not (Is.posebone(bone) and bone.parent): return parents maxlen = len(bone.parent_recursive) def scan(constraint): if (constraint.type in ('IK', 'SPLINE_IK')): count = constraint.chain_count if (count == 0 or count > maxlen): parents.extend(bone.parent_recursive) else: if getattr(constraint, 'use_tail', True): # Spline IK starts count from the bone with the constraint chain_range = count - 1 else: chain_range = count for chain in range(chain_range): parents.append(bone.parent_recursive[chain]) if constraint is None: for constraint in bone.constraints: scan(constraint) else: scan(constraint) parents_sorted = list() if parents: for parent in bone.parent_recursive: if parent in parents: parents_sorted.append(parent) return parents_sorted
def matrix(src, matrix, local=False, basis=False): """ Set the visual transforms for a bone or object The parameters vary, so the input matrix should too: editbone. matrix bone. matrix, matrix_local posebone. matrix, matrix_basis, matrix_channel object. matrix_world, matrix_basis, matrix_local, matrix_parent_inverse """ if Is.object(src): if basis: src.matrix_basis = matrix elif local: src.matrix_local = matrix else: src.matrix_world = matrix else: if basis or local: if Is.posebone(src): src.matrix_basis = matrix elif Is.bone(src): src.matrix_local = matrix else: src.matrix = matrix else: src.matrix = matrix
def active_select(context, target, isolate=True): # , isolate_mode=False): """ Set target as active scene object or bone, and select it\\ # isolate_mode would set all other selected objects to Object mode """ # def deselect(src): # if isolate_mode and src.id_data.mode != 'OBJECT': # Set.mode(context, 'OBJECT', src) # Set.select(src, False) if isolate: objects = Get.selected_objects(context) bones = Get.selected_pose_bones(context) for src in [*objects, *bones]: # deselect(src) Set.select(src, False) Set.visible(context, target, True) Set.active(context, target) Set.select(target, True) if Is.posebone(target): Set.mode(context, 'POSE', target)
def action_fcurves(src, actions=None, raw_action=True): "Find actions used by a bone/object and return it's action + fcurves" fcurves = list() if actions is None: rig = src.id_data strips = Get.strips(rig, selected=False) anim = rig.animation_data an_act = getattr(anim, 'action', None) if raw_action else None actions = {an_act, *(s.action for s in strips)} for action in actions: if not action: continue for fc in action.fcurves: if any(( Is.object(src), Is.posebone(src) and fc.data_path.find(f'bones[\"{src.name}\"]') != -1, )): fcurves.append((action, fc)) return fcurves
def matrix_constraints(context, src, mat=None, force_constraints=[]): """Convert matrix so that it doesn't offset with the item's constraints""" from mathutils import Matrix mats_cons = list() mat_new = Get.matrix(src) if mat is None: mat = mat_new if Is.posebone(src): inv = src.id_data.matrix_world.inverted() mat = utils.multiply_matrix(inv, mat) mat_new = utils.multiply_matrix(inv, mat_new) for con in src.constraints: if not getattr(con, 'target', None) or \ con.mute or \ (con.influence == 0.0 and con not in force_constraints): continue else: pose = con.target.pose bone = None if pose and getattr(con, 'subtarget', None): bone = pose.bones.get(con.subtarget) if hasattr(con, 'inverse_matrix'): im = con.inverse_matrix imi = im.inverted() mat0 = utils.multiply_matrix(imi, mat) # The constraint with cleared inverse mat1 = mat.copy() # The goal pose without constraint con.inverse_matrix == utils.multiply_matrix(mat1, mat0.inverted()) # The value set initially for the constraint mat_tar = con.target.matrix_world.inverted() if bone: mat_tar = utils.multiply_matrix(mat_tar, bone.matrix.inverted()) mat_value = utils.multiply_matrix(imi, mat_tar) # # TODO: This get's the standard Child of, but it doesn't get # # a constraint without all 3 Rotation enabled # This is meant to give a list of matrices to cycle through # to try to find the correct mixture. What's wrong appears to # be that the target matrix from above needs to be altered, # and the current math is already valid # mat0 and mat # mats = ( # Matrix(), # 0 # mat0, # 1 # mat1, # 2 # mat_new, # 3 # mat_tar, # 4 # im, # 5 # mat0.inverted(), # 6 # mat1.inverted(), # 7 # mat_new.inverted(), # 8 # mat_tar.inverted(), # 9 # imi, # 10 # ) # ac = [' ' + a for a in ['a', 'b', 'c', 'd', 'e', 'f', 'g']] # for a in ac: # if src.get(a) is None: # src[a] = 0 # mat_value = multiply_matrix( # *( # mats[src[a]] for a in ac # ), # ) # Set influence of the result from constraint mat_influce = Matrix().lerp(mat_value, con.influence) mats_cons.append(mat_influce) elif getattr(con, 'use_offset', False): mats_cons.append(Matrix().lerp( utils.multiply_matrix(mat, mat_new.inverted()), con.influence)) return utils.multiply_matrix(*mats_cons, mat)
def visible(context, object, value=True, **kargs): """ Set an object's (or bone's object's) visibility to the specified value """ scn = context.scene if not Is.object(object): if isinstance(object, bpy.types.Collection): found = False def loop(root, tree=list()): nonlocal found if root.collection == object: return True for child in root.children: if loop(child, tree): found = True tree.append(child) break if found: return tree view_layer = kargs.get('view_layer', False) if not view_layer: object.hide_viewport = not value if value or view_layer: # Only enables the collection for the view layer once tree = loop(context.view_layer.layer_collection) for col in tree: if (col.exclude == value) and (col.name == object.name): # When a collection is enabled in the view layer, # all of its child collections are as well. col.exclude = not value if value and col.collection.hide_viewport: col.collection.hide_viewport = False elif Is.posebone(object): return Set.visible(context, object.id_data, value) elif Is.bone(object) or Is.editbone(object): return Set.visible(context, Get.rig(context, object), value) else: assert None, ( "Set.visible() does not work with the specified item", object, ) return Set.in_scene(context, object) is_visible = Is.visible(context, object) object_visible = not object.hide_viewport # if Is.visible(context, object) is value: # return visible while (Is.visible(context, object) is not value): "If object isn't in the desired visiblity, loop until it is" if (object.hide_viewport is value): object.hide_viewport = not value continue is_visible = object_visible view = None for collection in object.users_collection: view = context.view_layer.layer_collection.children.get( collection.name) if not view: # collection isn't in scene or whatever continue if view.hide_viewport is value: view.hide_viewport = not value break if view is None: assert None, ( "Set.visible(): Object[", object, "] \nis hidden from viewport and I don't know how to change it" ) # collection.hide_viewport = value break return is_visible
def driver(src, path, **kargs): driver_type = kargs.get('driver_type', None) # 'AVERAGE', 'Sum Values', 'SCRIPTED', 'Minimum Value', 'Maximum Value expression = kargs.get('expression', None) frames = kargs.get('frames', list()) # keyframe.co for the driver's fcurve name = kargs.get('name', "var") # Name of the variable added to the driver overwrite = kargs.get('overwrite', False) # Delete the existing driver rotation_mode = kargs.get('rotation_mode', 'AUTO') target = kargs.get('target', None) target_path = kargs.get('target_path', '') transform_space = kargs.get('transform_space', 'LOCAL_SPACE') transform_type = kargs.get('transform_type', 'LOC_X') var_type = kargs.get('var_type', None) # 'SINGLE_PROP', 'TRANSFORMS', 'Rotational Difference', 'Distance' if var_type is None: if target and (not target_path): var_type = 'TRANSFORMS' else: var_type = 'SINGLE_PROP' Driver = Get.driver(src, path) if not Driver: Driver = src.driver_add(path) overwrite = True if overwrite: while Driver.keyframe_points: Driver.keyframe_points.remove(Driver.keyframe_points[0]) if frames: if overwrite: Driver.extrapolation = 'LINEAR' while Driver.modifiers: Driver.modifiers.remove(Driver.modifiers[0]) Driver.keyframe_points.add(len(frames)) for key, co in zip(Driver.keyframe_points[:], frames): key.interpolation = 'LINEAR' key.co = co driver = Driver.driver if overwrite: if (expression is None): if (driver_type is None): driver_type = 'AVERAGE' elif (driver.type == 'SCRIPTED'): driver.expression = name while driver.variables: driver.variables.remove(driver.variables[0]) if expression is not None: driver.expression = expression if driver_type: driver.type = driver_type var = driver.variables.new() var.name = name var.type = var_type var_target = var.targets[0] if target: is_pose = Is.posebone(target) is_bone = Is.bone(target) or Is.editbone(target) is_obj = Is.object(target) if is_obj: var_target.id = target elif (is_pose or is_bone): var_target.id = target.id_data var_target.bone_target = target.name if target_path and (not target_path.startswith( ('pose.bones', 'bones'))): if is_pose: text = f'pose.bones["{target.name}"]' else: text = f'bones["{target.name}"]' if (target_path[0] != '['): text += '.' target_path = text + target_path else: try: var_target.id = target except: var_target.id = target.id_data var_target.data_path = target_path var_target.rotation_mode = rotation_mode var_target.transform_space = transform_space var_target.transform_type = transform_type return Driver
def execute(self, context): scn = context.scene active = Get.active(context) selected = Get.selected(context) is_pose = bool(context.mode == 'POSE') if not is_pose: self.use_tails = False mode = ('HEADS', 'TAILS')[self.use_tails] # Use the line thickness of the active item, across the selection if getattr(active, 'motion_path', None): line = active.motion_path.line_thickness else: line = 1 colors = dict() types = dict() for src in Get.selected(context): mp = src.motion_path if not mp: continue if mp.use_custom_color: colors[src] = mp.color if Is.posebone(src): display = src.id_data.pose.animation_visualization.motion_path else: display = src.animation_visualization.motion_path types[src] = display.type # Get the frame range to bake motion paths in motion = utils.prefs(__package__).motion if self.use_start_end: start = self.start_frame end = self.end_frame elif (motion.use_relative_range) or (scn.use_preview_range): start = scn.frame_preview_start end = scn.frame_preview_end fc = scn.frame_current fb = motion.frame_before fa = motion.frame_after if not (scn.use_preview_range) or (abs(end - start) > 100 > (fb + fa)): # If the preview range is too high, just default to nearby start = fc - fb end = fc + fa else: # if (active): # # Use the active object's motion path's in_range distance # if (Is.posebone(active)): # mp = active.id_data.pose.animation_visualization.motion_path # else: # mp = active.animation_visualization.motion_path # fb = mp.frame_before # fa = mp.frame_after # if (fb < 25): fb = 25 # if (fa < 25): fa = 25 # start = scn.frame_current - fb # end = scn.frame_current + fa start = scn.frame_start end = scn.frame_end fc = scn.frame_current if 150 < abs(end - start): start = fc - 50 end = fc + 50 # Create the motion paths args = dict(start_frame=start, end_frame=end + 1) if is_pose: op = bpy.ops.pose args['bake_location'] = mode # Operator only runs on active rig, so repeat for all in pose mode obs = {b.id_data for b in selected} for ob in obs: Set.active(context, ob) op.paths_clear(only_selected=True) op.paths_calculate(**args) else: Set.active(context, active) else: op = bpy.ops.object op.paths_clear(only_selected=True) op.paths_calculate(**args) for src in selected: mp = src.motion_path if not mp: continue mp.line_thickness = line color = colors.get(src) if color: mp.color = color if Is.posebone(src): display = src.id_data.pose.animation_visualization.motion_path else: display = src.animation_visualization.motion_path display.type = types.get(src, 'CURRENT_FRAME') # Set to use the frame hider instead of ever displaying all points # if is_pose: # src.id_data.pose.animation_visualization. \ # motion_path.type = 'CURRENT_FRAME' # else: # src.animation_visualization. \ # motion_path.type = 'CURRENT_FRAME' # scn.frame_set(scn.frame_current) return {'FINISHED'}
def value_from_nla_strips(context, src, path, index=-1, frame=None, selected=False, tweak=False): # Tries to actually read the strips # but it doesn't work with COMBINE quaternions anim = getattr(src.id_data, 'animation_data', None) if not anim: return if frame is None: frame = context.scene.frame_current_final if Is.posebone(src) and not path.startswith('pose.bones'): kpath = (f'pose.bones[\"{src.name}\"].' + path) else: kpath = path # Establish default if any(( 'default' is 1, path.endswith('scale'), path.endswith('rotation_quaternion') and index == 0, path.endswith('rotation_axis_angle') and index == 2, )): default = 1.0 else: default = 0.0 value = default default_quat = Quaternion() value_quat = default_quat first_strip = True for track in anim.nla_tracks: if track.mute: continue if tweak and anim.use_tweak_mode: for strip in track.strips: if strip.active: break else: strip = Get.strip_in_track(track, frame=frame) else: strip = Get.strip_in_track(track, frame=frame) action = getattr(strip, 'action', None) if not action or strip.mute: # Either no strip evaluated or strip is meta strips (TODO) continue # Try to find property in fcurve if index == -1: fc = action.fcurves.get(kpath) else: for fc in action.fcurves: if fc.data_path == kpath and fc.array_index == index: break else: fc = None if fc is None: if path.endswith('rotation_quaternion') and action.fcurves.get( kpath): pass else: continue if frame < strip.frame_start: frame_strip = strip.frame_start elif (frame > strip.frame_end): frame_strip = strip.frame_end else: frame_strip = frame if tweak and strip.active: ff = frame inf = 1.0 else: ff = frame_strip inf = Get.strip_influence(context, strip, frame=frame_strip) next_quat = Quaternion() if path.endswith('rotation_quaternion'): for i in range(4): # Don't research curves for the already found curve if fc and i == index: next = fc.evaluate(Get.frame_to_strip(strip, ff)) next_quat[i] = next continue for afc in action.fcurves: if afc.data_path == kpath and afc.array_index == i: next = afc.evaluate(Get.frame_to_strip(strip, ff)) next_quat[i] = next break else: next_quat[i] = default_quat[i] if fc: next = fc.evaluate(Get.frame_to_strip(context, strip, ff)) else: next = None if (first_strip): value = next value_quat = next_quat first_strip = False elif (strip.blend_type == 'COMBINE'): if (path.endswith('rotation_axis_angle') and index == 2): result = value + (next * inf) elif (path.endswith('rotation_quaternion') and index == 0): result = value + ((default - next) * inf) # result = value * pow(next, inf) elif path.endswith('scale'): result = value * (next * inf) else: # result = value - ((default - next) * inf) result = value + (next - default) * inf # utils.debug(f"{path}[{index}]\t{strip.name}") # utils.debug(f"\tValue\t{value}") # utils.debug(f"\tNext\t{next}") # utils.debug(f"\tResult\t{result}") # utils.debug(f'\tGoal\t{getattr(src, path)[index]}') value = result # "Quaternion blending is deferred until all sub-channel values are known." # # next_val = abs(next - default) * (-1 if next < default else 1) # # value += utils.lerp(0.0, next_val, inf) # if path.endswith('rotation_quaternion'): # mix_mode = 'QUATERNION' # return old value # mix_mode = 'AXIS_ANGLE' # return old_value + (value - base_value) * inf # # value = nla_combine_value(mix_mode, default, value, next, inf) # # nla_invert_combine_quaternion(value_quat, next_quat, inf, value_quat) # nla_combine_quaternion(value_quat, next_quat, inf, value_quat) # value = value_quat[index] # else: # # mix_mode = 'ADD' # pass # mix_mode = 'AXIS_ANGLE' # return old_value + (value - base_value) * inf # # mix_mode = 'MULTIPLY' # return old_value * pow(value / base_value, inf) # value = nla_combine_value(mix_mode, default, value, next, inf) else: value = nla_blend_value(strip.blend_type, value, next, inf) # nla_combine_quaternion(value_quat, next_quat, inf, value_quat) return value