Fixing a small glitch: drawing into a ghost cell is possible, but did not turn off the ghost cell flag. Now the flag is still on, but a non-empty cell is not considered a ghost cell for most purposes.

This commit is contained in:
Matthias Koefferlein 2026-02-19 23:26:48 +01:00
parent 95d6b0aca5
commit bc49082955
10 changed files with 63 additions and 41 deletions

View File

@ -945,7 +945,7 @@ std::string
Cell::get_display_name () const Cell::get_display_name () const
{ {
tl_assert (layout () != 0); tl_assert (layout () != 0);
if (is_ghost_cell () && empty ()) { if (is_real_ghost_cell ()) {
return std::string ("(") + layout ()->cell_name (cell_index ()) + std::string (")"); return std::string ("(") + layout ()->cell_name (cell_index ()) + std::string (")");
} else { } else {
return layout ()->cell_name (cell_index ()); return layout ()->cell_name (cell_index ());
@ -959,6 +959,20 @@ 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::set_ghost_cell (bool g)
{
// NOTE: this change is not undo managed
if (m_ghost_cell != g) {
m_ghost_cell = g;
tl_assert (layout () != 0);
// To trigger a redraw and cell tree rebuild
layout ()->cell_name_changed ();
}
}
void void
Cell::check_locked () const Cell::check_locked () const
{ {

View File

@ -925,15 +925,26 @@ public:
return m_ghost_cell; return m_ghost_cell;
} }
/**
* @brief Gets a value indicating whether the cell is a "real" ghost cell
*
* A ghost cell is a real ghost cell only if the ghost cell flag is set
* and the cell is empty. Only in that case for example the cell is written
* to GDS files as a ghost cell.
*
* Otherwise, the ghost cell flag is mostly ignored.
*/
bool is_real_ghost_cell () const
{
return m_ghost_cell && empty ();
}
/** /**
* @brief Sets the "ghost cell" flag * @brief Sets the "ghost cell" flag
* *
* See "is_ghost_cell" for a description of this property. * See "is_ghost_cell" for a description of this property.
*/ */
void set_ghost_cell (bool g) void set_ghost_cell (bool g);
{
m_ghost_cell = g;
}
/** /**
* @brief Gets a value indicating whether the cell is locked * @brief Gets a value indicating whether the cell is locked

View File

@ -1417,7 +1417,7 @@ Layout::add_cell (const char *name)
if (cm != m_cell_map.end ()) { if (cm != m_cell_map.end ()) {
const db::Cell &c= cell (cm->second); const db::Cell &c= cell (cm->second);
if (c.is_ghost_cell () && c.empty ()) { if (c.is_real_ghost_cell ()) {
// ghost cells are available as new cells - the idea is to // ghost cells are available as new cells - the idea is to
// treat them as non-existing. // treat them as non-existing.
return cm->second; return cm->second;

View File

@ -183,6 +183,14 @@ public:
return m_busy; return m_busy;
} }
/**
* @brief Issue a "cell name changed event"
*/
void cell_name_changed ()
{
cell_name_changed_event ();
}
protected: protected:
friend class PropertiesRepository; friend class PropertiesRepository;
@ -191,14 +199,6 @@ protected:
*/ */
virtual void do_update () { } virtual void do_update () { }
/**
* @brief Issue a "prop id's changed event"
*/
void cell_name_changed ()
{
cell_name_changed_event ();
}
/** /**
* @brief Issue a "layer properties changed event" * @brief Issue a "layer properties changed event"
*/ */

View File

@ -3450,6 +3450,9 @@ Class<db::Cell> decl_Cell ("db", "Cell",
"To satisfy the references inside the layout, a dummy cell is created in this case\n" "To satisfy the references inside the layout, a dummy cell is created in this case\n"
"which has the \"ghost cell\" flag set to true.\n" "which has the \"ghost cell\" flag set to true.\n"
"\n" "\n"
"A ghost cell is a real ghost cell only if the cell is empty. In that case, it is written "
"as a ghost cell to GDS files for example. If a cell is not empty, this flag is ignored.\n"
"\n"
"This method has been introduced in version 0.20.\n" "This method has been introduced in version 0.20.\n"
) + ) +
gsi::method ("ghost_cell=", &db::Cell::set_ghost_cell, gsi::arg ("flag"), gsi::method ("ghost_cell=", &db::Cell::set_ghost_cell, gsi::arg ("flag"),

View File

@ -220,7 +220,7 @@ Finder::do_find (const db::Cell &cell, int level, const db::DCplxTrans &vp, cons
&& (t * m_cell_box_convert (cell)).touches (m_scan_region) && (t * m_cell_box_convert (cell)).touches (m_scan_region)
&& (mp_view->select_inside_pcells_mode () || !cell.is_proxy ()) && (mp_view->select_inside_pcells_mode () || !cell.is_proxy ())
&& !mp_view->is_cell_hidden (cell.cell_index (), m_cv_index) && !mp_view->is_cell_hidden (cell.cell_index (), m_cv_index)
&& !cell.is_ghost_cell ()) { && !cell.is_real_ghost_cell ()) {
db::ICplxTrans it = t.inverted (); db::ICplxTrans it = t.inverted ();
db::Box scan_box (it * m_scan_region); db::Box scan_box (it * m_scan_region);
@ -846,7 +846,7 @@ InstFinder::reset_counter ()
bool bool
InstFinder::consider_cell (const db::Cell &cell) const InstFinder::consider_cell (const db::Cell &cell) const
{ {
return cell.is_ghost_cell () ? m_consider_ghost_cells : m_consider_normal_cells; return cell.is_real_ghost_cell () ? m_consider_ghost_cells : m_consider_normal_cells;
} }
void void
@ -877,7 +877,7 @@ InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const d
// just consider the instances exactly at the last level of // just consider the instances exactly at the last level of
// hierarchy (this is where the boxes are drawn) or of cells that // hierarchy (this is where the boxes are drawn) or of cells that
// are hidden. // are hidden.
if (level == max_level () - 1 || inst_cell.is_proxy () || inst_cell.is_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) { if (level == max_level () - 1 || inst_cell.is_proxy () || inst_cell.is_real_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) {
db::box_convert <db::CellInst, false> bc (layout ()); db::box_convert <db::CellInst, false> bc (layout ());
for (db::CellInstArray::iterator p = cell_inst.begin_touching (search_box, bc); ! p.at_end (); ++p) { for (db::CellInstArray::iterator p = cell_inst.begin_touching (search_box, bc); ! p.at_end (); ++p) {
@ -885,7 +885,7 @@ InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const d
checkpoint (); checkpoint ();
db::Box ibox; db::Box ibox;
if (! m_visible_layers || level == mp_view->get_max_hier_levels () - 1 || inst_cell.is_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) { if (! m_visible_layers || level == mp_view->get_max_hier_levels () - 1 || inst_cell.is_real_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) {
ibox = inst_cell.bbox_with_empty (); ibox = inst_cell.bbox_with_empty ();
} else if (inst_cell.bbox ().empty ()) { } else if (inst_cell.bbox ().empty ()) {
// empty cells cannot be found by visible layers, so we always select them here // empty cells cannot be found by visible layers, so we always select them here
@ -954,7 +954,7 @@ InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const d
// just consider the instances exactly at the last level of // just consider the instances exactly at the last level of
// hierarchy (this is where the boxes are drawn) or if of cells that // hierarchy (this is where the boxes are drawn) or if of cells that
// are hidden. // are hidden.
if (level == max_level () - 1 || inst_cell.is_proxy () || inst_cell.is_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) { if (level == max_level () - 1 || inst_cell.is_proxy () || inst_cell.is_real_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) {
db::box_convert <db::CellInst, false> bc (layout ()); db::box_convert <db::CellInst, false> bc (layout ());
for (db::CellInstArray::iterator p = cell_inst.begin_touching (search_box, bc); ! p.at_end (); ++p) { for (db::CellInstArray::iterator p = cell_inst.begin_touching (search_box, bc); ! p.at_end (); ++p) {
@ -965,7 +965,7 @@ InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const d
double d = std::numeric_limits<double>::max (); double d = std::numeric_limits<double>::max ();
db::Box ibox; db::Box ibox;
if (! m_visible_layers || level == mp_view->get_max_hier_levels () - 1 || inst_cell.is_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) { if (! m_visible_layers || level == mp_view->get_max_hier_levels () - 1 || inst_cell.is_real_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) {
ibox = inst_cell.bbox_with_empty (); ibox = inst_cell.bbox_with_empty ();
} else if (inst_cell.bbox ().empty ()) { } else if (inst_cell.bbox ().empty ()) {
// empty cells cannot be found by visible layers, so we always select them here // empty cells cannot be found by visible layers, so we always select them here

View File

@ -714,7 +714,7 @@ RedrawThreadWorker::setup (LayoutViewBase *view, RedrawThreadCanvas *canvas, con
std::set <lay::LayoutViewBase::cell_index_type> &gc = m_ghost_cells [i]; std::set <lay::LayoutViewBase::cell_index_type> &gc = m_ghost_cells [i];
const db::Layout &ly = view->cellview (i)->layout (); const db::Layout &ly = view->cellview (i)->layout ();
for (auto c = ly.begin (); c != ly.end (); ++c) { for (auto c = ly.begin (); c != ly.end (); ++c) {
if (c->is_ghost_cell ()) { if (c->is_real_ghost_cell ()) {
gc.insert (c->cell_index ()); gc.insert (c->cell_index ());
} }
} }
@ -950,12 +950,12 @@ RedrawThreadWorker::draw_boxes_impl (bool drawing_context, db::cell_index_type c
bbox_for_label = bbox; bbox_for_label = bbox;
} }
if (for_ghosts && cell.is_ghost_cell ()) { if (for_ghosts && cell.is_real_ghost_cell ()) {
// paint the box on this level // paint the box on this level
draw_cell (drawing_context, level, trans, bbox, bbox_for_label, empty_cell, mp_layout->display_name (ci), opt_bitmap); draw_cell (drawing_context, level, trans, bbox, bbox_for_label, empty_cell, mp_layout->display_name (ci), opt_bitmap);
} else if (! for_ghosts && ! cell.is_ghost_cell () && (level == m_to_level || (m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (ci) != m_hidden_cells [m_cv_index].end ()))) { } else if (! for_ghosts && ! cell.is_real_ghost_cell () && (level == m_to_level || (m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (ci) != m_hidden_cells [m_cv_index].end ()))) {
// paint the box on this level // paint the box on this level
draw_cell (drawing_context, level, trans, bbox, bbox_for_label, empty_cell, mp_layout->display_name (ci), opt_bitmap); draw_cell (drawing_context, level, trans, bbox, bbox_for_label, empty_cell, mp_layout->display_name (ci), opt_bitmap);
@ -1186,12 +1186,12 @@ RedrawThreadWorker::draw_box_properties_impl (bool drawing_context, db::cell_ind
// small cell dropped // small cell dropped
} else if (for_ghosts && cell.is_ghost_cell ()) { } else if (for_ghosts && cell.is_real_ghost_cell ()) {
// paint the box on this level // paint the box on this level
draw_cell_properties (drawing_context, level, trans, bbox, prop_id); draw_cell_properties (drawing_context, level, trans, bbox, prop_id);
} else if (! for_ghosts && ! cell.is_ghost_cell () && (level == m_to_level || (m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (ci) != m_hidden_cells [m_cv_index].end ()))) { } else if (! for_ghosts && ! cell.is_real_ghost_cell () && (level == m_to_level || (m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (ci) != m_hidden_cells [m_cv_index].end ()))) {
// paint the box on this level // paint the box on this level
draw_cell_properties (drawing_context, level, trans, bbox, prop_id); draw_cell_properties (drawing_context, level, trans, bbox, prop_id);
@ -1317,7 +1317,7 @@ RedrawThreadWorker::any_shapes (db::cell_index_type cell_index, unsigned int lev
// Ghost cells are not drawn either // Ghost cells are not drawn either
const db::Cell &cell = mp_layout->cell (cell_index); const db::Cell &cell = mp_layout->cell (cell_index);
if (cell.is_ghost_cell ()) { if (cell.is_real_ghost_cell ()) {
return false; return false;
} }
@ -1359,7 +1359,7 @@ RedrawThreadWorker::any_cell_box (db::cell_index_type cell_index, unsigned int l
// ghost cells are also drawn // ghost cells are also drawn
const db::Cell &cell = mp_layout->cell (cell_index); const db::Cell &cell = mp_layout->cell (cell_index);
if (cell.is_ghost_cell ()) { if (cell.is_real_ghost_cell ()) {
return true; return true;
} }
@ -1400,7 +1400,7 @@ RedrawThreadWorker::any_text_shapes (db::cell_index_type cell_index, unsigned in
// Ghost cells are not drawn either // Ghost cells are not drawn either
const db::Cell &cell = mp_layout->cell (cell_index); const db::Cell &cell = mp_layout->cell (cell_index);
if (cell.is_ghost_cell ()) { if (cell.is_real_ghost_cell ()) {
return false; return false;
} }
@ -1503,7 +1503,7 @@ RedrawThreadWorker::draw_text_layer (bool drawing_context, db::cell_index_type c
} else if (! bbox.empty ()) { } else if (! bbox.empty ()) {
bool hidden = cell.is_ghost_cell () || ((m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (ci) != m_hidden_cells [m_cv_index].end ())); bool hidden = cell.is_real_ghost_cell () || ((m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (ci) != m_hidden_cells [m_cv_index].end ()));
bool need_to_dive = (level + 1 < m_to_level) && ! hidden; bool need_to_dive = (level + 1 < m_to_level) && ! hidden;
db::Box cell_bbox = cell.bbox (); db::Box cell_bbox = cell.bbox ();
@ -1637,7 +1637,7 @@ RedrawThreadWorker::draw_text_layer (bool drawing_context, db::cell_index_type c
++inst; ++inst;
db::cell_index_type new_ci = cell_inst.object ().cell_index (); db::cell_index_type new_ci = cell_inst.object ().cell_index ();
bool hidden = mp_layout->cell (new_ci).is_ghost_cell () || ((m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (new_ci) != m_hidden_cells [m_cv_index].end ())); bool hidden = mp_layout->cell (new_ci).is_real_ghost_cell () || ((m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (new_ci) != m_hidden_cells [m_cv_index].end ()));
db::Box cell_box = mp_layout->cell (new_ci).bbox (m_layer); db::Box cell_box = mp_layout->cell (new_ci).bbox (m_layer);
if (! cell_box.empty () && ! hidden) { if (! cell_box.empty () && ! hidden) {
@ -1939,7 +1939,7 @@ RedrawThreadWorker::draw_layer_wo_cache (int from_level, int to_level, db::cell_
++inst; ++inst;
db::cell_index_type new_ci = cell_inst.object ().cell_index (); db::cell_index_type new_ci = cell_inst.object ().cell_index ();
bool hidden = mp_layout->cell (new_ci).is_ghost_cell () || ((m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (new_ci) != m_hidden_cells [m_cv_index].end ())); bool hidden = mp_layout->cell (new_ci).is_real_ghost_cell () || ((m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (new_ci) != m_hidden_cells [m_cv_index].end ()));
db::Box new_cell_box = mp_layout->cell (new_ci).bbox (m_layer); db::Box new_cell_box = mp_layout->cell (new_ci).bbox (m_layer);
if (! new_cell_box.empty () && ! hidden) { if (! new_cell_box.empty () && ! hidden) {
@ -2096,7 +2096,7 @@ RedrawThreadWorker::draw_layer (int from_level, int to_level, db::cell_index_typ
db::Box cell_bbox = cell.bbox (); db::Box cell_bbox = cell.bbox ();
// Nothing to draw // Nothing to draw
if (bbox.empty () || cell.is_ghost_cell ()) { if (bbox.empty () || cell.is_real_ghost_cell ()) {
return; return;
} }

View File

@ -577,7 +577,7 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S
// don't write ghost cells unless they are not empty (any more) // don't write ghost cells unless they are not empty (any more)
// also don't write proxy cells which are not employed // also don't write proxy cells which are not employed
if ((! cref.is_ghost_cell () || ! cref.empty ()) && (! cref.is_proxy () || ! cref.is_top ())) { if (! cref.is_real_ghost_cell () && (! cref.is_proxy () || ! cref.is_top ())) {
try { try {
write_cell (layout, cref, layers, cell_set, sf, time_data); write_cell (layout, cref, layers, cell_set, sf, time_data);

View File

@ -913,7 +913,7 @@ Writer::make_meta_data (const db::Cell *cell, stream::metaData::MetaData::Builde
void void
Writer::write_cell (db::cell_index_type ci, kj::BufferedOutputStream &os) Writer::write_cell (db::cell_index_type ci, kj::BufferedOutputStream &os)
{ {
bool needs_layout_view = ! mp_layout->cell (ci).is_ghost_cell (); bool needs_layout_view = ! mp_layout->cell (ci).is_real_ghost_cell ();
bool needs_meta_data_view = mp_layout->begin_meta (ci) != mp_layout->end_meta (ci); bool needs_meta_data_view = mp_layout->begin_meta (ci) != mp_layout->end_meta (ci);
capnp::MallocMessageBuilder message; capnp::MallocMessageBuilder message;

View File

@ -1443,12 +1443,6 @@ static bool must_write_cell (const db::Cell &cref)
return ! cref.is_proxy () || ! cref.is_top (); return ! cref.is_proxy () || ! cref.is_top ();
} }
static bool skip_cell_body (const db::Cell &cref)
{
// Skip cell bodies for ghost cells unless empty (they are not longer ghost cells in this case)
return cref.is_ghost_cell () && cref.empty ();
}
void void
OASISWriter::create_cell_nstrings (const db::Layout &layout, const std::set <db::cell_index_type> &cell_set) OASISWriter::create_cell_nstrings (const db::Layout &layout, const std::set <db::cell_index_type> &cell_set)
{ {
@ -1670,7 +1664,7 @@ OASISWriter::write (db::Layout &layout, tl::OutputStream &stream, const db::Save
mp_cell = &cref; mp_cell = &cref;
// skip cell body if the cell is not to be written // skip cell body if the cell is not to be written
if (skip_cell_body (cref)) { if (cref.is_real_ghost_cell ()) {
continue; continue;
} }