Added tests and two convenience methods

The two convenience methods are

Library#library_from_file
Library#library_from_files

Both create and register a Library object tied to a file.
This object supports proper reloading and re-mapping on
"refresh".
This commit is contained in:
Matthias Koefferlein 2026-03-21 14:59:08 +01:00
parent ca3505b872
commit f501f039c0
9 changed files with 441 additions and 82 deletions

View File

@ -34,6 +34,7 @@ SOURCES = \
dbEdgeProcessor.cc \
dbEdges.cc \
dbEdgesLocalOperations.cc \
dbFileBasedLibrary.cc \
dbFillTool.cc \
dbFuzzyCellMapping.cc \
dbGenericShapeIterator.cc \
@ -274,6 +275,7 @@ HEADERS = \
dbEdges.h \
dbEdgesLocalOperations.h \
dbEdgesToContours.h \
dbFileBasedLibrary.h \
dbFillTool.h \
dbFuzzyCellMapping.h \
dbGenericShapeIterator.h \

View File

@ -0,0 +1,120 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2026 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 "dbFileBasedLibrary.h"
#include "dbReader.h"
#include "dbCellMapping.h"
#include "tlFileUtils.h"
#include "tlStream.h"
namespace db
{
// -------------------------------------------------------------------------------------------
FileBasedLibrary::FileBasedLibrary (const std::string &path, const std::string &name)
: db::Library (), m_name (name), m_path (path), m_is_loaded (false)
{
set_description (tl::filename (path));
}
void
FileBasedLibrary::merge_with_other_layout (const std::string &path)
{
m_other_paths.push_back (path);
if (m_is_loaded) {
merge_impl (path);
}
}
std::string
FileBasedLibrary::load ()
{
if (! m_is_loaded) {
return reload ();
} else {
return get_name ();
}
}
std::string
FileBasedLibrary::reload ()
{
std::string name = m_name.empty () ? tl::basename (m_path) : m_name;
layout ().clear ();
tl::InputStream stream (m_path);
db::Reader reader (stream);
reader.read (layout ());
// Use the libname if there is one
if (m_name.empty ()) {
db::Layout::meta_info_name_id_type libname_name_id = layout ().meta_info_name_id ("libname");
for (db::Layout::meta_info_iterator m = layout ().begin_meta (); m != layout ().end_meta (); ++m) {
if (m->first == libname_name_id && ! m->second.value.is_nil ()) {
name = m->second.value.to_string ();
break;
}
}
}
for (auto p = m_other_paths.begin (); p != m_other_paths.end (); ++p) {
merge_impl (*p);
}
m_is_loaded = true;
return name;
}
void
FileBasedLibrary::merge_impl (const std::string &path)
{
db::Layout ly;
tl::InputStream stream (path);
db::Reader reader (stream);
reader.read (ly);
std::vector<db::cell_index_type> target_cells, source_cells;
// collect the cells to pull in (all top cells of the library layout)
// NOTE: cells are not overwritten - the first layout wins, in terms
// of cell names and also in terms of database unit.
for (auto c = ly.begin_top_down (); c != ly.end_top_cells (); ++c) {
std::string cn = ly.cell_name (*c);
if (! layout ().has_cell (cn.c_str ())) {
source_cells.push_back (*c);
target_cells.push_back (layout ().add_cell (cn.c_str ()));
}
}
db::CellMapping cm;
cm.create_multi_mapping_full (layout (), target_cells, ly, source_cells);
layout ().copy_tree_shapes (ly, cm);
}
}

View File

