KnowShare

Home

❯

Tips&Tutorials

❯

Maya Tips

❯

スクリーンショットツール

スクリーンショットツール

Oct 11, 202510 min read

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()
 

Graph View

Created with Quartz v4.5.1 © 2025

  • GitHub
  • Discord Community