Compare commits

...

26 Commits

Author SHA1 Message Date
Matthias Köfferlein ad1a3a0be3
Merge 4eaae15ce0 into 3a069427cd 2025-10-26 16:45:54 +01:00
Matthias Köfferlein 3a069427cd
Merge pull request #2202 from KLayout/bugfix/issue-2201
Fixed issue #2201 (trace path)
2025-10-26 16:35:43 +01:00
Matthias Köfferlein fdeab5d104
Merge pull request #2200 from KLayout/issue/issue-2183
Implementing request from issue #2183
2025-10-26 16:35:29 +01:00
Matthias Köfferlein ff49a81723
Merge pull request #2199 from KLayout/feature/issue-2195
Feature/issue 2195
2025-10-26 16:35:14 +01:00
Matthias Köfferlein d015b98534
Merge pull request #2198 from KLayout/bugfix/issue-2194
Fixing issue #2194 (can't attach key binding to 'forward'/'backward')
2025-10-26 16:34:57 +01:00
Matthias Koefferlein b454d2ae42 Fixed issue #2201 (trace path)
* you can zoom in now to select the end point. Problem was
  actually that zooming in was a problem when the start point
  went out of the viewport

In addition:
* Messages are sticky now ("Click on second point")
* "Esc" will cancel path trace mode
* The cursor switches back to normal after tracing
2025-10-25 23:12:22 +02:00
Matthias Koefferlein b9906180e8 Implementing request from issue #2183
For strmxor, --drop-empty-cells now is default.
To explicitly turn it OFF, use

strmxor --drop-empty-cells=false ...
2025-10-25 17:54:26 +02:00
Matthias Koefferlein fc3185165f Added some tests 2025-10-25 17:16:13 +02:00
Matthias Koefferlein b523f05f80 Some typos fixed 2025-10-25 00:33:25 +02:00
Matthias Koefferlein e8e2858af3 Implemented a solution for #2195
A new PCell method cell_name/cell_name_impl that
delivers a cell name, which is used for "convert to
static cell" and as cell name in general.

Needs testing.
2025-10-25 00:21:04 +02:00
Matthias Koefferlein e6e85ab3b3 FIxing issue #2194 (can't attach key binding to 'forward'/'backward') 2025-10-23 21:49:24 +02:00
Matthias Koefferlein 4eaae15ce0 Fixing Windows builds, another step 2024-09-08 17:20:43 +02:00
Matthias Koefferlein 66ee87b725 Fixing Windows builds 2024-09-08 10:08:43 +02:00
Matthias Koefferlein 36002c38b8 Adding a header that was missing 2024-09-07 17:52:24 +02:00
Matthias Koefferlein ccf5314fdb Typo fixed 2024-08-29 22:54:37 +02:00
Matthias Koefferlein a2e449989e GSI binding of Region#binned_area - experimental! 2024-08-29 22:53:19 +02:00
Matthias Koefferlein 8fa308b74c Renaming 2024-08-29 22:17:17 +02:00
Matthias Koefferlein b7a2926f1f More tests for area collector, debugging 2024-08-29 21:48:56 +02:00
Matthias Koefferlein 9d45a01abf String representation of tl::BitSet and tl::BitSetMask 2024-08-29 21:30:21 +02:00
Matthias Koefferlein 8c596fddc3 WIP: debugging 2024-08-29 00:38:54 +02:00
Matthias Koefferlein 863d835ff7 WIP: tagged area collector 2024-08-29 00:12:30 +02:00
Matthias Koefferlein b74e9567fd More tests 2024-08-27 22:07:23 +02:00
Matthias Koefferlein 1890a17544 First tests for bit set map. Good performance :) 2024-08-25 23:12:53 +02:00
Matthias Koefferlein e7f8290edb WIP: debugging, map implementation 2024-08-25 22:33:28 +02:00
Matthias Koefferlein 7219742b87 WIP: tl::BitSetMask 2024-08-25 20:26:48 +02:00
Matthias Koefferlein 100d861336 WIP: tl::BitSet plus tests 2024-08-25 18:20:55 +02:00
51 changed files with 3298 additions and 65 deletions

View File

