예제 #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")
    pmx = pmxlib.read_pmx(input_filename_pmx, moreinfo=moreinfo)

    # to shift the model by a set amount:
    # first, ask user for X Y Z

    # create the prompt popup
    shift_str = core.MY_GENERAL_INPUT_FUNC(
        lambda x: (is_3float(x) is not None), [
            "Enter the X,Y,Z amount to shift this model by:",
            "Three decimal values separated by commas.",
            "Empty input will quit the script."
        ])

    # if empty, quit
    if shift_str == "":
        core.MY_PRINT_FUNC("quitting")
        return None
    # use the same func to convert the input string
    shift = is_3float(shift_str)

    ####################
    # then execute the shift:
    for v in pmx.verts:
        # every vertex position
        for i in range(3):
            v.pos[i] += shift[i]
        # c, r0, r1 params of every SDEF vertex
        # these correspond to real positions in 3d space so they need to be modified
        if v.weighttype == pmxstruct.WeightMode.SDEF:
            for param in v.weight_sdef:
                for i in range(3):
                    param[i] += shift[i]

    # bone position
    for b in pmx.bones:
        for i in range(3):
            b.pos[i] += shift[i]

    # rigid body position
    for rb in pmx.rigidbodies:
        for i in range(3):
            rb.pos[i] += shift[i]

    # joint position
    for j in pmx.joints:
        for i in range(3):
            j.pos[i] += shift[i]

    # that's it? that's it!

    # write out
    output_filename_pmx = input_filename_pmx[0:-4] + "_shift.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
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)
	
	# usually want to hide many morphs at a time, so put all this in a loop
	num_hidden = 0
	while True:
		core.MY_PRINT_FUNC("")
		# valid input is any string that can matched aginst a morph idx
		s = core.MY_GENERAL_INPUT_FUNC(lambda x: (morph_scale.get_idx_in_pmxsublist(x, pmx.morphs) is not None),
		   ["Please specify the target morph: morph #, JP name, or EN name (names are not case sensitive).",
			"Empty input will quit the script."])
		# do it again, cuz the lambda only returns true/false
		target_index = morph_scale.get_idx_in_pmxsublist(s, pmx.morphs)
		
		# when given empty text, done!
		if target_index == -1 or target_index is None:
			core.MY_PRINT_FUNC("quitting")
			break
		
		# determine the morph type
		morphtype = pmx.morphs[target_index].morphtype
		core.MY_PRINT_FUNC("Found {} morph #{}: '{}' / '{}'".format(
			morphtype, target_index, pmx.morphs[target_index].name_jp, pmx.morphs[target_index].name_en))
		core.MY_PRINT_FUNC("Was group {}, now group {}".format(
			pmx.morphs[target_index].panel, pmxstruct.MorphPanel.HIDDEN))
		# make the actual change
		pmx.morphs[target_index].panel = pmxstruct.MorphPanel.HIDDEN
		num_hidden += 1
		pass
	
	if num_hidden == 0:
		core.MY_PRINT_FUNC("Nothing was changed")
		return None
	
	# last step: remove all invalid morphs from all display panels
	for d, frame in enumerate(pmx.frames):  # for each display group,
		i = 0
		while i < len(frame.items):  # for each item in that display group,
			item = frame.items[i]
			if item.is_morph:  # if it is a morph
				# look up the morph
				morph = pmx.morphs[item.idx]
				# figure out what panel of this morph is
				# if it has an invalid panel #, delete it here
				if morph.panel == pmxstruct.MorphPanel.HIDDEN:
					frame.items.pop(i)
				else:
					i += 1
			else:
				i += 1
	
	# write out
	output_filename_pmx = input_filename_pmx[0:-4] + "_morphhide.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
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)

    core.MY_PRINT_FUNC("L upper arm...")
    make_autotwist_segment(pmx, jp_l, jp_arm, jp_armtwist, jp_elbow, moreinfo)

    core.MY_PRINT_FUNC("R upper arm...")
    make_autotwist_segment(pmx, jp_r, jp_arm, jp_armtwist, jp_elbow, moreinfo)

    core.MY_PRINT_FUNC("L lower arm...")
    make_autotwist_segment(pmx, jp_l, jp_elbow, jp_wristtwist, jp_wrist,
                           moreinfo)

    core.MY_PRINT_FUNC("R lower arm...")
    make_autotwist_segment(pmx, jp_r, jp_elbow, jp_wristtwist, jp_wrist,
                           moreinfo)

    # if i want to, set elbowD parent to armT...?
    # if i want to, set wrist parent to elbowT...?
    # that's what the original does, but why? why would I want that?
    # armT should have exactly the same deformations as armtwist
    # it's better to have the twist-rigs be isolated from eachother

    # TODO: examine leg system! not universal because nobody has legtwist bones to hijack but worth understanding

    # write out
    output_filename_pmx = input_filename_pmx[0:-4] + "_autotwist.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
예제 #4
0
def showprompt():
    # print info to explain what inputs/outputs it needs/creates
    core.MY_PRINT_FUNC(iotext)

    # prompt PMX name
    core.MY_PRINT_FUNC("Please enter name of PMX model file:")
    input_filename_pmx = core.prompt_user_filename(".pmx")
    pmx = pmxlib.read_pmx(input_filename_pmx, moreinfo=True)
    return pmx, input_filename_pmx
예제 #5
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)
    realbones = pmx.bones  # get bones
    realmorphs = pmx.morphs  # get morphs
    modelname_jp = pmx.header.name_jp
    modelname_en = pmx.header.name_en

    bonelist_out = [["modelname_jp", "'" + modelname_jp + "'"],
                    ["modelname_en", "'" + modelname_en + "'"],
                    ["bonename_jp", "bonename_en"]]
    morphlist_out = [["modelname_jp", "'" + modelname_jp + "'"],
                     ["modelname_en", "'" + modelname_en + "'"],
                     ["morphname_jp", "morphname_en"]]

    # in both lists, idx0 = name_jp, idx1 = name_en
    bonelist_pairs = [[a.name_jp, a.name_en] for a in realbones]
    morphlist_pairs = [[a.name_jp, a.name_en] for a in realmorphs]
    bonelist_out += bonelist_pairs
    morphlist_out += morphlist_pairs

    # write out
    output_filename_bone = "%s_bone_names.txt" % input_filename_pmx[0:-4]
    # output_filename_bone = output_filename_bone.replace(" ", "_")
    output_filename_bone = core.get_unused_file_name(output_filename_bone)
    core.MY_PRINT_FUNC("...writing result to file '%s'..." %
                       output_filename_bone)
    core.write_csvlist_to_file(output_filename_bone,
                               bonelist_out,
                               use_jis_encoding=False)

    output_filename_morph = "%s_morph_names.txt" % input_filename_pmx[0:-4]
    output_filename_morph = core.get_unused_file_name(output_filename_morph)
    core.MY_PRINT_FUNC("...writing result to file '%s'..." %
                       output_filename_morph)
    core.write_csvlist_to_file(output_filename_morph,
                               morphlist_out,
                               use_jis_encoding=False)
    core.MY_PRINT_FUNC("Done!")
    return None
예제 #6
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
예제 #7
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)

    core.MY_PRINT_FUNC("")
    # valid input is any string that can matched aginst a morph idx
    s = core.MY_GENERAL_INPUT_FUNC(
        lambda x: get_idx_in_pmxsublist(x, pmx.morphs) is not None, [
            "Please specify the target morph: morph #, JP name, or EN name (names are not case sensitive).",
            "Empty input will quit the script."
        ])
    # do it again, cuz the lambda only returns true/false
    target_index = get_idx_in_pmxsublist(s, pmx.morphs)

    # when given empty text, done!
    if target_index == -1 or target_index is None:
        core.MY_PRINT_FUNC("quitting")
        return None

    # determine the morph type
    morphtype = pmx.morphs[target_index].morphtype
    core.MY_PRINT_FUNC("Found {} morph #{}: '{}' / '{}'".format(
        morphtype, target_index, pmx.morphs[target_index].name_jp,
        pmx.morphs[target_index].name_en))

    # if it is a bone morph, ask for translation/rotation/both
    bone_mode = 0
    if morphtype == pmxstruct.MorphType.BONE:
        bone_mode = core.MY_SIMPLECHOICE_FUNC((1, 2, 3), [
            "Bone morph detected: do you want to scale the motion(translation), rotation, or both?",
            "1 = motion(translation), 2 = rotation, 3 = both"
        ])

    # ask for factor: keep looping this prompt until getting a valid float
    def is_float(x):
        try:
            v = float(x)
            return True
        except ValueError:
            core.MY_PRINT_FUNC("Please enter a decimal number")
            return False

    factor_str = core.MY_GENERAL_INPUT_FUNC(
        is_float, "Enter the factor that you want to scale this morph by:")
    if factor_str == "":
        core.MY_PRINT_FUNC("quitting")
        return None
    factor = float(factor_str)

    # important values: target_index, factor, morphtype, bone_mode
    # first create the new morph that is a copy of current
    if SCALE_MORPH_IN_PLACE:
        newmorph = pmx.morphs[target_index]
    else:
        newmorph = copy.deepcopy(pmx.morphs[target_index])
        # then modify the names
        name_suffix = "*" + (str(factor)[0:6])
        newmorph.name_jp += name_suffix
        newmorph.name_en += name_suffix
    # now scale the actual values

    r = morph_scale(newmorph, factor, bone_mode)

    if not r:
        core.MY_PRINT_FUNC("quitting")
        return None

    pmx.morphs.append(newmorph)

    # write out
    output_filename_pmx = input_filename_pmx[0:-4] + ("_%dscal.pmx" %
                                                      target_index)
    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
def main(moreinfo=False):
	# prompt PMX name
	core.MY_PRINT_FUNC("Please enter name of PMX model file:")
	input_filename_pmx = core.MY_FILEPROMPT_FUNC(".pmx")
	pmx = pmxlib.read_pmx(input_filename_pmx, moreinfo=moreinfo)
	
	#### how should these operations be ordered?
	# faces before verts, because faces define what verts are used
	# verts before weights, so i operate on fewer vertices & run faster
	# weights before bones, because weights determine what bones are used
	# verts before morph winnow, so i operate on fewer vertices & run faster
	# translate after bones/disp groups/morph winnow because they reduce the # of things to translate
	# uniquify after translate, because translate can map multiple different JP to same EN names
	# alphamorphs after translate, so it uses post-translate names for printing
	# deform order after translate, so it uses post-translate names for printing
	
	# if ANY stage returns True then it has made changes
	# final file-write is skipped only if NO stage has made changes
	is_changed = False
	core.MY_PRINT_FUNC("\n>>>> Deleting invalid & duplicate faces <<<<")
	pmx, is_changed_t = _prune_invalid_faces.prune_invalid_faces(pmx, moreinfo)
	is_changed |= is_changed_t	# or-equals: if any component returns true, then ultimately this func returns true
	core.MY_PRINT_FUNC("\n>>>> Deleting orphaned/unused vertices <<<<")
	pmx, is_changed_t = _prune_unused_vertices.prune_unused_vertices(pmx, moreinfo)
	is_changed |= is_changed_t
	core.MY_PRINT_FUNC("\n>>>> Deleting unused bones <<<<")
	pmx, is_changed_t = _prune_unused_bones.prune_unused_bones(pmx, moreinfo)
	is_changed |= is_changed_t
	core.MY_PRINT_FUNC("\n>>>> Normalizing vertex weights & normals <<<<")
	pmx, is_changed_t = _weight_cleanup.weight_cleanup(pmx, moreinfo)
	is_changed |= is_changed_t
	core.MY_PRINT_FUNC("\n>>>> Pruning imperceptible vertex morphs <<<<")
	pmx, is_changed_t = _morph_winnow.morph_winnow(pmx, moreinfo)
	is_changed |= is_changed_t
	core.MY_PRINT_FUNC("\n>>>> Fixing display groups: duplicates, empty groups, missing items <<<<")
	pmx, is_changed_t = _dispframe_fix.dispframe_fix(pmx, moreinfo)
	is_changed |= is_changed_t
	core.MY_PRINT_FUNC("\n>>>> Adding missing English names <<<<")
	pmx, is_changed_t = _translate_to_english.translate_to_english(pmx, moreinfo)
	is_changed |= is_changed_t
	core.MY_PRINT_FUNC("\n>>>> Ensuring all names in the model are unique <<<<")
	pmx, is_changed_t = _uniquify_names.uniquify_names(pmx, moreinfo)
	is_changed |= is_changed_t
	core.MY_PRINT_FUNC("\n>>>> Fixing bone deform order <<<<")
	pmx, is_changed_t = _bonedeform_fix.bonedeform_fix(pmx, moreinfo)
	is_changed |= is_changed_t
	core.MY_PRINT_FUNC("\n>>>> Standardizing alphamorphs and accounting for edging <<<<")
	pmx, is_changed_t = _alphamorph_correct.alphamorph_correct(pmx, moreinfo)
	is_changed |= is_changed_t

	core.MY_PRINT_FUNC("")
	core.MY_PRINT_FUNC("++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
	core.MY_PRINT_FUNC("++++      Scanning for other potential issues      ++++")

	longbone, longmorph = find_toolong_bonemorph(pmx)
	# also checks that bone/morph names can be stored in shift_jis for VMD usage
	if longmorph or longbone:
		core.MY_PRINT_FUNC("")
		core.MY_PRINT_FUNC("Minor warning: this model contains bones/morphs with JP names that are too long (>15 bytes)")
		core.MY_PRINT_FUNC("These will work just fine in MMD but will not properly save/load in VMD motion files")
		if longbone:
			ss = "[" + ", ".join(longbone[0:MAX_WARNING_LIST]) + "]"
			if len(longbone) > MAX_WARNING_LIST:
				ss = ss[0:-1] + ", ...]"
			core.MY_PRINT_FUNC("These %d bones are too long (index[length]): %s" % (len(longbone), ss))
		if longmorph:
			ss = "[" + ", ".join(longmorph[0:MAX_WARNING_LIST]) + "]"
			if len(longmorph) > MAX_WARNING_LIST:
				ss = ss[0:-1] + ", ...]"
			core.MY_PRINT_FUNC("These %d morphs are too long (index[length]): %s" % (len(longmorph), ss))
	
	shadowy_mats = find_shadowy_materials(pmx)
	if shadowy_mats:
		core.MY_PRINT_FUNC("")
		core.MY_PRINT_FUNC("Minor warning: this model contains transparent materials with visible edging")
		core.MY_PRINT_FUNC("Edging is visible even if the material is transparent, so this will look like an ugly silhouette")
		core.MY_PRINT_FUNC("Either disable edging in MMD when using this model, or reduce the edge parameters to 0 and re-add them in the morph that restores its opacity")
		ss = str(shadowy_mats[0:MAX_WARNING_LIST])
		if len(shadowy_mats) > MAX_WARNING_LIST:
			ss = ss[0:-1] + ", ...]"
		core.MY_PRINT_FUNC("These %d materials need edging disabled (index): %s" % (len(shadowy_mats), ss))
	
	boneless_bodies = find_boneless_bonebodies(pmx)
	if boneless_bodies:
		core.MY_PRINT_FUNC("")
		core.MY_PRINT_FUNC("WARNING: this model has bone-type rigidbodies that aren't anchored to any bones")
		core.MY_PRINT_FUNC("This won't crash MMD but it is probably a mistake that needs corrected")
		ss = str(boneless_bodies[0:MAX_WARNING_LIST])
		if len(boneless_bodies) > MAX_WARNING_LIST:
			ss = ss[0:-1] + ", ...]"
		core.MY_PRINT_FUNC("These %d bodies are boneless (index): %s" % (len(boneless_bodies), ss))
		
	jointless_bodies = find_jointless_physbodies(pmx)
	if jointless_bodies:
		core.MY_PRINT_FUNC("")
		core.MY_PRINT_FUNC("WARNING: this model has physics-type rigidbodies that aren't constrained by joints")
		core.MY_PRINT_FUNC("These will just roll around on the floor wasting processing power in MMD")
		ss = str(jointless_bodies[0:MAX_WARNING_LIST])
		if len(jointless_bodies) > MAX_WARNING_LIST:
			ss = ss[0:-1] + ", ...]"
		core.MY_PRINT_FUNC("These %d bodies are jointless (index): %s" % (len(jointless_bodies), ss))
		
	crashing_joints = find_crashing_joints(pmx)
	if crashing_joints:
		# make the biggest f*****g alert i can cuz this is a critical issue
		core.MY_PRINT_FUNC("")
		core.MY_PRINT_FUNC("! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ")
		core.MY_PRINT_FUNC("CRITICAL WARNING: this model contains invalid joints which WILL cause MMD to crash!")
		core.MY_PRINT_FUNC("These must be manually deleted or repaired using PMXE")
		core.MY_PRINT_FUNC("These %d joints are invalid (index): %s" % (len(crashing_joints), crashing_joints))
	
	core.MY_PRINT_FUNC("")
	core.MY_PRINT_FUNC("++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
	if not is_changed:
		core.MY_PRINT_FUNC("++++             No writeback required              ++++")
		core.MY_PRINT_FUNC("Done!")
		return
	
	core.MY_PRINT_FUNC("++++ Done with cleanup, saving improvements to file ++++")
	
	# write out
	# output_filename_pmx = "%s_better.pmx" % core.get_clean_basename(input_filename_pmx)
	output_filename_pmx = input_filename_pmx[0:-4] + "_better.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
예제 #9
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)

    # usually want to add/remove endpoints for many bones at once, so put all this in a loop
    num_changed = 0
    while True:
        core.MY_PRINT_FUNC("")
        # valid input is any string that can matched aginst a bone idx
        s = core.MY_GENERAL_INPUT_FUNC(
            lambda x:
            (morph_scale.get_idx_in_pmxsublist(x, pmx.bones) is not None), [
                "Please specify the target bone: bone #, JP name, or EN name (names are not case sensitive).",
                "Empty input will quit the script."
            ])
        # do it again, cuz the lambda only returns true/false
        target_index = morph_scale.get_idx_in_pmxsublist(s, pmx.bones)

        # when given empty text, done!
        if target_index == -1 or target_index is None:
            core.MY_PRINT_FUNC("quitting")
            break
        target_bone = pmx.bones[target_index]

        # print the bone it found
        core.MY_PRINT_FUNC("Found bone #{}: '{}' / '{}'".format(
            target_index, target_bone.name_jp, target_bone.name_en))

        if target_bone.tail_type:
            core.MY_PRINT_FUNC(
                "Was tailmode 'bonelink', changing to mode 'offset'")
            if target_bone.tail == -1:
                core.MY_PRINT_FUNC(
                    "Error: bone is not linked to anything, skipping")
                continue
            # find the location of the bone currently pointing at
            endpos = pmx.bones[target_bone.tail].pos
            # determine the equivalent offset vector
            offset = [endpos[i] - target_bone.pos[i] for i in range(3)]
            # write it into the bone
            target_bone.tail_type = False
            target_bone.tail = offset
            # done unlinking endpoint!
            pass

        else:
            core.MY_PRINT_FUNC(
                "Was tailmode 'offset', changing to mode 'bonelink' and adding new endpoint bone"
            )
            if target_bone.tail == [0, 0, 0]:
                core.MY_PRINT_FUNC(
                    "Error: bone has offset of [0,0,0], skipping")
                continue
            # determine the position of the new endpoint bone
            endpos = [
                target_bone.pos[i] + target_bone.tail[i] for i in range(3)
            ]
            # create the new bone
            newbone = pmxstruct.PmxBone(
                name_jp=target_bone.name_jp + endpoint_suffix_jp,
                name_en=target_bone.name_en + endpoint_suffix_en,
                pos=endpos,
                parent_idx=target_index,
                deform_layer=target_bone.deform_layer,
                deform_after_phys=target_bone.deform_after_phys,
                has_rotate=False,
                has_translate=False,
                has_visible=False,
                has_enabled=True,
                has_ik=False,
                has_localaxis=False,
                has_fixedaxis=False,
                has_externalparent=False,
                inherit_rot=False,
                inherit_trans=False,
                tail_type=True,
                tail=-1)
            # set the target to point at the new bone
            target_bone.tail_type = True
            target_bone.tail = len(pmx.bones)
            # append the new bone
            pmx.bones.append(newbone)
            # done adding endpoint!
            pass

        num_changed += 1
        pass

    if num_changed == 0:
        core.MY_PRINT_FUNC("Nothing was changed")
        return None

    core.MY_PRINT_FUNC("")

    # write out
    output_filename_pmx = input_filename_pmx[0:-4] + "_endpoints.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
예제 #10
0
def main(moreinfo=False):
    core.MY_PRINT_FUNC("Please enter name of PMX model file:")
    input_filename_pmx = core.MY_FILEPROMPT_FUNC(".pmx")

    # step zero: set up the translator thingy
    translate_to_english.init_googletrans()

    # texture sorting plan:
    # 1. get startpath = basepath of input PMX
    # 2. get lists of relevant files
    # 	2a. extract top-level 'neighbor' pmx files from all-set
    # 3. ask about modifying neighbor PMX
    # 4. read PMX: either target or target+all neighbor
    # 5. "categorize files & normalize usages within PMX", NEW FUNC!!!
    # 6. translate all names via Google Trans, don't even bother with local dict
    # 7. mask out invalid windows filepath chars just to be safe
    # 8. print proposed names & other findings
    # 	for unused files under a folder, combine & replace with ***
    # 9. ask for confirmation
    # 10. zip backup (NEW FUNC!)
    # 11. apply renaming, NEW FUNC! rename all including old PMXes on disk
    # 12. get new names for PMXes, write PMX from mem to disk if any of its contents changed
    #	i.e. of all FileRecord with a new name, create a set of all the PMX that use them

    # absolute path to directory holding the pmx
    input_filename_pmx_abs = os.path.normpath(
        os.path.abspath(input_filename_pmx))
    startpath, input_filename_pmx_rel = os.path.split(input_filename_pmx_abs)

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # first, build the list of ALL files that actually exist, then filter it down to neighbor PMXs and relevant files
    relative_all_exist_files = file_sort_textures.walk_filetree_from_root(
        startpath)
    core.MY_PRINT_FUNC("ALL EXISTING FILES:", len(relative_all_exist_files))
    # now fill "neighbor_pmx" by finding files without path separator that end in PMX
    # these are relative paths tho
    neighbor_pmx = [
        f for f in relative_all_exist_files
        if (f.lower().endswith(".pmx")) and (
            os.path.sep not in f) and f != input_filename_pmx_rel
    ]

    # no filtering, all files are relevant
    relevant_exist_files = relative_all_exist_files

    core.MY_PRINT_FUNC("NEIGHBOR PMX FILES:", len(neighbor_pmx))

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # now ask if I care about the neighbors and read the PMXes into memory

    pmx_filenames = [input_filename_pmx_rel]

    if neighbor_pmx:
        core.MY_PRINT_FUNC("")
        info = [
            "Detected %d top-level neighboring PMX files, these probably share the same filebase as the target."
            % len(neighbor_pmx),
            "If files are moved/renamed but the neighbors are not processed, the neighbor texture references will probably break.",
            "Do you want to process all neighbors in addition to the target? (highly recommended)",
            "1 = Yes, 2 = No"
        ]
        r = core.MY_SIMPLECHOICE_FUNC((1, 2), info)
        if r == 1:
            core.MY_PRINT_FUNC("Processing target + all neighbor files")
            # append neighbor PMX files onto the list of files to be processed
            pmx_filenames += neighbor_pmx
        else:
            core.MY_PRINT_FUNC(
                "WARNING: Processing only target, ignoring %d neighbor PMX files"
                % len(neighbor_pmx))
    # now read all the PMX objects & store in dict alongside the relative name
    # dictionary where keys are filename and values are resulting pmx objects
    all_pmx_obj = {}
    for this_pmx_name in pmx_filenames:
        this_pmx_obj = pmxlib.read_pmx(os.path.join(startpath, this_pmx_name),
                                       moreinfo=moreinfo)
        all_pmx_obj[this_pmx_name] = this_pmx_obj

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # 	for each pmx, for each file on disk, match against files used in textures (case-insensitive) and replace with canonical name-on-disk
    #	also fill out how much and how each file is used, and unify dupes between files, all that good stuff

    filerecord_list = file_sort_textures.categorize_files(
        all_pmx_obj, relevant_exist_files, moreinfo)

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # DETERMINE NEW NAMES FOR FILES

    # how to remap: build a list of all destinations (lowercase) to see if any proposed change would lead to collision
    all_new_names = set()

    # get new names via google
    # force it to use chunk-wise translate
    newname_list = translate_to_english.google_translate(
        [p.name for p in filerecord_list], strategy=1)

    # now repair any windows-forbidden symbols that might have shown up after translation
    newname_list = [
        n.translate(invalid_windows_chars_ord) for n in newname_list
    ]

    # iterate over the results in parallel with the FileRecord items
    for p, newname in zip(filerecord_list, newname_list):
        if newname != p.name:
            # resolve potential collisions by adding numbers suffix to file names
            # first need to make path absolute so get_unused_file_name can check the disk.
            # then check uniqueness against files on disk and files in namelist (files that WILL be on disk)
            newname = core.get_unused_file_name(os.path.join(
                startpath, newname),
                                                namelist=all_new_names)
            # now dest path is guaranteed unique against other existing files & other proposed name changes
            all_new_names.add(newname.lower())
            # make the path no longer absolute: undo adding "startpath" above
            newname = os.path.relpath(newname, startpath)
            p.newname = newname

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # NOW PRINT MY PROPOSED RENAMINGS and other findings

    # isolate the ones with proposed renaming
    translated_file = [u for u in filerecord_list if u.newname is not None]

    if translated_file:
        core.MY_PRINT_FUNC("=" * 60)
        core.MY_PRINT_FUNC("Found %d JP filenames to be translated:" %
                           len(translated_file))
        oldname_list = core.MY_JUSTIFY_STRINGLIST(
            [p.name for p in translated_file])
        newname_list = [p.newname for p in translated_file]
        zipped = list(zip(oldname_list, newname_list))
        zipped_and_sorted = sorted(
            zipped, key=lambda y: file_sort_textures.sortbydirdepth(y[0]))
        for o, n in zipped_and_sorted:
            # print 'from' with the case/separator it uses in the PMX
            core.MY_PRINT_FUNC("   {:s} --> {:s}".format(o, n))
        core.MY_PRINT_FUNC("=" * 60)
    else:
        core.MY_PRINT_FUNC("No proposed file changes")
        core.MY_PRINT_FUNC("Aborting: no files were changed")
        return None

    info = [
        "Do you accept these new names/locations?", "1 = Yes, 2 = No (abort)"
    ]
    r = core.MY_SIMPLECHOICE_FUNC((1, 2), info)
    if r == 2:
        core.MY_PRINT_FUNC("Aborting: no files were changed")
        return None

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # finally, do the actual renaming:

    # first, create a backup of the folder
    if MAKE_BACKUP_BEFORE_RENAMES:
        r = file_sort_textures.make_zipfile_backup(startpath, BACKUP_SUFFIX)
        if not r:
            # this happens if the backup failed somehow AND the user decided to quit
            core.MY_PRINT_FUNC("Aborting: no files were changed")
            return None

    # do all renaming on disk and in PMXes, and also handle the print statements
    file_sort_textures.apply_file_renaming(all_pmx_obj, filerecord_list,
                                           startpath)

    # write out
    for this_pmx_name, this_pmx_obj in all_pmx_obj.items():
        # what name do i write this pmx to? it may be different now! find it in the FileRecord!
        # this script does not filter filerecord_list so it is guaranteed to hae a record
        rec = None
        for r in filerecord_list:
            if r.name == this_pmx_name:
                rec = r
                break
        if rec.newname is None:
            # if there is no new name, write back to the name it had previously
            new_pmx_name = rec.name
        else:
            # if there is a new name, write to the new name
            new_pmx_name = rec.newname
        # make the name absolute
        output_filename_pmx = os.path.join(startpath, new_pmx_name)
        # write it, overwriting the existing file at that name
        pmxlib.write_pmx(output_filename_pmx, this_pmx_obj, moreinfo=moreinfo)

    core.MY_PRINT_FUNC("Done!")
    return None
예제 #11
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
예제 #12
0
def main(moreinfo=True):
    # copied codes
    core.MY_PRINT_FUNC("Please enter name of PMX model file:")
    input_filename_pmx = core.MY_FILEPROMPT_FUNC(".pmx")

    # object
    pmx_file_obj: pmxstruct.Pmx = pmxlib.read_pmx(input_filename_pmx,
                                                  moreinfo=moreinfo)

    # since there is an update to Valve Bip tools (I guess?), there is different bone names: the old and new one
    # only prefixes are changed along with order, thus there is a little bit scripting here to find the last leg
    big_dict: dict = {**body_dict, **leg_dict, **arm_dict, **finger_dict}

    #########################################################################
    # stage 1: create & insert core/base bones (grooves, mother,...)
    #########################################################################

    # base bone section
    # base order: 上半身, 下半身, 腰 (b_1), グルーブ, センター, 全ての親
    base_bone_4_name = "全ての親"  # motherbone
    base_bone_3_name = "センター"  # center
    base_bone_2_name = "グルーブ"  # groove
    base_bone_1_name = "腰"  # waist

    # note: Source models apparently have a much larger scale than MMD models
    base_bone_4_pos = [0, 0, 0]
    base_bone_3_pos = [0, 21, -0.533614993095398]
    base_bone_2_pos = base_bone_3_pos
    base_bone_1_pos = [0, 32, -0.533614993095398]

    # pelvis_pos = [-4.999999873689376e-06, 38.566917419433594, -0.533614993095398]

    # 全ての親, name_en, [0.0, 0.0, -0.4735046625137329], -1, 0, False,
    # True, True, True, True,
    # False, [0.0, 0.0, 0.0], False, False, None,
    # None, False, None, False, None, None, False, None, False,
    # None, None, None, None

    # base order: 上半身, 下半身, 腰 (b_1), グルーブ, センター, 全ての親
    base_bone_4_obj = pmxstruct.PmxBone(
        name_jp=base_bone_4_name,
        name_en="",
        pos=base_bone_4_pos,
        parent_idx=-1,
        deform_layer=0,
        deform_after_phys=False,
        has_rotate=True,
        has_translate=True,
        has_visible=True,
        has_enabled=True,
        has_ik=False,
        tail_usebonelink=False,
        tail=[0, 3, 0],
        inherit_rot=False,
        inherit_trans=False,
        has_fixedaxis=False,
        has_localaxis=False,
        has_externalparent=False,
    )
    insert_single_bone(pmx_file_obj, base_bone_4_obj, 0)

    base_bone_3_obj = pmxstruct.PmxBone(
        name_jp=base_bone_3_name,
        name_en="",
        pos=base_bone_3_pos,
        parent_idx=0,
        deform_layer=0,
        deform_after_phys=False,
        has_rotate=True,
        has_translate=True,
        has_visible=True,
        has_enabled=True,
        has_ik=False,
        tail_usebonelink=False,
        tail=[0, -3, 0],
        inherit_rot=False,
        inherit_trans=False,
        has_fixedaxis=False,
        has_localaxis=False,
        has_externalparent=False,
    )
    insert_single_bone(pmx_file_obj, base_bone_3_obj, 1)

    base_bone_2_obj = pmxstruct.PmxBone(
        name_jp=base_bone_2_name,
        name_en="",
        pos=base_bone_2_pos,
        parent_idx=1,
        deform_layer=0,
        deform_after_phys=False,
        has_rotate=True,
        has_translate=True,
        has_visible=True,
        has_enabled=True,
        has_ik=False,
        tail_usebonelink=False,
        tail=[0, 0, 1.5],
        inherit_rot=False,
        inherit_trans=False,
        has_fixedaxis=False,
        has_localaxis=False,
        has_externalparent=False,
    )
    insert_single_bone(pmx_file_obj, base_bone_2_obj, 2)

    base_bone_1_obj = pmxstruct.PmxBone(
        name_jp=base_bone_1_name,
        name_en="",
        pos=base_bone_1_pos,
        parent_idx=2,
        deform_layer=0,
        deform_after_phys=False,
        has_rotate=True,
        has_translate=True,
        has_visible=True,
        has_enabled=True,
        has_ik=False,
        tail_usebonelink=False,
        tail=[0, 0, 0],
        inherit_rot=False,
        inherit_trans=False,
        has_fixedaxis=False,
        has_localaxis=False,
        has_externalparent=False,
    )
    insert_single_bone(pmx_file_obj, base_bone_1_obj, 3)

    #########################################################################
    # phase 2: translate Source names to MMD names
    #########################################################################

    # for each mapping of source-name to mmd-name,
    for mmd_name, source_possible_names in big_dict.items():
        # for each bone,
        for index, bone_object in enumerate(pmx_file_obj.bones):
            # if it has a source-name, replace with mmd-name
            if bone_object.name_jp in source_possible_names:
                pmx_file_obj.bones[index].name_jp = mmd_name

    # next, fix the lowerbody bone
    # find lowerbod
    lowerbod_obj = core.my_list_search(pmx_file_obj.bones,
                                       lambda x: x.name_jp == "下半身",
                                       getitem=True)
    # elif bone_object.name_jp in ["ValveBiped.Bip01_Pelvis", "bip_pelvis"]:
    if lowerbod_obj is not None:
        # should not be translateable
        lowerbod_obj.has_translate = False
        # parent should be waist
        lowerbod_obj.parent_idx = 3
    # next, fix the upperbody bone
    upperbod_obj = core.my_list_search(pmx_file_obj.bones,
                                       lambda x: x.name_jp == "上半身",
                                       getitem=True)
    if upperbod_obj is not None:
        # should not be translateable
        upperbod_obj.has_translate = False
        # parent should be waist
        upperbod_obj.parent_idx = 3

    #########################################################################
    # phase 3: create & insert IK bones for leg/toe
    #########################################################################
    # find the last leg item index
    # when creating IK bones, want to insert the IK bones after both legs
    r_l_index = core.my_list_search(pmx_file_obj.bones,
                                    lambda x: x.name_jp == "右足")
    r_k_index = core.my_list_search(pmx_file_obj.bones,
                                    lambda x: x.name_jp == "右ひざ")
    r_a_index = core.my_list_search(pmx_file_obj.bones,
                                    lambda x: x.name_jp == "右足首")
    r_t_index = core.my_list_search(pmx_file_obj.bones,
                                    lambda x: x.name_jp == "右つま先")
    l_l_index = core.my_list_search(pmx_file_obj.bones,
                                    lambda x: x.name_jp == "左足")
    l_k_index = core.my_list_search(pmx_file_obj.bones,
                                    lambda x: x.name_jp == "左ひざ")
    l_a_index = core.my_list_search(pmx_file_obj.bones,
                                    lambda x: x.name_jp == "左足首")
    l_t_index = core.my_list_search(pmx_file_obj.bones,
                                    lambda x: x.name_jp == "左つま先")
    # if somehow they aren't found, default to 0
    if r_l_index is None: r_l_index = 0
    if r_k_index is None: r_k_index = 0
    if r_a_index is None: r_a_index = 0
    if r_t_index is None: r_t_index = 0
    if l_l_index is None: l_l_index = 0
    if l_k_index is None: l_k_index = 0
    if l_a_index is None: l_a_index = 0
    if l_t_index is None: l_t_index = 0

    if r_t_index > l_t_index:
        last_leg_item_index = r_t_index
    else:
        last_leg_item_index = l_t_index

    leg_left_ik_name = "左足IK"
    leg_left_toe_ik_name = "左つま先IK"
    leg_right_ik_name = "右足IK"
    leg_right_toe_ik_name = "右つま先IK"

    # these limits in degrees
    knee_limit_1 = [-180, 0.0, 0.0]
    knee_limit_2 = [-0.5, 0.0, 0.0]
    # other parameters
    ik_loops = 40
    ik_toe_loops = 8
    ik_angle = 114.5916  # degrees, =2 radians
    ik_toe_angle = 229.1831  # degrees, =4 radians

    # adding IK and such
    leg_left_ankle_obj = pmx_file_obj.bones[l_a_index]
    leg_left_toe_obj = pmx_file_obj.bones[l_t_index]
    leg_right_ankle_obj = pmx_file_obj.bones[r_a_index]
    leg_right_toe_obj = pmx_file_obj.bones[r_t_index]

    leg_left_ankle_pos = leg_left_ankle_obj.pos
    leg_left_toe_pos = leg_left_toe_obj.pos
    leg_right_ankle_pos = leg_right_ankle_obj.pos
    leg_right_toe_pos = leg_right_toe_obj.pos

    # toe /// places of some value wont match with the struct /// taken from hololive's korone model
    # name, name, [-0.823277473449707, 0.2155265510082245, -1.8799238204956055], 112, 0, False,
    # True, True, True, True,
    # False, [0.0, -1.3884940147399902, 1.2653569569920364e-07] /// This is offset, False, False, None,
    # None, False, None, False, None, None, False, None, True,
    # 111, 160, 1.0, [[110, None, None]]

    # leg
    # 右足IK, en_name, [-0.8402935862541199, 1.16348397731781, 0.3492986857891083], 0, 0, False,
    # True, True, True, True,
    # False, [0.0, -2.53071505085245e-07, 1.3884940147399902], False, False, None,
    # None, False, None, False, None, None, False, None, True,
    # 110, 85, 1.9896754026412964, [[109, [-3.1415927410125732, 0.0, 0.0], [-0.008726646192371845, 0.0, 0.0]]
    # /// These ik_links are in radians /// , [108, None, None]]

    leg_left_ik_obj = pmxstruct.PmxBone(
        name_jp=leg_left_ik_name,
        name_en="",
        pos=leg_left_ankle_pos,
        parent_idx=0,
        deform_layer=0,
        deform_after_phys=False,
        has_rotate=True,
        has_translate=True,
        has_visible=True,
        has_enabled=True,
        has_ik=True,
        tail_usebonelink=False,
        tail=[0.0, 0.0, 1.0],
        inherit_rot=False,
        inherit_trans=False,
        has_fixedaxis=False,
        has_localaxis=False,
        has_externalparent=False,
        ik_target_idx=l_a_index,
        ik_numloops=ik_loops,
        ik_angle=ik_angle,
        ik_links=[
            pmxstruct.PmxBoneIkLink(idx=l_k_index,
                                    limit_min=knee_limit_1,
                                    limit_max=knee_limit_2),
            pmxstruct.PmxBoneIkLink(idx=l_l_index)
        ],
    )
    insert_single_bone(pmx_file_obj, leg_left_ik_obj, last_leg_item_index + 1)

    leg_left_toe_ik_obj = pmxstruct.PmxBone(
        name_jp=leg_left_toe_ik_name,
        name_en="",
        pos=leg_left_toe_pos,
        parent_idx=last_leg_item_index + 1,
        deform_layer=0,
        deform_after_phys=False,
        has_rotate=True,
        has_translate=True,
        has_visible=True,
        has_enabled=True,
        has_ik=True,
        tail_usebonelink=False,
        tail=[0.0, -1.0, 0.0],
        inherit_rot=False,
        inherit_trans=False,
        has_fixedaxis=False,
        has_localaxis=False,
        has_externalparent=False,
        ik_target_idx=l_t_index,
        ik_numloops=ik_toe_loops,
        ik_angle=ik_toe_angle,
        ik_links=[pmxstruct.PmxBoneIkLink(idx=l_a_index)],
    )
    insert_single_bone(pmx_file_obj, leg_left_toe_ik_obj,
                       last_leg_item_index + 2)

    leg_right_ik_obj = pmxstruct.PmxBone(
        name_jp=leg_right_ik_name,
        name_en="",
        pos=leg_right_ankle_pos,
        parent_idx=0,
        deform_layer=0,
        deform_after_phys=False,
        has_rotate=True,
        has_translate=True,
        has_visible=True,
        has_enabled=True,
        has_ik=True,
        tail_usebonelink=False,
        tail=[0.0, 0.0, 1.0],
        inherit_rot=False,
        inherit_trans=False,
        has_fixedaxis=False,
        has_localaxis=False,
        has_externalparent=False,
        ik_target_idx=r_a_index,
        ik_numloops=ik_loops,
        ik_angle=ik_angle,
        ik_links=[
            pmxstruct.PmxBoneIkLink(idx=r_k_index,
                                    limit_min=knee_limit_1,
                                    limit_max=knee_limit_2),
            pmxstruct.PmxBoneIkLink(idx=r_l_index)
        ],
    )
    insert_single_bone(pmx_file_obj, leg_right_ik_obj, last_leg_item_index + 3)

    leg_right_toe_ik_obj = pmxstruct.PmxBone(
        name_jp=leg_right_toe_ik_name,
        name_en="",
        pos=leg_right_toe_pos,
        parent_idx=last_leg_item_index + 3,
        deform_layer=0,
        deform_after_phys=False,
        has_rotate=True,
        has_translate=True,
        has_visible=True,
        has_enabled=True,
        has_ik=True,
        tail_usebonelink=False,
        tail=[0.0, -1.0, 0.0],
        inherit_rot=False,
        inherit_trans=False,
        has_fixedaxis=False,
        has_localaxis=False,
        has_externalparent=False,
        ik_target_idx=r_t_index,
        ik_numloops=ik_toe_loops,
        ik_angle=ik_toe_angle,
        ik_links=[pmxstruct.PmxBoneIkLink(idx=r_a_index)],
    )
    insert_single_bone(pmx_file_obj, leg_right_toe_ik_obj,
                       last_leg_item_index + 4)

    # output the file
    output_filename_pmx = input_filename_pmx[0:-4] + "_sourcetrans.pmx"
    pmxlib.write_pmx(output_filename_pmx, pmx_file_obj, moreinfo=moreinfo)
    core.MY_PRINT_FUNC("Done!")
    return None
예제 #13
0
def main():
    # copied codes
    core.MY_PRINT_FUNC("Please enter name of PMX model file:")
    input_filename_pmx = core.MY_FILEPROMPT_FUNC(".pmx")

    moreinfo = False

    input_filename_pmx_abs = os.path.normpath(
        os.path.abspath(input_filename_pmx))
    startpath, input_filename_pmx_rel = os.path.split(input_filename_pmx_abs)

    # object
    retme: pmxstruct.Pmx = pmxlib.read_pmx(input_filename_pmx,
                                           moreinfo=moreinfo)

    # since there is an update to Valve Bip tools (I guess?), there is different bone names: the old and new one
    # only prefixes are changed along with order, thus there is a little bit scripting here to find the last leg
    big_dict: dict
    is_old: bool = False

    if "bip_" in retme.bones[0].name_jp:
        big_dict = {
            **old_body_dict,
            **old_arm_dict,
            **old_leg_dict,
            **old_finger_dict
        }
        is_old = True

    else:
        big_dict = {
            **new_body_dict,
            **new_arm_dict,
            **new_leg_dict,
            **new_finger_dict
        }

    # checking for last leg item so the code could be scalable
    last_leg_item: int
    last_leg_name: str
    last_leg_name_cmp_r: str = ""
    last_leg_name_cmp_l: str = ""

    r_l_index: int = 0
    r_k_index: int = 0
    r_a_index: int = 0
    r_t_index: int = 0
    l_l_index: int = 0
    l_k_index: int = 0
    l_a_index: int = 0
    l_t_index: int = 0

    # lol this is a mess but it works just fine okay
    for key in big_dict:
        for index, i in enumerate(retme.bones):
            # usually, the toes are the last parts of the legs, from there, we can interject the IK bones
            if i.name_jp == "bip_toe_R" or i.name_jp == "ValveBiped.Bip01_R_Toe0":
                r_t_index = index
                # last_leg_name_cmp_r = i.name_jp
            elif i.name_jp == "bip_toe_L" or i.name_jp == "ValveBiped.Bip01_L_Toe0":
                l_t_index = index
                # last_leg_name_cmp_l = i.name_jp
            # without this, the pelvis will start as "green"
            elif i.name_jp == "ValveBiped.Bip01_Pelvis" or i.name_jp == "bip_pelvis":
                retme.bones[index].has_translate = False

            elif i.name_jp == "ValveBiped.Bip01_R_Foot" or i.name_jp == "bip_foot_R":
                r_a_index = index
            elif i.name_jp == "ValveBiped.Bip01_L_Foot" or i.name_jp == "bip_foot_L":
                l_a_index = index
            elif i.name_jp == "ValveBiped.Bip01_R_Calf" or i.name_jp == "bip_knee_R":
                r_k_index = index
            elif i.name_jp == "ValveBiped.Bip01_L_Calf" or i.name_jp == "bip_knee_L":
                l_k_index = index
            elif i.name_jp == "ValveBiped.Bip01_R_Thigh" or i.name_jp == "bip_hip_R":
                r_l_index = index
            elif i.name_jp == "ValveBiped.Bip01_L_Thigh" or i.name_jp == "bip_hip_L":
                l_l_index = index

            # the part that replaces texts
            if i.name_jp == key:
                retme.bones[index].name_jp = big_dict[key]

    last_bone = len(retme.bones) - 1
    # if r_t_index > l_t_index:
    #     last_leg_item = r_t_index
    #     last_leg_name = last_leg_name_cmp_r
    # else:
    #     last_leg_item = l_t_index
    #     last_leg_name = last_leg_name_cmp_l
    # # print(f"This is last leg item {old_last_leg_item}")

    # base bone section
    # base order: 上半身, 下半身, 腰 (b_1), グルーブ, センター, 全ての親
    b1_name = "腰"
    b2_name = "グルーブ"
    b3_name = "センター"
    b4_name = "全ての親"

    # IK bone section
    leg_left_ik_name = "左足IK"
    leg_left_toe_ik_name = "左つま先IK"
    leg_right_ik_name = "右足IK"
    leg_right_toe_ik_name = "右つま先IK"

    knee_limit_1 = [-3.1415927410125732, 0.0, 0.0]
    knee_limit_2 = [-0.008726646192371845, 0.0, 0.0]

    # for some reasons, this value will always be the same
    # pelvis_pos = [-4.999999873689376e-06, 38.566917419433594, -0.533614993095398]

    # adding IK and such
    # leg_left_obj = retme.bones[last_leg_item + l_l]
    # leg_left_knee_obj = retme.bones[last_leg_item + l_k]
    leg_left_ankle_obj = retme.bones[l_a_index]
    leg_left_toe_obj = retme.bones[l_t_index]
    # leg_right_obj = retme.bones[last_leg_item + r_l]
    # leg_right_knee_obj = retme.bones[last_leg_item + r_k]
    leg_right_ankle_obj = retme.bones[r_a_index]
    leg_right_toe_obj = retme.bones[r_t_index]

    leg_left_ankle_pos = leg_left_ankle_obj.pos
    leg_left_toe_pos = leg_left_toe_obj.pos
    leg_right_ankle_pos = leg_right_ankle_obj.pos
    leg_right_toe_pos = leg_right_toe_obj.pos

    # toe /// places of some value wont match with the struct /// taken from hololive's korone model
    # name, name, [-0.823277473449707, 0.2155265510082245, -1.8799238204956055], 112, 0, False,
    # True, True, True, True,
    # False, [0.0, -1.3884940147399902, 1.2653569569920364e-07] /// This is offset, False, False, None,
    # None, False, None, False, None, None, False, None, True,
    # 111, 160, 1.0, [[110, None, None]]

    # leg
    # 右足IK, en_name, [-0.8402935862541199, 1.16348397731781, 0.3492986857891083], 0, 0, False,
    # True, True, True, True,
    # False, [0.0, -2.53071505085245e-07, 1.3884940147399902], False, False, None,
    # None, False, None, False, None, None, False, None, True,
    # 110, 85, 1.9896754026412964, [[109, [-3.1415927410125732, 0.0, 0.0], [-0.008726646192371845, 0.0, 0.0]]
    # /// These ik_links are in radians /// , [108, None, None]]
    # if name == "ValveBiped.Bip01_R_Toe0":
    #     retme.bones.insert(last_leg_item + 1, )

    leg_left_ik_obj = pmxstruct.PmxBone(
        leg_left_ik_name, "", leg_left_ankle_pos, last_bone + 5, 0, False,
        True, True, True, True, True, False, [0.0, 0.0, 0.0], False, False,
        False, False, False, None, None, None, None, None, None, l_a_index, 40,
        114.5916,
        [[l_k_index, knee_limit_1, knee_limit_2], [l_l_index, None, None]])
    retme.bones.insert(last_bone + 1, leg_left_ik_obj)

    leg_left_toe_ik_obj = pmxstruct.PmxBone(
        leg_left_toe_ik_name, "", leg_left_toe_pos, last_bone + 1, 0, False,
        True, True, True, True, True, False, [0, 0, 0], False, False, False,
        False, False, None, None, None, None, None, None, l_t_index, 3,
        229.1831, [[l_a_index, None, None]])
    retme.bones.insert(last_bone + 2, leg_left_toe_ik_obj)

    leg_right_ik_obj = pmxstruct.PmxBone(
        leg_right_ik_name, "", leg_right_ankle_pos, last_bone + 5, 0, False,
        True, True, True, True, True, False, [0.0, 0.0, 0.0], False, False,
        False, False, False, None, None, None, None, None, None, r_a_index, 40,
        114.5916,
        [[r_k_index, knee_limit_1, knee_limit_2], [r_l_index, None, None]])
    retme.bones.insert(last_bone + 3, leg_right_ik_obj)

    leg_right_toe_ik_obj = pmxstruct.PmxBone(
        leg_right_toe_ik_name, "", leg_right_toe_pos, last_bone + 3, 0, False,
        True, True, True, True, True, False, [0, 0, 0], False, False, False,
        False, False, None, None, None, None, None, None, r_t_index, 3,
        229.1831, [[r_a_index, None, None]])
    retme.bones.insert(last_bone + 4, leg_right_toe_ik_obj)

    # # base part
    b4_pos = [0, 0, 0]

    # for some reasons, if we pass value from pelvis_pos to b3_pos, pelvis_pos will change as well?
    b3_pos = [-4.999999873689376e-06, 21, -0.533614993095398]
    b2_pos = b3_pos
    b1_pos = [-4.999999873689376e-06, 32, -0.533614993095398]
    #
    # # 全ての親, name_en, [0.0, 0.0, -0.4735046625137329], -1, 0, False,
    # # True, True, True, True,
    # # False, [0.0, 0.0, 0.0], False, False, None,
    # # None, False, None, False, None, None, False, None, False,
    # # None, None, None, None
    #
    # # base order: 上半身, 下半身, 腰 (b_1), グルーブ, センター, 全ての親
    # # the parents would be fixed later
    b4_obj = pmxstruct.PmxBone(b4_name, "", b4_pos, -1, 0, False, True, True,
                               True, True, False, False, [0, 0, 0], False,
                               False, None, None, False, None, None, None,
                               None, None, None, None, None, None, None)
    retme.bones.insert(last_bone + 5, b4_obj)

    b3_obj = pmxstruct.PmxBone(b3_name, "", b3_pos, last_bone + 5, 0, False,
                               True, True, True, True, False, False, [0, 0, 0],
                               False, False, None, None, False, None, None,
                               None, None, None, None, None, None, None, None)
    retme.bones.insert(last_bone + 6, b3_obj)

    b2_obj = pmxstruct.PmxBone(b2_name, "", b2_pos, last_bone + 6, 0, False,
                               True, True, True, True, False, False, [0, 0, 0],
                               False, False, None, None, False, None, None,
                               None, None, None, None, None, None, None, None)
    retme.bones.insert(last_bone + 7, b2_obj)

    b1_obj = pmxstruct.PmxBone(b1_name, "", b1_pos, last_bone + 7, 0, False,
                               True, False, True, True, False, False,
                               [0, 0, 0], False, False, None, None, False,
                               None, None, None, None, None, None, None, None,
                               None, None)
    retme.bones.insert(last_bone + 8, b1_obj)

    output_filename_pmx = input_filename_pmx[0:-4] + "_sourcetrans.pmx"
    pmxlib.write_pmx(output_filename_pmx, retme, moreinfo=moreinfo)
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)
    # prompt VMD file name
    core.MY_PRINT_FUNC("")
    core.MY_PRINT_FUNC(
        "Please enter name of VMD motion or VPD pose file to check compatability with:"
    )
    input_filename = core.MY_FILEPROMPT_FUNC(".vmd .vpd")
    if not input_filename.lower().endswith(".vpd"):
        # the actual VMD part isn't even used, only bonedict and morphdict
        vmd = vmdlib.read_vmd(input_filename, moreinfo=moreinfo)
    else:
        vmd = vpdlib.read_vpd(input_filename, moreinfo=moreinfo)
    bonedict = vmdlib.parse_vmd_used_dict(vmd.boneframes,
                                          frametype="bone",
                                          moreinfo=moreinfo)
    morphdict = vmdlib.parse_vmd_used_dict(vmd.morphframes,
                                           frametype="morph",
                                           moreinfo=moreinfo)

    core.MY_PRINT_FUNC("")

    # must use same encoding as I used when the VMD was unpacked, since the hex bytes only have meaning in that encoding
    core.set_encoding("shift_jis")

    ##############################################
    # check morph compatability

    # build list of morphs used in the dance VMD
    morphs_in_vmd = list(morphdict.keys())

    # build list of ALL morphs in the PMX
    morphs_in_model = [pmxmorph.name_jp for pmxmorph in pmx.morphs]

    # ensure that the VMD contains at least some morphs, to prevent zero-divide error
    if len(morphs_in_vmd) == 0:
        core.MY_PRINT_FUNC(
            "MORPH SKIP: VMD '%s' does not contain any morphs that are used in a meaningful way."
            % core.get_clean_basename(input_filename))
    elif len(morphs_in_model) == 0:
        core.MY_PRINT_FUNC(
            "MORPH SKIP: PMX '%s' does not contain any morphs." %
            core.get_clean_basename(input_filename_pmx))
    else:

        # convert pmx-morph names to bytes
        # these can plausibly fail shift_jis encoding because they came from the UTF-8 pmx file
        morphs_in_model_b = []
        for a in morphs_in_model:
            try:
                b = core.encode_string_with_escape(a)
            except UnicodeEncodeError as e:
                newerrstr = "%s: '%s' codec cannot encode char '%s' within string '%s'" % (
                    e.__class__.__name__, e.encoding, e.reason[e.start:e.end],
                    e.reason)
                core.MY_PRINT_FUNC(newerrstr)
                b = bytearray()
            morphs_in_model_b.append(b)

        # convert vmd-morph names to bytes
        # these might be truncated but cannot fail because they were already decoded from the shift_jis vmd file
        morphs_in_vmd_b = [
            core.encode_string_with_escape(a) for a in morphs_in_vmd
        ]

        matching_morphs = {}
        missing_morphs = {}
        # iterate over list of morphs
        for vmdmorph, vmdmorph_b in zip(morphs_in_vmd, morphs_in_vmd_b):
            # question: does "vmdmorph" match something in "morphs_in_model"?
            # BUT, doing comparison in bytes-space to handle escape characters: vmdmorph_b vs morphs_in_model_b
            # NOTE: MMD does not try to use "begins-with" matching like I had hoped/assumed, it only looks for exact matches
            # return list of ALL matches, this way i can raise an error if there are multiple matches
            # exact match
            modelmorphmatch_b = [
                a for a in morphs_in_model_b if a == vmdmorph_b
            ]

            # copy the key,val in one of the dicts depending on results of matching attempt
            if len(modelmorphmatch_b) == 0:
                # MISS! key is the VMD morph name since that's the best clue Ive got
                missing_morphs[vmdmorph] = morphdict[vmdmorph]
            elif len(modelmorphmatch_b) == 1:
                # MATCH! key is the PMX morph name it matched against, since it might be a longer version wtihout escape char
                matching_morphs[core.decode_bytes_with_escape(
                    modelmorphmatch_b[0])] = morphdict[vmdmorph]
            else:
                # more than 1 morph was a match!?
                core.MY_PRINT_FUNC(
                    "Warning: VMDmorph '%s' matched multiple PMXmorphs, its behavior is uncertain."
                    % vmdmorph)
                modelmorphmatch = [
                    core.decode_bytes_with_escape(a) for a in modelmorphmatch_b
                ]
                # core.MY_PRINT_FUNC(modelmorphmatch)
                matching_morphs[modelmorphmatch[0]] = morphdict[vmdmorph]

        # display results!
        r = "PASS" if len(matching_morphs) == len(morphs_in_vmd) else "FAIL"
        core.MY_PRINT_FUNC(
            "MORPH {}: {} / {} = {:.1%} of the morphs are supported".format(
                r, len(matching_morphs), len(morphs_in_vmd),
                len(matching_morphs) / len(morphs_in_vmd)))

        # if there are no missing morphs (all match), don't print anything at all
        if missing_morphs:
            if not moreinfo:
                core.MY_PRINT_FUNC(
                    "For detailed list, please re-run with 'more info' enabled"
                )
            else:
                # convert the dicts to lists and sort for printing
                # sort in-place descending by 2nd element as primary
                missing_morphs_list = sorted(list(missing_morphs.items()),
                                             key=core.get2nd,
                                             reverse=True)
                # justify the names!
                missing_just = core.MY_JUSTIFY_STRINGLIST(
                    ["'" + m[0] + "'" for m in missing_morphs_list])
                # re-attach the justified names to the usage numbers
                missing_morphs_list = list(
                    zip(missing_just, [m[1] for m in missing_morphs_list]))

                core.MY_PRINT_FUNC("")
                core.MY_PRINT_FUNC("Unsupported morphs: name + times used")
                for m, num in missing_morphs_list:
                    core.MY_PRINT_FUNC("  %s  ||  %d" % (m, int(num)))

                # only print the matching morphs if there are some, and if enabled by options
                if matching_morphs and PRINT_MATCHING_ITEMS:
                    matching_morphs_list = list(matching_morphs.items())
                    matching_morphs_list.sort(
                        key=core.get2nd, reverse=True
                    )  # sort in-place descending by 2nd element as primary
                    matching_just = core.MY_JUSTIFY_STRINGLIST(
                        ["'" + m[0] + "'" for m in matching_morphs_list])
                    matching_morphs_list = list(
                        zip(matching_just,
                            [m[1] for m in matching_morphs_list]))
                    core.MY_PRINT_FUNC("")
                    core.MY_PRINT_FUNC("Supported morphs: name + times used")
                    for m, num in matching_morphs_list:
                        core.MY_PRINT_FUNC("  %s  ||  %d" % (m, int(num)))

    ##############################################
    # check bone compatability
    core.MY_PRINT_FUNC("")

    # build list of bones used in the dance VMD
    bones_in_vmd = list(bonedict.keys())

    # build list of ALL bones in the PMX
    # first item of pmxbone is the jp name
    bones_in_model = [pmxbone.name_jp for pmxbone in pmx.bones]

    # ensure that the VMD contains at least some bones, to prevent zero-divide error
    if len(bones_in_vmd) == 0:
        core.MY_PRINT_FUNC(
            "BONE SKIP: VMD '%s' does not contain any bones that are used in a meaningful way."
            % core.get_clean_basename(input_filename))
    elif len(bones_in_model) == 0:
        core.MY_PRINT_FUNC("BONE SKIP: PMX '%s' does not contain any bones." %
                           core.get_clean_basename(input_filename_pmx))
    else:

        # convert pmx-bone names to bytes
        # these can plausibly fail shift_jis encoding because they came from the UTF-8 pmx file
        bones_in_model_b = []
        for a in bones_in_model:
            try:
                b = core.encode_string_with_escape(a)
            except UnicodeEncodeError as e:
                newerrstr = "%s: '%s' codec cannot encode char '%s' within string '%s'" % (
                    e.__class__.__name__, e.encoding, e.reason[e.start:e.end],
                    e.reason)
                core.MY_PRINT_FUNC(newerrstr)
                b = bytearray()
            bones_in_model_b.append(b)

        # convert vmd-bone names to bytes
        # these might be truncated but cannot fail because they were already decoded from the shift_jis vmd file
        bones_in_vmd_b = [
            core.encode_string_with_escape(a) for a in bones_in_vmd
        ]

        matching_bones = {}
        missing_bones = {}
        # iterate over list of bones that pass the size check
        for vmdbone, vmdbone_b in zip(bones_in_vmd, bones_in_vmd_b):
            # question: does "vmdbone" match something in "bones_in_model"?
            # BUT, doing comparison in bytes-space to handle escape characters: vmdbone_b vs bones_in_model_b
            # NOTE: MMD does not try to use "begins-with" matching like I had hoped/assumed, it only looks for exact matches
            # return list of ALL matches, this way i can raise an error if there are multiple matches
            # exact match
            modelbonematch_b = [a for a in bones_in_model_b if a == vmdbone_b]

            # copy the key,val in one of the dicts depending on results of matching attempt
            if len(modelbonematch_b) == 0:
                # MISS! key is the VMD bone name since that's the best clue Ive got
                missing_bones[vmdbone] = bonedict[vmdbone]
            elif len(modelbonematch_b) == 1:
                # MATCH! key is the PMX bone name it matched against, since it might be a longer version wtihout escape char
                matching_bones[core.decode_bytes_with_escape(
                    modelbonematch_b[0])] = bonedict[vmdbone]
            else:
                # more than 1 bone was a match!?
                core.MY_PRINT_FUNC(
                    "Warning: VMDbone '%s' matched multiple PMXbones, its behavior is uncertain."
                    % vmdbone)
                modelbonematch = [
                    core.decode_bytes_with_escape(a) for a in modelbonematch_b
                ]
                # core.MY_PRINT_FUNC(modelbonematch)
                matching_bones[modelbonematch[0]] = bonedict[vmdbone]

        # display results!
        r = "PASS" if len(matching_bones) == len(bones_in_vmd) else "FAIL"
        core.MY_PRINT_FUNC(
            "BONE {}: {} / {} = {:.1%} of the bones are supported".format(
                r, len(matching_bones), len(bones_in_vmd),
                len(matching_bones) / len(bones_in_vmd)))

        # if there are no missing bones (all match), don't print anything at all
        if missing_bones:
            if not moreinfo:
                core.MY_PRINT_FUNC(
                    "For detailed list, please re-run with 'more info' enabled"
                )
            else:
                # convert the dicts to lists and sort for printing
                # sort in-place descending by 2nd element as primary
                missing_bones_list = sorted(list(missing_bones.items()),
                                            key=core.get2nd,
                                            reverse=True)
                # justify the names!
                missing_just = core.MY_JUSTIFY_STRINGLIST(
                    ["'" + m[0] + "'" for m in missing_bones_list])
                # re-attach the justified names to the usage numbers
                missing_bones_list = list(
                    zip(missing_just, [m[1] for m in missing_bones_list]))

                core.MY_PRINT_FUNC("")
                core.MY_PRINT_FUNC("Unsupported bones: name + times used")
                for m, num in missing_bones_list:
                    core.MY_PRINT_FUNC("  %s  ||  %d" % (m, int(num)))

                # only print the matching bones if there are some, and if enabled by options
                if matching_bones and PRINT_MATCHING_ITEMS:
                    matching_bones_list = list(matching_bones.items())
                    matching_bones_list.sort(
                        key=core.get2nd, reverse=True
                    )  # sort in-place descending by 2nd element as primary
                    matching_just = core.MY_JUSTIFY_STRINGLIST(
                        ["'" + m[0] + "'" for m in matching_bones_list])
                    matching_bones_list = list(
                        zip(matching_just,
                            [m[1] for m in matching_bones_list]))
                    core.MY_PRINT_FUNC("")
                    core.MY_PRINT_FUNC("Supported bones: name + times used")
                    for m, num in matching_bones_list:
                        core.MY_PRINT_FUNC("  %s  ||  %d" % (m, int(num)))

        # NEW: among matching bones, check whether any bones have unsupported translation/rotation
        for bonestr in sorted(list(matching_bones.keys())):
            # get the bone to get whether rot/trans enabled
            bone = core.my_list_search(pmx.bones,
                                       lambda x: x.name_jp == bonestr,
                                       getitem=True)
            # get all the frames from the VMD that are relevant to this bone
            thisboneframes = [f for f in vmd.boneframes if f.name == bonestr]
            # does the VMD use rotation? probably, check anyway
            vmd_use_rot = any(f.rot != [0, 0, 0] for f in thisboneframes)
            if vmd_use_rot and not (bone.has_rotate and bone.has_enabled):
                # raise some sort of warning
                w = "Warning: supported bone '%s' uses rotation in VMD, but rotation not allowed by PMX" % bonestr
                core.MY_PRINT_FUNC(w)
            # does the VMD use translation?
            vmd_use_trans = any(f.pos != [0, 0, 0] for f in thisboneframes)
            if vmd_use_trans and not (bone.has_translate and bone.has_enabled):
                # raise some sort of warning
                w = "Warning: supported bone '%s' uses move/shift in VMD, but move/shift not allowed by PMX" % bonestr
                core.MY_PRINT_FUNC(w)

    core.MY_PRINT_FUNC("")
    core.MY_PRINT_FUNC("Done!")
    return None
