def get_idx_in_pmxsublist(s: str, pmxlist: List): if s == "": return -1 # then get the morph index from this # search JP names first t = core.my_list_search(pmxlist, lambda x: x.name_jp.lower() == s.lower()) if t is not None: return t # search EN names next t = core.my_list_search(pmxlist, lambda x: x.name_en.lower() == s.lower()) if t is not None: return t # try to cast to int next try: t = int(s) if 0 <= t < len(pmxlist): return t else: core.MY_PRINT_FUNC("valid indexes are [0-'%d']" % (len(pmxlist) - 1)) return None except ValueError: core.MY_PRINT_FUNC("unable to find matching item for input '%s'" % s) return None
def build_bonechain(allbones: List[pmxstruct.PmxBone], endbone: str) -> List[Bone]: nextbone = endbone buildme = [] while True: r = core.my_list_search(allbones, lambda x: x.name_jp == nextbone, getitem=True) if r is None: core.MY_PRINT_FUNC( "ERROR: unable to find '" + nextbone + "' in input file, unable to build parentage chain") raise RuntimeError() # 0 = bname, 5 = parent index, 234 = xyz position nextbone = allbones[r.parent_idx].name_jp newrow = Bone(r.name_jp, r.pos[0], r.pos[1], r.pos[2]) buildme.append(newrow) # if parent index is -1, that means there is no parent. so we reached root. so break. if r.parent_idx == -1: break buildme.reverse() return buildme
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
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=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
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
def make_autotwist_segment(pmx: pmxstruct.Pmx, side, arm_s, armtwist_s, elbow_s, moreinfo): # note: will be applicable to elbow-wristtwist-wrist as well! just named like armtwist for simplicity # 1, locate arm/armtwist/elbow idx and obj r = [] for n in (arm_s, armtwist_s, elbow_s): n2 = side[0] + n[0] i = core.my_list_search(pmx.bones, lambda x: x.name_jp == n2) if i is None: core.MY_PRINT_FUNC( "ERROR: standard bone '%s' not found in model, this is required!" % n2) return None r.append(i) arm_idx, armtwist_idx, elbow_idx = r # unpack into named variables arm = pmx.bones[arm_idx] armtwist = pmx.bones[armtwist_idx] elbow = pmx.bones[elbow_idx] # 2, find all armtwist-sub bones armtwist_sub_obj = [] # # dont forget to refresh elbow_idx # elbow_idx = core.my_list_search(pmx.bones, lambda x: x.name_jp == side + elbow_s) for d, bone in enumerate(pmx.bones): # anything that partial inherit from armtwist (except arm) if bone.inherit_rot and bone.inherit_parent_idx == armtwist_idx: if d == arm_idx: continue armtwist_sub_obj.append(bone) # anything that has armtwist as parent (except elbow or elbow helper (full parent armtwist, partial parent elbow)) if bone.parent_idx == armtwist_idx: if d == elbow_idx: continue if bone.inherit_rot and bone.inherit_parent_idx == elbow_idx: continue armtwist_sub_obj.append(bone) # 3, calculate "perpendicular" location # get axis from arm to elbow, normalize to 1 axis = [b - a for a, b in zip(arm.pos, elbow.pos)] axis = core.normalize_distance(axis) # calc vector in XZ plane at 90-deg from axis frontback = core.my_cross_product(axis, [0, 1, 0]) frontback = core.normalize_distance(frontback) # calc vector in the same vertical plane as axis, at 90-deg from axis perpendicular = core.my_cross_product(axis, frontback) perpendicular = core.normalize_distance(perpendicular) # if result has negative y, invert if perpendicular[1] < 0: perpendicular = [p * -1 for p in perpendicular] # normalize to perpendicular_offset_dist perpendicular = [perpendicular_offset_dist * t for t in perpendicular] # add this to elbow pos and save perp_pos = [a + b for a, b in zip(elbow.pos, perpendicular)] # 4, create the six ik bones # cannot reference other bone idxs until they are inserted!! start = max(arm_idx, armtwist_idx) armD_idx = start + 1 armDend_idx = start + 2 armDik_idx = start + 3 armT_idx = start + 4 armTend_idx = start + 5 armTik_idx = start + 6 # make armD, pos=arm.pos, parent=arm.parent, tail=armDend armD = pmxstruct.PmxBone( name_jp=side[0] + arm_s[0] + n_base[0], name_en=arm_s[1] + n_base[1] + side[1], pos=arm.pos, parent_idx=-99, deform_layer=0, deform_after_phys=False, has_rotate=True, has_translate=False, has_visible=False, has_enabled=False, has_ik=False, tail_usebonelink=True, tail=-99, inherit_rot=False, inherit_trans=False, has_fixedaxis=False, has_localaxis=arm.has_localaxis, localaxis_x=arm.localaxis_x, localaxis_z=arm.localaxis_z, has_externalparent=False, ) # make armDend, pos=elbow.pos, parent=armD_idx armDend = pmxstruct.PmxBone( name_jp=side[0] + arm_s[0] + n_base[0] + n_end[0], name_en=arm_s[1] + n_base[1] + side[1] + n_end[1], pos=elbow.pos, parent_idx=-99, deform_layer=0, deform_after_phys=False, has_rotate=True, has_translate=False, has_visible=False, has_enabled=False, has_ik=False, tail_usebonelink=True, tail=-1, inherit_rot=False, inherit_trans=False, has_fixedaxis=False, has_localaxis=False, has_externalparent=False, ) # make armD_IK, pos=elbow.pos, parent=elbow.parent, target=armDend, link=armD armDik = pmxstruct.PmxBone( name_jp=side[0] + arm_s[0] + n_base[0] + n_ik[0], name_en=arm_s[1] + n_base[1] + n_ik[1] + side[1], pos=elbow.pos, parent_idx=-99, deform_layer=0, deform_after_phys=False, has_rotate=True, has_translate=True, has_visible=False, has_enabled=False, has_ik=True, tail_usebonelink=True, tail=-1, inherit_rot=False, inherit_trans=False, has_fixedaxis=False, has_localaxis=False, has_externalparent=False, ik_target_idx=-99, ik_numloops=ik_numloops, ik_angle=ik_angle, ik_links=[ pmxstruct.PmxBoneIkLink(idx=-99, limit_min=ikD_lim_min, limit_max=ikD_lim_max) ]) # make armT, pos=elbow.pos, parent=armD_idx, tail=armTend armT = pmxstruct.PmxBone( name_jp=side[0] + arm_s[0] + n_twist[0], name_en=arm_s[1] + n_twist[1] + side[1], pos=elbow.pos, parent_idx=-99, deform_layer=0, deform_after_phys=False, has_rotate=True, has_translate=False, has_visible=False, has_enabled=False, has_ik=False, tail_usebonelink=True, tail=-99, inherit_rot=False, inherit_trans=False, has_fixedaxis=False, has_localaxis=False, has_externalparent=False, ) # make armTend, pos=perp_pos, parent=armT_idx armTend = pmxstruct.PmxBone( name_jp=side[0] + arm_s[0] + n_twist[0] + n_end[0], name_en=arm_s[1] + n_twist[1] + side[1] + n_end[1], pos=perp_pos, parent_idx=-99, deform_layer=0, deform_after_phys=False, has_rotate=True, has_translate=False, has_visible=False, has_enabled=False, has_ik=False, tail_usebonelink=True, tail=-1, inherit_rot=False, inherit_trans=False, has_fixedaxis=False, has_localaxis=False, has_externalparent=False, ) # make armT_IK, pos=perp_pos, parent=elbow.parent, target=armTend, link=armT armTik = pmxstruct.PmxBone( name_jp=side[0] + arm_s[0] + n_twist[0] + n_ik[0], name_en=arm_s[1] + n_twist[1] + n_ik[1] + side[1], pos=perp_pos, parent_idx=-99, deform_layer=0, deform_after_phys=False, has_rotate=True, has_translate=True, has_visible=False, has_enabled=False, has_ik=True, tail_usebonelink=True, tail=-1, inherit_rot=False, inherit_trans=False, has_fixedaxis=False, has_localaxis=False, has_externalparent=False, ik_target_idx=-99, ik_numloops=ik_numloops, ik_angle=ik_angle, ik_links=[pmxstruct.PmxBoneIkLink(idx=-99)]) # insert these 6 bones # TODO: create more efficient function for multi-insert? nah, this is fine insert_single_bone(pmx, armD, armD_idx) insert_single_bone(pmx, armDend, armDend_idx) insert_single_bone(pmx, armDik, armDik_idx) insert_single_bone(pmx, armT, armT_idx) insert_single_bone(pmx, armTend, armTend_idx) insert_single_bone(pmx, armTik, armTik_idx) # fix all references to other bones (-99s) armD.tail = armDend_idx armT.tail = armTend_idx armD.parent_idx = arm.parent_idx armDend.parent_idx = armD_idx armDik.parent_idx = elbow.parent_idx armT.parent_idx = armD_idx armTend.parent_idx = armT_idx armTik.parent_idx = elbow.parent_idx armDik.ik_target_idx = armDend_idx armDik.ik_links[0].idx = armD_idx armTik.ik_target_idx = armTend_idx armTik.ik_links[0].idx = armT_idx # 5, modify the armtwist-sub bones # first go back from obj to indices, since the bones moved armtwist_sub = [b.idx_within(pmx.bones) for b in armtwist_sub_obj] for b_idx in armtwist_sub: bone = pmx.bones[b_idx] # change parent from arm to armD bone.parent_idx = armD_idx # change partial inherit from armtwist to armT bone.inherit_parent_idx = armT_idx # 6, insert additional armtwist-sub bones and transfer weight to them # first, check whether armtwistX would receive any weights/RBs # note, transferring from armtwist to armtwist changes nothing, this is harmless, just for looking armtwistX_used = transfer_to_armtwist_sub(pmx, armtwist_idx, armtwist_idx) if armtwistX_used: asdf = len(armtwist_sub) + 1 # make armtwistX, pos=armtwist.pos, parent=armD_idx, inherit armT=1.00 armtwistX = pmxstruct.PmxBone( name_jp=side[0] + armtwist_s[0] + str(asdf), name_en=armtwist_s[1] + str(asdf) + side[1], pos=armtwist.pos, parent_idx=-99, deform_layer=0, deform_after_phys=False, has_rotate=True, has_translate=False, has_visible=False, has_enabled=True, has_ik=False, tail_usebonelink=True, tail=-1, inherit_rot=True, inherit_trans=False, inherit_parent_idx=-99, inherit_ratio=1.00, has_fixedaxis=False, has_localaxis=False, has_externalparent=False, ) # insert armtwistX at max(armtwist_sub) + 1 armtwistX_idx = max(armtwist_sub) + 1 insert_single_bone(pmx, armtwistX, armtwistX_idx) # fix references to other bones armtwistX.parent_idx = armD_idx armtwistX.inherit_parent_idx = armT_idx # transfer all weight and rigidbody references from armtwist to armtwistX # this time the return val is not needed transfer_to_armtwist_sub(pmx, armtwist_idx, armtwistX_idx) armtwist_sub_obj.append(armtwistX) # second, do the same thing for armtwist0 armtwist0_used = transfer_to_armtwist_sub(pmx, arm_idx, arm_idx) if armtwist0_used: # make armtwist0, pos=arm.pos, parent=armD_idx, inherit armT=0.00 armtwist0 = pmxstruct.PmxBone( name_jp=side[0] + armtwist_s[0] + "0", name_en=armtwist_s[1] + "0" + side[1], pos=arm.pos, parent_idx=-99, deform_layer=0, deform_after_phys=False, has_rotate=True, has_translate=False, has_visible=False, has_enabled=True, has_ik=False, tail_usebonelink=True, tail=-1, inherit_rot=True, inherit_trans=False, inherit_parent_idx=-99, inherit_ratio=0.00, has_fixedaxis=False, has_localaxis=False, has_externalparent=False, ) # insert armtwist0 at min(armtwist_sub) armtwist0_idx = min(armtwist_sub) insert_single_bone(pmx, armtwist0, armtwist0_idx) # fix references to other bones armtwist0.parent_idx = armD_idx armtwist0.inherit_parent_idx = armT_idx # transfer all weight and rigidbody references from arm to armtwist0 # this time the return val is not needed transfer_to_armtwist_sub(pmx, arm_idx, armtwist0_idx) armtwist_sub_obj.append(armtwist0) # 7, detect & fix incorrect structure among primary bones # refresh list of armtwist_sub indixes cuz stuff was inserted armtwist_sub = [b.idx_within(pmx.bones) for b in armtwist_sub_obj] # elbow should be a child of arm or armtwist, NOT any of the armtwist-sub bones # this is to prevent deform layers from getting all fucky if elbow.parent_idx in armtwist_sub: newparent = max(arm_idx, armtwist_idx) core.MY_PRINT_FUNC("WARNING: fixing improper parenting for bone '%s'" % elbow.name_jp) core.MY_PRINT_FUNC("parent was '%s', changing to '%s'" % (pmx.bones[elbow.parent_idx].name_jp, pmx.bones[newparent].name_jp)) core.MY_PRINT_FUNC( "if this bone has a 'helper bone' please change its parent in the same way" ) elbow.parent_idx = newparent # 8, fix shoulder-helper and elbow-helper if they exist # shoulder helper: parent=shoulder(C), inherit=arm # goto: parent=shoulder(C), inherit=armD # elbow helper: parent=arm(twist), inherit=elbow # goto: parent=armT, inherit=elbowD # need to refresh elbow idx cuz it moved elbow_idx = elbow.idx_within(pmx.bones) for d, bone in enumerate(pmx.bones): # transfer "inherit arm" to "inherit armD" # this should be safe for all bones if bone.inherit_rot and bone.inherit_parent_idx == arm_idx: bone.inherit_parent_idx = armD_idx # transfer "parent armtwist" to "parent armT" # this needs to exclude elbow, D_IK, T_IK, armtwist, arm if bone.parent_idx == armtwist_idx: if d not in (elbow_idx, armDik_idx, armTik_idx, armtwist_idx, arm_idx): bone.parent_idx = armT_idx if bone.parent_idx == arm_idx: if d not in (elbow_idx, armDik_idx, armTik_idx, armtwist_idx, arm_idx): bone.parent_idx = armD_idx # 9, set the deform order of all the bones so that it doesn't break when armIK is added # what deform level should they start from? base_deform = max(arm.deform_layer, armtwist.deform_layer, elbow.deform_layer) armD.deform_layer = base_deform + 2 armDend.deform_layer = base_deform + 2 armDik.deform_layer = base_deform + 2 armT.deform_layer = base_deform + 3 armTend.deform_layer = base_deform + 3 armTik.deform_layer = base_deform + 3 for bone in armtwist_sub_obj: bone.deform_layer = base_deform + 4 # fix deform for anything hanging off of the armtwist bones (rare but sometimes exists) deform_changed = 0 deform_changed += fix_deform_for_children(pmx, armD_idx) deform_changed += fix_deform_for_children(pmx, armT_idx) for idx in armtwist_sub: deform_changed += fix_deform_for_children(pmx, idx) if moreinfo and deform_changed: core.MY_PRINT_FUNC("modified deform order for %d existing bones" % deform_changed) # done with this function??? 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
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 dispframe_fix(pmx: pmxstruct.Pmx, moreinfo=False): # root group: "Root"/"Root" # facial group: "表情"/"Exp" fix_root = 0 fix_center = 0 hidden_morphs_removed = 0 duplicate_entries_removed = 0 empty_groups_removed = 0 # find the ID# for motherbone... if not found, use whatever is at 0 motherid = core.my_list_search(pmx.bones, lambda x: x.name_jp == "全ての親") if motherid is None: motherid = 0 # ensure that "motherbone" and nothing else is in the root: for d, frame in enumerate(pmx.frames): # only operate on the root group if frame.name_jp == "Root" and frame.name_en == "Root" and frame.is_special: newframelist = [ pmxstruct.PmxFrameItem(is_morph=False, idx=motherid) ] if frame.items != newframelist: # if the itemslist is not exactly only motherbone, make it exactly only motherbone frame.items = newframelist fix_root += 1 break if fix_root and moreinfo: core.MY_PRINT_FUNC("fixing root group") # fix the contents of the "center"/"センター" group # first, find it, or if it does not exist, make it centerid = core.my_list_search(pmx.frames, lambda x: x.name_jp == "センター") if centerid is None: centerid = 2 newframe = pmxstruct.PmxFrame(name_jp="センター", name_en="Center", is_special=False, items=[]) pmx.frames.insert(2, newframe) fix_center += 1 # if i set "motherbone" to be root, then remove it from center if fix_root: removeme = core.my_list_search(pmx.frames[centerid].items, lambda x: x.idx == motherid) if removeme is not None: pmx.frames[centerid].items.pop(removeme) # ensure center contains the proper semistandard contents: view/center/groove/waist # find bone IDs for each of these desired bones centerframeboneids = [ core.my_list_search(pmx.bones, lambda x: x.name_jp == name) for name in CENTER_FRAME_BONES ] for boneid in centerframeboneids: # if this bone does not exist, skip if boneid is None: continue # if this bone already in center, skip if any(item.idx == boneid for item in pmx.frames[centerid].items): continue # add an item for this bone to the group newitem = pmxstruct.PmxFrameItem(is_morph=False, idx=boneid) pmx.frames[centerid].items.append(newitem) # do not count moving a bone from root to center fix_center += 1 if fix_center and moreinfo: core.MY_PRINT_FUNC("fixing center group") displayed_morphs = set() displayed_bones = set() # build sets of all bones/morphs that are in the panels # delete bones that are in the panels more than once # remove all morphs that are group 0 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 #, discard it if morph.panel == pmxstruct.MorphPanel.HIDDEN: frame.items.pop(i) hidden_morphs_removed += 1 # if this is valid but already in the set of used morphs, discard it elif item.idx in displayed_morphs: frame.items.pop(i) duplicate_entries_removed += 1 # otherwise, add it to set of used morphs else: displayed_morphs.add(item.idx) i += 1 else: # if it is a bone # if this is already in the set of used bones, delete it if item.idx in displayed_bones: frame.items.pop(i) duplicate_entries_removed += 1 # otherwise, add it to set of used bones else: displayed_bones.add(item.idx) i += 1 if hidden_morphs_removed: core.MY_PRINT_FUNC("removed %d hidden morphs (cause of crashes)" % hidden_morphs_removed) # core.MY_PRINT_FUNC("!!! Warning: do not add 'hidden' morphs to the display group! MMD will crash!") if duplicate_entries_removed and moreinfo: core.MY_PRINT_FUNC("removed %d duplicate bones or morphs" % duplicate_entries_removed) # have identified which bones/morphs are displayed: now identify which ones are NOT # want all bones not already in 'displayed_bones' that are also visible and enabled undisplayed_bones = [ d for d, bone in enumerate(pmx.bones) if (d not in displayed_bones) and bone.has_visible and bone.has_enabled ] if undisplayed_bones: if moreinfo: core.MY_PRINT_FUNC( "added %d undisplayed bones to new group 'morebones'" % len(undisplayed_bones)) # add a new frame to hold all bones newframelist = [ pmxstruct.PmxFrameItem(is_morph=False, idx=x) for x in undisplayed_bones ] newframe = pmxstruct.PmxFrame(name_jp="morebones", name_en="morebones", is_special=False, items=newframelist) pmx.frames.append(newframe) # build list of which morphs are NOT shown # want all morphs not already in 'displayed_morphs' that are not hidden undisplayed_morphs = [ d for d, morph in enumerate(pmx.morphs) if (d not in displayed_morphs) and ( morph.panel != pmxstruct.MorphPanel.HIDDEN) ] if undisplayed_morphs: if moreinfo: core.MY_PRINT_FUNC("added %d undisplayed morphs to Facials group" % len(undisplayed_morphs)) newframelist = [ pmxstruct.PmxFrameItem(is_morph=True, idx=x) for x in undisplayed_morphs ] # find morphs group and only add to it # should ALWAYS be at index 1 but whatever might as well be extra safe idx = core.my_list_search( pmx.frames, lambda x: (x.name_jp == "表情" and x.is_special)) if idx is not None: # concatenate to end of item list pmx.frames[idx].items += newframelist else: core.MY_PRINT_FUNC( "ERROR: unable to find semistandard 'expressions' display frame" ) # check if there are too many morphs among all frames... if so, trim and remake "displayed morphs" # morphs can theoretically be in any frame, they SHOULD only be in the "expressions" frame but people mess things up total_num_morphs = 0 for frame in pmx.frames: i = 0 while i < len(frame.items): # if this is a bone, skip it if not frame.items[i].is_morph: i += 1 else: # if it is a morph, count it total_num_morphs += 1 # if i have already counted too many morphs, pop it if total_num_morphs > MAX_MORPHS_IN_DISPLAY: frame.items.pop(i) else: i += 1 num_morphs_over_limit = max(total_num_morphs - MAX_MORPHS_IN_DISPLAY, 0) if num_morphs_over_limit: core.MY_PRINT_FUNC( "removed %d morphs to stay under the %d morph limit (cause of crashes)" % (num_morphs_over_limit, MAX_MORPHS_IN_DISPLAY)) core.MY_PRINT_FUNC( "!!! Warning: do not add the remaining morphs to the display group! MMD will crash!" ) # delete any groups that are empty i = 0 while i < len(pmx.frames): frame = pmx.frames[i] # if it is empty AND it is not "special" then delete it if len(frame.items) == 0 and not frame.is_special: pmx.frames.pop(i) empty_groups_removed += 1 else: i += 1 if empty_groups_removed and moreinfo: core.MY_PRINT_FUNC("removed %d empty groups" % empty_groups_removed) overall = num_morphs_over_limit + \ fix_center + \ empty_groups_removed + \ len(undisplayed_bones) + \ len(undisplayed_morphs) + \ duplicate_entries_removed + \ hidden_morphs_removed + \ fix_root if overall == 0: core.MY_PRINT_FUNC("No changes are required") return pmx, False core.MY_PRINT_FUNC("Fixed %d things related to display pane groups" % overall) return pmx, True
def identify_unused_bones(pmx: pmxstruct.Pmx, moreinfo: bool) -> List[int]: """ Process the PMX and return a list of all unused bone indicies in the model. 1. get bones used by a rigidbody. 2. get bones that have weight on at least 1 vertex. 3. mark "exception" bones, done here so parents of exception bones are kept too. 4. inheritance, aka "bones used by bones", recursively climb the tree & get all bones the "true" used bones depend on. 5. tails or point-ats. 6. invert used to get set of unused. :param pmx: PMX list-of-lists object :param moreinfo: print extra info for debug or whatever :return: list of bone indices that are not used """ # python set: no duplicates! .add(newbone), "in", .discard(delbone) # true_used_bones is set of BONE INDEXES true_used_bones = set() # exception bones + rigidbody bones + vertex bones vertex_ct = { } # how many vertexes does each bone control? sometimes useful info # first: bones used by a rigidbody for body in pmx.rigidbodies: true_used_bones.add(body.bone_idx) # second: bones used by a vertex i.e. has nonzero weight # any vertex that has nonzero weight for that bone for vert in pmx.verts: for boneidx, weightval in vert.weight: if weightval != 0: true_used_bones.add(boneidx) core.increment_occurance_dict(vertex_ct, boneidx) # NOTE: some vertices/rigidbodies depend on "invalid" (-1) bones, clean that up here true_used_bones.discard(-1) # third: mark the "exception" bones as "used" if they are in the model for protect in BONES_TO_PROTECT: # get index from JP name i = core.my_list_search(pmx.bones, lambda x: x.name_jp == protect) if i is not None: true_used_bones.add(i) # build ik groups here # IKbone + chain + target are treated as a group... if any 1 is used, all of them are used. build those groups now. ik_groups = [] # list of sets for d, bone in enumerate(pmx.bones): if bone.has_ik: # if ik enabled for this bone, ik_set = set() ik_set.add(d) # this bone ik_set.add(bone.ik_target_idx) # this bone's target for link in bone.ik_links: ik_set.add(link.idx) # all this bone's IK links ik_groups.append(ik_set) # fourth: NEW APPROACH FOR SOLVING INHERITANCE: RECURSION! # for each bone that we know to be used, run UP the inheritance tree and collect everything that it depends on # recursion inputs: pmx bonelist, ik groups, set of already-known-used, and the bone to start from # bonelist is readonly, ik groups are readonly # set of already-known-used overlaps with set-being-built, probably just use one global ref to save time merging sets # standard way: input is set-of-already-known, return set-built-from-target, that requires merging results after each call tho # BUT each function level adds exactly 1 or 0 bones to the set, therefore can just modify the set that is being passed around def recursive_climb_inherit_tree(target: int, set_being_built): # implicitly inherits variables pmx, ik_groups from outer scope if target in set_being_built or target == -1: # stop condition: if this bone idx is already known to be used, i have already ran recursion from this node. don't do it again. # also abort if the target is -1 which means invalid bone return # if not already in the set, but recursion is being called on this, then this bone is a "parent" of a used bone and should be added. set_being_built.add(target) # now the parents of THIS bone are also used, so recurse into those. bb = pmx.bones[target] # acutal parent recursive_climb_inherit_tree(bb.parent_idx, set_being_built) # partial inherit: if partial rot or partial move, and ratio is nonzero and parent is valid if (bb.inherit_rot or bb.inherit_trans ) and bb.inherit_ratio != 0 and bb.inherit_parent_idx != -1: recursive_climb_inherit_tree(bb.inherit_parent_idx, set_being_built) # IK groups: if in an IK group, recurse to all members of that IK group for group in ik_groups: if target in group: for ik_member in group: recursive_climb_inherit_tree(ik_member, set_being_built) parent_used_bones = set() # true_used_bones + parents + point-at links # now that the recursive function is defined, actually invoke the function from every truly-used bone for tu in true_used_bones: recursive_climb_inherit_tree(tu, parent_used_bones) # fifth: "tail" or point-at links # propogate DOWN the inheritance tree exactly 1 level, no more. # also get all bones these tails depend on, it shouldn't depend on anything new but it theoretically can. final_used_bones = set() for bidx in parent_used_bones: b = pmx.bones[bidx] # if this bone has a tail, if b.tail_usebonelink: # add it and anything it depends on to the set. recursive_climb_inherit_tree(b.tail, final_used_bones) # now merge the two sets final_used_bones = final_used_bones.union(parent_used_bones) # sixth: assemble the final "unused" set by inverting # set of all bones, for inverting purposes all_bones_list = list(range(len(pmx.bones))) all_bones_set = set(all_bones_list) unused_bones = all_bones_set.difference(final_used_bones) unused_bones_list = sorted(list(unused_bones)) # print neat stuff if moreinfo: if unused_bones_list: core.MY_PRINT_FUNC( "Bones: total=%d, true_used=%d, parents=%d, tails=%d, unused=%d" % (len(pmx.bones), len(true_used_bones), len(parent_used_bones) - len(true_used_bones), len(final_used_bones) - len(parent_used_bones), len(unused_bones_list))) # debug aid if PRINT_VERTICES_CONTROLLED_BY_EACH_BONE: core.MY_PRINT_FUNC("Number of vertices controlled by each bone:") for bp in all_bones_list: if bp in vertex_ct: core.MY_PRINT_FUNC("#: %d ct: %d" % (bp, vertex_ct[bp])) return unused_bones_list
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