def test_reset_complement_03(self): # モーションの宣言 motion = VmdMotion() motion.frames["左手首"] = [] bf = VmdBoneFrame() bf.format_name = "左手首" bf.frame = 0 bf.read = True bf.key = True bf.complement = [ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 ] motion.frames["左手首"].append(bf) bf = VmdBoneFrame() bf.format_name = "左手首" bf.frame = 5 bf.read = True # 間に有効キーなし bf.key = False bf.complement = [ 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 ] motion.frames["左手首"].append(bf) bf = VmdBoneFrame() bf.format_name = "左手首" bf.frame = 30 bf.read = True bf.key = True bf.complement = [ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 ] motion.frames["左手首"].append(bf) # 腕リストの宣言 arm_links = self.create_arm_links() # 処理実施 sub_arm_ik.reset_complement(motion, arm_links) for c in motion.frames["左手首"][0].complement: self.assertEqual(10, c) for c in motion.frames["左手首"][1].complement: self.assertEqual(30, c) for c in motion.frames["左手首"][2].complement: self.assertEqual(20, c)
def create_body_global_3ds(model, motion_frames, body_links, frame, link_names=None): # bf生成 bf = VmdBoneFrame() bf.frame = frame # 全身のグローバル位置 # キー:ボーン名、値:ボーングローバル位置 body_global_3ds = {} for limb_links in body_links: # 頭と腕のリンクからグローバル位置を算出 _, _, _, _, global_3ds = utils.create_matrix_global( model, limb_links, motion_frames, bf) for l, g in zip(reversed(limb_links), global_3ds): if link_names is None or (link_names and l.name in link_names): # 指定リンク名リストがないか、ある場合、ボーン名がリンク名リストにある場合、登録 body_global_3ds[l.name] = g logger.info("m: %s, frame: %s ---------------------", model.name, frame) for k, v in body_global_3ds.items(): logger.info("%s: %s", k, v) return body_global_3ds
def create_bone_global_3ds(model, motion_frames, body_links, frame, link_names, bone_name, parent_bone_name): # bf生成 bf = VmdBoneFrame() bf.frame = frame # 指定されたボーンを含むリンクのグローバル位置を算出 logger.info("replace_model: %s", model.name) logger.info("bone_name: %s", bone_name) logger.debug("link_names[bone_name]: %s", link_names[bone_name]) logger.debug("body_links[link_names[bone_name]]: %s", body_links[link_names[bone_name]]) _, _, _, _, global_3ds = utils.create_matrix_global( model, body_links[link_names[bone_name]], motion_frames, bf) pgpos = QVector3D() for l, g in zip(reversed(body_links[link_names[bone_name]]), global_3ds): if l.name == parent_bone_name: # 該当親ボーンに相当するグローバル位置を取得 pgpos = g if l.name == bone_name: # 該当ボーンに相当するグローバル位置を取得 logger.info("parent: %s", pgpos) logger.info("pos: %s: %s", bone_name, g) return g, pgpos # 指定されたボーンのグローバル位置が取得できなかった場合 logger.warn("指定ボーンのグローバル位置取得失敗 %s", bone_name) return QVector3D(), QVector3D()
def calc_bone_by_complement(frames, bone_name, frameno): fillbf = VmdBoneFrame() # ボーン登録がなければ初期値 if bone_name not in frames: return fillbf for bidx, bf in enumerate(frames[bone_name]): if bf.frame >= frameno: # 前のキーIDXを0に見立てて、その間の補間曲線を埋める fillbf.name = bf.name fillbf.frame = frameno # 実際に登録はしない fillbf.key = False # 指定されたフレーム以降 prev_bf = frames[bone_name][bidx - 1] if prev_bf.rotation != bf.rotation: # 回転補間曲線 rn = calc_interpolate_bezier(bf.complement[48], bf.complement[52], bf.complement[56], bf.complement[60], prev_bf.frame, bf.frame, fillbf.frame) fillbf.rotation = QQuaternion.nlerp(prev_bf.rotation, bf.rotation, rn) # logger.debug("key: %s, n: %s, rn: %s, r: %s ", k, prev_frame + n, rn, QQuaternion.nlerp(prev_bf.rotation, bf.rotation, rn) ) # logger.debug("rotation: prev: %s, fill: %s ", prev_bf.rotation.toEulerAngles(), fillbf.rotation.toEulerAngles() ) else: fillbf.rotation = copy.deepcopy(prev_bf.rotation) # 補間曲線を元に間を埋める if prev_bf.position != bf.position: # http://rantyen.blog.fc2.com/blog-entry-65.html # X移動補間曲線 xn = calc_interpolate_bezier(bf.complement[0], bf.complement[4], bf.complement[8], bf.complement[12], prev_bf.frame, bf.frame, fillbf.frame) # Y移動補間曲線 yn = calc_interpolate_bezier(bf.complement[16], bf.complement[20], bf.complement[24], bf.complement[28], prev_bf.frame, bf.frame, fillbf.frame) # Z移動補間曲線 zn = calc_interpolate_bezier(bf.complement[32], bf.complement[36], bf.complement[40], bf.complement[44], prev_bf.frame, bf.frame, fillbf.frame) fillbf.position.setX(prev_bf.position.x() + ( (bf.position.x() - prev_bf.position.x()) * xn)) fillbf.position.setY(prev_bf.position.y() + ( (bf.position.y() - prev_bf.position.y()) * yn)) fillbf.position.setZ(prev_bf.position.z() + ( (bf.position.z() - prev_bf.position.z()) * zn)) # logger.debug("key: %s, n: %s, xn: %s, yn: %s, zn: %s, xa: %s", k, prev_frame + n, xn, yn, zn, ( bf.position.x() - prev_bf.position.x()) * xn ) # logger.debug("position: prev: %s, fill: %s ", prev_bf.position, fillbf.position ) else: fillbf.position = copy.deepcopy(prev_bf.position) # logger.debug("position stop: %s,%s prev: %s, fill: %s ", prev_frame + n, k, prev_bf.position, bf.position ) return fillbf # 最後まで行っても見つからなければ、最終項目を返す return copy.deepcopy(frames[bone_name][-1])
def read_vmd_file(self, filepath): # VMDファイルをバイナリ読み込み self.buffer = open(filepath, "rb").read() # vmdバージョン signature = self.unpack(30, "30s") logger.debug("signature %s", signature) motion = VmdMotion() # モーションパス motion.path = filepath # モデル名 model_bname, model_name = self.read_text(20) logger.debug("model_bname %s, model_name: %s", model_bname, model_name) motion.model_name = model_name # モーション数 motion.motion_cnt = self.read_uint(4) logger.debug("motion.motion_cnt %s", motion.motion_cnt) # モーションのあるキーのINDEX motion_indexes = {} # 1F分のモーション情報 for n in range(motion.motion_cnt): frame = VmdBoneFrame() frame.key = True frame.read = True # ボーン ---------------------- # ボーン名 bone_bname, bone_name = self.read_text(15) frame.name = bone_bname frame.format_name = bone_name logger.debug("name: %s, format_name %s", bone_bname, bone_name) # フレームIDX frame.frame = self.read_uint(4) logger.debug("frame.frame %s", frame.frame) # 位置X,Y,Z frame.position = self.read_Vector3D() logger.debug("frame.position %s", frame.position) # オリジナルを保持 frame.org_position = copy.deepcopy(frame.position) # 回転X,Y,Z,scalar frame.rotation = self.read_Quaternion() logger.debug("frame.rotation %s", frame.rotation) logger.debug("frame.rotation.euler %s", frame.rotation.toEulerAngles()) # オリジナルを保持 frame.org_rotation = copy.deepcopy(frame.rotation) # 補間曲線 frame.complement = list(self.unpack(64, "64B", True)) logger.debug("complement %s", frame.complement) # オリジナルの補間曲線を保持しておく frame.org_complement = copy.deepcopy(frame.complement) logger.debug("org_complement %s", frame.org_complement) if bone_name not in motion.frames: # まだ辞書にない場合、配列追加 motion.frames[bone_name] = [] motion_indexes[bone_name] = {} is_not_existed = True if frame.frame in motion_indexes[bone_name]: is_not_existed = False # 辞書の該当部分にボーンフレームを追加 if is_not_existed == True: motion.frames[bone_name].append(frame) motion_indexes[bone_name][frame.frame] = frame.frame if frame.frame > motion.last_motion_frame: # 最終フレームを記録 motion.last_motion_frame = frame.frame if n % 10000 == 0: print("VMDモーション読み込み キー: %s" % n) # ソート for k, v in motion.frames.items(): motion.frames[k] = sorted(v, key=lambda u: u.frame) # モーフ数 motion.morph_cnt = self.read_uint(4) logger.debug("motion.morph_cnt %s", motion.morph_cnt) # モーションのあるキーのINDEX morph_indexes = {} # 1F分のモーフ情報 for n in range(motion.morph_cnt): morph = VmdMorphFrame() # ボーン ---------------------- # ボーン名 morph_bname, morph_name = self.read_text(15) morph.name = morph_bname morph.format_name = morph_name logger.debug("name: %s, format_name %s", morph_bname, morph_name) # フレームIDX morph.frame = self.read_uint(4) logger.debug("morph.frame %s", morph.frame) # 度数 morph.ratio = self.read_float(4) logger.debug("morph.ratio %s", morph.ratio) if morph_name not in motion.morphs: # まだ辞書にない場合、配列追加 motion.morphs[morph_name] = [] morph_indexes[morph_name] = {} is_not_existed = True if morph.frame in morph_indexes[morph_name]: is_not_existed = False if is_not_existed == True: # まだなければ辞書の該当部分にモーフフレームを追加 motion.morphs[morph_name].append(morph) morph_indexes[morph_name][morph.frame] = morph.frame if n % 1000 == 0: print("VMDモーション読み込み モーフ: %s" % n) # ソート for k, v in motion.morphs.items(): motion.morphs[k] = sorted(v, key=lambda u: u.frame) # カメラ数 motion.camera_cnt = self.read_uint(4) logger.debug("motion.camera_cnt %s", motion.camera_cnt) # 1F分のカメラ情報 for _ in range(motion.camera_cnt): camera = VmdCameraFrame() # フレームIDX camera.frame = self.read_uint(4) logger.debug("camera.frame %s", camera.frame) # 距離 camera.length = self.read_float(4) logger.debug("camera.length %s", camera.length) # 位置X,Y,Z camera.position = self.read_Vector3D() logger.debug("camera.position %s", camera.position) # 角度(オイラー角) camera.euler = self.read_Vector3D() logger.debug("camera.euler %s", camera.euler) # 補間曲線 camera.complement = self.unpack(24, "24B", True) logger.debug("camera.complement %s", camera.complement) # 視野角 camera.angle = self.read_uint(4) logger.debug("camera.angle %s", camera.angle) # パース有無 camera.perspective = self.unpack(1, "B") logger.debug("camera.perspective %s", camera.perspective) # カメラを追加 motion.cameras.append(camera) # ソート motion.cameras = sorted(motion.cameras, key=lambda u: u.frame) # 照明数 try: motion.light_cnt = self.read_uint(4) logger.debug("motion.light_cnt %s", motion.light_cnt) except Exception as e: # 情報がない場合、catchして握りつぶす motion.light_cnt = 0 # 1F分の照明情報 for _ in range(motion.light_cnt): light = VmdLightFrame() # フレームIDX light.frame = self.read_uint(4) logger.debug("light.frame %s", light.frame) # 照明色(RGBだが、下手に数値が変わるのも怖いのでV3D) light.color = self.read_Vector3D() logger.debug("light.color %s", light.color) # 照明位置 light.position = self.read_Vector3D() logger.debug("light.position %s", light.position) # 追加 motion.lights.append(light) # セルフシャドウ数 try: motion.shadow_cnt = self.read_uint(4) logger.debug("motion.shadow_cnt %s", motion.shadow_cnt) # 1F分のシャドウ情報 for _ in range(motion.shadow_cnt): shadow = VmdShadowFrame() # フレームIDX shadow.frame = self.read_uint(4) logger.debug("shadow.frame %s", shadow.frame) # シャドウ種別 shadow.type = self.read_uint(1) logger.debug("shadow.type %s", shadow.type) # 距離 shadow.distance = self.read_float() logger.debug("shadow.distance %s", shadow.distance) # 追加 motion.shadows.append(shadow) except Exception as e: # 情報がない場合、catchして握りつぶす motion.shadow_cnt = 0 # IK数 try: motion.ik_cnt = self.read_uint(4) logger.debug("motion.ik_cnt %s", motion.ik_cnt) # 1F分のIK情報 for _ in range(motion.ik_cnt): ik = VmdShowIkFrame() # フレームIDX ik.frame = self.read_uint(4) logger.debug("ik.frame %s", ik.frame) # モデル表示, 0:OFF, 1:ON ik.show = self.read_uint(1) logger.debug("ik.show %s", ik.show) # 記録するIKの数 ik.ik_count = self.read_uint(4) logger.debug("ik.ik_count %s", ik.ik_count) for _ in range(ik.ik_count): ik_info = VmdInfoIk() # IK名 ik_bname, ik_name = self.read_text(20) ik_info.name = ik_bname logger.debug("ik_info.name %s", ik_name) # モデル表示, 0:OFF, 1:ON ik_info.onoff = self.read_uint(1) logger.debug("ik_info.onoff %s", ik_info.onoff) ik.ik.append(ik_info) # 追加 motion.showiks.append(ik) except Exception as e: # 昔のMMD(MMDv7.39.x64以前)はIK情報がないため、catchして握りつぶす motion.ik_cnt = 0 # ハッシュを設定 motion.digest = self.hexdigest(filepath) logger.debug("motion: %s, hash: %s", motion.path, motion.digest) return motion
def positions_to_frames(pos, vis, frame_num=0, center_enabled=False, head_rotation=None): """convert positions to bone frames""" frames = [] if len(pos) < 17: return frames # センター if center_enabled: bf = VmdBoneFrame() bf.name = b'\x83\x5a\x83\x93\x83\x5e\x81\x5b' # 'センター' bf.frame = frame_num bf.position = pos[7] frames.append(bf) # 上半身 bf = VmdBoneFrame() bf.name = b'\x8f\xe3\x94\xbc\x90\x67' # '上半身' bf.frame = frame_num direction = pos[8] - pos[7] up = QVector3D.crossProduct(direction, (pos[14] - pos[11])).normalized() upper_body_orientation = QQuaternion.fromDirection(direction, up) initial = QQuaternion.fromDirection(QVector3D(0, 1, 0), QVector3D(0, 0, 1)) bf.rotation = upper_body_orientation * initial.inverted() frames.append(bf) upper_body_rotation = bf.rotation # 下半身 bf = VmdBoneFrame() bf.name = b'\x89\xba\x94\xbc\x90\x67' # '下半身' bf.frame = frame_num direction = pos[0] - pos[7] up = QVector3D.crossProduct(direction, (pos[4] - pos[1])) lower_body_orientation = QQuaternion.fromDirection(direction, up) initial = QQuaternion.fromDirection(QVector3D(0, -1, 0), QVector3D(0, 0, 1)) bf.rotation = lower_body_orientation * initial.inverted() lower_body_rotation = bf.rotation frames.append(bf) # 首は回転させず、頭のみ回転させる # 頭 bf = VmdBoneFrame() bf.name = b'\x93\xaa' # '頭' bf.frame = frame_num if head_rotation is None: # direction = pos[10] - pos[9] direction = pos[10] - pos[8] up = QVector3D.crossProduct((pos[9] - pos[8]), (pos[10] - pos[9])) orientation = QQuaternion.fromDirection(direction, up) initial_orientation = QQuaternion.fromDirection(QVector3D(0, 1, 0), QVector3D(1, 0, 0)) rotation = orientation * initial_orientation.inverted() bf.rotation = upper_body_rotation.inverted() * rotation else: bf.rotation = upper_body_rotation.inverted() * head_rotation frames.append(bf) # 左腕 bf = VmdBoneFrame() bf.name = b'\x8d\xb6\x98\x72' # '左腕' bf.frame = frame_num direction = pos[12] - pos[11] up = QVector3D.crossProduct((pos[12] - pos[11]), (pos[13] - pos[12])) orientation = QQuaternion.fromDirection(direction, up) initial_orientation = QQuaternion.fromDirection(QVector3D(1.73, -1, 0), QVector3D(1, 1.73, 0)) rotation = orientation * initial_orientation.inverted() # 左腕ポーンの回転から親ボーンの回転を差し引いてbf.rotationに格納する。 # upper_body_rotation * bf.rotation = rotation なので、 bf.rotation = upper_body_rotation.inverted() * rotation left_arm_rotation = bf.rotation # 後で使うので保存しておく if vis[6]: # 左ひじが見えているなら frames.append(bf) # 左ひじ bf = VmdBoneFrame() bf.name = b'\x8d\xb6\x82\xd0\x82\xb6' # '左ひじ' bf.frame = frame_num direction = pos[13] - pos[12] up = QVector3D.crossProduct((pos[12] - pos[11]), (pos[13] - pos[12])) orientation = QQuaternion.fromDirection(direction, up) initial_orientation = QQuaternion.fromDirection(QVector3D(1.73, -1, 0), QVector3D(1, 1.73, 0)) rotation = orientation * initial_orientation.inverted() # 左ひじポーンの回転から親ボーンの回転を差し引いてbf.rotationに格納する。 # upper_body_rotation * left_arm_rotation * bf.rotation = rotation なので、 bf.rotation = left_arm_rotation.inverted() * upper_body_rotation.inverted() * rotation # bf.rotation = (upper_body_rotation * left_arm_rotation).inverted() * rotation # 別の表現 if vis[6] and vis[7]: # 左ひじと左手首が見えているなら frames.append(bf) # 右腕 bf = VmdBoneFrame() bf.name = b'\x89\x45\x98\x72' # '右腕' bf.frame = frame_num direction = pos[15] - pos[14] up = QVector3D.crossProduct((pos[15] - pos[14]), (pos[16] - pos[15])) orientation = QQuaternion.fromDirection(direction, up) initial_orientation = QQuaternion.fromDirection(QVector3D(-1.73, -1, 0), QVector3D(1, -1.73, 0)) rotation = orientation * initial_orientation.inverted() bf.rotation = upper_body_rotation.inverted() * rotation right_arm_rotation = bf.rotation if vis[3]: # 右ひじが見えているなら frames.append(bf) # 右ひじ bf = VmdBoneFrame() bf.name = b'\x89\x45\x82\xd0\x82\xb6' # '右ひじ' bf.frame = frame_num direction = pos[16] - pos[15] up = QVector3D.crossProduct((pos[15] - pos[14]), (pos[16] - pos[15])) orientation = QQuaternion.fromDirection(direction, up) initial_orientation = QQuaternion.fromDirection(QVector3D(-1.73, -1, 0), QVector3D(1, -1.73, 0)) rotation = orientation * initial_orientation.inverted() bf.rotation = right_arm_rotation.inverted() * upper_body_rotation.inverted() * rotation if vis[3] and vis[4]: # 右ひじと右手首が見えているなら frames.append(bf) # 左足 bf = VmdBoneFrame() bf.name = b'\x8d\xb6\x91\xab' # '左足' bf.frame = frame_num direction = pos[5] - pos[4] up = QVector3D.crossProduct((pos[5] - pos[4]), (pos[6] - pos[5])) orientation = QQuaternion.fromDirection(direction, up) initial_orientation = QQuaternion.fromDirection(QVector3D(0, -1, 0), QVector3D(-1, 0, 0)) rotation = orientation * initial_orientation.inverted() bf.rotation = lower_body_rotation.inverted() * rotation left_leg_rotation = bf.rotation if vis[12]: # 左ひざが見えているなら frames.append(bf) # 左ひざ bf = VmdBoneFrame() bf.name = b'\x8d\xb6\x82\xd0\x82\xb4' # '左ひざ' bf.frame = frame_num direction = pos[6] - pos[5] up = QVector3D.crossProduct((pos[5] - pos[4]), (pos[6] - pos[5])) orientation = QQuaternion.fromDirection(direction, up) initial_orientation = QQuaternion.fromDirection(QVector3D(0, -1, 0), QVector3D(-1, 0, 0)) rotation = orientation * initial_orientation.inverted() bf.rotation = left_leg_rotation.inverted() * lower_body_rotation.inverted() * rotation if vis[12] and vis[13]: # 左ひざと左足首が見えているなら frames.append(bf) # 右足 bf = VmdBoneFrame() bf.name = b'\x89\x45\x91\xab' # '右足' bf.frame = frame_num direction = pos[2] - pos[1] up = QVector3D.crossProduct((pos[2] - pos[1]), (pos[3] - pos[2])) orientation = QQuaternion.fromDirection(direction, up) initial_orientation = QQuaternion.fromDirection(QVector3D(0, -1, 0), QVector3D(-1, 0, 0)) rotation = orientation * initial_orientation.inverted() bf.rotation = lower_body_rotation.inverted() * rotation right_leg_rotation = bf.rotation if vis[9]: # 右ひざが見えているなら frames.append(bf) # 右ひざ bf = VmdBoneFrame() bf.name = b'\x89\x45\x82\xd0\x82\xb4' # '右ひざ' bf.frame = frame_num direction = pos[3] - pos[2] up = QVector3D.crossProduct((pos[2] - pos[1]), (pos[3] - pos[2])) orientation = QQuaternion.fromDirection(direction, up) initial_orientation = QQuaternion.fromDirection(QVector3D(0, -1, 0), QVector3D(-1, 0, 0)) rotation = orientation * initial_orientation.inverted() bf.rotation = right_leg_rotation.inverted() * lower_body_rotation.inverted() * rotation if vis[9] and vis[10]: # 右ひざと右足首が見えているなら frames.append(bf) return frames
def read_vmd_file(self, filename): """Read VMD data to a file""" fin = open(filename, "rb").read() motion = VmdMotion() # vmdバージョン signature = struct.unpack_from("30s", fin, 0) # vmdバージョンは英語だけなので、とりあえずutf-8変換 motion.signature = byte_decode(signature[0], "utf-8") logger.debug("signature %s", motion.signature) # モデル名 model_name = struct.unpack_from("20s", fin, 30) # 文字コード encoding = get_encoding(model_name[0]) motion.model_name = byte_decode(model_name[0], encoding) logger.debug("model_name %s", motion.model_name) # モーション数 motion_cnt = struct.unpack_from("I", fin, 50) motion.motion_cnt = motion_cnt[0] logger.debug("motion_cnt %s", motion.motion_cnt) # 1F分の情報 for n in range(motion.motion_cnt): frame = VmdBoneFrame() # ボーン名 bone_bname = struct.unpack_from("15s", fin, 54 + (n * 111)) # ボーン名はそのまま追加 frame.name = bone_bname[0] # ボーン名を分かる形に変換(キー用) bone_name = byte_decode(bone_bname[0], encoding) logger.debug("frame.name %s", bone_name) # フレームIDX frame.frame = struct.unpack_from("I", fin, 69 + (n * 111))[0] logger.debug("frame.frame %s", frame.frame) # 位置X,Y,Z frame.position.setX(struct.unpack_from("f", fin, 73 + (n * 111))[0]) frame.position.setY(struct.unpack_from("f", fin, 77 + (n * 111))[0]) frame.position.setZ(struct.unpack_from("f", fin, 81 + (n * 111))[0]) logger.debug("frame.position %s", frame.position) # 回転X,Y,Z,scalar frame.rotation.setX(struct.unpack_from("f", fin, 85 + (n * 111))[0]) frame.rotation.setY(struct.unpack_from("f", fin, 89 + (n * 111))[0]) frame.rotation.setZ(struct.unpack_from("f", fin, 93 + (n * 111))[0]) frame.rotation.setScalar(struct.unpack_from("f", fin, 97 + (n * 111))[0]) logger.debug("frame.rotation %s", frame.rotation) logger.debug("frame.rotation.toEulerAngles() %s", frame.rotation.toEulerAngles()) # 補間曲線 frame.complement=['%x' % x for x in range(struct.unpack_from("64B", fin, 101 + (n * 111))[0]) ] logger.debug("frame.complement %s", frame.complement) if bone_name not in motion.frames: # まだ辞書にない場合、配列追加 motion.frames[bone_name] = [] # 辞書の該当部分にボーンフレームを追加 motion.frames[bone_name].append(frame) return motion
def main(csv_bone_path, csv_morph_path, csv_camera_path): try: bone_frames = [] morph_frames = [] camera_frames = [] if csv_bone_path: output_vmd_path = re.sub( r'\.csv$', "_{0:%Y%m%d_%H%M%S}.vmd".format(datetime.now()), csv_bone_path) # ボーンCSV読み込み with open(csv_bone_path, encoding='cp932', mode='r') as f: reader = csv.reader(f) next(reader) # ヘッダーを読み飛ばす for row in reader: bf = VmdBoneFrame() # ボーン名 bf.format_name = row[0] # ボーン名(バイト) bf.name = bf.format_name.encode('cp932').decode( 'shift_jis').encode('shift_jis') # フレーム bf.frame = int(row[1]) # 位置 bf.position = QVector3D(float(row[2]), float(row[3]), float(row[4])) # 回転 bf.rotation = QQuaternion.fromEulerAngles( float(row[5]), float(row[6]) * -1, float(row[7]) * -1) # 補間曲線 bf.complement = [ int(row[8]), int(row[9]), int(row[10]), int(row[11]), int(row[12]), int(row[13]), int(row[14]), int(row[15]), int(row[16]), int(row[17]), int(row[18]), int(row[19]), int(row[20]), int(row[21]), int(row[22]), int(row[23]), int(row[24]), int(row[25]), int(row[26]), int(row[27]), int(row[28]), int(row[29]), int(row[30]), int(row[31]), int(row[32]), int(row[33]), int(row[34]), int(row[35]), int(row[36]), int(row[37]), int(row[38]), int(row[39]), int(row[40]), int(row[41]), int(row[42]), int(row[43]), int(row[44]), int(row[45]), int(row[46]), int(row[47]), int(row[48]), int(row[49]), int(row[50]), int(row[51]), int(row[52]), int(row[53]), int(row[54]), int(row[55]), int(row[56]), int(row[57]), int(row[58]), int(row[59]), int(row[60]), int(row[61]), int(row[62]), int(row[63]), int(row[64]), int(row[65]), int(row[66]), int(row[67]), int(row[68]), int(row[69]), int(row[70]), int(row[71]) ] bone_frames.append(bf) # logger.debug("bf: %s %s", bf.name, bf) if csv_morph_path: output_vmd_path = re.sub( r'\.csv$', "_{0:%Y%m%d_%H%M%S}.vmd".format(datetime.now()), csv_morph_path) # モーフCSV読み込み with open(csv_morph_path, encoding='cp932', mode='r') as f: reader = csv.reader(f) next(reader) # ヘッダーを読み飛ばす for row in reader: mf = VmdMorphFrame() # ボーン名 mf.format_name = row[0] # ボーン名(バイト) mf.name = mf.format_name.encode('cp932').decode( 'shift_jis').encode('shift_jis') # フレーム mf.frame = int(row[1]) # 位置 mf.ratio = float(row[2]) morph_frames.append(mf) writer = VmdWriter() if len(morph_frames) > 0 or len(bone_frames) > 0: # ボーン・モーフモーション生成 writer.write_vmd_file(output_vmd_path, "CSV Convert Model", bone_frames, morph_frames, [], [], [], []) print("ボーン・モーフVMD出力成功: %s" % output_vmd_path) if csv_camera_path: # カメラCSV読み込み with open(csv_camera_path, encoding='cp932', mode='r') as f: reader = csv.reader(f) next(reader) # ヘッダーを読み飛ばす for row in reader: cf = VmdCameraFrame() # フレーム cf.frame = int(row[0]) # 位置 cf.position = QVector3D(float(row[1]), float(row[2]), float(row[3])) # 回転(オイラー角) cf.euler = QVector3D(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.complement = [ int(row[10]), int(row[11]), int(row[12]), int(row[13]), int(row[14]), int(row[15]), int(row[16]), int(row[17]), int(row[18]), int(row[19]), int(row[20]), int(row[21]), int(row[22]), int(row[23]), int(row[24]), int(row[25]), int(row[26]), int(row[27]), int(row[28]), int(row[29]), int(row[30]), int(row[31]), int(row[32]), int(row[33]) ] camera_frames.append(cf) # logger.debug("bf: %s %s", bf.name, bf) if len(camera_frames) > 0: # カメラモーション生成 output_vmd_path = re.sub( r'\.csv$', "_{0:%Y%m%d_%H%M%S}.vmd".format(datetime.now()), csv_camera_path) writer.write_vmd_file(output_vmd_path, "CSV Convert Model", [], [], camera_frames, [], [], []) print("カメラVMD出力成功: %s" % output_vmd_path) except Exception: print("■■■■■■■■■■■■■■■■■") print("■ **ERROR** ") print("■ CSV解析処理が意図せぬエラーで終了しました。") print("■■■■■■■■■■■■■■■■■") print(traceback.format_exc())
def test_reset_complement_06(self): # モーションの宣言 motion = VmdMotion() motion.frames["左手首"] = [] bf = VmdBoneFrame() bf.format_name = "左手首" bf.frame = 0 bf.read = True bf.key = True for r in utils.R_x1_idxs: bf.complement[r] = 10 for r in utils.R_y1_idxs: bf.complement[r] = 10 for r in utils.R_x2_idxs: bf.complement[r] = 10 for r in utils.R_y2_idxs: bf.complement[r] = 10 motion.frames["左手首"].append(bf) bf = VmdBoneFrame() bf.format_name = "左手首" bf.frame = 5 bf.read = True # 間に有効キーあり bf.key = True for r in utils.R_x1_idxs: bf.complement[r] = 30 for r in utils.R_y1_idxs: bf.complement[r] = 30 for r in utils.R_x2_idxs: bf.complement[r] = 30 for r in utils.R_y2_idxs: bf.complement[r] = 30 motion.frames["左手首"].append(bf) bf = VmdBoneFrame() bf.format_name = "左手首" bf.frame = 10 bf.read = False # 有効キーの次に無効キー bf.key = False for r in utils.R_x1_idxs: bf.complement[r] = 30 for r in utils.R_y1_idxs: bf.complement[r] = 30 for r in utils.R_x2_idxs: bf.complement[r] = 30 for r in utils.R_y2_idxs: bf.complement[r] = 30 motion.frames["左手首"].append(bf) bf = VmdBoneFrame() bf.format_name = "左手首" bf.frame = 15 bf.read = False # 2つ目の有効キー bf.key = True for r in utils.R_x1_idxs: bf.complement[r] = 30 for r in utils.R_y1_idxs: bf.complement[r] = 30 for r in utils.R_x2_idxs: bf.complement[r] = 30 for r in utils.R_y2_idxs: bf.complement[r] = 30 motion.frames["左手首"].append(bf) bf = VmdBoneFrame() bf.format_name = "左手首" bf.frame = 30 bf.read = True bf.key = True for r in utils.R_x1_idxs: bf.complement[r] = 10 for r in utils.R_y1_idxs: bf.complement[r] = 10 for r in utils.R_x2_idxs: bf.complement[r] = 20 for r in utils.R_y2_idxs: bf.complement[r] = 20 motion.frames["左手首"].append(bf) # 腕リストの宣言 arm_links = self.create_arm_links() # 処理実施 sub_arm_ik.reset_complement(motion, arm_links) # --------------------------------- # 前回の開始X for r in utils.R_x1_idxs: self.assertEqual(10, motion.frames["左手首"][0].complement[r]) # 前回の開始Y for r in utils.R_y1_idxs: self.assertEqual(10, motion.frames["左手首"][0].complement[r]) # 前回の終了X for r in utils.R_x2_idxs: self.assertEqual(10, motion.frames["左手首"][0].complement[r]) # 前回の終了Y for r in utils.R_y2_idxs: self.assertEqual(10, motion.frames["左手首"][0].complement[r]) # --------------------------------- # 今回の開始X for r in utils.R_x1_idxs: self.assertEqual(30, motion.frames["左手首"][1].complement[r]) # 今回の開始Y for r in utils.R_y1_idxs: self.assertEqual(30, motion.frames["左手首"][1].complement[r]) # 今回の終了X for r in utils.R_x2_idxs: self.assertEqual(30, motion.frames["左手首"][1].complement[r]) # 今回の終了Y for r in utils.R_y2_idxs: self.assertEqual(30, motion.frames["左手首"][1].complement[r]) # --------------------------------- # 無効次回の開始X for r in utils.R_x1_idxs: self.assertEqual(30, motion.frames["左手首"][2].complement[r]) # 無効次回の開始Y for r in utils.R_y1_idxs: self.assertEqual(30, motion.frames["左手首"][2].complement[r]) # 無効次回の終了X for r in utils.R_x2_idxs: self.assertEqual(30, motion.frames["左手首"][2].complement[r]) # 無効次回の終了Y for r in utils.R_y2_idxs: self.assertEqual(30, motion.frames["左手首"][2].complement[r]) # --------------------------------- # 有効次回の開始X for r in utils.R_x1_idxs: self.assertEqual(17, motion.frames["左手首"][3].complement[r]) # 有効次回の開始Y for r in utils.R_y1_idxs: self.assertEqual(17, motion.frames["左手首"][3].complement[r]) # 有効次回の終了X for r in utils.R_x2_idxs: self.assertEqual(34, motion.frames["左手首"][3].complement[r]) # 有効次回の終了Y for r in utils.R_y2_idxs: self.assertEqual(34, motion.frames["左手首"][3].complement[r]) # --------------------------------- # 次回の開始X for r in utils.R_x1_idxs: self.assertEqual(29, motion.frames["左手首"][4].complement[r]) # 次回の開始Y for r in utils.R_y1_idxs: self.assertEqual(29, motion.frames["左手首"][4].complement[r]) # 次回の終了X for r in utils.R_x2_idxs: self.assertEqual(70, motion.frames["左手首"][4].complement[r]) # 次回の終了Y for r in utils.R_y2_idxs: self.assertEqual(70, motion.frames["左手首"][4].complement[r])
def exec(motion, trace_model, replace_model, output_vmd_path, org_motion_frames, camera_motion): if not camera_motion: # カメラモーションが未指定の場合、処理しない return True if camera_motion.camera_cnt == 0: # カメラフレームがなかったら処理しない return True # 足IKのXYZの比率 # 横と前後方向の移動はこれと同じ幅に補正されるので。 xz_ratio, _, _ = sub_move.calc_leg_ik_ratio(trace_model, replace_model) # 全身比率、頭身比率、Z比率 height_ratio, head_ratio, head_z_ratio, org_head_ratio = calc_head_ratio( trace_model, replace_model) # 情報提供 # print("カメラ補正: x=%s, y=%s, z=%s + %s 距離=%s" % (xz_ratio, y_ratio, xz_ratio, replace_model.bones[offset_target_bone].offset_z, xz_ratio)) print("カメラ補正: x=%s, y=%s, z=%s" % (xz_ratio, height_ratio, xz_ratio)) print("カメラ補正: 全身比率=%s, 頭身比率=%s(%s)" % (height_ratio, head_ratio, org_head_ratio)) # 作成元モデル:全身のリンク org_body_links, org_link_names = create_body_links(trace_model) # 変換先モデル:全身のリンク rep_body_links, rep_link_names = create_body_links(replace_model) # 移動縮尺 prev_cf_pos = None is_only_length = False for cf_idx, cf in enumerate(camera_motion.cameras): logger.debug("cf.frame: %s, l: %s, a: %s", cf.frame, cf.length, cf.angle) logger.info("cf.p: %s", cf.position) if prev_cf_pos: logger.info("prev_cf_pos: %s", prev_cf_pos) logger.debug("cf.e: %s", cf.euler) org_nearest_bone_name = None org_nearest_bone_global_pos = None rep_bone_global_pos = None org_head_min_boundingbox = None org_head_max_boundingbox = None rep_head_min_boundingbox = None rep_head_max_boundingbox = None if cf_idx > 0 and prev_cf_pos is not None and cf.position == prev_cf_pos: # 前回と同じカメラ位置の場合、カメラ位置コピー logger.info("カメラ位置コピー") # 実際にコピーするのは、サイジングした位置情報 cf.position = copy.deepcopy(camera_motion.cameras[cf_idx - 1].position) is_only_length = True else: # カメラ位置が変わっている場合、距離以外も調整する is_only_length = False # 作成元モデルの各ボーングローバル位置 org_body_global_3ds = create_body_global_3ds( trace_model, org_motion_frames, org_body_links, cf.frame, rep_link_names) # 作成元モデルのどのボーンに最も近いか org_nearest_bone_name, org_nearest_bone_global_pos, org_nearest_parent_bone_name, _ = calc_nearest_bone( org_body_global_3ds, cf) # 作成元モデルの最も近いボーン名と同じボーンの位置を、変換先モデルから取得する rep_bone_global_pos, _ = create_bone_global_3ds( replace_model, motion.frames, rep_body_links, cf.frame, rep_link_names, org_nearest_bone_name, org_nearest_parent_bone_name) # 直近ボーンが頭付近の場合、頭の頂点を取得 org_head_min_boundingbox = org_head_max_boundingbox = rep_head_min_boundingbox = rep_head_max_boundingbox = None if org_nearest_bone_name in ["首", "頭"]: bf = VmdBoneFrame() bf.frame = cf.frame org_head_min_boundingbox, org_head_max_boundingbox = calc_boundingbox( trace_model, "頭", org_body_links[org_link_names["頭"]], org_motion_frames, bf) rep_head_min_boundingbox, rep_head_max_boundingbox = calc_boundingbox( replace_model, "頭", rep_body_links[rep_link_names["頭"]], motion.frames, bf) # 前回カメラ位置として保持(カメラ位置変えるので、コピー保持) prev_cf_pos = copy.deepcopy(cf.position) # 新しいカメラを生成 create_camera_frame( org_nearest_bone_name, org_nearest_bone_global_pos, rep_bone_global_pos, \ org_head_min_boundingbox, org_head_max_boundingbox, rep_head_min_boundingbox, rep_head_max_boundingbox, \ is_only_length, xz_ratio, height_ratio, head_ratio, head_z_ratio, org_head_ratio, cf ) logger.info("[after] cf.frame: %s", cf.frame) logger.info("[after] cf.position: %s", cf.position) logger.info("[after] cf.euler: %s", cf.euler) logger.info("[after] cf.length: %s", cf.length) # if cf.frame > 540: # break print("カメラ調整終了") return True
def read_vmd_file(self, filename): # VMDファイルをバイナリ読み込み self.buffer = open(filename, "rb").read() # vmdバージョン signature = self.unpack(30, "30s") logger.debug("signature %s", signature) motion = VmdMotion() # モデル名 model_bname, model_name = self.read_text(20) logger.debug("model_bname %s", model_bname) motion.model_name = model_name # モーション数 motion.motion_cnt = self.read_uint(4) logger.debug("motion.motion_cnt %s", motion.motion_cnt) # 1F分のモーション情報 for _ in range(motion.motion_cnt): frame = VmdBoneFrame() # ボーン ---------------------- # ボーン名 bone_bname, bone_name = self.read_text(15) frame.name = bone_bname frame.format_name = bone_name logger.debug("name: %s, format_name %s", bone_bname, bone_name) # フレームIDX frame.frame = self.read_uint(4) logger.debug("frame.frame %s", frame.frame) # 位置X,Y,Z frame.position = self.read_Vector3D() logger.debug("frame.position %s", frame.position) # 回転X,Y,Z,scalar frame.rotation = self.read_Quaternion() logger.debug("frame.rotation %s", frame.rotation) logger.debug("frame.rotation.euler %s", frame.rotation.toEulerAngles()) # 補間曲線 frame.complement = self.unpack(64, "64B", True) logger.debug("complement %s", frame.complement) if bone_name not in motion.frames: # まだ辞書にない場合、配列追加 motion.frames[bone_name] = [] # 辞書の該当部分にボーンフレームを追加 motion.frames[bone_name].append(frame) if frame.frame > motion.last_motion_frame: # 最終フレームを記録 motion.last_motion_frame = frame.frame # ソート for k, v in motion.frames.items(): motion.frames[k] = sorted(v, key=lambda u: u.frame) # モーフ数 motion.morph_cnt = self.read_uint(4) logger.debug("motion.morph_cnt %s", motion.morph_cnt) # 1F分のモーフ情報 for _ in range(motion.morph_cnt): morph = VmdMorphFrame() # ボーン ---------------------- # ボーン名 morph_bname, morph_name = self.read_text(15) morph.name = morph_bname morph.format_name = morph_name logger.debug("name: %s, format_name %s", morph_bname, morph_name) # フレームIDX morph.frame = self.read_uint(4) logger.debug("morph.frame %s", morph.frame) # 度数 morph.ratio = self.read_float(4) logger.debug("morph.ratio %s", morph.ratio) if morph_name not in motion.morphs: # まだ辞書にない場合、配列追加 motion.morphs[morph_name] = [] # 辞書の該当部分にモーフフレームを追加 motion.morphs[morph_name].append(morph) # ソート for k, v in motion.morphs.items(): motion.morphs[k] = sorted(v, key=lambda u: u.frame) # カメラ数 motion.camera_cnt = self.read_uint(4) logger.debug("motion.camera_cnt %s", motion.camera_cnt) # 1F分のカメラ情報 for _ in range(motion.camera_cnt): camera = VmdCameraFrame() # フレームIDX camera.frame = self.read_uint(4) logger.debug("camera.frame %s", camera.frame) # 距離 camera.length = self.read_float(4) logger.debug("camera.length %s", camera.length) # 位置X,Y,Z camera.position = self.read_Vector3D() logger.debug("camera.position %s", camera.position) # 角度(オイラー角) camera.euler = self.read_Vector3D() logger.debug("camera.euler %s", camera.euler) # 補間曲線 camera.complement = self.unpack(24, "24B", True) logger.debug("camera.complement %s", camera.complement) # 視野角 camera.angle = self.read_uint(4) logger.debug("camera.angle %s", camera.angle) # パース有無 camera.perspective = self.unpack(1, "B") # カメラを追加 motion.cameras.append(camera) # ソート motion.cameras = sorted(motion.cameras, key=lambda u: u.frame) return motion