├── Nodes ├── ShaderNodeCompare.py ├── ShaderNodeDisplacementBake.py ├── ShaderNodeInterpolate.py ├── ShaderNodeLoop.py ├── ShaderNodeNormalBake.py ├── ShaderNodeSwitchFloat.py └── __init__.py ├── README.md ├── ShaderNodeBase.py └── __init__.py /Nodes/ShaderNodeCompare.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Node Authors: Secrop 4 | # 5 | # Node Description: Compare Node 6 | # 7 | # version: (0,1,1) 8 | # 9 | 10 | import bpy 11 | from ShaderNodeBase import ShaderNodeCompact 12 | 13 | class ShaderNodeCompare(ShaderNodeCompact): 14 | 15 | bl_name='ShaderNodeCompare' 16 | bl_label='Compare' 17 | bl_icon='NONE' 18 | 19 | def init(self, context): 20 | name=self.bl_name + '_nodetree' 21 | if bpy.data.node_groups.find(name)>-1: 22 | self.node_tree=bpy.data.node_groups[name] 23 | for ind in [0,2,3,4,5]: 24 | self.outputs[ind].enabled=False 25 | else: 26 | self.node_tree=bpy.data.node_groups.new(name, 'ShaderNodeTree') 27 | if hasattr(self.node_tree, 'is_hidden'): 28 | self.node_tree.is_hidden=True 29 | self.addNodes([('NodeGroupInput', {'name':'Group Input'}), 30 | ('NodeGroupOutput', {'name':'Group Output'}), 31 | ('ShaderNodeMath', {'name':'Math.002', 'operation':'MAXIMUM', 'use_clamp':0.0}), 32 | ('ShaderNodeMath', {'name':'Greater', 'operation':'GREATER_THAN', 'use_clamp':0.0}), 33 | ('ShaderNodeMath', {'name':'Equal', 'operation':'LESS_THAN', 'use_clamp':0.0, 'inputs[1].default_value':0.500}), 34 | ('ShaderNodeMath', {'name':'GreaterEqual', 'operation':'MAXIMUM', 'use_clamp':0.0}), 35 | ('ShaderNodeMath', {'name':'LessEqual', 'operation':'MAXIMUM', 'use_clamp':0.0}), 36 | ('ShaderNodeMath', {'name':'Less', 'operation':'LESS_THAN', 'use_clamp':0.0}), 37 | ('ShaderNodeMath', {'name':'NotEqual', 'operation':'GREATER_THAN', 'use_clamp':0.0, 'inputs[1].default_value':0.500}), 38 | ('ShaderNodeMath', {'name':'Sim01', 'operation':'SUBTRACT', 'use_clamp':0.0}), 39 | ('ShaderNodeMath', {'name':'Sim02', 'operation':'ABSOLUTE', 'use_clamp':0.0}), 40 | ('ShaderNodeMath', {'name':'Similar', 'operation':'LESS_THAN', 'use_clamp':0.0}),]) 41 | self.addInputs([('NodeSocketFloat', {'name':'Threshold', 'default_value':0.500, 'min_value':0.0, 'max_value':10000.0, 'enabled':False}), 42 | ('NodeSocketFloat', {'name':'A', 'default_value':0.500, 'min_value':-10000.0, 'max_value':10000.0}), 43 | ('NodeSocketFloat', {'name':'B', 'default_value':0.500, 'min_value':-10000.0, 'max_value':10000.0})]) 44 | self.addOutputs([('NodeSocketInt', {'name':'Value', 'enabled':False}), 45 | ('NodeSocketInt', {'name':'Value', 'enabled':False}), 46 | ('NodeSocketInt', {'name':'Value'}), 47 | ('NodeSocketInt', {'name':'Value', 'enabled':False}), 48 | ('NodeSocketInt', {'name':'Value', 'enabled':False}), 49 | ('NodeSocketInt', {'name':'Value', 'enabled':False}), 50 | ('NodeSocketInt', {'name':'Value', 'enabled':False})]) 51 | self.addLinks([('inputs[0]', 'nodes["Similar"].inputs[1]'), 52 | ('inputs[1]', 'nodes["Greater"].inputs[0]'), 53 | ('inputs[2]', 'nodes["Greater"].inputs[1]'), 54 | ('inputs[1]', 'nodes["Less"].inputs[0]'), 55 | ('inputs[2]', 'nodes["Less"].inputs[1]'), 56 | ('inputs[1]', 'nodes["Sim01"].inputs[0]'), 57 | ('inputs[2]', 'nodes["Sim01"].inputs[1]'), 58 | ('nodes["Greater"].outputs[0]', 'outputs[0]'), 59 | ('nodes["Less"].outputs[0]', 'outputs[4]'), 60 | ('nodes["Greater"].outputs[0]', 'nodes["Math.002"].inputs[0]'), 61 | ('nodes["Less"].outputs[0]', 'nodes["Math.002"].inputs[1]'), 62 | ('nodes["Math.002"].outputs[0]', 'nodes["NotEqual"].inputs[0]'), 63 | ('nodes["Equal"].outputs[0]', 'outputs[2]'), 64 | ('nodes["Math.002"].outputs[0]', 'nodes["Equal"].inputs[0]'), 65 | ('nodes["Greater"].outputs[0]', 'nodes["GreateEqual"].inputs[0]'), 66 | ('nodes["Equal"].outputs[0]', 'nodes["GreateEqual"].inputs[1]'), 67 | ('nodes["GreateEqual"].outputs[0]', 'outputs[1]'), 68 | ('nodes["Equal"].outputs[0]', 'nodes["LessEqual"].inputs[0]'), 69 | ('nodes["Less"].outputs[0]', 'nodes["LessEqual"].inputs[1]'), 70 | ('nodes["LessEqual"].outputs[0]', 'outputs[3]'), 71 | ('nodes["NotEqual"].outputs[0]', 'outputs[5]'), 72 | ('nodes["Sim01"].outputs[0]', 'nodes["Sim02"].inputs[0]'), 73 | ('nodes["Sim02"].outputs[0]', 'nodes["Similar"].inputs[0]'), 74 | ('nodes["Similar"].outputs[0]', 'outputs[6]')]) 75 | 76 | def ops_update(self, context): 77 | outs=[] 78 | if self.operation=='Similar To': 79 | self.inputs[0].enabled=True 80 | else: 81 | self.inputs[0].enabled=False 82 | for i in self.outputs: 83 | if i.is_linked: 84 | for link in i.links: 85 | outs.append(link.to_socket) 86 | context.space_data.edit_tree.links.remove(link) 87 | i.enabled=False 88 | ind=self.bl_rna.properties['operation'].enum_items.find(self.operation) 89 | self.outputs[ind].enabled=True 90 | for ln in outs: 91 | context.space_data.edit_tree.links.new(self.outputs[ind], ln) 92 | 93 | 94 | ops_items=[('Greater Than', 'Greater Than', 'Greater Than'), ('Greater Than or Equal To', 'Greater Than or Equal To', 'Greater Than or Equal To'), ('Equal To', 'Equal To', 'Equal To'), ('Less Than or Equal To', 'Less Than or Equal To', 'Less Than or Equal To'), ('Less Than', 'Less Than', 'Less Than'), ('Not Equal To', 'Not Equal To', 'Not Equal To'), ('Similar To', 'Similar To', 'Similar To')] 95 | operation=bpy.props.EnumProperty(default = 'Equal To', items = ops_items, name = "Operation", update = ops_update) 96 | 97 | def free(self): 98 | if self.node_tree.users==1: 99 | bpy.data.node_groups.remove(self.node_tree, do_unlink=True) 100 | 101 | def draw_buttons(self, context, layout): 102 | layout.prop(self, 'operation', text='') 103 | 104 | def draw_label(self): 105 | idx=self.bl_rna.properties['operation'].enum_items.find(self.operation) 106 | return self.bl_rna.properties['operation'].enum_items[idx].name 107 | 108 | def draw_menu(): 109 | return 'SH_NEW_CONVERTOR' , 'Converter' 110 | -------------------------------------------------------------------------------- /Nodes/ShaderNodeDisplacementBake.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Node Authors: nudelZ, Secrop 4 | # 5 | # Node Description: Utility node for baking various Displacement maps 6 | # 7 | # version: (0,1,1) 8 | # 9 | 10 | import bpy 11 | from ShaderNodeBase import ShaderNodeCompact 12 | 13 | class ShaderNodeDisplacementBake(ShaderNodeCompact): 14 | 15 | bl_name='ShaderNodeDisplacementBake' 16 | bl_label='Displacement Bake' 17 | bl_icon='NONE' 18 | 19 | axis_items=(('X', 'X', 'POS_X'), 20 | ('-X', '-X', 'NEG_X'), 21 | ('Y', 'Y', 'POS_Y'), 22 | ('-Y', '-Y', 'NEG_Y'), 23 | ('Z', 'Z', 'POS_Z'), 24 | ('-Z', '-Z', 'NEG_Z')) 25 | 26 | display_items=(('xyz','xyz', 'xyz'),('fas', 'fas', 'fas')) 27 | 28 | output_items=(('0','Position','Position'),('1','Scalar Displacement','Scalar Displacement'), ('2','Object Vector Displacement','World Displacement'), ('3','Tangent Vector Displacement','Tangent Displacement')) 29 | 30 | def enumupdate(self,context): 31 | def filteraxis(axis): 32 | if axis=='X': 33 | if int((self.zenum-1)%2)==1: 34 | axis = '-' + axis 35 | elif axis=='Y': 36 | if int((self.zenum-1)/4%2)==1: 37 | axis = '-' + axis 38 | elif axis=='Z': 39 | if int((self.zenum-1)/2%2)==1: 40 | axis = '-' + axis 41 | return axis 42 | axis_order=(('Y','Z','X'),('Z','Y','X'),('Y','X','Z'),('X','Y','Z'),('Z','X','Y'),('X','Z','Y')) 43 | seq=axis_order[int(self.zenum/8%6)] 44 | self.axis_X=filteraxis(seq[0]) 45 | self.axis_Y=filteraxis(seq[1]) 46 | self.axis_Z=filteraxis(seq[2]) 47 | 48 | def axisupdate(self, context): 49 | if (self.axis_X.lstrip('-')==self.axis_Y.lstrip('-')) or (self.axis_X.lstrip('-')==self.axis_Z.lstrip('-')) or (self.axis_Y.lstrip('-')==self.axis_Z.lstrip('-')): 50 | return 51 | for i, cmp in enumerate([self.axis_X, self.axis_Y, self.axis_Z]): 52 | if self.outvalue=='2': 53 | fromsocket=self.node_tree.nodes["WorldSep"].outputs[i] 54 | elif self.outvalue=='3': 55 | fromsocket=self.node_tree.nodes[cmp.lstrip('-')+'Dot'].outputs[1] 56 | tosocket=self.node_tree.nodes['Combine XYZ'].inputs[i] 57 | if tosocket.is_linked: 58 | self.node_tree.links.remove(tosocket.links[0]) 59 | self.node_tree.links.new(fromsocket, tosocket) 60 | if cmp.startswith('-'): 61 | self.node_tree.nodes['FlipArray'].inputs[i].default_value=-1 62 | else: 63 | self.node_tree.nodes['FlipArray'].inputs[i].default_value=1 64 | 65 | def uvmapupdate(self, context): 66 | self.node_tree.nodes['Tangent'].uv_map=self.uvmap 67 | 68 | def outputupdate(self, context): 69 | nexit=self.node_tree.nodes["Emission"] 70 | if nexit.inputs[0].is_linked: 71 | self.node_tree.links.remove(nexit.inputs[0].links[0]) 72 | if nexit.inputs[1].is_linked: 73 | self.node_tree.links.remove(nexit.inputs[1].links[0]) 74 | 75 | if self.outvalue=='0': 76 | if self.inputs[0].is_linked: 77 | context.space_data.edit_tree.links.remove(self.inputs[0].links[0]) 78 | if self.inputs[1].is_linked: 79 | context.space_data.edit_tree.links.remove(self.inputs[1].links[0]) 80 | self.inputs[0].hide=True 81 | self.inputs[1].hide=True 82 | position=self.node_tree.nodes['Coordinates'].outputs[3] 83 | self.node_tree.links.new(position, nexit.inputs[0]) 84 | nexit.inputs[1].default_value=1 85 | else: 86 | self.inputs[0].hide=False 87 | self.inputs[1].hide=False 88 | if self.outvalue=='1': 89 | input=self.node_tree.nodes['YDot'].outputs[1] 90 | else: 91 | input=self.node_tree.nodes['Multiply'].outputs[0] 92 | self.axisupdate(context) 93 | self.node_tree.links.new(input , nexit.inputs[0]) 94 | self.node_tree.links.new(self.node_tree.nodes['Group Input'].outputs[1], nexit.inputs[1]) 95 | 96 | 97 | axis_X = bpy.props.EnumProperty(default = 'X', items = axis_items, name = "X_list", update = axisupdate) 98 | axis_Y = bpy.props.EnumProperty(default = 'Y',items = axis_items, name = "Y_list", update = axisupdate) 99 | axis_Z = bpy.props.EnumProperty(default = 'Z',items = axis_items, name = "Z_list", update = axisupdate) 100 | zenum = bpy.props.IntProperty(default = 25, name = 'FlipAndSwitch', min = 1, max = 48, update = enumupdate) 101 | uvmap = bpy.props.StringProperty(name = 'UV Map',default = '', update = uvmapupdate) 102 | display = bpy.props.EnumProperty(default = 'FlipAndSwitch' , items = (('XYZ','XYZ', 'Custom XYZ'),('FlipAndSwitch', 'ZBrush', 'Zbrush Compatible'))) # to be added : , ('Presets', 'Presets', 'Presets') 103 | outvalue = bpy.props.EnumProperty(default = '0' , items = output_items, name = 'Output', update=outputupdate) 104 | 105 | def init(self, context): 106 | self.width = 200 107 | self.node_tree = bpy.data.node_groups.new(self.bl_name+'nodetree', 'ShaderNodeTree') 108 | self.node_tree.is_hidden=True 109 | self.addNodes([('NodeGroupInput', {'name':'Group Input'}), 110 | ('NodeGroupOutput', {'name':'Group Output'}), 111 | ('ShaderNodeEmission', {'name':'Emission'}), 112 | ('ShaderNodeTexCoord', {'name':'Coordinates'}), 113 | ('ShaderNodeTangent', {'name':'Tangent', 'direction_type':'UV_MAP'}), 114 | ('ShaderNodeVectorMath', {'name':'SubPosition', 'operation':'SUBTRACT'}), 115 | ('ShaderNodeSeparateXYZ', {'name':'WorldSep'}), 116 | ('ShaderNodeVectorMath', {'name':'XDot', 'operation':'DOT_PRODUCT'}), 117 | ('ShaderNodeVectorMath', {'name':'ZDot', 'operation':'DOT_PRODUCT'}), 118 | ('ShaderNodeVectorMath', {'name':'YDot', 'operation':'DOT_PRODUCT'}), 119 | ('ShaderNodeVectorMath', {'name':'Vector Math', 'operation':'CROSS_PRODUCT'}), 120 | ('ShaderNodeCombineXYZ', {'name':'Combine XYZ'}), 121 | ('ShaderNodeMixRGB', {'name':'Multiply', 'blend_type':'MULTIPLY', 'use_clamp':False, 'inputs[0].default_value':1.0}), 122 | ('ShaderNodeCombineXYZ', {'name':'FlipArray', 'inputs[0].default_value':1.0, 'inputs[1].default_value':1.0, 'inputs[2].default_value':1.0})]) 123 | self.addInputs([('NodeSocketVector', {'name':'Position (High Resolution)', 'hide_value':True, 'hide':True}), 124 | ('NodeSocketFloat', {'name':'Scale', 'default_value':1.0, 'min_value':0.0, 'max_value':10.0, 'hide':True})]) 125 | self.addOutputs([('NodeSocketShader', {'name':'Vector'})]) 126 | self.addLinks([('nodes["Vector Math"].outputs[0]', 'nodes["ZDot"].inputs[1]'), 127 | ('inputs[0]', 'nodes["SubPosition"].inputs[0]'), 128 | ('nodes["Coordinates"].outputs[3]', 'nodes["Emission"].inputs[0]'), 129 | ('nodes["Emission"].outputs[0]', 'outputs[0]'), 130 | ('nodes["Coordinates"].outputs[3]', 'nodes["SubPosition"].inputs[1]'), 131 | ('nodes["Coordinates"].outputs[1]', 'nodes["Vector Math"].inputs[0]'), 132 | ('nodes["Coordinates"].outputs[1]', 'nodes["YDot"].inputs[1]'), 133 | ('nodes["Tangent"].outputs[0]', 'nodes["XDot"].inputs[1]'), 134 | ('nodes["Tangent"].outputs[0]', 'nodes["Vector Math"].inputs[1]'), 135 | ('nodes["SubPosition"].outputs[0]', 'nodes["XDot"].inputs[0]'), 136 | ('nodes["SubPosition"].outputs[0]', 'nodes["YDot"].inputs[0]'), 137 | ('nodes["SubPosition"].outputs[0]', 'nodes["ZDot"].inputs[0]'), 138 | ('nodes["SubPosition"].outputs[0]', 'nodes["WorldSep"].inputs[0]'), 139 | ('nodes["FlipArray"].outputs[0]', 'nodes["Multiply"].inputs[1]'), 140 | ('nodes["Combine XYZ"].outputs[0]', 'nodes["Multiply"].inputs[2]'), 141 | ('nodes["XDot"].outputs[1]', 'nodes["Combine XYZ"].inputs[0]'), 142 | ('nodes["YDot"].outputs[1]', 'nodes["Combine XYZ"].inputs[1]'), 143 | ('nodes["ZDot"].outputs[1]', 'nodes["Combine XYZ"].inputs[2]')]) 144 | 145 | def draw_buttons(self, context, layout): 146 | layout.prop(self, 'outvalue', text='') 147 | if self.outvalue=='2': 148 | row=layout.row(align=True) 149 | row.alert=(self.axis_X.lstrip('-')==self.axis_Y.lstrip('-') or self.axis_X.lstrip('-')==self.axis_Z.lstrip('-')) 150 | row.prop(self, 'axis_X', text='') 151 | row.alert=(self.axis_Y.lstrip('-')==self.axis_X.lstrip('-') or self.axis_Y.lstrip('-')==self.axis_Z.lstrip('-')) 152 | row.prop(self, 'axis_Y', text='') 153 | row.alert=(self.axis_Z.lstrip('-')==self.axis_X.lstrip('-') or self.axis_Z.lstrip('-')==self.axis_Y.lstrip('-')) 154 | row.prop(self, 'axis_Z', text='') 155 | elif self.outvalue=='3': 156 | if self.display=='FlipAndSwitch': 157 | row=layout.row() 158 | row.prop(self, 'zenum', text='FlipAndSwitch:') 159 | elif self.display=='XYZ': 160 | row=layout.row(align=True) 161 | row.alert=(self.axis_X.lstrip('-')==self.axis_Y.lstrip('-') or self.axis_X.lstrip('-')==self.axis_Z.lstrip('-')) 162 | row.prop(self, 'axis_X', text='') 163 | row.alert=(self.axis_Y.lstrip('-')==self.axis_X.lstrip('-') or self.axis_Y.lstrip('-')==self.axis_Z.lstrip('-')) 164 | row.prop(self, 'axis_Y', text='') 165 | row.alert=(self.axis_Z.lstrip('-')==self.axis_X.lstrip('-') or self.axis_Z.lstrip('-')==self.axis_Y.lstrip('-')) 166 | row.prop(self, 'axis_Z', text='') 167 | row=layout.row() 168 | row.prop_search(self, "uvmap", context.active_object.data, "uv_layers", icon='GROUP_UVS') 169 | 170 | def draw_buttons_ext(self, context, layout): 171 | if self.outvalue=='3': 172 | layout.prop(self, 'display', text='Interface:') 173 | 174 | def copy(self, node): 175 | self.node_tree=node.node_tree.copy() 176 | 177 | def free(self): 178 | bpy.data.node_groups.remove(self.node_tree) 179 | 180 | def draw_menu(): 181 | return 'SH_NEW_BakeTools' , 'Bake Nodes' 182 | -------------------------------------------------------------------------------- /Nodes/ShaderNodeInterpolate.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Node Authors: Secrop 4 | # 5 | # Node Description: Interpolation Node 6 | # 7 | # version: (0,1,0) 8 | # 9 | 10 | import bpy 11 | from ShaderNodeBase import ShaderNodeCompact 12 | 13 | class ShaderNodeInterpolate(ShaderNodeCompact): 14 | 15 | bl_name='ShaderNodeInterpolate' 16 | bl_label='Interpolate' 17 | bl_icon='NONE' 18 | 19 | interpolation_list=[('LIN', 'Lerp', 'Lerp'), 20 | ('SMTS', 'SmoothStep', 'SmoothStep'), 21 | ('SMTRS', 'SmootherStep', 'SmootherStep'), 22 | ('HP', 'HighPower', 'HighPower'), 23 | ('IHP', 'Invert HighPower', 'InvHighPower'), 24 | ('SIN', 'Sine', 'Sine'), 25 | ('ISIN', 'Invert Sine', 'InvSine'), 26 | ('COS', 'Cos', 'CosPi'), 27 | ('CTMR', 'Catmull-Rom', 'Catmull-Rom')] 28 | 29 | def interpol_update(self, context): 30 | out=self.node_tree.nodes['MIXENTRY'].inputs[0] 31 | if out.is_linked: 32 | self.node_tree.links.remove(out.links[0]) 33 | self.inputs[1].enabled=False 34 | self.inputs[2].enabled=False 35 | self.inputs[3].enabled=False 36 | if self.interpolation=='LIN': 37 | src='inputs[0]' 38 | elif self.interpolation=='SMTS': 39 | src='nodes["SmoothStep"].outputs[0]' 40 | elif self.interpolation=='SMTRS': 41 | src='nodes["SmootherStep"].outputs[0]' 42 | elif self.interpolation=='HP': 43 | self.inputs[1].enabled=True 44 | src='nodes["HighPower"].outputs[0]' 45 | elif self.interpolation=='IHP': 46 | self.inputs[1].enabled=True 47 | src='nodes["InvHighPower"].outputs[0]' 48 | elif self.interpolation=='SIN': 49 | src='nodes["Sin"].outputs[0]' 50 | elif self.interpolation=='ISIN': 51 | src='nodes["InvSin"].outputs[0]' 52 | elif self.interpolation=='COS': 53 | src='nodes["Cos"].outputs[0]' 54 | elif self.interpolation=='CTMR': 55 | self.inputs[2].enabled=True 56 | self.inputs[3].enabled=True 57 | src='nodes["CATMULLROM"].outputs[0]' 58 | self.addLinks([(src, out)]) 59 | 60 | 61 | interpolation=bpy.props.EnumProperty(name='interpolation', items=interpolation_list, default='LIN', update=interpol_update) 62 | 63 | 64 | def init(self, context): 65 | name=self.bl_name + '_nodetree' 66 | self.node_tree=bpy.data.node_groups.new(name, 'ShaderNodeTree') 67 | if hasattr(self.node_tree, 'is_hidden'): 68 | self.node_tree.is_hidden=True 69 | self.addNodes([('NodeGroupInput', {'name':'Group Input'}), 70 | ('NodeGroupOutput', {'name':'Group Output'}), 71 | ('ShaderNodeMath', {'name':'CAT01', 'operation':'SUBTRACT', 'use_clamp':0.0, 'inputs[0].default_value':1.0}), 72 | ('ShaderNodeMath', {'name':'CAT03', 'operation':'MULTIPLY', 'use_clamp':0.0, 'inputs[1].default_value':2.0}), 73 | ('ShaderNodeMath', {'name':'CAT04', 'operation':'ADD', 'use_clamp':0.0, 'inputs[1].default_value':4.0}), 74 | ('ShaderNodeMath', {'name':'CAT05', 'operation':'SUBTRACT', 'use_clamp':0.0}), 75 | ('ShaderNodeMath', {'name':'CAT07', 'operation':'MULTIPLY', 'use_clamp':0.0}), 76 | ('ShaderNodeMath', {'name':'CAT09', 'operation':'SUBTRACT', 'use_clamp':0.0}), 77 | ('ShaderNodeMath', {'name':'CAT10', 'operation':'SUBTRACT', 'use_clamp':0.0, 'inputs[1].default_value':3.0}), 78 | ('ShaderNodeMath', {'name':'CAT12', 'operation':'MULTIPLY', 'use_clamp':0.0}), 79 | ('ShaderNodeMath', {'name':'CAT08', 'operation':'ADD', 'use_clamp':0.0}), 80 | ('ShaderNodeMath', {'name':'CAT13', 'operation':'ADD', 'use_clamp':0.0}), 81 | ('ShaderNodeMath', {'name':'CATMULLROM', 'operation':'MULTIPLY', 'use_clamp':0.0, 'inputs[1].default_value':0.5}), 82 | ('ShaderNodeMath', {'name':'IS02', 'operation':'MULTIPLY', 'use_clamp':0.0, 'inputs[1].default_value':1.571}), 83 | ('ShaderNodeMath', {'name':'IS03', 'operation':'SINE', 'use_clamp':0.0, 'inputs[1].default_value':1.571}), 84 | ('ShaderNodeMath', {'name':'InvSin', 'operation':'SUBTRACT', 'use_clamp':0.0, 'inputs[0].default_value':1.0}), 85 | ('ShaderNodeMath', {'name':'Sin', 'operation':'SINE', 'use_clamp':0.0, 'inputs[1].default_value':0.5}), 86 | ('ShaderNodeMath', {'name':'IHP2', 'operation':'POWER', 'use_clamp':0.0}), 87 | ('ShaderNodeMath', {'name':'InvHighPower', 'operation':'SUBTRACT', 'use_clamp':0.0, 'inputs[0].default_value':1.0}), 88 | ('ShaderNodeMath', {'name':'SMM03', 'operation':'MULTIPLY', 'use_clamp':0.0, 'inputs[1].default_value':6.0}), 89 | ('ShaderNodeMath', {'name':'SMM04', 'operation':'SUBTRACT', 'use_clamp':0.0, 'inputs[1].default_value':15.0}), 90 | ('ShaderNodeMath', {'name':'SMM05', 'operation':'MULTIPLY', 'use_clamp':0.0}), 91 | ('ShaderNodeMath', {'name':'SMM06', 'operation':'ADD', 'use_clamp':0.0, 'inputs[1].default_value':10.0}), 92 | ('ShaderNodeMath', {'name':'SM03', 'operation':'SUBTRACT', 'use_clamp':0.0, 'inputs[0].default_value':3.0}), 93 | ('ShaderNodeMath', {'name':'SM02', 'operation':'MULTIPLY', 'use_clamp':0.0, 'inputs[1].default_value':2.0}), 94 | ('ShaderNodeMath', {'name':'HighPower', 'operation':'POWER', 'use_clamp':0.0}), 95 | ('ShaderNodeMath', {'name':'IHP01', 'operation':'SUBTRACT', 'use_clamp':0.0, 'inputs[0].default_value':1.0}), 96 | ('ShaderNodeMath', {'name':'S01', 'operation':'MULTIPLY', 'use_clamp':0.0, 'inputs[1].default_value':1.571}), 97 | ('ShaderNodeMath', {'name':'IS01', 'operation':'SUBTRACT', 'use_clamp':0.0, 'inputs[0].default_value':1.0}), 98 | ('ShaderNodeMath', {'name':'CAT02', 'operation':'MULTIPLY', 'use_clamp':0.0}), 99 | ('ShaderNodeMath', {'name':'SmoothStep', 'operation':'MULTIPLY', 'use_clamp':0.0}), 100 | ('ShaderNodeMath', {'name':'SmootherStep', 'operation':'MULTIPLY', 'use_clamp':0.0}), 101 | ('ShaderNodeMath', {'name':'VPOW3', 'operation':'MULTIPLY', 'use_clamp':0.0}), 102 | ('ShaderNodeMath', {'name':'VPOW2', 'operation':'MULTIPLY', 'use_clamp':0.0}), 103 | ('ShaderNodeMath', {'name':'C03', 'operation':'MULTIPLY', 'use_clamp':0.0, 'inputs[1].default_value':0.5}), 104 | ('ShaderNodeMath', {'name':'Cos', 'operation':'SUBTRACT', 'use_clamp':0.0, 'inputs[0].default_value':0.5}), 105 | ('ShaderNodeMath', {'name':'C02', 'operation':'COSINE', 'use_clamp':0.0, 'inputs[1].default_value':0.5}), 106 | ('ShaderNodeMath', {'name':'C01', 'operation':'MULTIPLY', 'use_clamp':0.0, 'inputs[1].default_value':-3.142}), 107 | ('ShaderNodeMath', {'name':'MIX01', 'operation':'SUBTRACT', 'use_clamp':0.0, 'inputs[0].default_value':1.0}), 108 | ('NodeReroute', {'name':'MIXENTRY'}), 109 | ('ShaderNodeMath', {'name':'MIX02', 'operation':'MULTIPLY', 'use_clamp':0.0}), 110 | ('ShaderNodeMath', {'name':'MIX03', 'operation':'MULTIPLY', 'use_clamp':0.0}), 111 | ('ShaderNodeMath', {'name':'MIX04', 'operation':'ADD', 'use_clamp':0.0})]) 112 | self.addInputs([('NodeSocketFloat', {'name':'Fac', 'default_value':0.0, 'min_value':0.0, 'max_value':1.0}), 113 | ('NodeSocketFloat', {'name':'Power', 'default_value':2.0, 'min_value':1.0, 'enabled':False}), 114 | ('NodeSocketFloat', {'name':'Catmull1', 'default_value':0.0, 'enabled':False}), 115 | ('NodeSocketFloat', {'name':'Catmull2', 'default_value':1.0, 'enabled':False}), 116 | ('NodeSocketFloat', {'name':'Value1', 'default_value':0.0}), 117 | ('NodeSocketFloat', {'name':'Value2', 'default_value':1.0})]) 118 | self.addOutputs([('NodeSocketFloat', {'name':'Value'})]) 119 | self.addLinks([('nodes["MIX01"].outputs[0]', 'nodes["MIX03"].inputs[0]'), 120 | ('nodes["MIX03"].outputs[0]', 'nodes["MIX04"].inputs[0]'), 121 | ('nodes["MIX02"].outputs[0]', 'nodes["MIX04"].inputs[1]'), 122 | ('nodes["MIXENTRY"].outputs[0]', 'nodes["MIX01"].inputs[1]'), 123 | ('nodes["MIXENTRY"].outputs[0]', 'nodes["MIX02"].inputs[0]'), 124 | ('nodes["SM02"].outputs[0]', 'nodes["SM03"].inputs[1]'), 125 | ('nodes["VPOW2"].outputs[0]', 'nodes["SmoothStep"].inputs[1]'), 126 | ('nodes["MIX04"].outputs[0]', 'outputs[0]'), 127 | ('nodes["SMM04"].outputs[0]', 'nodes["SMM05"].inputs[1]'), 128 | ('nodes["VPOW3"].outputs[0]', 'nodes["SmootherStep"].inputs[1]'), 129 | ('nodes["SMM03"].outputs[0]', 'nodes["SMM04"].inputs[0]'), 130 | ('nodes["SMM05"].outputs[0]', 'nodes["SMM06"].inputs[0]'), 131 | ('nodes["IHP01"].outputs[0]', 'nodes["IHP2"].inputs[0]'), 132 | ('nodes["IHP2"].outputs[0]', 'nodes["InvHighPower"].inputs[1]'), 133 | ('nodes["S01"].outputs[0]', 'nodes["Sin"].inputs[0]'), 134 | ('nodes["IS02"].outputs[0]', 'nodes["IS03"].inputs[0]'), 135 | ('nodes["IS01"].outputs[0]', 'nodes["IS02"].inputs[0]'), 136 | ('nodes["IS03"].outputs[0]', 'nodes["InvSin"].inputs[1]'), 137 | ('nodes["C01"].outputs[0]', 'nodes["C02"].inputs[0]'), 138 | ('nodes["C02"].outputs[0]', 'nodes["C03"].inputs[0]'), 139 | ('nodes["C03"].outputs[0]', 'nodes["Cos"].inputs[1]'), 140 | ('nodes["CAT01"].outputs[0]', 'nodes["CAT02"].inputs[1]'), 141 | ('nodes["CAT04"].outputs[0]', 'nodes["CAT05"].inputs[0]'), 142 | ('nodes["CAT07"].outputs[0]', 'nodes["CAT08"].inputs[1]'), 143 | ('nodes["CAT08"].outputs[0]', 'nodes["CAT13"].inputs[0]'), 144 | ('nodes["CAT12"].outputs[0]', 'nodes["CAT13"].inputs[1]'), 145 | ('nodes["CAT13"].outputs[0]', 'nodes["CATMULLROM"].inputs[0]'), 146 | ('nodes["CAT05"].outputs[0]', 'nodes["CAT07"].inputs[1]'), 147 | ('nodes["CAT02"].outputs[0]', 'nodes["CAT08"].inputs[0]'), 148 | ('nodes["CAT03"].outputs[0]', 'nodes["CAT04"].inputs[0]'), 149 | ('nodes["CAT09"].outputs[0]', 'nodes["CAT10"].inputs[0]'), 150 | ('nodes["CAT10"].outputs[0]', 'nodes["CAT12"].inputs[1]'), 151 | ('inputs[1]', 'nodes["HighPower"].inputs[1]'), 152 | ('inputs[1]', 'nodes["IHP2"].inputs[1]'), 153 | ('inputs[2]', 'nodes["CAT01"].inputs[1]'), 154 | ('inputs[2]', 'nodes["CAT03"].inputs[0]'), 155 | ('inputs[2]', 'nodes["CAT09"].inputs[1]'), 156 | ('inputs[3]', 'nodes["CAT05"].inputs[1]'), 157 | ('inputs[3]', 'nodes["CAT09"].inputs[0]'), 158 | ('inputs[4]', 'nodes["MIX03"].inputs[1]'), 159 | ('inputs[5]', 'nodes["MIX02"].inputs[1]'), 160 | ('inputs[0]', 'nodes["VPOW2"].inputs[0]'), 161 | ('inputs[0]', 'nodes["VPOW2"].inputs[1]'), 162 | ('inputs[0]', 'nodes["SM02"].inputs[0]'), 163 | ('nodes["VPOW2"].outputs[0]', 'nodes["VPOW3"].inputs[0]'), 164 | ('inputs[0]', 'nodes["VPOW3"].inputs[1]'), 165 | ('inputs[0]', 'nodes["SMM05"].inputs[0]'), 166 | ('inputs[0]', 'nodes["SMM03"].inputs[0]'), 167 | ('inputs[0]', 'nodes["HighPower"].inputs[0]'), 168 | ('inputs[0]', 'nodes["IHP01"].inputs[1]'), 169 | ('inputs[0]', 'nodes["S01"].inputs[0]'), 170 | ('inputs[0]', 'nodes["IS01"].inputs[1]'), 171 | ('inputs[0]', 'nodes["C01"].inputs[0]'), 172 | ('inputs[0]', 'nodes["CAT02"].inputs[0]'), 173 | ('nodes["VPOW2"].outputs[0]', 'nodes["CAT07"].inputs[0]'), 174 | ('nodes["VPOW3"].outputs[0]', 'nodes["CAT12"].inputs[0]'), 175 | ('nodes["SM03"].outputs[0]', 'nodes["SmoothStep"].inputs[0]'), 176 | ('nodes["SMM06"].outputs[0]', 'nodes["SmootherStep"].inputs[0]'), 177 | ('inputs[0]', 'nodes["MIXENTRY"].inputs[0]')]) 178 | 179 | def copy(self, node): 180 | self.node_tree=node.node_tree.copy() 181 | 182 | def free(self): 183 | bpy.data.node_groups.remove(self.node_tree, do_unlink=True) 184 | 185 | def draw_buttons(self, context, layout): 186 | layout.prop(self, "interpolation", text='') 187 | 188 | def draw_label(self): 189 | idx=self.bl_rna.properties['interpolation'].enum_items.find(self.interpolation) 190 | return self.bl_rna.properties['interpolation'].enum_items[idx].name 191 | 192 | def draw_menu(): 193 | return 'SH_NEW_CONVERTOR' , 'Converter' 194 | 195 | -------------------------------------------------------------------------------- /Nodes/ShaderNodeLoop.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Node Authors: Secrop 4 | # 5 | # Node Description: Node for creating loops over some nodegroup 6 | # 7 | # version: (0,0,3) 8 | # 9 | 10 | import bpy 11 | 12 | class ShaderNodeLoop(bpy.types.NodeCustomGroup): 13 | 14 | bl_name='ShaderNodeLoop' 15 | bl_label='Loop Node' 16 | 17 | def nodegroups(self, context): 18 | nt=context.space_data.edit_tree 19 | list=[('None','None','None')] 20 | for nd in nt.nodes: 21 | if nd.type=='GROUP': 22 | ng=nd.node_tree 23 | if ng.inputs.get('iterator'): 24 | list.append((ng.name, ng.name, ng.name)) 25 | return list 26 | 27 | def __nodeinterface_setup__(self): 28 | self.node_tree.outputs.clear() 29 | self.node_tree.inputs.clear() 30 | if self.step_nodegroup=='None': 31 | return 32 | for input in bpy.data.node_groups[self.step_nodegroup].inputs: 33 | if not input.name=='iterator': 34 | self.node_tree.nodes['Group Input'].outputs.new(input.bl_socket_idname, input.name) 35 | self.node_tree.inputs.new(input.bl_socket_idname, input.name) 36 | for output in bpy.data.node_groups[self.step_nodegroup].outputs: 37 | self.node_tree.nodes['Group Output'].inputs.new(output.bl_socket_idname, output.name) 38 | self.node_tree.outputs.new(output.bl_socket_idname, output.name) 39 | 40 | def __nodetree_setup__(self): 41 | self.node_tree.links.clear() 42 | for node in self.node_tree.nodes: 43 | if not node.name in ['Group Input','Group Output']: 44 | self.node_tree.nodes.remove(node) 45 | if self.step_nodegroup=='None': 46 | return 47 | previousnode=self.node_tree.nodes['Group Input'] 48 | for iter in range(self.iterations): 49 | curnode=self.node_tree.nodes.new('ShaderNodeGroup') 50 | curnode.node_tree=bpy.data.node_groups[self.step_nodegroup] 51 | curnode.inputs['iterator'].default_value=iter 52 | for input in curnode.inputs: 53 | poutput=previousnode.outputs.get(input.name) 54 | if poutput: 55 | self.node_tree.links.new(poutput, input) 56 | if iter==self.iterations-1: 57 | for input in self.node_tree.nodes['Group Output'].inputs: 58 | poutput=curnode.outputs.get(input.name) 59 | if poutput: 60 | self.node_tree.links.new(poutput, input) 61 | else: 62 | previousnode=curnode 63 | 64 | def update_nt(self, context): 65 | self.__nodeinterface_setup__() 66 | self.__nodetree_setup__() 67 | 68 | def update_it(self, context): 69 | self.__nodetree_setup__() 70 | 71 | step_nodegroup=bpy.props.EnumProperty(name="step_nodegroup", items=nodegroups, update=update_nt) 72 | 73 | iterations=bpy.props.IntProperty(name="iterations", min=1, max=63, default=8, update=update_it) 74 | 75 | 76 | def init(self, context): 77 | self.node_tree=bpy.data.node_groups.new(self.bl_name, 'ShaderNodeTree') 78 | if hasattr(self.node_tree, 'is_hidden'): 79 | self.node_tree.is_hidden=True 80 | self.node_tree.nodes.new('NodeGroupInput') 81 | self.node_tree.nodes.new('NodeGroupOutput') 82 | 83 | def draw_buttons(self, context, layout): 84 | row=layout.row() 85 | row.alert=(self.step_nodegroup=='None') 86 | row.prop(self, 'step_nodegroup', text='') 87 | row=layout.row() 88 | row.prop(self, 'iterations', text='iterations') 89 | 90 | def copy(self, node): 91 | self.node_tree=node.node_tree.copy() 92 | 93 | def free(self): 94 | bpy.data.node_groups.remove(self.node_tree, do_unlink=True) 95 | -------------------------------------------------------------------------------- /Nodes/ShaderNodeNormalBake.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Node Authors: Secrop 4 | # 5 | # Node Description: Utility node for baking various Displacement maps 6 | # 7 | # version: (0,1,1) 8 | # 9 | 10 | import bpy 11 | from ShaderNodeBase import ShaderNodeCompact 12 | 13 | class ShaderNodeNormalBake(ShaderNodeCompact): 14 | 15 | bl_name='ShaderNodeNormalBake' 16 | bl_label='Normal Bake' 17 | bl_icon='NONE' 18 | 19 | axis_items=(('X', 'X', 'POS_X'), 20 | ('-X', '-X', 'NEG_X'), 21 | ('Y', 'Y', 'POS_Y'), 22 | ('-Y', '-Y', 'NEG_Y'), 23 | ('Z', 'Z', 'POS_Z'), 24 | ('-Z', '-Z', 'NEG_Z')) 25 | 26 | def axisupdate(self, context): 27 | if (self.axis_X.lstrip('-')==self.axis_Y.lstrip('-')) or (self.axis_X.lstrip('-')==self.axis_Z.lstrip('-')) or (self.axis_Y.lstrip('-')==self.axis_Z.lstrip('-')): 28 | return 29 | for i, cmp in enumerate([self.axis_X, self.axis_Y, self.axis_Z]): 30 | orig=['X','Y','Z'].index(cmp.lstrip('-')) 31 | if cmp.startswith('-'): 32 | fromsocket=self.node_tree.nodes['Negative'].outputs[orig] 33 | else: 34 | fromsocket=self.node_tree.nodes['Positive'].outputs[orig] 35 | tosocket=self.node_tree.nodes['FinalCombine'].inputs[i] 36 | if tosocket.is_linked: 37 | self.node_tree.links.remove(tosocket.links[0]) 38 | self.node_tree.links.new(fromsocket, tosocket) 39 | 40 | def uvmapupdate(self, context): 41 | self.node_tree.nodes['Tangent'].uv_map=self.uvmap 42 | 43 | axis_X = bpy.props.EnumProperty(default = 'X', items = axis_items, name = "X_list", update = axisupdate) 44 | axis_Y = bpy.props.EnumProperty(default = 'Y',items = axis_items, name = "Y_list", update = axisupdate) 45 | axis_Z = bpy.props.EnumProperty(default = 'Z',items = axis_items, name = "Z_list", update = axisupdate) 46 | uvmap = bpy.props.StringProperty(name = 'UV Map',default = '', update = uvmapupdate) 47 | 48 | def init(self, context): 49 | self.node_tree = bpy.data.node_groups.new(self.bl_name+'nodetree', 'ShaderNodeTree') 50 | if hasattr(self.node_tree, 'is:hidden'): 51 | self.node_tree.is_hidden=True 52 | self.addNodes([('NodeGroupInput', {'name':'Group Input'}), 53 | ('NodeGroupOutput', {'name':'Group Output'}), 54 | ('ShaderNodeNewGeometry', {'name':'Geometry'}), 55 | ('ShaderNodeTangent', {'name':'Tangent', 'direction_type':'UV_MAP'}), 56 | ('ShaderNodeVectorMath', {'name':'CoTangent', 'operation':'CROSS_PRODUCT'}), 57 | ('ShaderNodeVectorMath', {'name':'YDot', 'operation':'DOT_PRODUCT'}), 58 | ('ShaderNodeVectorMath', {'name':'ZDot', 'operation':'DOT_PRODUCT'}), 59 | ('ShaderNodeCombineXYZ', {'name':'Combine XYZ'}), 60 | ('ShaderNodeVectorMath', {'name':'XDot', 'operation':'DOT_PRODUCT'}), 61 | ('ShaderNodeMixRGB', {'name':'Scale', 'blend_type':'MULTIPLY', 'inputs[0].default_value':1.0, 'inputs[2].default_value':[0.5,0.5,0.5,1.0]}), 62 | ('ShaderNodeMixRGB', {'name':'Translate', 'blend_type':'ADD', 'inputs[0].default_value':1.0, 'inputs[2].default_value':[0.5,0.5,0.5,1.0]}), 63 | ('ShaderNodeInvert', {'name':'Invert', 'inputs[0].default_value':1.000}), 64 | ('ShaderNodeSeparateRGB', {'name':'Negative'}), 65 | ('ShaderNodeSeparateRGB', {'name':'Positive'}), 66 | ('ShaderNodeCombineRGB', {'name':'FinalCombine', 'inputs[0].default_value':0.000, 'inputs[1].default_value':0.000, 'inputs[2].default_value':0.000}), 67 | ('ShaderNodeEmission', {'name':'EmiClosure', 'inputs[1].default_value':1.0})]) 68 | self.addInputs([('NodeSocketVector', {'name':'Normal', 'hide_value':True})]) 69 | self.addOutputs([('NodeSocketShader', {'name':'Tangent'})]) 70 | self.addLinks([('nodes["Geometry"].outputs[1]', 'nodes["CoTangent"].inputs[0]'), 71 | ('nodes["Tangent"].outputs[0]', 'nodes["CoTangent"].inputs[1]'), 72 | ('nodes["Geometry"].outputs[1]', 'nodes["ZDot"].inputs[1]'), 73 | ('nodes["CoTangent"].outputs[0]', 'nodes["YDot"].inputs[1]'), 74 | ('nodes["Tangent"].outputs[0]', 'nodes["XDot"].inputs[1]'), 75 | ('nodes["XDot"].outputs[1]', 'nodes["Combine XYZ"].inputs[0]'), 76 | ('nodes["YDot"].outputs[1]', 'nodes["Combine XYZ"].inputs[1]'), 77 | ('nodes["ZDot"].outputs[1]', 'nodes["Combine XYZ"].inputs[2]'), 78 | ('nodes["Combine XYZ"].outputs[0]', 'nodes["Scale"].inputs[1]'), 79 | ('nodes["Scale"].outputs[0]', 'nodes["Translate"].inputs[1]'), 80 | ('nodes["Translate"].outputs[0]', 'nodes["Group Output"].inputs[0]'), 81 | ('inputs[0]', 'nodes["XDot"].inputs[0]'), 82 | ('inputs[0]', 'nodes["YDot"].inputs[0]'), 83 | ('inputs[0]', 'nodes["ZDot"].inputs[0]'), 84 | ('nodes["Translate"].outputs[0]', 'nodes["Invert"].inputs[1]'), 85 | ('nodes["Invert"].outputs[0]', 'nodes["Negative"].inputs[0]'), 86 | ('nodes["Translate"].outputs[0]', 'nodes["Positive"].inputs[0]'), 87 | ('nodes["Positive"].outputs[0]', 'nodes["FinalCombine"].inputs[0]'), 88 | ('nodes["Positive"].outputs[1]', 'nodes["FinalCombine"].inputs[1]'), 89 | ('nodes["Positive"].outputs[2]', 'nodes["FinalCombine"].inputs[2]'), 90 | ('nodes["FinalCombine"].outputs[0]', 'nodes["EmiClosure"].inputs[0]'), 91 | ('nodes["EmiClosure"].outputs[0]', 'outputs[0]')]) 92 | 93 | def copy(self, node): 94 | self.node_tree=node.node_tree.copy() 95 | 96 | def free(self): 97 | bpy.data.node_groups.remove(self.node_tree, do_unlink=True) 98 | 99 | def draw_buttons(self, context, layout): 100 | row=layout.row(align=True) 101 | row.alert=(self.axis_X.lstrip('-')==self.axis_Y.lstrip('-') or self.axis_X.lstrip('-')==self.axis_Z.lstrip('-')) 102 | row.prop(self, 'axis_X', text='') 103 | row.alert=(self.axis_Y.lstrip('-')==self.axis_X.lstrip('-') or self.axis_Y.lstrip('-')==self.axis_Z.lstrip('-')) 104 | row.prop(self, 'axis_Y', text='') 105 | row.alert=(self.axis_Z.lstrip('-')==self.axis_X.lstrip('-') or self.axis_Z.lstrip('-')==self.axis_Y.lstrip('-')) 106 | row.prop(self, 'axis_Z', text='') 107 | row=layout.row() 108 | row.prop_search(self, "uvmap", context.active_object.data, "uv_layers", icon='GROUP_UVS') 109 | 110 | def draw_menu(): 111 | return 'SH_NEW_BakeTools' , 'Bake Nodes' 112 | -------------------------------------------------------------------------------- /Nodes/ShaderNodeSwitchFloat.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Node Authors: Secrop 4 | # 5 | # Node Description: Switch Node 6 | # 7 | # version: (0,1,1) 8 | # 9 | 10 | import bpy 11 | from ShaderNodeBase import ShaderNodeCompact 12 | 13 | class ShaderNodeSwitchFloat(ShaderNodeCompact): 14 | 15 | bl_name='ShaderNodeSwitchFloat' 16 | bl_label='Switch Float' 17 | bl_icon='NONE' 18 | 19 | def init(self, context): 20 | name=self.bl_name + '_nodetree' 21 | if bpy.data.node_groups.find(name)>-1: 22 | self.node_tree=bpy.data.node_groups[name] 23 | else: 24 | self.node_tree=bpy.data.node_groups.new(name, 'ShaderNodeTree') 25 | if hasattr(self.node_tree, 'is_hidden'): 26 | self.node_tree.is_hidden=True 27 | self.addNodes([('NodeGroupInput', {'name':'Group Input'}), 28 | ('NodeGroupOutput', {'name':'Group Output'}), 29 | ('ShaderNodeMath', {'name':'Math', 'operation':'GREATER_THAN', 'use_clamp':0.0, 'inputs[1].default_value':0.0}), 30 | ('ShaderNodeMath', {'name':'Math.002', 'operation':'SUBTRACT', 'use_clamp':0.0, 'inputs[0].default_value':1.0}), 31 | ('ShaderNodeMath', {'name':'Math.003', 'operation':'MULTIPLY', 'use_clamp':0.0}), 32 | ('ShaderNodeMath', {'name':'Math.001', 'operation':'MULTIPLY', 'use_clamp':0.0}), 33 | ('ShaderNodeMath', {'name':'Math.004', 'operation':'ADD', 'use_clamp':0.0})]) 34 | self.addInputs([('NodeSocketInt', {'name':'Switch', 'default_value':0, 'min_value':0, 'max_value':1}), 35 | ('NodeSocketFloat', {'name':'Value1', 'default_value':0.5, 'min_value':-10000.0, 'max_value':10000.0}), 36 | ('NodeSocketFloat', {'name':'Value2', 'default_value':0.5, 'min_value':-10000.0, 'max_value':10000.0})]) 37 | self.addOutputs([('NodeSocketFloat', {'name':'Value'})]) 38 | self.addLinks([('nodes["Math"].outputs[0]', 'nodes["Math.001"].inputs[0]'), 39 | ('nodes["Math"].outputs[0]', 'nodes["Math.002"].inputs[1]'), 40 | ('nodes["Math.002"].outputs[0]', 'nodes["Math.003"].inputs[0]'), 41 | ('nodes["Math.003"].outputs[0]', 'nodes["Math.004"].inputs[0]'), 42 | ('nodes["Math.001"].outputs[0]', 'nodes["Math.004"].inputs[1]'), 43 | ('nodes["Group Input"].outputs[0]', 'nodes["Math"].inputs[0]'), 44 | ('nodes["Group Input"].outputs[1]', 'nodes["Math.003"].inputs[1]'), 45 | ('nodes["Group Input"].outputs[2]', 'nodes["Math.001"].inputs[1]'), 46 | ('nodes["Math.004"].outputs[0]', 'nodes["Group Output"].inputs[0]')]) 47 | 48 | def free(self): 49 | if self.node_tree.users==1: 50 | bpy.data.node_groups.remove(self.node_tree, do_unlink=True) 51 | 52 | def draw_menu(): 53 | return 'SH_NEW_CONVERTOR' , 'Converter' 54 | -------------------------------------------------------------------------------- /Nodes/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | ignore_list=['__init__.py', 'ShaderNodeBase.py'] 4 | def listNodes(): 5 | nodelist=[] 6 | for subdir, dirs, files in os.walk(os.path.dirname(__file__)): 7 | current_dir = os.path.basename(subdir) 8 | if current_dir == '__pycache__': 9 | continue 10 | for file in files: 11 | if file in ignore_list: 12 | continue 13 | if not file.endswith('.py'): 14 | continue 15 | nodelist.append(file[:-3]) 16 | return nodelist 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Note: this version is for blender 2.7x 2 | 3 | If you're using 2.80+, please look here: http://github.com/Secrop/ShaderNodesExtra2.80 4 | 5 | # ShaderNodesExtra 6 | Utilities for Cycles PyNodes 7 | 8 | This Addon serves the purpose of simplifying the creation of new custom nodes for Cycles, and it can be considered as a better API for custom nodes. 9 | The nodes produced by this addon are GPU compatible, and internally they work as an ordinary nodegroup. The main difference is that it's possible to change completly the node layout and add functionality to the node. 10 | 11 | Most of it will work in background, and visibly there's not much to see yet. 12 | 13 | Apart from the typical node functions (draw_buttons, draw_buttons_ext, update, free, copy, etc), the nodes integrate the following API(s): 14 | 15 | ShaderNodeBase API: 16 | addNode(nodeType, **nodeAttributes) 17 | delNode(node) 18 | addInput(socketType, **socketAttributes) 19 | delInput(socket) 20 | addOutput(socketType, **socketAttributes) 21 | delOutput(socket) 22 | addLink(socket, socket) 23 | delLink(link) 24 | 25 | Nodes created with this API should call setupTree() in the init() function, and should have a defaultNodeTree() to define the initial node configuration. 26 | 27 | For now, while there isn't any MenuEditor, nodes are in charged to define their menu categories. This is not a good approach as a design perspective, and editing the menu will soon become something aside from the custom nodes. 28 | 29 | The addon includes a converter operator, that can automatically write a custom node based in some nodegroup. 30 | To use it, select the nodegroup you want to convert into a script, press SPACE and look for 'Convert Selected Nodegroup to PyNode'. It will prompt for a bl_name and a bl_label. For consistency, bl_name should start by 'ShaderNode', thouhgt there's nothing to stop one from naming it whatever they want. 31 | The node will be automatically added to the 'Custom Nodes' category, and to change this, one should add a draw_menu function to the node script (located in the 'Nodes' folder inside the Addon path). 32 | 33 | At the moment, I'm also sharing some nodes with the purpose to show what is possible.. 34 | 35 | -DisplacementBake: is a node to help baking displacement maps in a similar fashion as in ZBrush. (Credits to nudelZ) 36 | 37 | -NormalBake: will help the creation of tangent normal maps from a normal vector. 38 | 39 | -SwitchFloat: is a simple switch, if the 'Switch' input is set to 0, the output will be the 'value1'; if 1, the output will be 'value2' 40 | 41 | -Compare: this node will compare two values and return a boolean. It features '>', '>=', '==', '!=', '<=', '<' and '~='. 42 | 43 | -Interpolate: as the name says, it will interpolate between two values. Defaults to Lerp(linear interpolation), but also features 'SmoothStep', 'SmootherStep', 'HighPower' and its inverse, 'Sine' and its inverse, 'Cosine' and 'Catmull-Rom' interpolations. 44 | 45 | -Loop: A more complex node that uses a nodegroup as a loop step. The nodegroup requires to have an input socket named 'iterator' for feeding the node with the step integer, and at least 1 output with the same name as the input for the data that is trasnfer from each cycle to the next. 46 | -------------------------------------------------------------------------------- /ShaderNodeBase.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ShaderNodeBase(bpy.types.NodeCustomGroup): 4 | 5 | def __path_resolve__(self, obj, path): 6 | if "." in path: 7 | extrapath, path= path.rsplit(".", 1) 8 | obj = obj.path_resolve(extrapath) 9 | return obj, path 10 | 11 | def value_get(self, obj, path): 12 | obj, path=self.__path_resolve__(obj, path) 13 | return getattr(obj, path) 14 | 15 | def value_set(self, obj, path, val): 16 | obj, path=self.__path_resolve__(obj, path) 17 | setattr(obj, path, val) 18 | 19 | def setupTree(self, unique=False): 20 | name=self.bl_name + '_nodetree' 21 | if unique or bpy.data.node_groups.find(name)==-1: 22 | self.node_tree=bpy.data.node_groups.new(name + '_nodetree', 'ShaderNodeTree') 23 | self.addNode('NodeGroupInput', {'name':'Group Input'}) 24 | self.addNode('NodeGroupOutput', {'name':'Group Output'}) 25 | if hasattr(self.node_tree, 'is_hidden'): 26 | self.node_tree.is_hidden=True 27 | if hasattr(self, 'defaultNodeTree'): 28 | self.defaultNodeTree() 29 | elif hasattr(self, 'defaultNodeScript'): 30 | import ShadeNodeScript 31 | ShaderNodeScript.compile(self, name) 32 | else: 33 | print('No default NodeTree found') 34 | else: 35 | self.node_tree=bpy.data.node_groups[name] 36 | self.nodes=self.node_tree.nodes 37 | self.links=self.node_tree.links 38 | 39 | def addNode(self, nodetype, attrs): 40 | node=self.node_tree.nodes.new(nodetype) 41 | for attr in attrs: 42 | self.value_set(node, attr, attrs[attr]) 43 | return node 44 | 45 | def delNode(self, node): 46 | if isinstance(node, str): 47 | node=self.node_tree.path_resolve(node) 48 | self.node_tree.nodes.remove(node) 49 | 50 | def addLink(self, socketFrom, socketTo): 51 | if isinstance(socketFrom, str): 52 | if socketFrom.startswith('inputs'): 53 | socketFrom=self.node_tree.path_resolve('nodes["Group Input"].outputs' + socketFrom[socketFrom.rindex('['):]) 54 | else: 55 | socketFrom=self.node_tree.path_resolve(socketFrom) 56 | else: 57 | socketFrom=link[0] 58 | if isinstance(socketTo, str): 59 | if socketTo.startswith('outputs'): 60 | socketTo=self.node_tree.path_resolve('nodes["Group Output"].inputs' + socketTo[socketTo.rindex('['):]) 61 | else: 62 | socketTo=self.node_tree.path_resolve(socketTo) 63 | else: 64 | socketTo=link[1] 65 | self.node_tree.links.new(socketFrom, socketTo) 66 | 67 | def delLink(self, link): 68 | if isinstance(link, str): 69 | link=self.node_tree.path_resolve(link) 70 | self.node_tree.links.remove(link) 71 | 72 | def addInput(self, sockettype, attrs): 73 | name = attrs.pop('name') 74 | socketInterface=self.node_tree.inputs.new(sockettype, name) 75 | socket=self.path_resolve(socketInterface.path_from_id()) 76 | for attr in attrs: 77 | if attr in ['default_value', 'hide', 'hide_value']: 78 | self.value_set(socket, attr, attrs[attr]) 79 | else: 80 | self.value_set(socketInterface, attr, attrs[attr]) 81 | return socket 82 | 83 | def delInput(self, socket): 84 | if isinstance(socket, str): 85 | socket=self.node_tree.path_resolve(socket) 86 | self.node_tree.inputs.remove(socket) 87 | 88 | def addOutput(self, sockettype, attrs): 89 | name = attrs.pop('name') 90 | socketInterface=self.node_tree.outputs.new(sockettype, name) 91 | socket=self.path_resolve(socketInterface.path_from_id()) 92 | for attr in attrs: 93 | if attr in ['default_value']: 94 | self.value_set(socket, attr, attrs[attr]) 95 | else: 96 | self.value_set(socketInterface, attr, attrs[attr]) 97 | return socket 98 | 99 | def delOutput(self, socket): 100 | if isinstance(socket, str): 101 | socket=self.node_tree.path_resolve(socket) 102 | self.node_tree.outputs.remove(socket) 103 | 104 | class ShaderNodeCompact(bpy.types.NodeCustomGroup): 105 | def __path_resolve__(self, obj, path): 106 | if "." in path: 107 | extrapath, path= path.rsplit(".", 1) 108 | obj = obj.path_resolve(extrapath) 109 | return obj, path 110 | 111 | def value_set(self, obj, path, val): 112 | obj, path=self.__path_resolve__(obj, path) 113 | setattr(obj, path, val) 114 | 115 | def addNodes(self, nodes): 116 | for nodeitem in nodes: 117 | node=self.node_tree.nodes.new(nodeitem[0]) 118 | for attr in nodeitem[1]: 119 | self.value_set(node, attr, nodeitem[1][attr]) 120 | 121 | def addLinks(self, links): 122 | for link in links: 123 | if isinstance(link[0], str): 124 | if link[0].startswith('inputs'): 125 | socketFrom=self.node_tree.path_resolve('nodes["Group Input"].outputs' + link[0][link[0].rindex('['):]) 126 | else: 127 | socketFrom=self.node_tree.path_resolve(link[0]) 128 | else: 129 | socketFrom=link[0] 130 | if isinstance(link[1], str): 131 | if link[1].startswith('outputs'): 132 | socketTo=self.node_tree.path_resolve('nodes["Group Output"].inputs' + link[1][link[1].rindex('['):]) 133 | else: 134 | socketTo=self.node_tree.path_resolve(link[1]) 135 | else: 136 | socketTo=link[1] 137 | self.node_tree.links.new(socketFrom, socketTo) 138 | 139 | def addInputs(self, inputs): 140 | for inputitem in inputs: 141 | name = inputitem[1].pop('name') 142 | socketInterface=self.node_tree.inputs.new(inputitem[0], name) 143 | socket=self.path_resolve(socketInterface.path_from_id()) 144 | for attr in inputitem[1]: 145 | if attr in ['default_value', 'hide', 'hide_value', 'enabled']: 146 | self.value_set(socket, attr, inputitem[1][attr]) 147 | else: 148 | self.value_set(socketInterface, attr, inputitem[1][attr]) 149 | 150 | def addOutputs(self, outputs): 151 | for outputitem in outputs: 152 | name = outputitem[1].pop('name') 153 | socketInterface=self.node_tree.outputs.new(outputitem[0], name) 154 | socket=self.path_resolve(socketInterface.path_from_id()) 155 | for attr in outputitem[1]: 156 | if attr in ['default_value', 'hide', 'hide_value', 'enabled']: 157 | self.value_set(socket, attr, outputitem[1][attr]) 158 | else: 159 | self.value_set(socketInterface, attr, outputitem[1][attr]) 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | 2 | bl_info = { 3 | "name": "ShaderNodesExtra", 4 | "author": "Secrop", 5 | "version": (0, 1, 6), 6 | "blender": (2, 78, 0), 7 | "location": "Node", 8 | "description": "Tools for NodeGroups", 9 | "warning": "This is still is alpha testing. Although, the basic API is complete, please keep your original nodegroups as backup", 10 | "wiki_url": "", 11 | "category": "Node", 12 | } 13 | 14 | import bpy 15 | import os, sys, importlib 16 | import nodeitems_utils 17 | from nodeitems_utils import NodeCategory, NodeItem, NodeItemCustom 18 | from nodeitems_builtins import ShaderNewNodeCategory, node_tree_group_type, group_tools_draw, node_group_items 19 | sys.path.append(os.path.dirname(os.path.realpath(__file__))) 20 | import Nodes 21 | 22 | 23 | #default menu entry if node doesn't have a draw_menu function 24 | menu_def_id='SH_EXTRA' 25 | menu_def_name='Custom Nodes' 26 | 27 | 28 | def NodesPath(): 29 | path=os.path.dirname(os.path.realpath(__file__)) + '/Nodes' 30 | return path 31 | 32 | def exportNodetree(nodetree, bl_name, bl_label): 33 | forbidden_nodes=['ShaderNodeTexImage','ShaderNodeTexEnviroment','ShaderNodeTexSky', 'ShaderNodeMapping'] 34 | nodes_with_from_dupli=['ShaderNodeTexCoord', 'ShaderNodeUVMap'] 35 | nodes_with_distribution=['ShaderNodeBsdfGlossy', 'ShaderNodeBsdfRefraction', 'ShaderNodeBsdfGlass', 'ShaderNodeBsdfAnisotropic'] 36 | nodes_with_falloff=['ShaderNodeSubsurfaceScattering'] 37 | nodes_with_component=['ShaderNodeBsdfToon', 'ShaderNodeBsdfHair'] 38 | nodes_with_operation=['ShaderNodeMath', 'ShaderNodeVectorMath'] 39 | nodes_with_blend_type=['ShaderNodeMixRGB'] 40 | nodes_with_clamp=['ShaderNodeMixRGB', 'ShaderNodeMath'] 41 | nodes_with_outputs=['ShaderNodeRGB', 'ShaderNodeValue', 'ShaderNodeNormal'] 42 | nodes_with_pixel_size=['ShaderNodeWireframe'] 43 | 44 | def socketattrs(socket): 45 | items=['name'] 46 | if socket.bl_socket_idname!='NodeSocketShader': 47 | items.append('default_value') 48 | if socket.bl_socket_idname!='NodeSocketColor' and socket.bl_socket_idname!='NodeSocketBool': 49 | items.append('min_value') 50 | items.append('max_value') 51 | return items 52 | 53 | def nodesattrs(node): 54 | nodeinstance=node.bl_idname 55 | items=['name'] 56 | if nodeinstance in nodes_with_from_dupli: 57 | items.append('from_dupli') 58 | if nodeinstance in nodes_with_distribution: 59 | items.append('distribution') 60 | if nodeinstance in nodes_with_falloff: 61 | items.append('falloff') 62 | if nodeinstance in nodes_with_component: 63 | items.append('component') 64 | if nodeinstance in nodes_with_operation: 65 | items.append('operation') 66 | if nodeinstance in nodes_with_blend_type: 67 | items.append('blend_type') 68 | if nodeinstance in nodes_with_clamp: 69 | items.append('use_clamp') 70 | if nodeinstance in nodes_with_pixel_size: 71 | items.append('use_pixel_size') 72 | if nodeinstance in nodes_with_outputs: 73 | items.append('outputs[0]') 74 | return items 75 | for input in node.inputs: 76 | if input.is_linked==False and input.bl_idname!='NodeSocketShader': 77 | items.append(input.path_from_id().rsplit(".",1)[1] + '.default_value') 78 | return items 79 | 80 | def getpath(obj, path): 81 | if "." in path: 82 | path_prop, path_attr = path.rsplit(".", 1) 83 | prop = obj.path_resolve(path_prop) 84 | else: 85 | prop = obj 86 | path_attr = path 87 | return prop, path_attr 88 | 89 | def value_get(obj, path): 90 | prop, path_attr=getpath(obj, path) 91 | return getattr(prop, path_attr) 92 | 93 | def formatVal(val): 94 | result='' 95 | if str(type(val))=="": #this is really ugly! 96 | result+='[' 97 | for n, comp in enumerate(val): 98 | result+="{0:.3f}".format(comp) 99 | if n < len(val)-1: 100 | result+=',' 101 | else: 102 | result+=']' 103 | elif str(type(val))=="": 104 | result+='\'' + val + '\'' 105 | else: 106 | flt="{0:.3f}".format(val) 107 | flt.rstrip('0') 108 | if flt.endswith('.'): 109 | flt+='0' 110 | result+=flt 111 | return result 112 | 113 | filepath=NodesPath() +'/' + bl_name + '.py' 114 | file=open(filepath, 'w') 115 | file.write('import bpy\n') 116 | file.write('from ShaderNodeBase import ShaderNodeBase\n\n') 117 | file.write('class ' + bl_name + '(ShaderNodeBase):\n\n') 118 | file.write(' bl_name=\'' + bl_name + '\'\n') 119 | file.write(' bl_label=\'' + bl_label + '\'\n') 120 | file.write(' bl_icon=\'NONE\'\n\n') 121 | file.write(' def defaultNodeTree(self):\n') 122 | #Write Nodes creation functions 123 | for node in nodetree.nodes: 124 | if node.bl_idname!='NodeGroupInput' and node.bl_idname!='NodeGroupOutput': 125 | file.write(' self.addNode(\'' + node.bl_idname + '\', ') 126 | file.write('{') 127 | attrs=nodesattrs(node) 128 | for i, attr in enumerate(attrs): 129 | val=value_get(node, attr) 130 | file.write('\'' + attr + '\':' + formatVal(val)) 131 | if i