예제 #15
0
def main(moreinfo=False):
    core.MY_PRINT_FUNC("Please enter name of PMX model file:")
    input_filename_pmx = core.MY_FILEPROMPT_FUNC(".pmx")

    # texture sorting plan:
    # 1. get startpath = basepath of input PMX
    # 2. get lists of relevant files
    # 	2a. get list of ALL files within the tree, relative to startpath
    # 	2b. extract top-level 'neighbor' pmx files from all-set
    # 	2c. remove files i intend to ignore (filter by file ext or containing folder)
    # 3. ask about modifying neighbor PMX
    # 4. read PMX: either target or target+all neighbor
    # 5. "categorize files & normalize usages within PMX", NEW FUNC!!!
    # 	inputs: list of PMX obj, list of relevant files
    # 	outputs: list of structs that bundle all relevant info about the file (replace 2 structs currently used)
    # 	for each pmx, for each file on disk, match against files used in textures (case-insensitive) and replace with canonical name-on-disk
    # now have all files, know their states!
    # 6. ask for "aggression level" to control how files will be moved
    # 7. determine new names for files
    # 	this is the big one, slightly different logic for different categories
    # 8. print proposed names & other findings
    # 	for unused files under a folder, combine & replace with ***
    # 9. ask for confirmation
    # 10. zip backup (NEW FUNC!)
    # 11. apply renaming, NEW FUNC!
    # 	first try to rename all files
    # 		could plausibly fail, if so, set to-name to None/blank
    # 	then, in the PMXs, rename all files that didn't fail

    # absolute path to directory holding the pmx
    input_filename_pmx_abs = os.path.normpath(
        os.path.abspath(input_filename_pmx))
    startpath, input_filename_pmx_rel = os.path.split(input_filename_pmx_abs)

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # first, build the list of ALL files that actually exist, then filter it down to neighbor PMXs and relevant files
    relative_all_exist_files = walk_filetree_from_root(startpath)
    core.MY_PRINT_FUNC("ALL EXISTING FILES:", len(relative_all_exist_files))
    # now fill "neighbor_pmx" by finding files without path separator that end in PMX
    # these are relative paths tho
    neighbor_pmx = [
        f for f in relative_all_exist_files
        if (f.lower().endswith(".pmx")) and (
            os.path.sep not in f) and f != input_filename_pmx_rel
    ]

    relevant_exist_files = []
    for f in relative_all_exist_files:
        # ignore all files I expect to find alongside a PMX and don't want to touch or move
        if f.lower().endswith(IGNORE_FILETYPES): continue
        # ignore any files living below/inside 'special' folders like "fx/"
        if match_folder_anylevel(f, IGNORE_FOLDERS, toponly=False): continue
        # create the list of files we know exist and we know we care about
        relevant_exist_files.append(f)

    core.MY_PRINT_FUNC("RELEVANT EXISTING FILES:", len(relevant_exist_files))

    core.MY_PRINT_FUNC("NEIGHBOR PMX FILES:", len(neighbor_pmx))

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # now ask if I care about the neighbors and read the PMXes into memory

    pmx_filenames = [input_filename_pmx_rel]

    if neighbor_pmx:
        core.MY_PRINT_FUNC("")
        info = [
            "Detected %d top-level neighboring PMX files, these probably share the same filebase as the target."
            % len(neighbor_pmx),
            "If files are moved/renamed but the neighbors are not processed, the neighbor texture references will probably break.",
            "Do you want to process all neighbors in addition to the target? (highly recommended)",
            "1 = Yes, 2 = No"
        ]
        r = core.MY_SIMPLECHOICE_FUNC((1, 2), info)
        if r == 1:
            core.MY_PRINT_FUNC("Processing target + all neighbor files")
            # append neighbor PMX files onto the list of files to be processed
            pmx_filenames += neighbor_pmx
        else:
            core.MY_PRINT_FUNC(
                "WARNING: Processing only target, ignoring %d neighbor PMX files"
                % len(neighbor_pmx))
    # now read all the PMX objects & store in dict alongside the relative name
    # dictionary where keys are filename and values are resulting pmx objects
    all_pmx_obj = {}
    for this_pmx_name in pmx_filenames:
        this_pmx_obj = pmxlib.read_pmx(os.path.join(startpath, this_pmx_name),
                                       moreinfo=moreinfo)
        all_pmx_obj[this_pmx_name] = this_pmx_obj

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # 	for each pmx, for each file on disk, match against files used in textures (case-insensitive) and replace with canonical name-on-disk
    #	also fill out how much and how each file is used, and unify dupes between files, all that good stuff

    filerecord_list = categorize_files(all_pmx_obj, relevant_exist_files,
                                       moreinfo)

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # now check which files are used/unused/dont exist

    # break this into used/notused/notexist lists for simplicity sake
    # all -> used + notused
    # used -> used_exist + used_notexist
    # notused -> notused_img + notused_notimg
    used, notused = core.my_list_partition(filerecord_list,
                                           lambda q: q.numused != 0)
    used_exist, used_notexist = core.my_list_partition(used,
                                                       lambda q: q.exists)
    notused_img, notused_notimg = core.my_list_partition(
        notused, lambda q: q.name.lower().endswith(IMG_EXT))

    core.MY_PRINT_FUNC("PMX TEXTURE SOURCES:", len(used))
    if moreinfo:
        for x in used:
            core.MY_PRINT_FUNC("  " + str(x))

    # now:
    # all duplicates have been resolved within PMX, including modifying the PMX
    # all duplicates have been resolved across PMXes
    # all file exist/notexist status is known
    # all file used/notused status is known (via numused), or used_pmx
    # all ways a file is used is known

    move_toplevel_unused_img = True
    move_all_unused_img = False
    # only ask what files to move if there are files that could potentially be moved
    if notused_img:
        # count the number of toplevel vs not-toplevel in "notused_img"
        num_toplevel = len(
            [p for p in notused_img if (os.path.sep not in p.name)])
        num_nontoplevel = len(notused_img) - num_toplevel
        # ask the user what "aggression" level they want
        showinfo = [
            "Detected %d unused top-level files and %d unused files in directories."
            % (num_toplevel, num_nontoplevel),
            "Which files do you want to move to 'unused' folder?",
            "1 = Do not move any, 2 = Move only top-level unused, 3 = Move all unused"
        ]
        c = core.MY_SIMPLECHOICE_FUNC((1, 2, 3), showinfo)
        if c == 2:
            move_toplevel_unused_img = True
            move_all_unused_img = False
        elif c == 3:
            move_toplevel_unused_img = True
            move_all_unused_img = True
        else:  # c == 1:
            move_toplevel_unused_img = False
            move_all_unused_img = False

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # DETERMINE NEW NAMES FOR FILES

    # how to remap: build a list of all destinations (lowercase) to see if any proposed change would lead to collision
    all_new_names = set()

    # don't touch the unused_notimg files at all, unless some flag is set

    # not-used top-level image files get moved to 'unused' folder
    # also all spa/sph get renamed to .bmp (but remember these are all unused so i don't need to update them in the pmx)
    for p in notused_img:
        newname = remove_pattern(p.name)
        if ((os.path.sep not in p.name)
                and move_toplevel_unused_img) or move_all_unused_img:
            # this deserves to be moved to 'unused' folder!
            newname = os.path.join(FOLDER_UNUSED, os.path.basename(newname))

        # ensure the extension is lowercase, for cleanliness
        dot = newname.rfind(".")
        newname = newname[:dot] + newname[dot:].lower()
        if CONVERT_SPA_SPH_TO_BMP and newname.endswith((".spa", ".sph")):
            newname = newname[:-4] + ".bmp"
        # if the name I build is not the name it already has, queue it for actual rename
        if newname != p.name:
            # resolve potential collisions by adding numbers suffix to file names
            # first need to make path absolute so get_unused_file_name can check the disk.
            # then check uniqueness against files on disk and files in namelist (files that WILL be on disk)
            newname = core.get_unused_file_name(os.path.join(
                startpath, newname),
                                                namelist=all_new_names)
            # now dest path is guaranteed unique against other existing files & other proposed name changes
            all_new_names.add(newname.lower())
            # make the path no longer absolute: undo adding "startpath" above
            newname = os.path.relpath(newname, startpath)
            p.newname = newname

    # used files get sorted into tex/toon/sph/multi (unless tex and already in a folder that says clothes, etc)
    # all SPH/SPA get renamed to BMP, used or unused
    for p in used_exist:
        newname = remove_pattern(p.name)
        usage_list = list(p.usage)
        if len(p.usage) != 1:
            # this is a rare multiple-use file
            newname = os.path.join(FOLDER_MULTI, os.path.basename(newname))
        elif usage_list[0] == FOLDER_SPH:
            # this is an sph, duh
            if not match_folder_anylevel(
                    p.name, KEEP_FOLDERS_SPH, toponly=True):
                # if its name isn't already good, then move it to my new location
                newname = os.path.join(FOLDER_SPH, os.path.basename(newname))
        elif usage_list[0] == FOLDER_TOON:
            # this is a toon, duh
            if not match_folder_anylevel(
                    p.name, KEEP_FOLDERS_TOON, toponly=True):
                # if its name isn't already good, then move it to my new location
                newname = os.path.join(FOLDER_TOON, os.path.basename(newname))
        elif usage_list[0] == FOLDER_TEX:
            # if a tex AND already in a folder like body, clothes, wear, tex, etc then keep that folder
            if not match_folder_anylevel(
                    p.name, KEEP_FOLDERS_TEX, toponly=True):
                # if its name isn't already good, then move it to my new location
                newname = os.path.join(FOLDER_TEX, os.path.basename(newname))

        # ensure the extension is lowercase, for cleanliness
        dot = newname.rfind(".")
        newname = newname[:dot] + newname[dot:].lower()
        if CONVERT_SPA_SPH_TO_BMP and newname.lower().endswith(
            (".spa", ".sph")):
            newname = newname[:-4] + ".bmp"
        # if the name I build is not the name it already has, queue it for actual rename
        if newname != p.name:
            # resolve potential collisions by adding numbers suffix to file names
            # first need to make path absolute so get_unused_file_name can check the disk.
            # then check uniqueness against files on disk and files in namelist (files that WILL be on disk)
            newname = core.get_unused_file_name(os.path.join(
                startpath, newname),
                                                namelist=all_new_names)
            # now dest path is guaranteed unique against other existing files & other proposed name changes
            all_new_names.add(newname.lower())
            # make the path no longer absolute: undo adding "startpath" above
            newname = os.path.relpath(newname, startpath)
            p.newname = newname

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # NOW PRINT MY PROPOSED RENAMINGS and other findings

    # isolate the ones with proposed renaming
    used_rename = [u for u in used_exist if u.newname is not None]
    notused_img_rename = [u for u in notused_img if u.newname is not None]
    notused_img_norename = [u for u in notused_img if u.newname is None]

    # bonus goal: if ALL files under a folder are unused, replace its name with a star
    # first build dict of each dirs to each file any depth below that dir
    all_dirnames = {}
    for f in relative_all_exist_files:
        d = os.path.dirname(f)
        while d != "":
            try:
                all_dirnames[d].append(f)
            except KeyError:
                all_dirnames[d] = [f]
            d = os.path.dirname(d)
    unused_dirnames = []
    all_notused_searchable = [x.name for x in notused_img_norename
                              ] + [x.name for x in notused_notimg]
    for d, files_under_d in all_dirnames.items():
        # if all files beginning with d are notused (either type), this dir can be replaced with *
        # note: min crashes if input list is empty, but this is guaranteed to not be empty
        dir_notused = min([(f in all_notused_searchable)
                           for f in files_under_d])
        if dir_notused:
            unused_dirnames.append(d)
    # print("allundir", unused_dirnames)
    # now, remove all dirnames that are encompassed by another dirname
    j = 0
    while j < len(unused_dirnames):
        dj = unused_dirnames[j]
        k = 0
        while k < len(unused_dirnames):
            dk = unused_dirnames[k]
            if dj != dk and dk.startswith(dj):
                unused_dirnames.pop(k)
            else:
                k += 1
        j += 1
    # make sure unused_dirnames has the deepest directories first
    unused_dirnames = sorted(unused_dirnames,
                             key=lambda y: y.count(os.path.sep),
                             reverse=True)
    # print("unqundir", unused_dirnames)
    # then as I go to print notused_img_norename or notused_notimg, collapse them?

    # for each section, if it exists, print its names sorted first by directory depth then alphabetically (case insensitive)

    if used_notexist:
        core.MY_PRINT_FUNC("=" * 60)
        core.MY_PRINT_FUNC(
            "Found %d references to images that don't exist (no proposed changes)"
            % len(used_notexist))
        for p in sorted(used_notexist, key=lambda y: sortbydirdepth(y.name)):
            # print orig name, usage modes, # used, and # files that use it
            core.MY_PRINT_FUNC("   " + str(p))
    if notused_img_norename:
        core.MY_PRINT_FUNC("=" * 60)
        core.MY_PRINT_FUNC(
            "Found %d not-used images in the file tree (no proposed changes)" %
            len(notused_img_norename))
        printme = set()
        for p in notused_img_norename:
            # is this notused-file anywhere below any unused dir?
            t = False
            for d in unused_dirnames:
                if p.name.startswith(d):
                    # add this dir, not this file, to the print set
                    printme.add(os.path.join(d, "***"))
                    t = True
            if not t:
                # if not encompassed by an unused dir, add the filename
                printme.add(p.name)
        # convert set back to sorted list
        printme = sorted(list(printme), key=sortbydirdepth)
        for s in printme:
            core.MY_PRINT_FUNC("   " + s)
    if notused_notimg:
        core.MY_PRINT_FUNC("=" * 60)
        core.MY_PRINT_FUNC(
            "Found %d not-used not-images in the file tree (no proposed changes)"
            % len(notused_notimg))
        printme = set()
        for p in notused_notimg:
            # is this notused-file anywhere below any unused dir?
            t = False
            for d in unused_dirnames:
                if p.name.startswith(d):
                    # add this dir, not this file, to the print set
                    printme.add(os.path.join(d, "***"))
                    t = True
            if not t:
                # if not encompassed by an unused dir, add the filename
                printme.add(p.name)
        # convert set back to sorted list
        printme = sorted(list(printme), key=sortbydirdepth)
        for s in printme:
            core.MY_PRINT_FUNC("   " + s)
    # print with all "from" file names left-justified so all the arrows are nicely lined up (unless they use jp characters)
    longest_name_len = 0
    for p in used_rename:
        longest_name_len = max(longest_name_len, len(p.name))
    for p in notused_img_rename:
        longest_name_len = max(longest_name_len, len(p.name))
    if used_rename:
        core.MY_PRINT_FUNC("=" * 60)
        core.MY_PRINT_FUNC("Found %d used files to be moved/renamed:" %
                           len(used_rename))
        oldname_list = core.MY_JUSTIFY_STRINGLIST(
            [p.name for p in used_rename])
        newname_list = [p.newname for p in used_rename]
        zipped = list(zip(oldname_list, newname_list))
        zipped_and_sorted = sorted(zipped, key=lambda y: sortbydirdepth(y[0]))
        for o, n in zipped_and_sorted:
            # print 'from' with the case/separator it uses in the PMX
            core.MY_PRINT_FUNC("   {:s} --> {:s}".format(o, n))
    if notused_img_rename:
        core.MY_PRINT_FUNC("=" * 60)
        core.MY_PRINT_FUNC("Found %d not-used images to be moved/renamed:" %
                           len(notused_img_rename))
        oldname_list = core.MY_JUSTIFY_STRINGLIST(
            [p.name for p in notused_img_rename])
        newname_list = [p.newname for p in notused_img_rename]
        zipped = list(zip(oldname_list, newname_list))
        zipped_and_sorted = sorted(zipped, key=lambda y: sortbydirdepth(y[0]))
        for o, n in zipped_and_sorted:
            # print 'from' with the case/separator it uses in the PMX
            core.MY_PRINT_FUNC("   {:s} --> {:s}".format(o, n))
    core.MY_PRINT_FUNC("=" * 60)

    if not (used_rename or notused_img_rename):
        core.MY_PRINT_FUNC("No proposed file changes")
        core.MY_PRINT_FUNC("Aborting: no files were changed")
        return None

    info = [
        "Do you accept these new names/locations?", "1 = Yes, 2 = No (abort)"
    ]
    r = core.MY_SIMPLECHOICE_FUNC((1, 2), info)
    if r == 2:
        core.MY_PRINT_FUNC("Aborting: no files were changed")
        return None

    # =========================================================================================================
    # =========================================================================================================
    # =========================================================================================================
    # finally, do the actual renaming:

    # first, create a backup of the folder
    if MAKE_BACKUP_BEFORE_RENAMES:
        r = make_zipfile_backup(startpath, BACKUP_SUFFIX)
        if not r:
            # this happens if the backup failed somehow AND the user decided to quit
            core.MY_PRINT_FUNC("Aborting: no files were changed")
            return None

    # do all renaming on disk and in PMXes, and also handle the print statements
    apply_file_renaming(all_pmx_obj, filerecord_list, startpath)

    # write out
    for this_pmx_name, this_pmx_obj in all_pmx_obj.items():
        # NOTE: this is OVERWRITING THE PREVIOUS PMX FILE, NOT CREATING A NEW ONE
        # because I make a zipfile backup I don't need to feel worried about preserving the old version
        output_filename_pmx = os.path.join(startpath, this_pmx_name)
        # output_filename_pmx = core.get_unused_file_name(output_filename_pmx)
        pmxlib.write_pmx(output_filename_pmx, this_pmx_obj, moreinfo=moreinfo)

    core.MY_PRINT_FUNC("Done!")
    return None
