class SizingFileSet(): def __init__(self, frame: wx.Frame, panel: wx.Panel, file_hitories: dict, set_no): self.file_hitories = file_hitories self.frame = frame self.panel = panel self.set_no = set_no self.STANCE_DETAIL_CHOICES = [ "センターXZ補正", "上半身補正", "下半身補正", "足IK補正", "つま先補正", "つま先IK補正", "肩補正", "センターY補正" ] self.selected_stance_details = [0, 1, 2, 4, 5, 6, 7] if self.set_no == 1: # ファイルパネルのはそのまま追加 self.set_sizer = wx.BoxSizer(wx.VERTICAL) else: self.set_sizer = wx.StaticBoxSizer(wx.StaticBox( self.panel, wx.ID_ANY, "【No.{0}】".format(set_no)), orient=wx.VERTICAL) able_aster_toottip = "ファイル名にアスタリスク(*)を使用すると複数件のデータを一度にサイジングできます。" if self.set_no == 1 else "一括指定はできません。" # VMD/VPDファイルコントロール self.motion_vmd_file_ctrl = HistoryFilePickerCtrl(frame, panel, u"調整対象モーションVMD/VPD", u"調整対象モーションVMD/VPDファイルを開く", ("vmd", "vpd"), wx.FLP_DEFAULT_STYLE, \ u"調整したいモーションのVMD/VPDパスを指定してください。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。\n{0}".format(able_aster_toottip), \ file_model_spacer=46, title_parts_ctrl=None, title_parts2_ctrl=None, file_histories_key="vmd", is_change_output=True, \ is_aster=True, is_save=False, set_no=set_no) self.set_sizer.Add(self.motion_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) # 作成元のスタンス詳細再現FLG detail_stance_flg_ctrl = wx.CheckBox(panel, wx.ID_ANY, u"スタンス追加補正", wx.DefaultPosition, wx.DefaultSize, 0) detail_stance_flg_ctrl.SetToolTip( u"チェックを入れると、細かいスタンス補正を追加で行う事ができます。\n補正内容の詳細は隣の「*」ボタンを押してみてください。") detail_stance_flg_ctrl.Bind(wx.EVT_CHECKBOX, self.set_output_vmd_path) # スタンス補正 detail_btn_ctrl = wx.Button(panel, wx.ID_ANY, u"*", wx.DefaultPosition, (20, 20), 0) detail_btn_ctrl.SetToolTip("スタンス追加補正の内訳確認、および取捨選択を行う事が出来ます。") detail_btn_ctrl.Bind(wx.EVT_BUTTON, self.select_detail) # 作成元PMXファイルコントロール self.org_model_file_ctrl = HistoryFilePickerCtrl(frame, panel, u"モーション作成元モデルPMX", u"モーション作成元モデルPMXファイルを開く", ("pmx"), wx.FLP_DEFAULT_STYLE, \ u"モーション作成に使用されたモデルのPMXパスを指定してください。\n精度は落ちますが、類似したサイズ・ボーン構造のモデルでも代用できます。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=1, title_parts_ctrl=detail_stance_flg_ctrl, title_parts2_ctrl=detail_btn_ctrl, \ file_histories_key="org_pmx", is_change_output=False, is_aster=False, is_save=False, set_no=set_no) self.set_sizer.Add(self.org_model_file_ctrl.sizer, 1, wx.EXPAND, 0) # 捩り分散追加FLG twist_flg_ctrl = wx.CheckBox(panel, wx.ID_ANY, u"捩り分散あり", wx.DefaultPosition, wx.DefaultSize, 0) twist_flg_ctrl.SetToolTip(u"チェックを入れると、腕捻り等への分散処理を追加できます。\n時間がかかります。") twist_flg_ctrl.Bind(wx.EVT_CHECKBOX, self.set_output_vmd_path) # 変換先PMXファイルコントロール self.rep_model_file_ctrl = HistoryFilePickerCtrl(frame, panel, u"モーション変換先モデルPMX", u"モーション変換先モデルPMXファイルを開く", ("pmx"), wx.FLP_DEFAULT_STYLE, \ u"実際にモーションを読み込ませたいモデルのPMXパスを指定してください。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=18, title_parts_ctrl=twist_flg_ctrl, title_parts2_ctrl=None, file_histories_key="rep_pmx", \ is_change_output=True, is_aster=False, is_save=False, set_no=set_no) self.set_sizer.Add(self.rep_model_file_ctrl.sizer, 1, wx.EXPAND, 0) # 出力先VMDファイルコントロール self.output_vmd_file_ctrl = BaseFilePickerCtrl(frame, panel, u"出力VMD", u"出力VMDファイルを開く", ("vmd"), wx.FLP_OVERWRITE_PROMPT | wx.FLP_SAVE | wx.FLP_USE_TEXTCTRL, \ u"調整結果のVMD出力パスを指定してください。\nVMDファイルと変換先PMXのファイル名に基づいて自動生成されますが、任意のパスに変更することも可能です。", \ is_aster=False, is_save=True, set_no=set_no) self.set_sizer.Add(self.output_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) def get_selected_stance_details(self): # 選択されたINDEXの名称を返す return [ self.STANCE_DETAIL_CHOICES[n] for n in self.selected_stance_details ] def select_detail(self, event: wx.Event): with wx.MultiChoiceDialog(self.panel, "スタンス追加補正のうち、チェックが入っている補正のみ実施します", caption="スタンス追加補正選択", \ choices=self.STANCE_DETAIL_CHOICES, style=wx.CHOICEDLG_STYLE) as choiceDialog: choiceDialog.SetSelections(self.selected_stance_details) if choiceDialog.ShowModal() == wx.ID_CANCEL: return # the user changed their mind self.selected_stance_details = choiceDialog.GetSelections() if len(self.selected_stance_details) == 0: self.org_model_file_ctrl.title_parts_ctrl.SetValue(0) else: self.org_model_file_ctrl.title_parts_ctrl.SetValue(1) def save(self): self.motion_vmd_file_ctrl.save() self.org_model_file_ctrl.save() self.rep_model_file_ctrl.save() # フォーム無効化 def disable(self): self.motion_vmd_file_ctrl.disable() self.org_model_file_ctrl.disable() self.rep_model_file_ctrl.disable() self.output_vmd_file_ctrl.disable() # フォーム無効化 def enable(self): self.motion_vmd_file_ctrl.enable() self.org_model_file_ctrl.enable() self.rep_model_file_ctrl.enable() self.output_vmd_file_ctrl.enable() # ファイル読み込み前のチェック def is_valid(self): result = True if self.set_no == 1: # 1番目は必ず調べる result = self.motion_vmd_file_ctrl.is_valid() and result result = self.org_model_file_ctrl.is_valid() and result result = self.rep_model_file_ctrl.is_valid() and result result = self.output_vmd_file_ctrl.is_valid() and result else: # 2番目以降は、ファイルが揃ってたら調べる if self.motion_vmd_file_ctrl.is_set_path() or self.org_model_file_ctrl.is_set_path() or \ self.rep_model_file_ctrl.is_set_path() or self.output_vmd_file_ctrl.is_set_path(): result = self.motion_vmd_file_ctrl.is_valid() and result result = self.org_model_file_ctrl.is_valid() and result result = self.rep_model_file_ctrl.is_valid() and result result = self.output_vmd_file_ctrl.is_valid() and result return result # 入力後の入力可否チェック def is_loaded_valid(self): if self.set_no == 0: # CSVとかのファイルは番号出力なし display_set_no = "" else: display_set_no = "{0}番目の".format(self.set_no) # 両方のPMXが読めて、モーションも読み込めた場合、キーチェック not_org_bones = [] not_org_morphs = [] not_rep_bones = [] not_rep_morphs = [] mismatch_bones = [] motion = self.motion_vmd_file_ctrl.data org_pmx = self.org_model_file_ctrl.data rep_pmx = self.rep_model_file_ctrl.data if not motion or not org_pmx or not rep_pmx: # どれか読めてなければそのまま終了 return True if motion.motion_cnt == 0: logger.warning("%sボーンモーションデータにキーフレームが登録されていません。", display_set_no, decoration=MLogger.DECORATION_BOX) return True result = True is_warning = False # ボーン for k in motion.bones.keys(): bone_fnos = motion.get_bone_fnos(k) for fno in bone_fnos: if motion.bones[k][fno].position != MVector3D( ) or motion.bones[k][fno].rotation != MQuaternion(): # キーが存在しており、かつ初期値ではない値が入っている場合、警告対象 if k not in org_pmx.bones: not_org_bones.append(k) if k not in rep_pmx.bones: not_rep_bones.append(k) if k in org_pmx.bones and k in rep_pmx.bones: mismatch_types = [] # 両方にボーンがある場合、フラグが同じであるかチェック if org_pmx.bones[k].getRotatable( ) != rep_pmx.bones[k].getRotatable(): mismatch_types.append("性能:回転") if org_pmx.bones[k].getTranslatable( ) != rep_pmx.bones[k].getTranslatable(): mismatch_types.append("性能:移動") if org_pmx.bones[k].getIkFlag( ) != rep_pmx.bones[k].getIkFlag(): mismatch_types.append("性能:IK") if org_pmx.bones[k].getVisibleFlag( ) != rep_pmx.bones[k].getVisibleFlag(): mismatch_types.append("性能:表示") if org_pmx.bones[k].getManipulatable( ) != rep_pmx.bones[k].getManipulatable(): mismatch_types.append("性能:操作") if org_pmx.bones[k].display != rep_pmx.bones[k].display: mismatch_types.append("表示枠") if len(mismatch_types) > 0: mismatch_bones.append( f"{k} 【差異】{', '.join(mismatch_types)})") # 1件あればOK break for k in motion.morphs.keys(): morph_fnos = motion.get_morph_fnos(k) for fno in morph_fnos: if motion.morphs[k][fno].ratio != 0: # キーが存在しており、かつ初期値ではない値が入っている場合、警告対象 if k not in org_pmx.morphs: not_org_morphs.append(k) if k not in rep_pmx.morphs: not_rep_morphs.append(k) # 1件あればOK break if len(not_org_bones) > 0 or len(not_org_morphs) > 0: logger.warning("%s%sに、モーションで使用されているボーン・モーフが不足しています。\nモデル: %s\n不足ボーン: %s\n不足モーフ: %s", \ display_set_no, self.org_model_file_ctrl.title, org_pmx.name, ",".join(not_org_bones), ",".join(not_org_morphs), decoration=MLogger.DECORATION_BOX) is_warning = True if len(not_rep_bones) > 0 or len(not_rep_morphs) > 0: logger.warning("%s%sに、モーションで使用されているボーン・モーフが不足しています。\nモデル: %s\n不足ボーン: %s\n不足モーフ: %s", \ display_set_no, self.rep_model_file_ctrl.title, rep_pmx.name, ",".join(not_rep_bones), ",".join(not_rep_morphs), decoration=MLogger.DECORATION_BOX) is_warning = True if len(mismatch_bones) > 0: logger.warning("%s%sで、モーションで使用されているボーンの性能等が異なっています。\nモデル: %s\n差異ボーン:\n %s", \ display_set_no, self.rep_model_file_ctrl.title, rep_pmx.name, "\n ".join(mismatch_bones), decoration=MLogger.DECORATION_BOX) is_warning = True if not is_warning: logger.info("モーションで使用されているボーン・モーフが揃っています。", decoration=MLogger.DECORATION_BOX, title="OK") return result def is_loaded(self): result = True if self.is_valid(): result = self.motion_vmd_file_ctrl.data and result result = self.org_model_file_ctrl.data and result result = self.rep_model_file_ctrl.data and result else: result = False return result def load(self): result = True try: result = self.motion_vmd_file_ctrl.load() and result result = self.org_model_file_ctrl.load() and result result = self.rep_model_file_ctrl.load() and result except Exception: result = False return result # VMD出力ファイルパス生成 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)
class MultiJoinPanel(BasePanel): def __init__(self, frame: wx.Frame, multi_join: wx.Notebook, tab_idx: int): super().__init__(frame, multi_join, tab_idx) self.timer = wx.Timer(self, TIMER_ID) self.convert_multi_join_worker = None self.header_sizer = wx.BoxSizer(wx.VERTICAL) self.description_txt = wx.StaticText(self, wx.ID_ANY, u"モーションの指定ボーンの移動量XYZと回転量XYZを統合します。\n" \ + "統合するボーンは「ボーン指定」ボタンから定義できます。多段分割の設定エクスポートファイル流用可能です。" \ + "\n不要キー削除を行うと、キーが間引きされます。キー間がオリジナルから多少ずれ、やや時間がかかります。", wx.DefaultPosition, wx.DefaultSize, 0) self.header_sizer.Add(self.description_txt, 0, wx.ALL, 5) self.static_line01 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) self.header_sizer.Add(self.static_line01, 0, wx.EXPAND | wx.ALL, 5) # 対象VMDファイルコントロール self.vmd_file_ctrl = HistoryFilePickerCtrl(self.frame, self, u"対象モーションVMD/VPD", u"対象モーションVMDファイルを開く", ("vmd", "vpd"), wx.FLP_DEFAULT_STYLE, \ u"調整したい対象モーションのVMDパスを指定してください。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=46, title_parts_ctrl=None, title_parts2_ctrl=None, file_histories_key="multi_join_vmd", is_change_output=True, \ is_aster=False, is_save=False, set_no=1) self.vmd_file_ctrl.file_ctrl.Bind(wx.EVT_FILEPICKER_CHANGED, self.on_change_file) self.header_sizer.Add(self.vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) # 対象PMXファイルコントロール self.model_file_ctrl = HistoryFilePickerCtrl(self.frame, self, u"多段化済みモデルPMX", u"多段化済みモデルPMXファイルを開く", ("pmx"), wx.FLP_DEFAULT_STYLE, \ u"モーションを適用したい多段化済みモデルのPMXパスを指定してください。\n人体モデル以外にも適用可能です。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=49, title_parts_ctrl=None, title_parts2_ctrl=None, file_histories_key="multi_join_pmx", \ is_change_output=True, is_aster=False, is_save=False, set_no=1) self.model_file_ctrl.file_ctrl.Bind(wx.EVT_FILEPICKER_CHANGED, self.on_change_file) self.header_sizer.Add(self.model_file_ctrl.sizer, 1, wx.EXPAND, 0) # 出力先VMDファイルコントロール self.output_vmd_file_ctrl = BaseFilePickerCtrl(frame, self, u"出力対象VMD", u"出力対象VMDファイルを開く", ("vmd"), wx.FLP_OVERWRITE_PROMPT | wx.FLP_SAVE | wx.FLP_USE_TEXTCTRL, \ u"調整結果の対象VMD出力パスを指定してください。\n対象VMDファイル名に基づいて自動生成されますが、任意のパスに変更することも可能です。", \ is_aster=False, is_save=True, set_no=1) self.header_sizer.Add(self.output_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) self.sizer.Add(self.header_sizer, 0, wx.EXPAND | wx.ALL, 5) self.setting_sizer = wx.BoxSizer(wx.HORIZONTAL) # ボーン名指定 self.bone_target_txt_ctrl = wx.TextCtrl( self, wx.ID_ANY, "", wx.DefaultPosition, (450, 50), wx.HSCROLL | wx.VSCROLL | wx.TE_MULTILINE | wx.TE_READONLY) self.bone_target_txt_ctrl.SetBackgroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)) self.setting_sizer.Add(self.bone_target_txt_ctrl, 1, wx.EXPAND | wx.ALL, 5) self.bone_target_btn_ctrl = wx.Button(self, wx.ID_ANY, u"ボーン指定", wx.DefaultPosition, wx.DefaultSize, 0) self.bone_target_btn_ctrl.SetToolTip( u"モーションに登録されているボーンから、統合したいボーンを指定できます") self.bone_target_btn_ctrl.Bind(wx.EVT_BUTTON, self.on_click_bone_target) self.setting_sizer.Add(self.bone_target_btn_ctrl, 0, wx.ALIGN_BOTTOM | wx.ALL, 5) self.sizer.Add(self.setting_sizer, 0, wx.EXPAND | wx.ALL, 5) # 不要キー削除処理 self.flg_sizer = wx.BoxSizer(wx.VERTICAL) self.remove_unnecessary_flg_ctrl = wx.CheckBox(self, wx.ID_ANY, u"不要キー削除処理を追加実行する", wx.DefaultPosition, wx.DefaultSize, 0) self.remove_unnecessary_flg_ctrl.SetToolTip( u"チェックを入れると、不要キー削除処理を追加で実行します。キーが減る分、キー間が少しズレる事があります。") self.remove_unnecessary_flg_ctrl.Bind(wx.EVT_CHECKBOX, self.on_change_file) self.flg_sizer.Add(self.remove_unnecessary_flg_ctrl, 0, wx.ALL, 5) self.sizer.Add(self.flg_sizer, 0, wx.EXPAND | wx.ALL, 5) btn_sizer = wx.BoxSizer(wx.HORIZONTAL) # 実行ボタン self.multi_join_btn_ctrl = wx.Button(self, wx.ID_ANY, u"多段統合", wx.DefaultPosition, wx.Size(200, 50), 0) self.multi_join_btn_ctrl.SetToolTip(u"キーフレを多段用に統合したモーションを生成します") self.multi_join_btn_ctrl.Bind(wx.EVT_LEFT_DOWN, self.on_convert_multi_join) self.multi_join_btn_ctrl.Bind(wx.EVT_LEFT_DCLICK, self.on_doubleclick) btn_sizer.Add(self.multi_join_btn_ctrl, 0, wx.ALL, 5) self.sizer.Add(btn_sizer, 0, wx.ALIGN_CENTER | wx.SHAPED, 5) # コンソール self.console_ctrl = ConsoleCtrl(self, self.frame.logging_level, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(-1, 420), \ wx.TE_MULTILINE | wx.TE_READONLY | wx.BORDER_NONE | wx.HSCROLL | wx.VSCROLL | wx.WANTS_CHARS) self.console_ctrl.SetBackgroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)) self.console_ctrl.Bind( wx.EVT_CHAR, lambda event: MFormUtils.on_select_all(event, self.console_ctrl)) self.sizer.Add(self.console_ctrl, 1, wx.ALL | wx.EXPAND, 5) # ゲージ self.gauge_ctrl = wx.Gauge(self, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.GA_HORIZONTAL) self.gauge_ctrl.SetValue(0) self.sizer.Add(self.gauge_ctrl, 0, wx.ALL | wx.EXPAND, 5) self.Layout() self.fit() # ボーン選択用ダイアログ self.bone_dialog = TargetBoneDialog(self.frame, self, "←", "分割") # フレームに変換完了処理バインド self.frame.Bind(EVT_SMOOTH_THREAD, self.on_convert_multi_join_result) def on_click_bone_target(self, event: wx.Event): self.disable() # VMD読み込み sys.stdout = self.console_ctrl self.vmd_file_ctrl.load() # PMX読み込み self.model_file_ctrl.load() if (self.vmd_file_ctrl.data and self.model_file_ctrl.data and \ (self.vmd_file_ctrl.data.digest != self.bone_dialog.vmd_digest or self.model_file_ctrl.data.digest != self.bone_dialog.pmx_digest)): # データが揃ってたら押下可能 self.bone_target_btn_ctrl.Enable() # リストクリア self.bone_target_txt_ctrl.SetValue("") # ボーン選択用ダイアログ self.bone_dialog.Destroy() self.bone_dialog = TargetBoneDialog(self.frame, self, "←", "統合") self.bone_dialog.initialize() else: if not self.vmd_file_ctrl.data or not self.model_file_ctrl.data: logger.error("対象モーションVMD/VPDもしくは多段化済みモデルPMXが未指定です。", decoration=MLogger.DECORATION_BOX) self.enable() return self.enable() if self.bone_dialog.ShowModal() == wx.ID_CANCEL: return # the user changed their mind # 選択されたボーンリストを入力欄に設定 bone_list = self.bone_dialog.get_bone_list() selections = ["{0} {7} 【回転X: {1}】【回転Y: {2}】【回転Z: {3}】【移動X: {4}】【移動Y: {5}】【移動Z: {6}】" \ .format(bset[0], bset[1], bset[2], bset[3], bset[4], bset[5], bset[6], "←") for bset in bone_list] self.bone_target_txt_ctrl.SetValue('\n'.join(selections)) self.bone_dialog.Hide() # ファイル変更時の処理 def on_change_file(self, event: wx.Event): self.set_output_vmd_path(event, is_force=True) def set_output_vmd_path(self, event: wx.Event, is_force=False): output_multi_join_vmd_path = MFileUtils.get_output_multi_join_vmd_path( self.vmd_file_ctrl.file_ctrl.GetPath(), self.model_file_ctrl.file_ctrl.GetPath(), self.output_vmd_file_ctrl.file_ctrl.GetPath(), is_force) self.output_vmd_file_ctrl.file_ctrl.SetPath(output_multi_join_vmd_path) if len(output_multi_join_vmd_path) >= 255 and os.name == "nt": logger.error("生成予定のファイルパスがWindowsの制限を超えています。\n生成予定パス: {0}".format( output_multi_join_vmd_path), decoration=MLogger.DECORATION_BOX) # フォーム無効化 def disable(self): self.vmd_file_ctrl.disable() self.model_file_ctrl.disable() self.output_vmd_file_ctrl.disable() self.bone_target_btn_ctrl.Disable() self.multi_join_btn_ctrl.Disable() self.remove_unnecessary_flg_ctrl.Disable() # フォーム無効化 def enable(self): self.vmd_file_ctrl.enable() self.model_file_ctrl.enable() self.output_vmd_file_ctrl.enable() self.bone_target_btn_ctrl.Enable() self.multi_join_btn_ctrl.Enable() self.remove_unnecessary_flg_ctrl.Enable() def on_doubleclick(self, event: wx.Event): self.timer.Stop() logger.warning("ダブルクリックされました。", decoration=MLogger.DECORATION_BOX) event.Skip(False) return False def on_convert_multi_join(self, event: wx.Event): self.timer.Start(200) self.Bind(wx.EVT_TIMER, self.on_convert, id=TIMER_ID) # 多段統合変換 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 on_convert_multi_join_result(self, event: wx.Event): self.elapsed_time = event.elapsed_time logger.info("\n処理時間: %s", self.show_worked_time()) self.multi_join_btn_ctrl.SetLabel("多段統合") # 終了音 self.frame.sound_finish() # タブ移動可 self.release_tab() # フォーム有効化 self.enable() # ワーカー終了 self.convert_multi_join_worker = None # プログレス非表示 self.gauge_ctrl.SetValue(0) # ラベル変更 self.multi_join_btn_ctrl.SetLabel("多段統合") self.multi_join_btn_ctrl.Enable() def show_worked_time(self): # 経過秒数を時分秒に変換 td_m, td_s = divmod(self.elapsed_time, 60) if td_m == 0: worked_time = "{0:02d}秒".format(int(td_s)) else: worked_time = "{0:02d}分{1:02d}秒".format(int(td_m), int(td_s)) return worked_time
class SmoothPanel(BasePanel): def __init__(self, frame: wx.Frame, parent: wx.Notebook, tab_idx: int): super().__init__(frame, parent, tab_idx) self.convert_smooth_worker = None self.header_sizer = wx.BoxSizer(wx.VERTICAL) self.description_txt = wx.StaticText(self, wx.ID_ANY, u"指定されたVMDファイルに対して、キーを分割し、滑らかな補間曲線で繋いで、再出力します。\n" \ + "スムージング回数1回で、全打ちとなり、2回目以降はフィルタリングした後に補間曲線で繋ぎます。", wx.DefaultPosition, wx.DefaultSize, 0) self.header_sizer.Add(self.description_txt, 0, wx.ALL, 5) self.static_line01 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) self.header_sizer.Add(self.static_line01, 0, wx.EXPAND | wx.ALL, 5) # 対象VMDファイルコントロール self.smooth_vmd_file_ctrl = HistoryFilePickerCtrl(self.frame, self, u"対象モーションVMD", u"対象モーションVMDファイルを開く", ("vmd"), wx.FLP_DEFAULT_STYLE, \ u"調整したい対象モーションのVMDパスを指定してください。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=0, title_parts_ctrl=None, title_parts2_ctrl=None, file_histories_key="smooth_vmd", is_change_output=True, \ is_aster=False, is_save=False, set_no=1) self.header_sizer.Add(self.smooth_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) # 対象PMXファイルコントロール self.smooth_model_file_ctrl = HistoryFilePickerCtrl(self.frame, self, u"適用モデルPMX", u"適用モデルPMXファイルを開く", ("pmx"), wx.FLP_DEFAULT_STYLE, \ u"モーションを適用したいモデルのPMXパスを指定してください。\n人体モデル以外にも適用可能です。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=0, title_parts_ctrl=None, title_parts2_ctrl=None, file_histories_key="smooth_pmx", \ is_change_output=True, is_aster=False, is_save=False, set_no=1) self.header_sizer.Add(self.smooth_model_file_ctrl.sizer, 1, wx.EXPAND, 0) # 出力先VMDファイルコントロール self.output_smooth_vmd_file_ctrl = BaseFilePickerCtrl(frame, self, u"出力対象VMD", u"出力対象VMDファイルを開く", ("vmd"), wx.FLP_OVERWRITE_PROMPT | wx.FLP_SAVE | wx.FLP_USE_TEXTCTRL, \ u"調整結果の対象VMD出力パスを指定してください。\n対象VMDファイル名に基づいて自動生成されますが、任意のパスに変更することも可能です。", \ is_aster=False, is_save=True, set_no=1) self.header_sizer.Add(self.output_smooth_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) self.sizer.Add(self.header_sizer, 0, wx.EXPAND | wx.ALL, 5) self.setting_sizer = wx.BoxSizer(wx.HORIZONTAL) # 処理回数 self.loop_cnt_txt = wx.StaticText(self, wx.ID_ANY, u"処理回数", wx.DefaultPosition, wx.DefaultSize, 0) self.setting_sizer.Add(self.loop_cnt_txt, 0, wx.ALL, 5) self.loop_cnt_ctrl = wx.SpinCtrl(self, id=wx.ID_ANY, size=wx.Size(60, -1), value="2", min=1, max=99999999, initial=2) self.loop_cnt_ctrl.SetToolTip( u"スムージングを行う回数を指定してください。\n1回だと全打ちになります。\n2回目以降はフィルタをかけた上で間引きします。\n回数が増えると、変化が遅く、弱くなります。" ) self.loop_cnt_ctrl.Bind(wx.EVT_SPINCTRL, self.on_change_file) self.setting_sizer.Add(self.loop_cnt_ctrl, 0, wx.ALL, 5) # 補間 self.interpolation_txt = wx.StaticText(self, wx.ID_ANY, u"補間方法", wx.DefaultPosition, wx.DefaultSize, 0) self.setting_sizer.Add(self.interpolation_txt, 0, wx.ALL, 5) self.interpolation_ctrl = wx.Choice( self, id=wx.ID_ANY, choices=["補間曲線に従う", "補間曲線無視(円形)", "補間曲線無視(曲線)"]) self.interpolation_ctrl.SetSelection(0) self.interpolation_ctrl.SetToolTip(u"キーとキーの補間方法を指定してください。\n「補間曲線に従う」は、補間曲線に従って繋ぎます。" \ + "\n「補間曲線無視(円形)」は、補間曲線を無視して、\n3つのキーを円周上に置いた円になるように補間します。" \ + "\n「補間曲線無視(曲線)」は、補間曲線を無視して、\nキーを滑らかな曲線(カトマル曲線)で繋いで補間します。") self.interpolation_ctrl.Bind(wx.EVT_CHOICE, self.on_change_file) self.setting_sizer.Add(self.interpolation_ctrl, 0, wx.ALL, 5) self.sizer.Add(self.setting_sizer, 0, wx.EXPAND | wx.ALL, 5) btn_sizer = wx.BoxSizer(wx.HORIZONTAL) # スムージング変換実行ボタン self.smooth_btn_ctrl = wx.Button(self, wx.ID_ANY, u"スムージング実行", wx.DefaultPosition, wx.Size(200, 50), 0) self.smooth_btn_ctrl.SetToolTip(u"VMDを滑らかに繋いだモーションを再生成します。") self.smooth_btn_ctrl.Bind(wx.EVT_BUTTON, self.on_convert_smooth) btn_sizer.Add(self.smooth_btn_ctrl, 0, wx.ALL, 5) self.sizer.Add(btn_sizer, 0, wx.ALIGN_CENTER | wx.SHAPED, 5) # コンソール self.console_ctrl = ConsoleCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(-1, 420), \ wx.TE_MULTILINE | wx.TE_READONLY | wx.BORDER_NONE | wx.HSCROLL | wx.VSCROLL | wx.WANTS_CHARS) self.console_ctrl.SetBackgroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)) self.console_ctrl.Bind( wx.EVT_CHAR, lambda event: MFormUtils.on_select_all(event, self.console_ctrl)) self.sizer.Add(self.console_ctrl, 1, wx.ALL | wx.EXPAND, 5) # ゲージ self.gauge_ctrl = wx.Gauge(self, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.GA_HORIZONTAL) self.gauge_ctrl.SetValue(0) self.sizer.Add(self.gauge_ctrl, 0, wx.ALL | wx.EXPAND, 5) self.Layout() self.fit() # フレームに変換完了処理バインド self.frame.Bind(EVT_SMOOTH_THREAD, self.on_convert_smooth_result) # ファイル変更時の処理 def on_change_file(self, event: wx.Event): self.set_output_vmd_path(event) 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 disable(self): self.smooth_vmd_file_ctrl.disable() self.smooth_model_file_ctrl.disable() self.output_smooth_vmd_file_ctrl.disable() self.loop_cnt_ctrl.Disable() self.interpolation_ctrl.Disable() self.smooth_btn_ctrl.Disable() # フォーム無効化 def enable(self): self.smooth_vmd_file_ctrl.enable() self.smooth_model_file_ctrl.enable() self.output_smooth_vmd_file_ctrl.enable() self.loop_cnt_ctrl.Enable() self.interpolation_ctrl.Enable() self.smooth_btn_ctrl.Enable() # スムージング変換 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_convert_smooth_result(self, event: wx.Event): self.elapsed_time = event.elapsed_time logger.info("\n処理時間: %s", self.show_worked_time()) # 終了音 self.frame.sound_finish() # タブ移動可 self.release_tab() # フォーム有効化 self.enable() # ワーカー終了 self.convert_smooth_worker = None # プログレス非表示 self.gauge_ctrl.SetValue(0) # 出力先をデフォルトに戻す sys.stdout = self.frame.file_panel_ctrl.console_ctrl def show_worked_time(self): # 経過秒数を時分秒に変換 td_m, td_s = divmod(self.elapsed_time, 60) if td_m == 0: worked_time = "{0:02d}秒".format(int(td_s)) else: worked_time = "{0:02d}分{1:02d}秒".format(int(td_m), int(td_s)) return worked_time
class ArmIKtoFKPanel(BasePanel): def __init__(self, frame: wx.Frame, arm_ik2fk: wx.Notebook, tab_idx: int): super().__init__(frame, arm_ik2fk, tab_idx) self.convert_arm_ik2fk_worker = None self.header_sizer = wx.BoxSizer(wx.VERTICAL) self.description_txt = wx.StaticText(self, wx.ID_ANY, u"腕IKを腕FK(腕・ひじ・手首)に変換します" \ + "\n不要キー削除を行うと、キーが間引きされます。キー間がオリジナルから多少ずれ、またそれなりに時間がかかります。", wx.DefaultPosition, wx.DefaultSize, 0) self.header_sizer.Add(self.description_txt, 0, wx.ALL, 5) self.static_line01 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) self.header_sizer.Add(self.static_line01, 0, wx.EXPAND | wx.ALL, 5) # 対象VMDファイルコントロール self.arm_ik2fk_vmd_file_ctrl = HistoryFilePickerCtrl(self.frame, self, u"対象モーションVMD/VPD", u"対象モーションVMDファイルを開く", ("vmd", "vpd"), wx.FLP_DEFAULT_STYLE, \ u"調整したい対象モーションのVMDパスを指定してください。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=46, title_parts_ctrl=None, title_parts2_ctrl=None, file_histories_key="arm_ik2fk_vmd", is_change_output=True, \ is_aster=False, is_save=False, set_no=1) self.header_sizer.Add(self.arm_ik2fk_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) # 対象PMXファイルコントロール self.arm_ik2fk_model_file_ctrl = HistoryFilePickerCtrl(self.frame, self, u"適用モデルPMX", u"適用モデルPMXファイルを開く", ("pmx"), wx.FLP_DEFAULT_STYLE, \ u"モーションを適用したいモデルのPMXパスを指定してください。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=60, title_parts_ctrl=None, title_parts2_ctrl=None, file_histories_key="arm_ik2fk_pmx", \ is_change_output=True, is_aster=False, is_save=False, set_no=1) self.header_sizer.Add(self.arm_ik2fk_model_file_ctrl.sizer, 1, wx.EXPAND, 0) # 出力先VMDファイルコントロール self.output_arm_ik2fk_vmd_file_ctrl = BaseFilePickerCtrl(frame, self, u"出力対象VMD", u"出力対象VMDファイルを開く", ("vmd"), wx.FLP_OVERWRITE_PROMPT | wx.FLP_SAVE | wx.FLP_USE_TEXTCTRL, \ u"調整結果の対象VMD出力パスを指定してください。\n対象VMDファイル名に基づいて自動生成されますが、任意のパスに変更することも可能です。", \ is_aster=False, is_save=True, set_no=1) self.header_sizer.Add(self.output_arm_ik2fk_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) # 不要キー削除処理 self.remove_unnecessary_flg_ctrl = wx.CheckBox(self, wx.ID_ANY, u"不要キー削除処理を追加実行する", wx.DefaultPosition, wx.DefaultSize, 0) self.remove_unnecessary_flg_ctrl.SetToolTip( u"チェックを入れると、不要キー削除処理を追加で実行します。キーが減る分、キー間が少しズレる事があります。") self.header_sizer.Add(self.remove_unnecessary_flg_ctrl, 0, wx.ALL, 5) self.sizer.Add(self.header_sizer, 0, wx.EXPAND | wx.ALL, 5) btn_sizer = wx.BoxSizer(wx.HORIZONTAL) # 多段分割変換実行ボタン self.arm_ik2fk_btn_ctrl = wx.Button(self, wx.ID_ANY, u"腕IK変換", wx.DefaultPosition, wx.Size(200, 50), 0) self.arm_ik2fk_btn_ctrl.SetToolTip(u"足FKを足IKに変換したモーションを再生成します。") self.arm_ik2fk_btn_ctrl.Bind(wx.EVT_LEFT_DOWN, self.on_convert_arm_ik2fk) self.arm_ik2fk_btn_ctrl.Bind(wx.EVT_LEFT_DCLICK, self.on_doubleclick) btn_sizer.Add(self.arm_ik2fk_btn_ctrl, 0, wx.ALL, 5) self.sizer.Add(btn_sizer, 0, wx.ALIGN_CENTER | wx.SHAPED, 5) # コンソール self.console_ctrl = ConsoleCtrl(self, self.frame.logging_level, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(-1, 420), \ wx.TE_MULTILINE | wx.TE_READONLY | wx.BORDER_NONE | wx.HSCROLL | wx.VSCROLL | wx.WANTS_CHARS) self.console_ctrl.SetBackgroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)) self.console_ctrl.Bind( wx.EVT_CHAR, lambda event: MFormUtils.on_select_all(event, self.console_ctrl)) self.sizer.Add(self.console_ctrl, 1, wx.ALL | wx.EXPAND, 5) # ゲージ self.gauge_ctrl = wx.Gauge(self, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.GA_HORIZONTAL) self.gauge_ctrl.SetValue(0) self.sizer.Add(self.gauge_ctrl, 0, wx.ALL | wx.EXPAND, 5) self.Layout() self.fit() # フレームに変換完了処理バインド self.frame.Bind(EVT_SMOOTH_THREAD, self.on_convert_arm_ik2fk_result) def on_wheel_spin_ctrl(self, event: wx.Event, inc=1): self.frame.on_wheel_spin_ctrl(event, inc) self.set_output_vmd_path(event) # ファイル変更時の処理 def on_change_file(self, event: wx.Event): self.set_output_vmd_path(event, is_force=True) def set_output_vmd_path(self, event, is_force=False): output_arm_ik2fk_vmd_path = MFileUtils.get_output_arm_ik2fk_vmd_path( self.arm_ik2fk_vmd_file_ctrl.file_ctrl.GetPath(), self.arm_ik2fk_model_file_ctrl.file_ctrl.GetPath(), self.output_arm_ik2fk_vmd_file_ctrl.file_ctrl.GetPath(), is_force) self.output_arm_ik2fk_vmd_file_ctrl.file_ctrl.SetPath( output_arm_ik2fk_vmd_path) if len(output_arm_ik2fk_vmd_path) >= 255 and os.name == "nt": logger.error("生成予定のファイルパスがWindowsの制限を超えています。\n生成予定パス: {0}".format( output_arm_ik2fk_vmd_path), decoration=MLogger.DECORATION_BOX) # フォーム無効化 def disable(self): self.arm_ik2fk_vmd_file_ctrl.disable() self.arm_ik2fk_model_file_ctrl.disable() self.output_arm_ik2fk_vmd_file_ctrl.disable() self.arm_ik2fk_btn_ctrl.Disable() # フォーム無効化 def enable(self): self.arm_ik2fk_vmd_file_ctrl.enable() self.arm_ik2fk_model_file_ctrl.enable() self.output_arm_ik2fk_vmd_file_ctrl.enable() self.arm_ik2fk_btn_ctrl.Enable() def on_doubleclick(self, event: wx.Event): self.timer.Stop() logger.warning("ダブルクリックされました。", decoration=MLogger.DECORATION_BOX) event.Skip(False) return False # 多段分割変換 def on_convert_arm_ik2fk(self, event: wx.Event): self.timer = wx.Timer(self, TIMER_ID) self.timer.Start(200) self.Bind(wx.EVT_TIMER, self.on_convert, id=TIMER_ID) # 多段分割変換 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.arm_ik2fk_vmd_file_ctrl.save() self.arm_ik2fk_model_file_ctrl.save() # JSON出力 MFileUtils.save_history(self.frame.mydir_path, self.frame.file_hitories) self.elapsed_time = 0 result = True result = self.arm_ik2fk_vmd_file_ctrl.is_valid( ) and self.arm_ik2fk_model_file_ctrl.is_valid() and result if not result: # 終了音 self.frame.sound_finish() # タブ移動可 self.release_tab() # フォーム有効化 self.enable() return result # 腕IK変換変換開始 if self.arm_ik2fk_btn_ctrl.GetLabel( ) == "腕IK変換停止" and self.convert_arm_ik2fk_worker: # フォーム無効化 self.disable() # 停止状態でボタン押下時、停止 self.convert_arm_ik2fk_worker.stop() # タブ移動可 self.frame.release_tab() # フォーム有効化 self.frame.enable() # ワーカー終了 self.convert_arm_ik2fk_worker = None # プログレス非表示 self.gauge_ctrl.SetValue(0) logger.warning("腕IK変換を中断します。", decoration=MLogger.DECORATION_BOX) self.arm_ik2fk_btn_ctrl.SetLabel("腕IK変換") event.Skip(False) elif not self.convert_arm_ik2fk_worker: # フォーム無効化 self.disable() # タブ固定 self.fix_tab() # コンソールクリア self.console_ctrl.Clear() # ラベル変更 self.arm_ik2fk_btn_ctrl.SetLabel("腕IK変換停止") self.arm_ik2fk_btn_ctrl.Enable() self.convert_arm_ik2fk_worker = ArmIKtoFKWorkerThread( self.frame, ArmIKtoFKThreadEvent, self.frame.is_saving, self.frame.is_out_log) self.convert_arm_ik2fk_worker.start() event.Skip() else: logger.error("まだ処理が実行中です。終了してから再度実行してください。", decoration=MLogger.DECORATION_BOX) event.Skip(False) return result # 多段分割変換完了処理 def on_convert_arm_ik2fk_result(self, event: wx.Event): self.elapsed_time = event.elapsed_time logger.info("\n処理時間: %s", self.show_worked_time()) self.arm_ik2fk_btn_ctrl.SetLabel("腕IK変換") # 終了音 self.frame.sound_finish() # タブ移動可 self.release_tab() # フォーム有効化 self.enable() # ワーカー終了 self.convert_arm_ik2fk_worker = None # プログレス非表示 self.gauge_ctrl.SetValue(0) def show_worked_time(self): # 経過秒数を時分秒に変換 td_m, td_s = divmod(self.elapsed_time, 60) if td_m == 0: worked_time = "{0:02d}秒".format(int(td_s)) else: worked_time = "{0:02d}分{1:02d}秒".format(int(td_m), int(td_s)) return worked_time
class BulkPanel(BasePanel): def __init__(self, frame: wx.Frame, parent: wx.Notebook, tab_idx: int): super().__init__(frame, parent, tab_idx) self.description_txt = wx.StaticText(self, wx.ID_ANY, "設定を一括で指定して、連続して処理させる事ができます。", wx.DefaultPosition, wx.DefaultSize, 0) self.sizer.Add(self.description_txt, 0, wx.ALL, 5) self.static_line = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) self.sizer.Add(self.static_line, 0, wx.EXPAND | wx.ALL, 5) # バルクBULKファイルコントロール self.bulk_csv_file_ctrl = HistoryFilePickerCtrl(frame, self, u"一括処理用CSV", u"一括処理用CSVファイルを開く", ("csv"), wx.FLP_DEFAULT_STYLE, \ u"一括処理用のCSVを指定してください。\nフォーマットは、DLボタンから取得できます。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=0, title_parts_ctrl=None, title_parts2_ctrl=None, \ file_histories_key="bulk_csv", is_change_output=False, is_aster=False, is_save=False, set_no=0) self.sizer.Add(self.bulk_csv_file_ctrl.sizer, 0, wx.EXPAND | wx.ALL, 0) btn_sizer = wx.BoxSizer(wx.HORIZONTAL) # 一括サイジング確認ボタン self.check_btn_ctrl = wx.Button(self, wx.ID_ANY, u"一括サイジング確認", wx.DefaultPosition, wx.Size(200, 50), 0) self.check_btn_ctrl.SetToolTip(u"指定されたCSVデータの設定を確認します。") self.check_btn_ctrl.Bind(wx.EVT_LEFT_DCLICK, self.on_doubleclick) self.check_btn_ctrl.Bind(wx.EVT_LEFT_DOWN, self.on_check_click) btn_sizer.Add(self.check_btn_ctrl, 0, wx.ALL, 5) # 一括サイジング実行ボタン self.bulk_btn_ctrl = wx.Button(self, wx.ID_ANY, u"一括サイジング実行", wx.DefaultPosition, wx.Size(200, 50), 0) self.bulk_btn_ctrl.SetToolTip(u"一括でサイジングを実行します") self.bulk_btn_ctrl.Bind(wx.EVT_LEFT_DCLICK, self.on_doubleclick) self.bulk_btn_ctrl.Bind(wx.EVT_LEFT_DOWN, self.on_bulk_click) btn_sizer.Add(self.bulk_btn_ctrl, 0, wx.ALL, 5) self.sizer.Add(btn_sizer, 0, wx.ALIGN_CENTER | wx.SHAPED, 5) # コンソール self.console_ctrl = ConsoleCtrl(self, self.frame.logging_level, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(-1, 420), \ wx.TE_MULTILINE | wx.TE_READONLY | wx.BORDER_NONE | wx.HSCROLL | wx.VSCROLL | wx.WANTS_CHARS) self.console_ctrl.SetBackgroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)) self.console_ctrl.Bind( wx.EVT_CHAR, lambda event: MFormUtils.on_select_all(event, self.console_ctrl)) self.sizer.Add(self.console_ctrl, 1, wx.ALL | wx.EXPAND, 5) # ゲージ self.gauge_ctrl = wx.Gauge(self, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.GA_HORIZONTAL) self.gauge_ctrl.SetValue(0) self.sizer.Add(self.gauge_ctrl, 0, wx.ALL | wx.EXPAND, 5) self.fit() # 変換完了処理バインド self.frame.Bind(EVT_BULK_LOAD_THREAD, self.on_load_result) self.frame.Bind(EVT_BULK_SIZING_THREAD, self.on_exec_result) # フォーム無効化 def disable(self): self.bulk_csv_file_ctrl.disable() self.bulk_btn_ctrl.Disable() self.check_btn_ctrl.Disable() # フォーム無効化 def enable(self): self.bulk_csv_file_ctrl.enable() self.bulk_btn_ctrl.Enable() self.check_btn_ctrl.Enable() def on_doubleclick(self, event: wx.Event): self.timer.Stop() logger.warning("ダブルクリックされました。", decoration=MLogger.DECORATION_BOX) event.Skip(False) return False def on_bulk_click(self, event: wx.Event): self.timer = wx.Timer(self, TIMER_ID) self.timer.Start(200) self.Bind(wx.EVT_TIMER, self.on_bulk, id=TIMER_ID) # サイジング一括実行 def on_bulk(self, event: wx.Event): if self.timer: self.timer.Stop() self.Unbind(wx.EVT_TIMER, id=TIMER_ID) # 出力先をファイルパネルのコンソールに変更 sys.stdout = self.console_ctrl if self.bulk_btn_ctrl.GetLabel() == "一括サイジング停止" and self.frame.worker: # フォーム無効化 self.disable() # 停止状態でボタン押下時、停止 self.frame.worker.stop() # タブ移動可 self.release_tab() # フォーム有効化 self.enable() # ワーカー終了 self.frame.worker = None # プログレス非表示 self.gauge_ctrl.SetValue(0) logger.warning("VMDサイジング一括処理を中断します。", decoration=MLogger.DECORATION_BOX) event.Skip(False) elif not self.frame.worker: # フォーム無効化 self.disable() # タブ固定 self.fix_tab() # コンソールクリア self.console_ctrl.Clear() # 履歴保持 self.save() # サイジング可否チェックの後に実行 self.check(event, True) event.Skip() else: logger.error("まだ処理が実行中です。終了してから再度実行してください。", decoration=MLogger.DECORATION_BOX) event.Skip(False) def on_check_click(self, event: wx.Event): self.timer = wx.Timer(self, TIMER_ID) self.timer.Start(200) self.Bind(wx.EVT_TIMER, self.on_check, id=TIMER_ID) # サイジング一括確認 def on_check(self, event: wx.Event): if self.timer: self.timer.Stop() self.Unbind(wx.EVT_TIMER, id=TIMER_ID) # 出力先をファイルパネルのコンソールに変更 sys.stdout = self.console_ctrl # サイジング可否チェックのみ self.check(event, False) return def save(self): # 履歴保持 self.bulk_csv_file_ctrl.save() # JSON出力 MFileUtils.save_history(self.frame.mydir_path, self.frame.file_hitories) # データチェック def check(self, event: wx.Event, is_exec: bool): # フォーム無効化 self.disable() # タブ固定 self.fix_tab() if not self.bulk_csv_file_ctrl.is_valid(): # CSVパスが無効な場合、終了 self.enable() self.release_tab() return result = True with open(self.bulk_csv_file_ctrl.path(), encoding='cp932', mode='r') as f: reader = csv.reader(f) next(reader) # ヘッダーを読み飛ばす prev_group_no = -1 now_model_no = -1 service_data_txt = "" for ridx, rows in enumerate(reader): row_no = ridx group_no_result, group_no = self.read_csv_row( rows, row_no, 0, "グループNo", True, int, r"\d+", "数値のみ", None) org_motion_result, org_motion_path = self.read_csv_row( rows, row_no, 1, "調整対象モーションVMD/VPD", True, str, None, None, (".vmd", ".vpd")) org_model_result, org_model_path = self.read_csv_row( rows, row_no, 2, "モーション作成元モデルPMX", True, str, None, None, (".pmx")) rep_model_result, rep_model_path = self.read_csv_row( rows, row_no, 3, "モーション変換先モデルPMX", True, str, None, None, (".pmx")) stance_center_xz_result, stance_center_xz_datas = self.read_csv_row( rows, row_no, 4, "センターXZ補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_upper_result, stance_upper_datas = self.read_csv_row( rows, row_no, 5, "上半身補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_lower_result, stance_lower_datas = self.read_csv_row( rows, row_no, 6, "下半身補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_leg_ik_result, stance_leg_ik_datas = self.read_csv_row( rows, row_no, 7, "足IK補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_toe_result, stance_toe_datas = self.read_csv_row( rows, row_no, 8, "つま先補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_toe_ik_result, stance_toe_ik_datas = self.read_csv_row( rows, row_no, 9, "つま先IK補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_shoulder_result, stance_shoulder_datas = self.read_csv_row( rows, row_no, 10, "肩補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_center_y_result, stance_center_y_datas = self.read_csv_row( rows, row_no, 11, "センターY補正", True, int, r"^(0|1)$", "0 もしくは 1", None) separate_twist_result, separate_twist_datas = self.read_csv_row( rows, row_no, 12, "捩り分散", True, int, r"^(0|1)$", "0 もしくは 1", None) morph_result, morph_datas = self.read_csv_row( rows, row_no, 13, "モーフ置換", False, str, r"[^\:]+\:[^\:]+\:\d+\.?\d*\;", "元:先:大きさ;", None) arm_avoidance_result, arm_avoidance_datas = self.read_csv_row( rows, row_no, 14, "接触回避", True, int, r"^(0|1)$", "0 もしくは 1", None) avoidance_name_result, avoidance_name_datas = self.read_csv_row( rows, row_no, 15, "接触回避剛体", False, str, r"[^\;]+\;", "剛体名;", None) arm_alignment_result, arm_alignment_datas = self.read_csv_row( rows, row_no, 16, "位置合わせ", True, int, r"^(0|1)$", "0 もしくは 1", None) finger_alignment_result, finger_alignment_datas = self.read_csv_row( rows, row_no, 17, "指位置合わせ", False, int, r"^(0|1)$", "0 もしくは 1", None) floor_alignment_result, floor_alignment_datas = self.read_csv_row( rows, row_no, 18, "床位置合わせ", False, int, r"^(0|1)$", "0 もしくは 1", None) arm_alignment_length_result, arm_alignment_length_datas = self.read_csv_row( rows, row_no, 19, "手首の距離", False, float, None, None, None) finger_alignment_length_result, finger_alignment_length_datas = self.read_csv_row( rows, row_no, 20, "指の距離", False, float, None, None, None) floor_alignment_length_result, floor_alignment_length_datas = self.read_csv_row( rows, row_no, 21, "床との距離", False, float, None, None, None) arm_check_skip_result, arm_check_skip_datas = self.read_csv_row( rows, row_no, 22, "腕チェックスキップ", True, int, r"^(0|1)$", "0 もしくは 1", None) org_camera_motion_result, org_camera_motion_path = self.read_csv_row( rows, row_no, 23, "カメラモーションVMD", False, str, None, None, (".vmd")) camera_length_result, camera_length_datas = self.read_csv_row( rows, row_no, 24, "距離稼働範囲", False, float, r"^[1-9]\d*\.?\d*", "1以上", None) org_camera_model_result, org_camera_model_path = self.read_csv_row( rows, row_no, 25, "カメラ作成元モデルPMX", False, str, None, None, (".pmx")) camera_y_offset_result, camera_y_offset_datas = self.read_csv_row( rows, row_no, 26, "全長Yオフセット", False, float, None, None, None) result = result & group_no_result & org_motion_result & org_model_result & rep_model_result & stance_center_xz_result \ & stance_upper_result & stance_lower_result & stance_leg_ik_result & stance_toe_result & stance_toe_ik_result & stance_shoulder_result \ & stance_center_y_result & separate_twist_result & arm_check_skip_result & morph_result & arm_avoidance_result & avoidance_name_result \ & arm_alignment_result & finger_alignment_result & floor_alignment_result & arm_alignment_length_result & finger_alignment_length_result \ & floor_alignment_length_result & org_camera_motion_result & camera_length_result & org_camera_model_result \ & camera_y_offset_result if result: if prev_group_no != group_no[0]: now_model_no = 1 if len(service_data_txt) > 0: # 既存データがある場合、出力 logger.info(service_data_txt, decoration=MLogger.DECORATION_BOX) # 先頭モーションの場合 service_data_txt = f"\n【グループNo.{group_no[0]}】 \n" arm_avoidance_txt = "あり" if arm_avoidance_datas[ 0] == 1 else "なし" service_data_txt = f"{service_data_txt} 剛体接触回避: {arm_avoidance_txt}\n" arm_alignment_txt = "あり" if arm_alignment_datas[ 0] == 1 else "なし" service_data_txt = f"{service_data_txt} 手首位置合わせ: {arm_alignment_txt} ({arm_alignment_length_datas})\n" finger_alignment_txt = "あり" if finger_alignment_datas[ 0] == 1 else "なし" service_data_txt = f"{service_data_txt} 指位置合わせ: {finger_alignment_txt} ({finger_alignment_length_datas})\n" floor_alignment_txt = "あり" if floor_alignment_datas[ 0] == 1 else "なし" service_data_txt = f"{service_data_txt} 床位置合わせ: {floor_alignment_txt} ({floor_alignment_length_datas})\n" arm_check_skip_txt = "あり" if arm_check_skip_datas[ 0] == 1 else "なし" service_data_txt = f"{service_data_txt} 腕チェックスキップ: {arm_check_skip_txt}\n" service_data_txt = f"{service_data_txt} カメラ: {org_camera_motion_path}\n" service_data_txt = f"{service_data_txt} 距離制限: {camera_length_datas}\n" else: # 複数人モーションの場合、No加算 now_model_no += 1 service_data_txt = f"{service_data_txt}\n 【人物No.{now_model_no}】 --------- \n" service_data_txt = f"{service_data_txt} モーション: {org_motion_path}\n" service_data_txt = f"{service_data_txt} 作成元モデル: {org_model_path}\n" service_data_txt = f"{service_data_txt} 変換先モデル: {rep_model_path}\n" service_data_txt = f"{service_data_txt} カメラ作成元モデル: {org_camera_model_path}\n" service_data_txt = f"{service_data_txt} Yオフセット: {camera_y_offset_datas}\n" detail_stance_list = [] if stance_center_xz_datas[0] == 1: detail_stance_list.append("センターXZ補正") if stance_upper_datas[0] == 1: detail_stance_list.append("上半身補正") if stance_lower_datas[0] == 1: detail_stance_list.append("下半身補正") if stance_leg_ik_datas[0] == 1: detail_stance_list.append("足IK補正") if stance_toe_datas[0] == 1: detail_stance_list.append("つま先補正") if stance_toe_ik_datas[0] == 1: detail_stance_list.append("つま先IK補正") if stance_shoulder_datas[0] == 1: detail_stance_list.append("肩補正") if stance_center_y_datas[0] == 1: detail_stance_list.append("センターY補正") detail_stance_txt = ", ".join(detail_stance_list) service_data_txt = f"{service_data_txt} スタンス追加補正有無: {detail_stance_txt}\n" twist_txt = "あり" if separate_twist_datas[0] == 1 else "なし" service_data_txt = f"{service_data_txt} 捩り分散有無: {twist_txt}\n" # モーフデータ morph_list = [] for morph_data in morph_datas: m = re.findall(r"([^\:]+)\:([^\:]+)\:(\d+\.?\d*)\;", morph_data) morph_list.append( f"{m[0][0]} → {m[0][1]} ({float(m[0][2])})") morph_txt = ", ".join(morph_list) service_data_txt = f"{service_data_txt} モーフ置換: {morph_txt}\n" # 接触回避データ arm_avoidance_name_list = [] for avoidance_data in avoidance_name_datas: m = re.findall(r"([^\:]+)\;", avoidance_data) arm_avoidance_name_list.append(m[0]) arm_avoidance_name_txt = ", ".join(arm_avoidance_name_list) service_data_txt = f"{service_data_txt} 対象剛体名: {arm_avoidance_name_txt}\n" prev_group_no = group_no[0] if result: if is_exec: # 全部OKなら処理開始 self.load(event, 0) else: if len(service_data_txt) > 0: # 既存データがある場合、最後に出力 logger.info(service_data_txt, decoration=MLogger.DECORATION_BOX) # OKかつ確認のみの場合、出力して終了 logger.info("CSVデータの確認が成功しました。", decoration=MLogger.DECORATION_BOX, title="OK") self.enable() self.release_tab() return else: logger.error("CSVデータに不整合があるため、処理を中断します", decoration=MLogger.DECORATION_BOX) self.enable() self.release_tab() return def read_csv_row(self, rows: list, row_no: int, row_idx: int, row_name: str, row_required: bool, row_type: type, row_regex: str, row_regex_str: str, path_exts: tuple): try: if row_required and (len(rows) < row_idx or not rows[row_idx]): logger.warning("%s行目の%s(%s列目)が設定されていません", row_no + 1, row_name, row_idx + 1) return False, None try: if rows[row_idx] and not row_type(rows[row_idx]): pass except Exception: row_type_str = "半角整数" if row_type == int else "半角数字" logger.warning("%s行目の%s(%s列目)の型(%s)が合っていません", row_no + 1, row_name, row_idx + 1, row_type_str) return False, None if rows[row_idx] and row_regex and not re.findall( row_regex, rows[row_idx]): logger.warning("%s行目の%s(%s列目)の表示形式(%s)が合っていません", row_no + 1, row_name, row_idx + 1, row_regex_str) return False, None if rows[row_idx] and path_exts: if not rows[row_idx] or (not os.path.exists(rows[row_idx]) or not os.path.isfile(rows[row_idx])): logger.warning("%s行目の%s(%s列目)のファイルが存在していません", row_no + 1, row_name, row_idx + 1) return False, None # ファイル名・拡張子 file_name, ext = os.path.splitext( os.path.basename(rows[row_idx])) if (ext not in path_exts): logger.warning("%s行目の%s(%s列目)のファイル拡張子(%s)が合っていません", row_no + 1, row_name, row_idx + 1, \ ','.join(map(str, path_exts)) if len(path_exts) > 1 else path_exts) return False, None # 読み取り実施 if rows[row_idx] and row_regex: # 正規表現の場合は、リスト変換して返す if row_type: # 型指定がある場合は変換して返す return True, [ row_type(v) for v in re.findall(row_regex, rows[row_idx]) ] else: return True, re.findall(row_regex, rows[row_idx]) if (row_type == float or row_type == int) and not rows[row_idx]: # 数値で任意はゼロ設定 return True, 0 elif row_type: return True, row_type(rows[row_idx]) return True, rows[row_idx] except Exception as e: logger.warning("%s行目の%s(%s列目)の読み取りに失敗しました\n%s", row_no + 1, row_name, row_idx + 1, e) return False, None # 読み込み def load(self, event, line_idx): # グループ単位で設定 now_group_no = -1 now_motion_idx = -1 row_no = 0 with open(self.bulk_csv_file_ctrl.path(), encoding='cp932', mode='r') as f: reader = csv.reader(f) next(reader) # ヘッダーを読み飛ばす for ridx, rows in enumerate(reader): row_no = ridx if row_no < line_idx: # 自分より前の行の場合、スキップ continue group_no_result, group_no = self.read_csv_row( rows, row_no, 0, "グループNo", True, int, r"\d+", "数値のみ", None) if len(group_no) == 0: # グループNOが取れなかったから終了 return if len(group_no) > 0 and row_no == line_idx: # 指定INDEXに到達したら設定して読み取り開始 now_motion_idx = 0 now_group_no = group_no[0] else: now_motion_idx += 1 if len(group_no) > 0 and group_no[0] != now_group_no: # グループNOが変わっていたら、そのまま終了 break group_no_result, group_no = self.read_csv_row( rows, row_no, 0, "グループNo", True, int, r"\d+", "数値のみ", None) org_motion_result, org_motion_path = self.read_csv_row( rows, row_no, 1, "調整対象モーションVMD/VPD", True, str, None, None, (".vmd", ".vpd")) org_model_result, org_model_path = self.read_csv_row( rows, row_no, 2, "モーション作成元モデルPMX", True, str, None, None, (".pmx")) rep_model_result, rep_model_path = self.read_csv_row( rows, row_no, 3, "モーション変換先モデルPMX", True, str, None, None, (".pmx")) stance_center_xz_result, stance_center_xz_datas = self.read_csv_row( rows, row_no, 4, "センターXZ補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_upper_result, stance_upper_datas = self.read_csv_row( rows, row_no, 5, "上半身補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_lower_result, stance_lower_datas = self.read_csv_row( rows, row_no, 6, "下半身補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_leg_ik_result, stance_leg_ik_datas = self.read_csv_row( rows, row_no, 7, "足IK補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_toe_result, stance_toe_datas = self.read_csv_row( rows, row_no, 8, "つま先補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_toe_ik_result, stance_toe_ik_datas = self.read_csv_row( rows, row_no, 9, "つま先IK補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_shoulder_result, stance_shoulder_datas = self.read_csv_row( rows, row_no, 10, "肩補正", True, int, r"^(0|1)$", "0 もしくは 1", None) stance_center_y_result, stance_center_y_datas = self.read_csv_row( rows, row_no, 11, "センターY補正", True, int, r"^(0|1)$", "0 もしくは 1", None) separate_twist_result, separate_twist_datas = self.read_csv_row( rows, row_no, 12, "捩り分散", True, int, r"^(0|1)$", "0 もしくは 1", None) morph_result, morph_datas = self.read_csv_row( rows, row_no, 13, "モーフ置換", False, str, r"[^\:]+\:[^\:]+\:\d+\.?\d*\;", "元:先:大きさ;", None) arm_avoidance_result, arm_avoidance_datas = self.read_csv_row( rows, row_no, 14, "接触回避", True, int, r"^(0|1)$", "0 もしくは 1", None) avoidance_name_result, avoidance_name_datas = self.read_csv_row( rows, row_no, 15, "接触回避剛体", False, str, r"[^\;]+\;", "剛体名;", None) arm_alignment_result, arm_alignment_datas = self.read_csv_row( rows, row_no, 16, "位置合わせ", True, int, r"^(0|1)$", "0 もしくは 1", None) finger_alignment_result, finger_alignment_datas = self.read_csv_row( rows, row_no, 17, "指位置合わせ", False, int, r"^(0|1)$", "0 もしくは 1", None) floor_alignment_result, floor_alignment_datas = self.read_csv_row( rows, row_no, 18, "床位置合わせ", False, int, r"^(0|1)$", "0 もしくは 1", None) arm_alignment_length_result, arm_alignment_length_datas = self.read_csv_row( rows, row_no, 19, "手首の距離", False, float, None, None, None) finger_alignment_length_result, finger_alignment_length_datas = self.read_csv_row( rows, row_no, 20, "指の距離", False, float, None, None, None) floor_alignment_length_result, floor_alignment_length_datas = self.read_csv_row( rows, row_no, 21, "床との距離", False, float, None, None, None) arm_check_skip_result, arm_check_skip_datas = self.read_csv_row( rows, row_no, 22, "腕チェックスキップ", True, int, r"^(0|1)$", "0 もしくは 1", None) org_camera_motion_result, org_camera_motion_path = self.read_csv_row( rows, row_no, 23, "カメラモーションVMD", False, str, None, None, (".vmd")) camera_length_result, camera_length_datas = self.read_csv_row( rows, row_no, 24, "距離稼働範囲", False, float, None, None, None) org_camera_model_result, org_camera_model_path = self.read_csv_row( rows, row_no, 25, "カメラ作成元モデルPMX", False, str, None, None, (".pmx")) camera_y_offset_result, camera_y_offset_datas = self.read_csv_row( rows, row_no, 26, "全長Yオフセット", False, float, None, None, None) if now_motion_idx == 0: # 複数パネルはクリア self.frame.multi_panel_ctrl.on_clear_set(event) # ファイルパネル設定 self.frame.file_panel_ctrl.file_set.motion_vmd_file_ctrl.file_ctrl.SetPath( org_motion_path) self.frame.file_panel_ctrl.file_set.org_model_file_ctrl.file_ctrl.SetPath( org_model_path) self.frame.file_panel_ctrl.file_set.rep_model_file_ctrl.file_ctrl.SetPath( rep_model_path) self.frame.file_panel_ctrl.file_set.output_vmd_file_ctrl.file_ctrl.SetPath( "") self.frame.file_panel_ctrl.file_set.org_model_file_ctrl.title_parts_ctrl.SetValue( stance_center_xz_datas[0] | stance_upper_datas[0] | stance_lower_datas[0] | stance_leg_ik_datas[0] | \ stance_toe_datas[0] | stance_toe_ik_datas[0] | stance_shoulder_datas[0] | stance_center_y_datas[0] ) # スタンス追加補正 self.frame.file_panel_ctrl.file_set.selected_stance_details = [] if stance_center_xz_datas[0] == 1: self.frame.file_panel_ctrl.file_set.selected_stance_details.append( 0) if stance_upper_datas[0] == 1: self.frame.file_panel_ctrl.file_set.selected_stance_details.append( 1) if stance_lower_datas[0] == 1: self.frame.file_panel_ctrl.file_set.selected_stance_details.append( 2) if stance_leg_ik_datas[0] == 1: self.frame.file_panel_ctrl.file_set.selected_stance_details.append( 3) if stance_toe_datas[0] == 1: self.frame.file_panel_ctrl.file_set.selected_stance_details.append( 4) if stance_toe_ik_datas[0] == 1: self.frame.file_panel_ctrl.file_set.selected_stance_details.append( 5) if stance_shoulder_datas[0] == 1: self.frame.file_panel_ctrl.file_set.selected_stance_details.append( 6) if stance_center_y_datas[0] == 1: self.frame.file_panel_ctrl.file_set.selected_stance_details.append( 7) # 捩り分散 self.frame.file_panel_ctrl.file_set.rep_model_file_ctrl.title_parts_ctrl.SetValue( separate_twist_datas[0]) # 腕チェックスキップ self.frame.arm_panel_ctrl.arm_check_skip_flg_ctrl.SetValue( arm_check_skip_datas[0]) # モーフデータ self.frame.morph_panel_ctrl.bulk_morph_set_dict[1] = [] for morph_data in morph_datas: m = re.findall(r"([^\:]+)\:([^\:]+)\:(\d+\.?\d*)\;", morph_data) self.frame.morph_panel_ctrl.bulk_morph_set_dict[ 1].append((m[0][0], m[0][1], float(m[0][2]))) # 接触回避 self.frame.arm_panel_ctrl.arm_process_flg_avoidance.SetValue( arm_avoidance_datas[0]) # 接触回避データ self.frame.arm_panel_ctrl.bulk_avoidance_set_dict[0] = [] for avoidance_data in avoidance_name_datas: m = re.findall(r"([^\:]+)\;", avoidance_data) self.frame.arm_panel_ctrl.bulk_avoidance_set_dict[ 0].append(m[0][0]) # 位置合わせ self.frame.arm_panel_ctrl.arm_process_flg_alignment.SetValue( arm_alignment_datas[0]) self.frame.arm_panel_ctrl.arm_alignment_finger_flg_ctrl.SetValue( finger_alignment_datas[0]) self.frame.arm_panel_ctrl.arm_alignment_floor_flg_ctrl.SetValue( floor_alignment_datas[0]) # 位置合わせ距離 self.frame.arm_panel_ctrl.alignment_distance_wrist_slider.SetValue( arm_alignment_length_datas) self.frame.arm_panel_ctrl.alignment_distance_finger_slider.SetValue( finger_alignment_length_datas) self.frame.arm_panel_ctrl.alignment_distance_floor_slider.SetValue( floor_alignment_length_datas) # カメラ self.frame.camera_panel_ctrl.camera_vmd_file_ctrl.file_ctrl.SetPath( org_camera_motion_path) self.frame.camera_panel_ctrl.output_camera_vmd_file_ctrl.file_ctrl.SetPath( "") self.frame.camera_panel_ctrl.camera_length_slider.SetValue( camera_length_datas) # カメラ元情報 self.frame.camera_panel_ctrl.initialize(event) self.frame.camera_panel_ctrl.camera_set_dict[ 1].camera_model_file_ctrl.file_ctrl.SetPath( org_camera_model_path) self.frame.camera_panel_ctrl.camera_set_dict[ 1].camera_offset_y_ctrl.SetValue(camera_y_offset_datas) # 出力パス変更 self.frame.file_panel_ctrl.file_set.set_output_vmd_path( event) self.frame.camera_panel_ctrl.set_output_vmd_path(event) else: # 複数パネルセット追加 self.frame.multi_panel_ctrl.on_add_set(event) # ファイルパネル設定 self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].motion_vmd_file_ctrl.file_ctrl.SetPath( org_motion_path) self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].org_model_file_ctrl.file_ctrl.SetPath( org_model_path) self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].rep_model_file_ctrl.file_ctrl.SetPath( rep_model_path) self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].output_vmd_file_ctrl.file_ctrl.SetPath("") self.frame.multi_panel_ctrl.file_set_list[now_motion_idx - 1].org_model_file_ctrl.title_parts_ctrl.SetValue( stance_center_xz_datas[0] | stance_upper_datas[0] | stance_lower_datas[0] | stance_leg_ik_datas[0] | \ stance_toe_datas[0] | stance_toe_ik_datas[0] | stance_shoulder_datas[0] | stance_center_y_datas[0] ) # スタンス追加補正 self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].selected_stance_details = [] if stance_center_xz_datas[0] == 1: self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].selected_stance_details.append(0) if stance_upper_datas[0] == 1: self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].selected_stance_details.append(1) if stance_lower_datas[0] == 1: self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].selected_stance_details.append(2) if stance_leg_ik_datas[0] == 1: self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].selected_stance_details.append(3) if stance_toe_datas[0] == 1: self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].selected_stance_details.append(4) if stance_toe_ik_datas[0] == 1: self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].selected_stance_details.append(5) if stance_shoulder_datas[0] == 1: self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].selected_stance_details.append(6) if stance_center_y_datas[0] == 1: self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].selected_stance_details.append(7) # 捩り分散 self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].rep_model_file_ctrl.title_parts_ctrl.SetValue( separate_twist_datas[0]) # モーフデータ self.frame.morph_panel_ctrl.bulk_morph_set_dict[ now_motion_idx + 1] = [] for morph_data in morph_datas: m = re.findall(r"([^\:]+)\:([^\:]+)\:(\d+\.?\d*)\;", morph_data) self.frame.morph_panel_ctrl.bulk_morph_set_dict[ now_motion_idx + 1].append( (m[0][0], m[0][1], float(m[0][2]))) # 接触回避データ self.frame.arm_panel_ctrl.bulk_avoidance_set_dict[ now_motion_idx - 1] = [] for avoidance_data in avoidance_name_datas: m = re.findall(r"([^\:]+)\;", avoidance_data) self.frame.arm_panel_ctrl.bulk_avoidance_set_dict[ now_motion_idx - 1].append(m[0][0]) # 指位置合わせは常に0(ダイアログ防止) self.frame.arm_panel_ctrl.arm_alignment_finger_flg_ctrl.SetValue( 0) # カメラ元情報 self.frame.camera_panel_ctrl.initialize(event) self.frame.camera_panel_ctrl.camera_set_dict[ now_motion_idx + 1].camera_model_file_ctrl.file_ctrl.SetPath( org_camera_model_path) self.frame.camera_panel_ctrl.camera_set_dict[ now_motion_idx + 1].camera_offset_y_ctrl.SetValue(camera_y_offset_datas) # 出力パス変更 self.frame.multi_panel_ctrl.file_set_list[ now_motion_idx - 1].set_output_vmd_path(event) # 一旦リリース self.frame.release_tab() # ファイルタブに移動 self.frame.note_ctrl.ChangeSelection( self.frame.file_panel_ctrl.tab_idx) # フォーム無効化 self.frame.file_panel_ctrl.disable() # タブ固定 self.frame.file_panel_ctrl.fix_tab() # ファイルタブのコンソール sys.stdout = self.frame.file_panel_ctrl.console_ctrl self.frame.elapsed_time = 0 result = True result = self.frame.is_valid() and result if not result: # タブ移動可 self.frame.release_tab() # フォーム有効化 self.frame.enable() return result # 読み込み開始 if self.frame.load_worker: logger.error("まだ処理が実行中です。終了してから再度実行してください。", decoration=MLogger.DECORATION_BOX) else: # 停止ボタンに切り替え self.frame.file_panel_ctrl.check_btn_ctrl.SetLabel("読み込み処理停止") self.frame.file_panel_ctrl.check_btn_ctrl.Enable() # 別スレッドで実行(次行がない場合、-1で終了フラグ) self.frame.load_worker = LoadWorkerThread( self.frame, BulkLoadThreadEvent, row_no if row_no > line_idx else -1, True, False, False) self.frame.load_worker.start() return result # 読み込み完了処理 def on_load_result(self, event: wx.Event): self.frame.elapsed_time = event.elapsed_time # タブ移動可 self.frame.release_tab() # フォーム有効化 self.frame.enable() # ワーカー終了 self.frame.load_worker = None # プログレス非表示 self.frame.file_panel_ctrl.gauge_ctrl.SetValue(0) if not event.result: # 終了音を鳴らす self.frame.sound_finish() event.Skip() return False result = self.frame.is_loaded_valid() if not result: # タブ移動可 self.frame.release_tab() # フォーム有効化 self.frame.enable() event.Skip() return False logger.info("ファイルデータ読み込みが完了しました", decoration=MLogger.DECORATION_BOX, title="OK") # フォーム無効化 self.frame.file_panel_ctrl.disable() # タブ固定 self.frame.file_panel_ctrl.fix_tab() if self.frame.worker: logger.error("まだ処理が実行中です。終了してから再度実行してください。", decoration=MLogger.DECORATION_BOX) else: # 停止ボタンに切り替え self.frame.file_panel_ctrl.exec_btn_ctrl.SetLabel("VMDサイジング停止") self.frame.file_panel_ctrl.exec_btn_ctrl.Enable() # 別スレッドで実行 self.frame.worker = SizingWorkerThread(self.frame, BulkSizingThreadEvent, event.target_idx, self.frame.is_saving, self.frame.is_out_log) self.frame.worker.start() # スレッド実行結果 def on_exec_result(self, event: wx.Event): # 実行ボタンに切り替え self.frame.file_panel_ctrl.exec_btn_ctrl.SetLabel("VMDサイジング実行") self.frame.file_panel_ctrl.exec_btn_ctrl.Enable() if not event.result: # 終了音を鳴らす self.frame.sound_finish() event.Skip() return False self.frame.elapsed_time += event.elapsed_time worked_time = "\n処理時間: {0}".format(self.frame.show_worked_time()) logger.info(worked_time) if self.frame.is_out_log and event.output_log_path and os.path.exists( event.output_log_path): # ログ出力対象である場合、追記 with open(event.output_log_path, mode='a', encoding='utf-8') as f: f.write(worked_time) # ワーカー終了 self.frame.worker = None if event.target_idx >= 0: # 次のターゲットがある場合、次を処理 logger.info("\n----------------------------------") return self.load(event, event.target_idx) # ファイルタブのコンソール sys.stdout = self.frame.file_panel_ctrl.console_ctrl # 終了音を鳴らす self.frame.sound_finish() # ファイルタブのコンソール if sys.stdout != self.frame.file_panel_ctrl.console_ctrl: sys.stdout = self.frame.file_panel_ctrl.console_ctrl # Bulk用データ消去 self.frame.morph_panel_ctrl.bulk_morph_set_dict = {} self.frame.arm_panel_ctrl.bulk_avoidance_set_dict = {} self.frame.camera_panel_ctrl.bulk_camera_set_dict = {} # タブ移動可 self.frame.release_tab() # フォーム有効化 self.frame.enable() # プログレス非表示 self.frame.file_panel_ctrl.gauge_ctrl.SetValue(0) logger.info("全てのサイジング処理が終了しました", decoration=MLogger.DECORATION_BOX, title="一括処理")
class CameraPanel(BasePanel): def __init__(self, frame: wx.Frame, parent: wx.Notebook, tab_idx: int): super().__init__(frame, parent, tab_idx) self.header_panel = CameraHeaderPanel(self.frame, self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL) self.header_sizer = wx.BoxSizer(wx.VERTICAL) self.description_txt = wx.StaticText(self.header_panel, wx.ID_ANY, u"指定されたカメラモーションのサイジングを、ボーンモーションのサイジングと同時に行えます。\n" \ + "全長オフセットYは、カメラに映す変換先モデルの全長を調整するオフセット値を指定できます。", wx.DefaultPosition, wx.DefaultSize, 0) self.header_sizer.Add(self.description_txt, 0, wx.ALL, 5) self.static_line01 = wx.StaticLine(self.header_panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) self.header_sizer.Add(self.static_line01, 0, wx.EXPAND | wx.ALL, 5) # カメラVMDファイルコントロール self.camera_vmd_file_ctrl = HistoryFilePickerCtrl(self.frame, self.header_panel, u"カメラモーションVMD", u"カメラモーションVMDファイルを開く", ("vmd"), wx.FLP_DEFAULT_STYLE, \ u"調整したいカメラモーションのVMDパスを指定してください。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=0, title_parts_ctrl=None, title_parts2_ctrl=None, file_histories_key="camera_vmd", is_change_output=True, \ is_aster=False, is_save=False, set_no=1) self.header_sizer.Add(self.camera_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) # 出力先VMDファイルコントロール self.output_camera_vmd_file_ctrl = BaseFilePickerCtrl(frame, self.header_panel, u"出力カメラVMD", u"出力カメラVMDファイルを開く", ("vmd"), wx.FLP_OVERWRITE_PROMPT | wx.FLP_SAVE | wx.FLP_USE_TEXTCTRL, \ u"調整結果のカメラVMD出力パスを指定してください。\nカメラVMDファイル名に基づいて自動生成されますが、任意のパスに変更することも可能です。", \ is_aster=False, is_save=True, set_no=1) self.header_sizer.Add(self.output_camera_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) self.header_panel.SetSizer(self.header_sizer) self.header_panel.Layout() self.sizer.Add(self.header_panel, 0, wx.EXPAND | wx.ALL, 5) # カメラセット(key: ファイルセット番号, value: カメラセット) self.camera_set_dict = {} # カメラセット用基本Sizer self.set_list_sizer = wx.BoxSizer(wx.VERTICAL) self.scrolled_window = CameraScrolledWindow(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, \ wx.FULL_REPAINT_ON_RESIZE | wx.VSCROLL | wx.ALWAYS_SHOW_SB) self.scrolled_window.SetScrollRate(5, 5) # スクロールバーの表示のためにサイズ調整 self.scrolled_window.SetSizer(self.set_list_sizer) self.scrolled_window.Layout() self.sizer.Add(self.scrolled_window, 1, wx.ALL | wx.EXPAND | wx.FIXED_MINSIZE, 5) self.sizer.Layout() self.fit() # カメラタブからカメラリスト生成 def get_camera_list(self, set_no: int): if set_no not in self.camera_set_dict: # そもそも登録がなければ何もなし return [] else: # あれば、そのNoのカメラリスト return self.camera_set_dict[set_no].get_camera_list() # カメラタブ初期化処理 def initialize(self, event: wx.Event): if 1 not in self.camera_set_dict: # 空から作る場合、ファイルタブのファイルセット参照 self.add_set(1, self.frame.file_panel_ctrl.file_set) else: # ある場合、モデル名だけ入替 self.camera_set_dict[1].model_name_txt.SetLabel("{0} → {1}".format(\ self.frame.file_panel_ctrl.file_set.org_model_file_ctrl.file_model_ctrl.txt_ctrl.GetValue()[1:-1], \ self.frame.file_panel_ctrl.file_set.rep_model_file_ctrl.file_model_ctrl.txt_ctrl.GetValue()[1:-1])) # multiはあるだけ調べる for multi_file_set_idx, multi_file_set in enumerate( self.frame.multi_panel_ctrl.file_set_list): set_no = multi_file_set_idx + 2 if set_no not in self.camera_set_dict: # 空から作る場合、複数タブのファイルセット参照 self.add_set(set_no, multi_file_set) else: # ある場合、モデル名だけ入替 self.camera_set_dict[set_no].model_name_txt.SetLabel("{0} → {1}".format(\ multi_file_set.org_model_file_ctrl.file_model_ctrl.txt_ctrl.GetValue()[1:-1], \ multi_file_set.rep_model_file_ctrl.file_model_ctrl.txt_ctrl.GetValue()[1:-1])) def add_set(self, set_idx: int, file_set: SizingFileSet): new_camera_set = CameraSet(self.frame, self, self.scrolled_window, set_idx, file_set) self.set_list_sizer.Add(new_camera_set.set_sizer, 0, wx.EXPAND | wx.ALL, 5) self.camera_set_dict[set_idx] = new_camera_set # スクロールバーの表示のためにサイズ調整 self.set_list_sizer.Layout() self.set_list_sizer.FitInside(self.scrolled_window) # フォーム無効化 def disable(self): self.file_set.disable() # フォーム無効化 def enable(self): self.file_set.enable() def save(self): self.camera_vmd_file_ctrl.save()
class CameraPanel(BasePanel): def __init__(self, frame: wx.Frame, parent: wx.Notebook, tab_idx: int): super().__init__(frame, parent, tab_idx) self.header_panel = CameraHeaderPanel(self.frame, self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL) self.header_sizer = wx.BoxSizer(wx.VERTICAL) self.description_txt = wx.StaticText(self.header_panel, wx.ID_ANY, u"指定されたカメラモーションのサイジングを、ボーンモーションのサイジングと同時に行えます。\n" \ + "全長オフセットYは、カメラに映す変換先モデルの全長を調整するオフセット値を指定できます。", wx.DefaultPosition, wx.DefaultSize, 0) self.header_sizer.Add(self.description_txt, 0, wx.ALL, 5) self.static_line01 = wx.StaticLine(self.header_panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) self.header_sizer.Add(self.static_line01, 0, wx.EXPAND | wx.ALL, 5) camera_only_flg_spacer_ctrl = wx.StaticText(self.header_panel, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, 0) # カメラサイジングのみ実行 self.camera_only_flg_ctrl = wx.CheckBox(self.header_panel, wx.ID_ANY, u"カメラサイジングのみ実行", wx.DefaultPosition, wx.DefaultSize, 0) self.camera_only_flg_ctrl.SetToolTip( u"ボーンサイジング済みファイルを出力ファイルに指定した上でチェックを入れると、\nそのサイジング済みVMDを元にカメラサイジングを実行します。" ) self.camera_only_flg_ctrl.Bind(wx.EVT_CHECKBOX, self.set_output_vmd_path) # カメラVMDファイルコントロール self.camera_vmd_file_ctrl = HistoryFilePickerCtrl(self.frame, self.header_panel, u"カメラモーションVMD", u"カメラモーションVMDファイルを開く", ("vmd"), wx.FLP_DEFAULT_STYLE, \ u"調整したいカメラモーションのVMDパスを指定してください。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=0, title_parts_ctrl=camera_only_flg_spacer_ctrl, title_parts2_ctrl=self.camera_only_flg_ctrl, file_histories_key="camera_vmd", \ is_change_output=True, is_aster=False, is_save=False, set_no=1) self.header_sizer.Add(self.camera_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) # 出力先VMDファイルコントロール self.output_camera_vmd_file_ctrl = BaseFilePickerCtrl(frame, self.header_panel, u"出力カメラVMD", u"出力カメラVMDファイルを開く", ("vmd"), wx.FLP_OVERWRITE_PROMPT | wx.FLP_SAVE | wx.FLP_USE_TEXTCTRL, \ u"調整結果のカメラVMD出力パスを指定してください。\nカメラVMDファイル名に基づいて自動生成されますが、任意のパスに変更することも可能です。", \ is_aster=False, is_save=True, set_no=1) self.header_sizer.Add(self.output_camera_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) # カメラ距離調整スライダー self.camera_length_sizer = wx.BoxSizer(wx.HORIZONTAL) self.camera_length_txt = wx.StaticText(self.header_panel, wx.ID_ANY, u"距離可動範囲", wx.DefaultPosition, wx.DefaultSize, 0) self.camera_length_txt.SetToolTip(u"ステージの大きさなどにより、カメラの距離の調整範囲を限定したい場合に\n" \ + "カメラの距離可動範囲を限定することができます。\n" \ + "可動範囲は手動で調整する事も可能です。") self.camera_length_txt.Wrap(-1) self.camera_length_sizer.Add(self.camera_length_txt, 0, wx.ALL, 5) self.camera_length_type_ctrl = wx.Choice( self.header_panel, id=wx.ID_ANY, choices=["距離制限強", "距離制限弱", "距離制限なし"]) self.camera_length_type_ctrl.SetSelection(2) self.camera_length_type_ctrl.Bind(wx.EVT_CHOICE, self.on_camera_length_type) self.camera_length_type_ctrl.SetToolTip(u"「距離制限強」 … 小さめのステージ用。距離可動範囲を厳しめに制限します。\n" \ + "「距離制限弱」 … 中くらいのステージ用。距離可動範囲を多少制限します。\n" \ + "「距離制限なし」 … 距離可動範囲を無制限とし、元モデルと同じ映り具合になるよう、最大限調整します。") self.camera_length_sizer.Add(self.camera_length_type_ctrl, 0, wx.ALL, 5) self.camera_length_label = wx.StaticText(self.header_panel, wx.ID_ANY, u"(5)", wx.DefaultPosition, wx.DefaultSize, 0) self.camera_length_label.SetToolTip(u"現在指定されているカメラ距離の可動範囲です。") self.camera_length_label.Wrap(-1) self.camera_length_sizer.Add(self.camera_length_label, 0, wx.ALL, 5) self.camera_length_slider = FloatSliderCtrl(self.header_panel, wx.ID_ANY, 5, 1, 5, 0.01, self.camera_length_label, wx.DefaultPosition, wx.DefaultSize, wx.SL_HORIZONTAL) self.camera_length_slider.Bind(wx.EVT_SCROLL_CHANGED, self.set_output_vmd_path) self.camera_length_sizer.Add(self.camera_length_slider, 1, wx.ALL | wx.EXPAND, 5) self.header_sizer.Add(self.camera_length_sizer, 0, wx.ALL | wx.EXPAND, 5) self.header_panel.SetSizer(self.header_sizer) self.header_panel.Layout() self.sizer.Add(self.header_panel, 0, wx.EXPAND | wx.ALL, 5) # カメラセット(key: ファイルセット番号, value: カメラセット) self.camera_set_dict = {} # Bulk用カメラセット self.bulk_camera_set_dict = {} # カメラセット用基本Sizer self.set_list_sizer = wx.BoxSizer(wx.VERTICAL) self.scrolled_window = CameraScrolledWindow(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, \ wx.FULL_REPAINT_ON_RESIZE | wx.VSCROLL | wx.ALWAYS_SHOW_SB) self.scrolled_window.SetScrollRate(5, 5) # スクロールバーの表示のためにサイズ調整 self.scrolled_window.SetSizer(self.set_list_sizer) self.scrolled_window.Layout() self.sizer.Add(self.scrolled_window, 1, wx.ALL | wx.EXPAND | wx.FIXED_MINSIZE, 5) self.sizer.Layout() self.fit() def on_camera_length_type(self, event): if self.camera_length_type_ctrl.GetSelection() == 0: self.camera_length_slider.SetValue(1.05) elif self.camera_length_type_ctrl.GetSelection() == 1: self.camera_length_slider.SetValue(1.3) else: self.camera_length_slider.SetValue(5) self.set_output_vmd_path(event) def set_output_vmd_path(self, event, is_force=False): # カメラ出力パスを強制的に変更する self.header_panel.set_output_vmd_path(event, True) # カメラタブ初期化処理 def initialize(self, event: wx.Event): self.bulk_camera_set_dict = {} if 1 not in self.camera_set_dict: # 空から作る場合、ファイルタブのファイルセット参照 self.add_set(1, self.frame.file_panel_ctrl.file_set) else: # ある場合、モデル名だけ入替 self.camera_set_dict[1].model_name_txt.SetLabel("{0} → {1}".format(\ self.frame.file_panel_ctrl.file_set.org_model_file_ctrl.file_model_ctrl.txt_ctrl.GetValue()[1:-1], \ self.frame.file_panel_ctrl.file_set.rep_model_file_ctrl.file_model_ctrl.txt_ctrl.GetValue()[1:-1])) # multiはあるだけ調べる for multi_file_set_idx, multi_file_set in enumerate( self.frame.multi_panel_ctrl.file_set_list): set_no = multi_file_set_idx + 2 if set_no not in self.camera_set_dict: # 空から作る場合、複数タブのファイルセット参照 self.add_set(set_no, multi_file_set) else: # ある場合、モデル名だけ入替 self.camera_set_dict[set_no].model_name_txt.SetLabel("{0} → {1}".format(\ multi_file_set.org_model_file_ctrl.file_model_ctrl.txt_ctrl.GetValue()[1:-1], \ multi_file_set.rep_model_file_ctrl.file_model_ctrl.txt_ctrl.GetValue()[1:-1])) def add_set(self, set_idx: int, file_set: SizingFileSet): new_camera_set = CameraSet(self.frame, self, self.scrolled_window, set_idx, file_set) self.set_list_sizer.Add(new_camera_set.set_sizer, 0, wx.EXPAND | wx.ALL, 5) self.camera_set_dict[set_idx] = new_camera_set # スクロールバーの表示のためにサイズ調整 self.set_list_sizer.Layout() self.set_list_sizer.FitInside(self.scrolled_window) # フォーム無効化 def disable(self): self.file_set.disable() # フォーム無効化 def enable(self): self.file_set.enable() def save(self): self.camera_vmd_file_ctrl.save()
class SmoothPanel(BasePanel): def __init__(self, frame: wx.Frame, parent: wx.Notebook, tab_idx: int): super().__init__(frame, parent, tab_idx) self.convert_smooth_worker = None self.header_sizer = wx.BoxSizer(wx.VERTICAL) self.description_txt = wx.StaticText(self, wx.ID_ANY, u"指定されたVMDファイルに対して、キーを分割し、滑らかな補間曲線で繋いで、再出力します。\n" \ + "スムージング回数1回で、全打ちとなり、2回目以降はフィルタリングした後に補間曲線で繋ぎます。\n" \ + "不要キー削除を行うと、キーが間引きされます。キー間がオリジナルから多少ずれ、やや時間がかかります。", wx.DefaultPosition, wx.DefaultSize, 0) self.header_sizer.Add(self.description_txt, 0, wx.ALL, 5) self.static_line01 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) self.header_sizer.Add(self.static_line01, 0, wx.EXPAND | wx.ALL, 5) # 対象VMDファイルコントロール self.smooth_vmd_file_ctrl = HistoryFilePickerCtrl(self.frame, self, u"対象モーションVMD", u"対象モーションVMDファイルを開く", ("vmd"), wx.FLP_DEFAULT_STYLE, \ u"調整したい対象モーションのVMDパスを指定してください。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=46, title_parts_ctrl=None, title_parts2_ctrl=None, file_histories_key="smooth_vmd", is_change_output=True, \ is_aster=False, is_save=False, set_no=1) self.header_sizer.Add(self.smooth_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) # 対象PMXファイルコントロール self.smooth_model_file_ctrl = HistoryFilePickerCtrl(self.frame, self, u"適用モデルPMX", u"適用モデルPMXファイルを開く", ("pmx"), wx.FLP_DEFAULT_STYLE, \ u"モーションを適用したいモデルのPMXパスを指定してください。\n人体モデル以外にも適用可能です。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=52, title_parts_ctrl=None, title_parts2_ctrl=None, file_histories_key="smooth_pmx", \ is_change_output=True, is_aster=False, is_save=False, set_no=1) self.header_sizer.Add(self.smooth_model_file_ctrl.sizer, 1, wx.EXPAND, 0) # 出力先VMDファイルコントロール self.output_smooth_vmd_file_ctrl = BaseFilePickerCtrl(frame, self, u"出力対象VMD", u"出力対象VMDファイルを開く", ("vmd"), wx.FLP_OVERWRITE_PROMPT | wx.FLP_SAVE | wx.FLP_USE_TEXTCTRL, \ u"調整結果の対象VMD出力パスを指定してください。\n対象VMDファイル名に基づいて自動生成されますが、任意のパスに変更することも可能です。", \ is_aster=False, is_save=True, set_no=1) self.header_sizer.Add(self.output_smooth_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) self.sizer.Add(self.header_sizer, 0, wx.EXPAND | wx.ALL, 5) self.target_sizer = wx.BoxSizer(wx.HORIZONTAL) # ボーン名指定 self.bone_target_txt_ctrl = wx.TextCtrl( self, wx.ID_ANY, "", wx.DefaultPosition, (450, 50), wx.HSCROLL | wx.VSCROLL | wx.TE_MULTILINE | wx.TE_READONLY) self.bone_target_txt_ctrl.SetBackgroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)) self.target_sizer.Add(self.bone_target_txt_ctrl, 1, wx.EXPAND | wx.ALL, 5) self.bone_target_btn_ctrl = wx.Button(self, wx.ID_ANY, u"対象指定", wx.DefaultPosition, wx.DefaultSize, 0) self.bone_target_btn_ctrl.SetToolTip( u"モーションに登録されているボーン・モーフから、スムージングにかけたいボーン・モーフを指定できます") self.bone_target_btn_ctrl.Bind(wx.EVT_BUTTON, self.on_click_bone_target) self.target_sizer.Add(self.bone_target_btn_ctrl, 0, wx.ALIGN_BOTTOM | wx.ALL, 5) self.sizer.Add(self.target_sizer, 0, wx.EXPAND | wx.ALL, 5) self.setting_sizer = wx.BoxSizer(wx.HORIZONTAL) # 処理回数 self.loop_cnt_txt = wx.StaticText(self, wx.ID_ANY, u"処理回数", wx.DefaultPosition, wx.DefaultSize, 0) self.setting_sizer.Add(self.loop_cnt_txt, 0, wx.ALL, 5) self.loop_cnt_ctrl = wx.SpinCtrl(self, id=wx.ID_ANY, size=wx.Size(60, -1), value="2", min=1, max=99999999, initial=2) self.loop_cnt_ctrl.SetToolTip( u"スムージングを行う回数を指定してください。\n1回だと全打ちになります。\n2回目以降はフィルタをかけた上で間引きします。\n回数が増えると、変化が遅く、弱くなります。" ) self.loop_cnt_ctrl.Bind(wx.EVT_SPINCTRL, self.on_change_file) self.setting_sizer.Add(self.loop_cnt_ctrl, 0, wx.ALL, 5) # 補間 self.interpolation_txt = wx.StaticText(self, wx.ID_ANY, u"補間方法", wx.DefaultPosition, wx.DefaultSize, 0) self.setting_sizer.Add(self.interpolation_txt, 0, wx.ALL, 5) self.interpolation_ctrl = wx.Choice( self, id=wx.ID_ANY, choices=["補間曲線に従う", "補間曲線無視(円形)", "補間曲線無視(曲線)"]) self.interpolation_ctrl.SetSelection(0) self.interpolation_ctrl.SetToolTip(u"キーとキーの補間方法を指定してください。\n「補間曲線に従う」は、補間曲線に従って繋ぎます。" \ + "\n「補間曲線無視(円形)」は、補間曲線を無視して、\n3つのキーを円周上に置いた円になるように補間します。" \ + "\n「補間曲線無視(曲線)」は、補間曲線を無視して、\nキーを滑らかな曲線(カトマル曲線)で繋いで補間します。") self.interpolation_ctrl.Bind(wx.EVT_CHOICE, self.on_change_file) self.setting_sizer.Add(self.interpolation_ctrl, 0, wx.ALL, 5) # 不要キー削除処理 self.remove_unnecessary_flg_ctrl = wx.CheckBox(self, wx.ID_ANY, u"不要キー削除処理を追加実行する", wx.DefaultPosition, wx.DefaultSize, 0) self.remove_unnecessary_flg_ctrl.SetToolTip( u"チェックを入れると、不要キー削除処理を追加で実行します。キーが減る分、キー間が少しズレる事があります。") self.remove_unnecessary_flg_ctrl.Bind(wx.EVT_CHECKBOX, self.on_change_file) self.setting_sizer.Add(self.remove_unnecessary_flg_ctrl, 0, wx.ALL, 5) self.sizer.Add(self.setting_sizer, 0, wx.EXPAND | wx.ALL, 5) btn_sizer = wx.BoxSizer(wx.HORIZONTAL) # スムージング変換実行ボタン self.smooth_btn_ctrl = wx.Button(self, wx.ID_ANY, u"スムージング実行", wx.DefaultPosition, wx.Size(200, 50), 0) self.smooth_btn_ctrl.SetToolTip(u"VMDを滑らかに繋いだモーションを再生成します。") self.smooth_btn_ctrl.Bind(wx.EVT_BUTTON, self.on_convert_smooth) self.smooth_btn_ctrl.Bind(wx.EVT_LEFT_DCLICK, self.on_doubleclick) btn_sizer.Add(self.smooth_btn_ctrl, 0, wx.ALL, 5) self.sizer.Add(btn_sizer, 0, wx.ALIGN_CENTER | wx.SHAPED, 5) # コンソール self.console_ctrl = ConsoleCtrl(self, self.frame.logging_level, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(-1, 420), \ wx.TE_MULTILINE | wx.TE_READONLY | wx.BORDER_NONE | wx.HSCROLL | wx.VSCROLL | wx.WANTS_CHARS) self.console_ctrl.SetBackgroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)) self.console_ctrl.Bind( wx.EVT_CHAR, lambda event: MFormUtils.on_select_all(event, self.console_ctrl)) self.sizer.Add(self.console_ctrl, 1, wx.ALL | wx.EXPAND, 5) # ゲージ self.gauge_ctrl = wx.Gauge(self, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.GA_HORIZONTAL) self.gauge_ctrl.SetValue(0) self.sizer.Add(self.gauge_ctrl, 0, wx.ALL | wx.EXPAND, 5) self.Layout() self.fit() # ボーン選択用ダイアログ self.bone_dialog = TargetBoneDialog(self.frame, self) self.bone_list = [] # フレームに変換完了処理バインド self.frame.Bind(EVT_SMOOTH_THREAD, self.on_convert_smooth_result) # ファイル変更時の処理 def on_change_file(self, event: wx.Event): self.set_output_vmd_path(event) 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 disable(self): self.smooth_vmd_file_ctrl.disable() self.smooth_model_file_ctrl.disable() self.output_smooth_vmd_file_ctrl.disable() self.loop_cnt_ctrl.Disable() self.interpolation_ctrl.Disable() self.smooth_btn_ctrl.Disable() self.remove_unnecessary_flg_ctrl.Disable() # フォーム無効化 def enable(self): self.smooth_vmd_file_ctrl.enable() self.smooth_model_file_ctrl.enable() self.output_smooth_vmd_file_ctrl.enable() self.loop_cnt_ctrl.Enable() self.interpolation_ctrl.Enable() self.smooth_btn_ctrl.Enable() self.remove_unnecessary_flg_ctrl.Enable() def on_doubleclick(self, event: wx.Event): self.timer.Stop() logger.warning("ダブルクリックされました。", decoration=MLogger.DECORATION_BOX) event.Skip(False) return False # 多段分割変換 def on_convert_smooth(self, event: wx.Event): self.timer = wx.Timer(self, TIMER_ID) self.timer.Start(200) self.Bind(wx.EVT_TIMER, self.on_convert, id=TIMER_ID) # スムージング変換 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 on_convert_smooth_result(self, event: wx.Event): self.elapsed_time = event.elapsed_time logger.info("\n処理時間: %s", self.show_worked_time()) self.smooth_btn_ctrl.SetLabel("スムージング実行") # 終了音 self.frame.sound_finish() # タブ移動可 self.release_tab() # フォーム有効化 self.enable() # ワーカー終了 self.convert_smooth_worker = None # プログレス非表示 self.gauge_ctrl.SetValue(0) def show_worked_time(self): # 経過秒数を時分秒に変換 td_m, td_s = divmod(self.elapsed_time, 60) if td_m == 0: worked_time = "{0:02d}秒".format(int(td_s)) else: worked_time = "{0:02d}分{1:02d}秒".format(int(td_m), int(td_s)) return worked_time def on_click_bone_target(self, event: wx.Event): self.disable() sys.stdout = self.console_ctrl # VMD読み込み self.smooth_vmd_file_ctrl.load() # PMX読み込み self.smooth_model_file_ctrl.load() if (self.smooth_vmd_file_ctrl.data and self.smooth_model_file_ctrl.data and \ (self.smooth_vmd_file_ctrl.data.digest != self.bone_dialog.vmd_digest or self.smooth_model_file_ctrl.data.digest != self.bone_dialog.pmx_digest)): # データが揃ってたら押下可能 self.bone_target_btn_ctrl.Enable() # リストクリア self.bone_target_txt_ctrl.SetValue("") # ボーン選択用ダイアログ self.bone_dialog.Destroy() self.bone_dialog = TargetBoneDialog(self.frame, self) else: if not self.smooth_vmd_file_ctrl.data or not self.smooth_model_file_ctrl.data: logger.error("対象モーションVMDもしくは適用モデルPMXが未指定です。", decoration=MLogger.DECORATION_BOX) self.enable() return self.enable() if self.bone_dialog.ShowModal() == wx.ID_CANCEL: return # the user changed their mind # 選択されたボーンリストを入力欄に設定 self.bone_list = self.bone_dialog.get_bone_list() self.bone_target_txt_ctrl.SetValue(', '.join(self.bone_list)) self.bone_dialog.Hide() def get_bone_list(self): return self.bone_list
class NoisePanel(BasePanel): def __init__(self, frame: wx.Frame, noise: wx.Notebook, tab_idx: int): super().__init__(frame, noise, tab_idx) self.timer = wx.Timer(self, TIMER_ID) self.convert_noise_worker = None self.header_sizer = wx.BoxSizer(wx.VERTICAL) self.description_txt = wx.StaticText(self, wx.ID_ANY, u"モーションをゆらぎ(ノイズ)を付与して複製します。\n" \ + "出力ファイル名のNxxは指定ゆらぎの大きさ、nxxxは複製連番、axxはやる気係数(身体の振りの大きさ)です。", wx.DefaultPosition, wx.DefaultSize, 0) self.header_sizer.Add(self.description_txt, 0, wx.ALL, 5) self.static_line01 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) self.header_sizer.Add(self.static_line01, 0, wx.EXPAND | wx.ALL, 5) # 対象VMDファイルコントロール self.noise_vmd_file_ctrl = HistoryFilePickerCtrl(self.frame, self, u"対象モーションVMD/VPD", u"対象モーションVMD/VPDファイルを開く", ("vmd", "vpd"), wx.FLP_DEFAULT_STYLE, \ u"調整したい対象モーションのVMDパスを指定してください。\nD&Dでの指定、開くボタンからの指定、履歴からの選択ができます。", \ file_model_spacer=46, title_parts_ctrl=None, title_parts2_ctrl=None, file_histories_key="noise_vmd", is_change_output=True, \ is_aster=False, is_save=False, set_no=1) self.header_sizer.Add(self.noise_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) # 出力先VMDファイルコントロール self.output_noise_vmd_file_ctrl = BaseFilePickerCtrl(frame, self, u"出力対象VMD", u"出力対象VMDファイルを開く", ("vmd"), wx.FLP_OVERWRITE_PROMPT | wx.FLP_SAVE | wx.FLP_USE_TEXTCTRL, \ u"調整結果の対象VMD出力パスを指定してください。\n対象VMDファイル名に基づいて自動生成されますが、任意のパスに変更することも可能です。", \ is_aster=False, is_save=True, set_no=1) self.header_sizer.Add(self.output_noise_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) self.sizer.Add(self.header_sizer, 0, wx.EXPAND | wx.ALL, 5) self.setting_sizer = wx.BoxSizer(wx.HORIZONTAL) # ゆらぎの大きさ self.noise_size_txt = wx.StaticText(self, wx.ID_ANY, u"ゆらぎの大きさ", wx.DefaultPosition, wx.DefaultSize, 0) self.setting_sizer.Add(self.noise_size_txt, 0, wx.ALL, 5) self.noise_size_ctrl = wx.SpinCtrl(self, id=wx.ID_ANY, size=wx.Size(60, -1), value="8", min=0, max=99999999, initial=8) self.noise_size_ctrl.SetToolTip( u"ゆらぎの大きさを指定して下さい。値が大きいほど、ゆらぎが大きくなります。") self.noise_size_ctrl.Bind(wx.EVT_SPINCTRL, self.on_change_file) self.setting_sizer.Add(self.noise_size_ctrl, 0, wx.ALL, 5) # 複製数 self.copy_cnt_txt = wx.StaticText(self, wx.ID_ANY, u"複製数", wx.DefaultPosition, wx.DefaultSize, 0) self.setting_sizer.Add(self.copy_cnt_txt, 0, wx.ALL, 5) self.copy_cnt_ctrl = wx.SpinCtrl(self, id=wx.ID_ANY, size=wx.Size(60, -1), value="2", min=1, max=99999999, initial=2) self.copy_cnt_ctrl.SetToolTip(u"複製する数を指定して下さい。") self.copy_cnt_ctrl.Bind(wx.EVT_SPINCTRL, self.on_change_file) self.setting_sizer.Add(self.copy_cnt_ctrl, 0, wx.ALL, 5) # やる気係数 self.motivation_flg_ctrl = wx.CheckBox(self, wx.ID_ANY, u"やる気係数を適用する", wx.DefaultPosition, wx.DefaultSize, 0) self.motivation_flg_ctrl.SetToolTip( u"チェックを入れると、やる気係数もランダムに発生します。\nやる気係数は値が大きいほどモーションの振り幅が大きくなります。\n値が小さいほどモーションの振り幅が小さくなります。" ) self.setting_sizer.Add(self.motivation_flg_ctrl, 0, wx.ALL, 5) # 指ゆらぎ self.finger_noise_flg_ctrl = wx.CheckBox(self, wx.ID_ANY, u"指にもゆらぎを適用する", wx.DefaultPosition, wx.DefaultSize, 0) self.finger_noise_flg_ctrl.SetToolTip( u"チェックを入れると、「指」をボーン名に含むボーンも揺らがせます。") self.setting_sizer.Add(self.finger_noise_flg_ctrl, 0, wx.ALL, 5) self.sizer.Add(self.setting_sizer, 0, wx.EXPAND | wx.ALL, 5) btn_sizer = wx.BoxSizer(wx.HORIZONTAL) # 実行ボタン self.noise_btn_ctrl = wx.Button(self, wx.ID_ANY, u"ゆらぎ複製", wx.DefaultPosition, wx.Size(200, 50), 0) self.noise_btn_ctrl.SetToolTip(u"ゆらぎを付与したモーションを複製します") self.noise_btn_ctrl.Bind(wx.EVT_LEFT_DOWN, self.on_convert_noise) self.noise_btn_ctrl.Bind(wx.EVT_LEFT_DCLICK, self.on_doubleclick) btn_sizer.Add(self.noise_btn_ctrl, 0, wx.ALL, 5) self.sizer.Add(btn_sizer, 0, wx.ALIGN_CENTER | wx.SHAPED, 5) # コンソール self.console_ctrl = ConsoleCtrl(self, self.frame.logging_level, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(-1, 420), \ wx.TE_MULTILINE | wx.TE_READONLY | wx.BORDER_NONE | wx.HSCROLL | wx.VSCROLL | wx.WANTS_CHARS) self.console_ctrl.SetBackgroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)) self.console_ctrl.Bind( wx.EVT_CHAR, lambda event: MFormUtils.on_select_all(event, self.console_ctrl)) self.sizer.Add(self.console_ctrl, 1, wx.ALL | wx.EXPAND, 5) # ゲージ self.gauge_ctrl = wx.Gauge(self, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.GA_HORIZONTAL) self.gauge_ctrl.SetValue(0) self.sizer.Add(self.gauge_ctrl, 0, wx.ALL | wx.EXPAND, 5) self.Layout() self.fit() # フレームに変換完了処理バインド self.frame.Bind(EVT_SMOOTH_THREAD, self.on_convert_noise_result) def on_wheel_spin_ctrl(self, event: wx.Event, inc=1): # self.frame.on_wheel_spin_ctrl(event, inc) self.set_output_vmd_path(event) # ファイル変更時の処理 def on_change_file(self, event: wx.Event): self.set_output_vmd_path(event, is_force=True) def set_output_vmd_path(self, event, is_force=False): output_noise_vmd_path = MFileUtils.get_output_noise_vmd_path( self.noise_vmd_file_ctrl.file_ctrl.GetPath(), self.output_noise_vmd_file_ctrl.file_ctrl.GetPath(), self.noise_size_ctrl.GetValue(), is_force) self.output_noise_vmd_file_ctrl.file_ctrl.SetPath( output_noise_vmd_path) if len(output_noise_vmd_path) >= 255 and os.name == "nt": logger.error("生成予定のファイルパスがWindowsの制限を超えています。\n生成予定パス: {0}".format( output_noise_vmd_path), decoration=MLogger.DECORATION_BOX) # フォーム無効化 def disable(self): self.noise_vmd_file_ctrl.disable() self.output_noise_vmd_file_ctrl.disable() self.noise_btn_ctrl.Disable() # フォーム無効化 def enable(self): self.noise_vmd_file_ctrl.enable() self.output_noise_vmd_file_ctrl.enable() self.noise_btn_ctrl.Enable() def on_doubleclick(self, event: wx.Event): self.timer.Stop() logger.warning("ダブルクリックされました。", decoration=MLogger.DECORATION_BOX) event.Skip(False) return False # 多段分割変換 def on_convert_noise(self, event: wx.Event): self.timer.Start(200) self.Bind(wx.EVT_TIMER, self.on_convert, id=TIMER_ID) # 多段分割変換 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.noise_vmd_file_ctrl.save() # JSON出力 MFileUtils.save_history(self.frame.mydir_path, self.frame.file_hitories) self.elapsed_time = 0 result = True result = self.noise_vmd_file_ctrl.is_valid() and result if not result: # 終了音 self.frame.sound_finish() # タブ移動可 self.release_tab() # フォーム有効化 self.enable() return result # ゆらぎ複製変換開始 if self.noise_btn_ctrl.GetLabel( ) == "ゆらぎ複製停止" and self.convert_noise_worker: # フォーム無効化 self.disable() # 停止状態でボタン押下時、停止 self.convert_noise_worker.stop() # タブ移動可 self.frame.release_tab() # フォーム有効化 self.frame.enable() # ワーカー終了 self.convert_noise_worker = None # プログレス非表示 self.gauge_ctrl.SetValue(0) logger.warning("ゆらぎ複製を中断します。", decoration=MLogger.DECORATION_BOX) self.noise_btn_ctrl.SetLabel("ゆらぎ複製") event.Skip(False) elif not self.convert_noise_worker: # フォーム無効化 self.disable() # タブ固定 self.fix_tab() # コンソールクリア self.console_ctrl.Clear() # ラベル変更 self.noise_btn_ctrl.SetLabel("ゆらぎ複製停止") self.noise_btn_ctrl.Enable() self.convert_noise_worker = NoiseWorkerThread( self.frame, NoiseThreadEvent, self.frame.is_saving, self.frame.is_out_log) self.convert_noise_worker.start() event.Skip() else: logger.error("まだ処理が実行中です。終了してから再度実行してください。", decoration=MLogger.DECORATION_BOX) event.Skip(False) return result # 多段分割変換完了処理 def on_convert_noise_result(self, event: wx.Event): self.elapsed_time = event.elapsed_time logger.info("\n処理時間: %s", self.show_worked_time()) self.noise_btn_ctrl.SetLabel("ゆらぎ複製") # 終了音 self.frame.sound_finish() # タブ移動可 self.release_tab() # フォーム有効化 self.enable() # ワーカー終了 self.convert_noise_worker = None # プログレス非表示 self.gauge_ctrl.SetValue(0) def show_worked_time(self): # 経過秒数を時分秒に変換 td_m, td_s = divmod(self.elapsed_time, 60) if td_m == 0: worked_time = "{0:02d}秒".format(int(td_s)) else: worked_time = "{0:02d}分{1:02d}秒".format(int(td_m), int(td_s)) return worked_time