From f501f039c0707d418525594f77b52f71f9f54781 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 21 Mar 2026 14:59:08 +0100 Subject: [PATCH] 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". --- src/db/db/db.pro | 2 + src/db/db/dbFileBasedLibrary.cc | 120 ++++++++++++++++++++++++++++ src/db/db/dbFileBasedLibrary.h | 105 ++++++++++++++++++++++++ src/db/db/dbLibrary.cc | 2 - src/db/db/gsiDeclDbLibrary.cc | 53 ++++++++++++ src/db/unit_tests/dbLibraryTests.cc | 92 +++++++++++++++++++++ src/db/unit_tests/unit_tests.pro | 1 + src/lay/lay/layLibraryController.cc | 84 +------------------ testdata/ruby/dbLibrary.rb | 64 +++++++++++++++ 9 files changed, 441 insertions(+), 82 deletions(-) create mode 100644 src/db/db/dbFileBasedLibrary.cc create mode 100644 src/db/db/dbFileBasedLibrary.h create mode 100644 src/db/unit_tests/dbLibraryTests.cc diff --git a/src/db/db/db.pro b/src/db/db/db.pro index 188abf0d8..9add5b7e8 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -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 \ diff --git a/src/db/db/dbFileBasedLibrary.cc b/src/db/db/dbFileBasedLibrary.cc new file mode 100644 index 000000000..63086fda1 --- /dev/null +++ b/src/db/db/dbFileBasedLibrary.cc @@ -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 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); +} + +} + diff --git a/src/db/db/dbFileBasedLibrary.h b/src/db/db/dbFileBasedLibrary.h new file mode 100644 index 000000000..921ebe383 --- /dev/null +++ b/src/db/db/dbFileBasedLibrary.h @@ -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 +#include + +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 &other_paths = std::list ()) + { + m_path = path; + m_other_paths = other_paths; + } + +private: + std::string m_name; + std::string m_path; + std::list m_other_paths; + bool m_is_loaded; + + void merge_impl (const std::string &path); +}; + +} + +#endif + + diff --git a/src/db/db/dbLibrary.cc b/src/db/db/dbLibrary.cc index 66c1217cf..921ce25ce 100644 --- a/src/db/db/dbLibrary.cc +++ b/src/db/db/dbLibrary.cc @@ -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 (); } diff --git a/src/db/db/gsiDeclDbLibrary.cc b/src/db/db/gsiDeclDbLibrary.cc index 4da1335f2..0c111ae48 100644 --- a/src/db/db/gsiDeclDbLibrary.cc +++ b/src/db/db/gsiDeclDbLibrary.cc @@ -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 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 &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 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 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" diff --git a/src/db/unit_tests/dbLibraryTests.cc b/src/db/unit_tests/dbLibraryTests.cc new file mode 100644 index 000000000..3959e677f --- /dev/null +++ b/src/db/unit_tests/dbLibraryTests.cc @@ -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); +} diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index a51884427..fa26f60fb 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -11,6 +11,7 @@ SOURCES = \ dbCompoundOperationTests.cc \ dbEdgeNeighborhoodTests.cc \ dbFillToolTests.cc \ + dbLibraryTests.cc \ dbLogTests.cc \ dbObjectWithPropertiesTests.cc \ dbPLCConvexDecompositionTests.cc \ diff --git a/src/lay/lay/layLibraryController.cc b/src/lay/lay/layLibraryController.cc index 97b2b54d0..e507b7e93 100644 --- a/src/lay/lay/layLibraryController.cc +++ b/src/lay/lay/layLibraryController.cc @@ -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 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 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 libs_by_name_here; + std::map 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 lib (new FileBasedLibrary (lib_path)); + std::unique_ptr 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); diff --git a/testdata/ruby/dbLibrary.rb b/testdata/ruby/dbLibrary.rb index 38841ff35..ac1a15235 100644 --- a/testdata/ruby/dbLibrary.rb +++ b/testdata/ruby/dbLibrary.rb @@ -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")