shotgun camera publish

반투50·2022년 4월 13일
0

카메라 퍼블리시 전체파일

# Copyright (c) 2017 Shotgun Software Inc.
#
# CONFIDENTIAL AND PROPRIETARY
#
# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
# Source Code License included in this distribution package. See LICENSE.
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.

import fnmatch
import os

import maya.cmds as cmds
import maya.mel as mel

import sgtk

# this method returns the evaluated hook base class. This could be the Hook
# class defined in Toolkit core or it could be the publisher app's base publish
# plugin class as defined in the configuration.
HookBaseClass = sgtk.get_hook_baseclass()


class MayaCameraPublishPlugin(HookBaseClass):
    """
    This class defines the required interface for a publish plugin. Publish
    plugins are responsible for operating on items collected by the collector
    plugin. Publish plugins define which items they will operate on as well as
    the execution logic for each phase of the publish process.
    """

    @property
    def description(self):
        ""
        플러그인이 하는 일에 대한 자세한 여러 줄 설명(:class:`str`).
        문자열에는 UI에 표시할 형식화를 위한 html이 포함될 수 있습니다(모든
        Qt의 서식 있는 텍스트 엔진에서 지원하는 html 태그).
        ""
        return """
        <p>This plugin handles publishing of cameras from maya.
        A publish template is required to define the destination of the output
        file.
        </p>
        """

    @property
    def settings(self):
        """
        A :class:`dict`는 이 플러그인의 구성 인터페이스를 정의합니다.
        사전에는 요구하는 설정의 수만큼 포함할 수 있습니다.
        플러그인이며 다음과 같은 형식을 취합니다.
            {
                <setting_name>: {
                    "type": <type>,
                    "default": <default>,
                    "description": <description>
                },
                <setting_name>: {
                    "type": <type>,
                    "default": <default>,
                    "description": <description>
                },
                ...
            }
		사전의 키는 설정 이름을 나타냅니다.
        그 값은 3개의 추가 key/value 쌍으로 구성된 사전입니다.
        * ``유형``: 설정 유형입니다. 
        이것은 다음 중 하나에 해당해야 합니다.
        툴킷이 앱 및 엔진 설정에 대해 허용하는 데이터 유형
          ``hook``, ``template``, ``string``  
        * ``default``: 설정의 기본값입니다. ``None``일 수 있습니다.
        * ``description``: 설정에 대한 설명을 문자열로 나타냅니다.
        
        플러그인에 대해 구성된 값은 setting parameter의 methods
        :meth:`accept`, :meth:`validate`, :meth:`publish` 및
        :meth:`finalize`로 제공됩니다.
        
        그 값은 또한 플러그인에 의해 정의된 사용자 정의 UI를 구동합니다.
        아티스트가 런타임에 설정을 조작할 수 있습니다. 
        참조
        :meth:`create_settings_widget`, :meth:`set_ui_settings` 및
        추가 정보는 :meth:`get_ui_settings`를 참조하세요.
        
        .. 참고:: publisher 앱의 ``hooks/`` 폴더에 정의된 후크를 참조하세요.
           추가 예제 구현을 위해.
        ""
        
        # 기본 publish 플러그인에서 설정을 상속합니다.
        plugin_settings = super(MayaCameraPublishPlugin, self).settings or {}

        # 이 클래스에 특정한 설정
        maya_camera_publish_settings = {
            "Publish Template": {
                "type": "template",
                "default": None,
                "description": "Template path for published camera. Should"
                               "correspond to a template defined in "
                               "templates.yml.",
            },
            "Cameras": {
                "type": "list",
                "default": ["camera*"],
                "description": "Glob-style list of camera names to publish. "
                               "Example: ['camMain', 'camAux*']."
            }
        }

        # 기본 설정 업데이트
        plugin_settings.update(maya_camera_publish_settings)

        return plugin_settings

    @property
    def item_filters(self):
        """
        A :class:`list` of item type wildcard :class:`str` objects that this
        plugin is interested in.
        
        As items are collected by the collector hook, they are given an item
        type string (see :meth:`~.processing.Item.create_item`). The strings
        provided by this property will be compared to each collected item's
        type.
        
        Only items with types matching entries in this list will be considered
        by the :meth:`accept` method. As such, this method makes it possible to
        quickly identify which items the plugin may be interested in. Any
        sophisticated acceptance logic is deferred to the :meth:`accept` method.
        
        문자열은 ``*``와 같은 glob 패턴을 포함할 수 있습니다(예: ``["maya.*",
        "file.maya"]``.
        """
        return ["maya.session.camera"]

    def accept(self, settings, item):
        """
        This method is called by the publisher to see if the plugin accepts the
        supplied item for processing.
        :data:`item_filters`를 통해 정의된 필터와 일치하는 항목만 속성이 이 메서드에 
        표시됩니다.

        여기에서 수락된 각 항목에 대해 게시 작업이 생성됩니다.
        This method returns a :class:`dict` of the following form::
            {
                "accepted": <bool>,
                "enabled": <bool>,
                "visible": <bool>,
                "checked": <bool>,
            }
        The keys correspond to the acceptance state of the supplied item. Not
        all keys are required. The keys are defined as follows:
        * ``accepted``: Indicates if the plugin is interested in this value at all.
          If ``False``, no task will be created for this plugin. Required.
          
        * ``enabled``: If ``True``, the created task will be enabled in the UI,
          otherwise it will be disabled (no interaction allowed). Optional,
          ``True`` by default.
          
        * ``visible``: If ``True``, the created task will be visible in the UI,
          otherwise it will be hidden. Optional, ``True`` by default.
          
        * ``checked``: If ``True``, the created task will be checked in the UI,
          otherwise it will be unchecked. Optional, ``True`` by default.
          
        In addition to the item, the configured settings for this plugin are
        supplied. The information provided by each of these arguments can be
        used to decide whether to accept the item.
        
        예를 들어 항목의 ``속성`` :class:`dict`에는 메타 데이터가 포함될 수 있습니다.
        항목에 대해 수집하는 동안 채워집니다. 이 데이터는 다음을 수행하는 데 사용할 수 있습니다.
        수락 논리를 알립니다.
        :param dict settings: The keys are strings, matching the keys returned
            in the :data:`settings` property. The values are
            :class:`~.processing.Setting` instances.
        :param item: The :class:`~.processing.Item` instance to process for
            acceptance.
        :returns: dictionary with boolean keys accepted, required and enabled
        """

        publisher = self.parent
        template_name = settings["Publish Template"].value

        # 먼저 카메라 이름을 확인합니다.
        cam_name = item.properties.get("camera_name")
        cam_shape = item.properties.get("camera_shape")

        if cam_name and cam_shape:
            if not self._cam_name_matches_settings(cam_name, settings):
                self.logger.debug(
                    "Camera name %s does not match any of the configured "
                    "patterns for camera names to publish. Not accepting "
                    "session camera item." % (cam_name,)
                )
                return {"accepted": False}
        else:
            self.logger.debug(
                "Camera name or shape was set on the item properties. Not "
                "accepting session camera item."
            )
            return {"accepted": False}

        # 상위 항목에서 카메라 파일 템플릿을 사용할 수 있는지 확인
        work_template = item.parent.properties.get("work_template")
        if not work_template:
            self.logger.debug(
                "A work template is required for the session item in order to "
                "publish a camera. Not accepting session camera item."
            )
            return {"accepted": False}

        # 게시 템플릿이 정의되고 유효한지 확인하고
        publish_template = publisher.get_template_by_name(template_name)
        if publish_template:
            item.properties["publish_template"] = publish_template
            # 게시 템플릿이 구성되어 있으므로 컨텍스트 변경을 비활성화합니다.
            # 게시자가 컨텍스트를 처리할 때까지 임시 조치입니다.
            # 기본적으로 전환합니다.
            item.context_change_allowed = False
        else:
            self.logger.debug(
                "The valid publish template could not be determined for the "
                "session camera item. Not accepting the item."
            )
            return {"accepted": False}

        # FBXExport 명령을 사용할 수 있는지 확인하십시오!
        if not mel.eval("exists \"FBXExport\""):
            self.logger.debug(
                "Item not accepted because fbx export command 'FBXExport' "
                "is not available. Perhaps the plugin is not enabled?"
            )
            return {"accepted": False}

        # all good!
        return {
            "accepted": True,
            "checked": True
        }

    def validate(self, settings, item):
        """
        게시하기에 적합한지 확인하여 지정된 항목의 유효성을 검사합니다.
        항목을 게시할 준비가 되었는지 여부를 나타내는 bool을 반환합니다.
        ``True``를 반환하면 항목을 게시할 준비가 되었음을 나타냅니다. 만약에
        ``False``가 반환되면 publisher는 아이템의 게시를 중단합니다.
        유효성 검사가 실패했음을 나타내기 위해 예외가 발생할 수도 있습니다.
        예외가 발생하면 오류 메시지가 다음과 같이 표시됩니다.
        작업에 대한 툴팁과 게시자의 로깅 보기에서 확인할 수 있습니다.
        
        :param dict settings: The keys are strings, matching the keys returned
            in the :data:`settings` property. The values are
            :class:`~.processing.Setting` instances.
        :param item: The :class:`~.processing.Item` instance to validate.
        :returns: True if item is valid, False otherwise.
        """

        path = _session_path()

        # ---- 세션이 저장되었는지 확인

        if not path:
            # the session still requires saving. provide a save button.
            # validation fails.
            error_msg = "The Maya session has not been saved."
            self.logger.error(
                error_msg,
                extra=_get_save_as_action()
            )
            raise Exception(error_msg)

        # 정규화된 경로를 얻습니다.
        path = sgtk.util.ShotgunPath.normalize(path)

        cam_name = item.properties["camera_name"]

        # 카메라가 파일에 여전히 존재하는지 확인
        if not cmds.ls(cam_name):
            error_msg = (
                "Validation failed because the collected camera (%s) is no "
                "longer in the scene. You can uncheck this plugin or create "
                "a camera with this name to export to avoid this error." %
                (cam_name,)
            )
            self.logger.error(error_msg)
            raise Exception(error_msg)

        # 구성된 작업 파일 템플릿을 가져옵니다.
        work_template = item.parent.properties.get("work_template")
        publish_template = item.properties.get("publish_template")

        # 현재 장면 경로를 가져오고 작업을 사용하여 여기에서 필드를 추출합니다.
        # template:
        work_fields = work_template.get_fields(path)

        # 필드에 카메라 이름을 포함합니다.
        work_fields["name"] = cam_name

        # 게시 템플릿에서 필드가 작동하는지 확인
        missing_keys = publish_template.missing_keys(work_fields)
        if missing_keys:
            error_msg = "Work file '%s' missing keys required for the " \
                        "publish template: %s" % (path, missing_keys)
            self.logger.error(error_msg)
            raise Exception(error_msg)

        # create the publish path by applying the fields. store it in the item's
        # properties. This is the path we'll create and then publish in the base
        # publish plugin. Also set the publish_path to be explicit.
        publish_path = publish_template.apply_fields(work_fields)
        item.properties["path"] = publish_path
        item.properties["publish_path"] = publish_path

        # 게시할 때 작업 파일의 버전 번호를 사용합니다.
        if "version" in work_fields:
            item.properties["publish_version"] = work_fields["version"]

        # 기본 클래스 유효성 검사를 실행합니다.
        return super(MayaCameraPublishPlugin, self).validate(settings, item)

    def publish(self, settings, item):
        """
        Executes the publish logic for the given item and settings.
        Any raised exceptions will indicate that the publish pass has failed and
        the publisher will stop execution.
        
        :param dict settings: The keys are strings, matching the keys returned
            in the :data:`settings` property. The values are
            :class:`~.processing.Setting` instances.
        :param item: The :class:`~.processing.Item` instance to validate.
        """

        # 현재 선택된 모든 항목을 추적합니다. 우리는에서 복원 할 것입니다
        # publish 메소드의 끝
        cur_selection = cmds.ls(selection=True)

        # publish 카메라
        cam_shape = item.properties["camera_shape"]

        # 선택되었는지 확인
        cmds.select(cam_shape)

        # 생성하고 게시할 경로를 얻습니다.
        publish_path = item.properties["publish_path"]

        # 게시 폴더가 있는지 확인합니다.
        publish_folder = os.path.dirname(publish_path)
        self.parent.ensure_folder_exists(publish_folder)

        fbx_export_cmd = 'FBXExport -f "%s" -s' % (publish_path.replace(os.path.sep, "/"),)

        # ...and execute it:
        try:
            self.logger.debug("Executing command: %s" % fbx_export_cmd)
            mel.eval(fbx_export_cmd)
        except Exception, e:
            self.logger.error("Failed to export camera: %s" % e)
            return

        # 항목의 속성에서 게시 유형을 설정합니다. 기본 플러그인은
        # Shotgun에 파일을 등록할 때 사용
        item.properties["publish_type"] = "FBX Camera"

        # 이제 경로가 생성되었으므로
        super(MayaCameraPublishPlugin, self).publish(settings, item)

        # selection 복원
        cmds.select(cur_selection)

    def _cam_name_matches_settings(self, cam_name, settings):
        """
        제공된 카메라 이름이 구성된 카메라 이름과 일치하는 경우 True를 반환합니다.
        카메라 이름 패턴.
        """

        # loop through each pattern specified and see if the supplied camera
        # name matches the pattern
        cam_patterns = settings["Cameras"].value

        # if no patterns specified, then no constraints on camera name
        if not cam_patterns:
            return True

        for camera_pattern in cam_patterns:
            if fnmatch.fnmatch(cam_name, camera_pattern):
                return True

        return False


def _session_path():
    """
    현재 세션의 경로를 반환
    :return:
    """
    path = cmds.file(query=True, sn=True)

    if isinstance(path, unicode):
        path = path.encode("utf-8")

    return path


def _get_save_as_action():
    """
    세션 저장을 위한 로그 작업 딕셔너리를 반환하는 간단한 도우미
    """

    engine = sgtk.platform.current_engine()

    # default save callback
    callback = cmds.SaveScene

    # if workfiles2 is configured, use that for file save
    if "tk-multi-workfiles2" in engine.apps:
        app = engine.apps["tk-multi-workfiles2"]
        if hasattr(app, "show_file_save_dlg"):
            callback = app.show_file_save_dlg

    return {
        "action_button": {
            "label": "Save As...",
            "tooltip": "Save the current session",
            "callback": callback
        }
    }
profile
취미로 개발

0개의 댓글