Merge branch 'develop' into enhancement/maya_yeti_user_variables

This commit is contained in:
Roy Nieterau 2024-03-27 14:51:52 +01:00 committed by GitHub
commit ffa368f0e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 298 additions and 97 deletions

View file

@ -14,3 +14,15 @@ AYON_SERVER_ENABLED = True
# Indicate if AYON entities should be used instead of OpenPype entities
USE_AYON_ENTITIES = True
# -------------------------
__all__ = (
"__version__",
# Deprecated
"AYON_CORE_ROOT",
"PACKAGE_DIR",
"PLUGINS_DIR",
"AYON_SERVER_ENABLED",
"USE_AYON_ENTITIES",
)

View file

@ -3,7 +3,6 @@
import os
import sys
import json
import warnings
class Commands:

View file

@ -31,6 +31,7 @@ __all__ = [
"get_stub",
# pipeline
"AfterEffectsHost",
"ls",
"containerise",

View file

@ -1,14 +1,11 @@
import os
import re
import tempfile
import attr
import attr
import pyblish.api
from ayon_core.settings import get_project_settings
from ayon_core.pipeline import publish
from ayon_core.pipeline.publish import RenderInstance
from ayon_core.hosts.aftereffects.api import get_stub

View file

@ -4,7 +4,6 @@ import bpy
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import plugin
from ayon_core.hosts.blender.api.pipeline import AVALON_PROPERTY
class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin):

View file

@ -4,7 +4,6 @@ import bpy
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import plugin
from ayon_core.hosts.blender.api.pipeline import AVALON_PROPERTY
class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin):

View file

