Exemple #1
0
def parse_vmd_header(raw: bytearray, moreinfo: bool) -> vmdstruct.VmdHeader:
    ############################
    # unpack the header, get file version and model name
    # version only affects the length of the model name text field, but i'll return it anyway
    try:
        header = core.my_unpack(fmt_header, raw)
    except Exception as e:
        core.MY_PRINT_FUNC(e.__class__.__name__, e)
        core.MY_PRINT_FUNC("section=header")
        core.MY_PRINT_FUNC(
            "Err: something went wrong while parsing, file is probably corrupt/malformed"
        )
        raise RuntimeError()

    if header == "Vocaloid Motion Data 0002":
        # if this matches, this is version >= 1.30
        # but i will just return "2"
        version = 2
        # model name string is 20-chars long
        useme = fmt_modelname_new
    elif header == "Vocaloid Motion Data file":
        # this is actually untested & unverified, but according to the docs this is how it's labelled
        # if this matches, this is version < 1.30
        # but i will just return "1"
        version = 1
        # model name string is 10-chars long
        useme = fmt_modelname_old
    else:
        core.MY_PRINT_FUNC(
            "ERR: found unsupported file version identifier string, '%s'" %
            header)
        raise RuntimeError()

    try:
        modelname = core.my_unpack(useme, raw)
    except Exception as e:
        core.MY_PRINT_FUNC(e.__class__.__name__, e)
        core.MY_PRINT_FUNC("section=modelname")
        core.MY_PRINT_FUNC(
            "Err: something went wrong while parsing, file is probably corrupt/malformed"
        )
        raise RuntimeError()

    if moreinfo: core.MY_PRINT_FUNC("...model name   = JP:'%s'" % modelname)

    return vmdstruct.VmdHeader(version=version, modelname=modelname)
Exemple #2
0
def read_vmdtext_header(rawlist_text: List[list]) -> vmdstruct.VmdHeader:
	##################################
	# header data
	global readfrom_line
	
	# first check for bad format
	check2_match_first_item(rawlist_text, keystr_version)

	# read version
	version = rawlist_text[readfrom_line][1]
	readfrom_line += 1

	# check for bad format again
	check2_match_first_item(rawlist_text, keystr_modelname)

	# read model name
	modelname = rawlist_text[readfrom_line][1]
	readfrom_line += 1
	core.MY_PRINT_FUNC("...model name   = JP:'%s'" % modelname)
	# assemble and return
	return vmdstruct.VmdHeader(version, modelname)