예제 #16
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)

    core.MY_PRINT_FUNC("")
    # valid input is any string that can matched aginst a morph idx
    s = core.MY_GENERAL_INPUT_FUNC(
        lambda x: morph_scale.get_idx_in_pmxsublist(x, pmx.morphs) is not None,
        [
            "Please specify the target morph: morph #, JP name, or EN name (names are not case sensitive).",
            "Empty input will quit the script."
        ])
    # do it again, cuz the lambda only returns true/false
    target_index = morph_scale.get_idx_in_pmxsublist(s, pmx.morphs)

    # when given empty text, done!
    if target_index == -1 or target_index is None:
        core.MY_PRINT_FUNC("quitting")
        return None

    morphtype = pmx.morphs[target_index].morphtype
    # 1=vert
    # 3=UV
    # 8=material
    core.MY_PRINT_FUNC("Found {} morph #{}: '{}' / '{}'".format(
        morphtype, target_index, pmx.morphs[target_index].name_jp,
        pmx.morphs[target_index].name_en))

    if morphtype == pmxstruct.MorphType.VERTEX:  # vertex
        # for each item in this morph:
        item: pmxstruct.PmxMorphItemVertex  # type annotation for pycharm
        for d, item in enumerate(pmx.morphs[target_index].items):
            # apply the offset
            pmx.verts[item.vert_idx].pos[0] += item.move[0]
            pmx.verts[item.vert_idx].pos[1] += item.move[1]
            pmx.verts[item.vert_idx].pos[2] += item.move[2]
            # invert the morph
        morph_scale.morph_scale(pmx.morphs[target_index], -1)
    elif morphtype == pmxstruct.MorphType.UV:  # UV
        item: pmxstruct.PmxMorphItemUV  # type annotation for pycharm
        for d, item in enumerate(pmx.morphs[target_index].items):
            # (vert_idx, A, B, C, D)
            # apply the offset
            pmx.verts[item.vert_idx].uv[0] += item.move[0]
            pmx.verts[item.vert_idx].uv[1] += item.move[1]
            # invert the morph
        morph_scale.morph_scale(pmx.morphs[target_index], -1)
    elif morphtype in (pmxstruct.MorphType.UV_EXT1,
                       pmxstruct.MorphType.UV_EXT2,
                       pmxstruct.MorphType.UV_EXT3,
                       pmxstruct.MorphType.UV_EXT4):  # UV1 UV2 UV3 UV4
        whichuv = morphtype.value - pmxstruct.MorphType.UV_EXT1.value
        item: pmxstruct.PmxMorphItemUV  # type annotation for pycharm
        for d, item in enumerate(pmx.morphs[target_index].items):
            # apply the offset
            pmx.verts[item.vert_idx].addl_vec4s[whichuv][0] += item.move[0]
            pmx.verts[item.vert_idx].addl_vec4s[whichuv][1] += item.move[1]
            pmx.verts[item.vert_idx].addl_vec4s[whichuv][2] += item.move[2]
            pmx.verts[item.vert_idx].addl_vec4s[whichuv][3] += item.move[3]
            # invert the morph
        morph_scale.morph_scale(pmx.morphs[target_index], -1)
    elif morphtype == pmxstruct.MorphType.MATERIAL:  # material
        core.MY_PRINT_FUNC("WIP")
        # todo
        # to invert a material morph means inverting the material's visible/notvisible state as well as flipping the morph
        # hide morph add -> show morph add
        # hide morph mult -> show morph add
        # show morph add -> hide morph mult
        core.MY_PRINT_FUNC("quitting")
        return None
    else:
        core.MY_PRINT_FUNC("Unhandled morph type")
        core.MY_PRINT_FUNC("quitting")
        return None

    # write out
    output_filename_pmx = input_filename_pmx[0:-4] + ("_%dinv.pmx" %
                                                      target_index)
    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
