Ejemplo n.º 1
0
    def test_replace_morph_03(self):
        motion = VmdMotion()
        motion.morphs = {"A": {}}
        for fno, ratio in [(0, 10), (1, 20), (3, 30), (7, 0), (10, 3)]:
            motion.morphs["A"][fno] = VmdMorphFrame(fno)
            motion.morphs["A"][fno].ratio = ratio

        morph_list = [("A", "B", 0.5), ("A", "C", 2)]

        data_set = MOptionsDataSet(motion, None, None, None, False, False,
                                   morph_list, None, None)

        service = MorphService(None)
        service.replace_morph(0, data_set)

        self.assertEqual(["B", "C"], sorted(list(motion.morphs.keys())))

        self.assertTrue("B" in motion.morphs)
        self.assertEqual([0, 1, 3, 7, 10],
                         sorted(list(motion.morphs["B"].keys())))
        self.assertEqual(5, motion.morphs["B"][0].ratio)
        self.assertEqual(10, motion.morphs["B"][1].ratio)
        self.assertEqual(15, motion.morphs["B"][3].ratio)
        self.assertEqual(0, motion.morphs["B"][7].ratio)
        self.assertEqual(1.5, motion.morphs["B"][10].ratio)

        self.assertTrue("C" in motion.morphs)
        self.assertEqual([0, 1, 3, 7, 10],
                         sorted(list(motion.morphs["C"].keys())))
        self.assertEqual(20, motion.morphs["C"][0].ratio)
        self.assertEqual(40, motion.morphs["C"][1].ratio)
        self.assertEqual(60, motion.morphs["C"][3].ratio)
        self.assertEqual(0, motion.morphs["C"][7].ratio)
        self.assertEqual(6, motion.morphs["C"][10].ratio)
Ejemplo n.º 2
0
def calc_relative_rotation(model: PmxModel,
                           links: BoneLinks,
                           motion: VmdMotion,
                           fno: int,
                           limit_links=None):
    add_qs = []

    for link_idx, link_bone_name in enumerate(links.all()):
        link_bone = links.get(link_bone_name)

        if not limit_links or (limit_links
                               and limit_links.get(link_bone_name)):
            # 上限リンクがある場合、ボーンが存在している場合のみ、モーション内のキー情報を取得
            fill_bf = motion.calc_bf(link_bone.name, fno)
        else:
            # 上限リンクでボーンがない場合、ボーンは初期値
            fill_bf = VmdBoneFrame(fno=fno)
            fill_bf.set_name(link_bone_name)

        # 実際の回転量を計算
        rot = deform_rotation(model, motion, fill_bf)

        add_qs.append(rot)

    return add_qs
Ejemplo n.º 3
0
def calc_relative_position(model: PmxModel,
                           links: BoneLinks,
                           motion: VmdMotion,
                           fno: int,
                           limit_links=None):
    trans_vs = []

    for link_idx, link_bone_name in enumerate(links.all()):
        link_bone = links.get(link_bone_name)

        if not limit_links or (limit_links
                               and limit_links.get(link_bone_name)):
            # 上限リンクがある倍、ボーンが存在している場合のみ、モーション内のキー情報を取得
            fill_bf = motion.calc_bf(link_bone.name, fno)
        else:
            # 上限リンクでボーンがない場合、ボーンは初期値
            fill_bf = VmdBoneFrame(fno=fno)
            fill_bf.set_name(link_bone_name)

        # 位置
        if link_idx == 0:
            # 一番親は、グローバル座標を考慮
            trans_vs.append(link_bone.position + fill_bf.position)
        else:
            # 位置:自身から親の位置を引いた相対位置
            trans_vs.append(link_bone.position + fill_bf.position -
                            links.get(link_bone_name, offset=-1).position)

    return trans_vs
    def prepare_split_stance(self, motion: VmdMotion, target_bone_name: str):
        fnos = motion.get_bone_fnos(target_bone_name)

        for fidx, fno in enumerate(fnos):
            if fidx == 0:
                continue

            prev_bf = motion.bones[target_bone_name][fnos[fidx - 1]]
            bf = motion.bones[target_bone_name][fno]
            diff_degree = abs(prev_bf.rotation.toDegree() -
                              bf.rotation.toDegree())

            if diff_degree >= 150:
                # 回転量が約150度以上の場合、半分に分割しておく
                half_fno = prev_bf.fno + round((bf.fno - prev_bf.fno) / 2)

                if prev_bf.fno < half_fno < bf.fno:
                    # キーが追加できる状態であれば、追加
                    half_bf = motion.calc_bf(target_bone_name, half_fno)
                    motion.regist_bf(half_bf, target_bone_name, half_fno)
Ejemplo n.º 5
0
    def test_replace_morph_01(self):
        motion = VmdMotion()
        motion.morphs = {"A": {}}
        for fno, ratio in [(0, 10), (1, 20), (3, 30), (7, 0), (10, 3)]:
            motion.morphs["A"][fno] = VmdMorphFrame(fno)
            motion.morphs["A"][fno].ratio = ratio

        morph_list = []

        data_set = MOptionsDataSet(motion, None, None, None, False, False,
                                   morph_list, None, None)

        service = MorphService(None)
        service.replace_morph(0, data_set)

        self.assertEqual(["A"], sorted(list(motion.morphs.keys())))

        self.assertTrue("A" in motion.morphs)
        self.assertEqual([0, 1, 3, 7, 10],
                         sorted(list(motion.morphs["A"].keys())))
Ejemplo n.º 6
0
def calc_local_axis(model, bone_name):
    # 定義されていないのも含め全ボーンリンクを取得する
    links = model.create_link_2_top_one(bone_name, is_defined=False)

    # 初期スタンスのボーングローバル位置と行列を取得
    global_3ds_dic, total_mats = MServiceUtils.calc_global_pos(
        model, links, VmdMotion(), 0, return_matrix=True, is_local_x=True)

    # target_mat = MMatrix4x4()
    # target_mat.setToIdentity()

    # # 処理対象行列
    # for n, (lname, mat) in enumerate(total_mats.items()):
    #     target_link = links.get(lname)

    #     if n == 0:
    #         # 最初は行列そのもの
    #         target_mat = mat.copy()
    #     else:
    #         # 2番目以降は行列をかける
    #         target_mat *= mat.copy()

    #     if n > 0:
    #         # ボーン自身にローカル軸が設定されているか
    #         local_x_matrix = MMatrix4x4()
    #         local_x_matrix.setToIdentity()

    #         local_axis_qq = MQuaternion()

    #         if target_link.local_x_vector == MVector3D():
    #             # ローカル軸が設定されていない場合、計算

    #             # 自身から親を引いた軸の向き
    #             local_axis = target_link.position - links.get(lname, offset=-1).position
    #             local_axis_qq = MQuaternion.fromDirection(local_axis.normalized(), MVector3D(0, 0, 1))
    #         else:
    #             # ローカル軸が設定されている場合、その値を採用
    #             local_axis_qq = MQuaternion.fromDirection(target_link.local_x_vector.normalized(), MVector3D(0, 0, 1))

    #         local_x_matrix.rotate(local_axis_qq)

    #         target_mat *= local_x_matrix

    # ワールド座標系から注目ノードの局所座標系への変換
    inv_coord = total_mats[bone_name].inverted()

    # 自身のボーンからの向き先を取得
    axis = model.get_local_x_axis(bone_name)

    # 最終的な対象ボーンのローカル軸の向き
    local_axis = (inv_coord * axis).normalized()

    return local_axis
Ejemplo n.º 7
0
    def test_replace_morph_06(self):
        motion = VmdMotion()
        motion.morphs["A"] = {}
        for fno, ratio in [(0, 10), (1, 20), (3, 30), (7, 0), (10, 3)]:
            motion.morphs["A"][fno] = VmdMorphFrame(fno)
            motion.morphs["A"][fno].ratio = ratio

        motion.morphs["B"] = {}
        for fno, ratio in [(1, 3), (2, 5), (7, 4), (11, 2)]:
            motion.morphs["B"][fno] = VmdMorphFrame(fno)
            motion.morphs["B"][fno].ratio = ratio

        motion.morphs["C"] = {}
        for fno, ratio in [(5, 3), (7, 4), (8, 5)]:
            motion.morphs["C"][fno] = VmdMorphFrame(fno)
            motion.morphs["C"][fno].ratio = ratio

        morph_list = [("A", "B", 2), ("B", "C", 2)]

        data_set = MOptionsDataSet(motion, None, None, None, False, False,
                                   morph_list, None, None)

        service = MorphService(None)
        service.replace_morph(0, data_set)

        self.assertEqual(["B", "C"], sorted(list(motion.morphs.keys())))

        self.assertTrue("B" in motion.morphs)
        self.assertEqual([0, 1, 2, 3, 7, 10, 11],
                         sorted(list(motion.morphs["B"].keys())))
        self.assertEqual(20, motion.morphs["B"][0].ratio)
        self.assertEqual(43, motion.morphs["B"][1].ratio)
        self.assertEqual(5, motion.morphs["B"][2].ratio)
        self.assertEqual(60, motion.morphs["B"][3].ratio)
        self.assertEqual(4, motion.morphs["B"][7].ratio)
        self.assertEqual(6, motion.morphs["B"][10].ratio)
        self.assertEqual(2, motion.morphs["B"][11].ratio)

        self.assertTrue("C" in motion.morphs)
        self.assertEqual([1, 2, 5, 7, 8, 11],
                         sorted(list(motion.morphs["C"].keys())))
        self.assertEqual(6, motion.morphs["C"][1].ratio)
        self.assertEqual(10, motion.morphs["C"][2].ratio)
        self.assertEqual(3, motion.morphs["C"][5].ratio)
        self.assertEqual(12, motion.morphs["C"][7].ratio)
        self.assertEqual(4, motion.morphs["C"][11].ratio)
Ejemplo n.º 8
0
def deform_rotation(model: PmxModel, motion: VmdMotion, bf: VmdBoneFrame):
    if bf.name not in model.bones:
        return MQuaternion()

    bone = model.bones[bf.name]
    rot = bf.rotation.normalized().copy()

    if bone.fixed_axis != MVector3D():
        # 回転角度を求める
        if rot != MQuaternion():
            # 回転補正
            if "右" in bone.name and rot.x() > 0 and bone.fixed_axis.x() <= 0:
                rot.setX(rot.x() * -1)
                rot.setScalar(rot.scalar() * -1)
            elif "左" in bone.name and rot.x() < 0 and bone.fixed_axis.x() >= 0:
                rot.setX(rot.x() * -1)
                rot.setScalar(rot.scalar() * -1)
            # 回転補正(コロン式ミクさん等軸反転パターン)
            elif "右" in bone.name and rot.x() < 0 and bone.fixed_axis.x() > 0:
                rot.setX(rot.x() * -1)
                rot.setScalar(rot.scalar() * -1)
            elif "左" in bone.name and rot.x() > 0 and bone.fixed_axis.x() < 0:
                rot.setX(rot.x() * -1)
                rot.setScalar(rot.scalar() * -1)

            rot.normalize()

        # 軸固定の場合、回転を制限する
        rot = MQuaternion.fromAxisAndAngle(bone.fixed_axis, rot.toDegree())

    if bone.getExternalRotationFlag(
    ) and bone.effect_index in model.bone_indexes:

        effect_parent_bone = bone
        effect_bone = model.bones[model.bone_indexes[bone.effect_index]]
        cnt = 0

        while cnt < 100:
            # 付与親が取得できたら、該当する付与親の回転を取得する
            effect_bf = motion.calc_bf(effect_bone.name, bf.fno)

            # 自身の回転量に付与親の回転量を付与率を加味して付与する
            if effect_parent_bone.effect_factor < 0:
                # マイナス付与の場合、逆回転
                rot = rot * (effect_bf.rotation *
                             abs(effect_parent_bone.effect_factor)).inverted()
            else:
                rot = rot * (effect_bf.rotation *
                             effect_parent_bone.effect_factor)

            if effect_bone.getExternalRotationFlag(
            ) and effect_bone.effect_index in model.bone_indexes:
                # 付与親の親として現在のeffectboneを保持
                effect_parent_bone = effect_bone
                # 付与親置き換え
                effect_bone = model.bones[model.bone_indexes[
                    effect_bone.effect_index]]
            else:
                break

            cnt += 1

    return rot
Ejemplo n.º 9
0
def calc_IK(model: PmxModel,
            links: BoneLinks,
            motion: VmdMotion,
            fno: int,
            target_pos: MVector3D,
            ik_links: BoneLinks,
            max_count=10):
    for bone_name in list(ik_links.all().keys())[1:]:
        # bfをモーションに登録
        bf = motion.calc_bf(bone_name, fno)
        motion.regist_bf(bf, bone_name, fno)

    local_effector_pos = MVector3D()
    local_target_pos = MVector3D()

    for cnt in range(max_count):
        # 規定回数ループ
        for ik_idx, joint_name in enumerate(list(ik_links.all().keys())[1:]):
            # 処理対象IKボーン
            ik_bone = ik_links.get(joint_name)

            # 現在のボーングローバル位置と行列を取得
            global_3ds_dic, total_mats = calc_global_pos(model,
                                                         links,
                                                         motion,
                                                         fno,
                                                         return_matrix=True)

            # エフェクタ(末端)
            global_effector_pos = global_3ds_dic[ik_links.first_name()]

            # 注目ノード(実際に動かすボーン)
            joint_mat = total_mats[joint_name]

            # ワールド座標系から注目ノードの局所座標系への変換
            inv_coord = joint_mat.inverted()

            # 注目ノードを起点とした、エフェクタのローカル位置
            local_effector_pos = inv_coord * global_effector_pos
            local_target_pos = inv_coord * target_pos

            #  (1) 基準関節→エフェクタ位置への方向ベクトル
            basis2_effector = local_effector_pos.normalized()
            #  (2) 基準関節→目標位置への方向ベクトル
            basis2_target = local_target_pos.normalized()

            # ベクトル (1) を (2) に一致させるための最短回転量(Axis-Angle)
            # 回転角
            rotation_dot = MVector3D.dotProduct(basis2_effector, basis2_target)
            # 回転角度
            rotation_radian = math.acos(max(-1, min(1, rotation_dot)))

            if abs(rotation_radian) > 0.0001:
                # 一定角度以上の場合

                # 回転軸
                rotation_axis = MVector3D.crossProduct(
                    basis2_effector, basis2_target).normalized()
                # 回転角度
                rotation_degree = math.degrees(rotation_radian)

                # 関節回転量の補正(最大変位量を制限する)
                correct_qq = MQuaternion.fromAxisAndAngle(
                    rotation_axis, min(rotation_degree, ik_bone.degree_limit))

                # ジョイントに補正をかける
                bf = motion.calc_bf(joint_name, fno)
                new_ik_qq = correct_qq * bf.rotation

                # IK軸制限がある場合、上限下限をチェック
                if ik_bone.ik_limit_min != MVector3D(
                ) and ik_bone.ik_limit_max != MVector3D():
                    x_qq, y_qq, z_qq, yz_qq = separate_local_qq(
                        fno, bone_name, new_ik_qq,
                        model.get_local_x_axis(ik_bone.name))

                    logger.test("new_ik_qq: %s, x_qq: %s, y_qq: %s, z_qq: %s",
                                new_ik_qq.toEulerAngles(),
                                x_qq.toEulerAngles(), y_qq.toEulerAngles(),
                                z_qq.toEulerAngles())
                    logger.test("new_ik_qq: %s, x_qq: %s, y_qq: %s, z_qq: %s",
                                new_ik_qq.toDegree(), x_qq.toDegree(),
                                y_qq.toDegree(), z_qq.toDegree())

                    euler_x = min(
                        ik_bone.ik_limit_max.x(),
                        max(ik_bone.ik_limit_min.x(), x_qq.toDegree()))
                    euler_y = min(
                        ik_bone.ik_limit_max.y(),
                        max(ik_bone.ik_limit_min.y(), y_qq.toDegree()))
                    euler_z = min(
                        ik_bone.ik_limit_max.z(),
                        max(ik_bone.ik_limit_min.z(), z_qq.toDegree()))

                    logger.test(
                        "limit_qq: %s -> %s", new_ik_qq.toEulerAngles(),
                        MQuaternion.fromEulerAngles(euler_x, euler_y,
                                                    euler_z).toEulerAngles())

                    new_ik_qq = MQuaternion.fromEulerAngles(
                        euler_x, euler_y, euler_z)

                bf.rotation = new_ik_qq

        # 位置の差がほとんどない場合、終了
        if (local_effector_pos - local_target_pos).lengthSquared() < 0.0001:
            return

    return
