Files
2026-03-17 14:30:01 -06:00

284 lines
9.7 KiB
Python

"""
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 <http://www.gnu.org/licenses/>.
"""
# 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