Global transformations for DRC and RecursiveShapeIterator

This commit is contained in:
Matthias Koefferlein 2021-03-21 23:09:09 +01:00
parent 536681f5e1
commit afdc50d05a
20 changed files with 608 additions and 48 deletions

View File

@ -953,7 +953,7 @@ DeepShapeStore::cell_mapping_to_original (unsigned int layout_index, db::Layout
// collects the cell mappings we skip because they are variants (variant building or box variants)
std::map<db::cell_index_type, db::HierarchyBuilder::CellMapKey> cm_skipped_variants;
if (into_layout == original_builder.source ().layout () && &into_layout->cell (into_cell) == original_builder.source ().top_cell ()) {
if (into_layout == original_builder.source ().layout () && &into_layout->cell (into_cell) == original_builder.source ().top_cell () && original_builder.source ().global_trans ().is_unity ()) {
// This is the case of mapping back to the original. In this case we can use the information
// provided inside the original hierarchy builders. They list the source cells and the target cells

View File

@ -40,10 +40,10 @@ int
compare_iterators_with_respect_to_target_hierarchy (const db::RecursiveShapeIterator &iter1, const db::RecursiveShapeIterator &iter2)
{
if ((iter1.layout () == 0) != (iter2.layout () == 0)) {
return (iter1.layout () == 0) < (iter2.layout () == 0);
return (iter1.layout () == 0) < (iter2.layout () == 0) ? -1 : 1;
}
if ((iter1.top_cell () == 0) != (iter2.top_cell () == 0)) {
return (iter1.top_cell () == 0) < (iter2.top_cell () == 0);
return (iter1.top_cell () == 0) < (iter2.top_cell () == 0) ? -1 : 1;
}
// basic source (layout, top_cell) needs to be the same of course
@ -70,6 +70,11 @@ compare_iterators_with_respect_to_target_hierarchy (const db::RecursiveShapeIter
return iter1.enables () < iter2.enables () ? -1 : 1;
}
// compare global transformations
if (! iter1.global_trans ().equal (iter2.global_trans ())) {
return iter1.global_trans ().less (iter2.global_trans ()) ? -1 : 1;
}
// if a region is set, the hierarchical appearance is the same only if the layers and
// complex region are identical
if ((iter1.region () == db::Box::world ()) != (iter2.region () == db::Box::world ())) {
@ -338,7 +343,7 @@ HierarchyBuilder::make_cell_variant (const HierarchyBuilder::CellMapKey &key, co
}
HierarchyBuilder::new_inst_mode
HierarchyBuilder::new_inst (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool all)
HierarchyBuilder::new_inst (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool all)
{
if (all) {
@ -349,6 +354,7 @@ HierarchyBuilder::new_inst (const RecursiveShapeIterator *iter, const db::CellIn
if (m_cell_stack.back ().first) {
db::CellInstArray new_inst (inst, &mp_target->array_repository ());
new_inst.object () = db::CellInst (new_cell);
new_inst.transform (always_apply);
new_inst.transform_into (m_trans);
for (std::vector<db::Cell *>::const_iterator c = m_cell_stack.back ().second.begin (); c != m_cell_stack.back ().second.end (); ++c) {
(*c)->insert (new_inst);
@ -367,7 +373,7 @@ HierarchyBuilder::new_inst (const RecursiveShapeIterator *iter, const db::CellIn
}
bool
HierarchyBuilder::new_inst_member (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &trans, const db::Box &region, const box_tree_type *complex_region, bool all)
HierarchyBuilder::new_inst_member (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box &region, const box_tree_type *complex_region, bool all)
{
if (all) {
@ -386,7 +392,7 @@ HierarchyBuilder::new_inst_member (const RecursiveShapeIterator *iter, const db:
// for a new cell, create this instance
if (m_cell_stack.back ().first) {
db::CellInstArray new_inst (db::CellInst (new_cell), trans);
db::CellInstArray new_inst (db::CellInst (new_cell), always_apply * trans);
new_inst.transform_into (m_trans);
for (std::vector<db::Cell *>::const_iterator c = m_cell_stack.back ().second.begin (); c != m_cell_stack.back ().second.end (); ++c) {
(*c)->insert (new_inst);
@ -399,11 +405,11 @@ HierarchyBuilder::new_inst_member (const RecursiveShapeIterator *iter, const db:
}
void
HierarchyBuilder::shape (const RecursiveShapeIterator * /*iter*/, const db::Shape &shape, const db::ICplxTrans & /*trans*/, const db::Box &region, const box_tree_type *complex_region)
HierarchyBuilder::shape (const RecursiveShapeIterator * /*iter*/, const db::Shape &shape, const db::ICplxTrans &apply_always, const db::ICplxTrans & /*trans*/, const db::Box &region, const box_tree_type *complex_region)
{
for (std::vector<db::Cell *>::const_iterator c = m_cell_stack.back ().second.begin (); c != m_cell_stack.back ().second.end (); ++c) {
db::Shapes &shapes = (*c)->shapes (m_target_layer);
mp_pipe->push (shape, m_trans, region, complex_region, &shapes);
mp_pipe->push (shape, m_trans * apply_always, region, complex_region, &shapes);
}
}

View File

@ -273,9 +273,9 @@ public:
virtual void end (const RecursiveShapeIterator *iter);
virtual void enter_cell (const RecursiveShapeIterator *iter, const db::Cell *cell, const db::Box &region, const box_tree_type *complex_region);
virtual void leave_cell (const RecursiveShapeIterator *iter, const db::Cell *cell);
virtual new_inst_mode new_inst (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::Box &region, const box_tree_type *complex_region, bool all);
virtual bool new_inst_member (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &trans, const db::Box &region, const box_tree_type *complex_region, bool all);
virtual void shape (const RecursiveShapeIterator *iter, const db::Shape &shape, const db::ICplxTrans &trans, const db::Box &region, const box_tree_type *complex_region);
virtual new_inst_mode new_inst (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const ICplxTrans &always_apply, const db::Box &region, const box_tree_type *complex_region, bool all);
virtual bool new_inst_member (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box &region, const box_tree_type *complex_region, bool all);
virtual void shape (const RecursiveShapeIterator *iter, const db::Shape &shape, const db::ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box &region, const box_tree_type *complex_region);
/**
* @brief Sets the target layer - shapes will be put there

View File

@ -72,6 +72,7 @@ RecursiveShapeIterator &RecursiveShapeIterator::operator= (const RecursiveShapeI
m_current_layer = d.m_current_layer;
m_shape = d.m_shape;
m_trans = d.m_trans;
m_global_trans = d.m_global_trans;
m_trans_stack = d.m_trans_stack;
m_inst_iterators = d.m_inst_iterators;
m_inst_array_iterators = d.m_inst_array_iterators;
@ -286,6 +287,7 @@ RecursiveShapeIterator::init ()
m_shape_quad_id = 0;
mp_cell = 0;
m_current_layer = 0;
m_global_trans = cplx_trans_type ();
}
void
@ -318,6 +320,26 @@ RecursiveShapeIterator::init_region (const RecursiveShapeIterator::region_type &
}
}
void
RecursiveShapeIterator::set_global_trans (const cplx_trans_type &tr)
{
if (m_global_trans != tr) {
m_global_trans = tr;
m_needs_reinit = true;
}
}
const db::RecursiveShapeIterator::cplx_trans_type &
RecursiveShapeIterator::always_apply () const
{
if (m_trans_stack.empty ()) {
return m_global_trans;
} else {
static cplx_trans_type unity;
return unity;
}
}
void
RecursiveShapeIterator::set_region (const box_type &region)
{
@ -420,13 +442,13 @@ RecursiveShapeIterator::validate (RecursiveShapeReceiver *receiver) const
m_inst_quad_id_stack.clear ();
m_inst_array_iterators.clear ();
m_cells.clear ();
m_trans = cplx_trans_type ();
m_trans = m_global_trans;
m_current_layer = 0;
m_shape = shape_iterator ();
m_shape_quad_id = 0;
m_local_region_stack.clear ();
m_local_region_stack.push_back (m_region);
m_local_region_stack.push_back (m_global_trans.inverted () * m_region);
m_local_complex_region_stack.clear ();
if (mp_complex_region.get ()) {
@ -749,8 +771,8 @@ RecursiveShapeIterator::down (RecursiveShapeReceiver *receiver) const
box_type new_region = box_type::world ();
// compute the region inside the new cell
if (new_region != m_local_region_stack.front ()) {
new_region = m_trans.inverted () * m_local_region_stack.front ();
if (new_region != m_region) {
new_region = m_trans.inverted () * m_region;
new_region &= cell ()->bbox ();
}
m_local_region_stack.push_back (new_region);
@ -911,7 +933,7 @@ RecursiveShapeIterator::new_inst (RecursiveShapeReceiver *receiver) const
RecursiveShapeReceiver::new_inst_mode ni = RecursiveShapeReceiver::NI_all;
if (receiver) {
ni = receiver->new_inst (this, m_inst->cell_inst (), m_local_region_stack.back (), m_local_complex_region_stack.empty () ? 0 : &m_local_complex_region_stack.back (), all_of_instance);
ni = receiver->new_inst (this, m_inst->cell_inst (), always_apply (), m_local_region_stack.back (), m_local_complex_region_stack.empty () ? 0 : &m_local_complex_region_stack.back (), all_of_instance);
}
if (ni == RecursiveShapeReceiver::NI_skip) {
@ -956,7 +978,7 @@ RecursiveShapeIterator::new_inst_member (RecursiveShapeReceiver *receiver) const
}
while (! m_inst_array.at_end () && receiver) {
if (receiver->new_inst_member (this, m_inst->cell_inst (), m_inst->complex_trans (*m_inst_array), m_local_region_stack.back (), m_local_complex_region_stack.empty () ? 0 : &m_local_complex_region_stack.back (), is_all_of_instance ())) {
if (receiver->new_inst_member (this, m_inst->cell_inst (), always_apply (), m_inst->complex_trans (*m_inst_array), m_local_region_stack.back (), m_local_complex_region_stack.empty () ? 0 : &m_local_complex_region_stack.back (), is_all_of_instance ())) {
break;
} else {
++m_inst_array;
@ -999,7 +1021,7 @@ RecursiveShapeIterator::push (RecursiveShapeReceiver *receiver)
validate (receiver);
while (! at_end ()) {
receiver->shape (this, *m_shape, m_trans, m_local_region_stack.back (), m_local_complex_region_stack.empty () ? 0 : &m_local_complex_region_stack.back ());
receiver->shape (this, *m_shape, always_apply (), m_trans, m_local_region_stack.back (), m_local_complex_region_stack.empty () ? 0 : &m_local_complex_region_stack.back ());
next (receiver);
}

View File

@ -391,6 +391,30 @@ public:
}
}
/**
* @brief Sets a global transformation
*
* The global transformation will be applied to all shapes delivered by biasing the "trans" attribute
*/
void set_global_trans (const cplx_trans_type &tr);
/**
* @brief Gets the global transformation
*/
cplx_trans_type global_trans () const
{
return m_global_trans;
}
/**
* @brief Gets the transformation which is to be applied always in push mode
*
* The reasoning behind this method is that in push mode and with the presence of a global transformation we need to
* somehow reflect the fact that the top-level is transformed. Instead of transforming every shape and instance we use
* this attribute. It is unity for all cells below top level and equal to the global transformation for the top cell.
*/
const cplx_trans_type &always_apply () const;
/**
* @brief Reset the iterator
*/
@ -727,6 +751,7 @@ private:
bool m_shape_inv_prop_sel;
bool m_overlapping;
std::set<db::cell_index_type> m_start, m_stop;
cplx_trans_type m_global_trans;
const layout_type *mp_layout;
const cell_type *mp_top_cell;
@ -881,7 +906,7 @@ public:
* - NI_single: iterate a single member (the first one)
* - NI_skip: skips the whole array (not a single instance is iterated)
*/
virtual new_inst_mode new_inst (const RecursiveShapeIterator * /*iter*/, const db::CellInstArray & /*inst*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/) { return NI_all; }
virtual new_inst_mode new_inst (const RecursiveShapeIterator * /*iter*/, const db::CellInstArray & /*inst*/, const db::ICplxTrans & /*always_apply*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/) { return NI_all; }
/**
* @brief Enters a new array member of the instance
@ -894,14 +919,14 @@ public:
*
* If this method returns false, this array instance (but not the whole array) is skipped and the cell is not entered.
*/
virtual bool new_inst_member (const RecursiveShapeIterator * /*iter*/, const db::CellInstArray & /*inst*/, const db::ICplxTrans & /*trans*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/) { return true; }
virtual bool new_inst_member (const RecursiveShapeIterator * /*iter*/, const db::CellInstArray & /*inst*/, const db::ICplxTrans & /*always_apply*/, const db::ICplxTrans & /*trans*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/) { return true; }
/**
* @brief Delivers a shape
*
* @param trans The transformation which maps the shape to the top cell.
*/
virtual void shape (const RecursiveShapeIterator * /*iter*/, const db::Shape & /*shape*/, const db::ICplxTrans & /*trans*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/) { }
virtual void shape (const RecursiveShapeIterator * /*iter*/, const db::Shape & /*shape*/, const db::ICplxTrans & /*always_apply*/, const db::ICplxTrans & /*trans*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/) { }
};
} // namespace db

View File

@ -70,6 +70,27 @@ static db::DCplxTrans si_dtrans (const db::RecursiveShapeIterator *r)
return db::CplxTrans (ly->dbu ()) * r->trans () * db::VCplxTrans (1.0 / ly->dbu ());
}
static db::DCplxTrans si_global_dtrans (const db::RecursiveShapeIterator *r)
{
const db::Layout *ly = r->layout ();
tl_assert (ly != 0);
return db::CplxTrans (ly->dbu ()) * r->global_trans () * db::VCplxTrans (1.0 / ly->dbu ());
}
static db::DCplxTrans si_always_apply_dtrans (const db::RecursiveShapeIterator *r)
{
const db::Layout *ly = r->layout ();
tl_assert (ly != 0);
return db::CplxTrans (ly->dbu ()) * r->always_apply () * db::VCplxTrans (1.0 / ly->dbu ());
}
static void si_set_global_dtrans (db::RecursiveShapeIterator *r, const db::DCplxTrans &gt)
{
const db::Layout *ly = r->layout ();
tl_assert (ly != 0);
r->set_global_trans (db::VCplxTrans (1.0 / ly->dbu ()) * gt * db::CplxTrans (ly->dbu ()));
}
static void select_cells1 (db::RecursiveShapeIterator *r, const std::vector<db::cell_index_type> &cells)
{
std::set<db::cell_index_type> cc;
@ -270,7 +291,47 @@ Class<db::RecursiveShapeIterator> decl_RecursiveShapeIterator ("db", "RecursiveS
"\n"
"This method has been introduced in version 0.23.\n"
) +
gsi::method ("region", &db::RecursiveShapeIterator::region,
gsi::method ("global_trans=", &db::RecursiveShapeIterator::set_global_trans, gsi::arg ("t"),
"@brief Sets the global transformation to apply to all shapes delivered\n"
"The global transformation will be applied to all shapes delivered by biasing the \"trans\" attribute.\n"
"The search regions apply to the coordinate space after global transformation.\n"
"\n"
"This method has been introduced in version 0.27.\n"
) +
gsi::method ("global_trans", &db::RecursiveShapeIterator::global_trans,
"@brief Gets the global transformation to apply to all shapes delivered\n"
"See also \\global_trans=.\n"
"\n"
"This method has been introduced in version 0.27.\n"
) +
gsi::method_ext ("global_dtrans=", &si_set_global_dtrans,
"@brief Sets the global transformation to apply to all shapes delivered (transformation in micrometer units)\n"
"The global transformation will be applied to all shapes delivered by biasing the \"trans\" attribute.\n"
"The search regions apply to the coordinate space after global transformation.\n"
"\n"
"This method has been introduced in version 0.27.\n"
) +
gsi::method_ext ("global_dtrans", &si_global_dtrans,
"@brief Gets the global transformation to apply to all shapes delivered (in micrometer units)\n"
"See also \\global_dtrans=.\n"
"\n"
"This method has been introduced in version 0.27.\n"
) +
gsi::method ("always_apply_trans", &db::RecursiveShapeIterator::always_apply,
"@brief Gets the global transformation if at top level, unity otherwise\n"
"As the global transformation is only applicable on top level, use this method to transform shapes and instances into their local (cell-level) version "
"while considering the global transformation properly.\n"
"\n"
"This method has been introduced in version 0.27.\n"
) +
gsi::method_ext ("always_apply_dtrans", &si_always_apply_dtrans,
"@brief Gets the global transformation if at top level, unity otherwise (micrometer-unit version)\n"
"As the global transformation is only applicable on top level, use this method to transform shapes and instances into their local (cell-level) version "
"while considering the global transformation properly.\n"
"\n"
"This method has been introduced in version 0.27.\n"
) +
gsi::method ("region", &db::RecursiveShapeIterator::region,
"@brief Gets the basic region that is iterator is using\n"
"The basic region is the overall box the region iterator iterates over. "
"There may be an additional complex region that confines the region iterator. "

View File

@ -113,6 +113,19 @@ TEST(1)
x = collect_with_copy(i1, g);
EXPECT_EQ (x, "[$1](0,100;1000,1200)/[$2](0,100;1000,1200)/[$3](100,0;1100,1100)");
i1.set_global_trans (db::ICplxTrans (db::Trans (db::Vector (10, 20))));
i1.set_region (db::Box (10, 20, 110, 120));
x = collect(i1, g);
EXPECT_EQ (x, "[$1](10,120;1010,1220)/[$2](10,120;1010,1220)/[$3](110,20;1110,1120)");
x = collect_with_copy(i1, g);
EXPECT_EQ (x, "[$1](10,120;1010,1220)/[$2](10,120;1010,1220)/[$3](110,20;1110,1120)");
i1.reset ();
x = collect(i1, g);
EXPECT_EQ (x, "[$1](10,120;1010,1220)/[$2](10,120;1010,1220)/[$3](110,20;1110,1120)");
x = collect_with_copy(i1, g);
EXPECT_EQ (x, "[$1](10,120;1010,1220)/[$2](10,120;1010,1220)/[$3](110,20;1110,1120)");
db::RecursiveShapeIterator i1_1inf (g, c0, 0, db::Box (0, 0, 100, 100));
i1_1inf.min_depth(1);
x = collect(i1_1inf, g);
@ -731,7 +744,7 @@ namespace {
public:
FlatPusher (std::set<db::Box> *boxes) : mp_boxes (boxes) { }
void shape (const db::RecursiveShapeIterator * /*iter*/, const db::Shape &shape, const db::ICplxTrans &trans, const db::Box & /*region*/, const box_tree_type * /*complex_region*/)
void shape (const db::RecursiveShapeIterator * /*iter*/, const db::Shape &shape, const db::ICplxTrans & /*always_apply*/, const db::ICplxTrans &trans, const db::Box & /*region*/, const box_tree_type * /*complex_region*/)
{
mp_boxes->insert (trans * shape.bbox ());
}
@ -766,6 +779,8 @@ TEST(4)
}
// ...
db::Box search_box (2500, 2500, 7500, 7500);
std::set<db::Box> selected_boxes;
@ -794,6 +809,45 @@ TEST(4)
EXPECT_EQ (selected_boxes.size () > 100, true);
EXPECT_EQ (db::compare_layouts (boxes2layout (selected_boxes), boxes2layout (selected_boxes2), db::layout_diff::f_verbose, 0, 100 /*max diff lines*/), true);
// with global trans
selected_boxes.clear ();
selected_boxes2.clear ();
db::ICplxTrans ctr (db::Trans (db::Vector (10, 20)));
{
db::RecursiveShapeIterator iter (g, c0, 0, search_box, true);
iter.set_global_trans (ctr);
for ( ; !iter.at_end (); ++iter) {
selected_boxes.insert (iter->bbox ().transformed (iter.trans ()));
}
}
{
for (std::set<db::Box>::const_iterator b = boxes.begin (); b != boxes.end (); ++b) {
if (search_box.overlaps (b->transformed (ctr))) {
selected_boxes2.insert (b->transformed (ctr));
}
}
}
EXPECT_EQ (selected_boxes.size () > 100, true);
EXPECT_EQ (db::compare_layouts (boxes2layout (selected_boxes), boxes2layout (selected_boxes2), db::layout_diff::f_verbose, 0, 100 /*max diff lines*/), true);
// push mode
{
selected_boxes.clear ();
FlatPusher pusher (&selected_boxes);
db::RecursiveShapeIterator iter (g, c0, 0, search_box, true);
iter.set_global_trans (ctr);
iter.push (&pusher);
}
EXPECT_EQ (selected_boxes.size () > 100, true);
EXPECT_EQ (db::compare_layouts (boxes2layout (selected_boxes), boxes2layout (selected_boxes2), db::layout_diff::f_verbose, 0, 100 /*max diff lines*/), true);
// ...
db::Box search_box2 (500, 500, 1000, 1000);
selected_boxes.clear ();
@ -882,6 +936,40 @@ TEST(5)
EXPECT_EQ (selected_boxes.size () > 100, true);
EXPECT_EQ (db::compare_layouts (boxes2layout (selected_boxes), boxes2layout (selected_boxes2), db::layout_diff::f_verbose, 0, 100 /*max diff lines*/), true);
selected_boxes.clear ();
selected_boxes2.clear ();
db::ICplxTrans ctr (db::Trans (db::Vector (10, 20)));
{
db::RecursiveShapeIterator iter = db::RecursiveShapeIterator (g, c0, 0, search_box, true);
iter.set_global_trans (ctr);
for ( ; !iter.at_end (); ++iter) {
selected_boxes.insert (iter.trans () * iter->bbox ());
}
}
for (std::set<db::Box>::const_iterator b = boxes.begin (); b != boxes.end (); ++b) {
if (search_box.overlaps (b->transformed (ctr))) {
selected_boxes2.insert (b->transformed (ctr));
}
}
EXPECT_EQ (selected_boxes.size () > 100, true);
EXPECT_EQ (db::compare_layouts (boxes2layout (selected_boxes), boxes2layout (selected_boxes2), db::layout_diff::f_verbose, 0, 100 /*max diff lines*/), true);
// push mode
{
selected_boxes.clear ();
FlatPusher pusher (&selected_boxes);
db::RecursiveShapeIterator iter (g, c0, 0, search_box, true);
iter.set_global_trans (ctr);
iter.push (&pusher);
}
EXPECT_EQ (selected_boxes.size () > 100, true);
EXPECT_EQ (db::compare_layouts (boxes2layout (selected_boxes), boxes2layout (selected_boxes2), db::layout_diff::f_verbose, 0, 100 /*max diff lines*/), true);
db::Box search_box2 (500, 500, 1000, 1000);
selected_boxes.clear ();
@ -936,7 +1024,7 @@ public:
m_text += std::string ("leave_cell(") + iter->layout ()->cell_name (cell->cell_index ()) + ")\n";
}
virtual new_inst_mode new_inst (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool all)
virtual new_inst_mode new_inst (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans & /*always_apply*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool all)
{
m_text += std::string ("new_inst(") + iter->layout ()->cell_name (inst.object ().cell_index ());
if (all) {
@ -946,9 +1034,9 @@ public:
return NI_all;
}
virtual bool new_inst_member (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &trans, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool all)
virtual bool new_inst_member (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool all)
{
m_text += std::string ("new_inst_member(") + iter->layout ()->cell_name (inst.object ().cell_index ()) + "," + tl::to_string (trans);
m_text += std::string ("new_inst_member(") + iter->layout ()->cell_name (inst.object ().cell_index ()) + "," + tl::to_string (always_apply * trans);
if (all) {
m_text += ",all";
}
@ -956,7 +1044,7 @@ public:
return true;
}
virtual void shape (const db::RecursiveShapeIterator * /*iter*/, const db::Shape &shape, const db::ICplxTrans &trans, const db::Box & /*region*/, const box_tree_type * /*complex_region*/)
virtual void shape (const db::RecursiveShapeIterator * /*iter*/, const db::Shape &shape, const db::ICplxTrans & /*always_apply*/, const db::ICplxTrans &trans, const db::Box & /*region*/, const box_tree_type * /*complex_region*/)
{
m_text += "shape(" + shape.to_string () + "," + tl::to_string (trans) + ")\n";
}
@ -971,9 +1059,9 @@ class ReceiverRejectingACellInstanceArray
public:
ReceiverRejectingACellInstanceArray (db::cell_index_type rejected) : m_rejected (rejected) { }
virtual new_inst_mode new_inst (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::Box &region, const box_tree_type *complex_region, bool all)
virtual new_inst_mode new_inst (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::Box &region, const box_tree_type *complex_region, bool all)
{
LoggingReceiver::new_inst (iter, inst, region, complex_region, all);
LoggingReceiver::new_inst (iter, inst, always_apply, region, complex_region, all);
return inst.object ().cell_index () != m_rejected ? NI_all : NI_skip;
}
@ -987,9 +1075,9 @@ class ReceiverRejectingACellInstanceArrayExceptOne
public:
ReceiverRejectingACellInstanceArrayExceptOne (db::cell_index_type rejected) : m_rejected (rejected) { }
virtual new_inst_mode new_inst (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::Box &region, const box_tree_type *complex_region, bool all)
virtual new_inst_mode new_inst (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::Box &region, const box_tree_type *complex_region, bool all)
{
LoggingReceiver::new_inst (iter, inst, region, complex_region, all);
LoggingReceiver::new_inst (iter, inst, always_apply, region, complex_region, all);
return inst.object ().cell_index () != m_rejected ? NI_all : NI_single;
}
@ -1003,9 +1091,9 @@ class ReceiverRejectingACellInstance
public:
ReceiverRejectingACellInstance (db::cell_index_type rejected, const db::ICplxTrans &trans_rejected) : m_rejected (rejected), m_trans_rejected (trans_rejected) { }
virtual bool new_inst_member (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &trans, const db::Box &region, const box_tree_type *complex_region, bool all)
virtual bool new_inst_member (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box &region, const box_tree_type *complex_region, bool all)
{
LoggingReceiver::new_inst_member (iter, inst, trans, region, complex_region, all);
LoggingReceiver::new_inst_member (iter, inst, always_apply, trans, region, complex_region, all);
return inst.object ().cell_index () != m_rejected || trans != m_trans_rejected;
}
@ -1127,6 +1215,100 @@ TEST(10)
"end\n"
);
LoggingReceiver lr1_gt;
db::RecursiveShapeIterator i1_gt (g, c0, 0);
i1_gt.set_global_trans (db::ICplxTrans (db::Trans (db::Vector (10, 20))));
i1_gt.push (&lr1_gt);
EXPECT_EQ (lr1_gt.text (),
"begin\n"
"new_inst($2,all)\n"
"new_inst_member($2,r0 *1 10,20,all)\n"
// It's a bit weird to have shape events after new_inst_member, but remember, new_inst_member is a query callback, not an event.
"shape(box (0,0;1000,1000),r0 *1 10,20)\n"
"shape(box (-1000,0;0,1000),r0 *1 10,20)\n"
"enter_cell($2)\n"
"new_inst($3,all)\n"
"new_inst_member($3,r0 *1 0,0,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 10,20)\n"
"leave_cell($3)\n"
"new_inst_member($3,r0 *1 0,2000,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 10,2020)\n"
"leave_cell($3)\n"
"new_inst_member($3,r0 *1 3000,1000,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 3010,1020)\n"
"leave_cell($3)\n"
"new_inst_member($3,r0 *1 3000,3000,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 3010,3020)\n"
"leave_cell($3)\n"
"leave_cell($2)\n"
"new_inst_member($2,r0 *1 10,6020,all)\n"
"enter_cell($2)\n"
"new_inst($3,all)\n"
"new_inst_member($3,r0 *1 0,0,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 10,6020)\n"
"leave_cell($3)\n"
"new_inst_member($3,r0 *1 0,2000,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 10,8020)\n"
"leave_cell($3)\n"
"new_inst_member($3,r0 *1 3000,1000,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 3010,7020)\n"
"leave_cell($3)\n"
"new_inst_member($3,r0 *1 3000,3000,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 3010,9020)\n"
"leave_cell($3)\n"
"leave_cell($2)\n"
"new_inst_member($2,r0 *1 6010,20,all)\n"
"enter_cell($2)\n"
"new_inst($3,all)\n"
"new_inst_member($3,r0 *1 0,0,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 6010,20)\n"
"leave_cell($3)\n"
"new_inst_member($3,r0 *1 0,2000,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 6010,2020)\n"
"leave_cell($3)\n"
"new_inst_member($3,r0 *1 3000,1000,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 9010,1020)\n"
"leave_cell($3)\n"
"new_inst_member($3,r0 *1 3000,3000,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 9010,3020)\n"
"leave_cell($3)\n"
"leave_cell($2)\n"
"new_inst_member($2,r0 *1 6010,6020,all)\n"
"enter_cell($2)\n"
"new_inst($3,all)\n"
"new_inst_member($3,r0 *1 0,0,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 6010,6020)\n"
"leave_cell($3)\n"
"new_inst_member($3,r0 *1 0,2000,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 6010,8020)\n"
"leave_cell($3)\n"
"new_inst_member($3,r0 *1 3000,1000,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 9010,7020)\n"
"leave_cell($3)\n"
"new_inst_member($3,r0 *1 3000,3000,all)\n"
"enter_cell($3)\n"
"shape(box (1000,-500;2000,500),r0 *1 9010,9020)\n"
"leave_cell($3)\n"
"leave_cell($2)\n"
"end\n"
);
ReceiverRejectingACellInstanceArray rr1 (c2.cell_index ());
db::RecursiveShapeIterator ir1 (g, c0, 0);
ir1.push (&rr1);

View File

@ -63,6 +63,36 @@ module DRC
@in_context = nil
end
def shift(x, y)
self._context("shift") do
RBA::DCplxTrans::new(RBA::DVector::new(_make_value(x) * self.dbu, _make_value(y) * self.dbu))
end
end
def magnify(m)
self._context("magnify") do
RBA::DCplxTrans::new(_make_numeric_value(m))
end
end
def rotate(a)
self._context("rotate") do
RBA::DCplxTrans::new(1.0, _make_numeric_value(a), false, RBA::DVector::new)
end
end
def mirror_x
self._context("mirror_x") do
RBA::DCplxTrans::new(1.0, 0.0, true, RBA::DVector::new)
end
end
def mirror_y
self._context("mirror_y") do
RBA::DCplxTrans::new(1.0, 180.0, true, RBA::DVector::new)
end
end
def joined
DRCJoinFlag::new(true)
@ -1494,6 +1524,22 @@ CODE
nil
end
# %DRC%
# @name global_transform
# @brief Gets or sets a global transformation
# @synopsis global_transform
# @synopsis global_transform([ transformations ])
#
# Applies a global transformation to the default source layout.
# See \Source#global_transform for a description of this feature.
def global_transform(*args)
self._context("global_transform") do
@def_source = layout.global_transform(*args)
end
nil
end
# %DRC%
# @name cheat
# @brief Hierarchy cheats
@ -1679,8 +1725,6 @@ CODE
pull_overlapping
rectangles
rectilinear
rotate
rotated
rounded_corners
scale
scaled
@ -2440,7 +2484,7 @@ CODE
end
end
def _input(layout, cell_index, layers, sel, box, clip, overlapping, shape_flags, cls)
def _input(layout, cell_index, layers, sel, box, clip, overlapping, shape_flags, global_trans, cls)
if layers.empty? && ! @deep
@ -2454,6 +2498,7 @@ CODE
iter = RBA::RecursiveShapeIterator::new(layout, layout.cell(cell_index), layers)
end
iter.shape_flags = shape_flags
iter.global_dtrans = global_trans
sel.each do |s|
if s == "-"

View File

@ -27,6 +27,7 @@ module DRC
@clip = false
@overlapping = false
@tmp_layers = []
@global_trans = RBA::DCplxTrans::new
end
# Conceptual deep copy (not including the temp layers)
@ -70,6 +71,24 @@ module DRC
def cell_obj
@cell
end
def inplace_global_transform(*args)
gt = RBA::DCplxTrans::new
args.each do |a|
if a.is_a?(RBA::DVector) || a.is_a?(RBA::DTrans)
gt = RBA::DCplxTrans::new(a) * gt
elsif a.is_a?(RBA::DCplxTrans)
gt = a * gt
else
raise("Expected a transformation spec instead of #{a.inspect}")
end
end
@global_trans = gt
end
def global_transformation
@global_trans
end
def finish
@tmp_layers.each do |li|
@ -245,8 +264,40 @@ module DRC
# \touching is a similar method which delivers shapes touching
# the search region with their bounding box (without the requirement to overlap)
# %DRC%
# @name global_transform
# @brief Gets or sets a global transformation
# @synopsis global_transform
# @synopsis global_transform([ transformations ])
#
# This method returns a new source representing the transformed layout. It is provided in the spritit of
# \Source#clip and similar methods.
#
# The transformation
# is either given as a RBA::DTrans, RBA::DVector or RBA::DCplxTrans object or as one of the
# following specifications:
#
# @ul
# @li "shift(x, y)": shifts the input layout horizontally by x and vertically by y micrometers
# @li "rotate(a)": rotates the input layout by a degree counter-clockwise
# @li "magnify(m)": magnifies the input layout by the factor m (NOTE: using fractional scale factors may result in small gaps due to grid snapping)
# @li "mirror_x": mirrors the input layout at the x axis
# @li "mirror_y": mirrors the input layout at the y axis
# @/ul
#
# Multiple transformation specs can be given. In that case the transformations are applied right to left.
# Using "global_transform" will reset any global transformation present already.
# Without an argument, the global transformation is reset.
#
# The following example rotates the layout by 90 degree at the origin (0, 0) and then shifts it up by
# 100 micrometers:
#
# @code
# source.global_transform(shift(0, 100.um), rotate(90.0))
# @/code
# export inplace_* as * out-of-place
%w(select cell clip touching overlapping).each do |f|
%w(select cell clip touching overlapping global_transform).each do |f|
eval <<"CODE"
def #{f}(*args)
@engine._context("#{f}") do
@ -274,7 +325,7 @@ CODE
if @box
layer.insert(RBA::DBox::from_ibox(@box) * @layout.dbu)
else
layer.insert(RBA::DBox::from_ibox(@cell.bbox) * @layout.dbu)
layer.insert((RBA::DBox::from_ibox(@cell.bbox) * @layout.dbu).transformed(@global_trans))
end
layer
end
@ -326,7 +377,7 @@ CODE
def input(*args)
@engine._context("input") do
layers = parse_input_layers(*args)
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll, RBA::Region))
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll, @global_trans, RBA::Region))
end
end
@ -350,7 +401,7 @@ CODE
def labels(*args)
@engine._context("labels") do
layers = parse_input_layers(*args)
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::STexts, RBA::Texts))
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::STexts, @global_trans, RBA::Texts))
end
end
@ -373,7 +424,7 @@ CODE
def polygons(*args)
@engine._context("polygons") do
layers = parse_input_layers(*args)
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SBoxes | RBA::Shapes::SPaths | RBA::Shapes::SPolygons | RBA::Shapes::SEdgePairs, RBA::Region))
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SBoxes | RBA::Shapes::SPaths | RBA::Shapes::SPolygons | RBA::Shapes::SEdgePairs, @global_trans, RBA::Region))
end
end
@ -399,7 +450,7 @@ CODE
def edges(*args)
@engine._context("edges") do
layers = parse_input_layers(*args)
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SBoxes | RBA::Shapes::SPaths | RBA::Shapes::SPolygons | RBA::Shapes::SEdgePairs | RBA::Shapes::SEdges, RBA::Edges))
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SBoxes | RBA::Shapes::SPaths | RBA::Shapes::SPolygons | RBA::Shapes::SEdgePairs | RBA::Shapes::SEdges, @global_trans, RBA::Edges))
end
end
@ -425,7 +476,7 @@ CODE
def edge_pairs(*args)
@engine._context("edge_pairs") do
layers = parse_input_layers(*args)
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SEdgePairs, RBA::EdgePairs))
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SEdgePairs, @global_trans, RBA::EdgePairs))
end
end
@ -437,7 +488,7 @@ CODE
def make_layer
layers = []
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll, RBA::Region))
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll, @global_trans, RBA::Region))
end
# %DRC%

