<commit_before># -*- coding: utf-8 -*-
#$HeadURL$
#$LastChangedDate$
#$LastChangedRevision$

import os
import sys
import re
from reportlab.platypus import *
import reportlab.lib.colors as colors
import reportlab.lib.units as units
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.fonts import addMapping
from reportlab.lib.styles import *
from reportlab.lib.enums import *
from reportlab.pdfbase import pdfmetrics
import reportlab.lib.pagesizes as pagesizes
from log import log
from simplejson import loads
import docutils.nodes

try:
    from wordaxe.rl.paragraph import Paragraph
    from wordaxe.rl.styles import ParagraphStyle,getSampleStyleSheet
except:
    log.warning("No hyphenation support, install wordaxe")

class StyleSheet(object):
    '''Class to handle a collection of stylesheets'''
    def __init__(self,flist,ffolder=None):
        log.info('Using stylesheets: %s'%','.join(flist))
        '''flist is a list of stylesheet filenames.They will
        be loaded and merged in order'''
        self.TTFSearchPath=['.',os.path.join(os.path.abspath(os.path.dirname(__file__)), 'fonts')]
        self.StyleSearchPath=['.',os.path.join(os.path.abspath(os.path.dirname(__file__)), 'styles')]
        if ffolder:
            self.TTFSearchPath.insert(0,ffolder)
        # Page width, height
        self.pw=0
        self.ph=0

        # Page size [w,h]
        self.ps=None

        # Margins (top,bottom,left,right,gutter)
        self.tm=0
        self.bm=0
        self.lm=0
        self.rm=0
        self.gm=0

        #text width
        self.tw=0

        self.languages=[]
        ssdata=[]
        # First parse all files and store the results
        for fname in flist:
            fname=self.findStyle(fname)
            try:
                ssdata.append(loads(open(fname).read()))
            except ValueError,e: # Error parsing the JSON data
                log.critical('Error parsing stylesheet "%s": %s'%(fname,str(e)))
                sys.exit(1)
            except IOError,e: #Error opening the ssheet
                log.critical('Error opening stylesheet "%s": %s'%(fname,str(e)))
                sys.exit(1)

        # Get pageSetup data from all stylessheets in order:
        self.ps=pagesizes.A4
        for data,ssname in zip(ssdata,flist):
            page=data.get('pageSetup',{})
            if page:
                if page.get('size',None): # A standard size
                    if page['size'] in pagesizes.__dict__:
                        self.ps=pagesizes.__dict__[page['size']]
                    else:
                        log.critical('Unknown page size %s in stylesheet %s' % (page['size'],ssname))
                        sys.exit(1)
                else: #A custom size
                    # The sizes are expressed in some unit.
                    # For example, 2cm is 2 centimeters, and we need
                    # to do 2*cm (cm comes from reportlab.lib.units)
                    if 'width' in page:
                        self.ps[0]=self.adjustUnits(page['width'])
                    if 'height' in page:
                        self.ps[1]=self.adjustUnits(page['height'])
                self.pw,self.ph=self.ps
                if 'margin-left' in page:
                    self.lm=self.adjustUnits(page['margin-left'])
                if 'margin-right' in page:
                    self.rm=self.adjustUnits(page['margin-right'])
                if 'margin-top' in page:
                    self.tm=self.adjustUnits(page['margin-top'])
                if 'margin-bottom' in page:
                    self.bm=self.adjustUnits(page['margin-bottom'])
                if 'margin-gutter' in page:
                    self.gm=self.adjustUnits(page['margin-gutter'])
                if 'firstTemplate' in page:
                    self.firstTemplate=page['firstTemplate']

                # tw is the text width.
                # We need it to calculate header-footer height
                # and compress literal blocks.
                self.tw=self.pw-self.lm-self.rm-self.gm

        # Get page templates from all stylesheets
        self.pageTemplates={}
        for data,ssname in zip(ssdata,flist):
            templates=data.get('pageTemplates',{})
            # templates is a dictionary of pageTemplates
            for key in templates:
                template=templates[key]
                # template is a dict.
                # template[´frames'] is a list of frames
                if key in self.pageTemplates:
                    self.pageTemplates[key].update(template)
                else:
                    self.pageTemplates[key]=template

        # Get font aliases from all stylesheets in order
        self.fonts={}
        for data,ssname in zip(ssdata,flist):
            self.fonts.update(data.get('fontsAlias',{}))
            embedded=data.get('embeddedFonts',[])

            for font in embedded:
                try:
                    # Each "font" is a list of four files, which will be used for
                    # regular / bold / italic / bold+italic versions of the font.
                    # If your font doesn't have one of them, just repeat the regular
                    # font.

                    # Example, using the Tuffy font from http://tulrich.com/fonts/
                    # "embeddedFonts" : [
                    #                    ["Tuffy.ttf","Tuffy_Bold.ttf","Tuffy_Italic.ttf","Tuffy_Bold_Italic.ttf"]
                    #                   ],

                    # The fonts will be registered with the file name, minus the extension.

                    for variant in font:
                        pdfmetrics.registerFont(TTFont(str(variant.split('.')[0]), self.findFont(variant)))

                        # And map them all together
                        regular,bold,italic,bolditalic = [ variant.split('.')[0] for variant in font ]
                        addMapping(regular,0,0,regular)
                        addMapping(regular,0,1,italic)
                        addMapping(regular,1,0,bold)
                        addMapping(regular,1,1,bolditalic)
                except Exception, e:
                    try:
                        fname=font[0].split('.')[0]
                        log.error("Error processing font %s: %s",fname,str(e))
                        log.error("Registering %s as Helvetica alias",fname)
                        self.fonts[fname]='Helvetica'
                    except Exception,e:
                        log.critical("Error processing font %s: %s",fname,str(e))
                        sys.exit(1)

        # Get styles from all stylesheets in order
        self.stylesheet = {}
        self.styles=[]
        self.linkColor = 'navy'
        for data,ssname in zip(ssdata,flist):
            self.linkColor=data.get('linkColor') or self.linkColor
            styles=data.get('styles',{})
            for [skey,style] in styles:
                sdict={}
                # FIXME: this is done completely backwards
                for key in style:

                    # Handle font aliases
                    if (key == 'fontName' or key.endswith('FontName')) and style[key] in self.fonts:
                        style[key]=self.fonts[style[key]]

                    # Handle color references by name
                    elif key == 'color' or key.endswith('Color') and style[key]:
                        style[key]=formatColor(style[key])

                    # Handle alignment constants
                    elif key == 'alignment':
                        style[key]={'TA_LEFT':0, 'TA_CENTER':1, 'TA_CENTRE':1, 'TA_RIGHT':2, 'TA_JUSTIFY':4, 'DECIMAL':8}[style[key]]

                    elif key == 'language':
                        if not style[key] in self.languages:
                            self.languages.append(style[key])
                    # Make keys str instead of unicode (required by reportlab)
                    sdict[str(key)]=style[key]
                    sdict['name']=skey
                # If the style already exists, update it
                if skey in self.stylesheet:
                    self.stylesheet[skey].update(sdict)
                else: # New style
                    self.stylesheet[skey]=sdict
                    self.styles.append(sdict)
        # And create  reportlabs stylesheet
        self.StyleSheet=StyleSheet1()
        for s in self.styles:
            if 'parent' in s:
                if s['parent'] is None:
                    if s['name'] <> 'base':
                        s['parent']=self.StyleSheet['base']
                    else:
                        del(s['parent'])
                else:
                    s['parent']=self.StyleSheet[s['parent']]
            else:
                if s['name'] <> 'base':
                    s['parent']=self.StyleSheet['base']
            self.StyleSheet.add(ParagraphStyle(**s))

    def __getitem__(self,key):
        return self.StyleSheet[key]

    def findStyle(self,fn):
        '''Given a style filename, searches for it in StyleSearchPath
        and returns the real file name'''
        if not os.path.isabs(fn):
            for D in self.StyleSearchPath:
                tfn = os.path.join(D,fn)
                if os.path.isfile(tfn):
                    return str(tfn)
        return str(fn)

    def findFont(self,fn):
        '''Given a font filename, searches for it in TTFSearchPath
        and returns the real file name'''
        if not os.path.isabs(fn):
            for D in self.TTFSearchPath:
                tfn = os.path.join(D,fn)
                if os.path.isfile(tfn):
                    return str(tfn)
        return str(fn)

    def findStyle(self,fn):
        '''Given a style filename, searches for it in StyleSearchPath
        and returns the real file name'''
        if not os.path.isabs(fn):
            for D in self.StyleSearchPath:
                tfn = os.path.join(D,fn)
                if os.path.isfile(tfn):
                    return str(tfn)
        return str(fn)

    def styleForNode(self,node):
        '''Returns the right default style for any kind of node.
        That usually means "bodytext", but for sidebars, for
        example, it's sidebar'''

        if isinstance(node,docutils.nodes.sidebar):
            return self['sidebar']
        return self['bodytext']

    def tstyleHead(self,rows=1):
        '''Returns a table style spec for a table header of `rows` based on the
        table-heading style from the stylesheet'''
        # This alignment thing is exactly backwards from the alignment for paragraphstyles
        alignment={0:'LEFT', 1:'CENTER', 1:'CENTRE', 2:'RIGHT', 4:'JUSTIFY',8:'DECIMAL'}[self['table-heading'].alignment]
        return [('BACKGROUND',(0,0),(-1,rows-1),self['table-heading'].backColor),
                ('ALIGN',(0,0),(-1,rows-1),alignment),
                ('TEXTCOLOR',(0,0),(-1,rows-1),self['table-heading'].textColor),
                ('FONT',(0,0),(-1,rows-1),self['table-heading'].fontName,
                                          self['table-heading'].fontSize,
                                          self['table-heading'].leading),
                ('VALIGN',(0,0),(-1,rows-1),self['table-heading'].valign)
               ]
    def tstyleBody(self,rows=1):
        '''Returns a table style spec for a table of any size based on the
        table style from the stylesheet'''
        return [("ROWBACKGROUNDS",(0,0),(-1,-1),[formatColor(c,numeric=False) for c in self['table'].rowBackgrounds]),
            ]

    def pStyleToTStyle(self,style,x,y):
        '''Given a reportlab paragraph style, returns a spec for a table style
           that adopts some of its features (example, background color)'''
        results=[]
        if style.backColor:
            results.append(('BACKGROUND',(x,y),(x,y),style.backColor))
        if style.borderWidth:
            bw=style.borderWidth
            del(style.__dict__['borderWidth'])
            if style.borderColor:
                bc=style.borderColor
                del(style.__dict__['borderColor'])
            else:
                bc=colors.black
            results.append(('BOX',(x,y),(x,y),bw,bc))
        return results

    def adjustUnits(self,v,total=None):
        if total is None:
            total=self.pw
        return adjustUnits(v,total)

