mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
Merge branch 'develop' into maya_look_containerize_reference_only
This commit is contained in:
commit
228ae756a9
46 changed files with 902 additions and 296 deletions
96
CHANGELOG.md
96
CHANGELOG.md
|
|
@ -1,62 +1,66 @@
|
|||
# Changelog
|
||||
|
||||
## [3.8.0-nightly.5](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
## [3.8.0](https://github.com/pypeclub/OpenPype/tree/3.8.0) (2022-01-24)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.7.0...HEAD)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Renamed to proper name [\#2546](https://github.com/pypeclub/OpenPype/pull/2546)
|
||||
- Slack: Add review to notification message [\#2498](https://github.com/pypeclub/OpenPype/pull/2498)
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.7.0...3.8.0)
|
||||
|
||||
**🆕 New features**
|
||||
|
||||
- Flame: OpenTimelineIO Export Modul [\#2398](https://github.com/pypeclub/OpenPype/pull/2398)
|
||||
- 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)
|
||||
- General: Validate if current process OpenPype version is requested version [\#2529](https://github.com/pypeclub/OpenPype/pull/2529)
|
||||
- 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)
|
||||
- TimersManager: Move module one hierarchy higher [\#2501](https://github.com/pypeclub/OpenPype/pull/2501)
|
||||
- Slack: notifications are sent with Openpype logo and bot name [\#2499](https://github.com/pypeclub/OpenPype/pull/2499)
|
||||
- Ftrack: Event handlers settings [\#2496](https://github.com/pypeclub/OpenPype/pull/2496)
|
||||
- Flame - create publishable clips [\#2495](https://github.com/pypeclub/OpenPype/pull/2495)
|
||||
- Tools: Fix style and modality of errors in loader and creator [\#2489](https://github.com/pypeclub/OpenPype/pull/2489)
|
||||
- Project Manager: Remove project button cleanup [\#2482](https://github.com/pypeclub/OpenPype/pull/2482)
|
||||
- Tools: Be able to change models of tasks and assets widgets [\#2475](https://github.com/pypeclub/OpenPype/pull/2475)
|
||||
- Publish pype: Reduce publish process defering [\#2464](https://github.com/pypeclub/OpenPype/pull/2464)
|
||||
- Maya: Improve speed of Collect History logic [\#2460](https://github.com/pypeclub/OpenPype/pull/2460)
|
||||
- Maya: Validate Rig Controllers - fix Error: in script editor [\#2459](https://github.com/pypeclub/OpenPype/pull/2459)
|
||||
- Maya: Optimize Validate Locked Normals speed for dense polymeshes [\#2457](https://github.com/pypeclub/OpenPype/pull/2457)
|
||||
- Fix \#2453 Refactor missing \_get\_reference\_node method [\#2455](https://github.com/pypeclub/OpenPype/pull/2455)
|
||||
- Houdini: Remove broken unique name counter [\#2450](https://github.com/pypeclub/OpenPype/pull/2450)
|
||||
- Maya: Improve lib.polyConstraint performance when Select tool is not the active tool context [\#2447](https://github.com/pypeclub/OpenPype/pull/2447)
|
||||
- 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)
|
||||
- General: Validate third party before build [\#2425](https://github.com/pypeclub/OpenPype/pull/2425)
|
||||
- Maya : add option to not group reference in ReferenceLoader [\#2383](https://github.com/pypeclub/OpenPype/pull/2383)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- AfterEffects: Fix - removed obsolete import [\#2577](https://github.com/pypeclub/OpenPype/pull/2577)
|
||||
- General: OpenPype version updates [\#2575](https://github.com/pypeclub/OpenPype/pull/2575)
|
||||
- Ftrack: Delete action revision [\#2563](https://github.com/pypeclub/OpenPype/pull/2563)
|
||||
- Webpublisher: ftrack shows incorrect user names [\#2560](https://github.com/pypeclub/OpenPype/pull/2560)
|
||||
- General: Do not validate version if build does not support it [\#2557](https://github.com/pypeclub/OpenPype/pull/2557)
|
||||
- Webpublisher: Fixed progress reporting [\#2553](https://github.com/pypeclub/OpenPype/pull/2553)
|
||||
- Fix Maya AssProxyLoader version switch [\#2551](https://github.com/pypeclub/OpenPype/pull/2551)
|
||||
- General: Fix install thread in igniter [\#2549](https://github.com/pypeclub/OpenPype/pull/2549)
|
||||
- Houdini: vdbcache family preserve frame numbers on publish integration + enable validate version for Houdini [\#2535](https://github.com/pypeclub/OpenPype/pull/2535)
|
||||
- Maya: Fix Load VDB to V-Ray [\#2533](https://github.com/pypeclub/OpenPype/pull/2533)
|
||||
- Maya: ReferenceLoader fix not unique group name error for attach to root [\#2532](https://github.com/pypeclub/OpenPype/pull/2532)
|
||||
- Maya: namespaced context go back to original namespace when started from inside a namespace [\#2531](https://github.com/pypeclub/OpenPype/pull/2531)
|
||||
- Fix create zip tool - path argument [\#2522](https://github.com/pypeclub/OpenPype/pull/2522)
|
||||
- Maya: Fix Extract Look with space in names [\#2518](https://github.com/pypeclub/OpenPype/pull/2518)
|
||||
- Fix published frame content for sequence starting with 0 [\#2513](https://github.com/pypeclub/OpenPype/pull/2513)
|
||||
- Fix \#2497: reset empty string attributes correctly to "" instead of "None" [\#2506](https://github.com/pypeclub/OpenPype/pull/2506)
|
||||
- General: Settings work if OpenPypeVersion is available [\#2494](https://github.com/pypeclub/OpenPype/pull/2494)
|
||||
- General: PYTHONPATH may break OpenPype dependencies [\#2493](https://github.com/pypeclub/OpenPype/pull/2493)
|
||||
- Workfiles tool: Files widget show files on first show [\#2488](https://github.com/pypeclub/OpenPype/pull/2488)
|
||||
- General: Custom template paths filter fix [\#2483](https://github.com/pypeclub/OpenPype/pull/2483)
|
||||
- Loader: Remove always on top flag in tray [\#2480](https://github.com/pypeclub/OpenPype/pull/2480)
|
||||
- General: Anatomy does not return root envs as unicode [\#2465](https://github.com/pypeclub/OpenPype/pull/2465)
|
||||
- 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)
|
||||
- Maya: Validate Shape Zero do not keep fixed geometry vertices selected/active after repair [\#2456](https://github.com/pypeclub/OpenPype/pull/2456)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Variable in docs renamed to proper name [\#2546](https://github.com/pypeclub/OpenPype/pull/2546)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- General: Fix install thread in igniter [\#2549](https://github.com/pypeclub/OpenPype/pull/2549)
|
||||
- AfterEffects: Move implementation to OpenPype [\#2543](https://github.com/pypeclub/OpenPype/pull/2543)
|
||||
- Fix create zip tool - path argument [\#2522](https://github.com/pypeclub/OpenPype/pull/2522)
|
||||
- General: Modules import function output fix [\#2492](https://github.com/pypeclub/OpenPype/pull/2492)
|
||||
- AE: fix hiding of alert window below Publish [\#2491](https://github.com/pypeclub/OpenPype/pull/2491)
|
||||
- Maya: Validate NGONs re-use polyConstraint code from openpype.host.maya.api.lib [\#2458](https://github.com/pypeclub/OpenPype/pull/2458)
|
||||
- Maya: Remove Maya Look Assigner check on startup [\#2540](https://github.com/pypeclub/OpenPype/pull/2540)
|
||||
- build\(deps\): bump shelljs from 0.8.4 to 0.8.5 in /website [\#2538](https://github.com/pypeclub/OpenPype/pull/2538)
|
||||
- build\(deps\): bump follow-redirects from 1.14.4 to 1.14.7 in /website [\#2534](https://github.com/pypeclub/OpenPype/pull/2534)
|
||||
- Nuke: Merge avalon's implementation into OpenPype [\#2514](https://github.com/pypeclub/OpenPype/pull/2514)
|
||||
|
||||
## [3.7.0](https://github.com/pypeclub/OpenPype/tree/3.7.0) (2022-01-04)
|
||||
|
||||
|
|
@ -71,39 +75,15 @@
|
|||
- Settings UI: Hyperlinks to settings [\#2420](https://github.com/pypeclub/OpenPype/pull/2420)
|
||||
- Modules: JobQueue module moved one hierarchy level higher [\#2419](https://github.com/pypeclub/OpenPype/pull/2419)
|
||||
- TimersManager: Start timer post launch hook [\#2418](https://github.com/pypeclub/OpenPype/pull/2418)
|
||||
- General: Run applications as separate processes under linux [\#2408](https://github.com/pypeclub/OpenPype/pull/2408)
|
||||
- Ftrack: Check existence of object type on recreation [\#2404](https://github.com/pypeclub/OpenPype/pull/2404)
|
||||
- Enhancement: Global cleanup plugin that explicitly remove paths from context [\#2402](https://github.com/pypeclub/OpenPype/pull/2402)
|
||||
- General: MongoDB ability to specify replica set groups [\#2401](https://github.com/pypeclub/OpenPype/pull/2401)
|
||||
- Flame: moving `utility\_scripts` to api folder also with `scripts` [\#2385](https://github.com/pypeclub/OpenPype/pull/2385)
|
||||
- Centos 7 dependency compatibility [\#2384](https://github.com/pypeclub/OpenPype/pull/2384)
|
||||
- Enhancement: Settings: Use project settings values from another project [\#2382](https://github.com/pypeclub/OpenPype/pull/2382)
|
||||
- Blender 3: Support auto install for new blender version [\#2377](https://github.com/pypeclub/OpenPype/pull/2377)
|
||||
- Maya add render image path to settings [\#2375](https://github.com/pypeclub/OpenPype/pull/2375)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- TVPaint: Create render layer dialog is in front [\#2471](https://github.com/pypeclub/OpenPype/pull/2471)
|
||||
- Short Pyblish plugin path [\#2428](https://github.com/pypeclub/OpenPype/pull/2428)
|
||||
- PS: Introduced settings for invalid characters to use in ValidateNaming plugin [\#2417](https://github.com/pypeclub/OpenPype/pull/2417)
|
||||
- Settings UI: Breadcrumbs path does not create new entities [\#2416](https://github.com/pypeclub/OpenPype/pull/2416)
|
||||
- AfterEffects: Variant 2022 is in defaults but missing in schemas [\#2412](https://github.com/pypeclub/OpenPype/pull/2412)
|
||||
- Nuke: baking representations was not additive [\#2406](https://github.com/pypeclub/OpenPype/pull/2406)
|
||||
- General: Fix access to environments from default settings [\#2403](https://github.com/pypeclub/OpenPype/pull/2403)
|
||||
- Fix: Placeholder Input color set fix [\#2399](https://github.com/pypeclub/OpenPype/pull/2399)
|
||||
- Settings: Fix state change of wrapper label [\#2396](https://github.com/pypeclub/OpenPype/pull/2396)
|
||||
- Flame: fix ftrack publisher [\#2381](https://github.com/pypeclub/OpenPype/pull/2381)
|
||||
- hiero: solve custom ocio path [\#2379](https://github.com/pypeclub/OpenPype/pull/2379)
|
||||
- hiero: fix workio and flatten [\#2378](https://github.com/pypeclub/OpenPype/pull/2378)
|
||||
- Nuke: fixing menu re-drawing during context change [\#2374](https://github.com/pypeclub/OpenPype/pull/2374)
|
||||
- Webpublisher: Fix assignment of families of TVpaint instances [\#2373](https://github.com/pypeclub/OpenPype/pull/2373)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Forced cx\_freeze to include sqlite3 into build [\#2432](https://github.com/pypeclub/OpenPype/pull/2432)
|
||||
- Maya: Replaced PATH usage with vendored oiio path for maketx utility [\#2405](https://github.com/pypeclub/OpenPype/pull/2405)
|
||||
- \[Fix\]\[MAYA\] Handle message type attribute within CollectLook [\#2394](https://github.com/pypeclub/OpenPype/pull/2394)
|
||||
- Add validator to check correct version of extension for PS and AE [\#2387](https://github.com/pypeclub/OpenPype/pull/2387)
|
||||
|
||||
## [3.6.4](https://github.com/pypeclub/OpenPype/tree/3.6.4) (2021-11-23)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import re
|
|||
import tempfile
|
||||
import attr
|
||||
|
||||
from avalon import aftereffects
|
||||
import pyblish.api
|
||||
|
||||
from openpype.settings import get_project_settings
|
||||
|
|
|
|||
|
|
@ -74,6 +74,9 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
|
||||
instance = context.create_instance(label)
|
||||
|
||||
# Include `families` using `family` data
|
||||
instance.data["families"] = [instance.data["family"]]
|
||||
|
||||
instance[:] = [node]
|
||||
instance.data.update(data)
|
||||
|
||||
|
|
|
|||
|
|
@ -37,5 +37,7 @@ class ExtractVDBCache(openpype.api.Extractor):
|
|||
"ext": "vdb",
|
||||
"files": output,
|
||||
"stagingDir": staging_dir,
|
||||
"frameStart": instance.data["frameStart"],
|
||||
"frameEnd": instance.data["frameEnd"],
|
||||
}
|
||||
instance.data["representations"].append(representation)
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ def shape_from_element(element):
|
|||
return node
|
||||
|
||||
|
||||
def collect_animation_data():
|
||||
def collect_animation_data(fps=False):
|
||||
"""Get the basic animation data
|
||||
|
||||
Returns:
|
||||
|
|
@ -291,7 +291,6 @@ def collect_animation_data():
|
|||
# get scene values as defaults
|
||||
start = cmds.playbackOptions(query=True, animationStartTime=True)
|
||||
end = cmds.playbackOptions(query=True, animationEndTime=True)
|
||||
fps = mel.eval('currentTimeUnitToFPS()')
|
||||
|
||||
# build attributes
|
||||
data = OrderedDict()
|
||||
|
|
@ -299,7 +298,9 @@ def collect_animation_data():
|
|||
data["frameEnd"] = end
|
||||
data["handles"] = 0
|
||||
data["step"] = 1.0
|
||||
data["fps"] = fps
|
||||
|
||||
if fps:
|
||||
data["fps"] = mel.eval('currentTimeUnitToFPS()')
|
||||
|
||||
return data
|
||||
|
||||
|
|
@ -2887,3 +2888,27 @@ def set_colorspace():
|
|||
cmds.colorManagementPrefs(e=True, renderingSpaceName=renderSpace)
|
||||
viewTransform = root_dict["viewTransform"]
|
||||
cmds.colorManagementPrefs(e=True, viewTransformName=viewTransform)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def root_parent(nodes):
|
||||
# type: (list) -> list
|
||||
"""Context manager to un-parent provided nodes and return then back."""
|
||||
import pymel.core as pm # noqa
|
||||
|
||||
node_parents = []
|
||||
for node in nodes:
|
||||
n = pm.PyNode(node)
|
||||
try:
|
||||
root = pm.listRelatives(n, parent=1)[0]
|
||||
except IndexError:
|
||||
root = None
|
||||
node_parents.append((n, root))
|
||||
try:
|
||||
for node in node_parents:
|
||||
node[0].setParent(world=True)
|
||||
yield
|
||||
finally:
|
||||
for node in node_parents:
|
||||
if node[1]:
|
||||
node[0].setParent(node[1])
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class CreateReview(plugin.Creator):
|
|||
|
||||
# get basic animation data : start / end / handles / steps
|
||||
data = OrderedDict(**self.data)
|
||||
animation_data = lib.collect_animation_data()
|
||||
animation_data = lib.collect_animation_data(fps=True)
|
||||
for key, value in animation_data.items():
|
||||
data[key] = value
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,58 @@
|
|||
from openpype.hosts.maya.api import plugin
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Creator for Unreal Static Meshes."""
|
||||
from openpype.hosts.maya.api import plugin, lib
|
||||
from avalon.api import Session
|
||||
from openpype.api import get_project_settings
|
||||
from maya import cmds # noqa
|
||||
|
||||
|
||||
class CreateUnrealStaticMesh(plugin.Creator):
|
||||
"""Unreal Static Meshes with collisions."""
|
||||
name = "staticMeshMain"
|
||||
label = "Unreal - Static Mesh"
|
||||
family = "unrealStaticMesh"
|
||||
icon = "cube"
|
||||
dynamic_subset_keys = ["asset"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Constructor."""
|
||||
super(CreateUnrealStaticMesh, self).__init__(*args, **kwargs)
|
||||
self._project_settings = get_project_settings(
|
||||
Session["AVALON_PROJECT"])
|
||||
|
||||
@classmethod
|
||||
def get_dynamic_data(
|
||||
cls, variant, task_name, asset_id, project_name, host_name
|
||||
):
|
||||
dynamic_data = super(CreateUnrealStaticMesh, cls).get_dynamic_data(
|
||||
variant, task_name, asset_id, project_name, host_name
|
||||
)
|
||||
dynamic_data["asset"] = Session.get("AVALON_ASSET")
|
||||
|
||||
return dynamic_data
|
||||
|
||||
def process(self):
|
||||
with lib.undo_chunk():
|
||||
instance = super(CreateUnrealStaticMesh, self).process()
|
||||
content = cmds.sets(instance, query=True)
|
||||
|
||||
# empty set and process its former content
|
||||
cmds.sets(content, rm=instance)
|
||||
geometry_set = cmds.sets(name="geometry_SET", empty=True)
|
||||
collisions_set = cmds.sets(name="collisions_SET", empty=True)
|
||||
|
||||
cmds.sets([geometry_set, collisions_set], forceElement=instance)
|
||||
|
||||
members = cmds.ls(content, long=True) or []
|
||||
children = cmds.listRelatives(members, allDescendents=True,
|
||||
fullPath=True) or []
|
||||
children = cmds.ls(children, type="transform")
|
||||
for node in children:
|
||||
if cmds.listRelatives(node, type="shape"):
|
||||
if [
|
||||
n for n in self.collision_prefixes
|
||||
if node.startswith(n)
|
||||
]:
|
||||
cmds.sets(node, forceElement=collisions_set)
|
||||
else:
|
||||
cmds.sets(node, forceElement=geometry_set)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,72 @@ from avalon import api
|
|||
from openpype.api import get_project_settings
|
||||
import os
|
||||
|
||||
from maya import cmds
|
||||
|
||||
# List of 3rd Party Channels Mapping names for VRayVolumeGrid
|
||||
# See: https://docs.chaosgroup.com/display/VRAY4MAYA/Input
|
||||
# #Input-3rdPartyChannelsMapping
|
||||
THIRD_PARTY_CHANNELS = {
|
||||
2: "Smoke",
|
||||
1: "Temperature",
|
||||
10: "Fuel",
|
||||
4: "Velocity.x",
|
||||
5: "Velocity.y",
|
||||
6: "Velocity.z",
|
||||
7: "Red",
|
||||
8: "Green",
|
||||
9: "Blue",
|
||||
14: "Wavelet Energy",
|
||||
19: "Wavelet.u",
|
||||
20: "Wavelet.v",
|
||||
21: "Wavelet.w",
|
||||
# These are not in UI or documentation but V-Ray does seem to set these.
|
||||
15: "AdvectionOrigin.x",
|
||||
16: "AdvectionOrigin.y",
|
||||
17: "AdvectionOrigin.z",
|
||||
|
||||
}
|
||||
|
||||
|
||||
def _fix_duplicate_vvg_callbacks():
|
||||
"""Workaround to kill duplicate VRayVolumeGrids attribute callbacks.
|
||||
|
||||
This fixes a huge lag in Maya on switching 3rd Party Channels Mappings
|
||||
or to different .vdb file paths because it spams an attribute changed
|
||||
callback: `vvgUserChannelMappingsUpdateUI`.
|
||||
|
||||
ChaosGroup bug ticket: 154-008-9890
|
||||
|
||||
Found with:
|
||||
- Maya 2019.2 on Windows 10
|
||||
- V-Ray: V-Ray Next for Maya, update 1 version 4.12.01.00001
|
||||
|
||||
Bug still present in:
|
||||
- Maya 2022.1 on Windows 10
|
||||
- V-Ray 5 for Maya, Update 2.1 (v5.20.01 from Dec 16 2021)
|
||||
|
||||
"""
|
||||
# todo(roy): Remove when new V-Ray release fixes duplicate calls
|
||||
|
||||
jobs = cmds.scriptJob(listJobs=True)
|
||||
|
||||
matched = set()
|
||||
for entry in jobs:
|
||||
# Remove the number
|
||||
index, callback = entry.split(":", 1)
|
||||
callback = callback.strip()
|
||||
|
||||
# Detect whether it is a `vvgUserChannelMappingsUpdateUI`
|
||||
# attribute change callback
|
||||
if callback.startswith('"-runOnce" 1 "-attributeChange" "'):
|
||||
if '"vvgUserChannelMappingsUpdateUI(' in callback:
|
||||
if callback in matched:
|
||||
# If we've seen this callback before then
|
||||
# delete the duplicate callback
|
||||
cmds.scriptJob(kill=int(index))
|
||||
else:
|
||||
matched.add(callback)
|
||||
|
||||
|
||||
class LoadVDBtoVRay(api.Loader):
|
||||
|
||||
|
|
@ -14,15 +80,24 @@ class LoadVDBtoVRay(api.Loader):
|
|||
|
||||
def load(self, context, name, namespace, data):
|
||||
|
||||
from maya import cmds
|
||||
import avalon.maya.lib as lib
|
||||
from avalon.maya.pipeline import containerise
|
||||
|
||||
assert os.path.exists(self.fname), (
|
||||
"Path does not exist: %s" % self.fname
|
||||
)
|
||||
|
||||
try:
|
||||
family = context["representation"]["context"]["family"]
|
||||
except ValueError:
|
||||
family = "vdbcache"
|
||||
|
||||
# Ensure V-ray is loaded with the vrayvolumegrid
|
||||
if not cmds.pluginInfo("vrayformaya", query=True, loaded=True):
|
||||
cmds.loadPlugin("vrayformaya")
|
||||
if not cmds.pluginInfo("vrayvolumegrid", query=True, loaded=True):
|
||||
cmds.loadPlugin("vrayvolumegrid")
|
||||
|
||||
# Check if viewport drawing engine is Open GL Core (compat)
|
||||
render_engine = None
|
||||
compatible = "OpenGLCoreProfileCompat"
|
||||
|
|
@ -30,13 +105,11 @@ class LoadVDBtoVRay(api.Loader):
|
|||
render_engine = cmds.optionVar(query="vp2RenderingEngine")
|
||||
|
||||
if not render_engine or render_engine != compatible:
|
||||
raise RuntimeError("Current scene's settings are incompatible."
|
||||
"See Preferences > Display > Viewport 2.0 to "
|
||||
"set the render engine to '%s'" % compatible)
|
||||
self.log.warning("Current scene's settings are incompatible."
|
||||
"See Preferences > Display > Viewport 2.0 to "
|
||||
"set the render engine to '%s'" % compatible)
|
||||
|
||||
asset = context['asset']
|
||||
version = context["version"]
|
||||
|
||||
asset_name = asset["name"]
|
||||
namespace = namespace or lib.unique_namespace(
|
||||
asset_name + "_",
|
||||
|
|
@ -45,7 +118,7 @@ class LoadVDBtoVRay(api.Loader):
|
|||
)
|
||||
|
||||
# Root group
|
||||
label = "{}:{}".format(namespace, name)
|
||||
label = "{}:{}_VDB".format(namespace, name)
|
||||
root = cmds.group(name=label, empty=True)
|
||||
|
||||
settings = get_project_settings(os.environ['AVALON_PROJECT'])
|
||||
|
|
@ -55,20 +128,24 @@ class LoadVDBtoVRay(api.Loader):
|
|||
if c is not None:
|
||||
cmds.setAttr(root + ".useOutlinerColor", 1)
|
||||
cmds.setAttr(root + ".outlinerColor",
|
||||
(float(c[0])/255),
|
||||
(float(c[1])/255),
|
||||
(float(c[2])/255)
|
||||
)
|
||||
float(c[0]) / 255,
|
||||
float(c[1]) / 255,
|
||||
float(c[2]) / 255)
|
||||
|
||||
# Create VR
|
||||
# Create VRayVolumeGrid
|
||||
grid_node = cmds.createNode("VRayVolumeGrid",
|
||||
name="{}VVGShape".format(label),
|
||||
name="{}Shape".format(label),
|
||||
parent=root)
|
||||
|
||||
# Set attributes
|
||||
cmds.setAttr("{}.inFile".format(grid_node), self.fname, type="string")
|
||||
cmds.setAttr("{}.inReadOffset".format(grid_node),
|
||||
version["startFrames"])
|
||||
# Ensure .currentTime is connected to time1.outTime
|
||||
cmds.connectAttr("time1.outTime", grid_node + ".currentTime")
|
||||
|
||||
# Set path
|
||||
self._set_path(grid_node, self.fname, show_preset_popup=True)
|
||||
|
||||
# Lock the shape node so the user can't delete the transform/shape
|
||||
# as if it was referenced
|
||||
cmds.lockNode(grid_node, lock=True)
|
||||
|
||||
nodes = [root, grid_node]
|
||||
self[:] = nodes
|
||||
|
|
@ -79,3 +156,132 @@ class LoadVDBtoVRay(api.Loader):
|
|||
nodes=nodes,
|
||||
context=context,
|
||||
loader=self.__class__.__name__)
|
||||
|
||||
def _set_path(self, grid_node, path, show_preset_popup=True):
|
||||
|
||||
from openpype.hosts.maya.api.lib import attribute_values
|
||||
from maya import cmds
|
||||
|
||||
def _get_filename_from_folder(path):
|
||||
# Using the sequence of .vdb files we check the frame range, etc.
|
||||
# to set the filename with #### padding.
|
||||
files = sorted(x for x in os.listdir(path) if x.endswith(".vdb"))
|
||||
if not files:
|
||||
raise RuntimeError("Couldn't find .vdb files in: %s" % path)
|
||||
|
||||
if len(files) == 1:
|
||||
# Ensure check for single file is also done in folder
|
||||
fname = files[0]
|
||||
else:
|
||||
# Sequence
|
||||
from avalon.vendor import clique
|
||||
# todo: check support for negative frames as input
|
||||
collections, remainder = clique.assemble(files)
|
||||
assert len(collections) == 1, (
|
||||
"Must find a single image sequence, "
|
||||
"found: %s" % (collections,)
|
||||
)
|
||||
collection = collections[0]
|
||||
|
||||
fname = collection.format('{head}{{padding}}{tail}')
|
||||
padding = collection.padding
|
||||
if padding == 0:
|
||||
# Clique doesn't provide padding if the frame number never
|
||||
# starts with a zero and thus has never any visual padding.
|
||||
# So we fall back to the smallest frame number as padding.
|
||||
padding = min(len(str(i)) for i in collection.indexes)
|
||||
|
||||
# Supply frame/padding with # signs
|
||||
padding_str = "#" * padding
|
||||
fname = fname.format(padding=padding_str)
|
||||
|
||||
return os.path.join(path, fname)
|
||||
|
||||
# The path is either a single file or sequence in a folder so
|
||||
# we do a quick lookup for our files
|
||||
if os.path.isfile(path):
|
||||
path = os.path.dirname(path)
|
||||
path = _get_filename_from_folder(path)
|
||||
|
||||
# Even when not applying a preset V-Ray will reset the 3rd Party
|
||||
# Channels Mapping of the VRayVolumeGrid when setting the .inPath
|
||||
# value. As such we try and preserve the values ourselves.
|
||||
# Reported as ChaosGroup bug ticket: 154-011-2909
|
||||
# todo(roy): Remove when new V-Ray release preserves values
|
||||
original_user_mapping = cmds.getAttr(grid_node + ".usrchmap") or ""
|
||||
|
||||
# Workaround for V-Ray bug: fix lag on path change, see function
|
||||
_fix_duplicate_vvg_callbacks()
|
||||
|
||||
# Suppress preset pop-up if we want.
|
||||
popup_attr = "{0}.inDontOfferPresets".format(grid_node)
|
||||
popup = {popup_attr: not show_preset_popup}
|
||||
with attribute_values(popup):
|
||||
cmds.setAttr(grid_node + ".inPath", path, type="string")
|
||||
|
||||
# Reapply the 3rd Party channels user mapping when no preset popup
|
||||
# was shown to the user
|
||||
if not show_preset_popup:
|
||||
channels = cmds.getAttr(grid_node + ".usrchmapallch").split(";")
|
||||
channels = set(channels) # optimize lookup
|
||||
restored_mapping = ""
|
||||
for entry in original_user_mapping.split(";"):
|
||||
if not entry:
|
||||
# Ignore empty entries
|
||||
continue
|
||||
|
||||
# If 3rd Party Channels selection channel still exists then
|
||||
# add it again.
|
||||
index, channel = entry.split(",")
|
||||
attr = THIRD_PARTY_CHANNELS.get(int(index),
|
||||
# Fallback for when a mapping
|
||||
# was set that is not in the
|
||||
# documentation
|
||||
"???")
|
||||
if channel in channels:
|
||||
restored_mapping += entry + ";"
|
||||
else:
|
||||
self.log.warning("Can't preserve '%s' mapping due to "
|
||||
"missing channel '%s' on node: "
|
||||
"%s" % (attr, channel, grid_node))
|
||||
|
||||
if restored_mapping:
|
||||
cmds.setAttr(grid_node + ".usrchmap",
|
||||
restored_mapping,
|
||||
type="string")
|
||||
|
||||
def update(self, container, representation):
|
||||
|
||||
path = api.get_representation_path(representation)
|
||||
|
||||
# Find VRayVolumeGrid
|
||||
members = cmds.sets(container['objectName'], query=True)
|
||||
grid_nodes = cmds.ls(members, type="VRayVolumeGrid", long=True)
|
||||
assert len(grid_nodes) > 0, "This is a bug"
|
||||
|
||||
# Update the VRayVolumeGrid
|
||||
for grid_node in grid_nodes:
|
||||
self._set_path(grid_node, path=path, show_preset_popup=False)
|
||||
|
||||
# Update container representation
|
||||
cmds.setAttr(container["objectName"] + ".representation",
|
||||
str(representation["_id"]),
|
||||
type="string")
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def remove(self, container):
|
||||
|
||||
# Get all members of the avalon container, ensure they are unlocked
|
||||
# and delete everything
|
||||
members = cmds.sets(container['objectName'], query=True)
|
||||
cmds.lockNode(members, lock=False)
|
||||
cmds.delete([container['objectName']] + members)
|
||||
|
||||
# Clean up the namespace
|
||||
try:
|
||||
cmds.namespace(removeNamespace=container['namespace'],
|
||||
deleteNamespaceContent=True)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ from openpype.api import get_project_settings
|
|||
class VRayProxyLoader(api.Loader):
|
||||
"""Load VRay Proxy with Alembic or VrayMesh."""
|
||||
|
||||
families = ["vrayproxy"]
|
||||
representations = ["vrmesh"]
|
||||
families = ["vrayproxy", "model", "pointcache", "animation"]
|
||||
representations = ["vrmesh", "abc"]
|
||||
|
||||
label = "Import VRay Proxy"
|
||||
order = -10
|
||||
|
|
|
|||
31
openpype/hosts/maya/plugins/publish/clean_nodes.py
Normal file
31
openpype/hosts/maya/plugins/publish/clean_nodes.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Cleanup leftover nodes."""
|
||||
from maya import cmds # noqa
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CleanNodesUp(pyblish.api.InstancePlugin):
|
||||
"""Cleans up the staging directory after a successful publish.
|
||||
|
||||
This will also clean published renders and delete their parent directories.
|
||||
|
||||
"""
|
||||
|
||||
order = pyblish.api.IntegratorOrder + 10
|
||||
label = "Clean Nodes"
|
||||
optional = True
|
||||
active = True
|
||||
|
||||
def process(self, instance):
|
||||
if not instance.data.get("cleanNodes"):
|
||||
self.log.info("Nothing to clean.")
|
||||
return
|
||||
|
||||
nodes_to_clean = instance.data.pop("cleanNodes", [])
|
||||
self.log.info("Removing {} nodes".format(len(nodes_to_clean)))
|
||||
for node in nodes_to_clean:
|
||||
try:
|
||||
cmds.delete(node)
|
||||
except ValueError:
|
||||
# object might be already deleted, don't complain about it
|
||||
pass
|
||||
|
|
@ -4,25 +4,31 @@ import pyblish.api
|
|||
|
||||
|
||||
class CollectUnrealStaticMesh(pyblish.api.InstancePlugin):
|
||||
"""Collect unreal static mesh
|
||||
"""Collect Unreal Static Mesh
|
||||
|
||||
Ensures always only a single frame is extracted (current frame). This
|
||||
also sets correct FBX options for later extraction.
|
||||
|
||||
Note:
|
||||
This is a workaround so that the `pype.model` family can use the
|
||||
same pointcache extractor implementation as animation and pointcaches.
|
||||
This always enforces the "current" frame to be published.
|
||||
|
||||
"""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.2
|
||||
label = "Collect Model Data"
|
||||
label = "Collect Unreal Static Meshes"
|
||||
families = ["unrealStaticMesh"]
|
||||
|
||||
def process(self, instance):
|
||||
# add fbx family to trigger fbx extractor
|
||||
instance.data["families"].append("fbx")
|
||||
# take the name from instance (without the `S_` prefix)
|
||||
instance.data["staticMeshCombinedName"] = instance.name[2:]
|
||||
|
||||
geometry_set = [i for i in instance if i == "geometry_SET"]
|
||||
instance.data["membersToCombine"] = cmds.sets(
|
||||
geometry_set, query=True)
|
||||
|
||||
collision_set = [i for i in instance if i == "collisions_SET"]
|
||||
instance.data["collisionMembers"] = cmds.sets(
|
||||
collision_set, query=True)
|
||||
|
||||
# set fbx overrides on instance
|
||||
instance.data["smoothingGroups"] = True
|
||||
instance.data["smoothMesh"] = True
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
from maya import cmds
|
||||
import maya.mel as mel
|
||||
from maya import cmds # noqa
|
||||
import maya.mel as mel # noqa
|
||||
from openpype.hosts.maya.api.lib import root_parent
|
||||
|
||||
import pyblish.api
|
||||
import avalon.maya
|
||||
|
|
@ -192,10 +194,7 @@ class ExtractFBX(openpype.api.Extractor):
|
|||
if isinstance(value, bool):
|
||||
value = str(value).lower()
|
||||
|
||||
template = "FBXExport{0} -v {1}"
|
||||
if key == "UpAxis":
|
||||
template = "FBXExport{0} {1}"
|
||||
|
||||
template = "FBXExport{0} {1}" if key == "UpAxis" else "FBXExport{0} -v {1}" # noqa
|
||||
cmd = template.format(key, value)
|
||||
self.log.info(cmd)
|
||||
mel.eval(cmd)
|
||||
|
|
@ -205,9 +204,16 @@ class ExtractFBX(openpype.api.Extractor):
|
|||
mel.eval("FBXExportGenerateLog -v false")
|
||||
|
||||
# Export
|
||||
with avalon.maya.maintained_selection():
|
||||
cmds.select(members, r=1, noExpand=True)
|
||||
mel.eval('FBXExport -f "{}" -s'.format(path))
|
||||
if "unrealStaticMesh" in instance.data["families"]:
|
||||
with avalon.maya.maintained_selection():
|
||||
with root_parent(members):
|
||||
self.log.info("Un-parenting: {}".format(members))
|
||||
cmds.select(members, r=1, noExpand=True)
|
||||
mel.eval('FBXExport -f "{}" -s'.format(path))
|
||||
else:
|
||||
with avalon.maya.maintained_selection():
|
||||
cmds.select(members, r=1, noExpand=True)
|
||||
mel.eval('FBXExport -f "{}" -s'.format(path))
|
||||
|
||||
if "representations" not in instance.data:
|
||||
instance.data["representations"] = []
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Create Unreal Static Mesh data to be extracted as FBX."""
|
||||
import openpype.api
|
||||
import pyblish.api
|
||||
from maya import cmds # noqa
|
||||
|
||||
|
||||
class ExtractUnrealStaticMesh(openpype.api.Extractor):
|
||||
"""Extract FBX from Maya. """
|
||||
|
||||
order = pyblish.api.ExtractorOrder - 0.1
|
||||
label = "Extract Unreal Static Mesh"
|
||||
families = ["unrealStaticMesh"]
|
||||
|
||||
def process(self, instance):
|
||||
to_combine = instance.data.get("membersToCombine")
|
||||
static_mesh_name = instance.data.get("staticMeshCombinedName")
|
||||
self.log.info(
|
||||
"merging {} into {}".format(
|
||||
" + ".join(to_combine), static_mesh_name))
|
||||
duplicates = cmds.duplicate(to_combine, ic=True)
|
||||
cmds.polyUnite(
|
||||
*duplicates,
|
||||
n=static_mesh_name, ch=False)
|
||||
|
||||
if not instance.data.get("cleanNodes"):
|
||||
instance.data["cleanNodes"] = []
|
||||
|
||||
instance.data["cleanNodes"].append(static_mesh_name)
|
||||
instance.data["cleanNodes"] += duplicates
|
||||
|
||||
instance.data["setMembers"] = [static_mesh_name]
|
||||
instance.data["setMembers"] += instance.data["collisionMembers"]
|
||||
|
|
@ -30,7 +30,8 @@ class ValidateAssemblyName(pyblish.api.InstancePlugin):
|
|||
descendants = cmds.listRelatives(content_instance,
|
||||
allDescendents=True,
|
||||
fullPath=True) or []
|
||||
descendants = cmds.ls(descendants, noIntermediate=True, long=True)
|
||||
descendants = cmds.ls(
|
||||
descendants, noIntermediate=True, type="transform")
|
||||
content_instance = list(set(content_instance + descendants))
|
||||
assemblies = cmds.ls(content_instance, assemblies=True, long=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from maya import cmds
|
||||
from maya import cmds # noqa
|
||||
import pyblish.api
|
||||
import openpype.api
|
||||
import openpype.hosts.maya.api.action
|
||||
from avalon.api import Session
|
||||
from openpype.api import get_project_settings
|
||||
import re
|
||||
|
||||
|
||||
class ValidateUnrealStaticmeshName(pyblish.api.InstancePlugin):
|
||||
class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin):
|
||||
"""Validate name of Unreal Static Mesh
|
||||
|
||||
Unreals naming convention states that staticMesh should start with `SM`
|
||||
prefix - SM_[Name]_## (Eg. SM_sube_01). This plugin also validates other
|
||||
types of meshes - collision meshes:
|
||||
prefix - SM_[Name]_## (Eg. SM_sube_01).These prefixes can be configured
|
||||
in Settings UI. This plugin also validates other types of
|
||||
meshes - collision meshes:
|
||||
|
||||
UBX_[RenderMeshName]_##:
|
||||
UBX_[RenderMeshName]*:
|
||||
Boxes are created with the Box objects type in
|
||||
Max or with the Cube polygonal primitive in Maya.
|
||||
You cannot move the vertices around or deform it
|
||||
in any way to make it something other than a
|
||||
rectangular prism, or else it will not work.
|
||||
|
||||
UCP_[RenderMeshName]_##:
|
||||
UCP_[RenderMeshName]*:
|
||||
Capsules are created with the Capsule object type.
|
||||
The capsule does not need to have many segments
|
||||
(8 is a good number) at all because it is
|
||||
|
|
@ -29,7 +32,7 @@ class ValidateUnrealStaticmeshName(pyblish.api.InstancePlugin):
|
|||
boxes, you should not move the individual
|
||||
vertices around.
|
||||
|
||||
USP_[RenderMeshName]_##:
|
||||
USP_[RenderMeshName]*:
|
||||
Spheres are created with the Sphere object type.
|
||||
The sphere does not need to have many segments
|
||||
(8 is a good number) at all because it is
|
||||
|
|
@ -37,7 +40,7 @@ class ValidateUnrealStaticmeshName(pyblish.api.InstancePlugin):
|
|||
boxes, you should not move the individual
|
||||
vertices around.
|
||||
|
||||
UCX_[RenderMeshName]_##:
|
||||
UCX_[RenderMeshName]*:
|
||||
Convex objects can be any completely closed
|
||||
convex 3D shape. For example, a box can also be
|
||||
a convex object
|
||||
|
|
@ -52,67 +55,86 @@ class ValidateUnrealStaticmeshName(pyblish.api.InstancePlugin):
|
|||
families = ["unrealStaticMesh"]
|
||||
label = "Unreal StaticMesh Name"
|
||||
actions = [openpype.hosts.maya.api.action.SelectInvalidAction]
|
||||
regex_mesh = r"SM_(?P<renderName>.*)_(\d{2})"
|
||||
regex_collision = r"((UBX)|(UCP)|(USP)|(UCX))_(?P<renderName>.*)_(\d{2})"
|
||||
regex_mesh = r"(?P<renderName>.*))"
|
||||
regex_collision = r"(?P<renderName>.*)"
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
||||
# find out if supplied transform is group or not
|
||||
def is_group(groupName):
|
||||
try:
|
||||
children = cmds.listRelatives(groupName, children=True)
|
||||
for child in children:
|
||||
if not cmds.ls(child, transforms=True):
|
||||
return False
|
||||
invalid = []
|
||||
|
||||
project_settings = get_project_settings(Session["AVALON_PROJECT"])
|
||||
collision_prefixes = (
|
||||
project_settings
|
||||
["maya"]
|
||||
["create"]
|
||||
["CreateUnrealStaticMesh"]
|
||||
["collision_prefixes"]
|
||||
)
|
||||
|
||||
combined_geometry_name = instance.data.get(
|
||||
"staticMeshCombinedName", None)
|
||||
if cls.validate_mesh:
|
||||
# compile regex for testing names
|
||||
regex_mesh = "{}{}".format(
|
||||
("_" + cls.static_mesh_prefix) or "", cls.regex_mesh
|
||||
)
|
||||
sm_r = re.compile(regex_mesh)
|
||||
if not sm_r.match(combined_geometry_name):
|
||||
cls.log.error("Mesh doesn't comply with name validation.")
|
||||
return True
|
||||
except Exception:
|
||||
|
||||
if cls.validate_collision:
|
||||
collision_set = instance.data.get("collisionMembers", None)
|
||||
# soft-fail is there are no collision objects
|
||||
if not collision_set:
|
||||
cls.log.warning("No collision objects to validate.")
|
||||
return False
|
||||
|
||||
invalid = []
|
||||
content_instance = instance.data.get("setMembers", None)
|
||||
if not content_instance:
|
||||
cls.log.error("Instance has no nodes!")
|
||||
return True
|
||||
pass
|
||||
descendants = cmds.listRelatives(content_instance,
|
||||
allDescendents=True,
|
||||
fullPath=True) or []
|
||||
regex_collision = "{}{}".format(
|
||||
"(?P<prefix>({}))_".format(
|
||||
"|".join("{0}".format(p) for p in collision_prefixes)
|
||||
) or "", cls.regex_collision
|
||||
)
|
||||
|
||||
descendants = cmds.ls(descendants, noIntermediate=True, long=True)
|
||||
trns = cmds.ls(descendants, long=False, type=('transform'))
|
||||
cl_r = re.compile(regex_collision)
|
||||
|
||||
# filter out groups
|
||||
filter = [node for node in trns if not is_group(node)]
|
||||
|
||||
# compile regex for testing names
|
||||
sm_r = re.compile(cls.regex_mesh)
|
||||
cl_r = re.compile(cls.regex_collision)
|
||||
|
||||
sm_names = []
|
||||
col_names = []
|
||||
for obj in filter:
|
||||
sm_m = sm_r.match(obj)
|
||||
if sm_m is None:
|
||||
# test if it matches collision mesh
|
||||
cl_r = sm_r.match(obj)
|
||||
if cl_r is None:
|
||||
cls.log.error("invalid mesh name on: {}".format(obj))
|
||||
for obj in collision_set:
|
||||
cl_m = cl_r.match(obj)
|
||||
if not cl_m:
|
||||
cls.log.error("{} is invalid".format(obj))
|
||||
invalid.append(obj)
|
||||
else:
|
||||
col_names.append((cl_r.group("renderName"), obj))
|
||||
else:
|
||||
sm_names.append(sm_m.group("renderName"))
|
||||
expected_collision = "{}_{}".format(
|
||||
cl_m.group("prefix"),
|
||||
combined_geometry_name
|
||||
)
|
||||
|
||||
for c_mesh in col_names:
|
||||
if c_mesh[0] not in sm_names:
|
||||
cls.log.error(("collision name {} doesn't match any "
|
||||
"static mesh names.").format(obj))
|
||||
invalid.append(c_mesh[1])
|
||||
if not obj.startswith(expected_collision):
|
||||
|
||||
cls.log.error(
|
||||
"Collision object name doesn't match "
|
||||
"static mesh name"
|
||||
)
|
||||
cls.log.error("{}_{} != {}_{}".format(
|
||||
cl_m.group("prefix"),
|
||||
cl_m.group("renderName"),
|
||||
cl_m.group("prefix"),
|
||||
combined_geometry_name,
|
||||
))
|
||||
invalid.append(obj)
|
||||
|
||||
return invalid
|
||||
|
||||
def process(self, instance):
|
||||
if not self.validate_mesh and not self.validate_collision:
|
||||
self.log.info("Validation of both mesh and collision names"
|
||||
"is disabled.")
|
||||
return
|
||||
|
||||
if not instance.data.get("collisionMembers", None):
|
||||
self.log.info("There are no collision objects to validate")
|
||||
return
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
|
||||
|
|
|
|||
|
|
@ -175,7 +175,8 @@ from .openpype_version import (
|
|||
get_expected_version,
|
||||
is_running_from_build,
|
||||
is_running_staging,
|
||||
is_current_version_studio_latest
|
||||
is_current_version_studio_latest,
|
||||
is_current_version_higher_than_expected
|
||||
)
|
||||
|
||||
terminal = Terminal
|
||||
|
|
|
|||
|
|
@ -195,3 +195,32 @@ def is_current_version_studio_latest():
|
|||
expected_version = get_expected_version()
|
||||
# Check if current version is expected version
|
||||
return current_version == expected_version
|
||||
|
||||
|
||||
def is_current_version_higher_than_expected():
|
||||
"""Is current OpenPype version higher than version defined by studio.
|
||||
|
||||
Returns:
|
||||
None: Can't determine. e.g. when running from code or the build is
|
||||
too old.
|
||||
bool: True when is higher than studio version.
|
||||
"""
|
||||
output = None
|
||||
# Skip if is not running from build or build does not support version
|
||||
# control or path to folder with zip files is not accessible
|
||||
if (
|
||||
not is_running_from_build()
|
||||
or not op_version_control_available()
|
||||
or not openpype_path_is_accessible()
|
||||
):
|
||||
return output
|
||||
|
||||
# Get OpenPypeVersion class
|
||||
OpenPypeVersion = get_OpenPypeVersion()
|
||||
# Convert current version to OpenPypeVersion object
|
||||
current_version = OpenPypeVersion(version=get_openpype_version())
|
||||
|
||||
# Get expected version (from settings)
|
||||
expected_version = get_expected_version()
|
||||
# Check if current version is expected version
|
||||
return current_version > expected_version
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@ from .execute import get_openpype_execute_args
|
|||
from .local_settings import get_local_site_id
|
||||
from .openpype_version import (
|
||||
is_running_from_build,
|
||||
get_openpype_version
|
||||
get_openpype_version,
|
||||
get_build_version
|
||||
)
|
||||
|
||||
|
||||
def get_pype_info():
|
||||
def get_openpype_info():
|
||||
"""Information about currently used Pype process."""
|
||||
executable_args = get_openpype_execute_args()
|
||||
if is_running_from_build():
|
||||
|
|
@ -23,6 +24,7 @@ def get_pype_info():
|
|||
version_type = "code"
|
||||
|
||||
return {
|
||||
"build_verison": get_build_version(),
|
||||
"version": get_openpype_version(),
|
||||
"version_type": version_type,
|
||||
"executable": executable_args[-1],
|
||||
|
|
@ -51,7 +53,7 @@ def get_workstation_info():
|
|||
def get_all_current_info():
|
||||
"""All information about current process in one dictionary."""
|
||||
return {
|
||||
"pype": get_pype_info(),
|
||||
"pype": get_openpype_info(),
|
||||
"workstation": get_workstation_info(),
|
||||
"env": os.environ.copy(),
|
||||
"local_settings": get_local_settings()
|
||||
|
|
|
|||
|
|
@ -389,6 +389,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
repre["ext"] = ext
|
||||
template_data["ext"] = ext
|
||||
|
||||
self.log.info(template_name)
|
||||
template = os.path.normpath(
|
||||
anatomy.templates[template_name]["path"])
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class ValidateVersion(pyblish.api.InstancePlugin):
|
|||
order = pyblish.api.ValidatorOrder
|
||||
|
||||
label = "Validate Version"
|
||||
hosts = ["nuke", "maya", "blender", "standalonepublisher"]
|
||||
hosts = ["nuke", "maya", "houdini", "blender", "standalonepublisher"]
|
||||
|
||||
optional = False
|
||||
active = True
|
||||
|
|
|
|||
|
|
@ -27,5 +27,10 @@
|
|||
"path": "{@folder}/{@file}"
|
||||
},
|
||||
"delivery": {},
|
||||
"unreal": {
|
||||
"folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}",
|
||||
"file": "{subset}_{@version}<_{output}><.{@frame}>.{ext}",
|
||||
"path": "{@folder}/{@file}"
|
||||
},
|
||||
"others": {}
|
||||
}
|
||||
|
|
@ -219,7 +219,7 @@
|
|||
"hosts": [],
|
||||
"task_types": [],
|
||||
"tasks": [],
|
||||
"template": "{family}{Variant}"
|
||||
"template": "{family}{variant}"
|
||||
},
|
||||
{
|
||||
"families": [
|
||||
|
|
@ -264,6 +264,17 @@
|
|||
"task_types": [],
|
||||
"tasks": [],
|
||||
"template": "render{Task}{Variant}"
|
||||
},
|
||||
{
|
||||
"families": [
|
||||
"unrealStaticMesh"
|
||||
],
|
||||
"hosts": [
|
||||
"maya"
|
||||
],
|
||||
"task_types": [],
|
||||
"tasks": [],
|
||||
"template": "S_{asset}{variant}"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -297,6 +308,7 @@
|
|||
"family_filter_profiles": [
|
||||
{
|
||||
"hosts": [],
|
||||
"is_include": true,
|
||||
"task_types": [],
|
||||
"filter_families": []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,20 @@
|
|||
"aov_separator": "underscore",
|
||||
"default_render_image_folder": "renders"
|
||||
},
|
||||
"CreateUnrealStaticMesh": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"",
|
||||
"_Main"
|
||||
],
|
||||
"static_mesh_prefix": "S_",
|
||||
"collision_prefixes": [
|
||||
"UBX",
|
||||
"UCP",
|
||||
"USP",
|
||||
"UCX"
|
||||
]
|
||||
},
|
||||
"CreateAnimation": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
|
|
@ -123,12 +137,6 @@
|
|||
"Anim"
|
||||
]
|
||||
},
|
||||
"CreateUnrealStaticMesh": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateVrayProxy": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
|
|
@ -180,6 +188,11 @@
|
|||
"whitelist_native_plugins": false,
|
||||
"authorized_plugins": []
|
||||
},
|
||||
"ValidateUnrealStaticMeshName": {
|
||||
"enabled": true,
|
||||
"validate_mesh": false,
|
||||
"validate_collision": true
|
||||
},
|
||||
"ValidateRenderSettings": {
|
||||
"arnold_render_attributes": [],
|
||||
"vray_render_attributes": [],
|
||||
|
|
@ -197,6 +210,11 @@
|
|||
"regex": "(.*)_(\\d)*_(?P<shader>.*)_(GEO)",
|
||||
"top_level_regex": ".*_GRP"
|
||||
},
|
||||
"ValidateModelContent": {
|
||||
"enabled": true,
|
||||
"optional": false,
|
||||
"validate_top_group": true
|
||||
},
|
||||
"ValidateTransformNamingSuffix": {
|
||||
"enabled": true,
|
||||
"SUFFIX_NAMING_TABLE": {
|
||||
|
|
@ -281,11 +299,6 @@
|
|||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateModelContent": {
|
||||
"enabled": true,
|
||||
"optional": false,
|
||||
"validate_top_group": true
|
||||
},
|
||||
"ValidateNoAnimation": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
"studio_name": "Studio name",
|
||||
"studio_code": "stu",
|
||||
"admin_password": "",
|
||||
"production_version": "",
|
||||
"staging_version": "",
|
||||
"version_check_interval": 5,
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"global": []
|
||||
|
|
@ -19,5 +16,8 @@
|
|||
"windows": [],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
}
|
||||
},
|
||||
"production_version": "",
|
||||
"staging_version": "",
|
||||
"version_check_interval": 5
|
||||
}
|
||||
|
|
@ -143,6 +143,28 @@
|
|||
"label": "Delivery",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "unreal",
|
||||
"label": "Unreal",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "folder",
|
||||
"label": "Folder"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "file",
|
||||
"label": "File"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "path",
|
||||
"label": "Path"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict-modifiable",
|
||||
"key": "others",
|
||||
|
|
|
|||
|
|
@ -267,7 +267,9 @@
|
|||
"label": "Task types"
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
"type": "boolean",
|
||||
"key": "is_include",
|
||||
"label": "Exclude / Include"
|
||||
},
|
||||
{
|
||||
"type": "template",
|
||||
|
|
|
|||
|
|
@ -66,6 +66,38 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "CreateUnrealStaticMesh",
|
||||
"label": "Create Unreal - Static Mesh",
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "defaults",
|
||||
"label": "Default Subsets",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "static_mesh_prefix",
|
||||
"label": "Static Mesh Prefix"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "collision_prefixes",
|
||||
"label": "Collision Mesh Prefixes",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_create_plugin",
|
||||
|
|
@ -118,10 +150,6 @@
|
|||
"key": "CreateSetDress",
|
||||
"label": "Create Set Dress"
|
||||
},
|
||||
{
|
||||
"key": "CreateUnrealStaticMesh",
|
||||
"label": "Create Unreal - Static Mesh"
|
||||
},
|
||||
{
|
||||
"key": "CreateVrayProxy",
|
||||
"label": "Create VRay Proxy"
|
||||
|
|
|
|||
|
|
@ -129,6 +129,31 @@
|
|||
]
|
||||
},
|
||||
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "ValidateUnrealStaticMeshName",
|
||||
"label": "Validate Unreal Static Mesh Name",
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "validate_mesh",
|
||||
"label": "Validate mesh Names "
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "validate_collision",
|
||||
"label": "Validate collision names"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
|
|
|
|||
|
|
@ -10,23 +10,39 @@
|
|||
"multiselection": "{multiselection}",
|
||||
"type": "enum",
|
||||
"enum_items": [
|
||||
{"action": "action"},
|
||||
{"animation": "animation"},
|
||||
{"audio": "audio"},
|
||||
{"camera": "camera"},
|
||||
{"editorial": "editorial"},
|
||||
{"layout": "layout"},
|
||||
{"look": "look"},
|
||||
{"mayaAscii": "mayaAscii"},
|
||||
{"model": "model"},
|
||||
{"pointcache": "pointcache"},
|
||||
{"reference": "reference"},
|
||||
{"render": "render"},
|
||||
{"review": "review"},
|
||||
{"rig": "rig"},
|
||||
{"setdress": "setdress"},
|
||||
{"workfile": "workfile"},
|
||||
{"xgen": "xgen"}
|
||||
{"action": "action"},
|
||||
{"animation": "animation"},
|
||||
{"assembly": "assembly"},
|
||||
{"audio": "audio"},
|
||||
{"backgroundComp": "backgroundComp"},
|
||||
{"backgroundLayout": "backgroundLayout"},
|
||||
{"camera": "camera"},
|
||||
{"editorial": "editorial"},
|
||||
{"gizmo": "gizmo"},
|
||||
{"image": "image"},
|
||||
{"layout": "layout"},
|
||||
{"look": "look"},
|
||||
{"matchmove": "matchmove"},
|
||||
{"mayaScene": "mayaScene"},
|
||||
{"model": "model"},
|
||||
{"nukenodes": "nukenodes"},
|
||||
{"plate": "plate"},
|
||||
{"pointcache": "pointcache"},
|
||||
{"prerender": "prerender"},
|
||||
{"redshiftproxy": "redshiftproxy"},
|
||||
{"reference": "reference"},
|
||||
{"render": "render"},
|
||||
{"review": "review"},
|
||||
{"rig": "rig"},
|
||||
{"setdress": "setdress"},
|
||||
{"take": "take"},
|
||||
{"usdShade": "usdShade"},
|
||||
{"vdbcache": "vdbcache"},
|
||||
{"vrayproxy": "vrayproxy"},
|
||||
{"workfile": "workfile"},
|
||||
{"xgen": "xgen"},
|
||||
{"yetiRig": "yetiRig"},
|
||||
{"yeticache": "yeticache"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -30,36 +30,6 @@
|
|||
{
|
||||
"type": "splitter"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Define explicit OpenPype version that should be used. Keep empty to use latest available version."
|
||||
},
|
||||
{
|
||||
"type": "production-versions-text",
|
||||
"key": "production_version",
|
||||
"label": "Production version"
|
||||
},
|
||||
{
|
||||
"type": "staging-versions-text",
|
||||
"key": "staging_version",
|
||||
"label": "Staging version"
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Trigger validation if running OpenPype is using studio defined version each 'n' <b>minutes</b>. Validation happens in OpenPype tray application."
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "version_check_interval",
|
||||
"label": "Version check interval",
|
||||
"minimum": 0
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
},
|
||||
{
|
||||
"key": "environment",
|
||||
"label": "Environment",
|
||||
|
|
@ -141,12 +111,49 @@
|
|||
"type": "splitter"
|
||||
},
|
||||
{
|
||||
"type": "path",
|
||||
"key": "openpype_path",
|
||||
"label": "Versions Repository",
|
||||
"multiplatform": true,
|
||||
"multipath": true,
|
||||
"require_restart": true
|
||||
"type": "collapsible-wrap",
|
||||
"label": "OpenPype deployment control",
|
||||
"collapsible": false,
|
||||
"children": [
|
||||
{
|
||||
"type": "path",
|
||||
"key": "openpype_path",
|
||||
"label": "Versions Repository",
|
||||
"multiplatform": true,
|
||||
"multipath": true,
|
||||
"require_restart": true
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Define explicit OpenPype version that should be used. Keep empty to use latest available version."
|
||||
},
|
||||
{
|
||||
"type": "production-versions-text",
|
||||
"key": "production_version",
|
||||
"label": "Production version"
|
||||
},
|
||||
{
|
||||
"type": "staging-versions-text",
|
||||
"key": "staging_version",
|
||||
"label": "Staging version"
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Trigger validation if running OpenPype is using studio defined version each 'n' <b>minutes</b>. Validation happens in OpenPype tray application."
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "version_check_interval",
|
||||
"label": "Version check interval",
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ from .commands import (
|
|||
)
|
||||
from .vray_proxies import vrayproxy_assign_look
|
||||
|
||||
|
||||
module = sys.modules[__name__]
|
||||
module.window = None
|
||||
|
||||
|
|
@ -210,7 +209,7 @@ class App(QtWidgets.QWidget):
|
|||
# Assign the first matching look relevant for this asset
|
||||
# (since assigning multiple to the same nodes makes no sense)
|
||||
assign_look = next((subset for subset in item["looks"]
|
||||
if subset["name"] in looks), None)
|
||||
if subset["name"] in looks), None)
|
||||
if not assign_look:
|
||||
self.echo("{} No matching selected "
|
||||
"look for {}".format(prefix, asset))
|
||||
|
|
@ -229,11 +228,14 @@ class App(QtWidgets.QWidget):
|
|||
|
||||
if cmds.pluginInfo('vrayformaya', query=True, loaded=True):
|
||||
self.echo("Getting vray proxy nodes ...")
|
||||
vray_proxies = set(cmds.ls(type="VRayProxy"))
|
||||
nodes = list(set(item["nodes"]).difference(vray_proxies))
|
||||
vray_proxies = set(cmds.ls(type="VRayProxy", long=True))
|
||||
|
||||
if vray_proxies:
|
||||
for vp in vray_proxies:
|
||||
vrayproxy_assign_look(vp, subset_name)
|
||||
if vp in nodes:
|
||||
vrayproxy_assign_look(vp, subset_name)
|
||||
|
||||
nodes = list(set(item["nodes"]).difference(vray_proxies))
|
||||
|
||||
# Assign look
|
||||
if nodes:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from openpype.hosts.maya.api import lib
|
|||
|
||||
from avalon import io, api
|
||||
|
||||
|
||||
from .vray_proxies import get_alembic_ids_cache
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -90,6 +89,7 @@ def get_all_asset_nodes():
|
|||
container_name = container["objectName"]
|
||||
nodes += lib.get_container_members(container_name)
|
||||
|
||||
nodes = list(set(nodes))
|
||||
return nodes
|
||||
|
||||
|
||||
|
|
@ -106,9 +106,19 @@ def create_asset_id_hash(nodes):
|
|||
# iterate over content of reference node
|
||||
if cmds.nodeType(node) == "reference":
|
||||
ref_hashes = create_asset_id_hash(
|
||||
cmds.referenceQuery(node, nodes=True))
|
||||
list(set(cmds.referenceQuery(node, nodes=True, dp=True))))
|
||||
for asset_id, ref_nodes in ref_hashes.items():
|
||||
node_id_hash[asset_id] += ref_nodes
|
||||
elif cmds.pluginInfo('vrayformaya', query=True,
|
||||
loaded=True) and cmds.nodeType(
|
||||
node) == "VRayProxy":
|
||||
path = cmds.getAttr("{}.fileName".format(node))
|
||||
ids = get_alembic_ids_cache(path)
|
||||
for k, _ in ids.items():
|
||||
pid = k.split(":")[0]
|
||||
if node not in node_id_hash[pid]:
|
||||
node_id_hash[pid].append(node)
|
||||
|
||||
else:
|
||||
value = lib.get_id(node)
|
||||
if value is None:
|
||||
|
|
@ -141,22 +151,8 @@ def create_items_from_nodes(nodes):
|
|||
|
||||
id_hashes = create_asset_id_hash(nodes)
|
||||
|
||||
# get ids from alembic
|
||||
if cmds.pluginInfo('vrayformaya', query=True, loaded=True):
|
||||
vray_proxy_nodes = cmds.ls(nodes, type="VRayProxy")
|
||||
for vp in vray_proxy_nodes:
|
||||
path = cmds.getAttr("{}.fileName".format(vp))
|
||||
ids = get_alembic_ids_cache(path)
|
||||
parent_id = {}
|
||||
for k, _ in ids.items():
|
||||
pid = k.split(":")[0]
|
||||
if not parent_id.get(pid):
|
||||
parent_id.update({pid: [vp]})
|
||||
|
||||
print("Adding ids from alembic {}".format(path))
|
||||
id_hashes.update(parent_id)
|
||||
|
||||
if not id_hashes:
|
||||
log.warning("No id hashes")
|
||||
return asset_view_items
|
||||
|
||||
for _id, id_nodes in id_hashes.items():
|
||||
|
|
|
|||
|
|
@ -41,7 +41,12 @@ def get_alembic_paths_by_property(filename, attr, verbose=False):
|
|||
filename = filename.replace("\\", "/")
|
||||
filename = str(filename) # path must be string
|
||||
|
||||
archive = alembic.Abc.IArchive(filename)
|
||||
try:
|
||||
archive = alembic.Abc.IArchive(filename)
|
||||
except RuntimeError:
|
||||
# invalid alembic file - probably vrmesh
|
||||
log.warning("{} is not an alembic file".format(filename))
|
||||
return {}
|
||||
root = archive.getTop()
|
||||
|
||||
iterator = list(root.children)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ MODELINDEX = QtCore.QModelIndex()
|
|||
|
||||
|
||||
class AssetOutliner(QtWidgets.QWidget):
|
||||
|
||||
refreshed = QtCore.Signal()
|
||||
selection_changed = QtCore.Signal()
|
||||
|
||||
|
|
@ -84,14 +83,13 @@ class AssetOutliner(QtWidgets.QWidget):
|
|||
"""
|
||||
|
||||
selection_model = self.view.selectionModel()
|
||||
items = [row.data(TreeModel.ItemRole) for row in
|
||||
selection_model.selectedRows(0)]
|
||||
|
||||
return items
|
||||
return [row.data(TreeModel.ItemRole)
|
||||
for row in selection_model.selectedRows(0)]
|
||||
|
||||
def get_all_assets(self):
|
||||
"""Add all items from the current scene"""
|
||||
|
||||
items = []
|
||||
with lib.preserve_expanded_rows(self.view):
|
||||
with lib.preserve_selection(self.view):
|
||||
self.clear()
|
||||
|
|
@ -118,7 +116,7 @@ class AssetOutliner(QtWidgets.QWidget):
|
|||
|
||||
# Collect all nodes by hash (optimization)
|
||||
if not selection:
|
||||
nodes = cmds.ls(dag=True, long=True)
|
||||
nodes = cmds.ls(dag=True, long=True)
|
||||
else:
|
||||
nodes = commands.get_selected_nodes()
|
||||
id_nodes = commands.create_asset_id_hash(nodes)
|
||||
|
|
@ -187,7 +185,6 @@ class AssetOutliner(QtWidgets.QWidget):
|
|||
|
||||
|
||||
class LookOutliner(QtWidgets.QWidget):
|
||||
|
||||
menu_apply_action = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
|
|
@ -237,9 +234,7 @@ class LookOutliner(QtWidgets.QWidget):
|
|||
"""
|
||||
|
||||
datas = [i.data(TreeModel.ItemRole) for i in self.view.get_indices()]
|
||||
items = [d for d in datas if d is not None] # filter Nones
|
||||
|
||||
return items
|
||||
return [d for d in datas if d is not None]
|
||||
|
||||
def right_mouse_menu(self, pos):
|
||||
"""Build RMB menu for look view"""
|
||||
|
|
|
|||
|
|
@ -92,8 +92,7 @@ class CollapsibleWrapper(WrapperWidget):
|
|||
self.content_layout = content_layout
|
||||
|
||||
if self.collapsible:
|
||||
if not self.collapsed:
|
||||
body_widget.toggle_content()
|
||||
body_widget.toggle_content(self.collapsed)
|
||||
else:
|
||||
body_widget.hide_toolbox(hide_content=False)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from openpype.api import resources
|
|||
from openpype.settings.lib import get_local_settings
|
||||
from openpype.lib.pype_info import (
|
||||
get_all_current_info,
|
||||
get_pype_info,
|
||||
get_openpype_info,
|
||||
get_workstation_info,
|
||||
extract_pype_info_to_file
|
||||
)
|
||||
|
|
@ -426,7 +426,7 @@ class PypeInfoSubWidget(QtWidgets.QWidget):
|
|||
"""Create widget with information about OpenPype application."""
|
||||
|
||||
# Get pype info data
|
||||
pype_info = get_pype_info()
|
||||
pype_info = get_openpype_info()
|
||||
# Modify version key/values
|
||||
version_value = "{} ({})".format(
|
||||
pype_info.pop("version", self.not_applicable),
|
||||
|
|
@ -435,13 +435,20 @@ class PypeInfoSubWidget(QtWidgets.QWidget):
|
|||
pype_info["version_value"] = version_value
|
||||
# Prepare lable mapping
|
||||
key_label_mapping = {
|
||||
"version_value": "OpenPype version:",
|
||||
"version_value": "Running version:",
|
||||
"build_verison": "Build version:",
|
||||
"executable": "OpenPype executable:",
|
||||
"pype_root": "OpenPype location:",
|
||||
"mongo_url": "OpenPype Mongo URL:"
|
||||
}
|
||||
# Prepare keys order
|
||||
keys_order = ["version_value", "executable", "pype_root", "mongo_url"]
|
||||
keys_order = [
|
||||
"version_value",
|
||||
"build_verison",
|
||||
"executable",
|
||||
"pype_root",
|
||||
"mongo_url"
|
||||
]
|
||||
for key in pype_info.keys():
|
||||
if key not in keys_order:
|
||||
keys_order.append(key)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from openpype.lib import (
|
|||
get_openpype_execute_args,
|
||||
op_version_control_available,
|
||||
is_current_version_studio_latest,
|
||||
is_current_version_higher_than_expected,
|
||||
is_running_from_build,
|
||||
is_running_staging,
|
||||
get_expected_version,
|
||||
|
|
@ -84,7 +85,7 @@ class VersionDialog(QtWidgets.QDialog):
|
|||
|
||||
def __init__(self, parent=None):
|
||||
super(VersionDialog, self).__init__(parent)
|
||||
self.setWindowTitle("OpenPype update is needed")
|
||||
|
||||
icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
self.setWindowFlags(
|
||||
|
|
@ -104,13 +105,12 @@ class VersionDialog(QtWidgets.QDialog):
|
|||
label_widget.setWordWrap(True)
|
||||
|
||||
top_layout = QtWidgets.QHBoxLayout(top_widget)
|
||||
# top_layout.setContentsMargins(0, 0, 0, 0)
|
||||
top_layout.setSpacing(10)
|
||||
top_layout.addWidget(gift_icon_label, 0, QtCore.Qt.AlignCenter)
|
||||
top_layout.addWidget(label_widget, 1)
|
||||
|
||||
ignore_btn = QtWidgets.QPushButton("Later", self)
|
||||
restart_btn = QtWidgets.QPushButton("Restart && Update", self)
|
||||
ignore_btn = QtWidgets.QPushButton(self)
|
||||
restart_btn = QtWidgets.QPushButton(self)
|
||||
restart_btn.setObjectName("TrayRestartButton")
|
||||
|
||||
btns_layout = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -127,7 +127,12 @@ class VersionDialog(QtWidgets.QDialog):
|
|||
restart_btn.clicked.connect(self._on_reset)
|
||||
|
||||
self._label_widget = label_widget
|
||||
self._gift_icon_label = gift_icon_label
|
||||
self._ignore_btn = ignore_btn
|
||||
self._restart_btn = restart_btn
|
||||
|
||||
self._restart_accepted = False
|
||||
self._current_is_higher = False
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
|
|
@ -152,15 +157,41 @@ class VersionDialog(QtWidgets.QDialog):
|
|||
|
||||
def closeEvent(self, event):
|
||||
super().closeEvent(event)
|
||||
if not self._restart_accepted:
|
||||
self.ignore_requested.emit()
|
||||
if self._restart_accepted or self._current_is_higher:
|
||||
return
|
||||
# Trigger ignore requested only if restart was not clicked and current
|
||||
# version is lower
|
||||
self.ignore_requested.emit()
|
||||
|
||||
def update_versions(self, current_version, expected_version):
|
||||
message = (
|
||||
"Running OpenPype version is <b>{}</b>."
|
||||
" Your production has been updated to version <b>{}</b>."
|
||||
).format(str(current_version), str(expected_version))
|
||||
self._label_widget.setText(message)
|
||||
def update_versions(
|
||||
self, current_version, expected_version, current_is_higher
|
||||
):
|
||||
if not current_is_higher:
|
||||
title = "OpenPype update is needed"
|
||||
label_message = (
|
||||
"Running OpenPype version is <b>{}</b>."
|
||||
" Your production has been updated to version <b>{}</b>."
|
||||
).format(str(current_version), str(expected_version))
|
||||
ignore_label = "Later"
|
||||
restart_label = "Restart && Update"
|
||||
else:
|
||||
title = "OpenPype version is higher"
|
||||
label_message = (
|
||||
"Running OpenPype version is <b>{}</b>."
|
||||
" Your production uses version <b>{}</b>."
|
||||
).format(str(current_version), str(expected_version))
|
||||
ignore_label = "Ignore"
|
||||
restart_label = "Restart && Change"
|
||||
|
||||
self.setWindowTitle(title)
|
||||
|
||||
self._current_is_higher = current_is_higher
|
||||
|
||||
self._gift_icon_label.setVisible(not current_is_higher)
|
||||
|
||||
self._label_widget.setText(label_message)
|
||||
self._ignore_btn.setText(ignore_label)
|
||||
self._restart_btn.setText(restart_label)
|
||||
|
||||
def _on_ignore(self):
|
||||
self.reject()
|
||||
|
|
@ -227,6 +258,10 @@ class TrayManager:
|
|||
|
||||
def validate_openpype_version(self):
|
||||
using_requested = is_current_version_studio_latest()
|
||||
# TODO Handle situations when version can't be detected
|
||||
if using_requested is None:
|
||||
using_requested = True
|
||||
|
||||
self._restart_action.setVisible(not using_requested)
|
||||
if using_requested:
|
||||
if (
|
||||
|
|
@ -247,15 +282,17 @@ class TrayManager:
|
|||
|
||||
expected_version = get_expected_version()
|
||||
current_version = get_openpype_version()
|
||||
current_is_higher = is_current_version_higher_than_expected()
|
||||
|
||||
self._version_dialog.update_versions(
|
||||
current_version, expected_version
|
||||
current_version, expected_version, current_is_higher
|
||||
)
|
||||
self._version_dialog.show()
|
||||
self._version_dialog.raise_()
|
||||
self._version_dialog.activateWindow()
|
||||
|
||||
def _restart_and_install(self):
|
||||
self.restart()
|
||||
self.restart(use_expected_version=True)
|
||||
|
||||
def _outdated_version_ignored(self):
|
||||
self.show_tray_message(
|
||||
|
|
@ -328,8 +365,8 @@ class TrayManager:
|
|||
self.main_thread_timer = main_thread_timer
|
||||
|
||||
version_check_timer = QtCore.QTimer()
|
||||
version_check_timer.timeout.connect(self._on_version_check_timer)
|
||||
if self._version_check_interval > 0:
|
||||
version_check_timer.timeout.connect(self._on_version_check_timer)
|
||||
version_check_timer.setInterval(self._version_check_interval)
|
||||
version_check_timer.start()
|
||||
self._version_check_timer = version_check_timer
|
||||
|
|
@ -341,6 +378,9 @@ class TrayManager:
|
|||
|
||||
def _startup_validations(self):
|
||||
"""Run possible startup validations."""
|
||||
# Trigger version validation on start
|
||||
self._version_check_timer.timeout.emit()
|
||||
|
||||
self._validate_settings_defaults()
|
||||
|
||||
def _validate_settings_defaults(self):
|
||||
|
|
@ -429,12 +469,18 @@ class TrayManager:
|
|||
self._restart_action = restart_action
|
||||
|
||||
def _on_restart_action(self):
|
||||
self.restart()
|
||||
self.restart(use_expected_version=True)
|
||||
|
||||
def restart(self, reset_version=True):
|
||||
def restart(self, use_expected_version=False, reset_version=False):
|
||||
"""Restart Tray tool.
|
||||
|
||||
First creates new process with same argument and close current tray.
|
||||
|
||||
Args:
|
||||
use_expected_version(bool): OpenPype version is set to expected
|
||||
version.
|
||||
reset_version(bool): OpenPype version is cleaned up so igniters
|
||||
logic will decide which version will be used.
|
||||
"""
|
||||
args = get_openpype_execute_args()
|
||||
kwargs = {
|
||||
|
|
@ -448,6 +494,15 @@ class TrayManager:
|
|||
if args[-1] == additional_args[0]:
|
||||
additional_args.pop(0)
|
||||
|
||||
if use_expected_version:
|
||||
expected_version = get_expected_version()
|
||||
if expected_version is not None:
|
||||
reset_version = False
|
||||
kwargs["env"]["OPENPYPE_VERSION"] = str(expected_version)
|
||||
else:
|
||||
# Trigger reset of version if expected version was not found
|
||||
reset_version = True
|
||||
|
||||
# Pop OPENPYPE_VERSION
|
||||
if reset_version:
|
||||
# Add staging flag if was running from staging
|
||||
|
|
|
|||
|
|
@ -231,6 +231,7 @@ class FamilyConfigCache:
|
|||
self.dbcon = dbcon
|
||||
self.family_configs = {}
|
||||
self._family_filters_set = False
|
||||
self._family_filters_is_include = True
|
||||
self._require_refresh = True
|
||||
|
||||
@classmethod
|
||||
|
|
@ -252,7 +253,7 @@ class FamilyConfigCache:
|
|||
"icon": self.default_icon()
|
||||
}
|
||||
if self._family_filters_set:
|
||||
item["state"] = False
|
||||
item["state"] = not self._family_filters_is_include
|
||||
return item
|
||||
|
||||
def refresh(self, force=False):
|
||||
|
|
@ -316,20 +317,23 @@ class FamilyConfigCache:
|
|||
matching_item = filter_profiles(profiles, profiles_filter)
|
||||
|
||||
families = []
|
||||
is_include = True
|
||||
if matching_item:
|
||||
families = matching_item["filter_families"]
|
||||
is_include = matching_item["is_include"]
|
||||
|
||||
if not families:
|
||||
return
|
||||
|
||||
self._family_filters_set = True
|
||||
self._family_filters_is_include = is_include
|
||||
|
||||
# Replace icons with a Qt icon we can use in the user interfaces
|
||||
for family in families:
|
||||
family_info = {
|
||||
"name": family,
|
||||
"icon": self.default_icon(),
|
||||
"state": True
|
||||
"state": is_include
|
||||
}
|
||||
|
||||
self.family_configs[family] = family_info
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.8.0-nightly.5"
|
||||
__version__ = "3.8.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.8.0-nightly.5" # OpenPype
|
||||
version = "3.8.0" # OpenPype
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit ffe9e910f1f382e222d457d8e4a8426c41ed43ae
|
||||
Subproject commit 159d2f23e4c79c04dfac57b68d2ee6ac67adec1b
|
||||
|
|
@ -130,8 +130,8 @@ main () {
|
|||
fi
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Generating zip from current sources ..."
|
||||
PYTHONPATH="$openpype_root:$PYTHONPATH"
|
||||
OPENPYPE_ROOT="$openpype_root"
|
||||
export PYTHONPATH="$openpype_root:$PYTHONPATH"
|
||||
export OPENPYPE_ROOT="$openpype_root"
|
||||
"$POETRY_HOME/bin/poetry" run python3 "$openpype_root/tools/create_zip.py" "$@"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import TabItem from '@theme/TabItem';
|
|||
|
||||
Settings applicable to the full studio.
|
||||
|
||||

|
||||
|
||||
**`Studio Name`** - Full name of the studio (can be used as variable on some places)
|
||||
|
||||
**`Studio Code`** - Studio acronym or a short code (can be used as variable on some places)
|
||||
|
|
@ -24,10 +26,27 @@ as a naive barier to prevent artists from accidental setting changes.
|
|||
**`Disk mapping`** - Platform dependent configuration for mapping of virtual disk(s) on an artist's OpenPype machines before OP starts up.
|
||||
Uses `subst` command, if configured volume character in `Destination` field already exists, no re-mapping is done for that character(volume).
|
||||
|
||||
### OpenPype deployment control
|
||||
**`Versions Repository`** - Location where automatic update mechanism searches for zip files with
|
||||
OpenPype update packages. To read more about preparing OpenPype for automatic updates go to [Admin Distribute docs](admin_distribute#2-openpype-codebase)
|
||||
|
||||

|
||||
**`Production version`** - Define what is current production version. When value is not set then latest version available in versions repository is resolved as production version.
|
||||
|
||||
**`Staging version`** - Define what is current staging version. When value is not set then latest staging version available in versions repository is resolved as staging version.
|
||||
|
||||
For more information about Production and Staging go to [Distribute](admin_distribute#staging-vs-production).
|
||||
|
||||
**Production version** and **Staging version** fields will define which version will be used in studio. Filling explicit version will force new OpenPype processes to use it. That gives more control over studio deployment especially when some workstations don't have access to version repository (e.g. remote users). It can be also used to downgrade studio version when newer version have production breaking bug.
|
||||
|
||||
When fields are not filled the latest version in versions repository is used as studio version. That makes updating easier as it is not needed to modify settings but workstations without access to versions repository can't find out which OpenPype version should be used.
|
||||
|
||||
If version repository is not set or is not accessible for workstation the latest available version on workstation is used or version inside build.
|
||||
|
||||
**`Version check interval`** - OpenPype tray application check if currently used OpenPype version is up to date with production/staging version. It is possible to modify how often the validation is triggered in minutes. It is possible to set the interval to `0`. That will turn off version validations but it is not recommend.
|
||||
|
||||
A dialog asking for restart is shown when OpenPype tray application detect that different version should be used.
|
||||

|
||||

|
||||
|
||||
## Modules
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
BIN
website/docs/assets/settings/settings_system_version_update.png
Normal file
BIN
website/docs/assets/settings/settings_system_version_update.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
Loading…
Add table
Add a link
Reference in a new issue