From f2d106651b3f0c8116fa542ddd99c41fe3297f38 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 11 Mar 2021 21:27:49 +0100 Subject: [PATCH] Reworked the fill scheme for better support of skewed fill repetitions. --- src/db/db/dbFillTool.cc | 510 +++++-------------------------- src/db/db/dbPolygonTools.cc | 27 +- src/db/db/dbPolygonTools.h | 4 +- src/db/db/gsiDeclDbCell.cc | 5 +- testdata/algo/fill_tool_au3.gds | Bin 2228 -> 4604 bytes testdata/algo/fill_tool_au3a.gds | Bin 2142 -> 3844 bytes testdata/algo/fill_tool_au3b.gds | Bin 2208 -> 3680 bytes testdata/algo/fill_tool_au3c.gds | Bin 2056 -> 4022 bytes 8 files changed, 99 insertions(+), 447 deletions(-) diff --git a/src/db/db/dbFillTool.cc b/src/db/db/dbFillTool.cc index c6ee42710..9e4e65759 100644 --- a/src/db/db/dbFillTool.cc +++ b/src/db/db/dbFillTool.cc @@ -32,243 +32,6 @@ namespace db { -namespace -{ - struct AddJoinOperator - { - void operator() (unsigned int &a, unsigned int b) - { - a += b; - } - }; -} - -static db::Vector -optimize_offset (const db::Polygon &fp, const db::AreaMap &am) -{ - db::Coord xshift = 0, yshift = 0; - - { - - tl::interval_map voting; - AddJoinOperator op; - - db::AreaMap::area_type amax = db::AreaMap::area_type (am.d ().x ()) * db::AreaMap::area_type (am.d ().y ()); - - db::Coord dx = am.d ().x (); - db::Coord dy = am.d ().y (); - size_t nx = am.nx (); - size_t ny = am.ny (); - - // Derive a optimal new x offset from the mapping - for (size_t j = 0; j < size_t (ny); ++j) { - - bool x1set = false; - bool x2set = false; - db::Coord x1 = 0; - db::Coord x2 = 0; - - for (size_t i = 0; i < size_t (nx); ++i) { - - if (am.get (i, j) >= amax) { - - if (! x1set) { - - x1 = 0; - x1set = true; - - } else if (x2set) { - - x1 = x2; - x1set = true; - x2set = false; - - } - - } else if (am.get (i, j) > 0) { - - if (! x1set || x2set) { - - x1 = db::Coord (am.get (i, j) / dy); - x1set = true; - x2set = false; - - } else if (! x2set) { - - x2 = db::Coord (am.get (i, j) / dy); - x2set = true; - - } - - } else if (am.get (i, j) == 0) { - - if (x1set) { - - if (! x2set) { - x2 = 0; - } - - if (x1 + x2 < dx) { - voting.add (-x1, x2 + 1, (unsigned int) 1, op); - } else { - voting.add (-x1, x2 - dx + 1, (unsigned int) 1, op); - voting.add (dx - x1, x2 + 1, (unsigned int) 1, op); - } - - x1set = false; - x2set = false; - - } - - } - - } - - if (x1set) { - - if (! x2set) { - x2 = 0; - } - - if (x1 + x2 < dx) { - voting.add (-x1, x2 + 1, (unsigned int) 1, op); - } else { - voting.add (-x1, x2 - dx + 1, (unsigned int) 1, op); - voting.add (dx - x1, x2 + 1, (unsigned int) 1, op); - } - - } - - } - - std::set xshifts; - for (db::Polygon::polygon_edge_iterator e = fp.begin_edge (); ! e.at_end (); ++e) { - xshifts.insert (((*e).p1 ().x () - am.p0 ().x ()) % dx); - } - - unsigned int max_votes = 0; - for (std::set ::const_iterator xs = xshifts.begin (); xs != xshifts.end (); ++xs) { - const unsigned int *z = voting.mapped (*xs); - if (z && *z > max_votes) { - xshift = *xs; - max_votes = *z; - } - } - - } - - { - - tl::interval_map voting; - AddJoinOperator op; - - db::AreaMap::area_type amax = db::AreaMap::area_type (am.d ().x ()) * db::AreaMap::area_type (am.d ().y ()); - - db::Coord dx = am.d ().x (); - db::Coord dy = am.d ().y (); - size_t nx = am.nx (); - size_t ny = am.ny (); - - // Derive a optimal new y offset from the mapping - for (size_t i = 0; i < size_t (nx); ++i) { - - bool y1set = false; - bool y2set = false; - db::Coord y1 = 0; - db::Coord y2 = 0; - - for (size_t j = 0; j < size_t (ny); ++j) { - - if (am.get (i, j) >= amax) { - - if (! y1set) { - - y1 = 0; - y1set = true; - - } else if (y2set) { - - y1 = y2; - y1set = true; - y2set = false; - - } - - } else if (am.get (i, j) > 0) { - - if (! y1set || y2set) { - - y1 = db::Coord (am.get (i, j) / dx); - y1set = true; - y2set = false; - - } else if (! y2set) { - - y2 = db::Coord (am.get (i, j) / dx); - y2set = true; - - } - - } else if (am.get (i, j) == 0) { - - if (y1set) { - - if (! y2set) { - y2 = 0; - } - - if (y1 + y2 < dy) { - voting.add (-y1, y2 + 1, (unsigned int) 1, op); - } else { - voting.add (-y1, y2 - dy + 1, (unsigned int) 1, op); - voting.add (dy - y1, y2 + 1, (unsigned int) 1, op); - } - - y1set = false; - y2set = false; - - } - - } - - } - - if (y1set) { - - if (! y2set) { - y2 = 0; - } - - if (y1 + y2 < dy) { - voting.add (-y1, y2 + 1, (unsigned int) 1, op); - } else { - voting.add (-y1, y2 - dy + 1, (unsigned int) 1, op); - voting.add (dy - y1, y2 + 1, (unsigned int) 1, op); - } - - } - - } - - std::set yshifts; - for (db::Polygon::polygon_edge_iterator e = fp.begin_edge (); ! e.at_end (); ++e) { - yshifts.insert (((*e).p1 ().y () - am.p0 ().y ()) % dy); - } - - unsigned int max_votes = 0; - for (std::set ::const_iterator ys = yshifts.begin (); ys != yshifts.end (); ++ys) { - const unsigned int *z = voting.mapped (*ys); - if (z && *z > max_votes) { - yshift = *ys; - max_votes = *z; - } - } - - } - - return db::Vector (xshift, yshift); -} - class GenericRasterizer { public: @@ -281,37 +44,53 @@ public: GenericRasterizer (const db::Polygon &fp, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin) : m_row_step (row_step), m_column_step (column_step), m_row_steps (0), m_column_steps (0), m_origin (origin) { - db::Coord dx = row_step.x (); - db::Coord dy = column_step.y (); + rasterize (fp); + } - if (row_step.y () == 0) { + void move (const db::Vector &d) + { + m_origin += d; + clear (); + } + + void clear () + { + m_area_maps.clear (); + } + + void rasterize (const db::Polygon &fp) + { + db::Coord dx = m_row_step.x (); + db::Coord dy = m_column_step.y (); + + if (m_row_step.y () == 0) { m_row_steps = 1; } else { - m_row_steps = tl::lcm (dy, std::abs (row_step.y ())) / std::abs (row_step.y ()); + m_row_steps = tl::lcm (dy, std::abs (m_row_step.y ())) / std::abs (m_row_step.y ()); } - if (column_step.x () == 0) { + if (m_column_step.x () == 0) { m_column_steps = 1; } else { - m_column_steps = tl::lcm (dx, std::abs (column_step.x ())) / std::abs (column_step.x ()); + m_column_steps = tl::lcm (dx, std::abs (m_column_step.x ())) / std::abs (m_column_step.x ()); } db::Box fp_bbox = fp.box (); // compensate for distortion by sheared kernel - fp_bbox.enlarge (db::Vector (db::coord_traits::rounded (double (fp_bbox.height ()) * std::abs (column_step.x ()) / dy), db::coord_traits::rounded (double (fp_bbox.width ()) * std::abs (row_step.y ()) / dx))); + fp_bbox.enlarge (db::Vector (db::coord_traits::rounded (double (fp_bbox.height ()) * std::abs (m_column_step.x ()) / dy), db::coord_traits::rounded (double (fp_bbox.width ()) * std::abs (m_row_step.y ()) / dx))); - int columns_per_rows = (int (m_row_steps) * row_step.y ()) / dy; - int rows_per_columns = (int (m_column_steps) * column_step.x ()) / dx; + int columns_per_rows = (int (m_row_steps) * m_row_step.y ()) / dy; + int rows_per_columns = (int (m_column_steps) * m_column_step.x ()) / dx; - db::Coord ddx = dx * db::Coord (m_row_steps) - column_step.x () * columns_per_rows; - db::Coord ddy = dy * db::Coord (m_column_steps) - row_step.y () * rows_per_columns; + db::Coord ddx = dx * db::Coord (m_row_steps) - m_column_step.x () * columns_per_rows; + db::Coord ddy = dy * db::Coord (m_column_steps) - m_row_step.y () * rows_per_columns; // round polygon bbox - db::Coord fp_left = db::Coord (tl::round_down (fp_bbox.left () - origin.x (), ddx)) + origin.x (); - db::Coord fp_bottom = db::Coord (tl::round_down (fp_bbox.bottom () - origin.y (), ddy)) + origin.y (); - db::Coord fp_right = db::Coord (tl::round_up (fp_bbox.right () - origin.x (), ddx)) + origin.x (); - db::Coord fp_top = db::Coord (tl::round_up (fp_bbox.top () - origin.y (), ddy)) + origin.y (); + db::Coord fp_left = db::Coord (tl::round_down (fp_bbox.left () - m_origin.x (), ddx)) + m_origin.x (); + db::Coord fp_bottom = db::Coord (tl::round_down (fp_bbox.bottom () - m_origin.y (), ddy)) + m_origin.y (); + db::Coord fp_right = db::Coord (tl::round_up (fp_bbox.right () - m_origin.x (), ddx)) + m_origin.x (); + db::Coord fp_top = db::Coord (tl::round_up (fp_bbox.top () - m_origin.y (), ddy)) + m_origin.y (); fp_bbox = db::Box (fp_left, fp_bottom, fp_right, fp_top); size_t nx = fp_bbox.width () / ddx; @@ -326,6 +105,8 @@ public: m_area_maps.reserve (m_row_steps * m_column_steps + std::abs (columns_per_rows) * std::abs (rows_per_columns)); + db::AreaMap am; + for (unsigned int ic = 0; ic < m_column_steps; ++ic) { for (unsigned int ir = 0; ir < m_row_steps; ++ir) { @@ -333,10 +114,12 @@ public: db::Vector dr = m_row_step * long (ir); db::Vector dc = m_column_step * long (ic); - m_area_maps.push_back (db::AreaMap ()); - m_area_maps.back ().reinitialize (db::Point (fp_left, fp_bottom) + dr + dc, db::Vector (ddx, ddy), db::Vector (dx, dy), nx, ny); + am.reinitialize (db::Point (fp_left, fp_bottom) + dr + dc, db::Vector (ddx, ddy), db::Vector (dx, dy), nx, ny); - db::rasterize (fp, m_area_maps.back ()); + if (db::rasterize (fp, am)) { + m_area_maps.push_back (db::AreaMap ()); + m_area_maps.back ().swap (am); + } } @@ -351,16 +134,42 @@ public: db::Vector dr = m_row_step * long ((rows_per_columns > 0 ? -(ir + 1) : ir) + m_row_steps); db::Vector dc = m_column_step * long ((columns_per_rows > 0 ? -(ic + 1) : ic) + m_column_steps); - m_area_maps.push_back (db::AreaMap ()); - m_area_maps.back ().reinitialize (db::Point (fp_left, fp_bottom) + dr + dc, db::Vector (ddx, ddy), db::Vector (dx, dy), nx, ny); + am.reinitialize (db::Point (fp_left, fp_bottom) + dr + dc, db::Vector (ddx, ddy), db::Vector (dx, dy), nx, ny); - db::rasterize (fp, m_area_maps.back ()); + if (db::rasterize (fp, am)) { + m_area_maps.push_back (db::AreaMap ()); + m_area_maps.back ().swap (am); + } } } } + size_t filled_pixels () const + { + size_t n = 0; + + for (std::vector::const_iterator a = m_area_maps.begin (); a != m_area_maps.end (); ++a) { + + db::Coord nx = db::Coord (a->nx ()); + db::Coord ny = db::Coord (a->ny ()); + db::AreaMap::area_type amax = a->pixel_area (); + + double n = 0; + for (size_t i = 0; i < size_t (nx); ++i) { + for (size_t j = 0; j < size_t (ny); ++j) { + if (a->get (i, j) >= amax) { + n += 1; + } + } + } + + } + + return n; + } + const db::Point &p0 () const { return m_origin; } unsigned int row_steps () const { return m_row_steps; } @@ -384,178 +193,6 @@ private: }; -static bool -rasterize_simple (const db::Polygon &fp, const db::Box &fc_bbox, const db::Point &p0, db::AreaMap &am) -{ - - db::Coord dx = fc_bbox.width (); - db::Coord dy = fc_bbox.height (); - - if (tl::verbosity () >= 50) { - tl::info << "Simple rasterize polygon: " << fp.to_string () << " with box " << fc_bbox.to_string (); - } - - db::Box fp_bbox = fp.box (); - - // round polygon bbox - db::Coord fp_left = dx * ((fp_bbox.left () - p0.x ()) / dx) + p0.x (); - db::Coord fp_bottom = dy * ((fp_bbox.bottom () - p0.y ()) / dy) + p0.y (); - db::Coord fp_right = dx * ((fp_bbox.right () + dx - 1 - p0.x ()) / dx) + p0.x (); - db::Coord fp_top = dy * ((fp_bbox.top () + dy - 1 - p0.y ()) / dy) + p0.y (); - fp_bbox = db::Box (fp_left, fp_bottom, fp_right, fp_top); - - db::Coord nx = fp_bbox.width () / dx; - db::Coord ny = fp_bbox.height () / dy; - - if (nx <= 0 || ny <= 0) { - // nothing to rasterize: - return false; - } - - am.reinitialize (fp_bbox.p1 (), db::Vector (dx, dy), size_t (nx), size_t (ny)); - - // Rasterize to determine fill regions - db::rasterize (fp, am); - - return true; -} - -static bool -rasterize_extended (const db::Polygon &fp, const db::Box &fc_bbox, db::AreaMap &am) -{ - db::Coord dx = fc_bbox.width (); - db::Coord dy = fc_bbox.height (); - - if (tl::verbosity () >= 50) { - tl::info << "Optimized rasterize polygon: " << fp.to_string () << " with box " << fc_bbox.to_string (); - } - - db::Box fp_bbox = fp.box (); - - db::Coord nx = (fp_bbox.width () + dx - 1) / dx; - db::Coord ny = (fp_bbox.height () + dy - 1) / dy; - - if (nx <= 0 || ny <= 0) { - // nothing to rasterize: - return false; - } - -// @@@ - // try to create a point for which the fill box is inside the polygon - size_t nhull = fp.hull ().size (); - if (nhull < 3) { - return false; - } - - db::Point p0 = fp.hull ()[0]; - db::Point p1 = fp.hull ()[1]; - db::Point pm1 = fp.hull ()[nhull - 1]; - - db::Coord hx = (dx + 1) / 2; - db::Coord hy = (dy + 1) / 2; - - db::Edge e1 (p0, p1); - if (e1.dx () < 0) { - e1.move (db::Vector (hx, hy)); - } else { - e1.move (db::Vector (hx, -hy)); - } - - db::Edge em1 (p0, pm1); - if (em1.dy () < 0) { - em1.move (db::Vector (hx, hy)); - } else { - em1.move (db::Vector (-hx, hy)); - } - - std::pair cp = e1.cut_point (em1); - - db::Point o = fp_bbox.p1 (); - if (cp.first) { - db::Point po = cp.second - db::Vector (hx, hy); - o = po - db::Vector (dx * ((po.x () - fp_bbox.p1 ().x ()) / dx), dy * ((po.y () - fp_bbox.p1 ().y ()) / dy)); - } - - printf("@@@ fp=%s\n", fp.to_string().c_str()); fflush(stdout); // @@@ - printf("@@@ -> o=%s\n", o.to_string().c_str()); fflush(stdout); // @@@ -// @@@ - - am.reinitialize (o, db::Vector (dx, dy), size_t (nx), size_t (ny)); - - // Rasterize to determine fill regions - db::rasterize (fp, am); - -// @@@ -{ -db::Coord nx = db::Coord (am.nx ()); -db::Coord ny = db::Coord (am.ny ()); -db::AreaMap::area_type amax = am.pixel_area (); -double n = 0; -for (size_t i = 0; i < size_t (nx); ++i) { - for (size_t j = 0; j < size_t (ny); ++j) { - if (am.get (i, j) >= amax) { - n += 1; - } - } -} -printf("@@@ -> n=%d\n", int(n)); fflush(stdout); // @@@ -} -// @@@ - return true; // @@@ - - if (tl::verbosity () >= 50) { - - db::Coord nx = db::Coord (am.nx ()); - db::Coord ny = db::Coord (am.ny ()); - db::AreaMap::area_type amax = am.pixel_area (); - double n = 0; - for (size_t i = 0; i < size_t (nx); ++i) { - for (size_t j = 0; j < size_t (ny); ++j) { - if (am.get (i, j) >= amax) { - n += 1; - } - } - } - - tl::info << "Number of fill regions before optimization: " << n; - - } - - db::Vector d = optimize_offset (fp, am); - - if (tl::verbosity () >= 50) { - tl::info << "Shift vector: " << d.to_string (); - } - - if (d.x () != 0 || d.y () != 0) { - - am.move (d); - am.clear (); - db::rasterize (fp, am); - - if (tl::verbosity () >= 50) { - - db::Coord nx = db::Coord (am.nx ()); - db::Coord ny = db::Coord (am.ny ()); - db::AreaMap::area_type amax = am.pixel_area (); - double n = 0; - for (size_t i = 0; i < size_t (nx); ++i) { - for (size_t j = 0; j < size_t (ny); ++j) { - if (am.get (i, j) >= amax) { - n += 1; - } - } - } - - tl::info << "Number of fill regions after optimization: " << n; - - } - - } - - return true; -} - DB_PUBLIC bool fill_region (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type fill_cell_index, const db::Box &fc_bbox, const db::Point &origin, bool enhanced_fill, std::vector *remaining_parts, const db::Vector &fill_margin) @@ -612,11 +249,18 @@ fill_region (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type fill_ce for (std::vector ::const_iterator fp = fpb.begin (); fp != fpb.end (); ++fp) { + if (fp->hull ().size () == 0) { + continue; + } + size_t ninsts = 0; - GenericRasterizer am (*fp, row_step, column_step, origin); + db::Point o = origin; + if (enhanced_fill) { + o = fp->hull () [0]; + } - // @@@ optimize fill offset ... + GenericRasterizer am (*fp, row_step, column_step, o); for (unsigned int i = 0; i < am.area_maps (); ++i) { @@ -804,8 +448,6 @@ fill_region_repeat (db::Cell *cell, const db::Region &fr, db::cell_index_type fi new_fill_region.swap (remaining); fill_region = &new_fill_region; - break; // @@@ - } } diff --git a/src/db/db/dbPolygonTools.cc b/src/db/db/dbPolygonTools.cc index 45905b4e0..fc268796c 100644 --- a/src/db/db/dbPolygonTools.cc +++ b/src/db/db/dbPolygonTools.cc @@ -1617,15 +1617,20 @@ AreaMap::reinitialize (const db::Point &p0, const db::Vector &d, const db::Vecto m_p0 = p0; m_d = d; m_p = db::Vector (std::min (d.x (), p.x ()), std::min (d.y (), p.y ())); - m_nx = nx; - m_ny = ny; - if (mp_av) { - delete mp_av; + if (nx != m_nx || ny != m_ny) { + + m_nx = nx; + m_ny = ny; + + if (mp_av) { + delete mp_av; + } + + mp_av = new area_type [nx * ny]; + } - mp_av = new area_type [nx * ny]; - clear (); } @@ -1645,6 +1650,7 @@ AreaMap::swap (AreaMap &other) { std::swap (m_p0, other.m_p0); std::swap (m_d, other.m_d); + std::swap (m_p, other.m_p); std::swap (m_nx, other.m_nx); std::swap (m_ny, other.m_ny); std::swap (mp_av, other.mp_av); @@ -1676,7 +1682,7 @@ AreaMap::bbox () const // ------------------------------------------------------------------------- // Implementation of rasterize -void +bool rasterize (const db::Polygon &polygon, db::AreaMap &am) { typedef db::AreaMap::area_type area_type; @@ -1685,7 +1691,7 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) // check if the polygon overlaps the rasterization area. Otherwise, we simply do nothing. if (! pbox.overlaps (box)) { - return; + return false; } db::Coord ymin = box.bottom (), ymax = box.top (); @@ -1702,7 +1708,7 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) // no scanning required (i.e. degenerated polygon) -> do nothing if (iy0 == iy1 || ix0 == ix1) { - return; + return false; } // collect edges @@ -1733,7 +1739,7 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) } if (c == edges.end ()) { - return; + return false; } std::vector ::iterator f = c; @@ -1876,6 +1882,7 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) } + return true; } // ------------------------------------------------------------------------- diff --git a/src/db/db/dbPolygonTools.h b/src/db/db/dbPolygonTools.h index 423be8216..99d4fd051 100644 --- a/src/db/db/dbPolygonTools.h +++ b/src/db/db/dbPolygonTools.h @@ -640,8 +640,10 @@ private: * * This will decompose the polygon and produce per-pixel area values for the given * polygon. The area contributions will be added to the given area map. + * + * Returns a value indicating whether the map will be non-empty. */ -void DB_PUBLIC rasterize (const db::Polygon &polygon, db::AreaMap &am); +bool DB_PUBLIC rasterize (const db::Polygon &polygon, db::AreaMap &am); /** * @brief Minkowsky sum of an edge and a polygon diff --git a/src/db/db/gsiDeclDbCell.cc b/src/db/db/gsiDeclDbCell.cc index 2d9eb3847..a6dacf348 100644 --- a/src/db/db/gsiDeclDbCell.cc +++ b/src/db/db/gsiDeclDbCell.cc @@ -1886,9 +1886,10 @@ Class decl_Cell ("db", "Cell", ) + gsi::method_ext ("fill_region_multi", &fill_region_repeat, gsi::arg ("region"), gsi::arg ("fill_cell_index"), gsi::arg ("kernel_origin"), gsi::arg ("row_step"), gsi::arg ("column_step"), gsi::arg ("fill_margin"), gsi::arg ("remaining_polygons"), "@brief Fills the given region with cells of the given type in enhanced mode with iterations\n" - "This version operates like \\fill_region, but repeates the fill generation until no further fill cells can be placed. " + "This version operates like \\fill_region, but repeats the fill generation until no further fill cells can be placed. " "As the fill pattern origin changes between the iterations, narrow regions can be filled which cannot with a fixed fill pattern origin. " - "The \\fill_margin parameter is important as it controls the distance between fill cells with a different origin and therefore pitch-incompatible arrays.\n" + "The \\fill_margin parameter is important as it controls the distance between fill cells with a different origin and therefore " + "introduces a safety distance between pitch-incompatible arrays.\n" "\n" "This method has been introduced in version 0.27.\n" ) + diff --git a/testdata/algo/fill_tool_au3.gds b/testdata/algo/fill_tool_au3.gds index 7d6e50a8e169bd860e585b36617059584360bd7a..9fa4c273977a7b671dc1da101faee2e27b1f425c 100644 GIT binary patch literal 4604 zcmbuDO=w+36vxl~n){yCM=Vh{MM9BMBt$7itCSL}mQ){6BuEKTyQoqmgd(AcixPs1 zDy5Vxq-YQ+qNQ|EL`vvFLMd6vqm-_s#2^Z#gd#;s4K>%_%$@0bsqdZ?vbgV`x%W38 z=giERGZ7Ise07FTFPV+_ztk zj>x90x?aPWJ$swY_g{Uz*%V1@BIdDJ_-)wV{+^^LiW#1xR(W%fXDVoTZ&lLA+x6pB z^;>*D&(osbHlDYGhWA#>`njm1tr;`&7S+>7L>e1AN4}va5y`I<#l6L{rS3%SLQ(u% zt0szKwG&8^xj?jfe&zc4$Pr1>%54BpV=(X+(Y(Yv(nhwg7Rfn!r*# zR(!7!Zhf?>3H&id8+SA!It2B!M#RGqdo&{14l%9~wLa`QrxA4najlx*>jJ$T*NEaW z#81@(U(fPf*NEt(NR(VT+wIOD1S|jQM5ErWnz8ZS@SS6Mq zzN#kh)>Vj08WGJx+|-Ep8ysQzm!;IyVY65EqXk%YBLH%BcNtIZJIHM8K zBE%Jqi02{hYD98QB5(#tAw4`N9p z(%(h0UX36QO14cS*!Pl+Y6N>lvMG&V^+&`Aj)_)=s<^CWJoz|>f|0yQ2 z);WhQYoC>CWET(PkF_$khnkl)vS8Zi$fOCa_D&G_oPNS(+GAwe`{BkoAyOhMv9na> zBIY4CEtPM#`SuU&Z|uSAZRPBdlz17`Mz#8R=F@3t-)ZOFB8-E(iQ6e+R!Yr(rR^Vi zyoUE^XZ?fTj-9rC{iCuUtK18uKHZ_57{o`Sstk`Je*NM4Ad#o3$tLK|`r|>WFF=)Jn{eTTYqrq}+ zgUV<*-=#8+3Cdrzwd|KM`pkjpwd**{|Ck+La-$rN(-$b)xd}SL`rKjdFO|$t(gTdg zYtr}s^_xzYc3E}%p=`gKe^`IkfAq;6%;{|uK5com(PECW*ZZ8$Vji$(ySu9!_$7RB z$L=++y=aTFSNGjY}=vE|*`xaoB>?`g+)wcl;-j8^!QdayZ;3Gs$}-^CrL#1(D`k_WY1$yc_Q u?uMVlR;v%hulx+}XLuhpaFeUX_d)xAA!VEjaVf;9py7RpSNty|P31pP25!p$ literal 2228 zcma)-O-PhM7{{M|zt?QB4?bxX!(DmCcJ~?}B{@v#J_75lCyz1$eqF-BA2!f8zQt9U9E2WZ% z7l{OW9qb1M_4inl8^`f$#sfuG`WX)`?Yg3P`%=BLOTWnX3C1G2Q;aT4yRNA9q15#@ z3WDzI$h|!xh5cF2qXP~(38bhzE)Qezc1pYxZ(g5JLm6phTtf90bwd1nCgLyGRpyma zMTBx8W$q0sAH~&jn#?vB7Lw8stYObp^-a6kV5Dr8=ud-CV7V zrcu5pdc*v2D$%Qzrl;wSq^Ad!PvJlu@AN6+n@2>;mej&wG+Od)i*LW#`;}N$Qo1d; zMQ2$$Cq?oTc%0DMPnf+M)(`F$9kR+OYlfe(J;^Y%>oBYD&)%)nPy8KX5PyZ0II8M< zcjo~=x!=*F4CjdFE^|Yb-~5Qqvts$ViZ`XsC;R)Vo;RS*lXdn}(u4)_y1?A>47rqqx9$u!TA5Qf&5T{A>iI)9-kAE>x(D1+Re#0Z^}}u(@f}H-Thq0% zlpS5K3J)=V%Kn7-nVgfbTK$rIxIawaDK^nhj0(4zpJQ(5(0kr3{4{j&JLx||<-gyh z<4{ccTHCL2J~?Z|-&Rd`3(jD3Ow*l(+(1ibh53DTzGiM4URC}XHo+FrpGG~kFjpa_j0nYww-th{WP(Vn}EHF{uelvA$P;-u{&ebT3X2YGM7cSU^g@1^uq ztS?l4G*#UGM}}e8&$mI&v%J9kFmuZ)@0d9+>(8C^ogwyBMp@IoPnLGwzEk{$d!GCS D10)}L diff --git a/testdata/algo/fill_tool_au3a.gds b/testdata/algo/fill_tool_au3a.gds index edddbc81fac4edaaa35c2f23aba0b887df1ef63c..e67a6a2ec107b5de42e6c52b28de5554241fe213 100644 GIT binary patch literal 3844 zcmaKvUx-yj9LIlW&YVBDwq+A52w5cdDHf9uvXF(WmaGd&EFmH$viT4a51B4UeB4-yg*vc>K_eP+(gy?5iB-^1MRote*X{{Cj> zcO(~M_Q0RCKn=ic7?TXk^B!9>VzoYJVaxAT?>MZGfrv zIS=uUMwpWjpJ_ySh|w;!6V(1f8#gtA)sn=v6MhVmOlm|jM;kL5VGcsfX+-)m#CeUd zTOh7#L^;LIeWwwf35eV61S|F>z5JjNc%USA+6lj}Vm4?5t0m?+ji6RoHGe-edvdxN z;-p5Dk202XDzOUjb34I$9HWiaS}U3S$$n~<0Bu`%|F;TwZAW}(;DZ*trPEp6vw0bcT{P37hkSS&!oIAVPfD!SGps3EeTovV)Jnhak)ImUE9B|ZMNa9`zz+MS`OLW(dw5^v zCi!jHd!?WIX-oA4J`%lnnZPdGAMVX@ y_lH+kUe~wU+w1TLnX}XX-9P>E5c7rkzcu(KWUR}iUaj$7i5kn}{lYh)welaHO6@EF literal 2142 zcma)-O-NKx6vxlp_c=BVWzuRE5fvd~5<(wHl$j(A6(SK46=BgLLP8=$T(qi)hzJ)Y zAt4m4BwDy=5iVRvxM~$aE@~4Y(XN>0^}Fxh>x?lzT^xU&_wN7PbIv{Qj)bDrSt;dI z^i4boMI;IQnh2_3(krppFbmZQ?6LT+r9_X3bGx_Y?fRuvOb;U63?JJir9z9kr zi}YK4A*_tV${y1m$GNB3OQ=p)I@ zHV(sqQ^@@dBE{zV%;PPJo`h0LGagBj>Q|y#R_`M@Zg$90lGi0v1xNlLLOGPFjP#xy zNH429Ym|18z;`Dyk|>EfrX|9V^=e0@y9B33nwJ`wv3X6c^8di|iSqpbDPuJg2{=3EQD z<4OEK_>P>En!`Zmb- z6F(*Up22UeG1rB!$k$Tm>+(*Sp8J~T@6K;j-(@T348O!o>G^|d6!z4Z8^_o4YAM{h z)hFbalDy~fsn=28kt=L@t{a|rPnr+=`MPH2Ivg|%^sV`xS}lFuBl@t+R`xNvM(4gG zozuI-yJ314(Kp>g?TdNdNSDrcZk#h6Wxf6Wyd`|T!;~LjjPE#QXU9?NjJ{F)37wbU ziRPN2$9Llwi0MeL{ds&xdL2yTTk10p$lo#Ct$RdWmQfLZKlwSXb${4A$frKvE45!+ PVlQIryf65*^?msV8@czk diff --git a/testdata/algo/fill_tool_au3b.gds b/testdata/algo/fill_tool_au3b.gds index 08057877ce75dc9fe606e064d3dfecf62a6e4572..878de1226e306d3fce9194180f249e7ad2ba2e32 100644 GIT binary patch literal 3680 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