def adjustUnits(v,total=None):
    '''Takes something like 2cm and returns 2*cm.
    If you use % as a unit, it returns the percentage
    of "total".

    If total is not given, returns a percentage of the page
    width. However, if you get to that stage, you are
    doing it wrong.

    Example::

            >>> adjustUnits('50%',200)
            100
    '''

    v=str(v)
    _,n,u=re.split('(-?[0-9\.]*)',v)
    if not u:
        return float(n) # assume points
    if u in units.__dict__:
        return float(n)*units.__dict__[u]
    else:
        if u=='%':
            return float(n)*total/100
        log.error('Unknown unit "%s"' % u)
    return float(n)

# Some table styles used for pieces of the document

tstyles={}

# Used for regular tables

tstyleNorm = [ ('VALIGN',(0,0),(-1,-1),'TOP'),
               ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
               ('BOX', (0,0), (-1,-1), 0.25, colors.black),
             ]

# Header row in tables

tstyles['normal']=TableStyle(tstyleNorm)

# Used for field lists

tstyles['field']=TableStyle([ ('VALIGN',(0,0),(-1,-1),'TOP'),
                              ('ALIGNMENT',(0,0),(1,-1),'RIGHT'),
                            ])
fieldlist_lwidth=3*units.cm

# Used for endnotes

tstyles['endnote']=TableStyle([ ('VALIGN',(0,0),(-1,-1),'TOP'),
                                ('ALIGNMENT',(0,0),(1,-1),'RIGHT'),
                              ])
# Left column of the endnote. The content of the note takes the rest of
# the available space
endnote_lwidth=2*units.cm

# Used for sidebars

tstyles['sidebar']=TableStyle([ ('VALIGN',(0,0),(-1,-1),'TOP'),
                             ('BACKGROUND',(0,0),(-1,-1),colors.lightyellow),
                           ])

tstyles['bullet']=TableStyle([ ('VALIGN',(0,0),(-1,-1),'TOP'),
                             ])

def formatColor(value,numeric=True):
    '''Converts a color like "gray" or "0xf" or "ffff" to something
    reportlab will like'''
    if value in colors.__dict__:
        return colors.__dict__[value]
    else: # Hopefully, a hex color:
        c=value.strip()
        if c[0]=='#':
            c=c[1:]
        while len(c)<6:c='0'+c
        if numeric:
            r=int(c[:2],16)
            g=int(c[2:4],16)
            b=int(c[4:6],16)
            return colors.Color(r,g,b)
        else:
            return str("#"+c)
<commit_msg>cosmetics<commit_after># -*- coding: utf-8 -*-
#$HeadURL$
#$LastChangedDate$
#$LastChangedRevision$

import os
import sys
import re
from reportlab.platypus import *
import reportlab.lib.colors as colors
import reportlab.lib.units as units
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.fonts import addMapping
from reportlab.lib.styles import *
from reportlab.lib.enums import *
from reportlab.pdfbase import pdfmetrics
import reportlab.lib.pagesizes as pagesizes
from log import log
from simplejson import loads
import docutils.nodes

try:
    from wordaxe.rl.paragraph import Paragraph
    from wordaxe.rl.styles import ParagraphStyle,getSampleStyleSheet
except:
    log.warning("No hyphenation support, install wordaxe")

unit_separator = re.compile('(-?[0-9\.]*)')

class StyleSheet(object):
    '''Class to handle a collection of stylesheets'''
    def __init__(self,flist,ffolder=None):
        log.info('Using stylesheets: %s'%','.join(flist))
        '''flist is a list of stylesheet filenames.They will
        be loaded and merged in order'''
        self.TTFSearchPath=['.',os.path.join(os.path.abspath(os.path.dirname(__file__)), 'fonts')]
        self.StyleSearchPath=['.',os.path.join(os.path.abspath(os.path.dirname(__file__)), 'styles')]
        if ffolder:
            self.TTFSearchPath.insert(0,ffolder)
        # Page width, height
        self.pw=0
        self.ph=0

        # Page size [w,h]
        self.ps=None

        # Margins (top,bottom,left,right,gutter)
        self.tm=0
        self.bm=0
        self.lm=0
        self.rm=0
        self.gm=0

        #text width
        self.tw=0

        self.languages=[]
        ssdata=[]
        # First parse all files and store the results
        for fname in flist:
            fname = self.findStyle(fname)
            try:
                ssdata.append(loads(open(fname).read()))
            except ValueError,e: # Error parsing the JSON data
                log.critical('Error parsing stylesheet "%s": %s'%(fname,str(e)))
                sys.exit(1)
            except IOError,e: #Error opening the ssheet
                log.critical('Error opening stylesheet "%s": %s'%(fname,str(e)))
                sys.exit(1)

        # Get pageSetup data from all stylessheets in order:
        self.ps=pagesizes.A4
        for data, ssname in zip(ssdata, flist):
            page=data.get('pageSetup', {})
            if page:
                if page.get('size', None): # A standard size
                    if page['size'] in pagesizes.__dict__:
                        self.ps=pagesizes.__dict__[page['size']]
                    else:
                        log.critical('Unknown page size %s in stylesheet %s' % (page['size'],ssname))
                        sys.exit(1)
                else: #A custom size
                    # The sizes are expressed in some unit.
                    # For example, 2cm is 2 centimeters, and we need
                    # to do 2*cm (cm comes from reportlab.lib.units)
                    if 'width' in page:
                        self.ps[0]=self.adjustUnits(page['width'])
                    if 'height' in page:
                        self.ps[1]=self.adjustUnits(page['height'])
                self.pw,self.ph=self.ps
                if 'margin-left' in page:
                    self.lm=self.adjustUnits(page['margin-left'])
                if 'margin-right' in page:
                    self.rm=self.adjustUnits(page['margin-right'])
                if 'margin-top' in page:
                    self.tm=self.adjustUnits(page['margin-top'])
                if 'margin-bottom' in page:
                    self.bm=self.adjustUnits(page['margin-bottom'])
                if 'margin-gutter' in page:
                    self.gm=self.adjustUnits(page['margin-gutter'])
                if 'firstTemplate' in page:
                    self.firstTemplate=page['firstTemplate']

                # tw is the text width.
                # We need it to calculate header-footer height
                # and compress literal blocks.
                self.tw=self.pw-self.lm-self.rm-self.gm

        # Get page templates from all stylesheets
        self.pageTemplates={}
        for data, ssname in zip(ssdata,flist):
            templates=data.get('pageTemplates',{})
            # templates is a dictionary of pageTemplates
            for key in templates:
                template=templates[key]
                # template is a dict.
                # template[´frames'] is a list of frames
                if key in self.pageTemplates:
                    self.pageTemplates[key].update(template)
                else:
                    self.pageTemplates[key]=template

        # Get font aliases from all stylesheets in order
        self.fonts={}
        for data, ssname in zip(ssdata, flist):
            self.fonts.update(data.get('fontsAlias',{}))
            embedded=data.get('embeddedFonts',[])

            for font in embedded:
                try:
                    # Each "font" is a list of four files, which will be used for
                    # regular / bold / italic / bold+italic versions of the font.
                    # If your font doesn't have one of them, just repeat the regular
                    # font.

                    # Example, using the Tuffy font from http://tulrich.com/fonts/
                    # "embeddedFonts" : [
                    #                    ["Tuffy.ttf","Tuffy_Bold.ttf","Tuffy_Italic.ttf","Tuffy_Bold_Italic.ttf"]
                    #                   ],

                    # The fonts will be registered with the file name, minus the extension.

                    for variant in font:
                        pdfmetrics.registerFont(TTFont(str(variant.split('.')[0]), self.findFont(variant)))

                        # And map them all together
                        regular,bold,italic,bolditalic = [ variant.split('.')[0] for variant in font ]
                        addMapping(regular,0,0,regular)
                        addMapping(regular,0,1,italic)
                        addMapping(regular,1,0,bold)
                        addMapping(regular,1,1,bolditalic)
                except Exception, e:
                    try:
                        fname=font[0].split('.')[0]
                        log.error("Error processing font %s: %s",fname,str(e))
                        log.error("Registering %s as Helvetica alias",fname)
                        self.fonts[fname]='Helvetica'
                    except Exception,e:
                        log.critical("Error processing font %s: %s",fname,str(e))
                        sys.exit(1)

        # Get styles from all stylesheets in order
        self.stylesheet = {}
        self.styles=[]
        self.linkColor = 'navy'
        for data, ssname in zip(ssdata, flist):
            self.linkColor = data.get('linkColor') or self.linkColor
            styles = data.get('styles', {})
            for [skey, style] in styles:
                sdict = {}
                # FIXME: this is done completely backwards
                for key in style:

                    # Handle font aliases
                    if (key == 'fontName' or key.endswith('FontName')) and style[key] in self.fonts:
                        style[key]=self.fonts[style[key]]

                    # Handle color references by name
                    elif key == 'color' or key.endswith('Color') and style[key]:
                        style[key]=formatColor(style[key])

                    # Handle alignment constants
                    elif key == 'alignment':
                        style[key]={'TA_LEFT':0, 'TA_CENTER':1, 'TA_CENTRE':1, 'TA_RIGHT':2, 'TA_JUSTIFY':4, 'DECIMAL':8}[style[key]]

                    elif key == 'language':
                        if not style[key] in self.languages:
                            self.languages.append(style[key])
                    # Make keys str instead of unicode (required by reportlab)
                    sdict[str(key)]=style[key]
                    sdict['name']=skey
                # If the style already exists, update it
                if skey in self.stylesheet:
                    self.stylesheet[skey].update(sdict)
                else: # New style
                    self.stylesheet[skey]=sdict
                    self.styles.append(sdict)
        # And create  reportlabs stylesheet
        self.StyleSheet = StyleSheet1()
        for s in self.styles:
            if 'parent' in s:
                if s['parent'] is None:
                    if s['name'] != 'base':
                        s['parent'] = self.StyleSheet['base']
                    else:
                        del(s['parent'])
                else:
                    s['parent'] = self.StyleSheet[s['parent']]
            else:
                if s['name'] != 'base':
                    s['parent'] = self.StyleSheet['base']
            self.StyleSheet.add(ParagraphStyle(**s))

    def __getitem__(self,key):
        return self.StyleSheet[key]

    def findStyle(self,fn):
        '''Given a style filename, searches for it in StyleSearchPath
        and returns the real file name'''
        if not os.path.isabs(fn):
            for D in self.StyleSearchPath:
                tfn = os.path.join(D,fn)
                if os.path.isfile(tfn):
                    return str(tfn)
        return str(fn)

    def findFont(self,fn):
        '''Given a font filename, searches for it in TTFSearchPath
        and returns the real file name'''
        if not os.path.isabs(fn):
            for D in self.TTFSearchPath:
                tfn = os.path.join(D,fn)
                if os.path.isfile(tfn):
                    return str(tfn)
        return str(fn)

    def styleForNode(self,node):
        '''Returns the right default style for any kind of node.
        That usually means "bodytext", but for sidebars, for
        example, it's sidebar'''

        if isinstance(node,docutils.nodes.sidebar):
            return self['sidebar']
        return self['bodytext']

    def tstyleHead(self,rows=1):
        '''Returns a table style spec for a table header of `rows` based on the
        table-heading style from the stylesheet'''
        # This alignment thing is exactly backwards from the alignment for paragraphstyles
        alignment={0:'LEFT', 1:'CENTER', 1:'CENTRE', 2:'RIGHT', 4:'JUSTIFY',8:'DECIMAL'}[self['table-heading'].alignment]
        return [('BACKGROUND',(0,0),(-1,rows-1),self['table-heading'].backColor),
                ('ALIGN',(0,0),(-1,rows-1),alignment),
                ('TEXTCOLOR',(0,0),(-1,rows-1),self['table-heading'].textColor),
                ('FONT',(0,0),(-1,rows-1),self['table-heading'].fontName,
                                          self['table-heading'].fontSize,
                                          self['table-heading'].leading),
                ('VALIGN',(0,0),(-1,rows-1),self['table-heading'].valign)
               ]

    def tstyleBody(self,rows=1):
        '''Returns a table style spec for a table of any size based on the
        table style from the stylesheet'''
        return [("ROWBACKGROUNDS",(0,0),(-1,-1),
                [formatColor(c,numeric=False) for c in self['table'].rowBackgrounds]),
            ]

    def pStyleToTStyle(self,style,x,y):
        '''Given a reportlab paragraph style, returns a spec for a table style
           that adopts some of its features (example, background color)'''
        results=[]
        if style.backColor:
            results.append(('BACKGROUND',(x,y),(x,y),style.backColor))
        if style.borderWidth:
            bw=style.borderWidth
            del(style.__dict__['borderWidth'])
            if style.borderColor:
                bc=style.borderColor
                del(style.__dict__['borderColor'])
            else:
                bc=colors.black
            results.append(('BOX',(x,y),(x,y),bw,bc))
        return results

    def adjustUnits(self, v, total = None):
        if total is None:
            total = self.pw
        return adjustUnits(v,total)

