2
$\begingroup$

I want to ask if someone could help me with Octane plugin. There is conversion plugin changing material from Cycles Mat to Octane Mat but problem is that instead of changing nodes it creates new material with .001 so in scene where I have right now about 400 materials and 20 instances in different buildings extend a lot conversion process. If someone would help me with that I will be very grateful. If may ask to avoid deleting other textures(right now its leaving only color map and delete other maps) that it would change whole texture nodes do Octane ones and leave disconnected I will be even more grateful.

Thank you for all your support.

    import bpy
import math
import functools

NORMAL_TYPE_TAG = '[NORMAL]'
IMAGE_TYPE_TAG = '[IMAGE]'

CONVERTERS_NODE_MAPPER = {
    'BSDF_PRINCIPLED': 
    (
        'ShaderNodeOctUniversalMat', 
        {
            'Base Color': 'Albedo color',
            'Metallic': 'Metallic',
            'Specular': 'Specular',
            'Roughness': 'Roughness',
            'Anisotropic': 'Anisotropy',
            'Anisotropic Rotation': 'Rotation',
            'Sheen': 'Sheen',
            'IOR': 'Dielectric IOR',
            NORMAL_TYPE_TAG + 'Normal': 'Normal',
        }
    ),
    'TEX_IMAGE': 
    (
        'ShaderNodeOctImageTex', 
        {
            IMAGE_TYPE_TAG + 'image': IMAGE_TYPE_TAG + 'image',
        }
    ),
}

CONVERTERS_OUTPUT_MAPPER = {
    'OUTPUT_MATERIAL': 'ShaderNodeOutputMaterial',
}


def is_converter_applicable(material):
    if material and material.node_tree:
        for node in material.node_tree.nodes:
            if node.type in CONVERTERS_NODE_MAPPER:
                return True
    return False


def find_output_node(node_tree, output_type):
    output_node = None
    if node_tree:
        for node in node_tree.nodes:
            if node.type == output_type:
                output_node = node
                break
    return output_node


def find_input_socket(node, input_name):
    input_socket = None
    if node:
        for socket in node.inputs:
            if socket.name == input_name:
                input_socket = socket
                break
    return input_socket


def find_node_to_target_input(node_tree, node, input_name):
    node_to_target_input = None
    input_socket = find_input_socket(node, input_name)
    if node_tree:
        for link in node_tree.links:
            if link.to_socket == input_socket:
                node_to_target_input = link.from_node
                break
    return node_to_target_input


def _convert_attribute(cur_node, octane_node, cur_attribute_name, octane_attribute_name):
    setattr(octane_node, octane_attribute_name, getattr(cur_node, cur_attribute_name, None))    


def _convert_socket(cur_node, octane_node, cur_node_tree, octane_node_tree, cur_name, octane_name):    
    octane_linked_nodes = []
    cur_links = cur_node_tree.links
    cur_input = cur_node.inputs[cur_name]
    octane_links = octane_node_tree.links
    octane_input = octane_node.inputs[octane_name]     
    if hasattr(cur_input, 'default_value') and hasattr(octane_input, 'default_value'):
        if octane_input.type == cur_input.type:
            octane_input.default_value = cur_input.default_value
        elif cur_input.type == 'VALUE' and octane_input.type == 'RGBA':
            octane_input.default_value[0] = cur_input.default_value
            octane_input.default_value[1] = cur_input.default_value
            octane_input.default_value[2] = cur_input.default_value
            octane_input.default_value[3] = cur_input.default_value
    for cur_link in cur_input.links:
        octane_from_node = convert_to_octane_node(cur_link.from_node, cur_node_tree, octane_node_tree)
        if octane_from_node:
            octane_links.new(octane_from_node.outputs[0], octane_input)   
            octane_linked_nodes.append(octane_from_node) 
    return octane_linked_nodes


def _convert_cycles_normal_to_octane_node(cur_node, octane_node, cur_node_tree, octane_node_tree, cur_name, octane_name):
    _convert_socket(cur_node, octane_node, cur_node_tree, octane_node_tree, cur_name, octane_name)
    cur_links = cur_node_tree.links
    cur_input = cur_node.inputs[cur_name]
    octane_links = octane_node_tree.links
    octane_input = octane_node.inputs[octane_name]     
    for cur_link in cur_input.links:
        if cur_link.from_node.type == 'NORMAL_MAP':
            color_input = cur_link.from_node.inputs['Color']
            for cur_color_link in color_input.links:
                octane_from_node = convert_to_octane_node(cur_color_link.from_node, cur_node_tree, octane_node_tree)            
                if octane_from_node:
                    octane_links.new(octane_from_node.outputs[0], octane_input)
                    if octane_from_node.type == 'OCT_IMAGE_TEX':
                        _convert_socket(cur_link.from_node, octane_from_node, cur_node_tree, octane_node_tree, 'Strength', 'Power')                    


