Merge remote-tracking branch 'origin/develop' into feature/royalrender-integration
28
CHANGELOG.md
|
|
@ -1,6 +1,6 @@
|
|||
# Changelog
|
||||
|
||||
## [3.6.0-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
## [3.6.0-nightly.5](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.5.0...HEAD)
|
||||
|
||||
|
|
@ -9,10 +9,17 @@
|
|||
- Maya : Colorspace configuration [\#2170](https://github.com/pypeclub/OpenPype/pull/2170)
|
||||
- Blender: Added support for audio [\#2168](https://github.com/pypeclub/OpenPype/pull/2168)
|
||||
- Flame: a host basic integration [\#2165](https://github.com/pypeclub/OpenPype/pull/2165)
|
||||
- Houdini: simple HDA workflow [\#2072](https://github.com/pypeclub/OpenPype/pull/2072)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Add both side availability on Site Sync sites to Loader [\#2220](https://github.com/pypeclub/OpenPype/pull/2220)
|
||||
- Tools: Center loader and library loader on show [\#2219](https://github.com/pypeclub/OpenPype/pull/2219)
|
||||
- Maya : Validate shape zero [\#2212](https://github.com/pypeclub/OpenPype/pull/2212)
|
||||
- Maya : validate unique names [\#2211](https://github.com/pypeclub/OpenPype/pull/2211)
|
||||
- Tools: OpenPype stylesheet in workfiles tool [\#2208](https://github.com/pypeclub/OpenPype/pull/2208)
|
||||
- Ftrack: Replace Queue with deque in event handlers logic [\#2204](https://github.com/pypeclub/OpenPype/pull/2204)
|
||||
- Tools: New select context dialog [\#2200](https://github.com/pypeclub/OpenPype/pull/2200)
|
||||
- Maya : Validate mesh ngons [\#2199](https://github.com/pypeclub/OpenPype/pull/2199)
|
||||
- Delivery: Check 'frame' key in template for sequence delivery [\#2196](https://github.com/pypeclub/OpenPype/pull/2196)
|
||||
- Usage of tools code [\#2185](https://github.com/pypeclub/OpenPype/pull/2185)
|
||||
|
|
@ -21,11 +28,11 @@
|
|||
- Tools: Experimental tools [\#2167](https://github.com/pypeclub/OpenPype/pull/2167)
|
||||
- Loader: Refactor and use OpenPype stylesheets [\#2166](https://github.com/pypeclub/OpenPype/pull/2166)
|
||||
- Add loader for linked smart objects in photoshop [\#2149](https://github.com/pypeclub/OpenPype/pull/2149)
|
||||
- Burnins: DNxHD profiles handling [\#2142](https://github.com/pypeclub/OpenPype/pull/2142)
|
||||
- Tools: Single access point for host tools [\#2139](https://github.com/pypeclub/OpenPype/pull/2139)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Maya : multiple subsets review broken [\#2210](https://github.com/pypeclub/OpenPype/pull/2210)
|
||||
- Fix - different command used for Linux and Mac OS [\#2207](https://github.com/pypeclub/OpenPype/pull/2207)
|
||||
- Tools: Workfiles tool don't use avalon widgets [\#2205](https://github.com/pypeclub/OpenPype/pull/2205)
|
||||
- Ftrack: Fill missing ftrack id on mongo project [\#2203](https://github.com/pypeclub/OpenPype/pull/2203)
|
||||
- Project Manager: Fix copying of tasks [\#2191](https://github.com/pypeclub/OpenPype/pull/2191)
|
||||
|
|
@ -34,10 +41,7 @@
|
|||
- MacOS: Launching of applications may cause Permissions error [\#2175](https://github.com/pypeclub/OpenPype/pull/2175)
|
||||
- Maya: Aspect ratio [\#2174](https://github.com/pypeclub/OpenPype/pull/2174)
|
||||
- Blender: Fix 'Deselect All' with object not in 'Object Mode' [\#2163](https://github.com/pypeclub/OpenPype/pull/2163)
|
||||
- Tools: Stylesheets are applied after tool show [\#2161](https://github.com/pypeclub/OpenPype/pull/2161)
|
||||
- Maya: Collect render - fix UNC path support 🐛 [\#2158](https://github.com/pypeclub/OpenPype/pull/2158)
|
||||
- Maya: Fix hotbox broken by scriptsmenu [\#2151](https://github.com/pypeclub/OpenPype/pull/2151)
|
||||
- Ftrack: Ignore save warnings exception in Prepare project action [\#2150](https://github.com/pypeclub/OpenPype/pull/2150)
|
||||
- Added validator for source files for Standalone Publisher [\#2138](https://github.com/pypeclub/OpenPype/pull/2138)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
|
@ -45,7 +49,6 @@
|
|||
- Settings: Site sync project settings improvement [\#2193](https://github.com/pypeclub/OpenPype/pull/2193)
|
||||
- Add validate active site button to sync queue on a project [\#2176](https://github.com/pypeclub/OpenPype/pull/2176)
|
||||
- Bump pillow from 8.2.0 to 8.3.2 [\#2162](https://github.com/pypeclub/OpenPype/pull/2162)
|
||||
- Bump axios from 0.21.1 to 0.21.4 in /website [\#2059](https://github.com/pypeclub/OpenPype/pull/2059)
|
||||
|
||||
## [3.5.0](https://github.com/pypeclub/OpenPype/tree/3.5.0) (2021-10-17)
|
||||
|
||||
|
|
@ -62,8 +65,6 @@
|
|||
- PYPE-1218 - changed namespace to contain subset name in Maya [\#2114](https://github.com/pypeclub/OpenPype/pull/2114)
|
||||
- Added running configurable disk mapping command before start of OP [\#2091](https://github.com/pypeclub/OpenPype/pull/2091)
|
||||
- SFTP provider [\#2073](https://github.com/pypeclub/OpenPype/pull/2073)
|
||||
- Houdini: simple HDA workflow [\#2072](https://github.com/pypeclub/OpenPype/pull/2072)
|
||||
- Maya: Validate setdress top group [\#2068](https://github.com/pypeclub/OpenPype/pull/2068)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
|
|
@ -79,8 +80,6 @@
|
|||
- Global: add global validators to settings [\#2078](https://github.com/pypeclub/OpenPype/pull/2078)
|
||||
- Use CRF for burnin when available [\#2070](https://github.com/pypeclub/OpenPype/pull/2070)
|
||||
- Project manager: Filter first item after selection of project [\#2069](https://github.com/pypeclub/OpenPype/pull/2069)
|
||||
- Nuke: Adding `still` image family workflow [\#2064](https://github.com/pypeclub/OpenPype/pull/2064)
|
||||
- Maya: validate authorized loaded plugins [\#2062](https://github.com/pypeclub/OpenPype/pull/2062)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
|
|
@ -102,8 +101,6 @@
|
|||
- Deadline: Collect deadline server does not check existence of deadline key [\#2082](https://github.com/pypeclub/OpenPype/pull/2082)
|
||||
- Blender: fixed Curves with modifiers in Rigs [\#2081](https://github.com/pypeclub/OpenPype/pull/2081)
|
||||
- Nuke UI scaling [\#2077](https://github.com/pypeclub/OpenPype/pull/2077)
|
||||
- Maya: Fix multi-camera renders [\#2065](https://github.com/pypeclub/OpenPype/pull/2065)
|
||||
- Fix Sync Queue when project disabled [\#2063](https://github.com/pypeclub/OpenPype/pull/2063)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
|
|
@ -113,11 +110,6 @@
|
|||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.4.1-nightly.1...3.4.1)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Timers manger: Typo fix [\#2058](https://github.com/pypeclub/OpenPype/pull/2058)
|
||||
- Hiero: Editorial fixes [\#2057](https://github.com/pypeclub/OpenPype/pull/2057)
|
||||
|
||||
## [3.4.0](https://github.com/pypeclub/OpenPype/tree/3.4.0) (2021-09-17)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.4.0-nightly.6...3.4.0)
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ def publish(debug, paths, targets, gui):
|
|||
@click.option("-p", "--project", help="Project")
|
||||
@click.option("-t", "--targets", help="Targets", default=None,
|
||||
multiple=True)
|
||||
def remotepublishfromapp(debug, project, path, host, targets=None, user=None):
|
||||
def remotepublishfromapp(debug, project, path, host, user=None, targets=None):
|
||||
"""Start CLI publishing.
|
||||
|
||||
Publish collects json from paths provided as an argument.
|
||||
|
|
@ -176,18 +176,19 @@ def remotepublishfromapp(debug, project, path, host, targets=None, user=None):
|
|||
"""
|
||||
if debug:
|
||||
os.environ['OPENPYPE_DEBUG'] = '3'
|
||||
PypeCommands.remotepublishfromapp(project, path, host, user,
|
||||
targets=targets)
|
||||
PypeCommands.remotepublishfromapp(
|
||||
project, path, host, user, targets=targets
|
||||
)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("path")
|
||||
@click.option("-d", "--debug", is_flag=True, help="Print debug messages")
|
||||
@click.option("-h", "--host", help="Host")
|
||||
@click.option("-u", "--user", help="User email address")
|
||||
@click.option("-p", "--project", help="Project")
|
||||
@click.option("-t", "--targets", help="Targets", default=None,
|
||||
multiple=True)
|
||||
def remotepublish(debug, project, path, host, targets=None, user=None):
|
||||
def remotepublish(debug, project, path, user=None, targets=None):
|
||||
"""Start CLI publishing.
|
||||
|
||||
Publish collects json from paths provided as an argument.
|
||||
|
|
@ -195,7 +196,7 @@ def remotepublish(debug, project, path, host, targets=None, user=None):
|
|||
"""
|
||||
if debug:
|
||||
os.environ['OPENPYPE_DEBUG'] = '3'
|
||||
PypeCommands.remotepublish(project, path, host, user, targets=targets)
|
||||
PypeCommands.remotepublish(project, path, user, targets=targets)
|
||||
|
||||
|
||||
@main.command()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from pyblish import api as pyblish
|
|||
from openpype.lib import any_outdated
|
||||
import openpype.hosts.maya
|
||||
from openpype.hosts.maya.lib import copy_workspace_mel
|
||||
from openpype.lib.path_tools import HostDirmap
|
||||
from . import menu, lib
|
||||
|
||||
log = logging.getLogger("openpype.hosts.maya")
|
||||
|
|
@ -30,7 +31,8 @@ def install():
|
|||
|
||||
project_settings = get_project_settings(os.getenv("AVALON_PROJECT"))
|
||||
# process path mapping
|
||||
process_dirmap(project_settings)
|
||||
dirmap_processor = MayaDirmap("maya", project_settings)
|
||||
dirmap_processor.process_dirmap()
|
||||
|
||||
pyblish.register_plugin_path(PUBLISH_PATH)
|
||||
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
|
||||
|
|
@ -60,110 +62,6 @@ def install():
|
|||
avalon.data["familiesStateToggled"] = ["imagesequence"]
|
||||
|
||||
|
||||
def process_dirmap(project_settings):
|
||||
# type: (dict) -> None
|
||||
"""Go through all paths in Settings and set them using `dirmap`.
|
||||
|
||||
If artists has Site Sync enabled, take dirmap mapping directly from
|
||||
Local Settings when artist is syncing workfile locally.
|
||||
|
||||
Args:
|
||||
project_settings (dict): Settings for current project.
|
||||
|
||||
"""
|
||||
local_mapping = _get_local_sync_dirmap(project_settings)
|
||||
if not project_settings["maya"].get("maya-dirmap") and not local_mapping:
|
||||
return
|
||||
|
||||
mapping = local_mapping or \
|
||||
project_settings["maya"]["maya-dirmap"]["paths"] \
|
||||
or {}
|
||||
mapping_enabled = project_settings["maya"]["maya-dirmap"]["enabled"] \
|
||||
or bool(local_mapping)
|
||||
|
||||
if not mapping or not mapping_enabled:
|
||||
return
|
||||
if mapping.get("source-path") and mapping_enabled is True:
|
||||
log.info("Processing directory mapping ...")
|
||||
cmds.dirmap(en=True)
|
||||
for k, sp in enumerate(mapping["source-path"]):
|
||||
try:
|
||||
print("{} -> {}".format(sp, mapping["destination-path"][k]))
|
||||
cmds.dirmap(m=(sp, mapping["destination-path"][k]))
|
||||
cmds.dirmap(m=(mapping["destination-path"][k], sp))
|
||||
except IndexError:
|
||||
# missing corresponding destination path
|
||||
log.error(("invalid dirmap mapping, missing corresponding"
|
||||
" destination directory."))
|
||||
break
|
||||
except RuntimeError:
|
||||
log.error("invalid path {} -> {}, mapping not registered".format(
|
||||
sp, mapping["destination-path"][k]
|
||||
))
|
||||
continue
|
||||
|
||||
|
||||
def _get_local_sync_dirmap(project_settings):
|
||||
"""
|
||||
Returns dirmap if synch to local project is enabled.
|
||||
|
||||
Only valid mapping is from roots of remote site to local site set in
|
||||
Local Settings.
|
||||
|
||||
Args:
|
||||
project_settings (dict)
|
||||
Returns:
|
||||
dict : { "source-path": [XXX], "destination-path": [YYYY]}
|
||||
"""
|
||||
import json
|
||||
mapping = {}
|
||||
|
||||
if not project_settings["global"]["sync_server"]["enabled"]:
|
||||
log.debug("Site Sync not enabled")
|
||||
return mapping
|
||||
|
||||
from openpype.settings.lib import get_site_local_overrides
|
||||
from openpype.modules import ModulesManager
|
||||
|
||||
manager = ModulesManager()
|
||||
sync_module = manager.modules_by_name["sync_server"]
|
||||
|
||||
project_name = os.getenv("AVALON_PROJECT")
|
||||
sync_settings = sync_module.get_sync_project_setting(
|
||||
os.getenv("AVALON_PROJECT"), exclude_locals=False, cached=False)
|
||||
log.debug(json.dumps(sync_settings, indent=4))
|
||||
|
||||
active_site = sync_module.get_local_normalized_site(
|
||||
sync_module.get_active_site(project_name))
|
||||
remote_site = sync_module.get_local_normalized_site(
|
||||
sync_module.get_remote_site(project_name))
|
||||
log.debug("active {} - remote {}".format(active_site, remote_site))
|
||||
|
||||
if active_site == "local" \
|
||||
and project_name in sync_module.get_enabled_projects()\
|
||||
and active_site != remote_site:
|
||||
overrides = get_site_local_overrides(os.getenv("AVALON_PROJECT"),
|
||||
active_site)
|
||||
for root_name, value in overrides.items():
|
||||
if os.path.isdir(value):
|
||||
try:
|
||||
mapping["destination-path"] = [value]
|
||||
mapping["source-path"] = [sync_settings["sites"]\
|
||||
[remote_site]\
|
||||
["root"]\
|
||||
[root_name]]
|
||||
except IndexError:
|
||||
# missing corresponding destination path
|
||||
log.debug("overrides".format(overrides))
|
||||
log.error(
|
||||
("invalid dirmap mapping, missing corresponding"
|
||||
" destination directory."))
|
||||
break
|
||||
|
||||
log.debug("local sync mapping:: {}".format(mapping))
|
||||
return mapping
|
||||
|
||||
|
||||
def uninstall():
|
||||
pyblish.deregister_plugin_path(PUBLISH_PATH)
|
||||
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
|
||||
|
|
@ -326,3 +224,12 @@ def before_workfile_save(workfile_path):
|
|||
|
||||
workdir = os.path.dirname(workfile_path)
|
||||
copy_workspace_mel(workdir)
|
||||
|
||||
|
||||
class MayaDirmap(HostDirmap):
|
||||
def on_enable_dirmap(self):
|
||||
cmds.dirmap(en=True)
|
||||
|
||||
def dirmap_routine(self, source_path, destination_path):
|
||||
cmds.dirmap(m=(source_path, destination_path))
|
||||
cmds.dirmap(m=(destination_path, source_path))
|
||||
|
|
|
|||
|
|
@ -2183,10 +2183,11 @@ def load_capture_preset(data=None):
|
|||
for key in preset['Display Options']:
|
||||
if key.startswith('background'):
|
||||
disp_options[key] = preset['Display Options'][key]
|
||||
disp_options[key][0] = (float(disp_options[key][0])/255)
|
||||
disp_options[key][1] = (float(disp_options[key][1])/255)
|
||||
disp_options[key][2] = (float(disp_options[key][2])/255)
|
||||
disp_options[key].pop()
|
||||
if len(disp_options[key]) == 4:
|
||||
disp_options[key][0] = (float(disp_options[key][0])/255)
|
||||
disp_options[key][1] = (float(disp_options[key][1])/255)
|
||||
disp_options[key][2] = (float(disp_options[key][2])/255)
|
||||
disp_options[key].pop()
|
||||
else:
|
||||
disp_options['displayGradient'] = True
|
||||
|
||||
|
|
|
|||
|
|
@ -45,9 +45,12 @@ class ExtractPlayblast(openpype.api.Extractor):
|
|||
# get cameras
|
||||
camera = instance.data['review_camera']
|
||||
|
||||
override_viewport_options = (
|
||||
self.capture_preset['Viewport Options']
|
||||
['override_viewport_options']
|
||||
)
|
||||
preset = lib.load_capture_preset(data=self.capture_preset)
|
||||
|
||||
|
||||
preset['camera'] = camera
|
||||
preset['start_frame'] = start
|
||||
preset['end_frame'] = end
|
||||
|
|
@ -92,6 +95,12 @@ class ExtractPlayblast(openpype.api.Extractor):
|
|||
|
||||
self.log.info('using viewport preset: {}'.format(preset))
|
||||
|
||||
# Update preset with current panel setting
|
||||
# if override_viewport_options is turned off
|
||||
if not override_viewport_options:
|
||||
panel_preset = capture.parse_active_view()
|
||||
preset.update(panel_preset)
|
||||
|
||||
path = capture.capture(**preset)
|
||||
|
||||
self.log.debug("playblast path {}".format(path))
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ class ExtractThumbnail(openpype.api.Extractor):
|
|||
capture_preset = (
|
||||
instance.context.data["project_settings"]['maya']['publish']['ExtractPlayblast']['capture_preset']
|
||||
)
|
||||
override_viewport_options = (
|
||||
capture_preset['Viewport Options']['override_viewport_options']
|
||||
)
|
||||
|
||||
try:
|
||||
preset = lib.load_capture_preset(data=capture_preset)
|
||||
|
|
@ -86,6 +89,12 @@ class ExtractThumbnail(openpype.api.Extractor):
|
|||
# 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_active_view()
|
||||
preset.update(panel_preset)
|
||||
|
||||
path = capture.capture(**preset)
|
||||
playblast = self._fix_playblast_output_path(path)
|
||||
|
||||
|
|
|
|||
59
openpype/hosts/maya/plugins/publish/validate_shape_zero.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
from maya import cmds
|
||||
|
||||
import pyblish.api
|
||||
import openpype.api
|
||||
import openpype.hosts.maya.api.action
|
||||
|
||||
|
||||
class ValidateShapeZero(pyblish.api.Validator):
|
||||
"""shape can't have any values
|
||||
|
||||
To solve this issue, try freezing the shapes. So long
|
||||
as the translation, rotation and scaling values are zero,
|
||||
you're all good.
|
||||
|
||||
"""
|
||||
|
||||
order = openpype.api.ValidateContentsOrder
|
||||
hosts = ["maya"]
|
||||
families = ["model"]
|
||||
label = "Shape Zero (Freeze)"
|
||||
actions = [
|
||||
openpype.hosts.maya.api.action.SelectInvalidAction,
|
||||
openpype.api.RepairAction
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_invalid(instance):
|
||||
"""Returns the invalid shapes in the instance.
|
||||
|
||||
This is the same as checking:
|
||||
- all(pnt == [0,0,0] for pnt in shape.pnts[:])
|
||||
|
||||
Returns:
|
||||
list: Shape with non freezed vertex
|
||||
|
||||
"""
|
||||
|
||||
shapes = cmds.ls(instance, type="shape")
|
||||
|
||||
invalid = []
|
||||
for shape in shapes:
|
||||
if cmds.polyCollapseTweaks(shape, q=True, hasVertexTweaks=True):
|
||||
invalid.append(shape)
|
||||
|
||||
return invalid
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
invalid_shapes = cls.get_invalid(instance)
|
||||
for shape in invalid_shapes:
|
||||
cmds.polyCollapseTweaks(shape)
|
||||
|
||||
def process(self, instance):
|
||||
"""Process all the nodes in the instance "objectSet"""
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise ValueError("Nodes found with shape or vertices not freezed"
|
||||
"values: {0}".format(invalid))
|
||||
|
|
@ -24,6 +24,10 @@ from openpype.api import (
|
|||
ApplicationManager
|
||||
)
|
||||
from openpype.tools.utils import host_tools
|
||||
from openpype.lib.path_tools import HostDirmap
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.modules import ModulesManager
|
||||
|
||||
import nuke
|
||||
|
||||
from .utils import set_context_favorites
|
||||
|
|
@ -1795,3 +1799,69 @@ def recreate_instance(origin_node, avalon_data=None):
|
|||
dn.setInput(0, new_node)
|
||||
|
||||
return new_node
|
||||
|
||||
|
||||
class NukeDirmap(HostDirmap):
|
||||
def __init__(self, host_name, project_settings, sync_module, file_name):
|
||||
"""
|
||||
Args:
|
||||
host_name (str): Nuke
|
||||
project_settings (dict): settings of current project
|
||||
sync_module (SyncServerModule): to limit reinitialization
|
||||
file_name (str): full path of referenced file from workfiles
|
||||
"""
|
||||
self.host_name = host_name
|
||||
self.project_settings = project_settings
|
||||
self.file_name = file_name
|
||||
self.sync_module = sync_module
|
||||
|
||||
self._mapping = None # cache mapping
|
||||
|
||||
def on_enable_dirmap(self):
|
||||
pass
|
||||
|
||||
def dirmap_routine(self, source_path, destination_path):
|
||||
log.debug("{}: {}->{}".format(self.file_name,
|
||||
source_path, destination_path))
|
||||
source_path = source_path.lower().replace(os.sep, '/')
|
||||
destination_path = destination_path.lower().replace(os.sep, '/')
|
||||
if platform.system().lower() == "windows":
|
||||
self.file_name = self.file_name.lower().replace(
|
||||
source_path, destination_path)
|
||||
else:
|
||||
self.file_name = self.file_name.replace(
|
||||
source_path, destination_path)
|
||||
|
||||
|
||||
class DirmapCache:
|
||||
"""Caching class to get settings and sync_module easily and only once."""
|
||||
_project_settings = None
|
||||
_sync_module = None
|
||||
|
||||
@classmethod
|
||||
def project_settings(cls):
|
||||
if cls._project_settings is None:
|
||||
cls._project_settings = get_project_settings(
|
||||
os.getenv("AVALON_PROJECT"))
|
||||
return cls._project_settings
|
||||
|
||||
@classmethod
|
||||
def sync_module(cls):
|
||||
if cls._sync_module is None:
|
||||
cls._sync_module = ModulesManager().modules_by_name["sync_server"]
|
||||
return cls._sync_module
|
||||
|
||||
|
||||
def dirmap_file_name_filter(file_name):
|
||||
"""Nuke callback function with single full path argument.
|
||||
|
||||
Checks project settings for potential mapping from source to dest.
|
||||
"""
|
||||
dirmap_processor = NukeDirmap("nuke",
|
||||
DirmapCache.project_settings(),
|
||||
DirmapCache.sync_module(),
|
||||
file_name)
|
||||
dirmap_processor.process_dirmap()
|
||||
if os.path.exists(dirmap_processor.file_name):
|
||||
return dirmap_processor.file_name
|
||||
return file_name
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ from openpype.hosts.nuke.api.lib import (
|
|||
|
||||
import nuke
|
||||
from openpype.api import Logger
|
||||
from openpype.hosts.nuke.api.lib import dirmap_file_name_filter
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
|
||||
# fix ffmpeg settings on script
|
||||
nuke.addOnScriptLoad(on_script_load)
|
||||
|
||||
|
|
@ -20,4 +20,6 @@ nuke.addOnScriptSave(check_inventory_versions)
|
|||
# # set apply all workfile settings on script load and save
|
||||
nuke.addOnScriptLoad(WorkfileSettings().set_context_settings)
|
||||
|
||||
nuke.addFilenameFilter(dirmap_file_name_filter)
|
||||
|
||||
log.info('Automatic syncing of write file knob to script version')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
"""Loads batch context from json and continues in publish process.
|
||||
|
||||
Provides:
|
||||
context -> Loaded batch file.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import pyblish.api
|
||||
from avalon import io
|
||||
from openpype.lib.plugin_tools import (
|
||||
parse_json,
|
||||
get_batch_asset_task_info
|
||||
)
|
||||
from openpype.lib.remote_publish import get_webpublish_conn
|
||||
|
||||
|
||||
class CollectBatchData(pyblish.api.ContextPlugin):
|
||||
"""Collect batch data from json stored in 'OPENPYPE_PUBLISH_DATA' env dir.
|
||||
|
||||
The directory must contain 'manifest.json' file where batch data should be
|
||||
stored.
|
||||
"""
|
||||
# must be really early, context values are only in json file
|
||||
order = pyblish.api.CollectorOrder - 0.495
|
||||
label = "Collect batch data"
|
||||
host = ["webpublisher"]
|
||||
|
||||
def process(self, context):
|
||||
batch_dir = os.environ.get("OPENPYPE_PUBLISH_DATA")
|
||||
|
||||
assert batch_dir, (
|
||||
"Missing `OPENPYPE_PUBLISH_DATA`")
|
||||
|
||||
assert os.path.exists(batch_dir), \
|
||||
"Folder {} doesn't exist".format(batch_dir)
|
||||
|
||||
project_name = os.environ.get("AVALON_PROJECT")
|
||||
if project_name is None:
|
||||
raise AssertionError(
|
||||
"Environment `AVALON_PROJECT` was not found."
|
||||
"Could not set project `root` which may cause issues."
|
||||
)
|
||||
|
||||
batch_data = parse_json(os.path.join(batch_dir, "manifest.json"))
|
||||
|
||||
context.data["batchDir"] = batch_dir
|
||||
context.data["batchData"] = batch_data
|
||||
|
||||
asset_name, task_name, task_type = get_batch_asset_task_info(
|
||||
batch_data["context"]
|
||||
)
|
||||
|
||||
os.environ["AVALON_ASSET"] = asset_name
|
||||
io.Session["AVALON_ASSET"] = asset_name
|
||||
os.environ["AVALON_TASK"] = task_name
|
||||
io.Session["AVALON_TASK"] = task_name
|
||||
|
||||
context.data["asset"] = asset_name
|
||||
context.data["task"] = task_name
|
||||
context.data["taskType"] = task_type
|
||||
|
||||
self._set_ctx_path(batch_data)
|
||||
|
||||
def _set_ctx_path(self, batch_data):
|
||||
dbcon = get_webpublish_conn()
|
||||
|
||||
batch_id = batch_data["batch"]
|
||||
ctx_path = batch_data["context"]["path"]
|
||||
self.log.info("ctx_path: {}".format(ctx_path))
|
||||
self.log.info("batch_id: {}".format(batch_id))
|
||||
if ctx_path and batch_id:
|
||||
self.log.info("Updating log record")
|
||||
dbcon.update_one(
|
||||
{
|
||||
"batch_id": batch_id,
|
||||
"status": "in_progress"
|
||||
},
|
||||
{
|
||||
"$set": {
|
||||
"path": ctx_path
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -20,9 +20,8 @@ class CollectFPS(pyblish.api.InstancePlugin):
|
|||
hosts = ["webpublisher"]
|
||||
|
||||
def process(self, instance):
|
||||
fps = instance.context.data["fps"]
|
||||
instance_fps = instance.data.get("fps")
|
||||
if instance_fps is None:
|
||||
instance.data["fps"] = instance.context.data["fps"]
|
||||
|
||||
instance.data.update({
|
||||
"fps": fps
|
||||
})
|
||||
self.log.debug(f"instance.data: {pformat(instance.data)}")
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
"""Loads publishing context from json and continues in publish process.
|
||||
"""Create instances from batch data and continues in publish process.
|
||||
|
||||
Requires:
|
||||
anatomy -> context["anatomy"] *(pyblish.api.CollectorOrder - 0.11)
|
||||
CollectBatchData
|
||||
|
||||
Provides:
|
||||
context, instances -> All data from previous publishing process.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import clique
|
||||
import tempfile
|
||||
|
||||
import pyblish.api
|
||||
from avalon import io
|
||||
import pyblish.api
|
||||
from openpype.lib import prepare_template_data
|
||||
from openpype.lib.plugin_tools import parse_json, get_batch_asset_task_info
|
||||
from openpype.lib.plugin_tools import parse_json
|
||||
|
||||
|
||||
class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
||||
|
|
@ -28,28 +26,28 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
order = pyblish.api.CollectorOrder - 0.490
|
||||
label = "Collect rendered frames"
|
||||
host = ["webpublisher"]
|
||||
|
||||
_context = None
|
||||
targets = ["filespublish"]
|
||||
|
||||
# from Settings
|
||||
task_type_to_family = {}
|
||||
|
||||
def _process_batch(self, dir_url):
|
||||
task_subfolders = [
|
||||
os.path.join(dir_url, o)
|
||||
for o in os.listdir(dir_url)
|
||||
if os.path.isdir(os.path.join(dir_url, o))]
|
||||
def process(self, context):
|
||||
batch_dir = context.data["batchDir"]
|
||||
task_subfolders = []
|
||||
for folder_name in os.listdir(batch_dir):
|
||||
full_path = os.path.join(batch_dir, folder_name)
|
||||
if os.path.isdir(full_path):
|
||||
task_subfolders.append(full_path)
|
||||
|
||||
self.log.info("task_sub:: {}".format(task_subfolders))
|
||||
|
||||
asset_name = context.data["asset"]
|
||||
task_name = context.data["task"]
|
||||
task_type = context.data["taskType"]
|
||||
for task_dir in task_subfolders:
|
||||
task_data = parse_json(os.path.join(task_dir,
|
||||
"manifest.json"))
|
||||
self.log.info("task_data:: {}".format(task_data))
|
||||
ctx = task_data["context"]
|
||||
|
||||
asset, task_name, task_type = get_batch_asset_task_info(ctx)
|
||||
|
||||
if task_name:
|
||||
os.environ["AVALON_TASK"] = task_name
|
||||
|
||||
is_sequence = len(task_data["files"]) > 1
|
||||
|
||||
|
|
@ -60,26 +58,20 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
is_sequence,
|
||||
extension.replace(".", ''))
|
||||
|
||||
subset = self._get_subset_name(family, subset_template, task_name,
|
||||
task_data["variant"])
|
||||
subset = self._get_subset_name(
|
||||
family, subset_template, task_name, task_data["variant"]
|
||||
)
|
||||
version = self._get_last_version(asset_name, subset) + 1
|
||||
|
||||
os.environ["AVALON_ASSET"] = asset
|
||||
io.Session["AVALON_ASSET"] = asset
|
||||
|
||||
instance = self._context.create_instance(subset)
|
||||
instance.data["asset"] = asset
|
||||
instance = context.create_instance(subset)
|
||||
instance.data["asset"] = asset_name
|
||||
instance.data["subset"] = subset
|
||||
instance.data["family"] = family
|
||||
instance.data["families"] = families
|
||||
instance.data["version"] = \
|
||||
self._get_last_version(asset, subset) + 1
|
||||
instance.data["version"] = version
|
||||
instance.data["stagingDir"] = tempfile.mkdtemp()
|
||||
instance.data["source"] = "webpublisher"
|
||||
|
||||
# to store logging info into DB openpype.webpublishes
|
||||
instance.data["ctx_path"] = ctx["path"]
|
||||
instance.data["batch_id"] = task_data["batch"]
|
||||
|
||||
# to convert from email provided into Ftrack username
|
||||
instance.data["user_email"] = task_data["user"]
|
||||
|
||||
|
|
@ -230,23 +222,3 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
return version[0].get("version") or 0
|
||||
else:
|
||||
return 0
|
||||
|
||||
def process(self, context):
|
||||
self._context = context
|
||||
|
||||
batch_dir = os.environ.get("OPENPYPE_PUBLISH_DATA")
|
||||
|
||||
assert batch_dir, (
|
||||
"Missing `OPENPYPE_PUBLISH_DATA`")
|
||||
|
||||
assert os.path.exists(batch_dir), \
|
||||
"Folder {} doesn't exist".format(batch_dir)
|
||||
|
||||
project_name = os.environ.get("AVALON_PROJECT")
|
||||
if project_name is None:
|
||||
raise AssertionError(
|
||||
"Environment `AVALON_PROJECT` was not found."
|
||||
"Could not set project `root` which may cause issues."
|
||||
)
|
||||
|
||||
self._process_batch(batch_dir)
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import os
|
||||
|
||||
import pyblish.api
|
||||
from openpype.lib import OpenPypeMongoConnection
|
||||
|
||||
|
||||
class IntegrateContextToLog(pyblish.api.ContextPlugin):
|
||||
""" Adds context information to log document for displaying in front end"""
|
||||
|
||||
label = "Integrate Context to Log"
|
||||
order = pyblish.api.IntegratorOrder - 0.1
|
||||
hosts = ["webpublisher"]
|
||||
|
||||
def process(self, context):
|
||||
self.log.info("Integrate Context to Log")
|
||||
|
||||
mongo_client = OpenPypeMongoConnection.get_mongo_client()
|
||||
database_name = os.environ["OPENPYPE_DATABASE_NAME"]
|
||||
dbcon = mongo_client[database_name]["webpublishes"]
|
||||
|
||||
for instance in context:
|
||||
self.log.info("ctx_path: {}".format(instance.data.get("ctx_path")))
|
||||
self.log.info("batch_id: {}".format(instance.data.get("batch_id")))
|
||||
if instance.data.get("ctx_path") and instance.data.get("batch_id"):
|
||||
self.log.info("Updating log record")
|
||||
dbcon.update_one(
|
||||
{
|
||||
"batch_id": instance.data.get("batch_id"),
|
||||
"status": "in_progress"
|
||||
},
|
||||
{"$set":
|
||||
{
|
||||
"path": instance.data.get("ctx_path")
|
||||
|
||||
}}
|
||||
)
|
||||
|
||||
return
|
||||
|
|
@ -176,23 +176,48 @@ class TaskNode(Node):
|
|||
class WebpublisherBatchPublishEndpoint(_RestApiEndpoint):
|
||||
"""Triggers headless publishing of batch."""
|
||||
async def post(self, request) -> Response:
|
||||
# for postprocessing in host, currently only PS
|
||||
host_map = {"photoshop": [".psd", ".psb"]}
|
||||
# Validate existence of openpype executable
|
||||
openpype_app = self.resource.executable
|
||||
if not openpype_app or not os.path.exists(openpype_app):
|
||||
msg = "Non existent OpenPype executable {}".format(openpype_app)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
# for postprocessing in host, currently only PS
|
||||
output = {}
|
||||
log.info("WebpublisherBatchPublishEndpoint called")
|
||||
content = await request.json()
|
||||
|
||||
batch_path = os.path.join(self.resource.upload_dir,
|
||||
content["batch"])
|
||||
# Each filter have extensions which are checked on first task item
|
||||
# - first filter with extensions that are on first task is used
|
||||
# - filter defines command and can extend arguments dictionary
|
||||
# This is used only if 'studio_processing' is enabled on batch
|
||||
studio_processing_filters = [
|
||||
# Photoshop filter
|
||||
{
|
||||
"extensions": [".psd", ".psb"],
|
||||
"command": "remotepublishfromapp",
|
||||
"arguments": {
|
||||
# Command 'remotepublishfromapp' requires --host argument
|
||||
"host": "photoshop",
|
||||
# Make sure targets are set to None for cases that default
|
||||
# would change
|
||||
# - targets argument is not used in 'remotepublishfromapp'
|
||||
"targets": None
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
add_args = {
|
||||
"host": "webpublisher",
|
||||
"project": content["project_name"],
|
||||
"user": content["user"]
|
||||
}
|
||||
batch_path = os.path.join(self.resource.upload_dir, content["batch"])
|
||||
|
||||
# Default command and arguments
|
||||
command = "remotepublish"
|
||||
add_args = {
|
||||
# All commands need 'project' and 'user'
|
||||
"project": content["project_name"],
|
||||
"user": content["user"],
|
||||
|
||||
"targets": ["filespublish"]
|
||||
}
|
||||
|
||||
if content.get("studio_processing"):
|
||||
log.info("Post processing called")
|
||||
|
|
@ -208,32 +233,34 @@ class WebpublisherBatchPublishEndpoint(_RestApiEndpoint):
|
|||
raise ValueError(
|
||||
"Cannot parse batch meta in {} folder".format(task_data))
|
||||
|
||||
command = "remotepublishfromapp"
|
||||
for host, extensions in host_map.items():
|
||||
for ext in extensions:
|
||||
for file_name in task_data["files"]:
|
||||
if ext in file_name:
|
||||
add_args["host"] = host
|
||||
break
|
||||
for process_filter in studio_processing_filters:
|
||||
filter_extensions = process_filter.get("extensions") or []
|
||||
for file_name in task_data["files"]:
|
||||
file_ext = os.path.splitext(file_name)[-1].lower()
|
||||
if file_ext in filter_extensions:
|
||||
# Change command
|
||||
command = process_filter["command"]
|
||||
# Update arguments
|
||||
add_args.update(
|
||||
process_filter.get("arguments") or {}
|
||||
)
|
||||
break
|
||||
|
||||
if not add_args.get("host"):
|
||||
raise ValueError(
|
||||
"Couldn't discern host from {}".format(task_data["files"]))
|
||||
|
||||
openpype_app = self.resource.executable
|
||||
args = [
|
||||
openpype_app,
|
||||
command,
|
||||
batch_path
|
||||
]
|
||||
|
||||
if not openpype_app or not os.path.exists(openpype_app):
|
||||
msg = "Non existent OpenPype executable {}".format(openpype_app)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
for key, value in add_args.items():
|
||||
args.append("--{}".format(key))
|
||||
args.append(value)
|
||||
# Skip key values where value is None
|
||||
if value is not None:
|
||||
args.append("--{}".format(key))
|
||||
# Extend list into arguments (targets can be a list)
|
||||
if isinstance(value, (tuple, list)):
|
||||
args.extend(value)
|
||||
else:
|
||||
args.append(value)
|
||||
|
||||
log.info("args:: {}".format(args))
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import json
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
import abc
|
||||
import six
|
||||
|
||||
|
||||
from .anatomy import Anatomy
|
||||
|
|
@ -196,3 +198,159 @@ def get_project_basic_paths(project_name):
|
|||
if isinstance(folder_structure, str):
|
||||
folder_structure = json.loads(folder_structure)
|
||||
return _list_path_items(folder_structure)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class HostDirmap:
|
||||
"""
|
||||
Abstract class for running dirmap on a workfile in a host.
|
||||
|
||||
Dirmap is used to translate paths inside of host workfile from one
|
||||
OS to another. (Eg. arstist created workfile on Win, different artists
|
||||
opens same file on Linux.)
|
||||
|
||||
Expects methods to be implemented inside of host:
|
||||
on_dirmap_enabled: run host code for enabling dirmap
|
||||
do_dirmap: run host code to do actual remapping
|
||||
"""
|
||||
def __init__(self, host_name, project_settings, sync_module=None):
|
||||
self.host_name = host_name
|
||||
self.project_settings = project_settings
|
||||
self.sync_module = sync_module # to limit reinit of Modules
|
||||
|
||||
self._mapping = None # cache mapping
|
||||
|
||||
@abc.abstractmethod
|
||||
def on_enable_dirmap(self):
|
||||
"""
|
||||
Run host dependent operation for enabling dirmap if necessary.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def dirmap_routine(self, source_path, destination_path):
|
||||
"""
|
||||
Run host dependent remapping from source_path to destination_path
|
||||
"""
|
||||
|
||||
def process_dirmap(self):
|
||||
# type: (dict) -> None
|
||||
"""Go through all paths in Settings and set them using `dirmap`.
|
||||
|
||||
If artists has Site Sync enabled, take dirmap mapping directly from
|
||||
Local Settings when artist is syncing workfile locally.
|
||||
|
||||
Args:
|
||||
project_settings (dict): Settings for current project.
|
||||
|
||||
"""
|
||||
if not self._mapping:
|
||||
self._mapping = self.get_mappings(self.project_settings)
|
||||
if not self._mapping:
|
||||
return
|
||||
|
||||
log.info("Processing directory mapping ...")
|
||||
self.on_enable_dirmap()
|
||||
log.info("mapping:: {}".format(self._mapping))
|
||||
|
||||
for k, sp in enumerate(self._mapping["source-path"]):
|
||||
try:
|
||||
print("{} -> {}".format(sp,
|
||||
self._mapping["destination-path"][k]))
|
||||
self.dirmap_routine(sp,
|
||||
self._mapping["destination-path"][k])
|
||||
except IndexError:
|
||||
# missing corresponding destination path
|
||||
log.error(("invalid dirmap mapping, missing corresponding"
|
||||
" destination directory."))
|
||||
break
|
||||
except RuntimeError:
|
||||
log.error("invalid path {} -> {}, mapping not registered".format( # noqa: E501
|
||||
sp, self._mapping["destination-path"][k]
|
||||
))
|
||||
continue
|
||||
|
||||
def get_mappings(self, project_settings):
|
||||
"""Get translation from source-path to destination-path.
|
||||
|
||||
It checks if Site Sync is enabled and user chose to use local
|
||||
site, in that case configuration in Local Settings takes precedence
|
||||
"""
|
||||
local_mapping = self._get_local_sync_dirmap(project_settings)
|
||||
dirmap_label = "{}-dirmap".format(self.host_name)
|
||||
if not self.project_settings[self.host_name].get(dirmap_label) and \
|
||||
not local_mapping:
|
||||
return []
|
||||
mapping = local_mapping or \
|
||||
self.project_settings[self.host_name][dirmap_label]["paths"] or {}
|
||||
enbled = self.project_settings[self.host_name][dirmap_label]["enabled"]
|
||||
mapping_enabled = enbled or bool(local_mapping)
|
||||
|
||||
if not mapping or not mapping_enabled or \
|
||||
not mapping.get("destination-path") or \
|
||||
not mapping.get("source-path"):
|
||||
return []
|
||||
return mapping
|
||||
|
||||
def _get_local_sync_dirmap(self, project_settings):
|
||||
"""
|
||||
Returns dirmap if synch to local project is enabled.
|
||||
|
||||
Only valid mapping is from roots of remote site to local site set
|
||||
in Local Settings.
|
||||
|
||||
Args:
|
||||
project_settings (dict)
|
||||
Returns:
|
||||
dict : { "source-path": [XXX], "destination-path": [YYYY]}
|
||||
"""
|
||||
import json
|
||||
mapping = {}
|
||||
|
||||
if not project_settings["global"]["sync_server"]["enabled"]:
|
||||
log.debug("Site Sync not enabled")
|
||||
return mapping
|
||||
|
||||
from openpype.settings.lib import get_site_local_overrides
|
||||
|
||||
if not self.sync_module:
|
||||
from openpype.modules import ModulesManager
|
||||
manager = ModulesManager()
|
||||
self.sync_module = manager.modules_by_name["sync_server"]
|
||||
|
||||
project_name = os.getenv("AVALON_PROJECT")
|
||||
|
||||
active_site = self.sync_module.get_local_normalized_site(
|
||||
self.sync_module.get_active_site(project_name))
|
||||
remote_site = self.sync_module.get_local_normalized_site(
|
||||
self.sync_module.get_remote_site(project_name))
|
||||
log.debug("active {} - remote {}".format(active_site, remote_site))
|
||||
|
||||
if active_site == "local" \
|
||||
and project_name in self.sync_module.get_enabled_projects()\
|
||||
and active_site != remote_site:
|
||||
|
||||
sync_settings = self.sync_module.get_sync_project_setting(
|
||||
os.getenv("AVALON_PROJECT"), exclude_locals=False,
|
||||
cached=False)
|
||||
|
||||
active_overrides = get_site_local_overrides(
|
||||
os.getenv("AVALON_PROJECT"), active_site)
|
||||
remote_overrides = get_site_local_overrides(
|
||||
os.getenv("AVALON_PROJECT"), remote_site)
|
||||
|
||||
log.debug("local overrides".format(active_overrides))
|
||||
log.debug("remote overrides".format(remote_overrides))
|
||||
for root_name, active_site_dir in active_overrides.items():
|
||||
remote_site_dir = remote_overrides.get(root_name) or\
|
||||
sync_settings["sites"][remote_site]["root"][root_name]
|
||||
if os.path.isdir(active_site_dir):
|
||||
if not mapping.get("destination-path"):
|
||||
mapping["destination-path"] = []
|
||||
mapping["destination-path"].append(active_site_dir)
|
||||
|
||||
if not mapping.get("source-path"):
|
||||
mapping["source-path"] = []
|
||||
mapping["source-path"].append(remote_site_dir)
|
||||
|
||||
log.debug("local sync mapping:: {}".format(mapping))
|
||||
return mapping
|
||||
|
|
|
|||
|
|
@ -4,6 +4,18 @@ from Qt import QtCore, QtWidgets, QtGui
|
|||
from openpype.lib import PypeLogger
|
||||
from . import lib
|
||||
|
||||
from openpype.tools.utils.constants import (
|
||||
LOCAL_PROVIDER_ROLE,
|
||||
REMOTE_PROVIDER_ROLE,
|
||||
LOCAL_PROGRESS_ROLE,
|
||||
REMOTE_PROGRESS_ROLE,
|
||||
LOCAL_DATE_ROLE,
|
||||
REMOTE_DATE_ROLE,
|
||||
LOCAL_FAILED_ROLE,
|
||||
REMOTE_FAILED_ROLE,
|
||||
EDIT_ICON_ROLE
|
||||
)
|
||||
|
||||
log = PypeLogger().get_logger("SyncServer")
|
||||
|
||||
|
||||
|
|
@ -14,7 +26,7 @@ class PriorityDelegate(QtWidgets.QStyledItemDelegate):
|
|||
|
||||
if option.widget.selectionModel().isSelected(index) or \
|
||||
option.state & QtWidgets.QStyle.State_MouseOver:
|
||||
edit_icon = index.data(lib.EditIconRole)
|
||||
edit_icon = index.data(EDIT_ICON_ROLE)
|
||||
if not edit_icon:
|
||||
return
|
||||
|
||||
|
|
@ -38,7 +50,7 @@ class PriorityDelegate(QtWidgets.QStyledItemDelegate):
|
|||
editor = PriorityLineEdit(
|
||||
parent,
|
||||
option.widget.selectionModel().selectedRows())
|
||||
editor.setFocus(True)
|
||||
editor.setFocus()
|
||||
return editor
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
|
|
@ -71,19 +83,30 @@ class ImageDelegate(QtWidgets.QStyledItemDelegate):
|
|||
Prints icon of site and progress of synchronization
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, parent=None, side=None):
|
||||
super(ImageDelegate, self).__init__(parent)
|
||||
self.icons = {}
|
||||
self.side = side
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
super(ImageDelegate, self).paint(painter, option, index)
|
||||
option = QtWidgets.QStyleOptionViewItem(option)
|
||||
option.showDecorationSelected = True
|
||||
|
||||
provider = index.data(lib.ProviderRole)
|
||||
value = index.data(lib.ProgressRole)
|
||||
date_value = index.data(lib.DateRole)
|
||||
is_failed = index.data(lib.FailedRole)
|
||||
if not self.side:
|
||||
log.warning("No side provided, delegate won't work")
|
||||
return
|
||||
|
||||
if self.side == 'local':
|
||||
provider = index.data(LOCAL_PROVIDER_ROLE)
|
||||
value = index.data(LOCAL_PROGRESS_ROLE)
|
||||
date_value = index.data(LOCAL_DATE_ROLE)
|
||||
is_failed = index.data(LOCAL_FAILED_ROLE)
|
||||
else:
|
||||
provider = index.data(REMOTE_PROVIDER_ROLE)
|
||||
value = index.data(REMOTE_PROGRESS_ROLE)
|
||||
date_value = index.data(REMOTE_DATE_ROLE)
|
||||
is_failed = index.data(REMOTE_FAILED_ROLE)
|
||||
|
||||
if not self.icons.get(provider):
|
||||
resource_path = os.path.dirname(__file__)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
from Qt import QtCore
|
||||
import attr
|
||||
import abc
|
||||
import six
|
||||
|
|
@ -19,14 +18,6 @@ STATUS = {
|
|||
|
||||
DUMMY_PROJECT = "No project configured"
|
||||
|
||||
ProviderRole = QtCore.Qt.UserRole + 2
|
||||
ProgressRole = QtCore.Qt.UserRole + 4
|
||||
DateRole = QtCore.Qt.UserRole + 6
|
||||
FailedRole = QtCore.Qt.UserRole + 8
|
||||
HeaderNameRole = QtCore.Qt.UserRole + 10
|
||||
FullItemRole = QtCore.Qt.UserRole + 12
|
||||
EditIconRole = QtCore.Qt.UserRole + 14
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AbstractColumnFilter:
|
||||
|
|
@ -161,7 +152,7 @@ def translate_provider_for_icon(sync_server, project, site):
|
|||
return sync_server.get_provider_for_site(site=site)
|
||||
|
||||
|
||||
def get_item_by_id(model, object_id):
|
||||
def get_value_from_id_by_role(model, object_id, role):
|
||||
"""Return value from item with 'object_id' with 'role'."""
|
||||
index = model.get_index(object_id)
|
||||
item = model.data(index, FullItemRole)
|
||||
return item
|
||||
return model.data(index, role)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,23 @@ from openpype.api import get_local_site_id
|
|||
|
||||
from . import lib
|
||||
|
||||
from openpype.tools.utils.constants import (
|
||||
LOCAL_PROVIDER_ROLE,
|
||||
REMOTE_PROVIDER_ROLE,
|
||||
LOCAL_PROGRESS_ROLE,
|
||||
REMOTE_PROGRESS_ROLE,
|
||||
HEADER_NAME_ROLE,
|
||||
EDIT_ICON_ROLE,
|
||||
LOCAL_DATE_ROLE,
|
||||
REMOTE_DATE_ROLE,
|
||||
LOCAL_FAILED_ROLE,
|
||||
REMOTE_FAILED_ROLE,
|
||||
STATUS_ROLE,
|
||||
PATH_ROLE,
|
||||
ERROR_ROLE,
|
||||
TRIES_ROLE
|
||||
)
|
||||
|
||||
|
||||
log = PypeLogger().get_logger("SyncServer")
|
||||
|
||||
|
|
@ -68,10 +85,68 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
if orientation == Qt.Horizontal:
|
||||
return self.COLUMN_LABELS[section][1]
|
||||
|
||||
if role == lib.HeaderNameRole:
|
||||
if role == HEADER_NAME_ROLE:
|
||||
if orientation == Qt.Horizontal:
|
||||
return self.COLUMN_LABELS[section][0] # return name
|
||||
|
||||
def data(self, index, role):
|
||||
item = self._data[index.row()]
|
||||
|
||||
header_value = self._header[index.column()]
|
||||
if role == LOCAL_PROVIDER_ROLE:
|
||||
return item.local_provider
|
||||
|
||||
if role == REMOTE_PROVIDER_ROLE:
|
||||
return item.remote_provider
|
||||
|
||||
if role == LOCAL_PROGRESS_ROLE:
|
||||
return item.local_progress
|
||||
|
||||
if role == REMOTE_PROGRESS_ROLE:
|
||||
return item.remote_progress
|
||||
|
||||
if role == LOCAL_DATE_ROLE:
|
||||
if item.created_dt:
|
||||
return pretty_timestamp(item.created_dt)
|
||||
|
||||
if role == REMOTE_DATE_ROLE:
|
||||
if item.sync_dt:
|
||||
return pretty_timestamp(item.sync_dt)
|
||||
|
||||
if role == LOCAL_FAILED_ROLE:
|
||||
return item.status == lib.STATUS[2] and \
|
||||
item.local_progress < 1
|
||||
|
||||
if role == REMOTE_FAILED_ROLE:
|
||||
return item.status == lib.STATUS[2] and \
|
||||
item.remote_progress < 1
|
||||
|
||||
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||
# because of ImageDelegate
|
||||
if header_value in ['remote_site', 'local_site']:
|
||||
return ""
|
||||
|
||||
return attr.asdict(item)[self._header[index.column()]]
|
||||
|
||||
if role == EDIT_ICON_ROLE:
|
||||
if self.can_edit and header_value in self.EDITABLE_COLUMNS:
|
||||
return self.edit_icon
|
||||
|
||||
if role == PATH_ROLE:
|
||||
return item.path
|
||||
|
||||
if role == ERROR_ROLE:
|
||||
return item.error
|
||||
|
||||
if role == TRIES_ROLE:
|
||||
return item.tries
|
||||
|
||||
if role == STATUS_ROLE:
|
||||
return item.status
|
||||
|
||||
if role == Qt.UserRole:
|
||||
return item._id
|
||||
|
||||
@property
|
||||
def can_edit(self):
|
||||
"""Returns true if some site is user local site, eg. could edit"""
|
||||
|
|
@ -456,55 +531,6 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
|
|||
self.timer.timeout.connect(self.tick)
|
||||
self.timer.start(self.REFRESH_SEC)
|
||||
|
||||
def data(self, index, role):
|
||||
item = self._data[index.row()]
|
||||
|
||||
if role == lib.FullItemRole:
|
||||
return item
|
||||
|
||||
header_value = self._header[index.column()]
|
||||
if role == lib.ProviderRole:
|
||||
if header_value == 'local_site':
|
||||
return item.local_provider
|
||||
if header_value == 'remote_site':
|
||||
return item.remote_provider
|
||||
|
||||
if role == lib.ProgressRole:
|
||||
if header_value == 'local_site':
|
||||
return item.local_progress
|
||||
if header_value == 'remote_site':
|
||||
return item.remote_progress
|
||||
|
||||
if role == lib.DateRole:
|
||||
if header_value == 'local_site':
|
||||
if item.created_dt:
|
||||
return pretty_timestamp(item.created_dt)
|
||||
if header_value == 'remote_site':
|
||||
if item.sync_dt:
|
||||
return pretty_timestamp(item.sync_dt)
|
||||
|
||||
if role == lib.FailedRole:
|
||||
if header_value == 'local_site':
|
||||
return item.status == lib.STATUS[2] and \
|
||||
item.local_progress < 1
|
||||
if header_value == 'remote_site':
|
||||
return item.status == lib.STATUS[2] and \
|
||||
item.remote_progress < 1
|
||||
|
||||
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||
# because of ImageDelegate
|
||||
if header_value in ['remote_site', 'local_site']:
|
||||
return ""
|
||||
|
||||
return attr.asdict(item)[self._header[index.column()]]
|
||||
|
||||
if role == lib.EditIconRole:
|
||||
if self.can_edit and header_value in self.EDITABLE_COLUMNS:
|
||||
return self.edit_icon
|
||||
|
||||
if role == Qt.UserRole:
|
||||
return item._id
|
||||
|
||||
def add_page_records(self, local_site, remote_site, representations):
|
||||
"""
|
||||
Process all records from 'representation' and add them to storage.
|
||||
|
|
@ -985,55 +1011,6 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
|
|||
self.timer.timeout.connect(self.tick)
|
||||
self.timer.start(SyncRepresentationSummaryModel.REFRESH_SEC)
|
||||
|
||||
def data(self, index, role):
|
||||
item = self._data[index.row()]
|
||||
|
||||
if role == lib.FullItemRole:
|
||||
return item
|
||||
|
||||
header_value = self._header[index.column()]
|
||||
if role == lib.ProviderRole:
|
||||
if header_value == 'local_site':
|
||||
return item.local_provider
|
||||
if header_value == 'remote_site':
|
||||
return item.remote_provider
|
||||
|
||||
if role == lib.ProgressRole:
|
||||
if header_value == 'local_site':
|
||||
return item.local_progress
|
||||
if header_value == 'remote_site':
|
||||
return item.remote_progress
|
||||
|
||||
if role == lib.DateRole:
|
||||
if header_value == 'local_site':
|
||||
if item.created_dt:
|
||||
return pretty_timestamp(item.created_dt)
|
||||
if header_value == 'remote_site':
|
||||
if item.sync_dt:
|
||||
return pretty_timestamp(item.sync_dt)
|
||||
|
||||
if role == lib.FailedRole:
|
||||
if header_value == 'local_site':
|
||||
return item.status == lib.STATUS[2] and \
|
||||
item.local_progress < 1
|
||||
if header_value == 'remote_site':
|
||||
return item.status == lib.STATUS[2] and \
|
||||
item.remote_progress < 1
|
||||
|
||||
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||
# because of ImageDelegate
|
||||
if header_value in ['remote_site', 'local_site']:
|
||||
return ""
|
||||
|
||||
return attr.asdict(item)[self._header[index.column()]]
|
||||
|
||||
if role == lib.EditIconRole:
|
||||
if self.can_edit and header_value in self.EDITABLE_COLUMNS:
|
||||
return self.edit_icon
|
||||
|
||||
if role == Qt.UserRole:
|
||||
return item._id
|
||||
|
||||
def add_page_records(self, local_site, remote_site, representations):
|
||||
"""
|
||||
Process all records from 'representation' and add them to storage.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,20 @@ from .models import (
|
|||
from . import lib
|
||||
from . import delegates
|
||||
|
||||
from openpype.tools.utils.constants import (
|
||||
LOCAL_PROGRESS_ROLE,
|
||||
REMOTE_PROGRESS_ROLE,
|
||||
HEADER_NAME_ROLE,
|
||||
STATUS_ROLE,
|
||||
PATH_ROLE,
|
||||
LOCAL_SITE_NAME_ROLE,
|
||||
REMOTE_SITE_NAME_ROLE,
|
||||
LOCAL_DATE_ROLE,
|
||||
REMOTE_DATE_ROLE,
|
||||
ERROR_ROLE,
|
||||
TRIES_ROLE
|
||||
)
|
||||
|
||||
log = PypeLogger().get_logger("SyncServer")
|
||||
|
||||
|
||||
|
|
@ -289,14 +303,19 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
|
||||
if is_multi:
|
||||
index = self.model.get_index(list(self._selected_ids)[0])
|
||||
item = self.model.data(index, lib.FullItemRole)
|
||||
local_progress = self.model.data(index, LOCAL_PROGRESS_ROLE)
|
||||
remote_progress = self.model.data(index, REMOTE_PROGRESS_ROLE)
|
||||
status = self.model.data(index, STATUS_ROLE)
|
||||
else:
|
||||
item = self.model.data(point_index, lib.FullItemRole)
|
||||
local_progress = self.model.data(point_index, LOCAL_PROGRESS_ROLE)
|
||||
remote_progress = self.model.data(point_index,
|
||||
REMOTE_PROGRESS_ROLE)
|
||||
status = self.model.data(point_index, STATUS_ROLE)
|
||||
|
||||
|
||||
can_edit = self.model.can_edit
|
||||
action_kwarg_map, actions_mapping, menu = self._prepare_menu(item,
|
||||
is_multi,
|
||||
can_edit)
|
||||
action_kwarg_map, actions_mapping, menu = self._prepare_menu(
|
||||
local_progress, remote_progress, is_multi, can_edit, status)
|
||||
|
||||
result = menu.exec_(QtGui.QCursor.pos())
|
||||
if result:
|
||||
|
|
@ -307,7 +326,8 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
|
||||
self.model.refresh()
|
||||
|
||||
def _prepare_menu(self, item, is_multi, can_edit):
|
||||
def _prepare_menu(self, local_progress, remote_progress,
|
||||
is_multi, can_edit, status=None):
|
||||
menu = QtWidgets.QMenu(self)
|
||||
|
||||
actions_mapping = {}
|
||||
|
|
@ -316,11 +336,6 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
active_site = self.model.active_site
|
||||
remote_site = self.model.remote_site
|
||||
|
||||
local_progress = item.local_progress
|
||||
remote_progress = item.remote_progress
|
||||
|
||||
project = self.model.project
|
||||
|
||||
for site, progress in {active_site: local_progress,
|
||||
remote_site: remote_progress}.items():
|
||||
provider = self.sync_server.get_provider_for_site(site=site)
|
||||
|
|
@ -360,12 +375,6 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
actions_mapping[action] = self._change_priority
|
||||
menu.addAction(action)
|
||||
|
||||
# # temp for testing only !!!
|
||||
# action = QtWidgets.QAction("Download")
|
||||
# action_kwarg_map[action] = self._get_action_kwargs(active_site)
|
||||
# actions_mapping[action] = self._add_site
|
||||
# menu.addAction(action)
|
||||
|
||||
if not actions_mapping:
|
||||
action = QtWidgets.QAction("< No action >")
|
||||
actions_mapping[action] = None
|
||||
|
|
@ -376,11 +385,15 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
def _pause(self, selected_ids=None):
|
||||
log.debug("Pause {}".format(selected_ids))
|
||||
for representation_id in selected_ids:
|
||||
item = lib.get_item_by_id(self.model, representation_id)
|
||||
if item.status not in [lib.STATUS[0], lib.STATUS[1]]:
|
||||
status = lib.get_value_from_id_by_role(self.model,
|
||||
representation_id,
|
||||
STATUS_ROLE)
|
||||
if status not in [lib.STATUS[0], lib.STATUS[1]]:
|
||||
continue
|
||||
for site_name in [self.model.active_site, self.model.remote_site]:
|
||||
check_progress = self._get_progress(item, site_name)
|
||||
check_progress = self._get_progress(self.model,
|
||||
representation_id,
|
||||
site_name)
|
||||
if check_progress < 1:
|
||||
self.sync_server.pause_representation(self.model.project,
|
||||
representation_id,
|
||||
|
|
@ -391,11 +404,15 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
def _unpause(self, selected_ids=None):
|
||||
log.debug("UnPause {}".format(selected_ids))
|
||||
for representation_id in selected_ids:
|
||||
item = lib.get_item_by_id(self.model, representation_id)
|
||||
if item.status not in lib.STATUS[3]:
|
||||
status = lib.get_value_from_id_by_role(self.model,
|
||||
representation_id,
|
||||
STATUS_ROLE)
|
||||
if status not in lib.STATUS[3]:
|
||||
continue
|
||||
for site_name in [self.model.active_site, self.model.remote_site]:
|
||||
check_progress = self._get_progress(item, site_name)
|
||||
check_progress = self._get_progress(self.model,
|
||||
representation_id,
|
||||
site_name)
|
||||
if check_progress < 1:
|
||||
self.sync_server.unpause_representation(
|
||||
self.model.project,
|
||||
|
|
@ -408,8 +425,11 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
def _add_site(self, selected_ids=None, site_name=None):
|
||||
log.debug("Add site {}:{}".format(selected_ids, site_name))
|
||||
for representation_id in selected_ids:
|
||||
item = lib.get_item_by_id(self.model, representation_id)
|
||||
if item.local_site == site_name or item.remote_site == site_name:
|
||||
item_local_site = lib.get_value_from_id_by_role(
|
||||
self.model, representation_id, LOCAL_SITE_NAME_ROLE)
|
||||
item_remote_site = lib.get_value_from_id_by_role(
|
||||
self.model, representation_id, REMOTE_SITE_NAME_ROLE)
|
||||
if site_name in [item_local_site, item_remote_site]:
|
||||
# site already exists skip
|
||||
continue
|
||||
|
||||
|
|
@ -460,8 +480,8 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
"""
|
||||
log.debug("Reset site {}:{}".format(selected_ids, site_name))
|
||||
for representation_id in selected_ids:
|
||||
item = lib.get_item_by_id(self.model, representation_id)
|
||||
check_progress = self._get_progress(item, site_name, True)
|
||||
check_progress = self._get_progress(self.model, representation_id,
|
||||
site_name, True)
|
||||
|
||||
# do not reset if opposite side is not fully there
|
||||
if check_progress != 1:
|
||||
|
|
@ -482,11 +502,8 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
def _open_in_explorer(self, selected_ids=None, site_name=None):
|
||||
log.debug("Open in Explorer {}:{}".format(selected_ids, site_name))
|
||||
for selected_id in selected_ids:
|
||||
item = lib.get_item_by_id(self.model, selected_id)
|
||||
if not item:
|
||||
return
|
||||
|
||||
fpath = item.path
|
||||
fpath = lib.get_value_from_id_by_role(self.model, selected_id,
|
||||
PATH_ROLE)
|
||||
project = self.model.project
|
||||
fpath = self.sync_server.get_local_file_path(project,
|
||||
site_name,
|
||||
|
|
@ -514,10 +531,17 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
self.model.is_editing = True
|
||||
self.table_view.openPersistentEditor(real_index)
|
||||
|
||||
def _get_progress(self, item, site_name, opposite=False):
|
||||
def _get_progress(self, model, representation_id,
|
||||
site_name, opposite=False):
|
||||
"""Returns progress value according to site (side)"""
|
||||
progress = {'local': item.local_progress,
|
||||
'remote': item.remote_progress}
|
||||
local_progress = lib.get_value_from_id_by_role(model,
|
||||
representation_id,
|
||||
LOCAL_PROGRESS_ROLE)
|
||||
remote_progress = lib.get_value_from_id_by_role(model,
|
||||
representation_id,
|
||||
REMOTE_PROGRESS_ROLE)
|
||||
progress = {'local': local_progress,
|
||||
'remote': remote_progress}
|
||||
side = 'remote'
|
||||
if site_name == self.model.active_site:
|
||||
side = 'local'
|
||||
|
|
@ -591,11 +615,11 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget):
|
|||
table_view.viewport().setAttribute(QtCore.Qt.WA_Hover, True)
|
||||
|
||||
column = table_view.model().get_header_index("local_site")
|
||||
delegate = delegates.ImageDelegate(self)
|
||||
delegate = delegates.ImageDelegate(self, side="local")
|
||||
table_view.setItemDelegateForColumn(column, delegate)
|
||||
|
||||
column = table_view.model().get_header_index("remote_site")
|
||||
delegate = delegates.ImageDelegate(self)
|
||||
delegate = delegates.ImageDelegate(self, side="remote")
|
||||
table_view.setItemDelegateForColumn(column, delegate)
|
||||
|
||||
column = table_view.model().get_header_index("priority")
|
||||
|
|
@ -631,19 +655,21 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget):
|
|||
self.selection_model = self.table_view.selectionModel()
|
||||
self.selection_model.selectionChanged.connect(self._selection_changed)
|
||||
|
||||
def _prepare_menu(self, item, is_multi, can_edit):
|
||||
def _prepare_menu(self, local_progress, remote_progress,
|
||||
is_multi, can_edit, status=None):
|
||||
action_kwarg_map, actions_mapping, menu = \
|
||||
super()._prepare_menu(item, is_multi, can_edit)
|
||||
super()._prepare_menu(local_progress, remote_progress,
|
||||
is_multi, can_edit)
|
||||
|
||||
if can_edit and (
|
||||
item.status in [lib.STATUS[0], lib.STATUS[1]] or is_multi):
|
||||
status in [lib.STATUS[0], lib.STATUS[1]] or is_multi):
|
||||
action = QtWidgets.QAction("Pause in queue")
|
||||
actions_mapping[action] = self._pause
|
||||
# pause handles which site_name it will pause itself
|
||||
action_kwarg_map[action] = {"selected_ids": self._selected_ids}
|
||||
menu.addAction(action)
|
||||
|
||||
if can_edit and (item.status == lib.STATUS[3] or is_multi):
|
||||
if can_edit and (status == lib.STATUS[3] or is_multi):
|
||||
action = QtWidgets.QAction("Unpause in queue")
|
||||
actions_mapping[action] = self._unpause
|
||||
action_kwarg_map[action] = {"selected_ids": self._selected_ids}
|
||||
|
|
@ -753,11 +779,11 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget):
|
|||
table_view.verticalHeader().hide()
|
||||
|
||||
column = model.get_header_index("local_site")
|
||||
delegate = delegates.ImageDelegate(self)
|
||||
delegate = delegates.ImageDelegate(self, side="local")
|
||||
table_view.setItemDelegateForColumn(column, delegate)
|
||||
|
||||
column = model.get_header_index("remote_site")
|
||||
delegate = delegates.ImageDelegate(self)
|
||||
delegate = delegates.ImageDelegate(self, side="remote")
|
||||
table_view.setItemDelegateForColumn(column, delegate)
|
||||
|
||||
if model.can_edit:
|
||||
|
|
@ -815,12 +841,14 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget):
|
|||
|
||||
detail_window.exec()
|
||||
|
||||
def _prepare_menu(self, item, is_multi, can_edit):
|
||||
def _prepare_menu(self, local_progress, remote_progress,
|
||||
is_multi, can_edit, status=None):
|
||||
"""Adds view (and model) dependent actions to default ones"""
|
||||
action_kwarg_map, actions_mapping, menu = \
|
||||
super()._prepare_menu(item, is_multi, can_edit)
|
||||
super()._prepare_menu(local_progress, remote_progress,
|
||||
is_multi, can_edit, status)
|
||||
|
||||
if item.status == lib.STATUS[2] or is_multi:
|
||||
if status == lib.STATUS[2] or is_multi:
|
||||
action = QtWidgets.QAction("Open error detail")
|
||||
actions_mapping[action] = self._show_detail
|
||||
action_kwarg_map[action] = {"selected_ids": self._selected_ids}
|
||||
|
|
@ -835,8 +863,8 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget):
|
|||
redo of upload/download
|
||||
"""
|
||||
for file_id in selected_ids:
|
||||
item = lib.get_item_by_id(self.model, file_id)
|
||||
check_progress = self._get_progress(item, site_name, True)
|
||||
check_progress = self._get_progress(self.model, file_id,
|
||||
site_name, True)
|
||||
|
||||
# do not reset if opposite side is not fully there
|
||||
if check_progress != 1:
|
||||
|
|
@ -895,20 +923,28 @@ class SyncRepresentationErrorWidget(QtWidgets.QWidget):
|
|||
|
||||
no_errors = True
|
||||
for file_id in selected_ids:
|
||||
item = lib.get_item_by_id(model, file_id)
|
||||
if not item.created_dt or not item.sync_dt or not item.error:
|
||||
created_dt = lib.get_value_from_id_by_role(model, file_id,
|
||||
LOCAL_DATE_ROLE)
|
||||
sync_dt = lib.get_value_from_id_by_role(model, file_id,
|
||||
REMOTE_DATE_ROLE)
|
||||
errors = lib.get_value_from_id_by_role(model, file_id,
|
||||
ERROR_ROLE)
|
||||
if not created_dt or not sync_dt or not errors:
|
||||
continue
|
||||
|
||||
tries = lib.get_value_from_id_by_role(model, file_id,
|
||||
TRIES_ROLE)
|
||||
|
||||
no_errors = False
|
||||
dt = max(item.created_dt, item.sync_dt)
|
||||
dt = max(created_dt, sync_dt)
|
||||
|
||||
txts = []
|
||||
txts.append("{}: {}<br>".format("<b>Last update date</b>",
|
||||
pretty_timestamp(dt)))
|
||||
txts.append("{}: {}<br>".format("<b>Retries</b>",
|
||||
str(item.tries)))
|
||||
str(tries)))
|
||||
txts.append("{}: {}<br>".format("<b>Error message</b>",
|
||||
item.error))
|
||||
errors))
|
||||
|
||||
text_area = QtWidgets.QTextEdit("\n\n".join(txts))
|
||||
text_area.setReadOnly(True)
|
||||
|
|
@ -1162,7 +1198,7 @@ class HorizontalHeader(QtWidgets.QHeaderView):
|
|||
|
||||
column_name = self.model.headerData(column_idx,
|
||||
QtCore.Qt.Horizontal,
|
||||
lib.HeaderNameRole)
|
||||
HEADER_NAME_ROLE)
|
||||
button = self.filter_buttons.get(column_name)
|
||||
if not button:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -649,6 +649,8 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
AssertionError: if more then one collection is obtained.
|
||||
|
||||
"""
|
||||
start_frame = int(start_frame)
|
||||
end_frame = int(end_frame)
|
||||
collections = clique.assemble(files)[0]
|
||||
assert len(collections) == 1, "Multiple collections found."
|
||||
col = collections[0]
|
||||
|
|
|
|||
39
openpype/plugins/publish/validate_unique_names.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
from maya import cmds
|
||||
|
||||
import pyblish.api
|
||||
import openpype.api
|
||||
import openpype.hosts.maya.api.action
|
||||
|
||||
|
||||
class ValidateUniqueNames(pyblish.api.Validator):
|
||||
"""transform names should be unique
|
||||
|
||||
ie: using cmds.ls(someNodeName) should always return shortname
|
||||
|
||||
"""
|
||||
|
||||
order = openpype.api.ValidateContentsOrder
|
||||
hosts = ["maya"]
|
||||
families = ["model"]
|
||||
label = "Unique transform name"
|
||||
actions = [openpype.hosts.maya.api.action.SelectInvalidAction]
|
||||
|
||||
@staticmethod
|
||||
def get_invalid(instance):
|
||||
"""Returns the invalid transforms in the instance.
|
||||
|
||||
Returns:
|
||||
list: Non unique name transforms
|
||||
|
||||
"""
|
||||
|
||||
return [tr for tr in cmds.ls(instance, type="transform")
|
||||
if '|' in tr]
|
||||
|
||||
def process(self, instance):
|
||||
"""Process all the nodes in the instance "objectSet"""
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise ValueError("Nodes found with none unique names. "
|
||||
"values: {0}".format(invalid))
|
||||
|
|
@ -160,16 +160,13 @@ class PypeCommands:
|
|||
SLEEP = 5 # seconds for another loop check for concurrently runs
|
||||
WAIT_FOR = 300 # seconds to wait for conc. runs
|
||||
|
||||
from openpype import install, uninstall
|
||||
from openpype.api import Logger
|
||||
from openpype.lib import ApplicationManager
|
||||
|
||||
log = Logger.get_logger()
|
||||
|
||||
log.info("remotepublishphotoshop command")
|
||||
|
||||
install()
|
||||
|
||||
from openpype.lib import ApplicationManager
|
||||
application_manager = ApplicationManager()
|
||||
|
||||
found_variant_key = find_variant_key(application_manager, host)
|
||||
|
|
@ -243,10 +240,8 @@ class PypeCommands:
|
|||
while launched_app.poll() is None:
|
||||
time.sleep(0.5)
|
||||
|
||||
uninstall()
|
||||
|
||||
@staticmethod
|
||||
def remotepublish(project, batch_path, host, user, targets=None):
|
||||
def remotepublish(project, batch_path, user, targets=None):
|
||||
"""Start headless publishing.
|
||||
|
||||
Used to publish rendered assets, workfiles etc.
|
||||
|
|
@ -258,10 +253,9 @@ class PypeCommands:
|
|||
per call of remotepublish
|
||||
batch_path (str): Path batch folder. Contains subfolders with
|
||||
resources (workfile, another subfolder 'renders' etc.)
|
||||
targets (string): What module should be targeted
|
||||
(to choose validator for example)
|
||||
host (string)
|
||||
user (string): email address for webpublisher
|
||||
targets (list): Pyblish targets
|
||||
(to choose validator for example)
|
||||
|
||||
Raises:
|
||||
RuntimeError: When there is no path to process.
|
||||
|
|
@ -269,21 +263,22 @@ class PypeCommands:
|
|||
if not batch_path:
|
||||
raise RuntimeError("No publish paths specified")
|
||||
|
||||
from openpype import install, uninstall
|
||||
from openpype.api import Logger
|
||||
|
||||
# Register target and host
|
||||
import pyblish.api
|
||||
import pyblish.util
|
||||
import avalon.api
|
||||
from openpype.hosts.webpublisher import api as webpublisher
|
||||
|
||||
log = Logger.get_logger()
|
||||
log = PypeLogger.get_logger()
|
||||
|
||||
log.info("remotepublish command")
|
||||
|
||||
install()
|
||||
host_name = "webpublisher"
|
||||
os.environ["OPENPYPE_PUBLISH_DATA"] = batch_path
|
||||
os.environ["AVALON_PROJECT"] = project
|
||||
os.environ["AVALON_APP"] = host_name
|
||||
|
||||
if host:
|
||||
pyblish.api.register_host(host)
|
||||
pyblish.api.register_host(host_name)
|
||||
|
||||
if targets:
|
||||
if isinstance(targets, str):
|
||||
|
|
@ -291,13 +286,6 @@ class PypeCommands:
|
|||
for target in targets:
|
||||
pyblish.api.register_target(target)
|
||||
|
||||
os.environ["OPENPYPE_PUBLISH_DATA"] = batch_path
|
||||
os.environ["AVALON_PROJECT"] = project
|
||||
os.environ["AVALON_APP"] = host
|
||||
|
||||
import avalon.api
|
||||
from openpype.hosts.webpublisher import api as webpublisher
|
||||
|
||||
avalon.api.install(webpublisher)
|
||||
|
||||
log.info("Running publish ...")
|
||||
|
|
@ -309,7 +297,6 @@ class PypeCommands:
|
|||
publish_and_log(dbcon, _id, log)
|
||||
|
||||
log.info("Publish finished.")
|
||||
uninstall()
|
||||
|
||||
@staticmethod
|
||||
def extractenvironments(output_json_path, project, asset, task, app):
|
||||
|
|
|
|||
|
|
@ -8,16 +8,10 @@
|
|||
"yetiRig": "ma"
|
||||
},
|
||||
"maya-dirmap": {
|
||||
"enabled": true,
|
||||
"enabled": false,
|
||||
"paths": {
|
||||
"source-path": [
|
||||
"foo1",
|
||||
"foo2"
|
||||
],
|
||||
"destination-path": [
|
||||
"bar1",
|
||||
"bar2"
|
||||
]
|
||||
"source-path": [],
|
||||
"destination-path": []
|
||||
}
|
||||
},
|
||||
"scriptsmenu": {
|
||||
|
|
@ -315,11 +309,21 @@
|
|||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateShapeZero": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateTransformZero": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateUniqueNames": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateRigContents": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@
|
|||
"build_workfile": "ctrl+alt+b"
|
||||
}
|
||||
},
|
||||
"nuke-dirmap": {
|
||||
"enabled": false,
|
||||
"paths": {
|
||||
"source-path": [],
|
||||
"destination-path": []
|
||||
}
|
||||
},
|
||||
"create": {
|
||||
"CreateWriteRender": {
|
||||
"fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}",
|
||||
|
|
@ -130,8 +137,7 @@
|
|||
},
|
||||
"LoadClip": {
|
||||
"enabled": true,
|
||||
"_representations": [
|
||||
],
|
||||
"_representations": [],
|
||||
"node_name_template": "{class_name}_{ext}"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -46,6 +46,39 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"key": "nuke-dirmap",
|
||||
"label": "Nuke Directory Mapping",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "paths",
|
||||
"children": [
|
||||
{
|
||||
"type": "list",
|
||||
"object_type": "text",
|
||||
"key": "source-path",
|
||||
"label": "Source Path"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"object_type": "text",
|
||||
"key": "destination-path",
|
||||
"label": "Destination Path"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
|
|
|
|||
|
|
@ -324,9 +324,17 @@
|
|||
"key": "ValidateShapeRenderStats",
|
||||
"label": "ValidateShapeRenderStats"
|
||||
},
|
||||
{
|
||||
"key": "ValidateShapeZero",
|
||||
"label": "ValidateShapeZero"
|
||||
},
|
||||
{
|
||||
"key": "ValidateTransformZero",
|
||||
"label": "ValidateTransformZero"
|
||||
},
|
||||
{
|
||||
"key": "ValidateUniqueNames",
|
||||
"label": "ValidateUniqueNames"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
"bg-view-header": "#373D48",
|
||||
"bg-view-hover": "rgba(168, 175, 189, .3)",
|
||||
"bg-view-alternate": "rgb(36, 42, 50)",
|
||||
"bg-view-disabled": "#434a56",
|
||||
"bg-view-disabled": "#2C313A",
|
||||
"bg-view-alternate-disabled": "#2C313A",
|
||||
"bg-view-selection": "rgba(92, 173, 214, .4)",
|
||||
"bg-view-selection-hover": "rgba(92, 173, 214, .8)",
|
||||
|
|
|
|||
BIN
openpype/style/images/checkbox_checked.png
Normal file
|
After Width: | Height: | Size: 1,019 B |
BIN
openpype/style/images/checkbox_checked_disabled.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
openpype/style/images/checkbox_checked_focus.png
Normal file
|
After Width: | Height: | Size: 1,023 B |
BIN
openpype/style/images/checkbox_checked_hover.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
openpype/style/images/checkbox_indeterminate.png
Normal file
|
After Width: | Height: | Size: 476 B |
BIN
openpype/style/images/checkbox_indeterminate_disabled.png
Normal file
|
After Width: | Height: | Size: 508 B |
BIN
openpype/style/images/checkbox_indeterminate_focus.png
Normal file
|
After Width: | Height: | Size: 481 B |
BIN
openpype/style/images/checkbox_indeterminate_hover.png
Normal file
|
After Width: | Height: | Size: 495 B |
BIN
openpype/style/images/checkbox_unchecked.png
Normal file
|
After Width: | Height: | Size: 347 B |
BIN
openpype/style/images/checkbox_unchecked_disabled.png
Normal file
|
After Width: | Height: | Size: 374 B |
BIN
openpype/style/images/checkbox_unchecked_focus.png
Normal file
|
After Width: | Height: | Size: 343 B |
BIN
openpype/style/images/checkbox_unchecked_hover.png
Normal file
|
After Width: | Height: | Size: 361 B |
|
Before Width: | Height: | Size: 69 B After Width: | Height: | Size: 1.4 KiB |
|
|
@ -19,6 +19,18 @@
|
|||
<file>images/up_arrow.png</file>
|
||||
<file>images/up_arrow_disabled.png</file>
|
||||
<file>images/up_arrow_on.png</file>
|
||||
<file>images/checkbox_checked.png</file>
|
||||
<file>images/checkbox_checked_hover.png</file>
|
||||
<file>images/checkbox_checked_focus.png</file>
|
||||
<file>images/checkbox_checked_disabled.png</file>
|
||||
<file>images/checkbox_unchecked.png</file>
|
||||
<file>images/checkbox_unchecked_hover.png</file>
|
||||
<file>images/checkbox_unchecked_focus.png</file>
|
||||
<file>images/checkbox_unchecked_disabled.png</file>
|
||||
<file>images/checkbox_indeterminate.png</file>
|
||||
<file>images/checkbox_indeterminate_hover.png</file>
|
||||
<file>images/checkbox_indeterminate_focus.png</file>
|
||||
<file>images/checkbox_indeterminate_disabled.png</file>
|
||||
<file>images/transparent.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
|||
|
|
@ -57,9 +57,67 @@ QAbstractSpinBox:focus, QLineEdit:focus, QPlainTextEdit:focus, QTextEdit:focus{
|
|||
border-color: {color:border-focus};
|
||||
}
|
||||
|
||||
/* Checkbox */
|
||||
QCheckBox {
|
||||
background: transparent;
|
||||
QAbstractSpinBox:up-button {
|
||||
margin: 0px;
|
||||
background-color: transparent;
|
||||
subcontrol-origin: border;
|
||||
subcontrol-position: top right;
|
||||
border-top-right-radius: 0.3em;
|
||||
border-top: 0px solid transparent;
|
||||
border-right: 0px solid transparent;
|
||||
border-left: 1px solid {color:border};
|
||||
border-bottom: 1px solid {color:border};
|
||||
}
|
||||
|
||||
QAbstractSpinBox:down-button {
|
||||
margin: 0px;
|
||||
background-color: transparent;
|
||||
subcontrol-origin: border;
|
||||
subcontrol-position: bottom right;
|
||||
border-bottom-right-radius: 0.3em;
|
||||
border-bottom: 0px solid transparent;
|
||||
border-right: 0px solid transparent;
|
||||
border-left: 1px solid {color:border};
|
||||
border-top: 1px solid {color:border};
|
||||
}
|
||||
|
||||
QAbstractSpinBox:up-button:focus, QAbstractSpinBox:down-button:focus {
|
||||
border-color: {color:border-focus};
|
||||
}
|
||||
QAbstractSpinBox::up-arrow, QAbstractSpinBox::up-arrow:off {
|
||||
image: url(:/openpype/images/up_arrow.png);
|
||||
width: 0.5em;
|
||||
height: 1em;
|
||||
border-width: 1px;
|
||||
}
|
||||
QAbstractSpinBox::up-arrow:hover {
|
||||
image: url(:/openpype/images/up_arrow_on.png);
|
||||
bottom: 1;
|
||||
}
|
||||
QAbstractSpinBox::up-arrow:disabled {
|
||||
image: url(:/openpype/images/up_arrow_disabled.png);
|
||||
}
|
||||
QAbstractSpinBox::up-arrow:pressed {
|
||||
image: url(:/openpype/images/up_arrow_on.png);
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
QAbstractSpinBox::down-arrow, QAbstractSpinBox::down-arrow:off {
|
||||
image: url(:/openpype/images/down_arrow.png);
|
||||
width: 0.5em;
|
||||
height: 1em;
|
||||
border-width: 1px;
|
||||
}
|
||||
QAbstractSpinBox::down-arrow:hover {
|
||||
image: url(:/openpype/images/down_arrow_on.png);
|
||||
bottom: 1;
|
||||
}
|
||||
QAbstractSpinBox::down-arrow:disabled {
|
||||
image: url(:/openpype/images/down_arrow_disabled.png);
|
||||
}
|
||||
QAbstractSpinBox::down-arrow:hover:pressed {
|
||||
image: url(:/openpype/images/down_arrow_on.png);
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
|
|
@ -210,24 +268,9 @@ QSplitter::handle {
|
|||
border: 3px solid transparent;
|
||||
}
|
||||
|
||||
QSplitter::handle:horizontal {
|
||||
QSplitter::handle:horizontal, QSplitter::handle:vertical, QSplitter::handle:horizontal:hover, QSplitter::handle:vertical:hover {
|
||||
/* must be single like because of Nuke*/
|
||||
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,stop:0.3 rgba(0, 0, 0, 0),stop:0.5 {color:bg-splitter},stop:0.7 rgba(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
QSplitter::handle:vertical {
|
||||
/* must be single like because of Nuke*/
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,stop:0.3 rgba(0, 0, 0, 0),stop:0.5 {color:bg-splitter},stop:0.7 rgba(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
QSplitter::handle:horizontal:hover {
|
||||
/* must be single like because of Nuke*/
|
||||
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,stop:0.3 rgba(0, 0, 0, 0),stop:0.5 {color:bg-splitter-hover},stop:0.7 rgba(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
QSplitter::handle:vertical:hover {
|
||||
/* must be single like because of Nuke*/
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,stop:0.3 rgba(0, 0, 0, 0),stop:0.5 {color:bg-splitter-hover},stop:0.7 rgba(0, 0, 0, 0));
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* SLider */
|
||||
|
|
@ -325,8 +368,8 @@ QTabBar::tab:only-one {
|
|||
}
|
||||
|
||||
QHeaderView {
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
border: 0px solid {color:border};
|
||||
border-radius: 0px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
|
@ -346,6 +389,10 @@ QHeaderView::section:first {
|
|||
QHeaderView::section:last {
|
||||
border-right: none;
|
||||
}
|
||||
QHeaderView::section:only-one {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
QHeaderView::down-arrow {
|
||||
image: url(:/openpype/images/down_arrow.png);
|
||||
|
|
@ -355,10 +402,59 @@ QHeaderView::up-arrow {
|
|||
image: url(:/openpype/images/up_arrow.png);
|
||||
}
|
||||
|
||||
/* Checkboxes */
|
||||
QCheckBox {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QCheckBox::indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
QAbstractItemView::indicator:checked, QCheckBox::indicator:checked {
|
||||
image: url(:/openpype/images/checkbox_checked.png);
|
||||
}
|
||||
QAbstractItemView::indicator:checked:focus, QCheckBox::indicator:checked:focus {
|
||||
image: url(:/openpype/images/checkbox_checked_focus.png);
|
||||
}
|
||||
QAbstractItemView::indicator:checked:hover, QAbstractItemView::indicator:checked:pressed, QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:pressed {
|
||||
image: url(:/openpype/images/checkbox_checked_hover.png);
|
||||
}
|
||||
QAbstractItemView::indicator:checked:disabled, QCheckBox::indicator:checked:disabled {
|
||||
image: url(:/openpype/images/checkbox_checked_disabled.png);
|
||||
}
|
||||
|
||||
QAbstractItemView::indicator:unchecked, QCheckBox::indicator:unchecked {
|
||||
image: url(:/openpype/images/checkbox_unchecked.png);
|
||||
}
|
||||
QAbstractItemView::indicator:unchecked:focus, QCheckBox::indicator:unchecked:focus {
|
||||
image: url(:/openpype/images/checkbox_unchecked_focus.png);
|
||||
}
|
||||
QAbstractItemView::indicator:unchecked:hover, QAbstractItemView::indicator:unchecked:pressed, QCheckBox::indicator:unchecked:hover, QCheckBox::indicator:unchecked:pressed {
|
||||
image: url(:/openpype/images/checkbox_unchecked_hover.png);
|
||||
}
|
||||
QAbstractItemView::indicator:unchecked:disabled, QCheckBox::indicator:unchecked:disabled {
|
||||
image: url(:/openpype/images/checkbox_unchecked_disabled.png);
|
||||
}
|
||||
|
||||
QAbstractItemView::indicator:indeterminate, QCheckBox::indicator:indeterminate {
|
||||
image: url(:/openpype/images/checkbox_indeterminate.png);
|
||||
}
|
||||
QAbstractItemView::indicator:indeterminate:focus, QCheckBox::indicator:indeterminate:focus {
|
||||
image: url(:/openpype/images/checkbox_indeterminate_focus.png);
|
||||
}
|
||||
QAbstractItemView::indicator:indeterminate:hover, QAbstractItemView::indicator:indeterminate:pressed, QCheckBox::indicator:indeterminate:hover, QCheckBox::indicator:indeterminate:pressed {
|
||||
image: url(:/openpype/images/checkbox_indeterminate_hover.png);
|
||||
}
|
||||
QAbstractItemView::indicator:indeterminate:disabled, QCheckBox::indicator:indeterminate:disabled {
|
||||
image: url(:/openpype/images/checkbox_indeterminate_disabled.png);
|
||||
}
|
||||
|
||||
/* Views QListView QTreeView QTableView */
|
||||
QAbstractItemView {
|
||||
border: 0px solid {color:border};
|
||||
border-radius: 0.2em;
|
||||
border-radius: 0px;
|
||||
background: {color:bg-view};
|
||||
alternate-background-color: {color:bg-view-alternate};
|
||||
/* Mac shows selection color on branches. */
|
||||
|
|
@ -373,6 +469,7 @@ QAbstractItemView::item {
|
|||
QAbstractItemView:disabled{
|
||||
background: {color:bg-view-disabled};
|
||||
alternate-background-color: {color:bg-view-alternate-disabled};
|
||||
border: 1px solid {color:border};
|
||||
}
|
||||
|
||||
QAbstractItemView::item:hover {
|
||||
|
|
|
|||
|
|
@ -198,6 +198,8 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
else:
|
||||
self.resize(1300, 700)
|
||||
|
||||
tools_lib.center_window(self)
|
||||
|
||||
if not self._initial_refresh:
|
||||
self._initial_refresh = True
|
||||
self.refresh()
|
||||
|
|
@ -405,7 +407,8 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
self.data["state"]["assetIds"] = asset_ids
|
||||
|
||||
# reset repre list
|
||||
self._repres_widget.set_version_ids([])
|
||||
if self._repres_widget:
|
||||
self._repres_widget.set_version_ids([])
|
||||
|
||||
def _subsetschanged(self):
|
||||
asset_ids = self.data["state"]["assetIds"]
|
||||
|
|
@ -495,7 +498,8 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
self._thumbnail_widget.set_thumbnail(thumbnail_docs)
|
||||
|
||||
version_ids = [doc["_id"] for doc in version_docs or []]
|
||||
self._repres_widget.set_version_ids(version_ids)
|
||||
if self._repres_widget:
|
||||
self._repres_widget.set_version_ids(version_ids)
|
||||
|
||||
def _set_context(self, context, refresh=True):
|
||||
"""Set the selection in the interface using a context.
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
self.resize(1800, 900)
|
||||
else:
|
||||
self.resize(1300, 700)
|
||||
lib.center_window(self)
|
||||
|
||||
# -------------------------------
|
||||
# Delay calling blocking methods
|
||||
|
|
|
|||
|
|
@ -15,6 +15,12 @@ from openpype.tools.utils.models import TreeModel, Item
|
|||
from openpype.tools.utils import lib
|
||||
|
||||
from openpype.modules import ModulesManager
|
||||
from openpype.tools.utils.constants import (
|
||||
LOCAL_PROVIDER_ROLE,
|
||||
REMOTE_PROVIDER_ROLE,
|
||||
LOCAL_AVAILABILITY_ROLE,
|
||||
REMOTE_AVAILABILITY_ROLE
|
||||
)
|
||||
|
||||
|
||||
def is_filtering_recursible():
|
||||
|
|
@ -333,7 +339,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
repre_info = version_data.get("repre_info")
|
||||
if repre_info:
|
||||
item["repre_info"] = repre_info
|
||||
item["repre_icon"] = version_data.get("repre_icon")
|
||||
|
||||
def _fetch(self):
|
||||
asset_docs = self.dbcon.find(
|
||||
|
|
@ -445,14 +450,16 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
for _subset_id, doc in last_versions_by_subset_id.items():
|
||||
version_ids.add(doc["_id"])
|
||||
|
||||
site = self.active_site
|
||||
query = self._repre_per_version_pipeline(list(version_ids), site)
|
||||
query = self._repre_per_version_pipeline(list(version_ids),
|
||||
self.active_site,
|
||||
self.remote_site)
|
||||
|
||||
repre_info = {}
|
||||
for doc in self.dbcon.aggregate(query):
|
||||
if self._doc_fetching_stop:
|
||||
return
|
||||
doc["provider"] = self.active_provider
|
||||
doc["active_provider"] = self.active_provider
|
||||
doc["remote_provider"] = self.remote_provider
|
||||
repre_info[doc["_id"]] = doc
|
||||
|
||||
self._doc_payload["repre_info_by_version_id"] = repre_info
|
||||
|
|
@ -666,8 +673,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
if not index.isValid():
|
||||
return
|
||||
|
||||
item = index.internalPointer()
|
||||
if role == self.SortDescendingRole:
|
||||
item = index.internalPointer()
|
||||
if item.get("isGroup"):
|
||||
# Ensure groups be on top when sorting by descending order
|
||||
prefix = "2"
|
||||
|
|
@ -683,7 +690,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
return prefix + order
|
||||
|
||||
if role == self.SortAscendingRole:
|
||||
item = index.internalPointer()
|
||||
if item.get("isGroup"):
|
||||
# Ensure groups be on top when sorting by ascending order
|
||||
prefix = "0"
|
||||
|
|
@ -701,14 +707,12 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
if role == QtCore.Qt.DisplayRole:
|
||||
if index.column() == self.columns_index["family"]:
|
||||
# Show familyLabel instead of family
|
||||
item = index.internalPointer()
|
||||
return item.get("familyLabel", None)
|
||||
|
||||
elif role == QtCore.Qt.DecorationRole:
|
||||
|
||||
# Add icon to subset column
|
||||
if index.column() == self.columns_index["subset"]:
|
||||
item = index.internalPointer()
|
||||
if item.get("isGroup") or item.get("isMerged"):
|
||||
return item["icon"]
|
||||
else:
|
||||
|
|
@ -716,20 +720,32 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
|
||||
# Add icon to family column
|
||||
if index.column() == self.columns_index["family"]:
|
||||
item = index.internalPointer()
|
||||
return item.get("familyIcon", None)
|
||||
|
||||
if index.column() == self.columns_index.get("repre_info"):
|
||||
item = index.internalPointer()
|
||||
return item.get("repre_icon", None)
|
||||
|
||||
elif role == QtCore.Qt.ForegroundRole:
|
||||
item = index.internalPointer()
|
||||
version_doc = item.get("version_document")
|
||||
if version_doc and version_doc.get("type") == "hero_version":
|
||||
if not version_doc["is_from_latest"]:
|
||||
return self.not_last_hero_brush
|
||||
|
||||
elif role == LOCAL_AVAILABILITY_ROLE:
|
||||
if not item.get("isGroup"):
|
||||
return item.get("repre_info_local")
|
||||
else:
|
||||
return None
|
||||
|
||||
elif role == REMOTE_AVAILABILITY_ROLE:
|
||||
if not item.get("isGroup"):
|
||||
return item.get("repre_info_remote")
|
||||
else:
|
||||
return None
|
||||
|
||||
elif role == LOCAL_PROVIDER_ROLE:
|
||||
return self.active_provider
|
||||
|
||||
elif role == REMOTE_PROVIDER_ROLE:
|
||||
return self.remote_provider
|
||||
|
||||
return super(SubsetsModel, self).data(index, role)
|
||||
|
||||
def flags(self, index):
|
||||
|
|
@ -759,19 +775,25 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
return data
|
||||
|
||||
def _get_repre_dict(self, repre_info):
|
||||
"""Returns icon and str representation of availability"""
|
||||
"""Returns str representation of availability"""
|
||||
data = {}
|
||||
if repre_info:
|
||||
repres_str = "{}/{}".format(
|
||||
int(math.floor(float(repre_info['avail_repre']))),
|
||||
int(math.floor(float(repre_info['avail_repre_local']))),
|
||||
int(math.floor(float(repre_info['repre_count']))))
|
||||
|
||||
data["repre_info"] = repres_str
|
||||
data["repre_icon"] = self.repre_icons.get(self.active_provider)
|
||||
data["repre_info_local"] = repres_str
|
||||
|
||||
repres_str = "{}/{}".format(
|
||||
int(math.floor(float(repre_info['avail_repre_remote']))),
|
||||
int(math.floor(float(repre_info['repre_count']))))
|
||||
|
||||
data["repre_info_remote"] = repres_str
|
||||
|
||||
return data
|
||||
|
||||
def _repre_per_version_pipeline(self, version_ids, site):
|
||||
def _repre_per_version_pipeline(self, version_ids,
|
||||
active_site, remote_site):
|
||||
query = [
|
||||
{"$match": {"parent": {"$in": version_ids},
|
||||
"type": "representation",
|
||||
|
|
@ -780,7 +802,13 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
{'$addFields': {
|
||||
'order_local': {
|
||||
'$filter': {'input': '$files.sites', 'as': 'p',
|
||||
'cond': {'$eq': ['$$p.name', site]}
|
||||
'cond': {'$eq': ['$$p.name', active_site]}
|
||||
}}
|
||||
}},
|
||||
{'$addFields': {
|
||||
'order_remote': {
|
||||
'$filter': {'input': '$files.sites', 'as': 'p',
|
||||
'cond': {'$eq': ['$$p.name', remote_site]}
|
||||
}}
|
||||
}},
|
||||
{'$addFields': {
|
||||
|
|
@ -795,19 +823,32 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
]}
|
||||
]}, 0]}
|
||||
}},
|
||||
{'$addFields': {
|
||||
'progress_remote': {"$arrayElemAt": [{
|
||||
'$cond': [{'$size': "$order_remote.progress"},
|
||||
"$order_remote.progress",
|
||||
# if exists created_dt count is as available
|
||||
{'$cond': [
|
||||
{'$size': "$order_remote.created_dt"},
|
||||
[1],
|
||||
[0]
|
||||
]}
|
||||
]}, 0]}
|
||||
}},
|
||||
{'$group': { # first group by repre
|
||||
'_id': '$_id',
|
||||
'parent': {'$first': '$parent'},
|
||||
'files_count': {'$sum': 1},
|
||||
'files_avail': {'$sum': "$progress_local"},
|
||||
'avail_ratio': {'$first': {
|
||||
'$divide': [{'$sum': "$progress_local"}, {'$sum': 1}]}}
|
||||
'avail_ratio_local': {'$first': {
|
||||
'$divide': [{'$sum': "$progress_local"}, {'$sum': 1}]}},
|
||||
'avail_ratio_remote': {'$first': {
|
||||
'$divide': [{'$sum': "$progress_remote"}, {'$sum': 1}]}}
|
||||
}},
|
||||
{'$group': { # second group by parent, eg version_id
|
||||
'_id': '$parent',
|
||||
'repre_count': {'$sum': 1}, # total representations
|
||||
# fully available representation for site
|
||||
'avail_repre': {'$sum': "$avail_ratio"}
|
||||
'avail_repre_local': {'$sum': "$avail_ratio_local"},
|
||||
'avail_repre_remote': {'$sum': "$avail_ratio_remote"},
|
||||
}},
|
||||
]
|
||||
return query
|
||||
|
|
|
|||
|
|
@ -31,6 +31,13 @@ from .model import (
|
|||
)
|
||||
from . import lib
|
||||
|
||||
from openpype.tools.utils.constants import (
|
||||
LOCAL_PROVIDER_ROLE,
|
||||
REMOTE_PROVIDER_ROLE,
|
||||
LOCAL_AVAILABILITY_ROLE,
|
||||
REMOTE_AVAILABILITY_ROLE
|
||||
)
|
||||
|
||||
|
||||
class OverlayFrame(QtWidgets.QFrame):
|
||||
def __init__(self, label, parent):
|
||||
|
|
@ -197,6 +204,10 @@ class SubsetWidget(QtWidgets.QWidget):
|
|||
column = model.Columns.index("time")
|
||||
view.setItemDelegateForColumn(column, time_delegate)
|
||||
|
||||
avail_delegate = AvailabilityDelegate(self.dbcon, view)
|
||||
column = model.Columns.index("repre_info")
|
||||
view.setItemDelegateForColumn(column, avail_delegate)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addLayout(top_bar_layout)
|
||||
|
|
@ -1578,3 +1589,54 @@ def _load_subsets_by_loader(loader, subset_contexts, options,
|
|||
))
|
||||
|
||||
return error_info
|
||||
|
||||
|
||||
class AvailabilityDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""
|
||||
Prints icons and downloaded representation ration for both sides.
|
||||
"""
|
||||
|
||||
def __init__(self, dbcon, parent=None):
|
||||
super(AvailabilityDelegate, self).__init__(parent)
|
||||
self.icons = tools_lib.get_repre_icons()
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
super(AvailabilityDelegate, self).paint(painter, option, index)
|
||||
option = QtWidgets.QStyleOptionViewItem(option)
|
||||
option.showDecorationSelected = True
|
||||
|
||||
provider_active = index.data(LOCAL_PROVIDER_ROLE)
|
||||
provider_remote = index.data(REMOTE_PROVIDER_ROLE)
|
||||
|
||||
availability_active = index.data(LOCAL_AVAILABILITY_ROLE)
|
||||
availability_remote = index.data(REMOTE_AVAILABILITY_ROLE)
|
||||
|
||||
if not availability_active or not availability_remote: # group lines
|
||||
return
|
||||
|
||||
idx = 0
|
||||
height = width = 24
|
||||
for value, provider in [(availability_active, provider_active),
|
||||
(availability_remote, provider_remote)]:
|
||||
icon = self.icons.get(provider)
|
||||
if not icon:
|
||||
continue
|
||||
|
||||
pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(height, width)))
|
||||
padding = 10 + (70 * idx)
|
||||
point = QtCore.QPoint(option.rect.x() + padding,
|
||||
option.rect.y() +
|
||||
(option.rect.height() - pixmap.height()) / 2)
|
||||
painter.drawPixmap(point, pixmap)
|
||||
|
||||
text_rect = option.rect.translated(padding + width + 10, 0)
|
||||
painter.drawText(
|
||||
text_rect,
|
||||
option.displayAlignment,
|
||||
value
|
||||
)
|
||||
|
||||
idx += 1
|
||||
|
||||
def displayText(self, value, locale):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -190,7 +190,9 @@ class Controller(QtCore.QObject):
|
|||
|
||||
plugins = pyblish.api.discover()
|
||||
|
||||
targets = pyblish.logic.registered_targets() or ["default"]
|
||||
targets = set(pyblish.logic.registered_targets())
|
||||
targets.add("default")
|
||||
targets = list(targets)
|
||||
plugins_by_targets = pyblish.logic.plugins_by_targets(plugins, targets)
|
||||
|
||||
_plugins = []
|
||||
|
|
|
|||
|
|
@ -8,3 +8,22 @@ PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 102
|
|||
TASK_NAME_ROLE = QtCore.Qt.UserRole + 301
|
||||
TASK_TYPE_ROLE = QtCore.Qt.UserRole + 302
|
||||
TASK_ORDER_ROLE = QtCore.Qt.UserRole + 403
|
||||
|
||||
LOCAL_PROVIDER_ROLE = QtCore.Qt.UserRole + 500 # provider of active site
|
||||
REMOTE_PROVIDER_ROLE = QtCore.Qt.UserRole + 501 # provider of remote site
|
||||
LOCAL_PROGRESS_ROLE = QtCore.Qt.UserRole + 502 # percentage downld on active
|
||||
REMOTE_PROGRESS_ROLE = QtCore.Qt.UserRole + 503 # percentage upload on remote
|
||||
LOCAL_AVAILABILITY_ROLE = QtCore.Qt.UserRole + 504 # ratio of presence active
|
||||
REMOTE_AVAILABILITY_ROLE = QtCore.Qt.UserRole + 505
|
||||
LOCAL_DATE_ROLE = QtCore.Qt.UserRole + 506 # created_dt on active site
|
||||
REMOTE_DATE_ROLE = QtCore.Qt.UserRole + 507
|
||||
LOCAL_FAILED_ROLE = QtCore.Qt.UserRole + 508
|
||||
REMOTE_FAILED_ROLE = QtCore.Qt.UserRole + 509
|
||||
HEADER_NAME_ROLE = QtCore.Qt.UserRole + 510
|
||||
EDIT_ICON_ROLE = QtCore.Qt.UserRole + 511
|
||||
STATUS_ROLE = QtCore.Qt.UserRole + 512
|
||||
PATH_ROLE = QtCore.Qt.UserRole + 513
|
||||
LOCAL_SITE_NAME_ROLE = QtCore.Qt.UserRole + 514
|
||||
REMOTE_SITE_NAME_ROLE = QtCore.Qt.UserRole + 515
|
||||
ERROR_ROLE = QtCore.Qt.UserRole + 516
|
||||
TRIES_ROLE = QtCore.Qt.UserRole + 517
|
||||
|
|
|
|||
|
|
@ -55,8 +55,6 @@ class HostToolsHelper:
|
|||
|
||||
def show_workfiles(self, parent=None, use_context=None, save=None):
|
||||
"""Workfiles tool for changing context and saving workfiles."""
|
||||
from avalon import style
|
||||
|
||||
if use_context is None:
|
||||
use_context = True
|
||||
|
||||
|
|
@ -80,7 +78,6 @@ class HostToolsHelper:
|
|||
# Pull window to the front.
|
||||
workfiles_tool.raise_()
|
||||
workfiles_tool.activateWindow()
|
||||
workfiles_tool.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
def get_loader_tool(self, parent):
|
||||
"""Create, cache and return loader tool window."""
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ def center_window(window):
|
|||
screen_geo = desktop.screenGeometry(screen_idx)
|
||||
geo = window.frameGeometry()
|
||||
geo.moveCenter(screen_geo.center())
|
||||
if geo.y() < screen_geo.y():
|
||||
geo.setY(screen_geo.y())
|
||||
window.move(geo.topLeft())
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import datetime
|
|||
|
||||
import Qt
|
||||
from Qt import QtWidgets, QtCore
|
||||
from avalon import style, io, api, pipeline
|
||||
from avalon import io, api, pipeline
|
||||
|
||||
from openpype import style
|
||||
from openpype.tools.utils.lib import (
|
||||
schedule, qt_app_context
|
||||
)
|
||||
|
|
@ -131,6 +132,9 @@ class NameWindow(QtWidgets.QDialog):
|
|||
|
||||
# Extensions combobox
|
||||
ext_combo = QtWidgets.QComboBox(inputs_widget)
|
||||
# Add styled delegate to use stylesheets
|
||||
ext_delegate = QtWidgets.QStyledItemDelegate()
|
||||
ext_combo.setItemDelegate(ext_delegate)
|
||||
ext_combo.addItems(self.host.file_extensions())
|
||||
|
||||
# Build inputs
|
||||
|
|
@ -186,6 +190,7 @@ class NameWindow(QtWidgets.QDialog):
|
|||
self.preview_label = preview_label
|
||||
self.subversion_input = subversion_input
|
||||
self.ext_combo = ext_combo
|
||||
self._ext_delegate = ext_delegate
|
||||
|
||||
self.refresh()
|
||||
|
||||
|
|
@ -426,6 +431,7 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
"""A widget displaying files that allows to save and open files."""
|
||||
file_selected = QtCore.Signal(str)
|
||||
workfile_created = QtCore.Signal(str)
|
||||
file_opened = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(FilesWidget, self).__init__(parent=parent)
|
||||
|
|
@ -616,7 +622,7 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
|
||||
self._enter_session()
|
||||
host.open_file(filepath)
|
||||
self.window().close()
|
||||
self.file_opened.emit()
|
||||
|
||||
def save_changes_prompt(self):
|
||||
self._messagebox = messagebox = QtWidgets.QMessageBox()
|
||||
|
|
@ -634,7 +640,7 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
|
||||
# Parenting the QMessageBox to the Widget seems to crash
|
||||
# so we skip parenting and explicitly apply the stylesheet.
|
||||
messagebox.setStyleSheet(style.load_stylesheet())
|
||||
messagebox.setStyle(self.style())
|
||||
|
||||
result = messagebox.exec_()
|
||||
if result == messagebox.Yes:
|
||||
|
|
@ -994,6 +1000,7 @@ class Window(QtWidgets.QMainWindow):
|
|||
tasks_widget.task_changed.connect(self.on_task_changed)
|
||||
files_widget.file_selected.connect(self.on_file_select)
|
||||
files_widget.workfile_created.connect(self.on_workfile_create)
|
||||
files_widget.file_opened.connect(self._on_file_opened)
|
||||
side_panel.save_clicked.connect(self.on_side_panel_save)
|
||||
|
||||
self.home_page_widget = home_page_widget
|
||||
|
|
@ -1006,13 +1013,19 @@ class Window(QtWidgets.QMainWindow):
|
|||
self.files_widget = files_widget
|
||||
self.side_panel = side_panel
|
||||
|
||||
self.refresh()
|
||||
|
||||
# Force focus on the open button by default, required for Houdini.
|
||||
files_widget.btn_open.setFocus()
|
||||
|
||||
self.resize(1200, 600)
|
||||
|
||||
self._first_show = True
|
||||
|
||||
def showEvent(self, event):
|
||||
super(Window, self).showEvent(event)
|
||||
if self._first_show:
|
||||
self._first_show = False
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""Custom keyPressEvent.
|
||||
|
||||
|
|
@ -1054,6 +1067,9 @@ class Window(QtWidgets.QMainWindow):
|
|||
def on_workfile_create(self, filepath):
|
||||
self._create_workfile_doc(filepath)
|
||||
|
||||
def _on_file_opened(self):
|
||||
self.close()
|
||||
|
||||
def on_side_panel_save(self):
|
||||
workfile_doc, data = self.side_panel.get_workfile_data()
|
||||
if not workfile_doc:
|
||||
|
|
@ -1201,7 +1217,6 @@ def show(root=None, debug=False, parent=None, use_context=True, save=True):
|
|||
window.set_save_enabled(save)
|
||||
|
||||
window.show()
|
||||
window.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
module.window = window
|
||||
|
||||
|
|
|
|||
2
openpype/vendor/python/common/capture.py
vendored
|
|
@ -116,6 +116,8 @@ def capture(camera=None,
|
|||
if not cmds.objExists(camera):
|
||||
raise RuntimeError("Camera does not exist: {0}".format(camera))
|
||||
|
||||
if width and height :
|
||||
maintain_aspect_ratio = False
|
||||
width = width or cmds.getAttr("defaultResolution.width")
|
||||
height = height or cmds.getAttr("defaultResolution.height")
|
||||
if maintain_aspect_ratio:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.6.0-nightly.4"
|
||||
__version__ = "3.6.0-nightly.5"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.6.0-nightly.4" # OpenPype
|
||||
version = "3.6.0-nightly.5" # OpenPype
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
30
start.py
|
|
@ -293,20 +293,32 @@ def run_disk_mapping_commands(mongo_url):
|
|||
|
||||
mappings = disk_mapping.get(low_platform) or []
|
||||
for source, destination in mappings:
|
||||
args = ["subst", destination.rstrip('/'), source.rstrip('/')]
|
||||
destination = destination.rstrip('/')
|
||||
source = source.rstrip('/')
|
||||
|
||||
if low_platform == "windows":
|
||||
args = ["subst", destination, source]
|
||||
elif low_platform == "darwin":
|
||||
scr = "do shell script \"ln -s {} {}\" with administrator privileges".format(source, destination) # noqa: E501
|
||||
args = ["osascript", "-e", scr]
|
||||
else:
|
||||
args = ["sudo", "ln", "-s", source, destination]
|
||||
|
||||
_print("disk mapping args:: {}".format(args))
|
||||
try:
|
||||
output = subprocess.Popen(args)
|
||||
if output.returncode and output.returncode != 0:
|
||||
exc_msg = "Executing args was not successful: \"{}\"".format(
|
||||
args)
|
||||
if not os.path.exists(destination):
|
||||
output = subprocess.Popen(args)
|
||||
if output.returncode and output.returncode != 0:
|
||||
exc_msg = "Executing was not successful: \"{}\"".format(
|
||||
args)
|
||||
|
||||
raise RuntimeError(exc_msg)
|
||||
except TypeError:
|
||||
_print("Error in mapping drive")
|
||||
raise RuntimeError(exc_msg)
|
||||
except TypeError as exc:
|
||||
_print("Error {} in mapping drive {}, {}".format(str(exc),
|
||||
source,
|
||||
destination))
|
||||
raise
|
||||
|
||||
|
||||
def set_avalon_environments():
|
||||
"""Set avalon specific environments.
|
||||
|
||||
|
|
|
|||