Fusion: new creator for image product type (#6057)

* Introduced image product type

'image' product type should result in single frame output, 'render' should be more focused on multiple frames.

* Updated logging

* Refactor moved generic creaor class to better location

* Update openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json

Co-authored-by: Jakub Ježek <jakubjezek001@gmail.com>

* Change label

It might be movie type not only image sequence.

* OP-7470 - fix name

* OP-7470 - update docstring

There were objections for setting up this creator as it seems unnecessary. There is currently no other way how to implement customer requirement but this, but in the future 'alias' product types implementation might solve this.

* Implementing changes from #6060

https://github.com/ynput/OpenPype/pull/6060

* Update openpype/settings/defaults/project_settings/fusion.json

Co-authored-by: Jakub Ježek <jakubjezek001@gmail.com>

* Update server_addon/fusion/server/settings.py

Co-authored-by: Jakub Ježek <jakubjezek001@gmail.com>

* Update openpype/hosts/fusion/api/plugin.py

Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>

* OP-7470 - added explicit frame field

Artist can insert specific frame from which `image` instance should be created.

* OP-7470 - fix name and logging

Prints better message even in debug mode.

* OP-7470 - update instance label

It contained original frames which was confusing.

* Update openpype/hosts/fusion/plugins/create/create_image_saver.py

Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>

* OP-7470 - fix documentation

* OP-7470 - moved frame range resolution earlier

This approach is safer, as frame range is resolved sooner.

* OP-7470 - added new validator for single frame

* OP-7470 - Hound

* OP-7470 - removed unnecessary as label

* OP-7470 - use internal class anatomy

* OP-7470 - add explicit settings_category to propagete values from Setting correctly

apply_settings is replaced by correct value in `settings_category`

* OP-7470 - typo

* OP-7470 - update docstring

* OP-7470 - update formatting data

This probably fixes issue with missing product key in intermediate product name.

* OP-7470 - moved around only proper fields

Some fields (frame and frame_range) are making sense only in specific creator.

* OP-7470 - added defaults to Settings

* OP-7470 - fixed typo

* OP-7470 - bumped up version

Settings changed, so addon version should change too. 0.1.2 is in develop

* Update openpype/hosts/fusion/plugins/publish/collect_instances.py

Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>

* OP-7470 - removed unnecessary variables

There was logic intended to use those, deemed not necessary.

* OP-7470 - update to error message

* OP-7470 - removed unneded method

---------

Co-authored-by: Jakub Ježek <jakubjezek001@gmail.com>
Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>
This commit is contained in:
Petr Kalis 2024-01-15 10:32:39 +01:00 committed by GitHub
parent 8afd062337
commit 7d94fb92c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 482 additions and 259 deletions

View file

@ -0,0 +1,221 @@
from copy import deepcopy
import os
from openpype.hosts.fusion.api import (
get_current_comp,
comp_lock_and_undo_chunk,
)
from openpype.lib import (
BoolDef,
EnumDef,
)
from openpype.pipeline import (
legacy_io,
Creator,
CreatedInstance
)
class GenericCreateSaver(Creator):
default_variants = ["Main", "Mask"]
description = "Fusion Saver to generate image sequence"
icon = "fa5.eye"
instance_attributes = [
"reviewable"
]
settings_category = "fusion"
image_format = "exr"
# TODO: This should be renamed together with Nuke so it is aligned
temp_rendering_path_template = (
"{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}")
def create(self, subset_name, instance_data, pre_create_data):
self.pass_pre_attributes_to_instance(instance_data, pre_create_data)
instance = CreatedInstance(
family=self.family,
subset_name=subset_name,
data=instance_data,
creator=self,
)
data = instance.data_to_store()
comp = get_current_comp()
with comp_lock_and_undo_chunk(comp):
args = (-32768, -32768) # Magical position numbers
saver = comp.AddTool("Saver", *args)
self._update_tool_with_data(saver, data=data)
# Register the CreatedInstance
self._imprint(saver, data)
# Insert the transient data
instance.transient_data["tool"] = saver
self._add_instance_to_context(instance)
return instance
def collect_instances(self):
comp = get_current_comp()
tools = comp.GetToolList(False, "Saver").values()
for tool in tools:
data = self.get_managed_tool_data(tool)
if not data:
continue
# Add instance
created_instance = CreatedInstance.from_existing(data, self)
# Collect transient data
created_instance.transient_data["tool"] = tool
self._add_instance_to_context(created_instance)
def update_instances(self, update_list):
for created_inst, _changes in update_list:
new_data = created_inst.data_to_store()
tool = created_inst.transient_data["tool"]
self._update_tool_with_data(tool, new_data)
self._imprint(tool, new_data)
def remove_instances(self, instances):
for instance in instances:
# Remove the tool from the scene
tool = instance.transient_data["tool"]
if tool:
tool.Delete()
# Remove the collected CreatedInstance to remove from UI directly
self._remove_instance_from_context(instance)
def _imprint(self, tool, data):
# Save all data in a "openpype.{key}" = value data
# Instance id is the tool's name so we don't need to imprint as data
data.pop("instance_id", None)
active = data.pop("active", None)
if active is not None:
# Use active value to set the passthrough state
tool.SetAttrs({"TOOLB_PassThrough": not active})
for key, value in data.items():
tool.SetData(f"openpype.{key}", value)
def _update_tool_with_data(self, tool, data):
"""Update tool node name and output path based on subset data"""
if "subset" not in data:
return
original_subset = tool.GetData("openpype.subset")
original_format = tool.GetData(
"openpype.creator_attributes.image_format"
)
subset = data["subset"]
if (
original_subset != subset
or original_format != data["creator_attributes"]["image_format"]
):
self._configure_saver_tool(data, tool, subset)
def _configure_saver_tool(self, data, tool, subset):
formatting_data = deepcopy(data)
# get frame padding from anatomy templates
frame_padding = self.project_anatomy.templates["frame_padding"]
# get output format
ext = data["creator_attributes"]["image_format"]
# Subset change detected
workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"])
formatting_data.update({
"workdir": workdir,
"frame": "0" * frame_padding,
"ext": ext,
"product": {
"name": formatting_data["subset"],
"type": formatting_data["family"],
},
})
# build file path to render
filepath = self.temp_rendering_path_template.format(**formatting_data)
comp = get_current_comp()
tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath))
# Rename tool
if tool.Name != subset:
print(f"Renaming {tool.Name} -> {subset}")
tool.SetAttrs({"TOOLS_Name": subset})
def get_managed_tool_data(self, tool):
"""Return data of the tool if it matches creator identifier"""
data = tool.GetData("openpype")
if not isinstance(data, dict):
return
required = {
"id": "pyblish.avalon.instance",
"creator_identifier": self.identifier,
}
for key, value in required.items():
if key not in data or data[key] != value:
return
# Get active state from the actual tool state
attrs = tool.GetAttrs()
passthrough = attrs["TOOLB_PassThrough"]
data["active"] = not passthrough
# Override publisher's UUID generation because tool names are
# already unique in Fusion in a comp
data["instance_id"] = tool.Name
return data
def get_instance_attr_defs(self):
"""Settings for publish page"""
return self.get_pre_create_attr_defs()
def pass_pre_attributes_to_instance(self, instance_data, pre_create_data):
creator_attrs = instance_data["creator_attributes"] = {}
for pass_key in pre_create_data.keys():
creator_attrs[pass_key] = pre_create_data[pass_key]
def _get_render_target_enum(self):
rendering_targets = {
"local": "Local machine rendering",
"frames": "Use existing frames",
}
if "farm_rendering" in self.instance_attributes:
rendering_targets["farm"] = "Farm rendering"
return EnumDef(
"render_target", items=rendering_targets, label="Render target"
)
def _get_reviewable_bool(self):
return BoolDef(
"review",
default=("reviewable" in self.instance_attributes),
label="Review",
)
def _get_image_format_enum(self):
image_format_options = ["exr", "tga", "tif", "png", "jpg"]
return EnumDef(
"image_format",
items=image_format_options,
default=self.image_format,
label="Output Image Format",
)

