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)
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
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)
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())))
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
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)
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
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
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
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
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
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
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