mirror of https://github.com/KLayout/klayout.git
1621 lines
53 KiB
C++
1621 lines
53 KiB
C++
|
|
/*
|
|
|
|
KLayout Layout Viewer
|
|
Copyright (C) 2006-2024 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
|
|
|
|
*/
|
|
|
|
|
|
#include "layXORToolDialog.h"
|
|
#include "layXORProgress.h"
|
|
#include "antService.h"
|
|
#include "rdb.h"
|
|
#include "rdbUtils.h"
|
|
#include "dbShapeProcessor.h"
|
|
#include "dbRecursiveShapeIterator.h"
|
|
#include "dbClip.h"
|
|
#include "dbLayoutUtils.h"
|
|
#include "dbRegion.h"
|
|
#include "dbDeepShapeStore.h"
|
|
#include "tlTimer.h"
|
|
#include "tlProgress.h"
|
|
#include "tlThreadedWorkers.h"
|
|
#include "tlEnv.h"
|
|
#include "tlExceptions.h"
|
|
#include "tlMath.h"
|
|
#include "layCellView.h"
|
|
#include "layLayoutView.h"
|
|
#include "layApplication.h"
|
|
#include "layMainWindow.h"
|
|
|
|
#include "ui_XORToolDialog.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <QMessageBox>
|
|
|
|
namespace lay
|
|
{
|
|
|
|
bool merge_before_bool ()
|
|
{
|
|
// $KLAYOUT_XOR_MERGE_BEFORE_BOOLEAN
|
|
return tl::app_flag ("xor-merge-before-boolean");
|
|
}
|
|
|
|
std::string cfg_xor_input_mode ("xor-input-mode");
|
|
std::string cfg_xor_output_mode ("xor-output-mode");
|
|
std::string cfg_xor_nworkers ("xor-num-workers");
|
|
std::string cfg_xor_layer_offset ("xor-layer-offset");
|
|
std::string cfg_xor_axorb ("xor-axorb");
|
|
std::string cfg_xor_anotb ("xor-anotb");
|
|
std::string cfg_xor_bnota ("xor-bnota");
|
|
std::string cfg_xor_summarize ("xor-summarize");
|
|
std::string cfg_xor_tolerances ("xor-tolerances");
|
|
std::string cfg_xor_deep ("xor-deep");
|
|
std::string cfg_xor_tiling ("xor-tiling");
|
|
std::string cfg_xor_tiling_heal ("xor-tiling-heal");
|
|
std::string cfg_xor_region_mode ("xor-region-mode");
|
|
|
|
// Note: this enum must match with the order of the combo box entries in the
|
|
// dialog implementation
|
|
enum input_mode_t { IMAll = 0, IMVisible, IMSpecific };
|
|
|
|
// Note: this enum must match with the order of the combo box entries in the
|
|
// dialog implementation
|
|
enum output_mode_t { OMMarkerDatabase = 0, OMNewLayout, OMNewLayersA, OMNewLayersB, OMNone };
|
|
|
|
// Note: this enum must match with the order of the combo box entries in the
|
|
// dialog implementation
|
|
enum region_mode_t { RMAll = 0, RMVisible, RMRulers };
|
|
|
|
struct InputModeConverter
|
|
{
|
|
std::string to_string (input_mode_t t)
|
|
{
|
|
switch (t) {
|
|
case IMAll:
|
|
return "all";
|
|
case IMVisible:
|
|
return "visible";
|
|
case IMSpecific:
|
|
return "specific";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
void from_string (const std::string &s, input_mode_t &t)
|
|
{
|
|
t = IMAll;
|
|
if (s == "visible") {
|
|
t = IMVisible;
|
|
} else if (s == "specific") {
|
|
t = IMSpecific;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct OutputModeConverter
|
|
{
|
|
std::string to_string (output_mode_t t)
|
|
{
|
|
switch (t) {
|
|
case OMMarkerDatabase:
|
|
return "rdb";
|
|
case OMNewLayout:
|
|
return "layout";
|
|
case OMNewLayersA:
|
|
return "layers-a";
|
|
case OMNewLayersB:
|
|
return "layers-b";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
void from_string (const std::string &s, output_mode_t &t)
|
|
{
|
|
t = OMMarkerDatabase;
|
|
if (s == "layout") {
|
|
t = OMNewLayout;
|
|
} else if (s == "layers-a") {
|
|
t = OMNewLayersA;
|
|
} else if (s == "layers-b") {
|
|
t = OMNewLayersB;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct RegionModeConverter
|
|
{
|
|
std::string to_string (region_mode_t t)
|
|
{
|
|
switch (t) {
|
|
case RMAll:
|
|
return "all";
|
|
case RMVisible:
|
|
return "visible";
|
|
case RMRulers:
|
|
return "rulers";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
void from_string (const std::string &s, region_mode_t &t)
|
|
{
|
|
t = RMAll;
|
|
if (s == "all") {
|
|
t = RMAll;
|
|
} else if (s == "visible") {
|
|
t = RMVisible;
|
|
} else if (s == "rulers") {
|
|
t = RMRulers;
|
|
}
|
|
}
|
|
};
|
|
|
|
XORToolDialog::XORToolDialog (QWidget *parent)
|
|
: QDialog (parent), mp_view (0)
|
|
{
|
|
mp_ui = new Ui::XORToolDialog ();
|
|
mp_ui->setupUi (this);
|
|
|
|
connect (mp_ui->input_layers_cbx, SIGNAL (currentIndexChanged (int)), this, SLOT (input_changed (int)));
|
|
connect (mp_ui->output_cbx, SIGNAL (currentIndexChanged (int)), this, SLOT (output_changed (int)));
|
|
connect (mp_ui->deep, SIGNAL (clicked ()), this, SLOT (deep_changed ()));
|
|
|
|
input_changed (0);
|
|
output_changed (0);
|
|
}
|
|
|
|
XORToolDialog::~XORToolDialog ()
|
|
{
|
|
delete mp_ui;
|
|
mp_ui = 0;
|
|
}
|
|
|
|
int
|
|
XORToolDialog::exec_dialog (lay::LayoutViewBase *view)
|
|
{
|
|
mp_view = view;
|
|
|
|
if (view != mp_ui->layouta->layout_view () || view != mp_ui->layoutb->layout_view ()) {
|
|
|
|
mp_ui->layouta->set_layout_view (view);
|
|
mp_ui->layoutb->set_layout_view (view);
|
|
|
|
if (view->cellviews () >= 2) {
|
|
mp_ui->layouta->set_current_cv_index (0);
|
|
mp_ui->layoutb->set_current_cv_index (1);
|
|
}
|
|
|
|
} else {
|
|
// force update of the layer list
|
|
// TODO: the controls should register a listener for the view so this activity is not necessary:
|
|
mp_ui->layouta->set_layout_view (view);
|
|
mp_ui->layoutb->set_layout_view (view);
|
|
}
|
|
|
|
// take current settings from the configurations
|
|
lay::Dispatcher *config_root = lay::Dispatcher::instance ();
|
|
|
|
input_mode_t im = IMAll;
|
|
if (config_root->config_get (cfg_xor_input_mode, im, InputModeConverter ())) {
|
|
mp_ui->input_layers_cbx->setCurrentIndex ((int) im);
|
|
}
|
|
|
|
output_mode_t om = OMMarkerDatabase;
|
|
if (config_root->config_get (cfg_xor_output_mode, om, OutputModeConverter ())) {
|
|
mp_ui->output_cbx->setCurrentIndex ((int) om);
|
|
}
|
|
|
|
region_mode_t rm = RMAll;
|
|
if (config_root->config_get (cfg_xor_region_mode, rm, RegionModeConverter ())) {
|
|
mp_ui->region_cbx->setCurrentIndex ((int) rm);
|
|
}
|
|
|
|
int nw = 1;
|
|
if (config_root->config_get (cfg_xor_nworkers, nw)) {
|
|
mp_ui->threads->setValue (nw);
|
|
}
|
|
|
|
std::string lo;
|
|
if (config_root->config_get (cfg_xor_layer_offset, lo)) {
|
|
mp_ui->layer_offset_le->setText (tl::to_qstring (lo));
|
|
}
|
|
|
|
bool f = false;
|
|
if (config_root->config_get (cfg_xor_axorb, f)) {
|
|
mp_ui->axorb_cb->setChecked (f);
|
|
}
|
|
if (config_root->config_get (cfg_xor_anotb, f)) {
|
|
mp_ui->anotb_cb->setChecked (f);
|
|
}
|
|
if (config_root->config_get (cfg_xor_bnota, f)) {
|
|
mp_ui->bnota_cb->setChecked (f);
|
|
}
|
|
|
|
bool summarize = false;
|
|
if (config_root->config_get (cfg_xor_summarize, summarize)) {
|
|
mp_ui->summarize_cb->setChecked (summarize);
|
|
}
|
|
|
|
std::string tol;
|
|
if (config_root->config_get (cfg_xor_tolerances, tol)) {
|
|
mp_ui->tolerances->setText (tl::to_qstring (tol));
|
|
}
|
|
|
|
bool deep = false;
|
|
if (config_root->config_get (cfg_xor_deep, deep)) {
|
|
mp_ui->deep->setChecked (deep);
|
|
}
|
|
deep_changed ();
|
|
|
|
std::string tiling;
|
|
if (config_root->config_get (cfg_xor_tiling, tiling)) {
|
|
mp_ui->tiling->setText (tl::to_qstring (tiling));
|
|
}
|
|
|
|
bool heal = false;
|
|
if (config_root->config_get (cfg_xor_tiling_heal, heal)) {
|
|
mp_ui->heal_cb->setChecked (heal);
|
|
}
|
|
|
|
int ret = QDialog::exec ();
|
|
|
|
if (ret) {
|
|
run_xor ();
|
|
}
|
|
|
|
mp_view = 0;
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
XORToolDialog::accept ()
|
|
{
|
|
BEGIN_PROTECTED
|
|
|
|
bool axorb = mp_ui->axorb_cb->isChecked ();
|
|
bool anotb = mp_ui->anotb_cb->isChecked ();
|
|
bool bnota = mp_ui->bnota_cb->isChecked ();
|
|
if (axorb + anotb + bnota == 0) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("No mode selected")));
|
|
}
|
|
|
|
int cv_index_a = mp_ui->layouta->current_cv_index ();
|
|
int cv_index_b = mp_ui->layoutb->current_cv_index ();
|
|
|
|
const lay::CellView &cva = mp_view->cellview (cv_index_a);
|
|
const lay::CellView &cvb = mp_view->cellview (cv_index_b);
|
|
|
|
if (&cva->layout () == &cvb->layout () && cva.cell_index () == cvb.cell_index ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Trying to perform an XOR between identical layouts")));
|
|
}
|
|
|
|
if (!cva.is_valid ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("First layout is not a valid input")));
|
|
}
|
|
if (!cvb.is_valid ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Second layout is not a valid input")));
|
|
}
|
|
|
|
{
|
|
std::string text (tl::to_string (mp_ui->tolerances->text ()));
|
|
tl::Extractor ex (text.c_str ());
|
|
while (! ex.at_end ()) {
|
|
double t;
|
|
if (! ex.try_read (t) || t < -1e-6) {
|
|
break;
|
|
}
|
|
ex.test (",");
|
|
}
|
|
}
|
|
|
|
{
|
|
std::string text (tl::to_string (mp_ui->tiling->text ()));
|
|
tl::Extractor ex (text.c_str ());
|
|
double t = 0.0;
|
|
if (ex.try_read (t)) {
|
|
if (t < 0.001) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Invalid tile size (invalid text or negative)")));
|
|
}
|
|
}
|
|
}
|
|
|
|
lay::Dispatcher *config_root = lay::Dispatcher::instance ();
|
|
|
|
config_root->config_set (cfg_xor_input_mode, InputModeConverter ().to_string ((input_mode_t) mp_ui->input_layers_cbx->currentIndex ()));
|
|
config_root->config_set (cfg_xor_output_mode, OutputModeConverter ().to_string ((output_mode_t) mp_ui->output_cbx->currentIndex ()));
|
|
config_root->config_set (cfg_xor_region_mode, RegionModeConverter ().to_string ((region_mode_t) mp_ui->region_cbx->currentIndex ()));
|
|
config_root->config_set (cfg_xor_axorb, mp_ui->axorb_cb->isChecked ());
|
|
config_root->config_set (cfg_xor_anotb, mp_ui->anotb_cb->isChecked ());
|
|
config_root->config_set (cfg_xor_bnota, mp_ui->bnota_cb->isChecked ());
|
|
config_root->config_set (cfg_xor_nworkers, mp_ui->threads->value ());
|
|
config_root->config_set (cfg_xor_layer_offset, tl::to_string (mp_ui->layer_offset_le->text ()));
|
|
config_root->config_set (cfg_xor_summarize, mp_ui->summarize_cb->isChecked ());
|
|
config_root->config_set (cfg_xor_tolerances, tl::to_string (mp_ui->tolerances->text ()));
|
|
config_root->config_set (cfg_xor_deep, mp_ui->deep->isChecked ());
|
|
config_root->config_set (cfg_xor_tiling, tl::to_string (mp_ui->tiling->text ()));
|
|
config_root->config_set (cfg_xor_tiling_heal, mp_ui->heal_cb->isChecked ());
|
|
config_root->config_end ();
|
|
|
|
QDialog::accept ();
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
XORToolDialog::deep_changed ()
|
|
{
|
|
bool deep = mp_ui->deep->isChecked ();
|
|
mp_ui->tiling->setEnabled (!deep);
|
|
mp_ui->heal_cb->setEnabled (!deep);
|
|
}
|
|
|
|
void
|
|
XORToolDialog::input_changed (int /*index*/)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
void
|
|
XORToolDialog::output_changed (int index)
|
|
{
|
|
bool enabled = (index == 2 || index == 3);
|
|
mp_ui->layer_offset_lbl->setEnabled (enabled);
|
|
mp_ui->layer_offset_le->setEnabled (enabled);
|
|
}
|
|
|
|
|
|
class XORJob
|
|
: public tl::JobBase
|
|
{
|
|
public:
|
|
enum EmptyLayerHandling
|
|
{
|
|
EL_optimize, // copy the non-empty contributions of a or b
|
|
EL_summarize, // print a message about leaving away some operations and don't do anything
|
|
EL_process // include in processing - the non-empty layer will be merged
|
|
};
|
|
|
|
XORJob (int nworkers,
|
|
output_mode_t output_mode,
|
|
db::BooleanOp::BoolOp op,
|
|
EmptyLayerHandling el_handling,
|
|
double dbu,
|
|
const lay::CellView &cva, const lay::CellView &cvb,
|
|
const std::vector <db::Coord> &tolerances,
|
|
const std::vector <rdb::Category *> &sub_categories,
|
|
const std::vector <std::vector <rdb::Category *> > &layer_categories,
|
|
const std::vector <db::Cell *> &sub_cells,
|
|
const std::vector <std::vector <unsigned int> > &sub_output_layers,
|
|
rdb::Database *rdb,
|
|
rdb::Cell *rdb_cell)
|
|
: tl::JobBase (nworkers),
|
|
m_output_mode (output_mode),
|
|
m_op (op),
|
|
m_el_handling (el_handling),
|
|
m_has_tiles (false),
|
|
m_tile_heal (false),
|
|
m_dbu (dbu),
|
|
m_cva (cva),
|
|
m_cvb (cvb),
|
|
m_tolerances (tolerances),
|
|
m_sub_categories (sub_categories),
|
|
m_layer_categories (layer_categories),
|
|
m_sub_cells (sub_cells),
|
|
m_sub_output_layers (sub_output_layers),
|
|
m_rdb (rdb),
|
|
m_rdb_cell (rdb_cell),
|
|
m_progress (0),
|
|
m_nx (0), m_ny (0)
|
|
{
|
|
}
|
|
|
|
output_mode_t output_mode () const
|
|
{
|
|
return m_output_mode;
|
|
}
|
|
|
|
db::BooleanOp::BoolOp op () const
|
|
{
|
|
return m_op;
|
|
}
|
|
|
|
EmptyLayerHandling el_handling () const
|
|
{
|
|
return m_el_handling;
|
|
}
|
|
|
|
bool has_tiles () const
|
|
{
|
|
return m_has_tiles;
|
|
}
|
|
|
|
void set_tiles (bool ht, int nx, int ny, bool heal)
|
|
{
|
|
m_has_tiles = ht;
|
|
m_nx = ht ? nx : 0;
|
|
m_ny = ht ? ny : 0;
|
|
m_tile_heal = heal;
|
|
}
|
|
|
|
double dbu () const
|
|
{
|
|
return m_dbu;
|
|
}
|
|
|
|
const lay::CellView &cva () const
|
|
{
|
|
return m_cva;
|
|
}
|
|
|
|
const lay::CellView &cvb () const
|
|
{
|
|
return m_cvb;
|
|
}
|
|
|
|
const std::vector <db::Coord> &tolerances () const
|
|
{
|
|
return m_tolerances;
|
|
}
|
|
|
|
const std::vector <std::vector <unsigned int> > &sub_output_layers () const
|
|
{
|
|
return m_sub_output_layers;
|
|
}
|
|
|
|
void next_progress ()
|
|
{
|
|
QMutexLocker locker (&m_mutex);
|
|
++m_progress;
|
|
}
|
|
|
|
void add_results (const db::LayerProperties &lp, db::Coord tol, size_t n, size_t ix, size_t iy)
|
|
{
|
|
QMutexLocker locker (&m_mutex);
|
|
|
|
std::vector<std::vector<size_t> > &cc = m_results [std::make_pair (lp, tol)];
|
|
if (cc.size () <= ix) {
|
|
cc.resize (ix + 1, std::vector<size_t> ());
|
|
}
|
|
if (cc [ix].size () <= iy) {
|
|
cc [ix].resize (iy + 1, 0);
|
|
}
|
|
|
|
if (n == missing_in_a || n == missing_in_b) {
|
|
cc[ix][iy] = n;
|
|
} else {
|
|
// NOTE: we will not get a "normal" n after missing_in_a or missing_in_b
|
|
cc[ix][iy] += n;
|
|
}
|
|
}
|
|
|
|
void update_progress (XORProgress &progress)
|
|
{
|
|
unsigned int p;
|
|
{
|
|
QMutexLocker locker (&m_mutex);
|
|
p = m_progress;
|
|
progress.configure (m_dbu, int (m_nx), int (m_ny), m_tolerances);
|
|
progress.merge_results (m_results);
|
|
}
|
|
|
|
progress.set (p, true /*force yield*/);
|
|
}
|
|
|
|
void issue_string (unsigned int tol_index, unsigned int layer_index, const std::string &s)
|
|
{
|
|
QMutexLocker locker (&m_mutex);
|
|
|
|
if (m_output_mode == OMMarkerDatabase) {
|
|
|
|
rdb::Category *layercat = m_layer_categories[tol_index][layer_index];
|
|
|
|
rdb::Item *item = m_rdb->create_item (m_rdb_cell->id (), layercat->id ());
|
|
item->values ().add (new rdb::Value <std::string> (s));
|
|
|
|
}
|
|
}
|
|
|
|
void issue_region (unsigned int tol_index, unsigned int layer_index, const db::Region ®ion)
|
|
{
|
|
QMutexLocker locker (&m_mutex);
|
|
db::CplxTrans trans (dbu ());
|
|
|
|
if (m_output_mode == OMMarkerDatabase) {
|
|
|
|
rdb::Category *layercat = m_layer_categories[tol_index][layer_index];
|
|
|
|
std::pair<db::RecursiveShapeIterator, db::ICplxTrans> it = region.begin_iter ();
|
|
rdb::scan_layer (layercat, m_rdb_cell, trans * it.second, it.first, false);
|
|
|
|
} else {
|
|
|
|
db::Cell *output_cell = m_sub_cells[tol_index];
|
|
unsigned int output_layer = m_sub_output_layers[tol_index][layer_index];
|
|
|
|
region.insert_into (output_cell->layout (), output_cell->cell_index (), output_layer);
|
|
|
|
}
|
|
}
|
|
|
|
void issue_polygon (unsigned int tol_index, unsigned int layer_index, const db::Polygon &polygon, bool touches_border = false)
|
|
{
|
|
QMutexLocker locker (&m_mutex);
|
|
db::CplxTrans trans (dbu ());
|
|
|
|
if (m_tile_heal && touches_border) {
|
|
|
|
// save for merging later
|
|
m_polygons_to_heal [std::make_pair (tol_index, layer_index)].insert (polygon);
|
|
|
|
} else if (m_output_mode == OMMarkerDatabase) {
|
|
|
|
rdb::Category *layercat = m_layer_categories[tol_index][layer_index];
|
|
|
|
rdb::Item *item = m_rdb->create_item (m_rdb_cell->id (), layercat->id ());
|
|
item->values ().add (new rdb::Value <db::DPolygon> (polygon.transformed (trans)));
|
|
|
|
} else {
|
|
|
|
db::Cell *subcell = 0;
|
|
unsigned int layout_layer = 0;
|
|
|
|
subcell = m_sub_cells[tol_index];
|
|
layout_layer = m_sub_output_layers[tol_index][layer_index];
|
|
|
|
double factor = 1.0;
|
|
if (subcell->layout ()) {
|
|
factor = dbu () / subcell->layout ()->dbu ();
|
|
}
|
|
if (tl::equal (factor, 1.0)) {
|
|
subcell->shapes (layout_layer).insert (polygon);
|
|
} else {
|
|
subcell->shapes (layout_layer).insert (db::Polygon (polygon * factor));
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void finish ()
|
|
{
|
|
// merge the polygons to heal and re-issue (this time without healing)
|
|
for (std::map<std::pair<size_t, size_t>, db::Region>::iterator p = m_polygons_to_heal.begin (); p != m_polygons_to_heal.end (); ++p) {
|
|
for (db::Region::const_iterator mp = p->second.begin_merged (); !mp.at_end (); ++mp) {
|
|
issue_polygon ((unsigned int) p->first.first, (unsigned int) p->first.second, *mp, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual tl::Worker *create_worker ();
|
|
|
|
private:
|
|
output_mode_t m_output_mode;
|
|
db::BooleanOp::BoolOp m_op;
|
|
EmptyLayerHandling m_el_handling;
|
|
bool m_has_tiles;
|
|
bool m_tile_heal;
|
|
double m_dbu;
|
|
lay::CellView m_cva;
|
|
lay::CellView m_cvb;
|
|
std::vector <db::Coord> m_tolerances;
|
|
std::vector <rdb::Category *> m_sub_categories;
|
|
std::vector <std::vector <rdb::Category *> > m_layer_categories;
|
|
std::vector <db::Cell *> m_sub_cells;
|
|
std::vector <std::vector <unsigned int> > m_sub_output_layers;
|
|
rdb::Database *m_rdb;
|
|
rdb::Cell *m_rdb_cell;
|
|
unsigned int m_progress;
|
|
QMutex m_mutex;
|
|
std::string m_result_string;
|
|
size_t m_nx, m_ny;
|
|
std::map<std::pair<db::LayerProperties, db::Coord>, std::vector<std::vector<size_t> > > m_results;
|
|
std::map<std::pair<size_t, size_t>, db::Region> m_polygons_to_heal;
|
|
};
|
|
|
|
class XORTask
|
|
: public tl::Task
|
|
{
|
|
public:
|
|
XORTask (bool deep, const std::string &tile_desc, const db::Box &clip_box, const db::Box ®ion_a, const db::Box ®ion_b, unsigned int layer_index, const db::LayerProperties &lp, const std::vector<unsigned int> &la, const std::vector<unsigned int> &lb, int ix, int iy)
|
|
: m_deep (deep), m_tile_desc (tile_desc), m_clip_box (clip_box), m_region_a (region_a), m_region_b (region_b), m_layer_index (layer_index), m_lp (lp), m_la (la), m_lb (lb), m_ix (ix), m_iy (iy)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
bool deep () const
|
|
{
|
|
return m_deep;
|
|
}
|
|
|
|
const std::string &tile_desc () const
|
|
{
|
|
return m_tile_desc;
|
|
}
|
|
|
|
const db::Box &clip_box () const
|
|
{
|
|
return m_clip_box;
|
|
}
|
|
|
|
const db::Box ®ion_a () const
|
|
{
|
|
return m_region_a;
|
|
}
|
|
|
|
const db::Box ®ion_b () const
|
|
{
|
|
return m_region_b;
|
|
}
|
|
|
|
const std::vector<unsigned int> &la () const
|
|
{
|
|
return m_la;
|
|
}
|
|
|
|
const std::vector<unsigned int> &lb () const
|
|
{
|
|
return m_lb;
|
|
}
|
|
|
|
unsigned int layer_index () const
|
|
{
|
|
return m_layer_index;
|
|
}
|
|
|
|
const db::LayerProperties &lp () const
|
|
{
|
|
return m_lp;
|
|
}
|
|
|
|
int ix () const
|
|
{
|
|
return m_ix;
|
|
}
|
|
|
|
int iy () const
|
|
{
|
|
return m_iy;
|
|
}
|
|
|
|
private:
|
|
bool m_deep;
|
|
std::string m_tile_desc;
|
|
db::Box m_clip_box, m_region_a, m_region_b;
|
|
unsigned int m_layer_index;
|
|
db::LayerProperties m_lp;
|
|
std::vector<unsigned int> m_la, m_lb;
|
|
int m_ix, m_iy;
|
|
};
|
|
|
|
class XORWorker
|
|
: public tl::Worker
|
|
{
|
|
public:
|
|
XORWorker (XORJob *job)
|
|
: tl::Worker (), mp_job (job)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
void perform_task (tl::Task *task)
|
|
{
|
|
XORTask *xor_task = dynamic_cast <XORTask *> (task);
|
|
if (xor_task) {
|
|
do_perform (xor_task);
|
|
}
|
|
}
|
|
|
|
private:
|
|
XORJob *mp_job;
|
|
|
|
void do_perform (const XORTask *task);
|
|
void do_perform_tiled (const XORTask *task);
|
|
void do_perform_deep (const XORTask *task);
|
|
};
|
|
|
|
void
|
|
XORWorker::do_perform (const XORTask *xor_task)
|
|
{
|
|
if (xor_task->deep ()) {
|
|
do_perform_deep (xor_task);
|
|
} else {
|
|
do_perform_tiled (xor_task);
|
|
}
|
|
}
|
|
|
|
void
|
|
XORWorker::do_perform_deep (const XORTask *xor_task)
|
|
{
|
|
db::DeepShapeStore dss;
|
|
db::Region rr;
|
|
|
|
unsigned int tol_index = 0;
|
|
for (std::vector <db::Coord>::const_iterator t = mp_job->tolerances ().begin (); t != mp_job->tolerances ().end (); ++t, ++tol_index) {
|
|
|
|
const std::vector<unsigned int> &la = xor_task->la ();
|
|
const std::vector<unsigned int> &lb = xor_task->lb ();
|
|
|
|
if ((!la.empty () && !lb.empty ()) || mp_job->el_handling () != XORJob::EL_summarize) {
|
|
|
|
if (tl::verbosity () >= 10) {
|
|
tl::info << "XOR tool (hierarchical): layer " << xor_task->lp ().to_string () << ", tolerance " << *t * mp_job->dbu ();
|
|
}
|
|
|
|
tl::SelfTimer timer (tl::verbosity () >= 11, "Elapsed time");
|
|
|
|
if (tol_index == 0) {
|
|
|
|
if ((!la.empty () && !lb.empty ()) || mp_job->el_handling () == XORJob::EL_process) {
|
|
|
|
tl::SelfTimer timer (tl::verbosity () >= 21, "Boolean part");
|
|
|
|
db::RecursiveShapeIterator s_a (mp_job->cva ()->layout (), mp_job->cva ()->layout ().cell (mp_job->cva ().cell_index ()), la, xor_task->region_a ());
|
|
db::RecursiveShapeIterator s_b (mp_job->cvb ()->layout (), mp_job->cvb ()->layout ().cell (mp_job->cvb ().cell_index ()), lb, xor_task->region_b ());
|
|
|
|
s_a.set_for_merged_input (true);
|
|
s_b.set_for_merged_input (true);
|
|
|
|
db::Region ra (s_a, dss, db::ICplxTrans (mp_job->cva ()->layout ().dbu () / mp_job->dbu ()));
|
|
db::Region rb (s_b, dss, db::ICplxTrans (mp_job->cvb ()->layout ().dbu () / mp_job->dbu ()));
|
|
|
|
if (mp_job->op () == db::BooleanOp::Xor) {
|
|
rr = ra ^ rb;
|
|
} else if (mp_job->op () == db::BooleanOp::ANotB) {
|
|
rr = ra - rb;
|
|
} else if (mp_job->op () == db::BooleanOp::BNotA) {
|
|
rr = rb - ra;
|
|
}
|
|
|
|
} else if (mp_job->op () == db::BooleanOp::Xor ||
|
|
(mp_job->op () == db::BooleanOp::ANotB && !la.empty ()) ||
|
|
(mp_job->op () == db::BooleanOp::BNotA && !lb.empty ())) {
|
|
|
|
tl::SelfTimer timer (tl::verbosity () >= 21, "Boolean part (shortcut)");
|
|
|
|
db::RecursiveShapeIterator s;
|
|
db::ICplxTrans dbu_scale;
|
|
|
|
if (!la.empty ()) {
|
|
s = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la, xor_task->region_a ());
|
|
dbu_scale = db::ICplxTrans (mp_job->cva ()->layout ().dbu () / mp_job->dbu ());
|
|
} else if (!lb.empty ()) {
|
|
s = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb, xor_task->region_b ());
|
|
dbu_scale = db::ICplxTrans (mp_job->cvb ()->layout ().dbu () / mp_job->dbu ());
|
|
}
|
|
|
|
s.set_for_merged_input (true);
|
|
|
|
rr = db::Region (s, dss, dbu_scale);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (*t > 0) {
|
|
tl::SelfTimer timer (tl::verbosity () >= 21, "Sizing part");
|
|
rr.size (-((*t + 1) / 2), (unsigned int)2);
|
|
rr.size (((*t + 1) / 2), (unsigned int)2);
|
|
}
|
|
|
|
// TODO: no clipping for hierarchical mode yet
|
|
mp_job->issue_region (tol_index, xor_task->layer_index (), rr);
|
|
|
|
mp_job->add_results (xor_task->lp (), *t, rr.count (), xor_task->ix (), xor_task->iy ());
|
|
|
|
} else if (mp_job->op () == db::BooleanOp::Xor ||
|
|
(mp_job->op () == db::BooleanOp::ANotB && !la.empty ()) ||
|
|
(mp_job->op () == db::BooleanOp::BNotA && !lb.empty ())) {
|
|
|
|
if (!la.empty ()) {
|
|
mp_job->issue_string (tol_index, xor_task->layer_index (), tl::to_string (QObject::tr ("Layer not present at all in layout B")));
|
|
mp_job->add_results (xor_task->lp (), *t, missing_in_b, 0, 0);
|
|
}
|
|
|
|
if (!lb.empty ()) {
|
|
mp_job->issue_string (tol_index, xor_task->layer_index (), tl::to_string (QObject::tr ("Layer not present at all in layout A")));
|
|
mp_job->add_results (xor_task->lp (), *t, missing_in_a, 0, 0);
|
|
}
|
|
|
|
}
|
|
|
|
mp_job->next_progress ();
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
XORWorker::do_perform_tiled (const XORTask *xor_task)
|
|
{
|
|
db::ShapeProcessor sp (true);
|
|
|
|
// prepare a layout for the results
|
|
db::Layout xor_results;
|
|
xor_results.dbu (mp_job->dbu ());
|
|
db::Cell &xor_results_cell = xor_results.cell (xor_results.add_cell ());
|
|
xor_results.insert_layer (0);
|
|
|
|
unsigned int tol_index = 0;
|
|
for (std::vector <db::Coord>::const_iterator t = mp_job->tolerances ().begin (); t != mp_job->tolerances ().end (); ++t, ++tol_index) {
|
|
|
|
const std::vector<unsigned int> &la = xor_task->la ();
|
|
const std::vector<unsigned int> &lb = xor_task->lb ();
|
|
|
|
if ((!la.empty () && !lb.empty ()) || mp_job->el_handling () != XORJob::EL_summarize) {
|
|
|
|
if (tl::verbosity () >= (mp_job->has_tiles () ? 20 : 10)) {
|
|
tl::info << "XOR tool: layer " << xor_task->lp ().to_string () << ", tolerance " << *t * mp_job->dbu () << ", tile " << xor_task->tile_desc ();
|
|
}
|
|
|
|
tl::SelfTimer timer (tl::verbosity () >= (mp_job->has_tiles () ? 21 : 11), "Elapsed time");
|
|
|
|
if (tol_index == 0) {
|
|
|
|
if ((!la.empty () && !lb.empty ()) || mp_job->el_handling () == XORJob::EL_process) {
|
|
|
|
tl::SelfTimer timer (tl::verbosity () >= 31, "Boolean part");
|
|
size_t n;
|
|
|
|
if (! merge_before_bool ()) {
|
|
|
|
// Straightforward implementation
|
|
sp.clear ();
|
|
|
|
db::CplxTrans dbu_scale_a (mp_job->cva ()->layout ().dbu () / xor_results.dbu ());
|
|
db::CplxTrans dbu_scale_b (mp_job->cvb ()->layout ().dbu () / xor_results.dbu ());
|
|
|
|
n = 0;
|
|
db::RecursiveShapeIterator s_a;
|
|
if (mp_job->has_tiles ()) {
|
|
s_a = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la, xor_task->region_a ());
|
|
} else {
|
|
s_a = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la);
|
|
}
|
|
s_a.set_for_merged_input (true);
|
|
for ( ; ! s_a.at_end (); ++s_a, ++n) {
|
|
sp.insert (s_a.shape (), dbu_scale_a * s_a.trans (), n * 2);
|
|
}
|
|
|
|
n = 0;
|
|
db::RecursiveShapeIterator s_b;
|
|
if (mp_job->has_tiles ()) {
|
|
s_b = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb, xor_task->region_b ());
|
|
} else {
|
|
s_b = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb);
|
|
}
|
|
s_b.set_for_merged_input (true);
|
|
for (; ! s_b.at_end (); ++s_b, ++n) {
|
|
sp.insert (s_b.shape (), dbu_scale_b * s_b.trans (), n * 2 + 1);
|
|
}
|
|
|
|
db::BooleanOp bool_op (mp_job->op ());
|
|
db::ShapeGenerator sg (xor_results_cell.shapes (0), true /*clear shapes*/);
|
|
db::PolygonGenerator out (sg, false /*don't resolve holes*/, false /*no min. coherence*/);
|
|
sp.process (out, bool_op);
|
|
|
|
} else {
|
|
|
|
// This implementation is faster when a lot of overlapping shapes are involved
|
|
db::Layout merge_helper;
|
|
merge_helper.dbu (mp_job->dbu ());
|
|
db::Cell &merge_helper_cell = merge_helper.cell (merge_helper.add_cell ());
|
|
merge_helper.insert_layer (0);
|
|
merge_helper.insert_layer (1);
|
|
|
|
// This implementation is faster when a lot of overlapping shapes are involved
|
|
if (!la.empty ()) {
|
|
|
|
sp.clear ();
|
|
|
|
db::CplxTrans dbu_scale (mp_job->cva ()->layout ().dbu () / xor_results.dbu ());
|
|
|
|
n = 0;
|
|
db::RecursiveShapeIterator s;
|
|
if (mp_job->has_tiles ()) {
|
|
s = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la, xor_task->region_a ());
|
|
} else {
|
|
s = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la);
|
|
}
|
|
s.set_for_merged_input (true);
|
|
for ( ; ! s.at_end (); ++s, ++n) {
|
|
sp.insert (s.shape (), dbu_scale * s.trans (), n);
|
|
}
|
|
|
|
db::MergeOp op (0);
|
|
db::ShapeGenerator sg (merge_helper_cell.shapes (0), true /*clear shapes*/);
|
|
db::PolygonGenerator out (sg, false /*don't resolve holes*/, false /*no min. coherence*/);
|
|
sp.process (out, op);
|
|
|
|
}
|
|
|
|
if (!lb.empty ()) {
|
|
|
|
sp.clear ();
|
|
|
|
db::CplxTrans dbu_scale (mp_job->cvb ()->layout ().dbu () / xor_results.dbu ());
|
|
|
|
n = 0;
|
|
db::RecursiveShapeIterator s;
|
|
if (mp_job->has_tiles ()) {
|
|
s = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb, xor_task->region_b ());
|
|
} else {
|
|
s = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb);
|
|
}
|
|
s.set_for_merged_input (true);
|
|
for ( ; ! s.at_end (); ++s, ++n) {
|
|
sp.insert (s.shape (), dbu_scale * s.trans (), n);
|
|
}
|
|
|
|
db::MergeOp op (0);
|
|
db::ShapeGenerator sg (merge_helper_cell.shapes (1), true /*clear shapes*/);
|
|
db::PolygonGenerator out (sg, false /*don't resolve holes*/, false /*no min. coherence*/);
|
|
sp.process (out, op);
|
|
|
|
}
|
|
|
|
sp.boolean (merge_helper, merge_helper_cell, 0,
|
|
merge_helper, merge_helper_cell, 1,
|
|
xor_results_cell.shapes (0), mp_job->op (), true, false, true);
|
|
|
|
}
|
|
|
|
} else if (mp_job->op () == db::BooleanOp::Xor ||
|
|
(mp_job->op () == db::BooleanOp::ANotB && !la.empty ()) ||
|
|
(mp_job->op () == db::BooleanOp::BNotA && !lb.empty ())) {
|
|
|
|
db::RecursiveShapeIterator s;
|
|
db::CplxTrans dbu_scale;
|
|
|
|
if (!la.empty ()) {
|
|
if (mp_job->has_tiles ()) {
|
|
s = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la, xor_task->region_a ());
|
|
} else {
|
|
s = db::RecursiveShapeIterator (mp_job->cva ()->layout (), *mp_job->cva ().cell (), la);
|
|
}
|
|
dbu_scale = db::CplxTrans (mp_job->cva ()->layout ().dbu () / xor_results.dbu ());
|
|
} else if (!lb.empty ()) {
|
|
if (mp_job->has_tiles ()) {
|
|
s = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb, xor_task->region_b ());
|
|
} else {
|
|
s = db::RecursiveShapeIterator (mp_job->cvb ()->layout (), *mp_job->cvb ().cell (), lb);
|
|
}
|
|
dbu_scale = db::CplxTrans (mp_job->cvb ()->layout ().dbu () / xor_results.dbu ());
|
|
}
|
|
|
|
s.set_for_merged_input (true);
|
|
|
|
for (; ! s.at_end (); ++s) {
|
|
if (s->is_polygon () || s->is_box () || s->is_path ()) {
|
|
db::Polygon p;
|
|
s->polygon (p);
|
|
p.transform (dbu_scale * s.trans ());
|
|
xor_results_cell.shapes (0).insert (p);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (*t > 0) {
|
|
tl::SelfTimer timer (tl::verbosity () >= (mp_job->has_tiles () ? 31 : 21), "Sizing part");
|
|
sp.size (xor_results, xor_results_cell, 0, xor_results_cell.shapes (0), -((*t + 1) / 2), (unsigned int)2, false);
|
|
sp.size (xor_results, xor_results_cell, 0, xor_results_cell.shapes (0), ((*t + 1) / 2), (unsigned int)2, false);
|
|
}
|
|
|
|
size_t n = 0;
|
|
|
|
for (db::Shapes::shape_iterator s = xor_results_cell.shapes (0).begin (db::ShapeIterator::All); ! s.at_end (); ++s) {
|
|
|
|
if (mp_job->has_tiles ()) {
|
|
|
|
std::vector <db::Polygon> clipped_poly;
|
|
clip_poly (s->polygon (), xor_task->clip_box (), clipped_poly, false /*don't resolve holes*/);
|
|
db::Box inner = xor_task->clip_box ().enlarged (db::Vector (-1, -1));
|
|
|
|
for (std::vector <db::Polygon>::const_iterator cp = clipped_poly.begin (); cp != clipped_poly.end (); ++cp) {
|
|
|
|
mp_job->issue_polygon (tol_index, xor_task->layer_index (), *cp, !cp->box ().inside (inner));
|
|
++n;
|
|
}
|
|
|
|
} else {
|
|
mp_job->issue_polygon (tol_index, xor_task->layer_index (), s->polygon ());
|
|
++n;
|
|
}
|
|
|
|
}
|
|
|
|
mp_job->add_results (xor_task->lp (), *t, n, xor_task->ix (), xor_task->iy ());
|
|
|
|
} else if (mp_job->op () == db::BooleanOp::Xor ||
|
|
(mp_job->op () == db::BooleanOp::ANotB && !la.empty ()) ||
|
|
(mp_job->op () == db::BooleanOp::BNotA && !lb.empty ())) {
|
|
|
|
if (!la.empty ()) {
|
|
mp_job->issue_string (tol_index, xor_task->layer_index (), tl::to_string (QObject::tr ("Layer not present at all in layout B")));
|
|
mp_job->add_results (xor_task->lp (), *t, missing_in_b, 0, 0);
|
|
}
|
|
|
|
if (!lb.empty ()) {
|
|
mp_job->issue_string (tol_index, xor_task->layer_index (), tl::to_string (QObject::tr ("Layer not present at all in layout A")));
|
|
mp_job->add_results (xor_task->lp (), *t, missing_in_a, 0, 0);
|
|
}
|
|
|
|
}
|
|
|
|
mp_job->next_progress ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tl::Worker *
|
|
XORJob::create_worker ()
|
|
{
|
|
return new XORWorker (this);
|
|
}
|
|
|
|
void
|
|
XORToolDialog::run_xor ()
|
|
{
|
|
input_mode_t input_mode = (input_mode_t) mp_ui->input_layers_cbx->currentIndex ();
|
|
output_mode_t output_mode = (output_mode_t) mp_ui->output_cbx->currentIndex ();
|
|
region_mode_t region_mode = (region_mode_t) mp_ui->region_cbx->currentIndex ();
|
|
|
|
int nworkers = mp_ui->threads->value ();
|
|
|
|
db::LayerOffset layer_offset;
|
|
if (output_mode == OMNewLayersA || output_mode == OMNewLayersB) {
|
|
std::string lo = tl::to_string (mp_ui->layer_offset_le->text ());
|
|
tl::Extractor ex (lo.c_str ());
|
|
layer_offset.read (ex);
|
|
}
|
|
|
|
bool axorb = mp_ui->axorb_cb->isChecked ();
|
|
bool anotb = mp_ui->anotb_cb->isChecked ();
|
|
bool bnota = mp_ui->bnota_cb->isChecked ();
|
|
|
|
bool deep = mp_ui->deep->isChecked ();
|
|
|
|
bool summarize = mp_ui->summarize_cb->isChecked ();
|
|
// TODO: make this a user interface feature later
|
|
bool process_el = tl::app_flag ("always-do-xor");
|
|
|
|
int cv_index_a = mp_ui->layouta->current_cv_index ();
|
|
int cv_index_b = mp_ui->layoutb->current_cv_index ();
|
|
|
|
lay::CellView cva = mp_view->cellview (cv_index_a);
|
|
lay::CellView cvb = mp_view->cellview (cv_index_b);
|
|
|
|
// NOTE: basically we should take the common denominator rather than the minimum of the layout's DBU's.
|
|
// But this could be a very small number resulting in coordinate overflow issues.
|
|
double dbu = std::min (cva->layout ().dbu (), cvb->layout ().dbu ());
|
|
|
|
std::map<db::LayerProperties, std::pair<std::vector<unsigned int>, std::vector<unsigned int> >, db::LPLogicalLessFunc> layers;
|
|
|
|
for (db::Layout::layer_iterator la = cva->layout ().begin_layers (); la != cva->layout ().end_layers (); ++la) {
|
|
layers[*(*la).second].first.push_back ((*la).first);
|
|
}
|
|
|
|
for (db::Layout::layer_iterator lb = cvb->layout ().begin_layers (); lb != cvb->layout ().end_layers (); ++lb) {
|
|
layers[*(*lb).second].second.push_back ((*lb).first);
|
|
}
|
|
|
|
// Keep only visible layers if requested. Treat invisible ones as empty.
|
|
if (input_mode == IMVisible) {
|
|
|
|
std::set<unsigned int> visible_layers_a;
|
|
std::set<unsigned int> visible_layers_b;
|
|
|
|
for (lay::LayerPropertiesConstIterator l = mp_view->begin_layers (); l != mp_view->end_layers (); ++l) {
|
|
if (! l->has_children () && l->layer_index () >= 0 && l->cellview_index () == cv_index_a && l->visible (true)) {
|
|
visible_layers_a.insert (l->layer_index ());
|
|
}
|
|
if (! l->has_children () && l->layer_index () >= 0 && l->cellview_index () == cv_index_b && l->visible (true)) {
|
|
visible_layers_b.insert (l->layer_index ());
|
|
}
|
|
}
|
|
|
|
for (std::map<db::LayerProperties, std::pair<std::vector<unsigned int>, std::vector<unsigned int> >, db::LPLogicalLessFunc>::iterator lm = layers.begin (); lm != layers.end (); ) {
|
|
|
|
std::map<db::LayerProperties, std::pair<std::vector<unsigned int>, std::vector<unsigned int> >, db::LPLogicalLessFunc>::iterator lm_next = lm;
|
|
++lm_next;
|
|
|
|
std::sort (lm->second.first.begin (), lm->second.first.end ());
|
|
std::sort (lm->second.second.begin (), lm->second.second.end ());
|
|
|
|
lm->second.first.erase (std::set_intersection (lm->second.first.begin (), lm->second.first.end (), visible_layers_a.begin (), visible_layers_a.end (), lm->second.first.begin ()), lm->second.first.end ());
|
|
lm->second.second.erase (std::set_intersection (lm->second.second.begin (), lm->second.second.end (), visible_layers_b.begin (), visible_layers_b.end (), lm->second.second.begin ()), lm->second.second.end ());
|
|
|
|
if (lm->second.first.empty () && lm->second.second.empty ()) {
|
|
layers.erase (lm);
|
|
}
|
|
|
|
lm = lm_next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
std::vector <db::Coord> tolerances;
|
|
|
|
{
|
|
std::string text (tl::to_string (mp_ui->tolerances->text ()));
|
|
tl::Extractor ex (text.c_str ());
|
|
while (! ex.at_end ()) {
|
|
double t;
|
|
if (! ex.try_read (t) || t < -1e-6) {
|
|
break;
|
|
}
|
|
ex.test (",");
|
|
tolerances.push_back (db::coord_traits <db::Coord>::rounded (t / dbu));
|
|
}
|
|
|
|
std::sort (tolerances.begin (), tolerances.end ());
|
|
tolerances.erase (std::unique (tolerances.begin (), tolerances.end ()), tolerances.end ());
|
|
|
|
if (tolerances.size () == 0) {
|
|
tolerances.push_back (0);
|
|
}
|
|
}
|
|
|
|
// Create a map of new layers for original ones
|
|
std::map <db::LayerProperties, db::LayerProperties> new_layer_props;
|
|
for (std::map<db::LayerProperties, std::pair<std::vector<unsigned int>, std::vector<unsigned int> >, db::LPLogicalLessFunc>::iterator lm = layers.begin (); lm != layers.end (); ++lm) {
|
|
new_layer_props.insert (std::make_pair (lm->first, lm->first));
|
|
}
|
|
|
|
double tile_size = 0; // in micron units
|
|
bool tile_heal = mp_ui->heal_cb->isChecked ();
|
|
|
|
if (! deep) {
|
|
std::string text (tl::to_string (mp_ui->tiling->text ()));
|
|
tl::Extractor ex (text.c_str ());
|
|
double t = 0.0;
|
|
if (ex.try_read (t)) {
|
|
tile_size = t;
|
|
if (tile_size < 1.0) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Invalid tile size (smaller than 1 micron or negative)")));
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string srca = cva->name () + ", Cell " + cva->layout ().cell_name (cva.cell_index ());
|
|
std::string srcb = cvb->name () + ", Cell " + cvb->layout ().cell_name (cvb.cell_index ());
|
|
|
|
// Create the report database or identify the output layout
|
|
rdb::Database *rdb = 0;
|
|
rdb::Cell *rdb_cell = 0;
|
|
int rdb_index = 0;
|
|
|
|
int output_cv = -1;
|
|
db::Layout *output_layout = 0;
|
|
db::Cell *output_cell = 0;
|
|
std::vector <unsigned int> output_layers;
|
|
|
|
if (output_mode == OMMarkerDatabase) {
|
|
|
|
rdb = new rdb::Database ();
|
|
rdb->set_name ("XOR");
|
|
rdb->set_top_cell_name (cva->layout ().cell_name (cva.cell_index ()));
|
|
rdb_cell = rdb->create_cell (rdb->top_cell_name ());
|
|
|
|
rdb_index = mp_view->add_rdb (rdb);
|
|
|
|
rdb->set_description ("Comparison of '" + srca + "' vs. '" + srcb + "'");
|
|
|
|
} else if (output_mode == OMNewLayout) {
|
|
|
|
output_cv = mp_view->create_layout (true);
|
|
output_layout = &mp_view->cellview (output_cv)->layout ();
|
|
output_layout->dbu (dbu);
|
|
|
|
for (std::map <db::LayerProperties, db::LayerProperties>::const_iterator lp = new_layer_props.begin (); lp != new_layer_props.end (); ++lp) {
|
|
output_layers.push_back (output_layout->insert_layer (lp->second));
|
|
lay::LayerProperties lay_lp;
|
|
lay_lp.set_source (lay::ParsedLayerSource (lp->second, output_cv));
|
|
mp_view->init_layer_properties (lay_lp);
|
|
mp_view->insert_layer (mp_view->end_layers (), lay_lp);
|
|
}
|
|
|
|
} else if (output_mode == OMNewLayersA) {
|
|
|
|
output_cv = cv_index_a;
|
|
output_layout = &cva->layout ();
|
|
output_cell = cva.cell ();
|
|
|
|
} else if (output_mode == OMNewLayersB) {
|
|
|
|
output_cv = cv_index_b;
|
|
output_layout = &cvb->layout ();
|
|
output_cell = cvb.cell ();
|
|
|
|
}
|
|
|
|
// Clear undo buffers if layout is created.
|
|
if (output_layout) {
|
|
mp_view->manager ()->clear ();
|
|
}
|
|
|
|
std::vector<db::DBox> boxes;
|
|
|
|
db::DBox overall_box = (db::DBox (cva.cell ()->bbox ()) * cva->layout ().dbu ()) + (db::DBox (cvb.cell ()->bbox ()) * cvb->layout ().dbu ());
|
|
|
|
if (region_mode == RMVisible) {
|
|
overall_box &= mp_view->viewport ().box ();
|
|
boxes.push_back (overall_box);
|
|
} else if (region_mode == RMRulers) {
|
|
ant::Service *ant_service = mp_view->get_plugin <ant::Service> ();
|
|
if (ant_service) {
|
|
ant::AnnotationIterator ant = ant_service->begin_annotations ();
|
|
while (! ant.at_end ()) {
|
|
boxes.push_back (overall_box & db::DBox (ant->p1 (), ant->p2 ()));
|
|
++ant;
|
|
}
|
|
}
|
|
} else {
|
|
boxes.push_back (overall_box);
|
|
}
|
|
|
|
bool was_cancelled = false;
|
|
for (int mode = 0; mode < 3 && ! was_cancelled; ++mode) {
|
|
|
|
rdb::Category *cat = 0;
|
|
db::BooleanOp::BoolOp op;
|
|
std::string op_name;
|
|
std::string op_desc;
|
|
|
|
if (mode == 0 && axorb) {
|
|
op = db::BooleanOp::Xor;
|
|
op_name = "XOR";
|
|
op_desc = "XOR between '" + srca + "' (Layout A) and '" + srcb + "' (Layout B)";
|
|
} else if (mode == 1 && anotb) {
|
|
op = db::BooleanOp::ANotB;
|
|
op_name = "ANOTB";
|
|
op_desc = "Geometry in '" + srca + "' (Layout A) but not in '" + srcb + "' (Layout B)";
|
|
} else if (mode == 2 && bnota) {
|
|
op = db::BooleanOp::BNotA;
|
|
op_name = "BNOTA";
|
|
op_desc = "Geometry in '" + srca + "' (Layout B) but not in '" + srcb + "' (Layout A)";
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
if (output_mode == OMMarkerDatabase) {
|
|
cat = rdb->create_category (op_name);
|
|
cat->set_description (op_desc);
|
|
} else if (output_mode == OMNewLayout) {
|
|
output_cell = &output_layout->cell (output_layout->add_cell (op_name.c_str ()));
|
|
}
|
|
|
|
std::vector <rdb::Category *> sub_categories;
|
|
std::vector <std::vector <rdb::Category *> > layer_categories;
|
|
std::vector <db::Cell *> sub_cells;
|
|
std::vector <std::vector <unsigned int> > sub_output_layers;
|
|
|
|
if (output_mode == OMMarkerDatabase) {
|
|
|
|
// create the categories for database output
|
|
|
|
if (tolerances.size () == 1) {
|
|
|
|
sub_categories.push_back (cat);
|
|
|
|
} else {
|
|
|
|
for (std::vector <db::Coord>::const_iterator t = tolerances.begin (); t != tolerances.end (); ++t) {
|
|
|
|
rdb::Category *subcat;
|
|
subcat = rdb->create_category (cat, tl::sprintf ("Tol_%g", *t * dbu));
|
|
subcat->set_description (tl::sprintf ("XOR tolerance (min width reported): %g um", *t * dbu));
|
|
sub_categories.push_back (subcat);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
layer_categories.reserve (sub_categories.size ());
|
|
for (size_t i = 0; i < sub_categories.size (); ++i) {
|
|
|
|
layer_categories.push_back (std::vector <rdb::Category *> ());
|
|
|
|
for (std::map<db::LayerProperties, std::pair<std::vector<unsigned int>, std::vector<unsigned int> >, db::LPLogicalLessFunc>::const_iterator l = layers.begin (); l != layers.end (); ++l) {
|
|
|
|
rdb::Category *layercat = rdb->create_category (sub_categories [i], l->first.to_string ());
|
|
layercat->set_description ("Results for layer " + l->first.to_string ());
|
|
layer_categories.back ().push_back (layercat);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (output_mode == OMNewLayout) {
|
|
|
|
if (tolerances.size () == 1) {
|
|
|
|
sub_cells.push_back (output_cell);
|
|
sub_output_layers.push_back (output_layers);
|
|
|
|
} else {
|
|
|
|
for (std::vector <db::Coord>::const_iterator t = tolerances.begin (); t != tolerances.end (); ++t) {
|
|
|
|
sub_cells.push_back (&output_layout->cell (output_layout->add_cell (tl::sprintf ("%s_TOL_%g", op_name, *t * dbu).c_str ())));
|
|
output_cell->insert (db::CellInstArray (db::CellInst (sub_cells.back ()->cell_index ()), db::Trans ()));
|
|
|
|
sub_output_layers.push_back (output_layers);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (output_mode == OMNewLayersA || output_mode == OMNewLayersB) {
|
|
|
|
if (tolerances.size () == 1) {
|
|
|
|
db::LayerOffset o = layer_offset;
|
|
if (! o.is_named ()) {
|
|
o.name = "*_" + op_desc; // "_XOR" postfix by default
|
|
}
|
|
|
|
for (std::map <db::LayerProperties, db::LayerProperties>::iterator lp = new_layer_props.begin (); lp != new_layer_props.end (); ++lp) {
|
|
if (lp->first.is_named ()) {
|
|
lp->second.name = lp->first.name;
|
|
}
|
|
lp->second += o;
|
|
}
|
|
|
|
output_layers.clear ();
|
|
for (std::map <db::LayerProperties, db::LayerProperties>::iterator lp = new_layer_props.begin (); lp != new_layer_props.end (); ++lp) {
|
|
output_layers.push_back (output_layout->insert_layer (lp->second));
|
|
lay::LayerProperties lay_lp;
|
|
lay_lp.set_source (lay::ParsedLayerSource (lp->second, output_cv));
|
|
mp_view->init_layer_properties (lay_lp);
|
|
mp_view->insert_layer (mp_view->end_layers (), lay_lp);
|
|
}
|
|
|
|
sub_cells.push_back (output_cell);
|
|
sub_output_layers.push_back (output_layers);
|
|
|
|
} else {
|
|
|
|
for (std::vector <db::Coord>::const_iterator t = tolerances.begin (); t != tolerances.end (); ++t) {
|
|
|
|
db::LayerOffset o = layer_offset;
|
|
if (! o.is_named ()) {
|
|
o.name = "*_" + op_desc + tl::sprintf ("_T%d", int (t - tolerances.begin ()) + 1); // "_XOR" postfix by default
|
|
}
|
|
|
|
for (std::map <db::LayerProperties, db::LayerProperties>::iterator lp = new_layer_props.begin (); lp != new_layer_props.end (); ++lp) {
|
|
if (lp->first.is_named ()) {
|
|
lp->second.name = lp->first.name;
|
|
}
|
|
lp->second += o;
|
|
}
|
|
|
|
output_layers.clear ();
|
|
for (std::map <db::LayerProperties, db::LayerProperties>::iterator lp = new_layer_props.begin (); lp != new_layer_props.end (); ++lp) {
|
|
output_layers.push_back (output_layout->insert_layer (lp->second));
|
|
lay::LayerProperties lay_lp;
|
|
lay_lp.set_source (lay::ParsedLayerSource (lp->second, output_cv));
|
|
mp_view->init_layer_properties (lay_lp);
|
|
mp_view->insert_layer (mp_view->end_layers (), lay_lp);
|
|
}
|
|
|
|
sub_cells.push_back (output_cell);
|
|
sub_output_layers.push_back (output_layers);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
size_t todo_count = 0;
|
|
XORJob::EmptyLayerHandling el_handling = XORJob::EL_optimize;
|
|
if (summarize && output_mode == OMMarkerDatabase) {
|
|
el_handling = XORJob::EL_summarize;
|
|
} else if (process_el) {
|
|
el_handling = XORJob::EL_process;
|
|
}
|
|
XORJob job (nworkers, output_mode, op, el_handling, dbu, cva, cvb, tolerances, sub_categories, layer_categories, sub_cells, sub_output_layers, rdb, rdb_cell);
|
|
|
|
double common_dbu = tl::lcm (cva->layout ().dbu (), cvb->layout ().dbu ());
|
|
|
|
for (std::vector<db::DBox>::const_iterator b = boxes.begin (); b != boxes.end (); ++b) {
|
|
|
|
db::DBox box (tl::round_down (b->left (), common_dbu), tl::round_down (b->bottom (), common_dbu),
|
|
tl::round_up (b->right (), common_dbu), tl::round_up (b->top (), common_dbu));
|
|
|
|
// compute the tiles if required
|
|
db::Box box_a, box_b, box_out;
|
|
db::Coord box_width_a = 0, box_height_a = 0, box_width_out = 0;
|
|
db::Coord box_width_b = 0, box_height_b = 0, box_height_out = 0;
|
|
|
|
size_t ntiles_w = 1, ntiles_h = 1;
|
|
if (box.empty ()) {
|
|
|
|
ntiles_w = ntiles_h = 0;
|
|
|
|
} else {
|
|
|
|
box_a = db::Box (box * (1.0 / cva->layout ().dbu ()));
|
|
box_b = db::Box (box * (1.0 / cvb->layout ().dbu ()));
|
|
box_out = db::Box (box * (1.0 / dbu));
|
|
|
|
if (tile_size > 0.0) {
|
|
|
|
ntiles_w = std::max (size_t (1), size_t (floor (box.width () / tile_size + 0.5)));
|
|
ntiles_h = std::max (size_t (1), size_t (floor (box.height () / tile_size + 0.5)));
|
|
|
|
double box_width = tl::round_up (box.width () / ntiles_w, common_dbu);
|
|
double box_height = tl::round_up (box.height () / ntiles_h, common_dbu);
|
|
|
|
box_width_a = db::coord_traits<db::Coord>::rounded (box_width / cva->layout ().dbu ());
|
|
box_height_a = db::coord_traits<db::Coord>::rounded (box_height / cva->layout ().dbu ());
|
|
|
|
box_width_b = db::coord_traits<db::Coord>::rounded (box_width / cvb->layout ().dbu ());
|
|
box_height_b = db::coord_traits<db::Coord>::rounded (box_height / cvb->layout ().dbu ());
|
|
|
|
box_width_out = db::coord_traits<db::Coord>::rounded (box_width / dbu);
|
|
box_height_out = db::coord_traits<db::Coord>::rounded (box_height / dbu);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Enlarge the tiles by half the maximum tolerance
|
|
db::Coord tile_enlargement = 0;
|
|
for (std::vector <db::Coord>::iterator t = tolerances.begin (); t != tolerances.end (); ++t) {
|
|
db::Coord enlargement = (*t + 1) / 2; // round up
|
|
if (enlargement > tile_enlargement) {
|
|
tile_enlargement = enlargement;
|
|
}
|
|
}
|
|
|
|
db::Coord tile_enlargement_a = db::coord_traits<db::Coord>::rounded_up (tile_enlargement * dbu / cva->layout ().dbu ());
|
|
db::Coord tile_enlargement_b = db::coord_traits<db::Coord>::rounded_up (tile_enlargement * dbu / cvb->layout ().dbu ());
|
|
|
|
if (ntiles_w > 1 || ntiles_h > 1 || region_mode != RMAll /*enforces clip*/) {
|
|
job.set_tiles (true, int (ntiles_w), int (ntiles_h), tile_heal);
|
|
}
|
|
|
|
// create the XOR tasks
|
|
for (size_t nw = 0; nw < ntiles_w; ++nw) {
|
|
|
|
for (size_t nh = 0; nh < ntiles_h; ++nh) {
|
|
|
|
db::Box clip_box (box_out.left () + db::Coord (nw * box_width_out),
|
|
box_out.bottom () + db::Coord (nh * box_height_out),
|
|
(nw == ntiles_w - 1) ? box_out.right () : box_out.left () + db::Coord ((nw + 1) * box_width_out),
|
|
(nh == ntiles_h - 1) ? box_out.top () : box_out.bottom () + db::Coord ((nh + 1) * box_height_out));
|
|
|
|
db::Box region_a (box_a.left () + db::Coord (nw * box_width_a),
|
|
box_a.bottom () + db::Coord (nh * box_height_a),
|
|
(nw == ntiles_w - 1) ? box_a.right () : box_a.left () + db::Coord ((nw + 1) * box_width_a),
|
|
(nh == ntiles_h - 1) ? box_a.top () : box_a.bottom () + db::Coord ((nh + 1) * box_height_a));
|
|
|
|
db::Box region_b (box_b.left () + db::Coord (nw * box_width_b),
|
|
box_b.bottom () + db::Coord (nh * box_height_b),
|
|
(nw == ntiles_w - 1) ? box_b.right () : box_b.left () + db::Coord ((nw + 1) * box_width_b),
|
|
(nh == ntiles_h - 1) ? box_b.top () : box_b.bottom () + db::Coord ((nh + 1) * box_height_b));
|
|
|
|
region_a.enlarge (db::Vector (tile_enlargement_a, tile_enlargement_a));
|
|
region_b.enlarge (db::Vector (tile_enlargement_b, tile_enlargement_b));
|
|
|
|
std::string tile_desc = tl::sprintf ("%d/%d,%d/%d", int (nw + 1), ntiles_w, int (nh + 1), ntiles_h);
|
|
|
|
unsigned int layer_index = 0;
|
|
for (std::map<db::LayerProperties, std::pair<std::vector<unsigned int>, std::vector<unsigned int> >, db::LPLogicalLessFunc>::const_iterator l = layers.begin (); l != layers.end (); ++l, ++layer_index) {
|
|
job.schedule (new XORTask (deep, tile_desc, clip_box, region_a, region_b, layer_index, l->first, l->second.first, l->second.second, int (nw), int (nh)));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
todo_count += ntiles_w * ntiles_h * tolerances.size () * layers.size ();
|
|
|
|
}
|
|
|
|
bool was_cancelled = false;
|
|
|
|
if (todo_count > 0) {
|
|
|
|
tl::SelfTimer timer_tot (tl::verbosity () >= 11, "Total boolean time");
|
|
|
|
// TODO: there should be a general scheme of how thread-specific progress is merged
|
|
// into a global one ..
|
|
XORProgress progress (tl::to_string (QObject::tr ("Performing ")) + op_name, todo_count, 1);
|
|
|
|
// We need to lock the layouts during the processing - in OMNewLayerA and OMNewLayerB mode
|
|
// we actually modify the layout we iterate over
|
|
db::LayoutLocker locker_a (& cva->layout ());
|
|
db::LayoutLocker locker_b (& cvb->layout ());
|
|
|
|
try {
|
|
|
|
job.start ();
|
|
while (job.is_running ()) {
|
|
// This may throw an exception, if the cancel button has been pressed.
|
|
job.update_progress (progress);
|
|
job.wait (100);
|
|
}
|
|
|
|
} catch (tl::BreakException & /*ex*/) {
|
|
job.terminate ();
|
|
was_cancelled = true;
|
|
} catch (tl::Exception &ex) {
|
|
job.terminate ();
|
|
throw ex;
|
|
}
|
|
|
|
if (job.has_error ()) {
|
|
if (mp_view && output_mode == OMMarkerDatabase) {
|
|
mp_view->remove_rdb (rdb_index);
|
|
}
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Errors occurred during processing. First error message says:\n")) + job.error_messages ().front ());
|
|
}
|
|
|
|
// apply healing if required
|
|
job.finish ();
|
|
|
|
}
|
|
|
|
if (was_cancelled && output_mode == OMMarkerDatabase) {
|
|
// If the output mode is database, ask whether to keep the data collected so far.
|
|
// If the answer is yes, remove the RDB.
|
|
// Don't ask if the application has exit (window was closed)
|
|
if (lay::ApplicationBase::instance ()->main_window () && !lay::ApplicationBase::instance ()->main_window ()->exited ()) {
|
|
QMessageBox msgbox (QMessageBox::Question,
|
|
QObject::tr ("Keep Data For Cancelled Job"),
|
|
QObject::tr ("The job has been cancelled. Keep the data collected so far?"),
|
|
QMessageBox::Yes | QMessageBox::No);
|
|
if (msgbox.exec () == QMessageBox::No) {
|
|
if (mp_view) {
|
|
mp_view->remove_rdb (rdb_index);
|
|
}
|
|
output_mode = OMNone;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (mp_view) {
|
|
|
|
if (output_mode == OMMarkerDatabase) {
|
|
mp_view->open_rdb_browser (rdb_index, cv_index_a);
|
|
}
|
|
|
|
mp_view->update_content ();
|
|
|
|
if (output_mode == OMNewLayout && output_cell != 0 && output_cv >= 0) {
|
|
mp_view->select_cell (output_cell->cell_index (), output_cv);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|