Addon: start of framework for API communication
This commit is contained in:
parent
b678b90932
commit
616784df0a
1
.gitignore
vendored
1
.gitignore
vendored
@ -15,4 +15,3 @@ __pycache__
|
|||||||
*.pyc
|
*.pyc
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
.openapi-generator/
|
.openapi-generator/
|
||||||
.openapi-generator-ignore
|
|
||||||
|
26
addon/.openapi-generator-ignore
Normal file
26
addon/.openapi-generator-ignore
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# OpenAPI Generator Ignore
|
||||||
|
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
||||||
|
|
||||||
|
# Use this file to prevent files from being overwritten by the generator.
|
||||||
|
# The patterns follow closely to .gitignore or .dockerignore.
|
||||||
|
|
||||||
|
# As an example, the C# client generator defines ApiClient.cs.
|
||||||
|
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
||||||
|
#ApiClient.cs
|
||||||
|
|
||||||
|
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||||
|
#foo/*/qux
|
||||||
|
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||||
|
#foo/**/qux
|
||||||
|
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can also negate patterns with an exclamation (!).
|
||||||
|
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||||
|
#docs/*.md
|
||||||
|
# Then explicitly reverse the ignore rule for a single file:
|
||||||
|
#!docs/README.md
|
||||||
|
|
||||||
|
# This file is written by a human, and should not be overwritten by the generator.
|
||||||
|
flamenco/__init__.py
|
@ -19,8 +19,8 @@
|
|||||||
# <pep8 compliant>
|
# <pep8 compliant>
|
||||||
|
|
||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Blender Cloud",
|
"name": "Flamenco 3",
|
||||||
"author": "Sybren A. Stüvel, Francesco Siddi, Inês Almeida, Antony Riakiotakis",
|
"author": "Sybren A. Stüvel",
|
||||||
"version": (3, 0),
|
"version": (3, 0),
|
||||||
"blender": (3, 1, 0),
|
"blender": (3, 1, 0),
|
||||||
"description": "Flamenco client for Blender.",
|
"description": "Flamenco client for Blender.",
|
||||||
@ -29,3 +29,45 @@ bl_info = {
|
|||||||
"category": "System",
|
"category": "System",
|
||||||
"support": "COMMUNITY",
|
"support": "COMMUNITY",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__is_first_load = "operators" not in locals()
|
||||||
|
if __is_first_load:
|
||||||
|
from . import operators, gui, job_types, comms
|
||||||
|
else:
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
operators = importlib.reload(operators)
|
||||||
|
gui = importlib.reload(gui)
|
||||||
|
job_types = importlib.reload(job_types)
|
||||||
|
comms = importlib.reload(comms)
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
|
@bpy.app.handlers.persistent
|
||||||
|
def discard_global_flamenco_data(_) -> None:
|
||||||
|
job_types.discard_flamenco_data()
|
||||||
|
comms.discard_flamenco_data()
|
||||||
|
|
||||||
|
|
||||||
|
def register() -> None:
|
||||||
|
from . import dependencies
|
||||||
|
|
||||||
|
dependencies.preload_modules()
|
||||||
|
|
||||||
|
bpy.app.handlers.load_pre.append(discard_global_flamenco_data)
|
||||||
|
bpy.app.handlers.load_factory_preferences_post.append(discard_global_flamenco_data)
|
||||||
|
|
||||||
|
operators.register()
|
||||||
|
gui.register()
|
||||||
|
job_types.register()
|
||||||
|
|
||||||
|
|
||||||
|
def unregister() -> None:
|
||||||
|
discard_global_flamenco_data(None)
|
||||||
|
bpy.app.handlers.load_pre.remove(discard_global_flamenco_data)
|
||||||
|
bpy.app.handlers.load_factory_preferences_post.remove(discard_global_flamenco_data)
|
||||||
|
|
||||||
|
job_types.unregister()
|
||||||
|
gui.unregister()
|
||||||
|
operators.unregister()
|
||||||
|
57
addon/flamenco/comms.py
Normal file
57
addon/flamenco/comms.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software Foundation,
|
||||||
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
#
|
||||||
|
# ##### END GPL LICENSE BLOCK #####
|
||||||
|
|
||||||
|
# <pep8 compliant>
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
_flamenco_client = None
|
||||||
|
_log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def flamenco_api_client(manager_url="http://localhost:8080"):
|
||||||
|
"""Returns an API client for communicating with a Manager."""
|
||||||
|
global _flamenco_client
|
||||||
|
|
||||||
|
if _flamenco_client is not None:
|
||||||
|
return _flamenco_client
|
||||||
|
|
||||||
|
from . import dependencies
|
||||||
|
|
||||||
|
dependencies.preload_modules()
|
||||||
|
|
||||||
|
from flamenco import manager
|
||||||
|
|
||||||
|
configuration = manager.Configuration(host=manager_url.rstrip("/"))
|
||||||
|
_flamenco_client = manager.ApiClient(configuration)
|
||||||
|
_log.info("created API client for Manager at %s", manager_url)
|
||||||
|
|
||||||
|
return _flamenco_client
|
||||||
|
|
||||||
|
|
||||||
|
def discard_flamenco_data():
|
||||||
|
global _flamenco_client
|
||||||
|
|
||||||
|
if _flamenco_client is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
_log.info("closing Flamenco client")
|
||||||
|
_flamenco_client.close()
|
||||||
|
_flamenco_client = None
|
30
addon/flamenco/dependencies.py
Normal file
30
addon/flamenco/dependencies.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software Foundation,
|
||||||
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
#
|
||||||
|
# ##### END GPL LICENSE BLOCK #####
|
||||||
|
|
||||||
|
|
||||||
|
def preload_modules() -> None:
|
||||||
|
"""Pre-load the datetime module from a wheel so that the API can find it."""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if "dateutil" in sys.modules:
|
||||||
|
return
|
||||||
|
|
||||||
|
from flamenco import wheels
|
||||||
|
|
||||||
|
wheels.load_wheel_global("six", "six")
|
||||||
|
wheels.load_wheel_global("dateutil", "python_dateutil")
|
36
addon/flamenco/gui.py
Normal file
36
addon/flamenco/gui.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software Foundation,
|
||||||
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
#
|
||||||
|
# ##### END GPL LICENSE BLOCK #####
|
||||||
|
|
||||||
|
# <pep8 compliant>
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
|
class FLAMENCO_PT_job_submission(bpy.types.Panel):
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_category = "Export"
|
||||||
|
bl_label = "Flamenco 3"
|
||||||
|
|
||||||
|
def draw(self, context: bpy.types.Context) -> None:
|
||||||
|
layout = self.layout
|
||||||
|
col = layout.column(align=True)
|
||||||
|
col.operator("flamenco.fetch_job_types")
|
||||||
|
|
||||||
|
|
||||||
|
classes = (FLAMENCO_PT_job_submission,)
|
||||||
|
register, unregister = bpy.utils.register_classes_factory(classes)
|
256
addon/flamenco/job_types.py
Normal file
256
addon/flamenco/job_types.py
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software Foundation,
|
||||||
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
#
|
||||||
|
# ##### END GPL LICENSE BLOCK #####
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Callable, Optional
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
_log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class JobTypePropertyGroup:
|
||||||
|
@classmethod
|
||||||
|
def register_property_group(cls):
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def unregister_property_group(cls):
|
||||||
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
|
|
||||||
|
# Mapping from AvailableJobType.setting.type to a callable that converts a value
|
||||||
|
# to the appropriate type. This is necessary due to the ambiguity between floats
|
||||||
|
# and ints in JavaScript (and thus JSON).
|
||||||
|
_value_coerce = {
|
||||||
|
"bool": bool,
|
||||||
|
"string": str,
|
||||||
|
"int32": int,
|
||||||
|
"float": float,
|
||||||
|
}
|
||||||
|
|
||||||
|
_prop_types = {
|
||||||
|
"bool": bpy.props.BoolProperty,
|
||||||
|
"string": bpy.props.StringProperty,
|
||||||
|
"int32": bpy.props.IntProperty,
|
||||||
|
"float": bpy.props.FloatProperty,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# type: list[flamenco.manager.model.available_job_type.AvailableJobType]
|
||||||
|
_available_job_types = None
|
||||||
|
|
||||||
|
# Items for a bpy.props.EnumProperty()
|
||||||
|
_job_type_enum_items = []
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_available_job_types(api_client):
|
||||||
|
global _available_job_types
|
||||||
|
global _job_type_enum_items
|
||||||
|
|
||||||
|
from flamenco.manager import ApiClient
|
||||||
|
from flamenco.manager.api import jobs_api
|
||||||
|
from flamenco.manager.model.available_job_types import AvailableJobTypes
|
||||||
|
from flamenco.manager.model.available_job_type import AvailableJobType
|
||||||
|
|
||||||
|
assert isinstance(api_client, ApiClient)
|
||||||
|
|
||||||
|
job_api_instance = jobs_api.JobsApi(api_client)
|
||||||
|
response: AvailableJobTypes = job_api_instance.get_job_types()
|
||||||
|
|
||||||
|
_available_job_types = response.job_types
|
||||||
|
|
||||||
|
assert isinstance(_available_job_types, list)
|
||||||
|
if _available_job_types:
|
||||||
|
assert isinstance(_available_job_types[0], AvailableJobType)
|
||||||
|
|
||||||
|
# Convert from API response type to list suitable for an EnumProperty.
|
||||||
|
_job_type_enum_items = [
|
||||||
|
(job_type.name, job_type.label, "") for job_type in _available_job_types
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def are_job_types_available() -> bool:
|
||||||
|
"""Returns whether job types have been fetched and are available."""
|
||||||
|
return bool(_job_type_enum_items)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_property_group(job_type):
|
||||||
|
from flamenco.manager.model.available_job_type import AvailableJobType
|
||||||
|
|
||||||
|
assert isinstance(job_type, AvailableJobType)
|
||||||
|
|
||||||
|
classname = _job_type_to_class_name(job_type.name)
|
||||||
|
|
||||||
|
pg_type = type(
|
||||||
|
classname,
|
||||||
|
(JobTypePropertyGroup, bpy.types.PropertyGroup), # Base classes.
|
||||||
|
{ # Class attributes.
|
||||||
|
"job_type": job_type,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
pg_type.__annotations__ = {}
|
||||||
|
|
||||||
|
print(f"\033[38;5;214m{job_type.label}\033[0m ({job_type.name})")
|
||||||
|
for setting in job_type.settings:
|
||||||
|
prop = _create_property(job_type, setting)
|
||||||
|
pg_type.__annotations__[setting.key] = prop
|
||||||
|
|
||||||
|
pg_type.register_property_group()
|
||||||
|
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
print(pg_type)
|
||||||
|
pprint(pg_type.__annotations__)
|
||||||
|
|
||||||
|
return pg_type
|
||||||
|
|
||||||
|
|
||||||
|
def _create_property(job_type, setting):
|
||||||
|
from flamenco.manager.model.available_job_setting import AvailableJobSetting
|
||||||
|
from flamenco.manager.model_utils import ModelSimple
|
||||||
|
|
||||||
|
assert isinstance(setting, AvailableJobSetting)
|
||||||
|
|
||||||
|
print(f" - {setting.key:23} type: {setting.type!r:10}", end="")
|
||||||
|
|
||||||
|
# Special case: a string property with 'choices' setting. This should translate to an EnumProperty
|
||||||
|
prop_type, prop_kwargs = _find_prop_type(job_type, setting)
|
||||||
|
|
||||||
|
assert isinstance(setting.type, ModelSimple)
|
||||||
|
value_coerce = _value_coerce[setting.type.to_str()]
|
||||||
|
_set_if_available(prop_kwargs, setting, "default", transform=value_coerce)
|
||||||
|
_set_if_available(prop_kwargs, setting, "subtype", transform=_transform_subtype)
|
||||||
|
print()
|
||||||
|
|
||||||
|
prop_name = _job_setting_key_to_label(setting.key)
|
||||||
|
prop = prop_type(name=prop_name, **prop_kwargs)
|
||||||
|
return prop
|
||||||
|
|
||||||
|
|
||||||
|
def _find_prop_type(job_type, setting):
|
||||||
|
# The special case is a 'string' property with 'choices' setting, which
|
||||||
|
# should translate to an EnumProperty. All others just map to a simple
|
||||||
|
# bpy.props type.
|
||||||
|
|
||||||
|
setting_type = setting.type.to_str()
|
||||||
|
|
||||||
|
if "choices" not in setting:
|
||||||
|
return _prop_types[setting_type], {}
|
||||||
|
|
||||||
|
if setting_type != "string":
|
||||||
|
# There was a 'choices' key, but not for a supported type. Ignore the
|
||||||
|
# choices but complain about it.
|
||||||
|
_log.warn(
|
||||||
|
"job type %r, setting %r: only string choices are supported, but property is of type %s",
|
||||||
|
job_type.name,
|
||||||
|
setting.key,
|
||||||
|
setting_type,
|
||||||
|
)
|
||||||
|
return _prop_types[setting_type], {}
|
||||||
|
|
||||||
|
choices = setting.choices
|
||||||
|
enum_items = [(choice, choice, "") for choice in choices]
|
||||||
|
return bpy.props.EnumProperty, {"items": enum_items}
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_subtype(subtype: object) -> str:
|
||||||
|
uppercase = str(subtype).upper()
|
||||||
|
if uppercase == "HASHED_FILE_PATH":
|
||||||
|
# Flamenco has a concept of 'hashed file path' subtype, but Blender does not.
|
||||||
|
return "FILE_PATH"
|
||||||
|
return uppercase
|
||||||
|
|
||||||
|
|
||||||
|
def _job_type_to_class_name(job_type_name: str) -> str:
|
||||||
|
"""Change 'job-type-name' to 'JobTypeName'.
|
||||||
|
|
||||||
|
>>> _job_type_to_class_name('job-type-name')
|
||||||
|
'JobTypeName'
|
||||||
|
"""
|
||||||
|
return job_type_name.title().replace("-", "")
|
||||||
|
|
||||||
|
|
||||||
|
def _job_setting_key_to_label(setting_key: str) -> str:
|
||||||
|
"""Change 'some_setting_key' to 'Some Setting Key'.
|
||||||
|
|
||||||
|
>>> _job_setting_key_to_label('some_setting_key')
|
||||||
|
'Some Setting Key'
|
||||||
|
"""
|
||||||
|
return setting_key.title().replace("_", " ")
|
||||||
|
|
||||||
|
|
||||||
|
def _set_if_available(
|
||||||
|
some_dict: dict,
|
||||||
|
setting,
|
||||||
|
key: str,
|
||||||
|
transform: Optional[Callable] = None,
|
||||||
|
):
|
||||||
|
"""some_dict[key] = setting.key, if that key is available.
|
||||||
|
|
||||||
|
>>> class Setting:
|
||||||
|
... pass
|
||||||
|
>>> setting = Setting()
|
||||||
|
>>> setting.exists = 47
|
||||||
|
>>> d = {}
|
||||||
|
>>> _set_if_available(d, setting, "exists")
|
||||||
|
>>> _set_if_available(d, setting, "other")
|
||||||
|
>>> d
|
||||||
|
{'exists': 47}
|
||||||
|
>>> d = {}
|
||||||
|
>>> _set_if_available(d, setting, "exists", transform=lambda v: str(v))
|
||||||
|
>>> d
|
||||||
|
{'exists': '47'}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
value = getattr(setting, key)
|
||||||
|
except AttributeError:
|
||||||
|
return
|
||||||
|
|
||||||
|
if transform is None:
|
||||||
|
some_dict[key] = value
|
||||||
|
else:
|
||||||
|
some_dict[key] = transform(value)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_job_types_enum_items(dummy1, dummy2):
|
||||||
|
return _job_type_enum_items
|
||||||
|
|
||||||
|
|
||||||
|
def discard_flamenco_data():
|
||||||
|
if _available_job_types:
|
||||||
|
_available_job_types.clear()
|
||||||
|
if _job_type_enum_items:
|
||||||
|
_job_type_enum_items.clear()
|
||||||
|
|
||||||
|
|
||||||
|
def register() -> None:
|
||||||
|
bpy.types.WindowManager.flamenco3_job_types = bpy.props.EnumProperty(
|
||||||
|
name="Job Type",
|
||||||
|
items=_get_job_types_enum_items,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister() -> None:
|
||||||
|
del bpy.types.WindowManager.flamenco3_job_types
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
print(doctest.testmod())
|
@ -76,7 +76,7 @@ class ApiClient(object):
|
|||||||
self.default_headers[header_name] = header_value
|
self.default_headers[header_name] = header_value
|
||||||
self.cookie = cookie
|
self.cookie = cookie
|
||||||
# Set default User-Agent.
|
# Set default User-Agent.
|
||||||
self.user_agent = 'OpenAPI-Generator/3.0/python'
|
self.user_agent = 'Flamenco/3.0 (Blender add-on)'
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
return self
|
return self
|
||||||
|
48
addon/flamenco/operators.py
Normal file
48
addon/flamenco/operators.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software Foundation,
|
||||||
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
#
|
||||||
|
# ##### END GPL LICENSE BLOCK #####
|
||||||
|
|
||||||
|
# <pep8 compliant>
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
|
class FLAMENCO_OT_fetch_job_types(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]:
|
||||||
|
from . import comms, job_types
|
||||||
|
|
||||||
|
# Getting the client also loads the dependencies, so we can only import
|
||||||
|
# API stuff after it.
|
||||||
|
api_client = comms.flamenco_api_client()
|
||||||
|
|
||||||
|
from flamenco.manager import ApiException
|
||||||
|
|
||||||
|
try:
|
||||||
|
job_types.fetch_available_job_types(api_client)
|
||||||
|
except ApiException as ex:
|
||||||
|
self.report({"ERROR"}, "Error getting job types: %s" % ex)
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
classes = (FLAMENCO_OT_fetch_job_types,)
|
||||||
|
register, unregister = bpy.utils.register_classes_factory(classes)
|
150
addon/flamenco/wheels/__init__.py
Normal file
150
addon/flamenco/wheels/__init__.py
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software Foundation,
|
||||||
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
#
|
||||||
|
# ##### END GPL LICENSE BLOCK #####
|
||||||
|
|
||||||
|
"""External dependencies loader."""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
_my_dir = Path(__file__).parent
|
||||||
|
_log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def load_wheel(module_name: str, fname_prefix: str) -> ModuleType:
|
||||||
|
"""Loads a wheel from 'fname_prefix*.whl', unless the named module can be imported.
|
||||||
|
|
||||||
|
This allows us to use system-installed packages before falling back to the shipped wheels.
|
||||||
|
This is useful for development, less so for deployment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
module = __import__(module_name)
|
||||||
|
except ImportError as ex:
|
||||||
|
_log.debug("Unable to import %s directly, will try wheel: %s", module_name, ex)
|
||||||
|
else:
|
||||||
|
_log.debug(
|
||||||
|
"Was able to load %s from %s, no need to load wheel %s",
|
||||||
|
module_name,
|
||||||
|
module.__file__,
|
||||||
|
fname_prefix,
|
||||||
|
)
|
||||||
|
return module
|
||||||
|
|
||||||
|
wheel = _wheel_filename(fname_prefix)
|
||||||
|
|
||||||
|
# Load the module from the wheel file. Keep a backup of sys.path so that it
|
||||||
|
# can be restored later. This should ensure that future import statements
|
||||||
|
# cannot find this wheel file, increasing the separation of dependencies of
|
||||||
|
# this add-on from other add-ons.
|
||||||
|
with _sys_path_mod_backup(wheel):
|
||||||
|
try:
|
||||||
|
module = __import__(module_name)
|
||||||
|
except ImportError as ex:
|
||||||
|
raise ImportError(
|
||||||
|
"Unable to load %r from %s: %s" % (module_name, wheel, ex)
|
||||||
|
) from None
|
||||||
|
|
||||||
|
_log.debug("Loaded %s from %s", module_name, module.__file__)
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
def load_wheel_global(module_name: str, fname_prefix: str) -> ModuleType:
|
||||||
|
"""Loads a wheel from 'fname_prefix*.whl', unless the named module can be imported.
|
||||||
|
|
||||||
|
This allows us to use system-installed packages before falling back to the shipped wheels.
|
||||||
|
This is useful for development, less so for deployment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
module = __import__(module_name)
|
||||||
|
except ImportError as ex:
|
||||||
|
_log.debug("Unable to import %s directly, will try wheel: %s", module_name, ex)
|
||||||
|
else:
|
||||||
|
_log.debug(
|
||||||
|
"Was able to load %s from %s, no need to load wheel %s",
|
||||||
|
module_name,
|
||||||
|
module.__file__,
|
||||||
|
fname_prefix,
|
||||||
|
)
|
||||||
|
return module
|
||||||
|
|
||||||
|
wheel = _wheel_filename(fname_prefix)
|
||||||
|
|
||||||
|
wheel_filepath = str(wheel)
|
||||||
|
if wheel_filepath not in sys.path:
|
||||||
|
sys.path.insert(0, wheel_filepath)
|
||||||
|
|
||||||
|
try:
|
||||||
|
module = __import__(module_name)
|
||||||
|
except ImportError as ex:
|
||||||
|
raise ImportError(
|
||||||
|
"Unable to load %r from %s: %s" % (module_name, wheel, ex)
|
||||||
|
) from None
|
||||||
|
|
||||||
|
_log.debug("Globally loaded %s from %s", module_name, module.__file__)
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _sys_path_mod_backup(wheel_file: Path):
|
||||||
|
old_syspath = sys.path[:]
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.path.insert(0, str(wheel_file))
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
# Restore without assigning new instances. That way references held by
|
||||||
|
# other code will stay valid.
|
||||||
|
|
||||||
|
sys.path[:] = old_syspath
|
||||||
|
|
||||||
|
|
||||||
|
def _wheel_filename(fname_prefix: str) -> Path:
|
||||||
|
path_pattern = "%s*.whl" % fname_prefix
|
||||||
|
wheels: list[Path] = list(_my_dir.glob(path_pattern))
|
||||||
|
if not wheels:
|
||||||
|
raise RuntimeError("Unable to find wheel at %r" % path_pattern)
|
||||||
|
|
||||||
|
# If there are multiple wheels that match, load the last-modified one.
|
||||||
|
# Alphabetical sorting isn't going to cut it since BAT 1.10 was released.
|
||||||
|
def modtime(filepath: Path) -> int:
|
||||||
|
return filepath.stat().st_mtime
|
||||||
|
|
||||||
|
wheels.sort(key=modtime)
|
||||||
|
return wheels[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def preload_dependencies() -> None:
|
||||||
|
"""Pre-load the datetime module from a wheel so that the API can find it."""
|
||||||
|
|
||||||
|
# The generated Flamenco Manager API uses the `dateutil.parser.parse` function.
|
||||||
|
# It needs to be able to do `from dateutil.parser import parse`.
|
||||||
|
load_wheel_global("six", "six")
|
||||||
|
load_wheel_global("dateutil", "python_dateutil")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
wheel = _wheel_filename("python_dateutil")
|
||||||
|
print(f"Wheel: {wheel}")
|
||||||
|
module = load_wheel("dateutil", "python_dateutil")
|
||||||
|
print(f"module: {module}")
|
BIN
addon/flamenco/wheels/python_dateutil-2.8.2-py2.py3-none-any.whl
Normal file
BIN
addon/flamenco/wheels/python_dateutil-2.8.2-py2.py3-none-any.whl
Normal file
Binary file not shown.
BIN
addon/flamenco/wheels/six-1.16.0-py2.py3-none-any.whl
Normal file
BIN
addon/flamenco/wheels/six-1.16.0-py2.py3-none-any.whl
Normal file
Binary file not shown.
@ -11,6 +11,11 @@ PKG_NAME=flamenco.manager
|
|||||||
PKG_VERSION=3.0
|
PKG_VERSION=3.0
|
||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
|
# The generator doesn't consistently overwrite existing files, nor does it
|
||||||
|
# remove no-longer-generated files.
|
||||||
|
rm -rf ./flamenco/manager
|
||||||
|
|
||||||
java -jar openapi-generator-cli.jar \
|
java -jar openapi-generator-cli.jar \
|
||||||
generate \
|
generate \
|
||||||
-i ../pkg/api/flamenco-manager.yaml \
|
-i ../pkg/api/flamenco-manager.yaml \
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import flamenco.manager
|
||||||
import flamenco3_client
|
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
from flamenco3_client.api import jobs_api
|
from flamenco.manager.api import jobs_api
|
||||||
from flamenco3_client.model.available_job_types import AvailableJobTypes
|
from flamenco.manager.model.available_job_types import AvailableJobTypes
|
||||||
from flamenco3_client.model.available_job_type import AvailableJobType
|
from flamenco.manager.model.available_job_type import AvailableJobType
|
||||||
from flamenco3_client.model.error import Error
|
from flamenco.manager.model.error import Error
|
||||||
from flamenco3_client.model.job import Job
|
from flamenco.manager.model.job import Job
|
||||||
from flamenco3_client.model.submitted_job import SubmittedJob
|
from flamenco.manager.model.submitted_job import SubmittedJob
|
||||||
|
|
||||||
# Defining the host is optional and defaults to http://localhost
|
# Defining the host is optional and defaults to http://localhost
|
||||||
# See configuration.py for a list of all supported configuration parameters.
|
# See configuration.py for a list of all supported configuration parameters.
|
||||||
configuration = flamenco3_client.Configuration(host="http://localhost:8080")
|
configuration = flamenco.manager.Configuration(host="http://localhost:8080")
|
||||||
|
|
||||||
|
|
||||||
# Enter a context with an instance of the API client
|
# Enter a context with an instance of the API client
|
||||||
with flamenco3_client.ApiClient(configuration) as api_client:
|
with flamenco.manager.ApiClient(configuration) as api_client:
|
||||||
job_api_instance = jobs_api.JobsApi(api_client)
|
job_api_instance = jobs_api.JobsApi(api_client)
|
||||||
|
|
||||||
response: AvailableJobTypes = job_api_instance.get_job_types()
|
response: AvailableJobTypes = job_api_instance.get_job_types()
|
||||||
@ -35,5 +34,5 @@ with flamenco3_client.ApiClient(configuration) as api_client:
|
|||||||
# # Fetch info about the job.
|
# # Fetch info about the job.
|
||||||
# api_response = job_api_instance.fetch_job(job_id)
|
# api_response = job_api_instance.fetch_job(job_id)
|
||||||
# pprint(api_response)
|
# pprint(api_response)
|
||||||
# except flamenco3_client.ApiException as e:
|
# except flamenco.manager.ApiException as e:
|
||||||
# print("Exception when calling JobsApi->fetch_job: %s\n" % e)
|
# print("Exception when calling JobsApi->fetch_job: %s\n" % e)
|
||||||
|
36
addon/test_jobtypes.py
Normal file
36
addon/test_jobtypes.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
my_dir = Path(__file__).parent
|
||||||
|
sys.path.append(str(my_dir))
|
||||||
|
|
||||||
|
|
||||||
|
import atexit
|
||||||
|
from flamenco import dependencies, job_types
|
||||||
|
|
||||||
|
dependencies.preload_modules()
|
||||||
|
|
||||||
|
import flamenco.manager
|
||||||
|
|
||||||
|
from flamenco.manager.api import jobs_api
|
||||||
|
from flamenco.manager.model.available_job_types import AvailableJobTypes
|
||||||
|
|
||||||
|
# Defining the host is optional and defaults to http://localhost
|
||||||
|
# See configuration.py for a list of all supported configuration parameters.
|
||||||
|
configuration = flamenco.manager.Configuration(host="http://localhost:8080")
|
||||||
|
|
||||||
|
|
||||||
|
api_client = flamenco.manager.ApiClient(configuration)
|
||||||
|
atexit.register(api_client.close)
|
||||||
|
|
||||||
|
job_api_instance = jobs_api.JobsApi(api_client)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response: AvailableJobTypes = job_api_instance.get_job_types()
|
||||||
|
except flamenco.manager.ApiException as ex:
|
||||||
|
raise SystemExit("Exception when calling JobsApi->fetch_job: %s" % ex)
|
||||||
|
|
||||||
|
job_type = next(jt for jt in response.job_types if jt.name == "simple-blender-render")
|
||||||
|
pg = job_types.generate_property_group(job_type)
|
Loading…
x
Reference in New Issue
Block a user