Exemple #3
0
def read_vpd(vpd_filepath: str, moreinfo=False) -> vmdstruct.Vmd:
    """
	Read a VPD text file and convert it to a VMD object with all boneframes and morphframes at time=0.
	
	:param vpd_filepath: destination filepath/name, relative from CWD or absolute
	:param moreinfo: if true, get extra printouts with more info about stuff
	:return: VMD object
	"""
    cleanname = core.get_clean_basename(vpd_filepath) + ".vpd"
    core.MY_PRINT_FUNC("Begin reading VPD file '%s'" % cleanname)

    # read textfile to linelist, no CSV fields to untangle here
    lines = core.read_txtfile_to_list(vpd_filepath, use_jis_encoding=True)

    # verify magic header "Vocaloid Pose Data file"
    if lines[0] != "Vocaloid Pose Data file":
        core.MY_PRINT_FUNC(
            "warning: did not find expected magic header! this might not be a real VPD file!"
        )
    # get rid of the header
    lines.pop(0)

    # this var is a state machine that keeps track of what I expect to find next
    # if i find anything other than blankspace or what I expect, then err & die
    parse_state = 0

    # save this so I know when I'm done reading all the bones the header promised
    num_bones = 0

    # temp vars to hold stuff from previous lines
    temp_title = "qwertyuiop"
    temp_name = "foobar"
    temp_pos = tuple()
    temp_rot = tuple()
    temp_value = 0.0

    # this is the VMD object that will be ultimately returned
    vmd_boneframes = []
    vmd_morphframes = []

    # iterate over the remaining lines until end-of-file
    for d, line in enumerate(lines):
        # vertical whitespace is always acceptable
        if not line or line.isspace(): continue

        # if line is not blank, it had better be something good:
        if parse_state == 0:  # 0 = model title
            m = title_re.match(line)  # regex match from beginning of line
            if m is None:
                core.MY_PRINT_FUNC(
                    "Parse err line %d state %d: failed to find model title" %
                    (d + 2, parse_state))
                core.MY_PRINT_FUNC("line = '%s'" % line)
                raise RuntimeError()
            temp_title = m.group(
                1)  # if valid match, then grab the actual title
            if moreinfo:
                core.MY_PRINT_FUNC("...model name   = JP:'%s'" % temp_title)
            parse_state = 10  # next thing to look for is #bones

        elif parse_state == 10:  # 10 = #bones
            m = f1_re.match(line)  # regex match from beginning of line
            if m is None:
                core.MY_PRINT_FUNC(
                    "Parse err line %d state %d: failed to find number of bones"
                    % (d + 2, parse_state))
                core.MY_PRINT_FUNC("line = '%s'" % line)
                raise RuntimeError()
            num_bones = int(float(m.group(
                1)))  # if a valid match, then grab the actual # of bones
            if moreinfo:
                core.MY_PRINT_FUNC("...# of boneframes          = %d" %
                                   num_bones)
            if num_bones == 0:
                parse_state = 30  # if there are 0 bones then immediately begin with the morphs
            else:
                parse_state = 20  # otherwise look for bones next

        elif parse_state == 20:  # 20 = boneA, name
            m = bone_re.match(line)  # regex match from beginning of line
            if m is None:
                core.MY_PRINT_FUNC(
                    "Parse err line %d state %d: failed to find bone name" %
                    (d + 2, parse_state))
                core.MY_PRINT_FUNC("line = '%s'" % line)
                raise RuntimeError()
            idx, name = m.group(1, 2)  # get idx and name
            temp_name = name
            # can i use idx for anything? or is it totally useless?
            parse_state = 21  # next look for quaternion rotation

        elif parse_state == 21:  # 21 = boneB, xyz pos
            m = f3_re.match(line)  # regex match from beginning of line
            if m is None:
                core.MY_PRINT_FUNC(
                    "Parse err line %d state %d: failed to find bone XYZ position"
                    % (d + 2, parse_state))
                core.MY_PRINT_FUNC("line = '%s'" % line)
                raise RuntimeError()
            pos = m.group(1, 2, 3)  # get all 3 components
            temp_pos = [float(f) for f in pos]  # convert strings to floats
            parse_state = 22  # next look for quaternion rotation

        elif parse_state == 22:  # 22 = boneC, xyzw quaternion rotation
            m = f4_re.match(line)  # regex match from beginning of line
            if m is None:
                core.MY_PRINT_FUNC(
                    "Parse err line %d state %d: failed to find bone XYZW rotation"
                    % (d + 2, parse_state))
                core.MY_PRINT_FUNC("line = '%s'" % line)
                raise RuntimeError()
            quat = m.group(1, 2, 3, 4)  # get all 4 components
            quat = [float(f) for f in quat]  # convert strings to floats
            quat.insert(
                0, quat.pop(-1))  # WXYZW -> XYZW, AKA move tail (w) to head
            temp_rot = core.quaternion_to_euler(
                quat)  # convert quaternion to euler angles
            parse_state = 23  # next look for closing curly

        elif parse_state == 23:  # 23 = boneD, closing curly
            m = close_re.match(line)  # regex match from beginning of line
            if m is None:
                core.MY_PRINT_FUNC(
                    "Parse err line %d state %d: bone item not properly closed"
                    % (d + 2, parse_state))
                core.MY_PRINT_FUNC("line = '%s'" % line)
                raise RuntimeError()
            # finish the bone-obj and add to VMD structure
            # this_boneframe = [bname_str, f, xp, yp, zp, xrot, yrot, zrot, phys_off, x_ax, y_ax, z_ax, r_ax, x_ay, y_ay,
            # 				  z_ay, r_ay, x_bx, y_bx, z_bx, r_bx, x_by, y_by, z_by, r_by]
            newframe = vmdstruct.VmdBoneFrame(
                name=temp_name,
                f=0,
                pos=temp_pos,
                rot=list(temp_rot),
                phys_off=False,
                interp=list(core.bone_interpolation_default_linear))
            vmd_boneframes.append(newframe)
            if len(vmd_boneframes) == num_bones:
                parse_state = 30  # if i got all the bones i expected, move to morphs
            else:
                parse_state = 20  # otherwise, get another bone

        elif parse_state == 30:  # 30 = morphA, name
            m = morph_re.match(line)  # regex match from beginning of line
            if m is None:
                core.MY_PRINT_FUNC(
                    "Parse err line %d state %d: failed to find morph name" %
                    (d + 2, parse_state))
                core.MY_PRINT_FUNC("line = '%s'" % line)
                raise RuntimeError()
            idx, name = m.group(1, 2)  # get idx and name
            temp_name = name
            # can i use idx for anything? or is it totally useless?
            parse_state = 31  # next look for value

        elif parse_state == 31:  # 31 = morphB, value
            m = f1_re.match(line)  # regex match from beginning of line
            if m is None:
                core.MY_PRINT_FUNC(
                    "Parse err line %d state %d: failed to find morph value" %
                    (d + 2, parse_state))
                core.MY_PRINT_FUNC("line = '%s'" % line)
                raise RuntimeError()
            v = m.group(1)  # get value
            temp_value = float(v)  # convert strings to floats
            parse_state = 32  # next look for close

        elif parse_state == 32:  # 32 = morphC, closing curly
            m = close_re.match(line)  # regex match from beginning of line
            if m is None:
                core.MY_PRINT_FUNC(
                    "Parse err line %d state %d: morph item not properly closed"
                    % (d + 2, parse_state))
                core.MY_PRINT_FUNC("line = '%s'" % line)
                raise RuntimeError()
            # finish the morph-obj and add to VMD structure
            # morphframe_list.append([mname_str, f, v])
            newframe = vmdstruct.VmdMorphFrame(name=temp_name,
                                               f=0,
                                               val=temp_value)
            vmd_morphframes.append(newframe)
            parse_state = 30  # loop morphs until end-of-file

        else:
            core.MY_PRINT_FUNC("this should not happen, err & die")
            raise RuntimeError()

    if moreinfo:
        core.MY_PRINT_FUNC("...# of morphframes         = %d" %
                           len(vmd_morphframes))

    # verify we did not hit end-of-file unexpectedly, looking-for-morphA is only valid ending state
    if parse_state != 30:
        core.MY_PRINT_FUNC("Parse err state %d: hit end-of-file unexpectedly" %
                           parse_state)
        raise RuntimeError()

    # after hitting end-of-file, assemble the parts of the final returnable VMD-list thing
    # builds object 	(header, boneframe_list, morphframe_list, camframe_list, lightframe_list, shadowframe_list, ikdispframe_list)
    vmd_retme = vmdstruct.Vmd(
        vmdstruct.VmdHeader(version=2, modelname=temp_title), vmd_boneframes,
        vmd_morphframes, list(), list(), list(), list())

    core.MY_PRINT_FUNC("Done reading VPD file '%s'" % cleanname)

    return vmd_retme
Exemple #4
0
def main(moreinfo=True):
    # prompt PMX name
    core.MY_PRINT_FUNC("Please enter name of PMX input file:")
    input_filename_pmx = core.MY_FILEPROMPT_FUNC(".pmx")
    pmx = pmxlib.read_pmx(input_filename_pmx, moreinfo=moreinfo)
    # get bones
    realbones = pmx.bones
    # then, make 2 lists: one starting from jp_righttoe, one starting from jp_lefttoe
    # start from each "toe" bone (names are known), go parent-find-parent-find until reaching no-parent
    bonechain_r = build_bonechain(realbones, jp_righttoe)
    bonechain_l = build_bonechain(realbones, jp_lefttoe)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    # output array
    ikframe_list = []

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

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

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

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

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

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

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

    core.MY_PRINT_FUNC("Done!")
    return None