Addon: cleanup, split propgroup generator from job type code
This commit is contained in:
parent
2e78e00a0b
commit
ad3750dfbe
@ -6,37 +6,10 @@ from typing import TYPE_CHECKING, Callable, Optional, Union
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
|
from . import job_types_propgroup
|
||||||
|
|
||||||
_log = logging.getLogger(__name__)
|
_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,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from flamenco.manager.models import AvailableJobType, SubmittedJob, JobSettings
|
from flamenco.manager.models import AvailableJobType, SubmittedJob, JobSettings
|
||||||
|
|
||||||
@ -51,7 +24,7 @@ _job_type_enum_items: list[
|
|||||||
Union[tuple[str, str, str], tuple[str, str, str, int, int]]
|
Union[tuple[str, str, str], tuple[str, str, str, int, int]]
|
||||||
] = []
|
] = []
|
||||||
|
|
||||||
_selected_job_type_propgroup: Optional[JobTypePropertyGroup] = None
|
_selected_job_type_propgroup: Optional[job_types_propgroup.JobTypePropertyGroup] = None
|
||||||
|
|
||||||
|
|
||||||
def fetch_available_job_types(api_client):
|
def fetch_available_job_types(api_client):
|
||||||
@ -99,13 +72,16 @@ def update_job_type_properties(scene: bpy.types.Scene) -> None:
|
|||||||
from flamenco.manager.model.available_job_type import AvailableJobType
|
from flamenco.manager.model.available_job_type import AvailableJobType
|
||||||
|
|
||||||
job_type = active_job_type(scene)
|
job_type = active_job_type(scene)
|
||||||
|
_clear_job_type_propgroup()
|
||||||
|
|
||||||
|
if job_type is None:
|
||||||
|
return
|
||||||
|
|
||||||
assert isinstance(job_type, AvailableJobType), "did not expect type %r" % type(
|
assert isinstance(job_type, AvailableJobType), "did not expect type %r" % type(
|
||||||
job_type
|
job_type
|
||||||
)
|
)
|
||||||
|
|
||||||
_clear_job_type_propgroup()
|
pg = job_types_propgroup.generate(job_type)
|
||||||
|
|
||||||
pg = generate_property_group(job_type)
|
|
||||||
pg.register_property_group()
|
pg.register_property_group()
|
||||||
_selected_job_type_propgroup = pg
|
_selected_job_type_propgroup = pg
|
||||||
|
|
||||||
@ -199,151 +175,6 @@ def active_job_type(scene: bpy.types.Scene) -> Optional[_AvailableJobType]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def generate_property_group(job_type):
|
|
||||||
"""Create a PropertyGroup for the job type.
|
|
||||||
|
|
||||||
Does not register the property group.
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
|
|
||||||
assert issubclass(pg_type, JobTypePropertyGroup), "did not expect type %r" % type(
|
|
||||||
pg_type
|
|
||||||
)
|
|
||||||
|
|
||||||
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, "description")
|
|
||||||
_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[object, object],
|
|
||||||
setting: object,
|
|
||||||
key: str,
|
|
||||||
transform: Optional[Callable[[object], object]] = None,
|
|
||||||
) -> 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):
|
def _get_job_types_enum_items(dummy1, dummy2):
|
||||||
return _job_type_enum_items
|
return _job_type_enum_items
|
||||||
|
|
||||||
@ -362,6 +193,10 @@ def register() -> None:
|
|||||||
update=_update_job_type,
|
update=_update_job_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bpy.types.Scene.flamenco_available_job_types_json = bpy.props.StringProperty(
|
||||||
|
name="Available Job Types",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def unregister() -> None:
|
def unregister() -> None:
|
||||||
del bpy.types.Scene.flamenco_job_type
|
del bpy.types.Scene.flamenco_job_type
|
||||||
|
205
addon/flamenco/job_types_propgroup.py
Normal file
205
addon/flamenco/job_types_propgroup.py
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
"""Flamenco Job Type to bpy.props.PropertyGroup conversion."""
|
||||||
|
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import TYPE_CHECKING, Callable, Optional, Any
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
_log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from flamenco.manager.models import AvailableJobType, AvailableJobSetting
|
||||||
|
else:
|
||||||
|
AvailableJobType = object
|
||||||
|
AvailableJobSetting = object
|
||||||
|
|
||||||
|
|
||||||
|
class JobTypePropertyGroup:
|
||||||
|
"""Mix-in class for PropertyGroups for Flamenco Job Types.
|
||||||
|
|
||||||
|
Use `generate(job_type: AvailableJobType)` to create such a subclass.
|
||||||
|
"""
|
||||||
|
|
||||||
|
job_type: AvailableJobType
|
||||||
|
"""The job type passed to `generate(job_type)`."""
|
||||||
|
|
||||||
|
@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,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def generate(job_type: AvailableJobType) -> type[JobTypePropertyGroup]:
|
||||||
|
"""Create a PropertyGroup for the job type.
|
||||||
|
|
||||||
|
Does not register the property group.
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
assert issubclass(pg_type, JobTypePropertyGroup), "did not expect type %r" % type(
|
||||||
|
pg_type
|
||||||
|
)
|
||||||
|
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
print(pg_type)
|
||||||
|
pprint(pg_type.__annotations__)
|
||||||
|
|
||||||
|
return pg_type
|
||||||
|
|
||||||
|
|
||||||
|
def _create_property(job_type: AvailableJobType, setting: AvailableJobSetting) -> Any:
|
||||||
|
"""Create a bpy.props property for the given job setting.
|
||||||
|
|
||||||
|
Depending on the setting, will be a StringProperty, EnumProperty, FloatProperty, etc.
|
||||||
|
"""
|
||||||
|
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, "description")
|
||||||
|
_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: AvailableJobType, setting: AvailableJobSetting
|
||||||
|
) -> tuple[Any, dict[str, Any]]:
|
||||||
|
"""Return a tuple (bpy.props.XxxProperty, kwargs for construction)."""
|
||||||
|
|
||||||
|
# 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[object, object],
|
||||||
|
setting: object,
|
||||||
|
key: str,
|
||||||
|
transform: Optional[Callable[[object], object]] = None,
|
||||||
|
) -> 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)
|
@ -8,7 +8,7 @@ sys.path.append(str(my_dir))
|
|||||||
|
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
from flamenco import dependencies, job_types
|
from flamenco import dependencies, job_types_propgroup as jt_propgroup
|
||||||
|
|
||||||
dependencies.preload_modules()
|
dependencies.preload_modules()
|
||||||
|
|
||||||
@ -33,4 +33,4 @@ except flamenco.manager.ApiException as ex:
|
|||||||
raise SystemExit("Exception when calling JobsApi->fetch_job: %s" % 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")
|
job_type = next(jt for jt in response.job_types if jt.name == "simple-blender-render")
|
||||||
pg = job_types.generate_property_group(job_type)
|
pg = jt_propgroup.generate(job_type)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user