Merge branch 'develop' into bugfix/traypublisher-explicit-colorspace

This commit is contained in:
Jakub Ježek 2023-12-19 15:55:53 +01:00 committed by GitHub
commit 302b92d1c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 361 additions and 529 deletions

View file

@ -35,6 +35,9 @@ body:
label: Version
description: What version are you running? Look to OpenPype Tray
options:
- 3.18.2-nightly.1
- 3.18.1
- 3.18.1-nightly.1
- 3.18.0
- 3.17.7
- 3.17.7-nightly.7
@ -132,9 +135,6 @@ body:
- 3.15.4-nightly.3
- 3.15.4-nightly.2
- 3.15.4-nightly.1
- 3.15.3
- 3.15.3-nightly.4
- 3.15.3-nightly.3
validations:
required: true
- type: dropdown

View file

@ -1,6 +1,27 @@
# Changelog
## [3.18.1](https://github.com/ynput/OpenPype/tree/3.18.1)
[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.0...3.18.1)
### **🚀 Enhancements**
<details>
<summary>AYON: Update ayon api to 1.0.0-rc.3 <a href="https://github.com/ynput/OpenPype/pull/6052">#6052</a></summary>
Updated ayon python api to 1.0.0-rc.3.
___
</details>
## [3.18.0](https://github.com/ynput/OpenPype/tree/3.18.0)

View file

