diff --git a/src/edt/edt/edt.pro b/src/edt/edt/edt.pro index b57f0144e..c9f440dca 100644 --- a/src/edt/edt/edt.pro +++ b/src/edt/edt/edt.pro @@ -21,7 +21,8 @@ HEADERS = \ edtServiceImpl.h \ edtUtils.h \ edtCommon.h \ - edtPCellParametersDialog.h + edtPCellParametersDialog.h \ + edtDistribute.h FORMS = \ AlignOptionsDialog.ui \ @@ -60,7 +61,8 @@ SOURCES = \ edtServiceImpl.cc \ edtUtils.cc \ gsiDeclEdt.cc \ - edtPCellParametersDialog.cc + edtPCellParametersDialog.cc \ + edtDistribute.cc INCLUDEPATH += $$TL_INC $$GSI_INC $$LAYBASIC_INC $$DB_INC DEPENDPATH += $$TL_INC $$GSI_INC $$LAYBASIC_INC $$DB_INC diff --git a/src/edt/edt/edtDistribute.cc b/src/edt/edt/edtDistribute.cc new file mode 100644 index 000000000..1b3631cb2 --- /dev/null +++ b/src/edt/edt/edtDistribute.cc @@ -0,0 +1,27 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2020 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 "edtDistribute.h" + + +// .. nothing yet (all in header) .. + diff --git a/src/edt/edt/edtDistribute.h b/src/edt/edt/edtDistribute.h new file mode 100644 index 000000000..89b725dec --- /dev/null +++ b/src/edt/edt/edtDistribute.h @@ -0,0 +1,413 @@ +/* + + KLayout Layout Viewer + Copyright (C) 2006-2020 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_edtDistribute +#define HDR_edtDistribute + +#include "dbBox.h" + +#include "dbTypes.h" +#include "tlIntervalMap.h" + +namespace edt +{ + +/** + * @brief Gets the box position by reference position + */ +template +typename Box::coord_type box_position (const Box &box, int ref) +{ + if (horizontally) { + if (ref < 0) { + return box.left (); + } else if (ref == 0) { + return box.center ().x (); + } else { + return box.right (); + } + } else { + if (ref < 0) { + return box.bottom (); + } else if (ref == 0) { + return box.center ().y (); + } else { + return box.top (); + } + } +} + +/** + * @brief Compares boxes by their reference position + */ +template +class box_compare +{ +public: + typedef typename Box::coord_type coord_type; + + box_compare (int ref) + : m_ref (ref) + { + // .. nothing yet .. + } + + bool operator() (const std::pair &a, const std::pair &b) const + { + coord_type ca = box_position (a.first, m_ref); + coord_type cb = box_position (b.first, m_ref); + + if (! db::coord_traits::equal (ca, cb)) { + return db::coord_traits::less (ca, cb); + } else { + coord_type ca2 = box_position (a.first, m_ref); + coord_type cb2 = box_position (b.first, m_ref); + return db::coord_traits::less (ca2, cb2); + } + } + +private: + int m_ref; +}; + +/** + * @brief Does some heuristic binning of coordinates + */ +template +void do_bin (typename std::vector >::const_iterator b, typename std::vector >::const_iterator e, int ref, std::vector > &bins) +{ + typedef typename Box::coord_type coord_type; + + // determine maximum distance between adjacent coordinates + + coord_type max_dist = 0; + for (typename std::vector >::const_iterator i = b + 1; i != e; ++i) { + max_dist = std::max (max_dist, box_position (i->first, ref) - box_position ((i - 1)->first, ref)); + } + + // heuristically, everything that has a distance of less than 1/3 of the maximum distance falls into one bin + + coord_type bin_start = box_position (b->first, ref); + bins.push_back (std::vector ()); + bins.back ().push_back (b->second); + + coord_type thr = max_dist / 3; + + for (typename std::vector >::const_iterator i = b + 1; i != e; ++i) { + coord_type c = box_position (i->first, ref); + if (c - bin_start > thr) { + // start a new bin + bins.push_back (std::vector ()); + bin_start = c; + } + bins.back ().push_back (i->second); + } +} + +template +struct max_coord_join_op +{ + void operator() (Coord &a, const Coord &b) const + { + a = std::max (a, b); + } +}; + +/** + * @brief Computes the actual row/columns positions starting from 0 + * The positions are the "low" side positions of the boxes (ref = -1) + */ +template +void compute_positions (int ref, typename Box::coord_type pitch, typename Box::coord_type space, std::vector > &bins, Objects &objects) +{ + typedef typename Box::coord_type coord_type; + + tl::interval_map limits; + + coord_type min, max; + bool first = true; + for (typename Objects::const_iterator o = objects.begin (); o != objects.end (); ++o) { + coord_type b1 = box_position (o->first, -1); + coord_type b2 = box_position (o->first, 1); + if (first) { + min = b1; + max = b2; + } else { + min = std::min (min, b1); + max = std::max (max, b1); + } + } + + max_coord_join_op join_op; + + limits.add (min, max, (coord_type) 0, join_op); + + // Determines the next column/row's position as the minimum position which is compatible with + // the space constraint. + + for (std::vector >::const_iterator b = bins.begin (); b != bins.end (); ++b) { + + coord_type min_pos = 1; + + for (std::vector::const_iterator i = b->begin (); i != b->end (); ++i) { + + const Box &box = objects [*i].first; + + coord_type b1 = box_position (box, -1); + coord_type b2 = box_position (box, 1); + + coord_type start = box_position (box, -1); + coord_type ref_pos = box_position (box, ref); + + for (typename tl::interval_map::const_iterator j = limits.find (b1); j != limits.end () && db::coord_traits::less (j->first.first, b2); ++j) { + min_pos = std::max (min_pos, j->second + space + (ref_pos - start)); + } + + } + + if (pitch > 0) { + min_pos = db::coord_traits::rounded (ceil (double (min_pos) / double (pitch) - 1e-10) * double (pitch)); + } + + for (std::vector::const_iterator i = b->begin (); i != b->end (); ++i) { + + Box &box = objects [*i].first; + + coord_type b1 = box_position (box, -1); + coord_type b2 = box_position (box, 1); + + coord_type ref_pos = box_position (box, ref); + coord_type end_pos = box_position (box, 1); + + if (horizontally) { + box.move (db::vector (min_pos - ref_pos, 0)); + } else { + box.move (db::vector (0, min_pos - ref_pos)); + } + + limits.add (b1, b2, min_pos + (end_pos - ref_pos), join_op); + + } + + } +} + +/** + * @brief Implements an algorithm for 2d-distributing rectangular objects + */ +template +class distributed_placer +{ +public: + typedef typename Box::coord_type coord_type; + typedef std::vector > objects; + typedef typename objects::const_iterator iterator; + + /** + * @brief Constructor + */ + distributed_placer () + { + // .. nothing yet .. + } + + /** + * @brief Reserves space for n objects + */ + void reserve (size_t n) + { + m_objects.reserve (n); + } + + /** + * @brief Inserts a new object + */ + void insert (const Box &box, const Value &value) + { + tl_assert (! box.empty ()); + m_objects.push_back (std::make_pair (box, value)); + } + + /** + * @brief Stored objects iterator: begin + */ + iterator begin () const + { + return m_objects.begin (); + } + + /** + * @brief Stored objects iterator: end + */ + iterator end () const + { + return m_objects.end (); + } + + /** + * @brief Distributes the stored objects in vertical direction only + * + * @param ref The reference location (-1: bottom, 0: center, 1: top) + * @param pitch The distribution pitch (grid) or 0 for no pitch + * @param space The minimum space between the objects + */ + void distribute_v (int ref, coord_type pitch, coord_type space) + { + do_distribute_1d (ref, pitch, space); + } + + /** + * @brief Distributes the stored objects in horizontal direction only + * + * @param ref The reference location (-1: left, 0: center, 1: right) + * @param pitch The distribution pitch (grid) or 0 for no pitch + * @param space The minimum space between the objects + */ + void distribute_h (int ref, coord_type pitch, coord_type space) + { + do_distribute_1d (ref, pitch, space); + } + + /** + * @brief Distributes the stored objects in horizontal and vertical direction + * + * @param href The horizontal reference location (-1: left, 0: center, 1: right) + * @param hpitch The horizontal distribution pitch (grid) or 0 for no pitch + * @param hspace The horizontal minimum space between the objects + * @param vref The vertical reference location (-1: bottom, 0: center, 1: top) + * @param vpitch The vertical distribution pitch (grid) or 0 for no pitch + * @param vspace The vertical minimum space between the objects + */ + void distribute_matrix (int href, coord_type hpitch, coord_type hspace, int vref, coord_type vpitch, coord_type vspace) + { + if (m_objects.size () < 2) { + return; + } + + // The algorithm is this: + // 1.) Bin the boxes according to their positions in horizontal and vertical direction. + // This forms the potential columns and rows + // 2.) Compute the actual column and row positions by applying space and pitch constraints: + // horizontally first, then vertically (TODO: we could try both ways and test which one is better) + + std::vector > indexed_boxes; + indexed_boxes.reserve (m_objects.size ()); + + Box all; + for (typename objects::iterator i = m_objects.begin (); i != m_objects.end (); ++i) { + all += i->first; + } + + size_t n = 0; + for (typename objects::iterator i = m_objects.begin (); i != m_objects.end (); ++i, ++n) { + indexed_boxes.push_back (std::make_pair (i->first, n)); + } + + std::vector > hbins, vbins; + + std::sort (indexed_boxes.begin (), indexed_boxes.end (), box_compare (href)); + do_bin (indexed_boxes.begin (), indexed_boxes.end (), href, hbins); + + std::sort (indexed_boxes.begin (), indexed_boxes.end (), box_compare (vref)); + do_bin (indexed_boxes.begin (), indexed_boxes.end (), vref, vbins); + + compute_positions (href, hpitch, hspace, hbins, m_objects); + compute_positions (vref, vpitch, vspace, vbins, m_objects); + + // Final adjustments + + Box new_all; + for (typename objects::iterator i = m_objects.begin (); i != m_objects.end (); ++i, ++n) { + new_all += i->first; + } + + coord_type dh = box_position (all, href) - box_position (new_all, href); + coord_type dv = box_position (all, vref) - box_position (new_all, vref); + db::vector mv (dh, dv); + + for (typename objects::iterator i = m_objects.begin (); i != m_objects.end (); ++i) { + i->first.move (mv); + } + } + +private: + objects m_objects; + + template + void do_distribute_1d (int ref, coord_type pitch, coord_type space) + { + if (m_objects.size () < 2) { + return; + } + + Box all; + for (typename objects::const_iterator i = m_objects.begin () + 1; i != m_objects.end (); ++i) { + all += i->first; + } + + std::sort (m_objects.begin (), m_objects.end (), box_compare (ref)); + + Box current = m_objects.front ().first; + coord_type p0 = box_position (current, ref); + + for (typename objects::iterator i = m_objects.begin () + 1; i != m_objects.end (); ++i) { + + coord_type p = box_position (i->first, -1); + coord_type offset = box_position (i->first, ref) - p; + coord_type pnew = box_position (current, 1) + space; + + if (db::coord_traits::less (0, pitch)) { + pnew = coord_type (ceil (double (pnew + offset - p0) / double (pitch) - 1e-10)) * pitch - offset; + } + + db::vector mv; + if (horizontally) { + mv = db::vector (pnew - p, 0); + } else { + mv = db::vector (0, pnew - p); + } + + i->first.move (mv); + current = i->first; + + } + + // final adjustment + Box new_all = m_objects.front ().first + m_objects.back ().first; + + db::vector mv; + coord_type d = box_position (all, ref) - box_position (new_all, ref); + if (horizontally) { + mv = db::vector (d, 0); + } else { + mv = db::vector (0, d); + } + + for (typename objects::iterator i = m_objects.begin (); i != m_objects.end (); ++i) { + i->first.move (mv); + } + } +}; + +} + +#endif + diff --git a/src/edt/edt/edtMainService.cc b/src/edt/edt/edtMainService.cc index 89df404ac..614e1f4e4 100644 --- a/src/edt/edt/edtMainService.cc +++ b/src/edt/edt/edtMainService.cc @@ -43,6 +43,7 @@ #include "edtConfig.h" #include "edtDialogs.h" #include "edtEditorOptionsPages.h" +#include "edtDistribute.h" #include #include @@ -1872,73 +1873,86 @@ MainService::cm_distribute () return; } - db::DBox prim_box; - bool has_secondary = false; + if (! m_hdistribute && ! m_vdistribute) { + return; + } - // get (common) bbox index of the primary selection + // count the items + size_t n = 0; for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { for (edt::Service::obj_iterator s = (*es)->selection ().begin (); s != (*es)->selection ().end (); ++s) { + ++n; + } + } - if (s->seq () == 0) { + std::vector > objects_for_service; + std::vector transformations; + + { + + std::vector org_boxes; + org_boxes.reserve (n); + + edt::distributed_placer placer; + placer.reserve (n); + + size_t i = 0; + + for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { + + objects_for_service.push_back (std::make_pair (i, i)); + + for (edt::Service::obj_iterator s = (*es)->selection ().begin (); s != (*es)->selection ().end (); ++s) { const db::Layout &layout = view ()->cellview (s->cv_index ())->layout (); db::CplxTrans tr = db::CplxTrans (layout.dbu ()) * s->trans (); + db::DBox box; if (! s->is_cell_inst ()) { - prim_box += tr * s->shape ().bbox (); + box = tr * s->shape ().bbox (); } else { - prim_box += inst_bbox (tr, view (), s->cv_index (), s->back (), m_distribute_visible_layers); + box = inst_bbox (tr, view (), s->cv_index (), s->back (), m_distribute_visible_layers); } - } else { - has_secondary = true; + org_boxes.push_back (box); + placer.insert (box, i); + + ++i; + } + objects_for_service.back ().second = i; + } + + if (m_hdistribute && m_vdistribute) { + placer.distribute_matrix (int (m_distribute_hmode - 2), m_distribute_hpitch, m_distribute_hspace, + 2 - int (m_distribute_vmode), m_distribute_vpitch, m_distribute_vspace); + } else if (m_hdistribute) { + placer.distribute_h (int (m_distribute_hmode - 2), m_distribute_hpitch, m_distribute_hspace); + } else if (m_vdistribute) { + placer.distribute_v (2 - int (m_distribute_vmode), m_distribute_vpitch, m_distribute_vspace); + } + + transformations.resize (org_boxes.size ()); + + for (edt::distributed_placer::iterator i = placer.begin (); i != placer.end (); ++i) { + transformations[i->second] = db::DCplxTrans (i->first.p1 () - org_boxes[i->second].p1 ()); + } + } - if (! prim_box.empty ()) { - + { view ()->cancel_edits (); - manager ()->transaction (tl::to_string (QObject::tr ("Alignment"))); + manager ()->transaction (tl::to_string (QObject::tr ("Distribution"))); - - - // do the alignment + // do the distribution for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { + size_t ie = es - edt_services.begin (); + // create a transformation vector that describes each shape's transformation - std::vector tv; - tv.reserve ((*es)->selection ().size ()); - - for (edt::Service::obj_iterator s = (*es)->selection ().begin (); s != (*es)->selection ().end (); ++s) { - - db::DVector v; - -// @@@ - if (s->seq () > 0 || !has_secondary) { - - db::Layout &layout = view ()->cellview (s->cv_index ())->layout (); - db::CplxTrans tr = db::CplxTrans (layout.dbu ()) * s->trans (); - - if (! s->is_cell_inst ()) { - - db::DBox box = tr * s->shape ().bbox (); - v = compute_alignment_vector (prim_box, box, m_distribute_hmode, m_distribute_vmode); - - } else { - - db::DBox box = inst_bbox (tr, view (), s->cv_index (), s->back (), m_distribute_visible_layers); - v = compute_alignment_vector (prim_box, box, m_distribute_hmode, m_distribute_vmode); - - } - - } -// @@@ - - tv.push_back (db::DCplxTrans (db::DTrans (v))); - - } + std::vector tv (transformations.begin () + objects_for_service [ie].first, transformations.begin () + objects_for_service [ie].second); // use the "transform" method to transform the shapes and instances (with individual transformations) (*es)->transform (db::DCplxTrans () /*dummy*/, &tv); diff --git a/src/edt/unit_tests/edtDistributeTests.cc b/src/edt/unit_tests/edtDistributeTests.cc new file mode 100644 index 000000000..aa5927a81 --- /dev/null +++ b/src/edt/unit_tests/edtDistributeTests.cc @@ -0,0 +1,98 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2020 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 "tlUnitTest.h" + +#include "edtDistribute.h" + +template +static std::string plc2string (const edt::distributed_placer &plc) +{ + std::string s; + for (typename edt::distributed_placer::iterator i = plc.begin (); i != plc.end (); ++i) { + if (! s.empty ()) { + s += ","; + } + s += tl::to_string (i->first); + s += "["; + s += tl::to_string (i->second); + s += "]"; + } + return s; +} + +TEST(1) +{ + edt::distributed_placer placer; + + placer.insert (db::Box (1000, 0, 1100, 200), 0); + placer.insert (db::Box (2000, 0, 2100, 500), 1); + placer.insert (db::Box (0, -100, 100, 100), 2); + placer.insert (db::Box (1000, 100, 1050, 250), 3); + placer.insert (db::Box (1050, -50, 1100, 150), 4); + + edt::distributed_placer p; + + p = placer; + p.distribute_h (-1, 0, 100); + + EXPECT_EQ (plc2string (p), "(0,-100;100,100)[2],(200,0;300,200)[0],(400,100;450,250)[3],(550,-50;600,150)[4],(700,0;800,500)[1]"); + + p = placer; + p.distribute_h (-1, 100, 0); + + EXPECT_EQ (plc2string (p), "(0,-100;100,100)[2],(100,0;200,200)[0],(200,100;250,250)[3],(300,-50;350,150)[4],(400,0;500,500)[1]"); + + p = placer; + p.distribute_h (-1, 0, 0); + + EXPECT_EQ (plc2string (p), "(0,-100;100,100)[2],(100,0;200,200)[0],(200,100;250,250)[3],(250,-50;300,150)[4],(300,0;400,500)[1]"); + + p = placer; + p.distribute_h (1, 0, 100); + + EXPECT_EQ (plc2string (p), "(1300,-100;1400,100)[2],(1500,100;1550,250)[3],(1650,-50;1700,150)[4],(1800,0;1900,200)[0],(2000,0;2100,500)[1]"); + + p = placer; + p.distribute_v (-1, 0, 100); + + EXPECT_EQ (plc2string (p), "(0,-100;100,100)[2],(1050,200;1100,400)[4],(1000,500;1100,700)[0],(2000,800;2100,1300)[1],(1000,1400;1050,1550)[3]"); +} + + +TEST(2) +{ + edt::distributed_placer placer; + + placer.insert (db::Box (-5, 1, 95, 101), 0); + placer.insert (db::Box (1, 95, 101, 195), 1); + placer.insert (db::Box (110, 105, 210, 205), 2); + placer.insert (db::Box (101, 0, 201, 100), 3); + + edt::distributed_placer p; + + p = placer; + p.distribute_matrix (-1, 0, 0, -1, 0, 0); + + EXPECT_EQ (plc2string (p), "(-5,0;95,100)[0],(-5,100;95,200)[1],(95,100;195,200)[2],(95,0;195,100)[3]"); +} + diff --git a/src/edt/unit_tests/unit_tests.pro b/src/edt/unit_tests/unit_tests.pro index 630f1cd9b..14be20d9e 100644 --- a/src/edt/unit_tests/unit_tests.pro +++ b/src/edt/unit_tests/unit_tests.pro @@ -8,6 +8,7 @@ include($$PWD/../../lib_ut.pri) SOURCES = \ edtBasicTests.cc \ + edtDistributeTests.cc INCLUDEPATH += $$EDT_INC $$TL_INC $$LAYBASIC_INC $$DB_INC $$GSI_INC DEPENDPATH += $$EDT_INC $$TL_INC $$LAYBASIC_INC $$DB_INC $$GSI_INC diff --git a/src/tl/tl/tlIntervalMap.h b/src/tl/tl/tlIntervalMap.h index edfb819d9..0f84b29b9 100644 --- a/src/tl/tl/tlIntervalMap.h +++ b/src/tl/tl/tlIntervalMap.h @@ -270,6 +270,23 @@ public: } } + /** + * @brief Returns the iterator for a given index + * + * This will return the iterator for the interval which contains the index. If there is no such interval, this method + * will return end(). + * + * If there is no interval for the given index (not set), the returned iterator will not be end(), but it's + * start value will not be less or equal to the index. + * + * @param i The index to search for + * @return The iterator to the corresponding interval or end() if there is no such interval + */ + const_iterator find (I i) const + { + return std::lower_bound (m_index_map.begin (), m_index_map.end (), i, iv_compare_f ()); + } + /** * @brief Do a brief check if the structure is sorted */