コード例 #1
0
ファイル: test_utils.py プロジェクト: miu200521358/vmd_sizing
    def test_separate(self):
        MLogger.initialize(level=MLogger.TEST, is_file=True)
        logger = MLogger(__name__, level=MLogger.TEST)

        # motion = VmdReader("D:\\MMD\\MikuMikuDance_v926x64\\UserFile\\Motion\\ダンス_1人\\桃源恋歌配布用motion moka\\ノーマルTda式用0-2000.vmd").read_data()
        model = PmxReader(
            "D:\\MMD\\MikuMikuDance_v926x64\\UserFile\\Model\\VOCALOID\\初音ミク\\Tda式初音ミク・アペンドVer1.10\\Tda式初音ミク・アペンド_Ver1.10.pmx",
            is_check=False).read_data()

        bone_axis_dict = {}
        for bone_name in ["左ひじ", "右ひじ"]:
            local_x_axis = model.get_local_x_axis("左ひじ")
            local_z_axis = MVector3D(0, 0, -1)
            local_y_axis = MVector3D.crossProduct(local_x_axis,
                                                  local_z_axis).normalized()
            bone_axis_dict[bone_name] = {
                "x": local_x_axis,
                "y": local_y_axis,
                "z": local_z_axis
            }

        new_ik_qq = MQuaternion.fromEulerAngles(24.58152072747821,
                                                135.9182003500461,
                                                56.36785502950723)
        ik_bone = model.bones["左ひじ"]
        fno = 394

        x_qq, y_qq, z_qq, yz_qq = MServiceUtils.separate_local_qq(
            fno, ik_bone.name, new_ik_qq, bone_axis_dict[ik_bone.name]["x"])

        logger.debug(
            f"now: {new_ik_qq.toEulerAngles()} -> {(y_qq * x_qq * z_qq).toEulerAngles()}"
        )
        logger.debug(
            f"now: x: {x_qq.toDegree()}, y: {y_qq.toDegree()}, z: {z_qq.toDegree()}"
        )

        for (x_sign, y_sign,
             z_sign) in list(itertools.product((1, -1), (1, -1), (1, -1))):
            new_x_qq = MQuaternion.fromAxisAndAngle(x_qq.vector(),
                                                    x_qq.toDegree() * x_sign)
            new_y_qq = MQuaternion.fromAxisAndAngle(y_qq.vector(),
                                                    y_qq.toDegree() * y_sign)
            new_z_qq = MQuaternion.fromAxisAndAngle(z_qq.vector(),
                                                    z_qq.toDegree() * z_sign)

            logger.debug(
                f"x: {x_sign}, y: {y_sign}, z: {z_sign} -> {(new_y_qq * new_x_qq * new_z_qq).toEulerAngles()}"
            )

        self.assertTrue(True)