def main(moreinfo=True):
	# the goal: extract rotation around the "arm" bone local X? axis and transfer it to rotation around the "armtwist" bone local axis
	
	# 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)
	core.MY_PRINT_FUNC("")
	# get bones
	realbones = pmx.bones
	
	twistbone_axes = []
	# then, grab the "twist" bones & save their fixed-rotate axes, if they have them
	# fallback plan: find the arm-to-elbow and elbow-to-wrist unit vectors and use those
	for i in range(len(jp_twistbones)):
		r = core.my_list_search(realbones, lambda x: x.name_jp == jp_twistbones[i], getitem=True)
		if r is None:
			core.MY_PRINT_FUNC("ERROR1: twist bone '{}'({}) cannot be found model, unable to continue. Ensure they use the correct semistandard names, or edit the script to change the JP names it is looking for.".format(jp_twistbones[i], eng_twistbones[i]))
			raise RuntimeError()
		if r.has_fixedaxis:
			# this bone DOES have fixed-axis enabled! use the unit vector in r[18]
			twistbone_axes.append(r.fixedaxis)
		else:
			# i can infer local axis by angle from arm-to-elbow or elbow-to-wrist
			start = core.my_list_search(realbones, lambda x: x.name_jp == jp_sourcebones[i], getitem=True)
			if start is None:
				core.MY_PRINT_FUNC("ERROR2: semistandard bone '%s' is missing from the model, unable to infer axis of rotation" % jp_sourcebones[i])
				raise RuntimeError()
			end = core.my_list_search(realbones, lambda x: x.name_jp == jp_pointat_bones[i], getitem=True)
			if end is None:
				core.MY_PRINT_FUNC("ERROR3: semistandard bone '%s' is missing from the model, unable to infer axis of rotation" % jp_pointat_bones[i])
				raise RuntimeError()
			start_pos = start.pos
			end_pos = end.pos
			# now have both startpoint and endpoint! find the delta!
			delta = [b - a for a,b in zip(start_pos, end_pos)]
			# normalize to length of 1
			length = core.my_euclidian_distance(delta)
			unit = [t / length for t in delta]
			twistbone_axes.append(unit)
	
	# done extracting axes limits from bone CSV, in list "twistbone_axes"
	core.MY_PRINT_FUNC("...done extracting axis limits from PMX...")
	
	
	###################################################################################
	# prompt VMD file name
	core.MY_PRINT_FUNC("Please enter name of VMD dance input file:")
	input_filename_vmd = core.MY_FILEPROMPT_FUNC(".vmd")
	
	# next, read/use/prune the dance vmd
	nicelist_in = vmdlib.read_vmd(input_filename_vmd, moreinfo=moreinfo)
	
	# sort boneframes into individual lists: one for each [Larm + Lelbow + Rarm + Relbow] and remove them from the master boneframelist
	# frames for all other bones stay in the master boneframelist
	all_sourcebone_frames = []
	for sourcebone in jp_sourcebones:
		# partition & writeback
		temp, nicelist_in.boneframes = core.my_list_partition(nicelist_in.boneframes, lambda x: x.name == sourcebone)
		# all frames for "sourcebone" get their own sublist here
		all_sourcebone_frames.append(temp)
	
	# verify that there is actually arm/elbow frames to process
	sourcenumframes = sum([len(x) for x in all_sourcebone_frames])
	if sourcenumframes == 0:
		core.MY_PRINT_FUNC("No arm/elbow bone frames are found in the VMD, nothing for me to do!")
		core.MY_PRINT_FUNC("Aborting: no files were changed")
		return None
	else:
		core.MY_PRINT_FUNC("...source contains " + str(sourcenumframes) + " arm/elbow bone frames to decompose...")
	
	if USE_OVERKEY_BANDAID:
		# to fix the path that the arms take during interpolation we need to overkey the frames
		# i.e. create intermediate frames that they should have been passing through already, to FORCE it to take the right path
		# i'm replacing the interpolation curves with actual frames
		for sublist in all_sourcebone_frames:
			newframelist = []
			sublist.sort(key=lambda x: x.f) # ensure they are sorted by frame number
			# for each frame
			for i in range(1, len(sublist)):
				this = sublist[i]
				prev = sublist[i-1]
				# use interpolation curve i to interpolate from i-1 to i
				# first: do i need to do anything or are they already close on the timeline?
				thisframenum = this.f
				prevframenum = prev.f
				if (thisframenum - prevframenum) <= OVERKEY_FRAME_SPACING:
					continue
				# if they are far enough apart that i need to do something,
				thisframequat = core.euler_to_quaternion(this.rot)
				prevframequat = core.euler_to_quaternion(prev.rot)
				# 3, 7, 11, 15 = r_ax, r_ay, r_bx, r_by
				bez = core.MyBezier((this.interp[3], this.interp[7]), (this.interp[11], this.interp[15]), resolution=50)
				# create new frames at these frame numbers, spacing is OVERKEY_FRAME_SPACING
				for interp_framenum in range(prevframenum + OVERKEY_FRAME_SPACING, thisframenum, OVERKEY_FRAME_SPACING):
					# calculate the x time percentage from prev frame to this frame
					x = (interp_framenum - prevframenum) / (thisframenum - prevframenum)
					# apply the interpolation curve to translate X to Y
					y = bez.approximate(x)
					# interpolate from prev to this by amount Y
					interp_quat = core.my_slerp(prevframequat, thisframequat, y)
					# begin building the new frame
					newframe = vmdstruct.VmdBoneFrame(
						name=this.name,  # same name
						f=interp_framenum,  # overwrite frame num
						pos=list(this.pos),  # same pos (but make a copy)
						rot=list(core.quaternion_to_euler(interp_quat)),  # overwrite euler angles
						phys_off=this.phys_off,  # same phys_off
						interp=list(core.bone_interpolation_default_linear)  # overwrite interpolation
					)
					newframelist.append(newframe)
				# overwrite thisframe interp curve with default too
				this.interp = list(core.bone_interpolation_default_linear) # overwrite custom interpolation
			# concat the new frames onto the existing frames for this sublist
			sublist += newframelist
			
	# re-count the number of frames for printing purposes
	totalnumframes = sum([len(x) for x in all_sourcebone_frames])
	overkeyframes = totalnumframes - sourcenumframes
	if overkeyframes != 0:
		core.MY_PRINT_FUNC("...overkeying added " + str(overkeyframes) + " arm/elbow bone frames...")
	core.MY_PRINT_FUNC("...beginning decomposition of " + str(totalnumframes) + " arm/elbow bone frames...")
	
	# now i am completely done reading the VMD file and parsing its data! everything has been distilled down to:
	# all_sourcebone_frames = [Larm, Lelbow, Rarm, Relbow] plus nicelist_in[1]
	
	###################################################################################
	# begin the actual calculations
	
	# output array
	new_twistbone_frames = []
	# progress tracker
	curr_progress = 0
	
	# for each sourcebone & corresponding twistbone,
	for (twistbone, axis_orig, sourcebone_frames) in zip(jp_twistbones, twistbone_axes, all_sourcebone_frames):
		# for each frame of the sourcebone,
		for frame in sourcebone_frames:
			# XYZrot = 567 euler
			quat_in = core.euler_to_quaternion(frame.rot)
			axis = list(axis_orig)	# make a copy to be safe
			
			# "swing twist decomposition"
			# swing = "local" x rotation and nothing else
			# swing = sourcebone, twist = twistbone
			(swing, twist) = swing_twist_decompose(quat_in, axis)
			
			# modify "frame" in-place
			# only modify the XYZrot to use new values
			new_sourcebone_euler = core.quaternion_to_euler(swing)
			frame.rot = list(new_sourcebone_euler)
			
			# create & store new twistbone frame
			# name=twistbone, framenum=copy, XYZpos=copy, XYZrot=new, phys=copy, interp16=copy
			new_twistbone_euler = core.quaternion_to_euler(twist)
			newframe = vmdstruct.VmdBoneFrame(
				name=twistbone,
				f=frame.f,
				pos=list(frame.pos),
				rot=list(new_twistbone_euler),
				phys_off=frame.phys_off,
				interp=list(frame.interp)
			)
			new_twistbone_frames.append(newframe)
			# print progress updates
			curr_progress += 1
			core.print_progress_oneline(curr_progress / totalnumframes)
	
	
	######################################################################
	# done with calculations!
	core.MY_PRINT_FUNC("...done with decomposition, now reassembling output...")
	# attach the list of newly created boneframes, modify the original input
	for sublist in all_sourcebone_frames:
		nicelist_in.boneframes += sublist
	nicelist_in.boneframes += new_twistbone_frames
	
	core.MY_PRINT_FUNC("")
	# write out the VMD
	output_filename_vmd = "%s_twistbones_for_%s.vmd" % \
						   (input_filename_vmd[0:-4], core.get_clean_basename(input_filename_pmx))
	output_filename_vmd = core.get_unused_file_name(output_filename_vmd)
	vmdlib.write_vmd(output_filename_vmd, nicelist_in, moreinfo=moreinfo)
	
	core.MY_PRINT_FUNC("Done!")
	return None
