# lrib.py
# One of 7 python scripts that implement a simple L system. 
# Useage:
#     lrib.render(lstring, path_to_output_rib, database)
#
# The database is created by lscriptreader.py.
# The output (archive) rib file should be rendered by a RenderMan Compliant 
# renderer. A sample beauty pass rib that can be used to render the geometry 
# produced by the lrib module is also included in the collection of L system 
# scripts. It is named "archive_tester.rib".
#
# Malcolm Kesson
# 18 Jan 2012, 26 Jan 2012
  
import os, random
from math import fabs
from lmel import getval
  
Cs_stack = []
Os_stack = []
  
def decr_comp(index, delta):
    r,g,b = Cs_stack.pop()
    temp = [r,g,b]
    temp[index] -= delta
    if temp[index] < 0.0:
        temp[index] = 0.0
    Cs_stack.append(temp)
    return temp
  
def incr_comp(index, delta):
    r,g,b = Cs_stack.pop()
    temp = [r,g,b]
    temp[index] += delta
    if temp[index] > 1.0:
        temp[index] = 1.0
    Cs_stack.append(temp)
    return temp
  
                
# Interpret the L string
def render(lstr, ribfile, database):
    rib = []
    move_data = database['move']
    angle_data = database['angle']
    scale_data = database['scale']
    curvewidth_data = database['curvewidth']
    rgb_delta = database['rgb_delta']
    
    scale_invert_stack = []
    scale_invert = False
    scale_invert_stack.append(scale_invert)
    
    move_sign = 1
    angle_sign = 1
    
    # RenderMan specific attributes
    red_sign = 1
    green_sign = 1
    blue_sign = 1
    opaciy_sign = 1
    
    # Lock the random number generator
    random.seed(database['seed'])
    Cs_stack.append([1.0,1.0,1.0])
    Os_stack.append([1,1,1])
    
    for c in lstr:
        # Transformations_______________________
        if c == '+':   move_sign = 1
        elif c == '-': move_sign = -1
        elif c == '>': angle_sign = 1
        elif c == '<': angle_sign = -1
        elif c == 'S': scale_invert = (True, False)[scale_invert == True] # toggle True/False
        
        elif c == 'x': rib.append('Rotate %1.3f 1 0 0\n' % (fabs(getval(angle_data)) * angle_sign))
        elif c == 'y': rib.append('Rotate %1.3f 0 1 0\n' % (fabs(getval(angle_data)) * angle_sign))
        elif c == 'z': rib.append('Rotate %1.3f 0 0 1\n' % (fabs(getval(angle_data)) * angle_sign))
        elif c == 's': 
            scale = getval(scale_data)
            scale = (scale, 1.0/scale)[scale_invert == True]
            rib.append('Scale %1.3f %1.3f %1.3f\n' % (scale, scale, scale))
        
        elif c == 'X': rib.append('Translate %1.3f 0 0 $tnode;\n' % (fabs(getval(move_data)) * move_sign))
        elif c == 'Y': rib.append('Translate 0 %1.3f 0 $tnode;\n' % (fabs(getval(move_data)) * move_sign))
        elif c == 'Z': rib.append('Translate 0 0 %1.3f $tnode;\n' % (fabs(getval(move_data)) * move_sign))
        
        # Attribute Stack_______________________
        elif c == '{':
            cs = Cs_stack[len(Cs_stack) - 1]
            Cs_stack.append(cs);
            rib.append('AttributeBegin\n')
            scale_invert_stack.append(scale_invert)
            
        elif c == '}':
            cs = Cs_stack.pop();
            rib.append('AttributeEnd\n')
            scale_invert = scale_invert_stack.pop()
            
        # Attributes____________________________
        elif c == 'r': r,g,b = decr_comp(0, getval(rgb_delta))
        elif c == 'g': r,g,b = decr_comp(1, getval(rgb_delta))
        elif c == 'b': r,g,b = decr_comp(2, getval(rgb_delta))
        elif c == 'R': r,g,b = incr_comp(0, getval(rgb_delta))
        elif c == 'G': r,g,b = incr_comp(1, getval(rgb_delta))
        elif c == 'B': r,g,b = incr_comp(2, getval(rgb_delta))
  
            
        # Geometry______________________________
        elif c == '4':
            r,g,b = Cs_stack[len(Cs_stack) - 1]
            rib.append('Color %1.3f %1.3f %1.3f\n' % (r,g,b))
            rib.append('Cone 0.25 1 360\n')
            rib.append('Translate 0 1 0\n')
        elif c == '1':
            r,g,b = Cs_stack[len(Cs_stack) - 1]
            rib.append('Color %1.3f %1.3f %1.3f\n' % (r,g,b))
            
            rib.append('Curves "linear" [2] "nonperiodic" "P" [0 0 0  0 1 0]\n')
            rib.append('      "constantwidth" [%1.3f]\n' % (fabs(getval(curvewidth_data))))
            rib.append('Translate 0 1 0\n')
            
    script_path = database['script_path']
    
    # lprocedural does not provide a path to an output rib file
    # because it pipes the rib statements to prman directly. 
    if len(ribfile) == 0:
        return ''.join(rib)
    else:
        # Copy the text from the original script so that it can be put
        # into the output rib file as a header comment
        script_text = ''
        if os.path.exists(script_path) and os.path.isfile(script_path):
            f = open(script_path, 'r')
            temp = f.readlines()
            f.close()
            lines = []
            for line in temp:
                lines.append('# %s' % line)
            script_text = ''.join(lines)
            
        f = open(ribfile, 'w')
        f.write('# Created by "%s"\n' % script_path)
        f.write('%s\n' % script_text)
        f.write('AttributeBegin\n')
        f.write(''.join(rib))
        f.write('AttributeEnd\n')
        f.close()