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:
parent
7bfde1df0b
commit
09a476e11a
@ -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,18 +72,48 @@ class FLAMENCO_PT_job_submission(bpy.types.Panel):
|
||||
layout.label(text="Job Settings:")
|
||||
layout.use_property_split = True
|
||||
for setting in job_type.settings:
|
||||
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):
|
||||
continue
|
||||
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 setting_eval:
|
||||
props = row.operator(
|
||||
"flamenco.eval_setting", text="", icon="SCRIPTPLUGINS"
|
||||
)
|
||||
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
|
||||
) -> None:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -10,7 +10,7 @@
|
||||
"""
|
||||
|
||||
|
||||
__version__ = "4196460c-dirty"
|
||||
__version__ = "7bfde1df-dirty"
|
||||
|
||||
# import ApiClient
|
||||
from flamenco.manager.api_client import ApiClient
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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/)
|
||||
|
||||
|
@ -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"}
|
||||
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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":
|
||||
|
@ -18,65 +18,66 @@ import (
|
||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"H4sIAAAAAAAC/9xb3W4bN/Z/FWL6B9riP5KcOLvA+mrTpEkd5MOonfYiMWRq5khDm0NOSI4UbWCgD7Fv",
|
||||
"sltgL7ZX+wLuGy0OP+ZDQ1l266Tt5iIYzZCH5xyezx/pD0kmy0oKEEYnBx8SnRVQUvv4UGu2EJCfUH2B",
|
||||
"v3PQmWKVYVIkB72vhGlCicEnqgkz+FtBBmwJOZmtiSmAfC/VBahxkiaVkhUow8CuksmypCK3z8xAaR/+",
|
||||
"T8E8OUg+m7TMTTxnk0duQnKZJmZdQXKQUKXoGn+fyxnO9q+1UUws/PtppZhUzKw7A5gwsAAVRri3kemC",
|
||||
"lvEP19PUhpp6pziov2M3EiWi+mI7I3XNcvwwl6qkJjlwL9LNgZdpouBdzRTkycGbMAiV42VpeOuIsKGl",
|
||||
"jkq6XKXtfp0268rZOWQGGXy4pIzTGYdncnYMxiA7A8s5ZmLBgWj3ncg5oeSZnBGkpiMGUkiWucc+ne8L",
|
||||
"EGTBliBSwlnJjLWzJeUsx/9r0MRIfKeBeCJj8krwNak18khWzBTEKc0ujms3JjhQ/qax5TCnNTdDvk4K",
|
||||
"IP6j44PoQq6EZ4bUGhRZIe85GFAlE3b9gumgkrEj36EZX6J5MzFScsMqvxAT7UJoj2pOM7BEIWcGRXcU",
|
||||
"Pf9zyjWkQ+WaAhQyTTmXK4JTNxkldG5wTAHkXM5IQTWZAQii61nJjIF8TL6XNc8JKyu+JjlwcNM4J/Ce",
|
||||
"aUeQ6gtN5lI50udylhIqcgwgsqwYxzHMjN+K1tBnUnKgwkq0pHyon6O1KaQg8L5SoDWTVvkzIDi6pgZy",
|
||||
"1JFUuRMw7ANYSfpb1/DV7E06NI0LWA95OMxBGDZnoDyRxuRTUtbaID+1YO9qZ4h+0869I0TXab36FtvH",
|
||||
"yhJyRg3wNVGAnkqoXSaHORMMJ6TohFZKXDK1/MjauFcVVYZlNaeq0cGWvdD1LISu6yJeJEgc+5mNm92a",
|
||||
"womfvmSabRq4UfV1CkKn6Zu134vXhy44obKCSSvyBWcXQCj5ioNAA6J5PpLiyzE5BoPkzuyGnDkXd7mQ",
|
||||
"CueHgvJmDVNQg0vXPBefW2NoogSI3Dqvjit6I7yj8flBNwzJx+0+bUTmejbCL84cnDGGPSePaqVAGL4m",
|
||||
"EmMoDXStdXeiqB6Ts28eHn/z9ePpk8PnX0+PHp58c+YqhJwpyIxUa1JRU5D/J2dvk8ln9t/b5IzQqkKV",
|
||||
"5k5sEHWJ8s0ZhymOT9IkZyo82tc+mxVUF5BP25GnEefZZjTD4Oo10JG+47EudVBNDh8fuTy1tmKj0XiT",
|
||||
"GJOXkgjQGGe0UXVmagWafGFTh05JzjJciioG+ktCFRBdV5VUZlN0z3yKVcX+fRSaS2qS1NrCTiHj0oVM",
|
||||
"267pKjSmyQsq6AKUC7/MWNenJQbHSFrmdAb8duWSV+bNS71YOTHIxBvu4E3CsddZc5dvoLYiRcZzpk0w",
|
||||
"Bmvd2/U21FEooX6ZxCe9iLhF3HaJmIChVh6I5T8QBZghkQVCiXaFma/wbCR6D1ltYFcNv71Abgyo8zmw",
|
||||
"F9+4zpSYRF8rJRUS2+wicuhVxsFjhmV5CVrTRYzfDYYszXZ8jJsnnJYgMvkdKO0LtRtqZtnOuJ6LMND7",
|
||||
"VYyLZ67toZy/micHb663sONQm+Gsy3SgSAVYH0UsBj/YSoqVoA0tK4xHQd05NTDCL7GyhUXIvX59+Dik",
|
||||
"mWe2M9nR1Ny0n8JQ0bRTdZXfsTQbu2M5DTpr12uYPb08dRv0AgzNqaF2o/Lcll2UH/V0P5B4o+NWM2YU",
|
||||
"VWtSemI+7eoxeSGVddyKw/tuzsmowKxVSqy9bcSq0cvJGR3PxtkZEdI4PYQS9QLW6N/wniItb9DW0A6S",
|
||||
"40oxA+SJYosCsxDWKGMoKePI9XqmQPx15lOgVIswwvlAcmwHkGPzn38vgXcCW8+Qjzs5Iq4nV81F5zYG",
|
||||
"EhIozQxb2q6Vigw14BrYioPxz8Ipi0kxmlPmRjQPFa21fXhXQ20fqMoKtuw8uvzsyI/QMmza90R6L+yz",
|
||||
"o1KjikbdxZM0WVHbYI3mUo2wktHRBP8tLJg2oCB3wXgYcmieY9MTNShOtZlapfRRi07yZtnF9nDOqUEn",
|
||||
"iWd3OTcrqrak/hv5rhOpdd8m1U4bBKKfSnc26b8KMWl0kTZK7SInQRlpkrnS2HKZbGq5o5ktEsVi+jFk",
|
||||
"tWJmvSXf3TiJXZe9eqkgWii2LWLb02NdEPLeRqgoO0Hu44UN/2H/6u/k5x+ufrz66eqfVz/+/MPVv65+",
|
||||
"uvpHF9I6+NNev+j0q0yzMk8Okg/+5yXuYFGLi6lmf4PkYB9lMopmZkrrnMkQctApbXdxkEyUnTnR88m5",
|
||||
"nKEBg4B79/fHlmQ3lRy9fIo/K50c3H+QJnMsbnRykNwb3dvDwr6kC9BTqaZLloPESsW+SdJE1qaqjWtq",
|
||||
"4L0B4eqFZFzZkOM4mLpRfZbcIg1THb/QDLdq5AUfuSkOyexbV7uPO3Jtk9duipM2XTluTgQ07WzXrjQf",
|
||||
"hnZQg+udwTuzRzIbrmK+0YFlb5FPmszRhHr0/TazRPKEzzGxWI88vLYVRaRJbb4RC1wIg8md+hIdfdTV",
|
||||
"Ig53soKQt/Xe3v0/Ey4X2gEbFrFn5nPtC32Pb23kk0666PPwSsCIM+FhJpGzDBdcFRQpZg1cUNi+HqsO",
|
||||
"C7giQ7jwmLxaglphbNCkUrBkstZ87WQJizYVTqwg5DKCLj+XC4JMdSBFXC0lK8Y51kIBZUCmrSrsgkAV",
|
||||
"Z663GSaVni3cFMyPFThud1wOV9TEW4ZfnoEhU2Din35lJt1wJL9SLwlGl+gk0dOt+jhmC/HqtpoISXW6",
|
||||
"vZO6c7E7BcEWaQdcXSO1oQYeFVQsYCi689hpGyhuVTlt7tYmsRsxlW/j6g542cFBP+hqQ5VxdTZd0Qtb",
|
||||
"jmkOgC0b2PIoTXRRm1yuLFwK2o+W8zlGgkhsdc5iC6xj5NqJt7IMTGmNOX7QsGpQuPcYbjGEucHk8HFK",
|
||||
"Kqr1Sqo8fHLe4Y6mCDVhqOq4PcYZqy+L7FLNsjbwFMZUySXyyMRcOnRDGJqZFlBogAdyAhSdr1bcz9QH",
|
||||
"k8k8lGdMToZ95LcOt35CVUlKB12Rh0eHWLiyDISGzjpPj54v9wf0V6vVeCFqrNYmfo6eLCo+2h/vjUGM",
|
||||
"C1O6Bo8Z3uPWL5d08I/k3nhvvIejZQWCVgxLO/sKc6Mp7M5MaMVspWVtUmqrCrRMq8zD3GHXJTMOSvCW",
|
||||
"/pXM10F9IOwcWlUc0xSTYnKuXdRwdrvLqvu4yeVAqxZXlb5MTrpGj9Wj9QJdSdQUrnR/b+/OOLuGoRXV",
|
||||
"RNdZBnpec74m7kTNHn/5lL1keU25O4Qbbxxr3gl3roGJ8Gc/kNCfWJesy5KqdbOZhBIBKwu9Yi5vrMjj",
|
||||
"rR2A0qZtilWjRUR1ctoj9ywc4LizQBB5JZkwVt7GtCZNdlhAxL6egmlQ4o+4mUNIOqK6ZlALS28o8CkY",
|
||||
"wgfQtUV1C2BqA9m/RnXtUo36z9uz+p7+PpzL2ZTll1tV+ARMVjgP7QLDbz4kDKXyBzs+8jhiA0dKO3rc",
|
||||
"1dSf/jZOZ6N2fzus5PYDoTN3smr37gZ26yaJ3MfOEjkPau+UPtts9rsGPv5oqtgEwSNqEbhTnAQWIsaK",
|
||||
"CmkszMvVnEq/aNJGUBZ2qBvKcuWDgzxrDb70lyQrILtwv5jGxqKmGAppu5wGtcTSP6jV5euJ8lDbaNUi",
|
||||
"bdHUEzA5j8h9nPwTaR0iim7bv8D9J01FA3TyJrbwCXNOLeB9BZmBnIAf0zWhwL5PPKuwn8Hq/IvTyCS3",
|
||||
"JWix7Uy9aVGaLcRIzufXVDHYCs3nQ3d9MKxIf3+K9CW1Dem9YvrNKQbjVmcvqLroVtFUk1Cs79D2I8r9",
|
||||
"QUbwd2zjfQAJhcGFsDc6YP25ArKQ7paZJT+Ob4nYsSPiozq1X2K7Ozd43Kf05WGX+odw5hvb4MPaFCCM",
|
||||
"A608NIbWEG7/rJrD7js2SAU0X+MopOcuW/TgOtZu+NBcjUcDo/m+s2XJb20ZllOS2e+khR4u023BjGyf",
|
||||
"8fs2qdubhytJVuEKWgEK3DWx9RYlxO1glHWAmmjwioA6HzWQdReKqPdlkxqdnDeIZ/9bec/Hc79vTglj",
|
||||
"coK1aWYvys7s1TKaYcDgkLt634H1Ppa0hwc9W0mJVBi5glZCfAE14jKj3IY2yvVdx7Ml9KSp9cBUjf/z",
|
||||
"gS3pNSsgrzmcuKPTj9dXd/+YIbKx9s8YuoDCtkD1Uvoby/0LkLa/CPejLtPkwd7+3UFPvbPgCPNHoAK2",
|
||||
"8RgEc0Hzwd5fInfmnQEyTYQ0IdO5Uy1nTinRMny2F7+hdxHMiW5PcomQKyfq/f1Pm1qCF1GBXMqZoUzY",
|
||||
"sttyl5JZbdx9zYW099eFtHHWedstPfaVo04b+h1t7HIla1PaG7iKwE4dD5l8sOcIHj6J+0rnPPAmCIon",
|
||||
"+OshlLtPFx1Jtvmir4eYcCwGDOPW2eKkgEBrZUNrBlXIqFEXOfHnkzYj+6jRNSO3adZPTJ+29Zku/T9K",
|
||||
"WnrdHhW7s1KzrlhmYZLuyW6l5EKB1qm/aeb/dECROWW8VrAzt4SMokHkPTQM1R2oYxTDiih4qloGG3eH",
|
||||
"EJOkU3QN/uiiD7ENIGNmNPD5uHUSCyRdprHLL74nsMy9q0Ex0GkHRk43ULlxD7vUEaIPjw77QHa3JJRl",
|
||||
"WQt/gs5MMWC9Q97r9vL08r8BAAD//yU2/SO+NwAA",
|
||||
"H4sIAAAAAAAC/9xb224cN5N+FaL/BZJge2Zky7vA6mr127EjwwchIycXtjDidNdMU2KTbZI9o1lDQB5i",
|
||||
"32Q3wF5srvYFlDdaFA99mObokMhO8vvC6OnmoapYVV/xI/UpyWRZSQHC6OTgU6KzAkpqHw+1ZksB+QnV",
|
||||
"F/g7B50pVhkmRXLQ+0qYJpQYfKKaMIO/FWTAVpCT+YaYAsiPUl2AGidpUilZgTIM7CyZLEsqcvvMDJT2",
|
||||
"4Z8ULJKD5G+TVriJl2zy1HVIrtLEbCpIDhKqFN3g73M5x97+tTaKiaV/P6sUk4qZTacBEwaWoEIL9zbS",
|
||||
"XdAy/uHmMbWhpr5VHbTf1LVEjai+2C1IXbMcPyykKqlJDtyLdLvhVZoo+FgzBXly8D40QuN4XRrZOips",
|
||||
"Waljkq5Uabtep828cn4OmUEBD1eUcTrn8FLOp2AMijPwnCkTSw5Eu+9ELgglL+Wc4Gg64iCFZJl77I/z",
|
||||
"YwGCLNkKREo4K5mxfrainOX4fw2aGInvNBA/yJi8FXxDao0ykjUzBXFGs5Pj3I0LDoy/7Ww5LGjNzVCu",
|
||||
"kwKI/+jkILqQa+GFIbUGRdYoew4GVMmEnb9gOphk7IbvjBmfonkzMVJywyo/ERPtROiPakEzsINCzgyq",
|
||||
"7kb08i8o15AOjWsKUCg05VyuCXbdFpTQhcE2BZBzOScF1WQOIIiu5yUzBvIx+VHWPCesrPiG5MDBdeOc",
|
||||
"wCXTbkCqLzRZSOWGPpfzlFCRYwKRZcU4tmFm/EG0jj6XkgMVVqMV5UP7HG9MIQWBy0qB1kxa48+BYOua",
|
||||
"GsjRRlLlTsGwDmA16S9dI1ezNunQNS5gM5ThKAdh2IKB8oM0Lp+SstYG5akF+1g7R/SLdu4DIToPBgZV",
|
||||
"y0gsHIoNgUujKKFqWZeYYYK/zavNGDvq8VSWcOxia/P1NyTDZag15NgyU0ANOFV9/G06MrQh3maWe7gQ",
|
||||
"K0vIGTXAN0QBDkWoVTWHBRMMO6SYCOz0OGVqbSJr4yWiyrCs5lQ167DDH3Q9D+nzpqwbSVRT37MJ9XuP",
|
||||
"cOK7r5hm20FmVH2TgTBw+6Hl/eHdkUuQaKwQVop8zdkFEEr+zkGgE9M8H0nxzZhMweBwZ3ZBzlyacXhM",
|
||||
"hcsFgvJmDlNQg1PXPBdfWYdsMhWI3CYQHTf0FsRgAPhGd4SFabtOW+hQz0f4xbmDC4iw5uRprRQIwzdE",
|
||||
"Yh6nYVwbYZ1Mrsfk7LvD6XffPps9P3r17ez48OS7M1el5ExBZqTakIqagvwzOfuQTP5m/31IzgitKjRp",
|
||||
"7tQGUZeo34JxmGH7JE1ypsKjfe0RtaC6gHzWtjyNBPAupxkmeG+BjvadrOHgi2py9CzEs1Ubnca7xJi8",
|
||||
"kUSAxlynjaozUyvQ5GsLXzolOctwKqoY6G8IVUB0XVVSmW3VvfApVjb7j1FpLqlJUusLtyoZ1y6gfTun",
|
||||
"qxKZJq+poEtQDgKYsaFPS0zQkdKA0znw+5Vs3ph3LzdjJc2gGtgKB+8STrzOnLfFBlorktxfMW2CM1jv",
|
||||
"3m23oY1CGffbND7pZcQd6rZTxBQM9fpALf+BKECUtpBFiXbFoa8ybSa6hKw2cNs+YneR3jhQ53MQL75w",
|
||||
"nS4xjb5VSiocbHsnk0OvOg8RM9walKA1Xcbk3RLIjtm2j0nznNMSRCZ/AKV9sXhHy6zaHjdLERr6uIpJ",
|
||||
"8dJtvSjnbxfJwfubPWwa6kPsdZUODGlrkZjH4AdbzbEStKFlhfkomDunBkb4JVY6schw794dPQsw89Lu",
|
||||
"jm7ZWN11T4epotnS1VX+wNpsrY6VNNisna8R9vTq1C3QazA0p4bahcpzW3ZRftyz/UDjrTpTzZlRVG1I",
|
||||
"6QfzsKvH5LVUNnArDpddzMmoQNQqJdb/NmPVGOXkjI7n4+yMCGmcHUKZfAG29IRLimN5h7aOdpBMK8UM",
|
||||
"kOeKLQtEIaxRxlBSxlHqzVyB+Pe5h0CplqGFi4FkahuQqfm//10B7yS2niNPOxgRt5Or5qJ9GwcJAEoz",
|
||||
"w1Z250xFhhZwm+iKg/HPwhmLSTFaUOZaNA8VxRI9SZOPNdT2gaqsYKvOo8NnN/wIPcPCvh+k98I+u1Fq",
|
||||
"NNGoO3mSJmtqN3mjhVQjrGR0FOC/hyXTBhTkLhkPUw7Nc9x4RR2KU21m1ih95qQD3iy72J3OOTUYJHF0",
|
||||
"lwuzpmoH9N8pdp1Kbfg2UDtrWJA+lN5KFPwu1qaxRdoYtcveBGOkSeZKYytlsm3ljmV2aBTL6VPIasXM",
|
||||
"Zgfe3RnEbkKvHhREC8V2i9jyClgXBNzbShVlJ8l9vrThP+xf/yf59afrn69/uf7v659//en6f65/uf6v",
|
||||
"Lq128C97/aLTzzLLyjw5SD75n1e4gkUtLmaa/QckB/uok1E0MzNa50yGlINBaXcXB8lE2Z4TvZicyzk6",
|
||||
"MAh49Hh/bIfsQsnxmxf4s9LJweMnabLA4kYnB8mj0aM9LOxLugQ9k2q2YjlIrFTsmyRNZG2q2rhNDVwa",
|
||||
"EK5eSMaVTTlOgplr1RfJTdII1YkLzXCpRl7xkesS2I2ud7XreAvWNrh2V6622ZXj4kSI285y3QbzoWmH",
|
||||
"Nbg5GHwweza1kSoWGx1q+B540iBHk+ox9ltkieCEx5hYrkcZ3tmKIrJJbb4RS1wIg+BOfYmOMepqEcd9",
|
||||
"WUXIh3pv7/G/Ei6X2hEb9tSAma+0L/Q9x7aFJx246MvwVsCIM+FpJpGzDCdcFxRHzBq6oLD7eqw6LOmL",
|
||||
"AuHEY/J2BWqNuUGTSsGKyVrzjdMlTNpUOLGCkMsIw/1KLgkK1aE1cbaUrBnnWAsFlgGFtqawEwJVnLm9",
|
||||
"zRBUer5w1wOFWIHjVsdhuKImvmX47QgMmQIT//Q7kXQrkPxMPRCMTtEB0dOd9piypXh7X0sEUJ3t3kk9",
|
||||
"uNqdgmCHtgOpbtDaUANPCyqWMFTdReysTRT3qpy2V2t7sDsJle+S6gFkuUWCftLVhirj6my6phe2HNMc",
|
||||
"ALdsYMujNNFFbXK5tnQpaN9aLhaYCSK51QWLLbCmKLVTb20FmNEaMX6wYdWgcO0x3WIKc43J0bOUVFTr",
|
||||
"tVR5+OSiwx2PEWpCU9UJe8wz1l6W2aWaZW3iKYypkiuUkYmFdOyGMDQzLaHQEA/kBCgGX62476kPJpNF",
|
||||
"KM+YnAz3kd873vo5VSUpHXVFDo+PsHBlGQgNnXleHL9a7Q/GX6/X46WosVqb+D56sqz4aH+8NwYxLkzp",
|
||||
"NnjM8J60frqkw38kj8Z74z1sLSsQtGJY2tlXiI2msCszoRWzlZb1SamtKdAzrTGPcsddl8w4KsF7+t9l",
|
||||
"vgnmA2H70KriCFNMism5dlnD+e1tXt3nTa4GVrW8qvRlctJ1eqwebRToSqKlcKbHe3sPJtkNAq2pJrrO",
|
||||
"MtCLmvMNcad69gjOQ/aK5TXl7iBwvHW0+iDSuQ1MRD77gYT9iQ3Juiyp2jSLSSgRsLbUK2J540Web+0Q",
|
||||
"lBa2KVaNlhHVyWlvuJfhAMedR4LIK8mEsfo2rjVp0GEJEf96AaZhiT/jYg4p6YjpmkYtLb1lwBdgCB9Q",
|
||||
"15bVLYCpLWb/BtO1UzXmP2/vC/Ts9+lczmcsv9ppwudgssJFaJcYfv8pYaiVP9jxmccNNgiktGPH2zb1",
|
||||
"p39M0Nms3V8Oq7n9QOjcnazatbuD37pOIve5s0TJg9k7pc8un/2hoY8/mym2SfCIWQSuFCdBhIizokEa",
|
||||
"D/N6NafSrxvYCMbCHeqWsVz54CjPWvvjdCNJVkB24X4xjRuLmmIqpO10GtQKS/9gVofXE+WpttG6Zdqi",
|
||||
"0BM4Oc/IfR78iWwdIoZut39B+i8KRQN28i6+8AUxpxZwWUFmICfg23RdKIjvgWcd1jN4nX9xGunklgQ9",
|
||||
"tu2ptz1Ks6UYycXihioGt0KLxTBcnwwr0j+fIX1JbVN6r5h+f4rJuLXZa6ouulU01SQU67dY+ynl/iAj",
|
||||
"xDtu430CCYXBhbA3OmDzlQKylO6mmx1+HF8SccuKiM8a1H6K3eHc8HFfMpaHu9S/RDDf2QcPa1OAMI60",
|
||||
"8tQYekO4/bNuDrsf2CEV0HyDrXA8d9miR9exdsGH7mo8GxjF+86SJX+0Z1hJSWa/k5Z6uEp3JTOyu8ef",
|
||||
"26Xu7x6uJFmHK2gFKHDXxDY7jBD3g1HWIWqiyStC6nzWRNadKGLeNw00Oj3vkM/+sXDP53O/bs4IY3KC",
|
||||
"tWlmL+vO7dUymmHC4JC7et+R9T6XtIcHPV9JiVSYuYJVQn4BNeIyo9ymNsr1Q+ezFfS0qfXAVY3/E4Yd",
|
||||
"8JoVkNccTtzR6efbV3f/oCKysPZPKbqEwq5E9Ub6W9P9C5B2fxHuR12lyZO9/YejnnpnwRHhj0EFbuMZ",
|
||||
"COaS5pO9f4vc23cOyDQR0gSkc6dazp1SomX4bC+fQ+8imFPdnuQSIddO1cf7XxZaQhRRgVLKuaFM2LLb",
|
||||
"SpeSeW3cfc2ltHfohbR51kXbPSP2rRudNuN3rHFbKFmf0t7BVYR26kTI5JM9R/D0STxWOueBd2FQ/IC/",
|
||||
"n0J5eLjoaLIrFn09xIQTMXAY90aLkwLCWGubWjOoAqJGQ+TEn09aRPZZo+tGbtFsnJj+2DZmuuP/VWDp",
|
||||
"XXtU7M5KzaZimaVJuie7lZJLBVqn/qaZ/9MBRRaU8VrBrdgSEEWDyHtsGJo7jI5ZDCuiEKlqFXzcHUJM",
|
||||
"kk7RNfjDjz7FNqCMmdHAF+M2SCyRdJXGLr/4PYEV7mMNioFOOzRyusXKjXvcpY4Menh81CeyuyWhLMta",
|
||||
"+BN0ZoqB6J3hvW2vTq/+PwAA///UdlNGQjgAAA==",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification 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"`
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user