From 1b5ea72125324acf1a06ae2c005603071dfce40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6fferlein?= Date: Thu, 10 Mar 2022 19:15:35 +0100 Subject: [PATCH] Issue 1017 (#1023) * Write polygons as single POLYLINE or LWPOLYLINE to DXF * DXF writer creates simple polygons from ones with holes in POLYLINE and LWPOLYLINE mode, added tests --- .../streamers/dxf/db_plugin/dbDXFWriter.cc | 85 +++-- .../dxf/unit_tests/dbDXFWriterTests.cc | 247 +++++++++++++++ .../streamers/dxf/unit_tests/unit_tests.pro | 3 +- testdata/dxf/dxf1.gds | Bin 0 -> 186 bytes testdata/dxf/dxf1a_au.dxf | 110 +++++++ testdata/dxf/dxf1b_au.dxf | 82 +++++ testdata/dxf/dxf1c_au.dxf | 88 ++++++ testdata/dxf/dxf1d_au.dxf | 88 ++++++ testdata/dxf/dxf1e_au.dxf | 132 ++++++++ testdata/dxf/dxf2.gds | Bin 0 -> 168 bytes testdata/dxf/dxf2_au.dxf | 86 ++++++ testdata/dxf/dxf3.gds | Bin 0 -> 302 bytes testdata/dxf/dxf3_au.dxf | 218 +++++++++++++ testdata/dxf/dxf4.gds | Bin 0 -> 234 bytes testdata/dxf/dxf4a_au.dxf | 254 +++++++++++++++ testdata/dxf/dxf4b_au.dxf | 170 ++++++++++ testdata/dxf/dxf4c_au.dxf | 188 +++++++++++ testdata/dxf/dxf4d_au.dxf | 188 +++++++++++ testdata/dxf/dxf4e_au.dxf | 292 ++++++++++++++++++ 19 files changed, 2183 insertions(+), 48 deletions(-) create mode 100644 src/plugins/streamers/dxf/unit_tests/dbDXFWriterTests.cc create mode 100644 testdata/dxf/dxf1.gds create mode 100644 testdata/dxf/dxf1a_au.dxf create mode 100644 testdata/dxf/dxf1b_au.dxf create mode 100644 testdata/dxf/dxf1c_au.dxf create mode 100644 testdata/dxf/dxf1d_au.dxf create mode 100644 testdata/dxf/dxf1e_au.dxf create mode 100644 testdata/dxf/dxf2.gds create mode 100644 testdata/dxf/dxf2_au.dxf create mode 100644 testdata/dxf/dxf3.gds create mode 100644 testdata/dxf/dxf3_au.dxf create mode 100644 testdata/dxf/dxf4.gds create mode 100644 testdata/dxf/dxf4a_au.dxf create mode 100644 testdata/dxf/dxf4b_au.dxf create mode 100644 testdata/dxf/dxf4c_au.dxf create mode 100644 testdata/dxf/dxf4d_au.dxf create mode 100644 testdata/dxf/dxf4e_au.dxf diff --git a/src/plugins/streamers/dxf/db_plugin/dbDXFWriter.cc b/src/plugins/streamers/dxf/db_plugin/dbDXFWriter.cc index f418a614f..0280a5c99 100644 --- a/src/plugins/streamers/dxf/db_plugin/dbDXFWriter.cc +++ b/src/plugins/streamers/dxf/db_plugin/dbDXFWriter.cc @@ -387,65 +387,56 @@ DXFWriter::write_polygons (const db::Layout & /*layout*/, const db::Cell &cell, void DXFWriter::write_polygon (const db::Polygon &polygon, double sf) { - if (m_options.polygon_mode == 0) { + if (polygon.holes () > 0 && (m_options.polygon_mode == 0 || m_options.polygon_mode == 1 || m_options.polygon_mode == 2)) { - for (unsigned int c = 0; c < polygon.holes () + 1; ++c) { + // resolve holes or merge polygon as a preparation step + std::vector polygons; - *this << 0 << endl << "POLYLINE" << endl; - *this << 8 << endl; emit_layer (m_layer); - *this << 70 << endl << 1 << endl; - *this << 40 << endl << 0.0 << endl; - *this << 41 << endl << 0.0 << endl; - *this << 66 << endl << 1 << endl; // required by TrueView - - for (db::Polygon::polygon_contour_iterator p = polygon.contour (c).begin (); p != polygon.contour (c).end (); ++p) { - *this << 0 << endl << "VERTEX" << endl; - *this << 8 << endl; emit_layer (m_layer); // required by TrueView - *this << 10 << endl << (*p).x () * sf << endl; - *this << 20 << endl << (*p).y () * sf << endl; - } - - *this << 0 << endl << "SEQEND" << endl; + db::EdgeProcessor ep; + ep.insert_sequence (polygon.begin_edge ()); + db::PolygonContainer pc (polygons); + db::PolygonGenerator out (pc, true /*resolve holes*/, false /*min coherence for splitting*/); + db::SimpleMerge op; + ep.process (out, op); + for (std::vector::const_iterator p = polygons.begin (); p != polygons.end (); ++p) { + write_polygon (*p, sf); } + } else if (m_options.polygon_mode == 0) { + + *this << 0 << endl << "POLYLINE" << endl; + *this << 8 << endl; emit_layer (m_layer); + *this << 70 << endl << 1 << endl; + *this << 40 << endl << 0.0 << endl; + *this << 41 << endl << 0.0 << endl; + *this << 66 << endl << 1 << endl; // required by TrueView + + for (db::Polygon::polygon_contour_iterator p = polygon.begin_hull (); p != polygon.end_hull (); ++p) { + *this << 0 << endl << "VERTEX" << endl; + *this << 8 << endl; emit_layer (m_layer); // required by TrueView + *this << 10 << endl << (*p).x () * sf << endl; + *this << 20 << endl << (*p).y () * sf << endl; + } + + *this << 0 << endl << "SEQEND" << endl; + } else if (m_options.polygon_mode == 1) { - for (unsigned int c = 0; c < polygon.holes () + 1; ++c) { - - *this << 0 << endl << "LWPOLYLINE" << endl; - *this << 8 << endl; emit_layer (m_layer); - *this << 90 << endl << polygon.contour (0).size () << endl; - *this << 70 << endl << 1 << endl; - *this << 43 << endl << 0.0 << endl; - - for (db::Polygon::polygon_contour_iterator p = polygon.contour (c).begin (); p != polygon.contour (c).end (); ++p) { - *this << 10 << endl << (*p).x () * sf << endl; - *this << 20 << endl << (*p).y () * sf << endl; - } + *this << 0 << endl << "LWPOLYLINE" << endl; + *this << 8 << endl; emit_layer (m_layer); + *this << 90 << endl << polygon.contour (0).size () << endl; + *this << 70 << endl << 1 << endl; + *this << 43 << endl << 0.0 << endl; + for (db::Polygon::polygon_contour_iterator p = polygon.begin_hull (); p != polygon.end_hull (); ++p) { + *this << 10 << endl << (*p).x () * sf << endl; + *this << 20 << endl << (*p).y () * sf << endl; } } else if (m_options.polygon_mode == 2) { - if (polygon.holes () > 0) { - - // resolve holes or merge polygon as a preparation step for split_polygon which only works properly - // on merged polygons ... - std::vector polygons; - - db::EdgeProcessor ep; - ep.insert_sequence (polygon.begin_edge ()); - db::PolygonContainer pc (polygons); - db::PolygonGenerator out (pc, true /*resolve holes*/, false /*min coherence for splitting*/); - db::SimpleMerge op; - ep.process (out, op); - - for (std::vector::const_iterator p = polygons.begin (); p != polygons.end (); ++p) { - write_polygon (*p, sf); - } - - } else if (polygon.vertices () > 4) { + if (polygon.vertices () > 4) { std::vector polygons; db::split_polygon (polygon, polygons); diff --git a/src/plugins/streamers/dxf/unit_tests/dbDXFWriterTests.cc b/src/plugins/streamers/dxf/unit_tests/dbDXFWriterTests.cc new file mode 100644 index 000000000..c5e432737 --- /dev/null +++ b/src/plugins/streamers/dxf/unit_tests/dbDXFWriterTests.cc @@ -0,0 +1,247 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2022 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "dbReader.h" +#include "dbRegion.h" +#include "dbDXFWriter.h" +#include "dbDXFFormat.h" +#include "dbTestSupport.h" +#include "dbRecursiveShapeIterator.h" +#include "tlUnitTest.h" +#include "tlStream.h" + +#include + +static void do_run_test (tl::TestBase *_this, db::Layout &layout, const std::string &fn_au, const db::DXFWriterOptions &opt) +{ + std::string tmp = _this->tmp_file ("tmp.dxf"); + + db::SaveLayoutOptions options; + options.set_options (new db::DXFWriterOptions (opt)); + options.set_format ("DXF"); + + { + tl::OutputStream stream (tmp); + db::Writer writer (options); + writer.write (layout, stream); + } + + _this->compare_text_files (tmp, fn_au); +} + +static void run_test (tl::TestBase *_this, const char *file, const char *file_au, const db::DXFWriterOptions &opt = db::DXFWriterOptions ()) +{ + std::string fn = tl::testdata_private () + "/dxf/" + file; + + db::Layout layout; + + { + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (layout); + } + + std::string fn_au = tl::testdata_private () + std::string ("/dxf/") + file_au; + + do_run_test (_this, layout, fn_au, opt); +} + +static void run_test_public (tl::TestBase *_this, const char *file, const char *file_au, const db::DXFWriterOptions &opt = db::DXFWriterOptions ()) +{ + std::string fn = tl::testdata () + "/dxf/" + file; + std::string fn_au = tl::testdata () + std::string ("/dxf/") + file_au; + + db::Layout layout; + + { + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (layout); + } + + do_run_test (_this, layout, fn_au, opt); +} + +TEST(Polygons1a) +{ + db::DXFWriterOptions opt; + run_test_public (_this, "dxf1.gds", "dxf1a_au.dxf", opt); +} + +TEST(Polygons1b) +{ + db::DXFWriterOptions opt; + opt.polygon_mode = 1; + run_test_public (_this, "dxf1.gds", "dxf1b_au.dxf", opt); +} + +TEST(Polygons1c) +{ + db::DXFWriterOptions opt; + opt.polygon_mode = 2; + run_test_public (_this, "dxf1.gds", "dxf1c_au.dxf", opt); +} + +TEST(Polygons1d) +{ + db::DXFWriterOptions opt; + opt.polygon_mode = 3; + run_test_public (_this, "dxf1.gds", "dxf1d_au.dxf", opt); +} + +TEST(Polygons1e) +{ + db::DXFWriterOptions opt; + opt.polygon_mode = 4; + run_test_public (_this, "dxf1.gds", "dxf1e_au.dxf", opt); +} + +TEST(Polygons2) +{ + db::DXFWriterOptions opt; + run_test_public (_this, "dxf2.gds", "dxf2_au.dxf", opt); +} + +TEST(Polygons3) +{ + db::DXFWriterOptions opt; + run_test_public (_this, "dxf3.gds", "dxf3_au.dxf", opt); +} + +TEST(Polygons4a) +{ + db::Layout l; + std::string fn = tl::testdata () + "/dxf/dxf4.gds"; + + { + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (l); + } + + unsigned int l1 = l.get_layer (db::LayerProperties (1, 0)); + unsigned int l2 = l.get_layer (db::LayerProperties (2, 0)); + unsigned int l100 = l.get_layer (db::LayerProperties (100, 0)); + + db::Region r1 = db::Region (db::RecursiveShapeIterator (l, l.cell (*l.begin_top_down ()), l1)); + db::Region r2 = db::Region (db::RecursiveShapeIterator (l, l.cell (*l.begin_top_down ()), l2)); + (r1 ^ r2).insert_into (&l, *l.begin_top_down (), l100); + + db::DXFWriterOptions opt; + do_run_test (_this, l, tl::testdata () + std::string ("/dxf/") + "dxf4a_au.dxf", opt); +} + +TEST(Polygons4b) +{ + db::Layout l; + std::string fn = tl::testdata () + "/dxf/dxf4.gds"; + + { + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (l); + } + + unsigned int l1 = l.get_layer (db::LayerProperties (1, 0)); + unsigned int l2 = l.get_layer (db::LayerProperties (2, 0)); + unsigned int l100 = l.get_layer (db::LayerProperties (100, 0)); + + db::Region r1 = db::Region (db::RecursiveShapeIterator (l, l.cell (*l.begin_top_down ()), l1)); + db::Region r2 = db::Region (db::RecursiveShapeIterator (l, l.cell (*l.begin_top_down ()), l2)); + (r1 ^ r2).insert_into (&l, *l.begin_top_down (), l100); + + db::DXFWriterOptions opt; + opt.polygon_mode = 1; + do_run_test (_this, l, tl::testdata () + std::string ("/dxf/") + "dxf4b_au.dxf", opt); +} + +TEST(Polygons4c) +{ + db::Layout l; + std::string fn = tl::testdata () + "/dxf/dxf4.gds"; + + { + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (l); + } + + unsigned int l1 = l.get_layer (db::LayerProperties (1, 0)); + unsigned int l2 = l.get_layer (db::LayerProperties (2, 0)); + unsigned int l100 = l.get_layer (db::LayerProperties (100, 0)); + + db::Region r1 = db::Region (db::RecursiveShapeIterator (l, l.cell (*l.begin_top_down ()), l1)); + db::Region r2 = db::Region (db::RecursiveShapeIterator (l, l.cell (*l.begin_top_down ()), l2)); + (r1 ^ r2).insert_into (&l, *l.begin_top_down (), l100); + + db::DXFWriterOptions opt; + opt.polygon_mode = 2; + do_run_test (_this, l, tl::testdata () + std::string ("/dxf/") + "dxf4c_au.dxf", opt); +} + +TEST(Polygons4d) +{ + db::Layout l; + std::string fn = tl::testdata () + "/dxf/dxf4.gds"; + + { + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (l); + } + + unsigned int l1 = l.get_layer (db::LayerProperties (1, 0)); + unsigned int l2 = l.get_layer (db::LayerProperties (2, 0)); + unsigned int l100 = l.get_layer (db::LayerProperties (100, 0)); + + db::Region r1 = db::Region (db::RecursiveShapeIterator (l, l.cell (*l.begin_top_down ()), l1)); + db::Region r2 = db::Region (db::RecursiveShapeIterator (l, l.cell (*l.begin_top_down ()), l2)); + (r1 ^ r2).insert_into (&l, *l.begin_top_down (), l100); + + db::DXFWriterOptions opt; + opt.polygon_mode = 3; + do_run_test (_this, l, tl::testdata () + std::string ("/dxf/") + "dxf4d_au.dxf", opt); +} + +TEST(Polygons4e) +{ + db::Layout l; + std::string fn = tl::testdata () + "/dxf/dxf4.gds"; + + { + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (l); + } + + unsigned int l1 = l.get_layer (db::LayerProperties (1, 0)); + unsigned int l2 = l.get_layer (db::LayerProperties (2, 0)); + unsigned int l100 = l.get_layer (db::LayerProperties (100, 0)); + + db::Region r1 = db::Region (db::RecursiveShapeIterator (l, l.cell (*l.begin_top_down ()), l1)); + db::Region r2 = db::Region (db::RecursiveShapeIterator (l, l.cell (*l.begin_top_down ()), l2)); + (r1 ^ r2).insert_into (&l, *l.begin_top_down (), l100); + + db::DXFWriterOptions opt; + opt.polygon_mode = 4; + do_run_test (_this, l, tl::testdata () + std::string ("/dxf/") + "dxf4e_au.dxf", opt); +} diff --git a/src/plugins/streamers/dxf/unit_tests/unit_tests.pro b/src/plugins/streamers/dxf/unit_tests/unit_tests.pro index 98d781faa..6ffe9f557 100644 --- a/src/plugins/streamers/dxf/unit_tests/unit_tests.pro +++ b/src/plugins/streamers/dxf/unit_tests/unit_tests.pro @@ -6,7 +6,8 @@ TARGET = dxf_tests include($$PWD/../../../../lib_ut.pri) SOURCES = \ - dbDXFReaderTests.cc + dbDXFReaderTests.cc \ + dbDXFWriterTests.cc INCLUDEPATH += $$LAY_INC $$TL_INC $$DB_INC $$GSI_INC $$PWD/../db_plugin $$PWD/../../../common DEPENDPATH += $$LAY_INC $$TL_INC $$DB_INC $$GSI_INC $$PWD/../db_plugin $$PWD/../../../common diff --git a/testdata/dxf/dxf1.gds b/testdata/dxf/dxf1.gds new file mode 100644 index 0000000000000000000000000000000000000000..116d49af414aee8e4602c70a52f76e28c317bb31 GIT binary patch literal 186 zcmZQzV_;&6V31*CVt>ZK%)rSY%Am=hh0JE)U}E#}bYfr-VP>^+>@@d2w)}&o%MSeo zv!g;7WLWX&V`B^P4`5(m;b353<7HxCWMJcCVqjpf5nu+ANPuwy0|U?v3^1By0|Nu| TC!j_sjii)C5Gc$J#4HQ|_~{is literal 0 HcmV?d00001 diff --git a/testdata/dxf/dxf1a_au.dxf b/testdata/dxf/dxf1a_au.dxf new file mode 100644 index 000000000..780a89a40 --- /dev/null +++ b/testdata/dxf/dxf1a_au.dxf @@ -0,0 +1,110 @@ +0 +SECTION +2 +HEADER +9 +$ACADVER +1 +AC1006 +0 +ENDSEC +0 +SECTION +2 +TABLES +0 +TABLE +2 +LAYER +70 +1 +0 +LAYER +70 +0 +62 +1 +6 +CONTINUOUS +2 +L1D0 +0 +ENDTAB +0 +ENDSEC +0 +SECTION +2 +BLOCKS +0 +ENDSEC +0 +SECTION +2 +ENTITIES +0 +POLYLINE +8 +L1D0 +70 +1 +40 +0 +41 +0 +66 +1 +0 +VERTEX +8 +L1D0 +10 +0 +20 +0 +0 +VERTEX +8 +L1D0 +10 +0 +20 +0.4 +0 +VERTEX +8 +L1D0 +10 +0.6 +20 +0.4 +0 +VERTEX +8 +L1D0 +10 +0.6 +20 +1.2 +0 +VERTEX +8 +L1D0 +10 +1.01 +20 +1.2 +0 +VERTEX +8 +L1D0 +10 +1.01 +20 +0 +0 +SEQEND +0 +ENDSEC +0 +EOF diff --git a/testdata/dxf/dxf1b_au.dxf b/testdata/dxf/dxf1b_au.dxf new file mode 100644 index 000000000..4a30cee2b --- /dev/null +++ b/testdata/dxf/dxf1b_au.dxf @@ -0,0 +1,82 @@ +0 +SECTION +2 +HEADER +9 +$ACADVER +1 +AC1006 +0 +ENDSEC +0 +SECTION +2 +TABLES +0 +TABLE +2 +LAYER +70 +1 +0 +LAYER +70 +0 +62 +1 +6 +CONTINUOUS +2 +L1D0 +0 +ENDTAB +0 +ENDSEC +0 +SECTION +2 +BLOCKS +0 +ENDSEC +0 +SECTION +2 +ENTITIES +0 +LWPOLYLINE +8 +L1D0 +90 +6 +70 +1 +43 +0 +10 +0 +20 +0 +10 +0 +20 +0.4 +10 +0.6 +20 +0.4 +10 +0.6 +20 +1.2 +10 +1.01 +20 +1.2 +10 +1.01 +20 +0 +0 +ENDSEC +0 +EOF diff --git a/testdata/dxf/dxf1c_au.dxf b/testdata/dxf/dxf1c_au.dxf new file mode 100644 index 000000000..f8c798346 --- /dev/null +++ b/testdata/dxf/dxf1c_au.dxf @@ -0,0 +1,88 @@ +0 +SECTION +2 +HEADER +9 +$ACADVER +1 +AC1006 +0 +ENDSEC +0 +SECTION +2 +TABLES +0 +TABLE +2 +LAYER +70 +1 +0 +LAYER +70 +0 +62 +1 +6 +CONTINUOUS +2 +L1D0 +0 +ENDTAB +0 +ENDSEC +0 +SECTION +2 +BLOCKS +0 +ENDSEC +0 +SECTION +2 +ENTITIES +0 +SOLID +8 +L1D0 +10 +0 +20 +0 +11 +0 +21 +0.4 +12 +1.01 +22 +0 +13 +1.01 +23 +0.4 +0 +SOLID +8 +L1D0 +10 +0.6 +20 +0.4 +11 +0.6 +21 +1.2 +12 +1.01 +22 +0.4 +13 +1.01 +23 +1.2 +0 +ENDSEC +0 +EOF diff --git a/testdata/dxf/dxf1d_au.dxf b/testdata/dxf/dxf1d_au.dxf new file mode 100644 index 000000000..14a8b79a3 --- /dev/null +++ b/testdata/dxf/dxf1d_au.dxf @@ -0,0 +1,88 @@ +0 +SECTION +2 +HEADER +9 +$ACADVER +1 +AC1006 +0 +ENDSEC +0 +SECTION +2 +TABLES +0 +TABLE +2 +LAYER +70 +1 +0 +LAYER +70 +0 +62 +1 +6 +CONTINUOUS +2 +L1D0 +0 +ENDTAB +0 +ENDSEC +0 +SECTION +2 +BLOCKS +0 +ENDSEC +0 +SECTION +2 +ENTITIES +0 +HATCH +8 +L1D0 +70 +1 +91 +1 +92 +3 +72 +0 +73 +1 +93 +6 +10 +0 +20 +0 +10 +0 +20 +0.4 +10 +0.6 +20 +0.4 +10 +0.6 +20 +1.2 +10 +1.01 +20 +1.2 +10 +1.01 +20 +0 +0 +ENDSEC +0 +EOF diff --git a/testdata/dxf/dxf1e_au.dxf b/testdata/dxf/dxf1e_au.dxf new file mode 100644 index 000000000..05853ba02 --- /dev/null +++ b/testdata/dxf/dxf1e_au.dxf @@ -0,0 +1,132 @@ +0 +SECTION +2 +HEADER +9 +$ACADVER +1 +AC1006 +0 +ENDSEC +0 +SECTION +2 +TABLES +0 +TABLE +2 +LAYER +70 +1 +0 +LAYER +70 +0 +62 +1 +6 +CONTINUOUS +2 +L1D0 +0 +ENDTAB +0 +ENDSEC +0 +SECTION +2 +BLOCKS +0 +ENDSEC +0 +SECTION +2 +ENTITIES +0 +LINE +8 +L1D0 +66 +1 +10 +0 +20 +0 +11 +0 +21 +0.4 +0 +LINE +8 +L1D0 +66 +1 +10 +0 +20 +0.4 +11 +0.6 +21 +0.4 +0 +LINE +8 +L1D0 +66 +1 +10 +0.6 +20 +0.4 +11 +0.6 +21 +1.2 +0 +LINE +8 +L1D0 +66 +1 +10 +0.6 +20 +1.2 +11 +1.01 +21 +1.2 +0 +LINE +8 +L1D0 +66 +1 +10 +1.01 +20 +1.2 +11 +1.01 +21 +0 +0 +LINE +8 +L1D0 +66 +1 +10 +1.01 +20 +0 +11 +0 +21 +0 +0 +ENDSEC +0 +EOF diff --git a/testdata/dxf/dxf2.gds b/testdata/dxf/dxf2.gds new file mode 100644 index 0000000000000000000000000000000000000000..8930f2d65e057b6854302a01eb79c7be38123606 GIT binary patch literal 168 zcmZQzV_;&6V31*CVt>ZK%)rSY%Am!-j?8A@U}E#}bYfr-VP>^+>@@d2w)}&o%MSeo zv!g;7WLWX&V`B^P4`5(m;bdT7<7HxCWMJcCVgRxgK_mx1GmuPSkP!gUCm@*l1%d{$ NSpZK%)rSY%Amu*jm&1?U}E#}bYfr-VP>^+>@@d2w)}&o%MSeo zv!g;7WLR6a}?~Lzszy3FvPbkRI+G V3=FIl5SrHmq5{Z=xq^j-0RV;<8ZK%)rSY%Amtwfy`#$U}E#}bYfr-VP>^+>@@d2w)}&o%MSeo zv!g;7WLWX&V`B^P4`5(m;b353<7HxCWMJcCVqjp<5nu+ANPx`&r~nBgNwEkb^fMvr fXPm&mz}N=FK$>L(5br|JFmWIa*3S-