def adjustUnits(v,total=None):
    '''Takes something like 2cm and returns 2*cm.
    If you use % as a unit, it returns the percentage
    of "total".

    If total is not given, returns a percentage of the page
    width. However, if you get to that stage, you are
    doing it wrong.

    Example::

            >>> adjustUnits('50%',200)
            100
    '''

    v = str(v)
    _,n,u = re.split('(-?[0-9\.]*)',v)
    if not u:
        return float(n) # assume points
    if u in units.__dict__:
        return float(n) * units.__dict__[u]
    else:
        if u=='%':
            return float(n) * total/100
        log.error('Unknown unit "%s"' % u)
    return float(n)

# Some table styles used for pieces of the document

tstyles={}

# Used for regular tables

tstyleNorm = [ ('VALIGN',(0,0),(-1,-1),'TOP'),
               ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
               ('BOX', (0,0), (-1,-1), 0.25, colors.black),
             ]

# Header row in tables

tstyles['normal']=TableStyle(tstyleNorm)

# Used for field lists

tstyles['field']=TableStyle([ ('VALIGN',(0,0),(-1,-1),'TOP'),
                              ('ALIGNMENT',(0,0),(1,-1),'RIGHT'),
                            ])
fieldlist_lwidth=3*units.cm

# Used for endnotes

tstyles['endnote']=TableStyle([ ('VALIGN',(0,0),(-1,-1),'TOP'),
                                ('ALIGNMENT',(0,0),(1,-1),'RIGHT'),
                              ])
# Left column of the endnote. The content of the note takes the rest of
# the available space
endnote_lwidth=2*units.cm

# Used for sidebars

tstyles['sidebar']=TableStyle([ ('VALIGN',(0,0),(-1,-1),'TOP'),
                             ('BACKGROUND',(0,0),(-1,-1),colors.lightyellow),
                           ])

tstyles['bullet']=TableStyle([ ('VALIGN',(0,0),(-1,-1),'TOP'),
                             ])

def formatColor(value,numeric=True):
    '''Converts a color like "gray" or "0xf" or "ffff" to something
    reportlab will like'''
    if value in colors.__dict__:
        return colors.__dict__[value]
    else: # Hopefully, a hex color:
        c = value.strip()
        if c[0] == '#':
            c = c[1:]
        while len(c) < 6:
            c = '0'+c
        if numeric:
            r = int(c[:2],16)
            g = int(c[2:4],16)
            b = int(c[4:6],16)
            return colors.Color(r,g,b)
        else:
            return str("#"+c)
<|endoftext|>


<commit_before>

"""Encapsulates functionality of moving around robot arm"""
import os

import zbar
from PIL import Image
import cv2
import numpy as np
import time

import bot.lib.lib as lib
from bot.hardware.servo_cape import ServoCape

from bot.hardware.qr_code import QRCode
from bot.hardware.complex_hardware.QRCode2 import QRCode2
from SeventhDOF import Rail_Mover
from bot.hardware.complex_hardware.camera_reader import Camera

class RobotArm(object):

    JUNK_BUFFER = [0]*5
    HOME = [0, 145, 0, 160, 0]
    GRAB = 5

    """An object that resembles a robotic arm with n joints"""
    def __init__(self, arm_config):

        self.logger = lib.get_logger()
        self.bot_config = lib.get_config()

        self.servo_cape \
            = ServoCape(self.bot_config["dagu_arm"]["servo_cape_arm"])
        self.servo_cape_grabber \
            = ServoCape(self.bot_config["dagu_arm"]["servo_cape_grabber"])
<<<<<<< HEAD
        # Empty list of zeros representing each joint
        #self.joints = [0]*5