예제 #18
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)

    # detect whether arm ik exists
    r = core.my_list_search(pmx.bones, lambda x: x.name_jp == jp_r + jp_newik)
    if r is None:
        r = core.my_list_search(pmx.bones,
                                lambda x: x.name_jp == jp_r + jp_newik2)
    l = core.my_list_search(pmx.bones, lambda x: x.name_jp == jp_l + jp_newik)
    if l is None:
        l = core.my_list_search(pmx.bones,
                                lambda x: x.name_jp == jp_l + jp_newik2)

    # decide whether to create or remove arm ik
    if r is None and l is None:
        # add IK branch
        core.MY_PRINT_FUNC(">>>> Adding arm IK <<<")
        # set output name
        if input_filename_pmx.lower().endswith(pmx_noik_suffix.lower()):
            output_filename = input_filename_pmx[0:-(
                len(pmx_noik_suffix))] + pmx_yesik_suffix
        else:
            output_filename = input_filename_pmx[0:-4] + pmx_yesik_suffix
        for side in [jp_l, jp_r]:
            # first find all 3 arm bones
            # even if i insert into the list, this will still be a valid reference i think
            bones = []
            bones: List[pmxstruct.PmxBone]
            for n in [jp_arm, jp_elbow, jp_wrist]:
                i = core.my_list_search(pmx.bones,
                                        lambda x: x.name_jp == side + n,
                                        getitem=True)
                if i is None:
                    core.MY_PRINT_FUNC(
                        "ERROR1: semistandard bone '%s' is missing from the model, unable to create attached arm IK"
                        % (side + n))
                    raise RuntimeError()
                bones.append(i)
            # get parent of arm bone
            shoulder_idx = bones[0].parent_idx

            # then do the "remapping" on all existing bone references, to make space for inserting 4 bones
            # don't delete any bones, just remap them
            bone_shiftmap = ([shoulder_idx + 1], [-4])
            apply_bone_remapping(pmx, [], bone_shiftmap)
            # new bones will be inserted AFTER shoulder_idx
            # newarm_idx = shoulder_idx+1
            # newelbow_idx = shoulder_idx+2
            # newwrist_idx = shoulder_idx+3
            # newik_idx = shoulder_idx+4

            # make copies of the 3 armchain bones
            for i, b in enumerate(bones):
                b: pmxstruct.PmxBone

                # newarm = b[0:5] + [shoulder_idx + i] + b[6:8]  # copy names/pos, parent, copy deform layer
                # newarm += [1, 0, 0, 0]  # rotateable, not translateable, not visible, not enabled(?)
                # newarm += [1, [shoulder_idx + 2 + i], 0, 0, [], 0, []]  # tail type, no inherit, no fixed axis,
                # newarm += b[19:21] + [0, [], 0, []]  # copy local axis, no ext parent, no ik
                # newarm[0] += jp_ikchainsuffix  # add suffix to jp name
                # newarm[1] += jp_ikchainsuffix  # add suffix to en name
                newarm = pmxstruct.PmxBone(
                    name_jp=b.name_jp + jp_ikchainsuffix,
                    name_en=b.name_en + jp_ikchainsuffix,
                    pos=b.pos,
                    parent_idx=b.parent_idx,
                    deform_layer=b.deform_layer,
                    deform_after_phys=b.deform_after_phys,
                    has_rotate=True,
                    has_translate=False,
                    has_visible=False,
                    has_enabled=True,
                    tail_type=True,
                    tail=shoulder_idx + 2 + i,
                    inherit_rot=False,
                    inherit_trans=False,
                    has_fixedaxis=False,
                    has_localaxis=b.has_localaxis,
                    localaxis_x=b.localaxis_x,
                    localaxis_z=b.localaxis_z,
                    has_externalparent=False,
                    has_ik=False,
                )
                pmx.bones.insert(shoulder_idx + 1 + i, newarm)
                # then change the existing arm/elbow (not the wrist) to inherit rot from them
                if i != 2:
                    b.inherit_rot = True
                    b.inherit_parent_idx = shoulder_idx + 1 + i
                    b.inherit_ratio = 1

            # copy the wrist to make the IK bone
            en_suffix = "_L" if side == jp_l else "_R"
            # get index of "upperbody" to use as parent of hand IK bone
            ikpar = core.my_list_search(pmx.bones,
                                        lambda x: x.name_jp == jp_upperbody)
            if ikpar is None:
                core.MY_PRINT_FUNC(
                    "ERROR1: semistandard bone '%s' is missing from the model, unable to create attached arm IK"
                    % jp_upperbody)
                raise RuntimeError()

            # newik = [side + jp_newik, en_newik + en_suffix] + bones[2][2:5] + [ikpar]  # new names, copy pos, new par
            # newik += bones[2][6:8] + [1, 1, 1, 1]  + [0, [0,1,0]] # copy deform layer, rot/trans/vis/en, tail type
            # newik += [0, 0, [], 0, [], 0, [], 0, []]  # no inherit, no fixed axis, no local axis, no ext parent, yes IK
            # # add the ik info: [is_ik, [target, loops, anglelimit, [[link_idx, []]], [link_idx, []]]] ] ]
            # newik += [1, [shoulder_idx+3, newik_loops, newik_angle, [[shoulder_idx+2,[]],[shoulder_idx+1,[]]] ] ]
            newik = pmxstruct.PmxBone(
                name_jp=side + jp_newik,
                name_en=en_newik + en_suffix,
                pos=bones[2].pos,
                parent_idx=ikpar,
                deform_layer=bones[2].deform_layer,
                deform_after_phys=bones[2].deform_after_phys,
                has_rotate=True,
                has_translate=True,
                has_visible=True,
                has_enabled=True,
                tail_type=False,
                tail=[0, 1, 0],
                inherit_rot=False,
                inherit_trans=False,
                has_fixedaxis=False,
                has_localaxis=False,
                has_externalparent=False,
                has_ik=True,
                ik_target_idx=shoulder_idx + 3,
                ik_numloops=newik_loops,
                ik_angle=newik_angle,
                ik_links=[
                    pmxstruct.PmxBoneIkLink(idx=shoulder_idx + 2),
                    pmxstruct.PmxBoneIkLink(idx=shoulder_idx + 1)
                ])
            pmx.bones.insert(shoulder_idx + 4, newik)

            # then add to dispframe
            # first, does the frame already exist?
            f = core.my_list_search(pmx.frames,
                                    lambda x: x.name_jp == jp_newik,
                                    getitem=True)
            if f is None:
                # need to create the new dispframe! easy
                newframe = pmxstruct.PmxFrame(name_jp=jp_newik,
                                              name_en=en_newik,
                                              is_special=False,
                                              items=[[0, shoulder_idx + 4]])
                pmx.frames.append(newframe)
            else:
                # frame already exists, also easy
                f.items.append([0, shoulder_idx + 4])
    else:
        # remove IK branch
        core.MY_PRINT_FUNC(">>>> Removing arm IK <<<")
        # set output name
        if input_filename_pmx.lower().endswith(pmx_yesik_suffix.lower()):
            output_filename = input_filename_pmx[0:-(
                len(pmx_yesik_suffix))] + pmx_noik_suffix
        else:
            output_filename = input_filename_pmx[0:-4] + pmx_noik_suffix
        # identify all bones in ik chain of hand ik bones
        bone_dellist = []
        for b in [r, l]:
            bone_dellist.append(b)  # this IK bone
            bone_dellist.append(
                pmx.bones[b].ik_target_idx)  # the target of the bone
            for v in pmx.bones[b].ik_links:
                bone_dellist.append(v.idx)  # each link along the bone
        bone_dellist.sort()
        # build the remap thing
        bone_shiftmap = delme_list_to_rangemap(bone_dellist)
        # do the actual delete & shift
        apply_bone_remapping(pmx, bone_dellist, bone_shiftmap)

        # delete dispframe for hand ik
        # first, does the frame already exist?
        f = core.my_list_search(pmx.frames, lambda x: x.name_jp == jp_newik)
        if f is not None:
            # frame already exists, delete it
            pmx.frames.pop(f)

        pass

    # write out
    output_filename = core.get_unused_file_name(output_filename)
    pmxlib.write_pmx(output_filename, pmx, moreinfo=moreinfo)
    core.MY_PRINT_FUNC("Done!")
    return None
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)

    # to shift the model by a set amount:
    # first, ask user for X Y Z

    # create the prompt popup
    scale_str = core.MY_GENERAL_INPUT_FUNC(
        lambda x: (model_shift.is_3float(x) is not None), [
            "Enter the X,Y,Z amount to scale this model by:",
            "Three decimal values separated by commas.",
            "Empty input will quit the script."
        ])

    # if empty, quit
    if scale_str == "":
        core.MY_PRINT_FUNC("quitting")
        return None
    # use the same func to convert the input string
    scale = model_shift.is_3float(scale_str)

    uniform_scale = (scale[0] == scale[1] == scale[2])
    if not uniform_scale:
        core.MY_PRINT_FUNC(
            "Warning: when scaling by non-uniform amounts, rigidbody sizes will not be modified"
        )

    ####################
    # what does it mean to scale the entire model?
    # scale vertex position, sdef params
    # ? scale vertex normal vectors, then normalize? need to convince myself of this interaction
    # scale bone position, tail offset
    # scale fixedaxis and localaxis vectors, then normalize
    # scale vert morph, bone morph
    # scale rigid pos, size
    # scale joint pos, movelimits

    for v in pmx.verts:
        # vertex position
        for i in range(3):
            v.pos[i] *= scale[i]
        # vertex normal
        for i in range(3):
            if scale[i] != 0:
                v.norm[i] /= scale[i]
            else:
                v.norm[i] = 100000
        # then re-normalize the normal vector
        v.norm = core.normalize_distance(v.norm)
        # c, r0, r1 params of every SDEF vertex
        # these correspond to real positions in 3d space so they need to be modified
        if v.weighttype == pmxstruct.WeightMode.SDEF:
            for param in v.weight_sdef:
                for i in range(3):
                    param[i] *= scale[i]

    for b in pmx.bones:
        # bone position
        for i in range(3):
            b.pos[i] *= scale[i]
        # bone tail if using offset mode
        if not b.tail_usebonelink:
            for i in range(3):
                b.tail[i] *= scale[i]
        # scale fixedaxis and localaxis vectors, then normalize
        if b.has_fixedaxis:
            for i in range(3):
                b.fixedaxis[i] *= scale[i]
            # then re-normalize
            b.fixedaxis = core.normalize_distance(b.fixedaxis)
        # scale fixedaxis and localaxis vectors, then normalize
        if b.has_localaxis:
            for i in range(3):
                b.localaxis_x[i] *= scale[i]
            for i in range(3):
                b.localaxis_z[i] *= scale[i]
            # then re-normalize
            b.localaxis_x = core.normalize_distance(b.localaxis_x)
            b.localaxis_z = core.normalize_distance(b.localaxis_z)

    for m in pmx.morphs:
        # vertex morph and bone morph (only translate, not rotate)
        if m.morphtype in (pmxstruct.MorphType.VERTEX,
                           pmxstruct.MorphType.BONE):
            morph_scale.morph_scale(m, scale, bone_mode=1)

    for rb in pmx.rigidbodies:
        # rigid body position
        for i in range(3):
            rb.pos[i] *= scale[i]
        # rigid body size
        # NOTE: rigid body size is a special conundrum
        # spheres have only one dimension, capsules have two, and only boxes have 3
        # what's the "right" way to scale a sphere by 1,5,1? there isn't a right way!
        # boxes and capsules can be rotated and stuff so their axes dont line up with world axes, too
        # is it at least possible to rotate bodies so they are still aligned with their bones?
        # eh, why even bother with any of that. 95% of the time full-model scale will be uniform scaling.
        # only scale the rigidbody size if doing uniform scaling: that is guaranteed to be safe!
        if uniform_scale:
            for i in range(3):
                rb.size[i] *= scale[i]

    for j in pmx.joints:
        # joint position
        for i in range(3):
            j.pos[i] *= scale[i]
        # joint min slip
        for i in range(3):
            j.movemin[i] *= scale[i]
        # joint max slip
        for i in range(3):
            j.movemax[i] *= scale[i]

    # that's it? that's it!

    # write out
    output_filename_pmx = input_filename_pmx[0:-4] + "_scale.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
