diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index ee03e04360..eefbcc5d20 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -113,6 +113,14 @@ def check_inventory_versions(): "_id": io.ObjectId(avalon_knob_data["representation"]) }) + # Failsafe for not finding the representation. + if not representation: + log.warning( + "Could not find the representation on " + "node \"{}\"".format(node.name()) + ) + continue + # Get start frame from version data version = io.find_one({ "type": "version", diff --git a/openpype/hosts/nuke/plugins/load/load_effects.py b/openpype/hosts/nuke/plugins/load/load_effects.py index 6306767f37..8ba1b6b7c1 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects.py +++ b/openpype/hosts/nuke/plugins/load/load_effects.py @@ -214,7 +214,7 @@ class LoadEffects(api.Loader): self.log.warning(e) continue - if isinstance(v, list) and len(v) > 3: + if isinstance(v, list) and len(v) > 4: node[k].setAnimated() for i, value in enumerate(v): if isinstance(value, list): diff --git a/openpype/hosts/nuke/plugins/load/load_effects_ip.py b/openpype/hosts/nuke/plugins/load/load_effects_ip.py index 6c71f2ae16..d0cab26842 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_effects_ip.py @@ -217,7 +217,7 @@ class LoadEffectsInputProcess(api.Loader): self.log.warning(e) continue - if isinstance(v, list) and len(v) > 3: + if isinstance(v, list) and len(v) > 4: node[k].setAnimated() for i, value in enumerate(v): if isinstance(value, list): @@ -239,10 +239,10 @@ class LoadEffectsInputProcess(api.Loader): output = nuke.createNode("Output") output.setInput(0, pre_node) - # try to place it under Viewer1 - if not self.connect_active_viewer(GN): - nuke.delete(GN) - return + # # try to place it under Viewer1 + # if not self.connect_active_viewer(GN): + # nuke.delete(GN) + # return # get all versions in list versions = io.find({ @@ -298,7 +298,11 @@ class LoadEffectsInputProcess(api.Loader): viewer["input_process_node"].setValue(group_node_name) # put backdrop under - lib.create_backdrop(label="Input Process", layer=2, nodes=[viewer, group_node], color="0x7c7faaff") + lib.create_backdrop( + label="Input Process", + layer=2, + nodes=[viewer, group_node], + color="0x7c7faaff") return True diff --git a/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py b/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py index 214f1ecf18..b38e18d089 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py +++ b/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py @@ -1,3 +1,4 @@ +import sys import json import collections import ftrack_api @@ -90,27 +91,28 @@ class PushHierValuesToNonHier(ServerAction): try: result = self.propagate_values(session, event, entities) - job["status"] = "done" - session.commit() - - return result - - except Exception: - session.rollback() - job["status"] = "failed" - session.commit() + except Exception as exc: msg = "Pushing Custom attribute values to task Failed" + self.log.warning(msg, exc_info=True) + + session.rollback() + + description = "{} (Download traceback)".format(msg) + self.add_traceback_to_job( + job, session, sys.exc_info(), description + ) + return { "success": False, - "message": msg + "message": "Error: {}".format(str(exc)) } - finally: - if job["status"] == "running": - job["status"] = "failed" - session.commit() + job["status"] = "done" + session.commit() + + return result def attrs_configurations(self, session, object_ids, interest_attributes): attrs = session.query(self.cust_attrs_query.format( diff --git a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py index 5298c06371..5c40ec0d30 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py @@ -1,6 +1,8 @@ import json +from avalon.api import AvalonMongoDB from openpype.api import ProjectSettings +from openpype.lib import create_project from openpype.modules.ftrack.lib import ( BaseAction, @@ -24,6 +26,21 @@ class PrepareProjectLocal(BaseAction): # Key to store info about trigerring create folder structure item_splitter = {"type": "label", "value": "---"} + _keys_order = ( + "fps", + "frameStart", + "frameEnd", + "handleStart", + "handleEnd", + "clipIn", + "clipOut", + "resolutionHeight", + "resolutionWidth", + "pixelAspect", + "applications", + "tools_env", + "library_project", + ) def discover(self, session, entities, event): """Show only on project.""" @@ -48,13 +65,7 @@ class PrepareProjectLocal(BaseAction): project_entity = entities[0] project_name = project_entity["full_name"] - try: - project_settings = ProjectSettings(project_name) - except ValueError: - return { - "message": "Project is not synchronized yet", - "success": False - } + project_settings = ProjectSettings(project_name) project_anatom_settings = project_settings["project_anatomy"] root_items = self.prepare_root_items(project_anatom_settings) @@ -200,7 +211,18 @@ class PrepareProjectLocal(BaseAction): str([key for key in attributes_to_set]) )) - for key, in_data in attributes_to_set.items(): + attribute_keys = set(attributes_to_set.keys()) + keys_order = [] + for key in self._keys_order: + if key in attribute_keys: + keys_order.append(key) + + attribute_keys = attribute_keys - set(keys_order) + for key in sorted(attribute_keys): + keys_order.append(key) + + for key in keys_order: + in_data = attributes_to_set[key] attr = in_data["object"] # initial item definition @@ -338,7 +360,27 @@ class PrepareProjectLocal(BaseAction): self.log.debug("Setting Custom Attribute values") - project_name = entities[0]["full_name"] + project_entity = entities[0] + project_name = project_entity["full_name"] + + # Try to find project document + dbcon = AvalonMongoDB() + dbcon.install() + dbcon.Session["AVALON_PROJECT"] = project_name + project_doc = dbcon.find_one({ + "type": "project" + }) + # Create project if is not available + # - creation is required to be able set project anatomy and attributes + if not project_doc: + project_code = project_entity["name"] + self.log.info("Creating project \"{} [{}]\"".format( + project_name, project_code + )) + create_project(project_name, project_code, dbcon=dbcon) + + dbcon.uninstall() + project_settings = ProjectSettings(project_name) project_anatomy_settings = project_settings["project_anatomy"] project_anatomy_settings["roots"] = root_data diff --git a/openpype/modules/ftrack/lib/ftrack_base_handler.py b/openpype/modules/ftrack/lib/ftrack_base_handler.py index 817841df4a..011ce8db9d 100644 --- a/openpype/modules/ftrack/lib/ftrack_base_handler.py +++ b/openpype/modules/ftrack/lib/ftrack_base_handler.py @@ -1,4 +1,9 @@ +import os +import tempfile +import json import functools +import datetime +import traceback import time from openpype.api import Logger from openpype.settings import get_project_settings @@ -583,3 +588,105 @@ class BaseHandler(object): return "/".join( [ent["name"] for ent in entity["link"]] ) + + @classmethod + def add_traceback_to_job( + cls, job, session, exc_info, + description=None, + component_name=None, + job_status=None + ): + """Add traceback file to a job. + + Args: + job (JobEntity): Entity of job where file should be able to + download (Created or queried with passed session). + session (Session): Ftrack session which was used to query/create + entered job. + exc_info (tuple): Exception info (e.g. from `sys.exc_info()`). + description (str): Change job description to describe what + happened. Job description won't change if not passed. + component_name (str): Name of component and default name of + downloaded file. Class name and current date time are used if + not specified. + job_status (str): Status of job which will be set. By default is + set to 'failed'. + """ + if description: + job_data = { + "description": description + } + job["data"] = json.dumps(job_data) + + if not job_status: + job_status = "failed" + + job["status"] = job_status + + # Create temp file where traceback will be stored + temp_obj = tempfile.NamedTemporaryFile( + mode="w", prefix="openpype_ftrack_", suffix=".txt", delete=False + ) + temp_obj.close() + temp_filepath = temp_obj.name + + # Store traceback to file + result = traceback.format_exception(*exc_info) + with open(temp_filepath, "w") as temp_file: + temp_file.write("".join(result)) + + # Upload file with traceback to ftrack server and add it to job + if not component_name: + component_name = "{}_{}".format( + cls.__name__, + datetime.datetime.now().strftime("%y-%m-%d-%H%M") + ) + cls.add_file_component_to_job( + job, session, temp_filepath, component_name + ) + # Delete temp file + os.remove(temp_filepath) + + @staticmethod + def add_file_component_to_job(job, session, filepath, basename=None): + """Add filepath as downloadable component to job. + + Args: + job (JobEntity): Entity of job where file should be able to + download (Created or queried with passed session). + session (Session): Ftrack session which was used to query/create + entered job. + filepath (str): Path to file which should be added to job. + basename (str): Defines name of file which will be downloaded on + user's side. Must be without extension otherwise extension will + be duplicated in downloaded name. Basename from entered path + used when not entered. + """ + # Make sure session's locations are configured + # - they can be deconfigured e.g. using `rollback` method + session._configure_locations() + + # Query `ftrack.server` location where component will be stored + location = session.query( + "Location where name is \"ftrack.server\"" + ).one() + + # Use filename as basename if not entered (must be without extension) + if basename is None: + basename = os.path.splitext( + os.path.basename(filepath) + )[0] + + component = session.create_component( + filepath, + data={"name": basename}, + location=location + ) + session.create( + "JobComponent", + { + "component_id": component["id"], + "job_id": job["id"] + } + ) + session.commit() diff --git a/tools/build.ps1 b/tools/build.ps1 index cc4253fe24..e1962ee933 100644 --- a/tools/build.ps1 +++ b/tools/build.ps1 @@ -83,8 +83,12 @@ function Show-PSWarning() { function Install-Poetry() { Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Installing Poetry ... " + $python = "python" + if (Get-Command "pyenv" -ErrorAction SilentlyContinue) { + $python = & pyenv which python + } $env:POETRY_HOME="$openpype_root\.poetry" - (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python - + (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | & $($python) - } $art = @" diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index 6c8124ccb2..2ab6abe76e 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -48,15 +48,23 @@ function Show-PSWarning() { function Install-Poetry() { Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Installing Poetry ... " + $python = "python" + if (Get-Command "pyenv" -ErrorAction SilentlyContinue) { + $python = & pyenv which python + } $env:POETRY_HOME="$openpype_root\.poetry" - (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python - + (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | & $($python) - } function Test-Python() { Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "Detecting host Python ... " -NoNewline - if (-not (Get-Command "python" -ErrorAction SilentlyContinue)) { + $python = "python" + if (Get-Command "pyenv" -ErrorAction SilentlyContinue) { + $python = & pyenv which python + } + if (-not (Get-Command "python3" -ErrorAction SilentlyContinue)) { Write-Host "!!! Python not detected" -ForegroundColor red Set-Location -Path $current_dir Exit-WithCode 1 @@ -66,7 +74,7 @@ import sys print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1])) '@ - $p = & python -c $version_command + $p = & $python -c $version_command $env:PYTHON_VERSION = $p $m = $p -match '(\d+)\.(\d+)' if(-not $m) { diff --git a/tools/run_mongo.ps1 b/tools/run_mongo.ps1 index 6719e520fe..32f6cfed17 100644 --- a/tools/run_mongo.ps1 +++ b/tools/run_mongo.ps1 @@ -41,22 +41,40 @@ function Exit-WithCode($exitcode) { } -function Find-Mongo { +function Find-Mongo ($preferred_version) { $defaultPath = "C:\Program Files\MongoDB\Server" Write-Host ">>> " -NoNewLine -ForegroundColor Green Write-Host "Detecting MongoDB ... " -NoNewline if (-not (Get-Command "mongod" -ErrorAction SilentlyContinue)) { if(Test-Path "$($defaultPath)\*\bin\mongod.exe" -PathType Leaf) { # we have mongo server installed on standard Windows location - # so we can inject it to the PATH. We'll use latest version available. + # so we can inject it to the PATH. We'll use latest version available, or the one defined by + # $preferred_version. $mongoVersions = Get-ChildItem -Directory 'C:\Program Files\MongoDB\Server' | Sort-Object -Property {$_.Name -as [int]} if(Test-Path "$($mongoVersions[-1])\bin\mongod.exe" -PathType Leaf) { - $env:PATH = "$($env:PATH);$($mongoVersions[-1])\bin\" Write-Host "OK" -ForegroundColor Green + $use_version = $mongoVersions[-1] + foreach ($v in $mongoVersions) { + Write-Host " - found [ " -NoNewline + Write-Host $v -NoNewLine -ForegroundColor Cyan + Write-Host " ]" -NoNewLine + + $version = Split-Path $v -Leaf + + if ($preferred_version -eq $version) { + Write-Host " *" -ForegroundColor Green + $use_version = $v + } else { + Write-Host "" + } + } + + $env:PATH = "$($env:PATH);$($use_version)\bin\" + Write-Host " - auto-added from [ " -NoNewline - Write-Host "$($mongoVersions[-1])\bin\mongod.exe" -NoNewLine -ForegroundColor Cyan + Write-Host "$($use_version)\bin\mongod.exe" -NoNewLine -ForegroundColor Cyan Write-Host " ]" - return "$($mongoVersions[-1])\bin\mongod.exe" + return "$($use_version)\bin\mongod.exe" } else { Write-Host "FAILED " -NoNewLine -ForegroundColor Red Write-Host "MongoDB not detected" -ForegroundColor Yellow @@ -95,7 +113,18 @@ $port = 2707 # path to database $dbpath = (Get-Item $openpype_root).parent.FullName + "\mongo_db_data" -$mongoPath = Find-Mongo -Start-Process -FilePath $mongopath "--dbpath $($dbpath) --port $($port)" -PassThru +$preferred_version = "4.0" +$mongoPath = Find-Mongo $preferred_version +Write-Host ">>> " -NoNewLine -ForegroundColor Green +Write-Host "Using DB path: " -NoNewLine +Write-Host " [ " -NoNewline -ForegroundColor Cyan +Write-Host "$($dbpath)" -NoNewline -ForegroundColor White +Write-Host " ] "-ForegroundColor Cyan +Write-Host ">>> " -NoNewLine -ForegroundColor Green +Write-Host "Port: " -NoNewLine +Write-Host " [ " -NoNewline -ForegroundColor Cyan +Write-Host "$($port)" -NoNewline -ForegroundColor White +Write-Host " ] " -ForegroundColor Cyan +Start-Process -FilePath $mongopath "--dbpath $($dbpath) --port $($port)" -PassThru | Out-Null