Exemplo n.º 1
0
def main(moreinfo=True):
    # prompt PMX name
    core.MY_PRINT_FUNC("Please enter name of PMX input file:")
    input_filename_pmx = core.MY_FILEPROMPT_FUNC(".pmx")
    # input_filename_pmx = "../../python_scripts/grasstest_better.pmx"
    pmx = pmxlib.read_pmx(input_filename_pmx, moreinfo=moreinfo)

    ##################################
    # user flow:
    # first ask whether they want to add armtwist, yes/no
    # second ask whether they want to add legtwist, yes/no
    # then do it
    # then write out to file
    ##################################

    working_queue = []

    s = core.MY_SIMPLECHOICE_FUNC((1, 2), [
        "Do you wish to add magic twistbones to the ARMS?", "1 = Yes, 2 = No"
    ])
    if s == 1:
        # add upperarm set and lowerarm set to the queue
        working_queue.append(armset)
        working_queue.append(wristset)
        pass
    s = core.MY_SIMPLECHOICE_FUNC((1, 2), [
        "Do you wish to add magic twistbones to the LEGS?", "1 = Yes, 2 = No"
    ])
    if s == 1:
        # TODO detect whether d-bones exist or not
        # add legs or d-legs set to the queue
        pass

    if not working_queue:
        core.MY_PRINT_FUNC("Nothing was changed")
        core.MY_PRINT_FUNC("Done")
        return None

    # for each set in the queue,
    for boneset in working_queue:
        # boneset = (start, end, preferred, oldrigs, bezier)
        for side in [jp_l, jp_r]:
            # print(side)
            # print(boneset)
            # 1. first, validate that start/end exist, these are required
            # NOTE: remember to prepend 'side' before all jp names!
            start_jp = side + boneset[0]
            start_idx = core.my_list_search(pmx.bones,
                                            lambda x: x.name_jp == start_jp)
            if start_idx is None:
                core.MY_PRINT_FUNC(
                    "ERROR: standard bone '%s' not found in model, this is required!"
                    % start_jp)
                continue
            end_jp = side + boneset[1]
            end_idx = core.my_list_search(pmx.bones,
                                          lambda x: x.name_jp == end_jp)
            if end_idx is None:
                core.MY_PRINT_FUNC(
                    "ERROR: standard bone '%s' not found in model, this is required!"
                    % end_jp)
                continue

            # 2. determine whether the 'preferredparent' exists and therefore what to acutally use as the parent
            parent_jp = side + boneset[2]
            parent_idx = core.my_list_search(pmx.bones,
                                             lambda x: x.name_jp == parent_jp)
            if parent_idx is None:
                parent_idx = start_idx

            # 3. attempt to collapse known armtwist rig names onto 'parent' so that the base case is further automated
            # for each bonename in boneset[3], if it exists, collapse onto boneidx parent_idx
            for bname in boneset[3]:
                rig_idx = core.my_list_search(
                    pmx.bones, lambda x: x.name_jp == side + bname)
                if rig_idx is None: continue  # if not found, try the next
                # when it is found, what 'factor' do i use?
                # print(side+bname)
                if pmx.bones[rig_idx].inherit_rot and pmx.bones[
                        rig_idx].inherit_parent_idx == parent_idx and pmx.bones[
                            rig_idx].inherit_ratio != 0:
                    # if using partial rot inherit AND inheriting from parent_idx AND ratio != 0, use that
                    # think this is good, if twistbones exist they should be children of preferred
                    f = pmx.bones[rig_idx].inherit_ratio
                elif pmx.bones[rig_idx].parent_idx == parent_idx:
                    # this should be just in case?
                    f = 1
                elif pmx.bones[rig_idx].parent_idx == start_idx:
                    # this should catch magic armtwist bones i previously created
                    f = 1
                else:
                    core.MY_PRINT_FUNC(
                        "Warning, found unusual relationship when collapsing old armtwist rig, assuming ratio=1"
                    )
                    f = 1
                transfer_bone_weights(pmx, parent_idx, rig_idx, f)
                pass
            # also collapse 'start' onto 'preferredparent' if it exists... want to transfer weight from 'arm' to 'armtwist'
            # if start == preferredparent this does nothing, no harm done
            transfer_bone_weights(pmx, parent_idx, start_idx, scalefactor=1)

            # 4. run the weight-cleanup function
            normalize_weights(pmx)

            # 5. append 3 new bones to end of bonelist
            # 	armYZ gets pos = start pos & parent = start parent
            basename_jp = pmx.bones[start_idx].name_jp
            armYZ_new_idx = len(pmx.bones)
            # armYZ = [basename_jp + yz_suffix, local_translate(basename_jp + yz_suffix)]  # name_jp,en
            # armYZ += pmx[5][start_idx][2:]					# copy the whole rest of the bone
            # armYZ[10:12] = [False, False]					# visible=false, enabled=false
            # armYZ[12:14] = [True, [armYZ_new_idx + 1]]		# tail type = tail, tail pointat = armYZend
            # armYZ[14:19] = [False, False, [], False, []]	# disable partial inherit + fixed axis
            # # local axis is copy
            # armYZ[21:25] = [False, [], False, []]			# disable ext parent + ik
            armYZ = pmxstruct.PmxBone(
                name_jp=basename_jp + yz_suffix,
                name_en=local_translate(basename_jp + yz_suffix),
                pos=pmx.bones[start_idx].pos,
                parent_idx=pmx.bones[start_idx].parent_idx,
                deform_layer=pmx.bones[start_idx].deform_layer,
                deform_after_phys=pmx.bones[start_idx].deform_after_phys,
                has_localaxis=True,
                localaxis_x=pmx.bones[start_idx].localaxis_x,
                localaxis_z=pmx.bones[start_idx].localaxis_z,
                tail_type=True,
                tail=armYZ_new_idx + 1,
                has_rotate=True,
                has_translate=True,
                has_visible=False,
                has_enabled=True,
                has_ik=False,
                inherit_rot=False,
                inherit_trans=False,
                has_fixedaxis=False,
                has_externalparent=False,
            )

            # 	armYZend gets pos = end pos & parent = armYZ
            # armYZend = [basename_jp + yz_suffix + "先", local_translate(basename_jp + yz_suffix + "先")]  # name_jp,en
            # armYZend += pmx[5][end_idx][2:]					# copy the whole rest of the bone
            # armYZend[5] = armYZ_new_idx						# parent = armYZ
            # armYZend[10:12] = [False, False]				# visible=false, enabled=false
            # armYZend[12:14] = [True, [-1]]					# tail type = tail, tail pointat = none
            # armYZend[14:19] = [False, False, [], False, []]	# disable partial inherit + fixed axis
            # # local axis is copy
            # armYZend[21:25] = [False, [], False, []]		# disable ext parent + ik
            armYZend = pmxstruct.PmxBone(
                name_jp=basename_jp + yz_suffix + "先",
                name_en=local_translate(basename_jp + yz_suffix + "先"),
                pos=pmx.bones[end_idx].pos,
                parent_idx=armYZ_new_idx,
                deform_layer=pmx.bones[end_idx].deform_layer,
                deform_after_phys=pmx.bones[end_idx].deform_after_phys,
                has_localaxis=True,
                localaxis_x=pmx.bones[end_idx].localaxis_x,
                localaxis_z=pmx.bones[end_idx].localaxis_z,
                tail_type=True,
                tail=-1,
                has_rotate=True,
                has_translate=True,
                has_visible=False,
                has_enabled=True,
                has_ik=False,
                inherit_rot=False,
                inherit_trans=False,
                has_fixedaxis=False,
                has_externalparent=False,
            )

            # # 	elbowIK gets pos = end pos & parent = end parent
            # armYZIK = [basename_jp + yz_suffix + "IK", local_translate(basename_jp + yz_suffix + "IK")]  # name_jp,en
            # armYZIK += pmx[5][end_idx][2:]					# copy the whole rest of the bone
            # armYZIK[10:12] = [False, False]					# visible=false, enabled=false
            # armYZIK[12:14] = [True, [-1]]					# tail type = tail, tail pointat = none
            # armYZIK[14:19] = [False, False, [], False, []]	# disable partial inherit + fixed axis
            # # local axis is copy
            # armYZIK[21:23] = [False, []]					# disable ext parent
            # armYZIK[23] = True								# ik=true
            # # add the ik info: [target, loops, anglelimit, [[link_idx, []], [link_idx, []]] ]
            # armYZIK[24] = [armYZ_new_idx+1, newik_loops, newik_angle, [[armYZ_new_idx, []]]]
            armYZIK = pmxstruct.PmxBone(
                name_jp=basename_jp + yz_suffix + "IK",
                name_en=local_translate(basename_jp + yz_suffix + "IK"),
                pos=pmx.bones[end_idx].pos,
                parent_idx=pmx.bones[end_idx].parent_idx,
                deform_layer=pmx.bones[end_idx].deform_layer,
                deform_after_phys=pmx.bones[end_idx].deform_after_phys,
                has_localaxis=True,
                localaxis_x=pmx.bones[end_idx].localaxis_x,
                localaxis_z=pmx.bones[end_idx].localaxis_z,
                tail_type=True,
                tail=-1,
                has_rotate=True,
                has_translate=True,
                has_visible=False,
                has_externalparent=False,
                has_enabled=True,
                inherit_rot=False,
                inherit_trans=False,
                has_fixedaxis=False,
                has_ik=True,
                ik_target_idx=armYZ_new_idx + 1,
                ik_numloops=newik_loops,
                ik_angle=newik_angle,
                ik_links=[pmxstruct.PmxBoneIkLink(idx=armYZ_new_idx)])

            # now append them to the bonelist
            pmx.bones.append(armYZ)
            pmx.bones.append(armYZend)
            pmx.bones.append(armYZIK)

            # 6. build the bezier curve
            bezier_curve = core.MyBezier(boneset[4][0],
                                         boneset[4][1],
                                         resolution=50)

            # 7. find relevant verts & determine unbounded percentile for each
            (verts, percentiles,
             centers) = calculate_percentiles(pmx, start_idx, end_idx,
                                              parent_idx)

            if moreinfo:
                core.MY_PRINT_FUNC(
                    "Blending between bones '{}'/'{}'=ZEROtwist and '{}'/'{}'=FULLtwist"
                    .format(armYZ.name_jp, armYZ.name_en,
                            pmx.bones[parent_idx].name_jp,
                            pmx.bones[parent_idx].name_en))
                core.MY_PRINT_FUNC(
                    "   Found %d potentially relevant vertices" % len(verts))

            # 8. use X or Y to choose border points, print for debugging, also scale the percentiles
            # first sort ascending by percentile value
            vert_zip = list(zip(verts, percentiles, centers))
            vert_zip.sort(key=lambda x: x[1])
            verts, percentiles, centers = zip(*vert_zip)  # unzip

            # X. highest point mode
            # "liberal" endpoints: extend as far as i can, include all good stuff even if i include some bad stuff with it
            # start at each end and work inward until i find a vert controlled by only parent_idx
            i_min_liberal = 0
            i_max_liberal = len(verts) - 1
            i_min_conserv = -1
            i_max_conserv = len(verts)
            for i_min_liberal in range(
                    0, len(verts)):  # start at head and work down,
                if pmx.verts[verts[
                        i_min_liberal]].weighttype == 0:  # if the vertex is BDEF1 type,
                    break  # then stop looking,
            p_min_liberal = percentiles[
                i_min_liberal]  # and save the percentile it found.
            for i_max_liberal in reversed(range(
                    0, len(verts))):  # start at tail and work up,
                if pmx.verts[verts[
                        i_max_liberal]].weighttype == 0:  # if the vertex is BDEF1 type,
                    break  # then stop looking,
            p_max_liberal = percentiles[
                i_max_liberal]  # and save the percentile it found.
            # Y. lowest highest point mode
            # "conservative" endpoints: define ends such that no bad stuff exists within bounds, even if i miss some good stuff
            # start in the middle and work outward until i find a vert NOT controlled by only parent_idx, then back off 1
            # where is the middle? use "bisect_left"
            middle = core.bisect_left(percentiles, 0.5)
            for i_min_conserv in reversed(
                    range(middle - 1)):  # start in middle, work toward head,
                if pmx.verts[verts[
                        i_min_conserv]].weighttype != 0:  # if the vertex is NOT BDEF1 type,
                    break  # then stop looking,
            i_min_conserv += 1  # and step back 1 to find the last vert that was good BDEF1,
            p_min_conserv = percentiles[
                i_min_conserv]  # and save the percentile it found.
            for i_max_conserv in range(
                    middle + 1,
                    len(verts)):  # start in middle, work toward tail,
                if pmx.verts[verts[
                        i_max_conserv]].weighttype != 0:  # if the vertex is NOT BDEF1 type,
                    break  # then stop looking,
            i_max_conserv -= 1  # and step back 1 to find the last vert that was good BDEF1,
            p_max_conserv = percentiles[
                i_max_conserv]  # and save the percentile it found.

            foobar = False
            if not (i_min_liberal <= i_min_conserv <= i_max_conserv <=
                    i_max_liberal):
                core.MY_PRINT_FUNC(
                    "ERROR: bounding indexes do not follow the expected relationship, results may be bad!"
                )
                foobar = True
            if foobar or moreinfo:
                core.MY_PRINT_FUNC(
                    "   Max liberal bounds:      idx = %d to %d, %% = %f to %f"
                    % (i_min_liberal, i_max_liberal, p_min_liberal,
                       p_max_liberal))
                core.MY_PRINT_FUNC(
                    "   Max conservative bounds: idx = %d to %d, %% = %f to %f"
                    % (i_min_conserv, i_max_conserv, p_min_conserv,
                       p_max_conserv))

            # IDEA: WEIGHTED BLEND! sliding scale!
            avg_factor = core.clamp(ENDPOINT_AVERAGE_FACTOR, 0.0, 1.0)
            if p_min_liberal != p_min_conserv:
                p_min = (p_min_liberal * avg_factor) + (p_min_conserv *
                                                        (1 - avg_factor))
            else:
                p_min = p_min_liberal
            if p_max_liberal != p_max_conserv:
                p_max = (p_max_liberal * avg_factor) + (p_max_conserv *
                                                        (1 - avg_factor))
            else:
                p_max = p_max_liberal
            # clamp just in case
            p_min = core.clamp(p_min, 0.0, 1.0)
            p_max = core.clamp(p_max, 0.0, 1.0)
            if moreinfo:
                i_min = core.bisect_left(percentiles, p_min)
                i_max = core.bisect_left(percentiles, p_max)
                core.MY_PRINT_FUNC(
                    "   Compromise bounds:       idx = %d to %d, %% = %f to %f"
                    % (i_min, i_max, p_min, p_max))

            # now normalize the percentiles to these endpoints
            p_len = p_max - p_min
            percentiles = [(p - p_min) / p_len for p in percentiles]

            # 9. divide weight between preferredparent (or parent) and armYZ
            vert_zip = list(zip(verts, percentiles, centers))
            num_modified, num_bleeders = divvy_weights(
                pmx=pmx,
                vert_zip=vert_zip,
                axis_limits=(pmx.bones[start_idx].pos, pmx.bones[end_idx].pos),
                bone_hasweight=parent_idx,
                bone_getsweight=armYZ_new_idx,
                bezier=bezier_curve)
            if moreinfo:
                core.MY_PRINT_FUNC(
                    "  Modified %d verts to use blending, %d are questionable 'bleeding' points"
                    % (num_modified, num_bleeders))
            pass
        pass

    # 10. run final weight-cleanup
    normalize_weights(pmx)

    # 11. write out
    output_filename_pmx = input_filename_pmx[0:-4] + "_magictwist.pmx"
    output_filename_pmx = core.get_unused_file_name(output_filename_pmx)
    pmxlib.write_pmx(output_filename_pmx, pmx, moreinfo=moreinfo)
    core.MY_PRINT_FUNC("Done!")
    return None