예제 #20
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)
	
	# detect whether arm ik exists
	r = core.my_list_search(pmx.bones, lambda x: x.name_jp == jp_r + jp_newik)
	if r is None:
		r = core.my_list_search(pmx.bones, lambda x: x.name_jp == jp_r + jp_newik2)
	l = core.my_list_search(pmx.bones, lambda x: x.name_jp == jp_l + jp_newik)
	if l is None:
		l = core.my_list_search(pmx.bones, lambda x: x.name_jp == jp_l + jp_newik2)
	
	# decide whether to create or remove arm ik
	if r is None and l is None:
		# add IK branch
		core.MY_PRINT_FUNC(">>>> Adding arm IK <<<")
		# set output name
		if input_filename_pmx.lower().endswith(pmx_noik_suffix.lower()):
			output_filename = input_filename_pmx[0:-(len(pmx_noik_suffix))] + pmx_yesik_suffix
		else:
			output_filename = input_filename_pmx[0:-4] + pmx_yesik_suffix
		for side in [jp_l, jp_r]:
			# first find all 3 arm bones
			# even if i insert into the list, this will still be a valid reference i think
			bones = []
			bones: List[pmxstruct.PmxBone]
			for n in [jp_arm, jp_elbow, jp_wrist]:
				i = core.my_list_search(pmx.bones, lambda x: x.name_jp == side + n, getitem=True)
				if i is None:
					core.MY_PRINT_FUNC("ERROR1: semistandard bone '%s' is missing from the model, unable to create attached arm IK" % (side + n))
					raise RuntimeError()
				bones.append(i)
			# get parent of arm bone (shoulder bone), new bones will be inserted after this
			shoulder_idx = bones[0].parent_idx
			
			# new bones will be inserted AFTER shoulder_idx
			# newarm_idx = shoulder_idx+1
			# newelbow_idx = shoulder_idx+2
			# newwrist_idx = shoulder_idx+3
			# newik_idx = shoulder_idx+4
			
			# make copies of the 3 armchain bones
			
			# arm: parent is shoulder
			newarm = pmxstruct.PmxBone(
				name_jp=bones[0].name_jp + jp_ikchainsuffix, name_en=bones[0].name_en + jp_ikchainsuffix, 
				pos=bones[0].pos, parent_idx=shoulder_idx, deform_layer=bones[0].deform_layer, 
				deform_after_phys=bones[0].deform_after_phys,
				has_rotate=True, has_translate=False, has_visible=False, has_enabled=True, has_ik=False,
				tail_usebonelink=True, tail=0,  # want arm tail to point at the elbow, can't set it until elbow is created
				inherit_rot=False, inherit_trans=False,
				has_localaxis=bones[0].has_localaxis, localaxis_x=bones[0].localaxis_x, localaxis_z=bones[0].localaxis_z,
				has_externalparent=False, has_fixedaxis=False, 
			)
			insert_single_bone(pmx, newarm, shoulder_idx + 1)
			# change existing arm to inherit rot from this
			bones[0].inherit_rot = True
			bones[0].inherit_parent_idx = shoulder_idx + 1
			bones[0].inherit_ratio = 1
			
			# elbow: parent is newarm
			newelbow = pmxstruct.PmxBone(
				name_jp=bones[1].name_jp + jp_ikchainsuffix, name_en=bones[1].name_en + jp_ikchainsuffix, 
				pos=bones[1].pos, parent_idx=shoulder_idx+1, deform_layer=bones[1].deform_layer, 
				deform_after_phys=bones[1].deform_after_phys,
				has_rotate=True, has_translate=False, has_visible=False, has_enabled=True, has_ik=False,
				tail_usebonelink=True, tail=0,  # want elbow tail to point at the wrist, can't set it until wrist is created
				inherit_rot=False, inherit_trans=False,
				has_localaxis=bones[1].has_localaxis, localaxis_x=bones[1].localaxis_x, localaxis_z=bones[1].localaxis_z,
				has_externalparent=False, has_fixedaxis=False, 
			)
			insert_single_bone(pmx, newelbow, shoulder_idx + 2)
			# change existing elbow to inherit rot from this
			bones[1].inherit_rot = True
			bones[1].inherit_parent_idx = shoulder_idx + 2
			bones[1].inherit_ratio = 1
			# now that newelbow exists, change newarm tail to point to this
			newarm.tail = shoulder_idx + 2
			
			# wrist: parent is newelbow
			newwrist = pmxstruct.PmxBone(
				name_jp=bones[2].name_jp + jp_ikchainsuffix, name_en=bones[2].name_en + jp_ikchainsuffix, 
				pos=bones[2].pos, parent_idx=shoulder_idx+2, deform_layer=bones[2].deform_layer, 
				deform_after_phys=bones[2].deform_after_phys,
				has_rotate=True, has_translate=False, has_visible=False, has_enabled=True, has_ik=False,
				tail_usebonelink=True, tail=-1,  # newwrist has no tail
				inherit_rot=False, inherit_trans=False,
				has_localaxis=bones[2].has_localaxis, localaxis_x=bones[2].localaxis_x, localaxis_z=bones[2].localaxis_z,
				has_externalparent=False, has_fixedaxis=False, 
			)
			insert_single_bone(pmx, newwrist, shoulder_idx + 3)
			# now that newwrist exists, change newelbow tail to point to this
			newelbow.tail = shoulder_idx + 3
			
			# copy the wrist to make the IK bone
			en_suffix = "_L" if side == jp_l else "_R"
			# get index of "upperbody" to use as parent of hand IK bone
			ikpar = core.my_list_search(pmx.bones, lambda x: x.name_jp == jp_upperbody)
			if ikpar is None:
				core.MY_PRINT_FUNC("ERROR1: semistandard bone '%s' is missing from the model, unable to create attached arm IK" % jp_upperbody)
				raise RuntimeError()
			
			# newik = [side + jp_newik, en_newik + en_suffix] + bones[2][2:5] + [ikpar]  # new names, copy pos, new par
			# newik += bones[2][6:8] + [1, 1, 1, 1]  + [0, [0,1,0]] # copy deform layer, rot/trans/vis/en, tail type
			# newik += [0, 0, [], 0, [], 0, [], 0, []]  # no inherit, no fixed axis, no local axis, no ext parent, yes IK
			# # add the ik info: [is_ik, [target, loops, anglelimit, [[link_idx, []]], [link_idx, []]]] ] ]
			# newik += [1, [shoulder_idx+3, newik_loops, newik_angle, [[shoulder_idx+2,[]],[shoulder_idx+1,[]]] ] ]
			newik = pmxstruct.PmxBone(
				name_jp=side + jp_newik, name_en=en_newik + en_suffix, pos=bones[2].pos,
				parent_idx=ikpar, deform_layer=bones[2].deform_layer, deform_after_phys=bones[2].deform_after_phys,
				has_rotate=True, has_translate=True, has_visible=True, has_enabled=True,
				tail_usebonelink=False, tail=[0,1,0], inherit_rot=False, inherit_trans=False,
				has_fixedaxis=False, has_localaxis=False, has_externalparent=False, has_ik=True,
				ik_target_idx=shoulder_idx+3, ik_numloops=newik_loops, ik_angle=newik_angle,
				ik_links=[pmxstruct.PmxBoneIkLink(idx=shoulder_idx+2), pmxstruct.PmxBoneIkLink(idx=shoulder_idx+1)]
			)
			insert_single_bone(pmx, newik, shoulder_idx + 4)
			
			# then add to dispframe
			# first, does the frame already exist?
			f = core.my_list_search(pmx.frames, lambda x: x.name_jp == jp_newik, getitem=True)
			newframeitem = pmxstruct.PmxFrameItem(is_morph=False, idx=shoulder_idx + 4)
			if f is None:
				# need to create the new dispframe! easy
				newframe = pmxstruct.PmxFrame(name_jp=jp_newik, name_en=en_newik, is_special=False, items=[newframeitem])
				pmx.frames.append(newframe)
			else:
				# frame already exists, also easy
				f.items.append(newframeitem)
	else:
		# remove IK branch
		core.MY_PRINT_FUNC(">>>> Removing arm IK <<<")
		# set output name
		if input_filename_pmx.lower().endswith(pmx_yesik_suffix.lower()):
			output_filename = input_filename_pmx[0:-(len(pmx_yesik_suffix))] + pmx_noik_suffix
		else:
			output_filename = input_filename_pmx[0:-4] + pmx_noik_suffix
		# identify all bones in ik chain of hand ik bones
		bone_dellist = []
		for b in [r, l]:
			bone_dellist.append(b) # this IK bone
			bone_dellist.append(pmx.bones[b].ik_target_idx) # the target of the bone
			for v in pmx.bones[b].ik_links:
				bone_dellist.append(v.idx) # each link along the bone
		bone_dellist.sort()
		# do the actual delete & shift
		delete_multiple_bones(pmx, bone_dellist)
		
		# delete dispframe for hand ik
		# first, does the frame already exist?
		f = core.my_list_search(pmx.frames, lambda x: x.name_jp == jp_newik)
		if f is not None:
			# frame already exists, delete it
			pmx.frames.pop(f)
		
		pass
	
	# write out
	output_filename = core.get_unused_file_name(output_filename)
	pmxlib.write_pmx(output_filename, pmx, moreinfo=moreinfo)
	core.MY_PRINT_FUNC("Done!")
	return None
def main(moreinfo=False):
	# step zero: verify that Pillow exists
	if Image is None:
		core.MY_PRINT_FUNC("ERROR: Python library 'Pillow' not found. This script requires this library to run!")
		core.MY_PRINT_FUNC("This script cannot be ran from the EXE version, the Pillow library is too large to package into the executable.")
		core.MY_PRINT_FUNC("To install Pillow, please use the command 'pip install Pillow' in the Windows command prompt and then run the Python scripts directly.")
		return None
	# print pillow version just cuz
	core.MY_PRINT_FUNC("Using Pillow version '%s'" % Image.__version__)
	
	core.MY_PRINT_FUNC("Please enter name of PMX model file:")
	input_filename_pmx = core.MY_FILEPROMPT_FUNC(".pmx")
	
	# absolute path to directory holding the pmx
	input_filename_pmx_abs = os.path.normpath(os.path.abspath(input_filename_pmx))
	startpath, input_filename_pmx_rel = os.path.split(input_filename_pmx_abs)
	
	# =========================================================================================================
	# =========================================================================================================
	# =========================================================================================================
	# first, build the list of ALL files that actually exist, then filter it down to neighbor PMXs and relevant files
	relative_all_exist_files = file_sort_textures.walk_filetree_from_root(startpath)
	core.MY_PRINT_FUNC("ALL EXISTING FILES:", len(relative_all_exist_files))
	# now fill "neighbor_pmx" by finding files without path separator that end in PMX
	# these are relative paths tho
	neighbor_pmx = [f for f in relative_all_exist_files if 
					(f.lower().endswith(".pmx")) and
					(os.path.sep not in f) and
					f != input_filename_pmx_rel]
	core.MY_PRINT_FUNC("NEIGHBOR PMX FILES:", len(neighbor_pmx))
	
	# filter down to just image files
	relevant_exist_files = [f for f in relative_all_exist_files if f.lower().endswith(IMG_EXT)]
	core.MY_PRINT_FUNC("RELEVANT EXISTING FILES:", len(relevant_exist_files))

	# =========================================================================================================
	# =========================================================================================================
	# =========================================================================================================
	# now ask if I care about the neighbors and read the PMXes into memory
	
	pmx_filenames = [input_filename_pmx_rel]
	
	if neighbor_pmx:
		core.MY_PRINT_FUNC("")
		info = [
			"Detected %d top-level neighboring PMX files, these probably share the same filebase as the target." % len(neighbor_pmx),
			"If files are moved/renamed but the neighbors are not processed, the neighbor texture references will probably break.",
			"Do you want to process all neighbors in addition to the target? (highly recommended)",
			"1 = Yes, 2 = No"]
		r = core.MY_SIMPLECHOICE_FUNC((1, 2), info)
		if r == 1:
			core.MY_PRINT_FUNC("Processing target + all neighbor files")
			# append neighbor PMX files onto the list of files to be processed
			pmx_filenames += neighbor_pmx
		else:
			core.MY_PRINT_FUNC("WARNING: Processing only target, ignoring %d neighbor PMX files" % len(neighbor_pmx))
	# now read all the PMX objects & store in dict alongside the relative name
	# dictionary where keys are filename and values are resulting pmx objects
	all_pmx_obj = {}
	for this_pmx_name in pmx_filenames:
		this_pmx_obj = pmxlib.read_pmx(os.path.join(startpath, this_pmx_name), moreinfo=moreinfo)
		all_pmx_obj[this_pmx_name] = this_pmx_obj
	
	# =========================================================================================================
	# =========================================================================================================
	# =========================================================================================================
	# 	for each pmx, for each file on disk, match against files used in textures (case-insensitive) and replace with canonical name-on-disk
	#	also fill out how much and how each file is used, and unify dupes between files, all that good stuff
	
	filerecord_list = file_sort_textures.categorize_files(all_pmx_obj, relevant_exist_files, moreinfo)
	
	# =========================================================================================================
	# =========================================================================================================
	# =========================================================================================================
	# DETERMINE NEW NAMES FOR FILES
	
	# first, create a backup of the folder
	# save the name, so that i can delete it if i didn't make any changes
	zipfile_name = ""
	if MAKE_BACKUP_ZIPFILE:
		r = file_sort_textures.make_zipfile_backup(startpath, BACKUP_SUFFIX)
		if not r:
			# this happens if the backup failed somehow AND the user decided to quit
			core.MY_PRINT_FUNC("Aborting: no files were changed")
			return None
		zipfile_name = r
		
	# name used for temporary location
	tempfilename = os.path.join(startpath,"temp_image_file_just_delete_me.png")
	
	pil_cannot_inspect = 0
	pil_cannot_inspect_list = []
	pil_imgext_mismatch = 0
	
	num_recompressed = 0
	
	# list of memory saved by recompressing each file. same order/length as "image_filerecords"
	mem_saved = []
	
	# make image persistient, so I know it always exists and I can always call "close" before open
	im = None
	
	# only iterate over images that exist, obviously
	image_filerecords = [f for f in filerecord_list if f.exists]
	# iterate over the images
	for i, p in enumerate(image_filerecords):
		abspath = os.path.join(startpath, p.name)
		orig_size = os.path.getsize(abspath)

		# if not moreinfo, then each line overwrites the previous like a progress printout does
		# if moreinfo, then each line is printed permanently
		core.MY_PRINT_FUNC("...analyzing {:>3}/{:>3}, file='{}', size={}                ".format(
			i+1, len(image_filerecords), p.name, core.prettyprint_file_size(orig_size)), is_progress=(not moreinfo))
		mem_saved.append(0)

		# before opening, try to close it just in case
		if im is not None:
			im.close()
		# open the image & catch all possible errors
		try:
			im = Image.open(abspath)
		except FileNotFoundError as eeee:
			core.MY_PRINT_FUNC("FILESYSTEM MALFUNCTION!!", eeee.__class__.__name__, eeee)
			core.MY_PRINT_FUNC("os.walk created a list of all filenames on disk, but then this filename doesn't exist when i try to open it?")
			im = None
		except OSError as eeee:
			# this has 2 causes, "Unsupported BMP bitfields layout" or "cannot identify image file"
			if DEBUG:
				print("CANNOT INSPECT!1", eeee.__class__.__name__, eeee, p.name)
			im = None
		except NotImplementedError as eeee:
			# this is because there's some DDS format it can't make sense of
			if DEBUG:
				print("CANNOT INSPECT!2", eeee.__class__.__name__, eeee, p.name)
			im = None
		if im is None:
			pil_cannot_inspect += 1
			pil_cannot_inspect_list.append(p.name)
			continue
			
		if im.format not in IMG_TYPE_TO_EXT:
			core.MY_PRINT_FUNC("WARNING: file '%s' has unusual image format '%s', attempting to continue" % (p.name, im.format))
		# now the image is successfully opened!

		newname = p.name
		base, currext = os.path.splitext(newname)

		# 1, depending on image format, attempt to re-save as PNG
		if im.format not in IM_FORMAT_ALWAYS_SKIP:
			# delete temp file if it still exists
			if os.path.exists(tempfilename):
				try:
					os.remove(tempfilename)
				except OSError as e:
					core.MY_PRINT_FUNC(e.__class__.__name__, e)
					core.MY_PRINT_FUNC("ERROR1: failed to delete temp image file '%s' during processing" % tempfilename)
					break
			# save to tempfilename with png format, use optimize=true
			try:
				im.save(tempfilename, format="PNG", optimize=True)
			except OSError as e:
				core.MY_PRINT_FUNC(e.__class__.__name__, e)
				core.MY_PRINT_FUNC("ERROR2: failed to re-compress image '%s', original not modified" % p.name)
				continue
			# measure & compare file size
			new_size = os.path.getsize(tempfilename)
			diff = orig_size - new_size
			
			# if using a 16-bit BMP format, re-save back to bmp with same name
			is_bad_bmp = False
			if im.format == "BMP":
				try:
					# this might fail, images are weird, sometimes they don't have the attributes i expect
					if im.tile[0][3][0] in KNOWN_BAD_FORMATS:
						is_bad_bmp = True
				except Exception as e:
					if DEBUG:
						print(e.__class__.__name__, e, "BMP THING", p.name, im.tile)

			if diff > (REQUIRED_COMPRESSION_AMOUNT_KB * 1024) \
					or is_bad_bmp\
					or im.format in IM_FORMAT_ALWAYS_CONVERT:
				# if it frees up at least XXX kb, i will keep it!
				# also keep it if it is a bmp encoded with 15-bit or 16-bit colors
				# set p.newname = png, and delete original and move tempname to base.png
				try:
					# delete original
					os.remove(os.path.join(startpath, p.name))
				except OSError as e:
					core.MY_PRINT_FUNC(e.__class__.__name__, e)
					core.MY_PRINT_FUNC("ERROR3: failed to delete old image '%s' after recompressing" % p.name)
					continue
				
				newname = base + ".png"
				# resolve potential collisions by adding numbers suffix to file names
				# first need to make path absolute so get_unused_file_name can check the disk.
				newname = os.path.join(startpath, newname)
				# then check uniqueness against files on disk
				newname = core.get_unused_file_name(newname)
				# now dest path is guaranteed unique against other existing files
				# make the path no longer absolute: undo adding "startpath" above
				newname = os.path.relpath(newname, startpath)
				
				try:
					# move new into place
					os.rename(tempfilename, os.path.join(startpath, newname))
				except OSError as e:
					core.MY_PRINT_FUNC(e.__class__.__name__, e)
					core.MY_PRINT_FUNC("ERROR4: after deleting original '%s', failed to move recompressed version into place!" % p.name)
					continue
				num_recompressed += 1
				p.newname = newname
				mem_saved[-1] = diff
				continue # if succesfully re-saved, do not do the extension-checking below
			# if this is not sufficiently compressed, do not use "continue", DO hit the extension-checking below
			
		# 2, if the file extension doesn't match with the image type, then make it match
		# this only happens if the image was not re-saved above
		if im.format in IMG_TYPE_TO_EXT and currext not in IMG_TYPE_TO_EXT[im.format]:
			newname = base + IMG_TYPE_TO_EXT[im.format][0]
			# resolve potential collisions by adding numbers suffix to file names
			# first need to make path absolute so get_unused_file_name can check the disk.
			newname = os.path.join(startpath, newname)
			# then check uniqueness against files on disk
			newname = core.get_unused_file_name(newname)
			# now dest path is guaranteed unique against other existing files
			# make the path no longer absolute: undo adding "startpath" above
			newname = os.path.relpath(newname, startpath)
			
			# do the actual rename here & now
			try:
				# os.renames creates all necessary intermediate folders needed for the destination
				# it also deletes the source folders if they become empty after the rename operation
				os.renames(os.path.join(startpath, p.name), os.path.join(startpath, newname))
			except OSError as e:
				core.MY_PRINT_FUNC(e.__class__.__name__, e)
				core.MY_PRINT_FUNC("ERROR5: unable to rename file '%s' --> '%s', attempting to continue with other file rename operations"
					% (p.name, newname))
				continue
				
			pil_imgext_mismatch += 1
			p.newname = newname
			continue
	
	# these must be the same length after iterating
	assert len(mem_saved) == len(image_filerecords)
	# if the image is still open, close it
	if im is not None:
		im.close()
	
	# delete temp file if it still exists
	if os.path.exists(tempfilename):
		try:
			os.remove(tempfilename)
		except OSError as e:
			core.MY_PRINT_FUNC(e.__class__.__name__, e)
			core.MY_PRINT_FUNC("WARNING: failed to delete temp image file '%s' after processing" % tempfilename)
	
	# =========================================================================================================
	# =========================================================================================================
	# =========================================================================================================

	# are there any with proposed renaming?
	if not any(u.newname is not None for u in image_filerecords):
		core.MY_PRINT_FUNC("No proposed file changes")
		# if nothing was changed, delete the backup zip!
		core.MY_PRINT_FUNC("Deleting backup archive")
		if os.path.exists(zipfile_name):
			try:
				os.remove(zipfile_name)
			except OSError as e:
				core.MY_PRINT_FUNC(e.__class__.__name__, e)
				core.MY_PRINT_FUNC("WARNING: failed to delete pointless zip file '%s'" % zipfile_name)
		core.MY_PRINT_FUNC("Aborting: no files were changed")
		return None

	# =========================================================================================================
	# =========================================================================================================
	# =========================================================================================================

	# finally, do the actual renaming:
	
	# do all renaming in PMXes
	file_sort_textures.apply_file_renaming(all_pmx_obj, image_filerecords, startpath, skipdiskrename=True)
	
	# write out
	for this_pmx_name, this_pmx_obj in all_pmx_obj.items():
		# NOTE: this is OVERWRITING THE PREVIOUS PMX FILE, NOT CREATING A NEW ONE
		# because I make a zipfile backup I don't need to feel worried about preserving the old version
		output_filename_pmx = os.path.join(startpath, this_pmx_name)
		# output_filename_pmx = core.get_unused_file_name(output_filename_pmx)
		pmxlib.write_pmx(output_filename_pmx, this_pmx_obj, moreinfo=moreinfo)
	
	# =========================================================================================================
	# =========================================================================================================
	# =========================================================================================================
	# NOW PRINT MY RENAMINGS and other findings
	
	filerecord_with_savings = zip(image_filerecords, mem_saved)
	changed_files = [u for u in filerecord_with_savings if u[0].newname is not None]

	core.MY_PRINT_FUNC("="*60)
	if pil_cannot_inspect:
		core.MY_PRINT_FUNC("WARNING: failed to inspect %d image files, these must be handled manually" % pil_cannot_inspect)
		core.MY_PRINT_FUNC(pil_cannot_inspect_list)
	if num_recompressed:
		core.MY_PRINT_FUNC("Recompressed %d images! %s of disk space has been freed" % (num_recompressed, core.prettyprint_file_size(sum(mem_saved))))
	if pil_imgext_mismatch:
		core.MY_PRINT_FUNC("Renamed %d images that had incorrect extensions (included below)" % pil_imgext_mismatch)
	oldname_list = [p[0].name for p in changed_files]
	oldname_list_j = core.MY_JUSTIFY_STRINGLIST(oldname_list)
	newname_list = [p[0].newname for p in changed_files]
	newname_list_j = core.MY_JUSTIFY_STRINGLIST(newname_list)
	savings_list = [("" if p[1]==0 else "saved " + core.prettyprint_file_size(p[1])) for p in changed_files]
	zipped = list(zip(oldname_list_j, newname_list_j, savings_list))
	zipped_and_sorted = sorted(zipped, key=lambda y: file_sort_textures.sortbydirdepth(y[0]))
	for o,n,s in zipped_and_sorted:
		# print 'from' with the case/separator it uses in the PMX
		core.MY_PRINT_FUNC("   {:s} --> {:s} | {:s}".format(o, n, s))
		
	core.MY_PRINT_FUNC("Done!")
	return None
