mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Merge branch 'develop' into feature/1235-hiero-unify-otio-workflow-from-resolve
This commit is contained in:
commit
87ad6fed45
65 changed files with 1189 additions and 857 deletions
|
|
@ -6,7 +6,7 @@ class PrePython2Vendor(PreLaunchHook):
|
|||
"""Prepend python 2 dependencies for py2 hosts."""
|
||||
# WARNING This hook will probably be deprecated in OpenPype 3 - kept for test
|
||||
order = 10
|
||||
app_groups = ["hiero", "nuke", "nukex"]
|
||||
app_groups = ["hiero", "nuke", "nukex", "unreal"]
|
||||
|
||||
def execute(self):
|
||||
# Prepare vendor dir path
|
||||
|
|
|
|||
|
|
@ -51,8 +51,37 @@ def set_start_end_frames():
|
|||
"name": asset_name
|
||||
})
|
||||
|
||||
bpy.context.scene.frame_start = asset_doc["data"]["frameStart"]
|
||||
bpy.context.scene.frame_end = asset_doc["data"]["frameEnd"]
|
||||
scene = bpy.context.scene
|
||||
|
||||
# Default scene settings
|
||||
frameStart = scene.frame_start
|
||||
frameEnd = scene.frame_end
|
||||
fps = scene.render.fps
|
||||
resolution_x = scene.render.resolution_x
|
||||
resolution_y = scene.render.resolution_y
|
||||
|
||||
# Check if settings are set
|
||||
data = asset_doc.get("data")
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
||||
if data.get("frameStart"):
|
||||
frameStart = data.get("frameStart")
|
||||
if data.get("frameEnd"):
|
||||
frameEnd = data.get("frameEnd")
|
||||
if data.get("fps"):
|
||||
fps = data.get("fps")
|
||||
if data.get("resolutionWidth"):
|
||||
resolution_x = data.get("resolutionWidth")
|
||||
if data.get("resolutionHeight"):
|
||||
resolution_y = data.get("resolutionHeight")
|
||||
|
||||
scene.frame_start = frameStart
|
||||
scene.frame_end = frameEnd
|
||||
scene.render.fps = fps
|
||||
scene.render.resolution_x = resolution_x
|
||||
scene.render.resolution_y = resolution_y
|
||||
|
||||
|
||||
def on_new(arg1, arg2):
|
||||
|
|
|
|||
|
|
@ -292,6 +292,9 @@ class UnrealLayoutLoader(plugin.AssetLoader):
|
|||
icon = "code-fork"
|
||||
color = "orange"
|
||||
|
||||
animation_creator_name = "CreateAnimation"
|
||||
setdress_creator_name = "CreateSetDress"
|
||||
|
||||
def _remove_objects(self, objects):
|
||||
for obj in list(objects):
|
||||
if obj.type == 'ARMATURE':
|
||||
|
|
@ -368,7 +371,7 @@ class UnrealLayoutLoader(plugin.AssetLoader):
|
|||
location.get('z')
|
||||
)
|
||||
obj.rotation_euler = (
|
||||
rotation.get('x'),
|
||||
rotation.get('x') + math.pi / 2,
|
||||
-rotation.get('y'),
|
||||
-rotation.get('z')
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
from typing import List
|
||||
|
||||
import pyblish.api
|
||||
import openpype.hosts.blender.api.action
|
||||
|
||||
|
||||
class ValidateObjectIsInObjectMode(pyblish.api.InstancePlugin):
|
||||
"""Validate that the current object is in Object Mode."""
|
||||
|
||||
order = pyblish.api.ValidatorOrder - 0.01
|
||||
hosts = ["blender"]
|
||||
families = ["model", "rig"]
|
||||
category = "geometry"
|
||||
label = "Object is in Object Mode"
|
||||
actions = [openpype.hosts.blender.api.action.SelectInvalidAction]
|
||||
optional = True
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance) -> List:
|
||||
invalid = []
|
||||
for obj in [obj for obj in instance]:
|
||||
try:
|
||||
if obj.type == 'MESH' or obj.type == 'ARMATURE':
|
||||
# Check if the object is in object mode.
|
||||
if not obj.mode == 'OBJECT':
|
||||
invalid.append(obj)
|
||||
except Exception:
|
||||
continue
|
||||
return invalid
|
||||
|
||||
def process(self, instance):
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise RuntimeError(
|
||||
f"Object found in instance is not in Object Mode: {invalid}")
|
||||
|
|
@ -11,7 +11,9 @@ from .api.pipeline import (
|
|||
update_container,
|
||||
publish,
|
||||
launch_workfiles_app,
|
||||
maintained_selection
|
||||
maintained_selection,
|
||||
remove_instance,
|
||||
list_instances
|
||||
)
|
||||
|
||||
from .api.lib import (
|
||||
|
|
@ -73,6 +75,8 @@ __all__ = [
|
|||
"publish",
|
||||
"launch_workfiles_app",
|
||||
"maintained_selection",
|
||||
"remove_instance",
|
||||
"list_instances",
|
||||
|
||||
# utils
|
||||
"setup",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ from avalon.tools import (
|
|||
creator,
|
||||
loader,
|
||||
sceneinventory,
|
||||
libraryloader
|
||||
libraryloader,
|
||||
subsetmanager
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -64,8 +65,9 @@ class OpenPypeMenu(QtWidgets.QWidget):
|
|||
publish_btn = QtWidgets.QPushButton("Publish ...", self)
|
||||
load_btn = QtWidgets.QPushButton("Load ...", self)
|
||||
inventory_btn = QtWidgets.QPushButton("Inventory ...", self)
|
||||
subsetm_btn = QtWidgets.QPushButton("Subset Manager ...", self)
|
||||
libload_btn = QtWidgets.QPushButton("Library ...", self)
|
||||
# rename_btn = QtWidgets.QPushButton("Rename ...", self)
|
||||
# rename_btn = QtWidgets.QPushButton("Rename", self)
|
||||
# set_colorspace_btn = QtWidgets.QPushButton(
|
||||
# "Set colorspace from presets", self
|
||||
# )
|
||||
|
|
@ -81,6 +83,7 @@ class OpenPypeMenu(QtWidgets.QWidget):
|
|||
layout.addWidget(publish_btn)
|
||||
layout.addWidget(load_btn)
|
||||
layout.addWidget(inventory_btn)
|
||||
layout.addWidget(subsetm_btn)
|
||||
|
||||
layout.addWidget(Spacer(15, self))
|
||||
|
||||
|
|
@ -102,6 +105,7 @@ class OpenPypeMenu(QtWidgets.QWidget):
|
|||
publish_btn.clicked.connect(self.on_publish_clicked)
|
||||
load_btn.clicked.connect(self.on_load_clicked)
|
||||
inventory_btn.clicked.connect(self.on_inventory_clicked)
|
||||
subsetm_btn.clicked.connect(self.on_subsetm_clicked)
|
||||
libload_btn.clicked.connect(self.on_libload_clicked)
|
||||
# rename_btn.clicked.connect(self.on_rename_clicked)
|
||||
# set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked)
|
||||
|
|
@ -127,6 +131,10 @@ class OpenPypeMenu(QtWidgets.QWidget):
|
|||
print("Clicked Inventory")
|
||||
sceneinventory.show()
|
||||
|
||||
def on_subsetm_clicked(self):
|
||||
print("Clicked Subset Manager")
|
||||
subsetmanager.show()
|
||||
|
||||
def on_libload_clicked(self):
|
||||
print("Clicked Library")
|
||||
libraryloader.show()
|
||||
|
|
|
|||
|
|
@ -258,3 +258,51 @@ def on_pyblish_instance_toggled(instance, old_value, new_value):
|
|||
# Whether instances should be passthrough based on new value
|
||||
timeline_item = instance.data["item"]
|
||||
set_publish_attribute(timeline_item, new_value)
|
||||
|
||||
|
||||
def remove_instance(instance):
|
||||
"""Remove instance marker from track item."""
|
||||
instance_id = instance.get("uuid")
|
||||
|
||||
selected_timeline_items = lib.get_current_timeline_items(
|
||||
filter=True, selecting_color=lib.publish_clip_color)
|
||||
|
||||
found_ti = None
|
||||
for timeline_item_data in selected_timeline_items:
|
||||
timeline_item = timeline_item_data["clip"]["item"]
|
||||
|
||||
# get openpype tag data
|
||||
tag_data = lib.get_timeline_item_pype_tag(timeline_item)
|
||||
_ti_id = tag_data.get("uuid")
|
||||
if _ti_id == instance_id:
|
||||
found_ti = timeline_item
|
||||
break
|
||||
|
||||
if found_ti is None:
|
||||
return
|
||||
|
||||
# removing instance by marker color
|
||||
print(f"Removing instance: {found_ti.GetName()}")
|
||||
found_ti.DeleteMarkersByColor(lib.pype_marker_color)
|
||||
|
||||
|
||||
def list_instances():
|
||||
"""List all created instances from current workfile."""
|
||||
listed_instances = []
|
||||
selected_timeline_items = lib.get_current_timeline_items(
|
||||
filter=True, selecting_color=lib.publish_clip_color)
|
||||
|
||||
for timeline_item_data in selected_timeline_items:
|
||||
timeline_item = timeline_item_data["clip"]["item"]
|
||||
ti_name = timeline_item.GetName().split(".")[0]
|
||||
|
||||
# get openpype tag data
|
||||
tag_data = lib.get_timeline_item_pype_tag(timeline_item)
|
||||
|
||||
if tag_data:
|
||||
asset = tag_data.get("asset")
|
||||
subset = tag_data.get("subset")
|
||||
tag_data["label"] = f"{ti_name} [{asset}-{subset}]"
|
||||
listed_instances.append(tag_data)
|
||||
|
||||
return listed_instances
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import re
|
||||
import uuid
|
||||
from avalon import api
|
||||
import openpype.api as pype
|
||||
from openpype.hosts import resolve
|
||||
|
|
@ -697,13 +698,13 @@ class PublishClip:
|
|||
Populating the tag data into internal variable self.tag_data
|
||||
"""
|
||||
# define vertical sync attributes
|
||||
master_layer = True
|
||||
hero_track = True
|
||||
self.review_layer = ""
|
||||
if self.vertical_sync:
|
||||
# check if track name is not in driving layer
|
||||
if self.track_name not in self.driving_layer:
|
||||
# if it is not then define vertical sync as None
|
||||
master_layer = False
|
||||
hero_track = False
|
||||
|
||||
# increasing steps by index of rename iteration
|
||||
self.count_steps *= self.rename_index
|
||||
|
|
@ -717,7 +718,7 @@ class PublishClip:
|
|||
self.tag_data[_k] = _v["value"]
|
||||
|
||||
# driving layer is set as positive match
|
||||
if master_layer or self.vertical_sync:
|
||||
if hero_track or self.vertical_sync:
|
||||
# mark review layer
|
||||
if self.review_track and (
|
||||
self.review_track not in self.review_track_default):
|
||||
|
|
@ -751,35 +752,39 @@ class PublishClip:
|
|||
hierarchy_formating_data
|
||||
)
|
||||
|
||||
tag_hierarchy_data.update({"masterLayer": True})
|
||||
if master_layer and self.vertical_sync:
|
||||
# tag_hierarchy_data.update({"masterLayer": True})
|
||||
tag_hierarchy_data.update({"heroTrack": True})
|
||||
if hero_track and self.vertical_sync:
|
||||
self.vertical_clip_match.update({
|
||||
(self.clip_in, self.clip_out): tag_hierarchy_data
|
||||
})
|
||||
|
||||
if not master_layer and self.vertical_sync:
|
||||
if not hero_track and self.vertical_sync:
|
||||
# driving layer is set as negative match
|
||||
for (_in, _out), master_data in self.vertical_clip_match.items():
|
||||
master_data.update({"masterLayer": False})
|
||||
for (_in, _out), hero_data in self.vertical_clip_match.items():
|
||||
hero_data.update({"heroTrack": False})
|
||||
if _in == self.clip_in and _out == self.clip_out:
|
||||
data_subset = master_data["subset"]
|
||||
# add track index in case duplicity of names in master data
|
||||
data_subset = hero_data["subset"]
|
||||
# add track index in case duplicity of names in hero data
|
||||
if self.subset in data_subset:
|
||||
master_data["subset"] = self.subset + str(
|
||||
hero_data["subset"] = self.subset + str(
|
||||
self.track_index)
|
||||
# in case track name and subset name is the same then add
|
||||
if self.subset_name == self.track_name:
|
||||
master_data["subset"] = self.subset
|
||||
hero_data["subset"] = self.subset
|
||||
# assing data to return hierarchy data to tag
|
||||
tag_hierarchy_data = master_data
|
||||
tag_hierarchy_data = hero_data
|
||||
|
||||
# add data to return data dict
|
||||
self.tag_data.update(tag_hierarchy_data)
|
||||
|
||||
if master_layer and self.review_layer:
|
||||
# add uuid to tag data
|
||||
self.tag_data["uuid"] = str(uuid.uuid4())
|
||||
|
||||
# add review track only to hero track
|
||||
if hero_track and self.review_layer:
|
||||
self.tag_data.update({"reviewTrack": self.review_layer})
|
||||
|
||||
|
||||
def _solve_tag_hierarchy_data(self, hierarchy_formating_data):
|
||||
""" Solve tag data from hierarchy data and templates. """
|
||||
# fill up clip name and hierarchy keys
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ class CreateShotClip(resolve.Creator):
|
|||
"vSyncTrack": {
|
||||
"value": gui_tracks, # noqa
|
||||
"type": "QComboBox",
|
||||
"label": "Master track",
|
||||
"label": "Hero track",
|
||||
"target": "ui",
|
||||
"toolTip": "Select driving track name which should be mastering all others", # noqa
|
||||
"order": 1}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ from openpype.hosts import resolve
|
|||
from pprint import pformat
|
||||
|
||||
|
||||
class CollectInstances(pyblish.api.ContextPlugin):
|
||||
class PrecollectInstances(pyblish.api.ContextPlugin):
|
||||
"""Collect all Track items selection."""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.59
|
||||
label = "Collect Instances"
|
||||
label = "Precollect Instances"
|
||||
hosts = ["resolve"]
|
||||
|
||||
def process(self, context):
|
||||
|
|
@ -26,7 +26,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
data = dict()
|
||||
timeline_item = timeline_item_data["clip"]["item"]
|
||||
|
||||
# get openpype tag data
|
||||
# get pype tag data
|
||||
tag_data = resolve.get_timeline_item_pype_tag(timeline_item)
|
||||
self.log.debug(f"__ tag_data: {pformat(tag_data)}")
|
||||
|
||||
|
|
@ -102,10 +102,10 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
})
|
||||
|
||||
def create_shot_instance(self, context, timeline_item, **data):
|
||||
master_layer = data.get("masterLayer")
|
||||
hero_track = data.get("heroTrack")
|
||||
hierarchy_data = data.get("hierarchyData")
|
||||
|
||||
if not master_layer:
|
||||
if not hero_track:
|
||||
return
|
||||
|
||||
if not hierarchy_data:
|
||||
|
|
@ -9,10 +9,10 @@ from openpype.hosts.resolve.otio import davinci_export
|
|||
reload(davinci_export)
|
||||
|
||||
|
||||
class CollectWorkfile(pyblish.api.ContextPlugin):
|
||||
"""Inject the current working file into context"""
|
||||
class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
||||
"""Precollect the current working file into context"""
|
||||
|
||||
label = "Collect Workfile"
|
||||
label = "Precollect Workfile"
|
||||
order = pyblish.api.CollectorOrder - 0.6
|
||||
|
||||
def process(self, context):
|
||||
|
|
@ -21,8 +21,6 @@ class CollectWorkfile(pyblish.api.ContextPlugin):
|
|||
subset = "workfile"
|
||||
project = resolve.get_current_project()
|
||||
fps = project.GetSetting("timelineFrameRate")
|
||||
|
||||
active_timeline = resolve.get_current_timeline()
|
||||
video_tracks = resolve.get_video_track_names()
|
||||
|
||||
# adding otio timeline to context
|
||||
|
|
@ -58,9 +58,8 @@ def _close_window(event):
|
|||
def _export_button(event):
|
||||
pm = resolve.GetProjectManager()
|
||||
project = pm.GetCurrentProject()
|
||||
fps = project.GetSetting("timelineFrameRate")
|
||||
timeline = project.GetCurrentTimeline()
|
||||
otio_timeline = otio_export.create_otio_timeline(timeline, fps)
|
||||
otio_timeline = otio_export.create_otio_timeline(project)
|
||||
otio_path = os.path.join(
|
||||
itm["exportfilebttn"].Text,
|
||||
timeline.GetName() + ".otio")
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
))
|
||||
|
||||
for instance_data in workfile_instances:
|
||||
instance_data["fps"] = context.data["fps"]
|
||||
instance_data["fps"] = context.data["sceneFps"]
|
||||
|
||||
# Store workfile instance data to instance data
|
||||
instance_data["originData"] = copy.deepcopy(instance_data)
|
||||
|
|
@ -32,6 +32,11 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
subset_name = instance_data["subset"]
|
||||
name = instance_data.get("name", subset_name)
|
||||
instance_data["name"] = name
|
||||
instance_data["label"] = "{} [{}-{}]".format(
|
||||
name,
|
||||
context.data["sceneFrameStart"],
|
||||
context.data["sceneFrameEnd"]
|
||||
)
|
||||
|
||||
active = instance_data.get("active", True)
|
||||
instance_data["active"] = active
|
||||
|
|
@ -73,8 +78,8 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
if instance is None:
|
||||
continue
|
||||
|
||||
instance.data["frameStart"] = context.data["frameStart"]
|
||||
instance.data["frameEnd"] = context.data["frameEnd"]
|
||||
instance.data["frameStart"] = context.data["sceneFrameStart"]
|
||||
instance.data["frameEnd"] = context.data["sceneFrameEnd"]
|
||||
|
||||
self.log.debug("Created instance: {}\n{}".format(
|
||||
instance, json.dumps(instance.data, indent=4)
|
||||
|
|
|
|||
|
|
@ -127,11 +127,11 @@ class CollectWorkfileData(pyblish.api.ContextPlugin):
|
|||
"currentFile": workfile_path,
|
||||
"sceneWidth": width,
|
||||
"sceneHeight": height,
|
||||
"pixelAspect": pixel_apsect,
|
||||
"frameStart": frame_start,
|
||||
"frameEnd": frame_end,
|
||||
"fps": frame_rate,
|
||||
"fieldOrder": field_order
|
||||
"scenePixelAspect": pixel_apsect,
|
||||
"sceneFrameStart": frame_start,
|
||||
"sceneFrameEnd": frame_end,
|
||||
"sceneFps": frame_rate,
|
||||
"sceneFieldOrder": field_order
|
||||
}
|
||||
self.log.debug(
|
||||
"Scene data: {}".format(json.dumps(scene_data, indent=4))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import json
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class ValidateProjectSettings(pyblish.api.ContextPlugin):
|
||||
"""Validate project settings against database.
|
||||
"""
|
||||
|
||||
label = "Validate Project Settings"
|
||||
order = pyblish.api.ValidatorOrder
|
||||
optional = True
|
||||
|
||||
def process(self, context):
|
||||
scene_data = {
|
||||
"frameStart": context.data.get("sceneFrameStart"),
|
||||
"frameEnd": context.data.get("sceneFrameEnd"),
|
||||
"fps": context.data.get("sceneFps"),
|
||||
"resolutionWidth": context.data.get("sceneWidth"),
|
||||
"resolutionHeight": context.data.get("sceneHeight"),
|
||||
"pixelAspect": context.data.get("scenePixelAspect")
|
||||
}
|
||||
invalid = {}
|
||||
for k in scene_data.keys():
|
||||
expected_value = context.data["assetEntity"]["data"][k]
|
||||
if scene_data[k] != expected_value:
|
||||
invalid[k] = {
|
||||
"current": scene_data[k], "expected": expected_value
|
||||
}
|
||||
|
||||
if invalid:
|
||||
raise AssertionError(
|
||||
"Project settings does not match database:\n{}".format(
|
||||
json.dumps(invalid, sort_keys=True, indent=4)
|
||||
)
|
||||
)
|
||||
0
openpype/hosts/unreal/__init__.py
Normal file
0
openpype/hosts/unreal/__init__.py
Normal file
|
|
@ -23,8 +23,8 @@ class UnrealPrelaunchHook(PreLaunchHook):
|
|||
def execute(self):
|
||||
asset_name = self.data["asset_name"]
|
||||
task_name = self.data["task_name"]
|
||||
workdir = self.env["AVALON_WORKDIR"]
|
||||
engine_version = self.app_name.split("_")[-1]
|
||||
workdir = self.launch_context.env["AVALON_WORKDIR"]
|
||||
engine_version = self.app_name.split("_")[-1].replace("-", ".")
|
||||
unreal_project_name = f"{asset_name}_{task_name}"
|
||||
|
||||
# Unreal is sensitive about project names longer then 20 chars
|
||||
|
|
@ -81,8 +81,8 @@ class UnrealPrelaunchHook(PreLaunchHook):
|
|||
# Set "AVALON_UNREAL_PLUGIN" to current process environment for
|
||||
# execution of `create_unreal_project`
|
||||
env_key = "AVALON_UNREAL_PLUGIN"
|
||||
if self.env.get(env_key):
|
||||
os.environ[env_key] = self.env[env_key]
|
||||
if self.launch_context.env.get(env_key):
|
||||
os.environ[env_key] = self.launch_context.env[env_key]
|
||||
|
||||
unreal_lib.create_unreal_project(
|
||||
unreal_project_name,
|
||||
|
|
|
|||
|
|
@ -104,7 +104,8 @@ from .plugin_tools import (
|
|||
from .local_settings import (
|
||||
IniSettingRegistry,
|
||||
JSONSettingRegistry,
|
||||
PypeSettingsRegistry,
|
||||
OpenPypeSecureRegistry,
|
||||
OpenPypeSettingsRegistry,
|
||||
get_local_site_id,
|
||||
change_openpype_mongo_url
|
||||
)
|
||||
|
|
@ -217,7 +218,8 @@ __all__ = [
|
|||
|
||||
"IniSettingRegistry",
|
||||
"JSONSettingRegistry",
|
||||
"PypeSettingsRegistry",
|
||||
"OpenPypeSecureRegistry",
|
||||
"OpenPypeSettingsRegistry",
|
||||
"get_local_site_id",
|
||||
"change_openpype_mongo_url",
|
||||
|
||||
|
|
|
|||
|
|
@ -1123,6 +1123,7 @@ class BuildWorkfile:
|
|||
return output
|
||||
|
||||
|
||||
@with_avalon
|
||||
def get_creator_by_name(creator_name, case_sensitive=False):
|
||||
"""Find creator plugin by name.
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from datetime import datetime
|
|||
from abc import ABCMeta, abstractmethod
|
||||
import json
|
||||
|
||||
# TODO Use pype igniter logic instead of using duplicated code
|
||||
# disable lru cache in Python 2
|
||||
try:
|
||||
from functools import lru_cache
|
||||
|
|
@ -25,11 +26,115 @@ except ImportError:
|
|||
|
||||
import platform
|
||||
|
||||
import appdirs
|
||||
import six
|
||||
import appdirs
|
||||
|
||||
from .import validate_mongo_connection
|
||||
|
||||
_PLACEHOLDER = object()
|
||||
|
||||
|
||||
class OpenPypeSecureRegistry:
|
||||
"""Store information using keyring.
|
||||
|
||||
Registry should be used for private data that should be available only for
|
||||
user.
|
||||
|
||||
All passed registry names will have added prefix `OpenPype/` to easier
|
||||
identify which data were created by OpenPype.
|
||||
|
||||
Args:
|
||||
name(str): Name of registry used as identifier for data.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
try:
|
||||
import keyring
|
||||
|
||||
except Exception:
|
||||
raise NotImplementedError(
|
||||
"Python module `keyring` is not available."
|
||||
)
|
||||
|
||||
# hack for cx_freeze and Windows keyring backend
|
||||
if platform.system().lower() == "windows":
|
||||
from keyring.backends import Windows
|
||||
|
||||
keyring.set_keyring(Windows.WinVaultKeyring())
|
||||
|
||||
# Force "OpenPype" prefix
|
||||
self._name = "/".join(("OpenPype", name))
|
||||
|
||||
def set_item(self, name, value):
|
||||
# type: (str, str) -> None
|
||||
"""Set sensitive item into system's keyring.
|
||||
|
||||
This uses `Keyring module`_ to save sensitive stuff into system's
|
||||
keyring.
|
||||
|
||||
Args:
|
||||
name (str): Name of the item.
|
||||
value (str): Value of the item.
|
||||
|
||||
.. _Keyring module:
|
||||
https://github.com/jaraco/keyring
|
||||
|
||||
"""
|
||||
import keyring
|
||||
|
||||
keyring.set_password(self._name, name, value)
|
||||
|
||||
@lru_cache(maxsize=32)
|
||||
def get_item(self, name, default=_PLACEHOLDER):
|
||||
"""Get value of sensitive item from system's keyring.
|
||||
|
||||
See also `Keyring module`_
|
||||
|
||||
Args:
|
||||
name (str): Name of the item.
|
||||
default (Any): Default value if item is not available.
|
||||
|
||||
Returns:
|
||||
value (str): Value of the item.
|
||||
|
||||
Raises:
|
||||
ValueError: If item doesn't exist and default is not defined.
|
||||
|
||||
.. _Keyring module:
|
||||
https://github.com/jaraco/keyring
|
||||
|
||||
"""
|
||||
import keyring
|
||||
|
||||
value = keyring.get_password(self._name, name)
|
||||
if value is not None:
|
||||
return value
|
||||
|
||||
if default is not _PLACEHOLDER:
|
||||
return default
|
||||
|
||||
# NOTE Should raise `KeyError`
|
||||
raise ValueError(
|
||||
"Item {}:{} does not exist in keyring.".format(self._name, name)
|
||||
)
|
||||
|
||||
def delete_item(self, name):
|
||||
# type: (str) -> None
|
||||
"""Delete value stored in system's keyring.
|
||||
|
||||
See also `Keyring module`_
|
||||
|
||||
Args:
|
||||
name (str): Name of the item to be deleted.
|
||||
|
||||
.. _Keyring module:
|
||||
https://github.com/jaraco/keyring
|
||||
|
||||
"""
|
||||
import keyring
|
||||
|
||||
self.get_item.cache_clear()
|
||||
keyring.delete_password(self._name, name)
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class ASettingRegistry():
|
||||
|
|
@ -48,13 +153,6 @@ class ASettingRegistry():
|
|||
# type: (str) -> ASettingRegistry
|
||||
super(ASettingRegistry, self).__init__()
|
||||
|
||||
if six.PY3:
|
||||
import keyring
|
||||
# hack for cx_freeze and Windows keyring backend
|
||||
if platform.system() == "Windows":
|
||||
from keyring.backends import Windows
|
||||
keyring.set_keyring(Windows.WinVaultKeyring())
|
||||
|
||||
self._name = name
|
||||
self._items = {}
|
||||
|
||||
|
|
@ -120,7 +218,7 @@ class ASettingRegistry():
|
|||
"""Delete item from settings.
|
||||
|
||||
Note:
|
||||
see :meth:`pype.lib.local_settings.ARegistrySettings.delete_item`
|
||||
see :meth:`openpype.lib.user_settings.ARegistrySettings.delete_item`
|
||||
|
||||
"""
|
||||
pass
|
||||
|
|
@ -129,78 +227,6 @@ class ASettingRegistry():
|
|||
del self._items[name]
|
||||
self._delete_item(name)
|
||||
|
||||
def set_secure_item(self, name, value):
|
||||
# type: (str, str) -> None
|
||||
"""Set sensitive item into system's keyring.
|
||||
|
||||
This uses `Keyring module`_ to save sensitive stuff into system's
|
||||
keyring.
|
||||
|
||||
Args:
|
||||
name (str): Name of the item.
|
||||
value (str): Value of the item.
|
||||
|
||||
.. _Keyring module:
|
||||
https://github.com/jaraco/keyring
|
||||
|
||||
"""
|
||||
if six.PY2:
|
||||
raise NotImplementedError(
|
||||
"Keyring not available on Python 2 hosts")
|
||||
import keyring
|
||||
keyring.set_password(self._name, name, value)
|
||||
|
||||
@lru_cache(maxsize=32)
|
||||
def get_secure_item(self, name):
|
||||
# type: (str) -> str
|
||||
"""Get value of sensitive item from system's keyring.
|
||||
|
||||
See also `Keyring module`_
|
||||
|
||||
Args:
|
||||
name (str): Name of the item.
|
||||
|
||||
Returns:
|
||||
value (str): Value of the item.
|
||||
|
||||
Raises:
|
||||
ValueError: If item doesn't exist.
|
||||
|
||||
.. _Keyring module:
|
||||
https://github.com/jaraco/keyring
|
||||
|
||||
"""
|
||||
if six.PY2:
|
||||
raise NotImplementedError(
|
||||
"Keyring not available on Python 2 hosts")
|
||||
import keyring
|
||||
value = keyring.get_password(self._name, name)
|
||||
if not value:
|
||||
raise ValueError(
|
||||
"Item {}:{} does not exist in keyring.".format(
|
||||
self._name, name))
|
||||
return value
|
||||
|
||||
def delete_secure_item(self, name):
|
||||
# type: (str) -> None
|
||||
"""Delete value stored in system's keyring.
|
||||
|
||||
See also `Keyring module`_
|
||||
|
||||
Args:
|
||||
name (str): Name of the item to be deleted.
|
||||
|
||||
.. _Keyring module:
|
||||
https://github.com/jaraco/keyring
|
||||
|
||||
"""
|
||||
if six.PY2:
|
||||
raise NotImplementedError(
|
||||
"Keyring not available on Python 2 hosts")
|
||||
import keyring
|
||||
self.get_secure_item.cache_clear()
|
||||
keyring.delete_password(self._name, name)
|
||||
|
||||
|
||||
class IniSettingRegistry(ASettingRegistry):
|
||||
"""Class using :mod:`configparser`.
|
||||
|
|
@ -218,7 +244,7 @@ class IniSettingRegistry(ASettingRegistry):
|
|||
if not os.path.exists(self._registry_file):
|
||||
with open(self._registry_file, mode="w") as cfg:
|
||||
print("# Settings registry", cfg)
|
||||
print("# Generated by Pype {}".format(version), cfg)
|
||||
print("# Generated by OpenPype {}".format(version), cfg)
|
||||
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
||||
print("# {}".format(now), cfg)
|
||||
|
||||
|
|
@ -352,7 +378,7 @@ class IniSettingRegistry(ASettingRegistry):
|
|||
"""Delete item from default section.
|
||||
|
||||
Note:
|
||||
See :meth:`~pype.lib.IniSettingsRegistry.delete_item_from_section`
|
||||
See :meth:`~openpype.lib.IniSettingsRegistry.delete_item_from_section`
|
||||
|
||||
"""
|
||||
self.delete_item_from_section("MAIN", name)
|
||||
|
|
@ -369,7 +395,7 @@ class JSONSettingRegistry(ASettingRegistry):
|
|||
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
||||
header = {
|
||||
"__metadata__": {
|
||||
"pype-version": os.getenv("OPENPYPE_VERSION", "N/A"),
|
||||
"openpype-version": os.getenv("OPENPYPE_VERSION", "N/A"),
|
||||
"generated": now
|
||||
},
|
||||
"registry": {}
|
||||
|
|
@ -387,7 +413,7 @@ class JSONSettingRegistry(ASettingRegistry):
|
|||
"""Get item value from registry json.
|
||||
|
||||
Note:
|
||||
See :meth:`pype.lib.JSONSettingRegistry.get_item`
|
||||
See :meth:`openpype.lib.JSONSettingRegistry.get_item`
|
||||
|
||||
"""
|
||||
with open(self._registry_file, mode="r") as cfg:
|
||||
|
|
@ -420,7 +446,7 @@ class JSONSettingRegistry(ASettingRegistry):
|
|||
"""Set item value to registry json.
|
||||
|
||||
Note:
|
||||
See :meth:`pype.lib.JSONSettingRegistry.set_item`
|
||||
See :meth:`openpype.lib.JSONSettingRegistry.set_item`
|
||||
|
||||
"""
|
||||
with open(self._registry_file, "r+") as cfg:
|
||||
|
|
@ -452,8 +478,8 @@ class JSONSettingRegistry(ASettingRegistry):
|
|||
json.dump(data, cfg, indent=4)
|
||||
|
||||
|
||||
class PypeSettingsRegistry(JSONSettingRegistry):
|
||||
"""Class handling Pype general settings registry.
|
||||
class OpenPypeSettingsRegistry(JSONSettingRegistry):
|
||||
"""Class handling OpenPype general settings registry.
|
||||
|
||||
Attributes:
|
||||
vendor (str): Name used for path construction.
|
||||
|
|
@ -461,21 +487,23 @@ class PypeSettingsRegistry(JSONSettingRegistry):
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, name=None):
|
||||
self.vendor = "pypeclub"
|
||||
self.product = "pype"
|
||||
self.product = "openpype"
|
||||
if not name:
|
||||
name = "openpype_settings"
|
||||
path = appdirs.user_data_dir(self.product, self.vendor)
|
||||
super(PypeSettingsRegistry, self).__init__("pype_settings", path)
|
||||
super(OpenPypeSettingsRegistry, self).__init__(name, path)
|
||||
|
||||
|
||||
def _create_local_site_id(registry=None):
|
||||
"""Create a local site identifier."""
|
||||
from uuid import uuid4
|
||||
from coolname import generate_slug
|
||||
|
||||
if registry is None:
|
||||
registry = PypeSettingsRegistry()
|
||||
registry = OpenPypeSettingsRegistry()
|
||||
|
||||
new_id = str(uuid4())
|
||||
new_id = generate_slug(3)
|
||||
|
||||
print("Created local site id \"{}\"".format(new_id))
|
||||
|
||||
|
|
@ -489,7 +517,7 @@ def get_local_site_id():
|
|||
|
||||
Identifier is created if does not exists yet.
|
||||
"""
|
||||
registry = PypeSettingsRegistry()
|
||||
registry = OpenPypeSettingsRegistry()
|
||||
try:
|
||||
return registry.get_item("localId")
|
||||
except ValueError:
|
||||
|
|
@ -504,5 +532,9 @@ def change_openpype_mongo_url(new_mongo_url):
|
|||
"""
|
||||
|
||||
validate_mongo_connection(new_mongo_url)
|
||||
registry = PypeSettingsRegistry()
|
||||
registry.set_secure_item("pypeMongo", new_mongo_url)
|
||||
key = "openPypeMongo"
|
||||
registry = OpenPypeSecureRegistry("mongodb")
|
||||
existing_value = registry.get_item(key, None)
|
||||
if existing_value is not None:
|
||||
registry.delete_item(key)
|
||||
registry.set_item(key, new_mongo_url)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
import os
|
||||
import re
|
||||
import time
|
||||
import requests
|
||||
import json
|
||||
import datetime
|
||||
import requests
|
||||
from .constants import (
|
||||
CLOCKIFY_ENDPOINT, ADMIN_PERMISSION_NAMES, CREDENTIALS_JSON_PATH
|
||||
CLOCKIFY_ENDPOINT,
|
||||
ADMIN_PERMISSION_NAMES
|
||||
)
|
||||
|
||||
from openpype.lib.local_settings import OpenPypeSecureRegistry
|
||||
|
||||
|
||||
def time_check(obj):
|
||||
if obj.request_counter < 10:
|
||||
|
|
@ -31,6 +34,8 @@ class ClockifyAPI:
|
|||
self.request_counter = 0
|
||||
self.request_time = time.time()
|
||||
|
||||
self.secure_registry = OpenPypeSecureRegistry("clockify")
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return {"X-Api-Key": self.api_key}
|
||||
|
|
@ -129,22 +134,10 @@ class ClockifyAPI:
|
|||
return False
|
||||
|
||||
def get_api_key(self):
|
||||
api_key = None
|
||||
try:
|
||||
file = open(CREDENTIALS_JSON_PATH, 'r')
|
||||
api_key = json.load(file).get('api_key', None)
|
||||
if api_key == '':
|
||||
api_key = None
|
||||
except Exception:
|
||||
file = open(CREDENTIALS_JSON_PATH, 'w')
|
||||
file.close()
|
||||
return api_key
|
||||
return self.secure_registry.get_item("api_key", None)
|
||||
|
||||
def save_api_key(self, api_key):
|
||||
data = {'api_key': api_key}
|
||||
file = open(CREDENTIALS_JSON_PATH, 'w')
|
||||
file.write(json.dumps(data))
|
||||
file.close()
|
||||
self.secure_registry.set_item("api_key", api_key)
|
||||
|
||||
def get_workspaces(self):
|
||||
action_url = 'workspaces/'
|
||||
|
|
|
|||
|
|
@ -1,17 +1,12 @@
|
|||
import os
|
||||
import appdirs
|
||||
|
||||
|
||||
CLOCKIFY_FTRACK_SERVER_PATH = os.path.join(
|
||||
os.path.dirname(__file__), "ftrack", "server"
|
||||
os.path.dirname(os.path.abspath(__file__)), "ftrack", "server"
|
||||
)
|
||||
CLOCKIFY_FTRACK_USER_PATH = os.path.join(
|
||||
os.path.dirname(__file__), "ftrack", "user"
|
||||
os.path.dirname(os.path.abspath(__file__)), "ftrack", "user"
|
||||
)
|
||||
CREDENTIALS_JSON_PATH = os.path.normpath(os.path.join(
|
||||
appdirs.user_data_dir("pype-app", "pype"),
|
||||
"clockify.json"
|
||||
))
|
||||
|
||||
ADMIN_PERMISSION_NAMES = ["WORKSPACE_OWN", "WORKSPACE_ADMIN"]
|
||||
CLOCKIFY_ENDPOINT = "https://api.clockify.me/api/"
|
||||
|
|
|
|||
|
|
@ -210,3 +210,7 @@ class FtrackModule(
|
|||
|
||||
def tray_exit(self):
|
||||
return self.tray_module.stop_action_server()
|
||||
|
||||
def set_credentials_to_env(self, username, api_key):
|
||||
os.environ["FTRACK_API_USER"] = username or ""
|
||||
os.environ["FTRACK_API_KEY"] = api_key or ""
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class PrePython2Support(PreLaunchHook):
|
|||
Path to vendor modules is added to the beggining of PYTHONPATH.
|
||||
"""
|
||||
# There will be needed more granular filtering in future
|
||||
app_groups = ["maya", "nuke", "nukex", "hiero", "nukestudio"]
|
||||
app_groups = ["maya", "nuke", "nukex", "hiero", "nukestudio", "unreal"]
|
||||
|
||||
def execute(self):
|
||||
# Prepare vendor dir path
|
||||
|
|
|
|||
|
|
@ -891,6 +891,33 @@ class SyncEntitiesFactory:
|
|||
|
||||
self.entities_dict[parent_id]["children"].remove(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
|
||||
|
|
@ -1000,31 +1027,13 @@ class SyncEntitiesFactory:
|
|||
copy.deepcopy(prepared_avalon_attr_ca_id)
|
||||
)
|
||||
|
||||
# TODO query custom attributes by entity_id
|
||||
entity_ids_joined = ", ".join([
|
||||
"\"{}\"".format(id) for id in sync_ids
|
||||
])
|
||||
attributes_joined = ", ".join([
|
||||
"\"{}\"".format(attr_id) for attr_id in attribute_key_by_id.keys()
|
||||
])
|
||||
|
||||
cust_attr_query = (
|
||||
"select value, configuration_id, entity_id"
|
||||
" from ContextCustomAttributeValue"
|
||||
" where entity_id in ({}) and configuration_id in ({})"
|
||||
items = self._query_custom_attributes(
|
||||
self.session,
|
||||
list(attribute_key_by_id.keys()),
|
||||
sync_ids
|
||||
)
|
||||
call_expr = [{
|
||||
"action": "query",
|
||||
"expression": cust_attr_query.format(
|
||||
entity_ids_joined, attributes_joined
|
||||
)
|
||||
}]
|
||||
if hasattr(self.session, "call"):
|
||||
[values] = self.session.call(call_expr)
|
||||
else:
|
||||
[values] = self.session._call(call_expr)
|
||||
|
||||
for item in values["data"]:
|
||||
for item in items:
|
||||
entity_id = item["entity_id"]
|
||||
attr_id = item["configuration_id"]
|
||||
key = attribute_key_by_id[attr_id]
|
||||
|
|
@ -1106,28 +1115,14 @@ class SyncEntitiesFactory:
|
|||
for key, val in prepare_dict_avalon.items():
|
||||
entity_dict["avalon_attrs"][key] = val
|
||||
|
||||
# Prepare values to query
|
||||
entity_ids_joined = ", ".join([
|
||||
"\"{}\"".format(id) for id in sync_ids
|
||||
])
|
||||
attributes_joined = ", ".join([
|
||||
"\"{}\"".format(attr_id) for attr_id in attribute_key_by_id.keys()
|
||||
])
|
||||
avalon_hier = []
|
||||
call_expr = [{
|
||||
"action": "query",
|
||||
"expression": (
|
||||
"select value, entity_id, configuration_id"
|
||||
" from ContextCustomAttributeValue"
|
||||
" where entity_id in ({}) and configuration_id in ({})"
|
||||
).format(entity_ids_joined, attributes_joined)
|
||||
}]
|
||||
if hasattr(self.session, "call"):
|
||||
[values] = self.session.call(call_expr)
|
||||
else:
|
||||
[values] = self.session._call(call_expr)
|
||||
items = self._query_custom_attributes(
|
||||
self.session,
|
||||
list(attribute_key_by_id.keys()),
|
||||
sync_ids
|
||||
)
|
||||
|
||||
for item in values["data"]:
|
||||
avalon_hier = []
|
||||
for item in items:
|
||||
value = item["value"]
|
||||
# WARNING It is not possible to propage enumerate hierachical
|
||||
# attributes with multiselection 100% right. Unseting all values
|
||||
|
|
@ -1256,19 +1251,21 @@ class SyncEntitiesFactory:
|
|||
if not msg or not items:
|
||||
continue
|
||||
self.report_items["warning"][msg] = items
|
||||
tasks = {}
|
||||
for task_type in task_types:
|
||||
task_type_name = task_type["name"]
|
||||
# Set short name to empty string
|
||||
# QUESTION Maybe better would be to lower and remove spaces
|
||||
# from task type name.
|
||||
tasks[task_type_name] = {
|
||||
"short_name": ""
|
||||
}
|
||||
|
||||
current_project_anatomy_data = get_anatomy_settings(
|
||||
project_name, exclude_locals=True
|
||||
)
|
||||
anatomy_tasks = current_project_anatomy_data["tasks"]
|
||||
tasks = {}
|
||||
default_type_data = {
|
||||
"short_name": ""
|
||||
}
|
||||
for task_type in task_types:
|
||||
task_type_name = task_type["name"]
|
||||
tasks[task_type_name] = copy.deepcopy(
|
||||
anatomy_tasks.get(task_type_name)
|
||||
or default_type_data
|
||||
)
|
||||
|
||||
project_config = {
|
||||
"tasks": tasks,
|
||||
|
|
|
|||
|
|
@ -1,23 +1,16 @@
|
|||
import os
|
||||
import json
|
||||
import ftrack_api
|
||||
import appdirs
|
||||
import getpass
|
||||
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
from urlparse import urlparse
|
||||
|
||||
|
||||
CONFIG_PATH = os.path.normpath(appdirs.user_data_dir("pype-app", "pype"))
|
||||
CREDENTIALS_FILE_NAME = "ftrack_cred.json"
|
||||
CREDENTIALS_PATH = os.path.join(CONFIG_PATH, CREDENTIALS_FILE_NAME)
|
||||
CREDENTIALS_FOLDER = os.path.dirname(CREDENTIALS_PATH)
|
||||
from openpype.lib import OpenPypeSecureRegistry
|
||||
|
||||
if not os.path.isdir(CREDENTIALS_FOLDER):
|
||||
os.makedirs(CREDENTIALS_FOLDER)
|
||||
|
||||
USER_GETTER = None
|
||||
USERNAME_KEY = "username"
|
||||
API_KEY_KEY = "api_key"
|
||||
|
||||
|
||||
def get_ftrack_hostname(ftrack_server=None):
|
||||
|
|
@ -30,112 +23,73 @@ def get_ftrack_hostname(ftrack_server=None):
|
|||
return urlparse(ftrack_server).hostname
|
||||
|
||||
|
||||
def get_user():
|
||||
if USER_GETTER:
|
||||
return USER_GETTER()
|
||||
return getpass.getuser()
|
||||
def _get_ftrack_secure_key(hostname, key):
|
||||
"""Secure item key for entered hostname."""
|
||||
return "/".join(("ftrack", hostname, key))
|
||||
|
||||
|
||||
def get_credentials(ftrack_server=None, user=None):
|
||||
credentials = {}
|
||||
if not os.path.exists(CREDENTIALS_PATH):
|
||||
with open(CREDENTIALS_PATH, "w") as file:
|
||||
file.write(json.dumps(credentials))
|
||||
file.close()
|
||||
return credentials
|
||||
|
||||
with open(CREDENTIALS_PATH, "r") as file:
|
||||
content = file.read()
|
||||
|
||||
def get_credentials(ftrack_server=None):
|
||||
hostname = get_ftrack_hostname(ftrack_server)
|
||||
if not user:
|
||||
user = get_user()
|
||||
username_name = _get_ftrack_secure_key(hostname, USERNAME_KEY)
|
||||
api_key_name = _get_ftrack_secure_key(hostname, API_KEY_KEY)
|
||||
|
||||
content_json = json.loads(content or "{}")
|
||||
credentials = content_json.get(hostname, {}).get(user) or {}
|
||||
username_registry = OpenPypeSecureRegistry(username_name)
|
||||
api_key_registry = OpenPypeSecureRegistry(api_key_name)
|
||||
|
||||
return credentials
|
||||
|
||||
|
||||
def save_credentials(ft_user, ft_api_key, ftrack_server=None, user=None):
|
||||
hostname = get_ftrack_hostname(ftrack_server)
|
||||
if not user:
|
||||
user = get_user()
|
||||
|
||||
with open(CREDENTIALS_PATH, "r") as file:
|
||||
content = file.read()
|
||||
|
||||
content_json = json.loads(content or "{}")
|
||||
if hostname not in content_json:
|
||||
content_json[hostname] = {}
|
||||
|
||||
content_json[hostname][user] = {
|
||||
"username": ft_user,
|
||||
"api_key": ft_api_key
|
||||
return {
|
||||
USERNAME_KEY: username_registry.get_item(USERNAME_KEY, None),
|
||||
API_KEY_KEY: api_key_registry.get_item(API_KEY_KEY, None)
|
||||
}
|
||||
|
||||
# Deprecated keys
|
||||
if "username" in content_json:
|
||||
content_json.pop("username")
|
||||
if "apiKey" in content_json:
|
||||
content_json.pop("apiKey")
|
||||
|
||||
with open(CREDENTIALS_PATH, "w") as file:
|
||||
file.write(json.dumps(content_json, indent=4))
|
||||
|
||||
|
||||
def clear_credentials(ft_user=None, ftrack_server=None, user=None):
|
||||
if not ft_user:
|
||||
ft_user = os.environ.get("FTRACK_API_USER")
|
||||
|
||||
if not ft_user:
|
||||
return
|
||||
|
||||
def save_credentials(username, api_key, ftrack_server=None):
|
||||
hostname = get_ftrack_hostname(ftrack_server)
|
||||
if not user:
|
||||
user = get_user()
|
||||
username_name = _get_ftrack_secure_key(hostname, USERNAME_KEY)
|
||||
api_key_name = _get_ftrack_secure_key(hostname, API_KEY_KEY)
|
||||
|
||||
with open(CREDENTIALS_PATH, "r") as file:
|
||||
content = file.read()
|
||||
# Clear credentials
|
||||
clear_credentials(ftrack_server)
|
||||
|
||||
content_json = json.loads(content or "{}")
|
||||
if hostname not in content_json:
|
||||
content_json[hostname] = {}
|
||||
username_registry = OpenPypeSecureRegistry(username_name)
|
||||
api_key_registry = OpenPypeSecureRegistry(api_key_name)
|
||||
|
||||
content_json[hostname].pop(user, None)
|
||||
|
||||
with open(CREDENTIALS_PATH, "w") as file:
|
||||
file.write(json.dumps(content_json))
|
||||
username_registry.set_item(USERNAME_KEY, username)
|
||||
api_key_registry.set_item(API_KEY_KEY, api_key)
|
||||
|
||||
|
||||
def set_env(ft_user=None, ft_api_key=None):
|
||||
os.environ["FTRACK_API_USER"] = ft_user or ""
|
||||
os.environ["FTRACK_API_KEY"] = ft_api_key or ""
|
||||
def clear_credentials(ftrack_server=None):
|
||||
hostname = get_ftrack_hostname(ftrack_server)
|
||||
username_name = _get_ftrack_secure_key(hostname, USERNAME_KEY)
|
||||
api_key_name = _get_ftrack_secure_key(hostname, API_KEY_KEY)
|
||||
|
||||
username_registry = OpenPypeSecureRegistry(username_name)
|
||||
api_key_registry = OpenPypeSecureRegistry(api_key_name)
|
||||
|
||||
current_username = username_registry.get_item(USERNAME_KEY, None)
|
||||
current_api_key = api_key_registry.get_item(API_KEY_KEY, None)
|
||||
|
||||
if current_username is not None:
|
||||
username_registry.delete_item(USERNAME_KEY)
|
||||
|
||||
if current_api_key is not None:
|
||||
api_key_registry.delete_item(API_KEY_KEY)
|
||||
|
||||
|
||||
def get_env_credentials():
|
||||
return (
|
||||
os.environ.get("FTRACK_API_USER"),
|
||||
os.environ.get("FTRACK_API_KEY")
|
||||
)
|
||||
|
||||
|
||||
def check_credentials(ft_user, ft_api_key, ftrack_server=None):
|
||||
def check_credentials(username, api_key, ftrack_server=None):
|
||||
if not ftrack_server:
|
||||
ftrack_server = os.environ["FTRACK_SERVER"]
|
||||
|
||||
if not ft_user or not ft_api_key:
|
||||
if not username or not api_key:
|
||||
return False
|
||||
|
||||
try:
|
||||
session = ftrack_api.Session(
|
||||
server_url=ftrack_server,
|
||||
api_key=ft_api_key,
|
||||
api_user=ft_user
|
||||
api_key=api_key,
|
||||
api_user=username
|
||||
)
|
||||
session.close()
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
from openpype.api import get_system_settings
|
||||
|
||||
|
||||
def get_ftrack_settings():
|
||||
return get_system_settings()["modules"]["ftrack"]
|
||||
|
||||
|
|
@ -10,7 +11,6 @@ def get_ftrack_url_from_settings():
|
|||
|
||||
|
||||
def get_ftrack_event_mongo_info():
|
||||
ftrack_settings = get_ftrack_settings()
|
||||
database_name = os.environ["OPENPYPE_DATABASE_NAME"]
|
||||
collection_name = "ftrack_events"
|
||||
return database_name, collection_name
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class FtrackTrayWrapper:
|
|||
self.bool_action_thread_running = False
|
||||
self.bool_timer_event = False
|
||||
|
||||
self.widget_login = login_dialog.CredentialsDialog()
|
||||
self.widget_login = login_dialog.CredentialsDialog(module)
|
||||
self.widget_login.login_changed.connect(self.on_login_change)
|
||||
self.widget_login.logout_signal.connect(self.on_logout)
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ class FtrackTrayWrapper:
|
|||
validation = credentials.check_credentials(ft_user, ft_api_key)
|
||||
if validation:
|
||||
self.widget_login.set_credentials(ft_user, ft_api_key)
|
||||
credentials.set_env(ft_user, ft_api_key)
|
||||
self.module.set_credentials_to_env(ft_user, ft_api_key)
|
||||
log.info("Connected to Ftrack successfully")
|
||||
self.on_login_change()
|
||||
|
||||
|
|
@ -337,7 +337,7 @@ class FtrackTrayWrapper:
|
|||
|
||||
def changed_user(self):
|
||||
self.stop_action_server()
|
||||
credentials.set_env()
|
||||
self.module.set_credentials_to_env(None, None)
|
||||
self.validate()
|
||||
|
||||
def start_timer_manager(self, data):
|
||||
|
|
|
|||
|
|
@ -14,11 +14,13 @@ class CredentialsDialog(QtWidgets.QDialog):
|
|||
login_changed = QtCore.Signal()
|
||||
logout_signal = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, module, parent=None):
|
||||
super(CredentialsDialog, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle("OpenPype - Ftrack Login")
|
||||
|
||||
self._module = module
|
||||
|
||||
self._login_server_thread = None
|
||||
self._is_logged = False
|
||||
self._in_advance_mode = False
|
||||
|
|
@ -268,7 +270,7 @@ class CredentialsDialog(QtWidgets.QDialog):
|
|||
verification = credentials.check_credentials(username, api_key)
|
||||
if verification:
|
||||
credentials.save_credentials(username, api_key, False)
|
||||
credentials.set_env(username, api_key)
|
||||
self._module.set_credentials_to_env(username, api_key)
|
||||
self.set_credentials(username, api_key)
|
||||
self.login_changed.emit()
|
||||
return verification
|
||||
|
|
|
|||
|
|
@ -40,8 +40,7 @@ class IdleManager(PypeModule, ITrayService):
|
|||
name = "idle_manager"
|
||||
|
||||
def initialize(self, module_settings):
|
||||
idle_man_settings = module_settings[self.name]
|
||||
self.enabled = idle_man_settings["enabled"]
|
||||
self.enabled = True
|
||||
|
||||
self.time_callbacks = collections.defaultdict(list)
|
||||
self.idle_thread = None
|
||||
|
|
@ -50,7 +49,8 @@ class IdleManager(PypeModule, ITrayService):
|
|||
return
|
||||
|
||||
def tray_start(self):
|
||||
self.start_thread()
|
||||
if self.time_callbacks:
|
||||
self.start_thread()
|
||||
|
||||
def tray_exit(self):
|
||||
self.stop_thread()
|
||||
|
|
|
|||
|
|
@ -45,11 +45,13 @@ class TimersManager(PypeModule, ITrayService, IIdleManager, IWebServerRoutes):
|
|||
timers_settings = modules_settings[self.name]
|
||||
|
||||
self.enabled = timers_settings["enabled"]
|
||||
auto_stop = timers_settings["auto_stop"]
|
||||
# When timer will stop if idle manager is running (minutes)
|
||||
full_time = int(timers_settings["full_time"] * 60)
|
||||
# How many minutes before the timer is stopped will popup the message
|
||||
message_time = int(timers_settings["message_time"] * 60)
|
||||
|
||||
self.auto_stop = auto_stop
|
||||
self.time_show_message = full_time - message_time
|
||||
self.time_stop_timer = full_time
|
||||
|
||||
|
|
@ -160,6 +162,9 @@ class TimersManager(PypeModule, ITrayService, IIdleManager, IWebServerRoutes):
|
|||
def callbacks_by_idle_time(self):
|
||||
"""Implementation of IIdleManager interface."""
|
||||
# Time when message is shown
|
||||
if not self.auto_stop:
|
||||
return {}
|
||||
|
||||
callbacks = collections.defaultdict(list)
|
||||
callbacks[self.time_show_message].append(lambda: self.time_callback(0))
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin):
|
|||
continue
|
||||
|
||||
# exclude if not masterLayer True
|
||||
if not instance.data.get("masterLayer"):
|
||||
if not instance.data.get("heroTrack"):
|
||||
continue
|
||||
|
||||
# get asset build data if any available
|
||||
|
|
@ -50,7 +50,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin):
|
|||
|
||||
# suppose that all instances are Shots
|
||||
shot_data['entity_type'] = 'Shot'
|
||||
shot_data['tasks'] = instance.data.get("tasks") or []
|
||||
shot_data['tasks'] = instance.data.get("tasks") or {}
|
||||
shot_data["comments"] = instance.data.get("comments", [])
|
||||
|
||||
shot_data['custom_attributes'] = {
|
||||
|
|
|
|||
10
openpype/settings/defaults/project_settings/tvpaint.json
Normal file
10
openpype/settings/defaults/project_settings/tvpaint.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"publish": {
|
||||
"ValidateMissingLayers": {
|
||||
"enabled": true,
|
||||
"optional": true,
|
||||
"active": true
|
||||
}
|
||||
},
|
||||
"filters": {}
|
||||
}
|
||||
|
|
@ -126,6 +126,7 @@
|
|||
},
|
||||
"timers_manager": {
|
||||
"enabled": true,
|
||||
"auto_stop": true,
|
||||
"full_time": 15.0,
|
||||
"message_time": 0.5
|
||||
},
|
||||
|
|
@ -165,8 +166,5 @@
|
|||
},
|
||||
"standalonepublish_tool": {
|
||||
"enabled": true
|
||||
},
|
||||
"idle_manager": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
|
|
@ -82,6 +82,10 @@
|
|||
"type": "schema",
|
||||
"name": "schema_project_harmony"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_project_tvpaint"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_project_celaction"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "tvpaint",
|
||||
"label": "TVPaint",
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "publish",
|
||||
"label": "Publish plugins",
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_publish_plugin",
|
||||
"template_data": [
|
||||
{
|
||||
"key": "ValidateMissingLayers",
|
||||
"label": "ValidateMissingLayers"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_publish_gui_filter"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -42,6 +42,11 @@
|
|||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "auto_stop",
|
||||
"label": "Auto stop timer"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"decimal": 2,
|
||||
|
|
@ -176,20 +181,6 @@
|
|||
"label": "Enabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "idle_manager",
|
||||
"label": "Idle Manager",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,28 +5,16 @@ class LocalGeneralWidgets(QtWidgets.QWidget):
|
|||
def __init__(self, parent):
|
||||
super(LocalGeneralWidgets, self).__init__(parent)
|
||||
|
||||
local_site_name_input = QtWidgets.QLineEdit(self)
|
||||
|
||||
layout = QtWidgets.QFormLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
layout.addRow("Local site label", local_site_name_input)
|
||||
|
||||
self.local_site_name_input = local_site_name_input
|
||||
|
||||
def update_local_settings(self, value):
|
||||
site_label = ""
|
||||
if value:
|
||||
site_label = value.get("site_label", site_label)
|
||||
self.local_site_name_input.setText(site_label)
|
||||
return
|
||||
|
||||
# RETURNING EARLY TO HIDE WIDGET WITHOUT CONTENT
|
||||
|
||||
def settings_value(self):
|
||||
# Add changed
|
||||
# If these have changed then
|
||||
output = {}
|
||||
local_site_name = self.local_site_name_input.text()
|
||||
if local_site_name:
|
||||
output["site_label"] = local_site_name
|
||||
# Do not return output yet since we don't have mechanism to save or
|
||||
# load these data through api calls
|
||||
# TEMPORARILY EMPTY AS THERE IS NOTHING TO PUT HERE
|
||||
|
||||
return output
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ class LocalSettingsWidget(QtWidgets.QWidget):
|
|||
|
||||
general_widget = LocalGeneralWidgets(general_content)
|
||||
general_layout.addWidget(general_widget)
|
||||
general_expand_widget.hide()
|
||||
|
||||
self.main_layout.addWidget(general_expand_widget)
|
||||
|
||||
|
|
@ -126,9 +127,9 @@ class LocalSettingsWidget(QtWidgets.QWidget):
|
|||
self.system_settings.reset()
|
||||
self.project_settings.reset()
|
||||
|
||||
self.general_widget.update_local_settings(
|
||||
value.get(LOCAL_GENERAL_KEY)
|
||||
)
|
||||
# self.general_widget.update_local_settings(
|
||||
# value.get(LOCAL_GENERAL_KEY)
|
||||
# )
|
||||
self.app_widget.update_local_settings(
|
||||
value.get(LOCAL_APPS_KEY)
|
||||
)
|
||||
|
|
@ -138,9 +139,9 @@ class LocalSettingsWidget(QtWidgets.QWidget):
|
|||
|
||||
def settings_value(self):
|
||||
output = {}
|
||||
general_value = self.general_widget.settings_value()
|
||||
if general_value:
|
||||
output[LOCAL_GENERAL_KEY] = general_value
|
||||
# general_value = self.general_widget.settings_value()
|
||||
# if general_value:
|
||||
# output[LOCAL_GENERAL_KEY] = general_value
|
||||
|
||||
app_value = self.app_widget.settings_value()
|
||||
if app_value:
|
||||
|
|
|
|||
|
|
@ -363,7 +363,7 @@ class PypeInfoWidget(QtWidgets.QWidget):
|
|||
"version_value": "OpenPype version:",
|
||||
"executable": "OpenPype executable:",
|
||||
"pype_root": "OpenPype location:",
|
||||
"mongo_url": "OpenPype Mongo URL:"
|
||||
"mongo_url": "OpenPype Mongo URL:"
|
||||
}
|
||||
# Prepare keys order
|
||||
keys_order = ["version_value", "executable", "pype_root", "mongo_url"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue