def test_MQuaternion_inverted(self): parent_qq = MQuaternion.fromEulerAngles(4.444088972232067, -131.68893846184505, 6.602773293502102) from_orientation = MQuaternion.fromEulerAngles(-28.005875419210547, -34.26259576800703, -81.65247193883769) initial = MQuaternion.fromEulerAngles(-0.0, 90.0, -90.0) cancel_qq = MQuaternion(-0.9992778836743259, -0.037996199801564046, -5.551115123125783e-17, 5.551115123125783e-17) from_rotation = parent_qq.inverted() * from_orientation * initial.inverted() * cancel_qq.inverted() print(from_rotation.toEulerAngles4MMD())
def test_MQuaternion_inverted02(self): initial = MQuaternion.fromEulerAngles(-23.093702434660457, 104.29537469962791, -100.75495862294058) orientation = MQuaternion.fromEulerAngles(2.8645534869583353, 112.98932633880118, -74.65035471978359) rot = orientation * initial.inverted() print(rot.toEulerAngles()) rot = initial.inverted() * orientation print(rot.toEulerAngles())
def test_MMatrix4x4_rotate(self): mat = MMatrix4x4() mat.setToIdentity() mat.translate(MVector3D(1, 2, 3)) mat.rotate(MQuaternion.fromEulerAngles(10, 20, 30)) print(mat)
def calc_camera_qq(self, cf: VmdCameraFrame): # カメラ角度 camera_qq = MQuaternion.fromEulerAngles(-cf.euler.x(), cf.euler.y(), cf.euler.z()) camera_qq.setX(-camera_qq.x()) camera_qq.setScalar(-camera_qq.scalar()) return camera_qq
def interpolate_rot_circle(self, prev_bf: VmdBoneFrame, now_bf: VmdBoneFrame, next_bf: VmdBoneFrame, target_bf: VmdBoneFrame, local_axis: MVector3D): if local_axis != MVector3D(): # 軸がある場合、その方向に回す p_qq = MQuaternion.fromAxisAndAngle(local_axis, prev_bf.rotation.toDegree()) w_qq = MQuaternion.fromAxisAndAngle(local_axis, now_bf.rotation.toDegree()) n_qq = MQuaternion.fromAxisAndAngle(local_axis, next_bf.rotation.toDegree()) p = p_qq.toEulerAngles() w = w_qq.toEulerAngles() n = n_qq.toEulerAngles() else: p_qq = prev_bf.rotation.copy() w_qq = now_bf.rotation.copy() n_qq = next_bf.rotation.copy() # 軸がない場合、そのまま回転 p = p_qq.toEulerAngles() w = w_qq.toEulerAngles() n = n_qq.toEulerAngles() if target_bf.fno < now_bf.fno: # 変化量 t = (target_bf.fno - prev_bf.fno) / (now_bf.fno - prev_bf.fno) # デフォルト値 d_qq = MQuaternion.slerp(p_qq, w_qq, t) d = d_qq.toEulerAngles() out = interpolate_vec3(p, w, n, d, t, target_bf.fno) else: # 変化量 t = (target_bf.fno - now_bf.fno) / (next_bf.fno - now_bf.fno) # デフォルト値 d_qq = MQuaternion.slerp(w_qq, n_qq, t) d = d_qq.toEulerAngles() out = interpolate_vec3(w, n, p, d, t, target_bf.fno) out_qq = MQuaternion.fromEulerAngles(out.x(), out.y(), out.z()) if local_axis != MVector3D(): # 回転を元に戻す if target_bf.fno < now_bf.fno: d2_qq = MQuaternion.slerp(prev_bf.rotation, now_bf.rotation, t) else: d2_qq = MQuaternion.slerp(now_bf.rotation, next_bf.rotation, t) result_qq = (d_qq.inverted() * out_qq * d2_qq) else: result_qq = out_qq target_bf.rotation = result_qq
def smooth_filter_bf(self, data_set_no: int, bone_name: str, is_rot: bool, is_mov: bool, loop=1, \ config={"freq": 30, "mincutoff": 0.3, "beta": 0.01, "dcutoff": 0.25}, start_fno=-1, end_fno=-1, is_show_log=True): for n in range(loop): # 移動用フィルタ pxfilter = OneEuroFilter(**config) pyfilter = OneEuroFilter(**config) pzfilter = OneEuroFilter(**config) # 回転用フィルタ rxfilter = OneEuroFilter(**config) ryfilter = OneEuroFilter(**config) rzfilter = OneEuroFilter(**config) fnos = self.get_bone_fnos(bone_name) prev_sep_fno = 0 # キーフレを取得する if start_fno < 0 and end_fno < 0: # 範囲指定がない場合、全範囲 fnos = self.get_bone_fnos(bone_name) else: # 範囲指定がある場合はその範囲内だけ fnos = self.get_bone_fnos(bone_name, start_fno=start_fno, end_fno=end_fno) # 全区間をフィルタにかける for fno in fnos: now_bf = self.calc_bf(bone_name, fno) if is_mov: # 移動XYZそれぞれにフィルターをかける px = pxfilter(now_bf.position.x(), fno) py = pyfilter(now_bf.position.y(), fno) pz = pzfilter(now_bf.position.z(), fno) now_bf.position = MVector3D(px, py, pz) if is_rot: # 回転XYZそれぞれにフィルターをかける(オイラー角) now_qq = now_bf.rotation r = now_qq.toEulerAngles() rx = rxfilter(r.x(), fno) ry = ryfilter(r.y(), fno) rz = rzfilter(r.z(), fno) # クォータニオンに戻して保持 new_qq = MQuaternion.fromEulerAngles(rx, ry, rz) now_bf.rotation = new_qq if is_show_log and data_set_no > 0 and fno // 1000 > prev_sep_fno and fnos[-1] > 0: logger.info("-- %sフレーム目:終了(%s%)【No.%s - フィルタリング - %s(%s)】", fno, round((fno / fnos[-1]) * 100, 3), data_set_no, bone_name, (n + 1)) prev_sep_fno = fno // 1000
def test_separate(self): MLogger.initialize(level=MLogger.TEST, is_file=True) logger = MLogger(__name__, level=MLogger.TEST) # motion = VmdReader("D:\\MMD\\MikuMikuDance_v926x64\\UserFile\\Motion\\ダンス_1人\\桃源恋歌配布用motion moka\\ノーマルTda式用0-2000.vmd").read_data() model = PmxReader( "D:\\MMD\\MikuMikuDance_v926x64\\UserFile\\Model\\VOCALOID\\初音ミク\\Tda式初音ミク・アペンドVer1.10\\Tda式初音ミク・アペンド_Ver1.10.pmx", is_check=False).read_data() bone_axis_dict = {} for bone_name in ["左ひじ", "右ひじ"]: local_x_axis = model.get_local_x_axis("左ひじ") local_z_axis = MVector3D(0, 0, -1) local_y_axis = MVector3D.crossProduct(local_x_axis, local_z_axis).normalized() bone_axis_dict[bone_name] = { "x": local_x_axis, "y": local_y_axis, "z": local_z_axis } new_ik_qq = MQuaternion.fromEulerAngles(24.58152072747821, 135.9182003500461, 56.36785502950723) ik_bone = model.bones["左ひじ"] fno = 394 x_qq, y_qq, z_qq, yz_qq = MServiceUtils.separate_local_qq( fno, ik_bone.name, new_ik_qq, bone_axis_dict[ik_bone.name]["x"]) logger.debug( f"now: {new_ik_qq.toEulerAngles()} -> {(y_qq * x_qq * z_qq).toEulerAngles()}" ) logger.debug( f"now: x: {x_qq.toDegree()}, y: {y_qq.toDegree()}, z: {z_qq.toDegree()}" ) for (x_sign, y_sign, z_sign) in list(itertools.product((1, -1), (1, -1), (1, -1))): new_x_qq = MQuaternion.fromAxisAndAngle(x_qq.vector(), x_qq.toDegree() * x_sign) new_y_qq = MQuaternion.fromAxisAndAngle(y_qq.vector(), y_qq.toDegree() * y_sign) new_z_qq = MQuaternion.fromAxisAndAngle(z_qq.vector(), z_qq.toDegree() * z_sign) logger.debug( f"x: {x_sign}, y: {y_sign}, z: {z_sign} -> {(new_y_qq * new_x_qq * new_z_qq).toEulerAngles()}" ) self.assertTrue(True)
def convert_multi_split(self, bone_name: str, rrxbn: str, rrybn: str, rrzbn: str, rmxbn: str, rmybn: str, rmzbn: str, center_mx: str, center_my: str, center_mz: str): logger.info("多段分割【%s】", bone_name, decoration=MLogger.DECORATION_LINE) motion = self.options.motion model = self.options.model # 事前に変化量全打ち if bone_name == "センター" or bone_name == "グルーブ": fnos = self.prev_motion.get_differ_fnos(0, ["センター", "グルーブ"], limit_degrees=70, limit_length=1) else: fnos = self.prev_motion.get_differ_fnos(0, [bone_name], limit_degrees=70, limit_length=1) if len(fnos) == 0: return prev_sep_fno = 0 for fno in fnos: # 一度そのままキーを登録 motion.regist_bf(motion.calc_bf(bone_name, fno), bone_name, fno) # 補間曲線のため、もう一度取得しなおし bf = motion.calc_bf(bone_name, fno) if model.bones[bone_name].getRotatable(): rx_bf = motion.calc_bf(rrxbn, fno) motion.copy_interpolation(bf, rx_bf, MBezierUtils.BZ_TYPE_R) motion.regist_bf(rx_bf, rx_bf.name, fno, copy_interpolation=True) ry_bf = motion.calc_bf(rrybn, fno) motion.copy_interpolation(bf, ry_bf, MBezierUtils.BZ_TYPE_R) motion.regist_bf(ry_bf, ry_bf.name, fno, copy_interpolation=True) rz_bf = motion.calc_bf(rrzbn, fno) motion.copy_interpolation(bf, rz_bf, MBezierUtils.BZ_TYPE_R) motion.regist_bf(rz_bf, rz_bf.name, fno, copy_interpolation=True) if model.bones[bone_name].getTranslatable(): mx_bf = motion.calc_bf(rmxbn, fno) motion.copy_interpolation(bf, mx_bf, MBezierUtils.BZ_TYPE_MX) motion.regist_bf(mx_bf, mx_bf.name, fno, copy_interpolation=True) my_bf = motion.calc_bf(rmybn, fno) motion.copy_interpolation(bf, my_bf, MBezierUtils.BZ_TYPE_MY) motion.regist_bf(my_bf, my_bf.name, fno, copy_interpolation=True) mz_bf = motion.calc_bf(rmzbn, fno) motion.copy_interpolation(bf, mz_bf, MBezierUtils.BZ_TYPE_MZ) motion.regist_bf(mz_bf, mz_bf.name, fno, copy_interpolation=True) if fno // 500 > prev_sep_fno and fnos[-1] > 0: logger.info("-- %sフレーム目:終了(%s%)【キーフレ追加 - %s】", fno, round((fno / fnos[-1]) * 100, 3), bone_name) prev_sep_fno = fno // 500 logger.info("分割準備完了【%s】", bone_name, decoration=MLogger.DECORATION_LINE) # ローカルX軸 local_x_axis = model.bones[bone_name].local_x_vector if local_x_axis == MVector3D(1, 0, 0) or local_x_axis == MVector3D(): # 指定が無い場合、腕系はローカルX軸、それ以外はノーマル if "腕" in bone_name or "ひじ" in bone_name or "手首" in bone_name: local_x_axis = model.get_local_x_axis(bone_name) else: local_x_axis = None logger.debug(f"{bone_name}, local_x_axis: {local_x_axis}") prev_sep_fno = 0 for fno in fnos: bf = motion.calc_bf(bone_name, fno) # 多段分割 self.split_bf(fno, bf, local_x_axis, bone_name, rrxbn, rrybn, rrzbn, rmxbn, rmybn, rmzbn) if fno // 500 > prev_sep_fno and fnos[-1] > 0: logger.info("-- %sフレーム目:終了(%s%)【多段分割 - %s】", fno, round((fno / fnos[-1]) * 100, 3), bone_name) prev_sep_fno = fno // 500 check_fnos = [] check_prev_next_fnos = {} # 分離後に乖離起こしてないかチェック for fno_idx, (prev_fno, next_fno) in enumerate(zip(fnos[:-1], fnos[1:])): fno = int(prev_fno + ((next_fno - prev_fno) / 2)) if fno not in fnos: check_fnos.append(fno) check_prev_next_fnos[fno] = { "prev": prev_fno, "next": next_fno } check_fnos = list(sorted(list(set(check_fnos)))) logger.debug("bone_name: %s, check_fnos: %s", bone_name, check_fnos) prev_sep_fno = 0 for fno in check_fnos: is_subdiv = False prev_motion_bf = self.prev_motion.calc_bf(bone_name, fno).copy() if model.bones[bone_name].getRotatable(): # 回転を分ける if local_x_axis: # ローカルX軸がある場合 x_qq, y_qq, z_qq, _ = MServiceUtils.separate_local_qq( fno, bone_name, prev_motion_bf.rotation, local_x_axis) else: # ローカルX軸の指定が無い場合、グローバルで分ける euler = prev_motion_bf.rotation.toEulerAngles() x_qq = MQuaternion.fromEulerAngles(euler.x(), 0, 0) y_qq = MQuaternion.fromEulerAngles(0, euler.y(), 0) z_qq = MQuaternion.fromEulerAngles(0, 0, euler.z()) if len(rrxbn) > 0: rx_bf = motion.calc_bf(rrxbn, fno) dot = MQuaternion.dotProduct(x_qq.normalized(), rx_bf.rotation.normalized()) if dot < 0.98: is_subdiv = True if len(rrybn) > 0: ry_bf = motion.calc_bf(rrybn, fno) dot = MQuaternion.dotProduct(y_qq.normalized(), ry_bf.rotation.normalized()) if dot < 0.98: is_subdiv = True if len(rrzbn) > 0: rz_bf = motion.calc_bf(rrzbn, fno) dot = MQuaternion.dotProduct(z_qq.normalized(), rz_bf.rotation.normalized()) if dot < 0.98: is_subdiv = True if model.bones[bone_name].getTranslatable(): if len(center_mx) > 0 or len(center_my) > 0 or len( center_mz) > 0: # センターとグルーブを両方分割してる場合 prev_center_motion_bf = self.prev_motion.calc_bf( "センター", fno).copy() if len(center_mx) > 0 and rmxbn == center_mx: prev_motion_bf.position.setX( prev_motion_bf.position.x() + prev_center_motion_bf.position.x()) if len(center_my) > 0 and rmybn == center_my: prev_motion_bf.position.setY( prev_motion_bf.position.y() + prev_center_motion_bf.position.y()) if len(center_mz) > 0 and rmzbn == center_mz: prev_motion_bf.position.setZ( prev_motion_bf.position.z() + prev_center_motion_bf.position.z()) # 移動を分ける if len(rmxbn) > 0: mx_bf = motion.calc_bf(rmxbn, fno) if np.diff( [mx_bf.position.x(), prev_motion_bf.position.x()]) > 0.1: is_subdiv = True if len(rmybn) > 0: my_bf = motion.calc_bf(rmybn, fno) if np.diff( [my_bf.position.y(), prev_motion_bf.position.y()]) > 0.1: is_subdiv = True if len(rmzbn) > 0: mz_bf = motion.calc_bf(rmzbn, fno) if np.diff( [mz_bf.position.z(), prev_motion_bf.position.z()]) > 0.1: is_subdiv = True if is_subdiv: # 細分化ONの場合、更に分割する if model.bones[bone_name].getRotatable(): if len(rrxbn) > 0: motion.regist_bf(self.prev_motion.calc_bf(rrxbn, fno), rrxbn, fno) if len(rrybn) > 0: motion.regist_bf(self.prev_motion.calc_bf(rrybn, fno), rrybn, fno) if len(rrzbn) > 0: motion.regist_bf(self.prev_motion.calc_bf(rrzbn, fno), rrzbn, fno) if model.bones[bone_name].getTranslatable(): if len(rmxbn) > 0: motion.regist_bf(self.prev_motion.calc_bf(rmxbn, fno), rmxbn, fno) if len(rmybn) > 0: motion.regist_bf(self.prev_motion.calc_bf(rmybn, fno), rmybn, fno) if len(rmzbn) > 0: motion.regist_bf(self.prev_motion.calc_bf(rmzbn, fno), rmzbn, fno) # 分割前の値を再登録 motion.regist_bf(self.prev_motion.calc_bf(bone_name, fno), bone_name, fno) subdiv_bf = motion.calc_bf(bone_name, fno) if bone_name == "グルーブ" and (len(center_mx) > 0 or len(center_my) > 0 or len(center_mz) > 0): prev_center_motion_bf = self.prev_motion.calc_bf( "センター", fno) if len(center_mx) > 0 and rmxbn == center_mx: subdiv_bf.position.setX( subdiv_bf.position.x() + prev_center_motion_bf.position.x()) if len(center_my) > 0 and rmybn == center_my: subdiv_bf.position.setY( subdiv_bf.position.y() + prev_center_motion_bf.position.y()) if len(center_mz) > 0 and rmzbn == center_mz: subdiv_bf.position.setZ( subdiv_bf.position.z() + prev_center_motion_bf.position.z()) # 多段分割 self.split_bf(fno, subdiv_bf, local_x_axis, bone_name, rrxbn, rrybn, rrzbn, rmxbn, rmybn, rmzbn) # prev_fno = check_prev_next_fnos[fno]["prev"] # next_fno = check_prev_next_fnos[fno]["next"] # logger.info(f"-- 軌跡ズレ防止のため、「{bone_name}」の{prev_fno}F~{next_fno}F間を細分化・不要キー除去します") # for f in range(prev_fno, next_fno + 1): # # 区間内を初期登録 # if model.bones[bone_name].getRotatable(): # # 回転を分ける # if local_x_axis: # # ローカルX軸がある場合 # x_qq, y_qq, z_qq, _ = MServiceUtils.separate_local_qq(f, bone_name, prev_motion_bf.rotation, local_x_axis) # else: # # ローカルX軸の指定が無い場合、グローバルで分ける # euler = prev_motion_bf.rotation.toEulerAngles() # x_qq = MQuaternion.fromEulerAngles(euler.x(), 0, 0) # y_qq = MQuaternion.fromEulerAngles(0, euler.y(), 0) # z_qq = MQuaternion.fromEulerAngles(0, 0, euler.z()) # if len(rrxbn) > 0: # prev_rx_bf = self.prev_motion.calc_bf(rrxbn, f).copy() # prev_rx_bf.rotation = x_qq # motion.regist_bf(prev_rx_bf, rrxbn, f) # if len(rrybn) > 0: # prev_ry_bf = self.prev_motion.calc_bf(rrybn, f).copy() # prev_ry_bf.rotation = y_qq # motion.regist_bf(prev_ry_bf, rrybn, f) # if len(rrzbn) > 0: # prev_rz_bf = self.prev_motion.calc_bf(rrzbn, f).copy() # prev_rz_bf.rotation = z_qq # motion.regist_bf(prev_rz_bf, rrzbn, f) # if model.bones[bone_name].getTranslatable(): # if len(center_mx) > 0 or len(center_my) > 0 or len(center_mz) > 0: # # センターとグルーブを両方分割してる場合 # prev_center_motion_bf = self.prev_motion.calc_bf("センター", fno).copy() # if len(center_mx) > 0 and rmxbn == center_mx: # prev_motion_bf.position.setX(prev_motion_bf.position.x() + prev_center_motion_bf.position.x()) # if len(center_my) > 0 and rmybn == center_my: # prev_motion_bf.position.setY(prev_motion_bf.position.y() + prev_center_motion_bf.position.y()) # if len(center_mz) > 0 and rmzbn == center_mz: # prev_motion_bf.position.setZ(prev_motion_bf.position.z() + prev_center_motion_bf.position.z()) # if len(rmxbn) > 0: # prev_mx_bf = self.prev_motion.calc_bf(rmxbn, f).copy() # prev_mx_bf.position.setX(prev_motion_bf.position.x()) # motion.regist_bf(prev_mx_bf, rmxbn, f) # if len(rmybn) > 0: # prev_my_bf = self.prev_motion.calc_bf(rmybn, f).copy() # prev_my_bf.position.setY(prev_motion_bf.position.y()) # motion.regist_bf(prev_my_bf, rmybn, f) # if len(rmzbn) > 0: # prev_mz_bf = self.prev_motion.calc_bf(rmzbn, f).copy() # prev_mz_bf.position.setZ(prev_motion_bf.position.z()) # motion.regist_bf(prev_mz_bf, rmzbn, f) # # 不要キー削除 # futures = [] # with ThreadPoolExecutor(thread_name_prefix="remove", max_workers=self.options.max_workers) as executor: # if model.bones[bone_name].getRotatable(): # if len(rrxbn) > 0: # futures.append(executor.submit(self.remove_unnecessary_bf, rrxbn, start_fno=prev_fno, end_fno=next_fno)) # if len(rrybn) > 0: # futures.append(executor.submit(self.remove_unnecessary_bf, rrybn, start_fno=prev_fno, end_fno=next_fno)) # if len(rrzbn) > 0: # futures.append(executor.submit(self.remove_unnecessary_bf, rrzbn, start_fno=prev_fno, end_fno=next_fno)) # if model.bones[bone_name].getTranslatable(): # if len(rmxbn) > 0: # futures.append(executor.submit(self.remove_unnecessary_bf, rmxbn, start_fno=prev_fno, end_fno=next_fno)) # if len(rmybn) > 0: # futures.append(executor.submit(self.remove_unnecessary_bf, rmybn, start_fno=prev_fno, end_fno=next_fno)) # if len(rmzbn) > 0: # futures.append(executor.submit(self.remove_unnecessary_bf, rmzbn, start_fno=prev_fno, end_fno=next_fno)) # concurrent.futures.wait(futures, timeout=None, return_when=concurrent.futures.FIRST_EXCEPTION) # for f in futures: # if not f.result(): # return False if fno // 1000 > prev_sep_fno and fnos[-1] > 0: logger.count(f"【分割後チェック - {bone_name}】", fno, fnos) prev_sep_fno = fno // 1000 logger.info("分割完了【%s】", bone_name, decoration=MLogger.DECORATION_LINE) # 元のボーン削除 if rrxbn != bone_name and rrybn != bone_name and rrzbn != bone_name and rmxbn != bone_name and rmybn != bone_name and rmzbn != bone_name: del motion.bones[bone_name] # # 跳ねてるの除去 # futures = [] # with ThreadPoolExecutor(thread_name_prefix="smooth", max_workers=self.options.max_workers) as executor: # if model.bones[bone_name].getRotatable(): # if len(rrxbn) > 0: # futures.append(executor.submit(self.smooth_bf, rrxbn)) # if len(rrybn) > 0: # futures.append(executor.submit(self.smooth_bf, rrybn)) # if len(rrzbn) > 0: # futures.append(executor.submit(self.smooth_bf, rrzbn)) # if model.bones[bone_name].getTranslatable(): # if len(rmxbn) > 0: # futures.append(executor.submit(self.smooth_bf, rmxbn)) # if len(rmybn) > 0: # futures.append(executor.submit(self.smooth_bf, rmybn)) # if len(rmzbn) > 0: # futures.append(executor.submit(self.smooth_bf, rmzbn)) # concurrent.futures.wait(futures, timeout=None, return_when=concurrent.futures.FIRST_EXCEPTION) # for f in futures: # if not f.result(): # return False return True
def convert_noise(self, copy_no: int, seed: float): logger.info("ゆらぎ複製 【No.%s】", (copy_no + 1), decoration=MLogger.DECORATION_LINE) # データをコピーしてそっちを弄る motion = self.options.motion.copy() for bone_name in motion.bones.keys(): if not self.options.finger_noise_flg and "指" in bone_name: logger.info("-- 指スキップ【No.%s - %s】", copy_no + 1, bone_name) continue fnos = motion.get_bone_fnos(bone_name) prev_fno = 0 prev_sep_fno = 0 # 事前に細分化 self.prepare_split_stance(motion, bone_name) logger.info("-- 準備完了【No.%s - %s】", copy_no + 1, bone_name) for fno in fnos: bf = motion.bones[bone_name][fno] org_bf = self.options.motion.calc_bf(bone_name, fno) # 移動 if bf.position != MVector3D(): prev_org_bf = self.options.motion.calc_bf( bone_name, prev_fno) if org_bf.position == prev_org_bf.position and fno > 0: bf.position = motion.calc_bf(bone_name, prev_fno).position else: # 0だったら動かさない if round(org_bf.position.x(), 1) != 0: if self.options.motivation_flg: bf.position.setX( bf.position.x() * seed + (0.5 - np.random.rand()) * (self.options.noise_size / 10)) else: bf.position.setX( bf.position.x() + (0.5 - np.random.rand()) * (self.options.noise_size / 10)) if round(org_bf.position.y(), 1) != 0 and "足IK" not in bone_name: # 足IKのYは動かさない if self.options.motivation_flg: if org_bf.position.y() < 0: # Yはオリジナルがマイナスの場合は、マイナスのみに動かす bf.position.setY( bf.position.y() * seed + (0 - np.random.rand()) * (self.options.noise_size / 10)) elif org_bf.position.y() > 0: bf.position.setY( bf.position.y() * seed + (0.5 - np.random.rand()) * (self.options.noise_size / 10)) else: bf.position.setY( bf.position.y() + (0.5 - np.random.rand()) * (self.options.noise_size / 10)) if round(org_bf.position.z(), 1) != 0: if self.options.motivation_flg: bf.position.setZ( bf.position.z() * seed + (0.5 - np.random.rand()) * (self.options.noise_size / 10)) else: bf.position.setZ( bf.position.z() + (0.5 - np.random.rand()) * (self.options.noise_size / 10)) # 移動補間曲線 for (bz_idx1, bz_idx2, bz_idx3, bz_idx4) in [MBezierUtils.MX_x1_idxs, MBezierUtils.MX_y1_idxs, MBezierUtils.MX_x2_idxs, MBezierUtils.MX_y2_idxs, \ MBezierUtils.MY_x1_idxs, MBezierUtils.MY_y1_idxs, MBezierUtils.MY_x2_idxs, MBezierUtils.MY_y2_idxs, \ MBezierUtils.MZ_x1_idxs, MBezierUtils.MZ_y1_idxs, MBezierUtils.MZ_x2_idxs, MBezierUtils.MZ_y2_idxs]: noise_interpolation = bf.interpolation[ bz_idx1] + math.ceil((0.5 - np.random.rand()) * self.options.noise_size) bf.interpolation[bz_idx1] = bf.interpolation[ bz_idx2] = bf.interpolation[ bz_idx3] = bf.interpolation[bz_idx4] = int( noise_interpolation) # 回転 euler = bf.rotation.toEulerAngles() # 回転は元が0であっても動かす(足は除く) if "足" not in bone_name and "ひざ" not in bone_name and "足首" not in bone_name: if self.options.motivation_flg: euler.setX(euler.x() * seed + (0.5 - np.random.rand()) * self.options.noise_size) euler.setY(euler.y() * seed + (0.5 - np.random.rand()) * self.options.noise_size) euler.setZ(euler.z() * seed + (0.5 - np.random.rand()) * self.options.noise_size) else: euler.setX(euler.x() + (0.5 - np.random.rand()) * self.options.noise_size) euler.setY(euler.y() + (0.5 - np.random.rand()) * self.options.noise_size) euler.setZ(euler.z() + (0.5 - np.random.rand()) * self.options.noise_size) bf.rotation = MQuaternion.fromEulerAngles( euler.x(), euler.y(), euler.z()) # 回転補間曲線 for (bz_idx1, bz_idx2, bz_idx3, bz_idx4) in [ MBezierUtils.R_x1_idxs, MBezierUtils.R_y1_idxs, MBezierUtils.R_x2_idxs, MBezierUtils.R_y2_idxs ]: noise_interpolation = bf.interpolation[bz_idx1] + math.ceil( (0.5 - np.random.rand()) * self.options.noise_size) bf.interpolation[bz_idx1] = bf.interpolation[ bz_idx2] = bf.interpolation[ bz_idx3] = bf.interpolation[bz_idx4] = int( noise_interpolation) # 前回fno保持 prev_fno = fno if fno // 2000 > prev_sep_fno and fnos[-1] > 0: logger.count(f"【No.{copy_no + 1} - {bone_name}】", fno, fnos) prev_sep_fno = fno // 2000 output_path = self.options.output_path.replace( "nxxx", "n{0:03d}".format(copy_no + 1)) output_path = output_path.replace( "axxx", "a{0:+03d}".format(int(seed * 100) - 100)) # 最後に出力 VmdWriter( MOptionsDataSet(motion, None, self.options.model, output_path, False, False, [], None, 0, [])).write() logger.info("出力成功: %s", os.path.basename(output_path), 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 prepare_curve(self, bone_name: str): try: logger.copy(self.options) logger.info("【スムージング1回目】%s 開始", bone_name) # 全キーフレを取得 fnos = self.options.motion.get_bone_fnos(bone_name, is_read=True) rx_values = [] ry_values = [] rz_values = [] mx_values = [] my_values = [] mz_values = [] for fno in fnos: bf = self.options.motion.calc_bf(bone_name, fno) if self.options.model.bones[bone_name].getRotatable(): euler = bf.rotation.toEulerAngles() rx_values.append(euler.x()) ry_values.append(euler.y()) rz_values.append(euler.z()) if self.options.model.bones[bone_name].getTranslatable(): mx_values.append(bf.position.x()) my_values.append(bf.position.y()) mz_values.append(bf.position.z()) if self.options.model.bones[bone_name].getRotatable(): rx_all_values = MBezierUtils.calc_value_from_catmullrom(bone_name, fnos, rx_values) logger.info("【スムージング1回目】%s - 回転X 終了", bone_name) ry_all_values = MBezierUtils.calc_value_from_catmullrom(bone_name, fnos, ry_values) logger.info("【スムージング1回目】%s - 回転Y 終了", bone_name) rz_all_values = MBezierUtils.calc_value_from_catmullrom(bone_name, fnos, rz_values) logger.info("【スムージング1回目】%s - 回転Z 終了", bone_name) else: if len(fnos) > 0: rx_all_values = np.zeros(fnos[-1] + 1) ry_all_values = np.zeros(fnos[-1] + 1) rz_all_values = np.zeros(fnos[-1] + 1) else: rx_all_values = [0] ry_all_values = [0] rz_all_values = [0] if self.options.model.bones[bone_name].getTranslatable(): mx_all_values = MBezierUtils.calc_value_from_catmullrom(bone_name, fnos, mx_values) logger.info("【スムージング1回目】%s - 移動X 終了", bone_name) my_all_values = MBezierUtils.calc_value_from_catmullrom(bone_name, fnos, my_values) logger.info("【スムージング1回目】%s - 移動Y 終了", bone_name) mz_all_values = MBezierUtils.calc_value_from_catmullrom(bone_name, fnos, mz_values) logger.info("【スムージング1回目】%s - 移動Z 終了", bone_name) else: if len(fnos) > 0: mx_all_values = np.zeros(fnos[-1] + 1) my_all_values = np.zeros(fnos[-1] + 1) mz_all_values = np.zeros(fnos[-1] + 1) else: mx_all_values = [0] my_all_values = [0] mz_all_values = [0] # カトマル曲線で生成した値を全打ち for fno, (rx, ry, rz, mx, my, mz) in enumerate(zip(rx_all_values, ry_all_values, rz_all_values, mx_all_values, my_all_values, mz_all_values)): bf = self.options.motion.calc_bf(bone_name, fno) bf.rotation = MQuaternion.fromEulerAngles(rx, ry, rz) bf.position = MVector3D(mx, my, mz) self.options.motion.regist_bf(bf, bone_name, fno) logger.info("【スムージング1回目】%s 終了", bone_name) return True except SizingException as se: logger.error("スムージング処理が処理できないデータで終了しました。\n\n%s", se.message) return False except Exception as e: logger.error("スムージング処理が意図せぬエラーで終了しました。", e) return False
def not_test_stance_shoulder_08(self): new_rep_to_pos = MVector3D(16.638640587237894, 19.455697325211673, 4.067013732312591) # rep_base_pos = MVector3D(15.541596036361701, 18.419343683301417, 2.4565491530944494) rep_from_pos = MVector3D(16.07129122417615, 19.252113744303983, 2.952803072461259) up_pos = MVector3D(0.02597404821369409, -0.6341368197928823, 0.7727844735774392) parent_qq = MQuaternion.fromEulerAngles(4.444080622855673, 131.6889133202979, -6.602768822699441) arm_pos = MVector3D(-1.837494969367981, 18.923479080200195, 0.4923856854438782) shoulder_pos = MVector3D(-0.8141360282897949, 19.6701602935791, 0.4931679964065552) neck_base_pos = MVector3D(0.0, 18.923479080200195, 0.4923856854438782) another_arm_pos = MVector3D(1.837494969367981, 18.923479080200195, 0.4923856854438782) another_shoulder_pos = MVector3D(0.8141360282897949, 19.6701602935791, 0.493167906999588) # 傾きパターン test_slope_param = [arm_pos, shoulder_pos, neck_base_pos] all_slope_test_params = list( itertools.product(test_slope_param, repeat=2)) slope_test_params = [(x00, x01) for (x00, x01) in all_slope_test_params if x00 != x01] # upパターン test_up_param = [ arm_pos, shoulder_pos, neck_base_pos, another_arm_pos, another_shoulder_pos ] all_up_test_params = list(itertools.product(test_up_param, repeat=2)) up_test_params = [(x00, x01) for (x00, x01) in all_up_test_params if x00 != x01] # 数値パターン test_number_param = [0, -1, 1] all_number_test_params = list( itertools.product(test_number_param, repeat=3)) number_test_params = [(x00, x01, x02) for (x00, x01, x02) in all_number_test_params if x00 == 0 or x01 == 0 or x02 == 0] target_test_params = list( itertools.product(slope_test_params, number_test_params, up_test_params)) print(len(target_test_params)) random.shuffle(target_test_params) for params in target_test_params: print("-----------------------") print(params[0][0]) print(params[0][1]) print(params[1]) print(params[2][0]) print(params[2][1]) rep_shoulder_slope = (params[0][0] - params[0][1]).normalized() print("rep_shoulder_slope: %s" % rep_shoulder_slope) rep_shoulder_slope_up = MVector3D(params[1][0], params[1][1], params[1][2]) print("rep_shoulder_slope_up: %s" % rep_shoulder_slope_up) shoulder_cross = MVector3D.crossProduct( rep_shoulder_slope, rep_shoulder_slope_up).normalized() print("shoulder_cross: %s" % shoulder_cross) initial = MQuaternion.fromDirection(rep_shoulder_slope, shoulder_cross) print("initial: %s" % initial.toEulerAngles()) direction = new_rep_to_pos - rep_from_pos up = MVector3D.crossProduct(direction, up_pos) from_orientation = MQuaternion.fromDirection( direction.normalized(), up.normalized()) from_rotation = parent_qq.inverted( ) * from_orientation * initial.inverted() from_rotation.normalize() print("rot %s" % from_rotation.toEulerAngles4MMD()) print("original: %s" % MVector3D( 21.338723875696445, 15.845333083479046, 37.954108757721826))
def test_MQuaternion_dotProduct(self): qq1 = MQuaternion.fromEulerAngles(0, 0, 0) qq2 = MQuaternion.fromEulerAngles(160, 0, 0) dot = MQuaternion.dotProduct(qq1, qq2) print(dot)
def test_MQuaternion_toMatrix4x4(self): qq = MQuaternion.fromEulerAngles(0, 0, 0) print(qq.toMatrix4x4()) self.assertTrue(True)
def split_bf(self, fno: int, bf: VmdBoneFrame, local_x_axis: MVector3D, bone_name: str, rrxbn: str, rrybn: str, rrzbn: str, rmxbn: str, rmybn: str, rmzbn: str): motion = self.options.motion model = self.options.model if model.bones[bone_name].getRotatable(): # 回転を分ける if local_x_axis: # ローカルX軸がある場合 x_qq, y_qq, z_qq, _ = MServiceUtils.separate_local_qq( fno, bone_name, bf.rotation, local_x_axis) else: # ローカルX軸の指定が無い場合、グローバルで分ける euler = bf.rotation.toEulerAngles() x_qq = MQuaternion.fromEulerAngles(euler.x(), 0, 0) y_qq = MQuaternion.fromEulerAngles(0, euler.y(), 0) z_qq = MQuaternion.fromEulerAngles(0, 0, euler.z()) logger.debug( f"fno: {fno}, x_qq: {x_qq.toEulerAngles4MMD().to_log()}, y_qq: {y_qq.toEulerAngles4MMD().to_log()}, z_qq: {z_qq.toEulerAngles4MMD().to_log()}" ) if len(rrybn) > 0: ry_bf = motion.calc_bf(rrybn, fno) ry_bf.rotation = y_qq * ry_bf.rotation motion.regist_bf(ry_bf, ry_bf.name, fno) # 減算 bf.rotation *= y_qq.inverted() if len(rrxbn) > 0: rx_bf = motion.calc_bf(rrxbn, fno) rx_bf.rotation = x_qq * rx_bf.rotation motion.regist_bf(rx_bf, rx_bf.name, fno) # 減算 bf.rotation *= x_qq.inverted() if len(rrzbn) > 0: rz_bf = motion.calc_bf(rrzbn, fno) rz_bf.rotation = z_qq * rz_bf.rotation motion.regist_bf(rz_bf, rz_bf.name, fno) # 減算 bf.rotation *= z_qq.inverted() if len(rrxbn) > 0 and len(rrybn) > 0 and len(rrzbn) > 0: bf.rotation = MQuaternion() motion.regist_bf(bf, bf.name, fno) if model.bones[bone_name].getTranslatable(): # 移動を分ける if len(rmxbn) > 0: mx_bf = motion.calc_bf(rmxbn, fno) mx_bf.position.setX(mx_bf.position.x() + bf.position.x()) motion.regist_bf(mx_bf, mx_bf.name, fno) # 減算 bf.position.setX(0) if len(rmybn) > 0: my_bf = motion.calc_bf(rmybn, fno) my_bf.position.setY(my_bf.position.y() + bf.position.y()) motion.regist_bf(my_bf, my_bf.name, fno) # 減算 bf.position.setY(0) if len(rmzbn) > 0: mz_bf = motion.calc_bf(rmzbn, fno) mz_bf.position.setZ(mz_bf.position.z() + bf.position.z()) motion.regist_bf(mz_bf, mz_bf.name, fno) # 減算 bf.position.setZ(0) if len(rmxbn) > 0 and len(rmybn) > 0 and len(rmzbn) > 0: bf.position = MVector3D() motion.regist_bf(bf, bf.name, fno)
def not_test_stance_shoulder_06(self): new_rep_to_pos = MVector3D(16.638640587237887, 19.455697325211673, 4.067013732312591) rep_base_pos = MVector3D(15.527995423468052, 19.158781645638516, 2.353690174209908) up_pos = MVector3D(0.02597404821369409, -0.6341368197928823, 0.7727844735774392) parent_qq = MQuaternion.fromEulerAngles(4.444080622855673, 131.6889133202979, -6.602768822699441) arm_pos = MVector3D(-1.837494969367981, 18.923479080200195, 0.4923856854438782) shoulder_pos = MVector3D(-0.8141360282897949, 19.6701602935791, 0.4931679964065552) neck_base_pos = MVector3D(0.0, 19.6701602935791, 0.4931679517030716) # stance_shoulder2neck_qq = MQuaternion.fromEulerAngles(0.015789129707102025, -0.040575112727633096, 42.52534119964952) # stance_shoulder2arm_qq = MQuaternion.fromEulerAngles(0.011536139571057251, 0.03538278693844499, -36.1158910081693) # 傾きパターン test_slope_param = [arm_pos, shoulder_pos, neck_base_pos] all_slope_test_params = list( itertools.product(test_slope_param, repeat=2)) slope_test_params = [(x00, x01) for (x00, x01) in all_slope_test_params if x00 != x01] # 数値パターン test_number_param = [0, -1, 1] all_number_test_params = list( itertools.product(test_number_param, repeat=3)) number_test_params = [(x00, x01, x02) for (x00, x01, x02) in all_number_test_params if x00 == 0 or x01 == 0 or x02 == 0] # qqパターン # test_qq_param = [stance_shoulder2neck_qq, stance_shoulder2neck_qq.inverted(), stance_shoulder2arm_qq, stance_shoulder2arm_qq.inverted(), MQuaternion()] test_qq_param = [MQuaternion()] target_test_params = list( itertools.product(slope_test_params, number_test_params, test_qq_param)) print(len(target_test_params)) random.shuffle(target_test_params) for params in target_test_params: print("-----------------------") print(params[0][0]) print(params[0][1]) print(params[1]) print(params[2].toEulerAngles()) rep_shoulder_slope = (params[0][0] - params[0][1]).normalized() print("rep_shoulder_slope: %s" % rep_shoulder_slope) rep_shoulder_slope_up = MVector3D(params[1][0], params[1][1], params[1][2]) print("rep_shoulder_slope_up: %s" % rep_shoulder_slope_up) initial = MQuaternion.fromDirection(rep_shoulder_slope, rep_shoulder_slope_up) print("initial: %s" % initial.toEulerAngles()) direction = new_rep_to_pos - rep_base_pos up = MVector3D.crossProduct(direction, up_pos) from_orientation = MQuaternion.fromDirection( direction.normalized(), up.normalized()) from_rotation = parent_qq.inverted( ) * from_orientation * initial.inverted() * params[2] from_rotation.normalize() print("rot %s" % from_rotation.toEulerAngles4MMD())
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 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