def test_move_by_orient(coords: Tuple[int, int, int, int], orient: Orient2D, dt: int, dp: int, ecoords: Tuple[int, int, int, int]) -> None: box = BBox(coords[0], coords[1], coords[2], coords[3]) expect = BBox(ecoords[0], ecoords[1], ecoords[2], ecoords[3]) box.move_by_orient(orient, dt, dp) assert box == expect
def get_mos_ext_edge_info(self, blk_w: int, einfo: MOSEdgeInfo) -> LayoutInfo: sd_pitch = self.sd_pitch cpo_h = self.mos_config['cpo_h'] mos_lay_table = self.tech_info.config['mos_lay_table'] cpo_lp = mos_lay_table['CPO'] po_lp = mos_lay_table['PO_DUMMY'] blk_h = einfo['blk_h'] row_type = einfo['row_type'] mos_type = einfo['mos_type'] threshold = einfo['threshold'] blk_rect = BBox(0, 0, blk_w, blk_h) imp_rect = BBox(blk_w - sd_pitch, 0, blk_w, blk_h) cpo_h2 = cpo_h // 2 builder = LayoutInfoBuilder() builder.add_rect_arr(cpo_lp, BBox(blk_w - sd_pitch, -cpo_h2, blk_w, cpo_h2)) builder.add_rect_arr(cpo_lp, BBox(blk_w - sd_pitch, blk_h - cpo_h2, blk_w, blk_h + cpo_h2)) num_sd_pitch = blk_w // sd_pitch self._add_po_array(builder, po_lp, (0, blk_h), num_sd_pitch - 1, num_sd_pitch) self._add_fb(builder, imp_rect) for lay_purp in self._thres_imp_well_layers_iter(row_type, mos_type, threshold): builder.add_rect_arr(lay_purp, imp_rect) return builder.get_info(blk_rect)
def _get_mos_ext_info_helper(self, num_cols: int, blk_h: int, row_type: MOSType, threshold: str ) -> ExtEndLayInfo: sd_pitch = self.sd_pitch cpo_h = self.mos_config['cpo_h'] mos_lay_table = self.tech_info.config['mos_lay_table'] cpo_lp = mos_lay_table['CPO'] po_lp = mos_lay_table['PO_DUMMY'] blk_w = num_cols * sd_pitch cpo_h2 = cpo_h // 2 blk_rect = BBox(0, 0, blk_w, blk_h) builder = LayoutInfoBuilder() builder.add_rect_arr(cpo_lp, BBox(0, -cpo_h2, blk_w, cpo_h2)) builder.add_rect_arr(cpo_lp, BBox(0, blk_h - cpo_h2, blk_w, blk_h + cpo_h2)) self._add_fb(builder, blk_rect) self._add_po_array(builder, po_lp, (0, blk_h), 0, num_cols) for lay_purp in self._thres_imp_well_layers_iter(row_type, row_type, threshold): builder.add_rect_arr(lay_purp, blk_rect) edge_info = MOSEdgeInfo(blk_h=blk_h, row_type=row_type, mos_type=row_type, threshold=threshold) return ExtEndLayInfo(builder.get_info(blk_rect), edge_info)
def add_base(builder: LayoutInfoBuilder, row_type: MOSType, threshold: str, imp_y: Tuple[int, int], rect: BBox, well_x: Optional[Tuple[int, int]] = None) -> None: if rect.is_physical(): if not row_type.is_pwell: well_lp = ('nwell', 'drawing') if well_x is None: builder.add_rect_arr(well_lp, rect) else: builder.add_rect_arr( well_lp, BBox(well_x[0], rect.yl, well_x[1], rect.yh)) if row_type.is_n_plus: builder.add_rect_arr(('nsdm', 'drawing'), rect) else: pimp_lp = ('psdm', 'drawing') nimp_lp = ('nsdm', 'drawing') if rect.yl < imp_y[0]: builder.add_rect_arr(nimp_lp, BBox(rect.xl, rect.yl, rect.xh, imp_y[0])) if imp_y[1] < rect.yh: builder.add_rect_arr(nimp_lp, BBox(rect.xl, imp_y[1], rect.xh, rect.yh)) if imp_y[0] < imp_y[1]: builder.add_rect_arr( pimp_lp, BBox(rect.xl, imp_y[0], rect.xh, imp_y[1])) thres_lp = _get_thres_lp(row_type, threshold) if thres_lp[0] != '': builder.add_rect_arr(thres_lp, rect)
def test_physical_valid(xl, yl, xh, yh, physical, valid): ans = BBox(xl, yl, xh, yh) assert ans.is_physical() == physical assert ans.is_valid() == valid # check orientation based constructor works b1 = BBox(Orient2D.x, xl, xh, yl, yh) b2 = BBox(Orient2D.y, yl, yh, xl, xh) assert b1 == ans assert b2 == ans
def test_transform(box0, dx, dy, orient, box1): xform = Transform(dx, dy, Orientation[orient]) a = BBox(box0[0], box0[1], box0[2], box0[3]) ans = BBox(box1[0], box1[1], box1[2], box1[3]) b = a.get_transform(xform) assert b == ans assert b is not a c = a.transform(xform) assert a == ans assert c is a
def test_overlap(): rtree = RTree() box_list = [BBox(0, 0, 2, 3), BBox(-2, -3, 0, -1), BBox(10, 10, 20, 15)] test_box = BBox(-1, -2, 0, 0) for box in box_list: rtree.insert(None, box) obj_list = list(rtree.overlap_iter(test_box)) assert len(obj_list) == 1 assert obj_list[0][0] == box_list[1]
def test_insert(): rtree = RTree() box_list = [BBox(0, 0, 2, 3), BBox(-2, -3, 0, -1)] items = [None, {'hi': 2, 'bye': 'foo'}] for obj, box in zip(items, box_list): rtree.insert(obj, box) for box, obj_id in rtree: assert rtree[obj_id] is items[obj_id] assert box == box_list[obj_id]
def get_warr_bbox(self, warr: WireArray) -> BBox: """Computes the overall bounding box of the given WireArray. Parameters ---------- warr : WireArray the WireArray object. Returns ------- bbox : BBox the overall bounding box of the WireArray. """ tid = warr.track_id layer_id = tid.layer_id lower, upper = self.get_wire_bounds_htr(layer_id, tid.base_htr, tid.width) delta = (tid.num - 1) * int(tid.pitch * self.get_track_pitch(layer_id)) if delta >= 0: upper += delta else: lower += delta return BBox(self.get_direction(layer_id), warr.lower, warr.upper, lower, upper)
def test_pop(): rtree = RTree() box_list = [BBox(0, 0, 2, 3), BBox(-2, -3, 0, -1), BBox(10, 10, 20, 15)] for box in box_list: rtree.insert(None, box) ans = rtree.pop(1) assert ans is None results = list(rtree) assert len(results) == 2 for box, obj_id in results: assert obj_id != 1 assert box == box_list[obj_id]
def test_merge_invalid(): a = BBox(0, 0, 2, 3) b = BBox(100, 103, 200, 102) a2 = a.get_merge(b) a3 = b.get_merge(a) assert a2 == a assert a3 == a assert a2 is not a assert a3 is not a a2 = a.merge(b) assert a2 is a b2 = b.merge(a) assert b2 is b assert b2 == a
def _add_vg_half(vg_x: int) -> None: xl = vg_x - mp_delta xr = vg_x + mp_delta builder.add_rect_arr(mp_lp, BBox(xl, mp_yb, xr, mp_yt), nx=num_vg2, spx=vg_pitch) builder.add_via(g_info.get_via_info('M1_LiPo', vg_x, mp_yc, mp_h, nx=num_vg2, spx=vg_pitch))
def get_mos_corner_info(self, blk_w: int, blk_h: int, einfo: MOSEdgeInfo) -> CornerLayInfo: sd_pitch = self.sd_pitch cpo_h = self.mos_config['cpo_h'] mos_lay_table = self.tech_info.config['mos_lay_table'] cpo_lp = mos_lay_table['CPO'] cpo_h2 = cpo_h // 2 blk_rect = BBox(0, 0, blk_w, blk_h) builder = LayoutInfoBuilder() builder.add_rect_arr(cpo_lp, BBox(blk_w - sd_pitch, blk_h - cpo_h2, blk_w, blk_h + cpo_h2)) edgel = edgeb = ImmutableSortedDict(dict(dev_type=DeviceType.MOS, well_margin=sd_pitch)) return CornerLayInfo(builder.get_info(blk_rect), (0, 0), edgel, edgeb)
def _get_mos_active_rect_list(self, builder: LayoutInfoBuilder, row_info: MOSRowInfo, fg: int, w: int, dev_type: MOSType) -> BBox: lch = self.lch sd_pitch = self.sd_pitch od_po_extx = self.od_po_extx md_w: int = self.mos_config['md_w'] row_type = row_info.row_type blk_yt = row_info.height od_yb: int = row_info['od_yb'] md_y: Tuple[int, int] = row_info['md_y'] mos_lay_table = self.tech_info.config['mos_lay_table'] od_lp = mos_lay_table['OD'] po_lp = mos_lay_table['PO'] md_lp = mos_lay_table['MD'] blk_xr = fg * sd_pitch po_x0 = (sd_pitch - lch) // 2 od_yt = od_yb + self.get_od_height(w) # draw PO self._add_po_array(builder, po_lp, (0, blk_yt), 0, fg) # draw OD od_sd_dx = od_po_extx - po_x0 od_xl = -od_sd_dx od_xr = fg * sd_pitch + od_sd_dx builder.add_rect_arr(od_lp, BBox(od_xl, od_yb, od_xr, od_yt)) # draw MD md_x0 = -md_w // 2 builder.add_rect_arr(md_lp, BBox(md_x0, md_y[0], md_x0 + md_w, md_y[1]), nx=fg + 1, spx=sd_pitch) # draw threshold and implant layers blk_rect = BBox(0, 0, blk_xr, blk_yt) blk_box = BBox(0, 0, blk_xr, blk_yt) for lay_purp in self._thres_imp_well_layers_iter( row_type, dev_type, row_info.threshold): builder.add_rect_arr(lay_purp, blk_box) self._add_fb(builder, blk_rect) return blk_rect
def get_mos_end_info(self, blk_h: int, num_cols: int, einfo: RowExtInfo) -> ExtEndLayInfo: sd_pitch = self.sd_pitch cpo_h = self.mos_config['cpo_h'] mos_lay_table = self.tech_info.config['mos_lay_table'] cpo_lp = mos_lay_table['CPO'] cpo_h2 = cpo_h // 2 blk_w = num_cols * sd_pitch blk_rect = BBox(0, 0, blk_w, blk_h) builder = LayoutInfoBuilder() builder.add_rect_arr(cpo_lp, BBox(0, blk_h - cpo_h2, blk_w, blk_h + cpo_h2)) edge_info = MOSEdgeInfo() return ExtEndLayInfo(builder.get_info(blk_rect), edge_info)
def _add_fb(self, builder: LayoutInfoBuilder, rect: BBox) -> None: fin_h = self.fin_h fin_p = self.mos_config['fin_p'] mos_lay_table = self.tech_info.config['mos_lay_table'] fb_lp = mos_lay_table['FB'] dy = (fin_p + fin_h) // 2 builder.add_rect_arr(fb_lp, BBox(rect.xl, rect.yl - dy, rect.xh, rect.yh + dy))
def _add_po_array(self, builder: LayoutInfoBuilder, po_lp: Tuple[str, str], po_y: Tuple[int, int], start: int, stop: int) -> None: lch = self.lch sd_pitch = self.sd_pitch po_x0 = (sd_pitch - lch) // 2 + sd_pitch * start fg = stop - start builder.add_rect_arr(po_lp, BBox(po_x0, po_y[0], po_x0 + lch, po_y[1]), nx=fg, spx=sd_pitch)
def draw_layout(self): w: int = self.params['w'] h: int = self.params['h'] fill_layer: int = self.params['fill_layer'] grid = self.grid tech_info = grid.tech_info fill_info = tech_info.get_max_space_fill_info(fill_layer) self.set_size_from_bound_box(fill_layer, BBox(0, 0, w, h), round_up=True) bbox = self.bound_box tdir = grid.get_direction(fill_layer) pdir = tdir.perpendicular() margin = fill_info.get_margin(pdir) margin_le = fill_info.get_margin(tdir) dim = bbox.get_dim(pdir) dim_le = bbox.get_dim(tdir) wlen = grid.get_min_cont_length(fill_layer, 1) # fill edges and ends tidxl = grid.coord_to_track(fill_layer, margin, mode=RoundMode.LESS_EQ, even=True) tidxr = grid.coord_to_track(fill_layer, dim - margin, mode=RoundMode.GREATER_EQ, even=True) tcoord_u = dim_le - margin_le num = tidxr - tidxl - 1 self.add_wires(fill_layer, tidxl, margin_le, tcoord_u, num=2, pitch=tidxr - tidxl) self.add_wires(fill_layer, tidxl + 1, margin_le, margin_le + wlen, num=num, pitch=1) self.add_wires(fill_layer, tidxl + 1, tcoord_u - wlen, tcoord_u, num=num, pitch=1) self.do_max_space_fill(fill_layer, bbox)
def get_ext_geometries(self, re_bot: RowExtInfo, re_top: RowExtInfo, be_bot: ImmutableList[BlkExtInfo], be_top: ImmutableList[BlkExtInfo], cut_mode: MOSCutMode, bot_exty: int, top_exty: int, dx: int, dy: int, w_edge: int) -> LayoutInfo: builder = LayoutInfoBuilder() if cut_mode is MOSCutMode.MID: sd_pitch = self.sd_pitch cpo_h = self.mos_config['cpo_h'] cpo_h2 = cpo_h // 2 cpo_lp = self.tech_info.config['mos_lay_table']['CPO'] wr = dx for be in be_bot: wr += be.fg * sd_pitch builder.add_rect_arr(cpo_lp, BBox(dx - sd_pitch, dy - cpo_h2, wr + sd_pitch, dy + cpo_h2)) return builder.get_info(BBox(0, 0, 0, 0))
def draw_layout(self): fill_layer: int = self.params['fill_layer'] grid = self.grid tech_info = grid.tech_info fill_info = tech_info.get_max_space_fill_info(fill_layer) tdir = grid.get_direction(fill_layer) pdir = tdir.perpendicular() margin = fill_info.get_margin(pdir) margin_le = fill_info.get_margin(tdir) sp_le = fill_info.get_space(tdir) blk_arr = grid.get_block_size(fill_layer, half_blk_x=False, half_blk_y=False) dim_q = blk_arr[pdir.value] w = (int(round(margin * 1.5)) // dim_q) * dim_q wlen = grid.get_min_cont_length(fill_layer, 1) h = 2 * margin_le + 7 * wlen + 5 * sp_le self.set_size_from_bound_box(fill_layer, BBox(0, 0, w, h), round_up=True, half_blk_x=False, half_blk_y=False) bbox = self.bound_box dim = bbox.get_dim(pdir) tidx0 = grid.find_next_track(fill_layer, dim - margin, mode=RoundMode.LESS) tidx1 = grid.find_next_track(fill_layer, margin, mode=RoundMode.GREATER) tidx_l = tidx0 + 1 tidx_r = tidx1 - 1 lower = margin_le self.add_wires(fill_layer, tidx0, lower, lower + wlen) self.add_wires(fill_layer, tidx1, lower, lower + wlen) lower += wlen + sp_le self.add_wires(fill_layer, tidx1, lower, lower + wlen) lower += wlen + sp_le self.add_wires(fill_layer, tidx0, lower, lower + wlen) lower += 2 * wlen + 2 * sp_le self.add_wires(fill_layer, tidx_l, lower, lower + wlen) lower += 2 * wlen + sp_le self.add_wires(fill_layer, tidx_r, lower, lower + wlen) self.do_max_space_fill(fill_layer, bbox)
def test_constructor(box_data, nx, ny, spx, spy): base = BBox(*box_data) ref = BBoxArray(base, nx, ny, spx, spy) assert ref.base == base assert ref.nx == nx assert ref.ny == ny assert ref.spx == spx assert ref.spy == spy a1 = BBoxArray(base, Orient2D.x, nx, spx, ny, spy) a2 = BBoxArray(base, Orient2D.y, ny, spy, nx, spx) assert a1 == ref assert a2 == ref
def get_mos_row_edge_info(self, blk_w: int, rinfo: MOSRowInfo, einfo: MOSEdgeInfo) -> LayoutInfo: lch = self.lch sd_pitch = self.sd_pitch od_po_extx = self.od_po_extx od_extx = od_po_extx - (sd_pitch - lch) // 2 mos_config = self.mos_config imp_od_encx: int = mos_config['imp_od_encx'] mos_lay_table = self.tech_info.config['mos_lay_table'] row_type = rinfo.row_type blk_h = rinfo.height mos_type = einfo['mos_type'] has_od = einfo.get('has_od', False) blk_rect = BBox(0, 0, blk_w, blk_h) imp_rect = BBox(blk_w - imp_od_encx - od_extx, 0, blk_w, blk_h) po_xl = blk_w - sd_pitch // 2 - lch // 2 builder = LayoutInfoBuilder() if has_od: po_lp = mos_lay_table['PO_PODE'] else: po_lp = mos_lay_table['PO_DUMMY'] builder.add_rect_arr(po_lp, BBox(po_xl, 0, po_xl + lch, rinfo.height)) self._add_fb(builder, imp_rect) if mos_type.is_substrate and mos_type is not row_type.sub_type: row_type = mos_type for lay_purp in self._thres_imp_well_layers_iter( row_type, mos_type, rinfo.threshold): builder.add_rect_arr(lay_purp, imp_rect) return builder.get_info(blk_rect)
def set_mos_size(self, num_cols: int = 0, num_tiles: int = 0) -> None: if not self.size_defined: ainfo = self._arr_info used_arr = self._used_arr if num_cols > 0: used_arr.num_cols = num_cols if num_tiles > 0: used_arr.set_num_tiles(num_tiles) width = used_arr.num_cols * ainfo.sd_pitch height = used_arr.height self.set_size_from_bound_box(ainfo.top_layer, BBox(0, 0, width, height)) else: raise ValueError('Cannot change tile_size once it is set.')
def test_properties(xl, yl, xh, yh, physical, valid): ans = BBox(xl, yl, xh, yh) assert ans.xl == xl assert ans.yl == yl assert ans.xh == xh assert ans.yh == yh assert ans.xm == (xl + xh) // 2 assert ans.ym == (yl + yh) // 2 assert ans.w == xh - xl assert ans.h == yh - yl assert ans.get_immutable_key() == (xl, yl, xh, yh) assert ans.get_dim(Orient2D.x) == ans.w assert ans.get_dim(Orient2D.y) == ans.h assert ans.get_interval(Orient2D.x) == (xl, xh) assert ans.get_interval(Orient2D.y) == (yl, yh) """
def test_empty(): rtree = RTree() assert not rtree assert not rtree.bound_box.is_valid() with pytest.raises(IndexError): # noinspection PyStatementEffect rtree[0] with pytest.raises(IndexError): rtree.pop(0) objects = [obj for obj in rtree] assert not objects objects = [obj for obj in rtree.intersect_iter(BBox(0, 0, 1, 1))] assert not objects
def draw_layout(self): w: int = self.params['w'] h: int = self.params['h'] fill_layer: int = self.params['fill_layer'] grid = self.grid tech_info = grid.tech_info fill_info = tech_info.get_max_space_fill_info(fill_layer) self.set_size_from_bound_box(fill_layer, BBox(0, 0, w, h), round_up=True) bbox = self.bound_box tdir = grid.get_direction(fill_layer) pdir = tdir.perpendicular() margin = fill_info.get_margin(pdir) margin_le = fill_info.get_margin(tdir) sp_le = fill_info.get_space(tdir) dim = bbox.get_dim(pdir) dim_le = bbox.get_dim(tdir) tidxl = grid.coord_to_track(fill_layer, margin, mode=RoundMode.LESS_EQ) tidxr = grid.coord_to_track(fill_layer, dim - margin, mode=RoundMode.GREATER_EQ) wlen = grid.get_min_cont_length(fill_layer, 1) # fill inner self.add_wires(fill_layer, tidxl + 1, margin_le, dim_le - margin_le, num=tidxr - tidxl - 1) lower = margin_le self.add_wires(fill_layer, tidxl, lower, lower + wlen) lower += wlen + sp_le self.add_wires(fill_layer, tidxl, lower, lower + wlen) lower += wlen + sp_le + 2 self.add_wires(fill_layer, tidxl, lower, lower + wlen) lower += wlen + 2 * sp_le + wlen * 2 self.add_wires(fill_layer, tidxl, lower, lower + wlen) self.do_max_space_fill(fill_layer, bbox)
def draw_boundaries(self, master: MOSBase, top_layer: int, *, half_blk_x: bool = True, half_blk_y: bool = True) -> PyLayInstance: self._core = master tech_cls = master.tech_cls bbox = master.bound_box used_arr = master.used_array w_blk, h_blk = self.grid.get_block_size(top_layer, half_blk_x=half_blk_x, half_blk_y=half_blk_y) w_master = bbox.w h_master = bbox.h w_edge = tech_cls.get_edge_width(w_master, w_blk) base_end_info = tech_cls.get_mos_base_end_info(master.place_info, h_blk) # get top/bottom boundary delta/height num_tiles = used_arr.num_tiles idx_bot = int(used_arr.get_flip_tile(0)) idx_top = int(not used_arr.get_flip_tile(num_tiles - 1)) dy_bot = base_end_info.h_blk[idx_bot] dy_top = base_end_info.h_blk[idx_top] h_end_bot = base_end_info.h_mos_end[idx_bot] h_end_top = base_end_info.h_mos_end[idx_top] self._xform = Transform(w_edge, dy_bot) inst = self.add_instance(master, inst_name='X0', xform=self._xform) my_used_arr = used_arr.get_copy() sd_pitch = tech_cls.sd_pitch w_tot = w_edge * 2 + w_master h_tot = dy_bot + dy_top + h_master self._fill_space(master.grid, tech_cls, w_edge, my_used_arr, sd_pitch, w_tot, h_tot, dy_bot, h_end_bot, h_end_top) self.set_size_from_bound_box(top_layer, BBox(0, 0, w_tot, h_tot)) return inst
def draw_layout(self): w: int = self.params['w'] h: int = self.params['h'] fill_layer: int = self.params['fill_layer'] grid = self.grid tech_info = grid.tech_info fill_info = tech_info.get_max_space_fill_info(fill_layer) self.set_size_from_bound_box(fill_layer, BBox(0, 0, w, h), round_up=True) bbox = self.bound_box tdir = grid.get_direction(fill_layer) pdir = tdir.perpendicular() margin = fill_info.get_margin(pdir) margin_le = fill_info.get_margin(tdir) sp_le = fill_info.get_space(tdir) dim = bbox.get_dim(pdir) dim_le = bbox.get_dim(tdir) tidxl = grid.coord_to_track(fill_layer, margin, mode=RoundMode.LESS_EQ) tidxr = grid.coord_to_track(fill_layer, dim - margin, mode=RoundMode.GREATER_EQ) tidx_end = grid.coord_to_track(fill_layer, dim, mode=RoundMode.LESS) wlen = grid.get_min_cont_length(fill_layer, 1) # fill inner and transverse edges gap = margin_le + wlen + sp_le with open('debug.txt', 'w') as f: print(margin_le, wlen, sp_le, dim_le, file=f) self.add_wires(fill_layer, tidxl, gap, dim_le - gap, num=tidxr - tidxl + 1) self.add_wires(fill_layer, 0, margin_le, dim_le - margin_le) self.add_wires(fill_layer, tidx_end, margin_le, dim_le - margin_le) self.do_max_space_fill(fill_layer, bbox)
def bound_box(self) -> BBox: """BBox: the bounding box of this WireArray.""" tid = self._tid layer_id = tid.layer_id grid = tid.grid if grid is None: raise ValueError( 'Cannot computing WireArray bounding box without RoutingGrid.') lower, upper = grid.get_wire_bounds_htr(layer_id, tid.base_htr, tid.width) delta = (tid.num - 1) * int(tid.pitch * grid.get_track_pitch(layer_id)) if delta >= 0: upper += delta else: lower += delta return BBox(grid.get_direction(layer_id), self._lower, self._upper, lower, upper)
def draw_layout(self): w: int = self.params['width'] h: int = self.params['height'] edges: Tuple[Param, Param, Param, Param] = self.params['edges'] mos_type: str = self.params['mos_type'] threshold: str = self.params['threshold'] # draw fill grid = self.grid tech_cls: FillTech = grid.tech_info.get_device_tech('fill') # set size box = BBox(0, 0, w, h) self.prim_top_layer = grid.bot_layer self.array_box = self.prim_bound_box = box mos_type = mos_type or tech_cls.mos_type_default threshold = threshold or tech_cls.threshold_default info = tech_cls.get_fill_info(mos_type, threshold, w, h, edges[0], edges[1], edges[2], edges[3]) draw_layout_in_template(self, info)