@ -23,7 +23,7 @@ from .lib import (
reset_segment_selection,
get_segment_attributes,
get_clips_in_reels,
get_reformated_filename,
get_reformatted_filename,
get_frame_from_filename,
get_padding_from_filename,
maintained_object_duplication,
@ -101,7 +101,7 @@ __all__ = [
"reset_segment_selection",
"get_segment_attributes",
"get_clips_in_reels",
"get_reformated_filename",
"get_reformatted_filename",
"get_frame_from_filename",
"get_padding_from_filename",
"maintained_object_duplication",

View file

@ -607,7 +607,7 @@ def get_clips_in_reels(project):
return output_clips
def get_reformated_filename(filename, padded=True):
def get_reformatted_filename(filename, padded=True):
"""
Return fixed python expression path
@ -618,7 +618,7 @@ def get_reformated_filename(filename, padded=True):
type: string with reformatted path
Example:
get_reformated_filename("plate.1001.exr") > plate.%04d.exr
get_reformatted_filename("plate.1001.exr") > plate.%04d.exr
"""
found = FRAME_PATTERN.search(filename)

View file

@ -256,7 +256,7 @@ def create_otio_reference(clip_data, fps=None):
if not otio_ex_ref_item:
dirname, file_name = os.path.split(path)
file_name = utils.get_reformated_filename(file_name, padded=False)
file_name = utils.get_reformatted_filename(file_name, padded=False)
reformated_path = os.path.join(dirname, file_name)
# in case old OTIO or video file create `ExternalReference`
otio_ex_ref_item = otio.schema.ExternalReference(

View file

@ -21,7 +21,7 @@ def frames_to_seconds(frames, framerate):
return otio.opentime.to_seconds(rt)
def get_reformated_filename(filename, padded=True):
def get_reformatted_filename(filename, padded=True):
"""
Return fixed python expression path
@ -32,7 +32,7 @@ def get_reformated_filename(filename, padded=True):
type: string with reformatted path
Example:
get_reformated_filename("plate.1001.exr") > plate.%04d.exr
get_reformatted_filename("plate.1001.exr") > plate.%04d.exr
"""
found = FRAME_PATTERN.search(filename)

View file

@ -28,7 +28,6 @@ from ayon_core.tools.utils import host_tools
from .lib import (
get_current_comp,
comp_lock_and_undo_chunk,
validate_comp_prefs
)

View file

@ -1,6 +1,11 @@
from ayon_core.lib import EnumDef
from ayon_core.lib import (
UILabelDef,
NumberDef,
EnumDef
)
from ayon_core.hosts.fusion.api.plugin import GenericCreateSaver
from ayon_core.hosts.fusion.api.lib import get_current_comp
class CreateSaver(GenericCreateSaver):
@ -45,6 +50,7 @@ class CreateSaver(GenericCreateSaver):
self._get_reviewable_bool(),
self._get_frame_range_enum(),
self._get_image_format_enum(),
*self._get_custom_frame_range_attribute_defs()
]
return attr_defs
@ -53,6 +59,7 @@ class CreateSaver(GenericCreateSaver):
"current_folder": "Current Folder context",
"render_range": "From render in/out",
"comp_range": "From composition timeline",
"custom_range": "Custom frame range",
}
return EnumDef(
@ -61,3 +68,82 @@ class CreateSaver(GenericCreateSaver):
label="Frame range source",
default=self.default_frame_range_option
)
@staticmethod
def _get_custom_frame_range_attribute_defs() -> list:
# Define custom frame range defaults based on current comp
# timeline settings (if a comp is currently open)
comp = get_current_comp()
if comp is not None:
attrs = comp.GetAttrs()
frame_defaults = {
"frameStart": int(attrs["COMPN_GlobalStart"]),
"frameEnd": int(attrs["COMPN_GlobalEnd"]),
"handleStart": int(
attrs["COMPN_RenderStart"] - attrs["COMPN_GlobalStart"]
),
"handleEnd": int(
attrs["COMPN_GlobalEnd"] - attrs["COMPN_RenderEnd"]
),
}
else:
frame_defaults = {
"frameStart": 1001,
"frameEnd": 1100,
"handleStart": 0,
"handleEnd": 0
}
return [
UILabelDef(
label="<br><b>Custom Frame Range</b><br>"
"<i>only used with 'Custom frame range' source</i>"
),
NumberDef(
"custom_frameStart",
label="Frame Start",
default=frame_defaults["frameStart"],
minimum=0,
decimals=0,
tooltip=(
"Set the start frame for the export.\n"
"Only used if frame range source is 'Custom frame range'."
)
),
NumberDef(
"custom_frameEnd",
label="Frame End",
default=frame_defaults["frameEnd"],
minimum=0,
decimals=0,
tooltip=(
"Set the end frame for the export.\n"
"Only used if frame range source is 'Custom frame range'."
)
),
NumberDef(
"custom_handleStart",
label="Handle Start",
default=frame_defaults["handleStart"],
minimum=0,
decimals=0,
tooltip=(
"Set the start handles for the export, this will be "
"added before the start frame.\n"
"Only used if frame range source is 'Custom frame range'."
)
),
NumberDef(
"custom_handleEnd",
label="Handle End",
default=frame_defaults["handleEnd"],
minimum=0,
decimals=0,
tooltip=(
"Set the end handles for the export, this will be added "
"after the end frame.\n"
"Only used if frame range source is 'Custom frame range'."
)
)
]

View file

@ -57,6 +57,14 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
start_with_handle = comp_start
end_with_handle = comp_end
if frame_range_source == "custom_range":
start = int(instance.data["custom_frameStart"])
end = int(instance.data["custom_frameEnd"])
handle_start = int(instance.data["custom_handleStart"])
handle_end = int(instance.data["custom_handleEnd"])
start_with_handle = start - handle_start
end_with_handle = end + handle_end
frame = instance.data["creator_attributes"].get("frame")
# explicitly publishing only single frame
if frame is not None:

View file

@ -1,8 +1,8 @@
import os
import pyblish.api
import pyblish.api
class CollectAudio(pyblish.api.InstancePlugin):
"""
Collect relative path for audio file to instance.

View file

@ -1,10 +1,12 @@
import os
import hiero.core.events
from ayon_core.lib import Logger, register_event_callback
from .lib import (
sync_avalon_data_to_workfile,
launch_workfiles_app,
selection_changed_timeline,
before_project_save,
)
from .tags import add_tags_to_workfile

View file

@ -3,9 +3,11 @@
# Note: This only prints the text data that is visible in the active Spreadsheet View.
# If you've filtered text, only the visible text will be printed to the CSV file
# Usage: Copy to ~/.hiero/Python/StartupUI
import os
import csv
import hiero.core.events
import hiero.ui
import os, csv
try:
from PySide.QtGui import *
from PySide.QtCore import *

View file

@ -1,5 +1,5 @@
from itertools import product
import re
import pyblish.api

View file

@ -447,7 +447,7 @@ def maintained_selection():
node.setSelected(on=True)
def reset_framerange():
def reset_framerange(fps=True, frame_range=True):
"""Set frame range and FPS to current folder."""
project_name = get_current_project_name()
@ -456,29 +456,32 @@ def reset_framerange():
folder_entity = ayon_api.get_folder_by_path(project_name, folder_path)
folder_attributes = folder_entity["attrib"]
# Get FPS
fps = get_folder_fps(folder_entity)
# Set FPS
if fps:
fps = get_folder_fps(folder_entity)
print("Setting scene FPS to {}".format(int(fps)))
set_scene_fps(fps)
# Get Start and End Frames
frame_start = folder_attributes.get("frameStart")
frame_end = folder_attributes.get("frameEnd")
if frame_range:
if frame_start is None or frame_end is None:
log.warning("No edit information found for '{}'".format(folder_path))
return
# Set Start and End Frames
frame_start = folder_attributes.get("frameStart")
frame_end = folder_attributes.get("frameEnd")
handle_start = folder_attributes.get("handleStart", 0)
handle_end = folder_attributes.get("handleEnd", 0)
if frame_start is None or frame_end is None:
log.warning("No edit information found for '%s'", folder_path)
return
frame_start -= int(handle_start)
frame_end += int(handle_end)
handle_start = folder_attributes.get("handleStart", 0)
handle_end = folder_attributes.get("handleEnd", 0)
# Set frame range and FPS
print("Setting scene FPS to {}".format(int(fps)))
set_scene_fps(fps)
hou.playbar.setFrameRange(frame_start, frame_end)
hou.playbar.setPlaybackRange(frame_start, frame_end)
hou.setFrame(frame_start)
frame_start -= int(handle_start)
frame_end += int(handle_end)
# Set frame range and FPS
hou.playbar.setFrameRange(frame_start, frame_end)
hou.playbar.setPlaybackRange(frame_start, frame_end)
hou.setFrame(frame_start)
def get_main_window():
@ -993,3 +996,84 @@ def add_self_publish_button(node):
template = node.parmTemplateGroup()
template.insertBefore((0,), button_parm)
node.setParmTemplateGroup(template)
def update_content_on_context_change():
"""Update all Creator instances to current asset"""
host = registered_host()
context = host.get_current_context()
folder_path = context["folder_path"]
task = context["task_name"]
create_context = CreateContext(host, reset=True)
for instance in create_context.instances:
instance_folder_path = instance.get("folderPath")
if instance_folder_path and instance_folder_path != folder_path:
instance["folderPath"] = folder_path
instance_task = instance.get("task")
if instance_task and instance_task != task:
instance["task"] = task
create_context.save_changes()
def prompt_reset_context():
"""Prompt the user what context settings to reset.
This prompt is used on saving to a different task to allow the scene to
get matched to the new context.
"""
# TODO: Cleanup this prototyped mess of imports and odd dialog
from ayon_core.tools.attribute_defs.dialog import (
AttributeDefinitionsDialog
)
from ayon_core.style import load_stylesheet
from ayon_core.lib import BoolDef, UILabelDef
definitions = [
UILabelDef(
label=(
"You are saving your workfile into a different folder or task."
"\n\n"
"Would you like to update some settings to the new context?\n"
)
),
BoolDef(
"fps",
label="FPS",
tooltip="Reset workfile FPS",
default=True
),
BoolDef(
"frame_range",
label="Frame Range",
tooltip="Reset workfile start and end frame ranges",
default=True
),
BoolDef(
"instances",
label="Publish instances",
tooltip="Update all publish instance's folder and task to match "
"the new folder and task",
default=True
),
]
dialog = AttributeDefinitionsDialog(definitions)
dialog.setWindowTitle("Saving to different context.")
dialog.setStyleSheet(load_stylesheet())
if not dialog.exec_():
return None
options = dialog.get_values()
if options["fps"] or options["frame_range"]:
reset_framerange(
fps=options["fps"],
frame_range=options["frame_range"]
)
if options["instances"]:
update_content_on_context_change()
dialog.deleteLater()

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Pipeline tools for OpenPype Houdini integration."""
import os
import sys
import logging
import hou # noqa
@ -39,6 +38,9 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
# Track whether the workfile tool is about to save
ABOUT_TO_SAVE = False
class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
name = "houdini"
@ -61,10 +63,12 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
log.info("Installing callbacks ... ")
# register_event_callback("init", on_init)
self._register_callbacks()
register_event_callback("workfile.save.before", before_workfile_save)
register_event_callback("before.save", before_save)
register_event_callback("save", on_save)
register_event_callback("open", on_open)
register_event_callback("new", on_new)
register_event_callback("taskChanged", on_task_changed)
self._has_been_setup = True
@ -287,6 +291,11 @@ def ls():
yield parse_container(container)
def before_workfile_save(event):
global ABOUT_TO_SAVE
ABOUT_TO_SAVE = True
def before_save():
return lib.validate_fps()
@ -298,6 +307,21 @@ def on_save():
# update houdini vars
lib.update_houdini_vars_context_dialog()
nodes = lib.get_id_required_nodes()
for node, new_id in lib.generate_ids(nodes):
lib.set_id(node, new_id, overwrite=False)
# We are now starting the actual save directly
global ABOUT_TO_SAVE
ABOUT_TO_SAVE = False
def on_task_changed():
global ABOUT_TO_SAVE
if not IS_HEADLESS and ABOUT_TO_SAVE:
# Let's prompt the user to update the context settings or not
lib.prompt_reset_context()
def _show_outdated_content_popup():
# Get main window

View file

@ -7,7 +7,6 @@ from ayon_core.hosts.max.api.lib import (
maintained_selection,
object_transform_set
)
from ayon_core.hosts.max.api.lib import maintained_selection
from ayon_core.hosts.max.api.pipeline import (
containerise,
get_previous_loaded_object,

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import pyblish.api
from ayon_core.pipeline import PublishValidationError
from pymxs import runtime as rt
class ValidateCameraContent(pyblish.api.InstancePlugin):

View file

@ -12,7 +12,6 @@ from ayon_core.hosts.maya.api.lib import (
unique_namespace,
get_attribute_input,
maintained_selection,
convert_to_maya_fps
)
from ayon_core.hosts.maya.api.pipeline import containerise
from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type

View file

@ -1,5 +1,3 @@
import os
import maya.cmds as cmds
from ayon_core.hosts.maya.api.pipeline import containerise

View file

@ -1,10 +1,8 @@
import os
import copy
from ayon_core.lib import EnumDef
from ayon_core.pipeline import (
load,
get_representation_context,
get_current_host_name,
)
from ayon_core.pipeline.load.utils import get_representation_path_from_context

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Create Unreal Skeletal Mesh data to be extracted as FBX."""
import os
from contextlib import contextmanager
from maya import cmds # noqa

View file

@ -7,7 +7,6 @@ from maya import cmds
import pyblish.api
from ayon_core.hosts.maya.api.lib import extract_alembic
from ayon_core.pipeline import publish
from ayon_core.lib import StringTemplate
class ExtractWorkfileXgen(publish.Extractor):

View file

@ -9,7 +9,6 @@ from ayon_core.pipeline import publish
from ayon_core.hosts.maya.api.lib import (
maintained_selection, attribute_values, write_xgen_file, delete_after
)
from ayon_core.lib import StringTemplate
class ExtractXgen(publish.Extractor):

View file

@ -30,13 +30,11 @@ from ayon_core.tools.utils import host_tools
from ayon_core.hosts.nuke import NUKE_ROOT_DIR
from ayon_core.tools.workfile_template_build import open_template_ui
from .command import viewer_update_and_undo_stop
from .lib import (
Context,
ROOT_DATA_KNOB,
INSTANCE_DATA_KNOB,
get_main_window,
add_publish_knob,
WorkfileSettings,
# TODO: remove this once workfile builder will be removed
process_workfile_builder,

View file

@ -6,7 +6,6 @@ import six
import random
import string
from collections import OrderedDict, defaultdict
from abc import abstractmethod
from ayon_core.settings import get_current_project_settings
from ayon_core.lib import (
@ -14,7 +13,6 @@ from ayon_core.lib import (
EnumDef
)
from ayon_core.pipeline import (
LegacyCreator,
LoaderPlugin,
CreatorError,
Creator as NewCreator,
@ -34,18 +32,13 @@ from ayon_core.lib.transcoding import (
from .lib import (
INSTANCE_DATA_KNOB,
Knobby,
check_product_name_exists,
maintained_selection,
get_avalon_knob_data,
set_avalon_knob_data,
add_publish_knob,
get_nuke_imageio_settings,
set_node_knobs_from_settings,
set_node_data,
get_node_data,
get_view_process_node,
get_viewer_config_from_string,
deprecated,
get_filenames_without_hash,
link_knobs
)

View file

@ -1,6 +1,5 @@
import os
import math
from pprint import pformat
import nuke

View file

@ -1,3 +1,5 @@
import json
import nuke
import six
import pyblish.api

View file

@ -1,6 +1,6 @@
import os
import pyblish.api
import clique
from ayon_core.pipeline import PublishXmlValidationError
from ayon_core.pipeline.publish import get_errored_instances_from_context

View file

@ -11,7 +11,7 @@ from wsrpc_aiohttp import (
import ayon_api
from qtpy import QtCore
from ayon_core.lib import Logger, StringTemplate
from ayon_core.lib import Logger
from ayon_core.pipeline import (
registered_host,
Anatomy,

View file

@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
"""Close PS after publish. For Webpublishing only."""
import os
import pyblish.api
from ayon_core.hosts.photoshop import api as photoshop

View file

@ -12,17 +12,14 @@ import substance_painter.project
import pyblish.api
from ayon_core.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost
from ayon_core.settings import (
get_current_project_settings,
get_project_settings,
)
from ayon_core.settings import get_current_project_settings
from ayon_core.pipeline.template_data import get_template_data_with_names
from ayon_core.pipeline import (
register_creator_plugin_path,
register_loader_plugin_path,
AVALON_CONTAINER_ID,
Anatomy
Anatomy,
)
from ayon_core.lib import (
StringTemplate,

View file

@ -1,12 +1,11 @@
import os
from pathlib import Path
import unreal
import pyblish.api
from ayon_core.pipeline import get_current_project_name
from ayon_core.pipeline import Anatomy
from ayon_core.hosts.unreal.api import pipeline
import pyblish.api
class CollectRenderInstances(pyblish.api.InstancePlugin):

View file

@ -1,3 +1,5 @@
# Backwards compatibility support
# - TODO should be removed before release 1.0.0
from ayon_core.addon import (
AYONAddon,
AddonsManager,
@ -12,3 +14,16 @@ from ayon_core.addon.base import (
ModulesManager = AddonsManager
TrayModulesManager = TrayAddonsManager
load_modules = load_addons
__all__ = (
"AYONAddon",
"AddonsManager",
"TrayAddonsManager",
"load_addons",
"OpenPypeModule",
"OpenPypeAddOn",
"ModulesManager",
"TrayModulesManager",
"load_modules",
)

View file

@ -1,6 +1,4 @@
import os
import re
import time
import json
import datetime
import requests

View file

@ -651,7 +651,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
return job_info, attr.asdict(plugin_info)
def _get_arnold_render_payload(self, data):
from maya import cmds
# Job Info
job_info = copy.deepcopy(self.job_info)
job_info.Name = self._job_info_label("Render")

View file

@ -7,7 +7,6 @@ from Deadline.Plugins import PluginType, DeadlinePlugin
from Deadline.Scripting import (
StringUtils,
FileUtils,
DirectoryUtils,
RepositoryUtils
)

View file

@ -12,7 +12,6 @@ from Deadline.Scripting import (
RepositoryUtils,
FileUtils,
DirectoryUtils,
ProcessUtils,
)
__version__ = "1.0.1"
VERSION_REGEX = re.compile(

View file

@ -2,7 +2,6 @@
"""Submitting render job to RoyalRender."""
import os
import json
import platform
import re
import tempfile
import uuid

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Submit jobs to RoyalRender."""
import tempfile
import platform
import pyblish.api
from ayon_core.modules.royalrender.api import (

View file

@ -1,5 +1,3 @@
import ayon_api
from ayon_core.settings import get_project_settings
from ayon_core.lib import filter_profiles, prepare_template_data

View file

@ -1,6 +1,6 @@
import pyblish.api
from ayon_core.pipeline import Anatomy
from typing import Tuple, Union, List
from typing import Tuple, List
class TimeData:

View file

@ -2,7 +2,6 @@ import os
import logging
from ayon_core.settings import get_project_settings
from ayon_core.pipeline import schema
from ayon_core.pipeline.plugin_discover import (
discover,
register_plugin,

View file

@ -2,7 +2,6 @@ import inspect
from abc import ABCMeta
import pyblish.api
from pyblish.plugin import MetaPlugin, ExplicitMetaPlugin
from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS
from ayon_core.lib import BoolDef
from .lib import (

View file

@ -13,3 +13,21 @@ from .items import (
)
from .lib import create_slates
from .example import example
__all__ = (
"FontFactory",
"BaseObj",
"load_default_style",
"MainFrame",
"Layer",
"BaseItem",
"ItemImage",
"ItemRectangle",
"ItemPlaceHolder",
"ItemText",
"ItemTable",
"TableField",
"create_slates",
"example",
)

View file

@ -1,4 +1,4 @@
from qtpy import QtWidgets, QtCore, QtGui
from qtpy import QtWidgets, QtCore
from ayon_core.lib.events import QueuedEventSystem
from ayon_core.tools.utils import PlaceholderLineEdit, GoToCurrentButton

View file

@ -1,4 +1,4 @@
from qtpy import QtWidgets, QtCore, QtGui
from qtpy import QtWidgets
from ayon_core.lib.events import QueuedEventSystem
from ayon_core.tools.utils import PlaceholderLineEdit, FoldersWidget

View file

@ -1,7 +1,3 @@
import os
import json
import time
from qtpy import QtWidgets, QtCore
from .widgets import (

View file

@ -1,4 +1,4 @@
from qtpy import QtWidgets, QtCore, QtGui
from qtpy import QtCore, QtGui
from ayon_core.style import get_default_entity_icon_color
from ayon_core.tools.utils import get_qt_icon

View file

@ -7,8 +7,6 @@ from __future__ import (
import os
import sys
import numbers
import copy
import collections
from qtpy import QtCore

View file

@ -7,7 +7,6 @@ from qtpy import QtWidgets, QtCore, QtGui
import qtawesome
from ayon_core.style import (
get_default_entity_icon_color,
get_objected_colors,
get_app_icon_path,
)

View file

@ -2,7 +2,7 @@ import re
import logging
import qtpy
from qtpy import QtCore, QtGui
from qtpy import QtCore
log = logging.getLogger(__name__)

View file

@ -1,7 +1,6 @@
import os
import sys
import re
import json
import shutil
import argparse
import zipfile

View file

@ -1,7 +1,6 @@
from ayon_server.settings import (
BaseSettingsModel,
SettingsField,
ensure_unique_names
)
from .general import (

View file

@ -1,7 +1,6 @@
from ayon_server.settings import (
BaseSettingsModel,
SettingsField,
ensure_unique_names,
)
from .imageio import TVPaintImageIOModel