This is an working draft of a script to create cycles materials. You feed it a control file and it generates the material for you.
The input at first looks like this:
# b
* texvoronoi
* texvoronoi
* texmusgrave
* texnoise
# c
* texcoord
* mapping
The ‘*’ lines are the names of the shader classes, less the ‘ShaderNode’ on the front; one of these per node. The ‘#’ lines are comments. The result will be another file that you can edit to create the input to the second phase, which you run through the script again to create the actual nodes.
The input in its second form looks like this:
&ShaderNodeTexVoronoi/texvoronoi0 # ---------------- texvoronoi0
texvoronoi0.Vector < mapping5.Vector
texvoronoi0.Scale = 2.7
texvoronoi0.coloring := 'CELLS'
Significance:
&nodeclass/name – generates a node
innode.input < outnode.output – generates a link
node.input = value – sets the (‘default’) value of an input
node.attribute := value – this sets an attribute of the node object
These lines do not need to be in order; you can refer to a node before you create it.
Code for the script
#!/usr/bin/python
# -*- coding: utf-8 -*-
import bpy
import re
print ('------
')
class Material:
def set_cycles(self):
scn = bpy.context.scene
if not scn.render.engine == 'CYCLES':
scn.render.engine = 'CYCLES'
def make_material(self, name):
if name in bpy.data.materials:
bpy.data.materials.remove(bpy.data.materials[name])
self.mat = bpy.data.materials.new(name)
self.mat.use_nodes = True
self.nodes = self.mat.node_tree.nodes
def link(
self,
from_node,
from_slot_name,
to_node,
to_slot_name,
):
input = to_node.inputs[to_slot_name]
output = from_node.outputs[from_slot_name]
self.mat.node_tree.links.new(input, output)
def makeNode(self, type, name):
self.node = self.nodes.new(type)
self.node.name = name
self.xpos += 200
self.node.location = (self.xpos, self.ypos)
return self.node
def genCode(self, node, name):
outf.write('&%s/%s # ---------------- %s
' % (node, name, name))
n = m.makeNode(node, name)
i = 0
inputNames = {}
outputNames = {}
for ni in n.inputs:
if ni.name not in inputNames:
inputNames[ni.name] = 0
inputNames[ni.name] += 1
for no in n.outputs:
if no.name not in outputNames:
outputNames[no.name] = 0
outputNames[no.name] += 1
for ni in n.inputs:
indic = ('\'' + ni.name + '\'' if inputNames[ni.name]
== 1 else i)
className = re.sub('NodeSocket', '', ni.__class__.__name__ )
outf.write('%s.%s < # %s
' % (name, ni.name, className))
i += 1
i = 0
outf.write('# Outputs: ')
for no in n.outputs:
indic = (no.name if outputNames[no.name] == 1 else i)
className = re.sub('NodeSocket', '', no.__class__.__name__ )
outf.write('%s.%s (%s) ' % (name, indic, className))
i += 1
outf.write('
')
for p in dir(n):
if p[0] != '_' and p not in self.inherited_properties:
outf.write('%s.%s :=
' % (name, p))
return n
def new_row():
self.xpos = 0
self.ypos += 200
def __init__(self):
self.xpos = 0
self.ypos = 0
self.inherited_properties = {}
for p in [
'as_pointer',
'bl_description',
'bl_height_default',
'bl_height_max',
'bl_height_min',
'bl_icon',
'bl_idname',
'bl_label',
'bl_static_type',
'bl_width_default',
'bl_width_max',
'bl_width_min',
'color',
'copy',
'dimensions',
'draw_buttons',
'draw_buttons_ext',
'driver_add',
'driver_remove',
'free',
'get',
'height',
'hide',
'id_data',
'init',
'inputs',
'internal_links',
'is_property_hidden',
'is_property_set',
'is_registered_node_type',
'items',
'keyframe_delete',
'keyframe_insert',
'keys',
'label',
'location',
'mute',
'name',
'outputs',
'parent',
'path_from_id',
'path_resolve',
'poll',
'poll_instance',
'property_unset',
'select',
'Shaderpoll',
'show_options',
'show_preview',
'show_texture',
'socket_value_update',
'type',
'type_recast',
'update',
'use_custom_color',
'values',
'width',
'width_hidden',
]:
self.inherited_properties[p] = 1
shaders = {}
dataDir = '/home/martin/git/blend/'
ctlf = open(dataDir+'shader-data.txt', 'r')
for line in ctlf:
shaders[line.rstrip().lower()] = line.strip()
ctlf.close()
m = Material()
m.set_cycles()
m.make_material('test')
inf = open(dataDir+'py/cycles-materials3b-rock-a.txt', 'r')
outf = open(dataDir+'py/cycles-materials3b-rock-a.txt.new', 'w')
reExpand = re.compile(r"\s*\*\s*([^# ]+)")
reComment = re.compile(r"\s*\#.*$")
reNode = re.compile(r"\s*&\s*([a-zA-Z]+)\s*/\s*([A-Za-z0-9]+)")
reLink = \
re.compile(r"\s*([a-zA-Z0-9]+)\s*\.\s*([a-zA-Z0-9]+)\s*<\s*([a-zA-Z0-9]+)\s*\.\s*([a-zA-Z0-9]+)\s*$"
)
reSetVal = re.compile(r"\s*([a-zA-Z0-9]+)\s*\.\s*([a-zA-Z0-9_]+)\s*\=\s*(.*)$")
reSetAttrib = re.compile(r"\s*([a-zA-Z0-9]+)\s*\.\s*([a-zA-Z0-9_]+)\s*:=\s*(.*)$")
nodeIndex = 0
nodes = {}
lineNum = 0
links = []
setVals = []
setAttrs = []
for line in inf:
lineNum += 1
match = reExpand.match(line.strip())
if match:
shaderName = match.group(1)
if shaderName not in shaders:
print ('%d: unknown shader: %s' % (lineNum, shaderName))
continue
m.genCode('ShaderNode' + shaders[shaderName], shaderName + str(nodeIndex))
nodeIndex += 1
continue
#if not re.search('<', line) and not re.search('\\.', line):
outf.write('%s
' % line.rstrip())
useLine = reComment.sub('', line.rstrip())
if useLine == '':
continue
match = reLink.match(useLine)
if match:
link = {'nodeFrom': match.group(3), 'slotFrom': match.group(4),'nodeTo': match.group(1), 'slotTo': match.group(2)}
links.append(link)
continue
match = reSetVal.match(useLine)
if match:
setval = {'nodeName': match.group(1), 'attrib': match.group(2), 'val': match.group(3)}
setVals.append(setval)
continue
match = reSetAttrib.match(useLine)
if match:
setattr = {'nodeName': match.group(1), 'attrib': match.group(2), 'val': match.group(3)}
setAttrs.append(setattr)
continue
match = reNode.match(useLine)
if match:
shaderName, name = match.group(1), match.group(2)
node = m.makeNode(shaderName, name)
nodes[name] = node
continue
if useLine[0] == '-':
m.new_row()
continue
print ('%d: cannot interpret \'%s\'' % (lineNum, useLine))
for link in links:
print ('linking %s %s to %s %s' % (link['nodeFrom'], link['slotFrom'],
link['nodeTo'], link['slotTo']))
if link['nodeFrom'] not in nodes:
print('%d: no node called %s
' % (lineNum, link['nodeFrom']))
continue
if link['nodeTo'] not in nodes:
print('%d: no node called %s
' % (lineNum, link['nodeTo']))
continue
m.link(nodes[link['nodeFrom']], link['slotFrom'], nodes[link['nodeTo']],
link['slotTo'])
for setval in setVals:
if setval['nodeName'] not in nodes:
print('no node called %s
' % setval['nodeName'] )
continue
print('setting %s.%s slot to %s' % (setval['nodeName'], setval['attrib'], setval['val']))
if setval['val'] != '':
nodes[setval['nodeName']].inputs[setval['attrib']].default_value = eval(setval['val'])
continue
for setattr in setAttrs:
if setattr['nodeName'] not in nodes:
print('no node called %s
' % setattr['nodeName'] )
continue
print('setting %s.%s attr to %s' % (setattr['nodeName'], setattr['attrib'], setattr['val']))
if setattr['val'] != '':
cmnd = 'nodes[\'' + setattr['nodeName'] + '\'].' + setattr['attrib'] + ' = ' + setattr['val']
print('running \'%s\'' % cmnd)
exec(cmnd)
outf.close()
inf.close()