Пример #1
0
def parse_vmd_used_dict(frames: List[Union[vmdstruct.VmdBoneFrame,
                                           vmdstruct.VmdMorphFrame]],
                        frametype="",
                        moreinfo=False) -> dict:
    """
	Generate a dictionary where keys are bones/morphs that are "actually used" and values are # of times they are used.
	"Actually used" means the first frame with a nonzero value and each frame after that. (ignore leading repeated zeros)
	
	:param frames: list of VmdBoneFrame obj or VmdMorphFrame obj
	:param frametype: str "bone" or str "morph" to indicate which kind of frames are being processed
	:param moreinfo: print extra info and stuff
	:return: dict of {name: used_ct} that only includes names of "actually used" bones/morphs
	"""
    if frametype == "bone":
        t = True
    elif frametype == "morph":
        t = False
    else:
        core.MY_PRINT_FUNC("parse_vmd_used_dict invalid mode '%s' given" %
                           frametype)
        raise RuntimeError()

    bonedict = {}
    # 1, ensure frames are in sorted order
    frames_sorted = sorted(frames, key=lambda x: x.f)
    boneset = set()  # set of everything that exists, used or not
    # 2, iterate over items and count all instances except first if first has no value
    for bone in frames_sorted:
        boneset.add(bone.name)
        if bone.name not in bonedict:  # if this has not been used before,
            if t is False:
                if bone.val == 0.0:  # if it is not used now,
                    continue  # do not count it.
            else:
                if list(bone.pos) == [0.0, 0.0, 0.0] and list(
                        bone.rot) == [0.0, 0.0, 0.0]:  # if it is not used now,
                    continue  # do not count it.
        core.increment_occurance_dict(
            bonedict,
            bone.name)  # if it has been used before or is used now, count it.
    # 3, if there are any "used" items then print a statement saying so
    if len(bonedict) > 0 and moreinfo:
        if t is False:
            core.MY_PRINT_FUNC("...unique morphs, used/total= %d / %d" %
                               (len(bonedict), len(boneset)))
        else:
            core.MY_PRINT_FUNC("...unique bones, used/total = %d / %d" %
                               (len(bonedict), len(boneset)))

    return bonedict