View File

@ -1168,3 +1168,23 @@ TEST(30_density)
run_test (_this, "30", false);
}
TEST(31_globaTransformation)
{
run_test (_this, "31", false);
}
TEST(31d_globalTransformation)
{
run_test (_this, "31", true);
}
TEST(32_globalTransformationWithClip)
{
run_test (_this, "32", false);
}
TEST(32d_globalTransformationWithClip)
{
run_test (_this, "32", true);
}

View File

@ -146,7 +146,7 @@ public:
m_cell_stack.pop_back ();
}
virtual new_inst_mode new_inst (const db::RecursiveShapeIterator * /*iter*/, const db::CellInstArray &inst, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/)
virtual new_inst_mode new_inst (const db::RecursiveShapeIterator * /*iter*/, const db::CellInstArray &inst, const db::ICplxTrans & /*always_apply*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/)
{
db::cell_index_type ci = inst.object ().cell_index ();
if (m_id_to_cell.find (ci) != m_id_to_cell.end ()) {
@ -156,7 +156,7 @@ public:
}
}
virtual void shape (const db::RecursiveShapeIterator * /*iter*/, const db::Shape &shape, const db::ICplxTrans & /*trans*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/)
virtual void shape (const db::RecursiveShapeIterator * /*iter*/, const db::Shape &shape, const db::ICplxTrans & /*always_apply*/, const db::ICplxTrans & /*trans*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/)
{
tl_assert (! m_cell_stack.empty ());
create_item_from_shape (mp_rdb, m_cell_stack.back ()->id (), mp_cat->id (), m_trans, shape);
@ -209,9 +209,9 @@ public:
}
}
virtual void shape (const db::RecursiveShapeIterator *iter, const db::Shape &shape, const db::ICplxTrans & /*trans*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/)
virtual void shape (const db::RecursiveShapeIterator * /*iter*/, const db::Shape &shape, const db::ICplxTrans & /*always_apply*/, const db::ICplxTrans &trans, const db::Box & /*region*/, const box_tree_type * /*complex_region*/)
{
create_item_from_shape (mp_rdb, mp_rdb_cell->id (), mp_cat->id (), m_trans * iter->trans (), shape);
create_item_from_shape (mp_rdb, mp_rdb_cell->id (), mp_cat->id (), m_trans * trans, shape);
}
public:

57
testdata/drc/drcSimpleTests_31.drc vendored Normal file
View File

@ -0,0 +1,57 @@
source $drc_test_source
target $drc_test_target
if $drc_test_deep
deep
end
def self_test(id, a, b)
a == b || raise(id + ": self-test failed (" + a.inspect + " != " + b.inspect + ")")
end
self_test("magnify(2.0)", source.global_transformation.to_s, "r0 *1 0,0")
global_transform(magnify(2.0))
self_test("magnify(2.0)", source.global_transformation.to_s, "r0 *2 0,0")
global_transform(magnify(2.0), rotate(90.0))
self_test("magnify(2.0)", source.global_transformation.to_s, "r90 *2 0,0")
global_transform(mirror_x, mirror_y)
self_test("magnify(2.0)", source.global_transformation.to_s, "r180 *1 0,0")
global_transform(magnify(2.0), shift(10.um, 20.um))
self_test("magnify(2.0)", source.global_transformation.to_s, "r0 *2 10,20")
# The actual DRC test
l1 = input(1, 0)
l2 = input(2, 0)
l1.output(1, 0)
l2.output(2, 0)
l1.merged.output(10, 0)
l1.sized(100.nm).output(11, 0)
l2.sized(100.nm).output(12, 0)
# reset
global_transform
l1 = input(1, 0)
l2 = input(2, 0)
l1.output(101, 0)
l2.output(102, 0)
l1.merged.output(110, 0)
l1.sized(100.nm).output(111, 0)
l2.sized(100.nm).output(112, 0)

BIN
testdata/drc/drcSimpleTests_31.gds vendored Normal file

Binary file not shown.

59
testdata/drc/drcSimpleTests_32.drc vendored Normal file
View File

@ -0,0 +1,59 @@
source $drc_test_source
target $drc_test_target
clip(0.um, 0.um, 26.um, 45.um)
if $drc_test_deep
deep
end
def self_test(id, a, b)
a == b || raise(id + ": self-test failed (" + a.inspect + " != " + b.inspect + ")")
end
self_test("magnify(2.0)", source.global_transformation.to_s, "r0 *1 0,0")
global_transform(magnify(2.0))
self_test("magnify(2.0)", source.global_transformation.to_s, "r0 *2 0,0")
global_transform(magnify(2.0), rotate(90.0))
self_test("magnify(2.0)", source.global_transformation.to_s, "r90 *2 0,0")
global_transform(mirror_x, mirror_y)
self_test("magnify(2.0)", source.global_transformation.to_s, "r180 *1 0,0")
global_transform(magnify(2.0), shift(10.um, 20.um))
self_test("magnify(2.0)", source.global_transformation.to_s, "r0 *2 10,20")
# The actual DRC test
l1 = input(1, 0)
l2 = input(2, 0)
l1.output(1, 0)
l2.output(2, 0)
l1.merged.output(10, 0)
l1.sized(100.nm).output(11, 0)
l2.sized(100.nm).output(12, 0)
# reset
global_transform
l1 = input(1, 0)
l2 = input(2, 0)
l1.output(101, 0)
l2.output(102, 0)
l1.merged.output(110, 0)
l1.sized(100.nm).output(111, 0)
l2.sized(100.nm).output(112, 0)

BIN
testdata/drc/drcSimpleTests_32.gds vendored Normal file

Binary file not shown.

BIN
testdata/drc/drcSimpleTests_au31.gds vendored Normal file

Binary file not shown.

BIN
testdata/drc/drcSimpleTests_au31d.gds vendored Normal file

Binary file not shown.

BIN
testdata/drc/drcSimpleTests_au32.gds vendored Normal file

Binary file not shown.

BIN
testdata/drc/drcSimpleTests_au32d.gds vendored Normal file

Binary file not shown.

View File

@ -63,6 +63,25 @@ class DBRecursiveShapeIterator_TestClass < TestBase
end
def acollect(s, l)
res = []
while !s.at_end?
r = "[#{l.cell_name(s.cell_index)}]"
if s.shape.is_box?
box = s.shape.box
r += box.transformed(s.always_apply_trans).to_s
else
r += "X";
end
s.next
res.push(r)
end
return res.join("/")
end
def test_1
# Recursive shape iterator tests
@ -197,6 +216,19 @@ class DBRecursiveShapeIterator_TestClass < TestBase
ii = RBA::RecursiveShapeIterator::new(l, c2, [0, 1], RBA::Box.new(-100, 0, 2000, 101), false)
assert_equal(collect(ii, l), "[c2](0,100;1000,1200)/[c3](1100,100;2100,1200)/[c3](1101,101;2101,1201)")
ii.reset
assert_equal(acollect(ii, l), "[c2](0,100;1000,1200)/[c3](0,100;1000,1200)/[c3](1,101;1001,1201)")
ii = RBA::RecursiveShapeIterator::new(l, c2, [0, 1], RBA::Box.new(-100, 20, 2000, 121), true)
ii.global_trans = RBA::ICplxTrans::new(RBA::Vector::new(10, 20))
assert_equal(ii.global_trans.to_s, "r0 *1 10,20")
assert_equal(collect(ii, l), "[c2](10,120;1010,1220)/[c3](1110,120;2110,1220)")
ii.global_dtrans = RBA::DCplxTrans::new(RBA::DVector::new(0.01, 0.02))
ii.reset
assert_equal(ii.global_dtrans.to_s, "r0 *1 0.01,0.02")
assert_equal(collect(ii, l), "[c2](10,120;1010,1220)/[c3](1110,120;2110,1220)")
ii.reset
assert_equal(acollect(ii, l), "[c2](10,120;1010,1220)/[c3](0,100;1000,1200)")
ii = RBA::RecursiveShapeIterator::new(l, c2, [0, 1], RBA::Box.new(-100, 0, 2000, 101), true)
assert_equal(collect(ii, l), "[c2](0,100;1000,1200)/[c3](1100,100;2100,1200)")