Ejemplo n.º 10
0
    def read_data(self):
        # モーションパス
        motion = VmdMotion()
        motion.path = self.file_path

        try:
            with open(self.file_path, "rb") as f:
                # VMDファイルをバイナリ読み込み
                self.buffer = f.read()

                # vmdバージョン
                signature = self.unpack(30, "30s")
                logger.test("signature %s", signature)

                # モデル名
                model_bname, model_name = self.read_text(20)
                logger.test("model_bname %s, model_name: %s", model_bname,
                            model_name)
                motion.model_name = model_name

                # モーション数
                motion.motion_cnt = self.read_uint(4)
                logger.test("motion.motion_cnt %s", motion.motion_cnt)

                # 1F分のモーション情報

                prev_n = 0
                for n in range(motion.motion_cnt):
                    frame = VmdBoneFrame(0)
                    frame.key = True
                    frame.read = True

                    # ボーン ----------------------
                    # ボーン名
                    bone_bname, bone_name = self.read_text(15)

                    frame.name = bone_name
                    frame.bname = bone_bname
                    logger.test("name: %s, bname %s", bone_name, bone_bname)

                    # フレームIDX
                    frame.fno = self.read_uint(4)
                    logger.test("frame.fno %s", frame.fno)

                    # 位置X,Y,Z
                    frame.position = self.read_Vector3D()
                    logger.test("frame.position %s", frame.position)

                    # 回転X,Y,Z,scalar
                    frame.rotation = self.read_Quaternion()
                    logger.test("frame.rotation %s", frame.rotation)
                    logger.test("frame.rotation.euler %s",
                                frame.rotation.toEulerAngles())
                    # オリジナルを保持
                    frame.org_rotation = frame.rotation.copy()

                    # 補間曲線
                    frame.interpolation = list(self.unpack(64, "64B", True))
                    logger.test("interpolation %s", frame.interpolation)

                    if bone_name not in motion.bones:
                        # まだ辞書にない場合、配列追加
                        motion.bones[bone_name] = {}

                    # 辞書の該当部分にボーンフレームを追加
                    if frame.fno not in motion.bones[bone_name]:
                        motion.bones[bone_name][frame.fno] = frame

                    if frame.fno > motion.last_motion_frame:
                        # 最終フレームを記録
                        motion.last_motion_frame = frame.fno

                    if n // 10000 > prev_n:
                        prev_n = n // 10000
                        logger.info("-- VMDモーション読み込み キー: %s" % n)

                try:
                    # モーフ数
                    motion.morph_cnt = self.read_uint(4)
                    logger.test("motion.morph_cnt %s", motion.morph_cnt)

                    # 1F分のモーフ情報
                    prev_n = 0
                    for n in range(motion.morph_cnt):
                        morph = VmdMorphFrame()
                        morph.key = True
                        morph.read = True

                        # モーフ ----------------------
                        # モーフ名
                        morph_bname, morph_name = self.read_text(15)

                        morph.name = morph_name
                        morph.bname = morph_bname
                        logger.test("name: %s, bname %s", morph_name,
                                    morph_bname)

                        # フレームIDX
                        morph.fno = self.read_uint(4)
                        logger.test("morph.fno %s", morph.fno)

                        # 度数
                        morph.ratio = self.read_float(4)
                        logger.test("morph.ratio %s", morph.ratio)

                        if morph_name not in motion.morphs:
                            # まだ辞書にない場合、配列追加
                            motion.morphs[morph_name] = {}

                        if morph.fno not in motion.morphs[morph_name]:
                            # まだなければ辞書の該当部分にモーフフレームを追加
                            motion.morphs[morph_name][morph.fno] = morph

                        if n // 1000 > prev_n:
                            prev_n = n // 1000
                            logger.info("-- VMDモーション読み込み モーフ: %s" % n)
                except Exception:
                    # 情報がない場合、catchして握りつぶす
                    motion.morph_cnt = 0

                try:
                    # カメラ数
                    motion.camera_cnt = self.read_uint(4)
                    logger.test("motion.camera_cnt %s", motion.camera_cnt)

                    # 1F分のカメラ情報
                    prev_n = 0
                    for n in range(motion.camera_cnt):
                        camera = VmdCameraFrame()

                        # フレームIDX
                        camera.fno = self.read_uint(4)
                        logger.test("camera.fno %s", camera.fno)

                        # 距離
                        camera.length = self.read_float(4)
                        logger.test("camera.length %s", camera.length)

                        # 0距離の場合、念のため少しだけ距離を入れておく
                        if camera.length == 0:
                            camera.length = -0.00001

                        # 位置X,Y,Z
                        camera.position = self.read_Vector3D()
                        logger.test("camera.position %s", camera.position)

                        # 角度(オイラー角)
                        camera.euler = self.read_Vector3D()
                        logger.test("camera.euler %s", camera.euler)

                        # 補間曲線
                        camera.interpolation = self.unpack(24, "24B", True)
                        logger.test("camera.interpolation %s",
                                    camera.interpolation)

                        # 視野角
                        camera.angle = self.read_uint(4)
                        logger.test("camera.angle %s", camera.angle)

                        # パース有無
                        camera.perspective = self.unpack(1, "B")
                        logger.test("camera.perspective %s",
                                    camera.perspective)

                        # オリジナルを保持
                        camera.org_length = camera.org_length
                        camera.org_position = camera.org_position.copy()

                        # カメラを追加
                        motion.cameras[camera.fno] = camera

                        if n // 10000 > prev_n:
                            prev_n = n // 10000
                            logger.info("VMDカメラ読み込み キー: %s" % n)

                except Exception:
                    # 情報がない場合、catchして握りつぶす
                    motion.camera_cnt = 0

                # 照明数
                try:
                    motion.light_cnt = self.read_uint(4)
                    logger.test("motion.light_cnt %s", motion.light_cnt)

                    # 1F分の照明情報
                    for _ in range(motion.light_cnt):
                        light = VmdLightFrame()

                        # フレームIDX
                        light.fno = self.read_uint(4)
                        logger.test("light.fno %s", light.fno)

                        # 照明色(RGBだが、下手に数値が変わるのも怖いのでV3D)
                        light.color = self.read_Vector3D()
                        logger.test("light.color %s", light.color)

                        # 照明位置
                        light.position = self.read_Vector3D()
                        logger.test("light.position %s", light.position)

                        # 追加
                        motion.lights.append(light)

                except Exception:
                    # 情報がない場合、catchして握りつぶす
                    motion.light_cnt = 0

                # セルフシャドウ数
                try:
                    motion.shadow_cnt = self.read_uint(4)
                    logger.test("motion.shadow_cnt %s", motion.shadow_cnt)

                    # 1F分のシャドウ情報
                    for _ in range(motion.shadow_cnt):
                        shadow = VmdShadowFrame()

                        # フレームIDX
                        shadow.fno = self.read_uint(4)
                        logger.test("shadow.fno %s", shadow.fno)

                        # シャドウ種別
                        shadow.type = self.read_uint(1)
                        logger.test("shadow.type %s", shadow.type)

                        # 距離
                        shadow.distance = self.read_float()
                        logger.test("shadow.distance %s", shadow.distance)

                        # 追加
                        motion.shadows.append(shadow)

                except Exception:
                    # 情報がない場合、catchして握りつぶす
                    motion.shadow_cnt = 0

                # IK数
                try:
                    motion.ik_cnt = self.read_uint(4)
                    logger.test("motion.ik_cnt %s", motion.ik_cnt)

                    # 1F分のIK情報
                    for _ in range(motion.ik_cnt):
                        show_ik = VmdShowIkFrame()

                        # フレームIDX
                        show_ik.fno = self.read_uint(4)
                        logger.test("ik.fno %s", show_ik.fno)

                        # モデル表示, 0:OFF, 1:ON
                        show_ik.show = self.read_uint(1)
                        logger.test("ik.show %s", show_ik.show)

                        # 記録するIKの数
                        show_ik.ik_count = self.read_uint(4)
                        logger.test("ik.ik_count %s", show_ik.ik_count)

                        for _ in range(show_ik.ik_count):
                            ik_info = VmdInfoIk()

                            # IK名
                            ik_bname, ik_name = self.read_text(20)
                            ik_info.name = ik_name
                            ik_info.bname = ik_bname
                            logger.test("ik_info.name %s", ik_name)

                            # モデル表示, 0:OFF, 1:ON
                            ik_info.onoff = self.read_uint(1)
                            logger.test("ik_info.onoff %s", ik_info.onoff)

                            show_ik.ik.append(ik_info)

                        # 追加
                        motion.showiks.append(show_ik)

                except Exception:
                    # 昔のMMD(MMDv7.39.x64以前)はIK情報がないため、catchして握りつぶす
                    motion.ik_cnt = 0

            # ハッシュを設定
            motion.digest = self.hexdigest()
            logger.test("motion: %s, hash: %s", motion.path, motion.digest)

            return motion
        except MKilledException as ke:
            # 終了命令
            raise ke
        except SizingException as se:
            logger.error("サイジング処理が処理できないデータで終了しました。\n\n%s", se.message)
            return se
        except Exception as e:
            import traceback
            logger.error("サイジング処理が意図せぬエラーで終了しました。\n\n%s",
                         traceback.format_exc())
            raise e
Ejemplo n.º 11
0
    def blend_morph(self):
        # モーションVMDディレクトリパス
        pmx_dir_path = MFileUtils.get_dir_path(self.options.model.path)
        # モーションVMDファイル名・拡張子
        pmx_file_name, pmx_ext = os.path.splitext(
            os.path.basename(self.options.model.path))

        dt_now = datetime.now()

        blend_fpath = "{0}\\{1}_blend_{2:%Y%m%d_%H%M%S}.vmd".format(
            pmx_dir_path, pmx_file_name, dt_now)

        bone_motion = VmdMotion()

        # 処理対象モーフ名(文字列)
        target_morphs = self.options.eye_list + self.options.eyebrow_list + self.options.lip_list + self.options.other_list

        # 処理対象モーフデータ
        all_morphs = []
        for mk, mv in self.options.model.morphs.items():
            if mv.display and mv.name in target_morphs:
                all_morphs.append(mv)
                bone_motion.morphs[mk] = {}

        # 変化量(少ない方のの割合を多くする)
        ratio_values = [self.options.inc_value * x for x in range(math.ceil(self.options.min_value / self.options.inc_value), \
                        math.ceil(self.options.max_value / self.options.inc_value) + 1) if self.options.min_value <= self.options.inc_value * x <= self.options.max_value]
        ratio_lower_values = [self.options.inc_value * x for x in range(math.ceil(self.options.min_value / self.options.inc_value), \
                              math.ceil(self.options.max_value / self.options.inc_value / 2) + 1) if self.options.min_value <= self.options.inc_value * x <= self.options.max_value]
        ratio_zero_values = [0 for x in range(len(all_morphs))]

        # 全体の比率増減量
        ratio_total_values = copy.deepcopy(ratio_values)
        ratio_total_values.extend(ratio_lower_values)
        ratio_total_values.extend(ratio_lower_values)
        ratio_total_values.extend(ratio_zero_values)

        # モーフの組合せ数
        morph_comb_cnt = 1
        # 変化量の組合せ数
        ratio_product_cnt = 1
        for n in range(len(all_morphs), 0, -1):
            morph_comb_cnt *= n
            ratio_product_cnt *= len(ratio_values)

        morph_total_cnt = 0

        if morph_comb_cnt * ratio_product_cnt <= 1000:
            # モーフの組合せ
            morph_comb = list(
                itertools.combinations(all_morphs, len(all_morphs)))
            logger.debug("morph_comb: %s", len(morph_comb))

            # 変化量の直積(同じ値を許容する)
            ratio_product = list(
                itertools.product(ratio_values, repeat=len(all_morphs)))
            logger.debug("ratio_product: %s", len(ratio_product))

            # 組合せが1000以下なら組合せをそのまま出力
            brend_mr_pairs_list = list(
                itertools.product(morph_comb, ratio_product))
            logger.debug("brend_mr_pairs_list: %s", len(brend_mr_pairs_list))

            for mframe, (morphs, ratios) in enumerate(brend_mr_pairs_list):
                # 上限までしか登録しない
                if morph_total_cnt > 19000:
                    break

                for morph, ratio in zip(morphs, ratios):
                    vmd_morph = VmdMorphFrame()
                    vmd_morph.fno = mframe
                    vmd_morph.set_name(morph.name)
                    vmd_morph.ratio = ratio
                    vmd_morph.key = True

                    bone_motion.morphs[morph.name][mframe] = vmd_morph
                    logger.test(vmd_morph)

                    morph_total_cnt += 1

        else:
            # 組合せが1000より多い場合、ランダム
            for mframe in range(1000):
                # 上限までしか登録しない
                if morph_total_cnt > 19000:
                    break

                for morph in all_morphs:
                    ratio = ratio_total_values[random.randint(
                        0,
                        len(ratio_values) - 1)]

                    vmd_morph = VmdMorphFrame()
                    vmd_morph.fno = mframe
                    vmd_morph.set_name(morph.name)
                    vmd_morph.ratio = ratio
                    vmd_morph.key = True

                    bone_motion.morphs[morph.name][mframe] = vmd_morph
                    logger.test(vmd_morph)

                    morph_total_cnt += 1

        data_set = MOptionsDataSet(bone_motion, self.options.model,
                                   self.options.model, blend_fpath, False,
                                   False, [], None, 0, [])

        VmdWriter(data_set).write()

        logger.info("モーフブレンドVMD: %s",
                    blend_fpath,
                    decoration=MLogger.DECORATION_BOX)

        return True
Ejemplo n.º 12
0
    def convert_vmd(self):
        dt_now = datetime.now()

        bone_fpath = None
        bone_motion = VmdMotion()

        if self.options.bone_csv_path and os.path.exists(self.options.bone_csv_path):
            # ボーンモーションCSVディレクトリパス
            motion_csv_dir_path = MFileUtils.get_dir_path(self.options.bone_csv_path)
            # ボーンモーションCSVファイル名・拡張子
            motion_csv_file_name, _ = os.path.splitext(os.path.basename(self.options.bone_csv_path))

            bone_fpath = "{0}\\{1}_bone_{2:%Y%m%d_%H%M%S}.vmd".format(motion_csv_dir_path, motion_csv_file_name, dt_now)

            # ボーンCSV読み込み
            with open(self.options.bone_csv_path, encoding='cp932', mode='r') as f:
                reader = csv.reader(f)
                next(reader)  # ヘッダーを読み飛ばす

                cnt = 0
                for row in reader:
                    bf = VmdBoneFrame()

                    # ボーン名
                    bf.set_name(row[0])

                    # フレーム
                    bf.fno = int(float(row[1]))

                    # 位置
                    bf.position = MVector3D(float(row[2]), float(row[3]), float(row[4]))

                    # 回転
                    bf.rotation = MQuaternion.fromEulerAngles(float(row[5]), float(row[6]) * -1, float(row[7]) * -1)

                    # 補間曲線
                    # 補間曲線(一旦floatで読み込んで指数等も読み込んだ後、intに変換)
                    bf.interpolation = [int(float(row[8])), int(float(row[9])), int(float(row[10])), int(float(row[11])), int(float(row[12])), int(float(row[13])), \
                                        int(float(row[14])), int(float(row[15])), int(float(row[16])), int(float(row[17])), int(float(row[18])), int(float(row[19])), \
                                        int(float(row[20])), int(float(row[21])), int(float(row[22])), int(float(row[23])), int(float(row[24])), int(float(row[25])), \
                                        int(float(row[26])), int(float(row[27])), int(float(row[28])), int(float(row[29])), int(float(row[30])), int(float(row[31])), \
                                        int(float(row[32])), int(float(row[33])), int(float(row[34])), int(float(row[35])), int(float(row[36])), int(float(row[37])), \
                                        int(float(row[38])), int(float(row[39])), int(float(row[40])), int(float(row[41])), int(float(row[42])), int(float(row[43])), \
                                        int(float(row[44])), int(float(row[45])), int(float(row[46])), int(float(row[47])), int(float(row[48])), int(float(row[49])), \
                                        int(float(row[50])), int(float(row[51])), int(float(row[52])), int(float(row[53])), int(float(row[54])), int(float(row[55])), \
                                        int(float(row[56])), int(float(row[57])), int(float(row[58])), int(float(row[59])), int(float(row[60])), int(float(row[61])), \
                                        int(float(row[62])), int(float(row[63])), int(float(row[64])), int(float(row[65])), int(float(row[66])), int(float(row[67])), \
                                        int(float(row[68])), int(float(row[69])), int(float(row[70])), int(float(row[71]))]
                    
                    bf.read = True
                    bf.key = True

                    if bf.name not in bone_motion.bones:
                        bone_motion.bones[bf.name] = {}

                    bone_motion.bones[bf.name][bf.fno] = bf

                    cnt += 1

                    if cnt % 10000 == 0:
                        logger.info("[ボーン] %sキー目:終了", cnt)

        if self.options.morph_csv_path and os.path.exists(self.options.morph_csv_path):
            # モーフモーションCSVディレクトリパス
            motion_csv_dir_path = MFileUtils.get_dir_path(self.options.morph_csv_path)
            # モーフモーションCSVファイル名・拡張子
            motion_csv_file_name, _ = os.path.splitext(os.path.basename(self.options.morph_csv_path))

            if not bone_fpath:
                bone_fpath = "{0}\\{1}_morph_{2:%Y%m%d_%H%M%S}.vmd".format(motion_csv_dir_path, motion_csv_file_name, dt_now)

            # モーフCSV読み込み
            with open(self.options.morph_csv_path, encoding='cp932', mode='r') as f:
                reader = csv.reader(f)
                next(reader)  # ヘッダーを読み飛ばす

                cnt = 0
                for row in reader:
                    mf = VmdMorphFrame()

                    # ボーン名
                    mf.set_name(row[0])

                    # フレーム
                    mf.fno = int(float(row[1]))

                    # 位置
                    mf.ratio = float(row[2])

                    if mf.name not in bone_motion.morphs:
                        bone_motion.morphs[mf.name] = {}

                    bone_motion.morphs[mf.name][mf.fno] = mf

                    cnt += 1

                    if cnt % 1000 == 0:
                        logger.info("[モーフ] %sキー目:終了", cnt)

        if len(bone_motion.bones.keys()) > 0 or len(bone_motion.morphs.keys()) > 0:
            # ボーンかモーフのキーがある場合、まとめて出力

            model = PmxModel()
            model.name = "CSV Convert Model"
            data_set = MOptionsDataSet(bone_motion, None, model, bone_fpath, False, False, [], None, None, [])

            VmdWriter(data_set).write()

            logger.info("ボーン・モーフモーションVMD: %s", bone_fpath, decoration=MLogger.DECORATION_BOX)

        if self.options.camera_csv_path and os.path.exists(self.options.camera_csv_path):
            # カメラモーションCSVディレクトリパス
            motion_csv_dir_path = MFileUtils.get_dir_path(self.options.camera_csv_path)
            # カメラモーションCSVファイル名・拡張子
            motion_csv_file_name, _ = os.path.splitext(os.path.basename(self.options.camera_csv_path))

            camera_fpath = "{0}\\{1}_camera_{2:%Y%m%d_%H%M%S}.vmd".format(motion_csv_dir_path, motion_csv_file_name, dt_now)
            camera_motion = VmdMotion()

            # カメラCSV読み込み
            with open(self.options.camera_csv_path, encoding='cp932', mode='r') as f:
                reader = csv.reader(f)
                next(reader)  # ヘッダーを読み飛ばす

                cnt = 0
                for row in reader:
                    cf = VmdCameraFrame()

                    # フレーム
                    cf.fno = int(row[0])

                    # 位置
                    cf.position = MVector3D(float(row[1]), float(row[2]), float(row[3]))

                    # 回転(オイラー角)
                    cf.euler = MVector3D(float(row[4]), float(row[5]), float(row[6]))

                    # 距離
                    cf.length = -(float(row[7]))

                    # 視野角
                    cf.angle = int(row[8])

                    # パース
                    cf.perspective = int(row[9])

                    # 補間曲線
                    cf.interpolation = [int(float(row[10])), int(float(row[11])), int(float(row[12])), int(float(row[13])), int(float(row[14])), int(float(row[15])), \
                                        int(float(row[16])), int(float(row[17])), int(float(row[18])), int(float(row[19])), int(float(row[20])), int(float(row[21])), \
                                        int(float(row[22])), int(float(row[23])), int(float(row[24])), int(float(row[25])), int(float(row[26])), int(float(row[27])), \
                                        int(float(row[28])), int(float(row[29])), int(float(row[30])), int(float(row[31])), int(float(row[32])), int(float(row[33]))]

                    camera_motion.cameras[cf.fno] = cf

                    cnt += 1

                    if cnt % 500 == 0:
                        logger.info("[カメラ] %sキー目:終了", cnt)

            if len(camera_motion.cameras) > 0:
                # ボーンかモーフのキーがある場合、まとめて出力

                model = PmxModel()
                model.name = "カメラ・照明"
                data_set = MOptionsDataSet(camera_motion, None, model, camera_fpath, False, False, [], None, None, [])

                VmdWriter(data_set).write()

                logger.info("カメラモーションVMD: %s", camera_fpath, decoration=MLogger.DECORATION_BOX)

        return True
