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

    # 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
Exemple #2
0
def main(moreinfo=True):
	# prompt PMX name
	core.MY_PRINT_FUNC("Please enter name of PMX input file:")
	input_filename_pmx = core.MY_FILEPROMPT_FUNC(".pmx")
	pmx = pmxlib.read_pmx(input_filename_pmx, moreinfo=moreinfo)
	
	# 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
Exemple #3
0
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