View file

@ -0,0 +1,64 @@
from openpype.lib import NumberDef
from openpype.hosts.fusion.api.plugin import GenericCreateSaver
from openpype.hosts.fusion.api import get_current_comp
class CreateImageSaver(GenericCreateSaver):
"""Fusion Saver to generate single image.
Created to explicitly separate single ('image') or
multi frame('render) outputs.
This might be temporary creator until 'alias' functionality will be
implemented to limit creation of additional product types with similar, but
not the same workflows.
"""
identifier = "io.openpype.creators.fusion.imagesaver"
label = "Image (saver)"
name = "image"
family = "image"
description = "Fusion Saver to generate image"
default_frame = 0
def get_detail_description(self):
return """Fusion Saver to generate single image.
This creator is expected for publishing of single frame `image` product
type.
Artist should provide frame number (integer) to specify which frame
should be published. It must be inside of global timeline frame range.
Supports local and deadline rendering.
Supports selection from predefined set of output file extensions:
- exr
- tga
- png
- tif
- jpg
Created to explicitly separate single frame ('image') or
multi frame ('render') outputs.
"""
def get_pre_create_attr_defs(self):
"""Settings for create page"""
attr_defs = [
self._get_render_target_enum(),
self._get_reviewable_bool(),
self._get_frame_int(),
self._get_image_format_enum(),
]
return attr_defs
def _get_frame_int(self):
return NumberDef(
"frame",
default=self.default_frame,
label="Frame",
tooltip="Set frame to be rendered, must be inside of global "
"timeline range"
)

