From f4f61ea5934cf808d08e80b3b25792e9fab483e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 1 Jun 2023 16:07:11 +0200 Subject: [PATCH] Add 'project finders' to the add-on The Flamenco add-on can now find the top-level directory of your Blender project. It can be configured to find any of these directories: - `.blender_project` - `.git` - `.subversion` - None, which is the default and the old behaviour of just using the directory of the current blend file as the 'project directory'. --- CHANGELOG.md | 1 + addon/flamenco/__init__.py | 11 +++- addon/flamenco/operators.py | 11 ++-- addon/flamenco/preferences.py | 33 +++++++++++ addon/flamenco/projects.py | 100 ++++++++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 addon/flamenco/projects.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 028681ba..40e65e77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ bugs in actually-released versions. - Improve logging of job deletion. - Add Worker Cluster support. Workers can be members of any number of clusters. Workers will only work on jobs that are assigned to that cluster. Jobs that do not have a cluster will be available to all workers, regardless of their cluster assignment. As a result, clusterless workers will only work on clusterless jobs. - Fix limitation where a job could have no more than 1000 tasks ([#104201](https://projects.blender.org/studio/flamenco/issues/104201)) +- Add support for finding the top-level 'project' directory. When submitting files to Flamenco, the add-on will try to retain the directory structure of your Blender project as precisely as possible. This new feature allows the add-on to find the top-level directory of your project by finding a `.blender_project`, `.git`, or `.subversion` directory. This can be configured in the add-on preferences. ## 3.2 - released 2023-02-21 diff --git a/addon/flamenco/__init__.py b/addon/flamenco/__init__.py index 5a6abec3..10de1bc9 100644 --- a/addon/flamenco/__init__.py +++ b/addon/flamenco/__init__.py @@ -19,7 +19,15 @@ from pathlib import Path __is_first_load = "operators" not in locals() if __is_first_load: - from . import operators, gui, job_types, comms, preferences, worker_clusters + from . import ( + operators, + gui, + job_types, + comms, + preferences, + projects, + worker_clusters, + ) else: import importlib @@ -28,6 +36,7 @@ else: job_types = importlib.reload(job_types) comms = importlib.reload(comms) preferences = importlib.reload(preferences) + projects = importlib.reload(projects) worker_clusters = importlib.reload(worker_clusters) import bpy diff --git a/addon/flamenco/operators.py b/addon/flamenco/operators.py index a7dcf153..5ee0a0e3 100644 --- a/addon/flamenco/operators.py +++ b/addon/flamenco/operators.py @@ -381,8 +381,9 @@ class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator): """ from .bat import interface as bat_interface - # TODO: get project path from addon preferences / project definition on Manager. - project_path = blendfile.parent + # Get project path from addon preferences. + prefs = preferences.get(context) + project_path: Path = prefs.project_root() try: project_path = Path(bpy.path.abspath(str(project_path))).resolve() except FileNotFoundError: @@ -395,7 +396,6 @@ class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator): datetime.datetime.now().isoformat("-").replace(":", ""), self.job_name, ) - prefs = preferences.get(context) pack_target_dir = Path(prefs.job_storage) / unique_dir # TODO: this should take the blendfile location relative to the project path into account. @@ -438,9 +438,12 @@ class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator): assert self.job is not None self.log.info("Sending BAT pack to Shaman") + prefs = preferences.get(context) + project_path: Path = prefs.project_root() + self.packthread = bat_interface.copy( base_blendfile=blendfile, - project=blendfile.parent, # TODO: get from preferences/GUI. + project=project_path, target="/", # Target directory irrelevant for Shaman transfers. exclusion_filter="", # TODO: get from GUI. relative_only=True, # TODO: get from GUI. diff --git a/addon/flamenco/preferences.py b/addon/flamenco/preferences.py index 80451e7d..9a05923c 100644 --- a/addon/flamenco/preferences.py +++ b/addon/flamenco/preferences.py @@ -1,8 +1,12 @@ # SPDX-License-Identifier: GPL-3.0-or-later # +from pathlib import Path + import bpy +from . import projects + def discard_flamenco_client(context): """Discard any cached Flamenco client after the Manager URL changes.""" @@ -34,6 +38,11 @@ def _manager_url_updated(prefs, context): comms.ping_manager_with_report(context.window_manager, api_client, prefs) +_project_finder_enum_items = [ + (key, info.label, info.description) for key, info in projects.finders.items() +] + + class WorkerCluster(bpy.types.PropertyGroup): id: bpy.props.StringProperty(name="id") # type: ignore name: bpy.props.StringProperty(name="Name") # type: ignore @@ -50,6 +59,13 @@ class FlamencoPreferences(bpy.types.AddonPreferences): update=_manager_url_updated, ) + project_finder: bpy.props.EnumProperty( # type: ignore + name="Project Finder", + description="Strategy for Flamenco to find the top level directory of your project", + default=_project_finder_enum_items[0][0], + 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", @@ -114,6 +130,23 @@ class FlamencoPreferences(bpy.types.AddonPreferences): text_row(col, "Shaman enabled") col.prop(self, "job_storage_for_gui", text="Job Storage") + # Project Root + col = layout.column(align=True) + col.prop(self, "project_finder") + try: + project_root = self.project_root() + except ValueError: + pass + else: + text_row(col, str(project_root)) + + def project_root(self) -> Path: + """Use the configured project finder to find the project root directory.""" + + # It is assumed that the blendfile is saved. + blendfile = Path(bpy.data.filepath) + return projects.for_blendfile(blendfile, self.project_finder) + def get(context: bpy.types.Context) -> FlamencoPreferences: """Return the add-on preferences.""" diff --git a/addon/flamenco/projects.py b/addon/flamenco/projects.py new file mode 100644 index 00000000..cb0eece6 --- /dev/null +++ b/addon/flamenco/projects.py @@ -0,0 +1,100 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# + +from pathlib import Path +from typing import Callable, TypeAlias +import dataclasses + + +def for_blendfile(blendfile: Path, strategy: str) -> Path: + """Return what is considered to be the project directory containing the given file. + + If none can be found, the directory containing the current blend file is returned. + If the current blend file has no path (because it was not saved), a ValueError is raised. + + :param blendfile: the path of the blend file for which to find the project. + :param strategy: the name of the finder to use, see `finders`. + """ + if blendfile.is_dir(): + msg = f"{blendfile} is not a blend file, cannot find project directory" + raise ValueError(msg) + + try: + finder_info = finders[strategy] + except KeyError: + msg = f"Unknown strategy {strategy!r}, cannot find project directory" + raise ValueError(msg) from None + + return finder_info.finder(blendfile) + + +def _finder_none(blendfile: Path) -> Path: + return blendfile.absolute().parent + + +def _finder_blender_project(blendfile: Path) -> Path: + return _search_path_marker(blendfile, ".blender_project") + + +def _finder_git(blendfile: Path) -> Path: + return _search_path_marker(blendfile, ".git") + + +def _finder_subversion(blendfile: Path) -> Path: + return _search_path_marker(blendfile, ".svn") + + +def _search_path_marker(blendfile: Path, marker_path: str) -> Path: + """Go up the directory hierarchy until a file or directory 'marker_path' is found.""" + + blendfile_dir = blendfile.absolute().parent + + directory = blendfile_dir + while True: + marker: Path = directory / marker_path + if marker.exists(): + return directory + + parent = directory.parent + if directory == parent: + # If a directory is its own parent, we're at the root and cannot go + # up further. + break + directory = parent + + # Could not find the marker, so use the directory containing the blend file. + return blendfile_dir + + +Finder: TypeAlias = Callable[[Path], Path] + + +@dataclasses.dataclass +class FinderInfo: + label: str + description: str + finder: Finder + + +finders: dict[str, FinderInfo] = { + "NONE": FinderInfo( + "None", + "Just use the directory containing the current blend file as the project directory.", + _finder_none, + ), + "BLENDER_PROJECT": FinderInfo( + "Blender Project", + "Find a .blend_project directory and use that as indicator for the top level project directory.", + _finder_blender_project, + ), + "GIT": FinderInfo( + "Git", + "Find a .git directory and use that as indicator for the top level project directory.", + _finder_git, + ), + "SUBVERSION": FinderInfo( + "Subversion", + "Find a .svn directory and use that as indicator for the top level project directory.", + _finder_subversion, + ), +}