Merge remote-tracking branch 'origin/develop' into bugfix/set-certifi-for-all-platforms
|
|
@ -203,6 +203,12 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
openpype_version.staging = True
|
||||
return openpype_version
|
||||
|
||||
def __hash__(self):
|
||||
if self.path:
|
||||
return hash(self.path)
|
||||
else:
|
||||
return hash(str(self))
|
||||
|
||||
|
||||
class BootstrapRepos:
|
||||
"""Class for bootstrapping local OpenPype installation.
|
||||
|
|
@ -650,6 +656,9 @@ class BootstrapRepos:
|
|||
v for v in openpype_versions if v.path.suffix != ".zip"
|
||||
]
|
||||
|
||||
# remove duplicates
|
||||
openpype_versions = list(set(openpype_versions))
|
||||
|
||||
return openpype_versions
|
||||
|
||||
def process_entered_location(self, location: str) -> Union[Path, None]:
|
||||
|
|
|
|||
|
|
@ -60,13 +60,6 @@ def tray(debug=False):
|
|||
help="Ftrack api user")
|
||||
@click.option("--ftrack-api-key", envvar="FTRACK_API_KEY",
|
||||
help="Ftrack api key")
|
||||
@click.option("--ftrack-events-path",
|
||||
envvar="FTRACK_EVENTS_PATH",
|
||||
help=("path to ftrack event handlers"))
|
||||
@click.option("--no-stored-credentials", is_flag=True,
|
||||
help="don't use stored credentials")
|
||||
@click.option("--store-credentials", is_flag=True,
|
||||
help="store provided credentials")
|
||||
@click.option("--legacy", is_flag=True,
|
||||
help="run event server without mongo storing")
|
||||
@click.option("--clockify-api-key", envvar="CLOCKIFY_API_KEY",
|
||||
|
|
@ -77,9 +70,6 @@ def eventserver(debug,
|
|||
ftrack_url,
|
||||
ftrack_user,
|
||||
ftrack_api_key,
|
||||
ftrack_events_path,
|
||||
no_stored_credentials,
|
||||
store_credentials,
|
||||
legacy,
|
||||
clockify_api_key,
|
||||
clockify_workspace):
|
||||
|
|
@ -87,10 +77,6 @@ def eventserver(debug,
|
|||
|
||||
This should be ideally used by system service (such us systemd or upstart
|
||||
on linux and window service).
|
||||
|
||||
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.
|
||||
"""
|
||||
if debug:
|
||||
os.environ['OPENPYPE_DEBUG'] = "3"
|
||||
|
|
@ -99,9 +85,6 @@ def eventserver(debug,
|
|||
ftrack_url,
|
||||
ftrack_user,
|
||||
ftrack_api_key,
|
||||
ftrack_events_path,
|
||||
no_stored_credentials,
|
||||
store_credentials,
|
||||
legacy,
|
||||
clockify_api_key,
|
||||
clockify_workspace
|
||||
|
|
|
|||
|
|
@ -680,6 +680,10 @@ class TrayModulesManager(ModulesManager):
|
|||
output.append(module)
|
||||
return output
|
||||
|
||||
def restart_tray(self):
|
||||
if self.tray_manager:
|
||||
self.tray_manager.restart()
|
||||
|
||||
def tray_init(self):
|
||||
report = {}
|
||||
time_start = time.time()
|
||||
|
|
|
|||
|
|
@ -422,17 +422,18 @@ def run_event_server(
|
|||
ftrack_url,
|
||||
ftrack_user,
|
||||
ftrack_api_key,
|
||||
ftrack_events_path,
|
||||
no_stored_credentials,
|
||||
store_credentials,
|
||||
legacy,
|
||||
clockify_api_key,
|
||||
clockify_workspace
|
||||
):
|
||||
if not no_stored_credentials:
|
||||
if not ftrack_user or not ftrack_api_key:
|
||||
print((
|
||||
"Ftrack user/api key were not passed."
|
||||
" Trying to use credentials from user keyring."
|
||||
))
|
||||
cred = credentials.get_credentials(ftrack_url)
|
||||
username = cred.get('username')
|
||||
api_key = cred.get('api_key')
|
||||
ftrack_user = cred.get("username")
|
||||
ftrack_api_key = cred.get("api_key")
|
||||
|
||||
if clockify_workspace and clockify_api_key:
|
||||
os.environ["CLOCKIFY_WORKSPACE"] = clockify_workspace
|
||||
|
|
@ -445,209 +446,16 @@ def run_event_server(
|
|||
return 1
|
||||
|
||||
# Validate entered credentials
|
||||
if not validate_credentials(ftrack_url, username, api_key):
|
||||
if not validate_credentials(ftrack_url, ftrack_user, ftrack_api_key):
|
||||
print('Exiting! < Please enter valid credentials >')
|
||||
return 1
|
||||
|
||||
if store_credentials:
|
||||
credentials.save_credentials(username, api_key, ftrack_url)
|
||||
|
||||
# Set Ftrack environments
|
||||
os.environ["FTRACK_SERVER"] = ftrack_url
|
||||
os.environ["FTRACK_API_USER"] = username
|
||||
os.environ["FTRACK_API_KEY"] = api_key
|
||||
# TODO This won't work probably
|
||||
if ftrack_events_path:
|
||||
if isinstance(ftrack_events_path, (list, tuple)):
|
||||
ftrack_events_path = os.pathsep.join(ftrack_events_path)
|
||||
os.environ["FTRACK_EVENTS_PATH"] = ftrack_events_path
|
||||
os.environ["FTRACK_API_USER"] = ftrack_user
|
||||
os.environ["FTRACK_API_KEY"] = ftrack_api_key
|
||||
|
||||
if legacy:
|
||||
return legacy_server(ftrack_url)
|
||||
|
||||
return main_loop(ftrack_url)
|
||||
|
||||
|
||||
def main(argv):
|
||||
'''
|
||||
There are 4 values neccessary for event server:
|
||||
1.) Ftrack url - "studio.ftrackapp.com"
|
||||
2.) Username - "my.username"
|
||||
3.) API key - "apikey-long11223344-6665588-5565"
|
||||
4.) Path/s to events - "X:/path/to/folder/with/events"
|
||||
|
||||
All these values can be entered with arguments or environment variables.
|
||||
- arguments:
|
||||
"-ftrackurl {url}"
|
||||
"-ftrackuser {username}"
|
||||
"-ftrackapikey {api key}"
|
||||
"-ftrackeventpaths {path to events}"
|
||||
- environment variables:
|
||||
FTRACK_SERVER
|
||||
FTRACK_API_USER
|
||||
FTRACK_API_KEY
|
||||
FTRACK_EVENTS_PATH
|
||||
|
||||
Credentials (Username & API key):
|
||||
- Credentials can be stored for auto load on next start
|
||||
- To *Store/Update* these values add argument "-storecred"
|
||||
- They will be stored to appsdir file when login is successful
|
||||
- To *Update/Override* values with enviromnet variables is also needed to:
|
||||
- *don't enter argument for that value*
|
||||
- add argument "-noloadcred" (currently stored credentials won't be loaded)
|
||||
|
||||
Order of getting values:
|
||||
1.) Arguments are always used when entered.
|
||||
- entered values through args have most priority! (in each case)
|
||||
2.) Credentials are tried to load from appsdir file.
|
||||
- skipped when credentials were entered through args or credentials
|
||||
are not stored yet
|
||||
- can be skipped with "-noloadcred" argument
|
||||
3.) Environment variables are last source of values.
|
||||
- will try to get not yet set values from environments
|
||||
|
||||
Best practice:
|
||||
- set environment variables FTRACK_SERVER & FTRACK_EVENTS_PATH
|
||||
- launch event_server_cli with args:
|
||||
~/event_server_cli.py -ftrackuser "{username}" -ftrackapikey "{API key}" -storecred
|
||||
- next time launch event_server_cli.py only with set environment variables
|
||||
FTRACK_SERVER & FTRACK_EVENTS_PATH
|
||||
'''
|
||||
parser = argparse.ArgumentParser(description='Ftrack event server')
|
||||
parser.add_argument(
|
||||
"-ftrackurl", type=str, metavar='FTRACKURL',
|
||||
help=(
|
||||
"URL to ftrack server where events should handle"
|
||||
" (default from environment: $FTRACK_SERVER)"
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"-ftrackuser", type=str,
|
||||
help=(
|
||||
"Username should be the username of the user in ftrack"
|
||||
" to record operations against."
|
||||
" (default from environment: $FTRACK_API_USER)"
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"-ftrackapikey", type=str,
|
||||
help=(
|
||||
"Should be the API key to use for authentication"
|
||||
" (default from environment: $FTRACK_API_KEY)"
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"-ftrackeventpaths", nargs='+',
|
||||
help=(
|
||||
"List of paths where events are stored."
|
||||
" (default from environment: $FTRACK_EVENTS_PATH)"
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
'-storecred',
|
||||
help=(
|
||||
"Entered credentials will be also stored"
|
||||
" to apps dir for future usage"
|
||||
),
|
||||
action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
'-noloadcred',
|
||||
help="Load creadentials from apps dir",
|
||||
action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
'-legacy',
|
||||
help="Load creadentials from apps dir",
|
||||
action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-clockifyapikey", type=str,
|
||||
help=(
|
||||
"Enter API key for Clockify actions."
|
||||
" (default from environment: $CLOCKIFY_API_KEY)"
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"-clockifyworkspace", type=str,
|
||||
help=(
|
||||
"Enter workspace for Clockify."
|
||||
" (default from module presets or "
|
||||
"environment: $CLOCKIFY_WORKSPACE)"
|
||||
)
|
||||
)
|
||||
ftrack_url = os.environ.get("FTRACK_SERVER")
|
||||
username = os.environ.get("FTRACK_API_USER")
|
||||
api_key = os.environ.get("FTRACK_API_KEY")
|
||||
|
||||
kwargs, args = parser.parse_known_args(argv)
|
||||
|
||||
if kwargs.ftrackurl:
|
||||
ftrack_url = kwargs.ftrackurl
|
||||
|
||||
# Load Ftrack url from settings if not set
|
||||
if not ftrack_url:
|
||||
ftrack_url = get_ftrack_url_from_settings()
|
||||
|
||||
event_paths = None
|
||||
if kwargs.ftrackeventpaths:
|
||||
event_paths = kwargs.ftrackeventpaths
|
||||
|
||||
if not kwargs.noloadcred:
|
||||
cred = credentials.get_credentials(ftrack_url)
|
||||
username = cred.get('username')
|
||||
api_key = cred.get('api_key')
|
||||
|
||||
if kwargs.ftrackuser:
|
||||
username = kwargs.ftrackuser
|
||||
|
||||
if kwargs.ftrackapikey:
|
||||
api_key = kwargs.ftrackapikey
|
||||
|
||||
if kwargs.clockifyworkspace:
|
||||
os.environ["CLOCKIFY_WORKSPACE"] = kwargs.clockifyworkspace
|
||||
|
||||
if kwargs.clockifyapikey:
|
||||
os.environ["CLOCKIFY_API_KEY"] = kwargs.clockifyapikey
|
||||
|
||||
legacy = kwargs.legacy
|
||||
|
||||
# Check url regex and accessibility
|
||||
ftrack_url = check_ftrack_url(ftrack_url)
|
||||
if not ftrack_url:
|
||||
print('Exiting! < Please enter Ftrack server url >')
|
||||
return 1
|
||||
|
||||
# Validate entered credentials
|
||||
if not validate_credentials(ftrack_url, username, api_key):
|
||||
print('Exiting! < Please enter valid credentials >')
|
||||
return 1
|
||||
|
||||
if kwargs.storecred:
|
||||
credentials.save_credentials(username, api_key, ftrack_url)
|
||||
|
||||
# Set Ftrack environments
|
||||
os.environ["FTRACK_SERVER"] = ftrack_url
|
||||
os.environ["FTRACK_API_USER"] = username
|
||||
os.environ["FTRACK_API_KEY"] = api_key
|
||||
if event_paths:
|
||||
if isinstance(event_paths, (list, tuple)):
|
||||
event_paths = os.pathsep.join(event_paths)
|
||||
os.environ["FTRACK_EVENTS_PATH"] = event_paths
|
||||
|
||||
if legacy:
|
||||
return legacy_server(ftrack_url)
|
||||
|
||||
return main_loop(ftrack_url)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Register interupt signal
|
||||
def signal_handler(sig, frame):
|
||||
print("You pressed Ctrl+C. Process ended.")
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
sys.exit(main(sys.argv))
|
||||
|
|
|
|||
|
|
@ -67,6 +67,10 @@ class SettingsAction(PypeModule, ITrayAction):
|
|||
return
|
||||
from openpype.tools.settings import MainWidget
|
||||
self.settings_window = MainWidget(self.user_role)
|
||||
self.settings_window.trigger_restart.connect(self._on_trigger_restart)
|
||||
|
||||
def _on_trigger_restart(self):
|
||||
self.manager.restart_tray()
|
||||
|
||||
def show_settings_window(self):
|
||||
"""Show settings tool window.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ import re
|
|||
import copy
|
||||
import json
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import six
|
||||
|
||||
import clique
|
||||
|
||||
import pyblish.api
|
||||
|
|
@ -873,12 +876,6 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
"""
|
||||
filters = []
|
||||
|
||||
letter_box_def = output_def["letter_box"]
|
||||
letter_box_enabled = letter_box_def["enabled"]
|
||||
|
||||
# Get instance data
|
||||
pixel_aspect = temp_data["pixel_aspect"]
|
||||
|
||||
# NOTE Skipped using instance's resolution
|
||||
full_input_path_single_file = temp_data["full_input_path_single_file"]
|
||||
input_data = ffprobe_streams(
|
||||
|
|
@ -887,6 +884,33 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
input_width = int(input_data["width"])
|
||||
input_height = int(input_data["height"])
|
||||
|
||||
# NOTE Setting only one of `width` or `heigth` is not allowed
|
||||
# - settings value can't have None but has value of 0
|
||||
output_width = output_def.get("width") or None
|
||||
output_height = output_def.get("height") or None
|
||||
|
||||
# Convert overscan value video filters
|
||||
overscan_crop = output_def.get("overscan_crop")
|
||||
overscan = OverscanCrop(input_width, input_height, overscan_crop)
|
||||
overscan_crop_filters = overscan.video_filters()
|
||||
# Add overscan filters to filters if are any and modify input
|
||||
# resolution by it's values
|
||||
if overscan_crop_filters:
|
||||
filters.extend(overscan_crop_filters)
|
||||
input_width = overscan.width()
|
||||
input_height = overscan.height()
|
||||
# Use output resolution as inputs after cropping to skip usage of
|
||||
# instance data resolution
|
||||
if output_width is None or output_height is None:
|
||||
output_width = input_width
|
||||
output_height = input_height
|
||||
|
||||
letter_box_def = output_def["letter_box"]
|
||||
letter_box_enabled = letter_box_def["enabled"]
|
||||
|
||||
# Get instance data
|
||||
pixel_aspect = temp_data["pixel_aspect"]
|
||||
|
||||
# Make sure input width and height is not an odd number
|
||||
input_width_is_odd = bool(input_width % 2 != 0)
|
||||
input_height_is_odd = bool(input_height % 2 != 0)
|
||||
|
|
@ -911,10 +935,6 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
self.log.debug("input_width: `{}`".format(input_width))
|
||||
self.log.debug("input_height: `{}`".format(input_height))
|
||||
|
||||
# NOTE Setting only one of `width` or `heigth` is not allowed
|
||||
# - settings value can't have None but has value of 0
|
||||
output_width = output_def.get("width") or None
|
||||
output_height = output_def.get("height") or None
|
||||
# Use instance resolution if output definition has not set it.
|
||||
if output_width is None or output_height is None:
|
||||
output_width = temp_data["resolution_width"]
|
||||
|
|
@ -1438,3 +1458,291 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
vf_back = "-vf " + ",".join(vf_fixed)
|
||||
|
||||
return vf_back
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class _OverscanValue:
|
||||
def __repr__(self):
|
||||
return "<{}> {}".format(self.__class__.__name__, str(self))
|
||||
|
||||
@abstractmethod
|
||||
def copy(self):
|
||||
"""Create a copy of object."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def size_for(self, value):
|
||||
"""Calculate new value for passed value."""
|
||||
pass
|
||||
|
||||
|
||||
class PixValueExplicit(_OverscanValue):
|
||||
def __init__(self, value):
|
||||
self._value = int(value)
|
||||
|
||||
def __str__(self):
|
||||
return "{}px".format(self._value)
|
||||
|
||||
def copy(self):
|
||||
return PixValueExplicit(self._value)
|
||||
|
||||
def size_for(self, value):
|
||||
if self._value == 0:
|
||||
return value
|
||||
return self._value
|
||||
|
||||
|
||||
class PercentValueExplicit(_OverscanValue):
|
||||
def __init__(self, value):
|
||||
self._value = float(value)
|
||||
|
||||
def __str__(self):
|
||||
return "{}%".format(abs(self._value))
|
||||
|
||||
def copy(self):
|
||||
return PercentValueExplicit(self._value)
|
||||
|
||||
def size_for(self, value):
|
||||
if self._value == 0:
|
||||
return value
|
||||
return int((value / 100) * self._value)
|
||||
|
||||
|
||||
class PixValueRelative(_OverscanValue):
|
||||
def __init__(self, value):
|
||||
self._value = int(value)
|
||||
|
||||
def __str__(self):
|
||||
sign = "-" if self._value < 0 else "+"
|
||||
return "{}{}px".format(sign, abs(self._value))
|
||||
|
||||
def copy(self):
|
||||
return PixValueRelative(self._value)
|
||||
|
||||
def size_for(self, value):
|
||||
return value + self._value
|
||||
|
||||
|
||||
class PercentValueRelative(_OverscanValue):
|
||||
def __init__(self, value):
|
||||
self._value = float(value)
|
||||
|
||||
def __str__(self):
|
||||
return "{}%".format(self._value)
|
||||
|
||||
def copy(self):
|
||||
return PercentValueRelative(self._value)
|
||||
|
||||
def size_for(self, value):
|
||||
if self._value == 0:
|
||||
return value
|
||||
|
||||
offset = int((value / 100) * self._value)
|
||||
|
||||
return value + offset
|
||||
|
||||
|
||||
class PercentValueRelativeSource(_OverscanValue):
|
||||
def __init__(self, value, source_sign):
|
||||
self._value = float(value)
|
||||
if source_sign not in ("-", "+"):
|
||||
raise ValueError(
|
||||
"Invalid sign value \"{}\" expected \"-\" or \"+\"".format(
|
||||
source_sign
|
||||
)
|
||||
)
|
||||
self._source_sign = source_sign
|
||||
|
||||
def __str__(self):
|
||||
return "{}%{}".format(self._value, self._source_sign)
|
||||
|
||||
def copy(self):
|
||||
return PercentValueRelativeSource(self._value, self._source_sign)
|
||||
|
||||
def size_for(self, value):
|
||||
if self._value == 0:
|
||||
return value
|
||||
return int((value * 100) / (100 - self._value))
|
||||
|
||||
|
||||
class OverscanCrop:
|
||||
"""Helper class to read overscan string and calculate output resolution.
|
||||
|
||||
It is possible to enter single value for both width and heigh or two values
|
||||
for width and height. Overscan string may have a few variants. Each variant
|
||||
define output size for input size.
|
||||
|
||||
### Example
|
||||
For input size: 2200px
|
||||
|
||||
| String | Output | Description |
|
||||
|----------|--------|-------------------------------------------------|
|
||||
| "" | 2200px | Empty string does nothing. |
|
||||
| "10%" | 220px | Explicit percent size. |
|
||||
| "-10%" | 1980px | Relative percent size (decrease). |
|
||||
| "+10%" | 2420px | Relative percent size (increase). |
|
||||
| "-10%+" | 2000px | Relative percent size to output size. |
|
||||
| "300px" | 300px | Explicit output size cropped or expanded. |
|
||||
| "-300px" | 1900px | Relative pixel size (decrease). |
|
||||
| "+300px" | 2500px | Relative pixel size (increase). |
|
||||
| "300" | 300px | Value without "%" and "px" is used as has "px". |
|
||||
|
||||
Value without sign (+/-) in is always explicit and value with sign is
|
||||
relative. Output size for "200px" and "+200px" are not the same.
|
||||
Values "0", "0px" or "0%" are ignored.
|
||||
|
||||
All values that cause output resolution smaller than 1 pixel are invalid.
|
||||
|
||||
Value "-10%+" is a special case which says that input's resolution is
|
||||
bigger by 10% than expected output.
|
||||
|
||||
It is possible to combine these variants to define different output for
|
||||
width and height.
|
||||
|
||||
Resolution: 2000px 1000px
|
||||
|
||||
| String | Output |
|
||||
|---------------|---------------|
|
||||
| "100px 120px" | 2100px 1120px |
|
||||
| "-10% -200px" | 1800px 800px |
|
||||
"""
|
||||
|
||||
item_regex = re.compile(r"([\+\-])?([0-9]+)(.+)?")
|
||||
relative_source_regex = re.compile(r"%([\+\-])")
|
||||
|
||||
def __init__(self, input_width, input_height, string_value):
|
||||
# Make sure that is not None
|
||||
string_value = string_value or ""
|
||||
|
||||
self.input_width = input_width
|
||||
self.input_height = input_height
|
||||
|
||||
width, height = self._convert_string_to_values(string_value)
|
||||
self._width_value = width
|
||||
self._height_value = height
|
||||
|
||||
self._string_value = string_value
|
||||
|
||||
def __str__(self):
|
||||
return "{}".format(self._string_value)
|
||||
|
||||
def __repr__(self):
|
||||
return "<{}>".format(self.__class__.__name__)
|
||||
|
||||
def width(self):
|
||||
"""Calculated width."""
|
||||
return self._width_value.size_for(self.input_width)
|
||||
|
||||
def height(self):
|
||||
"""Calculated height."""
|
||||
return self._height_value.size_for(self.input_height)
|
||||
|
||||
def video_filters(self):
|
||||
"""FFmpeg video filters to achieve expected result.
|
||||
|
||||
Filter may be empty, use "crop" filter, "pad" filter or combination of
|
||||
"crop" and "pad".
|
||||
|
||||
Returns:
|
||||
list: FFmpeg video filters.
|
||||
"""
|
||||
# crop=width:height:x:y - explicit start x, y position
|
||||
# crop=width:height - x, y are related to center by width/height
|
||||
# pad=width:heigth:x:y - explicit start x, y position
|
||||
# pad=width:heigth - x, y are set to 0 by default
|
||||
|
||||
width = self.width()
|
||||
height = self.height()
|
||||
|
||||
output = []
|
||||
if self.input_width == width and self.input_height == height:
|
||||
return output
|
||||
|
||||
# Make sure resolution has odd numbers
|
||||
if width % 2 == 1:
|
||||
width -= 1
|
||||
|
||||
if height % 2 == 1:
|
||||
height -= 1
|
||||
|
||||
if width <= self.input_width and height <= self.input_height:
|
||||
output.append("crop={}:{}".format(width, height))
|
||||
|
||||
elif width >= self.input_width and height >= self.input_height:
|
||||
output.append(
|
||||
"pad={}:{}:(iw-ow)/2:(ih-oh)/2".format(width, height)
|
||||
)
|
||||
|
||||
elif width > self.input_width and height < self.input_height:
|
||||
output.append("crop=iw:{}".format(height))
|
||||
output.append("pad={}:ih:(iw-ow)/2:(ih-oh)/2".format(width))
|
||||
|
||||
elif width < self.input_width and height > self.input_height:
|
||||
output.append("crop={}:ih".format(width))
|
||||
output.append("pad=iw:{}:(iw-ow)/2:(ih-oh)/2".format(height))
|
||||
|
||||
return output
|
||||
|
||||
def _convert_string_to_values(self, orig_string_value):
|
||||
string_value = orig_string_value.strip().lower()
|
||||
if not string_value:
|
||||
return
|
||||
|
||||
# Replace "px" (and spaces before) with single space
|
||||
string_value = re.sub(r"([ ]+)?px", " ", string_value)
|
||||
string_value = re.sub(r"([ ]+)%", "%", string_value)
|
||||
# Make sure +/- sign at the beggining of string is next to number
|
||||
string_value = re.sub(r"^([\+\-])[ ]+", "\g<1>", string_value)
|
||||
# Make sure +/- sign in the middle has zero spaces before number under
|
||||
# which belongs
|
||||
string_value = re.sub(
|
||||
r"[ ]([\+\-])[ ]+([0-9])",
|
||||
r" \g<1>\g<2>",
|
||||
string_value
|
||||
)
|
||||
string_parts = [
|
||||
part
|
||||
for part in string_value.split(" ")
|
||||
if part
|
||||
]
|
||||
|
||||
error_msg = "Invalid string for rescaling \"{}\"".format(
|
||||
orig_string_value
|
||||
)
|
||||
if 1 > len(string_parts) > 2:
|
||||
raise ValueError(error_msg)
|
||||
|
||||
output = []
|
||||
for item in string_parts:
|
||||
groups = self.item_regex.findall(item)
|
||||
if not groups:
|
||||
raise ValueError(error_msg)
|
||||
|
||||
relative_sign, value, ending = groups[0]
|
||||
if not relative_sign:
|
||||
if not ending:
|
||||
output.append(PixValueExplicit(value))
|
||||
else:
|
||||
output.append(PercentValueExplicit(value))
|
||||
else:
|
||||
source_sign_group = self.relative_source_regex.findall(ending)
|
||||
if not ending:
|
||||
output.append(PixValueRelative(int(relative_sign + value)))
|
||||
|
||||
elif source_sign_group:
|
||||
source_sign = source_sign_group[0]
|
||||
output.append(PercentValueRelativeSource(
|
||||
float(relative_sign + value), source_sign
|
||||
))
|
||||
else:
|
||||
output.append(
|
||||
PercentValueRelative(float(relative_sign + value))
|
||||
)
|
||||
|
||||
if len(output) == 1:
|
||||
width = output.pop(0)
|
||||
height = width.copy()
|
||||
else:
|
||||
width, height = output
|
||||
|
||||
return width, height
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
"ftrack"
|
||||
]
|
||||
},
|
||||
"overscan_crop": "",
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"bg_color": [
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ class BaseItemEntity(BaseEntity):
|
|||
self.file_item = None
|
||||
# Reference to `RootEntity`
|
||||
self.root_item = None
|
||||
# Change of value requires restart of OpenPype
|
||||
self._require_restart_on_change = False
|
||||
|
||||
# Entity is in hierarchy of dynamically created entity
|
||||
self.is_in_dynamic_item = False
|
||||
|
|
@ -171,6 +173,14 @@ class BaseItemEntity(BaseEntity):
|
|||
roles = [roles]
|
||||
self.roles = roles
|
||||
|
||||
@property
|
||||
def require_restart_on_change(self):
|
||||
return self._require_restart_on_change
|
||||
|
||||
@property
|
||||
def require_restart(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def has_studio_override(self):
|
||||
"""Says if entity or it's children has studio overrides."""
|
||||
|
|
@ -261,6 +271,14 @@ class BaseItemEntity(BaseEntity):
|
|||
self, "Dynamic entity has set `is_group` to true."
|
||||
)
|
||||
|
||||
if (
|
||||
self.require_restart_on_change
|
||||
and (self.is_dynamic_item or self.is_in_dynamic_item)
|
||||
):
|
||||
raise EntitySchemaError(
|
||||
self, "Dynamic entity can't require restart."
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def set_override_state(self, state):
|
||||
"""Set override state and trigger it on children.
|
||||
|
|
@ -788,6 +806,15 @@ class ItemEntity(BaseItemEntity):
|
|||
# Root item reference
|
||||
self.root_item = self.parent.root_item
|
||||
|
||||
# Item require restart on value change
|
||||
require_restart_on_change = self.schema_data.get("require_restart")
|
||||
if (
|
||||
require_restart_on_change is None
|
||||
and not (self.is_dynamic_item or self.is_in_dynamic_item)
|
||||
):
|
||||
require_restart_on_change = self.parent.require_restart_on_change
|
||||
self._require_restart_on_change = require_restart_on_change
|
||||
|
||||
# File item reference
|
||||
if self.parent.is_file:
|
||||
self.file_item = self.parent
|
||||
|
|
|
|||
|
|
@ -439,10 +439,10 @@ class DictMutableKeysEntity(EndpointEntity):
|
|||
new_initial_value = []
|
||||
for key, value in _settings_value:
|
||||
if key in initial_value:
|
||||
new_initial_value.append(key, initial_value.pop(key))
|
||||
new_initial_value.append([key, initial_value.pop(key)])
|
||||
|
||||
for key, value in initial_value.items():
|
||||
new_initial_value.append(key, value)
|
||||
new_initial_value.append([key, value])
|
||||
initial_value = new_initial_value
|
||||
else:
|
||||
initial_value = _settings_value
|
||||
|
|
|
|||
|
|
@ -68,8 +68,18 @@ class EndpointEntity(ItemEntity):
|
|||
def on_change(self):
|
||||
for callback in self.on_change_callbacks:
|
||||
callback()
|
||||
|
||||
if self.require_restart_on_change:
|
||||
if self.require_restart:
|
||||
self.root_item.add_item_require_restart(self)
|
||||
else:
|
||||
self.root_item.remove_item_require_restart(self)
|
||||
self.parent.on_child_change(self)
|
||||
|
||||
@property
|
||||
def require_restart(self):
|
||||
return self.has_unsaved_changes
|
||||
|
||||
def update_default_value(self, value):
|
||||
value = self._check_update_value(value, "default")
|
||||
self._default_value = value
|
||||
|
|
@ -115,6 +125,10 @@ class InputEntity(EndpointEntity):
|
|||
"""Entity's value without metadata."""
|
||||
return self._current_value
|
||||
|
||||
@property
|
||||
def require_restart(self):
|
||||
return self._value_is_modified
|
||||
|
||||
def _settings_value(self):
|
||||
return copy.deepcopy(self.value)
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ class RootEntity(BaseItemEntity):
|
|||
|
||||
def __init__(self, schema_data, reset):
|
||||
super(RootEntity, self).__init__(schema_data)
|
||||
self._require_restart_callbacks = []
|
||||
self._item_ids_require_restart = set()
|
||||
self._item_initalization()
|
||||
if reset:
|
||||
self.reset()
|
||||
|
|
@ -64,6 +66,31 @@ class RootEntity(BaseItemEntity):
|
|||
"""Current OverrideState."""
|
||||
return self._override_state
|
||||
|
||||
@property
|
||||
def require_restart(self):
|
||||
return bool(self._item_ids_require_restart)
|
||||
|
||||
def add_require_restart_change_callback(self, callback):
|
||||
self._require_restart_callbacks.append(callback)
|
||||
|
||||
def _on_require_restart_change(self):
|
||||
for callback in self._require_restart_callbacks:
|
||||
callback()
|
||||
|
||||
def add_item_require_restart(self, item):
|
||||
was_empty = len(self._item_ids_require_restart) == 0
|
||||
self._item_ids_require_restart.add(item.id)
|
||||
if was_empty:
|
||||
self._on_require_restart_change()
|
||||
|
||||
def remove_item_require_restart(self, item):
|
||||
if item.id not in self._item_ids_require_restart:
|
||||
return
|
||||
|
||||
self._item_ids_require_restart.remove(item.id)
|
||||
if not self._item_ids_require_restart:
|
||||
self._on_require_restart_change()
|
||||
|
||||
@abstractmethod
|
||||
def reset(self):
|
||||
"""Reset values and entities to initial state.
|
||||
|
|
|
|||
|
|
@ -173,6 +173,15 @@
|
|||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Crop input overscan. See the documentation for more information."
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "overscan_crop",
|
||||
"label": "Overscan crop"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Width and Height must be both set to higher value than 0 else source resolution is used."
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
"key": "ftrack",
|
||||
"label": "Ftrack",
|
||||
"collapsible": true,
|
||||
"require_restart": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@
|
|||
"key": "environment",
|
||||
"label": "Environment",
|
||||
"type": "raw-json",
|
||||
"env_group_key": "global"
|
||||
"env_group_key": "global",
|
||||
"require_restart": true
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
|
|
@ -44,7 +45,8 @@
|
|||
"key": "openpype_path",
|
||||
"label": "Versions Repository",
|
||||
"multiplatform": true,
|
||||
"multipath": true
|
||||
"multipath": true,
|
||||
"require_restart": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"key": "avalon",
|
||||
"label": "Avalon",
|
||||
"collapsible": true,
|
||||
"require_restart": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "number",
|
||||
|
|
@ -35,6 +36,7 @@
|
|||
"key": "timers_manager",
|
||||
"label": "Timers Manager",
|
||||
"collapsible": true,
|
||||
"require_restart": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
@ -66,6 +68,7 @@
|
|||
"key": "clockify",
|
||||
"label": "Clockify",
|
||||
"collapsible": true,
|
||||
"require_restart": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
@ -84,6 +87,7 @@
|
|||
"key": "sync_server",
|
||||
"label": "Site Sync",
|
||||
"collapsible": true,
|
||||
"require_restart": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
@ -114,6 +118,7 @@
|
|||
"type": "dict",
|
||||
"key": "deadline",
|
||||
"label": "Deadline",
|
||||
"require_restart": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
|
|
@ -133,6 +138,7 @@
|
|||
"type": "dict",
|
||||
"key": "muster",
|
||||
"label": "Muster",
|
||||
"require_restart": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ class IgnoreInputChangesObj:
|
|||
class SettingsCategoryWidget(QtWidgets.QWidget):
|
||||
state_changed = QtCore.Signal()
|
||||
saved = QtCore.Signal(QtWidgets.QWidget)
|
||||
restart_required_trigger = QtCore.Signal()
|
||||
|
||||
def __init__(self, user_role, parent=None):
|
||||
super(SettingsCategoryWidget, self).__init__(parent)
|
||||
|
|
@ -185,9 +186,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
if self.user_role == "developer":
|
||||
self._add_developer_ui(footer_layout)
|
||||
|
||||
save_btn = QtWidgets.QPushButton("Save")
|
||||
spacer_widget = QtWidgets.QWidget()
|
||||
footer_layout.addWidget(spacer_widget, 1)
|
||||
save_btn = QtWidgets.QPushButton("Save", footer_widget)
|
||||
require_restart_label = QtWidgets.QLabel(footer_widget)
|
||||
require_restart_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
footer_layout.addWidget(require_restart_label, 1)
|
||||
footer_layout.addWidget(save_btn, 0)
|
||||
|
||||
configurations_layout = QtWidgets.QVBoxLayout(configurations_widget)
|
||||
|
|
@ -205,6 +207,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
save_btn.clicked.connect(self._save)
|
||||
|
||||
self.save_btn = save_btn
|
||||
self.require_restart_label = require_restart_label
|
||||
self.scroll_widget = scroll_widget
|
||||
self.content_layout = content_layout
|
||||
self.content_widget = content_widget
|
||||
|
|
@ -323,6 +326,15 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
def _on_reset_start(self):
|
||||
return
|
||||
|
||||
def _on_require_restart_change(self):
|
||||
value = ""
|
||||
if self.entity.require_restart:
|
||||
value = (
|
||||
"Your changes require restart of"
|
||||
" all running OpenPype processes to take affect."
|
||||
)
|
||||
self.require_restart_label.setText(value)
|
||||
|
||||
def reset(self):
|
||||
self.set_state(CategoryState.Working)
|
||||
|
||||
|
|
@ -339,6 +351,9 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
dialog = None
|
||||
try:
|
||||
self._create_root_entity()
|
||||
self.entity.add_require_restart_change_callback(
|
||||
self._on_require_restart_change
|
||||
)
|
||||
|
||||
self.add_children_gui()
|
||||
|
||||
|
|
@ -433,6 +448,15 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
return
|
||||
|
||||
def _save(self):
|
||||
# Don't trigger restart if defaults are modified
|
||||
if (
|
||||
self.modify_defaults_checkbox
|
||||
and self.modify_defaults_checkbox.isChecked()
|
||||
):
|
||||
require_restart = False
|
||||
else:
|
||||
require_restart = self.entity.require_restart
|
||||
|
||||
self.set_state(CategoryState.Working)
|
||||
|
||||
if self.items_are_valid():
|
||||
|
|
@ -442,6 +466,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
|
||||
self.saved.emit(self)
|
||||
|
||||
if require_restart:
|
||||
self.restart_required_trigger.emit()
|
||||
self.require_restart_label.setText("")
|
||||
|
||||
def _on_refresh(self):
|
||||
self.reset()
|
||||
|
||||
|
|
|
|||
|
|
@ -275,8 +275,6 @@ class UnsavedChangesDialog(QtWidgets.QDialog):
|
|||
layout.addWidget(message_label)
|
||||
layout.addWidget(btns_widget)
|
||||
|
||||
self.state = None
|
||||
|
||||
def on_cancel_pressed(self):
|
||||
self.done(0)
|
||||
|
||||
|
|
@ -287,6 +285,48 @@ class UnsavedChangesDialog(QtWidgets.QDialog):
|
|||
self.done(2)
|
||||
|
||||
|
||||
class RestartDialog(QtWidgets.QDialog):
|
||||
message = (
|
||||
"Your changes require restart of process to take effect."
|
||||
" Do you want to restart now?"
|
||||
)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(RestartDialog, self).__init__(parent)
|
||||
message_label = QtWidgets.QLabel(self.message)
|
||||
|
||||
btns_widget = QtWidgets.QWidget(self)
|
||||
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
|
||||
|
||||
btn_restart = QtWidgets.QPushButton("Restart")
|
||||
btn_restart.clicked.connect(self.on_restart_pressed)
|
||||
btn_cancel = QtWidgets.QPushButton("Cancel")
|
||||
btn_cancel.clicked.connect(self.on_cancel_pressed)
|
||||
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(btn_restart)
|
||||
btns_layout.addWidget(btn_cancel)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(message_label)
|
||||
layout.addWidget(btns_widget)
|
||||
|
||||
self.btn_cancel = btn_cancel
|
||||
self.btn_restart = btn_restart
|
||||
|
||||
def showEvent(self, event):
|
||||
super(RestartDialog, self).showEvent(event)
|
||||
btns_width = max(self.btn_cancel.width(), self.btn_restart.width())
|
||||
self.btn_cancel.setFixedWidth(btns_width)
|
||||
self.btn_restart.setFixedWidth(btns_width)
|
||||
|
||||
def on_cancel_pressed(self):
|
||||
self.done(0)
|
||||
|
||||
def on_restart_pressed(self):
|
||||
self.done(1)
|
||||
|
||||
|
||||
class SpacerWidget(QtWidgets.QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super(SpacerWidget, self).__init__(parent)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from .categories import (
|
|||
SystemWidget,
|
||||
ProjectWidget
|
||||
)
|
||||
from .widgets import ShadowWidget
|
||||
from .widgets import ShadowWidget, RestartDialog
|
||||
from . import style
|
||||
|
||||
from openpype.tools.settings import (
|
||||
|
|
@ -14,6 +14,8 @@ from openpype.tools.settings import (
|
|||
|
||||
|
||||
class MainWidget(QtWidgets.QWidget):
|
||||
trigger_restart = QtCore.Signal()
|
||||
|
||||
widget_width = 1000
|
||||
widget_height = 600
|
||||
|
||||
|
|
@ -60,6 +62,9 @@ class MainWidget(QtWidgets.QWidget):
|
|||
for tab_widget in tab_widgets:
|
||||
tab_widget.saved.connect(self._on_tab_save)
|
||||
tab_widget.state_changed.connect(self._on_state_change)
|
||||
tab_widget.restart_required_trigger.connect(
|
||||
self._on_restart_required
|
||||
)
|
||||
|
||||
self.tab_widgets = tab_widgets
|
||||
|
||||
|
|
@ -132,3 +137,15 @@ class MainWidget(QtWidgets.QWidget):
|
|||
|
||||
for tab_widget in self.tab_widgets:
|
||||
tab_widget.reset()
|
||||
|
||||
def _on_restart_required(self):
|
||||
# Don't show dialog if there are not registered slots for
|
||||
# `trigger_restart` signal.
|
||||
# - For example when settings are runnin as standalone tool
|
||||
if self.receivers(self.trigger_restart) < 1:
|
||||
return
|
||||
|
||||
dialog = RestartDialog(self)
|
||||
result = dialog.exec_()
|
||||
if result == 1:
|
||||
self.trigger_restart.emit()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import os
|
||||
import sys
|
||||
import atexit
|
||||
import subprocess
|
||||
|
||||
import platform
|
||||
from avalon import style
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
from openpype.api import Logger, resources
|
||||
from openpype.lib import get_pype_execute_args
|
||||
from openpype.modules import TrayModulesManager, ITrayService
|
||||
from openpype.settings.lib import get_system_settings
|
||||
import openpype.version
|
||||
|
|
@ -92,6 +95,34 @@ class TrayManager:
|
|||
self.tray_widget.menu.addAction(version_action)
|
||||
self.tray_widget.menu.addSeparator()
|
||||
|
||||
def restart(self):
|
||||
"""Restart Tray tool.
|
||||
|
||||
First creates new process with same argument and close current tray.
|
||||
"""
|
||||
args = get_pype_execute_args()
|
||||
# Create a copy of sys.argv
|
||||
additional_args = list(sys.argv)
|
||||
# Check last argument from `get_pype_execute_args`
|
||||
# - when running from code it is the same as first from sys.argv
|
||||
if args[-1] == additional_args[0]:
|
||||
additional_args.pop(0)
|
||||
args.extend(additional_args)
|
||||
|
||||
kwargs = {}
|
||||
if platform.system().lower() == "windows":
|
||||
flags = (
|
||||
subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
| subprocess.DETACHED_PROCESS
|
||||
)
|
||||
kwargs["creationflags"] = flags
|
||||
|
||||
subprocess.Popen(args, **kwargs)
|
||||
self.exit()
|
||||
|
||||
def exit(self):
|
||||
self.tray_widget.exit()
|
||||
|
||||
def on_exit(self):
|
||||
self.modules_manager.on_exit()
|
||||
|
||||
|
|
@ -116,6 +147,8 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
|||
|
||||
super(SystemTrayIcon, self).__init__(icon, parent)
|
||||
|
||||
self._exited = False
|
||||
|
||||
# Store parent - QtWidgets.QMainWindow()
|
||||
self.parent = parent
|
||||
|
||||
|
|
@ -134,6 +167,8 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
|||
# Add menu to Context of SystemTrayIcon
|
||||
self.setContextMenu(self.menu)
|
||||
|
||||
atexit.register(self.exit)
|
||||
|
||||
def on_systray_activated(self, reason):
|
||||
# show contextMenu if left click
|
||||
if reason == QtWidgets.QSystemTrayIcon.Trigger:
|
||||
|
|
@ -145,6 +180,10 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
|||
|
||||
- Icon won't stay in tray after exit.
|
||||
"""
|
||||
if self._exited:
|
||||
return
|
||||
self._exited = True
|
||||
|
||||
self.hide()
|
||||
self.tray_man.on_exit()
|
||||
QtCore.QCoreApplication.exit()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.0.0-rc.5"
|
||||
__version__ = "3.0.0-rc.6"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.0.0-rc.5"
|
||||
version = "3.0.0-rc.6"
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
123
start.py
|
|
@ -6,10 +6,11 @@ Bootstrapping process of OpenPype is as follows:
|
|||
`OPENPYPE_PATH` is checked for existence - either one from environment or
|
||||
from user settings. Precedence takes the one set by environment.
|
||||
|
||||
On this path we try to find OpenPype in directories version string in their names.
|
||||
For example: `openpype-v3.0.1-foo` is valid name, or even `foo_3.0.2` - as long
|
||||
as version can be determined from its name _AND_ file `openpype/openpype/version.py`
|
||||
can be found inside, it is considered OpenPype installation.
|
||||
On this path we try to find OpenPype in directories version string in their
|
||||
names. For example: `openpype-v3.0.1-foo` is valid name, or
|
||||
even `foo_3.0.2` - as long as version can be determined from its name
|
||||
_AND_ file `openpype/openpype/version.py` can be found inside, it is
|
||||
considered OpenPype installation.
|
||||
|
||||
If no OpenPype repositories are found in `OPENPYPE_PATH` (user data dir)
|
||||
then **Igniter** (OpenPype setup tool) will launch its GUI.
|
||||
|
|
@ -20,19 +21,19 @@ appdata dir in user home and extract it there. Version will be determined by
|
|||
version specified in OpenPype module.
|
||||
|
||||
If OpenPype repository directories are found in default install location
|
||||
(user data dir) or in `OPENPYPE_PATH`, it will get list of those dirs there and
|
||||
use latest one or the one specified with optional `--use-version` command
|
||||
line argument. If the one specified doesn't exist then latest available
|
||||
version will be used. All repositories in that dir will be added
|
||||
(user data dir) or in `OPENPYPE_PATH`, it will get list of those dirs
|
||||
there and use latest one or the one specified with optional `--use-version`
|
||||
command line argument. If the one specified doesn't exist then latest
|
||||
available version will be used. All repositories in that dir will be added
|
||||
to `sys.path` and `PYTHONPATH`.
|
||||
|
||||
If OpenPype is live (not frozen) then current version of OpenPype module will be
|
||||
used. All directories under `repos` will be added to `sys.path` and
|
||||
If OpenPype is live (not frozen) then current version of OpenPype module
|
||||
will be used. All directories under `repos` will be added to `sys.path` and
|
||||
`PYTHONPATH`.
|
||||
|
||||
OpenPype depends on connection to `MongoDB`_. You can specify MongoDB connection
|
||||
string via `OPENPYPE_MONGO` set in environment or it can be set in user
|
||||
settings or via **Igniter** GUI.
|
||||
OpenPype depends on connection to `MongoDB`_. You can specify MongoDB
|
||||
connection string via `OPENPYPE_MONGO` set in environment or it can be set
|
||||
in user settings or via **Igniter** GUI.
|
||||
|
||||
So, bootstrapping OpenPype looks like this::
|
||||
|
||||
|
|
@ -305,7 +306,8 @@ def _process_arguments() -> tuple:
|
|||
_print(" --use-version=3.0.0")
|
||||
sys.exit(1)
|
||||
|
||||
m = re.search(r"--use-version=(?P<version>\d+\.\d+\.\d*.+?)", arg)
|
||||
m = re.search(
|
||||
r"--use-version=(?P<version>\d+\.\d+\.\d+(?:\S*)?)", arg)
|
||||
if m and m.group('version'):
|
||||
use_version = m.group('version')
|
||||
sys.argv.remove(arg)
|
||||
|
|
@ -437,6 +439,7 @@ def _find_frozen_openpype(use_version: str = None,
|
|||
(if requested).
|
||||
|
||||
"""
|
||||
version_path = None
|
||||
openpype_version = None
|
||||
openpype_versions = bootstrap.find_openpype(include_zips=True,
|
||||
staging=use_staging)
|
||||
|
|
@ -456,7 +459,6 @@ def _find_frozen_openpype(use_version: str = None,
|
|||
if local_version == openpype_versions[-1]:
|
||||
os.environ["OPENPYPE_TRYOUT"] = "1"
|
||||
openpype_versions = []
|
||||
|
||||
else:
|
||||
_print("!!! Warning: cannot determine current running version.")
|
||||
|
||||
|
|
@ -503,17 +505,25 @@ def _find_frozen_openpype(use_version: str = None,
|
|||
return version_path
|
||||
|
||||
# get path of version specified in `--use-version`
|
||||
version_path = BootstrapRepos.get_version_path_from_list(
|
||||
use_version, openpype_versions)
|
||||
local_version = bootstrap.get_version(OPENPYPE_ROOT)
|
||||
if use_version and use_version != local_version:
|
||||
# force the one user has selected
|
||||
openpype_version = None
|
||||
openpype_versions = bootstrap.find_openpype(include_zips=True,
|
||||
staging=use_staging)
|
||||
v: OpenPypeVersion
|
||||
found = [v for v in openpype_versions if str(v) == use_version]
|
||||
if found:
|
||||
openpype_version = sorted(found)[-1]
|
||||
if not openpype_version:
|
||||
_print(f"!!! requested version {use_version} was not found.")
|
||||
if openpype_versions:
|
||||
_print(" - found: ")
|
||||
for v in sorted(openpype_versions):
|
||||
_print(f" - {v}: {v.path}")
|
||||
|
||||
if not version_path:
|
||||
if use_version is not None and openpype_version:
|
||||
_print(("!!! Specified version was not found, using "
|
||||
"latest available"))
|
||||
# specified version was not found so use latest detected.
|
||||
version_path = openpype_version.path
|
||||
_print(f">>> Using version [ {openpype_version} ]")
|
||||
_print(f" From {version_path}")
|
||||
_print(f" - local version {local_version}")
|
||||
sys.exit(1)
|
||||
|
||||
# test if latest detected is installed (in user data dir)
|
||||
is_inside = False
|
||||
|
|
@ -544,7 +554,7 @@ def _find_frozen_openpype(use_version: str = None,
|
|||
openpype_version.path = version_path
|
||||
|
||||
_initialize_environment(openpype_version)
|
||||
return version_path
|
||||
return openpype_version.path
|
||||
|
||||
|
||||
def _bootstrap_from_code(use_version):
|
||||
|
|
@ -559,36 +569,53 @@ def _bootstrap_from_code(use_version):
|
|||
"""
|
||||
# run through repos and add them to `sys.path` and `PYTHONPATH`
|
||||
# set root
|
||||
_openpype_root = OPENPYPE_ROOT
|
||||
if getattr(sys, 'frozen', False):
|
||||
local_version = bootstrap.get_version(Path(OPENPYPE_ROOT))
|
||||
local_version = bootstrap.get_version(Path(_openpype_root))
|
||||
_print(f" - running version: {local_version}")
|
||||
assert local_version
|
||||
else:
|
||||
# get current version of OpenPype
|
||||
local_version = bootstrap.get_local_live_version()
|
||||
|
||||
os.environ["OPENPYPE_VERSION"] = local_version
|
||||
if use_version and use_version != local_version:
|
||||
version_to_use = None
|
||||
openpype_versions = bootstrap.find_openpype(include_zips=True)
|
||||
version_path = BootstrapRepos.get_version_path_from_list(
|
||||
use_version, openpype_versions)
|
||||
if version_path:
|
||||
# use specified
|
||||
bootstrap.add_paths_from_directory(version_path)
|
||||
os.environ["OPENPYPE_VERSION"] = use_version
|
||||
else:
|
||||
version_path = OPENPYPE_ROOT
|
||||
v: OpenPypeVersion
|
||||
found = [v for v in openpype_versions if str(v) == use_version]
|
||||
if found:
|
||||
version_to_use = sorted(found)[-1]
|
||||
|
||||
repos = os.listdir(os.path.join(OPENPYPE_ROOT, "repos"))
|
||||
repos = [os.path.join(OPENPYPE_ROOT, "repos", repo) for repo in repos]
|
||||
if version_to_use:
|
||||
# use specified
|
||||
if version_to_use.path.is_file():
|
||||
version_to_use.path = bootstrap.extract_openpype(
|
||||
version_to_use)
|
||||
bootstrap.add_paths_from_directory(version_to_use.path)
|
||||
os.environ["OPENPYPE_VERSION"] = use_version
|
||||
version_path = version_to_use.path
|
||||
os.environ["OPENPYPE_REPOS_ROOT"] = (version_path / "openpype").as_posix() # noqa: E501
|
||||
_openpype_root = version_to_use.path.as_posix()
|
||||
else:
|
||||
_print(f"!!! requested version {use_version} was not found.")
|
||||
if openpype_versions:
|
||||
_print(" - found: ")
|
||||
for v in sorted(openpype_versions):
|
||||
_print(f" - {v}: {v.path}")
|
||||
|
||||
_print(f" - local version {local_version}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
os.environ["OPENPYPE_VERSION"] = local_version
|
||||
version_path = Path(_openpype_root)
|
||||
os.environ["OPENPYPE_REPOS_ROOT"] = _openpype_root
|
||||
|
||||
repos = os.listdir(os.path.join(_openpype_root, "repos"))
|
||||
repos = [os.path.join(_openpype_root, "repos", repo) for repo in repos]
|
||||
# add self to python paths
|
||||
repos.insert(0, OPENPYPE_ROOT)
|
||||
repos.insert(0, _openpype_root)
|
||||
for repo in repos:
|
||||
sys.path.insert(0, repo)
|
||||
|
||||
# Set OPENPYPE_REPOS_ROOT to code root
|
||||
os.environ["OPENPYPE_REPOS_ROOT"] = OPENPYPE_ROOT
|
||||
|
||||
# add venv 'site-packages' to PYTHONPATH
|
||||
python_path = os.getenv("PYTHONPATH", "")
|
||||
split_paths = python_path.split(os.pathsep)
|
||||
|
|
@ -603,11 +630,11 @@ def _bootstrap_from_code(use_version):
|
|||
# point to same hierarchy from code and from frozen OpenPype
|
||||
additional_paths = [
|
||||
# add OpenPype tools
|
||||
os.path.join(OPENPYPE_ROOT, "openpype", "tools"),
|
||||
os.path.join(_openpype_root, "openpype", "tools"),
|
||||
# add common OpenPype vendor
|
||||
# (common for multiple Python interpreter versions)
|
||||
os.path.join(
|
||||
OPENPYPE_ROOT,
|
||||
_openpype_root,
|
||||
"openpype",
|
||||
"vendor",
|
||||
"python",
|
||||
|
|
@ -620,7 +647,7 @@ def _bootstrap_from_code(use_version):
|
|||
|
||||
os.environ["PYTHONPATH"] = os.pathsep.join(split_paths)
|
||||
|
||||
return Path(version_path)
|
||||
return version_path
|
||||
|
||||
|
||||
def boot():
|
||||
|
|
@ -647,6 +674,10 @@ def boot():
|
|||
|
||||
use_version, use_staging = _process_arguments()
|
||||
|
||||
if os.getenv("OPENPYPE_VERSION"):
|
||||
use_staging = "staging" in os.getenv("OPENPYPE_VERSION")
|
||||
use_version = os.getenv("OPENPYPE_VERSION")
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Determine mongodb connection
|
||||
# ------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -175,9 +175,9 @@ if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) {
|
|||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
Write-Host "Cleaning cache files ... " -NoNewline
|
||||
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Remove-Item -Force
|
||||
Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Remove-Item -Force
|
||||
Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse | Remove-Item -Force -Recurse
|
||||
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
|
||||
Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
|
||||
Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force -Recurse
|
||||
Write-Host "OK" -ForegroundColor green
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
|
|
|
|||
|
|
@ -122,7 +122,8 @@ clean_pyc () {
|
|||
local path
|
||||
path=$openpype_root
|
||||
echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c"
|
||||
find "$path" -regex '^.*\(__pycache__\|\.py[co]\)$' -delete
|
||||
find "$path" -path ./build -prune -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete
|
||||
|
||||
echo -e "${BIGreen}DONE${RST}"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,16 +76,20 @@ print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))
|
|||
Set-Location -Path $current_dir
|
||||
Exit-WithCode 1
|
||||
}
|
||||
# We are supporting python 3.6 and up
|
||||
if(($matches[1] -lt 3) -or ($matches[2] -lt 7)) {
|
||||
# We are supporting python 3.7 only
|
||||
if (($matches[1] -lt 3) -or ($matches[2] -lt 7)) {
|
||||
Write-Host "FAILED Version [ $p ] is old and unsupported" -ForegroundColor red
|
||||
Set-Location -Path $current_dir
|
||||
Exit-WithCode 1
|
||||
} elseif (($matches[1] -eq 3) -and ($matches[2] -gt 7)) {
|
||||
Write-Host "WARNING Version [ $p ] is unsupported, use at your own risk." -ForegroundColor yellow
|
||||
Write-Host "*** " -NoNewline -ForegroundColor yellow
|
||||
Write-Host "OpenPype supports only Python 3.7" -ForegroundColor white
|
||||
} else {
|
||||
Write-Host "OK [ $p ]" -ForegroundColor green
|
||||
}
|
||||
Write-Host "OK [ $p ]" -ForegroundColor green
|
||||
}
|
||||
|
||||
|
||||
$current_dir = Get-Location
|
||||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ clean_pyc () {
|
|||
local path
|
||||
path=$openpype_root
|
||||
echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c"
|
||||
find "$path" -regex '^.*\(__pycache__\|\.py[co]\)$' -delete
|
||||
find "$path" -path ./build -prune -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete
|
||||
echo -e "${BIGreen}DONE${RST}"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,9 +98,9 @@ if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) {
|
|||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
Write-Host "Cleaning cache files ... " -NoNewline
|
||||
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Remove-Item -Force
|
||||
Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Remove-Item -Force
|
||||
Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse | Remove-Item -Force -Recurse
|
||||
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
|
||||
Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
|
||||
Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse| Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force -Recurse
|
||||
Write-Host "OK" -ForegroundColor green
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
|
|
|
|||
|
|
@ -89,23 +89,6 @@ detect_python () {
|
|||
fi
|
||||
}
|
||||
|
||||
##############################################################################
|
||||
# Clean pyc files in specified directory
|
||||
# Globals:
|
||||
# None
|
||||
# Arguments:
|
||||
# Optional path to clean
|
||||
# Returns:
|
||||
# None
|
||||
###############################################################################
|
||||
clean_pyc () {
|
||||
local path
|
||||
path=$openpype_root
|
||||
echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c"
|
||||
find "$path" -regex '^.*\(__pycache__\|\.py[co]\)$' -delete
|
||||
echo -e "${BIGreen}DONE${RST}"
|
||||
}
|
||||
|
||||
##############################################################################
|
||||
# Return absolute path
|
||||
# Globals:
|
||||
|
|
|
|||
|
|
@ -94,8 +94,8 @@ if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) {
|
|||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
Write-Host "Cleaning cache files ... " -NoNewline
|
||||
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Remove-Item -Force
|
||||
Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse | Remove-Item -Force -Recurse
|
||||
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
|
||||
Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force -Recurse
|
||||
Write-Host "OK" -ForegroundColor green
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ clean_pyc () {
|
|||
local path
|
||||
path=$openpype_root
|
||||
echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c"
|
||||
find "$path" -regex '^.*\(__pycache__\|\.py[co]\)$' -delete
|
||||
find "$path" -path ./build -prune -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete
|
||||
echo -e "${BIGreen}DONE${RST}"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
|
@ -69,6 +69,49 @@ Profile may generate multiple outputs from a single input. Each output must defi
|
|||
- it is possible to rescale output to specified resolution and keep aspect ratio.
|
||||
- If value is set to 0, source resolution will be used.
|
||||
|
||||
- **`Overscan crop`**
|
||||
- Crop input resolution before rescaling.
|
||||
|
||||
- Value is text may have a few variants. Each variant define output size for input size.
|
||||
|
||||
- All values that cause output resolution smaller than 1 pixel are invalid.
|
||||
|
||||
- Value without sign (+/-) in is always explicit and value with sign is
|
||||
relative. Output size for values "200px" and "+200px" are not the same "+200px" will add 200 pixels to source and "200px" will keep only 200px from source. Value of "0", "0px" or "0%" are automatically converted to "+0px" as 0px is invalid ouput.
|
||||
|
||||
- Cropped value is related to center. It is better to avoid odd numbers if
|
||||
possible.
|
||||
|
||||
**Example outputs for input size: 2200px**
|
||||
|
||||
| String | Output | Description |
|
||||
|---|---|---|
|
||||
| ` ` | 2200px | Empty string keep resolution unchanged. |
|
||||
| `50%` | 1100px | Crop 25% of input width on left and right side. |
|
||||
| `300px` | 300px | Keep 300px in center of input and crop rest on left adn right. |
|
||||
| `300` | 300px | Values without units are used as pixels (`px`). |
|
||||
| `+0px` | 2200px | Keep resolution unchanged. |
|
||||
| `0px` | 2200px | Same as `+0px`. |
|
||||
| `+300px` | 2500px | Add black pillars of 150px width on left and right side. |
|
||||
| `-300px` | 1900px | Crop 150px on left and right side |
|
||||
| `+10%` | 2420px | Add black pillars of 5% size of input on left and right side. |
|
||||
| `-10%` | 1980px | Crop 5% of input size by on left and right side. |
|
||||
| `-10%+` | 2000px | Input width is 110% of output width. |
|
||||
|
||||
**Value "-10%+" is a special case which says that input's resolution is
|
||||
bigger by 10% than expected output.**
|
||||
|
||||
- It is possible to enter single value for both width and height or
|
||||
combination of two variants for width and height separated with space.
|
||||
|
||||
**Example for resolution: 2000px 1000px**
|
||||
|
||||
| String | Output |
|
||||
|---------------|---------------|
|
||||
| "100px 120px" | 2100px 1120px |
|
||||
| "-10% -200px" | 1800px 800px |
|
||||
| "-10% -0px" | 1800px 1000px |
|
||||
|
||||
- **`Letter Box`**
|
||||
- **Enabled** - Enable letter boxes
|
||||
- **Ratio** - Ratio of letter boxes
|
||||
|
|
|
|||