=======
>>>>>>> 10dc643f7ae5c002b24dad995419b5eaa108fd9b

        # QR scanning tools.
        self.scanner = zbar.ImageScanner()
        self.scanner.parse_config('enable')

        # Figure out what camera is being used
        cam_model = arm_config["camera"]
        self.cam = Camera(self.bot_config[cam_model])
        self.rail = Rail_Mover()

        # initialize vertices of QR code
        l = 1.5
        self.qr_verts = np.float32([[-l/2, -l/2, 0],
                            [-l/2,  l/2, 0],
                            [ l/2, -l/2, 0],
                            [ l/2,  l/2, 0]])

        # Angles of all of the joints.
        # DO NOT SEND ANGLES ANY OTHER WAY
        self.joints = self.HOME

        self.hopper = [None, None, None, None]

    @property
    def joints(self):
        return self.__joints

    @joints.setter
    def joints(self, vals):
        vals =  [int(x) for x in vals]
        # validate values
        if len(vals) == 5:
            self.__joints = vals
        else:
            self.__joints[:len(vals)] = vals
        print "Joints to be sent: ", vals
        self.servo_cape.transmit_block([0] + self.__joints)

    @lib.api_call
    def draw_qr_on_frame(self, zbar_dat, draw_frame):

        self.scanner.scan(zbar_dat)
        for symbol in zbar_dat:
            tl, bl, br, tr = [item for item in symbol.location]
            points = np.float32([[tl[0], tl[1]],
                                 [tr[0], tr[1]],
                                 [bl[0], bl[1]],
                                 [br[0], br[1]]])

            cv2.line(draw_frame, tl, bl, (100,0,255), 8, 8)
            cv2.line(draw_frame, bl, br, (100,0,255), 8, 8)
            cv2.line(draw_frame, br, tr, (100,0,255), 8, 8)
            cv2.line(draw_frame, tr, tl, (100,0,255), 8, 8)

        return draw_frame
    @lib.api_call
    def grab(self):

        self.servo_cape_grabber.transmit_block([5] + self.JUNK_BUFFER)

    @lib.api_call
    def release(self):
        self.servo_cape_grabber.transmit_block([6] + self.JUNK_BUFFER)

    @lib.api_call
    def joint_center_on_qr(self):
        """Attempts to center arm on qr code using only arm itself.
        Only the rotational joints,
        joint 0 corrects X
        joint 3 corrects Y
        joint 5 corrects rotation
        """

        # Correction constants for P(ID) controller.
        # unlikely that we'll bother using I or D
        p_x = 1
        p_y = 1

        while True:
            ret = self.cam.QRSweep()

            # Calculate new vector for change
            if ret != None:

                dx = ret.tvec[0]
                dy = ret.tvec[1]

                if abs(dx) > 0.2:
                    self.joints[0] += p_x * dx
                if abs(dy) > 0.2:
                    self.joints[3] += p_y * dy
                #print "Joints = ", self.joints
                self.joints = self.joints
                #TODO Find method for calculating rotational oreientation

        return True

    @lib.api_call
    def demo_set_angles(self):
        while(1):
            A1 = (input("What is Servo 1's angle?: "))
            if (A1 < 0 or A1> 180):
                print "Error: Given angle is out of range."
                continue
            else:
                break

        while(1):
            A2 = (input("What is Servo 2's angle?: "))
            if (A2 < 0 or A2> 180):
                print "Error: Given angle is out of range."
                continue
            else:
                break

        while(1):
            A3 = (input("What is Servo 3's angle?: "))
            if (A3 < 0 or A3> 180):
                print "Error: Given angle is out of range."
                continue
            else:
                break

        while(1):
            A4 = (input("What is Servo 4's angle?: "))
            if (A4 < 0 or A4> 180):
                print "Error: Given angle is out of range."
                continue
            else:
                break

        while(1):
            A5 = (input("What is Servo 5's angle?: "))
            if (A5 < 0 or A5> 180):
                print "Error: Given angle is out of range."
                continue
            else:
                break

        while(1):
            answer = raw_input("Do you want to send these angles(y/n)?: ")
            if (answer == 'n'):
                return
            elif (answer == "y"):
                array = [A1,A2,A3,A4,A5]
                self.servo_cape.transmit_block([0] + array)
                return
            else:
                print "Error: Invalid reply. Please answer in y/n format."

    @lib.api_call
    def reset_home_position(self):
        """sets angles back to default position."""
        self.servo_cape.transmit_block([0] + HOME)

    @lib.api_call
    def fancy_demo(self):
        os.system('clear')
        print "Welcome to the Team 26: Robotic Arm Mainipulation and Vision demo function."
        print "Demo number      Function       "
        print "1                Input custom angles"
        print "2                Block grap demo"
        print "3                Wave"
        print "4                Stand up straight"
        print "5                Grab"
        print "6                Release"
        print "7                Show range of servos"
        print "8                Initial position"
        print "9                Exit"
        print ""
        while(1):
            demo_number = (input("Please input your desired operation number: "))
            if (demo_number is "exit"):
                return
            elif (demo_number > 9 or demo_number < 0):
                print "Number not within valid range (1-9)."
                continue
            elif (demo_number == 9):
                return
            elif (demo_number == 1):
                self.set_angles()
            else:
                self.demo(demo_number - 1)

    @lib.api_call
    def demo(self, demo_number):
        """runs demos 1-7"""
        self.servo_cape.transmit_block([demo_number]
                                         + self.JUNK_BUFFER)

    def rail_test(self):

        while True:
            #time.sleep(2)
            ret = self.cam.QRSweep()
            if ret != None:
                x_disp = ret.tvec[0]
                print "Checking Alignment with x_disp = ", x_disp
                if abs(x_disp) > .2:
                    self.rail.DisplacementConverter(-1 * x_disp)

            ret = None

    def rail_feedback(self):
        """
        align the arm on the rail to a qrcode and return the code
        """

        while(True):
            ret = None
            ret = self.cam.QRSweep()
            if ret != None:
                x_disp = ret.tvec[0]
                if abs(x_disp) < .1:
                    return ret
                else:
                    print "Checking Alignment with x_disp = ", x_disp
                    if abs(x_disp) > .1:
                        rail_ret = self.rail.DisplacementConverter(-1 * x_disp)
                        if rail_ret == 0:
                            #out of range, reset to middle and try again
                            disp = self.rail.DMCC[1].motors[2].position
                            ticks = 3000 - disp
                            self.rail.DisplacementMover(ticks)

    def test_look(self):
        self.servo_cape.transmit_block([0] + [0, 125, 0, 170, 0])

    def control_test(self):

        #assuming rail height of 5 inches for test
        BLOCK_MOVE_5 = [0, 60, 20, 40, 0]
        BLOCK_GRAB_5 = [0, 0, 10, 50, 0]
        HOPPER1 = [0, 0, 10, 0, 180]
        HOPPER2 = [0, 0, 180, 0, 180]
        HOPPER3 = [0, 40, 180, 0, 180]
        LOOK_5 = [0, 115, 0, 160, 0]

        """
        initial: 0, 145, 0, 160, 0
        4
        move1: 0, 60, 20, 40, 0
        3
        low_grab: 0, 0, 10, 40, 0
        3
        hopper1: 0, 0, 10, 0, 0
        2
        hopper2: 0, 0, 180, 0, 0
        3
        hopper3: 0, 40, 180, 0, 0

        """
        time.sleep(3)
        self.servo_cape.transmit_block([0] + LOOK_5)
        time.sleep(3)
        self.rail.DisplacementConverter(4.5)  #get the rail to the middle

        qr = self.rail_feedback()           #position infront of QRCode
        print "waiting after rail feedback"
        time.sleep(3.5)
        print "trying to move the arm"
        self.servo_cape.transmit_block([0] + BLOCK_MOVE_5)
        time.sleep(3.5)                     #wait for arm to move to location
        self.servo_cape.transmit_block([0] + BLOCK_GRAB_5)
        time.sleep(3.5)                     #wait for arm to move to location
        self.grab()
        time.sleep(2)                       #wait for arm to grab
        self.servo_cape.transmit_block([0] + HOPPER1)
        time.sleep(2)                       #wait for arm to move to location
        self.servo_cape.transmit_block([0] + HOPPER2)
        time.sleep(3.5)                     #wait for arm to move to location
        self.rail.Orientor(1)
        time.sleep(.25)                     #wait for rail to move to bin location
        self.servo_cape.transmit_block([0] + HOPPER3)
        time.sleep(1.5)                     #wait for arm to move to location

        self.release()
        time.sleep(2)

    def basic_control(self, signal):
        #Start signal recieved
        barge_level = signal
        grab_pos = 0;

        if barge_height == 1:
            grab_pos = self.BARGE_GRAB1
            look_pos = self.BARGE_LOOK1
        elif barge_height == 2:
            grab_pos = self.BARGE_GRAB2
            look_pos = self.BARGE_LOOK2
        elif barge_height == 3:
            grab_pos = self.BARGE_GRAB3
            look_pos = self.BARGE_LOOK3

        if self.hopper[0] != None:
            hopper_pos = 1
        elif self.hopper[1] != None:
            hopper_pos = 2
        elif self.hopper[2] != None:
            hopper_pos = 3
        elif self.hopper[3] != None:
            hopper_pos = 4
        else:
            print "Error: Hopper is full."
            return

        #Set the Arm to its default looking position
        self.demo(self.DEFAULT_LOOK)
        #Read from webcam
        time.sleep(2)
        ret = self.camera.readQR()

        #No QRs found
        if ret == None:
            time.sleep(2)
            ret = self.readQR()
            if ret == None:
                print "No QRCode Found"
                return

        #Multiple QRs Found
            # Choose one (closest X  then highest Y) -> 1 QR found
            # built into readQR()

        #adjust 7DOF to center on QR
        while(True):
            disp_x = ret.tvec[0]
            print "Checking Alignment with x_disp = ", x_disp
            if abs(x_disp) > .2:
                self.rail.DisplacementConverter(x_disp)
            else:
                break

        #extend arm towards block for 2nd check
        self.set_pos(look_pos)
        time.sleep(2)

        ret = self.cam.readQR()

        #If not aligned adjust again with 7 DOF
        while(True):
            x_disp = ret.tvec[0]
            print "Checking Alignment with x_disp = ", x_disp
            if abs(x_disp) > .2:
                self.rail.DisplacementConverter(x_disp)
            else:
                break

        #Once Aligned: extend arm fully to grab position
        self.demo(grab_pos)
        time.sleep(2)

        #Check for QR again if possible (if even needed) then grab
        self.grab()
        time.sleep(1)

        #Pick up and place in empty hopper bin
        self.demo(self.DEFAULT_GRABBED)
        time.sleep(2)

        self.rail.Orientor(hopper_pos)
        time.sleep(1.5)
        self.release()
        time.sleep(1)
        self.hopper[hopper_pos] = ret

        #return to default looking position
        self.demo(DEFAULT)
        time.sleep(1)
        return 1

<commit_msg>Making a generic tier grab function<commit_after>

"""Encapsulates functionality of moving around robot arm"""
import os

import zbar
from PIL import Image
import cv2
import numpy as np
import time

import bot.lib.lib as lib
from bot.hardware.servo_cape import ServoCape

from bot.hardware.qr_code import QRCode
from bot.hardware.complex_hardware.QRCode2 import QRCode2
from SeventhDOF import Rail_Mover
from bot.hardware.complex_hardware.camera_reader import Camera

class RobotArm(object):

    JUNK_BUFFER = [0]*5
    HOME = [0, 145, 0, 160, 0]
    GRAB = 5

    """An object that resembles a robotic arm with n joints"""
    def __init__(self, arm_config):

        self.logger = lib.get_logger()
        self.bot_config = lib.get_config()

        self.servo_cape \
            = ServoCape(self.bot_config["dagu_arm"]["servo_cape_arm"])
        self.servo_cape_grabber \
            = ServoCape(self.bot_config["dagu_arm"]["servo_cape_grabber"])
<<<<<<< HEAD
        # Empty list of zeros representing each joint
        #self.joints = [0]*5

=======
>>>>>>> 10dc643f7ae5c002b24dad995419b5eaa108fd9b

        # QR scanning tools.
        self.scanner = zbar.ImageScanner()
        self.scanner.parse_config('enable')

        # Figure out what camera is being used
        cam_model = arm_config["camera"]
        self.cam = Camera(self.bot_config[cam_model])
        self.rail = Rail_Mover()

        # initialize vertices of QR code
        l = 1.5
        self.qr_verts = np.float32([[-l/2, -l/2, 0],
                            [-l/2,  l/2, 0],
                            [ l/2, -l/2, 0],
                            [ l/2,  l/2, 0]])

        # Angles of all of the joints.
        # DO NOT SEND ANGLES ANY OTHER WAY
        self.joints = self.HOME

        self.hopper = [None, None, None, None]

    @property
    def joints(self):
        return self.__joints

    @joints.setter
    def joints(self, vals):
        vals =  [int(x) for x in vals]
        # validate values
        if len(vals) == 5:
            self.__joints = vals
        else:
            self.__joints[:len(vals)] = vals
        print "Joints to be sent: ", vals
        self.servo_cape.transmit_block([0] + self.__joints)

    @lib.api_call
    def draw_qr_on_frame(self, zbar_dat, draw_frame):

        self.scanner.scan(zbar_dat)
        for symbol in zbar_dat:
            tl, bl, br, tr = [item for item in symbol.location]
            points = np.float32([[tl[0], tl[1]],
                                 [tr[0], tr[1]],
                                 [bl[0], bl[1]],
                                 [br[0], br[1]]])

            cv2.line(draw_frame, tl, bl, (100,0,255), 8, 8)
            cv2.line(draw_frame, bl, br, (100,0,255), 8, 8)
            cv2.line(draw_frame, br, tr, (100,0,255), 8, 8)
            cv2.line(draw_frame, tr, tl, (100,0,255), 8, 8)

        return draw_frame
    @lib.api_call
    def grab(self):

        self.servo_cape_grabber.transmit_block([5] + self.JUNK_BUFFER)

    @lib.api_call
    def release(self):
        self.servo_cape_grabber.transmit_block([6] + self.JUNK_BUFFER)

    @lib.api_call
    def joint_center_on_qr(self):
        """Attempts to center arm on qr code using only arm itself.
        Only the rotational joints,
        joint 0 corrects X
        joint 3 corrects Y
        joint 5 corrects rotation
        """

        # Correction constants for P(ID) controller.
        # unlikely that we'll bother using I or D
        p_x = 1
        p_y = 1

        while True:
            ret = self.cam.QRSweep()

            # Calculate new vector for change
            if ret != None:

                dx = ret.tvec[0]
                dy = ret.tvec[1]

                if abs(dx) > 0.2:
                    self.joints[0] += p_x * dx
                if abs(dy) > 0.2:
                    self.joints[3] += p_y * dy
                #print "Joints = ", self.joints
                self.joints = self.joints
                #TODO Find method for calculating rotational oreientation

        return True

    @lib.api_call
    def demo_set_angles(self):
        while(1):
            A1 = (input("What is Servo 1's angle?: "))
            if (A1 < 0 or A1> 180):
                print "Error: Given angle is out of range."
                continue
            else:
                break

        while(1):
            A2 = (input("What is Servo 2's angle?: "))
            if (A2 < 0 or A2> 180):
                print "Error: Given angle is out of range."
                continue
            else:
                break

        while(1):
            A3 = (input("What is Servo 3's angle?: "))
            if (A3 < 0 or A3> 180):
                print "Error: Given angle is out of range."
                continue
            else:
                break

        while(1):
            A4 = (input("What is Servo 4's angle?: "))
            if (A4 < 0 or A4> 180):
                print "Error: Given angle is out of range."
                continue
            else:
                break

        while(1):
            A5 = (input("What is Servo 5's angle?: "))
            if (A5 < 0 or A5> 180):
                print "Error: Given angle is out of range."
                continue
            else:
                break

        while(1):
            answer = raw_input("Do you want to send these angles(y/n)?: ")
            if (answer == 'n'):
                return
            elif (answer == "y"):
                array = [A1,A2,A3,A4,A5]
                self.servo_cape.transmit_block([0] + array)
                return
            else:
                print "Error: Invalid reply. Please answer in y/n format."

    @lib.api_call
    def reset_home_position(self):
        """sets angles back to default position."""
        self.servo_cape.transmit_block([0] + HOME)

    @lib.api_call
    def fancy_demo(self):
        os.system('clear')
        print "Welcome to the Team 26: Robotic Arm Mainipulation and Vision demo function."
        print "Demo number      Function       "
        print "1                Input custom angles"
        print "2                Block grap demo"
        print "3                Wave"
        print "4                Stand up straight"
        print "5                Grab"
        print "6                Release"
        print "7                Show range of servos"
        print "8                Initial position"
        print "9                Exit"
        print ""
        while(1):
            demo_number = (input("Please input your desired operation number: "))
            if (demo_number is "exit"):
                return
            elif (demo_number > 9 or demo_number < 0):
                print "Number not within valid range (1-9)."
                continue
            elif (demo_number == 9):
                return
            elif (demo_number == 1):
                self.set_angles()
            else:
                self.demo(demo_number - 1)

    @lib.api_call
    def demo(self, demo_number):
        """runs demos 1-7"""
        self.servo_cape.transmit_block([demo_number]
                                         + self.JUNK_BUFFER)

    def rail_test(self):

        while True:
            #time.sleep(2)
            ret = self.cam.QRSweep()
            if ret != None:
                x_disp = ret.tvec[0]
                print "Checking Alignment with x_disp = ", x_disp
                if abs(x_disp) > .2:
                    self.rail.DisplacementConverter(-1 * x_disp)

            ret = None

    def rail_feedback(self):
        """
        align the arm on the rail to a qrcode and return the code
        """

        while(True):
            ret = None
            ret = self.cam.QRSweep()
            if ret != None:
                x_disp = ret.tvec[0]
                if abs(x_disp) < .1:
                    return ret
                else:
                    print "Checking Alignment with x_disp = ", x_disp
                    if abs(x_disp) > .1:
                        rail_ret = self.rail.DisplacementConverter(-1 * x_disp)
                        if rail_ret == 0:
                            #out of range, reset to middle and try again
                            disp = self.rail.DMCC[1].motors[2].position
                            ticks = 3000 - disp
                            self.rail.DisplacementMover(ticks)

    def test_look(self):
        self.servo_cape.transmit_block([0] + [0, 125, 0, 170, 0])

	def Tier_Grab(self, Tier):
		if Tier == 'A':
			print "Not coded yet"
			BLOCK_MOVE_5 = [0, 90, 90, 90, 0]
			BLOCK_GRAB_5 = [0, 90, 90, 90, 0]

		elif Tier == 'B':
			BLOCK_MOVE_5 = [0, 80, 10, 100, 0]
			BLOCK_GRAB_5 = [0, 74, 10, 100, 0]
			LOOK_5 = [0, 115, 0, 140, 0]
			HOPPER1 = [0, 78, 10, 0, 180]
			HOPPER2 = [0, 80, 180, 0, 180]
			HOPPER3 = [0, 40, 180, 0, 180]

		elif Tier == 'C':
			BLOCK_MOVE_5 = [0, 60, 20, 40, 0]
			BLOCK_GRAB_5 = [0, 0, 10, 50, 0]
			LOOK_5 = [0, 115, 0, 150, 0]
		    HOPPER1 = [0, 0, 10, 0, 180]
			HOPPER2 = [0, 0, 180, 0, 180]
			HOPPER3 = [0, 40, 180, 0, 180]

		time.sleep(3)
        self.servo_cape.transmit_block([0] + LOOK_5)
        time.sleep(3)
        self.rail.DisplacementConverter(4.5)  #get the rail to the middle

		if Tier == 'B' or Tier == 'C':
			qr = self.rail_feedback()           #position infront of QRCode
		else:
			##Todo: Add in generic block code here
			print "Line up with generic blocks"

		hopper_pos = 5

		if self.hopper[0] == None:
            hopper_pos = 1
        elif self.hopper[1] == None:
            hopper_pos = 2
        elif self.hopper[2] == None:
            hopper_pos = 3
        elif self.hopper[3] == None:
            hopper_pos = 4
		else:
			print "error~Hopper Full"
			return 0

		self.servo_cape.transmit_block([0] + BLOCK_MOVE_5)
        time.sleep(3)                     #wait for arm to move to location
        self.servo_cape.transmit_block([0] + BLOCK_GRAB_5)
        time.sleep(3)                     #wait for arm to move to location
        self.grab()
        time.sleep(2)                       #wait for arm to grab
        self.servo_cape.transmit_block([0] + HOPPER1)
        time.sleep(2)                       #wait for arm to move to location
        self.servo_cape.transmit_block([0] + HOPPER2)
        time.sleep(3)                     #wait for arm to move to location
        self.rail.Orientor(hopper_pos)
        time.sleep(.25)                     #wait for rail to move to bin location
        self.servo_cape.transmit_block([0] + HOPPER3)
        time.sleep(1.5)                     #wait for arm to move to location

		self.hopper[hopper_pos] = qr

    def control_test(self):

        #assuming rail height of 5 inches for test
        BLOCK_MOVE_5 = [0, 60, 20, 40, 0]
        BLOCK_GRAB_5 = [0, 0, 10, 50, 0]
        HOPPER1 = [0, 0, 10, 0, 180]
        HOPPER2 = [0, 0, 180, 0, 180]
        HOPPER3 = [0, 40, 180, 0, 180]
        LOOK_5 = [0, 115, 0, 160, 0]

        """
        initial: 0, 145, 0, 160, 0
        4
        move1: 0, 60, 20, 40, 0
        3
        low_grab: 0, 0, 10, 40, 0
        3
        hopper1: 0, 0, 10, 0, 0
        2
        hopper2: 0, 0, 180, 0, 0
        3
        hopper3: 0, 40, 180, 0, 0

        """
        time.sleep(3)
        self.servo_cape.transmit_block([0] + LOOK_5)
        time.sleep(3)
        self.rail.DisplacementConverter(4.5)  #get the rail to the middle

        qr = self.rail_feedback()           #position infront of QRCode
        print "waiting after rail feedback"
        time.sleep(3.5)
        print "trying to move the arm"
        self.servo_cape.transmit_block([0] + BLOCK_MOVE_5)
        time.sleep(3.5)                     #wait for arm to move to location
        self.servo_cape.transmit_block([0] + BLOCK_GRAB_5)
        time.sleep(3.5)                     #wait for arm to move to location
        self.grab()
        time.sleep(2)                       #wait for arm to grab
        self.servo_cape.transmit_block([0] + HOPPER1)
        time.sleep(2)                       #wait for arm to move to location
        self.servo_cape.transmit_block([0] + HOPPER2)
        time.sleep(3.5)                     #wait for arm to move to location
        self.rail.Orientor(1)
        time.sleep(.25)                     #wait for rail to move to bin location
        self.servo_cape.transmit_block([0] + HOPPER3)
        time.sleep(1.5)                     #wait for arm to move to location

        self.release()
        time.sleep(2)

    def basic_control(self, signal):
        #Start signal recieved
        barge_level = signal
        grab_pos = 0;

        if barge_height == 1:
            grab_pos = self.BARGE_GRAB1
            look_pos = self.BARGE_LOOK1
        elif barge_height == 2:
            grab_pos = self.BARGE_GRAB2
            look_pos = self.BARGE_LOOK2
        elif barge_height == 3:
            grab_pos = self.BARGE_GRAB3
            look_pos = self.BARGE_LOOK3

        if self.hopper[0] != None:
            hopper_pos = 1
        elif self.hopper[1] != None:
            hopper_pos = 2
        elif self.hopper[2] != None:
            hopper_pos = 3
        elif self.hopper[3] != None:
            hopper_pos = 4
        else:
            print "Error: Hopper is full."
            return

        #Set the Arm to its default looking position
        self.demo(self.DEFAULT_LOOK)
        #Read from webcam
        time.sleep(2)
        ret = self.camera.readQR()

        #No QRs found
        if ret == None:
            time.sleep(2)
            ret = self.readQR()
            if ret == None:
                print "No QRCode Found"
                return

        #Multiple QRs Found
            # Choose one (closest X  then highest Y) -> 1 QR found
            # built into readQR()

        #adjust 7DOF to center on QR
        while(True):
            disp_x = ret.tvec[0]
            print "Checking Alignment with x_disp = ", x_disp
            if abs(x_disp) > .2:
                self.rail.DisplacementConverter(x_disp)
            else:
                break

        #extend arm towards block for 2nd check
        self.set_pos(look_pos)
        time.sleep(2)

        ret = self.cam.readQR()

        #If not aligned adjust again with 7 DOF
        while(True):
            x_disp = ret.tvec[0]
            print "Checking Alignment with x_disp = ", x_disp
            if abs(x_disp) > .2:
                self.rail.DisplacementConverter(x_disp)
            else:
                break

        #Once Aligned: extend arm fully to grab position
        self.demo(grab_pos)
        time.sleep(2)

        #Check for QR again if possible (if even needed) then grab
        self.grab()
        time.sleep(1)

        #Pick up and place in empty hopper bin
        self.demo(self.DEFAULT_GRABBED)
        time.sleep(2)

        self.rail.Orientor(hopper_pos)
        time.sleep(1.5)
        self.release()
        time.sleep(1)
        self.hopper[hopper_pos] = ret

        #return to default looking position
        self.demo(DEFAULT)
        time.sleep(1)
        return 1

<|endoftext|>


<commit_before>'''
Copyright (c) 2011 by RisingTide Systems LLC
Copyright (c) 2013 by Andy Grover

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, version 3 (AGPLv3).

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Description
-----------

Instead of having a class to handle each fabric type, rtslib uses a
single FabricModule class. An instance of FabricModule changes its
behavior depending on parameters it picks up from here. These used to
be listed in "spec" files, but are now here.

Available parameters
--------------------

* features
Lists the target fabric available features. Default value:
("discovery_auth", "acls", "acls_auth", "nps")
example: features = ("discovery_auth", "acls", "acls_auth")
example: features = () # no features supported

Detail of features:

  * tpgts
  The target fabric module is using iSCSI-style target portal group tags.

  * discovery_auth
  The target fabric module supports a fabric-wide authentication for
  discovery.

  * acls
  The target's TPGTs support explicit initiator ACLs.

  * acls_auth
  The target's TPGT's ACLs support per-ACL initiator authentication.

  * nps
  The TPGTs support iSCSI-like IPv4/IPv6 network portals, using IP:PORT
  group names.

  * nexus
  The TPGTs have a 'nexus' attribute that contains the local initiator
  serial unit. This attribute must be set before being able to create any
  LUNs.

* wwn_type
Sets the type of WWN expected by the target fabric. Defaults to 'free'.
Example: wwn_type = "iqn"
Current valid types are:

  * free
  Freeform WWN.

  * iqn
  The fabric module targets are using iSCSI-type IQNs.

  * naa
  NAA SAS address type WWN.

  * unit_serial
  Disk-type unit serial.

* wwns()
This function returns an iterable (either generator or list) of valid
target WWNs for the fabric, if WWNs should be chosen from existing
fabric interfaces. The most common case for this is hardware-set
WWNs. This function should return a string with the WWN formatted for
what the fabric module expects.

* kernel_module
Sets the name of the kernel module implementing the fabric modules. If not
specified, it will be assumed to be MODNAME_target_mod, where MODNAME is the
name of the fabric module, as used to name the spec file. Note that you must
not specify any .ko or such extension here.
Example: kernel_module = "my_module"

* configfs_group
Sets the name of the configfs group used by the fabric module. Defaults to the
name of the module as used to name the spec file.
Example: configfs_group = "iscsi"

'''

import os
from glob import iglob as glob
from utils import fread

def _colonize(str):
    '''
    helper function for the specfiles to add colons every 2 chars
    '''
    new_str = ""
    while str:
        new_str += str[:2] + ":"
        str = str[2:]
        return new_str[:-1]

# ---- Fabric override definitions ----

# Transform 'fe80:0000:0000:0000:0002:1903:000e:8acd' WWN notation to
# '0xfe8000000000000000021903000e8acd'
def _srpt_wwns():
    for wwn_file in glob("/sys/class/infiniband/*/ports/*/gids/0"):
        yield "0x" + fread(wwn_file).translate(None, ":")

_ib_srpt = dict(features = ("acls",),
                kernel_module="ib_srpt",
                configfs_group="srpt",
                wwns=_srpt_wwns)

_iscsi = dict(wwn_type="iqn")

_loopback = dict(features=("nexus",),
                 wwn_type = "naa",
                 kernel_module = "tcm_loop")

def _qla_wwns():
    for wwn_file in glob("/sys/class/fc_host/host*/port_name"):
        yield _colonize(fread(wwn_file)[2:])

_qla2xxx = dict(features=("acls",),
                kernel_module="tcm_qla2xxx",
                wwns=_qla_wwns)

# We need a single unique value to create the target.
# Return the first local 1394 device's guid.
def _sbp_wwns():
    for fname in glob("/sys/bus/firewire/devices/fw*/is_local"):
        if bool(int(fread(fname))):
            guid_path = os.path.dirname(fname) + "/guid"
            yield fread(guid_path)[2:]
            break

_sbp = dict(features=(),
            kernel_module="sbp_target",
            wwns=_sbp_wwns)

# Transform '0x1234567812345678' WWN notation to '12:34:56:78:12:34:56:78'
def _fc_wwns():
    for wwn_file in glob("/sys/class/fc_host/host*/port_name"):
        yield _colonize(fread(wwn_file)[2:])

_tcm_fc = dict(features = ("acls",),
               kernel_module = "tcm_fc",
               configfs_group = "fc",
               wwns=_fc_wwns)

_usb_gadget = dict(features=("nexus",),
                   wwn_type="naa",
                   kernel_module="tcm_usb_gadget")

# ---- Putting it all together ----

# list of tuples containing fabric name and spec overrides, if any
fabrics = [
    ("ib_srpt", _ib_srpt),
    ("iscsi", _iscsi),
    ("loopback", _loopback),
    ("qla2xxxx", _qla2xxx),
    ("sbp", _sbp),
    ("tcm_fc", _tcm_fc),
    ("usb_gadget", _usb_gadget),
    ]

_default_features = ('discovery_auth', 'acls', 'acls_auth', 'nps', 'tpgts')
_default_wwn_type = 'free'

# Merge defaults and overrides to create actual specfile dict
specs = {}
for f_name, f_ovr in fabrics:
    fabric = {}
    fabric['features'] = f_ovr.get("features", _default_features)
    fabric['kernel_module'] = f_ovr.get("kernel_module", "%s_target_mod" % f_name)
    fabric['configfs_group'] = f_ovr.get("configfs_group", f_name)
    fabric['wwn_type'] = f_ovr.get("wwn_type", _default_wwn_type)
    if 'wwns' in f_ovr:
        fabric['wwn_list'] = list(f_ovr['wwns']())
    else:
        fabric['wwn_list'] = None

    specs[f_name] = fabric
<commit_msg>Fix CnP error<commit_after>'''
Copyright (c) 2011 by RisingTide Systems LLC
Copyright (c) 2013 by Andy Grover

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, version 3 (AGPLv3).

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Description
-----------

Instead of having a class to handle each fabric type, rtslib uses a
single FabricModule class. An instance of FabricModule changes its
behavior depending on parameters it picks up from here. These used to
be listed in "spec" files, but are now here.

Available parameters
--------------------

* features
Lists the target fabric available features. Default value:
("discovery_auth", "acls", "acls_auth", "nps")
example: features = ("discovery_auth", "acls", "acls_auth")
example: features = () # no features supported

Detail of features:

  * tpgts
  The target fabric module is using iSCSI-style target portal group tags.

  * discovery_auth
  The target fabric module supports a fabric-wide authentication for
  discovery.

  * acls
  The target's TPGTs support explicit initiator ACLs.

  * acls_auth
  The target's TPGT's ACLs support per-ACL initiator authentication.

  * nps
  The TPGTs support iSCSI-like IPv4/IPv6 network portals, using IP:PORT
  group names.

  * nexus
  The TPGTs have a 'nexus' attribute that contains the local initiator
  serial unit. This attribute must be set before being able to create any
  LUNs.

* wwn_type
Sets the type of WWN expected by the target fabric. Defaults to 'free'.
Example: wwn_type = "iqn"
Current valid types are:

  * free
  Freeform WWN.

  * iqn
  The fabric module targets are using iSCSI-type IQNs.

  * naa
  NAA SAS address type WWN.

  * unit_serial
  Disk-type unit serial.

* wwns()
This function returns an iterable (either generator or list) of valid
target WWNs for the fabric, if WWNs should be chosen from existing
fabric interfaces. The most common case for this is hardware-set
WWNs. This function should return a string with the WWN formatted for
what the fabric module expects.

* kernel_module
Sets the name of the kernel module implementing the fabric modules. If not
specified, it will be assumed to be MODNAME_target_mod, where MODNAME is the
name of the fabric module, as used to name the spec file. Note that you must
not specify any .ko or such extension here.
Example: kernel_module = "my_module"

* configfs_group
Sets the name of the configfs group used by the fabric module. Defaults to the
name of the module as used to name the spec file.
Example: configfs_group = "iscsi"

'''

import os
from glob import iglob as glob
from utils import fread

def _colonize(str):
    '''
    helper function to add colons every 2 chars
    '''
    new_str = ""
    while str:
        new_str += str[:2] + ":"
        str = str[2:]
    return new_str

# ---- Fabric override definitions ----

# Transform 'fe80:0000:0000:0000:0002:1903:000e:8acd' WWN notation to
# '0xfe8000000000000000021903000e8acd'
def _srpt_wwns():
    for wwn_file in glob("/sys/class/infiniband/*/ports/*/gids/0"):
        yield "0x" + fread(wwn_file).translate(None, ":")

_ib_srpt = dict(features = ("acls",),
                kernel_module="ib_srpt",
                configfs_group="srpt",
                wwns=_srpt_wwns)

_iscsi = dict(wwn_type="iqn")

_loopback = dict(features=("nexus",),
                 wwn_type = "naa",
                 kernel_module = "tcm_loop")

def _qla_wwns():
    for wwn_file in glob("/sys/class/fc_host/host*/port_name"):
        yield _colonize(fread(wwn_file)[2:])

_qla2xxx = dict(features=("acls",),
                kernel_module="tcm_qla2xxx",
                wwns=_qla_wwns)

# We need a single unique value to create the target.
# Return the first local 1394 device's guid.
def _sbp_wwns():
    for fname in glob("/sys/bus/firewire/devices/fw*/is_local"):
        if bool(int(fread(fname))):
            guid_path = os.path.dirname(fname) + "/guid"
            yield fread(guid_path)[2:]
            break

_sbp = dict(features=(),
            kernel_module="sbp_target",
            wwns=_sbp_wwns)

# Transform '0x1234567812345678' WWN notation to '12:34:56:78:12:34:56:78'
def _fc_wwns():
    for wwn_file in glob("/sys/class/fc_host/host*/port_name"):
        yield _colonize(fread(wwn_file)[2:])

_tcm_fc = dict(features = ("acls",),
               kernel_module = "tcm_fc",
               configfs_group = "fc",
               wwns=_fc_wwns)

_usb_gadget = dict(features=("nexus",),
                   wwn_type="naa",
                   kernel_module="tcm_usb_gadget")

# ---- Putting it all together ----

# list of tuples containing fabric name and spec overrides, if any
fabrics = [
    ("ib_srpt", _ib_srpt),
    ("iscsi", _iscsi),
    ("loopback", _loopback),
    ("qla2xxxx", _qla2xxx),
    ("sbp", _sbp),
    ("tcm_fc", _tcm_fc),
    ("usb_gadget", _usb_gadget),
    ]

_default_features = ('discovery_auth', 'acls', 'acls_auth', 'nps', 'tpgts')
_default_wwn_type = 'free'

# Merge defaults and overrides to create actual specfile dict
specs = {}
for f_name, f_ovr in fabrics:
    fabric = {}
    fabric['features'] = f_ovr.get("features", _default_features)
    fabric['kernel_module'] = f_ovr.get("kernel_module", "%s_target_mod" % f_name)
    fabric['configfs_group'] = f_ovr.get("configfs_group", f_name)
    fabric['wwn_type'] = f_ovr.get("wwn_type", _default_wwn_type)
    if 'wwns' in f_ovr:
        fabric['wwn_list'] = list(f_ovr['wwns']())
    else:
        fabric['wwn_list'] = None

    specs[f_name] = fabric
<|endoftext|>


<commit_before># Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Utilities for API compatibility between TensorFlow release versions.

See [Version
Compatibility](https://tensorflow.org/guide/version_compat#backward_forward)
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import datetime
from tensorflow.python.util import tf_contextlib
from tensorflow.python.util.tf_export import tf_export

_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2018, 10, 3)

@tf_export("compat.forward_compatible")
def forward_compatible(year, month, day):
  """Return true if the forward compatibility window has expired.

  See [Version
  compatibility](https://tensorflow.org/guide/version_compat#backward_forward).

  Forward-compatibility refers to scenarios where the producer of a TensorFlow
  model (a GraphDef or SavedModel) is compiled against a version of the
  TensorFlow library newer than what the consumer was compiled against. The
  "producer" is typically a Python program that constructs and trains a model
  while the "consumer" is typically another program that loads and serves the
  model.

  TensorFlow has been supporting a 3 week forward-compatibility window for
  programs compiled from source at HEAD.

  For example, consider the case where a new operation `MyNewAwesomeAdd` is
  created with the intent of replacing the implementation of an existing Python
  wrapper - `tf.add`.  The Python wrapper implementation should change from
  something like:

  ```python
  def add(inputs, name=None):
    return gen_math_ops.add(inputs, name)
  ```

  to:

  ```python
  from tensorflow.python.compat import compat

  def add(inputs, name=None):
    if compat.forward_compatible(year, month, day):
      # Can use the awesome new implementation.
      return gen_math_ops.my_new_awesome_add(inputs, name)
    # To maintain forward compatibiltiy, use the old implementation.
    return gen_math_ops.add(inputs, name)
  ```

  Where `year`, `month`, and `day` specify the date beyond which binaries
  that consume a model are expected to have been updated to include the
  new operations. This date is typically at least 3 weeks beyond the date
  the code that adds the new operation is committed.

  Args:
    year:  A year (e.g., 2018).
    month: A month (1 <= month <= 12) in year.
    day:   A day (1 <= day <= 31, or 30, or 29, or 28) in month.

  Returns:
    True if the caller can expect that serialized TensorFlow graphs produced
    can be consumed by programs that are compiled with the TensorFlow library
    source code after (year, month, day).
  """
  return _FORWARD_COMPATIBILITY_HORIZON > datetime.date(year, month, day)

@tf_export("compat.forward_compatibility_horizon")
@tf_contextlib.contextmanager
def forward_compatibility_horizon(year, month, day):
  """Context manager for testing forward compatibility of generated graphs.

  See [Version
  compatibility](https://tensorflow.org/guide/version_compat#backward_forward).

  To ensure forward compatibility of generated graphs (see `forward_compatible`)
  with older binaries, new features can be gated with:

  ```python
  if compat.forward_compatible(year=2018, month=08, date=01):
    generate_graph_with_new_features()
  else:
    generate_graph_so_older_binaries_can_consume_it()
  ```

  However, when adding new features, one may want to unittest it before
  the forward compatibility window expires. This context manager enables
  such tests. For example:

  ```python
  from tensorflow.python.compat import compat

  def testMyNewFeature(self):
    with compat.forward_compatibility_horizon(2018, 08, 02):
       # Test that generate_graph_with_new_features() has an effect
  ```

  Args :
    year:  A year (e.g. 2018).
    month: A month (1 <= month <= 12) in year.
    day:   A day (1 <= day <= 31, or 30, or 29, or 28) in month.

  Yields:
    Nothing.
  """
  global _FORWARD_COMPATIBILITY_HORIZON
  try:
    old_compat_date = _FORWARD_COMPATIBILITY_HORIZON
    _FORWARD_COMPATIBILITY_HORIZON = datetime.date(year, month, day)
    yield
  finally:
    _FORWARD_COMPATIBILITY_HORIZON = old_compat_date
<commit_msg>compat: Update forward compatibility horizon to 2018-10-04<commit_after># Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Utilities for API compatibility between TensorFlow release versions.

See [Version
Compatibility](https://tensorflow.org/guide/version_compat#backward_forward)
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import datetime
from tensorflow.python.util import tf_contextlib
from tensorflow.python.util.tf_export import tf_export

_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2018, 10, 4)

@tf_export("compat.forward_compatible")
def forward_compatible(year, month, day):
  """Return true if the forward compatibility window has expired.

  See [Version
  compatibility](https://tensorflow.org/guide/version_compat#backward_forward).

  Forward-compatibility refers to scenarios where the producer of a TensorFlow
  model (a GraphDef or SavedModel) is compiled against a version of the
  TensorFlow library newer than what the consumer was compiled against. The
  "producer" is typically a Python program that constructs and trains a model
  while the "consumer" is typically another program that loads and serves the
  model.

  TensorFlow has been supporting a 3 week forward-compatibility window for
  programs compiled from source at HEAD.

  For example, consider the case where a new operation `MyNewAwesomeAdd` is
  created with the intent of replacing the implementation of an existing Python
  wrapper - `tf.add`.  The Python wrapper implementation should change from
  something like:

  ```python
  def add(inputs, name=None):
    return gen_math_ops.add(inputs, name)
  ```

  to:

  ```python
  from tensorflow.python.compat import compat

  def add(inputs, name=None):
    if compat.forward_compatible(year, month, day):
      # Can use the awesome new implementation.
      return gen_math_ops.my_new_awesome_add(inputs, name)
    # To maintain forward compatibiltiy, use the old implementation.
    return gen_math_ops.add(inputs, name)
  ```

  Where `year`, `month`, and `day` specify the date beyond which binaries
  that consume a model are expected to have been updated to include the
  new operations. This date is typically at least 3 weeks beyond the date
  the code that adds the new operation is committed.

  Args:
    year:  A year (e.g., 2018).
    month: A month (1 <= month <= 12) in year.
    day:   A day (1 <= day <= 31, or 30, or 29, or 28) in month.

  Returns:
    True if the caller can expect that serialized TensorFlow graphs produced
    can be consumed by programs that are compiled with the TensorFlow library
    source code after (year, month, day).
  """
  return _FORWARD_COMPATIBILITY_HORIZON > datetime.date(year, month, day)

@tf_export("compat.forward_compatibility_horizon")
@tf_contextlib.contextmanager
def forward_compatibility_horizon(year, month, day):
  """Context manager for testing forward compatibility of generated graphs.

  See [Version
  compatibility](https://tensorflow.org/guide/version_compat#backward_forward).

  To ensure forward compatibility of generated graphs (see `forward_compatible`)
  with older binaries, new features can be gated with:

  ```python
  if compat.forward_compatible(year=2018, month=08, date=01):
    generate_graph_with_new_features()
  else:
    generate_graph_so_older_binaries_can_consume_it()
  ```

  However, when adding new features, one may want to unittest it before
  the forward compatibility window expires. This context manager enables
  such tests. For example:

  ```python
  from tensorflow.python.compat import compat

  def testMyNewFeature(self):
    with compat.forward_compatibility_horizon(2018, 08, 02):
       # Test that generate_graph_with_new_features() has an effect
  ```

  Args :
    year:  A year (e.g. 2018).
    month: A month (1 <= month <= 12) in year.
    day:   A day (1 <= day <= 31, or 30, or 29, or 28) in month.

  Yields:
    Nothing.
  """
  global _FORWARD_COMPATIBILITY_HORIZON
  try:
    old_compat_date = _FORWARD_COMPATIBILITY_HORIZON
    _FORWARD_COMPATIBILITY_HORIZON = datetime.date(year, month, day)
    yield
  finally:
    _FORWARD_COMPATIBILITY_HORIZON = old_compat_date
<|endoftext|>


<commit_before># Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Utilities for API compatibility between TensorFlow release versions.

