From 47ee970b9bcb86f161fd1fca2f0bf31225edb2ad Mon Sep 17 00:00:00 2001 From: miu200521358 Date: Sat, 22 May 2021 13:33:19 +0900 Subject: [PATCH] =?UTF-8?q?1.03=5F=CE=B205?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MotionSupporter_1.03_β05 (2021/05/22) ・足FKtoIK  ・初期足首水平化チェック追加   ・チェックを入れると、初期値(X=0, Y=0, Z=0)が設定されている足首ボーンの角度を地面と水平の角度に調整する  ・かかと・つま先Y=0チェック追加   ・チェックを入れると、左右のかか・とつま先の4点のうちもっとも低いY値をY=0にセンターを調整する  ・接地固定指定追加   ・指定すると、指定されたキーフレ範囲の軸足を、Y=0になるようセンターの高さを調節し、かつセンターXZを固定します。  ※いずれも未指定の場合は調整はせずにそのまま足FKをIKに変換する --- motion_supporter64.spec | 2 +- src/executor.py | 2 +- src/form/panel/LegFKtoIKPanel.py | 287 +++++++++++++++++++++- src/form/worker/LegFKtoIKWorkerThread.py | 3 + src/mmd/PmxReader.py | 72 ++++-- src/module/MOptions.pxd | 3 + src/module/MOptions.pyx | 7 +- src/service/ConvertLegFKtoIKService.py | 258 ++++++++++++++++++- src/utils/MBezierUtils.cp38-win_amd64.pyd | Bin 214528 -> 214528 bytes src/utils/MFileUtils.py | 24 ++ 10 files changed, 628 insertions(+), 30 deletions(-) diff --git a/motion_supporter64.spec b/motion_supporter64.spec index e35b769..a4e3df9 100644 --- a/motion_supporter64.spec +++ b/motion_supporter64.spec @@ -26,7 +26,7 @@ exe = EXE(pyz, a.zipfiles, a.datas, [], - name='MotionSupporter_1.03_β04_64bit', + name='MotionSupporter_1.03_β05_64bit', debug=False, bootloader_ignore_signals=False, strip=False, diff --git a/src/executor.py b/src/executor.py index 2559d5b..ce98e3f 100644 --- a/src/executor.py +++ b/src/executor.py @@ -15,7 +15,7 @@ from utils import MFileUtils from utils.MException import SizingException -VERSION_NAME = "1.03_β04" +VERSION_NAME = "1.03_β05" # 指数表記なし、有効小数点桁数6、30を超えると省略あり、一行の文字数200 np.set_printoptions(suppress=True, precision=6, threshold=30, linewidth=200) diff --git a/src/form/panel/LegFKtoIKPanel.py b/src/form/panel/LegFKtoIKPanel.py index d758d85..0a52461 100644 --- a/src/form/panel/LegFKtoIKPanel.py +++ b/src/form/panel/LegFKtoIKPanel.py @@ -4,6 +4,9 @@ import wx import wx.lib.newevent import sys +import csv +import traceback +import operator from form.panel.BasePanel import BasePanel from form.parts.BaseFilePickerCtrl import BaseFilePickerCtrl @@ -19,6 +22,8 @@ # イベント定義 (LegFKtoIKThreadEvent, EVT_SMOOTH_THREAD) = wx.lib.newevent.NewEvent() +GROUND_LEGS = ["", "右かかと", "左かかと", "右つま先", "左つま先"] + class LegFKtoIKPanel(BasePanel): @@ -56,11 +61,37 @@ def __init__(self, frame: wx.Frame, leg_fk2ik: wx.Notebook, tab_idx: int): is_aster=False, is_save=True, set_no=1) self.header_sizer.Add(self.output_leg_fk2ik_vmd_file_ctrl.sizer, 1, wx.EXPAND, 0) + self.setting_sizer = wx.BoxSizer(wx.HORIZONTAL) + + # 接地指定 + self.ground_txt_ctrl = wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, (450, 50), wx.HSCROLL | wx.VSCROLL | wx.TE_MULTILINE | wx.TE_READONLY) + self.ground_txt_ctrl.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)) + self.setting_sizer.Add(self.ground_txt_ctrl, 1, wx.EXPAND | wx.ALL, 5) + + self.ground_btn_ctrl = wx.Button(self, wx.ID_ANY, u"接地固定指定", wx.DefaultPosition, wx.DefaultSize, 0) + self.ground_btn_ctrl.SetToolTip(u"地面に接地し、かつ足IKを動かしたくない範囲を指定できます。") + self.ground_btn_ctrl.Bind(wx.EVT_BUTTON, self.on_click_ground) + self.setting_sizer.Add(self.ground_btn_ctrl, 0, wx.ALIGN_BOTTOM | wx.ALL, 5) + + self.header_sizer.Add(self.setting_sizer, 0, wx.EXPAND | wx.ALL, 5) + + self.setting_flg_sizer = wx.BoxSizer(wx.HORIZONTAL) + + self.ankle_horizonal_flg_ctrl = wx.CheckBox(self, wx.ID_ANY, u"初期足首水平化", wx.DefaultPosition, wx.DefaultSize, 0) + self.ankle_horizonal_flg_ctrl.SetToolTip(u"チェックを入れると、初期値が指定されている足首キーフレを地面と水平になるように角度を調整します。") + self.setting_flg_sizer.Add(self.ankle_horizonal_flg_ctrl, 0, wx.ALL, 5) + + self.ground_leg_flg_ctrl = wx.CheckBox(self, wx.ID_ANY, u"かかと・つま先Y=0", wx.DefaultPosition, wx.DefaultSize, 0) + self.ground_leg_flg_ctrl.SetToolTip(u"チェックを入れると、「右かかと、左かかと、右つま先、左つま先」の最も低いY値がY=0になるように合わせます。") + self.setting_flg_sizer.Add(self.ground_leg_flg_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.header_sizer.Add(self.remove_unnecessary_flg_ctrl, 0, wx.ALL, 5) + self.setting_flg_sizer.Add(self.remove_unnecessary_flg_ctrl, 0, wx.ALL, 5) + + self.header_sizer.Add(self.setting_flg_sizer, 0, wx.EXPAND | wx.ALL, 5) self.sizer.Add(self.header_sizer, 0, wx.EXPAND | wx.ALL, 5) @@ -90,8 +121,50 @@ def __init__(self, frame: wx.Frame, leg_fk2ik: wx.Notebook, tab_idx: int): self.Layout() self.fit() + # ボーン選択用ダイアログ + self.leg_ground_dialog = LegGroundDialog(self.frame, self) + # フレームに変換完了処理バインド self.frame.Bind(EVT_SMOOTH_THREAD, self.on_convert_leg_fk2ik_result) + + def on_click_ground(self, event: wx.Event): + self.disable() + + # VMD読み込み + sys.stdout = self.console_ctrl + self.leg_fk2ik_vmd_file_ctrl.load() + # PMX読み込み + self.leg_fk2ik_model_file_ctrl.load() + + if (self.leg_fk2ik_vmd_file_ctrl.data and self.leg_fk2ik_model_file_ctrl.data and \ + (self.leg_fk2ik_vmd_file_ctrl.data.digest != self.leg_ground_dialog.vmd_digest or self.leg_fk2ik_model_file_ctrl.data.digest != self.leg_ground_dialog.pmx_digest)): + + # データが揃ってたら押下可能 + self.ground_btn_ctrl.Enable() + # リストクリア + self.ground_txt_ctrl.SetValue("") + # ボーン選択用ダイアログ + self.leg_ground_dialog.Destroy() + self.leg_ground_dialog = LegGroundDialog(self.frame, self) + self.leg_ground_dialog.initialize() + else: + if not self.leg_fk2ik_vmd_file_ctrl.data or not self.leg_fk2ik_model_file_ctrl.data: + logger.error("対象モーションVMD/VPDもしくはモデルPMXが未指定です。", decoration=MLogger.DECORATION_BOX) + self.enable() + return + + self.enable() + + if self.leg_ground_dialog.ShowModal() == wx.ID_CANCEL: + return # the user changed their mind + + # 選択されたボーンリストを入力欄に設定 + leg_list = self.leg_ground_dialog.get_leg_list() + + selections = [f"{bset[0]} ~ {bset[1]} F 【{bset[2]}】" for bset in leg_list] + self.ground_txt_ctrl.SetValue('\n'.join(selections)) + + self.leg_ground_dialog.Hide() def on_wheel_spin_ctrl(self, event: wx.Event, inc=1): self.frame.on_wheel_spin_ctrl(event, inc) @@ -118,6 +191,9 @@ def disable(self): self.leg_fk2ik_model_file_ctrl.disable() self.output_leg_fk2ik_vmd_file_ctrl.disable() self.leg_fk2ik_btn_ctrl.Disable() + self.ground_btn_ctrl.Disable() + self.ground_leg_flg_ctrl.Disable() + self.ankle_horizonal_flg_ctrl.Disable() self.remove_unnecessary_flg_ctrl.Disable() # フォーム無効化 @@ -126,6 +202,9 @@ def enable(self): self.leg_fk2ik_model_file_ctrl.enable() self.output_leg_fk2ik_vmd_file_ctrl.enable() self.leg_fk2ik_btn_ctrl.Enable() + self.ground_btn_ctrl.Enable() + self.ground_leg_flg_ctrl.Enable() + self.ankle_horizonal_flg_ctrl.Enable() self.remove_unnecessary_flg_ctrl.Enable() def on_doubleclick(self, event: wx.Event): @@ -243,3 +322,209 @@ def show_worked_time(self): worked_time = "{0:02d}分{1:02d}秒".format(int(td_m), int(td_s)) return worked_time + + +class LegGroundDialog(wx.Dialog): + def __init__(self, frame: wx.Frame, panel: wx.Panel): + super().__init__(frame, id=wx.ID_ANY, title="接地固定指定", pos=(-1, -1), size=(700, 450), style=wx.DEFAULT_DIALOG_STYLE, name="LegGroundDialog") + + self.frame = frame + self.panel = panel + self.vmd_digest = 0 if not self.panel.leg_fk2ik_vmd_file_ctrl.data else self.panel.leg_fk2ik_vmd_file_ctrl.data.digest + self.pmx_digest = 0 if not self.panel.leg_fk2ik_model_file_ctrl.data else self.panel.leg_fk2ik_model_file_ctrl.data.digest + self.frame_from_ctrls = [] # 選択コントロール + self.frame_to_ctrls = [] + self.leg_ground_ctrls = [] + + self.sizer = wx.BoxSizer(wx.VERTICAL) + + # 説明文 + self.description_txt = wx.StaticText(self, wx.ID_ANY, "接地固定させたいキーフレと軸足を指定して下さい。\n" \ + + "指定されたキーフレ範囲の軸足を、Y=0になるようセンターの高さを調節し、かつセンターXZを固定します。", wx.DefaultPosition, wx.DefaultSize, 0) + self.sizer.Add(self.description_txt, 0, wx.ALL, 5) + + # ボタン + self.btn_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.ok_btn = wx.Button(self, wx.ID_OK, "OK") + self.btn_sizer.Add(self.ok_btn, 0, wx.ALL, 5) + + self.calcel_btn = wx.Button(self, wx.ID_CANCEL, "キャンセル") + self.btn_sizer.Add(self.calcel_btn, 0, wx.ALL, 5) + + # インポートボタン + self.import_btn_ctrl = wx.Button(self, wx.ID_ANY, u"インポート ...", wx.DefaultPosition, wx.DefaultSize, 0) + self.import_btn_ctrl.SetToolTip(u"接地データをCSVファイルから読み込みます。\nファイル選択ダイアログが開きます。") + self.import_btn_ctrl.Bind(wx.EVT_BUTTON, self.on_import) + self.btn_sizer.Add(self.import_btn_ctrl, 0, wx.ALL, 5) + + # エクスポートボタン + self.export_btn_ctrl = wx.Button(self, wx.ID_ANY, u"エクスポート ...", wx.DefaultPosition, wx.DefaultSize, 0) + self.export_btn_ctrl.SetToolTip(u"接地データをCSVファイルに出力します。\n調整対象VMDと同じフォルダに出力します。") + self.export_btn_ctrl.Bind(wx.EVT_BUTTON, self.on_export) + self.btn_sizer.Add(self.export_btn_ctrl, 0, wx.ALL, 5) + + # 行追加ボタン + self.add_line_btn_ctrl = wx.Button(self, wx.ID_ANY, u"行追加", wx.DefaultPosition, wx.DefaultSize, 0) + self.add_line_btn_ctrl.SetToolTip(u"接地キーフレの設定行を追加します。\n上限はありません。") + self.add_line_btn_ctrl.Bind(wx.EVT_BUTTON, self.on_add_line) + self.btn_sizer.Add(self.add_line_btn_ctrl, 0, wx.ALL, 5) + + self.sizer.Add(self.btn_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 5) + + self.static_line01 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) + self.sizer.Add(self.static_line01, 0, wx.EXPAND | wx.ALL, 5) + + self.window = wx.ScrolledWindow(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.FULL_REPAINT_ON_RESIZE | wx.HSCROLL | wx.ALWAYS_SHOW_SB) + self.window.SetScrollRate(5, 5) + + # セット用基本Sizer + self.set_list_sizer = wx.BoxSizer(wx.HORIZONTAL) + + # タイトル部分 + self.grid_sizer = wx.FlexGridSizer(0, 3, 0, 0) + self.grid_sizer.SetFlexibleDirection(wx.BOTH) + self.grid_sizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED) + + self.frame_from_txt = wx.StaticText(self.window, wx.ID_ANY, "開始キーフレ", wx.DefaultPosition, wx.DefaultSize, 0) + self.frame_from_txt.SetToolTip("接地させるキーフレの開始") + self.frame_from_txt.Wrap(-1) + self.grid_sizer.Add(self.frame_from_txt, 0, wx.ALL, 5) + + self.frame_to_txt = wx.StaticText(self.window, wx.ID_ANY, "終了キーフレ", wx.DefaultPosition, wx.DefaultSize, 0) + self.frame_to_txt.SetToolTip("接地させるキーフレの終端") + self.frame_to_txt.Wrap(-1) + self.grid_sizer.Add(self.frame_to_txt, 0, wx.ALL, 5) + + self.leg_ground_txt = wx.StaticText(self.window, wx.ID_ANY, "接地軸足", wx.DefaultPosition, wx.DefaultSize, 0) + self.leg_ground_txt.SetToolTip("接地させる軸足") + self.leg_ground_txt.Wrap(-1) + self.grid_sizer.Add(self.leg_ground_txt, 0, wx.ALL, 5) + + self.set_list_sizer.Add(self.grid_sizer, 0, wx.ALL, 5) + + # スクロールバーの表示のためにサイズ調整 + self.window.SetSizer(self.set_list_sizer) + self.window.Layout() + self.sizer.Add(self.window, 1, wx.ALL | wx.EXPAND, 5) + self.SetSizer(self.sizer) + self.sizer.Layout() + + # 画面中央に表示 + self.CentreOnScreen() + + # 最初は隠しておく + self.Hide() + + def initialize(self): + # 一行追加 + self.add_line() + + def add_line(self, leg_ground_idx=0): + self.frame_from_ctrls.append(wx.SpinCtrl(self.window, id=wx.ID_ANY, size=wx.Size(100, -1), value="0", min=0, max=99999999, initial=0)) + self.grid_sizer.Add(self.frame_from_ctrls[-1], 0, wx.ALL, 5) + + self.frame_to_ctrls.append(wx.SpinCtrl(self.window, id=wx.ID_ANY, size=wx.Size(100, -1), value="0", min=0, max=99999999, initial=0)) + self.grid_sizer.Add(self.frame_to_ctrls[-1], 0, wx.ALL, 5) + + self.leg_ground_ctrls.append(wx.Choice(self.window, id=wx.ID_ANY, choices=GROUND_LEGS)) + self.leg_ground_ctrls[-1].SetSelection(leg_ground_idx) + self.leg_ground_ctrls[-1].Bind(wx.EVT_CHOICE, self.on_change_leg) + self.grid_sizer.Add(self.leg_ground_ctrls[-1], 0, wx.ALL, 5) + + # スクロールバーの表示のためにサイズ調整 + self.set_list_sizer.Layout() + self.set_list_sizer.FitInside(self.window) + + def on_import(self, event: wx.Event): + input_bone_path = MFileUtils.get_output_leg_ground_path( + self.panel.leg_fk2ik_vmd_file_ctrl.file_ctrl.GetPath(), + self.panel.leg_fk2ik_model_file_ctrl.file_ctrl.GetPath() + ) + + with wx.FileDialog(self.frame, "接地指定CSVを読み込む", wildcard=u"CSVファイル (*.csv)|*.csv|すべてのファイル (*.*)|*.*", + defaultDir=os.path.dirname(input_bone_path), + style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog: + + if fileDialog.ShowModal() == wx.ID_CANCEL: + return # the user changed their mind + + # Proceed loading the file chosen by the user + target_bone_path = fileDialog.GetPath() + try: + with open(target_bone_path, 'r') as f: + cr = csv.reader(f, delimiter=",", quotechar='"') + ground_lines = [row for row in cr] + + if len(ground_lines) == 0: + return + + for (fromv, tov, legv) in ground_lines: + self.frame_from_ctrls[-1].SetValue(int(fromv)) + self.frame_to_ctrls[-1].SetValue(int(tov)) + for gidx, leg_name in enumerate(GROUND_LEGS): + if legv == leg_name: + self.leg_ground_ctrls[-1].SetSelection(gidx) + + # 行追加 + self.add_line() + + # パス変更 + self.panel.set_output_vmd_path(event) + + except Exception: + dialog = wx.MessageDialog(self.frame, "CSVファイルが読み込めませんでした '%s'\n\n%s." % (target_bone_path, traceback.format_exc()), style=wx.OK) + dialog.ShowModal() + dialog.Destroy() + + def on_export(self, event: wx.Event): + output_ground_path = MFileUtils.get_output_leg_ground_path( + self.panel.leg_fk2ik_vmd_file_ctrl.file_ctrl.GetPath(), + self.panel.leg_fk2ik_model_file_ctrl.file_ctrl.GetPath() + ) + + try: + with open(output_ground_path, encoding='cp932', mode='w', newline='') as f: + cw = csv.writer(f, delimiter=",", quotechar='"', quoting=csv.QUOTE_ALL) + + for m in self.get_leg_list(): + cw.writerow(m) + + logger.info("出力成功: %s" % output_ground_path) + + dialog = wx.MessageDialog(self.frame, "接地設定のエクスポートに成功しました \n'%s'" % (output_ground_path), style=wx.OK) + dialog.ShowModal() + dialog.Destroy() + + except Exception: + dialog = wx.MessageDialog(self.frame, "接地設定のエクスポートに失敗しました \n'%s'\n\n%s." % (output_ground_path, traceback.format_exc()), style=wx.OK) + dialog.ShowModal() + dialog.Destroy() + + def on_change_leg(self, event: wx.Event): + leg_ground_idx = event.GetEventObject().GetSelection() + if leg_ground_idx > 0: + self.add_line() + + def on_add_line(self, event: wx.Event): + # 行追加 + self.add_line() + + def get_leg_list(self): + leg_list = [] + + for midx, (fromc, toc, legc) in enumerate(zip(self.frame_from_ctrls, self.frame_to_ctrls, self.leg_ground_ctrls)): + if fromc.GetValue() >= 0 and toc.GetValue() > 0 and legc.GetSelection() > 0: + + fromv = fromc.GetValue() + tov = toc.GetValue() + legv = legc.GetString(legc.GetSelection()) + + if (fromv, tov, legv) not in leg_list: + # ボーンペアがまだ登録されてないければ登録 + leg_list.append((fromv, tov, legv)) + + leg_list.sort(key=operator.itemgetter(0)) + + # どれも設定されていなければFalse + return leg_list + diff --git a/src/form/worker/LegFKtoIKWorkerThread.py b/src/form/worker/LegFKtoIKWorkerThread.py index 54f3149..3224a65 100644 --- a/src/form/worker/LegFKtoIKWorkerThread.py +++ b/src/form/worker/LegFKtoIKWorkerThread.py @@ -43,6 +43,9 @@ def thread_event(self): logging_level=self.frame.logging_level, \ motion=self.frame.leg_fk2ik_panel_ctrl.leg_fk2ik_vmd_file_ctrl.data.copy(), \ model=self.frame.leg_fk2ik_panel_ctrl.leg_fk2ik_model_file_ctrl.data, \ + target_legs=self.frame.leg_fk2ik_panel_ctrl.leg_ground_dialog.get_leg_list(), \ + ground_leg_flg=self.frame.leg_fk2ik_panel_ctrl.ground_leg_flg_ctrl.GetValue(), \ + ankle_horizonal_flg=self.frame.leg_fk2ik_panel_ctrl.ankle_horizonal_flg_ctrl.GetValue(), \ output_path=self.frame.leg_fk2ik_panel_ctrl.output_leg_fk2ik_vmd_file_ctrl.file_ctrl.GetPath(), \ remove_unnecessary_flg=self.frame.leg_fk2ik_panel_ctrl.remove_unnecessary_flg_ctrl.GetValue(), \ monitor=self.frame.leg_fk2ik_panel_ctrl.console_ctrl, \ diff --git a/src/mmd/PmxReader.py b/src/mmd/PmxReader.py index b59098f..e477484 100644 --- a/src/mmd/PmxReader.py +++ b/src/mmd/PmxReader.py @@ -338,30 +338,6 @@ def read_data(self): pmx.bones[head_top_bone.name] = head_top_bone pmx.bone_indexes[head_top_bone.index] = head_top_bone.name - if "右足IK" in pmx.bones or "右つま先IK" in pmx.bones: - # 右つま先ボーン - right_toe_vertex = pmx.get_toe_vertex("右") - if right_toe_vertex: - pmx.right_toe_vertex = right_toe_vertex - right_toe_pos = right_toe_vertex.position.copy() - right_toe_pos.setY(0) - right_toe_bone = Bone("右つま先実体", "right toe entity", right_toe_pos, -1, 0, 0) - right_toe_bone.index = len(pmx.bones.keys()) - pmx.bones[right_toe_bone.name] = right_toe_bone - pmx.bone_indexes[right_toe_bone.index] = right_toe_bone.name - - if "左足IK" in pmx.bones or "左つま先IK" in pmx.bones: - # 左つま先ボーン - left_toe_vertex = pmx.get_toe_vertex("左") - if left_toe_vertex: - pmx.left_toe_vertex = left_toe_vertex - left_toe_pos = left_toe_vertex.position.copy() - left_toe_pos.setY(0) - left_toe_bone = Bone("左つま先実体", "left toe entity", left_toe_pos, -1, 0, 0) - left_toe_bone.index = len(pmx.bones.keys()) - pmx.bones[left_toe_bone.name] = left_toe_bone - pmx.bone_indexes[left_toe_bone.index] = left_toe_bone.name - if "右足先EX" in pmx.bones or "右足IK" in pmx.bones: # 右足底実体ボーン right_sole_vertex = None @@ -374,6 +350,12 @@ def read_data(self): pmx.right_sole_vertex = right_sole_vertex right_sole_bone = Bone("右足底実体", "right sole entity", right_sole_vertex.position.copy(), -1, 0, 0) right_sole_bone.index = len(pmx.bones.keys()) + + if "右足先EX" in pmx.bones: + right_sole_bone.parent_index = pmx.bones["右足先EX"].index + else: + right_sole_bone.parent_index = pmx.bones["右足首"].index + pmx.bones[right_sole_bone.name] = right_sole_bone pmx.bone_indexes[right_sole_bone.index] = right_sole_bone.name @@ -389,9 +371,51 @@ def read_data(self): pmx.left_sole_vertex = left_sole_vertex left_sole_bone = Bone("左足底実体", "left sole entity", left_sole_vertex.position.copy(), -1, 0, 0) left_sole_bone.index = len(pmx.bones.keys()) + + if "左足先EX" in pmx.bones: + left_sole_bone.parent_index = pmx.bones["左足先EX"].index + else: + left_sole_bone.parent_index = pmx.bones["左足首"].index + pmx.bones[left_sole_bone.name] = left_sole_bone pmx.bone_indexes[left_sole_bone.index] = left_sole_bone.name + if "右足IK" in pmx.bones or "右つま先IK" in pmx.bones: + # 右つま先ボーン + right_toe_vertex = pmx.get_toe_vertex("右") + if right_toe_vertex: + pmx.right_toe_vertex = right_toe_vertex + right_toe_pos = right_toe_vertex.position.copy() + right_toe_pos.setY(0) + right_toe_bone = Bone("右つま先実体", "right toe entity", right_toe_pos, -1, 0, 0) + right_toe_bone.index = len(pmx.bones.keys()) + + if "右足底実体" in pmx.bones: + right_toe_bone.parent_index = pmx.bones["右足底実体"].index + else: + right_toe_bone.parent_index = pmx.bones["右足首"].index + + pmx.bones[right_toe_bone.name] = right_toe_bone + pmx.bone_indexes[right_toe_bone.index] = right_toe_bone.name + + if "左足IK" in pmx.bones or "左つま先IK" in pmx.bones: + # 左つま先ボーン + left_toe_vertex = pmx.get_toe_vertex("左") + if left_toe_vertex: + pmx.left_toe_vertex = left_toe_vertex + left_toe_pos = left_toe_vertex.position.copy() + left_toe_pos.setY(0) + left_toe_bone = Bone("左つま先実体", "left toe entity", left_toe_pos, -1, 0, 0) + left_toe_bone.index = len(pmx.bones.keys()) + + if "左足底実体" in pmx.bones: + left_toe_bone.parent_index = pmx.bones["左足底実体"].index + else: + left_toe_bone.parent_index = pmx.bones["左足首"].index + + pmx.bones[left_toe_bone.name] = left_toe_bone + pmx.bone_indexes[left_toe_bone.index] = left_toe_bone.name + if "右足IK" in pmx.bones: # 右足IK底実体ボーン right_ik_sole_vertex = Vertex(-1, MVector3D(pmx.bones["右足IK"].position.x(), 0, pmx.bones["右足IK"].position.z()), MVector3D(), [], [], Bdef1(-1), -1) diff --git a/src/module/MOptions.pxd b/src/module/MOptions.pxd index 90b3b07..09735ac 100644 --- a/src/module/MOptions.pxd +++ b/src/module/MOptions.pxd @@ -139,6 +139,9 @@ cdef class MLegFKtoIKOptions: cdef public int logging_level cdef public VmdMotion motion cdef public PmxModel model + cdef public list target_legs + cdef public bint ground_leg_flg + cdef public bint ankle_horizonal_flg cdef public str output_path cdef public bint remove_unnecessary_flg cdef public object monitor diff --git a/src/module/MOptions.pyx b/src/module/MOptions.pyx index 4822055..92b1a41 100644 --- a/src/module/MOptions.pyx +++ b/src/module/MOptions.pyx @@ -156,12 +156,15 @@ cdef class MMultiJoinOptions: cdef class MLegFKtoIKOptions: - def __init__(self, str version_name, int logging_level, int max_workers, VmdMotion motion, PmxModel model, str output_path, \ - bint remove_unnecessary_flg, object monitor, bint is_file, str outout_datetime): + def __init__(self, str version_name, int logging_level, int max_workers, VmdMotion motion, PmxModel model, list target_legs, bint ground_leg_flg, \ + bint ankle_horizonal_flg, str output_path, bint remove_unnecessary_flg, object monitor, bint is_file, str outout_datetime): self.version_name = version_name self.logging_level = logging_level self.motion = motion self.model = model + self.target_legs = target_legs + self.ground_leg_flg = ground_leg_flg + self.ankle_horizonal_flg = ankle_horizonal_flg self.output_path = output_path self.remove_unnecessary_flg = remove_unnecessary_flg self.monitor = monitor diff --git a/src/service/ConvertLegFKtoIKService.py b/src/service/ConvertLegFKtoIKService.py index 5d0f190..e329880 100644 --- a/src/service/ConvertLegFKtoIKService.py +++ b/src/service/ConvertLegFKtoIKService.py @@ -15,7 +15,7 @@ from utils.MLogger import MLogger # noqa from utils.MException import SizingException, MKilledException -logger = MLogger(__name__, level=1) +logger = MLogger(__name__, level=MLogger.INFO) class ConvertLegFKtoIKService(): @@ -32,11 +32,28 @@ def execute(self): vmd=os.path.basename(self.options.motion.path)) # noqa service_data_txt = "{service_data_txt} モデル: {model}({model_name})\n".format(service_data_txt=service_data_txt, model=os.path.basename(self.options.motion.path), model_name=self.options.model.name) # noqa + service_data_txt = "{service_data_txt} 足首水平化: {target_legs}\n".format(service_data_txt=service_data_txt, + target_legs=self.options.ankle_horizonal_flg) # noqa + service_data_txt = "{service_data_txt} かかと・つま先Y=0: {target_legs}\n".format(service_data_txt=service_data_txt, + target_legs=self.options.ground_leg_flg) # noqa + if len(self.options.target_legs) > 0: + service_data_txt = "{service_data_txt} 接地固定設定: {target_legs}\n".format(service_data_txt=service_data_txt, + target_legs=(len(self.options.target_legs) > 0)) # noqa service_data_txt = "{service_data_txt} 不要キー削除: {center_rotation}\n".format(service_data_txt=service_data_txt, center_rotation=self.options.remove_unnecessary_flg) # noqa logger.info(service_data_txt, decoration=MLogger.DECORATION_BOX) + # 足首水平設定がある場合、足首水平化 + if self.options.ankle_horizonal_flg: + self.prepare_ankle_horizonal() + + # 接地設定がある場合、接地設定 + if self.options.ground_leg_flg: + self.prepare_ground() + elif len(self.options.target_legs) > 0: + self.prepare_ground2() + futures = [] with ThreadPoolExecutor(thread_name_prefix="leffk", max_workers=self.options.max_workers) as executor: @@ -63,6 +80,245 @@ def execute(self): logger.critical("足IK変換処理が意図せぬエラーで終了しました。\n\n%s", traceback.format_exc(), decoration=MLogger.DECORATION_BOX) finally: logging.shutdown() + + # 足首の水平化 + def prepare_ankle_horizonal(self): + logger.info("初期足首水平化", decoration=MLogger.DECORATION_LINE) + + motion = self.options.motion + model = self.options.model + + # グルーブに値が入ってる場合、Yはグルーブに入れる + center_x_bone_name = "センター" + if not motion.is_active_bones("センター") and motion.is_active_bones("センターMX"): + center_x_bone_name = "センターMX" + + center_y_bone_name = "センター" + if motion.is_active_bones("グルーブ"): + center_y_bone_name = "グルーブ" + elif not motion.is_active_bones("センター") and motion.is_active_bones("センターMX"): + center_y_bone_name = "グルーブMY" + + center_z_bone_name = "センター" + if not motion.is_active_bones("センター") and motion.is_active_bones("センターMZ"): + center_z_bone_name = "センターMZ" + + # 足首角度 + for direction in ["右", "左"]: + prev_sep_fno = 0 + + # 足FK末端までのリンク + # toe_fk_links = model.create_link_2_top_one(f"{direction}つま先実体", is_defined=False) + ankle_fk_links = model.create_link_2_top_one(f"{direction}足首", is_defined=False) + + # 足首から先を固定で付与する + if f"{direction}足底実体" in model.bones: + ankle_fk_links.append(model.bones[f"{direction}足底実体"]) + if f"{direction}足先EX" in model.bones: + ankle_fk_links.append(model.bones[f"{direction}足先EX"]) + if f"{direction}つま先実体" in model.bones: + ankle_fk_links.append(model.bones[f"{direction}つま先実体"]) + + # 指定範囲内の足首キーフレを取得 + fnos = motion.get_bone_fnos(f"{direction}足首") + + for fidx, fno in enumerate(fnos): + toe_bf = motion.calc_bf(f"{direction}足首", fno) + + if toe_bf.rotation == MQuaternion(): + toe_fk_3ds, toe_fk_matrixs = MServiceUtils.calc_global_pos(model, ankle_fk_links, motion, fno, return_matrix=True) + toe_pos = toe_fk_3ds[f"{direction}つま先実体"] + sole_pos = toe_fk_3ds[f"{direction}足底実体"] + + toe_slope_from_pos = toe_pos + toe_slope_to_pos = MVector3D(toe_pos.x(), sole_pos.y(), toe_pos.z()) + + toe_slope_from_local_pos = toe_fk_matrixs[f"{direction}足底実体"].inverted() * toe_slope_from_pos + toe_slope_to_local_pos = toe_fk_matrixs[f"{direction}足底実体"].inverted() * toe_slope_to_pos + + # 足首角度を調整する + toe_bf.rotation = MQuaternion.rotationTo(toe_slope_from_local_pos, toe_slope_to_local_pos) + motion.regist_bf(toe_bf, toe_bf.name, fno) + + if fno // 500 > prev_sep_fno: + logger.count(f"【初期足首水平化({direction})】", fno, fnos) + prev_sep_fno = fno // 500 + + # 足IKの接地準備 + def prepare_ground(self): + logger.info("足IK接地", decoration=MLogger.DECORATION_LINE) + + motion = self.options.motion + model = self.options.model + + # 足FK末端までのリンク + right_fk_links = model.create_link_2_top_one("右つま先実体", is_defined=False) + left_fk_links = model.create_link_2_top_one("左つま先実体", is_defined=False) + + # グルーブに値が入ってる場合、Yはグルーブに入れる + center_x_bone_name = "センター" + if not motion.is_active_bones("センター") and motion.is_active_bones("センターMX"): + center_x_bone_name = "センターMX" + + center_y_bone_name = "センター" + if motion.is_active_bones("グルーブ"): + center_y_bone_name = "グルーブ" + elif not motion.is_active_bones("センター") and motion.is_active_bones("センターMX"): + center_y_bone_name = "グルーブMY" + + center_z_bone_name = "センター" + if not motion.is_active_bones("センター") and motion.is_active_bones("センターMZ"): + center_z_bone_name = "センターMZ" + + # 指定範囲内の足FKキーフレを取得 + fnos = motion.get_bone_fnos("左足", "左ひざ", "左足首", "右足", "右ひざ", "右足首", "下半身", center_x_bone_name, center_y_bone_name, center_z_bone_name) + + # センター調整 + prev_sep_fno = 0 + for fidx, fno in enumerate(fnos): + right_fk_3ds = MServiceUtils.calc_global_pos(model, right_fk_links, motion, fno) + right_toe_pos = right_fk_3ds["右つま先実体"] + right_sole_pos = right_fk_3ds["右足底実体"] + + left_fk_3ds = MServiceUtils.calc_global_pos(model, left_fk_links, motion, fno) + left_toe_pos = left_fk_3ds["左つま先実体"] + left_sole_pos = left_fk_3ds["左足底実体"] + + min_y = min(right_sole_pos.y(), left_sole_pos.y(), right_toe_pos.y(), left_toe_pos.y()) + + # Y位置を調整する + center_y_bf = motion.calc_bf(center_y_bone_name, fno) + center_y_bf.position.setY(center_y_bf.position.y() - min_y) + motion.regist_bf(center_y_bf, center_y_bone_name, fno) + + if fno // 500 > prev_sep_fno: + logger.count("【足IK接地】", fno, fnos) + prev_sep_fno = fno // 500 + + # 足IKの接地準備 + def prepare_ground2(self): + logger.info("足IK接地固定", decoration=MLogger.DECORATION_LINE) + + motion = self.options.motion + model = self.options.model + + # 足FK末端までのリンク + right_fk_links = model.create_link_2_top_one("右つま先実体", is_defined=False) + left_fk_links = model.create_link_2_top_one("左つま先実体", is_defined=False) + + # グルーブに値が入ってる場合、Yはグルーブに入れる + center_x_bone_name = "センター" + if not motion.is_active_bones("センター") and motion.is_active_bones("センターMX"): + center_x_bone_name = "センターMX" + + center_y_bone_name = "センター" + if motion.is_active_bones("グルーブ"): + center_y_bone_name = "グルーブ" + elif not motion.is_active_bones("センター") and motion.is_active_bones("センターMX"): + center_y_bone_name = "グルーブMY" + + center_z_bone_name = "センター" + if not motion.is_active_bones("センター") and motion.is_active_bones("センターMZ"): + center_z_bone_name = "センターMZ" + + # 指定範囲内の足FKキーフレを取得 + fnos = motion.get_bone_fnos("左足", "左ひざ", "左足首", "右足", "右ひざ", "右足首", "下半身", center_x_bone_name, center_y_bone_name, center_z_bone_name) + + target_legs = {} + for lidx, (fromv, tov, ground_leg) in enumerate(self.options.target_legs): + for fno in fnos: + if fromv <= fno <= tov: + target_legs[fno] = ground_leg + + # # まずキー登録 + # prev_sep_fno = 0 + # for fidx, fno in enumerate(fnos): + # center_x_bf = motion.calc_bf(center_x_bone_name, fno) + # motion.regist_bf(center_x_bf, center_x_bone_name, fno) + + # if center_x_bone_name != center_y_bone_name: + # center_y_bf = motion.calc_bf(center_y_bone_name, fno) + # motion.regist_bf(center_y_bf, center_y_bone_name, fno) + + # if center_x_bone_name != center_z_bone_name: + # center_z_bf = motion.calc_bf(center_z_bone_name, fno) + # motion.regist_bf(center_z_bf, center_z_bone_name, fno) + + # if fno // 1000 > prev_sep_fno: + # logger.count("【足IK接地準備①】", fno, fnos) + # prev_sep_fno = fno // 1000 + + # センター調整 + for lidx, (fromv, tov, ground_leg) in enumerate(self.options.target_legs): + fix_x_pos = MVector3D() + fix_z_pos = MVector3D() + for fidx, fno in enumerate(fnos): + if fromv <= fno <= tov: + right_fk_3ds = MServiceUtils.calc_global_pos(model, right_fk_links, motion, fno) + right_toe_pos = right_fk_3ds["右つま先実体"] + right_sole_pos = right_fk_3ds["右足底実体"] + + left_fk_3ds = MServiceUtils.calc_global_pos(model, left_fk_links, motion, fno) + left_toe_pos = left_fk_3ds["左つま先実体"] + left_sole_pos = left_fk_3ds["左足底実体"] + + target_leg_x = None + target_leg_z = None + target_leg_ys = [] + if ground_leg == "右かかと": + target_leg_x = right_sole_pos.x() + target_leg_ys.append(right_sole_pos.y()) + target_leg_z = right_sole_pos.z() + elif ground_leg == "左かかと": + target_leg_x = left_sole_pos.x() + target_leg_ys.append(left_sole_pos.y()) + target_leg_z = left_sole_pos.z() + elif ground_leg == "右つま先": + target_leg_x = right_toe_pos.x() + target_leg_ys.append(right_toe_pos.y()) + target_leg_z = right_toe_pos.z() + elif ground_leg == "左つま先": + target_leg_x = left_toe_pos.x() + target_leg_ys.append(left_toe_pos.y()) + target_leg_z = left_toe_pos.z() + + min_y = min(target_leg_ys) + + # Y位置を調整する + center_y_bf = motion.calc_bf(center_y_bone_name, fno) + center_y_bf.position.setY(center_y_bf.position.y() - min_y) + motion.regist_bf(center_y_bf, center_y_bone_name, fno) + + # XZを固定する + if fix_x_pos == MVector3D() and fix_z_pos == MVector3D(): + # 最初のキーフレで固定する + fix_x_pos = MVector3D(target_leg_x, 0, 0) + fix_z_pos = MVector3D(0, 0, target_leg_z) + + if center_x_bone_name == center_z_bone_name: + # 固定位置からの差分 + diff_pos = (fix_x_pos + fix_z_pos) - MVector3D(target_leg_x, 0, target_leg_z) + + # 差分を加算 + center_bf = motion.calc_bf(center_x_bone_name, fno) + center_bf.position += diff_pos + motion.regist_bf(center_bf, center_x_bone_name, fno) + else: + # 固定位置からの差分 + diff_x_pos = fix_x_pos - MVector3D(target_leg_x, 0, 0) + diff_z_pos = fix_z_pos - MVector3D(0, 0, target_leg_z) + + # X差分を加算 + center_x_bf = motion.calc_bf(center_x_bone_name, fno) + center_x_bf.position += diff_x_pos + motion.regist_bf(center_x_bf, center_x_bone_name, fno) + + # Z差分を加算 + center_z_bf = motion.calc_bf(center_z_bone_name, fno) + center_z_bf.position += diff_z_pos + motion.regist_bf(center_z_bf, center_z_bone_name, fno) + + logger.info(f"-- 【足IK接地固定】{fromv} ~ {tov} F:{ground_leg}") # 足IK変換処理実行 def convert_leg_fk2ik(self, direction: str): diff --git a/src/utils/MBezierUtils.cp38-win_amd64.pyd b/src/utils/MBezierUtils.cp38-win_amd64.pyd index e88a97e93e5ae6b6869e87d645316ba8996d56a1..df3b579ff938573c56daa3acee785a6833c9ed4a 100644 GIT binary patch delta 36 qcmZqZ;ce*Q-5|io%v`^sS(ve1n2~Y2Fe4M=VvxXg@g+ 0 and not os.path.exists(file_path_list[0])) or not os.path.exists(pmx_path): + return "" + + # モーションVMDディレクトリパス + motion_vmd_dir_path = get_dir_path(file_path_list[0]) + # モーションVMDファイル名・拡張子 + motion_vmd_file_name, motion_vmd_ext = os.path.splitext(os.path.basename(file_path_list[0])) + # 変換先モデルファイル名・拡張子 + pmx_file_name, _ = os.path.splitext(os.path.basename(pmx_path)) + + # 出力ファイルパス生成 + new_output_bone_path = os.path.join(motion_vmd_dir_path, "{0}_{1}{2}".format(motion_vmd_file_name, pmx_file_name, ".csv")) + + return new_output_bone_path + + def get_output_multi_join_vmd_path(base_file_path: str, pmx_path: str, output_multi_join_vmd_path: str, is_force=False): # モーションVMDパスの拡張子リスト if not os.path.exists(base_file_path) or not os.path.exists(pmx_path):