def createUnaryOp(self, instruction, function, context): self.createNode(instruction.value, function, context) type = instruction.value.type if (self.isBoolOnlyOp(instruction.op) and type != 'bool'): raise Snixception( instruction.offset, "Unary operation '" + instruction.op + "' can only be applied to booleans!") # Make last use references point to this node if (hasattr(instruction.value, 'name')): instruction.name = instruction.value.name instruction.type = type baseType = self.getBaseType(type) if (baseType == 'float' or type == 'int'): self.createNumericUnaryOp(instruction, function, context) elif (instruction.op == '!'): instruction.node = function.node.addNot(instruction.value.node) else: raise Snixception( instruction.offset, "Unknown unary operation '" + instruction.op + "'!") if (context == 'For'): instruction.node = function.node.addSet(instruction.name, instruction.node, instruction.type)
def createArrayRef(self, instruction, function, context): self.createNode(instruction.variable, function, context) if (instruction.subscript.nodeType != 'Constant' or instruction.subscript.type != 'int'): raise Snixception( instruction.offset, "Subscript index has to be a constant integer, Substance Designer does not support abstract indexing." ) index = int(instruction.subscript.value) type = instruction.variable.type baseType = self.getBaseType(type) typeSize = int(self.getTypeSize(instruction.variable.type)) if (index > typeSize or index < 0): raise Snixception(instruction.offset, "Index out of bounds: '" + str(index) +\ "' (variable '" + instruction.variable.name + "' is size '" + str(typeSize - 1) + "')") else: if (baseType == 'float'): instruction.node = function.node.addSwizzleFloat( instruction.variable.node, str(index)) elif (baseType == 'int'): instruction.node = function.node.addSwizzleInt( instruction.variable.node, str(index)) else: raise Snixception( instruction.offset, "Accessing indices is only supported on float and int type variables!" ) instruction.type = baseType
def createVectorConnector(self, instruction, function, context): node = None valueCount = len(instruction.values) if (valueCount > 1): baseType = instruction.values[0].type endDigit = re.match('(\d)$', baseType) if (endDigit == None): type = str(baseType) + str(valueCount) if (valueCount > 4): raise Snixception(instruction.offset, "Vector overload: max vector size is 4!") # Make sure all values are of the same type for value in instruction.values: if (value.type != baseType): raise Snixception(instruction.offset, "Vector type mismatch: expected '" + baseType + \ "' but got '" + value.type + "'") # Create the necessary vectors if (type == 'float2' or type == 'float3' or type == 'float4'): node = function.node.addVectorFloat(instruction.values[0].node, instruction.values[1].node, 'float2') if (type == 'float3'): node = function.node.addVectorFloat(node, instruction.values[2].node, type) if (type == 'float4'): node2 = function.node.addVectorFloat( instruction.values[2].node, instruction.values[3].node, 'float2') node = function.node.addVectorFloat(node, node2, type) if (type == 'int2' or type == 'int3' or type == 'int4'): node = function.node.addVectorInt(instruction.values[0].node, instruction.values[1].node, type) if (type == 'int3'): node = function.node.addVectorInt(node, instruction.values[2].node, type) if (type == 'int4'): node2 = function.node.addVectorInt(instruction.values[2].node, instruction.values[3].node, 'int2') node = function.node.addVectorInt(node, node2, type) if (node == None): raise Snixception(instruction.offset, "Unknown vector type '" + type + "'") instruction.type = type instruction.node = node elif (valueCount == 1): instruction.type = instruction.values[0].type instruction.node = instruction.values[0].node
def createDeclare(self, instruction, function, context): if (instruction.value != None): self.createNode(instruction.value, function, context) if (instruction.type == instruction.value.type): instruction.node = instruction.value.node else: raise Snixception(instruction.offset, "Can't assign type '" + instruction.value.type + "' to type '" + \ instruction.type + "'") else: raise Snixception( instruction.offset, "Declaring a variable without a value is not allowed!")
def __init__(self, stmt): self.nodeType = 'Input' self.offset = stmt['coord'] self.offsetFix() if (stmt['init'] != None): raise Snixception(stmt['coord'], "You can't set a default value for a function parameter in Substance Designer! ('" + \ stmt['type']['declname'] + "' in function '" + self.name + "')") if (stmt['_nodetype'] != 'Decl' or stmt['type']['_nodetype'] != 'TypeDecl'): raise Snixception(stmt['coord'], "Unknown parameter type for function '" + self.name + "'") self.value = DeclareNode(stmt) self.name = self.value.name self.label = re.sub('_', ' ', self.name).title()
def __init__(self, instruction): self.nodeType = 'FuncCall' self.offset = instruction['coord'] if (instruction['name']['_nodetype'] != 'ID'): raise Snixception(instruction['coord'], "Only named functions can be called!") self.name = instruction['name']['name'] self.args = self.getNode(instruction['args']) self.offsetFix()
def createFunctionCall(self, instruction, function, context): self.createNode(instruction.args, function, context) name = instruction.name if (self.isNativeFunction(name)): self.createNativeCall(instruction, function, context) else: call = None if (name == function.name): raise Snixception( instruction.offset, "Substance Designer does not allow functions calling themselves! (In function: '" + name + "')") for func in self.functions: if (func.name == name): call = func break if (instruction.args != None): args = instruction.args.values else: args = [] if (call == None): raise Snixception( instruction.offset, "Attempted to call undeclared function '" + name + "'!") if (len(args) != len(call.inputs)): raise Snixception(instruction.offset, "Function: '" + name + "' takes " +\ str(len(call.inputs)) + " parameter(s), not " + str(len(args)) + ".") connections = [] for i, input in enumerate(call.inputs): if (self.isTypeCompatible(args[i].type, input.type)): connections.append((input.name, args[i].node)) else: raise Snixception(instruction.offset, "Function '" + name + "' parameter '" + input.name + "' is of type '" + \ input.type + "', passed parameter is of type '" + args[i].type + "'") instruction.type = call.type instruction.name = call.name instruction.node = function.node.addFunctionCall(name, connections)
def getNode(self, instruction): if (instruction != None): if (instruction['_nodetype'] == 'Constant'): return ConstantNode(instruction) elif (instruction['_nodetype'] == 'Decl'): return DeclareNode(instruction) elif (instruction['_nodetype'] == 'ID'): return IDNode(instruction) elif (instruction['_nodetype'] == 'Return'): return ReturnNode(instruction) elif (instruction['_nodetype'] == 'UnaryOp'): return UnaryOpNode(instruction) elif (instruction['_nodetype'] == 'Assignment'): return AssignmentNode(instruction) elif (instruction['_nodetype'] == 'BinaryOp'): return BinaryOpNode(instruction) elif (instruction['_nodetype'] == 'FuncCall'): return FuncCallNode(instruction) elif (instruction['_nodetype'] == 'ExprList'): return ExprListNode(instruction) elif (instruction['_nodetype'] == 'CompoundLiteral'): return CompoundLiteralNode(instruction) elif (instruction['_nodetype'] == 'InitList'): return InitListNode(instruction) elif (instruction['_nodetype'] == 'If'): return IfNode(instruction) elif (instruction['_nodetype'] == 'Compound'): return CompoundNode(instruction) elif (instruction['_nodetype'] == 'ArrayRef'): return ArrayRefNode(instruction) elif (instruction['_nodetype'] == 'Cast'): return CastNode(instruction) elif (instruction['_nodetype'] == 'EmptyStatement'): return EmptyStatementNode(instruction) elif (instruction['_nodetype'] == 'For'): return ForNode(instruction) elif (instruction['_nodetype'] == 'DeclList'): return DeclListNode(instruction) else: if (instruction['coord'] != None): raise Snixception(instruction['coord'], "Unknown node type '" + instruction['_nodetype'] + "'") else: raise Snixception('?', "Unknown node type '" + instruction['_nodetype'] + "'") else: return None
def createReturn(self, instruction, function, context): self.createNode(instruction.value, function, context) instruction.type = instruction.value.type if (instruction.type == function.type): function.output = instruction.value else: raise Snixception(instruction.offset, "Function '" + function.name + "' of type '" + function.type +\ "' cannot return type '" + instruction.type + "'!")
def getLastNodeTypeReference(self, nodeType, function): for node in reversed(function.body.instructions): if (node.nodeType == nodeType): return node if (node != None): return node raise Snixception( instruction.offset, "Attempted to reference a non-existing '" + nodeType + "' statement. Snix sad and confused! :(")
def getVariableDeclare(self, instruction, function, context): node = self.getDeclareInBody(instruction.name, function.body, function) if (node == None): node = self.getOthersByName(instruction.name, function) if (node != None): return node raise Snixception( instruction.offset, "Attempted to use an undeclared variable '" + instruction.name + "'. Snix sad and confused! :(")
def createMathOp(self, instruction, op, function, context): scalarMulTypes = ['float2', 'float3', 'float4'] left = instruction.left.node right = instruction.right.node type = instruction.left.type instruction.type = type baseType = self.getBaseType(type) if (type == 'int' or baseType == 'float'): if (op == '+'): instruction.node = function.node.addAdd(left, right, type) elif (op == '-'): instruction.node = function.node.addSubtract(left, right, type) elif (op == '/'): instruction.node = function.node.addDivide(left, right, type) elif (op == '%'): instruction.node = function.node.addMod(left, right, type) elif (op == '*'): if (instruction.left.type == instruction.right.type): instruction.node = function.node.addMultiply( left, right, type) elif (instruction.left.type == 'float' and instruction.right.type in scalarMulTypes): instruction.node = function.node.addScalarMultiply( right, left, instruction.right.type) instruction.type = instruction.right.type elif (instruction.right.type == 'float' and instruction.left.type in scalarMulTypes): instruction.node = function.node.addScalarMultiply( left, right, instruction.left.type) instruction.type = instruction.left.type else: raise Snixception(instruction.offset, "Unsupported operand '" + op + "'") else: raise Snixception( instruction.offset, "Operand '" + op + "' is incompatible with type '" + type + "'!")
def compile(self, functions, outfile=None): self.populateNativeData() self.functions = [] for function in functions: self.functions.append(self.createFunction(function)) if (function.output != None and function.output.node != None): function.node.setOutput(function.output.node) else: raise Snixception( function.offset, "Function '" + function.name + "' has no return statement!") snixel.compile(outfile)
def createAssignment(self, instruction, function, context): if (context == 'For'): context = 'ForSet' self.createNode(instruction.left, function, context) if (context == 'ForSet'): context = 'ForGet' self.createNode(instruction.right, function, context) if (context == 'ForGet'): context = 'For' if (instruction.left.nodeType == 'ID'): instruction.type = instruction.left.type instruction.name = instruction.left.name baseOp = re.match('([\+\-\*\/])\=', instruction.op) if (instruction.left.type == instruction.right.type): if (instruction.op == '='): if (context == 'For'): instruction.node = function.node.addSet( instruction.name, instruction.right.node, instruction.type) else: instruction.node = instruction.right.node elif (baseOp): self.createMathOp(instruction, baseOp.group(1), function, context) else: raise Snixception( instruction.offset, "Unsupported assign operand: '" + instruction.op + "'") else: raise Snixception(instruction.offset, "Can't assign type '" + instruction.right.type + "' to type '" + \ instruction.type + "'") else: raise Snixception( instruction.offset, "You can only assign values to variables. (Subscripting (assignign to index) also not yet supported.)" )
def createIf(self, instruction, function, context): self.createNode(instruction.iftrue, function, context) self.createNode(instruction.iffalse, function, context) self.createNode(instruction.condition, function, context) if (instruction.iftrue.nodeType != 'Compound' or instruction.iffalse.nodeType != 'Compound'): raise Snixception( instruction.offset, 'If statements need full compound bodies with curly brackets for both the true and false statements!' ) nodeType = instruction.condition.nodeType if (instruction.condition.type == 'bool'): # Get the final nodes for connecting with the If node trueNode = instruction.iftrue.instructions[ len(instruction.iftrue.instructions) - 1] falseNode = instruction.iffalse.instructions[ len(instruction.iffalse.instructions) - 1] if (not self.isTypeCompatible(trueNode.type, falseNode.type)): raise Snixception( instruction.offset, "'If' statement expects type '" + trueNode.type + "', but else outputs type '" + falseNode.type + "'!") instruction.type = trueNode.type instruction.node = function.node.addIf(instruction.condition.node, trueNode.node, falseNode.node, instruction.type) else: raise Snixception( instruction.offset, "Cannot compare type '" + instruction.condition.type + "' in if statement, boolean needed!")
def verifyNativeFunctionTypes(self, instruction, functionName): func = self.getNativeFunctionByName(instruction.name) type = func[0] name = func[1] args = func[2] for i, value in enumerate(instruction.args.values): acceptedList = '' for j in range(len(args[i])): acceptedList += args[i][j] + ', ' if (value.type not in args[i]): raise Snixception(instruction.offset, "Parameter '" + str(i + 1) + "' of function '" + name +\ "' accepts type(s) '" + acceptedList[:-2] + "', but type '" + value.type + "' was passed!") return 1
def createNode(self, instruction, function, context=None): if (instruction != None): if (instruction.nodeType == 'Declare'): self.createDeclare(instruction, function, context) elif (instruction.nodeType == 'Constant'): self.createConstant(instruction, function, context) elif (instruction.nodeType == 'ID'): self.createID(instruction, function, context) elif (instruction.nodeType == 'Return'): self.createReturn(instruction, function, context) elif (instruction.nodeType == 'UnaryOp'): self.createUnaryOp(instruction, function, context) elif (instruction.nodeType == 'Assignment'): self.createAssignment(instruction, function, context) elif (instruction.nodeType == 'BinaryOp'): self.createBinaryOp(instruction, function, context) elif (instruction.nodeType == 'FuncCall'): self.createFunctionCall(instruction, function, context) elif (instruction.nodeType == 'ExprList'): self.createExpressionList(instruction, function, context) elif (instruction.nodeType == 'CompoundLiteral'): self.createCompoundLiteral(instruction, function, context) elif (instruction.nodeType == 'InitList'): self.createInitList(instruction, function, context) elif (instruction.nodeType == 'If'): self.createIf(instruction, function, context) elif (instruction.nodeType == 'Compound'): self.createCompound(instruction, function, context) elif (instruction.nodeType == 'ArrayRef'): self.createArrayRef(instruction, function, context) elif (instruction.nodeType == 'Cast'): self.createCast(instruction, function, context) elif (instruction.nodeType == 'For'): self.createFor(instruction, function, context) elif (instruction.nodeType == 'DeclList'): self.createDeclList(instruction, function, context) elif (instruction.nodeType == 'EmptyStatement'): pass else: raise Snixception( instruction.offset, "Can't create node of unknown type '" + instruction.nodeType + "' (Internal error, please let developer know!)")
def createNumericUnaryOp(self, instruction, function, context): op = instruction.op type = instruction.value.type if (op == '-'): instruction.node = function.node.addNegate(instruction.value.node, type) elif (op == '--' or op == '++'): right = self.createConstantWithValue('1', type, function) if (op == '++'): instruction.node = function.node.addAdd( instruction.value.node, right.node, type) else: instruction.node = function.node.addSubtract( instruction.value.node, right.node, type) else: raise Snixception( instruction.offset, "Unknown unary operation '" + instruction.op + "'!")
def createCast(self, instruction, function, context): self.createNode(instruction.expression, function, context) type = instruction.expression.type toType = instruction.toType if (type == 'int' and toType == 'float'): instruction.node = function.node.addToFloat( instruction.expression.node) elif (type == 'float' and toType == 'int'): instruction.node = function.node.addToInt( instruction.expression.node) elif (type == toType): instruction.node = instruction.expression.node else: raise Snixception( instruction.offset, "Cannot cast from: type '" + type + "' to '" + toType + "'") if (hasattr(instruction.expression, 'name')): instruction.name = instruction.expression.name instruction.type = toType
def createSystemNode(self, var, function): type = var[0] name = var[1] if (self.isNativeVariable(name)): sysNode = self.createNativeVar(var, function) else: if (name == 'true'): sysNode = self.createConstantWithValue(1, type, function) sysNode.name = var[1] elif (name == 'false'): sysNode = self.createConstantWithValue(0, type, function) sysNode.name = var[1] elif (name == '__if__'): sysNode = self.getLastNodeTypeReference('If', function) elif (name == '__for__'): sysNode = self.getLastNodeTypeReference('For', function) else: raise Snixception( 'Internal', "Attempted to create unknown system node '" + name + "'!") return sysNode
def createFor(self, instruction, function, context): self.createNode(instruction.condition, function, instruction.nodeType) self.createNode(instruction.init, function, instruction.nodeType) #self.createNode(instruction.next, function, instruction.nodeType) self.createNode(instruction.body, function, instruction.nodeType) if (instruction.condition.nodeType == 'ExprList' or instruction.init.nodeType == 'ExprList' or instruction.next.nodeType == 'ExprList'): raise Snixception( instruction.offset, "For loops only support single init, next and condition statements at the moment!" ) finalNode = instruction.body.instructions[ len(instruction.body.instructions) - 1] type = finalNode.type zeroNode = self.createConstantWithValue(0, type, function) instruction.node = function.node.addIf(instruction.condition.node, finalNode.node, zeroNode.node, type) instruction.condition.left = instruction.next loops = self.getMaxForLoops(instruction, function, context) for i in range(loops): self.createNode(instruction.condition, function, instruction.nodeType) self.createNode(instruction.body, function, instruction.nodeType) instruction.node = function.node.addIf(instruction.condition.node, finalNode.node, instruction.node, type) instruction.type = type
def parse(self, body): if (body['block_items'] != None): for child in body['block_items']: self.instructions.append(self.getNode(child)) else: raise Snixception(body['coord'], "Empty bodies are not allowed!")
def getMaxForLoops(self, instruction, function, context): loops = None initVal = None cond = instruction.condition init = instruction.init next = instruction.next # Try to figure out the max amount of loops to create if (cond.right.nodeType == 'Constant' and cond.nodeType == 'BinaryOp' and init.nodeType == 'Assignment'): if (next.nodeType == 'UnaryOp'): op = next.op var = next.value.name condOp = cond.op if (cond.left.nodeType == 'UnaryOp' and cond.left.value.nodeType == 'ID' and cond.right.nodeType == 'Constant'): condVar = cond.left.value.name condVal = int(cond.right.value) if (init.right.nodeType == 'Constant' and init.left.name == var and condVar == var): initVal = int(init.right.value) if (isinstance(initVal, int)): if (condOp[0] == '<' and op == '++'): if (initVal <= condVal): if (condOp == '<'): loops = condVal - initVal - 1 elif (condOp == '<='): loops = condVal - initVal else: raise Snixception( instruction.offset, "That for loop looks like it would create an infinite number of loops!" ) if (condOp[0] == '>' and op == '++'): raise Snixception(instruction.offset, "That for loop doesn't look right!") if (condOp[0] == '>' and op == '--'): if (initVal >= condVal): if (condOp == '>'): loops = initVal - condVal - 1 elif (condOp == '>'): loops = initVal - condVal else: raise Snixception( instruction.offset, "That for loop looks like it would create an infinite number of loops!" ) if (condOp[0] == '<' and op == '--'): raise Snixception(instruction.offset, "That for loop doesn't look right!") if (loops == None): loops = int(self.getDefinedValue('LOOP_MAX')) print( "Notice: Unable to discern loop max value, max loops will be LOOP_MAX. (LOOP_MAX = '" + str(loops) + "')") if (loops >= 100): print( "Notice: That number of loops per pixel can get dangerously slow!" ) return loops
def createBinaryOp(self, instruction, function, context): ops = [ '&&', '||', '!', '!=', '==', '>', '>=', '<', '<=', '+', '-', '*', '/', '%' ] self.createNode(instruction.left, function, context) self.createNode(instruction.right, function, context) op = instruction.op if (instruction.left.type != instruction.right.type and op != '*'): raise Snixception(instruction.offset, "Cannot perform binary operation '" + instruction.op + "' on incompatible types '" + \ instruction.left.type + "' and '" + instruction.right.type + "'") type = instruction.left.type instruction.type = 'bool' left = instruction.left.node right = instruction.right.node if (self.isTypeCompatible(instruction.left.type, instruction.right.type) or op == '*'): baseType = self.getBaseType(type) if (type == 'bool'): if (op == '&&'): instruction.node = function.node.addAnd(left, right) elif (op == '||'): instruction.node = function.node.addOr(left, right) elif (op == '!'): instruction.node = function.node.addNot(left, right) if ((type == 'bool' or type == 'int' or type == 'float') and instruction.node == None): if (op == '!='): instruction.node = function.node.addNotEquals( left, right, type) elif (op == '=='): instruction.node = function.node.addEquals( left, right, type) if (type == 'int' or type == 'float' and instruction.node == None): if (op == '>'): instruction.node = function.node.addGreaterThan( left, right, type) elif (op == '>='): instruction.node = function.node.addGreaterOrEqual( left, right, type) elif (op == '<'): instruction.node = function.node.addLessThan( left, right, type) elif (op == '<='): instruction.node = function.node.addLessOrEqual( left, right, type) if ((type == 'int' or baseType == 'float') and instruction.node == None): self.createMathOp(instruction, op, function, context) if (instruction.node == None): if (op == '&&' or op == '||' or op == '!'): exception = "Can only compare boolean values with '" + op + "'!" elif (op in ops): exception = "Operand '" + op + "' is incompatible with type '" + type + "'!" else: exception = "Unsupported binary operation '" + op + "'!" raise Snixception(instruction.offset, exception) else: raise Snixception( instruction.offset, "Cannot perform binary operation on incompatible types!")
def createNativeCall(self, instruction, function, context): func = self.getNativeFunctionByName(instruction.name) type = func[0] name = func[1] args = func[2] # Some native functions can output multiple types depending on the input if (type == 'input'): type = instruction.args.type instruction.type = type instruction.name = name if (instruction.args == None): argCount = 0 else: argCount = len(instruction.args.values) if (argCount != len(args)): raise Snixception(instruction.offset, "Function '" + name + "' expects " + str(len(args)) + " parameter(s), but received "\ + str(argCount) + "!") a = instruction.args.values[0].node argType = instruction.args.values[0].type if (self.verifyNativeFunctionTypes(instruction, name)): if (name == 'sample' or name == 'sampleGrayscale'): instruction.node = function.node.addSampleGrayscale(a) elif (name == 'sampleColor'): instruction.node = function.node.addSampleColor(a) elif (name == 'abs'): instruction.node = function.node.addAbs(a, argType) elif (name == 'floor'): instruction.node = function.node.addFloor(a, argType) elif (name == 'ceil'): instruction.node = function.node.addCeil(a, argType) elif (name == 'cos'): instruction.node = function.node.addCos(a, argType) elif (name == 'sin'): instruction.node = function.node.addSin(a, argType) elif (name == 'tan'): instruction.node = function.node.addTan(a, argType) elif (name == 'atan2'): instruction.node = function.node.addArcTan2(a, argType) elif (name == 'cartesian'): b = instruction.args.values[1].node instruction.node = function.node.addCartesian(a, b, argType) elif (name == 'sqrt'): instruction.node = function.node.addSqrt(a, argType) elif (name == 'dot'): b = instruction.args.values[1].node instruction.node = function.node.addDot(a, b, argType) elif (name == 'log'): instruction.node = function.node.addLog(a, argType) elif (name == 'exp'): instruction.node = function.node.addExp(a, argType) elif (name == 'log2'): instruction.node = function.node.addLog2(a, argType) elif (name == 'pow2'): instruction.node = function.node.addPow2(a, argType) elif (name == 'lerp'): b = instruction.args.values[1].node c = instruction.args.values[2].node instruction.node = function.node.addLerp(a, b, c, argType) elif (name == 'min'): b = instruction.args.values[1].node instruction.node = function.node.addMin(a, b, argType) elif (name == 'max'): b = instruction.args.values[1].node instruction.node = function.node.addMax(a, b, argType) elif (name == 'random'): instruction.node = function.node.addRandom(a, argType) elif (name == 'reflect'): b = instruction.args.values[1].node instruction.node = function.node.addReflect(a, b, argType) elif (name == 'pow'): b = instruction.args.values[1].node instruction.node = function.node.addPow(a, b, argType) else: raise Snixception(instruction.offset, "Attempted to call undefined native function '" + instruction.name +\ "'. This is an internal error, please report to the developer!")