View file

@ -1,187 +1,42 @@
from copy import deepcopy
import os
from openpype.lib import EnumDef
from openpype.hosts.fusion.api import (
get_current_comp,
comp_lock_and_undo_chunk,
)
from openpype.lib import (
BoolDef,
EnumDef,
)
from openpype.pipeline import (
legacy_io,
Creator as NewCreator,
CreatedInstance,
Anatomy,
)
from openpype.hosts.fusion.api.plugin import GenericCreateSaver
class CreateSaver(NewCreator):
class CreateSaver(GenericCreateSaver):
"""Fusion Saver to generate image sequence of 'render' product type.
Original Saver creator targeted for 'render' product type. It uses
original not to descriptive name because of values in Settings.
"""
identifier = "io.openpype.creators.fusion.saver"
label = "Render (saver)"
name = "render"
family = "render"
default_variants = ["Main", "Mask"]
description = "Fusion Saver to generate image sequence"
icon = "fa5.eye"
instance_attributes = ["reviewable"]
image_format = "exr"
default_frame_range_option = "asset_db"
# TODO: This should be renamed together with Nuke so it is aligned
temp_rendering_path_template = (
"{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}"
)
def get_detail_description(self):
return """Fusion Saver to generate image sequence.
def create(self, subset_name, instance_data, pre_create_data):
self.pass_pre_attributes_to_instance(instance_data, pre_create_data)
This creator is expected for publishing of image sequences for 'render'
product type. (But can publish even single frame 'render'.)
instance_data.update(
{"id": "pyblish.avalon.instance", "subset": subset_name}
)
Select what should be source of render range:
- "Current asset context" - values set on Asset in DB (Ftrack)
- "From render in/out" - from node itself
- "From composition timeline" - from timeline
comp = get_current_comp()
with comp_lock_and_undo_chunk(comp):
args = (-32768, -32768) # Magical position numbers
saver = comp.AddTool("Saver", *args)
Supports local and farm rendering.
self._update_tool_with_data(saver, data=instance_data)
# Register the CreatedInstance
instance = CreatedInstance(
family=self.family,
subset_name=subset_name,
data=instance_data,
creator=self,
)
data = instance.data_to_store()
self._imprint(saver, data)
# Insert the transient data
instance.transient_data["tool"] = saver
self._add_instance_to_context(instance)
return instance
def collect_instances(self):
comp = get_current_comp()
tools = comp.GetToolList(False, "Saver").values()
for tool in tools:
data = self.get_managed_tool_data(tool)
if not data:
continue
# Add instance
created_instance = CreatedInstance.from_existing(data, self)
# Collect transient data
created_instance.transient_data["tool"] = tool
self._add_instance_to_context(created_instance)
def update_instances(self, update_list):
for created_inst, _changes in update_list:
new_data = created_inst.data_to_store()
tool = created_inst.transient_data["tool"]
self._update_tool_with_data(tool, new_data)
self._imprint(tool, new_data)
def remove_instances(self, instances):
for instance in instances:
# Remove the tool from the scene
tool = instance.transient_data["tool"]
if tool:
tool.Delete()
# Remove the collected CreatedInstance to remove from UI directly
self._remove_instance_from_context(instance)
def _imprint(self, tool, data):
# Save all data in a "openpype.{key}" = value data
# Instance id is the tool's name so we don't need to imprint as data
data.pop("instance_id", None)
active = data.pop("active", None)
if active is not None:
# Use active value to set the passthrough state
tool.SetAttrs({"TOOLB_PassThrough": not active})
for key, value in data.items():
tool.SetData(f"openpype.{key}", value)
def _update_tool_with_data(self, tool, data):
"""Update tool node name and output path based on subset data"""
if "subset" not in data:
return
original_subset = tool.GetData("openpype.subset")
original_format = tool.GetData(
"openpype.creator_attributes.image_format"
)
subset = data["subset"]
if (
original_subset != subset
or original_format != data["creator_attributes"]["image_format"]
):
self._configure_saver_tool(data, tool, subset)
def _configure_saver_tool(self, data, tool, subset):
formatting_data = deepcopy(data)
# get frame padding from anatomy templates
anatomy = Anatomy()
frame_padding = anatomy.templates["frame_padding"]
# get output format
ext = data["creator_attributes"]["image_format"]
# Subset change detected
workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"])
formatting_data.update(
{"workdir": workdir, "frame": "0" * frame_padding, "ext": ext}
)
# build file path to render
filepath = self.temp_rendering_path_template.format(**formatting_data)
comp = get_current_comp()
tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath))
# Rename tool
if tool.Name != subset:
print(f"Renaming {tool.Name} -> {subset}")
tool.SetAttrs({"TOOLS_Name": subset})
def get_managed_tool_data(self, tool):
"""Return data of the tool if it matches creator identifier"""
data = tool.GetData("openpype")
if not isinstance(data, dict):
return
required = {
"id": "pyblish.avalon.instance",
"creator_identifier": self.identifier,
}
for key, value in required.items():
if key not in data or data[key] != value:
return
# Get active state from the actual tool state
attrs = tool.GetAttrs()
passthrough = attrs["TOOLB_PassThrough"]
data["active"] = not passthrough
# Override publisher's UUID generation because tool names are
# already unique in Fusion in a comp
data["instance_id"] = tool.Name
return data
Supports selection from predefined set of output file extensions:
- exr
- tga
- png
- tif
- jpg
"""
def get_pre_create_attr_defs(self):
"""Settings for create page"""
@ -193,29 +48,6 @@ class CreateSaver(NewCreator):
]
return attr_defs
def get_instance_attr_defs(self):
"""Settings for publish page"""
return self.get_pre_create_attr_defs()
def pass_pre_attributes_to_instance(self, instance_data, pre_create_data):
creator_attrs = instance_data["creator_attributes"] = {}
for pass_key in pre_create_data.keys():
creator_attrs[pass_key] = pre_create_data[pass_key]
# These functions below should be moved to another file
# so it can be used by other plugins. plugin.py ?
def _get_render_target_enum(self):
rendering_targets = {
"local": "Local machine rendering",
"frames": "Use existing frames",
}
if "farm_rendering" in self.instance_attributes:
rendering_targets["farm"] = "Farm rendering"
return EnumDef(
"render_target", items=rendering_targets, label="Render target"
)
def _get_frame_range_enum(self):
frame_range_options = {
"asset_db": "Current asset context",
@ -227,42 +59,5 @@ class CreateSaver(NewCreator):
"frame_range_source",
items=frame_range_options,
label="Frame range source",
)
def _get_reviewable_bool(self):
return BoolDef(
"review",
default=("reviewable" in self.instance_attributes),
label="Review",
)
def _get_image_format_enum(self):
image_format_options = ["exr", "tga", "tif", "png", "jpg"]
return EnumDef(
"image_format",
items=image_format_options,
default=self.image_format,
label="Output Image Format",
)
def apply_settings(self, project_settings):
"""Method called on initialization of plugin to apply settings."""
# plugin settings
plugin_settings = project_settings["fusion"]["create"][
self.__class__.__name__
]
# individual attributes
self.instance_attributes = plugin_settings.get(
"instance_attributes", self.instance_attributes
)
self.default_variants = plugin_settings.get(
"default_variants", self.default_variants
)
self.temp_rendering_path_template = plugin_settings.get(
"temp_rendering_path_template", self.temp_rendering_path_template
)
self.image_format = plugin_settings.get(
"image_format", self.image_format
default=self.default_frame_range_option
)

