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) # 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
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=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
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) 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