def produce_impl(self): # Creates the patterns # Convert parameters from [um] to [dbu] or database units d = self.specDiameter[self.diameter] / self.layout.dbu * 1000 pFlat = self.specPrimaryFlat[self.diameter] / self.layout.dbu * 1000 sFlat = self.specSecondaryFlat[self.diameter] / self.layout.dbu * 1000 aFlat = self.specSecondaryFlatAngle[self.secondaryFlatAngle] b = self.border / self.layout.dbu * 1000 dIn = self.dIn / self.layout.dbu * 1000 dOut = self.dOut / self.layout.dbu * 1000 # Create the wafer shape s = shape() region = s.siWafer(d, pFlat, sFlat, aFlat, 128) if (self.doubleFlat): regionTmp = region.dup() regionTmp.transform(pya.ICplxTrans(1, 180, False, 0, 0)) region = region & regionTmp if (self.ring): scaleIn = (d - dIn) / d scaleOut = (d + dOut) / d regionOut = region.dup() regionOut.transform(pya.ICplxTrans(scaleOut, 0, False, 0, 0)) regionIn = region.dup() regionIn.transform(pya.ICplxTrans(scaleIn, 0, False, 0, 0)) region = regionOut - regionIn if (self.invert): region = s.invert(region, b) self.cell.shapes(self.layer_layer).insert(region)
def vernier(self, width, length, pitch): ''' vernier(width, length, pitch) Generates a vernier scale Parameters --------- width : integer The width of each tick marker length : integer The length of the central tick mark The major tick marks are 3/4 this length The minor tick marks are half this length pitch : integer The distance between each tick mark Returns ------ region : [pya.Region] A region containing the vernier scale Description --------- A pair of vernier scale can be used to measure misalignment by eye. In photolithography the wafer will contain one vernier pattern (width = 4, length = 40, pitch = 8, units = micron) and the mask will contain a second vernier pattern (pitch = 8.2) providing an alignment measurement resolution of 0.2 micron. ''' scaleM = 0.75 scaleS = 0.5 # Create the large tick mark polygons = [] #tick = pya.Polygon([pya.Point(0,0), pya.Point(length,0), pya.Point(length,width), pya.Point(0,width)]) tick = pya.Polygon([pya.Point(0,0), pya.Point(0,width), pya.Point(length,width), pya.Point(length,0)]) tc = pya.ICplxTrans(int(-length/2),int(-width/2)) polygons.append(tc.trans(tick)) # Create the medium tick mark #tickm = pya.Polygon([pya.Point(0,0), pya.Point(length*scaleM,0), pya.Point(length*scaleM,width), pya.Point(0,width)]) tickm = pya.Polygon([pya.Point(0,0), pya.Point(0,width), pya.Point(length*scaleM,width), pya.Point(length*scaleM,0)]) pos = [-2, -1, 1, 2] for i in pos: tt = pya.ICplxTrans(0,int(i*pitch*5)) polygons.append(tc.trans(tt.trans(tickm))) # Create the small tick mark #ticks = pya.Polygon([pya.Point(0,0), pya.Point(length*scaleS,0), pya.Point(length*scaleS,width), pya.Point(0,width)]) ticks = pya.Polygon([pya.Point(0,0), pya.Point(0,width), pya.Point(length*scaleS,width), pya.Point(length*scaleS,0)]) pos = [-9, -8, -7, -6, -4, -3, -2, -1, 1, 2, 3, 4, 6, 7, 8, 9] for i in pos: tt = pya.ICplxTrans(0,int(i*pitch)) polygons.append(tc.trans(tt.trans(ticks))) return pya.Region(polygons)
def pieceHolderCassette(self, dbu=1): ''' pieceHolderCassette() Generates the shape of the Jeol JBX-5500FS piece holder cassette Parameters --------- dbu : double The database unit Returns ------ region : [pya.Region] A region containing the piece holder cassette shape Description ------ The center of this piece holder shape (0,0) is at stage position (62.5mm, 37.5mm) Jeol Stage Y axis is reverse of KLayout Y axis ''' # Create the quarter circle r = 36100 / dbu rx = 62500 / dbu ry = -37500 / dbu polygon = pya.Polygon([ pya.Point(-r, -r), pya.Point(-r, r), pya.Point(r, r), pya.Point(r, -r) ]) polygon = polygon.round_corners(0, r, 128) rectangle = pya.Polygon([ pya.Point(-r, 0), pya.Point(-r, r), pya.Point(r, r), pya.Point(r, 0) ]) tt = pya.ICplxTrans(1, 90, False, 0, 0) qCircle = pya.Region(polygon) - pya.Region(rectangle) - pya.Region( rectangle.transform(tt)) # Create the trapezoid trapezoid = pya.Polygon([ pya.Point(49500 / dbu, -70500 / dbu), pya.Point(40500 / dbu, -20500 / dbu), pya.Point(60500 / dbu, -20500 / dbu), pya.Point(51500 / dbu, -70500 / dbu) ]) tt = pya.ICplxTrans(-rx, -ry) cassette = qCircle + pya.Region(trapezoid.transform(tt)) return cassette
def checkerboard(self, width, num=5): ''' checkerboard(width, num) Generates a checkerboard pattern Parameters --------- width : integer The width of each square num : integer (5) The number of squares Returns ------ region : pya.Region A region containing the checkerboard Description --------- A checkerboard pattern is used to qualitatively evaluate the resolution of the print. The corners of the checkboard pattern will degrade as resolution gets worse ''' # Create a box square = pya.Polygon([ pya.Point(0, 0), pya.Point(0, width), pya.Point(width, width), pya.Point(width, 0) ]) polygons = [] tc = pya.ICplxTrans(-int(num * width / 2), -int(num * width / 2)) if (num % 2 == 1): for i in range(num * num): if (i % 2 == 0): tt = pya.ICplxTrans(int(i % num * width), int(i / num * width)) polygons.append(tc.trans(tt.trans(square))) else: for i in range(num): for j in range(num): if ((j + i) % 2 == 0): tt = pya.ICplxTrans(int(i * width), int(j * width)) polygons.append(tc.trans(tt.trans(square))) return pya.Region(polygons)
def test_4_Trans(self): a = pya.Trans() m = pya.CplxTrans(a, 1.1) da = pya.DTrans() dm = pya.DCplxTrans(da, 1.1) self.assertEqual(str(m), "r0 *1.1 0,0") self.assertEqual(str(pya.DCplxTrans.from_s(str(m))), str(m)) self.assertEqual(str(m.trans(pya.Point(5, -7))), "5.5,-7.7") im = pya.ICplxTrans(a, 0.5) im_old = im.dup() self.assertEqual(str(im), "r0 *0.5 0,0") self.assertEqual(str(pya.ICplxTrans.from_s(str(im))), str(im)) self.assertEqual(str(im.trans(pya.Point(5, -7))), "3,-4") im = pya.ICplxTrans(m) self.assertEqual(str(im), "r0 *1.1 0,0") self.assertEqual(str(im.trans(pya.Point(5, -7))), "6,-8") im = pya.ICplxTrans(dm) self.assertEqual(str(im), "r0 *1.1 0,0") self.assertEqual(str(im.trans(pya.Point(5, -7))), "6,-8") im.assign(im_old) self.assertEqual(str(im), "r0 *0.5 0,0") self.assertEqual(str(im.trans(pya.Point(5, -7))), "3,-4") self.assertEqual(str(pya.ICplxTrans(5, -7)), "r0 *1 5,-7") self.assertEqual(str(pya.ICplxTrans(pya.ICplxTrans.R180, 1.5, 5, -7)), "r180 *1.5 5,-7") self.assertEqual( str(pya.ICplxTrans(pya.ICplxTrans.R180, 1.5, pya.Point(5, -7))), "r180 *1.5 5,-7") self.assertEqual( str(pya.ICplxTrans(pya.ICplxTrans.R180, 1.5, pya.Vector(5, -7))), "r180 *1.5 5,-7") self.assertEqual( str(pya.ICplxTrans(pya.ICplxTrans.R180, 1.5, pya.DVector(5, -7))), "r180 *1.5 5,-7") self.assertEqual(str(pya.ICplxTrans(pya.ICplxTrans.R180, 1.5)), "r180 *1.5 0,0") c = pya.ICplxTrans.from_dtrans(pya.DCplxTrans.M135) self.assertEqual(str(c), "m135 *1 0,0") c = pya.ICplxTrans.from_trans(pya.CplxTrans.M135) self.assertEqual(str(c), "m135 *1 0,0")
def siWafer(self, diameter, primaryFlat, secondaryFlat, angle, vertices = 128): ''' siWafer(diameter, secondaryFlatAngle) Generates a Silicon Wafer shape Parameters --------- diameter : integer The diameter of a standard silicon wafer primaryFlat : integer The length of the primary flat secondaryFlat : integer The length of the secondary flat angle : double The location of the secondary flat relative (counterclockwise) to primary flat vertices : integer (coerce to even number) The number of vertices used to generate the circle Returns ------ region : [pya.Region] A region containing the Si Wafer shape Description --------- SEMI Wafer Flat M1-0302 Specification Wafer Size = [2", 3", 100mm, 125mm, 150mm, 200mm, 300mm] Diameter [mm] = [50.8, 76.2, 100, 125, 150, 200, 300] Thickness [um] = [279, 381, 525 or 625, 625, 675 or 625, 725, 775] Primary Flat Length = [15.88, 22.22, 32.5, 42.5, 57.5, Notch, Notch] Secondary Flat Length = [8, 11.18, 18, 27.5, 37.5, NA, NA] ''' dList = [50800, 76200, 10000, 12500, 15000] pFlatLengthList = [15.88, 22.22, 32.5, 42.5, 57.5] sFlatLengthList = [8, 11.18, 18, 27.5, 37.5] r = int(diameter/2) #Height of arc position (https://mathworld.wolfram.com/CircularSegment.html) pH = r- int(np.sqrt(4*np.power(r,2)-np.power(primaryFlat,2))/2) sH = r - int(np.sqrt(4*np.power(r,2)-np.power(secondaryFlat,2))/2) # Create a circle polygon = pya.Polygon([pya.Point(-r,-r), pya.Point(-r,r), pya.Point(r,r), pya.Point(r,-r)]) polygon = polygon.round_corners(0,r,vertices) #Create a rectangle to produce the primary flat pRectangle = pya.Polygon([pya.Point(-r,r-pH), pya.Point(-r,r+pH), pya.Point(r,r+pH), pya.Point(r,r-pH)]) #Create a rectangle to produce the secondary flat sRectangle = pya.Polygon([pya.Point(-r,r-sH), pya.Point(-r,r+sH), pya.Point(r,r+sH), pya.Point(r,r-sH)]) tt = pya.ICplxTrans(1, angle, False, 0, 0) return pya.Region(polygon)-pya.Region(pRectangle)-pya.Region(sRectangle.transform(tt))
def move_instance(self, ind: int, trans: 'pya.ICplxTrans', mirror: bool = False): """Moves an InstanceHolder object :param ind: id of the InstanceHolder :param trans: list of transformations :param mirror: bool whether to mirror the object """ trans = pya.ICplxTrans(1, trans[2], mirror, trans[0], trans[1]) self.add_transformation(trans, ind)
def create(lx, ly, ang, p, dc): """Create a square grating. lx: length in x-direction ly: length in y-direction ang: rotation angle (degrees) p: period of grating dc: duty cycle of grating """ reg = pya.Region() reg.insert(pya.Box(-lx / 2, -ly / 2, lx / 2, ly / 2)) gbar = pya.Region() greps = int((lx) // p) for k in range(greps): gbar.insert(pya.Box(p * (k + dc / 2), -ly, p * (k + (1 - dc / 2)), ly)) gbar.insert( pya.Box(p * (-k - dc / 2), -ly, p * (-k - (1 - dc / 2)), ly)) gbar.transform(pya.ICplxTrans(1, ang, False, 0, 0)) reg.__iand__(gbar) return reg
def calculate_ports(self, instances: list): """Calculates port locations in the cell layout. This is to propagate the port locations upwards :param instances: list containing :class:`kppc.photonics.InstanceHolder` """ porttrans = [] if self.transformations != '': # Check child cells ports. If there are ports which are not populated by other ports, add them as own ports trans = [ pya.ICplxTrans.from_s(i) for i in self.transformations.split(';') ] for i, inst in enumerate(instances): if not inst.params_mod[0]: continue ports = [ pya.ICplxTrans.from_s(k.split(':')[0]) for k in inst.params_mod[0].split(';') ] lengths = [ k.split(':')[1] for k in inst.params_mod[0].split(';') ] for q, r in zip(ports, lengths): porttrans.append([trans[i] * q, r]) if self.portlist != '': # Add manually created Ports porttrans.extend( [[pya.ICplxTrans.from_s(p.split(':')[0]), p.split(':')[1]] for p in self.portlist.split(';')]) porttrans = sorted(porttrans, key=lambda x: (x[0].disp.x, x[0].disp.y)) # Add the calculated ports as own ports self.portlist = '' M180 = pya.ICplxTrans(1, 180, True, 0, 0) for p in porttrans: if not ([p[0] * pya.ICplxTrans.R180, p[1]] in porttrans or [p[0] * M180, p[1]] in porttrans): if self.portlist != '': self.portlist += ';' self.portlist += str(p[0]) + ':' + p[1]
def produce_impl(self): # Gets the reference to the cell view cv = pya.CellView().active() #Gets the reference to the layout layout = cv.layout() #Gets the databause unit dbu = self.layout.dbu # Create a temporary cell cell = layout.create_cell("tmp_cell") # Copy the target cell to the temporary cell cell.copy_tree(layout.cell(self.cellName)) # Flatten the temporary cell cell.flatten(True) # Retrieve all shapes from the temporary cell shapes = cell.shapes(layout.layer(self.layer)) # Convert parameters from [um] to [dbu] or database units px = int(self.pitchX / dbu) py = int(self.pitchY / dbu) # Create the dose series for i in range(self.nX): for j in range(self.nY): # Create a new datatype for each dose layer = self.layout.layer(int(self.layer.layer), i * self.nY + j) # Create a position for each dose tt = pya.ICplxTrans(px * j, py * i) # Insert the shapes into the PCell with the new layer and position self.cell.shapes(layer).insert(shapes, tt) # Delete the temporary cell cell.delete() # Make the newly added layers visible pya.LayoutView.current().add_missing_layers()
def DrawText_LiftOff(self, cell, layer1, teststr, DCplxTrans1): ''' 左下角坐标,每个字宽0.6*倍数高0.7*倍数线宽0.1*倍数 tr=pya.DCplxTrans(10,0,False,0,0) 倍数,逆时针度数,是否绕x翻转,平移x,平移y ''' reverse = False cellname = "TEXT(\"%s\")" % (teststr[:30].replace('\n', 'N')) charset = self.charset tr = pya.CplxTrans.from_dtrans(DCplxTrans1) filename = self.charfile layout = pya.Layout() layout.read(filename) cellList = [ii for ii in layout.top_cells()] layerList = self.charLayerList _layerlist = [] for ii in layerList: _layerlist.append(layout.find_layer(ii[0], ii[1])) layers = [ index for index in layout.layer_indices() if index in _layerlist ] fullregion = pya.Region() for layer_ in layers: fullregion.insert(cellList[0].begin_shapes_rec(layer_)) fullregion.merge() charshapes = [] for ii in range(len(charset)): subregion = pya.Region(pya.Box(ii * 600, 0, ii * 600 + 500, 700)) if not reverse: subregion = subregion & fullregion else: subregion = subregion - fullregion subregion.merge() subregion.transform(pya.ICplxTrans(1, 0, False, -ii * 600, 0)) charshapes.append(subregion) # cell.shapes(layer1).insert(subregion) pass ncell = IO.layout.create_cell(cellname) cell.insert(pya.CellInstArray(ncell.cell_index(), tr)) currentx = 0 currenty = 0 for cc in teststr.upper(): if cc == '\n': currenty += 1 currentx = 0 continue if cc in charset: ii = charset.index(cc) ncell.shapes(layer1).insert(charshapes[ii].transformed( pya.ICplxTrans(1, 0, False, currentx * 600, -currenty * 800))) currentx += 1 continue if cc in '\r\t\b\f': continue raise RuntimeError(f'"{cc}" is not supported')
def rotate(theta=360 / n_sides): return pya.ICplxTrans(1, theta, False, 0, 0)
def cross(self, width, length, crossType=0): ''' cross(width, length) Generates a cross Parameters --------- width : integer The width of each leg of the cross length : integer The length of each leg of the cross crossType : integer The type of cross 0 : A regular cross 1 : A dashed cross with center 2 : A dashed cross without center Returns ------ region : pya.Region A region containing the cross Description --------- A cross is a common pattern used for alignment. In photolithography a cross (width = 10, length = 100, unit = micron) can be used for coarse alignment. In ebeam lithography a cross (width = 3, length = 1000, unit = micron) can be used for global/coarse alignment and a cross (width = 3, length = 60, unit = micron) can be used for local/fine alignment. Typically, the cross pattern is made of 100 to 200 nm of dense material such Cr(10nm)/Au, Ti(10nm)/W, Ti(10nm)/Pt to provide ebeam contrast from the substrate. ''' wh = int(width / 2) lh = int(length / 2) #Create a cross if (crossType == 0): #Create a rectangle with length along X xBox = pya.Box(-wh, -lh, wh, lh) #Create a rectangle with length along y yBox = pya.Box(-lh, -wh, lh, wh) #Combine the two rectangles together region = pya.Region(xBox) + pya.Region(yBox) #Merge the region region.merge() #Create a dashed cross with center elif (crossType == 1): #Define the position of the segments pos = [-4, -2, 2, 4] #Calculate segment dimension s = int((lh - wh) / 4) sh = int(s / 2) #Create segments xBox = pya.Box(-sh, -wh, sh, wh) yBox = pya.Box(-wh, -sh, wh, sh) #Define a region with a center cross region = pya.Region(pya.Box(-wh, -wh, wh, wh)) #Calculate an offset due to center dw = sh - wh for i in pos: #Determine translations for negative positions if (i < 0): tx = pya.ICplxTrans(int(i * s + dw), 0) ty = pya.ICplxTrans(0, int(i * s + dw)) else: #Determine translations for positive positions tx = pya.ICplxTrans(int(i * s - dw), 0) ty = pya.ICplxTrans(0, int(i * s - dw)) #Insert the segments into the region region.insert(tx.trans(xBox)) region.insert(ty.trans(yBox)) #Create a dashed cross without a center elif (crossType == 2): #Define the position of the segments pos = [-3, -1, 1, 3] #Calculate the segment dimension s = int((lh - wh) / 4) sh = int(s / 2) #Create segments xBox = pya.Box(-sh, -wh, sh, wh) yBox = pya.Box(-wh, -sh, wh, sh) #Calculate an offset due to center dw = sh - wh #Define a region with nothing region = pya.Region() for i in pos: if (i < 0): #Determine translations for negative positions tx = pya.ICplxTrans(int(i * s + dw), 0) ty = pya.ICplxTrans(0, int(i * s + dw)) else: #Determine the translations for positive positions tx = pya.ICplxTrans(int(i * s - dw), 0) ty = pya.ICplxTrans(0, int(i * s - dw)) #Insert the segments into the region region.insert(tx.trans(xBox)) region.insert(ty.trans(yBox)) return region
def testKLayout(): #This performs a simple test of the class and draws the result on the active layout in KLayout #Gets a reference to the active cell view cv = pya.CellView().active() #Gets a reference to the active layout layout = cv.layout() #Gets a reference or creates layer 1/0 layer = layout.layer(1, 0) #Creates a new cell called TestShape if (layout.cell('TestShape') == None): cell = layout.create_cell('TestShape') #Clears the cell TestShape if it already exists else: cell = layout.cell('TestShape') cell.clear() #Gets the database units dbu = layout.dbu #Creates a shape object a = shape() #Creates a cross and insert it into the test cell region = a.cross(10 / dbu, 100 / dbu) cell.shapes(layer).insert(region) #Creates an inverted cross and insert it into the test cell region = a.cross(14 / dbu, 104 / dbu) region = a.invert(region, 10 / dbu) cell.shapes(layer).insert(region) #Creates a dashed cross tt = pya.ICplxTrans(0, -200000) region = a.cross(10 / dbu, 50 / dbu, 1) cell.shapes(layer).insert(region, tt) region = a.cross(10 / dbu, 50 / dbu, 2) cell.shapes(layer).insert(region, tt) tt = pya.ICplxTrans(-100000, -200000) region = a.cross(10 / dbu, 100 / dbu, 1) cell.shapes(layer).insert(region, tt) region = a.cross(10 / dbu, 100 / dbu, 2) cell.shapes(layer).insert(region, tt) #Creates a vernier pattern and inserts it into the test cell tt = pya.ICplxTrans(-100000, 0) region = a.vernier(4 / dbu, 40 / dbu, 8.2 / dbu) region.transform(tt) cell.shapes(layer).insert(region) #Creates an invert vernier pattern and inserts it into the test cell tt = pya.Trans(2, False, -160000, 0) region = a.vernier(4 / dbu, 40 / dbu, 8 / dbu) region = a.invert(region, 10 / dbu) cell.shapes(layer).insert(region, tt) tt = pya.ICplxTrans(0, 80000) region = a.text("0123456789 Hello") cell.shapes(layer).insert(region, tt) tt = pya.ICplxTrans(0, 100000) region = a.text("0123456789") region = a.invert(region, 10 / dbu) cell.shapes(layer).insert(region, tt) #Create checkboard patterns tt = pya.ICplxTrans(-100000, 150000) region = a.checkerboard(10 / dbu, 4) cell.shapes(layer).insert(region, tt) tt = pya.ICplxTrans(0, 150000) region = a.checkerboard(10 / dbu, 5) cell.shapes(layer).insert(region, tt) #Create Si Wafer patterns tt = pya.ICplxTrans(-100000, 200000) region = a.siWafer(50.8 / dbu, 15.88 / dbu, 8 / dbu, 90, 128) cell.shapes(layer).insert(region, tt) tt = pya.ICplxTrans(0, 200000) region = a.siWafer(50.8 / dbu, 15.88 / dbu, 8 / dbu, 45, 128) cell.shapes(layer).insert(region, tt) #Create cassette patterns tt = pya.ICplxTrans(100000, 0) region = a.pieceHolderCassette() cell.shapes(layer).insert(region, tt) #Create circle patterns tt = pya.ICplxTrans(-100000, 250000) region = a.circle(50 / dbu, 16) cell.shapes(layer).insert(region, tt) #Create ring patterns tt = pya.ICplxTrans(0, 250000) region = a.ring(50 / dbu, 25 / dbu, 32) cell.shapes(layer).insert(region, tt) tt = pya.ICplxTrans(100000, 250000) region = a.ring(50 / dbu, 25 / dbu, 32, False) cell.shapes(layer).insert(region, tt) cv.cell = cell
def calculate_ports(self, instances: list): """Calculates port locations in the cell layout. This is to propagate the port locations upwards :param instances: list containing :class:`kppc.photonics.InstanceHolder` """ porttrans = [] if self.transformations != '': # Check child cells ports. If there are ports which are not populated by other ports, add them as own ports trans = [ pya.ICplxTrans.from_s(i) for i in self.transformations.split(';') ] for i, inst in enumerate(instances): if not inst.params_mod[0]: continue ports = [ pya.ICplxTrans.from_s(k.split(':')[0]) for k in inst.params_mod[0].split(';') ] lengths = [ k.split(':')[1] for k in inst.params_mod[0].split(';') ] names = [ k.split(':')[2] for k in inst.params_mod[0].split(';') ] for q, w, n in zip(ports, lengths, names): porttrans.append([trans[i] * q, w, n]) if self.portlist != '': # Add manually created Ports porttrans.extend([[ pya.ICplxTrans.from_s(p.split(':')[0]), p.split(':')[1], p.split(':')[2] ] for p in self.portlist.split(';')]) porttrans = sorted(porttrans, key=lambda x: (x[0].disp.x, x[0].disp.y)) # Add the calculated ports as own ports self.portlist = '' M180 = pya.ICplxTrans(1, 180, True, 0, 0) nn, ne, ns, nw = 0, 0, 0, 0 def regrep(match, nn, ne, ns, nw): if match.group(1) == 'N': ret = f'{match.group(1)}{nn}' nn += 1 return ret if match.group(1) == 'E': ret = f'{match.group(1)}{ne}' ne += 1 return ret if match.group(1) == 'S': ret = f'{match.group(1)}{ns}' ns += 1 return ret if match.group(1) == 'W': ret = f'{match.group(1)}{nw}' nw += 1 return ret for p in porttrans: if not ([p[0] * pya.ICplxTrans.R180, p[1]] in porttrans or [p[0] * M180, p[1]] in porttrans): if self.portlist != '': self.portlist += ';' rawname = p[2] regex = r"([NSEW])(?:\d+|{n})$" name = re.sub(regex, lambda x: regrep(x, nn, ne, ns, nw), rawname) if name == rawname: if 'N{n}' in rawname: name = rawname.replace('{n}', str(nn)) nn += 1 elif 'E{n}' in rawname: name = rawname.replace('{n}', str(ne)) ne += 1 elif 'W{n}' in rawname: name = rawname.replace('{n}', str(nw)) nw += 1 elif 'S{n}' in rawname: name = rawname.replace('{n}', str(ns)) ns += 1 self.portlist += str(p[0]) + ':' + p[1] + ':' + name
def test_3_DTrans(self): c = pya.DCplxTrans(5.0, -7.0) self.assertEqual(str(c), "r0 *1 5,-7") c = pya.DCplxTrans(pya.DCplxTrans.M135) self.assertEqual(str(c), "m135 *1 0,0") self.assertEqual(c.is_unity(), False) self.assertEqual(c.is_ortho(), True) self.assertEqual(c.is_mag(), False) self.assertEqual(c.is_mirror(), True) self.assertEqual(c.rot(), pya.DCplxTrans.M135.rot()) self.assertEqual(str(c.s_trans()), "m135 0,0") self.assertAlmostEqual(c.angle, 270) self.assertEqual(str(c.trans(pya.DEdge(0, 1, 2, 3))), "(-3,-2;-1,0)") self.assertEqual(str((c * pya.DEdge(0, 1, 2, 3))), "(-3,-2;-1,0)") self.assertEqual(str(c.trans(pya.DBox(0, 1, 2, 3))), "(-3,-2;-1,0)") self.assertEqual(str((c * pya.DBox(0, 1, 2, 3))), "(-3,-2;-1,0)") self.assertEqual(str(c.trans(pya.DText("text", pya.DVector(0, 1)))), "('text',m135 -1,0)") self.assertEqual(str((c * pya.DText("text", pya.DVector(0, 1)))), "('text',m135 -1,0)") self.assertEqual( str( c.trans( pya.DPolygon([ pya.DPoint(0, 1), pya.DPoint(2, -3), pya.DPoint(4, 5) ]))), "(-5,-4;-1,0;3,-2)") self.assertEqual( str((c * pya.DPolygon( [pya.DPoint(0, 1), pya.DPoint(2, -3), pya.DPoint(4, 5)]))), "(-5,-4;-1,0;3,-2)") self.assertEqual( str(c.trans(pya.DPath( [pya.DPoint(0, 1), pya.DPoint(2, 3)], 10))), "(-1,0;-3,-2) w=10 bx=0 ex=0 r=false") self.assertEqual( str((c * pya.DPath( [pya.DPoint(0, 1), pya.DPoint(2, 3)], 10))), "(-1,0;-3,-2) w=10 bx=0 ex=0 r=false") c = pya.DCplxTrans.from_itrans(pya.CplxTrans.M135) self.assertEqual(str(c), "m135 *1 0,0") c = pya.DCplxTrans(1.5) self.assertEqual(str(c), "r0 *1.5 0,0") self.assertEqual(c.is_unity(), False) self.assertEqual(c.is_ortho(), True) self.assertEqual(c.is_mag(), True) self.assertEqual(c.is_mirror(), False) self.assertEqual(c.rot(), pya.DCplxTrans.R0.rot()) self.assertEqual(str(c.s_trans()), "r0 0,0") self.assertAlmostEqual(c.angle, 0) c = pya.DCplxTrans(0.75, 45, True, 2.5, -12.5) self.assertEqual(str(c), "m22.5 *0.75 2.5,-12.5") c = pya.DCplxTrans(0.75, 45, True, pya.DPoint(2.5, -12.5)) self.assertEqual(str(c), "m22.5 *0.75 2.5,-12.5") self.assertEqual(c.is_unity(), False) self.assertEqual(c.is_ortho(), False) self.assertEqual(c.is_mag(), True) self.assertEqual(c.rot(), pya.DCplxTrans.M0.rot()) self.assertEqual(str(c.s_trans()), "m0 2.5,-12.5") self.assertAlmostEqual(c.angle, 45) self.assertEqual(str(c.ctrans(5)), "3.75") self.assertEqual(str(c.trans(pya.DPoint(12, 16))), "17.3492424049,-14.6213203436") self.assertEqual(str(pya.DCplxTrans()), "r0 *1 0,0") self.assertEqual(pya.DCplxTrans().is_unity(), True) self.assertEqual((c * c.inverted()).is_unity(), True) c.mirror = False self.assertEqual(str(c), "r45 *0.75 2.5,-12.5") c.mag = 1.5 self.assertEqual(str(c), "r45 *1.5 2.5,-12.5") c.disp = pya.DPoint(-1.0, 5.5) self.assertEqual(str(c), "r45 *1.5 -1,5.5") self.assertEqual(c.mag, 1.5) c.angle = 60 self.assertEqual(str(c), "r60 *1.5 -1,5.5") self.assertEqual(("%g" % c.angle), "60") # Constructor variations self.assertEqual(str(pya.ICplxTrans()), "r0 *1 0,0") self.assertEqual(str(pya.ICplxTrans(1.5)), "r0 *1.5 0,0") self.assertEqual(str(pya.ICplxTrans(pya.Trans(1, False, 10, 20), 1.5)), "r90 *1.5 10,20") self.assertEqual(str(pya.ICplxTrans(pya.Trans(1, False, 10, 20))), "r90 *1 10,20") self.assertEqual( str(pya.ICplxTrans(1.5, 80, True, pya.Vector(100, 200))), "m40 *1.5 100,200") self.assertEqual(str(pya.ICplxTrans(1.5, 80, True, 100, 200)), "m40 *1.5 100,200") self.assertEqual(str(pya.ICplxTrans(pya.Vector(100, 200))), "r0 *1 100,200") self.assertEqual(str(pya.ICplxTrans(100, 200)), "r0 *1 100,200") self.assertEqual(str(pya.ICplxTrans(pya.ICplxTrans(100, 200))), "r0 *1 100,200") self.assertEqual(str(pya.ICplxTrans(pya.ICplxTrans(100, 200), 1.5)), "r0 *1.5 150,300") self.assertEqual( str( pya.ICplxTrans(pya.ICplxTrans(100, 200), 1.5, pya.Vector(10, 20))), "r0 *1.5 160,320") self.assertEqual( str(pya.ICplxTrans(pya.ICplxTrans(100, 200), 1.5, 10, 20)), "r0 *1.5 160,320") self.assertEqual(str(pya.DCplxTrans()), "r0 *1 0,0") self.assertEqual(str(pya.DCplxTrans(1.5)), "r0 *1.5 0,0") self.assertEqual( str(pya.DCplxTrans(pya.DTrans(1, False, 0.01, 0.02), 1.5)), "r90 *1.5 0.01,0.02") self.assertEqual(str(pya.DCplxTrans(pya.DTrans(1, False, 0.01, 0.02))), "r90 *1 0.01,0.02") self.assertEqual( str(pya.DCplxTrans(1.5, 80, True, pya.DVector(0.1, 0.2))), "m40 *1.5 0.1,0.2") self.assertEqual(str(pya.DCplxTrans(1.5, 80, True, 0.1, 0.2)), "m40 *1.5 0.1,0.2") self.assertEqual(str(pya.DCplxTrans(pya.DVector(0.1, 0.2))), "r0 *1 0.1,0.2") self.assertEqual(str(pya.DCplxTrans(0.1, 0.2)), "r0 *1 0.1,0.2") self.assertEqual(str(pya.DCplxTrans(pya.DCplxTrans(0.1, 0.2))), "r0 *1 0.1,0.2") self.assertEqual(str(pya.DCplxTrans(pya.DCplxTrans(0.1, 0.2), 1.5)), "r0 *1.5 0.15,0.3") self.assertEqual( str( pya.DCplxTrans(pya.DCplxTrans(0.1, 0.2), 1.5, pya.DVector(0.01, 0.02))), "r0 *1.5 0.16,0.32") self.assertEqual( str(pya.DCplxTrans(pya.DCplxTrans(0.1, 0.2), 1.5, 0.01, 0.02)), "r0 *1.5 0.16,0.32")
def vernier_single_gen(param, t=pya.ICplxTrans(0, 0), dbu=1.0): """ Returns list of polygons of vernier generated. Optionally with describtors Cleaner and more universal in comparism to previous pya.Cell atempt it doesn't require the layer and layout to be passed Parameters ---------- param : obj dataclass containing the vernier design parameters class Vernier: tLong : float = 50.0 #Long tick lenght tShort : float = 30.0 #Short tick lenght tWidth : float = 5.0 #Tick wall width sp : float = 8.0 #Tick Spacing tCnt : int = 12 #Number of ticks overall group : int = 4 #Number of ticks per group (resolution) markers : bool = False #Allow markers above / bellow ticks asc : bool = False #ascending of Label signs (+++/---) centered : bool = True #True: Center tick is 0, False: counts from side markSize : float = 10.0 #Magnification of sign markStp : int = 1 #Increment per tick markTickSep : float = 5.0 #Separation between tick and marker """ tick_ln = pya.Box(-param.tWidth / 2 / dbu, 0.0, param.tWidth / 2 / dbu, param.tLong / dbu) tick_sh = pya.Box(-param.tWidth / 2 / dbu, 0.0, param.tWidth / 2 / dbu, param.tShort / dbu) #verniCell = pya.Cell() verni_polys = [] #get the generator direction if param.centered: print(param.asc) if param.asc: tick_range = range(-param.tCnt, param.tCnt + 1, +1) dire = +1 else: tick_range = range(param.tCnt, -param.tCnt - 1, -1) dire = -1 else: if param.asc: tick_range = range(-param.tCnt, 1, +1) dire = +1 else: tick_range = range(param.tCnt, -1, -1) dire = -1 for loc in tick_range: #loc = tick_range[i] t_loc = pya.ICplxTrans(loc * param.sp / dbu, param.offset / dbu) if loc % param.group == 0: #possition of long tick tick_t = tick_ln.transformed(t_loc) tick_t = tick_t.transformed(t) poly = pya.Polygon(tick_t) verni_polys.append(poly) if param.markers: gen = pya.TextGenerator.default_generator() if (loc / param.group * param.markStp) == 0: text = gen.text("0", dbu, param.markSize) else: text = gen.text( "{:+.0f}".format(loc / param.group * param.markStp), dbu, param.markSize) #text is generated with origin in the LB corner -> corecting by bbox size t_loc_text = pya.ICplxTrans( ((dire * loc * param.sp) / dbu - (text.bbox().p2.x) / 2), -((text.bbox().p2.y) + param.tLong / dbu + param.markTickSep / dbu)) #print(t_loc_text) text_t = text.transformed(t_loc_text) t_text = t.to_trans() #print(t_text) #t_text.disp = t_text.disp / dbu t_text.angle = t_text.angle + 180.0 #print(t_text) #verni_polys.append(text_t) verni_polys.append(text_t.transformed(t_text)) #TODO: generate marker labels else: #position of short tick tick_t = tick_sh.transformed(t_loc) verni_polys.append(pya.Polygon(tick_t.transformed(t))) return verni_polys
def test_1_Polygon(self): a = pya.Polygon() self.assertEqual( str(a), "()" ) self.assertEqual( str(pya.Polygon.from_s(str(a))), str(a) ) self.assertEqual( a.is_box(), False ) b = a.dup() a = pya.Polygon( [ pya.Point( 0, 1 ), pya.Point( 1, 5 ), pya.Point( 5, 5 ) ] ) self.assertEqual( str(a), "(0,1;1,5;5,5)" ) self.assertEqual( str(a * 2), "(0,2;2,10;10,10)" ) self.assertEqual( str(pya.Polygon.from_s(str(a))), str(a) ) self.assertEqual( a.num_points_hull(), 3 ) c = a.dup() self.assertEqual( a == b, False ) self.assertEqual( a == c, True ) self.assertEqual( a != b, True ) self.assertEqual( a != c, False ) a = pya.Polygon( pya.Box( 5, -10, 20, 15 ) ) self.assertEqual( a.is_box(), True ) self.assertEqual( str(a), "(5,-10;5,15;20,15;20,-10)" ) self.assertEqual( str(pya.DPolygon(a)), "(5,-10;5,15;20,15;20,-10)" ) self.assertEqual( a.num_points_hull(), 4 ) self.assertEqual( a.area(), 15*25 ) self.assertEqual( a.perimeter(), 80 ) self.assertEqual( a.inside( pya.Point( 10, 0 ) ), True ) self.assertEqual( a.inside( pya.Point( 5, 0 ) ), True ) self.assertEqual( a.inside( pya.Point( 30, 0 ) ), False ) arr = [] for p in a.each_point_hull(): arr.append(str(p)) self.assertEqual( arr, ["5,-10", "5,15", "20,15", "20,-10"] ) b = a.dup() self.assertEqual( str(a.moved( pya.Point( 0, 1 ) )), "(5,-9;5,16;20,16;20,-9)" ) self.assertEqual( str(a.moved( 0, 1 )), "(5,-9;5,16;20,16;20,-9)" ) aa = a.dup() aa.move( 1, 0 ) self.assertEqual( str(aa), "(6,-10;6,15;21,15;21,-10)" ) a.move( pya.Point( 1, 0 ) ) self.assertEqual( str(a), "(6,-10;6,15;21,15;21,-10)" ) b = b.transformed( pya.Trans( pya.Trans.R0, pya.Point( 1, 0 )) ) self.assertEqual( str(b), "(6,-10;6,15;21,15;21,-10)" ) m = pya.CplxTrans( pya.Trans(), 1.5 ) self.assertEqual( str(a.transformed(m)), "(9,-15;9,22.5;31.5,22.5;31.5,-15)" ) self.assertEqual( str(a.transformed(pya.ICplxTrans(m))), "(9,-15;9,23;32,23;32,-15)" ) a.hull = [ pya.Point( 0, 1 ), pya.Point( 1, 1 ), pya.Point( 1, 5 ) ] self.assertEqual( str(a.bbox()), "(0,1;1,5)" ) self.assertEqual( a.holes(), 0 ) a.insert_hole( [ pya.Point( 1, 2 ), pya.Point( 2, 2 ), pya.Point( 2, 6 ) ] ) self.assertEqual( str(a), "(0,1;1,5;1,1/1,2;2,2;2,6)" ) self.assertEqual( str(pya.Polygon.from_s(str(a))), str(a) ) self.assertEqual( a.area(), 0 ) self.assertEqual( a.num_points_hole(0), 3 ) self.assertEqual( a.holes(), 1 ) self.assertEqual( str(a.point_hull(1)), "1,5" ) self.assertEqual( str(a.point_hull(0)), "0,1" ) self.assertEqual( str(a.point_hull(100)), "0,0" ) self.assertEqual( str(a.point_hole(0, 100)), "0,0" ) self.assertEqual( str(a.point_hole(0, 1)), "2,2" ) self.assertEqual( str(a.point_hole(1, 1)), "0,0" ) a.compress(False); self.assertEqual( str(a), "(0,1;1,5;1,1/1,2;2,2;2,6)" ) a.compress(True); self.assertEqual( str(a), "(0,1;1,5;1,1/1,2;2,2;2,6)" ) b = a.dup() b.assign_hole(0, pya.Box( 10, 20, 20, 60 )) self.assertEqual( str(b), "(0,1;1,5;1,1/10,20;20,20;20,60;10,60)" ) self.assertEqual( b.is_box(), False ) b.insert_hole(pya.Box( 10, 20, 20, 60 )) self.assertEqual( str(b), "(0,1;1,5;1,1/10,20;20,20;20,60;10,60/10,20;20,20;20,60;10,60)" ) b = a.dup() b.assign_hole(0, [ pya.Point( 10, 20 ), pya.Point( 20, 20 ), pya.Point( 20, 60 ) ]) self.assertEqual( str(b), "(0,1;1,5;1,1/10,20;20,20;20,60)" ) b.assign_hole(1, [ pya.Point( 15, 25 ), pya.Point( 25, 25 ), pya.Point( 25, 65 ) ]) self.assertEqual( str(b), "(0,1;1,5;1,1/10,20;20,20;20,60)" ) b.insert_hole( [ pya.Point( 1, 2 ), pya.Point( 2, 2 ), pya.Point( 2, 6 ) ] ) self.assertEqual( str(b), "(0,1;1,5;1,1/1,2;2,2;2,6/10,20;20,20;20,60)" ) b.assign_hole(0, [ pya.Point( 15, 25 ), pya.Point( 25, 25 ), pya.Point( 25, 65 ) ]) self.assertEqual( str(b), "(0,1;1,5;1,1/15,25;25,25;25,65/10,20;20,20;20,60)" ) arr = [] for p in a.each_point_hole(0): arr.append(str(p)) self.assertEqual( arr, ["1,2", "2,2", "2,6"] ) arr = [] for p in a.each_edge(): arr.append(str(p)) self.assertEqual( arr, ["(0,1;1,5)", "(1,5;1,1)", "(1,1;0,1)", "(1,2;2,2)", "(2,2;2,6)", "(2,6;1,2)"] ) a = pya.Polygon( [ pya.Point( 0, 1 ), pya.Point( 1, 5 ), pya.Point( 5, 5 ) ] ) self.assertEqual( str(a), "(0,1;1,5;5,5)" ) self.assertEqual( str(a.sized(2)), "(0,-2;-2,0;-1,7;7,7;8,5)" ) self.assertEqual( str(a.sized(2, 2)), "(0,-2;-2,0;-1,7;7,7;8,5)" ) aa = a.dup() a.size(2, 2) self.assertEqual( str(a), "(0,-2;-2,0;-1,7;7,7;8,5)" ) a = aa.dup() a.size(2) self.assertEqual( str(a), "(0,-2;-2,0;-1,7;7,7;8,5)" ) a = pya.Polygon( [ pya.Point( 0, 1 ), pya.Point( 1, 5 ), pya.Point( 5, 5 ) ] ) self.assertEqual( str(a), "(0,1;1,5;5,5)" ) self.assertEqual( str(a.sized(2, 0, 2)), "(-2,1;-1,5;7,5;2,1)" ) a.size(2, 0, 2); self.assertEqual( str(a), "(-2,1;-1,5;7,5;2,1)" ) a = pya.Polygon() self.assertEqual( str(a), "()" ) # corner rounding a = pya.Polygon( [ pya.Point(0, 0), pya.Point(0, 2000), pya.Point(4000, 2000), pya.Point(4000, 1000), pya.Point(2000, 1000), pya.Point(2000, 0) ] ) ar = a.round_corners(100, 200, 8) self.assertEqual( str(ar), "(117,0;0,117;0,1883;117,2000;3883,2000;4000,1883;4000,1117;3883,1000;2059,1000;2000,941;2000,117;1883,0)" ) ar = a.round_corners(200, 100, 32) self.assertEqual( str(ar), "(90,0;71,4;53,11;36,22;22,36;11,53;4,71;0,90;0,1910;4,1929;11,1947;22,1964;36,1978;53,1989;71,1996;90,2000;3910,2000;3929,1996;3947,1989;3964,1978;3978,1964;3989,1947;3996,1929;4000,1910;4000,1090;3996,1071;3989,1053;3978,1036;3964,1022;3947,1011;3929,1004;3910,1000;2180,1000;2142,992;2105,977;2073,955;2045,927;2023,895;2008,858;2000,820;2000,90;1996,71;1989,53;1978,36;1964,22;1947,11;1929,4;1910,0)" ) # Minkowsky sums p = pya.Polygon( [ pya.Point.new(0, -100), pya.Point.new(0, -50), pya.Point.new(-100, -75), pya.Point.new(0, 100), pya.Point.new(50, 50), pya.Point.new(100, 75), pya.Point.new(100, 0), pya.Point.new(100, -50) ] ) self.assertEqual(str(p.minkowsky_sum(pya.Edge.new(pya.Point.new(10, 10), pya.Point.new(210, 110)), True)), "(10,-90;10,-40;-90,-65;10,110;210,210;260,160;310,185;310,60)") self.assertEqual(str(p.minkowsky_sum([pya.Point.new(10, 10), pya.Point.new(10, 310), pya.Point.new(510, 310), pya.Point.new(510, 10), pya.Point.new(10, 10) ], False)), "(10,-90;10,-65;-90,-65;-90,235;10,410;510,410;535,385;610,385;610,-40;510,-90/110,110;410,110;410,210;110,210)") self.assertEqual(str(p.minkowsky_sum([pya.Point.new(10, 10), pya.Point.new(10, 310), pya.Point.new(510, 310), pya.Point.new(510, 10), pya.Point.new(10, 10) ], True)), "(10,-90;10,-65;-90,-65;-90,210;110,210;110,110;410,110;410,210;-90,210;-90,235;10,410;510,410;535,385;610,385;610,-40;510,-90)") self.assertEqual(str(p.minkowsky_sum(pya.Box.new(pya.Point.new(10, 10), pya.Point.new(210, 110)), True)), "(10,-90;10,-65;-90,-65;-90,35;10,210;210,210;235,185;310,185;310,-40;210,-90)") self.assertEqual(str(p.minkowsky_sum(pya.Box.new(pya.Point.new(10, 10), pya.Point.new(210, 10)), True)), "(10,-90;10,-65;-90,-65;10,110;210,110;235,85;310,85;310,-40;210,-90)") self.assertEqual(str(p.minkowsky_sum(pya.Polygon.new(pya.Box.new(pya.Point.new(10, 10), pya.Point.new(210, 110))), True)), "(10,-90;10,-65;-90,-65;-90,35;10,210;210,210;235,185;310,185;310,-40;210,-90)") # Smoothing p = pya.Polygon( [ pya.Point.new(0, 0), pya.Point.new(10, 50), pya.Point.new(0, 100), pya.Point.new(200, 100), pya.Point.new(200, 0) ]) self.assertEqual(str(p.smooth(5)), "(0,0;10,50;0,100;200,100;200,0)") self.assertEqual(str(p.smooth(15)), "(0,0;0,100;200,100;200,0)") # Ellipse constructor p = pya.Polygon.ellipse( pya.Box(-10000, -20000, 30000, 40000), 200 ) self.assertEqual(p.num_points(), 200) self.assertEqual(str(p.bbox()), "(-10000,-20000;30000,40000)") self.assertEqual(p.area(), 1884651158) # roughly box.area*PI/4 p = pya.Polygon.ellipse( pya.Box(-10000, -20000, 30000, 40000), 4 ) self.assertEqual(str(p), "(10000,-20000;-10000,10000;10000,40000;30000,10000)")
def produce_impl(self): #Using Double values only - no dbu required (alternatively use scaling instead) #dbu definition is only for backwards compatibility with not "D" functions # @dataclass # class SqinSq: # a : float = 100.0 #Side lenght of the center square # wall : float = 10.0 #Wallthickness of the center square FM @dataclass class Vernier: tLong: float = 50.0 #Long tick lenght tShort: float = 30.0 #Short tick lenght tWidth: float = 5.0 #Tick wall width sp: float = 8.0 #Spacing first exposure offset: float = 0.0 #offset in y direction tCnt: int = 12 #Number of ticks per side (not inc. center) group: int = 4 #Number of ticks per group (resolution) markers: bool = False #Allow markers above / bellow ticks asc: bool = False #ascending of Label signs (+++/---) centered: bool = True #True: Center tick is 0, False: counts from side markSize: float = 40.0 #Magnification of sign markStp: int = 1 #Increment number per long tick markTickSep: float = 5.0 #Separation bFalsetween tick and marker class HolowSq(pya.Polygon): """ A class is child of pya -> DPolygon Builds a hollow square from 2 parameters ... Attributes ---------- side : float first name of the person wall : float family name of the person Methods ------- N/A """ def __init__(self, side, wall, dbu=1): self.a = side / 2 / dbu self.h = side / 2 / dbu - wall / dbu self.assign( pya.Polygon(pya.Box(-self.a, -self.a, self.a, self.a))) self.insert_hole(pya.Box(-self.h, -self.h, self.h, self.h)) ''' Constants and variables: DIST_VERNIER : float - distance of vernier structures center from center of square DBU : float - database unit for backwards compatibility OL_OVERLAP: float - defines the FM and OL step vernier overlap (-) or gap (+) COMMENT_LOC : List : floats - defines the location of optional comment under the marker ''' DIST_VERNIER = 200 DBU = 0.001 OL_OVERLAP = 0 COMMENT_LOC = [0, -350] ALIGN_LOC = [-DIST_VERNIER, DIST_VERNIER] ALIGN_TYPE = ["", "T\\nS\\nA", "B\\nS\\nA"] MARK_SIZE = [1000, 1000] sqFM = HolowSq(200.0, 20.0, DBU) sqOL = HolowSq(100.0, 20.0, DBU) sqArea = pya.Box(-MARK_SIZE[0] / 2 / DBU, -MARK_SIZE[0] / 2 / DBU, MARK_SIZE[1] / 2 / DBU, MARK_SIZE[1] / 2 / DBU) _scale = pya.ICplxTrans(1 / DBU) verniL = Vernier(tLong=40.0, tShort=30.0, tWidth=5.0, sp=13.0, tCnt=12, group=13) verniC_OL_B = Vernier(sp=13.25, asc=True, markers=True, offset=OL_OVERLAP) verniC_OL_L = Vernier(sp=13.25, asc=False, markers=True, offset=OL_OVERLAP) verniF_OL_R = Vernier(tWidth=5.0, sp=15.1, tCnt=10, group=10, markers=True, asc=True, offset=OL_OVERLAP) verniF_OL_T = Vernier(tWidth=5.0, sp=15.1, tCnt=10, group=10, markers=True, asc=False, offset=OL_OVERLAP) #Generate vernier array into the cell (layers would be taken from original parameters) def vernier_single_gen(param, t=pya.ICplxTrans(0, 0), dbu=1.0): """ Returns list of polygons of vernier generated. Optionally with describtors Cleaner and more universal in comparism to previous pya.Cell atempt it doesn't require the layer and layout to be passed Parameters ---------- param : obj dataclass containing the vernier design parameters class Vernier: tLong : float = 50.0 #Long tick lenght tShort : float = 30.0 #Short tick lenght tWidth : float = 5.0 #Tick wall width sp : float = 8.0 #Tick Spacing tCnt : int = 12 #Number of ticks overall group : int = 4 #Number of ticks per group (resolution) markers : bool = False #Allow markers above / bellow ticks asc : bool = False #ascending of Label signs (+++/---) centered : bool = True #True: Center tick is 0, False: counts from side markSize : float = 10.0 #Magnification of sign markStp : int = 1 #Increment per tick markTickSep : float = 5.0 #Separation between tick and marker """ tick_ln = pya.Box(-param.tWidth / 2 / dbu, 0.0, param.tWidth / 2 / dbu, param.tLong / dbu) tick_sh = pya.Box(-param.tWidth / 2 / dbu, 0.0, param.tWidth / 2 / dbu, param.tShort / dbu) #verniCell = pya.Cell() verni_polys = [] #get the generator direction if param.centered: print(param.asc) if param.asc: tick_range = range(-param.tCnt, param.tCnt + 1, +1) dire = +1 else: tick_range = range(param.tCnt, -param.tCnt - 1, -1) dire = -1 else: if param.asc: tick_range = range(-param.tCnt, 1, +1) dire = +1 else: tick_range = range(param.tCnt, -1, -1) dire = -1 for loc in tick_range: #loc = tick_range[i] t_loc = pya.ICplxTrans(loc * param.sp / dbu, param.offset / dbu) if loc % param.group == 0: #possition of long tick tick_t = tick_ln.transformed(t_loc) tick_t = tick_t.transformed(t) poly = pya.Polygon(tick_t) verni_polys.append(poly) if param.markers: gen = pya.TextGenerator.default_generator() if (loc / param.group * param.markStp) == 0: text = gen.text("0", dbu, param.markSize) else: text = gen.text( "{:+.0f}".format(loc / param.group * param.markStp), dbu, param.markSize) #text is generated with origin in the LB corner -> corecting by bbox size t_loc_text = pya.ICplxTrans( ((dire * loc * param.sp) / dbu - (text.bbox().p2.x) / 2), -((text.bbox().p2.y) + param.tLong / dbu + param.markTickSep / dbu)) #print(t_loc_text) text_t = text.transformed(t_loc_text) t_text = t.to_trans() #print(t_text) #t_text.disp = t_text.disp / dbu t_text.angle = t_text.angle + 180.0 #print(t_text) #verni_polys.append(text_t) verni_polys.append(text_t.transformed(t_text)) #TODO: generate marker labels else: #position of short tick tick_t = tick_sh.transformed(t_loc) verni_polys.append(pya.Polygon(tick_t.transformed(t))) return verni_polys #lets store objects as regions obj_FM_layer = pya.Region() obj_OL_layer = pya.Region() obj_INV_layer = pya.Region() #First the squares # Square in square structure obj_FM_layer.insert(sqFM) obj_OL_layer.insert(sqOL) obj_INV_layer.insert(sqArea) #self.cell.shapes(self.l_layer).insert(sqFM) #self.cell.shapes(self.lo_layer).insert(sqOL) #depriciated verniers_poly = [[], []] #Lay step first first: rot_matrix = [[0, -1], [1, 0], [0, 1], [-1, 0]] #First layer vernier markers for i in range(0, 4): t = pya.ICplxTrans(1.0, 90 * i, False, rot_matrix[i][0] * DIST_VERNIER / DBU, rot_matrix[i][1] * DIST_VERNIER / DBU) verniers_poly[0].append(vernier_single_gen(verniL, t, DBU)) #Overlay vernier markers with annotation t = pya.ICplxTrans(1.0, 180 + 90 * 0, False, rot_matrix[0][0] * DIST_VERNIER / DBU, rot_matrix[0][1] * DIST_VERNIER / DBU) verniers_poly[1].append(vernier_single_gen(verniC_OL_B, t, DBU)) t = pya.ICplxTrans(1.0, 180 + 90 * 3, False, rot_matrix[3][0] * DIST_VERNIER / DBU, rot_matrix[3][1] * DIST_VERNIER / DBU) verniers_poly[1].append(vernier_single_gen(verniC_OL_L, t, DBU)) t = pya.ICplxTrans(1.0, 180 + 90 * 1, False, rot_matrix[1][0] * DIST_VERNIER / DBU, rot_matrix[1][1] * DIST_VERNIER / DBU) verniers_poly[1].append(vernier_single_gen(verniF_OL_R, t, DBU)) t = pya.ICplxTrans(1.0, 180 + 90 * 2, False, rot_matrix[2][0] * DIST_VERNIER / DBU, rot_matrix[2][1] * DIST_VERNIER / DBU) verniers_poly[1].append(vernier_single_gen(verniF_OL_T, t, DBU)) for obj in verniers_poly[0]: for arr in obj: obj_FM_layer.insert(arr) for obj in verniers_poly[1]: for arr in obj: obj_OL_layer.insert(arr) #Generate names around the marker (e.g. 2A->1A) gen = pya.TextGenerator.default_generator() # positive stepText = gen.text( "{}\\n->\\n{}".format(self.step_name_L, self.step_name_OL), DBU, 40, False, 0, 0, -1) # positive to UR transformation t_stepText = pya.ICplxTrans( (DIST_VERNIER / DBU - stepText.bbox().p1.x), (DIST_VERNIER / DBU - stepText.bbox().p1.y)) obj_FM_layer.insert(stepText.transform(t_stepText)) stepTextInv = gen.text( "{}\\n->\\n{}".format(self.step_name_L, self.step_name_OL), DBU, 40, True, 0, 0, -1) obj_OL_layer.insert(stepTextInv.transform(t_stepText)) #put the alignment indication marker if prefered if self.align_type != 0: align_text = gen.text(ALIGN_TYPE[self.align_type], DBU, 40, False, 0, 0, -1) align_textInv = gen.text(ALIGN_TYPE[self.align_type], DBU, 40, True, 0, 0, -1) # positive to UR transformation t_align_text = pya.ICplxTrans( ALIGN_LOC[0] / DBU - align_text.bbox().p2.x, ALIGN_LOC[1] / DBU - align_text.bbox().p1.y) obj_FM_layer.insert(align_text.transform(t_align_text)) obj_OL_layer.insert(align_textInv.transform(t_align_text)) #comment section if self.comment != "": comment_text = gen.text(self.comment[:25], DBU, 40, False, 0, 0, -1) comment_textInv = gen.text(self.comment[:25], DBU, 40, True, 0, 0, -1) # positive to UR transformation t_com_text = pya.ICplxTrans( (COMMENT_LOC[0] / DBU - comment_text.bbox().center().x), (COMMENT_LOC[1] / DBU - comment_text.bbox().center().y)) obj_FM_layer.insert(comment_text.transform(t_com_text)) obj_OL_layer.insert(comment_textInv.transform(t_com_text)) #Tone destinqution: if self.fm_tone: # is possitive # used to be >> self.cell.shapes(self.l_layer).insert(obj_FM_layer.transform(_scale)) self.cell.shapes(self.l_layer).insert(obj_FM_layer) else: print("tone FM negative") obj_neg = obj_INV_layer - obj_FM_layer self.cell.shapes(self.l_layer).insert(obj_neg) if self.ol_tone: # is possitive self.cell.shapes(self.lo_layer).insert(obj_OL_layer) else: print("tone OL negative") obj_neg = obj_INV_layer - obj_OL_layer self.cell.shapes(self.lo_layer).insert(obj_neg) if self.lb_allow: self.cell.shapes(self.lb_layer).insert(obj_INV_layer)