Example #1
0
def copy_basic_node_props(from_node: bpy.types.Node, to_node: bpy.types.Node):
    """Copy non-node-specific properties to a different node."""
    to_node.parent = from_node.parent
    to_node.location = from_node.location
    to_node.select = from_node.select

    to_node.arm_logic_id = from_node.arm_logic_id
    to_node.arm_watch = from_node.arm_watch
Example #2
0
def parse_shader(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> Tuple[str, ...]:
    # Use switch-like lookup via dictionary
    # (better performance, better code readability)
    # 'NODE_TYPE': parser_function
    node_parser_funcs: Dict[str, Callable] = {
        'MIX_SHADER': nodes_shader.parse_mixshader,
        'ADD_SHADER': nodes_shader.parse_addshader,
        'BSDF_PRINCIPLED': nodes_shader.parse_bsdfprincipled,
        'BSDF_DIFFUSE': nodes_shader.parse_bsdfdiffuse,
        'BSDF_GLOSSY': nodes_shader.parse_bsdfglossy,
        'AMBIENT_OCCLUSION': nodes_shader.parse_ambientocclusion,
        'BSDF_ANISOTROPIC': nodes_shader.parse_bsdfanisotropic,
        'EMISSION': nodes_shader.parse_emission,
        'BSDF_GLASS': nodes_shader.parse_bsdfglass,
        'HOLDOUT': nodes_shader.parse_holdout,
        'SUBSURFACE_SCATTERING': nodes_shader.parse_subsurfacescattering,
        'BSDF_TRANSLUCENT': nodes_shader.parse_bsdftranslucent,
        'BSDF_TRANSPARENT': nodes_shader.parse_bsdftransparent,
        'BSDF_VELVET': nodes_shader.parse_bsdfvelvet,
    }

    if node.type in node_parser_funcs:
        node_parser_funcs[node.type](node, socket, state)

    elif node.type == 'GROUP':
        if node.node_tree.name.startswith('Armory PBR'):
            if state.parse_surface:
                # Normal
                if node.inputs[5].is_linked and node.inputs[5].links[0].from_node.type == 'NORMAL_MAP':
                    log.warn(mat_name() + ' - Do not use Normal Map node with Armory PBR, connect Image Texture directly')
                parse_normal_map_color_input(node.inputs[5])
                # Base color
                state.out_basecol = parse_vector_input(node.inputs[0])
                # Occlusion
                state.out_occlusion = parse_value_input(node.inputs[2])
                # Roughness
                state.out_roughness = parse_value_input(node.inputs[3])
                # Metallic
                state.out_metallic = parse_value_input(node.inputs[4])
                # Emission
                if node.inputs[6].is_linked or node.inputs[6].default_value != 0.0:
                    state.out_emission = parse_value_input(node.inputs[6])
                    state.emission_found = True
            if state.parse_opacity:
                state.out_opacity = parse_value_input(node.inputs[1])
        else:
            return parse_group(node, socket)

    elif node.type == 'GROUP_INPUT':
        return parse_group_input(node, socket)

    elif node.type == 'CUSTOM':
        if node.bl_idname == 'ArmShaderDataNode':
            return node.parse(state.frag, state.vert)

    else:
        # TODO: Print node tree name (save in ParserState)
        log.warn(f'Material node type {node.type} not supported')

    return state.get_outs()
def select_node_target(arg_material: bpy.types.Material,
                       arg_node: bpy.types.Node):
    """対象マテリアルの指定ノードのみを選択状態する

    Args:
        arg_material (bpy.types.Material): 対象マテリアル
        arg_node (bpy.types.Node): 指定ノード
    """

    # ノード操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.Node.html)
    # ノードリスト操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.Nodes.html)

    # ターゲットマテリアルのノード参照を取得
    mat_nodes = arg_material.node_tree.nodes

    # 全てのノードの選択状態を解除する
    for mat_node in mat_nodes:
        # 選択状態を解除する
        mat_node.select = False

    # 指定ノードを選択状態にする
    arg_node.select = True

    # 指定ノードをアクティブにする
    mat_nodes.active = arg_node

    return
Example #4
0
 def select_children_of(self, node: bpy.types.Node,
                        links: list[bpy.types.NodeLink],
                        visited: list[bpy.types.Node]) -> None:
     # Prevent loops
     if node in visited:
         return
     node.select = True
     visited.append(node)
     if not node.inputs:
         return
     for link in links:
         if link.to_node == node:
             self.select_children_of(link.from_node, links, visited)
Example #5
0
def setting_node_MRTKStandard_ui(arg_node: bpy.types.Node) -> bpy.types.Node:
    """MRTKStandard設定を構成するノードグループの入力UIを設定する

    Args:
        arg_node (bpy.types.Node): 指定ノードグループ(ノード参照)

    Returns:
        bpy.types.Node: 作成ノードの参照
    """

    # ノードバージョンをラベルに記述する
    arg_node.label = def_nodegroup_version

    # ノーマル設定のデフォルト値を隠蔽する
    input_normal = arg_node.inputs[def_inputnode_input_normal_name]
    input_normal.hide_value = True

    return arg_node
Example #6
0
def replace(tree: bpy.types.NodeTree, node: bpy.types.Node):
    """Replaces the given node with its replacement."""

    # the node can either return a NodeReplacement object (for simple replacements)
    # or a brand new node, for more complex stuff.
    response = node.get_replacement_node(tree)

    if isinstance(response, bpy.types.Node):
        newnode = response
        # some misc. properties
        newnode.parent = node.parent
        newnode.location = node.location
        newnode.select = node.select
    elif isinstance(response, list):  # a list of nodes:
        for newnode in response:
            newnode.parent = node.parent
            newnode.location = node.location
            newnode.select = node.select
    elif isinstance(response, arm_nodes.NodeReplacement):
        replacement = response
        # if the returned object is a NodeReplacement, check that it corresponds to the node (also, create the new node)
        if node.bl_idname != replacement.from_node or node.arm_version != replacement.from_node_version:
            raise LookupError(
                "the provided NodeReplacement doesn't seem to correspond to the node needing replacement"
            )
        newnode = tree.nodes.new(response.to_node)
        if newnode.arm_version != replacement.to_node_version:
            raise LookupError(
                "the provided NodeReplacement doesn't seem to correspond to the node needing replacement"
            )

        # some misc. properties
        newnode.parent = node.parent
        newnode.location = node.location
        newnode.select = node.select

        # now, use the `replacement` to hook up the new node correctly
        # start by applying defaults
        for prop_name, prop_value in replacement.property_defaults.items():
            setattr(newnode, prop_name, prop_value)
        for input_id, input_value in replacement.input_defaults.items():
            input_socket = newnode.inputs[input_id]
            if isinstance(input_socket,
                          arm.logicnode.arm_sockets.ArmCustomSocket):
                if input_socket.arm_socket_type != 'NONE':
                    input_socket.default_value_raw = input_value
            elif input_socket.type != 'SHADER':
                # note: shader-type sockets don't have a default value...
                input_socket.default_value = input_value

        # map properties
        for src_prop_name, dest_prop_name in replacement.property_mapping.items(
        ):
            setattr(newnode, dest_prop_name, getattr(node, src_prop_name))

        # map inputs
        for src_socket_id, dest_socket_id in replacement.in_socket_mapping.items(
        ):
            src_socket = node.inputs[src_socket_id]
            dest_socket = newnode.inputs[dest_socket_id]
            if src_socket.is_linked:
                # an input socket only has one link
                datasource_socket = src_socket.links[0].from_socket
                tree.links.new(datasource_socket, dest_socket)
            else:
                if isinstance(dest_socket,
                              arm.logicnode.arm_sockets.ArmCustomSocket):
                    if dest_socket.arm_socket_type != 'NONE':
                        dest_socket.default_value_raw = src_socket.default_value_raw
                elif dest_socket.type != 'SHADER':
                    # note: shader-type sockets don't have a default value...
                    dest_socket.default_value = src_socket.default_value

        # map outputs
        for src_socket_id, dest_socket_id in replacement.out_socket_mapping.items(
        ):
            dest_socket = newnode.outputs[dest_socket_id]
            for link in node.outputs[src_socket_id].links:
                tree.links.new(dest_socket, link.to_socket)
    else:
        print(response)
    tree.nodes.remove(node)
Example #7
0
def parse_material_output(node: bpy.types.Node,
                          custom_particle_node: bpy.types.Node):
    global particle_info

    parse_surface = state.parse_surface
    parse_opacity = state.parse_opacity
    parse_displacement = state.parse_displacement
    state.emission_found = False
    particle_info = {
        'index': False,
        'age': False,
        'lifetime': False,
        'location': False,
        'size': False,
        'velocity': False,
        'angular_velocity': False
    }
    state.sample_bump = False
    state.sample_bump_res = ''
    state.procedurals_written = False
    wrd = bpy.data.worlds['Arm']

    # Surface
    if parse_surface or parse_opacity:
        state.parents = []
        state.parsed = set()
        state.normal_parsed = False
        curshader = state.frag
        state.curshader = curshader

        out_basecol, out_roughness, out_metallic, out_occlusion, out_specular, out_opacity, out_emission = parse_shader_input(
            node.inputs[0])
        if parse_surface:
            curshader.write('basecol = {0};'.format(out_basecol))
            curshader.write('roughness = {0};'.format(out_roughness))
            curshader.write('metallic = {0};'.format(out_metallic))
            curshader.write('occlusion = {0};'.format(out_occlusion))
            curshader.write('specular = {0};'.format(out_specular))
            if '_Emission' in wrd.world_defs:
                curshader.write('emission = {0};'.format(out_emission))
        if parse_opacity:
            curshader.write('opacity = {0} - 0.0002;'.format(out_opacity))

    # Volume
    # parse_volume_input(node.inputs[1])

    # Displacement
    if parse_displacement and disp_enabled() and node.inputs[2].is_linked:
        state.parents = []
        state.parsed = set()
        state.normal_parsed = False
        rpdat = arm.utils.get_rp()
        if rpdat.arm_rp_displacement == 'Tessellation' and state.tese is not None:
            state.curshader = state.tese
        else:
            state.curshader = state.vert
        out_disp = parse_displacement_input(node.inputs[2])
        state.curshader.write('vec3 disp = {0};'.format(out_disp))

    if custom_particle_node is not None:
        if not (parse_displacement and disp_enabled()
                and node.inputs[2].is_linked):
            state.parents = []
            state.parsed = set()
        state.normal_parsed = False

        state.curshader = state.vert
        custom_particle_node.parse(state.curshader, state.con)
Example #8
0
def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str:
    """Parses the vector/color output value from the given node and socket."""
    node_parser_funcs: Dict[str, Callable] = {
        'ATTRIBUTE': nodes_input.parse_attribute,

        # RGB outputs
        'RGB': nodes_input.parse_rgb,
        'TEX_BRICK': nodes_texture.parse_tex_brick,
        'TEX_CHECKER': nodes_texture.parse_tex_checker,
        'TEX_ENVIRONMENT': nodes_texture.parse_tex_environment,
        'TEX_GRADIENT': nodes_texture.parse_tex_gradient,
        'TEX_IMAGE': nodes_texture.parse_tex_image,
        'TEX_MAGIC': nodes_texture.parse_tex_magic,
        'TEX_MUSGRAVE': nodes_texture.parse_tex_musgrave,
        'TEX_NOISE': nodes_texture.parse_tex_noise,
        'TEX_POINTDENSITY': nodes_texture.parse_tex_pointdensity,
        'TEX_SKY': nodes_texture.parse_tex_sky,
        'TEX_VORONOI': nodes_texture.parse_tex_voronoi,
        'TEX_WAVE': nodes_texture.parse_tex_wave,
        'VERTEX_COLOR': nodes_input.parse_vertex_color,
        'BRIGHTCONTRAST': nodes_color.parse_brightcontrast,
        'GAMMA': nodes_color.parse_gamma,
        'HUE_SAT': nodes_color.parse_huesat,
        'INVERT': nodes_color.parse_invert,
        'MIX_RGB': nodes_color.parse_mixrgb,
        'BLACKBODY': nodes_converter.parse_blackbody,
        'VALTORGB': nodes_converter.parse_valtorgb,  # ColorRamp
        'CURVE_VEC': nodes_vector.parse_curvevec,  # Vector Curves
        'CURVE_RGB': nodes_color.parse_curvergb,
        'COMBHSV': nodes_converter.parse_combhsv,
        'COMBRGB': nodes_converter.parse_combrgb,
        'WAVELENGTH': nodes_converter.parse_wavelength,

        # Vector outputs
        'CAMERA': nodes_input.parse_camera,
        'NEW_GEOMETRY': nodes_input.parse_geometry,
        'HAIR_INFO': nodes_input.parse_hairinfo,
        'OBJECT_INFO': nodes_input.parse_objectinfo,
        'PARTICLE_INFO': nodes_input.parse_particleinfo,
        'TANGENT': nodes_input.parse_tangent,
        'TEX_COORD': nodes_input.parse_texcoord,
        'UVMAP': nodes_input.parse_uvmap,
        'BUMP': nodes_vector.parse_bump,
        'MAPPING': nodes_vector.parse_mapping,
        'NORMAL': nodes_vector.parse_normal,
        'NORMAL_MAP': nodes_vector.parse_normalmap,
        'VECT_TRANSFORM': nodes_vector.parse_vectortransform,
        'COMBXYZ': nodes_converter.parse_combxyz,
        'VECT_MATH': nodes_converter.parse_vectormath,
        'DISPLACEMENT': nodes_vector.parse_displacement,
        'VECTOR_ROTATE': nodes_vector.parse_vectorrotate,
    }

    if node.type in node_parser_funcs:
        return node_parser_funcs[node.type](node, socket, state)

    elif node.type == 'GROUP':
        return parse_group(node, socket)

    elif node.type == 'GROUP_INPUT':
        return parse_group_input(node, socket)

    elif node.type == 'CUSTOM':
        if node.bl_idname == 'ArmShaderDataNode':
            return node.parse(state.frag, state.vert)

    log.warn(f'Material node type {node.type} not supported')
    return "vec3(0, 0, 0)"