@ -0,0 +1,105 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2026 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_dbFileBasedLibrary
#define HDR_dbFileBasedLibrary
#include "dbLibrary.h"
#include "dbCommon.h"
#include <string>
#include <list>
namespace db
{
/**
* @brief A Library specialization that ties a library to a file
*
* This object supports loading a library from multiple files and merging them into
* a single library (e.g. for loading a directory of cell files).
*/
class DB_PUBLIC FileBasedLibrary
: public db::Library
{
public:
/**
* @brief Creates a file-based library object
*
* @param path The file path
* @param name The library name
*
* If the library name is an empty string, the library name is taken from
* a GDS LIBNAME or from the file name in the path.
*
* Note that you need to call "load" in order to actually load the file.
*/
FileBasedLibrary (const std::string &path, const std::string &name = std::string ());
/**
* @brief Merges another file into this library
*
* If the library was not loaded already, the merge requests are postponed
* until "load" is called.
*/
void merge_with_other_layout (const std::string &path);
/**
* @brief Loads the files
*
* If the files are already loaded, this method does nothing.
* It returns the name of the library derived from the first file
* or the name given in the constructor. The constructor name has
* priority.
*/
std::string load ();
/**
* @brief Implements the reload feature
*/
virtual std::string reload ();
/**
* @brief Set the paths
* This method is provided for test purposes only.
*/
void set_paths (const std::string &path, const std::list<std::string> &other_paths = std::list<std::string> ())
{
m_path = path;
m_other_paths = other_paths;
}
private:
std::string m_name;
std::string m_path;
std::list<std::string> m_other_paths;
bool m_is_loaded;
void merge_impl (const std::string &path);
};
}
#endif

View File

