Merge branch 'develop' into enhancement/OP-7596_3dsmax-unit-scale-issue-for-FBX

This commit is contained in:
Kayla Man 2024-01-04 00:15:59 +08:00 committed by GitHub
commit 0dad84a909
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 785 additions and 350 deletions

View file

@ -35,6 +35,8 @@ body:
label: Version
description: What version are you running? Look to OpenPype Tray
options:
- 3.18.2
- 3.18.2-nightly.6
- 3.18.2-nightly.5
- 3.18.2-nightly.4
- 3.18.2-nightly.3
@ -133,8 +135,6 @@ body:
- 3.15.6-nightly.2
- 3.15.6-nightly.1
- 3.15.5
- 3.15.5-nightly.2
- 3.15.5-nightly.1
validations:
required: true
- type: dropdown

View file

@ -1,6 +1,323 @@
# Changelog
## [3.18.2](https://github.com/ynput/OpenPype/tree/3.18.2)
[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.1...3.18.2)
### **🚀 Enhancements**
<details>
<summary>Testing: Release Maya/Deadline job from pending when testing. <a href="https://github.com/ynput/OpenPype/pull/5988">#5988</a></summary>
When testing we wont put the Deadline jobs into pending with dependencies, so the worker can start as soon as possible.
___
</details>
<details>
<summary>Max: Tweaks on Extractions for the exporters <a href="https://github.com/ynput/OpenPype/pull/5814">#5814</a></summary>
With this PR
- Suspend Refresh would be introduced in abc & obj extractors for optimization.
- Allow users to choose the custom attributes to be included in abc exports
___
</details>
<details>
<summary>Maya: Optional preserve references. <a href="https://github.com/ynput/OpenPype/pull/5994">#5994</a></summary>
Optional preserve references when publishing Maya scenes.
___
</details>
<details>
<summary>AYON ftrack: Expect 'ayon' group in custom attributes <a href="https://github.com/ynput/OpenPype/pull/6066">#6066</a></summary>
Expect `ayon` group as one of options to get custom attributes.
___
</details>
<details>
<summary>AYON Chore: Remove dependencies related to separated addons <a href="https://github.com/ynput/OpenPype/pull/6074">#6074</a></summary>
Removed dependencies from openpype client pyproject.toml that are already defined by addons which require them.
___
</details>
<details>
<summary>Editorial & chore: Stop using pathlib2 <a href="https://github.com/ynput/OpenPype/pull/6075">#6075</a></summary>
Do not use `pathlib2` which is Python 2 backport for `pathlib` module in python 3.
___
</details>
<details>
<summary>Traypublisher: Correct validator label <a href="https://github.com/ynput/OpenPype/pull/6084">#6084</a></summary>
Use correct label for Validate filepaths.
___
</details>
<details>
<summary>Nuke: Extract Review Intermediate disabled when both Extract Review Mov and Extract Review Intermediate disabled in setting <a href="https://github.com/ynput/OpenPype/pull/6089">#6089</a></summary>
Report in Discord https://discord.com/channels/517362899170230292/563751989075378201/1187874498234556477
___
</details>
### **🐛 Bug fixes**
<details>
<summary>Maya: Bug fix the file from texture node not being collected correctly in Yeti Rig <a href="https://github.com/ynput/OpenPype/pull/5990">#5990</a></summary>
Fix the bug of collect Yeti Rig not being able to get the file parameter(s) from the texture node(s), resulting to the failure of publishing the textures to the resource directory.
___
</details>
<details>
<summary>Bug: fix AYON settings for Maya workspace <a href="https://github.com/ynput/OpenPype/pull/6069">#6069</a></summary>
This is changing bug in default AYON setting for Maya workspace, where missing semicolumn caused workspace not being set. This is also syncing default workspace settings to OpenPype
___
</details>
<details>
<summary>Refactor colorspace handling in CollectColorspace plugin <a href="https://github.com/ynput/OpenPype/pull/6033">#6033</a></summary>
Traypublisher is now capable set available colorspaces or roles to publishing images sequence or video. This is fix of new implementation where we allowed to use roles in the enumerator selector.
___
</details>
<details>
<summary>Bugfix: Houdini render split bugs <a href="https://github.com/ynput/OpenPype/pull/6037">#6037</a></summary>
This PR is a follow up PR to https://github.com/ynput/OpenPype/pull/5420This PR does:
- refactor `get_output_parameter` to what is used to be.
- fix a bug with split render
- rename `exportJob` flag to `split_render`
___
</details>
<details>
<summary>Fusion: fix for single frame rendering <a href="https://github.com/ynput/OpenPype/pull/6056">#6056</a></summary>
Fixes publishes of single frame of `render` product type.
___
</details>
<details>
<summary>Photoshop: fix layer publish thumbnail missing in loader <a href="https://github.com/ynput/OpenPype/pull/6061">#6061</a></summary>
Thumbnails from any products (either `review` nor separate layer instances) weren't stored in Ayon.This resulted in not showing them in Loader and Server UI. After this PR thumbnails should be shown in the Loader and on the Server (`http://YOUR_AYON_HOSTNAME:5000/projects/YOUR_PROJECT/browser`).
___
</details>
<details>
<summary>AYON Chore: Do not use thumbnailSource for thumbnail integration <a href="https://github.com/ynput/OpenPype/pull/6063">#6063</a></summary>
Do not use `thumbnailSource` for thumbnail integration.
___
</details>
<details>
<summary>Photoshop: fix creation of .mov <a href="https://github.com/ynput/OpenPype/pull/6064">#6064</a></summary>
Generation of .mov file with 1 frame per published layer was failing.
___
</details>
<details>
<summary>Photoshop: fix Collect Color Coded settings <a href="https://github.com/ynput/OpenPype/pull/6065">#6065</a></summary>
Fix for wrong default value for `Collect Color Coded Instances` Settings
___
</details>
<details>
<summary>Bug: Fix Publisher parent window in Nuke <a href="https://github.com/ynput/OpenPype/pull/6067">#6067</a></summary>
Fixing issue where publisher parent window wasn't set because wrong use of version constant.
___
</details>
<details>
<summary>Python console widget: Save registry fix <a href="https://github.com/ynput/OpenPype/pull/6076">#6076</a></summary>
Do not save registry until there is something to save.
___
</details>
<details>
<summary>Ftrack: update asset names for multiple reviewable items <a href="https://github.com/ynput/OpenPype/pull/6077">#6077</a></summary>
Multiple reviewable assetVersion components with better grouping to asset version name.
___
</details>
<details>
<summary>Ftrack: DJV action fixes <a href="https://github.com/ynput/OpenPype/pull/6098">#6098</a></summary>
Fix bugs in DJV ftrack action.
___
</details>
<details>
<summary>AYON Workfiles tool: Fix arrow to timezone typo <a href="https://github.com/ynput/OpenPype/pull/6099">#6099</a></summary>
Fix parenthesis typo with arrow local timezone function.
___
</details>
### **🔀 Refactored code**
<details>
<summary>Chore: Update folder-favorite icon to ayon icon <a href="https://github.com/ynput/OpenPype/pull/5718">#5718</a></summary>
Updates old "Pype-2.0-era" (from ancient greece times) to AYON logo equivalent.I believe it's only used in Nuke.
___
</details>
### **Merged pull requests**
<details>
<summary>Chore: Maya / Nuke remove publish gui filters from settings <a href="https://github.com/ynput/OpenPype/pull/5570">#5570</a></summary>
- Remove Publish GUI Filters from Nuke settings
- Remove Publish GUI Filters from Maya settings
___
</details>
<details>
<summary>Fusion: Project/User option for output format (create_saver) <a href="https://github.com/ynput/OpenPype/pull/6045">#6045</a></summary>
Adds "Output Image Format" option which can be set via project settings and overwritten by users in "Create" menu. This replaces the current behaviour of being hardcoded to "exr". Replacing the need for people to manually edit the saver path if they require a different extension.
___
</details>
<details>
<summary>Fusion: Output Image Format Updating Instances (create_saver) <a href="https://github.com/ynput/OpenPype/pull/6060">#6060</a></summary>
Adds the ability to update Saver image output format if changed in the Publish UI.~~Adds an optional validator that compares "Output Image Format" in the Publish menu against the one currently found on the saver. It then offers a repair action to update the output extension on the saver.~~
___
</details>
<details>
<summary>Tests: Fix representation count for AE legacy test <a href="https://github.com/ynput/OpenPype/pull/6072">#6072</a></summary>
___
</details>
## [3.18.1](https://github.com/ynput/OpenPype/tree/3.18.1)