예제 #22
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)
    # get bones
    realbones = pmx.bones
    # then, make 2 lists: one starting from jp_righttoe, one starting from jp_lefttoe
    # start from each "toe" bone (names are known), go parent-find-parent-find until reaching no-parent
    bonechain_r = build_bonechain(realbones, jp_righttoe)
    bonechain_l = build_bonechain(realbones, jp_lefttoe)

    # assert that the bones were found, have correct names, and are in the correct positions
    # also verifies that they are direct parent-child with nothing in between
    try:
        assert bonechain_r[-1].name == jp_righttoe
        assert bonechain_r[-2].name == jp_rightfoot
        assert bonechain_l[-1].name == jp_lefttoe
        assert bonechain_l[-2].name == jp_leftfoot
    except AssertionError:
        core.MY_PRINT_FUNC(
            "ERROR: unexpected structure found for foot/toe bones, verify semistandard names and structure"
        )
        raise RuntimeError()

    # then walk down these 2 lists, add each name to a set: build union of all relevant bones
    relevant_bones = set()
    for b in bonechain_r + bonechain_l:
        relevant_bones.add(b.name)

    # check if waist-cancellation bones are in "relevant_bones", print a warning if they are
    if jp_left_waistcancel in relevant_bones or jp_right_waistcancel in relevant_bones:
        # TODO LOW: i probably could figure out how to support them but this whole script is useless so idgaf
        core.MY_PRINT_FUNC(
            "Warning: waist-cancellation bones found in the model! These are not supported, tool may produce bad results! Attempting to continue..."
        )

    # also need to find initial positions of ik bones (names are known)
    # build a full parentage-chain for each leg
    bonechain_ikr = build_bonechain(realbones, jp_righttoe_ik)
    bonechain_ikl = build_bonechain(realbones, jp_lefttoe_ik)

    # verify that the ik bones were found, have correct names, and are in the correct positions
    try:
        assert bonechain_ikr[-1].name == jp_righttoe_ik
        assert bonechain_ikr[-2].name == jp_rightfoot_ik
        assert bonechain_ikl[-1].name == jp_lefttoe_ik
        assert bonechain_ikl[-2].name == jp_leftfoot_ik
    except AssertionError:
        core.MY_PRINT_FUNC(
            "ERROR: unexpected structure found for foot/toe IK bones, verify semistandard names and structure"
        )
        raise RuntimeError()

    # verify that the bonechains are symmetric in length
    try:
        assert len(bonechain_l) == len(bonechain_r)
        assert len(bonechain_ikl) == len(bonechain_ikr)
    except AssertionError:
        core.MY_PRINT_FUNC(
            "ERROR: unexpected structure found, model is not left-right symmetric"
        )
        raise RuntimeError()

    # determine how many levels of parentage, this value "t" should hold the first level where they are no longer shared
    t = 0
    while bonechain_l[t].name == bonechain_ikl[t].name:
        t += 1
    # back off one level
    lowest_shared_parent = t - 1

    # now i am completely done with the bones CSV, all the relevant info has been distilled down to:
    # !!! bonechain_r, bonechain_l, bonechain_ikr, bonechain_ikl, relevant_bones
    core.MY_PRINT_FUNC("...identified " + str(len(bonechain_l)) +
                       " bones per leg-chain, " + str(len(relevant_bones)) +
                       " relevant bones total")
    core.MY_PRINT_FUNC("...identified " + str(len(bonechain_ikl)) +
                       " bones per IK leg-chain")

    ###################################################################################
    # prompt VMD file name
    core.MY_PRINT_FUNC("Please enter name of VMD dance input file:")
    input_filename_vmd = core.MY_FILEPROMPT_FUNC(".vmd")
    nicelist_in = vmdlib.read_vmd(input_filename_vmd, moreinfo=moreinfo)

    # check if this VMD uses IK or not, print a warning if it does
    any_ik_on = False
    for ikdispframe in nicelist_in.ikdispframes:
        for ik_bone in ikdispframe.ikbones:
            if ik_bone.enable is True:
                any_ik_on = True
                break
    if any_ik_on:
        core.MY_PRINT_FUNC(
            "Warning: the input VMD already has IK enabled, there is no point in running this script. Attempting to continue..."
        )

    # reduce down to only the boneframes for the relevant bones
    # also build a list of each framenumber with a frame for a bone we care about
    relevant_framenums = set()
    boneframe_list = []
    for boneframe in nicelist_in.boneframes:
        if boneframe.name in relevant_bones:
            boneframe_list.append(boneframe)
            relevant_framenums.add(boneframe.f)
    # sort the boneframes by frame number
    boneframe_list.sort(key=lambda x: x.f)
    # make the relevant framenumbers also an ascending list
    relevant_framenums = sorted(list(relevant_framenums))

    boneframe_dict = dict()
    # now restructure the data from a list to a dictionary, keyed by bone name. also discard excess data when i do
    for b in boneframe_list:
        if b.name not in boneframe_dict:
            boneframe_dict[b.name] = []
        # only storing the frame#(1) + position(234) + rotation values(567)
        saveme = [b.f, *b.pos, *b.rot]
        boneframe_dict[b.name].append(saveme)

    core.MY_PRINT_FUNC(
        "...running interpolation to rectangularize the frames...")

    has_warned = False
    # now fill in the blanks by using interpolation, if needed
    for key, bone in boneframe_dict.items():  # for each bone,
        # start a list of frames generated by interpolation
        interpframe_list = []
        i = 0
        j = 0
        while j < len(relevant_framenums):  # for each frame it should have,
            if i == len(bone):
                # if i is beyond end of bone, then copy the values from the last frame and use as a new frame
                newframe = [relevant_framenums[j]] + bone[-1][1:7]
                interpframe_list.append(newframe)
                j += 1
            elif bone[i][0] == relevant_framenums[j]:  # does it have it?
                i += 1
                j += 1
            else:
                # TODO LOW: i could modify this to include my interpolation curve math now that I understand it, but i dont care
                if not has_warned:
                    core.MY_PRINT_FUNC(
                        "Warning: interpolation is needed but interpolation curves are not fully tested! Assuming linear interpolation..."
                    )
                    has_warned = True
                # if there is a mismatch then the target framenum is less than the boneframe framenum
                # build a frame that has frame# + position(123) + rotation values(456)
                newframe = [relevant_framenums[j]]
                # if target is less than the current boneframe, interp between here and prev boneframe
                for p in range(1, 4):
                    # interpolate for each position offset
                    newframe.append(
                        core.linear_map(bone[i][0], bone[i][p], bone[i - 1][0],
                                        bone[i - 1][p], relevant_framenums[j]))
                # rotation interpolation must happen in the quaternion-space
                quat1 = core.euler_to_quaternion(bone[i - 1][4:7])
                quat2 = core.euler_to_quaternion(bone[i][4:7])
                # desired frame is relevant_framenums[j] = d
                # available frames are bone[i-1][0] = s and bone[i][0] = e
                # percentage = (d - s) / (e - s)
                percentage = (relevant_framenums[j] -
                              bone[i - 1][0]) / (bone[i][0] - bone[i - 1][0])
                quat_slerp = core.my_slerp(quat1, quat2, percentage)
                euler_slerp = core.quaternion_to_euler(quat_slerp)
                newframe += euler_slerp
                interpframe_list.append(newframe)
                j += 1
        bone += interpframe_list
        bone.sort(key=core.get1st)

    # the dictionary should be fully filled out and rectangular now
    for bone in boneframe_dict:
        assert len(boneframe_dict[bone]) == len(relevant_framenums)

    # now i am completely done reading the VMD file and parsing its data! everything has been distilled down to:
    # relevant_framenums, boneframe_dict

    ###################################################################################
    # begin the actual calculations
    core.MY_PRINT_FUNC("...beginning forward kinematics computation for " +
                       str(len(relevant_framenums)) + " frames...")

    # output array
    ikframe_list = []

    # have list of bones, parentage, initial pos
    # have list of frames
    # now i "run the dance" and build the ik frames
    # for each relevant frame,
    for I in range(len(relevant_framenums)):
        # for each side,
        for (thisik, this_chain) in zip([bonechain_ikr, bonechain_ikl],
                                        [bonechain_r, bonechain_l]):
            # for each bone in this_chain (ordered, start with root!),
            for J in range(len(this_chain)):
                # reset the current to be the inital position again
                this_chain[J].reset()
            # for each bone in this_chain (ordered, start with toe! do children before parents!)
            # also, don't read/use root! because the IK are also children of root, they inherit the same root transformations
            # count backwards from end to lowest_shared_parent, not including lowest_shared_parent
            for J in range(len(this_chain) - 1, lowest_shared_parent, -1):
                # get bone J within this_chain, translate to name
                name = this_chain[J].name
                # get bone [name] at index I: position & rotation
                try:
                    xpos, ypos, zpos, xrot, yrot, zrot = boneframe_dict[name][
                        I][1:7]
                except KeyError:
                    continue
                # apply position offset to self & children
                # also resets the currposition when changing frames
                for K in range(J, len(this_chain)):
                    # set this_chain[K].current456 = current456 + position
                    this_chain[K].xcurr += xpos
                    this_chain[K].ycurr += ypos
                    this_chain[K].zcurr += zpos
                # apply rotation offset to all children, but not self
                _origin = [
                    this_chain[J].xcurr, this_chain[J].ycurr,
                    this_chain[J].zcurr
                ]
                _angle = [xrot, yrot, zrot]
                _angle_quat = core.euler_to_quaternion(_angle)
                for K in range(J, len(this_chain)):
                    # set this_chain[K].current456 = current rotated around this_chain[J].current456
                    _point = [
                        this_chain[K].xcurr, this_chain[K].ycurr,
                        this_chain[K].zcurr
                    ]
                    _newpoint = rotate3d(_origin, _angle_quat, _point)
                    (this_chain[K].xcurr, this_chain[K].ycurr,
                     this_chain[K].zcurr) = _newpoint

                    # also rotate the angle of this bone
                    curr_angle_euler = [
                        this_chain[K].xrot, this_chain[K].yrot,
                        this_chain[K].zrot
                    ]
                    curr_angle_quat = core.euler_to_quaternion(
                        curr_angle_euler)
                    new_angle_quat = core.hamilton_product(
                        _angle_quat, curr_angle_quat)
                    new_angle_euler = core.quaternion_to_euler(new_angle_quat)
                    (this_chain[K].xrot, this_chain[K].yrot,
                     this_chain[K].zrot) = new_angle_euler
                    pass
                pass
            # now i have cascaded this frame's pose data down the this_chain
            # grab foot/toe (-2 and -1) current position and calculate IK offset from that

            # first, foot:
            # footikend - footikinit = footikoffset
            xfoot = this_chain[-2].xcurr - thisik[-2].xinit
            yfoot = this_chain[-2].ycurr - thisik[-2].yinit
            zfoot = this_chain[-2].zcurr - thisik[-2].zinit
            # save as boneframe to be ultimately formatted for VMD:
            # 	need bonename = (known)
            # 	need frame# = relevantframe#s[I]
            # 	position = calculated
            # 	rotation = 0
            # 	phys = not disabled
            # 	interp = default (20/107)
            # # then, foot-angle: just copy the angle that the foot has
            if STORE_IK_AS_FOOT_ONLY:
                ikframe = [
                    thisik[-2].name, relevant_framenums[I], xfoot, yfoot,
                    zfoot, this_chain[-2].xrot, this_chain[-2].yrot,
                    this_chain[-2].zrot, False
                ]
            else:
                ikframe = [
                    thisik[-2].name, relevant_framenums[I], xfoot, yfoot,
                    zfoot, 0.0, 0.0, 0.0, False
                ]
            ikframe += [20] * 8
            ikframe += [107] * 8
            # append the freshly-built frame
            ikframe_list.append(ikframe)
            if not STORE_IK_AS_FOOT_ONLY:
                # then, toe:
                # toeikend - toeikinit - footikoffset = toeikoffset
                xtoe = this_chain[-1].xcurr - thisik[-1].xinit - xfoot
                ytoe = this_chain[-1].ycurr - thisik[-1].yinit - yfoot
                ztoe = this_chain[-1].zcurr - thisik[-1].zinit - zfoot
                ikframe = [
                    thisik[-1].name, relevant_framenums[I], xtoe, ytoe, ztoe,
                    0.0, 0.0, 0.0, False
                ]
                ikframe += [20] * 8
                ikframe += [107] * 8
                # append the freshly-built frame
                ikframe_list.append(ikframe)
        # now done with a timeframe for all bones on both sides
        # print progress updates
        core.print_progress_oneline(I / len(relevant_framenums))

    core.MY_PRINT_FUNC(
        "...done with forward kinematics computation, now writing output...")

    if INCLUDE_IK_ENABLE_FRAME:
        # create a single ikdispframe that enables the ik bones at frame 0
        ikbones = [
            vmdstruct.VmdIkbone(name=jp_rightfoot_ik, enable=True),
            vmdstruct.VmdIkbone(name=jp_righttoe_ik, enable=True),
            vmdstruct.VmdIkbone(name=jp_leftfoot_ik, enable=True),
            vmdstruct.VmdIkbone(name=jp_lefttoe_ik, enable=True)
        ]
        ikdispframe_list = [
            vmdstruct.VmdIkdispFrame(f=0, disp=True, ikbones=ikbones)
        ]
    else:
        ikdispframe_list = []
        core.MY_PRINT_FUNC(
            "Warning: IK following will NOT be enabled when this VMD is loaded, you will need enable it manually!"
        )

    # convert old-style bonelist ikframe_list to new object format
    ikframe_list = [
        vmdstruct.VmdBoneFrame(name=r[0],
                               f=r[1],
                               pos=r[2:5],
                               rot=r[5:8],
                               phys_off=r[8],
                               interp=r[9:]) for r in ikframe_list
    ]
    # build actual VMD object
    nicelist_out = vmdstruct.Vmd(
        vmdstruct.VmdHeader(2, "SEMISTANDARD-IK-BONES--------"),
        ikframe_list,  # bone
        [],  # morph
        [],  # cam
        [],  # light
        [],  # shadow
        ikdispframe_list  # ikdisp
    )

    # write out
    output_filename_vmd = "%s_ik_from_%s.vmd" % \
           (input_filename_vmd[0:-4], core.get_clean_basename(input_filename_pmx))
    output_filename_vmd = core.get_unused_file_name(output_filename_vmd)
    vmdlib.write_vmd(output_filename_vmd, nicelist_out, moreinfo=moreinfo)

    core.MY_PRINT_FUNC("Done!")
    return None