Compute render output path when evaluating job settings

Compute render output path when evaluating job settings, which is done
within the Flamenco add-on, instead of in the job compiler script. This
allows the UI to show the render path, rather than it only being known
after the job has been submitted.
This commit is contained in:
Sybren A. Stüvel 2022-03-15 16:56:44 +01:00
parent 7bfde1df0b
commit 09a476e11a
15 changed files with 287 additions and 108 deletions

View File

@ -1,8 +1,22 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# <pep8 compliant>
from typing import Optional, TYPE_CHECKING
from flamenco import job_submission
from flamenco.job_types_propgroup import JobTypePropertyGroup
import bpy
if TYPE_CHECKING:
from flamenco.manager.models import (
AvailableJobSetting as _AvailableJobSetting,
SubmittedJob as _SubmittedJob,
)
else:
_AvailableJobSetting = object
_SubmittedJob = object
class FLAMENCO_PT_job_submission(bpy.types.Panel):
bl_space_type = "PROPERTIES"
@ -10,6 +24,10 @@ class FLAMENCO_PT_job_submission(bpy.types.Panel):
bl_context = "output"
bl_label = "Flamenco 3"
# A temporary job can be constructed so that dynamic, read-only properties can be evaluated.
# This is only scoped to a single draw() call.
job: Optional[_SubmittedJob] = None
def draw(self, context: bpy.types.Context) -> None:
from . import job_types
@ -24,14 +42,20 @@ class FLAMENCO_PT_job_submission(bpy.types.Panel):
row.prop(context.scene, "flamenco_job_type", text="")
row.operator("flamenco.fetch_job_types", text="", icon="FILE_REFRESH")
layout.separator()
col = layout.column(align=True)
col.prop(context.scene, "flamenco_job_name", text="Job Name")
layout.separator()
self.draw_job_settings(context, layout.column(align=True))
layout.separator()
col = layout.column(align=True)
col.prop(context.scene, "flamenco_job_name", text="Job Name")
self.draw_flamenco_status(context, layout)
self.job = None
def draw_job_settings(
self, context: bpy.types.Context, layout: bpy.types.UILayout
) -> None:
@ -48,17 +72,47 @@ class FLAMENCO_PT_job_submission(bpy.types.Panel):
layout.label(text="Job Settings:")
layout.use_property_split = True
for setting in job_type.settings:
if not setting.get("visible", True):
continue
row = layout.row(align=True)
row.prop(propgroup, setting.key)
setting_eval = setting.get("eval", "")
if setting_eval:
props = row.operator(
"flamenco.eval_setting", text="", icon="SCRIPTPLUGINS"
)
props.setting_key = setting.key
props.setting_eval = setting_eval
self.draw_setting(context, layout, propgroup, setting)
def draw_setting(
self,
context: bpy.types.Context,
layout: bpy.types.UILayout,
propgroup: JobTypePropertyGroup,
setting: _AvailableJobSetting,
) -> None:
if not setting.get("visible", True):
return
if setting.get("editable", True):
self.draw_setting_editable(layout, propgroup, setting)
else:
self.draw_setting_readonly(context, layout, propgroup, setting)
def draw_setting_editable(
self,
layout: bpy.types.UILayout,
propgroup: JobTypePropertyGroup,
setting: _AvailableJobSetting,
) -> None:
row = layout.row(align=True)
row.prop(propgroup, setting.key)
setting_eval = setting.get("eval", "")
if not setting_eval:
return
props = row.operator("flamenco.eval_setting", text="", icon="SCRIPTPLUGINS")
props.setting_key = setting.key
props.setting_eval = setting_eval
def draw_setting_readonly(
self,
context: bpy.types.Context,
layout: bpy.types.UILayout,
propgroup: JobTypePropertyGroup,
setting: _AvailableJobSetting,
) -> None:
layout.prop(propgroup, setting.key)
def draw_flamenco_status(
self, context: bpy.types.Context, layout: bpy.types.UILayout

View File

@ -83,7 +83,9 @@ def eval_hidden_settings(
setting_eval = setting.get("eval", "")
if setting_eval:
value = JobTypePropertyGroup.eval_setting(context, setting_eval)
value = JobTypePropertyGroup.eval_setting(
context, job, setting.key, setting_eval
)
elif "default" in setting:
value = setting.default
else:

View File

@ -3,7 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import logging
from typing import TYPE_CHECKING, Callable, Optional, Any
import pprint
from pathlib import Path
from typing import TYPE_CHECKING, Callable, Optional, Any, Union
import bpy
@ -14,11 +16,38 @@ if TYPE_CHECKING:
AvailableJobType as _AvailableJobType,
AvailableJobSetting as _AvailableJobSetting,
JobSettings as _JobSettings,
SubmittedJob as _SubmittedJob,
)
else:
_AvailableJobType = object
_AvailableJobSetting = object
_JobSettings = object
_SubmittedJob = object
class SettingEvalError(Exception):
"""Raised when the evaluation of a setting fails."""
def __init__(
self,
setting_key: str,
setting_eval: str,
eval_locals: dict[str, Any],
exception: Exception,
) -> None:
self.setting_key = setting_key
self.setting_eval = setting_eval
self.locals = eval_locals.copy()
self.exception = exception
print("Evaluation error of setting %r:" % setting_key)
print("Expression: %s" % setting_eval)
print("Local variables:")
pprint.pprint(eval_locals)
print("Exception: %s" % exception)
msg = "Evaluation error of setting %r: %s" % (setting_key, exception)
super().__init__(msg)
class JobTypePropertyGroup:
@ -47,24 +76,74 @@ class JobTypePropertyGroup:
return js
def label(self, setting_key: str) -> str:
"""Return the UI label for this setting."""
return self.bl_rna.properties[setting_key].name
def eval_and_assign(
self, context: bpy.types.Context, setting_key: str, setting_eval: str
self,
context: bpy.types.Context,
job: _SubmittedJob,
setting_key: str,
setting_eval: str,
) -> None:
"""Evaluate `setting_eval` and assign the result to the job setting."""
value = self.eval_setting(context, setting_eval)
value = self.eval_setting(context, setting_key, setting_eval)
setattr(self, setting_key, value)
@staticmethod
def eval_setting(context: bpy.types.Context, setting_eval: str) -> Any:
def eval_setting(
self,
context: bpy.types.Context,
setting_key: str,
setting_eval: str,
) -> Any:
"""Evaluate `setting_eval` and return the result."""
eval_globals = {
eval_locals = {
"bpy": bpy,
"C": context,
"jobname": context.scene.flamenco_job_name,
"Path": Path,
"last_n_dir_parts": self.last_n_dir_parts,
"settings": self,
}
value = eval(setting_eval, eval_globals, {})
try:
value = eval(setting_eval, {}, eval_locals)
except Exception as ex:
raise SettingEvalError(setting_key, setting_eval, eval_locals, ex) from ex
return value
@staticmethod
def last_n_dir_parts(n: int, filepath: Union[str, Path, None] = None) -> Path:
"""Return the last `n` parts of the directory of `filepath`.
If `n` is 0, returns an empty `Path()`.
If `filepath` is None, uses bpy.data.filepath instead.
>>> str(lastNDirParts(2, "/path/to/some/file.blend"))
"to/some"
Always returns a relative path:
>>> str(lastNDirParts(200, "C:\\path\\to\\some\\file.blend"))
"path\\to\\some"
"""
if n <= 0:
return Path()
if filepath is None:
filepath = Path(bpy.data.filepath)
elif isinstance(filepath, str):
filepath = Path(filepath)
dirpath = filepath.parent
if n >= len(dirpath.parts):
all_parts = dirpath.relative_to(dirpath.anchor)
return all_parts
subset = Path(*dirpath.parts[-n:])
return subset
# 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
@ -137,6 +216,19 @@ def _create_property(job_type: _AvailableJobType, setting: _AvailableJobSetting)
# Remove the 'ANIMATABLE' option.
prop_kwargs.setdefault("options", set())
# Add any extra arguments.
propargs = setting.get("propargs", {})
coerce_keys = {"min", "max"}
for key, value in propargs.items():
if key in coerce_keys:
propargs[key] = value_coerce(value)
prop_kwargs.update(propargs)
# Construct a getter if it's a non-editable property. By having a getter and
# not a setter, the property automatically becomes read-only in the UI.
if not setting.get("editable", True):
prop_kwargs["get"] = _create_prop_getter(job_type, setting)
prop_name = _job_setting_key_to_label(setting.key)
prop = prop_type(name=prop_name, **prop_kwargs)
return prop
@ -229,3 +321,22 @@ def _set_if_available(
some_dict[key] = value
else:
some_dict[key] = transform(value)
def _create_prop_getter(
job_type: _AvailableJobType, setting: _AvailableJobSetting
) -> Callable[[JobTypePropertyGroup], Any]:
def evaluate_setting(propgroup: JobTypePropertyGroup) -> Any:
value = propgroup.eval_setting(
bpy.context,
setting.key,
setting.eval,
)
return value
def default_value(propgroup: JobTypePropertyGroup) -> Any:
return setting.default
if setting.get("eval"):
return evaluate_setting
return default_value

View File

@ -10,7 +10,7 @@
"""
__version__ = "4196460c-dirty"
__version__ = "7bfde1df-dirty"
# import ApiClient
from flamenco.manager.api_client import ApiClient

View File

@ -76,7 +76,7 @@ class ApiClient(object):
self.default_headers[header_name] = header_value
self.cookie = cookie
# Set default User-Agent.
self.user_agent = 'Flamenco/4196460c-dirty (Blender add-on)'
self.user_agent = 'Flamenco/7bfde1df-dirty (Blender add-on)'
def __enter__(self):
return self

View File

@ -404,7 +404,7 @@ conf = flamenco.manager.Configuration(
"OS: {env}\n"\
"Python Version: {pyversion}\n"\
"Version of the API: 1.0.0\n"\
"SDK Package Version: 4196460c-dirty".\
"SDK Package Version: 7bfde1df-dirty".\
format(env=sys.platform, pyversion=sys.version)
def get_host_settings(self):

View File

@ -9,6 +9,7 @@ Name | Type | Description | Notes
**type** | [**AvailableJobSettingType**](AvailableJobSettingType.md) | |
**subtype** | [**AvailableJobSettingSubtype**](AvailableJobSettingSubtype.md) | | [optional]
**choices** | **[str]** | When given, limit the valid values to these choices. Only usable with string type. | [optional]
**propargs** | **{str: (bool, date, datetime, dict, float, int, list, str, none_type)}** | Any extra arguments to the bpy.props.SomeProperty() call used to create this property. | [optional]
**description** | **bool, date, datetime, dict, float, int, list, str, none_type** | The description/tooltip shown in the user interface. | [optional]
**default** | **bool, date, datetime, dict, float, int, list, str, none_type** | The default value shown to the user when determining this setting. | [optional]
**eval** | **str** | Python expression to be evaluated in order to determine the default value for this setting. | [optional]

View File

@ -93,6 +93,7 @@ class AvailableJobSetting(ModelNormal):
'type': (AvailableJobSettingType,), # noqa: E501
'subtype': (AvailableJobSettingSubtype,), # noqa: E501
'choices': ([str],), # noqa: E501
'propargs': ({str: (bool, date, datetime, dict, float, int, list, str, none_type)},), # noqa: E501
'description': (bool, date, datetime, dict, float, int, list, str, none_type,), # noqa: E501
'default': (bool, date, datetime, dict, float, int, list, str, none_type,), # noqa: E501
'eval': (str,), # noqa: E501
@ -111,6 +112,7 @@ class AvailableJobSetting(ModelNormal):
'type': 'type', # noqa: E501
'subtype': 'subtype', # noqa: E501
'choices': 'choices', # noqa: E501
'propargs': 'propargs', # noqa: E501
'description': 'description', # noqa: E501
'default': 'default', # noqa: E501
'eval': 'eval', # noqa: E501
@ -166,6 +168,7 @@ class AvailableJobSetting(ModelNormal):
_visited_composed_classes = (Animal,)
subtype (AvailableJobSettingSubtype): [optional] # noqa: E501
choices ([str]): When given, limit the valid values to these choices. Only usable with string type.. [optional] # noqa: E501
propargs ({str: (bool, date, datetime, dict, float, int, list, str, none_type)}): Any extra arguments to the bpy.props.SomeProperty() call used to create this property.. [optional] # noqa: E501
description (bool, date, datetime, dict, float, int, list, str, none_type): The description/tooltip shown in the user interface.. [optional] # noqa: E501
default (bool, date, datetime, dict, float, int, list, str, none_type): The default value shown to the user when determining this setting.. [optional] # noqa: E501
eval (str): Python expression to be evaluated in order to determine the default value for this setting.. [optional] # noqa: E501
@ -261,6 +264,7 @@ class AvailableJobSetting(ModelNormal):
_visited_composed_classes = (Animal,)
subtype (AvailableJobSettingSubtype): [optional] # noqa: E501
choices ([str]): When given, limit the valid values to these choices. Only usable with string type.. [optional] # noqa: E501
propargs ({str: (bool, date, datetime, dict, float, int, list, str, none_type)}): Any extra arguments to the bpy.props.SomeProperty() call used to create this property.. [optional] # noqa: E501
description (bool, date, datetime, dict, float, int, list, str, none_type): The description/tooltip shown in the user interface.. [optional] # noqa: E501
default (bool, date, datetime, dict, float, int, list, str, none_type): The default value shown to the user when determining this setting.. [optional] # noqa: E501
eval (str): Python expression to be evaluated in order to determine the default value for this setting.. [optional] # noqa: E501

View File

@ -4,7 +4,7 @@ Render Farm manager API
The `flamenco.manager` package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.0.0
- Package version: 4196460c-dirty
- Package version: 7bfde1df-dirty
- Build package: org.openapitools.codegen.languages.PythonClientCodegen
For more information, please visit [https://flamenco.io/](https://flamenco.io/)

View File

@ -5,6 +5,8 @@ import logging
from pathlib import Path
from typing import Optional, TYPE_CHECKING
from flamenco.job_types_propgroup import JobTypePropertyGroup
import bpy
from . import job_types, job_submission
@ -115,8 +117,13 @@ class FLAMENCO_OT_eval_setting(FlamencoOpMixin, bpy.types.Operator):
setting_eval: bpy.props.StringProperty(name="Python Expression") # type: ignore
def execute(self, context: bpy.types.Context) -> set[str]:
propgroup = context.scene.flamenco_job_settings
propgroup.eval_and_assign(context, self.setting_key, self.setting_eval)
job = job_submission.job_for_scene(context.scene)
if job is None:
self.report({"ERROR"}, "This Scene has no Flamenco job")
return {"CANCELLED"}
propgroup: JobTypePropertyGroup = context.scene.flamenco_job_settings
propgroup.eval_and_assign(context, job, self.setting_key, self.setting_eval)
return {"FINISHED"}

View File

@ -28,7 +28,9 @@ func exampleSubmittedJob() api.SubmittedJob {
"images_or_video": "images",
"image_file_extension": ".png",
"video_container_format": "",
"render_output": "/render/sprites/farm_output/promo/square_ellie/square_ellie.lighting_light_breakdown2/######",
"render_output_root": "/render/sprites/farm_output/promo/square_ellie",
"add_path_components": 1,
"render_output_path": "/render/sprites/farm_output/promo/square_ellie/square_ellie.lighting_light_breakdown2/######",
}}
metadata := api.JobMetadata{
AdditionalProperties: map[string]string{
@ -144,7 +146,7 @@ func TestSimpleBlenderRenderWindowsPaths(t *testing.T) {
// Adjust the job to get paths in Windows notation.
sj.Settings.AdditionalProperties["blendfile"] = "R:\\sf\\jobs\\scene123.blend"
sj.Settings.AdditionalProperties["render_output"] = "R:\\sprites\\farm_output\\promo\\square_ellie\\square_ellie.lighting_light_breakdown2\\######"
sj.Settings.AdditionalProperties["render_output_path"] = "R:\\sprites\\farm_output\\promo\\square_ellie\\square_ellie.lighting_light_breakdown2\\######"
aj, err := s.Compile(ctx, sj)
if err != nil {

View File

@ -7,14 +7,19 @@ const JOB_TYPE = {
{ key: "chunk_size", type: "int32", default: 1, description: "Number of frames to render in one Blender render task" },
{ key: "frames", type: "string", required: true, eval: "f'{C.scene.frame_start}-{C.scene.frame_end}'",
description: "Frame range to render. Examples: '47', '1-30', '3, 5-10, 47-327'" },
// render_output_root + add_path_components determine the value of render_output_path.
{ key: "render_output_root", type: "string", subtype: "dir_path", required: true,
description: "Base directory of where render output is stored. Will have some job-specific parts appended to it"},
{ key: "add_path_components", type: "int32", required: true, default: 0, propargs: {min: 0, max: 32},
description: "Number of path components of the current blend file to use in the render output path"},
{ key: "render_output_path", type: "string", subtype: "file_path", editable: false,
eval: "str(Path(settings.render_output_root) / last_n_dir_parts(settings.add_path_components) / jobname / '{timestamp}' / '######.{ext}')",
description: "Final file path of where render output will be saved"},
// Automatically evaluated settings:
{ key: "blender_cmd", type: "string", default: "{blender}", visible: false },
{ key: "blendfile", type: "string", required: true, description: "Path of the Blend file to render", visible: false },
{ key: "render_output_path", type: "string", subtype: "file_path", visible: false,
description: "Final file path of where render output is stored, set by the job compiler"},
{ key: "fps", type: "float", eval: "C.scene.render.fps / C.scene.render.fps_base", visible: false },
{
key: "images_or_video",
@ -55,15 +60,14 @@ const videoContainerToExtension = {
function compileJob(job) {
print("Blender Render job submitted");
const renderOutput = renderOutputPath(job);
job.settings.render_output_path = renderOutput;
print("job: ", job);
const settings = job.settings;
const renderOutput = settings.render_output_path;
const finalDir = path.dirname(renderOutput);
const renderDir = intermediatePath(job, finalDir);
const settings = job.settings;
const renderTasks = authorRenderTasks(settings, renderDir, renderOutput);
const videoTask = authorCreateVideoTask(settings, renderDir);
@ -79,19 +83,6 @@ function compileJob(job) {
}
}
// Return the intended render output path.
function renderOutputPath(job) {
// {DIR}/{job name}/{date and time of job submission}/######.{extension}
const pathSafeJobName = job.name.replace(/[/\\:?*]/, "-");
const extension = guessOutputFileExtension(job.settings);
return path.join(
job.settings.render_output_root,
pathSafeJobName,
formatTimestampLocal(job.created),
`######${extension}`
);
}
// Determine the intermediate render output path.
function intermediatePath(job, finalDir) {
const basename = path.basename(finalDir);

View File

@ -403,6 +403,9 @@ components:
description: When given, limit the valid values to these choices. Only usable with string type.
type: array
items: {type: string}
"propargs":
description: Any extra arguments to the bpy.props.SomeProperty() call used to create this property.
type: object
"description":
description: The description/tooltip shown in the user interface.
"default":

View File

@ -18,65 +18,66 @@ import (
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
"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",
"+BN0ZoqB6J3hvW2vTq/+PwAA///UdlNGQjgAAA==",
}
// GetSwagger returns the content of the embedded swagger specification file

View File

@ -135,6 +135,9 @@ type AvailableJobSetting struct {
// Identifier for the setting, must be unique within the job type.
Key string `json:"key"`
// Any extra arguments to the bpy.props.SomeProperty() call used to create this property.
Propargs *map[string]interface{} `json:"propargs,omitempty"`
// Whether to immediately reject a job definition, of this type, without this particular setting.
Required *bool `json:"required,omitempty"`