See [Version
Compatibility](https://tensorflow.org/guide/version_compat#backward_forward)
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import datetime
import os

from tensorflow.python.platform import tf_logging as logging
from tensorflow.python.util import tf_contextlib
from tensorflow.python.util.tf_export import tf_export

# This value changes every day with an automatic CL. It can be modified in code
# via `forward_compatibility_horizon()` or with the environment variable
# TF_FORWARD_COMPATIBILITY_DELTA_DAYS, which is added to the compatibility date.
_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2021, 9, 26)
_FORWARD_COMPATIBILITY_DELTA_DAYS_VAR_NAME = "TF_FORWARD_COMPATIBILITY_DELTA_DAYS"
_FORWARD_COMPATIBILITY_DATE_NUMBER = None

def _date_to_date_number(year, month, day):
  return (year << 9) | (month << 5) | day

def _update_forward_compatibility_date_number(date_to_override=None):
  """Update the base date to compare in forward_compatible function."""

  global _FORWARD_COMPATIBILITY_DATE_NUMBER

  if date_to_override:
    date = date_to_override
  else:
    date = _FORWARD_COMPATIBILITY_HORIZON
    delta_days = os.getenv(_FORWARD_COMPATIBILITY_DELTA_DAYS_VAR_NAME)
    if delta_days:
      date += datetime.timedelta(days=int(delta_days))

  if date < _FORWARD_COMPATIBILITY_HORIZON:
    logging.warning("Trying to set the forward compatibility date to the past"
                    " date %s. This will be ignored by TensorFlow." % (date))
    return
  _FORWARD_COMPATIBILITY_DATE_NUMBER = _date_to_date_number(
      date.year, date.month, date.day)

_update_forward_compatibility_date_number()

@tf_export("compat.forward_compatible")
def forward_compatible(year, month, day):
  """Return true if the forward compatibility window has expired.

  See [Version
  compatibility](https://tensorflow.org/guide/version_compat#backward_forward).

  Forward-compatibility refers to scenarios where the producer of a TensorFlow
  model (a GraphDef or SavedModel) is compiled against a version of the
  TensorFlow library newer than what the consumer was compiled against. The
  "producer" is typically a Python program that constructs and trains a model
  while the "consumer" is typically another program that loads and serves the
  model.

  TensorFlow has been supporting a 3 week forward-compatibility window for
  programs compiled from source at HEAD.

  For example, consider the case where a new operation `MyNewAwesomeAdd` is
  created with the intent of replacing the implementation of an existing Python
  wrapper - `tf.add`.  The Python wrapper implementation should change from
  something like:

  ```python
  def add(inputs, name=None):
    return gen_math_ops.add(inputs, name)
  ```

  to:

  ```python
  from tensorflow.python.compat import compat

  def add(inputs, name=None):
    if compat.forward_compatible(year, month, day):
      # Can use the awesome new implementation.
      return gen_math_ops.my_new_awesome_add(inputs, name)
    # To maintain forward compatibility, use the old implementation.
    return gen_math_ops.add(inputs, name)
  ```

  Where `year`, `month`, and `day` specify the date beyond which binaries
  that consume a model are expected to have been updated to include the
  new operations. This date is typically at least 3 weeks beyond the date
  the code that adds the new operation is committed.

  Args:
    year:  A year (e.g., 2018). Must be an `int`.
    month: A month (1 <= month <= 12) in year. Must be an `int`.
    day:   A day (1 <= day <= 31, or 30, or 29, or 28) in month. Must be an
      `int`.

  Returns:
    True if the caller can expect that serialized TensorFlow graphs produced
    can be consumed by programs that are compiled with the TensorFlow library
    source code after (year, month, day).
  """
  return _FORWARD_COMPATIBILITY_DATE_NUMBER > _date_to_date_number(
      year, month, day)

@tf_export("compat.forward_compatibility_horizon")
@tf_contextlib.contextmanager
def forward_compatibility_horizon(year, month, day):
  """Context manager for testing forward compatibility of generated graphs.

  See [Version
  compatibility](https://tensorflow.org/guide/version_compat#backward_forward).

  To ensure forward compatibility of generated graphs (see `forward_compatible`)
  with older binaries, new features can be gated with:

  ```python
  if compat.forward_compatible(year=2018, month=08, date=01):
    generate_graph_with_new_features()
  else:
    generate_graph_so_older_binaries_can_consume_it()
  ```

  However, when adding new features, one may want to unittest it before
  the forward compatibility window expires. This context manager enables
  such tests. For example:

  ```python
  from tensorflow.python.compat import compat

  def testMyNewFeature(self):
    with compat.forward_compatibility_horizon(2018, 08, 02):
       # Test that generate_graph_with_new_features() has an effect
  ```

  Args:
    year:  A year (e.g., 2018). Must be an `int`.
    month: A month (1 <= month <= 12) in year. Must be an `int`.
    day:   A day (1 <= day <= 31, or 30, or 29, or 28) in month. Must be an
      `int`.

  Yields:
    Nothing.
  """
  try:
    _update_forward_compatibility_date_number(datetime.date(year, month, day))
    yield
  finally:
    _update_forward_compatibility_date_number()
<commit_msg>compat: Update forward compatibility horizon to 2021-09-27<commit_after># Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Utilities for API compatibility between TensorFlow release versions.

See [Version
Compatibility](https://tensorflow.org/guide/version_compat#backward_forward)
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import datetime
import os

from tensorflow.python.platform import tf_logging as logging
from tensorflow.python.util import tf_contextlib
from tensorflow.python.util.tf_export import tf_export

# This value changes every day with an automatic CL. It can be modified in code
# via `forward_compatibility_horizon()` or with the environment variable
# TF_FORWARD_COMPATIBILITY_DELTA_DAYS, which is added to the compatibility date.
_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2021, 9, 27)
_FORWARD_COMPATIBILITY_DELTA_DAYS_VAR_NAME = "TF_FORWARD_COMPATIBILITY_DELTA_DAYS"
_FORWARD_COMPATIBILITY_DATE_NUMBER = None

def _date_to_date_number(year, month, day):
  return (year << 9) | (month << 5) | day

def _update_forward_compatibility_date_number(date_to_override=None):
  """Update the base date to compare in forward_compatible function."""

  global _FORWARD_COMPATIBILITY_DATE_NUMBER

  if date_to_override:
    date = date_to_override
  else:
    date = _FORWARD_COMPATIBILITY_HORIZON
    delta_days = os.getenv(_FORWARD_COMPATIBILITY_DELTA_DAYS_VAR_NAME)
    if delta_days:
      date += datetime.timedelta(days=int(delta_days))

  if date < _FORWARD_COMPATIBILITY_HORIZON:
    logging.warning("Trying to set the forward compatibility date to the past"
                    " date %s. This will be ignored by TensorFlow." % (date))
    return
  _FORWARD_COMPATIBILITY_DATE_NUMBER = _date_to_date_number(
      date.year, date.month, date.day)

_update_forward_compatibility_date_number()

@tf_export("compat.forward_compatible")
def forward_compatible(year, month, day):
  """Return true if the forward compatibility window has expired.

  See [Version
  compatibility](https://tensorflow.org/guide/version_compat#backward_forward).

  Forward-compatibility refers to scenarios where the producer of a TensorFlow
  model (a GraphDef or SavedModel) is compiled against a version of the
  TensorFlow library newer than what the consumer was compiled against. The
  "producer" is typically a Python program that constructs and trains a model
  while the "consumer" is typically another program that loads and serves the
  model.

  TensorFlow has been supporting a 3 week forward-compatibility window for
  programs compiled from source at HEAD.

  For example, consider the case where a new operation `MyNewAwesomeAdd` is
  created with the intent of replacing the implementation of an existing Python
  wrapper - `tf.add`.  The Python wrapper implementation should change from
  something like:

  ```python
  def add(inputs, name=None):
    return gen_math_ops.add(inputs, name)
  ```

  to:

  ```python
  from tensorflow.python.compat import compat

  def add(inputs, name=None):
    if compat.forward_compatible(year, month, day):
      # Can use the awesome new implementation.
      return gen_math_ops.my_new_awesome_add(inputs, name)
    # To maintain forward compatibility, use the old implementation.
    return gen_math_ops.add(inputs, name)
  ```

  Where `year`, `month`, and `day` specify the date beyond which binaries
  that consume a model are expected to have been updated to include the
  new operations. This date is typically at least 3 weeks beyond the date
  the code that adds the new operation is committed.

  Args:
    year:  A year (e.g., 2018). Must be an `int`.
    month: A month (1 <= month <= 12) in year. Must be an `int`.
    day:   A day (1 <= day <= 31, or 30, or 29, or 28) in month. Must be an
      `int`.

  Returns:
    True if the caller can expect that serialized TensorFlow graphs produced
    can be consumed by programs that are compiled with the TensorFlow library
    source code after (year, month, day).
  """
  return _FORWARD_COMPATIBILITY_DATE_NUMBER > _date_to_date_number(
      year, month, day)

@tf_export("compat.forward_compatibility_horizon")
@tf_contextlib.contextmanager
def forward_compatibility_horizon(year, month, day):
  """Context manager for testing forward compatibility of generated graphs.

  See [Version
  compatibility](https://tensorflow.org/guide/version_compat#backward_forward).

  To ensure forward compatibility of generated graphs (see `forward_compatible`)
  with older binaries, new features can be gated with:

  ```python
  if compat.forward_compatible(year=2018, month=08, date=01):
    generate_graph_with_new_features()
  else:
    generate_graph_so_older_binaries_can_consume_it()
  ```

  However, when adding new features, one may want to unittest it before
  the forward compatibility window expires. This context manager enables
  such tests. For example:

  ```python
  from tensorflow.python.compat import compat

  def testMyNewFeature(self):
    with compat.forward_compatibility_horizon(2018, 08, 02):
       # Test that generate_graph_with_new_features() has an effect
  ```

  Args:
    year:  A year (e.g., 2018). Must be an `int`.
    month: A month (1 <= month <= 12) in year. Must be an `int`.
    day:   A day (1 <= day <= 31, or 30, or 29, or 28) in month. Must be an
      `int`.

  Yields:
    Nothing.
  """
  try:
    _update_forward_compatibility_date_number(datetime.date(year, month, day))
    yield
  finally:
    _update_forward_compatibility_date_number()
<|endoftext|>