@ -30,9 +30,23 @@ namespace bd
{
GenericWriterOptions::GenericWriterOptions ()
: m_scale_factor (1.0)
{
db::SaveLayoutOptions save_options;
db::SaveLayoutOptions options;
init_from_options (options);
}
GenericWriterOptions::GenericWriterOptions (const db::SaveLayoutOptions &options)
{
init_from_options (options);
}
void
GenericWriterOptions::init_from_options (const db::SaveLayoutOptions &save_options_nc)
{
// const_cast needed because "get_option_by_name" is not const as it should be
db::SaveLayoutOptions &save_options = const_cast<db::SaveLayoutOptions &> (save_options_nc);
m_scale_factor = 1.0;
m_dbu = save_options.get_option_by_name ("dbu").to_double ();
m_libname = save_options.get_option_by_name ("libname").to_string ();

View File

@ -53,6 +53,13 @@ public:
*/
GenericWriterOptions ();
/**
* @brief Constructor
*
* The "options" object specifies the defaults to be used.
*/
GenericWriterOptions (const db::SaveLayoutOptions &options);
/**
* @brief Adds the generic options to the command line parser object
* The format string gives a hint about the target format. Certain options will be
@ -142,6 +149,7 @@ private:
int m_dxf_polygon_mode;
void set_oasis_substitution_char (const std::string &text);
void init_from_options (const db::SaveLayoutOptions &options);
};
}

View File

@ -380,7 +380,9 @@ BD_PUBLIC int strmxor (int argc, char *argv[])
generic_reader_options_a.add_options (cmd);
generic_reader_options_b.add_options (cmd);
bd::GenericWriterOptions writer_options;
db::SaveLayoutOptions def_writer_options;
def_writer_options.set_dont_write_empty_cells (true);
bd::GenericWriterOptions writer_options (def_writer_options);
writer_options.add_options (cmd);
cmd << tl::arg ("input_a", &infile_a, "The first input file (any format, may be gzip compressed)")
@ -447,7 +449,7 @@ BD_PUBLIC int strmxor (int argc, char *argv[])
cmd.parse (argc, argv);
if (top_a.empty () != top_b.empty ()) {
throw tl::Exception ("Both -ta|--top-a and -tb|--top-b top cells must be given");
throw tl::Exception ("Both -ta|--top-a and -tb|--top-b top cells must be given, not just one of them");
}
if (tolerances.empty ()) {

View File

@ -129,6 +129,48 @@ TEST(1A_Deep)
std::string output = this->tmp_file ("tmp.oas");
const char *argv[] = { "x", "--deep", "--drop-empty-cells=false", input_a.c_str (), input_b.c_str (), output.c_str () };
EXPECT_EQ (strmxor (sizeof (argv) / sizeof (argv[0]), (char **) argv), 1);
db::Layout layout;
{
tl::InputStream stream (output);
db::Reader reader (stream);
reader.read (layout);
}
db::compare_layouts (this, layout, au, db::NormalizationMode (db::NoNormalization | db::AsPolygons));
EXPECT_EQ (cap.captured_text (),
"Layer 10/0 is not present in first layout, but in second\n"
"Result summary (layers without differences are not shown):\n"
"\n"
" Layer Output Differences (hierarchical shape count)\n"
" ----------------------------------------------------------------\n"
" 3/0 3/0 3\n"
" 6/0 6/0 314\n"
" 8/1 8/1 1\n"
" 10/0 - (no such layer in first layout)\n"
"\n"
);
}
TEST(1A_DeepNoEmptyCells)
{
tl::CaptureChannel cap;
std::string input_a = tl::testdata ();
input_a += "/bd/strmxor_in1.gds";
std::string input_b = tl::testdata ();
input_b += "/bd/strmxor_in2.gds";
std::string au = tl::testdata ();
au += "/bd/strmxor_au1d2.oas";
std::string output = this->tmp_file ("tmp.oas");
const char *argv[] = { "x", "--deep", input_a.c_str (), input_b.c_str (), output.c_str () };
EXPECT_EQ (strmxor (sizeof (argv) / sizeof (argv[0]), (char **) argv), 1);
@ -342,7 +384,7 @@ TEST(2_Deep)
std::string output = this->tmp_file ("tmp.oas");
const char *argv[] = { "x", "-u", "--no-summary", "-l", input_a.c_str (), input_b.c_str (), output.c_str () };
const char *argv[] = { "x", "-u", "--no-summary", "--drop-empty-cells=false", "-l", input_a.c_str (), input_b.c_str (), output.c_str () };
EXPECT_EQ (strmxor (sizeof (argv) / sizeof (argv[0]), (char **) argv), 1);
@ -508,7 +550,7 @@ TEST(3_Deep)
std::string output = this->tmp_file ("tmp.oas");
// NOTE: -p is ignored in deep mode
const char *argv[] = { "x", "-u", "--no-summary", "-p=1.0", "-n=4", input_a.c_str (), input_b.c_str (), output.c_str () };
const char *argv[] = { "x", "-u", "--drop-empty-cells=false", "--no-summary", "-p=1.0", "-n=4", input_a.c_str (), input_b.c_str (), output.c_str () };
EXPECT_EQ (strmxor (sizeof (argv) / sizeof (argv[0]), (char **) argv), 1);
@ -607,7 +649,7 @@ TEST(4_Deep)
std::string output = this->tmp_file ("tmp.oas");
const char *argv[] = { "x", "-u", "--no-summary", "-p=1.0", "-n=4", "-t=0.0,0.005,0.01,0.02,0.09,0.1", input_a.c_str (), input_b.c_str (), output.c_str () };
const char *argv[] = { "x", "-u", "--drop-empty-cells=false", "--no-summary", "-p=1.0", "-n=4", "-t=0.0,0.005,0.01,0.02,0.09,0.1", input_a.c_str (), input_b.c_str (), output.c_str () };
EXPECT_EQ (strmxor (sizeof (argv) / sizeof (argv[0]), (char **) argv), 1);
@ -673,7 +715,7 @@ TEST(5_Deep)
std::string output = this->tmp_file ("tmp.oas");
const char *argv[] = { "x", "-u", "--no-summary", "-b=1000", "-t=0.0,0.005,0.01,0.02,0.09,0.1", input_a.c_str (), input_b.c_str (), output.c_str () };
const char *argv[] = { "x", "-u", "--drop-empty-cells=false", "--no-summary", "-b=1000", "-t=0.0,0.005,0.01,0.02,0.09,0.1", input_a.c_str (), input_b.c_str (), output.c_str () };
EXPECT_EQ (strmxor (sizeof (argv) / sizeof (argv[0]), (char **) argv), 1);

View File

@ -149,6 +149,18 @@ created as well.
This method must be reimplemented in a PCell class to identify the PCell in human-readable form.
This text is shown in the cell tree for the PCell for example.
@method cell_name_impl
@brief Delivers the cell name to be used for the PCell variant
A PCell variant is represented in the cell tree by a placeholder cell. By
default, the name of this cell is the PCell name. Since multiple variants
may exist, usually a disambiguator is added to the name (e.g. "..$1").
This method allows encoding the PCell parameters into that cell name,
so the PCell variant is easier to identify in the cell tree - for example
in the GDS file - instead of the unspecific disambiguator.
@method produce_impl
@brief Produces the layout
@ -466,6 +478,19 @@ module RBA
text
end
# implementation of cell_name
def cell_name(parameters)
self._start
@param_values = parameters
text = ""
begin
text = cell_name_impl
ensure
self._finish
end
text
end
# get the parameters
def get_parameters
@param_decls
@ -568,6 +593,11 @@ module RBA
""
end
# default implementation
def cell_name_impl
self.name
end
# default implementation
def coerce_parameters_impl
end

View File

@ -158,6 +158,18 @@ created as well.
This method must be reimplemented in a PCell class to identify the PCell in human-readable form.
This text is shown in the cell tree for the PCell for example.
@method cell_name_impl
@brief Delivers the cell name to be used for the PCell variant
A PCell variant is represented in the cell tree by a placeholder cell. By
default, the name of this cell is the PCell name. Since multiple variants
may exist, usually a disambiguator is added to the name (e.g. "..$1").
This method allows encoding the PCell parameters into that cell name,
so the PCell variant is easier to identify in the cell tree - for example
in the GDS file - instead of the unspecific disambiguator.
@method produce_impl
@brief Produces the layout

View File

@ -8,6 +8,7 @@ DEFINES += MAKE_DB_LIBRARY
SOURCES = \
dbArray.cc \
dbBinnedAreaCollector.cc \
dbBox.cc \
dbBoxConvert.cc \
dbBoxScanner.cc \
@ -245,6 +246,7 @@ SOURCES = \
HEADERS = \
dbArray.h \
dbBinnedAreaCollector.h \
dbBoxConvert.h \
dbBox.h \
dbBoxScanner.h \

View File

@ -0,0 +1,127 @@
/*
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 "dbBinnedAreaCollector.h"
#include "dbTypes.h"
#include "dbRegion.h"
#include "gsiDecl.h"
#include <vector>
namespace db
{
namespace
{
class AreaReceiver
: public db::binned_area_receiver<unsigned int>
{
public:
typedef db::coord_traits<db::Coord>::area_type area_type;
AreaReceiver (unsigned int count)
{
m_areas.resize (count, 0.0);
}
virtual void add_area (area_type area, const unsigned int &index)
{
m_areas [index] += area;
}
const std::vector<area_type> &get () const
{
return m_areas;
}
private:
std::vector<area_type> m_areas;
};
}
// NOTE: this does not belong here. It is an experimental feature
static std::vector<AreaReceiver::area_type>
binned_area (const std::vector<const db::Region *> &inputs, const std::vector<std::string> &vectors)
{
db::EdgeProcessor ep;
unsigned int index = 0;
for (auto r = inputs.begin (); r != inputs.end (); ++r, ++index) {
for (auto p = (*r)->begin (); ! p.at_end (); ++p) {
ep.insert (*p, index);
}
}
tl::bit_set_map<unsigned int> bsm;
index = 0;
for (auto i = vectors.begin (); i != vectors.end (); ++i, ++index) {
bsm.insert (tl::BitSetMask (*i), index);
}
bsm.sort ();
AreaReceiver rec (index);
db::binned_area_collector<unsigned int> coll (bsm, rec);
ep.process (coll, coll);
return rec.get ();
}
gsi::ClassExt<db::Region> extend_region_by_binned_area (
gsi::method ("binned_area", &binned_area, gsi::arg ("inputs"), gsi::arg ("masks"),
"@brief Computes the areas of a binned decomposition of the overall region.\n"
"In this function, the overall region is decomposed into subregions with different overlap situations. "
"Each overlap case is assigned a bin using a bit mask from the 'masks' argument. "
"Each bit corresponds to one input from 'inputs' - bit 0 is the first one etc.\n"
"The masks are strings of characters 0, 1 or 'X', representing 'inside', 'outside' and "
"'any' for the respective input. The first character represents the first input, the second the second input etc.\n"
"Missing characters are treated as 'any', so the empty string matches every situation.\n"
"\n"
"The result is a vector of accumulated areas for each bin identified by one mask. "
"Bins may overlay if multiple masks match, so the total sum of areas is not necessarily "
"identical to the total area. A bin with an empty string mask will deliver the total area.\n"
"\n"
"Merge semantics always applies - i.e. all shapes inside the regions are conceptually "
"merged in 'positive wrap count' mode before computing the area. Hence overlapping shapes "
"per input region just count once.\n"
"\n"
"Example:\n"
"\n"
"@code\n"
"r1 = RBA::Region::new\n"
"r1.insert(RBA::Box::new(0, 0, 1000, 2000))\n"
"\n"
"r2 = RBA::Region::new\n"
"r2.insert(RBA::Box::new(500, 1000, 1500, 3000))\n"
"\n"
"areas = RBA::Region::binned_area([ r1, r2 ], [ \"10\", \"01\", \"\" ])\n"
"r1_not_r2, r2_not_r1, all = areas\n"
"@/code\n"
"\n"
"This feature is highly experimental."
)
);
}

View File

@ -0,0 +1,237 @@
/*
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
*/
#ifndef HDR_dbBinnedAreaCollector
#define HDR_dbBinnedAreaCollector
#include "dbCommon.h"
#include "dbEdgeProcessor.h"
#include "tlBitSetMap.h"
namespace db {
/**
* @brief The receiver for the binned partial areas
*
* See description of binned_area_collector for details.
*/
template <class Value>
class DB_PUBLIC_TEMPLATE binned_area_receiver
{
public:
typedef db::coord_traits<db::Coord>::area_type area_type;
/**
* @brief Constructor
*/
binned_area_receiver<Value> () { }
/**
* @brief Destructor
*/
virtual ~binned_area_receiver () { }
/**
* @brief This method gets called when the scanline process starts
*/
virtual void start () { }
/**
* @brief This method gets called when the scanline process finishes
*/
virtual void finish () { }
/**
* @brief Adds some partial area with the given value
*/
virtual void add_area (area_type /*area*/, const Value & /*value*/) { }
};
/**
* @brief A helper class providing an inserter that is the connection between the receiver and the provider
*/
template <class Value>
class DB_PUBLIC_TEMPLATE binned_area_inserter
{
public:
typedef db::coord_traits<db::Coord>::area_type area_type;
binned_area_inserter<Value> (area_type area, binned_area_receiver<Value> *receiver)
: m_area (area), mp_receiver (receiver)
{
// .. nothing yet ..
}
// methods necessary, so this object can act as an inserter
binned_area_inserter<Value> &operator* () { return *this; }
binned_area_inserter<Value> &operator++ (int) { return *this; }
binned_area_inserter<Value> &operator= (const Value &value)
{
mp_receiver->add_area (m_area, value);
return *this;
}
private:
area_type m_area;
binned_area_receiver<Value> *mp_receiver;
};
/**
* @brief Provides the operation and edge receiver part of the binned area collector
*
* Use this object both as the edge operator and as an edge collector.
* After running the edge processor, use "area" to obtain the area.
*
* This method collects "binned areas". That is, each field of the area divided by
* the edges carries a bit set which is made from the combinations of overlapping
* layers. The layers are given by the property number where the number is the
* bit set in the bit field. Hence, every field is associated with a bit set.
*
* The Area collector will now report the field's areas for accumulation together with
* a field value that is obtained from the bit set map. As the bit set map
* may deliver multiple fields, multiple such values can be present for each field.
* The areas are reported through the binned_area_receiver object. This object
* is supposed to add up the areas in an application specific fashion.
*/
template <class Value>
class DB_PUBLIC_TEMPLATE binned_area_collector
: public EdgeEvaluatorBase,
public EdgeSink
{
public:
typedef db::coord_traits<db::Coord>::area_type area_type;
/**
* @brief Constructor
*/
binned_area_collector<Value> (const tl::bit_set_map<Value> &bsm, binned_area_receiver<Value> &receiver)
: mp_bsm (&bsm), mp_receiver (&receiver), m_state_one_bits (0), m_prev_one_bits (0)
{
// .. nothing yet ..
}
// implementation of EdgeEvaluatorBase
virtual void reset ()
{
m_prev = tl::BitSet ();
m_state = tl::BitSet ();
m_state_one_bits = 0;
m_prev_one_bits = 0;
}
virtual void begin_group ()
{
m_prev = m_state;
m_prev_one_bits = m_state_one_bits;
}
virtual int edge (bool north, bool enter, property_type p)
{
if (north) {
while (m_counts.size () <= p) {
m_counts.push_back (0);
}
int &count = m_counts [p];
if (enter) {
if (count == 0) {
m_state.set (p);
++m_state_one_bits;
}
++count;
} else {
--count;
if (count == 0) {
m_state.reset (p);
--m_state_one_bits;
}
}
// this will call "put" when the group is finished
return 1;
} else {
return 0;
}
}
virtual bool is_reset () const
{
return m_state_one_bits == 0;
}
virtual bool prefer_touch () const
{
// leave events come before enter events
return false;
}
virtual bool selects_edges () const
{
// select_edge is not needed
return false;
}
// implementation of EdgeSink
virtual void start ()
{
mp_receiver->start ();
}
virtual void flush ()
{
mp_receiver->finish ();
}
virtual void put (const db::Edge &, int)
{
// not used.
}
virtual void put (const db::Edge &edge)
{
area_type partial_area = area_type (edge.p1 ().x () + edge.p2 ().x ()) * area_type (edge.dy ()) * 0.5;
if (m_prev_one_bits > 0) {
mp_bsm->lookup (m_prev, binned_area_inserter<Value> (partial_area, mp_receiver));
}
if (m_state_one_bits > 0) {
mp_bsm->lookup (m_state, binned_area_inserter<Value> (-partial_area, mp_receiver));
}
}
private:
area_type m_area_sum;
const tl::bit_set_map<Value> *mp_bsm;
binned_area_receiver<Value> *mp_receiver;
tl::BitSet m_prev, m_state;
std::vector<int> m_counts;
unsigned int m_state_one_bits, m_prev_one_bits;
};
}
#endif

View File

@ -862,6 +862,12 @@ Cell::get_basic_name () const
return layout ()->cell_name (cell_index ());
}
std::string
Cell::get_variant_name () const
{
return get_basic_name ();
}
std::string
Cell::get_qualified_name () const
{

View File

@ -890,7 +890,7 @@ public:
void set_name (const std::string &name);
/**
* @brief Get the basic name
* @brief Gets the basic name
*
* The basic name of the cell is either the cell name or the cell name in the
* target library (for library proxies) or the PCell name (for PCell proxies).
@ -898,6 +898,14 @@ public:
*/
virtual std::string get_basic_name () const;
/**
* @brief Gets the variant name
*
* The variant name is the PCell's "cell_name" - which may encode the PCell parameters
* into a formal name. Usually that is identical to the PCell name.
*/
virtual std::string get_variant_name () const;
/**
* @brief Gets the display name
*

View File

@ -1670,6 +1670,7 @@ public:
void next_coincident ()
{
m_pn = m_ps = 0;
mp_op->begin_group ();
}
void end_coincident ()

View File

@ -265,6 +265,7 @@ public:
virtual void reset () { }
virtual void reserve (size_t /*n*/) { }
virtual void begin_group () { }
virtual int edge (bool /*north*/, bool /*enter*/, property_type /*p*/) { return 0; }
virtual int select_edge (bool /*horizontal*/, property_type /*p*/) { return 0; }
virtual int compare_ns () const { return 0; }

View File

@ -2462,7 +2462,7 @@ Layout::get_pcell_variant_dict (pcell_id_type pcell_id, const std::map<std::stri
pcell_variant_type *variant = header->get_variant (*this, parameters);
if (! variant) {
std::string b (header->get_name ());
std::string b (header->declaration ()->get_cell_name (parameters));
if (m_cell_map.find (b.c_str ()) != m_cell_map.end ()) {
b = uniquify_cell_name (b.c_str ());
}
@ -2501,7 +2501,7 @@ Layout::get_pcell_variant (pcell_id_type pcell_id, const std::vector<tl::Variant
pcell_variant_type *variant = header->get_variant (*this, parameters);
if (! variant) {
std::string b (header->get_name ());
std::string b (header->declaration ()->get_cell_name (parameters));
if (m_cell_map.find (b.c_str ()) != m_cell_map.end ()) {
b = uniquify_cell_name (b.c_str ());
}
@ -2627,9 +2627,18 @@ Layout::convert_cell_to_static (db::cell_index_type ci)
const cell_type &org_cell = cell (ci);
// Note: convert to static cell by explicitly cloning to the db::Cell class
ret_ci = add_cell (org_cell.get_basic_name ().c_str ());
std::string vn = org_cell.get_variant_name ();
if (vn == std::string (cell_name (ci), vn.size ())) {
// there is a cell name conflict: give priority to the static cell, so it
// will see the variant name or at least the original disambiguated name
std::string rename_org = uniquify_cell_name (vn.c_str ());
vn = cell_name (ci);
rename_cell (ci, rename_org.c_str ());
}
ret_ci = add_cell (vn.c_str ());
cell_type &new_cell = cell (ret_ci);
// Note: we convert to static cell by explicitly converting to the db::Cell class
new_cell = org_cell;
new_cell.set_cell_index (ret_ci);
@ -3126,6 +3135,12 @@ Layout::basic_name (cell_index_type cell_index) const
return cell (cell_index).get_basic_name ();
}
std::string
Layout::variant_name (cell_index_type cell_index) const
{
return cell (cell_index).get_variant_name ();
}
void
Layout::register_lib_proxy (db::LibraryProxy *lib_proxy)
{
@ -3161,7 +3176,7 @@ Layout::get_lib_proxy (Library *lib, cell_index_type cell_index)
} else {
// create a new unique name
std::string b (lib->layout ().basic_name (cell_index));
std::string b (lib->layout ().variant_name (cell_index));
if (m_cell_map.find (b.c_str ()) != m_cell_map.end ()) {
b = uniquify_cell_name (b.c_str ());
}

View File

@ -675,7 +675,7 @@ public:
std::string display_name (cell_index_type cell_index) const;
/**
* @brief Return the basic name for the given cell
* @brief Returns the basic name for the given cell
*
* This method is forwarded to the respective method of the cell.
* The basic name is the "original" cell name within the library or
@ -684,6 +684,15 @@ public:
*/
std::string basic_name (cell_index_type cell_index) const;
/**
* @brief Returns the variant name for the given cell
*
* The variant name usually is the basic name. For PCells, this name
* can encode PCell parameters, depending on the definition of the
* PCell.
*/
std::string variant_name (cell_index_type cell_index) const;
/**
* @brief Add a cell object with the given ID and name
*

View File

@ -257,6 +257,22 @@ LibraryProxy::get_basic_name () const
}
}
std::string
LibraryProxy::get_variant_name () const
{
Library *lib = LibraryManager::instance ().lib (lib_id ());
if (lib) {
if (! lib->layout ().is_valid_cell_index (library_cell_index ())) {
return "<defunct>";
} else {
const db::Cell &lib_cell = lib->layout ().cell (library_cell_index ());
return lib_cell.get_variant_name ();
}
} else {
return Cell::get_variant_name ();
}
}
std::string
LibraryProxy::get_display_name () const
{

View File

@ -96,25 +96,28 @@ public:
/**
* @brief Gets the basic name
*
* The basic name of the cell is either the cell name or the cell name in the
* target library (for library proxies) or the PCell name (for PCell proxies).
* The actual name may be different by a extension to make it unique.
* This returns the basic name of the proxy target
*/
virtual std::string get_basic_name () const;
/**
* @brief Gets the variant name
*
* This returns the basic name of the proxy target
*/
virtual std::string get_variant_name () const;
/**
* @brief Gets the display name
*
* The display name is some "nice" descriptive name of the cell (variant)
* For normal cells this name is equivalent to the normal cell name.
* This returns the basic name of the proxy target
*/
virtual std::string get_display_name () const;
/**
* @brief Gets the qualified name
*
* The qualified name for a library proxy is made from the library name, a
* dot and the cell's name.
* Gets a combination of the library name and the target cell's qualified name
*/
virtual std::string get_qualified_name () const;

View File

@ -673,6 +673,18 @@ public:
return std::string ();
}
/**
* @brief Gets a cell name for the PCell, which can depend on the parameters
*
* The actual cell name in the layout may differ by disambiguation. This method
* delivers a proposal for a cell name.
* By default, the PCell name is returned.
*/
virtual std::string get_cell_name (const pcell_parameters_type &) const
{
return m_name;
}
/**
* @brief Returns the description text of the PCell
*

View File

@ -86,6 +86,17 @@ PCellVariant::get_basic_name () const
}
}
std::string
PCellVariant::get_variant_name () const
{
const PCellHeader *header = pcell_header ();
if (header) {
return m_variant_name;
} else {
return Cell::get_basic_name ();
}
}
std::string
PCellVariant::get_display_name () const
{
@ -172,6 +183,7 @@ PCellVariant::update (ImportLayerMapping *layer_mapping)
header->declaration ()->produce (*layout (), layer_ids, plist, *this);
m_display_name = header->declaration ()->get_display_name (plist);
m_variant_name = header->declaration ()->get_cell_name (plist);
} catch (tl::Exception &ex) {

View File

@ -94,42 +94,37 @@ public:
}
/**
* @brief Get the basic name
*
* The basic name of the cell is either the cell name or the cell name in the
* target library (for library proxies) or the PCell name (for PCell proxies).
* The actual name may be different by a extension to make it unique.
* @brief Gets the basic name
*/
virtual std::string get_basic_name () const;
/**
* @brief Get the display name
*
* The display name is some "nice" descriptive name of the cell (variant)
* For normal cells this name is equivalent to the normal cell name.
* @brief Gets the variant name
*/
virtual std::string get_variant_name () const;
/**
* @brief Gets the display name
*/
virtual std::string get_display_name () const;
/**
* @brief Unregister a cell from it's context.
* @brief Unregisters a cell from its context.
*/
virtual void unregister ();
/**
* @brief Reregister a cell inside it's context.
* @brief Re-registers a cell inside its context.
*/
virtual void reregister ();
/**
* @brief Update the layout
* @brief Updates the layout
*/
virtual void update (ImportLayerMapping *layer_mapping = 0);
/**
* @brief Tell, if this cell is a proxy cell
*
* Proxy cells are such whose layout represents a snapshot of another entity.
* Such cells can be PCell variants or library references for example.
* @brief Gets a value indicating if this cell is a proxy cell
*/
virtual bool is_proxy () const
{
@ -138,7 +133,7 @@ public:
protected:
/**
* @brief Get the PCell header for this variant
* @brief Gets the PCell header for this variant
*/
PCellHeader *pcell_header ()
{
@ -146,7 +141,7 @@ protected:
}
/**
* @brief Get the PCell header for this variant
* @brief Gets the PCell header for this variant
*/
const PCellHeader *pcell_header () const
{
@ -156,6 +151,7 @@ protected:
private:
pcell_parameters_type m_parameters;
mutable std::string m_display_name;
mutable std::string m_variant_name;
db::pcell_id_type m_pcell_id;
bool m_registered;
};

View File

@ -368,6 +368,7 @@ Class<db::PCellDeclaration> decl_PCellDeclaration_Native ("db", "PCellDeclaratio
gsi::method ("via_types", &db::PCellDeclaration::via_types) +
gsi::method ("description", &db::PCellDeclaration::get_description) +
gsi::method ("display_text", &db::PCellDeclaration::get_display_name, gsi::arg ("parameters")) +
gsi::method ("cell_name", &db::PCellDeclaration::get_cell_name, gsi::arg ("parameters")) +
gsi::method ("layout", &db::PCellDeclaration::layout,
"@brief Gets the Layout object the PCell is registered in or nil if it is not registered yet.\n"
"This attribute has been added in version 0.27.5."
@ -659,6 +660,20 @@ public:
}
}
std::string get_cell_name_fb (const db::pcell_parameters_type &parameters) const
{
return db::PCellDeclaration::get_cell_name (parameters);
}
virtual std::string get_cell_name (const db::pcell_parameters_type &parameters) const
{
if (cb_get_cell_name.can_issue ()) {
return cb_get_cell_name.issue<db::PCellDeclaration, std::string, const db::pcell_parameters_type &> (&db::PCellDeclaration::get_cell_name, parameters);
} else {
return db::PCellDeclaration::get_cell_name (parameters);
}
}
gsi::Callback cb_get_layer_declarations;
gsi::Callback cb_get_parameter_declarations;
gsi::Callback cb_produce;
@ -669,6 +684,7 @@ public:
gsi::Callback cb_coerce_parameters;
gsi::Callback cb_callback;
gsi::Callback cb_get_display_name;
gsi::Callback cb_get_cell_name;
gsi::Callback cb_get_description;
gsi::Callback cb_via_types;
};
@ -682,6 +698,7 @@ Class<PCellDeclarationImpl> decl_PCellDeclaration (decl_PCellDeclaration_Native,
gsi::method ("parameters_from_shape", &PCellDeclarationImpl::parameters_from_shape_fb, "@hide") +
gsi::method ("transformation_from_shape", &PCellDeclarationImpl::transformation_from_shape_fb, "@hide") +
gsi::method ("display_text", &PCellDeclarationImpl::get_display_name_fb, "@hide") +
gsi::method ("cell_name", &PCellDeclarationImpl::get_cell_name_fb, "@hide") +
gsi::method ("wants_lazy_evaluation", &PCellDeclarationImpl::wants_lazy_evaluation_fb, "@hide") +
gsi::method ("description", &PCellDeclarationImpl::get_description_fb, "@hide") +
gsi::method ("via_types", &PCellDeclarationImpl::via_types_fb, "@hide") +
@ -809,6 +826,13 @@ Class<PCellDeclarationImpl> decl_PCellDeclaration (decl_PCellDeclaration_Native,
"@brief Returns the display text for this PCell given a certain parameter set\n"
"Reimplement this method to create a distinct display text for a PCell variant with \n"
"the given parameter set. If this method is not implemented, a default text is created. \n"
) +
gsi::callback ("cell_name", &PCellDeclarationImpl::get_cell_name, &PCellDeclarationImpl::cb_get_cell_name, gsi::arg ("parameters"),
"@brief Returns a cell name used for the PCell variant\n"
"Reimplement this method to create a cell name the system uses for the PCell variant. By default that is the PCell name.\n"
"This feature allows encoding the PCell parameters into the cell name for easier identification of the PCell variant in a cell tree.\n"
"\n"
"This feature has been added in version 0.30.5.\n"
),
"@brief A PCell declaration providing the parameters and code to produce the PCell\n"
"\n"

View File

@ -0,0 +1,156 @@
/*
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 "dbBinnedAreaCollector.h"
#include "dbEdgeProcessor.h"
#include "tlUnitTest.h"
namespace
{
class AreaReceiver
: public db::binned_area_receiver<double>
{
public:
typedef db::coord_traits<db::Coord>::area_type area_type;
AreaReceiver () : m_sum (0.0) { }
virtual void add_area (area_type area, const double &value)
{
m_sum += value * area;
}
double get () const { return m_sum; }
private:
double m_sum;
};
}
TEST(1_Basic)
{
db::EdgeProcessor ep;
ep.insert (db::SimplePolygon (db::Box (0, 0, 1000, 2000)), 0);
ep.insert (db::SimplePolygon (db::Box (500, 1000, 1500, 3000)), 1);
// set up an XOR mask where 1-vs-0 is counted twice
tl::bit_set_map<double> bsm;
tl::BitSetMask bs0;
bs0.set (0, tl::BitSetMask::True);
bs0.set (1, tl::BitSetMask::False);
tl::BitSetMask bs1;
bs1.set (0, tl::BitSetMask::False);
bs1.set (1, tl::BitSetMask::True);
bsm.insert (bs0, 1.0);
bsm.insert (bs1, 2.0);
bsm.sort ();
AreaReceiver rec;
db::binned_area_collector<double> coll (bsm, rec);
ep.process (coll, coll);
EXPECT_EQ (rec.get (), 4500000);
}
TEST(2_ShapesGetMerged)
{
db::EdgeProcessor ep;
ep.insert (db::SimplePolygon (db::Box (0, -1000, 1000, 1000)), 0);
ep.insert (db::SimplePolygon (db::Box (0, 0, 1000, 2000)), 0);
ep.insert (db::SimplePolygon (db::Box (500, 1000, 1500, 3000)), 1);
ep.insert (db::SimplePolygon (db::Box (0, 0, 1000, 2000)), 0);
ep.insert (db::SimplePolygon (db::Box (1000, 1000, 1500, 3000)), 1);
// set up an XOR mask where 1-vs-0 is counted twice
tl::bit_set_map<double> bsm;
tl::BitSetMask bs0;
bs0.set (0, tl::BitSetMask::True);
bs0.set (1, tl::BitSetMask::False);
tl::BitSetMask bs1;
bs1.set (0, tl::BitSetMask::False);
bs1.set (1, tl::BitSetMask::True);
bsm.insert (bs0, 1.0);
bsm.insert (bs1, 2.0);
bsm.sort ();
AreaReceiver rec;
db::binned_area_collector<double> coll (bsm, rec);
ep.process (coll, coll);
EXPECT_EQ (rec.get (), 5500000);
}
TEST(3_TouchingOnly)
{
db::EdgeProcessor ep;
ep.insert (db::SimplePolygon (db::Box (0, -1000, 1000, 1000)), 0);
ep.insert (db::SimplePolygon (db::Box (1000, 0, 2000, 2000)), 1);
ep.insert (db::SimplePolygon (db::Box (1000, 500, 1500, 1500)), 1);
ep.insert (db::SimplePolygon (db::Box (0, 0, 1000, 1000)), 0);
ep.insert (db::SimplePolygon (db::Box (1500, 500, 2000, 2000)), 1);
// set up an XOR mask where 1-vs-0 is counted twice
tl::bit_set_map<double> bsm;
tl::BitSetMask bs0;
bs0.set (0, tl::BitSetMask::True);
bs0.set (1, tl::BitSetMask::False);
tl::BitSetMask bs1;
bs1.set (0, tl::BitSetMask::False);
bs1.set (1, tl::BitSetMask::True);
bsm.insert (bs0, 1.0);
bsm.insert (bs1, 2.0);
bsm.sort ();
AreaReceiver rec;
db::binned_area_collector<double> coll (bsm, rec);
ep.process (coll, coll);
EXPECT_EQ (rec.get (), 6000000);
}
TEST(4_PlainAreaApproximation)
{
db::EdgeProcessor ep;
ep.insert (db::SimplePolygon (db::Box (0, -1000, 1000, 1000)), 0);
ep.insert (db::SimplePolygon (db::Box (0, 0, 1000, 2000)), 0);
ep.insert (db::SimplePolygon (db::Box (500, 1000, 1500, 3000)), 1);
ep.insert (db::SimplePolygon (db::Box (0, 0, 1000, 2000)), 0);
ep.insert (db::SimplePolygon (db::Box (1000, 1000, 1500, 3000)), 1);
tl::bit_set_map<double> bsm;
bsm.insert (tl::BitSetMask (), 1.0);
bsm.sort ();
AreaReceiver rec;
db::binned_area_collector<double> coll (bsm, rec);
ep.process (coll, coll);
EXPECT_EQ (rec.get (), 4500000);
}

View File

@ -88,6 +88,13 @@ class PD
cell.shapes (l_metal0).insert (db::Box (0, 0, width, height));
}
virtual std::string get_display_name (const db::pcell_parameters_type &parameters) const
{
db::Coord width = db::coord_traits<db::Coord>::rounded (parameters[0].to_double () * 1000.0);
db::Coord height = db::coord_traits<db::Coord>::rounded (parameters[1].to_double () * 1000.0);
return tl::sprintf ("PD(W=%d,H=%d)", width, height);
}
};
TEST(0)
@ -252,3 +259,114 @@ TEST(1)
}
}
// PCell names, convert PCell to static
class PD2
: public PD
{
public:
virtual std::string get_cell_name (const db::pcell_parameters_type &parameters) const
{
db::Coord width = db::coord_traits<db::Coord>::rounded (parameters[0].to_double () * 1000.0);
db::Coord height = db::coord_traits<db::Coord>::rounded (parameters[1].to_double () * 1000.0);
return tl::sprintf ("PD_W%d_H%d", width, height);
}
};
TEST(2)
{
db::Manager m (true);
db::Layout layout(&m);
layout.dbu (0.001);
db::LayerProperties p;
p.layer = 23;
p.datatype = 0;
unsigned int l_cont = layout.insert_layer (p);
p.layer = 16;
p.datatype = 0;
unsigned int l_gate = layout.insert_layer (p);
db::Cell &cell_a = layout.cell (layout.add_cell ("A"));
cell_a.shapes(l_cont).insert(db::Box (50, 50, 150, 150));
cell_a.shapes(l_gate).insert(db::Box (0, 0, 200, 1000));
db::Cell &top = layout.cell (layout.add_cell ("TOP"));
db::pcell_id_type pd = layout.register_pcell ("PD", new PD2 ());
std::vector<tl::Variant> parameters;
parameters.push_back (tl::Variant ());
parameters.push_back (tl::Variant ());
parameters.push_back (tl::Variant ());
tl::Variant &width = parameters[0];
tl::Variant &height = parameters[1];
tl::Variant &orientation = parameters[2];
width = 0.5;
height = 1.0;
orientation = long (0);
db::cell_index_type pd1 = layout.get_pcell_variant (pd, parameters);
top.insert (db::CellInstArray (db::CellInst (pd1), db::Trans (db::Vector (0, 0))));
EXPECT_EQ (layout.display_name (pd1), "PD(W=500,H=1000)");
EXPECT_EQ (layout.cell_name (pd1), "PD_W500_H1000");
EXPECT_EQ (layout.variant_name (pd1), "PD_W500_H1000");
width = 0.4;
height = 0.8;
db::cell_index_type pd2 = layout.get_pcell_variant (pd, parameters);
top.insert (db::CellInstArray (db::CellInst (pd2), db::Trans (db::Vector (0, 2000))));
EXPECT_EQ (layout.display_name (pd2), "PD(W=400,H=800)");
EXPECT_EQ (layout.cell_name (pd2), "PD_W400_H800");
EXPECT_EQ (layout.variant_name (pd2), "PD_W400_H800");
EXPECT_NE (pd1, pd2);
width = 0.4;
height = 0.8;
orientation = long (1);
db::cell_index_type pd3 = layout.get_pcell_variant (pd, parameters);
auto i3 = top.insert (db::CellInstArray (db::CellInst (pd3), db::Trans (db::Vector (2000, 0))));
EXPECT_EQ (layout.display_name (pd3), "PD(W=400,H=800)");
EXPECT_EQ (layout.cell_name (pd3), "PD_W400_H800$1");
EXPECT_EQ (layout.variant_name (pd3), "PD_W400_H800");
EXPECT_NE (pd2, pd3);
auto pd3_org = pd3;
pd3 = layout.convert_cell_to_static (pd3);
EXPECT_NE (pd3, pd3_org);
auto ci3 = i3.cell_inst ();
ci3.object ().cell_index (pd3);
top.replace (i3, ci3);
EXPECT_EQ (layout.cell (pd3_org).is_proxy (), true);
EXPECT_EQ (layout.cell (pd3).is_proxy (), false);
EXPECT_EQ (layout.display_name (pd3_org), "PD(W=400,H=800)");
EXPECT_EQ (layout.cell_name (pd3_org), "PD_W400_H800$2");
EXPECT_EQ (layout.variant_name (pd3_org), "PD_W400_H800");
layout.do_cleanup (true);
layout.cleanup ();
EXPECT_EQ (layout.is_valid_cell_index (pd3_org), false);
EXPECT_EQ (layout.display_name (pd3), "PD_W400_H800$1");
EXPECT_EQ (layout.cell_name (pd3), "PD_W400_H800$1");
EXPECT_EQ (layout.variant_name (pd3), "PD_W400_H800$1");
CHECKPOINT ();
db::compare_layouts (_this, layout, tl::testdata () + "/gds/pcell_test20.gds", db::NoNormalization);
}

View File

@ -7,6 +7,7 @@ TARGET = db_tests
include($$PWD/../../lib_ut.pri)
SOURCES = \
dbBinnedAreaCollectorTests.cc \
dbCompoundOperationTests.cc \
dbEdgeNeighborhoodTests.cc \
dbFillToolTests.cc \

View File

@ -1327,6 +1327,13 @@ static std::string path_to_string (const db::Layout &layout, const lay::ObjectIn
void
Service::display_status (bool transient)
{
if (transient && view ()->canvas ()->begin_mouse_receivers () != view ()->canvas ()->end_mouse_receivers ()
&& (*view ()->canvas ()->begin_mouse_receivers ())->claims_message_bar ()) {
// do not display transient if there is a plugin active that has captured the mouse and claims the message bar -
// this way we can use messages in active plugins and still get visual feedback by the transient selection.
return;
}
EditableSelectionIterator r = transient ? begin_transient_selection () : begin_selection ();
EditableSelectionIterator rr = r;

View File

@ -621,6 +621,11 @@ struct LAYBASIC_PUBLIC AbstractMenuItem
return m_primary;
}
void set_primary (bool p)
{
m_primary = p;
}
std::list <AbstractMenuItem> children;
private:

View File

@ -63,7 +63,7 @@ static int inst_point_sel_tests = 10000;
Finder::Finder (bool point_mode, bool top_level_sel)
: m_min_level (0), m_max_level (0),
mp_layout (0), mp_view (0), m_cv_index (0), m_point_mode (point_mode), m_catch_all (false), m_top_level_sel (top_level_sel)
mp_layout (0), mp_view (0), m_cv_index (0), m_point_mode (point_mode), m_catch_all (false), m_consider_viewport (true), m_top_level_sel (top_level_sel)
{
m_distance = std::numeric_limits<double>::max ();
}
@ -482,7 +482,10 @@ ShapeFinder::visit_cell (const db::Cell &cell, const db::Box &hit_box, const db:
checkpoint ();
// Viewport in current cell coordinate space (DBU)
db::Box viewport_box = (vp * db::CplxTrans (layout ().dbu ()) * t).inverted () * db::DBox (0, 0, view ()->viewport ().width (), view ()->viewport ().height ());
db::Box viewport_box;
if (consider_viewport ()) {
viewport_box = (vp * db::CplxTrans (layout ().dbu ()) * t).inverted () * db::DBox (0, 0, view ()->viewport ().width (), view ()->viewport ().height ());
}
if (! m_context_layers.empty ()) {
@ -583,7 +586,7 @@ ShapeFinder::visit_cell (const db::Cell &cell, const db::Box &hit_box, const db:
bool any_valid_edge = m_capture_all_shapes;
for (db::Shape::polygon_edge_iterator e = shape->begin_edge (); ! e.at_end (); ++e) {
if ((*e).clipped (viewport_box).first) {
if (viewport_box.empty () || (*e).clipped (viewport_box).first) {
any_valid_edge = true;
test_edge (t, *e, d, match);
}
@ -606,7 +609,7 @@ ShapeFinder::visit_cell (const db::Cell &cell, const db::Box &hit_box, const db:
++pt;
for (; pt != shape->end_point (); ++pt) {
db::Edge e (p, *pt);
if (e.clipped (viewport_box).first) {
if (viewport_box.empty () || e.clipped (viewport_box).first) {
any_valid_edge = true;
test_edge (t, e, d, match);
}
@ -618,7 +621,7 @@ ShapeFinder::visit_cell (const db::Cell &cell, const db::Box &hit_box, const db:
db::Polygon poly;
shape->polygon (poly);
for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); ++e) {
if ((*e).clipped (viewport_box).first) {
if (viewport_box.empty () || (*e).clipped (viewport_box).first) {
any_valid_edge = true;
test_edge (t, *e, d, match);
}
@ -651,7 +654,7 @@ ShapeFinder::visit_cell (const db::Cell &cell, const db::Box &hit_box, const db:
// convert to polygon and test those edges
db::Polygon poly (box);
for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); ++e) {
if ((*e).clipped (viewport_box).first) {
if (viewport_box.empty () || (*e).clipped (viewport_box).first) {
any_valid_edge = true;
test_edge (t, *e, d, match);
}
@ -819,7 +822,10 @@ InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const d
checkpoint ();
// Viewport in current cell coordinate space (DBU)
db::Box viewport_box = (vp * db::CplxTrans (layout ().dbu ()) * t).inverted () * db::DBox (0, 0, view ()->viewport ().width (), view ()->viewport ().height ());
db::Box viewport_box;
if (consider_viewport ()) {
viewport_box = (vp * db::CplxTrans (layout ().dbu ()) * t).inverted () * db::DBox (0, 0, view ()->viewport ().width (), view ()->viewport ().height ());
}
if (! point_mode ()) {
@ -952,7 +958,7 @@ InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const d
bool any_valid_edge = false;
for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); ++e) {
// only consider edges that cut through the viewport
if ((*e).clipped (viewport_box).first) {
if (viewport_box.empty () || (*e).clipped (viewport_box).first) {
any_valid_edge = true;
test_edge (t, *e, d, match);
}

View File

@ -96,6 +96,25 @@ public:
m_catch_all = f;
}
/**
* @brief Gets a flag indicating that the viewport will be considered
*/
bool consider_viewport () const
{
return m_consider_viewport;
}
/**
* @brief Sets a flag indicating that the viewport will be considered
* If this flag is true (the default), only shapes and instances will be considered
* if edges (or polygons) or boundary edges (for instances) are visible in the
* viewport. If this flag is false, shapes or instances are considered always.
*/
void set_consider_viewport (bool f)
{
m_consider_viewport = f;
}
/**
* @brief Destructor (just provided to please the compiler)
*/
@ -217,6 +236,7 @@ private:
double m_distance;
bool m_point_mode;
bool m_catch_all;
bool m_consider_viewport;
bool m_top_level_sel;
db::box_convert <db::CellInst, false> m_box_convert;
db::box_convert <db::Cell, false> m_cell_box_convert;

View File

@ -197,6 +197,12 @@ PluginDeclaration::init_menu (lay::Dispatcher *dispatcher)
if (! m->copy_from.empty ()) {
// As a general strategy for now we take primary ownership from the copy source as pass it to
// the copy. This is important for "next/prev_display_state" as the first registry is @toolbar
// (which is not accessible by Setup menu) and the second one is "zoom_menu" which should be
// the primary one. For later, we may want to make this configurable (move/leave primary flag).
menu.find_item_exact (m->copy_from)->set_primary (false);
menu.insert_item (m->insert_pos, m->menu_name, menu.action (m->copy_from));
} else if (m->separator) {

View File

@ -621,7 +621,10 @@ END_PROTECTED
void
ViewObjectUI::set_cursor (lay::Cursor::cursor_shape cursor)
{
if (m_cursor != cursor) {
m_cursor = cursor;
realize_cursor ();
}
}
void
@ -629,15 +632,7 @@ ViewObjectUI::set_default_cursor (lay::Cursor::cursor_shape cursor)
{
if (cursor != m_default_cursor) {
m_default_cursor = cursor;
#if defined(HAVE_QT)
if (m_cursor == lay::Cursor::none && mp_widget) {
if (m_default_cursor == lay::Cursor::none) {
mp_widget->unsetCursor ();
} else {
mp_widget->setCursor (lay::Cursor::qcursor (m_default_cursor));
}
}
#endif
realize_cursor ();
}
}
@ -652,11 +647,17 @@ ViewObjectUI::ensure_entered ()
void
ViewObjectUI::begin_mouse_event (lay::Cursor::cursor_shape cursor)
{
m_cursor = cursor;
set_cursor (cursor);
}
void
ViewObjectUI::end_mouse_event ()
{
realize_cursor ();
}
void
ViewObjectUI::realize_cursor ()
{
#if defined(HAVE_QT)
if (mp_widget) {

View File

@ -285,6 +285,14 @@ public:
*/
virtual void drag_cancel () { }
/**
* @brief Gets a value indicating whether the mouse receiver claims the view message bar
*
* If this method returns true, other services are not supposed to emit transient
* messages.
*/
virtual bool claims_message_bar () const { return false; }
/**
* @brief Gets a value indicating whether a cursor position it set
*/
@ -1121,6 +1129,7 @@ private:
void objects_changed ();
int widget_height () const;
int widget_width () const;
void realize_cursor ();
/**
* @brief Register a service

View File

@ -202,13 +202,32 @@ NetTracerDialog::item_double_clicked (QListWidgetItem *item)
}
}
void
NetTracerDialog::drag_cancel ()
{
if (m_mouse_state > 0) {
view ()->message ();
ui ()->ungrab_mouse (this);
set_cursor (lay::Cursor::none);
m_mouse_state = 0;
}
}
bool
NetTracerDialog::claims_message_bar () const
{
return true;
}
bool
NetTracerDialog::mouse_move_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, bool prio)
{
if (prio && m_mouse_state != 0) {
set_cursor (lay::Cursor::cross);
}
return false;
}
@ -397,11 +416,13 @@ NetTracerDialog::do_trace (const db::DBox &start_search_box, const db::DBox &sto
{
unsigned int start_layer = 0;
db::Point start_point;
db::Shape start_shape;
// locate the seed
{
lay::ShapeFinder finder (true /*point mode*/, false /*all levels*/, db::ShapeIterator::All);
finder.set_consider_viewport (false);
// go through all visible layers of all cellviews and find a seed shape
for (lay::LayerPropertiesConstIterator lprop = view ()->begin_layers (); ! lprop.at_end (); ++lprop) {
@ -417,7 +438,7 @@ NetTracerDialog::do_trace (const db::DBox &start_search_box, const db::DBox &sto
}
m_cv_index = r->cv_index ();
start_shape = r->shape ();
start_layer = r->layer ();
}
@ -440,6 +461,12 @@ NetTracerDialog::do_trace (const db::DBox &start_search_box, const db::DBox &sto
start_point = tt.inverted ().trans (start_search_box.center ());
// stop if the center start point is not inside the start polygon
db::Polygon poly;
if (start_shape.polygon (poly) && db::inside_poly (poly.begin_edge (), start_point) < 0) {
return 0;
}
}
// Set up the net tracer environment
@ -455,6 +482,7 @@ NetTracerDialog::do_trace (const db::DBox &start_search_box, const db::DBox &sto
if (trace_path) {
lay::ShapeFinder finder (true /*point mode*/, false /*all levels*/, db::ShapeIterator::All);
finder.set_consider_viewport (false);
// go through all visible layers of all cellviews and find a seed shape
for (lay::LayerPropertiesConstIterator lprop = view ()->begin_layers (); ! lprop.at_end (); ++lprop) {
@ -483,6 +511,12 @@ NetTracerDialog::do_trace (const db::DBox &start_search_box, const db::DBox &sto
stop_point = tt.inverted ().trans (stop_search_box.center ());
stop_layer = r->layer ();
// stop if the center stop point is not inside the stop polygon
db::Polygon poly;
if (r->shape ().polygon (poly) && db::inside_poly (poly.begin_edge (), stop_point) < 0) {
return 0;
}
}
db::NetTracer net_tracer;
@ -1261,6 +1295,7 @@ NetTracerDialog::release_mouse ()
m_mouse_state = 0;
view ()->message ();
ui ()->ungrab_mouse (this);
set_cursor (lay::Cursor::none);
}
void

View File

@ -60,6 +60,8 @@ public:
NetTracerDialog (lay::Dispatcher *root, lay::LayoutViewBase *view);
virtual ~NetTracerDialog ();
virtual void drag_cancel ();
virtual bool claims_message_bar () const;
virtual bool mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio);
virtual bool mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio);
virtual void menu_activated (const std::string &symbol);

View File

@ -138,6 +138,21 @@ class _PCellDeclarationHelperMixin:
self._finish()
return text
def cell_name(self, parameters):
"""
Reimplementation of PCellDeclaration.cell_name
This function delegates the implementation to self.cell_name_impl
after configuring the PCellDeclaration object.
"""
self._start()
self._param_values = parameters
try:
text = self.cell_name_impl()
finally:
self._finish()
return text
def get_parameters(self):
"""
Reimplementation of PCellDeclaration.get_parameters
@ -333,6 +348,12 @@ class _PCellDeclarationHelperMixin:
"""
return ""
def cell_name_impl(self):
"""
default implementation
"""
return self.name()
def coerce_parameters_impl(self):
"""
default implementation

View File

@ -11,6 +11,9 @@ FORMS =
SOURCES = \
tlAssert.cc \
tlBase64.cc \
tlBitSet.cc \
tlBitSetMap.cc \
tlBitSetMask.cc \
tlColor.cc \
tlClassRegistry.cc \
tlCopyOnWrite.cc \
@ -62,6 +65,9 @@ HEADERS = \
tlAlgorithm.h \
tlAssert.h \
tlBase64.h \
tlBitSet.h \
tlBitSetMap.h \
tlBitSetMask.h \
tlColor.h \
tlClassRegistry.h \
tlCopyOnWrite.h \

244
src/tl/tl/tlBitSet.cc Normal file
View File

@ -0,0 +1,244 @@
/*
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 "tlCommon.h"
#include "tlBitSet.h"
namespace tl
{
static inline unsigned int nwords (BitSet::size_type size)
{
return (size + (sizeof (BitSet::data_type) * 8 - 1)) / (sizeof (BitSet::data_type) * 8);
}
static inline unsigned int word (BitSet::size_type index)
{
return index / (sizeof (BitSet::data_type) * 8);
}
static inline unsigned int bit (BitSet::size_type index)
{
// first bit is the highest bit, so that comparing the uint's is good enough
// for lexical order.
return 31 - (index % (sizeof (BitSet::data_type) * 8));
}
BitSet::BitSet ()
: mp_data (0), m_size (0)
{
// .. nothing yet ..
}
BitSet::BitSet (const std::string &s)
: mp_data (0), m_size (0)
{
index_type bit = 0;
for (const char *cp = s.c_str (); *cp; ++cp, ++bit) {
set_value (bit, *cp == '1');
}
}
BitSet::BitSet (const BitSet &other)
: mp_data (0), m_size (0)
{
operator= (other);
}
BitSet::BitSet (BitSet &&other)
: mp_data (0), m_size (0)
{
operator= (std::move (other));
}
std::string
BitSet::to_string () const
{
std::string r;
r.reserve (m_size);
for (index_type i = 0; i < m_size; ++i) {
r += operator[] (i) ? '1' : '0';
}
return r;
}
BitSet::~BitSet ()
{
clear ();
}
BitSet &
BitSet::operator= (const BitSet &other)
{
if (&other != this) {
clear ();
// reallocate
m_size = other.m_size;
unsigned int words = nwords (m_size);
mp_data = new data_type[words];
data_type *t = mp_data;
data_type *s = other.mp_data;
for (unsigned int i = 0; i < words; ++i) {
*t++ = *s++;
}
}
return *this;
}
BitSet &
BitSet::operator= (BitSet &&other)
{
if (&other != this) {
swap (other);
}
return *this;
}
void
BitSet::clear ()
{
if (mp_data) {
delete [] mp_data;
}
mp_data = 0;
m_size = 0;
}
void
BitSet::resize (size_type size)
{
if (size > m_size) {
unsigned int words = nwords (m_size);
unsigned int new_words = nwords (size);
if (new_words > words) {
// reallocate
data_type *new_data = new data_type[new_words];
data_type *t = new_data;
data_type *s = mp_data;
unsigned int i;
for (i = 0; i < words; ++i) {
*t++ = *s++;
}
for (; i < new_words; ++i) {
*t++ = 0;
}
delete mp_data;
mp_data = new_data;
m_size = size;
}
}
}
bool
BitSet::operator== (const BitSet &other) const
{
unsigned int words = nwords (m_size);
unsigned int other_words = nwords (other.m_size);
const data_type *p = mp_data;
const data_type *op = other.mp_data;
unsigned int i;
for (i = 0; i < words && i < other_words; ++i) {
if (*p++ != *op++) {
return false;
}
}
for (; i < words; ++i) {
if (*p++ != 0) {
return false;
}
}
for (; i < other_words; ++i) {
if (0 != *op++) {
return false;
}
}
return true;
}
bool
BitSet::operator< (const BitSet &other) const
{
unsigned int words = nwords (m_size);
unsigned int other_words = nwords (other.m_size);
const data_type *p = mp_data;
const data_type *op = other.mp_data;
unsigned int i;
for (i = 0; i < words && i < other_words; ++i, ++p, ++op) {
if (*p != *op) {
return *p < *op;
}
}
for (; i < other_words; ++i, ++op) {
if (0 != *op) {
return true;
}
}
return false;
}
void
BitSet::set (index_type index)
{
unsigned int wi = word (index);
if (wi >= nwords (m_size)) {
resize (index + 1);
} else if (index >= m_size) {
m_size = index + 1;
}
mp_data [wi] |= (1 << bit (index));
}
void
BitSet::reset (index_type index)
{
if (index < m_size) {
unsigned int wi = word (index);
mp_data [wi] &= ~(1 << bit (index));
}
}
bool
BitSet::operator[] (index_type index) const
{
if (index < m_size) {
unsigned int wi = word (index);
return (mp_data [wi] & (1 << bit (index))) != 0;
} else {
return false;
}
}
}

251
src/tl/tl/tlBitSet.h Normal file
View File

@ -0,0 +1,251 @@
/*
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
*/
#ifndef HDR_tlBitSet
#define HDR_tlBitSet
#include "tlCommon.h"
#include <cstdint>
#include <algorithm>
#include <string>
namespace tl
{
/**
* @brief A bit set
*
* This object can store a set of n bits, each being true or false.
* Essentially is it like a vector<bool>, but optimized to cooperate
* with tl::BitSetMap and tl::BitSetMatch.
*
* Allocation is dynamic when a bit is accessed for write. Bits beyond the
* allocated size are treated as "false" or zero.
*/
class TL_PUBLIC BitSet
{
public:
typedef unsigned int index_type;
typedef unsigned int size_type;
typedef uint32_t data_type;
/**
* @brief Default constructor: creates an empty bit set
*/
BitSet ();
/**
* @brief Creates and initializes a bit set from a range of indexes
* Every bit given by an index from the range is set.
*/
template <class Iter>
BitSet (Iter from, Iter to)
: mp_data (0), m_size (0)
{
set (from, to);
}
/**
* @brief Creates a bit set from a string
*
* In the string, a '0' character is for False, '1' for True.
*/
BitSet (const std::string &s);
/**
* @brief Copy constructor
*/
BitSet (const BitSet &other);
/**
* @brief Move constructor
*/
BitSet (BitSet &&other);
/**
* @brief Converts the bit set to a string
*/
std::string to_string () const;
/**
* @brief Destructor
*/
~BitSet ();
/**
* @brief Assignment
*/
BitSet &operator= (const BitSet &other);
/**
* @brief Move assignment
*/
BitSet &operator= (BitSet &&other);
/**
* @brief Swaps the contents of this bit set with the other
*/
void swap (BitSet &other)
{
std::swap (mp_data, other.mp_data);
std::swap (m_size, other.m_size);
}
/**
* @brief Clears this bit set
*/
void clear ();
/**
* @brief Sizes the bit set to "size" bits
*
* New bits are set to false.
*/
void resize (size_type size);
/**
* @brief Equality
*/
bool operator== (const BitSet &other) const;
/**
* @brief Inequality
*/
bool operator!= (const BitSet &other) const
{
return !operator== (other);
}
/**
* @brief Less operator
*
* The bits are compared in lexical order, first bit first.
*/
bool operator< (const BitSet &other) const;
/**
* @brief Sets the given bit
*/
void set (index_type index);
/**
* @brief Sets a range of bits
* The indexes are taken from the sequence delivered by the iterator.
*/
template <class Iter>
void set (Iter from, Iter to)
{
for (Iter i = from; i != to; ++i) {
set (*i);
}
}
/**
* @brief Resets the given bit
*/
void reset (index_type index);
/**
* @brief Resets a range of bits
* The indexes are taken from the sequence delivered by the iterator.
*/
template <class Iter>
void reset (Iter from, Iter to)
{
for (Iter i = from; i != to; ++i) {
reset (*i);
}
}
/**
* @brief Sets the values for a given bit
*/
void set_value (index_type index, bool f)
{
if (f) {
set (index);
} else {
reset (index);
}
}
/**
* @brief Sets the values for a range of bits
* The indexes are taken from the sequence delivered by the iterator.
*/
template <class Iter>
void set_value (Iter from, Iter to, bool f)
{
for (Iter i = from; i != to; ++i) {
set_value (*i, f);
}
}
/**
* @brief Gets a bit from the given index
*/
bool operator[] (index_type index) const;
/**
* @brief Gets a value indicating whether the set is empty
*
* "empty" means, no bits have been written yet. "empty" does NOT mean
* all bits are zero.
*/
bool is_empty () const
{
return m_size == 0;
}
/**
* @brief Gets the number of bits stored
*
* The number of bits is the highest bit written so far.
*/
size_type size () const
{
return m_size;
}
private:
friend class BitSetMask;
data_type *mp_data;
size_type m_size;
};
}
namespace std
{
inline void
swap (tl::BitSet &a, tl::BitSet &b)
{
a.swap (b);
}
}
#endif

31
src/tl/tl/tlBitSetMap.cc Normal file
View File

@ -0,0 +1,31 @@
/*
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 "tlCommon.h"
#include "tlBitSetMap.h"
namespace tl
{
// .. nothing yet ..
}

374
src/tl/tl/tlBitSetMap.h Normal file
View File

@ -0,0 +1,374 @@
/*
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
*/
#ifndef HDR_tlBitSetMap
#define HDR_tlBitSetMap
#include "tlCommon.h"
#include "tlBitSetMask.h"
#include "tlBitSet.h"
#include "tlAssert.h"
#include <algorithm>
#include <vector>
namespace tl
{
template <class Value>
struct TL_PUBLIC_TEMPLATE bit_set_mask_node
{
bit_set_mask_node ()
: mask (), next (0), value ()
{
}
tl::BitSetMask mask;
size_t next;
Value value;
void swap (bit_set_mask_node<Value> &other)
{
std::swap (mask, other.mask);
std::swap (next, other.next);
std::swap (value, other.value);
}
};
template <class Value>
class TL_PUBLIC_TEMPLATE bit_set_mask_compare
{
public:
bit_set_mask_compare (tl::BitSetMask::index_type bit, tl::BitSetMask::mask_type mask)
: m_bit (bit), m_mask (mask)
{
}
bool operator() (const bit_set_mask_node<Value> &node) const
{
return node.mask [m_bit] < m_mask;
}
private:
tl::BitSetMask::index_type m_bit;
tl::BitSetMask::mask_type m_mask;
};
}
namespace std
{
template <class Value>
inline void
swap (tl::bit_set_mask_node<Value> &a, tl::bit_set_mask_node<Value> &b)
{
a.swap (b);
}
}
namespace tl
{
/**
* @brief A bit set map
*
* This specialized map stores tl::BitSetMask keys and corresponding values.
* tl::BitSet objects can be used to retrieve values. Masks may overlap, hence
* multiple matches are possible. The "lookup" method employs a visitor
* pattern to deliver these multiple matches.
*
* In order to use the map, it first has to be sorted. Insert masks using
* "insert" and do a "sort" before using "lookup".
*/
template <class Value>
class TL_PUBLIC_TEMPLATE bit_set_map
{
public:
typedef std::vector<bit_set_mask_node<Value> > node_list;
typedef typename node_list::const_iterator const_iterator;
typedef typename node_list::iterator iterator;
/**
* @brief Default constructor: creates an empty bit set
*/
bit_set_map ()
{
// .. nothing yet ..
}
/**
* @brief Copy constructor
*/
bit_set_map (const bit_set_map &other)
{
operator= (other);
}
/**
* @brief Move constructor
*/
bit_set_map (bit_set_map &&other)
{
operator= (std::move (other));
}
/**
* @brief Assignment
*/
bit_set_map &operator= (const bit_set_map &other)
{
if (this != &other) {
m_nodes = other.m_nodes;
m_sorted = other.m_sorted;
}
return *this;
}
/**
* @brief Move assignment
*/
bit_set_map &operator= (bit_set_map &&other)
{
if (this != &other) {
swap (other);
}
return *this;
}
/**
* @brief Swaps the contents of this bit set with the other
*/
void swap (bit_set_map &other)
{
if (this != &other) {
m_nodes.swap (other.m_nodes);
std::swap (m_sorted, other.m_sorted);
}
}
/**
* @brief Clears this map
*/
void clear ()
{
m_nodes.clear ();
m_sorted = true;
}
/**
* @brief Reserves "size" entries
*
* Use this method to specify the intended size of the map.
* This optimizes performance and memory allocation.
*/
void reserve (size_t n)
{
m_nodes.reserve (n);
}
/**
* @brief Inserts an item into the map
*/
void insert (const tl::BitSetMask &mask, const Value &value)
{
m_nodes.push_back (tl::bit_set_mask_node<Value> ());
m_nodes.back ().mask = mask;
m_nodes.back ().next = 0;
m_nodes.back ().value = value;
m_sorted = false;
}
/**
* @brief Sorts the map
*
* "sort" needs to be called before "lookup" can be used.
*/
void sort ()
{
if (! m_sorted) {
if (! m_nodes.empty ()) {
m_nodes.front ().next = m_nodes.size ();
sort_range (0, m_nodes.begin (), m_nodes.end ());
}
m_sorted = true;
}
}
/**
* @brief Gets a value indicating whether the set is empty
*
* "empty" means, no bits have been written yet. "empty" does NOT mean
* all masks are of some specific value.
*/
bool is_empty () const
{
return m_nodes.empty ();
}
/**
* @brief Gets the number of bits for the mask stored
*
* The number of bits is the highest bit written so far.
*/
size_t size () const
{
return m_nodes.size ();
}
/**
* @brief Looks up items by bit set
*
* For each item found, the value is delivered through the
* Inserter provided.
*
* The return value is true, if any value has been found.
*/
template <class Inserter>
bool lookup (const tl::BitSet &bit_set, Inserter inserter) const
{
tl_assert (m_sorted);
return partial_lookup (0, m_nodes.begin (), m_nodes.end (), bit_set, inserter);
}
/**
* @brief Begin iterator
*/
iterator begin ()
{
return m_nodes.begin ();
}
/**
* @brief End iterator
*/
iterator end ()
{
return m_nodes.end ();
}
/**
* @brief Begin iterator (const version)
*/
const_iterator begin () const
{
return m_nodes.begin ();
}
/**
* @brief End iterator (const version)
*/
const_iterator end () const
{
return m_nodes.end ();
}
private:
node_list m_nodes;
bool m_sorted;
void sort_range (tl::BitSetMask::index_type bit, iterator from, iterator to)
{
if (from == to) {
return;
}
// special case of identical entries which creates a sequence of
// single entries
bool all_same = true;
for (auto i = from + 1; i != to && all_same; ++i) {
if (i->mask != from->mask) {
all_same = false;
}
}
if (all_same) {
// this is also the case for a single element
for (auto i = from + 1; i != to; ++i) {
i->next = 1;
}
return;
}
// we have at least one element. The first one is taken for the previous level node, so we start partitioning
// at the second node
++from;
auto middle_false = std::partition (from, to, tl::bit_set_mask_compare<Value> (bit, tl::BitSetMask::False));
auto middle_true = std::partition (middle_false, to, tl::bit_set_mask_compare<Value> (bit, tl::BitSetMask::True));
auto middle_never = std::partition (middle_true, to, tl::bit_set_mask_compare<Value> (bit, tl::BitSetMask::Never));
from->next = middle_false - from;
if (middle_false != to) {
middle_false->next = middle_true - middle_false;
}
if (middle_true != to) {
middle_true->next = middle_never - middle_true;
}
if (middle_never != to) {
middle_never->next = to - middle_never;
}
sort_range (bit + 1, from, middle_false);
sort_range (bit + 1, middle_false, middle_true);
sort_range (bit + 1, middle_true, middle_never);
sort_range (bit + 1, middle_never, to);
}
template <class Inserter>
bool partial_lookup (tl::BitSetMask::index_type bit, const_iterator from, const_iterator to, const tl::BitSet &bit_set, Inserter inserter) const
{
if (from == to) {
return false;
}
bool any = false;
if (from->mask.match (bit_set)) {
*inserter++ = from->value;
any = true;
}
bool b = bit_set [bit];
auto i = ++from;
while (i != to) {
auto m = i->mask [bit];
if (m == tl::BitSetMask::Any || (m == tl::BitSetMask::True && b) || (m == tl::BitSetMask::False && !b)) {
if (partial_lookup (bit + 1, i, i + i->next, bit_set, inserter)) {
any = true;
}
}
if (i->next == 0) {
break;
}
i += i->next;
}
return any;
}
};
}
#endif

373
src/tl/tl/tlBitSetMask.cc Normal file
View File

@ -0,0 +1,373 @@
/*
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 "tlCommon.h"
#include "tlBitSetMask.h"
#include "tlBitSet.h"
namespace tl
{
static inline unsigned int nwords (BitSetMask::size_type size)
{
return (size + (sizeof (BitSetMask::data_type) * 8 - 1)) / (sizeof (BitSetMask::data_type) * 8);
}
static inline unsigned int word (BitSetMask::size_type index)
{
return index / (sizeof (BitSetMask::data_type) * 8);
}
static inline unsigned int bit (BitSetMask::size_type index)
{
// first bit is the highest bit, so that comparing the uint's is good enough
// for lexical order.
return 31 - (index % (sizeof (BitSetMask::data_type) * 8));
}
BitSetMask::BitSetMask ()
: mp_data0 (0), mp_data1 (0), m_size (0)
{
// .. nothing yet ..
}
BitSetMask::BitSetMask (const std::string &s)
: mp_data0 (0), mp_data1 (0), m_size (0)
{
index_type bit = 0;
for (const char *cp = s.c_str (); *cp; ++cp, ++bit) {
mask_type m = Any;
if (*cp == '0') {
m = False;
} else if (*cp == '1') {
m = True;
} else if (*cp == '-') {
m = Never;
}
set (bit, m);
}
}
BitSetMask::BitSetMask (const BitSetMask &other)
: mp_data0 (0), mp_data1 (0), m_size (0)
{
operator= (other);
}
BitSetMask::BitSetMask (BitSetMask &&other)
: mp_data0 (0), mp_data1 (0), m_size (0)
{
operator= (std::move (other));
}
BitSetMask::~BitSetMask ()
{
clear ();
}
std::string
BitSetMask::to_string () const
{
std::string r;
r.reserve (m_size);
for (index_type i = 0; i < m_size; ++i) {
switch (operator[] (i)) {
case False:
r += '0';
break;
case True:
r += '1';
break;
case Never:
r += '-';
break;
case Any:
default:
r += 'X';
break;
}
}
return r;
}
BitSetMask &
BitSetMask::operator= (const BitSetMask &other)
{
if (&other != this) {
clear ();
// reallocate
m_size = other.m_size;
unsigned int words = nwords (m_size);
mp_data0 = new data_type[words];
mp_data1 = new data_type[words];
data_type *t0 = mp_data0;
data_type *s0 = other.mp_data0;
data_type *t1 = mp_data1;
data_type *s1 = other.mp_data1;
for (unsigned int i = 0; i < words; ++i) {
*t0++ = *s0++;
*t1++ = *s1++;
}
}
return *this;
}
BitSetMask &
BitSetMask::operator= (BitSetMask &&other)
{
if (&other != this) {
swap (other);
}
return *this;
}
void
BitSetMask::clear ()
{
if (mp_data0) {
delete [] mp_data0;
}
mp_data0 = 0;
if (mp_data1) {
delete [] mp_data1;
}
mp_data1 = 0;
m_size = 0;
}
void
BitSetMask::resize (size_type size)
{
if (size > m_size) {
unsigned int words = nwords (m_size);
unsigned int new_words = nwords (size);
if (new_words > words) {
// reallocate
data_type *new_data0 = new data_type[new_words];
data_type *new_data1 = new data_type[new_words];
data_type *t0 = new_data0;
data_type *s0 = mp_data0;
data_type *t1 = new_data1;
data_type *s1 = mp_data1;
unsigned int i;
for (i = 0; i < words; ++i) {
*t0++ = *s0++;
*t1++ = *s1++;
}
for (; i < new_words; ++i) {
// corresponds to "Any"
*t0++ = 0;
*t1++ = 0;
}
delete mp_data0;
mp_data0 = new_data0;
delete mp_data1;
mp_data1 = new_data1;
m_size = size;
}
}
}
bool
BitSetMask::operator== (const BitSetMask &other) const
{
unsigned int words = nwords (m_size);
unsigned int other_words = nwords (other.m_size);
const data_type *p0 = mp_data0;
const data_type *p1 = mp_data1;
const data_type *op0 = other.mp_data0;
const data_type *op1 = other.mp_data1;
unsigned int i;
for (i = 0; i < words && i < other_words; ++i) {
if (*p0++ != *op0++) {
return false;
}
if (*p1++ != *op1++) {
return false;
}
}
for (; i < words; ++i) {
if (*p0++ != 0) {
return false;
}
if (*p1++ != 0) {
return false;
}
}
for (; i < other_words; ++i) {
if (0 != *op0++) {
return false;
}
if (0 != *op1++) {
return false;
}
}
return true;
}
/**
* @brief Gets the most significant bit of a bit set
*
* For example b:00101101 will give b:00100000.
*/
static inline BitSetMask::data_type msb_only (BitSetMask::data_type value)
{
const unsigned int smax = sizeof (BitSetMask::data_type) * 8;
BitSetMask::data_type m = value;
for (unsigned int s = 1; s < smax; s *= 2) {
m |= (m >> s);
}
return value & ~(m >> 1);
}
bool
BitSetMask::operator< (const BitSetMask &other) const
{
unsigned int words = nwords (m_size);
unsigned int other_words = nwords (other.m_size);
const data_type *p0 = mp_data0;
const data_type *p1 = mp_data1;
const data_type *op0 = other.mp_data0;
const data_type *op1 = other.mp_data1;
unsigned int i;
for (i = 0; i < words && i < other_words; ++i, ++p0, ++p1, ++op0, ++op1) {
data_type diff = (*p0 ^ *op0) | (*p1 ^ *op1);
if (diff) {
// compare the most significant position of the differences by value
data_type mb = msb_only (diff);
unsigned int m = ((*p0 & mb) != 0 ? 1 : 0) + ((*p1 & mb) != 0 ? 2 : 0);
unsigned int om = ((*op0 & mb) != 0 ? 1 : 0) + ((*op1 & mb) != 0 ? 2 : 0);
return m < om;
}
}
// the remaining part of other is simply checked for
// not being zero
for (; i < other_words; ++i, ++op0, ++op1) {
if (0 != *op0 || 0 != *op1) {
return true;
}
}
return false;
}
void
BitSetMask::set (index_type index, mask_type mask)
{
if (index >= m_size && mask == Any) {
return;
}
unsigned int wi = word (index);
if (wi >= nwords (m_size)) {
resize (index + 1);
} else if (index >= m_size) {
m_size = index + 1;
}
unsigned int mi = (unsigned int) mask;
data_type bm = (1 << bit (index));
if (mi & 1) {
mp_data0 [wi] |= bm;
} else {
mp_data0 [wi] &= ~bm;
}
if (mi & 2) {
mp_data1 [wi] |= bm;
} else {
mp_data1 [wi] &= ~bm;
}
}
BitSetMask::mask_type
BitSetMask::operator[] (index_type index) const
{
if (index < m_size) {
unsigned int wi = word (index);
data_type bm = (1 << bit (index));
unsigned int mi = ((mp_data0 [wi] & bm) != 0 ? 1 : 0) | ((mp_data1 [wi] & bm) != 0 ? 2 : 0);
return mask_type (mi);
} else {
return Any;
}
}
bool
BitSetMask::match (const tl::BitSet &bs) const
{
unsigned int nw_bs = nwords (bs.m_size);
unsigned int nw = nwords (m_size);
const tl::BitSet::data_type *d0 = mp_data0, *d1 = mp_data1;
const tl::BitSet::data_type *s = bs.mp_data;
unsigned int i;
for (i = 0; i < nw; ++i, ++d0, ++d1) {
tl::BitSet::data_type d = i < nw_bs ? *s++ : 0;
tl::BitSet::data_type invalid = 0;
if (i >= nw_bs) {
invalid = ~invalid;
} else if (bs.m_size < (i + 1) * (sizeof (tl::BitSet::data_type) * 8)) {
invalid = (1 << ((i + 1) * (sizeof (tl::BitSet::data_type) * 8) - bs.m_size)) - 1;
}
// "never" matches no valid bit ("never" is: d0 and d1 bits are ones)
if (((*d0 & *d1) & ~invalid) != 0) {
return false;
}
// A "true" in place of "false expected" gives "no match"
if ((*d0 & ~*d1 & d) != 0) {
return false;
}
// A "false" in place of "true expected" gives "no match"
if ((*d1 & ~*d0 & ~d) != 0) {
return false;
}
}
// as "not set" corresponds to "Any", we can stop here and have a match.
return true;
}
}

198
src/tl/tl/tlBitSetMask.h Normal file
View File

@ -0,0 +1,198 @@
/*
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
*/
#ifndef HDR_tlBitSetMask
#define HDR_tlBitSetMask
#include "tlCommon.h"
#include "tlBitSet.h"
#include <cstdint>
#include <algorithm>
#include <string>
namespace tl
{
/**
* @brief A bit set
*
* This object can store a mask for a bit set.
* Each element of the mask corresponds to one bit. Each element can be "True"
* (matching to true), "False" (matching to false), "Any" matching to true
* or false and "Never" matches neither to true nor false.
*
* A bit set match can be matched against a bit set and will return true if
* the bit set corresponds to the mask.
*
* Allocation is dynamic when a mask element is accessed for write. Bits beyond the
* allocated size are treated as "Any".
*/
class TL_PUBLIC BitSetMask
{
public:
typedef enum { Any = 0, False = 1, True = 2, Never = 3 } mask_type;
typedef tl::BitSet::index_type index_type;
typedef tl::BitSet::size_type size_type;
typedef tl::BitSet::data_type data_type;
/**
* @brief Default constructor: creates an empty bit set
*/
BitSetMask ();
/**
* @brief Creates a bit set mask from a string
*
* In the string, a '0' character is for False, '1' for True, 'X' for Any and '-' for Never.
*/
BitSetMask (const std::string &s);
/**
* @brief Copy constructor
*/
BitSetMask (const BitSetMask &other);
/**
* @brief Move constructor
*/
BitSetMask (BitSetMask &&other);
/**
* @brief Destructor
*/
~BitSetMask ();
/**
* @brief Converts the mask to a string
*/
std::string to_string () const;
/**
* @brief Assignment
*/
BitSetMask &operator= (const BitSetMask &other);
/**
* @brief Move assignment
*/
BitSetMask &operator= (BitSetMask &&other);
/**
* @brief Swaps the contents of this bit set with the other
*/
void swap (BitSetMask &other)
{
std::swap (mp_data0, other.mp_data0);
std::swap (mp_data1, other.mp_data1);
std::swap (m_size, other.m_size);
}
/**
* @brief Clears this bit set
*/
void clear ();
/**
* @brief Sizes the bit set to "size" bits
*
* New bits are set to false.
*/
void resize (size_type size);
/**
* @brief Equality
*/
bool operator== (const BitSetMask &other) const;
/**
* @brief Inequality
*/
bool operator!= (const BitSetMask &other) const
{
return !operator== (other);
}
/**
* @brief Less operator
*
* The bits are compared in lexical order, first bit first.
*/
bool operator< (const BitSetMask &other) const;
/**
* @brief Sets the mask for the given bit
*/
void set (index_type index, mask_type mask);
/**
* @brief Gets a mask from the given bit
*/
mask_type operator[] (index_type index) const;
/**
* @brief Gets a value indicating whether the set is empty
*
* "empty" means, no bits have been written yet. "empty" does NOT mean
* all masks are of some specific value.
*/
bool is_empty () const
{
return m_size == 0;
}
/**
* @brief Gets the number of bits for the mask stored
*
* The number of bits is the highest bit written so far.
*/
size_type size () const
{
return m_size;
}
/**
* @brief Matches the given bit set against this mask
*/
bool match (const tl::BitSet &) const;
private:
data_type *mp_data0, *mp_data1;
size_type m_size;
};
}
namespace std
{
inline void
swap (tl::BitSetMask &a, tl::BitSetMask &b)
{
a.swap (b);
}
}
#endif

View File

@ -0,0 +1,218 @@
/*
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 "tlBitSetMap.h"
#include "tlUnitTest.h"
#include "tlString.h"
#include "tlTimer.h"
#include <iostream>
static tl::BitSet bs (const char *s)
{
return tl::BitSet (s);
}
static tl::BitSetMask bsm (const char *s)
{
return tl::BitSetMask (s);
}
struct SetInserter
{
SetInserter (std::set<int> &s) : ps (&s) { }
SetInserter &operator++(int) { return *this; }
SetInserter &operator* () { return *this; }
SetInserter &operator= (int v) { ps->insert (v); return *this; }
std::set<int> *ps;
};
static std::string s2s (const std::set<int> &values)
{
std::string res;
for (auto i = values.begin (); i != values.end (); ++i) {
if (!res.empty ()) {
res += ",";
}
res += tl::to_string (*i);
}
return res;
}
static std::string match (const tl::bit_set_map<int> &bsm, const tl::BitSet &bs)
{
std::set<int> values;
bsm.lookup (bs, SetInserter (values));
return s2s (values);
}
namespace
{
TEST(1_Basic)
{
tl::bit_set_map<int> map;
map.insert (bsm ("X10"), 1);
map.insert (bsm ("X10"), 11);
map.insert (bsm ("1"), 2);
map.insert (bsm ("101"), 3);
map.insert (bsm ("1X0"), 4);
map.insert (bsm ("110"), 5);
map.sort ();
EXPECT_EQ (match (map, bs ("")), "");
EXPECT_EQ (match (map, bs ("1")), "2,4");
EXPECT_EQ (match (map, bs ("110")), "1,2,4,5,11");
EXPECT_EQ (match (map, bs ("01")), "1,11");
EXPECT_EQ (match (map, bs ("010000")), "1,11");
map.insert (bsm (""), 0);
try {
match (map, bs (""));
EXPECT_EQ (true, false); // not sorted
} catch (...) { }
map.sort ();
EXPECT_EQ (match (map, bs ("")), "0");
}
static std::string bitstr (unsigned int n, unsigned int nbits)
{
std::string r;
while (nbits > 0) {
r += ((n & 1) != 0 ? '1' : '0');
n >>= 1;
--nbits;
}
return r;
}
TEST(2_Regular)
{
tl::bit_set_map<int> map;
unsigned int num = 10000;
unsigned int nbits = 20;
for (unsigned int i = 0; i < num; ++i) {
map.insert (bsm (bitstr (i, nbits).c_str ()), int (i));
}
{
tl::SelfTimer timer ("sorting");
map.sort ();
}
{
tl::SelfTimer timer ("match method");
for (unsigned int i = 0; i < num; ++i) {
EXPECT_EQ (match (map, bs (bitstr (i, nbits).c_str ())), tl::to_string (i));
}
}
// brute force
{
tl::SelfTimer timer ("brute force");
for (unsigned int i = 0; i < num; ++i) {
tl::BitSet k = bs (bitstr (i, nbits).c_str ());
int value = 0;
for (auto j = map.begin (); j != map.end (); ++j) {
if (j->mask.match (k)) {
value = j->value;
}
}
EXPECT_EQ (value, int (i));
}
}
}
TEST(3_IrregularTest)
{
srand (0);
tl::bit_set_map<int> map;
unsigned int num = 10000;
unsigned int nbits_min = 10;
unsigned int nbits_max = 20;
for (unsigned int i = 0; i < num; ++i) {
std::string s;
unsigned int n = nbits_min + (rand () % (nbits_max - nbits_min));
for (unsigned int j = 0; j < n; ++j) {
// this pattern gives roughly 5 matches per entry with 10k entries
s += "010101X"[rand () % 7];
}
map.insert (bsm (s.c_str ()), int (i));
}
std::vector<tl::BitSet> test_vectors;
for (unsigned int i = 0; i < num; ++i) {
std::string s;
unsigned int n = nbits_min + (rand () % (nbits_max - nbits_min));
for (unsigned int j = 0; j < n; ++j) {
s += "01"[rand () % 2];
}
test_vectors.push_back (bs (s.c_str ()));
}
{
tl::SelfTimer timer ("sorting");
map.sort ();
}
std::vector<std::string> matches;
{
tl::SelfTimer timer ("match method");
for (auto i = test_vectors.begin (); i != test_vectors.end (); ++i) {
matches.push_back (match (map, *i));
}
}
size_t max_matches = 0;
// brute force
{
tl::SelfTimer timer ("brute force");
for (auto i = test_vectors.begin (); i != test_vectors.end (); ++i) {
std::set<int> values;
for (auto j = map.begin (); j != map.end (); ++j) {
if (j->mask.match (*i)) {
values.insert(j->value);
}
}
max_matches = std::max (max_matches, values.size ());
EXPECT_EQ (s2s (values), matches [i - test_vectors.begin ()]);
}
}
// sanity check
tl::info << "Max. matches: " << max_matches;
EXPECT_EQ (max_matches > 5, true);
}
}

View File

@ -0,0 +1,304 @@
/*
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 "tlBitSetMask.h"
#include "tlUnitTest.h"
#include "tlString.h"
namespace
{
static std::string l2s (const tl::BitSetMask &s)
{
return s.to_string ();
}
static tl::BitSet bs (const char *s)
{
return tl::BitSet (s);
}
TEST(1_Basic)
{
tl::BitSetMask bs;
EXPECT_EQ (bs.is_empty (), true);
EXPECT_EQ (bs.size (), 0u);
EXPECT_EQ (l2s (bs), "");
EXPECT_EQ (l2s (tl::BitSetMask (l2s (bs))), "");
bs.set (1, tl::BitSetMask::True);
EXPECT_EQ (bs.size (), 2u);
EXPECT_EQ (l2s (bs), "X1");
EXPECT_EQ (l2s (tl::BitSetMask (l2s (bs))), "X1");
bs.set (32, tl::BitSetMask::False);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "X1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
EXPECT_EQ (l2s (tl::BitSetMask (l2s (bs))), "X1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
bs.set (3, tl::BitSetMask::False);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "X1X0XXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
EXPECT_EQ (l2s (tl::BitSetMask (l2s (bs))), "X1X0XXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
bs.set (128, tl::BitSetMask::Any);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "X1X0XXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
EXPECT_EQ (l2s (tl::BitSetMask (l2s (bs))), "X1X0XXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
bs.clear ();
EXPECT_EQ (bs.size (), 0u);
EXPECT_EQ (l2s (bs), "");
bs.resize (6);
EXPECT_EQ (bs.size (), 6u);
EXPECT_EQ (l2s (bs), "XXXXXX");
}
TEST(2_Equality)
{
tl::BitSetMask bs1, bs2, bs3;
EXPECT_EQ (bs1 == bs2, true);
EXPECT_EQ (bs1 != bs2, false);
bs1.set (0, tl::BitSetMask::True);
EXPECT_EQ (bs1 == bs2, false);
EXPECT_EQ (bs1 != bs2, true);
bs1.set (32, tl::BitSetMask::False);
EXPECT_EQ (bs1 == bs2, false);
EXPECT_EQ (bs1 != bs2, true);
bs2.set (0, tl::BitSetMask::True);
bs2.set (32, tl::BitSetMask::False);
EXPECT_EQ (bs1 == bs2, true);
EXPECT_EQ (bs1 == bs3, false);
EXPECT_EQ (bs1 != bs2, false);
EXPECT_EQ (bs1 != bs3, true);
bs1.set (0, tl::BitSetMask::Any);
bs1.set (32, tl::BitSetMask::Any);
EXPECT_EQ (bs1 == bs2, false);
EXPECT_EQ (bs1 == bs3, true);
EXPECT_EQ (bs1 != bs2, true);
EXPECT_EQ (bs1 != bs3, false);
}
TEST(3_Compare)
{
tl::BitSetMask bs1, bs2, bs3;
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, false);
bs1.set (0, tl::BitSetMask::True);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, true);
bs1.set (32, tl::BitSetMask::False);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, true);
bs2.set (32, tl::BitSetMask::False);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs1 < bs3, false);
EXPECT_EQ (bs2 < bs1, true);
EXPECT_EQ (bs3 < bs1, true);
bs2.set (0, tl::BitSetMask::True);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs1 < bs3, false);
EXPECT_EQ (bs2 < bs1, false);
EXPECT_EQ (bs3 < bs1, true);
bs1.set (0, tl::BitSetMask::Any);
bs1.set (32, tl::BitSetMask::Any);
EXPECT_EQ (bs1 < bs2, true);
EXPECT_EQ (bs1 < bs3, false);
EXPECT_EQ (bs2 < bs1, false);
EXPECT_EQ (bs3 < bs1, false);
bs1.clear ();
bs2.clear ();
bs1.set (0, tl::BitSetMask::Any);
bs2.set (0, tl::BitSetMask::Any);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, false);
bs2.set (0, tl::BitSetMask::False);
EXPECT_EQ (bs1 < bs2, true);
EXPECT_EQ (bs2 < bs1, false);
bs2.set (0, tl::BitSetMask::True);
EXPECT_EQ (bs1 < bs2, true);
EXPECT_EQ (bs2 < bs1, false);
bs2.set (0, tl::BitSetMask::Never);
EXPECT_EQ (bs1 < bs2, true);
EXPECT_EQ (bs2 < bs1, false);
bs1.set (0, tl::BitSetMask::False);
bs2.set (0, tl::BitSetMask::Any);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, true);
bs2.set (0, tl::BitSetMask::False);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, false);
bs2.set (0, tl::BitSetMask::True);
EXPECT_EQ (bs1 < bs2, true);
EXPECT_EQ (bs2 < bs1, false);
bs2.set (0, tl::BitSetMask::Never);
EXPECT_EQ (bs1 < bs2, true);
EXPECT_EQ (bs2 < bs1, false);
bs1.set (0, tl::BitSetMask::True);
bs2.set (0, tl::BitSetMask::Any);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, true);
bs2.set (0, tl::BitSetMask::False);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, true);
bs2.set (0, tl::BitSetMask::True);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, false);
bs2.set (0, tl::BitSetMask::Never);
EXPECT_EQ (bs1 < bs2, true);
EXPECT_EQ (bs2 < bs1, false);
bs1.set (0, tl::BitSetMask::Never);
bs2.set (0, tl::BitSetMask::Any);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, true);
bs2.set (0, tl::BitSetMask::False);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, true);
bs2.set (0, tl::BitSetMask::True);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, true);
bs2.set (0, tl::BitSetMask::Never);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, false);
}
TEST(4_Assign)
{
tl::BitSetMask bs;
EXPECT_EQ (l2s (bs), "");
EXPECT_EQ (l2s (tl::BitSetMask (bs)), "");
bs.set (3, tl::BitSetMask::True);
bs.set (32, tl::BitSetMask::False);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "XXX1XXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
EXPECT_EQ (tl::BitSetMask (bs).size (), 33u);
EXPECT_EQ (l2s (tl::BitSetMask (bs)), "XXX1XXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
tl::BitSetMask bs2;
bs2.swap (bs);
EXPECT_EQ (bs.size (), 0u);
EXPECT_EQ (bs2.size (), 33u);
EXPECT_EQ (l2s (bs), "");
EXPECT_EQ (l2s (bs2), "XXX1XXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
bs = bs2;
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "XXX1XXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
bs2.clear ();
EXPECT_EQ (bs2.size (), 0u);
EXPECT_EQ (l2s (bs2), "");
bs2 = std::move (bs);
EXPECT_EQ (bs.size (), 0u);
EXPECT_EQ (l2s (bs), "");
EXPECT_EQ (bs2.size (), 33u);
EXPECT_EQ (l2s (bs2), "XXX1XXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
tl::BitSetMask bs3 (std::move (bs2));
EXPECT_EQ (bs2.size (), 0u);
EXPECT_EQ (l2s (bs2), "");
EXPECT_EQ (bs3.size (), 33u);
EXPECT_EQ (l2s (bs3), "XXX1XXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
}
TEST(5_Match)
{
tl::BitSetMask bsm;
EXPECT_EQ (bsm.match (bs ("")), true);
EXPECT_EQ (bsm.match (bs ("0")), true);
EXPECT_EQ (bsm.match (bs ("1")), true);
EXPECT_EQ (bsm.match (bs ("10101")), true);
bsm.set (1, tl::BitSetMask::Never);
EXPECT_EQ (l2s (bsm), "X-");
EXPECT_EQ (bsm.match (bs ("")), true);
EXPECT_EQ (bsm.match (bs ("0")), true);
EXPECT_EQ (bsm.match (bs ("1")), true);
EXPECT_EQ (bsm.match (bs ("10101")), false); // fails because "never bit" is used.
bsm.clear ();
bsm.set (1, tl::BitSetMask::True);
bsm.set (2, tl::BitSetMask::False);
EXPECT_EQ (l2s (bsm), "X10");
EXPECT_EQ (bsm.match (bs ("")), false);
EXPECT_EQ (bsm.match (bs ("0")), false);
EXPECT_EQ (bsm.match (bs ("000")), false);
EXPECT_EQ (bsm.match (bs ("001")), false);
EXPECT_EQ (bsm.match (bs ("010")), true);
EXPECT_EQ (bsm.match (bs ("011")), false);
EXPECT_EQ (bsm.match (bs ("1")), false);
EXPECT_EQ (bsm.match (bs ("100")), false);
EXPECT_EQ (bsm.match (bs ("101")), false);
EXPECT_EQ (bsm.match (bs ("110")), true);
EXPECT_EQ (bsm.match (bs ("111")), false);
EXPECT_EQ (bsm.match (bs ("10101")), false);
EXPECT_EQ (bsm.match (bs ("11001")), true);
bsm.clear ();
bsm.set (32, tl::BitSetMask::True);
EXPECT_EQ (bsm.match (bs ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0")), false);
EXPECT_EQ (bsm.match (bs ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1")), true);
EXPECT_EQ (bsm.match (bs ("")), false);
bsm.clear ();
bsm.set (32, tl::BitSetMask::False);
EXPECT_EQ (bsm.match (bs ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0")), true);
EXPECT_EQ (bsm.match (bs ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1")), false);
EXPECT_EQ (bsm.match (bs ("")), true); // because unset bits count as zero
}
}

View File

@ -0,0 +1,209 @@
/*
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 "tlBitSet.h"
#include "tlUnitTest.h"
#include "tlString.h"
namespace
{
static std::string l2s (const tl::BitSet &s)
{
return s.to_string ();
}
TEST(1_Basic)
{
tl::BitSet bs;
EXPECT_EQ (bs.is_empty (), true);
EXPECT_EQ (bs.size (), 0u);
EXPECT_EQ (l2s (bs), "");
EXPECT_EQ (l2s (tl::BitSet (l2s (bs))), "");
bs.set (1);
EXPECT_EQ (bs.size (), 2u);
EXPECT_EQ (l2s (bs), "01");
EXPECT_EQ (l2s (tl::BitSet (l2s (bs))), "01");
bs.set (32);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "010000000000000000000000000000001");
EXPECT_EQ (l2s (tl::BitSet (l2s (bs))), "010000000000000000000000000000001");
bs.set (3);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "010100000000000000000000000000001");
EXPECT_EQ (l2s (tl::BitSet (l2s (bs))), "010100000000000000000000000000001");
unsigned int indexes[] = { 5, 6, 7 };
bs.set (indexes + 0, indexes + sizeof (indexes) / sizeof (indexes [0]));
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "010101110000000000000000000000001");
EXPECT_EQ (l2s (tl::BitSet (l2s (bs))), "010101110000000000000000000000001");
bs.reset (128);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "010101110000000000000000000000001");
bs.reset (1);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "000101110000000000000000000000001");
bs.reset (indexes + 0, indexes + sizeof (indexes) / sizeof (indexes [0]));
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "000100000000000000000000000000001");
bs.set_value (0, true);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "100100000000000000000000000000001");
bs.set_value (indexes + 0, indexes + sizeof (indexes) / sizeof (indexes [0]), true);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "100101110000000000000000000000001");
bs.set_value (indexes + 0, indexes + sizeof (indexes) / sizeof (indexes [0]), false);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "100100000000000000000000000000001");
bs.set_value (0, false);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "000100000000000000000000000000001");
bs.clear ();
EXPECT_EQ (bs.size (), 0u);
EXPECT_EQ (l2s (bs), "");
bs.resize (6);
EXPECT_EQ (bs.size (), 6u);
EXPECT_EQ (l2s (bs), "000000");
}
TEST(2_Equality)
{
tl::BitSet bs1, bs2, bs3;
EXPECT_EQ (bs1 == bs2, true);
EXPECT_EQ (bs1 != bs2, false);
bs1.set (0);
EXPECT_EQ (bs1 == bs2, false);
EXPECT_EQ (bs1 != bs2, true);
bs1.set (32);
EXPECT_EQ (bs1 == bs2, false);
EXPECT_EQ (bs1 != bs2, true);
bs2.set (0);
bs2.set (32);
EXPECT_EQ (bs1 == bs2, true);
EXPECT_EQ (bs1 == bs3, false);
EXPECT_EQ (bs1 != bs2, false);
EXPECT_EQ (bs1 != bs3, true);
bs1.reset (0);
bs1.reset (32);
EXPECT_EQ (bs1 == bs2, false);
EXPECT_EQ (bs1 == bs3, true);
EXPECT_EQ (bs1 != bs2, true);
EXPECT_EQ (bs1 != bs3, false);
}
TEST(3_Compare)
{
tl::BitSet bs1, bs2, bs3;
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, false);
bs1.set (0);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, true);
bs1.set (32);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs2 < bs1, true);
bs2.set (0);
bs2.set (32);
EXPECT_EQ (bs1 < bs2, false);
EXPECT_EQ (bs1 < bs3, false);
EXPECT_EQ (bs2 < bs1, false);
EXPECT_EQ (bs3 < bs1, true);
bs1.reset (0);
bs1.reset (32);
EXPECT_EQ (bs1 < bs2, true);
EXPECT_EQ (bs1 < bs3, false);
EXPECT_EQ (bs2 < bs1, false);
EXPECT_EQ (bs3 < bs1, false);
}
TEST(4_Assign)
{
tl::BitSet bs;
EXPECT_EQ (l2s (bs), "");
EXPECT_EQ (l2s (tl::BitSet (bs)), "");
bs.set (3);
bs.set (32);
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "000100000000000000000000000000001");
EXPECT_EQ (tl::BitSet (bs).size (), 33u);
EXPECT_EQ (l2s (tl::BitSet (bs)), "000100000000000000000000000000001");
tl::BitSet bs2;
bs2.swap (bs);
EXPECT_EQ (bs.size (), 0u);
EXPECT_EQ (bs2.size (), 33u);
EXPECT_EQ (l2s (bs), "");
EXPECT_EQ (l2s (bs2), "000100000000000000000000000000001");
bs = bs2;
EXPECT_EQ (bs.size (), 33u);
EXPECT_EQ (l2s (bs), "000100000000000000000000000000001");
bs2.clear ();
EXPECT_EQ (bs2.size (), 0u);
EXPECT_EQ (l2s (bs2), "");
bs2 = std::move (bs);
EXPECT_EQ (bs.size (), 0u);
EXPECT_EQ (l2s (bs), "");
EXPECT_EQ (bs2.size (), 33u);
EXPECT_EQ (l2s (bs2), "000100000000000000000000000000001");
tl::BitSet bs3 (std::move (bs2));
EXPECT_EQ (bs2.size (), 0u);
EXPECT_EQ (l2s (bs2), "");
EXPECT_EQ (bs3.size (), 33u);
EXPECT_EQ (l2s (bs3), "000100000000000000000000000000001");
bs.clear ();
unsigned int indexes[] = { 5, 6, 7 };
bs = std::move (tl::BitSet (indexes + 0, indexes + sizeof (indexes) / sizeof (indexes [0])));
EXPECT_EQ (bs.size (), 8u);
EXPECT_EQ (l2s (bs), "00000111");
}
}

View File

@ -9,6 +9,9 @@ include($$PWD/../../lib_ut.pri)
SOURCES = \
tlAlgorithmTests.cc \
tlBase64Tests.cc \
tlBitSetMapTests.cc \
tlBitSetMaskTests.cc \
tlBitSetTests.cc \
tlClassRegistryTests.cc \
tlCommandLineParserTests.cc \
tlColorTests.cc \

BIN
testdata/bd/strmxor_au1d2.oas vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
testdata/gds/pcell_test20.gds vendored Normal file

Binary file not shown.

View File

@ -27,6 +27,10 @@ class BoxPCell(pya.PCellDeclaration):
# provide a descriptive text for the cell
return "Box(L=" + str(parameters[0]) + ",W=" + ('%.3f' % parameters[1]) + ",H=" + ('%.3f' % parameters[2]) + ")"
def cell_name(self, parameters):
# provide a descriptive text for the cell
return "Box_L" + str(parameters[0]).replace("/", "d") + "_W" + ('%.3f' % parameters[1]).replace(".", "p") + "_H" + ('%.3f' % parameters[2]).replace(".", "p")
def get_parameters(self):
# prepare a set of parameter declarations
@ -100,6 +104,10 @@ if "PCellDeclarationHelper" in pya.__dict__:
# provide a descriptive text for the cell
return "Box2(L=" + str(self.layer) + ",W=" + ('%.3f' % self.width) + ",H=" + ('%.3f' % self.height) + ")"
def cell_name_impl(self):
# provide a descriptive text for the cell
return "Box2_L" + str(self.layer).replace("/", "d") + "_W" + ('%.3f' % self.width).replace(".", "p") + "_H" + ('%.3f' % self.height).replace(".", "p")
def wants_lazy_evaluation(self):
return True
@ -260,6 +268,8 @@ class DBPCellTests(unittest.TestCase):
self.assertEqual(pcell_var.is_pcell_variant(), True)
self.assertEqual(pcell_var.display_title(), "PCellTestLib.Box(L=1/0,W=1.000,H=1.000)")
self.assertEqual(pcell_var.basic_name(), "Box")
self.assertEqual(pcell_var.qname(), "PCellTestLib.Box")
self.assertEqual(pcell_var.name, "Box_L1d0_W1p000_H1p000")
self.assertEqual(pcell_var.pcell_declaration().wants_lazy_evaluation(), False)
self.assertEqual(c1.is_pcell_variant(), False)
self.assertEqual(c1.is_pcell_variant(pcell_inst), True)
@ -394,6 +404,8 @@ class DBPCellTests(unittest.TestCase):
pcell_var = ly.cell(pcell_var_id)
pcell_inst = c1.insert(pya.CellInstArray(pcell_var_id, pya.Trans()))
self.assertEqual(pcell_var.basic_name(), "Box2")
self.assertEqual(pcell_var.name, "Box2_L1d0_W1p000_H1p000")
self.assertEqual(pcell_var.qname(), "PCellTestLib2.Box2")
self.assertEqual(pcell_var.pcell_parameters().__repr__(), "[<1/0>, 1.0, 1.0]")
self.assertEqual(pcell_var.display_title(), "PCellTestLib2.Box2(L=1/0,W=1.000,H=1.000)")
self.assertEqual(nh(pcell_var.pcell_parameters_by_name()), "{'height': 1.0, 'layer': <1/0>, 'width': 1.0}")

View File

@ -40,6 +40,11 @@ class BoxPCell < RBA::PCellDeclaration
return "Box(L=#{parameters[0].to_s},W=#{'%.3f' % parameters[1].to_s},H=#{'%.3f' % parameters[2].to_s})"
end
def cell_name(parameters)
# provide a cell name for the PCell
return "Box_L#{parameters[0].to_s.gsub('/','d')}_W#{('%.3f' % parameters[1]).gsub('.','p')}_H#{('%.3f' % parameters[2]).gsub('.','p')}"
end
def get_parameters
# prepare a set of parameter declarations
@ -129,6 +134,11 @@ if RBA.constants.member?(:PCellDeclarationHelper)
return "Box2(L=" + layer.to_s + ",W=" + ('%.3f' % width) + ",H=" + ('%.3f' % height) + ")"
end
def cell_name_impl
# provide a cell name for the PCell
return "Box2_L" + layer.to_s.gsub('/', 'd') + "_W" + ('%.3f' % width).gsub('.', 'p') + "_H" + ('%.3f' % height).gsub('.', 'p')
end
def produce_impl
# fetch the parameters
@ -371,6 +381,9 @@ class DBPCell_TestClass < TestBase
param = [ RBA::LayerInfo::new(1, 0) ] # rest is filled with defaults
pcell_var_id = ly.add_pcell_variant(lib, pcell_decl_id, param)
pcell_var = ly.cell(pcell_var_id)
assert_equal(pcell_var.name, "Box_L1d0_W1p000_H1p000")
assert_equal(pcell_var.qname, "PCellTestLib.Box")
assert_equal(pcell_var.basic_name, "Box")
pcell_inst = c1.insert(RBA::CellInstArray::new(pcell_var_id, RBA::Trans::new))
assert_equal(pcell_var.layout.inspect, ly.inspect)
assert_equal(pcell_var.library.inspect, lib.inspect)
@ -512,6 +525,8 @@ class DBPCell_TestClass < TestBase
pcell_var = ly.cell(pcell_var_id)
pcell_inst = c1.insert(RBA::CellInstArray::new(pcell_var_id, RBA::Trans::new))
assert_equal(pcell_var.basic_name, "Box2")
assert_equal(pcell_var.name, "Box2_L1d0_W1p000_H1p000")
assert_equal(pcell_var.qname, "PCellTestLib2.Box2")
assert_equal(pcell_var.pcell_parameters().inspect, "[<1/0>, 1.0, 1.0, 0]")
assert_equal(pcell_var.display_title(), "PCellTestLib2.Box2(L=1/0,W=1.000,H=1.000)")
assert_equal(norm_hash(pcell_var.pcell_parameters_by_name()), "{\"height\"=>1.0, \"layer\"=><1/0>, \"secret\"=>0, \"width\"=>1.0}")
@ -908,6 +923,37 @@ class DBPCell_TestClass < TestBase
end
# convert to static cell
def test_13
if !RBA.constants.member?(:PCellDeclarationHelper)
return
end
# instantiate and register the library
tl = PCellTestLib2::new
ly = RBA::Layout::new(true)
ly.dbu = 0.01
ci1 = ly.add_cell("c1")
c1 = ly.cell(ci1)
lib = RBA::Library.library_by_name("PCellTestLib2")
assert_equal(lib != nil, true)
pcell_decl = lib.layout().pcell_declaration("Box2")
param = [ RBA::LayerInfo::new(1, 0) ] # rest is filled with defaults
pcell_var_id = ly.add_pcell_variant(lib, pcell_decl.id(), param)
static_id = ly.convert_cell_to_static(pcell_var_id)
static = ly.cell(static_id)
assert_equal(static.basic_name, "Box2_L1d0_W1p000_H1p000")
assert_equal(static.name, "Box2_L1d0_W1p000_H1p000")
assert_equal(static.qname, "Box2_L1d0_W1p000_H1p000")
assert_equal(static.is_proxy?, false)
end
end
class DBPCellParameterStates_TestClass < TestBase