""" 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: utils.py # brief: General utility functions # author Adobe - 3D & Immersive # copyright 2023 Adobe Inc. All rights reserved. import bpy import os import json from time import time import xml.etree.ElementTree as ET from .common import ( ICONS_DICT, ICONS_IMAGES, ADDON_ROOT, ADDON_PACKAGE, INPUT_ANGLE_CONVERSION, IMAGE_EXPORT_FORMAT, Code_InputWidget, Code_InputType, Code_InputIdentifier ) class SUBSTANCE_Utils(): # Preview Update FIX @staticmethod def toggle_redraw(context): if context.scene.sbsar_redraw < 2: context.scene.sbsar_redraw += 1 else: context.scene.sbsar_redraw = 0 # IMAGE COLORSPACES @staticmethod def get_colorspaces(): _colorspaces = [] _items = bpy.types.Image.bl_rna.properties['colorspace_settings'].fixed_type.properties['name'].enum_items for _item in _items: _colorspaces.append((_item.identifier, _item.name, _item.description)) return _colorspaces # JSON @staticmethod def get_json(filepath): _json_obj = None with open(filepath, encoding="utf8") as _json_file: _json_obj = json.load(_json_file) return _json_obj @staticmethod def set_json(filepath, obj): with open(filepath, "w", encoding="utf8") as _json_file: json.dump(obj, _json_file, ensure_ascii=False) # Time @staticmethod def get_current_time_in_ms(): return int(round(time() * 1000)) # Icons @staticmethod def init_icons(): _icons_dir = os.path.join(ADDON_ROOT, "_icons").replace("\\", "/") for _image in ICONS_IMAGES: if _image["id"] not in ICONS_DICT: ICONS_DICT.load(_image["id"], os.path.join(_icons_dir, _image["filename"]), 'IMAGE') # Log @staticmethod def log_data(type, message, display=False): if display: try: bpy.ops.substance.send_message(type=type, message="Substance 3D in Blender: "+message) except Exception: print("Substance 3D in Blender: {} - {}".format(type, message)) else: print("Substance 3D in Blender: {} - {}".format(type, message)) @staticmethod def log_traceback(message): print("Substance 3D in Blender: ERROR - TRACEBACK:\n") print(message) # Geo @staticmethod def get_selected_geo(selected_objects): _filtered = [] for _item in selected_objects: if hasattr(_item.data, 'materials'): _filtered.append(_item) return _filtered # Graph @staticmethod def get_selected_graph(context=bpy.context): _selected_sbsar = context.scene.loaded_sbsars[context.scene.sbsar_index] _selected_graph_idx = int(_selected_sbsar.graphs_list) _selected_graph = _selected_sbsar.graphs[_selected_graph_idx] return _selected_graph # Shader @staticmethod def get_selected_shader(context=bpy.context, idx=-1): _addon_prefs = context.preferences.addons[ADDON_PACKAGE].preferences _selected_preset_idx = idx if _selected_preset_idx < 0: _selected_preset_idx = int(_addon_prefs.shader_list) _selected_shader_preset = _addon_prefs.shaders[_selected_preset_idx] return _addon_prefs, "{}".format(_selected_preset_idx), _selected_shader_preset @staticmethod def get_shader_file(filename): _custom_shaders_dir = os.path.join(ADDON_ROOT, "_presets/custom").replace('\\', '/') _shaders_dir = os.path.join(ADDON_ROOT, "_presets/default").replace('\\', '/') _path = os.path.join(_custom_shaders_dir, filename) if not os.path.exists(_path): _path = os.path.join(_shaders_dir, filename) return _path @staticmethod def get_shader_prefs(context, shader): _temp_inputs = getattr(context.scene, shader.inputs_class_name) _temp_outputs = {} for _output in shader.outputs: _temp_outputs[_output.id] = _output.to_json() return { "inputs": _temp_inputs, "outputs": _temp_outputs, } # SBSAR @staticmethod def get_unique_name(filename, context): _unique_name = filename.replace(".sbsar", "") _temp_name = _unique_name _counter = 0 while _temp_name in context.scene.loaded_sbsars: _counter += 1 _temp_name = _unique_name + "_{}".format(_counter) if _counter: return _unique_name + "_{}".format(_counter) return _unique_name # SBSAR INPUTS @staticmethod def inputs_empty(inputs): for _input in inputs: if _input.guiWidget != Code_InputWidget.nowidget.value or _input.type == Code_InputType.string.name: return False return True @staticmethod def value_fix_type(identifier, widget, type, new_value): if widget == Code_InputWidget.combobox.value: return int(new_value) elif widget == Code_InputWidget.slider.value: if type == Code_InputType.integer.name: return int(new_value) elif type == Code_InputType.float.name: return float(new_value) elif (type == Code_InputType.integer2.name or type == Code_InputType.integer3.name or type == Code_InputType.integer4.name): return list(new_value) elif (type == Code_InputType.float2.name or type == Code_InputType.float3.name or type == Code_InputType.float4.name): return list(new_value) else: return 0 elif widget == Code_InputWidget.color.value: if type == Code_InputType.float.name: return float(new_value) elif type == Code_InputType.float3.name or type == Code_InputType.float4.name: return list(new_value) else: return 0 elif widget == Code_InputWidget.togglebutton.value: return int(new_value) elif widget == Code_InputWidget.angle.value: return float(new_value) / INPUT_ANGLE_CONVERSION elif widget == Code_InputWidget.position.value: return list(new_value) elif widget == Code_InputWidget.image.value: if isinstance(new_value, bpy.types.Image): return new_value.name else: return "" elif widget == Code_InputWidget.nowidget.value: if identifier == Code_InputIdentifier.outputsize.value: return list(new_value) elif identifier == Code_InputIdentifier.randomseed.value: return int(new_value) elif type == Code_InputType.string.name: return str(new_value) else: return 0 else: return 0 # Render @staticmethod def render_image_input(img, context): if not context: context = bpy.context _addon_prefs = context.preferences.addons[ADDON_PACKAGE].preferences _default_path = _addon_prefs.path_default _format_idx = int(_addon_prefs.default_export_format) _file_extension = IMAGE_EXPORT_FORMAT[_format_idx][2].replace("*", "") img.file_format = IMAGE_EXPORT_FORMAT[_format_idx][1] _image_filepath = os.path.join(_default_path, img.name + _file_extension).replace("\\", "/") img.save_render(_image_filepath) return _image_filepath # Physical Size @staticmethod def get_physical_size(physical_size, context): _scale = context.scene.unit_settings.scale_length _new_physical_size = [ physical_size[0] * _scale, physical_size[1] * _scale, physical_size[2] * _scale ] return _new_physical_size # Output Size @staticmethod def update_preset_outputsize(preset_value, input, type, value): _preset_xml = ET.fromstring(preset_value) _input_item = None for _preset_input in _preset_xml: if _preset_input.attrib["uid"] == str(input.id): _input_item = _preset_input if _input_item is not None: if type == Code_InputType.integer.value: _input_item.attrib["value"] = "{}".format(value) elif type == Code_InputType.integer2.value: _input_item.attrib["value"] = "{},{}".format(value[0], value[1]) else: _attrib = { "identifier": input.identifier, "uid": "{}".format(input.id), "type": "{}".format(type) } if type == Code_InputType.integer.value: _attrib["value"] = "{}".format(value) elif type == Code_InputType.integer2.value: _attrib["value"] = "{},{}".format(value[0], value[1]) _el = ET.SubElement(_preset_xml, "presetinput", attrib=_attrib) _el.tail = "\n" _new_preset_value = ET.tostring(_preset_xml, encoding='unicode') return _new_preset_value