Ejemplo n.º 13
0
    def read_data(self):
        # VPDファイルを通常読み込み
        lines = []

        try:
            with open(self.file_path,
                      "r",
                      encoding=self.get_file_encoding(self.file_path)) as f:
                lines = f.readlines()

            if len(lines) > 0:
                # vpdバージョン
                signature = lines[0]
                logger.test("signature %s", signature)

            motion = VmdMotion()
            # モーション数(常に1)
            motion.motion_cnt = 1
            motion.last_motion_frame = 0

            # 各パターン(括弧はひとつのみ実体として取得する)
            model_name_pattern = re.compile(r'(.*)(?:\.osm;)(?:.*// 親ファイル名.*)',
                                            flags=re.IGNORECASE)
            bone_start_pattern = re.compile(r'(?:.*)(?:{)(.*)',
                                            flags=re.IGNORECASE)
            bone_pos_pattern = re.compile(
                r'([+-]?\d+(?:\.\d+))(?:,)([+-]?\d+(?:\.\d+))(?:,)([+-]?\d+(?:\.\d+))(?:;)(?:.*trans.*)',
                flags=re.IGNORECASE)
            bone_rot_pattern = re.compile(
                r'([+-]?\d+(?:\.\d+))(?:,)([+-]?\d+(?:\.\d+))(?:,)([+-]?\d+(?:\.\d+))(?:,)([+-]?\d+(?:\.\d+))(?:;)(?:.*Quaternion.*)',
                flags=re.IGNORECASE)
            bone_end_pattern = re.compile(r'(?:.*)(})(?:.*)',
                                          flags=re.IGNORECASE)

            frame = None

            for n in range(len(lines)):
                # モデル名
                result_values = self.read_line(lines[n], model_name_pattern, n)
                if result_values:
                    motion.model_name = result_values[0]

                    continue

                # 括弧開始
                result_values = self.read_line(lines[n], bone_start_pattern, n)
                if result_values:
                    bone_name = result_values[0]

                    # キーフレ生成
                    frame = VmdBoneFrame(0)
                    frame.set_name(bone_name)
                    frame.key = True
                    frame.read = True

                    continue

                if frame:
                    # 括弧内のチェック

                    # 位置
                    result_values = self.read_line(lines[n], bone_pos_pattern,
                                                   n)
                    if result_values:
                        # 位置X,Y,Z
                        frame.position = MVector3D(float(result_values[0]),
                                                   float(result_values[1]),
                                                   float(result_values[2]))
                        continue

                    # 角度
                    result_values = self.read_line(lines[n], bone_rot_pattern,
                                                   n)
                    if result_values:
                        # 回転scalar,X,Y,Z
                        frame.rotation = MQuaternion(float(result_values[3]),
                                                     float(result_values[0]),
                                                     float(result_values[1]),
                                                     float(result_values[2]))
                        continue

                    # 括弧終了
                    result_values = self.read_line(lines[n], bone_end_pattern,
                                                   n)
                    if result_values:
                        motion.bones[bone_name] = {0: frame}
                        frame = None
                        continue

            # ハッシュを設定
            motion.digest = self.hexdigest()
            logger.test("motion: %s, hash: %s", motion.path, motion.digest)

            return motion
        except MKilledException as ke:
            # 終了命令
            raise ke
        except SizingException as se:
            logger.error("サイジング処理が処理できないデータで終了しました。\n\n%s", se.message)
            return se
        except Exception as e:
            import traceback
            logger.error("サイジング処理が意図せぬエラーで終了しました。\n\n%s",
                         traceback.print_exc())
            raise e
