mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 21:32:15 +01:00
Merge branch 'pypeclub:develop' into develop
This commit is contained in:
commit
dbe3e2f8ab
58 changed files with 1214 additions and 526 deletions
|
|
@ -5,7 +5,7 @@ import pyblish.api
|
|||
class PreCollectClipEffects(pyblish.api.InstancePlugin):
|
||||
"""Collect soft effects instances."""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.579
|
||||
order = pyblish.api.CollectorOrder - 0.479
|
||||
label = "Precollect Clip Effects Instances"
|
||||
families = ["clip"]
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from pprint import pformat
|
|||
class PrecollectInstances(pyblish.api.ContextPlugin):
|
||||
"""Collect all Track items selection."""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.59
|
||||
order = pyblish.api.CollectorOrder - 0.49
|
||||
label = "Precollect Instances"
|
||||
hosts = ["hiero"]
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
|||
"""Inject the current working file into context"""
|
||||
|
||||
label = "Precollect Workfile"
|
||||
order = pyblish.api.CollectorOrder - 0.6
|
||||
order = pyblish.api.CollectorOrder - 0.5
|
||||
|
||||
def process(self, context):
|
||||
|
||||
|
|
|
|||
|
|
@ -727,7 +727,7 @@ class WorkfileSettings(object):
|
|||
log.error(msg)
|
||||
nuke.message(msg)
|
||||
|
||||
log.warning(">> root_dict: {}".format(root_dict))
|
||||
log.debug(">> root_dict: {}".format(root_dict))
|
||||
|
||||
# first set OCIO
|
||||
if self._root_node["colorManagement"].value() \
|
||||
|
|
@ -1277,6 +1277,7 @@ class ExporterReview:
|
|||
def clean_nodes(self):
|
||||
for node in self._temp_nodes:
|
||||
nuke.delete(node)
|
||||
self._temp_nodes = []
|
||||
self.log.info("Deleted nodes...")
|
||||
|
||||
|
||||
|
|
@ -1301,6 +1302,7 @@ class ExporterReviewLut(ExporterReview):
|
|||
lut_style=None):
|
||||
# initialize parent class
|
||||
ExporterReview.__init__(self, klass, instance)
|
||||
self._temp_nodes = []
|
||||
|
||||
# deal with now lut defined in viewer lut
|
||||
if hasattr(klass, "viewer_lut_raw"):
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import nuke
|
|||
import pyblish.api
|
||||
from avalon.nuke import maintained_selection
|
||||
|
||||
|
||||
class CreateOutputNode(pyblish.api.ContextPlugin):
|
||||
"""Adding output node for each ouput write node
|
||||
So when latly user will want to Load .nk as LifeGroup or Precomp
|
||||
|
|
@ -15,8 +16,8 @@ class CreateOutputNode(pyblish.api.ContextPlugin):
|
|||
def process(self, context):
|
||||
# capture selection state
|
||||
with maintained_selection():
|
||||
active_node = [node for inst in context[:]
|
||||
for node in inst[:]
|
||||
active_node = [node for inst in context
|
||||
for node in inst
|
||||
if "ak:family" in node.knobs()]
|
||||
|
||||
if active_node:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import pyblish.api
|
|||
from avalon.nuke import lib as anlib
|
||||
from openpype.hosts.nuke.api import lib as pnlib
|
||||
import openpype
|
||||
|
||||
try:
|
||||
from __builtin__ import reload
|
||||
except ImportError:
|
||||
from importlib import reload
|
||||
|
||||
reload(pnlib)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@ from avalon.nuke import lib as anlib
|
|||
from openpype.hosts.nuke.api import lib as pnlib
|
||||
import openpype
|
||||
|
||||
try:
|
||||
from __builtin__ import reload
|
||||
except ImportError:
|
||||
from importlib import reload
|
||||
|
||||
reload(pnlib)
|
||||
|
||||
|
||||
class ExtractReviewDataMov(openpype.api.Extractor):
|
||||
"""Extracts movie and thumbnail with baked in luts
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import sys
|
||||
import os
|
||||
import nuke
|
||||
from avalon.nuke import lib as anlib
|
||||
|
|
@ -5,6 +6,10 @@ import pyblish.api
|
|||
import openpype
|
||||
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
unicode = str
|
||||
|
||||
|
||||
class ExtractThumbnail(openpype.api.Extractor):
|
||||
"""Extracts movie and thumbnail with baked in luts
|
||||
|
||||
|
|
@ -112,24 +117,26 @@ class ExtractThumbnail(openpype.api.Extractor):
|
|||
|
||||
# create write node
|
||||
write_node = nuke.createNode("Write")
|
||||
file = fhead + "jpeg"
|
||||
file = fhead + "jpg"
|
||||
name = "thumbnail"
|
||||
path = os.path.join(staging_dir, file).replace("\\", "/")
|
||||
instance.data["thumbnail"] = path
|
||||
write_node["file"].setValue(path)
|
||||
write_node["file_type"].setValue("jpeg")
|
||||
write_node["file_type"].setValue("jpg")
|
||||
write_node["raw"].setValue(1)
|
||||
write_node.setInput(0, previous_node)
|
||||
temporary_nodes.append(write_node)
|
||||
tags = ["thumbnail", "publish_on_farm"]
|
||||
|
||||
# retime for
|
||||
mid_frame = int((int(last_frame) - int(first_frame)) / 2) \
|
||||
+ int(first_frame)
|
||||
first_frame = int(last_frame) / 2
|
||||
last_frame = int(last_frame) / 2
|
||||
|
||||
repre = {
|
||||
'name': name,
|
||||
'ext': "jpeg",
|
||||
'ext': "jpg",
|
||||
"outputName": "thumb",
|
||||
'files': file,
|
||||
"stagingDir": staging_dir,
|
||||
|
|
@ -140,7 +147,7 @@ class ExtractThumbnail(openpype.api.Extractor):
|
|||
instance.data["representations"].append(repre)
|
||||
|
||||
# Render frames
|
||||
nuke.execute(write_node.name(), int(first_frame), int(last_frame))
|
||||
nuke.execute(write_node.name(), int(mid_frame), int(mid_frame))
|
||||
|
||||
self.log.debug(
|
||||
"representations: {}".format(instance.data["representations"]))
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from avalon.nuke import lib as anlib
|
|||
class PreCollectNukeInstances(pyblish.api.ContextPlugin):
|
||||
"""Collect all nodes with Avalon knob."""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.59
|
||||
order = pyblish.api.CollectorOrder - 0.49
|
||||
label = "Pre-collect Instances"
|
||||
hosts = ["nuke", "nukeassist"]
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@ import pyblish.api
|
|||
import os
|
||||
import openpype.api as pype
|
||||
from avalon.nuke import lib as anlib
|
||||
reload(anlib)
|
||||
|
||||
|
||||
class CollectWorkfile(pyblish.api.ContextPlugin):
|
||||
"""Collect current script for publish."""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.60
|
||||
order = pyblish.api.CollectorOrder - 0.50
|
||||
label = "Pre-collect Workfile"
|
||||
hosts = ['nuke']
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from avalon import io, api
|
|||
class CollectNukeWrites(pyblish.api.InstancePlugin):
|
||||
"""Collect all write nodes."""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.58
|
||||
order = pyblish.api.CollectorOrder - 0.48
|
||||
label = "Pre-collect Writes"
|
||||
hosts = ["nuke", "nukeassist"]
|
||||
families = ["write"]
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@ def evaluate_filepath_new(k_value, k_eval, project_dir, first_frame):
|
|||
frames = sorted(frames)
|
||||
firstframe = frames[0]
|
||||
lastframe = frames[len(frames) - 1]
|
||||
if lastframe < 0:
|
||||
|
||||
if int(lastframe) < 0:
|
||||
lastframe = firstframe
|
||||
|
||||
return filepath, firstframe, lastframe
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from pprint import pformat
|
|||
class PrecollectInstances(pyblish.api.ContextPlugin):
|
||||
"""Collect all Track items selection."""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.59
|
||||
order = pyblish.api.CollectorOrder - 0.49
|
||||
label = "Precollect Instances"
|
||||
hosts = ["resolve"]
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
|||
"""Precollect the current working file into context"""
|
||||
|
||||
label = "Precollect Workfile"
|
||||
order = pyblish.api.CollectorOrder - 0.6
|
||||
order = pyblish.api.CollectorOrder - 0.5
|
||||
|
||||
def process(self, context):
|
||||
|
||||
|
|
|
|||
|
|
@ -160,6 +160,11 @@ from .editorial import (
|
|||
make_sequence_collection
|
||||
)
|
||||
|
||||
from .pype_info import (
|
||||
get_openpype_version,
|
||||
get_build_version
|
||||
)
|
||||
|
||||
terminal = Terminal
|
||||
|
||||
__all__ = [
|
||||
|
|
@ -280,5 +285,8 @@ __all__ = [
|
|||
"frames_to_timecode",
|
||||
"make_sequence_collection",
|
||||
"create_project_folders",
|
||||
"get_project_basic_paths"
|
||||
"get_project_basic_paths",
|
||||
|
||||
"get_openpype_version",
|
||||
"get_build_version",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ def get_version_from_path(file):
|
|||
"""
|
||||
pattern = re.compile(r"[\._]v([0-9]+)", re.IGNORECASE)
|
||||
try:
|
||||
return pattern.findall(file)[0]
|
||||
return pattern.findall(file)[-1]
|
||||
except IndexError:
|
||||
log.error(
|
||||
"templates:get_version_from_workfile:"
|
||||
|
|
@ -178,7 +178,9 @@ def _list_path_items(folder_structure):
|
|||
if not isinstance(path, (list, tuple)):
|
||||
path = [path]
|
||||
|
||||
output.append([key, *path])
|
||||
item = [key]
|
||||
item.extend(path)
|
||||
output.append(item)
|
||||
|
||||
return output
|
||||
|
||||
|
|
|
|||
|
|
@ -9,23 +9,65 @@ import openpype.version
|
|||
from openpype.settings.lib import get_local_settings
|
||||
from .execute import get_pype_execute_args
|
||||
from .local_settings import get_local_site_id
|
||||
from .python_module_tools import import_filepath
|
||||
|
||||
|
||||
def get_openpype_version():
|
||||
"""Version of pype that is currently used."""
|
||||
return openpype.version.__version__
|
||||
|
||||
|
||||
def get_pype_version():
|
||||
"""Version of pype that is currently used."""
|
||||
return openpype.version.__version__
|
||||
"""Backwards compatibility. Remove when 100% not used."""
|
||||
print((
|
||||
"Using deprecated function 'openpype.lib.pype_info.get_pype_version'"
|
||||
" replace with 'openpype.lib.pype_info.get_openpype_version'."
|
||||
))
|
||||
return get_openpype_version()
|
||||
|
||||
|
||||
def get_build_version():
|
||||
"""OpenPype version of build."""
|
||||
# Return OpenPype version if is running from code
|
||||
if not is_running_from_build():
|
||||
return get_openpype_version()
|
||||
|
||||
# Import `version.py` from build directory
|
||||
version_filepath = os.path.join(
|
||||
os.environ["OPENPYPE_ROOT"],
|
||||
"openpype",
|
||||
"version.py"
|
||||
)
|
||||
if not os.path.exists(version_filepath):
|
||||
return None
|
||||
|
||||
module = import_filepath(version_filepath, "openpype_build_version")
|
||||
return getattr(module, "__version__", None)
|
||||
|
||||
|
||||
def is_running_from_build():
|
||||
"""Determine if current process is running from build or code.
|
||||
|
||||
Returns:
|
||||
bool: True if running from build.
|
||||
"""
|
||||
executable_path = os.environ["OPENPYPE_EXECUTABLE"]
|
||||
executable_filename = os.path.basename(executable_path)
|
||||
if "python" in executable_filename.lower():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_pype_info():
|
||||
"""Information about currently used Pype process."""
|
||||
executable_args = get_pype_execute_args()
|
||||
if len(executable_args) == 1:
|
||||
if is_running_from_build():
|
||||
version_type = "build"
|
||||
else:
|
||||
version_type = "code"
|
||||
|
||||
return {
|
||||
"version": get_pype_version(),
|
||||
"version": get_openpype_version(),
|
||||
"version_type": version_type,
|
||||
"executable": executable_args[-1],
|
||||
"pype_root": os.environ["OPENPYPE_REPOS_ROOT"],
|
||||
|
|
@ -73,7 +115,7 @@ def extract_pype_info_to_file(dirpath):
|
|||
filepath (str): Full path to file where data were extracted.
|
||||
"""
|
||||
filename = "{}_{}_{}.json".format(
|
||||
get_pype_version(),
|
||||
get_openpype_version(),
|
||||
get_local_site_id(),
|
||||
datetime.datetime.now().strftime("%y%m%d%H%M%S")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,125 +1,143 @@
|
|||
# OpenPype modules/addons
|
||||
OpenPype modules should contain separated logic of specific kind of implementation, like Ftrack connection and usage code or Deadline farm rendering or may contain only special plugins. Addons work the same way currently there is no difference in module and addon.
|
||||
OpenPype modules should contain separated logic of specific kind of implementation, such as Ftrack connection and its usage code, Deadline farm rendering or may contain only special plugins. Addons work the same way currently, there is no difference between module and addon functionality.
|
||||
|
||||
## Modules concept
|
||||
- modules and addons are dynamically imported to virtual python module `openpype_modules` from which it is possible to import them no matter where is the modulo located
|
||||
- modules or addons should never be imported directly even if you know possible full import path
|
||||
- it is because all of their content must be imported in specific order and should not be imported without defined functions as it may also break few implementation parts
|
||||
- modules and addons are dynamically imported to virtual python module `openpype_modules` from which it is possible to import them no matter where is the module located
|
||||
- modules or addons should never be imported directly, even if you know possible full import path
|
||||
- it is because all of their content must be imported in specific order and should not be imported without defined functions as it may also break few implementation parts
|
||||
|
||||
### TODOs
|
||||
- add module/addon manifest
|
||||
- definition of module (not 100% defined content e.g. minimum require OpenPype version etc.)
|
||||
- defying that folder is content of a module or an addon
|
||||
- module/addon have it's settings schemas and default values outside OpenPype
|
||||
- add general setting of paths to modules
|
||||
- definition of module (not 100% defined content e.g. minimum required OpenPype version etc.)
|
||||
- defining a folder as a content of a module or an addon
|
||||
|
||||
## Base class `OpenPypeModule`
|
||||
- abstract class as base for each module
|
||||
- implementation should be module's api withou GUI parts
|
||||
- may implement `get_global_environments` method which should return dictionary of environments that are globally appliable and value is the same for whole studio if launched at any workstation (except os specific paths)
|
||||
- implementation should contain module's api without GUI parts
|
||||
- may implement `get_global_environments` method which should return dictionary of environments that are globally applicable and value is the same for whole studio if launched at any workstation (except os specific paths)
|
||||
- abstract parts:
|
||||
- `name` attribute - name of a module
|
||||
- `initialize` method - method for own initialization of a module (should not override `__init__`)
|
||||
- `connect_with_modules` method - where module may look for it's interfaces implementations or check for other modules
|
||||
- `__init__` should not be overriden and `initialize` should not do time consuming part but only prepare base data about module
|
||||
- also keep in mind that they may be initialized in headless mode
|
||||
- `name` attribute - name of a module
|
||||
- `initialize` method - method for own initialization of a module (should not override `__init__`)
|
||||
- `connect_with_modules` method - where module may look for it's interfaces implementations or check for other modules
|
||||
- `__init__` should not be overridden and `initialize` should not do time consuming part but only prepare base data about module
|
||||
- also keep in mind that they may be initialized in headless mode
|
||||
- connection with other modules is made with help of interfaces
|
||||
|
||||
## Addon class `OpenPypeAddOn`
|
||||
- inherits from `OpenPypeModule` but is enabled by default and doesn't have to implement `initialize` and `connect_with_modules` methods
|
||||
- that is because it is expected that addons don't need to have system settings and `enabled` value on it (but it is possible...)
|
||||
|
||||
## How to add addons/modules
|
||||
- in System settings go to `modules/addon_paths` (`Modules/OpenPype AddOn Paths`) where you have to add path to addon root folder
|
||||
- for openpype example addons use `{OPENPYPE_REPOS_ROOT}/openpype/modules/example_addons`
|
||||
|
||||
## Addon/module settings
|
||||
- addons/modules may have defined custom settings definitions with default values
|
||||
- it is based on settings type `dynamic_schema` which has `name`
|
||||
- that item defines that it can be replaced dynamically with any schemas from module or module which won't be saved to openpype core defaults
|
||||
- they can't be added to any schema hierarchy
|
||||
- item must not be in settings group (under overrides) or in dynamic item (e.g. `list` of `dict-modifiable`)
|
||||
- addons may define it's dynamic schema items
|
||||
- they can be defined with class which inherits from `BaseModuleSettingsDef`
|
||||
- it is recommended to use pre implemented `JsonFilesSettingsDef` which defined structure and use json files to define dynamic schemas, schemas and default values
|
||||
- check it's docstring and check for `example_addon` in example addons
|
||||
- settings definition returns schemas by dynamic schemas names
|
||||
|
||||
# Interfaces
|
||||
- interface is class that has defined abstract methods to implement and may contain preimplemented helper methods
|
||||
- interface is class that has defined abstract methods to implement and may contain pre implemented helper methods
|
||||
- module that inherit from an interface must implement those abstract methods otherwise won't be initialized
|
||||
- it is easy to find which module object inherited from which interfaces withh 100% chance they have implemented required methods
|
||||
- it is easy to find which module object inherited from which interfaces with 100% chance they have implemented required methods
|
||||
- interfaces can be defined in `interfaces.py` inside module directory
|
||||
- the file can't use relative imports or import anything from other parts
|
||||
of module itself at the header of file
|
||||
- this is one of reasons why modules/addons can't be imported directly without using defined functions in OpenPype modules implementation
|
||||
- the file can't use relative imports or import anything from other parts
|
||||
of module itself at the header of file
|
||||
- this is one of reasons why modules/addons can't be imported directly without using defined functions in OpenPype modules implementation
|
||||
|
||||
## Base class `OpenPypeInterface`
|
||||
- has nothing implemented
|
||||
- has ABCMeta as metaclass
|
||||
- is defined to be able find out classes which inherit from this base to be
|
||||
able tell this is an Interface
|
||||
able tell this is an Interface
|
||||
|
||||
## Global interfaces
|
||||
- few interfaces are implemented for global usage
|
||||
|
||||
### IPluginPaths
|
||||
- module want to add directory path/s to avalon or publish plugins
|
||||
- module wants to add directory path/s to avalon or publish plugins
|
||||
- module must implement `get_plugin_paths` which must return dictionary with possible keys `"publish"`, `"load"`, `"create"` or `"actions"`
|
||||
- each key may contain list or string with path to directory with plugins
|
||||
- each key may contain list or string with a path to directory with plugins
|
||||
|
||||
### ITrayModule
|
||||
- module has more logic when used in tray
|
||||
- it is possible that module can be used only in tray
|
||||
- module has more logic when used in a tray
|
||||
- it is possible that module can be used only in the tray
|
||||
- abstract methods
|
||||
- `tray_init` - initialization triggered after `initialize` when used in `TrayModulesManager` and before `connect_with_modules`
|
||||
- `tray_menu` - add actions to tray widget's menu that represent the module
|
||||
- `tray_start` - start of module's login in tray
|
||||
- module is initialized and connected with other modules
|
||||
- `tray_exit` - module's cleanup like stop and join threads etc.
|
||||
- order of calling is based on implementation this order is how it works with `TrayModulesManager`
|
||||
- it is recommended to import and use GUI implementaion only in these methods
|
||||
- `tray_init` - initialization triggered after `initialize` when used in `TrayModulesManager` and before `connect_with_modules`
|
||||
- `tray_menu` - add actions to tray widget's menu that represent the module
|
||||
- `tray_start` - start of module's login in tray
|
||||
- module is initialized and connected with other modules
|
||||
- `tray_exit` - module's cleanup like stop and join threads etc.
|
||||
- order of calling is based on implementation this order is how it works with `TrayModulesManager`
|
||||
- it is recommended to import and use GUI implementation only in these methods
|
||||
- has attribute `tray_initialized` (bool) which is set to False by default and is set by `TrayModulesManager` to True after `tray_init`
|
||||
- if module has logic only in tray or for both then should be checking for `tray_initialized` attribute to decide how should handle situations
|
||||
- if module has logic only in tray or for both then should be checking for `tray_initialized` attribute to decide how should handle situations
|
||||
|
||||
### ITrayService
|
||||
- inherit from `ITrayModule` and implement `tray_menu` method for you
|
||||
- add action to submenu "Services" in tray widget menu with icon and label
|
||||
- abstract atttribute `label`
|
||||
- label shown in menu
|
||||
- interface has preimplemented methods to change icon color
|
||||
- `set_service_running` - green icon
|
||||
- `set_service_failed` - red icon
|
||||
- `set_service_idle` - orange icon
|
||||
- these states must be set by module itself `set_service_running` is default state on initialization
|
||||
- inherits from `ITrayModule` and implements `tray_menu` method for you
|
||||
- adds action to submenu "Services" in tray widget menu with icon and label
|
||||
- abstract attribute `label`
|
||||
- label shown in menu
|
||||
- interface has pre implemented methods to change icon color
|
||||
- `set_service_running` - green icon
|
||||
- `set_service_failed` - red icon
|
||||
- `set_service_idle` - orange icon
|
||||
- these states must be set by module itself `set_service_running` is default state on initialization
|
||||
|
||||
### ITrayAction
|
||||
- inherit from `ITrayModule` and implement `tray_menu` method for you
|
||||
- add action to tray widget menu with label
|
||||
- abstract atttribute `label`
|
||||
- label shown in menu
|
||||
- inherits from `ITrayModule` and implements `tray_menu` method for you
|
||||
- adds action to tray widget menu with label
|
||||
- abstract attribute `label`
|
||||
- label shown in menu
|
||||
- abstract method `on_action_trigger`
|
||||
- what should happen when action is triggered
|
||||
- NOTE: It is good idea to implement logic in `on_action_trigger` to api method and trigger that methods on callbacks this gives ability to trigger that method outside tray
|
||||
- what should happen when an action is triggered
|
||||
- NOTE: It is a good idea to implement logic in `on_action_trigger` to the api method and trigger that method on callbacks. This gives ability to trigger that method outside tray
|
||||
|
||||
## Modules interfaces
|
||||
- modules may have defined their interfaces to be able recognize other modules that would want to use their features
|
||||
-
|
||||
### Example:
|
||||
- Ftrack module has `IFtrackEventHandlerPaths` which helps to tell Ftrack module which of other modules want to add paths to server/user event handlers
|
||||
- Clockify module use `IFtrackEventHandlerPaths` and return paths to clockify ftrack synchronizers
|
||||
- modules may have defined their own interfaces to be able to recognize other modules that would want to use their features
|
||||
|
||||
- Clockify has more inharitance it's class definition looks like
|
||||
### Example:
|
||||
- Ftrack module has `IFtrackEventHandlerPaths` which helps to tell Ftrack module which other modules want to add paths to server/user event handlers
|
||||
- Clockify module use `IFtrackEventHandlerPaths` and returns paths to clockify ftrack synchronizers
|
||||
|
||||
- Clockify inherits from more interfaces. It's class definition looks like:
|
||||
```
|
||||
class ClockifyModule(
|
||||
OpenPypeModule, # Says it's Pype module so ModulesManager will try to initialize.
|
||||
ITrayModule, # Says has special implementation when used in tray.
|
||||
IPluginPaths, # Says has plugin paths that want to register (paths to clockify actions for launcher).
|
||||
IFtrackEventHandlerPaths, # Says has Ftrack actions/events for user/server.
|
||||
ITimersManager # Listen to other modules with timer and can trigger changes in other module timers through `TimerManager` module.
|
||||
OpenPypeModule, # Says it's Pype module so ModulesManager will try to initialize.
|
||||
ITrayModule, # Says has special implementation when used in tray.
|
||||
IPluginPaths, # Says has plugin paths that want to register (paths to clockify actions for launcher).
|
||||
IFtrackEventHandlerPaths, # Says has Ftrack actions/events for user/server.
|
||||
ITimersManager # Listen to other modules with timer and can trigger changes in other module timers through `TimerManager` module.
|
||||
):
|
||||
```
|
||||
|
||||
### ModulesManager
|
||||
- collect module classes and tries to initialize them
|
||||
- collects module classes and tries to initialize them
|
||||
- important attributes
|
||||
- `modules` - list of available attributes
|
||||
- `modules_by_id` - dictionary of modules mapped by their ids
|
||||
- `modules_by_name` - dictionary of modules mapped by their names
|
||||
- all these attributes contain all found modules even if are not enabled
|
||||
- `modules` - list of available attributes
|
||||
- `modules_by_id` - dictionary of modules mapped by their ids
|
||||
- `modules_by_name` - dictionary of modules mapped by their names
|
||||
- all these attributes contain all found modules even if are not enabled
|
||||
- helper methods
|
||||
- `collect_global_environments` to collect all global environments from enabled modules with calling `get_global_environments` on each of them
|
||||
- `collect_plugin_paths` collect plugin paths from all enabled modules
|
||||
- output is always dictionary with all keys and values as list
|
||||
```
|
||||
{
|
||||
"publish": [],
|
||||
"create": [],
|
||||
"load": [],
|
||||
"actions": []
|
||||
}
|
||||
```
|
||||
- `collect_global_environments` to collect all global environments from enabled modules with calling `get_global_environments` on each of them
|
||||
- `collect_plugin_paths` collects plugin paths from all enabled modules
|
||||
- output is always dictionary with all keys and values as an list
|
||||
```
|
||||
{
|
||||
"publish": [],
|
||||
"create": [],
|
||||
"load": [],
|
||||
"actions": []
|
||||
}
|
||||
```
|
||||
|
||||
### TrayModulesManager
|
||||
- inherit from `ModulesManager`
|
||||
- has specific implementations for Pype Tray tool and handle `ITrayModule` methods
|
||||
- inherits from `ModulesManager`
|
||||
- has specific implementation for Pype Tray tool and handle `ITrayModule` methods
|
||||
|
|
@ -495,6 +495,7 @@ class ModulesManager:
|
|||
if (
|
||||
not inspect.isclass(modules_item)
|
||||
or modules_item is OpenPypeModule
|
||||
or modules_item is OpenPypeAddOn
|
||||
or not issubclass(modules_item, OpenPypeModule)
|
||||
):
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import time
|
||||
import sys
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from openpype_modules.ftrack.lib import ServerAction
|
||||
|
|
@ -52,17 +54,80 @@ class SyncToAvalonServer(ServerAction):
|
|||
return False
|
||||
|
||||
def launch(self, session, in_entities, event):
|
||||
self.log.debug("{}: Creating job".format(self.label))
|
||||
|
||||
user_entity = session.query(
|
||||
"User where id is {}".format(event["source"]["user"]["id"])
|
||||
).one()
|
||||
job_entity = session.create("Job", {
|
||||
"user": user_entity,
|
||||
"status": "running",
|
||||
"data": json.dumps({
|
||||
"description": "Sync to avalon is running..."
|
||||
})
|
||||
})
|
||||
session.commit()
|
||||
|
||||
project_entity = self.get_project_from_entity(in_entities[0])
|
||||
project_name = project_entity["full_name"]
|
||||
|
||||
try:
|
||||
result = self.synchronization(event, project_name)
|
||||
|
||||
except Exception:
|
||||
self.log.error(
|
||||
"Synchronization failed due to code error", exc_info=True
|
||||
)
|
||||
|
||||
description = "Sync to avalon Crashed (Download traceback)"
|
||||
self.add_traceback_to_job(
|
||||
job_entity, session, sys.exc_info(), description
|
||||
)
|
||||
|
||||
msg = "An error has happened during synchronization"
|
||||
title = "Synchronization report ({}):".format(project_name)
|
||||
items = []
|
||||
items.append({
|
||||
"type": "label",
|
||||
"value": "# {}".format(msg)
|
||||
})
|
||||
items.append({
|
||||
"type": "label",
|
||||
"value": (
|
||||
"<p>Download report from job for more information.</p>"
|
||||
)
|
||||
})
|
||||
|
||||
report = {}
|
||||
try:
|
||||
report = self.entities_factory.report()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_items = report.get("items") or []
|
||||
if _items:
|
||||
items.append(self.entities_factory.report_splitter)
|
||||
items.extend(_items)
|
||||
|
||||
self.show_interface(items, title, event, submit_btn_label="Ok")
|
||||
|
||||
return {"success": True, "message": msg}
|
||||
|
||||
job_entity["status"] = "done"
|
||||
job_entity["data"] = json.dumps({
|
||||
"description": "Sync to avalon finished."
|
||||
})
|
||||
session.commit()
|
||||
|
||||
return result
|
||||
|
||||
def synchronization(self, event, project_name):
|
||||
time_start = time.time()
|
||||
|
||||
self.show_message(event, "Synchronization - Preparing data", True)
|
||||
# Get ftrack project
|
||||
if in_entities[0].entity_type.lower() == "project":
|
||||
ft_project_name = in_entities[0]["full_name"]
|
||||
else:
|
||||
ft_project_name = in_entities[0]["project"]["full_name"]
|
||||
|
||||
try:
|
||||
output = self.entities_factory.launch_setup(ft_project_name)
|
||||
output = self.entities_factory.launch_setup(project_name)
|
||||
if output is not None:
|
||||
return output
|
||||
|
||||
|
|
@ -72,7 +137,7 @@ class SyncToAvalonServer(ServerAction):
|
|||
time_2 = time.time()
|
||||
|
||||
# This must happen before all filtering!!!
|
||||
self.entities_factory.prepare_avalon_entities(ft_project_name)
|
||||
self.entities_factory.prepare_avalon_entities(project_name)
|
||||
time_3 = time.time()
|
||||
|
||||
self.entities_factory.filter_by_ignore_sync()
|
||||
|
|
@ -118,7 +183,7 @@ class SyncToAvalonServer(ServerAction):
|
|||
report = self.entities_factory.report()
|
||||
if report and report.get("items"):
|
||||
default_title = "Synchronization report ({}):".format(
|
||||
ft_project_name
|
||||
project_name
|
||||
)
|
||||
self.show_interface(
|
||||
items=report["items"],
|
||||
|
|
@ -130,46 +195,6 @@ class SyncToAvalonServer(ServerAction):
|
|||
"message": "Synchronization Finished"
|
||||
}
|
||||
|
||||
except Exception:
|
||||
self.log.error(
|
||||
"Synchronization failed due to code error", exc_info=True
|
||||
)
|
||||
msg = "An error has happened during synchronization"
|
||||
title = "Synchronization report ({}):".format(ft_project_name)
|
||||
items = []
|
||||
items.append({
|
||||
"type": "label",
|
||||
"value": "# {}".format(msg)
|
||||
})
|
||||
items.append({
|
||||
"type": "label",
|
||||
"value": "## Traceback of the error"
|
||||
})
|
||||
items.append({
|
||||
"type": "label",
|
||||
"value": "<p>{}</p>".format(
|
||||
str(traceback.format_exc()).replace(
|
||||
"\n", "<br>").replace(
|
||||
" ", " "
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
report = {"items": []}
|
||||
try:
|
||||
report = self.entities_factory.report()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_items = report.get("items", [])
|
||||
if _items:
|
||||
items.append(self.entities_factory.report_splitter)
|
||||
items.extend(_items)
|
||||
|
||||
self.show_interface(items, title, event)
|
||||
|
||||
return {"success": True, "message": msg}
|
||||
|
||||
finally:
|
||||
try:
|
||||
self.entities_factory.dbcon.uninstall()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import time
|
||||
import sys
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from openpype_modules.ftrack.lib import BaseAction, statics_icon
|
||||
|
|
@ -30,17 +32,10 @@ class SyncToAvalonLocal(BaseAction):
|
|||
- or do it manually (Not recommended)
|
||||
"""
|
||||
|
||||
#: Action identifier.
|
||||
identifier = "sync.to.avalon.local"
|
||||
#: Action label.
|
||||
label = "OpenPype Admin"
|
||||
#: Action variant
|
||||
variant = "- Sync To Avalon (Local)"
|
||||
#: Action description.
|
||||
description = "Send data from Ftrack to Avalon"
|
||||
#: priority
|
||||
priority = 200
|
||||
#: roles that are allowed to register this action
|
||||
icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg")
|
||||
|
||||
settings_key = "sync_to_avalon_local"
|
||||
|
|
@ -63,17 +58,80 @@ class SyncToAvalonLocal(BaseAction):
|
|||
return is_valid
|
||||
|
||||
def launch(self, session, in_entities, event):
|
||||
self.log.debug("{}: Creating job".format(self.label))
|
||||
|
||||
user_entity = session.query(
|
||||
"User where id is {}".format(event["source"]["user"]["id"])
|
||||
).one()
|
||||
job_entity = session.create("Job", {
|
||||
"user": user_entity,
|
||||
"status": "running",
|
||||
"data": json.dumps({
|
||||
"description": "Sync to avalon is running..."
|
||||
})
|
||||
})
|
||||
session.commit()
|
||||
|
||||
project_entity = self.get_project_from_entity(in_entities[0])
|
||||
project_name = project_entity["full_name"]
|
||||
|
||||
try:
|
||||
result = self.synchronization(event, project_name)
|
||||
|
||||
except Exception:
|
||||
self.log.error(
|
||||
"Synchronization failed due to code error", exc_info=True
|
||||
)
|
||||
|
||||
description = "Sync to avalon Crashed (Download traceback)"
|
||||
self.add_traceback_to_job(
|
||||
job_entity, session, sys.exc_info(), description
|
||||
)
|
||||
|
||||
msg = "An error has happened during synchronization"
|
||||
title = "Synchronization report ({}):".format(project_name)
|
||||
items = []
|
||||
items.append({
|
||||
"type": "label",
|
||||
"value": "# {}".format(msg)
|
||||
})
|
||||
items.append({
|
||||
"type": "label",
|
||||
"value": (
|
||||
"<p>Download report from job for more information.</p>"
|
||||
)
|
||||
})
|
||||
|
||||
report = {}
|
||||
try:
|
||||
report = self.entities_factory.report()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_items = report.get("items") or []
|
||||
if _items:
|
||||
items.append(self.entities_factory.report_splitter)
|
||||
items.extend(_items)
|
||||
|
||||
self.show_interface(items, title, event, submit_btn_label="Ok")
|
||||
|
||||
return {"success": True, "message": msg}
|
||||
|
||||
job_entity["status"] = "done"
|
||||
job_entity["data"] = json.dumps({
|
||||
"description": "Sync to avalon finished."
|
||||
})
|
||||
session.commit()
|
||||
|
||||
return result
|
||||
|
||||
def synchronization(self, event, project_name):
|
||||
time_start = time.time()
|
||||
|
||||
self.show_message(event, "Synchronization - Preparing data", True)
|
||||
# Get ftrack project
|
||||
if in_entities[0].entity_type.lower() == "project":
|
||||
ft_project_name = in_entities[0]["full_name"]
|
||||
else:
|
||||
ft_project_name = in_entities[0]["project"]["full_name"]
|
||||
|
||||
try:
|
||||
output = self.entities_factory.launch_setup(ft_project_name)
|
||||
output = self.entities_factory.launch_setup(project_name)
|
||||
if output is not None:
|
||||
return output
|
||||
|
||||
|
|
@ -83,7 +141,7 @@ class SyncToAvalonLocal(BaseAction):
|
|||
time_2 = time.time()
|
||||
|
||||
# This must happen before all filtering!!!
|
||||
self.entities_factory.prepare_avalon_entities(ft_project_name)
|
||||
self.entities_factory.prepare_avalon_entities(project_name)
|
||||
time_3 = time.time()
|
||||
|
||||
self.entities_factory.filter_by_ignore_sync()
|
||||
|
|
@ -129,7 +187,7 @@ class SyncToAvalonLocal(BaseAction):
|
|||
report = self.entities_factory.report()
|
||||
if report and report.get("items"):
|
||||
default_title = "Synchronization report ({}):".format(
|
||||
ft_project_name
|
||||
project_name
|
||||
)
|
||||
self.show_interface(
|
||||
items=report["items"],
|
||||
|
|
@ -141,46 +199,6 @@ class SyncToAvalonLocal(BaseAction):
|
|||
"message": "Synchronization Finished"
|
||||
}
|
||||
|
||||
except Exception:
|
||||
self.log.error(
|
||||
"Synchronization failed due to code error", exc_info=True
|
||||
)
|
||||
msg = "An error occurred during synchronization"
|
||||
title = "Synchronization report ({}):".format(ft_project_name)
|
||||
items = []
|
||||
items.append({
|
||||
"type": "label",
|
||||
"value": "# {}".format(msg)
|
||||
})
|
||||
items.append({
|
||||
"type": "label",
|
||||
"value": "## Traceback of the error"
|
||||
})
|
||||
items.append({
|
||||
"type": "label",
|
||||
"value": "<p>{}</p>".format(
|
||||
str(traceback.format_exc()).replace(
|
||||
"\n", "<br>").replace(
|
||||
" ", " "
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
report = {"items": []}
|
||||
try:
|
||||
report = self.entities_factory.report()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_items = report.get("items", [])
|
||||
if _items:
|
||||
items.append(self.entities_factory.report_splitter)
|
||||
items.extend(_items)
|
||||
|
||||
self.show_interface(items, title, event)
|
||||
|
||||
return {"success": True, "message": msg}
|
||||
|
||||
finally:
|
||||
try:
|
||||
self.entities_factory.dbcon.uninstall()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import subprocess
|
|||
import socket
|
||||
import json
|
||||
import platform
|
||||
import argparse
|
||||
import getpass
|
||||
import atexit
|
||||
import time
|
||||
|
|
@ -16,7 +15,9 @@ import ftrack_api
|
|||
import pymongo
|
||||
from openpype.lib import (
|
||||
get_pype_execute_args,
|
||||
OpenPypeMongoConnection
|
||||
OpenPypeMongoConnection,
|
||||
get_openpype_version,
|
||||
get_build_version
|
||||
)
|
||||
from openpype_modules.ftrack import FTRACK_MODULE_DIR
|
||||
from openpype_modules.ftrack.lib import credentials
|
||||
|
|
@ -236,14 +237,16 @@ def main_loop(ftrack_url):
|
|||
statuser_thread=statuser_thread
|
||||
)
|
||||
|
||||
system_name, pc_name = platform.uname()[:2]
|
||||
host_name = socket.gethostname()
|
||||
main_info = {
|
||||
"created_at": datetime.datetime.now().strftime("%Y.%m.%d %H:%M:%S"),
|
||||
"Username": getpass.getuser(),
|
||||
"Host Name": host_name,
|
||||
"Host IP": socket.gethostbyname(host_name)
|
||||
}
|
||||
main_info = [
|
||||
["created_at", datetime.datetime.now().strftime("%Y.%m.%d %H:%M:%S")],
|
||||
["Username", getpass.getuser()],
|
||||
["Host Name", host_name],
|
||||
["Host IP", socket.gethostbyname(host_name)],
|
||||
["OpenPype executable", get_pype_execute_args()[-1]],
|
||||
["OpenPype version", get_openpype_version() or "N/A"],
|
||||
["OpenPype build version", get_build_version() or "N/A"]
|
||||
]
|
||||
main_info_str = json.dumps(main_info)
|
||||
# Main loop
|
||||
while True:
|
||||
|
|
|
|||
|
|
@ -384,8 +384,8 @@ class BaseHandler(object):
|
|||
)
|
||||
|
||||
def show_interface(
|
||||
self, items, title='',
|
||||
event=None, user=None, username=None, user_id=None
|
||||
self, items, title="", event=None, user=None,
|
||||
username=None, user_id=None, submit_btn_label=None
|
||||
):
|
||||
"""
|
||||
Shows interface to user
|
||||
|
|
@ -428,14 +428,18 @@ class BaseHandler(object):
|
|||
'applicationId=ftrack.client.web and user.id="{0}"'
|
||||
).format(user_id)
|
||||
|
||||
event_data = {
|
||||
"type": "widget",
|
||||
"items": items,
|
||||
"title": title
|
||||
}
|
||||
if submit_btn_label:
|
||||
event_data["submit_button_label"] = submit_btn_label
|
||||
|
||||
self.session.event_hub.publish(
|
||||
ftrack_api.event.base.Event(
|
||||
topic='ftrack.action.trigger-user-interface',
|
||||
data=dict(
|
||||
type='widget',
|
||||
items=items,
|
||||
title=title
|
||||
),
|
||||
data=event_data,
|
||||
target=target
|
||||
),
|
||||
on_error='ignore'
|
||||
|
|
@ -443,7 +447,7 @@ class BaseHandler(object):
|
|||
|
||||
def show_interface_from_dict(
|
||||
self, messages, title="", event=None,
|
||||
user=None, username=None, user_id=None
|
||||
user=None, username=None, user_id=None, submit_btn_label=None
|
||||
):
|
||||
if not messages:
|
||||
self.log.debug("No messages to show! (messages dict is empty)")
|
||||
|
|
@ -469,7 +473,9 @@ class BaseHandler(object):
|
|||
message = {'type': 'label', 'value': '<p>{}</p>'.format(value)}
|
||||
items.append(message)
|
||||
|
||||
self.show_interface(items, title, event, user, username, user_id)
|
||||
self.show_interface(
|
||||
items, title, event, user, username, user_id, submit_btn_label
|
||||
)
|
||||
|
||||
def trigger_action(
|
||||
self, action_name, event=None, session=None,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@ from openpype_modules.ftrack.ftrack_server.lib import (
|
|||
from openpype.modules import ModulesManager
|
||||
|
||||
from openpype.api import Logger
|
||||
from openpype.lib import (
|
||||
get_openpype_version,
|
||||
get_build_version
|
||||
)
|
||||
|
||||
|
||||
import ftrack_api
|
||||
|
||||
|
|
@ -40,9 +45,11 @@ def send_status(event):
|
|||
new_event_data = {
|
||||
"subprocess_id": subprocess_id,
|
||||
"source": "processor",
|
||||
"status_info": {
|
||||
"created_at": subprocess_started.strftime("%Y.%m.%d %H:%M:%S")
|
||||
}
|
||||
"status_info": [
|
||||
["created_at", subprocess_started.strftime("%Y.%m.%d %H:%M:%S")],
|
||||
["OpenPype version", get_openpype_version() or "N/A"],
|
||||
["OpenPype build version", get_build_version() or "N/A"]
|
||||
]
|
||||
}
|
||||
|
||||
new_event = ftrack_api.event.base.Event(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import os
|
|||
import sys
|
||||
import json
|
||||
import threading
|
||||
import collections
|
||||
import signal
|
||||
import socket
|
||||
import datetime
|
||||
|
|
@ -165,7 +166,7 @@ class StatusFactory:
|
|||
return
|
||||
|
||||
source = event["data"]["source"]
|
||||
data = event["data"]["status_info"]
|
||||
data = collections.OrderedDict(event["data"]["status_info"])
|
||||
|
||||
self.update_status_info(source, data)
|
||||
|
||||
|
|
@ -348,7 +349,7 @@ def heartbeat():
|
|||
|
||||
def main(args):
|
||||
port = int(args[-1])
|
||||
server_info = json.loads(args[-2])
|
||||
server_info = collections.OrderedDict(json.loads(args[-2]))
|
||||
|
||||
# Create a TCP/IP socket
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,11 @@ from openpype_modules.ftrack.ftrack_server.lib import (
|
|||
TOPIC_STATUS_SERVER_RESULT
|
||||
)
|
||||
from openpype_modules.ftrack.lib import get_ftrack_event_mongo_info
|
||||
from openpype.lib import OpenPypeMongoConnection
|
||||
from openpype.lib import (
|
||||
OpenPypeMongoConnection,
|
||||
get_openpype_version,
|
||||
get_build_version
|
||||
)
|
||||
from openpype.api import Logger
|
||||
|
||||
log = Logger.get_logger("Event storer")
|
||||
|
|
@ -153,9 +157,11 @@ def send_status(event):
|
|||
new_event_data = {
|
||||
"subprocess_id": os.environ["FTRACK_EVENT_SUB_ID"],
|
||||
"source": "storer",
|
||||
"status_info": {
|
||||
"created_at": subprocess_started.strftime("%Y.%m.%d %H:%M:%S")
|
||||
}
|
||||
"status_info": [
|
||||
["created_at", subprocess_started.strftime("%Y.%m.%d %H:%M:%S")],
|
||||
["OpenPype version", get_openpype_version() or "N/A"],
|
||||
["OpenPype build version", get_build_version() or "N/A"]
|
||||
]
|
||||
}
|
||||
|
||||
new_event = ftrack_api.event.base.Event(
|
||||
|
|
|
|||
|
|
@ -29,13 +29,35 @@ class AbstractProvider:
|
|||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_configurable_items(cls):
|
||||
def get_system_settings_schema(cls):
|
||||
"""
|
||||
Returns filtered dict of editable properties
|
||||
Returns dict for editable properties on system settings level
|
||||
|
||||
|
||||
Returns:
|
||||
(dict)
|
||||
(list) of dict
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_project_settings_schema(cls):
|
||||
"""
|
||||
Returns dict for editable properties on project settings level
|
||||
|
||||
|
||||
Returns:
|
||||
(list) of dict
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_local_settings_schema(cls):
|
||||
"""
|
||||
Returns dict for editable properties on local settings level
|
||||
|
||||
|
||||
Returns:
|
||||
(list) of dict
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import platform
|
|||
from openpype.api import Logger
|
||||
from openpype.api import get_system_settings
|
||||
from .abstract_provider import AbstractProvider
|
||||
from ..utils import time_function, ResumableError, EditableScopes
|
||||
from ..utils import time_function, ResumableError
|
||||
|
||||
log = Logger().get_logger("SyncServer")
|
||||
|
||||
|
|
@ -96,30 +96,61 @@ class GDriveHandler(AbstractProvider):
|
|||
return self.service is not None
|
||||
|
||||
@classmethod
|
||||
def get_configurable_items(cls):
|
||||
def get_system_settings_schema(cls):
|
||||
"""
|
||||
Returns filtered dict of editable properties.
|
||||
Returns dict for editable properties on system settings level
|
||||
|
||||
|
||||
Returns:
|
||||
(list) of dict
|
||||
"""
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def get_project_settings_schema(cls):
|
||||
"""
|
||||
Returns dict for editable properties on project settings level
|
||||
|
||||
|
||||
Returns:
|
||||
(list) of dict
|
||||
"""
|
||||
# {platform} tells that value is multiplatform and only specific OS
|
||||
# should be returned
|
||||
editable = [
|
||||
# credentials could be overriden on Project or User level
|
||||
{
|
||||
'key': "credentials_url",
|
||||
'label': "Credentials url",
|
||||
'type': 'text'
|
||||
},
|
||||
# roots could be overriden only on Project leve, User cannot
|
||||
{
|
||||
'key': "roots",
|
||||
'label': "Roots",
|
||||
'type': 'dict'
|
||||
}
|
||||
]
|
||||
return editable
|
||||
|
||||
@classmethod
|
||||
def get_local_settings_schema(cls):
|
||||
"""
|
||||
Returns dict for editable properties on local settings level
|
||||
|
||||
|
||||
Returns:
|
||||
(dict)
|
||||
"""
|
||||
# {platform} tells that value is multiplatform and only specific OS
|
||||
# should be returned
|
||||
editable = {
|
||||
editable = [
|
||||
# credentials could be override on Project or User level
|
||||
'credentials_url': {
|
||||
'scope': [EditableScopes.PROJECT,
|
||||
EditableScopes.LOCAL],
|
||||
{
|
||||
'key': "credentials_url",
|
||||
'label': "Credentials url",
|
||||
'type': 'text',
|
||||
'namespace': '{project_settings}/global/sync_server/sites/{site}/credentials_url/{platform}' # noqa: E501
|
||||
},
|
||||
# roots could be override only on Project leve, User cannot
|
||||
'root': {'scope': [EditableScopes.PROJECT],
|
||||
'label': "Roots",
|
||||
'type': 'dict'}
|
||||
}
|
||||
}
|
||||
]
|
||||
return editable
|
||||
|
||||
def get_roots_config(self, anatomy=None):
|
||||
|
|
|
|||
|
|
@ -76,6 +76,14 @@ class ProviderFactory:
|
|||
|
||||
return provider_info[0].get_configurable_items()
|
||||
|
||||
def get_provider_cls(self, provider_code):
|
||||
"""
|
||||
Returns class object for 'provider_code' to run class methods on.
|
||||
"""
|
||||
provider_info = self._get_creator_info(provider_code)
|
||||
|
||||
return provider_info[0]
|
||||
|
||||
def _get_creator_info(self, provider):
|
||||
"""
|
||||
Collect all necessary info for provider. Currently only creator
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import time
|
|||
from openpype.api import Logger, Anatomy
|
||||
from .abstract_provider import AbstractProvider
|
||||
|
||||
from ..utils import EditableScopes
|
||||
|
||||
log = Logger().get_logger("SyncServer")
|
||||
|
||||
|
||||
|
|
@ -30,18 +28,51 @@ class LocalDriveHandler(AbstractProvider):
|
|||
return True
|
||||
|
||||
@classmethod
|
||||
def get_configurable_items(cls):
|
||||
def get_system_settings_schema(cls):
|
||||
"""
|
||||
Returns filtered dict of editable properties
|
||||
Returns dict for editable properties on system settings level
|
||||
|
||||
|
||||
Returns:
|
||||
(list) of dict
|
||||
"""
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def get_project_settings_schema(cls):
|
||||
"""
|
||||
Returns dict for editable properties on project settings level
|
||||
|
||||
|
||||
Returns:
|
||||
(list) of dict
|
||||
"""
|
||||
# for non 'studio' sites, 'studio' is configured in Anatomy
|
||||
editable = [
|
||||
{
|
||||
'key': "roots",
|
||||
'label': "Roots",
|
||||
'type': 'dict'
|
||||
}
|
||||
]
|
||||
return editable
|
||||
|
||||
@classmethod
|
||||
def get_local_settings_schema(cls):
|
||||
"""
|
||||
Returns dict for editable properties on local settings level
|
||||
|
||||
|
||||
Returns:
|
||||
(dict)
|
||||
"""
|
||||
editable = {
|
||||
'root': {'scope': [EditableScopes.LOCAL],
|
||||
'label': "Roots",
|
||||
'type': 'dict'}
|
||||
}
|
||||
editable = [
|
||||
{
|
||||
'key': "roots",
|
||||
'label': "Roots",
|
||||
'type': 'dict'
|
||||
}
|
||||
]
|
||||
return editable
|
||||
|
||||
def upload_file(self, source_path, target_path,
|
||||
|
|
|
|||
|
|
@ -16,14 +16,13 @@ from openpype.api import (
|
|||
get_local_site_id)
|
||||
from openpype.lib import PypeLogger
|
||||
from openpype.settings.lib import (
|
||||
get_default_project_settings,
|
||||
get_default_anatomy_settings,
|
||||
get_anatomy_settings)
|
||||
|
||||
from .providers.local_drive import LocalDriveHandler
|
||||
from .providers import lib
|
||||
|
||||
from .utils import time_function, SyncStatus, EditableScopes
|
||||
from .utils import time_function, SyncStatus
|
||||
|
||||
|
||||
log = PypeLogger().get_logger("SyncServer")
|
||||
|
|
@ -399,204 +398,239 @@ class SyncServerModule(OpenPypeModule, ITrayModule):
|
|||
|
||||
return remote_site
|
||||
|
||||
def get_local_settings_schema(self):
|
||||
"""Wrapper for Local settings - all projects incl. Default"""
|
||||
return self.get_configurable_items(EditableScopes.LOCAL)
|
||||
# Methods for Settings UI to draw appropriate forms
|
||||
@classmethod
|
||||
def get_system_settings_schema(cls):
|
||||
""" Gets system level schema of configurable items
|
||||
|
||||
def get_configurable_items(self, scope=None):
|
||||
Used for Setting UI to provide forms.
|
||||
"""
|
||||
Returns list of sites that could be configurable for all projects.
|
||||
ret_dict = {}
|
||||
for provider_code in lib.factory.providers:
|
||||
ret_dict[provider_code] = \
|
||||
lib.factory.get_provider_cls(provider_code). \
|
||||
get_system_settings_schema()
|
||||
|
||||
Could be filtered by 'scope' argument (list)
|
||||
return ret_dict
|
||||
|
||||
Args:
|
||||
scope (list of utils.EditableScope)
|
||||
@classmethod
|
||||
def get_project_settings_schema(cls):
|
||||
""" Gets project level schema of configurable items.
|
||||
|
||||
Returns:
|
||||
(dict of list of dict)
|
||||
{
|
||||
siteA : [
|
||||
{
|
||||
key:"root", label:"root",
|
||||
"value":"{'work': 'c:/projects'}",
|
||||
"type": "dict",
|
||||
"children":[
|
||||
{ "key": "work",
|
||||
"type": "text",
|
||||
"value": "c:/projects"}
|
||||
]
|
||||
},
|
||||
{
|
||||
key:"credentials_url", label:"Credentials url",
|
||||
"value":"'c:/projects/cred.json'", "type": "text",
|
||||
"namespace": "{project_setting}/global/sync_server/
|
||||
sites"
|
||||
}
|
||||
]
|
||||
}
|
||||
It is not using Setting! Used for Setting UI to provide forms.
|
||||
"""
|
||||
editable = {}
|
||||
applicable_projects = list(self.connection.projects())
|
||||
applicable_projects.append(None)
|
||||
for project in applicable_projects:
|
||||
project_name = None
|
||||
if project:
|
||||
project_name = project["name"]
|
||||
ret_dict = {}
|
||||
for provider_code in lib.factory.providers:
|
||||
ret_dict[provider_code] = \
|
||||
lib.factory.get_provider_cls(provider_code). \
|
||||
get_project_settings_schema()
|
||||
|
||||
items = self.get_configurable_items_for_project(project_name,
|
||||
scope)
|
||||
editable.update(items)
|
||||
return ret_dict
|
||||
|
||||
return editable
|
||||
@classmethod
|
||||
def get_local_settings_schema(cls):
|
||||
""" Gets local level schema of configurable items.
|
||||
|
||||
def get_local_settings_schema_for_project(self, project_name):
|
||||
"""Wrapper for Local settings - for specific 'project_name'"""
|
||||
return self.get_configurable_items_for_project(project_name,
|
||||
EditableScopes.LOCAL)
|
||||
|
||||
def get_configurable_items_for_project(self, project_name=None,
|
||||
scope=None):
|
||||
It is not using Setting! Used for Setting UI to provide forms.
|
||||
"""
|
||||
Returns list of items that could be configurable for specific
|
||||
'project_name'
|
||||
ret_dict = {}
|
||||
for provider_code in lib.factory.providers:
|
||||
ret_dict[provider_code] = \
|
||||
lib.factory.get_provider_cls(provider_code). \
|
||||
get_local_settings_schema()
|
||||
|
||||
Args:
|
||||
project_name (str) - None > default project,
|
||||
scope (list of utils.EditableScope)
|
||||
(optional, None is all scopes, default is LOCAL)
|
||||
return ret_dict
|
||||
|
||||
Returns:
|
||||
(dict of list of dict)
|
||||
{
|
||||
siteA : [
|
||||
{
|
||||
key:"root", label:"root",
|
||||
"type": "dict",
|
||||
"children":[
|
||||
{ "key": "work",
|
||||
"type": "text",
|
||||
"value": "c:/projects"}
|
||||
]
|
||||
},
|
||||
{
|
||||
key:"credentials_url", label:"Credentials url",
|
||||
"value":"'c:/projects/cred.json'", "type": "text",
|
||||
"namespace": "{project_setting}/global/sync_server/
|
||||
sites"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
allowed_sites = set()
|
||||
sites = self.get_all_site_configs(project_name)
|
||||
if project_name:
|
||||
# Local Settings can select only from allowed sites for project
|
||||
allowed_sites.update(set(self.get_active_sites(project_name)))
|
||||
allowed_sites.update(set(self.get_remote_sites(project_name)))
|
||||
|
||||
editable = {}
|
||||
for site_name in sites.keys():
|
||||
if allowed_sites and site_name not in allowed_sites:
|
||||
continue
|
||||
|
||||
items = self.get_configurable_items_for_site(project_name,
|
||||
site_name,
|
||||
scope)
|
||||
# Local Settings need 'local' instead of real value
|
||||
site_name = site_name.replace(get_local_site_id(), 'local')
|
||||
editable[site_name] = items
|
||||
|
||||
return editable
|
||||
|
||||
def get_local_settings_schema_for_site(self, project_name, site_name):
|
||||
"""Wrapper for Local settings - for particular 'site_name and proj."""
|
||||
return self.get_configurable_items_for_site(project_name,
|
||||
site_name,
|
||||
EditableScopes.LOCAL)
|
||||
|
||||
def get_configurable_items_for_site(self, project_name=None,
|
||||
site_name=None,
|
||||
scope=None):
|
||||
"""
|
||||
Returns list of items that could be configurable.
|
||||
|
||||
Args:
|
||||
project_name (str) - None > default project
|
||||
site_name (str)
|
||||
scope (list of utils.EditableScope)
|
||||
(optional, None is all scopes)
|
||||
|
||||
Returns:
|
||||
(list)
|
||||
[
|
||||
{
|
||||
key:"root", label:"root", type:"dict",
|
||||
"children":[
|
||||
{ "key": "work",
|
||||
"type": "text",
|
||||
"value": "c:/projects"}
|
||||
]
|
||||
}, ...
|
||||
]
|
||||
"""
|
||||
provider_name = self.get_provider_for_site(site=site_name)
|
||||
items = lib.factory.get_provider_configurable_items(provider_name)
|
||||
|
||||
if project_name:
|
||||
sync_s = self.get_sync_project_setting(project_name,
|
||||
exclude_locals=True,
|
||||
cached=False)
|
||||
else:
|
||||
sync_s = get_default_project_settings(exclude_locals=True)
|
||||
sync_s = sync_s["global"]["sync_server"]
|
||||
sync_s["sites"].update(
|
||||
self._get_default_site_configs(self.enabled))
|
||||
|
||||
editable = []
|
||||
if type(scope) is not list:
|
||||
scope = [scope]
|
||||
scope = set(scope)
|
||||
for key, properties in items.items():
|
||||
if scope is None or scope.intersection(set(properties["scope"])):
|
||||
val = sync_s.get("sites", {}).get(site_name, {}).get(key)
|
||||
|
||||
item = {
|
||||
"key": key,
|
||||
"label": properties["label"],
|
||||
"type": properties["type"]
|
||||
}
|
||||
|
||||
if properties.get("namespace"):
|
||||
item["namespace"] = properties.get("namespace")
|
||||
if "platform" in item["namespace"]:
|
||||
try:
|
||||
if val:
|
||||
val = val[platform.system().lower()]
|
||||
except KeyError:
|
||||
st = "{}'s field value {} should be".format(key, val) # noqa: E501
|
||||
log.error(st + " multiplatform dict")
|
||||
|
||||
item["namespace"] = item["namespace"].replace('{site}',
|
||||
site_name)
|
||||
children = []
|
||||
if properties["type"] == "dict":
|
||||
if val:
|
||||
for val_key, val_val in val.items():
|
||||
child = {
|
||||
"type": "text",
|
||||
"key": val_key,
|
||||
"value": val_val
|
||||
}
|
||||
children.append(child)
|
||||
|
||||
if properties["type"] == "dict":
|
||||
item["children"] = children
|
||||
else:
|
||||
item["value"] = val
|
||||
|
||||
editable.append(item)
|
||||
|
||||
return editable
|
||||
# Needs to be refactored after Settings are updated
|
||||
# # Methods for Settings to get appriate values to fill forms
|
||||
# def get_configurable_items(self, scope=None):
|
||||
# """
|
||||
# Returns list of sites that could be configurable for all projects
|
||||
#
|
||||
# Could be filtered by 'scope' argument (list)
|
||||
#
|
||||
# Args:
|
||||
# scope (list of utils.EditableScope)
|
||||
#
|
||||
# Returns:
|
||||
# (dict of list of dict)
|
||||
# {
|
||||
# siteA : [
|
||||
# {
|
||||
# key:"root", label:"root",
|
||||
# "value":"{'work': 'c:/projects'}",
|
||||
# "type": "dict",
|
||||
# "children":[
|
||||
# { "key": "work",
|
||||
# "type": "text",
|
||||
# "value": "c:/projects"}
|
||||
# ]
|
||||
# },
|
||||
# {
|
||||
# key:"credentials_url", label:"Credentials url",
|
||||
# "value":"'c:/projects/cred.json'", "type": "text", # noqa: E501
|
||||
# "namespace": "{project_setting}/global/sync_server/ # noqa: E501
|
||||
# sites"
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# """
|
||||
# editable = {}
|
||||
# applicable_projects = list(self.connection.projects())
|
||||
# applicable_projects.append(None)
|
||||
# for project in applicable_projects:
|
||||
# project_name = None
|
||||
# if project:
|
||||
# project_name = project["name"]
|
||||
#
|
||||
# items = self.get_configurable_items_for_project(project_name,
|
||||
# scope)
|
||||
# editable.update(items)
|
||||
#
|
||||
# return editable
|
||||
#
|
||||
# def get_local_settings_schema_for_project(self, project_name):
|
||||
# """Wrapper for Local settings - for specific 'project_name'"""
|
||||
# return self.get_configurable_items_for_project(project_name,
|
||||
# EditableScopes.LOCAL)
|
||||
#
|
||||
# def get_configurable_items_for_project(self, project_name=None,
|
||||
# scope=None):
|
||||
# """
|
||||
# Returns list of items that could be configurable for specific
|
||||
# 'project_name'
|
||||
#
|
||||
# Args:
|
||||
# project_name (str) - None > default project,
|
||||
# scope (list of utils.EditableScope)
|
||||
# (optional, None is all scopes, default is LOCAL)
|
||||
#
|
||||
# Returns:
|
||||
# (dict of list of dict)
|
||||
# {
|
||||
# siteA : [
|
||||
# {
|
||||
# key:"root", label:"root",
|
||||
# "type": "dict",
|
||||
# "children":[
|
||||
# { "key": "work",
|
||||
# "type": "text",
|
||||
# "value": "c:/projects"}
|
||||
# ]
|
||||
# },
|
||||
# {
|
||||
# key:"credentials_url", label:"Credentials url",
|
||||
# "value":"'c:/projects/cred.json'", "type": "text",
|
||||
# "namespace": "{project_setting}/global/sync_server/
|
||||
# sites"
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# """
|
||||
# allowed_sites = set()
|
||||
# sites = self.get_all_site_configs(project_name)
|
||||
# if project_name:
|
||||
# # Local Settings can select only from allowed sites for project
|
||||
# allowed_sites.update(set(self.get_active_sites(project_name)))
|
||||
# allowed_sites.update(set(self.get_remote_sites(project_name)))
|
||||
#
|
||||
# editable = {}
|
||||
# for site_name in sites.keys():
|
||||
# if allowed_sites and site_name not in allowed_sites:
|
||||
# continue
|
||||
#
|
||||
# items = self.get_configurable_items_for_site(project_name,
|
||||
# site_name,
|
||||
# scope)
|
||||
# # Local Settings need 'local' instead of real value
|
||||
# site_name = site_name.replace(get_local_site_id(), 'local')
|
||||
# editable[site_name] = items
|
||||
#
|
||||
# return editable
|
||||
#
|
||||
# def get_configurable_items_for_site(self, project_name=None,
|
||||
# site_name=None,
|
||||
# scope=None):
|
||||
# """
|
||||
# Returns list of items that could be configurable.
|
||||
#
|
||||
# Args:
|
||||
# project_name (str) - None > default project
|
||||
# site_name (str)
|
||||
# scope (list of utils.EditableScope)
|
||||
# (optional, None is all scopes)
|
||||
#
|
||||
# Returns:
|
||||
# (list)
|
||||
# [
|
||||
# {
|
||||
# key:"root", label:"root", type:"dict",
|
||||
# "children":[
|
||||
# { "key": "work",
|
||||
# "type": "text",
|
||||
# "value": "c:/projects"}
|
||||
# ]
|
||||
# }, ...
|
||||
# ]
|
||||
# """
|
||||
# provider_name = self.get_provider_for_site(site=site_name)
|
||||
# items = lib.factory.get_provider_configurable_items(provider_name)
|
||||
#
|
||||
# if project_name:
|
||||
# sync_s = self.get_sync_project_setting(project_name,
|
||||
# exclude_locals=True,
|
||||
# cached=False)
|
||||
# else:
|
||||
# sync_s = get_default_project_settings(exclude_locals=True)
|
||||
# sync_s = sync_s["global"]["sync_server"]
|
||||
# sync_s["sites"].update(
|
||||
# self._get_default_site_configs(self.enabled))
|
||||
#
|
||||
# editable = []
|
||||
# if type(scope) is not list:
|
||||
# scope = [scope]
|
||||
# scope = set(scope)
|
||||
# for key, properties in items.items():
|
||||
# if scope is None or scope.intersection(set(properties["scope"])):
|
||||
# val = sync_s.get("sites", {}).get(site_name, {}).get(key)
|
||||
#
|
||||
# item = {
|
||||
# "key": key,
|
||||
# "label": properties["label"],
|
||||
# "type": properties["type"]
|
||||
# }
|
||||
#
|
||||
# if properties.get("namespace"):
|
||||
# item["namespace"] = properties.get("namespace")
|
||||
# if "platform" in item["namespace"]:
|
||||
# try:
|
||||
# if val:
|
||||
# val = val[platform.system().lower()]
|
||||
# except KeyError:
|
||||
# st = "{}'s field value {} should be".format(key, val) # noqa: E501
|
||||
# log.error(st + " multiplatform dict")
|
||||
#
|
||||
# item["namespace"] = item["namespace"].replace('{site}',
|
||||
# site_name)
|
||||
# children = []
|
||||
# if properties["type"] == "dict":
|
||||
# if val:
|
||||
# for val_key, val_val in val.items():
|
||||
# child = {
|
||||
# "type": "text",
|
||||
# "key": val_key,
|
||||
# "value": val_val
|
||||
# }
|
||||
# children.append(child)
|
||||
#
|
||||
# if properties["type"] == "dict":
|
||||
# item["children"] = children
|
||||
# else:
|
||||
# item["value"] = val
|
||||
#
|
||||
# editable.append(item)
|
||||
#
|
||||
# return editable
|
||||
|
||||
def reset_timer(self):
|
||||
"""
|
||||
|
|
@ -611,7 +645,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule):
|
|||
enabled_projects = []
|
||||
|
||||
if self.enabled:
|
||||
for project in self.connection.projects():
|
||||
for project in self.connection.projects(projection={"name": 1}):
|
||||
project_name = project["name"]
|
||||
project_settings = self.get_sync_project_setting(project_name)
|
||||
if project_settings and project_settings.get("enabled"):
|
||||
|
|
|
|||
15
openpype/modules/example_addons/example_addon/__init__.py
Normal file
15
openpype/modules/example_addons/example_addon/__init__.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
""" Addon class definition and Settings definition must be imported here.
|
||||
|
||||
If addon class or settings definition won't be here their definition won't
|
||||
be found by OpenPype discovery.
|
||||
"""
|
||||
|
||||
from .addon import (
|
||||
AddonSettingsDef,
|
||||
ExampleAddon
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
"AddonSettingsDef",
|
||||
"ExampleAddon"
|
||||
)
|
||||
132
openpype/modules/example_addons/example_addon/addon.py
Normal file
132
openpype/modules/example_addons/example_addon/addon.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
"""Addon definition is located here.
|
||||
|
||||
Import of python packages that may not be available should not be imported
|
||||
in global space here until are required or used.
|
||||
- Qt related imports
|
||||
- imports of Python 3 packages
|
||||
- we still support Python 2 hosts where addon definition should available
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from openpype.modules import (
|
||||
JsonFilesSettingsDef,
|
||||
OpenPypeAddOn
|
||||
)
|
||||
# Import interface defined by this addon to be able find other addons using it
|
||||
from openpype_interfaces import (
|
||||
IExampleInterface,
|
||||
IPluginPaths,
|
||||
ITrayAction
|
||||
)
|
||||
|
||||
|
||||
# Settings definition of this addon using `JsonFilesSettingsDef`
|
||||
# - JsonFilesSettingsDef is prepared settings definition using json files
|
||||
# to define settings and store default values
|
||||
class AddonSettingsDef(JsonFilesSettingsDef):
|
||||
# This will add prefixes to every schema and template from `schemas`
|
||||
# subfolder.
|
||||
# - it is not required to fill the prefix but it is highly
|
||||
# recommended as schemas and templates may have name clashes across
|
||||
# multiple addons
|
||||
# - it is also recommended that prefix has addon name in it
|
||||
schema_prefix = "example_addon"
|
||||
|
||||
def get_settings_root_path(self):
|
||||
"""Implemented abstract class of JsonFilesSettingsDef.
|
||||
|
||||
Return directory path where json files defying addon settings are
|
||||
located.
|
||||
"""
|
||||
return os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"settings"
|
||||
)
|
||||
|
||||
|
||||
class ExampleAddon(OpenPypeAddOn, IPluginPaths, ITrayAction):
|
||||
"""This Addon has defined it's settings and interface.
|
||||
|
||||
This example has system settings with an enabled option. And use
|
||||
few other interfaces:
|
||||
- `IPluginPaths` to define custom plugin paths
|
||||
- `ITrayAction` to be shown in tray tool
|
||||
"""
|
||||
label = "Example Addon"
|
||||
name = "example_addon"
|
||||
|
||||
def initialize(self, settings):
|
||||
"""Initialization of addon."""
|
||||
module_settings = settings[self.name]
|
||||
# Enabled by settings
|
||||
self.enabled = module_settings.get("enabled", False)
|
||||
|
||||
# Prepare variables that can be used or set afterwards
|
||||
self._connected_modules = None
|
||||
# UI which must not be created at this time
|
||||
self._dialog = None
|
||||
|
||||
def tray_init(self):
|
||||
"""Implementation of abstract method for `ITrayAction`.
|
||||
|
||||
We're definitely in tray tool so we can pre create dialog.
|
||||
"""
|
||||
|
||||
self._create_dialog()
|
||||
|
||||
def connect_with_modules(self, enabled_modules):
|
||||
"""Method where you should find connected modules.
|
||||
|
||||
It is triggered by OpenPype modules manager at the best possible time.
|
||||
Some addons and modules may required to connect with other modules
|
||||
before their main logic is executed so changes would require to restart
|
||||
whole process.
|
||||
"""
|
||||
self._connected_modules = []
|
||||
for module in enabled_modules:
|
||||
if isinstance(module, IExampleInterface):
|
||||
self._connected_modules.append(module)
|
||||
|
||||
def _create_dialog(self):
|
||||
# Don't recreate dialog if already exists
|
||||
if self._dialog is not None:
|
||||
return
|
||||
|
||||
from .widgets import MyExampleDialog
|
||||
|
||||
self._dialog = MyExampleDialog()
|
||||
|
||||
def show_dialog(self):
|
||||
"""Show dialog with connected modules.
|
||||
|
||||
This can be called from anywhere but can also crash in headless mode.
|
||||
There is no way to prevent addon to do invalid operations if he's
|
||||
not handling them.
|
||||
"""
|
||||
# Make sure dialog is created
|
||||
self._create_dialog()
|
||||
# Change value of dialog by current state
|
||||
self._dialog.set_connected_modules(self.get_connected_modules())
|
||||
# Show dialog
|
||||
self._dialog.open()
|
||||
|
||||
def get_connected_modules(self):
|
||||
"""Custom implementation of addon."""
|
||||
names = set()
|
||||
if self._connected_modules is not None:
|
||||
for module in self._connected_modules:
|
||||
names.add(module.name)
|
||||
return names
|
||||
|
||||
def on_action_trigger(self):
|
||||
"""Implementation of abstract method for `ITrayAction`."""
|
||||
self.show_dialog()
|
||||
|
||||
def get_plugin_paths(self):
|
||||
"""Implementation of abstract method for `IPluginPaths`."""
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
return {
|
||||
"publish": [os.path.join(current_dir, "plugins", "publish")]
|
||||
}
|
||||
28
openpype/modules/example_addons/example_addon/interfaces.py
Normal file
28
openpype/modules/example_addons/example_addon/interfaces.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
""" Using interfaces is one way of connecting multiple OpenPype Addons/Modules.
|
||||
|
||||
Interfaces must be in `interfaces.py` file (or folder). Interfaces should not
|
||||
import module logic or other module in global namespace. That is because
|
||||
all of them must be imported before all OpenPype AddOns and Modules.
|
||||
|
||||
Ideally they should just define abstract and helper methods. If interface
|
||||
require any logic or connection it should be defined in module.
|
||||
|
||||
Keep in mind that attributes and methods will be added to other addon
|
||||
attributes and methods so they should be unique and ideally contain
|
||||
addon name in it's name.
|
||||
"""
|
||||
|
||||
from abc import abstractmethod
|
||||
from openpype.modules import OpenPypeInterface
|
||||
|
||||
|
||||
class IExampleInterface(OpenPypeInterface):
|
||||
"""Example interface of addon."""
|
||||
_example_module = None
|
||||
|
||||
def get_example_module(self):
|
||||
return self._example_module
|
||||
|
||||
@abstractmethod
|
||||
def example_method_of_example_interface(self):
|
||||
pass
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class CollectExampleAddon(pyblish.api.ContextPlugin):
|
||||
order = pyblish.api.CollectorOrder + 0.4
|
||||
label = "Collect Example Addon"
|
||||
|
||||
def process(self, context):
|
||||
self.log.info("I'm in example addon's plugin!")
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"project_settings/example_addon": {
|
||||
"number": 0,
|
||||
"color_1": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"color_2": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"modules/example_addon": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"project_settings/global": {
|
||||
"type": "schema",
|
||||
"name": "example_addon/main"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"system_settings/modules": {
|
||||
"type": "schema",
|
||||
"name": "example_addon/main"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"key": "example_addon",
|
||||
"label": "Example addon",
|
||||
"collapsible": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "number",
|
||||
"key": "number",
|
||||
"label": "This is your lucky number:",
|
||||
"minimum": 7,
|
||||
"maximum": 7,
|
||||
"decimals": 0
|
||||
},
|
||||
{
|
||||
"type": "template",
|
||||
"name": "example_addon/the_template",
|
||||
"template_data": [
|
||||
{
|
||||
"name": "color_1",
|
||||
"label": "Color 1"
|
||||
},
|
||||
{
|
||||
"name": "color_2",
|
||||
"label": "Color 2"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
[
|
||||
{
|
||||
"type": "list-strict",
|
||||
"key": "{name}",
|
||||
"label": "{label}",
|
||||
"object_types": [
|
||||
{
|
||||
"label": "Red",
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"decimal": 3
|
||||
},
|
||||
{
|
||||
"label": "Green",
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"decimal": 3
|
||||
},
|
||||
{
|
||||
"label": "Blue",
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"decimal": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"key": "example_addon",
|
||||
"label": "Example addon",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
39
openpype/modules/example_addons/example_addon/widgets.py
Normal file
39
openpype/modules/example_addons/example_addon/widgets.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
from Qt import QtWidgets
|
||||
|
||||
from openpype.style import load_stylesheet
|
||||
|
||||
|
||||
class MyExampleDialog(QtWidgets.QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super(MyExampleDialog, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle("Connected modules")
|
||||
|
||||
label_widget = QtWidgets.QLabel(self)
|
||||
|
||||
ok_btn = QtWidgets.QPushButton("OK", self)
|
||||
btns_layout = QtWidgets.QHBoxLayout()
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(ok_btn)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(label_widget)
|
||||
layout.addLayout(btns_layout)
|
||||
|
||||
ok_btn.clicked.connect(self._on_ok_clicked)
|
||||
|
||||
self._label_widget = label_widget
|
||||
|
||||
self.setStyleSheet(load_stylesheet())
|
||||
|
||||
def _on_ok_clicked(self):
|
||||
self.done(1)
|
||||
|
||||
def set_connected_modules(self, connected_modules):
|
||||
if connected_modules:
|
||||
message = "\n".join(connected_modules)
|
||||
else:
|
||||
message = (
|
||||
"Other enabled modules/addons are not using my interface."
|
||||
)
|
||||
self._label_widget.setText(message)
|
||||
9
openpype/modules/example_addons/tiny_addon.py
Normal file
9
openpype/modules/example_addons/tiny_addon.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from openpype.modules import OpenPypeAddOn
|
||||
|
||||
|
||||
class TinyAddon(OpenPypeAddOn):
|
||||
"""This is tiniest possible addon.
|
||||
|
||||
This addon won't do much but will exist in OpenPype modules environment.
|
||||
"""
|
||||
name = "tiniest_addon_ever"
|
||||
|
|
@ -8,7 +8,7 @@ from openpype.api import get_system_settings
|
|||
|
||||
class StopTimer(pyblish.api.ContextPlugin):
|
||||
label = "Stop Timer"
|
||||
order = pyblish.api.ExtractorOrder - 0.5
|
||||
order = pyblish.api.ExtractorOrder - 0.49
|
||||
hosts = ["*"]
|
||||
|
||||
def process(self, context):
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@
|
|||
"environment": {}
|
||||
},
|
||||
"__dynamic_keys_labels__": {
|
||||
"13-0": "13.0 (Testing only)",
|
||||
"13-0": "13.0",
|
||||
"12-2": "12.2",
|
||||
"12-0": "12.0",
|
||||
"11-3": "11.3",
|
||||
|
|
@ -331,7 +331,7 @@
|
|||
"environment": {}
|
||||
},
|
||||
"__dynamic_keys_labels__": {
|
||||
"13-0": "13.0 (Testing only)",
|
||||
"13-0": "13.0",
|
||||
"12-2": "12.2",
|
||||
"12-0": "12.0",
|
||||
"11-3": "11.3",
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@ from .enum_entity import (
|
|||
AppsEnumEntity,
|
||||
ToolsEnumEntity,
|
||||
TaskTypeEnumEntity,
|
||||
ProvidersEnum,
|
||||
DeadlineUrlEnumEntity,
|
||||
AnatomyTemplatesEnumEntity
|
||||
)
|
||||
|
|
@ -113,7 +112,10 @@ from .enum_entity import (
|
|||
from .list_entity import ListEntity
|
||||
from .dict_immutable_keys_entity import DictImmutableKeysEntity
|
||||
from .dict_mutable_keys_entity import DictMutableKeysEntity
|
||||
from .dict_conditional import DictConditionalEntity
|
||||
from .dict_conditional import (
|
||||
DictConditionalEntity,
|
||||
SyncServerProviders
|
||||
)
|
||||
|
||||
from .anatomy_entities import AnatomyEntity
|
||||
|
||||
|
|
@ -161,7 +163,6 @@ __all__ = (
|
|||
"AppsEnumEntity",
|
||||
"ToolsEnumEntity",
|
||||
"TaskTypeEnumEntity",
|
||||
"ProvidersEnum",
|
||||
"DeadlineUrlEnumEntity",
|
||||
"AnatomyTemplatesEnumEntity",
|
||||
|
||||
|
|
@ -172,6 +173,7 @@ __all__ = (
|
|||
"DictMutableKeysEntity",
|
||||
|
||||
"DictConditionalEntity",
|
||||
"SyncServerProviders",
|
||||
|
||||
"AnatomyEntity"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -724,3 +724,49 @@ class DictConditionalEntity(ItemEntity):
|
|||
for children in self.children.values():
|
||||
for child_entity in children:
|
||||
child_entity.reset_callbacks()
|
||||
|
||||
|
||||
class SyncServerProviders(DictConditionalEntity):
|
||||
schema_types = ["sync-server-providers"]
|
||||
|
||||
def _add_children(self):
|
||||
self.enum_key = "provider"
|
||||
self.enum_label = "Provider"
|
||||
|
||||
enum_children = self._get_enum_children()
|
||||
if not enum_children:
|
||||
enum_children.append({
|
||||
"key": None,
|
||||
"label": "< Nothing >"
|
||||
})
|
||||
self.enum_children = enum_children
|
||||
|
||||
super(SyncServerProviders, self)._add_children()
|
||||
|
||||
def _get_enum_children(self):
|
||||
from openpype_modules import sync_server
|
||||
|
||||
from openpype_modules.sync_server.providers import lib as lib_providers
|
||||
|
||||
provider_code_to_label = {}
|
||||
providers = lib_providers.factory.providers
|
||||
for provider_code, provider_info in providers.items():
|
||||
provider, _ = provider_info
|
||||
provider_code_to_label[provider_code] = provider.LABEL
|
||||
|
||||
system_settings_schema = (
|
||||
sync_server
|
||||
.SyncServerModule
|
||||
.get_system_settings_schema()
|
||||
)
|
||||
|
||||
enum_children = []
|
||||
for provider_code, configurables in system_settings_schema.items():
|
||||
label = provider_code_to_label.get(provider_code) or provider_code
|
||||
|
||||
enum_children.append({
|
||||
"key": provider_code,
|
||||
"label": label,
|
||||
"children": configurables
|
||||
})
|
||||
return enum_children
|
||||
|
|
|
|||
|
|
@ -376,11 +376,16 @@ class TaskTypeEnumEntity(BaseEnumEntity):
|
|||
schema_types = ["task-types-enum"]
|
||||
|
||||
def _item_initalization(self):
|
||||
self.multiselection = True
|
||||
self.value_on_not_set = []
|
||||
self.multiselection = self.schema_data.get("multiselection", True)
|
||||
if self.multiselection:
|
||||
self.valid_value_types = (list, )
|
||||
self.value_on_not_set = []
|
||||
else:
|
||||
self.valid_value_types = (STRING_TYPE, )
|
||||
self.value_on_not_set = ""
|
||||
|
||||
self.enum_items = []
|
||||
self.valid_keys = set()
|
||||
self.valid_value_types = (list, )
|
||||
self.placeholder = None
|
||||
|
||||
def _get_enum_values(self):
|
||||
|
|
@ -396,53 +401,51 @@ class TaskTypeEnumEntity(BaseEnumEntity):
|
|||
|
||||
return enum_items, valid_keys
|
||||
|
||||
def _convert_value_for_current_state(self, source_value):
|
||||
if self.multiselection:
|
||||
output = []
|
||||
for key in source_value:
|
||||
if key in self.valid_keys:
|
||||
output.append(key)
|
||||
return output
|
||||
|
||||
if source_value not in self.valid_keys:
|
||||
# Take first item from enum items
|
||||
for item in self.enum_items:
|
||||
for key in item.keys():
|
||||
source_value = key
|
||||
break
|
||||
return source_value
|
||||
|
||||
def set_override_state(self, *args, **kwargs):
|
||||
super(TaskTypeEnumEntity, self).set_override_state(*args, **kwargs)
|
||||
|
||||
self.enum_items, self.valid_keys = self._get_enum_values()
|
||||
new_value = []
|
||||
for key in self._current_value:
|
||||
if key in self.valid_keys:
|
||||
new_value.append(key)
|
||||
self._current_value = new_value
|
||||
|
||||
if self.multiselection:
|
||||
new_value = []
|
||||
for key in self._current_value:
|
||||
if key in self.valid_keys:
|
||||
new_value.append(key)
|
||||
|
||||
class ProvidersEnum(BaseEnumEntity):
|
||||
schema_types = ["providers-enum"]
|
||||
if self._current_value != new_value:
|
||||
self.set(new_value)
|
||||
else:
|
||||
if not self.enum_items:
|
||||
self.valid_keys.add("")
|
||||
self.enum_items.append({"": "< Empty >"})
|
||||
|
||||
def _item_initalization(self):
|
||||
self.multiselection = False
|
||||
self.value_on_not_set = ""
|
||||
self.enum_items = []
|
||||
self.valid_keys = set()
|
||||
self.valid_value_types = (str, )
|
||||
self.placeholder = None
|
||||
for item in self.enum_items:
|
||||
for key in item.keys():
|
||||
value_on_not_set = key
|
||||
break
|
||||
|
||||
def _get_enum_values(self):
|
||||
from openpype_modules.sync_server.providers import lib as lib_providers
|
||||
|
||||
providers = lib_providers.factory.providers
|
||||
|
||||
valid_keys = set()
|
||||
valid_keys.add('')
|
||||
enum_items = [{'': 'Choose Provider'}]
|
||||
for provider_code, provider_info in providers.items():
|
||||
provider, _ = provider_info
|
||||
enum_items.append({provider_code: provider.LABEL})
|
||||
valid_keys.add(provider_code)
|
||||
|
||||
return enum_items, valid_keys
|
||||
|
||||
def set_override_state(self, *args, **kwargs):
|
||||
super(ProvidersEnum, self).set_override_state(*args, **kwargs)
|
||||
|
||||
self.enum_items, self.valid_keys = self._get_enum_values()
|
||||
|
||||
value_on_not_set = list(self.valid_keys)[0]
|
||||
if self._current_value is NOT_SET:
|
||||
self._current_value = value_on_not_set
|
||||
|
||||
self.value_on_not_set = value_on_not_set
|
||||
self.value_on_not_set = value_on_not_set
|
||||
if (
|
||||
self._current_value is NOT_SET
|
||||
or self._current_value not in self.valid_keys
|
||||
):
|
||||
self.set(value_on_not_set)
|
||||
|
||||
|
||||
class DeadlineUrlEnumEntity(BaseEnumEntity):
|
||||
|
|
|
|||
|
|
@ -379,6 +379,11 @@ class NumberEntity(InputEntity):
|
|||
|
||||
# UI specific attributes
|
||||
self.show_slider = self.schema_data.get("show_slider", False)
|
||||
steps = self.schema_data.get("steps", None)
|
||||
# Make sure that steps are not set to `0`
|
||||
if steps == 0:
|
||||
steps = None
|
||||
self.steps = steps
|
||||
|
||||
def _convert_to_valid_type(self, value):
|
||||
if isinstance(value, str):
|
||||
|
|
|
|||
|
|
@ -168,9 +168,13 @@ class SchemasHub:
|
|||
if isinstance(def_schema, dict):
|
||||
def_schema = [def_schema]
|
||||
|
||||
all_def_schema = []
|
||||
for item in def_schema:
|
||||
item["_dynamic_schema_id"] = def_id
|
||||
output.extend(def_schema)
|
||||
items = self.resolve_schema_data(item)
|
||||
for _item in items:
|
||||
_item["_dynamic_schema_id"] = def_id
|
||||
all_def_schema.extend(items)
|
||||
output.extend(all_def_schema)
|
||||
return output
|
||||
|
||||
def get_template_name(self, item_def, default=None):
|
||||
|
|
|
|||
|
|
@ -316,6 +316,7 @@ How output of the schema could look like on save:
|
|||
- key `"decimal"` defines how many decimal places will be used, 0 is for integer input (Default: `0`)
|
||||
- key `"minimum"` as minimum allowed number to enter (Default: `-99999`)
|
||||
- key `"maxium"` as maximum allowed number to enter (Default: `99999`)
|
||||
- key `"steps"` will change single step value of UI inputs (using arrows and wheel scroll)
|
||||
- for UI it is possible to show slider to enable this option set `show_slider` to `true`
|
||||
```
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@
|
|||
"type": "number",
|
||||
"key": "AVALON_TIMEOUT",
|
||||
"minimum": 0,
|
||||
"label": "Avalon Mongo Timeout (ms)"
|
||||
"label": "Avalon Mongo Timeout (ms)",
|
||||
"steps": 100
|
||||
},
|
||||
{
|
||||
"type": "path",
|
||||
|
|
@ -121,14 +122,7 @@
|
|||
"collapsible_key": false,
|
||||
"object_type":
|
||||
{
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"type": "providers-enum",
|
||||
"key": "provider",
|
||||
"label": "Provider"
|
||||
}
|
||||
]
|
||||
"type": "sync-server-providers"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -242,6 +236,10 @@
|
|||
"label": "Enabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dynamic_schema",
|
||||
"name": "system_settings/modules"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -411,7 +411,8 @@ class NumberWidget(InputWidget):
|
|||
kwargs = {
|
||||
"minimum": self.entity.minimum,
|
||||
"maximum": self.entity.maximum,
|
||||
"decimal": self.entity.decimal
|
||||
"decimal": self.entity.decimal,
|
||||
"steps": self.entity.steps
|
||||
}
|
||||
self.input_field = NumberSpinBox(self.content_widget, **kwargs)
|
||||
input_field_stretch = 1
|
||||
|
|
@ -426,6 +427,10 @@ class NumberWidget(InputWidget):
|
|||
int(self.entity.minimum * slider_multiplier),
|
||||
int(self.entity.maximum * slider_multiplier)
|
||||
)
|
||||
if self.entity.steps is not None:
|
||||
slider_widget.setSingleStep(
|
||||
self.entity.steps * slider_multiplier
|
||||
)
|
||||
|
||||
self.content_layout.addWidget(slider_widget, 1)
|
||||
|
||||
|
|
|
|||
|
|
@ -92,11 +92,15 @@ class NumberSpinBox(QtWidgets.QDoubleSpinBox):
|
|||
min_value = kwargs.pop("minimum", -99999)
|
||||
max_value = kwargs.pop("maximum", 99999)
|
||||
decimals = kwargs.pop("decimal", 0)
|
||||
steps = kwargs.pop("steps", None)
|
||||
|
||||
super(NumberSpinBox, self).__init__(*args, **kwargs)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
self.setDecimals(decimals)
|
||||
self.setMinimum(min_value)
|
||||
self.setMaximum(max_value)
|
||||
if steps is not None:
|
||||
self.setSingleStep(steps)
|
||||
|
||||
def focusInEvent(self, event):
|
||||
super(NumberSpinBox, self).focusInEvent(event)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit f48fce09c0986c1fd7f6731de33907be46b436c5
|
||||
Subproject commit 1e94241ffe2dd7ce65ca66b08e452ffc03180235
|
||||
|
|
@ -55,7 +55,7 @@ openpype_console tray --debug
|
|||
---
|
||||
### `launch` arguments {#eventserver-arguments}
|
||||
You have to set either proper environment variables to provide URL and credentials or use
|
||||
option to specify them. If you use `--store_credentials` provided credentials will be stored for later use.
|
||||
option to specify them.
|
||||
|
||||
| Argument | Description |
|
||||
| --- | --- |
|
||||
|
|
@ -63,16 +63,13 @@ option to specify them. If you use `--store_credentials` provided credentials wi
|
|||
| `--ftrack-url` | URL to ftrack server (can be set with `FTRACK_SERVER`) |
|
||||
| `--ftrack-user` |user name to log in to ftrack (can be set with `FTRACK_API_USER`) |
|
||||
| `--ftrack-api-key` | ftrack api key (can be set with `FTRACK_API_KEY`) |
|
||||
| `--ftrack-events-path` | path to event server plugins (can be set with `FTRACK_EVENTS_PATH`) |
|
||||
| `--no-stored-credentials` | will use credential specified with options above |
|
||||
| `--store-credentials` | will store credentials to file for later use |
|
||||
| `--legacy` | run event server without mongo storing |
|
||||
| `--clockify-api-key` | Clockify API key (can be set with `CLOCKIFY_API_KEY`) |
|
||||
| `--clockify-workspace` | Clockify workspace (can be set with `CLOCKIFY_WORKSPACE`) |
|
||||
|
||||
To run ftrack event server:
|
||||
```shell
|
||||
openpype_console eventserver --ftrack-url=<url> --ftrack-user=<user> --ftrack-api-key=<key> --ftrack-events-path=<path> --no-stored-credentials --store-credentials
|
||||
openpype_console eventserver --ftrack-url=<url> --ftrack-user=<user> --ftrack-api-key=<key>
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -51,10 +51,7 @@ There are specific launch arguments for event server. With `openpype_console eve
|
|||
|
||||
- **`--ftrack-user "your.username"`** : Ftrack Username
|
||||
- **`--ftrack-api-key "00000aaa-11bb-22cc-33dd-444444eeeee"`** : User's API key
|
||||
- **`--store-crededentials`** : Entered credentials will be stored for next launch with this argument _(It is not needed to enter **ftrackuser** and **ftrackapikey** args on next launch)_
|
||||
- **`--no-stored-credentials`** : Stored credentials are loaded first so if you want to change credentials use this argument
|
||||
- `--ftrack-url "https://yourdomain.ftrackapp.com/"` : Ftrack server URL _(it is not needed to enter if you have set `FTRACK_SERVER` in OpenPype' environments)_
|
||||
- `--ftrack-events-path "//Paths/To/Events/"` : Paths to events folder. May contain multiple paths separated by `;`. _(it is not needed to enter if you have set `FTRACK_EVENTS_PATH` in OpenPype' environments)_
|
||||
|
||||
So if you want to use OpenPype's environments then you can launch event server for first time with these arguments `openpype_console.exe eventserver --ftrack-user "my.username" --ftrack-api-key "00000aaa-11bb-22cc-33dd-444444eeeee" --store-credentials`. Since that time, if everything was entered correctly, you can launch event server with `openpype_console.exe eventserver`.
|
||||
|
||||
|
|
@ -64,8 +61,6 @@ So if you want to use OpenPype's environments then you can launch event server f
|
|||
- `FTRACK_API_USER` - Username _("your.username")_
|
||||
- `FTRACK_API_KEY` - User's API key _("00000aaa-11bb-22cc-33dd-444444eeeee")_
|
||||
- `FTRACK_SERVER` - Ftrack server url _("<https://yourdomain.ftrackapp.com/">)_
|
||||
- `FTRACK_EVENTS_PATH` - Paths to events _("//Paths/To/Events/")_
|
||||
We do not recommend you this way.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
|
@ -103,10 +98,12 @@ Event server should **not** run more than once! It may cause major issues.
|
|||
`sudo vi /opt/openpype/run_event_server.sh`
|
||||
- add content to the file:
|
||||
```sh
|
||||
#!/usr/bin/env
|
||||
export OPENPYPE_DEBUG=3
|
||||
pushd /mnt/pipeline/prod/openpype-setup
|
||||
. openpype_console eventserver --ftrack-user <openpype-admin-user> --ftrack-api-key <api-key>
|
||||
#!/usr/bin/env bash
|
||||
export OPENPYPE_DEBUG=1
|
||||
export OPENPYPE_MONGO=<openpype-mongo-url>
|
||||
|
||||
pushd /mnt/path/to/openpype
|
||||
./openpype_console eventserver --ftrack-user <openpype-admin-user> --ftrack-api-key <api-key>
|
||||
```
|
||||
- change file permission:
|
||||
`sudo chmod 0755 /opt/openpype/run_event_server.sh`
|
||||
|
|
@ -146,9 +143,11 @@ WantedBy=multi-user.target
|
|||
- add content to the service file:
|
||||
```sh
|
||||
@echo off
|
||||
set OPENPYPE_DEBUG=3
|
||||
pushd \\path\to\file\
|
||||
openpype_console.exe eventserver --ftrack-user <openpype-admin-user> --ftrack-api-key <api-key>
|
||||
set OPENPYPE_DEBUG=1
|
||||
set OPENPYPE_MONGO=<openpype-mongo-url>
|
||||
|
||||
pushd \\path\to\openpype
|
||||
openpype_console.exe eventserver --ftrack-user <openpype-admin-user> --ftrack-api-key <api-key>
|
||||
```
|
||||
- download and install `nssm.cc`
|
||||
- create Windows service according to nssm.cc manual
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue