WIP: distribute feature.

This commit is contained in:
Matthias Koefferlein 2020-08-08 00:14:48 +02:00
parent 8e4e5cde12
commit 28520697a3
7 changed files with 619 additions and 47 deletions

View File

@ -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

View File

@ -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) ..

413
src/edt/edt/edtDistribute.h Normal file
View File

@ -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 <class Box, bool horizontally>
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, class Value, bool horizontally>
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<Box, Value> &a, const std::pair<Box, Value> &b) const
{
coord_type ca = box_position<Box, horizontally> (a.first, m_ref);
coord_type cb = box_position<Box, horizontally> (b.first, m_ref);
if (! db::coord_traits<coord_type>::equal (ca, cb)) {
return db::coord_traits<coord_type>::less (ca, cb);
} else {
coord_type ca2 = box_position<Box, !horizontally> (a.first, m_ref);
coord_type cb2 = box_position<Box, !horizontally> (b.first, m_ref);
return db::coord_traits<coord_type>::less (ca2, cb2);
}
}
private:
int m_ref;
};
/**
* @brief Does some heuristic binning of coordinates
*/
template <class Box, bool horizontally>
void do_bin (typename std::vector<std::pair<Box, size_t> >::const_iterator b, typename std::vector<std::pair<Box, size_t> >::const_iterator e, int ref, std::vector<std::vector<size_t> > &bins)
{
typedef typename Box::coord_type coord_type;
// determine maximum distance between adjacent coordinates
coord_type max_dist = 0;
for (typename std::vector<std::pair<Box, size_t> >::const_iterator i = b + 1; i != e; ++i) {
max_dist = std::max (max_dist, box_position<Box, horizontally> (i->first, ref) - box_position<Box, horizontally> ((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<Box, horizontally> (b->first, ref);
bins.push_back (std::vector<size_t> ());
bins.back ().push_back (b->second);
coord_type thr = max_dist / 3;
for (typename std::vector<std::pair<Box, size_t> >::const_iterator i = b + 1; i != e; ++i) {
coord_type c = box_position<Box, horizontally> (i->first, ref);
if (c - bin_start > thr) {
// start a new bin
bins.push_back (std::vector<size_t> ());
bin_start = c;
}
bins.back ().push_back (i->second);
}
}
template <class Coord>
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 <class Box, class Objects, bool horizontally>
void compute_positions (int ref, typename Box::coord_type pitch, typename Box::coord_type space, std::vector<std::vector<size_t> > &bins, Objects &objects)
{
typedef typename Box::coord_type coord_type;
tl::interval_map<coord_type, coord_type> 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<Box, ! horizontally> (o->first, -1);
coord_type b2 = box_position<Box, ! horizontally> (o->first, 1);
if (first) {
min = b1;
max = b2;
} else {
min = std::min (min, b1);
max = std::max (max, b1);
}
}
max_coord_join_op<coord_type> 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<std::vector<size_t> >::const_iterator b = bins.begin (); b != bins.end (); ++b) {
coord_type min_pos = 1;
for (std::vector<size_t>::const_iterator i = b->begin (); i != b->end (); ++i) {
const Box &box = objects [*i].first;
coord_type b1 = box_position<Box, ! horizontally> (box, -1);
coord_type b2 = box_position<Box, ! horizontally> (box, 1);
coord_type start = box_position<Box, horizontally> (box, -1);
coord_type ref_pos = box_position<Box, horizontally> (box, ref);
for (typename tl::interval_map<coord_type, coord_type>::const_iterator j = limits.find (b1); j != limits.end () && db::coord_traits<coord_type>::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<coord_type>::rounded (ceil (double (min_pos) / double (pitch) - 1e-10) * double (pitch));
}
for (std::vector<size_t>::const_iterator i = b->begin (); i != b->end (); ++i) {
Box &box = objects [*i].first;
coord_type b1 = box_position<Box, ! horizontally> (box, -1);
coord_type b2 = box_position<Box, ! horizontally> (box, 1);
coord_type ref_pos = box_position<Box, horizontally> (box, ref);
coord_type end_pos = box_position<Box, horizontally> (box, 1);
if (horizontally) {
box.move (db::vector<coord_type> (min_pos - ref_pos, 0));
} else {
box.move (db::vector<coord_type> (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 Box, class Value>
class distributed_placer
{
public:
typedef typename Box::coord_type coord_type;
typedef std::vector<std::pair<Box, Value> > 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<false> (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<true> (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<std::pair<Box, size_t> > 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<std::vector<size_t> > hbins, vbins;
std::sort (indexed_boxes.begin (), indexed_boxes.end (), box_compare<Box, size_t, true> (href));
do_bin<Box, true> (indexed_boxes.begin (), indexed_boxes.end (), href, hbins);
std::sort (indexed_boxes.begin (), indexed_boxes.end (), box_compare<Box, size_t, false> (vref));
do_bin<Box, false> (indexed_boxes.begin (), indexed_boxes.end (), vref, vbins);
compute_positions<Box, objects, true> (href, hpitch, hspace, hbins, m_objects);
compute_positions<Box, objects, false> (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<Box, true> (all, href) - box_position<Box, true> (new_all, href);
coord_type dv = box_position<Box, false> (all, vref) - box_position<Box, false> (new_all, vref);
db::vector<coord_type> 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 <bool horizontally>
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<Box, Value, horizontally> (ref));
Box current = m_objects.front ().first;
coord_type p0 = box_position<Box, horizontally> (current, ref);
for (typename objects::iterator i = m_objects.begin () + 1; i != m_objects.end (); ++i) {
coord_type p = box_position<Box, horizontally> (i->first, -1);
coord_type offset = box_position<Box, horizontally> (i->first, ref) - p;
coord_type pnew = box_position<Box, horizontally> (current, 1) + space;
if (db::coord_traits<coord_type>::less (0, pitch)) {
pnew = coord_type (ceil (double (pnew + offset - p0) / double (pitch) - 1e-10)) * pitch - offset;
}
db::vector<coord_type> mv;
if (horizontally) {
mv = db::vector<coord_type> (pnew - p, 0);
} else {
mv = db::vector<coord_type> (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<coord_type> mv;
coord_type d = box_position<Box, horizontally> (all, ref) - box_position<Box, horizontally> (new_all, ref);
if (horizontally) {
mv = db::vector<coord_type> (d, 0);
} else {
mv = db::vector<coord_type> (0, d);
}
for (typename objects::iterator i = m_objects.begin (); i != m_objects.end (); ++i) {
i->first.move (mv);
}
}
};
}
#endif

View File

@ -43,6 +43,7 @@
#include "edtConfig.h"
#include "edtDialogs.h"
#include "edtEditorOptionsPages.h"
#include "edtDistribute.h"
#include <QMessageBox>
#include <QFontInfo>
@ -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<edt::Service *>::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<std::pair<size_t, size_t> > objects_for_service;
std::vector<db::DCplxTrans> transformations;
{
std::vector<db::DBox> org_boxes;
org_boxes.reserve (n);
edt::distributed_placer<db::DBox, size_t> placer;
placer.reserve (n);
size_t i = 0;
for (std::vector<edt::Service *>::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<db::DBox, size_t>::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<edt::Service *>::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 <db::DCplxTrans> 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 <db::DCplxTrans> 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);

View File

@ -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 <class Box, class Value>
static std::string plc2string (const edt::distributed_placer<Box, Value> &plc)
{
std::string s;
for (typename edt::distributed_placer<Box, Value>::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<db::Box, size_t> 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<db::Box, size_t> 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<db::Box, size_t> 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<db::Box, size_t> 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]");
}

View File

@ -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

View File

@ -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<I, T> ());
}
/**
* @brief Do a brief check if the structure is sorted
*/