def lerp(current, target, factor=1.0, falloff=False): """ Blend between two values\\ current <to> target """ if falloff: factor = utils.proportional(factor, mode=falloff) def blend(current, target): if (Is.digit(current) and Is.digit(target)) and not \ (Is.string(current) or Is.string(target)): return (current * (1.0 - factor) + target * factor) elif (factor): return target else: return current if (Is.matrix(current) and Is.matrix(target)): return current.lerp(target, factor) elif (Is.iterable(current) and Is.iterable(target)) and not \ (Is.string(current) or Is.string(target)): # Assume the items are tuple/list/set. Not dict (but dicts can merge) merge = list() for (s, o) in zip(current, target): merge.append(blend(s, o)) return merge else: return blend(current, target)
def blend(current, target): if (Is.digit(current) and Is.digit(target)) and not \ (Is.string(current) or Is.string(target)): return (current * (1.0 - factor) + target * factor) elif (factor): return target else: return current
def valid_op(*ops): """Validate whether or not an operator is in bpy.ops; if True, return operator""" valid = list() for op in ops: if not Is.string(op): if hasattr(op, 'bl_idname'): op = op.bl_idname else: continue try: exec(f'bpy.ops.{op}.get_rna_type()') valid.append(op) except: continue if len(ops) > 1: return valid elif valid: return valid[0] else: return None
def proportional(dist, mode: "string or context" = None, rng: "Random Seed" = None): """Convert a number (from 0-1) to its proportional equivalent""" from math import sqrt from random import random if not (0 <= dist <= 1): return dist if not Is.string(mode) and mode is not None: mode = mode.scene.tool_settings.proportional_edit_falloff if mode == 'SHARP': return dist * dist elif mode == 'SMOOTH': return 3.0 * dist * dist - 2.0 * dist * dist * dist elif mode == 'ROOT': return sqrt(dist) elif mode == 'LINEAR': return dist elif mode == 'CONSTANT': return 1.0 elif mode == 'SPHERE': return sqrt(2 * dist - dist * dist) elif mode == 'RANDOM': if (rng is None): rng = random() return rng * dist elif mode == 'INVERSE_SQUARE': return dist * (2.0 - dist) else: # default equivalent to constant return 1
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 macro(*ops, poll=None, **kargs): """Get an operator to run a sequence of operators in succession if they aren't cancelled""" idname = kargs.get('idname', '_macro_._macro_start_') # For defaults, insert ops as operator or bl_idname # For operators with props: # ops = (bl_idname, dict(prop=var, )) class MACRO(bpy.types.Macro): bl_idname = idname bl_label = "Start Macro" bl_options = {'MACRO'} @classmethod def poll(self, context): if poll: return poll(self, context) return True bpy.utils.register_class(MACRO) # bpy.macro = MACRO for op in ops: if Is.iterable(op) and not Is.string(op): (op, props) = (op[0], dict(*op[1:])) else: props = dict() if hasattr(op, 'bl_idname'): op = op.bl_idname if Is.string(op): if op.startswith('bpy.ops.'): op = eval(op) else: op = eval('bpy.ops.' + op) operator = MACRO.define(op.idname()) for prop in props: setattr(operator.properties, prop, props[prop]) return eval('bpy.ops.' + idname)
def poll_insert(context, insert_key=None, src=None): if (src is not None): if Is.string(src): name = src else: name = getattr(src, 'name', '') if name.startswith(keyframe.badBonePrefixes): return False if insert_key is None: return keyframe.use_auto(context) return insert_key
def bone_group(bone, group, color=None): """ Assign bone to group\\ if group is text, find the group or create it first """ if Is.string(group): bgs = bone.id_data.pose.bone_groups if group in bgs: group = bgs[group] else: from zpy import New group = New.bone_group(bone, name=group) 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 bone.bone_group = group return group
def object_bone_from_string(target: "String or Object", subtarget=''): """Try to find an object an bone from text""" if Is.string(target): obj = bpy.data.objects.get(target) else: obj = target bone = None if Is.armature(obj) and subtarget: if (obj.mode == 'EDIT'): bones = obj.data.edit_bones else: bones = obj.pose.bones bone = bones.get(subtarget) return (obj, bone)
def strip(anim, action, name=True, blend_type=None, extrapolation=None, track=None): """Create new strip using specifed action""" # Get strip blend and extend modes if blend_type is None: blend_type = anim.action_blend_type if extrapolation is None: extrapolation = anim.action_extrapolation # Get new strip's name if not Is.string(name): blend = Get.nla_blend_name(anim) if name is True: name = f"{blend}: {action.name}" elif name is None: name = f"{blend}: Action" # Don't use unique names else: name = action.name # Find or create track to place new strip (astart, aend) = (action.frame_range) if track: pass elif anim.nla_tracks: active_track = anim.nla_tracks[-1] for strip in active_track.strips: if active_track.lock: track = anim.nla_tracks.new() break sstart = strip.frame_start send = strip.frame_end if (send <= astart) or (aend <= sstart): # Strip does not conflict with action continue if any(( (sstart <= astart <= send), (sstart <= aend <= send), (astart <= sstart <= aend), (astart <= send <= aend), )): # The strip is in the range of the action track = anim.nla_tracks.new() break else: track = active_track else: track = anim.nla_tracks.new() # Create and name new strip strip = track.strips.new("", astart, action) strip.name = name strip.blend_type = blend_type strip.extrapolation = extrapolation return strip