@ -177,8 +177,6 @@ Library::refresh ()
void
Library::remap_to (db::Library *other, db::Layout *original_layout)
{
tl_assert (other != this || original_layout != 0);
if (! original_layout) {
original_layout = &layout ();
}

View File

@ -29,6 +29,7 @@
#include "dbPCellDeclaration.h"
#include "dbLibrary.h"
#include "dbLibraryManager.h"
#include "dbFileBasedLibrary.h"
#include "tlLog.h"
namespace gsi
@ -156,11 +157,63 @@ static LibraryImpl *new_lib ()
return new LibraryImpl ();
}
static db::Library *library_from_file (const std::string &path, const std::string &name)
{
std::unique_ptr<db::FileBasedLibrary> lib (new db::FileBasedLibrary (path, name));
std::string n = lib->load ();
db::Library *ret = lib.get ();
register_lib (lib.release (), n);
return ret;
}
static db::Library *library_from_files (const std::vector<std::string> &paths, const std::string &name)
{
if (paths.empty ()) {
throw tl::Exception (tl::to_string (tr ("At least one path must be given")));
}
std::unique_ptr<db::FileBasedLibrary> lib (new db::FileBasedLibrary (paths.front (), name));
for (auto i = paths.begin () + 1; i != paths.end (); ++i) {
lib->merge_with_other_layout (*i);
}
std::string n = lib->load ();
db::Library *ret = lib.get ();
register_lib (lib.release (), n);
return ret;
}
/**
* @brief A basic implementation of the library
*/
LibraryClass<db::Library> decl_Library ("db", "LibraryBase",
gsi::method ("library_from_file", &library_from_file, gsi::arg ("path"), gsi::arg ("name", std::string (), "auto"),
"@brief Creates a library from a file\n"
"@param path The path to the file from which to create the library from.\n"
"@param name The name of the library. If empty, the name will be derived from the GDS LIBNAME or the file name.\n"
"@return The library object created. It is already registered with the name given or derived from the file.\n"
"\n"
"This method will create a \\Library object which is tied to a specific file. This object supports "
"automatic reloading when the \\Library#refresh method is called.\n"
"\n"
"This convenience method has been added in version 0.30.8.\n"
) +
gsi::method ("library_from_files", &library_from_files, gsi::arg ("paths"), gsi::arg ("name", std::string (), "auto"),
"@brief Creates a library from a set of files\n"
"@param paths The paths to the files from which to create the library from. At least one file needs to be given.\n"
"@param name The name of the library. If empty, the name will be derived from the GDS LIBNAME or the file name.\n"
"@return The library object created. It is already registered with the name given or derived from the file.\n"
"\n"
"This method will create a \\Library object which is tied to several files. This object supports "
"automatic reloading when the \\Library#refresh method is called. The content of the files is merged "
"into the library. This is useful for example to create one library from a collection of files.\n"
"\n"
"This convenience method has been added in version 0.30.8.\n"
) +
gsi::method ("library_by_name", &library_by_name, gsi::arg ("name"), gsi::arg ("for_technology", std::string (), "unspecific"),
"@brief Gets a library by name\n"
"Returns the library object for the given name. If the name is not a valid library name, nil is returned.\n"

View File

@ -0,0 +1,92 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2026 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 "dbLibraryManager.h"
#include "dbLibrary.h"
#include "dbFileBasedLibrary.h"
#include "tlUnitTest.h"
#include "tlFileUtils.h"
// map library to different file (aka reload/refresh)
TEST(1)
{
std::string pa = tl::testsrc () + "/testdata/gds/lib_a.gds";
std::string pb = tl::testsrc () + "/testdata/gds/lib_b.gds";
db::FileBasedLibrary *lib = new db::FileBasedLibrary (pa);
lib->load ();
lib->set_name ("LIB");
db::LibraryManager::instance ().register_lib (lib);
db::Layout l;
auto top_cell_index = l.add_cell ("TOP");
auto lib_proxy = l.get_lib_proxy (lib, lib->layout ().cell_by_name ("A").second);
l.cell (top_cell_index).insert (db::CellInstArray (db::CellInst (lib_proxy), db::Trans ()));
EXPECT_EQ (l.cell (top_cell_index).bbox ().to_string (), "(0,0;2000,2000)");
// switch to a different file and refresh
lib->set_paths (pb);
lib->refresh ();
EXPECT_EQ (l.cell (top_cell_index).bbox ().to_string (), "(-1000,-1000;1000,1000)");
db::LibraryManager::instance ().delete_lib (lib);
}
// merge multiple files into one library
TEST(2)
{
std::string pa = tl::testsrc () + "/testdata/gds/lib_a.gds";
std::string pb = tl::testsrc () + "/testdata/gds/lib_b.gds";
db::FileBasedLibrary *lib = new db::FileBasedLibrary (pa);
lib->merge_with_other_layout (pb);
lib->load ();
lib->set_name ("LIB");
db::LibraryManager::instance ().register_lib (lib);
{
db::Layout l;
auto top_cell_index = l.add_cell ("TOP");
auto lib_proxy = l.get_lib_proxy (lib, lib->layout ().cell_by_name ("A").second);
l.cell (top_cell_index).insert (db::CellInstArray (db::CellInst (lib_proxy), db::Trans ()));
// A comes from the first layout
EXPECT_EQ (l.cell (top_cell_index).bbox ().to_string (), "(0,0;2000,2000)");
}
{
db::Layout l;
auto top_cell_index = l.add_cell ("TOP");
auto lib_proxy = l.get_lib_proxy (lib, lib->layout ().cell_by_name ("Z").second);
l.cell (top_cell_index).insert (db::CellInstArray (db::CellInst (lib_proxy), db::Trans ()));
// Z comes from the second layout
EXPECT_EQ (l.cell (top_cell_index).bbox ().to_string (), "(0,0;100,100)");
}
db::LibraryManager::instance ().delete_lib (lib);
}

View File

@ -11,6 +11,7 @@ SOURCES = \
dbCompoundOperationTests.cc \
dbEdgeNeighborhoodTests.cc \
dbFillToolTests.cc \
dbLibraryTests.cc \
dbLogTests.cc \
dbObjectWithPropertiesTests.cc \
dbPLCConvexDecompositionTests.cc \

View File

@ -29,8 +29,7 @@
#include "layQtTools.h"
#include "dbLibraryManager.h"
#include "dbLibrary.h"
#include "dbReader.h"
#include "dbCellMapping.h"
#include "dbFileBasedLibrary.h"
#include "tlLog.h"
#include "tlStream.h"
#include "tlFileUtils.h"
@ -42,81 +41,6 @@ namespace lay
// -------------------------------------------------------------------------------------------
class FileBasedLibrary
: public db::Library
{
public:
FileBasedLibrary (const std::string &path)
: db::Library (), m_path (path)
{
set_description (tl::filename (path));
}
void merge_with_other_layout (const std::string &path)
{
m_other_paths.push_back (path);
merge_impl (path);
}
virtual std::string reload ()
{
std::string name = tl::basename (m_path);
layout ().clear ();
tl::InputStream stream (m_path);
db::Reader reader (stream);
reader.read (layout ());
// Use the libname if there is one
db::Layout::meta_info_name_id_type libname_name_id = layout ().meta_info_name_id ("libname");
for (db::Layout::meta_info_iterator m = layout ().begin_meta (); m != layout ().end_meta (); ++m) {
if (m->first == libname_name_id && ! m->second.value.is_nil ()) {
name = m->second.value.to_string ();
break;
}
}
for (auto p = m_other_paths.begin (); p != m_other_paths.end (); ++p) {
merge_impl (*p);
}
return name;
}
private:
std::string m_path;
std::list<std::string> m_other_paths;
void merge_impl (const std::string &path)
{
db::Layout ly;
tl::InputStream stream (path);
db::Reader reader (stream);
reader.read (ly);
std::vector<db::cell_index_type> target_cells, source_cells;
// collect the cells to pull in (all top cells of the library layout)
// NOTE: cells are not overwritten - the first layout wins, in terms
// of cell names and also in terms of database unit.
for (auto c = ly.begin_top_down (); c != ly.end_top_cells (); ++c) {
std::string cn = ly.cell_name (*c);
if (! layout ().has_cell (cn.c_str ())) {
source_cells.push_back (*c);
target_cells.push_back (layout ().add_cell (cn.c_str ()));
}
}
db::CellMapping cm;
cm.create_multi_mapping_full (layout (), target_cells, ly, source_cells);
layout ().copy_tree_shapes (ly, cm);
}
};
// -------------------------------------------------------------------------------------------
LibraryController::LibraryController ()
: m_file_watcher (0),
dm_sync_files (this, &LibraryController::sync_files)
@ -279,7 +203,7 @@ LibraryController::sync_files ()
} else {
std::map<std::string, FileBasedLibrary *> libs_by_name_here;
std::map<std::string, db::FileBasedLibrary *> libs_by_name_here;
// Reload all files
for (QStringList::const_iterator im = libs.begin (); im != libs.end (); ++im) {
@ -289,13 +213,13 @@ LibraryController::sync_files ()
try {
std::unique_ptr<FileBasedLibrary> lib (new FileBasedLibrary (lib_path));
std::unique_ptr<db::FileBasedLibrary> lib (new db::FileBasedLibrary (lib_path));
if (! p->second.empty ()) {
lib->set_technology (p->second);
}
tl::log << "Reading library '" << lib_path << "'";
std::string libname = lib->reload ();
std::string libname = lib->load ();
// merge with existing lib if there is already one in this folder with the right name
auto il = libs_by_name_here.find (libname);

View File

@ -265,6 +265,70 @@ class DBLibrary_TestClass < TestBase
end
def test_8_file_based_library
# clean up before
[ "RBA-unit-test" ].each do |name|
l = RBA::Library::library_by_name(name)
l && l.unregister
end
lylib = RBA::Layout::new
lylib.read(File.join($ut_testsrc, "testdata", "gds", "lib_a.gds"))
tmp = File::join($ut_testtmp, "rba_dbLibrary_8.gds")
lylib.write(tmp)
lib = RBA::Library::library_from_file(tmp, "RBA-unit-test")
ly = RBA::Layout::new
top = ly.create_cell("TOP")
a = ly.create_cell("A", "RBA-unit-test")
top.insert(RBA::CellInstArray::new(a, RBA::Trans::new))
assert_equal(top.dbbox.to_s, "(0,0;2,2)")
lylib.clear
lylib.read(File.join($ut_testsrc, "testdata", "gds", "lib_b.gds"))
lylib.write(tmp)
lib.refresh
assert_equal(top.dbbox.to_s, "(-1,-1;1,1)")
end
def test_9_file_based_library_multiple_files
# clean up before
[ "RBA-unit-test" ].each do |name|
l = RBA::Library::library_by_name(name)
l && l.unregister
end
files = [
File.join($ut_testsrc, "testdata", "gds", "lib_a.gds"),
File.join($ut_testsrc, "testdata", "gds", "lib_b.gds")
]
lib = RBA::Library::library_from_files(files, "RBA-unit-test")
ly = RBA::Layout::new
top = ly.create_cell("TOP")
a = ly.create_cell("A", "RBA-unit-test")
top.insert(RBA::CellInstArray::new(a, RBA::Trans::new))
assert_equal(top.dbbox.to_s, "(0,0;2,2)")
ly = RBA::Layout::new
top = ly.create_cell("TOP")
z = ly.create_cell("Z", "RBA-unit-test")
top.insert(RBA::CellInstArray::new(z, RBA::Trans::new))
assert_equal(top.dbbox.to_s, "(0,0;0.1,0.1)")
end
end
load("test_epilogue.rb")