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
|
||||
|
||||
from . import job_types_propgroup
|
||||
|
||||
_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:
|
||||
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]]
|
||||
] = []
|
||||
|
||||
_selected_job_type_propgroup: Optional[JobTypePropertyGroup] = None
|
||||
_selected_job_type_propgroup: Optional[job_types_propgroup.JobTypePropertyGroup] = None
|
||||
|
||||
|
||||
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
|
||||
|
||||
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(
|
||||
job_type
|
||||
)
|
||||
|
||||
_clear_job_type_propgroup()
|
||||
|
||||
pg = generate_property_group(job_type)
|
||||
pg = job_types_propgroup.generate(job_type)
|
||||
pg.register_property_group()
|
||||
_selected_job_type_propgroup = pg
|
||||
|
||||
@ -199,151 +175,6 @@ def active_job_type(scene: bpy.types.Scene) -> Optional[_AvailableJobType]:
|
||||
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):
|
||||
return _job_type_enum_items
|
||||
|
||||
@ -362,6 +193,10 @@ def register() -> None:
|
||||
update=_update_job_type,
|
||||
)
|
||||
|
||||
bpy.types.Scene.flamenco_available_job_types_json = bpy.props.StringProperty(
|
||||
name="Available Job Types",
|
||||
)
|
||||
|
||||
|
||||
def unregister() -> None:
|
||||
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
|
||||
from flamenco import dependencies, job_types
|
||||
from flamenco import dependencies, job_types_propgroup as jt_propgroup
|
||||
|
||||
dependencies.preload_modules()
|
||||
|
||||
@ -33,4 +33,4 @@ 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)
|
||||
pg = jt_propgroup.generate(job_type)
|
||||
|
Loading…
x
Reference in New Issue
Block a user