View file

@ -95,7 +95,7 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin):
label = "Collect Inputs"
order = pyblish.api.CollectorOrder + 0.2
hosts = ["fusion"]
families = ["render"]
families = ["render", "image"]
def process(self, instance):

View file

@ -57,6 +57,18 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
start_with_handle = comp_start
end_with_handle = comp_end
frame = instance.data["creator_attributes"].get("frame")
# explicitly publishing only single frame
if frame is not None:
frame = int(frame)
start = frame
end = frame
handle_start = 0
handle_end = 0
start_with_handle = frame
end_with_handle = frame
# Include start and end render frame in label
subset = instance.data["subset"]
label = (

View file

@ -50,7 +50,7 @@ class CollectFusionRender(
continue
family = inst.data["family"]
if family != "render":
if family not in ["render", "image"]:
continue
task_name = context.data["task"]
@ -59,7 +59,7 @@ class CollectFusionRender(
instance_families = inst.data.get("families", [])
subset_name = inst.data["subset"]
instance = FusionRenderInstance(
family="render",
family=family,
tool=tool,
workfileComp=comp,
families=instance_families,

View file

@ -7,7 +7,7 @@ class FusionSaveComp(pyblish.api.ContextPlugin):
label = "Save current file"
order = pyblish.api.ExtractorOrder - 0.49
hosts = ["fusion"]
families = ["render", "workfile"]
families = ["render", "image", "workfile"]
def process(self, context):

View file

@ -17,7 +17,7 @@ class ValidateBackgroundDepth(
order = pyblish.api.ValidatorOrder
label = "Validate Background Depth 32 bit"
hosts = ["fusion"]
families = ["render"]
families = ["render", "image"]
optional = True
actions = [SelectInvalidAction, publish.RepairAction]

View file

@ -9,7 +9,7 @@ class ValidateFusionCompSaved(pyblish.api.ContextPlugin):
order = pyblish.api.ValidatorOrder
label = "Validate Comp Saved"
families = ["render"]
families = ["render", "image"]
hosts = ["fusion"]
def process(self, context):

View file

@ -15,7 +15,7 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder
label = "Validate Create Folder Checked"
families = ["render"]
families = ["render", "image"]
hosts = ["fusion"]
actions = [RepairAction, SelectInvalidAction]

View file

@ -17,7 +17,7 @@ class ValidateFilenameHasExtension(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder
label = "Validate Filename Has Extension"
families = ["render"]
families = ["render", "image"]
hosts = ["fusion"]
actions = [SelectInvalidAction]

View file

@ -0,0 +1,27 @@
import pyblish.api
from openpype.pipeline import PublishValidationError
class ValidateImageFrame(pyblish.api.InstancePlugin):
"""Validates that `image` product type contains only single frame."""
order = pyblish.api.ValidatorOrder
label = "Validate Image Frame"
families = ["image"]
hosts = ["fusion"]
def process(self, instance):
render_start = instance.data["frameStartHandle"]
render_end = instance.data["frameEndHandle"]
too_many_frames = (isinstance(instance.data["expectedFiles"], list)
and len(instance.data["expectedFiles"]) > 1)
if render_end - render_start > 0 or too_many_frames:
desc = ("Trying to render multiple frames. 'image' product type "
"is meant for single frame. Please use 'render' creator.")
raise PublishValidationError(
title="Frame range outside of comp range",
message=desc,
description=desc
)

View file

@ -7,8 +7,8 @@ class ValidateInstanceFrameRange(pyblish.api.InstancePlugin):
"""Validate instance frame range is within comp's global render range."""
order = pyblish.api.ValidatorOrder
label = "Validate Filename Has Extension"
families = ["render"]
label = "Validate Frame Range"
families = ["render", "image"]
hosts = ["fusion"]
def process(self, instance):

View file

@ -13,7 +13,7 @@ class ValidateSaverHasInput(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder
label = "Validate Saver Has Input"
families = ["render"]
families = ["render", "image"]
hosts = ["fusion"]
actions = [SelectInvalidAction]

View file

@ -9,7 +9,7 @@ class ValidateSaverPassthrough(pyblish.api.ContextPlugin):
order = pyblish.api.ValidatorOrder
label = "Validate Saver Passthrough"
families = ["render"]
families = ["render", "image"]
hosts = ["fusion"]
actions = [SelectInvalidAction]

View file

@ -64,7 +64,7 @@ class ValidateSaverResolution(
order = pyblish.api.ValidatorOrder
label = "Validate Asset Resolution"
families = ["render"]
families = ["render", "image"]
hosts = ["fusion"]
optional = True
actions = [SelectInvalidAction]

View file

@ -11,7 +11,7 @@ class ValidateUniqueSubsets(pyblish.api.ContextPlugin):
order = pyblish.api.ValidatorOrder
label = "Validate Unique Subsets"
families = ["render"]
families = ["render", "image"]
hosts = ["fusion"]
actions = [SelectInvalidAction]

View file

@ -32,6 +32,18 @@
"farm_rendering"
],
"image_format": "exr"
},
"CreateImageSaver": {
"temp_rendering_path_template": "{workdir}/renders/fusion/{subset}/{subset}.{ext}",
"default_variants": [
"Main",
"Mask"
],
"instance_attributes": [
"reviewable",
"farm_rendering"
],
"image_format": "exr"
}
},
"publish": {

View file

@ -74,7 +74,56 @@
"type": "dict",
"collapsible": true,
"key": "CreateSaver",
"label": "Create Saver",
"label": "Create Render Saver",
"is_group": true,
"children": [
{
"type": "text",
"key": "temp_rendering_path_template",
"label": "Temporary rendering path template"
},
{
"type": "list",
"key": "default_variants",
"label": "Default variants",
"object_type": {
"type": "text"
}
},
{
"key": "instance_attributes",
"label": "Instance attributes",
"type": "enum",
"multiselection": true,
"enum_items": [
{
"reviewable": "Reviewable"
},
{
"farm_rendering": "Farm rendering"
}
]
},
{
"key": "image_format",
"label": "Output Image Format",
"type": "enum",
"multiselect": false,
"enum_items": [
{"exr": "exr"},
{"tga": "tga"},
{"png": "png"},
{"tif": "tif"},
{"jpg": "jpg"}
]
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "CreateImageSaver",
"label": "Create Image Saver",
"is_group": true,
"children": [
{

View file

@ -25,6 +25,24 @@ def _create_saver_instance_attributes_enum():
]
def _image_format_enum():
return [
{"value": "exr", "label": "exr"},
{"value": "tga", "label": "tga"},
{"value": "png", "label": "png"},
{"value": "tif", "label": "tif"},
{"value": "jpg", "label": "jpg"},
]
def _frame_range_options_enum():
return [
{"value": "asset_db", "label": "Current asset context"},
{"value": "render_range", "label": "From render in/out"},
{"value": "comp_range", "label": "From composition timeline"},
]
class CreateSaverPluginModel(BaseSettingsModel):
_isGroup = True
temp_rendering_path_template: str = Field(
@ -59,10 +77,29 @@ class HooksModel(BaseSettingsModel):
)
class CreateSaverModel(CreateSaverPluginModel):
default_frame_range_option: str = Field(
default="asset_db",
enum_resolver=_frame_range_options_enum,
title="Default frame range source"
)
class CreateImageSaverModel(CreateSaverPluginModel):
default_frame: int = Field(
0,
title="Default rendered frame"
)
class CreatPluginsModel(BaseSettingsModel):
CreateSaver: CreateSaverPluginModel = Field(
default_factory=CreateSaverPluginModel,
title="Create Saver"
CreateSaver: CreateSaverModel = Field(
default_factory=CreateSaverModel,
title="Create Saver",
description="Creator for render product type (eg. sequence)"
)
CreateImageSaver: CreateImageSaverModel = Field(
default_factory=CreateImageSaverModel,
title="Create Image Saver",
description="Creator for image product type (eg. single)"
)
@ -117,15 +154,21 @@ DEFAULT_VALUES = {
"reviewable",
"farm_rendering"
],
"output_formats": [
"exr",
"jpg",
"jpeg",
"jpg",
"tiff",
"png",
"tga"
]
"image_format": "exr",
"default_frame_range_option": "asset_db"
},
"CreateImageSaver": {
"temp_rendering_path_template": "{workdir}/renders/fusion/{product[name]}/{product[name]}.{ext}",
"default_variants": [
"Main",
"Mask"
],
"instance_attributes": [
"reviewable",
"farm_rendering"
],
"image_format": "exr",
"default_frame": 0
}
}
}

View file

@ -1 +1 @@
__version__ = "0.1.2"
__version__ = "0.1.3"