From 40267dddb62643f7e574302783b72e83385ab554 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 17 Jan 2023 19:07:32 +0100 Subject: [PATCH] WIP: polygons to edges with properties --- src/db/db/dbAsIfFlatEdges.cc | 7 ++- src/db/db/dbAsIfFlatRegion.cc | 15 ++++-- src/db/db/dbDeepEdges.cc | 9 +++- src/db/db/dbDeepEdges.h | 2 +- src/db/db/dbFlatEdges.cc | 12 +++-- src/db/db/dbFlatEdges.h | 2 +- src/db/db/dbMutableEdges.cc | 59 ++++++++++++++++++--- src/db/db/dbMutableEdges.h | 18 +++++-- src/db/unit_tests/dbAsIfFlatRegionTests.cc | 54 +++++++++++++++++++ src/db/unit_tests/dbDeepRegionTests.cc | 56 +++++++++++++++++++ testdata/algo/deep_region_au41.gds | Bin 0 -> 8414 bytes testdata/algo/flat_region_au41.gds | Bin 0 -> 12942 bytes 12 files changed, 209 insertions(+), 25 deletions(-) create mode 100644 testdata/algo/deep_region_au41.gds create mode 100644 testdata/algo/flat_region_au41.gds diff --git a/src/db/db/dbAsIfFlatEdges.cc b/src/db/db/dbAsIfFlatEdges.cc index 40f0ae8d3..76498ca37 100644 --- a/src/db/db/dbAsIfFlatEdges.cc +++ b/src/db/db/dbAsIfFlatEdges.cc @@ -960,10 +960,15 @@ AsIfFlatEdges::insert_into (Layout *layout, db::cell_index_type into_cell, unsig { // improves performance when inserting an original layout into the same layout db::LayoutLocker locker (layout); + db::PropertyMapper pm (&layout->properties_repository (), properties_repository ()); db::Shapes &shapes = layout->cell (into_cell).shapes (into_layer); for (EdgesIterator e (begin ()); ! e.at_end (); ++e) { - shapes.insert (*e); + if (e.prop_id () != 0) { + shapes.insert (db::EdgeWithProperties (*e, pm (e.prop_id ()))); + } else { + shapes.insert (*e); + } } } diff --git a/src/db/db/dbAsIfFlatRegion.cc b/src/db/db/dbAsIfFlatRegion.cc index 939588633..7042bb121 100644 --- a/src/db/db/dbAsIfFlatRegion.cc +++ b/src/db/db/dbAsIfFlatRegion.cc @@ -130,6 +130,7 @@ EdgesDelegate * AsIfFlatRegion::edges (const EdgeFilterBase *filter) const { std::unique_ptr result (new FlatEdges ()); + db::PropertyMapper pm (result->properties_repository (), properties_repository ()); size_t n = 0; for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) { @@ -138,10 +139,11 @@ AsIfFlatRegion::edges (const EdgeFilterBase *filter) const result->reserve (n); for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) { + db::properties_id_type prop_id = p.prop_id (); for (db::Polygon::polygon_edge_iterator e = p->begin_edge (); ! e.at_end (); ++e) { if (! filter || filter->selected (*e)) { - if (p.prop_id () != 0) { - result->insert (db::EdgeWithProperties (*e, p.prop_id ())); + if (prop_id != 0) { + result->insert (db::EdgeWithProperties (*e, pm (prop_id))); } else { result->insert (*e); } @@ -1707,7 +1709,7 @@ AsIfFlatRegion::xor_with (const Region &other, PropertyConstraint prop_constrain } RegionDelegate * -AsIfFlatRegion::or_with (const Region &other, PropertyConstraint prop_constraint) const +AsIfFlatRegion::or_with (const Region &other, PropertyConstraint /*prop_constraint*/) const { if (empty () && ! other.strict_handling ()) { @@ -1807,10 +1809,15 @@ AsIfFlatRegion::insert_into (Layout *layout, db::cell_index_type into_cell, unsi { // improves performance when inserting an original layout into the same layout db::LayoutLocker locker (layout); + db::PropertyMapper pm (&layout->properties_repository (), properties_repository ()); db::Shapes &shapes = layout->cell (into_cell).shapes (into_layer); for (RegionIterator p (begin ()); ! p.at_end (); ++p) { - shapes.insert (*p); + if (p.prop_id () != 0) { + shapes.insert (db::PolygonWithProperties (*p, pm (p.prop_id ()))); + } else { + shapes.insert (*p); + } } } diff --git a/src/db/db/dbDeepEdges.cc b/src/db/db/dbDeepEdges.cc index 489efcc19..499096a24 100644 --- a/src/db/db/dbDeepEdges.cc +++ b/src/db/db/dbDeepEdges.cc @@ -219,12 +219,17 @@ void DeepEdges::merged_semantics_changed () // .. nothing yet .. } -void DeepEdges::do_insert (const db::Edge &edge) +void DeepEdges::do_insert (const db::Edge &edge, db::properties_id_type prop_id) { db::Layout &layout = deep_layer ().layout (); if (layout.begin_top_down () != layout.end_top_down ()) { db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); - top_cell.shapes (deep_layer ().layer ()).insert (edge); + db::Shapes &shapes = top_cell.shapes (deep_layer ().layer ()); + if (prop_id == 0) { + shapes.insert (edge); + } else { + shapes.insert (db::EdgeWithProperties (edge, prop_id)); + } } invalidate_bbox (); diff --git a/src/db/db/dbDeepEdges.h b/src/db/db/dbDeepEdges.h index 0e252b4e5..7944e1b57 100644 --- a/src/db/db/dbDeepEdges.h +++ b/src/db/db/dbDeepEdges.h @@ -62,7 +62,7 @@ public: virtual void reserve (size_t n); - virtual void do_insert (const db::Edge &edge); + virtual void do_insert (const db::Edge &edge, properties_id_type prop_id); EdgesDelegate *clone () const; diff --git a/src/db/db/dbFlatEdges.cc b/src/db/db/dbFlatEdges.cc index ec0023b0c..e3c9dfa99 100644 --- a/src/db/db/dbFlatEdges.cc +++ b/src/db/db/dbFlatEdges.cc @@ -88,7 +88,8 @@ void FlatEdges::init () void FlatEdges::insert_into (Layout *layout, db::cell_index_type into_cell, unsigned int into_layer) const { - layout->cell (into_cell).shapes (into_layer).insert (*mp_edges); + db::PropertyMapper pm (&layout->properties_repository (), mp_properties_repository.get_const ()); + layout->cell (into_cell).shapes (into_layer).insert (*mp_edges, pm); } void FlatEdges::merged_semantics_changed () @@ -355,11 +356,16 @@ const db::PropertiesRepository *FlatEdges::properties_repository () const } void -FlatEdges::do_insert (const db::Edge &edge) +FlatEdges::do_insert (const db::Edge &edge, db::properties_id_type prop_id) { m_is_merged = empty (); - mp_edges->insert (edge); + if (prop_id == 0) { + mp_edges->insert (edge); + } else { + mp_edges->insert (db::EdgeWithProperties (edge, prop_id)); + } + invalidate_cache (); } diff --git a/src/db/db/dbFlatEdges.h b/src/db/db/dbFlatEdges.h index 538099130..ac24652d9 100644 --- a/src/db/db/dbFlatEdges.h +++ b/src/db/db/dbFlatEdges.h @@ -96,7 +96,7 @@ public: virtual db::PropertiesRepository *properties_repository (); virtual const db::PropertiesRepository *properties_repository () const; - void do_insert (const db::Edge &edge); + void do_insert (const db::Edge &edge, properties_id_type prop_id); void do_transform (const db::Trans &t) { diff --git a/src/db/db/dbMutableEdges.cc b/src/db/db/dbMutableEdges.cc index 4c4e6166c..2ce7efc41 100644 --- a/src/db/db/dbMutableEdges.cc +++ b/src/db/db/dbMutableEdges.cc @@ -50,10 +50,21 @@ void MutableEdges::insert (const db::Box &box) { if (! box.empty () && box.width () > 0 && box.height () > 0) { - do_insert (db::Edge (box.lower_left (), box.upper_left ())); - do_insert (db::Edge (box.upper_left (), box.upper_right ())); - do_insert (db::Edge (box.upper_right (), box.lower_right ())); - do_insert (db::Edge (box.lower_right (), box.lower_left ())); + do_insert (db::Edge (box.lower_left (), box.upper_left ()), 0); + do_insert (db::Edge (box.upper_left (), box.upper_right ()), 0); + do_insert (db::Edge (box.upper_right (), box.lower_right ()), 0); + do_insert (db::Edge (box.lower_right (), box.lower_left ()), 0); + } +} + +void +MutableEdges::insert (const db::BoxWithProperties &box) +{ + if (! box.empty () && box.width () > 0 && box.height () > 0) { + do_insert (db::Edge (box.lower_left (), box.upper_left ()), box.properties_id ()); + do_insert (db::Edge (box.upper_left (), box.upper_right ()), box.properties_id ()); + do_insert (db::Edge (box.upper_right (), box.lower_right ()), box.properties_id ()); + do_insert (db::Edge (box.lower_right (), box.lower_left ()), box.properties_id ()); } } @@ -65,12 +76,30 @@ MutableEdges::insert (const db::Path &path) } } +void +MutableEdges::insert (const db::PathWithProperties &path) +{ + if (path.points () > 0) { + insert (db::PolygonWithProperties (path.polygon (), path.properties_id ())); + } +} + void MutableEdges::insert (const db::Polygon &polygon) { if (polygon.holes () > 0 || polygon.vertices () > 0) { for (db::Polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { - do_insert (*e); + do_insert (*e, 0); + } + } +} + +void +MutableEdges::insert (const db::PolygonWithProperties &polygon) +{ + if (polygon.holes () > 0 || polygon.vertices () > 0) { + for (db::Polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { + do_insert (*e, polygon.properties_id ()); } } } @@ -80,7 +109,17 @@ MutableEdges::insert (const db::SimplePolygon &polygon) { if (polygon.vertices () > 0) { for (db::SimplePolygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { - do_insert (*e); + do_insert (*e, 0); + } + } +} + +void +MutableEdges::insert (const db::SimplePolygonWithProperties &polygon) +{ + if (polygon.vertices () > 0) { + for (db::SimplePolygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { + do_insert (*e, polygon.properties_id ()); } } } @@ -88,17 +127,21 @@ MutableEdges::insert (const db::SimplePolygon &polygon) void MutableEdges::insert (const db::Shape &shape) { + db::properties_id_type prop_id = shape.prop_id (); + if (shape.is_polygon () || shape.is_path () || shape.is_box ()) { db::Polygon poly; shape.polygon (poly); - insert (poly); + for (auto e = poly.begin_edge (); ! e.at_end (); ++e) { + do_insert (*e, prop_id); + } } else if (shape.is_edge ()) { db::Edge edge; shape.edge (edge); - do_insert (edge); + do_insert (edge, prop_id); } } diff --git a/src/db/db/dbMutableEdges.h b/src/db/db/dbMutableEdges.h index 206161e36..40b097999 100644 --- a/src/db/db/dbMutableEdges.h +++ b/src/db/db/dbMutableEdges.h @@ -54,7 +54,7 @@ public: virtual void reserve (size_t n) = 0; - virtual void do_insert (const db::Edge &edge) = 0; + virtual void do_insert (const db::Edge &edge, db::properties_id_type prop_id) = 0; void transform (const db::UnitTrans &) { } void transform (const db::Disp &t) { do_transform (db::Trans (t)); } @@ -63,29 +63,37 @@ public: void transform (const db::IMatrix2d &t) { do_transform (t); } void transform (const db::IMatrix3d &t) { do_transform (t); } - void insert (const db::Edge &edge) { do_insert (edge); } + void insert (const db::Edge &edge) { do_insert (edge, 0); } + void insert (const db::EdgeWithProperties &edge) { do_insert (edge, edge.properties_id ()); } void insert (const db::Box &box); + void insert (const db::BoxWithProperties &box); void insert (const db::Path &path); + void insert (const db::PathWithProperties &path); void insert (const db::SimplePolygon &polygon); + void insert (const db::SimplePolygonWithProperties &polygon); void insert (const db::Polygon &polygon); + void insert (const db::PolygonWithProperties &polygon); void insert (const db::Shape &shape); template void insert (const db::Shape &shape, const T &trans) { + db::properties_id_type prop_id = shape.prop_id (); + if (shape.is_polygon () || shape.is_path () || shape.is_box ()) { db::Polygon poly; shape.polygon (poly); - poly.transform (trans); - insert (poly); + for (auto e = poly.begin_edge (); ! e.at_end (); ++e) { + do_insert ((*e).transformed (trans), prop_id); + } } else if (shape.is_edge ()) { db::Edge edge; shape.edge (edge); edge.transform (trans); - insert (edge); + do_insert (edge, prop_id); } } diff --git a/src/db/unit_tests/dbAsIfFlatRegionTests.cc b/src/db/unit_tests/dbAsIfFlatRegionTests.cc index 9c5549d75..5e6ab2f60 100644 --- a/src/db/unit_tests/dbAsIfFlatRegionTests.cc +++ b/src/db/unit_tests/dbAsIfFlatRegionTests.cc @@ -1716,3 +1716,57 @@ TEST(40_BoolWithProperties) db::compare_layouts (_this, target, tl::testdata () + "/algo/flat_region_au40.gds"); } +TEST(41_EdgesWithProperties) +{ + db::Layout ly; + { + std::string fn (tl::testdata ()); + fn += "/algo/deep_region_40.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::cell_index_type top_cell_index = *ly.begin_top_down (); + db::Cell &top_cell = ly.cell (top_cell_index); + + unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0)); + unsigned int l2 = ly.get_layer (db::LayerProperties (2, 0)); + unsigned int l3 = ly.get_layer (db::LayerProperties (3, 0)); // empty + + db::RecursiveShapeIterator si1 (ly, top_cell, l1); + si1.shape_flags (db::ShapeIterator::All | db::ShapeIterator::RegardProperties); + db::Region r1wp (si1); + db::Region r1wp_nomerge = r1wp; + r1wp_nomerge.set_merged_semantics (false); + + si1.shape_flags (db::ShapeIterator::All); + db::Region r1 (si1); + + db::RecursiveShapeIterator si2 (ly, top_cell, l2); + si2.shape_flags (db::ShapeIterator::All | db::ShapeIterator::RegardProperties); + db::Region r2wp (si2); + db::Region r2wp_nomerge = r2wp; + r2wp_nomerge.set_merged_semantics (false); + + si2.shape_flags (db::ShapeIterator::All); + db::Region r2 (si2); + + db::Layout target; + unsigned int target_top_cell_index = target.add_cell (ly.cell_name (top_cell_index)); + + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (1, 0)), r1wp); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (2, 0)), r2wp); + + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (10, 0)), r1.edges ()); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (11, 0)), r1wp.edges ()); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (12, 0)), r1wp_nomerge.edges ()); + + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (20, 0)), r2.edges ()); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (21, 0)), r2wp.edges ()); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (22, 0)), r2wp_nomerge.edges ()); + + CHECKPOINT(); + db::compare_layouts (_this, target, tl::testdata () + "/algo/flat_region_au41.gds"); +} + diff --git a/src/db/unit_tests/dbDeepRegionTests.cc b/src/db/unit_tests/dbDeepRegionTests.cc index 1d162d43a..8d9ae2c2e 100644 --- a/src/db/unit_tests/dbDeepRegionTests.cc +++ b/src/db/unit_tests/dbDeepRegionTests.cc @@ -2204,6 +2204,62 @@ TEST(40_BoolWithProperties) db::compare_layouts (_this, target, tl::testdata () + "/algo/deep_region_au40.gds"); } +TEST(41_EdgesWithProperties) +{ + db::Layout ly; + { + std::string fn (tl::testdata ()); + fn += "/algo/deep_region_40.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::cell_index_type top_cell_index = *ly.begin_top_down (); + db::Cell &top_cell = ly.cell (top_cell_index); + + db::DeepShapeStore dss; + + unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0)); + unsigned int l2 = ly.get_layer (db::LayerProperties (2, 0)); + unsigned int l3 = ly.get_layer (db::LayerProperties (3, 0)); // empty + + db::RecursiveShapeIterator si1 (ly, top_cell, l1); + si1.shape_flags (db::ShapeIterator::All | db::ShapeIterator::RegardProperties); + db::Region r1wp (si1, dss); + db::Region r1wp_nomerge = r1wp; + r1wp_nomerge.set_merged_semantics (false); + + si1.shape_flags (db::ShapeIterator::All); + db::Region r1 (si1, dss); + + db::RecursiveShapeIterator si2 (ly, top_cell, l2); + si2.shape_flags (db::ShapeIterator::All | db::ShapeIterator::RegardProperties); + db::Region r2wp (si2, dss); + db::Region r2wp_nomerge = r2wp; + r2wp_nomerge.set_merged_semantics (false); + + si2.shape_flags (db::ShapeIterator::All); + db::Region r2 (si2, dss); + + db::Layout target; + unsigned int target_top_cell_index = target.add_cell (ly.cell_name (top_cell_index)); + + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (1, 0)), r1wp); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (2, 0)), r2wp); + + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (10, 0)), r1.edges ()); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (11, 0)), r1wp.edges ()); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (12, 0)), r1wp_nomerge.edges ()); + + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (20, 0)), r2.edges ()); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (21, 0)), r2wp.edges ()); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (22, 0)), r2wp_nomerge.edges ()); + + CHECKPOINT(); + db::compare_layouts (_this, target, tl::testdata () + "/algo/deep_region_au41.gds"); +} + TEST(100_Integration) { db::Layout ly; diff --git a/testdata/algo/deep_region_au41.gds b/testdata/algo/deep_region_au41.gds new file mode 100644 index 0000000000000000000000000000000000000000..7e84f976d430944ad49f80108a35805a68dfba78 GIT binary patch literal 8414 zcmb_hziU)M5T5(>a^{ygxkL?zL~No4Ohie9AS75Q2DC^a`3v#~SmY0|5iBe$1T8EC zEi6-5Sc`>aN((#N6e%pkbLPFBcYAZUv-37a!go2|_wDS=?999sGN{*|K(AJx_yslS z;J;P4?SF=LJ==M(4bW}0?ruF_fB5yo@tu30Z@hc6w+p>in3rYQ7C^HNkj>OlJDaTo zz{*?$U}_hj{indrD}ec*0F3-SVzU$A*IoiNmH-GRU*PZZ0Qq~w?0UZLN_Ok!kXGH< zvPrhIUjxkT7kK^%VCEA5WA`zBp2n>^!JH~Rg*M}z>aABCNa^)I;A zcL2tj`>1`0Yv#y2!l>=JA6s2q;{bmVNn`fu0Qq~w(hqCvh23Y0m`pl9WS1#2?X&0& z{ONLpI3kGoZsRNw#|kVN_4ryMhm329j4`rAepO(3*=S^m-1b>h;dm*YkFWxsS6GVY z6_)UPnUj1rUBndyS~rlB3QNQ;VO>)2;+UWyVj5DI4#!vVd4v`4n8H$WDZ&cmlEON@ zXN-BJ*kH_%h=L(Qy5aZ^xD)3eEABYsY>RykRUwZsaX zEQKX%b%M1@HY)3ivstbyrg}v|oSjlZ%wURwc+)5q>^6wno}i%AdBt+^Zd7^Qk#1Wm z*u5vZMo3{HTw9dPiLkiGi7u?Ll(QUR1-h`pQqFIL6*#{^mbu!f3WHe)XQG5S8tZe= zhT@IH>z)ZVoIV?k^+eglpq7rm^>A~JsxUO?B>2HtR}~wKRZ&qeR;P%9H#3)h;L1D! z>TO*+%XS~(HCJ;I?}pCN&0V%w!*`MCUTb?Ve<#7ubTf4E8{WH}6w}(6XAg&-#Xr^$ zif7x!ofF>!=67#bj%iK>AiTN{u)I0I`3nGRZ}D!>AwP#WvNxyA_i(v3*}3qsuy+X` zxP*6cjJ;<7*H+OX4*5C6n09{eM%tavv$Pb^T-1vxnl~Lf65Wi^>d6=Leyp*6IG9f{MZ18-ZpdkDoQc!M2 z{*MCZHe!Me#%LC6vRw{^2q}U+xtyHMC3y$Wv`Nnm_hz(vIhHOw~Q==au z3SMO>Z4jPR6pZI0qQIU+Ot676iWQVtC^m3bM^2WaKun%deH%nZMHG12O;g}qxD!@- T(r$xe=5Hh8CT)D%X*S^>HR4 z4?t9qP=P=o5Qr**Kq3$b1PKU4MFoj1)9=c`EQy}nj7!GL$sW(zxnp(*FN~^n}=__`@<{uzWnHz_SYS*X^ua< zNi>@iHD@QR-kh5d(eqKgIo*=sV6_HvU$F_N?)j4RcUZdIe zN$msCo*xmNeXp|@f93D|LZnuIXItf`mx!MGlSu5#cUbG&mDS(bR{2d{%k(oMv5S48 z-TN!6zq1|Ia&%bNI`W%u}C zPJK+IR>!d|*ZG*0Fy<#Ovmp>>1O)=~?lR#a(vi@4MBimD9e!JI!`b)RT!7 zo4308iGA(6&fnCsFP~84<;k7UIktW)>)HNySXaAVS*NU9e`gD6$qG04BVy$omN!>c ze`h;r>E1DNVqW44G6%7T)a{a5{hjRzt$x=|_dZyh1B)!>9L!B=Jp-ePdj(RmlS=XJ z8$lI!7NkrB8bN8CVhd6xx{aW0ER1*FL~Za}LTPMg3sNQ{jG#1cumvfTZAMTyNsZUTm(`kbB&;MhMMPM-!Pd9{FcdUAZ4=P2&%}LC3U7Aqnx`OP?fvvP|jTr zsLEY-D8~;2s^W(Z<@jMhRs7JQOimfHlF8+Pf@TV~2?djDk_tuMNhp~6m{cfg&V+)w zr%8q4&L$L0&KeZ7;^`xs;}0f(B@|39ODYt3Eumm?TvDOPcQu7YDwYORC6+pr6H5cC z5=$M*iKPKmiKPzZ#L|GO#8QU>?@pjl)P4z{0q>482T=g-j-vqHoj~FL-5oHmIdd@m zh~X?uW^@$5ycQ?`dCgG(^ID()pz6pyBh zi`IfO>_@lM`3LB?gyPX{e+s9>=au4E1kO+>iClfZ5TyFiEydl)5fqQs-X%zxPUi?p z`=M+>N;-B*aW`=U#iQxTM{O?>r25e<#r`;gQt!nn(~SndWqQ{jB{8b#bdR94AI=t} zBw8z_-SapC2xWWjF=aZv;0#S)7^EcH6kX&IRMB$=so9OLgDv*;{x1;mZuFzu2!K-( zH+XbQaoq^cP^h-b{G+uXwbP9zu}43m-|9!Ve<#B!>4Wj;mf{ltXDF1MSAPN}wNtNc zoEjjYDm8#ZIW<5)RcZi-a%zBps?-1u<;25)(#Xbk#Ca)YBAfFqIc1G(IOW7c=UYxZ zbSP6t)m}`2LQzvC6ij`UR5<@&sI?Lbi@T52T}g%A`&}$xn^2G_q6%DzBo&GpCZS;J znWREd+awfBE=wwChGLsgFgY%%P~^LWg2{bJ1|7(&^Fz<_`g6u`Vnwr9Hs*Vk)N*(J^P8}Oil{(g;oH{n3Ds`+wIdyD6 zX+>)4Sch`n^#N6>V;xE&u2SY*A5fKdy+eVqF;Db*!TR*0F(t zuEMEf9R=`x7bpPlJ4Znxj4JReZ8!_uqeKCC-#K#t-*kc{r~^~ literal 0 HcmV?d00001