Guest User

save_shader_map.py

a guest
May 2nd, 2021
120
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 50.69 KB | None | 0 0
  1. import bpy
  2.  
  3. import base64
  4.  
  5. import json
  6.  
  7. import os
  8.  
  9. from bpy.props import (StringProperty,
  10.                        BoolProperty,
  11.                        IntProperty,
  12.                        FloatProperty,
  13.                        FloatVectorProperty,
  14.                        EnumProperty,
  15.                        PointerProperty,
  16.                        CollectionProperty
  17.                        )
  18.  
  19. from bpy.types import (Panel,
  20.                        Operator,
  21.                        AddonPreferences,
  22.                        PropertyGroup,
  23.                        UIList
  24.                        )
  25.  
  26.  
  27.  
  28.  
  29.  
  30. #import libraries required for types
  31. #for dict to nodes
  32. from mathutils import (Vector, Euler, Color)
  33. from bpy.types import Text
  34. from bpy.types import Object
  35. from bpy.types import ColorMapping, CurveMapping, ColorRamp
  36. from bpy.types import Image, ImageUser, ParticleSystem
  37.  
  38.  
  39. #import creating panel for panel related code
  40. #specifically operators to create ui lists
  41. #from . import panel
  42. #from . import load_save_common
  43.  
  44. #from .load_save_common import nodes_dict_to_json, get_selected_folder_presets, save_pref, redraw_all
  45.  
  46.  
  47. from bpy.types import (
  48.     NodeSocketBool, NodeSocketColor, NodeSocketFloat, NodeSocketFloatAngle,
  49.     NodeSocketFloatFactor, NodeSocketFloatPercentage, NodeSocketFloatTime,
  50.     NodeSocketFloatUnsigned, NodeSocketInt, NodeSocketIntFactor, NodeSocketIntPercentage,
  51.     NodeSocketIntUnsigned, NodeSocketShader, NodeSocketString, NodeSocketVector,
  52.     NodeSocketVectorAcceleration, NodeSocketVectorDirection, NodeSocketVectorEuler,
  53.     NodeSocketVectorTranslation, NodeSocketVectorVelocity, NodeSocketVectorXYZ, NodeSocketVirtual
  54. )
  55.  
  56.  
  57. #define globals
  58. SHADER_EDITOR = "ShaderNodeTree"
  59. COMPOSITOR_EDITOR = "CompositorNodeTree"
  60. ANIMATION_NODE_EDITOR = "an_AnimationNodeTree"
  61.  
  62.  
  63.  
  64. class SAVEUESHADERSCRIPT_OT_save_shader_map(bpy.types.Operator):
  65.     #default name is for Roman Noodles label
  66.     #text is changed for other Shader Map Types
  67.     bl_label = "Save Shader Map"
  68.     bl_idname = "saveueshaderscript.saveshadermap_operator"
  69.     def execute(self, context):
  70.  
  71.  
  72.         #store active/selected scene to variable
  73.         scene = context.scene
  74.        
  75.         #allow access to user inputted properties through pointer
  76.         #to properties
  77.         savetool = scene.save_tool
  78.  
  79.         if preset_name_exist(savetool.cust_map_name):
  80.             bpy.ops.ueshaderscript.show_message(
  81.             message="The nodes preset name exists, please choose another name and try again.")
  82.             return {'FINISHED'}
  83.  
  84.         area = context.area
  85.         editor_type = area.ui_type
  86.        
  87.         scene = context.scene
  88.         #allow access to user inputted properties through pointer
  89.         #to properties
  90.         savetool = scene.save_tool
  91.        
  92.         #if(savetool.is_add_img_textures == True):
  93.         #    print("hey")
  94.        
  95.         node_editor = area
  96.        
  97.         tree = node_editor.spaces[0].node_tree
  98.        
  99.        
  100.         #debug
  101.         #print("context.area.spaces[0].node_tree: ", tree)
  102.         #print("tree.nodes[0].bl_idname", tree.nodes[0].bl_idname)
  103.        
  104.         nodes_list, links_list, img_textures_list = nodes_to_dict(tree, savetool)
  105.        
  106.         #img_textures_list will be blank {} if add_image_textures is False or if no image textures should be loaded
  107.         nodes_dict = {"nodes_list": nodes_list, "links_list": links_list, "img_textures_list": img_textures_list}
  108.         nodes_dict["editor_type"] = editor_type
  109.         shader_type = area.spaces[0].shader_type
  110.         nodes_dict["shader_type"] = shader_type
  111.         # Debug:
  112.         # print(nodes_dict)
  113.         JSON = nodes_dict_to_json(nodes_dict)
  114.         selected_folder_presets = get_selected_folder_presets()
  115.         presets = selected_folder_presets.presets
  116.         new_preset = presets.add()
  117.         #debug
  118.         #print("savetool.cust_map_name:", savetool.cust_map_name)
  119.         new_preset.name = savetool.cust_map_name
  120.         new_preset.content = JSON
  121.         selected_folder_presets.preset_index = len(presets) - 1
  122.         save_pref()
  123.         redraw_all()
  124.        
  125.         return {"FINISHED"}
  126.  
  127.  
  128.  
  129.  
  130.  
  131. #------------------------NODES TO DICTIONARY RELATED CODE
  132.  
  133.  
  134.  
  135.  
  136. def nodes_to_dict(tree, savetool):
  137.     """ Actually, we construct and return a List """
  138.     if tree is not None:
  139.         nodes = tree.nodes
  140.     else:
  141.         nodes = []
  142.     nodes_list = []
  143.     for node in nodes:
  144.         #copy default node properties to node_dict
  145.         node_dict = {"node_name": node.bl_idname}
  146.         node_dict["x"] = node.location.x
  147.         node_dict["y"] = node.location.y
  148.         node_dict["width"] = node.width
  149.         node_dict["width_hidden"] = node.width_hidden
  150.         node_dict["height"] = node.height
  151.  
  152.         #debug
  153.         #print("node dict after basic node properties:", node_dict)
  154.        
  155.         #copy node parents means which frames
  156.         #each node belongs to to node_dict
  157.         parent = node.parent
  158.         if parent == None:
  159.             node_dict["parent"] = "None"
  160.         else:
  161.             parent_index = get_node_index(nodes, parent)
  162.             node_dict["parent"] = parent_index  
  163.        
  164.         #copy node attributes to node_dict
  165.         #dir() returns all properties and methods of
  166.         #the specified object, without the values.
  167.         attrs = dir(node)
  168.         attrs_list = []
  169.        
  170.         for attr in attrs:
  171.             attr_dict = attr_to_dict(node, attr)
  172.             if attr_dict["type_name"] == "NoneType" \
  173.                     or attr_dict["type_name"] == "Not Handle Type":
  174.                 continue
  175.             attrs_list.append(attr_dict)
  176.         node_dict["attrs"] = attrs_list
  177.         #debug
  178.         #print("node dict after attrs:", node_dict)
  179.        
  180.         #copy node inputs and output values and types to node_dict
  181.         inputs = []
  182.         for input in node.inputs:
  183.             input_dict = socket_to_dict_input(input)
  184.             inputs.append(input_dict)
  185.         if node.bl_idname != "CompositorNodeOutputFile":
  186.             node_dict["inputs"] = inputs
  187.         else:
  188.             node_dict["inputs"] = []
  189.         outputs = []
  190.         for output in node.outputs:
  191.             output_dict = socket_to_dict_output(output)
  192.             outputs.append(output_dict)
  193.         node_dict["outputs"] = outputs
  194.         #debug
  195.         #print("node dict after inputs outputs:", node_dict)
  196.        
  197.  
  198.         # Special handling ShaderNodeGroup
  199.         # this is for recording node groups
  200.         if node.bl_idname == "ShaderNodeGroup":
  201.             node_dict["node_tree"] = nodes_to_dict_handle_shader_node_group(
  202.                 node, savetool)
  203.  
  204.         ### ignore as we only need to use the Shader Editor none of the other editors
  205. #        if node.bl_idname == "CompositorNodeGroup":
  206. #            node_dict["node_tree"] = nodes_to_dict_handle_compositor_node_group(
  207. #                node)
  208. #        if node.bl_idname == "CompositorNodeOutputFile":
  209. #            # We just handle OutputFile->file_slots, does not handle layer_slots (Waiting for bugs)
  210. #            node_dict["file_slots"] = nodes_to_dict_handle_compositor_node_output_file(
  211. #                node)
  212. #            # We treat all file slot's file format as the same of OutputFile->format->file_format
  213. #            node_dict["file_format"] = node.format.file_format
  214.  
  215.         nodes_list.append(node_dict)
  216.     #debug
  217.     #print("full node_dict:", node_dict)
  218.     #print("\n\nfull nodes_list", nodes_list)
  219.     #print("\n\nfull nodes_list length", len(nodes_list))
  220.    
  221.     #record all links in a python list
  222.     links_list = links_to_list(tree)
  223.    
  224.     #debug
  225.     #print("\n\nlinks_list", links_list)
  226.    
  227.     #if the option to load image textures is true then record
  228.     #what image texture suffixes and node names the user has written
  229.     if(savetool.is_add_img_textures == True):
  230.         img_textures_list = textures_to_list(savetool)
  231.     else:
  232.         img_textures_list = []
  233.    
  234.     return (nodes_list, links_list, img_textures_list)
  235.  
  236. def socket_to_dict_output(output):
  237.     return socket_to_dict_input(output)
  238.  
  239. def socket_to_dict_input(input):
  240.     t = type(input)
  241.     dict = {}
  242.     if t == NodeSocketColor:
  243.         dict["type_name"] = "NodeSocketColor"
  244.         dict["value"] = list(input.default_value)
  245.     elif t == NodeSocketFloatFactor:
  246.         dict["type_name"] = "NodeSocketFloatFactor"
  247.         dict["value"] = input.default_value
  248.     elif t == NodeSocketVector or t == NodeSocketVectorDirection or \
  249.             t == NodeSocketVectorEuler or t == NodeSocketVectorTranslation or \
  250.             t == NodeSocketVectorVelocity or t == NodeSocketVectorXYZ:
  251.         dict["type_name"] = "NodeSocketVector"
  252.         dict["value"] = list(input.default_value)
  253.     elif t == NodeSocketBool:
  254.         dict["type_name"] = "NodeSocketBool"
  255.         if input.default_value == True:
  256.             value = 1
  257.         else:
  258.             value = 0
  259.         dict["value"] = value
  260.     elif t == NodeSocketFloat:
  261.         dict["type_name"] = "NodeSocketFloat"
  262.         dict["value"] = input.default_value
  263.     elif t == NodeSocketFloatAngle:
  264.         dict["type_name"] = "NodeSocketFloatAngle"
  265.         dict["value"] = input.default_value
  266.     elif t == NodeSocketFloatPercentage:
  267.         dict["type_name"] = "NodeSocketFloatPercentage"
  268.         dict["value"] = input.default_value
  269.     elif t == NodeSocketFloatTime:
  270.         dict["type_name"] = "NodeSocketFloatTime"
  271.         dict["value"] = input.default_value
  272.     elif t == NodeSocketFloatUnsigned:
  273.         dict["type_name"] = "NodeSocketFloatUnsigned"
  274.         dict["value"] = input.default_value
  275.     elif t == NodeSocketInt:
  276.         dict["type_name"] = "NodeSocketInt"
  277.         dict["value"] = input.default_value
  278.     elif t == NodeSocketIntFactor:
  279.         dict["type_name"] = "NodeSocketIntFactor"
  280.         dict["value"] = input.default_value
  281.     elif t == NodeSocketIntPercentage:
  282.         dict["type_name"] = "NodeSocketIntPercentage"
  283.         dict["value"] = input.default_value
  284.     elif t == NodeSocketIntUnsigned:
  285.         dict["type_name"] = "NodeSocketIntUnsigned"
  286.         dict["value"] = input.default_value
  287.     elif t == NodeSocketString:
  288.         dict["type_name"] = "NodeSocketString"
  289.         dict["value"] = input.default_value
  290.     elif t == NodeSocketVector:
  291.         dict["type_name"] = "NodeSocketVector"
  292.         dict["value"] = list(input.default_value)
  293.     elif t == NodeSocketVectorAcceleration:
  294.         dict["type_name"] = "NodeSocketVectorAcceleration"
  295.         dict["value"] = list(input.default_value)
  296.     elif t == NodeSocketShader:
  297.         dict["type_name"] = "NodeSocketShader"
  298.     elif t == NodeSocketVirtual:
  299.         dict["type_name"] = "NodeSocketVirtual"
  300.     else:
  301.         log("socket_to_dict_input() can not handle input type: %s" % t)
  302.         raise ValueError(
  303.             "socket_to_dict_input() can not handle input type: %s" % t)
  304.     dict["name"] = input.name
  305.     return dict
  306.  
  307.  
  308.  
  309. def get_node_index(nodes, node):
  310.     index = 0
  311.     for n in nodes:
  312.         if n == node:
  313.             return index
  314.         index += 1
  315.     return "None"
  316.  
  317. def attr_to_dict(node, attr):
  318.     dict = {}
  319.     if not is_default_attr(attr):
  320.         t = type(getattr(node, attr))
  321.         v = getattr(node, attr)
  322.         if v == None:
  323.             dict["type_name"] = "NoneType"
  324.         elif t == str:
  325.             dict["type_name"] = "str"
  326.             dict["value"] = getattr(node, attr)
  327.         elif t == int:
  328.             dict["type_name"] = "int"
  329.             dict["value"] = getattr(node, attr)
  330.         elif t == float:
  331.             dict["type_name"] = "float"
  332.             dict["value"] = getattr(node, attr)
  333.         elif t == bool:
  334.             dict["type_name"] = "bool"
  335.             if getattr(node, attr) == True:
  336.                 value = 1
  337.             else:
  338.                 value = 0
  339.             dict["value"] = value
  340.         elif t == list:
  341.             dict["type_name"] = "list"
  342.             dict["value"] = getattr(node, attr)
  343.         elif t == tuple:
  344.             dict["type_name"] = "tuple"
  345.             dict["value"] = list(getattr(node, attr))
  346.         elif t == Vector:
  347.             dict["type_name"] = "Vector"
  348.             dict["value"] = list(getattr(node, attr).to_tuple())
  349.         elif t == Euler:
  350.             dict["type_name"] = "Euler"
  351.             value = getattr(node, attr)
  352.             dict["value"] = list(value[:])
  353.         elif t == Text:
  354.             dict["type_name"] = "Text"
  355.             value = getattr(node, attr)
  356.             dict["value"] = value.name
  357.         elif t == ColorMapping:
  358.             dict["type_name"] = "NoneType"
  359.         elif t == Image:
  360.             dict["type_name"] = "Image"
  361.             image = getattr(node, attr)
  362.             dict["value"] = image.name
  363.             dict["image_filepath"] = image.filepath
  364.             dict["image_source"] = image.source
  365.         elif t == Object:
  366.             dict["type_name"] = "Object"
  367.             value = getattr(node, attr)
  368.             dict["value"] = value.name
  369.         elif t == ImageUser:
  370.             dict["type_name"] = "ImageUser"
  371.             image_user = getattr(node, attr)
  372.             value = get_value_from_ImageUser(image_user)
  373.             dict["value"] = value
  374.         elif t == ParticleSystem:
  375.             dict["type_name"] = "ParticleSystem"
  376.             value = getattr(node, attr)
  377.             dict["value"] = value.name
  378.             dict["object_name"] = bpy.context.object.name
  379.         elif t == CurveMapping:
  380.             dict["type_name"] = "CurveMapping"
  381.             dict["value"] = get_value_from_CurveMapping(getattr(node, attr))
  382.         elif t == Color:
  383.             dict["type_name"] = "Color"
  384.             value = getattr(node, attr)
  385.             dict["value"] = list(value)
  386.         elif t == ColorRamp:
  387.             dict["type_name"] = "ColorRamp"
  388.             color_ramp = getattr(node, attr)
  389.             color_ramp_dict = {}
  390.             color_ramp_dict["color_mode"] = color_ramp.color_mode
  391.             color_ramp_dict["hue_interpolation"] = color_ramp.hue_interpolation
  392.             color_ramp_dict["interpolation"] = color_ramp.interpolation
  393.             elements = []
  394.             for ele in color_ramp.elements:
  395.                 color_ramp_element = {}
  396.                 color_ramp_element["alpha"] = ele.alpha
  397.                 color_ramp_element["color"] = list(ele.color)
  398.                 color_ramp_element["position"] = ele.position
  399.                 elements.append(color_ramp_element)
  400.             color_ramp_dict["elements"] = elements
  401.             dict["value"] = color_ramp_dict
  402.         else:
  403.             dict["type_name"] = "NoneType"
  404.             log("attr_to_dict() can not handle attr type: %s attr:%s" % (t, attr))
  405.             # We don't raise error because some type no need to handle, and
  406.             # it works well for restore
  407.             #raise ValueError("attr_to_dict() can not handle attr type: %s" % t)
  408.     else:
  409.         dict["type_name"] = "Not Handle Type"
  410.     dict["attr_name"] = attr
  411.     return dict
  412.  
  413.  
  414.  
  415. def get_value_from_CurveMapping(curve_mapping):
  416.     def get_curve_points(curve):
  417.         ret = []
  418.         for p in curve.points:
  419.             point_dict = {}
  420.             point_dict["handle_type"] = p.handle_type
  421.             point_dict["location"] = list(p.location)
  422.             ret.append(point_dict)
  423.         return ret
  424.  
  425.     dict = {}
  426.     dict["black_level"] = list(curve_mapping.black_level)
  427.     dict["clip_max_x"] = curve_mapping.clip_max_x
  428.     dict["clip_max_y"] = curve_mapping.clip_max_y
  429.     dict["clip_min_x"] = curve_mapping.clip_min_x
  430.     dict["clip_min_y"] = curve_mapping.clip_min_y
  431.     dict["tone"] = curve_mapping.tone
  432.     dict["use_clip"] = curve_mapping.use_clip
  433.     dict["white_level"] = list(curve_mapping.white_level)
  434.     curves = []
  435.     for curve in curve_mapping.curves:
  436.         curve_dict = {}
  437.         #curve_dict["extend"] = curve.extend
  438.         set_attr_if_exist_for_dict(curve, "extend", curve_dict)
  439.         curve_dict["points"] = get_curve_points(curve)
  440.         curves.append(curve_dict)
  441.     dict["curves"] = curves
  442.     return dict
  443.  
  444. def set_attr_if_exist_for_dict(obj, attr, dict):
  445.     if hasattr(obj, attr):
  446.         dict[attr] = getattr(obj, attr)
  447.     else:
  448.         dict[attr] = "None"
  449.  
  450. def get_value_from_ImageUser(image_user):
  451.     dict = {}
  452.     dict["frame_current"] = image_user.frame_current
  453.     dict["frame_duration"] = image_user.frame_duration
  454.     dict["frame_offset"] = image_user.frame_offset
  455.     dict["frame_start"] = image_user.frame_start
  456.     dict["use_cyclic"] = image_user.use_cyclic
  457.     dict["use_auto_refresh"] = image_user.use_auto_refresh
  458.     return dict
  459.  
  460.  
  461. def get_value_from_ColorMapping(color_mapping):
  462.     dict = {}
  463.     dict["blend_color"] = list(color_mapping.blend_color)
  464.     dict["blend_factor"] = color_mapping.blend_factor
  465.     dict["blend_type"] = color_mapping.blend_type
  466.     dict["brightness"] = color_mapping.brightness
  467.     dict["contrast"] = color_mapping.contrast
  468.     dict["saturation"] = color_mapping.saturation
  469.     dict["use_color_ramp"] = color_mapping.use_color_ramp
  470.     return dict
  471.  
  472.  
  473.  
  474.  
  475. def is_default_attr(attr):
  476.     if attr in get_default_attrs():
  477.         return True
  478.     else:
  479.         return False
  480.  
  481.  
  482. def get_default_attrs():
  483.     # `codeEffects` is a value which can not be JSONify in KDTree (Animation Node)
  484.     return ['__doc__', '__module__', '__slots__', 'bl_description',
  485.             'bl_height_default',
  486.             'bl_height_max', 'bl_height_min', 'bl_icon', 'bl_idname',
  487.             'bl_label', 'bl_rna', 'bl_static_type', 'bl_width_default',
  488.             'bl_width_max', 'bl_width_min', 'dimensions',
  489.             'draw_buttons', 'draw_buttons_ext', 'height',
  490.             'input_template', 'inputs', 'internal_links', 'is_active_output',
  491.             'is_registered_node_type', 'location', 'mute',
  492.             'output_template', 'outputs', 'parent', 'poll', 'poll_instance',
  493.             'rna_type', 'select', 'show_options', 'show_preview',
  494.             'show_texture', 'socket_value_update', 'type', 'update',
  495.             'width', 'width_hidden',
  496.             'codeEffects'
  497.             ]
  498.  
  499. def nodes_to_dict_handle_shader_node_group(node, savetool):
  500.     node_tree_of_node_group = node.node_tree
  501.     inputs = node_tree_of_node_group.inputs
  502.     interface_inputs_list = interface_inputs_to_list(inputs)
  503.     node_tree_dict = {}
  504.     node_tree_dict["interface_inputs"] = interface_inputs_list
  505.     node_tree_dict["name"] = node_tree_of_node_group.name
  506.     #img_textures list is unused here but that is intentional
  507.     nodes_list, links_list, img_textures_list = nodes_to_dict(node_tree_of_node_group, savetool)
  508.     node_tree_dict["nodes_list"] = nodes_list
  509.     node_tree_dict["links_list"] = links_list
  510.     return node_tree_dict
  511.  
  512. def interface_inputs_to_list(inputs):
  513.     inputs_list = []
  514.     for input in inputs:
  515.         if hasattr(input, "min_value"):
  516.             min_value = input.min_value
  517.         else:
  518.             min_value = "None"
  519.         if hasattr(input, "max_value"):
  520.             max_value = input.max_value
  521.         else:
  522.             max_value = "None"
  523.         dict = {"min_value": min_value, "max_value": max_value}
  524.         inputs_list.append(dict)
  525.     return inputs_list
  526.  
  527. def links_to_list(tree):
  528.     if tree is None:
  529.         links = []
  530.         nodes = []
  531.     else:
  532.         links = tree.links
  533.         nodes = tree.nodes
  534.     links_list = []
  535.     for link in links:
  536.         link_dict = {}
  537.         for node_index, n in enumerate(nodes):
  538.             inputs = n.inputs
  539.             outputs = n.outputs
  540.             for index, o in enumerate(outputs):
  541.                 if link.from_socket == o:
  542.                     link_dict["from_node_index"] = node_index
  543.                     link_dict["from_socket_index"] = index
  544.                     link_dict["from_socket_name"] = o.name
  545.             for index, i in enumerate(inputs):
  546.                 if link.to_socket == i:
  547.                     link_dict["to_node_index"] = node_index
  548.                     link_dict["to_socket_index"] = index
  549.                     link_dict["to_socket_name"] = i.name
  550.         links_list.append(link_dict)
  551.     return links_list
  552.  
  553. def textures_to_list(savetool):
  554.     img_textures_list = []
  555.     suffix_node_to_list(savetool.bc_suffix, savetool.bc_suffix_node, img_textures_list, "diffuse")
  556.     suffix_node_to_list(savetool.orm_suffix, savetool.orm_suffix_node, img_textures_list, "packed_orm")
  557.     suffix_node_to_list(savetool.n_suffix, savetool.n_suffix_node,  img_textures_list, "normal")
  558.     suffix_node_to_list(savetool.m_suffix, savetool.m_suffix_node,  img_textures_list, "transparency")
  559.     suffix_node_to_list(savetool.bde_suffix, savetool.bde_suffix_node, img_textures_list, "emissions")
  560.     suffix_node_to_list(savetool.hm_suffix, savetool.hm_suffix_node, img_textures_list, "height")
  561.     suffix_node_to_list(savetool.skin_suffix, savetool.skin_suffix_node, img_textures_list, "skin")
  562.     suffix_node_to_list(savetool.cust1_suffix, savetool.cust1_suffix_node, img_textures_list, "cust1")
  563.     suffix_node_to_list(savetool.cust2_suffix, savetool.cust2_suffix_node, img_textures_list, "cust2")
  564.  
  565.    
  566.     #if the img_textures list is empty
  567.     #that is equivalent to having
  568.     #no image textures to load
  569.  
  570.    
  571.     #debug
  572.     #print("img_textures_dict: ", img_textures_dict)
  573.     #print("img_textures_list length: ", len(img_textures_list))
  574.    
  575.     return img_textures_list
  576.  
  577.  
  578. def suffix_node_to_list(suffix, suffix_node, img_textures_list, texture):
  579.     img_textures_dict = {}
  580.     #if both the suffix and suffix node are not empty
  581.     #record the suffix in the dictionary
  582.     if suffix != "" and suffix_node != "" :
  583.         #texture is just for debugging purposes so can check JSON file
  584.         img_textures_dict["texture"] = texture
  585.         img_textures_dict["suffix"] = suffix
  586.         img_textures_dict["suffix_node"] = suffix_node
  587.    
  588.     #if an entry was added into the img_textures_dict
  589.     #append it to the list
  590.     #otherwise don't append anything to the list
  591.     if img_textures_dict != {}:
  592.         img_textures_list.append(img_textures_dict)
  593.  
  594.  
  595.  
  596. #---------------------------Panel related code including preset and folder new , deletion, renaming
  597. #define all user input properties
  598. class SaveProperties(bpy.types.PropertyGroup):
  599.     cust_map_name: bpy.props.StringProperty(name="Name of Shader Map", description="Name of your custom shader map")
  600.     bc_suffix: bpy.props.StringProperty(name="Diffuse Suffix", description="Suffix of Diffuse", default="_BC")
  601.     bc_suffix_node: bpy.props.StringProperty(name="Diffuse Node Name", description="Diffuse image texture node name", default="Diffuse Node")
  602.     orm_suffix: bpy.props.StringProperty(name="Packed RGB ARM Suffix", description="Suffix of Packed RGB (AO, Rough, Metallic)", default="_ORM")
  603.     orm_suffix_node: bpy.props.StringProperty(name="Packed RGB Node Name", description="Packed RGB image texture node name", default="Packed RGB Node")
  604.     n_suffix: bpy.props.StringProperty(name="Normal Map Suffix", description="Suffix of Normal Map", default="_N")
  605.     n_suffix_node: bpy.props.StringProperty(name="Normal Map Node Name", description="Normal Map image texture node name", default="Normal Map Node")
  606.     m_suffix: bpy.props.StringProperty(name="Alpha Map Suffix", description="Suffix of Alpha (Transparency) Map", default="_M")
  607.     m_suffix_node: bpy.props.StringProperty(name="Alpha Map Node Name", description="Alpha Map image texture node name", default="Transparency Map Node")
  608.     bde_suffix: bpy.props.StringProperty(name="Emissions Map Suffix", description="Suffix of Emissions Map", default="_BDE")
  609.     bde_suffix_node: bpy.props.StringProperty(name="Emissions Map Node Name", description="Emissions Map image texture node name", default="Emissions Map Node")
  610.     hm_suffix: bpy.props.StringProperty(name="Height Map Suffix", description="Suffix of Height Map", default="")
  611.     hm_suffix_node: bpy.props.StringProperty(name="Height Map Node Name", description="Height Map image texture node name", default="")
  612.     skin_suffix: bpy.props.StringProperty(name="Skin Suffix", description="Suffix of Skin Texture", default="")
  613.     skin_suffix_node: bpy.props.StringProperty(name="Skin Node Name", description="Skin image texture node name", default="")
  614.     cust1_suffix: bpy.props.StringProperty(name="Custom1 Suffix", description="Suffix of Custom1 Texture", default="")
  615.     cust1_suffix_node: bpy.props.StringProperty(name="Custom1 Node Name", description="Custom1 image texture node name", default="")
  616.     cust2_suffix: bpy.props.StringProperty(name="Custom2 Suffix", description="Suffix of Custom2 Texture", default="")
  617.     cust2_suffix_node: bpy.props.StringProperty(name="Custom2 Node Name", description="Custom2 image texture node name", default="")
  618.     is_add_img_textures: bpy.props.BoolProperty(name="Load Image Textures to Shader Map", default= True)
  619.  
  620.  
  621.  
  622.  
  623. #code for drawing main panel in the 3D View
  624. class SAVEUESHADERSCRIPT_PT_main_panel(bpy.types.Panel):
  625.     bl_label = "Save UE Shaders"
  626.     bl_idname = "SAVEUESHADERSCRIPT_PT_main_panel"
  627.     bl_space_type = 'NODE_EDITOR'
  628.     bl_region_type = 'UI'
  629.     bl_category = "Save UE Shaders"
  630.  
  631.     def draw(self, context):
  632.         layout = self.layout
  633.        
  634.         #store active/selected scene to variable
  635.         scene = context.scene
  636.        
  637.         #allow access to user inputted properties through pointer
  638.         #to properties
  639.         savetool = scene.save_tool
  640.  
  641.         preferences = get_preferences()
  642.  
  643.         #make folder section
  644.         box = layout.box()
  645.         row = box.row()
  646.         row.label(text="Folders")
  647.         row = box.row()
  648.         left = row.column()
  649.         left.alignment = "RIGHT"
  650.         left.prop(preferences, 'folders', expand=False)
  651.         right = row.column()
  652.         right.alignment = "LEFT"
  653.         right.operator("nodekit.folder_actions", text="", icon="MENU_PANEL")
  654.  
  655.         selected_folders_presets = get_selected_folder_presets()
  656.         layout.label(text = "Your presets")
  657.         #create the list of current presets
  658.         layout.template_list("SHADER_PRESETS_UL_items", "", selected_folders_presets,
  659.                                "presets", selected_folders_presets, "preset_index", rows=5)
  660.         layout.operator("saveueshaderscript.remove_preset",
  661.                         text="Remove", icon="REMOVE")
  662.         layout.label(text = "Save a Custom Shader Map")
  663.        
  664.         layout.prop(savetool, "cust_map_name")
  665.        
  666.         layout.prop(savetool, "is_add_img_textures")
  667.         if (savetool.is_add_img_textures == True):
  668.             box = layout.box()
  669.             box.label(text = "Image Texture Suffixes and Node Names")
  670.             box.label(text = "(leave suffix OR node name empty if you do NOT want to load the specific image texture)")
  671.             box.label(text = "Node Names can be found/changed by selecting an image texture node > Press n > Item > Name")
  672.             box.prop(savetool, "bc_suffix")
  673.             box.prop(savetool, "bc_suffix_node")
  674.             box.prop(savetool, "orm_suffix")
  675.             box.prop(savetool, "orm_suffix_node")
  676.             box.prop(savetool, "n_suffix")
  677.             box.prop(savetool, "n_suffix_node")
  678.             box.prop(savetool, "m_suffix")
  679.             box.prop(savetool, "m_suffix_node")
  680.             box.prop(savetool, "bde_suffix")
  681.             box.prop(savetool, "bde_suffix_node")
  682.             box.prop(savetool, "hm_suffix")
  683.             box.prop(savetool, "hm_suffix_node")
  684.             box.prop(savetool, "skin_suffix")
  685.             box.prop(savetool, "skin_suffix_node")
  686.             box.prop(savetool, "cust1_suffix")
  687.             box.prop(savetool, "cust1_suffix_node")
  688.             box.prop(savetool, "cust2_suffix")
  689.             box.prop(savetool, "cust2_suffix_node")
  690.        
  691.         layout.operator("SAVEUESHADERSCRIPT.saveshadermap_operator")
  692.         layout.operator("SAVEUESHADERSCRIPT.loadimagetexture_operator")
  693.  
  694. #this class is the list to be displayed in the main panel
  695. class SHADER_PRESETS_UL_items(bpy.types.UIList):
  696.     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
  697.         row = layout.row()
  698.         custom_icon = "NODE"
  699.         row.label(text=item.name, icon=custom_icon)
  700.  
  701.     def invoke(self, context, event):
  702.         pass
  703.  
  704. class SHADER_MT_FolderActionsMenu(bpy.types.Menu):
  705.     bl_label = ""
  706.     bl_idname = "SHADER_MT_FolderActionsMenu"
  707.  
  708.     def draw(self, context):
  709.         layout = self.layout
  710.         # https://docs.blender.org/api/current/bpy.types.Menu.html
  711.         # https://docs.blender.org/api/current/bpy.ops.html#operator-execution-context
  712.         # This fix below operators not working properly
  713.         layout.operator_context = 'INVOKE_DEFAULT'
  714.         row = layout.row()
  715.         row.operator("nodekit.new_folder", text="Add New Folder", icon="ADD")
  716.         row = layout.row()
  717.         row.operator("nodekit.remove_folder",
  718.                      text="Remove Selected Folder", icon="REMOVE")
  719.         row = layout.row()
  720.         row.operator("nodekit.rename_folder",
  721.                      text="Rename Selected Folder", icon="EVENT_R")
  722.  
  723.  
  724. class Shader_ShowFolderActionsOperator(bpy.types.Operator):
  725.     bl_idname = "nodekit.folder_actions"
  726.     bl_label = ""
  727.     bl_description = "Show Message for Node Kit"
  728.     bl_options = {'REGISTER'}
  729.  
  730.     @classmethod
  731.     def poll(cls, context):
  732.         return True
  733.  
  734.     def execute(self, context):
  735.         bpy.ops.wm.call_menu(name="SHADER_MT_FolderActionsMenu")
  736.         return {'FINISHED'}
  737.  
  738.  
  739. class Shader_NewFolderOperator(bpy.types.Operator):
  740.     bl_idname = "nodekit.new_folder"
  741.     bl_label = "New Folder"
  742.     bl_description = "New Folder"
  743.     folder_name: bpy.props.StringProperty(name="")
  744.  
  745.     @classmethod
  746.     def poll(cls, context):
  747.         return True
  748.  
  749.     def draw(self, context):
  750.         layout = self.layout
  751.         row = layout.row()
  752.         left, right = get_two_column(self, row)
  753.         left.label(text="New Folder Name:")
  754.         right.prop(self, "folder_name")
  755.  
  756.     def execute(self, context):
  757.         if folder_name_exist(self.folder_name):
  758.             bpy.ops.nodekit.show_message(
  759.                 message="The folder name exists, please choose another name and try again.")
  760.             return {'FINISHED'}
  761.         pref = get_preferences()
  762.         new_folder_presets = pref.folders_presets.add()
  763.         new_folder_presets.folder_name = self.folder_name
  764.         index = len(pref.folders_presets) - 1
  765.         pref.folders = "%d" % index
  766.         save_pref()
  767.         redraw_all()
  768.         return {'FINISHED'}
  769.  
  770.     def invoke(self, context, event):
  771.         suggested_name = suggested_folder_name()
  772.         self.folder_name = suggested_name
  773.         wm = context.window_manager
  774.         return wm.invoke_props_dialog(self, width=300)
  775.  
  776. #-------------check if preset or folder name exists
  777. def preset_name_exist(name):
  778.     selected_folder_presets = get_selected_folder_presets()
  779.     presets = selected_folder_presets.presets
  780.     for preset in presets:
  781.         if name == preset.name:
  782.             return True
  783.     return False
  784.  
  785.  
  786. def folder_name_exist(name):
  787.     pref = get_preferences()
  788.     folder_presets = pref.folders_presets
  789.     for folder_preset in folder_presets:
  790.         if name == folder_preset.folder_name:
  791.             return True
  792.     return False
  793.  
  794. def preset_name_exist_in_folder(folder_name, preset_name):
  795.     pref = get_preferences()
  796.     folders_presets = pref.folders_presets
  797.     for folder in folders_presets:
  798.         if folder.folder_name == folder_name:
  799.             for preset in folder.presets:
  800.                 if preset.name == preset_name:
  801.                     return True
  802.     return False
  803.  
  804.  
  805. def suggested_preset_name():
  806.     name = "preset"
  807.     index = 1
  808.     suggested_name = "%s %d" % (name, index)
  809.     while preset_name_exist(suggested_name):
  810.         index += 1
  811.         suggested_name = "%s %d" % (name, index)
  812.     return suggested_name
  813.  
  814.  
  815. def suggested_folder_name():
  816.     name = "folder"
  817.     index = 1
  818.     suggested_name = "%s %d" % (name, index)
  819.     while folder_name_exist(suggested_name):
  820.         index += 1
  821.         suggested_name = "%s %d" % (name, index)
  822.     return suggested_name
  823.  
  824. #----check if preset or folder name exists end
  825.  
  826.  
  827. class Shader_RemoveFolderOperator(bpy.types.Operator):
  828.     bl_idname = "nodekit.remove_folder"
  829.     bl_label = "Do you really want to remove selected folder?"
  830.     bl_description = "Remove Folder"
  831.  
  832.     @classmethod
  833.     def poll(cls, context):
  834.         return True
  835.  
  836.     def execute(self, context):
  837.         pref = get_preferences()
  838.         selected_folder = pref.folders
  839.         remove_folder(selected_folder)
  840.         save_pref()
  841.         redraw_all()
  842.         return {'FINISHED'}
  843.  
  844.     def invoke(self, context, event):
  845.         return context.window_manager.invoke_confirm(self, event)
  846.  
  847. def remove_folder(selected_folder_index):
  848.     pref = get_preferences()
  849.     folder_presets = pref.folders_presets
  850.     for index, folder_preset in enumerate(folder_presets):
  851.         if index == int(selected_folder_index):
  852.             folder_presets.remove(index)
  853.             l = len(pref.folders_presets) - 1
  854.             if l >= 0:
  855.                 pref.folders = "%d" % l
  856.             break
  857.  
  858. class Shader_RenameFolderOperator(bpy.types.Operator):
  859.     bl_idname = "nodekit.rename_folder"
  860.     bl_label = "Rename Folder"
  861.     bl_description = "Rename Folder"
  862.     bl_options = {'REGISTER'}
  863.     folder_name: bpy.props.StringProperty(name="")
  864.  
  865.     @classmethod
  866.     def poll(cls, context):
  867.         return True
  868.  
  869.     def invoke(self, context, event):
  870.         pref = get_preferences()
  871.         selected_folder_index = pref.folders
  872.         folder_name = get_folder_name(selected_folder_index)
  873.         self.folder_name = folder_name
  874.         wm = context.window_manager
  875.         return wm.invoke_props_dialog(self, width=300)
  876.  
  877.     def draw(self, context):
  878.         layout = self.layout
  879.         row = layout.row()
  880.         left, right = get_two_column(self, row)
  881.         left.label(text="New Folder Name:")
  882.         right.prop(self, "folder_name")
  883.  
  884.     def execute(self, context):
  885.         pref = get_preferences()
  886.         if folder_name_exist(self.folder_name):
  887.             bpy.ops.nodekit.show_message(
  888.                 message="The folder name exists, please choose another name and try again.")
  889.             return {'FINISHED'}
  890.         selected_folder_index = pref.folders
  891.         new_folder_name = self.folder_name
  892.         print("index: ", selected_folder_index,
  893.               " new folder name:", new_folder_name)
  894.         rename_folder(selected_folder_index, new_folder_name)
  895.         save_pref()
  896.         redraw_all()
  897.         return {'FINISHED'}
  898.  
  899. def rename_folder(selected_folder_index, new_folder_name):
  900.     pref = get_preferences()
  901.     folder_presets = pref.folders_presets
  902.     for index, folder_preset in enumerate(folder_presets):
  903.         if index == int(selected_folder_index):
  904.             folder_preset.folder_name = new_folder_name
  905.  
  906.  
  907. def get_folder_name(selected_folder_index):
  908.     pref = get_preferences()
  909.     folder_presets = pref.folders_presets
  910.     for index, folder_preset in enumerate(folder_presets):
  911.         if index == int(selected_folder_index):
  912.             return folder_preset.folder_name
  913.     return ""
  914.  
  915. #--------formatting functions for panels
  916. def get_two_column(self, row, factor=0.5):
  917.     splitrow = layout_split(row, factor=factor)
  918.     left = splitrow.column()
  919.     left.alignment = "RIGHT"
  920.     right = splitrow.column()
  921.     right.alignment = "LEFT"
  922.     return (left, right)
  923.  
  924. def layout_split(layout, factor=0.0, align=False):
  925.     """Intermediate method for pre and post blender 2.8 split UI function"""
  926.     if not hasattr(bpy.app, "version") or bpy.app.version < (2, 80):
  927.         return layout.split(percentage=factor, align=align)
  928.     return layout.split(factor=factor, align=align)
  929.  
  930. #--------end formatting functions for panels
  931.  
  932. #remove preset button
  933. class SAVEUESHADERSCRIPT_OT_remove_preset(bpy.types.Operator):
  934.     bl_idname = "saveueshaderscript.remove_preset"
  935.     bl_label = "Do you really want to remove selected preset?"
  936.     bl_description = "Remove Preset"
  937.     bl_options = {'REGISTER'}
  938.  
  939.     @classmethod
  940.     def poll(cls, context):
  941.         return True
  942.  
  943.     def execute(self, context):
  944.         selected_folder_presets = get_selected_folder_presets()
  945.         index = selected_folder_presets.preset_index
  946.         if index < 0:
  947.             return {'FINISHED'}
  948.         folder_name = get_selected_folder_name()
  949.         preset_name = get_selected_preset_name()
  950.         selected_folder_presets.presets.remove(index)
  951.         selected_folder_presets.preset_index = len(
  952.             selected_folder_presets.presets) - 1
  953.         #remove_preset_from_10_last_used(folder_name, preset_name)
  954.         #update_10_most_used_presets()
  955.         save_pref()
  956.         redraw_all()
  957.         return {'FINISHED'}
  958.  
  959.     def invoke(self, context, event):
  960.         return context.window_manager.invoke_confirm(self, event)
  961.  
  962. def get_selected_folder_name():
  963.     pref = get_preferences()
  964.     if pref.folders == "" or pref.folders == None:
  965.         return None
  966.     index = int(pref.folders)
  967.     return pref.folders_presets[index].folder_name
  968.  
  969.  
  970. def get_selected_preset_name():
  971.     return get_selected_preset().name
  972.  
  973. def get_selected_preset():
  974.     selected_folder_presets = get_selected_folder_presets()
  975.     if selected_folder_presets.preset_index < 0 or \
  976.             len(selected_folder_presets.presets) <= 0:
  977.         return None
  978.     return selected_folder_presets.presets[selected_folder_presets.preset_index]
  979.  
  980.  
  981. #----------------------------------CONVERT TO JSON AND SAVE PRESETS RELATED CODE
  982.  
  983.  
  984.  
  985. def nodes_dict_to_json(nodes_dict):
  986.     try:
  987.         JSON = dict_to_string(nodes_dict)
  988.     except Exception as e:
  989.         report_error(self, str(e))
  990.     return JSON
  991.  
  992.  
  993. def json_to_nodes_dict(JSON):
  994.     try:
  995.         nodes_dict = json_to_dict(JSON)
  996.     except Exception as e:
  997.         report_error(self, str(e))
  998.     return nodes_dict
  999.  
  1000. def json_to_dict(json_string):
  1001.     """Convert JSON string into a Python dictionary"""
  1002.     return json.loads(json_string)
  1003.  
  1004.  
  1005. def dict_to_string(d):
  1006.     """Convert a Python dictionary to JSON string"""
  1007.     return json.dumps(d, indent=4)
  1008.  
  1009. DEFAULT_JSON_FILE = "ue_shader_script_default_json"
  1010.  
  1011.  
  1012. def get_default_json_path():
  1013.     home = os.path.expanduser("~")
  1014.     full_path = os.path.join(home, DEFAULT_JSON_FILE)
  1015.     #debug
  1016.     #print("full_path: ", full_path)
  1017.     return full_path
  1018.  
  1019.  
  1020. def import_default_json():
  1021.     """Use this to import default json file in ~/node_kit_default_json"""
  1022.     full_path = get_default_json_path()
  1023.     try:
  1024.         with open(full_path) as f:
  1025.             json_string = f.read()
  1026.             json_string_to_presets(json_string, skip_autosave=True)
  1027.     except IOError:
  1028.         print(full_path, ": file not accessible")
  1029.  
  1030. _10_LAST_USED_PRESETS_KEY = "10_last_used_presets"
  1031. USED_KEY = "_used"
  1032.  
  1033. def base64encode(s):
  1034.     return base64.b64encode(s.encode("utf-8")).decode("utf-8")
  1035.  
  1036. def base64decode(s):
  1037.     return base64.b64decode(s.encode("utf-8")).decode("utf-8")
  1038.  
  1039. def json_string_to_presets(json_string, skip_autosave=False):
  1040.     dict = json_to_dict(json_string)
  1041.     #debug
  1042.     #print("dict: ", dict)
  1043.     pref = get_preferences()
  1044.     folders_presets = pref.folders_presets
  1045.     folders_presets.clear()
  1046.     for key in dict:
  1047.         if key == _10_LAST_USED_PRESETS_KEY:
  1048.             presets_list = dict[key]
  1049.             import_10_last_used_presets(presets_list)
  1050.             continue
  1051.         presets = dict[key]
  1052.         new_folder = folders_presets.add()
  1053.         new_folder.folder_name = key
  1054.         #returns instance of FolderPresetsCollection
  1055.         new_folder_presets = new_folder.presets
  1056.         #presets is a dict of all presets in a single folder
  1057.         #print("presets:", presets)
  1058.         for preset in presets:
  1059.             #debug
  1060.             #print("preset:", preset)
  1061.             #print("\n\n")
  1062.  
  1063.             #preset is an individual preset such as test1
  1064.             #print("preset:", preset)
  1065.             #.add is method to add an instance of a class
  1066.             new_preset = new_folder_presets.add()
  1067.             new_preset.used = 0
  1068.             for k in preset:
  1069.                 #if the key is _used it's going to be something like 1
  1070.                 #this is when it was last used
  1071.                 if k == USED_KEY:
  1072.                     new_preset.used = preset[USED_KEY]
  1073.                 else:
  1074.                 #otherwise if it is not the used key we shall decode it
  1075.                 #with base64
  1076.                     content = base64decode(preset[k])
  1077.                     new_preset.name = k
  1078.                     new_preset.content = content
  1079.                 #debug
  1080.                 #print("preset[k]", preset[k])
  1081.     import_10_most_used_presets()
  1082.     save_pref(skip_autosave=skip_autosave)
  1083.  
  1084. def import_10_last_used_presets(presets_list):
  1085.     pref = get_preferences()
  1086.     pref.ten_last_used_presets.clear()
  1087.     for preset in presets_list:
  1088.         new_preset = pref.ten_last_used_presets.add()
  1089.         new_preset.folder_name = preset[0]
  1090.         new_preset.preset_name = preset[1]
  1091.  
  1092. def import_10_most_used_presets():
  1093.     update_10_most_used_presets()
  1094.  
  1095. def is_preset_added(folder_name, preset_name):
  1096.     preferences = get_preferences()
  1097.     for p in preferences.ten_most_used_presets:
  1098.         if p.folder_name == folder_name and \
  1099.                 p.preset_name == preset_name:
  1100.             return True
  1101.     return False
  1102.  
  1103. def update_10_most_used_presets():
  1104.     pref = get_preferences()
  1105.     ten_most_used_presets = pref.ten_most_used_presets
  1106.     ten_most_used_presets.clear()
  1107.     folders = pref.folders_presets
  1108.     presets = []
  1109.     for folder in folders:
  1110.         for preset in folder.presets:
  1111.             if hasattr(preset, "used"):
  1112.                 presets.append((folder.folder_name, preset.name, preset.used))
  1113.  
  1114.     def sort_fun(ele):
  1115.         return ele[2]
  1116.     presets = sorted(presets, key=sort_fun, reverse=True)
  1117.     l = len(presets)
  1118.     if l > 10:
  1119.         l = 10
  1120.     for i in range(0, l):
  1121.         preset = presets[i]
  1122.         if not is_preset_added(preset[0], preset[1]):
  1123.             new_preset = pref.ten_most_used_presets.add()
  1124.             new_preset.folder_name = preset[0]
  1125.             new_preset.preset_name = preset[1]
  1126.     if len(pref.ten_most_used_presets) >= 0:
  1127.         pref.ten_most_used_presets_index = 0
  1128.  
  1129.  
  1130. def get_selected_folder_presets(isOverridePackage = False):
  1131.     #getting preferences for add on
  1132.     #preferences are anything the add on needs
  1133.     #to store permanently for later use
  1134.     #such as the JSON strings associated with each preset
  1135.     pref = get_preferences(isOverridePackage)
  1136.     #print("pref: ", pref)
  1137.     if pref.folders == "" or pref.folders == None:
  1138.         return None
  1139.     #print("pref.folders:", pref.folders)
  1140.     index = int(pref.folders)
  1141.     #print("index:", index)
  1142.  
  1143.     #print("dir(pref)", dir(pref))
  1144.     #debug
  1145.     #print("pref.folders_presets:", pref.folders_presets)
  1146.     #print("dir(pref.folders_presets[index]):", dir(pref.folders_presets[index]))
  1147.     return pref.folders_presets[index]
  1148.  
  1149.  
  1150. class PresetCollection(PropertyGroup):
  1151.     name: StringProperty()
  1152.     content: StringProperty()
  1153.     used: IntProperty(default=0)
  1154.  
  1155.  
  1156. class FolderPresetsCollection(PropertyGroup):
  1157.     folder_name: StringProperty()
  1158.     presets: CollectionProperty(type=PresetCollection)
  1159.     preset_index: IntProperty()
  1160.  
  1161.  
  1162. class TenLastUsedPresetsCollection(PropertyGroup):
  1163.     folder_name: StringProperty()
  1164.     preset_name: StringProperty()
  1165.  
  1166.  
  1167. class TenMostUsedPresetsCollection(PropertyGroup):
  1168.     folder_name: StringProperty()
  1169.     preset_name: StringProperty()
  1170.  
  1171. def get_folders_items(self, context):
  1172.     pref = get_preferences()
  1173.     enum_types_items = []
  1174.     for index, folder_preset in enumerate(pref.folders_presets):
  1175.         enum_types_items.append(("%d" % index, folder_preset.folder_name, ""))
  1176.     return tuple(enum_types_items)
  1177.  
  1178. def update_folders(self, context):
  1179.     redraw_all()    
  1180.  
  1181.  
  1182. class SavePreferences(bpy.types.AddonPreferences):
  1183.     bl_idname = __package__
  1184.     folders_presets: CollectionProperty(type=FolderPresetsCollection)
  1185.     folders: EnumProperty(name="",
  1186.                           description="",
  1187.                           items=get_folders_items,
  1188.                           update=update_folders,
  1189.                           default=None)
  1190.     ten_last_used_presets: CollectionProperty(type=TenLastUsedPresetsCollection)
  1191.     ten_last_used_presets_index: IntProperty()
  1192.     ten_most_used_presets: CollectionProperty(type=TenMostUsedPresetsCollection)
  1193.     ten_most_used_presets_index: IntProperty()
  1194.     # `presets` and `preset_index` are not used
  1195.     # `presets` is for the old version
  1196.     presets: CollectionProperty(type=PresetCollection)
  1197.     preset_index: IntProperty()
  1198.  
  1199.     def draw(self, context):
  1200.         wm = context.window_manager
  1201.         layout = self.layout
  1202.  
  1203. #by default use __package__ however, if the function is being imported elsewhere
  1204. #the __package__ variable does not work, allow another paramter to override with the UEShaderScript string
  1205. def get_preferences(isOverridePackage = False, package=__package__, context=None):
  1206.     """Multi version compatibility for getting preferences"""
  1207.     if isOverridePackage:
  1208.         #debug
  1209.         #print("Override Package!!!!!")
  1210.         package = "UEShaderScript"
  1211.  
  1212.     if not context:
  1213.         context = bpy.context
  1214.     prefs = None
  1215.     #blender 2.7x
  1216.     if hasattr(context, "user_preferences"):
  1217.         prefs = context.user_preferences.addons.get(package, None)
  1218.     #blender 2.8+
  1219.     elif hasattr(context, "preferences"):
  1220.         #debug
  1221.         #print("package:", package)
  1222.         #__package__ is the name of the folder that the add on is in
  1223.         #it would be UEShaderScript for this one
  1224.         #This line here gets a prefs struct which is not what we
  1225.         #about we want the attribute preferences inside the prefs struct
  1226.         prefs = context.preferences.addons.get(package, None)
  1227.         #debug
  1228.         #print("dir(prefs):", dir(prefs))
  1229.     if prefs:
  1230.         #debug
  1231.         #print("prefs.preferences", prefs.preferences)
  1232.         #print("dir(prefs.preferences)", dir(prefs.preferences))
  1233.         #print("\n\n")
  1234.         #this prefs.preferences here
  1235.         #prefs.preferences refers to the subclass/child class SavePreferences
  1236.         #SavePreferences is a child class and builds on bpy.types.AddonPreferences
  1237.         #adding extra attributes
  1238.         #bpy.types.AddonPreferences is when you need to
  1239.         #save something in the domain of your addon, not in the scene of your blend file.
  1240.         return prefs.preferences
  1241.     else:
  1242.         raise Exception("Could not fetch user preferences")
  1243.  
  1244.  
  1245.  
  1246. def save_pref(skip_autosave=False):
  1247.     #save_userpref will not work for
  1248.     #import_default_json()
  1249.     #when the file starts as blender cannot allow
  1250.     #to access ops when file starts
  1251.     bpy.ops.wm.save_userpref()
  1252.     if not skip_autosave:
  1253.         export_to_default_json()
  1254.  
  1255.  
  1256. def redraw_all():
  1257.     """For redraw Slash panel"""
  1258.     for area in bpy.context.window.screen.areas:
  1259.         for region in area.regions:
  1260.             if region.type == "UI":
  1261.                 region.tag_redraw()
  1262.  
  1263.  
  1264. def export_to_default_json():
  1265.     """Use this to export to default json file in ~/node_kit_default_json"""
  1266.     full_path = get_default_json_path()
  1267.     f = open(full_path, "w+")
  1268.     json_string = presets_to_json_string()
  1269.     f.write(json_string)
  1270.     f.close()
  1271.  
  1272.  
  1273. def presets_to_json_string():
  1274.     JSON = {}
  1275.     pref = get_preferences()
  1276.     folders_presets = pref.folders_presets
  1277.     for folder_presets in folders_presets:
  1278.         presets = []
  1279.         for preset in folder_presets.presets:
  1280.             preset_dict = {}
  1281.             preset_dict[preset.name] = base64encode(preset.content)
  1282.             if hasattr(preset, "used"):
  1283.                 preset_dict[USED_KEY] = preset.used
  1284.             presets.append(preset_dict)
  1285.         JSON[folder_presets.folder_name] = presets
  1286.     #JSON[_10_LAST_USED_PRESETS_KEY] = _10_last_used_presets_to_json()
  1287.     return dict_to_string(JSON)
  1288.  
  1289. #-----------------------------------message and console log related code
  1290. VERBOSE = True
  1291.  
  1292. def log(msg):
  1293.     if VERBOSE:
  1294.         print("[UE Shader]", msg)
  1295.  
  1296. def report_error(self, message):
  1297.     full_message = "%s\n\nPlease report to the add-on developer with this error message (A screenshot is awesome)" % message
  1298.     self.report({"ERROR"}, full_message)
  1299.  
  1300. class ShowMessageOperator(bpy.types.Operator):
  1301.     bl_idname = "ueshaderscript.show_message"
  1302.     bl_label = ""
  1303.     bl_description = "Show Message for Node Kit"
  1304.     bl_options = {'REGISTER'}
  1305.     message: bpy.props.StringProperty(default="Message Dummy")
  1306.     called: bpy.props.BoolProperty(default=False)
  1307.  
  1308.     @classmethod
  1309.     def poll(cls, context):
  1310.         return True
  1311.  
  1312.     def invoke(self, context, event):
  1313.         wm = context.window_manager
  1314.         return wm.invoke_props_dialog(self, width=300)
  1315.  
  1316.     def draw(self, context):
  1317.         layout = self.layout
  1318.         row = layout.row()
  1319.         row.label(text=self.message)
  1320.  
  1321.     def execute(self, context):
  1322.         if not self.called:
  1323.             wm = context.window_manager
  1324.             self.called = True
  1325.             return wm.invoke_props_dialog(self, width=400)
  1326.         return {'FINISHED'}    
  1327.  
  1328.  
  1329.  
  1330.  
  1331.  
  1332.  
  1333. class SAVEUESHADERSCRIPT_OT_load_image_texture(bpy.types.Operator):
  1334.     #default name is for Roman Noodles label
  1335.     #text is changed for other Shader Map Types
  1336.     bl_label = "Load Image Texture"
  1337.     bl_idname = "saveueshaderscript.loadimagetexture_operator"
  1338.     def execute(self, context):
  1339.         scene = context.scene
  1340.         #allow access to user inputted properties through pointer
  1341.         #to properties
  1342.         savetool = scene.save_tool
  1343.        
  1344.         if(savetool.is_add_img_textures == True):
  1345.             node_to_load = bpy.context.active_object.active_material.node_tree.nodes["Diffuse Node"]
  1346.             node_to_load.image = bpy.data.images.load("C:\Seabrook\Dwight Recolor\Game\Characters\Campers\Dwight\Textures\Outfit01\T_DFHair01_BC.tga")
  1347.            
  1348.        
  1349.         return {"FINISHED"}
  1350.  
  1351.  
  1352. classes = [SaveProperties, PresetCollection,
  1353.     FolderPresetsCollection, TenLastUsedPresetsCollection, TenMostUsedPresetsCollection, SavePreferences,
  1354.    
  1355.      SHADER_PRESETS_UL_items, SAVEUESHADERSCRIPT_PT_main_panel,
  1356. Shader_ShowFolderActionsOperator, SHADER_MT_FolderActionsMenu, Shader_NewFolderOperator,
  1357. Shader_RemoveFolderOperator, Shader_RenameFolderOperator, SAVEUESHADERSCRIPT_OT_remove_preset, ShowMessageOperator,
  1358.  
  1359.     SAVEUESHADERSCRIPT_OT_save_shader_map, SAVEUESHADERSCRIPT_OT_load_image_texture]
  1360.  
  1361. def register():
  1362.     for cls in classes:
  1363.         bpy.utils.register_class(cls)
  1364.        
  1365.         #register save_tool as a type which has all
  1366.         #the user input properties from the properties class
  1367.         bpy.types.Scene.save_tool = bpy.props.PointerProperty(type = SaveProperties)
  1368.  
  1369.  
  1370. def unregister():
  1371.     for cls in classes:
  1372.         bpy.utils.unregister_class(cls)
  1373.        
  1374.         #unregister save_tool as a type
  1375.         del bpy.types.Scene.save_tool
  1376.  
  1377.  
  1378. if __name__ == "__main__":
  1379.     register()
Add Comment
Please, Sign In to add comment