def identify_unused_bones(pmx: pmxstruct.Pmx, moreinfo: bool) -> List[int]:
    """
	Process the PMX and return a list of all unused bone indicies in the model.
	1. get bones used by a rigidbody.
	2. get bones that have weight on at least 1 vertex.
	3. mark "exception" bones, done here so parents of exception bones are kept too.
	4. inheritance, aka "bones used by bones", recursively climb the tree & get all bones the "true" used bones depend on.
	5. tails or point-ats.
	6. invert used to get set of unused.

	:param pmx: PMX list-of-lists object
	:param moreinfo: print extra info for debug or whatever
	:return: list of bone indices that are not used
	"""
    # python set: no duplicates! .add(newbone), "in", .discard(delbone)
    # true_used_bones is set of BONE INDEXES
    true_used_bones = set()  # exception bones + rigidbody bones + vertex bones
    vertex_ct = {
    }  # how many vertexes does each bone control? sometimes useful info

    # first: bones used by a rigidbody
    for body in pmx.rigidbodies:
        true_used_bones.add(body.bone_idx)

    # second: bones used by a vertex i.e. has nonzero weight
    # any vertex that has nonzero weight for that bone
    for vert in pmx.verts:
        for boneidx, weightval in vert.weight:
            if weightval != 0:
                true_used_bones.add(boneidx)
                core.increment_occurance_dict(vertex_ct, boneidx)

    # NOTE: some vertices/rigidbodies depend on "invalid" (-1) bones, clean that up here
    true_used_bones.discard(-1)

    # third: mark the "exception" bones as "used" if they are in the model
    for protect in BONES_TO_PROTECT:
        # get index from JP name
        i = core.my_list_search(pmx.bones, lambda x: x.name_jp == protect)
        if i is not None:
            true_used_bones.add(i)

    # build ik groups here
    # IKbone + chain + target are treated as a group... if any 1 is used, all of them are used. build those groups now.
    ik_groups = []  # list of sets
    for d, bone in enumerate(pmx.bones):
        if bone.has_ik:  # if ik enabled for this bone,
            ik_set = set()
            ik_set.add(d)  # this bone
            ik_set.add(bone.ik_target_idx)  # this bone's target
            for link in bone.ik_links:
                ik_set.add(link.idx)  # all this bone's IK links
            ik_groups.append(ik_set)

    # fourth: NEW APPROACH FOR SOLVING INHERITANCE: RECURSION!
    # for each bone that we know to be used, run UP the inheritance tree and collect everything that it depends on
    # recursion inputs: pmx bonelist, ik groups, set of already-known-used, and the bone to start from
    # bonelist is readonly, ik groups are readonly
    # set of already-known-used overlaps with set-being-built, probably just use one global ref to save time merging sets
    # standard way: input is set-of-already-known, return set-built-from-target, that requires merging results after each call tho
    # BUT each function level adds exactly 1 or 0 bones to the set, therefore can just modify the set that is being passed around

    def recursive_climb_inherit_tree(target: int, set_being_built):
        # implicitly inherits variables pmx, ik_groups from outer scope
        if target in set_being_built or target == -1:
            # stop condition: if this bone idx is already known to be used, i have already ran recursion from this node. don't do it again.
            # also abort if the target is -1 which means invalid bone
            return
        # if not already in the set, but recursion is being called on this, then this bone is a "parent" of a used bone and should be added.
        set_being_built.add(target)
        # now the parents of THIS bone are also used, so recurse into those.
        bb = pmx.bones[target]
        # acutal parent
        recursive_climb_inherit_tree(bb.parent_idx, set_being_built)
        # partial inherit: if partial rot or partial move, and ratio is nonzero and parent is valid
        if (bb.inherit_rot or bb.inherit_trans
            ) and bb.inherit_ratio != 0 and bb.inherit_parent_idx != -1:
            recursive_climb_inherit_tree(bb.inherit_parent_idx,
                                         set_being_built)
        # IK groups: if in an IK group, recurse to all members of that IK group
        for group in ik_groups:
            if target in group:
                for ik_member in group:
                    recursive_climb_inherit_tree(ik_member, set_being_built)

    parent_used_bones = set()  # true_used_bones + parents + point-at links
    # now that the recursive function is defined, actually invoke the function from every truly-used bone
    for tu in true_used_bones:
        recursive_climb_inherit_tree(tu, parent_used_bones)

    # fifth: "tail" or point-at links
    # propogate DOWN the inheritance tree exactly 1 level, no more.
    # also get all bones these tails depend on, it shouldn't depend on anything new but it theoretically can.
    final_used_bones = set()
    for bidx in parent_used_bones:
        b = pmx.bones[bidx]
        # if this bone has a tail,
        if b.tail_usebonelink:
            # add it and anything it depends on to the set.
            recursive_climb_inherit_tree(b.tail, final_used_bones)
    # now merge the two sets
    final_used_bones = final_used_bones.union(parent_used_bones)

    # sixth: assemble the final "unused" set by inverting
    # set of all bones, for inverting purposes
    all_bones_list = list(range(len(pmx.bones)))
    all_bones_set = set(all_bones_list)

    unused_bones = all_bones_set.difference(final_used_bones)
    unused_bones_list = sorted(list(unused_bones))

    # print neat stuff
    if moreinfo:
        if unused_bones_list:
            core.MY_PRINT_FUNC(
                "Bones: total=%d, true_used=%d, parents=%d, tails=%d, unused=%d"
                %
                (len(pmx.bones), len(true_used_bones), len(parent_used_bones) -
                 len(true_used_bones), len(final_used_bones) -
                 len(parent_used_bones), len(unused_bones_list)))
        # debug aid
        if PRINT_VERTICES_CONTROLLED_BY_EACH_BONE:
            core.MY_PRINT_FUNC("Number of vertices controlled by each bone:")
            for bp in all_bones_list:
                if bp in vertex_ct:
                    core.MY_PRINT_FUNC("#: %d    ct: %d" % (bp, vertex_ct[bp]))

    return unused_bones_list