""" Copyright (C) 2023 Adobe. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ # file: factory/sbsar.py # brief: Dynamic class creation for sbsar objects # author Adobe - 3D & Immersive # copyright 2023 Adobe Inc. All rights reserved. import bpy from copy import deepcopy import sys import traceback from ..utils import SUBSTANCE_Utils from ..sbsar.callbacks import SUBSTANCE_SbsarCallbacks from ..common import ( Code_InputWidget, Code_InputType, Code_InputIdentifier, Code_OutputSizeSuffix, Code_Response, RESOLUTIONS_DICT, INPUT_ANGLE_CONVERSION, INPUT_INT_MAX ) # Inputs functions def _to_json(self): pass def _from_json(self, inputs, data): for _item in data: if Code_InputIdentifier.outputsize.value == _item["identifier"]: _output_size = _item["value"] _output_size_linked = _output_size[0] == _output_size[1] setattr( self, Code_InputIdentifier.outputsize.value + Code_OutputSizeSuffix.width.value, str(_output_size[0])) setattr( self, Code_InputIdentifier.outputsize.value + Code_OutputSizeSuffix.height.value, str(_output_size[1])) setattr( self, Code_InputIdentifier.outputsize.value + Code_OutputSizeSuffix.linked.value, _output_size_linked) continue elif not hasattr(self, _item["identifier"]): continue _input = inputs[_item["identifier"]] _value = _item["value"] if _input.guiWidget == Code_InputWidget.combobox.value: _value = str(_value) elif _input.guiWidget == Code_InputWidget.slider.value: pass elif _input.guiWidget == Code_InputWidget.color.value: pass elif _input.guiWidget == Code_InputWidget.togglebutton.value: _value = str(_value) elif _input.guiWidget == Code_InputWidget.angle.value: _value = _value * INPUT_ANGLE_CONVERSION elif _input.guiWidget == Code_InputWidget.position.value: pass elif _input.guiWidget == Code_InputWidget.image.value: pass elif _input.identifier == Code_InputIdentifier.randomseed.value: pass else: pass setattr(self, _item["identifier"], _value) class SUBSTANCE_SbsarFactory(): @staticmethod def init_enum_values(values): _items = [] for _item in values: _items.append( (str(_item.first), _item.second, "{}:{}".format(_item.first, _item.second)) ) return _items @staticmethod def init_toggle_values(values): _items = [] for _idx, _item in enumerate(values): _items.append( (str(_idx), _item, "{}:{}".format(_idx, _item)) ) return _items @staticmethod def create_input_item(input, class_name): if input.type == Code_InputType.string.name: return bpy.props.StringProperty( name=input.label, description=input.guiDescription, default=str(input.defaultValue), update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed(self, context, input.identifier)) elif input.guiWidget == Code_InputWidget.combobox.value: return bpy.props.EnumProperty( name=input.label, description=input.guiDescription, default=str(input.defaultValue), items=SUBSTANCE_SbsarFactory.init_enum_values(input.enumValues), update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed(self, context, input.identifier)) elif input.guiWidget == Code_InputWidget.slider.value: if input.type == Code_InputType.integer.name: _max = input.maxValue _min = input.minValue if _max == _min: _max = INPUT_INT_MAX _min = INPUT_INT_MAX*-1 if hasattr(input, "sliderClamp") and input.sliderClamp: return bpy.props.IntProperty( name=input.label, description=input.guiDescription, default=input.defaultValue, max=_max, min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) else: return bpy.props.IntProperty( name=input.label, description=input.guiDescription, default=input.defaultValue, soft_max=_max, soft_min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) elif input.type == Code_InputType.float.name: _max = input.maxValue _min = input.minValue if _max == _min: _max = sys.float_info.max _min = sys.float_info.min if hasattr(input, "sliderClamp") and input.sliderClamp: return bpy.props.FloatProperty( name=input.label, description=input.guiDescription, default=input.defaultValue, max=_max, min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) else: return bpy.props.FloatProperty( name=input.label, description=input.guiDescription, default=input.defaultValue, soft_max=_max, soft_min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) elif (input.type == Code_InputType.integer2.name or input.type == Code_InputType.integer3.name or input.type == Code_InputType.integer4.name): _dimension = len(input.defaultValue) _max = input.maxValue[0] _min = input.minValue[0] if _max == _min: _max = INPUT_INT_MAX _min = INPUT_INT_MAX*-1 if hasattr(input, "sliderClamp") and input.sliderClamp: return bpy.props.IntVectorProperty( name=input.label, description=input.guiDescription, size=_dimension, default=input.defaultValue, max=_max, min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) else: return bpy.props.IntVectorProperty( name=input.label, description=input.guiDescription, size=_dimension, default=input.defaultValue, soft_max=_max, soft_min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) elif (input.type == Code_InputType.float2.name or input.type == Code_InputType.float3.name or input.type == Code_InputType.float4.name): _dimension = len(input.defaultValue) _max = input.maxValue[0] _min = input.minValue[0] if _max == _min: _max = sys.float_info.max _min = sys.float_info.min if hasattr(input, "sliderClamp") and input.sliderClamp: return bpy.props.FloatVectorProperty( name=input.label, description=input.guiDescription, size=_dimension, default=input.defaultValue, max=_max, min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) else: return bpy.props.FloatVectorProperty( name=input.label, description=input.guiDescription, size=_dimension, default=input.defaultValue, soft_max=_max, soft_min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) else: return None elif input.guiWidget == Code_InputWidget.color.value: if input.type == Code_InputType.float.name: _max = input.maxValue _min = input.minValue if _max == _min: _max = 1 _min = 0 if hasattr(input, "sliderClamp") and input.sliderClamp: return bpy.props.FloatProperty( name=input.label, description=input.guiDescription, default=input.defaultValue, max=_max, min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) else: return bpy.props.FloatProperty( name=input.label, description=input.guiDescription, default=input.defaultValue, soft_max=_max, soft_min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) elif input.type == Code_InputType.float3.name or input.type == Code_InputType.float4.name: _dimension = len(input.defaultValue) _max = input.maxValue[0] _min = input.minValue[0] if _max == _min: _max = 1 _min = 0 if hasattr(input, "sliderClamp") and input.sliderClamp: return bpy.props.FloatVectorProperty( name=input.label, description=input.guiDescription, subtype="COLOR", size=_dimension, default=input.defaultValue, max=_max, min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) else: return bpy.props.FloatVectorProperty( name=input.label, description=input.guiDescription, subtype="COLOR", size=_dimension, default=input.defaultValue, soft_max=_max, soft_min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) else: return None elif input.guiWidget == Code_InputWidget.togglebutton.value: _label_true = input.labelTrue if input.labelTrue != "" else "True" _label_false = input.labelFalse if input.labelFalse != "" else "False" _toggle_labels = [_label_false, _label_true] return bpy.props.EnumProperty( name=input.label, description=input.guiDescription, default=str(input.defaultValue), items=SUBSTANCE_SbsarFactory.init_toggle_values(_toggle_labels), update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) elif input.guiWidget == Code_InputWidget.angle.value: _max = input.maxValue * INPUT_ANGLE_CONVERSION _min = input.minValue * INPUT_ANGLE_CONVERSION _default = input.defaultValue * INPUT_ANGLE_CONVERSION if _max == _min: _max = sys.float_info.max _min = sys.float_info.min if hasattr(input, "sliderClamp") and input.sliderClamp: return bpy.props.FloatProperty( name=input.label, description=input.guiDescription, subtype="ANGLE", default=_default, max=_max, min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) else: return bpy.props.FloatProperty( name=input.label, description=input.guiDescription, subtype="ANGLE", default=_default, soft_max=_max, soft_min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) elif input.guiWidget == Code_InputWidget.position.value: _dimension = len(input.defaultValue) _max = input.maxValue[0] _min = input.minValue[0] if _max == _min: _max = 1 _min = 0 if hasattr(input, "sliderClamp") and input.sliderClamp: return bpy.props.FloatVectorProperty( name=input.label, description=input.guiDescription, subtype="XYZ", size=_dimension, default=input.defaultValue, max=_max, min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) else: return bpy.props.FloatVectorProperty( name=input.label, description=input.guiDescription, subtype="XYZ", size=_dimension, default=input.defaultValue, soft_max=_max, soft_min=_min, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) elif input.guiWidget == Code_InputWidget.image.value: return bpy.props.PointerProperty( name=input.label, description=input.guiDescription, type=bpy.types.Image, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) elif input.guiWidget == Code_InputWidget.nowidget.value: if input.identifier == Code_InputIdentifier.randomseed.value: if hasattr(input, "sliderClamp") and input.sliderClamp: return bpy.props.IntProperty( name=input.label, description=input.guiDescription, default=input.defaultValue, max=input.maxValue, min=input.minValue, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) else: return bpy.props.IntProperty( name=input.label, description=input.guiDescription, default=input.defaultValue, soft_max=input.maxValue, soft_min=input.minValue, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_input_changed( self, context, input.identifier)) if input.identifier == Code_InputIdentifier.outputsize.value: return [ bpy.props.EnumProperty( name=input.label, description=input.guiDescription, default=str(input.defaultValue[0]), items=RESOLUTIONS_DICT, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_linked_changed( self, context, input.identifier + Code_OutputSizeSuffix.width.value)), bpy.props.EnumProperty( name=input.label, description=input.guiDescription, default=str(input.defaultValue[1]), items=RESOLUTIONS_DICT, update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_outputsize_changed( self, context, input.identifier + Code_OutputSizeSuffix.height.value)), bpy.props.BoolProperty( name=input.label, description=input.guiDescription, default=input.defaultValue[0] == input.defaultValue[1], update=lambda self, context: SUBSTANCE_SbsarCallbacks.on_linked_changed( self, context, input.identifier + Code_OutputSizeSuffix.linked.value)) ] else: return None else: return None @staticmethod def register_inputs_class(sbsar): for _graph in sbsar.graphs: _attributes = {} for _key, _input in _graph.inputs.items(): _attribute = SUBSTANCE_SbsarFactory.create_input_item(_input, _graph.inputs_class_name) if _attribute: if _input.identifier == Code_InputIdentifier.outputsize.value: _attributes[_input.identifier + Code_OutputSizeSuffix.width.value] = _attribute[0] _attributes[_input.identifier + Code_OutputSizeSuffix.height.value] = _attribute[1] _attributes[_input.identifier + Code_OutputSizeSuffix.linked.value] = _attribute[2] else: _attributes[_input.identifier] = _attribute else: SUBSTANCE_Utils.log_data("WARNING", "Input not created [{}]".format(_input.identifier)) _inputs_class = type(_graph.inputs_class_name, (bpy.types.PropertyGroup,), { "__annotations__": _attributes, "sbsar_uuid": sbsar.uuid, "default": deepcopy(_graph), "callback": {"enabled": True}, "to_json": _to_json, "from_json": _from_json }) bpy.utils.register_class(_inputs_class) setattr( bpy.types.Scene, _graph.inputs_class_name, bpy.props.PointerProperty(name=_graph.inputs_class_name, type=_inputs_class)) @staticmethod def register_class(sbsar): try: SUBSTANCE_SbsarFactory.register_inputs_class(sbsar) return (Code_Response.success, sbsar) except Exception: SUBSTANCE_Utils.log_data("ERROR", "Exception - Susbtance register error:") SUBSTANCE_Utils.log_traceback(traceback.format_exc()) return (Code_Response.sbsar_factory_register_error, None) @staticmethod def unregister_class(class_name): if hasattr(bpy.context.scene, class_name): _object = getattr(bpy.context.scene, class_name) _class_type = type(_object) delattr(bpy.types.Scene, class_name) bpy.utils.unregister_class(_class_type)