
'''
Create a layout of a paperclip spiral,
uses: delay line, waveguide loss cutback structure

- this can be executed directly, or loaded by the technology library
- you can resize the PCell by using the Partial select tool "L", and moving 
   the GUIDING SHAPE Box
- Waveguide types are loaded from the XML file
- Pitch of the waveguides is determined by the DevRec layer
- When executed directly, the unit test instantiates all the waveguide types

by Lukas Chrostowski, 2023
'''


import pya
from pya import *
from SiEPIC.utils import get_technology_by_name

class spiral_paperclip(pya.PCellDeclarationHelper):

    def __init__(self):
        # Important: initialize the super class
        self.cellName=self.__class__.__name__
        super(eval(self.cellName), self).__init__()
        
        self.technology_name = 'EBeam' 

        from SiEPIC.utils import get_technology_by_name, load_Waveguides_by_Tech
        self.TECHNOLOGY = get_technology_by_name(self.technology_name)
                        
        # Load all strip waveguides
        self.waveguide_types = load_Waveguides_by_Tech(self.technology_name)   
            
        # declare the parameters
        p = self.param("waveguide_type", self.TypeList, "Waveguide Type", default = self.waveguide_types[0]['name'])
        for wa in self.waveguide_types:
            p.add_choice(wa['name'],wa['name'])
        
        self.param("loops", self.TypeInt, "Number of loops", default = 2)
        self.minlength = 2*float(self.waveguide_types[0]['radius'])
        self.param("length", self.TypeDouble, "Inner length (min 2 x bend radius)", default = self.minlength)
        self.param("box", self.TypeShape, "Box", default = pya.DBox(-self.minlength, -self.minlength/2, self.minlength, self.minlength/2))
        self.param("length1", self.TypeDouble, "length: for PCell", default = self.minlength, hidden = True)
        self.param("flatten", self.TypeBoolean, "Flatten the PCell, for scripting", default = False)
        
    def display_text_impl(self):
        # Provide a descriptive text for the cell
        return "%s_%s_%s_%s" % (self.cellName, self.loops, self.length, self.waveguide_type.replace(' ','_'))

    def get_waveguide_parameters(self):
        '''
        Get the Waveguide Type from the PCell, and update waveguide parameters
        '''
        self.waveguide_params = [t for t in self.waveguide_types if t['name'] == self.waveguide_type]
        if not self.waveguide_params:
            raise Exception ('Waveguides.XML not correctly configured')
        self.waveguide_params = self.waveguide_params [0]
        wg_params = self.waveguide_params
        wg_params2 = wg_params
        
        # check for compound type
        if 'compound_waveguide' in wg_params:
            sm_wg = self.waveguide_params['compound_waveguide']['singlemode']
            mm_wg = self.waveguide_params['compound_waveguide']['multimode']
            wg_params = [t for t in self.waveguide_types if t['name'] == sm_wg][0]
            wg_params2 = [t for t in self.waveguide_types if t['name'] == mm_wg][0]

        # DevRec width
        if ('DevRec' not in [wg['layer'] for wg in wg_params2['component']]):
            from SiEPIC import _globals
            self.devrec = max([float(wg['width']) for wg in wg_params2['component']]) + _globals.WG_DEVREC_SPACE * 2
        else:
            self.devrec = float([f for f in wg_params2['component'] if f['layer']=='DevRec'][0]['width'])

        # Radius
        self.radius = float(wg_params['radius'])

        # Waveguide pin width
        self.wg_width = float(wg_params['width'])

    def coerce_parameters_impl(self):
        self.get_waveguide_parameters()
        
        # We employ coerce_parameters_impl to decide whether the handle or the 
        # numeric parameters have changed. We update the 
        # numerical value or the shape, depending on which on has not changed.
        w = None
        if isinstance(self.box, pya.Box): 
            w = self.box.right*self.layout.dbu
            # print('%s Box: width %s ' %(self.cellName,w))
        if isinstance(self.box, pya.DBox): 
            w = self.box.right
            # print('%s DBox: width %s ' %(self.cellName,w))
        if w != None and abs(self.length1-self.length) < 1e-6:
            if w < self.minlength:
                w = self.minlength
            # print('%s update from GUI Box: %s ' %(self.cellName,w))
            self.length = w
            self.length1 = w
        else:
            # print('%s update from PCell parameters: %s ' %(self.cellName,self.length1))
            self.length1 = self.length
        self.box = pya.DBox(-self.length,-self.minlength/2,self.length,self.minlength/2)
                    

    def can_create_from_shape_impl(self):
        return self.shape.is_box()

    def transformation_from_shape_impl(self):
        # Implement the "Create PCell from shape" protocol: we use the center of the shape's
        # bounding box to determine the transformation
        return pya.Trans(self.shape.bbox().center())

    def parameters_from_shape_impl(self):
        # self.path = self.shape.path
        # Implement the "Create PCell from shape" protocol: we set r and l from the shape's 
        # bounding box width and layer
        self.width = self.shape.bbox().width() * self.layout.dbu
        self.height = self.shape.bbox().height() * self.layout.dbu
    
    def produce_impl(self):
    
        self.get_waveguide_parameters()
        devrec=self.devrec
        radius = self.radius

        # SBend offset in the middle of the spiral
        if 'sbends' in self.waveguide_params:
            # Use the Bezier S-Bend (more space efficient)
            # if sbends=True in Waveguide.xml
            offset = radius - devrec/2  
            extra = 0
        else:
            # Use 2 x 90 degree bends
            offset = radius
            extra = devrec
    
        # Ensure the length is sufficient
        self.minlength = 2*radius
        length0 = max(self.minlength, self.length)
        
        # min loops
        self.loops = max(self.loops, 1)
    
        # spiral points    
        points = [DPoint(-length0,offset), DPoint(0.0,offset), DPoint(0.0,-offset), DPoint(length0,-offset)]
        for i in range(1,self.loops*2,2):
            points.insert(0, DPoint(-length0-devrec*(i-1),offset-radius*2-devrec*(i-1)-extra))
            points.insert(0, DPoint(length0+devrec*i,offset-radius*2-devrec*(i-1)-extra))
            points.insert(0, DPoint(length0+devrec*i,-offset+radius*2+devrec*i+extra))
            points.insert(0, DPoint(-length0-devrec*(i+1),-offset+radius*2+devrec*i+extra))
            points.append(DPoint(length0+devrec*(i-1),radius*2-offset+devrec*(i-1)+extra))
            points.append(DPoint(-length0-devrec*i,radius*2-offset+devrec*(i-1)+extra))
            points.append(DPoint(-length0-devrec*i,-radius*2+offset-devrec*i-extra))
            points.append(DPoint(length0+devrec*(i+1),-radius*2+offset-devrec*i-extra))
        points.append(DPoint(length0+devrec*(i+1),radius*2-offset+devrec*(i+1)+extra))
        points.append(DPoint(-length0-devrec*(i+1),radius*2-offset+devrec*(i+1)+extra))
        points.pop(0)
        points.insert(0, DPoint(-length0-devrec*(i+1),-offset+radius*2+devrec*i+extra))

        # Create a path and waveguide    
        path  = DPath(points, 0.5)
        self.layout.technology_name = self.technology_name # required otherwise "create_cell" doesn't load
        pcell = self.layout.create_cell("Waveguide", self.technology_name, 
                    {"path": path,
                     "waveguide_type": self.waveguide_type})
        t = Trans(Trans.R0, 0, 0)
        self.cell.insert(CellInstArray(pcell.cell_index(), t))

        # If the waveguide is a Compound type:
        # Flatten the PCell and remove the DevRec and PinRec layers
        if 'compound_waveguide' in self.waveguide_params and self.flatten:
            self.cell.flatten(True)
            LayerPinRecN = self.layout.layer(self.TECHNOLOGY['PinRec'])
            LayerDevRecN = self.layout.layer(self.TECHNOLOGY['DevRec']) 
            self.cell.clear(LayerPinRecN)
            bbox = self.cell.bbox()
            self.cell.clear(LayerDevRecN)     
            self.cell.clear(self.layout.layer(self.TECHNOLOGY['Waveguide']))
            self.cell.shapes(LayerDevRecN).insert(bbox)
    
            # Create the pins on the input & output waveguides
            from SiEPIC.utils.layout import make_pin
            make_pin(self.cell, "optA", [-length0-devrec*(i+1),radius*2-offset+devrec*(i+1)+extra], self.wg_width, LayerPinRecN, 180)
            make_pin(self.cell, "optB", [-length0-devrec*(i+1),-offset+radius*2+devrec*i+extra], self.wg_width, LayerPinRecN, 180)