View file

@ -0,0 +1,139 @@
"""Backwards compatible implementation of ExitStack for Python 2.
ExitStack contextmanager was implemented with Python 3.3.
As long as we supportPython 2 hosts we can use this backwards
compatible implementation to support bothPython 2 and Python 3.
Instead of using ExitStack from contextlib, use it from this module:
>>> from openpype.hosts.maya.api.exitstack import ExitStack
It will provide the appropriate ExitStack implementation for the current
running Python version.
"""
# TODO: Remove the entire script once dropping Python 2 support.
import contextlib
if getattr(contextlib, "nested", None):
from contextlib import ExitStack # noqa
else:
import sys
from collections import deque
class ExitStack(object):
"""Context manager for dynamic management of a stack of exit callbacks
For example:
with ExitStack() as stack:
files = [stack.enter_context(open(fname))
for fname in filenames]
# All opened files will automatically be closed at the end of
# the with statement, even if attempts to open files later
# in the list raise an exception
"""
def __init__(self):
self._exit_callbacks = deque()
def pop_all(self):
"""Preserve the context stack by transferring
it to a new instance"""
new_stack = type(self)()
new_stack._exit_callbacks = self._exit_callbacks
self._exit_callbacks = deque()
return new_stack
def _push_cm_exit(self, cm, cm_exit):
"""Helper to correctly register callbacks
to __exit__ methods"""
def _exit_wrapper(*exc_details):
return cm_exit(cm, *exc_details)
_exit_wrapper.__self__ = cm
self.push(_exit_wrapper)
def push(self, exit):
"""Registers a callback with the standard __exit__ method signature
Can suppress exceptions the same way __exit__ methods can.
Also accepts any object with an __exit__ method (registering a call
to the method instead of the object itself)
"""
# We use an unbound method rather than a bound method to follow
# the standard lookup behaviour for special methods
_cb_type = type(exit)
try:
exit_method = _cb_type.__exit__
except AttributeError:
# Not a context manager, so assume its a callable
self._exit_callbacks.append(exit)
else:
self._push_cm_exit(exit, exit_method)
return exit # Allow use as a decorator
def callback(self, callback, *args, **kwds):
"""Registers an arbitrary callback and arguments.
Cannot suppress exceptions.
"""
def _exit_wrapper(exc_type, exc, tb):
callback(*args, **kwds)
# We changed the signature, so using @wraps is not appropriate, but
# setting __wrapped__ may still help with introspection
_exit_wrapper.__wrapped__ = callback
self.push(_exit_wrapper)
return callback # Allow use as a decorator
def enter_context(self, cm):
"""Enters the supplied context manager
If successful, also pushes its __exit__ method as a callback and
returns the result of the __enter__ method.
"""
# We look up the special methods on the type to
# match the with statement
_cm_type = type(cm)
_exit = _cm_type.__exit__
result = _cm_type.__enter__(cm)
self._push_cm_exit(cm, _exit)
return result
def close(self):
"""Immediately unwind the context stack"""
self.__exit__(None, None, None)
def __enter__(self):
return self
def __exit__(self, *exc_details):
# We manipulate the exception state so it behaves as though
# we were actually nesting multiple with statements
frame_exc = sys.exc_info()[1]
def _fix_exception_context(new_exc, old_exc):
while 1:
exc_context = new_exc.__context__
if exc_context in (None, frame_exc):
break
new_exc = exc_context
new_exc.__context__ = old_exc
# Callbacks are invoked in LIFO order to match the behaviour of
# nested context managers
suppressed_exc = False
while self._exit_callbacks:
cb = self._exit_callbacks.pop()
try:
if cb(*exc_details):
suppressed_exc = True
exc_details = (None, None, None)
except Exception:
new_exc_details = sys.exc_info()
# simulate the stack of exceptions by setting the context
_fix_exception_context(new_exc_details[1], exc_details[1])
if not self._exit_callbacks:
raise
exc_details = new_exc_details
return suppressed_exc

