diff --git a/pype/settings/entities/_item_entity.py b/pype/settings/entities/_item_entity.py new file mode 100644 index 0000000000..c3073c5497 --- /dev/null +++ b/pype/settings/entities/_item_entity.py @@ -0,0 +1,3420 @@ +class InputObject(SettingObject): + """Class for inputs with pre-implemented methods. + + Class is for item types not creating or using other item types, most + of methods has same code in that case. + """ + + def update_default_values(self, parent_values): + self._state = None + self._is_modified = False + + value = NOT_SET + if self.as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + if value is NOT_SET: + if self.available_for_role("developer"): + self.defaults_not_set = True + value = self.default_input_value + if value is NOT_SET: + raise NotImplementedError(( + "{} Does not have implemented" + " attribute `default_input_value`" + ).format(self)) + + else: + raise ValueError( + "Default value is not set. This is implementation BUG." + ) + else: + self.defaults_not_set = False + + self.default_value = value + self._has_studio_override = False + self._had_studio_override = False + try: + self.set_value(value) + except InvalidValueType as exc: + self.default_value = NOT_SET + self.defaults_not_set = True + self.log.warning(exc.msg) + + def update_studio_values(self, parent_values): + self._state = None + self._is_modified = False + + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + self.studio_value = value + if value is not NOT_SET: + self._has_studio_override = True + self._had_studio_override = True + + else: + self._has_studio_override = False + self._had_studio_override = False + value = self.default_value + + try: + self.set_value(value) + except InvalidValueType as exc: + self.studio_value = NOT_SET + self.log.warning(exc.msg) + + def apply_overrides(self, parent_values): + self._is_modified = False + self._state = None + self._had_studio_override = bool(self._has_studio_override) + if self._as_widget: + override_value = parent_values + elif parent_values is NOT_SET or self.key not in parent_values: + override_value = NOT_SET + else: + override_value = parent_values[self.key] + + self.override_value = override_value + + if override_value is NOT_SET: + self._is_overriden = False + self._was_overriden = False + if self.has_studio_override: + value = self.studio_value + else: + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + try: + self.set_value(value) + except InvalidValueType as exc: + self.override_value = NOT_SET + self.log.warning(exc.msg) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if not self.any_parent_as_widget: + if self.is_overidable: + self._is_overriden = True + else: + self._has_studio_override = True + + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + elif self._has_studio_override: + self._is_modified = self.item_value() != self.studio_value + else: + self._is_modified = self.item_value() != self.default_value + + self.update_style() + + self.value_changed.emit(self) + + def studio_overrides(self): + if ( + not (self.as_widget or self.any_parent_as_widget) + and not self.has_studio_override + ): + return NOT_SET, False + return self.config_value(), self.is_group + + def overrides(self): + if ( + not (self.as_widget or self.any_parent_as_widget) + and not self.is_overriden + ): + return NOT_SET, False + return self.config_value(), self.is_group + + def hierarchical_style_update(self): + self.update_style() + + def _style_state(self): + if self.as_widget: + state = self.style_state( + False, + self._is_invalid, + False, + self.is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + return state + + def update_style(self): + state = self._style_state() + if self._state == state: + return + + self._state = state + + self.input_field.setProperty("input-state", state) + self.input_field.style().polish(self.input_field) + if self.label_widget: + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + if self.has_studio_override: + self.set_value(self.studio_value) + else: + self.set_value(self.default_value) + self._is_overriden = False + self._is_modified = False + + def reset_to_pype_default(self): + self.set_value(self.default_value) + self._has_studio_override = False + + def set_studio_default(self): + self._has_studio_override = True + + def discard_changes(self): + self._is_overriden = self._was_overriden + self._has_studio_override = self._had_studio_override + if self.is_overidable: + if self._was_overriden and self.override_value is not NOT_SET: + self.set_value(self.override_value) + else: + if self._had_studio_override: + self.set_value(self.studio_value) + else: + self.set_value(self.default_value) + + if not self.is_overidable: + if self.has_studio_override: + self._is_modified = self.studio_value != self.item_value() + else: + self._is_modified = self.default_value != self.item_value() + self._is_overriden = False + return + + self._state = None + self._is_modified = False + self._is_overriden = self._was_overriden + + def set_as_overriden(self): + self._is_overriden = True + + @property + def child_has_studio_override(self): + return self._has_studio_override + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def child_invalid(self): + return self.is_invalid + + def get_invalid(self): + output = [] + if self.is_invalid: + output.append(self) + return output + + def reset_children_attributes(self): + return + + +class BooleanWidget(QtWidgets.QWidget, InputObject): + default_input_value = True + value_changed = QtCore.Signal(object) + valid_value_types = (bool, ) + + def __init__( + self, input_data, parent, + as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(BooleanWidget, self).__init__(parent_widget) + + self.initial_attributes(input_data, parent, as_widget) + + def create_ui(self, label_widget=None): + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + if not self.as_widget and not label_widget: + label = self.schema_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + + checkbox_height = self.style().pixelMetric( + QtWidgets.QStyle.PM_IndicatorHeight + ) + self.input_field = NiceCheckbox(height=checkbox_height, parent=self) + + spacer = QtWidgets.QWidget(self) + spacer.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + layout.addWidget(self.input_field, 0) + layout.addWidget(spacer, 1) + + self.setFocusProxy(self.input_field) + + self.input_field.stateChanged.connect(self._on_value_change) + + def set_value(self, value): + # Ignore value change because if `self.isChecked()` has same + # value as `value` the `_on_value_change` is not triggered + self.validate_value(value) + self.input_field.setChecked(value) + + def item_value(self): + return self.input_field.isChecked() + + +class NumberWidget(QtWidgets.QWidget, InputObject): + default_input_value = 0 + value_changed = QtCore.Signal(object) + input_modifiers = ("minimum", "maximum", "decimal") + valid_value_types = (int, float) + + def __init__( + self, schema_data, parent, as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(NumberWidget, self).__init__(parent_widget) + + self.initial_attributes(schema_data, parent, as_widget) + + def create_ui(self, label_widget=None): + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + kwargs = { + modifier: self.schema_data.get(modifier) + for modifier in self.input_modifiers + if self.schema_data.get(modifier) + } + self.input_field = NumberSpinBox(self, **kwargs) + + self.setFocusProxy(self.input_field) + + if not self._as_widget and not label_widget: + label = self.schema_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + + layout.addWidget(self.input_field, 1) + + self.input_field.valueChanged.connect(self._on_value_change) + + def set_value(self, value): + self.validate_value(value) + self.input_field.setValue(value) + + def item_value(self): + return self.input_field.value() + + +class TextWidget(QtWidgets.QWidget, InputObject): + default_input_value = "" + value_changed = QtCore.Signal(object) + valid_value_types = (str, ) + + def __init__( + self, schema_data, parent, as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(TextWidget, self).__init__(parent_widget) + + self.initial_attributes(schema_data, parent, as_widget) + self.multiline = schema_data.get("multiline", False) + self.placeholder_text = schema_data.get("placeholder") + + def create_ui(self, label_widget=None): + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + if self.multiline: + self.input_field = QtWidgets.QPlainTextEdit(self) + else: + self.input_field = QtWidgets.QLineEdit(self) + + if self.placeholder_text: + self.input_field.setPlaceholderText(self.placeholder_text) + + self.setFocusProxy(self.input_field) + + layout_kwargs = {} + if self.multiline: + layout_kwargs["alignment"] = QtCore.Qt.AlignTop + + if not self._as_widget and not label_widget: + label = self.schema_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0, **layout_kwargs) + self.label_widget = label_widget + + layout.addWidget(self.input_field, 1, **layout_kwargs) + + self.input_field.textChanged.connect(self._on_value_change) + + def set_value(self, value): + self.validate_value(value) + if self.multiline: + self.input_field.setPlainText(value) + else: + self.input_field.setText(value) + + def item_value(self): + if self.multiline: + return self.input_field.toPlainText() + else: + return self.input_field.text() + + +class PathInputWidget(QtWidgets.QWidget, InputObject): + default_input_value = "" + value_changed = QtCore.Signal(object) + valid_value_types = (str, list) + + def __init__( + self, schema_data, parent, as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(PathInputWidget, self).__init__(parent_widget) + + self.initial_attributes(schema_data, parent, as_widget) + + self.with_arguments = schema_data.get("with_arguments", False) + + def create_ui(self, label_widget=None): + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + if not self._as_widget and not label_widget: + label = self.schema_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + + self.input_field = QtWidgets.QLineEdit(self) + self.args_input_field = None + if self.with_arguments: + self.input_field.setPlaceholderText("Executable path") + self.args_input_field = QtWidgets.QLineEdit(self) + self.args_input_field.setPlaceholderText("Arguments") + + self.setFocusProxy(self.input_field) + layout.addWidget(self.input_field, 8) + self.input_field.textChanged.connect(self._on_value_change) + + if self.args_input_field: + layout.addWidget(self.args_input_field, 2) + self.args_input_field.textChanged.connect(self._on_value_change) + + def set_value(self, value): + self.validate_value(value) + + if not isinstance(value, list): + self.input_field.setText(value) + elif self.with_arguments: + self.input_field.setText(value[0]) + self.args_input_field.setText(value[1]) + else: + self.input_field.setText(value[0]) + + def item_value(self): + path_value = self.input_field.text() + if self.with_arguments: + return [path_value, self.args_input_field.text()] + return path_value + + +class EnumeratorWidget(QtWidgets.QWidget, InputObject): + default_input_value = True + value_changed = QtCore.Signal(object) + + def __init__( + self, schema_data, parent, as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(EnumeratorWidget, self).__init__(parent_widget) + + self.initial_attributes(schema_data, parent, as_widget) + self.multiselection = schema_data.get("multiselection") + self.enum_items = schema_data["enum_items"] + if not self.enum_items: + raise ValueError("Attribute `enum_items` is not defined.") + + def create_ui(self, label_widget=None): + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + if not self._as_widget and not label_widget: + label = self.schema_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + + if self.multiselection: + placeholder = self.schema_data.get("placeholder") + self.input_field = MultiSelectionComboBox( + placeholder=placeholder, parent=self + ) + else: + self.input_field = ComboBox(self) + + first_value = NOT_SET + for enum_item in self.enum_items: + for value, label in enum_item.items(): + if first_value is NOT_SET: + first_value = value + self.input_field.addItem(label, value) + self._first_value = first_value + + if self.multiselection: + model = self.input_field.model() + for idx in range(self.input_field.count()): + model.item(idx).setCheckable(True) + + layout.addWidget(self.input_field, 0) + + self.setFocusProxy(self.input_field) + + self.input_field.value_changed.connect(self._on_value_change) + + @property + def default_input_value(self): + if self.multiselection: + return [] + return self._first_value + + def set_value(self, value): + # Ignore value change because if `self.isChecked()` has same + # value as `value` the `_on_value_change` is not triggered + self.input_field.set_value(value) + + def update_style(self): + if self.as_widget: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + + if self._state == state: + return + + self._state = state + self.input_field.setProperty("input-state", state) + self.input_field.style().polish(self.input_field) + if self.label_widget: + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.input_field.value() + + +class RawJsonInput(QtWidgets.QPlainTextEdit): + tab_length = 4 + + def __init__(self, *args, **kwargs): + super(RawJsonInput, self).__init__(*args, **kwargs) + self.setObjectName("RawJsonInput") + self.setTabStopDistance( + QtGui.QFontMetricsF( + self.font() + ).horizontalAdvance(" ") * self.tab_length + ) + + def sizeHint(self): + document = self.document() + layout = document.documentLayout() + + height = document.documentMargin() + 2 * self.frameWidth() + 1 + block = document.begin() + while block != document.end(): + height += layout.blockBoundingRect(block).height() + block = block.next() + + hint = super(RawJsonInput, self).sizeHint() + hint.setHeight(height) + + return hint + + def set_value(self, value): + if value is NOT_SET: + value = "" + + elif not isinstance(value, str): + try: + value = json.dumps(value, indent=4) + except Exception: + value = "" + self.setPlainText(value) + + def json_value(self): + return json.loads(self.toPlainText()) + + def has_invalid_value(self): + try: + self.json_value() + return False + except Exception: + return True + + def resizeEvent(self, event): + self.updateGeometry() + super(RawJsonInput, self).resizeEvent(event) + + +class RawJsonWidget(QtWidgets.QWidget, InputObject): + default_input_value = "{}" + value_changed = QtCore.Signal(object) + valid_value_types = (str, dict, list, type(NOT_SET)) + allow_to_environment = True + + def __init__( + self, schema_data, parent, as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(RawJsonWidget, self).__init__(parent_widget) + + self.initial_attributes(schema_data, parent, as_widget) + # By default must be invalid + self._is_invalid = True + + def create_ui(self, label_widget=None): + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + self.input_field = RawJsonInput(self) + self.input_field.setSizePolicy( + QtWidgets.QSizePolicy.Minimum, + QtWidgets.QSizePolicy.MinimumExpanding + ) + + self.setFocusProxy(self.input_field) + + if not self.as_widget and not label_widget: + if self.label: + label_widget = QtWidgets.QLabel(self.label) + layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) + self.label_widget = label_widget + + layout.addWidget(self.input_field, 1, alignment=QtCore.Qt.AlignTop) + + self.input_field.textChanged.connect(self._on_value_change) + + def update_studio_values(self, parent_values): + self._is_invalid = self.input_field.has_invalid_value() + return super(RawJsonWidget, self).update_studio_values(parent_values) + + def set_value(self, value): + self.validate_value(value) + self.input_field.set_value(value) + + def _on_value_change(self, *args, **kwargs): + self._is_invalid = self.input_field.has_invalid_value() + return super(RawJsonWidget, self)._on_value_change(*args, **kwargs) + + def item_value(self): + if self.is_invalid: + return NOT_SET + + value = self.input_field.json_value() + if not self.is_environ: + return value + + output = {} + for key, value in value.items(): + output[key.upper()] = value + return output + + def config_value(self): + value = self.item_value() + if self.is_environ: + if METADATA_KEY not in value: + value[METADATA_KEY] = {} + + env_keys = [] + for key in value.keys(): + if key is not METADATA_KEY: + env_keys.append(key) + + value[METADATA_KEY]["environments"] = { + self.env_group_key: env_keys + } + return {self.key: value} + + +class ListItem(QtWidgets.QWidget, SettingObject): + _btn_size = 20 + value_changed = QtCore.Signal(object) + + def __init__( + self, item_schema, config_parent, parent, is_strict=False + ): + super(ListItem, self).__init__(parent) + + self._set_default_attributes() + + self._is_strict = is_strict + + self._parent = config_parent + self._any_parent_is_group = True + self._is_empty = False + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + char_up = qtawesome.charmap("fa.angle-up") + char_down = qtawesome.charmap("fa.angle-down") + + if not self._is_strict: + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + self.up_btn = QtWidgets.QPushButton(char_up) + self.down_btn = QtWidgets.QPushButton(char_down) + + font_up_down = qtawesome.font("fa", 13) + self.up_btn.setFont(font_up_down) + self.down_btn.setFont(font_up_down) + + self.add_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.up_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.down_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + + self.add_btn.setFixedSize(self._btn_size, self._btn_size) + self.remove_btn.setFixedSize(self._btn_size, self._btn_size) + self.up_btn.setFixedSize(self._btn_size, self._btn_size) + self.down_btn.setFixedSize(self._btn_size, self._btn_size) + + self.add_btn.setProperty("btn-type", "tool-item") + self.remove_btn.setProperty("btn-type", "tool-item") + self.up_btn.setProperty("btn-type", "tool-item") + self.down_btn.setProperty("btn-type", "tool-item") + + self.add_btn.clicked.connect(self._on_add_clicked) + self.remove_btn.clicked.connect(self._on_remove_clicked) + self.up_btn.clicked.connect(self._on_up_clicked) + self.down_btn.clicked.connect(self._on_down_clicked) + + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + + ItemKlass = TypeToKlass.types[item_schema["type"]] + self.value_input = ItemKlass( + item_schema, + self, + as_widget=True + ) + self.value_input.create_ui() + + layout.addWidget(self.value_input, 1) + + if not self._is_strict: + self.spacer_widget = QtWidgets.QWidget(self) + self.spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + self.spacer_widget.setVisible(False) + + layout.addWidget(self.spacer_widget, 1) + + layout.addWidget(self.up_btn, 0) + layout.addWidget(self.down_btn, 0) + + self.value_input.value_changed.connect(self._on_value_change) + + @property + def as_widget(self): + return self._parent.as_widget + + @property + def any_parent_as_widget(self): + return self.as_widget or self._parent.any_parent_as_widget + + def set_as_empty(self, is_empty=True): + self._is_empty = is_empty + + self.spacer_widget.setVisible(is_empty) + self.value_input.setVisible(not is_empty) + self.remove_btn.setEnabled(not is_empty) + self.up_btn.setVisible(not is_empty) + self.down_btn.setVisible(not is_empty) + self.order_changed() + self._on_value_change() + + def order_changed(self): + row = self.row() + parent_row_count = self.parent_rows_count() + if parent_row_count == 1: + self.up_btn.setVisible(False) + self.down_btn.setVisible(False) + return + + if not self.up_btn.isVisible(): + self.up_btn.setVisible(True) + self.down_btn.setVisible(True) + + if row == 0: + self.up_btn.setEnabled(False) + self.down_btn.setEnabled(True) + + elif row == parent_row_count - 1: + self.up_btn.setEnabled(True) + self.down_btn.setEnabled(False) + + else: + self.up_btn.setEnabled(True) + self.down_btn.setEnabled(True) + + def _on_value_change(self, item=None): + self.value_changed.emit(self) + + def row(self): + return self._parent.input_fields.index(self) + + def parent_rows_count(self): + return len(self._parent.input_fields) + + def _on_add_clicked(self): + if self._is_empty: + self.set_as_empty(False) + else: + self._parent.add_row(row=self.row() + 1) + + def _on_remove_clicked(self): + self._parent.remove_row(self) + + def _on_up_clicked(self): + row = self.row() + self._parent.swap_rows(row - 1, row) + + def _on_down_clicked(self): + row = self.row() + self._parent.swap_rows(row, row + 1) + + def config_value(self): + if not self._is_empty: + return self.value_input.item_value() + return NOT_SET + + @property + def is_modified(self): + if self._is_empty: + return False + return self.value_input.is_modified + + @property + def child_has_studio_override(self): + return self.value_input.child_has_studio_override + + @property + def child_modified(self): + return self.value_input.child_modified + + @property + def child_overriden(self): + return self.value_input.child_overriden + + def hierarchical_style_update(self): + self.value_input.hierarchical_style_update() + + def mouseReleaseEvent(self, event): + return QtWidgets.QWidget.mouseReleaseEvent(self, event) + + def update_default_values(self, value): + self.value_input.update_default_values(value) + + def update_studio_values(self, value): + self.value_input.update_studio_values(value) + + def apply_overrides(self, value): + self.value_input.apply_overrides(value) + + +class ListWidget(QtWidgets.QWidget, InputObject): + default_input_value = [] + value_changed = QtCore.Signal(object) + valid_value_types = (list, ) + + def __init__( + self, schema_data, parent, as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(ListWidget, self).__init__(parent_widget) + self.setObjectName("ListWidget") + + self.initial_attributes(schema_data, parent, as_widget) + + self.use_label_wrap = schema_data.get("use_label_wrap") or False + # Used only if `use_label_wrap` is set to True + self.collapsible = schema_data.get("collapsible") or True + self.collapsed = schema_data.get("collapsed") or False + + self.expand_in_grid = bool(self.use_label_wrap) + + if self.as_widget and self.use_label_wrap: + raise ValueError( + "`ListWidget` can't have set `use_label_wrap` to True and" + " be used as widget at the same time." + ) + + if self.use_label_wrap and not self.label: + raise ValueError( + "`ListWidget` can't have set `use_label_wrap` to True and" + " not have set \"label\" key at the same time." + ) + + self.input_fields = [] + + object_type = schema_data["object_type"] + if isinstance(object_type, dict): + self.item_schema = object_type + else: + self.item_schema = { + "type": object_type + } + + def create_ui(self, label_widget=None): + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + body_widget = None + if self.as_widget: + pass + + elif self.use_label_wrap: + body_widget = ExpandingWidget(self.label, self) + main_layout.addWidget(body_widget) + + label_widget = body_widget.label_widget + + elif not label_widget: + if self.label: + label_widget = QtWidgets.QLabel(self.label, self) + main_layout.addWidget( + label_widget, alignment=QtCore.Qt.AlignTop + ) + + self.label_widget = label_widget + + self.body_widget = body_widget + + if body_widget is None: + content_parent_widget = self + else: + content_parent_widget = body_widget + + content_state = "" + + inputs_widget = QtWidgets.QWidget(content_parent_widget) + inputs_widget.setObjectName("ContentWidget") + inputs_widget.setProperty("content_state", content_state) + inputs_layout = QtWidgets.QVBoxLayout(inputs_widget) + inputs_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 5) + + if body_widget is None: + main_layout.addWidget(inputs_widget) + else: + body_widget.set_content_widget(inputs_widget) + + self.body_widget = body_widget + self.inputs_widget = inputs_widget + self.inputs_layout = inputs_layout + + if body_widget: + if not self.collapsible: + body_widget.hide_toolbox(hide_content=False) + + elif self.collapsed: + body_widget.toggle_content() + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self.add_row(is_empty=True) + + def count(self): + return len(self.input_fields) + + def update_studio_values(self, parent_values): + super(ListWidget, self).update_studio_values(parent_values) + + self.hierarchical_style_update() + + def set_value(self, value): + self.validate_value(value) + + previous_inputs = tuple(self.input_fields) + for item_value in value: + self.add_row(value=item_value) + + for input_field in previous_inputs: + self.remove_row(input_field) + + if self.count() == 0: + self.add_row(is_empty=True) + + def swap_rows(self, row_1, row_2): + if row_1 == row_2: + return + + if row_1 > row_2: + row_1, row_2 = row_2, row_1 + + field_1 = self.input_fields[row_1] + field_2 = self.input_fields[row_2] + + self.input_fields[row_1] = field_2 + self.input_fields[row_2] = field_1 + + layout_index = self.inputs_layout.indexOf(field_1) + self.inputs_layout.insertWidget(layout_index + 1, field_1) + + field_1.order_changed() + field_2.order_changed() + + def add_row(self, row=None, value=None, is_empty=False): + # Create new item + item_widget = ListItem(self.item_schema, self, self.inputs_widget) + + previous_field = None + next_field = None + + if row is None: + if self.input_fields: + previous_field = self.input_fields[-1] + self.inputs_layout.addWidget(item_widget) + self.input_fields.append(item_widget) + else: + if row > 0: + previous_field = self.input_fields[row - 1] + + max_index = self.count() + if row < max_index: + next_field = self.input_fields[row] + + self.inputs_layout.insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + + if previous_field: + previous_field.order_changed() + + if next_field: + next_field.order_changed() + + if is_empty: + item_widget.set_as_empty() + item_widget.value_changed.connect(self._on_value_change) + + item_widget.order_changed() + + previous_input = None + for input_field in self.input_fields: + if previous_input is not None: + self.setTabOrder( + previous_input, input_field.value_input.focusProxy() + ) + previous_input = input_field.value_input.focusProxy() + + # Set text if entered text is not None + # else (when add button clicked) trigger `_on_value_change` + if value is not None: + if self._is_overriden: + item_widget.apply_overrides(value) + elif not self._has_studio_override: + item_widget.update_default_values(value) + else: + item_widget.update_studio_values(value) + self.hierarchical_style_update() + else: + self._on_value_change() + self.updateGeometry() + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + row = self.input_fields.index(item_widget) + previous_field = None + next_field = None + if row > 0: + previous_field = self.input_fields[row - 1] + + if row != len(self.input_fields) - 1: + next_field = self.input_fields[row + 1] + + self.inputs_layout.removeWidget(item_widget) + self.input_fields.pop(row) + item_widget.setParent(None) + item_widget.deleteLater() + + if previous_field: + previous_field.order_changed() + + if next_field: + next_field.order_changed() + + if self.count() == 0: + self.add_row(is_empty=True) + + self._on_value_change() + self.updateGeometry() + + def apply_overrides(self, parent_values): + self._is_modified = False + if self.as_widget: + override_value = parent_values + elif parent_values is NOT_SET or self.key not in parent_values: + override_value = NOT_SET + else: + override_value = parent_values[self.key] + + self.override_value = override_value + + if override_value is NOT_SET: + self._is_overriden = False + self._was_overriden = False + if self.has_studio_override: + value = self.studio_value + else: + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self._is_modified = False + self._state = None + + self.set_value(value) + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + + @property + def is_modified(self): + is_modified = super(ListWidget, self).is_modified + if is_modified: + return is_modified + + for input_field in self.input_fields: + if input_field.is_modified: + return True + return False + + def update_style(self, is_overriden=None): + if not self.label_widget: + return + + child_invalid = self.child_invalid + if self.body_widget: + child_state = self.style_state( + self.child_has_studio_override, + child_invalid, + self.child_overriden, + self.child_modified + ) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.body_widget.side_line_widget.setProperty( + "state", child_state + ) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + self._child_state = child_state + + state = self.style_state( + self.had_studio_override, + child_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + def item_value(self): + output = [] + for item in self.input_fields: + value = item.config_value() + if value is not NOT_SET: + output.append(value) + return output + + +class ListStrictWidget(QtWidgets.QWidget, InputObject): + value_changed = QtCore.Signal(object) + _default_input_value = None + valid_value_types = (list, ) + + def __init__( + self, schema_data, parent, as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(ListStrictWidget, self).__init__(parent_widget) + self.setObjectName("ListStrictWidget") + + self.initial_attributes(schema_data, parent, as_widget) + + self.is_horizontal = schema_data.get("horizontal", True) + self.object_types = self.schema_data["object_types"] + + self.input_fields = [] + + def create_ui(self, label_widget=None): + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 5) + layout.setSpacing(5) + + if not self.as_widget and not label_widget: + label = self.schema_data.get("label") + if label: + label_widget = QtWidgets.QLabel(label, self) + layout.addWidget(label_widget, alignment=QtCore.Qt.AlignTop) + elif self._is_group: + raise KeyError(( + "Schema item must contain \"label\" if `is_group` is True" + " to be able visualize changes and show actions." + )) + + self.label_widget = label_widget + + self._add_children(layout) + + def _add_children(self, layout): + inputs_widget = QtWidgets.QWidget(self) + inputs_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(inputs_widget) + + if self.is_horizontal: + inputs_layout = QtWidgets.QHBoxLayout(inputs_widget) + else: + inputs_layout = QtWidgets.QGridLayout(inputs_widget) + + inputs_layout.setContentsMargins(0, 0, 0, 0) + inputs_layout.setSpacing(3) + + self.inputs_widget = inputs_widget + self.inputs_layout = inputs_layout + + children_item_mapping = [] + for child_configuration in self.object_types: + item_widget = ListItem( + child_configuration, self, self.inputs_widget, is_strict=True + ) + + self.input_fields.append(item_widget) + item_widget.value_changed.connect(self._on_value_change) + + label = child_configuration.get("label") + label_widget = None + if label: + label_widget = QtWidgets.QLabel(label, self) + + children_item_mapping.append((label_widget, item_widget)) + + if self.is_horizontal: + self._add_children_horizontally(children_item_mapping) + else: + self._add_children_vertically(children_item_mapping) + + self.updateGeometry() + + def _add_children_vertically(self, children_item_mapping): + any_has_label = False + for item_mapping in children_item_mapping: + if item_mapping[0]: + any_has_label = True + break + + row = self.inputs_layout.count() + if not any_has_label: + self.inputs_layout.setColumnStretch(1, 1) + for item_mapping in children_item_mapping: + item_widget = item_mapping[1] + self.inputs_layout.addWidget(item_widget, row, 0, 1, 1) + + spacer_widget = QtWidgets.QWidget(self.inputs_widget) + self.inputs_layout.addWidget(spacer_widget, row, 1, 1, 1) + row += 1 + + else: + self.inputs_layout.setColumnStretch(2, 1) + for label_widget, item_widget in children_item_mapping: + self.inputs_layout.addWidget( + label_widget, row, 0, 1, 1, + alignment=QtCore.Qt.AlignRight | QtCore.Qt.AlignTop + ) + self.inputs_layout.addWidget(item_widget, row, 1, 1, 1) + + spacer_widget = QtWidgets.QWidget(self.inputs_widget) + self.inputs_layout.addWidget(spacer_widget, row, 2, 1, 1) + row += 1 + + def _add_children_horizontally(self, children_item_mapping): + for label_widget, item_widget in children_item_mapping: + if label_widget: + self.inputs_layout.addWidget(label_widget, 0) + self.inputs_layout.addWidget(item_widget, 0) + + spacer_widget = QtWidgets.QWidget(self.inputs_widget) + self.inputs_layout.addWidget(spacer_widget, 1) + + @property + def default_input_value(self): + if self._default_input_value is None: + self.set_value(NOT_SET) + self._default_input_value = self.item_value() + return self._default_input_value + + def set_value(self, value): + if self._is_overriden: + method_name = "apply_overrides" + elif not self._has_studio_override: + method_name = "update_default_values" + else: + method_name = "update_studio_values" + + for idx, input_field in enumerate(self.input_fields): + if value is NOT_SET: + _value = value + else: + if idx > len(value) - 1: + _value = NOT_SET + else: + _value = value[idx] + _method = getattr(input_field, method_name) + _method(_value) + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + + def update_style(self): + if not self.label_widget: + return + + state = self._style_state() + + if self._state == state: + return + + self._state = state + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + output = [] + for item in self.input_fields: + output.append(item.config_value()) + return output + + +class ModifiableDictItem(QtWidgets.QWidget, SettingObject): + _btn_size = 20 + value_changed = QtCore.Signal(object) + + def __init__(self, item_schema, config_parent, parent): + super(ModifiableDictItem, self).__init__(parent) + + self._set_default_attributes() + self._parent = config_parent + + any_parent_as_widget = config_parent.as_widget + if not any_parent_as_widget: + any_parent_as_widget = config_parent.any_parent_as_widget + + self._any_parent_as_widget = any_parent_as_widget + self._any_parent_is_group = True + + self._is_empty = False + self._is_key_duplicated = False + + self._is_required = False + + self.origin_key = NOT_SET + self.origin_key_label = NOT_SET + + if self.collapsable_key: + layout = QtWidgets.QVBoxLayout(self) + else: + layout = QtWidgets.QHBoxLayout(self) + + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + ItemKlass = TypeToKlass.types[item_schema["type"]] + value_input = ItemKlass( + item_schema, + self, + as_widget=True + ) + value_input.create_ui() + + key_input = QtWidgets.QLineEdit(self) + key_input.setObjectName("DictKey") + + key_label_input = None + wrapper_widget = None + if self.collapsable_key: + key_label_input = QtWidgets.QLineEdit(self) + + wrapper_widget = ExpandingWidget("", self) + layout.addWidget(wrapper_widget) + + content_widget = QtWidgets.QWidget(wrapper_widget) + content_widget.setObjectName("ContentWidget") + content_layout = QtWidgets.QHBoxLayout(content_widget) + content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) + content_layout.setSpacing(5) + + wrapper_widget.set_content_widget(content_widget) + + content_layout.addWidget(value_input) + + def key_input_focused_out(event): + QtWidgets.QLineEdit.focusOutEvent(key_input, event) + self._on_focus_lose() + + def key_label_input_focused_out(event): + QtWidgets.QLineEdit.focusOutEvent(key_label_input, event) + self._on_focus_lose() + + key_input.focusOutEvent = key_input_focused_out + key_label_input.focusOutEvent = key_label_input_focused_out + + spacer_widget = None + add_btn = None + if not self.collapsable_key: + spacer_widget = QtWidgets.QWidget(self) + spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + spacer_widget.setVisible(False) + + add_btn = QtWidgets.QPushButton("+") + add_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + add_btn.setProperty("btn-type", "tool-item") + add_btn.setFixedSize(self._btn_size, self._btn_size) + + edit_btn = None + if self.collapsable_key: + edit_btn = IconButton( + "fa.edit", QtCore.Qt.lightGray, QtCore.Qt.white + ) + edit_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + edit_btn.setProperty("btn-type", "tool-item-icon") + edit_btn.setFixedHeight(self._btn_size) + + remove_btn = QtWidgets.QPushButton("-") + remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + remove_btn.setProperty("btn-type", "tool-item") + remove_btn.setFixedSize(self._btn_size, self._btn_size) + + key_input_label_widget = None + key_label_input_label_widget = None + if self.collapsable_key: + key_input_label_widget = QtWidgets.QLabel("Key:") + key_label_input_label_widget = QtWidgets.QLabel("Label:") + wrapper_widget.add_widget_before_label(edit_btn) + wrapper_widget.add_widget_after_label(key_input_label_widget) + wrapper_widget.add_widget_after_label(key_input) + wrapper_widget.add_widget_after_label(key_label_input_label_widget) + wrapper_widget.add_widget_after_label(key_label_input) + wrapper_widget.add_widget_after_label(remove_btn) + + else: + layout.addWidget(add_btn, 0) + layout.addWidget(remove_btn, 0) + layout.addWidget(key_input, 0) + layout.addWidget(spacer_widget, 1) + layout.addWidget(value_input, 1) + + self.setFocusProxy(value_input) + + key_input.textChanged.connect(self._on_key_change) + key_input.returnPressed.connect(self._on_enter_press) + if key_label_input: + key_label_input.textChanged.connect(self._on_key_change) + key_label_input.returnPressed.connect(self._on_enter_press) + + value_input.value_changed.connect(self._on_value_change) + if add_btn: + add_btn.clicked.connect(self.on_add_clicked) + if edit_btn: + edit_btn.clicked.connect(self.on_edit_pressed) + remove_btn.clicked.connect(self.on_remove_clicked) + + self.key_input = key_input + self.key_input_label_widget = key_input_label_widget + self.key_label_input = key_label_input + self.key_label_input_label_widget = key_label_input_label_widget + self.value_input = value_input + self.wrapper_widget = wrapper_widget + + self.spacer_widget = spacer_widget + + self.add_btn = add_btn + self.edit_btn = edit_btn + self.remove_btn = remove_btn + + self.set_as_empty(self._is_empty) + + def _style_state(self): + if self.as_widget: + state = self.style_state( + False, + self._is_invalid, + False, + self.is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + return state + + @property + def collapsable_key(self): + return self._parent.collapsable_key + + def key_value(self): + return self.key_input.text() + + def is_key_invalid(self): + if self._is_empty: + return False + + if self.key_value() == "": + return True + + if self._is_key_duplicated: + return True + return False + + def set_key_is_duplicated(self, duplicated): + if duplicated == self._is_key_duplicated: + return + + self._is_key_duplicated = duplicated + if self.collapsable_key: + if duplicated: + self.set_edit_mode(True) + else: + self._on_focus_lose() + self.update_style() + + def set_as_required(self, key): + self.key_input.setText(key) + self.key_input.setEnabled(False) + self._is_required = True + + if self._is_empty: + self.set_as_empty(False) + + if self.collapsable_key: + self.remove_btn.setVisible(False) + else: + self.remove_btn.setEnabled(False) + self.add_btn.setEnabled(False) + + def set_as_last_required(self): + if self.add_btn: + self.add_btn.setEnabled(True) + + def _on_focus_lose(self): + if ( + self.edit_btn.hasFocus() + or self.key_input.hasFocus() + or self.key_label_input.hasFocus() + or self.remove_btn.hasFocus() + ): + return + self._on_enter_press() + + def _on_enter_press(self): + if not self.collapsable_key: + return + + if self._is_empty: + self.on_add_clicked() + else: + self.set_edit_mode(False) + + def _on_key_label_change(self): + self.update_key_label() + + def _on_key_change(self): + if self.value_is_env_group: + self.value_input.env_group_key = self.key_input.text() + + self.update_key_label() + + self._on_value_change() + + def _on_value_change(self, item=None): + self.update_style() + self.value_changed.emit(self) + + def update_default_values(self, key, label, value): + self.origin_key = key + self.key_input.setText(key) + if self.key_label_input: + label = label or "" + self.origin_key_label = label + self.key_label_input.setText(label) + self.value_input.update_default_values(value) + + def update_studio_values(self, key, label, value): + self.origin_key = key + self.key_input.setText(key) + if self.key_label_input: + label = label or "" + self.origin_key_label = label + self.key_label_input.setText(label) + self.value_input.update_studio_values(value) + + def apply_overrides(self, key, label, value): + self.origin_key = key + self.key_input.setText(key) + if self.key_label_input: + label = label or "" + self.origin_key_label = label + self.key_label_input.setText(label) + self.value_input.apply_overrides(value) + + @property + def value_is_env_group(self): + return self._parent.value_is_env_group + + @property + def is_group(self): + return self._parent.is_group + + def update_key_label(self): + if not self.wrapper_widget: + return + key_value = self.key_input.text() + key_label_value = self.key_label_input.text() + if key_label_value: + label = "{} ({})".format(key_label_value, key_value) + else: + label = key_value + self.wrapper_widget.label_widget.setText(label) + + def on_add_clicked(self): + if not self.collapsable_key: + if self._is_empty: + self.set_as_empty(False) + else: + self._parent.add_row(row=self.row() + 1) + return + + if not self._is_empty: + return + + if not self.key_value(): + return + + self.set_as_empty(False) + self._parent.add_row(row=self.row() + 1, is_empty=True) + + def on_edit_pressed(self): + if not self.key_input.isVisible(): + self.set_edit_mode() + else: + self.key_input.setFocus() + + def set_edit_mode(self, enabled=True): + if self.is_invalid and not enabled: + return + self.wrapper_widget.label_widget.setVisible(not enabled) + self.key_label_input_label_widget.setVisible(enabled) + self.key_input.setVisible(enabled) + self.key_input_label_widget.setVisible(enabled) + self.key_label_input.setVisible(enabled) + if not self._is_required: + self.remove_btn.setVisible(enabled) + if enabled: + if self.key_input.isEnabled(): + self.key_input.setFocus() + else: + self.key_label_input.setFocus() + + def on_remove_clicked(self): + self._parent.remove_row(self) + + def set_as_empty(self, is_empty=True): + self._is_empty = is_empty + + self.value_input.setVisible(not is_empty) + if not self.collapsable_key: + self.key_input.setVisible(not is_empty) + self.remove_btn.setEnabled(not is_empty) + self.spacer_widget.setVisible(is_empty) + + else: + self.remove_btn.setVisible(False) + self.key_input_label_widget.setVisible(is_empty) + self.key_input.setVisible(is_empty) + self.key_label_input_label_widget.setVisible(is_empty) + self.key_label_input.setVisible(is_empty) + self.edit_btn.setVisible(not is_empty) + + self.wrapper_widget.label_widget.setVisible(not is_empty) + if is_empty: + self.wrapper_widget.hide_toolbox() + else: + self.wrapper_widget.show_toolbox() + self._on_value_change() + + @property + def any_parent_is_group(self): + return self._parent.any_parent_is_group + + def is_key_modified(self): + return self.key_value() != self.origin_key + + def is_key_label_modified(self): + return self.key_label_value() != self.origin_key_label + + def is_value_modified(self): + return self.value_input.is_modified + + @property + def is_modified(self): + if self._is_empty: + return False + return ( + self.is_key_modified() + or self.is_key_label_modified() + or self.is_value_modified() + ) + + def hierarchical_style_update(self): + self.value_input.hierarchical_style_update() + self.update_style() + + @property + def is_invalid(self): + if self._is_empty: + return False + return self.is_key_invalid() or self.value_input.is_invalid + + def update_style(self): + key_input_state = "" + if not self._is_empty: + if self.is_key_invalid(): + key_input_state = "invalid" + elif self.is_key_modified(): + key_input_state = "modified" + + self.key_input.setProperty("state", key_input_state) + self.key_input.style().polish(self.key_input) + + if not self.wrapper_widget: + return + + state = self._style_state() + + if self._state == state: + return + + self._state = state + + if self.wrapper_widget.label_widget: + self.wrapper_widget.label_widget.setProperty("state", state) + self.wrapper_widget.label_widget.style().polish( + self.wrapper_widget.label_widget + ) + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + + self.wrapper_widget.side_line_widget.setProperty("state", child_state) + self.wrapper_widget.side_line_widget.style().polish( + self.wrapper_widget.side_line_widget + ) + + def row(self): + return self._parent.input_fields.index(self) + + def key_label_value(self): + if self.collapsable_key: + return self.key_label_input.text() + return NOT_SET + + def item_value(self): + key = self.key_input.text() + value = self.value_input.item_value() + return {key: value} + + def config_value(self): + if self._is_empty: + return {} + return self.item_value() + + def mouseReleaseEvent(self, event): + return QtWidgets.QWidget.mouseReleaseEvent(self, event) + + +class ModifiableDict(QtWidgets.QWidget, InputObject): + default_input_value = {} + # Should be used only for dictionary with one datatype as value + # TODO this is actually input field (do not care if is group or not) + value_changed = QtCore.Signal(object) + expand_in_grid = True + valid_value_types = (dict, ) + + def __init__( + self, schema_data, parent, as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(ModifiableDict, self).__init__(parent_widget) + self.setObjectName("ModifiableDict") + + self.initial_attributes(schema_data, parent, as_widget) + + self.input_fields = [] + self.required_inputs_by_key = {} + + # Validation of "key" key + self.key = schema_data["key"] + self.value_is_env_group = ( + schema_data.get("value_is_env_group") or False + ) + self.hightlight_content = schema_data.get("highlight_content") or False + self.collapsable_key = schema_data.get("collapsable_key") or False + self.required_keys = schema_data.get("required_keys") or [] + + object_type = schema_data["object_type"] + if isinstance(object_type, dict): + self.item_schema = object_type + else: + # Backwards compatibility + self.item_schema = { + "type": object_type + } + input_modifiers = schema_data.get("input_modifiers") or {} + if input_modifiers: + self.log.warning(( + "Used deprecated key `input_modifiers` to define item." + " Rather use `object_type` as dictionary with modifiers." + )) + self.item_schema.update(input_modifiers) + + if self.value_is_env_group: + self.item_schema["env_group_key"] = "" + + def create_ui(self, label_widget=None): + if self.hightlight_content: + content_state = "hightlighted" + bottom_margin = 5 + else: + content_state = "" + bottom_margin = 0 + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + label = self.schema_data.get("label") + + if self.as_widget: + body_widget = None + self.label_widget = label_widget + + elif label is None: + body_widget = None + self.label_widget = None + else: + body_widget = ExpandingWidget(self.schema_data["label"], self) + main_layout.addWidget(body_widget) + + self.label_widget = body_widget.label_widget + + self.body_widget = body_widget + + if body_widget is None: + content_parent_widget = self + else: + content_parent_widget = body_widget + + content_widget = QtWidgets.QWidget(content_parent_widget) + content_widget.setObjectName("ContentWidget") + content_widget.setProperty("content_state", content_state) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, bottom_margin) + + if body_widget is None: + main_layout.addWidget(content_widget) + else: + body_widget.set_content_widget(content_widget) + + self.body_widget = body_widget + self.content_widget = content_widget + self.content_layout = content_layout + + if body_widget: + collapsable = self.schema_data.get("collapsable", True) + if collapsable: + collapsed = self.schema_data.get("collapsed", True) + if not collapsed: + body_widget.toggle_content() + + else: + body_widget.hide_toolbox(hide_content=False) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + last_required_item = None + for key in self.required_keys: + last_required_item = self.add_row(key=key, is_required=True) + + if last_required_item: + last_required_item.set_as_last_required() + else: + self.add_row(is_empty=True) + + def count(self): + return len(self.input_fields) + + def set_value(self, value): + self.validate_value(value) + + metadata = value.get(METADATA_KEY, {}) + dynamic_key_labels = metadata.get("dynamic_key_label") or {} + + required_items = list(self.required_inputs_by_key.values()) + previous_inputs = list() + for input_field in self.input_fields: + if input_field not in required_items: + previous_inputs.append(input_field) + + for item_key, item_value in value.items(): + if item_key is METADATA_KEY: + continue + + label = dynamic_key_labels.get(item_key) + self.add_row(key=item_key, label=label, value=item_value) + + if self.collapsable_key: + self.add_row(is_empty=True) + + for input_field in previous_inputs: + self.remove_row(input_field) + + if self.count() == 0: + self.add_row(is_empty=True) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + fields_by_keys = collections.defaultdict(list) + for input_field in self.input_fields: + key = input_field.key_value() + fields_by_keys[key].append(input_field) + + for fields in fields_by_keys.values(): + if len(fields) == 1: + field = fields[0] + field.set_key_is_duplicated(False) + else: + for field in fields: + field.set_key_is_duplicated(True) + + if self.is_overidable: + self._is_overriden = True + else: + self._has_studio_override = True + + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + elif self._has_studio_override: + self._is_modified = self.item_value() != self.studio_value + else: + self._is_modified = self.item_value() != self.default_value + + self.update_style() + + self.value_changed.emit(self) + + @property + def is_modified(self): + is_modified = super(ModifiableDict, self).is_modified + if is_modified: + return is_modified + + for input_field in self.input_fields: + if input_field.is_modified: + return True + return False + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + + def update_style(self): + state = self._style_state() + + if self._state == state: + return + + self._state = state + + if self.label_widget: + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + if not self.body_widget: + return + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + + def all_item_values(self): + output = {} + for item in self.input_fields: + output.update(item.item_value()) + return output + + def item_value_with_metadata(self): + if not self.collapsable_key: + output = self.item_value() + + else: + output = {} + labels_by_key = {} + for item in self.input_fields: + labels_by_key[item.key_value()] = item.key_label_value() + output.update(item.config_value()) + if METADATA_KEY not in output: + output[METADATA_KEY] = {} + output[METADATA_KEY]["dynamic_key_label"] = labels_by_key + + if self.value_is_env_group: + for env_group_key, value in tuple(output.items()): + env_keys = [] + for key in value.keys(): + if key is not METADATA_KEY: + env_keys.append(key) + + if METADATA_KEY not in value: + value[METADATA_KEY] = {} + + value[METADATA_KEY]["environments"] = {env_group_key: env_keys} + output[env_group_key] = value + return output + + def item_value(self): + output = {} + for item in self.input_fields: + output.update(item.config_value()) + return output + + def config_value(self): + return {self.key: self.item_value_with_metadata()} + + def _create_item(self, row, key, is_empty, is_required): + # Create new item + item_widget = ModifiableDictItem( + self.item_schema, self, self.content_widget + ) + if is_empty: + item_widget.set_as_empty() + + if is_required: + item_widget.set_as_required(key) + self.required_inputs_by_key[key] = item_widget + + item_widget.value_changed.connect(self._on_value_change) + + if row is None: + self.content_layout.addWidget(item_widget) + self.input_fields.append(item_widget) + else: + self.content_layout.insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + + previous_input = None + if self.collapsable_key: + for input_field in self.input_fields: + if previous_input is not None: + self.setTabOrder( + previous_input, input_field.value_input + ) + previous_input = input_field.value_input.focusProxy() + + else: + for input_field in self.input_fields: + if previous_input is not None: + self.setTabOrder( + previous_input, input_field.key_input + ) + previous_input = input_field.value_input.focusProxy() + self.setTabOrder( + input_field.key_input, previous_input + ) + return item_widget + + def add_row( + self, + row=None, + key=None, + label=None, + value=None, + is_empty=False, + is_required=False + ): + item_widget = self.required_inputs_by_key.get(key) + if not item_widget: + item_widget = self._create_item(row, key, is_empty, is_required) + + # Set value if entered value is not None + # else (when add button clicked) trigger `_on_value_change` + if value is not None and key is not None: + if not self._has_studio_override: + item_widget.update_default_values(key, label, value) + elif self._is_overriden: + item_widget.apply_overrides(key, label, value) + else: + item_widget.update_studio_values(key, label, value) + self.hierarchical_style_update() + else: + self._on_value_change() + self.parent().updateGeometry() + + return item_widget + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + self.content_layout.removeWidget(item_widget) + self.input_fields.remove(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() + + if self.count() == 0: + self.add_row(is_empty=True) + + self._on_value_change() + self.parent().updateGeometry() + + @property + def is_invalid(self): + return self._is_invalid or self.child_invalid + + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.is_invalid: + return True + return False + + +# Dictionaries +class DictWidget(QtWidgets.QWidget, SettingObject): + value_changed = QtCore.Signal(object) + expand_in_grid = True + valid_value_types = (dict, type(NOT_SET)) + + def __init__( + self, schema_data, parent, as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(DictWidget, self).__init__(parent_widget) + + self.initial_attributes(schema_data, parent, as_widget) + + self.input_fields = [] + + self.checkbox_widget = None + self.checkbox_key = schema_data.get("checkbox_key") + + self.highlight_content = schema_data.get("highlight_content", False) + self.show_borders = schema_data.get("show_borders", True) + self.collapsable = schema_data.get("collapsable", True) + self.collapsed = schema_data.get("collapsed", True) + + def create_ui(self, label_widget=None): + if not self.as_widget and self.schema_data.get("label") is None: + self._ui_item_without_label() + else: + self._ui_item_or_as_widget(label_widget) + + for child_data in self.schema_data.get("children", []): + self.add_children_gui(child_data) + + any_visible = False + for input_field in self.input_fields: + if not input_field.hidden_by_role: + any_visible = True + break + + if not any_visible: + self.hide() + + def _ui_item_without_label(self): + if self._is_group: + raise TypeError( + "Dictionary without label can't be marked as group input." + ) + + self.setObjectName("DictInvisible") + + self.label_widget = None + self.body_widget = None + self.content_layout = QtWidgets.QGridLayout(self) + self.content_layout.setContentsMargins(0, 0, 0, 0) + self.content_layout.setSpacing(5) + + def _ui_item_or_as_widget(self, label_widget): + content_widget = QtWidgets.QWidget(self) + + if self.as_widget: + content_widget.setObjectName("DictAsWidgetBody") + show_borders = str(int(self.show_borders)) + content_widget.setProperty("show_borders", show_borders) + content_layout_margins = (5, 5, 5, 5) + main_layout_spacing = 5 + body_widget = None + + else: + content_widget.setObjectName("ContentWidget") + if self.highlight_content: + content_state = "hightlighted" + bottom_margin = 5 + else: + content_state = "" + bottom_margin = 0 + content_widget.setProperty("content_state", content_state) + content_layout_margins = (CHILD_OFFSET, 5, 0, bottom_margin) + main_layout_spacing = 0 + + body_widget = ExpandingWidget(self.schema_data["label"], self) + label_widget = body_widget.label_widget + body_widget.set_content_widget(content_widget) + + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setContentsMargins(*content_layout_margins) + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(main_layout_spacing) + if not body_widget: + main_layout.addWidget(content_widget) + else: + main_layout.addWidget(body_widget) + + self.label_widget = label_widget + self.body_widget = body_widget + self.content_layout = content_layout + + if body_widget: + if len(self.input_fields) == 1 and self.checkbox_widget: + body_widget.hide_toolbox(hide_content=True) + + elif self.collapsable: + if not self.collapsed: + body_widget.toggle_content() + else: + body_widget.hide_toolbox(hide_content=False) + + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + row = self.content_layout.rowCount() + if not getattr(klass, "is_input_type", False): + item = klass(child_configuration, self) + self.content_layout.addWidget(item, row, 0, 1, 2) + return item + + if self.checkbox_key and not self.checkbox_widget: + key = child_configuration.get("key") + if key == self.checkbox_key: + if child_configuration["type"] != "boolean": + self.log.warning(( + "SCHEMA BUG: Dictionary item has set as checkbox" + " item invalid type \"{}\". Expected \"boolean\"." + ).format(child_configuration["type"])) + elif self.body_widget is None: + self.log.warning(( + "SCHEMA BUG: Dictionary item has set checkbox" + " item but item does not have label." + ).format(child_configuration["type"])) + else: + return self._add_checkbox_child(child_configuration) + + label_widget = None + item = klass(child_configuration, self) + if not item.expand_in_grid: + label = child_configuration.get("label") + if label is not None: + label_widget = GridLabelWidget(label, self) + self.content_layout.addWidget(label_widget, row, 0, 1, 1) + + item.create_ui(label_widget=label_widget) + item.value_changed.connect(self._on_value_change) + + if label_widget: + if item.hidden_by_role: + label_widget.hide() + label_widget.input_field = item + self.content_layout.addWidget(item, row, 1, 1, 1) + else: + self.content_layout.addWidget(item, row, 0, 1, 2) + + self.input_fields.append(item) + return item + + def _add_checkbox_child(self, child_configuration): + item = BooleanWidget( + child_configuration, self + ) + item.create_ui(label_widget=self.label_widget) + item.value_changed.connect(self._on_value_change) + + self.body_widget.add_widget_before_label(item) + self.checkbox_widget = item + self.input_fields.append(item) + return item + + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + for input_field in self.input_fields: + input_field.remove_overrides() + + def reset_to_pype_default(self): + for input_field in self.input_fields: + input_field.reset_to_pype_default() + self._has_studio_override = False + + def set_studio_default(self): + for input_field in self.input_fields: + input_field.set_studio_default() + + if self.is_group: + self._has_studio_override = True + + def discard_changes(self): + self._is_modified = False + self._is_overriden = self._was_overriden + self._has_studio_override = self._had_studio_override + + for input_field in self.input_fields: + input_field.discard_changes() + + self._is_modified = self.child_modified + if not self.is_overidable and self.as_widget: + if self.has_studio_override: + self._is_modified = self.studio_value != self.item_value() + else: + self._is_modified = self.default_value != self.item_value() + + self._state = None + self._is_overriden = self._was_overriden + + def set_as_overriden(self): + if self.is_overriden: + return + + if self.is_group: + self._is_overriden = True + return + + for item in self.input_fields: + item.set_as_overriden() + + def update_default_values(self, parent_values): + # Make sure this is set to False + self._state = None + self._child_state = None + + value = NOT_SET + if self.as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + try: + self.validate_value(value) + except InvalidValueType as exc: + value = NOT_SET + self.log.warning(exc.msg) + + for item in self.input_fields: + item.update_default_values(value) + + def update_studio_values(self, parent_values): + # Make sure this is set to False + self._state = None + self._child_state = None + value = NOT_SET + if self.as_widget: + value = parent_values + else: + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + self._has_studio_override = False + if self.is_group and value is not NOT_SET: + self._has_studio_override = True + + self._had_studio_override = bool(self._has_studio_override) + + try: + self.validate_value(value) + except InvalidValueType as exc: + value = NOT_SET + self._has_studio_override = False + self._had_studio_override = False + self.log.warning(exc.msg) + + for item in self.input_fields: + item.update_studio_values(value) + + def apply_overrides(self, parent_values): + # Make sure this is set to False + self._state = None + self._child_state = None + + if self.as_widget: + override_values = parent_values + else: + metadata = {} + groups = tuple() + override_values = NOT_SET + if parent_values is not NOT_SET: + metadata = parent_values.get(METADATA_KEY) or metadata + groups = metadata.get("groups") or groups + override_values = parent_values.get(self.key, override_values) + + self._is_overriden = self.key in groups + + try: + self.validate_value(override_values) + except InvalidValueType as exc: + override_values = NOT_SET + self.log.warning(exc.msg) + + for item in self.input_fields: + item.apply_overrides(override_values) + + if not self.as_widget: + if not self._is_overriden: + self._is_overriden = ( + self.is_group + and self.is_overidable + and self.child_overriden + ) + self._was_overriden = bool(self._is_overriden) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_group and not (self.as_widget or self.any_parent_as_widget): + if self.is_overidable: + self._is_overriden = True + else: + self._has_studio_override = True + + # TODO check if this is required + self.hierarchical_style_update() + + self.value_changed.emit(self) + + self.update_style() + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + + def update_style(self, is_overriden=None): + # TODO add style update when used as widget + if not self.body_widget: + return + + child_has_studio_override = self.child_has_studio_override + child_modified = self.child_modified + child_invalid = self.child_invalid + child_state = self.style_state( + child_has_studio_override, + child_invalid, + self.child_overriden, + child_modified + ) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + self._child_state = child_state + + state = self.style_state( + self.had_studio_override, + child_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + @property + def is_modified(self): + if self.is_group: + return self._is_modified or self.child_modified + return False + + @property + def child_has_studio_override(self): + for input_field in self.input_fields: + if ( + input_field.has_studio_override + or input_field.child_has_studio_override + ): + return True + return False + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.is_overriden or input_field.child_overriden: + return True + return False + + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.child_invalid: + return True + return False + + def get_invalid(self): + output = [] + for input_field in self.input_fields: + output.extend(input_field.get_invalid()) + return output + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def _override_values(self, project_overrides): + values = {} + groups = [] + for input_field in self.input_fields: + if project_overrides: + value, is_group = input_field.overrides() + else: + value, is_group = input_field.studio_overrides() + if value is NOT_SET: + continue + + if METADATA_KEY in value and METADATA_KEY in values: + new_metadata = value.pop(METADATA_KEY) + values[METADATA_KEY] = self.merge_metadata( + values[METADATA_KEY], new_metadata + ) + + values.update(value) + if is_group: + groups.extend(value.keys()) + + if groups: + if METADATA_KEY not in values: + values[METADATA_KEY] = {} + values[METADATA_KEY]["groups"] = groups + return {self.key: values}, self.is_group + + def studio_overrides(self): + if ( + not (self.as_widget or self.any_parent_as_widget) + and not self.has_studio_override + and not self.child_has_studio_override + ): + return NOT_SET, False + return self._override_values(False) + + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + return self._override_values(True) + + +class PathWidget(QtWidgets.QWidget, SettingObject): + value_changed = QtCore.Signal(object) + platforms = ("windows", "darwin", "linux") + platform_labels_mapping = { + "windows": "Windows", + "darwin": "MacOS", + "linux": "Linux" + } + + def __init__( + self, schema_data, parent, as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(PathWidget, self).__init__(parent_widget) + + self.initial_attributes(schema_data, parent, as_widget) + + # This is partial input and dictionary input + if not self.any_parent_is_group and not self._as_widget: + self._is_group = True + else: + self._is_group = False + + self.multiplatform = schema_data.get("multiplatform", False) + self.multipath = schema_data.get("multipath", False) + self.with_arguments = schema_data.get("with_arguments", False) + + self.input_field = None + + def create_ui(self, label_widget=None): + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + if not self.as_widget and not label_widget: + label_widget = QtWidgets.QLabel(self.schema_data["label"]) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) + self.label_widget = label_widget + + self.content_widget = QtWidgets.QWidget(self) + self.content_layout = QtWidgets.QVBoxLayout(self.content_widget) + self.content_layout.setSpacing(0) + self.content_layout.setContentsMargins(0, 0, 0, 0) + + layout.addWidget(self.content_widget) + + self.create_ui_inputs() + + @property + def default_input_value(self): + if self.multipath: + value_type = list + else: + value_type = str + + if self.multiplatform: + return { + platform: value_type() + for platform in self.platforms + } + return value_type() + + def create_ui_inputs(self): + if not self.multiplatform and not self.multipath: + item_schema = { + "key": self.key, + "with_arguments": self.with_arguments + } + path_input = PathInputWidget(item_schema, self, as_widget=True) + path_input.create_ui(label_widget=self.label_widget) + + self.setFocusProxy(path_input) + self.content_layout.addWidget(path_input) + self.input_field = path_input + path_input.value_changed.connect(self._on_value_change) + return + + if not self.multiplatform: + item_schema = { + "key": self.key, + "object_type": { + "type": "path-input", + "with_arguments": self.with_arguments + } + } + input_widget = ListWidget(item_schema, self, as_widget=True) + input_widget.create_ui(label_widget=self.label_widget) + self.setFocusProxy(input_widget) + self.content_layout.addWidget(input_widget) + self.input_field = input_widget + input_widget.value_changed.connect(self._on_value_change) + return + + item_schema = { + "type": "dict", + "show_borders": False, + "children": [] + } + for platform_key in self.platforms: + platform_label = self.platform_labels_mapping[platform_key] + child_item = { + "key": platform_key, + "label": platform_label + } + if self.multipath: + child_item["type"] = "list" + child_item["object_type"] = { + "type": "path-input", + "with_arguments": self.with_arguments + } + else: + child_item["type"] = "path-input" + child_item["with_arguments"] = self.with_arguments + + item_schema["children"].append(child_item) + + input_widget = DictWidget(item_schema, self, as_widget=True) + input_widget.create_ui(label_widget=self.label_widget) + + self.content_layout.addWidget(input_widget) + self.input_field = input_widget + input_widget.value_changed.connect(self._on_value_change) + + def update_default_values(self, parent_values): + self._state = None + self._child_state = None + self._is_modified = False + + value = NOT_SET + if self.as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + if value is NOT_SET: + if self.available_for_role("developer"): + self.defaults_not_set = True + value = self.default_input_value + if value is NOT_SET: + raise NotImplementedError(( + "{} Does not have implemented" + " attribute `default_input_value`" + ).format(self)) + + else: + raise ValueError( + "Default value is not set. This is implementation BUG." + ) + else: + self.defaults_not_set = False + + self.default_value = value + self._has_studio_override = False + self._had_studio_override = False + + # TODO handle invalid value type + self.input_field.update_default_values(value) + + def update_studio_values(self, parent_values): + self._state = None + self._child_state = None + self._is_modified = False + + value = NOT_SET + if self.as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + self.studio_value = value + if value is not NOT_SET: + self._has_studio_override = True + self._had_studio_override = True + else: + self._has_studio_override = False + self._had_studio_override = False + + # TODO handle invalid value type + self.input_field.update_studio_values(value) + + def apply_overrides(self, parent_values): + self._is_modified = False + self._state = None + self._child_state = None + + override_values = NOT_SET + if self._as_widget: + override_values = parent_values + elif parent_values is not NOT_SET: + override_values = parent_values.get(self.key, NOT_SET) + + self._is_overriden = override_values is not NOT_SET + self._was_overriden = bool(self._is_overriden) + + # TODO handle invalid value type + self.input_field.update_studio_values(override_values) + + if not self._is_overriden: + self._is_overriden = ( + self.is_group + and self.is_overidable + and self.child_overriden + ) + self._is_modified = False + self._was_overriden = bool(self._is_overriden) + + def set_value(self, value): + if not self.multiplatform: + return self.input_field.set_value(value) + + for _input_field in self.input_field.input_fields: + _value = value.get(_input_field.key, NOT_SET) + if _value is NOT_SET: + continue + _input_field.set_value(_value) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if not self.any_parent_as_widget: + if self.is_overidable: + self._is_overriden = True + else: + self._has_studio_override = True + + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + elif self._has_studio_override: + self._is_modified = self.item_value() != self.studio_value + else: + self._is_modified = self.item_value() != self.default_value + + self.hierarchical_style_update() + + self.value_changed.emit(self) + + def update_style(self, is_overriden=None): + child_has_studio_override = self.child_has_studio_override + child_modified = self.child_modified + child_invalid = self.child_invalid + child_state = self.style_state( + child_has_studio_override, + child_invalid, + self.child_overriden, + child_modified + ) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.setProperty("state", child_state) + self.style().polish(self) + self._child_state = child_state + + if self.label_widget: + state = self.style_state( + child_has_studio_override, + child_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + self.input_field.remove_overrides() + + def reset_to_pype_default(self): + self.input_field.reset_to_pype_default() + self._has_studio_override = False + + def set_studio_default(self): + self.input_field.set_studio_default() + + if self.is_group: + self._has_studio_override = True + + def discard_changes(self): + self._is_modified = False + self._is_overriden = self._was_overriden + self._has_studio_override = self._had_studio_override + + self.input_field.discard_changes() + + self._is_modified = self.child_modified + if not self.is_overidable and self.as_widget: + if self.has_studio_override: + self._is_modified = self.studio_value != self.item_value() + else: + self._is_modified = self.default_value != self.item_value() + + self._state = None + self._is_overriden = self._was_overriden + + def set_as_overriden(self): + self._is_overriden = True + + @property + def child_has_studio_override(self): + return self.has_studio_override + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self.is_overriden + + @property + def child_invalid(self): + return self.input_field.child_invalid + + def hierarchical_style_update(self): + self.input_field.hierarchical_style_update() + self.update_style() + + def item_value(self): + return self.input_field.item_value() + + def studio_overrides(self): + if ( + not (self.as_widget or self.any_parent_as_widget) + and not self.has_studio_override + and not self.child_has_studio_override + ): + return NOT_SET, False + + value = {self.key: self.item_value()} + return value, self.is_group + + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + + value = {self.key: self.item_value()} + return value, self.is_group + + +class WrapperItemWidget(QtWidgets.QWidget, SettingObject): + value_changed = QtCore.Signal(object) + allow_actions = False + expand_in_grid = True + is_wrapper_item = True + + def __init__( + self, schema_data, parent, as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(WrapperItemWidget, self).__init__(parent_widget) + + self.input_fields = [] + + self.initial_attributes(schema_data, parent, as_widget) + + if self.as_widget: + raise TypeError( + "Wrapper items ({}) can't be used as widgets.".format( + self.__class__.__name__ + ) + ) + + if self.is_group: + raise TypeError( + "Wrapper items ({}) can't be used as groups.".format( + self.__class__.__name__ + ) + ) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self.wrapper_initial_attributes(schema_data) + + def wrapper_initial_attributes(self, schema_data): + """Initialization of attributes for specific wrapper.""" + return + + def create_ui(self, label_widget=None): + """UI implementation.""" + raise NotImplementedError( + "Method `create_ui` not implemented." + ) + + def update_style(self): + """Update items styles.""" + return + + def apply_overrides(self, parent_values): + for item in self.input_fields: + item.apply_overrides(parent_values) + + def discard_changes(self): + self._is_modified = False + self._is_overriden = self._was_overriden + self._has_studio_override = self._had_studio_override + + for input_field in self.input_fields: + input_field.discard_changes() + + self._is_modified = self.child_modified + if not self.is_overidable and self.as_widget: + if self.has_studio_override: + self._is_modified = self.studio_value != self.item_value() + else: + self._is_modified = self.default_value != self.item_value() + + self._state = None + self._is_overriden = self._was_overriden + + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + for input_field in self.input_fields: + input_field.remove_overrides() + + def reset_to_pype_default(self): + for input_field in self.input_fields: + input_field.reset_to_pype_default() + self._has_studio_override = False + + def set_studio_default(self): + for input_field in self.input_fields: + input_field.set_studio_default() + + if self.is_group: + self._has_studio_override = True + + def set_as_overriden(self): + if self.is_overriden: + return + + if self.is_group: + self._is_overriden = True + return + + for item in self.input_fields: + item.set_as_overriden() + + def update_default_values(self, value): + for item in self.input_fields: + item.update_default_values(value) + + def update_studio_values(self, value): + for item in self.input_fields: + item.update_studio_values(value) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self.value_changed.emit(self) + if self.any_parent_is_group: + self.hierarchical_style_update() + self.update_style() + + @property + def child_has_studio_override(self): + for input_field in self.input_fields: + if ( + input_field.has_studio_override + or input_field.child_has_studio_override + ): + return True + return False + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.is_overriden or input_field.child_overriden: + return True + return False + + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.child_invalid: + return True + return False + + def get_invalid(self): + output = [] + for input_field in self.input_fields: + output.extend(input_field.get_invalid()) + return output + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def config_value(self): + return self.item_value() + + def studio_overrides(self): + if ( + not (self.as_widget or self.any_parent_as_widget) + and not self.has_studio_override + and not self.child_has_studio_override + ): + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.studio_overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + if METADATA_KEY not in values: + values[METADATA_KEY] = {} + values[METADATA_KEY]["groups"] = groups + return values, self.is_group + + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + if METADATA_KEY not in values: + values[METADATA_KEY] = {} + values[METADATA_KEY]["groups"] = groups + return values, self.is_group + + +# Proxy for form layout +class FormLabel(QtWidgets.QLabel): + def __init__(self, input_field, *args, **kwargs): + super(FormLabel, self).__init__(*args, **kwargs) + self.input_field = input_field + + def mouseReleaseEvent(self, event): + if self.input_field: + return self.input_field.show_actions_menu(event) + return super(FormLabel, self).mouseReleaseEvent(event) + + +class FormItemWidget(WrapperItemWidget): + def create_ui(self, label_widget=None): + self.content_layout = QtWidgets.QFormLayout(self) + self.content_layout.setContentsMargins(0, 0, 0, 0) + + for child_data in self.schema_data["children"]: + self.add_children_gui(child_data) + + any_visible = False + for input_field in self.input_fields: + if not input_field.hidden_by_role: + any_visible = True + break + + if not any_visible: + self.hidden_by_role = True + self.hide() + + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + # Pop label to not be set in child + label = child_configuration["label"] + + klass = TypeToKlass.types.get(item_type) + + item = klass(child_configuration, self) + + label_widget = FormLabel(item, label, self) + + item.create_ui(label_widget=label_widget) + + if item.hidden_by_role: + label_widget.hide() + + item.value_changed.connect(self._on_value_change) + self.content_layout.addRow(label_widget, item) + self.input_fields.append(item) + return item + + +class CollapsibleWrapperItem(WrapperItemWidget): + def wrapper_initial_attributes(self, schema_data): + self.collapsable = schema_data.get("collapsable", True) + self.collapsed = schema_data.get("collapsed", True) + + def create_ui(self, label_widget=None): + content_widget = QtWidgets.QWidget(self) + content_widget.setObjectName("ContentWidget") + content_widget.setProperty("content_state", "") + + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) + + body_widget = ExpandingWidget(self.schema_data["label"], self) + body_widget.set_content_widget(content_widget) + + label_widget = body_widget.label_widget + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + if not body_widget: + main_layout.addWidget(content_widget) + else: + main_layout.addWidget(body_widget) + + self.label_widget = label_widget + self.body_widget = body_widget + self.content_layout = content_layout + + if self.collapsable: + if not self.collapsed: + body_widget.toggle_content() + else: + body_widget.hide_toolbox(hide_content=False) + + for child_data in self.schema_data.get("children", []): + self.add_children_gui(child_data) + + any_visible = False + for input_field in self.input_fields: + if not input_field.hidden_by_role: + any_visible = True + break + + if not any_visible: + self.hide() + + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + row = self.content_layout.rowCount() + if not getattr(klass, "is_input_type", False): + item = klass(child_configuration, self) + self.content_layout.addWidget(item, row, 0, 1, 2) + return item + + label_widget = None + item = klass(child_configuration, self) + if not item.expand_in_grid: + label = child_configuration.get("label") + if label is not None: + label_widget = GridLabelWidget(label, self) + self.content_layout.addWidget(label_widget, row, 0, 1, 1) + + item.create_ui(label_widget=label_widget) + item.value_changed.connect(self._on_value_change) + + if label_widget: + if item.hidden_by_role: + label_widget.hide() + label_widget.input_field = item + self.content_layout.addWidget(item, row, 1, 1, 1) + else: + self.content_layout.addWidget(item, row, 0, 1, 2) + + self.input_fields.append(item) + return item + + def update_style(self, is_overriden=None): + child_has_studio_override = self.child_has_studio_override + child_modified = self.child_modified + child_invalid = self.child_invalid + child_state = self.style_state( + child_has_studio_override, + child_invalid, + self.child_overriden, + child_modified + ) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + self._child_state = child_state + + state = self.style_state( + self.had_studio_override, + child_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + +class LabelWidget(QtWidgets.QWidget): + is_input_type = False + + def __init__(self, configuration, parent): + super(LabelWidget, self).__init__(parent) + self.setObjectName("LabelWidget") + + label = configuration["label"] + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 5, 0, 5) + label_widget = QtWidgets.QLabel(label, self) + layout.addWidget(label_widget) + + # Role handling + roles = configuration.get("roles") + if roles is not None and not isinstance(roles, list): + roles = [roles] + + if roles and parent.user_role not in roles: + self.hide() + self.hidden_by_role = True + else: + self.hidden_by_role = False + + +class SplitterWidget(QtWidgets.QWidget): + is_input_type = False + _height = 2 + + def __init__(self, configuration, parent): + super(SplitterWidget, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + splitter_item = QtWidgets.QWidget(self) + splitter_item.setObjectName("SplitterItem") + splitter_item.setMinimumHeight(self._height) + splitter_item.setMaximumHeight(self._height) + layout.addWidget(splitter_item) + + # Role handling + roles = configuration.get("roles") + if roles is not None and not isinstance(roles, list): + roles = [roles] + + if roles and parent.user_role not in roles: + self.hide() + self.hidden_by_role = True + else: + self.hidden_by_role = False diff --git a/pype/settings/entities/_item_widgets.py b/pype/settings/entities/_item_widgets.py new file mode 100644 index 0000000000..1e48618e5e --- /dev/null +++ b/pype/settings/entities/_item_widgets.py @@ -0,0 +1,115 @@ +class SettingWidget: + def __init__(self): + # TODO move to widget + if not self.available_for_role(): + self.hide() + self.hidden_by_role = True + + @classmethod + def style_state( + cls, has_studio_override, is_invalid, is_overriden, is_modified + ): + """Return stylesheet state by intered booleans.""" + items = [] + if is_invalid: + items.append("invalid") + else: + if is_overriden: + items.append("overriden") + if is_modified: + items.append("modified") + + if not items and has_studio_override: + items.append("studio") + + return "-".join(items) or "" + + def show_actions_menu(self, event=None): + if event and event.button() != QtCore.Qt.RightButton: + return + + if not self.allow_actions: + if event: + return self.mouseReleaseEvent(event) + return + + menu = QtWidgets.QMenu() + + actions_mapping = {} + if self.child_modified: + action = QtWidgets.QAction("Discard changes") + actions_mapping[action] = self._discard_changes + menu.addAction(action) + + if ( + self.is_overidable + and not self.is_overriden + and not self.any_parent_is_group + ): + action = QtWidgets.QAction("Set project override") + actions_mapping[action] = self._set_as_overriden + menu.addAction(action) + + if ( + not self.is_overidable + and ( + self.has_studio_override or self.child_has_studio_override + ) + ): + action = QtWidgets.QAction("Reset to pype default") + actions_mapping[action] = self._reset_to_pype_default + menu.addAction(action) + + if ( + not self.is_overidable + and not self.is_overriden + and not self.any_parent_is_group + and not self._had_studio_override + ): + action = QtWidgets.QAction("Set studio default") + actions_mapping[action] = self._set_studio_default + menu.addAction(action) + + if ( + not self.any_parent_overriden() + and (self.is_overriden or self.child_overriden) + ): + # TODO better label + action = QtWidgets.QAction("Remove project override") + actions_mapping[action] = self._remove_overrides + menu.addAction(action) + + if not actions_mapping: + action = QtWidgets.QAction("< No action >") + actions_mapping[action] = None + menu.addAction(action) + + result = menu.exec_(QtGui.QCursor.pos()) + if result: + to_run = actions_mapping[result] + if to_run: + to_run() + + def mouseReleaseEvent(self, event): + if self.allow_actions and event.button() == QtCore.Qt.RightButton: + return self.show_actions_menu() + + mro = type(self).mro() + index = mro.index(self.__class__) + item = None + for idx in range(index + 1, len(mro)): + _item = mro[idx] + if hasattr(_item, "mouseReleaseEvent"): + item = _item + break + + if item: + return item.mouseReleaseEvent(self, event) + + def hierarchical_style_update(self): + """Trigger update style method down the hierarchy.""" + raise NotImplementedError( + "{} Method `hierarchical_style_update` not implemented!".format( + repr(self) + ) + ) diff --git a/pype/settings/entities/base_entity.py b/pype/settings/entities/base_entity.py new file mode 100644 index 0000000000..6b7a0f4cde --- /dev/null +++ b/pype/settings/entities/base_entity.py @@ -0,0 +1,549 @@ +import enum +import copy +import inspect +import logging +from abc import ABCMeta, abstractmethod, abstractproperty + +import six + +from lib import NOT_SET, convert_data_to_gui_data +from constants import WRAPPER_TYPES, SYSTEM_SETTINGS_KEY +# from pype.settings.lib import get_default_settings +import os +import json + + +def subkey_merge(_dict, value, keys): + key = keys.pop(0) + if not keys: + _dict[key] = value + return _dict + + if key not in _dict: + _dict[key] = {} + _dict[key] = subkey_merge(_dict[key], value, keys) + + return _dict + + +def load_jsons_from_dir(path, *args, **kwargs): + output = {} + + path = os.path.normpath(path) + if not os.path.exists(path): + # TODO warning + return output + + sub_keys = list(kwargs.pop("subkeys", args)) + for sub_key in tuple(sub_keys): + _path = os.path.join(path, sub_key) + if not os.path.exists(_path): + break + + path = _path + sub_keys.pop(0) + + base_len = len(path) + 1 + for base, _directories, filenames in os.walk(path): + base_items_str = base[base_len:] + if not base_items_str: + base_items = [] + else: + base_items = base_items_str.split(os.path.sep) + + for filename in filenames: + basename, ext = os.path.splitext(filename) + if ext == ".json": + full_path = os.path.join(base, filename) + with open(full_path, "r") as opened_file: + value = json.load(opened_file) + + dict_keys = base_items + [basename] + output = subkey_merge(output, value, dict_keys) + + for sub_key in sub_keys: + output = output[sub_key] + return output + + +def get_default_settings(): + defaults_dir = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "defaults" + ) + return load_jsons_from_dir(defaults_dir) + + +# from pype.api import Logger +class Logger: + def get_logger(self, name): + return logging.getLogger(name) + + +class InvalidValueType(Exception): + msg_template = "{}" + + def __init__(self, valid_types, invalid_type, key): + msg = "" + if key: + msg += "Key \"{}\". ".format(key) + + joined_types = ", ".join( + [str(valid_type) for valid_type in valid_types] + ) + msg += "Got invalid type \"{}\". Expected: {}".format( + invalid_type, joined_types + ) + self.msg = msg + super(InvalidValueType, self).__init__(msg) + + +class OverrideState(enum.Enum): + STUDIO = 1 + PROJECT = 1 + + +@six.add_metaclass(ABCMeta) +class BaseEntity: + """Partially abstract class for Setting's item type workflow.""" + # `is_input_type` attribute says if has implemented item type methods + is_input_type = True + # Each input must have implemented default value for development + # when defaults are not filled yet. + default_input_value = NOT_SET + # Will allow to show actions for the item type (disabled for proxies) else + # item is skipped and try to trigger actions on it's parent. + allow_actions = True + # If item can store environment values + allow_to_environment = False + # Item will expand to full width in grid layout + expand_in_grid = False + + def __init__(self, schema_data, parent, is_dynamic_item=False): + self.schema_data = schema_data + self.parent = parent + + # Log object + self._log = None + + # These should be set on initialization and not change then + self.valid_value_types = NOT_SET + + self.is_group = False + self.is_in_group = False + + # NOTE was `as_widget` + self.is_dynamic_item = is_dynamic_item + self.is_in_dynamic_item = False + + self.env_group_key = None + self.is_env_group = False + + self.roles = None + + # Item require key to be able load or store data + self.require_key = True + + self.key = None + self.label = None + + # These attributes may change values during existence of an object + # Values + self.default_value = NOT_SET + self.studio_override_value = NOT_SET + self.project_override_value = NOT_SET + self.current_value = NOT_SET + + # Only for develop mode + self.defaults_not_set = False + + # Default input attributes + self.has_studio_override = False + self.had_studio_override = False + + self.has_project_override = False + self.had_project_override = False + + self.value_is_modified = False + self.is_invalid = False + + self.override_state = OverrideState.STUDIO + + @abstractmethod + def set_override_state(self, state): + pass + + def on_value_change(self): + # TODO implement + pass + + @abstractmethod + def set_value(self, value): + pass + + def validate_value(self, value): + if not self.valid_value_types: + return + + for valid_type in self.valid_value_types: + if type(value) is valid_type: + return + + key = getattr(self, "key", None) + raise InvalidValueType(self.valid_value_types, type(value), key) + + def merge_metadata(self, current_metadata, new_metadata): + for key, value in new_metadata.items(): + if key not in current_metadata: + current_metadata[key] = value + + elif key == "groups": + current_metadata[key].extend(value) + + elif key == "environments": + for group_key, subvalue in value.items(): + if group_key not in current_metadata[key]: + current_metadata[key][group_key] = [] + current_metadata[key][group_key].extend(subvalue) + + else: + raise KeyError("Unknown metadata key: \"{}\"".format(key)) + return current_metadata + + def available_for_role(self, role_name=None): + if not self.roles: + return True + if role_name is None: + role_name = self.user_role + return role_name in self.roles + + @property + def user_role(self): + """Tool is running with any user role. + + Returns: + str: user role as string. + + """ + return self.parent.user_role + + @property + def is_overidable(self): + """ care about overrides.""" + return self.parent.is_overidable + + @property + def log(self): + """Auto created logger for debugging.""" + if self._log is None: + self._log = Logger().get_logger(self.__class__.__name__) + return self._log + + @abstractmethod + def update_default_values(self, parent_values): + """Fill default values on startup or on refresh. + + Default values stored in `pype` repository should update all items in + schema. Each item should take values for his key and set it's value or + pass values down to children items. + + Args: + parent_values (dict): Values of parent's item. But in case item is + used as widget, `parent_values` contain value for item. + """ + pass + + @abstractmethod + def update_studio_values(self, parent_values): + """Fill studio override values on startup or on refresh. + + Set studio value if is not set to NOT_SET, in that case studio + overrides are not set yet. + + Args: + parent_values (dict): Values of parent's item. But in case item is + used as widget, `parent_values` contain value for item. + """ + pass + + @abstractmethod + def update_project_values(self, parent_values): + """Fill project override values on startup, refresh or project change. + + Set project value if is not set to NOT_SET, in that case project + overrides are not set yet. + + Args: + parent_values (dict): Values of parent's item. But in case item is + used as widget, `parent_values` contain value for item. + """ + pass + + @abstractproperty + def schema_types(self): + pass + + @abstractproperty + def child_has_studio_override(self): + """Any children item has studio overrides.""" + pass + + @abstractproperty + def child_value_modified(self): + """Any children item is modified.""" + pass + + @abstractproperty + def child_overriden(self): + """Any children item has project overrides.""" + pass + + @abstractproperty + def child_invalid(self): + """Any children item does not have valid value.""" + pass + + @abstractmethod + def get_invalid(self): + """Return invalid item types all down the hierarchy.""" + pass + + @abstractmethod + def settings_value(self): + """Value of an item without key.""" + pass + + @abstractmethod + def discard_changes(self): + """Item's implementation to discard all changes made by user. + + Reset all values to same values as had when opened GUI + or when changed project. + + Must not affect `had_studio_override` value or `was_overriden` + value. It must be marked that there are keys/values which are not in + defaults or overrides. + """ + pass + + @abstractmethod + def set_studio_default(self): + """Item's implementation to set current values as studio's overrides. + + Mark item and it's children as they have studio overrides. + """ + pass + + @abstractmethod + def reset_to_pype_default(self): + """Item's implementation to remove studio overrides. + + Mark item as it does not have studio overrides unset studio + override values. + """ + pass + + @abstractmethod + def remove_overrides(self): + """Item's implementation to remove project overrides. + + Mark item as does not have project overrides. Must not change + `was_overriden` attribute value. + """ + pass + + @abstractmethod + def set_as_overriden(self): + """Item's implementation to set values as overriden for project. + + Mark item and all it's children as they're overriden. Must skip + items with children items that has attributes `is_group` + and `any_parent_is_group` set to False. In that case those items + are not meant to be overridable and should trigger the method on it's + children. + + """ + pass + + +class RootEntity(BaseEntity): + schema_types = ["root"] + + def __init__(self, schema_data): + super(RootEntity, self).__init__(schema_data, None, None) + self.item_initalization() + self.reset_values() + + def reset_values(self): + default_values = get_default_settings()[SYSTEM_SETTINGS_KEY] + for key, child_obj in self.non_gui_children.items(): + child_obj.update_default_values(default_values[key]) + + studio_overrides = {} + for key, child_obj in self.non_gui_children.items(): + value = studio_overrides.get(key, NOT_SET) + child_obj.update_studio_values(value) + + # self.set_override_state(self.override_state) + # if self._hide_studio_overrides: + # system_values = lib.NOT_SET + # else: + # system_values = lib.convert_overrides_to_gui_data( + # {self.main_schema_key: get_studio_system_settings_overrides()} + # ) + # + # for input_field in self.input_fields: + # input_field.update_studio_values(system_values) + + def __getitem__(self, key): + return self.non_gui_children[key] + + def __setitem__(self, key, value): + self.non_gui_children[key].set_value(value) + + def __iter__(self): + for key in self.keys(): + yield key + + def get(self, key, default=None): + return self.non_gui_children.get(key, default) + + def keys(self): + return self.non_gui_children.keys() + + def values(self): + return self.non_gui_children.values() + + def items(self): + return self.non_gui_children.items() + + def _add_children(self, schema_data, first=True): + added_children = [] + for children_schema in schema_data["children"]: + if children_schema["type"] in WRAPPER_TYPES: + _children_schema = copy.deepcopy(children_schema) + wrapper_children = self._add_children( + children_schema["children"] + ) + _children_schema["children"] = wrapper_children + added_children.append(_children_schema) + continue + + child_obj = self.create_schema_object(children_schema, self) + self.children.append(child_obj) + added_children.append(child_obj) + if type(child_obj) in self._gui_types: + continue + + if child_obj.key in self.non_gui_children: + raise KeyError("Duplicated key \"{}\"".format(child_obj.key)) + self.non_gui_children[child_obj.key] = child_obj + + if not first: + return added_children + + for child_obj in added_children: + if isinstance(child_obj, BaseEntity): + continue + self.gui_wrappers.append(child_obj) + + def item_initalization(self): + self._loaded_types = None + self._gui_types = None + # Children are stored by key as keys are immutable and are defined by + # schema + self.valid_value_types = (dict, ) + self.children = [] + self.non_gui_children = {} + self.gui_wrappers = [] + self._add_children(self.schema_data) + + def create_schema_object(self, schema_data, *args, **kwargs): + if self._loaded_types is None: + import item_entities + self._loaded_types = {} + self._gui_types = [] + for attr in dir(item_entities): + item = getattr(item_entities, attr) + if not inspect.isclass(item): + continue + + if ( + not issubclass(item, BaseEntity) + or inspect.isabstract(item) + ): + # if item is BaseEntity: + # continue + # item() + continue + + for schema_type in item.schema_types: + self._loaded_types[schema_type] = item + + gui_type = getattr(item, "gui_type", False) + if gui_type: + self._gui_types.append(item) + + klass = self._loaded_types.get(schema_data["type"]) + if not klass: + raise KeyError("Unknown type \"{}\"".format(schema_data["type"])) + return klass(schema_data, *args, **kwargs) + + def set_override_state(self, state): + self.override_state = state + for child_obj in self.non_gui_children.values(): + child_obj.set_override_state(state) + + def set_value(self, value): + raise KeyError("{} does not allow to use `set_value`.".format( + self.__class__.__name__ + )) + + @property + def child_has_studio_override(self): + pass + + @property + def child_invalid(self): + for child_obj in self.non_gui_children.values(): + if child_obj.child_invalid: + return True + return False + + @property + def child_value_modified(self): + pass + + @property + def child_overriden(self): + pass + + def discard_changes(self): + pass + + def get_invalid(self): + pass + + def settings_value(self): + pass + + def remove_overrides(self): + pass + + def reset_to_pype_default(self): + pass + + def set_as_overriden(self): + pass + + def set_studio_default(self): + pass + + def update_default_values(self, parent_values): + pass + + def update_studio_values(self, parent_values): + pass + + def update_project_values(self, parent_values): + pass diff --git a/pype/settings/entities/constants.py b/pype/settings/entities/constants.py new file mode 100644 index 0000000000..b2f932a463 --- /dev/null +++ b/pype/settings/entities/constants.py @@ -0,0 +1,35 @@ +# Metadata keys for work with studio and project overrides +M_OVERRIDEN_KEY = "__overriden_keys__" +# Metadata key for storing information about environments +M_ENVIRONMENT_KEY = "__environment_keys__" +# Metadata key for storing dynamic created labels +M_DYNAMIC_KEY_LABEL = "__dynamic_keys_labels__" +# NOTE key popping not implemented yet +M_POP_KEY = "__pop_key__" + +METADATA_KEYS = ( + M_OVERRIDEN_KEY, + M_ENVIRONMENT_KEY, + M_DYNAMIC_KEY_LABEL, + M_POP_KEY +) + +# File where studio's system overrides are stored +SYSTEM_SETTINGS_KEY = "system_settings" +PROJECT_SETTINGS_KEY = "project_settings" +PROJECT_ANATOMY_KEY = "project_anatomy" + +WRAPPER_TYPES = ["form", "collapsible-wrap"] + +__all__ = ( + "M_OVERRIDEN_KEY", + "M_ENVIRONMENT_KEY", + "M_DYNAMIC_KEY_LABEL", + "M_POP_KEY", + + "METADATA_KEYS", + + "SYSTEM_SETTINGS_KEY", + "PROJECT_SETTINGS_KEY", + "PROJECT_ANATOMY_KEY" +) diff --git a/pype/settings/entities/item_entities.py b/pype/settings/entities/item_entities.py new file mode 100644 index 0000000000..1d1a97d875 --- /dev/null +++ b/pype/settings/entities/item_entities.py @@ -0,0 +1,711 @@ +import copy +from abc import abstractmethod + +from lib import NOT_SET +from constants import WRAPPER_TYPES +from base_entity import BaseEntity, RootEntity, OverrideState + + +""" +# Abstract properties: +schema_types +child_has_studio_override +child_value_modified +child_overriden +child_invalid + +# Abstract methods: + +""" + +class ItemEntity(BaseEntity): + def __init__(self, schema_data, parent, is_dynamic_item=False): + super(ItemEntity, self).__init__(schema_data, parent, is_dynamic_item) + + self.create_schema_object = self.parent.create_schema_object + + self.is_group = schema_data.get("is_group", False) + self.is_in_group = bool( + not self.is_group + and (parent.is_group or parent.is_in_group) + ) + + self.is_in_dynamic_item = bool( + not is_dynamic_item + and (parent.is_dynamic_item or parent.is_in_dynamic_item) + ) + + # If value should be stored to environments + self.env_group_key = schema_data.get("env_group_key") + self.is_env_group = bool(self.env_group_key is not None) + + roles = schema_data.get("roles") + if roles is None: + roles = parent.roles + elif not isinstance(roles, list): + roles = [roles] + self.roles = roles + + # States of inputs + # QUESTION has usage in entity? + self.state = None + + self.key = schema_data.get("key") + self.label = schema_data.get("label") + + self.item_initalization() + + if self.valid_value_types is NOT_SET: + raise ValueError("Attribute `valid_value_types` is not filled.") + + if self.require_key and not self.key: + error_msg = "Missing \"key\" in schema data. {}".format( + str(schema_data).replace("'", '"') + ) + raise KeyError(error_msg) + + if not self.label and self.is_group: + raise ValueError( + "Item is set as `is_group` but has empty `label`." + ) + + @abstractmethod + def item_initalization(self): + pass + + def set_override_state(self, state): + if state == self.override_state: + return + + self.override_state = state + if state is OverrideState.STUDIO: + self.set_value(self.studio_override_value) + elif state is OverrideState.PROJECT: + self.set_value(self.project_override_value) + + +class DictImmutableKeysEntity(ItemEntity): + schema_types = ["dict"] + + def __getitem__(self, key): + return self.non_gui_children[key] + + def __setitem__(self, key, value): + child_obj = self.non_gui_children[key] + child_obj.set_value(value) + + def __iter__(self): + for key in self.keys(): + yield key + + def get(self, key, default=None): + return self.non_gui_children.get(key, default) + + def keys(self): + return self.non_gui_children.keys() + + def values(self): + return self.non_gui_children.values() + + def items(self): + return self.non_gui_children.items() + + def _add_children(self, schema_data, first=True): + added_children = [] + for children_schema in schema_data["children"]: + if children_schema["type"] in WRAPPER_TYPES: + _children_schema = copy.deepcopy(children_schema) + wrapper_children = self._add_children( + children_schema["children"] + ) + _children_schema["children"] = wrapper_children + added_children.append(_children_schema) + continue + + child_obj = self.create_schema_object(children_schema, self) + self.children.append(child_obj) + added_children.append(child_obj) + if isinstance(child_obj, GUIEntity): + continue + + if child_obj.key in self.non_gui_children: + raise KeyError("Duplicated key \"{}\"".format(child_obj.key)) + self.non_gui_children[child_obj.key] = child_obj + + if not first: + return added_children + + for child_obj in added_children: + if isinstance(child_obj, BaseEntity): + continue + self.gui_wrappers.append(child_obj) + + def item_initalization(self): + # Children are stored by key as keys are immutable and are defined by + # schema + self.valid_value_types = (dict, ) + self.children = [] + self.non_gui_children = {} + self.gui_wrappers = [] + self._add_children(self.schema_data) + + def set_value(self, value): + for _key, _value in value.items(): + self.non_gui_children[_key].set_value(_value) + + def set_override_state(self, state): + self.override_state = state + for child_obj in self.non_gui_children.values(): + child_obj.set_override_state(state) + + @property + def child_has_studio_override(self): + pass + + @property + def child_invalid(self): + pass + + @property + def child_value_modified(self): + pass + + @property + def child_overriden(self): + pass + + def discard_changes(self): + pass + + def get_invalid(self): + pass + + def settings_value(self): + pass + + def remove_overrides(self): + pass + + def reset_to_pype_default(self): + pass + + def set_as_overriden(self): + pass + + def set_studio_default(self): + pass + + def update_default_values(self, values): + for key, child_obj in self.non_gui_children.items(): + child_obj.update_default_values(values[key]) + + def update_studio_values(self, parent_values): + pass + + def update_project_values(self, parent_values): + pass + + +class DictMutableKeysEntity(ItemEntity): + schema_types = ["dict-modifiable"] + _miss_arg = object() + + def __getitem__(self, key): + return self.children_by_key[key] + + def __setitem__(self, key, value): + if key in self.children_by_key: + self.children_by_key[key].set_value(value) + else: + self._add_child(key, value) + + def __iter__(self): + for key in self.keys(): + yield key + + def pop(self, key, default=_miss_arg): + if key not in self.children_by_key: + if default is self._miss_arg: + raise KeyError("Key \"{}\" not found.".format(key)) + return default + + child_obj = self.children_by_key.pop(key) + self.children.remove(child_obj) + return child_obj + + def get(self, key, default=None): + return self.non_gui_children.get(key, default) + + def keys(self): + return self.non_gui_children.keys() + + def values(self): + return self.non_gui_children.values() + + def items(self): + return self.non_gui_children.items() + + def _add_child(self, key, value): + new_child = self.create_schema_object(self.item_schema, self, True) + + new_child.set_value(value) + + self.children.append(new_child) + self.children_by_key[key] = new_child + return new_child + + def item_initalization(self): + # Children are stored by key as keys are immutable and are defined by + # schema + self.valid_value_types = (dict, ) + self.children = [] + self.children_by_key = {} + object_type = self.schema_data["object_type"] + if isinstance(object_type, dict): + self.item_schema = object_type + else: + # Backwards compatibility + self.item_schema = { + "type": object_type + } + input_modifiers = self.schema_data.get("input_modifiers") or {} + if input_modifiers: + self.log.warning(( + "Used deprecated key `input_modifiers` to define item." + " Rather use `object_type` as dictionary with modifiers." + )) + self.item_schema.update(input_modifiers) + + def set_value(self, value): + # TODO cleanup previous keys and values + pass + + def set_override_state(self, state): + for child_obj in self.children_by_key.values(): + child_obj.set_override_state(state) + + @property + def child_has_studio_override(self): + pass + + @property + def child_invalid(self): + pass + + @property + def child_value_modified(self): + pass + + @property + def child_overriden(self): + pass + + def discard_changes(self): + pass + + def get_invalid(self): + pass + + def settings_value(self): + pass + + def remove_overrides(self): + pass + + def reset_to_pype_default(self): + pass + + def set_as_overriden(self): + pass + + def set_studio_default(self): + pass + + def update_default_values(self, parent_values): + pass + + def update_studio_values(self, parent_values): + pass + + def update_project_values(self, parent_values): + pass + + +class ListEntity(ItemEntity): + schema_types = ["list"] + + def __iter__(self): + pass + + def append(self, item): + pass + + def extend(self, items): + pass + + def clear(self): + pass + + def pop(self, idx): + pass + + def remove(self, item): + pass + + def insert(self, idx, item): + pass + + def reverse(self): + pass + + def sort(self): + pass + + def _add_children(self, schema_data, first=True): + added_children = [] + for children_schema in schema_data["children"]: + if children_schema["type"] in WRAPPER_TYPES: + _children_schema = copy.deepcopy(children_schema) + wrapper_children = self._add_children( + children_schema["children"] + ) + _children_schema["children"] = wrapper_children + added_children.append(_children_schema) + continue + + child_obj = self.create_schema_object(children_schema, self, True) + self.children.append(child_obj) + added_children.append(child_obj) + if isinstance(child_obj, GUIEntity): + continue + + if child_obj.key in self.non_gui_children: + raise KeyError("Duplicated key \"{}\"".format(child_obj.key)) + self.non_gui_children[child_obj.key] = child_obj + + if not first: + return added_children + + for child_obj in added_children: + if isinstance(child_obj, BaseEntity): + continue + self.gui_wrappers.append(child_obj) + + def item_initalization(self): + # Children are stored by key as keys are immutable and are defined by + # schema + self.valid_value_types = (list, ) + self.children = [] + + item_schema = self.schema_data["object_type"] + if not isinstance(item_schema, dict): + item_schema = {"type": item_schema} + self.item_schema = item_schema + + # GUI attributes + self.use_label_wrap = self.schema_data.get("use_label_wrap") or False + # Used only if `use_label_wrap` is set to True + self.collapsible = self.schema_data.get("collapsible") or True + self.collapsed = self.schema_data.get("collapsed") or False + + def set_value(self, value): + pass + + def set_override_state(self, state): + for child_obj in self.non_gui_children: + child_obj.set_override_state(state) + + @property + def child_has_studio_override(self): + pass + + @property + def child_invalid(self): + pass + + @property + def child_value_modified(self): + pass + + @property + def child_overriden(self): + pass + + def discard_changes(self): + pass + + def get_invalid(self): + pass + + def settings_value(self): + pass + + def remove_overrides(self): + pass + + def reset_to_pype_default(self): + pass + + def set_as_overriden(self): + pass + + def set_studio_default(self): + pass + + def update_default_values(self, parent_values): + pass + + def update_studio_values(self, parent_values): + pass + + def update_project_values(self, parent_values): + pass + + +class InputEntity(ItemEntity): + def __eq__(self, other): + if isinstance(other, ItemEntity): + return self.current_value == other.current_value + return self.current_value == other + + def update_default_values(self, value): + self.default_values = value + + def update_project_values(self, value): + self.studio_override_value = value + + def update_studio_values(self, value): + self.project_override_value = value + + @property + def child_has_studio_override(self): + return self.has_studio_override + + @property + def child_invalid(self): + return self.is_invalid + + @property + def child_value_modified(self): + return self.value_is_modified + + @property + def child_overriden(self): + return self.is_overriden + + def settings_value(self): + return self.current_value + + def get_invalid(self): + if self.is_invalid: + return [self] + + def remove_overrides(self): + current_value = self.default_value + if self.override_state is OverrideState.STUDIO: + self.has_studio_override = False + + elif self.override_state is OverrideState.PROJECT: + self.has_project_override = False + if self.studio_override_value is not NOT_SET: + current_value = self.studio_override_value + + self.current_value = current_value + + def reset_to_pype_default(self): + if self.override_state is OverrideState.PROJECT: + raise ValueError( + "Can't reset to Pype defaults on project overrides state." + ) + self.has_studio_override = False + self.set_value(self.default_value) + + def set_as_overriden(self): + self.is_overriden = True + + def set_studio_default(self): + self.set_value(self.studio_override_value) + + def discard_changes(self): + self.has_studio_override = self.had_studio_override + self.has_project_override = self.had_project_override + + +class GUIEntity(ItemEntity): + gui_type = True + schema_types = ["divider", "splitter", "label"] + child_has_studio_override = False + child_invalid = False + child_value_modified = False + child_overriden = False + + def item_initalization(self): + self.valid_value_types = tuple() + self.require_key = False + + def set_value(self, value): + pass + + def set_override_state(self, state): + pass + + def discard_changes(self): + pass + + def get_invalid(self): + pass + + def settings_value(self): + pass + + def remove_overrides(self): + pass + + def reset_to_pype_default(self): + pass + + def set_as_overriden(self): + pass + + def set_studio_default(self): + pass + + def update_default_values(self, parent_values): + pass + + def update_studio_values(self, parent_values): + pass + + def update_project_values(self, parent_values): + pass + + +class TextEntity(InputEntity): + schema_types = ["text"] + + def item_initalization(self): + # Children are stored by key as keys are immutable and are defined by + # schema + self.valid_value_types = (str, ) + + def set_value(self, value): + self.current_value = value + + +class PathEntity(InputEntity): + schema_types = ["path-widget"] + platforms = ("windows", "darwin", "linux") + + def item_initalization(self): + # Children are stored by key as keys are immutable and are defined by + # schema + self.multiplatform = self.schema_data.get("multiplatform", False) + self.multipath = self.schema_data.get("multipath", False) + self.with_arguments = self.schema_data.get("with_arguments", False) + if self.multiplatform: + valid_value_types = (dict, ) + elif self.multipath: + valid_value_types = (list, ) + else: + valid_value_types = (str, ) + + self.valid_value_types = valid_value_types + + def set_value(self, value): + if value == self.current_value: + return + + self.current_value = value + self.on_value_change() + + def on_value_change(self): + if self.override_state is OverrideState.STUDIO: + self.value_is_modified = ( + self.current_value != self.studio_override_value + ) + elif self.override_state is OverrideState.PROJECT: + self.value_is_modified = ( + self.current_value != self.project_override_value + ) + + +class RawJsonEntity(InputEntity): + schema_types = ["raw-json"] + + def item_initalization(self): + # Children are stored by key as keys are immutable and are defined by + # schema + self.valid_value_types = (list, dict) + + def set_value(self, value): + self.current_value = value + + +class NumberEntity(InputEntity): + schema_types = ["number"] + + def item_initalization(self): + # Children are stored by key as keys are immutable and are defined by + # schema + self.valid_value_types = (int, float) + + def set_value(self, value): + self.current_value = value + + +class BoolEntity(InputEntity): + schema_types = ["boolean"] + + def item_initalization(self): + # Children are stored by key as keys are immutable and are defined by + # schema + self.valid_value_types = (bool, ) + + def set_value(self, value): + self.current_value = value + + +class EnumEntity(InputEntity): + schema_types = ["enum"] + + def item_initalization(self): + # Children are stored by key as keys are immutable and are defined by + # schema + self.multiselection = self.schema_data.get("multiselection", False) + self.enum_items = self.schema_data["enum_items"] + if not self.enum_items: + raise ValueError("Attribute `enum_items` is not defined.") + + valid_value_types = set() + for item in self.enum_items: + valid_value_types.add(type(item)) + + self.valid_value_types = tuple(valid_value_types) + + def set_value(self, value): + if self.multiselection: + if not isinstance(value, list): + if isinstance(value, (set, tuple)): + value = list(value) + else: + value = [value] + check_values = value + else: + check_values = [value] + + for item in check_values: + if item not in self.enum_items: + raise ValueError( + "Invalid value \"{}\". Expected: {}".format( + item, self.enum_items + ) + ) + self.current_value = value + + +if __name__ == "__main__": + from lib import gui_schema + schema_data = gui_schema("system_schema", "schema_main") + root = RootEntity(schema_data) + a = root["general"]["studio_name"] + print(a.current_value) diff --git a/pype/settings/entities/lib.py b/pype/settings/entities/lib.py new file mode 100644 index 0000000000..34e787c86d --- /dev/null +++ b/pype/settings/entities/lib.py @@ -0,0 +1,599 @@ +import os +import re +import json +import copy +from constants import ( + M_OVERRIDEN_KEY, + M_ENVIRONMENT_KEY, + M_DYNAMIC_KEY_LABEL +) +from queue import Queue + + +# Singleton database of available inputs +class TypeToKlass: + types = {} + + +NOT_SET = type("NOT_SET", (), {"__bool__": lambda obj: False})() +METADATA_KEY = type("METADATA_KEY", (), {})() +OVERRIDE_VERSION = 1 +CHILD_OFFSET = 15 + +key_pattern = re.compile(r"(\{.*?[^{0]*\})") + + +def convert_gui_data_with_metadata(data, ignored_keys=None): + if not data or not isinstance(data, dict): + return data + + if ignored_keys is None: + ignored_keys = tuple() + + output = {} + if METADATA_KEY in data: + metadata = data.pop(METADATA_KEY) + for key, value in metadata.items(): + if key in ignored_keys or key == "groups": + continue + + if key == "environments": + output[M_ENVIRONMENT_KEY] = value + elif key == "dynamic_key_label": + output[M_DYNAMIC_KEY_LABEL] = value + else: + raise KeyError("Unknown metadata key \"{}\"".format(key)) + + for key, value in data.items(): + output[key] = convert_gui_data_with_metadata(value, ignored_keys) + return output + + +def convert_data_to_gui_data(data, first=True): + if not data or not isinstance(data, dict): + return data + + output = {} + if M_ENVIRONMENT_KEY in data: + data.pop(M_ENVIRONMENT_KEY) + + if M_DYNAMIC_KEY_LABEL in data: + if METADATA_KEY not in data: + data[METADATA_KEY] = {} + data[METADATA_KEY]["dynamic_key_label"] = data.pop(M_DYNAMIC_KEY_LABEL) + + for key, value in data.items(): + output[key] = convert_data_to_gui_data(value, False) + + return output + + +def convert_gui_data_to_overrides(data, first=True): + if not data or not isinstance(data, dict): + return data + + output = {} + if first: + output["__override_version__"] = OVERRIDE_VERSION + data = convert_gui_data_with_metadata(data) + + if METADATA_KEY in data: + metadata = data.pop(METADATA_KEY) + for key, value in metadata.items(): + if key == "groups": + output[M_OVERRIDEN_KEY] = value + else: + raise KeyError("Unknown metadata key \"{}\"".format(key)) + + for key, value in data.items(): + output[key] = convert_gui_data_to_overrides(value, False) + return output + + +def convert_overrides_to_gui_data(data, first=True): + if not data or not isinstance(data, dict): + return data + + if first: + data = convert_data_to_gui_data(data) + + output = {} + if M_OVERRIDEN_KEY in data: + groups = data.pop(M_OVERRIDEN_KEY) + if METADATA_KEY not in output: + output[METADATA_KEY] = {} + output[METADATA_KEY]["groups"] = groups + + for key, value in data.items(): + output[key] = convert_overrides_to_gui_data(value, False) + + return output + + +def _fill_schema_template_data( + template, template_data, required_keys=None, missing_keys=None +): + first = False + if required_keys is None: + first = True + required_keys = set() + missing_keys = set() + + _template = [] + default_values = {} + for item in template: + if isinstance(item, dict) and "__default_values__" in item: + default_values = item["__default_values__"] + else: + _template.append(item) + template = _template + + for key, value in default_values.items(): + if key not in template_data: + template_data[key] = value + + if not template: + output = template + + elif isinstance(template, list): + output = [] + for item in template: + output.append(_fill_schema_template_data( + item, template_data, required_keys, missing_keys + )) + + elif isinstance(template, dict): + output = {} + for key, value in template.items(): + output[key] = _fill_schema_template_data( + value, template_data, required_keys, missing_keys + ) + + elif isinstance(template, str): + # TODO find much better way how to handle filling template data + for replacement_string in key_pattern.findall(template): + key = str(replacement_string[1:-1]) + required_keys.add(key) + if key not in template_data: + missing_keys.add(key) + continue + + value = template_data[key] + if replacement_string == template: + # Replace the value with value from templates data + # - with this is possible to set value with different type + template = value + else: + # Only replace the key in string + template = template.replace(replacement_string, value) + output = template + + else: + output = template + + if first and missing_keys: + raise SchemaTemplateMissingKeys(missing_keys, required_keys) + + return output + + +def _fill_schema_template(child_data, schema_collection, schema_templates): + template_name = child_data["name"] + template = schema_templates.get(template_name) + if template is None: + if template_name in schema_collection: + raise KeyError(( + "Schema \"{}\" is used as `schema_template`" + ).format(template_name)) + raise KeyError("Schema template \"{}\" was not found".format( + template_name + )) + + # Default value must be dictionary (NOT list) + # - empty list would not add any item if `template_data` are not filled + template_data = child_data.get("template_data") or {} + if isinstance(template_data, dict): + template_data = [template_data] + + output = [] + for single_template_data in template_data: + try: + filled_child = _fill_schema_template_data( + template, single_template_data + ) + + except SchemaTemplateMissingKeys as exc: + raise SchemaTemplateMissingKeys( + exc.missing_keys, exc.required_keys, template_name + ) + + for item in filled_child: + filled_item = _fill_inner_schemas( + item, schema_collection, schema_templates + ) + if filled_item["type"] == "schema_template": + output.extend(_fill_schema_template( + filled_item, schema_collection, schema_templates + )) + else: + output.append(filled_item) + return output + + +def _fill_inner_schemas(schema_data, schema_collection, schema_templates): + if schema_data["type"] == "schema": + raise ValueError("First item in schema data can't be schema.") + + children_key = "children" + object_type_key = "object_type" + for item_key in (children_key, object_type_key): + children = schema_data.get(item_key) + if not children: + continue + + if object_type_key == item_key: + if not isinstance(children, dict): + continue + children = [children] + + new_children = [] + for child in children: + child_type = child["type"] + if child_type == "schema": + schema_name = child["name"] + if schema_name not in schema_collection: + if schema_name in schema_templates: + raise KeyError(( + "Schema template \"{}\" is used as `schema`" + ).format(schema_name)) + raise KeyError( + "Schema \"{}\" was not found".format(schema_name) + ) + + filled_child = _fill_inner_schemas( + schema_collection[schema_name], + schema_collection, + schema_templates + ) + + elif child_type == "schema_template": + for filled_child in _fill_schema_template( + child, schema_collection, schema_templates + ): + new_children.append(filled_child) + continue + + else: + filled_child = _fill_inner_schemas( + child, schema_collection, schema_templates + ) + + new_children.append(filled_child) + + if item_key == object_type_key: + if len(new_children) != 1: + raise KeyError(( + "Failed to fill object type with type: {} | name {}" + ).format( + child_type, str(child.get("name")) + )) + new_children = new_children[0] + + schema_data[item_key] = new_children + return schema_data + + +class SchemaTemplateMissingKeys(Exception): + def __init__(self, missing_keys, required_keys, template_name=None): + self.missing_keys = missing_keys + self.required_keys = required_keys + if template_name: + msg = f"Schema template \"{template_name}\" require more keys.\n" + else: + msg = "" + msg += "Required keys: {}\nMissing keys: {}".format( + self.join_keys(required_keys), + self.join_keys(missing_keys) + ) + super(SchemaTemplateMissingKeys, self).__init__(msg) + + def join_keys(self, keys): + return ", ".join([ + f"\"{key}\"" for key in keys + ]) + + +class SchemaMissingFileInfo(Exception): + def __init__(self, invalid): + full_path_keys = [] + for item in invalid: + full_path_keys.append("\"{}\"".format("/".join(item))) + + msg = ( + "Schema has missing definition of output file (\"is_file\" key)" + " for keys. [{}]" + ).format(", ".join(full_path_keys)) + super(SchemaMissingFileInfo, self).__init__(msg) + + +class SchemeGroupHierarchyBug(Exception): + def __init__(self, invalid): + full_path_keys = [] + for item in invalid: + full_path_keys.append("\"{}\"".format("/".join(item))) + + msg = ( + "Items with attribute \"is_group\" can't have another item with" + " \"is_group\" attribute as child. Error happened for keys: [{}]" + ).format(", ".join(full_path_keys)) + super(SchemeGroupHierarchyBug, self).__init__(msg) + + +class SchemaDuplicatedKeys(Exception): + def __init__(self, invalid): + items = [] + for key_path, keys in invalid.items(): + joined_keys = ", ".join([ + "\"{}\"".format(key) for key in keys + ]) + items.append("\"{}\" ({})".format(key_path, joined_keys)) + + msg = ( + "Schema items contain duplicated keys in one hierarchy level. {}" + ).format(" || ".join(items)) + super(SchemaDuplicatedKeys, self).__init__(msg) + + +class SchemaDuplicatedEnvGroupKeys(Exception): + def __init__(self, invalid): + items = [] + for key_path, keys in invalid.items(): + joined_keys = ", ".join([ + "\"{}\"".format(key) for key in keys + ]) + items.append("\"{}\" ({})".format(key_path, joined_keys)) + + msg = ( + "Schema items contain duplicated environment group keys. {}" + ).format(" || ".join(items)) + super(SchemaDuplicatedEnvGroupKeys, self).__init__(msg) + + +def file_keys_from_schema(schema_data): + output = [] + item_type = schema_data["type"] + klass = TypeToKlass.types[item_type] + if not klass.is_input_type: + return output + + keys = [] + key = schema_data.get("key") + if key: + keys.append(key) + + for child in schema_data["children"]: + if child.get("is_file"): + _keys = copy.deepcopy(keys) + _keys.append(child["key"]) + output.append(_keys) + continue + + for result in file_keys_from_schema(child): + _keys = copy.deepcopy(keys) + _keys.extend(result) + output.append(_keys) + return output + + +def validate_all_has_ending_file(schema_data, is_top=True): + item_type = schema_data["type"] + klass = TypeToKlass.types[item_type] + if not klass.is_input_type: + return None + + if schema_data.get("is_file"): + return None + + children = schema_data.get("children") + if not children: + return [[schema_data["key"]]] + + invalid = [] + keyless = "key" not in schema_data + for child in children: + result = validate_all_has_ending_file(child, False) + if result is None: + continue + + if keyless: + invalid.extend(result) + else: + for item in result: + new_invalid = [schema_data["key"]] + new_invalid.extend(item) + invalid.append(new_invalid) + + if not invalid: + return None + + if not is_top: + return invalid + + raise SchemaMissingFileInfo(invalid) + + +def validate_is_group_is_unique_in_hierarchy( + schema_data, any_parent_is_group=False, keys=None +): + is_top = keys is None + if keys is None: + keys = [] + + keyless = "key" not in schema_data + + if not keyless: + keys.append(schema_data["key"]) + + invalid = [] + is_group = schema_data.get("is_group") + if is_group and any_parent_is_group: + invalid.append(copy.deepcopy(keys)) + + if is_group: + any_parent_is_group = is_group + + children = schema_data.get("children") + if not children: + return invalid + + for child in children: + result = validate_is_group_is_unique_in_hierarchy( + child, any_parent_is_group, copy.deepcopy(keys) + ) + if not result: + continue + + invalid.extend(result) + + if invalid and is_group and keys not in invalid: + invalid.append(copy.deepcopy(keys)) + + if not is_top: + return invalid + + if invalid: + raise SchemeGroupHierarchyBug(invalid) + + +def validate_keys_are_unique(schema_data, keys=None): + children = schema_data.get("children") + if not children: + return + + is_top = keys is None + if keys is None: + keys = [schema_data["key"]] + else: + keys.append(schema_data["key"]) + + child_queue = Queue() + for child in children: + child_queue.put(child) + + child_inputs = [] + while not child_queue.empty(): + child = child_queue.get() + if "key" not in child: + _children = child.get("children") or [] + for _child in _children: + child_queue.put(_child) + else: + child_inputs.append(child) + + duplicated_keys = set() + child_keys = set() + for child in child_inputs: + key = child["key"] + if key in child_keys: + duplicated_keys.add(key) + else: + child_keys.add(key) + + invalid = {} + if duplicated_keys: + joined_keys = "/".join(keys) + invalid[joined_keys] = duplicated_keys + + for child in child_inputs: + result = validate_keys_are_unique(child, copy.deepcopy(keys)) + if result: + invalid.update(result) + + if not is_top: + return invalid + + if invalid: + raise SchemaDuplicatedKeys(invalid) + + +def validate_environment_groups_uniquenes( + schema_data, env_groups=None, keys=None +): + is_first = False + if env_groups is None: + is_first = True + env_groups = {} + keys = [] + + my_keys = copy.deepcopy(keys) + key = schema_data.get("key") + if key: + my_keys.append(key) + + env_group_key = schema_data.get("env_group_key") + if env_group_key: + if env_group_key not in env_groups: + env_groups[env_group_key] = [] + env_groups[env_group_key].append("/".join(my_keys)) + + children = schema_data.get("children") + if not children: + return + + for child in children: + validate_environment_groups_uniquenes( + child, env_groups, copy.deepcopy(my_keys) + ) + + if is_first: + invalid = {} + for env_group_key, key_paths in env_groups.items(): + if len(key_paths) > 1: + invalid[env_group_key] = key_paths + + if invalid: + raise SchemaDuplicatedEnvGroupKeys(invalid) + + +def validate_schema(schema_data): + return + # validate_all_has_ending_file(schema_data) + # validate_is_group_is_unique_in_hierarchy(schema_data) + # validate_keys_are_unique(schema_data) + # validate_environment_groups_uniquenes(schema_data) + + +def gui_schema(subfolder, main_schema_name): + dirpath = os.path.join( + os.path.dirname(__file__), + "schemas", + subfolder + ) + loaded_schemas = {} + loaded_schema_templates = {} + for root, _, filenames in os.walk(dirpath): + for filename in filenames: + basename, ext = os.path.splitext(filename) + if ext != ".json": + continue + + filepath = os.path.join(root, filename) + with open(filepath, "r") as json_stream: + try: + schema_data = json.load(json_stream) + except Exception as exc: + raise Exception(( + f"Unable to parse JSON file {filepath}\n{exc}" + )) from exc + if isinstance(schema_data, list): + loaded_schema_templates[basename] = schema_data + else: + loaded_schemas[basename] = schema_data + + main_schema = _fill_inner_schemas( + loaded_schemas[main_schema_name], + loaded_schemas, + loaded_schema_templates + ) + validate_schema(main_schema) + return main_schema diff --git a/pype/settings/entities/schemas/projects_schema/schema_main.json b/pype/settings/entities/schemas/projects_schema/schema_main.json new file mode 100644 index 0000000000..73266a9e79 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_main.json @@ -0,0 +1,76 @@ +{ + "key": "project", + "type": "dict", + "children": [ + { + "type": "anatomy", + "key": "project_anatomy", + "children": [ + { + "type": "anatomy_roots", + "key": "roots", + "label": "Roots", + "is_file": true + }, + { + "type": "schema", + "name": "schema_anatomy_templates" + }, + { + "type": "schema", + "name": "schema_anatomy_attributes" + }, + { + "type": "schema", + "name": "schema_anatomy_imageio" + } + ] + }, + { + "type": "dict", + "key": "project_settings", + "children": [ + { + "type": "schema", + "name": "schema_project_global" + }, + { + "type": "schema", + "name": "schema_project_ftrack" + }, + { + "type": "schema", + "name": "schema_project_maya" + }, + { + "type": "schema", + "name": "schema_project_nuke" + }, + { + "type": "schema", + "name": "schema_project_hiero" + }, + { + "type": "schema", + "name": "schema_project_harmony" + }, + { + "type": "schema", + "name": "schema_project_celaction" + }, + { + "type": "schema", + "name": "schema_project_resolve" + }, + { + "type": "schema", + "name": "schema_project_standalonepublisher" + }, + { + "type": "schema", + "name": "schema_project_unreal" + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schema_plugins.json b/pype/settings/entities/schemas/projects_schema/schema_plugins.json new file mode 100644 index 0000000000..ce90ceb881 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_plugins.json @@ -0,0 +1,54 @@ +{ + "type": "dict", + "collapsable": true, + "key": "plugins", + "label": "Plugins", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "standalonepublisher", + "label": "Standalone Publisher", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "ExtractThumbnailSP", + "label": "ExtractThumbnailSP", + "is_group": true, + "children": [ + { + "type": "dict", + "collapsable": false, + "key": "ffmpeg_args", + "label": "ffmpeg_args", + "children": [ + { + "type": "list", + "object_type": "text", + "key": "input", + "label": "input" + }, + { + "type": "list", + "object_type": "text", + "key": "output", + "label": "output" + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_celaction.json b/pype/settings/entities/schemas/projects_schema/schema_project_celaction.json new file mode 100644 index 0000000000..86d6141c4a --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_project_celaction.json @@ -0,0 +1,62 @@ +{ + "type": "dict", + "collapsable": true, + "key": "celaction", + "label": "CelAction", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractCelactionDeadline", + "label": "ExtractCelactionDeadline", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "deadline_department", + "label": "Deadline apartment" + }, + { + "type": "number", + "key": "deadline_priority", + "label": "Deadline priority" + }, + { + "type": "text", + "key": "deadline_pool", + "label": "Deadline pool" + }, + { + "type": "text", + "key": "deadline_pool_secondary", + "label": "Deadline pool (secondary)" + }, + { + "type": "text", + "key": "deadline_group", + "label": "Deadline Group" + }, + { + "type": "number", + "key": "deadline_chunk_size", + "label": "Deadline Chunk size" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/pype/settings/entities/schemas/projects_schema/schema_project_ftrack.json new file mode 100644 index 0000000000..cbff26e135 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -0,0 +1,644 @@ +{ + "type": "dict", + "key": "ftrack", + "label": "Ftrack", + "collapsable": true, + "checkbox_key": "enabled", + "is_file": true, + "children": [ + { + "type": "splitter" + }, + { + "type": "label", + "label": "Additional Ftrack paths" + }, + { + "type": "list", + "key": "ftrack_actions_path", + "label": "Action paths", + "object_type": "text" + }, + { + "type": "list", + "key": "ftrack_events_path", + "label": "Event paths", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "type": "dict", + "key": "events", + "label": "Server Actions/Events", + "children": [ + { + "type": "dict", + "key": "sync_to_avalon", + "label": "Sync to avalon", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Allow name and hierarchy change only if following statuses are on all children tasks" + }, + { + "type": "list", + "key": "statuses_name_change", + "label": "Statuses", + "object_type": { + "type": "text", + "multiline": false + } + } + ] + }, + { + "type": "dict", + "key": "sync_hier_entity_attributes", + "label": "Sync Hierarchical and Entity Attributes", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "interest_entity_types", + "label": "Entity types of interest", + "object_type": { + "type": "text", + "multiline": false + } + }, + { + "type": "list", + "key": "interest_attributes", + "label": "Attributes to sync", + "object_type": { + "type": "text", + "multiline": false + } + }, + { + "type": "separator" + }, + { + "type": "boolean", + "key": "action_enabled", + "label": "Enable Action" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles for action", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "thumbnail_updates", + "label": "Update Hierarchy thumbnails", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Push thumbnail from version, up through multiple hierarchy levels." + }, + { + "type": "number", + "key": "levels", + "label": "Levels" + } + ] + }, + { + "type": "dict", + "key": "user_assignment", + "label": "Run script on user assignments", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, + { + "type": "dict", + "key": "status_update", + "label": "Update status on task action", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "mapping", + "type": "dict-modifiable", + "object_type": { + "type": "list", + "object_type": "text" + } + } + ] + }, + { + "type": "dict", + "key": "status_task_to_parent", + "label": "Sync status from Task to Parent", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "List of parent object types where this is triggered (\"Shot\", \"Asset Build\", etc.). Skipped if list is empty." + }, + { + "type": "list", + "object_type": "text", + "key": "parent_object_types", + "label": "Object types" + }, + { + "key": "parent_status_match_all_task_statuses", + "type": "dict-modifiable", + "label": "Change parent if all tasks match", + "object_type": { + "type": "list", + "object_type": "text" + } + }, + { + "type": "list", + "key": "parent_status_by_task_status", + "label": "Change parent status if a single task matches", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "label": "New parent status", + "key": "new_status" + }, + { + "type": "separator" + }, + { + "type": "list", + "label": "Task status", + "key": "task_statuses", + "object_type": "text" + } + ] + } + } + ] + }, + { + "type": "dict", + "key": "status_task_to_version", + "label": "Sync status from Task to Version", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict-modifiable", + "key": "mapping", + "object_type": + { + "type": "list", + "object_type": "text" + } + }, + { + "type": "label", + "label": "Limit status changes to entered asset types. Limitation is ignored if nothing is entered." + }, + { + "type": "list", + "key": "asset_types_filter", + "label": "Asset types (short)", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "status_version_to_task", + "label": "Sync status from Version to Task", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Change Task status based on a changed Version status.
Version's new status on the left will trigger a change of a task status to the first available from the list on right.
- if no status from the list is available it will use the same status as the version." + }, + { + "type": "dict-modifiable", + "key": "mapping", + "object_type": { + "type": "list", + "object_type": "text" + } + }, + { + "type": "separator" + }, + { + "type": "label", + "label": "Disable event if status was changed on specific Asset type." + }, + { + "type": "list", + "label": "Asset types (short)", + "key": "asset_types_to_skip", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "first_version_status", + "label": "Set status on first created version", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "status", + "label": "Status" + } + ] + }, + { + "type": "dict", + "key": "next_task_update", + "label": "Update status on next task", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Change status on next task by task types order when task status state changed to \"Done\". All tasks with same Task type must be \"Done\"." + }, + { + "type": "label", + "label": "Mapping of next task status changes From -> To." + }, + { + "type": "dict-modifiable", + "key": "mapping", + "object_type": { + "type": "text" + } + }, + { + "type": "separator" + }, + { + "type": "label", + "label": "Status names that are ignored on \"Done\" check (e.g. \"Omitted\")." + }, + { + "type": "list", + "key": "ignored_statuses", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "type": "label", + "label": "Allow to break rule that all tasks with same Task type must be \"Done\" and change statuses with same type tasks ordered by name." + }, + { + "label": "Name sorting", + "type": "boolean", + "key": "name_sorting" + } + ] + } + ] + }, + { + "type": "dict", + "key": "user_handlers", + "label": "User Actions/Events", + "children": [ + { + "type": "dict", + "key": "application_launch_statuses", + "label": "Application - Status change on launch", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Do not change status if current status is:" + }, + { + "type": "list", + "key": "ignored_statuses", + "object_type": "text" + }, + { + "type": "label", + "label": "Change task's status to left side if current task status is in list on right side." + }, + { + "type": "dict-modifiable", + "key": "status_change", + "object_type": { + "type": "list", + "object_type": "text" + } + } + ] + }, + { + "type": "dict", + "key": "create_update_attributes", + "label": "Create/Update Avalon Attributes", + "children": [ + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "prepare_project", + "label": "Prepare Project", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "clean_hierarchical_attr", + "label": "Clean hierarchical custom attributes", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "delete_asset_subset", + "label": "Delete Asset/Subsets", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "delete_old_versions", + "label": "Delete old versions", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "delivery_action", + "label": "Delivery", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "store_thubmnail_to_avalon", + "label": "Store Thumbnails to avalon", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "job_killer", + "label": "Job Killer", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "sync_to_avalon_local", + "label": "Sync to avalon (local) - For development", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "seed_project", + "label": "Seed Debug Project", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text" + } + ] + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "IntegrateFtrackNote", + "label": "IntegrateFtrackNote", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "note_with_intent_template", + "label": "Note with intent template" + }, + { + "type": "list", + "object_type": "text", + "key": "note_labels", + "label": "Note labels" + } + ] + }, + + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ValidateFtrackAttributes", + "label": "ValidateFtrackAttributes", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "raw-json", + "key": "ftrack_custom_attributes", + "label": "Custom attributes to validate" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_global.json b/pype/settings/entities/schemas/projects_schema/schema_project_global.json new file mode 100644 index 0000000000..ab9b56115d --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_project_global.json @@ -0,0 +1,33 @@ +{ + "type": "dict", + "collapsable": true, + "key": "global", + "label": "Global", + "is_file": true, + "children": [ + { + "type": "schema", + "name": "schema_global_publish" + }, + { + "type": "schema", + "name": "schema_global_tools" + }, + + { + "type": "collapsible-wrap", + "label": "Project Folder Structure", + "children": [ + { + "type": "raw-json", + "key": "project_folder_structure" + } + ] + }, + + { + "type": "schema", + "name": "schema_project_syncserver" + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/pype/settings/entities/schemas/projects_schema/schema_project_harmony.json new file mode 100644 index 0000000000..791a08cb8d --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_project_harmony.json @@ -0,0 +1,34 @@ +{ + "type": "dict", + "collapsable": true, + "key": "harmony", + "label": "Harmony", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "children": [] + }, + { + "type": "dict", + "collapsable": true, + "key": "general", + "label": "General", + "children": [ + { + "type": "boolean", + "key": "skip_resolution_check", + "label": "Skip Resolution Check" + }, + { + "type": "boolean", + "key": "skip_timelines_check", + "label": "Skip Timeliene Check" + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/pype/settings/entities/schemas/projects_schema/schema_project_hiero.json new file mode 100644 index 0000000000..e55e652cb6 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -0,0 +1,57 @@ +{ + "type": "dict", + "collapsable": true, + "key": "hiero", + "label": "Hiero", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "CollectInstanceVersion", + "label": "Collect Instance Version", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewCutUpVideo", + "label": "Extract Review Cut Up Video", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "object_type": "text", + "key": "tags_addition", + "label": "Tags addition" + } + ] + } + ] + }, + { + "type": "schema", + "name": "schema_publish_gui_filter" + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_maya.json b/pype/settings/entities/schemas/projects_schema/schema_project_maya.json new file mode 100644 index 0000000000..069ae3c977 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -0,0 +1,29 @@ +{ + "type": "dict", + "collapsable": true, + "key": "maya", + "label": "Maya", + "is_file": true, + "children": [ + { + "type": "schema", + "name": "schema_maya_capture" + }, + { + "type": "schema", + "name": "schema_maya_publish" + }, + { + "type": "schema", + "name": "schema_maya_load" + }, + { + "type": "schema", + "name": "schema_workfile_build" + }, + { + "type": "schema", + "name": "schema_publish_gui_filter" + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/pype/settings/entities/schemas/projects_schema/schema_project_nuke.json new file mode 100644 index 0000000000..9bec54fa36 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -0,0 +1,179 @@ +{ + "type": "dict", + "collapsable": true, + "key": "nuke", + "label": "Nuke", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "create", + "label": "Create plugins", + "children": [ + { + "type": "dict", + "collapsable": false, + "key": "CreateWriteRender", + "label": "CreateWriteRender", + "is_group": true, + "children": [ + { + "type": "text", + "key": "fpath_template", + "label": "Path template" + } + ] + }, + { + "type": "dict", + "collapsable": false, + "key": "CreateWritePrerender", + "label": "CreateWritePrerender", + "is_group": true, + "children": [ + { + "type": "text", + "key": "fpath_template", + "label": "Path template" + } + ] + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractThumbnail", + "label": "ExtractThumbnail", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "raw-json", + "key": "nodes", + "label": "Nodes" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ValidateKnobs", + "label": "ValidateKnobs", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "raw-json", + "key": "knobs", + "label": "Knobs" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewDataLut", + "label": "ExtractReviewDataLut", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewDataMov", + "label": "ExtractReviewDataMov", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "viewer_lut_raw", + "label": "Viewer LUT raw" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "ExtractSlateFrame", + "label": "ExtractSlateFrame", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "viewer_lut_raw", + "label": "Viewer LUT raw" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "NukeSubmitDeadline", + "label": "NukeSubmitDeadline", + "is_group": true, + "children": [ + { + "type": "number", + "key": "deadline_priority", + "label": "deadline_priority" + }, + { + "type": "text", + "key": "deadline_pool", + "label": "deadline_pool" + }, + { + "type": "text", + "key": "deadline_pool_secondary", + "label": "deadline_pool_secondary" + }, + { + "type": "number", + "key": "deadline_chunk_size", + "label": "deadline_chunk_size" + } + ] + } + ] + }, + { + "type": "schema", + "name": "schema_workfile_build" + }, + { + "type": "schema", + "name": "schema_publish_gui_filter" + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_resolve.json b/pype/settings/entities/schemas/projects_schema/schema_project_resolve.json new file mode 100644 index 0000000000..fb9b9b7a0a --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_project_resolve.json @@ -0,0 +1,124 @@ +{ + "type": "dict", + "collapsable": true, + "key": "resolve", + "label": "DaVinci Resolve", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "create", + "label": "Creator plugins", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "CreateShotClip", + "label": "Create Shot Clip", + "is_group": true, + "children": [ + { + "type": "collapsible-wrap", + "label": "Shot Hierarchy And Rename Settings", + "collapsable": false, + "children": [ + { + "type": "text", + "key": "hierarchy", + "label": "Shot parent hierarchy" + }, + { + "type": "boolean", + "key": "clipRename", + "label": "Rename clips" + }, + { + "type": "text", + "key": "clipName", + "label": "Clip name template" + }, + { + "type": "number", + "key": "countFrom", + "label": "Count sequence from" + }, + { + "type": "number", + "key": "countSteps", + "label": "Stepping number" + } + ] + }, + { + "type": "collapsible-wrap", + "label": "Shot Template Keywords", + "collapsable": false, + "children": [ + { + "type": "text", + "key": "folder", + "label": "{folder}" + }, + { + "type": "text", + "key": "episode", + "label": "{episode}" + }, + { + "type": "text", + "key": "sequence", + "label": "{sequence}" + }, + { + "type": "text", + "key": "track", + "label": "{track}" + }, + { + "type": "text", + "key": "shot", + "label": "{shot}" + } + ] + }, + { + "type": "collapsible-wrap", + "label": "Vertical Synchronization Of Attributes", + "collapsable": false, + "children": [ + { + "type": "boolean", + "key": "vSyncOn", + "label": "Enable Vertical Sync" + } + ] + }, + { + "type": "collapsible-wrap", + "label": "Shot Attributes", + "collapsable": false, + "children": [ + { + "type": "number", + "key": "workfileFrameStart", + "label": "Workfiles Start Frame" + }, + { + "type": "number", + "key": "handleStart", + "label": "Handle start (head)" + }, + { + "type": "number", + "key": "handleEnd", + "label": "Handle end (tail)" + } + ] + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json b/pype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json new file mode 100644 index 0000000000..40b27f766f --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json @@ -0,0 +1,93 @@ +{ + "type": "dict", + "collapsable": true, + "key": "standalonepublisher", + "label": "Standalone Publisher", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "ExtractThumbnailSP", + "label": "ExtractThumbnailSP", + "is_group": true, + "children": [ + { + "type": "dict", + "collapsable": false, + "key": "ffmpeg_args", + "label": "ffmpeg_args", + "children": [ + { + "type": "list", + "object_type": "text", + "key": "input", + "label": "input" + }, + { + "type": "list", + "object_type": "text", + "key": "output", + "label": "output" + } + ] + } + ] + } + ] + }, + { + "type": "dict-modifiable", + "collapsable": true, + "key": "create", + "label": "Creator plugins", + "collapsable_key": true, + "is_file": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "type": "text", + "key": "family", + "label": "Family" + }, + { + "type": "text", + "key": "icon", + "label": "Icon" + }, + { + "type": "list", + "key": "defaults", + "label": "Defaults", + "object_type": { + "type": "text" + } + }, + { + "type": "text", + "key": "help", + "label": "Help" + } + ] + } + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_syncserver.json b/pype/settings/entities/schemas/projects_schema/schema_project_syncserver.json new file mode 100644 index 0000000000..396e4ca2dc --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_project_syncserver.json @@ -0,0 +1,76 @@ +{ + "type": "dict", + "key": "sync_server", + "label": "Sync Server (currently unused)", + "collapsable": true, + "checkbox_key": "enabled", + "is_file": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict", + "key": "config", + "label": "Config", + "collapsable": true, + "children": [ + + { + "type": "text", + "key": "local_id", + "label": "Local ID" + }, + { + "type": "text", + "key": "retry_cnt", + "label": "Retry Count" + }, + { + "type": "text", + "key": "loop_delay", + "label": "Loop Delay" + }, + { + "type": "text", + "key": "active_site", + "label": "Active Site" + }, + { + "type": "text", + "key": "remote_site", + "label": "Remote Site" + } + ] + }, { + "type": "dict-modifiable", + "collapsable": true, + "key": "sites", + "label": "Sites", + "collapsable_key": false, + "is_file": true, + "object_type": + { + "type": "dict", + "children": [ + { + "type": "text", + "key": "provider", + "label": "Provider" + }, + { + "type": "text", + "key": "credentials_url", + "label": "Credentials url" + }, + { + "type": "text", + "key": "root", + "label": "Root" + }] + } + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/pype/settings/entities/schemas/projects_schema/schema_project_unreal.json new file mode 100644 index 0000000000..392289296a --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -0,0 +1,27 @@ +{ + "type": "dict", + "collapsable": true, + "key": "unreal", + "label": "Unreal Engine", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "project_setup", + "label": "Project Setup", + "children": [ + { + "type": "boolean", + "key": "dev_mode", + "label": "Dev mode" + }, + { + "type": "boolean", + "key": "install_unreal_python_engine", + "label": "Install unreal python engine" + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_attributes.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_attributes.json new file mode 100644 index 0000000000..b045ef978b --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_attributes.json @@ -0,0 +1,79 @@ +{ + "type": "dict", + "collapsable": true, + "key": "attributes", + "label": "Attributes", + "is_file": true, + "children": [ + { + "type": "number", + "key": "fps", + "label": "Frame Rate" + }, + { + "type": "number", + "key": "frameStart", + "label": "Frame Start" + }, + { + "type": "number", + "key": "frameEnd", + "label": "Frame End" + }, + { + "type": "number", + "key": "clipIn", + "label": "Clip In" + }, + { + "type": "number", + "key": "clipOut", + "label": "Clip Out" + }, + { + "type": "number", + "key": "handleStart", + "label": "Handle Start" + }, + { + "type": "number", + "key": "handleEnd", + "label": "Handle End" + }, + { + "type": "number", + "key": "resolutionWidth", + "label": "Resolution Width" + }, + { + "type": "number", + "key": "resolutionHeight", + "label": "Resolution Height" + }, + { + "type": "number", + "key": "pixelAspect", + "label": "Pixel Aspect Ratio" + }, + { + "type": "enum", + "key": "applications", + "label": "Applications", + "multiselection": true, + "enum_items": [ + { "maya_2020": "Maya 2020" }, + { "nuke_12.2": "Nuke 12.2" }, + { "hiero_12.2": "Hiero 12.2" }, + { "houdini_18": "Houdini 18" }, + { "blender_2.91": "Blender 2.91" }, + { "aftereffects_2021": "After Effects 2021" } + ] + }, + { + "type": "dict-modifiable", + "key": "task_short_names", + "label": "Task short names (by Task type)", + "object_type": "text" + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json new file mode 100644 index 0000000000..0032e3de06 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json @@ -0,0 +1,353 @@ +{ + "type": "dict", + "key": "imageio", + "label": "Color Management and Output Formats", + "is_file": true, + "children": [ + { + "key": "hiero", + "type": "dict", + "label": "Hiero", + "children": [ + { + "key": "workfile", + "type": "dict", + "label": "Workfile", + "collapsable": false, + "children": [ + { + "type": "form", + "children": [ + { + "type": "enum", + "key": "ocioConfigName", + "label": "OpenColorIO Config", + "enum_items": [ + { + "nuke-default": "nuke-default" + }, + { + "aces_1.0.3": "aces_1.0.3" + }, + { + "aces_1.1": "aces_1.1" + }, + { + "custom": "custom" + } + ] + }, + { + "type": "path-widget", + "key": "ocioconfigpath", + "label": "Custom OCIO path", + "multiplatform": true, + "multipath": true + }, + { + "type": "text", + "key": "workingSpace", + "label": "Working Space" + }, + { + "type": "text", + "key": "sixteenBitLut", + "label": "16 Bit Files" + }, + { + "type": "text", + "key": "eightBitLut", + "label": "8 Bit Files" + }, + { + "type": "text", + "key": "floatLut", + "label": "Floating Point Files" + }, + { + "type": "text", + "key": "logLut", + "label": "Log Files" + }, + { + "type": "text", + "key": "viewerLut", + "label": "Viewer" + }, + { + "type": "text", + "key": "thumbnailLut", + "label": "Thumbnails" + } + ] + } + ] + }, + { + "key": "regexInputs", + "type": "dict", + "label": "Colorspace on Inputs by regex detection", + "collapsable": true, + "children": [ + { + "type": "list", + "key": "inputs", + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "regex", + "label": "Regex" + }, + { + "type": "text", + "key": "colorspace", + "label": "Colorspace" + } + ] + } + } + ] + } + ] + }, + { + "key": "nuke", + "type": "dict", + "label": "Nuke", + "children": [ + { + "key": "workfile", + "type": "dict", + "label": "Workfile", + "collapsable": false, + "is_group": true, + "children": [ + { + "type": "form", + "children": [ + { + "type": "enum", + "key": "colorManagement", + "label": "color management", + "enum_items": [ + { + "Nuke": "Nuke" + }, + { + "OCIO": "OCIO" + } + ] + }, + { + "type": "enum", + "key": "OCIO_config", + "label": "OpenColorIO Config", + "enum_items": [ + { + "nuke-default": "nuke-default" + }, + { + "spi-vfx": "spi-vfx" + }, + { + "spi-anim": "spi-anim" + }, + { + "aces_1.0.3": "aces_0.1.1" + }, + { + "aces_1.0.3": "aces_0.7.1" + }, + { + "aces_1.0.3": "aces_1.0.1" + }, + { + "aces_1.0.3": "aces_1.0.3" + }, + { + "aces_1.1": "aces_1.1" + }, + { + "custom": "custom" + } + ] + }, + { + "type": "path-widget", + "key": "customOCIOConfigPath", + "label": "Custom OCIO config path", + "multiplatform": true, + "multipath": true + }, + { + "type": "text", + "key": "workingSpaceLUT", + "label": "Working Space" + }, + { + "type": "text", + "key": "monitorLut", + "label": "monitor" + }, + { + "type": "text", + "key": "int8Lut", + "label": "8-bit files" + }, + { + "type": "text", + "key": "int16Lut", + "label": "16-bit files" + }, + { + "type": "text", + "key": "logLut", + "label": "log files" + }, + { + "type": "text", + "key": "floatLut", + "label": "float files" + } + ] + } + ] + }, + { + "key": "nodes", + "type": "dict", + "label": "Nodes", + "collapsable": true, + "is_group": true, + "children": [ + { + "key": "requiredNodes", + "type": "list", + "label": "Required Nodes", + "object_type": { + "type": "dict", + "children": [ + { + "type": "list", + "key": "plugins", + "label": "Used in plugins", + "object_type": { + "type": "text", + "key": "pluginClass", + "label": "Plugin Class" + } + }, + { + "type": "text", + "key": "nukeNodeClass", + "label": "Nuke Node Class" + }, + { + "type": "splitter" + }, + { + "key": "knobs", + "label": "Knobs", + "type": "list", + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "text", + "key": "value", + "label": "Value" + } + ] + } + } + ] + } + }, + { + "type": "list", + "key": "customNodes", + "label": "Custom Nodes", + "object_type": { + "type": "dict", + "children": [ + { + "type": "list", + "key": "plugins", + "label": "Used in plugins", + "object_type": { + "type": "text", + "key": "pluginClass", + "label": "Plugin Class" + } + }, + { + "type": "text", + "key": "nukeNodeClass", + "label": "Nuke Node Class" + }, + { + "type": "splitter" + }, + { + "key": "knobs", + "label": "Knobs", + "type": "list", + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "text", + "key": "value", + "label": "Value" + } + ] + } + } + ] + } + } + ] + }, + { + "key": "regexInputs", + "type": "dict", + "label": "Colorspace on Inputs by regex detection", + "collapsable": true, + "children": [ + { + "type": "list", + "key": "inputs", + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "regex", + "label": "Regex" + }, + { + "type": "text", + "key": "colorspace", + "label": "Colorspace" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_templates.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_templates.json new file mode 100644 index 0000000000..1f545f14be --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_templates.json @@ -0,0 +1,135 @@ +{ + "type": "dict", + "collapsable": true, + "key": "templates", + "label": "Templates", + "collapsable_key": true, + "is_file": true, + "children": [ + { + "type": "number", + "key": "version_padding", + "label": "Version Padding" + }, + { + "type": "text", + "key": "version", + "label": "Version" + }, + { + "type": "number", + "key": "frame_padding", + "label": "Frame Padding" + }, + { + "type": "text", + "key": "frame", + "label": "Frame" + }, + { + "type": "dict", + "key": "work", + "label": "Work", + "children": [ + { + "type": "text", + "key": "folder", + "label": "Folder" + }, + { + "type": "text", + "key": "file", + "label": "File" + }, + { + "type": "text", + "key": "path", + "label": "Path" + } + ] + }, + { + "type": "dict", + "key": "render", + "label": "Render", + "children": [ + { + "type": "text", + "key": "folder", + "label": "Folder" + }, + { + "type": "text", + "key": "file", + "label": "File" + }, + { + "type": "text", + "key": "path", + "label": "Path" + } + ] + }, + { + "type": "dict", + "key": "publish", + "label": "Publish", + "children": [ + { + "type": "text", + "key": "folder", + "label": "Folder" + }, + { + "type": "text", + "key": "file", + "label": "File" + }, + { + "type": "text", + "key": "path", + "label": "Path" + }, + { + "type": "text", + "key": "thumbnail", + "label": "Thumbnail" + } + ] + }, + { + "type": "dict", + "key": "master", + "label": "Master", + "children": [ + { + "type": "text", + "key": "folder", + "label": "Folder" + }, + { + "type": "text", + "key": "file", + "label": "File" + }, + { + "type": "text", + "key": "path", + "label": "Path" + } + ] + }, + { + "type": "dict-modifiable", + "key": "delivery", + "label": "Delivery", + "object_type": "text" + }, + { + "type": "dict-modifiable", + "key": "other", + "label": "Other", + "object_type": "text" + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json new file mode 100644 index 0000000000..5d17830b92 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -0,0 +1,407 @@ +{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "IntegrateMasterVersion", + "label": "IntegrateMasterVersion", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractJpegEXR", + "label": "ExtractJpegEXR", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict", + "key": "ffmpeg_args", + "children": [ + { + "type": "list", + "object_type": "text", + "key": "input", + "label": "FFmpeg input arguments" + }, + { + "type": "list", + "object_type": "text", + "key": "output", + "label": "FFmpeg output arguments" + } + ] + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "ExtractReview", + "label": "ExtractReview", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "hosts", + "label": "Hosts", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "outputs", + "label": "Output Definitions", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "ext", + "label": "Output extension", + "type": "text" + }, + { + "key": "tags", + "label": "Tags", + "type": "enum", + "multiselection": true, + "enum_items": [ + { + "burnin": "Add burnins" + }, + { + "ftrackreview": "Add to Ftrack" + }, + { + "delete": "Delete output" + }, + { + "slate-frame": "Add slate frame" + }, + { + "no-hnadles": "Skip handle frames" + } + ] + }, + { + "key": "ffmpeg_args", + "label": "FFmpeg arguments", + "type": "dict", + "highlight_content": true, + "children": [ + { + "key": "video_filters", + "label": "Video filters", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "audio_filters", + "label": "Audio filters", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "input", + "label": "Input arguments", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "output", + "label": "Output arguments", + "type": "list", + "object_type": "text" + } + ] + }, + { + "key": "filter", + "label": "Additional output filtering", + "type": "dict", + "highlight_content": true, + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + } + ] + } + ] + } + } + ] + } + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "ExtractBurnin", + "label": "ExtractBurnin", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict", + "collapsable": true, + "key": "options", + "label": "Burnin formating options", + "children": [ + { + "type": "number", + "key": "font_size", + "label": "Font size" + }, + { + "type": "number", + "key": "opacity", + "label": "Font opacity" + }, + { + "type": "number", + "key": "bg_opacity", + "label": "Background opacity" + }, + { + "type": "number", + "key": "x_offset", + "label": "X Offset" + }, + { + "type": "number", + "key": "y_offset", + "label": "Y Offset" + }, + { + "type": "number", + "key": "bg_padding", + "label": "Padding aroung text" + }, + { + "type": "splitter" + } + ] + }, + + { + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "hosts", + "label": "Hosts", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "burnins", + "label": "Burnins", + "type": "dict-modifiable", + "highlight_content": true, + "collapsable": false, + "object_type": { + "type": "dict", + "children": [ + { + "key": "TOP_LEFT", + "label": "Top Left", + "type": "text" + }, + { + "key": "TOP_CENTERED", + "label": "Top Centered", + "type": "text" + }, + { + "key": "TOP_RIGHT", + "label": "top Right", + "type": "text" + }, + { + "key": "BOTTOM_LEFT", + "label": "Bottom Left", + "type": "text" + }, + { + "key": "BOTTOM_CENTERED", + "label": "Bottom Centered", + "type": "text" + }, + { + "key": "BOTTOM_RIGHT", + "label": "BottomRight", + "type": "text" + } + ] + } + } + ] + } + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "IntegrateAssetNew", + "label": "IntegrateAssetNew", + "is_group": true, + "children": [ + { + "type": "raw-json", + "key": "template_name_profiles", + "label": "template_name_profiles" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "ProcessSubmittedJobOnFarm", + "label": "ProcessSubmittedJobOnFarm", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "deadline_department", + "label": "Deadline department" + }, + { + "type": "text", + "key": "deadline_pool", + "label": "Deadline Pool" + }, + { + "type": "text", + "key": "deadline_group", + "label": "Deadline Group" + }, + { + "type": "number", + "key": "deadline_chunk_size", + "label": "Deadline Chunk Size" + }, + { + "type": "number", + "key": "deadline_priority", + "label": "Deadline Priotity" + }, + { + "type": "dict", + "key": "aov_filter", + "label": "Reviewable subsets filter", + "children": [ + { + "type": "list", + "key": "maya", + "label": "Maya", + "object_type": { + "type": "text" + } + }, + { + "type": "list", + "key": "nuke", + "label": "Nuke", + "object_type": { + "type": "text" + } + }, + { + "type": "list", + "key": "aftereffects", + "label": "After Effects", + "object_type": { + "type": "text" + } + }, + { + "type": "list", + "key": "celaction", + "label": "Celaction", + "object_type": { + "type": "text" + } + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json new file mode 100644 index 0000000000..d89477edd1 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -0,0 +1,77 @@ +{ + "type": "dict", + "collapsable": true, + "key": "tools", + "label": "Tools", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "creator", + "label": "Creator", + "children": [ + { + "type": "dict-modifiable", + "collapsable": false, + "key": "families_smart_select", + "label": "Families smart select", + "object_type": { + "type": "list", + "object_type": "text" + } + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "Workfiles", + "label": "Workfiles", + "children": [ + { + "type": "list", + "key": "last_workfile_on_startup", + "label": "Open last workfiles on launch", + "is_group": true, + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "hosts", + "label": "Hosts", + "type": "list", + "object_type": "text" + }, + { + "key": "tasks", + "label": "Tasks", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + } + }, + { + "type": "dict-modifiable", + "collapsable": true, + "key": "sw_folders", + "label": "Extra task folders", + "is_group": true, + "object_type": { + "type": "list", + "object_type": "text" + } + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json new file mode 100644 index 0000000000..ba7cf4b525 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -0,0 +1,595 @@ +{ + "type": "collapsible-wrap", + "label": "Collapsible Wrapper without key", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "capture", + "label": "Maya Playblast settings", + "is_file": true, + "children": [ + { + "type": "dict", + "key": "Codec", + "children": [ + { + "type": "label", + "label": "Codec" + }, + { + "type": "text", + "key": "compression", + "label": "Compression type" + }, + { + "type": "text", + "key": "format", + "label": "Data format" + }, + { + "type": "number", + "key": "quality", + "label": "Quality", + "decimal": 0, + "minimum": 0, + "maximum": 100 + }, + + { + "type": "splitter" + } + ] + }, + { + "type": "dict", + "key": "Display Options", + "children": [ + { + "type": "label", + "label": "Display Options" + }, + { + "type": "list-strict", + "key": "background", + "label": "Background Color: ", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + } + ] + }, + { + "type": "list-strict", + "key": "backgroundBottom", + "label": "Background Bottom: ", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + } + ] + }, + { + "type": "list-strict", + "key": "backgroundTop", + "label": "Background Top: ", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + } + ] + }, + { + "type": "boolean", + "key": "override_display", + "label": "Override display options" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict", + "key": "Generic", + "children": [ + { + "type": "label", + "label": "Generic" + }, + { + "type": "boolean", + "key": "isolate_view", + "label": " Isolate view" + }, + { + "type": "boolean", + "key": "off_screen", + "label": " Off Screen" + } + ] + }, + { + "type": "dict", + "key": "IO", + "children": [ + { + "type": "label", + "label": "IO" + }, + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "boolean", + "key": "open_finished", + "label": "Open finished" + }, + { + "type": "boolean", + "key": "raw_frame_numbers", + "label": "Raw frame numbers" + }, + { + "type": "list", + "key": "recent_playblasts", + "label": "Recent Playblasts", + "object_type": "text" + }, + { + "type": "boolean", + "key": "save_file", + "label": "Save file" + } + ] + }, + { + "type": "dict", + "key": "PanZoom", + "children": [ + { + "type": "boolean", + "key": "pan_zoom", + "label": " Pan Zoom" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict", + "key": "Renderer", + "children": [ + { + "type": "label", + "label": "Renderer" + }, + { + "type": "text", + "key": "rendererName", + "label": " Renderer name" + } + ] + }, + { + "type": "dict", + "key": "Resolution", + "children": [ + { + "type": "splitter" + }, + { + "type": "label", + "label": "Resolution" + }, + { + "type": "number", + "key": "width", + "label": " Width", + "decimal": 0, + "minimum": 0, + "maximum": 99999 + }, + { + "type": "number", + "key": "height", + "label": "Height", + "decimal": 0, + "minimum": 0, + "maximum": 99999 + }, + { + "type": "number", + "key": "percent", + "label": "percent", + "decimal": 1, + "minimum": 0, + "maximum": 200 + }, + { + "type": "text", + "key": "mode", + "label": "Mode" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict", + "key": "Time Range", + "children": [ + { + "type": "label", + "label": "Time Range" + }, + { + "type": "number", + "key": "start_frame", + "label": " Start frame", + "decimal": 0, + "minimum": 0, + "maximum": 999999 + }, + { + "type": "number", + "key": "end_frame", + "label": "End frame", + "decimal": 0, + "minimum": 0, + "maximum": 999999 + }, + { + "type": "text", + "key": "frame", + "label": "Frame" + }, + { + "type": "text", + "key": "time", + "label": "Time" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "Viewport Options", + "label": "Viewport Options", + "children": [ + { + "type": "boolean", + "key": "cameras", + "label": "cameras" + }, + { + "type": "boolean", + "key": "clipGhosts", + "label": "clipGhosts" + }, + { + "type": "boolean", + "key": "controlVertices", + "label": "controlVertices" + }, + { + "type": "boolean", + "key": "deformers", + "label": "deformers" + }, + { + "type": "boolean", + "key": "dimensions", + "label": "dimensions" + }, + { + "type": "number", + "key": "displayLights", + "label": "displayLights", + "decimal": 0, + "minimum": 0, + "maximum": 10 + }, + { + "type": "boolean", + "key": "dynamicConstraints", + "label": "dynamicConstraints" + }, + { + "type": "boolean", + "key": "dynamics", + "label": "dynamics" + }, + { + "type": "boolean", + "key": "fluids", + "label": "fluids" + }, + { + "type": "boolean", + "key": "follicles", + "label": "follicles" + }, + { + "type": "boolean", + "key": "gpuCacheDisplayFilter", + "label": "gpuCacheDisplayFilter" + }, + { + "type": "boolean", + "key": "greasePencils", + "label": "greasePencils" + }, + { + "type": "boolean", + "key": "grid", + "label": "grid" + }, + { + "type": "boolean", + "key": "hairSystems", + "label": "hairSystems" + }, + { + "type": "boolean", + "key": "handles", + "label": "handles" + }, + { + "type": "boolean", + "key": "high_quality", + "label": "high_quality" + }, + { + "type": "boolean", + "key": "hud", + "label": "hud" + }, + { + "type": "boolean", + "key": "hulls", + "label": "hulls" + }, + { + "type": "boolean", + "key": "ikHandles", + "label": "ikHandles" + }, + { + "type": "boolean", + "key": "imagePlane", + "label": "imagePlane" + }, + { + "type": "boolean", + "key": "joints", + "label": "joints" + }, + { + "type": "boolean", + "key": "lights", + "label": "lights" + }, + { + "type": "boolean", + "key": "locators", + "label": "locators" + }, + { + "type": "boolean", + "key": "manipulators", + "label": "manipulators" + }, + { + "type": "boolean", + "key": "motionTrails", + "label": "motionTrails" + }, + { + "type": "boolean", + "key": "nCloths", + "label": "nCloths" + }, + { + "type": "boolean", + "key": "nParticles", + "label": "nParticles" + }, + { + "type": "boolean", + "key": "nRigids", + "label": "nRigids" + }, + { + "type": "boolean", + "key": "nurbsCurves", + "label": "nurbsCurves" + }, + { + "type": "boolean", + "key": "nurbsSurfaces", + "label": "nurbsSurfaces" + }, + { + "type": "boolean", + "key": "override_viewport_options", + "label": "override_viewport_options" + }, + { + "type": "boolean", + "key": "particleInstancers", + "label": "particleInstancers" + }, + { + "type": "boolean", + "key": "pivots", + "label": "pivots" + }, + { + "type": "boolean", + "key": "planes", + "label": "planes" + }, + { + "type": "boolean", + "key": "pluginShapes", + "label": "pluginShapes" + }, + { + "type": "boolean", + "key": "polymeshes", + "label": "polymeshes" + }, + { + "type": "boolean", + "key": "shadows", + "label": "shadows" + }, + { + "type": "boolean", + "key": "strokes", + "label": "strokes" + }, + { + "type": "boolean", + "key": "subdivSurfaces", + "label": "subdivSurfaces" + }, + { + "type": "boolean", + "key": "textures", + "label": "textures" + }, + { + "type": "boolean", + "key": "twoSidedLighting", + "label": "twoSidedLighting" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "Camera Options", + "label": "Camera Options", + "children": [ + { + "type": "boolean", + "key": "displayGateMask", + "label": "displayGateMask" + }, + { + "type": "boolean", + "key": "displayResolution", + "label": "displayResolution" + }, + { + "type": "boolean", + "key": "displayFilmGate", + "label": "displayFilmGate" + }, + { + "type": "boolean", + "key": "displayFieldChart", + "label": "displayFieldChart" + }, + { + "type": "boolean", + "key": "displaySafeAction", + "label": "displaySafeAction" + }, + { + "type": "boolean", + "key": "displaySafeTitle", + "label": "displaySafeTitle" + }, + { + "type": "boolean", + "key": "displayFilmPivot", + "label": "displayFilmPivot" + }, + { + "type": "boolean", + "key": "displayFilmOrigin", + "label": "displayFilmOrigin" + }, + { + "type": "number", + "key": "overscan", + "label": "overscan", + "decimal": 1, + "minimum": 0, + "maximum": 10 + } + ] + } + ] + }, + { + "type": "dict-modifiable", + "key": "ext_mapping", + "label": "Extension Mapping", + "object_type": { + "type": "text" + } + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_load.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_load.json new file mode 100644 index 0000000000..5aec3715bd --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_load.json @@ -0,0 +1,156 @@ +{ + "type": "dict", + "collapsable": true, + "key": "load", + "label": "Loader plugins", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "colors", + "label": "Loaded Subsets Outliner Colors", + "children": [ + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Model", + "name": "model" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Rig", + "name": "rig" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Pointcache", + "name": "pointcache" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Animation", + "name": "animation" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Arnold Standin", + "name": "ass" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Camera", + "name": "camera" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "FBX", + "name": "fbx" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Maya Scene", + "name": "mayaAscii" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Set Dress", + "name": "setdress" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Layout", + "name": "layout" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "VDB Cache", + "name": "vdbcache" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Vray Proxy", + "name": "vrayproxy" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Yeti Cache", + "name": "yeticache" + } + ] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Yeti Rig", + "name": "yetiRig" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json new file mode 100644 index 0000000000..d1de128934 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -0,0 +1,214 @@ +{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "label", + "label": "Collectors" + }, + { + "type": "dict", + "collapsable": true, + "key": "CollectMayaRender", + "label": "Collect Render Layers", + "children": [ + { + "type": "boolean", + "key": "sync_workfile_version", + "label": "Sync render version with workfile" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Collectors" + }, + { + "type": "dict", + "collapsable": true, + "key": "ValidateCameraAttributes", + "label": "Validate Camera Attributes", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "ValidateModelName", + "label": "Validate Model Name", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Path to material file defining list of material names to check. This is material name per line simple text file.
It will be checked against named group shader in your Validation regex.

For example:
^.*(?P=<shader>.+)_GEO

" + }, + { + "type": "path-widget", + "key": "material_file", + "label": "Material File", + "multiplatform": true, + "multipath": false + }, + { + "type": "text", + "key": "regex", + "label": "Validation regex" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "ValidateAssemblyName", + "label": "Validate Assembly Name", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "ValidateShaderName", + "label": "ValidateShaderName", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Shader name regex can use named capture group asset to validate against current asset name.

Example:
^.*(?P=<asset>.+)_SHD

" + }, + { + "type": "text", + "key": "regex", + "label": "Validation regex" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "ValidateMeshHasOverlappingUVs", + "label": "ValidateMeshHasOverlappingUVs", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "ValidateAttributes", + "label": "ValidateAttributes", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "raw-json", + "key": "attributes", + "label": "Attributes" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Extractors" + }, + { + "type": "dict", + "collapsable": true, + "key": "ExtractCameraAlembic", + "label": "Extract camera to Alembic", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "List of attributes that will be added to the baked alembic camera. Needs to be written in python list syntax.

For example:
[\"attributeName\", \"anotherAttribute\"]

" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "raw-json", + "key": "bake_attributes", + "label": "Bake Attributes" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "MayaSubmitDeadline", + "label": "Submit maya job to deadline", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "enum", + "key": "tile_assembler_plugin", + "label": "Tile Assembler Plugin", + "multiselection": false, + "enum_items": [ + { + "DraftTileAssembler": "Draft Tile Assembler" + }, + { + "oiio": "Open Image IO" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_publish_gui_filter.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_publish_gui_filter.json new file mode 100644 index 0000000000..efc6c1d629 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_publish_gui_filter.json @@ -0,0 +1,10 @@ +{ + "type": "dict-modifiable", + "collapsable": true, + "key": "filters", + "label": "Publish GUI Filters", + "object_type": { + "type": "raw-json", + "label": "Plugins" + } +} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_workfile_build.json b/pype/settings/entities/schemas/projects_schema/schemas/schema_workfile_build.json new file mode 100644 index 0000000000..bf0aff2d41 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/schema_workfile_build.json @@ -0,0 +1,97 @@ +{ + "type": "dict", + "collapsable": true, + "key": "workfile_build", + "label": "Workfile Build Settings", + "children": [ + { + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": { + "type": "dict", + "children": [ + { + "key": "tasks", + "label": "Tasks", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "current_context", + "label": "Current Context", + "type": "list", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "subset_name_filters", + "label": "Subset name Filters", + "type": "list", + "object_type": "text" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "repre_names", + "label": "Repre Names", + "type": "list", + "object_type": "text" + }, + { + "key": "loaders", + "label": "Loaders", + "type": "list", + "object_type": "text" + } + ] + } + }, + { + "key": "linked_assets", + "label": "Linked Assets", + "type": "list", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "subset_name_filters", + "label": "Subset name Filters", + "type": "list", + "object_type": "text" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "repre_names", + "label": "Repre Names", + "type": "list", + "object_type": "text" + }, + { + "key": "loaders", + "label": "Loaders", + "type": "list", + "object_type": "text" + } + ] + } + } + ] + } + } + ] +} diff --git a/pype/settings/entities/schemas/projects_schema/schemas/template_color.json b/pype/settings/entities/schemas/projects_schema/schemas/template_color.json new file mode 100644 index 0000000000..04ce055525 --- /dev/null +++ b/pype/settings/entities/schemas/projects_schema/schemas/template_color.json @@ -0,0 +1,30 @@ +[ + { + "type": "list-strict", + "key": "{name}", + "label": "{label}:", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + } + ] + } +] diff --git a/pype/settings/entities/schemas/system_schema/example_schema.json b/pype/settings/entities/schemas/system_schema/example_schema.json new file mode 100644 index 0000000000..4175cebd42 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/example_schema.json @@ -0,0 +1,485 @@ +{ + "key": "example_dict", + "label": "Examples", + "type": "dict", + "is_file": true, + "children": [ + { + "type": "dict", + "key": "schema_template_exaples", + "label": "Schema template examples", + "children": [ + { + "type": "schema_template", + "name": "example_template", + "template_data": { + "host_label": "Application 1", + "host_name": "app_1", + "multipath_executables": false + } + }, + { + "type": "schema_template", + "name": "example_template", + "template_data": { + "host_label": "Application 2", + "host_name": "app_2" + } + } + ] + }, + { + "key": "env_group_test", + "label": "EnvGroup Test", + "type": "dict", + "children": [ + { + "key": "key_to_store_in_system_settings", + "label": "Testing environment group", + "type": "raw-json", + "env_group_key": "test_group" + } + ] + }, + { + "key": "dict_wrapper", + "type": "dict", + "children": [ + { + "type": "enum", + "key": "test_enum_singleselection", + "label": "Enum Single Selection", + "enum_items": [ + { "value_1": "Label 1" }, + { "value_2": "Label 2" }, + { "value_3": "Label 3" } + ] + }, + { + "type": "enum", + "key": "test_enum_multiselection", + "label": "Enum Multi Selection", + "multiselection": true, + "enum_items": [ + { "value_1": "Label 1" }, + { "value_2": "Label 2" }, + { "value_3": "Label 3" } + ] + }, + { + "type": "boolean", + "key": "bool", + "label": "Boolean checkbox" + }, + { + "type": "label", + "label": "NOTE: This is label" + }, + { + "type": "splitter" + }, + { + "type": "number", + "key": "integer", + "label": "Integer", + "decimal": 0, + "minimum": 0, + "maximum": 10 + }, + { + "type": "number", + "key": "float", + "label": "Float (2 decimals)", + "decimal": 2, + "minimum": -10, + "maximum": -5 + }, + { + "type": "text", + "key": "singleline_text", + "label": "Singleline text" + }, + { + "type": "text", + "key": "multiline_text", + "label": "Multiline text", + "multiline": true + }, + { + "type": "raw-json", + "key": "raw_json", + "label": "Raw json input" + }, + { + "type": "list", + "key": "list_item_of_multiline_texts", + "label": "List of multiline texts", + "object_type": { + "type": "text", + "multiline": true + } + }, + { + "type": "list", + "key": "list_item_of_floats", + "label": "List of floats", + "object_type": { + "type": "number", + "decimal": 3, + "minimum": 1000, + "maximum": 2000 + } + }, + { + "type": "dict-modifiable", + "key": "modifiable_dict_of_integers", + "label": "Modifiable dict of integers", + "object_type": { + "type": "number", + "decimal": 0, + "minimum": 10, + "maximum": 100 + } + }, + { + "type": "list-strict", + "key": "strict_list_labels_horizontal", + "label": "StrictList-labels-horizontal (color)", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, + { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, + { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, + { + "label": "Alpha", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 6 + } + ] + }, + { + "type": "list-strict", + "key": "strict_list_labels_vertical", + "label": "StrictList-labels-vertical (color)", + "horizontal": false, + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, + { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, + { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, + { + "label": "Alpha", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 6 + } + ] + }, + { + "type": "list-strict", + "key": "strict_list_nolabels_horizontal", + "label": "StrictList-nolabels-horizontal (color)", + "object_types": [ + { + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, + { + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, + { + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, + { + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 6 + } + ] + }, + { + "type": "list-strict", + "key": "strict_list_nolabels_vertical", + "label": "StrictList-nolabels-vertical (color)", + "horizontal": false, + "object_types": [ + { + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, + { + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, + { + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, + { + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 6 + } + ] + }, + { + "type": "list", + "key": "dict_item", + "label": "DictItem in List", + "object_type": { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "hosts", + "label": "Hosts", + "type": "list", + "object_type": "text" + } + ] + } + }, + { + "type": "path-widget", + "key": "single_path_input", + "label": "Single path input", + "multiplatform": false, + "multipath": false + }, + { + "type": "path-widget", + "key": "multi_path_input", + "label": "Multi path input", + "multiplatform": false, + "multipath": true + }, + { + "type": "path-widget", + "key": "single_os_specific_path_input", + "label": "Single OS specific path input", + "multiplatform": true, + "multipath": false + }, + { + "type": "path-widget", + "key": "multi_os_specific_path_input", + "label": "Multi OS specific path input", + "multiplatform": true, + "multipath": true + }, + { + "key": "collapsable", + "type": "dict", + "label": "collapsable dictionary", + "collapsable": true, + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "_nothing", + "label": "Exmaple input" + } + ] + }, + { + "key": "collapsable_expanded", + "type": "dict", + "label": "collapsable dictionary, expanded on creation", + "collapsable": true, + "collapsed": false, + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "_nothing", + "label": "Exmaple input" + } + ] + }, + { + "key": "not_collapsable", + "type": "dict", + "label": "Not collapsable", + "collapsable": false, + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "_nothing", + "label": "Exmaple input" + } + ] + }, + { + "key": "nested_dict_lvl1", + "type": "dict", + "label": "Nested dictionary (level 1)", + "children": [ + { + "key": "nested_dict_lvl2", + "type": "dict", + "label": "Nested dictionary (level 2)", + "is_group": true, + "children": [ + { + "key": "nested_dict_lvl3", + "type": "dict", + "label": "Nested dictionary (level 3)", + "children": [ + { + "type": "boolean", + "key": "_nothing", + "label": "Exmaple input" + } + ] + }, + { + "key": "nested_dict_lvl3_2", + "type": "dict", + "label": "Nested dictionary (level 3) (2)", + "children": [ + { + "type": "text", + "key": "_nothing", + "label": "Exmaple input" + }, + { + "type": "text", + "key": "_nothing2", + "label": "Exmaple input 2" + } + ] + } + ] + } + ] + }, + { + "key": "form_examples", + "type": "dict", + "label": "Form examples", + "children": [ + { + "key": "inputs_without_form_example", + "type": "dict", + "label": "Inputs without form", + "children": [ + { + "type": "text", + "key": "_nothing_1", + "label": "Example label" + }, + { + "type": "text", + "key": "_nothing_2", + "label": "Example label ####" + }, + { + "type": "text", + "key": "_nothing_3", + "label": "Example label ########" + } + ] + }, + { + "key": "inputs_with_form_example", + "type": "dict", + "label": "Inputs with form", + "children": [ + { + "type": "form", + "children": [ + { + "type": "text", + "key": "_nothing_1", + "label": "Example label" + }, + { + "type": "text", + "key": "_nothing_2", + "label": "Example label ####" + }, + { + "type": "text", + "key": "_nothing_3", + "label": "Example label ########" + } + ] + } + ] + } + ] + }, + { + "type": "collapsible-wrap", + "label": "Collapsible Wrapper without key", + "children": [ + { + "type": "text", + "key": "_example_input_collapsible", + "label": "Example input in collapsible wrapper" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/example_template.json b/pype/settings/entities/schemas/system_schema/example_template.json new file mode 100644 index 0000000000..d9e6935407 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/example_template.json @@ -0,0 +1,20 @@ +[ + { + "__default_values__": { + "multipath_executables": true + } + }, + { + "type": "raw-json", + "label": "{host_label} Environments", + "key": "{host_name}_environments", + "env_group_key": "{host_name}" + }, + { + "type": "path-widget", + "key": "{host_name}_executables", + "label": "{host_label} - Full paths to executables", + "multiplatform": "{multipath_executables}", + "multipath": true + } +] diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json new file mode 100644 index 0000000000..4578e836b5 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json @@ -0,0 +1,44 @@ +{ + "type": "dict", + "key": "aftereffects", + "label": "Adobe AfterEffects", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "aftereffects" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "2020", + "host_name": "aftereffects" + }, + { + "host_version": "2021", + "host_name": "aftereffects" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_blender.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_blender.json new file mode 100644 index 0000000000..fbb9a82b87 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_blender.json @@ -0,0 +1,44 @@ +{ + "type": "dict", + "key": "blender", + "label": "Blender", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "blender" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "2.90", + "host_name": "blender" + }, + { + "host_version": "2.83", + "host_name": "blender" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_celaction.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_celaction.json new file mode 100644 index 0000000000..0578dcae9e --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_celaction.json @@ -0,0 +1,48 @@ +{ + "type": "dict", + "key": "celaction", + "label": "CelAction2D", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "celaction" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "Local", + "host_name": "celation", + "multiplatform": false, + "multipath_executables": false + }, + { + "host_version": "Publish", + "host_name": "celation", + "multiplatform": false, + "multipath_executables": false + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_djv.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_djv.json new file mode 100644 index 0000000000..6df690d43c --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_djv.json @@ -0,0 +1,38 @@ +{ + "type": "dict", + "key": "djvview", + "label": "DJV View", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "djvview" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": { + "host_version": "1.1", + "host_name": "djvview" + } + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_fusion.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_fusion.json new file mode 100644 index 0000000000..189588ace1 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_fusion.json @@ -0,0 +1,44 @@ +{ + "type": "dict", + "key": "fusion", + "label": "Blackmagic Fusion", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "fusion" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "16", + "host_name": "fusion" + }, + { + "host_version": "9", + "host_name": "fusion" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json new file mode 100644 index 0000000000..2a042be1e3 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json @@ -0,0 +1,44 @@ +{ + "type": "dict", + "key": "harmony", + "label": "Toon Boom Harmony", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "harmony" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "20", + "host_name": "harmony" + }, + { + "host_version": "17", + "host_name": "harmony" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_houdini.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_houdini.json new file mode 100644 index 0000000000..404b2bfbc7 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_houdini.json @@ -0,0 +1,44 @@ +{ + "type": "dict", + "key": "houdini", + "label": "SideFX Houdini", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "houdini" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "18", + "host_name": "houdini" + }, + { + "host_version": "17", + "host_name": "houdini" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_maya.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_maya.json new file mode 100644 index 0000000000..ba639ca134 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_maya.json @@ -0,0 +1,48 @@ +{ + "type": "dict", + "key": "maya", + "label": "Autodesk Maya", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "maya" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "2020", + "host_name": "maya" + }, + { + "host_version": "2019", + "host_name": "maya" + }, + { + "host_version": "2018", + "host_name": "maya" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_mayabatch.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_mayabatch.json new file mode 100644 index 0000000000..cee0cd8e9f --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_mayabatch.json @@ -0,0 +1,48 @@ +{ + "type": "dict", + "key": "mayabatch", + "label": "Autodesk Maya Batch", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "mayabatch" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "2020", + "host_name": "mayabatch" + }, + { + "host_version": "2019", + "host_name": "mayabatch" + }, + { + "host_version": "2018", + "host_name": "mayabatch" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json new file mode 100644 index 0000000000..61b9473f2b --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json @@ -0,0 +1,44 @@ +{ + "type": "dict", + "key": "photoshop", + "label": "Adobe Photoshop", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "photoshop" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "2020", + "host_name": "photoshop" + }, + { + "host_version": "2021", + "host_name": "photoshop" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json new file mode 100644 index 0000000000..c7aa4e15d3 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json @@ -0,0 +1,40 @@ +{ + "type": "dict", + "key": "resolve", + "label": "Blackmagic DaVinci Resolve", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "resolve" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "16", + "host_name": "resolve" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_shell.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_shell.json new file mode 100644 index 0000000000..f1241ea3bd --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_shell.json @@ -0,0 +1,44 @@ +{ + "type": "dict", + "key": "shell", + "label": "Shell", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "shell" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "Python 3.7", + "host_name": "python" + }, + { + "host_version": "Python 2.7", + "host_name": "python" + }, + { + "host_version": "Terminal", + "host_name": "terminal" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_tvpaint.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_tvpaint.json new file mode 100644 index 0000000000..b328e5cf79 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_tvpaint.json @@ -0,0 +1,44 @@ +{ + "type": "dict", + "key": "tvpaint", + "label": "TVPaint", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "tvpaint" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "Animation 11 (64bits)", + "host_name": "tvpaint" + }, + { + "host_version": "Animation 11 (32bits)", + "host_name": "tvpaint" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json b/pype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json new file mode 100644 index 0000000000..8560d8782e --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json @@ -0,0 +1,40 @@ +{ + "type": "dict", + "key": "unreal", + "label": "Unreal Editor", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "unreal" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "4.24", + "host_name": "unreal" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/host_settings/template_host_unchangables.json b/pype/settings/entities/schemas/system_schema/host_settings/template_host_unchangables.json new file mode 100644 index 0000000000..e8b2a70076 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/template_host_unchangables.json @@ -0,0 +1,38 @@ +[ + { + "type": "text", + "key": "label", + "label": "Label", + "placeholder": "Host label (without any version)", + "roles": ["developer"] + }, + { + "type": "text", + "key": "icon", + "label": "Icon", + "placeholder": "Host icon path template", + "roles": ["developer"] + }, + { + "type": "enum", + "key": "host_name", + "label": "Host implementation", + "enum_items": [ + { "": "< without host >" }, + { "aftereffects": "aftereffects" }, + { "blender": "blender" }, + { "celaction": "celaction" }, + { "fusion": "fusion" }, + { "harmony": "harmony" }, + { "hiero": "hiero" }, + { "houdini": "houdini" }, + { "maya": "maya" }, + { "nuke": "nuke" }, + { "photoshop": "photoshop" }, + { "resolve": "resolve" }, + { "tvpaint": "tvpaint" }, + { "unreal": "unreal" } + ], + "roles": ["developer"] + } +] diff --git a/pype/settings/entities/schemas/system_schema/host_settings/template_host_variant.json b/pype/settings/entities/schemas/system_schema/host_settings/template_host_variant.json new file mode 100644 index 0000000000..cea7da3a81 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/template_host_variant.json @@ -0,0 +1,57 @@ +[ + { + "__default_values__": { + "multipath_executables": true, + "multiplatform": true + } + }, + { + "type": "dict", + "key": "{host_name}_{host_version}", + "label": "{host_version}", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "label", + "label": "Label", + "placeholder": "Used from host label if not filled.", + "roles": ["developer"] + }, + { + "type": "text", + "key": "variant_label", + "label": "Variant label", + "placeholder": "Only \"Label\" is used if not filled.", + "roles": ["developer"] + }, + { + "type": "text", + "key": "icon", + "label": "Icon", + "placeholder": "Host icon path template. Used from host if not filled.", + "roles": ["developer"] + }, + { + "type": "path-widget", + "key": "executables", + "label": "Executables", + "multiplatform": "{multiplatform}", + "multipath": "{multipath_executables}", + "with_arguments": true + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "{host_name}_{host_version}" + } + ] + } +] diff --git a/pype/settings/entities/schemas/system_schema/host_settings/template_nuke.json b/pype/settings/entities/schemas/system_schema/host_settings/template_nuke.json new file mode 100644 index 0000000000..a1f975c2a7 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/host_settings/template_nuke.json @@ -0,0 +1,58 @@ +[ + { + "type": "dict", + "key": "{nuke_type}", + "label": "Foundry {nuke_label}", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "{nuke_type}" + }, + { + "type": "dict", + "key": "variants", + "children": [ + { + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "12.2", + "host_name": "{nuke_type}", + "multipath_executables": true + }, + { + "host_version": "12.0", + "host_name": "{nuke_type}", + "multipath_executables": true + }, + { + "host_version": "11.3", + "host_name": "{nuke_type}", + "multipath_executables": true + }, + { + "host_version": "11.2", + "host_name": "{nuke_type}", + "multipath_executables": true + } + ] + } + ] + } + ] + } +] diff --git a/pype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/pype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json new file mode 100644 index 0000000000..0683bd10b5 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -0,0 +1,163 @@ +{ + "type": "dict", + "key": "ftrack", + "label": "Ftrack", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "ftrack_server", + "label": "Server" + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Additional Ftrack paths" + }, + { + "type": "list", + "key": "ftrack_actions_path", + "label": "Action paths", + "object_type": "text" + }, + { + "type": "list", + "key": "ftrack_events_path", + "label": "Event paths", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Ftrack event server advanced settings" + }, + { + "type": "text", + "key": "mongo_database_name", + "label": "Event Mongo DB" + }, + { + "type": "text", + "key": "mongo_collection_name", + "label": "Events Mongo Collection" + }, + { + "type": "splitter" + }, + { + "key": "intent", + "type": "dict", + "label": "Intent", + "collapsable_key": true, + "is_group": true, + "children": [ + { + "type": "label", + "label": "Intent" + }, + { + "type": "dict-modifiable", + "object_type": "text", + "key": "items" + }, + { + "type": "label", + "label": " " + }, + { + "key": "default", + "type": "text", + "label": "Default Intent" + } + ] + }, + { + "key": "custom_attributes", + "label": "Custom Attributes", + "type": "dict", + "children": [ + { + "type": "dict", + "key": "show", + "label": "Project Custom attributes", + "children": [ + { + "type": "schema_template", + "name": "template_custom_attribute", + "template_data": [ + { + "key": "avalon_auto_sync" + }, + { + "key": "library_project" + }, + { + "key": "applications" + } + ] + } + ] + }, + { + "type": "dict", + "key": "is_hierarchical", + "label": "Hierarchical Attributes", + "children": [ + { + "type": "schema_template", + "name": "template_custom_attribute", + "template_data": [ + { + "key": "tools_env" + }, + { + "key": "avalon_mongo_id" + }, + { + "key": "fps" + }, + { + "key": "frameStart" + }, + { + "key": "frameEnd" + }, + { + "key": "clipIn" + }, + { + "key": "clipOut" + }, + { + "key": "handleStart" + }, + { + "key": "handleEnd" + }, + { + "key": "resolutionWidth" + }, + { + "key": "resolutionHeight" + }, + { + "key": "pixelAspect" + } + ] + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/module_settings/template_custom_attribute.json b/pype/settings/entities/schemas/system_schema/module_settings/template_custom_attribute.json new file mode 100644 index 0000000000..71b7f2ea53 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/module_settings/template_custom_attribute.json @@ -0,0 +1,21 @@ +[ + { + "key": "{key}", + "label": "{key}", + "type": "dict", + "children": [ + { + "key": "write_security_roles", + "label": "Write roles", + "type": "list", + "object_type": "text" + }, + { + "key": "read_security_roles", + "label": "Read roles", + "type": "list", + "object_type": "text" + } + ] + } +] diff --git a/pype/settings/entities/schemas/system_schema/schema_applications.json b/pype/settings/entities/schemas/system_schema/schema_applications.json new file mode 100644 index 0000000000..e2b882ba7d --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/schema_applications.json @@ -0,0 +1,97 @@ +{ + "key": "applications", + "type": "dict", + "label": "Applications", + "collapsable": true, + "is_file": true, + "children": [ + { + "type": "schema", + "name": "schema_maya" + }, + { + "type": "schema", + "name": "schema_mayabatch" + }, + { + "type": "schema_template", + "name": "template_nuke", + "template_data": { + "nuke_type": "nuke", + "nuke_label": "Nuke" + } + }, + { + "type": "schema_template", + "name": "template_nuke", + "template_data": { + "nuke_type": "nukex", + "nuke_label": "Nuke X" + } + }, + { + "type": "schema_template", + "name": "template_nuke", + "template_data": { + "nuke_type": "nukestudio", + "nuke_label": "Nuke Studio" + } + }, + { + "type": "schema_template", + "name": "template_nuke", + "template_data": { + "nuke_type": "hiero", + "nuke_label": "Hiero" + } + }, + { + "type": "schema", + "name": "schema_fusion" + }, + { + "type": "schema", + "name": "schema_resolve" + }, + { + "type": "schema", + "name": "schema_houdini" + }, + { + "type": "schema", + "name": "schema_blender" + }, + { + "type": "schema", + "name": "schema_harmony" + }, + { + "type": "schema", + "name": "schema_tvpaint" + }, + { + "type": "schema", + "name": "schema_photoshop" + }, + { + "type": "schema", + "name": "schema_aftereffects" + }, + { + "type": "schema", + "name": "schema_celaction" + }, + { + "type": "schema", + "name": "schema_unreal" + }, + { + "type": "schema", + "name": "schema_shell" + }, + { + "type": "schema", + "name": "schema_djv" + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/schema_general.json b/pype/settings/entities/schemas/system_schema/schema_general.json new file mode 100644 index 0000000000..a919526de8 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/schema_general.json @@ -0,0 +1,45 @@ +{ + "key": "general", + "type": "dict", + "label": "General", + "collapsable": true, + "is_file": true, + "children": [ + { + "key": "studio_name", + "type": "text", + "label": "Studio Name" + }, + { + "key": "studio_code", + "type": "text", + "label": "Studio Short Code" + }, + { + "type": "splitter" + }, + { + "key": "project_plugins", + "type": "path-widget", + "label": "Additional Project Plugins Path", + "multiplatform": true, + "multipath": false + }, + { + "key": "studio_soft", + "type": "path-widget", + "label": "Studio Software Location", + "multiplatform": true, + "multipath": false + }, + { + "type": "splitter" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "global" + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/schema_main.json b/pype/settings/entities/schemas/system_schema/schema_main.json new file mode 100644 index 0000000000..31c7a991aa --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/schema_main.json @@ -0,0 +1,22 @@ +{ + "key": "system", + "type": "dict", + "children": [ + { + "type": "schema", + "name": "schema_general" + }, + { + "type": "schema", + "name": "schema_modules" + }, + { + "type": "schema", + "name": "schema_applications" + }, + { + "type": "schema", + "name": "schema_tools" + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/schema_modules.json b/pype/settings/entities/schemas/system_schema/schema_modules.json new file mode 100644 index 0000000000..a3b34a37c7 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/schema_modules.json @@ -0,0 +1,231 @@ +{ + "key": "modules", + "type": "dict", + "label": "Modules", + "collapsable": true, + "is_file": true, + "children": [ + { + "type": "dict", + "key": "avalon", + "label": "Avalon", + "collapsable": true, + "children": [ + { + "type": "text", + "key": "AVALON_MONGO", + "label": "Avalon Mongo URL", + "placeholder": "Pype Mongo is used if not filled." + }, + { + "type": "number", + "key": "AVALON_TIMEOUT", + "minimum": 0, + "label": "Avalon Mongo Timeout (ms)" + }, + { + "type": "path-widget", + "label": "Thumbnail Storage Location", + "key": "AVALON_THUMBNAIL_ROOT", + "multiplatform": true, + "multipath": false + }, + { + "type": "text", + "key": "AVALON_DB_DATA", + "label": "Avalon Mongo Data Location" + } + ] + }, + { + "type": "schema", + "name": "schema_ftrack" + }, + { + "type": "dict", + "key": "rest_api", + "label": "Rest Api", + "collapsable": true, + "children": [ + { + "type": "number", + "key": "default_port", + "label": "Default Port", + "minimum": 1, + "maximum": 65535 + }, + { + "type": "list", + "key": "exclude_ports", + "label": "Exclude ports", + "object_type": { + "type": "number", + "minimum": 1, + "maximum": 65535 + } + } + ] + }, + { + "type": "dict", + "key": "timers_manager", + "label": "Timers Manager", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "number", + "decimal": 2, + "key": "full_time", + "label": "Max idle time" + }, + { + "type": "number", + "decimal": 2, + "key": "message_time", + "label": "When dialog will show" + } + ] + }, + { + "type": "dict", + "key": "clockify", + "label": "Clockify", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "workspace_name", + "label": "Workspace name" + } + ] + }, { + "type": "dict", + "key": "sync_server", + "label": "Sync Server", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + },{ + "type": "dict", + "key": "deadline", + "label": "Deadline", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "DEADLINE_REST_URL", + "label": "Deadline Resl URL" + } + ] + }, + { + "type": "dict", + "key": "muster", + "label": "Muster", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "MUSTER_REST_URL", + "label": "Muster Rest URL" + }, + { + "type": "dict-modifiable", + "object_type": { + "type": "number", + "minimum": 0, + "maximum": 300 + }, + "is_group": true, + "key": "templates_mapping", + "label": "Templates mapping", + "is_file": true + } + ] + }, + { + "type": "dict", + "key": "log_viewer", + "label": "Logging", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, + { + "type": "dict", + "key": "user", + "label": "User setting", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, + { + "type": "dict", + "key": "standalonepublish_tool", + "label": "Standalone Publish", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, + { + "type": "dict", + "key": "idle_manager", + "label": "Idle Manager", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + } + ] +} \ No newline at end of file diff --git a/pype/settings/entities/schemas/system_schema/schema_tools.json b/pype/settings/entities/schemas/system_schema/schema_tools.json new file mode 100644 index 0000000000..c8f4829a09 --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/schema_tools.json @@ -0,0 +1,37 @@ +{ + "key": "tools", + "type": "dict", + "label": "Tools", + "collapsable": true, + "is_file": true, + "children": [ + { + "type": "schema", + "name": "schema_arnold" + }, + { + "type": "schema", + "name": "schema_vray" + }, + { + "type": "schema", + "name": "schema_yeti" + }, + { + "type": "dict", + "key": "other", + "children": [ + { + "type": "schema_template", + "name": "template_tool_variant", + "template_data": [ + { + "tool_name": "othertools", + "tool_label": "Other Tools and Plugins" + } + ] + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/tool_settings/schema_arnold.json b/pype/settings/entities/schemas/system_schema/tool_settings/schema_arnold.json new file mode 100644 index 0000000000..aa50610ddc --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/tool_settings/schema_arnold.json @@ -0,0 +1,29 @@ +{ + "type": "dict", + "key": "mtoa", + "label": "Autodesk Arnold", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment (mtoa)", + "type": "raw-json", + "env_group_key": "mtoa" + }, + { + "type": "schema_template", + "name": "template_tool_variant", + "template_data": [ + { + "tool_label": "Arnold Versions" + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/tool_settings/schema_vray.json b/pype/settings/entities/schemas/system_schema/tool_settings/schema_vray.json new file mode 100644 index 0000000000..0ce24a310c --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/tool_settings/schema_vray.json @@ -0,0 +1,28 @@ +{ + "type": "dict", + "key": "vray", + "label": "Chaos Group Vray", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json" + }, + { + "type": "schema_template", + "name": "template_tool_variant", + "template_data": [ + { + "tool_label": "Vray Versions" + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/tool_settings/schema_yeti.json b/pype/settings/entities/schemas/system_schema/tool_settings/schema_yeti.json new file mode 100644 index 0000000000..b94dbbfcaa --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/tool_settings/schema_yeti.json @@ -0,0 +1,28 @@ +{ + "type": "dict", + "key": "yeti", + "label": "Pergrine Labs Yeti", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json" + }, + { + "type": "schema_template", + "name": "template_tool_variant", + "template_data": [ + { + "tool_label": "Yeti Versions" + } + ] + } + ] +} diff --git a/pype/settings/entities/schemas/system_schema/tool_settings/template_tool_variant.json b/pype/settings/entities/schemas/system_schema/tool_settings/template_tool_variant.json new file mode 100644 index 0000000000..b0ba63469c --- /dev/null +++ b/pype/settings/entities/schemas/system_schema/tool_settings/template_tool_variant.json @@ -0,0 +1,11 @@ +[ + { + "type": "dict-modifiable", + "key": "variants", + "label": "{tool_label}", + "value_is_env_group": true, + "object_type": { + "type": "raw-json" + } + } +]