# Save the path, used for loading WAVEGUIDES.XML
import os
filepath = os.path.dirname(os.path.realpath(__file__))


class test_lib(Library):
  def __init__(self):  
    tech = 'EBeam'
    library = tech + 'test_lib'
    self.technology=tech
    self.layout().register_pcell('spiral_paperclip', spiral_paperclip())
    self.register(library)
 
if __name__ == "__main__":
    print('Test layout for: Spiral Paperclip')
    
    from SiEPIC.utils.layout import new_layout, floorplan
    from SiEPIC.scripts import zoom_out

    # load the test library, and technology
    t = test_lib()
    tech = t.technology

    # Create a new layout for the chip floor plan   
    topcell, ly = new_layout(tech, "test", GUI = True, overwrite = True)
    #floorplan(topcell, 100e3, 100e3)
        
    # instantiate the cell
    library = tech + 'test_lib'

    if 0:
        # default waveguide
        pcell = ly.create_cell("spiral_paperclip", library, {})
        t = Trans(Trans.R0, 0, 0)
        topcell.insert(CellInstArray(pcell.cell_index(), t))
    
        # multimode
        pcell = ly.create_cell("spiral_paperclip", library, {
                'waveguide_type':'Si routing TE 1550 nm (compound waveguide)',
                'length':100,
                'loops':1})
        t = Trans(Trans.R0, 0, 40000)
        topcell.insert(CellInstArray(pcell.cell_index(), t))

    # Create spirals for all the types of waveguides
    from SiEPIC.utils import load_Waveguides_by_Tech
    waveguide_types = load_Waveguides_by_Tech(tech)   
    y = 0
    for wg in waveguide_types:
        pcell = ly.create_cell("spiral_paperclip", library, {
                'waveguide_type':wg['name'],
                'length':100,
                'loops':1})
        t = Trans(Trans.R0, 0, y - pcell.bbox().bottom)
        inst = topcell.insert(CellInstArray(pcell.cell_index(), t))
        y += pcell.bbox().height()+2000
    
    zoom_out(topcell)