View file

@ -1,6 +1,7 @@
"""Standalone helper functions"""
import os
import copy
from pprint import pformat
import sys
import uuid
@ -9,6 +10,8 @@ import re
import json
import logging
import contextlib
import capture
from .exitstack import ExitStack
from collections import OrderedDict, defaultdict
from math import ceil
from six import string_types
@ -172,6 +175,216 @@ def maintained_selection():
cmds.select(clear=True)
def reload_all_udim_tile_previews():
"""Regenerate all UDIM tile preview in texture file"""
for texture_file in cmds.ls(type="file"):
if cmds.getAttr("{}.uvTilingMode".format(texture_file)) > 0:
cmds.ogs(regenerateUVTilePreview=texture_file)
@contextlib.contextmanager
def panel_camera(panel, camera):
"""Set modelPanel's camera during the context.
Arguments:
panel (str): modelPanel name.
camera (str): camera name.
"""
original_camera = cmds.modelPanel(panel, query=True, camera=True)
try:
cmds.modelPanel(panel, edit=True, camera=camera)
yield
finally:
cmds.modelPanel(panel, edit=True, camera=original_camera)
def render_capture_preset(preset):
"""Capture playblast with a preset.
To generate the preset use `generate_capture_preset`.
Args:
preset (dict): preset options
Returns:
str: Output path of `capture.capture`
"""
# Force a refresh at the start of the timeline
# TODO (Question): Why do we need to do this? What bug does it solve?
# Is this for simulations?
cmds.refresh(force=True)
refresh_frame_int = int(cmds.playbackOptions(query=True, minTime=True))
cmds.currentTime(refresh_frame_int - 1, edit=True)
cmds.currentTime(refresh_frame_int, edit=True)
log.debug(
"Using preset: {}".format(
json.dumps(preset, indent=4, sort_keys=True)
)
)
preset = copy.deepcopy(preset)
# not supported by `capture` so we pop it off of the preset
reload_textures = preset["viewport_options"].pop("loadTextures", False)
panel = preset.pop("panel")
with ExitStack() as stack:
stack.enter_context(maintained_time())
stack.enter_context(panel_camera(panel, preset["camera"]))
stack.enter_context(viewport_default_options(panel, preset))
if reload_textures:
# Force immediate texture loading when to ensure
# all textures have loaded before the playblast starts
stack.enter_context(material_loading_mode(mode="immediate"))
# Regenerate all UDIM tiles previews
reload_all_udim_tile_previews()
path = capture.capture(log=self.log, **preset)
return path
def generate_capture_preset(instance, camera, path,
start=None, end=None, capture_preset=None):
"""Function for getting all the data of preset options for
playblast capturing
Args:
instance (pyblish.api.Instance): instance
camera (str): review camera
path (str): filepath
start (int): frameStart
end (int): frameEnd
capture_preset (dict): capture preset
Returns:
dict: Resulting preset
"""
preset = load_capture_preset(data=capture_preset)
preset["camera"] = camera
preset["start_frame"] = start
preset["end_frame"] = end
preset["filename"] = path
preset["overwrite"] = True
preset["panel"] = instance.data["panel"]
# Disable viewer since we use the rendering logic for publishing
# We don't want to open the generated playblast in a viewer directly.
preset["viewer"] = False
# "isolate_view" will already have been applied at creation, so we'll
# ignore it here.
preset.pop("isolate_view")
# Set resolution variables from capture presets
width_preset = capture_preset["Resolution"]["width"]
height_preset = capture_preset["Resolution"]["height"]
# Set resolution variables from asset values
asset_data = instance.data["assetEntity"]["data"]
asset_width = asset_data.get("resolutionWidth")
asset_height = asset_data.get("resolutionHeight")
review_instance_width = instance.data.get("review_width")
review_instance_height = instance.data.get("review_height")
# Use resolution from instance if review width/height is set
# Otherwise use the resolution from preset if it has non-zero values
# Otherwise fall back to asset width x height
# Else define no width, then `capture.capture` will use render resolution
if review_instance_width and review_instance_height:
preset["width"] = review_instance_width
preset["height"] = review_instance_height
elif width_preset and height_preset:
preset["width"] = width_preset
preset["height"] = height_preset
elif asset_width and asset_height:
preset["width"] = asset_width
preset["height"] = asset_height
# Isolate view is requested by having objects in the set besides a
# camera. If there is only 1 member it'll be the camera because we
# validate to have 1 camera only.
if instance.data["isolate"] and len(instance.data["setMembers"]) > 1:
preset["isolate"] = instance.data["setMembers"]
# Override camera options
# Enforce persisting camera depth of field
camera_options = preset.setdefault("camera_options", {})
camera_options["depthOfField"] = cmds.getAttr(
"{0}.depthOfField".format(camera)
)
# Use Pan/Zoom from instance data instead of from preset
preset.pop("pan_zoom", None)
camera_options["panZoomEnabled"] = instance.data["panZoom"]
# Override viewport options by instance data
viewport_options = preset.setdefault("viewport_options", {})
viewport_options["displayLights"] = instance.data["displayLights"]
viewport_options["imagePlane"] = instance.data.get("imagePlane", True)
# Override transparency if requested.
transparency = instance.data.get("transparency", 0)
if transparency != 0:
preset["viewport2_options"]["transparencyAlgorithm"] = transparency
# Update preset with current panel setting
# if override_viewport_options is turned off
if not capture_preset["Viewport Options"]["override_viewport_options"]:
panel_preset = capture.parse_view(preset["panel"])
panel_preset.pop("camera")
preset.update(panel_preset)
return preset
@contextlib.contextmanager
def viewport_default_options(panel, preset):
"""Context manager used by `render_capture_preset`.
We need to explicitly enable some viewport changes so the viewport is
refreshed ahead of playblasting.
"""
# TODO: Clarify in the docstring WHY we need to set it ahead of
# playblasting. What issues does it solve?
viewport_defaults = {}
try:
keys = [
"useDefaultMaterial",
"wireframeOnShaded",
"xray",
"jointXray",
"backfaceCulling",
"textures"
]
for key in keys:
viewport_defaults[key] = cmds.modelEditor(
panel, query=True, **{key: True}
)
if preset["viewport_options"].get(key):
cmds.modelEditor(
panel, edit=True, **{key: True}
)
yield
finally:
# Restoring viewport options.
if viewport_defaults:
cmds.modelEditor(
panel, edit=True, **viewport_defaults
)
@contextlib.contextmanager
def material_loading_mode(mode="immediate"):
"""Set material loading mode during context"""
original = cmds.displayPref(query=True, materialLoadingMode=True)
cmds.displayPref(materialLoadingMode=mode)
try:
yield
finally:
cmds.displayPref(materialLoadingMode=original)
def get_namespace(node):
"""Return namespace of given node"""
node_name = node.rsplit("|", 1)[-1]
@ -2677,7 +2890,7 @@ def bake_to_world_space(nodes,
return world_space_nodes
def load_capture_preset(data=None):
def load_capture_preset(data):
"""Convert OpenPype Extract Playblast settings to `capture` arguments
Input data is the settings from:
@ -2691,8 +2904,6 @@ def load_capture_preset(data=None):
"""
import capture
options = dict()
viewport_options = dict()
viewport2_options = dict()

View file

@ -1,9 +1,6 @@
import os
import json
import contextlib
import clique
import capture
from openpype.pipeline import publish
from openpype.hosts.maya.api import lib
@ -11,16 +8,6 @@ from openpype.hosts.maya.api import lib
from maya import cmds
@contextlib.contextmanager
def panel_camera(panel, camera):
original_camera = cmds.modelPanel(panel, query=True, camera=True)
try:
cmds.modelPanel(panel, edit=True, camera=camera)
yield
finally:
cmds.modelPanel(panel, edit=True, camera=original_camera)
class ExtractPlayblast(publish.Extractor):
"""Extract viewport playblast.
@ -36,19 +23,8 @@ class ExtractPlayblast(publish.Extractor):
capture_preset = {}
profiles = None
def _capture(self, preset):
if os.environ.get("OPENPYPE_DEBUG") == "1":
self.log.debug(
"Using preset: {}".format(
json.dumps(preset, indent=4, sort_keys=True)
)
)
path = capture.capture(log=self.log, **preset)
self.log.debug("playblast path {}".format(path))
def process(self, instance):
self.log.debug("Extracting capture..")
self.log.debug("Extracting playblast..")
# get scene fps
fps = instance.data.get("fps") or instance.context.data.get("fps")
@ -63,10 +39,6 @@ class ExtractPlayblast(publish.Extractor):
end = cmds.playbackOptions(query=True, animationEndTime=True)
self.log.debug("start: {}, end: {}".format(start, end))
# get cameras
camera = instance.data["review_camera"]
task_data = instance.data["anatomyData"].get("task", {})
capture_preset = lib.get_capture_preset(
task_data.get("name"),
@ -75,174 +47,35 @@ class ExtractPlayblast(publish.Extractor):
instance.context.data["project_settings"],
self.log
)
preset = lib.load_capture_preset(data=capture_preset)
# "isolate_view" will already have been applied at creation, so we'll
# ignore it here.
preset.pop("isolate_view")
# Set resolution variables from capture presets
width_preset = capture_preset["Resolution"]["width"]
height_preset = capture_preset["Resolution"]["height"]
# Set resolution variables from asset values
asset_data = instance.data["assetEntity"]["data"]
asset_width = asset_data.get("resolutionWidth")
asset_height = asset_data.get("resolutionHeight")
review_instance_width = instance.data.get("review_width")
review_instance_height = instance.data.get("review_height")
preset["camera"] = camera
# Tests if project resolution is set,
# if it is a value other than zero, that value is
# used, if not then the asset resolution is
# used
if review_instance_width and review_instance_height:
preset["width"] = review_instance_width
preset["height"] = review_instance_height
elif width_preset and height_preset:
preset["width"] = width_preset
preset["height"] = height_preset
elif asset_width and asset_height:
preset["width"] = asset_width
preset["height"] = asset_height
preset["start_frame"] = start
preset["end_frame"] = end
# Enforce persisting camera depth of field
camera_options = preset.setdefault("camera_options", {})
camera_options["depthOfField"] = cmds.getAttr(
"{0}.depthOfField".format(camera))
stagingdir = self.staging_dir(instance)
filename = "{0}".format(instance.name)
filename = instance.name
path = os.path.join(stagingdir, filename)
self.log.debug("Outputting images to %s" % path)
# get cameras
camera = instance.data["review_camera"]
preset = lib.generate_capture_preset(
instance, camera, path,
start=start, end=end,
capture_preset=capture_preset)
lib.render_capture_preset(preset)
preset["filename"] = path
preset["overwrite"] = True
cmds.refresh(force=True)
refreshFrameInt = int(cmds.playbackOptions(q=True, minTime=True))
cmds.currentTime(refreshFrameInt - 1, edit=True)
cmds.currentTime(refreshFrameInt, edit=True)
# Use displayLights setting from instance
key = "displayLights"
preset["viewport_options"][key] = instance.data[key]
# Override transparency if requested.
transparency = instance.data.get("transparency", 0)
if transparency != 0:
preset["viewport2_options"]["transparencyAlgorithm"] = transparency
# Isolate view is requested by having objects in the set besides a
# camera. If there is only 1 member it'll be the camera because we
# validate to have 1 camera only.
if instance.data["isolate"] and len(instance.data["setMembers"]) > 1:
preset["isolate"] = instance.data["setMembers"]
# Show/Hide image planes on request.
image_plane = instance.data.get("imagePlane", True)
if "viewport_options" in preset:
preset["viewport_options"]["imagePlane"] = image_plane
else:
preset["viewport_options"] = {"imagePlane": image_plane}
# Disable Pan/Zoom.
pan_zoom = cmds.getAttr("{}.panZoomEnabled".format(preset["camera"]))
preset.pop("pan_zoom", None)
preset["camera_options"]["panZoomEnabled"] = instance.data["panZoom"]
# Need to explicitly enable some viewport changes so the viewport is
# refreshed ahead of playblasting.
keys = [
"useDefaultMaterial",
"wireframeOnShaded",
"xray",
"jointXray",
"backfaceCulling"
]
viewport_defaults = {}
for key in keys:
viewport_defaults[key] = cmds.modelEditor(
instance.data["panel"], query=True, **{key: True}
)
if preset["viewport_options"][key]:
cmds.modelEditor(
instance.data["panel"], edit=True, **{key: True}
)
override_viewport_options = (
capture_preset["Viewport Options"]["override_viewport_options"]
)
# Force viewer to False in call to capture because we have our own
# viewer opening call to allow a signal to trigger between
# playblast and viewer
preset["viewer"] = False
# Update preset with current panel setting
# if override_viewport_options is turned off
if not override_viewport_options:
panel_preset = capture.parse_view(instance.data["panel"])
panel_preset.pop("camera")
preset.update(panel_preset)
# Need to ensure Python 2 compatibility.
# TODO: Remove once dropping Python 2.
if getattr(contextlib, "nested", None):
# Python 3 compatibility.
with contextlib.nested(
lib.maintained_time(),
panel_camera(instance.data["panel"], preset["camera"])
):
self._capture(preset)
else:
# Python 2 compatibility.
with contextlib.ExitStack() as stack:
stack.enter_context(lib.maintained_time())
stack.enter_context(
panel_camera(instance.data["panel"], preset["camera"])
)
self._capture(preset)
# Restoring viewport options.
if viewport_defaults:
cmds.modelEditor(
instance.data["panel"], edit=True, **viewport_defaults
)
try:
cmds.setAttr(
"{}.panZoomEnabled".format(preset["camera"]), pan_zoom)
except RuntimeError:
self.log.warning("Cannot restore Pan/Zoom settings.")
# Find playblast sequence
collected_files = os.listdir(stagingdir)
patterns = [clique.PATTERNS["frames"]]
collections, remainder = clique.assemble(collected_files,
minimum_items=1,
patterns=patterns)
filename = preset.get("filename", "%TEMP%")
self.log.debug("filename {}".format(filename))
self.log.debug("Searching playblast collection for: %s", path)
frame_collection = None
for collection in collections:
filebase = collection.format("{head}").rstrip(".")
self.log.debug("collection head {}".format(filebase))
if filebase in filename:
self.log.debug("Checking collection head: %s", filebase)
if filebase in path:
frame_collection = collection
self.log.debug(
"we found collection of interest {}".format(
str(frame_collection)))
if "representations" not in instance.data:
instance.data["representations"] = []
"Found playblast collection: %s", frame_collection
)
tags = ["review"]
if not instance.data.get("keepImages"):
@ -256,6 +89,9 @@ class ExtractPlayblast(publish.Extractor):
if len(collected_files) == 1:
collected_files = collected_files[0]
if "representations" not in instance.data:
instance.data["representations"] = []
representation = {
"name": capture_preset["Codec"]["compression"],
"ext": capture_preset["Codec"]["compression"],

View file

@ -1,15 +1,10 @@
import os
import glob
import tempfile
import json
import capture
from openpype.pipeline import publish
from openpype.hosts.maya.api import lib
from maya import cmds
class ExtractThumbnail(publish.Extractor):
"""Extract viewport thumbnail.
@ -24,7 +19,7 @@ class ExtractThumbnail(publish.Extractor):
families = ["review"]
def process(self, instance):
self.log.debug("Extracting capture..")
self.log.debug("Extracting thumbnail..")
camera = instance.data["review_camera"]
@ -37,20 +32,24 @@ class ExtractThumbnail(publish.Extractor):
self.log
)
preset = lib.load_capture_preset(data=capture_preset)
# "isolate_view" will already have been applied at creation, so we'll
# ignore it here.
preset.pop("isolate_view")
override_viewport_options = (
capture_preset["Viewport Options"]["override_viewport_options"]
# Create temp directory for thumbnail
# - this is to avoid "override" of source file
dst_staging = tempfile.mkdtemp(prefix="pyblish_tmp_thumbnail")
self.log.debug(
"Create temp directory {} for thumbnail".format(dst_staging)
)
# Store new staging to cleanup paths
filename = instance.name
path = os.path.join(dst_staging, filename)
preset["camera"] = camera
preset["start_frame"] = instance.data["frameStart"]
preset["end_frame"] = instance.data["frameStart"]
preset["camera_options"] = {
self.log.debug("Outputting images to %s" % path)
preset = lib.generate_capture_preset(
instance, camera, path,
start=1, end=1,
capture_preset=capture_preset)
preset["camera_options"].update({
"displayGateMask": False,
"displayResolution": False,
"displayFilmGate": False,
@ -60,101 +59,10 @@ class ExtractThumbnail(publish.Extractor):
"displayFilmPivot": False,
"displayFilmOrigin": False,
"overscan": 1.0,
"depthOfField": cmds.getAttr("{0}.depthOfField".format(camera)),
}
# Set resolution variables from capture presets
width_preset = capture_preset["Resolution"]["width"]
height_preset = capture_preset["Resolution"]["height"]
# Set resolution variables from asset values
asset_data = instance.data["assetEntity"]["data"]
asset_width = asset_data.get("resolutionWidth")
asset_height = asset_data.get("resolutionHeight")
review_instance_width = instance.data.get("review_width")
review_instance_height = instance.data.get("review_height")
# Tests if project resolution is set,
# if it is a value other than zero, that value is
# used, if not then the asset resolution is
# used
if review_instance_width and review_instance_height:
preset["width"] = review_instance_width
preset["height"] = review_instance_height
elif width_preset and height_preset:
preset["width"] = width_preset
preset["height"] = height_preset
elif asset_width and asset_height:
preset["width"] = asset_width
preset["height"] = asset_height
})
path = lib.render_capture_preset(preset)
# Create temp directory for thumbnail
# - this is to avoid "override" of source file
dst_staging = tempfile.mkdtemp(prefix="pyblish_tmp_")
self.log.debug(
"Create temp directory {} for thumbnail".format(dst_staging)
)
# Store new staging to cleanup paths
filename = "{0}".format(instance.name)
path = os.path.join(dst_staging, filename)
self.log.debug("Outputting images to %s" % path)
preset["filename"] = path
preset["overwrite"] = True
cmds.refresh(force=True)
refreshFrameInt = int(cmds.playbackOptions(q=True, minTime=True))
cmds.currentTime(refreshFrameInt - 1, edit=True)
cmds.currentTime(refreshFrameInt, edit=True)
# Use displayLights setting from instance
key = "displayLights"
preset["viewport_options"][key] = instance.data[key]
# Override transparency if requested.
transparency = instance.data.get("transparency", 0)
if transparency != 0:
preset["viewport2_options"]["transparencyAlgorithm"] = transparency
# Isolate view is requested by having objects in the set besides a
# camera. If there is only 1 member it'll be the camera because we
# validate to have 1 camera only.
if instance.data["isolate"] and len(instance.data["setMembers"]) > 1:
preset["isolate"] = instance.data["setMembers"]
# Show or Hide Image Plane
image_plane = instance.data.get("imagePlane", True)
if "viewport_options" in preset:
preset["viewport_options"]["imagePlane"] = image_plane
else:
preset["viewport_options"] = {"imagePlane": image_plane}
# Disable Pan/Zoom.
preset.pop("pan_zoom", None)
preset["camera_options"]["panZoomEnabled"] = instance.data["panZoom"]
with lib.maintained_time():
# Force viewer to False in call to capture because we have our own
# viewer opening call to allow a signal to trigger between
# playblast and viewer
preset["viewer"] = False
# Update preset with current panel setting
# if override_viewport_options is turned off
panel = cmds.getPanel(withFocus=True) or ""
if not override_viewport_options and "modelPanel" in panel:
panel_preset = capture.parse_active_view()
preset.update(panel_preset)
cmds.setFocus(panel)
if os.environ.get("OPENPYPE_DEBUG") == "1":
self.log.debug(
"Using preset: {}".format(
json.dumps(preset, indent=4, sort_keys=True)
)
)
path = capture.capture(**preset)
playblast = self._fix_playblast_output_path(path)
playblast = self._fix_playblast_output_path(path)
_, thumbnail = os.path.split(playblast)

View file

@ -34,6 +34,11 @@ class ExtractReviewIntermediates(publish.Extractor):
nuke_publish = project_settings["nuke"]["publish"]
deprecated_setting = nuke_publish["ExtractReviewDataMov"]
current_setting = nuke_publish.get("ExtractReviewIntermediates")
if not deprecated_setting["enabled"] and (
not current_setting["enabled"]
):
cls.enabled = False
if deprecated_setting["enabled"]:
# Use deprecated settings if they are still enabled
cls.viewer_lut_raw = deprecated_setting["viewer_lut_raw"]

View file

@ -231,7 +231,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
job_info.EnvironmentKeyValue["OPENPYPE_LOG_NO_COLORS"] = "1"
# Adding file dependencies.
if self.asset_dependencies:
if not bool(os.environ.get("IS_TEST")) and self.asset_dependencies:
dependencies = instance.context.data["fileDependencies"]
for dependency in dependencies:
job_info.AssetDependency += dependency
@ -570,7 +570,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
job_info = copy.deepcopy(self.job_info)
if self.asset_dependencies:
if not bool(os.environ.get("IS_TEST")) and self.asset_dependencies:
# Asset dependency to wait for at least the scene file to sync.
job_info.AssetDependency += self.scene_path

View file

@ -297,7 +297,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin,
job_index)] = assembly_id # noqa: E501
job_index += 1
elif instance.data.get("bakingSubmissionJobs"):
self.log.info("Adding baking submission jobs as dependencies...")
self.log.info(
"Adding baking submission jobs as dependencies..."
)
job_index = 0
for assembly_id in instance.data["bakingSubmissionJobs"]:
payload["JobInfo"]["JobDependency{}".format(

View file

@ -13,7 +13,7 @@ class DJVViewAction(BaseAction):
description = "DJV View Launcher"
icon = statics_icon("app_icons", "djvView.png")
type = 'Application'
type = "Application"
allowed_types = [
"cin", "dpx", "avi", "dv", "gif", "flv", "mkv", "mov", "mpg", "mpeg",
@ -60,7 +60,7 @@ class DJVViewAction(BaseAction):
return False
def interface(self, session, entities, event):
if event['data'].get('values', {}):
if event["data"].get("values", {}):
return
entity = entities[0]
@ -70,32 +70,32 @@ class DJVViewAction(BaseAction):
if entity_type == "assetversion":
if (
entity[
'components'
][0]['file_type'][1:] in self.allowed_types
"components"
][0]["file_type"][1:] in self.allowed_types
):
versions.append(entity)
else:
master_entity = entity
if entity_type == "task":
master_entity = entity['parent']
master_entity = entity["parent"]
for asset in master_entity['assets']:
for version in asset['versions']:
for asset in master_entity["assets"]:
for version in asset["versions"]:
# Get only AssetVersion of selected task
if (
entity_type == "task" and
version['task']['id'] != entity['id']
version["task"]["id"] != entity["id"]
):
continue
# Get only components with allowed type
filetype = version['components'][0]['file_type']
filetype = version["components"][0]["file_type"]
if filetype[1:] in self.allowed_types:
versions.append(version)
if len(versions) < 1:
return {
'success': False,
'message': 'There are no Asset Versions to open.'
"success": False,
"message": "There are no Asset Versions to open."
}
# TODO sort them (somehow?)
@ -134,68 +134,68 @@ class DJVViewAction(BaseAction):
last_available = None
select_value = None
for version in versions:
for component in version['components']:
for component in version["components"]:
label = base_label.format(
str(version['version']).zfill(3),
version['asset']['type']['name'],
component['name']
str(version["version"]).zfill(3),
version["asset"]["type"]["name"],
component["name"]
)
try:
location = component[
'component_locations'
][0]['location']
"component_locations"
][0]["location"]
file_path = location.get_filesystem_path(component)
except Exception:
file_path = component[
'component_locations'
][0]['resource_identifier']
"component_locations"
][0]["resource_identifier"]
if os.path.isdir(os.path.dirname(file_path)):
last_available = file_path
if component['name'] == default_component:
if component["name"] == default_component:
select_value = file_path
version_items.append(
{'label': label, 'value': file_path}
{"label": label, "value": file_path}
)
if len(version_items) == 0:
return {
'success': False,
'message': (
'There are no Asset Versions with accessible path.'
"success": False,
"message": (
"There are no Asset Versions with accessible path."
)
}
item = {
'label': 'Items to view',
'type': 'enumerator',
'name': 'path',
'data': sorted(
"label": "Items to view",
"type": "enumerator",
"name": "path",
"data": sorted(
version_items,
key=itemgetter('label'),
key=itemgetter("label"),
reverse=True
)
}
if select_value is not None:
item['value'] = select_value
item["value"] = select_value
else:
item['value'] = last_available
item["value"] = last_available
items.append(item)
return {'items': items}
return {"items": items}
def launch(self, session, entities, event):
"""Callback method for DJVView action."""
# Launching application
event_data = event["data"]
if "values" not in event_data:
event_values = event["data"].get("values")
if not event_values:
return
djv_app_name = event_data["djv_app_name"]
app = self.applicaion_manager.applications.get(djv_app_name)
djv_app_name = event_values["djv_app_name"]
app = self.application_manager.applications.get(djv_app_name)
executable = None
if app is not None:
executable = app.find_executable()
@ -206,18 +206,21 @@ class DJVViewAction(BaseAction):
"message": "Couldn't find DJV executable."
}
filpath = os.path.normpath(event_data["values"]["path"])
filpath = os.path.normpath(event_values["path"])
cmd = [
# DJV path
executable,
str(executable),
# PATH TO COMPONENT
filpath
]
try:
# Run DJV with these commands
subprocess.Popen(cmd)
_process = subprocess.Popen(cmd)
# Keep process in memory for some time
time.sleep(0.1)
except FileNotFoundError:
return {
"success": False,

View file

@ -1289,6 +1289,7 @@
"twoSidedLighting": true,
"lineAAEnable": true,
"multiSample": 8,
"loadTextures": false,
"useDefaultMaterial": false,
"wireframeOnShaded": false,
"xray": false,

View file

@ -236,6 +236,11 @@
{
"type": "splitter"
},
{
"type": "boolean",
"key": "loadTextures",
"label": "Load Textures"
},
{
"type": "boolean",
"key": "useDefaultMaterial",
@ -908,6 +913,12 @@
{
"type": "splitter"
},
{
"type": "boolean",
"key": "loadTextures",
"label": "Load Textures",
"default": false
},
{
"type": "boolean",
"key": "useDefaultMaterial",

View file

@ -606,7 +606,7 @@ class PublishWorkfilesModel:
print("Failed to format workfile path: {}".format(exc))
dirpath, filename = os.path.split(workfile_path)
created_at = arrow.get(repre_entity["createdAt"].to("local"))
created_at = arrow.get(repre_entity["createdAt"]).to("local")
return FileItem(
dirpath,
filename,

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring Pype version."""
__version__ = "3.18.2-nightly.5"
__version__ = "3.18.2"

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "OpenPype"
version = "3.18.1" # OpenPype
version = "3.18.2" # OpenPype
description = "Open VFX and Animation pipeline with support."
authors = ["OpenPype Team <info@openpype.io>"]
license = "MIT License"

View file

@ -108,6 +108,7 @@ class ViewportOptionsSetting(BaseSettingsModel):
True, title="Enable Anti-Aliasing", section="Anti-Aliasing"
)
multiSample: int = Field(8, title="Anti Aliasing Samples")
loadTextures: bool = Field(False, title="Load Textures")
useDefaultMaterial: bool = Field(False, title="Use Default Material")
wireframeOnShaded: bool = Field(False, title="Wireframe On Shaded")
xray: bool = Field(False, title="X-Ray")
@ -302,6 +303,7 @@ DEFAULT_PLAYBLAST_SETTING = {
"twoSidedLighting": True,
"lineAAEnable": True,
"multiSample": 8,
"loadTextures": False,
"useDefaultMaterial": False,
"wireframeOnShaded": False,
"xray": False,

View file

@ -185,7 +185,7 @@ createNode objectSet -n "modelMain";
addAttr -ci true -sn "attrPrefix" -ln "attrPrefix" -dt "string";
addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string";
addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string";
addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys"
addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys"
-dt "string";
setAttr ".ihi" 0;
setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:7364ea6776c9";
@ -296,7 +296,7 @@ createNode objectSet -n "workfileMain";
addAttr -ci true -sn "task" -ln "task" -dt "string";
addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string";
addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string";
addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys"
addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys"
-dt "string";
setAttr ".ihi" 0;
setAttr ".hio" yes;

View file

@ -185,7 +185,7 @@ createNode objectSet -n "modelMain";
addAttr -ci true -sn "attrPrefix" -ln "attrPrefix" -dt "string";
addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string";
addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string";
addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys"
addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys"
-dt "string";
setAttr ".ihi" 0;
setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:7364ea6776c9";
@ -296,7 +296,7 @@ createNode objectSet -n "workfileMain";
addAttr -ci true -sn "task" -ln "task" -dt "string";
addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string";
addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string";
addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys"
addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys"
-dt "string";
setAttr ".ihi" 0;
setAttr ".hio" yes;

View file

@ -185,7 +185,7 @@ createNode objectSet -n "modelMain";
addAttr -ci true -sn "attrPrefix" -ln "attrPrefix" -dt "string";
addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string";
addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string";
addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys"
addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys"
-dt "string";
setAttr ".ihi" 0;
setAttr ".cbId" -type "string" "60df31e2be2b48bd3695c056:7364ea6776c9";
@ -296,7 +296,7 @@ createNode objectSet -n "workfileMain";
addAttr -ci true -sn "task" -ln "task" -dt "string";
addAttr -ci true -sn "publish_attributes" -ln "publish_attributes" -dt "string";
addAttr -ci true -sn "creator_attributes" -ln "creator_attributes" -dt "string";
addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys"
addAttr -ci true -sn "__creator_attributes_keys" -ln "__creator_attributes_keys"
-dt "string";
setAttr ".ihi" 0;
setAttr ".hio" yes;