mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into feature/OP-3593_Move-load-functions-into-pipeline
This commit is contained in:
commit
d232ad4f41
31 changed files with 726 additions and 70 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -1,23 +1,29 @@
|
|||
# Changelog
|
||||
|
||||
## [3.12.2-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
## [3.12.2-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.1...HEAD)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- General: Interactive console in cli [\#3526](https://github.com/pypeclub/OpenPype/pull/3526)
|
||||
- Ftrack: Automatic daily review session creation can define trigger hour [\#3516](https://github.com/pypeclub/OpenPype/pull/3516)
|
||||
- Ftrack: add source into Note [\#3509](https://github.com/pypeclub/OpenPype/pull/3509)
|
||||
- Ftrack: Trigger custom ftrack topic of project structure creation [\#3506](https://github.com/pypeclub/OpenPype/pull/3506)
|
||||
- Settings UI: Add extract to file action on project view [\#3505](https://github.com/pypeclub/OpenPype/pull/3505)
|
||||
- Add pack and unpack convenience scripts [\#3502](https://github.com/pypeclub/OpenPype/pull/3502)
|
||||
- General: Event system [\#3499](https://github.com/pypeclub/OpenPype/pull/3499)
|
||||
- NewPublisher: Keep plugins with mismatch target in report [\#3498](https://github.com/pypeclub/OpenPype/pull/3498)
|
||||
- Nuke: load clip with options from settings [\#3497](https://github.com/pypeclub/OpenPype/pull/3497)
|
||||
- TrayPublisher: implemented render\_mov\_batch [\#3486](https://github.com/pypeclub/OpenPype/pull/3486)
|
||||
- Migrate basic families to the new Tray Publisher [\#3469](https://github.com/pypeclub/OpenPype/pull/3469)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Additional fixes for powershell scripts [\#3525](https://github.com/pypeclub/OpenPype/pull/3525)
|
||||
- Maya: Added wrapper around cmds.setAttr [\#3523](https://github.com/pypeclub/OpenPype/pull/3523)
|
||||
- General: Fix hash of centos oiio archive [\#3519](https://github.com/pypeclub/OpenPype/pull/3519)
|
||||
- Maya: Renderman display output fix [\#3514](https://github.com/pypeclub/OpenPype/pull/3514)
|
||||
- TrayPublisher: Simple creation enhancements and fixes [\#3513](https://github.com/pypeclub/OpenPype/pull/3513)
|
||||
- NewPublisher: Publish attributes are properly collected [\#3510](https://github.com/pypeclub/OpenPype/pull/3510)
|
||||
- TrayPublisher: Make sure host name is filled [\#3504](https://github.com/pypeclub/OpenPype/pull/3504)
|
||||
|
|
@ -25,6 +31,7 @@
|
|||
|
||||
**🔀 Refactored code**
|
||||
|
||||
- General: Client docstrings cleanup [\#3529](https://github.com/pypeclub/OpenPype/pull/3529)
|
||||
- TimersManager: Use query functions [\#3495](https://github.com/pypeclub/OpenPype/pull/3495)
|
||||
|
||||
## [3.12.1](https://github.com/pypeclub/OpenPype/tree/3.12.1) (2022-07-13)
|
||||
|
|
@ -51,7 +58,6 @@
|
|||
- Blender: Bugfix - Set fps properly on open [\#3426](https://github.com/pypeclub/OpenPype/pull/3426)
|
||||
- Hiero: Add custom scripts menu [\#3425](https://github.com/pypeclub/OpenPype/pull/3425)
|
||||
- Blender: pre pyside install for all platforms [\#3400](https://github.com/pypeclub/OpenPype/pull/3400)
|
||||
- Maya: Add additional playblast options to review Extractor. [\#3384](https://github.com/pypeclub/OpenPype/pull/3384)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
|
|
@ -71,7 +77,6 @@
|
|||
- Maya: fix hashing in Python 3 for tile rendering [\#3447](https://github.com/pypeclub/OpenPype/pull/3447)
|
||||
- LogViewer: Escape html characters in log message [\#3443](https://github.com/pypeclub/OpenPype/pull/3443)
|
||||
- Nuke: Slate frame is integrated [\#3427](https://github.com/pypeclub/OpenPype/pull/3427)
|
||||
- Maya: Camera extra data - additional fix for \#3304 [\#3386](https://github.com/pypeclub/OpenPype/pull/3386)
|
||||
|
||||
**🔀 Refactored code**
|
||||
|
||||
|
|
@ -85,8 +90,6 @@
|
|||
- General: Move publish plugin and publish render abstractions [\#3442](https://github.com/pypeclub/OpenPype/pull/3442)
|
||||
- General: Use Anatomy after move to pipeline [\#3436](https://github.com/pypeclub/OpenPype/pull/3436)
|
||||
- General: Anatomy moved to pipeline [\#3435](https://github.com/pypeclub/OpenPype/pull/3435)
|
||||
- Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380)
|
||||
- Resolve: Use client query functions [\#3379](https://github.com/pypeclub/OpenPype/pull/3379)
|
||||
|
||||
## [3.12.0](https://github.com/pypeclub/OpenPype/tree/3.12.0) (2022-06-28)
|
||||
|
||||
|
|
@ -111,7 +114,6 @@
|
|||
- Nuke: Collect representation files based on Write [\#3407](https://github.com/pypeclub/OpenPype/pull/3407)
|
||||
- General: Filter representations before integration start [\#3398](https://github.com/pypeclub/OpenPype/pull/3398)
|
||||
- Maya: look collector typo [\#3392](https://github.com/pypeclub/OpenPype/pull/3392)
|
||||
- Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381)
|
||||
|
||||
**🔀 Refactored code**
|
||||
|
||||
|
|
@ -121,7 +123,6 @@
|
|||
- Houdini: Use client query functions [\#3395](https://github.com/pypeclub/OpenPype/pull/3395)
|
||||
- Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393)
|
||||
- Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391)
|
||||
- Maya: Use client query functions [\#3385](https://github.com/pypeclub/OpenPype/pull/3385)
|
||||
|
||||
## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"""Package for handling pype command line arguments."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import code
|
||||
import click
|
||||
|
||||
# import sys
|
||||
|
|
@ -424,3 +424,22 @@ def pack_project(project, dirpath):
|
|||
def unpack_project(zipfile, root):
|
||||
"""Create a package of project with all files and database dump."""
|
||||
PypeCommands().unpack_project(zipfile, root)
|
||||
|
||||
|
||||
@main.command()
|
||||
def interactive():
|
||||
"""Interative (Python like) console.
|
||||
|
||||
Helpfull command not only for development to directly work with python
|
||||
interpreter.
|
||||
|
||||
Warning:
|
||||
Executable 'openpype_gui' on windows won't work.
|
||||
"""
|
||||
|
||||
from openpype.version import __version__
|
||||
|
||||
banner = "OpenPype {}\nPython {} on {}".format(
|
||||
__version__, sys.version, sys.platform
|
||||
)
|
||||
code.interact(banner)
|
||||
|
|
|
|||
|
|
@ -323,6 +323,8 @@ class IntegrateBatchGroup(pyblish.api.InstancePlugin):
|
|||
def _get_shot_task_dir_path(self, instance, task_data):
|
||||
project_doc = instance.data["projectEntity"]
|
||||
asset_entity = instance.data["assetEntity"]
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
|
||||
return get_workdir(
|
||||
project_doc, asset_entity, task_data["name"], "flame")
|
||||
project_doc, asset_entity, task_data["name"], "flame", anatomy
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1087,7 +1087,7 @@ class RenderProductsRenderman(ARenderProducts):
|
|||
"d_tiff": "tif"
|
||||
}
|
||||
|
||||
displays = get_displays()["displays"]
|
||||
displays = get_displays(override_dst="render")["displays"]
|
||||
for name, display in displays.items():
|
||||
enabled = display["params"]["enable"]["value"]
|
||||
if not enabled:
|
||||
|
|
@ -1106,9 +1106,33 @@ class RenderProductsRenderman(ARenderProducts):
|
|||
display["driverNode"]["type"], "exr")
|
||||
|
||||
for camera in cameras:
|
||||
product = RenderProduct(productName=aov_name,
|
||||
ext=extensions,
|
||||
camera=camera)
|
||||
# Create render product and set it as multipart only on
|
||||
# display types supporting it. In all other cases, Renderman
|
||||
# will create separate output per channel.
|
||||
if display["driverNode"]["type"] in ["d_openexr", "d_deepexr", "d_tiff"]: # noqa
|
||||
product = RenderProduct(
|
||||
productName=aov_name,
|
||||
ext=extensions,
|
||||
camera=camera,
|
||||
multipart=True
|
||||
)
|
||||
else:
|
||||
# this code should handle the case where no multipart
|
||||
# capable format is selected. But since it involves
|
||||
# shady logic to determine what channel become what
|
||||
# lets not do that as all productions will use exr anyway.
|
||||
"""
|
||||
for channel in display['params']['displayChannels']['value']: # noqa
|
||||
product = RenderProduct(
|
||||
productName="{}_{}".format(aov_name, channel),
|
||||
ext=extensions,
|
||||
camera=camera,
|
||||
multipart=False
|
||||
)
|
||||
"""
|
||||
raise UnsupportedImageFormatException(
|
||||
"Only exr, deep exr and tiff formats are supported.")
|
||||
|
||||
products.append(product)
|
||||
|
||||
return products
|
||||
|
|
@ -1201,3 +1225,7 @@ class UnsupportedRendererException(Exception):
|
|||
|
||||
Raised when requesting data from unsupported renderer.
|
||||
"""
|
||||
|
||||
|
||||
class UnsupportedImageFormatException(Exception):
|
||||
"""Custom exception to report unsupported output image format."""
|
||||
|
|
|
|||
|
|
@ -2440,10 +2440,12 @@ def _launch_workfile_app():
|
|||
if starting_up or closing_down:
|
||||
return
|
||||
|
||||
from .pipeline import get_main_window
|
||||
|
||||
main_window = get_main_window()
|
||||
host_tools.show_workfiles(parent=main_window)
|
||||
# Make sure on top is enabled on first show so the window is not hidden
|
||||
# under main nuke window
|
||||
# - this happened on Centos 7 and it is because the focus of nuke
|
||||
# changes to the main window after showing because of initialization
|
||||
# which moves workfiles tool under it
|
||||
host_tools.show_workfiles(parent=None, on_top=True)
|
||||
|
||||
|
||||
def process_workfile_builder():
|
||||
|
|
|
|||
|
|
@ -142,6 +142,14 @@ def uninstall():
|
|||
_uninstall_menu()
|
||||
|
||||
|
||||
def _show_workfiles():
|
||||
# Make sure parent is not set
|
||||
# - this makes Workfiles tool as separated window which
|
||||
# avoid issues with reopening
|
||||
# - it is possible to explicitly change on top flag of the tool
|
||||
host_tools.show_workfiles(parent=None, on_top=False)
|
||||
|
||||
|
||||
def _install_menu():
|
||||
# uninstall original avalon menu
|
||||
main_window = get_main_window()
|
||||
|
|
@ -158,7 +166,7 @@ def _install_menu():
|
|||
menu.addSeparator()
|
||||
menu.addCommand(
|
||||
"Work Files...",
|
||||
lambda: host_tools.show_workfiles(parent=main_window)
|
||||
_show_workfiles
|
||||
)
|
||||
|
||||
menu.addSeparator()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
import copy
|
||||
import os
|
||||
import re
|
||||
|
||||
from openpype.client import get_assets, get_asset_by_name
|
||||
from openpype.lib import (
|
||||
FileDef,
|
||||
BoolDef,
|
||||
get_subset_name_with_asset_doc,
|
||||
TaskNotSetError,
|
||||
)
|
||||
from openpype.pipeline import (
|
||||
CreatedInstance,
|
||||
CreatorError
|
||||
)
|
||||
|
||||
from openpype.hosts.traypublisher.api.plugin import TrayPublishCreator
|
||||
|
||||
|
||||
class BatchMovieCreator(TrayPublishCreator):
|
||||
"""Creates instances from movie file(s).
|
||||
|
||||
Intended for .mov files, but should work for any video file.
|
||||
Doesn't handle image sequences though.
|
||||
"""
|
||||
identifier = "render_movie_batch"
|
||||
label = "Batch Movies"
|
||||
family = "render"
|
||||
description = "Publish batch of video files"
|
||||
|
||||
create_allow_context_change = False
|
||||
version_regex = re.compile(r"^(.+)_v([0-9]+)$")
|
||||
|
||||
def __init__(self, project_settings, *args, **kwargs):
|
||||
super(BatchMovieCreator, self).__init__(project_settings,
|
||||
*args, **kwargs)
|
||||
creator_settings = (
|
||||
project_settings["traypublisher"]["BatchMovieCreator"]
|
||||
)
|
||||
self.default_variants = creator_settings["default_variants"]
|
||||
self.default_tasks = creator_settings["default_tasks"]
|
||||
self.extensions = creator_settings["extensions"]
|
||||
|
||||
def get_icon(self):
|
||||
return "fa.file"
|
||||
|
||||
def create(self, subset_name, data, pre_create_data):
|
||||
file_paths = pre_create_data.get("filepath")
|
||||
if not file_paths:
|
||||
return
|
||||
|
||||
for file_info in file_paths:
|
||||
instance_data = copy.deepcopy(data)
|
||||
file_name = file_info["filenames"][0]
|
||||
filepath = os.path.join(file_info["directory"], file_name)
|
||||
instance_data["creator_attributes"] = {"filepath": filepath}
|
||||
|
||||
asset_doc, version = self.get_asset_doc_from_file_name(
|
||||
file_name, self.project_name)
|
||||
|
||||
subset_name, task_name = self._get_subset_and_task(
|
||||
asset_doc, data["variant"], self.project_name)
|
||||
|
||||
instance_data["task"] = task_name
|
||||
instance_data["asset"] = asset_doc["name"]
|
||||
|
||||
# Create new instance
|
||||
new_instance = CreatedInstance(self.family, subset_name,
|
||||
instance_data, self)
|
||||
self._store_new_instance(new_instance)
|
||||
|
||||
def get_asset_doc_from_file_name(self, source_filename, project_name):
|
||||
"""Try to parse out asset name from file name provided.
|
||||
|
||||
Artists might provide various file name formats.
|
||||
Currently handled:
|
||||
- chair.mov
|
||||
- chair_v001.mov
|
||||
- my_chair_to_upload.mov
|
||||
"""
|
||||
version = None
|
||||
asset_name = os.path.splitext(source_filename)[0]
|
||||
# Always first check if source filename is in assets
|
||||
matching_asset_doc = self._get_asset_by_name_case_not_sensitive(
|
||||
project_name, asset_name)
|
||||
|
||||
if matching_asset_doc is None:
|
||||
matching_asset_doc, version = (
|
||||
self._parse_with_version(project_name, asset_name))
|
||||
|
||||
if matching_asset_doc is None:
|
||||
matching_asset_doc = self._parse_containing(project_name,
|
||||
asset_name)
|
||||
|
||||
if matching_asset_doc is None:
|
||||
raise CreatorError(
|
||||
"Cannot guess asset name from {}".format(source_filename))
|
||||
|
||||
return matching_asset_doc, version
|
||||
|
||||
def _parse_with_version(self, project_name, asset_name):
|
||||
"""Try to parse asset name from a file name containing version too
|
||||
|
||||
Eg. 'chair_v001.mov' >> 'chair', 1
|
||||
"""
|
||||
self.log.debug((
|
||||
"Asset doc by \"{}\" was not found, trying version regex."
|
||||
).format(asset_name))
|
||||
|
||||
matching_asset_doc = version_number = None
|
||||
|
||||
regex_result = self.version_regex.findall(asset_name)
|
||||
if regex_result:
|
||||
_asset_name, _version_number = regex_result[0]
|
||||
matching_asset_doc = self._get_asset_by_name_case_not_sensitive(
|
||||
project_name, _asset_name)
|
||||
if matching_asset_doc:
|
||||
version_number = int(_version_number)
|
||||
|
||||
return matching_asset_doc, version_number
|
||||
|
||||
def _parse_containing(self, project_name, asset_name):
|
||||
"""Look if file name contains any existing asset name"""
|
||||
for asset_doc in get_assets(project_name, fields=["name"]):
|
||||
if asset_doc["name"].lower() in asset_name.lower():
|
||||
return get_asset_by_name(project_name, asset_doc["name"])
|
||||
|
||||
def _get_subset_and_task(self, asset_doc, variant, project_name):
|
||||
"""Create subset name according to standard template process"""
|
||||
task_name = self._get_task_name(asset_doc)
|
||||
|
||||
try:
|
||||
subset_name = get_subset_name_with_asset_doc(
|
||||
self.family,
|
||||
variant,
|
||||
task_name,
|
||||
asset_doc,
|
||||
project_name
|
||||
)
|
||||
except TaskNotSetError:
|
||||
# Create instance with fake task
|
||||
# - instance will be marked as invalid so it can't be published
|
||||
# but user have ability to change it
|
||||
# NOTE: This expect that there is not task 'Undefined' on asset
|
||||
task_name = "Undefined"
|
||||
subset_name = get_subset_name_with_asset_doc(
|
||||
self.family,
|
||||
variant,
|
||||
task_name,
|
||||
asset_doc,
|
||||
project_name
|
||||
)
|
||||
|
||||
return subset_name, task_name
|
||||
|
||||
def _get_task_name(self, asset_doc):
|
||||
"""Get applicable task from 'asset_doc' """
|
||||
available_task_names = {}
|
||||
asset_tasks = asset_doc.get("data", {}).get("tasks") or {}
|
||||
for task_name in asset_tasks.keys():
|
||||
available_task_names[task_name.lower()] = task_name
|
||||
|
||||
task_name = None
|
||||
for _task_name in self.default_tasks:
|
||||
_task_name_low = _task_name.lower()
|
||||
if _task_name_low in available_task_names:
|
||||
task_name = available_task_names[_task_name_low]
|
||||
break
|
||||
|
||||
return task_name
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
return [
|
||||
BoolDef(
|
||||
"add_review_family",
|
||||
default=True,
|
||||
label="Review"
|
||||
)
|
||||
]
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
# Use same attributes as for instance attributes
|
||||
return [
|
||||
FileDef(
|
||||
"filepath",
|
||||
folders=False,
|
||||
single_item=False,
|
||||
extensions=self.extensions,
|
||||
label="Filepath"
|
||||
),
|
||||
BoolDef(
|
||||
"add_review_family",
|
||||
default=True,
|
||||
label="Review"
|
||||
)
|
||||
]
|
||||
|
||||
def get_detail_description(self):
|
||||
return """# Publish batch of .mov to multiple assets.
|
||||
|
||||
File names must then contain only asset name, or asset name + version.
|
||||
(eg. 'chair.mov', 'chair_v001.mov', not really safe `my_chair_v001.mov`
|
||||
"""
|
||||
|
||||
def _get_asset_by_name_case_not_sensitive(self, project_name, asset_name):
|
||||
"""Handle more cases in file names"""
|
||||
asset_name = re.compile(asset_name, re.IGNORECASE)
|
||||
|
||||
assets = list(get_assets(project_name, asset_names=[asset_name]))
|
||||
if assets:
|
||||
if len(assets) > 1:
|
||||
self.log.warning("Too many records found for {}".format(
|
||||
asset_name))
|
||||
return
|
||||
|
||||
return assets.pop()
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import os
|
||||
|
||||
import pyblish.api
|
||||
from openpype.pipeline import OpenPypePyblishPluginMixin
|
||||
|
||||
|
||||
class CollectMovieBatch(
|
||||
pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin
|
||||
):
|
||||
"""Collect file url for batch movies and create representation.
|
||||
|
||||
Adds review on instance and to repre.tags based on value of toggle button
|
||||
on creator.
|
||||
"""
|
||||
|
||||
label = "Collect Movie Batch Files"
|
||||
order = pyblish.api.CollectorOrder
|
||||
|
||||
hosts = ["traypublisher"]
|
||||
|
||||
def process(self, instance):
|
||||
if instance.data.get("creator_identifier") != "render_movie_batch":
|
||||
return
|
||||
|
||||
creator_attributes = instance.data["creator_attributes"]
|
||||
|
||||
file_url = creator_attributes["filepath"]
|
||||
file_name = os.path.basename(file_url)
|
||||
_, ext = os.path.splitext(file_name)
|
||||
|
||||
repre = {
|
||||
"name": ext[1:],
|
||||
"ext": ext[1:],
|
||||
"files": file_name,
|
||||
"stagingDir": os.path.dirname(file_url),
|
||||
"tags": []
|
||||
}
|
||||
|
||||
if creator_attributes["add_review_family"]:
|
||||
repre["tags"].append("review")
|
||||
instance.data["families"].append("review")
|
||||
|
||||
instance.data["representations"].append(repre)
|
||||
|
||||
instance.data["source"] = file_url
|
||||
|
||||
self.log.debug("instance.data {}".format(instance.data))
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<error id="main">
|
||||
<title>Invalid frame range</title>
|
||||
<description>
|
||||
## Invalid frame range
|
||||
|
||||
Expected duration or '{duration}' frames set in database, workfile contains only '{found}' frames.
|
||||
|
||||
### How to repair?
|
||||
|
||||
Modify configuration in the database or tweak frame range in the workfile.
|
||||
</description>
|
||||
</error>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import re
|
||||
|
||||
import pyblish.api
|
||||
|
||||
import openpype.api
|
||||
from openpype.pipeline import (
|
||||
PublishXmlValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateFrameRange(OptionalPyblishPluginMixin,
|
||||
pyblish.api.InstancePlugin):
|
||||
"""Validating frame range of rendered files against state in DB."""
|
||||
|
||||
label = "Validate Frame Range"
|
||||
hosts = ["traypublisher"]
|
||||
families = ["render"]
|
||||
order = openpype.api.ValidateContentsOrder
|
||||
|
||||
optional = True
|
||||
# published data might be sequence (.mov, .mp4) in that counting files
|
||||
# doesnt make sense
|
||||
check_extensions = ["exr", "dpx", "jpg", "jpeg", "png", "tiff", "tga",
|
||||
"gif", "svg"]
|
||||
skip_timelines_check = [] # skip for specific task names (regex)
|
||||
|
||||
def process(self, instance):
|
||||
# Skip the instance if is not active by data on the instance
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
if (self.skip_timelines_check and
|
||||
any(re.search(pattern, instance.data["task"])
|
||||
for pattern in self.skip_timelines_check)):
|
||||
self.log.info("Skipping for {} task".format(instance.data["task"]))
|
||||
|
||||
asset_doc = instance.data["assetEntity"]
|
||||
asset_data = asset_doc["data"]
|
||||
frame_start = asset_data["frameStart"]
|
||||
frame_end = asset_data["frameEnd"]
|
||||
handle_start = asset_data["handleStart"]
|
||||
handle_end = asset_data["handleEnd"]
|
||||
duration = (frame_end - frame_start + 1) + handle_start + handle_end
|
||||
|
||||
repres = instance.data.get("representations")
|
||||
if not repres:
|
||||
self.log.info("No representations, skipping.")
|
||||
return
|
||||
|
||||
first_repre = repres[0]
|
||||
ext = first_repre['ext'].replace(".", '')
|
||||
|
||||
if not ext or ext.lower() not in self.check_extensions:
|
||||
self.log.warning("Cannot check for extension {}".format(ext))
|
||||
return
|
||||
|
||||
files = first_repre["files"]
|
||||
if isinstance(files, str):
|
||||
files = [files]
|
||||
frames = len(files)
|
||||
|
||||
msg = (
|
||||
"Frame duration from DB:'{}' doesn't match number of files:'{}'"
|
||||
" Please change frame range for Asset or limit no. of files"
|
||||
). format(int(duration), frames)
|
||||
|
||||
formatting_data = {"duration": duration,
|
||||
"found": frames}
|
||||
if frames != duration:
|
||||
raise PublishXmlValidationError(self, msg,
|
||||
formatting_data=formatting_data)
|
||||
|
||||
self.log.debug("Valid ranges expected '{}' - found '{}'".
|
||||
format(int(duration), frames))
|
||||
|
|
@ -524,10 +524,10 @@ def get_workdir_with_workdir_data(
|
|||
|
||||
anatomy_filled = anatomy.format(workdir_data)
|
||||
# Output is TemplateResult object which contain useful data
|
||||
path = anatomy_filled[template_key]["folder"]
|
||||
if path:
|
||||
path = os.path.normpath(path)
|
||||
return path
|
||||
output = anatomy_filled[template_key]["folder"]
|
||||
if output:
|
||||
return output.normalized()
|
||||
return output
|
||||
|
||||
|
||||
def get_workdir(
|
||||
|
|
|
|||
|
|
@ -409,6 +409,19 @@ class TemplateResult(str):
|
|||
self.invalid_types
|
||||
)
|
||||
|
||||
def normalized(self):
|
||||
"""Convert to normalized path."""
|
||||
|
||||
cls = self.__class__
|
||||
return cls(
|
||||
os.path.normpath(self),
|
||||
self.template,
|
||||
self.solved,
|
||||
self.used_values,
|
||||
self.missing_keys,
|
||||
self.invalid_types
|
||||
)
|
||||
|
||||
|
||||
class TemplatesResultDict(dict):
|
||||
"""Holds and wrap TemplateResults for easy bug report."""
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ def pack_project(project_name, destination_dir=None):
|
|||
|
||||
Args:
|
||||
project_name(str): Project that should be packaged.
|
||||
destination_dir(str): Optinal path where zip will be stored. Project's
|
||||
destination_dir(str): Optional path where zip will be stored. Project's
|
||||
root is used if not passed.
|
||||
"""
|
||||
print("Creating package of project \"{}\"".format(project_name))
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class HoudiniSubmitPublishDeadline(pyblish.api.ContextPlugin):
|
|||
scenename = os.path.basename(scene)
|
||||
|
||||
# Get project code
|
||||
project = legacy_io.find_one({"type": "project"})
|
||||
project = context.data["projectEntity"]
|
||||
code = project["data"].get("code", project["name"])
|
||||
|
||||
job_name = "{scene} [PUBLISH]".format(scene=scenename)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ import clique
|
|||
|
||||
import pyblish.api
|
||||
|
||||
from openpype.client import get_last_version_by_subset_name
|
||||
from openpype.client import (
|
||||
get_last_version_by_subset_name,
|
||||
get_representations,
|
||||
)
|
||||
from openpype.pipeline import (
|
||||
get_representation_path,
|
||||
legacy_io,
|
||||
|
|
@ -18,15 +21,23 @@ from openpype.pipeline import (
|
|||
from openpype.pipeline.farm.patterning import match_aov_pattern
|
||||
|
||||
|
||||
def get_resources(version, extension=None):
|
||||
def get_resources(project_name, version, extension=None):
|
||||
"""Get the files from the specific version."""
|
||||
query = {"type": "representation", "parent": version["_id"]}
|
||||
|
||||
# TODO this functions seems to be weird
|
||||
# - it's looking for representation with one extension or first (any)
|
||||
# representation from a version?
|
||||
# - not sure how this should work, maybe it does for specific use cases
|
||||
# but probably can't be used for all resources from 2D workflows
|
||||
extensions = None
|
||||
if extension:
|
||||
query["name"] = extension
|
||||
|
||||
representation = legacy_io.find_one(query)
|
||||
assert representation, "This is a bug"
|
||||
extensions = [extension]
|
||||
repre_docs = list(get_representations(
|
||||
project_name, version_ids=[version["_id"]], extensions=extensions
|
||||
))
|
||||
assert repre_docs, "This is a bug"
|
||||
|
||||
representation = repre_docs[0]
|
||||
directory = get_representation_path(representation)
|
||||
print("Source: ", directory)
|
||||
resources = sorted(
|
||||
|
|
@ -330,6 +341,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
self.log.info("Preparing to copy ...")
|
||||
start = instance.data.get("frameStart")
|
||||
end = instance.data.get("frameEnd")
|
||||
project_name = legacy_io.active_project()
|
||||
|
||||
# get latest version of subset
|
||||
# this will stop if subset wasn't published yet
|
||||
|
|
@ -341,7 +353,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
)
|
||||
|
||||
# get its files based on extension
|
||||
subset_resources = get_resources(version, representation.get("ext"))
|
||||
subset_resources = get_resources(
|
||||
project_name, version, representation.get("ext")
|
||||
)
|
||||
r_col, _ = clique.assemble(subset_resources)
|
||||
|
||||
# if override remove all frames we are expecting to be rendered
|
||||
|
|
|
|||
|
|
@ -380,6 +380,19 @@ class AnatomyTemplateResult(TemplateResult):
|
|||
)
|
||||
return self.__class__(tmp, self.rootless)
|
||||
|
||||
def normalized(self):
|
||||
"""Convert to normalized path."""
|
||||
|
||||
tmp = TemplateResult(
|
||||
os.path.normpath(self),
|
||||
self.template,
|
||||
self.solved,
|
||||
self.used_values,
|
||||
self.missing_keys,
|
||||
self.invalid_types
|
||||
)
|
||||
return self.__class__(tmp, self.rootless)
|
||||
|
||||
|
||||
class AnatomyTemplates(TemplatesDict):
|
||||
inner_key_pattern = re.compile(r"(\{@.*?[^{}0]*\})")
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import time
|
|||
|
||||
from openpype.lib import PypeLogger
|
||||
from openpype.api import get_app_environments_for_context
|
||||
from openpype.lib.plugin_tools import parse_json, get_batch_asset_task_info
|
||||
from openpype.lib.plugin_tools import get_batch_asset_task_info
|
||||
from openpype.lib.remote_publish import (
|
||||
get_webpublish_conn,
|
||||
start_webpublish_log,
|
||||
|
|
|
|||
|
|
@ -273,6 +273,49 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hosts": [
|
||||
"traypublisher"
|
||||
],
|
||||
"families": [],
|
||||
"task_types": [],
|
||||
"tasks": [],
|
||||
"add_ftrack_family": true,
|
||||
"advanced_filtering": []
|
||||
},
|
||||
{
|
||||
"hosts": [
|
||||
"traypublisher"
|
||||
],
|
||||
"families": [
|
||||
"matchmove",
|
||||
"shot"
|
||||
],
|
||||
"task_types": [],
|
||||
"tasks": [],
|
||||
"add_ftrack_family": false,
|
||||
"advanced_filtering": []
|
||||
},
|
||||
{
|
||||
"hosts": [
|
||||
"traypublisher"
|
||||
],
|
||||
"families": [
|
||||
"plate"
|
||||
],
|
||||
"task_types": [],
|
||||
"tasks": [],
|
||||
"add_ftrack_family": false,
|
||||
"advanced_filtering": [
|
||||
{
|
||||
"families": [
|
||||
"clip",
|
||||
"review"
|
||||
],
|
||||
"add_ftrack_family": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hosts": [
|
||||
"maya"
|
||||
|
|
|
|||
|
|
@ -235,5 +235,12 @@
|
|||
"allow_multiple_items": true,
|
||||
"extensions": []
|
||||
}
|
||||
]
|
||||
],
|
||||
"BatchMovieCreator": {
|
||||
"default_variants": ["Main"],
|
||||
"default_tasks": ["Compositing"],
|
||||
"extensions": [
|
||||
".mov"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -169,6 +169,7 @@ class HostsEnumEntity(BaseEnumEntity):
|
|||
"tvpaint",
|
||||
"unreal",
|
||||
"standalonepublisher",
|
||||
"traypublisher",
|
||||
"webpublisher"
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,44 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "BatchMovieCreator",
|
||||
"label": "Batch Movie Creator",
|
||||
"collapsible_key": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Allows to publish multiple video files in one go. <br />Name of matching asset is parsed from file names ('asset.mov', 'asset_v001.mov', 'my_asset_to_publish.mov')"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default variants",
|
||||
"object_type": {
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "default_tasks",
|
||||
"label": "Default tasks",
|
||||
"object_type": {
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "extensions",
|
||||
"label": "Extensions",
|
||||
"use_label_wrap": true,
|
||||
"collapsible_key": true,
|
||||
"collapsed": false,
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import uuid
|
||||
import html
|
||||
from Qt import QtCore, QtGui
|
||||
|
||||
import pyblish.api
|
||||
|
|
@ -45,7 +46,8 @@ class InstancesModel(QtGui.QStandardItemModel):
|
|||
all_removed = True
|
||||
for instance_item in instance_items:
|
||||
item = QtGui.QStandardItem(instance_item.label)
|
||||
item.setData(instance_item.label, ITEM_LABEL_ROLE)
|
||||
instance_label = html.escape(instance_item.label)
|
||||
item.setData(instance_label, ITEM_LABEL_ROLE)
|
||||
item.setData(instance_item.errored, ITEM_ERRORED_ROLE)
|
||||
item.setData(instance_item.id, ITEM_ID_ROLE)
|
||||
item.setData(instance_item.removed, INSTANCE_REMOVED_ROLE)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ Only one item can be selected at a time.
|
|||
|
||||
import re
|
||||
import collections
|
||||
import html
|
||||
|
||||
from Qt import QtWidgets, QtCore
|
||||
|
||||
|
|
@ -307,7 +308,7 @@ class InstanceCardWidget(CardWidget):
|
|||
self._last_variant = variant
|
||||
self._last_subset_name = subset_name
|
||||
# Make `variant` bold
|
||||
label = self.instance.label
|
||||
label = html.escape(self.instance.label)
|
||||
found_parts = set(re.findall(variant, label, re.IGNORECASE))
|
||||
if found_parts:
|
||||
for part in found_parts:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ selection can be enabled disabled using checkbox or keyboard key presses:
|
|||
```
|
||||
"""
|
||||
import collections
|
||||
import html
|
||||
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
|
|
@ -113,7 +114,9 @@ class InstanceListItemWidget(QtWidgets.QWidget):
|
|||
|
||||
self.instance = instance
|
||||
|
||||
subset_name_label = QtWidgets.QLabel(instance.label, self)
|
||||
instance_label = html.escape(instance.label)
|
||||
|
||||
subset_name_label = QtWidgets.QLabel(instance_label, self)
|
||||
subset_name_label.setObjectName("ListViewSubsetName")
|
||||
|
||||
active_checkbox = NiceCheckbox(parent=self)
|
||||
|
|
@ -178,7 +181,7 @@ class InstanceListItemWidget(QtWidgets.QWidget):
|
|||
# Check subset name
|
||||
label = self.instance.label
|
||||
if label != self._instance_label_widget.text():
|
||||
self._instance_label_widget.setText(label)
|
||||
self._instance_label_widget.setText(html.escape(label))
|
||||
# Check active state
|
||||
self.set_active(self.instance["active"])
|
||||
# Check valid states
|
||||
|
|
|
|||
|
|
@ -60,31 +60,14 @@ class HostToolsHelper:
|
|||
|
||||
return self._workfiles_tool
|
||||
|
||||
def show_workfiles(self, parent=None, use_context=None, save=None):
|
||||
def show_workfiles(
|
||||
self, parent=None, use_context=None, save=None, on_top=None
|
||||
):
|
||||
"""Workfiles tool for changing context and saving workfiles."""
|
||||
if use_context is None:
|
||||
use_context = True
|
||||
|
||||
if save is None:
|
||||
save = True
|
||||
|
||||
with qt_app_context():
|
||||
workfiles_tool = self.get_workfiles_tool(parent)
|
||||
workfiles_tool.set_save_enabled(save)
|
||||
|
||||
if not workfiles_tool.isVisible():
|
||||
workfiles_tool.show()
|
||||
|
||||
if use_context:
|
||||
context = {
|
||||
"asset": legacy_io.Session["AVALON_ASSET"],
|
||||
"task": legacy_io.Session["AVALON_TASK"]
|
||||
}
|
||||
workfiles_tool.set_context(context)
|
||||
|
||||
# Pull window to the front.
|
||||
workfiles_tool.raise_()
|
||||
workfiles_tool.activateWindow()
|
||||
workfiles_tool.ensure_visible(use_context, save, on_top)
|
||||
|
||||
def get_loader_tool(self, parent):
|
||||
"""Create, cache and return loader tool window."""
|
||||
|
|
@ -395,9 +378,9 @@ def show_tool_by_name(tool_name, parent=None, *args, **kwargs):
|
|||
_SingletonPoint.show_tool_by_name(tool_name, parent, *args, **kwargs)
|
||||
|
||||
|
||||
def show_workfiles(parent=None, use_context=None, save=None):
|
||||
def show_workfiles(*args, **kwargs):
|
||||
_SingletonPoint.show_tool_by_name(
|
||||
"workfiles", parent, use_context=use_context, save=save
|
||||
"workfiles", *args, **kwargs
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
import datetime
|
||||
from Qt import QtCore, QtWidgets
|
||||
from Qt import QtCore, QtWidgets, QtGui
|
||||
|
||||
from openpype.client import (
|
||||
get_asset_by_id,
|
||||
|
|
@ -8,6 +8,7 @@ from openpype.client import (
|
|||
get_workfile_info,
|
||||
)
|
||||
from openpype import style
|
||||
from openpype import resources
|
||||
from openpype.lib import (
|
||||
create_workfile_doc,
|
||||
save_workfile_data_to_doc,
|
||||
|
|
@ -142,21 +143,19 @@ class SidePanelWidget(QtWidgets.QWidget):
|
|||
return self._workfile_doc, data
|
||||
|
||||
|
||||
class Window(QtWidgets.QMainWindow):
|
||||
class Window(QtWidgets.QWidget):
|
||||
"""Work Files Window"""
|
||||
title = "Work Files"
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(Window, self).__init__(parent=parent)
|
||||
self.setWindowTitle(self.title)
|
||||
window_flags = QtCore.Qt.Window | QtCore.Qt.WindowCloseButtonHint
|
||||
if not parent:
|
||||
window_flags |= QtCore.Qt.WindowStaysOnTopHint
|
||||
self.setWindowFlags(window_flags)
|
||||
icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
self.setWindowFlags(self.windowFlags() | QtCore.Qt.Window)
|
||||
|
||||
# Create pages widget and set it as central widget
|
||||
pages_widget = QtWidgets.QStackedWidget(self)
|
||||
self.setCentralWidget(pages_widget)
|
||||
|
||||
home_page_widget = QtWidgets.QWidget(pages_widget)
|
||||
home_body_widget = QtWidgets.QWidget(home_page_widget)
|
||||
|
|
@ -191,6 +190,9 @@ class Window(QtWidgets.QMainWindow):
|
|||
# the files widget has a filter field which tasks does not.
|
||||
tasks_widget.setContentsMargins(0, 32, 0, 0)
|
||||
|
||||
main_layout = QtWidgets.QHBoxLayout(self)
|
||||
main_layout.addWidget(pages_widget, 1)
|
||||
|
||||
# Set context after asset widget is refreshed
|
||||
# - to do so it is necessary to wait until refresh is done
|
||||
set_context_timer = QtCore.QTimer()
|
||||
|
|
@ -227,6 +229,49 @@ class Window(QtWidgets.QMainWindow):
|
|||
self._first_show = True
|
||||
self._context_to_set = None
|
||||
|
||||
def ensure_visible(
|
||||
self, use_context=None, save=None, on_top=None
|
||||
):
|
||||
if save is None:
|
||||
save = True
|
||||
|
||||
self.set_save_enabled(save)
|
||||
|
||||
if self.isVisible():
|
||||
use_context = False
|
||||
elif use_context is None:
|
||||
use_context = True
|
||||
|
||||
if on_top is None and self._first_show:
|
||||
on_top = self.parent() is None
|
||||
|
||||
window_flags = self.windowFlags()
|
||||
new_window_flags = window_flags
|
||||
if on_top is True:
|
||||
new_window_flags = window_flags | QtCore.Qt.WindowStaysOnTopHint
|
||||
elif on_top is False:
|
||||
new_window_flags = window_flags & ~QtCore.Qt.WindowStaysOnTopHint
|
||||
|
||||
if new_window_flags != window_flags:
|
||||
# Note this is not propagated after initialization of widget in
|
||||
# some Qt builds
|
||||
self.setWindowFlags(new_window_flags)
|
||||
self.show()
|
||||
|
||||
elif not self.isVisible():
|
||||
self.show()
|
||||
|
||||
if use_context is None or use_context is True:
|
||||
context = {
|
||||
"asset": legacy_io.Session["AVALON_ASSET"],
|
||||
"task": legacy_io.Session["AVALON_TASK"]
|
||||
}
|
||||
self.set_context(context)
|
||||
|
||||
# Pull window to the front.
|
||||
self.raise_()
|
||||
self.activateWindow()
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
return legacy_io.Session["AVALON_PROJECT"]
|
||||
|
|
@ -331,6 +376,7 @@ class Window(QtWidgets.QMainWindow):
|
|||
if self.assets_widget.refreshing:
|
||||
return
|
||||
|
||||
self._set_context_timer.stop()
|
||||
self._context_to_set, context = None, self._context_to_set
|
||||
if "asset" in context:
|
||||
asset_doc = get_asset_by_name(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.12.2-nightly.1"
|
||||
__version__ = "3.12.2-nightly.2"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.12.2-nightly.1" # OpenPype
|
||||
version = "3.12.2-nightly.2" # OpenPype
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
39
tools/pack_project.ps1
Normal file
39
tools/pack_project.ps1
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Helper script OpenPype Packing project.
|
||||
|
||||
.DESCRIPTION
|
||||
Once you are happy with the project and want to preserve it for future work, just change the project name on line 38 and copy the file into .\OpenPype\tools. Then use the cmd form .EXAMPLE
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
PS> .\tools\run_pack_project.ps1
|
||||
|
||||
#>
|
||||
$current_dir = Get-Location
|
||||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
||||
$env:_INSIDE_OPENPYPE_TOOL = "1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if (-not (Test-Path 'env:POETRY_HOME')) {
|
||||
$env:POETRY_HOME = "$openpype_root\.poetry"
|
||||
}
|
||||
$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
|
||||
|
||||
Set-Location -Path $openpype_root
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor Green
|
||||
Write-Host "Reading Poetry ... " -NoNewline
|
||||
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
|
||||
Write-Host "NOT FOUND" -ForegroundColor Yellow
|
||||
Write-Host "*** " -NoNewline -ForegroundColor Yellow
|
||||
Write-Host "We need to install Poetry create virtual env first ..."
|
||||
& "$openpype_root\tools\create_env.ps1"
|
||||
} else {
|
||||
Write-Host "OK" -ForegroundColor Green
|
||||
}
|
||||
|
||||
& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" pack-project --project $ARGS
|
||||
Set-Location -Path $current_dir
|
||||
39
tools/unpack_project.ps1
Normal file
39
tools/unpack_project.ps1
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Helper script OpenPype Unpacking project.
|
||||
|
||||
.DESCRIPTION
|
||||
Make sure you had dropped the project from your db and removed the poject data in case you were having them previously. Then on line 38 change the <Path to zip> to any path where the zip with project is - usually we are having it here https://drive.google.com/drive/u/0/folders/0AKE4mxImOsAGUk9PVA . Copy the file into .\OpenPype\tools. Then use the cmd form .EXAMPLE
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
PS> .\tools\run_unpack_project.ps1
|
||||
|
||||
#>
|
||||
$current_dir = Get-Location
|
||||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
||||
$env:_INSIDE_OPENPYPE_TOOL = "1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if (-not (Test-Path 'env:POETRY_HOME')) {
|
||||
$env:POETRY_HOME = "$openpype_root\.poetry"
|
||||
}
|
||||
$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
|
||||
|
||||
Set-Location -Path $openpype_root
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor Green
|
||||
Write-Host "Reading Poetry ... " -NoNewline
|
||||
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
|
||||
Write-Host "NOT FOUND" -ForegroundColor Yellow
|
||||
Write-Host "*** " -NoNewline -ForegroundColor Yellow
|
||||
Write-Host "We need to install Poetry create virtual env first ..."
|
||||
& "$openpype_root\tools\create_env.ps1"
|
||||
} else {
|
||||
Write-Host "OK" -ForegroundColor Green
|
||||
}
|
||||
|
||||
& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" unpack-project --zipfile $ARGS
|
||||
Set-Location -Path $current_dir
|
||||
|
|
@ -45,6 +45,7 @@ For more information [see here](admin_use.md#run-openpype).
|
|||
| publish | Pype takes JSON from provided path and use it to publish data in it. | [📑](#publish-arguments) |
|
||||
| extractenvironments | Extract environment variables for entered context to a json file. | [📑](#extractenvironments-arguments) |
|
||||
| run | Execute given python script within OpenPype environment. | [📑](#run-arguments) |
|
||||
| interactive | Start python like interactive console session. | |
|
||||
| projectmanager | Launch Project Manager UI | [📑](#projectmanager-arguments) |
|
||||
| settings | Open Settings UI | [📑](#settings-arguments) |
|
||||
| standalonepublisher | Open Standalone Publisher UI | [📑](#standalonepublisher-arguments) |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue