Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import bpy
- import base64
- import json
- import os
- from bpy.props import (StringProperty,
- BoolProperty,
- IntProperty,
- FloatProperty,
- FloatVectorProperty,
- EnumProperty,
- PointerProperty,
- CollectionProperty
- )
- from bpy.types import (Panel,
- Operator,
- AddonPreferences,
- PropertyGroup,
- UIList
- )
- #import libraries required for types
- #for dict to nodes
- from mathutils import (Vector, Euler, Color)
- from bpy.types import Text
- from bpy.types import Object
- from bpy.types import ColorMapping, CurveMapping, ColorRamp
- from bpy.types import Image, ImageUser, ParticleSystem
- #import creating panel for panel related code
- #specifically operators to create ui lists
- #from . import panel
- #from . import load_save_common
- #from .load_save_common import nodes_dict_to_json, get_selected_folder_presets, save_pref, redraw_all
- from bpy.types import (
- NodeSocketBool, NodeSocketColor, NodeSocketFloat, NodeSocketFloatAngle,
- NodeSocketFloatFactor, NodeSocketFloatPercentage, NodeSocketFloatTime,
- NodeSocketFloatUnsigned, NodeSocketInt, NodeSocketIntFactor, NodeSocketIntPercentage,
- NodeSocketIntUnsigned, NodeSocketShader, NodeSocketString, NodeSocketVector,
- NodeSocketVectorAcceleration, NodeSocketVectorDirection, NodeSocketVectorEuler,
- NodeSocketVectorTranslation, NodeSocketVectorVelocity, NodeSocketVectorXYZ, NodeSocketVirtual
- )
- #define globals
- SHADER_EDITOR = "ShaderNodeTree"
- COMPOSITOR_EDITOR = "CompositorNodeTree"
- ANIMATION_NODE_EDITOR = "an_AnimationNodeTree"
- class SAVEUESHADERSCRIPT_OT_save_shader_map(bpy.types.Operator):
- #default name is for Roman Noodles label
- #text is changed for other Shader Map Types
- bl_label = "Save Shader Map"
- bl_idname = "saveueshaderscript.saveshadermap_operator"
- def execute(self, context):
- #store active/selected scene to variable
- scene = context.scene
- #allow access to user inputted properties through pointer
- #to properties
- savetool = scene.save_tool
- if preset_name_exist(savetool.cust_map_name):
- bpy.ops.ueshaderscript.show_message(
- message="The nodes preset name exists, please choose another name and try again.")
- return {'FINISHED'}
- area = context.area
- editor_type = area.ui_type
- scene = context.scene
- #allow access to user inputted properties through pointer
- #to properties
- savetool = scene.save_tool
- #if(savetool.is_add_img_textures == True):
- # print("hey")
- node_editor = area
- tree = node_editor.spaces[0].node_tree
- #debug
- #print("context.area.spaces[0].node_tree: ", tree)
- #print("tree.nodes[0].bl_idname", tree.nodes[0].bl_idname)
- nodes_list, links_list, img_textures_list = nodes_to_dict(tree, savetool)
- #img_textures_list will be blank {} if add_image_textures is False or if no image textures should be loaded
- nodes_dict = {"nodes_list": nodes_list, "links_list": links_list, "img_textures_list": img_textures_list}
- nodes_dict["editor_type"] = editor_type
- shader_type = area.spaces[0].shader_type
- nodes_dict["shader_type"] = shader_type
- # Debug:
- # print(nodes_dict)
- JSON = nodes_dict_to_json(nodes_dict)
- selected_folder_presets = get_selected_folder_presets()
- presets = selected_folder_presets.presets
- new_preset = presets.add()
- #debug
- #print("savetool.cust_map_name:", savetool.cust_map_name)
- new_preset.name = savetool.cust_map_name
- new_preset.content = JSON
- selected_folder_presets.preset_index = len(presets) - 1
- save_pref()
- redraw_all()
- return {"FINISHED"}
- #------------------------NODES TO DICTIONARY RELATED CODE
- def nodes_to_dict(tree, savetool):
- """ Actually, we construct and return a List """
- if tree is not None:
- nodes = tree.nodes
- else:
- nodes = []
- nodes_list = []
- for node in nodes:
- #copy default node properties to node_dict
- node_dict = {"node_name": node.bl_idname}
- node_dict["x"] = node.location.x
- node_dict["y"] = node.location.y
- node_dict["width"] = node.width
- node_dict["width_hidden"] = node.width_hidden
- node_dict["height"] = node.height
- #debug
- #print("node dict after basic node properties:", node_dict)
- #copy node parents means which frames
- #each node belongs to to node_dict
- parent = node.parent
- if parent == None:
- node_dict["parent"] = "None"
- else:
- parent_index = get_node_index(nodes, parent)
- node_dict["parent"] = parent_index
- #copy node attributes to node_dict
- #dir() returns all properties and methods of
- #the specified object, without the values.
- attrs = dir(node)
- attrs_list = []
- for attr in attrs:
- attr_dict = attr_to_dict(node, attr)
- if attr_dict["type_name"] == "NoneType" \
- or attr_dict["type_name"] == "Not Handle Type":
- continue
- attrs_list.append(attr_dict)
- node_dict["attrs"] = attrs_list
- #debug
- #print("node dict after attrs:", node_dict)
- #copy node inputs and output values and types to node_dict
- inputs = []
- for input in node.inputs:
- input_dict = socket_to_dict_input(input)
- inputs.append(input_dict)
- if node.bl_idname != "CompositorNodeOutputFile":
- node_dict["inputs"] = inputs
- else:
- node_dict["inputs"] = []
- outputs = []
- for output in node.outputs:
- output_dict = socket_to_dict_output(output)
- outputs.append(output_dict)
- node_dict["outputs"] = outputs
- #debug
- #print("node dict after inputs outputs:", node_dict)
- # Special handling ShaderNodeGroup
- # this is for recording node groups
- if node.bl_idname == "ShaderNodeGroup":
- node_dict["node_tree"] = nodes_to_dict_handle_shader_node_group(
- node, savetool)
- ### ignore as we only need to use the Shader Editor none of the other editors
- # if node.bl_idname == "CompositorNodeGroup":
- # node_dict["node_tree"] = nodes_to_dict_handle_compositor_node_group(
- # node)
- # if node.bl_idname == "CompositorNodeOutputFile":
- # # We just handle OutputFile->file_slots, does not handle layer_slots (Waiting for bugs)
- # node_dict["file_slots"] = nodes_to_dict_handle_compositor_node_output_file(
- # node)
- # # We treat all file slot's file format as the same of OutputFile->format->file_format
- # node_dict["file_format"] = node.format.file_format
- nodes_list.append(node_dict)
- #debug
- #print("full node_dict:", node_dict)
- #print("\n\nfull nodes_list", nodes_list)
- #print("\n\nfull nodes_list length", len(nodes_list))
- #record all links in a python list
- links_list = links_to_list(tree)
- #debug
- #print("\n\nlinks_list", links_list)
- #if the option to load image textures is true then record
- #what image texture suffixes and node names the user has written
- if(savetool.is_add_img_textures == True):
- img_textures_list = textures_to_list(savetool)
- else:
- img_textures_list = []
- return (nodes_list, links_list, img_textures_list)
- def socket_to_dict_output(output):
- return socket_to_dict_input(output)
- def socket_to_dict_input(input):
- t = type(input)
- dict = {}
- if t == NodeSocketColor:
- dict["type_name"] = "NodeSocketColor"
- dict["value"] = list(input.default_value)
- elif t == NodeSocketFloatFactor:
- dict["type_name"] = "NodeSocketFloatFactor"
- dict["value"] = input.default_value
- elif t == NodeSocketVector or t == NodeSocketVectorDirection or \
- t == NodeSocketVectorEuler or t == NodeSocketVectorTranslation or \
- t == NodeSocketVectorVelocity or t == NodeSocketVectorXYZ:
- dict["type_name"] = "NodeSocketVector"
- dict["value"] = list(input.default_value)
- elif t == NodeSocketBool:
- dict["type_name"] = "NodeSocketBool"
- if input.default_value == True:
- value = 1
- else:
- value = 0
- dict["value"] = value
- elif t == NodeSocketFloat:
- dict["type_name"] = "NodeSocketFloat"
- dict["value"] = input.default_value
- elif t == NodeSocketFloatAngle:
- dict["type_name"] = "NodeSocketFloatAngle"
- dict["value"] = input.default_value
- elif t == NodeSocketFloatPercentage:
- dict["type_name"] = "NodeSocketFloatPercentage"
- dict["value"] = input.default_value
- elif t == NodeSocketFloatTime:
- dict["type_name"] = "NodeSocketFloatTime"
- dict["value"] = input.default_value
- elif t == NodeSocketFloatUnsigned:
- dict["type_name"] = "NodeSocketFloatUnsigned"
- dict["value"] = input.default_value
- elif t == NodeSocketInt:
- dict["type_name"] = "NodeSocketInt"
- dict["value"] = input.default_value
- elif t == NodeSocketIntFactor:
- dict["type_name"] = "NodeSocketIntFactor"
- dict["value"] = input.default_value
- elif t == NodeSocketIntPercentage:
- dict["type_name"] = "NodeSocketIntPercentage"
- dict["value"] = input.default_value
- elif t == NodeSocketIntUnsigned:
- dict["type_name"] = "NodeSocketIntUnsigned"
- dict["value"] = input.default_value
- elif t == NodeSocketString:
- dict["type_name"] = "NodeSocketString"
- dict["value"] = input.default_value
- elif t == NodeSocketVector:
- dict["type_name"] = "NodeSocketVector"
- dict["value"] = list(input.default_value)
- elif t == NodeSocketVectorAcceleration:
- dict["type_name"] = "NodeSocketVectorAcceleration"
- dict["value"] = list(input.default_value)
- elif t == NodeSocketShader:
- dict["type_name"] = "NodeSocketShader"
- elif t == NodeSocketVirtual:
- dict["type_name"] = "NodeSocketVirtual"
- else:
- log("socket_to_dict_input() can not handle input type: %s" % t)
- raise ValueError(
- "socket_to_dict_input() can not handle input type: %s" % t)
- dict["name"] = input.name
- return dict
- def get_node_index(nodes, node):
- index = 0
- for n in nodes:
- if n == node:
- return index
- index += 1
- return "None"
- def attr_to_dict(node, attr):
- dict = {}
- if not is_default_attr(attr):
- t = type(getattr(node, attr))
- v = getattr(node, attr)
- if v == None:
- dict["type_name"] = "NoneType"
- elif t == str:
- dict["type_name"] = "str"
- dict["value"] = getattr(node, attr)
- elif t == int:
- dict["type_name"] = "int"
- dict["value"] = getattr(node, attr)
- elif t == float:
- dict["type_name"] = "float"
- dict["value"] = getattr(node, attr)
- elif t == bool:
- dict["type_name"] = "bool"
- if getattr(node, attr) == True:
- value = 1
- else:
- value = 0
- dict["value"] = value
- elif t == list:
- dict["type_name"] = "list"
- dict["value"] = getattr(node, attr)
- elif t == tuple:
- dict["type_name"] = "tuple"
- dict["value"] = list(getattr(node, attr))
- elif t == Vector:
- dict["type_name"] = "Vector"
- dict["value"] = list(getattr(node, attr).to_tuple())
- elif t == Euler:
- dict["type_name"] = "Euler"
- value = getattr(node, attr)
- dict["value"] = list(value[:])
- elif t == Text:
- dict["type_name"] = "Text"
- value = getattr(node, attr)
- dict["value"] = value.name
- elif t == ColorMapping:
- dict["type_name"] = "NoneType"
- elif t == Image:
- dict["type_name"] = "Image"
- image = getattr(node, attr)
- dict["value"] = image.name
- dict["image_filepath"] = image.filepath
- dict["image_source"] = image.source
- elif t == Object:
- dict["type_name"] = "Object"
- value = getattr(node, attr)
- dict["value"] = value.name
- elif t == ImageUser:
- dict["type_name"] = "ImageUser"
- image_user = getattr(node, attr)
- value = get_value_from_ImageUser(image_user)
- dict["value"] = value
- elif t == ParticleSystem:
- dict["type_name"] = "ParticleSystem"
- value = getattr(node, attr)
- dict["value"] = value.name
- dict["object_name"] = bpy.context.object.name
- elif t == CurveMapping:
- dict["type_name"] = "CurveMapping"
- dict["value"] = get_value_from_CurveMapping(getattr(node, attr))
- elif t == Color:
- dict["type_name"] = "Color"
- value = getattr(node, attr)
- dict["value"] = list(value)
- elif t == ColorRamp:
- dict["type_name"] = "ColorRamp"
- color_ramp = getattr(node, attr)
- color_ramp_dict = {}
- color_ramp_dict["color_mode"] = color_ramp.color_mode
- color_ramp_dict["hue_interpolation"] = color_ramp.hue_interpolation
- color_ramp_dict["interpolation"] = color_ramp.interpolation
- elements = []
- for ele in color_ramp.elements:
- color_ramp_element = {}
- color_ramp_element["alpha"] = ele.alpha
- color_ramp_element["color"] = list(ele.color)
- color_ramp_element["position"] = ele.position
- elements.append(color_ramp_element)
- color_ramp_dict["elements"] = elements
- dict["value"] = color_ramp_dict
- else:
- dict["type_name"] = "NoneType"
- log("attr_to_dict() can not handle attr type: %s attr:%s" % (t, attr))
- # We don't raise error because some type no need to handle, and
- # it works well for restore
- #raise ValueError("attr_to_dict() can not handle attr type: %s" % t)
- else:
- dict["type_name"] = "Not Handle Type"
- dict["attr_name"] = attr
- return dict
- def get_value_from_CurveMapping(curve_mapping):
- def get_curve_points(curve):
- ret = []
- for p in curve.points:
- point_dict = {}
- point_dict["handle_type"] = p.handle_type
- point_dict["location"] = list(p.location)
- ret.append(point_dict)
- return ret
- dict = {}
- dict["black_level"] = list(curve_mapping.black_level)
- dict["clip_max_x"] = curve_mapping.clip_max_x
- dict["clip_max_y"] = curve_mapping.clip_max_y
- dict["clip_min_x"] = curve_mapping.clip_min_x
- dict["clip_min_y"] = curve_mapping.clip_min_y
- dict["tone"] = curve_mapping.tone
- dict["use_clip"] = curve_mapping.use_clip
- dict["white_level"] = list(curve_mapping.white_level)
- curves = []
- for curve in curve_mapping.curves:
- curve_dict = {}
- #curve_dict["extend"] = curve.extend
- set_attr_if_exist_for_dict(curve, "extend", curve_dict)
- curve_dict["points"] = get_curve_points(curve)
- curves.append(curve_dict)
- dict["curves"] = curves
- return dict
- def set_attr_if_exist_for_dict(obj, attr, dict):
- if hasattr(obj, attr):
- dict[attr] = getattr(obj, attr)
- else:
- dict[attr] = "None"
- def get_value_from_ImageUser(image_user):
- dict = {}
- dict["frame_current"] = image_user.frame_current
- dict["frame_duration"] = image_user.frame_duration
- dict["frame_offset"] = image_user.frame_offset
- dict["frame_start"] = image_user.frame_start
- dict["use_cyclic"] = image_user.use_cyclic
- dict["use_auto_refresh"] = image_user.use_auto_refresh
- return dict
- def get_value_from_ColorMapping(color_mapping):
- dict = {}
- dict["blend_color"] = list(color_mapping.blend_color)
- dict["blend_factor"] = color_mapping.blend_factor
- dict["blend_type"] = color_mapping.blend_type
- dict["brightness"] = color_mapping.brightness
- dict["contrast"] = color_mapping.contrast
- dict["saturation"] = color_mapping.saturation
- dict["use_color_ramp"] = color_mapping.use_color_ramp
- return dict
- def is_default_attr(attr):
- if attr in get_default_attrs():
- return True
- else:
- return False
- def get_default_attrs():
- # `codeEffects` is a value which can not be JSONify in KDTree (Animation Node)
- return ['__doc__', '__module__', '__slots__', 'bl_description',
- 'bl_height_default',
- 'bl_height_max', 'bl_height_min', 'bl_icon', 'bl_idname',
- 'bl_label', 'bl_rna', 'bl_static_type', 'bl_width_default',
- 'bl_width_max', 'bl_width_min', 'dimensions',
- 'draw_buttons', 'draw_buttons_ext', 'height',
- 'input_template', 'inputs', 'internal_links', 'is_active_output',
- 'is_registered_node_type', 'location', 'mute',
- 'output_template', 'outputs', 'parent', 'poll', 'poll_instance',
- 'rna_type', 'select', 'show_options', 'show_preview',
- 'show_texture', 'socket_value_update', 'type', 'update',
- 'width', 'width_hidden',
- 'codeEffects'
- ]
- def nodes_to_dict_handle_shader_node_group(node, savetool):
- node_tree_of_node_group = node.node_tree
- inputs = node_tree_of_node_group.inputs
- interface_inputs_list = interface_inputs_to_list(inputs)
- node_tree_dict = {}
- node_tree_dict["interface_inputs"] = interface_inputs_list
- node_tree_dict["name"] = node_tree_of_node_group.name
- #img_textures list is unused here but that is intentional
- nodes_list, links_list, img_textures_list = nodes_to_dict(node_tree_of_node_group, savetool)
- node_tree_dict["nodes_list"] = nodes_list
- node_tree_dict["links_list"] = links_list
- return node_tree_dict
- def interface_inputs_to_list(inputs):
- inputs_list = []
- for input in inputs:
- if hasattr(input, "min_value"):
- min_value = input.min_value
- else:
- min_value = "None"
- if hasattr(input, "max_value"):
- max_value = input.max_value
- else:
- max_value = "None"
- dict = {"min_value": min_value, "max_value": max_value}
- inputs_list.append(dict)
- return inputs_list
- def links_to_list(tree):
- if tree is None:
- links = []
- nodes = []
- else:
- links = tree.links
- nodes = tree.nodes
- links_list = []
- for link in links:
- link_dict = {}
- for node_index, n in enumerate(nodes):
- inputs = n.inputs
- outputs = n.outputs
- for index, o in enumerate(outputs):
- if link.from_socket == o:
- link_dict["from_node_index"] = node_index
- link_dict["from_socket_index"] = index
- link_dict["from_socket_name"] = o.name
- for index, i in enumerate(inputs):
- if link.to_socket == i:
- link_dict["to_node_index"] = node_index
- link_dict["to_socket_index"] = index
- link_dict["to_socket_name"] = i.name
- links_list.append(link_dict)
- return links_list
- def textures_to_list(savetool):
- img_textures_list = []
- suffix_node_to_list(savetool.bc_suffix, savetool.bc_suffix_node, img_textures_list, "diffuse")
- suffix_node_to_list(savetool.orm_suffix, savetool.orm_suffix_node, img_textures_list, "packed_orm")
- suffix_node_to_list(savetool.n_suffix, savetool.n_suffix_node, img_textures_list, "normal")
- suffix_node_to_list(savetool.m_suffix, savetool.m_suffix_node, img_textures_list, "transparency")
- suffix_node_to_list(savetool.bde_suffix, savetool.bde_suffix_node, img_textures_list, "emissions")
- suffix_node_to_list(savetool.hm_suffix, savetool.hm_suffix_node, img_textures_list, "height")
- suffix_node_to_list(savetool.skin_suffix, savetool.skin_suffix_node, img_textures_list, "skin")
- suffix_node_to_list(savetool.cust1_suffix, savetool.cust1_suffix_node, img_textures_list, "cust1")
- suffix_node_to_list(savetool.cust2_suffix, savetool.cust2_suffix_node, img_textures_list, "cust2")
- #if the img_textures list is empty
- #that is equivalent to having
- #no image textures to load
- #debug
- #print("img_textures_dict: ", img_textures_dict)
- #print("img_textures_list length: ", len(img_textures_list))
- return img_textures_list
- def suffix_node_to_list(suffix, suffix_node, img_textures_list, texture):
- img_textures_dict = {}
- #if both the suffix and suffix node are not empty
- #record the suffix in the dictionary
- if suffix != "" and suffix_node != "" :
- #texture is just for debugging purposes so can check JSON file
- img_textures_dict["texture"] = texture
- img_textures_dict["suffix"] = suffix
- img_textures_dict["suffix_node"] = suffix_node
- #if an entry was added into the img_textures_dict
- #append it to the list
- #otherwise don't append anything to the list
- if img_textures_dict != {}:
- img_textures_list.append(img_textures_dict)
- #---------------------------Panel related code including preset and folder new , deletion, renaming
- #define all user input properties
- class SaveProperties(bpy.types.PropertyGroup):
- cust_map_name: bpy.props.StringProperty(name="Name of Shader Map", description="Name of your custom shader map")
- bc_suffix: bpy.props.StringProperty(name="Diffuse Suffix", description="Suffix of Diffuse", default="_BC")
- bc_suffix_node: bpy.props.StringProperty(name="Diffuse Node Name", description="Diffuse image texture node name", default="Diffuse Node")
- orm_suffix: bpy.props.StringProperty(name="Packed RGB ARM Suffix", description="Suffix of Packed RGB (AO, Rough, Metallic)", default="_ORM")
- orm_suffix_node: bpy.props.StringProperty(name="Packed RGB Node Name", description="Packed RGB image texture node name", default="Packed RGB Node")
- n_suffix: bpy.props.StringProperty(name="Normal Map Suffix", description="Suffix of Normal Map", default="_N")
- n_suffix_node: bpy.props.StringProperty(name="Normal Map Node Name", description="Normal Map image texture node name", default="Normal Map Node")
- m_suffix: bpy.props.StringProperty(name="Alpha Map Suffix", description="Suffix of Alpha (Transparency) Map", default="_M")
- m_suffix_node: bpy.props.StringProperty(name="Alpha Map Node Name", description="Alpha Map image texture node name", default="Transparency Map Node")
- bde_suffix: bpy.props.StringProperty(name="Emissions Map Suffix", description="Suffix of Emissions Map", default="_BDE")
- bde_suffix_node: bpy.props.StringProperty(name="Emissions Map Node Name", description="Emissions Map image texture node name", default="Emissions Map Node")
- hm_suffix: bpy.props.StringProperty(name="Height Map Suffix", description="Suffix of Height Map", default="")
- hm_suffix_node: bpy.props.StringProperty(name="Height Map Node Name", description="Height Map image texture node name", default="")
- skin_suffix: bpy.props.StringProperty(name="Skin Suffix", description="Suffix of Skin Texture", default="")
- skin_suffix_node: bpy.props.StringProperty(name="Skin Node Name", description="Skin image texture node name", default="")
- cust1_suffix: bpy.props.StringProperty(name="Custom1 Suffix", description="Suffix of Custom1 Texture", default="")
- cust1_suffix_node: bpy.props.StringProperty(name="Custom1 Node Name", description="Custom1 image texture node name", default="")
- cust2_suffix: bpy.props.StringProperty(name="Custom2 Suffix", description="Suffix of Custom2 Texture", default="")
- cust2_suffix_node: bpy.props.StringProperty(name="Custom2 Node Name", description="Custom2 image texture node name", default="")
- is_add_img_textures: bpy.props.BoolProperty(name="Load Image Textures to Shader Map", default= True)
- #code for drawing main panel in the 3D View
- class SAVEUESHADERSCRIPT_PT_main_panel(bpy.types.Panel):
- bl_label = "Save UE Shaders"
- bl_idname = "SAVEUESHADERSCRIPT_PT_main_panel"
- bl_space_type = 'NODE_EDITOR'
- bl_region_type = 'UI'
- bl_category = "Save UE Shaders"
- def draw(self, context):
- layout = self.layout
- #store active/selected scene to variable
- scene = context.scene
- #allow access to user inputted properties through pointer
- #to properties
- savetool = scene.save_tool
- preferences = get_preferences()
- #make folder section
- box = layout.box()
- row = box.row()
- row.label(text="Folders")
- row = box.row()
- left = row.column()
- left.alignment = "RIGHT"
- left.prop(preferences, 'folders', expand=False)
- right = row.column()
- right.alignment = "LEFT"
- right.operator("nodekit.folder_actions", text="", icon="MENU_PANEL")
- selected_folders_presets = get_selected_folder_presets()
- layout.label(text = "Your presets")
- #create the list of current presets
- layout.template_list("SHADER_PRESETS_UL_items", "", selected_folders_presets,
- "presets", selected_folders_presets, "preset_index", rows=5)
- layout.operator("saveueshaderscript.remove_preset",
- text="Remove", icon="REMOVE")
- layout.label(text = "Save a Custom Shader Map")
- layout.prop(savetool, "cust_map_name")
- layout.prop(savetool, "is_add_img_textures")
- if (savetool.is_add_img_textures == True):
- box = layout.box()
- box.label(text = "Image Texture Suffixes and Node Names")
- box.label(text = "(leave suffix OR node name empty if you do NOT want to load the specific image texture)")
- box.label(text = "Node Names can be found/changed by selecting an image texture node > Press n > Item > Name")
- box.prop(savetool, "bc_suffix")
- box.prop(savetool, "bc_suffix_node")
- box.prop(savetool, "orm_suffix")
- box.prop(savetool, "orm_suffix_node")
- box.prop(savetool, "n_suffix")
- box.prop(savetool, "n_suffix_node")
- box.prop(savetool, "m_suffix")
- box.prop(savetool, "m_suffix_node")
- box.prop(savetool, "bde_suffix")
- box.prop(savetool, "bde_suffix_node")
- box.prop(savetool, "hm_suffix")
- box.prop(savetool, "hm_suffix_node")
- box.prop(savetool, "skin_suffix")
- box.prop(savetool, "skin_suffix_node")
- box.prop(savetool, "cust1_suffix")
- box.prop(savetool, "cust1_suffix_node")
- box.prop(savetool, "cust2_suffix")
- box.prop(savetool, "cust2_suffix_node")
- layout.operator("SAVEUESHADERSCRIPT.saveshadermap_operator")
- layout.operator("SAVEUESHADERSCRIPT.loadimagetexture_operator")
- #this class is the list to be displayed in the main panel
- class SHADER_PRESETS_UL_items(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
- row = layout.row()
- custom_icon = "NODE"
- row.label(text=item.name, icon=custom_icon)
- def invoke(self, context, event):
- pass
- class SHADER_MT_FolderActionsMenu(bpy.types.Menu):
- bl_label = ""
- bl_idname = "SHADER_MT_FolderActionsMenu"
- def draw(self, context):
- layout = self.layout
- # https://docs.blender.org/api/current/bpy.types.Menu.html
- # https://docs.blender.org/api/current/bpy.ops.html#operator-execution-context
- # This fix below operators not working properly
- layout.operator_context = 'INVOKE_DEFAULT'
- row = layout.row()
- row.operator("nodekit.new_folder", text="Add New Folder", icon="ADD")
- row = layout.row()
- row.operator("nodekit.remove_folder",
- text="Remove Selected Folder", icon="REMOVE")
- row = layout.row()
- row.operator("nodekit.rename_folder",
- text="Rename Selected Folder", icon="EVENT_R")
- class Shader_ShowFolderActionsOperator(bpy.types.Operator):
- bl_idname = "nodekit.folder_actions"
- bl_label = ""
- bl_description = "Show Message for Node Kit"
- bl_options = {'REGISTER'}
- @classmethod
- def poll(cls, context):
- return True
- def execute(self, context):
- bpy.ops.wm.call_menu(name="SHADER_MT_FolderActionsMenu")
- return {'FINISHED'}
- class Shader_NewFolderOperator(bpy.types.Operator):
- bl_idname = "nodekit.new_folder"
- bl_label = "New Folder"
- bl_description = "New Folder"
- folder_name: bpy.props.StringProperty(name="")
- @classmethod
- def poll(cls, context):
- return True
- def draw(self, context):
- layout = self.layout
- row = layout.row()
- left, right = get_two_column(self, row)
- left.label(text="New Folder Name:")
- right.prop(self, "folder_name")
- def execute(self, context):
- if folder_name_exist(self.folder_name):
- bpy.ops.nodekit.show_message(
- message="The folder name exists, please choose another name and try again.")
- return {'FINISHED'}
- pref = get_preferences()
- new_folder_presets = pref.folders_presets.add()
- new_folder_presets.folder_name = self.folder_name
- index = len(pref.folders_presets) - 1
- pref.folders = "%d" % index
- save_pref()
- redraw_all()
- return {'FINISHED'}
- def invoke(self, context, event):
- suggested_name = suggested_folder_name()
- self.folder_name = suggested_name
- wm = context.window_manager
- return wm.invoke_props_dialog(self, width=300)
- #-------------check if preset or folder name exists
- def preset_name_exist(name):
- selected_folder_presets = get_selected_folder_presets()
- presets = selected_folder_presets.presets
- for preset in presets:
- if name == preset.name:
- return True
- return False
- def folder_name_exist(name):
- pref = get_preferences()
- folder_presets = pref.folders_presets
- for folder_preset in folder_presets:
- if name == folder_preset.folder_name:
- return True
- return False
- def preset_name_exist_in_folder(folder_name, preset_name):
- pref = get_preferences()
- folders_presets = pref.folders_presets
- for folder in folders_presets:
- if folder.folder_name == folder_name:
- for preset in folder.presets:
- if preset.name == preset_name:
- return True
- return False
- def suggested_preset_name():
- name = "preset"
- index = 1
- suggested_name = "%s %d" % (name, index)
- while preset_name_exist(suggested_name):
- index += 1
- suggested_name = "%s %d" % (name, index)
- return suggested_name
- def suggested_folder_name():
- name = "folder"
- index = 1
- suggested_name = "%s %d" % (name, index)
- while folder_name_exist(suggested_name):
- index += 1
- suggested_name = "%s %d" % (name, index)
- return suggested_name
- #----check if preset or folder name exists end
- class Shader_RemoveFolderOperator(bpy.types.Operator):
- bl_idname = "nodekit.remove_folder"
- bl_label = "Do you really want to remove selected folder?"
- bl_description = "Remove Folder"
- @classmethod
- def poll(cls, context):
- return True
- def execute(self, context):
- pref = get_preferences()
- selected_folder = pref.folders
- remove_folder(selected_folder)
- save_pref()
- redraw_all()
- return {'FINISHED'}
- def invoke(self, context, event):
- return context.window_manager.invoke_confirm(self, event)
- def remove_folder(selected_folder_index):
- pref = get_preferences()
- folder_presets = pref.folders_presets
- for index, folder_preset in enumerate(folder_presets):
- if index == int(selected_folder_index):
- folder_presets.remove(index)
- l = len(pref.folders_presets) - 1
- if l >= 0:
- pref.folders = "%d" % l
- break
- class Shader_RenameFolderOperator(bpy.types.Operator):
- bl_idname = "nodekit.rename_folder"
- bl_label = "Rename Folder"
- bl_description = "Rename Folder"
- bl_options = {'REGISTER'}
- folder_name: bpy.props.StringProperty(name="")
- @classmethod
- def poll(cls, context):
- return True
- def invoke(self, context, event):
- pref = get_preferences()
- selected_folder_index = pref.folders
- folder_name = get_folder_name(selected_folder_index)
- self.folder_name = folder_name
- wm = context.window_manager
- return wm.invoke_props_dialog(self, width=300)
- def draw(self, context):
- layout = self.layout
- row = layout.row()
- left, right = get_two_column(self, row)
- left.label(text="New Folder Name:")
- right.prop(self, "folder_name")
- def execute(self, context):
- pref = get_preferences()
- if folder_name_exist(self.folder_name):
- bpy.ops.nodekit.show_message(
- message="The folder name exists, please choose another name and try again.")
- return {'FINISHED'}
- selected_folder_index = pref.folders
- new_folder_name = self.folder_name
- print("index: ", selected_folder_index,
- " new folder name:", new_folder_name)
- rename_folder(selected_folder_index, new_folder_name)
- save_pref()
- redraw_all()
- return {'FINISHED'}
- def rename_folder(selected_folder_index, new_folder_name):
- pref = get_preferences()
- folder_presets = pref.folders_presets
- for index, folder_preset in enumerate(folder_presets):
- if index == int(selected_folder_index):
- folder_preset.folder_name = new_folder_name
- def get_folder_name(selected_folder_index):
- pref = get_preferences()
- folder_presets = pref.folders_presets
- for index, folder_preset in enumerate(folder_presets):
- if index == int(selected_folder_index):
- return folder_preset.folder_name
- return ""
- #--------formatting functions for panels
- def get_two_column(self, row, factor=0.5):
- splitrow = layout_split(row, factor=factor)
- left = splitrow.column()
- left.alignment = "RIGHT"
- right = splitrow.column()
- right.alignment = "LEFT"
- return (left, right)
- def layout_split(layout, factor=0.0, align=False):
- """Intermediate method for pre and post blender 2.8 split UI function"""
- if not hasattr(bpy.app, "version") or bpy.app.version < (2, 80):
- return layout.split(percentage=factor, align=align)
- return layout.split(factor=factor, align=align)
- #--------end formatting functions for panels
- #remove preset button
- class SAVEUESHADERSCRIPT_OT_remove_preset(bpy.types.Operator):
- bl_idname = "saveueshaderscript.remove_preset"
- bl_label = "Do you really want to remove selected preset?"
- bl_description = "Remove Preset"
- bl_options = {'REGISTER'}
- @classmethod
- def poll(cls, context):
- return True
- def execute(self, context):
- selected_folder_presets = get_selected_folder_presets()
- index = selected_folder_presets.preset_index
- if index < 0:
- return {'FINISHED'}
- folder_name = get_selected_folder_name()
- preset_name = get_selected_preset_name()
- selected_folder_presets.presets.remove(index)
- selected_folder_presets.preset_index = len(
- selected_folder_presets.presets) - 1
- #remove_preset_from_10_last_used(folder_name, preset_name)
- #update_10_most_used_presets()
- save_pref()
- redraw_all()
- return {'FINISHED'}
- def invoke(self, context, event):
- return context.window_manager.invoke_confirm(self, event)
- def get_selected_folder_name():
- pref = get_preferences()
- if pref.folders == "" or pref.folders == None:
- return None
- index = int(pref.folders)
- return pref.folders_presets[index].folder_name
- def get_selected_preset_name():
- return get_selected_preset().name
- def get_selected_preset():
- selected_folder_presets = get_selected_folder_presets()
- if selected_folder_presets.preset_index < 0 or \
- len(selected_folder_presets.presets) <= 0:
- return None
- return selected_folder_presets.presets[selected_folder_presets.preset_index]
- #----------------------------------CONVERT TO JSON AND SAVE PRESETS RELATED CODE
- def nodes_dict_to_json(nodes_dict):
- try:
- JSON = dict_to_string(nodes_dict)
- except Exception as e:
- report_error(self, str(e))
- return JSON
- def json_to_nodes_dict(JSON):
- try:
- nodes_dict = json_to_dict(JSON)
- except Exception as e:
- report_error(self, str(e))
- return nodes_dict
- def json_to_dict(json_string):
- """Convert JSON string into a Python dictionary"""
- return json.loads(json_string)
- def dict_to_string(d):
- """Convert a Python dictionary to JSON string"""
- return json.dumps(d, indent=4)
- DEFAULT_JSON_FILE = "ue_shader_script_default_json"
- def get_default_json_path():
- home = os.path.expanduser("~")
- full_path = os.path.join(home, DEFAULT_JSON_FILE)
- #debug
- #print("full_path: ", full_path)
- return full_path
- def import_default_json():
- """Use this to import default json file in ~/node_kit_default_json"""
- full_path = get_default_json_path()
- try:
- with open(full_path) as f:
- json_string = f.read()
- json_string_to_presets(json_string, skip_autosave=True)
- except IOError:
- print(full_path, ": file not accessible")
- _10_LAST_USED_PRESETS_KEY = "10_last_used_presets"
- USED_KEY = "_used"
- def base64encode(s):
- return base64.b64encode(s.encode("utf-8")).decode("utf-8")
- def base64decode(s):
- return base64.b64decode(s.encode("utf-8")).decode("utf-8")
- def json_string_to_presets(json_string, skip_autosave=False):
- dict = json_to_dict(json_string)
- #debug
- #print("dict: ", dict)
- pref = get_preferences()
- folders_presets = pref.folders_presets
- folders_presets.clear()
- for key in dict:
- if key == _10_LAST_USED_PRESETS_KEY:
- presets_list = dict[key]
- import_10_last_used_presets(presets_list)
- continue
- presets = dict[key]
- new_folder = folders_presets.add()
- new_folder.folder_name = key
- #returns instance of FolderPresetsCollection
- new_folder_presets = new_folder.presets
- #presets is a dict of all presets in a single folder
- #print("presets:", presets)
- for preset in presets:
- #debug
- #print("preset:", preset)
- #print("\n\n")
- #preset is an individual preset such as test1
- #print("preset:", preset)
- #.add is method to add an instance of a class
- new_preset = new_folder_presets.add()
- new_preset.used = 0
- for k in preset:
- #if the key is _used it's going to be something like 1
- #this is when it was last used
- if k == USED_KEY:
- new_preset.used = preset[USED_KEY]
- else:
- #otherwise if it is not the used key we shall decode it
- #with base64
- content = base64decode(preset[k])
- new_preset.name = k
- new_preset.content = content
- #debug
- #print("preset[k]", preset[k])
- import_10_most_used_presets()
- save_pref(skip_autosave=skip_autosave)
- def import_10_last_used_presets(presets_list):
- pref = get_preferences()
- pref.ten_last_used_presets.clear()
- for preset in presets_list:
- new_preset = pref.ten_last_used_presets.add()
- new_preset.folder_name = preset[0]
- new_preset.preset_name = preset[1]
- def import_10_most_used_presets():
- update_10_most_used_presets()
- def is_preset_added(folder_name, preset_name):
- preferences = get_preferences()
- for p in preferences.ten_most_used_presets:
- if p.folder_name == folder_name and \
- p.preset_name == preset_name:
- return True
- return False
- def update_10_most_used_presets():
- pref = get_preferences()
- ten_most_used_presets = pref.ten_most_used_presets
- ten_most_used_presets.clear()
- folders = pref.folders_presets
- presets = []
- for folder in folders:
- for preset in folder.presets:
- if hasattr(preset, "used"):
- presets.append((folder.folder_name, preset.name, preset.used))
- def sort_fun(ele):
- return ele[2]
- presets = sorted(presets, key=sort_fun, reverse=True)
- l = len(presets)
- if l > 10:
- l = 10
- for i in range(0, l):
- preset = presets[i]
- if not is_preset_added(preset[0], preset[1]):
- new_preset = pref.ten_most_used_presets.add()
- new_preset.folder_name = preset[0]
- new_preset.preset_name = preset[1]
- if len(pref.ten_most_used_presets) >= 0:
- pref.ten_most_used_presets_index = 0
- def get_selected_folder_presets(isOverridePackage = False):
- #getting preferences for add on
- #preferences are anything the add on needs
- #to store permanently for later use
- #such as the JSON strings associated with each preset
- pref = get_preferences(isOverridePackage)
- #print("pref: ", pref)
- if pref.folders == "" or pref.folders == None:
- return None
- #print("pref.folders:", pref.folders)
- index = int(pref.folders)
- #print("index:", index)
- #print("dir(pref)", dir(pref))
- #debug
- #print("pref.folders_presets:", pref.folders_presets)
- #print("dir(pref.folders_presets[index]):", dir(pref.folders_presets[index]))
- return pref.folders_presets[index]
- class PresetCollection(PropertyGroup):
- name: StringProperty()
- content: StringProperty()
- used: IntProperty(default=0)
- class FolderPresetsCollection(PropertyGroup):
- folder_name: StringProperty()
- presets: CollectionProperty(type=PresetCollection)
- preset_index: IntProperty()
- class TenLastUsedPresetsCollection(PropertyGroup):
- folder_name: StringProperty()
- preset_name: StringProperty()
- class TenMostUsedPresetsCollection(PropertyGroup):
- folder_name: StringProperty()
- preset_name: StringProperty()
- def get_folders_items(self, context):
- pref = get_preferences()
- enum_types_items = []
- for index, folder_preset in enumerate(pref.folders_presets):
- enum_types_items.append(("%d" % index, folder_preset.folder_name, ""))
- return tuple(enum_types_items)
- def update_folders(self, context):
- redraw_all()
- class SavePreferences(bpy.types.AddonPreferences):
- bl_idname = __package__
- folders_presets: CollectionProperty(type=FolderPresetsCollection)
- folders: EnumProperty(name="",
- description="",
- items=get_folders_items,
- update=update_folders,
- default=None)
- ten_last_used_presets: CollectionProperty(type=TenLastUsedPresetsCollection)
- ten_last_used_presets_index: IntProperty()
- ten_most_used_presets: CollectionProperty(type=TenMostUsedPresetsCollection)
- ten_most_used_presets_index: IntProperty()
- # `presets` and `preset_index` are not used
- # `presets` is for the old version
- presets: CollectionProperty(type=PresetCollection)
- preset_index: IntProperty()
- def draw(self, context):
- wm = context.window_manager
- layout = self.layout
- #by default use __package__ however, if the function is being imported elsewhere
- #the __package__ variable does not work, allow another paramter to override with the UEShaderScript string
- def get_preferences(isOverridePackage = False, package=__package__, context=None):
- """Multi version compatibility for getting preferences"""
- if isOverridePackage:
- #debug
- #print("Override Package!!!!!")
- package = "UEShaderScript"
- if not context:
- context = bpy.context
- prefs = None
- #blender 2.7x
- if hasattr(context, "user_preferences"):
- prefs = context.user_preferences.addons.get(package, None)
- #blender 2.8+
- elif hasattr(context, "preferences"):
- #debug
- #print("package:", package)
- #__package__ is the name of the folder that the add on is in
- #it would be UEShaderScript for this one
- #This line here gets a prefs struct which is not what we
- #about we want the attribute preferences inside the prefs struct
- prefs = context.preferences.addons.get(package, None)
- #debug
- #print("dir(prefs):", dir(prefs))
- if prefs:
- #debug
- #print("prefs.preferences", prefs.preferences)
- #print("dir(prefs.preferences)", dir(prefs.preferences))
- #print("\n\n")
- #this prefs.preferences here
- #prefs.preferences refers to the subclass/child class SavePreferences
- #SavePreferences is a child class and builds on bpy.types.AddonPreferences
- #adding extra attributes
- #bpy.types.AddonPreferences is when you need to
- #save something in the domain of your addon, not in the scene of your blend file.
- return prefs.preferences
- else:
- raise Exception("Could not fetch user preferences")
- def save_pref(skip_autosave=False):
- #save_userpref will not work for
- #import_default_json()
- #when the file starts as blender cannot allow
- #to access ops when file starts
- bpy.ops.wm.save_userpref()
- if not skip_autosave:
- export_to_default_json()
- def redraw_all():
- """For redraw Slash panel"""
- for area in bpy.context.window.screen.areas:
- for region in area.regions:
- if region.type == "UI":
- region.tag_redraw()
- def export_to_default_json():
- """Use this to export to default json file in ~/node_kit_default_json"""
- full_path = get_default_json_path()
- f = open(full_path, "w+")
- json_string = presets_to_json_string()
- f.write(json_string)
- f.close()
- def presets_to_json_string():
- JSON = {}
- pref = get_preferences()
- folders_presets = pref.folders_presets
- for folder_presets in folders_presets:
- presets = []
- for preset in folder_presets.presets:
- preset_dict = {}
- preset_dict[preset.name] = base64encode(preset.content)
- if hasattr(preset, "used"):
- preset_dict[USED_KEY] = preset.used
- presets.append(preset_dict)
- JSON[folder_presets.folder_name] = presets
- #JSON[_10_LAST_USED_PRESETS_KEY] = _10_last_used_presets_to_json()
- return dict_to_string(JSON)
- #-----------------------------------message and console log related code
- VERBOSE = True
- def log(msg):
- if VERBOSE:
- print("[UE Shader]", msg)
- def report_error(self, message):
- full_message = "%s\n\nPlease report to the add-on developer with this error message (A screenshot is awesome)" % message
- self.report({"ERROR"}, full_message)
- class ShowMessageOperator(bpy.types.Operator):
- bl_idname = "ueshaderscript.show_message"
- bl_label = ""
- bl_description = "Show Message for Node Kit"
- bl_options = {'REGISTER'}
- message: bpy.props.StringProperty(default="Message Dummy")
- called: bpy.props.BoolProperty(default=False)
- @classmethod
- def poll(cls, context):
- return True
- def invoke(self, context, event):
- wm = context.window_manager
- return wm.invoke_props_dialog(self, width=300)
- def draw(self, context):
- layout = self.layout
- row = layout.row()
- row.label(text=self.message)
- def execute(self, context):
- if not self.called:
- wm = context.window_manager
- self.called = True
- return wm.invoke_props_dialog(self, width=400)
- return {'FINISHED'}
- class SAVEUESHADERSCRIPT_OT_load_image_texture(bpy.types.Operator):
- #default name is for Roman Noodles label
- #text is changed for other Shader Map Types
- bl_label = "Load Image Texture"
- bl_idname = "saveueshaderscript.loadimagetexture_operator"
- def execute(self, context):
- scene = context.scene
- #allow access to user inputted properties through pointer
- #to properties
- savetool = scene.save_tool
- if(savetool.is_add_img_textures == True):
- node_to_load = bpy.context.active_object.active_material.node_tree.nodes["Diffuse Node"]
- node_to_load.image = bpy.data.images.load("C:\Seabrook\Dwight Recolor\Game\Characters\Campers\Dwight\Textures\Outfit01\T_DFHair01_BC.tga")
- return {"FINISHED"}
- classes = [SaveProperties, PresetCollection,
- FolderPresetsCollection, TenLastUsedPresetsCollection, TenMostUsedPresetsCollection, SavePreferences,
- SHADER_PRESETS_UL_items, SAVEUESHADERSCRIPT_PT_main_panel,
- Shader_ShowFolderActionsOperator, SHADER_MT_FolderActionsMenu, Shader_NewFolderOperator,
- Shader_RemoveFolderOperator, Shader_RenameFolderOperator, SAVEUESHADERSCRIPT_OT_remove_preset, ShowMessageOperator,
- SAVEUESHADERSCRIPT_OT_save_shader_map, SAVEUESHADERSCRIPT_OT_load_image_texture]
- def register():
- for cls in classes:
- bpy.utils.register_class(cls)
- #register save_tool as a type which has all
- #the user input properties from the properties class
- bpy.types.Scene.save_tool = bpy.props.PointerProperty(type = SaveProperties)
- def unregister():
- for cls in classes:
- bpy.utils.unregister_class(cls)
- #unregister save_tool as a type
- del bpy.types.Scene.save_tool
- if __name__ == "__main__":
- register()
Add Comment
Please, Sign In to add comment