def modifier(self, name, metadata): # transform into dict, drop typespecs (not needed for python) metadata = {md[1] : md[2] for md in metadata} if metadata else {} from generator import Modifier self.mod = Modifier(name, metadata) self.function(name)
class Converter(): def __init__(self): self.funcname = None self.parameters = [] self.mod = None self.mod_types = set() self.result = "" self.indent = 0 self.scope_uuid_next = 1 self.scopestack = [] self.return_branches = [] def scope_uuid_gen(self): uuid = self.scope_uuid_next self.scope_uuid_next += 1 return uuid @property def scope(self): return self.scopestack[-1] def line(self, text): self.result += " " * self.indent + text + "\n" self.scope.empty = False def symbol_string(self, sym): return "%s_%d" % (sym.name, sym.scope.uuid) def expression(self): return Expression() def generate(self, source): try: # result = grammar.modifier_parser.parse_text(source, reset=True, eof=True, matchtype='complete') result = grammar.modifier_parser.parse_text(source, reset=True, eof=True) except ParseError as err: print("Error: line %d: %s" % (err.line, err.message)) split_buffer = err.buffer.split('\n') print(split_buffer[err.line]) if err.col > 0: print("%s^" % (" " * (err.col-1) if err.col > 0 else "",)) result = None if result: result.codegen(self) if self.mod is not None: self.mod.source = self.result return self.mod ### Module ### def module(self): self.scopestack.append(Block(self.scope_uuid_gen(), 'MODULE', None)) def module_end(self): self.scopestack = [] # insert header and modifier functions into result header = "import bpy\n" \ "from mathutils import *\n" \ "\n" self.result = header \ + "\n".join(mod_type.source for mod_type in self.mod_types) \ + self.result ### Modifier ### def modifier(self, name, metadata): # transform into dict, drop typespecs (not needed for python) metadata = {md[1] : md[2] for md in metadata} if metadata else {} from generator import Modifier self.mod = Modifier(name, metadata) self.function(name) def modifier_body(self): self.function_body() def modifier_return(self): self.function_return() def modifier_parameter(self, name, typedesc, is_output, default, metadata): # transform into dict, drop typespecs (not needed for python) metadata = {md[1] : md[2] for md in metadata} if metadata else {} self.mod.parameter(name, typedesc, is_output, default, metadata) self.function_parameter(name, typedesc, is_output, default) ### Modifier Instance ### def modinst(self, modinst): from generator import ModifierLink self.mod_types.add(modinst.modifier) input_args = [] output_args = [] for param in modinst.modifier.parameters: name = "%s_%s" % (modinst.name, param.name) if param.is_output: sym = self.declare(name, param.typedesc) output_args.append(self.symbol_string(sym)) else: if param.name in modinst.param_value: value = modinst.param_value[param.name] if isinstance(value, ModifierLink): rhs_name = "%s_%s" % (value.mod_inst.name, value.param_name) rhs_sym = self.scope.symbol(rhs_name) sym = self.type_convert(param.typedesc, rhs_sym) arg = self.symbol_string(sym) else: arg = value input_args.append("_%s=%s" % (param.name, arg)) source = "" if output_args: source += "%s = " % ", ".join(arg for arg in output_args) source += "%s(%s)" % (modinst.modifier.name, ", ".join(arg for arg in input_args)) self.line(source) ### Function ### def function(self, name): self.funcname = name # NB: scopestack[0] is the return scope, # the actual base scope is created in function_body self.scopestack.append(Block(self.scope_uuid_gen(), 'RETURN', None)) def function_body(self): # NB: add '_' prefix to avoid python keywords self.line("def %s(%s):" % (self.funcname, ", ".join("_%s=%r" % (param.name, param.default) for param in self.parameters if not param.is_output))) # initialize parameter symbols for param in self.parameters: sym = self.scope.declare(param.name, param.typedesc, param.default) self.scope_begin() self.indent += 1 self.line("global %s" % ", ".join(self.symbol_string(sym) for sym in _global_syms.values())) for param in self.parameters: if param.is_output: # initial output parameter value for valid return statement self.line("%s_%d = %r" % (param.name, self.scope.parent.uuid, param.default)) else: # for name-based access to input symbols, make a copy with scope uuid appended self.line("%s_%d = _%s if _%s is not None else %r" % (param.name, self.scope.parent.uuid, param.name, param.name, self.default_value(param.typedesc))) def function_return(self): self.merge_returns() self.scope_end() # return output parameters values return_syms = [self.scope.symbol(param.name) for param in self.parameters if param.is_output] return_args = ["None" if sym is None else ("%s" % self.symbol_string(sym)) for sym in return_syms] self.line("return %s" % ", ".join(arg for arg in return_args)) self.indent -= 1 self.scopestack = [] self.parameters = [] def function_parameter(self, name, typedesc, is_output, default): from generator import Parameter self.parameters.append(Parameter(name, typedesc, is_output, default)) ### Symbols ### def declare(self, name, typedesc, value=None): sym = self.scope.declare(name, typedesc, value) if value is not None: self.line("%s = %r" % (self.symbol_string(sym), value)) return sym def declare_temp(self, typedesc, value=None): sym = self.scope.declare_temp(typedesc, value) if value is not None: self.line("%s = %r" % (self.symbol_string(sym), value)) return sym def assign(self, name, rhs_sym, array_index_sym=None): scope = self.scope sym = scope.symbol(name) rhs_sym = self.type_convert(sym.typedesc, rhs_sym) # make sure we have a matching type sym = scope.assign(sym, rhs_sym.value) # copy semantics if rhs_sym.typedesc.basetype == 'mesh': copy_str = "%s.copy()" % self.symbol_string(rhs_sym) else: copy_str = self.symbol_string(rhs_sym) self.line("%s%s = %s" % (self.symbol_string(sym), "" if array_index_sym is None else "[%s]" % self.symbol_string(array_index_sym), copy_str)) return sym def variable_ref(self, name, array_index_sym=None): scope = self.scope sym = scope.symbol(name) if array_index_sym is None: return sym else: deref_type = sym.typedesc.deref() lhs_sym = self.declare_temp(deref_type, None) self.line("%s = %s[%s]" % (self.symbol_string(lhs_sym), self.symbol_string(sym), self.symbol_string(array_index_sym))) return lhs_sym ### Scope ### def _scope_push(self, hint): block = Block(self.scope_uuid_gen(), hint, self.scope) self.scopestack.append(block) return block def _scope_pop(self): block = self.scopestack.pop() if not block.empty: self.scope.empty = False return block def merge_scope(self, branch): scope = self.scope if scope.returned: return if branch.returned: scope.returned = True return def merge_conditional(self, branch_if, branch_else): scope = self.scope if scope.returned: return if branch_if.returned and branch_else.returned: scope.returned = True return def merge_returns(self): scope = self.scope def scope_begin(self): self._scope_push('SCOPE') def scope_end(self): branch = self._scope_pop() self.merge_scope(branch) def conditional_if(self, condition): scope = self.scope self.line("if %s:" % self.symbol_string(condition)) scope.branch_if = self._scope_push('IF') self.indent += 1 def conditional_else(self, condition): if self.scope.empty: self.line("pass") self._scope_pop() self.indent -= 1 scope = self.scope self.line("else:") scope.branch_else = self._scope_push('ELSE') self.indent += 1 def conditional_end_if(self): if self.scope.empty: self.line("pass") self._scope_pop() self.indent -= 1 branch_if = self.scope.branch_if branch_else = self.scope.branch_else self.merge_conditional(branch_if, branch_else) def set_return(self, sym): # return output parameters values output_params = [param for param in self.parameters if param.is_output] return_syms = [sym] + [self.scope.symbol(param.name) for param in output_params[1:]] return_args = ["None" if sym is None else ("%s" % self.symbol_string(sym)) for sym in return_syms] self.line("return %s" % ", ".join(arg for arg in return_args)) self.scope.returned = True self.return_branches.append(self.scope) ### Types ### @classmethod def default_value(cls, typedesc): from modifier_functions import DerivedMesh b = typedesc.basetype if b in {'void', 'any'}: return None elif b == 'float': return 0.0 elif b == 'int': return 0 elif b == 'bool': return False elif b == 'color': return Color((0, 0, 0)) elif b in {'vector', 'point'}: return Vector((0, 0, 0)) elif b == 'normal': return Vector((0, 0, 1)) elif b == 'matrix': return Matrix.Identity(4) elif b == 'string': return "" elif b == 'mesh': return DerivedMesh() else: raise Exception("Unhandled default value type %r" % b) def define_int(self, value, driven=False): return self.declare_temp(TypeInt, int(value)) def define_float(self, value, driven=False): return self.declare_temp(TypeFloat, float(value)) def define_bool(self, value, driven=False): return self.declare_temp(TypeBool, bool(value)) def define_color(self, value, driven=False): return self.declare_temp(TypeColor, Color((float(value[0]), float(value[1]), float(value[2])))) def define_vector(self, value, basetype='vector', driven=False): return self.declare_temp(TypeDesc(basetype), Vector((float(value[0]), float(value[1]), float(value[2])))) def define_matrix(self, value, driven=False): return self.declare_temp(TypeMatrix, Matrix(tuple(value[0:16]))) def define_string(self, value, driven=False): return self.declare_temp(TypeString, str(value)) def define_mesh(self, value, driven=False): return self.declare_temp(TypeMesh, set()) def type_constructor(self, typedesc, args): try: if typedesc.basetype == 'int': if isinstance(args, Symbol): return self.to_int(args) else: return self.define_int(args) elif typedesc.basetype == 'float': if isinstance(args, Symbol): return self.to_float(args) else: return self.define_float(args) elif typedesc.basetype == 'bool': if isinstance(args, Symbol): return self.to_bool(args) else: return self.define_bool(args) # for vector types first try to construct float arguments elif typedesc.basetype in {'color', 'vector', 'normal', 'point', 'matrix'}: try: sym_args = [self.to_float(c) if isinstance(c, Symbol) else self.define_float(c) for c in args] if typedesc.basetype in {'vector', 'normal', 'point'}: return self.declare_temp(typedesc, Expression("Vector((%s, %s, %s))", sym_args[0], sym_args[1], sym_args[2])) elif typedesc.basetype == 'color': return self.declare_temp(typedesc, Expression("Color((%s, %s, %s))", sym_args[0], sym_args[1], sym_args[2])) elif typedesc.basetype == 'matrix': raise Exception("Not implemented yet") except: if isinstance(args, Symbol): if typedesc.basetype == 'color': return self.to_color(args) elif typedesc.basetype in {'vector', 'point', 'normal'}: return self.to_vector(args) elif typedesc.basetype == 'matrix': return self.to_matrix(args) else: raise elif typedesc.basetype == 'string': if isinstance(args, Symbol): return self.to_string(args) else: return self.define_string(args) elif typedesc.basetype == 'mesh': if isinstance(args, Symbol): return self.to_mesh(args) else: return self.define_mesh(args) else: raise Exception("Unknown type %r" % typedesc.basetype) except: raise Exception("Cannot construct %s from %r" % (str(typedesc), args)) def to_float(self, sym): t = sym.typedesc.basetype if t == 'float': return sym elif t in {'int', 'bool', 'any'}: return self.declare_temp(TypeFloat, Expression("float(%s)", sym)) else: raise Exception("Cannot convert %s to %s" % (t, 'float')) def to_int(self, sym): t = sym.typedesc.basetype if t == 'int': return sym elif t in {'float', 'bool', 'any'}: return self.declare_temp(TypeInt, Expression("int(%s)", sym)) else: raise Exception("Cannot convert %s to %s" % (t, 'int')) def to_bool(self, sym): t = sym.typedesc.basetype if t == 'bool': return sym elif t in {'float', 'int', 'any'}: return self.declare_temp(TypeBool, Expression("bool(%s)", sym)) else: raise Exception("Cannot convert %s to %s" % (t, 'bool')) def to_color(self, sym): t = sym.typedesc.basetype if t == 'color': return sym elif t in {'vector', 'point', 'normal', 'any'}: # use vector values as color return self.declare_temp(TypeColor, Expression("Color(%s[0:3])", sym)) else: raise Exception("Cannot convert %s to %s" % (t, 'color')) def to_vector(self, sym, typedesc=TypeVector): t = sym.typedesc.basetype if sym.typedesc == typedesc: return sym elif t in {'vector', 'point', 'normal', 'color', 'any'}: return self.declare_temp(typedesc, Expression("Vector(%s[0:3])", sym)) else: raise Exception("Cannot convert %s to %s" % (t, str(typedesc))) def to_matrix(self, sym): t = sym.typedesc.basetype if t == 'matrix': return sym elif t in {'any'}: return self.declare_temp(TypeMatrix, Expression("Matrix(%s[0:16])", sym)) else: raise Exception("Cannot convert %s to %s" % (t, 'matrix')) def to_string(self, sym): t = sym.typedesc.basetype if t == 'string': return sym elif t in {'any'}: return self.declare_temp(TypeString, Expression("str(%s)", sym)) else: raise Exception("Cannot convert %s to %s" % (t, 'string')) def to_mesh(self, sym): t = sym.typedesc.basetype if t == 'mesh': return sym else: raise Exception("Cannot convert %s to %s" % (t, 'mesh')) def to_any(self, sym): t = sym.typedesc.basetype if t == 'any': return sym else: return self.declare_temp(TypeAny, sym.value) def type_convert(self, typedesc, sym): if sym.typedesc.array_size != typedesc.array_size: raise Exception("Cannot convert array of size %r to size %r" % (sym.typedesc.array_size, typedesc.array_size)) if typedesc.basetype == 'int': return self.to_int(sym) elif typedesc.basetype == 'float': return self.to_float(sym) elif typedesc.basetype == 'bool': return self.to_bool(sym) elif typedesc.basetype == 'color': return self.to_color(sym) elif typedesc.basetype in {'vector', 'point', 'normal'}: return self.to_vector(sym, typedesc) elif typedesc.basetype == 'matrix': return self.to_matrix(sym) elif typedesc.basetype == 'string': return self.to_string(sym) elif typedesc.basetype == 'mesh': return self.to_mesh(sym) elif typedesc.basetype == 'any': return self.to_any(sym) else: raise Exception("Unknown type %r" % typedesc.basetype) def bool_neg(self, sym): assert(sym.typedesc.basetype == 'bool') return self.declare_temp(TypeBool, Expression("not %s", sym)) # NB: binary boolean ops gracefully accept None arguments for simplicity def bool_and(self, sym_a, sym_b): if sym_a is None: return sym_b elif sym_b is None: return sym_a else: assert(sym_a.typedesc.basetype == 'bool' and sym_b.typedesc.basetype == 'bool') return self.declare_temp(TypeBool, Expression("%s and %s", rsym_a, rsym_b)) def bool_or(self, sym_a, sym_b): if sym_a is None: return sym_b elif sym_b is None: return sym_a else: assert(sym_a.typedesc.basetype == 'bool' and sym_b.typedesc.basetype == 'bool') return self.declare_temp(TypeBool, Expression("%s or %s", rsym_a, rsym_b)) def bool_xor(self, sym_a, sym_b): if sym_a is None: return sym_b elif sym_b is None: return sym_a else: assert(sym_a.typedesc.basetype == 'bool' and sym_b.typedesc.basetype == 'bool') return self.declare_temp(TypeBool, Expression("%s != %s", rsym_a, rsym_b)) def binary_op(self, op, sym_a, sym_b): type_a = sym_a.typedesc.basetype type_b = sym_b.typedesc.basetype # some shortcuts def scalar_math_op(result_type, operation): rsym_a = self.type_convert(result_type, sym_a) rsym_b = self.type_convert(result_type, sym_b) return self.declare_temp(result_type, Expression("%%s %s %%s" % operation, rsym_a, rsym_b)) def scalar_compare_op(result_type, operation): rsym_a = self.type_convert(result_type, sym_a) rsym_b = self.type_convert(result_type, sym_b) return self.declare_temp(TypeBool, Expression("%%s %s %%s" % operation, rsym_a, rsym_b)) def float3_result_type(): type_a = sym_a.typedesc.basetype type_b = sym_b.typedesc.basetype if type_a == type_b: return sym_a.typedesc elif type_a == 'color' or type_b == 'color': return TypeColor else: # only exact matches of vector types (handled above) # can become anything other than generic vector type return TypeVector def float3_component_math_op(result_type, operation): rsym_a = self.type_convert(result_type, sym_a) rsym_b = self.type_convert(result_type, sym_b) return self.declare_temp(result_type, Expression("(%%s[0] %s %%s[0], %%s[1] %s %%s[1], %%s[2] %s %%s[2])" % (operation, operation, operation), rsym_a, rsym_b, rsym_a, rsym_b, rsym_a, rsym_b)) def mul_v3_fl_op(vec, fac): # XXX RGB mix node does some clamping of the factor value to 0..1 range, # which makes it useless for generic multiplication ... # Instead construct a color from float and then multiply that fac = self.type_convert(TypeFloat, fac) return self.declare_temp(result_type, Expression("(%s[0] * %s, %s[1] * %s, %s[2] * %s)", vec, fac, vec, fac, vec, fac)) if op == '*': if type_a in int_types and type_b in int_types: result = scalar_math_op(TypeInt, '*') elif type_a in scalar_types and type_b in scalar_types: result = scalar_math_op(TypeFloat, '*') elif type_a in float3_types and type_b in float3_types: result = float3_component_math_op(float3_result_type(), '*') elif type_a in scalar_types and type_b in float3_types: result = mul_v3_fl_op(sym_b, sym_a) elif type_a in float3_types and type_b in scalar_types: result = mul_v3_fl_op(sym_a, sym_b) else: raise Exception("NOT IMPLEMENTED") elif op == '/': if type_a in int_types and type_b in int_types: result = scalar_math_op(TypeInt, '/') elif type_a in scalar_types and type_b in scalar_types: result = scalar_math_op(TypeFloat, '/') elif type_a in float3_types and type_b in float3_types: result = float3_component_math_op(float3_result_type(), '/') elif type_a in float3_types and type_b in scalar_types: result = mul_v3_fl_op(sym_a, self.one_over(sym_b)) else: raise Exception("NOT IMPLEMENTED") elif op == '%': if type_a in int_types and type_b in int_types: result = scalar_math_op(TypeInt, '%') elif type_a in scalar_types and type_b in scalar_types: result = scalar_math_op(TypeFloat, '%') else: raise Exception("NOT IMPLEMENTED") elif op == '+': if type_a in int_types and type_b in int_types: result = scalar_math_op(TypeInt, '+') elif type_a in scalar_types and type_b in scalar_types: result = scalar_math_op(TypeFloat, '+') elif type_a in float3_types and type_b in float3_types: result = float3_component_math_op(float3_result_type(), '+') elif type_a in string_types and type_b in string_types: result = self.declare_temp(TypeString, Expression("%s + %s", sym_a, sym_b)) else: raise Exception("NOT IMPLEMENTED") elif op == '-': if type_a in int_types and type_b in int_types: result = scalar_math_op(TypeInt, '-') elif type_a in scalar_types and type_b in scalar_types: result = scalar_math_op(TypeFloat, '-') elif type_a in float3_types and type_b in float3_types: result = float3_component_math_op(float3_result_type(), '-') else: raise Exception("NOT IMPLEMENTED") elif op == '==': result = self.declare_temp(TypeBool, Expression("%s == %s", sym_a, sym_b)) elif op == '!=': result = self.declare_temp(TypeBool, Expression("%s != %s", sym_a, sym_b)) elif op == '<': result = self.declare_temp(TypeBool, Expression("%s < %s", sym_a, sym_b)) elif op == '<=': result = self.declare_temp(TypeBool, Expression("%s <= %s", sym_a, sym_b)) elif op == '>': result = self.declare_temp(TypeBool, Expression("%s > %s", sym_a, sym_b)) elif op == '>=': result = self.declare_temp(TypeBool, Expression("%s >= %s", sym_a, sym_b)) return result ### Function Calls ### def function_type_check(self, name, *arg_syms): name_found = False for fdesc in _function_desc: if fdesc.name == name: name_found = True # XXX for now just checks number of arguments to disambiguate function names. # According to OSL spec the types should also be checked, # this will require some compatibility test function if len(fdesc.params) == len(*arg_syms) + 1: # +1 to account for return parameter return fdesc if name_found: raise Exception("No matching parameter list for function %s" % name) else: raise Exception("Unknown function %s" % name) def function_call(self, name, *arg_syms): fdesc = self.function_type_check(name, arg_syms) # NB: first item in func_desc is the return value type, not specified in arg_syms input_syms = [rval for (rval, lval), (is_output, typedesc) in zip(arg_syms, fdesc.params[1:]) if not is_output] return_typedesc = fdesc.params[0][1] return_sym = self.declare_temp(return_typedesc) if return_typedesc.basetype != 'void' else None output_syms = ([return_sym] if return_sym else []) + [lval for (rval, lval), (is_output, typedesc) in zip(arg_syms, fdesc.params[1:]) if is_output] input_args = [self.symbol_string(sym) for sym in input_syms] if fdesc.use_object: input_args = ["__object__"] + input_args output_args = [self.symbol_string(sym) for sym in output_syms] fname = fdesc.impl.__name__ if hasattr(fdesc.impl, "__name__") else name self.line("%s%s(%s)" % ("%s = " % ", ".join(a for a in output_args) if output_args else "", fname, ", ".join(a for a in input_args))) if return_sym: return_sym.value = Expression(self.symbol_string(return_sym)) return return_sym ### RNA paths ### def rna_path(self, base, struct_path, prop, prop_index): self.line("__object_add_driver__(%r, %r, %r, %r)" % (base, struct_path, prop, prop_index)) path_value = rna_path_value(base, struct_path, prop, prop_index) return self.declare_temp(TypeAny, path_value) ### Comments ### def comment(self, text): split_text = text.split('\n') for line in split_text: self.line("# %s" % line)