Sync branch magefile with main #104308
@ -27,6 +27,7 @@ if __is_first_load:
|
||||
preferences,
|
||||
projects,
|
||||
worker_tags,
|
||||
manager_info,
|
||||
)
|
||||
else:
|
||||
import importlib
|
||||
@ -38,6 +39,7 @@ else:
|
||||
preferences = importlib.reload(preferences)
|
||||
projects = importlib.reload(projects)
|
||||
worker_tags = importlib.reload(worker_tags)
|
||||
manager_info = importlib.reload(manager_info)
|
||||
|
||||
import bpy
|
||||
|
||||
@ -160,6 +162,9 @@ def register() -> None:
|
||||
gui.register()
|
||||
job_types.register()
|
||||
|
||||
# Once everything is registered, load the cached manager info from JSON.
|
||||
manager_info.load_into_cache()
|
||||
|
||||
|
||||
def unregister() -> None:
|
||||
discard_global_flamenco_data(None)
|
||||
|
@ -3,13 +3,12 @@
|
||||
# <pep8 compliant>
|
||||
|
||||
import logging
|
||||
import dataclasses
|
||||
import platform
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from urllib3.exceptions import HTTPError, MaxRetryError
|
||||
import bpy
|
||||
|
||||
from flamenco import manager_info, job_types
|
||||
|
||||
_flamenco_client = None
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
@ -27,23 +26,6 @@ else:
|
||||
_SharedStorageLocation = object
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class ManagerInfo:
|
||||
version: Optional[_FlamencoVersion] = None
|
||||
storage: Optional[_SharedStorageLocation] = None
|
||||
error: str = ""
|
||||
|
||||
@classmethod
|
||||
def with_error(cls, error: str) -> "ManagerInfo":
|
||||
return cls(error=error)
|
||||
|
||||
@classmethod
|
||||
def with_info(
|
||||
cls, version: _FlamencoVersion, storage: _SharedStorageLocation
|
||||
) -> "ManagerInfo":
|
||||
return cls(version=version, storage=storage)
|
||||
|
||||
|
||||
def flamenco_api_client(manager_url: str) -> _ApiClient:
|
||||
"""Returns an API client for communicating with a Manager."""
|
||||
global _flamenco_client
|
||||
@ -87,12 +69,12 @@ def discard_flamenco_data():
|
||||
_flamenco_client = None
|
||||
|
||||
|
||||
def ping_manager_with_report(
|
||||
def ping_manager(
|
||||
window_manager: bpy.types.WindowManager,
|
||||
scene: bpy.types.Scene,
|
||||
api_client: _ApiClient,
|
||||
prefs: _FlamencoPreferences,
|
||||
) -> tuple[str, str]:
|
||||
"""Ping the Manager, update preferences, and return a report as string.
|
||||
"""Fetch Manager info, and update the scene for it.
|
||||
|
||||
:returns: tuple (report, level). The report will be something like "<name>
|
||||
version <version> found", or an error message. The level will be
|
||||
@ -100,55 +82,49 @@ def ping_manager_with_report(
|
||||
`Operator.report()`.
|
||||
"""
|
||||
|
||||
info = ping_manager(window_manager, api_client, prefs)
|
||||
if info.error:
|
||||
return info.error, "ERROR"
|
||||
|
||||
assert info.version is not None
|
||||
report = "%s version %s found" % (info.version.name, info.version.version)
|
||||
return report, "INFO"
|
||||
|
||||
|
||||
def ping_manager(
|
||||
window_manager: bpy.types.WindowManager,
|
||||
api_client: _ApiClient,
|
||||
prefs: _FlamencoPreferences,
|
||||
) -> ManagerInfo:
|
||||
"""Fetch Manager config & version, and update cached preferences."""
|
||||
|
||||
window_manager.flamenco_status_ping = "..."
|
||||
|
||||
# Do a late import, so that the API is only imported when actually used.
|
||||
from flamenco.manager import ApiException
|
||||
from flamenco.manager.apis import MetaApi
|
||||
from flamenco.manager.models import FlamencoVersion, SharedStorageLocation
|
||||
# Remember the old values, as they may have disappeared from the Manager.
|
||||
old_job_type_name = getattr(scene, "flamenco_job_type", "")
|
||||
old_tag_name = getattr(scene, "flamenco_worker_tag", "")
|
||||
|
||||
meta_api = MetaApi(api_client)
|
||||
error = ""
|
||||
try:
|
||||
version: FlamencoVersion = meta_api.get_version()
|
||||
storage: SharedStorageLocation = meta_api.get_shared_storage(
|
||||
"users", platform.system().lower()
|
||||
)
|
||||
except ApiException as ex:
|
||||
error = "Manager cannot be reached: %s" % ex
|
||||
except MaxRetryError as ex:
|
||||
# This is the common error, when for example the port number is
|
||||
# incorrect and nothing is listening. The exception text is not included
|
||||
# because it's very long and confusing.
|
||||
error = "Manager cannot be reached"
|
||||
except HTTPError as ex:
|
||||
error = "Manager cannot be reached: %s" % ex
|
||||
info = manager_info.fetch(api_client)
|
||||
except manager_info.FetchError as ex:
|
||||
report = str(ex)
|
||||
window_manager.flamenco_status_ping = report
|
||||
return report, "ERROR"
|
||||
|
||||
if error:
|
||||
window_manager.flamenco_status_ping = error
|
||||
return ManagerInfo.with_error(error)
|
||||
manager_info.save(info)
|
||||
|
||||
# Store whether this Manager supports the Shaman API.
|
||||
prefs.is_shaman_enabled = storage.shaman_enabled
|
||||
prefs.job_storage = storage.location
|
||||
report = "%s version %s found" % (
|
||||
info.flamenco_version.name,
|
||||
info.flamenco_version.version,
|
||||
)
|
||||
report_level = "INFO"
|
||||
|
||||
job_types.refresh_scene_properties(scene, info.job_types)
|
||||
|
||||
# Try to restore the old values.
|
||||
#
|
||||
# Since you cannot un-set an enum property, and 'empty string' is not a
|
||||
# valid value either, when the old choice is no longer available we remove
|
||||
# the underlying ID property.
|
||||
if old_job_type_name:
|
||||
try:
|
||||
scene.flamenco_job_type = old_job_type_name
|
||||
except TypeError: # Thrown when the old enum value no longer exists.
|
||||
del scene["flamenco_job_type"]
|
||||
report = f"Job type {old_job_type_name!r} no longer available, choose another one"
|
||||
report_level = "WARNING"
|
||||
|
||||
if old_tag_name:
|
||||
try:
|
||||
scene.flamenco_worker_tag = old_tag_name
|
||||
except TypeError: # Thrown when the old enum value no longer exists.
|
||||
del scene["flamenco_worker_tag"]
|
||||
report = f"Tag {old_tag_name!r} no longer available, choose another one"
|
||||
report_level = "WARNING"
|
||||
|
||||
report = "%s version %s found" % (version.name, version.version)
|
||||
window_manager.flamenco_status_ping = report
|
||||
|
||||
return ManagerInfo.with_info(version, storage)
|
||||
return report, report_level
|
||||
|
@ -43,23 +43,19 @@ class FLAMENCO_PT_job_submission(bpy.types.Panel):
|
||||
col.prop(context.scene, "flamenco_job_name", text="Job Name")
|
||||
col.prop(context.scene, "flamenco_job_priority", text="Priority")
|
||||
|
||||
# Worker tag:
|
||||
row = col.row(align=True)
|
||||
row.prop(context.scene, "flamenco_worker_tag", text="Tag")
|
||||
row.operator("flamenco.fetch_worker_tags", text="", icon="FILE_REFRESH")
|
||||
|
||||
layout.separator()
|
||||
|
||||
col = layout.column()
|
||||
# Refreshables:
|
||||
col = layout.column(align=True)
|
||||
col.operator(
|
||||
"flamenco.ping_manager", text="Refresh from Manager", icon="FILE_REFRESH"
|
||||
)
|
||||
if not job_types.are_job_types_available():
|
||||
col.operator("flamenco.fetch_job_types", icon="FILE_REFRESH")
|
||||
return
|
||||
col.prop(context.scene, "flamenco_worker_tag", text="Tag")
|
||||
|
||||
row = col.row(align=True)
|
||||
row.prop(context.scene, "flamenco_job_type", text="")
|
||||
row.operator("flamenco.fetch_job_types", text="", icon="FILE_REFRESH")
|
||||
|
||||
self.draw_job_settings(context, layout.column(align=True))
|
||||
# Job properties:
|
||||
job_col = layout.column(align=True)
|
||||
job_col.prop(context.scene, "flamenco_job_type", text="Job Type")
|
||||
self.draw_job_settings(context, job_col)
|
||||
|
||||
layout.separator()
|
||||
|
||||
|
@ -8,7 +8,7 @@ import bpy
|
||||
|
||||
from .job_types_propgroup import JobTypePropertyGroup
|
||||
from .bat.submodules import bpathlib
|
||||
from . import preferences
|
||||
from . import manager_info
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .manager import ApiClient as _ApiClient
|
||||
@ -133,8 +133,11 @@ def is_file_inside_job_storage(context: bpy.types.Context, blendfile: Path) -> b
|
||||
|
||||
blendfile = bpathlib.make_absolute(blendfile)
|
||||
|
||||
prefs = preferences.get(context)
|
||||
job_storage = bpathlib.make_absolute(Path(prefs.job_storage))
|
||||
info = manager_info.load_cached()
|
||||
if not info:
|
||||
raise RuntimeError("Flamenco Manager info unknown, please refresh.")
|
||||
|
||||
job_storage = bpathlib.make_absolute(Path(info.shared_storage.location))
|
||||
|
||||
log.info("Checking whether the file is already inside the job storage")
|
||||
log.info(" file : %s", blendfile)
|
||||
|
@ -1,14 +1,10 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
import bpy
|
||||
|
||||
from . import job_types_propgroup
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
from . import job_types_propgroup, manager_info
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from flamenco.manager import ApiClient as _ApiClient
|
||||
@ -39,24 +35,12 @@ _selected_job_type_propgroup: Optional[
|
||||
] = None
|
||||
|
||||
|
||||
def fetch_available_job_types(api_client: _ApiClient, scene: bpy.types.Scene) -> None:
|
||||
from flamenco.manager import ApiClient
|
||||
from flamenco.manager.api import jobs_api
|
||||
from flamenco.manager.model.available_job_types import AvailableJobTypes
|
||||
|
||||
assert isinstance(api_client, ApiClient)
|
||||
|
||||
job_api_instance = jobs_api.JobsApi(api_client)
|
||||
response: AvailableJobTypes = job_api_instance.get_job_types()
|
||||
|
||||
def refresh_scene_properties(
|
||||
scene: bpy.types.Scene, available_job_types: _AvailableJobTypes
|
||||
) -> None:
|
||||
_clear_available_job_types(scene)
|
||||
|
||||
# Store the response JSON on the scene. This is used when the blend file is
|
||||
# loaded (and thus the _available_job_types global variable is still empty)
|
||||
# to generate the PropertyGroup of the selected job type.
|
||||
scene.flamenco_available_job_types_json = json.dumps(response.to_dict())
|
||||
|
||||
_store_available_job_types(response)
|
||||
_store_available_job_types(available_job_types)
|
||||
update_job_type_properties(scene)
|
||||
|
||||
|
||||
def setting_is_visible(setting: _AvailableJobSetting) -> bool:
|
||||
@ -125,33 +109,6 @@ def _store_available_job_types(available_job_types: _AvailableJobTypes) -> None:
|
||||
_job_type_enum_items.insert(0, ("", "Select a Job Type", "", 0, 0))
|
||||
|
||||
|
||||
def _available_job_types_from_json(job_types_json: str) -> None:
|
||||
"""Convert JSON to AvailableJobTypes object, and update global variables for it."""
|
||||
from flamenco.manager.models import AvailableJobTypes
|
||||
from flamenco.manager.configuration import Configuration
|
||||
from flamenco.manager.model_utils import validate_and_convert_types
|
||||
|
||||
json_dict = json.loads(job_types_json)
|
||||
|
||||
dummy_cfg = Configuration()
|
||||
|
||||
try:
|
||||
job_types = validate_and_convert_types(
|
||||
json_dict, (AvailableJobTypes,), ["job_types"], True, True, dummy_cfg
|
||||
)
|
||||
except TypeError:
|
||||
_log.warn(
|
||||
"Flamenco: could not restore cached job types, refresh them from Flamenco Manager"
|
||||
)
|
||||
_store_available_job_types(AvailableJobTypes(job_types=[]))
|
||||
return
|
||||
|
||||
assert isinstance(
|
||||
job_types, AvailableJobTypes
|
||||
), "expected AvailableJobTypes, got %s" % type(job_types)
|
||||
_store_available_job_types(job_types)
|
||||
|
||||
|
||||
def are_job_types_available() -> bool:
|
||||
"""Returns whether job types have been fetched and are available."""
|
||||
return bool(_job_type_enum_items)
|
||||
@ -199,7 +156,7 @@ def _clear_available_job_types(scene: bpy.types.Scene) -> None:
|
||||
_clear_job_type_propgroup()
|
||||
|
||||
_available_job_types = None
|
||||
_job_type_enum_items.clear()
|
||||
_job_type_enum_items = []
|
||||
scene.flamenco_available_job_types_json = ""
|
||||
|
||||
|
||||
@ -238,21 +195,21 @@ def _get_job_types_enum_items(dummy1, dummy2):
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def restore_available_job_types(dummy1, dummy2):
|
||||
def restore_available_job_types(_filepath, _none):
|
||||
scene = bpy.context.scene
|
||||
job_types_json = getattr(scene, "flamenco_available_job_types_json", "")
|
||||
if not job_types_json:
|
||||
info = manager_info.load_cached()
|
||||
if info is None:
|
||||
_clear_available_job_types(scene)
|
||||
return
|
||||
_available_job_types_from_json(job_types_json)
|
||||
update_job_type_properties(scene)
|
||||
refresh_scene_properties(scene, info.job_types)
|
||||
|
||||
|
||||
def discard_flamenco_data():
|
||||
if _available_job_types:
|
||||
_available_job_types.clear()
|
||||
if _job_type_enum_items:
|
||||
_job_type_enum_items.clear()
|
||||
global _available_job_types
|
||||
global _job_type_enum_items
|
||||
|
||||
_available_job_types = None
|
||||
_job_type_enum_items = []
|
||||
|
||||
|
||||
def register() -> None:
|
||||
|
210
addon/flamenco/manager_info.py
Normal file
210
addon/flamenco/manager_info.py
Normal file
@ -0,0 +1,210 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# <pep8 compliant>
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from urllib3.exceptions import HTTPError, MaxRetryError
|
||||
|
||||
import bpy
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from flamenco.manager import ApiClient as _ApiClient
|
||||
from flamenco.manager.models import (
|
||||
AvailableJobTypes as _AvailableJobTypes,
|
||||
FlamencoVersion as _FlamencoVersion,
|
||||
SharedStorageLocation as _SharedStorageLocation,
|
||||
WorkerTagList as _WorkerTagList,
|
||||
)
|
||||
else:
|
||||
_ApiClient = object
|
||||
_AvailableJobTypes = object
|
||||
_FlamencoVersion = object
|
||||
_SharedStorageLocation = object
|
||||
_WorkerTagList = object
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ManagerInfo:
|
||||
"""Cached information obtained from a Flamenco Manager.
|
||||
|
||||
This is the root object of what is stored on disk, every time someone
|
||||
presses a 'refresh' button to update worker tags, job types, etc.
|
||||
"""
|
||||
|
||||
flamenco_version: _FlamencoVersion
|
||||
shared_storage: _SharedStorageLocation
|
||||
job_types: _AvailableJobTypes
|
||||
worker_tags: _WorkerTagList
|
||||
|
||||
@staticmethod
|
||||
def type_info() -> dict[str, type]:
|
||||
# Do a late import, so that the API is only imported when actually used.
|
||||
from flamenco.manager.models import (
|
||||
AvailableJobTypes,
|
||||
FlamencoVersion,
|
||||
SharedStorageLocation,
|
||||
WorkerTagList,
|
||||
)
|
||||
|
||||
# These types cannot be obtained by introspecting the ManagerInfo class, as
|
||||
# at runtime that doesn't use real type annotations.
|
||||
return {
|
||||
"flamenco_version": FlamencoVersion,
|
||||
"shared_storage": SharedStorageLocation,
|
||||
"job_types": AvailableJobTypes,
|
||||
"worker_tags": WorkerTagList,
|
||||
}
|
||||
|
||||
|
||||
class FetchError(RuntimeError):
|
||||
"""Raised when the manager info could not be fetched from the Manager."""
|
||||
|
||||
|
||||
class LoadError(RuntimeError):
|
||||
"""Raised when the manager info could not be loaded from disk cache."""
|
||||
|
||||
|
||||
_cached_manager_info: Optional[ManagerInfo] = None
|
||||
|
||||
|
||||
def fetch(api_client: _ApiClient) -> ManagerInfo:
|
||||
global _cached_manager_info
|
||||
|
||||
# Do a late import, so that the API is only imported when actually used.
|
||||
from flamenco.manager import ApiException
|
||||
from flamenco.manager.apis import MetaApi, JobsApi, WorkerMgtApi
|
||||
from flamenco.manager.models import (
|
||||
AvailableJobTypes,
|
||||
FlamencoVersion,
|
||||
SharedStorageLocation,
|
||||
WorkerTagList,
|
||||
)
|
||||
|
||||
meta_api = MetaApi(api_client)
|
||||
jobs_api = JobsApi(api_client)
|
||||
worker_mgt_api = WorkerMgtApi(api_client)
|
||||
|
||||
try:
|
||||
flamenco_version: FlamencoVersion = meta_api.get_version()
|
||||
shared_storage: SharedStorageLocation = meta_api.get_shared_storage(
|
||||
"users", platform.system().lower()
|
||||
)
|
||||
job_types: AvailableJobTypes = jobs_api.get_job_types()
|
||||
worker_tags: WorkerTagList = worker_mgt_api.fetch_worker_tags()
|
||||
except ApiException as ex:
|
||||
raise FetchError("Manager cannot be reached: %s" % ex) from ex
|
||||
except MaxRetryError as ex:
|
||||
# This is the common error, when for example the port number is
|
||||
# incorrect and nothing is listening. The exception text is not included
|
||||
# because it's very long and confusing.
|
||||
raise FetchError("Manager cannot be reached") from ex
|
||||
except HTTPError as ex:
|
||||
raise FetchError("Manager cannot be reached: %s" % ex) from ex
|
||||
|
||||
_cached_manager_info = ManagerInfo(
|
||||
flamenco_version=flamenco_version,
|
||||
shared_storage=shared_storage,
|
||||
job_types=job_types,
|
||||
worker_tags=worker_tags,
|
||||
)
|
||||
return _cached_manager_info
|
||||
|
||||
|
||||
class Encoder(json.JSONEncoder):
|
||||
def default(self, o):
|
||||
from flamenco.manager.model_utils import OpenApiModel
|
||||
|
||||
if isinstance(o, OpenApiModel):
|
||||
return o.to_dict()
|
||||
|
||||
if isinstance(o, ManagerInfo):
|
||||
# dataclasses.asdict() creates a copy of the OpenAPI models,
|
||||
# in a way that just doesn't work, hence this workaround.
|
||||
return {f.name: getattr(o, f.name) for f in dataclasses.fields(o)}
|
||||
|
||||
return super().default(o)
|
||||
|
||||
|
||||
def _to_json(info: ManagerInfo) -> str:
|
||||
return json.dumps(info, indent=" ", cls=Encoder)
|
||||
|
||||
|
||||
def _from_json(contents: str | bytes) -> ManagerInfo:
|
||||
# Do a late import, so that the API is only imported when actually used.
|
||||
from flamenco.manager.configuration import Configuration
|
||||
from flamenco.manager.model_utils import validate_and_convert_types
|
||||
|
||||
json_dict = json.loads(contents)
|
||||
dummy_cfg = Configuration()
|
||||
api_models = {}
|
||||
|
||||
for name, api_type in ManagerInfo.type_info().items():
|
||||
api_model = validate_and_convert_types(
|
||||
json_dict[name],
|
||||
(api_type,),
|
||||
[name],
|
||||
True,
|
||||
True,
|
||||
dummy_cfg,
|
||||
)
|
||||
api_models[name] = api_model
|
||||
|
||||
return ManagerInfo(**api_models)
|
||||
|
||||
|
||||
def _json_filepath() -> Path:
|
||||
# This is the '~/.config/blender/{version}' path.
|
||||
user_path = Path(bpy.utils.resource_path(type="USER"))
|
||||
return user_path / "config" / "flamenco-manager-info.json"
|
||||
|
||||
|
||||
def save(info: ManagerInfo) -> None:
|
||||
json_path = _json_filepath()
|
||||
json_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
as_json = _to_json(info)
|
||||
json_path.write_text(as_json, encoding="utf8")
|
||||
|
||||
|
||||
def load() -> ManagerInfo:
|
||||
json_path = _json_filepath()
|
||||
if not json_path.exists():
|
||||
raise FileNotFoundError(f"{json_path.name} not found in {json_path.parent}")
|
||||
|
||||
try:
|
||||
as_json = json_path.read_text(encoding="utf8")
|
||||
except OSError as ex:
|
||||
raise LoadError(f"Could not read {json_path}: {ex}") from ex
|
||||
|
||||
try:
|
||||
return _from_json(as_json)
|
||||
except json.JSONDecodeError as ex:
|
||||
raise LoadError(f"Could not decode JSON in {json_path}") from ex
|
||||
|
||||
|
||||
def load_into_cache() -> Optional[ManagerInfo]:
|
||||
global _cached_manager_info
|
||||
|
||||
_cached_manager_info = None
|
||||
try:
|
||||
_cached_manager_info = load()
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
except LoadError as ex:
|
||||
print(f"Could not load Flamenco Manager info from disk: {ex}")
|
||||
return None
|
||||
|
||||
return _cached_manager_info
|
||||
|
||||
|
||||
def load_cached() -> Optional[ManagerInfo]:
|
||||
global _cached_manager_info
|
||||
|
||||
if _cached_manager_info is not None:
|
||||
return _cached_manager_info
|
||||
|
||||
return load_into_cache()
|
@ -10,7 +10,7 @@ from urllib3.exceptions import HTTPError, MaxRetryError
|
||||
|
||||
import bpy
|
||||
|
||||
from . import job_types, job_submission, preferences, worker_tags
|
||||
from . import job_types, job_submission, preferences, manager_info
|
||||
from .job_types_propgroup import JobTypePropertyGroup
|
||||
from .bat.submodules import bpathlib
|
||||
|
||||
@ -51,80 +51,6 @@ class FlamencoOpMixin:
|
||||
return api_client
|
||||
|
||||
|
||||
class FLAMENCO_OT_fetch_job_types(FlamencoOpMixin, bpy.types.Operator):
|
||||
bl_idname = "flamenco.fetch_job_types"
|
||||
bl_label = "Fetch Job Types"
|
||||
bl_description = "Query Flamenco Manager to obtain the available job types"
|
||||
|
||||
def execute(self, context: bpy.types.Context) -> set[str]:
|
||||
api_client = self.get_api_client(context)
|
||||
|
||||
from flamenco.manager import ApiException
|
||||
|
||||
scene = context.scene
|
||||
old_job_type_name = getattr(scene, "flamenco_job_type", "")
|
||||
|
||||
try:
|
||||
job_types.fetch_available_job_types(api_client, scene)
|
||||
except ApiException as ex:
|
||||
self.report({"ERROR"}, "Error getting job types: %s" % ex)
|
||||
return {"CANCELLED"}
|
||||
except MaxRetryError as ex:
|
||||
# This is the common error, when for example the port number is
|
||||
# incorrect and nothing is listening.
|
||||
self.report({"ERROR"}, "Unable to reach Manager")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if old_job_type_name:
|
||||
try:
|
||||
scene.flamenco_job_type = old_job_type_name
|
||||
except TypeError: # Thrown when the old job type no longer exists.
|
||||
# You cannot un-set an enum property, and 'empty string' is not
|
||||
# a valid value either, so better to just remove the underlying
|
||||
# ID property.
|
||||
del scene["flamenco_job_type"]
|
||||
|
||||
self.report(
|
||||
{"WARNING"},
|
||||
"Job type %r no longer available, choose another one"
|
||||
% old_job_type_name,
|
||||
)
|
||||
|
||||
job_types.update_job_type_properties(scene)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FLAMENCO_OT_fetch_worker_tags(FlamencoOpMixin, bpy.types.Operator):
|
||||
bl_idname = "flamenco.fetch_worker_tags"
|
||||
bl_label = "Fetch Worker Tags"
|
||||
bl_description = "Query Flamenco Manager to obtain the available worker tags"
|
||||
|
||||
def execute(self, context: bpy.types.Context) -> set[str]:
|
||||
api_client = self.get_api_client(context)
|
||||
|
||||
from flamenco.manager import ApiException
|
||||
|
||||
scene = context.scene
|
||||
old_tag = getattr(scene, "flamenco_worker_tag", "")
|
||||
|
||||
try:
|
||||
worker_tags.refresh(context, api_client)
|
||||
except ApiException as ex:
|
||||
self.report({"ERROR"}, "Error getting job types: %s" % ex)
|
||||
return {"CANCELLED"}
|
||||
except MaxRetryError as ex:
|
||||
# This is the common error, when for example the port number is
|
||||
# incorrect and nothing is listening.
|
||||
self.report({"ERROR"}, "Unable to reach Manager")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if old_tag:
|
||||
# TODO: handle cases where the old tag no longer exists.
|
||||
scene.flamenco_worker_tag = old_tag
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FLAMENCO_OT_ping_manager(FlamencoOpMixin, bpy.types.Operator):
|
||||
bl_idname = "flamenco.ping_manager"
|
||||
bl_label = "Flamenco: Ping Manager"
|
||||
@ -132,13 +58,13 @@ class FLAMENCO_OT_ping_manager(FlamencoOpMixin, bpy.types.Operator):
|
||||
bl_options = {"REGISTER"} # No UNDO.
|
||||
|
||||
def execute(self, context: bpy.types.Context) -> set[str]:
|
||||
from . import comms, preferences
|
||||
from . import comms
|
||||
|
||||
api_client = self.get_api_client(context)
|
||||
prefs = preferences.get(context)
|
||||
|
||||
report, level = comms.ping_manager_with_report(
|
||||
context.window_manager, api_client, prefs
|
||||
report, level = comms.ping_manager(
|
||||
context.window_manager,
|
||||
context.scene,
|
||||
api_client,
|
||||
)
|
||||
self.report({level}, report)
|
||||
|
||||
@ -259,29 +185,31 @@ class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):
|
||||
|
||||
:return: an error string when something went wrong.
|
||||
"""
|
||||
from . import comms, preferences
|
||||
from . import comms
|
||||
|
||||
# Get the manager's info. This is cached in the preferences, so
|
||||
# regardless of whether this function actually responds to version
|
||||
# mismatches, it has to be called to also refresh the shared storage
|
||||
# location.
|
||||
# Get the manager's info. This is cached to disk, so regardless of
|
||||
# whether this function actually responds to version mismatches, it has
|
||||
# to be called to also refresh the shared storage location.
|
||||
api_client = self.get_api_client(context)
|
||||
prefs = preferences.get(context)
|
||||
mgrinfo = comms.ping_manager(context.window_manager, api_client, prefs)
|
||||
if mgrinfo.error:
|
||||
return mgrinfo.error
|
||||
|
||||
report, report_level = comms.ping_manager(
|
||||
context.window_manager,
|
||||
context.scene,
|
||||
api_client,
|
||||
)
|
||||
if report_level != "INFO":
|
||||
return report
|
||||
|
||||
# Check the Manager's version.
|
||||
if not self.ignore_version_mismatch:
|
||||
my_version = comms.flamenco_client_version()
|
||||
assert mgrinfo.version is not None
|
||||
mgrinfo = manager_info.load_cached()
|
||||
|
||||
# Safe to assume, as otherwise the ping_manager() call would not have succeeded.
|
||||
assert mgrinfo is not None
|
||||
|
||||
my_version = comms.flamenco_client_version()
|
||||
mgrversion = mgrinfo.flamenco_version.shortversion
|
||||
|
||||
try:
|
||||
mgrversion = mgrinfo.version.shortversion
|
||||
except AttributeError:
|
||||
# shortversion was introduced in Manager version 3.0-beta2, which
|
||||
# may not be running here yet.
|
||||
mgrversion = mgrinfo.version.version
|
||||
if mgrversion != my_version:
|
||||
context.window_manager.flamenco_version_mismatch = True
|
||||
return (
|
||||
@ -299,6 +227,23 @@ class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):
|
||||
# Empty error message indicates 'ok'.
|
||||
return ""
|
||||
|
||||
def _manager_info(
|
||||
self, context: bpy.types.Context
|
||||
) -> Optional[manager_info.ManagerInfo]:
|
||||
"""Load the manager info.
|
||||
|
||||
If it cannot be loaded, returns None after emitting an error message and
|
||||
calling self._quit(context).
|
||||
"""
|
||||
manager = manager_info.load_cached()
|
||||
if not manager:
|
||||
self.report(
|
||||
{"ERROR"}, "No information known about Flamenco Manager, refresh first."
|
||||
)
|
||||
self._quit(context)
|
||||
return None
|
||||
return manager
|
||||
|
||||
def _save_blendfile(self, context):
|
||||
"""Save to a different file, specifically for Flamenco.
|
||||
|
||||
@ -368,8 +313,11 @@ class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):
|
||||
self._quit(context)
|
||||
return {"CANCELLED"}
|
||||
|
||||
prefs = preferences.get(context)
|
||||
if prefs.is_shaman_enabled:
|
||||
manager = self._manager_info(context)
|
||||
if not manager:
|
||||
return {"CANCELLED"}
|
||||
|
||||
if manager.shared_storage.shaman_enabled:
|
||||
# self.blendfile_on_farm will be set when BAT created the checkout,
|
||||
# see _on_bat_pack_msg() below.
|
||||
self.blendfile_on_farm = None
|
||||
@ -414,11 +362,14 @@ class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):
|
||||
raise FileNotFoundError()
|
||||
|
||||
# Determine where the blend file will be stored.
|
||||
manager = self._manager_info(context)
|
||||
if not manager:
|
||||
raise FileNotFoundError("Manager info not known")
|
||||
unique_dir = "%s-%s" % (
|
||||
datetime.datetime.now().isoformat("-").replace(":", ""),
|
||||
self.job_name,
|
||||
)
|
||||
pack_target_dir = Path(prefs.job_storage) / unique_dir
|
||||
pack_target_dir = Path(manager.shared_storage.location) / unique_dir
|
||||
|
||||
# TODO: this should take the blendfile location relative to the project path into account.
|
||||
pack_target_file = pack_target_dir / blendfile.name
|
||||
@ -690,8 +641,6 @@ class FLAMENCO3_OT_explore_file_path(bpy.types.Operator):
|
||||
|
||||
|
||||
classes = (
|
||||
FLAMENCO_OT_fetch_job_types,
|
||||
FLAMENCO_OT_fetch_worker_tags,
|
||||
FLAMENCO_OT_ping_manager,
|
||||
FLAMENCO_OT_eval_setting,
|
||||
FLAMENCO_OT_submit_job,
|
||||
|
@ -5,7 +5,7 @@ from pathlib import Path
|
||||
|
||||
import bpy
|
||||
|
||||
from . import projects
|
||||
from . import projects, manager_info
|
||||
|
||||
|
||||
def discard_flamenco_client(context):
|
||||
@ -16,9 +16,7 @@ def discard_flamenco_client(context):
|
||||
context.window_manager.flamenco_status_ping = ""
|
||||
|
||||
|
||||
def _refresh_the_planet(
|
||||
prefs: "FlamencoPreferences", context: bpy.types.Context
|
||||
) -> None:
|
||||
def _refresh_the_planet(context: bpy.types.Context) -> None:
|
||||
"""Refresh all GUI areas."""
|
||||
for win in context.window_manager.windows:
|
||||
for area in win.screen.areas:
|
||||
@ -35,7 +33,8 @@ def _manager_url_updated(prefs, context):
|
||||
|
||||
# Warning, be careful what of the context to access here. Accessing /
|
||||
# changing too much can cause crashes, infinite loops, etc.
|
||||
comms.ping_manager_with_report(context.window_manager, api_client, prefs)
|
||||
comms.ping_manager(context.window_manager, context.scene, api_client)
|
||||
_refresh_the_planet(context)
|
||||
|
||||
|
||||
_project_finder_enum_items = [
|
||||
@ -66,22 +65,6 @@ class FlamencoPreferences(bpy.types.AddonPreferences):
|
||||
items=_project_finder_enum_items,
|
||||
)
|
||||
|
||||
is_shaman_enabled: bpy.props.BoolProperty( # type: ignore
|
||||
name="Shaman Enabled",
|
||||
description="Whether this Manager has the Shaman protocol enabled",
|
||||
default=False,
|
||||
update=_refresh_the_planet,
|
||||
)
|
||||
|
||||
# Property that should be editable from Python. It's not exposed to the GUI.
|
||||
job_storage: bpy.props.StringProperty( # type: ignore
|
||||
name="Job Storage Directory",
|
||||
subtype="DIR_PATH",
|
||||
default="",
|
||||
options={"HIDDEN"},
|
||||
description="Directory where blend files are stored when submitting them to Flamenco. This value is determined by Flamenco Manager",
|
||||
)
|
||||
|
||||
# Property that gets its value from the above _job_storage, and cannot be
|
||||
# set. This makes it read-only in the GUI.
|
||||
job_storage_for_gui: bpy.props.StringProperty( # type: ignore
|
||||
@ -90,14 +73,7 @@ class FlamencoPreferences(bpy.types.AddonPreferences):
|
||||
default="",
|
||||
options={"SKIP_SAVE"},
|
||||
description="Directory where blend files are stored when submitting them to Flamenco. This value is determined by Flamenco Manager",
|
||||
get=lambda prefs: prefs.job_storage,
|
||||
)
|
||||
|
||||
worker_tags: bpy.props.CollectionProperty( # type: ignore
|
||||
type=WorkerTag,
|
||||
name="Worker Tags",
|
||||
description="Cache for the worker tags available on the configured Manager",
|
||||
options={"HIDDEN"},
|
||||
get=lambda prefs: prefs._job_storage(),
|
||||
)
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
@ -116,7 +92,9 @@ class FlamencoPreferences(bpy.types.AddonPreferences):
|
||||
split.label(text="")
|
||||
split.label(text=label)
|
||||
|
||||
if not self.job_storage:
|
||||
manager = manager_info.load_cached()
|
||||
|
||||
if not manager:
|
||||
text_row(col, "Press the refresh button before using Flamenco")
|
||||
|
||||
if context.window_manager.flamenco_status_ping:
|
||||
@ -126,7 +104,7 @@ class FlamencoPreferences(bpy.types.AddonPreferences):
|
||||
text_row(aligned, "Press the refresh button to check the connection")
|
||||
text_row(aligned, "and update the job storage location")
|
||||
|
||||
if self.is_shaman_enabled:
|
||||
if manager and manager.shared_storage.shaman_enabled:
|
||||
text_row(col, "Shaman enabled")
|
||||
col.prop(self, "job_storage_for_gui", text="Job Storage")
|
||||
|
||||
@ -152,6 +130,12 @@ class FlamencoPreferences(bpy.types.AddonPreferences):
|
||||
blendfile = Path(bpy.data.filepath)
|
||||
return projects.for_blendfile(blendfile, self.project_finder)
|
||||
|
||||
def _job_storage(self) -> str:
|
||||
info = manager_info.load_cached()
|
||||
if not info:
|
||||
return "Unknown, refresh first."
|
||||
return str(info.shared_storage.location)
|
||||
|
||||
|
||||
def get(context: bpy.types.Context) -> FlamencoPreferences:
|
||||
"""Return the add-on preferences."""
|
||||
|
@ -1,57 +1,35 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from typing import TYPE_CHECKING, Union
|
||||
from typing import Union
|
||||
|
||||
import bpy
|
||||
|
||||
from . import preferences
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from flamenco.manager import ApiClient as _ApiClient
|
||||
else:
|
||||
_ApiClient = object
|
||||
from . import manager_info
|
||||
|
||||
|
||||
_enum_items: list[Union[tuple[str, str, str], tuple[str, str, str, int, int]]] = []
|
||||
|
||||
|
||||
def refresh(context: bpy.types.Context, api_client: _ApiClient) -> None:
|
||||
"""Fetch the available worker tags from the Manager."""
|
||||
from flamenco.manager import ApiClient
|
||||
from flamenco.manager.api import worker_mgt_api
|
||||
from flamenco.manager.model.worker_tag_list import WorkerTagList
|
||||
|
||||
assert isinstance(api_client, ApiClient)
|
||||
|
||||
api = worker_mgt_api.WorkerMgtApi(api_client)
|
||||
response: WorkerTagList = api.fetch_worker_tags()
|
||||
|
||||
# Store on the preferences, so a cached version persists until the next refresh.
|
||||
prefs = preferences.get(context)
|
||||
prefs.worker_tags.clear()
|
||||
|
||||
for tag in response.tags:
|
||||
rna_tag = prefs.worker_tags.add()
|
||||
rna_tag.id = tag.id
|
||||
rna_tag.name = tag.name
|
||||
rna_tag.description = getattr(tag, "description", "")
|
||||
|
||||
# Preferences have changed, so make sure that Blender saves them (assuming
|
||||
# auto-save here).
|
||||
context.preferences.is_dirty = True
|
||||
|
||||
|
||||
def _get_enum_items(self, context):
|
||||
global _enum_items
|
||||
prefs = preferences.get(context)
|
||||
|
||||
manager = manager_info.load_cached()
|
||||
if manager is None:
|
||||
_enum_items = [
|
||||
(
|
||||
"-",
|
||||
"-tags unknown-",
|
||||
"Refresh to load the available Worker tags from the Manager",
|
||||
),
|
||||
]
|
||||
return _enum_items
|
||||
|
||||
_enum_items = [
|
||||
("-", "All", "No specific tag assigned, any worker can handle this job"),
|
||||
]
|
||||
_enum_items.extend(
|
||||
(tag.id, tag.name, tag.description)
|
||||
for tag in prefs.worker_tags
|
||||
)
|
||||
for tag in manager.worker_tags.tags:
|
||||
_enum_items.append((tag.id, tag.name, getattr(tag, "description", "")))
|
||||
|
||||
return _enum_items
|
||||
|
||||
|
||||
@ -70,9 +48,3 @@ def unregister() -> None:
|
||||
delattr(ob, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
print(doctest.testmod())
|
||||
|
Loading…
Reference in New Issue
Block a user