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
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
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)
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
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)
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)
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)"