diff --git a/.run/custom_pre.run.xml b/.run/custom_pre.run.xml
new file mode 100644
index 0000000000..1d5cbd9f04
--- /dev/null
+++ b/.run/custom_pre.run.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.run/prod.run.xml b/.run/prod.run.xml
new file mode 100644
index 0000000000..067135fdac
--- /dev/null
+++ b/.run/prod.run.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.run/prod_pre.run.xml b/.run/prod_pre.run.xml
new file mode 100644
index 0000000000..357d7c2aed
--- /dev/null
+++ b/.run/prod_pre.run.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/quadpype/events/handler.py b/src/quadpype/events/handler.py
index 0302b9ea51..c47475f318 100644
--- a/src/quadpype/events/handler.py
+++ b/src/quadpype/events/handler.py
@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
"""Module storing the class and logic of the QuadPype Events Handler."""
import os
-import time
+import json
import functools
+from time import sleep
from typing import Optional
from datetime import datetime, timezone, timedelta
import pymongo
+import pymongo.errors
import requests
from qtpy import QtCore, QtWidgets
@@ -21,12 +23,11 @@
_EVENT_HANDLER = None
-CHECK_NEW_EVENTS_INTERVAL_SECONDS = 120
-MINIMUM_EVENT_EXPIRE_IN_SECONDS = CHECK_NEW_EVENTS_INTERVAL_SECONDS + 10
+
+DEFAULT_RESPONSES_WAITING_TIME_SECS = 3
class EventHandlerWorker(QtCore.QThread):
- task_completed = QtCore.Signal(int)
def __init__(self, manager, parent=None):
"""Initialize the worker thread."""
@@ -39,6 +40,9 @@ def __init__(self, manager, parent=None):
self._webserver_url = self._manager.webserver.url
+ # Store the amount of time an error occurred
+ self._error_count = 0
+
# Get info about the timestamp of the last handled event
self._last_handled_event_timestamp = self._manager.app_registry.get_item(
"last_handled_event_timestamp", fallback=0
@@ -53,58 +57,106 @@ def __init__(self, manager, parent=None):
self._last_handled_event_timestamp
)
+ def _respond_to_event(self, event_doc, response):
+ curr_user_id = self._manager.user_profile["user_id"]
+ response_data = {
+ "user_id": curr_user_id,
+ "content": ""
+ }
+
+ if response.text:
+ response_content = json.loads(response.text)
+ if "content" in response_content:
+ response_data["content"] = response_content["content"]
+ else:
+ response_data["content"] = response_content
+
+ try:
+ # Only push the response if the user_id hasn't already sent a response
+ self._manager.collection.update_one(
+ {"_id": event_doc["_id"], "responses.user_id": {"$ne": curr_user_id}},
+ {"$push": {"responses": response_data}}
+ )
+ except Exception as e:
+ print(f"Error updating event: {e}")
+
+ def _update_last_handled_event_timestamp(self):
+ # Update the local app registry
+ # To keep track and properly retrieve the next events after that
+ self._manager.app_registry.set_item(
+ "last_handled_event_timestamp",
+ get_timestamp_str(self._last_handled_event_timestamp))
+
+ def _process_event(self, event_doc):
+ current_timestamp = datetime.now()
+
+ if (event_doc["target_users"] and self._manager.user_profile["user_id"] not in event_doc["target_users"]) or \
+ (event_doc["target_groups"] and self._manager.user_profile["role"] not in event_doc["target_groups"]) or \
+ ("expire_at" in event_doc and current_timestamp > event_doc["expire_at"]):
+ # User is not targeted by this event OR
+ # This event is expired, so we skip it
+ self._last_handled_event_timestamp = event_doc["timestamp"]
+ return
+
+ route = event_doc["route"]
+ if not route.startswith("/"):
+ route = "/" + route
+
+ url_with_route = self._webserver_url + route
+
+ # Send the event to the webserver API
+ funct = getattr(requests, event_doc["type"])
+ if not event_doc["content"]:
+ response_obj = funct(url_with_route)
+ else:
+ response_obj = funct(url_with_route, **event_doc["content"])
+
+ if response_obj.status_code == 200 and "responses" in event_doc:
+ # A response is expected
+ self._respond_to_event(event_doc, response_obj)
+
+ # Update the last handled timestamp
+ self._last_handled_event_timestamp = event_doc["timestamp"]
+
def run(self):
- start_time = time.time()
+ # Reset the error count
+ self._error_count = 0
- # Retrieve the new events to process
+ # Retrieve the new events that were added since the last connection
db_cursor = self._manager.collection.find({
"timestamp": {
"$gt": self._last_handled_event_timestamp
}
}).sort("timestamp", 1)
- # Process the events
- current_timestamp = datetime.now()
- for doc in db_cursor:
+ # Process these events
+ for event_doc in db_cursor:
if not self._manager.is_running:
- break
- if "expire_at" in doc and doc["expire_at"] > current_timestamp:
- # This event is expired, skip
- self._last_handled_event_timestamp = doc["timestamp"]
- continue
-
- route = doc["route"]
- if not route.startwith("/"):
- route = "/" + route
-
- url_with_route = self._webserver_url + route
-
- # Send the event to the webserver API
- funct = getattr(requests, doc["type"])
- if not doc["content"]:
- response = funct(url_with_route)
- else:
- response = funct(url_with_route, **doc["content"])
-
- # Update the last handled timestamp
- self._last_handled_event_timestamp = doc["timestamp"]
+ self._update_last_handled_event_timestamp()
+ return
+ self._process_event(event_doc)
if db_cursor.retrieved > 0:
- # Update the local app registry
- # To keep track and properly retrieve the next events after that
- self._manager.app_registry.set_item(
- "last_handled_event_timestamp",
- get_timestamp_str(self._last_handled_event_timestamp))
-
- if not self._manager.is_running:
- # Simply exit without emiting the signal
- return
-
- elapsed_time = round(time.time() - start_time)
- waiting_time = max(0, CHECK_NEW_EVENTS_INTERVAL_SECONDS - elapsed_time)
- waiting_time_msec = waiting_time * 1000
-
- self.task_completed.emit(waiting_time_msec)
+ self._update_last_handled_event_timestamp()
+
+ # Watch for new event documents directly on the collection
+ with self._manager.collection.watch([{"$match": {"operationType": "insert"}}]) as stream:
+ while self._manager.is_running:
+ try:
+ # Non-blocking method to get the next change
+ change = stream.try_next()
+ if change:
+ event_doc = change["fullDocument"]
+ self._process_event(event_doc)
+ self._update_last_handled_event_timestamp()
+ except pymongo.errors.PyMongoError as e:
+ print(f"Error in change stream: {e}")
+ self._error_count += 1
+ if self._error_count > 4:
+ print("Stopping the Event Handling due to the errors.")
+ return
+
+ sleep(0.2)
class EventsHandlerManager:
@@ -172,9 +224,6 @@ def _start_worker(self):
self._is_running = True
self._worker_thread.start(QtCore.QThread.HighestPriority)
- def _restart_worker(self, waiting_time_msec):
- self._timer.start(waiting_time_msec)
-
def start(self):
if self._worker_thread:
raise RuntimeError("The Event Handler cannot be started multiple times.")
@@ -182,7 +231,6 @@ def start(self):
raise RuntimeError("Webserver not set. Cannot start the event handler.")
self._worker_thread = EventHandlerWorker(self)
- self._worker_thread.task_completed.connect(self._restart_worker)
if not self._webserver.is_running:
# The webserver is not yet running, wait a bit before starting the loop
@@ -245,12 +293,14 @@ def _validate_event_values(event_route: str,
@require_events_handler
-def register_event(event_route: str,
- event_type: str,
- content: Optional[dict],
- target_users: Optional[list] = None,
- target_groups: Optional[list] = None,
- expire_in: Optional[int] = None):
+def send_event(event_route: str,
+ event_type: str,
+ content: Optional[dict],
+ target_users: Optional[list] = None,
+ target_groups: Optional[list] = None,
+ expire_in: Optional[int] = None,
+ wait_for_responses: bool = False,
+ waiting_time_secs: int = DEFAULT_RESPONSES_WAITING_TIME_SECS):
if target_users is None:
target_users = []
elif isinstance(target_users, str):
@@ -268,9 +318,10 @@ def register_event(event_route: str,
if not is_valid:
raise ValueError(invalid_msg)
- timestamp = datetime.now()
+ timestamp = datetime.now(timezone.utc)
event = {
"timestamp": timestamp,
+ "user_id": _EVENT_HANDLER.user_profile["user_id"],
"route": event_route,
"type": event_type,
"target_users": target_users,
@@ -279,7 +330,14 @@ def register_event(event_route: str,
}
if expire_in is not None:
- expire_in += MINIMUM_EVENT_EXPIRE_IN_SECONDS
event["expire_at"] = timestamp + timedelta(seconds=expire_in)
- _EVENT_HANDLER.collection.insert_one(event)
+ if wait_for_responses:
+ event["responses"] = []
+
+ event_doc_id = _EVENT_HANDLER.collection.insert_one(event).inserted_id
+
+ if wait_for_responses:
+ sleep(waiting_time_secs)
+ event_doc = _EVENT_HANDLER.collection.find_one({"_id": event_doc_id})
+ return event_doc["responses"]
diff --git a/src/quadpype/events/notification.py b/src/quadpype/events/notification.py
index 984adfb541..83f41e95a3 100644
--- a/src/quadpype/events/notification.py
+++ b/src/quadpype/events/notification.py
@@ -17,6 +17,7 @@ async def show_tray_message(message: str):
tray_icon_widget.showMessage("Notification", message)
return {"message": "Message displayed"}
+
@router.post("/popup/", tags=["popup"])
async def show_popup_message(message: str):
tray_icon_widget: Optional[SystemTrayIcon] = get_tray_icon_widget()
diff --git a/src/quadpype/hosts/aftereffects/addon.py b/src/quadpype/hosts/aftereffects/addon.py
index 39ca383a0c..7e3a0e09e9 100644
--- a/src/quadpype/hosts/aftereffects/addon.py
+++ b/src/quadpype/hosts/aftereffects/addon.py
@@ -12,7 +12,7 @@ def add_implementation_envs(self, env, _app):
"""Modify environments to contain all required for implementation."""
defaults = {
"QUADPYPE_LOG_NO_COLORS": "True",
- "WEBSOCKET_URL": "ws://localhost:8097/ws/"
+ "QUADPYPE_WEBSOCKET_URL": "ws://localhost:8017/ws/"
}
for key, value in defaults.items():
if not env.get(key):
diff --git a/src/quadpype/hosts/aftereffects/api/README.md b/src/quadpype/hosts/aftereffects/api/README.md
index 938fad87d0..803af3ce2e 100644
--- a/src/quadpype/hosts/aftereffects/api/README.md
+++ b/src/quadpype/hosts/aftereffects/api/README.md
@@ -59,7 +59,7 @@ Expected deployed extension location on default Windows:
For easier debugging of Javascript:
https://community.adobe.com/t5/download-install/adobe-extension-debuger-problem/td-p/10911704?page=1
Add (optional) --enable-blink-features=ShadowDOMV0,CustomElementsV0 when starting Chrome
-then localhost:8092
+then localhost:8017
Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01
## Resources
diff --git a/src/quadpype/hosts/aftereffects/api/checksums b/src/quadpype/hosts/aftereffects/api/checksums
index 469abab1c8..90ea7ef012 100644
--- a/src/quadpype/hosts/aftereffects/api/checksums
+++ b/src/quadpype/hosts/aftereffects/api/checksums
@@ -1,9 +1,9 @@
-19D4C7C6C4BEA8D552873219030B5CB71D8A8DC2526B7F3909043725E36838EA:.debug
+C07F1436D9ED900B3E94605183DB29E82301191D9DC3D55917CF9A68842E6F92:.debug
48F60C67335C984141456712CE8E6F9D82B8C0DF52353571C8D4FCFAB9724A94:index.html
D6CCD9DD044B02E4F2ED1B49E9C083D823D6E3940F9825DD932D07C8B0229C78:css\boilerplate.css
0D183A454C941E5C937DCFDABFFE257F4FBB49BD418DF200AB6C01A39836BA27:css\styles.css
1CE5197F378AD02BCE717B29CB25E7423AFA2E6D22996BABB7297E025D192B4F:css\topcoat-desktop-dark.min.css
-4AC922CCE8FC86C9404831E609F3A718A3C0910690E528E413D52FC89810B0A3:CSXS\manifest.xml
+F7B0CEE06DDED97BDD89D21B246F80285AC2CFAD205966F39A39F4C644D4D5B6:CSXS\manifest.xml
9F51E857F342D27FF05A97A09E96401EE7EB901C978A31F560F0A49C3A6592FB:icons\ayon_logo.png
A1B1F026B3BB43F4801DB4FAB5CDAE7C1DDCABA064FEDB0A227F633F32EB4F61:icons\iconDarkNormal.png
6C04A66B67CCDA76D4689BF5F80DFB46377E8B3A652CFC5B3D268E9E27DC9853:icons\iconDarkRollover.png
@@ -11,7 +11,7 @@ A1B1F026B3BB43F4801DB4FAB5CDAE7C1DDCABA064FEDB0A227F633F32EB4F61:icons\iconDarkN
2BEECE898BE17D6843AD22D78139E306DEE28030FC79712B32D99F8487328AB9:icons\iconNormal.png
DD24CF1239E1FFA18B7EE76084E75CE74178CD60A1CF388784934D3DF2F9EED6:icons\iconQuadPype.png
F77A105590E0231818FBAFE766C7A672DC5C6A432E3416594C76D2249D2E978B:icons\iconRollover.png
-96F6BD26A8E430D1DE26E2D516DB9DA63328F15936D1F758969524924F9FA5AD:js\main.js
+76F230D6FA0F205C83F3FBBC8999D6DBE6A752D5BA7868FE463A1710A20C2C02:js\main.js
423DF7EC0AE7C32886E06C000AE90E3FDAA919DAC563DD9DDEBB787E9E1B6025:js\themeManager.js
5F8F8EBCFFC668009CF48DF1301F8C962A6D54F6EA12F0B0469C42C9DE48F742:js\libs\CSInterface.js
4D9586A075F082A04FD40178499C472012B351DB4C1A4D210907A0891F7D8AD9:js\libs\jquery-2.0.2.min.js
diff --git a/src/quadpype/hosts/aftereffects/api/extension.zxp b/src/quadpype/hosts/aftereffects/api/extension.zxp
index be989ad46e..22a1442f63 100644
Binary files a/src/quadpype/hosts/aftereffects/api/extension.zxp and b/src/quadpype/hosts/aftereffects/api/extension.zxp differ
diff --git a/src/quadpype/hosts/aftereffects/api/extension/.debug b/src/quadpype/hosts/aftereffects/api/extension/.debug
index a81c433392..8812ae67bf 100644
--- a/src/quadpype/hosts/aftereffects/api/extension/.debug
+++ b/src/quadpype/hosts/aftereffects/api/extension/.debug
@@ -6,25 +6,25 @@
-
+
+
+
+
-
+
-
+
-
-
-
-
+
-
+
-
+
diff --git a/src/quadpype/hosts/aftereffects/api/extension/CSXS/manifest.xml b/src/quadpype/hosts/aftereffects/api/extension/CSXS/manifest.xml
index a3289b331e..e1267bcb2e 100644
--- a/src/quadpype/hosts/aftereffects/api/extension/CSXS/manifest.xml
+++ b/src/quadpype/hosts/aftereffects/api/extension/CSXS/manifest.xml
@@ -1,6 +1,5 @@
-
-
+
+
@@ -39,7 +38,7 @@
-
+
./index.html
./jsx/hostscript.jsx
diff --git a/src/quadpype/hosts/aftereffects/api/extension/js/main.js b/src/quadpype/hosts/aftereffects/api/extension/js/main.js
index f47d60fdd7..b4c7fdfb56 100644
--- a/src/quadpype/hosts/aftereffects/api/extension/js/main.js
+++ b/src/quadpype/hosts/aftereffects/api/extension/js/main.js
@@ -57,7 +57,7 @@ function get_extension_version(){
function main(websocket_url){
// creates connection to 'websocket_url', registers routes
- var default_url = 'ws://localhost:8099/ws/';
+ var default_url = 'ws://localhost:8017/ws/';
if (websocket_url == ''){
websocket_url = default_url;
@@ -386,7 +386,7 @@ function main(websocket_url){
}
/** main entry point **/
-startUp("WEBSOCKET_URL");
+startUp("QUADPYPE_WEBSOCKET_URL");
(function () {
'use strict';
diff --git a/src/quadpype/hosts/blender/api/lib.py b/src/quadpype/hosts/blender/api/lib.py
index f76f1980eb..0cda91741b 100644
--- a/src/quadpype/hosts/blender/api/lib.py
+++ b/src/quadpype/hosts/blender/api/lib.py
@@ -7,7 +7,8 @@
import bpy
import addon_utils
from quadpype.lib import Logger, NumberDef
-
+from quadpype.pipeline import get_current_project_name, get_current_asset_name
+from quadpype.client import get_asset_by_name
if TYPE_CHECKING:
from quadpype.pipeline.create import CreateContext # noqa: F401
@@ -487,7 +488,7 @@ def setattr_deep(root, path, value):
setattr_deep(obj, key, value)
-def collect_animation_defs(create_context, step=True, fps=False):
+def collect_animation_defs(step=True, fps=False):
"""Get the basic animation attribute definitions for the publisher.
Arguments:
@@ -504,18 +505,15 @@ def collect_animation_defs(create_context, step=True, fps=False):
# get scene values as defaults
scene = bpy.context.scene
- # frame_start = scene.frame_start
- # frame_end = scene.frame_end
- # handle_start = 0
- # handle_end = 0
# use task entity attributes to set defaults based on current context
- task_entity = create_context.get_current_task_entity()
- attrib: dict = task_entity["attrib"]
- frame_start = attrib["frameStart"]
- frame_end = attrib["frameEnd"]
- handle_start = attrib["handleStart"]
- handle_end = attrib["handleEnd"]
+ project_name = get_current_project_name()
+ asset_name = get_current_asset_name()
+ attrib = get_asset_by_name(project_name, asset_name)
+ frame_start = attrib.get("frameStart", scene.frame_start)
+ frame_end = attrib.get("frameEnd", scene.frame_end)
+ handle_start = attrib.get("handleStart", 0)
+ handle_end = attrib.get("handleEnd", 0)
# build attributes
defs = [
diff --git a/src/quadpype/hosts/blender/api/ops.py b/src/quadpype/hosts/blender/api/ops.py
index c2ffca6472..8af0bf0d7e 100644
--- a/src/quadpype/hosts/blender/api/ops.py
+++ b/src/quadpype/hosts/blender/api/ops.py
@@ -47,7 +47,7 @@ class BlenderApplication:
def get_app(cls):
if cls._instance is None:
# If any other addon or plug-in may have initialed a Qt application
- # before AYON then we should take the existing instance instead.
+ # before QuadPype then we should take the existing instance instead.
application = QtWidgets.QApplication.instance()
if application is None:
application = QtWidgets.QApplication(sys.argv)
@@ -354,8 +354,19 @@ class LaunchWorkFiles(LaunchQtApp):
_tool_name = "workfiles"
def execute(self, context):
- return super().execute(context)
+ result = super().execute(context)
+ self._window.set_context({
+ "asset": get_current_asset_name(),
+ "task": get_current_task_name()
+ })
+ return result
+ def before_window_show(self):
+ self._window.root = str(Path(
+ os.environ.get("AVALON_WORKDIR", ""),
+ os.environ.get("AVALON_SCENEDIR", ""),
+ ))
+ self._window.refresh()
class SetFrameRange(bpy.types.Operator):
bl_idname = "wm.set_frame_range"
diff --git a/src/quadpype/hosts/blender/api/pipeline.py b/src/quadpype/hosts/blender/api/pipeline.py
index 90b4b1b664..e1a07832c3 100644
--- a/src/quadpype/hosts/blender/api/pipeline.py
+++ b/src/quadpype/hosts/blender/api/pipeline.py
@@ -626,7 +626,6 @@ def ls() -> Iterator:
called containers.
"""
container_ids = {
- AYON_CONTAINER_ID,
# Backwards compatibility
AVALON_CONTAINER_ID
}
diff --git a/src/quadpype/hosts/blender/plugins/create/create_animation.py b/src/quadpype/hosts/blender/plugins/create/create_animation.py
index 1cc6447d2e..a24e9961f6 100644
--- a/src/quadpype/hosts/blender/plugins/create/create_animation.py
+++ b/src/quadpype/hosts/blender/plugins/create/create_animation.py
@@ -32,6 +32,5 @@ def create(
return collection
def get_instance_attr_defs(self):
- defs = lib.collect_animation_defs(self.create_context,
- step=False)
+ defs = lib.collect_animation_defs(step=False)
return defs
diff --git a/src/quadpype/hosts/blender/plugins/create/create_pointcache.py b/src/quadpype/hosts/blender/plugins/create/create_pointcache.py
index ff5a2752df..c02dc0a004 100644
--- a/src/quadpype/hosts/blender/plugins/create/create_pointcache.py
+++ b/src/quadpype/hosts/blender/plugins/create/create_pointcache.py
@@ -29,7 +29,6 @@ def create(
return collection
def get_instance_attr_defs(self):
- defs = lib.collect_animation_defs(self.create_context,
- step=False)
+ defs = lib.collect_animation_defs(step=False)
return defs
diff --git a/src/quadpype/hosts/blender/plugins/create/create_render.py b/src/quadpype/hosts/blender/plugins/create/create_render.py
index e9d237d682..221449ea07 100644
--- a/src/quadpype/hosts/blender/plugins/create/create_render.py
+++ b/src/quadpype/hosts/blender/plugins/create/create_render.py
@@ -46,6 +46,6 @@ def create(
return collection
def get_instance_attr_defs(self):
- defs = lib.collect_animation_defs(self.create_context)
+ defs = lib.collect_animation_defs()
return defs
diff --git a/src/quadpype/hosts/blender/plugins/create/create_review.py b/src/quadpype/hosts/blender/plugins/create/create_review.py
index 3bc1131296..acccbaba2c 100644
--- a/src/quadpype/hosts/blender/plugins/create/create_review.py
+++ b/src/quadpype/hosts/blender/plugins/create/create_review.py
@@ -27,6 +27,6 @@ def create(
return collection
def get_instance_attr_defs(self):
- defs = lib.collect_animation_defs(self.create_context)
+ defs = lib.collect_animation_defs()
return defs
diff --git a/src/quadpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/src/quadpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py
index 0b0d350b87..6e3927c294 100644
--- a/src/quadpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py
+++ b/src/quadpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py
@@ -4,10 +4,11 @@
import quadpype.hosts.blender.api.action
-from ayon_core.pipeline.publish import (
- ValidateContentsOrder,
- OptionalPyblishPluginMixin,
- PublishValidationError
+from quadpype.pipeline import OptionalPyblishPluginMixin
+from quadpype.hosts.blender.api import plugin
+from quadpype.pipeline.publish import (
+ PublishValidationError,
+ ValidateContentsOrder
)
diff --git a/src/quadpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py b/src/quadpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py
index 3de1f32b15..de763aaa95 100644
--- a/src/quadpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py
+++ b/src/quadpype/hosts/blender/plugins/publish/validate_render_camera_is_set.py
@@ -6,8 +6,6 @@
OptionalPyblishPluginMixin,
PublishValidationError
)
-from ayon_blender.api import plugin
-
class ValidateRenderCameraIsSet(
plugin.BlenderInstancePlugin,
diff --git a/src/quadpype/hosts/photoshop/addon.py b/src/quadpype/hosts/photoshop/addon.py
index 432be25246..c9eba0bef9 100644
--- a/src/quadpype/hosts/photoshop/addon.py
+++ b/src/quadpype/hosts/photoshop/addon.py
@@ -15,7 +15,7 @@ def add_implementation_envs(self, env, _app):
"""Modify environments to contain all required for implementation."""
defaults = {
"QUADPYPE_LOG_NO_COLORS": "True",
- "WEBSOCKET_URL": "ws://localhost:8099/ws/"
+ "QUADPYPE_WEBSOCKET_URL": "ws://localhost:8018/ws/"
}
for key, value in defaults.items():
if not env.get(key):
diff --git a/src/quadpype/hosts/photoshop/api/checksums b/src/quadpype/hosts/photoshop/api/checksums
index 9a47a117fe..103dfcb93e 100644
--- a/src/quadpype/hosts/photoshop/api/checksums
+++ b/src/quadpype/hosts/photoshop/api/checksums
@@ -1,11 +1,11 @@
8716BC5C7DBF3338B8F104CFDEED84B1F88E2D4CC2A1AA2E5AAE577EDD396DB8:.debug
28997C43711E01361728A5FA0F67E7EEBDD616FB083B0210934C18FDA3D5E92B:index.html
-7046A70229FD9B8833C76A46E067734945E2C465C607965D9945E06EC4AD80BB:client\client.js
+E971793EC8E773D2D9FAAC01A35C94DEA2864BAD426460CA025285B5B78C943B:client\client.js
5F8F8EBCFFC668009CF48DF1301F8C962A6D54F6EA12F0B0469C42C9DE48F742:client\CSInterface.js
FD0E8BED2FA88E12F5D49646944793C134428F80311BC03F3BCB1196CE3AF0AD:client\loglevel.min.js
61E9E273ADF6E290D9E6911500D44F2AB02DAEFCD902888106017735590E26C1:client\wsrpc.js
78E2A27896C24A4AF1D232AAC321E09B8995CC9A86C9ABB19C8A97A861946F41:client\wsrpc.min.js
-5009853F7DDD19A2CB8F1A2920CF8D1008E61274766023A85114EF04D56039B4:CSXS\manifest.xml
+1630FED4CC9468A7C9DC3B1F8E5CE3159E3583579C1FE7CB35D4DD877C1A3856:CSXS\manifest.xml
52437DE708F4A7153DB0A0A5DB66658C2A12F3A8673054E87C7B84895D4CE584:host\index.jsx
CC7DDC2DA053A7B922BC0DA7023F734BB80D6E00B87926715206D530664BC415:host\json.js
D6F0B54C43673F18785AE31D4659289CCB08D9C79C1BBEF588ACBA8C9DBD96F4:host\JSX.js
diff --git a/src/quadpype/hosts/photoshop/api/extension.zxp b/src/quadpype/hosts/photoshop/api/extension.zxp
index 14fdd01218..71fbd7d11e 100644
Binary files a/src/quadpype/hosts/photoshop/api/extension.zxp and b/src/quadpype/hosts/photoshop/api/extension.zxp differ
diff --git a/src/quadpype/hosts/photoshop/api/extension/CSXS/manifest.xml b/src/quadpype/hosts/photoshop/api/extension/CSXS/manifest.xml
index dda2115061..280c829176 100644
--- a/src/quadpype/hosts/photoshop/api/extension/CSXS/manifest.xml
+++ b/src/quadpype/hosts/photoshop/api/extension/CSXS/manifest.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/src/quadpype/hosts/photoshop/api/extension/client/client.js b/src/quadpype/hosts/photoshop/api/extension/client/client.js
index 0a3515a927..6283ea8229 100644
--- a/src/quadpype/hosts/photoshop/api/extension/client/client.js
+++ b/src/quadpype/hosts/photoshop/api/extension/client/client.js
@@ -28,7 +28,7 @@
}
/** main entry point **/
- startUp("WEBSOCKET_URL");
+ startUp("QUADPYPE_WEBSOCKET_URL");
// get websocket server url from environment value
async function startUp(url){
@@ -67,7 +67,7 @@
function main(websocket_url){
// creates connection to 'websocket_url', registers routes
log.warn("websocket_url", websocket_url);
- var default_url = 'ws://localhost:8099/ws/';
+ var default_url = 'ws://localhost:8018/ws/';
if (websocket_url == ''){
websocket_url = default_url;
diff --git a/src/quadpype/hosts/tvpaint/api/communication_server.py b/src/quadpype/hosts/tvpaint/api/communication_server.py
index 427ca29ced..0d324df2cc 100644
--- a/src/quadpype/hosts/tvpaint/api/communication_server.py
+++ b/src/quadpype/hosts/tvpaint/api/communication_server.py
@@ -717,12 +717,12 @@ def launch(self, launch_args):
self._create_routes()
- os.environ["WEBSOCKET_URL"] = "ws://localhost:{}".format(
+ os.environ["QUADPYPE_WEBSOCKET_URL"] = "ws://localhost:{}".format(
self.websocket_server.port
)
log.info("Added request handler for url: {}".format(
- os.environ["WEBSOCKET_URL"]
+ os.environ["QUADPYPE_WEBSOCKET_URL"]
))
self._start_webserver()
diff --git a/src/quadpype/hosts/tvpaint/tvpaint_plugin/plugin_code/README.md b/src/quadpype/hosts/tvpaint/tvpaint_plugin/plugin_code/README.md
index 70a96b2919..ed7bfde228 100644
--- a/src/quadpype/hosts/tvpaint/tvpaint_plugin/plugin_code/README.md
+++ b/src/quadpype/hosts/tvpaint/tvpaint_plugin/plugin_code/README.md
@@ -7,7 +7,7 @@ This implementation is using TVPaint plugin (C/C++) which can communicate with p
Current implementation is based on websocket protocol, using json-rpc communication (specification 2.0). Project is in beta stage, tested only on Windows.
-To be able to load plugin, environment variable `WEBSOCKET_URL` must be set otherwise plugin won't load at all. Plugin should not affect TVPaint if python server crash, but buttons won't work.
+To be able to load plugin, environment variable `QUADPYPE_WEBSOCKET_URL` must be set otherwise plugin won't load at all. Plugin should not affect TVPaint if python server crash, but buttons won't work.
## Requirements - Python server
- python >= 3.6
diff --git a/src/quadpype/hosts/tvpaint/tvpaint_plugin/plugin_code/library.cpp b/src/quadpype/hosts/tvpaint/tvpaint_plugin/plugin_code/library.cpp
index 5971432af9..eac4d3e5d5 100644
--- a/src/quadpype/hosts/tvpaint/tvpaint_plugin/plugin_code/library.cpp
+++ b/src/quadpype/hosts/tvpaint/tvpaint_plugin/plugin_code/library.cpp
@@ -543,7 +543,7 @@ int FAR PASCAL PI_Open( PIFilter* iFilter )
{
PI_Parameters( iFilter, NULL ); // NULL as iArg means "open the requester"
}
- char *env_value = std::getenv("WEBSOCKET_URL");
+ char *env_value = std::getenv("QUADPYPE_WEBSOCKET_URL");
if (env_value != NULL) {
communication = new Communicator(env_value);
communication->connect();
diff --git a/src/quadpype/lib/dateutils.py b/src/quadpype/lib/dateutils.py
index a75ad3072a..b00cdd012b 100644
--- a/src/quadpype/lib/dateutils.py
+++ b/src/quadpype/lib/dateutils.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Get configuration data."""
-import datetime
+from datetime import datetime, timezone
def get_datetime_data(datetime_obj=None):
@@ -35,7 +35,7 @@ def get_datetime_data(datetime_obj=None):
"""
if not datetime_obj:
- datetime_obj = datetime.datetime.now()
+ datetime_obj = datetime.now()
year = datetime_obj.strftime("%Y")
@@ -76,8 +76,8 @@ def get_datetime_data(datetime_obj=None):
}
-def get_timestamp_str(datetime_obj=None):
- """Get standardized timestamp from datetime object.
+def get_timestamp_str(datetime_obj=None, local_timezone=False):
+ """Get standardized timestamp string from a datetime object.
Args:
datetime_obj (datetime.datetime): Object of datetime. Current time
@@ -85,11 +85,19 @@ def get_timestamp_str(datetime_obj=None):
"""
if datetime_obj is None:
- datetime_obj = datetime.datetime.now()
- return datetime_obj.astimezone().isoformat()
+ if local_timezone:
+ datetime_obj = datetime.now().astimezone()
+ else:
+ datetime_obj = datetime.now(timezone.utc)
+
+ if datetime_obj.tzinfo is None:
+ # If no timezone info passed, we assume it's UTC
+ datetime_obj = datetime_obj.replace(tzinfo=timezone.utc)
+ return datetime_obj.isoformat()
+
def get_datetime_from_timestamp_str(timestamp_str):
- return datetime.datetime.fromisoformat(timestamp_str)
+ return datetime.fromisoformat(timestamp_str)
def get_formatted_current_time():
diff --git a/src/quadpype/modules/webserver/server.py b/src/quadpype/modules/webserver/server.py
index b3a3bffe06..f7cda09e03 100644
--- a/src/quadpype/modules/webserver/server.py
+++ b/src/quadpype/modules/webserver/server.py
@@ -32,6 +32,12 @@
templates = Jinja2Templates(directory=Path(__file__).parent.joinpath("templates"))
+
+@WEB_API.post("/ping/", tags=["ping"])
+async def ping():
+ return {"message": "pong"}
+
+
@WEB_API.get('/favicon.ico', include_in_schema=False)
async def favicon():
return FileResponse(get_app_favicon_filepath())
diff --git a/src/quadpype/tools/adobe_webserver/app.py b/src/quadpype/tools/adobe_webserver/app.py
index 85265a5c86..51d0cf5fa1 100644
--- a/src/quadpype/tools/adobe_webserver/app.py
+++ b/src/quadpype/tools/adobe_webserver/app.py
@@ -39,13 +39,13 @@ def __init__(self):
port = None
host_name = "localhost"
- websocket_url = os.getenv("WEBSOCKET_URL")
+ websocket_url = os.getenv("QUADPYPE_WEBSOCKET_URL")
if websocket_url:
parsed = urllib.parse.urlparse(websocket_url)
port = parsed.port
host_name = parsed.netloc.split(":")[0]
if not port:
- port = 8098 # fallback
+ port = 8016 # fallback
self.port = port
self.host_name = host_name
@@ -76,7 +76,7 @@ async def send_context_change(self, host):
but one already running, without
this publish would point to old context.
"""
- client = WSRPCClient(os.getenv("WEBSOCKET_URL"),
+ client = WSRPCClient(os.getenv("QUADPYPE_WEBSOCKET_URL"),
loop=asyncio.get_event_loop())
await client.connect()
diff --git a/src/quadpype/version.py b/src/quadpype/version.py
index cf221b3986..6c21500d38 100644
--- a/src/quadpype/version.py
+++ b/src/quadpype/version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""File declaring QuadPype version."""
-__version__ = "4.0.9"
+__version__ = "4.0.10"
diff --git a/src/tools/_lib/database/database_init.js b/src/tools/_lib/database/database_init.js
new file mode 100644
index 0000000000..c028d6c142
--- /dev/null
+++ b/src/tools/_lib/database/database_init.js
@@ -0,0 +1,4 @@
+rs.initiate({
+ _id: "rs0",
+ members: [{ _id: 0, host: "localhost:27017" }]
+});
diff --git a/src/tools/_lib/database/docker-compose.yml b/src/tools/_lib/database/docker-compose.yml
new file mode 100644
index 0000000000..a459d48a04
--- /dev/null
+++ b/src/tools/_lib/database/docker-compose.yml
@@ -0,0 +1,14 @@
+services:
+ mongodb:
+ image: mongo:latest
+ container_name: ${LOCAL_MONGO_CONTAINER_ID:-mongodb}
+ ports:
+ - "${LOCAL_MONGO_PORT:-27017}:27017"
+ command: ["--replSet", "rs0"]
+ volumes:
+ - ./database_init.js:/docker-entrypoint-initdb.d/database_init.js:ro
+ healthcheck:
+ test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
diff --git a/src/tools/_lib/run/pre_run.py b/src/tools/_lib/run/pre_run.py
index 88672f7e27..2a2d6591e5 100644
--- a/src/tools/_lib/run/pre_run.py
+++ b/src/tools/_lib/run/pre_run.py
@@ -12,15 +12,21 @@ def main():
description='Mandatory script that will execute the platform dependant code before QuadPype run'
)
parser.add_argument("-d", "--dev", action="store_true")
+ parser.add_argument("-p", "--prod", action="store_true")
parser.add_argument("-m", "--mongo-uri", help='Format should be like: mongodb://uri.to.my.mongo-db:27017')
args = parser.parse_args()
- # Build the arguments string for the platform dependant script
+ # Build the argument string for the platform dependant script
args_string = ""
if args.dev:
args_string += "-d"
+ if args.prod and not args.dev:
+ # If the used add the flags for dev and prod,
+ # we start in dev, to avoid potential mistakes or issues
+ args_string += "-p"
+
if args.mongo_uri:
# First, check MongoDB connection string format validity
match = re.fullmatch(r"^mongodb(\+srv)?://([\w.%-]+:[\w.%-]+@)?[\w.%-]+(:\d{1,5})?/?$", args.mongo_uri)
diff --git a/src/tools/local_database_start.ps1 b/src/tools/local_database_start.ps1
index d385de3ca9..742787005c 100644
--- a/src/tools/local_database_start.ps1
+++ b/src/tools/local_database_start.ps1
@@ -17,16 +17,22 @@ if ($? -eq $true) {
# Check if there is already a Docker container using the required port
docker ps -a | findstr $QUADPYPE_MONGO_CONTAINER_PORT > $null
if ($? -eq $true) {
- write-output "Port 27017 is already used by Docker, operation aborted."
+ write-output "Port $QUADPYPE_MONGO_CONTAINER_PORT is already used by Docker, operation aborted."
return 1
}
# Check if there is already a process using the required port
-$PORT_TCP_CONNECTION = Get-NetTCPConnection | Where-Object Localport -eq 27017
+$PORT_TCP_CONNECTION = Get-NetTCPConnection | Where-Object Localport -eq $QUADPYPE_MONGO_CONTAINER_PORT
if ($PORT_TCP_CONNECTION) {
$OWNING_PROCESS_NAME = (Get-Process -Id $PORT_TCP_CONNECTION.OwningProcess).Name
- write-output "Port 27017 is already used by $OWNING_PROCESS_NAME, operation aborted."
+ write-output "Port $QUADPYPE_MONGO_CONTAINER_PORT is already used by $OWNING_PROCESS_NAME, operation aborted."
return 1
}
-docker run -p "${QUADPYPE_MONGO_CONTAINER_PORT}:${QUADPYPE_MONGO_CONTAINER_PORT}" --name $QUADPYPE_MONGO_CONTAINER_NAME -d mongo > $null
+$env:LOCAL_MONGO_PORT = "${QUADPYPE_MONGO_CONTAINER_PORT}"
+$env:LOCAL_MONGO_CONTAINER_ID = "${QUADPYPE_MONGO_CONTAINER_NAME}"
+
+$SCRIPT_DIR = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$DOCKER_COMPOSE_FILE_PATH = Join-Path -Path $SCRIPT_DIR -ChildPath "_lib\database\docker-compose.yml"
+
+docker-compose -f $DOCKER_COMPOSE_FILE_PATH up -d
diff --git a/src/tools/local_database_transfer.ps1 b/src/tools/local_database_transfer.ps1
index 393fa70a9b..be484aa1bb 100644
--- a/src/tools/local_database_transfer.ps1
+++ b/src/tools/local_database_transfer.ps1
@@ -1,7 +1,7 @@
$SCRIPT_DIR = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
function migrate_settings() {
- $MIGRATE_DB_SCRIPT_PATH = Join-Path -Path (Split-Path $SCRIPT_DIR -Parent) -ChildPath "tools\_lib\database\transfer_settings.js"
+ $MIGRATE_DB_SCRIPT_PATH = Join-Path -Path $SCRIPT_DIR -ChildPath "_lib\database\transfer_settings.js"
$MIGRATE_VAL = mongosh --file $MIGRATE_DB_SCRIPT_PATH --eval "MONGO_URI='$MONGO_URI', MONGO_DESTINATION='$MONGO_DESTINATION'" --quiet
return $MIGRATE_VAL
}
diff --git a/src/tools/pre_run.ps1 b/src/tools/pre_run.ps1
index 42beca5f84..9855471560 100644
--- a/src/tools/pre_run.ps1
+++ b/src/tools/pre_run.ps1
@@ -1,5 +1,6 @@
Param(
[alias("d")][switch]$DEV=$false,
+ [alias("p")][switch]$PROD=$false,
[alias("m")][string]$MONGO_URI=""
)
@@ -11,8 +12,14 @@ while ((Split-Path $PATH_QUADPYPE_ROOT -Leaf) -ne "src") {
$PATH_QUADPYPE_ROOT = (get-item $PATH_QUADPYPE_ROOT).Parent.FullName
}
-if ($MONGO_URI) {
+if ($DEV) {
+ $QUADPYPE_MONGO = "mongodb://localhost:27017"
+} elseif ($MONGO_URI) {
$QUADPYPE_MONGO = $MONGO_URI
+} elseif ($PROD) {
+ $QUADPYPE_MONGO = "IGNORED"
+} else {
+ $QUADPYPE_MONGO = [System.Environment]::GetEnvironmentVariable("QUADPYPE_MONGO", [System.EnvironmentVariableTarget]::User)
}
if (($QUADPYPE_MONGO -eq "")) {
@@ -23,6 +30,12 @@ if (($QUADPYPE_MONGO -eq "")) {
exit 1
}
+if (($QUADPYPE_MONGO -ne "IGNORED")) {
+ [System.Environment]::SetEnvironmentVariable("QUADPYPE_MONGO", $QUADPYPE_MONGO, [System.EnvironmentVariableTarget]::User)
+} else {
+ [System.Environment]::SetEnvironmentVariable("QUADPYPE_MONGO", $null, [System.EnvironmentVariableTarget]::User)
+}
+
[System.Environment]::SetEnvironmentVariable("QUADPYPE_ROOT", $PATH_QUADPYPE_ROOT, [System.EnvironmentVariableTarget]::User)
[System.Environment]::SetEnvironmentVariable("PYENV_ROOT", (Resolve-Path -Path "~\.pyenv"), [System.EnvironmentVariableTarget]::User)
@@ -34,7 +47,15 @@ if (Test-Path -Path $PATH_ADDITIONAL_ENV_FILE) {
Remove-Item $PATH_ADDITIONAL_ENV_FILE -Force -ErrorAction SilentlyContinue | Out-Null
}
-New-Item "$($PATH_ADDITIONAL_ENV_FILE)" -ItemType File -Value "QUADPYPE_ROOT=$PATH_QUADPYPE_ROOT" | Out-Null
+$ENV_FILE_CONTENT = ""
+
+if (($QUADPYPE_MONGO -ne "IGNORED")) {
+ $ENV_FILE_CONTENT = [string]::Concat($ENV_FILE_CONTENT,"QUADPYPE_MONGO=$QUADPYPE_MONGO$([Environment]::NewLine)")
+}
+
+$ENV_FILE_CONTENT = [string]::Concat($ENV_FILE_CONTENT,"QUADPYPE_ROOT=$PATH_QUADPYPE_ROOT$([Environment]::NewLine)")
+
+New-Item "$($PATH_ADDITIONAL_ENV_FILE)" -ItemType File -Value "${ENV_FILE_CONTENT}" | Out-Null
# Launch the activate script