# KLayout Layout Viewer
# Copyright (C) 2006-2025 Matthias Koefferlein
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


import pya
import unittest
import sys
import os

class DBLayoutTest(unittest.TestCase):

  # LayerInfo
  def test_1_Layout(self):

    lp = pya.LayerInfo()

    lp.name = "hallo"
    lp.layer = 5
    lp.datatype = 7
    self.assertEqual( lp.name, "hallo" )
    self.assertEqual( lp.layer, 5 )
    self.assertEqual( lp.datatype, 7 )
    self.assertEqual( pya.LayerInfo.from_string(lp.to_s()).to_s(), "hallo (5/7)" )

    lp.name = "x"
    self.assertEqual( lp.name, "x" )
    self.assertEqual( lp.layer, 5 )
    self.assertEqual( lp.datatype, 7 )

    lp.layer = 15
    self.assertEqual( lp.name, "x" )
    self.assertEqual( lp.layer, 15 )
    self.assertEqual( lp.datatype, 7 )

    lp.datatype = 3
    self.assertEqual( lp.name, "x" )
    self.assertEqual( lp.layer, 15 )
    self.assertEqual( lp.datatype, 3 )

    self.assertEqual( pya.LayerInfo.from_string("").to_s(), "" )
    self.assertEqual( pya.LayerInfo.from_string("name").to_s(), "name" )
    self.assertEqual( pya.LayerInfo.from_string("1").to_s(), "1/0" )
    self.assertEqual( pya.LayerInfo.from_string("name (17)").to_s(), "name (17/0)" )
    self.assertEqual( pya.LayerInfo.from_string("'1' (8/5)").to_s(), "'1' (8/5)" )

  # Basics: cells and instances
  def test_2_Layout(self):

    ly = pya.Layout()

    ly.dbu = 0.25
    self.assertEqual( ly.dbu, 0.25 )

    self.assertEqual( ly.has_cell("cell"), False )
    self.assertEqual( ly.cells(), 0 )

    ci = ly.add_cell( "new_cell" )
    self.assertEqual( ly.cells(), 1 )
    self.assertEqual( ly.has_cell("cell"), False )
    self.assertEqual( ly.has_cell("new_cell"), True )
    self.assertEqual( ly.cell_name(ci), "new_cell" )

    self.assertEqual( ly.cell_by_name("new_cell"), ci )
    self.assertEqual( ly.cell(ci).cell_index(), ci )
    self.assertEqual( ly.cell("new_cell").name, "new_cell" )
    self.assertEqual( repr(ly.cell("x")), "None" )
    lyc = ly.dup()
    self.assertEqual( lyc._to_const_object().cell("new_cell").name, "new_cell" )
    self.assertEqual( lyc._to_const_object().cell(ci).cell_index(), ci )

    ci2 = ly.add_cell( "new_cell_b" )
    self.assertEqual( ly.cells(), 2 )
    self.assertEqual( ly.cell_by_name("new_cell_b"), ci2 )
    self.assertEqual( sorted([ c.name for c in ly.cells("new*") ]), ['new_cell', 'new_cell_b'] )
    self.assertEqual( ci != ci2, True )
    lyc = ly.dup()
    self.assertEqual( sorted([ c.name for c in lyc._to_const_object().cells("new*") ]), ['new_cell', 'new_cell_b'] )

    ly.rename_cell( ci2, "x" )
    self.assertEqual( ly.cell_by_name("x"), ci2 )
    self.assertEqual( ly.cell_name(ci), "new_cell" )
    self.assertEqual( ly.cell_name(ci2), "x" )

    self.assertEqual( ly.under_construction(), False )
    ly.start_changes() 
    self.assertEqual( ly.under_construction(), True )
    ly.end_changes() 
    self.assertEqual( ly.under_construction(), False )

    li = pya.LayerInfo()
    li.layer = 16
    li.datatype = 1
    lindex = ly.insert_layer( li )
    self.assertEqual( ly.is_valid_layer( lindex ), True )
    self.assertEqual( ly.is_special_layer( lindex ), False )
    self.assertEqual( ly.is_valid_layer( 1234 ), False )

    li2 = ly.get_info( lindex ).dup()
    self.assertEqual( li2.layer, li.layer )
    self.assertEqual( li2.datatype, li.datatype )

    li2.layer = 17
    li2.datatype = 2

    ly.set_info( lindex, li2 )
    li3 = ly.get_info( lindex )
    self.assertEqual( li3.layer, 17 )
    self.assertEqual( li3.datatype, 2 )

    ly.delete_layer( lindex )
    self.assertEqual( ly.is_valid_layer( lindex ), False )

    arr = []
    for c in ly.each_cell():
      arr.append( c.cell_index() )
    self.assertEqual( arr, [ 0, 1 ] )

    arr = []
    for c in ly.each_top_cell():
      arr.append( c )
    self.assertEqual( arr, [ 0, 1 ] )

    arr = []
    for c in ly.each_cell_top_down():
      arr.append( c )
    self.assertEqual( arr, [ 0, 1 ] )

    arr = []
    for c in ly.each_cell_bottom_up():
      arr.append( c )
    self.assertEqual( arr, [ 1, 0 ] )

    ly.cell(ci2).insert( pya.CellInstArray( ci, pya.Trans( pya.Trans.R90, pya.Point( 1000, -500 ) ) ) )

    arr = []
    for c in ly.each_cell():
      arr.append( c.cell_index() )
    self.assertEqual( arr, [ 0, 1 ] )

    arr = []
    for c in ly.each_top_cell():
      arr.append( c )
    self.assertEqual( arr, [ 1 ] )

    arr = []
    for c in ly.each_cell_top_down():
      arr.append( c )
    self.assertEqual( arr, [ 1, 0 ] )

    arr = []
    for c in ly.each_cell_bottom_up():
      arr.append( c )
    self.assertEqual( arr, [ 0, 1 ] )

    self.assertEqual( ly.cell(ci2).is_top(), True )
    self.assertEqual( ly.cell(ci).is_top(), False )
    self.assertEqual( ly.cell(ci2).is_leaf(), False )
    self.assertEqual( ly.cell(ci).is_leaf(), True )

    ly.cell(ci2).clear_insts()

    arr = []
    for c in ly.each_cell():
      arr.append( c.cell_index() )
    self.assertEqual( arr, [ 0, 1 ] )

    arr = []
    for c in ly.each_top_cell():
      arr.append( c )
    self.assertEqual( arr, [ 0, 1 ] )

    arr = []
    for c in ly.each_cell_top_down():
      arr.append( c )
    self.assertEqual( arr, [ 0, 1 ] )

    arr = []
    for c in ly.each_cell_bottom_up():
      arr.append( c )
    self.assertEqual( arr, [ 1, 0 ] )

  # Instances and bboxes
  def test_5_Layout(self):

    ly = pya.Layout()

    ci1 = ly.add_cell( "c1" )
    ci2 = ly.add_cell( "c2" )

    linfo = pya.LayerInfo()
    linfo.layer = 16
    linfo.datatype = 1
    lindex = ly.insert_layer( linfo )

    linfo = pya.LayerInfo()
    linfo.layer = 16
    linfo.datatype = 2
    ldummy = ly.insert_layer( linfo )

    c1 = ly.cell( ci1 )
    c2 = ly.cell( ci2 )

    c1.shapes( lindex ).insert( pya.Box( 10, -10, 50, 40 ) )
    self.assertEqual( c1.bbox().to_s(), "(10,-10;50,40)" )
    self.assertEqual( c1.bbox_per_layer( lindex ).to_s(), "(10,-10;50,40)" )
    self.assertEqual( c1.bbox_per_layer( ldummy ).to_s(), "()" )

    c1.swap( lindex, ldummy )
    self.assertEqual( c1.bbox_per_layer( lindex ).to_s(), "()" )
    self.assertEqual( c1.bbox_per_layer( ldummy ).to_s(), "(10,-10;50,40)" )
    
    c1.clear( lindex )
    c1.clear( ldummy )
    self.assertEqual( c1.bbox_per_layer( lindex ).to_s(), "()" )
    self.assertEqual( c1.bbox_per_layer( ldummy ).to_s(), "()" )
    
    c1.shapes( lindex ).insert( pya.Box( 10, -10, 50, 40 ) )
    self.assertEqual( c1.bbox_per_layer( lindex ).to_s(), "(10,-10;50,40)" )
    self.assertEqual( c1.bbox_per_layer( ldummy ).to_s(), "()" )
    c1.clear_shapes()
    self.assertEqual( c1.bbox_per_layer( lindex ).to_s(), "()" )
    self.assertEqual( c1.bbox_per_layer( ldummy ).to_s(), "()" )

  # Instances and bboxes
  def test_6_Layout(self):

    ly = pya.Layout()

    ci1 = ly.add_cell( "c1" )
    ci2 = ly.add_cell( "c2" )

    linfo = pya.LayerInfo()
    linfo.layer = 16
    linfo.datatype = 1
    lindex = ly.insert_layer( linfo )

    linfo = pya.LayerInfo()
    linfo.layer = 16
    linfo.datatype = 2
    ldummy = ly.insert_layer( linfo )

    c1 = ly.cell( ci1 )
    c2 = ly.cell( ci2 )

    c1.shapes( lindex ).insert( pya.Box( 10, -10, 50, 40 ) )
    self.assertEqual( c1.bbox().to_s(), "(10,-10;50,40)" )

    tr = pya.Trans( pya.Trans.R90, pya.Point( 100, -50 ) ) 
    inst = pya.CellInstArray( c1.cell_index(), tr )
    self.assertEqual( inst.bbox( ly ).to_s(), c1.bbox().transformed(tr).to_s() )
    self.assertEqual( inst.bbox_per_layer( ly, lindex ).to_s(), c1.bbox().transformed(tr).to_s() )
    self.assertEqual( inst.bbox_per_layer( ly, ldummy ).to_s(), "()" )
    self.assertEqual( inst.size(), 1 )
    self.assertEqual( inst.is_complex(), False )
    c2.insert( inst )
    self.assertEqual( c2.bbox().to_s(), c1.bbox().transformed(tr).to_s() )

    c2.clear_insts()
    tr = pya.CplxTrans( 1.5, 90.0, True, pya.DPoint( 100, -50 ) ) 
    inst = pya.CellInstArray( c1.cell_index(), tr )
    inst2 = inst.dup()
    self.assertEqual( inst.bbox( ly ).to_s(), c1.bbox().transformed(tr).to_s() )
    self.assertEqual( inst.size(), 1 )
    self.assertEqual( inst.is_complex(), True )
    self.assertEqual( inst.is_regular_array(), False )
    c2.insert( inst )
    self.assertEqual( c2.bbox().to_s(), c1.bbox().transformed(tr).to_s() )

    c2.clear_insts()
    self.assertEqual( c2.bbox().to_s(), "()" )

    tr = pya.Trans( pya.Trans.R90, pya.Point( 100, -50 ) )
    inst = pya.CellInstArray( c1.cell_index(), tr, pya.Point( 100, 0 ), pya.Point( 0, 100 ), 10, 20 )
    self.assertEqual( inst == inst2, False )
    self.assertEqual( inst != inst2, True )
    inst2 = inst.dup()
    self.assertEqual( inst == inst2, True )
    self.assertEqual( inst != inst2, False )
    self.assertEqual( inst.bbox( ly ).to_s(), "(60,-40;1010,1900)" )
    self.assertEqual( inst.trans.to_s(), "r90 100,-50" )
    self.assertEqual( inst.cplx_trans.to_s(), "r90 *1 100,-50" )
    self.assertEqual( inst.size(), 200 )
    self.assertEqual( inst.is_complex(), False )
    self.assertEqual( inst.is_regular_array(), True )
    self.assertEqual( inst.a.to_s(), "100,0" )
    self.assertEqual( inst.b.to_s(), "0,100" )
    self.assertEqual( inst.na, 10 )
    self.assertEqual( inst.nb, 20 )
    self.assertEqual( inst.cell_index, c1.cell_index() )
    c2.insert( inst )
    self.assertEqual( c2.bbox().to_s(), "(60,-40;1010,1900)" )

    inst.invert()
    self.assertEqual( inst == inst2, False )
    self.assertEqual( inst != inst2, True )
    self.assertEqual( inst.bbox( ly ).to_s(), "(-1860,50;90,990)" )
    self.assertEqual( inst.size(), 200 )
    self.assertEqual( inst.is_complex(), False )
    self.assertEqual( inst.is_regular_array(), True )
    self.assertEqual( inst.a.to_s(), "0,100" )
    self.assertEqual( inst.b.to_s(), "-100,0" )
    self.assertEqual( inst.na, 10 )
    self.assertEqual( inst.nb, 20 )
    self.assertEqual( inst.cell_index, c1.cell_index() )

    if ly.is_editable():
      for inst in c2.each_inst():
        c2.erase( inst )
      self.assertEqual( c2.bbox().to_s(), "()" )
    else:
      c2.clear_insts()

    tr = pya.CplxTrans( 1.5, 90.0, True, pya.DPoint( 100, -50 ) ) 
    inst = pya.CellInstArray( c1.cell_index(), tr, pya.Point( 100, 0 ), pya.Point( 0, 100 ), 10, 20 )
    self.assertEqual( inst.bbox( ly ).to_s(), "(85,-35;1060,1925)" )
    self.assertEqual( inst.size(), 200 )
    self.assertEqual( inst.is_complex(), True )
    self.assertEqual( inst.cplx_trans.to_s(), "m45 *1.5 100,-50" )
    inst_ret = c2.insert( inst )
    self.assertEqual( c2.bbox().to_s(), "(85,-35;1060,1925)" )
    self.assertEqual( inst == inst_ret.cell_inst, True )
    self.assertEqual( inst_ret.prop_id, 0 )

    child_insts = [] 
    for i in c2.each_inst():
      child_insts.append( i )
    self.assertEqual( inst == child_insts[0].cell_inst, True )
    # in editable mode, the properties are present, but the id is 0:
    # therefore we do not check: self.assertEqual( inst_ret.has_prop_id, False )
    self.assertEqual( child_insts[0].prop_id, 0 )
    self.assertEqual( c2.child_instances(), 1 )

    arr = []
    for i in c2.each_inst():
      arr.append( i )
    self.assertEqual( len(arr), 1 )
    self.assertEqual( arr[0].cell_inst == inst, True )

    arr = []
    for i in c2.each_parent_inst():
      arr.append( i )
    self.assertEqual( len(arr), 0 )

    arr = []
    for i in c1.each_inst():
      arr.append( i )
    self.assertEqual( len(arr), 0 )

    arr = []
    for i in c1.each_parent_inst():
      arr.append( i )
    self.assertEqual( len(arr), 1 )

    self.assertEqual( arr[0].parent_cell_index(), c2.cell_index() )
    self.assertEqual( arr[0].child_inst().cell_index, c1.cell_index() )
    self.assertEqual( arr[0].inst().cell_index, c2.cell_index() )
    self.assertEqual( arr[0].inst().cplx_trans.to_s(), "m45 *0.666666667 33,-67" )
    
    arr = []
    for i in c2.each_overlapping_inst( pya.Box( 100, 0, 110, 10 ) ):
      arr.append( i )
    self.assertEqual( len(arr), 1 )
    self.assertEqual( arr[0].cell_inst == inst, True )

    arr = []
    for i in c2.each_overlapping_inst( pya.Box( -100, 0, -90, 10 ) ):
      arr.append( i )
    self.assertEqual( len(arr), 0 )

    arr = []
    for i in c2.each_touching_inst( pya.Box( 100, 0, 110, 10 ) ):
      arr.append( i )
    self.assertEqual( len(arr), 1 )
    self.assertEqual( arr[0].cell_inst == inst, True )

    arr = []
    for i in c2.each_touching_inst( pya.Box( -100, 0, -90, 10 ) ):
      arr.append( i )
    self.assertEqual( len(arr), 0 )

    arr = []
    for c in c2.each_child_cell():
      arr.append( ly.cell_name( c ) )
    self.assertEqual( arr, [ "c1" ] )
    arr = []
    for c in c2.each_parent_cell(): 
      arr.append( ly.cell_name( c ) )
    self.assertEqual( arr, [] )
    self.assertEqual( c2.child_cells(), 1 )
    self.assertEqual( c2.parent_cells(), 0 )
    self.assertEqual( c2.hierarchy_levels(), 1 )

    arr = []
    for c in c1.each_child_cell():
      arr.append( ly.cell_name( c ) )
    self.assertEqual( arr, [] )
    arr = []
    for c in c1.each_parent_cell():
      arr.append( ly.cell_name( c ) )
    self.assertEqual( arr, [ "c2" ] )
    self.assertEqual( c1.child_cells(), 0 )
    self.assertEqual( c1.parent_cells(), 1 )
    self.assertEqual( c1.hierarchy_levels(), 0 )


  # Instances and editable mode
  def test_6_EditableLayout(self):

    ly = pya.Layout( True )
    self.assertEqual( ly.is_editable(), True )

    ci1 = ly.add_cell( "c1" )
    ci2 = ly.add_cell( "c2" )

    c1 = ly.cell( ci1 )
    c2 = ly.cell( ci2 )

    tr = pya.CplxTrans( 1.5, 90.0, True, pya.DPoint( 100, -50 ) ) 
    inst = pya.CellInstArray( c1.cell_index(), tr )
    inst_ret = c2.insert( inst )
    self.assertEqual( inst_ret.trans.to_s(), "m45 100,-50" )
    inst = pya.CellInstArray( c1.cell_index(), pya.CplxTrans() )
    inst_ret.cell_inst = inst
    self.assertEqual( inst_ret.trans.to_s(), "r0 0,0" )

    manager = pya.Manager()

    ly = pya.Layout( True, manager )
    self.assertEqual( ly.is_editable(), True )

    ci1 = ly.add_cell( "c1" )
    ci2 = ly.add_cell( "c2" )

    c1 = ly.cell( ci1 )
    c2 = ly.cell( ci2 )

    tr = pya.CplxTrans( 1.5, 90.0, True, pya.DPoint( 100, -50 ) ) 
    inst = pya.CellInstArray( c1.cell_index(), tr )
    inst_ret = c2.insert( inst )
    self.assertEqual( inst_ret.trans.to_s(), "m45 100,-50" )
    manager.transaction( "trans" )
    inst = pya.CellInstArray( c1.cell_index(), pya.CplxTrans() )
    inst_ret.cell_inst = inst
    self.assertEqual( inst_ret.trans.to_s(), "r0 0,0" )
    manager.commit()
    manager.undo()
    c2 = ly.cell( ci2 )
    for i in c2.each_inst():
      inst_ret = i
      break
    self.assertEqual( inst_ret.trans.to_s(), "m45 100,-50" )

    manager = None

    ly = pya.Layout( False )
    self.assertEqual( ly.is_editable(), False )

    ci1 = ly.add_cell( "c1" )
    ci2 = ly.add_cell( "c2" )

    c1 = ly.cell( ci1 )
    c2 = ly.cell( ci2 )

    tr = pya.CplxTrans( 1.5, 90.0, True, pya.DPoint( 100, -50 ) ) 
    inst = pya.CellInstArray( c1.cell_index(), tr )
    inst_ret = c2.insert( inst )
    self.assertEqual( inst_ret.trans.to_s(), "m45 100,-50" )
    inst = pya.CellInstArray( c1.cell_index(), pya.CplxTrans() )
    error = True
    try:
      # should raise an error - non-editable layout does not support replacement of instances
      inst_ret.cell_inst = inst
      error = False
    except:
      pass
    self.assertEqual( error, True )

  # Instances and bboxes
  def test_6_Layout_props(self):

    ly = pya.Layout()
    pv = [ [ 17, "a" ], [ "b", [ 1, 5, 7 ] ] ]
    pid = ly.properties_id( pv )

    ci1 = ly.add_cell( "c1" )
    ci2 = ly.add_cell( "c2" )

    linfo = pya.LayerInfo()
    linfo.layer = 16
    linfo.datatype = 1
    lindex = ly.insert_layer( linfo )

    linfo = pya.LayerInfo()
    linfo.layer = 16
    linfo.datatype = 2
    ldummy = ly.insert_layer( linfo )

    c1 = ly.cell( ci1 )
    c2 = ly.cell( ci2 )

    c1.shapes( lindex ).insert( pya.Box( 10, -10, 50, 40 ), pid )
    self.assertEqual( c1.bbox().to_s(), "(10,-10;50,40)" )

    tr = pya.Trans( pya.Trans.R90, pya.Point( 100, -50 ) ) 
    inst = pya.CellInstArray( c1.cell_index(), tr )
    self.assertEqual( inst.bbox( ly ).to_s(), c1.bbox().transformed(tr).to_s() )
    self.assertEqual( inst.bbox_per_layer( ly, lindex ).to_s(), c1.bbox().transformed(tr).to_s() )
    self.assertEqual( inst.bbox_per_layer( ly, ldummy ).to_s(), "()" )
    self.assertEqual( inst.size(), 1 )
    self.assertEqual( inst.is_complex(), False )
    c2.insert( inst, pid )
    self.assertEqual( c2.bbox().to_s(), c1.bbox().transformed(tr).to_s() )

    c2.clear_insts()
    tr = pya.CplxTrans( 1.5, 90.0, True, pya.DPoint( 100, -50 ) ) 
    inst = pya.CellInstArray( c1.cell_index(), tr )
    inst2 = inst.dup()
    self.assertEqual( inst.bbox( ly ).to_s(), c1.bbox().transformed(tr).to_s() )
    self.assertEqual( inst.size(), 1 )
    self.assertEqual( inst.is_complex(), True )
    self.assertEqual( inst.is_regular_array(), False )
    c2.insert( inst, pid )
    self.assertEqual( c2.bbox().to_s(), c1.bbox().transformed(tr).to_s() )

    c2.clear_insts()
    self.assertEqual( c2.bbox().to_s(), "()" )

    tr = pya.Trans( pya.Trans.R90, pya.Point( 100, -50 ) )
    inst = pya.CellInstArray( c1.cell_index(), tr, pya.Point( 100, 0 ), pya.Point( 0, 100 ), 10, 20 )
    self.assertEqual( inst == inst2, False )
    self.assertEqual( inst != inst2, True )
    inst2 = inst.dup()
    self.assertEqual( inst == inst2, True )
    self.assertEqual( inst != inst2, False )
    self.assertEqual( inst.bbox( ly ).to_s(), "(60,-40;1010,1900)" )
    self.assertEqual( inst.trans.to_s(), "r90 100,-50" )
    self.assertEqual( inst.cplx_trans.to_s(), "r90 *1 100,-50" )
    self.assertEqual( inst.size(), 200 )
    self.assertEqual( inst.is_complex(), False )
    self.assertEqual( inst.is_regular_array(), True )
    self.assertEqual( inst.a.to_s(), "100,0" )
    self.assertEqual( inst.b.to_s(), "0,100" )
    self.assertEqual( inst.na, 10 )
    self.assertEqual( inst.nb, 20 )
    self.assertEqual( inst.cell_index, c1.cell_index() )
    c2.insert( inst, pid )
    self.assertEqual( c2.bbox().to_s(), "(60,-40;1010,1900)" )

    inst.invert()
    self.assertEqual( inst == inst2, False )
    self.assertEqual( inst != inst2, True )
    self.assertEqual( inst.bbox( ly ).to_s(), "(-1860,50;90,990)" )
    self.assertEqual( inst.size(), 200 )
    self.assertEqual( inst.is_complex(), False )
    self.assertEqual( inst.is_regular_array(), True )
    self.assertEqual( inst.a.to_s(), "0,100" )
    self.assertEqual( inst.b.to_s(), "-100,0" )
    self.assertEqual( inst.na, 10 )
    self.assertEqual( inst.nb, 20 )
    self.assertEqual( inst.cell_index, c1.cell_index() )

    if ly.is_editable():
      for inst in c2.each_inst():
        c2.erase( inst ) 
      self.assertEqual( c2.bbox().to_s(), "()" )
    else:
      c2.clear_insts()
    self.assertEqual( c2.bbox().to_s(), "()" )

    tr = pya.CplxTrans( 1.5, 90.0, True, pya.DPoint( 100, -50 ) ) 
    inst = pya.CellInstArray( c1.cell_index(), tr, pya.Point( 100, 0 ), pya.Point( 0, 100 ), 10, 20 )
    self.assertEqual( inst.bbox( ly ).to_s(), "(85,-35;1060,1925)" )
    self.assertEqual( inst.size(), 200 )
    self.assertEqual( inst.is_complex(), True )
    self.assertEqual( inst.cplx_trans.to_s(), "m45 *1.5 100,-50" )
    inst_ret = c2.insert( inst, pid )
    self.assertEqual( c2.bbox().to_s(), "(85,-35;1060,1925)" )
    self.assertEqual( inst == inst_ret.cell_inst, True )
    self.assertEqual( inst_ret.has_prop_id(), True )
    self.assertEqual( inst_ret.prop_id, pid )

    child_insts = [] 
    for i in c2.each_inst():
      child_insts.append( i )
    self.assertEqual( inst == child_insts[0].cell_inst, True )
    self.assertEqual( child_insts[0].has_prop_id(), True )
    self.assertEqual( child_insts[0].prop_id, pid )
    self.assertEqual( c2.child_instances(), 1 )

    arr = []
    for i in c2.each_inst():
      arr.append( i ) 
    self.assertEqual( len(arr), 1 )
    self.assertEqual( arr[0].cell_inst == inst, True )
    self.assertEqual( arr[0].prop_id == pid, True )

    arr = []
    for i in c2.each_parent_inst():
      arr.append( i )
    self.assertEqual( len(arr), 0 )

    arr = []
    for i in c1.each_inst():
      arr.append( i )
    self.assertEqual( len(arr), 0 )

    arr = []
    for i in c1.each_parent_inst():
      arr.append( i )
    self.assertEqual( len(arr), 1 )

    self.assertEqual( arr[0].parent_cell_index(), c2.cell_index() )
    self.assertEqual( arr[0].child_inst().cell_index, c1.cell_index() )
    self.assertEqual( arr[0].inst().cell_index, c2.cell_index() )
    self.assertEqual( arr[0].inst().cplx_trans.to_s(), "m45 *0.666666667 33,-67" )
    self.assertEqual( arr[0].child_inst().prop_id, pid )
    
    arr = []
    for i in c2.each_overlapping_inst( pya.Box( 100, 0, 110, 10 ) ):
      arr.append( i )
    self.assertEqual( len(arr), 1 )
    self.assertEqual( arr[0].cell_inst == inst, True )
    self.assertEqual( arr[0].prop_id == pid, True )

    arr = []
    for i in c2.each_overlapping_inst( pya.Box( -100, 0, -90, 10 ) ):
      arr.append( i )
    self.assertEqual( len(arr), 0 )

    arr = []
    for i in c2.each_touching_inst( pya.Box( 100, 0, 110, 10 ) ):
      arr.append( i )
    self.assertEqual( len(arr), 1 )
    self.assertEqual( arr[0].cell_inst == inst, True )
    self.assertEqual( arr[0].prop_id == pid, True )

    arr = []
    for i in c2.each_touching_inst( pya.Box( -100, 0, -90, 10 ) ):
      arr.append( i )
    self.assertEqual( len(arr), 0 )

    arr = []
    for c in c2.each_child_cell():
      arr.append( ly.cell_name( c ) )
    self.assertEqual( arr, [ "c1" ] )
    arr = []
    for c in c2.each_parent_cell():
      arr.append( ly.cell_name( c ) )
    self.assertEqual( arr, [] )
    self.assertEqual( c2.child_cells(), 1 )
    self.assertEqual( c2.parent_cells(), 0 )
    self.assertEqual( c2.hierarchy_levels(), 1 )

    arr = []
    for c in c1.each_child_cell():
      arr.append( ly.cell_name( c ) )
    self.assertEqual( arr, [] )
    arr = []
    for c in c1.each_parent_cell():
      arr.append( ly.cell_name( c ) )
    self.assertEqual( arr, [ "c2" ] )
    self.assertEqual( c1.child_cells(), 0 )
    self.assertEqual( c1.parent_cells(), 1 )
    self.assertEqual( c1.hierarchy_levels(), 0 )


  # Properties
  def test_6_Layout_props2(self):

    ly = pya.Layout(True)
    pv = [ [ 17, "a" ], [ "b", [ 1, 5, 7 ] ] ]
    pid = ly.properties_id( pv )
    # does not work? @@@
    # pv = { 17: "a", "b": [ 1, 5, 7 ] }
    # pid2 = ly.properties_id( pv )
    # self.assertEqual( pid, pid2 )

    ci1 = ly.add_cell( "c1" )
    ci2 = ly.add_cell( "c2" )

    linfo = pya.LayerInfo()
    linfo.layer = 16
    linfo.datatype = 1
    lindex = ly.insert_layer( linfo )

    c1 = ly.cell( ci1 )
    c2 = ly.cell( ci2 )
    tr = pya.Trans()
    inst = c2.insert( pya.CellInstArray( c1.cell_index(), tr ) )
    self.assertEqual( inst.parent_cell.name, c2.name )
    self.assertEqual( inst.cell.name, c1.name )
    self.assertEqual( repr(inst.layout()), repr(ly) )

    s1 = c1.shapes( lindex ).insert( pya.Box( 10, -10, 50, 40 ), pid )
    s2 = c1.shapes( lindex ).insert( pya.Box( 10, -10, 50, 40 ) )
    self.assertEqual( repr(s1.property( 17 )), "\'a\'" )
    s1.set_property( 5, 23 ) 
    s1.delete_property( 17 )
    self.assertEqual( repr(s1.property( 17 )), "None" )
    self.assertEqual( str(s1.property( 5 )), "23" )
    self.assertEqual( repr(s2.property( 17 )), "None" )

    self.assertEqual( repr(inst.property( "a" )), "None" )
    inst.set_property( "a", 33 )
    self.assertEqual( str(inst.property( "a" )), "33" )
    inst.delete_property( "a" )
    self.assertEqual( repr(inst.property( "a" )), "None" )

    # cell properties
    self.assertEqual( repr(c1.property( 17 )), "None" )
    c1.prop_id = pid
    self.assertEqual( c1.prop_id, pid )
    self.assertEqual( repr(c1.property( 17 )), "\'a\'" )
    c1.set_property( 5, 23 ) 
    c1.delete_property( 17 )
    self.assertEqual( repr(c1.property( 17 )), "None" )
    self.assertEqual( str(c1.property( 5 )), "23" )


  # Instances and bboxes (editable mode)
  def test_6_Layout_new(self):

    ly = pya.Layout()
    if ly.is_editable():

      pv = { 17: "a", "b": [ 1, 5, 7 ] }
      pid1 = ly.properties_id( pv )
      pv = { 100: "x" }
      pid2 = ly.properties_id( pv )

      ci1 = ly.add_cell( "c1" )
      ci2 = ly.add_cell( "c2" )
      ci3 = ly.add_cell( "c3" )
      c1 = ly.cell( ci1 )
      c2 = ly.cell( ci2 )
      c3 = ly.cell( ci3 )

      tr = pya.Trans( pya.Trans.R90, pya.Point( 100, -50 ) ) 
      inst_1 = pya.CellInstArray( c1.cell_index(), tr )
      new_inst_1 = c2.insert( inst_1, pid1 )
      new_inst_2 = c2.insert( inst_1, pid2 )
      inst_2 = pya.CellInstArray( c3.cell_index(), tr*tr )
      new_inst_3 = c1.insert( inst_2 )

      self.assertEqual( new_inst_1.cell_index, c1.cell_index() )
      self.assertEqual( str(new_inst_1.trans), str(tr) )
      self.assertEqual( new_inst_1.prop_id, pid1 )
      self.assertEqual( new_inst_2.cell_index, c1.cell_index() )
      self.assertEqual( str(new_inst_2.trans), str(tr) )
      self.assertEqual( new_inst_2.prop_id, pid2 )
      self.assertEqual( new_inst_3.cell_index, c3.cell_index() )
      self.assertEqual( new_inst_3.prop_id, 0 )
      self.assertEqual( str(new_inst_3.trans), str(tr*tr) )

      new_inst_3 = c1.replace_prop_id( new_inst_3, pid2 )
    
      self.assertEqual( new_inst_1.cell_index, c1.cell_index() )
      self.assertEqual( str(new_inst_1.trans), str(tr) )
      self.assertEqual( new_inst_1.prop_id, pid1 )
      self.assertEqual( new_inst_2.cell_index, c1.cell_index() )
      self.assertEqual( str(new_inst_2.trans), str(tr) )
      self.assertEqual( new_inst_2.prop_id, pid2 )
      self.assertEqual( new_inst_3.cell_index, c3.cell_index() )
      self.assertEqual( new_inst_3.prop_id, pid2 )
      self.assertEqual( str(new_inst_3.trans), str(tr*tr) )

      try:
        new_inst_1 = c1.replace( new_inst_1, inst_2 )
        self.assertEqual( True, False )
      except:
        # OK: gives an error since we are trying to erase an object from a list that is does not belong to
        pass

      new_inst_1 = c2.replace( new_inst_1, inst_2 )
    
      self.assertEqual( new_inst_1.cell_index, c3.cell_index() )
      self.assertEqual( str(new_inst_1.trans), str(tr*tr) )
      self.assertEqual( new_inst_1.prop_id, pid1 )
      self.assertEqual( new_inst_2.cell_index, c1.cell_index() )
      self.assertEqual( str(new_inst_2.trans), str(tr) )
      self.assertEqual( new_inst_2.prop_id, pid2 )
      self.assertEqual( new_inst_3.cell_index, c3.cell_index() )
      self.assertEqual( new_inst_3.prop_id, pid2 )
      self.assertEqual( str(new_inst_3.trans), str(tr*tr) )

      new_inst_1 = c2.replace( new_inst_1, inst_2, pid1 )
    
      self.assertEqual( new_inst_1.cell_index, c3.cell_index() )
      self.assertEqual( str(new_inst_1.trans), str(tr*tr) )
      self.assertEqual( new_inst_1.prop_id, pid1 )
      self.assertEqual( new_inst_2.cell_index, c1.cell_index() )
      self.assertEqual( str(new_inst_2.trans), str(tr) )
      self.assertEqual( new_inst_2.prop_id, pid2 )
      self.assertEqual( new_inst_3.cell_index, c3.cell_index() )
      self.assertEqual( new_inst_3.prop_id, pid2 )
      self.assertEqual( str(new_inst_3.trans), str(tr*tr) )

      self.assertEqual( new_inst_1.is_null(), False )
      self.assertEqual( pya.Instance().is_null(), True )

      self.assertEqual( c2.is_leaf(), False )
      c2.erase( new_inst_1 )
      c2.erase( new_inst_2 )
      self.assertEqual( c2.is_leaf(), True )
      self.assertEqual( c2.child_instances(), 0 )

  def shapes_to_s(self, ly, shapes):
    r = ""
    for s in shapes.each():
      if r != "":
        r += ";"
      r += s.to_s()
    return r

  # Copy/move between cells
  def test_7_cells_copy_move(self):

    ly1 = pya.Layout()

    # because of set_property ...
    if not ly1.is_editable():
      return

    la1 = ly1.insert_layer(pya.LayerInfo(1, 0))
    lb1 = ly1.insert_layer(pya.LayerInfo(2, 0))
    ly1.dbu = 0.001
    ca1 = ly1.cell(ly1.add_cell("a"))
    cb1 = ly1.cell(ly1.add_cell("b"))

    s1 = ca1.shapes(la1).insert(pya.Box(0, 500, 1000, 2000))
    s1.set_property(17, 5.0)
    s1.set_property(17, "hallo")
      
    ca1.copy(la1, lb1)
    cb1.copy(ca1, la1, lb1)
    self.assertEqual(self.shapes_to_s(ly1, ca1.shapes(la1)), "box (0,500;1000,2000) props={17=>hallo}")
    self.assertEqual(self.shapes_to_s(ly1, ca1.shapes(lb1)), "box (0,500;1000,2000) props={17=>hallo}")
    self.assertEqual(self.shapes_to_s(ly1, cb1.shapes(lb1)), "box (0,500;1000,2000) props={17=>hallo}")
    self.assertEqual(self.shapes_to_s(ly1, cb1.shapes(la1)), "")

    ly2 = pya.Layout()
    la2 = ly2.insert_layer(pya.LayerInfo(10, 0))
    lb2 = ly2.insert_layer(pya.LayerInfo(11, 0))
    ly2.dbu = 0.0005
    ca2 = ly2.cell(ly2.add_cell("a"))
    cb2 = ly2.cell(ly2.add_cell("b"))

    ca2.copy(ca1, la1, lb2)
    self.assertEqual(self.shapes_to_s(ly2, ca2.shapes(lb2)), "box (0,1000;2000,4000) props={17=>hallo}")
    self.assertEqual(self.shapes_to_s(ly1, ca1.shapes(la1)), "box (0,500;1000,2000) props={17=>hallo}")

    # move
    ca1.clear()
    cb1.clear()
    ca2.clear()
    cb2.clear()
    
    s1 = ca1.shapes(la1).insert(pya.Box(0, 500, 1000, 2000))
    s1.set_property(17, 5.0)
    s1.set_property(17, "hallo")
    self.assertEqual(self.shapes_to_s(ly1, ca1.shapes(la1)), "box (0,500;1000,2000) props={17=>hallo}")
    self.assertEqual(self.shapes_to_s(ly1, ca1.shapes(lb1)), "")
    ca1.move(la1, lb1)
    self.assertEqual(self.shapes_to_s(ly1, ca1.shapes(la1)), "")
    self.assertEqual(self.shapes_to_s(ly1, ca1.shapes(lb1)), "box (0,500;1000,2000) props={17=>hallo}")

    cb1.move(ca1, lb1, lb1)
    self.assertEqual(self.shapes_to_s(ly1, ca1.shapes(la1)), "")
    self.assertEqual(self.shapes_to_s(ly1, ca1.shapes(lb1)), "")
    self.assertEqual(self.shapes_to_s(ly1, cb1.shapes(lb1)), "box (0,500;1000,2000) props={17=>hallo}")
    self.assertEqual(self.shapes_to_s(ly1, cb1.shapes(la1)), "")

    ly2 = pya.Layout()
    la2 = ly2.insert_layer(pya.LayerInfo(10, 0))
    lb2 = ly2.insert_layer(pya.LayerInfo(11, 0))
    ly2.dbu = 0.0005
    ca2 = ly2.cell(ly2.add_cell("a"))
    cb2 = ly2.cell(ly2.add_cell("b"))

    ca2.move(cb1, lb1, lb2)
    self.assertEqual(self.shapes_to_s(ly1, ca1.shapes(la1)), "")
    self.assertEqual(self.shapes_to_s(ly1, ca1.shapes(lb1)), "")
    self.assertEqual(self.shapes_to_s(ly1, cb1.shapes(lb1)), "")
    self.assertEqual(self.shapes_to_s(ly1, cb1.shapes(la1)), "")
    self.assertEqual(self.shapes_to_s(ly2, ca2.shapes(lb2)), "box (0,1000;2000,4000) props={17=>hallo}")
    
  # top cells
  def test_8(self):

    l = pya.Layout()
    tc = []
    for t in l.each_top_cell():
      tc.append(l.cell(t).name())
    self.assertEqual(",".join(tc), "")
    self.assertEqual(repr(l.top_cell()), "None")
    self.assertEqual(len(l.top_cells()), 0)

    c0 = l.create_cell("c0")
    self.assertEqual(c0.name, "c0")
    tc = []
    for t in l.each_top_cell():
      tc.append(l.cell(t).name) 
    self.assertEqual(",".join(tc), "c0")
    self.assertEqual(l.top_cell().name, "c0")
    lc = l.dup()
    self.assertEqual(lc._to_const_object().top_cell().name, "c0")  # const version
    tc = []
    for t in l.top_cells():
      tc.append(t.name) 
    self.assertEqual(",".join(tc), "c0")

    c1 = l.create_cell("c1")
    self.assertEqual(c1.name, "c1")
    tc = []
    for t in l.each_top_cell():
      tc.append(l.cell(t).name)
    self.assertEqual(",".join(tc), "c0,c1")
    error = False
    try:
      self.assertEqual(l.top_cell.inspect, "never-true")
    except:
      error = True
    self.assertEqual(error, True)
    tc = []
    for t in l.top_cells():
      tc.append(t.name) 
    self.assertEqual(",".join(tc), "c0,c1")
    tc = []
    lc = l.dup()
    for t in lc._to_const_object().top_cells(): # const version
      tc.append(t.name) 
    self.assertEqual(",".join(tc), "c0,c1")

    c2 = l.create_cell("c1")
    self.assertEqual(c2.name, "c1$1")

  # under construction and update
  def test_9(self):

    ly = pya.Layout()
    l1 = ly.insert_layer(pya.LayerInfo(1, 0))
    c1 = ly.create_cell("a")
    self.assertEqual(ly.under_construction(), False)

    s1 = c1.shapes(l1).insert(pya.Box(0, 500, 1000, 2000))
    self.assertEqual(c1.bbox().to_s(), "(0,500;1000,2000)")

    ly = pya.Layout()
    ly.start_changes()
    self.assertEqual(ly.under_construction(), True)
    ly.start_changes()
    self.assertEqual(ly.under_construction(), True)
    l1 = ly.insert_layer(pya.LayerInfo(1, 0))
    c1 = ly.create_cell("a")
    s1 = c1.shapes(l1).insert(pya.Box(0, 500, 1000, 2000))
    self.assertEqual(c1.bbox().to_s(), "()")
    # while under_construction, an explicit update is required to update the cell's bbox
    ly.update()
    self.assertEqual(c1.bbox().to_s(), "(0,500;1000,2000)")
    ly.end_changes()
    self.assertEqual(ly.under_construction(), True)
    ly.end_changes()
    self.assertEqual(ly.under_construction(), False)

  # Instance editing
  def test_10(self):

    ly = pya.Layout()

    ci1 = ly.add_cell("c1")
    ci2 = ly.add_cell("c2")
    ci3 = ly.add_cell("c3")

    c1 = ly.cell(ci1)
    c2 = ly.cell(ci2)
    c3 = ly.cell(ci3)

    tr = pya.Trans(pya.Trans.R90, pya.Point(100, -50)) 
    i1 = c1.insert(pya.CellInstArray(c2.cell_index(), tr))

    sv = []
    for i in c1.each_inst():
      sv.append(i.to_s(True))
    self.assertEqual(";".join(sv), "c2 r90 100,-50")

    sv = []
    for i in c2.each_inst():
      sv.append(i.to_s(True))
    self.assertEqual(";".join(sv), "")

    sv = []
    for i in c3.each_inst():
      sv.append(i.to_s(True))
    self.assertEqual(";".join(sv), "")

    if ly.is_editable():

      i1.cell_index = ci3

      sv = []
      for i in c1.each_inst():
        sv.append(i.to_s(True))
      self.assertEqual(";".join(sv), "c3 r90 100,-50")

      sv = []
      for i in c2.each_inst():
        sv.append(i.to_s(True))
      self.assertEqual(";".join(sv), "")

      sv = []
      for i in c3.each_inst():
        sv.append(i.to_s(True))
      self.assertEqual(";".join(sv), "")

      i1.cell = c2

      sv = []
      for i in c1.each_inst():
        sv.append(i.to_s(True))
      self.assertEqual(";".join(sv), "c2 r90 100,-50")

      sv = []
      for i in c2.each_inst():
        sv.append(i.to_s(True))
      self.assertEqual(";".join(sv), "")

      sv = []
      for i in c3.each_inst():
        sv.append(i.to_s(True))
      self.assertEqual(";".join(sv), "")

      self.assertEqual(i1.parent_cell.name, "c1")

      i1.cell = c1
      i1.parent_cell = c2

      sv = []
      for i in c1.each_inst():
        sv.append(i.to_s(True))
      self.assertEqual(";".join(sv), "")

      sv = []
      for i in c2.each_inst():
        sv.append(i.to_s(True))
      self.assertEqual(";".join(sv), "c1 r90 100,-50")

      sv = []
      for i in c3.each_inst():
        sv.append(i.to_s(True))
      self.assertEqual(";".join(sv), "")

  # User properties
  def test_11(self):

    ly = pya.Layout()

    pid1 = ly.properties_id({ "x": 1 })
    pid2 = ly.properties_id({ "x": 17 })

    self.assertEqual(ly.prop_id, 0)
    ly.prop_id = pid1
    self.assertEqual(ly.prop_id, pid1)
    ly.prop_id = 0
    self.assertEqual(ly.prop_id, 0)

    ly.set_property("x", 1)
    self.assertEqual(ly.prop_id, pid1)
    self.assertEqual(ly.property("x"), 1)
    ly.set_property("x", 17)
    self.assertEqual(ly.prop_id, pid2)
    self.assertEqual(ly.property("x"), 17)
    self.assertEqual(ly.property("y"), None)

    ly.delete_property("x")
    self.assertEqual(ly.property("x"), None)

  # Performance test
  def test_13(self):

    n = 100
    w = 10000

    ly = pya.Layout()
    l1 = ly.layer(1, 0)
    top = ly.create_cell("TOP")

    ix = 0
    while ix < n:
      sys.stdout.write(str(ix) + "/" + str(n) + "\n")
      sys.stdout.flush()
      iy = 0
      while iy < n:
        x = ix * w
        y = iy * w
        cell = ly.create_cell("X" + str(ix) + "Y" + str(iy))
        cell.shapes(l1).insert(pya.Box(0, 0, w, w))
        top.insert(pya.CellInstArray(cell.cell_index(), pya.Trans(pya.Point(ix * w, iy * w))))
        iy += 1
      ix += 1

    ly._destroy()

  # Bug #109
  def test_bug109(self):

    testtmp = os.getenv("TESTTMP_WITH_NAME", os.getenv("TESTTMP", "."))
    
    file_gds = os.path.join(testtmp, "bug109.gds")
    file_oas = os.path.join(testtmp, "bug109.oas")

    ly = pya.Layout()
    top = ly.create_cell("TOP")
    l1 = ly.layer(1, 0)
    shape = top.shapes(l1).insert(pya.Box(0, 10, 20, 30))
    shape.set_property(2, "hello, world")
    shape.set_property("42", "the answer")

    ly.write(file_gds)

    opt = pya.SaveLayoutOptions()
    opt.format = "OASIS"
    opt.oasis_strict_mode = False
    ly.write(file_oas, opt)

    ly2 = pya.Layout()
    ly2.read(file_gds)
    l2 = ly2.layer(1, 0)
    shape = None
    for s in ly2.top_cell().shapes(l2).each():
      shape = s
    self.assertEqual(shape.property(2), "hello, world")
    self.assertEqual(shape.property("2"), None)
    self.assertEqual(shape.property(2.0), "hello, world")
    self.assertEqual(shape.property(22), None)
    self.assertEqual(shape.property(42), "the answer")
    self.assertEqual(shape.property("42"), None)
    self.assertEqual(shape.property(42.0), "the answer")

    ly2 = pya.Layout()
    ly2.read(file_oas)
    l2 = ly2.layer(1, 0)
    shape = None
    for s in ly2.top_cell().shapes(l2).each():
      shape = s
    self.assertEqual(shape.property(2), "hello, world")
    self.assertEqual(shape.property("2"), None)
    self.assertEqual(shape.property(2.0), "hello, world")
    self.assertEqual(shape.property(22), None)
    self.assertEqual(shape.property("42"), "the answer")
    self.assertEqual(shape.property(42), None)
    self.assertEqual(shape.property(42.0), None)

  # Bug #1397
  def test_bug1397(self):

    testtmp = os.getenv("TESTTMP_WITH_NAME", os.getenv("TESTTMP", "."))
    tmp = os.path.join(testtmp, "tmp.gds")

    l = pya.Layout()

    c = l.create_cell("test_cell")

    li = pya.LayerInfo(1, 0)
    t = pya.Trans.R180
    c.add_meta_info(pya.LayoutMetaInfo("kfactory:li", li, None, True))
    c.add_meta_info(pya.LayoutMetaInfo("kfactory:t", t, None, True))

    l.write(tmp)

    l2 = pya.Layout()
    l2.read(tmp)

    c2 = l2.cell("test_cell")

    li = c2.meta_info("kfactory:li").value
    self.assertEqual(li.layer, 1)
    self.assertEqual(li.datatype, 0)

    t = c2.meta_info("kfactory:t").value
    self.assertEqual(str(t), "r180 0,0")
    

  def test_read_bytes(self):

    testtmp = os.getenv("TESTTMP_WITH_NAME", os.getenv("TESTTMP", "."))

    file_gds = os.path.join(testtmp, "bytes.gds")

    ly = pya.Layout()
    top = ly.create_cell("TOP")
    l1 = ly.layer(1, 0)
    shape = top.shapes(l1).insert(pya.Box(0, 10, 20, 30))
    ly.write(file_gds)

    with open(file_gds, "rb") as f:
      byte_buffer = f.read()

    ly2 = pya.Layout()
    ly2.read_bytes(byte_buffer, pya.LoadLayoutOptions())
    l2 = ly2.layer(1, 0)
    self.assertEqual(ly2.top_cell().bbox().to_s(), "(0,10;20,30)")

  def test_write_bytes(self):

    ly = pya.Layout()
    top = ly.create_cell("TOP")
    l1 = ly.layer(1, 0)
    shape = top.shapes(l1).insert(pya.Box(0, 10, 20, 30))
    options = pya.SaveLayoutOptions()
    options.format = "GDS2"
    byte_buffer = ly.write_bytes(options)

    ly2 = pya.Layout()
    ly2.read_bytes(byte_buffer)
    l2 = ly2.layer(1, 0)
    self.assertEqual(ly2.top_cell().bbox().to_s(), "(0,10;20,30)")

  # Properties IDs
  def test_issue1549(self):

    ly = pya.Layout.new()

    ps1 = { 1: "one", "key": 17 }
    ps2 = [ ( 2, "two" ), ( "key", 42 ) ]

    pid1 = pya.Layout.properties_id(ps1)
    # deprecated, for backward compatibility:
    self.assertEqual(ly.properties_array(pid1), [[1, 'one'], ['key', 17]])
    self.assertEqual(ly.properties_hash(pid1), {1: 'one', 'key': 17})
    self.assertEqual(pid1, ly.properties_id(ps1))
    # static method versions
    self.assertEqual(pya.Layout.properties_array(pid1), [[1, 'one'], ['key', 17]])
    self.assertEqual(pya.Layout.properties_hash(pid1), {1: 'one', 'key': 17})
    self.assertEqual(pya.Layout.property(pid1, 42).__repr__(), "None")
    self.assertEqual(pya.Layout.property(pid1, 1).__repr__(), "'one'")

    pid2 = pya.Layout.properties_id(ps2)
    # deprecated, for backward compatibility:
    self.assertEqual(pid2, ly.properties_id(ps2))
    self.assertEqual(ly.properties_array(pid2), [[2, 'two'], ['key', 42]])
    self.assertEqual(ly.properties_hash(pid2), {2: 'two', 'key': 42})
    # static method versions
    self.assertEqual(pya.Layout.properties_array(pid2), [[2, 'two'], ['key', 42]])
    self.assertEqual(pya.Layout.properties_hash(pid2), {2: 'two', 'key': 42})
    self.assertEqual(pya.Layout.property(pid2, 42).__repr__(), "None")
    self.assertEqual(pya.Layout.property(pid2, 2).__repr__(), "'two'")

# run unit tests
if __name__ == '__main__':
  suite = unittest.TestLoader().loadTestsFromTestCase(DBLayoutTest)

  if not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful():
    sys.exit(1)

