mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' of github.com:pypeclub/OpenPype into chore/OP-2414_Move-harmony-to-openpype
This commit is contained in:
commit
28a745d54d
71 changed files with 2628 additions and 1165 deletions
56
CHANGELOG.md
56
CHANGELOG.md
|
|
@ -1,8 +1,38 @@
|
|||
# Changelog
|
||||
|
||||
## [3.8.3-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.2...HEAD)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- documentation: add example to `repack-version` command [\#2669](https://github.com/pypeclub/OpenPype/pull/2669)
|
||||
- Update docusaurus [\#2639](https://github.com/pypeclub/OpenPype/pull/2639)
|
||||
- Documentation: Fixed relative links [\#2621](https://github.com/pypeclub/OpenPype/pull/2621)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Ftrack: Sync description to assets [\#2670](https://github.com/pypeclub/OpenPype/pull/2670)
|
||||
- Houdini: Moved to OpenPype [\#2658](https://github.com/pypeclub/OpenPype/pull/2658)
|
||||
- Maya: Move implementation to OpenPype [\#2649](https://github.com/pypeclub/OpenPype/pull/2649)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Maya: Fix menu callbacks [\#2671](https://github.com/pypeclub/OpenPype/pull/2671)
|
||||
- hiero: removing obsolete unsupported plugin [\#2667](https://github.com/pypeclub/OpenPype/pull/2667)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix python install in docker for centos7 [\#2664](https://github.com/pypeclub/OpenPype/pull/2664)
|
||||
- Deadline: Be able to pass Mongo url to job [\#2616](https://github.com/pypeclub/OpenPype/pull/2616)
|
||||
|
||||
## [3.8.2](https://github.com/pypeclub/OpenPype/tree/3.8.2) (2022-02-07)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.1...3.8.2)
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.8.2-nightly.3...3.8.2)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Cosmetics: Fix common typos in openpype/website [\#2617](https://github.com/pypeclub/OpenPype/pull/2617)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
|
|
@ -11,21 +41,18 @@
|
|||
- nuke: adding clear button to write nodes [\#2627](https://github.com/pypeclub/OpenPype/pull/2627)
|
||||
- Ftrack: Family to Asset type mapping is in settings [\#2602](https://github.com/pypeclub/OpenPype/pull/2602)
|
||||
- Nuke: load color space from representation data [\#2576](https://github.com/pypeclub/OpenPype/pull/2576)
|
||||
- New Publisher: New features and preparations for new standalone publisher [\#2556](https://github.com/pypeclub/OpenPype/pull/2556)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Fix pulling of cx\_freeze 6.10 [\#2628](https://github.com/pypeclub/OpenPype/pull/2628)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Cosmetics: Fix common typos in openpype/website [\#2617](https://github.com/pypeclub/OpenPype/pull/2617)
|
||||
- Global: fix broken otio review extractor [\#2590](https://github.com/pypeclub/OpenPype/pull/2590)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Docker: enhance dockerfiles with metadata, fix pyenv initialization [\#2647](https://github.com/pypeclub/OpenPype/pull/2647)
|
||||
- WebPublisher: fix instance duplicates [\#2641](https://github.com/pypeclub/OpenPype/pull/2641)
|
||||
- Fix - safer pulling of task name for webpublishing from PS [\#2613](https://github.com/pypeclub/OpenPype/pull/2613)
|
||||
- Webpublisher: Skip version collect [\#2591](https://github.com/pypeclub/OpenPype/pull/2591)
|
||||
|
||||
## [3.8.1](https://github.com/pypeclub/OpenPype/tree/3.8.1) (2022-02-01)
|
||||
|
||||
|
|
@ -34,7 +61,6 @@
|
|||
**🚀 Enhancements**
|
||||
|
||||
- Webpublisher: Thumbnail extractor [\#2600](https://github.com/pypeclub/OpenPype/pull/2600)
|
||||
- Webpublisher: Added endpoint to reprocess batch through UI [\#2555](https://github.com/pypeclub/OpenPype/pull/2555)
|
||||
- Loader: Allow to toggle default family filters between "include" or "exclude" filtering [\#2541](https://github.com/pypeclub/OpenPype/pull/2541)
|
||||
- Launcher: Added context menu to to skip opening last workfile [\#2536](https://github.com/pypeclub/OpenPype/pull/2536)
|
||||
|
||||
|
|
@ -44,34 +70,37 @@
|
|||
- hotfix: OIIO tool path - add extension on windows [\#2618](https://github.com/pypeclub/OpenPype/pull/2618)
|
||||
- Settings: Enum does not store empty string if has single item to select [\#2615](https://github.com/pypeclub/OpenPype/pull/2615)
|
||||
- switch distutils to sysconfig for `get\_platform\(\)` [\#2594](https://github.com/pypeclub/OpenPype/pull/2594)
|
||||
- Global: fix broken otio review extractor [\#2590](https://github.com/pypeclub/OpenPype/pull/2590)
|
||||
- Fix poetry index and speedcopy update [\#2589](https://github.com/pypeclub/OpenPype/pull/2589)
|
||||
- Webpublisher: Fix - subset names from processed .psd used wrong value for task [\#2586](https://github.com/pypeclub/OpenPype/pull/2586)
|
||||
- `vrscene` creator Deadline webservice URL handling [\#2580](https://github.com/pypeclub/OpenPype/pull/2580)
|
||||
- global: track name was failing if duplicated root word in name [\#2568](https://github.com/pypeclub/OpenPype/pull/2568)
|
||||
- Validate Maya Rig produces no cycle errors [\#2484](https://github.com/pypeclub/OpenPype/pull/2484)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Bump pillow from 8.4.0 to 9.0.0 [\#2595](https://github.com/pypeclub/OpenPype/pull/2595)
|
||||
- Webpublisher: Skip version collect [\#2591](https://github.com/pypeclub/OpenPype/pull/2591)
|
||||
- build\(deps\): bump pillow from 8.4.0 to 9.0.0 [\#2523](https://github.com/pypeclub/OpenPype/pull/2523)
|
||||
|
||||
## [3.8.0](https://github.com/pypeclub/OpenPype/tree/3.8.0) (2022-01-24)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.8.0-nightly.7...3.8.0)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Variable in docs renamed to proper name [\#2546](https://github.com/pypeclub/OpenPype/pull/2546)
|
||||
|
||||
**🆕 New features**
|
||||
|
||||
- Flame: extracting segments with trans-coding [\#2547](https://github.com/pypeclub/OpenPype/pull/2547)
|
||||
- Maya : V-Ray Proxy - load all ABC files via proxy [\#2544](https://github.com/pypeclub/OpenPype/pull/2544)
|
||||
- Maya to Unreal: Extended static mesh workflow [\#2537](https://github.com/pypeclub/OpenPype/pull/2537)
|
||||
- Flame: collecting publishable instances [\#2519](https://github.com/pypeclub/OpenPype/pull/2519)
|
||||
- Flame: create publishable clips [\#2495](https://github.com/pypeclub/OpenPype/pull/2495)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Webpublisher: Moved error at the beginning of the log [\#2559](https://github.com/pypeclub/OpenPype/pull/2559)
|
||||
- Ftrack: Use ApplicationManager to get DJV path [\#2558](https://github.com/pypeclub/OpenPype/pull/2558)
|
||||
- Webpublisher: Added endpoint to reprocess batch through UI [\#2555](https://github.com/pypeclub/OpenPype/pull/2555)
|
||||
- Settings: PathInput strip passed string [\#2550](https://github.com/pypeclub/OpenPype/pull/2550)
|
||||
- Global: Exctract Review anatomy fill data with output name [\#2548](https://github.com/pypeclub/OpenPype/pull/2548)
|
||||
- Cosmetics: Clean up some cosmetics / typos [\#2542](https://github.com/pypeclub/OpenPype/pull/2542)
|
||||
|
|
@ -79,9 +108,6 @@
|
|||
- General: Be able to use anatomy data in ffmpeg output arguments [\#2525](https://github.com/pypeclub/OpenPype/pull/2525)
|
||||
- Expose toggle publish plug-in settings for Maya Look Shading Engine Naming [\#2521](https://github.com/pypeclub/OpenPype/pull/2521)
|
||||
- Photoshop: Move implementation to OpenPype [\#2510](https://github.com/pypeclub/OpenPype/pull/2510)
|
||||
- Slack: notifications are sent with Openpype logo and bot name [\#2499](https://github.com/pypeclub/OpenPype/pull/2499)
|
||||
- Slack: Add review to notification message [\#2498](https://github.com/pypeclub/OpenPype/pull/2498)
|
||||
- Maya: Collect 'fps' animation data only for "review" instances [\#2486](https://github.com/pypeclub/OpenPype/pull/2486)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
|
|
@ -103,10 +129,6 @@
|
|||
- Maya: reset empty string attributes correctly to "" instead of "None" [\#2506](https://github.com/pypeclub/OpenPype/pull/2506)
|
||||
- Improve FusionPreLaunch hook errors [\#2505](https://github.com/pypeclub/OpenPype/pull/2505)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Variable in docs renamed to proper name [\#2546](https://github.com/pypeclub/OpenPype/pull/2546)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- AfterEffects: Move implementation to OpenPype [\#2543](https://github.com/pypeclub/OpenPype/pull/2543)
|
||||
|
|
|
|||
|
|
@ -1,352 +0,0 @@
|
|||
# version_up_everywhere.py
|
||||
# Adds action to enable a Clip/Shot to be Min/Max/Next/Prev versioned in all shots used in a Project.
|
||||
#
|
||||
# Usage:
|
||||
# 1) Copy file to <HIERO_PLUGIN_PATH>/Python/Startup
|
||||
# 2) Right-click on Clip(s) or Bins containing Clips in in the Bin View, or on Shots in the Timeline/Spreadsheet
|
||||
# 3) Set Version for all Shots > OPTION to update the version in all shots where the Clip is used in the Project.
|
||||
|
||||
import hiero.core
|
||||
try:
|
||||
from PySide.QtGui import *
|
||||
from PySide.QtCore import *
|
||||
except:
|
||||
from PySide2.QtGui import *
|
||||
from PySide2.QtWidgets import *
|
||||
from PySide2.QtCore import *
|
||||
|
||||
|
||||
def whereAmI(self, searchType="TrackItem"):
|
||||
"""returns a list of TrackItem or Sequnece objects in the Project which contain this Clip.
|
||||
By default this will return a list of TrackItems where the Clip is used in its project.
|
||||
You can also return a list of Sequences by specifying the searchType to be "Sequence".
|
||||
Should consider putting this into hiero.core.Clip by default?
|
||||
|
||||
Example usage:
|
||||
|
||||
shotsForClip = clip.whereAmI("TrackItem")
|
||||
sequencesForClip = clip.whereAmI("Sequence")
|
||||
"""
|
||||
proj = self.project()
|
||||
|
||||
if ("TrackItem" not in searchType) and ("Sequence" not in searchType):
|
||||
print("searchType argument must be \"TrackItem\" or \"Sequence\"")
|
||||
return None
|
||||
|
||||
# If user specifies a TrackItem, then it will return
|
||||
searches = hiero.core.findItemsInProject(proj, searchType)
|
||||
|
||||
if len(searches) == 0:
|
||||
print("Unable to find {} in any items of type: {}".format(
|
||||
str(self), searchType))
|
||||
return None
|
||||
|
||||
# Case 1: Looking for Shots (trackItems)
|
||||
clipUsedIn = []
|
||||
if isinstance(searches[0], hiero.core.TrackItem):
|
||||
for shot in searches:
|
||||
# We have to wrap this in a try/except because it's possible through the Python API for a Shot to exist without a Clip in the Bin
|
||||
try:
|
||||
|
||||
# For versioning to work, we must look to the BinItem that a Clip is wrapped in.
|
||||
if shot.source().binItem() == self.binItem():
|
||||
clipUsedIn.append(shot)
|
||||
|
||||
# If we throw an exception here its because the Shot did not have a Source Clip in the Bin.
|
||||
except RuntimeError:
|
||||
hiero.core.log.info(
|
||||
'Unable to find Parent Clip BinItem for Shot: %s, Source:%s'
|
||||
% (shot, shot.source()))
|
||||
pass
|
||||
|
||||
# Case 1: Looking for Shots (trackItems)
|
||||
elif isinstance(searches[0], hiero.core.Sequence):
|
||||
for seq in searches:
|
||||
# Iterate tracks > shots...
|
||||
tracks = seq.items()
|
||||
for track in tracks:
|
||||
shots = track.items()
|
||||
for shot in shots:
|
||||
if shot.source().binItem() == self.binItem():
|
||||
clipUsedIn.append(seq)
|
||||
|
||||
return clipUsedIn
|
||||
|
||||
|
||||
# Add whereAmI method to Clip object
|
||||
hiero.core.Clip.whereAmI = whereAmI
|
||||
|
||||
|
||||
#### MAIN VERSION EVERYWHERE GUBBINS #####
|
||||
class VersionAllMenu(object):
|
||||
|
||||
# These are a set of action names we can use for operating on multiple Clip/TrackItems
|
||||
eMaxVersion = "Max Version"
|
||||
eMinVersion = "Min Version"
|
||||
eNextVersion = "Next Version"
|
||||
ePreviousVersion = "Previous Version"
|
||||
|
||||
# This is the title used for the Version Menu title. It's long isn't it?
|
||||
actionTitle = "Set Version for all Shots"
|
||||
|
||||
def __init__(self):
|
||||
self._versionEverywhereMenu = None
|
||||
self._versionActions = []
|
||||
|
||||
hiero.core.events.registerInterest("kShowContextMenu/kBin",
|
||||
self.binViewEventHandler)
|
||||
hiero.core.events.registerInterest("kShowContextMenu/kTimeline",
|
||||
self.binViewEventHandler)
|
||||
hiero.core.events.registerInterest("kShowContextMenu/kSpreadsheet",
|
||||
self.binViewEventHandler)
|
||||
|
||||
def showVersionUpdateReportFromShotManifest(self, sequenceShotManifest):
|
||||
"""This just displays an info Message box, based on a Sequence[Shot] manifest dictionary"""
|
||||
|
||||
# Now present an info dialog, explaining where shots were updated
|
||||
updateReportString = "The following Versions were updated:\n"
|
||||
for seq in sequenceShotManifest.keys():
|
||||
updateReportString += "%s:\n Shots:\n" % (seq.name())
|
||||
for shot in sequenceShotManifest[seq]:
|
||||
updateReportString += ' %s\n (New Version: %s)\n' % (
|
||||
shot.name(), shot.currentVersion().name())
|
||||
updateReportString += "\n"
|
||||
|
||||
infoBox = QMessageBox(hiero.ui.mainWindow())
|
||||
infoBox.setIcon(QMessageBox.Information)
|
||||
|
||||
if len(sequenceShotManifest) <= 0:
|
||||
infoBox.setText("No Shot Versions were updated")
|
||||
infoBox.setInformativeText(
|
||||
"Clip could not be found in any Shots in this Project")
|
||||
else:
|
||||
infoBox.setText(
|
||||
"Versions were updated in %i Sequences of this Project." %
|
||||
(len(sequenceShotManifest)))
|
||||
infoBox.setInformativeText("Show Details for more info.")
|
||||
infoBox.setDetailedText(updateReportString)
|
||||
|
||||
infoBox.exec_()
|
||||
|
||||
def makeVersionActionForSingleClip(self, version):
|
||||
"""This is used to populate the QAction list of Versions when a single Clip is selected in the BinView.
|
||||
It also triggers the Version Update action based on the version passed to it.
|
||||
(Not sure if this is good design practice, but it's compact!)"""
|
||||
action = QAction(version.name(), None)
|
||||
action.setData(lambda: version)
|
||||
|
||||
def updateAllTrackItems():
|
||||
currentClip = version.item()
|
||||
trackItems = currentClip.whereAmI()
|
||||
if not trackItems:
|
||||
return
|
||||
|
||||
proj = currentClip.project()
|
||||
|
||||
# A Sequence-Shot manifest dictionary
|
||||
sequenceShotManifest = {}
|
||||
|
||||
# Make this all undo-able in a single Group undo
|
||||
with proj.beginUndo(
|
||||
"Update All Versions for %s" % currentClip.name()):
|
||||
for shot in trackItems:
|
||||
seq = shot.parentSequence()
|
||||
if seq not in sequenceShotManifest.keys():
|
||||
sequenceShotManifest[seq] = [shot]
|
||||
else:
|
||||
sequenceShotManifest[seq] += [shot]
|
||||
shot.setCurrentVersion(version)
|
||||
|
||||
# We also should update the current Version of the selected Clip for completeness...
|
||||
currentClip.binItem().setActiveVersion(version)
|
||||
|
||||
# Now disaplay a Dialog which informs the user of where and what was changed
|
||||
self.showVersionUpdateReportFromShotManifest(sequenceShotManifest)
|
||||
|
||||
action.triggered.connect(updateAllTrackItems)
|
||||
return action
|
||||
|
||||
# This is just a convenience method for returning QActions with a title, triggered method and icon.
|
||||
def makeAction(self, title, method, icon=None):
|
||||
action = QAction(title, None)
|
||||
action.setIcon(QIcon(icon))
|
||||
|
||||
# We do this magic, so that the title string from the action is used to trigger the version change
|
||||
def methodWrapper():
|
||||
method(title)
|
||||
|
||||
action.triggered.connect(methodWrapper)
|
||||
return action
|
||||
|
||||
def clipSelectionFromView(self, view):
|
||||
"""Helper method to return a list of Clips in the Active View"""
|
||||
selection = hiero.ui.activeView().selection()
|
||||
|
||||
if len(selection) == 0:
|
||||
return None
|
||||
|
||||
if isinstance(view, hiero.ui.BinView):
|
||||
# We could have a mixture of Bins and Clips selected, so sort of the Clips and Clips inside Bins
|
||||
clipItems = [
|
||||
item.activeItem() for item in selection
|
||||
if hasattr(item, "activeItem")
|
||||
and isinstance(item.activeItem(), hiero.core.Clip)
|
||||
]
|
||||
|
||||
# We'll also append Bins here, and see if can find Clips inside
|
||||
bins = [
|
||||
item for item in selection if isinstance(item, hiero.core.Bin)
|
||||
]
|
||||
|
||||
# We search inside of a Bin for a Clip which is not already in clipBinItems
|
||||
if len(bins) > 0:
|
||||
# Grab the Clips inside of a Bin and append them to a list
|
||||
for bin in bins:
|
||||
clips = hiero.core.findItemsInBin(bin, "Clip")
|
||||
for clip in clips:
|
||||
if clip not in clipItems:
|
||||
clipItems.append(clip)
|
||||
|
||||
elif isinstance(view,
|
||||
(hiero.ui.TimelineEditor, hiero.ui.SpreadsheetView)):
|
||||
# Here, we have shots. To get to the Clip froma TrackItem, just call source()
|
||||
clipItems = [
|
||||
item.source() for item in selection if hasattr(item, "source")
|
||||
and isinstance(item, hiero.core.TrackItem)
|
||||
]
|
||||
|
||||
return clipItems
|
||||
|
||||
# This generates the Version Up Everywhere menu
|
||||
def createVersionEveryWhereMenuForView(self, view):
|
||||
|
||||
versionEverywhereMenu = QMenu(self.actionTitle)
|
||||
self._versionActions = []
|
||||
# We look to the activeView for a selection of Clips
|
||||
clips = self.clipSelectionFromView(view)
|
||||
|
||||
# And bail if nothing is found
|
||||
if len(clips) == 0:
|
||||
return versionEverywhereMenu
|
||||
|
||||
# Now, if we have just one Clip selected, we'll form a special menu, which lists all versions
|
||||
if len(clips) == 1:
|
||||
|
||||
# Get a reversed list of Versions, so that bigger ones appear at top
|
||||
versions = list(reversed(clips[0].binItem().items()))
|
||||
for version in versions:
|
||||
self._versionActions += [
|
||||
self.makeVersionActionForSingleClip(version)
|
||||
]
|
||||
|
||||
elif len(clips) > 1:
|
||||
# We will add Max/Min/Prev/Next options, which can be called on a TrackItem, without the need for a Version object
|
||||
self._versionActions += [
|
||||
self.makeAction(
|
||||
self.eMaxVersion,
|
||||
self.setTrackItemVersionForClipSelection,
|
||||
icon=None)
|
||||
]
|
||||
self._versionActions += [
|
||||
self.makeAction(
|
||||
self.eMinVersion,
|
||||
self.setTrackItemVersionForClipSelection,
|
||||
icon=None)
|
||||
]
|
||||
self._versionActions += [
|
||||
self.makeAction(
|
||||
self.eNextVersion,
|
||||
self.setTrackItemVersionForClipSelection,
|
||||
icon=None)
|
||||
]
|
||||
self._versionActions += [
|
||||
self.makeAction(
|
||||
self.ePreviousVersion,
|
||||
self.setTrackItemVersionForClipSelection,
|
||||
icon=None)
|
||||
]
|
||||
|
||||
for act in self._versionActions:
|
||||
versionEverywhereMenu.addAction(act)
|
||||
|
||||
return versionEverywhereMenu
|
||||
|
||||
def setTrackItemVersionForClipSelection(self, versionOption):
|
||||
|
||||
view = hiero.ui.activeView()
|
||||
if not view:
|
||||
return
|
||||
|
||||
clipSelection = self.clipSelectionFromView(view)
|
||||
|
||||
if len(clipSelection) == 0:
|
||||
return
|
||||
|
||||
proj = clipSelection[0].project()
|
||||
|
||||
# Create a Sequence-Shot Manifest, to report to users where a Shot was updated
|
||||
sequenceShotManifest = {}
|
||||
|
||||
with proj.beginUndo("Update multiple Versions"):
|
||||
for clip in clipSelection:
|
||||
|
||||
# Look to see if it exists in a TrackItem somewhere...
|
||||
shotUsage = clip.whereAmI("TrackItem")
|
||||
|
||||
# Next, depending on the versionOption, make the appropriate update
|
||||
# There's probably a more neat/compact way of doing this...
|
||||
for shot in shotUsage:
|
||||
|
||||
# This step is done for reporting reasons
|
||||
seq = shot.parentSequence()
|
||||
if seq not in sequenceShotManifest.keys():
|
||||
sequenceShotManifest[seq] = [shot]
|
||||
else:
|
||||
sequenceShotManifest[seq] += [shot]
|
||||
|
||||
if versionOption == self.eMaxVersion:
|
||||
shot.maxVersion()
|
||||
elif versionOption == self.eMinVersion:
|
||||
shot.minVersion()
|
||||
elif versionOption == self.eNextVersion:
|
||||
shot.nextVersion()
|
||||
elif versionOption == self.ePreviousVersion:
|
||||
shot.prevVersion()
|
||||
|
||||
# Finally, for completeness, set the Max/Min version of the Clip too (if chosen)
|
||||
# Note: It doesn't make sense to do Next/Prev on a Clip here because next/prev means different things for different Shots
|
||||
if versionOption == self.eMaxVersion:
|
||||
clip.binItem().maxVersion()
|
||||
elif versionOption == self.eMinVersion:
|
||||
clip.binItem().minVersion()
|
||||
|
||||
# Now disaplay a Dialog which informs the user of where and what was changed
|
||||
self.showVersionUpdateReportFromShotManifest(sequenceShotManifest)
|
||||
|
||||
# This handles events from the Project Bin View
|
||||
def binViewEventHandler(self, event):
|
||||
|
||||
if not hasattr(event.sender, "selection"):
|
||||
# Something has gone wrong, we should only be here if raised
|
||||
# by the Bin view which gives a selection.
|
||||
return
|
||||
selection = event.sender.selection()
|
||||
|
||||
# Return if there's no Selection. We won't add the Localise Menu.
|
||||
if selection == None:
|
||||
return
|
||||
|
||||
view = hiero.ui.activeView()
|
||||
# Only add the Menu if Bins or Sequences are selected (this ensures menu isn't added in the Tags Pane)
|
||||
if len(selection) > 0:
|
||||
self._versionEverywhereMenu = self.createVersionEveryWhereMenuForView(
|
||||
view)
|
||||
hiero.ui.insertMenuAction(
|
||||
self._versionEverywhereMenu.menuAction(),
|
||||
event.menu,
|
||||
after="foundry.menu.version")
|
||||
return
|
||||
|
||||
|
||||
# Instantiate the Menu to get it to register itself.
|
||||
VersionAllMenu = VersionAllMenu()
|
||||
|
|
@ -2,11 +2,11 @@ import re
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class PreCollectClipEffects(pyblish.api.InstancePlugin):
|
||||
class CollectClipEffects(pyblish.api.InstancePlugin):
|
||||
"""Collect soft effects instances."""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.479
|
||||
label = "Precollect Clip Effects Instances"
|
||||
order = pyblish.api.CollectorOrder - 0.078
|
||||
label = "Collect Clip Effects Instances"
|
||||
families = ["clip"]
|
||||
|
||||
def process(self, instance):
|
||||
|
|
@ -2440,34 +2440,27 @@ def validate_fps():
|
|||
# rounding, we have to round those numbers coming from Maya.
|
||||
current_fps = float_round(mel.eval('currentTimeUnitToFPS()'), 2)
|
||||
|
||||
if current_fps != fps:
|
||||
fps_match = current_fps == fps
|
||||
if not fps_match and not IS_HEADLESS:
|
||||
from openpype.widgets import popup
|
||||
|
||||
from Qt import QtWidgets
|
||||
from ...widgets import popup
|
||||
parent = get_main_window()
|
||||
|
||||
# Find maya main window
|
||||
top_level_widgets = {w.objectName(): w for w in
|
||||
QtWidgets.QApplication.topLevelWidgets()}
|
||||
dialog = popup.Popup2(parent=parent)
|
||||
dialog.setModal(True)
|
||||
dialog.setWindowTitle("Maya scene not in line with project")
|
||||
dialog.setMessage("The FPS is out of sync, please fix")
|
||||
|
||||
parent = top_level_widgets.get("MayaWindow", None)
|
||||
if parent is None:
|
||||
pass
|
||||
else:
|
||||
dialog = popup.Popup2(parent=parent)
|
||||
dialog.setModal(True)
|
||||
dialog.setWindowTitle("Maya scene not in line with project")
|
||||
dialog.setMessage("The FPS is out of sync, please fix")
|
||||
# Set new text for button (add optional argument for the popup?)
|
||||
toggle = dialog.widgets["toggle"]
|
||||
update = toggle.isChecked()
|
||||
dialog.on_show.connect(lambda: set_scene_fps(fps, update))
|
||||
|
||||
# Set new text for button (add optional argument for the popup?)
|
||||
toggle = dialog.widgets["toggle"]
|
||||
update = toggle.isChecked()
|
||||
dialog.on_show.connect(lambda: set_scene_fps(fps, update))
|
||||
dialog.show()
|
||||
|
||||
dialog.show()
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
return True
|
||||
return fps_match
|
||||
|
||||
|
||||
def bake(nodes,
|
||||
|
|
|
|||
|
|
@ -911,7 +911,7 @@ class RenderProductsRedshift(ARenderProducts):
|
|||
|
||||
"""
|
||||
prefix = super(RenderProductsRedshift, self).get_renderer_prefix()
|
||||
prefix = "{}.<aov>".format(prefix)
|
||||
prefix = "{}{}<aov>".format(prefix, self.aov_separator)
|
||||
return prefix
|
||||
|
||||
def get_render_products(self):
|
||||
|
|
|
|||
|
|
@ -108,17 +108,17 @@ def install():
|
|||
|
||||
cmds.menuItem(
|
||||
"Reset Frame Range",
|
||||
command=reset_frame_range
|
||||
command=lambda *args: reset_frame_range()
|
||||
)
|
||||
|
||||
cmds.menuItem(
|
||||
"Reset Resolution",
|
||||
command=lib.reset_scene_resolution
|
||||
command=lambda *args: lib.reset_scene_resolution()
|
||||
)
|
||||
|
||||
cmds.menuItem(
|
||||
"Set Colorspace",
|
||||
command=lib.set_colorspace,
|
||||
command=lambda *args: lib.set_colorspace(),
|
||||
)
|
||||
cmds.menuItem(divider=True, parent=MENU_NAME)
|
||||
cmds.menuItem(
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class CreateRender(plugin.Creator):
|
|||
'vray': 'maya/<scene>/<Layer>/<Layer>',
|
||||
'arnold': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>', # noqa
|
||||
'renderman': 'maya/<Scene>/<layer>/<layer>{aov_separator}<aov>',
|
||||
'redshift': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>' # noqa
|
||||
'redshift': 'maya/<Scene>/<RenderLayer>/<RenderLayer>' # noqa
|
||||
}
|
||||
|
||||
_aov_chars = {
|
||||
|
|
@ -455,9 +455,7 @@ class CreateRender(plugin.Creator):
|
|||
if renderer == "vray":
|
||||
self._set_vray_settings(asset)
|
||||
if renderer == "redshift":
|
||||
_ = self._set_renderer_option(
|
||||
"RedshiftOptions", "{}.imageFormat", 1
|
||||
)
|
||||
cmds.setAttr("redshiftOptions.imageFormat", 1)
|
||||
|
||||
# resolution
|
||||
cmds.setAttr(
|
||||
|
|
|
|||
|
|
@ -5,15 +5,16 @@ from maya import cmds
|
|||
|
||||
|
||||
class ValidateFrameRange(pyblish.api.InstancePlugin):
|
||||
"""Valides the frame ranges.
|
||||
"""Validates the frame ranges.
|
||||
|
||||
This is optional validator checking if the frame range on instance
|
||||
matches the one of asset. It also validate render frame range of render
|
||||
layers
|
||||
This is an optional validator checking if the frame range on instance
|
||||
matches the frame range specified for the asset.
|
||||
|
||||
Repair action will change everything to match asset.
|
||||
It also validates render frame ranges of render layers.
|
||||
|
||||
This can be turned off by artist to allow custom ranges.
|
||||
Repair action will change everything to match the asset frame range.
|
||||
|
||||
This can be turned off by the artist to allow custom ranges.
|
||||
"""
|
||||
|
||||
label = "Validate Frame Range"
|
||||
|
|
|
|||
|
|
@ -172,8 +172,8 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin):
|
|||
cls.log.error(("AOV ({}) image prefix is not set "
|
||||
"correctly {} != {}").format(
|
||||
cmds.getAttr("{}.name".format(aov)),
|
||||
cmds.getAttr("{}.filePrefix".format(aov)),
|
||||
aov_prefix
|
||||
aov_prefix,
|
||||
redshift_AOV_prefix
|
||||
))
|
||||
invalid = True
|
||||
# get aov format
|
||||
|
|
@ -329,7 +329,8 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin):
|
|||
for aov in rs_aovs:
|
||||
# fix AOV prefixes
|
||||
cmds.setAttr(
|
||||
"{}.filePrefix".format(aov), redshift_AOV_prefix)
|
||||
"{}.filePrefix".format(aov),
|
||||
redshift_AOV_prefix, type="string")
|
||||
# fix AOV file format
|
||||
default_ext = cmds.getAttr(
|
||||
"redshiftOptions.imageFormat", asString=True)
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class CollectBatchData(pyblish.api.ContextPlugin):
|
|||
context.data["asset"] = asset_name
|
||||
context.data["task"] = task_name
|
||||
context.data["taskType"] = task_type
|
||||
context.data["project_name"] = project_name
|
||||
|
||||
self._set_ctx_path(batch_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@ import tempfile
|
|||
from avalon import io
|
||||
import pyblish.api
|
||||
from openpype.lib import prepare_template_data
|
||||
from openpype.lib.plugin_tools import parse_json
|
||||
|
||||
from openpype.lib.plugin_tools import (
|
||||
parse_json,
|
||||
get_subset_name_with_asset_doc
|
||||
)
|
||||
|
||||
class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
||||
"""
|
||||
|
|
@ -34,7 +36,7 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
targets = ["filespublish"]
|
||||
|
||||
# from Settings
|
||||
task_type_to_family = {}
|
||||
task_type_to_family = []
|
||||
|
||||
def process(self, context):
|
||||
batch_dir = context.data["batchDir"]
|
||||
|
|
@ -47,8 +49,13 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
self.log.info("task_sub:: {}".format(task_subfolders))
|
||||
|
||||
asset_name = context.data["asset"]
|
||||
asset_doc = io.find_one({
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
})
|
||||
task_name = context.data["task"]
|
||||
task_type = context.data["taskType"]
|
||||
project_name = context.data["project_name"]
|
||||
for task_dir in task_subfolders:
|
||||
task_data = parse_json(os.path.join(task_dir,
|
||||
"manifest.json"))
|
||||
|
|
@ -57,20 +64,21 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
is_sequence = len(task_data["files"]) > 1
|
||||
|
||||
_, extension = os.path.splitext(task_data["files"][0])
|
||||
family, families, subset_template, tags = self._get_family(
|
||||
family, families, tags = self._get_family(
|
||||
self.task_type_to_family,
|
||||
task_type,
|
||||
is_sequence,
|
||||
extension.replace(".", ''))
|
||||
|
||||
subset = self._get_subset_name(
|
||||
family, subset_template, task_name, task_data["variant"]
|
||||
subset_name = get_subset_name_with_asset_doc(
|
||||
family, task_data["variant"], task_name, asset_doc,
|
||||
project_name=project_name, host_name="webpublisher"
|
||||
)
|
||||
version = self._get_last_version(asset_name, subset) + 1
|
||||
version = self._get_last_version(asset_name, subset_name) + 1
|
||||
|
||||
instance = context.create_instance(subset)
|
||||
instance = context.create_instance(subset_name)
|
||||
instance.data["asset"] = asset_name
|
||||
instance.data["subset"] = subset
|
||||
instance.data["subset"] = subset_name
|
||||
instance.data["family"] = family
|
||||
instance.data["families"] = families
|
||||
instance.data["version"] = version
|
||||
|
|
@ -149,7 +157,7 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
extension (str): without '.'
|
||||
|
||||
Returns:
|
||||
(family, [families], subset_template_name, tags) tuple
|
||||
(family, [families], tags) tuple
|
||||
AssertionError if not matching family found
|
||||
"""
|
||||
task_type = task_type.lower()
|
||||
|
|
@ -160,12 +168,21 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
assert task_obj, "No family configuration for '{}'".format(task_type)
|
||||
|
||||
found_family = None
|
||||
for family, content in task_obj.items():
|
||||
if is_sequence != content["is_sequence"]:
|
||||
families_config = []
|
||||
# backward compatibility, should be removed pretty soon
|
||||
if isinstance(task_obj, dict):
|
||||
for family, config in task_obj:
|
||||
config["result_family"] = family
|
||||
families_config.append(config)
|
||||
else:
|
||||
families_config = task_obj
|
||||
|
||||
for config in families_config:
|
||||
if is_sequence != config["is_sequence"]:
|
||||
continue
|
||||
if extension in content["extensions"] or \
|
||||
'' in content["extensions"]: # all extensions setting
|
||||
found_family = family
|
||||
if (extension in config["extensions"] or
|
||||
'' in config["extensions"]): # all extensions setting
|
||||
found_family = config["result_family"]
|
||||
break
|
||||
|
||||
msg = "No family found for combination of " +\
|
||||
|
|
@ -173,10 +190,9 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
task_type, is_sequence, extension)
|
||||
assert found_family, msg
|
||||
|
||||
return found_family, \
|
||||
content["families"], \
|
||||
content["subset_template_name"], \
|
||||
content["tags"]
|
||||
return (found_family,
|
||||
config["families"],
|
||||
config["tags"])
|
||||
|
||||
def _get_last_version(self, asset_name, subset_name):
|
||||
"""Returns version number or 0 for 'asset' and 'subset'"""
|
||||
|
|
|
|||
|
|
@ -359,12 +359,19 @@ class ConfiguredExtensionsEndpoint(_RestApiEndpoint):
|
|||
"studio_exts": set(["psd", "psb", "tvpp", "tvp"])
|
||||
}
|
||||
collect_conf = sett["webpublisher"]["publish"]["CollectPublishedFiles"]
|
||||
for _, mapping in collect_conf.get("task_type_to_family", {}).items():
|
||||
for _family, config in mapping.items():
|
||||
if config["is_sequence"]:
|
||||
configured["sequence_exts"].update(config["extensions"])
|
||||
else:
|
||||
configured["file_exts"].update(config["extensions"])
|
||||
configs = collect_conf.get("task_type_to_family", [])
|
||||
mappings = []
|
||||
for _, conf_mappings in configs.items():
|
||||
if isinstance(conf_mappings, dict):
|
||||
conf_mappings = conf_mappings.values()
|
||||
for conf_mapping in conf_mappings:
|
||||
mappings.append(conf_mapping)
|
||||
|
||||
for mapping in mappings:
|
||||
if mapping["is_sequence"]:
|
||||
configured["sequence_exts"].update(mapping["extensions"])
|
||||
else:
|
||||
configured["file_exts"].update(mapping["extensions"])
|
||||
|
||||
return Response(
|
||||
status=200,
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ from .avalon_context import (
|
|||
get_hierarchy,
|
||||
get_linked_assets,
|
||||
get_latest_version,
|
||||
get_system_general_anatomy_data,
|
||||
|
||||
get_workfile_template_key,
|
||||
get_workfile_template_key_from_context,
|
||||
|
|
@ -222,6 +223,7 @@ __all__ = [
|
|||
"get_hierarchy",
|
||||
"get_linked_assets",
|
||||
"get_latest_version",
|
||||
"get_system_general_anatomy_data",
|
||||
|
||||
"get_workfile_template_key",
|
||||
"get_workfile_template_key_from_context",
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ import six
|
|||
|
||||
from openpype.settings import (
|
||||
get_system_settings,
|
||||
get_project_settings,
|
||||
get_environments
|
||||
get_project_settings
|
||||
)
|
||||
from openpype.settings.constants import (
|
||||
METADATA_KEYS,
|
||||
|
|
@ -29,8 +28,7 @@ from .profiles_filtering import filter_profiles
|
|||
from .local_settings import get_openpype_username
|
||||
from .avalon_context import (
|
||||
get_workdir_data,
|
||||
get_workdir_with_workdir_data,
|
||||
get_workfile_template_key
|
||||
get_workdir_with_workdir_data
|
||||
)
|
||||
|
||||
from .python_module_tools import (
|
||||
|
|
@ -44,6 +42,9 @@ _logger = None
|
|||
|
||||
PLATFORM_NAMES = {"windows", "linux", "darwin"}
|
||||
DEFAULT_ENV_SUBGROUP = "standard"
|
||||
CUSTOM_LAUNCH_APP_GROUPS = {
|
||||
"djvview"
|
||||
}
|
||||
|
||||
|
||||
def parse_environments(env_data, env_group=None, platform_name=None):
|
||||
|
|
@ -405,11 +406,47 @@ class ApplicationManager:
|
|||
clear_metadata=False, exclude_locals=False
|
||||
)
|
||||
|
||||
all_app_defs = {}
|
||||
# Prepare known applications
|
||||
app_defs = settings["applications"]
|
||||
additional_apps = {}
|
||||
for group_name, variant_defs in app_defs.items():
|
||||
if group_name in METADATA_KEYS:
|
||||
continue
|
||||
|
||||
if group_name == "additional_apps":
|
||||
additional_apps = variant_defs
|
||||
else:
|
||||
all_app_defs[group_name] = variant_defs
|
||||
|
||||
# Prepare additional applications
|
||||
# - First find dynamic keys that can be used as labels of group
|
||||
dynamic_keys = {}
|
||||
for group_name, variant_defs in additional_apps.items():
|
||||
if group_name == M_DYNAMIC_KEY_LABEL:
|
||||
dynamic_keys = variant_defs
|
||||
break
|
||||
|
||||
# Add additional apps to known applications
|
||||
for group_name, variant_defs in additional_apps.items():
|
||||
if group_name in METADATA_KEYS:
|
||||
continue
|
||||
|
||||
# Determine group label
|
||||
label = variant_defs.get("label")
|
||||
if not label:
|
||||
# Look for label set in dynamic labels
|
||||
label = dynamic_keys.get(group_name)
|
||||
if not label:
|
||||
label = group_name
|
||||
variant_defs["label"] = label
|
||||
|
||||
all_app_defs[group_name] = variant_defs
|
||||
|
||||
for group_name, variant_defs in all_app_defs.items():
|
||||
if group_name in METADATA_KEYS:
|
||||
continue
|
||||
|
||||
group = ApplicationGroup(group_name, variant_defs, self)
|
||||
self.app_groups[group_name] = group
|
||||
for app in group:
|
||||
|
|
@ -892,7 +929,9 @@ class ApplicationLaunchContext:
|
|||
# --- START: Backwards compatibility ---
|
||||
hooks_dir = os.path.join(pype_dir, "hooks")
|
||||
|
||||
subfolder_names = ["global", self.host_name]
|
||||
subfolder_names = ["global"]
|
||||
if self.host_name:
|
||||
subfolder_names.append(self.host_name)
|
||||
for subfolder_name in subfolder_names:
|
||||
path = os.path.join(hooks_dir, subfolder_name)
|
||||
if (
|
||||
|
|
@ -903,10 +942,12 @@ class ApplicationLaunchContext:
|
|||
paths.append(path)
|
||||
# --- END: Backwards compatibility ---
|
||||
|
||||
subfolders_list = (
|
||||
["hooks"],
|
||||
("hosts", self.host_name, "hooks")
|
||||
)
|
||||
subfolders_list = [
|
||||
["hooks"]
|
||||
]
|
||||
if self.host_name:
|
||||
subfolders_list.append(["hosts", self.host_name, "hooks"])
|
||||
|
||||
for subfolders in subfolders_list:
|
||||
path = os.path.join(pype_dir, *subfolders)
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ import collections
|
|||
import functools
|
||||
import getpass
|
||||
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.settings import (
|
||||
get_project_settings,
|
||||
get_system_settings
|
||||
)
|
||||
from .anatomy import Anatomy
|
||||
from .profiles_filtering import filter_profiles
|
||||
|
||||
|
|
@ -258,6 +261,18 @@ def get_hierarchy(asset_name=None):
|
|||
return "/".join(hierarchy_items)
|
||||
|
||||
|
||||
def get_system_general_anatomy_data():
|
||||
system_settings = get_system_settings()
|
||||
studio_name = system_settings["general"]["studio_name"]
|
||||
studio_code = system_settings["general"]["studio_code"]
|
||||
return {
|
||||
"studio": {
|
||||
"name": studio_name,
|
||||
"code": studio_code
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_linked_asset_ids(asset_doc):
|
||||
"""Return linked asset ids for `asset_doc` from DB
|
||||
|
||||
|
|
@ -536,6 +551,10 @@ def get_workdir_data(project_doc, asset_doc, task_name, host_name):
|
|||
"user": getpass.getuser(),
|
||||
"hierarchy": hierarchy,
|
||||
}
|
||||
|
||||
system_general_data = get_system_general_anatomy_data()
|
||||
data.update(system_general_data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
|
|
@ -1505,7 +1524,7 @@ def _get_task_context_data_for_anatomy(
|
|||
"requested task type: `{}`".format(task_type)
|
||||
)
|
||||
|
||||
return {
|
||||
data = {
|
||||
"project": {
|
||||
"name": project_doc["name"],
|
||||
"code": project_doc["data"].get("code")
|
||||
|
|
@ -1518,6 +1537,11 @@ def _get_task_context_data_for_anatomy(
|
|||
}
|
||||
}
|
||||
|
||||
system_general_data = get_system_general_anatomy_data()
|
||||
data.update(system_general_data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_custom_workfile_template_by_context(
|
||||
template_profiles, project_doc, asset_doc, task_name, anatomy=None
|
||||
|
|
|
|||
|
|
@ -4,12 +4,35 @@ import logging
|
|||
import collections
|
||||
import tempfile
|
||||
|
||||
import xml.etree.ElementTree
|
||||
|
||||
from .execute import run_subprocess
|
||||
from .vendor_bin_utils import (
|
||||
get_oiio_tools_path,
|
||||
is_oiio_supported
|
||||
)
|
||||
|
||||
# Max length of string that is supported by ffmpeg
|
||||
MAX_FFMPEG_STRING_LEN = 8196
|
||||
# OIIO known xml tags
|
||||
STRING_TAGS = {
|
||||
"format"
|
||||
}
|
||||
INT_TAGS = {
|
||||
"x", "y", "z",
|
||||
"width", "height", "depth",
|
||||
"full_x", "full_y", "full_z",
|
||||
"full_width", "full_height", "full_depth",
|
||||
"tile_width", "tile_height", "tile_depth",
|
||||
"nchannels",
|
||||
"alpha_channel",
|
||||
"z_channel",
|
||||
"deep",
|
||||
"subimages",
|
||||
}
|
||||
# Regex to parse array attributes
|
||||
ARRAY_TYPE_REGEX = re.compile(r"^(int|float|string)\[\d+\]$")
|
||||
|
||||
|
||||
def get_transcode_temp_directory():
|
||||
"""Creates temporary folder for transcoding.
|
||||
|
|
@ -24,87 +47,215 @@ def get_transcode_temp_directory():
|
|||
|
||||
|
||||
def get_oiio_info_for_input(filepath, logger=None):
|
||||
"""Call oiiotool to get information about input and return stdout."""
|
||||
args = [
|
||||
get_oiio_tools_path(), "--info", "-v", filepath
|
||||
]
|
||||
return run_subprocess(args, logger=logger)
|
||||
"""Call oiiotool to get information about input and return stdout.
|
||||
|
||||
|
||||
def parse_oiio_info(oiio_info):
|
||||
"""Create an object based on output from oiiotool.
|
||||
|
||||
Removes quotation marks from compression value. Parse channels into
|
||||
dictionary - key is channel name value is determined type of channel
|
||||
(e.g. 'uint', 'float').
|
||||
|
||||
Args:
|
||||
oiio_info (str): Output of calling "oiiotool --info -v <path>"
|
||||
|
||||
Returns:
|
||||
dict: Loaded data from output.
|
||||
Stdout should contain xml format string.
|
||||
"""
|
||||
lines = [
|
||||
line.strip()
|
||||
for line in oiio_info.split("\n")
|
||||
args = [
|
||||
get_oiio_tools_path(), "--info", "-v", "-i:infoformat=xml", filepath
|
||||
]
|
||||
# Each line should contain information about one key
|
||||
# key - value are separated with ": "
|
||||
oiio_sep = ": "
|
||||
data_map = {}
|
||||
for line in lines:
|
||||
parts = line.split(oiio_sep)
|
||||
if len(parts) < 2:
|
||||
output = run_subprocess(args, logger=logger)
|
||||
output = output.replace("\r\n", "\n")
|
||||
|
||||
xml_started = False
|
||||
lines = []
|
||||
for line in output.split("\n"):
|
||||
if not xml_started:
|
||||
if not line.startswith("<"):
|
||||
continue
|
||||
xml_started = True
|
||||
if xml_started:
|
||||
lines.append(line)
|
||||
|
||||
if not xml_started:
|
||||
raise ValueError(
|
||||
"Failed to read input file \"{}\".\nOutput:\n{}".format(
|
||||
filepath, output
|
||||
)
|
||||
)
|
||||
|
||||
xml_text = "\n".join(lines)
|
||||
return parse_oiio_xml_output(xml_text, logger=logger)
|
||||
|
||||
|
||||
class RationalToInt:
|
||||
"""Rational value stored as division of 2 integers using string."""
|
||||
def __init__(self, string_value):
|
||||
parts = string_value.split("/")
|
||||
top = float(parts[0])
|
||||
bottom = 1.0
|
||||
if len(parts) != 1:
|
||||
bottom = float(parts[1])
|
||||
|
||||
self._value = top / bottom
|
||||
self._string_value = string_value
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def string_value(self):
|
||||
return self._string_value
|
||||
|
||||
def __format__(self, *args, **kwargs):
|
||||
return self._string_value.__format__(*args, **kwargs)
|
||||
|
||||
def __float__(self):
|
||||
return self._value
|
||||
|
||||
def __str__(self):
|
||||
return self._string_value
|
||||
|
||||
def __repr__(self):
|
||||
return "<{}> {}".format(self.__class__.__name__, self._string_value)
|
||||
|
||||
|
||||
def convert_value_by_type_name(value_type, value, logger=None):
|
||||
"""Convert value to proper type based on type name.
|
||||
|
||||
In some cases value types have custom python class.
|
||||
"""
|
||||
if logger is None:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Simple types
|
||||
if value_type == "string":
|
||||
return value
|
||||
|
||||
if value_type == "int":
|
||||
return int(value)
|
||||
|
||||
if value_type == "float":
|
||||
return float(value)
|
||||
|
||||
# Vectors will probably have more types
|
||||
if value_type == "vec2f":
|
||||
return [float(item) for item in value.split(",")]
|
||||
|
||||
# Matrix should be always have square size of element 3x3, 4x4
|
||||
# - are returned as list of lists
|
||||
if value_type == "matrix":
|
||||
output = []
|
||||
current_index = -1
|
||||
parts = value.split(",")
|
||||
parts_len = len(parts)
|
||||
if parts_len == 1:
|
||||
divisor = 1
|
||||
elif parts_len == 4:
|
||||
divisor = 2
|
||||
elif parts_len == 9:
|
||||
divisor == 3
|
||||
elif parts_len == 16:
|
||||
divisor = 4
|
||||
else:
|
||||
logger.info("Unknown matrix resolution {}. Value: \"{}\"".format(
|
||||
parts_len, value
|
||||
))
|
||||
for part in parts:
|
||||
output.append(float(part))
|
||||
return output
|
||||
|
||||
for idx, item in enumerate(parts):
|
||||
list_index = idx % divisor
|
||||
if list_index > current_index:
|
||||
current_index = list_index
|
||||
output.append([])
|
||||
output[list_index].append(float(item))
|
||||
return output
|
||||
|
||||
if value_type == "rational2i":
|
||||
return RationalToInt(value)
|
||||
|
||||
# Array of other types is converted to list
|
||||
re_result = ARRAY_TYPE_REGEX.findall(value_type)
|
||||
if re_result:
|
||||
array_type = re_result[0]
|
||||
output = []
|
||||
for item in value.split(","):
|
||||
output.append(
|
||||
convert_value_by_type_name(array_type, item, logger=logger)
|
||||
)
|
||||
return output
|
||||
|
||||
logger.info((
|
||||
"MISSING IMPLEMENTATION:"
|
||||
" Unknown attrib type \"{}\". Value: {}"
|
||||
).format(value_type, value))
|
||||
return value
|
||||
|
||||
|
||||
def parse_oiio_xml_output(xml_string, logger=None):
|
||||
"""Parse xml output from OIIO info command."""
|
||||
output = {}
|
||||
if not xml_string:
|
||||
return output
|
||||
|
||||
if logger is None:
|
||||
logger = logging.getLogger("OIIO-xml-parse")
|
||||
|
||||
tree = xml.etree.ElementTree.fromstring(xml_string)
|
||||
attribs = {}
|
||||
output["attribs"] = attribs
|
||||
for child in tree:
|
||||
tag_name = child.tag
|
||||
if tag_name == "attrib":
|
||||
attrib_def = child.attrib
|
||||
value = convert_value_by_type_name(
|
||||
attrib_def["type"], child.text, logger=logger
|
||||
)
|
||||
|
||||
attribs[attrib_def["name"]] = value
|
||||
continue
|
||||
key = parts.pop(0)
|
||||
value = oiio_sep.join(parts)
|
||||
data_map[key] = value
|
||||
|
||||
if "compression" in data_map:
|
||||
value = data_map["compression"]
|
||||
data_map["compression"] = value.replace("\"", "")
|
||||
# Channels are stored as tex on each child
|
||||
if tag_name == "channelnames":
|
||||
value = []
|
||||
for channel in child:
|
||||
value.append(channel.text)
|
||||
|
||||
channels_info = {}
|
||||
channels_value = data_map.get("channel list") or ""
|
||||
if channels_value:
|
||||
channels = channels_value.split(", ")
|
||||
type_regex = re.compile(r"(?P<name>[^\(]+) \((?P<type>[^\)]+)\)")
|
||||
for channel in channels:
|
||||
match = type_regex.search(channel)
|
||||
if not match:
|
||||
channel_name = channel
|
||||
channel_type = "uint"
|
||||
else:
|
||||
channel_name = match.group("name")
|
||||
channel_type = match.group("type")
|
||||
channels_info[channel_name] = channel_type
|
||||
data_map["channels_info"] = channels_info
|
||||
return data_map
|
||||
# Convert known integer type tags to int
|
||||
elif tag_name in INT_TAGS:
|
||||
value = int(child.text)
|
||||
|
||||
# Keep value of known string tags
|
||||
elif tag_name in STRING_TAGS:
|
||||
value = child.text
|
||||
|
||||
# Keep value as text for unknown tags
|
||||
# - feel free to add more tags
|
||||
else:
|
||||
value = child.text
|
||||
logger.info((
|
||||
"MISSING IMPLEMENTATION:"
|
||||
" Unknown tag \"{}\". Value \"{}\""
|
||||
).format(tag_name, value))
|
||||
|
||||
output[child.tag] = value
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def get_convert_rgb_channels(channels_info):
|
||||
def get_convert_rgb_channels(channel_names):
|
||||
"""Get first available RGB(A) group from channels info.
|
||||
|
||||
## Examples
|
||||
```
|
||||
# Ideal situation
|
||||
channels_info: {
|
||||
"R": ...,
|
||||
"G": ...,
|
||||
"B": ...,
|
||||
"A": ...
|
||||
channels_info: [
|
||||
"R", "G", "B", "A"
|
||||
}
|
||||
```
|
||||
Result will be `("R", "G", "B", "A")`
|
||||
|
||||
```
|
||||
# Not ideal situation
|
||||
channels_info: {
|
||||
"beauty.red": ...,
|
||||
"beuaty.green": ...,
|
||||
"beauty.blue": ...,
|
||||
"depth.Z": ...
|
||||
}
|
||||
channels_info: [
|
||||
"beauty.red",
|
||||
"beuaty.green",
|
||||
"beauty.blue",
|
||||
"depth.Z"
|
||||
]
|
||||
```
|
||||
Result will be `("beauty.red", "beauty.green", "beauty.blue", None)`
|
||||
|
||||
|
|
@ -116,7 +267,7 @@ def get_convert_rgb_channels(channels_info):
|
|||
"""
|
||||
rgb_by_main_name = collections.defaultdict(dict)
|
||||
main_name_order = [""]
|
||||
for channel_name in channels_info.keys():
|
||||
for channel_name in channel_names:
|
||||
name_parts = channel_name.split(".")
|
||||
rgb_part = name_parts.pop(-1).lower()
|
||||
main_name = ".".join(name_parts)
|
||||
|
|
@ -166,28 +317,35 @@ def should_convert_for_ffmpeg(src_filepath):
|
|||
return None
|
||||
|
||||
# Load info about info from oiio tool
|
||||
oiio_info = get_oiio_info_for_input(src_filepath)
|
||||
input_info = parse_oiio_info(oiio_info)
|
||||
input_info = get_oiio_info_for_input(src_filepath)
|
||||
if not input_info:
|
||||
return None
|
||||
|
||||
# Check compression
|
||||
compression = input_info["compression"]
|
||||
compression = input_info["attribs"].get("compression")
|
||||
if compression in ("dwaa", "dwab"):
|
||||
return True
|
||||
|
||||
# Check channels
|
||||
channels_info = input_info["channels_info"]
|
||||
review_channels = get_convert_rgb_channels(channels_info)
|
||||
channel_names = input_info["channelnames"]
|
||||
review_channels = get_convert_rgb_channels(channel_names)
|
||||
if review_channels is None:
|
||||
return None
|
||||
|
||||
for attr_value in input_info["attribs"].values():
|
||||
if (
|
||||
isinstance(attr_value, str)
|
||||
and len(attr_value) > MAX_FFMPEG_STRING_LEN
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def convert_for_ffmpeg(
|
||||
first_input_path,
|
||||
output_dir,
|
||||
input_frame_start,
|
||||
input_frame_end,
|
||||
input_frame_start=None,
|
||||
input_frame_end=None,
|
||||
logger=None
|
||||
):
|
||||
"""Contert source file to format supported in ffmpeg.
|
||||
|
|
@ -221,46 +379,76 @@ def convert_for_ffmpeg(
|
|||
if input_frame_start is not None and input_frame_end is not None:
|
||||
is_sequence = int(input_frame_end) != int(input_frame_start)
|
||||
|
||||
oiio_info = get_oiio_info_for_input(first_input_path)
|
||||
input_info = parse_oiio_info(oiio_info)
|
||||
input_info = get_oiio_info_for_input(first_input_path)
|
||||
|
||||
# Change compression only if source compression is "dwaa" or "dwab"
|
||||
# - they're not supported in ffmpeg
|
||||
compression = input_info["compression"]
|
||||
compression = input_info["attribs"].get("compression")
|
||||
if compression in ("dwaa", "dwab"):
|
||||
compression = "none"
|
||||
|
||||
# Prepare subprocess arguments
|
||||
oiio_cmd = [
|
||||
get_oiio_tools_path(),
|
||||
"--compression", compression,
|
||||
first_input_path
|
||||
]
|
||||
oiio_cmd = [get_oiio_tools_path()]
|
||||
# Add input compression if available
|
||||
if compression:
|
||||
oiio_cmd.extend(["--compression", compression])
|
||||
|
||||
channels_info = input_info["channels_info"]
|
||||
review_channels = get_convert_rgb_channels(channels_info)
|
||||
# Collect channels to export
|
||||
channel_names = input_info["channelnames"]
|
||||
review_channels = get_convert_rgb_channels(channel_names)
|
||||
if review_channels is None:
|
||||
raise ValueError(
|
||||
"Couldn't find channels that can be used for conversion."
|
||||
)
|
||||
|
||||
red, green, blue, alpha = review_channels
|
||||
input_channels = [red, green, blue]
|
||||
channels_arg = "R={},G={},B={}".format(red, green, blue)
|
||||
if alpha is not None:
|
||||
channels_arg += ",A={}".format(alpha)
|
||||
oiio_cmd.append("--ch")
|
||||
oiio_cmd.append(channels_arg)
|
||||
input_channels.append(alpha)
|
||||
input_channels_str = ",".join(input_channels)
|
||||
|
||||
oiio_cmd.extend([
|
||||
# Tell oiiotool which channels should be loaded
|
||||
# - other channels are not loaded to memory so helps to avoid memory
|
||||
# leak issues
|
||||
"-i:ch={}".format(input_channels_str), first_input_path,
|
||||
# Tell oiiotool which channels should be put to top stack (and output)
|
||||
"--ch", channels_arg
|
||||
])
|
||||
|
||||
# Add frame definitions to arguments
|
||||
if is_sequence:
|
||||
oiio_cmd.append("--frames")
|
||||
oiio_cmd.append("{}-{}".format(input_frame_start, input_frame_end))
|
||||
oiio_cmd.extend([
|
||||
"--frames", "{}-{}".format(input_frame_start, input_frame_end)
|
||||
])
|
||||
|
||||
ignore_attr_changes_added = False
|
||||
for attr_name, attr_value in input_info["attribs"].items():
|
||||
if not isinstance(attr_value, str):
|
||||
continue
|
||||
|
||||
# Remove attributes that have string value longer than allowed length
|
||||
# for ffmpeg
|
||||
if len(attr_value) > MAX_FFMPEG_STRING_LEN:
|
||||
if not ignore_attr_changes_added:
|
||||
# Attrite changes won't be added to attributes itself
|
||||
ignore_attr_changes_added = True
|
||||
oiio_cmd.append("--sansattrib")
|
||||
# Set attribute to empty string
|
||||
logger.info((
|
||||
"Removed attribute \"{}\" from metadata"
|
||||
" because has too long value ({} chars)."
|
||||
).format(attr_name, len(attr_value)))
|
||||
oiio_cmd.extend(["--eraseattrib", attr_name])
|
||||
|
||||
# Add last argument - path to output
|
||||
base_file_name = os.path.basename(first_input_path)
|
||||
output_path = os.path.join(output_dir, base_file_name)
|
||||
oiio_cmd.append("-o")
|
||||
oiio_cmd.append(output_path)
|
||||
oiio_cmd.extend([
|
||||
"-o", output_path
|
||||
])
|
||||
|
||||
logger.debug("Conversion command: {}".format(" ".join(oiio_cmd)))
|
||||
run_subprocess(oiio_cmd, logger=logger)
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
'publish',
|
||||
roothless_metadata_path,
|
||||
"--targets", "deadline",
|
||||
"--targets", "filesequence"
|
||||
"--targets", "farm"
|
||||
]
|
||||
|
||||
# Generate the payload for Deadline submission
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ import sys
|
|||
import json
|
||||
import collections
|
||||
import ftrack_api
|
||||
from openpype_modules.ftrack.lib import ServerAction
|
||||
from openpype_modules.ftrack.lib import (
|
||||
ServerAction,
|
||||
query_custom_attributes
|
||||
)
|
||||
|
||||
|
||||
class PushHierValuesToNonHier(ServerAction):
|
||||
|
|
@ -51,10 +54,6 @@ class PushHierValuesToNonHier(ServerAction):
|
|||
" from CustomAttributeConfiguration"
|
||||
" where key in ({})"
|
||||
)
|
||||
cust_attr_value_query = (
|
||||
"select value, entity_id from CustomAttributeValue"
|
||||
" where entity_id in ({}) and configuration_id in ({})"
|
||||
)
|
||||
|
||||
# configurable
|
||||
settings_key = "sync_hier_entity_attributes"
|
||||
|
|
@ -344,25 +343,11 @@ class PushHierValuesToNonHier(ServerAction):
|
|||
all_ids_with_parents.add(parent_id)
|
||||
_entity_id = parent_id
|
||||
|
||||
joined_entity_ids = self.join_query_keys(all_ids_with_parents)
|
||||
|
||||
hier_attr_ids = self.join_query_keys(
|
||||
tuple(hier_attr["id"] for hier_attr in hier_attrs)
|
||||
)
|
||||
hier_attr_ids = tuple(hier_attr["id"] for hier_attr in hier_attrs)
|
||||
hier_attrs_key_by_id = {
|
||||
hier_attr["id"]: hier_attr["key"]
|
||||
for hier_attr in hier_attrs
|
||||
}
|
||||
call_expr = [{
|
||||
"action": "query",
|
||||
"expression": self.cust_attr_value_query.format(
|
||||
joined_entity_ids, hier_attr_ids
|
||||
)
|
||||
}]
|
||||
if hasattr(session, "call"):
|
||||
[values] = session.call(call_expr)
|
||||
else:
|
||||
[values] = session._call(call_expr)
|
||||
|
||||
values_per_entity_id = {}
|
||||
for entity_id in all_ids_with_parents:
|
||||
|
|
@ -370,7 +355,10 @@ class PushHierValuesToNonHier(ServerAction):
|
|||
for key in hier_attrs_key_by_id.values():
|
||||
values_per_entity_id[entity_id][key] = None
|
||||
|
||||
for item in values["data"]:
|
||||
values = query_custom_attributes(
|
||||
session, all_ids_with_parents, hier_attr_ids, True
|
||||
)
|
||||
for item in values:
|
||||
entity_id = item["entity_id"]
|
||||
key = hier_attrs_key_by_id[item["configuration_id"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -17,11 +17,6 @@ class PushFrameValuesToTaskEvent(BaseEvent):
|
|||
" (object_type_id in ({}) or is_hierarchical is true)"
|
||||
)
|
||||
|
||||
cust_attr_query = (
|
||||
"select value, entity_id from ContextCustomAttributeValue "
|
||||
"where entity_id in ({}) and configuration_id in ({})"
|
||||
)
|
||||
|
||||
_cached_task_object_id = None
|
||||
_cached_interest_object_ids = None
|
||||
_cached_user_id = None
|
||||
|
|
@ -273,16 +268,23 @@ class PushFrameValuesToTaskEvent(BaseEvent):
|
|||
hier_attr_ids.append(attr_id)
|
||||
|
||||
conf_ids = list(hier_attr_ids)
|
||||
task_conf_ids = []
|
||||
for key, attr_id in task_attrs.items():
|
||||
attr_key_by_id[attr_id] = key
|
||||
nonhier_id_by_key[key] = attr_id
|
||||
conf_ids.append(attr_id)
|
||||
task_conf_ids.append(attr_id)
|
||||
|
||||
# Query custom attribute values
|
||||
# - result does not contain values for all entities only result of
|
||||
# query callback to ftrack server
|
||||
result = query_custom_attributes(
|
||||
session, conf_ids, whole_hierarchy_ids
|
||||
session, list(hier_attr_ids), whole_hierarchy_ids, True
|
||||
)
|
||||
result.extend(
|
||||
query_custom_attributes(
|
||||
session, task_conf_ids, whole_hierarchy_ids, False
|
||||
)
|
||||
)
|
||||
|
||||
# Prepare variables where result will be stored
|
||||
|
|
@ -547,7 +549,7 @@ class PushFrameValuesToTaskEvent(BaseEvent):
|
|||
)
|
||||
attr_ids = set(attr_id_to_key.keys())
|
||||
|
||||
current_values_by_id = self.current_values(
|
||||
current_values_by_id = self.get_current_values(
|
||||
session, attr_ids, entity_ids, task_entity_ids, hier_attrs
|
||||
)
|
||||
|
||||
|
|
@ -642,27 +644,17 @@ class PushFrameValuesToTaskEvent(BaseEvent):
|
|||
|
||||
return interesting_data, changed_keys_by_object_id
|
||||
|
||||
def current_values(
|
||||
def get_current_values(
|
||||
self, session, attr_ids, entity_ids, task_entity_ids, hier_attrs
|
||||
):
|
||||
current_values_by_id = {}
|
||||
if not attr_ids or not entity_ids:
|
||||
return current_values_by_id
|
||||
joined_conf_ids = self.join_query_keys(attr_ids)
|
||||
joined_entity_ids = self.join_query_keys(entity_ids)
|
||||
|
||||
call_expr = [{
|
||||
"action": "query",
|
||||
"expression": self.cust_attr_query.format(
|
||||
joined_entity_ids, joined_conf_ids
|
||||
)
|
||||
}]
|
||||
if hasattr(session, "call"):
|
||||
[values] = session.call(call_expr)
|
||||
else:
|
||||
[values] = session._call(call_expr)
|
||||
|
||||
for item in values["data"]:
|
||||
values = query_custom_attributes(
|
||||
session, attr_ids, entity_ids, True
|
||||
)
|
||||
for item in values:
|
||||
entity_id = item["entity_id"]
|
||||
attr_id = item["configuration_id"]
|
||||
if entity_id in task_entity_ids and attr_id in hier_attrs:
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ class SyncLinksToAvalon(BaseEvent):
|
|||
|
||||
def _get_mongo_ids_by_ftrack_ids(self, session, attr_id, ftrack_ids):
|
||||
output = query_custom_attributes(
|
||||
session, [attr_id], ftrack_ids
|
||||
session, [attr_id], ftrack_ids, True
|
||||
)
|
||||
mongo_id_by_ftrack_id = {}
|
||||
for item in output:
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from avalon.api import AvalonMongoDB
|
|||
|
||||
from openpype_modules.ftrack.lib import (
|
||||
get_openpype_attr,
|
||||
query_custom_attributes,
|
||||
CUST_ATTR_ID_KEY,
|
||||
CUST_ATTR_AUTO_SYNC,
|
||||
|
||||
|
|
@ -48,8 +49,8 @@ class SyncToAvalonEvent(BaseEvent):
|
|||
)
|
||||
|
||||
entities_query_by_id = (
|
||||
"select id, name, parent_id, link, custom_attributes from TypedContext"
|
||||
" where project_id is \"{}\" and id in ({})"
|
||||
"select id, name, parent_id, link, custom_attributes, description"
|
||||
" from TypedContext where project_id is \"{}\" and id in ({})"
|
||||
)
|
||||
|
||||
# useful for getting all tasks for asset
|
||||
|
|
@ -1073,9 +1074,8 @@ class SyncToAvalonEvent(BaseEvent):
|
|||
self.create_entity_in_avalon(entity, parent_avalon_ent)
|
||||
|
||||
for child in entity["children"]:
|
||||
if child.entity_type.lower() == "task":
|
||||
continue
|
||||
children_queue.append(child)
|
||||
if child.entity_type.lower() != "task":
|
||||
children_queue.append(child)
|
||||
|
||||
while children_queue:
|
||||
entity = children_queue.popleft()
|
||||
|
|
@ -1145,7 +1145,8 @@ class SyncToAvalonEvent(BaseEvent):
|
|||
"entityType": ftrack_ent.entity_type,
|
||||
"parents": parents,
|
||||
"tasks": {},
|
||||
"visualParent": vis_par
|
||||
"visualParent": vis_par,
|
||||
"description": ftrack_ent["description"]
|
||||
}
|
||||
}
|
||||
cust_attrs = self.get_cust_attr_values(ftrack_ent)
|
||||
|
|
@ -1822,7 +1823,15 @@ class SyncToAvalonEvent(BaseEvent):
|
|||
if ent_cust_attrs is None:
|
||||
ent_cust_attrs = {}
|
||||
|
||||
for key, values in ent_info["changes"].items():
|
||||
ent_changes = ent_info["changes"]
|
||||
if "description" in ent_changes:
|
||||
if "data" not in self.updates[mongo_id]:
|
||||
self.updates[mongo_id]["data"] = {}
|
||||
self.updates[mongo_id]["data"]["description"] = (
|
||||
ent_changes["description"]["new"] or ""
|
||||
)
|
||||
|
||||
for key, values in ent_changes.items():
|
||||
if key in hier_attrs_by_key:
|
||||
self.hier_cust_attrs_changes[key].append(ftrack_id)
|
||||
continue
|
||||
|
|
@ -2122,22 +2131,12 @@ class SyncToAvalonEvent(BaseEvent):
|
|||
for key in hier_cust_attrs_keys:
|
||||
configuration_ids.add(hier_attr_id_by_key[key])
|
||||
|
||||
entity_ids_joined = self.join_query_keys(cust_attrs_ftrack_ids)
|
||||
attributes_joined = self.join_query_keys(configuration_ids)
|
||||
|
||||
queries = [{
|
||||
"action": "query",
|
||||
"expression": (
|
||||
"select value, entity_id, configuration_id"
|
||||
" from CustomAttributeValue "
|
||||
"where entity_id in ({}) and configuration_id in ({})"
|
||||
).format(entity_ids_joined, attributes_joined)
|
||||
}]
|
||||
|
||||
if hasattr(self.process_session, "call"):
|
||||
[values] = self.process_session.call(queries)
|
||||
else:
|
||||
[values] = self.process_session._call(queries)
|
||||
values = query_custom_attributes(
|
||||
self.process_session,
|
||||
configuration_ids,
|
||||
cust_attrs_ftrack_ids,
|
||||
True
|
||||
)
|
||||
|
||||
ftrack_project_id = self.cur_project["id"]
|
||||
|
||||
|
|
@ -2162,7 +2161,7 @@ class SyncToAvalonEvent(BaseEvent):
|
|||
|
||||
# PREPARE DATA BEFORE THIS
|
||||
avalon_hier = []
|
||||
for item in values["data"]:
|
||||
for item in values:
|
||||
value = item["value"]
|
||||
if value is None:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ import os
|
|||
from uuid import uuid4
|
||||
|
||||
from openpype_modules.ftrack.lib import BaseAction
|
||||
from openpype.lib import (
|
||||
from openpype.lib.applications import (
|
||||
ApplicationManager,
|
||||
ApplicationLaunchFailed,
|
||||
ApplictionExecutableNotFound
|
||||
ApplictionExecutableNotFound,
|
||||
CUSTOM_LAUNCH_APP_GROUPS
|
||||
)
|
||||
from avalon.api import AvalonMongoDB
|
||||
|
||||
|
|
@ -136,6 +137,9 @@ class AppplicationsAction(BaseAction):
|
|||
if not app or not app.enabled:
|
||||
continue
|
||||
|
||||
if app.group.name in CUSTOM_LAUNCH_APP_GROUPS:
|
||||
continue
|
||||
|
||||
app_icon = app.icon
|
||||
if app_icon and self.icon_url:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ class CleanHierarchicalAttrsAction(BaseAction):
|
|||
" from TypedContext where project_id is \"{}\""
|
||||
)
|
||||
cust_attr_query = (
|
||||
"select value, entity_id from CustomAttributeValue "
|
||||
"where entity_id in ({}) and configuration_id is \"{}\""
|
||||
"select value, entity_id from CustomAttributeValue"
|
||||
" where entity_id in ({}) and configuration_id is \"{}\""
|
||||
)
|
||||
settings_key = "clean_hierarchical_attr"
|
||||
|
||||
|
|
@ -65,17 +65,14 @@ class CleanHierarchicalAttrsAction(BaseAction):
|
|||
)
|
||||
)
|
||||
configuration_id = attr["id"]
|
||||
call_expr = [{
|
||||
"action": "query",
|
||||
"expression": self.cust_attr_query.format(
|
||||
values = session.query(
|
||||
self.cust_attr_query.format(
|
||||
entity_ids_joined, configuration_id
|
||||
)
|
||||
}]
|
||||
|
||||
[values] = self.session.call(call_expr)
|
||||
).all()
|
||||
|
||||
data = {}
|
||||
for item in values["data"]:
|
||||
for item in values:
|
||||
value = item["value"]
|
||||
if value is None:
|
||||
data[item["entity_id"]] = value
|
||||
|
|
@ -90,10 +87,10 @@ class CleanHierarchicalAttrsAction(BaseAction):
|
|||
len(data), configuration_key
|
||||
))
|
||||
for entity_id, value in data.items():
|
||||
entity_key = collections.OrderedDict({
|
||||
"configuration_id": configuration_id,
|
||||
"entity_id": entity_id
|
||||
})
|
||||
entity_key = collections.OrderedDict((
|
||||
("configuration_id", configuration_id),
|
||||
("entity_id", entity_id)
|
||||
))
|
||||
session.recorded_operations.push(
|
||||
ftrack_api.operation.DeleteEntityOperation(
|
||||
"CustomAttributeValue",
|
||||
|
|
|
|||
|
|
@ -306,8 +306,8 @@ class CustomAttributes(BaseAction):
|
|||
}
|
||||
|
||||
cust_attr_query = (
|
||||
"select value, entity_id from ContextCustomAttributeValue "
|
||||
"where configuration_id is {}"
|
||||
"select value, entity_id from CustomAttributeValue"
|
||||
" where configuration_id is {}"
|
||||
)
|
||||
for attr_def in object_type_attrs:
|
||||
attr_ent_type = attr_def["entity_type"]
|
||||
|
|
@ -328,21 +328,14 @@ class CustomAttributes(BaseAction):
|
|||
self.log.debug((
|
||||
"Converting Avalon MongoID attr for Entity type \"{}\"."
|
||||
).format(entity_type_label))
|
||||
|
||||
call_expr = [{
|
||||
"action": "query",
|
||||
"expression": cust_attr_query.format(attr_def["id"])
|
||||
}]
|
||||
if hasattr(session, "call"):
|
||||
[values] = session.call(call_expr)
|
||||
else:
|
||||
[values] = session._call(call_expr)
|
||||
|
||||
for value in values["data"]:
|
||||
table_values = collections.OrderedDict({
|
||||
"configuration_id": hierarchical_attr["id"],
|
||||
"entity_id": value["entity_id"]
|
||||
})
|
||||
values = session.query(
|
||||
cust_attr_query.format(attr_def["id"])
|
||||
).all()
|
||||
for value in values:
|
||||
table_values = collections.OrderedDict([
|
||||
("configuration_id", hierarchical_attr["id"]),
|
||||
("entity_id", value["entity_id"])
|
||||
])
|
||||
|
||||
session.recorded_operations.push(
|
||||
ftrack_api.operation.UpdateEntityOperation(
|
||||
|
|
|
|||
|
|
@ -303,9 +303,10 @@ class FtrackModule(
|
|||
# TODO add add permissions check
|
||||
# TODO add value validations
|
||||
# - value type and list items
|
||||
entity_key = collections.OrderedDict()
|
||||
entity_key["configuration_id"] = configuration["id"]
|
||||
entity_key["entity_id"] = project_id
|
||||
entity_key = collections.OrderedDict([
|
||||
("configuration_id", configuration["id"]),
|
||||
("entity_id", project_id)
|
||||
])
|
||||
|
||||
session.recorded_operations.push(
|
||||
ftrack_api.operation.UpdateEntityOperation(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import os
|
||||
import re
|
||||
import json
|
||||
import collections
|
||||
import copy
|
||||
|
||||
import six
|
||||
|
||||
from avalon.api import AvalonMongoDB
|
||||
|
||||
import avalon
|
||||
|
|
@ -18,7 +15,7 @@ from openpype.api import (
|
|||
from openpype.lib import ApplicationManager
|
||||
|
||||
from .constants import CUST_ATTR_ID_KEY
|
||||
from .custom_attributes import get_openpype_attr
|
||||
from .custom_attributes import get_openpype_attr, query_custom_attributes
|
||||
|
||||
from bson.objectid import ObjectId
|
||||
from bson.errors import InvalidId
|
||||
|
|
@ -235,33 +232,19 @@ def get_hierarchical_attributes_values(
|
|||
|
||||
entity_ids = [item["id"] for item in entity["link"]]
|
||||
|
||||
join_ent_ids = join_query_keys(entity_ids)
|
||||
join_attribute_ids = join_query_keys(attr_key_by_id.keys())
|
||||
|
||||
queries = []
|
||||
queries.append({
|
||||
"action": "query",
|
||||
"expression": (
|
||||
"select value, configuration_id, entity_id"
|
||||
" from CustomAttributeValue"
|
||||
" where entity_id in ({}) and configuration_id in ({})"
|
||||
).format(join_ent_ids, join_attribute_ids)
|
||||
})
|
||||
|
||||
if hasattr(session, "call"):
|
||||
[values] = session.call(queries)
|
||||
else:
|
||||
[values] = session._call(queries)
|
||||
values = query_custom_attributes(
|
||||
session, list(attr_key_by_id.keys()), entity_ids, True
|
||||
)
|
||||
|
||||
hier_values = {}
|
||||
for key, val in defaults.items():
|
||||
hier_values[key] = val
|
||||
|
||||
if not values["data"]:
|
||||
if not values:
|
||||
return hier_values
|
||||
|
||||
values_by_entity_id = collections.defaultdict(dict)
|
||||
for item in values["data"]:
|
||||
for item in values:
|
||||
value = item["value"]
|
||||
if value is None:
|
||||
continue
|
||||
|
|
@ -304,7 +287,7 @@ class SyncEntitiesFactory:
|
|||
" from Project where full_name is \"{}\""
|
||||
)
|
||||
entities_query = (
|
||||
"select id, name, type_id, parent_id, link"
|
||||
"select id, name, type_id, parent_id, link, description"
|
||||
" from TypedContext where project_id is \"{}\""
|
||||
)
|
||||
ignore_custom_attr_key = "avalon_ignore_sync"
|
||||
|
|
@ -861,33 +844,6 @@ class SyncEntitiesFactory:
|
|||
|
||||
self.entities_dict[parent_id]["children"].remove(ftrack_id)
|
||||
|
||||
def _query_custom_attributes(self, session, conf_ids, entity_ids):
|
||||
output = []
|
||||
# Prepare values to query
|
||||
attributes_joined = join_query_keys(conf_ids)
|
||||
attributes_len = len(conf_ids)
|
||||
chunk_size = int(5000 / attributes_len)
|
||||
for idx in range(0, len(entity_ids), chunk_size):
|
||||
entity_ids_joined = join_query_keys(
|
||||
entity_ids[idx:idx + chunk_size]
|
||||
)
|
||||
|
||||
call_expr = [{
|
||||
"action": "query",
|
||||
"expression": (
|
||||
"select value, entity_id from ContextCustomAttributeValue "
|
||||
"where entity_id in ({}) and configuration_id in ({})"
|
||||
).format(entity_ids_joined, attributes_joined)
|
||||
}]
|
||||
if hasattr(session, "call"):
|
||||
[result] = session.call(call_expr)
|
||||
else:
|
||||
[result] = session._call(call_expr)
|
||||
|
||||
for item in result["data"]:
|
||||
output.append(item)
|
||||
return output
|
||||
|
||||
def set_cutom_attributes(self):
|
||||
self.log.debug("* Preparing custom attributes")
|
||||
# Get custom attributes and values
|
||||
|
|
@ -994,7 +950,7 @@ class SyncEntitiesFactory:
|
|||
copy.deepcopy(prepared_avalon_attr_ca_id)
|
||||
)
|
||||
|
||||
items = self._query_custom_attributes(
|
||||
items = query_custom_attributes(
|
||||
self.session,
|
||||
list(attribute_key_by_id.keys()),
|
||||
sync_ids
|
||||
|
|
@ -1082,10 +1038,11 @@ class SyncEntitiesFactory:
|
|||
for key, val in prepare_dict_avalon.items():
|
||||
entity_dict["avalon_attrs"][key] = val
|
||||
|
||||
items = self._query_custom_attributes(
|
||||
items = query_custom_attributes(
|
||||
self.session,
|
||||
list(attribute_key_by_id.keys()),
|
||||
sync_ids
|
||||
sync_ids,
|
||||
True
|
||||
)
|
||||
|
||||
avalon_hier = []
|
||||
|
|
@ -1231,6 +1188,8 @@ class SyncEntitiesFactory:
|
|||
data[key] = val
|
||||
|
||||
if ftrack_id != self.ft_project_id:
|
||||
data["description"] = entity["description"]
|
||||
|
||||
ent_path_items = [ent["name"] for ent in entity["link"]]
|
||||
parents = ent_path_items[1:len(ent_path_items) - 1:]
|
||||
|
||||
|
|
@ -1804,10 +1763,10 @@ class SyncEntitiesFactory:
|
|||
configuration_id = self.entities_dict[ftrack_id][
|
||||
"avalon_attrs_id"][CUST_ATTR_ID_KEY]
|
||||
|
||||
_entity_key = collections.OrderedDict({
|
||||
"configuration_id": configuration_id,
|
||||
"entity_id": ftrack_id
|
||||
})
|
||||
_entity_key = collections.OrderedDict([
|
||||
("configuration_id", configuration_id),
|
||||
("entity_id", ftrack_id)
|
||||
])
|
||||
|
||||
self.session.recorded_operations.push(
|
||||
ftrack_api.operation.UpdateEntityOperation(
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ def default_custom_attributes_definition():
|
|||
def app_definitions_from_app_manager(app_manager):
|
||||
_app_definitions = []
|
||||
for app_name, app in app_manager.applications.items():
|
||||
if app.enabled and app.is_host:
|
||||
if app.enabled:
|
||||
_app_definitions.append(
|
||||
(app_name, app.full_label)
|
||||
)
|
||||
|
|
@ -88,26 +88,36 @@ def join_query_keys(keys):
|
|||
return ",".join(["\"{}\"".format(key) for key in keys])
|
||||
|
||||
|
||||
def query_custom_attributes(session, conf_ids, entity_ids, table_name=None):
|
||||
def query_custom_attributes(
|
||||
session, conf_ids, entity_ids, only_set_values=False
|
||||
):
|
||||
"""Query custom attribute values from ftrack database.
|
||||
|
||||
Using ftrack call method result may differ based on used table name and
|
||||
version of ftrack server.
|
||||
|
||||
For hierarchical attributes you shou always use `only_set_values=True`
|
||||
otherwise result will be default value of custom attribute and it would not
|
||||
be possible to differentiate if value is set on entity or default value is
|
||||
used.
|
||||
|
||||
Args:
|
||||
session(ftrack_api.Session): Connected ftrack session.
|
||||
conf_id(list, set, tuple): Configuration(attribute) ids which are
|
||||
queried.
|
||||
entity_ids(list, set, tuple): Entity ids for which are values queried.
|
||||
table_name(str): Table nam from which values are queried. Not
|
||||
recommended to change until you know what it means.
|
||||
only_set_values(bool): Entities that don't have explicitly set
|
||||
value won't return a value. If is set to False then default custom
|
||||
attribute value is returned if value is not set.
|
||||
"""
|
||||
output = []
|
||||
# Just skip
|
||||
if not conf_ids or not entity_ids:
|
||||
return output
|
||||
|
||||
if table_name is None:
|
||||
if only_set_values:
|
||||
table_name = "CustomAttributeValue"
|
||||
else:
|
||||
table_name = "ContextCustomAttributeValue"
|
||||
|
||||
# Prepare values to query
|
||||
|
|
@ -122,19 +132,16 @@ def query_custom_attributes(session, conf_ids, entity_ids, table_name=None):
|
|||
entity_ids_joined = join_query_keys(
|
||||
entity_ids[idx:idx + chunk_size]
|
||||
)
|
||||
|
||||
call_expr = [{
|
||||
"action": "query",
|
||||
"expression": (
|
||||
"select value, entity_id from {}"
|
||||
" where entity_id in ({}) and configuration_id in ({})"
|
||||
).format(table_name, entity_ids_joined, attributes_joined)
|
||||
}]
|
||||
if hasattr(session, "call"):
|
||||
[result] = session.call(call_expr)
|
||||
else:
|
||||
[result] = session._call(call_expr)
|
||||
|
||||
for item in result["data"]:
|
||||
output.append(item)
|
||||
output.extend(
|
||||
session.query(
|
||||
(
|
||||
"select value, entity_id from {}"
|
||||
" where entity_id in ({}) and configuration_id in ({})"
|
||||
).format(
|
||||
table_name,
|
||||
entity_ids_joined,
|
||||
attributes_joined
|
||||
)
|
||||
).all()
|
||||
)
|
||||
return output
|
||||
|
|
|
|||
73
openpype/plugins/publish/cleanup_farm.py
Normal file
73
openpype/plugins/publish/cleanup_farm.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Cleanup leftover files from publish."""
|
||||
import os
|
||||
import shutil
|
||||
import pyblish.api
|
||||
import avalon.api
|
||||
|
||||
|
||||
class CleanUpFarm(pyblish.api.ContextPlugin):
|
||||
"""Cleans up the staging directory after a successful publish.
|
||||
|
||||
This will also clean published renders and delete their parent directories.
|
||||
"""
|
||||
|
||||
order = pyblish.api.IntegratorOrder + 11
|
||||
label = "Clean Up Farm"
|
||||
enabled = True
|
||||
|
||||
# Keep "filesequence" for backwards compatibility of older jobs
|
||||
targets = ["filesequence", "farm"]
|
||||
allowed_hosts = ("maya", )
|
||||
|
||||
def process(self, context):
|
||||
# Get source host from which farm publishing was started
|
||||
src_host_name = avalon.api.Session.get("AVALON_APP")
|
||||
self.log.debug("Host name from session is {}".format(src_host_name))
|
||||
# Skip process if is not in list of source hosts in which this
|
||||
# plugin should run
|
||||
if src_host_name not in self.allowed_hosts:
|
||||
self.log.info((
|
||||
"Source host \"{}\" is not in list of enabled hosts {}."
|
||||
" Skipping"
|
||||
).format(str(src_host_name), str(self.allowed_hosts)))
|
||||
return
|
||||
|
||||
self.log.debug("Preparing filepaths to remove")
|
||||
# Collect directories to remove
|
||||
dirpaths_to_remove = set()
|
||||
for instance in context:
|
||||
staging_dir = instance.data.get("stagingDir")
|
||||
if staging_dir:
|
||||
dirpaths_to_remove.add(os.path.normpath(staging_dir))
|
||||
|
||||
if "representations" in instance.data:
|
||||
for repre in instance.data["representations"]:
|
||||
staging_dir = repre.get("stagingDir")
|
||||
if staging_dir:
|
||||
dirpaths_to_remove.add(os.path.normpath(staging_dir))
|
||||
|
||||
if not dirpaths_to_remove:
|
||||
self.log.info("Nothing to remove. Skipping")
|
||||
return
|
||||
|
||||
self.log.debug("Filepaths to remove are:\n{}".format(
|
||||
"\n".join(["- {}".format(path) for path in dirpaths_to_remove])
|
||||
))
|
||||
|
||||
# clean dirs which are empty
|
||||
for dirpath in dirpaths_to_remove:
|
||||
if not os.path.exists(dirpath):
|
||||
self.log.debug("Skipping not existing directory \"{}\"".format(
|
||||
dirpath
|
||||
))
|
||||
continue
|
||||
|
||||
self.log.debug("Removing directory \"{}\"".format(dirpath))
|
||||
try:
|
||||
shutil.rmtree(dirpath)
|
||||
except OSError:
|
||||
self.log.warning(
|
||||
"Failed to remove directory \"{}\"".format(dirpath),
|
||||
exc_info=True
|
||||
)
|
||||
|
|
@ -12,11 +12,11 @@ Provides:
|
|||
context -> anatomyData
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
from openpype.lib import ApplicationManager
|
||||
from avalon import api, lib
|
||||
from openpype.lib import (
|
||||
get_system_general_anatomy_data
|
||||
)
|
||||
from avalon import api
|
||||
import pyblish.api
|
||||
|
||||
|
||||
|
|
@ -44,6 +44,7 @@ class CollectAnatomyContextData(pyblish.api.ContextPlugin):
|
|||
label = "Collect Anatomy Context Data"
|
||||
|
||||
def process(self, context):
|
||||
|
||||
task_name = api.Session["AVALON_TASK"]
|
||||
|
||||
project_entity = context.data["projectEntity"]
|
||||
|
|
@ -79,6 +80,10 @@ class CollectAnatomyContextData(pyblish.api.ContextPlugin):
|
|||
"app": context.data["hostName"]
|
||||
}
|
||||
|
||||
# add system general settings anatomy data
|
||||
system_general_data = get_system_general_anatomy_data()
|
||||
context_data.update(system_general_data)
|
||||
|
||||
datetime_data = context.data.get("datetimeData") or {}
|
||||
context_data.update(datetime_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin):
|
|||
|
||||
"""
|
||||
order = pyblish.api.CollectorOrder - 0.2
|
||||
targets = ["filesequence"]
|
||||
# Keep "filesequence" for backwards compatibility of older jobs
|
||||
targets = ["filesequence", "farm"]
|
||||
label = "Collect rendered frames"
|
||||
|
||||
_context = None
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ class ValidateFileSequences(pyblish.api.ContextPlugin):
|
|||
"""Validates whether any file sequences were collected."""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
targets = ["filesequence"]
|
||||
# Keep "filesequence" for backwards compatibility of older jobs
|
||||
targets = ["filesequence", "farm"]
|
||||
label = "Validate File Sequences"
|
||||
|
||||
def process(self, context):
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ class PypeCommands:
|
|||
print(f"setting target: {target}")
|
||||
pyblish.api.register_target(target)
|
||||
else:
|
||||
pyblish.api.register_target("filesequence")
|
||||
pyblish.api.register_target("farm")
|
||||
|
||||
os.environ["OPENPYPE_PUBLISH_DATA"] = os.pathsep.join(paths)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,15 @@ def get_resource(*args):
|
|||
return os.path.normpath(os.path.join(RESOURCES_DIR, *args))
|
||||
|
||||
|
||||
def get_image_path(*args):
|
||||
"""Helper function to get images.
|
||||
|
||||
Args:
|
||||
*<str>: Filepath part items.
|
||||
"""
|
||||
return get_resource("images", *args)
|
||||
|
||||
|
||||
def get_liberation_font_path(bold=False, italic=False):
|
||||
font_name = "LiberationSans"
|
||||
suffix = ""
|
||||
|
|
|
|||
BIN
openpype/resources/images/warning.png
Normal file
BIN
openpype/resources/images/warning.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
|
|
@ -5,6 +5,8 @@ from .constants import (
|
|||
PROJECT_ANATOMY_KEY,
|
||||
LOCAL_SETTING_KEY,
|
||||
|
||||
LEGACY_SETTINGS_VERSION,
|
||||
|
||||
SCHEMA_KEY_SYSTEM_SETTINGS,
|
||||
SCHEMA_KEY_PROJECT_SETTINGS,
|
||||
|
||||
|
|
@ -37,6 +39,8 @@ __all__ = (
|
|||
"PROJECT_ANATOMY_KEY",
|
||||
"LOCAL_SETTING_KEY",
|
||||
|
||||
"LEGACY_SETTINGS_VERSION",
|
||||
|
||||
"SCHEMA_KEY_SYSTEM_SETTINGS",
|
||||
"SCHEMA_KEY_PROJECT_SETTINGS",
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ PROJECT_SETTINGS_KEY = "project_settings"
|
|||
PROJECT_ANATOMY_KEY = "project_anatomy"
|
||||
LOCAL_SETTING_KEY = "local_settings"
|
||||
|
||||
LEGACY_SETTINGS_VERSION = "legacy"
|
||||
|
||||
# Schema hub names
|
||||
SCHEMA_KEY_SYSTEM_SETTINGS = "system_schema"
|
||||
SCHEMA_KEY_PROJECT_SETTINGS = "projects_schema"
|
||||
|
|
|
|||
|
|
@ -207,6 +207,9 @@
|
|||
"CleanUp": {
|
||||
"paterns": [],
|
||||
"remove_temp_renders": false
|
||||
},
|
||||
"CleanUpFarm": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
|
|
|
|||
|
|
@ -170,6 +170,11 @@
|
|||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateFrameRange": {
|
||||
"enabled": true,
|
||||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateShaderName": {
|
||||
"enabled": false,
|
||||
"regex": "(?P<asset>.*)_(.*)_SHD"
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@
|
|||
"publish": {
|
||||
"CollectPublishedFiles": {
|
||||
"task_type_to_family": {
|
||||
"Animation": {
|
||||
"workfile": {
|
||||
"Animation": [
|
||||
{
|
||||
"is_sequence": false,
|
||||
"extensions": [
|
||||
"tvp"
|
||||
],
|
||||
"families": [],
|
||||
"tags": [],
|
||||
"subset_template_name": ""
|
||||
"result_family": "workfile"
|
||||
},
|
||||
"render": {
|
||||
{
|
||||
"is_sequence": true,
|
||||
"extensions": [
|
||||
"png",
|
||||
|
|
@ -26,20 +26,20 @@
|
|||
"tags": [
|
||||
"review"
|
||||
],
|
||||
"subset_template_name": ""
|
||||
"result_family": "render"
|
||||
}
|
||||
},
|
||||
"Compositing": {
|
||||
"workfile": {
|
||||
],
|
||||
"Compositing": [
|
||||
{
|
||||
"is_sequence": false,
|
||||
"extensions": [
|
||||
"aep"
|
||||
],
|
||||
"families": [],
|
||||
"tags": [],
|
||||
"subset_template_name": ""
|
||||
"result_family": "workfile"
|
||||
},
|
||||
"render": {
|
||||
{
|
||||
"is_sequence": true,
|
||||
"extensions": [
|
||||
"png",
|
||||
|
|
@ -53,20 +53,20 @@
|
|||
"tags": [
|
||||
"review"
|
||||
],
|
||||
"subset_template_name": ""
|
||||
"result_family": "render"
|
||||
}
|
||||
},
|
||||
"Layout": {
|
||||
"workfile": {
|
||||
],
|
||||
"Layout": [
|
||||
{
|
||||
"is_sequence": false,
|
||||
"extensions": [
|
||||
"psd"
|
||||
],
|
||||
"families": [],
|
||||
"tags": [],
|
||||
"subset_template_name": ""
|
||||
"result_family": "workfile"
|
||||
},
|
||||
"image": {
|
||||
{
|
||||
"is_sequence": false,
|
||||
"extensions": [
|
||||
"png",
|
||||
|
|
@ -81,20 +81,21 @@
|
|||
"tags": [
|
||||
"review"
|
||||
],
|
||||
"subset_template_name": ""
|
||||
"result_family": "image"
|
||||
}
|
||||
},
|
||||
"default_task_type": {
|
||||
"workfile": {
|
||||
],
|
||||
"default_task_type": [
|
||||
{
|
||||
"is_sequence": false,
|
||||
"extensions": [
|
||||
"tvp"
|
||||
"tvp",
|
||||
"psd"
|
||||
],
|
||||
"families": [],
|
||||
"tags": [],
|
||||
"subset_template_name": "{family}{Variant}"
|
||||
"result_family": "workfile"
|
||||
},
|
||||
"render": {
|
||||
{
|
||||
"is_sequence": true,
|
||||
"extensions": [
|
||||
"png",
|
||||
|
|
@ -108,9 +109,9 @@
|
|||
"tags": [
|
||||
"review"
|
||||
],
|
||||
"subset_template_name": "{family}{Variant}"
|
||||
"result_family": "render"
|
||||
}
|
||||
},
|
||||
],
|
||||
"__dynamic_keys_labels__": {
|
||||
"default_task_type": "Default task type"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1186,58 +1186,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"shell": {
|
||||
"enabled": true,
|
||||
"environment": {},
|
||||
"variants": {
|
||||
"python_3-7": {
|
||||
"use_python_2": true,
|
||||
"executables": {
|
||||
"windows": [],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"arguments": {
|
||||
"windows": [],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"environment": {}
|
||||
},
|
||||
"python_2-7": {
|
||||
"use_python_2": true,
|
||||
"executables": {
|
||||
"windows": [],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"arguments": {
|
||||
"windows": [],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"environment": {}
|
||||
},
|
||||
"terminal": {
|
||||
"use_python_2": true,
|
||||
"executables": {
|
||||
"windows": [],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"arguments": {
|
||||
"windows": [],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"environment": {}
|
||||
},
|
||||
"__dynamic_keys_labels__": {
|
||||
"python_3-7": "Python 3.7",
|
||||
"python_2-7": "Python 2.7"
|
||||
}
|
||||
}
|
||||
},
|
||||
"djvview": {
|
||||
"enabled": true,
|
||||
"label": "DJV View",
|
||||
|
|
@ -1263,5 +1211,6 @@
|
|||
"1-1": "1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additional_apps": {}
|
||||
}
|
||||
|
|
@ -263,6 +263,7 @@ class HostsEnumEntity(BaseEnumEntity):
|
|||
|
||||
|
||||
class AppsEnumEntity(BaseEnumEntity):
|
||||
"""Enum of applications for project anatomy attributes."""
|
||||
schema_types = ["apps-enum"]
|
||||
|
||||
def _item_initialization(self):
|
||||
|
|
@ -279,16 +280,30 @@ class AppsEnumEntity(BaseEnumEntity):
|
|||
valid_keys = set()
|
||||
enum_items_list = []
|
||||
applications_entity = system_settings_entity["applications"]
|
||||
app_entities = {}
|
||||
additional_app_names = set()
|
||||
additional_apps_entity = None
|
||||
for group_name, app_group in applications_entity.items():
|
||||
if group_name != "additional_apps":
|
||||
app_entities[group_name] = app_group
|
||||
continue
|
||||
|
||||
additional_apps_entity = app_group
|
||||
for _group_name, _group in app_group.items():
|
||||
additional_app_names.add(_group_name)
|
||||
app_entities[_group_name] = _group
|
||||
|
||||
for group_name, app_group in app_entities.items():
|
||||
enabled_entity = app_group.get("enabled")
|
||||
if enabled_entity and not enabled_entity.value:
|
||||
continue
|
||||
|
||||
host_name_entity = app_group.get("host_name")
|
||||
if not host_name_entity or not host_name_entity.value:
|
||||
continue
|
||||
|
||||
group_label = app_group["label"].value
|
||||
if group_name in additional_app_names:
|
||||
group_label = additional_apps_entity.get_key_label(group_name)
|
||||
if not group_label:
|
||||
group_label = group_name
|
||||
else:
|
||||
group_label = app_group["label"].value
|
||||
variants_entity = app_group["variants"]
|
||||
for variant_name, variant_entity in variants_entity.items():
|
||||
enabled_entity = variant_entity.get("enabled")
|
||||
|
|
|
|||
|
|
@ -34,15 +34,24 @@ from openpype.settings.lib import (
|
|||
reset_default_settings,
|
||||
|
||||
get_studio_system_settings_overrides,
|
||||
get_studio_system_settings_overrides_for_version,
|
||||
save_studio_settings,
|
||||
get_available_studio_system_settings_overrides_versions,
|
||||
|
||||
get_studio_project_settings_overrides,
|
||||
get_studio_project_settings_overrides_for_version,
|
||||
get_studio_project_anatomy_overrides,
|
||||
get_studio_project_anatomy_overrides_for_version,
|
||||
get_project_settings_overrides,
|
||||
get_project_settings_overrides_for_version,
|
||||
get_project_anatomy_overrides,
|
||||
save_project_settings,
|
||||
save_project_anatomy,
|
||||
|
||||
get_available_project_settings_overrides_versions,
|
||||
get_available_studio_project_settings_overrides_versions,
|
||||
get_available_studio_project_anatomy_overrides_versions,
|
||||
|
||||
find_environments,
|
||||
apply_overrides
|
||||
)
|
||||
|
|
@ -495,17 +504,27 @@ class SystemSettings(RootEntity):
|
|||
root_key = SYSTEM_SETTINGS_KEY
|
||||
|
||||
def __init__(
|
||||
self, set_studio_state=True, reset=True, schema_hub=None
|
||||
self,
|
||||
set_studio_state=True,
|
||||
reset=True,
|
||||
schema_hub=None,
|
||||
source_version=None
|
||||
):
|
||||
if schema_hub is None:
|
||||
# Load system schemas
|
||||
schema_hub = SchemasHub(SCHEMA_KEY_SYSTEM_SETTINGS)
|
||||
|
||||
self._source_version = source_version
|
||||
|
||||
super(SystemSettings, self).__init__(schema_hub, reset)
|
||||
|
||||
if set_studio_state:
|
||||
self.set_studio_state()
|
||||
|
||||
@property
|
||||
def source_version(self):
|
||||
return self._source_version
|
||||
|
||||
def get_entity_from_path(self, path):
|
||||
"""Return system settings entity."""
|
||||
path_parts = path.split("/")
|
||||
|
|
@ -524,12 +543,24 @@ class SystemSettings(RootEntity):
|
|||
value = default_value.get(key, NOT_SET)
|
||||
child_obj.update_default_value(value)
|
||||
|
||||
studio_overrides = get_studio_system_settings_overrides()
|
||||
if self._source_version is None:
|
||||
studio_overrides, version = get_studio_system_settings_overrides(
|
||||
return_version=True
|
||||
)
|
||||
self._source_version = version
|
||||
|
||||
else:
|
||||
studio_overrides = (
|
||||
get_studio_system_settings_overrides_for_version(
|
||||
self._source_version
|
||||
)
|
||||
)
|
||||
|
||||
for key, child_obj in self.non_gui_children.items():
|
||||
value = studio_overrides.get(key, NOT_SET)
|
||||
child_obj.update_studio_value(value)
|
||||
|
||||
def reset(self, new_state=None):
|
||||
def reset(self, new_state=None, source_version=None):
|
||||
"""Discard changes and reset entit's values.
|
||||
|
||||
Reload default values and studio override values and update entities.
|
||||
|
|
@ -547,9 +578,22 @@ class SystemSettings(RootEntity):
|
|||
if new_state is OverrideState.PROJECT:
|
||||
raise ValueError("System settings can't store poject overrides.")
|
||||
|
||||
if source_version is not None:
|
||||
self._source_version = source_version
|
||||
|
||||
self._reset_values()
|
||||
self.set_override_state(new_state)
|
||||
|
||||
def get_available_source_versions(self, sorted=None):
|
||||
if self.is_in_studio_state():
|
||||
return self.get_available_studio_versions(sorted=sorted)
|
||||
return []
|
||||
|
||||
def get_available_studio_versions(self, sorted=None):
|
||||
return get_available_studio_system_settings_overrides_versions(
|
||||
sorted=sorted
|
||||
)
|
||||
|
||||
def defaults_dir(self):
|
||||
"""Path to defaults directory.
|
||||
|
||||
|
|
@ -566,6 +610,8 @@ class SystemSettings(RootEntity):
|
|||
json.dumps(settings_value, indent=4)
|
||||
))
|
||||
save_studio_settings(settings_value)
|
||||
# Reset source version after restart
|
||||
self._source_version = None
|
||||
|
||||
def _validate_defaults_to_save(self, value):
|
||||
"""Valiations of default values before save."""
|
||||
|
|
@ -622,11 +668,15 @@ class ProjectSettings(RootEntity):
|
|||
project_name=None,
|
||||
change_state=True,
|
||||
reset=True,
|
||||
schema_hub=None
|
||||
schema_hub=None,
|
||||
source_version=None,
|
||||
anatomy_source_version=None
|
||||
):
|
||||
self._project_name = project_name
|
||||
|
||||
self._system_settings_entity = None
|
||||
self._source_version = source_version
|
||||
self._anatomy_source_version = anatomy_source_version
|
||||
|
||||
if schema_hub is None:
|
||||
# Load system schemas
|
||||
|
|
@ -640,6 +690,14 @@ class ProjectSettings(RootEntity):
|
|||
else:
|
||||
self.set_project_state()
|
||||
|
||||
@property
|
||||
def source_version(self):
|
||||
return self._source_version
|
||||
|
||||
@property
|
||||
def anatomy_source_version(self):
|
||||
return self._anatomy_source_version
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
return self._project_name
|
||||
|
|
@ -682,23 +740,20 @@ class ProjectSettings(RootEntity):
|
|||
output = output[path_part]
|
||||
return output
|
||||
|
||||
def change_project(self, project_name):
|
||||
def change_project(self, project_name, source_version=None):
|
||||
if project_name == self._project_name:
|
||||
return
|
||||
if (
|
||||
source_version is None
|
||||
or source_version == self._source_version
|
||||
):
|
||||
if not self.is_in_project_state():
|
||||
self.set_project_state()
|
||||
return
|
||||
|
||||
self._project_name = project_name
|
||||
if project_name is None:
|
||||
self.set_studio_state()
|
||||
return
|
||||
|
||||
project_override_value = {
|
||||
PROJECT_SETTINGS_KEY: get_project_settings_overrides(project_name),
|
||||
PROJECT_ANATOMY_KEY: get_project_anatomy_overrides(project_name)
|
||||
}
|
||||
for key, child_obj in self.non_gui_children.items():
|
||||
value = project_override_value.get(key, NOT_SET)
|
||||
child_obj.update_project_value(value)
|
||||
self._source_version = source_version
|
||||
self._anatomy_source_version = None
|
||||
|
||||
self._set_values_for_project(project_name)
|
||||
self.set_project_state()
|
||||
|
||||
def _reset_values(self):
|
||||
|
|
@ -710,27 +765,97 @@ class ProjectSettings(RootEntity):
|
|||
value = default_values.get(key, NOT_SET)
|
||||
child_obj.update_default_value(value)
|
||||
|
||||
self._set_values_for_project(self.project_name)
|
||||
|
||||
def _set_values_for_project(self, project_name):
|
||||
self._project_name = project_name
|
||||
if project_name:
|
||||
project_settings_overrides = (
|
||||
get_studio_project_settings_overrides()
|
||||
)
|
||||
project_anatomy_overrides = (
|
||||
get_studio_project_anatomy_overrides()
|
||||
)
|
||||
else:
|
||||
if self._source_version is None:
|
||||
project_settings_overrides, version = (
|
||||
get_studio_project_settings_overrides(return_version=True)
|
||||
)
|
||||
self._source_version = version
|
||||
else:
|
||||
project_settings_overrides = (
|
||||
get_studio_project_settings_overrides_for_version(
|
||||
self._source_version
|
||||
)
|
||||
)
|
||||
|
||||
if self._anatomy_source_version is None:
|
||||
project_anatomy_overrides, anatomy_version = (
|
||||
get_studio_project_anatomy_overrides(return_version=True)
|
||||
)
|
||||
self._anatomy_source_version = anatomy_version
|
||||
else:
|
||||
project_anatomy_overrides = (
|
||||
get_studio_project_anatomy_overrides_for_version(
|
||||
self._anatomy_source_version
|
||||
)
|
||||
)
|
||||
|
||||
studio_overrides = {
|
||||
PROJECT_SETTINGS_KEY: get_studio_project_settings_overrides(),
|
||||
PROJECT_ANATOMY_KEY: get_studio_project_anatomy_overrides()
|
||||
PROJECT_SETTINGS_KEY: project_settings_overrides,
|
||||
PROJECT_ANATOMY_KEY: project_anatomy_overrides
|
||||
}
|
||||
|
||||
for key, child_obj in self.non_gui_children.items():
|
||||
value = studio_overrides.get(key, NOT_SET)
|
||||
child_obj.update_studio_value(value)
|
||||
|
||||
if not self.project_name:
|
||||
if not project_name:
|
||||
return
|
||||
|
||||
project_name = self.project_name
|
||||
if self._source_version is None:
|
||||
project_settings_overrides, version = (
|
||||
get_project_settings_overrides(
|
||||
project_name, return_version=True
|
||||
)
|
||||
)
|
||||
self._source_version = version
|
||||
else:
|
||||
project_settings_overrides = (
|
||||
get_project_settings_overrides_for_version(
|
||||
project_name, self._source_version
|
||||
)
|
||||
)
|
||||
|
||||
project_override_value = {
|
||||
PROJECT_SETTINGS_KEY: get_project_settings_overrides(project_name),
|
||||
PROJECT_SETTINGS_KEY: project_settings_overrides,
|
||||
PROJECT_ANATOMY_KEY: get_project_anatomy_overrides(project_name)
|
||||
}
|
||||
for key, child_obj in self.non_gui_children.items():
|
||||
value = project_override_value.get(key, NOT_SET)
|
||||
child_obj.update_project_value(value)
|
||||
|
||||
def get_available_source_versions(self, sorted=None):
|
||||
if self.is_in_studio_state():
|
||||
return self.get_available_studio_versions(sorted=sorted)
|
||||
elif self.is_in_project_state():
|
||||
return get_available_project_settings_overrides_versions(
|
||||
self.project_name, sorted=sorted
|
||||
)
|
||||
return []
|
||||
|
||||
def get_available_studio_versions(self, sorted=None):
|
||||
return get_available_studio_project_settings_overrides_versions(
|
||||
sorted=sorted
|
||||
)
|
||||
|
||||
def get_available_anatomy_source_versions(self, sorted=None):
|
||||
if self.is_in_studio_state():
|
||||
return get_available_studio_project_anatomy_overrides_versions(
|
||||
sorted=sorted
|
||||
)
|
||||
return []
|
||||
|
||||
def reset(self, new_state=None):
|
||||
"""Discard changes and reset entit's values.
|
||||
|
||||
|
|
@ -763,6 +888,9 @@ class ProjectSettings(RootEntity):
|
|||
|
||||
self._validate_values_to_save(settings_value)
|
||||
|
||||
self._source_version = None
|
||||
self._anatomy_source_version = None
|
||||
|
||||
self.log.debug("Saving project settings: {}".format(
|
||||
json.dumps(settings_value, indent=4)
|
||||
))
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@
|
|||
"label": "Task type to family mapping",
|
||||
"collapsible_key": true,
|
||||
"object_type": {
|
||||
"type": "dict-modifiable",
|
||||
"collapsible": false,
|
||||
"type": "list",
|
||||
"collapsible": true,
|
||||
"key": "task_type",
|
||||
"collapsible_key": false,
|
||||
"collapsible_key": true,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
|
|
@ -52,10 +52,13 @@
|
|||
"type": "schema",
|
||||
"name": "schema_representation_tags"
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "subset_template_name",
|
||||
"label": "Subset template name"
|
||||
"key": "result_family",
|
||||
"label": "Resulting family"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -677,8 +677,22 @@
|
|||
"label": "Remove Temp renders",
|
||||
"default": false
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": false,
|
||||
"key": "CleanUpFarm",
|
||||
"label": "Clean Up Farm",
|
||||
"is_group": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,16 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_publish_plugin",
|
||||
"template_data": [
|
||||
{
|
||||
"key": "ValidateFrameRange",
|
||||
"label": "Validate Frame Range"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"key": "shell",
|
||||
"label": "Shell",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"key": "environment",
|
||||
"label": "Environment",
|
||||
"type": "raw-json"
|
||||
},
|
||||
{
|
||||
"type": "dict-modifiable",
|
||||
"key": "variants",
|
||||
"collapsible_key": true,
|
||||
"use_label_wrap": false,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_host_variant_items"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -87,11 +87,50 @@
|
|||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_shell"
|
||||
"name": "schema_djv"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_djv"
|
||||
"type": "dict-modifiable",
|
||||
"key": "additional_apps",
|
||||
"label": "Additional",
|
||||
"collapsible": true,
|
||||
"collapsible_key": true,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_host_unchangables",
|
||||
"skip_paths": ["host_name", "label"]
|
||||
},
|
||||
{
|
||||
"key": "environment",
|
||||
"label": "Environment",
|
||||
"type": "raw-json"
|
||||
},
|
||||
{
|
||||
"type": "dict-modifiable",
|
||||
"key": "variants",
|
||||
"collapsible_key": true,
|
||||
"use_label_wrap": false,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_host_variant_items"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -266,23 +266,31 @@ def save_project_anatomy(project_name, anatomy_data):
|
|||
|
||||
|
||||
@require_handler
|
||||
def get_studio_system_settings_overrides():
|
||||
return _SETTINGS_HANDLER.get_studio_system_settings_overrides()
|
||||
def get_studio_system_settings_overrides(return_version=False):
|
||||
return _SETTINGS_HANDLER.get_studio_system_settings_overrides(
|
||||
return_version
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_studio_project_settings_overrides():
|
||||
return _SETTINGS_HANDLER.get_studio_project_settings_overrides()
|
||||
def get_studio_project_settings_overrides(return_version=False):
|
||||
return _SETTINGS_HANDLER.get_studio_project_settings_overrides(
|
||||
return_version
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_studio_project_anatomy_overrides():
|
||||
return _SETTINGS_HANDLER.get_studio_project_anatomy_overrides()
|
||||
def get_studio_project_anatomy_overrides(return_version=False):
|
||||
return _SETTINGS_HANDLER.get_studio_project_anatomy_overrides(
|
||||
return_version
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_project_settings_overrides(project_name):
|
||||
return _SETTINGS_HANDLER.get_project_settings_overrides(project_name)
|
||||
def get_project_settings_overrides(project_name, return_version=False):
|
||||
return _SETTINGS_HANDLER.get_project_settings_overrides(
|
||||
project_name, return_version
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
|
|
@ -290,6 +298,123 @@ def get_project_anatomy_overrides(project_name):
|
|||
return _SETTINGS_HANDLER.get_project_anatomy_overrides(project_name)
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_studio_system_settings_overrides_for_version(version):
|
||||
return (
|
||||
_SETTINGS_HANDLER
|
||||
.get_studio_system_settings_overrides_for_version(version)
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_studio_project_anatomy_overrides_for_version(version):
|
||||
return (
|
||||
_SETTINGS_HANDLER
|
||||
.get_studio_project_anatomy_overrides_for_version(version)
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_studio_project_settings_overrides_for_version(version):
|
||||
return (
|
||||
_SETTINGS_HANDLER
|
||||
.get_studio_project_settings_overrides_for_version(version)
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_project_settings_overrides_for_version(
|
||||
project_name, version
|
||||
):
|
||||
return (
|
||||
_SETTINGS_HANDLER
|
||||
.get_project_settings_overrides_for_version(project_name, version)
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_available_studio_system_settings_overrides_versions(sorted=None):
|
||||
return (
|
||||
_SETTINGS_HANDLER
|
||||
.get_available_studio_system_settings_overrides_versions(
|
||||
sorted=sorted
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_available_studio_project_anatomy_overrides_versions(sorted=None):
|
||||
return (
|
||||
_SETTINGS_HANDLER
|
||||
.get_available_studio_project_anatomy_overrides_versions(
|
||||
sorted=sorted
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_available_studio_project_settings_overrides_versions(sorted=None):
|
||||
return (
|
||||
_SETTINGS_HANDLER
|
||||
.get_available_studio_project_settings_overrides_versions(
|
||||
sorted=sorted
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_available_project_settings_overrides_versions(
|
||||
project_name, sorted=None
|
||||
):
|
||||
return (
|
||||
_SETTINGS_HANDLER
|
||||
.get_available_project_settings_overrides_versions(
|
||||
project_name, sorted=sorted
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def find_closest_version_for_projects(project_names):
|
||||
return (
|
||||
_SETTINGS_HANDLER
|
||||
.find_closest_version_for_projects(project_names)
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def clear_studio_system_settings_overrides_for_version(version):
|
||||
return (
|
||||
_SETTINGS_HANDLER
|
||||
.clear_studio_system_settings_overrides_for_version(version)
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def clear_studio_project_settings_overrides_for_version(version):
|
||||
return (
|
||||
_SETTINGS_HANDLER
|
||||
.clear_studio_project_settings_overrides_for_version(version)
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def clear_studio_project_anatomy_overrides_for_version(version):
|
||||
return (
|
||||
_SETTINGS_HANDLER
|
||||
.clear_studio_project_anatomy_overrides_for_version(version)
|
||||
)
|
||||
|
||||
|
||||
@require_handler
|
||||
def clear_project_settings_overrides_for_version(
|
||||
version, project_name
|
||||
):
|
||||
return _SETTINGS_HANDLER.clear_project_settings_overrides_for_version(
|
||||
version, project_name
|
||||
)
|
||||
|
||||
|
||||
@require_local_handler
|
||||
def save_local_settings(data):
|
||||
return _LOCAL_SETTINGS_HANDLER.save_local_settings(data)
|
||||
|
|
@ -580,11 +705,26 @@ def apply_local_settings_on_system_settings(system_settings, local_settings):
|
|||
return
|
||||
|
||||
current_platform = platform.system().lower()
|
||||
apps_settings = system_settings["applications"]
|
||||
additional_apps = apps_settings["additional_apps"]
|
||||
for app_group_name, value in local_settings["applications"].items():
|
||||
if not value or app_group_name not in system_settings["applications"]:
|
||||
if not value:
|
||||
continue
|
||||
|
||||
variants = system_settings["applications"][app_group_name]["variants"]
|
||||
if (
|
||||
app_group_name not in apps_settings
|
||||
and app_group_name not in additional_apps
|
||||
):
|
||||
continue
|
||||
|
||||
if app_group_name in apps_settings:
|
||||
variants = apps_settings[app_group_name]["variants"]
|
||||
|
||||
else:
|
||||
variants = (
|
||||
apps_settings["additional_apps"][app_group_name]["variants"]
|
||||
)
|
||||
|
||||
for app_name, app_value in value.items():
|
||||
if (
|
||||
not app_value
|
||||
|
|
|
|||
|
|
@ -118,7 +118,10 @@
|
|||
"image-btn-hover": "#189aea",
|
||||
"image-btn-disabled": "#bfccd6",
|
||||
"version-exists": "#458056",
|
||||
"version-not-found": "#ffc671"
|
||||
"version-not-found": "#ffc671",
|
||||
|
||||
"source-version": "#D3D8DE",
|
||||
"source-version-outdated": "#ffc671"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1117,6 +1117,20 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
|
|||
#ExpandLabel[state="invalid"]:hover, #SettingsLabel[state="invalid"]:hover {
|
||||
color: {color:settings:invalid-dark};
|
||||
}
|
||||
#SettingsOutdatedSourceVersion {
|
||||
color: {color:settings:source-version-outdated};
|
||||
}
|
||||
#SourceVersionLabel {
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
}
|
||||
|
||||
#SourceVersionLabel[state="same"] {
|
||||
color: {color:settings:source-version};
|
||||
}
|
||||
#SourceVersionLabel[state="different"] {
|
||||
color: {color:settings:source-version-outdated};
|
||||
}
|
||||
|
||||
/* TODO Replace these with explicit widget types if possible */
|
||||
#SettingsMainWidget QWidget[input-state="modified"] {
|
||||
|
|
@ -1132,8 +1146,8 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
|
|||
border-color: {color:settings:invalid-dark};
|
||||
}
|
||||
|
||||
#GroupWidget {
|
||||
border-bottom: 1px solid #21252B;
|
||||
#SettingsFooter {
|
||||
border-top: 1px solid #21252B;
|
||||
}
|
||||
|
||||
#ProjectListWidget QLabel {
|
||||
|
|
@ -1141,6 +1155,10 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
#ProjectListContentWidget {
|
||||
background: {color:bg-view};
|
||||
}
|
||||
|
||||
#MultiSelectionComboBox {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,12 @@ from .constants import (
|
|||
from .actions import ApplicationAction
|
||||
from Qt import QtCore, QtGui
|
||||
from avalon.vendor import qtawesome
|
||||
from avalon import style, api
|
||||
from openpype.lib import ApplicationManager, JSONSettingRegistry
|
||||
from avalon import api
|
||||
from openpype.lib import JSONSettingRegistry
|
||||
from openpype.lib.applications import (
|
||||
CUSTOM_LAUNCH_APP_GROUPS,
|
||||
ApplicationManager
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -72,6 +76,9 @@ class ActionModel(QtGui.QStandardItemModel):
|
|||
if not app or not app.enabled:
|
||||
continue
|
||||
|
||||
if app.group.name in CUSTOM_LAUNCH_APP_GROUPS:
|
||||
continue
|
||||
|
||||
# Get from app definition, if not there from app in project
|
||||
action = type(
|
||||
"app_{}".format(app_name),
|
||||
|
|
@ -313,7 +320,7 @@ class ActionModel(QtGui.QStandardItemModel):
|
|||
action = action[0]
|
||||
|
||||
compare_data = {}
|
||||
if action:
|
||||
if action and action.label:
|
||||
compare_data = {
|
||||
"app_label": action.label.lower(),
|
||||
"project_name": self.dbcon.Session["AVALON_PROJECT"],
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from avalon.vendor import qtawesome
|
|||
|
||||
from .delegates import ActionDelegate
|
||||
from . import lib
|
||||
from .actions import ApplicationAction
|
||||
from .models import ActionModel
|
||||
from openpype.tools.flickcharm import FlickCharm
|
||||
from .constants import (
|
||||
|
|
@ -239,10 +240,12 @@ class ActionBar(QtWidgets.QWidget):
|
|||
is_variant_group = index.data(VARIANT_GROUP_ROLE)
|
||||
if not is_group and not is_variant_group:
|
||||
action = index.data(ACTION_ROLE)
|
||||
if index.data(FORCE_NOT_OPEN_WORKFILE_ROLE):
|
||||
action.data["start_last_workfile"] = False
|
||||
else:
|
||||
action.data.pop("start_last_workfile", None)
|
||||
# Change data of application action
|
||||
if issubclass(action, ApplicationAction):
|
||||
if index.data(FORCE_NOT_OPEN_WORKFILE_ROLE):
|
||||
action.data["start_last_workfile"] = False
|
||||
else:
|
||||
action.data.pop("start_last_workfile", None)
|
||||
self._start_animation(index)
|
||||
self.action_clicked.emit(action)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ from Qt import QtWidgets, QtCore
|
|||
|
||||
from .widgets import (
|
||||
NameTextEdit,
|
||||
FilterComboBox
|
||||
FilterComboBox,
|
||||
SpinBoxScrollFixed,
|
||||
DoubleSpinBoxScrollFixed
|
||||
)
|
||||
from .multiselection_combobox import MultiSelectionComboBox
|
||||
|
||||
|
|
@ -89,9 +91,9 @@ class NumberDelegate(QtWidgets.QStyledItemDelegate):
|
|||
|
||||
def createEditor(self, parent, option, index):
|
||||
if self.decimals > 0:
|
||||
editor = QtWidgets.QDoubleSpinBox(parent)
|
||||
editor = DoubleSpinBoxScrollFixed(parent)
|
||||
else:
|
||||
editor = QtWidgets.QSpinBox(parent)
|
||||
editor = SpinBoxScrollFixed(parent)
|
||||
|
||||
editor.setObjectName("NumberEditor")
|
||||
# Set min/max
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import os
|
||||
from Qt import QtCore, QtGui
|
||||
|
||||
from openpype.style import get_objected_colors
|
||||
from avalon.vendor import qtawesome
|
||||
from openpype.tools.utils import paint_image_with_color
|
||||
|
||||
|
||||
class ResourceCache:
|
||||
|
|
@ -91,17 +91,6 @@ class ResourceCache:
|
|||
icon.addPixmap(disabled_pix, QtGui.QIcon.Disabled, QtGui.QIcon.Off)
|
||||
return icon
|
||||
|
||||
@classmethod
|
||||
def get_warning_pixmap(cls):
|
||||
src_image = get_warning_image()
|
||||
colors = get_objected_colors()
|
||||
color_value = colors["delete-btn-bg"]
|
||||
|
||||
return paint_image_with_color(
|
||||
src_image,
|
||||
color_value.get_qcolor()
|
||||
)
|
||||
|
||||
|
||||
def get_remove_image():
|
||||
image_path = os.path.join(
|
||||
|
|
@ -110,36 +99,3 @@ def get_remove_image():
|
|||
"bin.png"
|
||||
)
|
||||
return QtGui.QImage(image_path)
|
||||
|
||||
|
||||
def get_warning_image():
|
||||
image_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"images",
|
||||
"warning.png"
|
||||
)
|
||||
return QtGui.QImage(image_path)
|
||||
|
||||
|
||||
def paint_image_with_color(image, color):
|
||||
"""TODO: This function should be imported from utils.
|
||||
|
||||
At the moment of creation is not available yet.
|
||||
"""
|
||||
width = image.width()
|
||||
height = image.height()
|
||||
|
||||
alpha_mask = image.createAlphaMask()
|
||||
alpha_region = QtGui.QRegion(QtGui.QBitmap.fromImage(alpha_mask))
|
||||
|
||||
pixmap = QtGui.QPixmap(width, height)
|
||||
pixmap.fill(QtCore.Qt.transparent)
|
||||
|
||||
painter = QtGui.QPainter(pixmap)
|
||||
painter.setClipRegion(alpha_region)
|
||||
painter.setPen(QtCore.Qt.NoPen)
|
||||
painter.setBrush(color)
|
||||
painter.drawRect(QtCore.QRect(0, 0, width, height))
|
||||
painter.end()
|
||||
|
||||
return pixmap
|
||||
|
|
|
|||
|
|
@ -4,14 +4,16 @@ from .constants import (
|
|||
NAME_ALLOWED_SYMBOLS,
|
||||
NAME_REGEX
|
||||
)
|
||||
from .style import ResourceCache
|
||||
from openpype.lib import (
|
||||
create_project,
|
||||
PROJECT_NAME_ALLOWED_SYMBOLS,
|
||||
PROJECT_NAME_REGEX
|
||||
)
|
||||
from openpype.style import load_stylesheet
|
||||
from openpype.tools.utils import PlaceholderLineEdit
|
||||
from openpype.tools.utils import (
|
||||
PlaceholderLineEdit,
|
||||
get_warning_pixmap
|
||||
)
|
||||
from avalon.api import AvalonMongoDB
|
||||
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
|
@ -338,7 +340,7 @@ class ConfirmProjectDeletion(QtWidgets.QDialog):
|
|||
|
||||
top_widget = QtWidgets.QWidget(self)
|
||||
|
||||
warning_pixmap = ResourceCache.get_warning_pixmap()
|
||||
warning_pixmap = get_warning_pixmap()
|
||||
warning_icon_label = PixmapLabel(warning_pixmap, top_widget)
|
||||
|
||||
message_label = QtWidgets.QLabel(top_widget)
|
||||
|
|
@ -429,3 +431,29 @@ class ConfirmProjectDeletion(QtWidgets.QDialog):
|
|||
def _on_confirm_text_change(self):
|
||||
enabled = self._confirm_input.text() == self._project_name
|
||||
self._confirm_btn.setEnabled(enabled)
|
||||
|
||||
|
||||
class SpinBoxScrollFixed(QtWidgets.QSpinBox):
|
||||
"""QSpinBox which only allow edits change with scroll wheel when active"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SpinBoxScrollFixed, self).__init__(*args, **kwargs)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
def wheelEvent(self, event):
|
||||
if not self.hasFocus():
|
||||
event.ignore()
|
||||
else:
|
||||
super(SpinBoxScrollFixed, self).wheelEvent(event)
|
||||
|
||||
|
||||
class DoubleSpinBoxScrollFixed(QtWidgets.QDoubleSpinBox):
|
||||
"""QDoubleSpinBox which only allow edits with scroll wheel when active"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DoubleSpinBoxScrollFixed, self).__init__(*args, **kwargs)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
def wheelEvent(self, event):
|
||||
if not self.hasFocus():
|
||||
event.ignore()
|
||||
else:
|
||||
super(DoubleSpinBoxScrollFixed, self).wheelEvent(event)
|
||||
|
|
|
|||
|
|
@ -180,7 +180,16 @@ class LocalApplicationsWidgets(QtWidgets.QWidget):
|
|||
self.content_layout.removeItem(item)
|
||||
self.widgets_by_group_name.clear()
|
||||
|
||||
app_items = {}
|
||||
for key, entity in self.system_settings_entity["applications"].items():
|
||||
if key != "additional_apps":
|
||||
app_items[key] = entity
|
||||
continue
|
||||
|
||||
for _key, _entity in entity.items():
|
||||
app_items[_key] = _entity
|
||||
|
||||
for key, entity in app_items.items():
|
||||
# Filter not enabled app groups
|
||||
if not entity["enabled"].value:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ import sys
|
|||
import traceback
|
||||
import contextlib
|
||||
from enum import Enum
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
from Qt import QtWidgets, QtCore
|
||||
|
||||
from openpype.lib import get_openpype_version
|
||||
from openpype.tools.utils import set_style_property
|
||||
from openpype.settings.entities import (
|
||||
SystemSettings,
|
||||
ProjectSettings,
|
||||
|
|
@ -34,7 +36,10 @@ from openpype.settings.entities.op_version_entity import (
|
|||
)
|
||||
|
||||
from openpype.settings import SaveWarningExc
|
||||
from .widgets import ProjectListWidget
|
||||
from .widgets import (
|
||||
ProjectListWidget,
|
||||
VersionAction
|
||||
)
|
||||
from .breadcrumbs_widget import (
|
||||
BreadcrumbsAddressBar,
|
||||
SystemSettingsBreadcrumbs,
|
||||
|
|
@ -88,6 +93,20 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
restart_required_trigger = QtCore.Signal()
|
||||
full_path_requested = QtCore.Signal(str, str)
|
||||
|
||||
require_restart_label_text = (
|
||||
"Your changes require restart of"
|
||||
" all running OpenPype processes to take affect."
|
||||
)
|
||||
outdated_version_label_text = (
|
||||
"Your settings are loaded from an older version."
|
||||
)
|
||||
source_version_tooltip = "Using settings of current OpenPype version"
|
||||
source_version_tooltip_outdated = (
|
||||
"Please check that all settings are still correct (blue colour\n"
|
||||
"indicates potential changes in the new version) and save your\n"
|
||||
"settings to update them to you current running OpenPype version."
|
||||
)
|
||||
|
||||
def __init__(self, user_role, parent=None):
|
||||
super(SettingsCategoryWidget, self).__init__(parent)
|
||||
|
||||
|
|
@ -98,6 +117,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
self._state = CategoryState.Idle
|
||||
|
||||
self._hide_studio_overrides = False
|
||||
self._updating_root = False
|
||||
self._use_version = None
|
||||
self._current_version = get_openpype_version()
|
||||
|
||||
self.ignore_input_changes = IgnoreInputChangesObj(self)
|
||||
|
||||
self.keys = []
|
||||
|
|
@ -183,77 +206,126 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
def initialize_attributes(self):
|
||||
return
|
||||
|
||||
@property
|
||||
def is_modifying_defaults(self):
|
||||
if self.modify_defaults_checkbox is None:
|
||||
return False
|
||||
return self.modify_defaults_checkbox.isChecked()
|
||||
|
||||
def create_ui(self):
|
||||
self.modify_defaults_checkbox = None
|
||||
|
||||
scroll_widget = QtWidgets.QScrollArea(self)
|
||||
scroll_widget.setObjectName("GroupWidget")
|
||||
content_widget = QtWidgets.QWidget(scroll_widget)
|
||||
conf_wrapper_widget = QtWidgets.QWidget(self)
|
||||
configurations_widget = QtWidgets.QWidget(conf_wrapper_widget)
|
||||
|
||||
breadcrumbs_label = QtWidgets.QLabel("Path:", content_widget)
|
||||
breadcrumbs_widget = BreadcrumbsAddressBar(content_widget)
|
||||
# Breadcrumbs/Path widget
|
||||
breadcrumbs_widget = QtWidgets.QWidget(self)
|
||||
breadcrumbs_label = QtWidgets.QLabel("Path:", breadcrumbs_widget)
|
||||
breadcrumbs_bar = BreadcrumbsAddressBar(breadcrumbs_widget)
|
||||
|
||||
breadcrumbs_layout = QtWidgets.QHBoxLayout()
|
||||
refresh_icon = qtawesome.icon("fa.refresh", color="white")
|
||||
refresh_btn = QtWidgets.QPushButton(breadcrumbs_widget)
|
||||
refresh_btn.setIcon(refresh_icon)
|
||||
|
||||
breadcrumbs_layout = QtWidgets.QHBoxLayout(breadcrumbs_widget)
|
||||
breadcrumbs_layout.setContentsMargins(5, 5, 5, 5)
|
||||
breadcrumbs_layout.setSpacing(5)
|
||||
breadcrumbs_layout.addWidget(breadcrumbs_label)
|
||||
breadcrumbs_layout.addWidget(breadcrumbs_widget)
|
||||
breadcrumbs_layout.addWidget(breadcrumbs_label, 0)
|
||||
breadcrumbs_layout.addWidget(breadcrumbs_bar, 1)
|
||||
breadcrumbs_layout.addWidget(refresh_btn, 0)
|
||||
|
||||
# Widgets representing settings entities
|
||||
scroll_widget = QtWidgets.QScrollArea(configurations_widget)
|
||||
content_widget = QtWidgets.QWidget(scroll_widget)
|
||||
scroll_widget.setWidgetResizable(True)
|
||||
scroll_widget.setWidget(content_widget)
|
||||
|
||||
content_layout = QtWidgets.QVBoxLayout(content_widget)
|
||||
content_layout.setContentsMargins(3, 3, 3, 3)
|
||||
content_layout.setSpacing(5)
|
||||
content_layout.setAlignment(QtCore.Qt.AlignTop)
|
||||
|
||||
scroll_widget.setWidgetResizable(True)
|
||||
scroll_widget.setWidget(content_widget)
|
||||
# Footer widget
|
||||
footer_widget = QtWidgets.QWidget(self)
|
||||
footer_widget.setObjectName("SettingsFooter")
|
||||
|
||||
refresh_icon = qtawesome.icon("fa.refresh", color="white")
|
||||
refresh_btn = QtWidgets.QPushButton(self)
|
||||
refresh_btn.setIcon(refresh_icon)
|
||||
# Info labels
|
||||
# TODO dynamic labels
|
||||
labels_alignment = QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
|
||||
empty_label = QtWidgets.QLabel(footer_widget)
|
||||
|
||||
footer_layout = QtWidgets.QHBoxLayout()
|
||||
outdated_version_label = QtWidgets.QLabel(
|
||||
self.outdated_version_label_text, footer_widget
|
||||
)
|
||||
outdated_version_label.setToolTip(self.source_version_tooltip_outdated)
|
||||
outdated_version_label.setAlignment(labels_alignment)
|
||||
outdated_version_label.setVisible(False)
|
||||
outdated_version_label.setObjectName("SettingsOutdatedSourceVersion")
|
||||
|
||||
require_restart_label = QtWidgets.QLabel(
|
||||
self.require_restart_label_text, footer_widget
|
||||
)
|
||||
require_restart_label.setAlignment(labels_alignment)
|
||||
require_restart_label.setVisible(False)
|
||||
|
||||
# Label showing source version of loaded settings
|
||||
source_version_label = QtWidgets.QLabel("", footer_widget)
|
||||
source_version_label.setObjectName("SourceVersionLabel")
|
||||
set_style_property(source_version_label, "state", "")
|
||||
source_version_label.setToolTip(self.source_version_tooltip)
|
||||
|
||||
save_btn = QtWidgets.QPushButton("Save", footer_widget)
|
||||
|
||||
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
|
||||
footer_layout.setContentsMargins(5, 5, 5, 5)
|
||||
if self.user_role == "developer":
|
||||
self._add_developer_ui(footer_layout)
|
||||
self._add_developer_ui(footer_layout, footer_widget)
|
||||
|
||||
save_btn = QtWidgets.QPushButton("Save", self)
|
||||
require_restart_label = QtWidgets.QLabel(self)
|
||||
require_restart_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
|
||||
footer_layout.addWidget(refresh_btn, 0)
|
||||
footer_layout.addWidget(empty_label, 1)
|
||||
footer_layout.addWidget(outdated_version_label, 1)
|
||||
footer_layout.addWidget(require_restart_label, 1)
|
||||
footer_layout.addWidget(source_version_label, 0)
|
||||
footer_layout.addWidget(save_btn, 0)
|
||||
|
||||
configurations_layout = QtWidgets.QVBoxLayout()
|
||||
configurations_layout = QtWidgets.QVBoxLayout(configurations_widget)
|
||||
configurations_layout.setContentsMargins(0, 0, 0, 0)
|
||||
configurations_layout.setSpacing(0)
|
||||
|
||||
configurations_layout.addWidget(scroll_widget, 1)
|
||||
configurations_layout.addLayout(footer_layout, 0)
|
||||
|
||||
conf_wrapper_layout = QtWidgets.QHBoxLayout()
|
||||
conf_wrapper_layout = QtWidgets.QHBoxLayout(conf_wrapper_widget)
|
||||
conf_wrapper_layout.setContentsMargins(0, 0, 0, 0)
|
||||
conf_wrapper_layout.setSpacing(0)
|
||||
conf_wrapper_layout.addLayout(configurations_layout, 1)
|
||||
conf_wrapper_layout.addWidget(configurations_widget, 1)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.setSpacing(0)
|
||||
main_layout.addLayout(breadcrumbs_layout, 0)
|
||||
main_layout.addLayout(conf_wrapper_layout, 1)
|
||||
main_layout.addWidget(breadcrumbs_widget, 0)
|
||||
main_layout.addWidget(conf_wrapper_widget, 1)
|
||||
main_layout.addWidget(footer_widget, 0)
|
||||
|
||||
save_btn.clicked.connect(self._save)
|
||||
refresh_btn.clicked.connect(self._on_refresh)
|
||||
breadcrumbs_widget.path_edited.connect(self._on_path_edit)
|
||||
breadcrumbs_bar.path_edited.connect(self._on_path_edit)
|
||||
|
||||
self._require_restart_label = require_restart_label
|
||||
self._outdated_version_label = outdated_version_label
|
||||
self._empty_label = empty_label
|
||||
|
||||
self._is_loaded_version_outdated = False
|
||||
|
||||
self.save_btn = save_btn
|
||||
self.refresh_btn = refresh_btn
|
||||
self.require_restart_label = require_restart_label
|
||||
self._source_version_label = source_version_label
|
||||
|
||||
self.scroll_widget = scroll_widget
|
||||
self.content_layout = content_layout
|
||||
self.content_widget = content_widget
|
||||
self.breadcrumbs_widget = breadcrumbs_widget
|
||||
self.breadcrumbs_bar = breadcrumbs_bar
|
||||
|
||||
self.breadcrumbs_model = None
|
||||
self.refresh_btn = refresh_btn
|
||||
|
||||
self.conf_wrapper_layout = conf_wrapper_layout
|
||||
self.main_layout = main_layout
|
||||
|
||||
|
|
@ -308,21 +380,17 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
pass
|
||||
|
||||
def set_path(self, path):
|
||||
self.breadcrumbs_widget.set_path(path)
|
||||
self.breadcrumbs_bar.set_path(path)
|
||||
|
||||
def _add_developer_ui(self, footer_layout):
|
||||
modify_defaults_widget = QtWidgets.QWidget()
|
||||
modify_defaults_checkbox = QtWidgets.QCheckBox(modify_defaults_widget)
|
||||
def _add_developer_ui(self, footer_layout, footer_widget):
|
||||
modify_defaults_checkbox = QtWidgets.QCheckBox(footer_widget)
|
||||
modify_defaults_checkbox.setChecked(self._hide_studio_overrides)
|
||||
label_widget = QtWidgets.QLabel(
|
||||
"Modify defaults", modify_defaults_widget
|
||||
"Modify defaults", footer_widget
|
||||
)
|
||||
|
||||
modify_defaults_layout = QtWidgets.QHBoxLayout(modify_defaults_widget)
|
||||
modify_defaults_layout.addWidget(label_widget)
|
||||
modify_defaults_layout.addWidget(modify_defaults_checkbox)
|
||||
|
||||
footer_layout.addWidget(modify_defaults_widget, 0)
|
||||
footer_layout.addWidget(label_widget, 0)
|
||||
footer_layout.addWidget(modify_defaults_checkbox, 0)
|
||||
|
||||
modify_defaults_checkbox.stateChanged.connect(
|
||||
self._on_modify_defaults
|
||||
|
|
@ -361,6 +429,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
|
||||
try:
|
||||
self.entity.save()
|
||||
self._use_version = None
|
||||
|
||||
# NOTE There are relations to previous entities and C++ callbacks
|
||||
# so it is easier to just use new entity and recreate UI but
|
||||
|
|
@ -420,13 +489,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
return
|
||||
|
||||
def _on_require_restart_change(self):
|
||||
value = ""
|
||||
if self.entity.require_restart:
|
||||
value = (
|
||||
"Your changes require restart of"
|
||||
" all running OpenPype processes to take affect."
|
||||
)
|
||||
self.require_restart_label.setText(value)
|
||||
self._update_labels_visibility()
|
||||
|
||||
def reset(self):
|
||||
self.set_state(CategoryState.Working)
|
||||
|
|
@ -444,6 +507,8 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
widget.deleteLater()
|
||||
|
||||
dialog = None
|
||||
self._updating_root = True
|
||||
source_version = ""
|
||||
try:
|
||||
self._create_root_entity()
|
||||
|
||||
|
|
@ -459,6 +524,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
input_field.set_entity_value()
|
||||
|
||||
self.ignore_input_changes.set_ignore(False)
|
||||
source_version = self.entity.source_version
|
||||
|
||||
except DefaultsNotDefined:
|
||||
dialog = QtWidgets.QMessageBox(self)
|
||||
|
|
@ -502,6 +568,27 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
spacer, layout.rowCount(), 0, 1, layout.columnCount()
|
||||
)
|
||||
|
||||
self._updating_root = False
|
||||
|
||||
# Update source version label
|
||||
state_value = ""
|
||||
tooltip = ""
|
||||
outdated = False
|
||||
if source_version:
|
||||
if source_version != self._current_version:
|
||||
state_value = "different"
|
||||
tooltip = self.source_version_tooltip_outdated
|
||||
outdated = True
|
||||
else:
|
||||
state_value = "same"
|
||||
tooltip = self.source_version_tooltip
|
||||
|
||||
self._is_loaded_version_outdated = outdated
|
||||
self._source_version_label.setText(source_version)
|
||||
self._source_version_label.setToolTip(tooltip)
|
||||
set_style_property(self._source_version_label, "state", state_value)
|
||||
self._update_labels_visibility()
|
||||
|
||||
self.set_state(CategoryState.Idle)
|
||||
|
||||
if dialog:
|
||||
|
|
@ -510,6 +597,36 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
else:
|
||||
self._on_reset_success()
|
||||
|
||||
def _on_source_version_change(self, version):
|
||||
if self._updating_root:
|
||||
return
|
||||
|
||||
if version == self._current_version:
|
||||
version = None
|
||||
|
||||
self._use_version = version
|
||||
QtCore.QTimer.singleShot(20, self.reset)
|
||||
|
||||
def add_context_actions(self, menu):
|
||||
if not self.entity or self.is_modifying_defaults:
|
||||
return
|
||||
|
||||
versions = self.entity.get_available_studio_versions(sorted=True)
|
||||
if not versions:
|
||||
return
|
||||
|
||||
submenu = QtWidgets.QMenu("Use settings from version", menu)
|
||||
for version in reversed(versions):
|
||||
action = VersionAction(version, submenu)
|
||||
action.version_triggered.connect(
|
||||
self._on_context_version_trigger
|
||||
)
|
||||
submenu.addAction(action)
|
||||
menu.addMenu(submenu)
|
||||
|
||||
def _on_context_version_trigger(self, version):
|
||||
self._on_source_version_change(version)
|
||||
|
||||
def _on_reset_crash(self):
|
||||
self.save_btn.setEnabled(False)
|
||||
|
||||
|
|
@ -521,10 +638,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
self.save_btn.setEnabled(True)
|
||||
|
||||
if self.breadcrumbs_model is not None:
|
||||
path = self.breadcrumbs_widget.path()
|
||||
self.breadcrumbs_widget.set_path("")
|
||||
path = self.breadcrumbs_bar.path()
|
||||
self.breadcrumbs_bar.set_path("")
|
||||
self.breadcrumbs_model.set_entity(self.entity)
|
||||
self.breadcrumbs_widget.change_path(path)
|
||||
self.breadcrumbs_bar.change_path(path)
|
||||
|
||||
def add_children_gui(self):
|
||||
for child_obj in self.entity.children:
|
||||
|
|
@ -565,10 +682,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
|
||||
def _save(self):
|
||||
# Don't trigger restart if defaults are modified
|
||||
if (
|
||||
self.modify_defaults_checkbox
|
||||
and self.modify_defaults_checkbox.isChecked()
|
||||
):
|
||||
if self.is_modifying_defaults:
|
||||
require_restart = False
|
||||
else:
|
||||
require_restart = self.entity.require_restart
|
||||
|
|
@ -584,7 +698,29 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
|
||||
if require_restart:
|
||||
self.restart_required_trigger.emit()
|
||||
self.require_restart_label.setText("")
|
||||
|
||||
def _update_labels_visibility(self):
|
||||
visible_label = None
|
||||
labels = {
|
||||
self._empty_label,
|
||||
self._outdated_version_label,
|
||||
self._require_restart_label,
|
||||
}
|
||||
if self.entity.require_restart:
|
||||
visible_label = self._require_restart_label
|
||||
elif self._is_loaded_version_outdated:
|
||||
visible_label = self._outdated_version_label
|
||||
else:
|
||||
visible_label = self._empty_label
|
||||
|
||||
if visible_label.isVisible():
|
||||
return
|
||||
|
||||
for label in labels:
|
||||
if label is visible_label:
|
||||
visible_label.setVisible(True)
|
||||
else:
|
||||
label.setVisible(False)
|
||||
|
||||
def _on_refresh(self):
|
||||
self.reset()
|
||||
|
|
@ -594,25 +730,29 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
|
||||
|
||||
class SystemWidget(SettingsCategoryWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._actions = []
|
||||
super(SystemWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
def contain_category_key(self, category):
|
||||
if category == "system_settings":
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_category_path(self, category, path):
|
||||
self.breadcrumbs_widget.change_path(path)
|
||||
self.breadcrumbs_bar.change_path(path)
|
||||
|
||||
def _create_root_entity(self):
|
||||
self.entity = SystemSettings(set_studio_state=False)
|
||||
self.entity.on_change_callbacks.append(self._on_entity_change)
|
||||
entity = SystemSettings(
|
||||
set_studio_state=False, source_version=self._use_version
|
||||
)
|
||||
entity.on_change_callbacks.append(self._on_entity_change)
|
||||
self.entity = entity
|
||||
try:
|
||||
if (
|
||||
self.modify_defaults_checkbox
|
||||
and self.modify_defaults_checkbox.isChecked()
|
||||
):
|
||||
self.entity.set_defaults_state()
|
||||
if self.is_modifying_defaults:
|
||||
entity.set_defaults_state()
|
||||
else:
|
||||
self.entity.set_studio_state()
|
||||
entity.set_studio_state()
|
||||
|
||||
if self.modify_defaults_checkbox:
|
||||
self.modify_defaults_checkbox.setEnabled(True)
|
||||
|
|
@ -620,16 +760,16 @@ class SystemWidget(SettingsCategoryWidget):
|
|||
if not self.modify_defaults_checkbox:
|
||||
raise
|
||||
|
||||
self.entity.set_defaults_state()
|
||||
entity.set_defaults_state()
|
||||
self.modify_defaults_checkbox.setChecked(True)
|
||||
self.modify_defaults_checkbox.setEnabled(False)
|
||||
|
||||
def ui_tweaks(self):
|
||||
self.breadcrumbs_model = SystemSettingsBreadcrumbs()
|
||||
self.breadcrumbs_widget.set_model(self.breadcrumbs_model)
|
||||
self.breadcrumbs_bar.set_model(self.breadcrumbs_model)
|
||||
|
||||
def _on_modify_defaults(self):
|
||||
if self.modify_defaults_checkbox.isChecked():
|
||||
if self.is_modifying_defaults:
|
||||
if not self.entity.is_in_defaults_state():
|
||||
self.reset()
|
||||
else:
|
||||
|
|
@ -638,6 +778,9 @@ class SystemWidget(SettingsCategoryWidget):
|
|||
|
||||
|
||||
class ProjectWidget(SettingsCategoryWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ProjectWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
def contain_category_key(self, category):
|
||||
if category in ("project_settings", "project_anatomy"):
|
||||
return True
|
||||
|
|
@ -651,28 +794,28 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
else:
|
||||
path = category
|
||||
|
||||
self.breadcrumbs_widget.change_path(path)
|
||||
self.breadcrumbs_bar.change_path(path)
|
||||
|
||||
def initialize_attributes(self):
|
||||
self.project_name = None
|
||||
|
||||
def ui_tweaks(self):
|
||||
self.breadcrumbs_model = ProjectSettingsBreadcrumbs()
|
||||
self.breadcrumbs_widget.set_model(self.breadcrumbs_model)
|
||||
self.breadcrumbs_bar.set_model(self.breadcrumbs_model)
|
||||
|
||||
project_list_widget = ProjectListWidget(self)
|
||||
|
||||
self.conf_wrapper_layout.insertWidget(0, project_list_widget, 0)
|
||||
|
||||
project_list_widget.project_changed.connect(self._on_project_change)
|
||||
project_list_widget.version_change_requested.connect(
|
||||
self._on_source_version_change
|
||||
)
|
||||
|
||||
self.project_list_widget = project_list_widget
|
||||
|
||||
def get_project_names(self):
|
||||
if (
|
||||
self.modify_defaults_checkbox
|
||||
and self.modify_defaults_checkbox.isChecked()
|
||||
):
|
||||
if self.is_modifying_defaults:
|
||||
return []
|
||||
return self.project_list_widget.get_project_names()
|
||||
|
||||
|
|
@ -684,6 +827,10 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
if self is saved_tab_widget:
|
||||
return
|
||||
|
||||
def _on_context_version_trigger(self, version):
|
||||
self.project_list_widget.select_project(None)
|
||||
super(ProjectWidget, self)._on_context_version_trigger(version)
|
||||
|
||||
def _on_reset_start(self):
|
||||
self.project_list_widget.refresh()
|
||||
|
||||
|
|
@ -696,32 +843,29 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
super(ProjectWidget, self)._on_reset_success()
|
||||
|
||||
def _set_enabled_project_list(self, enabled):
|
||||
if (
|
||||
enabled
|
||||
and self.modify_defaults_checkbox
|
||||
and self.modify_defaults_checkbox.isChecked()
|
||||
):
|
||||
if enabled and self.is_modifying_defaults:
|
||||
enabled = False
|
||||
if self.project_list_widget.isEnabled() != enabled:
|
||||
self.project_list_widget.setEnabled(enabled)
|
||||
|
||||
def _create_root_entity(self):
|
||||
self.entity = ProjectSettings(change_state=False)
|
||||
self.entity.on_change_callbacks.append(self._on_entity_change)
|
||||
entity = ProjectSettings(
|
||||
change_state=False, source_version=self._use_version
|
||||
)
|
||||
entity.on_change_callbacks.append(self._on_entity_change)
|
||||
self.project_list_widget.set_entity(entity)
|
||||
self.entity = entity
|
||||
try:
|
||||
if (
|
||||
self.modify_defaults_checkbox
|
||||
and self.modify_defaults_checkbox.isChecked()
|
||||
):
|
||||
if self.is_modifying_defaults:
|
||||
self.entity.set_defaults_state()
|
||||
|
||||
elif self.project_name is None:
|
||||
self.entity.set_studio_state()
|
||||
|
||||
elif self.project_name == self.entity.project_name:
|
||||
self.entity.set_project_state()
|
||||
else:
|
||||
self.entity.change_project(self.project_name)
|
||||
self.entity.change_project(
|
||||
self.project_name, self._use_version
|
||||
)
|
||||
|
||||
if self.modify_defaults_checkbox:
|
||||
self.modify_defaults_checkbox.setEnabled(True)
|
||||
|
|
@ -754,7 +898,7 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
self.set_state(CategoryState.Idle)
|
||||
|
||||
def _on_modify_defaults(self):
|
||||
if self.modify_defaults_checkbox.isChecked():
|
||||
if self.is_modifying_defaults:
|
||||
self._set_enabled_project_list(False)
|
||||
if not self.entity.is_in_defaults_state():
|
||||
self.reset()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ DEFAULT_PROJECT_LABEL = "< Default >"
|
|||
PROJECT_NAME_ROLE = QtCore.Qt.UserRole + 1
|
||||
PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 2
|
||||
PROJECT_IS_SELECTED_ROLE = QtCore.Qt.UserRole + 3
|
||||
PROJECT_VERSION_ROLE = QtCore.Qt.UserRole + 4
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
|
@ -12,5 +13,6 @@ __all__ = (
|
|||
|
||||
"PROJECT_NAME_ROLE",
|
||||
"PROJECT_IS_ACTIVE_ROLE",
|
||||
"PROJECT_IS_SELECTED_ROLE"
|
||||
"PROJECT_IS_SELECTED_ROLE",
|
||||
"PROJECT_VERSION_ROLE",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import copy
|
||||
import uuid
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
from avalon.vendor import qtawesome
|
||||
from avalon.mongodb import (
|
||||
|
|
@ -12,8 +13,12 @@ from openpype.tools.utils.widgets import ImageButton
|
|||
from openpype.tools.utils.lib import paint_image_with_color
|
||||
|
||||
from openpype.widgets.nice_checkbox import NiceCheckbox
|
||||
from openpype.tools.utils import PlaceholderLineEdit
|
||||
from openpype.settings.lib import get_system_settings
|
||||
from openpype.tools.utils import (
|
||||
PlaceholderLineEdit,
|
||||
DynamicQThread
|
||||
)
|
||||
from openpype.settings.lib import find_closest_version_for_projects
|
||||
from openpype.lib import get_openpype_version
|
||||
from .images import (
|
||||
get_pixmap,
|
||||
get_image
|
||||
|
|
@ -21,11 +26,40 @@ from .images import (
|
|||
from .constants import (
|
||||
DEFAULT_PROJECT_LABEL,
|
||||
PROJECT_NAME_ROLE,
|
||||
PROJECT_VERSION_ROLE,
|
||||
PROJECT_IS_ACTIVE_ROLE,
|
||||
PROJECT_IS_SELECTED_ROLE
|
||||
)
|
||||
|
||||
|
||||
class SettingsTabWidget(QtWidgets.QTabWidget):
|
||||
context_menu_requested = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SettingsTabWidget, self).__init__(*args, **kwargs)
|
||||
self._right_click_tab_idx = None
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
super(SettingsTabWidget, self).mousePressEvent(event)
|
||||
if event.button() == QtCore.Qt.RightButton:
|
||||
tab_bar = self.tabBar()
|
||||
pos = tab_bar.mapFromGlobal(event.globalPos())
|
||||
tab_idx = tab_bar.tabAt(pos)
|
||||
if tab_idx < 0:
|
||||
tab_idx = None
|
||||
self._right_click_tab_idx = tab_idx
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
super(SettingsTabWidget, self).mouseReleaseEvent(event)
|
||||
if event.button() == QtCore.Qt.RightButton:
|
||||
tab_bar = self.tabBar()
|
||||
pos = tab_bar.mapFromGlobal(event.globalPos())
|
||||
tab_idx = tab_bar.tabAt(pos)
|
||||
if tab_idx == self._right_click_tab_idx:
|
||||
self.context_menu_requested.emit(tab_idx)
|
||||
self._right_click_tab = None
|
||||
|
||||
|
||||
class CompleterFilter(QtCore.QSortFilterProxyModel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CompleterFilter, self).__init__(*args, **kwargs)
|
||||
|
|
@ -603,7 +637,7 @@ class UnsavedChangesDialog(QtWidgets.QDialog):
|
|||
message = "You have unsaved changes. What do you want to do with them?"
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
super(UnsavedChangesDialog, self).__init__(parent)
|
||||
message_label = QtWidgets.QLabel(self.message)
|
||||
|
||||
btns_widget = QtWidgets.QWidget(self)
|
||||
|
|
@ -735,19 +769,65 @@ class SettingsNiceCheckbox(NiceCheckbox):
|
|||
|
||||
|
||||
class ProjectModel(QtGui.QStandardItemModel):
|
||||
_update_versions = QtCore.Signal()
|
||||
|
||||
def __init__(self, only_active, *args, **kwargs):
|
||||
super(ProjectModel, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setColumnCount(2)
|
||||
|
||||
self.dbcon = None
|
||||
|
||||
self._only_active = only_active
|
||||
self._default_item = None
|
||||
self._items_by_name = {}
|
||||
self._versions_by_project = {}
|
||||
|
||||
colors = get_objected_colors()
|
||||
font_color = colors["font"].get_qcolor()
|
||||
font_color.setAlpha(67)
|
||||
self._version_font_color = font_color
|
||||
self._current_version = get_openpype_version()
|
||||
|
||||
self._version_refresh_threads = []
|
||||
self._version_refresh_id = None
|
||||
|
||||
self._update_versions.connect(self._on_update_versions_signal)
|
||||
|
||||
def _on_update_versions_signal(self):
|
||||
for project_name, version in self._versions_by_project.items():
|
||||
if project_name is None:
|
||||
item = self._default_item
|
||||
else:
|
||||
item = self._items_by_name.get(project_name)
|
||||
|
||||
if item and version != self._current_version:
|
||||
item.setData(version, PROJECT_VERSION_ROLE)
|
||||
|
||||
def _fetch_settings_versions(self):
|
||||
"""Used versions per project are loaded in thread to not stuck UI."""
|
||||
version_refresh_id = self._version_refresh_id
|
||||
all_project_names = list(self._items_by_name.keys())
|
||||
all_project_names.append(None)
|
||||
closest_by_project_name = find_closest_version_for_projects(
|
||||
all_project_names
|
||||
)
|
||||
if self._version_refresh_id == version_refresh_id:
|
||||
self._versions_by_project = closest_by_project_name
|
||||
self._update_versions.emit()
|
||||
|
||||
def flags(self, index):
|
||||
if index.column() == 1:
|
||||
index = self.index(index.row(), 0, index.parent())
|
||||
return super(ProjectModel, self).flags(index)
|
||||
|
||||
def set_dbcon(self, dbcon):
|
||||
self.dbcon = dbcon
|
||||
|
||||
def refresh(self):
|
||||
# Change id of versions refresh
|
||||
self._version_refresh_id = uuid.uuid4()
|
||||
|
||||
new_items = []
|
||||
if self._default_item is None:
|
||||
item = QtGui.QStandardItem(DEFAULT_PROJECT_LABEL)
|
||||
|
|
@ -757,6 +837,7 @@ class ProjectModel(QtGui.QStandardItemModel):
|
|||
new_items.append(item)
|
||||
self._default_item = item
|
||||
|
||||
self._default_item.setData("", PROJECT_VERSION_ROLE)
|
||||
project_names = set()
|
||||
if self.dbcon is not None:
|
||||
for project_doc in self.dbcon.projects(
|
||||
|
|
@ -776,6 +857,7 @@ class ProjectModel(QtGui.QStandardItemModel):
|
|||
is_active = project_doc.get("data", {}).get("active", True)
|
||||
item.setData(project_name, PROJECT_NAME_ROLE)
|
||||
item.setData(is_active, PROJECT_IS_ACTIVE_ROLE)
|
||||
item.setData("", PROJECT_VERSION_ROLE)
|
||||
item.setData(False, PROJECT_IS_SELECTED_ROLE)
|
||||
|
||||
if not is_active:
|
||||
|
|
@ -792,15 +874,87 @@ class ProjectModel(QtGui.QStandardItemModel):
|
|||
if new_items:
|
||||
root_item.appendRows(new_items)
|
||||
|
||||
# Fetch versions per project in thread
|
||||
thread = DynamicQThread(self._fetch_settings_versions)
|
||||
self._version_refresh_threads.append(thread)
|
||||
thread.start()
|
||||
|
||||
class ProjectListView(QtWidgets.QListView):
|
||||
# Cleanup done threads
|
||||
for thread in tuple(self._version_refresh_threads):
|
||||
if thread.isFinished():
|
||||
self._version_refresh_threads.remove(thread)
|
||||
|
||||
def data(self, index, role=QtCore.Qt.DisplayRole):
|
||||
if index.column() == 1:
|
||||
if role == QtCore.Qt.TextAlignmentRole:
|
||||
return QtCore.Qt.AlignRight
|
||||
if role == QtCore.Qt.ForegroundRole:
|
||||
return self._version_font_color
|
||||
index = self.index(index.row(), 0, index.parent())
|
||||
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
|
||||
role = PROJECT_VERSION_ROLE
|
||||
|
||||
return super(ProjectModel, self).data(index, role)
|
||||
|
||||
def setData(self, index, value, role=QtCore.Qt.EditRole):
|
||||
if index.column() == 1:
|
||||
index = self.index(index.row(), 0, index.parent())
|
||||
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
|
||||
role = PROJECT_VERSION_ROLE
|
||||
return super(ProjectModel, self).setData(index, value, role)
|
||||
|
||||
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if section == 0:
|
||||
return "Project name"
|
||||
|
||||
elif section == 1:
|
||||
return "Used version"
|
||||
return ""
|
||||
return super(ProjectModel, self).headerData(
|
||||
section, orientation, role
|
||||
)
|
||||
|
||||
|
||||
class VersionAction(QtWidgets.QAction):
|
||||
version_triggered = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, version, *args, **kwargs):
|
||||
super(VersionAction, self).__init__(version, *args, **kwargs)
|
||||
self._version = version
|
||||
self.triggered.connect(self._on_trigger)
|
||||
|
||||
def _on_trigger(self):
|
||||
self.version_triggered.emit(self._version)
|
||||
|
||||
|
||||
class ProjectView(QtWidgets.QTreeView):
|
||||
left_mouse_released_at = QtCore.Signal(QtCore.QModelIndex)
|
||||
right_mouse_released_at = QtCore.Signal(QtCore.QModelIndex)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ProjectView, self).__init__(*args, **kwargs)
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self.setIndentation(0)
|
||||
|
||||
# Do not allow editing
|
||||
self.setEditTriggers(
|
||||
QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers
|
||||
)
|
||||
# Do not automatically handle selection
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
index = self.indexAt(event.pos())
|
||||
self.left_mouse_released_at.emit(index)
|
||||
super(ProjectListView, self).mouseReleaseEvent(event)
|
||||
|
||||
elif event.button() == QtCore.Qt.RightButton:
|
||||
index = self.indexAt(event.pos())
|
||||
self.right_mouse_released_at.emit(index)
|
||||
|
||||
super(ProjectView, self).mouseReleaseEvent(event)
|
||||
|
||||
|
||||
class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel):
|
||||
|
|
@ -846,18 +1000,21 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel):
|
|||
|
||||
class ProjectListWidget(QtWidgets.QWidget):
|
||||
project_changed = QtCore.Signal()
|
||||
version_change_requested = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, parent, only_active=False):
|
||||
self._parent = parent
|
||||
|
||||
self._entity = None
|
||||
self.current_project = None
|
||||
|
||||
super(ProjectListWidget, self).__init__(parent)
|
||||
self.setObjectName("ProjectListWidget")
|
||||
|
||||
label_widget = QtWidgets.QLabel("Projects")
|
||||
content_frame = QtWidgets.QFrame(self)
|
||||
content_frame.setObjectName("ProjectListContentWidget")
|
||||
|
||||
project_list = ProjectListView(self)
|
||||
project_list = ProjectView(content_frame)
|
||||
project_model = ProjectModel(only_active)
|
||||
project_proxy = ProjectSortFilterProxy()
|
||||
|
||||
|
|
@ -865,33 +1022,37 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
project_proxy.setSourceModel(project_model)
|
||||
project_list.setModel(project_proxy)
|
||||
|
||||
# Do not allow editing
|
||||
project_list.setEditTriggers(
|
||||
QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers
|
||||
)
|
||||
# Do not automatically handle selection
|
||||
project_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||
content_layout = QtWidgets.QVBoxLayout(content_frame)
|
||||
content_layout.setContentsMargins(0, 0, 0, 0)
|
||||
content_layout.setSpacing(0)
|
||||
content_layout.addWidget(project_list, 1)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setSpacing(3)
|
||||
layout.addWidget(label_widget, 0)
|
||||
layout.addWidget(project_list, 1)
|
||||
inactive_chk = None
|
||||
if not only_active:
|
||||
checkbox_wrapper = QtWidgets.QWidget(content_frame)
|
||||
checkbox_wrapper.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
|
||||
if only_active:
|
||||
inactive_chk = None
|
||||
else:
|
||||
inactive_chk = QtWidgets.QCheckBox(" Show Inactive Projects ")
|
||||
inactive_chk = QtWidgets.QCheckBox(
|
||||
"Show Inactive Projects", checkbox_wrapper
|
||||
)
|
||||
inactive_chk.setChecked(not project_proxy.is_filter_enabled())
|
||||
|
||||
layout.addSpacing(5)
|
||||
layout.addWidget(inactive_chk, 0)
|
||||
layout.addSpacing(5)
|
||||
wrapper_layout = QtWidgets.QHBoxLayout(checkbox_wrapper)
|
||||
wrapper_layout.addWidget(inactive_chk, 1)
|
||||
|
||||
content_layout.addWidget(checkbox_wrapper, 0)
|
||||
|
||||
inactive_chk.stateChanged.connect(self.on_inactive_vis_changed)
|
||||
|
||||
project_list.left_mouse_released_at.connect(self.on_item_clicked)
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
# Margins '3' are matching to configurables widget scroll area on right
|
||||
layout.setContentsMargins(5, 3, 3, 3)
|
||||
layout.addWidget(content_frame, 1)
|
||||
|
||||
self._default_project_item = None
|
||||
project_list.left_mouse_released_at.connect(self.on_item_clicked)
|
||||
project_list.right_mouse_released_at.connect(
|
||||
self._on_item_right_clicked
|
||||
)
|
||||
|
||||
self.project_list = project_list
|
||||
self.project_proxy = project_proxy
|
||||
|
|
@ -900,10 +1061,46 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
|
||||
self.dbcon = None
|
||||
|
||||
def on_item_clicked(self, new_index):
|
||||
new_project_name = new_index.data(QtCore.Qt.DisplayRole)
|
||||
if new_project_name is None:
|
||||
def set_entity(self, entity):
|
||||
self._entity = entity
|
||||
|
||||
def _on_item_right_clicked(self, index):
|
||||
if not index.isValid():
|
||||
return
|
||||
project_name = index.data(PROJECT_NAME_ROLE)
|
||||
if project_name is None:
|
||||
project_name = DEFAULT_PROJECT_LABEL
|
||||
|
||||
if self.current_project != project_name:
|
||||
self.on_item_clicked(index)
|
||||
|
||||
if self.current_project != project_name:
|
||||
return
|
||||
|
||||
if not self._entity:
|
||||
return
|
||||
|
||||
versions = self._entity.get_available_source_versions(sorted=True)
|
||||
if not versions:
|
||||
return
|
||||
|
||||
menu = QtWidgets.QMenu(self)
|
||||
submenu = QtWidgets.QMenu("Use settings from version", menu)
|
||||
for version in reversed(versions):
|
||||
action = VersionAction(version, submenu)
|
||||
action.version_triggered.connect(
|
||||
self.version_change_requested
|
||||
)
|
||||
submenu.addAction(action)
|
||||
menu.addMenu(submenu)
|
||||
menu.exec_(QtGui.QCursor.pos())
|
||||
|
||||
def on_item_clicked(self, new_index):
|
||||
if not new_index.isValid():
|
||||
return
|
||||
new_project_name = new_index.data(PROJECT_NAME_ROLE)
|
||||
if new_project_name is None:
|
||||
new_project_name = DEFAULT_PROJECT_LABEL
|
||||
|
||||
if self.current_project == new_project_name:
|
||||
return
|
||||
|
|
@ -963,12 +1160,30 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
index = model.indexFromItem(found_items[0])
|
||||
model.setData(index, True, PROJECT_IS_SELECTED_ROLE)
|
||||
|
||||
index = proxy.mapFromSource(index)
|
||||
src_indexes = []
|
||||
col_count = model.columnCount()
|
||||
if col_count > 1:
|
||||
for col in range(col_count):
|
||||
src_indexes.append(
|
||||
model.index(index.row(), col, index.parent())
|
||||
)
|
||||
dst_indexes = []
|
||||
for index in src_indexes:
|
||||
dst_indexes.append(proxy.mapFromSource(index))
|
||||
|
||||
self.project_list.selectionModel().clear()
|
||||
self.project_list.selectionModel().setCurrentIndex(
|
||||
index, QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent
|
||||
)
|
||||
selection_model = self.project_list.selectionModel()
|
||||
selection_model.clear()
|
||||
|
||||
first = True
|
||||
for index in dst_indexes:
|
||||
if first:
|
||||
selection_model.setCurrentIndex(
|
||||
index,
|
||||
QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent
|
||||
)
|
||||
first = False
|
||||
continue
|
||||
selection_model.select(index, QtCore.QItemSelectionModel.Select)
|
||||
|
||||
def get_project_names(self):
|
||||
output = []
|
||||
|
|
@ -980,7 +1195,7 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
def refresh(self):
|
||||
selected_project = None
|
||||
for index in self.project_list.selectedIndexes():
|
||||
selected_project = index.data(QtCore.Qt.DisplayRole)
|
||||
selected_project = index.data(PROJECT_NAME_ROLE)
|
||||
break
|
||||
|
||||
mongo_url = os.environ["OPENPYPE_MONGO"]
|
||||
|
|
@ -1008,5 +1223,6 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
self.select_project(selected_project)
|
||||
|
||||
self.current_project = self.project_list.currentIndex().data(
|
||||
QtCore.Qt.DisplayRole
|
||||
PROJECT_NAME_ROLE
|
||||
)
|
||||
self.project_list.resizeColumnToContents(0)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@ from .categories import (
|
|||
SystemWidget,
|
||||
ProjectWidget
|
||||
)
|
||||
from .widgets import ShadowWidget, RestartDialog
|
||||
from .widgets import (
|
||||
ShadowWidget,
|
||||
RestartDialog,
|
||||
SettingsTabWidget
|
||||
)
|
||||
from openpype import style
|
||||
|
||||
from openpype.lib import is_admin_password_required
|
||||
|
|
@ -34,7 +38,7 @@ class MainWidget(QtWidgets.QWidget):
|
|||
self.setStyleSheet(stylesheet)
|
||||
self.setWindowIcon(QtGui.QIcon(style.app_icon_path()))
|
||||
|
||||
header_tab_widget = QtWidgets.QTabWidget(parent=self)
|
||||
header_tab_widget = SettingsTabWidget(parent=self)
|
||||
|
||||
studio_widget = SystemWidget(user_role, header_tab_widget)
|
||||
project_widget = ProjectWidget(user_role, header_tab_widget)
|
||||
|
|
@ -65,6 +69,10 @@ class MainWidget(QtWidgets.QWidget):
|
|||
)
|
||||
tab_widget.full_path_requested.connect(self._on_full_path_request)
|
||||
|
||||
header_tab_widget.context_menu_requested.connect(
|
||||
self._on_context_menu_request
|
||||
)
|
||||
|
||||
self._header_tab_widget = header_tab_widget
|
||||
self.tab_widgets = tab_widgets
|
||||
|
||||
|
|
@ -100,6 +108,18 @@ class MainWidget(QtWidgets.QWidget):
|
|||
tab_widget.set_category_path(category, path)
|
||||
break
|
||||
|
||||
def _on_context_menu_request(self, tab_idx):
|
||||
widget = self._header_tab_widget.widget(tab_idx)
|
||||
if not widget:
|
||||
return
|
||||
|
||||
menu = QtWidgets.QMenu(self)
|
||||
widget.add_context_actions(menu)
|
||||
if menu.actions():
|
||||
result = menu.exec_(QtGui.QCursor.pos())
|
||||
if result is not None:
|
||||
self._header_tab_widget.setCurrentIndex(tab_idx)
|
||||
|
||||
def showEvent(self, event):
|
||||
super(MainWidget, self).showEvent(event)
|
||||
if self._reset_on_show:
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ from openpype.settings import (
|
|||
)
|
||||
from openpype.tools.utils import (
|
||||
WrappedCallbackItem,
|
||||
paint_image_with_color
|
||||
paint_image_with_color,
|
||||
get_warning_pixmap
|
||||
)
|
||||
|
||||
from .pype_info_widget import PypeInfoWidget
|
||||
|
|
@ -76,7 +77,7 @@ class PixmapLabel(QtWidgets.QLabel):
|
|||
super(PixmapLabel, self).resizeEvent(event)
|
||||
|
||||
|
||||
class VersionDialog(QtWidgets.QDialog):
|
||||
class VersionUpdateDialog(QtWidgets.QDialog):
|
||||
restart_requested = QtCore.Signal()
|
||||
ignore_requested = QtCore.Signal()
|
||||
|
||||
|
|
@ -84,7 +85,7 @@ class VersionDialog(QtWidgets.QDialog):
|
|||
_min_height = 130
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(VersionDialog, self).__init__(parent)
|
||||
super(VersionUpdateDialog, self).__init__(parent)
|
||||
|
||||
icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
|
|
@ -152,11 +153,11 @@ class VersionDialog(QtWidgets.QDialog):
|
|||
)
|
||||
|
||||
def showEvent(self, event):
|
||||
super().showEvent(event)
|
||||
super(VersionUpdateDialog, self).showEvent(event)
|
||||
self._restart_accepted = False
|
||||
|
||||
def closeEvent(self, event):
|
||||
super().closeEvent(event)
|
||||
super(VersionUpdateDialog, self).closeEvent(event)
|
||||
if self._restart_accepted or self._current_is_higher:
|
||||
return
|
||||
# Trigger ignore requested only if restart was not clicked and current
|
||||
|
|
@ -202,6 +203,63 @@ class VersionDialog(QtWidgets.QDialog):
|
|||
self.accept()
|
||||
|
||||
|
||||
class BuildVersionDialog(QtWidgets.QDialog):
|
||||
"""Build/Installation version is too low for current OpenPype version.
|
||||
|
||||
This dialog tells to user that it's build OpenPype is too old.
|
||||
"""
|
||||
def __init__(self, parent=None):
|
||||
super(BuildVersionDialog, self).__init__(parent)
|
||||
|
||||
icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
self.setWindowTitle("Outdated OpenPype installation")
|
||||
self.setWindowFlags(
|
||||
self.windowFlags()
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
)
|
||||
|
||||
top_widget = QtWidgets.QWidget(self)
|
||||
|
||||
warning_pixmap = get_warning_pixmap()
|
||||
warning_icon_label = PixmapLabel(warning_pixmap, top_widget)
|
||||
|
||||
message = (
|
||||
"Your installation of OpenPype <b>does not match minimum"
|
||||
" requirements</b>.<br/><br/>Please update OpenPype installation"
|
||||
" to newer version."
|
||||
)
|
||||
content_label = QtWidgets.QLabel(message, self)
|
||||
|
||||
top_layout = QtWidgets.QHBoxLayout(top_widget)
|
||||
top_layout.setContentsMargins(0, 0, 0, 0)
|
||||
top_layout.addWidget(
|
||||
warning_icon_label, 0,
|
||||
QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter
|
||||
)
|
||||
top_layout.addWidget(content_label, 1)
|
||||
|
||||
footer_widget = QtWidgets.QWidget(self)
|
||||
ok_btn = QtWidgets.QPushButton("I understand", footer_widget)
|
||||
|
||||
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
|
||||
footer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
footer_layout.addStretch(1)
|
||||
footer_layout.addWidget(ok_btn)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.addWidget(top_widget, 0)
|
||||
main_layout.addStretch(1)
|
||||
main_layout.addWidget(footer_widget, 0)
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
ok_btn.clicked.connect(self._on_ok_clicked)
|
||||
|
||||
def _on_ok_clicked(self):
|
||||
self.close()
|
||||
|
||||
|
||||
class TrayManager:
|
||||
"""Cares about context of application.
|
||||
|
||||
|
|
@ -272,7 +330,7 @@ class TrayManager:
|
|||
return
|
||||
|
||||
if self._version_dialog is None:
|
||||
self._version_dialog = VersionDialog()
|
||||
self._version_dialog = VersionUpdateDialog()
|
||||
self._version_dialog.restart_requested.connect(
|
||||
self._restart_and_install
|
||||
)
|
||||
|
|
@ -383,6 +441,10 @@ class TrayManager:
|
|||
|
||||
self._validate_settings_defaults()
|
||||
|
||||
if not op_version_control_available():
|
||||
dialog = BuildVersionDialog()
|
||||
dialog.exec_()
|
||||
|
||||
def _validate_settings_defaults(self):
|
||||
valid = True
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ from .widgets import (
|
|||
from .error_dialog import ErrorMessageBox
|
||||
from .lib import (
|
||||
WrappedCallbackItem,
|
||||
paint_image_with_color
|
||||
paint_image_with_color,
|
||||
get_warning_pixmap,
|
||||
set_style_property,
|
||||
DynamicQThread,
|
||||
)
|
||||
|
||||
from .models import (
|
||||
|
|
@ -29,6 +32,9 @@ __all__ = (
|
|||
|
||||
"WrappedCallbackItem",
|
||||
"paint_image_with_color",
|
||||
"get_warning_pixmap",
|
||||
"set_style_property",
|
||||
"DynamicQThread",
|
||||
|
||||
"RecursiveSortFilterProxyModel",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import Qt
|
|||
from Qt import QtWidgets, QtGui, QtCore
|
||||
|
||||
from avalon.lib import HeroVersionType
|
||||
from openpype.style import get_objected_colors
|
||||
from .models import TreeModel
|
||||
from . import lib
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ from openpype.api import (
|
|||
Logger
|
||||
)
|
||||
from openpype.lib import filter_profiles
|
||||
from openpype.style import get_objected_colors
|
||||
from openpype.resources import get_image_path
|
||||
|
||||
|
||||
def center_window(window):
|
||||
|
|
@ -28,6 +30,18 @@ def center_window(window):
|
|||
window.move(geo.topLeft())
|
||||
|
||||
|
||||
def set_style_property(widget, property_name, property_value):
|
||||
"""Set widget's property that may affect style.
|
||||
|
||||
If current property value is different then style of widget is polished.
|
||||
"""
|
||||
cur_value = widget.property(property_name)
|
||||
if cur_value == property_value:
|
||||
return
|
||||
widget.setProperty(property_name, property_value)
|
||||
widget.style().polish(widget)
|
||||
|
||||
|
||||
def paint_image_with_color(image, color):
|
||||
"""Redraw image with single color using it's alpha.
|
||||
|
||||
|
|
@ -670,3 +684,19 @@ class WrappedCallbackItem:
|
|||
|
||||
finally:
|
||||
self._done = True
|
||||
|
||||
|
||||
def get_warning_pixmap(color=None):
|
||||
"""Warning icon as QPixmap.
|
||||
|
||||
Args:
|
||||
color(QtGui.QColor): Color that will be used to paint warning icon.
|
||||
"""
|
||||
src_image_path = get_image_path("warning.png")
|
||||
src_image = QtGui.QImage(src_image_path)
|
||||
if color is None:
|
||||
colors = get_objected_colors()
|
||||
color_value = colors["delete-btn-bg"]
|
||||
color = color_value.get_qcolor()
|
||||
|
||||
return paint_image_with_color(src_image, color)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@ import logging
|
|||
|
||||
import Qt
|
||||
from Qt import QtCore, QtGui
|
||||
from avalon.vendor import qtawesome
|
||||
from avalon import style, io
|
||||
from . import lib
|
||||
from .constants import (
|
||||
PROJECT_IS_ACTIVE_ROLE,
|
||||
PROJECT_NAME_ROLE,
|
||||
|
|
|
|||
|
|
@ -21,14 +21,13 @@ from openpype.tools.utils.tasks_widget import TasksWidget
|
|||
from openpype.tools.utils.delegates import PrettyTimeDelegate
|
||||
from openpype.lib import (
|
||||
Anatomy,
|
||||
get_workdir,
|
||||
get_workfile_doc,
|
||||
create_workfile_doc,
|
||||
save_workfile_data_to_doc,
|
||||
get_workfile_template_key,
|
||||
create_workdir_extra_folders
|
||||
create_workdir_extra_folders,
|
||||
get_system_general_anatomy_data
|
||||
)
|
||||
|
||||
from .model import FilesModel
|
||||
from .view import FilesView
|
||||
|
||||
|
|
@ -110,6 +109,10 @@ class NameWindow(QtWidgets.QDialog):
|
|||
"ext": None
|
||||
}
|
||||
|
||||
# add system general settings anatomy data
|
||||
system_general_data = get_system_general_anatomy_data()
|
||||
self.data.update(system_general_data)
|
||||
|
||||
# Store project anatomy
|
||||
self.anatomy = anatomy
|
||||
self.template = anatomy.templates[template_key]["file"]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.8.2"
|
||||
__version__ = "3.8.3-nightly.1"
|
||||
|
|
|
|||
|
|
@ -132,12 +132,12 @@ class Popup2(Popup):
|
|||
"""
|
||||
parent_widget = self.parent()
|
||||
|
||||
app = QtWidgets.QApplication.instance()
|
||||
desktop = QtWidgets.QApplication.desktop()
|
||||
if parent_widget:
|
||||
screen = app.desktop().screenNumber(parent_widget)
|
||||
screen = desktop.screenNumber(parent_widget)
|
||||
else:
|
||||
screen = app.desktop().screenNumber(app.desktop().cursor().pos())
|
||||
center_point = app.desktop().screenGeometry(screen).center()
|
||||
screen = desktop.screenNumber(desktop.cursor().pos())
|
||||
center_point = desktop.screenGeometry(screen).center()
|
||||
|
||||
frame_geo = self.frameGeometry()
|
||||
frame_geo.moveCenter(center_point)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.8.2" # OpenPype
|
||||
version = "3.8.3-nightly.1" # OpenPype
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
|
|
@ -85,8 +85,8 @@ openpype_console eventserver --ftrack-url=<url> --ftrack-user=<user> --ftrack-ap
|
|||
| `--asset` | Asset name (default taken from `AVALON_ASSET` if set) |
|
||||
| `--task` | Task name (default taken from `AVALON_TASK` is set) |
|
||||
| `--tools` | *Optional: Additional tools to add* |
|
||||
| `--user` | *Optional: User on behalf to run* |
|
||||
| `--ftrack-server` / `-fs` | *Optional: Ftrack server URL* |
|
||||
| `--user` | *Optional: User on behalf to run* |
|
||||
| `--ftrack-server` / `-fs` | *Optional: Ftrack server URL* |
|
||||
| `--ftrack-user` / `-fu` | *Optional: Ftrack user* |
|
||||
| `--ftrack-key` / `-fk` | *Optional: Ftrack API key* |
|
||||
|
||||
|
|
@ -166,3 +166,6 @@ Takes path to unzipped and possibly modified OpenPype version. Files will be
|
|||
zipped, checksums recalculated and version will be determined by folder name
|
||||
(and written to `version.py`).
|
||||
|
||||
```shell
|
||||
./openpype_console repack-version /path/to/some/modified/unzipped/version/openpype-v3.8.3-modified
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue