def smooth_bf(self, data_set_no: int, bone_name: str, is_rot: bool, is_mov: bool, limit_degrees: float, start_fno=-1, end_fno=-1, is_show_log=True): # キーフレを取得する 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) prev_sep_fno = 0 if len(fnos) > 2: for fno in fnos: prev_bf = self.calc_bf(bone_name, fno - 1) now_bf = self.calc_bf(bone_name, fno) next_bf = self.calc_bf(bone_name, fno + 1) if is_rot and now_bf.key: # 前後の内積 prev_next_dot = MQuaternion.dotProduct(prev_bf.rotation, next_bf.rotation) # 自分と後の内積 now_next_dot = MQuaternion.dotProduct(now_bf.rotation, next_bf.rotation) # 内積差分 diff = np.abs(np.diff([prev_next_dot, now_next_dot])) logger.test("set: %s, %s, f: %s, diff: %s, prev_next_dot: %s, now_next_dot: %s", data_set_no, bone_name, fno, diff, prev_next_dot, now_next_dot) # 前後と自分の内積の差が一定以上の場合、円滑化 if prev_next_dot > now_next_dot and diff > math.radians(limit_degrees): logger.debug("★ 円滑化 set: %s, %s, f: %s, diff: %s, prev_next_dot: %s, now_next_dot: %s", \ data_set_no, bone_name, fno, diff, prev_next_dot, now_next_dot) now_bf.rotation = MQuaternion.slerp(prev_bf.rotation, next_bf.rotation, ((now_bf.fno - prev_bf.fno) / (next_bf.fno - prev_bf.fno))) if is_show_log and data_set_no > 0 and fno // 500 > prev_sep_fno and fnos[-1] > 0: logger.info("-- %sフレーム目:終了(%s%)【No.%s - 円滑化 - %s】", fno, round((fno / fnos[-1]) * 100, 3), data_set_no, bone_name) prev_sep_fno = fno // 500
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())
class VmdBoneFrame(): def __init__(self, fno=0): self.name = '' self.bname = '' self.fno = fno self.position = MVector3D() self.rotation = MQuaternion() self.org_position = MVector3D() self.org_rotation = MQuaternion() self.interpolation = [20, 20, 0, 0, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 20, 20, 20, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 0, 20, 20, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 0, 0, 20, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 0, 0, 0] # noqa self.org_interpolation = [20, 20, 0, 0, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 20, 20, 20, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 0, 20, 20, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 0, 0, 20, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 0, 0, 0] # noqa # 登録対象であるか否か self.key = False # VMD読み込み処理で読み込んだキーか self.read = False # 接触回避の方向 self.avoidance = "" def set_name(self, name): self.name = name self.bname = '' if not name else name.encode('cp932').decode('shift_jis').encode('shift_jis')[:15].ljust(15, b'\x00') def copy(self): bf = VmdBoneFrame(self.fno) bf.name = self.name bf.bname = self.bname bf.position = self.position.copy() bf.rotation = self.rotation.copy() bf.org_position = self.org_position.copy() bf.org_rotation = self.org_rotation.copy() bf.interpolation = cPickle.loads(cPickle.dumps(self.interpolation, -1)) bf.key = self.key bf.read = self.read return bf def __str__(self): return "<VmdBoneFrame name:{0}, fno:{1}, position:{2}, rotation:{3}, euler:{4}, key:{5}, read:{6}, interpolation: {7}>".format( \ self.name, self.fno, self.position, self.rotation, self.rotation.toEulerAngles4MMD(), self.key, self.read, self.interpolation) def write(self, fout): if not self.bname: self.bname = self.name.encode('cp932').decode('shift_jis').encode('shift_jis')[:15].ljust(15, b'\x00') # 15文字制限 fout.write(self.bname) fout.write(struct.pack('<L', int(self.fno))) fout.write(struct.pack('<f', float(self.position.x()))) fout.write(struct.pack('<f', float(self.position.y()))) fout.write(struct.pack('<f', float(self.position.z()))) v = self.rotation.normalized().toVector4D() fout.write(struct.pack('<f', float(v.x()))) fout.write(struct.pack('<f', float(v.y()))) fout.write(struct.pack('<f', float(v.z()))) fout.write(struct.pack('<f', float(v.w()))) fout.write(bytearray([int(min(127, max(0, x))) for x in self.interpolation]))
def interpolate_vec3(op: MVector3D, ow: MVector3D, on: MVector3D, d: MVector3D, t: float, f: int): # 念のためコピー p = op.copy() w = ow.copy() n = on.copy() # 半径は3点間の距離の最長の半分 r = max(p.distanceToPoint(w), p.distanceToPoint(n), w.distanceToPoint(n)) / 2 logger.test("op: %s, ow: %s, on: %s, d: %s, t: %s, f: %s, r: %s", p.to_log(), w.to_log(), n.to_log(), d.to_log(), t, f, r) if r == 0: # 半径が取れなかった場合、そもそもまったく移動がないので、線分移動 return (p + n) * t if p == w or p == n or w == n: # 半径が0の場合か、どれか同じ値の場合、線対称な値を使用する n = d # 3点を通る球体の原点を求める c, radius = calc_sphere_center(p, w, n, r) if radius == 0: # 半径が取れなかった場合、そもそもまったく移動がないので、線分移動 return (p + n) * t # prev -> now の t分の回転量 pn_qq = MQuaternion.rotationTo((p - c).normalized(), (c - c).normalized()) pw_qq = MQuaternion.rotationTo((p - c).normalized(), (w - c).normalized()) # 球形補間の移動量 t_qq = MQuaternion.slerp(pn_qq, pw_qq, t) logger.test("(p - c): %s, (c - c): %s, (w - c): %s", (p - c).normalized(), (c - c).normalized(), (w - c).normalized()) logger.test("pn_qq: %s, pw: %s, t: %s", pn_qq, pw_qq, t_qq) out = t_qq * (p - c) + c # 値の変化がない場合、上書き if p.x() == w.x() == n.x(): out.setX(w.x()) if p.y() == w.y() == n.y(): out.setY(w.y()) if p.z() == w.z() == n.z(): out.setZ(w.z()) out.effective() logger.test(out.to_log()) return out
def calc_direction_qq(model: PmxModel, links: BoneLinks, motion: VmdMotion, fno: int, limit_links=None): add_qs = calc_relative_rotation(model, links, motion, fno, limit_links) total_qq = MQuaternion() for qq in add_qs: total_qq *= qq return total_qq.normalized()
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 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 test_MMatrix4x4_rotate(self): mat = MMatrix4x4() mat.setToIdentity() mat.translate(MVector3D(1, 2, 3)) mat.rotate(MQuaternion.fromEulerAngles(10, 20, 30)) print(mat)
def __init__(self, fno=0): self.name = '' self.bname = '' self.fno = fno self.position = MVector3D() self.rotation = MQuaternion() self.org_position = MVector3D() self.org_rotation = MQuaternion() self.interpolation = [20, 20, 0, 0, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 20, 20, 20, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 0, 20, 20, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 0, 0, 20, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 0, 0, 0] # noqa self.org_interpolation = [20, 20, 0, 0, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 20, 20, 20, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 0, 20, 20, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 0, 0, 20, 20, 20, 20, 20, 107, 107, 107, 107, 107, 107, 107, 107, 0, 0, 0] # noqa # 登録対象であるか否か self.key = False # VMD読み込み処理で読み込んだキーか self.read = False # 接触回避の方向 self.avoidance = ""
def is_active_bones(self, bone_name): for bf in self.bones[bone_name].values(): if bf.position != MVector3D(): return True if bf.rotation != MQuaternion(): return True return False
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 calc_bf_rot(self, prev_bf: VmdBoneFrame, fill_bf: VmdBoneFrame, next_bf: VmdBoneFrame): if prev_bf.rotation != next_bf.rotation: # 回転補間曲線 rx, ry, rt = MBezierUtils.evaluate(next_bf.interpolation[MBezierUtils.R_x1_idxs[3]], next_bf.interpolation[MBezierUtils.R_y1_idxs[3]], \ next_bf.interpolation[MBezierUtils.R_x2_idxs[3]], next_bf.interpolation[MBezierUtils.R_y2_idxs[3]], \ prev_bf.fno, fill_bf.fno, next_bf.fno) return MQuaternion.slerp(prev_bf.rotation, next_bf.rotation, ry) return prev_bf.rotation.copy()
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_MQuaternion_rotationTo(self): # base_v = MVector3D(2.617903093134025, 17.718517382408315, -0.12363630515345259) from_v = MVector3D(3.1009119396999303, 18.20660094549826, 0.3268970645363245) new_to_v = MVector3D(3.8692103699906015, 17.718517382408315, -0.12363630515345259) old_to_v = MVector3D(3.7909166767007814, 17.28083485225695, 1.101139547122786) new_from_v = new_to_v - from_v print(new_from_v) old_from_v = old_to_v - from_v print(old_from_v) qq1 = MQuaternion.rotationTo(new_from_v, old_from_v) print(qq1.toEulerAngles4MMD())
def get_differ_fnos(self, data_set_no: int, bone_name_list: str, limit_degrees: float, limit_length: float): limit_radians = math.cos(math.radians(limit_degrees)) fnos = [0] for bone_name in bone_name_list: prev_sep_fno = 0 # 有効キーを取得 bone_fnos = self.get_bone_fnos(bone_name, is_key=True) if len(bone_fnos) <= 0: continue before_bf = self.calc_bf(bone_name, 0) # 比較対象bf for fno in range(1, bone_fnos[-1]): bf = self.calc_bf(bone_name, fno) if bf.read: # 読み込みキーである場合、必ず処理対象に追加 fnos.append(fno) # 前回キーとして保持 before_bf = bf.copy() else: # 読み込みキーではない場合、処理対象にするかチェック # 読み込みキーとの差 dot = MQuaternion.dotProduct(before_bf.rotation, bf.rotation) if dot < limit_radians: # 前と今回の内積の差が指定度数より離れている場合、追加 logger.test("★ 追加 set: %s, %s, f: %s, dot: %s", data_set_no, bone_name, fno, dot) fnos.append(fno) # 前回キーとして保持 before_bf = bf.copy() # 読み込みキーとの差 diff = before_bf.position.distanceToPoint(bf.position) if diff > limit_length: # 前と今回の移動量の差が指定値より離れている場合、追加 logger.test("★ 追加 set: %s, %s, f: %s, dot: %s", data_set_no, bone_name, fno, dot) fnos.append(fno) # 前回キーとして保持 before_bf = bf.copy() if data_set_no > 0 and fno // 500 > prev_sep_fno and bone_fnos[-1] > 0: logger.info("-- %sフレーム目:終了(%s%)【No.%s - キーフレ追加準備 - %s】", fno, round((fno / bone_fnos[-1]) * 100, 3), data_set_no, bone_name) prev_sep_fno = fno // 500 # 重複を除いて再計算 return sorted(list(set(fnos)))
def test_stance_shoulder_01(self): arm_slope = MVector3D(-1.837494969367981, 18.923479080200195, 0.4923856854438782) shoulder_slope = MVector3D(-0.8141360282897949, 19.6701602935791, 0.4931679964065552) neck_base = MVector3D(0.0, 18.923479080200195, 0.4923856854438782) # 傾きパターン test_slope_param = [arm_slope, shoulder_slope, neck_base] slope_test_params = list(itertools.product(test_slope_param, repeat=2)) # random.shuffle(slope_test_params) # 数値パターン test_number_param = [0, -1, 1] number_test_params = list( itertools.product(test_number_param, repeat=3)) # random.shuffle(number_test_params) target_test_params = list( itertools.product(slope_test_params, number_test_params)) for param in target_test_params: print("------------------") print("param[0][0]: %s" % param[0][0]) print("param[0][1]: %s" % param[0][1]) print("param[1][0]: %s" % param[1][0]) print("param[1][1]: %s" % param[1][1]) print("param[1][2]: %s" % param[1][2]) rep_shoulder_slope = (param[0][0] - param[0][1]).normalized() rep_shoulder_slope_up = MVector3D(param[1][0], param[1][1], param[1][2]) rep_shoulder_slope_cross = MVector3D.crossProduct( rep_shoulder_slope, rep_shoulder_slope_up).normalized() rep_shoulder_initial_slope_qq = MQuaternion.fromDirection( rep_shoulder_slope, rep_shoulder_slope_cross) print("rep_shoulder_slope: %s" % rep_shoulder_slope) print("rep_shoulder_slope_up: %s" % rep_shoulder_slope_up) print("qq: %s" % rep_shoulder_initial_slope_qq.toEulerAngles()) self.assertTrue(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 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_global_pos(model: PmxModel, links: BoneLinks, motion: VmdMotion, fno: int, limit_links=None, return_matrix=False, is_local_x=False): trans_vs = calc_relative_position(model, links, motion, fno, limit_links) add_qs = calc_relative_rotation(model, links, motion, fno, limit_links) # 行列 matrixs = [MMatrix4x4() for i in range(links.size())] for n, (lname, v, q) in enumerate(zip(links.all().keys(), trans_vs, add_qs)): # 行列を生成 matrixs[n] = MMatrix4x4() # 初期化 matrixs[n].setToIdentity() # 移動 matrixs[n].translate(v) # 回転 matrixs[n].rotate(q) total_mats = {} global_3ds_dic = OrderedDict() for n, (lname, v) in enumerate(zip(links.all().keys(), trans_vs)): if n == 0: total_mats[lname] = MMatrix4x4() total_mats[lname].setToIdentity() for m in range(n): # 最後のひとつ手前までループ if m == 0: # 0番目の位置を初期値とする total_mats[lname] = matrixs[0].copy() else: # 自分より前の行列結果を掛け算する total_mats[lname] *= matrixs[m].copy() # 自分は、位置だけ掛ける global_3ds_dic[lname] = total_mats[lname] * v # 最後の行列をかけ算する total_mats[lname] *= matrixs[n].copy() # ローカル軸の向きを調整する if n > 0 and is_local_x: # ボーン自身にローカル軸が設定されているか local_x_matrix = MMatrix4x4() local_x_matrix.setToIdentity() local_axis_qq = MQuaternion() if model.bones[lname].local_x_vector == MVector3D(): # ローカル軸が設定されていない場合、計算 # 自身から親を引いた軸の向き local_axis = model.bones[lname].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( model.bones[lname].local_x_vector.normalized(), MVector3D(0, 0, 1)) local_x_matrix.rotate(local_axis_qq) total_mats[lname] *= local_x_matrix if return_matrix: # 行列も返す場合 return global_3ds_dic, total_mats return global_3ds_dic
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 separate_local_qq(fno: int, bone_name: str, qq: MQuaternion, global_x_axis: MVector3D): # ローカル座標系(ボーンベクトルが(1,0,0)になる空間)の向き local_axis = MVector3D(1, 0, 0) # グローバル座標系(Aスタンス)からローカル座標系(ボーンベクトルが(1,0,0)になる空間)への変換 global2local_qq = MQuaternion.rotationTo(global_x_axis, local_axis) local2global_qq = MQuaternion.rotationTo(local_axis, global_x_axis) # X成分を抽出する ------------ mat_x1 = MMatrix4x4() mat_x1.setToIdentity() # 初期化 mat_x1.rotate(qq) # 入力qq mat_x1.translate(global_x_axis) # グローバル軸方向に伸ばす mat_x1_vec = mat_x1 * MVector3D() # YZの回転量(自身のねじれを無視する) yz_qq = MQuaternion.rotationTo(global_x_axis, mat_x1_vec) # 除去されたX成分を求める mat_x2 = MMatrix4x4() mat_x2.setToIdentity() # 初期化 mat_x2.rotate(qq) # 元々の回転量 mat_x3 = MMatrix4x4() mat_x3.setToIdentity() # 初期化 mat_x3.rotate(yz_qq) # YZの回転量 x_qq = (mat_x2 * mat_x3.inverted()).toQuaternion() # YZ回転からZ成分を抽出する -------------- mat_z1 = MMatrix4x4() mat_z1.setToIdentity() # 初期化 mat_z1.rotate(yz_qq) # YZの回転量 mat_z1.rotate(global2local_qq) # グローバル軸の回転量からローカルの回転量に変換 mat_z1.translate(local_axis) # ローカル軸方向に伸ばす mat_z1_vec = mat_z1 * MVector3D() mat_z1_vec.setZ(0) # Z方向の移動量を潰す # ローカル軸からZを潰した移動への回転量 local_z_qq = MQuaternion.rotationTo(local_axis, mat_z1_vec) # ボーンローカル座標系の回転をグローバル座標系の回転に戻す mat_z2 = MMatrix4x4() mat_z2.setToIdentity() # 初期化 mat_z2.rotate(local_z_qq) # ローカル軸上のZ回転 mat_z2.rotate(local2global_qq) # ローカル軸上からグローバル軸上に変換 z_qq = mat_z2.toQuaternion() # YZ回転からY成分だけ取り出す ----------- mat_y1 = MMatrix4x4() mat_y1.setToIdentity() # 初期化 mat_y1.rotate(yz_qq) # グローバルYZの回転量 mat_y2 = MMatrix4x4() mat_y2.setToIdentity() # 初期化 mat_y2.rotate(z_qq) # グローバルZの回転量 mat_y2_qq = (mat_y1 * mat_y2.inverted()).toQuaternion() # X成分の捻れが混入したので、XY回転からYZ回転を取り出すことでXキャンセルをかける。 mat_y3 = MMatrix4x4() mat_y3.setToIdentity() mat_y3.rotate(mat_y2_qq) mat_y3.translate(global_x_axis) mat_y3_vec = mat_y3 * MVector3D() y_qq = MQuaternion.rotationTo(global_x_axis, mat_y3_vec) return x_qq, y_qq, z_qq, yz_qq
def read_Quaternion(self): x = self.read_float() y = self.read_float() z = self.read_float() scalar = self.read_float() return MQuaternion(scalar, x, y, z)
def is_loaded_valid(self): if self.set_no == 0: # CSVとかのファイルは番号出力なし display_set_no = "" else: display_set_no = "{0}番目の".format(self.set_no) # 両方のPMXが読めて、モーションも読み込めた場合、キーチェック not_org_bones = [] not_org_morphs = [] not_rep_bones = [] not_rep_morphs = [] mismatch_bones = [] motion = self.motion_vmd_file_ctrl.data org_pmx = self.org_model_file_ctrl.data rep_pmx = self.rep_model_file_ctrl.data if not motion or not org_pmx or not rep_pmx: # どれか読めてなければそのまま終了 return True if motion.motion_cnt == 0: logger.warning("%sボーンモーションデータにキーフレームが登録されていません。", display_set_no, decoration=MLogger.DECORATION_BOX) return True result = True is_warning = False # ボーン for k in motion.bones.keys(): bone_fnos = motion.get_bone_fnos(k) for fno in bone_fnos: if motion.bones[k][fno].position != MVector3D( ) or motion.bones[k][fno].rotation != MQuaternion(): # キーが存在しており、かつ初期値ではない値が入っている場合、警告対象 if k not in org_pmx.bones: not_org_bones.append(k) if k not in rep_pmx.bones: not_rep_bones.append(k) if k in org_pmx.bones and k in rep_pmx.bones: mismatch_types = [] # 両方にボーンがある場合、フラグが同じであるかチェック if org_pmx.bones[k].getRotatable( ) != rep_pmx.bones[k].getRotatable(): mismatch_types.append("性能:回転") if org_pmx.bones[k].getTranslatable( ) != rep_pmx.bones[k].getTranslatable(): mismatch_types.append("性能:移動") if org_pmx.bones[k].getIkFlag( ) != rep_pmx.bones[k].getIkFlag(): mismatch_types.append("性能:IK") if org_pmx.bones[k].getVisibleFlag( ) != rep_pmx.bones[k].getVisibleFlag(): mismatch_types.append("性能:表示") if org_pmx.bones[k].getManipulatable( ) != rep_pmx.bones[k].getManipulatable(): mismatch_types.append("性能:操作") if org_pmx.bones[k].display != rep_pmx.bones[k].display: mismatch_types.append("表示枠") if len(mismatch_types) > 0: mismatch_bones.append( f"{k} 【差異】{', '.join(mismatch_types)})") # 1件あればOK break for k in motion.morphs.keys(): morph_fnos = motion.get_morph_fnos(k) for fno in morph_fnos: if motion.morphs[k][fno].ratio != 0: # キーが存在しており、かつ初期値ではない値が入っている場合、警告対象 if k not in org_pmx.morphs: not_org_morphs.append(k) if k not in rep_pmx.morphs: not_rep_morphs.append(k) # 1件あればOK break if len(not_org_bones) > 0 or len(not_org_morphs) > 0: logger.warning("%s%sに、モーションで使用されているボーン・モーフが不足しています。\nモデル: %s\n不足ボーン: %s\n不足モーフ: %s", \ display_set_no, self.org_model_file_ctrl.title, org_pmx.name, ",".join(not_org_bones), ",".join(not_org_morphs), decoration=MLogger.DECORATION_BOX) is_warning = True if len(not_rep_bones) > 0 or len(not_rep_morphs) > 0: logger.warning("%s%sに、モーションで使用されているボーン・モーフが不足しています。\nモデル: %s\n不足ボーン: %s\n不足モーフ: %s", \ display_set_no, self.rep_model_file_ctrl.title, rep_pmx.name, ",".join(not_rep_bones), ",".join(not_rep_morphs), decoration=MLogger.DECORATION_BOX) is_warning = True if len(mismatch_bones) > 0: logger.warning("%s%sで、モーションで使用されているボーンの性能等が異なっています。\nモデル: %s\n差異ボーン:\n %s", \ display_set_no, self.rep_model_file_ctrl.title, rep_pmx.name, "\n ".join(mismatch_bones), decoration=MLogger.DECORATION_BOX) is_warning = True if not is_warning: logger.info("モーションで使用されているボーン・モーフが揃っています。", decoration=MLogger.DECORATION_BOX, title="OK") return result
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 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_target_ik2fk(self, ik_bone: Bone): motion = self.options.motion model = self.options.model bone_name = ik_bone.name logger.info("-- 腕IK変換準備:開始【%s】", bone_name) # モデルのIKボーンのリンク target_links = model.create_link_2_top_one(bone_name, is_defined=False) # モデルの人差し指先ボーンのリンク finger_bone_name = "{0}人指先実体".format(bone_name[0]) finger2_bone_name = "{0}小指先実体".format(bone_name[0]) wrist_bone_name = "{0}手首".format(bone_name[0]) finger_links = model.create_link_2_top_one(finger_bone_name, is_defined=False) finger2_links = model.create_link_2_top_one(finger2_bone_name, is_defined=False) # モデルのIKリンク if not (ik_bone.ik.target_index in model.bone_indexes and model.bone_indexes[ik_bone.ik.target_index] in model.bones): raise SizingException("{0} のTargetが有効なINDEXではありません。PMXの構造を確認してください。".format(bone_name)) # IKエフェクタ effector_bone_name = model.bone_indexes[ik_bone.ik.target_index] effector_links = model.create_link_2_top_one(effector_bone_name, is_defined=False) ik_links = BoneLinks() # 末端にエフェクタ effector_bone = model.bones[effector_bone_name].copy() effector_bone.degree_limit = math.degrees(ik_bone.ik.limit_radian) ik_links.append(effector_bone) for ik_link in ik_bone.ik.link: # IKリンクを末端から順に追加 if not (ik_link.bone_index in model.bone_indexes and model.bone_indexes[ik_link.bone_index] in model.bones): raise SizingException("{0} のLinkに無効なINDEXが含まれています。PMXの構造を確認してください。".format(bone_name)) link_bone = model.bones[model.bone_indexes[ik_link.bone_index]].copy() if link_bone.fixed_axis != MVector3D(): # 捩り系は無視 continue # 単位角 link_bone.degree_limit = math.degrees(ik_bone.ik.limit_radian) # # 角度制限 # if ik_link.limit_angle == 1: # link_bone.limit_min = ik_link.limit_min # link_bone.limit_max = ik_link.limit_max ik_links.append(link_bone) # 回転移管先ボーン transferee_bone = self.get_transferee_bone(ik_bone, effector_bone) # 移管先ボーンのローカル軸 transferee_local_x_axis = model.get_local_x_axis(transferee_bone.name) # 差異の大きい箇所にFKキーフレ追加 fnos = motion.get_differ_fnos(0, [bone_name], limit_degrees=20, limit_length=0.5) prev_sep_fno = 0 for fno in fnos: for link_name in list(ik_links.all().keys())[1:]: bf = motion.calc_bf(link_name, fno) motion.regist_bf(bf, link_name, fno) if fno // 500 > prev_sep_fno and fnos[-1] > 0: logger.count(f"【キーフレ追加 - {bone_name}】", fno, fnos) prev_sep_fno = fno // 500 logger.info("-- 腕IK変換準備:終了【%s】", bone_name) org_motion = motion.copy() # 元モーションを保持したら、IKキーフレ削除 del motion.bones[bone_name] for fno in fnos: # グローバル位置計算(元モーションの位置) target_ik_global_3ds = MServiceUtils.calc_global_pos(model, target_links, org_motion, fno) finger_global_3ds = MServiceUtils.calc_global_pos(model, finger_links, org_motion, fno) finger2_global_3ds = MServiceUtils.calc_global_pos(model, finger2_links, org_motion, fno) target_effector_pos = target_ik_global_3ds[bone_name] prev_diff = MVector3D() org_bfs = {} for link_name in list(ik_links.all().keys())[1:]: # 元モーションの角度で保持 bf = org_motion.calc_bf(link_name, fno).copy() org_bfs[link_name] = bf # 今のモーションも前のキーフレをクリアして再セット motion.regist_bf(bf, link_name, fno) # IK計算実行 for ik_cnt in range(50): MServiceUtils.calc_IK(model, effector_links, motion, fno, target_effector_pos, ik_links, max_count=1) # どちらにせよ一旦bf確定 for link_name in list(ik_links.all().keys())[1:]: ik_bf = motion.calc_bf(link_name, fno) motion.regist_bf(ik_bf, link_name, fno) # 現在のエフェクタ位置 now_global_3ds = MServiceUtils.calc_global_pos(model, effector_links, motion, fno) now_effector_pos = now_global_3ds[effector_bone_name] # 現在のエフェクタ位置との差分(エフェクタ位置が指定されている場合のみ) diff_pos = MVector3D() if target_effector_pos == MVector3D() else target_effector_pos - now_effector_pos if prev_diff == MVector3D() or (prev_diff != MVector3D() and diff_pos.length() < prev_diff.length()): if diff_pos.length() < 0.1: logger.debug("☆腕IK変換成功(%s): f: %s(%s), 指定 [%s], 現在[%s], 差異[%s(%s)]", ik_cnt, fno, bone_name, \ target_effector_pos.to_log(), now_effector_pos.to_log(), diff_pos.to_log(), diff_pos.length()) # org_bfを保持し直し for link_name in list(ik_links.all().keys())[1:]: bf = motion.calc_bf(link_name, fno).copy() org_bfs[link_name] = bf logger.test("org_bf保持: %s [%s]", link_name, bf.rotation.toEulerAngles().to_log()) # そのまま終了 break elif prev_diff == MVector3D() or diff_pos.length() < prev_diff.length(): logger.debug("☆腕IK変換ちょっと失敗採用(%s): f: %s(%s), 指定 [%s], 現在[%s], 差異[%s(%s)]", ik_cnt, fno, bone_name, \ target_effector_pos.to_log(), now_effector_pos.to_log(), diff_pos.to_log(), diff_pos.length()) # org_bfを保持し直し for link_name in list(ik_links.all().keys())[1:]: bf = motion.calc_bf(link_name, fno).copy() org_bfs[link_name] = bf logger.test("org_bf保持: %s [%s]", link_name, bf.rotation.toEulerAngles().to_log()) # 前回とまったく同じ場合か、充分に近い場合、IK的に動きがないので終了 if prev_diff == diff_pos or np.count_nonzero(np.where(np.abs(diff_pos.data()) > 0.05, 1, 0)) == 0: logger.debug("動きがないので終了") break # 前回差異を保持 prev_diff = diff_pos else: logger.debug("★腕IK変換ちょっと失敗不採用(%s): f: %s(%s), 指定 [%s], 現在[%s], 差異[%s(%s)]", ik_cnt, fno, bone_name, \ target_effector_pos.to_log(), now_effector_pos.to_log(), diff_pos.to_log(), diff_pos.length()) # 前回とまったく同じ場合か、充分に近い場合、IK的に動きがないので終了 if prev_diff == diff_pos or np.count_nonzero(np.where(np.abs(diff_pos.data()) > 0.05, 1, 0)) == 0: break else: logger.debug("★腕IK変換失敗(%s): f: %s(%s), 指定 [%s], 現在[%s], 差異[%s(%s)]", ik_cnt, fno, bone_name, \ target_effector_pos.to_log(), now_effector_pos.to_log(), diff_pos.to_log(), diff_pos.length()) # 前回とまったく同じ場合か、充分に近い場合、IK的に動きがないので終了 if prev_diff == diff_pos or np.count_nonzero(np.where(np.abs(diff_pos.data()) > 0.05, 1, 0)) == 0: logger.debug("動きがないので終了") break # 最後に成功したところに戻す for link_name in list(ik_links.all().keys())[1:]: bf = org_bfs[link_name].copy() logger.debug("確定bf: %s [%s]", link_name, bf.rotation.toEulerAngles().to_log()) motion.regist_bf(bf, link_name, fno) # 指先の現在の位置を再計算 if finger_links and finger_links.get(finger_bone_name) and finger_links.get(wrist_bone_name) and \ finger2_links and finger2_links.get(finger2_bone_name) and finger2_links.get(wrist_bone_name) and transferee_bone.name == wrist_bone_name: # 手首がある場合のみ、再計算 now_finger_global_3ds, now_finger_matrixs = MServiceUtils.calc_global_pos(model, finger_links, motion, fno, return_matrix=True) # 人指先のローカル位置 finger_initial_local_pos = now_finger_matrixs[wrist_bone_name].inverted() * now_finger_global_3ds[finger_bone_name] finger_local_pos = now_finger_matrixs[wrist_bone_name].inverted() * finger_global_3ds[finger_bone_name] finger_rotation = MQuaternion.rotationTo(finger_initial_local_pos, finger_local_pos) logger.debug("finger_rotation: %s [%s]", finger_bone_name, finger_rotation.toEulerAngles().to_log()) finger_x_qq, finger_y_qq, finger_z_qq, _ = MServiceUtils.separate_local_qq(fno, bone_name, finger_rotation, transferee_local_x_axis) logger.debug("finger_x_qq: %s [%s]", finger_bone_name, finger_x_qq.toEulerAngles().to_log()) logger.debug("finger_y_qq: %s [%s]", finger_bone_name, finger_y_qq.toEulerAngles().to_log()) logger.debug("finger_z_qq: %s [%s]", finger_bone_name, finger_z_qq.toEulerAngles().to_log()) # 手首の回転は、手首から見た指先の方向 transferee_bf = motion.calc_bf(transferee_bone.name, fno) transferee_bf.rotation = finger_rotation # 捩りなしの状態で一旦登録する motion.regist_bf(transferee_bf, transferee_bone.name, fno) now_finger2_global_3ds, now_finger2_matrixs = MServiceUtils.calc_global_pos(model, finger2_links, motion, fno, return_matrix=True) # 小指先のローカル位置 finger2_initial_local_pos = now_finger2_matrixs[wrist_bone_name].inverted() * now_finger2_global_3ds[finger2_bone_name] finger2_local_pos = now_finger2_matrixs[wrist_bone_name].inverted() * finger2_global_3ds[finger2_bone_name] finger2_rotation = MQuaternion.rotationTo(finger2_initial_local_pos, finger2_local_pos) logger.debug("finger2_rotation: %s [%s]", finger2_bone_name, finger2_rotation.toEulerAngles().to_log()) finger2_x_qq = MQuaternion.fromAxisAndAngle(transferee_local_x_axis, finger_x_qq.toDegree() + finger2_rotation.toDegree()) transferee_bf.rotation = finger_y_qq * finger2_x_qq * finger_z_qq motion.regist_bf(transferee_bf, transferee_bone.name, fno) logger.debug("transferee_qq: %s [%s], x [%s]", transferee_bone.name, transferee_bf.rotation.toEulerAngles().to_log(), finger2_x_qq.toEulerAngles().to_log()) logger.count("【腕IK変換 - {0}】".format(bone_name), fno, fnos)
def convert_parent(self): motion = self.options.motion model = self.options.model root_bone_name = "全ての親" center_bone_name = "センター" center_parent_bone_name = "センター親" waist_bone_name = "腰" upper_bone_name = "上半身" lower_bone_name = "下半身" left_leg_ik_bone_name = "左足IK" right_leg_ik_bone_name = "右足IK" left_leg_ik_parent_bone_name = "左足IK親" right_leg_ik_parent_bone_name = "右足IK親" # まずキー登録 # for bone_name in [root_bone_name]: # if bone_name in model.bones: # prev_sep_fno = 0 # fnos = motion.get_bone_fnos(root_bone_name, center_bone_name, waist_bone_name, upper_bone_name, lower_bone_name, \ # left_leg_ik_bone_name, right_leg_ik_bone_name, center_parent_bone_name, \ # left_leg_ik_parent_bone_name, right_leg_ik_parent_bone_name) # for fno in fnos: # bf = motion.calc_bf(bone_name, fno) # motion.regist_bf(bf, bone_name, fno) # if fno // 2000 > 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 // 2000 for bone_name in [center_bone_name]: if bone_name in model.bones: prev_sep_fno = 0 fno = 0 fnos = motion.get_bone_fnos(bone_name, root_bone_name) for fno in fnos: bf = motion.calc_bf(bone_name, fno) motion.regist_bf(bf, bone_name, fno) if fno // 2000 > prev_sep_fno and fnos[-1] > 0: logger.count(f"【準備 - {bone_name}】", fno, fnos) prev_sep_fno = fno // 2000 logger.count(f"【準備 - {bone_name}】", fno, fnos) if self.options.center_rotatation_flg: for bone_name in [ center_bone_name, upper_bone_name, lower_bone_name ]: if bone_name in model.bones: prev_sep_fno = 0 fno = 0 fnos = motion.get_bone_fnos(bone_name, center_bone_name, root_bone_name) for fno in fnos: bf = motion.calc_bf(bone_name, fno) motion.regist_bf(bf, bone_name, fno) if fno // 2000 > prev_sep_fno and fnos[-1] > 0: logger.count(f"【準備 - {bone_name}】", fno, fnos) prev_sep_fno = fno // 2000 logger.count(f"【準備 - {bone_name}】", fno, fnos) for bone_name in [right_leg_ik_bone_name, left_leg_ik_bone_name]: if bone_name in model.bones: prev_sep_fno = 0 fno = 0 fnos = motion.get_bone_fnos(bone_name, root_bone_name, left_leg_ik_parent_bone_name, right_leg_ik_parent_bone_name) for fno in fnos: bf = motion.calc_bf(bone_name, fno) motion.regist_bf(bf, bone_name, fno) if fno // 2000 > prev_sep_fno and fnos[-1] > 0: logger.count(f"【準備 - {bone_name}】", fno, fnos) prev_sep_fno = fno // 2000 logger.count(f"【準備 - {bone_name}】", fno, fnos) logger.info("移植開始", decoration=MLogger.DECORATION_LINE) # センターの移植 for bone_name in [center_bone_name]: if bone_name in model.bones: prev_sep_fno = 0 fno = 0 links = model.create_link_2_top_one(bone_name, is_defined=False) fnos = motion.get_bone_fnos(bone_name, root_bone_name) # 移植 for fno in fnos: root_bf = motion.calc_bf(root_bone_name, fno) center_parent_bf = motion.calc_bf(center_parent_bone_name, fno) bf = motion.calc_bf(bone_name, fno) # logger.info("f: %s, prev bf.position: %s", fno, bf.position) # relative_3ds_dic = MServiceUtils.calc_relative_position(model, links, motion, fno) # [logger.info("R %s", v.to_log()) for v in relative_3ds_dic] global_3ds_dic = MServiceUtils.calc_global_pos( model, links, motion, fno) bone_global_pos = global_3ds_dic[bone_name] # [logger.info("G %s: %s", k, v.to_log()) for k, v in global_3ds_dic.items()] # logger.info("f: %s, bone_global_pos: %s", fno, bone_global_pos) # グローバル位置からの差(センター親があった場合、それも加味して入れてしまう) bf.position = bone_global_pos - model.bones[ bone_name].position # logger.info("f: %s, bf.position: %s", fno, bf.position) # 回転の吸収 bf.rotation = root_bf.rotation * center_parent_bf.rotation * bf.rotation motion.regist_bf(bf, bone_name, fno) if fno // 2000 > prev_sep_fno and fnos[-1] > 0: logger.count(f"【移植 - {bone_name}】", fno, fnos) prev_sep_fno = fno // 2000 logger.count(f"【移植 - {bone_name}】", fno, fnos) # 足IKの移植 for bone_name, parent_bone_name in [ (right_leg_ik_bone_name, right_leg_ik_parent_bone_name), (left_leg_ik_bone_name, left_leg_ik_parent_bone_name) ]: if bone_name in model.bones: prev_sep_fno = 0 fno = 0 links = model.create_link_2_top_one(bone_name, is_defined=False) fnos = motion.get_bone_fnos(bone_name, root_bone_name) # 移植 for fno in fnos: root_bf = motion.calc_bf(root_bone_name, fno) leg_ik_parent_bf = motion.calc_bf(parent_bone_name, fno) bf = motion.calc_bf(bone_name, fno) global_3ds_dic = MServiceUtils.calc_global_pos( model, links, motion, fno) bone_global_pos = global_3ds_dic[bone_name] # グローバル位置からの差(足IK親があった場合、それも加味して入れてしまう) bf.position = bone_global_pos - model.bones[ bone_name].position # 回転 bf.rotation = root_bf.rotation * leg_ik_parent_bf.rotation * bf.rotation motion.regist_bf(bf, bone_name, fno) if fno // 2000 > prev_sep_fno and fnos[-1] > 0: logger.count(f"【移植 - {bone_name}】", fno, fnos) prev_sep_fno = fno // 2000 logger.count(f"【移植 - {bone_name}】", fno, fnos) # 全ての親削除 if root_bone_name in motion.bones: del motion.bones[root_bone_name] # センター親削除 if center_parent_bone_name in motion.bones: del motion.bones[center_parent_bone_name] # 足IK親削除 if left_leg_ik_parent_bone_name in motion.bones: del motion.bones[left_leg_ik_parent_bone_name] if right_leg_ik_parent_bone_name in motion.bones: del motion.bones[right_leg_ik_parent_bone_name] # logger.info("center_rotatation_flg: %s", self.options.center_rotatation_flg) if self.options.center_rotatation_flg: # センター→上半身・下半身の移植 prev_sep_fno = 0 fno = 0 fnos = motion.get_bone_fnos(upper_bone_name, lower_bone_name, center_bone_name) for fno in fnos: center_bf = motion.calc_bf(center_bone_name, fno) waist_bf = motion.calc_bf(waist_bone_name, fno) upper_bf = motion.calc_bf(upper_bone_name, fno) lower_bf = motion.calc_bf(lower_bone_name, fno) center_links = model.create_link_2_top_one(center_bone_name, is_defined=False) lower_links = model.create_link_2_top_one(lower_bone_name, is_defined=False) # 一旦移動量を保持 center_global_3ds_dic = MServiceUtils.calc_global_pos( model, lower_links, motion, fno, limit_links=center_links) # 回転移植 upper_bf.rotation = center_bf.rotation * waist_bf.rotation * upper_bf.rotation motion.regist_bf(upper_bf, upper_bone_name, fno) lower_bf.rotation = center_bf.rotation * waist_bf.rotation * lower_bf.rotation motion.regist_bf(lower_bf, lower_bone_name, fno) # 腰クリア if waist_bone_name in model.bones: waist_bf.rotation = MQuaternion() motion.regist_bf(waist_bf, waist_bone_name, fno) # センター回転クリア center_bf.rotation = MQuaternion() # 移動を下半身ベースで再計算 center_bf.position = center_global_3ds_dic[ lower_bone_name] - model.bones[lower_bone_name].position motion.regist_bf(center_bf, center_bone_name, fno) if fno // 1000 > prev_sep_fno and fnos[-1] > 0: logger.count("【移植 - 上半身・下半身】", fno, fnos) prev_sep_fno = fno // 1000 logger.count("【移植 - 上半身・下半身】", fno, fnos) logger.info("移植完了", decoration=MLogger.DECORATION_LINE) if self.options.remove_unnecessary_flg: futures = [] with ThreadPoolExecutor( thread_name_prefix="remove", max_workers=self.options.max_workers) as executor: for bone_name in [ center_bone_name, upper_bone_name, lower_bone_name, right_leg_ik_bone_name, left_leg_ik_bone_name ]: futures.append( executor.submit(self.remove_unnecessary_bf, bone_name)) 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 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