@ -7,6 +7,10 @@ OpenPype
[![documentation](https://github.com/pypeclub/pype/actions/workflows/documentation.yml/badge.svg)](https://github.com/pypeclub/pype/actions/workflows/documentation.yml) ![GitHub VFX Platform](https://img.shields.io/badge/vfx%20platform-2022-lightgrey?labelColor=303846)
## Important Notice!
OpenPype as a standalone product has reach end of it's life and this repository is now used as a pipeline core code for [AYON](https://ynput.io/ayon/). You can read more details about the end of life process here https://community.ynput.io/t/openpype-end-of-life-timeline/877
Introduction
------------

View file

@ -1,6 +1,6 @@
# AfterEffects Integration
Requirements: This extension requires use of Javascript engine, which is
Requirements: This extension requires use of Javascript engine, which is
available since CC 16.0.
Please check your File>Project Settings>Expressions>Expressions Engine
@ -13,26 +13,28 @@ The After Effects integration requires two components to work; `extension` and `
To install the extension download [Extension Manager Command Line tool (ExManCmd)](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#option-2---exmancmd).
```
ExManCmd /install {path to avalon-core}\avalon\photoshop\extension.zxp
ExManCmd /install {path to addon}/api/extension.zxp
```
OR
download [Anastasiys Extension Manager](https://install.anastasiy.com/)
`{path to addon}` will be most likely in your AppData (on Windows, in your user data folder in Linux and MacOS.)
### Server
The easiest way to get the server and After Effects launch is with:
```
python -c ^"import avalon.photoshop;avalon.aftereffects.launch(""c:\Program Files\Adobe\Adobe After Effects 2020\Support Files\AfterFX.exe"")^"
python -c ^"import openpype.hosts.photoshop;openpype.hosts..aftereffects.launch(""c:\Program Files\Adobe\Adobe After Effects 2020\Support Files\AfterFX.exe"")^"
```
`avalon.aftereffects.launch` launches the application and server, and also closes the server when After Effects exists.
## Usage
The After Effects extension can be found under `Window > Extensions > OpenPype`. Once launched you should be presented with a panel like this:
The After Effects extension can be found under `Window > Extensions > AYON`. Once launched you should be presented with a panel like this:
![Avalon Panel](panel.PNG "Avalon Panel")
![Ayon Panel](panel.png "Ayon Panel")
## Developing
@ -43,8 +45,8 @@ When developing the extension you can load it [unsigned](https://github.com/Adob
When signing the extension you can use this [guide](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#package-distribute-install-guide).
```
ZXPSignCmd -selfSignedCert NA NA Avalon Avalon-After-Effects avalon extension.p12
ZXPSignCmd -sign {path to avalon-core}\avalon\aftereffects\extension {path to avalon-core}\avalon\aftereffects\extension.zxp extension.p12 avalon
ZXPSignCmd -selfSignedCert NA NA Ayon Avalon-After-Effects Ayon extension.p12
ZXPSignCmd -sign {path to addon}/api/extension {path to addon}/api/extension.zxp extension.p12 Ayon
```
### Plugin Examples
@ -52,14 +54,14 @@ ZXPSignCmd -sign {path to avalon-core}\avalon\aftereffects\extension {path to av
These plugins were made with the [polly config](https://github.com/mindbender-studio/config). To fully integrate and load, you will have to use this config and add `image` to the [integration plugin](https://github.com/mindbender-studio/config/blob/master/polly/plugins/publish/integrate_asset.py).
Expected deployed extension location on default Windows:
`c:\Program Files (x86)\Common Files\Adobe\CEP\extensions\com.openpype.AE.panel`
`c:\Program Files (x86)\Common Files\Adobe\CEP\extensions\io.ynput.AE.panel`
For easier debugging of Javascript:
https://community.adobe.com/t5/download-install/adobe-extension-debuger-problem/td-p/10911704?page=1
Add (optional) --enable-blink-features=ShadowDOMV0,CustomElementsV0 when starting Chrome
then localhost:8092
Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01
Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01
## Resources
- https://javascript-tools-guide.readthedocs.io/introduction/index.html
- https://github.com/Adobe-CEP/Getting-Started-guides

View file

@ -1,32 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionList>
<Extension Id="com.openpype.AE.panel">
<Extension Id="io.ynput.AE.panel">
<HostList>
<!-- Comment Host tags according to the apps you want your panel to support -->
<!-- Photoshop -->
<Host Name="PHXS" Port="8088"/>
<!-- Illustrator -->
<Host Name="ILST" Port="8089"/>
<!-- InDesign -->
<Host Name="IDSN" Port="8090" />
<!-- Premiere -->
<Host Name="PPRO" Port="8091" />
<!-- AfterEffects -->
<Host Name="AEFT" Port="8092" />
<!-- PRELUDE -->
<Host Name="PRLD" Port="8093" />
<!-- FLASH Pro -->
<Host Name="FLPR" Port="8094" />
</HostList>
</Extension>
</ExtensionList>

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionManifest Version="8.0" ExtensionBundleId="com.openpype.AE.panel" ExtensionBundleVersion="1.0.27"
ExtensionBundleName="com.openpype.AE.panel" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ExtensionManifest Version="8.0" ExtensionBundleId="io.ynput.AE.panel" ExtensionBundleVersion="1.1.0"
ExtensionBundleName="io.ynput.AE.panel" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ExtensionList>
<Extension Id="com.openpype.AE.panel" Version="1.0" />
<Extension Id="io.ynput.AE.panel" Version="1.0" />
</ExtensionList>
<ExecutionEnvironment>
<HostList>
@ -38,7 +38,7 @@
</RequiredRuntimeList>
</ExecutionEnvironment>
<DispatchInfoList>
<Extension Id="com.openpype.AE.panel">
<Extension Id="io.ynput.AE.panel">
<DispatchInfo >
<Resources>
<MainPath>./index.html</MainPath>
@ -49,7 +49,7 @@
</Lifecycle>
<UI>
<Type>Panel</Type>
<Menu>OpenPype</Menu>
<Menu>AYON</Menu>
<Geometry>
<Size>
<Height>200</Height>
@ -66,7 +66,7 @@
</Geometry>
<Icons>
<Icon Type="Normal">./icons/iconNormal.png</Icon>
<Icon Type="Normal">./icons/ayon_logo.png</Icon>
<Icon Type="RollOver">./icons/iconRollover.png</Icon>
<Icon Type="Disabled">./icons/iconDisabled.png</Icon>
<Icon Type="DarkNormal">./icons/iconDarkNormal.png</Icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -14,7 +14,7 @@ from openpype.pipeline import (
legacy_io,
Creator as NewCreator,
CreatedInstance,
Anatomy
Anatomy,
)
@ -27,28 +27,21 @@ class CreateSaver(NewCreator):
description = "Fusion Saver to generate image sequence"
icon = "fa5.eye"
instance_attributes = [
"reviewable"
]
instance_attributes = ["reviewable"]
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}")
"{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
self.pass_pre_attributes_to_instance(instance_data, pre_create_data)
instance_data.update(
{"id": "pyblish.avalon.instance", "subset": subset_name}
)
instance_data.update({
"id": "pyblish.avalon.instance",
"subset": subset_name
})
# TODO: Add pre_create attributes to choose file format?
file_format = "OpenEXRFormat"
comp = get_current_comp()
with comp_lock_and_undo_chunk(comp):
args = (-32768, -32768) # Magical position numbers
@ -56,19 +49,6 @@ class CreateSaver(NewCreator):
self._update_tool_with_data(saver, data=instance_data)
saver["OutputFormat"] = file_format
# Check file format settings are available
if saver[file_format] is None:
raise RuntimeError(
f"File format is not set to {file_format}, this is a bug"
)
# Set file format attributes
saver[file_format]["Depth"] = 0 # Auto | float16 | float32
# TODO Is this needed?
saver[file_format]["SaveAlpha"] = 1
# Register the CreatedInstance
instance = CreatedInstance(
family=self.family,
@ -140,8 +120,15 @@ class CreateSaver(NewCreator):
return
original_subset = tool.GetData("openpype.subset")
original_format = tool.GetData(
"openpype.creator_attributes.image_format"
)
subset = data["subset"]
if original_subset != 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):
@ -151,17 +138,17 @@ class CreateSaver(NewCreator):
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": "exr"
})
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)
filepath = self.temp_rendering_path_template.format(**formatting_data)
comp = get_current_comp()
tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath))
@ -201,7 +188,8 @@ class CreateSaver(NewCreator):
attr_defs = [
self._get_render_target_enum(),
self._get_reviewable_bool(),
self._get_frame_range_enum()
self._get_frame_range_enum(),
self._get_image_format_enum(),
]
return attr_defs
@ -209,11 +197,7 @@ class CreateSaver(NewCreator):
"""Settings for publish page"""
return self.get_pre_create_attr_defs()
def pass_pre_attributes_to_instance(
self,
instance_data,
pre_create_data
):
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]
@ -236,13 +220,13 @@ class CreateSaver(NewCreator):
frame_range_options = {
"asset_db": "Current asset context",
"render_range": "From render in/out",
"comp_range": "From composition timeline"
"comp_range": "From composition timeline",
}
return EnumDef(
"frame_range_source",
items=frame_range_options,
label="Frame range source"
label="Frame range source",
)
def _get_reviewable_bool(self):
@ -252,20 +236,33 @@ class CreateSaver(NewCreator):
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__]
)
plugin_settings = project_settings["fusion"]["create"][
self.__class__.__name__
]
# individual attributes
self.instance_attributes = plugin_settings.get(
"instance_attributes") or self.instance_attributes
self.default_variants = plugin_settings.get(
"default_variants") or self.default_variants
self.temp_rendering_path_template = (
plugin_settings.get("temp_rendering_path_template")
or self.temp_rendering_path_template
"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
)

View file

@ -146,11 +146,15 @@ class FusionRenderLocal(
staging_dir = os.path.dirname(path)
files = [os.path.basename(f) for f in expected_files]
if len(expected_files) == 1:
files = files[0]
repre = {
"name": ext[1:],
"ext": ext[1:],
"frameStart": f"%0{padding}d" % start,
"files": [os.path.basename(f) for f in expected_files],
"files": files,
"stagingDir": staging_dir,
}

View file

@ -511,3 +511,20 @@ def render_resolution(width, height):
finally:
rt.renderWidth = current_renderWidth
rt.renderHeight = current_renderHeight
@contextlib.contextmanager
def suspended_refresh():
"""Suspended refresh for scene and modify panel redraw.
"""
if is_headless():
yield
return
rt.disableSceneRedraw()
rt.suspendEditing()
try:
yield
finally:
rt.enableSceneRedraw()
rt.resumeEditing()

View file

@ -39,45 +39,41 @@ Note:
"""
import os
import pyblish.api
from openpype.pipeline import publish
from openpype.pipeline import publish, OptionalPyblishPluginMixin
from pymxs import runtime as rt
from openpype.hosts.max.api import maintained_selection
from openpype.hosts.max.api.lib import suspended_refresh
from openpype.lib import BoolDef
class ExtractAlembic(publish.Extractor):
class ExtractAlembic(publish.Extractor,
OptionalPyblishPluginMixin):
order = pyblish.api.ExtractorOrder
label = "Extract Pointcache"
hosts = ["max"]
families = ["pointcache"]
optional = True
def process(self, instance):
start = instance.data["frameStartHandle"]
end = instance.data["frameEndHandle"]
self.log.debug("Extracting pointcache ...")
if not self.is_active(instance.data):
return
parent_dir = self.staging_dir(instance)
file_name = "{name}.abc".format(**instance.data)
path = os.path.join(parent_dir, file_name)
# We run the render
self.log.info("Writing alembic '%s' to '%s'" % (file_name, parent_dir))
rt.AlembicExport.ArchiveType = rt.name("ogawa")
rt.AlembicExport.CoordinateSystem = rt.name("maya")
rt.AlembicExport.StartFrame = start
rt.AlembicExport.EndFrame = end
with maintained_selection():
# select and export
node_list = instance.data["members"]
rt.Select(node_list)
rt.exportFile(
path,
rt.name("noPrompt"),
selectedOnly=True,
using=rt.AlembicExport,
)
with suspended_refresh():
self._set_abc_attributes(instance)
with maintained_selection():
# select and export
node_list = instance.data["members"]
rt.Select(node_list)
rt.exportFile(
path,
rt.name("noPrompt"),
selectedOnly=True,
using=rt.AlembicExport,
)
if "representations" not in instance.data:
instance.data["representations"] = []
@ -89,3 +85,51 @@ class ExtractAlembic(publish.Extractor):
"stagingDir": parent_dir,
}
instance.data["representations"].append(representation)
def _set_abc_attributes(self, instance):
start = instance.data["frameStartHandle"]
end = instance.data["frameEndHandle"]
attr_values = self.get_attr_values_from_data(instance.data)
custom_attrs = attr_values.get("custom_attrs", False)
if not custom_attrs:
self.log.debug(
"No Custom Attributes included in this abc export...")
rt.AlembicExport.ArchiveType = rt.Name("ogawa")
rt.AlembicExport.CoordinateSystem = rt.Name("maya")
rt.AlembicExport.StartFrame = start
rt.AlembicExport.EndFrame = end
rt.AlembicExport.CustomAttributes = custom_attrs
@classmethod
def get_attribute_defs(cls):
return [
BoolDef("custom_attrs",
label="Custom Attributes",
default=False),
]
class ExtractCameraAlembic(ExtractAlembic):
"""Extract Camera with AlembicExport."""
label = "Extract Alembic Camera"
families = ["camera"]
class ExtractModel(ExtractAlembic):
"""Extract Geometry in Alembic Format"""
label = "Extract Geometry (Alembic)"
families = ["model"]
def _set_abc_attributes(self, instance):
attr_values = self.get_attr_values_from_data(instance.data)
custom_attrs = attr_values.get("custom_attrs", False)
if not custom_attrs:
self.log.debug(
"No Custom Attributes included in this abc export...")
rt.AlembicExport.ArchiveType = rt.name("ogawa")
rt.AlembicExport.CoordinateSystem = rt.name("maya")
rt.AlembicExport.CustomAttributes = custom_attrs
rt.AlembicExport.UVs = True
rt.AlembicExport.VertexColors = True
rt.AlembicExport.PreserveInstances = True

View file

@ -1,64 +0,0 @@
import os
import pyblish.api
from pymxs import runtime as rt
from openpype.hosts.max.api import maintained_selection
from openpype.pipeline import OptionalPyblishPluginMixin, publish
class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin):
"""Extract Camera with AlembicExport."""
order = pyblish.api.ExtractorOrder - 0.1
label = "Extract Alembic Camera"
hosts = ["max"]
families = ["camera"]
optional = True
def process(self, instance):
if not self.is_active(instance.data):
return
start = instance.data["frameStartHandle"]
end = instance.data["frameEndHandle"]
self.log.info("Extracting Camera ...")
stagingdir = self.staging_dir(instance)
filename = "{name}.abc".format(**instance.data)
path = os.path.join(stagingdir, filename)
# We run the render
self.log.info(f"Writing alembic '{filename}' to '{stagingdir}'")
rt.AlembicExport.ArchiveType = rt.Name("ogawa")
rt.AlembicExport.CoordinateSystem = rt.Name("maya")
rt.AlembicExport.StartFrame = start
rt.AlembicExport.EndFrame = end
rt.AlembicExport.CustomAttributes = True
with maintained_selection():
# select and export
node_list = instance.data["members"]
rt.Select(node_list)
rt.ExportFile(
path,
rt.Name("noPrompt"),
selectedOnly=True,
using=rt.AlembicExport,
)
self.log.info("Performing Extraction ...")
if "representations" not in instance.data:
instance.data["representations"] = []
representation = {
"name": "abc",
"ext": "abc",
"files": filename,
"stagingDir": stagingdir,
"frameStart": start,
"frameEnd": end,
}
instance.data["representations"].append(representation)
self.log.info(f"Extracted instance '{instance.name}' to: {path}")

View file

@ -20,13 +20,10 @@ class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin):
if not self.is_active(instance.data):
return
self.log.debug("Extracting Camera ...")
stagingdir = self.staging_dir(instance)
filename = "{name}.fbx".format(**instance.data)
filepath = os.path.join(stagingdir, filename)
self.log.info(f"Writing fbx file '{filename}' to '{filepath}'")
rt.FBXExporterSetParam("Animation", True)
rt.FBXExporterSetParam("Cameras", True)
rt.FBXExporterSetParam("AxisConversionMethod", "Animation")

View file

@ -26,7 +26,6 @@ class ExtractMaxSceneRaw(publish.Extractor, OptionalPyblishPluginMixin):
filename = "{name}.max".format(**instance.data)
max_path = os.path.join(stagingdir, filename)
self.log.info("Writing max file '%s' to '%s'" % (filename, max_path))
if "representations" not in instance.data:
instance.data["representations"] = []

View file

@ -1,63 +0,0 @@
import os
import pyblish.api
from openpype.pipeline import publish, OptionalPyblishPluginMixin
from pymxs import runtime as rt
from openpype.hosts.max.api import maintained_selection
class ExtractModel(publish.Extractor, OptionalPyblishPluginMixin):
"""
Extract Geometry in Alembic Format
"""
order = pyblish.api.ExtractorOrder - 0.1
label = "Extract Geometry (Alembic)"
hosts = ["max"]
families = ["model"]
optional = True
def process(self, instance):
if not self.is_active(instance.data):
return
self.log.debug("Extracting Geometry ...")
stagingdir = self.staging_dir(instance)
filename = "{name}.abc".format(**instance.data)
filepath = os.path.join(stagingdir, filename)
# We run the render
self.log.info("Writing alembic '%s' to '%s'" % (filename, stagingdir))
rt.AlembicExport.ArchiveType = rt.name("ogawa")
rt.AlembicExport.CoordinateSystem = rt.name("maya")
rt.AlembicExport.CustomAttributes = True
rt.AlembicExport.UVs = True
rt.AlembicExport.VertexColors = True
rt.AlembicExport.PreserveInstances = True
with maintained_selection():
# select and export
node_list = instance.data["members"]
rt.Select(node_list)
rt.exportFile(
filepath,
rt.name("noPrompt"),
selectedOnly=True,
using=rt.AlembicExport,
)
self.log.info("Performing Extraction ...")
if "representations" not in instance.data:
instance.data["representations"] = []
representation = {
"name": "abc",
"ext": "abc",
"files": filename,
"stagingDir": stagingdir,
}
instance.data["representations"].append(representation)
self.log.info(
"Extracted instance '%s' to: %s" % (instance.name, filepath)
)

View file

@ -20,12 +20,9 @@ class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin):
if not self.is_active(instance.data):
return
self.log.debug("Extracting Geometry ...")
stagingdir = self.staging_dir(instance)
filename = "{name}.fbx".format(**instance.data)
filepath = os.path.join(stagingdir, filename)
self.log.info("Writing FBX '%s' to '%s'" % (filepath, stagingdir))
rt.FBXExporterSetParam("Animation", False)
rt.FBXExporterSetParam("Cameras", False)
@ -46,7 +43,6 @@ class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin):
using=rt.FBXEXP,
)
self.log.info("Performing Extraction ...")
if "representations" not in instance.data:
instance.data["representations"] = []

View file

@ -3,6 +3,7 @@ import pyblish.api
from openpype.pipeline import publish, OptionalPyblishPluginMixin
from pymxs import runtime as rt
from openpype.hosts.max.api import maintained_selection
from openpype.hosts.max.api.lib import suspended_refresh
from openpype.pipeline.publish import KnownPublishError
@ -21,25 +22,21 @@ class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin):
if not self.is_active(instance.data):
return
self.log.debug("Extracting Geometry ...")
stagingdir = self.staging_dir(instance)
filename = "{name}.obj".format(**instance.data)
filepath = os.path.join(stagingdir, filename)
self.log.info("Writing OBJ '%s' to '%s'" % (filepath, stagingdir))
self.log.info("Performing Extraction ...")
with maintained_selection():
# select and export
node_list = instance.data["members"]
rt.Select(node_list)
rt.exportFile(
filepath,
rt.name("noPrompt"),
selectedOnly=True,
using=rt.ObjExp,
)
with suspended_refresh():
with maintained_selection():
# select and export
node_list = instance.data["members"]
rt.Select(node_list)
rt.exportFile(
filepath,
rt.name("noPrompt"),
selectedOnly=True,
using=rt.ObjExp,
)
if not os.path.exists(filepath):
raise KnownPublishError(
"File {} wasn't produced by 3ds max, please check the logs.")

View file

@ -260,7 +260,7 @@ def _install_menu():
"Create...",
lambda: host_tools.show_publisher(
parent=(
main_window if nuke.NUKE_VERSION_RELEASE >= 14 else None
main_window if nuke.NUKE_VERSION_MAJOR >= 14 else None
),
tab="create"
)
@ -271,7 +271,7 @@ def _install_menu():
"Publish...",
lambda: host_tools.show_publisher(
parent=(
main_window if nuke.NUKE_VERSION_RELEASE >= 14 else None
main_window if nuke.NUKE_VERSION_MAJOR >= 14 else None
),
tab="publish"
)

View file

@ -9,7 +9,7 @@ The Photoshop integration requires two components to work; `extension` and `serv
To install the extension download [Extension Manager Command Line tool (ExManCmd)](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#option-2---exmancmd).
```
ExManCmd /install {path to avalon-core}\avalon\photoshop\extension.zxp
ExManCmd /install {path to addon}/api/extension.zxp
```
### Server
@ -17,16 +17,16 @@ ExManCmd /install {path to avalon-core}\avalon\photoshop\extension.zxp
The easiest way to get the server and Photoshop launch is with:
```
python -c ^"import avalon.photoshop;avalon.photoshop.launch(""C:\Program Files\Adobe\Adobe Photoshop 2020\Photoshop.exe"")^"
python -c ^"import openpype.hosts.photoshop;openpype.hosts.photoshop.launch(""C:\Program Files\Adobe\Adobe Photoshop 2020\Photoshop.exe"")^"
```
`avalon.photoshop.launch` launches the application and server, and also closes the server when Photoshop exists.
## Usage
The Photoshop extension can be found under `Window > Extensions > Avalon`. Once launched you should be presented with a panel like this:
The Photoshop extension can be found under `Window > Extensions > Ayon`. Once launched you should be presented with a panel like this:
![Avalon Panel](panel.PNG "Avalon Panel")
![Ayon Panel](panel.png "AYON Panel")
## Developing
@ -37,7 +37,7 @@ When developing the extension you can load it [unsigned](https://github.com/Adob
When signing the extension you can use this [guide](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#package-distribute-install-guide).
```
ZXPSignCmd -selfSignedCert NA NA Avalon Avalon-Photoshop avalon extension.p12
ZXPSignCmd -selfSignedCert NA NA Ayon Ayon-Photoshop Ayon extension.p12
ZXPSignCmd -sign {path to avalon-core}\avalon\photoshop\extension {path to avalon-core}\avalon\photoshop\extension.zxp extension.p12 avalon
```

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionList>
<Extension Id="com.openpype.PS.panel">
<Extension Id="io.ynput.PS.panel">
<HostList>
<Host Name="PHXS" Port="8078"/>
<Host Name="FLPR" Port="8078"/>
</HostList>
</Extension>
</ExtensionList>
</ExtensionList>

View file

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<ExtensionManifest ExtensionBundleId="com.openpype.PS.panel" ExtensionBundleVersion="1.0.12" Version="7.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ExtensionManifest ExtensionBundleId="io.ynput.PS.panel" ExtensionBundleVersion="1.1.0" Version="7.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ExtensionList>
<Extension Id="com.openpype.PS.panel" Version="1.0.1" />
<Extension Id="io.ynput.PS.panel" Version="1.0.1" />
</ExtensionList>
<ExecutionEnvironment>
<HostList>
@ -16,7 +16,7 @@
</RequiredRuntimeList>
</ExecutionEnvironment>
<DispatchInfoList>
<Extension Id="com.openpype.PS.panel">
<Extension Id="io.ynput.PS.panel">
<DispatchInfo>
<Resources>
<MainPath>./index.html</MainPath>
@ -32,7 +32,7 @@
</Lifecycle>
<UI>
<Type>Panel</Type>
<Menu>OpenPype</Menu>
<Menu>AYON</Menu>
<Geometry>
<Size>
<Width>300</Width>
@ -44,7 +44,7 @@
</MaxSize>
</Geometry>
<Icons>
<Icon Type="Normal">./icons/avalon-logo-48.png</Icon>
<Icon Type="Normal">./icons/ayon_logo.png</Icon>
</Icons>
</UI>
</DispatchInfo>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

View file

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -170,8 +170,7 @@ class ExtractReview(publish.Extractor):
# Generate mov.
mov_path = os.path.join(staging_dir, "review.mov")
self.log.info(f"Generate mov review: {mov_path}")
args = [
ffmpeg_path,
args = ffmpeg_path + [
"-y",
"-i", source_files_pattern,
"-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2",
@ -224,6 +223,7 @@ class ExtractReview(publish.Extractor):
"stagingDir": staging_dir,
"tags": ["thumbnail", "delete"]
})
instance.data["thumbnailPath"] = thumbnail_path
def _check_and_resize(self, processed_img_names, source_files_pattern,
staging_dir):

View file

@ -66,7 +66,7 @@ def get_openpype_attr(session, split_hierarchical=True, query_keys=None):
"select {}"
" from CustomAttributeConfiguration"
# Kept `pype` for Backwards Compatibility
" where group.name in (\"pype\", \"{}\")"
" where group.name in (\"pype\", \"ayon\", \"{}\")"
).format(", ".join(query_keys), CUST_ATTR_GROUP)
all_avalon_attr = session.query(cust_attrs_query).all()
for cust_attr in all_avalon_attr:

View file

@ -21,7 +21,7 @@ def get_pype_attr(session, split_hierarchical=True):
"select id, entity_type, object_type_id, is_hierarchical, default"
" from CustomAttributeConfiguration"
# Kept `pype` for Backwards Compatibility
" where group.name in (\"pype\", \"{}\")"
" where group.name in (\"pype\", \"ayon\", \"{}\")"
).format(CUST_ATTR_GROUP)
all_avalon_attr = session.query(cust_attrs_query).all()
for cust_attr in all_avalon_attr:

View file

@ -82,7 +82,7 @@ class CollectResourcesPath(pyblish.api.InstancePlugin):
# Add fill keys for editorial publishing creating new entity
# TODO handle in editorial plugin
if instance.data.get("newAssetPublishing"):
if "hierarchy" not in instance.data:
if "hierarchy" not in template_data:
template_data["hierarchy"] = instance.data["hierarchy"]
if "asset" not in template_data:

View file

@ -204,7 +204,8 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin):
project_item = None
project_children_context = None
for key, value in context.data["hierarchyContext"].items():
hierarchy_context = copy.deepcopy(context.data["hierarchyContext"])
for key, value in hierarchy_context.items():
project_item = copy.deepcopy(value)
project_children_context = project_item.pop("childs", None)
project_item["name"] = key

View file

@ -5,7 +5,21 @@
pull into a scene.
This one is used only as image describing content of published item and
shows up only in Loader in right column section.
shows up only in Loader or WebUI.
Instance must have 'published_representations' to
be able to integrate thumbnail.
Possible sources of thumbnail paths:
- instance.data["thumbnailPath"]
- representation with 'thumbnail' name in 'published_representations'
- context.data["thumbnailPath"]
Notes:
Issue with 'thumbnail' representation is that we most likely don't
want to integrate it as representation. Integrated representation
is polluting Loader and database without real usage. That's why
they usually have 'delete' tag to skip the integration.
"""
import os
@ -92,11 +106,8 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin):
continue
# Find thumbnail path on instance
thumbnail_source = instance.data.get("thumbnailSource")
thumbnail_path = instance.data.get("thumbnailPath")
thumbnail_path = (
thumbnail_source
or thumbnail_path
instance.data.get("thumbnailPath")
or self._get_instance_thumbnail_path(published_repres)
)
if thumbnail_path:

View file

@ -25,7 +25,8 @@
"instance_attributes": [
"reviewable",
"farm_rendering"
]
],
"image_format": "exr"
}
},
"publish": {

View file

@ -436,7 +436,7 @@
"viewTransform": "sRGB gamma"
}
},
"mel_workspace": "workspace -fr \"shaders\" \"renderData/shaders\";\nworkspace -fr \"images\" \"renders/maya\";\nworkspace -fr \"particles\" \"particles\";\nworkspace -fr \"mayaAscii\" \"\";\nworkspace -fr \"mayaBinary\" \"\";\nworkspace -fr \"scene\" \"\";\nworkspace -fr \"alembicCache\" \"cache/alembic\";\nworkspace -fr \"renderData\" \"renderData\";\nworkspace -fr \"sourceImages\" \"sourceimages\";\nworkspace -fr \"fileCache\" \"cache/nCache\";\n",
"mel_workspace": "workspace -fr \"shaders\" \"renderData/shaders\";\nworkspace -fr \"images\" \"renders/maya\";\nworkspace -fr \"particles\" \"particles\";\nworkspace -fr \"mayaAscii\" \"\";\nworkspace -fr \"mayaBinary\" \"\";\nworkspace -fr \"scene\" \"\";\nworkspace -fr \"alembicCache\" \"cache/alembic\";\nworkspace -fr \"renderData\" \"renderData\";\nworkspace -fr \"sourceImages\" \"sourceimages\";\nworkspace -fr \"fileCache\" \"cache/nCache\";\nworkspace -fr \"autoSave\" \"autosave\";",
"ext_mapping": {
"model": "ma",
"mayaAscii": "ma",

View file

@ -80,6 +80,19 @@
"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"}
]
}
]
}

View file

@ -75,8 +75,6 @@ from ._api import (
download_installer,
upload_installer,
get_dependencies_info,
update_dependency_info,
get_dependency_packages,
create_dependency_package,
update_dependency_package,
@ -277,8 +275,6 @@ __all__ = (
"download_installer",
"upload_installer",
"get_dependencies_info",
"update_dependency_info",
"get_dependency_packages",
"create_dependency_package",
"update_dependency_package",

View file

@ -611,16 +611,6 @@ def upload_installer(*args, **kwargs):
# Dependency packages
def get_dependencies_info(*args, **kwargs):
con = get_server_api_connection()
return con.get_dependencies_info(*args, **kwargs)
def update_dependency_info(*args, **kwargs):
con = get_server_api_connection()
return con.update_dependency_info(*args, **kwargs)
def download_dependency_package(*args, **kwargs):
con = get_server_api_connection()
return con.download_dependency_package(*args, **kwargs)

View file

@ -7,9 +7,21 @@ import six
from ._api import get_server_api_connection
from .utils import create_entity_id, convert_entity_id, slugify_string
UNKNOWN_VALUE = object()
PROJECT_PARENT_ID = object()
_NOT_SET = object()
class _CustomNone(object):
def __init__(self, name=None):
self._name = name or "CustomNone"
def __repr__(self):
return "<{}>".format(self._name)
def __bool__(self):
return False
UNKNOWN_VALUE = _CustomNone("UNKNOWN_VALUE")
PROJECT_PARENT_ID = _CustomNone("PROJECT_PARENT_ID")
_NOT_SET = _CustomNone("_NOT_SET")
class EntityHub(object):
@ -1284,7 +1296,10 @@ class BaseEntity(object):
changes["name"] = self._name
if self._entity_hub.allow_data_changes:
if self._orig_data != self._data:
if (
self._data is not UNKNOWN_VALUE
and self._orig_data != self._data
):
changes["data"] = self._data
if self._orig_thumbnail_id != self._thumbnail_id:

View file

@ -8,6 +8,7 @@ import collections
import platform
import copy
import uuid
import warnings
from contextlib import contextmanager
import six
@ -1022,17 +1023,10 @@ class ServerAPI(object):
for attr, filter_value in filters.items():
query.set_variable_value(attr, filter_value)
# Backwards compatibility for server 0.3.x
# - will be removed in future releases
major, minor, _, _, _ = self.server_version_tuple
access_groups_field = "accessGroups"
if major == 0 and minor <= 3:
access_groups_field = "roles"
for parsed_data in query.continuous_query(self):
for user in parsed_data["users"]:
user[access_groups_field] = json.loads(
user[access_groups_field])
user["accessGroups"] = json.loads(
user["accessGroups"])
yield user
def get_user(self, username=None):
@ -2044,14 +2038,6 @@ class ServerAPI(object):
elif entity_type == "user":
entity_type_defaults = set(DEFAULT_USER_FIELDS)
# Backwards compatibility for server 0.3.x
# - will be removed in future releases
major, minor, _, _, _ = self.server_version_tuple
if major == 0 and minor <= 3:
entity_type_defaults.discard("accessGroups")
entity_type_defaults.discard("defaultAccessGroups")
entity_type_defaults.add("roles")
entity_type_defaults.add("defaultRoles")
else:
raise ValueError("Unknown entity type \"{}\"".format(entity_type))
@ -2306,125 +2292,8 @@ class ServerAPI(object):
progress=progress
)
def get_dependencies_info(self):
"""Information about dependency packages on server.
Example data structure:
{
"packages": [
{
"name": str,
"platform": str,
"checksum": str,
"sources": list[dict[str, Any]],
"supportedAddons": dict[str, str],
"pythonModules": dict[str, str]
}
],
"productionPackage": str
}
Deprecated:
Deprecated since server version 0.2.1. Use
'get_dependency_packages' instead.
Returns:
dict[str, Any]: Information about dependency packages known for
server.
"""
major, minor, patch, _, _ = self.server_version_tuple
if major == 0 and (minor < 2 or (minor == 2 and patch < 1)):
result = self.get("dependencies")
return result.data
packages = self.get_dependency_packages()
packages["productionPackage"] = None
return packages
def update_dependency_info(
self,
name,
platform_name,
size,
checksum,
checksum_algorithm=None,
supported_addons=None,
python_modules=None,
sources=None
):
"""Update or create dependency package for identifiers.
The endpoint can be used to create or update dependency package.
Deprecated:
Deprecated for server version 0.2.1. Use
'create_dependency_pacakge' instead.
Args:
name (str): Name of dependency package.
platform_name (Literal["windows", "linux", "darwin"]): Platform
for which is dependency package targeted.
size (int): Size of dependency package in bytes.
checksum (str): Checksum of archive file where dependencies are.
checksum_algorithm (Optional[str]): Algorithm used to calculate
checksum. By default, is used 'md5' (defined by server).
supported_addons (Optional[dict[str, str]]): Name of addons for
which was the package created.
'{"<addon name>": "<addon version>", ...}'
python_modules (Optional[dict[str, str]]): Python modules in
dependencies package.
'{"<module name>": "<module version>", ...}'
sources (Optional[list[dict[str, Any]]]): Information about
sources where dependency package is available.
"""
kwargs = {
key: value
for key, value in (
("checksumAlgorithm", checksum_algorithm),
("supportedAddons", supported_addons),
("pythonModules", python_modules),
("sources", sources),
)
if value
}
response = self.put(
"dependencies",
name=name,
platform=platform_name,
size=size,
checksum=checksum,
**kwargs
)
response.raise_for_status("Failed to create/update dependency")
return response.data
def _get_dependency_package_route(
self, filename=None, platform_name=None
):
major, minor, patch, _, _ = self.server_version_tuple
if (major, minor, patch) <= (0, 2, 0):
# Backwards compatibility for AYON server 0.2.0 and lower
self.log.warning((
"Using deprecated dependency package route."
" Please update your AYON server to version 0.2.1 or higher."
" Backwards compatibility for this route will be removed"
" in future releases of ayon-python-api."
))
if platform_name is None:
platform_name = platform.system().lower()
base = "dependencies"
if not filename:
return base
return "{}/{}/{}".format(base, filename, platform_name)
if (major, minor) <= (0, 3):
endpoint = "desktop/dependency_packages"
else:
endpoint = "desktop/dependencyPackages"
def _get_dependency_package_route(self, filename=None):
endpoint = "desktop/dependencyPackages"
if filename:
return "{}/{}".format(endpoint, filename)
return endpoint
@ -2535,14 +2404,21 @@ class ServerAPI(object):
"""Remove dependency package for specific platform.
Args:
filename (str): Filename of dependency package. Or name of package
for server version 0.2.0 or lower.
platform_name (Optional[str]): Which platform of the package
should be removed. Current platform is used if not passed.
Deprecated since version 0.2.1
filename (str): Filename of dependency package.
platform_name (Optional[str]): Deprecated.
"""
route = self._get_dependency_package_route(filename, platform_name)
if platform_name is not None:
warnings.warn(
(
"Argument 'platform_name' is deprecated in"
" 'delete_dependency_package'. The argument will be"
" removed, please modify your code accordingly."
),
DeprecationWarning
)
route = self._get_dependency_package_route(filename)
response = self.delete(route)
response.raise_for_status("Failed to delete dependency file")
return response.data
@ -2567,18 +2443,25 @@ class ServerAPI(object):
to download.
dst_directory (str): Where the file should be downloaded.
dst_filename (str): Name of destination filename.
platform_name (Optional[str]): Name of platform for which the
dependency package is targeted. Default value is
current platform. Deprecated since server version 0.2.1.
platform_name (Optional[str]): Deprecated.
chunk_size (Optional[int]): Download chunk size.
progress (Optional[TransferProgress]): Object that gives ability
to track download progress.
Returns:
str: Filepath to downloaded file.
"""
"""
route = self._get_dependency_package_route(src_filename, platform_name)
if platform_name is not None:
warnings.warn(
(
"Argument 'platform_name' is deprecated in"
" 'download_dependency_package'. The argument will be"
" removed, please modify your code accordingly."
),
DeprecationWarning
)
route = self._get_dependency_package_route(src_filename)
package_filepath = os.path.join(dst_directory, dst_filename)
self.download_file(
route,
@ -2597,32 +2480,24 @@ class ServerAPI(object):
src_filepath (str): Path to a package file.
dst_filename (str): Dependency package filename or name of package
for server version 0.2.0 or lower. Must be unique.
platform_name (Optional[str]): For which platform is the
package targeted. Deprecated since server version 0.2.1.
platform_name (Optional[str]): Deprecated.
progress (Optional[TransferProgress]): Object to keep track about
upload state.
"""
route = self._get_dependency_package_route(dst_filename, platform_name)
if platform_name is not None:
warnings.warn(
(
"Argument 'platform_name' is deprecated in"
" 'upload_dependency_package'. The argument will be"
" removed, please modify your code accordingly."
),
DeprecationWarning
)
route = self._get_dependency_package_route(dst_filename)
self.upload_file(route, src_filepath, progress=progress)
def create_dependency_package_basename(self, platform_name=None):
"""Create basename for dependency package file.
Deprecated:
Use 'create_dependency_package_basename' from `ayon_api` or
`ayon_api.utils` instead.
Args:
platform_name (Optional[str]): Name of platform for which the
bundle is targeted. Default value is current platform.
Returns:
str: Dependency package name with timestamp and platform.
"""
return create_dependency_package_basename(platform_name)
def upload_addon_zip(self, src_filepath, progress=None):
"""Upload addon zip file to server.
@ -2650,14 +2525,6 @@ class ServerAPI(object):
)
return response.json()
def _get_bundles_route(self):
major, minor, patch, _, _ = self.server_version_tuple
# Backwards compatibility for AYON server 0.3.0
# - first version where bundles were available
if major == 0 and minor == 3 and patch == 0:
return "desktop/bundles"
return "bundles"
def get_bundles(self):
"""Server bundles with basic information.
@ -2688,7 +2555,7 @@ class ServerAPI(object):
dict[str, Any]: Server bundles with basic information.
"""
response = self.get(self._get_bundles_route())
response = self.get("bundles")
response.raise_for_status()
return response.data
@ -2731,7 +2598,7 @@ class ServerAPI(object):
if value is not None:
body[key] = value
response = self.post(self._get_bundles_route(), **body)
response = self.post("bundles", **body)
response.raise_for_status()
def update_bundle(
@ -2766,7 +2633,7 @@ class ServerAPI(object):
if value is not None
}
response = self.patch(
"{}/{}".format(self._get_bundles_route(), bundle_name),
"{}/{}".format("bundles", bundle_name),
**body
)
response.raise_for_status()
@ -2779,7 +2646,7 @@ class ServerAPI(object):
"""
response = self.delete(
"{}/{}".format(self._get_bundles_route(), bundle_name)
"{}/{}".format("bundles", bundle_name)
)
response.raise_for_status()
@ -3102,16 +2969,13 @@ class ServerAPI(object):
- test how it behaves if there is not any production/staging
bundle.
Warnings:
For AYON server < 0.3.0 bundle name will be ignored.
Example output:
{
"addons": [
{
"name": "addon-name",
"version": "addon-version",
"settings": {...}
"settings": {...},
"siteSettings": {...}
}
]
@ -3121,7 +2985,6 @@ class ServerAPI(object):
dict[str, Any]: All settings for single bundle.
"""
major, minor, _, _, _ = self.server_version_tuple
query_values = {
key: value
for key, value in (
@ -3137,21 +3000,8 @@ class ServerAPI(object):
if site_id:
query_values["site_id"] = site_id
if major == 0 and minor >= 3:
url = "settings"
else:
# Backward compatibility for AYON server < 0.3.0
url = "settings/addons"
query_values.pop("bundle_name", None)
for new_key, old_key in (
("project_name", "project"),
("site_id", "site"),
):
if new_key in query_values:
query_values[old_key] = query_values.pop(new_key)
query = prepare_query_string(query_values)
response = self.get("{}{}".format(url, query))
response = self.get("settings{}".format(query))
response.raise_for_status()
return response.data
@ -3194,15 +3044,10 @@ class ServerAPI(object):
use_site=use_site
)
if only_values:
major, minor, patch, _, _ = self.server_version_tuple
if major == 0 and minor >= 3:
output = {
addon["name"]: addon["settings"]
for addon in output["addons"]
}
else:
# Backward compatibility for AYON server < 0.3.0
output = output["settings"]
output = {
addon["name"]: addon["settings"]
for addon in output["addons"]
}
return output
def get_addons_project_settings(
@ -3263,15 +3108,10 @@ class ServerAPI(object):
use_site=use_site
)
if only_values:
major, minor, patch, _, _ = self.server_version_tuple
if major == 0 and minor >= 3:
output = {
addon["name"]: addon["settings"]
for addon in output["addons"]
}
else:
# Backward compatibility for AYON server < 0.3.0
output = output["settings"]
output = {
addon["name"]: addon["settings"]
for addon in output["addons"]
}
return output
def get_addons_settings(

View file

@ -1,2 +1,2 @@
"""Package declaring Python API for Ayon server."""
__version__ = "1.0.0-rc.1"
__version__ = "1.0.0-rc.3"

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring Pype version."""
__version__ = "3.18.0"
__version__ = "3.18.2-nightly.1"

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "OpenPype"
version = "3.18.0" # OpenPype
version = "3.18.1" # OpenPype
description = "Open VFX and Animation pipeline with support."
authors = ["OpenPype Team <info@openpype.io>"]
license = "MIT License"

View file

@ -25,6 +25,16 @@ 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"},
]
class CreateSaverPluginModel(BaseSettingsModel):
_isGroup = True
temp_rendering_path_template: str = Field(
@ -39,6 +49,10 @@ class CreateSaverPluginModel(BaseSettingsModel):
enum_resolver=_create_saver_instance_attributes_enum,
title="Instance attributes"
)
image_format: str = Field(
enum_resolver=_image_format_enum,
title="Output Image Format"
)
class CreatPluginsModel(BaseSettingsModel):
@ -89,7 +103,8 @@ DEFAULT_VALUES = {
"instance_attributes": [
"reviewable",
"farm_rendering"
]
],
"image_format": "exr"
}
}
}

View file

@ -1 +1 @@
__version__ = "0.1.0"
__version__ = "0.1.1"

View file

@ -97,7 +97,7 @@ DEFAULT_MEL_WORKSPACE_SETTINGS = "\n".join((
'workspace -fr "renderData" "renderData";',
'workspace -fr "sourceImages" "sourceimages";',
'workspace -fr "fileCache" "cache/nCache";',
'workspace -fr "autoSave" "autosave"',
'workspace -fr "autoSave" "autosave";',
'',
))

View file

@ -29,7 +29,7 @@ class ColorCodeMappings(BaseSettingsModel):
)
layer_name_regex: list[str] = Field(
"",
default_factory=list,
title="Layer name regex"
)

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring addon version."""
__version__ = "0.1.0"
__version__ = "0.1.1"

View file

@ -366,8 +366,8 @@ def run_disk_mapping_commands(settings):
destination = destination.replace("/", "\\").rstrip("\\")
source = source.replace("/", "\\").rstrip("\\")
# Add slash after ':' ('G:' -> 'G:\')
if destination.endswith(":"):
destination += "\\"
if source.endswith(":"):
source += "\\"
else:
destination = destination.rstrip("/")
source = source.rstrip("/")