From 8e19474095ccf17ffb1c45465cdbd0c58bd2e004 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 9 Nov 2018 22:59:26 +0100 Subject: [PATCH] Introduced edge pairs as valid shapes for db::Shapes --- src/db/db/dbEdgePair.h | 20 +- src/db/db/dbEdgePairs.h | 10 +- src/db/db/dbEdges.cc | 1355 ---------------------------- src/db/db/dbEdges.h | 1232 ------------------------- src/db/db/dbFlatEdges.cc | 2 +- src/db/db/dbObjectWithProperties.h | 4 + src/db/db/dbShape.cc | 11 +- src/db/db/dbShape.h | 121 ++- src/db/db/dbShapeIterator.cc | 5 + src/db/db/dbShapes.cc | 32 + src/db/db/dbShapes.h | 46 +- src/db/db/dbShapes2.cc | 10 + src/db/db/dbShapes3.cc | 16 + src/db/unit_tests/dbShapes.cc | 46 + 14 files changed, 287 insertions(+), 2623 deletions(-) diff --git a/src/db/db/dbEdgePair.h b/src/db/db/dbEdgePair.h index b779a32f1..3ce03c3ef 100644 --- a/src/db/db/dbEdgePair.h +++ b/src/db/db/dbEdgePair.h @@ -53,7 +53,7 @@ public: typedef db::coord_traits coord_traits; typedef typename coord_traits::distance_type distance_type; typedef typename coord_traits::area_type area_type; - typedef db::object_tag< edge > tag; + typedef db::object_tag< edge_pair > tag; /** * @brief The default constructor. @@ -89,6 +89,24 @@ public: // .. nothing else .. } + /** + * @brief The (dummy) translation operator + */ + void translate (const edge_pair &d, db::generic_repository &, db::ArrayRepository &) + { + *this = d; + } + + /** + * @brief The (dummy) translation operator + */ + template + void translate (const edge_pair &d, const T &t, db::generic_repository &, db::ArrayRepository &) + { + *this = d; + transform (t); + } + /** * @brief A less operator to establish a sorting order. */ diff --git a/src/db/db/dbEdgePairs.h b/src/db/db/dbEdgePairs.h index 097617fef..c0f7498f4 100644 --- a/src/db/db/dbEdgePairs.h +++ b/src/db/db/dbEdgePairs.h @@ -219,7 +219,7 @@ public: } /** - * @brief Returns the transformed edge set + * @brief Returns the transformed edge pair set */ template EdgePairs transformed (const T &trans) const @@ -230,7 +230,7 @@ public: } /** - * @brief Swap with the other region + * @brief Swaps with the other edge pair set */ void swap (db::EdgePairs &other) { @@ -240,7 +240,7 @@ public: } /** - * @brief Convert to polygons + * @brief Converts to polygons * * Note: because of the include hierarchy we can't use a direct return value. * @@ -263,7 +263,7 @@ public: */ void edges (Edges &output) const; - /* + /** * @brief Returns the first edges * * Note: because of the include hierarchy we can't use a direct return value. @@ -273,7 +273,7 @@ public: */ void first_edges (Edges &output) const; - /* + /** * @brief Returns the second edges * * Note: because of the include hierarchy we can't use a direct return value. diff --git a/src/db/db/dbEdges.cc b/src/db/db/dbEdges.cc index 421612a05..e4c8abbd8 100644 --- a/src/db/db/dbEdges.cc +++ b/src/db/db/dbEdges.cc @@ -207,1358 +207,3 @@ namespace tl } } -#if 0 -// @@@@@@@@@@@@@@@@@@@@@@@@@@ ORIGINAL @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@qqq - -#include "dbEdges.h" -#include "dbEdgePairs.h" -#include "dbRegion.h" -#include "dbLayoutUtils.h" -#include "dbBoxConvert.h" -#include "dbBoxScanner.h" -#include "dbPolygonTools.h" -#include "dbShapeProcessor.h" - -#include "tlIntervalMap.h" -#include "tlVariant.h" - -#include - -namespace db -{ - -Edges::Edges (const RecursiveShapeIterator &si, bool as_edges) - : m_edges (false), m_merged_edges (false) -{ - init (); - if (! as_edges) { - m_iter = si; - } else { - for (RecursiveShapeIterator s = si; ! s.at_end (); ++s) { - insert (s.shape (), s.trans ()); - } - } - m_bbox_valid = false; - m_is_merged = false; -} - -Edges::Edges (const RecursiveShapeIterator &si, const db::ICplxTrans &trans, bool as_edges, bool merged_semantics) - : m_edges (false), m_merged_edges (false) -{ - init (); - if (! as_edges) { - m_iter = si; - m_iter_trans = trans; - } else { - for (RecursiveShapeIterator s = si; ! s.at_end (); ++s) { - insert (s.shape (), trans * s.trans ()); - } - } - m_bbox_valid = false; - m_is_merged = false; - m_merged_semantics = merged_semantics; -} - -bool -Edges::operator== (const db::Edges &other) const -{ - if (empty () != other.empty ()) { - return false; - } - if (size () != other.size ()) { - return false; - } - db::Edges::const_iterator o1 = begin (); - db::Edges::const_iterator o2 = other.begin (); - while (! o1.at_end () && ! o2.at_end ()) { - if (*o1 != *o2) { - return false; - } - ++o1; - ++o2; - } - return true; -} - -bool -Edges::operator< (const db::Edges &other) const -{ - if (empty () != other.empty ()) { - return empty () < other.empty (); - } - if (size () != other.size ()) { - return (size () < other.size ()); - } - db::Edges::const_iterator o1 = begin (); - db::Edges::const_iterator o2 = other.begin (); - while (! o1.at_end () && ! o2.at_end ()) { - if (*o1 != *o2) { - return *o1 < *o2; - } - ++o1; - ++o2; - } - return false; -} - -std::string -Edges::to_string (size_t nmax) const -{ - std::ostringstream os; - const_iterator e = begin (); - bool first = true; - for ( ; ! e.at_end () && nmax != 0; ++e, --nmax) { - if (! first) { - os << ";"; - } - first = false; - os << e->to_string (); - } - if (! e.at_end ()) { - os << "..."; - } - return os.str (); -} - -void -Edges::swap (Edges &other) -{ - std::swap (m_is_merged, other.m_is_merged); - std::swap (m_merged_semantics, other.m_merged_semantics); - m_edges.swap (other.m_edges); - m_merged_edges.swap (other.m_merged_edges); - std::swap (m_bbox, other.m_bbox); - std::swap (m_bbox_valid, other.m_bbox_valid); - std::swap (m_merged_edges_valid, other.m_merged_edges_valid); - std::swap (m_iter, other.m_iter); - std::swap (m_iter_trans, other.m_iter_trans); -} - -void -Edges::invalidate_cache () -{ - m_bbox_valid = false; - m_merged_edges.clear (); - m_merged_edges_valid = false; -} - -void -Edges::disable_progress () -{ - m_report_progress = false; -} - -void -Edges::enable_progress (const std::string &progress_desc) -{ - m_report_progress = true; - m_progress_desc = progress_desc; -} - -Edges::distance_type -Edges::length (const db::Box &box) const -{ - distance_type l = 0; - - for (const_iterator e = begin_merged (); ! e.at_end (); ++e) { - - if (box.empty () || (box.contains (e->p1 ()) && box.contains (e->p2 ()))) { - l += e->length (); - } else { - - std::pair ce = e->clipped (box); - if (ce.first) { - - db::Coord dx = ce.second.dx (); - db::Coord dy = ce.second.dy (); - db::Coord x = ce.second.p1 ().x (); - db::Coord y = ce.second.p1 ().y (); - if ((dx == 0 && x == box.left () && dy < 0) || - (dx == 0 && x == box.right () && dy > 0) || - (dy == 0 && y == box.top () && dx < 0) || - (dy == 0 && y == box.bottom () && dx > 0)) { - // not counted -> box is at outside side of the edge - } else { - l += ce.second.length (); - } - - } - - } - - } - - return l; -} - -Edges -Edges::start_segments (db::Edges::length_type length, double fraction) const -{ - Edges edges; - edges.reserve (m_edges.size ()); - - // zero-length edges would vanish in merged sematics, so we don't set it now - if (length == 0) { - edges.set_merged_semantics (false); - } - - for (const_iterator e = begin_merged (); ! e.at_end (); ++e) { - double l = std::max (e->double_length () * fraction, double (length)); - edges.insert (db::Edge (e->p1 (), db::Point (db::DPoint (e->p1 ()) + db::DVector (e->d()) * (l / e->double_length ())))); - } - - return edges; -} - -Edges -Edges::end_segments (db::Edges::length_type length, double fraction) const -{ - Edges edges; - edges.reserve (m_edges.size ()); - - // zero-length edges would vanish in merged sematics, so we don't set it now - if (length == 0) { - edges.set_merged_semantics (false); - } - - for (const_iterator e = begin_merged (); ! e.at_end (); ++e) { - double l = std::max (e->double_length () * fraction, double (length)); - edges.insert (db::Edge (db::Point (db::DPoint (e->p2 ()) - db::DVector (e->d()) * (l / e->double_length ())), e->p2 ())); - } - - return edges; -} - -Edges -Edges::centers (db::Edges::length_type length, double fraction) const -{ - Edges edges; - edges.reserve (m_edges.size ()); - - // zero-length edges would vanish in merged sematics, so we don't set it now - if (length == 0) { - edges.set_merged_semantics (false); - } - - for (const_iterator e = begin_merged (); ! e.at_end (); ++e) { - double l = std::max (e->double_length () * fraction, double (length)); - db::DVector dl = db::DVector (e->d()) * (0.5 * l / e->double_length ()); - db::DPoint center = db::DPoint (e->p1 ()) + db::DVector (e->p2 () - e->p1 ()) * 0.5; - edges.insert (db::Edge (db::Point (center - dl), db::Point (center + dl))); - } - - return edges; -} - -void -Edges::edge_region_op (const Region &other, bool outside, bool include_borders) -{ - // shortcuts - if (other.empty ()) { - if (! outside) { - clear (); - } - return; - } else if (empty ()) { - return; - } - - db::EdgeProcessor ep (m_report_progress, m_progress_desc); - - for (db::Region::const_iterator p = other.begin (); ! p.at_end (); ++p) { - if (p->box ().touches (bbox ())) { - ep.insert (*p, 0); - } - } - - for (const_iterator e = begin (); ! e.at_end (); ++e) { - ep.insert (*e, 1); - } - - invalidate_cache (); - - db::EdgeShapeGenerator cc (m_edges, true /*clear*/); - db::EdgePolygonOp op (outside, include_borders); - ep.process (cc, op); - - set_valid_edges (); - - m_is_merged = false; -} - -namespace -{ - -struct OrJoinOp -{ - void operator() (int &v, int n) - { - v += n; - } -}; - -struct AndJoinOp -{ - void operator() (int &v, int n) - { - if (n == 0) { - v = 0; - } - } -}; - -struct NotJoinOp -{ - void operator() (int &v, int n) - { - if (n != 0) { - v = 0; - } - } -}; - -struct XorJoinOp -{ - void operator() (int &v, int n) - { - if (n != 0) { - if (v == 0) { - v = (n > 0 ? 1 : -1); - } else { - v = 0; - } - } - } -}; - -struct EdgeBooleanCluster - : public db::cluster -{ - typedef db::Edge::coord_type coord_type; - - EdgeBooleanCluster (db::Edges *output, db::Edges::BoolOp op) - : mp_output (output), m_op (op) - { - // .. nothing yet .. - } - - void finish () - { - // determine base edge (longest overall edge) - - // shortcut for single edge - if (begin () + 1 == end ()) { - if (begin ()->second == 0) { - if (m_op != db::Edges::And) { - mp_output->insert (*(begin ()->first)); - } - } else { - if (m_op != db::Edges::And && m_op != db::Edges::Not) { - mp_output->insert (*(begin ()->first)); - } - } - return; - } - - db::Edge r = *begin ()->first; - double l1 = 0.0, l2 = r.double_length (); - double n = 1.0 / l2; - db::Point p1 = r.p1 (), p2 = r.p2 (); - - for (iterator o = begin () + 1; o != end (); ++o) { - double ll1 = db::sprod (db::Vector (o->first->p1 () - r.p1 ()), r.d ()) * n; - double ll2 = db::sprod (db::Vector (o->first->p2 () - r.p1 ()), r.d ()) * n; - if (ll1 < l1) { - p1 = o->first->p1 (); - l1 = ll1; - } - if (ll2 < l1) { - p1 = o->first->p2 (); - l1 = ll2; - } - if (ll1 > l2) { - p2 = o->first->p1 (); - l2 = ll1; - } - if (ll2 > l2) { - p2 = o->first->p2 (); - l2 = ll2; - } - } - - db::Vector d = db::Vector (p2 - p1); - n = 1.0 / d.double_length (); - - OrJoinOp or_jop; - AndJoinOp and_jop; - NotJoinOp not_jop; - XorJoinOp xor_jop; - - tl::interval_map a, b; - a.add (0, db::coord_traits::rounded (d.double_length ()), 0, or_jop); - b.add (0, db::coord_traits::rounded (d.double_length ()), 0, or_jop); - - for (iterator o = begin (); o != end (); ++o) { - db::Coord l1 = db::coord_traits::rounded (db::sprod (db::Vector (o->first->p1 () - p1), d) * n); - db::Coord l2 = db::coord_traits::rounded (db::sprod (db::Vector (o->first->p2 () - p1), d) * n); - if (o->second == 0 || m_op == db::Edges::Or) { - if (l1 < l2) { - a.add (l1, l2, 1, or_jop); - } else if (l1 > l2) { - a.add (l2, l1, -1, or_jop); - } - } else { - if (l1 < l2) { - b.add (l1, l2, 1, or_jop); - } else { - b.add (l2, l1, -1, or_jop); - } - } - } - - tl::interval_map q; - for (tl::interval_map::const_iterator ia = a.begin (); ia != a.end (); ++ia) { - q.add (ia->first.first, ia->first.second, ia->second > 0 ? 1 : (ia->second < 0 ? -1 : 0), or_jop); - } - - if (b.begin () == b.end ()) { - - // optimize for empty b - if (m_op != db::Edges::And) { - for (tl::interval_map::const_iterator ib = b.begin (); ib != b.end (); ++ib) { - if (ib->second > 0) { - mp_output->insert (db::Edge (p1 + db::Vector (d * (ib->first.first * n)), p1 + db::Vector (d * (ib->first.second * n)))); - } else if (ib->second < 0) { - mp_output->insert (db::Edge (p1 + db::Vector (d * (ib->first.second * n)), p1 + db::Vector (d * (ib->first.first * n)))); - } - } - } - - } else { - - if (m_op == db::Edges::And) { - for (tl::interval_map::const_iterator ib = b.begin (); ib != b.end (); ++ib) { - q.add (ib->first.first, ib->first.second, ib->second, and_jop); - } - } else if (m_op == db::Edges::Not) { - for (tl::interval_map::const_iterator ib = b.begin (); ib != b.end (); ++ib) { - q.add (ib->first.first, ib->first.second, ib->second, not_jop); - } - } else if (m_op == db::Edges::Xor) { - for (tl::interval_map::const_iterator ib = b.begin (); ib != b.end (); ++ib) { - q.add (ib->first.first, ib->first.second, ib->second, xor_jop); - } - } - - for (tl::interval_map::const_iterator iq = q.begin (); iq != q.end (); ++iq) { - if (iq->second > 0) { - mp_output->insert (db::Edge (p1 + db::Vector (d * (iq->first.first * n)), p1 + db::Vector (d * (iq->first.second * n)))); - } else if (iq->second < 0) { - mp_output->insert (db::Edge (p1 + db::Vector (d * (iq->first.second * n)), p1 + db::Vector (d * (iq->first.first * n)))); - } - } - - } - - } - -private: - db::Edges *mp_output; - db::Edges::BoolOp m_op; -}; - -struct EdgeBooleanClusterCollector - : public db::cluster_collector -{ - EdgeBooleanClusterCollector (db::Edges *output, Edges::BoolOp op) - : db::cluster_collector (EdgeBooleanCluster (output, op), op != Edges::And /*report single*/) - { - // .. nothing yet .. - } - - void add (const db::Edge *o1, size_t p1, const db::Edge *o2, size_t p2) - { - // Select edges which are: - // 1.) not degenerate - // 2.) parallel with some tolerance of roughly 1 dbu - // 3.) connected - if (! o1->is_degenerate () && ! o2->is_degenerate () - && fabs ((double) db::vprod (*o1, *o2)) < db::coord_traits::prec_distance () * std::min (o1->double_length (), o2->double_length ()) - && (o1->p1 () == o2->p1 () || o1->p1 () == o2->p2 () || o1->p2 () == o2->p1 () || o1->p2 () == o2->p2 () || o1->coincident (*o2))) { - db::cluster_collector::add (o1, p1, o2, p2); - } - } -}; - -} - -void -Edges::inplace_boolean (const Edges *other, Edges::BoolOp op) -{ - Edges out = boolean (other, op); - swap (out); -} - -Edges -Edges::boolean (const Edges *other, Edges::BoolOp op) const -{ - Edges output; - EdgeBooleanClusterCollector cluster_collector (&output, op); - - db::box_scanner scanner (m_report_progress, m_progress_desc); - scanner.reserve (m_edges.size () + (other ? other->size () : 0)); - - ensure_valid_edges (); - for (const_iterator e = begin (); ! e.at_end (); ++e) { - if (! e->is_degenerate ()) { - scanner.insert (&*e, 0); - } - } - if (other) { - other->ensure_valid_edges (); - for (const_iterator e = other->begin (); ! e.at_end (); ++e) { - if (! e->is_degenerate ()) { - scanner.insert (&*e, 1); - } - } - } - - scanner.process (cluster_collector, 1, db::box_convert ()); - - output.m_is_merged = true; - return output; -} - -void -Edges::ensure_valid_merged_edges () const -{ - // If no merged semantics applies or we will deliver the original - // edges as merged ones, we need to make sure those are valid - // ones (with a unique memory address) - if (! m_merged_semantics || m_is_merged) { - ensure_valid_edges (); - } else { - ensure_merged_edges_valid (); - } -} - -void -Edges::ensure_merged_edges_valid () const -{ - if (! m_merged_edges_valid) { - - m_merged_edges.clear (); - - Edges tmp; - EdgeBooleanClusterCollector cluster_collector (&tmp, Or); - - db::box_scanner scanner (m_report_progress, m_progress_desc); - scanner.reserve (m_edges.size ()); - - ensure_valid_edges (); - for (const_iterator e = begin (); ! e.at_end (); ++e) { - if (! e->is_degenerate ()) { - scanner.insert (&*e, 0); - } - } - - scanner.process (cluster_collector, 1, db::box_convert ()); - - m_merged_edges.swap (tmp.m_edges); - m_merged_edges_valid = true; - - } -} - -Edges & -Edges::operator+= (const Edges &other) -{ - invalidate_cache (); - - if (! has_valid_edges ()) { - - m_edges.clear (); - - size_t n = 0; - for (const_iterator e = begin (); ! e.at_end (); ++e) { - ++n; - } - for (const_iterator e = other.begin (); ! e.at_end (); ++e) { - ++n; - } - - m_edges.reserve (db::Edge::tag (), n); - - for (const_iterator e = begin (); ! e.at_end (); ++e) { - m_edges.insert (*e); - } - for (const_iterator e = other.begin (); ! e.at_end (); ++e) { - m_edges.insert (*e); - } - - set_valid_edges (); - - } else if (! other.has_valid_edges ()) { - - size_t n = m_edges.size (); - for (const_iterator e = other.begin (); ! e.at_end (); ++e) { - ++n; - } - - m_edges.reserve (db::Edge::tag (), n); - - for (const_iterator e = other.begin (); ! e.at_end (); ++e) { - m_edges.insert (*e); - } - - } else { - m_edges.insert (other.m_edges.get_layer ().begin (), other.m_edges.get_layer ().end ()); - } - - m_is_merged = false; - return *this; -} - -namespace -{ - -/** - * @brief A helper class for the edge to region interaction functionality which acts as an edge pair receiver - * - * Note: This special scanner uses pointers to two different objects: edges and polygons. - * It uses odd value pointers to indicate pointers to polygons and even value pointers to indicate - * pointers to edges. - * - * There is a special box converter which is able to sort that out as well. - */ -template -class edge_to_region_interaction_filter - : public db::box_scanner_receiver -{ -public: - edge_to_region_interaction_filter (OutputContainer &output) - : mp_output (&output) - { - // .. nothing yet .. - } - - void add (const char *o1, size_t p1, const char *o2, size_t p2) - { - const db::Edge *e = 0; - const db::Polygon *p = 0; - - // Note: edges have property 0 and have even-valued pointers. - // Polygons have property 1 and odd-valued pointers. - if (p1 == 0 && p2 == 1) { - e = reinterpret_cast (o1); - p = reinterpret_cast (o2 - 1); - } else if (p1 == 1 && p2 == 0) { - e = reinterpret_cast (o2); - p = reinterpret_cast (o1 - 1); - } - - if (e && p && m_seen.find (e) == m_seen.end ()) { - if (db::interact (*p, *e)) { - m_seen.insert (e); - mp_output->insert (*e); - } - } - } - -private: - OutputContainer *mp_output; - std::set m_seen; -}; - -/** - * @brief A special box converter that splits the pointers into polygon and edge pointers - */ -struct EdgeOrRegionBoxConverter -{ - typedef db::Box box_type; - - db::Box operator() (const char &c) const - { - // Note: edges have property 0 and have even-valued pointers. - // Polygons have property 1 and odd-valued pointers. - const char *cp = &c; - if ((size_t (cp) & 1) == 1) { - // it's a polygon - return (reinterpret_cast (cp - 1))->box (); - } else { - // it's an edge - const db::Edge *e = reinterpret_cast (cp); - return db::Box (e->p1 (), e->p2 ()); - } - } -}; - -} - -Edges & -Edges::select_interacting (const Region &other) -{ - // shortcuts - if (other.empty ()) { - clear (); - return *this; - } else if (empty ()) { - return *this; - } - - db::box_scanner scanner (m_report_progress, m_progress_desc); - scanner.reserve (size () + other.size ()); - - ensure_valid_merged_edges (); - for (const_iterator e = begin_merged (); ! e.at_end (); ++e) { - scanner.insert ((char *) &*e, 0); - } - - AddressablePolygonDelivery p = other.addressable_polygons (); - - for ( ; ! p.at_end (); ++p) { - // NOTE: dirty hack - we can take the address of the polygon because we used ensure_flat_polygons. - scanner.insert ((char *) p.operator-> () + 1, 1); - } - - Edges output; - edge_to_region_interaction_filter filter (output); - EdgeOrRegionBoxConverter bc; - scanner.process (filter, 1, bc); - - swap (output); - return *this; -} - -Edges & -Edges::select_not_interacting (const Region &other) -{ - // shortcuts - if (other.empty () || empty ()) { - return *this; - } - - db::box_scanner scanner (m_report_progress, m_progress_desc); - scanner.reserve (size () + other.size ()); - - ensure_valid_merged_edges (); - for (const_iterator e = begin_merged (); ! e.at_end (); ++e) { - scanner.insert ((char *) &*e, 0); - } - - AddressablePolygonDelivery p = other.addressable_polygons (); - for ( ; ! p.at_end (); ++p) { - // NOTE: dirty hack - we can take the address of the polygon because we used ensure_flat_polygons. - scanner.insert ((char *) p.operator-> () + 1, 1); - } - - std::set interacting; - edge_to_region_interaction_filter > filter (interacting); - EdgeOrRegionBoxConverter bc; - scanner.process (filter, 1, bc); - - Edges output; - for (const_iterator o = begin_merged (); ! o.at_end (); ++o) { - if (interacting.find (*o) == interacting.end ()) { - output.insert (*o); - } - } - - swap (output); - return *this; -} - -namespace -{ - -/** - * @brief A helper class for the edge interaction functionality which acts as an edge pair receiver - */ -template -class edge_interaction_filter - : public db::box_scanner_receiver -{ -public: - edge_interaction_filter (OutputContainer &output) - : mp_output (&output) - { - // .. nothing yet .. - } - - void add (const db::Edge *o1, size_t p1, const db::Edge *o2, size_t p2) - { - // Select the edges which intersect - if (p1 != p2) { - const db::Edge *o = p1 > p2 ? o2 : o1; - const db::Edge *oo = p1 > p2 ? o1 : o2; - if (o->intersect (*oo)) { - if (m_seen.insert (o).second) { - mp_output->insert (*o); - } - } - } - } - -private: - OutputContainer *mp_output; - std::set m_seen; -}; - -} - -Edges & -Edges::select_interacting (const Edges &other) -{ - db::box_scanner scanner (m_report_progress, m_progress_desc); - scanner.reserve (size () + other.size ()); - - ensure_valid_merged_edges (); - for (const_iterator e = begin_merged (); ! e.at_end (); ++e) { - scanner.insert (&*e, 0); - } - other.ensure_valid_edges (); - for (const_iterator e = other.begin (); ! e.at_end (); ++e) { - scanner.insert (&*e, 1); - } - - Edges output; - edge_interaction_filter filter (output); - scanner.process (filter, 1, db::box_convert ()); - - swap (output); - return *this; -} - -Edges & -Edges::select_not_interacting (const Edges &other) -{ - db::box_scanner scanner (m_report_progress, m_progress_desc); - scanner.reserve (size () + other.size ()); - - ensure_valid_merged_edges (); - for (const_iterator e = begin_merged (); ! e.at_end (); ++e) { - scanner.insert (&*e, 0); - } - other.ensure_valid_edges (); - for (const_iterator e = other.begin (); ! e.at_end (); ++e) { - scanner.insert (&*e, 1); - } - - std::set interacting; - edge_interaction_filter > filter (interacting); - scanner.process (filter, 1, db::box_convert ()); - - Edges output; - for (const_iterator o = begin_merged (); ! o.at_end (); ++o) { - if (interacting.find (*o) == interacting.end ()) { - output.insert (*o); - } - } - - swap (output); - return *this; -} - -namespace -{ - -struct JoinEdgesCluster - : public db::cluster -{ - typedef db::Edge::coord_type coord_type; - - JoinEdgesCluster (db::Region *output, coord_type ext_b, coord_type ext_e, coord_type ext_o, coord_type ext_i) - : mp_output (output), m_ext_b (ext_b), m_ext_e (ext_e), m_ext_o (ext_o), m_ext_i (ext_i) - { - // .. nothing yet .. - } - - void finish () - { - std::multimap objects_by_p1; - std::multimap objects_by_p2; - for (iterator o = begin (); o != end (); ++o) { - if (o->first->p1 () != o->first->p2 ()) { - objects_by_p1.insert (std::make_pair (o->first->p1 (), o)); - objects_by_p2.insert (std::make_pair (o->first->p2 (), o)); - } - } - - while (! objects_by_p2.empty ()) { - - tl_assert (! objects_by_p1.empty ()); - - // Find the beginning of a new sequence - std::multimap::iterator j0 = objects_by_p1.begin (); - std::multimap::iterator j = j0; - do { - std::multimap::iterator jj = objects_by_p2.find (j->first); - if (jj == objects_by_p2.end ()) { - break; - } else { - j = objects_by_p1.find (jj->second->first->p1 ()); - tl_assert (j != objects_by_p1.end ()); - } - } while (j != j0); - - iterator i = j->second; - - // determine a sequence - // TODO: this chooses any solution in case of forks. Choose a specific one? - std::vector pts; - pts.push_back (i->first->p1 ()); - - do { - - // record the next point - pts.push_back (i->first->p2 ()); - - // remove the edge as it's taken - std::multimap::iterator jj; - for (jj = objects_by_p2.find (i->first->p2 ()); jj != objects_by_p2.end () && jj->first == i->first->p2 (); ++jj) { - if (jj->second == i) { - break; - } - } - tl_assert (jj != objects_by_p2.end () && jj->second == i); - objects_by_p2.erase (jj); - objects_by_p1.erase (j); - - // process along the edge to the next one - // TODO: this chooses any solution in case of forks. Choose a specific one? - j = objects_by_p1.find (i->first->p2 ()); - if (j != objects_by_p1.end ()) { - i = j->second; - } else { - break; - } - - } while (true); - - bool cyclic = (pts.back () == pts.front ()); - - if (! cyclic) { - - // non-cyclic sequence - db::Path path (pts.begin (), pts.end (), 0, m_ext_b, m_ext_e, false); - std::vector hull; - path.hull (hull, m_ext_o, m_ext_i); - db::Polygon poly; - poly.assign_hull (hull.begin (), hull.end ()); - mp_output->insert (poly); - - } else { - - // we have a loop: form a contour by using the polygon size functions and a "Not" to form the hole - db::Polygon poly; - poly.assign_hull (pts.begin (), pts.end ()); - - db::EdgeProcessor ep; - db::RegionPolygonSink ps (*mp_output, false); - db::PolygonGenerator pg (ps, false, true); - - int mode_a = -1, mode_b = -1; - - if (m_ext_o == 0) { - ep.insert (poly, 0); - } else { - db::Polygon sized_poly (poly); - sized_poly.size (m_ext_o, m_ext_o, 2 /*sizing mode*/); - ep.insert (sized_poly, 0); - mode_a = 1; - } - - if (m_ext_i == 0) { - ep.insert (poly, 1); - } else { - db::Polygon sized_poly (poly); - sized_poly.size (-m_ext_i, -m_ext_i, 2 /*sizing mode*/); - ep.insert (sized_poly, 1); - mode_b = 1; - } - - db::BooleanOp2 op (db::BooleanOp::ANotB, mode_a, mode_b); - ep.process (pg, op); - - } - - } - } - -private: - db::Region *mp_output; - coord_type m_ext_b, m_ext_e, m_ext_o, m_ext_i; -}; - -struct JoinEdgesClusterCollector - : public db::cluster_collector -{ - typedef db::Edge::coord_type coord_type; - - JoinEdgesClusterCollector (db::Region *output, coord_type ext_b, coord_type ext_e, coord_type ext_o, coord_type ext_i) - : db::cluster_collector (JoinEdgesCluster (output, ext_b, ext_e, ext_o, ext_i), true) - { - // .. nothing yet .. - } - - void add (const db::Edge *o1, size_t p1, const db::Edge *o2, size_t p2) - { - if (o1->p2 () == o2->p1 () || o1->p1 () == o2->p2 ()) { - db::cluster_collector::add (o1, p1, o2, p2); - } - } -}; - -} - -void -Edges::extended (Region &output, coord_type ext_b, coord_type ext_e, coord_type ext_o, coord_type ext_i, bool join) const -{ - if (join) { - - JoinEdgesClusterCollector cluster_collector (&output, ext_b, ext_e, ext_o, ext_i); - - db::box_scanner scanner (m_report_progress, m_progress_desc); - scanner.reserve (size ()); - - ensure_valid_edges (); - size_t n = 0; - for (const_iterator e = begin (); ! e.at_end (); ++e) { - scanner.insert (&*e, n); - ++n; - } - - scanner.process (cluster_collector, 1, db::box_convert ()); - - } else { - - for (const_iterator e = begin_merged (); ! e.at_end (); ++e) { - - db::DVector d; - if (e->is_degenerate ()) { - d = db::DVector (1.0, 0.0); - } else { - d = db::DVector (e->d ()) * (1.0 / e->double_length ()); - } - - db::DVector n (-d.y (), d.x ()); - - db::Point pts[4] = { - db::Point (db::DPoint (e->p1 ()) - d * double (ext_b) + n * double (ext_o)), - db::Point (db::DPoint (e->p2 ()) + d * double (ext_e) + n * double (ext_o)), - db::Point (db::DPoint (e->p2 ()) + d * double (ext_e) - n * double (ext_i)), - db::Point (db::DPoint (e->p1 ()) - d * double (ext_b) - n * double (ext_i)), - }; - - db::Polygon poly; - poly.assign_hull (pts + 0, pts + 4); - output.insert (poly); - - } - - } -} - -Edges -Edges::in (const Edges &other, bool invert) const -{ - std::set op; - for (const_iterator o = other.begin_merged (); ! o.at_end (); ++o) { - op.insert (*o); - } - - Edges r; - for (const_iterator o = begin_merged (); ! o.at_end (); ++o) { - if ((op.find (*o) == op.end ()) == invert) { - r.insert (*o); - } - } - - return r; -} - -void -Edges::init () -{ - m_report_progress = false; - m_bbox_valid = true; - m_is_merged = true; - m_merged_semantics = true; - m_merged_edges_valid = false; -} - -void -Edges::ensure_bbox_valid () const -{ - if (! m_bbox_valid) { - m_bbox = db::Box (); - for (const_iterator e = begin (); ! e.at_end (); ++e) { - m_bbox += db::Box (e->p1 (), e->p2 ()); - } - m_bbox_valid = true; - } -} - -Edges::const_iterator -Edges::begin_merged () const -{ - if (! m_merged_semantics || m_is_merged) { - return begin (); - } else { - ensure_merged_edges_valid (); - return db::EdgesIterator (m_merged_edges.get_layer ().begin (), m_merged_edges.get_layer ().end ()); - } -} - -std::pair -Edges::begin_iter () const -{ - if (has_valid_edges ()) { - return std::make_pair (db::RecursiveShapeIterator (m_edges), db::ICplxTrans ()); - } else { - return std::make_pair (m_iter, m_iter_trans); - } -} - -std::pair -Edges::begin_merged_iter () const -{ - if (! m_merged_semantics || m_is_merged) { - return begin_iter (); - } else { - ensure_merged_edges_valid (); - return std::make_pair (db::RecursiveShapeIterator (m_merged_edges), db::ICplxTrans ()); - } -} - -void -Edges::insert (const db::Edge &edge) -{ - ensure_valid_edges (); - m_edges.insert (edge); - m_is_merged = false; - invalidate_cache (); -} - -void -Edges::insert (const db::Box &box) -{ - if (! box.empty ()) { - ensure_valid_edges (); - m_edges.insert (db::Edge (box.lower_left (), box.upper_left ())); - m_edges.insert (db::Edge (box.upper_left (), box.upper_right ())); - m_edges.insert (db::Edge (box.upper_right (), box.lower_right ())); - m_edges.insert (db::Edge (box.lower_right (), box.lower_left ())); - m_is_merged = false; - invalidate_cache (); - } -} - -void -Edges::insert (const db::Path &path) -{ - if (path.points () > 0) { - insert (path.polygon ()); - } -} - -void -Edges::insert (const db::Polygon &polygon) -{ - ensure_valid_edges (); - for (db::Polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { - m_edges.insert (*e); - } - m_is_merged = false; - invalidate_cache (); -} - -void -Edges::insert (const db::SimplePolygon &polygon) -{ - ensure_valid_edges (); - for (db::SimplePolygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { - m_edges.insert (*e); - } - m_is_merged = false; - invalidate_cache (); -} - -void -Edges::clear () -{ - m_edges.clear (); - m_bbox = db::Box (); - m_bbox_valid = true; - m_is_merged = true; - m_merged_edges.clear (); - m_merged_edges_valid = true; - m_iter = db::RecursiveShapeIterator (); - m_iter_trans = db::ICplxTrans (); -} - -namespace -{ - -/** - * @brief A helper class for the DRC functionality which acts as an edge pair receiver - * - * If will perform a edge by edge check using the provided EdgeRelationFilter - */ -class Edge2EdgeCheck - : public db::box_scanner_receiver -{ -public: - Edge2EdgeCheck (const EdgeRelationFilter &check, EdgePairs &output, bool requires_different_layers) - : mp_check (&check), mp_output (&output) - { - m_requires_different_layers = requires_different_layers; - } - - void add (const db::Edge *o1, size_t p1, const db::Edge *o2, size_t p2) - { - // Overlap or inside checks require input from different layers - if (! m_requires_different_layers || ((p1 ^ p2) & 1) != 0) { - - // ensure that the first check argument is of layer 1 and the second of - // layer 2 (unless both are of the same layer) - int l1 = int (p1 & size_t (1)); - int l2 = int (p2 & size_t (1)); - - db::EdgePair ep; - if (mp_check->check (l1 <= l2 ? *o1 : *o2, l1 <= l2 ? *o2 : *o1, &ep)) { - mp_output->insert (ep); - } - - } - } - -private: - const EdgeRelationFilter *mp_check; - EdgePairs *mp_output; - bool m_requires_different_layers; -}; - -} - -EdgePairs -Edges::run_check (db::edge_relation_type rel, const Edges *other, db::Coord d, bool whole_edges, metrics_type metrics, double ignore_angle, distance_type min_projection, distance_type max_projection) const -{ - EdgePairs result; - - db::box_scanner scanner (m_report_progress, m_progress_desc); - scanner.reserve (size () + (other ? other->size () : 0)); - - ensure_valid_merged_edges (); - size_t n = 0; - for (const_iterator e = begin_merged (); ! e.at_end (); ++e) { - scanner.insert (&*e, n); - n += 2; - } - - if (other) { - other->ensure_valid_merged_edges (); - n = 1; - for (const_iterator e = other->begin_merged (); ! e.at_end (); ++e) { - scanner.insert (&*e, n); - n += 2; - } - } - - EdgeRelationFilter check (rel, d, metrics); - check.set_include_zero (other != 0); - check.set_whole_edges (whole_edges); - check.set_ignore_angle (ignore_angle); - check.set_min_projection (min_projection); - check.set_max_projection (max_projection); - - Edge2EdgeCheck edge_check (check, result, other != 0); - scanner.process (edge_check, d, db::box_convert ()); - - return result; -} - -size_t -Edges::size () const -{ - if (! has_valid_edges ()) { - // If we have an iterator, we have to do it the hard way .. - size_t n = 0; - for (const_iterator e = begin (); ! e.at_end (); ++e) { - ++n; - } - return n; - } else { - return m_edges.size (); - } -} - -void -Edges::set_merged_semantics (bool f) -{ - if (f != m_merged_semantics) { - m_merged_semantics = f; - m_merged_edges.clear (); - m_merged_edges_valid = false; - } -} - -void -Edges::set_valid_edges () -{ - m_iter = db::RecursiveShapeIterator (); -} - -void -Edges::ensure_valid_edges () const -{ - if (! has_valid_edges ()) { - - m_edges.clear (); - - size_t n = 0; - for (const_iterator e = begin (); ! e.at_end (); ++e) { - ++n; - } - m_edges.reserve (db::Edge::tag (), n); - - for (const_iterator e = begin (); ! e.at_end (); ++e) { - m_edges.insert (*e); - } - - m_iter = db::RecursiveShapeIterator (); - - } -} - -} // namespace db - -namespace tl -{ - template<> DB_PUBLIC bool test_extractor_impl (tl::Extractor &ex, db::Edges &b) - { - db::Edge e; - - if (! ex.try_read (e)) { - return false; - } - b.insert (e); - - while (ex.test (";")) { - ex.read (e); - b.insert (e); - } - - return true; - } - - template<> DB_PUBLIC void extractor_impl (tl::Extractor &ex, db::Edges &b) - { - if (! test_extractor_impl (ex, b)) { - ex.error (tl::to_string (tr ("Expected an edge collection specification"))); - } - } -} - -#endif - diff --git a/src/db/db/dbEdges.h b/src/db/db/dbEdges.h index e264b732a..1236b5e05 100644 --- a/src/db/db/dbEdges.h +++ b/src/db/db/dbEdges.h @@ -1281,1235 +1281,3 @@ namespace tl } #endif - -#if 0 -// @@@@@@@@@@@@@@@@@@@@@@@@@@ ORIGINAL @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@qqq - -#ifndef HDR_dbEdges -#define HDR_dbEdges - -#include "dbCommon.h" - -#include "dbTypes.h" -#include "dbEdge.h" -#include "dbTrans.h" -#include "dbShape.h" -#include "dbShapes.h" -#include "dbShapes2.h" -#include "dbEdgePairRelations.h" -#include "dbEdgePairs.h" -#include "dbRecursiveShapeIterator.h" -#include "tlString.h" - -namespace db { - -class Edges; - -/** - * @brief A base class for polygon filters - */ -class DB_PUBLIC EdgeFilterBase -{ -public: - EdgeFilterBase () { } - virtual ~EdgeFilterBase () { } - - virtual bool selected (const db::Edge &edge) const = 0; -}; - -/** - * @brief An edge length filter for use with Edges::filter or Edges::filtered - * - * This filter has two parameters: lmin and lmax. - * It will filter all edges for which the length is >= lmin and < lmax. - * There is an "invert" flag which allows to select all edges not - * matching the criterion. - */ - -struct DB_PUBLIC EdgeLengthFilter - : public EdgeFilterBase -{ - typedef db::Edge::distance_type length_type; - - /** - * @brief Constructor - * - * @param lmin The minimum length - * @param lmax The maximum length - * @param inverse If set to true, only polygons not matching this criterion will be filtered - */ - EdgeLengthFilter (length_type lmin, length_type lmax, bool inverse) - : m_lmin (lmin), m_lmax (lmax), m_inverse (inverse) - { - // .. nothing yet .. - } - - /** - * @brief Returns true if the edge length matches the criterion - */ - bool selected (const db::Edge &edge) const - { - length_type l = edge.length (); - if (! m_inverse) { - return l >= m_lmin && l < m_lmax; - } else { - return ! (l >= m_lmin && l < m_lmax); - } - } - -private: - length_type m_lmin, m_lmax; - bool m_inverse; -}; - -/** - * @brief An edge orientation filter for use with Edges::filter or Edges::filtered - * - * This filter has two parameters: amin and amax. - * It will filter all edges for which the orientation angle is >= amin and < amax. - * The orientation angle is measured in degree against the x axis in the mathematical sense. - * There is an "invert" flag which allows to select all edges not - * matching the criterion. - */ - -struct DB_PUBLIC EdgeOrientationFilter - : public EdgeFilterBase -{ - /** - * @brief Constructor - * - * @param amin The minimum angle (measured against the x axis) - * @param amax The maximum angle (measured against the x axis) - * @param inverse If set to true, only polygons not matching this criterion will be filtered - * - * This filter will filter out all edges whose angle against x axis - * is larger or equal to amin and less than amax. - */ - EdgeOrientationFilter (double amin, double amax, bool inverse) - : m_inverse (inverse), m_exact (false) - { - m_emin = db::DVector (cos (amin * M_PI / 180.0), sin (amin * M_PI / 180.0)); - m_emax = db::DVector (cos (amax * M_PI / 180.0), sin (amax * M_PI / 180.0)); - } - - /** - * @brief Constructor - * - * @param a The angle (measured against the x axis) - * @param inverse If set to true, only polygons not matching this criterion will be filtered - * - * This filter will filter out all edges whose angle against x axis - * is equal to a. - */ - EdgeOrientationFilter (double a, bool inverse) - : m_inverse (inverse), m_exact (true) - { - m_emin = db::DVector (cos (a * M_PI / 180.0), sin (a * M_PI / 180.0)); - } - - /** - * @brief Returns true if the edge orientation matches the criterion - */ - bool selected (const db::Edge &edge) const - { - int smin = db::vprod_sign (m_emin, db::DVector (edge.d ())); - if (m_exact) { - if (! m_inverse) { - return smin == 0; - } else { - return smin != 0; - } - } else { - int smax = db::vprod_sign (m_emax, db::DVector (edge.d ())); - if (! m_inverse) { - return (smin >= 0 && smax < 0) || (smax > 0 && smin <= 0); - } else { - return ! ((smin >= 0 && smax < 0) || (smax > 0 && smin <= 0)); - } - } - } - -private: - db::DVector m_emin, m_emax; - bool m_inverse; - bool m_exact; -}; - -/** - * @brief A edge collection iterator - * - * The iterator delivers the edges of the edge collection - */ - -class DB_PUBLIC EdgesIterator -{ -public: - typedef db::Edge value_type; - typedef const db::Edge &reference; - typedef const db::Edge *pointer; - typedef std::forward_iterator_tag iterator_category; - typedef void difference_type; - - /** - * @Returns true, if the iterator is at the end - */ - bool at_end () const - { - return m_from == m_to && m_rec_iter.at_end (); - } - - /** - * @brief Increment - */ - EdgesIterator &operator++ () - { - inc (); - set (); - return *this; - } - - /** - * @brief Access - */ - reference operator* () const - { - if (m_rec_iter.at_end ()) { - return *m_from; - } else { - return m_edge; - } - } - - /** - * @brief Access - */ - pointer operator-> () const - { - if (m_rec_iter.at_end ()) { - return &*m_from; - } else { - return &m_edge; - } - } - -private: - friend class Edges; - - typedef db::layer edge_layer_type; - typedef edge_layer_type::iterator iterator_type; - - db::RecursiveShapeIterator m_rec_iter; - db::ICplxTrans m_iter_trans; - db::Edge m_edge; - iterator_type m_from, m_to; - - /** - * @brief ctor from a recursive shape iterator - */ - EdgesIterator (const db::RecursiveShapeIterator &iter, const db::ICplxTrans &trans) - : m_rec_iter (iter), m_iter_trans (trans), m_from (), m_to () - { - // NOTE: the following initialization appears to be required on some compilers - // (specifically MacOS/clang) to ensure the proper initialization of the iterators - m_from = m_to; - set (); - } - - /** - * @brief ctor from a range of edges inside a vector - */ - EdgesIterator (iterator_type from, iterator_type to) - : m_from (from), m_to (to) - { - // no required yet: set (); - } - - /** - * @brief Establish the iterator at the current position - */ - void set () - { - while (! m_rec_iter.at_end () && ! m_rec_iter.shape ().is_edge ()) { - inc (); - } - if (! m_rec_iter.at_end ()) { - m_rec_iter.shape ().edge (m_edge); - m_edge.transform (m_iter_trans * m_rec_iter.trans ()); - } - } - - /** - * @brief Increment the iterator - */ - void inc () - { - if (! m_rec_iter.at_end ()) { - ++m_rec_iter; - } else { - ++m_from; - } - } -}; - -/** - * @brief An edge set - * - * An edge set is basically a collection of edges. They do not necessarily need to form closed contours. - * Edges can be manipulated in various ways. Edge sets closely cooperate with the Region class which is a - * set of polygons. - * - * Edge sets have some methods in common with regions. Edge sets can also be merged, which means that - * edges which are continuations of other edges are joined. - * - * Edge sets can contain degenerated edges. Such edges are some which have identical start and end points. - * Such edges are basically points which have some applications, i.e. as markers for certain locations. - */ - -class DB_PUBLIC Edges -{ -public: - typedef db::Coord coord_type; - typedef db::coord_traits coord_traits; - typedef db::Edge edge_type; - typedef db::Vector vector_type; - typedef db::Point point_type; - typedef db::Box box_type; - typedef coord_traits::distance_type distance_type; - typedef db::Edge::distance_type length_type; - typedef EdgesIterator const_iterator; - enum BoolOp { Or, Not, Xor, And }; - - /** - * @brief Default constructor - * - * This constructor creates an empty edge set. - */ - Edges () - : m_edges (false), m_merged_edges (false) - { - init (); - } - - /** - * @brief Constructor from an object - * - * Creates a region representing a single instance of that object. - * The object is converted to a polygon and the edges of that polygon are inserted. - */ - template - Edges (const Sh &s) - : m_edges (false), m_merged_edges (false) - { - init (); - insert (s); - } - - /** - * @brief Sequence constructor - * - * Creates a region from a sequence of objects. The objects can be edges, boxes, - * polygons, paths or shapes. This version accepts iterators of the begin ... end - * style. - */ - template - Edges (const Iter &b, const Iter &e) - : m_edges (false), m_merged_edges (false) - { - init (); - reserve (e - b); - for (Iter i = b; i != e; ++i) { - insert (*i); - } - } - - /** - * @brief Constructor from a RecursiveShapeIterator - * - * Creates a region from a recursive shape iterator. This allows to feed an edge set - * from a hierarchy of cells. - * - * If as_edges is false, only edges will be taken from the recursive shape iterator. - * That is somewhat more efficient since it can avoid a copy in some cases. If as_edges - * is false, shapes will be converted to edges. - */ - Edges (const RecursiveShapeIterator &si, bool as_edges = true); - - /** - * @brief Constructor from a RecursiveShapeIterator with a transformation - * - * Creates a region from a recursive shape iterator. This allows to feed an edge set - * from a hierarchy of cells. The transformation is useful to scale to a specific - * DBU for example. - * - * If as_edges is true, only edges will be taken from the recursive shape iterator. - * That is somewhat more efficient since it can avoid a copy in some cases. If as_edges - * is false, shapes will be converted to edges. - */ - Edges (const RecursiveShapeIterator &si, const db::ICplxTrans &trans, bool as_edges = true, bool merged_semantics = true); - - /** - * @brief Enable progress reporting - * - * @param progress_text The description text of the progress object - */ - void enable_progress (const std::string &progress_desc = std::string ()); - - /** - * @brief Disable progress reporting - */ - void disable_progress (); - - /** - * @brief Iterator of the edge set - * - * The iterator delivers the edges of the edge set. - * It follows the at_end semantics. - */ - const_iterator begin () const - { - if (has_valid_edges ()) { - return const_iterator (m_edges.get_layer ().begin (), m_edges.get_layer ().end ()); - } else { - return const_iterator (m_iter, m_iter_trans); - } - } - - /** - * @brief Returns the merged edges if merge semantics applies - * - * If merge semantics is not enabled, this iterator delivers the individual edges. - */ - const_iterator begin_merged () const; - - /** - * @brief Delivers a RecursiveShapeIterator pointing to the edges plus the necessary transformation - */ - std::pair begin_iter () const; - - /** - * @brief Delivers a RecursiveShapeIterator pointing to the merged edges plus the necessary transformation - */ - std::pair begin_merged_iter () const; - - /** - * @brief Insert an edge into the edge set - */ - void insert (const db::Edge &edge); - - /** - * @brief Insert a box into the edge set - * - * This method will insert all edges the box is composed of. - */ - void insert (const db::Box &box); - - /** - * @brief Insert a path into the edge set - * - * This method will insert all edges the path is composed of. - */ - void insert (const db::Path &path); - - /** - * @brief Insert a simple polygon into the edge set - * - * This method will insert all edges the polygon is composed of. - */ - void insert (const db::SimplePolygon &polygon); - - /** - * @brief Insert a polygon into the edge set - * - * This method will insert all edges the polygon is composed of. - */ - void insert (const db::Polygon &polygon); - - /** - * @brief Insert a shape into the region - * - * If the shape is a polygon-type, the shape is converted to a - * polygon and it's edges are inserted into the edge set. - * If the shape is an edge, the edge is inserted into the edge set. - */ - void insert (const db::Shape &shape) - { - if (shape.is_edge ()) { - insert (shape.edge ()); - } else if (shape.is_polygon () || shape.is_path () || shape.is_box ()) { - db::Polygon polygon; - shape.polygon (polygon); - for (db::Polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { - insert (*e); - } - } - } - - /** - * @brief Insert a transformed shape into the edge set - */ - template - void insert (const db::Shape &shape, const T &trans) - { - if (shape.is_edge ()) { - insert (edge_type (trans * shape.edge ())); - } else if (shape.is_polygon () || shape.is_path () || shape.is_box ()) { - db::Polygon polygon; - shape.polygon (polygon); - for (db::Polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { - insert (edge_type (trans * *e)); - } - } - } - - /** - * @brief Returns true if the region is empty - */ - bool empty () const - { - return has_valid_edges () && m_edges.empty (); - } - - /** - * @brief Returns the number of polygons in the region - */ - size_t size () const; - - /** - * @brief Returns a string representing the region - * - * nmax specifies how many polygons are included (set to std::numeric_limits::max() for "all". - */ - std::string to_string (size_t nmax = 10) const; - - /** - * @brief Clear the edge set - */ - void clear (); - - /** - * @brief Reserve memory for the given number of edges - */ - void reserve (size_t n) - { - m_edges.reserve (db::Edge::tag (), n); - } - - /** - * @brief Sets the merged-semantics flag - * - * If merged semantics is enabled (the default), coherent polygons will be considered - * as single regions and artificial edges such as cut-lines will not be considered. - * Merged semantics thus is equivalent to considering coherent areas rather than - * single polygons. - */ - void set_merged_semantics (bool f); - - /** - * @brief Gets the merged-semantics flag - */ - bool merged_semantics () const - { - return m_merged_semantics; - } - - /** - * @brief Returns true if the region is merged - */ - bool is_merged () const - { - return m_is_merged; - } - - /** - * @brief Returns the total length of the edges - * Merged semantics applies. In merged semantics, the length is the correct total length of the edges. - * Without merged semantics, overlapping parts are counted twice. - * - * If a box is given, the computation is restricted to that box. - * Edges coincident with the box edges are counted only if the form outer edges at the box edge. - */ - length_type length (const db::Box &box = db::Box ()) const; - - /** - * @brief Returns the bounding box of the region - */ - Box bbox () const - { - ensure_bbox_valid (); - return m_bbox; - } - - /** - * @brief Filters the edge set - * - * This method will keep all edges for which the filter returns true. - * Merged semantics applies. - */ - Edges &filter (EdgeFilterBase &filter) - { - edge_iterator_type ew = m_edges.get_layer ().begin (); - for (const_iterator e = begin_merged (); ! e.at_end (); ++e) { - if (filter.selected (*e)) { - if (ew == m_edges.get_layer ().end ()) { - m_edges.get_layer ().insert (*e); - ew = m_edges.get_layer ().end (); - } else { - m_edges.get_layer ().replace (ew++, *e); - } - } - } - m_edges.get_layer ().erase (ew, m_edges.get_layer ().end ()); - m_merged_edges.clear (); - m_is_merged = m_merged_semantics; - m_iter = db::RecursiveShapeIterator (); - return *this; - } - - /** - * @brief Returns the filtered edges - * - * This method will return a new region with only those edges which - * conform to the filter criterion. - * Merged semantics applies. - */ - Edges filtered (const EdgeFilterBase &filter) const - { - Edges d; - for (const_iterator e = begin_merged (); ! e.at_end (); ++e) { - if (filter.selected (*e)) { - d.insert (*e); - } - } - return d; - } - - /** - * @brief Returns all edges found in the other edge collection as well - * - * If "invert" is true, all edges not found in the other collection - * are returned. - */ - Edges in (const Edges &other, bool invert) const; - - /** - * @brief Transform the edge set - */ - template - Edges &transform (const T &trans) - { - if (! trans.is_unity ()) { - ensure_valid_edges (); - for (edge_iterator_type e = m_edges.get_layer ().begin (); e != m_edges.get_layer ().end (); ++e) { - m_edges.get_layer ().replace (e, e->transformed (trans)); - } - m_iter_trans = db::ICplxTrans (trans) * m_iter_trans; - m_bbox_valid = false; - } - return *this; - } - - /** - * @brief Returns the transformed edge set - */ - template - Edges transformed (const T &trans) const - { - Edges d (*this); - d.transform (trans); - return d; - } - - /** - * @brief Swap with the other region - */ - void swap (db::Edges &other); - - /** - * @brief returns the extended edges - * - * Edges are extended by creating a rectangle on each edge. The rectangle is constructed on the - * edge by applying the extensions given by ext_o, ext_b, ext_e, ext_i at the outside, the - * beginning, the end and the inside. - * If the edge is laid flat pointing from left to right, the outside is at the top, the inside - * is at the bottom. - * - * For degenerated edges with length 0, the orientation is assumed to the horizontal. The extended - * method creates a rectangle with specified dimensions: ext_b to the left, ext_o to the top, ext_i to the bottom - * and ext_e to the right. - * - * If the joined parameter is set to true, adjacent edges are joined before the extension is applied. - * A the join points, the extension is created similar to what the sizing function does. - * - * Note: the output is given as an out parameter since because of the include hierarchy we can't use - * Region as a return value directly. - * - * Merged semantics applies. - */ - void extended (Region &output, coord_type ext_b, coord_type ext_e, coord_type ext_o, coord_type ext_i, bool join = false) const; - - /** - * @brief Returns edges (point-like) representing the start part of the edges - * - * The length of the part can be choosen by length or a fraction of the original length. - * If length and fraction are 0, a point at the beginning of the edge will be created. - * If length is non-zero and fraction is 0, a segment in the direction of the edge - * with the given length is created, even if the length is larger than the original - * edge. - * - * If fraction is given and length is 0, the segment will have a length which is the specified - * fraction of the original edge. - * If both values are given, the resulting edge will have a length which is the maximum of - * both the fixed length and the length derived from the fraction. - * - * Length and fraction can be negative in which case the resulting segment will point - * in the opposite direction of the original edge. - * - * Merged semantics applies. - */ - Edges start_segments (length_type length, double fraction) const; - - /** - * @brief Returns edges (point-like) representing the end part of the edges - * - * This method behaves similar to \start_segments but creates segments at the end of - * the edges. - * - * Merged semantics applies. - */ - Edges end_segments (length_type length, double fraction) const; - - /** - * @brief Returns edges (point-like) representing the center of the edges - * - * This method behaves similar to \start_segments but creates segments at the centers of - * the edges. - * - * Merged semantics applies. - */ - Edges centers (length_type length, double fraction) const; - - /** - * @brief Boolean AND operator - * - * This operation returns the parts of the edges which coincide with edges from "other" - * After this operation the edges are not necessarily merged. - */ - Edges operator& (const Edges &other) const - { - return boolean (&other, And); - } - - /** - * @brief In-place boolean AND operator - */ - Edges &operator&= (const Edges &other) - { - inplace_boolean (&other, And); - return *this; - } - - /** - * @brief Boolean AND operator with a region - * - * This operation returns the parts of the edges which are inside the given region. - * Edges on the borders of the polygons are included in the edge set. - * As a side effect, the edges are made non-intersecting by introducing cut points where - * edges intersect. - */ - Edges operator& (const Region &other) const - { - Edges d (*this); - d &= other; - return d; - } - - /** - * @brief In-place boolean AND operator with a region - */ - Edges &operator&= (const Region &other) - { - edge_region_op (other, false /*inside*/, true /*include borders*/); - return *this; - } - - /** - * @brief Boolean NOT operator - * - * This operation returns the parts of the edges which do not coincide with edges from "other" - * After this operation the edges are not necessarily merged. - */ - Edges operator- (const Edges &other) const - { - return boolean (&other, Not); - } - - /** - * @brief In-place boolean NOT operator - */ - Edges &operator-= (const Edges &other) - { - inplace_boolean (&other, Not); - return *this; - } - - /** - * @brief Boolean NOT operator with a region - * - * This operation returns the parts of the edges which are outside the given region. - * Edges on the borders of the polygons are removed from the edge set. - * As a side effect, the edges are made non-intersecting by introducing cut points where - * edges intersect. - */ - Edges operator- (const Region &other) const - { - Edges d (*this); - d -= other; - return d; - } - - /** - * @brief In-place boolean NOT operator with a region - */ - Edges &operator-= (const Region &other) - { - edge_region_op (other, true /*outside*/, true /*include borders*/); - return *this; - } - - /** - * @brief Boolean XOR operator - * - * This operation returns the parts of the edges which do not coincide with edges from "other" - * and vice versa. - * After this operation the edges are not necessarily merged. - */ - Edges operator^ (const Edges &other) const - { - return boolean (&other, Xor); - } - - /** - * @brief In-place boolean XOR operator - */ - Edges &operator^= (const Edges &other) - { - inplace_boolean (&other, Xor); - return *this; - } - - /** - * @brief Joining of edge sets - * - * This method will combine the edges from "other" with the egdes of "this". - * After this operation the edges are not necessarily merged. - */ - Edges operator+ (const Edges &other) const - { - Edges d (*this); - d += other; - return d; - } - - /** - * @brief In-place joining of edge sets - */ - Edges &operator+= (const Edges &other); - - /** - * @brief Boolean OR operator - * - * This method will combine the edges from "other" with the egdes of "this". - * After this operation the edges are usually merged. - */ - Edges operator| (const Edges &other) const - { - return boolean (&other, Or); - } - - /** - * @brief In-place boolean OR operator - */ - Edges &operator|= (const Edges &other) - { - inplace_boolean (&other, Or); - return *this; - } - - /** - * @brief Select the edges inside the given region - * - * This method will select the edges inside the given region. - * Edges on the border of the region won't be selected. - * As a side effect, the edges are made non-intersecting by introducing cut points where - * edges intersect. - */ - Edges &select_inside_part (const Region &other) - { - edge_region_op (other, false /*inside*/, false /*don't include borders*/); - return *this; - } - - /** - * @brief Returns the edges inside the given region - * - * This is an out-of-place version of "select_inside_part". - */ - Edges inside_part (const Region &other) const - { - Edges d (*this); - d.select_inside_part (other); - return d; - } - - /** - * @brief Select the edge parts outside of the given region - * - * This method will select the edge parts outside of the given region. - * Edges on the border of the region won't be selected. - * As a side effect, the edges are made non-intersecting by introducing cut points where - * edges intersect. - */ - Edges &select_outside_part (const Region &other) - { - edge_region_op (other, true /*outside*/, false /*don't include borders*/); - return *this; - } - - /** - * @brief Returns the edge parts outside of the given region - * - * This is an out-of-place version of "select_outside_part". - */ - Edges outside_part (const Region &other) const - { - Edges d (*this); - d.select_outside_part (other); - return d; - } - - /** - * @brief Selects all edges of this edge set which overlap or touch with polygons from the region - * - * Merged semantics applies. If merged semantics is chosen, the connected edge parts will be - * selected as a whole. - */ - Edges &select_interacting (const Region &other); - - /** - * @brief Returns all edges of this edge set which overlap or touch with polygons from the region - * - * This method is an out-of-place version of select_interacting. - */ - Edges selected_interacting (const Region &other) const - { - Edges d (*this); - d.select_interacting (other); - return d; - } - - /** - * @brief Selects all edges of this edge set which do not overlap or touch with polygons from the region - * - * Merged semantics applies. If merged semantics is chosen, the connected edge parts will be - * selected as a whole. - */ - Edges &select_not_interacting (const Region &other); - - /** - * @brief Returns all edges of this edge set which do not overlap or touch with polygons from the region - * - * This method is an out-of-place version of select_not_interacting. - */ - Edges selected_not_interacting (const Region &other) const - { - Edges d (*this); - d.select_not_interacting (other); - return d; - } - - /** - * @brief Selects all edges of this edge set which overlap or touch with edges from the other edge set - * - * Merged semantics applies. If merged semantics is chosen, the connected edge parts will be - * selected as a whole. - */ - Edges &select_interacting (const Edges &other); - - /** - * @brief Returns all edges of this edge set which overlap or touch with edges from the other edge set - * - * This method is an out-of-place version of select_interacting. - */ - Edges selected_interacting (const Edges &other) const - { - Edges d (*this); - d.select_interacting (other); - return d; - } - - /** - * @brief Selects all edges of this edge set which do not overlap or touch with edges from the other edge set - * - * Merged semantics applies. If merged semantics is chosen, the connected edge parts will be - * selected as a whole. - */ - Edges &select_not_interacting (const Edges &other); - - /** - * @brief Returns all edges of this edge set which do not overlap or touch with edges from the other edge set - * - * This method is an out-of-place version of select_not_interacting. - */ - Edges selected_not_interacting (const Edges &other) const - { - Edges d (*this); - d.select_not_interacting (other); - return d; - } - - /** - * @brief Merge the edge set - * - * This method merges the edges of the edge set if they are not merged already. - * It returns a reference to this edge set. - * Edges are merged by joining them if one edge is a continuation of another. - * An out-of-place merge version is "merged". - */ - Edges &merge () - { - if (! is_merged ()) { - inplace_boolean (0, Or); - } - return *this; - } - - /* - * @brief Returns the merged edge set - * - * This is the out-of-place merge. It returns a new edge set but does not modify - * the edge set it is called on. An in-place version is "merge". - */ - Edges merged () const - { - return boolean (0, Or); - } - - /** - * @brief Applies a width check and returns EdgePairs which correspond to violation markers - * - * The width check will create a edge pairs if the width of the area between the - * edges is less than the specified threshold d. Without "whole_edges", the parts of - * the edges are returned which violate the condition. If "whole_edges" is true, the - * result will contain the complete edges participating in the result. - * - * "Width" refers to the space between the "inside" sides of the edges. - * - * The metrics parameter specifies which metrics to use. "Euclidian", "Square" and "Projected" - * metrics are available. - * - * ignore_angle allows specification of a maximum angle the edges can have to not participate - * in the check. By choosing 90 degree, edges having an angle of 90 degree and larger are not checked, - * but acute corners are for example. - * - * With min_projection and max_projection it is possible to specify how edges must be related - * to each other. If the length of the projection of either edge on the other is >= min_projection - * or < max_projection, the edges are considered for the check. - * - * The order of the edges in the resulting edge pairs is undefined. - * - * Merged semantics applies. - */ - EdgePairs width_check (db::Coord d, bool whole_edges = false, metrics_type metrics = db::Euclidian, double ignore_angle = 90, distance_type min_projection = 0, distance_type max_projection = std::numeric_limits::max ()) const - { - return run_check (db::WidthRelation, 0, d, whole_edges, metrics, ignore_angle, min_projection, max_projection); - } - - /** - * @brief Applies a space check and returns EdgePairs which correspond to violation markers - * - * "Space" refers to the space between the "outside" sides of the edges. - * - * For the parameters see \width_check. The space check reports edges for which the space is - * less than the specified threshold d. - * - * Merged semantics applies. - */ - EdgePairs space_check (db::Coord d, bool whole_edges = false, metrics_type metrics = db::Euclidian, double ignore_angle = 90, distance_type min_projection = 0, distance_type max_projection = std::numeric_limits::max ()) const - { - return run_check (db::SpaceRelation, 0, d, whole_edges, metrics, ignore_angle, min_projection, max_projection); - } - - /** - * @brief Applies an enclosing check and returns EdgePairs which correspond to violation markers - * - * The check will return true for edges from this edge set and the other edge set, where the other edge - * is located on the "inside" side of the edge from this edge set, the orientation is parallel - * and the distance is less than the specified threshold d. - * - * The first edges of the edge pairs will be the ones from "this", the second edges will be those of "other". - * - * For the other parameters see \width_check. - * - * Merged semantics applies. - */ - EdgePairs enclosing_check (const Edges &other, db::Coord d, bool whole_edges = false, metrics_type metrics = db::Euclidian, double ignore_angle = 90, distance_type min_projection = 0, distance_type max_projection = std::numeric_limits::max ()) const - { - return run_check (db::OverlapRelation, &other, d, whole_edges, metrics, ignore_angle, min_projection, max_projection); - } - - /** - * @brief Applies an overlap check and returns EdgePairs which correspond to violation markers - * - * The check will return true for edges from this edge set and the other edge set, where the other edge - * is located on the "inside" side of the edge from this edge set, the orientation is anti-parallel - * and the distance is less than the specified threshold d. - * - * The first edges of the edge pairs will be the ones from "this", the second edges will be those of "other". - * - * For the other parameters see \width_check. - * - * Merged semantics applies. - */ - EdgePairs overlap_check (const Edges &other, db::Coord d, bool whole_edges = false, metrics_type metrics = db::Euclidian, double ignore_angle = 90, distance_type min_projection = 0, distance_type max_projection = std::numeric_limits::max ()) const - { - return run_check (db::WidthRelation, &other, d, whole_edges, metrics, ignore_angle, min_projection, max_projection); - } - - /** - * @brief Applies an separation check and returns EdgePairs which correspond to violation markers - * - * The check will return true for edges from this edge set and the other edge set, where the other edge - * is located on the "outside" side of the edge from this edge set, the orientation is anti-parallel - * and the distance is less than the specified threshold d. - * - * The first edges of the edge pairs will be the ones from "this", the second edges will be those of "other". - * - * For the other parameters see \width_check. - * - * Merged semantics applies. - */ - EdgePairs separation_check (const Edges &other, db::Coord d, bool whole_edges = false, metrics_type metrics = db::Euclidian, double ignore_angle = 90, distance_type min_projection = 0, distance_type max_projection = std::numeric_limits::max ()) const - { - return run_check (db::SpaceRelation, &other, d, whole_edges, metrics, ignore_angle, min_projection, max_projection); - } - - /** - * @brief Applies a inside check and returns EdgePairs which correspond to violation markers - * - * The check will return true for edges from this edge set and the other edge set, where the other edge - * is located on the "outide" side of the edge from this edge set, the orientation is parallel - * and the distance is less than the specified threshold d. - * - * The first edges of the edge pairs will be the ones from "this", the second edges will be those of "other". - * - * For the other parameters see \width_check. - * - * Merged semantics applies. - */ - EdgePairs inside_check (const Edges &other, db::Coord d, bool whole_edges = false, metrics_type metrics = db::Euclidian, double ignore_angle = 90, distance_type min_projection = 0, distance_type max_projection = std::numeric_limits::max ()) const - { - return run_check (db::InsideRelation, &other, d, whole_edges, metrics, ignore_angle, min_projection, max_projection); - } - - /** - * @brief Returns the nth edge - * - * This method will force the edges to be inside the edge vector and will invalidate any iterator. - * If that happens, the method may be costly. - * The iterator should be used whenever possible. - */ - const db::Edge *nth (size_t n) const - { - ensure_valid_edges (); - return n < m_edges.size () ? &m_edges.get_layer ().begin () [n] : 0; - } - - /** - * @brief Returns true, if the edge set has valid edges stored within itself - */ - bool has_valid_edges () const - { - return m_iter.at_end (); - } - - /** - * @brief Ensures the edge collection has valid edges - * - * This method is const since it has const semantics. - */ - void ensure_valid_edges () const; - - /** - * @brief Ensures the edge collection has valid merged edges - * - * It will make sure that begin_merged will deliver an - * iterator to an edge with a unique memory location. - */ - void ensure_valid_merged_edges () const; - - /** - * @brief Equality - */ - bool operator== (const db::Edges &other) const; - - /** - * @brief Inequality - */ - bool operator!= (const db::Edges &other) const - { - return !operator== (other); - } - - /** - * @brief Less operator - */ - bool operator< (const db::Edges &other) const; - -private: - typedef db::layer edge_layer_type; - typedef edge_layer_type::iterator edge_iterator_type; - - bool m_is_merged; - bool m_merged_semantics; - mutable db::Shapes m_edges; - mutable db::Shapes m_merged_edges; - mutable db::Box m_bbox; - mutable bool m_bbox_valid; - mutable bool m_merged_edges_valid; - mutable db::RecursiveShapeIterator m_iter; - db::ICplxTrans m_iter_trans; - bool m_report_progress; - std::string m_progress_desc; - - void init (); - void invalidate_cache (); - void set_valid_edges (); - void ensure_bbox_valid () const; - void ensure_merged_edges_valid () const; - EdgePairs run_check (db::edge_relation_type rel, const Edges *other, db::Coord d, bool whole_edges, metrics_type metrics, double ignore_angle, distance_type min_projection, distance_type max_projection) const; - void inplace_boolean (const Edges *other, BoolOp op); - Edges boolean (const Edges *other, BoolOp op) const; - void edge_region_op (const Region &other, bool outside, bool include_borders); -}; - -} // namespace db - -namespace tl -{ - /** - * @brief The type traits for the edges type - */ - template <> - struct type_traits : public type_traits - { - typedef true_tag supports_extractor; - typedef true_tag supports_to_string; - typedef true_tag has_less_operator; - typedef true_tag has_equal_operator; - }; - -} - -#endif - -#endif diff --git a/src/db/db/dbFlatEdges.cc b/src/db/db/dbFlatEdges.cc index 2807f1b57..4c33b6575 100644 --- a/src/db/db/dbFlatEdges.cc +++ b/src/db/db/dbFlatEdges.cc @@ -96,7 +96,7 @@ void FlatEdges::merged_semantics_changed () void FlatEdges::reserve (size_t n) { - m_edges.reserve (db::Polygon::tag (), n); + m_edges.reserve (db::Edge::tag (), n); } void diff --git a/src/db/db/dbObjectWithProperties.h b/src/db/db/dbObjectWithProperties.h index 0a52dfef0..d8afb6818 100644 --- a/src/db/db/dbObjectWithProperties.h +++ b/src/db/db/dbObjectWithProperties.h @@ -29,6 +29,7 @@ #include "dbPolygon.h" #include "dbPath.h" #include "dbEdge.h" +#include "dbEdgePair.h" #include "dbText.h" #include "dbBox.h" #include "dbArray.h" @@ -196,6 +197,9 @@ typedef object_with_properties DPathRefWithProperties; typedef object_with_properties EdgeWithProperties; typedef object_with_properties DEdgeWithProperties; +typedef object_with_properties EdgePairWithProperties; +typedef object_with_properties DEdgePairWithProperties; + typedef object_with_properties TextWithProperties; typedef object_with_properties DTextWithProperties; typedef object_with_properties TextRefWithProperties; diff --git a/src/db/db/dbShape.cc b/src/db/db/dbShape.cc index baaafbdcb..f09365a80 100644 --- a/src/db/db/dbShape.cc +++ b/src/db/db/dbShape.cc @@ -52,6 +52,8 @@ db::properties_id_type Shape::prop_id () const return (**((psimple_polygon_ptr_array_iter_type *) m_generic.iter)).properties_id (); case Edge: return (**((pedge_iter_type *) m_generic.iter)).properties_id (); + case EdgePair: + return (**((pedge_pair_iter_type *) m_generic.iter)).properties_id (); case Path: return (**((ppath_iter_type *) m_generic.iter)).properties_id (); case PathRef: @@ -99,6 +101,8 @@ db::properties_id_type Shape::prop_id () const return m_generic.psimple_polygon_aref->properties_id (); case Edge: return m_generic.pedge->properties_id (); + case EdgePair: + return m_generic.pedge_pair->properties_id (); case Path: return m_generic.ppath->properties_id (); case PathRef: @@ -701,6 +705,8 @@ Shape::box_type Shape::bbox () const return basic_ptr (text_ptr_array_type::tag ())->bbox (db::box_convert ()); case Edge: return box_type (edge ().p1 (), edge ().p2 ()); + case EdgePair: + return edge_pair ().bbox (); case Path: return path ().box (); case PathRef: @@ -772,6 +778,9 @@ Shape::to_string () const case Edge: r = "edge " + edge ().to_string (); break; + case EdgePair: + r = "edge_pair " + edge_pair ().to_string (); + break; case Path: case PathRef: case PathPtrArrayMember: @@ -810,6 +819,4 @@ Shape::to_string () const return r; } - } - diff --git a/src/db/db/dbShape.h b/src/db/db/dbShape.h index 63446c641..76220df8b 100644 --- a/src/db/db/dbShape.h +++ b/src/db/db/dbShape.h @@ -32,6 +32,7 @@ #include "dbPolygon.h" #include "dbPath.h" #include "dbEdge.h" +#include "dbEdgePair.h" #include "dbText.h" #include "dbBox.h" #include "dbBoxConvert.h" @@ -616,6 +617,7 @@ public: typedef db::path_ref path_ptr_type; typedef db::array path_ptr_array_type; typedef db::edge edge_type; + typedef db::edge_pair edge_pair_type; typedef db::text text_type; typedef db::text_ref text_ref_type; typedef db::text_ref text_ptr_type; @@ -642,6 +644,7 @@ public: typedef tl::reuse_vector >::const_iterator path_ptr_iter_type; typedef tl::reuse_vector >::const_iterator path_ptr_array_iter_type; typedef tl::reuse_vector >::const_iterator edge_iter_type; + typedef tl::reuse_vector >::const_iterator edge_pair_iter_type; typedef tl::reuse_vector >::const_iterator text_iter_type; typedef tl::reuse_vector >::const_iterator text_ref_iter_type; typedef tl::reuse_vector >::const_iterator text_ptr_iter_type; @@ -665,6 +668,7 @@ public: typedef tl::reuse_vector > >::const_iterator ppath_ptr_iter_type; typedef tl::reuse_vector > >::const_iterator ppath_ptr_array_iter_type; typedef tl::reuse_vector > >::const_iterator pedge_iter_type; + typedef tl::reuse_vector > >::const_iterator pedge_pair_iter_type; typedef tl::reuse_vector > >::const_iterator ptext_iter_type; typedef tl::reuse_vector > >::const_iterator ptext_ref_iter_type; typedef tl::reuse_vector > >::const_iterator ptext_ptr_iter_type; @@ -690,6 +694,7 @@ public: SimplePolygonPtrArray, SimplePolygonPtrArrayMember, Edge, + EdgePair, Path, PathRef, PathPtrArray, @@ -1016,7 +1021,15 @@ public: m_type = Edge; } - /** + /** + * @brief Construct a shape proxy as a reference to a edge pair + */ + void init (edge_pair_type::tag) + { + m_type = EdgePair; + } + + /** * @brief Construct a shape proxy as a reference to a box */ void init (box_type::tag) @@ -1344,7 +1357,23 @@ public: } } - /** + /** + * @brief Return the actual object that this shape reference is pointing to + * + * This is a generalisation of the polygon (), etc. methods using a tag to identify the + * target object. + */ + const edge_pair_type *basic_ptr (edge_pair_type::tag) const + { + tl_assert (m_type == EdgePair); + if (m_stable) { + return m_with_props ? &**(((pedge_pair_iter_type *) m_generic.iter)) : &**(((edge_pair_iter_type *) m_generic.iter)); + } else { + return m_with_props ? m_generic.pedge_pair : m_generic.edge_pair; + } + } + + /** * @brief Return the actual object that this shape reference is pointing to * * This is a generalisation of the polygon (), etc. methods using a tag to identify the @@ -1636,7 +1665,24 @@ public: } } - /** + /** + * @brief Return the actual object that this shape reference is pointing to for objects with properties + * + * This is a generalisation of the polygon (), etc. methods using a tag to identify the + * target object. + */ + const db::object_with_properties *basic_ptr (db::object_with_properties::tag) const + { + tl_assert (m_type == EdgePair); + tl_assert (m_with_props); + if (m_stable) { + return &**(((pedge_pair_iter_type *) m_generic.iter)); + } else { + return m_generic.pedge_pair; + } + } + + /** * @brief Return the actual object that this shape reference is pointing to for objects with properties * * This is a generalisation of the polygon (), etc. methods using a tag to identify the @@ -1862,7 +1908,16 @@ public: return *(((edge_iter_type *) m_generic.iter)); } - /** + /** + * @brief Return the iterator (in stable reference mode) by tag + */ + edge_pair_iter_type basic_iter (edge_pair_type::tag) const + { + tl_assert (m_type == EdgePair && ! m_with_props); + return *(((edge_pair_iter_type *) m_generic.iter)); + } + + /** * @brief Return the iterator (in stable reference mode) by tag */ text_iter_type basic_iter (text_type::tag) const @@ -2024,7 +2079,16 @@ public: return *(((pedge_iter_type *) m_generic.iter)); } - /** + /** + * @brief Return the iterator (in stable reference mode) by tag for objects with properties + */ + pedge_pair_iter_type basic_iter (db::object_with_properties::tag) const + { + tl_assert (m_type == EdgePair && m_with_props); + return *(((pedge_pair_iter_type *) m_generic.iter)); + } + + /** * @brief Return the iterator (in stable reference mode) by tag for objects with properties */ ptext_iter_type basic_iter (db::object_with_properties::tag) const @@ -2288,7 +2352,50 @@ public: return edge (p); } - /** + /** + * @brief Return a reference to the edge pair if one is referenced + */ + const edge_pair_type &edge_pair () const + { + tl_assert (m_type == EdgePair); + return *basic_ptr (edge_pair_type::tag ()); + } + + /** + * @brief Test if the shape proxy points to a edge pair + */ + bool is_edge_pair () const + { + return (m_type == EdgePair); + } + + /** + * @brief Instantiate the edge pair object + * + * If an edge pair is referenced, this object is instantiated + * by this method. + * Returns true, if the conversion was successful. + */ + bool edge_pair (edge_pair_type &e) const + { + if (is_edge_pair ()) { + e = edge_pair (); + return true; + } else { + return false; + } + } + + /** + * @brief Alias for polymorphic expansion + * Returns true, if the conversion was successful. + */ + bool instantiate (edge_pair_type &p) const + { + return edge_pair (p); + } + + /** * @brief Return a reference to the text if one is referenced */ const text_type &text () const @@ -2567,6 +2674,7 @@ public: const text_ref_type *text_ref; const text_ptr_array_type *text_aref; const edge_type *edge; + const edge_pair_type *edge_pair; const path_type *path; const path_ref_type *path_ref; const path_ptr_array_type *path_aref; @@ -2586,6 +2694,7 @@ public: const db::object_with_properties *ptext_ref; const db::object_with_properties *ptext_aref; const db::object_with_properties *pedge; + const db::object_with_properties *pedge_pair; const db::object_with_properties *ppath; const db::object_with_properties *ppath_ref; const db::object_with_properties *ppath_aref; diff --git a/src/db/db/dbShapeIterator.cc b/src/db/db/dbShapeIterator.cc index f976b2a15..4091dbf5c 100644 --- a/src/db/db/dbShapeIterator.cc +++ b/src/db/db/dbShapeIterator.cc @@ -634,6 +634,9 @@ ShapeIterator::advance_generic (int mode) case Edge: if (advance_shape (mode)) return; break; + case EdgePair: + if (advance_shape (mode)) return; + break; case Path: if (advance_shape (mode)) return; break; @@ -758,6 +761,8 @@ ShapeIterator::quad_box_generic () const return (quad_box_by_shape (region_tag)); case Edge: return (quad_box_by_shape (region_tag)); + case EdgePair: + return (quad_box_by_shape (region_tag)); case Path: return (quad_box_by_shape (region_tag)); case PathRef: diff --git a/src/db/db/dbShapes.cc b/src/db/db/dbShapes.cc index 1b4bcd13d..37f21b25e 100644 --- a/src/db/db/dbShapes.cc +++ b/src/db/db/dbShapes.cc @@ -295,6 +295,8 @@ Shapes::do_insert (const Shapes::shape_type &shape, const Shapes::unit_trans_typ return (insert_array_by_tag (shape_type::simple_polygon_ptr_array_type::tag (), shape, shape_repository (), pm)); case shape_type::Edge: return (insert_by_tag (shape_type::edge_type::tag (), shape, pm)); + case shape_type::EdgePair: + return (insert_by_tag (shape_type::edge_pair_type::tag (), shape, pm)); case shape_type::Path: return (insert_by_tag (shape_type::path_type::tag (), shape, pm)); case shape_type::PathRef: @@ -439,6 +441,16 @@ Shapes::do_insert (const Shapes::shape_type &shape, const Trans &t, tl::func_del return insert (db::object_with_properties (p, pm (shape.prop_id ()))); } } + case shape_type::EdgePair: + { + shape_type::edge_pair_type p (shape.edge_pair ()); + p.transform (t); + if (! shape.has_prop_id ()) { + return insert (p); + } else { + return insert (db::object_with_properties (p, pm (shape.prop_id ()))); + } + } case shape_type::Path: { shape_type::path_type p (shape.path ()); @@ -554,6 +566,8 @@ Shapes::find (const Shapes::shape_type &shape) const return find_shape_by_tag (shape_type::simple_polygon_ptr_array_type::tag (), shape); case shape_type::Edge: return find_shape_by_tag (shape_type::edge_type::tag (), shape); + case shape_type::EdgePair: + return find_shape_by_tag (shape_type::edge_pair_type::tag (), shape); case shape_type::Path: return find_shape_by_tag (shape_type::path_type::tag (), shape); case shape_type::PathRef: @@ -620,6 +634,9 @@ Shapes::replace_prop_id (const Shapes::shape_type &ref, db::properties_id_type p case shape_type::Edge: replace_prop_id (ref.basic_ptr (object_with_properties::tag ()), prop_id); break; + case shape_type::EdgePair: + replace_prop_id (ref.basic_ptr (object_with_properties::tag ()), prop_id); + break; case shape_type::Path: replace_prop_id (ref.basic_ptr (object_with_properties::tag ()), prop_id); break; @@ -682,6 +699,8 @@ Shapes::replace_prop_id (const Shapes::shape_type &ref, db::properties_id_type p return replace_prop_id_iter (shape_type::simple_polygon_ptr_array_type::tag (), ref.basic_iter (shape_type::simple_polygon_ptr_array_type::tag ()), prop_id); case shape_type::Edge: return replace_prop_id_iter (shape_type::edge_type::tag (), ref.basic_iter (shape_type::edge_type::tag ()), prop_id); + case shape_type::EdgePair: + return replace_prop_id_iter (shape_type::edge_pair_type::tag (), ref.basic_iter (shape_type::edge_pair_type::tag ()), prop_id); case shape_type::Path: return replace_prop_id_iter (shape_type::path_type::tag (), ref.basic_iter (shape_type::path_type::tag ()), prop_id); case shape_type::PathRef: @@ -759,6 +778,12 @@ Shapes::transform (const Shapes::shape_type &ref, const Trans &t) p.transform (t); return replace_member_with_props (shape_type::edge_type::tag (), ref, p); } + case shape_type::EdgePair: + { + shape_type::edge_pair_type p (ref.edge_pair ()); + p.transform (t); + return replace_member_with_props (shape_type::edge_pair_type::tag (), ref, p); + } case shape_type::Path: { shape_type::path_type p (ref.path ()); @@ -845,6 +870,8 @@ Shapes::replace (const Shapes::shape_type &ref, const Sh &sh) return replace_member_with_props (shape_type::simple_polygon_ptr_array_type::tag (), ref, sh); case shape_type::Edge: return replace_member_with_props (shape_type::edge_type::tag (), ref, sh); + case shape_type::EdgePair: + return replace_member_with_props (shape_type::edge_pair_type::tag (), ref, sh); case shape_type::Path: return replace_member_with_props (shape_type::path_type::tag (), ref, sh); case shape_type::PathRef: @@ -1212,6 +1239,7 @@ template DB_PUBLIC Shape Shapes::replace<>(const Shape &, const Polygon &); template DB_PUBLIC Shape Shapes::replace<>(const Shape &, const SimplePolygon &); template DB_PUBLIC Shape Shapes::replace<>(const Shape &, const Text &); template DB_PUBLIC Shape Shapes::replace<>(const Shape &, const Edge &); +template DB_PUBLIC Shape Shapes::replace<>(const Shape &, const EdgePair &); template DB_PUBLIC Shape Shapes::transform<> (const Shape &, const ICplxTrans &); template DB_PUBLIC Shape Shapes::transform<> (const Shape &, const Trans &); @@ -1239,6 +1267,8 @@ template class DB_PUBLIC layer_op, db::stable_layer_tag>; template class DB_PUBLIC layer_op; template class DB_PUBLIC layer_op, db::stable_layer_tag>; +template class DB_PUBLIC layer_op; +template class DB_PUBLIC layer_op, db::stable_layer_tag>; template class DB_PUBLIC layer_op; template class DB_PUBLIC layer_op, db::stable_layer_tag>; template class DB_PUBLIC layer_op; @@ -1275,6 +1305,8 @@ template class DB_PUBLIC layer_op, db::unstable_layer_tag>; template class DB_PUBLIC layer_op; template class DB_PUBLIC layer_op, db::unstable_layer_tag>; +template class DB_PUBLIC layer_op; +template class DB_PUBLIC layer_op, db::unstable_layer_tag>; template class DB_PUBLIC layer_op; template class DB_PUBLIC layer_op, db::unstable_layer_tag>; template class DB_PUBLIC layer_op; diff --git a/src/db/db/dbShapes.h b/src/db/db/dbShapes.h index 6fa907b9b..94d87b7bb 100644 --- a/src/db/db/dbShapes.h +++ b/src/db/db/dbShapes.h @@ -83,6 +83,7 @@ public: typedef db::array path_ptr_array_type; typedef path_ptr_array_type::iterator path_ptr_array_iterator_type; typedef db::edge edge_type; + typedef db::edge_pair edge_pair_type; typedef db::text text_type; typedef db::text_ref text_ref_type; typedef db::text_ref text_ptr_type; @@ -126,18 +127,19 @@ public: SimplePolygonRef = 4, SimplePolygonPtrArray = 5, Edge = 6, - Path = 7, - PathRef = 8, - PathPtrArray = 9, - Box = 10, - BoxArray = 11, - ShortBox = 12, - ShortBoxArray = 13, - Text = 14, - TextRef = 15, - TextPtrArray = 16, - UserObject = 17, - Null = 18 // must be last! + EdgePair = 7, + Path = 8, + PathRef = 9, + PathPtrArray = 10, + Box = 11, + BoxArray = 12, + ShortBox = 13, + ShortBoxArray = 14, + Text = 15, + TextRef = 16, + TextPtrArray = 17, + UserObject = 18, + Null = 19 // must be last! }; enum flags_type @@ -151,7 +153,8 @@ public: | (1 << SimplePolygonRef) | (1 << SimplePolygonPtrArray), Edges = (1 << Edge), - Paths = (1 << Path) + EdgePairs = (1 << EdgePair), + Paths = (1 << Path) | (1 << PathRef) | (1 << PathPtrArray), Boxes = (1 << Box) @@ -352,14 +355,15 @@ private: char sz8 [sizeof (per_shape_iter_size )]; char sz9 [sizeof (per_shape_iter_size )]; char sz10 [sizeof (per_shape_iter_size )]; - char sz11 [sizeof (per_shape_iter_size )]; - char sz12 [sizeof (per_shape_iter_size )]; - char sz13 [sizeof (per_shape_iter_size )]; - char sz14 [sizeof (per_shape_iter_size )]; - char sz15 [sizeof (per_shape_iter_size )]; - char sz16 [sizeof (per_shape_iter_size )]; - char sz17 [sizeof (per_shape_iter_size )]; - char sz18 [sizeof (per_shape_iter_size )]; + char sz11 [sizeof (per_shape_iter_size )]; + char sz12 [sizeof (per_shape_iter_size )]; + char sz13 [sizeof (per_shape_iter_size )]; + char sz14 [sizeof (per_shape_iter_size )]; + char sz15 [sizeof (per_shape_iter_size )]; + char sz16 [sizeof (per_shape_iter_size )]; + char sz17 [sizeof (per_shape_iter_size )]; + char sz18 [sizeof (per_shape_iter_size )]; + char sz19 [sizeof (per_shape_iter_size )]; }; // this union is simply there to determine the maximum size required for all diff --git a/src/db/db/dbShapes2.cc b/src/db/db/dbShapes2.cc index 0d45fe433..cd545ee98 100644 --- a/src/db/db/dbShapes2.cc +++ b/src/db/db/dbShapes2.cc @@ -673,6 +673,12 @@ inline unsigned int iterator_type_mask (ShapeIterator::edge_type::tag) return 1 << ShapeIterator::Edge; } +/// @brief Internal: ShapeIterator masks per shape type +inline unsigned int iterator_type_mask (ShapeIterator::edge_pair_type::tag) +{ + return 1 << ShapeIterator::EdgePair; +} + /// @brief Internal: ShapeIterator masks per shape type inline unsigned int iterator_type_mask (ShapeIterator::path_type::tag) { @@ -917,6 +923,8 @@ template class layer_class template class layer_class, db::stable_layer_tag>; template class layer_class; template class layer_class, db::stable_layer_tag>; +template class layer_class; +template class layer_class, db::stable_layer_tag>; template class layer_class; template class layer_class, db::stable_layer_tag>; template class layer_class; @@ -953,6 +961,8 @@ template class layer_class, db::unstable_layer_tag>; template class layer_class; template class layer_class, db::unstable_layer_tag>; +template class layer_class; +template class layer_class, db::unstable_layer_tag>; template class layer_class; template class layer_class, db::unstable_layer_tag>; template class layer_class; diff --git a/src/db/db/dbShapes3.cc b/src/db/db/dbShapes3.cc index f4dd737b9..0f5737665 100644 --- a/src/db/db/dbShapes3.cc +++ b/src/db/db/dbShapes3.cc @@ -117,6 +117,8 @@ template DB_PUBLIC layer & template DB_PUBLIC layer, db::stable_layer_tag> &Shapes::get_layer, db::stable_layer_tag> (); template DB_PUBLIC layer &Shapes::get_layer (); template DB_PUBLIC layer, db::stable_layer_tag> &Shapes::get_layer, db::stable_layer_tag> (); +template DB_PUBLIC layer &Shapes::get_layer (); +template DB_PUBLIC layer, db::stable_layer_tag> &Shapes::get_layer, db::stable_layer_tag> (); template DB_PUBLIC layer &Shapes::get_layer (); template DB_PUBLIC layer, db::stable_layer_tag> &Shapes::get_layer, db::stable_layer_tag> (); template DB_PUBLIC layer &Shapes::get_layer (); @@ -153,6 +155,8 @@ template DB_PUBLIC layer template DB_PUBLIC layer, db::unstable_layer_tag> &Shapes::get_layer, db::unstable_layer_tag> (); template DB_PUBLIC layer &Shapes::get_layer (); template DB_PUBLIC layer, db::unstable_layer_tag> &Shapes::get_layer, db::unstable_layer_tag> (); +template DB_PUBLIC layer &Shapes::get_layer (); +template DB_PUBLIC layer, db::unstable_layer_tag> &Shapes::get_layer, db::unstable_layer_tag> (); template DB_PUBLIC layer &Shapes::get_layer (); template DB_PUBLIC layer, db::unstable_layer_tag> &Shapes::get_layer, db::unstable_layer_tag> (); template DB_PUBLIC layer &Shapes::get_layer (); @@ -190,6 +194,8 @@ template DB_PUBLIC const layer, db::stable_layer_tag> &Shapes::get_layer, db::stable_layer_tag> () const; template DB_PUBLIC const layer &Shapes::get_layer () const; template DB_PUBLIC const layer, db::stable_layer_tag> &Shapes::get_layer, db::stable_layer_tag> () const; +template DB_PUBLIC const layer &Shapes::get_layer () const; +template DB_PUBLIC const layer, db::stable_layer_tag> &Shapes::get_layer, db::stable_layer_tag> () const; template DB_PUBLIC const layer &Shapes::get_layer () const; template DB_PUBLIC const layer, db::stable_layer_tag> &Shapes::get_layer, db::stable_layer_tag> () const; template DB_PUBLIC const layer &Shapes::get_layer () const; @@ -226,6 +232,8 @@ template DB_PUBLIC const layer, db::unstable_layer_tag> &Shapes::get_layer, db::unstable_layer_tag> () const; template DB_PUBLIC const layer &Shapes::get_layer () const; template DB_PUBLIC const layer, db::unstable_layer_tag> &Shapes::get_layer, db::unstable_layer_tag> () const; +template DB_PUBLIC const layer &Shapes::get_layer () const; +template DB_PUBLIC const layer, db::unstable_layer_tag> &Shapes::get_layer, db::unstable_layer_tag> () const; template DB_PUBLIC const layer &Shapes::get_layer () const; template DB_PUBLIC const layer, db::unstable_layer_tag> &Shapes::get_layer, db::unstable_layer_tag> () const; template DB_PUBLIC const layer &Shapes::get_layer () const; @@ -282,6 +290,8 @@ Shapes::is_valid (const Shapes::shape_type &shape) const return is_valid_shape_by_tag (shape_type::simple_polygon_ptr_array_type::tag (), shape); case shape_type::Edge: return is_valid_shape_by_tag (shape_type::edge_type::tag (), shape); + case shape_type::EdgePair: + return is_valid_shape_by_tag (shape_type::edge_pair_type::tag (), shape); case shape_type::Path: return is_valid_shape_by_tag (shape_type::path_type::tag (), shape); case shape_type::PathRef: @@ -440,6 +450,9 @@ Shapes::erase_shape (const Shapes::shape_type &shape) case shape_type::Edge: erase_shape_by_tag (shape_type::edge_type::tag (), shape); break; + case shape_type::EdgePair: + erase_shape_by_tag (shape_type::edge_pair_type::tag (), shape); + break; case shape_type::Path: erase_shape_by_tag (shape_type::path_type::tag (), shape); break; @@ -532,6 +545,9 @@ Shapes::erase_shapes (const std::vector &shapes) case shape_type::Edge: erase_shapes_by_tag (shape_type::edge_type::tag (), s, snext); break; + case shape_type::EdgePair: + erase_shapes_by_tag (shape_type::edge_pair_type::tag (), s, snext); + break; case shape_type::Path: erase_shapes_by_tag (shape_type::path_type::tag (), s, snext); break; diff --git a/src/db/unit_tests/dbShapes.cc b/src/db/unit_tests/dbShapes.cc index 46df094d1..f7c3af9f1 100644 --- a/src/db/unit_tests/dbShapes.cc +++ b/src/db/unit_tests/dbShapes.cc @@ -172,6 +172,16 @@ std::string shapes_to_string_norm (tl::TestBase *_this, const db::Shapes &shapes r += "path " + p.to_string (); EXPECT_EQ (p.box ().to_string (), shape->bbox ().to_string ()); EXPECT_EQ (p.area (), shape->area ()); + } else if (shape->is_edge ()) { + db::Shape::edge_type p; + shape->edge (p); + r += "edge " + p.to_string (); + EXPECT_EQ (p.bbox ().to_string (), shape->bbox ().to_string ()); + } else if (shape->is_edge_pair ()) { + db::Shape::edge_pair_type p; + shape->edge_pair (p); + r += "edge_pair " + p.to_string (); + EXPECT_EQ (p.bbox ().to_string (), shape->bbox ().to_string ()); } else if (shape->is_text ()) { db::Shape::text_type p; shape->text (p); @@ -3263,6 +3273,42 @@ TEST(22) EXPECT_EQ (shapes.find (*s).to_string (), "null"); } +// Edge pairs +TEST(23) +{ + db::Manager m; + db::Shapes s (&m, 0, db::default_editable_mode ()); + db::Box b_empty; + + s.update_bbox (); + EXPECT_EQ (s.bbox (), b_empty); + + db::EdgePair ep (db::Edge (-100, -200, 0, 0), db::Edge (0, -100, 100, 100)); + s.insert (ep); + s.update_bbox (); + EXPECT_EQ (s.bbox (), db::Box (-100, -200, 100, 100)); + + db::ShapeIterator si = s.begin (db::ShapeIterator::EdgePairs); + EXPECT_EQ (!si.at_end (), true); + EXPECT_EQ (si->edge_pair ().to_string (), "(-100,-200;0,0)/(0,-100;100,100)"); + EXPECT_EQ (si->is_edge_pair (), true); + + db::EdgePair ep2; + si->instantiate (ep2); + EXPECT_EQ (ep2.to_string (), "(-100,-200;0,0)/(0,-100;100,100)"); + + ++si; + EXPECT_EQ (si.at_end (), true); + + db::Shapes s2 = s; + EXPECT_EQ (shapes_to_string_norm (_this, s2), "edge_pair (-100,-200;0,0)/(0,-100;100,100) #0\n"); + + s2.clear (); + s2.insert (db::EdgePairWithProperties (db::EdgePair (db::Edge (0, 0, 1, 1), db::Edge (10, 10, 11, 11)), 17)); + + EXPECT_EQ (shapes_to_string_norm (_this, s2), "edge_pair (0,0;1,1)/(10,10;11,11) #17\n"); +} + // Bug #107 TEST(100) {