def save(self): # 履歴保持 self.bulk_csv_file_ctrl.save() # JSON出力 MFileUtils.save_history(self.frame.mydir_path, self.frame.file_hitories)
def on_convert_smooth(self, event: wx.Event): # フォーム無効化 self.disable() # タブ固定 self.fix_tab() # コンソールクリア self.console_ctrl.Clear() # 出力先をスムージングパネルのコンソールに変更 sys.stdout = self.console_ctrl wx.GetApp().Yield() self.smooth_vmd_file_ctrl.save() self.smooth_model_file_ctrl.save() # JSON出力 MFileUtils.save_history(self.frame.mydir_path, self.frame.file_hitories) self.elapsed_time = 0 result = True result = self.smooth_vmd_file_ctrl.is_valid( ) and self.smooth_model_file_ctrl.is_valid() and result if not result: # 終了音 self.frame.sound_finish() # タブ移動可 self.release_tab() # フォーム有効化 self.enable() # 出力先をデフォルトに戻す sys.stdout = self.frame.file_panel_ctrl.console_ctrl return result # スムージング変換開始 if self.convert_smooth_worker: logger.error("まだ処理が実行中です。終了してから再度実行してください。", decoration=MLogger.DECORATION_BOX) else: # 別スレッドで実行 self.convert_smooth_worker = SmoothWorkerThread( self.frame, SmoothThreadEvent, self.frame.is_saving) self.convert_smooth_worker.start() return result event.Skip()
def on_pick_file(self, event): if len(self.file_ctrl.GetPath()) == 0 and self.frame.file_hitories and self.file_histories_key in self.frame.file_hitories and len(self.frame.file_hitories[self.file_histories_key]) > 0: # パスが未指定である場合、直近のパスを設定してひらく self.file_ctrl.SetInitialDirectory(MFileUtils.get_dir_path(self.frame.file_hitories[self.file_histories_key][0])) event.Skip()
def parse(cls, version_name: str): parser = argparse.ArgumentParser() parser.add_argument('--motion_path', dest='motion_path', help='input vmd', type=str) parser.add_argument('--model_path', dest='model_path', help='model_path', type=str) parser.add_argument('--loop_cnt', dest='loop_cnt', help='loop_cnt', type=int) parser.add_argument('--interpolation', dest='interpolation', help='interpolation', type=int) parser.add_argument("--verbose", type=int, default=20) args = parser.parse_args() # ログディレクトリ作成 os.makedirs("log", exist_ok=True) MLogger.initialize(level=args.verbose, is_file=True) try: motion = VmdReader(args.motion_path).read_data() model = PmxReader(args.model_path).read_data() # 出力ファイルパス output_vmd_path = MFileUtils.get_output_smooth_vmd_path( motion.path, model.path, "", args.interpolation, args.loop_cnt, True) options = MSmoothOptions(\ version_name=version_name, \ logging_level=args.verbose, \ motion=motion, \ model=model, \ output_path=output_vmd_path, \ loop_cnt=args.loop_cnt, \ interpolation=args.interpolation, \ monitor=sys.stdout, \ is_file=True, \ outout_datetime=logger.outout_datetime, \ max_workers=1) return options except SizingException as se: logger.error("スムージング処理が処理できないデータで終了しました。\n\n%s", se.message, decoration=MLogger.DECORATION_BOX) except Exception as e: logger.critical("スムージング処理が意図せぬエラーで終了しました。", e, decoration=MLogger.DECORATION_BOX)
def set_output_vmd_path(self, event, is_force=False): output_leg_fk2ik_vmd_path = MFileUtils.get_output_leg_fk2ik_vmd_path( self.leg_fk2ik_vmd_file_ctrl.file_ctrl.GetPath(), self.leg_fk2ik_model_file_ctrl.file_ctrl.GetPath(), self.output_leg_fk2ik_vmd_file_ctrl.file_ctrl.GetPath(), is_force) self.output_leg_fk2ik_vmd_file_ctrl.file_ctrl.SetPath(output_leg_fk2ik_vmd_path) if len(output_leg_fk2ik_vmd_path) >= 255 and os.name == "nt": logger.error("生成予定のファイルパスがWindowsの制限を超えています。\n生成予定パス: {0}".format(output_leg_fk2ik_vmd_path), decoration=MLogger.DECORATION_BOX)
def on_export(self, event: wx.Event): org_choice_values = [] rep_rx_choice_values = [] rep_ry_choice_values = [] rep_rz_choice_values = [] rep_mx_choice_values = [] rep_my_choice_values = [] rep_mz_choice_values = [] for m in self.get_bone_list(): org_choice_values.append(m[0]) rep_rx_choice_values.append(m[1]) rep_ry_choice_values.append(m[2]) rep_rz_choice_values.append(m[3]) rep_mx_choice_values.append(m[4]) rep_my_choice_values.append(m[5]) rep_mz_choice_values.append(m[6]) output_bone_path = MFileUtils.get_output_split_bone_path( self.panel.vmd_file_ctrl.file_ctrl.GetPath(), self.panel.model_file_ctrl.file_ctrl.GetPath()) try: with open(output_bone_path, encoding='cp932', mode='w', newline='') as f: cw = csv.writer(f, delimiter=",", quotechar='"', quoting=csv.QUOTE_ALL) cw.writerow(org_choice_values) cw.writerow(rep_rx_choice_values) cw.writerow(rep_ry_choice_values) cw.writerow(rep_rz_choice_values) cw.writerow(rep_mx_choice_values) cw.writerow(rep_my_choice_values) cw.writerow(rep_mz_choice_values) logger.info("出力成功: %s" % output_bone_path) dialog = wx.MessageDialog(self.frame, "多段ボーンデータのエクスポートに成功しました \n'%s'" % (output_bone_path), style=wx.OK) dialog.ShowModal() dialog.Destroy() except Exception: dialog = wx.MessageDialog( self.frame, "多段ボーンデータのエクスポートに失敗しました \n'%s'\n\n%s." % (output_bone_path, traceback.format_exc()), style=wx.OK) dialog.ShowModal() dialog.Destroy()
def save(self): # 履歴保持 self.frame.file_panel_ctrl.file_set.save() # multiのも全部保持 for file_set in self.frame.multi_panel_ctrl.file_set_list: file_set.save() # カメラ履歴保持 self.frame.camera_panel_ctrl.save() # カメラ元モデル保持 for camera_set in self.frame.camera_panel_ctrl.camera_set_dict.values( ): camera_set.camera_model_file_ctrl.save() # JSON出力 MFileUtils.save_history(self.frame.mydir_path, self.frame.file_hitories)
def set_output_vmd_path(self, event, is_force=False): output_camera_vmd_path = MFileUtils.get_output_camera_vmd_path( self.parent.camera_vmd_file_ctrl.file_ctrl.GetPath(), self.frame.file_panel_ctrl.file_set.rep_model_file_ctrl.file_ctrl. GetPath(), self.parent.output_camera_vmd_file_ctrl.file_ctrl.GetPath(), self.parent.camera_length_slider.GetValue(), is_force) self.parent.output_camera_vmd_file_ctrl.file_ctrl.SetPath( output_camera_vmd_path) if len(output_camera_vmd_path) >= 255 and os.name == "nt": logger.error("生成予定のファイルパスがWindowsの制限を超えています。\n生成予定パス: {0}".format( output_camera_vmd_path), decoration=MLogger.DECORATION_BOX)
def set_output_vmd_path(self, event, is_force=False): output_smooth_vmd_path = MFileUtils.get_output_smooth_vmd_path( self.smooth_vmd_file_ctrl.file_ctrl.GetPath(), self.smooth_model_file_ctrl.file_ctrl.GetPath(), self.output_smooth_vmd_file_ctrl.file_ctrl.GetPath(), self.interpolation_ctrl.GetSelection(), self.loop_cnt_ctrl.GetValue(), is_force) self.output_smooth_vmd_file_ctrl.file_ctrl.SetPath( output_smooth_vmd_path) if len(output_smooth_vmd_path) >= 255 and os.name == "nt": logger.error("生成予定のファイルパスがWindowsの制限を超えています。\n生成予定パス: {0}".format( output_smooth_vmd_path), decoration=MLogger.DECORATION_BOX)
def on_export(self, event: wx.Event): org_morph_list = [] rep_morph_list = [] ratio_list = [] for m in self.get_morph_list(): org_morph_list.append(m[0]) rep_morph_list.append(m[1]) ratio_list.append(m[2]) output_morph_path = MFileUtils.get_output_morph_path( self.file_set.motion_vmd_file_ctrl.file_ctrl.GetPath(), self.file_set.org_model_file_ctrl.file_ctrl.GetPath(), self.file_set.rep_model_file_ctrl.file_ctrl.GetPath()) try: with open(output_morph_path, encoding='cp932', mode='w', newline='') as f: cw = csv.writer(f, delimiter=",", quotechar='"', quoting=csv.QUOTE_ALL) # 元モーフ行 cw.writerow(org_morph_list) # 先モーフ行 cw.writerow(rep_morph_list) # 大きさ cw.writerow(ratio_list) logger.info("出力成功: %s" % output_morph_path) dialog = wx.MessageDialog(self.frame, "モーフデータのエクスポートに成功しました \n'%s'" % (output_morph_path), style=wx.OK) dialog.ShowModal() dialog.Destroy() except Exception: dialog = wx.MessageDialog( self.frame, "モーフデータのエクスポートに失敗しました \n'%s'\n\n%s." % (output_morph_path, traceback.format_exc()), style=wx.OK) dialog.ShowModal() dialog.Destroy()
def set_output_vmd_path(self, event, is_force=False): output_vmd_path = MFileUtils.get_output_vmd_path( self.motion_vmd_file_ctrl.file_ctrl.GetPath(), self.rep_model_file_ctrl.file_ctrl.GetPath(), self.org_model_file_ctrl.title_parts_ctrl.GetValue(), self.rep_model_file_ctrl.title_parts_ctrl.GetValue(), self.frame.arm_panel_ctrl.arm_process_flg_avoidance.GetValue(), self.frame.arm_panel_ctrl.arm_process_flg_alignment.GetValue(), (self.set_no in self.frame.morph_panel_ctrl.morph_set_dict and self.frame.morph_panel_ctrl.morph_set_dict[self.set_no].is_set_morph()) \ or (self.set_no in self.frame.morph_panel_ctrl.bulk_morph_set_dict and len(self.frame.morph_panel_ctrl.bulk_morph_set_dict[self.set_no]) > 0), self.output_vmd_file_ctrl.file_ctrl.GetPath(), is_force) self.output_vmd_file_ctrl.file_ctrl.SetPath(output_vmd_path) if len(output_vmd_path) >= 255 and os.name == "nt": logger.error("生成予定のファイルパスがWindowsの制限を超えています。\n生成予定パス: {0}".format( output_vmd_path), decoration=MLogger.DECORATION_BOX)
def on_show_history(self, event): # 入力行を伸ばす hs = copy.deepcopy(self.frame.file_hitories[self.file_histories_key]) hs.extend(["" for x in range(self.frame.file_hitories["max"] + 1)]) with wx.SingleChoiceDialog(self.parent, "ファイルを選んでダブルクリック、またはOKボタンをクリックしてください。", caption="ファイル履歴選択", choices=hs[:(self.frame.file_hitories["max"] + 1)], style=wx.CAPTION | wx.CLOSE_BOX | wx.SYSTEM_MENU | wx.OK | wx.CANCEL | wx.CENTRE) as choiceDialog: if choiceDialog.ShowModal() == wx.ID_CANCEL: return # the user changed their mind # ファイルピッカーに選択したパスを設定 self.file_ctrl.SetPath(choiceDialog.GetStringSelection()) self.file_ctrl.UpdatePickerFromTextCtrl() self.file_ctrl.SetInitialDirectory(MFileUtils.get_dir_path(choiceDialog.GetStringSelection())) # ファイル変更処理 self.on_change_file(wx.FileDirPickerEvent())
def convert_csv(self): # モーションVMDディレクトリパス motion_vmd_dir_path = MFileUtils.get_dir_path(self.options.motion.path) # モーションVMDファイル名・拡張子 motion_vmd_file_name, motion_vmd_ext = os.path.splitext( os.path.basename(self.options.motion.path)) dt_now = datetime.now() if self.options.motion.motion_cnt == self.options.motion.morph_cnt == self.options.motion.camera_cnt == 0: logger.warning("出力可能なモーションデータ(ボーン・モーフ・カメラ)がありません", decoration=MLogger.DECORATION_BOX) if self.options.motion.motion_cnt > 0: # ボーンモーションがある場合、ボーンモーション出力 bone_fpath = "{0}\\{1}_bone_{2:%Y%m%d_%H%M%S}.csv".format( motion_vmd_dir_path, motion_vmd_file_name, dt_now) # Excel等で読めるよう、cp932限定 with open(bone_fpath, encoding='cp932', mode='w') as f: s = "ボーン名,フレーム,位置X,位置Y,位置Z,回転X,回転Y,回転Z,【X_x1】,Y_x1,Z_x1,R_x1,【X_y1】,Y_y1,Z_y1,R_y1,【X_x2】,Y_x2,Z_x2,R_x2,【X_y2】,Y_y2,Z_y2,R_y2," + \ "【Y_x1】,Z_x1,R_x1,X_y1,【Y_y1】,Z_y1,R_y1,X_x2,【Y_x2】,Z_x2,R_x2,X_y2,【Y_y2】,Z_y2,R_y2,1,【Z_x1】,R_x1,X_y1,Y_y1,【Z_y1】,R_y1,X_x2,Y_x2,【Z_x2】" + \ ",R_x2,X_y2,Y_y2,【Z_y2】,R_y2,1,0,【R_x1】,X_y1,Y_y1,Z_y1,【R_y1】,X_x2,Y_x2,Z_x2,【R_x2】,X_y2,Y_y2,Z_y2,【R_y2】,01,00,00" f.write(s) f.write("\n") for bone_name in self.options.motion.bones: for fno in self.options.motion.get_bone_fnos(bone_name): bf = self.options.motion.bones[bone_name][fno] s = "{0},{1},{2},{3},{4},{5},{6},{7},{8}".format(bf.name, bf.fno, \ bf.position.x(), bf.position.y(), bf.position.z(), bf.rotation.toEulerAngles4MMD().x(), \ bf.rotation.toEulerAngles4MMD().y(), bf.rotation.toEulerAngles4MMD().z(), ','.join([str(i) for i in bf.interpolation])) f.write(s) f.write("\n") logger.info("ボーンモーションCSV: %s", bone_fpath, decoration=MLogger.DECORATION_BOX) if self.options.motion.morph_cnt > 0: # モーフ出力 morph_fpath = "{0}\\{1}_morph_{2:%Y%m%d_%H%M%S}.csv".format( motion_vmd_dir_path, motion_vmd_file_name, dt_now) # Excel等で読めるよう、cp932限定 with open(morph_fpath, encoding='cp932', mode='w') as f: s = "モーフ名,フレーム,大きさ" f.write(s) f.write("\n") for morph_name in self.options.motion.morphs: for fno in self.options.motion.get_morph_fnos(morph_name): mf = self.options.motion.morphs[morph_name][fno] s = "{0},{1},{2}".format(mf.name, mf.fno, mf.ratio) f.write(s) f.write("\n") logger.info("モーフモーションCSV: %s", morph_fpath, decoration=MLogger.DECORATION_BOX) if self.options.motion.camera_cnt > 0: # カメラ出力 camera_fpath = "{0}\\{1}_camera_{2:%Y%m%d_%H%M%S}.csv".format( motion_vmd_dir_path, motion_vmd_file_name, dt_now) # Excel等で読めるよう、cp932限定 with open(camera_fpath, encoding='cp932', mode='w') as f: s = "フレーム,位置X,位置Y,位置Z,回転X,回転Y,回転Z,距離,視野角,パース,X_x1,Y_x1,Z_x1,R_x1,L_x1,VA_x1," + \ "X_y1,Y_y1,Z_y1,R_y1,L_y1,VA_y1,X_x2,Y_x2,Z_x2,R_x2,L_x2,VA_x2, X_y2,Y_y2,Z_y2,R_y2,L_y2,VA_y2" f.write(s) f.write("\n") for fno in self.options.motion.get_camera_fnos(): cf = self.options.motion.cameras[fno] s = "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}" \ .format(cf.fno, cf.position.x(), cf.position.y(), cf.position.z(), -math.degrees(cf.euler.x()), math.degrees(cf.euler.y()), math.degrees(cf.euler.z()), \ -cf.length, cf.angle, cf.perspective, ','.join([str(i) for i in cf.interpolation])) f.write(s) f.write("\n") logger.info("カメラモーションCSV: %s", camera_fpath, decoration=MLogger.DECORATION_BOX) return True
def __init__(self, parent, mydir_path: str, version_name: str, logging_level: int, is_saving: bool, is_out_log: bool): self.version_name = version_name self.logging_level = logging_level self.is_out_log = is_out_log self.is_saving = is_saving self.mydir_path = mydir_path self.elapsed_time = 0 self.popuped_finger_warning = False self.worker = None self.load_worker = None wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=u"VMDサイジング ローカル版 {0}".format(self.version_name), \ pos=wx.DefaultPosition, size=wx.Size(600, 650), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) # ファイル履歴読み込み self.file_hitories = MFileUtils.read_history(self.mydir_path) # --------------------------------------------- self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) bSizer1 = wx.BoxSizer(wx.VERTICAL) self.note_ctrl = wx.Notebook(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0) if self.logging_level == MLogger.FULL or self.logging_level == MLogger.DEBUG_FULL: # フルデータの場合 self.note_ctrl.SetBackgroundColour("RED") elif self.logging_level == MLogger.DEBUG: # テスト(デバッグ版)の場合 self.note_ctrl.SetBackgroundColour("CORAL") elif self.logging_level == MLogger.TIMER: # 時間計測の場合 self.note_ctrl.SetBackgroundColour("YELLOW") elif not is_saving: # ログありの場合、色変え self.note_ctrl.SetBackgroundColour("BLUE") elif is_out_log: # ログありの場合、色変え self.note_ctrl.SetBackgroundColour("AQUAMARINE") else: self.note_ctrl.SetBackgroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW)) # --------------------------------------------- # ファイルタブ self.file_panel_ctrl = FilePanel(self, self.note_ctrl, 0, self.file_hitories) self.note_ctrl.AddPage(self.file_panel_ctrl, u"ファイル", True) # 複数タブ self.multi_panel_ctrl = MultiPanel(self, self.note_ctrl, 1, self.file_hitories) self.note_ctrl.AddPage(self.multi_panel_ctrl, u"複数", False) # モーフタブ self.morph_panel_ctrl = MorphPanel(self, self.note_ctrl, 2) self.note_ctrl.AddPage(self.morph_panel_ctrl, u"モーフ", False) # 腕タブ self.arm_panel_ctrl = ArmPanel(self, self.note_ctrl, 3) self.note_ctrl.AddPage(self.arm_panel_ctrl, u"腕", False) # カメラタブ self.camera_panel_ctrl = CameraPanel(self, self.note_ctrl, 4) self.note_ctrl.AddPage(self.camera_panel_ctrl, u"カメラ", False) # 一括タブ self.bulk_panel_ctrl = BulkPanel(self, self.note_ctrl, 5) self.note_ctrl.AddPage(self.bulk_panel_ctrl, u"一括", False) # CSVタブ self.csv_panel_ctrl = CsvPanel(self, self.note_ctrl, 6) self.note_ctrl.AddPage(self.csv_panel_ctrl, u"CSV", False) # VMDタブ self.vmd_panel_ctrl = VmdPanel(self, self.note_ctrl, 7) self.note_ctrl.AddPage(self.vmd_panel_ctrl, u"VMD", False) # --------------------------------------------- # タブ押下時の処理 self.note_ctrl.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_tab_change) # --------------------------------------------- bSizer1.Add(self.note_ctrl, 1, wx.EXPAND, 5) # デフォルトの出力先はファイルタブのコンソール sys.stdout = self.file_panel_ctrl.console_ctrl # イベントバインド self.Bind(EVT_SIZING_THREAD, self.on_exec_result) self.Bind(EVT_LOAD_THREAD, self.on_load_result) self.SetSizer(bSizer1) self.Layout() self.Centre(wx.BOTH)
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 on_import(self, event: wx.Event): input_bone_path = MFileUtils.get_output_split_bone_path( self.panel.vmd_file_ctrl.file_ctrl.GetPath(), self.panel.model_file_ctrl.file_ctrl.GetPath()) with wx.FileDialog( self.frame, "ボーン組み合わせCSVを読み込む", wildcard=u"CSVファイル (*.csv)|*.csv|すべてのファイル (*.*)|*.*", defaultDir=os.path.dirname(input_bone_path), style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog: if fileDialog.ShowModal() == wx.ID_CANCEL: return # the user changed their mind # Proceed loading the file chosen by the user target_bone_path = fileDialog.GetPath() try: with open(target_bone_path, 'r') as f: cr = csv.reader(f, delimiter=",", quotechar='"') bone_lines = [row for row in cr] if len(bone_lines) == 0: return if not bone_lines[0]: raise Exception("処理対象ボーン名指定なし") org_choice_values = bone_lines[0] if len(bone_lines) >= 4: rep_rx_choice_values = bone_lines[1] rep_ry_choice_values = bone_lines[2] rep_rz_choice_values = bone_lines[3] else: rep_rx_choice_values = [""] rep_ry_choice_values = [""] rep_rz_choice_values = [""] if len(bone_lines) >= 7: rep_mx_choice_values = bone_lines[4] rep_my_choice_values = bone_lines[5] rep_mz_choice_values = bone_lines[6] else: rep_mx_choice_values = [""] rep_my_choice_values = [""] rep_mz_choice_values = [""] for (ov, rmxv, rmyv, rmzv, rrxv, rryv, rrzv) in zip(org_choice_values, rep_mx_choice_values, rep_my_choice_values, rep_mz_choice_values, \ rep_rx_choice_values, rep_ry_choice_values, rep_rz_choice_values): oc = self.org_choices[-1] rrxc = self.rep_rx_choices[-1] rryc = self.rep_ry_choices[-1] rrzc = self.rep_rz_choices[-1] rmxc = self.rep_mx_choices[-1] rmyc = self.rep_my_choices[-1] rmzc = self.rep_mz_choices[-1] is_seted = False for v, c in [(ov, oc), (rmxv, rmxc), (rmyv, rmyc), (rmzv, rmzc), (rrxv, rrxc), (rryv, rryc), (rrzv, rrzc)]: logger.debug("v: %s, c: %s", v, c) for n in range(c.GetCount()): if c.GetString(n).strip() == v: c.SetSelection(n) is_seted = True if is_seted: # 行追加 self.add_line() else: # ひとつも追加がなかった場合、終了 break # パス変更 self.panel.set_output_vmd_path(event) except Exception: dialog = wx.MessageDialog( self.frame, "CSVファイルが読み込めませんでした '%s'\n\n%s." % (target_bone_path, traceback.format_exc()), style=wx.OK) dialog.ShowModal() dialog.Destroy()
from module.MOptions import MOptions from utils.MLogger import MLogger from utils import MFileUtils from service.SizingService import SizingService from utils.MException import SizingException VERSION_NAME = "ver5.00" # 指数表記なし、有効小数点桁数6、30を超えると省略あり、一行の文字数200 np.set_printoptions(suppress=True, precision=6, threshold=30, linewidth=200) # Windowsマルチプロセス対策 multiprocessing.freeze_support() if __name__ == '__main__': mydir_path = MFileUtils.get_mydir_path(sys.argv[0]) if len(sys.argv) > 3 and "--motion_path" in sys.argv: if os.name == "nt": import winsound # Windows版のみインポート # 引数指定がある場合、コマンドライン実行 try: SizingService(MOptions.parse(VERSION_NAME)).execute() except SizingException as se: print("サイジング処理が処理できないデータで終了しました。\n\n%s", se.message) except Exception: print("サイジング処理が意図せぬエラーで終了しました。") print(traceback.format_exc()) finally: logging.shutdown()
def parse(cls, version_name: str): parser = argparse.ArgumentParser() parser.add_argument("--motion_path", required=True, type=(lambda x: list(map(str, x.split(';'))))) parser.add_argument("--org_model_path", required=True, type=(lambda x: list(map(str, x.split(';'))))) parser.add_argument("--rep_model_path", required=True, type=(lambda x: list(map(str, x.split(';'))))) parser.add_argument("--detail_stance_flg", required=True, type=(lambda x: list(map(int, x.split(';'))))) parser.add_argument("--twist_flg", required=True, type=(lambda x: list(map(int, x.split(';'))))) parser.add_argument("--arm_process_flg_avoidance", type=int, default=0) parser.add_argument("--avoidance_target_list", default=[], type=(lambda x: list(map(str, x.split(';'))))) parser.add_argument("--arm_process_flg_alignment", type=int, default=0) parser.add_argument("--alignment_finger_flg", type=int, default=0) parser.add_argument("--alignment_floor_flg", type=int, default=0) parser.add_argument("--alignment_distance_wrist", type=float, default=1.7) parser.add_argument("--alignment_distance_finger", type=float, default=1.4) parser.add_argument("--alignment_distance_floor", type=float, default=1.8) parser.add_argument("--arm_check_skip_flg", type=int, default=0) parser.add_argument("--camera_motion_path", type=str, default="") parser.add_argument("--camera_org_model_path", default=[], type=(lambda x: list(map(str, x.split(';'))))) parser.add_argument("--camera_offset_y", default=[], type=(lambda x: list(map(str, x.split(';'))))) parser.add_argument("--verbose", type=int, default=20) args = parser.parse_args() # ログディレクトリ作成 os.makedirs("log", exist_ok=True) MLogger.initialize(level=args.verbose, is_file=True) try: arm_process_flg_avoidance = True if args.arm_process_flg_avoidance == 1 else False arm_process_flg_alignment = True if args.arm_process_flg_alignment == 1 else False alignment_finger_flg = True if args.alignment_finger_flg == 1 else False alignment_floor_flg = True if args.alignment_floor_flg == 1 else False arm_check_skip_flg = True if args.arm_check_skip_flg == 1 else False arm_options = MArmProcessOptions( arm_process_flg_avoidance, \ {0: [(a.strip() if len(a.strip()) > 0 else "") for a in args.avoidance_target_list]}, \ arm_process_flg_alignment, \ alignment_finger_flg, \ alignment_floor_flg, \ args.alignment_distance_wrist, \ args.alignment_distance_finger, \ args.alignment_distance_floor, \ arm_check_skip_flg ) # 元モデルが未指定の場合、空で処理する if not args.camera_org_model_path or ( len(args.camera_org_model_path) == 1 and len(args.camera_org_model_path[0]) == 0): args.camera_org_model_path = [] for org_path in args.org_model_path: args.camera_org_model_path.append("") # オフセットYが未指定の場合、0で処理する if not args.camera_offset_y or (len(args.camera_offset_y) == 1 and len(args.camera_offset_y[0]) == 0): args.camera_offset_y = [] for org_path in args.org_model_path: args.camera_offset_y.append(0) data_set_list = [] for set_no, (motion_path, org_model_path, rep_model_path, detail_stance_flg_val, twist_flg_val, camera_org_model_path, camera_offset_y) in enumerate( \ zip(args.motion_path, args.org_model_path, args.rep_model_path, args.detail_stance_flg, args.twist_flg, args.camera_org_model_path, \ args.camera_offset_y)): # noqa display_set_no = "【No.{0}】".format(set_no + 1) # モーションパス -------- logger.info("%s 調整対象モーションVMD/VPDファイル 読み込み開始", display_set_no) file_name, input_ext = os.path.splitext( os.path.basename(motion_path)) if input_ext.lower() == ".vmd": motion_reader = VmdReader(motion_path) elif input_ext.lower() == ".vpd": motion_reader = VpdReader(motion_path) else: raise SizingException( "{0}.motion_path 読み込み失敗(拡張子不正): {1}".format( display_set_no, os.path.basename(motion_path))) motion = motion_reader.read_data() logger.info("%s 調整対象モーションVMD/VPDファイル 読み込み成功 %s", display_set_no, os.path.basename(motion_path)) # 元モデル ---------- logger.info("%s モーション作成元モデルPMXファイル 読み込み開始", display_set_no) file_name, input_ext = os.path.splitext( os.path.basename(org_model_path)) if input_ext.lower() == ".pmx": org_model_reader = PmxReader(org_model_path) else: raise SizingException( "{0}.org_model_path 読み込み失敗(拡張子不正): {1}".format( display_set_no, os.path.basename(org_model_path))) org_model = org_model_reader.read_data() logger.info("%s モーション作成元モデルPMXファイル 読み込み成功 %s", display_set_no, os.path.basename(org_model_path)) # 先モデル ---------- logger.info("%s モーション変換先モデルPMXファイル 読み込み開始", display_set_no) file_name, input_ext = os.path.splitext( os.path.basename(rep_model_path)) if input_ext.lower() == ".pmx": rep_model_reader = PmxReader(rep_model_path) else: raise SizingException( "{0}.rep_model_path 読み込み失敗(拡張子不正): {1}".format( display_set_no, os.path.basename(rep_model_path))) rep_model = rep_model_reader.read_data() logger.info("%s モーション変換先モデルPMXファイル 読み込み成功 %s", display_set_no, os.path.basename(rep_model_path)) # 元モデル ---------- if len(camera_org_model_path) > 0: logger.info("%s カメラ作成元モデルPMXファイル 読み込み開始", display_set_no) file_name, input_ext = os.path.splitext( os.path.basename(camera_org_model_path)) if input_ext.lower() == ".pmx": camera_org_model_reader = PmxReader( camera_org_model_path) else: raise SizingException( "{0}.camera_org_model_path 読み込み失敗(拡張子不正): {1}". format(display_set_no, os.path.basename(camera_org_model_path))) camera_org_model = camera_org_model_reader.read_data() logger.info("%s カメラ作成元モデルPMXファイル 読み込み成功 %s", display_set_no, os.path.basename(camera_org_model_path)) else: # カメラ元モデルが未指定の場合、作成元モデルをそのまま流用 camera_org_model = org_model detail_stance_flg = True if detail_stance_flg_val == 1 else False twist_flg = True if twist_flg_val == 1 else False # 出力ファイルパス output_vmd_path = MFileUtils.get_output_vmd_path( motion_path, rep_model_path, detail_stance_flg, twist_flg, arm_process_flg_avoidance, arm_process_flg_alignment, False, "", True) data_set = MOptionsDataSet( motion, org_model, rep_model, output_vmd_path, detail_stance_flg, twist_flg, [], camera_org_model, camera_offset_y, [ "センターXZ補正", "上半身補正", "下半身補正", "足IK補正", "つま先IK補正", "つま先補正", "肩補正", "センターY補正" ]) data_set_list.append(data_set) if len(args.camera_motion_path) != 0: # カメラパス -------- logger.info("調整対象カメラVMDファイル 読み込み開始") file_name, input_ext = os.path.splitext( os.path.basename(args.camera_motion_path)) if input_ext.lower() == ".vmd": camera_motion_reader = VmdReader(args.camera_motion_path) else: raise SizingException( "camera_motion_path 読み込み失敗(拡張子不正): %s", os.path.basename(args.camera_motion_path)) camera_motion = camera_motion_reader.read_data() camera_output_vmd_path = MFileUtils.get_output_camera_vmd_path( args.camera_motion_path, data_set_list[0].rep_model.path, "") logger.info("調整対象カメラVMD/VPDファイル 読み込み成功 %s", os.path.basename(args.camera_motion_path)) else: camera_motion = None camera_output_vmd_path = None options = MOptions(\ version_name=version_name, \ logging_level=args.verbose, \ data_set_list=data_set_list, \ arm_options=arm_options, \ camera_motion=camera_motion, \ camera_output_vmd_path=camera_output_vmd_path, \ monitor=sys.stdout, \ is_file=True, \ outout_datetime=logger.outout_datetime, \ max_workers=1) return options except SizingException as se: logger.error("サイジング処理が処理できないデータで終了しました。\n\n%s", se.message, decoration=MLogger.DECORATION_BOX) except Exception as e: logger.critical("サイジング処理が意図せぬエラーで終了しました。", e, decoration=MLogger.DECORATION_BOX)
def is_valid(self): if self.set_no == 0: # CSVとかのファイルは番号出力なし display_set_no = "" else: display_set_no = "{0}番目の".format(self.set_no) if self.is_aster and self.set_no <= 1: base_file_path = self.file_ctrl.GetPath() if os.path.exists(base_file_path): file_path_list = [base_file_path] else: file_path_list = [p for p in glob.glob(base_file_path) if os.path.isfile(p)] if len(file_path_list) == 0: logger.error("{0}{1}の条件に合致するファイルが見つかりませんでした。\n入力パス: {2}".format( display_set_no, self.title, self.file_ctrl.GetPath()), decoration=MLogger.DECORATION_BOX) return False file_path = file_path_list[0] else: file_path = self.file_ctrl.GetPath() if not self.is_save and not os.path.exists(file_path): if self.required: logger.error("{0}{1}が見つかりませんでした。\n入力パス: {2}".format( display_set_no, self.title, self.file_ctrl.GetPath()), decoration=MLogger.DECORATION_BOX) return False else: # 任意の場合、ファイルパスがなければスルー return True if not self.is_save and not os.path.isfile(file_path): logger.error("{0}{1}が正常なファイルとして見つかりませんでした。\n入力パス: {2}".format( display_set_no, self.title, self.file_ctrl.GetPath()), decoration=MLogger.DECORATION_BOX) return False # 拡張子 _, ext = os.path.splitext(os.path.basename(file_path)) if ext[1:].lower() not in self.file_type: logger.error("{0}{1}の拡張子が正しくありません。\n入力パス: {2}\n設定可能拡張子: {3}".format( display_set_no, self.title, self.file_ctrl.GetPath(), self.file_type), decoration=MLogger.DECORATION_BOX) return False # 親ディレクトリ取得 if self.is_save: # 書き込みはそのまま親 dir_path = os.path.dirname(self.file_ctrl.GetPath()) else: # 読み取りは解析する dir_path = MFileUtils.get_dir_path(self.file_ctrl.GetPath()) if not os.path.exists(dir_path): logger.error("{0}{1}の親フォルダが見つかりませんでした。\n入力パス: {2}".format( display_set_no, self.title, dir_path), decoration=MLogger.DECORATION_BOX) return False if not os.path.isdir(dir_path): logger.error("{0}{1}の親フォルダが正常なフォルダとして見つかりませんでした。\n入力パス: {2}".format( display_set_no, self.title, dir_path), decoration=MLogger.DECORATION_BOX) return False if not os.access(dir_path, os.W_OK): logger.error("{0}{1}の親フォルダに書き込み権限がありません。\n入力パス: {2}".format( display_set_no, self.title, dir_path), decoration=MLogger.DECORATION_BOX) return False # 出力系の場合、自身のファイル上書き用の書き込み権限 if self.is_save and os.path.isfile(self.file_ctrl.GetPath()) and not os.access(self.file_ctrl.GetPath(), os.W_OK): logger.error("{0}{1}に書き込み権限がありません。\n入力パス: {2}".format( display_set_no, self.title, self.file_ctrl.GetPath()), decoration=MLogger.DECORATION_BOX) return False return True
def on_import(self, event: wx.Event): input_morph_path = MFileUtils.get_output_morph_path( self.file_set.motion_vmd_file_ctrl.file_ctrl.GetPath(), self.file_set.org_model_file_ctrl.file_ctrl.GetPath(), self.file_set.rep_model_file_ctrl.file_ctrl.GetPath()) with wx.FileDialog( self.frame, "モーフ組み合わせCSVを読み込む", wildcard=u"CSVファイル (*.csv)|*.csv|すべてのファイル (*.*)|*.*", defaultDir=os.path.dirname(input_morph_path), style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog: if fileDialog.ShowModal() == wx.ID_CANCEL: return # the user changed their mind # Proceed loading the file chosen by the user target_morph_path = fileDialog.GetPath() try: with open(target_morph_path, 'r') as f: cr = csv.reader(f, delimiter=",", quotechar='"') morph_lines = [row for row in cr] if len(morph_lines) == 0: return org_choice_values = morph_lines[0] rep_choice_values = morph_lines[1] rep_rate_values = morph_lines[2] logger.debug("org_choice_values: %s", org_choice_values) logger.debug("rep_choice_values: %s", rep_choice_values) logger.debug("rep_rate_values: %s", rep_rate_values) if len(org_choice_values) == 0 or len( rep_choice_values) == 0 or len( rep_rate_values) == 0: return for vcv, rcv, rrv in zip(org_choice_values, rep_choice_values, rep_rate_values): vc = self.org_choices[-1] rc = self.rep_choices[-1] rr = self.ratios[-1] # 全件なめる for v, c in [(vcv, vc), (rcv, rc)]: logger.debug("v: %s, c: %s", v, c) is_seted = False for n in range(c.GetCount()): for p in ["目", "眉", "口", "他", "?"]: for s in ["", "○", "●", "▲"]: # パネル情報を含める txt = "{0}{1}:{2}".format(p, s, v[:10]) # if v == vcv: # logger.debug("txt: %s, c.GetString(n): %s", txt, c.GetString(n)) if c.GetString(n).strip() == txt: logger.debug( "[HIT] txt: %s, c.GetString(n): %s, n: %s", txt, c.GetString(n), n) # パネルとモーフ名で一致している場合、採用 c.SetSelection(n) is_seted = True break if is_seted: break # 大きさ補正を設定する try: rr.SetValue(float(rrv)) except Exception: pass # 行追加 self.add_line() # パス変更 self.file_set.set_output_vmd_path(event) except Exception: dialog = wx.MessageDialog( self.frame, "CSVファイルが読み込めませんでした '%s'\n\n%s." % (target_morph_path, traceback.format_exc()), style=wx.OK) dialog.ShowModal() dialog.Destroy()
def on_convert(self, event: wx.Event): self.timer.Stop() self.Unbind(wx.EVT_TIMER, id=TIMER_ID) # フォーム無効化 self.disable() # タブ固定 self.fix_tab() # コンソールクリア self.console_ctrl.Clear() # 出力先をスムージングパネルのコンソールに変更 sys.stdout = self.console_ctrl wx.GetApp().Yield() self.smooth_vmd_file_ctrl.save() self.smooth_model_file_ctrl.save() # JSON出力 MFileUtils.save_history(self.frame.mydir_path, self.frame.file_hitories) self.elapsed_time = 0 result = True result = self.smooth_vmd_file_ctrl.is_valid( ) and self.smooth_model_file_ctrl.is_valid() and result # スムージング変換開始 if self.smooth_btn_ctrl.GetLabel( ) == "スムージング停止" and self.convert_smooth_worker: # フォーム無効化 self.disable() # 停止状態でボタン押下時、停止 self.convert_smooth_worker.stop() # タブ移動可 self.frame.release_tab() # フォーム有効化 self.frame.enable() # ワーカー終了 self.convert_smooth_worker = None # プログレス非表示 self.gauge_ctrl.SetValue(0) logger.warning("スムージングを中断します。", decoration=MLogger.DECORATION_BOX) self.smooth_btn_ctrl.SetLabel("スムージング実行") event.Skip(False) elif not self.convert_smooth_worker: # フォーム無効化 self.disable() # タブ固定 self.fix_tab() # コンソールクリア self.console_ctrl.Clear() # ラベル変更 self.smooth_btn_ctrl.SetLabel("スムージング停止") self.smooth_btn_ctrl.Enable() self.convert_smooth_worker = SmoothWorkerThread( self.frame, SmoothThreadEvent, self.frame.is_saving, self.frame.is_out_log) self.convert_smooth_worker.start() event.Skip() else: logger.error("まだ処理が実行中です。終了してから再度実行してください。", decoration=MLogger.DECORATION_BOX) event.Skip(False) return result
def __init__(self, parent, mydir_path: str, version_name: str, logging_level: int, is_saving: bool, is_out_log: bool): self.version_name = version_name self.logging_level = logging_level self.is_out_log = is_out_log self.is_saving = is_saving self.mydir_path = mydir_path self.elapsed_time = 0 self.popuped_finger_warning = False self.worker = None self.load_worker = None wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=u"モーションサポーター ローカル版 {0}".format(self.version_name), \ pos=wx.DefaultPosition, size=wx.Size(600, 650), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) # ファイル履歴読み込み self.file_hitories = MFileUtils.read_history(self.mydir_path) # --------------------------------------------- self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) bSizer1 = wx.BoxSizer(wx.VERTICAL) self.note_ctrl = wx.Notebook(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0) if self.logging_level == MLogger.FULL or self.logging_level == MLogger.DEBUG_FULL: # フルデータの場合 self.note_ctrl.SetBackgroundColour("RED") elif self.logging_level == MLogger.DEBUG: # テスト(デバッグ版)の場合 self.note_ctrl.SetBackgroundColour("CORAL") elif self.logging_level == MLogger.TIMER: # 時間計測の場合 self.note_ctrl.SetBackgroundColour("YELLOW") elif not is_saving: # ログありの場合、色変え self.note_ctrl.SetBackgroundColour("BLUE") elif is_out_log: # ログありの場合、色変え self.note_ctrl.SetBackgroundColour("AQUAMARINE") else: self.note_ctrl.SetBackgroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW)) # --------------------------------------------- # 全親タブ self.parent_panel_ctrl = ParentPanel(self, self.note_ctrl, 9) self.note_ctrl.AddPage(self.parent_panel_ctrl, u"全親移植", False) # ゆらぎタブ self.noise_panel_ctrl = NoisePanel(self, self.note_ctrl, 9) self.note_ctrl.AddPage(self.noise_panel_ctrl, u"ゆらぎ複製", False) # 多段分割タブ self.multi_split_panel_ctrl = MultiSplitPanel(self, self.note_ctrl, 9) self.note_ctrl.AddPage(self.multi_split_panel_ctrl, u"多段分割", False) # 多段統合タブ self.multi_join_panel_ctrl = MultiJoinPanel(self, self.note_ctrl, 9) self.note_ctrl.AddPage(self.multi_join_panel_ctrl, u"多段統合", False) # 足FK2FKタブ self.leg_fk2ik_panel_ctrl = LegFKtoIKPanel(self, self.note_ctrl, 9) self.note_ctrl.AddPage(self.leg_fk2ik_panel_ctrl, u"足FKtoIK", False) # スムーズタブ self.smooth_panel_ctrl = SmoothPanel(self, self.note_ctrl, 5) self.note_ctrl.AddPage(self.smooth_panel_ctrl, u"スムーズ", False) # ブレンドタブ self.blend_panel_ctrl = BlendPanel(self, self.note_ctrl, 6) self.note_ctrl.AddPage(self.blend_panel_ctrl, u"ブレンド", False) # 補間タブ self.bezier_panel_ctrl = BezierPanel(self, self.note_ctrl, 9) self.note_ctrl.AddPage(self.bezier_panel_ctrl, u"補間", False) # # 腕IKtoFKタブ # self.arm_ik2fk_panel_ctrl = ArmIKtoFKPanel(self, self.note_ctrl, 9) # self.note_ctrl.AddPage(self.arm_ik2fk_panel_ctrl, u"腕IKtoFK", False) # --------------------------------------------- # タブ押下時の処理 self.note_ctrl.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_tab_change) # --------------------------------------------- bSizer1.Add(self.note_ctrl, 1, wx.EXPAND, 5) # デフォルトの出力先はファイルタブのコンソール sys.stdout = self.parent_panel_ctrl.console_ctrl self.SetSizer(bSizer1) self.Layout() self.Centre(wx.BOTH)
def blend_morph(self): # モーションVMDディレクトリパス pmx_dir_path = MFileUtils.get_dir_path(self.options.model.path) # モーションVMDファイル名・拡張子 pmx_file_name, pmx_ext = os.path.splitext( os.path.basename(self.options.model.path)) dt_now = datetime.now() blend_fpath = "{0}\\{1}_blend_{2:%Y%m%d_%H%M%S}.vmd".format( pmx_dir_path, pmx_file_name, dt_now) bone_motion = VmdMotion() # 処理対象モーフ名(文字列) target_morphs = self.options.eye_list + self.options.eyebrow_list + self.options.lip_list + self.options.other_list # 処理対象モーフデータ all_morphs = [] for mk, mv in self.options.model.morphs.items(): if mv.display and mv.name in target_morphs: all_morphs.append(mv) bone_motion.morphs[mk] = {} # 変化量(少ない方のの割合を多くする) ratio_values = [self.options.inc_value * x for x in range(math.ceil(self.options.min_value / self.options.inc_value), \ math.ceil(self.options.max_value / self.options.inc_value) + 1) if self.options.min_value <= self.options.inc_value * x <= self.options.max_value] ratio_lower_values = [self.options.inc_value * x for x in range(math.ceil(self.options.min_value / self.options.inc_value), \ math.ceil(self.options.max_value / self.options.inc_value / 2) + 1) if self.options.min_value <= self.options.inc_value * x <= self.options.max_value] ratio_zero_values = [0 for x in range(len(all_morphs))] # 全体の比率増減量 ratio_total_values = copy.deepcopy(ratio_values) ratio_total_values.extend(ratio_lower_values) ratio_total_values.extend(ratio_lower_values) ratio_total_values.extend(ratio_zero_values) # モーフの組合せ数 morph_comb_cnt = 1 # 変化量の組合せ数 ratio_product_cnt = 1 for n in range(len(all_morphs), 0, -1): morph_comb_cnt *= n ratio_product_cnt *= len(ratio_values) morph_total_cnt = 0 if morph_comb_cnt * ratio_product_cnt <= 1000: # モーフの組合せ morph_comb = list( itertools.combinations(all_morphs, len(all_morphs))) logger.debug("morph_comb: %s", len(morph_comb)) # 変化量の直積(同じ値を許容する) ratio_product = list( itertools.product(ratio_values, repeat=len(all_morphs))) logger.debug("ratio_product: %s", len(ratio_product)) # 組合せが1000以下なら組合せをそのまま出力 brend_mr_pairs_list = list( itertools.product(morph_comb, ratio_product)) logger.debug("brend_mr_pairs_list: %s", len(brend_mr_pairs_list)) for mframe, (morphs, ratios) in enumerate(brend_mr_pairs_list): # 上限までしか登録しない if morph_total_cnt > 19000: break for morph, ratio in zip(morphs, ratios): vmd_morph = VmdMorphFrame() vmd_morph.fno = mframe vmd_morph.set_name(morph.name) vmd_morph.ratio = ratio vmd_morph.key = True bone_motion.morphs[morph.name][mframe] = vmd_morph logger.test(vmd_morph) morph_total_cnt += 1 else: # 組合せが1000より多い場合、ランダム for mframe in range(1000): # 上限までしか登録しない if morph_total_cnt > 19000: break for morph in all_morphs: ratio = ratio_total_values[random.randint( 0, len(ratio_values) - 1)] vmd_morph = VmdMorphFrame() vmd_morph.fno = mframe vmd_morph.set_name(morph.name) vmd_morph.ratio = ratio vmd_morph.key = True bone_motion.morphs[morph.name][mframe] = vmd_morph logger.test(vmd_morph) morph_total_cnt += 1 data_set = MOptionsDataSet(bone_motion, self.options.model, self.options.model, blend_fpath, False, False, [], None, 0, []) VmdWriter(data_set).write() logger.info("モーフブレンドVMD: %s", blend_fpath, decoration=MLogger.DECORATION_BOX) return True
from form.MainFrame import MainFrame from utils.MLogger import MLogger from utils import MFileUtils from utils.MException import SizingException VERSION_NAME = "1.02" # 指数表記なし、有効小数点桁数6、30を超えると省略あり、一行の文字数200 np.set_printoptions(suppress=True, precision=6, threshold=30, linewidth=200) # Windowsマルチプロセス対策 multiprocessing.freeze_support() if __name__ == '__main__': mydir_path = MFileUtils.get_mydir_path(sys.argv[0]) if len(sys.argv) > 3 and "--motion_path" in sys.argv: if os.name == "nt": import winsound # Windows版のみインポート # 引数指定がある場合、コマンドライン実行 # try: # SizingService(MOptions.parse(VERSION_NAME)).execute() # except SizingException as se: # print("サポーター処理が処理できないデータで終了しました。\n\n%s", se.message) # except Exception: # print("サポーター処理が意図せぬエラーで終了しました。") # print(traceback.format_exc()) # finally: # logging.shutdown()
def on_convert(self, event: wx.Event): self.timer.Stop() self.Unbind(wx.EVT_TIMER, id=TIMER_ID) # フォーム無効化 self.disable() # タブ固定 self.fix_tab() # コンソールクリア self.console_ctrl.Clear() # 出力先を多段統合パネルのコンソールに変更 sys.stdout = self.console_ctrl self.vmd_file_ctrl.save() self.model_file_ctrl.save() # JSON出力 MFileUtils.save_history(self.frame.mydir_path, self.frame.file_hitories) self.elapsed_time = 0 result = True result = self.vmd_file_ctrl.is_valid() and result result = self.model_file_ctrl.is_valid() and result if len(self.bone_target_txt_ctrl.GetValue()) == 0: logger.error("統合対象ボーンが指定されていません。", decoration=MLogger.DECORATION_BOX) result = False if not result: # 終了音 self.frame.sound_finish() # タブ移動可 self.release_tab() # フォーム有効化 self.enable() return result # 多段統合変換開始 if self.multi_join_btn_ctrl.GetLabel( ) == "多段統合停止" and self.convert_multi_join_worker: # フォーム無効化 self.disable() # 停止状態でボタン押下時、停止 self.convert_multi_join_worker.stop() # タブ移動可 self.frame.release_tab() # フォーム有効化 self.frame.enable() # ワーカー終了 self.convert_multi_join_worker = None # プログレス非表示 self.gauge_ctrl.SetValue(0) logger.warning("多段統合を中断します。", decoration=MLogger.DECORATION_BOX) self.multi_join_btn_ctrl.SetLabel("多段統合") event.Skip(False) elif not self.convert_multi_join_worker: # フォーム無効化 self.disable() # タブ固定 self.fix_tab() # コンソールクリア self.console_ctrl.Clear() # ラベル変更 self.multi_join_btn_ctrl.SetLabel("多段統合停止") self.multi_join_btn_ctrl.Enable() self.convert_multi_join_worker = MultiJoinWorkerThread( self.frame, MultiJoinThreadEvent, self.frame.is_saving, self.frame.is_out_log) self.convert_multi_join_worker.start() event.Skip() else: logger.error("まだ処理が実行中です。終了してから再度実行してください。", decoration=MLogger.DECORATION_BOX) event.Skip(False) return result
def convert_vmd(self): dt_now = datetime.now() bone_fpath = None bone_motion = VmdMotion() if self.options.bone_csv_path and os.path.exists( self.options.bone_csv_path): # ボーンモーションCSVディレクトリパス motion_csv_dir_path = MFileUtils.get_dir_path( self.options.bone_csv_path) # ボーンモーションCSVファイル名・拡張子 motion_csv_file_name, _ = os.path.splitext( os.path.basename(self.options.bone_csv_path)) bone_fpath = "{0}\\{1}_bone_{2:%Y%m%d_%H%M%S}.vmd".format( motion_csv_dir_path, motion_csv_file_name, dt_now) # ボーンCSV読み込み with open(self.options.bone_csv_path, encoding='cp932', mode='r') as f: reader = csv.reader(f) next(reader) # ヘッダーを読み飛ばす cnt = 0 for ridx, row in enumerate(reader): bf = VmdBoneFrame() rno = ridx + 1 try: if len(row) < 0 or not row[0]: logger.error("[ボーン] %s行目のボーン名(1列目)が設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # ボーン名 bf.set_name(row[0]) except Exception as e: logger.error("[ボーン] %s行目のボーン名の読み取りに失敗しました\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False try: if len(row) < 1 or not row[1]: logger.error("[ボーン] %s行目のフレーム番号(2列目)が設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # フレーム bf.fno = int(float(row[1])) if bf.fno < 0: logger.error("[ボーン] %s行目のフレーム番号(2列目)に負数が設定されています", rno, decoration=MLogger.DECORATION_BOX) return False except Exception as e: logger.error( "[ボーン] %s行目のフレーム番号の読み取りに失敗しました\nフレーム番号は半角数字のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False try: if len(row ) < 4 or not row[2] or not row[3] or not row[4]: logger.error("[ボーン] %s行目の位置(3-5列目)のいずれかが設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # 位置 bf.position = MVector3D(float(row[2]), float(row[3]), float(row[4])) except Exception as e: logger.error( "[ボーン] %s行目の位置の読み取りに失敗しました\n位置は半角数字・符号・小数点のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False try: if len(row ) < 7 or not row[5] or not row[6] or not row[7]: logger.error("[ボーン] %s行目の回転(6-8列目)のいずれかが設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # 回転 bf.rotation = MQuaternion.fromEulerAngles( float(row[5]), float(row[6]) * -1, float(row[7]) * -1) except Exception as e: logger.error( "[ボーン] %s行目の回転の読み取りに失敗しました\n位置は半角数字・符号・小数点のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False try: if len(row) < 71: logger.error( "[ボーン] %s行目の補間曲線(9-72列目)のいずれかが設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False for cidx in range(8, 72): if not row[cidx]: logger.error("[ボーン] %s行目の補間曲線の%s番目が設定されていません", rno, cidx - 7, decoration=MLogger.DECORATION_BOX) return False # 補間曲線(一旦floatで読み込んで指数等も読み込んだ後、intに変換) bf.interpolation = [int(float(row[8])), int(float(row[9])), int(float(row[10])), int(float(row[11])), int(float(row[12])), int(float(row[13])), \ int(float(row[14])), int(float(row[15])), int(float(row[16])), int(float(row[17])), int(float(row[18])), int(float(row[19])), \ int(float(row[20])), int(float(row[21])), int(float(row[22])), int(float(row[23])), int(float(row[24])), int(float(row[25])), \ int(float(row[26])), int(float(row[27])), int(float(row[28])), int(float(row[29])), int(float(row[30])), int(float(row[31])), \ int(float(row[32])), int(float(row[33])), int(float(row[34])), int(float(row[35])), int(float(row[36])), int(float(row[37])), \ int(float(row[38])), int(float(row[39])), int(float(row[40])), int(float(row[41])), int(float(row[42])), int(float(row[43])), \ int(float(row[44])), int(float(row[45])), int(float(row[46])), int(float(row[47])), int(float(row[48])), int(float(row[49])), \ int(float(row[50])), int(float(row[51])), int(float(row[52])), int(float(row[53])), int(float(row[54])), int(float(row[55])), \ int(float(row[56])), int(float(row[57])), int(float(row[58])), int(float(row[59])), int(float(row[60])), int(float(row[61])), \ int(float(row[62])), int(float(row[63])), int(float(row[64])), int(float(row[65])), int(float(row[66])), int(float(row[67])), \ int(float(row[68])), int(float(row[69])), int(float(row[70])), int(float(row[71]))] for bidx, bi in enumerate(bf.interpolation): if 0 > bi: logger.error( "[ボーン] %s行目の補間曲線(%s列目)に負数が設定されています", rno, bidx + 9, decoration=MLogger.DECORATION_BOX) return False except Exception as e: logger.error( "[ボーン] %s行目の補間曲線の読み取りに失敗しました\n位置は半角数字のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False bf.read = True bf.key = True if bf.name not in bone_motion.bones: bone_motion.bones[bf.name] = {} bone_motion.bones[bf.name][bf.fno] = bf cnt += 1 if cnt % 10000 == 0: logger.info("[ボーン] %sキー目:終了", cnt) if self.options.morph_csv_path and os.path.exists( self.options.morph_csv_path): # モーフモーションCSVディレクトリパス motion_csv_dir_path = MFileUtils.get_dir_path( self.options.morph_csv_path) # モーフモーションCSVファイル名・拡張子 motion_csv_file_name, _ = os.path.splitext( os.path.basename(self.options.morph_csv_path)) if not bone_fpath: bone_fpath = "{0}\\{1}_morph_{2:%Y%m%d_%H%M%S}.vmd".format( motion_csv_dir_path, motion_csv_file_name, dt_now) # モーフCSV読み込み with open(self.options.morph_csv_path, encoding='cp932', mode='r') as f: reader = csv.reader(f) next(reader) # ヘッダーを読み飛ばす cnt = 0 for ridx, row in enumerate(reader): mf = VmdMorphFrame() rno = ridx + 1 try: if len(row) < 0 or not row[0]: logger.error("[モーフ] %s行目のモーフ名(1列目)が設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # ボーン名 mf.set_name(row[0]) except Exception as e: logger.error("[モーフ] %s行目のモーフ名の読み取りに失敗しました\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False try: if len(row) < 1 or not row[1]: logger.error("[モーフ] %s行目のフレーム番号(2列目)が設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # フレーム mf.fno = int(float(row[1])) if mf.fno < 0: logger.error("[モーフ] %s行目のフレーム番号(2列目)に負数が設定されています", rno, decoration=MLogger.DECORATION_BOX) return False except Exception as e: logger.error( "[モーフ] %s行目のフレーム番号の読み取りに失敗しました\nフレーム番号は半角数字のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False try: if len(row) < 2 or not row[2]: logger.error("[モーフ] %s行目の大きさ(3列目)が設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # 値 mf.ratio = float(row[2]) except Exception as e: logger.error( "[モーフ] %s行目の大きさの読み取りに失敗しました\n大きさは半角数字・符号・小数点のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False if mf.name not in bone_motion.morphs: bone_motion.morphs[mf.name] = {} bone_motion.morphs[mf.name][mf.fno] = mf cnt += 1 if cnt % 1000 == 0: logger.info("[モーフ] %sキー目:終了", cnt) if len(bone_motion.bones.keys()) > 0 or len( bone_motion.morphs.keys()) > 0: # ボーンかモーフのキーがある場合、まとめて出力 model = PmxModel() model.name = "CSV Convert Model" data_set = MOptionsDataSet(bone_motion, model, model, bone_fpath, False, False, [], None, 0, []) VmdWriter(data_set).write() logger.info("ボーン・モーフモーションVMD: %s", bone_fpath, decoration=MLogger.DECORATION_BOX) if self.options.camera_csv_path and os.path.exists( self.options.camera_csv_path): # カメラモーションCSVディレクトリパス motion_csv_dir_path = MFileUtils.get_dir_path( self.options.camera_csv_path) # カメラモーションCSVファイル名・拡張子 motion_csv_file_name, _ = os.path.splitext( os.path.basename(self.options.camera_csv_path)) camera_fpath = "{0}\\{1}_camera_{2:%Y%m%d_%H%M%S}.vmd".format( motion_csv_dir_path, motion_csv_file_name, dt_now) camera_motion = VmdMotion() # カメラCSV読み込み with open(self.options.camera_csv_path, encoding='cp932', mode='r') as f: reader = csv.reader(f) next(reader) # ヘッダーを読み飛ばす cnt = 0 for ridx, row in enumerate(reader): cf = VmdCameraFrame() rno = ridx + 1 try: if len(row) < 1 or not row[0]: logger.error("[カメラ] %s行目のフレーム番号(1列目)が設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # フレーム cf.fno = int(row[0]) if cf.fno < 0: logger.error("[カメラ] %s行目のフレーム番号(1列目)に負数が設定されています", rno, decoration=MLogger.DECORATION_BOX) return False except Exception as e: logger.error( "[カメラ] %s行目のフレーム番号の読み取りに失敗しました\nフレーム番号は半角数字のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False try: if len(row ) < 3 or not row[1] or not row[2] or not row[3]: logger.error("[カメラ] %s行目の位置(2-4列目)のいずれかが設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # 位置 cf.position = MVector3D(float(row[1]), float(row[2]), float(row[3])) except Exception as e: logger.error( "[カメラ] %s行目の位置の読み取りに失敗しました\n位置は半角数字・符号・小数点のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False try: if len(row ) < 6 or not row[4] or not row[5] or not row[6]: logger.error("[カメラ] %s行目の回転(5-7列目)のいずれかが設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # 回転(オイラー角) cf.euler = MVector3D(float(row[4]), float(row[5]), float(row[6])) except Exception as e: logger.error( "[カメラ] %s行目の回転の読み取りに失敗しました\n回転は半角数字・符号・小数点のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False try: if len(row) < 7 or not row[7]: logger.error("[カメラ] %s行目の距離(8列目)が設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # 距離 cf.length = -(float(row[7])) except Exception as e: logger.error( "[カメラ] %s行目の距離の読み取りに失敗しました\n距離は半角数字・符号・小数点のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False try: if len(row) < 8 or not row[8]: logger.error("[カメラ] %s行目の視野角(9列目)が設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # 視野角 cf.angle = int(row[8]) if cf.angle < 0: logger.error("[カメラ] %s行目の視野角(9列目)に負数が設定されています", rno, decoration=MLogger.DECORATION_BOX) return False except Exception as e: logger.error( "[カメラ] %s行目の視野角の読み取りに失敗しました\n視野角は半角数字のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False try: if len(row) < 8 or not row[9]: logger.error("[カメラ] %s行目のパース(10列目)が設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False # パース cf.perspective = int(row[9]) if cf.perspective not in [0, 1]: logger.error( "[カメラ] %s行目のパース(10列目)に0, 1以外の値が設定されています", rno, decoration=MLogger.DECORATION_BOX) return False except Exception as e: logger.error( "[カメラ] %s行目のパースの読み取りに失敗しました\nパースは0, 1のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False try: if len(row) < 33: logger.error( "[カメラ] %s行目の補間曲線(11-34列目)のいずれかが設定されていません", rno, decoration=MLogger.DECORATION_BOX) return False for cidx in range(10, 34): if not row[cidx]: logger.error("[カメラ] %s行目の補間曲線の%s番目が設定されていません", rno, cidx - 9, decoration=MLogger.DECORATION_BOX) return False # 補間曲線(一旦floatで読み込んで指数等も読み込んだ後、intに変換) cf.interpolation = [int(float(row[10])), int(float(row[11])), int(float(row[12])), int(float(row[13])), int(float(row[14])), int(float(row[15])), \ int(float(row[16])), int(float(row[17])), int(float(row[18])), int(float(row[19])), int(float(row[20])), int(float(row[21])), \ int(float(row[22])), int(float(row[23])), int(float(row[24])), int(float(row[25])), int(float(row[26])), int(float(row[27])), \ int(float(row[28])), int(float(row[29])), int(float(row[30])), int(float(row[31])), int(float(row[32])), int(float(row[33]))] for cidx, ci in enumerate(cf.interpolation): if 0 > ci: logger.error( "[カメラ] %s行目の補間曲線(%s列目)に負数が設定されています", rno, cidx + 11, decoration=MLogger.DECORATION_BOX) return False except Exception as e: logger.error( "[カメラ] %s行目の補間曲線の読み取りに失敗しました\n位置は半角数字のみ入力可能です。\n%s", rno, e, decoration=MLogger.DECORATION_BOX) return False camera_motion.cameras[cf.fno] = cf cnt += 1 if cnt % 500 == 0: logger.info("[カメラ] %sキー目:終了", cnt) if len(camera_motion.cameras) > 0: # ボーンかモーフのキーがある場合、まとめて出力 model = PmxModel() model.name = "カメラ・照明" data_set = MOptionsDataSet(camera_motion, model, model, camera_fpath, False, False, [], None, 0, []) VmdWriter(data_set).write() logger.info("カメラモーションVMD: %s", camera_fpath, decoration=MLogger.DECORATION_BOX) return True