def detect_manual_uv_wrapping(blender_shader_node): # Detects UV wrapping done using math nodes. This is for emulating wrap # modes Blender doesn't support. It looks like # # next_socket => [Sep XYZ] => [Wrap S] => [Comb XYZ] => blender_shader_node # => [Wrap T] => # # The [Wrap _] blocks are either math nodes (eg. PINGPONG for mirrored # repeat), or can be omitted. # # Returns None if not detected. Otherwise a dict containing the wrap # mode in each direction (or None), and next_socket. result = {} comb = previous_node(blender_shader_node.inputs['Vector']) if comb is None or comb.type != 'COMBXYZ': return None for soc in ['X', 'Y']: node = previous_node(comb.inputs[soc]) if node is None: return None if node.type == 'SEPXYZ': # Passed through without change wrap = None prev_socket = previous_socket(comb.inputs[soc]) elif node.type == 'MATH': # Math node applies a manual wrap if (node.operation == 'PINGPONG' and get_const_from_socket( node.inputs[1], kind='VALUE') == 1.0): # scale = 1 wrap = TextureWrap.MirroredRepeat elif (node.operation == 'WRAP' and get_const_from_socket( node.inputs[1], kind='VALUE') == 0.0 and # min = 0 get_const_from_socket(node.inputs[2], kind='VALUE') == 1.0): # max = 1 wrap = TextureWrap.Repeat else: return None prev_socket = previous_socket(node.inputs[0]) else: return None if prev_socket is None: return None prev_node = prev_socket.node if prev_node.type != 'SEPXYZ': return None # Make sure X goes to X, etc. if prev_socket.name != soc: return None # Make sure both attach to the same SeparateXYZ node if soc == 'X': sep = prev_node else: if sep != prev_node: return None result['wrap_s' if soc == 'X' else 'wrap_t'] = wrap result['next_socket'] = sep.inputs[0] return result
def __detect_lightpath_trick(socket): # Detects this (used to prevent casting light on other objects) See ex. # https://blender.stackexchange.com/a/21535/88681 # # [ Lightpath ] [ Mix ] # [ Is Camera Ray] => [Factor ] => socket # (don't care) => [Shader ] # next_socket => [ Emission ] => [Shader ] # # The Emission node can be omitted. # Returns None if not detected. Otherwise, a dict containing # next_socket. prev = gltf2_blender_get.previous_node(socket) if prev is None or prev.type != 'MIX_SHADER': return None in0 = gltf2_blender_get.previous_socket(prev.inputs[0]) if in0 is None or in0.node.type != 'LIGHT_PATH': return None if in0.name != 'Is Camera Ray': return None next_socket = prev.inputs[2] # Detect emission prev = gltf2_blender_get.previous_node(next_socket) if prev is not None and prev.type == 'EMISSION': next_socket = prev.inputs[0] return {'next_socket': next_socket}
def detect_shadeless_material(blender_material, export_settings): """Detect if this material is "shadeless" ie. should be exported with KHR_materials_unlit. Returns None if not. Otherwise, returns a dict with info from parsing the node tree. """ if not blender_material.use_nodes: return None # Old Background node detection (unlikely to happen) bg_socket = gltf2_blender_get.get_socket(blender_material, "Background") if bg_socket is not None: return {'rgb_socket': bg_socket} # Look for # * any color socket, connected to... # * optionally, the lightpath trick, connected to... # * optionally, a mix-with-transparent (for alpha), connected to... # * the output node info = {} for node in blender_material.node_tree.nodes: if node.type == 'OUTPUT_MATERIAL': socket = node.inputs[0] break else: return None # Be careful not to misidentify a lightpath trick as mix-alpha. result = __detect_lightpath_trick(socket) if result is not None: socket = result['next_socket'] else: result = __detect_mix_alpha(socket) if result is not None: socket = result['next_socket'] info['alpha_socket'] = result['alpha_socket'] result = __detect_lightpath_trick(socket) if result is not None: socket = result['next_socket'] # Check if a color socket, or connected to a color socket if socket.type != 'RGBA': from_socket = gltf2_blender_get.previous_socket(socket) if from_socket is None: return None if from_socket.type != 'RGBA': return None info['rgb_socket'] = socket return info