Exemplo n.º 2
0
def main(moreinfo=True):
    # prompt PMX name
    core.MY_PRINT_FUNC("Please enter name of PMX input file:")
    input_filename_pmx = core.MY_FILEPROMPT_FUNC(".pmx")
    pmx = pmxlib.read_pmx(input_filename_pmx, moreinfo=moreinfo)

    ##################################
    # user flow:
    # ask for the helper bone (to be merged)
    # ask for the destination bone (merged onto)
    # try to infer proper merge factor, if it cannot infer then prompt user
    # then write out to file
    ##################################

    dest_idx = 0
    while True:
        # any input is considered valid
        s = core.MY_GENERAL_INPUT_FUNC(lambda x: True, [
            "Please specify the DESTINATION bone that weights will be transferred to.",
            "Enter bone #, JP name, or EN name (names are case sensitive).",
            "Empty input will quit the script."
        ])
        # if empty, leave & do nothing
        if s == "":
            dest_idx = -1
            break
        # then get the bone index from this
        # search JP names first
        dest_idx = core.my_list_search(pmx.bones, lambda x: x.name_jp == s)
        if dest_idx is not None: break  # did i find a match?
        # search EN names next
        dest_idx = core.my_list_search(pmx.bones, lambda x: x.name_en == s)
        if dest_idx is not None: break  # did i find a match?
        # try to cast to int next
        try:
            dest_idx = int(s)
            if 0 <= dest_idx < len(pmx.bones):
                break  # is this within the proper bounds?
            else:
                core.MY_PRINT_FUNC("valid bone indexes are 0-%d" %
                                   (len(pmx.bones) - 1))
        except ValueError:
            pass
        core.MY_PRINT_FUNC("unable to find matching bone for name '%s'" % s)

    if dest_idx == -1:
        core.MY_PRINT_FUNC("quitting")
        return None

    dest_tag = "bone #{} JP='{}' / EN='{}'".format(dest_idx,
                                                   pmx.bones[dest_idx].name_jp,
                                                   pmx.bones[dest_idx].name_jp)
    source_idx = 0
    while True:
        # any input is considered valid
        s = core.MY_GENERAL_INPUT_FUNC(lambda x: True, [
            "Please specify the SOURCE bone that will be merged onto %s." %
            dest_tag,
            "Enter bone #, JP name, or EN name (names are case sensitive).",
            "Empty input will quit the script."
        ])
        # if empty, leave & do nothing
        if s == "":
            source_idx = -1
            break
        # then get the morph index from this
        # search JP names first
        source_idx = core.my_list_search(pmx.bones, lambda x: x.name_jp == s)
        if source_idx is not None: break  # did i find a match?
        # search EN names next
        source_idx = core.my_list_search(pmx.bones, lambda x: x.name_en == s)
        if source_idx is not None: break  # did i find a match?
        # try to cast to int next
        try:
            source_idx = int(s)
            if 0 <= source_idx < len(pmx.bones):
                break  # is this within the proper bounds?
            else:
                core.MY_PRINT_FUNC("valid bone indexes are 0-%d" %
                                   (len(pmx.bones) - 1))
        except ValueError:
            pass
        core.MY_PRINT_FUNC("unable to find matching bone for name '%s'" % s)

    if source_idx == -1:
        core.MY_PRINT_FUNC("quitting")
        return None

    # print to confirm
    core.MY_PRINT_FUNC(
        "Merging bone #{} JP='{}' / EN='{}' ===> bone #{} JP='{}' / EN='{}'".
        format(source_idx, pmx.bones[source_idx].name_jp,
               pmx.bones[source_idx].name_en, dest_idx,
               pmx.bones[dest_idx].name_jp, pmx.bones[dest_idx].name_en))
    # now try to infer the merge factor

    f = 0.0
    if pmx.bones[source_idx].inherit_rot and pmx.bones[
            source_idx].inherit_parent_idx == dest_idx and pmx.bones[
                source_idx].inherit_ratio != 0:
        # if using partial rot inherit AND inheriting from dest_idx AND ratio != 0, use that
        # think this is good, if twistbones exist they should be children of preferred
        f = pmx.bones[source_idx].inherit_ratio
    elif pmx.bones[source_idx].parent_idx == dest_idx:
        # if they have a direct parent-child relationship, then factor is 1
        f = 1
    else:
        # otherwise, prompt for the factor
        factor_str = core.MY_GENERAL_INPUT_FUNC(
            is_float,
            "Unable to infer relationship, please specify a merge factor:")
        if factor_str == "":
            core.MY_PRINT_FUNC("quitting")
            return None
        f = float(factor_str)

    # do the actual transfer
    transfer_bone_weights(pmx, dest_idx, source_idx, f)

    # run the weight-cleanup function
    dummy = normalize_weights(pmx)

    # write out
    output_filename_pmx = input_filename_pmx[0:-4] + "_weightmerge.pmx"
    output_filename_pmx = core.get_unused_file_name(output_filename_pmx)
    pmxlib.write_pmx(output_filename_pmx, pmx, moreinfo=moreinfo)
    core.MY_PRINT_FUNC("Done!")
    return None