From 1fec23b6938d53ee7321f7feed25251f16c73d05 Mon Sep 17 00:00:00 2001 From: NickTiny Date: Wed, 14 Jun 2023 12:09:23 -0400 Subject: [PATCH 01/12] Blender Kitsu: Update Gazu Module --- .../addons/blender_kitsu/gazu/__init__.py | 58 ++- .../addons/blender_kitsu/gazu/__version__.py | 2 +- .../addons/blender_kitsu/gazu/asset.py | 7 +- .../addons/blender_kitsu/gazu/cache.py | 1 - .../addons/blender_kitsu/gazu/client.py | 33 +- .../addons/blender_kitsu/gazu/edit.py | 174 +++++-- .../addons/blender_kitsu/gazu/entity.py | 77 ++- .../addons/blender_kitsu/gazu/events.py | 71 +++ .../addons/blender_kitsu/gazu/exception.py | 8 +- .../addons/blender_kitsu/gazu/files.py | 2 +- .../addons/blender_kitsu/gazu/helpers.py | 4 +- .../addons/blender_kitsu/gazu/person.py | 52 +- .../addons/blender_kitsu/gazu/project.py | 77 ++- .../addons/blender_kitsu/gazu/scene.py | 4 +- .../addons/blender_kitsu/gazu/shot.py | 4 +- .../addons/blender_kitsu/gazu/sync.py | 463 +++++++++++++++++- .../addons/blender_kitsu/gazu/task.py | 126 +++-- .../addons/blender_kitsu/gazu/user.py | 22 +- 18 files changed, 1044 insertions(+), 141 deletions(-) create mode 100644 scripts-blender/addons/blender_kitsu/gazu/events.py diff --git a/scripts-blender/addons/blender_kitsu/gazu/__init__.py b/scripts-blender/addons/blender_kitsu/gazu/__init__.py index ccdcfbaf..e9698548 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/__init__.py +++ b/scripts-blender/addons/blender_kitsu/gazu/__init__.py @@ -2,11 +2,16 @@ from . import client as raw from . import cache from . import helpers +try: + from . import events +except ImportError: + pass + from . import asset from . import casting from . import context -from . import entity from . import edit +from . import entity from . import files from . import project from . import person @@ -16,7 +21,11 @@ from . import task from . import user from . import playlist -from .exception import AuthFailedException, ParameterException +from .exception import ( + AuthFailedException, + ParameterException, + NotAuthenticatedException, +) from .__version__ import __version__ @@ -28,13 +37,30 @@ def set_host(url, client=raw.default_client): raw.set_host(url, client=client) -def log_in(email, password, client=raw.default_client): +def log_in( + email, + password, + totp=None, + email_otp=None, + fido_authentication_response=None, + recovery_code=None, + client=raw.default_client, +): tokens = {} try: tokens = raw.post( - "auth/login", {"email": email, "password": password}, client=client + "auth/login", + { + "email": email, + "password": password, + "totp": totp, + "email_otp": email_otp, + "fido_authentication_response": fido_authentication_response, + "recovery_code": recovery_code, + }, + client=client, ) - except ParameterException: + except (NotAuthenticatedException, ParameterException): pass if not tokens or ( @@ -46,6 +72,10 @@ def log_in(email, password, client=raw.default_client): return tokens +def send_email_otp(email, client=raw.default_client): + return raw.get("auth/email-otp", params={"email": email}, client=client) + + def log_out(client=raw.default_client): tokens = {} try: @@ -56,6 +86,24 @@ def log_out(client=raw.default_client): return tokens +def refresh_token(client=raw.default_client): + headers = {"User-Agent": "CGWire Gazu %s" % __version__} + if "refresh_token" in client.tokens: + headers["Authorization"] = "Bearer %s" % client.tokens["refresh_token"] + + response = client.session.get( + raw.get_full_url("auth/refresh-token", client=client), + headers=headers, + ) + raw.check_status(response, "auth/refresh-token") + + tokens = response.json() + + client.tokens["access_token"] = tokens["access_token"] + + return tokens + + def get_event_host(client=raw.default_client): return raw.get_event_host(client=client) diff --git a/scripts-blender/addons/blender_kitsu/gazu/__version__.py b/scripts-blender/addons/blender_kitsu/gazu/__version__.py index 21f38f03..c5981731 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/__version__.py +++ b/scripts-blender/addons/blender_kitsu/gazu/__version__.py @@ -1 +1 @@ -__version__ = "0.8.30" +__version__ = "0.9.3" diff --git a/scripts-blender/addons/blender_kitsu/gazu/asset.py b/scripts-blender/addons/blender_kitsu/gazu/asset.py index ba0fea05..700a53a3 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/asset.py +++ b/scripts-blender/addons/blender_kitsu/gazu/asset.py @@ -124,7 +124,7 @@ def get_asset_by_name(project, name, asset_type=None, client=default): def get_asset(asset_id, client=default): """ Args: - asset_id (str): Id of claimed asset. + asset_id (str): ID of claimed asset. Returns: dict: Asset matching given ID. @@ -270,6 +270,7 @@ def all_asset_types_for_project(project, client=default): Returns: list: Asset types from assets listed in given project. """ + project = normalize_model_parameter(project) path = "projects/%s/asset-types" % project["id"] return sort_by_name(raw.fetch_all(path, client=client)) @@ -291,7 +292,7 @@ def all_asset_types_for_shot(shot, client=default): def get_asset_type(asset_type_id, client=default): """ Args: - asset_type_id (str/): Id of claimed asset type. + asset_type_id (str/): ID of claimed asset type. Returns: dict: Asset Type matching given ID. @@ -358,7 +359,7 @@ def remove_asset_type(asset_type, client=default): def get_asset_instance(asset_instance_id, client=default): """ Args: - asset_instance_id (str): Id of claimed asset instance. + asset_instance_id (str): ID of claimed asset instance. Returns: dict: Asset Instance matching given ID. diff --git a/scripts-blender/addons/blender_kitsu/gazu/cache.py b/scripts-blender/addons/blender_kitsu/gazu/cache.py index 6325f4de..2a227335 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/cache.py +++ b/scripts-blender/addons/blender_kitsu/gazu/cache.py @@ -181,7 +181,6 @@ def cache(function, maxsize=300, expire=120): @wraps(function) def wrapper(*args, **kwargs): - if is_cache_enabled(state): key = get_cache_key(args, kwargs) diff --git a/scripts-blender/addons/blender_kitsu/gazu/client.py b/scripts-blender/addons/blender_kitsu/gazu/client.py index 399acdab..7597922c 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/client.py +++ b/scripts-blender/addons/blender_kitsu/gazu/client.py @@ -3,14 +3,10 @@ import functools import json import shutil import urllib +import os from .encoder import CustomJSONEncoder -if sys.version_info[0] == 3: - from json import JSONDecodeError -else: - JSONDecodeError = ValueError - from .__version__ import __version__ from .exception import ( @@ -24,6 +20,13 @@ from .exception import ( UploadFailedException, ) +if sys.version_info[0] == 3: + from json import JSONDecodeError +else: + JSONDecodeError = ValueError + +DEBUG = os.getenv("GAZU_DEBUG", "false").lower() == "true" + class KitsuClient(object): def __init__(self, host, ssl_verify=True, cert=None): @@ -47,10 +50,7 @@ try: requests.models.complexjson.dumps = functools.partial( json.dumps, cls=CustomJSONEncoder ) - # Set host to "" otherwise requests.Session() takes a long time during Blender startup - # Whyever that is. - # host = "http://gazu.change.serverhost/api" - host = "" + host = "http://gazu.change.serverhost/api" default_client = create_client(host) except Exception: print("Warning, running in setup mode!") @@ -77,9 +77,9 @@ def host_is_valid(client=default_client): if not host_is_up(client): return False try: - post("auth/login", {"email": "", "password": ""}) + post("auth/login", {"email": ""}) except Exception as exc: - return type(exc) == ParameterException + return isinstance(exc, (NotAuthenticatedException, ParameterException)) def get_host(client=default_client): @@ -196,6 +196,8 @@ def get(path, json_response=True, params=None, client=default_client): Returns: The request result. """ + if DEBUG: + print("GET", get_full_url(path, client)) path = build_path_with_params(path, params) response = client.session.get( get_full_url(path, client=client), @@ -216,6 +218,10 @@ def post(path, data, client=default_client): Returns: The request result. """ + if DEBUG: + print("POST", get_full_url(path, client)) + if not "password" in data: + print("Body:", data) response = client.session.post( get_full_url(path, client), json=data, @@ -237,6 +243,9 @@ def put(path, data, client=default_client): Returns: The request result. """ + if DEBUG: + print("PUT", get_full_url(path, client)) + print("Body:", data) response = client.session.put( get_full_url(path, client), json=data, @@ -253,6 +262,8 @@ def delete(path, params=None, client=default_client): Returns: The request result. """ + if DEBUG: + print("DELETE", get_full_url(path, client)) path = build_path_with_params(path, params) response = client.session.delete( diff --git a/scripts-blender/addons/blender_kitsu/gazu/edit.py b/scripts-blender/addons/blender_kitsu/gazu/edit.py index 9a06ef9a..06158c52 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/edit.py +++ b/scripts-blender/addons/blender_kitsu/gazu/edit.py @@ -1,59 +1,147 @@ -from blender_kitsu import gazu from . import client as raw -from .sorting import sort_by_name from .cache import cache from .helpers import normalize_model_parameter default = raw.default_client -@cache -def get_all_edits(relations=False, client=default): - """ - Retrieve all edit entries. - """ - params = {} - if relations: - params = {"relations": "true"} - path = "edits/all" - edits = raw.fetch_all(path, params, client=client) - return sort_by_name(edits) @cache -def get_edit(edit_id, relations=False, client=default): - """ - Retrieve all edit entries. - """ - edit_entry = normalize_model_parameter(edit_id) - params = {} - if relations: - params = {"relations": "true"} - path = f"edits/{edit_entry['id']}" - edit_entry = raw.fetch_all(path, params, client=client) - return edit_entry - -@cache -def get_all_edits_with_tasks(relations=False, client=default): - """ - Retrieve all edit entries. - """ - params = {} - if relations: - params = {"relations": "true"} - path = "edits/with-tasks" - edits_with_tasks = raw.fetch_all(path, params, client=client) - return sort_by_name(edits_with_tasks) - -@cache -def get_all_previews_for_edit(edit, client=default): +def get_edit(edit_id, client=default): """ Args: + edit_id (str): ID of claimed edit. + + Returns: + dict: Edit corresponding to given edit ID. + """ + return raw.fetch_one("edits", edit_id, client=client) + + +@cache +def get_edit_by_name(project, edit_name, client=default): + """ + Args: + project (str / dict): The project dict or the project ID. + edit_name (str): Name of claimed edit. + + Returns: + dict: Edit corresponding to given name and sequence. + """ + project = normalize_model_parameter(project) + return raw.fetch_first( + "edits/all", + {"project_id": project["id"], "name": edit_name}, + client=client, + ) + + +@cache +def get_edit_url(edit, client=default): + """ + Args: + edit (str / dict): The edit dict or the edit ID. + + Returns: + url (str): Web url associated to the given edit + """ + edit = normalize_model_parameter(edit) + edit = get_edit(edit["id"]) + path = "{host}/productions/{project_id}/" + if edit["episode_id"] is None: + path += "edits/{edit_id}/" + else: + path += "episodes/{episode_id}/edits/{edit_id}/" + return path.format( + host=raw.get_api_url_from_host(client=client), + project_id=edit["project_id"], + edit_id=edit["id"], + episode_id=edit["episode_id"], + ) + + +def new_edit( + project, + name, + description=None, + data={}, + episode=None, + client=default, +): + """ + Create an edit for given project (and episode if given). + Allow to set metadata too. + + Args: + project (str / dict): The project dict or the project ID. + name (str): The name of the edit to create. + description (str): The description of the edit to create. + data (dict): Free field to set metadata of any kind. episode (str / dict): The episode dict or the episode ID. Returns: - list: Shots which are children of given episode. + Created edit. + """ + project = normalize_model_parameter(project) + if episode is not None: + episode = normalize_model_parameter(episode) + + data = {"name": name, "data": data, "parent_id": episode["id"]} + + if description is not None: + data["description"] = description + + edit = get_edit_by_name(project, name, client=client) + if edit is None: + path = "data/projects/%s/edits" % project["id"] + return raw.post(path, data, client=client) + else: + return edit + + +def remove_edit(edit, force=False, client=default): + """ + Remove given edit from database. + + Args: + edit (dict / str): Edit to remove. """ edit = normalize_model_parameter(edit) - edit_previews = (raw.fetch_all(f"edits/{edit['id']}/preview-files", client=client)) - for key in [key for key in enumerate(edit_previews.keys())]: - return edit_previews[key[1]] \ No newline at end of file + path = "data/edits/%s" % edit["id"] + params = {} + if force: + params = {"force": "true"} + return raw.delete(path, params, client=client) + + +def update_edit(edit, client=default): + """ + Save given edit data into the API. Metadata are fully replaced by the ones + set on given edit. + + Args: + edit (dict): The edit dict to update. + + Returns: + dict: Updated edit. + """ + return raw.put("data/entities/%s" % edit["id"], edit, client=client) + + +def update_edit_data(edit, data={}, client=default): + """ + Update the metadata for the provided edit. Keys that are not provided are + not changed. + + Args: + edit (dict / ID): The edit dict or ID to save in database. + data (dict): Free field to set metadata of any kind. + + Returns: + dict: Updated edit. + """ + edit = normalize_model_parameter(edit) + current_edit = get_edit(edit["id"], client=client) + updated_edit = {"id": current_edit["id"], "data": current_edit["data"]} + updated_edit["data"].update(data) + return update_edit(updated_edit, client=client) diff --git a/scripts-blender/addons/blender_kitsu/gazu/entity.py b/scripts-blender/addons/blender_kitsu/gazu/entity.py index 30bdf7be..d7ff6d12 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/entity.py +++ b/scripts-blender/addons/blender_kitsu/gazu/entity.py @@ -29,36 +29,39 @@ def all_entity_types(client=default): def get_entity(entity_id, client=default): """ Args: - id (str, client=default): ID of claimed entity. + entity_id (str): ID of claimed entity. Returns: - dict: Retrieve entity matching given ID (It can be an entity of any + dict: Retrieve entity matching given ID (it can be an entity of any kind: asset, shot, sequence or episode). """ return raw.fetch_one("entities", entity_id, client=client) @cache -def get_entity_by_name(entity_name, client=default): +def get_entity_by_name(entity_name, project=None, client=default): """ Args: - name (str, client=default): The name of the claimed entity. - + name (str): The name of the claimed entity. + project (str, dict): Project ID or dict. Returns: - Retrieve entity matching given name. + Retrieve entity matching given name (and project if given). """ - return raw.fetch_first("entities", {"name": entity_name}, client=client) + params = {"name": entity_name} + if project is not None: + project = normalize_model_parameter(project) + params["project_id"] = project["id"] + return raw.fetch_first("entities", params, client=client) @cache def get_entity_type(entity_type_id, client=default): """ - Args: - id (str, client=default): ID of claimed entity type. - , client=client - Returns: - Retrieve entity type matching given ID (It can be an entity type of any - kind). + Args: + entity_type_id (str): ID of claimed entity type. + Returns: + Retrieve entity type matching given ID (It can be an entity type of any + kind). """ return raw.fetch_one("entity-types", entity_type_id, client=client) @@ -77,6 +80,41 @@ def get_entity_type_by_name(entity_type_name, client=default): ) +@cache +def guess_from_path(project_id, path, sep="/"): + """ + Get list of possible project file tree templates matching a file path + and data ids corresponding to template tokens. + + Args: + project_id (str): Project id of given file + file_path (str): Path to a file + sep (str): File path separator, defaults to "/" + Returns: + list: dictionnaries with the corresponding entities and template name. + + Example: + .. code-block:: text + + [ + { + 'Asset': '', + 'Project': '', + 'Template': 'asset' + }, + { + 'Project': '', + 'Template': 'instance' + }, + ... + ] + """ + return raw.post( + "/data/entities/guess_from_path", + {"project_id": project_id, "file_path": path, "sep": sep}, + ) + + def new_entity_type(name, client=default): """ Creates an entity type with the given name. @@ -104,16 +142,3 @@ def remove_entity(entity, force=False, client=default): if force: params = {"force": "true"} return raw.delete(path, params, client=client) - -def update_entity(entity, client=default): - """ - Save given shot data into the API. Metadata are fully replaced by the ones - set on given shot. - - Args: - Entity (dict): The shot dict to update. - - Returns: - dict: Updated entity. - """ - return raw.put(f"data/entities/{entity['id']}", entity, client=client) \ No newline at end of file diff --git a/scripts-blender/addons/blender_kitsu/gazu/events.py b/scripts-blender/addons/blender_kitsu/gazu/events.py new file mode 100644 index 00000000..0813d238 --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/gazu/events.py @@ -0,0 +1,71 @@ +import sys + +if sys.version_info[0] == 2: + raise ImportError( + "The events part of Gazu is not available for Python 2.7" + ) +from .exception import AuthFailedException +from .client import default_client, get_event_host +from gazu.client import make_auth_header +import socketio + + +class EventsNamespace(socketio.ClientNamespace): + def on_connect(self): + pass + + def on_disconnect(self): + pass + + def on_error(self, data): + return connect_error(data) + + +def init( + client=default_client, + ssl_verify=False, + reconnection=True, + logger=False, + **kwargs +): + """ + Init configuration for SocketIO client. + + Returns: + Event client that will be able to set listeners. + """ + params = {"ssl_verify": ssl_verify} + params.update(kwargs) + event_client = socketio.Client( + logger=logger, reconnection=reconnection, **params + ) + event_client.on("connect_error", connect_error) + event_client.register_namespace(EventsNamespace("/events")) + event_client.connect(get_event_host(client), make_auth_header()) + return event_client + + +def connect_error(data): + print("The connection failed!") + return data + + +def add_listener(event_client, event_name, event_handler): + """ + Set a listener that reacts to a given event. + """ + event_client.on(event_name, event_handler, "/events") + return event_client + + +def run_client(event_client): + """ + Run event client (it blocks current thread). It listens to all events + configured. + """ + try: + print("Listening to Kitsu events...") + event_client.wait() + except TypeError: + raise AuthFailedException + return event_client diff --git a/scripts-blender/addons/blender_kitsu/gazu/exception.py b/scripts-blender/addons/blender_kitsu/gazu/exception.py index 10b9074e..4f58cb6b 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/exception.py +++ b/scripts-blender/addons/blender_kitsu/gazu/exception.py @@ -79,7 +79,7 @@ class TooBigFileException(Exception): pass -class TaskStatusNotFound(Exception): +class TaskStatusNotFoundException(Exception): """ Error raised when a task status is not found. """ @@ -91,3 +91,9 @@ class DownloadFileException(Exception): """ Error raised when a file can't be downloaded. """ + + +class TaskMustBeADictException(Exception): + """ + Error raised when a task should be a dict. + """ diff --git a/scripts-blender/addons/blender_kitsu/gazu/files.py b/scripts-blender/addons/blender_kitsu/gazu/files.py index f377e887..ca0e153c 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/files.py +++ b/scripts-blender/addons/blender_kitsu/gazu/files.py @@ -1083,7 +1083,7 @@ def download_preview_file(preview_file, file_path, client=default): file_path (str): Location on hard drive where to save the file. """ return raw.download( - get_preview_file_url(preview_file), + get_preview_file_url(preview_file, client=client), file_path, client=client, ) diff --git a/scripts-blender/addons/blender_kitsu/gazu/helpers.py b/scripts-blender/addons/blender_kitsu/gazu/helpers.py index 5aeb2af1..4d1da73a 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/helpers.py +++ b/scripts-blender/addons/blender_kitsu/gazu/helpers.py @@ -1,5 +1,5 @@ -import os import sys +import os import re import datetime import shutil @@ -7,7 +7,7 @@ import requests import tempfile import mimetypes -from .exception import DownloadFileException +from gazu.exception import DownloadFileException if sys.version_info[0] == 3: import urllib.parse as urlparse diff --git a/scripts-blender/addons/blender_kitsu/gazu/person.py b/scripts-blender/addons/blender_kitsu/gazu/person.py index 1709e254..a09ca144 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/person.py +++ b/scripts-blender/addons/blender_kitsu/gazu/person.py @@ -38,15 +38,61 @@ def all_persons(client=default): @cache -def get_person(id, client=default): +def get_time_spents_range(person_id, start_date, end_date, client=default): + """ + Gets the time spents of the current user for the given date range. + + Args: + person_id (str): An uuid identifying a person. + start_date (str): The first day of the date range as a date string with + the following format: YYYY-MM-DD + end_date (str): The last day of the date range as a date string with + the following format: YYYY-MM-DD + Returns: + list: All of the person's time spents + """ + date_range = { + "start_date": start_date, + "end_date": end_date, + } + return raw.get( + "/data/persons/{}/time-spents".format(person_id), + params=date_range, + client=client, + ) + + +def get_all_month_time_spents(id, date, client=default): """ Args: id (str): An uuid identifying a person. + date (datetime.date): The date of the month to query. + + Returns: + list: All of the person's time spents for the given month. + """ + date = date.strftime("%Y/%m") + return raw.get( + "data/persons/{}/time-spents/month/all/{}".format(id, date), + client=client, + ) + + +@cache +def get_person(id, relations=False, client=default): + """ + Args: + id (str): An uuid identifying a person. + relations (bool): Whether to get the relations for the given person. Returns: dict: Person corresponding to given id. """ - return raw.fetch_one("persons", id, client=client) + params = {"id": id} + if relations: + params["relations"] = "true" + + return raw.fetch_first("persons", params=params, client=client) @cache @@ -152,7 +198,7 @@ def new_person( Returns: dict: Created person. """ - person = get_person_by_email(email) + person = get_person_by_email(email, client=client) if person is None: person = raw.post( "data/persons/new", diff --git a/scripts-blender/addons/blender_kitsu/gazu/project.py b/scripts-blender/addons/blender_kitsu/gazu/project.py index a90603cf..fd30094a 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/project.py +++ b/scripts-blender/addons/blender_kitsu/gazu/project.py @@ -256,6 +256,7 @@ def add_metadata_descriptor( project, name, entity_type, + data_type="string", choices=[], for_client=False, departments=[], @@ -278,6 +279,7 @@ def add_metadata_descriptor( project = normalize_model_parameter(project) data = { "name": name, + "data_type": data_type, "choices": choices, "for_client": for_client, "entity_type": entity_type, @@ -292,7 +294,27 @@ def add_metadata_descriptor( def get_metadata_descriptor(project, metadata_descriptor_id, client=default): """ - Get a metadata descriptor matchind it's ID. + Retrieve a the metadata descriptor matching given ID. + + Args: + project (dict / ID): The project dict or id. + metadata_descriptor_id (dict / ID): The metadata descriptor dict or id. + + Returns: + dict: The metadata descriptor matching the ID. + """ + project = normalize_model_parameter(project) + metadata_descriptor = normalize_model_parameter(metadata_descriptor_id) + return raw.fetch_one( + "projects/%s/metadata-descriptors" % project["id"], + metadata_descriptor["id"], + client=client, + ) + + +def get_metadata_descriptor_by_field_name(project, field_name, client=default): + """ + Get a metadata descriptor matchind given project and name. Args: project (dict / ID): The project dict or id. @@ -302,10 +324,12 @@ def get_metadata_descriptor(project, metadata_descriptor_id, client=default): dict: The metadata descriptor matchind the ID. """ project = normalize_model_parameter(project) - metadata_descriptor = normalize_model_parameter(metadata_descriptor_id) - return raw.fetch_one( - "projects/%s/metadata-descriptors" % project["id"], - metadata_descriptor["id"], + return raw.fetch_first( + "metadata-descriptors", + params={ + "project_id": project["id"], + "field_name": field_name, + }, client=client, ) @@ -375,3 +399,46 @@ def remove_metadata_descriptor( params, client=client, ) + + +def get_team(project, client=default): + """ + Get team for project. + + Args: + project (dict / ID): The project dict or id. + """ + project = normalize_model_parameter(project) + return raw.fetch_all("projects/%s/team" % project["id"], client=client) + + +def add_person_to_team(project, person, client=default): + """ + Add a person to the team project. + + Args: + project (dict / ID): The project dict or id. + person (dict / ID): The person dict or id. + """ + project = normalize_model_parameter(project) + person = normalize_model_parameter(person) + data = {"person_id": person["id"]} + return raw.post( + "data/projects/%s/team" % project["id"], data, client=client + ) + + +def remove_person_from_team(project, person, client=default): + """ + Remove a person from the team project. + + Args: + project (dict / ID): The project dict or id. + person (dict / ID): The person dict or id. + """ + project = normalize_model_parameter(project) + person = normalize_model_parameter(person) + return raw.delete( + "data/projects/%s/team/%s" % (project["id"], person["id"]), + client=client, + ) diff --git a/scripts-blender/addons/blender_kitsu/gazu/scene.py b/scripts-blender/addons/blender_kitsu/gazu/scene.py index dc9c2819..cfada37f 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/scene.py +++ b/scripts-blender/addons/blender_kitsu/gazu/scene.py @@ -14,9 +14,9 @@ def new_scene(project, sequence, name, client=default): """ project = normalize_model_parameter(project) sequence = normalize_model_parameter(sequence) - shot = {"name": name, "sequence_id": sequence["id"]} + scene = {"name": name, "sequence_id": sequence["id"]} return raw.post( - "data/projects/%s/scenes" % project["id"], shot, client=client + "data/projects/%s/scenes" % project["id"], scene, client=client ) diff --git a/scripts-blender/addons/blender_kitsu/gazu/shot.py b/scripts-blender/addons/blender_kitsu/gazu/shot.py index 00f1fa16..2e16a3ad 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/shot.py +++ b/scripts-blender/addons/blender_kitsu/gazu/shot.py @@ -114,7 +114,7 @@ def all_episodes_for_project(project, client=default): def get_episode(episode_id, client=default): """ Args: - episode_id (str): Id of claimed episode. + episode_id (str): ID of claimed episode. Returns: dict: Episode corresponding to given episode ID. @@ -205,7 +205,7 @@ def get_sequence_from_shot(shot, client=default): def get_shot(shot_id, client=default): """ Args: - shot_id (str): Id of claimed shot. + shot_id (str): ID of claimed shot. Returns: dict: Shot corresponding to given shot ID. diff --git a/scripts-blender/addons/blender_kitsu/gazu/sync.py b/scripts-blender/addons/blender_kitsu/gazu/sync.py index d249a700..ad2523dc 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/sync.py +++ b/scripts-blender/addons/blender_kitsu/gazu/sync.py @@ -1,4 +1,15 @@ +import os + +from .helpers import normalize_model_parameter + from . import client as raw +from . import asset as asset_module +from . import casting as casting_module +from . import person as person_module +from . import project as project_module +from . import files as files_module +from . import shot as shot_module +from . import task as task_module from .helpers import normalize_model_parameter, validate_date_format @@ -6,7 +17,12 @@ default = raw.default_client def get_last_events( - page_size=20000, project=None, after=None, before=None, client=default + page_size=20000, + project=None, + after=None, + before=None, + only_files=False, + client=default, ): """ Get last events that occured on the machine. @@ -16,13 +32,13 @@ def get_last_events( project (dict/id): Get only events related to this project. after (dict/id): Get only events occuring after given date. before (dict/id): Get only events occuring before given date. - + only_files (bool): Get only events related to files. Returns: dict: Last events matching criterions. """ path = "/data/events/last" - params = {"page_size": page_size} + params = {"page_size": page_size, "only_files": only_files} if project is not None: project = normalize_model_parameter(project) params["project_id"] = project["id"] @@ -167,6 +183,447 @@ def get_id_map_by_id(source_list, target_list, field="name"): def is_changed(source_model, target_model): + """ + Args: + source_model (dict): Model from the source API. + target_model (dict): Matching model from the target API. + + Returns: + bool: True if the source model is older than the target model (based on + `updated_at` field) + """ source_date = source_model["updated_at"] target_date = target_model["updated_at"] return source_date > target_date + + +def get_sync_department_id_map(source_client, target_client): + """ + Args: + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + dict: A dict matching source departments ids with target department ids + """ + departments_source = person_module.all_departments(client=source_client) + departments_target = person_module.all_departments(client=target_client) + return get_id_map_by_id(departments_source, departments_target) + + +def get_sync_asset_type_id_map(source_client, target_client): + """ + Args: + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + dict: A dict matching source asset type ids with target asset type ids + """ + asset_types_source = asset_module.all_asset_types(client=source_client) + asset_types_target = asset_module.all_asset_types(client=target_client) + return get_id_map_by_id(asset_types_source, asset_types_target) + + +def get_sync_project_id_map(source_client, target_client): + """ + Args: + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + dict: A dict matching source project ids with target project ids + """ + projects_source = project_module.all_projects(client=source_client) + projects_target = project_module.all_projects(client=target_client) + return get_id_map_by_id(projects_source, projects_target) + + +def get_sync_task_type_id_map(source_client, target_client): + """ + Args: + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + dict: A dict matching source task type ids with target task type ids + """ + task_types_source = task_module.all_task_types(client=source_client) + task_types_target = task_module.all_task_types(client=target_client) + return get_id_map_by_id(task_types_source, task_types_target) + + +def get_sync_task_status_id_map(source_client, target_client): + """ + Args: + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + dict: A dict matching source task status ids with target task status + ids + """ + task_statuses_source = task_module.all_task_statuses(client=source_client) + task_statuses_target = task_module.all_task_statuses(client=target_client) + return get_id_map_by_id(task_statuses_source, task_statuses_target) + + +def get_sync_person_id_map(source_client, target_client): + """ + Args: + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + dict: A dict matching source person ids with target person ids + """ + persons_source = person_module.all_persons(client=source_client) + persons_target = person_module.all_persons(client=target_client) + return get_id_map_by_id(persons_source, persons_target, field="email") + + +def push_assets(project_source, project_target, client_source, client_target): + """ + Copy assets from source to target and preserve audit fields (`id`, + `created_at`, and `updated_at`) + Args: + project_source (dict): The project to get assets from + project_target (dict): The project to push assets to + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + list: Pushed assets + """ + asset_types_map = get_sync_asset_type_id_map(client_source, client_target) + task_types_map = get_sync_task_type_id_map(client_source, client_target) + assets = asset_module.all_assets_for_project( + project_source, client=client_source + ) + for asset in assets: + asset["entity_type_id"] = asset_types_map[asset["entity_type_id"]] + if asset["ready_for"] is not None: + asset["ready_for"] = task_types_map[asset["ready_for"]] + asset["project_id"] = project_target["id"] + return import_entities(assets, client=client_target) + + +def push_episodes( + project_source, project_target, client_source, client_target +): + """ + Copy episodes from source to target and preserve audit fields (`id`, + `created_at`, and `updated_at`) + Args: + project_source (dict): The project to get episodes from + project_target (dict): The project to push episodes to + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + list: Pushed episodes + """ + episodes = shot_module.all_episodes_for_project( + project_source, client=client_source + ) + for episode in episodes: + episode["project_id"] = project_target["id"] + return import_entities(episodes, client=client_target) + + +def push_sequences( + project_source, project_target, client_source, client_target +): + """ + Copy sequences from source to target and preserve audit fields (`id`, + `created_at`, and `updated_at`) + Args: + project_source (dict): The project to get sequences from + project_target (dict): The project to push sequences to + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + list: Pushed sequences + """ + sequences = shot_module.all_sequences_for_project( + project_source, client=client_source + ) + for sequence in sequences: + sequence["project_id"] = project_target["id"] + return import_entities(sequences, client=client_target) + + +def push_shots(project_source, project_target, client_source, client_target): + """ + Copy shots from source to target and preserve audit fields (`id`, + `created_at`, and `updated_at`) + Args: + project_source (dict): The project to get shots from + project_target (dict): The project to push shots to + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + list: Pushed shots + """ + shots = shot_module.all_shots_for_project( + project_source, client=client_source + ) + for shot in shots: + shot["project_id"] = project_target["id"] + return import_entities(shots, client=client_target) + + +def push_entity_links( + project_source, project_target, client_source, client_target +): + """ + Copy assets from source to target and preserve audit fields (`id`, + `created_at`, and `updated_at`) + Args: + project_source (dict): The project to get assets from + project_target (dict): The project to push assets to + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + list: Pushed entity links + """ + links = casting_module.all_entity_links_for_project( + project_source, client=client_source + ) + return import_entity_links(links, client=client_target) + + +def push_project_entities( + project_source, project_target, client_source, client_target +): + """ + Copy assets, episodes, sequences, shots and entity links from source to + target and preserve audit fields (`id`, `created_at`, and `updated_at`) + Args: + project_source (dict): The project to get assets from + project_target (dict): The project to push assets to + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + dict: Pushed data + """ + assets = push_assets(project_source, project_target) + episodes = [] + if project_source["production_type"] == "tvshow": + episodes = push_episodes(project_source, project_target) + sequences = push_sequences(project_source, project_target) + shots = push_shots(project_source, project_target) + entity_links = push_entity_links(project_source, project_target) + return { + "assets": assets, + "episodes": episodes, + "sequences": sequences, + "shots": shots, + "entity_links": entity_links, + } + + +def push_tasks( + project_source, + project_target, + default_status, + client_source, + client_target, +): + """ + Copy tasks from source to target and preserve audit fields (`id`, + `created_at`, and `updated_at`) + Attachments and previews are created too. + Args: + project_source (dict): The project to get assets from + project_target (dict): The project to push assets to + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + list: Pushed entity links + """ + + default_status_id = normalize_model_parameter(default_status)["id"] + task_type_map = get_sync_task_type_id_map(client_source, client_target) + task_status_map = get_sync_task_status_id_map(client_source, client_target) + person_map = get_sync_person_id_map(client_source, client_target) + + tasks = task_module.all_tasks_for_project( + project_source, client=client_source + ) + for task in tasks: + task["task_type_id"] = task_type_map[task["task_type_id"]] + task["task_status_id"] = default_status_id + task["assigner_id"] = person_map[task["assigner_id"]] + task["project_id"] = project_target["id"] + + task["assignees"] = [ + person_map[person_id] for person_id in task["assignees"] + ] + return import_tasks(tasks, client=client_target) + + +def push_tasks_comments(project_source, client_source, client_target): + """ + Create a new comment into target api for each comment in source project + but preserve only `created_at` field. + Attachments and previews are created too. + Args: + project_source (dict): The project to get assets from + project_target (dict): The project to push assets to + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + list: Created comments + """ + + task_status_map = get_sync_task_status_id_map(client_source, client_target) + person_map = get_sync_person_id_map(client_source, client_target) + tasks = task_module.all_tasks_for_project( + project_source, client=client_source + ) + for task in tasks: + push_task_comments( + task_status_map, person_map, task, client_source, client_target + ) + return tasks + + +def push_task_comments( + task_status_map, person_map, task, client_source, client_target +): + """ + Create a new comment into target api for each comment in source task + but preserve only `created_at` field. + Attachments and previews are created too. + Args: + project_source (dict): The project to get assets from + project_target (dict): The project to push assets to + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + list: Created comments + """ + comments = task_module.all_comments_for_task(task, client=client_source) + comments.reverse() + comments_target = [] + for comment in comments: + comment_target = push_task_comment( + task_status_map, + person_map, + task, + comment, + client_source, + client_target, + ) + comments_target.append(comment_target) + return comments_target + + +def push_task_comment( + task_status_map, person_map, task, comment, client_source, client_target +): + """ + Create a new comment into target api for each comment in source task + but preserve only `created_at` field. + Attachments and previews are created too. + Args: + project_source (dict): The project to get assets from + project_target (dict): The project to push assets to + source_client (KitsuClient): client to get data from source API + target_client (KitsuClient): client to push data to target API + + Returns: + list: Created comments + """ + attachments = [] + for attachment_id in comment["attachment_files"]: + if type(attachment_id) == dict: + attachment_id = attachment_id["id"] + attachment_file = gazu.files.get_attachment_file( + attachment_id, client=client_source + ) + file_path = "/tmp/zou/sync/" + attachment_file["name"] + files_module.download_attachment_file( + attachment_file, file_path, client=client_source + ) + attachments.append(file_path) + + previews = [] + for preview_file in comment["previews"]: + if type(preview_file) is str: + preview_file_id = preview_file + else: + preview_file_id = preview_file["id"] + preview_file = files_module.get_preview_file( + preview_file_id, client=client_source + ) + if ( + preview_file["original_name"] is not None + and preview_file["extension"] is not None + ): + file_path = ( + "/tmp/zou/sync/" + + preview_file["original_name"] + + "." + + preview_file["extension"] + ) + files_module.download_preview_file( + preview_file, file_path, client=client_source + ) + previews.append( + { + "file_path": file_path, + "annotations": preview_file["annotations"], + } + ) + + task_status = {"id": task_status_map[comment["task_status_id"]]} + author_id = person_map[comment["person_id"]] + person = {"id": author_id} + + comment_target = task_module.add_comment( + task, + task_status, + attachments=attachments, + comment=comment["text"], + created_at=comment["created_at"], + person=person, + checklist=comment["checklist"] or [], + client=client_target, + ) + + for preview in previews: + new_preview_file = task_module.add_preview( + task, comment_target, preview["file_path"], client=client_target + ) + files_module.update_preview( + new_preview_file, + {"annotations": preview["annotations"]}, + client=client_target, + ) + os.remove(preview["file_path"]) + + for attachment_path in attachments: + os.remove(attachment_path) + + return comment + + +def convert_id_list(ids, model_map): + """ + Args: + ids (list): Ids to convert. + model_map (dict): Map matching ids to another value.c + + Returns: + list: Ids converted through given model map. + """ + return [model_map[id] for id in ids] diff --git a/scripts-blender/addons/blender_kitsu/gazu/task.py b/scripts-blender/addons/blender_kitsu/gazu/task.py index 1ef4f79f..22e0430f 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/task.py +++ b/scripts-blender/addons/blender_kitsu/gazu/task.py @@ -1,7 +1,10 @@ import string import json -from .exception import TaskStatusNotFound +from gazu.exception import ( + TaskStatusNotFoundException, + TaskMustBeADictException, +) from . import client as raw from .sorting import sort_by_name @@ -147,20 +150,6 @@ def all_tasks_for_episode(episode, relations=False, client=default): return sort_by_name(tasks) -@cache -def all_tasks_for_edit(edit, relations=False, client=default): - """ - Retrieve all tasks directly linked to given edit. - """ - edit = normalize_model_parameter(edit) - params = {} - if relations: - params = {"relations": "true"} - path = "edits/%s/tasks" % edit["id"] - tasks = raw.fetch_all(path, params, client=client) - return sort_by_name(tasks) - - @cache def all_shot_tasks_for_sequence(sequence, relations=False, client=default): """ @@ -394,7 +383,7 @@ def get_task_by_name(entity, task_type, name="main", client=default): def get_task_type(task_type_id, client=default): """ Args: - task_type_id (str): Id of claimed task type. + task_type_id (str): ID of claimed task type. Returns: dict: Task type matching given ID. @@ -437,11 +426,22 @@ def get_task_by_path(project, file_path, entity_type="shot", client=default): return raw.post("data/tasks/from-path/", data, client=client) +@cache +def get_default_task_status(client=default): + """ + Returns: + dict: The unique task status flagged with `is_default`. + """ + return raw.fetch_first( + "task-status", params={"is_default": True}, client=client + ) + + @cache def get_task_status(task_status_id, client=default): """ Args: - task_status_id (str): Id of claimed task status. + task_status_id (str): ID of claimed task status. Returns: dict: Task status matching given ID. @@ -521,7 +521,7 @@ def remove_task_status(task_status, client=default): def get_task(task_id, client=default): """ Args: - task_id (str): Id of claimed task. + task_id (str): ID of claimed task. Returns: dict: Task matching given ID. @@ -603,7 +603,7 @@ def start_task(task, started_task_status=None, client=default): "wip", client=client ) if started_task_status is None: - raise TaskStatusNotFound( + raise TaskStatusNotFoundException( ( "started_task_status is None : 'wip' task status is " "non-existent. You have to create it or to set an other " @@ -647,9 +647,9 @@ def task_to_review( @cache -def get_time_spent(task, date, client=default): +def get_time_spent(task, date=None, client=default): """ - Get the time spent by CG artists on a task at a given date. A field contains + Get the time spent by CG artists on a task at a given date if given. A field contains the total time spent. Durations are given in seconds. Date format is YYYY-MM-DD. @@ -661,7 +661,9 @@ def get_time_spent(task, date, client=default): dict: A dict with person ID as key and time spent object as value. """ task = normalize_model_parameter(task) - path = "actions/tasks/%s/time-spents/%s" % (task["id"], date) + path = "actions/tasks/%s/time-spents" % (task["id"]) + if date is not None: + path += "/%s" % (date) return raw.get(path, client=client) @@ -734,7 +736,9 @@ def add_comment( task_status (str / dict): The task status dict or ID. comment (str): Comment text person (str / dict): Comment author - date (str): Comment date + checklist (list): Comment checklist + attachments (list[file_path]): Attachments file paths + created_at (str): Comment date Returns: dict: Created comment. @@ -878,7 +882,9 @@ def add_preview( task (str / dict): The task dict or the task ID. comment (str / dict): The comment or the comment ID. preview_file_path (str): Path of the file to upload as preview. - + preview_file_path (str): Path of the file to upload as preview. + preview_file_url (str): Url to download the preview file if no path is + given. Returns: dict: Created preview file model. """ @@ -896,19 +902,74 @@ def add_preview( ) -def set_main_preview(preview_file, client=default): +def publish_preview( + task, + task_status, + comment="", + person=None, + checklist=[], + attachments=[], + created_at=None, + client=default, + preview_file_path=None, + preview_file_url=None, + normalize_movie=True, +): + """ + Publish a comment and include given preview for given task and set given + task status. + + Args: + task (str / dict): The task dict or the task ID. + task_status (str / dict): The task status dict or ID. + comment (str): Comment text + person (str / dict): Comment author + checklist (list): Comment checklist + attachments (list[file_path]): Attachments file paths + created_at (str): Comment date + preview_file_path (str): Path of the file to upload as preview. + preview_file_url (str): Url to download the preview file if no path is + given. + normalize_movie (bool): Set to false to not do operations on it on the + server side. + Returns: + dict: Created preview file model. + """ + new_comment = add_comment( + task, + task_status, + comment=comment, + person=person, + checklist=checklist, + attachments=[], + created_at=created_at, + client=client, + ) + add_preview( + task, + new_comment, + preview_file_path=preview_file_path, + preview_file_url=preview_file_url, + normalize_movie=normalize_movie, + ) + return new_comment + + +def set_main_preview(preview_file, frame_number, client=default): """ Set given preview as thumbnail of given entity. Args: preview_file (str / dict): The preview file dict or ID. + frame_number (int): Frame of preview video to set as main preview Returns: dict: Created preview file model. """ + data = {"frame_number": frame_number} if frame_number > 1 else {} preview_file = normalize_model_parameter(preview_file) path = "actions/preview-files/%s/set-main-preview" % preview_file["id"] - return raw.put(path, {}, client=client) + return raw.put(path, data, client=client) @cache @@ -954,17 +1015,19 @@ def assign_task(task, person, client=default): return raw.put(path, {"task_ids": task["id"]}, client=client) -def new_task_type(name, client=default): +def new_task_type(name, color="#000000", client=default): """ Create a new task type with the given name. Args: name (str): The name of the task type + color (str): The color of the task type as an hexadecimal string + with # as first character. ex : #00FF00 Returns: dict: The created task type """ - data = {"name": name} + data = {"name": name, "color": color} return raw.post("data/task-types", data, client=client) @@ -975,7 +1038,7 @@ def new_task_status(name, short_name, color, client=default): Args: name (str): The name of the task status short_name (str): The short name of the task status - color (str): The color of the task status has an hexadecimal string + color (str): The color of the task status as an hexadecimal string with # as first character. ex : #00FF00 Returns: @@ -1032,12 +1095,13 @@ def update_task_data(task, data={}, client=default): def get_task_url(task, client=default): """ Args: - task (str / dict): The task dict or the task ID. + task (dict): The task dict. Returns: url (str): Web url associated to the given task """ - task = normalize_model_parameter(task) + if not isinstance(task, dict): + raise TaskMustBeADictException path = "{host}/productions/{project_id}/shots/tasks/{task_id}/" return path.format( host=raw.get_api_url_from_host(client=client), diff --git a/scripts-blender/addons/blender_kitsu/gazu/user.py b/scripts-blender/addons/blender_kitsu/gazu/user.py index fe99d5d3..50083e4c 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/user.py +++ b/scripts-blender/addons/blender_kitsu/gazu/user.py @@ -1,5 +1,5 @@ import datetime -from .exception import NotAuthenticatedException +from gazu.exception import NotAuthenticatedException from . import client as raw from .sorting import sort_by_name @@ -247,6 +247,26 @@ def all_done_tasks(client=default): return raw.fetch_all("user/done-tasks", client=client) +@cache +def get_timespents_range(start_date, end_date, client=default): + """ + Gets the timespents of the current user for the given date range. + + Args: + start_date (str): The first day of the date range as a date string with + the following format: YYYY-MM-DD + end_date (str): The last day of the date range as a date string with + the following format: YYYY-MM-DD + Returns: + list: All of the person's time spents + """ + date_range = { + "start_date": start_date, + "end_date": end_date, + } + return raw.get("/data/user/time-spents", params=date_range, client=client) + + def log_desktop_session_log_in(client=default): """ Add a log entry to mention that the user logged in his computer. -- 2.30.2 From ce6c529d196aa442892fe78bb887446fa56fac3c Mon Sep 17 00:00:00 2001 From: NickTiny Date: Wed, 14 Jun 2023 12:20:17 -0400 Subject: [PATCH 02/12] Blender Kitsu: Use Current Frame as Playblast thumbnail --- scripts-blender/addons/blender_kitsu/playblast/ops.py | 5 ++++- scripts-blender/addons/blender_kitsu/types.py | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/playblast/ops.py b/scripts-blender/addons/blender_kitsu/playblast/ops.py index 0d7becb4..f94272b5 100644 --- a/scripts-blender/addons/blender_kitsu/playblast/ops.py +++ b/scripts-blender/addons/blender_kitsu/playblast/ops.py @@ -65,6 +65,7 @@ class KITSU_OT_playblast_create(bpy.types.Operator): use_user_shading: bpy.props.BoolProperty( name="Use Current Viewport Shading", default=True) + frame_number: bpy.props.IntProperty(name="Thumbnail Frame") @classmethod def poll(cls, context: bpy.types.Context) -> bool: @@ -191,6 +192,7 @@ class KITSU_OT_playblast_create(bpy.types.Operator): def invoke(self, context, event): # Initialize comment and playblast task status variable. self.comment = "" + self.frame_number = context.scene.frame_current prev_task_status_id = context.scene.kitsu.playblast_task_status_id if prev_task_status_id: @@ -211,6 +213,7 @@ class KITSU_OT_playblast_create(bpy.types.Operator): row.prop(self, "comment") row = layout.row(align=True) row.prop(self, "use_user_shading") + row.prop(self, "frame_number") def _upload_playblast(self, context: bpy.types.Context, filepath: Path) -> None: # Get shot. @@ -241,7 +244,7 @@ class KITSU_OT_playblast_create(bpy.types.Operator): ) # Add_preview_to_comment - task.add_preview_to_comment(comment, filepath.as_posix()) + task.add_preview_to_comment(comment, filepath.as_posix(), frame_number=self.frame_number) # Preview.set_main_preview() logger.info(f"Uploaded playblast for shot: {shot.name} under: {task_type.name}") diff --git a/scripts-blender/addons/blender_kitsu/types.py b/scripts-blender/addons/blender_kitsu/types.py index bcaf8049..49aa0b91 100644 --- a/scripts-blender/addons/blender_kitsu/types.py +++ b/scripts-blender/addons/blender_kitsu/types.py @@ -874,11 +874,12 @@ class Task(Entity): return comment_obj def add_preview_to_comment( - self, comment: Comment, preview_file_path: str - ) -> Preview: + self, comment: Comment, preview_file_path: str, + frame_number:int) -> Preview: preview_dict = gazu.task.add_preview( asdict(self), asdict(comment), preview_file_path ) + gazu.task.set_main_preview(preview_dict["id"], frame_number) return Preview.from_dict(preview_dict) def __bool__(self) -> bool: -- 2.30.2 From abffa63d69300d9096d0ff24a47564065ccdb40c Mon Sep 17 00:00:00 2001 From: NickTiny Date: Wed, 14 Jun 2023 12:57:47 -0400 Subject: [PATCH 03/12] Blender_Kitsu: Fix Import Errors --- scripts-blender/addons/blender_kitsu/gazu/helpers.py | 2 +- scripts-blender/addons/blender_kitsu/gazu/task.py | 2 +- scripts-blender/addons/blender_kitsu/gazu/user.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/gazu/helpers.py b/scripts-blender/addons/blender_kitsu/gazu/helpers.py index 4d1da73a..1545a663 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/helpers.py +++ b/scripts-blender/addons/blender_kitsu/gazu/helpers.py @@ -7,7 +7,7 @@ import requests import tempfile import mimetypes -from gazu.exception import DownloadFileException +from blender_kitsu.gazu.exception import DownloadFileException if sys.version_info[0] == 3: import urllib.parse as urlparse diff --git a/scripts-blender/addons/blender_kitsu/gazu/task.py b/scripts-blender/addons/blender_kitsu/gazu/task.py index 22e0430f..167b2f90 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/task.py +++ b/scripts-blender/addons/blender_kitsu/gazu/task.py @@ -1,7 +1,7 @@ import string import json -from gazu.exception import ( +from blender_kitsu.gazu.exception import ( TaskStatusNotFoundException, TaskMustBeADictException, ) diff --git a/scripts-blender/addons/blender_kitsu/gazu/user.py b/scripts-blender/addons/blender_kitsu/gazu/user.py index 50083e4c..c58f9aec 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/user.py +++ b/scripts-blender/addons/blender_kitsu/gazu/user.py @@ -1,5 +1,5 @@ import datetime -from gazu.exception import NotAuthenticatedException +from blender_kitsu.gazu.exception import NotAuthenticatedException from . import client as raw from .sorting import sort_by_name -- 2.30.2 From bcaf3f9fc42720671bbf9ed0bce308cc51384889 Mon Sep 17 00:00:00 2001 From: NickTiny Date: Wed, 14 Jun 2023 13:03:22 -0400 Subject: [PATCH 04/12] Blender Kitsu: Fix Thumbnail Frame Calculation --- scripts-blender/addons/blender_kitsu/playblast/ops.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_kitsu/playblast/ops.py b/scripts-blender/addons/blender_kitsu/playblast/ops.py index f94272b5..ed88cb17 100644 --- a/scripts-blender/addons/blender_kitsu/playblast/ops.py +++ b/scripts-blender/addons/blender_kitsu/playblast/ops.py @@ -244,7 +244,12 @@ class KITSU_OT_playblast_create(bpy.types.Operator): ) # Add_preview_to_comment - task.add_preview_to_comment(comment, filepath.as_posix(), frame_number=self.frame_number) + # Playblast file always starts at frame 0, account for this in thumbnail frame selection + task.add_preview_to_comment( + comment, + filepath.as_posix(), + frame_number=self.frame_number - context.scene.frame_start, + ) # Preview.set_main_preview() logger.info(f"Uploaded playblast for shot: {shot.name} under: {task_type.name}") -- 2.30.2 From 3be51efa9338853e3adcdf9e26ce00cb3e1d2768 Mon Sep 17 00:00:00 2001 From: NickTiny Date: Wed, 14 Jun 2023 13:03:46 -0400 Subject: [PATCH 05/12] Blender Kitsu: Enforce Black Formatting --- .../addons/blender_kitsu/playblast/ops.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/playblast/ops.py b/scripts-blender/addons/blender_kitsu/playblast/ops.py index ed88cb17..42e842e5 100644 --- a/scripts-blender/addons/blender_kitsu/playblast/ops.py +++ b/scripts-blender/addons/blender_kitsu/playblast/ops.py @@ -38,7 +38,10 @@ from blender_kitsu.types import ( TaskStatus, TaskType, ) -from blender_kitsu.playblast.core import playblast_with_shading_settings, playblast_user_shading_settings +from blender_kitsu.playblast.core import ( + playblast_with_shading_settings, + playblast_user_shading_settings, +) from blender_kitsu.playblast import opsdata logger = LoggerFactory.getLogger() @@ -64,7 +67,8 @@ class KITSU_OT_playblast_create(bpy.types.Operator): task_status: bpy.props.EnumProperty(items=cache.get_all_task_statuses_enum) # type: ignore use_user_shading: bpy.props.BoolProperty( - name="Use Current Viewport Shading", default=True) + name="Use Current Viewport Shading", default=True + ) frame_number: bpy.props.IntProperty(name="Thumbnail Frame") @classmethod @@ -77,7 +81,6 @@ class KITSU_OT_playblast_create(bpy.types.Operator): ) def execute(self, context: bpy.types.Context) -> Set[str]: - addon_prefs = prefs.addon_prefs_get(context) if not self.task_status: @@ -97,12 +100,14 @@ class KITSU_OT_playblast_create(bpy.types.Operator): # Render and save playblast if self.use_user_shading: output_path = playblast_user_shading_settings( - self, context, context.scene.kitsu.playblast_file) + self, context, context.scene.kitsu.playblast_file + ) else: # Get output path. output_path = playblast_with_shading_settings( - self, context, context.scene.kitsu.playblast_file) + self, context, context.scene.kitsu.playblast_file + ) context.window_manager.progress_update(1) @@ -153,7 +158,9 @@ class KITSU_OT_playblast_create(bpy.types.Operator): # Setup video sequence editor space. if "Video Editing" not in [ws.name for ws in bpy.data.workspaces]: scripts_path = bpy.utils.script_paths(use_user=False)[0] - template_path = "/startup/bl_app_templates_system/Video_Editing/startup.blend" + template_path = ( + "/startup/bl_app_templates_system/Video_Editing/startup.blend" + ) ws_filepath = Path(scripts_path + template_path) bpy.ops.workspace.append_activate( idname="Video Editing", @@ -275,7 +282,6 @@ class KITSU_OT_playblast_create(bpy.types.Operator): webbrowser.open(url) - class KITSU_OT_playblast_set_version(bpy.types.Operator): bl_idname = "kitsu.anim_set_playblast_version" bl_label = "Version" @@ -373,7 +379,6 @@ class KITSU_OT_playblast_increment_playblast_version(bpy.types.Operator): return True def execute(self, context: bpy.types.Context) -> Set[str]: - # Incremenet version. version = opsdata.add_playblast_version_increment(context) @@ -473,7 +478,6 @@ def register(): def unregister(): - # Clear handlers. bpy.app.handlers.load_post.remove(load_post_handler_check_frame_range) bpy.app.handlers.load_post.remove(load_post_handler_init_version_model) -- 2.30.2 From 9822252c22f78e0319bf5767ac5edde8eebf8d9e Mon Sep 17 00:00:00 2001 From: NickTiny Date: Wed, 14 Jun 2023 13:18:18 -0400 Subject: [PATCH 06/12] Blender Kitsu: Add feedback if thumbnail frame is invalid --- .../addons/blender_kitsu/playblast/ops.py | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/playblast/ops.py b/scripts-blender/addons/blender_kitsu/playblast/ops.py index 42e842e5..9a764213 100644 --- a/scripts-blender/addons/blender_kitsu/playblast/ops.py +++ b/scripts-blender/addons/blender_kitsu/playblast/ops.py @@ -69,7 +69,12 @@ class KITSU_OT_playblast_create(bpy.types.Operator): use_user_shading: bpy.props.BoolProperty( name="Use Current Viewport Shading", default=True ) - frame_number: bpy.props.IntProperty(name="Thumbnail Frame") + thumbnail_frame: bpy.props.IntProperty( + name="Thumbnail Frame", + description="Frame to use as the thumbnail on Kitsu", + min=0, + ) + thumbnail_frame_final: bpy.props.IntProperty(name="Thumbnail Frame Final") @classmethod def poll(cls, context: bpy.types.Context) -> bool: @@ -87,6 +92,19 @@ class KITSU_OT_playblast_create(bpy.types.Operator): self.report({"ERROR"}, "Failed to create playblast. Missing task status") return {"CANCELLED"} + # Playblast file always starts at frame 0, account for this in thumbnail frame selection + self.thumbnail_frame_final = self.thumbnail_frame - context.scene.frame_start + + # Ensure thumbnail frame is not outside of rendered frame range + if self.thumbnail_frame_final not in range( + 0, context.scene.frame_start - context.scene.frame_end + ): + self.report( + {"ERROR"}, + f"Thumbnail frame '{self.thumbnail_frame}' is outside of rendered frame range ", + ) + return {"CANCELLED"} + shot_active = cache.shot_active_get() # Save playblast task status id for next time. @@ -199,7 +217,7 @@ class KITSU_OT_playblast_create(bpy.types.Operator): def invoke(self, context, event): # Initialize comment and playblast task status variable. self.comment = "" - self.frame_number = context.scene.frame_current + self.thumbnail_frame = context.scene.frame_current prev_task_status_id = context.scene.kitsu.playblast_task_status_id if prev_task_status_id: @@ -220,7 +238,7 @@ class KITSU_OT_playblast_create(bpy.types.Operator): row.prop(self, "comment") row = layout.row(align=True) row.prop(self, "use_user_shading") - row.prop(self, "frame_number") + row.prop(self, "thumbnail_frame") def _upload_playblast(self, context: bpy.types.Context, filepath: Path) -> None: # Get shot. @@ -251,11 +269,10 @@ class KITSU_OT_playblast_create(bpy.types.Operator): ) # Add_preview_to_comment - # Playblast file always starts at frame 0, account for this in thumbnail frame selection task.add_preview_to_comment( comment, filepath.as_posix(), - frame_number=self.frame_number - context.scene.frame_start, + self.thumbnail_frame_final, ) # Preview.set_main_preview() -- 2.30.2 From 3baec3a9b782dc3058b2692602719ac459e09432 Mon Sep 17 00:00:00 2001 From: NickTiny Date: Wed, 14 Jun 2023 13:44:10 -0400 Subject: [PATCH 07/12] Blender Kitsu: Fix check if thumbnail is in range --- scripts-blender/addons/blender_kitsu/playblast/ops.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/playblast/ops.py b/scripts-blender/addons/blender_kitsu/playblast/ops.py index 9a764213..c791ca42 100644 --- a/scripts-blender/addons/blender_kitsu/playblast/ops.py +++ b/scripts-blender/addons/blender_kitsu/playblast/ops.py @@ -95,13 +95,13 @@ class KITSU_OT_playblast_create(bpy.types.Operator): # Playblast file always starts at frame 0, account for this in thumbnail frame selection self.thumbnail_frame_final = self.thumbnail_frame - context.scene.frame_start - # Ensure thumbnail frame is not outside of rendered frame range + # Ensure thumbnail frame is not outside of frame range if self.thumbnail_frame_final not in range( - 0, context.scene.frame_start - context.scene.frame_end + 0, (context.scene.frame_end - context.scene.frame_start) + 1 ): self.report( {"ERROR"}, - f"Thumbnail frame '{self.thumbnail_frame}' is outside of rendered frame range ", + f"Thumbnail frame '{self.thumbnail_frame}' is outside of frame range ", ) return {"CANCELLED"} -- 2.30.2 From bd547efcc92df6312c38f1ed82c640a9557fedf9 Mon Sep 17 00:00:00 2001 From: NickTiny Date: Wed, 14 Jun 2023 13:53:52 -0400 Subject: [PATCH 08/12] Revert "Blender Kitsu: Update Gazu Module" This reverts commit 1fec23b6938d53ee7321f7feed25251f16c73d05. --- .../addons/blender_kitsu/gazu/__init__.py | 58 +-- .../addons/blender_kitsu/gazu/__version__.py | 2 +- .../addons/blender_kitsu/gazu/asset.py | 7 +- .../addons/blender_kitsu/gazu/cache.py | 1 + .../addons/blender_kitsu/gazu/client.py | 33 +- .../addons/blender_kitsu/gazu/edit.py | 158 ++---- .../addons/blender_kitsu/gazu/entity.py | 77 +-- .../addons/blender_kitsu/gazu/events.py | 71 --- .../addons/blender_kitsu/gazu/exception.py | 8 +- .../addons/blender_kitsu/gazu/files.py | 2 +- .../addons/blender_kitsu/gazu/helpers.py | 2 +- .../addons/blender_kitsu/gazu/person.py | 52 +- .../addons/blender_kitsu/gazu/project.py | 77 +-- .../addons/blender_kitsu/gazu/scene.py | 4 +- .../addons/blender_kitsu/gazu/shot.py | 4 +- .../addons/blender_kitsu/gazu/sync.py | 463 +----------------- .../addons/blender_kitsu/gazu/task.py | 160 ++---- .../addons/blender_kitsu/gazu/user.py | 20 - 18 files changed, 140 insertions(+), 1059 deletions(-) delete mode 100644 scripts-blender/addons/blender_kitsu/gazu/events.py diff --git a/scripts-blender/addons/blender_kitsu/gazu/__init__.py b/scripts-blender/addons/blender_kitsu/gazu/__init__.py index e9698548..ccdcfbaf 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/__init__.py +++ b/scripts-blender/addons/blender_kitsu/gazu/__init__.py @@ -2,16 +2,11 @@ from . import client as raw from . import cache from . import helpers -try: - from . import events -except ImportError: - pass - from . import asset from . import casting from . import context -from . import edit from . import entity +from . import edit from . import files from . import project from . import person @@ -21,11 +16,7 @@ from . import task from . import user from . import playlist -from .exception import ( - AuthFailedException, - ParameterException, - NotAuthenticatedException, -) +from .exception import AuthFailedException, ParameterException from .__version__ import __version__ @@ -37,30 +28,13 @@ def set_host(url, client=raw.default_client): raw.set_host(url, client=client) -def log_in( - email, - password, - totp=None, - email_otp=None, - fido_authentication_response=None, - recovery_code=None, - client=raw.default_client, -): +def log_in(email, password, client=raw.default_client): tokens = {} try: tokens = raw.post( - "auth/login", - { - "email": email, - "password": password, - "totp": totp, - "email_otp": email_otp, - "fido_authentication_response": fido_authentication_response, - "recovery_code": recovery_code, - }, - client=client, + "auth/login", {"email": email, "password": password}, client=client ) - except (NotAuthenticatedException, ParameterException): + except ParameterException: pass if not tokens or ( @@ -72,10 +46,6 @@ def log_in( return tokens -def send_email_otp(email, client=raw.default_client): - return raw.get("auth/email-otp", params={"email": email}, client=client) - - def log_out(client=raw.default_client): tokens = {} try: @@ -86,24 +56,6 @@ def log_out(client=raw.default_client): return tokens -def refresh_token(client=raw.default_client): - headers = {"User-Agent": "CGWire Gazu %s" % __version__} - if "refresh_token" in client.tokens: - headers["Authorization"] = "Bearer %s" % client.tokens["refresh_token"] - - response = client.session.get( - raw.get_full_url("auth/refresh-token", client=client), - headers=headers, - ) - raw.check_status(response, "auth/refresh-token") - - tokens = response.json() - - client.tokens["access_token"] = tokens["access_token"] - - return tokens - - def get_event_host(client=raw.default_client): return raw.get_event_host(client=client) diff --git a/scripts-blender/addons/blender_kitsu/gazu/__version__.py b/scripts-blender/addons/blender_kitsu/gazu/__version__.py index c5981731..21f38f03 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/__version__.py +++ b/scripts-blender/addons/blender_kitsu/gazu/__version__.py @@ -1 +1 @@ -__version__ = "0.9.3" +__version__ = "0.8.30" diff --git a/scripts-blender/addons/blender_kitsu/gazu/asset.py b/scripts-blender/addons/blender_kitsu/gazu/asset.py index 700a53a3..ba0fea05 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/asset.py +++ b/scripts-blender/addons/blender_kitsu/gazu/asset.py @@ -124,7 +124,7 @@ def get_asset_by_name(project, name, asset_type=None, client=default): def get_asset(asset_id, client=default): """ Args: - asset_id (str): ID of claimed asset. + asset_id (str): Id of claimed asset. Returns: dict: Asset matching given ID. @@ -270,7 +270,6 @@ def all_asset_types_for_project(project, client=default): Returns: list: Asset types from assets listed in given project. """ - project = normalize_model_parameter(project) path = "projects/%s/asset-types" % project["id"] return sort_by_name(raw.fetch_all(path, client=client)) @@ -292,7 +291,7 @@ def all_asset_types_for_shot(shot, client=default): def get_asset_type(asset_type_id, client=default): """ Args: - asset_type_id (str/): ID of claimed asset type. + asset_type_id (str/): Id of claimed asset type. Returns: dict: Asset Type matching given ID. @@ -359,7 +358,7 @@ def remove_asset_type(asset_type, client=default): def get_asset_instance(asset_instance_id, client=default): """ Args: - asset_instance_id (str): ID of claimed asset instance. + asset_instance_id (str): Id of claimed asset instance. Returns: dict: Asset Instance matching given ID. diff --git a/scripts-blender/addons/blender_kitsu/gazu/cache.py b/scripts-blender/addons/blender_kitsu/gazu/cache.py index 2a227335..6325f4de 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/cache.py +++ b/scripts-blender/addons/blender_kitsu/gazu/cache.py @@ -181,6 +181,7 @@ def cache(function, maxsize=300, expire=120): @wraps(function) def wrapper(*args, **kwargs): + if is_cache_enabled(state): key = get_cache_key(args, kwargs) diff --git a/scripts-blender/addons/blender_kitsu/gazu/client.py b/scripts-blender/addons/blender_kitsu/gazu/client.py index 7597922c..399acdab 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/client.py +++ b/scripts-blender/addons/blender_kitsu/gazu/client.py @@ -3,10 +3,14 @@ import functools import json import shutil import urllib -import os from .encoder import CustomJSONEncoder +if sys.version_info[0] == 3: + from json import JSONDecodeError +else: + JSONDecodeError = ValueError + from .__version__ import __version__ from .exception import ( @@ -20,13 +24,6 @@ from .exception import ( UploadFailedException, ) -if sys.version_info[0] == 3: - from json import JSONDecodeError -else: - JSONDecodeError = ValueError - -DEBUG = os.getenv("GAZU_DEBUG", "false").lower() == "true" - class KitsuClient(object): def __init__(self, host, ssl_verify=True, cert=None): @@ -50,7 +47,10 @@ try: requests.models.complexjson.dumps = functools.partial( json.dumps, cls=CustomJSONEncoder ) - host = "http://gazu.change.serverhost/api" + # Set host to "" otherwise requests.Session() takes a long time during Blender startup + # Whyever that is. + # host = "http://gazu.change.serverhost/api" + host = "" default_client = create_client(host) except Exception: print("Warning, running in setup mode!") @@ -77,9 +77,9 @@ def host_is_valid(client=default_client): if not host_is_up(client): return False try: - post("auth/login", {"email": ""}) + post("auth/login", {"email": "", "password": ""}) except Exception as exc: - return isinstance(exc, (NotAuthenticatedException, ParameterException)) + return type(exc) == ParameterException def get_host(client=default_client): @@ -196,8 +196,6 @@ def get(path, json_response=True, params=None, client=default_client): Returns: The request result. """ - if DEBUG: - print("GET", get_full_url(path, client)) path = build_path_with_params(path, params) response = client.session.get( get_full_url(path, client=client), @@ -218,10 +216,6 @@ def post(path, data, client=default_client): Returns: The request result. """ - if DEBUG: - print("POST", get_full_url(path, client)) - if not "password" in data: - print("Body:", data) response = client.session.post( get_full_url(path, client), json=data, @@ -243,9 +237,6 @@ def put(path, data, client=default_client): Returns: The request result. """ - if DEBUG: - print("PUT", get_full_url(path, client)) - print("Body:", data) response = client.session.put( get_full_url(path, client), json=data, @@ -262,8 +253,6 @@ def delete(path, params=None, client=default_client): Returns: The request result. """ - if DEBUG: - print("DELETE", get_full_url(path, client)) path = build_path_with_params(path, params) response = client.session.delete( diff --git a/scripts-blender/addons/blender_kitsu/gazu/edit.py b/scripts-blender/addons/blender_kitsu/gazu/edit.py index 06158c52..9a06ef9a 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/edit.py +++ b/scripts-blender/addons/blender_kitsu/gazu/edit.py @@ -1,147 +1,59 @@ +from blender_kitsu import gazu from . import client as raw +from .sorting import sort_by_name from .cache import cache from .helpers import normalize_model_parameter default = raw.default_client +@cache +def get_all_edits(relations=False, client=default): + """ + Retrieve all edit entries. + """ + params = {} + if relations: + params = {"relations": "true"} + path = "edits/all" + edits = raw.fetch_all(path, params, client=client) + return sort_by_name(edits) @cache -def get_edit(edit_id, client=default): +def get_edit(edit_id, relations=False, client=default): """ - Args: - edit_id (str): ID of claimed edit. - - Returns: - dict: Edit corresponding to given edit ID. + Retrieve all edit entries. """ - return raw.fetch_one("edits", edit_id, client=client) - + edit_entry = normalize_model_parameter(edit_id) + params = {} + if relations: + params = {"relations": "true"} + path = f"edits/{edit_entry['id']}" + edit_entry = raw.fetch_all(path, params, client=client) + return edit_entry @cache -def get_edit_by_name(project, edit_name, client=default): +def get_all_edits_with_tasks(relations=False, client=default): """ - Args: - project (str / dict): The project dict or the project ID. - edit_name (str): Name of claimed edit. - - Returns: - dict: Edit corresponding to given name and sequence. + Retrieve all edit entries. """ - project = normalize_model_parameter(project) - return raw.fetch_first( - "edits/all", - {"project_id": project["id"], "name": edit_name}, - client=client, - ) - + params = {} + if relations: + params = {"relations": "true"} + path = "edits/with-tasks" + edits_with_tasks = raw.fetch_all(path, params, client=client) + return sort_by_name(edits_with_tasks) @cache -def get_edit_url(edit, client=default): +def get_all_previews_for_edit(edit, client=default): """ Args: - edit (str / dict): The edit dict or the edit ID. - - Returns: - url (str): Web url associated to the given edit - """ - edit = normalize_model_parameter(edit) - edit = get_edit(edit["id"]) - path = "{host}/productions/{project_id}/" - if edit["episode_id"] is None: - path += "edits/{edit_id}/" - else: - path += "episodes/{episode_id}/edits/{edit_id}/" - return path.format( - host=raw.get_api_url_from_host(client=client), - project_id=edit["project_id"], - edit_id=edit["id"], - episode_id=edit["episode_id"], - ) - - -def new_edit( - project, - name, - description=None, - data={}, - episode=None, - client=default, -): - """ - Create an edit for given project (and episode if given). - Allow to set metadata too. - - Args: - project (str / dict): The project dict or the project ID. - name (str): The name of the edit to create. - description (str): The description of the edit to create. - data (dict): Free field to set metadata of any kind. episode (str / dict): The episode dict or the episode ID. Returns: - Created edit. - """ - project = normalize_model_parameter(project) - if episode is not None: - episode = normalize_model_parameter(episode) - - data = {"name": name, "data": data, "parent_id": episode["id"]} - - if description is not None: - data["description"] = description - - edit = get_edit_by_name(project, name, client=client) - if edit is None: - path = "data/projects/%s/edits" % project["id"] - return raw.post(path, data, client=client) - else: - return edit - - -def remove_edit(edit, force=False, client=default): - """ - Remove given edit from database. - - Args: - edit (dict / str): Edit to remove. + list: Shots which are children of given episode. """ edit = normalize_model_parameter(edit) - path = "data/edits/%s" % edit["id"] - params = {} - if force: - params = {"force": "true"} - return raw.delete(path, params, client=client) - - -def update_edit(edit, client=default): - """ - Save given edit data into the API. Metadata are fully replaced by the ones - set on given edit. - - Args: - edit (dict): The edit dict to update. - - Returns: - dict: Updated edit. - """ - return raw.put("data/entities/%s" % edit["id"], edit, client=client) - - -def update_edit_data(edit, data={}, client=default): - """ - Update the metadata for the provided edit. Keys that are not provided are - not changed. - - Args: - edit (dict / ID): The edit dict or ID to save in database. - data (dict): Free field to set metadata of any kind. - - Returns: - dict: Updated edit. - """ - edit = normalize_model_parameter(edit) - current_edit = get_edit(edit["id"], client=client) - updated_edit = {"id": current_edit["id"], "data": current_edit["data"]} - updated_edit["data"].update(data) - return update_edit(updated_edit, client=client) + edit_previews = (raw.fetch_all(f"edits/{edit['id']}/preview-files", client=client)) + for key in [key for key in enumerate(edit_previews.keys())]: + return edit_previews[key[1]] \ No newline at end of file diff --git a/scripts-blender/addons/blender_kitsu/gazu/entity.py b/scripts-blender/addons/blender_kitsu/gazu/entity.py index d7ff6d12..30bdf7be 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/entity.py +++ b/scripts-blender/addons/blender_kitsu/gazu/entity.py @@ -29,39 +29,36 @@ def all_entity_types(client=default): def get_entity(entity_id, client=default): """ Args: - entity_id (str): ID of claimed entity. + id (str, client=default): ID of claimed entity. Returns: - dict: Retrieve entity matching given ID (it can be an entity of any + dict: Retrieve entity matching given ID (It can be an entity of any kind: asset, shot, sequence or episode). """ return raw.fetch_one("entities", entity_id, client=client) @cache -def get_entity_by_name(entity_name, project=None, client=default): +def get_entity_by_name(entity_name, client=default): """ Args: - name (str): The name of the claimed entity. - project (str, dict): Project ID or dict. + name (str, client=default): The name of the claimed entity. + Returns: - Retrieve entity matching given name (and project if given). + Retrieve entity matching given name. """ - params = {"name": entity_name} - if project is not None: - project = normalize_model_parameter(project) - params["project_id"] = project["id"] - return raw.fetch_first("entities", params, client=client) + return raw.fetch_first("entities", {"name": entity_name}, client=client) @cache def get_entity_type(entity_type_id, client=default): """ - Args: - entity_type_id (str): ID of claimed entity type. - Returns: - Retrieve entity type matching given ID (It can be an entity type of any - kind). + Args: + id (str, client=default): ID of claimed entity type. + , client=client + Returns: + Retrieve entity type matching given ID (It can be an entity type of any + kind). """ return raw.fetch_one("entity-types", entity_type_id, client=client) @@ -80,41 +77,6 @@ def get_entity_type_by_name(entity_type_name, client=default): ) -@cache -def guess_from_path(project_id, path, sep="/"): - """ - Get list of possible project file tree templates matching a file path - and data ids corresponding to template tokens. - - Args: - project_id (str): Project id of given file - file_path (str): Path to a file - sep (str): File path separator, defaults to "/" - Returns: - list: dictionnaries with the corresponding entities and template name. - - Example: - .. code-block:: text - - [ - { - 'Asset': '', - 'Project': '', - 'Template': 'asset' - }, - { - 'Project': '', - 'Template': 'instance' - }, - ... - ] - """ - return raw.post( - "/data/entities/guess_from_path", - {"project_id": project_id, "file_path": path, "sep": sep}, - ) - - def new_entity_type(name, client=default): """ Creates an entity type with the given name. @@ -142,3 +104,16 @@ def remove_entity(entity, force=False, client=default): if force: params = {"force": "true"} return raw.delete(path, params, client=client) + +def update_entity(entity, client=default): + """ + Save given shot data into the API. Metadata are fully replaced by the ones + set on given shot. + + Args: + Entity (dict): The shot dict to update. + + Returns: + dict: Updated entity. + """ + return raw.put(f"data/entities/{entity['id']}", entity, client=client) \ No newline at end of file diff --git a/scripts-blender/addons/blender_kitsu/gazu/events.py b/scripts-blender/addons/blender_kitsu/gazu/events.py deleted file mode 100644 index 0813d238..00000000 --- a/scripts-blender/addons/blender_kitsu/gazu/events.py +++ /dev/null @@ -1,71 +0,0 @@ -import sys - -if sys.version_info[0] == 2: - raise ImportError( - "The events part of Gazu is not available for Python 2.7" - ) -from .exception import AuthFailedException -from .client import default_client, get_event_host -from gazu.client import make_auth_header -import socketio - - -class EventsNamespace(socketio.ClientNamespace): - def on_connect(self): - pass - - def on_disconnect(self): - pass - - def on_error(self, data): - return connect_error(data) - - -def init( - client=default_client, - ssl_verify=False, - reconnection=True, - logger=False, - **kwargs -): - """ - Init configuration for SocketIO client. - - Returns: - Event client that will be able to set listeners. - """ - params = {"ssl_verify": ssl_verify} - params.update(kwargs) - event_client = socketio.Client( - logger=logger, reconnection=reconnection, **params - ) - event_client.on("connect_error", connect_error) - event_client.register_namespace(EventsNamespace("/events")) - event_client.connect(get_event_host(client), make_auth_header()) - return event_client - - -def connect_error(data): - print("The connection failed!") - return data - - -def add_listener(event_client, event_name, event_handler): - """ - Set a listener that reacts to a given event. - """ - event_client.on(event_name, event_handler, "/events") - return event_client - - -def run_client(event_client): - """ - Run event client (it blocks current thread). It listens to all events - configured. - """ - try: - print("Listening to Kitsu events...") - event_client.wait() - except TypeError: - raise AuthFailedException - return event_client diff --git a/scripts-blender/addons/blender_kitsu/gazu/exception.py b/scripts-blender/addons/blender_kitsu/gazu/exception.py index 4f58cb6b..10b9074e 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/exception.py +++ b/scripts-blender/addons/blender_kitsu/gazu/exception.py @@ -79,7 +79,7 @@ class TooBigFileException(Exception): pass -class TaskStatusNotFoundException(Exception): +class TaskStatusNotFound(Exception): """ Error raised when a task status is not found. """ @@ -91,9 +91,3 @@ class DownloadFileException(Exception): """ Error raised when a file can't be downloaded. """ - - -class TaskMustBeADictException(Exception): - """ - Error raised when a task should be a dict. - """ diff --git a/scripts-blender/addons/blender_kitsu/gazu/files.py b/scripts-blender/addons/blender_kitsu/gazu/files.py index ca0e153c..f377e887 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/files.py +++ b/scripts-blender/addons/blender_kitsu/gazu/files.py @@ -1083,7 +1083,7 @@ def download_preview_file(preview_file, file_path, client=default): file_path (str): Location on hard drive where to save the file. """ return raw.download( - get_preview_file_url(preview_file, client=client), + get_preview_file_url(preview_file), file_path, client=client, ) diff --git a/scripts-blender/addons/blender_kitsu/gazu/helpers.py b/scripts-blender/addons/blender_kitsu/gazu/helpers.py index 1545a663..515443b4 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/helpers.py +++ b/scripts-blender/addons/blender_kitsu/gazu/helpers.py @@ -1,5 +1,5 @@ -import sys import os +import sys import re import datetime import shutil diff --git a/scripts-blender/addons/blender_kitsu/gazu/person.py b/scripts-blender/addons/blender_kitsu/gazu/person.py index a09ca144..1709e254 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/person.py +++ b/scripts-blender/addons/blender_kitsu/gazu/person.py @@ -38,61 +38,15 @@ def all_persons(client=default): @cache -def get_time_spents_range(person_id, start_date, end_date, client=default): - """ - Gets the time spents of the current user for the given date range. - - Args: - person_id (str): An uuid identifying a person. - start_date (str): The first day of the date range as a date string with - the following format: YYYY-MM-DD - end_date (str): The last day of the date range as a date string with - the following format: YYYY-MM-DD - Returns: - list: All of the person's time spents - """ - date_range = { - "start_date": start_date, - "end_date": end_date, - } - return raw.get( - "/data/persons/{}/time-spents".format(person_id), - params=date_range, - client=client, - ) - - -def get_all_month_time_spents(id, date, client=default): +def get_person(id, client=default): """ Args: id (str): An uuid identifying a person. - date (datetime.date): The date of the month to query. - - Returns: - list: All of the person's time spents for the given month. - """ - date = date.strftime("%Y/%m") - return raw.get( - "data/persons/{}/time-spents/month/all/{}".format(id, date), - client=client, - ) - - -@cache -def get_person(id, relations=False, client=default): - """ - Args: - id (str): An uuid identifying a person. - relations (bool): Whether to get the relations for the given person. Returns: dict: Person corresponding to given id. """ - params = {"id": id} - if relations: - params["relations"] = "true" - - return raw.fetch_first("persons", params=params, client=client) + return raw.fetch_one("persons", id, client=client) @cache @@ -198,7 +152,7 @@ def new_person( Returns: dict: Created person. """ - person = get_person_by_email(email, client=client) + person = get_person_by_email(email) if person is None: person = raw.post( "data/persons/new", diff --git a/scripts-blender/addons/blender_kitsu/gazu/project.py b/scripts-blender/addons/blender_kitsu/gazu/project.py index fd30094a..a90603cf 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/project.py +++ b/scripts-blender/addons/blender_kitsu/gazu/project.py @@ -256,7 +256,6 @@ def add_metadata_descriptor( project, name, entity_type, - data_type="string", choices=[], for_client=False, departments=[], @@ -279,7 +278,6 @@ def add_metadata_descriptor( project = normalize_model_parameter(project) data = { "name": name, - "data_type": data_type, "choices": choices, "for_client": for_client, "entity_type": entity_type, @@ -294,27 +292,7 @@ def add_metadata_descriptor( def get_metadata_descriptor(project, metadata_descriptor_id, client=default): """ - Retrieve a the metadata descriptor matching given ID. - - Args: - project (dict / ID): The project dict or id. - metadata_descriptor_id (dict / ID): The metadata descriptor dict or id. - - Returns: - dict: The metadata descriptor matching the ID. - """ - project = normalize_model_parameter(project) - metadata_descriptor = normalize_model_parameter(metadata_descriptor_id) - return raw.fetch_one( - "projects/%s/metadata-descriptors" % project["id"], - metadata_descriptor["id"], - client=client, - ) - - -def get_metadata_descriptor_by_field_name(project, field_name, client=default): - """ - Get a metadata descriptor matchind given project and name. + Get a metadata descriptor matchind it's ID. Args: project (dict / ID): The project dict or id. @@ -324,12 +302,10 @@ def get_metadata_descriptor_by_field_name(project, field_name, client=default): dict: The metadata descriptor matchind the ID. """ project = normalize_model_parameter(project) - return raw.fetch_first( - "metadata-descriptors", - params={ - "project_id": project["id"], - "field_name": field_name, - }, + metadata_descriptor = normalize_model_parameter(metadata_descriptor_id) + return raw.fetch_one( + "projects/%s/metadata-descriptors" % project["id"], + metadata_descriptor["id"], client=client, ) @@ -399,46 +375,3 @@ def remove_metadata_descriptor( params, client=client, ) - - -def get_team(project, client=default): - """ - Get team for project. - - Args: - project (dict / ID): The project dict or id. - """ - project = normalize_model_parameter(project) - return raw.fetch_all("projects/%s/team" % project["id"], client=client) - - -def add_person_to_team(project, person, client=default): - """ - Add a person to the team project. - - Args: - project (dict / ID): The project dict or id. - person (dict / ID): The person dict or id. - """ - project = normalize_model_parameter(project) - person = normalize_model_parameter(person) - data = {"person_id": person["id"]} - return raw.post( - "data/projects/%s/team" % project["id"], data, client=client - ) - - -def remove_person_from_team(project, person, client=default): - """ - Remove a person from the team project. - - Args: - project (dict / ID): The project dict or id. - person (dict / ID): The person dict or id. - """ - project = normalize_model_parameter(project) - person = normalize_model_parameter(person) - return raw.delete( - "data/projects/%s/team/%s" % (project["id"], person["id"]), - client=client, - ) diff --git a/scripts-blender/addons/blender_kitsu/gazu/scene.py b/scripts-blender/addons/blender_kitsu/gazu/scene.py index cfada37f..dc9c2819 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/scene.py +++ b/scripts-blender/addons/blender_kitsu/gazu/scene.py @@ -14,9 +14,9 @@ def new_scene(project, sequence, name, client=default): """ project = normalize_model_parameter(project) sequence = normalize_model_parameter(sequence) - scene = {"name": name, "sequence_id": sequence["id"]} + shot = {"name": name, "sequence_id": sequence["id"]} return raw.post( - "data/projects/%s/scenes" % project["id"], scene, client=client + "data/projects/%s/scenes" % project["id"], shot, client=client ) diff --git a/scripts-blender/addons/blender_kitsu/gazu/shot.py b/scripts-blender/addons/blender_kitsu/gazu/shot.py index 2e16a3ad..00f1fa16 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/shot.py +++ b/scripts-blender/addons/blender_kitsu/gazu/shot.py @@ -114,7 +114,7 @@ def all_episodes_for_project(project, client=default): def get_episode(episode_id, client=default): """ Args: - episode_id (str): ID of claimed episode. + episode_id (str): Id of claimed episode. Returns: dict: Episode corresponding to given episode ID. @@ -205,7 +205,7 @@ def get_sequence_from_shot(shot, client=default): def get_shot(shot_id, client=default): """ Args: - shot_id (str): ID of claimed shot. + shot_id (str): Id of claimed shot. Returns: dict: Shot corresponding to given shot ID. diff --git a/scripts-blender/addons/blender_kitsu/gazu/sync.py b/scripts-blender/addons/blender_kitsu/gazu/sync.py index ad2523dc..d249a700 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/sync.py +++ b/scripts-blender/addons/blender_kitsu/gazu/sync.py @@ -1,15 +1,4 @@ -import os - -from .helpers import normalize_model_parameter - from . import client as raw -from . import asset as asset_module -from . import casting as casting_module -from . import person as person_module -from . import project as project_module -from . import files as files_module -from . import shot as shot_module -from . import task as task_module from .helpers import normalize_model_parameter, validate_date_format @@ -17,12 +6,7 @@ default = raw.default_client def get_last_events( - page_size=20000, - project=None, - after=None, - before=None, - only_files=False, - client=default, + page_size=20000, project=None, after=None, before=None, client=default ): """ Get last events that occured on the machine. @@ -32,13 +16,13 @@ def get_last_events( project (dict/id): Get only events related to this project. after (dict/id): Get only events occuring after given date. before (dict/id): Get only events occuring before given date. - only_files (bool): Get only events related to files. + Returns: dict: Last events matching criterions. """ path = "/data/events/last" - params = {"page_size": page_size, "only_files": only_files} + params = {"page_size": page_size} if project is not None: project = normalize_model_parameter(project) params["project_id"] = project["id"] @@ -183,447 +167,6 @@ def get_id_map_by_id(source_list, target_list, field="name"): def is_changed(source_model, target_model): - """ - Args: - source_model (dict): Model from the source API. - target_model (dict): Matching model from the target API. - - Returns: - bool: True if the source model is older than the target model (based on - `updated_at` field) - """ source_date = source_model["updated_at"] target_date = target_model["updated_at"] return source_date > target_date - - -def get_sync_department_id_map(source_client, target_client): - """ - Args: - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - dict: A dict matching source departments ids with target department ids - """ - departments_source = person_module.all_departments(client=source_client) - departments_target = person_module.all_departments(client=target_client) - return get_id_map_by_id(departments_source, departments_target) - - -def get_sync_asset_type_id_map(source_client, target_client): - """ - Args: - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - dict: A dict matching source asset type ids with target asset type ids - """ - asset_types_source = asset_module.all_asset_types(client=source_client) - asset_types_target = asset_module.all_asset_types(client=target_client) - return get_id_map_by_id(asset_types_source, asset_types_target) - - -def get_sync_project_id_map(source_client, target_client): - """ - Args: - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - dict: A dict matching source project ids with target project ids - """ - projects_source = project_module.all_projects(client=source_client) - projects_target = project_module.all_projects(client=target_client) - return get_id_map_by_id(projects_source, projects_target) - - -def get_sync_task_type_id_map(source_client, target_client): - """ - Args: - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - dict: A dict matching source task type ids with target task type ids - """ - task_types_source = task_module.all_task_types(client=source_client) - task_types_target = task_module.all_task_types(client=target_client) - return get_id_map_by_id(task_types_source, task_types_target) - - -def get_sync_task_status_id_map(source_client, target_client): - """ - Args: - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - dict: A dict matching source task status ids with target task status - ids - """ - task_statuses_source = task_module.all_task_statuses(client=source_client) - task_statuses_target = task_module.all_task_statuses(client=target_client) - return get_id_map_by_id(task_statuses_source, task_statuses_target) - - -def get_sync_person_id_map(source_client, target_client): - """ - Args: - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - dict: A dict matching source person ids with target person ids - """ - persons_source = person_module.all_persons(client=source_client) - persons_target = person_module.all_persons(client=target_client) - return get_id_map_by_id(persons_source, persons_target, field="email") - - -def push_assets(project_source, project_target, client_source, client_target): - """ - Copy assets from source to target and preserve audit fields (`id`, - `created_at`, and `updated_at`) - Args: - project_source (dict): The project to get assets from - project_target (dict): The project to push assets to - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - list: Pushed assets - """ - asset_types_map = get_sync_asset_type_id_map(client_source, client_target) - task_types_map = get_sync_task_type_id_map(client_source, client_target) - assets = asset_module.all_assets_for_project( - project_source, client=client_source - ) - for asset in assets: - asset["entity_type_id"] = asset_types_map[asset["entity_type_id"]] - if asset["ready_for"] is not None: - asset["ready_for"] = task_types_map[asset["ready_for"]] - asset["project_id"] = project_target["id"] - return import_entities(assets, client=client_target) - - -def push_episodes( - project_source, project_target, client_source, client_target -): - """ - Copy episodes from source to target and preserve audit fields (`id`, - `created_at`, and `updated_at`) - Args: - project_source (dict): The project to get episodes from - project_target (dict): The project to push episodes to - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - list: Pushed episodes - """ - episodes = shot_module.all_episodes_for_project( - project_source, client=client_source - ) - for episode in episodes: - episode["project_id"] = project_target["id"] - return import_entities(episodes, client=client_target) - - -def push_sequences( - project_source, project_target, client_source, client_target -): - """ - Copy sequences from source to target and preserve audit fields (`id`, - `created_at`, and `updated_at`) - Args: - project_source (dict): The project to get sequences from - project_target (dict): The project to push sequences to - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - list: Pushed sequences - """ - sequences = shot_module.all_sequences_for_project( - project_source, client=client_source - ) - for sequence in sequences: - sequence["project_id"] = project_target["id"] - return import_entities(sequences, client=client_target) - - -def push_shots(project_source, project_target, client_source, client_target): - """ - Copy shots from source to target and preserve audit fields (`id`, - `created_at`, and `updated_at`) - Args: - project_source (dict): The project to get shots from - project_target (dict): The project to push shots to - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - list: Pushed shots - """ - shots = shot_module.all_shots_for_project( - project_source, client=client_source - ) - for shot in shots: - shot["project_id"] = project_target["id"] - return import_entities(shots, client=client_target) - - -def push_entity_links( - project_source, project_target, client_source, client_target -): - """ - Copy assets from source to target and preserve audit fields (`id`, - `created_at`, and `updated_at`) - Args: - project_source (dict): The project to get assets from - project_target (dict): The project to push assets to - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - list: Pushed entity links - """ - links = casting_module.all_entity_links_for_project( - project_source, client=client_source - ) - return import_entity_links(links, client=client_target) - - -def push_project_entities( - project_source, project_target, client_source, client_target -): - """ - Copy assets, episodes, sequences, shots and entity links from source to - target and preserve audit fields (`id`, `created_at`, and `updated_at`) - Args: - project_source (dict): The project to get assets from - project_target (dict): The project to push assets to - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - dict: Pushed data - """ - assets = push_assets(project_source, project_target) - episodes = [] - if project_source["production_type"] == "tvshow": - episodes = push_episodes(project_source, project_target) - sequences = push_sequences(project_source, project_target) - shots = push_shots(project_source, project_target) - entity_links = push_entity_links(project_source, project_target) - return { - "assets": assets, - "episodes": episodes, - "sequences": sequences, - "shots": shots, - "entity_links": entity_links, - } - - -def push_tasks( - project_source, - project_target, - default_status, - client_source, - client_target, -): - """ - Copy tasks from source to target and preserve audit fields (`id`, - `created_at`, and `updated_at`) - Attachments and previews are created too. - Args: - project_source (dict): The project to get assets from - project_target (dict): The project to push assets to - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - list: Pushed entity links - """ - - default_status_id = normalize_model_parameter(default_status)["id"] - task_type_map = get_sync_task_type_id_map(client_source, client_target) - task_status_map = get_sync_task_status_id_map(client_source, client_target) - person_map = get_sync_person_id_map(client_source, client_target) - - tasks = task_module.all_tasks_for_project( - project_source, client=client_source - ) - for task in tasks: - task["task_type_id"] = task_type_map[task["task_type_id"]] - task["task_status_id"] = default_status_id - task["assigner_id"] = person_map[task["assigner_id"]] - task["project_id"] = project_target["id"] - - task["assignees"] = [ - person_map[person_id] for person_id in task["assignees"] - ] - return import_tasks(tasks, client=client_target) - - -def push_tasks_comments(project_source, client_source, client_target): - """ - Create a new comment into target api for each comment in source project - but preserve only `created_at` field. - Attachments and previews are created too. - Args: - project_source (dict): The project to get assets from - project_target (dict): The project to push assets to - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - list: Created comments - """ - - task_status_map = get_sync_task_status_id_map(client_source, client_target) - person_map = get_sync_person_id_map(client_source, client_target) - tasks = task_module.all_tasks_for_project( - project_source, client=client_source - ) - for task in tasks: - push_task_comments( - task_status_map, person_map, task, client_source, client_target - ) - return tasks - - -def push_task_comments( - task_status_map, person_map, task, client_source, client_target -): - """ - Create a new comment into target api for each comment in source task - but preserve only `created_at` field. - Attachments and previews are created too. - Args: - project_source (dict): The project to get assets from - project_target (dict): The project to push assets to - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - list: Created comments - """ - comments = task_module.all_comments_for_task(task, client=client_source) - comments.reverse() - comments_target = [] - for comment in comments: - comment_target = push_task_comment( - task_status_map, - person_map, - task, - comment, - client_source, - client_target, - ) - comments_target.append(comment_target) - return comments_target - - -def push_task_comment( - task_status_map, person_map, task, comment, client_source, client_target -): - """ - Create a new comment into target api for each comment in source task - but preserve only `created_at` field. - Attachments and previews are created too. - Args: - project_source (dict): The project to get assets from - project_target (dict): The project to push assets to - source_client (KitsuClient): client to get data from source API - target_client (KitsuClient): client to push data to target API - - Returns: - list: Created comments - """ - attachments = [] - for attachment_id in comment["attachment_files"]: - if type(attachment_id) == dict: - attachment_id = attachment_id["id"] - attachment_file = gazu.files.get_attachment_file( - attachment_id, client=client_source - ) - file_path = "/tmp/zou/sync/" + attachment_file["name"] - files_module.download_attachment_file( - attachment_file, file_path, client=client_source - ) - attachments.append(file_path) - - previews = [] - for preview_file in comment["previews"]: - if type(preview_file) is str: - preview_file_id = preview_file - else: - preview_file_id = preview_file["id"] - preview_file = files_module.get_preview_file( - preview_file_id, client=client_source - ) - if ( - preview_file["original_name"] is not None - and preview_file["extension"] is not None - ): - file_path = ( - "/tmp/zou/sync/" - + preview_file["original_name"] - + "." - + preview_file["extension"] - ) - files_module.download_preview_file( - preview_file, file_path, client=client_source - ) - previews.append( - { - "file_path": file_path, - "annotations": preview_file["annotations"], - } - ) - - task_status = {"id": task_status_map[comment["task_status_id"]]} - author_id = person_map[comment["person_id"]] - person = {"id": author_id} - - comment_target = task_module.add_comment( - task, - task_status, - attachments=attachments, - comment=comment["text"], - created_at=comment["created_at"], - person=person, - checklist=comment["checklist"] or [], - client=client_target, - ) - - for preview in previews: - new_preview_file = task_module.add_preview( - task, comment_target, preview["file_path"], client=client_target - ) - files_module.update_preview( - new_preview_file, - {"annotations": preview["annotations"]}, - client=client_target, - ) - os.remove(preview["file_path"]) - - for attachment_path in attachments: - os.remove(attachment_path) - - return comment - - -def convert_id_list(ids, model_map): - """ - Args: - ids (list): Ids to convert. - model_map (dict): Map matching ids to another value.c - - Returns: - list: Ids converted through given model map. - """ - return [model_map[id] for id in ids] diff --git a/scripts-blender/addons/blender_kitsu/gazu/task.py b/scripts-blender/addons/blender_kitsu/gazu/task.py index 167b2f90..0fad0a39 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/task.py +++ b/scripts-blender/addons/blender_kitsu/gazu/task.py @@ -46,9 +46,7 @@ def all_task_types_for_project(project, client=default): list: Task types stored in database. """ project = normalize_model_parameter(project) - task_types = raw.fetch_all( - "projects/%s/task-types" % project["id"], client=client - ) + task_types = raw.fetch_all("projects/%s/task-types" % project["id"], client=client) return sort_by_name(task_types) @@ -150,6 +148,20 @@ def all_tasks_for_episode(episode, relations=False, client=default): return sort_by_name(tasks) +@cache +def all_tasks_for_edit(edit, relations=False, client=default): + """ + Retrieve all tasks directly linked to given edit. + """ + edit = normalize_model_parameter(edit) + params = {} + if relations: + params = {"relations": "true"} + path = "edits/%s/tasks" % edit["id"] + tasks = raw.fetch_all(path, params, client=client) + return sort_by_name(tasks) + + @cache def all_shot_tasks_for_sequence(sequence, relations=False, client=default): """ @@ -264,9 +276,7 @@ def all_task_types_for_asset(asset, client=default): list: Task types of tasks related to given asset. """ asset = normalize_model_parameter(asset) - task_types = raw.fetch_all( - "assets/%s/task-types" % asset["id"], client=client - ) + task_types = raw.fetch_all("assets/%s/task-types" % asset["id"], client=client) return sort_by_name(task_types) @@ -383,7 +393,7 @@ def get_task_by_name(entity, task_type, name="main", client=default): def get_task_type(task_type_id, client=default): """ Args: - task_type_id (str): ID of claimed task type. + task_type_id (str): Id of claimed task type. Returns: dict: Task type matching given ID. @@ -400,9 +410,7 @@ def get_task_type_by_name(task_type_name, client=default): Returns: dict: Task type object for given name. """ - return raw.fetch_first( - "task-types", {"name": task_type_name}, client=client - ) + return raw.fetch_first("task-types", {"name": task_type_name}, client=client) @cache @@ -426,22 +434,11 @@ def get_task_by_path(project, file_path, entity_type="shot", client=default): return raw.post("data/tasks/from-path/", data, client=client) -@cache -def get_default_task_status(client=default): - """ - Returns: - dict: The unique task status flagged with `is_default`. - """ - return raw.fetch_first( - "task-status", params={"is_default": True}, client=client - ) - - @cache def get_task_status(task_status_id, client=default): """ Args: - task_status_id (str): ID of claimed task status. + task_status_id (str): Id of claimed task status. Returns: dict: Task status matching given ID. @@ -521,7 +518,7 @@ def remove_task_status(task_status, client=default): def get_task(task_id, client=default): """ Args: - task_id (str): ID of claimed task. + task_id (str): Id of claimed task. Returns: dict: Task matching given ID. @@ -599,11 +596,9 @@ def start_task(task, started_task_status=None, client=default): dict: Created comment. """ if started_task_status is None: - started_task_status = get_task_status_by_short_name( - "wip", client=client - ) + started_task_status = get_task_status_by_short_name("wip", client=client) if started_task_status is None: - raise TaskStatusNotFoundException( + raise TaskStatusNotFound( ( "started_task_status is None : 'wip' task status is " "non-existent. You have to create it or to set an other " @@ -647,9 +642,9 @@ def task_to_review( @cache -def get_time_spent(task, date=None, client=default): +def get_time_spent(task, date, client=default): """ - Get the time spent by CG artists on a task at a given date if given. A field contains + Get the time spent by CG artists on a task at a given date. A field contains the total time spent. Durations are given in seconds. Date format is YYYY-MM-DD. @@ -661,9 +656,7 @@ def get_time_spent(task, date=None, client=default): dict: A dict with person ID as key and time spent object as value. """ task = normalize_model_parameter(task) - path = "actions/tasks/%s/time-spents" % (task["id"]) - if date is not None: - path += "/%s" % (date) + path = "actions/tasks/%s/time-spents/%s" % (task["id"], date) return raw.get(path, client=client) @@ -736,9 +729,7 @@ def add_comment( task_status (str / dict): The task status dict or ID. comment (str): Comment text person (str / dict): Comment author - checklist (list): Comment checklist - attachments (list[file_path]): Attachments file paths - created_at (str): Comment date + date (str): Comment date Returns: dict: Created comment. @@ -759,9 +750,7 @@ def add_comment( data["created_at"] = created_at if len(attachments) == 0: - return raw.post( - "actions/tasks/%s/comment" % task["id"], data, client=client - ) + return raw.post("actions/tasks/%s/comment" % task["id"], data, client=client) else: attachment = attachments.pop() data["checklist"] = json.dumps(checklist) @@ -774,9 +763,7 @@ def add_comment( ) -def add_attachment_files_to_comment( - task, comment, attachments=[], client=default -): +def add_attachment_files_to_comment(task, comment, attachments=[], client=default): """ Add attachments files to a given comment. @@ -796,8 +783,7 @@ def add_attachment_files_to_comment( comment = normalize_model_parameter(comment) attachment = attachments.pop() return raw.upload( - "actions/tasks/%s/comments/%s/add-attachment" - % (task["id"], comment["id"]), + "actions/tasks/%s/comments/%s/add-attachment" % (task["id"], comment["id"]), attachment, extra_files=attachments, client=client, @@ -849,9 +835,7 @@ def create_preview(task, comment, client=default): return raw.post(path, {}, client=client) -def upload_preview_file( - preview, file_path, normalize_movie=True, client=default -): +def upload_preview_file(preview, file_path, normalize_movie=True, client=default): """ Create a preview into given comment. @@ -859,9 +843,7 @@ def upload_preview_file( task (str / dict): The task dict or the task ID. file_path (str): Path of the file to upload as preview. """ - path = ( - "pictures/preview-files/%s" % normalize_model_parameter(preview)["id"] - ) + path = "pictures/preview-files/%s" % normalize_model_parameter(preview)["id"] if not normalize_movie: path += "?normalize=false" return raw.upload(path, file_path, client=client) @@ -882,9 +864,7 @@ def add_preview( task (str / dict): The task dict or the task ID. comment (str / dict): The comment or the comment ID. preview_file_path (str): Path of the file to upload as preview. - preview_file_path (str): Path of the file to upload as preview. - preview_file_url (str): Url to download the preview file if no path is - given. + Returns: dict: Created preview file model. """ @@ -902,74 +882,19 @@ def add_preview( ) -def publish_preview( - task, - task_status, - comment="", - person=None, - checklist=[], - attachments=[], - created_at=None, - client=default, - preview_file_path=None, - preview_file_url=None, - normalize_movie=True, -): - """ - Publish a comment and include given preview for given task and set given - task status. - - Args: - task (str / dict): The task dict or the task ID. - task_status (str / dict): The task status dict or ID. - comment (str): Comment text - person (str / dict): Comment author - checklist (list): Comment checklist - attachments (list[file_path]): Attachments file paths - created_at (str): Comment date - preview_file_path (str): Path of the file to upload as preview. - preview_file_url (str): Url to download the preview file if no path is - given. - normalize_movie (bool): Set to false to not do operations on it on the - server side. - Returns: - dict: Created preview file model. - """ - new_comment = add_comment( - task, - task_status, - comment=comment, - person=person, - checklist=checklist, - attachments=[], - created_at=created_at, - client=client, - ) - add_preview( - task, - new_comment, - preview_file_path=preview_file_path, - preview_file_url=preview_file_url, - normalize_movie=normalize_movie, - ) - return new_comment - - -def set_main_preview(preview_file, frame_number, client=default): +def set_main_preview(preview_file, client=default): """ Set given preview as thumbnail of given entity. Args: preview_file (str / dict): The preview file dict or ID. - frame_number (int): Frame of preview video to set as main preview Returns: dict: Created preview file model. """ - data = {"frame_number": frame_number} if frame_number > 1 else {} preview_file = normalize_model_parameter(preview_file) path = "actions/preview-files/%s/set-main-preview" % preview_file["id"] - return raw.put(path, data, client=client) + return raw.put(path, {}, client=client) @cache @@ -1015,19 +940,17 @@ def assign_task(task, person, client=default): return raw.put(path, {"task_ids": task["id"]}, client=client) -def new_task_type(name, color="#000000", client=default): +def new_task_type(name, client=default): """ Create a new task type with the given name. Args: name (str): The name of the task type - color (str): The color of the task type as an hexadecimal string - with # as first character. ex : #00FF00 Returns: dict: The created task type """ - data = {"name": name, "color": color} + data = {"name": name} return raw.post("data/task-types", data, client=client) @@ -1038,7 +961,7 @@ def new_task_status(name, short_name, color, client=default): Args: name (str): The name of the task status short_name (str): The short name of the task status - color (str): The color of the task status as an hexadecimal string + color (str): The color of the task status has an hexadecimal string with # as first character. ex : #00FF00 Returns: @@ -1063,9 +986,7 @@ def update_task(task, client=default): dict: Updated task. """ if "assignees" in task: - task["assignees"] = normalize_list_of_models_for_links( - task["assignees"] - ) + task["assignees"] = normalize_list_of_models_for_links(task["assignees"]) return raw.put("data/tasks/%s" % task["id"], task, client=client) @@ -1095,13 +1016,12 @@ def update_task_data(task, data={}, client=default): def get_task_url(task, client=default): """ Args: - task (dict): The task dict. + task (str / dict): The task dict or the task ID. Returns: url (str): Web url associated to the given task """ - if not isinstance(task, dict): - raise TaskMustBeADictException + task = normalize_model_parameter(task) path = "{host}/productions/{project_id}/shots/tasks/{task_id}/" return path.format( host=raw.get_api_url_from_host(client=client), diff --git a/scripts-blender/addons/blender_kitsu/gazu/user.py b/scripts-blender/addons/blender_kitsu/gazu/user.py index c58f9aec..5f4c147d 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/user.py +++ b/scripts-blender/addons/blender_kitsu/gazu/user.py @@ -247,26 +247,6 @@ def all_done_tasks(client=default): return raw.fetch_all("user/done-tasks", client=client) -@cache -def get_timespents_range(start_date, end_date, client=default): - """ - Gets the timespents of the current user for the given date range. - - Args: - start_date (str): The first day of the date range as a date string with - the following format: YYYY-MM-DD - end_date (str): The last day of the date range as a date string with - the following format: YYYY-MM-DD - Returns: - list: All of the person's time spents - """ - date_range = { - "start_date": start_date, - "end_date": end_date, - } - return raw.get("/data/user/time-spents", params=date_range, client=client) - - def log_desktop_session_log_in(client=default): """ Add a log entry to mention that the user logged in his computer. -- 2.30.2 From 99197cfb972fc0a101d19e369affc6a7090a0001 Mon Sep 17 00:00:00 2001 From: NickTiny Date: Wed, 14 Jun 2023 14:24:08 -0400 Subject: [PATCH 09/12] Fix Revert "Blender Kitsu: Update Gazu Module" --- scripts-blender/addons/blender_kitsu/cache.py | 1 - .../addons/blender_kitsu/gazu/helpers.py | 2 +- .../addons/blender_kitsu/gazu/user.py | 2 +- .../shot_builder/connectors/kitsu.py | 106 ++++++++++++------ 4 files changed, 72 insertions(+), 39 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/cache.py b/scripts-blender/addons/blender_kitsu/cache.py index d628a650..3969890e 100644 --- a/scripts-blender/addons/blender_kitsu/cache.py +++ b/scripts-blender/addons/blender_kitsu/cache.py @@ -411,7 +411,6 @@ def get_user_all_tasks() -> List[Task]: def _init_cache_entity( entity_id: str, entity_type: Any, cache_variable_name: Any, cache_name: str ) -> None: - if entity_id: try: globals()[cache_variable_name] = entity_type.by_id(entity_id) diff --git a/scripts-blender/addons/blender_kitsu/gazu/helpers.py b/scripts-blender/addons/blender_kitsu/gazu/helpers.py index 515443b4..5aeb2af1 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/helpers.py +++ b/scripts-blender/addons/blender_kitsu/gazu/helpers.py @@ -7,7 +7,7 @@ import requests import tempfile import mimetypes -from blender_kitsu.gazu.exception import DownloadFileException +from .exception import DownloadFileException if sys.version_info[0] == 3: import urllib.parse as urlparse diff --git a/scripts-blender/addons/blender_kitsu/gazu/user.py b/scripts-blender/addons/blender_kitsu/gazu/user.py index 5f4c147d..fe99d5d3 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/user.py +++ b/scripts-blender/addons/blender_kitsu/gazu/user.py @@ -1,5 +1,5 @@ import datetime -from blender_kitsu.gazu.exception import NotAuthenticatedException +from .exception import NotAuthenticatedException from . import client as raw from .sorting import sort_by_name diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/connectors/kitsu.py b/scripts-blender/addons/blender_kitsu/shot_builder/connectors/kitsu.py index cb8858d9..45c0e6fc 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/connectors/kitsu.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/connectors/kitsu.py @@ -25,8 +25,8 @@ from blender_kitsu.shot_builder.task_type import TaskType from blender_kitsu.shot_builder.render_settings import RenderSettings from blender_kitsu.shot_builder.connectors.connector import Connector import requests -from blender_kitsu import cache -from blender_kitsu.gazu.asset import all_assets_for_shot +from blender_kitsu import cache +from blender_kitsu.gazu.asset import all_assets_for_shot from blender_kitsu.gazu.shot import all_shots_for_project, all_sequences_for_project import typing @@ -43,16 +43,19 @@ class KitsuPreferences(bpy.types.PropertyGroup): backend: bpy.props.StringProperty( # type: ignore name="Server URL", description="Kitsu server address", - default="https://kitsu.blender.cloud/api") + default="https://kitsu.blender.cloud/api", + ) username: bpy.props.StringProperty( # type: ignore name="Username", - description="Username to connect to Kitsu",) + description="Username to connect to Kitsu", + ) password: bpy.props.StringProperty( # type: ignore name="Password", description="Password to connect to Kitsu", - subtype='PASSWORD',) + subtype='PASSWORD', + ) def draw(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None: layout.label(text="Kitsu") @@ -63,10 +66,11 @@ class KitsuPreferences(bpy.types.PropertyGroup): def _validate(self): if not (self.backend and self.username and self.password): raise KitsuException( - "Kitsu connector has not been configured in the add-on preferences") + "Kitsu connector has not been configured in the add-on preferences" + ) -class KitsuDataContainer(): +class KitsuDataContainer: def __init__(self, data: typing.Dict[str, typing.Optional[str]]): self._data = data @@ -109,7 +113,17 @@ class KitsuSequenceRef(ShotRef): class KitsuShotRef(ShotRef): - def __init__(self, kitsu_id: str, name: str, code: str, frame_start: int, frames: int, frame_end: int, frames_per_second: float, sequence: KitsuSequenceRef): + def __init__( + self, + kitsu_id: str, + name: str, + code: str, + frame_start: int, + frames: int, + frame_end: int, + frames_per_second: float, + sequence: KitsuSequenceRef, + ): super().__init__(name=name, code=code) self.kitsu_id = kitsu_id self.frame_start = frame_start @@ -137,8 +151,7 @@ class KitsuConnector(Connector): def __get_production_data(self) -> KitsuProject: production = cache.project_active_get() - project = KitsuProject(typing.cast( - typing.Dict[str, typing.Any], production)) + project = KitsuProject(typing.cast(typing.Dict[str, typing.Any], production)) return project def get_name(self) -> str: @@ -147,8 +160,9 @@ class KitsuConnector(Connector): def get_task_types(self) -> typing.List[TaskType]: project = cache.project_active_get() - task_types = project.task_types + task_types = project.task_types import pprint + pprint.pprint(task_types) return [] @@ -156,56 +170,76 @@ class KitsuConnector(Connector): project = cache.project_active_get() kitsu_sequences = all_sequences_for_project(project.id) - sequence_lookup = {sequence_data['id']: KitsuSequenceRef( - kitsu_id=sequence_data['id'], - name=sequence_data['name'], - code=sequence_data['code'], - ) for sequence_data in kitsu_sequences} + sequence_lookup = { + sequence_data['id']: KitsuSequenceRef( + kitsu_id=sequence_data['id'], + name=sequence_data['name'], + code=sequence_data['code'], + ) + for sequence_data in kitsu_sequences + } kitsu_shots = all_shots_for_project(project.id) shots: typing.List[ShotRef] = [] for shot_data in kitsu_shots: - - #Initialize default values + # Initialize default values frame_start = vars.DEFAULT_FRAME_START frame_end = 0 # shot_data['data'] can be None if shot_data['data']: # If 3d_in key not found use default start frame. - frame_start = int(shot_data['data'].get('3d_in', vars.DEFAULT_FRAME_START)) + frame_start = int( + shot_data['data'].get('3d_in', vars.DEFAULT_FRAME_START) + ) frame_end = int(shot_data['data'].get('3d_out', 0)) # If 3d_in and 3d_out available use that to calculate frames. # If not try shot_data['nb_frames'] or 0 -> invalid. - frames = int((frame_end - frame_start + 1) if frame_end else shot_data['nb_frames'] or 0) + frames = int( + (frame_end - frame_start + 1) + if frame_end + else shot_data['nb_frames'] or 0 + ) if frames < 0: - logger.error("%s duration is negative: %i. Check frame range information on Kitsu", shot_data['name'], frames) + logger.error( + "%s duration is negative: %i. Check frame range information on Kitsu", + shot_data['name'], + frames, + ) frames = 0 - shots.append(KitsuShotRef( - kitsu_id=shot_data['id'], - name=shot_data['name'], - code=shot_data['code'], - frame_start=frame_start, - frames=frames, - frame_end = frame_end, - frames_per_second=24.0, - sequence=sequence_lookup[shot_data['parent_id']], - )) + shots.append( + KitsuShotRef( + kitsu_id=shot_data['id'], + name=shot_data['name'], + code=shot_data['code'], + frame_start=frame_start, + frames=frames, + frame_end=frame_end, + frames_per_second=24.0, + sequence=sequence_lookup[shot_data['parent_id']], + ) + ) return shots def get_assets_for_shot(self, shot: Shot) -> typing.List[AssetRef]: - kitsu_assets = all_assets_for_shot(shot.kitsu_id) - - return [AssetRef(name=asset_data['name'], code=asset_data['code']) - for asset_data in kitsu_assets] + kitsu_assets = all_assets_for_shot(shot.kitsu_id) + + return [ + AssetRef(name=asset_data['name'], code=asset_data['code']) + for asset_data in kitsu_assets + ] def get_render_settings(self, shot: Shot) -> RenderSettings: """ Retrieve the render settings for the given shot. """ project = cache.project_active_get() - return RenderSettings(width=int(project.resolution.split('x')[0]), height=int(project.resolution.split('x')[1]), frames_per_second=project.fps) + return RenderSettings( + width=int(project.resolution.split('x')[0]), + height=int(project.resolution.split('x')[1]), + frames_per_second=project.fps, + ) -- 2.30.2 From 8feb8e47ccd9601f7bfd5022bd02b25cf56e881d Mon Sep 17 00:00:00 2001 From: NickTiny Date: Wed, 14 Jun 2023 14:24:29 -0400 Subject: [PATCH 10/12] Blender_Kitsu: Add Frame_Number option to set_main_preview() --- scripts-blender/addons/blender_kitsu/gazu/task.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/gazu/task.py b/scripts-blender/addons/blender_kitsu/gazu/task.py index 0fad0a39..38a1f855 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/task.py +++ b/scripts-blender/addons/blender_kitsu/gazu/task.py @@ -1,11 +1,6 @@ import string import json -from blender_kitsu.gazu.exception import ( - TaskStatusNotFoundException, - TaskMustBeADictException, -) - from . import client as raw from .sorting import sort_by_name from .helpers import ( @@ -882,19 +877,21 @@ def add_preview( ) -def set_main_preview(preview_file, client=default): +def set_main_preview(preview_file, frame_number, client=default): """ Set given preview as thumbnail of given entity. Args: preview_file (str / dict): The preview file dict or ID. + frame_number (int): Frame of preview video to set as main preview Returns: dict: Created preview file model. """ + data = {"frame_number": frame_number} if frame_number > 1 else {} preview_file = normalize_model_parameter(preview_file) path = "actions/preview-files/%s/set-main-preview" % preview_file["id"] - return raw.put(path, {}, client=client) + return raw.put(path, data, client=client) @cache -- 2.30.2 From 3f0eb47f328950da197f41fd4c9b78bf6259b974 Mon Sep 17 00:00:00 2001 From: NickTiny Date: Wed, 14 Jun 2023 16:34:06 -0400 Subject: [PATCH 11/12] Blender_Kitsu: Remove Black Formatting from un-changed files --- scripts-blender/addons/blender_kitsu/cache.py | 1 + .../addons/blender_kitsu/gazu/task.py | 49 +++++--- .../shot_builder/connectors/kitsu.py | 106 ++++++------------ 3 files changed, 71 insertions(+), 85 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/cache.py b/scripts-blender/addons/blender_kitsu/cache.py index 3969890e..d628a650 100644 --- a/scripts-blender/addons/blender_kitsu/cache.py +++ b/scripts-blender/addons/blender_kitsu/cache.py @@ -411,6 +411,7 @@ def get_user_all_tasks() -> List[Task]: def _init_cache_entity( entity_id: str, entity_type: Any, cache_variable_name: Any, cache_name: str ) -> None: + if entity_id: try: globals()[cache_variable_name] = entity_type.by_id(entity_id) diff --git a/scripts-blender/addons/blender_kitsu/gazu/task.py b/scripts-blender/addons/blender_kitsu/gazu/task.py index 38a1f855..1ef4f79f 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/task.py +++ b/scripts-blender/addons/blender_kitsu/gazu/task.py @@ -1,6 +1,8 @@ import string import json +from .exception import TaskStatusNotFound + from . import client as raw from .sorting import sort_by_name from .helpers import ( @@ -41,7 +43,9 @@ def all_task_types_for_project(project, client=default): list: Task types stored in database. """ project = normalize_model_parameter(project) - task_types = raw.fetch_all("projects/%s/task-types" % project["id"], client=client) + task_types = raw.fetch_all( + "projects/%s/task-types" % project["id"], client=client + ) return sort_by_name(task_types) @@ -151,7 +155,7 @@ def all_tasks_for_edit(edit, relations=False, client=default): edit = normalize_model_parameter(edit) params = {} if relations: - params = {"relations": "true"} + params = {"relations": "true"} path = "edits/%s/tasks" % edit["id"] tasks = raw.fetch_all(path, params, client=client) return sort_by_name(tasks) @@ -271,7 +275,9 @@ def all_task_types_for_asset(asset, client=default): list: Task types of tasks related to given asset. """ asset = normalize_model_parameter(asset) - task_types = raw.fetch_all("assets/%s/task-types" % asset["id"], client=client) + task_types = raw.fetch_all( + "assets/%s/task-types" % asset["id"], client=client + ) return sort_by_name(task_types) @@ -405,7 +411,9 @@ def get_task_type_by_name(task_type_name, client=default): Returns: dict: Task type object for given name. """ - return raw.fetch_first("task-types", {"name": task_type_name}, client=client) + return raw.fetch_first( + "task-types", {"name": task_type_name}, client=client + ) @cache @@ -591,7 +599,9 @@ def start_task(task, started_task_status=None, client=default): dict: Created comment. """ if started_task_status is None: - started_task_status = get_task_status_by_short_name("wip", client=client) + started_task_status = get_task_status_by_short_name( + "wip", client=client + ) if started_task_status is None: raise TaskStatusNotFound( ( @@ -745,7 +755,9 @@ def add_comment( data["created_at"] = created_at if len(attachments) == 0: - return raw.post("actions/tasks/%s/comment" % task["id"], data, client=client) + return raw.post( + "actions/tasks/%s/comment" % task["id"], data, client=client + ) else: attachment = attachments.pop() data["checklist"] = json.dumps(checklist) @@ -758,7 +770,9 @@ def add_comment( ) -def add_attachment_files_to_comment(task, comment, attachments=[], client=default): +def add_attachment_files_to_comment( + task, comment, attachments=[], client=default +): """ Add attachments files to a given comment. @@ -778,7 +792,8 @@ def add_attachment_files_to_comment(task, comment, attachments=[], client=defaul comment = normalize_model_parameter(comment) attachment = attachments.pop() return raw.upload( - "actions/tasks/%s/comments/%s/add-attachment" % (task["id"], comment["id"]), + "actions/tasks/%s/comments/%s/add-attachment" + % (task["id"], comment["id"]), attachment, extra_files=attachments, client=client, @@ -830,7 +845,9 @@ def create_preview(task, comment, client=default): return raw.post(path, {}, client=client) -def upload_preview_file(preview, file_path, normalize_movie=True, client=default): +def upload_preview_file( + preview, file_path, normalize_movie=True, client=default +): """ Create a preview into given comment. @@ -838,7 +855,9 @@ def upload_preview_file(preview, file_path, normalize_movie=True, client=default task (str / dict): The task dict or the task ID. file_path (str): Path of the file to upload as preview. """ - path = "pictures/preview-files/%s" % normalize_model_parameter(preview)["id"] + path = ( + "pictures/preview-files/%s" % normalize_model_parameter(preview)["id"] + ) if not normalize_movie: path += "?normalize=false" return raw.upload(path, file_path, client=client) @@ -877,21 +896,19 @@ def add_preview( ) -def set_main_preview(preview_file, frame_number, client=default): +def set_main_preview(preview_file, client=default): """ Set given preview as thumbnail of given entity. Args: preview_file (str / dict): The preview file dict or ID. - frame_number (int): Frame of preview video to set as main preview Returns: dict: Created preview file model. """ - data = {"frame_number": frame_number} if frame_number > 1 else {} preview_file = normalize_model_parameter(preview_file) path = "actions/preview-files/%s/set-main-preview" % preview_file["id"] - return raw.put(path, data, client=client) + return raw.put(path, {}, client=client) @cache @@ -983,7 +1000,9 @@ def update_task(task, client=default): dict: Updated task. """ if "assignees" in task: - task["assignees"] = normalize_list_of_models_for_links(task["assignees"]) + task["assignees"] = normalize_list_of_models_for_links( + task["assignees"] + ) return raw.put("data/tasks/%s" % task["id"], task, client=client) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/connectors/kitsu.py b/scripts-blender/addons/blender_kitsu/shot_builder/connectors/kitsu.py index 45c0e6fc..cb8858d9 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/connectors/kitsu.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/connectors/kitsu.py @@ -25,8 +25,8 @@ from blender_kitsu.shot_builder.task_type import TaskType from blender_kitsu.shot_builder.render_settings import RenderSettings from blender_kitsu.shot_builder.connectors.connector import Connector import requests -from blender_kitsu import cache -from blender_kitsu.gazu.asset import all_assets_for_shot +from blender_kitsu import cache +from blender_kitsu.gazu.asset import all_assets_for_shot from blender_kitsu.gazu.shot import all_shots_for_project, all_sequences_for_project import typing @@ -43,19 +43,16 @@ class KitsuPreferences(bpy.types.PropertyGroup): backend: bpy.props.StringProperty( # type: ignore name="Server URL", description="Kitsu server address", - default="https://kitsu.blender.cloud/api", - ) + default="https://kitsu.blender.cloud/api") username: bpy.props.StringProperty( # type: ignore name="Username", - description="Username to connect to Kitsu", - ) + description="Username to connect to Kitsu",) password: bpy.props.StringProperty( # type: ignore name="Password", description="Password to connect to Kitsu", - subtype='PASSWORD', - ) + subtype='PASSWORD',) def draw(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None: layout.label(text="Kitsu") @@ -66,11 +63,10 @@ class KitsuPreferences(bpy.types.PropertyGroup): def _validate(self): if not (self.backend and self.username and self.password): raise KitsuException( - "Kitsu connector has not been configured in the add-on preferences" - ) + "Kitsu connector has not been configured in the add-on preferences") -class KitsuDataContainer: +class KitsuDataContainer(): def __init__(self, data: typing.Dict[str, typing.Optional[str]]): self._data = data @@ -113,17 +109,7 @@ class KitsuSequenceRef(ShotRef): class KitsuShotRef(ShotRef): - def __init__( - self, - kitsu_id: str, - name: str, - code: str, - frame_start: int, - frames: int, - frame_end: int, - frames_per_second: float, - sequence: KitsuSequenceRef, - ): + def __init__(self, kitsu_id: str, name: str, code: str, frame_start: int, frames: int, frame_end: int, frames_per_second: float, sequence: KitsuSequenceRef): super().__init__(name=name, code=code) self.kitsu_id = kitsu_id self.frame_start = frame_start @@ -151,7 +137,8 @@ class KitsuConnector(Connector): def __get_production_data(self) -> KitsuProject: production = cache.project_active_get() - project = KitsuProject(typing.cast(typing.Dict[str, typing.Any], production)) + project = KitsuProject(typing.cast( + typing.Dict[str, typing.Any], production)) return project def get_name(self) -> str: @@ -160,9 +147,8 @@ class KitsuConnector(Connector): def get_task_types(self) -> typing.List[TaskType]: project = cache.project_active_get() - task_types = project.task_types + task_types = project.task_types import pprint - pprint.pprint(task_types) return [] @@ -170,76 +156,56 @@ class KitsuConnector(Connector): project = cache.project_active_get() kitsu_sequences = all_sequences_for_project(project.id) - sequence_lookup = { - sequence_data['id']: KitsuSequenceRef( - kitsu_id=sequence_data['id'], - name=sequence_data['name'], - code=sequence_data['code'], - ) - for sequence_data in kitsu_sequences - } + sequence_lookup = {sequence_data['id']: KitsuSequenceRef( + kitsu_id=sequence_data['id'], + name=sequence_data['name'], + code=sequence_data['code'], + ) for sequence_data in kitsu_sequences} kitsu_shots = all_shots_for_project(project.id) shots: typing.List[ShotRef] = [] for shot_data in kitsu_shots: - # Initialize default values + + #Initialize default values frame_start = vars.DEFAULT_FRAME_START frame_end = 0 # shot_data['data'] can be None if shot_data['data']: # If 3d_in key not found use default start frame. - frame_start = int( - shot_data['data'].get('3d_in', vars.DEFAULT_FRAME_START) - ) + frame_start = int(shot_data['data'].get('3d_in', vars.DEFAULT_FRAME_START)) frame_end = int(shot_data['data'].get('3d_out', 0)) # If 3d_in and 3d_out available use that to calculate frames. # If not try shot_data['nb_frames'] or 0 -> invalid. - frames = int( - (frame_end - frame_start + 1) - if frame_end - else shot_data['nb_frames'] or 0 - ) + frames = int((frame_end - frame_start + 1) if frame_end else shot_data['nb_frames'] or 0) if frames < 0: - logger.error( - "%s duration is negative: %i. Check frame range information on Kitsu", - shot_data['name'], - frames, - ) + logger.error("%s duration is negative: %i. Check frame range information on Kitsu", shot_data['name'], frames) frames = 0 - shots.append( - KitsuShotRef( - kitsu_id=shot_data['id'], - name=shot_data['name'], - code=shot_data['code'], - frame_start=frame_start, - frames=frames, - frame_end=frame_end, - frames_per_second=24.0, - sequence=sequence_lookup[shot_data['parent_id']], - ) - ) + shots.append(KitsuShotRef( + kitsu_id=shot_data['id'], + name=shot_data['name'], + code=shot_data['code'], + frame_start=frame_start, + frames=frames, + frame_end = frame_end, + frames_per_second=24.0, + sequence=sequence_lookup[shot_data['parent_id']], + )) return shots def get_assets_for_shot(self, shot: Shot) -> typing.List[AssetRef]: - kitsu_assets = all_assets_for_shot(shot.kitsu_id) - - return [ - AssetRef(name=asset_data['name'], code=asset_data['code']) - for asset_data in kitsu_assets - ] + kitsu_assets = all_assets_for_shot(shot.kitsu_id) + + return [AssetRef(name=asset_data['name'], code=asset_data['code']) + for asset_data in kitsu_assets] def get_render_settings(self, shot: Shot) -> RenderSettings: """ Retrieve the render settings for the given shot. """ project = cache.project_active_get() - return RenderSettings( - width=int(project.resolution.split('x')[0]), - height=int(project.resolution.split('x')[1]), - frames_per_second=project.fps, - ) + return RenderSettings(width=int(project.resolution.split('x')[0]), height=int(project.resolution.split('x')[1]), frames_per_second=project.fps) -- 2.30.2 From b7c6af9c8b080420d11537b68df0770743ee87c6 Mon Sep 17 00:00:00 2001 From: NickTiny Date: Wed, 14 Jun 2023 16:41:25 -0400 Subject: [PATCH 12/12] Blender_Kitsu: Update set_main_preview() in Gazu Module --- scripts-blender/addons/blender_kitsu/gazu/task.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/gazu/task.py b/scripts-blender/addons/blender_kitsu/gazu/task.py index 1ef4f79f..349233b5 100644 --- a/scripts-blender/addons/blender_kitsu/gazu/task.py +++ b/scripts-blender/addons/blender_kitsu/gazu/task.py @@ -896,19 +896,21 @@ def add_preview( ) -def set_main_preview(preview_file, client=default): +def set_main_preview(preview_file, frame_number, client=default): """ Set given preview as thumbnail of given entity. Args: preview_file (str / dict): The preview file dict or ID. + frame_number (int): Frame of preview video to set as main preview Returns: dict: Created preview file model. """ + data = {"frame_number": frame_number} if frame_number > 1 else {} preview_file = normalize_model_parameter(preview_file) path = "actions/preview-files/%s/set-main-preview" % preview_file["id"] - return raw.put(path, {}, client=client) + return raw.put(path, data, client=client) @cache -- 2.30.2