Ejemplo n.º 14
0
    def convert_vmd(self):
        dt_now = datetime.now()

        bone_fpath = None
        bone_motion = VmdMotion()

        if self.options.bone_csv_path and os.path.exists(
                self.options.bone_csv_path):
            # ボーンモーションCSVディレクトリパス
            motion_csv_dir_path = MFileUtils.get_dir_path(
                self.options.bone_csv_path)
            # ボーンモーションCSVファイル名・拡張子
            motion_csv_file_name, _ = os.path.splitext(
                os.path.basename(self.options.bone_csv_path))

            bone_fpath = "{0}\\{1}_bone_{2:%Y%m%d_%H%M%S}.vmd".format(
                motion_csv_dir_path, motion_csv_file_name, dt_now)

            # ボーンCSV読み込み
            with open(self.options.bone_csv_path, encoding='cp932',
                      mode='r') as f:
                reader = csv.reader(f)
                next(reader)  # ヘッダーを読み飛ばす

                cnt = 0
                for ridx, row in enumerate(reader):
                    bf = VmdBoneFrame()
                    rno = ridx + 1

                    try:
                        if len(row) < 0 or not row[0]:
                            logger.error("[ボーン] %s行目のボーン名(1列目)が設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # ボーン名
                        bf.set_name(row[0])
                    except Exception as e:
                        logger.error("[ボーン] %s行目のボーン名の読み取りに失敗しました\n%s",
                                     rno,
                                     e,
                                     decoration=MLogger.DECORATION_BOX)
                        return False

                    try:
                        if len(row) < 1 or not row[1]:
                            logger.error("[ボーン] %s行目のフレーム番号(2列目)が設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # フレーム
                        bf.fno = int(float(row[1]))

                        if bf.fno < 0:
                            logger.error("[ボーン] %s行目のフレーム番号(2列目)に負数が設定されています",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                    except Exception as e:
                        logger.error(
                            "[ボーン] %s行目のフレーム番号の読み取りに失敗しました\nフレーム番号は半角数字のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    try:
                        if len(row
                               ) < 4 or not row[2] or not row[3] or not row[4]:
                            logger.error("[ボーン] %s行目の位置(3-5列目)のいずれかが設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # 位置
                        bf.position = MVector3D(float(row[2]), float(row[3]),
                                                float(row[4]))
                    except Exception as e:
                        logger.error(
                            "[ボーン] %s行目の位置の読み取りに失敗しました\n位置は半角数字・符号・小数点のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    try:
                        if len(row
                               ) < 7 or not row[5] or not row[6] or not row[7]:
                            logger.error("[ボーン] %s行目の回転(6-8列目)のいずれかが設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # 回転
                        bf.rotation = MQuaternion.fromEulerAngles(
                            float(row[5]),
                            float(row[6]) * -1,
                            float(row[7]) * -1)
                    except Exception as e:
                        logger.error(
                            "[ボーン] %s行目の回転の読み取りに失敗しました\n位置は半角数字・符号・小数点のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    try:
                        if len(row) < 71:
                            logger.error(
                                "[ボーン] %s行目の補間曲線(9-72列目)のいずれかが設定されていません",
                                rno,
                                decoration=MLogger.DECORATION_BOX)
                            return False

                        for cidx in range(8, 72):
                            if not row[cidx]:
                                logger.error("[ボーン] %s行目の補間曲線の%s番目が設定されていません",
                                             rno,
                                             cidx - 7,
                                             decoration=MLogger.DECORATION_BOX)
                                return False

                        # 補間曲線(一旦floatで読み込んで指数等も読み込んだ後、intに変換)
                        bf.interpolation = [int(float(row[8])), int(float(row[9])), int(float(row[10])), int(float(row[11])), int(float(row[12])), int(float(row[13])), \
                                            int(float(row[14])), int(float(row[15])), int(float(row[16])), int(float(row[17])), int(float(row[18])), int(float(row[19])), \
                                            int(float(row[20])), int(float(row[21])), int(float(row[22])), int(float(row[23])), int(float(row[24])), int(float(row[25])), \
                                            int(float(row[26])), int(float(row[27])), int(float(row[28])), int(float(row[29])), int(float(row[30])), int(float(row[31])), \
                                            int(float(row[32])), int(float(row[33])), int(float(row[34])), int(float(row[35])), int(float(row[36])), int(float(row[37])), \
                                            int(float(row[38])), int(float(row[39])), int(float(row[40])), int(float(row[41])), int(float(row[42])), int(float(row[43])), \
                                            int(float(row[44])), int(float(row[45])), int(float(row[46])), int(float(row[47])), int(float(row[48])), int(float(row[49])), \
                                            int(float(row[50])), int(float(row[51])), int(float(row[52])), int(float(row[53])), int(float(row[54])), int(float(row[55])), \
                                            int(float(row[56])), int(float(row[57])), int(float(row[58])), int(float(row[59])), int(float(row[60])), int(float(row[61])), \
                                            int(float(row[62])), int(float(row[63])), int(float(row[64])), int(float(row[65])), int(float(row[66])), int(float(row[67])), \
                                            int(float(row[68])), int(float(row[69])), int(float(row[70])), int(float(row[71]))]

                        for bidx, bi in enumerate(bf.interpolation):
                            if 0 > bi:
                                logger.error(
                                    "[ボーン] %s行目の補間曲線(%s列目)に負数が設定されています",
                                    rno,
                                    bidx + 9,
                                    decoration=MLogger.DECORATION_BOX)
                                return False

                    except Exception as e:
                        logger.error(
                            "[ボーン] %s行目の補間曲線の読み取りに失敗しました\n位置は半角数字のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    bf.read = True
                    bf.key = True

                    if bf.name not in bone_motion.bones:
                        bone_motion.bones[bf.name] = {}

                    bone_motion.bones[bf.name][bf.fno] = bf

                    cnt += 1

                    if cnt % 10000 == 0:
                        logger.info("[ボーン] %sキー目:終了", cnt)

        if self.options.morph_csv_path and os.path.exists(
                self.options.morph_csv_path):
            # モーフモーションCSVディレクトリパス
            motion_csv_dir_path = MFileUtils.get_dir_path(
                self.options.morph_csv_path)
            # モーフモーションCSVファイル名・拡張子
            motion_csv_file_name, _ = os.path.splitext(
                os.path.basename(self.options.morph_csv_path))

            if not bone_fpath:
                bone_fpath = "{0}\\{1}_morph_{2:%Y%m%d_%H%M%S}.vmd".format(
                    motion_csv_dir_path, motion_csv_file_name, dt_now)

            # モーフCSV読み込み
            with open(self.options.morph_csv_path, encoding='cp932',
                      mode='r') as f:
                reader = csv.reader(f)
                next(reader)  # ヘッダーを読み飛ばす

                cnt = 0
                for ridx, row in enumerate(reader):
                    mf = VmdMorphFrame()
                    rno = ridx + 1

                    try:
                        if len(row) < 0 or not row[0]:
                            logger.error("[モーフ] %s行目のモーフ名(1列目)が設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # ボーン名
                        mf.set_name(row[0])
                    except Exception as e:
                        logger.error("[モーフ] %s行目のモーフ名の読み取りに失敗しました\n%s",
                                     rno,
                                     e,
                                     decoration=MLogger.DECORATION_BOX)
                        return False

                    try:
                        if len(row) < 1 or not row[1]:
                            logger.error("[モーフ] %s行目のフレーム番号(2列目)が設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # フレーム
                        mf.fno = int(float(row[1]))

                        if mf.fno < 0:
                            logger.error("[モーフ] %s行目のフレーム番号(2列目)に負数が設定されています",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False
                    except Exception as e:
                        logger.error(
                            "[モーフ] %s行目のフレーム番号の読み取りに失敗しました\nフレーム番号は半角数字のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    try:
                        if len(row) < 2 or not row[2]:
                            logger.error("[モーフ] %s行目の大きさ(3列目)が設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # 値
                        mf.ratio = float(row[2])
                    except Exception as e:
                        logger.error(
                            "[モーフ] %s行目の大きさの読み取りに失敗しました\n大きさは半角数字・符号・小数点のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    if mf.name not in bone_motion.morphs:
                        bone_motion.morphs[mf.name] = {}

                    bone_motion.morphs[mf.name][mf.fno] = mf

                    cnt += 1

                    if cnt % 1000 == 0:
                        logger.info("[モーフ] %sキー目:終了", cnt)

        if len(bone_motion.bones.keys()) > 0 or len(
                bone_motion.morphs.keys()) > 0:
            # ボーンかモーフのキーがある場合、まとめて出力

            model = PmxModel()
            model.name = "CSV Convert Model"
            data_set = MOptionsDataSet(bone_motion, model, model, bone_fpath,
                                       False, False, [], None, 0, [])

            VmdWriter(data_set).write()

            logger.info("ボーン・モーフモーションVMD: %s",
                        bone_fpath,
                        decoration=MLogger.DECORATION_BOX)

        if self.options.camera_csv_path and os.path.exists(
                self.options.camera_csv_path):
            # カメラモーションCSVディレクトリパス
            motion_csv_dir_path = MFileUtils.get_dir_path(
                self.options.camera_csv_path)
            # カメラモーションCSVファイル名・拡張子
            motion_csv_file_name, _ = os.path.splitext(
                os.path.basename(self.options.camera_csv_path))

            camera_fpath = "{0}\\{1}_camera_{2:%Y%m%d_%H%M%S}.vmd".format(
                motion_csv_dir_path, motion_csv_file_name, dt_now)
            camera_motion = VmdMotion()

            # カメラCSV読み込み
            with open(self.options.camera_csv_path, encoding='cp932',
                      mode='r') as f:
                reader = csv.reader(f)
                next(reader)  # ヘッダーを読み飛ばす

                cnt = 0
                for ridx, row in enumerate(reader):
                    cf = VmdCameraFrame()
                    rno = ridx + 1

                    try:
                        if len(row) < 1 or not row[0]:
                            logger.error("[カメラ] %s行目のフレーム番号(1列目)が設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # フレーム
                        cf.fno = int(row[0])

                        if cf.fno < 0:
                            logger.error("[カメラ] %s行目のフレーム番号(1列目)に負数が設定されています",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False
                    except Exception as e:
                        logger.error(
                            "[カメラ] %s行目のフレーム番号の読み取りに失敗しました\nフレーム番号は半角数字のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    try:
                        if len(row
                               ) < 3 or not row[1] or not row[2] or not row[3]:
                            logger.error("[カメラ] %s行目の位置(2-4列目)のいずれかが設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # 位置
                        cf.position = MVector3D(float(row[1]), float(row[2]),
                                                float(row[3]))
                    except Exception as e:
                        logger.error(
                            "[カメラ] %s行目の位置の読み取りに失敗しました\n位置は半角数字・符号・小数点のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    try:
                        if len(row
                               ) < 6 or not row[4] or not row[5] or not row[6]:
                            logger.error("[カメラ] %s行目の回転(5-7列目)のいずれかが設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # 回転(オイラー角)
                        cf.euler = MVector3D(float(row[4]), float(row[5]),
                                             float(row[6]))
                    except Exception as e:
                        logger.error(
                            "[カメラ] %s行目の回転の読み取りに失敗しました\n回転は半角数字・符号・小数点のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    try:
                        if len(row) < 7 or not row[7]:
                            logger.error("[カメラ] %s行目の距離(8列目)が設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # 距離
                        cf.length = -(float(row[7]))
                    except Exception as e:
                        logger.error(
                            "[カメラ] %s行目の距離の読み取りに失敗しました\n距離は半角数字・符号・小数点のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    try:
                        if len(row) < 8 or not row[8]:
                            logger.error("[カメラ] %s行目の視野角(9列目)が設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # 視野角
                        cf.angle = int(row[8])

                        if cf.angle < 0:
                            logger.error("[カメラ] %s行目の視野角(9列目)に負数が設定されています",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                    except Exception as e:
                        logger.error(
                            "[カメラ] %s行目の視野角の読み取りに失敗しました\n視野角は半角数字のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    try:
                        if len(row) < 8 or not row[9]:
                            logger.error("[カメラ] %s行目のパース(10列目)が設定されていません",
                                         rno,
                                         decoration=MLogger.DECORATION_BOX)
                            return False

                        # パース
                        cf.perspective = int(row[9])

                        if cf.perspective not in [0, 1]:
                            logger.error(
                                "[カメラ] %s行目のパース(10列目)に0, 1以外の値が設定されています",
                                rno,
                                decoration=MLogger.DECORATION_BOX)
                            return False
                    except Exception as e:
                        logger.error(
                            "[カメラ] %s行目のパースの読み取りに失敗しました\nパースは0, 1のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    try:
                        if len(row) < 33:
                            logger.error(
                                "[カメラ] %s行目の補間曲線(11-34列目)のいずれかが設定されていません",
                                rno,
                                decoration=MLogger.DECORATION_BOX)
                            return False

                        for cidx in range(10, 34):
                            if not row[cidx]:
                                logger.error("[カメラ] %s行目の補間曲線の%s番目が設定されていません",
                                             rno,
                                             cidx - 9,
                                             decoration=MLogger.DECORATION_BOX)
                                return False

                        # 補間曲線(一旦floatで読み込んで指数等も読み込んだ後、intに変換)
                        cf.interpolation = [int(float(row[10])), int(float(row[11])), int(float(row[12])), int(float(row[13])), int(float(row[14])), int(float(row[15])), \
                                            int(float(row[16])), int(float(row[17])), int(float(row[18])), int(float(row[19])), int(float(row[20])), int(float(row[21])), \
                                            int(float(row[22])), int(float(row[23])), int(float(row[24])), int(float(row[25])), int(float(row[26])), int(float(row[27])), \
                                            int(float(row[28])), int(float(row[29])), int(float(row[30])), int(float(row[31])), int(float(row[32])), int(float(row[33]))]

                        for cidx, ci in enumerate(cf.interpolation):
                            if 0 > ci:
                                logger.error(
                                    "[カメラ] %s行目の補間曲線(%s列目)に負数が設定されています",
                                    rno,
                                    cidx + 11,
                                    decoration=MLogger.DECORATION_BOX)
                                return False

                    except Exception as e:
                        logger.error(
                            "[カメラ] %s行目の補間曲線の読み取りに失敗しました\n位置は半角数字のみ入力可能です。\n%s",
                            rno,
                            e,
                            decoration=MLogger.DECORATION_BOX)
                        return False

                    camera_motion.cameras[cf.fno] = cf

                    cnt += 1

                    if cnt % 500 == 0:
                        logger.info("[カメラ] %sキー目:終了", cnt)

            if len(camera_motion.cameras) > 0:
                # ボーンかモーフのキーがある場合、まとめて出力

                model = PmxModel()
                model.name = "カメラ・照明"
                data_set = MOptionsDataSet(camera_motion, model, model,
                                           camera_fpath, False, False, [],
                                           None, 0, [])

                VmdWriter(data_set).write()

                logger.info("カメラモーションVMD: %s",
                            camera_fpath,
                            decoration=MLogger.DECORATION_BOX)

        return True