Merge pull request #5942 from ynput/bugfix/OP-7281_Maya-Review---playblast-renders-without-textures

Maya: bug fix the playblast without textures
This commit is contained in:
Kayla Man 2024-01-04 00:03:11 +08:00 committed by GitHub
commit 5e48d24241
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 405 additions and 297 deletions

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

@ -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

@ -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,