Announcement

Collapse
No announcement yet.

Is it possible to use Python for editing material graphs?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Is it possible to use Python for editing material graphs?

    As stated in the title... I've been trying to figure out how to use Python to edit materials. So I'd like to be able to do operations like:
    • Check if a material base color has an incoming connection, and if so, what it is connected to.
    • Create nodes (constant, multiply, texture sample, etc)
    • Assign a texture to a texture sample node
    • Disconnect nodes from each other
    • Delete nodes
    • Edit node attribute values
    I have some Python experience, but I'm very new to Unreal. I've been searching for examples of Python code that can do any of those operations, but so far I haven't been able to find anything. Could anyone point me to any examples showing how to do any of the above?

    Thanks

    #2
    Hi,

    I'm looking for the same information, and am bumping up this thread in case someone has an idea. I essentially want to create a python script to look into all children of a material to see what overrides have been made. So for a start, getting to access a material and it's parameters would be great.

    Thanks for the help.

    Comment


      #3
      Think I can share two functions from our library

      Code:
      def material_instances():
          """ Get a list of all assets that are material instances. """
          instances = list()
          path = "/Game/"
      
          for asset in unreal.EditorAssetLibrary.list_assets(path):
              asset_data = unreal.EditorAssetLibrary.find_asset_data(asset)
              if asset_data.asset_class == "MaterialInstanceConstant":
                  instances.append(asset)
      
          return instances
      
      def get_static_switch_values(material):
          """ Iterate over Static Bool & Switch Material Expressions for the given 
          material to return a dictionary for parameter names and their default values.
      
          :param material: the material object we want to get parameters from
          :type unreal.Material:
          :returns: {foo: False, bar: True}
          """
      
          parameters = dict()
      
          filter_on = (
              unreal.MaterialExpressionStaticBoolParameter,
              unreal.MaterialExpressionStaticSwitchParameter
          )
      
          # Iterate loaded objects that are child of material
          # and matches filter types.
          it = unreal.ObjectIterator()
          for x in it:
              if x.get_outer() == material:
                  if isinstance(x, filter_on):
                      name = str(x.get_editor_property("parameter_name"))
                      value = x.get_editor_property("default_value")
                      parameters.update({name: value})
      
          return parameters
      Was some time since I worked with it but want to remember you could get/set most values on the expressions. Run pythons help() on material instance constant and its material expression to see the available methods.

      About creating and connecting expressions the unreal.MaterialEditingLibrary have methods for that.

      Comment


        #4
        Thank you. I've come up with a basic set of functions to help in reporting material instance info. Posting in case it helps anyone
        Code:
        import unreal
        import csv
        
        def get_instance_materials(asset_path, material_name):
            master_list = list()
            static_override_combo_dict = {}
            all_references_list = list()
            asset_registry = unreal.AssetRegistryHelpers.get_asset_registry()
            unreal_project_dir = unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir())
            all_assets = asset_registry.get_assets_by_path(asset_path, recursive=True)
        
            for asset in all_assets:
                loaded_parent_mat = unreal.EditorAssetLibrary.load_asset(asset.get_full_name())
                if (asset.asset_class == 'Material' or asset.asset_class == 'MaterialInstanceConstant') and asset.asset_name == material_name:
                    master_list = get_all_instances(loaded_parent_mat)
                    static_override_combo_dict = get_material_static_override_groups(loaded_parent_mat, master_list, static_override_combo_dict)
                    all_references_list = get_all_references(asset_path + '/' + material_name)
        
            csv_file = unreal_project_dir + material_name + '_instances.csv'
            with open(csv_file, 'w+') as f:
                f.write(asset_path + '/' + material_name + '\n')
                write_log_recursively(f, master_list, 1)
        
            csv_file = unreal_project_dir + material_name + '_instances_static_groups.csv'
            with open(csv_file, 'w+') as f:
                write_dict_to_file(f, static_override_combo_dict)
        
            csv_file = unreal_project_dir + material_name + '_references.csv'
            with open(csv_file, 'w+') as f:
                f.write(asset_path + '/' + material_name + '\n')
                for a in all_references_list:
                    f.write('\t' + a + '\n')
        
            log_file = unreal_project_dir + material_name + '_report.log'
            generate_report(material_name, master_list, all_references_list, static_override_combo_dict, log_file)
        
        def generate_report(material_name, all_instances_list, all_references_list, override_dict, log_file):
            total_instances = 0
            direct_instances = 0
            total_references = 0
            unique_static_combos = 0
        
            total_instances = get_instance_count(all_instances_list, True)
            direct_instances = get_instance_count(all_instances_list, False)
            unique_static_combos = len(override_dict.keys())
        
            with open(log_file, 'w+') as f:
                f.write(material_name + ' total instances:\t' + str(total_instances) + '\n')
                f.write(material_name + ' total direct instances:\t' + str(direct_instances) + '\n')
                f.write(material_name + ' total references:\t' + str(len(all_references_list)) + '\n')
                f.write(material_name + ' Unique Static Switches:\t' + str(len(override_dict.keys())-1) + '\n')
        
        def get_instance_count(all_instances_list, recursive):
            count = 0
            for a in xrange(0, len(all_instances_list)):
                if type(all_instances_list[a]) == str:
                    count += 1
                elif recursive:
                    count += get_instance_count(all_instances_list[a], True)
            return count
        
        def get_all_references(parent_material):
            mat_children_paths = unreal.EditorAssetLibrary.find_package_referencers_for_asset(parent_material, load_assets_to_confirm=False)
            return mat_children_paths
        
        def write_dict_to_file(log_file, my_dict):
            for key, values in my_dict.iteritems():
                log_file.write(key + '\n')
                for v in values:
                    log_file.write('\t' + v + '\n')
        
        def write_log_recursively(log_file, my_list, depth):
            for x in xrange (0,len(my_list)):
                for d in xrange(0,depth):
                     if type(my_list[x]) == str:
                        log_file.write('\t')
                if type(my_list[x]) == str:
                    log_file.write(my_list[x] + '\n')
                else:
                    write_log_recursively(log_file, my_list[x], depth + 1)
        
        def get_material_static_override_groups(loaded_parent_material, all_instances_list, return_dict):
            if len(return_dict) == 0:
                static_override_combo_dict = {}
            else:
                static_override_combo_dict = return_dict
        
            for i in all_instances_list:
                if type(i) == str:
                    instance_static_override_list = list()
                    loaded_instance = unreal.EditorAssetLibrary.load_asset(i)
                    instance_static_params = unreal.MaterialEditingLibrary.get_static_switch_parameter_names(loaded_instance)
        
                    for s in instance_static_params:
                        default_static_switch_value = unreal.MaterialEditingLibrary.get_material_default_static_switch_parameter_value(loaded_parent_material, s)
                        static_switch_instance_value = unreal.MaterialEditingLibrary.get_material_instance_static_switch_parameter_value(loaded_instance, s)
                        if default_static_switch_value != static_switch_instance_value:
                            instance_static_override_list.append(str(s))
        
                    override_string = ''
                    for t in instance_static_override_list:
                        if override_string == '':
                            override_string = override_string + t
                        else:
                            override_string = override_string + '_' + t
        
                    if override_string == '':
                        override_string = 'NoStaticOverride'
        
                    if override_string in static_override_combo_dict:
                        static_override_combo_dict[override_string].append(i)
                    else:
                        static_override_combo_dict[override_string] = [i]
                else:
                    get_material_static_override_groups(loaded_parent_material, i, static_override_combo_dict)
            return static_override_combo_dict
        
        def get_all_instances(parent_material):
            all_instances = list()
            child_instances = unreal.MaterialEditingLibrary.get_child_instances(parent_material)
        
            for ch in child_instances:
                loaded_instance = unreal.EditorAssetLibrary.load_asset(ch.get_full_name())
                all_instances.append(str(ch.package_name))
                if len(unreal.MaterialEditingLibrary.get_child_instances(loaded_instance)) > 0:
                    all_instances.append(get_all_instances(loaded_instance))
            return all_instances

        Comment


          #5
          Seems like a small treasure trove of code! Thank you both for taking the time to respond.

          Comment


            #6
            I can say also, Epic actually already have an amazing tool for what I assume you're doing - Material Analyzer. I too reinvented the wheel before finding this tool. Was nice introduction to python in Unreal but thankfully I found it before I got around to work out how to properly display the data.

            Comment

            Working...
            X