diff --git a/CHANGELOG.md b/CHANGELOG.md index 043295eb8c..d7c0a25c7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,59 @@ # Changelog +## [3.2.0-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/2.18.3...HEAD) + +**🚀 Enhancements** + +- Autoupdate launcher [\#1725](https://github.com/pypeclub/OpenPype/pull/1725) +- Subset template and TVPaint subset template docs [\#1717](https://github.com/pypeclub/OpenPype/pull/1717) +- Overscan color extract review [\#1701](https://github.com/pypeclub/OpenPype/pull/1701) +- Nuke: Prerender Frame Range by default [\#1699](https://github.com/pypeclub/OpenPype/pull/1699) +- Smoother edges of color triangle [\#1695](https://github.com/pypeclub/OpenPype/pull/1695) + +**🐛 Bug fixes** + +- TVPaint use layer name for default variant [\#1724](https://github.com/pypeclub/OpenPype/pull/1724) +- Default subset template for TVPaint review and workfile families [\#1716](https://github.com/pypeclub/OpenPype/pull/1716) +- Maya: Extract review hotfix [\#1714](https://github.com/pypeclub/OpenPype/pull/1714) +- Settings: Imageio improving granularity [\#1711](https://github.com/pypeclub/OpenPype/pull/1711) + +## [2.18.3](https://github.com/pypeclub/OpenPype/tree/2.18.3) (2021-06-18) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/2.18.2...2.18.3) + +**🐛 Bug fixes** + +- Remove publish highlight icon in AE [\#1664](https://github.com/pypeclub/OpenPype/pull/1664) + +**Merged pull requests:** + +- Sync main 2.x back to 2.x develop [\#1715](https://github.com/pypeclub/OpenPype/pull/1715) +- global: removing obsolete ftrack validator plugin [\#1710](https://github.com/pypeclub/OpenPype/pull/1710) +- \#683 - Validate frame range in Standalone Publisher [\#1680](https://github.com/pypeclub/OpenPype/pull/1680) +- Maya: Split model content validator [\#1654](https://github.com/pypeclub/OpenPype/pull/1654) + +## [2.18.2](https://github.com/pypeclub/OpenPype/tree/2.18.2) (2021-06-16) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.1.0...2.18.2) + +**🚀 Enhancements** + +- StandalonePublisher: adding exception for adding `delete` tag to repre [\#1650](https://github.com/pypeclub/OpenPype/pull/1650) + +**🐛 Bug fixes** + +- Maya: Extract review hotfix - 2.x backport [\#1713](https://github.com/pypeclub/OpenPype/pull/1713) +- StandalonePublisher: instance data attribute `keepSequence` [\#1668](https://github.com/pypeclub/OpenPype/pull/1668) + +**Merged pull requests:** + +- 1698 Nuke: Prerender Frame Range by default [\#1709](https://github.com/pypeclub/OpenPype/pull/1709) + ## [3.1.0](https://github.com/pypeclub/OpenPype/tree/3.1.0) (2021-06-15) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.0.0...3.1.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.1.0-nightly.4...3.1.0) **🚀 Enhancements** @@ -33,6 +84,7 @@ - Fix missing dbm python module [\#1652](https://github.com/pypeclub/OpenPype/pull/1652) - Transparent branches in view on Mac [\#1648](https://github.com/pypeclub/OpenPype/pull/1648) - Add asset on task item [\#1646](https://github.com/pypeclub/OpenPype/pull/1646) +- Project manager save and queue [\#1645](https://github.com/pypeclub/OpenPype/pull/1645) - New project anatomy values [\#1644](https://github.com/pypeclub/OpenPype/pull/1644) **Merged pull requests:** diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py index eeb7d32d50..af6c0f0eee 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -58,18 +58,14 @@ class CreateRenderlayer(plugin.Creator): # Get currently selected layers layers_data = lib.layers_data() - group_ids = set() - for layer in layers_data: - if layer["selected"]: - group_ids.add(layer["group_id"]) - + selected_layers = [ + layer + for layer in layers_data + if layer["selected"] + ] # Return layer name if only one is selected - if len(group_ids) == 1: - group_id = list(group_ids)[0] - groups_data = lib.groups_data() - for group in groups_data: - if group["group_id"] == group_id: - return group["name"] + if len(selected_layers) == 1: + return selected_layers[0]["name"] # Use defaults if cls.defaults: diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index b7fa5e32e8..037fa63a29 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -232,6 +232,17 @@ ], "tasks": [], "template": "{family}{Task}_{Render_layer}_{Render_pass}" + }, + { + "families": [ + "review", + "workfile" + ], + "hosts": [ + "tvpaint" + ], + "tasks": [], + "template": "{family}{Task}" } ] }, diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 25b6dcdbf0..846a07e081 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -325,19 +325,59 @@ class ProjectModel(QtGui.QStandardItemModel): self.hide_invisible = False self.project_icon = qtawesome.icon("fa.map", color="white") + self._project_names = set() def refresh(self): - self.clear() - self.beginResetModel() - + project_names = set() for project_doc in self.get_projects(): - item = QtGui.QStandardItem(self.project_icon, project_doc["name"]) - self.appendRow(item) + project_names.add(project_doc["name"]) - self.endResetModel() + origin_project_names = set(self._project_names) + self._project_names = project_names + + project_names_to_remove = origin_project_names - project_names + if project_names_to_remove: + row_counts = {} + continuous = None + for row in range(self.rowCount()): + index = self.index(row, 0) + index_name = index.data(QtCore.Qt.DisplayRole) + if index_name in project_names_to_remove: + if continuous is None: + continuous = row + row_counts[continuous] = 0 + row_counts[continuous] += 1 + else: + continuous = None + + for row in reversed(sorted(row_counts.keys())): + count = row_counts[row] + self.removeRows(row, count) + + continuous = None + row_counts = {} + for idx, project_name in enumerate(sorted(project_names)): + if project_name in origin_project_names: + continuous = None + continue + + if continuous is None: + continuous = idx + row_counts[continuous] = [] + + row_counts[continuous].append(project_name) + + for row in reversed(sorted(row_counts.keys())): + items = [] + for project_name in row_counts[row]: + item = QtGui.QStandardItem(self.project_icon, project_name) + items.append(item) + + self.invisibleRootItem().insertRows(row, items) def get_projects(self): project_docs = [] + for project_doc in sorted( self.dbcon.projects(), key=lambda x: x["name"] ): diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py index 22b08d7d15..0e8caeb278 100644 --- a/openpype/tools/launcher/widgets.py +++ b/openpype/tools/launcher/widgets.py @@ -22,6 +22,9 @@ from .constants import ( class ProjectBar(QtWidgets.QWidget): project_changed = QtCore.Signal(int) + # Project list will be refreshed each 10000 msecs + refresh_interval = 10000 + def __init__(self, dbcon, parent=None): super(ProjectBar, self).__init__(parent) @@ -47,14 +50,16 @@ class ProjectBar(QtWidgets.QWidget): QtWidgets.QSizePolicy.Maximum ) + refresh_timer = QtCore.QTimer() + refresh_timer.setInterval(self.refresh_interval) + self.model = model self.project_delegate = project_delegate self.project_combobox = project_combobox - - # Initialize - self.refresh() + self.refresh_timer = refresh_timer # Signals + refresh_timer.timeout.connect(self._on_refresh_timeout) self.project_combobox.currentIndexChanged.connect(self.project_changed) # Set current project by default if it's set. @@ -62,6 +67,20 @@ class ProjectBar(QtWidgets.QWidget): if project_name: self.set_project(project_name) + def showEvent(self, event): + if not self.refresh_timer.isActive(): + self.refresh_timer.start() + super(ProjectBar, self).showEvent(event) + + def _on_refresh_timeout(self): + if not self.isVisible(): + # Stop timer if widget is not visible + self.refresh_timer.stop() + + elif self.isActiveWindow(): + # Refresh projects if window is active + self.model.refresh() + def get_current_project(self): return self.project_combobox.currentText() @@ -69,27 +88,14 @@ class ProjectBar(QtWidgets.QWidget): index = self.project_combobox.findText(project_name) if index < 0: # Try refresh combobox model - self.project_combobox.blockSignals(True) - self.model.refresh() - self.project_combobox.blockSignals(False) - + self.refresh() index = self.project_combobox.findText(project_name) if index >= 0: self.project_combobox.setCurrentIndex(index) def refresh(self): - prev_project_name = self.get_current_project() - - # Refresh without signals - self.project_combobox.blockSignals(True) - self.model.refresh() - self.set_project(prev_project_name) - - self.project_combobox.blockSignals(False) - - self.project_changed.emit(self.project_combobox.currentIndex()) class ActionBar(QtWidgets.QWidget): diff --git a/openpype/tools/launcher/window.py b/openpype/tools/launcher/window.py index af749814b7..a6d34bbe9d 100644 --- a/openpype/tools/launcher/window.py +++ b/openpype/tools/launcher/window.py @@ -91,6 +91,8 @@ class ProjectsPanel(QtWidgets.QWidget): """Projects Page""" project_clicked = QtCore.Signal(str) + # Refresh projects each 10000 msecs + refresh_interval = 10000 def __init__(self, dbcon, parent=None): super(ProjectsPanel, self).__init__(parent=parent) @@ -106,21 +108,40 @@ class ProjectsPanel(QtWidgets.QWidget): flick.activateOn(view) model = ProjectModel(self.dbcon) model.hide_invisible = True - model.refresh() view.setModel(model) layout.addWidget(view) + refresh_timer = QtCore.QTimer() + refresh_timer.setInterval(self.refresh_interval) + + refresh_timer.timeout.connect(self._on_refresh_timeout) view.clicked.connect(self.on_clicked) self.model = model self.view = view + self.refresh_timer = refresh_timer def on_clicked(self, index): if index.isValid(): project_name = index.data(QtCore.Qt.DisplayRole) self.project_clicked.emit(project_name) + def showEvent(self, event): + self.model.refresh() + if not self.refresh_timer.isActive(): + self.refresh_timer.start() + super(ProjectsPanel, self).showEvent(event) + + def _on_refresh_timeout(self): + if not self.isVisible(): + # Stop timer if widget is not visible + self.refresh_timer.stop() + + elif self.isActiveWindow(): + # Refresh projects if window is active + self.model.refresh() + class AssetsPanel(QtWidgets.QWidget): """Assets page""" @@ -276,6 +297,8 @@ class AssetsPanel(QtWidgets.QWidget): class LauncherWindow(QtWidgets.QDialog): """Launcher interface""" + # Refresh actions each 10000msecs + actions_refresh_timeout = 10000 def __init__(self, parent=None): super(LauncherWindow, self).__init__(parent) @@ -344,6 +367,10 @@ class LauncherWindow(QtWidgets.QDialog): layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) + actions_refresh_timer = QtCore.QTimer() + actions_refresh_timer.setInterval(self.actions_refresh_timeout) + + self.actions_refresh_timer = actions_refresh_timer self.message_label = message_label self.project_panel = project_panel self.asset_panel = asset_panel @@ -353,6 +380,7 @@ class LauncherWindow(QtWidgets.QDialog): self._page = 0 # signals + actions_refresh_timer.timeout.connect(self._on_action_timer) actions_bar.action_clicked.connect(self.on_action_clicked) action_history.trigger_history.connect(self.on_history_action) project_panel.project_clicked.connect(self.on_project_clicked) @@ -367,9 +395,11 @@ class LauncherWindow(QtWidgets.QDialog): self.resize(520, 740) def showEvent(self, event): - super().showEvent(event) - # TODO implement refresh/reset which will trigger updating - self.discover_actions() + if not self.actions_refresh_timer.isActive(): + self.actions_refresh_timer.start() + self.discover_actions() + + super(LauncherWindow, self).showEvent(event) def set_page(self, page): current = self.page_slider.currentIndex() @@ -402,6 +432,15 @@ class LauncherWindow(QtWidgets.QDialog): def filter_actions(self): self.actions_bar.filter_actions() + def _on_action_timer(self): + if not self.isVisible(): + # Stop timer if widget is not visible + self.actions_refresh_timer.stop() + + elif self.isActiveWindow(): + # Refresh projects if window is active + self.discover_actions() + def on_project_clicked(self, project_name): self.dbcon.Session["AVALON_PROJECT"] = project_name # Refresh projects @@ -412,7 +451,6 @@ class LauncherWindow(QtWidgets.QDialog): def on_back_clicked(self): self.dbcon.Session["AVALON_PROJECT"] = None self.set_page(0) - self.project_panel.model.refresh() # Refresh projects self.discover_actions() def on_action_clicked(self, action): diff --git a/openpype/version.py b/openpype/version.py index 4312333660..ece0359506 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.1.0" +__version__ = "3.2.0-nightly.1" diff --git a/openpype/widgets/color_widgets/color_inputs.py b/openpype/widgets/color_widgets/color_inputs.py index eda8c618f1..6f5d4baa02 100644 --- a/openpype/widgets/color_widgets/color_inputs.py +++ b/openpype/widgets/color_widgets/color_inputs.py @@ -4,35 +4,6 @@ from Qt import QtWidgets, QtCore, QtGui from .color_view import draw_checkerboard_tile -slide_style = """ -QSlider::groove:horizontal { - background: qlineargradient( - x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #000, stop: 1 #fff - ); - height: 8px; - border-radius: 4px; -} - -QSlider::handle:horizontal { - background: qlineargradient( - x1:0, y1:0, x2:1, y2:1, stop:0 #ddd, stop:1 #bbb - ); - border: 1px solid #777; - width: 8px; - margin-top: -1px; - margin-bottom: -1px; - border-radius: 4px; -} - -QSlider::handle:horizontal:hover { - background: qlineargradient( - x1:0, y1:0, x2:1, y2:1, stop:0 #eee, stop:1 #ddd - ); - border: 1px solid #444;ff - border-radius: 4px; -}""" - - class AlphaSlider(QtWidgets.QSlider): def __init__(self, *args, **kwargs): super(AlphaSlider, self).__init__(*args, **kwargs) @@ -80,7 +51,7 @@ class AlphaSlider(QtWidgets.QSlider): painter.fillRect(event.rect(), QtCore.Qt.transparent) - painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform) + painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing) rect = self.style().subControlRect( QtWidgets.QStyle.CC_Slider, opt, @@ -135,19 +106,8 @@ class AlphaSlider(QtWidgets.QSlider): painter.save() - gradient = QtGui.QRadialGradient() - radius = handle_rect.height() / 2 - center_x = handle_rect.width() / 2 + handle_rect.x() - center_y = handle_rect.height() - gradient.setCenter(center_x, center_y) - gradient.setCenterRadius(radius) - gradient.setFocalPoint(center_x, center_y) - - gradient.setColorAt(0.9, QtGui.QColor(127, 127, 127)) - gradient.setColorAt(1, QtCore.Qt.transparent) - painter.setPen(QtCore.Qt.NoPen) - painter.setBrush(gradient) + painter.setBrush(QtGui.QColor(127, 127, 127)) painter.drawEllipse(handle_rect) painter.restore() diff --git a/openpype/widgets/color_widgets/color_triangle.py b/openpype/widgets/color_widgets/color_triangle.py index d4db175d84..f4a86c4fa5 100644 --- a/openpype/widgets/color_widgets/color_triangle.py +++ b/openpype/widgets/color_widgets/color_triangle.py @@ -241,7 +241,11 @@ class QtColorTriangle(QtWidgets.QWidget): # Blit the static generated background with the hue gradient onto # the double buffer. - buf = QtGui.QImage(self.bg_image.copy()) + buf = QtGui.QImage( + self.bg_image.width(), + self.bg_image.height(), + QtGui.QImage.Format_RGB32 + ) # Draw the trigon # Find the color with only the hue, and max value and saturation @@ -254,9 +258,21 @@ class QtColorTriangle(QtWidgets.QWidget): ) # Slow step: convert the image to a pixmap - pix = QtGui.QPixmap.fromImage(buf) + pix = self.bg_image.copy() pix_painter = QtGui.QPainter(pix) - pix_painter.setRenderHint(QtGui.QPainter.Antialiasing) + + pix_painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing) + + trigon_path = QtGui.QPainterPath() + trigon_path.moveTo(self.point_a) + trigon_path.lineTo(self.point_b) + trigon_path.lineTo(self.point_c) + trigon_path.closeSubpath() + pix_painter.setClipPath(trigon_path) + + pix_painter.drawImage(0, 0, buf) + + pix_painter.setClipping(False) # Draw an outline of the triangle pix_painter.setPen(self._triangle_outline_pen) @@ -724,27 +740,37 @@ class QtColorTriangle(QtWidgets.QWidget): lx = leftX[y] rx = rightX[y] + # if the xdist is 0, don't draw anything. + xdist = rx - lx + if xdist == 0.0: + continue + lxi = int(floor(lx)) rxi = int(floor(rx)) rc = rightColors[y] lc = leftColors[y] - # if the xdist is 0, don't draw anything. - xdist = rx - lx - if xdist != 0.0: - r = lc.r - g = lc.g - b = lc.b - rdelta = (rc.r - r) / xdist - gdelta = (rc.g - g) / xdist - bdelta = (rc.b - b) / xdist + r = lc.r + g = lc.g + b = lc.b + rdelta = (rc.r - r) / xdist + gdelta = (rc.g - g) / xdist + bdelta = (rc.b - b) / xdist - # Inner loop 2. Draws the line from left to right. - for x in range(lxi, rxi + 1): - buf.setPixel(x, y, QtGui.qRgb(int(r), int(g), int(b))) - r += rdelta - g += gdelta - b += bdelta + # Draw 2 more pixels on left side for smoothing + for x in range(lxi - 2, lxi): + buf.setPixel(x, y, QtGui.qRgb(int(r), int(g), int(b))) + + # Inner loop 2. Draws the line from left to right. + for x in range(lxi, rxi): + buf.setPixel(x, y, QtGui.qRgb(int(r), int(g), int(b))) + r += rdelta + g += gdelta + b += bdelta + + # Draw 2 more pixels on right side for smoothing + for x in range(rxi, rxi + 3): + buf.setPixel(x, y, QtGui.qRgb(int(r), int(g), int(b))) def _radius_at(self, pos, rect): mousexdist = pos.x() - float(rect.center().x()) diff --git a/website/docs/admin_hosts_tvpaint.md b/website/docs/admin_hosts_tvpaint.md new file mode 100644 index 0000000000..a99cd19010 --- /dev/null +++ b/website/docs/admin_hosts_tvpaint.md @@ -0,0 +1,30 @@ +--- +id: admin_hosts_tvpaint +title: TVPaint +sidebar_label: TVPaint +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## Subset name templates +Definition of possibile subset name templates in TVPaint integration. + +### [Render Layer](artist_hosts_tvpaint#render-layer) +Render layer has additional keys for subset name template. It is possible to use **render_layer** and **render_pass**. + +- Key **render_layer** is alias for variant (user's input). +- For key **render_pass** is used predefined value `"Beauty"` (ATM value can't be changed). + +### [Render pass](artist_hosts_tvpaint#render-pass) +Render pass has additional keys for subset name template. It is possible to use **render_layer** and **render_pass**. +- Key **render_layer** is filled with value of **render_pass** from `renderLayer` group. +- Key **render_pass** is alias for variant (user's input). + +:::important Render Layer/Pass templates +It is recommended to use same subset name template for both **renderLayer** and **renderPass** families. +- Example template: `"{family}{Task}_{Render_layer}_{Render_pass}"` +::: + +### [Review](artist_hosts_tvpaint#review) and [Workfile](artist_hosts_tvpaint#workfile) +Families **review** and **workfile** are not manually created but are automatically generated during publishing. That's why it is recommended to not use **variant** key in their subset name template. diff --git a/website/docs/artist_hosts_tvpaint.md b/website/docs/artist_hosts_tvpaint.md index 19cb615158..2e831e64d8 100644 --- a/website/docs/artist_hosts_tvpaint.md +++ b/website/docs/artist_hosts_tvpaint.md @@ -45,7 +45,7 @@ In TVPaint you can find the Tools in OpenPype menu extension. The OpenPype Tools ## Create -In TVPaint you can create and publish **[Reviews](#review)**, **[Render Passes](#render-pass)**, and **[Render Layers](#render-layer)**. +In TVPaint you can create and publish **[Reviews](#review)**, **[Workfile](#workfile)**, **[Render Passes](#render-pass)** and **[Render Layers](#render-layer)**. You have the possibility to organize your layers by using `Color group`. @@ -67,26 +67,13 @@ OpenPype specifically never tries to guess what you want to publish from the sce When you want to publish `review` or `render layer` or `render pass`, open the `Creator` through the Tools menu `Create` button. -### Review +### Review +`Review` renders the whole file as is and sends the resulting QuickTime to Ftrack. +- Is automatically created during publishing. -
-
- -`Review` renders the whole file as is and sends the resulting QuickTime to Ftrack. - -To create reviewable quicktime of your animation: - -- select `Review` in the `Creator` -- press `Create` -- When you run [publish](#publish), file will be rendered and converted to quicktime.` - -
-
- -![createreview](assets/tvp_create_review.png) - -
-
+### Workfile +`Workfile` stores the source workfile as is during publishing (e.g. for backup). +- Is automatically created during publishing. ### Render Layer diff --git a/website/docs/assets/tvp_create_review.png b/website/docs/assets/tvp_create_review.png deleted file mode 100644 index d6e9f63428..0000000000 Binary files a/website/docs/assets/tvp_create_review.png and /dev/null differ diff --git a/website/docs/project_settings/assets/global_tools_creator_subset_template.png b/website/docs/project_settings/assets/global_tools_creator_subset_template.png new file mode 100644 index 0000000000..c4e863c4e0 Binary files /dev/null and b/website/docs/project_settings/assets/global_tools_creator_subset_template.png differ diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 5c46cd185a..e6336c36e2 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -172,6 +172,39 @@ Applicable context filters: ## Tools Settings for OpenPype tools. +## Creator +Settings related to [Creator tool](artist_tools.md#details). + +### Subset name profiles +![global_tools_creator_subset_template](assets/global_tools_creator_subset_template.png) + +Subset name helps to identify published content. More specific name helps with organization and avoid mixing of published content. Subset name is defined using one of templates defined in **Subset name profiles settings**. The template is filled with context information at the time of creation. + +Usage of template is defined by profile filtering using creator's family, host and task name. Profile without filters is used as default template and it is recommend to set default template. If default template is not available `"{family}{Task}"` is used. + +**Formatting keys** + +All templates can contain text and formatting keys **family**, **task** and **variant** e.g. `"MyStudio_{family}_{task}"` (example - not recommended in production). + +|Key|Description| +|---|---| +|family|Creators family| +|task|Task under which is creation triggered| +|variant|User input in creator tool| + +**Formatting keys have 3 variants with different letter capitalization.** + +|Task|Key variant|Description|Result| +|---|---|---|---| +|`bgAnim`|`{task}`|Keep original value as is.|`bgAnim`| +|`bgAnim`|`{Task}`|Capitalize first letter of value.|`BgAnim`| +|`bgAnim`|`{TASK}`|Each letter which be capitalized.|`BGANIM`| + +Template may look like `"{family}{Task}{Variant}"`. + +Some creators may have other keys as their context may require more information or more specific values. Make sure you've read documentation of host you're using. + + ## Workfiles All settings related to Workfile tool. diff --git a/website/sidebars.js b/website/sidebars.js index 59071ec34f..d38973e40f 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -90,7 +90,8 @@ module.exports = { "admin_hosts_maya", "admin_hosts_resolve", "admin_hosts_harmony", - "admin_hosts_aftereffects" + "admin_hosts_aftereffects", + "admin_hosts_tvpaint" ], }, {