Implemented a solution for #1963 (locking cells)

A cell can be locked using
  cell.locked = true
and unlocked again using
  cell.locked = false
Also, cell.is_locked? can be used to test the locked state.

In locked state writing shapes and instances is forbidden
and doing so would raise an exception.

Also, cells cannot be deleted when locked. However, Layout#clear
and Layout#_destroy are always available.

Cells can still be renamed, even if locked.
This commit is contained in:
Matthias Koefferlein 2025-01-04 21:44:11 +01:00
parent 2d5ddd794c
commit bccf68504f
7 changed files with 342 additions and 7 deletions

View File

@ -93,7 +93,7 @@ Cell::box_type Cell::ms_empty_box = Cell::box_type ();
Cell::Cell (cell_index_type ci, db::Layout &l) Cell::Cell (cell_index_type ci, db::Layout &l)
: db::Object (l.manager ()), : db::Object (l.manager ()),
m_cell_index (ci), mp_layout (&l), m_instances (this), m_prop_id (0), m_hier_levels (0), m_cell_index (ci), mp_layout (&l), m_instances (this), m_prop_id (0), m_hier_levels (0),
m_bbox_needs_update (false), m_ghost_cell (false), m_bbox_needs_update (false), m_locked (false), m_ghost_cell (false),
mp_last (0), mp_next (0) mp_last (0), mp_next (0)
{ {
// .. nothing yet // .. nothing yet
@ -125,6 +125,7 @@ Cell::operator= (const Cell &d)
} }
m_ghost_cell = d.m_ghost_cell; m_ghost_cell = d.m_ghost_cell;
m_locked = d.m_locked;
m_instances = d.m_instances; m_instances = d.m_instances;
m_bbox = d.m_bbox; m_bbox = d.m_bbox;
m_bboxes = d.m_bboxes; m_bboxes = d.m_bboxes;
@ -138,6 +139,7 @@ Cell::operator= (const Cell &d)
Cell::~Cell () Cell::~Cell ()
{ {
m_locked = false;
clear_shapes (); clear_shapes ();
} }
@ -180,6 +182,8 @@ Cell::empty () const
void void
Cell::clear (unsigned int index) Cell::clear (unsigned int index)
{ {
check_locked ();
shapes_map::iterator s = m_shapes_map.find(index); shapes_map::iterator s = m_shapes_map.find(index);
if (s != m_shapes_map.end() && ! s->second.empty ()) { if (s != m_shapes_map.end() && ! s->second.empty ()) {
mp_layout->invalidate_bboxes (index); // HINT: must come before the change is done! mp_layout->invalidate_bboxes (index); // HINT: must come before the change is done!
@ -191,6 +195,8 @@ Cell::clear (unsigned int index)
void void
Cell::clear (unsigned int index, unsigned int types) Cell::clear (unsigned int index, unsigned int types)
{ {
check_locked ();
shapes_map::iterator s = m_shapes_map.find(index); shapes_map::iterator s = m_shapes_map.find(index);
if (s != m_shapes_map.end() && ! s->second.empty ()) { if (s != m_shapes_map.end() && ! s->second.empty ()) {
mp_layout->invalidate_bboxes (index); // HINT: must come before the change is done! mp_layout->invalidate_bboxes (index); // HINT: must come before the change is done!
@ -241,6 +247,8 @@ Cell::index_of_shapes (const Cell::shapes_type *shapes) const
void void
Cell::clear_shapes () Cell::clear_shapes ()
{ {
check_locked ();
mp_layout->invalidate_bboxes (std::numeric_limits<unsigned int>::max ()); // HINT: must come before the change is done! mp_layout->invalidate_bboxes (std::numeric_limits<unsigned int>::max ()); // HINT: must come before the change is done!
clear_shapes_no_invalidate (); clear_shapes_no_invalidate ();
} }
@ -345,6 +353,8 @@ Cell::update_bbox (unsigned int layers)
void void
Cell::copy (unsigned int src, unsigned int dest) Cell::copy (unsigned int src, unsigned int dest)
{ {
check_locked ();
if (src != dest) { if (src != dest) {
shapes (dest).insert (shapes (src)); shapes (dest).insert (shapes (src));
} else { } else {
@ -359,6 +369,8 @@ Cell::copy (unsigned int src, unsigned int dest)
void void
Cell::copy (unsigned int src, unsigned int dest, unsigned int types) Cell::copy (unsigned int src, unsigned int dest, unsigned int types)
{ {
check_locked ();
if (src != dest) { if (src != dest) {
shapes (dest).insert (shapes (src), types); shapes (dest).insert (shapes (src), types);
} else { } else {
@ -373,6 +385,8 @@ Cell::copy (unsigned int src, unsigned int dest, unsigned int types)
void void
Cell::move (unsigned int src, unsigned int dest) Cell::move (unsigned int src, unsigned int dest)
{ {
check_locked ();
if (src != dest) { if (src != dest) {
copy (src, dest); copy (src, dest);
clear (src); clear (src);
@ -382,6 +396,8 @@ Cell::move (unsigned int src, unsigned int dest)
void void
Cell::move (unsigned int src, unsigned int dest, unsigned int types) Cell::move (unsigned int src, unsigned int dest, unsigned int types)
{ {
check_locked ();
if (src != dest) { if (src != dest) {
copy (src, dest, types); copy (src, dest, types);
clear (src, types); clear (src, types);
@ -391,6 +407,8 @@ Cell::move (unsigned int src, unsigned int dest, unsigned int types)
void void
Cell::swap (unsigned int i1, unsigned int i2) Cell::swap (unsigned int i1, unsigned int i2)
{ {
check_locked ();
if (i1 != i2) { if (i1 != i2) {
if (manager () && manager ()->transacting ()) { if (manager () && manager ()->transacting ()) {
@ -784,6 +802,14 @@ Cell::set_name (const std::string &name)
layout ()->rename_cell (cell_index (), name.c_str ()); layout ()->rename_cell (cell_index (), name.c_str ());
} }
void
Cell::check_locked () const
{
if (m_locked) {
throw tl::Exception (tl::to_string (tr ("Cell '%s' cannot be modified as it is locked")), get_basic_name ());
}
}
void void
Cell::copy_shapes (const db::Cell &source_cell, const db::LayerMapping &layer_mapping) Cell::copy_shapes (const db::Cell &source_cell, const db::LayerMapping &layer_mapping)
{ {
@ -800,6 +826,8 @@ Cell::copy_shapes (const db::Cell &source_cell, const db::LayerMapping &layer_ma
throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout")));
} }
check_locked ();
if (target_layout != source_layout) { if (target_layout != source_layout) {
db::PropertyMapper pm (target_layout, source_layout); db::PropertyMapper pm (target_layout, source_layout);
db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ());
@ -824,6 +852,8 @@ Cell::copy_shapes (const db::Cell &source_cell)
throw tl::Exception (tl::to_string (tr ("Cell does not reside in a layout"))); throw tl::Exception (tl::to_string (tr ("Cell does not reside in a layout")));
} }
check_locked ();
if (target_layout != source_cell.layout ()) { if (target_layout != source_cell.layout ()) {
if (! source_cell.layout ()) { if (! source_cell.layout ()) {
throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout")));
@ -848,6 +878,8 @@ Cell::copy_instances (const db::Cell &source_cell)
throw tl::Exception (tl::to_string (tr ("Cells do not reside in the same layout"))); throw tl::Exception (tl::to_string (tr ("Cells do not reside in the same layout")));
} }
check_locked ();
for (db::Cell::const_iterator i = source_cell.begin (); ! i.at_end (); ++i) { for (db::Cell::const_iterator i = source_cell.begin (); ! i.at_end (); ++i) {
insert (*i); insert (*i);
} }
@ -869,6 +901,8 @@ Cell::copy_tree (const db::Cell &source_cell)
throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout")));
} }
check_locked ();
db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ());
db::CellMapping cm; db::CellMapping cm;
@ -900,6 +934,8 @@ Cell::copy_tree_shapes (const db::Cell &source_cell, const db::CellMapping &cm)
throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout")));
} }
check_locked ();
db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ());
db::LayerMapping lm; db::LayerMapping lm;
@ -926,6 +962,8 @@ Cell::copy_tree_shapes (const db::Cell &source_cell, const db::CellMapping &cm,
throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout")));
} }
check_locked ();
db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ());
std::vector <db::cell_index_type> source_cells; std::vector <db::cell_index_type> source_cells;
@ -949,6 +987,8 @@ Cell::move_shapes (db::Cell &source_cell, const db::LayerMapping &layer_mapping)
throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout")));
} }
check_locked ();
if (target_layout != source_layout) { if (target_layout != source_layout) {
db::PropertyMapper pm (target_layout, source_layout); db::PropertyMapper pm (target_layout, source_layout);
db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ());
@ -975,6 +1015,8 @@ Cell::move_shapes (db::Cell &source_cell)
throw tl::Exception (tl::to_string (tr ("Cell does not reside in a layout"))); throw tl::Exception (tl::to_string (tr ("Cell does not reside in a layout")));
} }
check_locked ();
if (target_layout != source_cell.layout ()) { if (target_layout != source_cell.layout ()) {
if (! source_cell.layout ()) { if (! source_cell.layout ()) {
throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout")));
@ -1000,6 +1042,8 @@ Cell::move_instances (db::Cell &source_cell)
throw tl::Exception (tl::to_string (tr ("Cells do not reside in the same layout"))); throw tl::Exception (tl::to_string (tr ("Cells do not reside in the same layout")));
} }
check_locked ();
for (db::Cell::const_iterator i = source_cell.begin (); ! i.at_end (); ++i) { for (db::Cell::const_iterator i = source_cell.begin (); ! i.at_end (); ++i) {
insert (*i); insert (*i);
} }
@ -1023,6 +1067,8 @@ Cell::move_tree (db::Cell &source_cell)
throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout")));
} }
check_locked ();
db::PropertyMapper pm (target_layout, source_layout); db::PropertyMapper pm (target_layout, source_layout);
db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ());
@ -1057,6 +1103,8 @@ Cell::move_tree_shapes (db::Cell &source_cell, const db::CellMapping &cm)
throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout")));
} }
check_locked ();
db::PropertyMapper pm (target_layout, source_layout); db::PropertyMapper pm (target_layout, source_layout);
db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ());
@ -1084,6 +1132,8 @@ Cell::move_tree_shapes (db::Cell &source_cell, const db::CellMapping &cm, const
throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout")));
} }
check_locked ();
db::PropertyMapper pm (target_layout, source_layout); db::PropertyMapper pm (target_layout, source_layout);
db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ());

View File

@ -832,6 +832,13 @@ public:
*/ */
virtual void update (ImportLayerMapping * /*layer_mapping*/ = 0) { } virtual void update (ImportLayerMapping * /*layer_mapping*/ = 0) { }
/**
* @brief Checks if the cell is locked
*
* This method throws an exception if the cell is locked.
*/
void check_locked () const;
/** /**
* @brief Tell, if this cell is a proxy cell * @brief Tell, if this cell is a proxy cell
* *
@ -903,6 +910,27 @@ public:
m_ghost_cell = g; m_ghost_cell = g;
} }
/**
* @brief Gets a value indicating whether the cell is locked
*
* A locked cell cannot be modified in terms of instances or shapes.
* The name of a locked cell can be changed though.
*/
bool is_locked () const
{
return m_locked;
}
/**
* @brief Sets the locked state of the cell
*
* See \is_locked for details about locked state.
*/
void set_locked (bool f)
{
m_locked = f;
}
/** /**
* @brief Returns a value indicating whether the cell is empty * @brief Returns a value indicating whether the cell is empty
* *
@ -1076,8 +1104,9 @@ private:
db::properties_id_type m_prop_id; db::properties_id_type m_prop_id;
// packed fields // packed fields
unsigned int m_hier_levels : 30; unsigned int m_hier_levels : 29;
bool m_bbox_needs_update : 1; bool m_bbox_needs_update : 1;
bool m_locked : 1;
bool m_ghost_cell : 1; bool m_ghost_cell : 1;
static box_type ms_empty_box; static box_type ms_empty_box;

View File

@ -1035,8 +1035,10 @@ Instances::layout () const
void void
Instances::invalidate_insts () Instances::invalidate_insts ()
{ {
if (cell ()) { db::Cell *cp = cell ();
cell ()->invalidate_insts (); if (cp) {
cp->check_locked ();
cp->invalidate_insts ();
} }
set_instance_by_cell_index_needs_made (true); set_instance_by_cell_index_needs_made (true);

View File

@ -873,6 +873,7 @@ Layout::delete_cells (const std::set<cell_index_type> &cells_to_delete)
std::set <cell_index_type> pcs; std::set <cell_index_type> pcs;
for (std::set<cell_index_type>::const_iterator c = cells_to_delete.begin (); c != cells_to_delete.end (); ++c) { for (std::set<cell_index_type>::const_iterator c = cells_to_delete.begin (); c != cells_to_delete.end (); ++c) {
const db::Cell &cref = cell (*c); const db::Cell &cref = cell (*c);
cref.check_locked ();
for (db::Cell::parent_cell_iterator pc = cref.begin_parent_cells (); pc != cref.end_parent_cells (); ++pc) { for (db::Cell::parent_cell_iterator pc = cref.begin_parent_cells (); pc != cref.end_parent_cells (); ++pc) {
pcs.insert (*pc); pcs.insert (*pc);
} }
@ -950,6 +951,7 @@ void
Layout::delete_cell (cell_index_type id) Layout::delete_cell (cell_index_type id)
{ {
db::Cell &cref = cell (id); db::Cell &cref = cell (id);
cref.check_locked ();
std::vector <cell_index_type> pcs; std::vector <cell_index_type> pcs;
for (db::Cell::parent_cell_iterator pc = cref.begin_parent_cells (); pc != cref.end_parent_cells (); ++pc) { for (db::Cell::parent_cell_iterator pc = cref.begin_parent_cells (); pc != cref.end_parent_cells (); ++pc) {
@ -1140,6 +1142,8 @@ Layout::flatten (const db::Cell &source_cell, db::Cell &target_cell, const db::I
void void
Layout::flatten (db::Cell &cell_to_flatten, int levels, bool prune) Layout::flatten (db::Cell &cell_to_flatten, int levels, bool prune)
{ {
cell_to_flatten.check_locked ();
std::set<db::cell_index_type> direct_children; std::set<db::cell_index_type> direct_children;
if (prune) { if (prune) {
// save direct children // save direct children

View File

@ -297,12 +297,16 @@ Shapes::array_repository () const
void void
Shapes::invalidate_state () Shapes::invalidate_state ()
{ {
db::Cell *cp = cell ();
if (cp) {
cp->check_locked ();
}
if (! is_dirty ()) { if (! is_dirty ()) {
set_dirty (true); set_dirty (true);
if (layout () && cell ()) { if (cp && cp->layout ()) {
unsigned int index = cell ()->index_of_shapes (this); unsigned int index = cp->index_of_shapes (this);
if (index != std::numeric_limits<unsigned int>::max ()) { if (index != std::numeric_limits<unsigned int>::max ()) {
layout ()->invalidate_bboxes (index); cp->layout ()->invalidate_bboxes (index);
} }
} }
} }

View File

@ -2923,6 +2923,28 @@ Class<db::Cell> decl_Cell ("db", "Cell",
"\n" "\n"
"@return A list of cell indices.\n" "@return A list of cell indices.\n"
) + ) +
gsi::method ("is_locked?", &db::Cell::is_locked,
"@brief Gets a value indicating whether the cell is locked\n"
"\n"
"Locked cells cannot be modified in terms of instances (children) and shapes. "
"Locked cells can still be renamed, but cannot be deleted or cleared.\n"
"Among other things, these features are disabled too: layer operations, copy of instances or shapes, "
"flattening or pruning.\n"
"\n"
"However, wiping the layout entirely with \\Layout#clear is always possible, even if cells are locked.\n"
"\n"
"Use \\locked= to set the locked state of the cell.\n"
"\n"
"The lock feature has been introduced in version 0.29.11."
) +
gsi::method ("locked=", &db::Cell::set_locked, gsi::arg ("l"),
"@brief Locks or unlocks the cell\n"
"\n"
"Set this predicate to 'true' to lock the cell and to 'false' to unlock it.\n"
"See \\is_locked? for details about the lock feature.\n"
"\n"
"The lock feature has been introduced in version 0.29.11."
) +
gsi::method ("bbox", (const db::Cell::box_type &(db::Cell::*) () const) &db::Cell::bbox, gsi::method ("bbox", (const db::Cell::box_type &(db::Cell::*) () const) &db::Cell::bbox,
"@brief Gets the bounding box of the cell\n" "@brief Gets the bounding box of the cell\n"
"\n" "\n"

View File

@ -124,6 +124,230 @@ class DBCellTests_TestClass < TestBase
end end
# locking
def test_4
ly = RBA::Layout::new
top = ly.create_cell("TOP")
other = ly.create_cell("OTHER")
child = ly.create_cell("CHILD")
l1 = ly.layer(1, 0)
l2 = ly.layer(2, 0)
shape = top.shapes(l1).insert(RBA::Box::new(0, 0, 100, 200))
inst = top.insert(RBA::CellInstArray::new(child, RBA::Trans::new(RBA::Vector::new(100, 200))))
other.insert(RBA::CellInstArray::new(child, RBA::Trans::new(RBA::Vector::new(10, 20))))
assert_equal(top.is_locked?, false)
top.locked = false
assert_equal(top.is_locked?, false)
top.locked = true
assert_equal(top.is_locked?, true)
# rename is still possible
top.name = "TOP2"
# instances of the cell can still be created
toptop = ly.create_cell("TOPTOP")
toptop.insert(RBA::CellInstArray::new(top, RBA::Trans::new))
assert_equal(top.each_parent_inst.to_a[0].child_inst.to_s, "cell_index=0 r0 0,0")
begin
# forbidden now
top.shapes(l2).insert(RBA::Box::new(0, 0, 100, 200))
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Shapes::insert")
end
begin
# forbidden now
shape.box = RBA::Box::new(1, 2, 101, 202)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Shape::box=")
end
begin
# forbidden now
shape.prop_id = 1
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Shape::prop_id=")
end
begin
# forbidden now
shape.polygon = RBA::Polygon::new(RBA::Box::new(0, 0, 200, 100))
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Shape::polygon=")
end
begin
# forbidden now
inst.cell_index = other.cell_index
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Instance::cell_index=")
end
begin
# forbidden now
inst.prop_id = 1
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Instance::prop_id=")
end
begin
# also forbidden
top.insert(RBA::CellInstArray::new(child, RBA::Trans::new))
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::insert")
end
begin
# clear is forbidding
top.clear
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::clear")
end
begin
# clear layer is forbidden
top.shapes(l1).clear
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Shapes::clear")
end
begin
# clear layer is forbidden
top.clear(l1)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::clear")
end
begin
# layer copy is forbidden
top.copy(l1, l2)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::copy")
end
begin
# layer move is forbidden
top.move(l1, l2)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::move")
end
begin
# layer swap is forbidden
top.swap(l1, l2)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::swap")
end
begin
# copy_instances is forbidden
top.copy_instances(other)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::copy_instances")
end
begin
# move_instances is forbidden
top.move_instances(other)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::move_instances")
end
begin
# copy_tree is forbidden
top.copy_tree(other)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::copy_tree")
end
begin
# move_tree is forbidden
top.move_tree(other)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::move_tree")
end
begin
# copy_shapes is forbidden
top.copy_shapes(other)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::copy_shapes")
end
begin
# move_shapes is forbidden
top.move_shapes(other)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::move_shapes")
end
begin
# flatten is forbidden
top.flatten(true)
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::flatten")
end
begin
# cell cannot be deleted
top.delete
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::delete")
end
# locked attribute is copied
ly2 = ly.dup
assert_equal(ly2.cell("TOP2").is_locked?, true)
begin
# cell cannot be deleted
ly2.cell("TOP2").delete
assert_equal(true, false)
rescue => ex
assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::delete")
end
# clear and _destroy is always possible
ly2.clear
ly2 = ly.dup
ly2._destroy
# resetting the locked flag enables things again (not all tested here)
top.locked = false
inst.cell_index = other.cell_index
top.shapes(l2).insert(RBA::Box::new(0, 0, 100, 200))
shape.box = RBA::Box::new(1, 2, 101, 202)
top.delete
end
end end
load("test_epilogue.rb") load("test_epilogue.rb")