def _convert_to_octane_node(cur_node, octane_node, cur_node_tree, octane_node_tree, cur_name, octane_name):
    # try:        
    if cur_name.startswith(NORMAL_TYPE_TAG):
        cur_normal_name = cur_name[len(NORMAL_TYPE_TAG):]
        _convert_cycles_normal_to_octane_node(cur_node, octane_node, cur_node_tree, octane_node_tree, cur_normal_name, octane_name)
    elif cur_name.startswith(IMAGE_TYPE_TAG) or octane_name.startswith(IMAGE_TYPE_TAG):
        assert cur_name.startswith(IMAGE_TYPE_TAG) and octane_name.startswith(IMAGE_TYPE_TAG)
        cur_attribute_name = cur_name[len(IMAGE_TYPE_TAG):]
        octane_attribute_name = octane_name[len(IMAGE_TYPE_TAG):]
        _convert_attribute(cur_node, octane_node, cur_attribute_name, octane_attribute_name)
    else: 
        _convert_socket(cur_node, octane_node, cur_node_tree, octane_node_tree, cur_name, octane_name)
    # except Exception as e:
    #     pass


def convert_to_octane_node(cur_node, cur_node_tree, octane_node_tree):
    octane_node = None
    if cur_node.type in CONVERTERS_OUTPUT_MAPPER:
        octane_node = octane_node_tree.nodes.new(CONVERTERS_OUTPUT_MAPPER[cur_node.type])
        octane_node.location = cur_node.location
        for input_socket in octane_node.inputs:
            _convert_to_octane_node(cur_node, octane_node, cur_node_tree, octane_node_tree, input_socket.name, input_socket.name)    
    if cur_node.type in CONVERTERS_NODE_MAPPER:
        octane_node = octane_node_tree.nodes.new(CONVERTERS_NODE_MAPPER[cur_node.type][0])
        octane_node.location = cur_node.location  
        for cur_name, octane_name in CONVERTERS_NODE_MAPPER[cur_node.type][1].items():
            _convert_to_octane_node(cur_node, octane_node, cur_node_tree, octane_node_tree, cur_name, octane_name)
    return octane_node


def convert_to_octane_material(cur_material, converted_material):
    if not cur_material or not cur_material.node_tree:
        return    
    if not converted_material or not converted_material.node_tree:
        return
    cur_node_tree = cur_material.node_tree
    octane_node_tree = converted_material.node_tree    
    octane_node_tree.nodes.clear()
    output = find_output_node(cur_node_tree, 'OUTPUT_MATERIAL') 
    if not output:
        return
    convert_to_octane_node(output, cur_node_tree, octane_node_tree)
$\endgroup$
1
  • $\begingroup$ I haven't looked at the details, but assuming that this works similar to LuxCore and other third party engines, they likely implement their own custom nodes and shader tree. Therefore you can't simply re-use the Cycles materials / node tree, which is why the script performs the conversion. $\endgroup$ – Robert Gützkow Dec 19 '19 at 20:34
2
$\begingroup$

Remove as Orphans?

If octane materials are orphans, ie have no users as indicated by 0 in materials menu then remove them using outliner in "Orphan Data" mode

How can I remove all unused materials from a .blend file?

Delete all materials that aren't assigned to a face?

Only materials with numeric suffix

If octane materials are the only ones with the suffix

import bpy
copy_mats = [m for m in bpy.data.materials
        if m.name[-3:].isnumeric()]

while copy_mats:
    bpy.data.materials.remove(copy_mats.pop())

How to merge around 300+ duplicate materials?

Edit Script and tag as "Octane" material.

The script posted has the definition of convert_to_octane_material(cur_material, converted_material) method but not the call.

If that method is slightly altered to tag a converted material as being for Octane

def convert_to_octane_material(cur_material, converted_material):
    if not cur_material or not cur_material.node_tree:
        return    
    if not converted_material or not converted_material.node_tree:
        return
    converted_material["octane"] = True #<== This line inserted
    cur_node_tree = cur_material.node_tree
    ...

can remove all later by looking for that key.

import bpy
octane_mats = [m for m in bpy.data.materials
        if m.get("octane", False)]

while octane_mats:
    bpy.data.materials.remove(octane_mats.pop())
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.