コード例 #2
0
    def split_bf(self, fno: int, bf: VmdBoneFrame, local_x_axis: MVector3D,
                 bone_name: str, rrxbn: str, rrybn: str, rrzbn: str,
                 rmxbn: str, rmybn: str, rmzbn: str):
        motion = self.options.motion
        model = self.options.model

        if model.bones[bone_name].getRotatable():
            # 回転を分ける
            if local_x_axis:
                # ローカルX軸がある場合
                x_qq, y_qq, z_qq, _ = MServiceUtils.separate_local_qq(
                    fno, bone_name, bf.rotation, local_x_axis)
            else:
                # ローカルX軸の指定が無い場合、グローバルで分ける
                euler = bf.rotation.toEulerAngles()
                x_qq = MQuaternion.fromEulerAngles(euler.x(), 0, 0)
                y_qq = MQuaternion.fromEulerAngles(0, euler.y(), 0)
                z_qq = MQuaternion.fromEulerAngles(0, 0, euler.z())

            logger.debug(
                f"fno: {fno}, x_qq: {x_qq.toEulerAngles4MMD().to_log()}, y_qq: {y_qq.toEulerAngles4MMD().to_log()}, z_qq: {z_qq.toEulerAngles4MMD().to_log()}"
            )

            if len(rrybn) > 0:
                ry_bf = motion.calc_bf(rrybn, fno)
                ry_bf.rotation = y_qq * ry_bf.rotation
                motion.regist_bf(ry_bf, ry_bf.name, fno)
                # 減算
                bf.rotation *= y_qq.inverted()

            if len(rrxbn) > 0:
                rx_bf = motion.calc_bf(rrxbn, fno)
                rx_bf.rotation = x_qq * rx_bf.rotation
                motion.regist_bf(rx_bf, rx_bf.name, fno)
                # 減算
                bf.rotation *= x_qq.inverted()

            if len(rrzbn) > 0:
                rz_bf = motion.calc_bf(rrzbn, fno)
                rz_bf.rotation = z_qq * rz_bf.rotation
                motion.regist_bf(rz_bf, rz_bf.name, fno)
                # 減算
                bf.rotation *= z_qq.inverted()

            if len(rrxbn) > 0 and len(rrybn) > 0 and len(rrzbn) > 0:
                bf.rotation = MQuaternion()
                motion.regist_bf(bf, bf.name, fno)

        if model.bones[bone_name].getTranslatable():
            # 移動を分ける
            if len(rmxbn) > 0:
                mx_bf = motion.calc_bf(rmxbn, fno)
                mx_bf.position.setX(mx_bf.position.x() + bf.position.x())
                motion.regist_bf(mx_bf, mx_bf.name, fno)
                # 減算
                bf.position.setX(0)

            if len(rmybn) > 0:
                my_bf = motion.calc_bf(rmybn, fno)
                my_bf.position.setY(my_bf.position.y() + bf.position.y())
                motion.regist_bf(my_bf, my_bf.name, fno)
                # 減算
                bf.position.setY(0)

            if len(rmzbn) > 0:
                mz_bf = motion.calc_bf(rmzbn, fno)
                mz_bf.position.setZ(mz_bf.position.z() + bf.position.z())
                motion.regist_bf(mz_bf, mz_bf.name, fno)
                # 減算
                bf.position.setZ(0)

            if len(rmxbn) > 0 and len(rmybn) > 0 and len(rmzbn) > 0:
                bf.position = MVector3D()
                motion.regist_bf(bf, bf.name, fno)
コード例 #3
0
    def convert_multi_split(self, bone_name: str, rrxbn: str, rrybn: str,
                            rrzbn: str, rmxbn: str, rmybn: str, rmzbn: str,
                            center_mx: str, center_my: str, center_mz: str):
        logger.info("多段分割【%s】", bone_name, decoration=MLogger.DECORATION_LINE)

        motion = self.options.motion
        model = self.options.model

        # 事前に変化量全打ち
        if bone_name == "センター" or bone_name == "グルーブ":
            fnos = self.prev_motion.get_differ_fnos(0, ["センター", "グルーブ"],
                                                    limit_degrees=70,
                                                    limit_length=1)
        else:
            fnos = self.prev_motion.get_differ_fnos(0, [bone_name],
                                                    limit_degrees=70,
                                                    limit_length=1)

        if len(fnos) == 0:
            return

        prev_sep_fno = 0
        for fno in fnos:
            # 一度そのままキーを登録
            motion.regist_bf(motion.calc_bf(bone_name, fno), bone_name, fno)
            # 補間曲線のため、もう一度取得しなおし
            bf = motion.calc_bf(bone_name, fno)

            if model.bones[bone_name].getRotatable():
                rx_bf = motion.calc_bf(rrxbn, fno)
                motion.copy_interpolation(bf, rx_bf, MBezierUtils.BZ_TYPE_R)
                motion.regist_bf(rx_bf,
                                 rx_bf.name,
                                 fno,
                                 copy_interpolation=True)

                ry_bf = motion.calc_bf(rrybn, fno)
                motion.copy_interpolation(bf, ry_bf, MBezierUtils.BZ_TYPE_R)
                motion.regist_bf(ry_bf,
                                 ry_bf.name,
                                 fno,
                                 copy_interpolation=True)

                rz_bf = motion.calc_bf(rrzbn, fno)
                motion.copy_interpolation(bf, rz_bf, MBezierUtils.BZ_TYPE_R)
                motion.regist_bf(rz_bf,
                                 rz_bf.name,
                                 fno,
                                 copy_interpolation=True)

            if model.bones[bone_name].getTranslatable():
                mx_bf = motion.calc_bf(rmxbn, fno)
                motion.copy_interpolation(bf, mx_bf, MBezierUtils.BZ_TYPE_MX)
                motion.regist_bf(mx_bf,
                                 mx_bf.name,
                                 fno,
                                 copy_interpolation=True)

                my_bf = motion.calc_bf(rmybn, fno)
                motion.copy_interpolation(bf, my_bf, MBezierUtils.BZ_TYPE_MY)
                motion.regist_bf(my_bf,
                                 my_bf.name,
                                 fno,
                                 copy_interpolation=True)

                mz_bf = motion.calc_bf(rmzbn, fno)
                motion.copy_interpolation(bf, mz_bf, MBezierUtils.BZ_TYPE_MZ)
                motion.regist_bf(mz_bf,
                                 mz_bf.name,
                                 fno,
                                 copy_interpolation=True)

            if fno // 500 > prev_sep_fno and fnos[-1] > 0:
                logger.info("-- %sフレーム目:終了(%s%)【キーフレ追加 - %s】", fno,
                            round((fno / fnos[-1]) * 100, 3), bone_name)
                prev_sep_fno = fno // 500

        logger.info("分割準備完了【%s】",
                    bone_name,
                    decoration=MLogger.DECORATION_LINE)

        # ローカルX軸
        local_x_axis = model.bones[bone_name].local_x_vector
        if local_x_axis == MVector3D(1, 0, 0) or local_x_axis == MVector3D():
            # 指定が無い場合、腕系はローカルX軸、それ以外はノーマル
            if "腕" in bone_name or "ひじ" in bone_name or "手首" in bone_name:
                local_x_axis = model.get_local_x_axis(bone_name)
            else:
                local_x_axis = None
        logger.debug(f"{bone_name}, local_x_axis: {local_x_axis}")

        prev_sep_fno = 0
        for fno in fnos:
            bf = motion.calc_bf(bone_name, fno)

            # 多段分割
            self.split_bf(fno, bf, local_x_axis, bone_name, rrxbn, rrybn,
                          rrzbn, rmxbn, rmybn, rmzbn)

            if fno // 500 > prev_sep_fno and fnos[-1] > 0:
                logger.info("-- %sフレーム目:終了(%s%)【多段分割 - %s】", fno,
                            round((fno / fnos[-1]) * 100, 3), bone_name)
                prev_sep_fno = fno // 500

        check_fnos = []
        check_prev_next_fnos = {}

        # 分離後に乖離起こしてないかチェック
        for fno_idx, (prev_fno,
                      next_fno) in enumerate(zip(fnos[:-1], fnos[1:])):
            fno = int(prev_fno + ((next_fno - prev_fno) / 2))
            if fno not in fnos:
                check_fnos.append(fno)
                check_prev_next_fnos[fno] = {
                    "prev": prev_fno,
                    "next": next_fno
                }

        check_fnos = list(sorted(list(set(check_fnos))))
        logger.debug("bone_name: %s, check_fnos: %s", bone_name, check_fnos)

        prev_sep_fno = 0
        for fno in check_fnos:
            is_subdiv = False
            prev_motion_bf = self.prev_motion.calc_bf(bone_name, fno).copy()

            if model.bones[bone_name].getRotatable():
                # 回転を分ける
                if local_x_axis:
                    # ローカルX軸がある場合
                    x_qq, y_qq, z_qq, _ = MServiceUtils.separate_local_qq(
                        fno, bone_name, prev_motion_bf.rotation, local_x_axis)
                else:
                    # ローカルX軸の指定が無い場合、グローバルで分ける
                    euler = prev_motion_bf.rotation.toEulerAngles()
                    x_qq = MQuaternion.fromEulerAngles(euler.x(), 0, 0)
                    y_qq = MQuaternion.fromEulerAngles(0, euler.y(), 0)
                    z_qq = MQuaternion.fromEulerAngles(0, 0, euler.z())

                if len(rrxbn) > 0:
                    rx_bf = motion.calc_bf(rrxbn, fno)
                    dot = MQuaternion.dotProduct(x_qq.normalized(),
                                                 rx_bf.rotation.normalized())
                    if dot < 0.98:
                        is_subdiv = True

                if len(rrybn) > 0:
                    ry_bf = motion.calc_bf(rrybn, fno)
                    dot = MQuaternion.dotProduct(y_qq.normalized(),
                                                 ry_bf.rotation.normalized())
                    if dot < 0.98:
                        is_subdiv = True

                if len(rrzbn) > 0:
                    rz_bf = motion.calc_bf(rrzbn, fno)
                    dot = MQuaternion.dotProduct(z_qq.normalized(),
                                                 rz_bf.rotation.normalized())
                    if dot < 0.98:
                        is_subdiv = True

            if model.bones[bone_name].getTranslatable():
                if len(center_mx) > 0 or len(center_my) > 0 or len(
                        center_mz) > 0:
                    # センターとグルーブを両方分割してる場合
                    prev_center_motion_bf = self.prev_motion.calc_bf(
                        "センター", fno).copy()
                    if len(center_mx) > 0 and rmxbn == center_mx:
                        prev_motion_bf.position.setX(
                            prev_motion_bf.position.x() +
                            prev_center_motion_bf.position.x())
                    if len(center_my) > 0 and rmybn == center_my:
                        prev_motion_bf.position.setY(
                            prev_motion_bf.position.y() +
                            prev_center_motion_bf.position.y())
                    if len(center_mz) > 0 and rmzbn == center_mz:
                        prev_motion_bf.position.setZ(
                            prev_motion_bf.position.z() +
                            prev_center_motion_bf.position.z())

                # 移動を分ける
                if len(rmxbn) > 0:
                    mx_bf = motion.calc_bf(rmxbn, fno)
                    if np.diff(
                        [mx_bf.position.x(),
                         prev_motion_bf.position.x()]) > 0.1:
                        is_subdiv = True

                if len(rmybn) > 0:
                    my_bf = motion.calc_bf(rmybn, fno)
                    if np.diff(
                        [my_bf.position.y(),
                         prev_motion_bf.position.y()]) > 0.1:
                        is_subdiv = True

                if len(rmzbn) > 0:
                    mz_bf = motion.calc_bf(rmzbn, fno)
                    if np.diff(
                        [mz_bf.position.z(),
                         prev_motion_bf.position.z()]) > 0.1:
                        is_subdiv = True

            if is_subdiv:
                # 細分化ONの場合、更に分割する
                if model.bones[bone_name].getRotatable():
                    if len(rrxbn) > 0:
                        motion.regist_bf(self.prev_motion.calc_bf(rrxbn, fno),
                                         rrxbn, fno)
                    if len(rrybn) > 0:
                        motion.regist_bf(self.prev_motion.calc_bf(rrybn, fno),
                                         rrybn, fno)
                    if len(rrzbn) > 0:
                        motion.regist_bf(self.prev_motion.calc_bf(rrzbn, fno),
                                         rrzbn, fno)

                if model.bones[bone_name].getTranslatable():
                    if len(rmxbn) > 0:
                        motion.regist_bf(self.prev_motion.calc_bf(rmxbn, fno),
                                         rmxbn, fno)
                    if len(rmybn) > 0:
                        motion.regist_bf(self.prev_motion.calc_bf(rmybn, fno),
                                         rmybn, fno)
                    if len(rmzbn) > 0:
                        motion.regist_bf(self.prev_motion.calc_bf(rmzbn, fno),
                                         rmzbn, fno)

                # 分割前の値を再登録
                motion.regist_bf(self.prev_motion.calc_bf(bone_name, fno),
                                 bone_name, fno)
                subdiv_bf = motion.calc_bf(bone_name, fno)

                if bone_name == "グルーブ" and (len(center_mx) > 0
                                            or len(center_my) > 0
                                            or len(center_mz) > 0):
                    prev_center_motion_bf = self.prev_motion.calc_bf(
                        "センター", fno)
                    if len(center_mx) > 0 and rmxbn == center_mx:
                        subdiv_bf.position.setX(
                            subdiv_bf.position.x() +
                            prev_center_motion_bf.position.x())
                    if len(center_my) > 0 and rmybn == center_my:
                        subdiv_bf.position.setY(
                            subdiv_bf.position.y() +
                            prev_center_motion_bf.position.y())
                    if len(center_mz) > 0 and rmzbn == center_mz:
                        subdiv_bf.position.setZ(
                            subdiv_bf.position.z() +
                            prev_center_motion_bf.position.z())

                # 多段分割
                self.split_bf(fno, subdiv_bf, local_x_axis, bone_name, rrxbn,
                              rrybn, rrzbn, rmxbn, rmybn, rmzbn)

                # prev_fno = check_prev_next_fnos[fno]["prev"]
                # next_fno = check_prev_next_fnos[fno]["next"]

                # logger.info(f"-- 軌跡ズレ防止のため、「{bone_name}」の{prev_fno}F~{next_fno}F間を細分化・不要キー除去します")

                # for f in range(prev_fno, next_fno + 1):

                # # 区間内を初期登録
                # if model.bones[bone_name].getRotatable():
                #     # 回転を分ける
                #     if local_x_axis:
                #         # ローカルX軸がある場合
                #         x_qq, y_qq, z_qq, _ = MServiceUtils.separate_local_qq(f, bone_name, prev_motion_bf.rotation, local_x_axis)
                #     else:
                #         # ローカルX軸の指定が無い場合、グローバルで分ける
                #         euler = prev_motion_bf.rotation.toEulerAngles()
                #         x_qq = MQuaternion.fromEulerAngles(euler.x(), 0, 0)
                #         y_qq = MQuaternion.fromEulerAngles(0, euler.y(), 0)
                #         z_qq = MQuaternion.fromEulerAngles(0, 0, euler.z())

                #     if len(rrxbn) > 0:
                #         prev_rx_bf = self.prev_motion.calc_bf(rrxbn, f).copy()
                #         prev_rx_bf.rotation = x_qq
                #         motion.regist_bf(prev_rx_bf, rrxbn, f)

                #     if len(rrybn) > 0:
                #         prev_ry_bf = self.prev_motion.calc_bf(rrybn, f).copy()
                #         prev_ry_bf.rotation = y_qq
                #         motion.regist_bf(prev_ry_bf, rrybn, f)

                #     if len(rrzbn) > 0:
                #         prev_rz_bf = self.prev_motion.calc_bf(rrzbn, f).copy()
                #         prev_rz_bf.rotation = z_qq
                #         motion.regist_bf(prev_rz_bf, rrzbn, f)

                # if model.bones[bone_name].getTranslatable():
                #     if len(center_mx) > 0 or len(center_my) > 0 or len(center_mz) > 0:
                #         # センターとグルーブを両方分割してる場合
                #         prev_center_motion_bf = self.prev_motion.calc_bf("センター", fno).copy()
                #         if len(center_mx) > 0 and rmxbn == center_mx:
                #             prev_motion_bf.position.setX(prev_motion_bf.position.x() + prev_center_motion_bf.position.x())
                #         if len(center_my) > 0 and rmybn == center_my:
                #             prev_motion_bf.position.setY(prev_motion_bf.position.y() + prev_center_motion_bf.position.y())
                #         if len(center_mz) > 0 and rmzbn == center_mz:
                #             prev_motion_bf.position.setZ(prev_motion_bf.position.z() + prev_center_motion_bf.position.z())

                #     if len(rmxbn) > 0:
                #         prev_mx_bf = self.prev_motion.calc_bf(rmxbn, f).copy()
                #         prev_mx_bf.position.setX(prev_motion_bf.position.x())
                #         motion.regist_bf(prev_mx_bf, rmxbn, f)

                #     if len(rmybn) > 0:
                #         prev_my_bf = self.prev_motion.calc_bf(rmybn, f).copy()
                #         prev_my_bf.position.setY(prev_motion_bf.position.y())
                #         motion.regist_bf(prev_my_bf, rmybn, f)

                #     if len(rmzbn) > 0:
                #         prev_mz_bf = self.prev_motion.calc_bf(rmzbn, f).copy()
                #         prev_mz_bf.position.setZ(prev_motion_bf.position.z())
                #         motion.regist_bf(prev_mz_bf, rmzbn, f)

                # # 不要キー削除
                # futures = []
                # with ThreadPoolExecutor(thread_name_prefix="remove", max_workers=self.options.max_workers) as executor:
                #     if model.bones[bone_name].getRotatable():
                #         if len(rrxbn) > 0:
                #             futures.append(executor.submit(self.remove_unnecessary_bf, rrxbn, start_fno=prev_fno, end_fno=next_fno))
                #         if len(rrybn) > 0:
                #             futures.append(executor.submit(self.remove_unnecessary_bf, rrybn, start_fno=prev_fno, end_fno=next_fno))
                #         if len(rrzbn) > 0:
                #             futures.append(executor.submit(self.remove_unnecessary_bf, rrzbn, start_fno=prev_fno, end_fno=next_fno))

                #     if model.bones[bone_name].getTranslatable():
                #         if len(rmxbn) > 0:
                #             futures.append(executor.submit(self.remove_unnecessary_bf, rmxbn, start_fno=prev_fno, end_fno=next_fno))
                #         if len(rmybn) > 0:
                #             futures.append(executor.submit(self.remove_unnecessary_bf, rmybn, start_fno=prev_fno, end_fno=next_fno))
                #         if len(rmzbn) > 0:
                #             futures.append(executor.submit(self.remove_unnecessary_bf, rmzbn, start_fno=prev_fno, end_fno=next_fno))

                # concurrent.futures.wait(futures, timeout=None, return_when=concurrent.futures.FIRST_EXCEPTION)

                # for f in futures:
                #     if not f.result():
                #         return False

            if fno // 1000 > prev_sep_fno and fnos[-1] > 0:
                logger.count(f"【分割後チェック - {bone_name}】", fno, fnos)
                prev_sep_fno = fno // 1000

        logger.info("分割完了【%s】", bone_name, decoration=MLogger.DECORATION_LINE)

        # 元のボーン削除
        if rrxbn != bone_name and rrybn != bone_name and rrzbn != bone_name and rmxbn != bone_name and rmybn != bone_name and rmzbn != bone_name:
            del motion.bones[bone_name]

        # # 跳ねてるの除去
        # futures = []
        # with ThreadPoolExecutor(thread_name_prefix="smooth", max_workers=self.options.max_workers) as executor:
        #     if model.bones[bone_name].getRotatable():
        #         if len(rrxbn) > 0:
        #             futures.append(executor.submit(self.smooth_bf, rrxbn))
        #         if len(rrybn) > 0:
        #             futures.append(executor.submit(self.smooth_bf, rrybn))
        #         if len(rrzbn) > 0:
        #             futures.append(executor.submit(self.smooth_bf, rrzbn))

        #     if model.bones[bone_name].getTranslatable():
        #         if len(rmxbn) > 0:
        #             futures.append(executor.submit(self.smooth_bf, rmxbn))
        #         if len(rmybn) > 0:
        #             futures.append(executor.submit(self.smooth_bf, rmybn))
        #         if len(rmzbn) > 0:
        #             futures.append(executor.submit(self.smooth_bf, rmzbn))

        # concurrent.futures.wait(futures, timeout=None, return_when=concurrent.futures.FIRST_EXCEPTION)

        # for f in futures:
        #     if not f.result():
        #         return False

        return True
コード例 #4
0
    def convert_target_ik2fk(self, ik_bone: Bone):
        motion = self.options.motion
        model = self.options.model
        bone_name = ik_bone.name

        logger.info("-- 腕IK変換準備:開始【%s】", bone_name)

        # モデルのIKボーンのリンク
        target_links = model.create_link_2_top_one(bone_name, is_defined=False)

        # モデルの人差し指先ボーンのリンク
        finger_bone_name = "{0}人指先実体".format(bone_name[0])
        finger2_bone_name = "{0}小指先実体".format(bone_name[0])
        wrist_bone_name = "{0}手首".format(bone_name[0])
        finger_links = model.create_link_2_top_one(finger_bone_name, is_defined=False)
        finger2_links = model.create_link_2_top_one(finger2_bone_name, is_defined=False)

        # モデルのIKリンク
        if not (ik_bone.ik.target_index in model.bone_indexes and model.bone_indexes[ik_bone.ik.target_index] in model.bones):
            raise SizingException("{0} のTargetが有効なINDEXではありません。PMXの構造を確認してください。".format(bone_name))

        # IKエフェクタ
        effector_bone_name = model.bone_indexes[ik_bone.ik.target_index]
        effector_links = model.create_link_2_top_one(effector_bone_name, is_defined=False)
        ik_links = BoneLinks()

        # 末端にエフェクタ
        effector_bone = model.bones[effector_bone_name].copy()
        effector_bone.degree_limit = math.degrees(ik_bone.ik.limit_radian)
        ik_links.append(effector_bone)

        for ik_link in ik_bone.ik.link:
            # IKリンクを末端から順に追加
            if not (ik_link.bone_index in model.bone_indexes and model.bone_indexes[ik_link.bone_index] in model.bones):
                raise SizingException("{0} のLinkに無効なINDEXが含まれています。PMXの構造を確認してください。".format(bone_name))
            
            link_bone = model.bones[model.bone_indexes[ik_link.bone_index]].copy()

            if link_bone.fixed_axis != MVector3D():
                # 捩り系は無視
                continue

            # 単位角
            link_bone.degree_limit = math.degrees(ik_bone.ik.limit_radian)

            # # 角度制限
            # if ik_link.limit_angle == 1:
            #     link_bone.limit_min = ik_link.limit_min
            #     link_bone.limit_max = ik_link.limit_max

            ik_links.append(link_bone)

        # 回転移管先ボーン
        transferee_bone = self.get_transferee_bone(ik_bone, effector_bone)

        # 移管先ボーンのローカル軸
        transferee_local_x_axis = model.get_local_x_axis(transferee_bone.name)

        # 差異の大きい箇所にFKキーフレ追加
        fnos = motion.get_differ_fnos(0, [bone_name], limit_degrees=20, limit_length=0.5)

        prev_sep_fno = 0
        for fno in fnos:
            for link_name in list(ik_links.all().keys())[1:]:
                bf = motion.calc_bf(link_name, fno)
                motion.regist_bf(bf, link_name, fno)

            if fno // 500 > prev_sep_fno and fnos[-1] > 0:
                logger.count(f"【キーフレ追加 - {bone_name}】", fno, fnos)
                prev_sep_fno = fno // 500

        logger.info("-- 腕IK変換準備:終了【%s】", bone_name)

        org_motion = motion.copy()

        # 元モーションを保持したら、IKキーフレ削除
        del motion.bones[bone_name]

        for fno in fnos:
            # グローバル位置計算(元モーションの位置)
            target_ik_global_3ds = MServiceUtils.calc_global_pos(model, target_links, org_motion, fno)
            finger_global_3ds = MServiceUtils.calc_global_pos(model, finger_links, org_motion, fno)
            finger2_global_3ds = MServiceUtils.calc_global_pos(model, finger2_links, org_motion, fno)
            target_effector_pos = target_ik_global_3ds[bone_name]

            prev_diff = MVector3D()

            org_bfs = {}
            for link_name in list(ik_links.all().keys())[1:]:
                # 元モーションの角度で保持
                bf = org_motion.calc_bf(link_name, fno).copy()
                org_bfs[link_name] = bf
                # 今のモーションも前のキーフレをクリアして再セット
                motion.regist_bf(bf, link_name, fno)

            # IK計算実行
            for ik_cnt in range(50):
                MServiceUtils.calc_IK(model, effector_links, motion, fno, target_effector_pos, ik_links, max_count=1)

                # どちらにせよ一旦bf確定
                for link_name in list(ik_links.all().keys())[1:]:
                    ik_bf = motion.calc_bf(link_name, fno)
                    motion.regist_bf(ik_bf, link_name, fno)

                # 現在のエフェクタ位置
                now_global_3ds = MServiceUtils.calc_global_pos(model, effector_links, motion, fno)
                now_effector_pos = now_global_3ds[effector_bone_name]

                # 現在のエフェクタ位置との差分(エフェクタ位置が指定されている場合のみ)
                diff_pos = MVector3D() if target_effector_pos == MVector3D() else target_effector_pos - now_effector_pos

                if prev_diff == MVector3D() or (prev_diff != MVector3D() and diff_pos.length() < prev_diff.length()):
                    if diff_pos.length() < 0.1:
                        logger.debug("☆腕IK変換成功(%s): f: %s(%s), 指定 [%s], 現在[%s], 差異[%s(%s)]", ik_cnt, fno, bone_name, \
                                     target_effector_pos.to_log(), now_effector_pos.to_log(), diff_pos.to_log(), diff_pos.length())

                        # org_bfを保持し直し
                        for link_name in list(ik_links.all().keys())[1:]:
                            bf = motion.calc_bf(link_name, fno).copy()
                            org_bfs[link_name] = bf
                            logger.test("org_bf保持: %s [%s]", link_name, bf.rotation.toEulerAngles().to_log())

                        # そのまま終了
                        break
                    elif prev_diff == MVector3D() or diff_pos.length() < prev_diff.length():
                        logger.debug("☆腕IK変換ちょっと失敗採用(%s): f: %s(%s), 指定 [%s], 現在[%s], 差異[%s(%s)]", ik_cnt, fno, bone_name, \
                                     target_effector_pos.to_log(), now_effector_pos.to_log(), diff_pos.to_log(), diff_pos.length())

                        # org_bfを保持し直し
                        for link_name in list(ik_links.all().keys())[1:]:
                            bf = motion.calc_bf(link_name, fno).copy()
                            org_bfs[link_name] = bf
                            logger.test("org_bf保持: %s [%s]", link_name, bf.rotation.toEulerAngles().to_log())

                        # 前回とまったく同じ場合か、充分に近い場合、IK的に動きがないので終了
                        if prev_diff == diff_pos or np.count_nonzero(np.where(np.abs(diff_pos.data()) > 0.05, 1, 0)) == 0:
                            logger.debug("動きがないので終了")
                            break

                        # 前回差異を保持
                        prev_diff = diff_pos
                    else:
                        logger.debug("★腕IK変換ちょっと失敗不採用(%s): f: %s(%s), 指定 [%s], 現在[%s], 差異[%s(%s)]", ik_cnt, fno, bone_name, \
                                     target_effector_pos.to_log(), now_effector_pos.to_log(), diff_pos.to_log(), diff_pos.length())

                        # 前回とまったく同じ場合か、充分に近い場合、IK的に動きがないので終了
                        if prev_diff == diff_pos or np.count_nonzero(np.where(np.abs(diff_pos.data()) > 0.05, 1, 0)) == 0:
                            break
                else:
                    logger.debug("★腕IK変換失敗(%s): f: %s(%s), 指定 [%s], 現在[%s], 差異[%s(%s)]", ik_cnt, fno, bone_name, \
                                 target_effector_pos.to_log(), now_effector_pos.to_log(), diff_pos.to_log(), diff_pos.length())

                    # 前回とまったく同じ場合か、充分に近い場合、IK的に動きがないので終了
                    if prev_diff == diff_pos or np.count_nonzero(np.where(np.abs(diff_pos.data()) > 0.05, 1, 0)) == 0:
                        logger.debug("動きがないので終了")
                        break
            
            # 最後に成功したところに戻す
            for link_name in list(ik_links.all().keys())[1:]:
                bf = org_bfs[link_name].copy()
                logger.debug("確定bf: %s [%s]", link_name, bf.rotation.toEulerAngles().to_log())
                motion.regist_bf(bf, link_name, fno)
            
            # 指先の現在の位置を再計算
            if finger_links and finger_links.get(finger_bone_name) and finger_links.get(wrist_bone_name) and \
                    finger2_links and finger2_links.get(finger2_bone_name) and finger2_links.get(wrist_bone_name) and transferee_bone.name == wrist_bone_name:
                # 手首がある場合のみ、再計算
                now_finger_global_3ds, now_finger_matrixs = MServiceUtils.calc_global_pos(model, finger_links, motion, fno, return_matrix=True)
                # 人指先のローカル位置
                finger_initial_local_pos = now_finger_matrixs[wrist_bone_name].inverted() * now_finger_global_3ds[finger_bone_name]
                finger_local_pos = now_finger_matrixs[wrist_bone_name].inverted() * finger_global_3ds[finger_bone_name]
                finger_rotation = MQuaternion.rotationTo(finger_initial_local_pos, finger_local_pos)
                logger.debug("finger_rotation: %s [%s]", finger_bone_name, finger_rotation.toEulerAngles().to_log())

                finger_x_qq, finger_y_qq, finger_z_qq, _ = MServiceUtils.separate_local_qq(fno, bone_name, finger_rotation, transferee_local_x_axis)
                logger.debug("finger_x_qq: %s [%s]", finger_bone_name, finger_x_qq.toEulerAngles().to_log())
                logger.debug("finger_y_qq: %s [%s]", finger_bone_name, finger_y_qq.toEulerAngles().to_log())
                logger.debug("finger_z_qq: %s [%s]", finger_bone_name, finger_z_qq.toEulerAngles().to_log())

                # 手首の回転は、手首から見た指先の方向
                transferee_bf = motion.calc_bf(transferee_bone.name, fno)
                transferee_bf.rotation = finger_rotation

                # 捩りなしの状態で一旦登録する
                motion.regist_bf(transferee_bf, transferee_bone.name, fno)

                now_finger2_global_3ds, now_finger2_matrixs = MServiceUtils.calc_global_pos(model, finger2_links, motion, fno, return_matrix=True)
                # 小指先のローカル位置
                finger2_initial_local_pos = now_finger2_matrixs[wrist_bone_name].inverted() * now_finger2_global_3ds[finger2_bone_name]
                finger2_local_pos = now_finger2_matrixs[wrist_bone_name].inverted() * finger2_global_3ds[finger2_bone_name]
                finger2_rotation = MQuaternion.rotationTo(finger2_initial_local_pos, finger2_local_pos)
                logger.debug("finger2_rotation: %s [%s]", finger2_bone_name, finger2_rotation.toEulerAngles().to_log())
                
                finger2_x_qq = MQuaternion.fromAxisAndAngle(transferee_local_x_axis, finger_x_qq.toDegree() + finger2_rotation.toDegree())
                transferee_bf.rotation = finger_y_qq * finger2_x_qq * finger_z_qq
                motion.regist_bf(transferee_bf, transferee_bone.name, fno)
                logger.debug("transferee_qq: %s [%s], x [%s]", transferee_bone.name, transferee_bf.rotation.toEulerAngles().to_log(), finger2_x_qq.toEulerAngles().to_log())

            logger.count("【腕IK変換 - {0}】".format(bone_name), fno, fnos)