Sync branch magefile with main #104308

Merged
Sybren A. Stüvel merged 85 commits from abelli/flamenco:magefile into magefile 2024-05-13 16:26:32 +02:00
9 changed files with 373 additions and 321 deletions
Showing only changes of commit 3b4da656c9 - Show all commits

View File

@ -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)

View File

@ -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
if error:
window_manager.flamenco_status_ping = error
return ManagerInfo.with_error(error)
# 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" % (version.name, version.version)
info = manager_info.fetch(api_client)
except manager_info.FetchError as ex:
report = str(ex)
window_manager.flamenco_status_ping = report
return report, "ERROR"
return ManagerInfo.with_info(version, storage)
manager_info.save(info)
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"
window_manager.flamenco_status_ping = report
return report, report_level

View File

@ -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()

View File

@ -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)

View File

@ -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:

View 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()

View File

@ -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,

View File

@ -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."""

View File

@ -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())