
NOTE
import pymel.core as pm import maya.mel as mel import maya.OpenMayaUI as omui import os import json import re # --- Settings --- SAVE_DIR = r'Q:/temp/screenshots/' # 保存先ディレクトリ (環境に合わせて変更してください) # --- Helper Functions --- def ensure_directory(path): """指定されたパスのディレクトリが存在しない場合、作成する""" if not os.path.exists(path): os.makedirs(path) def get_next_filename(base_name="camera_shot"): """指定されたベース名で連番のファイル名を生成する""" ensure_directory(SAVE_DIR) existing_files = os.listdir(SAVE_DIR) pattern = re.compile(re.escape(base_name) + r'_(\d+)\.png') numbers = [int(m.group(1)) for f in existing_files if (m := pattern.match(f))] next_num = max(numbers) + 1 if numbers else 1 return f"{base_name}_{next_num:03d}" def capture_playblast(camera, filename_base): """指定されたカメラから現在のフレームのスクリーンショットを保存する""" filepath = os.path.join(SAVE_DIR, filename_base + ".png") pm.lookThru(camera) # 現在のフレームを取得 current_frame = pm.currentTime(q=True) pm.playblast( completeFilename=filepath, format='image', width=1920, # 必要に応じて解像度を調整 height=1080, # 必要に応じて解像度を調整 frame=[current_frame], # 現在のフレームのみを対象 viewer=False, showOrnaments=False, percent=100, compression='png', quality=100 # PNGの場合、qualityは通常無視されるが念のため ) print(f"Screenshot saved to: {filepath}") def get_camera_rig_settings(camera_transform): """カメラリグの各ノードの設定値を取得する""" settings = {} try: # リグのノードを取得 (カメラからの親子関係を想定) twist = camera_transform.getParent() move = twist.getParent() rig_group = move.getParent() # aimpointノードをPyMelで取得 (rig_groupの子にあると想定) aimpoint = None if rig_group: # rig_groupが存在する場合のみ探す for child in rig_group.listRelatives(children=True, type='transform'): # ノード名で完全一致をチェック (より安全に) if child.nodeName() == 'aimpoint': aimpoint = child break # 各ノードの設定値を取得 if twist: # twistノードのローカル回転を取得 (親ノードmoveからの相対回転) settings["twist_rotate"] = twist.rotate.get().tolist() else: print("Warning: twistノードが見つかりません。カメラの直接の親を取得します。") settings["twist_rotate"] = None # 見つからない場合はNone if move: # moveノードのローカル移動を取得 (親ノードrig_groupからの相対移動) settings["move_translate"] = move.translate.get().tolist() # moveノードのローカル回転を取得 (親ノードrig_groupからの相対回転) settings["move_rotate"] = move.rotate.get().tolist() else: print("Warning: moveノードが見つかりません。") settings["move_translate"] = None settings["move_rotate"] = None if rig_group: # rig_group はワールド座標で取得 (リグ全体の配置) settings["camera_rig_translate"] = rig_group.getTranslation(space='world').tolist() settings["camera_rig_rotate"] = rig_group.getRotation(space='world').tolist() else: print("Warning: rig_groupノードが見つかりません。") settings["camera_rig_translate"] = None settings["camera_rig_rotate"] = None if aimpoint: # # moveノードのローカル移動を取得 (親ノードrig_groupからの相対移動) settings["aimpoint_translate"] = aimpoint.translate.get().tolist() else: # aimpointが見つからない場合は警告を出す(エラーではない) print("Warning: aimpointノードが見つかりません。") settings["aimpoint_translate"] = None # JSONにはnullとして記録 # カメラシェイプの設定を取得 (焦点距離のみ) camera_shape = camera_transform.getShape() if camera_shape and isinstance(camera_shape, pm.nodetypes.Camera): if camera_shape.hasAttr("focalLength"): settings["focalLength"] = camera_shape.focalLength.get() # 他に必要なカメラシェイプの属性があればここに追加 #例: settings["horizontalFilmAperture"] = camera_shape.horizontalFilmAperture.get() #例: settings["verticalFilmAperture"] = camera_shape.verticalFilmAperture.get() else: print(f"Warning: {camera_transform.name()} に有効なカメラシェイプが見つかりません。") except pm.MayaNodeError as e: print(f"Error: カメラリグのノード構造が想定と異なるか、存在しません: {e}") except AttributeError as e: # getParent() が None を返した場合などに発生しうる print(f"Error: ノードの取得に失敗しました。リグの階層構造を確認してください: {e}") except Exception as e: print(f"Error getting camera rig settings: {e}") return settings def apply_camera_rig_settings(camera_transform, settings): """JSONから読み込んだ設定値をカメラリグに適用する""" try: # リグのノードを取得 twist = camera_transform.getParent() move = twist.getParent() rig_group = move.getParent() # aimpointノードをPyMelで取得 aimpoint = None if rig_group: # rig_groupが存在する場合のみ探す for child in rig_group.listRelatives(children=True, type='transform'): if child.nodeName() == 'aimpoint': aimpoint = child break # --- 設定値を適用 --- # rig_group (ワールド座標) if rig_group: if "camera_rig_translate" in settings and settings["camera_rig_translate"] is not None: rig_group.setTranslation(settings["camera_rig_translate"], space='world') if "camera_rig_rotate" in settings and settings["camera_rig_rotate"] is not None: rig_group.setRotation(settings["camera_rig_rotate"], space='world') else: print("Warning: rig_groupノードが見つからないため、適用できません。") # move (ローカル座標) if move: if "move_translate" in settings and settings["move_translate"] is not None: move.translate.set(settings["move_translate"]) # ローカル移動を適用 if "move_rotate" in settings and settings["move_rotate"] is not None: move.rotate.set(settings["move_rotate"]) # ローカル回転を適用 else: print("Warning: moveノードが見つからないため、適用できません。") # twist (ローカル座標) if twist: if "twist_rotate" in settings and settings["twist_rotate"] is not None: twist.rotate.set(settings["twist_rotate"]) # ローカル回転を適用 else: print("Warning: twistノードが見つからないため、適用できません。") # aimpoint (ワールド座標) if aimpoint: if "aimpoint_translate" in settings and settings["aimpoint_translate"] is not None: aimpoint.setTranslation(settings["aimpoint_translate"], space='world') elif "aimpoint_translate" in settings and settings["aimpoint_translate"] is not None: # aimpoint を復元しようとしたが、シーン内にノードが見つからない場合 print("Warning: aimpointノードが見つからないため、適用できません。") # カメラシェイプの設定を適用 camera_shape = camera_transform.getShape() if camera_shape and isinstance(camera_shape, pm.nodetypes.Camera): if "focalLength" in settings and settings["focalLength"] is not None: camera_shape.focalLength.set(settings["focalLength"]) # 他のカメラシェイプ属性を復元する場合はここに追加 #例: if "horizontalFilmAperture" in settings: camera_shape.horizontalFilmAperture.set(settings["horizontalFilmAperture"]) #例: if "verticalFilmAperture" in settings: camera_shape.verticalFilmAperture.set(settings["verticalFilmAperture"]) else: print(f"Warning: {camera_transform.name()} に有効なカメラシェイプが見つからないため、適用できません。") except pm.MayaNodeError as e: print(f"Error: カメラリグのノードが存在しません: {e}") except AttributeError as e: print(f"Error: ノードの取得に失敗しました。リグの階層構造を確認してください: {e}") except Exception as e: print(f"Error applying camera rig settings: {e}") def save_camera_rig_data(filename, camera_transform): """カメラリグの設定を取得し、JSONファイルに保存する""" settings = get_camera_rig_settings(camera_transform) if not settings: # settingsが空辞書の場合はエラーがあった可能性 print("Error: カメラ設定の取得に失敗したため、JSONファイルを保存できません。") return try: ensure_directory(os.path.dirname(filename)) # 保存先ディレクトリを確認 with open(filename, "w") as f: json.dump(settings, f, indent=4, ensure_ascii=False) # 日本語ファイルパス等も考慮 print(f"Camera rig data saved to: {filename}") except Exception as e: print(f"Error saving camera rig data to file: {e}") def load_camera_rig_data(filename, camera_transform): """JSONファイルからカメラリグの設定を読み込み、適用する""" try: with open(filename, "r") as f: settings = json.load(f) apply_camera_rig_settings(camera_transform, settings) print(f"Camera rig data loaded from: {filename}") except FileNotFoundError: print(f"Error: ファイルが見つかりません: {filename}") except json.JSONDecodeError: print(f"Error: JSONファイルの形式が正しくありません: {filename}") except Exception as e: print(f"Error loading camera rig data from file: {e}") # --- UI --- def save_screenshot_and_camera_data(camera_transform, text_field, *args): """UIから呼び出され、スクリーンショットとJSONデータを保存する""" filename_base = text_field.getText().strip() # 前後の空白を除去 if not filename_base: pm.warning("ファイル名を入力してください。") return # スクリーンショット保存 capture_playblast(camera_transform, filename_base) # JSONデータ保存 json_filepath = os.path.join(SAVE_DIR, filename_base + ".json") save_camera_rig_data(json_filepath, camera_transform) pm.confirmDialog(title='完了', message='スクリーンショットとカメラリグデータを保存しました!', button=['OK']) # 保存後にファイル名を更新するかどうかは要件次第 # text_field.setText(get_next_filename()) # 次のファイル名を提案する場合 def load_camera_data_ui(camera_transform, text_field, *args): """UIから呼び出され、JSONデータをロードする""" filename_base = text_field.getText().strip() if not filename_base: pm.warning("ロードするファイル名を指定してください。") return filename = os.path.join(SAVE_DIR, filename_base + ".json") load_camera_rig_data(filename, camera_transform) pm.informDialog(title='完了', message=f'{filename_base}.json のカメラデータを適用しました。') def open_save_ui(): """メインのUIウィンドウを開く""" sel = pm.selected(type='transform') # トランスフォームノードのみ選択 if not sel: pm.warning("カメラのトランスフォームノードを選択してください。") return camera_transform = sel[0] # 選択したものがカメラかどうかの確認を強化 camera_shape = None try: shapes = camera_transform.getShapes() if shapes: for shape in shapes: if isinstance(shape, pm.nodetypes.Camera): camera_shape = shape break except Exception as e: print(f"シェイプ取得中にエラー: {e}") # ログに詳細を表示 if not camera_shape: pm.warning("選択されたオブジェクトにカメラシェイプが見つかりません。カメラを選択してください。") return # ウィンドウの重複起動を防止 win_name = "SaveShotCameraDataWindow" if pm.window(win_name, exists=True): pm.deleteUI(win_name) # 次のファイル名を提案 filename_base = get_next_filename() # ウィンドウ作成 with pm.window(win_name, title="Screenshot & Camera Data", sizeable=False, widthHeight=(350, 150)) as win: with pm.columnLayout(adjustableColumn=True, rowSpacing=10, columnAttach=('both', 5)): pm.text(label="ファイル名 (拡張子なし):") # テキストフィールド作成時に変数に格納 filename_textField = pm.textField(text=filename_base, enterCommand=lambda *args: save_screenshot_and_camera_data(camera_transform, filename_textField)) # 保存ボタン pm.button(label="Save Screenshot & Camera Data", command=lambda *args: save_screenshot_and_camera_data(camera_transform, filename_textField), height=30) # ロードボタン pm.button(label="Load Camera Data (from filename above)", command=lambda *args: load_camera_data_ui(camera_transform, filename_textField), height=30) # 保存先表示 (確認用) pm.text(label=f"保存先: {os.path.abspath(SAVE_DIR)}", align='left') pm.showWindow(win_name) # --- スクリプト実行 --- # 以下の行をコメントアウト解除するか、Mayaのスクリプトエディタで直接実行 open_save_ui()