From 7f148d6c0c07dce0582c62cfd51f10d0dca37f34 Mon Sep 17 00:00:00 2001 From: Thomas Moerwald Date: Mon, 17 Feb 2014 13:00:05 +0100 Subject: [PATCH 1/4] NURBS fitting: fixed trimming curve for surface fitting (parameter was wrong) --- examples/surface/example_nurbs_fitting_surface.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/surface/example_nurbs_fitting_surface.cpp b/examples/surface/example_nurbs_fitting_surface.cpp index 91e61112003..64bc5cdd655 100644 --- a/examples/surface/example_nurbs_fitting_surface.cpp +++ b/examples/surface/example_nurbs_fitting_surface.cpp @@ -73,7 +73,7 @@ main (int argc, char *argv[]) pcd_file = argv[1]; unsigned order (3); - unsigned refinement (6); + unsigned refinement (5); unsigned iterations (10); unsigned mesh_resolution (256); @@ -142,11 +142,11 @@ main (int argc, char *argv[]) // ############################################################################ // fit NURBS curve pcl::on_nurbs::FittingCurve2dAPDM::FitParameter curve_params; - curve_params.addCPsAccuracy = 3e-2; + curve_params.addCPsAccuracy = 5e-2; curve_params.addCPsIteration = 3; curve_params.maxCPs = 200; curve_params.accuracy = 1e-3; - curve_params.iterations = 10000; + curve_params.iterations = 100; curve_params.param.closest_point_resolution = 0; curve_params.param.closest_point_weight = 1.0; From 4a2647826b9f6350a1585a1404fe1b45db3921e6 Mon Sep 17 00:00:00 2001 From: Thomas Moerwald Date: Tue, 18 Feb 2014 13:49:18 +0100 Subject: [PATCH 2/4] Cleaned up code in B-spline fitting example; Added tutorial for trimmed B-spline fitting --- doc/tutorials/content/bspline_fitting.rst | 220 ++++++++++++++++++ .../surface/example_nurbs_fitting_surface.cpp | 153 ++++++------ 2 files changed, 304 insertions(+), 69 deletions(-) create mode 100644 doc/tutorials/content/bspline_fitting.rst diff --git a/doc/tutorials/content/bspline_fitting.rst b/doc/tutorials/content/bspline_fitting.rst new file mode 100644 index 00000000000..23a2be1e37b --- /dev/null +++ b/doc/tutorials/content/bspline_fitting.rst @@ -0,0 +1,220 @@ +.. _bspline_fitting: + +Fitting trimmed B-splines to unordered point clouds +--------------------------------------------------- + +This tutorial explains how to run a B-spline fitting algorithm on a +point-cloud, to obtain a parametric surface representation. +The algorithm consists of the following steps: + +* Choice of the parameters for B-spline surface fitting. + +* Initialize B-spline by using the Principal Component Analysis (PCA). This + assumes that the point-cloud has two main orientations, i.e. that it is roughly planar. + +* Refinement and fitting. + +* Choice of the parameters for B-spline curve fitting. + +* Circular initialization of the B-spline curve. Here we assume that the point-cloud is + compact, i.e. no separated clusters. + +* Triangulation of the trimmed B-spline surface. + +The algorithm applied to the frontal stanford bunny scan (204800 points): + +.. raw:: html + + + +Background +---------- + +Theoretical information on the algorithm can be found in this `report +`_ and in my `PhD thesis +`_. + + +The code +-------- + +The cpp file used in this tutorial can be found in +pcl/examples/surface/example_nurbs_fitting_surface.cpp + +.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp + :language: cpp + :linenos: + +The input file you can find at pcl/test/bunny.pcd + +The explanation +--------------- +Now, let's break down the code piece by piece. Lets start with the choice of the +parameters for B-spline surface fitting: + + +.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp + :language: cpp + :lines: 56-66 + +* *order* is the polynomial order of the B-spline surface. + +* *refinement* is the number of refinement iterations, where for each iteration control-points + are inserted, approximately doubeling the control points in each parametric direction + of the B-spline surface. + +* *iterations* is the number of iterations that are performed after refinement is completed. + +* *mesh_resoluation* the number of vertices in each parametric direction, + used for triangulation of the B-spline surface. + +* *interior_smoothness* is the smoothness of the surface interior. + +* *interior_weight* is the weight for optimization for the surface interior. + +* *boundary_smoothness* is the smoothness of the surface boundary. + +* *boundary_weight* is the weight for optimization for the surface boundary. + +Note, that the boundary in this case is not the trimming curve used later on. +The boundary can be used when a point-set exists that define the boundary. Those points +can be declared in *pcl::on_nurbs::NurbsDataSurface::boundary*. In this case, when the +*boundary_weight* is greater than 0.0, the algorithm tries to align the domain boundaries +to these points. In our example we are trimming the surface anyway, so there is no need +for aligning the boundary. + + +.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp + :language: cpp + :lines: 68-72 + +The command *initNurbsPCABoundingBox* uses PCA to create a coordinate systems, where the principal +eigenvectors point into the direction of the maximum, middle and minimum extension of the point-cloud. +The center of the coordinate system is located at the mean of the points. +To estimate the extension of the B-spline surface domain, a bounding box is computed in the plane formed +by the maximum and middle eigenvectors. That bounding box is used to initialize the B-spline surface with +its minimum number of control points, according to the polynomial degree chosen. + +The surface fitting class *pcl::on_nurbs::FittingSurface* is provided with the point data and the initial +B-spline. + + +.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp + :language: cpp + :lines: 74-80 + +The *on_nurbs* module allows easy conversion between the *ON_NurbsSurface* and the *PolygonMesh* class, +for visualization of the B-spline surfaces. Note that NURBS are a generalisation of B-splines, +and are therefore a valid container for B-splines, with all control-point weights = 1. + + +.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp + :language: cpp + :lines: 82-92 + +At this point of the code we have a B-spline surface with minimal number of control points. +Typically they are not enough to represent finer details of the underlying geometry +of the point-cloud. However, if we increase the control-points to our desired level of detail and +subsequently fit the refined B-spline, we run into problems. For robust fitting B-spline surfaces +the rule is: +"The higher the degree of freedom of the B-spline surface, the closer we have to be to the points to be approximated". + +This is the reason why we iteratively increase the degree of freedom by refinement in both directions (line 85-86), +and fit the B-spline surface to the point-cloud, getting closer to the final solution. + + +.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp + :language: cpp + :lines: 94-102 + +After we reached the final level of refinement, the surface is further fitted to the point-cloud +for a pleasing end result. + +Now that we have the surface fitted to the point-cloud, we want to cut off the overlapping regions of the surface. +To achiev this we project the point-cloud into the parametric domain using the closest points to the B-spline surface. +In this domain of R^2 we perform the weighted B-spline curve fitting, that creates a closed trimming curve that approximately +contains all the points. + +.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp + :language: cpp + :lines: 107-120 + +The topic of curve fitting goes a bit deeper into the thematics of B-splines. Here we assume that you are +familiar with the concept of B-splines, knot vectors, control-points, and so forthe. +Please consider the curve being split into supporting regions which is bound by consecutive knots. +Also note that points that are inside and outside the curve are distinguished. + +* *addCPsAccuracy* the distance of the supporting region of the curve to the closest data points has to be below + this value, otherwise a control point is inserted. + +* *addCPsIteration* inner iterations without inserting control points. + +* *maxCPs* the maximum total number of control-points. + +* *accuracy* the average fitting accuracy of the curve, w.r.t. the supporting regions. + +* *iterations* maximum number of iterations performed. + +* *closest_point_resolution* number of control points that must lie within each supporting region. (0 turns this constraint off) + +* *closest_point_weight* weight for fitting the curve to its closest points. + +* *closest_point_sigma2* threshold for closest points (disregard points that are further away from the curve). + +* *interior_sigma2* threshold for interior points (disregard points that are further away from and lie within the curve). + +* *smooth_concavity* value that leads to inward bending of the curve (0 = no bending; <0 inward bending; >0 outward bending). + +* *smoothness* weight of smoothness term. + + +.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp + :language: cpp + :lines: 122-127 + +The curve is initialised using a minimum number of control points to reprecent a circle, with the center located +at the mean of the point-cloud and the radius of the maximum distance of a point to the center. +Please note that in line 126 interior weighting is enabled. + + +.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp + :language: cpp + :lines: 129-133 + +Similar to the surface fitting approach, the curve is iteratively fitted and refined, as shown in the video. +Note how the curve tends to bend inwards at regions where it is not supported by any points. + + +.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp + :language: cpp + :lines: 136-142 + +After the curve fitting terminated, our geometric representation consists of a B-spline surface and a closed +B-spline curved, defined within the parametric domain of the B-spline surface. This is called trimmed B-spline surface. +In line 140 we can use the trimmed B-spline to create a triangular mesh. +When running this example and switch to wireframe mode (w), you will notice that the triangles are ordered in +a rectangular way, which is a result of the rectangular domain of the surface. + + +Compiling and running the program +--------------------------------- + +Add the following lines to your CMakeLists.txt file: + +.. TODO literalinclude:: sources/greedy_projection/CMakeLists.txt +.. TODO :language: cmake +.. TODO :linenos: + +After you have made the executable, you can run it. Simply do:: + + $ ./pcl_example_nurbs_fitting_surface ../../test/bunny.pcd + +Saving and viewing the result +----------------------------- + +* Saving as triangle mesh into a vtk file + TODO + +* Saving as OpenNURBS file + TODO + diff --git a/examples/surface/example_nurbs_fitting_surface.cpp b/examples/surface/example_nurbs_fitting_surface.cpp index 64bc5cdd655..6e23243e6c1 100644 --- a/examples/surface/example_nurbs_fitting_surface.cpp +++ b/examples/surface/example_nurbs_fitting_surface.cpp @@ -10,54 +10,12 @@ typedef pcl::PointXYZ Point; void -PointCloud2Vector3d (pcl::PointCloud::Ptr cloud, pcl::on_nurbs::vector_vec3d &data) -{ - for (unsigned i = 0; i < cloud->size (); i++) - { - Point &p = cloud->at (i); - if (!pcl_isnan (p.x) && !pcl_isnan (p.y) && !pcl_isnan (p.z)) - data.push_back (Eigen::Vector3d (p.x, p.y, p.z)); - } -} +PointCloud2Vector3d (pcl::PointCloud::Ptr cloud, pcl::on_nurbs::vector_vec3d &data); void -visualizeCurve (ON_NurbsCurve &curve, ON_NurbsSurface &surface, pcl::visualization::PCLVisualizer &viewer) -{ - pcl::PointCloud::Ptr curve_cloud (new pcl::PointCloud); - - pcl::on_nurbs::Triangulation::convertCurve2PointCloud (curve, surface, curve_cloud, 4); - for (std::size_t i = 0; i < curve_cloud->size () - 1; i++) - { - pcl::PointXYZRGB &p1 = curve_cloud->at (i); - pcl::PointXYZRGB &p2 = curve_cloud->at (i + 1); - std::ostringstream os; - os << "line" << i; - viewer.removeShape (os.str ()); - viewer.addLine (p1, p2, 1.0, 0.0, 0.0, os.str ()); - } - - pcl::PointCloud::Ptr curve_cps (new pcl::PointCloud); - for (int i = 0; i < curve.CVCount (); i++) - { - ON_3dPoint p1; - curve.GetCV (i, p1); - - double pnt[3]; - surface.Evaluate (p1.x, p1.y, 0, 3, pnt); - pcl::PointXYZRGB p2; - p2.x = float (pnt[0]); - p2.y = float (pnt[1]); - p2.z = float (pnt[2]); - - p2.r = 255; - p2.g = 0; - p2.b = 0; - - curve_cps->push_back (p2); - } - viewer.removePointCloud ("cloud_cps"); - viewer.addPointCloud (curve_cps, "cloud_cps"); -} +visualizeCurve (ON_NurbsCurve &curve, + ON_NurbsSurface &surface, + pcl::visualization::PCLVisualizer &viewer); int main (int argc, char *argv[]) @@ -72,16 +30,12 @@ main (int argc, char *argv[]) pcd_file = argv[1]; - unsigned order (3); - unsigned refinement (5); - unsigned iterations (10); - unsigned mesh_resolution (256); - - pcl::visualization::PCLVisualizer viewer ("Test: NURBS surface fitting"); + pcl::visualization::PCLVisualizer viewer ("B-spline surface fitting"); viewer.setSize (800, 600); // ############################################################################ // load point cloud + printf (" loading %s\n", pcd_file.c_str ()); pcl::PointCloud::Ptr cloud (new pcl::PointCloud); pcl::PCLPointCloud2 cloud2; @@ -97,27 +51,35 @@ main (int argc, char *argv[]) printf (" %zu points in data set\n", cloud->size ()); // ############################################################################ - // fit NURBS surface + // fit B-spline surface + + // parameters + unsigned order (3); + unsigned refinement (5); + unsigned iterations (10); + unsigned mesh_resolution (256); + + pcl::on_nurbs::FittingSurface::Parameter params; + params.interior_smoothness = 0.2; + params.interior_weight = 1.0; + params.boundary_smoothness = 0.2; + params.boundary_weight = 0.0; + + // initialize printf (" surface fitting ...\n"); ON_NurbsSurface nurbs = pcl::on_nurbs::FittingSurface::initNurbsPCABoundingBox (order, &data); pcl::on_nurbs::FittingSurface fit (&data, nurbs); - // fit.setQuiet (false); + // fit.setQuiet (false); // enable/disable debug output + // mesh for visualization pcl::PolygonMesh mesh; pcl::PointCloud::Ptr mesh_cloud (new pcl::PointCloud); std::vector mesh_vertices; - std::string mesh_id = "mesh_nurbs"; pcl::on_nurbs::Triangulation::convertSurface2PolygonMesh (fit.m_nurbs, mesh, mesh_resolution); viewer.addPolygonMesh (mesh, mesh_id); - pcl::on_nurbs::FittingSurface::Parameter params; - params.interior_smoothness = 0.15; - params.interior_weight = 1.0; - params.boundary_smoothness = 0.15; - params.boundary_weight = 0.0; - - // NURBS refinement + // surface refinement for (unsigned i = 0; i < refinement; i++) { fit.refine (0); @@ -129,7 +91,7 @@ main (int argc, char *argv[]) viewer.spinOnce (); } - // fitting iterations + // surface fitting with final refinement level for (unsigned i = 0; i < iterations; i++) { fit.assemble (params); @@ -140,7 +102,9 @@ main (int argc, char *argv[]) } // ############################################################################ - // fit NURBS curve + // fit B-spline curve + + // parameters pcl::on_nurbs::FittingCurve2dAPDM::FitParameter curve_params; curve_params.addCPsAccuracy = 5e-2; curve_params.addCPsIteration = 3; @@ -151,25 +115,26 @@ main (int argc, char *argv[]) curve_params.param.closest_point_resolution = 0; curve_params.param.closest_point_weight = 1.0; curve_params.param.closest_point_sigma2 = 0.1; - curve_params.param.interior_sigma2 = 0.0001; + curve_params.param.interior_sigma2 = 0.00001; curve_params.param.smooth_concavity = 1.0; curve_params.param.smoothness = 1.0; + // initialisation (circular) + printf (" curve fitting ...\n"); pcl::on_nurbs::NurbsDataCurve2d curve_data; curve_data.interior = data.interior_param; curve_data.interior_weight_function.push_back (true); - ON_NurbsCurve curve_nurbs = pcl::on_nurbs::FittingCurve2dAPDM::initNurbsCurve2D (order, curve_data.interior); + // curve fitting pcl::on_nurbs::FittingCurve2dASDM curve_fit (&curve_data, curve_nurbs); -// curve_fit.setQuiet (false); - - // ############################### FITTING ############################### + // curve_fit.setQuiet (false); // enable/disable debug output curve_fit.fitting (curve_params); visualizeCurve (curve_fit.m_nurbs, fit.m_nurbs, viewer); // ############################################################################ // triangulation of trimmed surface + printf (" triangulate trimmed surface ...\n"); viewer.removePolygonMesh (mesh_id); pcl::on_nurbs::Triangulation::convertTrimmedSurface2PolygonMesh (fit.m_nurbs, curve_fit.m_nurbs, mesh, @@ -181,3 +146,53 @@ main (int argc, char *argv[]) viewer.spin (); return 0; } + +void +PointCloud2Vector3d (pcl::PointCloud::Ptr cloud, pcl::on_nurbs::vector_vec3d &data) +{ + for (unsigned i = 0; i < cloud->size (); i++) + { + Point &p = cloud->at (i); + if (!pcl_isnan (p.x) && !pcl_isnan (p.y) && !pcl_isnan (p.z)) + data.push_back (Eigen::Vector3d (p.x, p.y, p.z)); + } +} + +void +visualizeCurve (ON_NurbsCurve &curve, ON_NurbsSurface &surface, pcl::visualization::PCLVisualizer &viewer) +{ + pcl::PointCloud::Ptr curve_cloud (new pcl::PointCloud); + + pcl::on_nurbs::Triangulation::convertCurve2PointCloud (curve, surface, curve_cloud, 4); + for (std::size_t i = 0; i < curve_cloud->size () - 1; i++) + { + pcl::PointXYZRGB &p1 = curve_cloud->at (i); + pcl::PointXYZRGB &p2 = curve_cloud->at (i + 1); + std::ostringstream os; + os << "line" << i; + viewer.removeShape (os.str ()); + viewer.addLine (p1, p2, 1.0, 0.0, 0.0, os.str ()); + } + + pcl::PointCloud::Ptr curve_cps (new pcl::PointCloud); + for (int i = 0; i < curve.CVCount (); i++) + { + ON_3dPoint p1; + curve.GetCV (i, p1); + + double pnt[3]; + surface.Evaluate (p1.x, p1.y, 0, 3, pnt); + pcl::PointXYZRGB p2; + p2.x = float (pnt[0]); + p2.y = float (pnt[1]); + p2.z = float (pnt[2]); + + p2.r = 255; + p2.g = 0; + p2.b = 0; + + curve_cps->push_back (p2); + } + viewer.removePointCloud ("cloud_cps"); + viewer.addPointCloud (curve_cps, "cloud_cps"); +} From a3aa73542a8388b2e5cc5f620d071dc70aeaafb1 Mon Sep 17 00:00:00 2001 From: Thomas Moerwald Date: Tue, 18 Feb 2014 15:57:23 +0100 Subject: [PATCH 3/4] added saving of trimmed bsplines, added viewer for trimmed bsplines, added saving of bspline in bspline fitting tutorial --- doc/tutorials/content/bspline_fitting.rst | 57 +++-- .../sources/bspline_fitting/CMakeLists.txt | 13 ++ .../bspline_fitting/bspline_fitting.cpp | 219 ++++++++++++++++++ examples/surface/CMakeLists.txt | 4 + .../surface/example_nurbs_fitting_surface.cpp | 29 ++- .../surface/example_nurbs_viewer_surface.cpp | 88 +++++++ 6 files changed, 388 insertions(+), 22 deletions(-) create mode 100644 doc/tutorials/content/sources/bspline_fitting/CMakeLists.txt create mode 100644 doc/tutorials/content/sources/bspline_fitting/bspline_fitting.cpp create mode 100644 examples/surface/example_nurbs_viewer_surface.cpp diff --git a/doc/tutorials/content/bspline_fitting.rst b/doc/tutorials/content/bspline_fitting.rst index 23a2be1e37b..192fbe74882 100644 --- a/doc/tutorials/content/bspline_fitting.rst +++ b/doc/tutorials/content/bspline_fitting.rst @@ -27,6 +27,7 @@ The algorithm applied to the frontal stanford bunny scan (204800 points): + Background ---------- @@ -34,6 +35,13 @@ Theoretical information on the algorithm can be found in this `report `_ and in my `PhD thesis `_. +Please note that the modules for NURBS and B-splines are not enabled by default. +Make sure you enable "BUILD_surface_on_nurbs" by setting it to ON. + +If your license permits also enable "USE_UMFPACK" for sparse linear solving. +This requires SuiteSparse (libsuitesparse-dev in Ubuntu) which is faster, +allows more degrees of freedom (i.e. control points) and more data points. + The code -------- @@ -41,7 +49,7 @@ The code The cpp file used in this tutorial can be found in pcl/examples/surface/example_nurbs_fitting_surface.cpp -.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp +.. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :linenos: @@ -53,7 +61,7 @@ Now, let's break down the code piece by piece. Lets start with the choice of the parameters for B-spline surface fitting: -.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp +.. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :lines: 56-66 @@ -84,7 +92,7 @@ to these points. In our example we are trimming the surface anyway, so there is for aligning the boundary. -.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp +.. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :lines: 68-72 @@ -99,7 +107,7 @@ The surface fitting class *pcl::on_nurbs::FittingSurface* is provided with the p B-spline. -.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp +.. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :lines: 74-80 @@ -108,7 +116,7 @@ for visualization of the B-spline surfaces. Note that NURBS are a generalisation and are therefore a valid container for B-splines, with all control-point weights = 1. -.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp +.. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :lines: 82-92 @@ -123,7 +131,7 @@ This is the reason why we iteratively increase the degree of freedom by refineme and fit the B-spline surface to the point-cloud, getting closer to the final solution. -.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp +.. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :lines: 94-102 @@ -135,7 +143,7 @@ To achiev this we project the point-cloud into the parametric domain using the c In this domain of R^2 we perform the weighted B-spline curve fitting, that creates a closed trimming curve that approximately contains all the points. -.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp +.. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :lines: 107-120 @@ -168,7 +176,7 @@ Also note that points that are inside and outside the curve are distinguished. * *smoothness* weight of smoothness term. -.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp +.. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :lines: 122-127 @@ -177,7 +185,7 @@ at the mean of the point-cloud and the radius of the maximum distance of a point Please note that in line 126 interior weighting is enabled. -.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp +.. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :lines: 129-133 @@ -185,7 +193,7 @@ Similar to the surface fitting approach, the curve is iteratively fitted and ref Note how the curve tends to bend inwards at regions where it is not supported by any points. -.. literalinclude:: ../../../../examples/surface/example_nurbs_fitting_surface.cpp +.. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :lines: 136-142 @@ -201,20 +209,33 @@ Compiling and running the program Add the following lines to your CMakeLists.txt file: -.. TODO literalinclude:: sources/greedy_projection/CMakeLists.txt -.. TODO :language: cmake -.. TODO :linenos: +.. literalinclude:: sources/bspline_fitting/CMakeLists.txt + :language: cmake + :linenos: -After you have made the executable, you can run it. Simply do:: +After you have made the executable, you can run it. Simply do: + + $ ./bspline_fitting ${PCL_ROOT}/test/bunny.pcd - $ ./pcl_example_nurbs_fitting_surface ../../test/bunny.pcd Saving and viewing the result ----------------------------- +* Saving as OpenNURBS (3dm) file +You can save the B-spline surface by using the commands provided by OpenNurbs: + +.. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp + :language: cpp + :lines: 145-163 + +The files generated can be viewed with the pcl/examples/surface/example_nurbs_viewer_surface.cpp. + * Saving as triangle mesh into a vtk file - TODO +You can save the triangle mesh for example by saving into a VTK file by: + + #include + ... + pcl::io::saveVTKFile ("mesh.vtk", mesh); -* Saving as OpenNURBS file - TODO +PCL also provides vtk conversion into other formats (PLY, OBJ). diff --git a/doc/tutorials/content/sources/bspline_fitting/CMakeLists.txt b/doc/tutorials/content/sources/bspline_fitting/CMakeLists.txt new file mode 100644 index 00000000000..332515885ea --- /dev/null +++ b/doc/tutorials/content/sources/bspline_fitting/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.8 FATAL_ERROR) + +project(bspline_fitting) + +find_package(PCL 1.7 REQUIRED) + +include_directories(${PCL_INCLUDE_DIRS}) +link_directories(${PCL_LIBRARY_DIRS}) +add_definitions(${PCL_DEFINITIONS}) + +add_executable (bspline_fitting bspline_fitting.cpp) +target_link_libraries (bspline_fitting ${PCL_LIBRARIES}) + diff --git a/doc/tutorials/content/sources/bspline_fitting/bspline_fitting.cpp b/doc/tutorials/content/sources/bspline_fitting/bspline_fitting.cpp new file mode 100644 index 00000000000..b5628ee64fc --- /dev/null +++ b/doc/tutorials/content/sources/bspline_fitting/bspline_fitting.cpp @@ -0,0 +1,219 @@ +#include +#include +#include + +#include +#include +#include +#include + +typedef pcl::PointXYZ Point; + +void +PointCloud2Vector3d (pcl::PointCloud::Ptr cloud, pcl::on_nurbs::vector_vec3d &data); + +void +visualizeCurve (ON_NurbsCurve &curve, + ON_NurbsSurface &surface, + pcl::visualization::PCLVisualizer &viewer); + +int +main (int argc, char *argv[]) +{ + std::string pcd_file, file_3dm; + + if (argc < 3) + { + printf ("\nUsage: pcl_example_nurbs_fitting_surface pcd-in-file 3dm-out-file\n\n"); + exit (0); + } + pcd_file = argv[1]; + file_3dm = argv[2]; + + pcl::visualization::PCLVisualizer viewer ("B-spline surface fitting"); + viewer.setSize (800, 600); + + // ############################################################################ + // load point cloud + + printf (" loading %s\n", pcd_file.c_str ()); + pcl::PointCloud::Ptr cloud (new pcl::PointCloud); + pcl::PCLPointCloud2 cloud2; + pcl::on_nurbs::NurbsDataSurface data; + + if (pcl::io::loadPCDFile (pcd_file, cloud2) == -1) + throw std::runtime_error (" PCD file not found."); + + fromPCLPointCloud2 (cloud2, *cloud); + PointCloud2Vector3d (cloud, data.interior); + pcl::visualization::PointCloudColorHandlerCustom handler (cloud, 0, 255, 0); + viewer.addPointCloud (cloud, handler, "cloud_cylinder"); + printf (" %zu points in data set\n", cloud->size ()); + + // ############################################################################ + // fit B-spline surface + + // parameters + unsigned order (3); + unsigned refinement (5); + unsigned iterations (10); + unsigned mesh_resolution (256); + + pcl::on_nurbs::FittingSurface::Parameter params; + params.interior_smoothness = 0.2; + params.interior_weight = 1.0; + params.boundary_smoothness = 0.2; + params.boundary_weight = 0.0; + + // initialize + printf (" surface fitting ...\n"); + ON_NurbsSurface nurbs = pcl::on_nurbs::FittingSurface::initNurbsPCABoundingBox (order, &data); + pcl::on_nurbs::FittingSurface fit (&data, nurbs); + // fit.setQuiet (false); // enable/disable debug output + + // mesh for visualization + pcl::PolygonMesh mesh; + pcl::PointCloud::Ptr mesh_cloud (new pcl::PointCloud); + std::vector mesh_vertices; + std::string mesh_id = "mesh_nurbs"; + pcl::on_nurbs::Triangulation::convertSurface2PolygonMesh (fit.m_nurbs, mesh, mesh_resolution); + viewer.addPolygonMesh (mesh, mesh_id); + + // surface refinement + for (unsigned i = 0; i < refinement; i++) + { + fit.refine (0); + fit.refine (1); + fit.assemble (params); + fit.solve (); + pcl::on_nurbs::Triangulation::convertSurface2Vertices (fit.m_nurbs, mesh_cloud, mesh_vertices, mesh_resolution); + viewer.updatePolygonMesh (mesh_cloud, mesh_vertices, mesh_id); + viewer.spinOnce (); + } + + // surface fitting with final refinement level + for (unsigned i = 0; i < iterations; i++) + { + fit.assemble (params); + fit.solve (); + pcl::on_nurbs::Triangulation::convertSurface2Vertices (fit.m_nurbs, mesh_cloud, mesh_vertices, mesh_resolution); + viewer.updatePolygonMesh (mesh_cloud, mesh_vertices, mesh_id); + viewer.spinOnce (); + } + + // ############################################################################ + // fit B-spline curve + + // parameters + pcl::on_nurbs::FittingCurve2dAPDM::FitParameter curve_params; + curve_params.addCPsAccuracy = 5e-2; + curve_params.addCPsIteration = 3; + curve_params.maxCPs = 200; + curve_params.accuracy = 1e-3; + curve_params.iterations = 100; + + curve_params.param.closest_point_resolution = 0; + curve_params.param.closest_point_weight = 1.0; + curve_params.param.closest_point_sigma2 = 0.1; + curve_params.param.interior_sigma2 = 0.00001; + curve_params.param.smooth_concavity = 1.0; + curve_params.param.smoothness = 1.0; + + // initialisation (circular) + printf (" curve fitting ...\n"); + pcl::on_nurbs::NurbsDataCurve2d curve_data; + curve_data.interior = data.interior_param; + curve_data.interior_weight_function.push_back (true); + ON_NurbsCurve curve_nurbs = pcl::on_nurbs::FittingCurve2dAPDM::initNurbsCurve2D (order, curve_data.interior); + + // curve fitting + pcl::on_nurbs::FittingCurve2dASDM curve_fit (&curve_data, curve_nurbs); + // curve_fit.setQuiet (false); // enable/disable debug output + curve_fit.fitting (curve_params); + visualizeCurve (curve_fit.m_nurbs, fit.m_nurbs, viewer); + + // ############################################################################ + // triangulation of trimmed surface + + printf (" triangulate trimmed surface ...\n"); + viewer.removePolygonMesh (mesh_id); + pcl::on_nurbs::Triangulation::convertTrimmedSurface2PolygonMesh (fit.m_nurbs, curve_fit.m_nurbs, mesh, + mesh_resolution); + viewer.addPolygonMesh (mesh, mesh_id); + + + // save trimmed B-spline surface + if ( fit.m_nurbs.IsValid() ) + { + ONX_Model model; + ONX_Model_Object& surf = model.m_object_table.AppendNew(); + surf.m_object = new ON_NurbsSurface(fit.m_nurbs); + surf.m_bDeleteObject = true; + surf.m_attributes.m_layer_index = 1; + surf.m_attributes.m_name = "surface"; + + ONX_Model_Object& curv = model.m_object_table.AppendNew(); + curv.m_object = new ON_NurbsCurve(curve_fit.m_nurbs); + curv.m_bDeleteObject = true; + curv.m_attributes.m_layer_index = 2; + curv.m_attributes.m_name = "trimming curve"; + + model.Write(file_3dm.c_str()); + printf(" model saved: %s\n", file_3dm.c_str()); + } + + printf (" ... done.\n"); + + viewer.spin (); + return 0; +} + +void +PointCloud2Vector3d (pcl::PointCloud::Ptr cloud, pcl::on_nurbs::vector_vec3d &data) +{ + for (unsigned i = 0; i < cloud->size (); i++) + { + Point &p = cloud->at (i); + if (!pcl_isnan (p.x) && !pcl_isnan (p.y) && !pcl_isnan (p.z)) + data.push_back (Eigen::Vector3d (p.x, p.y, p.z)); + } +} + +void +visualizeCurve (ON_NurbsCurve &curve, ON_NurbsSurface &surface, pcl::visualization::PCLVisualizer &viewer) +{ + pcl::PointCloud::Ptr curve_cloud (new pcl::PointCloud); + + pcl::on_nurbs::Triangulation::convertCurve2PointCloud (curve, surface, curve_cloud, 4); + for (std::size_t i = 0; i < curve_cloud->size () - 1; i++) + { + pcl::PointXYZRGB &p1 = curve_cloud->at (i); + pcl::PointXYZRGB &p2 = curve_cloud->at (i + 1); + std::ostringstream os; + os << "line" << i; + viewer.removeShape (os.str ()); + viewer.addLine (p1, p2, 1.0, 0.0, 0.0, os.str ()); + } + + pcl::PointCloud::Ptr curve_cps (new pcl::PointCloud); + for (int i = 0; i < curve.CVCount (); i++) + { + ON_3dPoint p1; + curve.GetCV (i, p1); + + double pnt[3]; + surface.Evaluate (p1.x, p1.y, 0, 3, pnt); + pcl::PointXYZRGB p2; + p2.x = float (pnt[0]); + p2.y = float (pnt[1]); + p2.z = float (pnt[2]); + + p2.r = 255; + p2.g = 0; + p2.b = 0; + + curve_cps->push_back (p2); + } + viewer.removePointCloud ("cloud_cps"); + viewer.addPointCloud (curve_cps, "cloud_cps"); +} diff --git a/examples/surface/CMakeLists.txt b/examples/surface/CMakeLists.txt index 1507f7871a8..9d105618d00 100644 --- a/examples/surface/CMakeLists.txt +++ b/examples/surface/CMakeLists.txt @@ -19,6 +19,10 @@ if(BUILD_surface_on_nurbs) PCL_ADD_EXAMPLE(pcl_example_nurbs_fitting_surface FILES example_nurbs_fitting_surface.cpp LINK_WITH pcl_common pcl_io pcl_surface pcl_visualization) + + PCL_ADD_EXAMPLE(pcl_example_nurbs_viewer_surface + FILES example_nurbs_viewer_surface.cpp + LINK_WITH pcl_common pcl_io pcl_surface pcl_visualization) PCL_ADD_EXAMPLE(pcl_example_nurbs_fitting_closed_curve FILES example_nurbs_fitting_closed_curve.cpp diff --git a/examples/surface/example_nurbs_fitting_surface.cpp b/examples/surface/example_nurbs_fitting_surface.cpp index 6e23243e6c1..b5628ee64fc 100644 --- a/examples/surface/example_nurbs_fitting_surface.cpp +++ b/examples/surface/example_nurbs_fitting_surface.cpp @@ -20,15 +20,15 @@ visualizeCurve (ON_NurbsCurve &curve, int main (int argc, char *argv[]) { - std::string pcd_file; + std::string pcd_file, file_3dm; - if (argc < 2) + if (argc < 3) { - printf ("\nUsage: pcl_example_nurbs_fitting_surface pcd-file\n\n"); + printf ("\nUsage: pcl_example_nurbs_fitting_surface pcd-in-file 3dm-out-file\n\n"); exit (0); } - pcd_file = argv[1]; + file_3dm = argv[2]; pcl::visualization::PCLVisualizer viewer ("B-spline surface fitting"); viewer.setSize (800, 600); @@ -141,6 +141,27 @@ main (int argc, char *argv[]) mesh_resolution); viewer.addPolygonMesh (mesh, mesh_id); + + // save trimmed B-spline surface + if ( fit.m_nurbs.IsValid() ) + { + ONX_Model model; + ONX_Model_Object& surf = model.m_object_table.AppendNew(); + surf.m_object = new ON_NurbsSurface(fit.m_nurbs); + surf.m_bDeleteObject = true; + surf.m_attributes.m_layer_index = 1; + surf.m_attributes.m_name = "surface"; + + ONX_Model_Object& curv = model.m_object_table.AppendNew(); + curv.m_object = new ON_NurbsCurve(curve_fit.m_nurbs); + curv.m_bDeleteObject = true; + curv.m_attributes.m_layer_index = 2; + curv.m_attributes.m_name = "trimming curve"; + + model.Write(file_3dm.c_str()); + printf(" model saved: %s\n", file_3dm.c_str()); + } + printf (" ... done.\n"); viewer.spin (); diff --git a/examples/surface/example_nurbs_viewer_surface.cpp b/examples/surface/example_nurbs_viewer_surface.cpp new file mode 100644 index 00000000000..710cfac9ddc --- /dev/null +++ b/examples/surface/example_nurbs_viewer_surface.cpp @@ -0,0 +1,88 @@ +#include +#include +#include + +#include +#include +#include +#include + +typedef pcl::PointXYZ Point; + +int +main (int argc, char *argv[]) +{ + std::string file_3dm; + + if (argc < 2) + { + printf ("\nUsage: pcl_example_nurbs_viewer_surface 3dm-out-file\n\n"); + exit (0); + } + file_3dm = argv[1]; + + pcl::visualization::PCLVisualizer viewer ("B-spline surface viewer"); + viewer.setSize (800, 600); + + int mesh_resolution = 128; + + ON::Begin(); + + // load surface + ONX_Model on_model; + bool rc = on_model.Read(file_3dm.c_str()); + + // print diagnostic + if ( rc ) + std::cout << "Successfully read: " << file_3dm << std::endl; + else + std::cout << "Errors during reading: " << file_3dm << std::endl; + +// ON_TextLog out; +// on_model.Dump(out); + + if(on_model.m_object_table.Count()==0) + { + std::cout << "3dm file does not contain any objects: " << file_3dm << std::endl; + return -1; + } + + const ON_Object* on_object = on_model.m_object_table[0].m_object; + if(on_object==NULL) + { + std::cout << "object[0] not valid." << std::endl; + return -1; + } + + const ON_NurbsSurface& on_surf = *(ON_NurbsSurface*)on_object; + + pcl::PolygonMesh mesh; + std::string mesh_id = "mesh_nurbs"; + if(on_model.m_object_table.Count()==1) + { + std::cout << "3dm file does not contain a trimming curve: " << file_3dm << std::endl; + + + pcl::on_nurbs::Triangulation::convertSurface2PolygonMesh (on_surf, mesh, mesh_resolution); + } + else + { + on_object = on_model.m_object_table[1].m_object; + if(on_object==NULL) + { + std::cout << "object[1] not valid." << std::endl; + return -1; + } + + const ON_NurbsCurve& on_curv = *(ON_NurbsCurve*)on_object; + + pcl::on_nurbs::Triangulation::convertTrimmedSurface2PolygonMesh (on_surf, on_curv, mesh, + mesh_resolution); + } + + viewer.addPolygonMesh (mesh, mesh_id); + + viewer.spin (); + return 0; +} + From 020cf60e53f7817031c15375fddc05c95976a3bd Mon Sep 17 00:00:00 2001 From: Thomas Moerwald Date: Wed, 19 Feb 2014 13:41:08 +0100 Subject: [PATCH 4/4] Bspline tutorial: reviewed tutorial and added it to the index --- doc/tutorials/content/bspline_fitting.rst | 105 ++++++++++++------ .../content/images/bspline_bunny.png | Bin 0 -> 85570 bytes doc/tutorials/content/index.rst | 16 +++ 3 files changed, 86 insertions(+), 35 deletions(-) create mode 100644 doc/tutorials/content/images/bspline_bunny.png diff --git a/doc/tutorials/content/bspline_fitting.rst b/doc/tutorials/content/bspline_fitting.rst index 192fbe74882..6d7d76e2bd5 100644 --- a/doc/tutorials/content/bspline_fitting.rst +++ b/doc/tutorials/content/bspline_fitting.rst @@ -4,78 +4,87 @@ Fitting trimmed B-splines to unordered point clouds --------------------------------------------------- This tutorial explains how to run a B-spline fitting algorithm on a -point-cloud, to obtain a parametric surface representation. +point-cloud, to obtain a smooth, parametric surface representation. The algorithm consists of the following steps: -* Choice of the parameters for B-spline surface fitting. - -* Initialize B-spline by using the Principal Component Analysis (PCA). This +* Initialization of the B-spline surface by using the Principal Component Analysis (PCA). This assumes that the point-cloud has two main orientations, i.e. that it is roughly planar. -* Refinement and fitting. - -* Choice of the parameters for B-spline curve fitting. +* Refinement and fitting of the B-spline surface. * Circular initialization of the B-spline curve. Here we assume that the point-cloud is compact, i.e. no separated clusters. +* Fitting of the B-spline curve. + * Triangulation of the trimmed B-spline surface. -The algorithm applied to the frontal stanford bunny scan (204800 points): +In this video, the algorithm is applied to the frontal scan of the stanford bunny (204800 points): .. raw:: html - + -Background ----------- +Theoretical background +---------------------- Theoretical information on the algorithm can be found in this `report `_ and in my `PhD thesis `_. + +PCL installation settings +------------------------- + Please note that the modules for NURBS and B-splines are not enabled by default. -Make sure you enable "BUILD_surface_on_nurbs" by setting it to ON. +Make sure you enable "BUILD_surface_on_nurbs" in your ccmake configuration, by setting it to ON. -If your license permits also enable "USE_UMFPACK" for sparse linear solving. +If your license permits, also enable "USE_UMFPACK" for sparse linear solving. This requires SuiteSparse (libsuitesparse-dev in Ubuntu) which is faster, allows more degrees of freedom (i.e. control points) and more data points. +The program created during this tutorial is available in +*pcl/examples/surface/example_nurbs_fitting_surface.cpp* and is built when +"BUILD_examples" is set to ON. This will create the binary called *pcl_example_nurbs_fitting_surface* +in your *bin* folder. + The code -------- -The cpp file used in this tutorial can be found in -pcl/examples/surface/example_nurbs_fitting_surface.cpp +The cpp file used in this tutorial can be found in *pcl/doc/tutorials/content/sources/bspline_fitting/bspline_fitting.cpp*. +You can find the input file at *pcl/test/bunny.pcd*. .. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :linenos: + :lines: 1-169 -The input file you can find at pcl/test/bunny.pcd The explanation --------------- -Now, let's break down the code piece by piece. Lets start with the choice of the -parameters for B-spline surface fitting: - +Now, let's break down the code piece by piece. +Lets start with the choice of the parameters for B-spline surface fitting: .. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp + :linenos: :lines: 56-66 * *order* is the polynomial order of the B-spline surface. * *refinement* is the number of refinement iterations, where for each iteration control-points - are inserted, approximately doubeling the control points in each parametric direction + are inserted, approximately doubling the control points in each parametric direction of the B-spline surface. * *iterations* is the number of iterations that are performed after refinement is completed. -* *mesh_resoluation* the number of vertices in each parametric direction, +* *mesh_resolution* the number of vertices in each parametric direction, used for triangulation of the B-spline surface. +Fitting: + * *interior_smoothness* is the smoothness of the surface interior. * *interior_weight* is the weight for optimization for the surface interior. @@ -85,12 +94,14 @@ parameters for B-spline surface fitting: * *boundary_weight* is the weight for optimization for the surface boundary. Note, that the boundary in this case is not the trimming curve used later on. -The boundary can be used when a point-set exists that define the boundary. Those points -can be declared in *pcl::on_nurbs::NurbsDataSurface::boundary*. In this case, when the +The boundary can be used when a point-set exists that defines the boundary. Those points +can be declared in *pcl::on_nurbs::NurbsDataSurface::boundary*. In that case, when the *boundary_weight* is greater than 0.0, the algorithm tries to align the domain boundaries to these points. In our example we are trimming the surface anyway, so there is no need for aligning the boundary. +Initialization of the B-spline surface +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp @@ -103,23 +114,24 @@ To estimate the extension of the B-spline surface domain, a bounding box is comp by the maximum and middle eigenvectors. That bounding box is used to initialize the B-spline surface with its minimum number of control points, according to the polynomial degree chosen. -The surface fitting class *pcl::on_nurbs::FittingSurface* is provided with the point data and the initial +The surface fitting class *pcl::on_nurbs::FittingSurface* is initialized with the point data and the initial B-spline. - .. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :lines: 74-80 -The *on_nurbs* module allows easy conversion between the *ON_NurbsSurface* and the *PolygonMesh* class, -for visualization of the B-spline surfaces. Note that NURBS are a generalisation of B-splines, +The *on_nurbs::Triangulation* class allows easy conversion between the *ON_NurbsSurface* and the *PolygonMesh* class, +for visualization of the B-spline surfaces. Note that NURBS are a generalization of B-splines, and are therefore a valid container for B-splines, with all control-point weights = 1. - .. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :lines: 82-92 +Refinement and fitting of the B-spline surface +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + At this point of the code we have a B-spline surface with minimal number of control points. Typically they are not enough to represent finer details of the underlying geometry of the point-cloud. However, if we increase the control-points to our desired level of detail and @@ -130,7 +142,6 @@ the rule is: This is the reason why we iteratively increase the degree of freedom by refinement in both directions (line 85-86), and fit the B-spline surface to the point-cloud, getting closer to the final solution. - .. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp :lines: 94-102 @@ -138,8 +149,11 @@ and fit the B-spline surface to the point-cloud, getting closer to the final sol After we reached the final level of refinement, the surface is further fitted to the point-cloud for a pleasing end result. +Initialization of the B-spline curve +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Now that we have the surface fitted to the point-cloud, we want to cut off the overlapping regions of the surface. -To achiev this we project the point-cloud into the parametric domain using the closest points to the B-spline surface. +To achieve this we project the point-cloud into the parametric domain using the closest points to the B-spline surface. In this domain of R^2 we perform the weighted B-spline curve fitting, that creates a closed trimming curve that approximately contains all the points. @@ -148,7 +162,7 @@ contains all the points. :lines: 107-120 The topic of curve fitting goes a bit deeper into the thematics of B-splines. Here we assume that you are -familiar with the concept of B-splines, knot vectors, control-points, and so forthe. +familiar with the concept of B-splines, knot vectors, control-points, and so forth. Please consider the curve being split into supporting regions which is bound by consecutive knots. Also note that points that are inside and outside the curve are distinguished. @@ -180,10 +194,12 @@ Also note that points that are inside and outside the curve are distinguished. :language: cpp :lines: 122-127 -The curve is initialised using a minimum number of control points to reprecent a circle, with the center located +The curve is initialized using a minimum number of control points to represent a circle, with the center located at the mean of the point-cloud and the radius of the maximum distance of a point to the center. -Please note that in line 126 interior weighting is enabled. +Please note that interior weighting is enabled for all points with the command *curve_data.interior_weight_function.push_back (true)*. +Fitting of the B-spline curve +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp @@ -192,6 +208,8 @@ Please note that in line 126 interior weighting is enabled. Similar to the surface fitting approach, the curve is iteratively fitted and refined, as shown in the video. Note how the curve tends to bend inwards at regions where it is not supported by any points. +Triangulation of the trimmed B-spline surface +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: sources/bspline_fitting/bspline_fitting.cpp :language: cpp @@ -199,10 +217,27 @@ Note how the curve tends to bend inwards at regions where it is not supported by After the curve fitting terminated, our geometric representation consists of a B-spline surface and a closed B-spline curved, defined within the parametric domain of the B-spline surface. This is called trimmed B-spline surface. -In line 140 we can use the trimmed B-spline to create a triangular mesh. -When running this example and switch to wireframe mode (w), you will notice that the triangles are ordered in +In line 140 we can use the trimmed B-spline to create a triangular mesh. The triangulation algorithm first triangulates +the whole domain and afterwards removes triangles that lie outside of the trimming curve. Vertices of triangles +that intersect the trimming curve are clamped to the curve. + +When running this example and switch to wire-frame mode (w), you will notice that the triangles are ordered in a rectangular way, which is a result of the rectangular domain of the surface. +Some hints +---------- +Please bear in mind that the robustness of this algorithm heavily depends on the underlying data. +The parameters for B-spline fitting are designed to model the characteristics of this data. + +* If you have holes or steps in your data, you might want to work with lower refinement levels and lower accuracy to + prevent the B-spline from folding and twisting. Moderately increasing of the smoothness might also work. + +* Try to introduce as much pre-conditioning and constraints to the parameters. E.g. if you know, that + the trimming curve is rather simple, then limit the number of maximum control points. + +* Start simple! Before giving up on gaining control over twisting and bending B-splines, I highly recommend + to start your fitting trials with a small number of control points (low refinement), + low accuracy but also low smoothness (B-splines have implicit smoothing property). Compiling and running the program --------------------------------- diff --git a/doc/tutorials/content/images/bspline_bunny.png b/doc/tutorials/content/images/bspline_bunny.png new file mode 100644 index 0000000000000000000000000000000000000000..90fbd1b9cc660d672b2fe22df621f4b8f3915f61 GIT binary patch literal 85570 zcmV)yK$5?SP)^G00001b5ch_0Itp) z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RY0u2orFkjy!3jhEB8FWQhbVF}# zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9bAOJ~3K~#9!?7exMY*|(3`(11Aea?x9 z%)CS1`PMv86clAKYC(ZO$sjUFBa^h<+O2ep+O$50_deRsMs4-k_UlPMPg~{n6BGpm zk)bI>1_7}UfwE?(f*Pnh)SdF?5E18`z1MnwL}t}3pnR{psj8-pmHCN`$e7NF*uTBj z`mIUaow>|qF5j4<^N(OIbD0BpE_0d79Kds#%UtFFp37Y30G`WS<}wHHTpnB;#{q2i z=Nl;^;E>O}F_$@j|IUdZYqEOP?|#eE-gVUr-u7ZdzEO@mdwUz#U;g%|y!7qg^XIo* zD{}}xLK5d6!6PewcKV|?obxy9)ubug-f_b_|K4RUeFRmiP(VSx^50)2o$#1XKcjzi zd*bd>Yw1;6k8zMQpD{Bq1i=ka1AUMeoXcDTf1?@C0K8z^3-|hY!K-H-`qoDjy)H0e z<1X$#!s&IGFqtszk*}HRlSrerieUZ9#!iO!nG{a$q%IU9?ixahWUYucr|=&%}B}WZlbOPd@#=L+{wLa^Sn4^r|=B_**~y*q?ECc?7L! z?>_E-^{hXCPU^x7E`I5IKK0hK{NOX?`M>?jU*FQh_bbohc0x|R`PMg{%LOmHKVA(Nuej-n*M9$p=imO^g#Jd>y$V&wziG5A2(s^24;5SO4fwzd!GqWaR^g|D=6%d+gNF zg^dnuhF;6r>0l~S;b9kx|NO##0#(U9LI@cK+4Io1hxJ(HX29a+X~Yx;qgb}(Wb*|u z2k833)g?-H6uQB9%0u;BFq3J_gk~_px)<0XF|KY|GC$E$H!mLZlu4w z`aM7Wwe=0u6_yVM}GS!c5OTTg}43Iy31tf)sWm0dImj;_EP!Mp7XALRUVx4 z%oqL8H!oiG*8RV?I__TePhUB+DPFtt`}SXW;8fc!?_8KKg1HI(4GdqAjo<&_%kAVH zee0_oCw6oB^Zwa)z4G5Zz;3Q7dcA4{jQro^r>I{oisX4 zx3b{m7O$D~5J*E1=;^1pP=;wGhr5sY&%N{3cH;HmysaI`&onLW+n(k!2k+Um7=#RT8GLRM50(syX9Lg2J@Ig{VSLE?(EHjpyNC8qkW335~OFl5hLyTkn6c@Go!r z*=xdox#!$_|K#wyA70eDyF)=v{Megb_^WTYy4GXEiZm!hw4#KdORKVD;sBtzGul)Mw{?!k@>(w_5 zZa!YgT4J1O3)P4^Y7A6m{l?FI_&!bbUFW@;dtu6`T7Eds07PVR zAAC}G`#z?^qyuHcI4RS%?W-!p1=fYGM+%itDc&<&Pzl6F`iM8ylp}Bdx$pke2j3*Z zJX329;Q!&>TcLX2;^-swQ~&*wL}qG7+}GDvKJ|momXE#n7w$Ro`IY6-7cUCEVv*4{ z%!qEMr$kw~=jK1U5C4xFJnj=uzxI>ofBg4f_@5sZ2glh=;w&fcc4< zj??#8X0ai(OeW+3hdun#yYdW2GNXn_XWV?Kx1S=ia{EXB!_9y2f7+h@+OzX}c9pcs zGmi26TgVBCXh-PiBLuQw5tPsooeJFPos#LC92mG3)pnYtaKc7U`6s{gglE2T?hii{ zG7lqteXP&#yyzwJ0jZp)q=Q#nyyw+VmW2h*YuH&cJ;9Eei?bsvXtbggJ|M~RL;eFjk9#a*DPRdM&xHG5`W2rD{DFIiS5i-FOtJ<7SXaB;RKmRZv zHUKBz@#Bx%p~ror>kLtb0!k^k(31##%qm!4sQY?cwVn_`1StdvjzAHLV30dh>+Irf zJ9O`RP8Ciqo^{EKfBrcy{NDL;nQP$xae(WB^vHE9w;V`|-?8-V|JToa+n*l3EnHoX zu9xTB>I*HS0jH;oB6&VbG* zIEr9eY}%%t-U6iFTcoH61sjJ9n|UX(AhQ%L#n`G^FrqNdZ3L}Y?P-K!lH}yq zJdU}{HSi+{;+6lf`sO!$>}RjE15f?k$?fg!8&?l*yP!Xn%J=-CFW!Y^%8*ns5`b~u zT(;^2VKn5#Dpt{Xt+M>wpM2w!p8mXt?qIX`$7S_}*Svbi>e^2|@#_A~?`7qdQ7O_{ z4#gol!9g*{1QRkyAKXTb}efpg@Ozt7q$X`wJr#bs7s!T4RSYT{;rgBthAwlQ3 zl25^qwvhX->Q1hnIQ9@7=4QA-u>KlK04yZ`c)hiDr5vgvBi99rLMD710RmDAm@ahaU3boAhxE*i9Z{*R~r>wj?R zE9Ut9@0WQP>A&;i+x{z${ju$D>8|(;M@Ec{v%k!X4|C-^uzS$~ae<;_r3eKw zS4=2^ILS#uaDs>;il~bS0V-(Bn~WC32}V`eSnoolbLw5Z^o>`C%gX8VPQB@d-*SU$vz}Qiob2fwiR40O ztWDS$v$jeaal>an_+LXj`wZ=$xb5S!LwOLFcXLm;^ZGydxw6rzf%s)j%aBDJ&Nx@59wAt|L| zHL$onjfC7u>r~?vS! zk~l zRK~<;1%)7V1v3gvI{HMO7QUi*=c`QNEq8WC{Fb}@W_B=HU3;L;MZ{ow_{RTo>gEsL z`hhnu#Yt3?it!5PF3=ZNlhsSKJ-t!aIG9RsNl3vRS3y)nH@A2}+)a@ns#}|giHISp zi4c(JrY44TqTp@qcP;zHt4yXP6!xUBZLRECJ7)9+}Td(ZtGmp8;I z+m3K`%jv`HRzjgSLSmrQf;nwRYUn%YAX92wD6`V|mLQZs>#VP^W5~;%{ir|s(C7aa zm46?20vsg%lgGX0H^20I{oeNaD(deqJn9wGlg{V54c^3l39TP!<&hj`g!QDheMS@6$YAGss@ZcKFCHiDg zrhzbvEqCIh{m**Y_w%r%m(2k@mj{&>FMsPN|Kv?)y|10toE%DO+=WqvZO~;pfe{-S zpHLTS;Z#ek>4afJFEmcuUhKlOw}Svr$4M5QaZg*LJ-yKtQi7J&>6}x2IQNPx-}Ls6nT;WVz|1i|0nIGSQkX&#z3COd z_{>jUVP8G~5yl{V($^9?J5SIN18XgoF{L*=vovD7!a&KneEG(YKY70l7BQn1b`2;k=EM;? zA_@&0T#mz-pK=L)jO!x@*O=~ExM#Ay`vi|Yuz%y;Nk8^VbV)-Ws0y~`l}aB<>Pb#E z+PE3@RUraFL?x&=E`q4&EWmr;xr>U6sNgQR8=KKzHxnSA?H!6tiVqi9 zFJ)M7pD&>~fFEfH!z~|s&(0BTVBdhn!yFoLa%i+39T%nr3xq=N#Dr)3D0lwF@zX-J z%2P5aF>Y}|B4oVK7Vh1kQgX*IGE#Q$!UG7Euq{vxsTwT6fI6|QrpdXAoO_!6$K|<$ z#Y_A8%j=7)_1=cHoyXH`9e?jq*1;%5aDRInMXrs`(UBNXvoT+9P_=URvHv=plbqXTEK>2sNz&<~^GJi9z} z_ve4?`IqciWLwR^X)6kxUSz%ItW(h8jw;2;EnOlj6q3>vIww1^0JP4yrzupK9=bpg z!h~u-E`*+bgcnAUkx@l-0Fj`iilvIBhNS_!hwK=zb7^s~IFg0>aO%1g`jhJyPnX7R zJDyA?*5e zkpd$a8cjoAQ6=iYAdw=?fZZc@M&gh~2?GghAt$0DYQ@+xzfH1ZAQq%|Eg{y$F^Wi=T#Hl|VjWjD z>fCj)YUb-{4&aAZ|GG`(@XufSOXp7X1Kq9x-~yjpBp>v1gyr zc-nr}-ko2(cXc`q5RG++J=&9vV6-5PJF7dmz2Ts-cb%nxDuY1fRDl>-5}H7$83kg) zKp9nNm57NHr-|FP*^S$~lYzlxntfna6juqJOg)&GD=N4k@?X~*hnfSH`ukCX$ye_ zs)!?n#t`U)0)58_2KQ2T?Y(#2HCm@Fgn(x}6P;pY#~gDM`X#QrSTBD0;_lNSeDL(( zt|Aj+AP6y11(pISK}C~DiMk>sX{tIji5Rrx3f*;*Sr%|xm^5M{^D^=U%|q$=HhF+$NiPXLhXnlN?T|ZQHD;tMu==gdLj07 zLK1pmJ&1S35;msNl}u?pn;^c)Go!d*AWD)4fHej}3_(>yBxs7Eig5_56>etfBiUa zA9DFd{`-f9O_kE1Zia(VtV$5fy?D+=`kZray%emsVx?dRh!dUSs31bruPl}jRh_kO zc6TtdjBQ!Wv$W^?3}zKDKO?T>vx0vNOz7}Q=@+A%q+P9jRN3+&rc0OZKKCj2S>P)1 zDSG9Rm)|S%Nh@;zKXh`O)z{ztQ{#ijPu~8?&)!;g7tYP>zmZC)SIeV|m!1$9M!Y3Y zm^#NJM;6#fUe>69p4P}lEJ(o%MMw$(ON^C~(2f`^kd!7-oMB>l8{^C%uo#fUWP~i^ z1?lLsyV~Mt*ra+SsRVUA6!*5L&wVMml)mI%pcF9_LN;l9|fA`eu-_gTp9?zj~x z<_{pT?$^MsA|RXTT5dDf*AM{{4^3!-tesrF`#`#_?#p_3&T!%Er@o?UcHNi1A)7yQ z^v(hNP)bAnjGb5Be)s#Ue>+*;PL-%@8Hzvo7UIC&=thJ{%!ELwC>@I@8J+U3FG*;7 zrUA>O%vzw!c%gz}riPVm96g8WIq7V(=17s#EJZ@S3Gao5MMz4lSQZASLfXa&v&+Y6 zdHYfwQw$*s-erpAQp_xOeYRXpOOw4stt6&EPx0XD5}arz5{eYjASw^!to*(!2JV)v z+^>N}RE({P!Z6e0E&?tR5X`WGXtb`hli0JkZRtEe__mV}%SM zQ#&cL5E(@7cr2fJ@sc*&HkrKWhO+Ok2$4Ec)x<#KG?CPV&?FzmoeS}48`K&OdOfPE zAnJIZTX*;3x#XO4@sdk6^$^1Vmq3sp?!{-dff7y4aTAqI63W4CHWFs04iN;WFD4?< zB{(XwZh0s%xtRDg5=w8bKw_G;37(=^rYj6fXThpP4QneC>1^GZR6Bn9weREM>To>= z@LcYfM7L4a`*Zq*A5Yc0ZF+YJJ=JXfrXr3yZNUqx6{m+J7>#jbQYcDiw1w=5Qy>OJ zkcdQLP3maYH%{$oE-iURi_A)ASVtKgfZ|AvaV#95KPsF@Xa%m8sjXC^T2ZX)hv#T@n0DnSfXoNO*$ z%-keI6LAwt+vh&!VCK2Ud^#<@P(O8TaeTY(zx34ymd={5r8$5f0UElIU7!2N(S854 zy614WSLI-!>50Zd%X%aW#pw-8Y-}g*j+5K+LZF>sPG@w^w9p$U0a8Q))M%n^c+whs zKT@t8Z~VjS*>e~ju~5Te6xAk#ZFLiZ#ww*0qXfFVnf(-I)dU6H%w~iW53CFj@Srk_ z{ip`8t(dMG#cXz4bf%tXk7Uz}EhdNsHSvw;D;2BBc6LFEd$DNXxS1gZ&83M_5KA#l zt=GP-f(fpvInnvvTlf6-5B>Vyv!6Xzy>kFR%*ZWwt$zOK9e2FtU%mbbMGm}=>cbou zdehTqCQ9d6K?CB{MqjhIN^>L!=VXsNWr`@V1E&L3mOC0nD!gJ8Sx}nH&f{G6=Umvc zFv15625Jj~QIQ40aA5%z2@*mOn}ur3aC0w)V6)f_j#&{A{VGyQL>xj0Z1!OnM~k_a zEFq{!F~byf%w|lrTR|}q4>DB_kL;8C?UQHms$019xYbklSyZjKix{zW%Niq=C!K2L z8m6TiRfFkG`g6A#fL#TY8H{~1RoU%<;{XM5aa2TO*lc&DqUL6p z3Sw3eOeCxHRgk`nHFtva+_~ht*DOsL6sjrH1lEh$q$)!XJ=#~^MjK6}pT=wp8)@6K zfA}}|U+|2tNnFik4&aAY|1M~M(|^A1TXt|NaP+Z!X+IZE(*>XEPpmQ+U>y@9B-)I3 zv=Qrxf;rPMtuTd2rWDLkr4hC}HXtekWjITOs#vI5j0^@;jY`mJP^T)5QbJWlW|ptx zE{a=mbN6Cq=IV|(vu;&FKpl5?4Js-zL@S=Hh?&ip;!PZ@%B%;hh>4h6F?B&y0_H9z zvp2y!7PVr_6Mft<+qcegFH0?2FsHyQe57JAAZ;-zrKt2F#pX!bdGIMO+kMXE`!D!b zX0fKZ`ke##VZ-jsZG8EoFMO=9GT?Mgd|td`Ump0v<%c$=hr7;c3Z>BY6vLDgW0nV; zI!TK3g)wwa=}8$6h|&lnr>&`!L136jkr>&QSO}B>+hPnUrYa3JRjQtXi%^hFir9-; zHZwCp6raU%d@B`MRAwW^XLwc@AZ)5wb@$o4B3uw91fLnn;;JfUiny7WyCY)asvx~# zdZ0^tSKL=Dh>+FD1oud3H6`s!pL(otXu#1WXjnR#(z)My<#+$eEG)Iv3~w%T06*lo zomqPF=3o1lfB4I9*uIS{m>`LTPo+JY+r`zBUve#oGR>gW33GAn3BhL#t%}A_EQexQ%tNHlA})d; zMG)C!zUtyW9IcPnn-o0jhVjv}dG}*&b$kE%@$-J}s^6Y{m~+E<4&a9xwVMdYeU2%; z{Hf<2zVoi-Ws1@}z2k6Tg%j_eeA5yhi?mD%p7285(+b5&r%9*Ala8XaLaQ`R0u5*& z1{MNUfB}n%6!9T>L>)+tEDqB^LWsH`Dls4=S5tRfZPttmcgzV;9B~BHHV1i%>wV*c zXMH1m6V4_YJK<3fAe!!>E@WFd5YB>zz>y)yvft@yT9><>B&>? z{@q_+U(cx`DeVS}+vz80WM!Qyvu{9u5_+Z!jK`!U$^y-4+6_|0#uS{WWFte92}%s4 zKpmiFBrHZsWFhi}b2+w`y5ZZ7uxPR^rY35v0#%e4L23_^XjRcQKS?5D3?Y8H;(bH};6se8I9RU$x>7*(8JW4g}b64iid zW@DYD5y5DMSkd>4CnO_JnNCRqrirTM-VsN3qJeW(+0`+CMkx)oFi7Z-m1X|=G2D6{ zOPOz5VTYwrh@lE8MFvqrqMMntL_%_UOpF4(nVYFO=Hlj#3B_hZO2xN$W{ok5AQVt> zcMDE5Jcy{q445iW;2;11AOJ~3K~x7RR+L^NiC~VRxLYZhAui^k1Qjn*qrGSex~>wk zb~akord`;?uY2hWegG=R9l5E${e7=^&u$-nlU(`XwLkyoAAH>v?Ro$AtNyrV zG)LDQ!2kc@x~Ysl_2KvY*?ZsNp>3>_Q2pU={eIm5%-XQkHM^AI#_`1kY)k>Eafm&m z3=`^#ut@Kuj09q2+=C$PX5mfBY$#R}3eqA*P;Ne(Q?G6G91i|TuOBPJQ^be|Qbnv` z$gO8{$2lC^MOkB8q7IFOq$x-UAqOc4I+Y@Y*whpx_tEHrbmp~C7jLumW-i`^nJN~s zS*pe+Su}Hh10Wv6hoaNo07Q=6>Y`<0iNj2|>bU zFbGr-4H}H?8czA78C9`lw@?Q&X|1wpNcFUCaKXBMomsn&I(hH59!7Pw7Ta;x50`fw z**Uy!o$vmh-`+laGIZ|s8N8HlG0Z-t-L|mvv>iL0zPz&D?v;Z_+fzH!#iM5XJcZ-o zk#Cf?vTo+He52Ks+kWMz{#hD^554dGsm)c*vSERBX9Oz=oCex4gCSK#+fp*f7UeS= zS}gzyS?FgYa0}v?5F9o9jMOHXHNM)qSs8@cU|cjZG`_f(W0!|im$MIr{fEl_mS)IM zS!h_SS*+-n;*mYFx-)3-=O3;XM?-?f7($3420_eiGvwofdCvW;ps%wsu`F$`J-u5C zbpi1hL_}1Dpr}X)s-h{Tl%fzV>nKg8o%NbzxDd3hx~XG33u zwzs}8-54F)e&EvQyu9f2-;RI(_ini6s`TPFf5$t&mT$sVD?WeAiTC{IM}F|4Zr6KW z_UC<7K6UsL%X02h_gy}R*57piZ!MYGIKI4o>iD-@xPQ+MwpWx)tO>?a;^c(w!tu=b zT<+P+{-f-jQn!fE_W-J_kEttWC9bo1BI=-+kRdCsbUg}Un-hclD@)$*r+kM!v7#h$ z+8hqn)#zf}Imx4nT8WAd7zP$zmO(CYBF{T)^Vmn8x>#IvT9CR8dPSi22 zgkDQ)p5tJ2dTlxyY|m}z@?yRJq77N7b{{zRng0xyrtUYcfAL9wa|wG`zINB~zklp+ z|CS0e#YX6v9O0aW{yS$v-}jZ@cV#n;$Bw@4`(Cos^DQ6y?Bma+EqG59hLDY7>`l1B zUhce*qpuAYeXu_L&hhRw;&x03fl^o;Y*zNMHP^2y6k!rTs12V*Tor+61Ze$%8u*Os zMbUspDp<@^!r?{A#o@f$vpUnl!jKq=N{WO^y^cHUN>Ef5BuE`&6&+^i)u2A}W*wa3 zo~;D;iRHCupIMlEaDR8M}#e_;Es)nF3rVwVS8?01v)9!fF^wiTA z1{a*@_v0J&FjA|BBtdHH!jgq*EW@(<(Fd;nnc?!IXX(=JAT+{7ql1sq$A12cw~kIO z{C|CG5@5L1DC^8m>_JG1x%|%xVCSyx3v285Ubg(SS&;TKw|(TtR{mrD>rZp*-Dgj{ z-c6Mm2-eVn8;cd8U_*u-R`Zx|US9m{%8se(cG5ZuvBIGWUrV_c+LkJkfJh6aW>lk7 z+A#*B_<)C7-pVT$`h-PlXJk}Dt~qOp-rSN2HNz1oI}={v6{@z__7=PhVwd{cPy4c*OcuOg{eH+b4d*0lQ{j_ec9Z?D2Z7-MKLsp`lQMXK@n;Q9P-NySmI? z>+a?78o4f9#>Z~?{ErR~?wizo$tmjkTDLUZS=Qp0t9|>Q`klM4_+DmN)%Ts*W(b#U zGP?E38vvfi*Bp}g6+qp({EC{Wc0P#k?+k#Q6LRlmA9{RT82;hGH$C@)=e_c~F1zzt z#}@Yt?avOKJ=V&KCID9yRHz{-9w<(i*xqvDohQy2AZzQ@X$AxQLC#E_g^saN4Oks< z`*#fQ-XWLu`|?|EKQQH3A}k>t@qt}ab2!8Q6C`Zb0|%oM#7LPeMBg&Pji#r9Dw!*q zBnAyZa;1IJp@vx}$C-;TGoDK>IpwNoe0Ss=EXQZl(r zor&1;nq7G`n&qAAJZepAQ!y`Y=FF0wUA?#$FSuGM!SEml*3z?XtCw28>UcbN$BsLu z!@T^c&|LVKtN+nu@lp5hLh?Xs_W>CG??;_H*o)8Qe?kCHnbhmk3qGGN{;f%6RQqw~ zQm1V=HepXi4M8`%lb1kpYDXibVLTNp)Cxm2UQJiLnAC2ea8!K_+A87gEpJY?%?go#m<0;8TvD2YB$gFC(A2?7g_8k!-|x%E~oXGJ!m zilIJBh1hb*i&=3qFXpAtdu%1woi&6g(TaCQCq>-5K=R;bCN>+(l6%8lqQoF#i4yZf z+5_J#7hHYilfT=2*2dlET5%5GudCIJblzTb>b*a4%P$`M%Z;@U-g15*B?L}vXM6#6 zr2D2rvhvaO-8IvWfe~iyofHx`oy8r`WF%X)F2;yYjWfytP4he!i$&BiHGq$m;ko;q!^(4NZ%ATzOTcMUT@M$z7K_t+gI3ro$WC@15>>$uGXmC5Z0bN|};5Zs!30`pZNn zFaGq)*vXT29N0UM!Maxwg{d=I;Ta#Hy3Y4!a?3(sp&*H(ct8WoiT)Vby{p!wb!iBO zCaOpfVj>nWqF{;%HB`dNfJ4jNxR1s;?+}l_7cayLkEoC#Iw8{p+6h%)5hP$OCq~?P zA*$>+!k)lL34%H*lv(sxFrmwv5h)c~;rt_9uu4_2ELlTnYspb67l|ROi=#MTxHzSh z8M9@S&NkX^68qxldZkqkk_#poFWQ^Yd(PI{+IS$L9xP71^o(vly>#(&e&ovMyl$)3 zYDOT>p*sigLnA|Ur*qz^=EmPS^rQ)+KvGm0PT6%2&0%swajM9IF$i>mCnQp3Ef<-~j^TWp2Hg4?h9z!?)jH-+am!g*9L{K*%V9C={A) zoG6sQVxm3HrSSXoNI(lEaAF&4 z=i|q?e9D*pezUS`x^qEJ9PRck(Vw8wtum(vX0o|pjv7-Z7U~|1ZZ;diJ$pY*q@LhW zX3ZeMah1)^II~K@;3BSSRHf|9-gqfG2wF8o<8(Sr7J^pd<2Eb|r>EPXXFcINe&`*W znJQ;w!OUe2;D^iy7J2;(uUNn1x;-@tl~4)2gA)`BWJfdmhx8NDB5ff$Niii>3>B@h zicv-k3z#!tOZ}=YdNj?h7M-T2Bx0lIWFH;E1}snQF-LuA5XULkMlt%l85uH|)ZDS1 z4?LTDmbv6)IC!#L`tIqMUZZu8ou_1d#g-CMc+ezNheClCG*X;oP>9M@ypp`xiCJN* zd_lsjwBBaATqM?gukI$8(^hI?rL<-_5SwhL$9b^58AiKnvU_F!x!?Ok|M7b6eVpuZ z>^Mgn!lLh)H&dSj_#xn)w-iG=zIXlDUCk1GAu3HI%|LLxaPvv_s?No%SEu+8-QLt2#g3)RU-09mma+A{Jm8B z8Ad823fZX=K?#9M@CtD%B?fAtOB`EdWyG}Nzy^EQ(H0YOA=Ica2!u?I#Do=-*oPQJ zXVZIT{gGX8ald3;MxD5^J{vc$I7A^E!zmM?Dx9~<1$TxMQ@P;FeRar|;hDEEy$N$t zCNvn>W}oEEeP?sdK|uXo^b6np%b!$@H~9^pNPo%Myrq8=Yj8FvV|VB3J!dVPGhg3x z0Dpb$X8!PwxBSz)zwdc}9JXIUH)h;3tO#m_O(oS_nOQl!d@myIJ!kL!%Dov5zFei89%0;CyIxt1{_zS<*cR}vyhTBJJDzgQu~pf2lF@HcCS+qK zR6!F~DG8s67ENMph*4{6ZeozZL^ZtNRVm7(uuu_~HmZXLNoa(kq(l;Ou(82TPuiua z)G4KoQIrgZIn^J(J`kuxkEd}sEIm~zphjOYLH2Q7RI>rwD68Ghnzih-e*eU(*zvvT zv@2WF$#SpHmbB#C=g;~(Kl3&<8#-twxcRN0`OaO~pe$MZ%Nzf#Voj{i_S%6z4JGgo zf9Vf=;d_q$kH7p~U-Z(gMGP~@BqJ|r5|R)jE3I?O15Dw8Pw<@6 zTvNzK5<*2)l0>T$l-5m6BG)EqMrEKwR)q-fMMEU2ViH=gB-0{TctbU5Jv7reRk#Qn zVg01mN&|DD?eJik(IAv?MzXm9r!{O5LkI;HLUO1S1Hpm{MVSTdk_c)>DnJneHxn7b zUXffrb@GpK%eyE3o}H})eM8%_*WnYY&=_W@(kPAKh8S%S#trAW@jPPWmS*(! z0a7dpW}=Z?1B^&&rW>xs#7#_v!0=GQLLGHi3AK(6VNXh*#m zC!h5d-}mg7ziuC8wI1}Y?WuF%VQ*ZdzVrY7<>fY4ZIB#Ml9H7)P{|EXjI5Y~k&L7o zcx{*%ngr48%q&{SW*Uk(LqADX!jX}#(KVJ?!l=XO*mSd(X6O$rBcaIY4rNI3o;G1+7Rho%4LS{xln#Ig2m6m&sL!pZm?ve);QG=t$PyySAsofrF5c zg>;V1#J>J@pLy=Xw;$gaQA(R>blPU6^!GcstP8F(Bg{ez5acG@B;UG>3`vUbGSUqxOFVQaPjfKz;yp~{&1qIxhOxfLI zW0O-CX=X%aqoWE%aiIonN1us?^V{;kp*9|zZ|;tMD^Lp^Bqf3ps1QczD8jI{$9A<` zLMD2ebi-Ht;7y`MdA^!0j*ECUwkOwkDX5 zA=iwoWFskNq%_9l+DIdkTs%i3sD=jSE-I#GswNsk1cvba#%oAOQ>ltVc3eokcS+`% z-OTw3SM4%eKG7?81z05JfpiR$DVLED@1EmG;Y8xXdA4RO3m&v}91#ti>?X7@S#o42 z!WR)#L5J3tQwlmzd4M}=c&4nzQIx!qTcpfo<6*A)%o|^QAoi|pZBJne{lU2N4gUIk z6aV_%-*fCrraRQcWS68!LIpFDjZaiahAAc_A-QKoO=MzCQ4HLZXrE{$S&&5Ph)BeB zROn;1X}e4`L|a50F@-pl%57JaJGc5T{-{2m=_79&2CWg)X$!rfm9`L--szO02;|E9 zuG15n?G^8wH%9Ey7=5Kzs$h;40=f+*!p1JwKNMFlm;r(pVm1tsNrD8;kVVJ1#x&A? zNOUFY#>V||?2G^AuWcSWu|BD5dwLvrGukWW?YX*)|9s|}T|y}>ZA*1#HrBvH3O&u( z0E$egqFIGlG)S9>lCwc{%1+|YQj&@|5~3am4#^DDp@$^lZO$mYQk?B2yQ!q@jeCzR ze)78A-_L&QJ?VM#h+%;MBBGY+9<@*_wUUEgNj;5Ig1r`TEQmx?xpLQ=bMxsONi2k& zJvKHm#|0Y}p}>U>a^>WCwx&o&D19OoyrDu4>Pmc21+k>=8i+@$_~G^*edcSv?WZ=j zPYin;>%+RXr@?`rWzTv0XS{Rw>}~)2gI}K$7p~^aGic9p^a5t%Lx=TR#SAr6Q6;r@ zX3Or`@y#Envd9oJ2d%zgF=zltNe~ge&#(VU$z=YBk41bHrw=A2@4Y`n^9q*}Q2`GFhM0wLLu! zY@}NzFM8*Df8*#L58ud#p2g|Ur{8H``1bzTgMO*v9axlgRMt&qS@gcGc#ae5LVO6?EjCHW>we)xk!XhptNX zq2r}W8rXt@8mOw|v{QX@!XVlXQBK64}VMnjtAEkxjE|ir7=X>xkTTq&zg?Yd+@3LO}_pj)fSw4zl$il!&5| zom`QiGo~G#(HRnWj(*X7# zjyfhZvchucQ2L4(VuDhLqYHW6eY5(YK6~C=H8Vq82_bULjuADf6wiJA_(uHp2|p|S ztM5s@`@%hYUJBGOk3L9UxJd71uQ(u@N(ryxAp^E)&>!^?+KdaevN>l`DbNh~N5Pd0 zZX=nMD+$DrAUPG#L5-V9Pc;Z81v9)7LNPMnq(TNu43fV&kvkT-v1V(h9Qm5R{)Y9@ zT-#IQz<+@EF6XEJ!}tG(=RE^W99xpl&|J_w@z!V>8pR+9rkIMEsfn1FS)-{bdi6P4 zNv)-n6fg*-Dq2E4qB_LgTmF{g%bz~HoH)-a9I;6jvl9J~9ZL-TBn6LHf;iQq8>rPI zM0AihaVMM_R2h}fhN_hy1qF(LU_J)V#;JoAsgFzp1BN7&fb3&BJuErkM9_l{CGqf! zb9?}A6jabw?t6H7^u@1AX6vK5wx>M;OGM4h`IFZ$ouU;@NJbjBjLC@H7zV$|$YyG4 zrly)rGb9&8R3J$Ots!7BARW=5^rdF|_?G|f*7C079NJ@^#Z5$vzE;8q&ekC_9>zw% zp!e;o9RHy7ZCuVY_gJRDu*6@-#fBj`S6ijTG2)ypD8gQ*m9f~X;CXFpdnzLEV>Vg>w8&=43iJuOOx<5kBtDF>=riy<-cGF6D&}+qqI3=gQ8Sr5m*)YSfkRAeTF3 zj0)b5_L7yjk;`N7Iu5tSfL;zq0v#5Eg|P9}4FOhzvVum>Gs)2yAH_&P2@qnK8wc|} zwl}fNY1!U9{vuhE(yi_3Zeodh{l+hU&8^qqJLCzL(7cb7RMTps_nIY5rlLALH&ZiH zLq$bo5V5bdIt)-N9Yp=An`nu+_M(3IV)n|1y~}$2UY|_Qm(E zlQBXH5~W7Ci%6gmQEJ!qgVNW9CbW;^o)4W}F2yHUN8eKs8yi!)FAOS|Wybg-06 z)kG8tcMrj93>~0>XsU=iI>>W-ttlcRx~L10Rw(Y0w5{Txs!b3f14xoOrlWC5;x|GM z&VHkmfDr+v;PsLk_#n}8Ku&D*YF|Cd0N&dFK0bzJS=||}Q#*8%KCndDg@z`Nb0&~l zW6_ajTqvBl>DlW;xwb!q(?vXK>`6g?A0NH;?a#U9;?{|u>0k8w^J!)~vo)ceVhK~4 z%(P{eXq0JdCSwH7jhQK^XsD7T(m;8&+IO!)6&X~Bdi9NkNA(@8U1F<4(M8m#)rF!` zJj99KL*g+@Ne@=L_W*U}0BJy$zp;JAgHkDlaB8JG?i8m$ajFw_^)F6^zEA>ZWXZB1 zK^~M(J=_!k03ZNKL_t)UY@83*hJdV05|f};vNACm#ad04kqGTh*Nt!av4kii1 zo|XulXIzus6)n_CsSFb>SNg)VpFCC|T!yEZu9(G0YVLLx+~T9{%=3d}<#LL5~O*ANp;2%^12A7PF;;^Glf z!eHQqbwV8X2+>|~MT1u zfzEaqMk#gZMHea~io*{KawRyNHYc1nSXOdFUm!>g4(*^nIPRBw>?4c)dU*pq5U^E)O*-6AhoCqPwj6`1Rh*Fr zCs(9WgbH4Wge54$h7?xD3DZmd^TQLNPq@(s$*6$?m2JZS=Yb2+Z0*81xN3ba*Y=b; z@ID%gkazs#KY0IkfA?!SvYX3X>s_TWCR2)#GFh1hn>~_s3N(SQFbMe0xd%nxn}&)<<(e|)m_f#v3sreW!{nN~v|_=`X%tO_B04&-2AyzblZ!`~H<&RAEGR2DN63iW7#ivOqO@t#v3edXJla_Zq{3*Aq|_4A!; zg^5vNl9@mjLPfHOm}J3BK}ImvcF=pco+Ab9{dWfJ1hc{56ozNm>_d)kgwOJp{wLNty^skAvWtlUsy#lg$}ZVUjp1OfqQ#Q>JFq zCg+@Nk|sH|gBYIl*a*^Po)Q@b}_Z;bdYjg4Hd-S_Dn(+LT#4 z-+t9y%$~upiY}Q3A`=NS6iK29O`<8JacM+?D1pYv9aSZO>|DDe(?z-=HP1XNn?+Qm zNT9MLLTDv!#C zr`rjs7wIE94@VFQk)kXTMMs1d`;UX2<@loqu;GjK25@G|hpym*$M7`HmXZ>A{1%~j zqID)66CYdCMd%VCxT2r|g7++gy_WM+sz;FO>IC*Qh0nrnON z92oe-{r9woSriT*VUp>K?96!JC}$5*rje4=mU-jzHr((eeZnmaaSb;RNyT#wfnK9a zGO5gpSV$kWgck{QA()$sNBDriyksK@P8^`NipwhD{WqVuXc!hJb`!mFcFM)fm3u6Q zWH_j46pjE?X&`}Al2HvY5;Wk(yden_M})T0x173;J6}jMp%{{hU?)=ocgnD^<#GNz zLBIFV@9mwrdwn|B_7tVi&`nFsGOZDXnX$7OZ+<2BALo_}yy^it_K-j{!K9LFjcgBX zvo|v>apM_pEtRgzlN=>V>5W<<`e_^CT1%?orAXl6)X)<6I7CG*5lL3Rtivq!NVmLf z#`O4{HACu~p~8#q=bG~XS1d7wU{*09j2tLPVj3)<4@yFXSs;aZWjrzpj*%g?(|>|g`%8sVn(B$%YCQrzxVp1*RJ)D*7j65@Z10E&-G_c>k;}) z7>%)L`QQyaa3%RPFSv-FAw{TZNivVPf6M=`8HzQvvLKKl3SHu3hk5_AxT>U+x0fp~QcqJV(UWwPTpc!+H0Xm~F-3!EDnI_6 zU-I|fcy6soxVBGw3LU}bM!n)NK9r7%8%cP^DUO}v_yrDkh)Ro!MsL2H*+)0a8;@Ol z>ka(+;b;zTyXjd#1*R4)K;R=WTyn+ZP>u#wnijM(-=_^ir`se6QijfYvI7oxO7Nwda52 zJHK-6y=(ilM_>{D=GT4gEyvlM(g@p?l$rGW`QPD#*Ko3M>mFj7i53e_5qfOtCoXVL zWwWwaE}M<%`L1gkoA&e))0)g<=l|WmxKMl;M-6+#OiHS9b4h>uo*c)wcK3Eqq-hC3O(Il6L~Dxhp#LPY$Jp27JdiCvex`8n z(&345T0C~atcp9>0f&o`%T5`$`B9q01w(p1V~?}Hz_A-^xualo!Dgag9{386P7{J@ zLlbP)oqzXtzwoC~;%a$} zOyQBPauC|_1Z}MqW^4EWvjc;w6}ZY*bYH`o{=z8@<{UlG=j_sdB(CVG2{yRcG0W)c zb4y)`0}mY0{TP93VPjfrI{yRz%dLOypWT-7bgh%LwkN*^e%IY^`%nMo`!@-f>V-wm$V~R*oYBt}fr$`-rZCG4B%L^ROG}SdbyD7~2gLZ0JHe z*+PSrIN>LZ0vsLyh6s0qkN#^20xR6~o{ONPx0XwB*3mVWM zIRn4Xum6Bv;QqqdM~g{%kQVvqjj0}iZX4ymP9`Yw*Z zFa6VRz4xQ6Qe~q9!6hIpd{SH@ENBZw znP-Y3wRW(`(IvucKjIyXdqca3)qeaXFNjB0xd$@qOPln==rv+gMHzN<;}JPIt8Sc* zI}hU*i7_DiN*!Y00YV9dL`ozX$)&}}4nm*kj3(GD%p6t1RtwB>i6ZZkfI|>MXIOA? z&N7G~6|R{s{?%W3&2Rnse;jr|)}eT9pOOQw@Q6EV)t4OFr0Z~HnJ8(L2$CSiISb|z zFkqi0q$!eIEwph$D2fZosTx(KCap$JmXqeJIay9QnVG7}=r!y&kXG19ANXGf>gGV5 zT(L^T`1J>6qfkV+f15jwbNg|MVT02e7RaE(AS~1PDMOAhaD2E>S1{>)!i`}qVfZIi zQo;;r*i6h4Z6;+Da?m)9BaA8yqGS-IC|%%{D0rc-^g->xm={p0GQUu+p6eh53wYYd6HHc<&BbQQ3?YtCR%uT{0RPhyi>yUYm+JdW5P#oa9@|AL@SEfC^ z(Pfr`44~yz^uGNBx1FRaf9>sT6ne!IlS(HP!<8zul{8!pHcEt!xDHmLp+X349SO9C z*0(v?*o9eTpw)0#PUt}j(}6+W$7a~1QiV=&CH0t(LZnGp7|{bPVNvjg=wh1-+xfyj z`13FPt~b1YjnP`$AJKt_>Cvlx{Y%^F+0Wi48Vo2qUPD z6195rLM5ya2qr2J8WvO$5iyZK`b)lfc{}(~X?#3tJ`_-l`LL8LsFPPhM5?10w+nlT zB3x`x$dxLjfzr|lr>z8_ObQ|7N_INMf_b9qfy`V`m^lk041>*r*b3AouseuK3!YV1oyWn!ZkppGRxetEhEA=1$(ua>6y*YyaZ%G-haIX7xNyvIO z_O#T%-}=ID+q%fvH$JcdWiHbxy^I_fz)O=^y=e^-vD(!evubEz{~C#6$1yC9xe$Vl(n{-C>6G5+g`Q#8iv6MY zrRPB?S0CbgzVdQfrtOr zzj57PJ^cQU{`#qZ`HoleSr2NvTbqWa@UD||$K$$X_jw<9+}79N&mL6*mCU3Kg(|4H|M zdoEbViDq~Ho;Q8#%k^*n_%9v1_7&~y$ded~1E2)+C6mcGUU<^3iM!>YZ+hU{5$^bf zkKFOicRWsrzI-aMwkNxZ#U%K)FZxI4oZtL&?0)uS8RW{O;}b`D*Ry!X&HF)P_``#N z$=R!i*IG&$)_DODQPTslG(L%oe7FO~)4#vvyC0^8Kq@}+zeZ3QAM?zWd)#=QlRM1H zKuk>-KI|48}99Fi4ER=+g9c@I(fq@6hps$oNKKg#ddknHHEGZ3l8>bE}t~eP#^6g*o z{eST#BFiW7pbLCxdFPkBQ zO1pO=&g?GcTZ_|&@d;_d&F7FE9%@HNs=nfj9Q`;->HEGfwRjBgib1V=48$s?`snjw z1EZy?nweqgz>WQ=NYyD1ZBQM`2-03UU8NSRi~(>EMwutNM76O;1vFLKiYrZU+co^c z^LX1cc=NM(aE4Vpm>WJsNTI0!uE>HLPV>hA{(XQ!i;F? zR=UBHLiP~~k}Ju{l^o>3sbzc&m)j-KnDfzR^2`4dKlzvCLoc(%7R|#S`>xkae(s0A z{sX`N=KZ68Fs572r_X)q zpSbFam3E(5aQLgI{?zPf`%22%1J(wzb+F^#hl$4J1x7JUTN!!253+o)Ny`04cfwsAK=)6BTLzt z#dJxRK(R{Sj3kw{<;^$YIo)!9dBKN~!Xn`VYHbjE8I+lv@;I4e1Qz%X*I2teefY++ zyY0@!e!Z^g?sh%Z@ly!+o2&m zVM-hzQY0@WnwWKuE`T2Sxg!04`H6r2mY@IeNuzzKO*2Vt+PSzhpYP2kjS|U1skN4Q zfB1)f@$Ij@@zt-r@zv0-lBob45wGw)ANmfSLEV}D@O%Ev4}ShXS$DZUeGc4z=1ZQx zeD58bZA7AE2#%iPnmtdskhmnNJw|&uh?=OS${)3LbqV%-;6x4k3?W=-`R!|X{(a0A zWA!`8DGUSKLEL1>INNSq_Z=Et7Gse~P=)85;!g^nILwRgABR%2o~BZVJ{A;1p$!^A zGuJNIEZVB};%V?+C6pp=l@?Os;si^S&w(Q%ef?kh=l|jxUbAReR(#4CLe})WUr=0Tvtl&s42=Tv9+= zLYbrz!V%-=|F=JV<}Ax5EuOn^Y_I>NANbd|9Q&$uE9=u3fkmWu-AhPMtE56v77mqM zshKK+I<{A@wZ@RMRd2H^x5mpj0wYSVXYj!*)SB zuH3e6;rBMlsqRfn?(PsSNyJE?gJjnSsajNv+@z=iTH{bXMQ`Dw= zd!4FjYHHnW?c7^_>c`&y-aAmH!G&FBTNJ@E^G!I%q#=a7O}WTsLkP=4N|d2%n3?zJ zjM9@6DKR$|g)B4yPG3QgM6~R6Of!>U7s#ZR4z?=i?ni`8iOC)3|HOoU{MBE*dx5Y0 zJKvpVH+{iZ{LgD&eQY26)cSn=?Z5k5|LCuL?p4a6#74u!u?ESAu{|LvO-tL*W@`*n zNv0`jV~S!anM%qjSymCtwtw3yQg`~f`d-w|t1Zf$+Ah76dd}+B#aIM;gC4JsdfBy% z;kOzWGxyH8{sPl(9OsCQ^PU*)4@H%1)I=MXAeO%3wF##ae4u476j*drC`y-@7IJ`+ z4b3DSnC>L2Sx(s!^Nwtd3$T|lWKnBbmNa*qds`_XdAHP_-sviRx0*ux*jisG)?)=% zR9R$}jttw#l_=;i{emL&fv)JaLg)*Pv$XNY2_trCGex}pn4PmLPe^38QNK15EFON5}FQbD@2GO=4w=R5UfNWUZ_%I*Qf;-269;# zhe_iiQDx>lXK&ON9HdSx#*?bCBEEBaAXo|}qki{(UG0vp5`bz%MF$}#$g7W3S7; zeE9v#%^p`Sb(hLmw+{t;m(U({WMIs9BC)eX;mC|wj&1FIC|Phx4n~812ZuXfAj>b^ zIJ#T*Uf*q<_Eg4wteVHUy4kqe5nw?Q6e{QjA2`WFuaI+B~BhC(A$c(i@-siqC!J=Y7@lUi>8|Z+z~f82@4g zmi_Mt-Sw$BumGR+%2(fV<4t+;vE_ofFtnZXN+vfzDy1SaDEA6f7lUe`v7aQ0dI`H! z18azih(~g&4;ZglX!Jqu*fqkTUbaYd2?P*~TNICXQ?Gnj0Y~k?7m-T`dw>0i75m)q zR@$$_S#`$Uw(cY1p#o0kzCtX}gfDi$ci3g>WxIA^E$7P`lwITb=5x4&;ea!SLQ z5S_@Cy*(&#WQLwY2mG1gK0>~A#r|iG*w~rh>u58_9*nlD(_*QKyojk&oGy)S|9BXc zqAXk9cQwle*KBazS9)7!=5KY)bAR-Wx9ykiK@hSOulUA* zZd*I>Cu#TZT-ex?rU_|iS5gB8+7W3CO~XoHXaFu1%)^rnn?(+;8ief6bTuSPu1XP# z;K7+m%){wo(lc4Ibjpg)F%CSDl_uz=k>x%naa@oZ%k0POZptHu-TN2H@KYRM-WpZQ zRYhnQhA?Uxm6Jur@%5#bVB-xkgO}aKi%+pzYKpqGN68duoXw8K3g7yeGvs;8y(f74 z>)2}Ajo+y^zp*T~=q@sA>E>K9VW;QN2Hthu!|(aJm&#Xt+uwQBmww%q$8MI0-;(!y z^_yP1>CL-d{m#SF!)pirWXCzLfBSu3d{cYtbx4Ln7;RzVlm@NTiaVu_L!^NYS)-y5 z1MjmC9;Slt7vvSG4OF3!D(d{7&xzZQ#IbXHRqdpN6p3eM^3IR1PR)TIi2yI6P$ehRflwPrO)Hu zy|S}PBKD^QaoNqK#@x7ZL3)4*c&1;{G>9;*ROoF4kPW%?{xbG3F4Bf(mq}$>XoJOM zB)gW%B9Dv+kL|$0-X?cmC*ARUsu#J^np`}i4SC8`SX4HrEIQguyT!!i&U^L%03ZNK zL_t*f_1AyjFaG=QJ8|6$zxVb3@vDCERS#UZH(564mS@)v{K==$0ZnOOk4eiC`a%Oa z(=mpTuB=GRl?akVAUVQJF0H0J%i0~t3xx>v2zNYsmA76S?>!vXwcL7?&)PxiI64|G z2(JWVJR&)jPJS5vZNmU1>#RTULC zWoSo9IMI}VC?-8Lg)T<5Q3G^@g0g!3`ypDq)7dSL0#bp3Ix%?1FFolJ%1&xm%nKqCLK{p7m+DLyrk`N41Z$k}i zEP`PJG*6H~jb2HW2Iea|v4iH9{$tqcx%GiK^z+?B)3s-)g~pJ?#fz)lq%c#gqYKi6 zykJ|n|HHrkm4D*-*S%PddULVKyNmg{iS-9tp`I&ydwTk>yy@$ghv@d8GM|&7Y_K~3 zl7r&o|GmV>r5Z}$23|uX+@rdql)he$?WvVatDOu{M2d-5sX};o_yzS3 zU*%o#ljr5?3q$>WDdQa)S7RI-6kB}Uny89ZXs|@@XwP(5hq^p{*w;4(QZTJ-^z0?} zj6QMm!=zL5w$iMqe;+Y;ICwW{&?9{An( z*oV65GjDy~m)*LWZ2$kiS^YkHrg1-rh-6}OfjCtzrGG;Pc-h+B zBN!{j3G|p_>k1e4m^JWF*{Z1MWr$z*c*KXQN=lk6Su(ZpSPlysr7zai9HHS0`+wMb z^PtbNv%c^1JkL4z{(kS)%k)go^emduXf%=#5{p;_7@19GHH&SCiP@Z(0C72SNIAA~ zIaH_^J7rfnAoTV-Q#IW^-S6@{_dLt@`}z8B+&ccm14ReE>4V&HC`n@cy@p6PYJT5XW*b+e zdHN;D6E%pBEBaFtPt8pl-gR_baOsQSAbN#G0del~|R&bIwH9l3?12V-Sf z*{&32KeM&Rws8F_x9#z}Z~pNQ{K`N7(ZBOO|NLE-b9eR=J74PeFXXs1J^Pl!H~ob- zf6I@2)<1f{UYstk|JvvMnU{YGlX0fwlo3YK*~OlXE@4JaSRyAn$k2R6DWQtp^g423 zoxS-AvU6(SVi!80g~>TKD3rqJOa*b==jH8<<656PnAuF5=KDkq6H+Vfny4c;_^X(z zilmgxPFy?+;hTniel>)NSWcFbn5wiqCuzUA_ZX(JdL6?LlK<-2@p6t@IJ4q&o`#X) zcu*?tOipdKtsC&&pkFb<)g_Of<=0-u=@kz?&Zj-jenWpf*E-$k@N(lzH=bZULKq#` z$jdU>pN}yyJ$dw5uw&3nS3(L{uvOb+5p`q(!Ecvgb8&^R5W1kPzDJ|0vn8{wriVmP zcE;z-+t_GVP97yrc7~W&+*%ooR5uzAhsxv>rNZt&D1)&toC%I5x{l+^#14~jFmOEZ zlGlC9-}?Li^C$2v{QpUbRbnbKF4%5&?7QFjOMmAte%T#oIn$FWkr3f%o8!|=+q~oo zJ5Hx8jBcZRNXoJ){te5LJY>U;t3q~)0_=_)8&Z&p7Y-8?ro!480-Tu8CYG$!V5%o^ z3iFAR`L8o2E7lJ1<`JWY8D?Utn#@d+ir}K+LQN*6T9DLgttDp*5%**oB4HapRiG|y z(k|}nX$rhV)bMVIDPk36Y?G}f9c%AUpgPlZqHRLxS4{BuDc*h`Z~08_JeJRWe|*V% zk&)wIZ1@2uRS>QRA3M#ZGrBW!%XL3}mF37fQ3Qq3M58@Vo#JEX^vp5mulo6e88gt9 zT|*){sWN1)+``4vBFNc&zwa`=(L1h;I^W){!STE)p#=@p7&`=Z1`ab_o$12c6J;6xF-D`jThktw! zRy|f2;Ul;3_-pOAUK8h^p_{NmSCN6W%r$ljYGH69lMAIXIg^trd7uQ7P=b|kd7Dc; zYmfmdWn!FIHw>i{1u8yK3Ytz9KXUVqRAxby3VkBmEZohx9dAiyDXSX7lqF1&Y>{Pa zYk@?cQ=e?=WGR|OL?s$hq%jFXLYp`wj>c&sJVZmP$J)I{goDUYj~1-GI+?ZpwP8ax zqRzF^6{>J$$z!Lu<&YOW#a$OUdqmzCeZ+8B4)n0UFND{dS~3xJM&`iW8*H=N>(Is*pq^VbL|vwc_r_9eZPN)oKe567-e6(rprzyPyk(p2S=PMWzq7oWAY+ znrTE4r73yZ=v#H(2uflR3>|JnFoIP)vu`}p*PShj6Kl2$;&uJx2Y=>&`|r-)d;h&J ze&r|LP5k@sfj>^ICcp!){8RbN;y3d8mRHNG-x_f{zGghCPo3w3_aKKX515>xu!LGT z>KPKEB-kFIvhGm9f&p?}heTGkYL6-qFFnmF9% z;x>=Hf=54#*SwK44|8^p!P&dUe&T#T{Ey%J%`f_i<=NZ+#O|rHpGYZF>EG&~77_f| z+n=y+J@?>W*!t)_)2W{yZ@tRSue19QcOJ60qE3uT?HDQvV!)k)UABZgA%P4Ss4yy4 zC`wT#;n|*#p5p2*i(@`7H9LkO_r~!a`Xn#b$kY3apjE zN)bNyQ9kPlZaL!gitMvIZd{;XmE!c_VvrtxBkOle0GC6 z_~Zfpc(sy1xA(;V`uK%kyl4F>@ip(U{^5G&X&!urVk~=H88S69O)M0v9QVBI0_#(p zKj4cVp`R!>IL`%742e-#t~q~^#Xe^*aqRSU&R!B{l~AXL{ek!I@aQ>iy|9eOS2q;D zCz?mSvF3gL2wF|mG+DybvS_l5tje9@hh4BH0v2 zB^5?@sz-QCBIZ=0ijR0x@Hh5lne9%9&M(>K0q_m!E_4n+tidcMRnpkvE(oEUSizXd zjwXg6lx;-|QzABO+E%H$z=E=3Z)EEtdzI^J7TZLj&#Z(l&%ZeX%2wsvGaT$w1?|WM zlX-_f2}{r(-9(ws%CjxHGytC*a>q?u)u^4CKwbqI-m6_lkwg=xV&U* z;=aqAzdk3tHLHUu1~_8$%;;>Kn!rgAl?9|if05lo{?9X#R^cP66wpG>1f1^B)y1!Q znf&CtS6$zKVvMHKzx_W_@($;J?saedzMubw55Dj6eZkH)V^7~P4%A?P7|1=-NL4N` zc*je4+$y2*B^8#h&3qPFQ>Lk09c zlLp#TpbDiO?@g=hi3ye&%N!RvVHcKx31b^&EeU5SEGI^zcXm2f6_{~ClE}_vn6ns| z7992jq>+VCg(6g=4{AnW;dHP{6oE=7To~9kv26}Ll3+m!a$5vC1XYPlDzirlFd<+} zj6H9=pAX!|Ek}Iy@32>iffPt(;gm*AFp!!4+lrf-C_ev1+rcW6f-?u){iZ0_Bk!?J z*vL>2g^3;GIC$W^{^hTD;D_Jxcti*i5u);Z@Bc%t)KXT@A~HR zrqkNDg}NXTTOE594zWz>87n5FN}ebqM{69yxNMwqBA9{^?s}GUPmqE|MWDcBl;Cu` z!4g+SMq%$dr{A%9@w2nWL-n~FfKA7%wmG_)E?e$$*L7XjnWb#n30srrMOU}_XP4#K zyn3jc9=OcQ@+q5Qr(2YWQKmv2>g1J5j1eQ$TC3OA*A3!%ibaVe5#(R&^1Wv62FD_+)Hv^@9D+YtJl9`MF6lXtiWGo6hW!CYkVhTd3q(s+|J9?ux7MWh@7nXDG za@Q~VjhRTZsrr<=uJ(C9`{QZ&=(3zC`aoIUbv3SygH_LJ(^Q9PDx;6Y5I%Y>W2x?n zm@`&Sr5;#)^fX;?`k3W_G#PW9hd0^hqswOM*0vm4ITj{n{Zn6wN-YFTN}BLGZ`x!7 z#CGNgD8DJztr}Bv;kRbhF=xt<=Dl&QBn4s8SzoQrB2L6`CLAqMNKRK-R*tr3!_#Qw zNaX|7_5S^a^{fhTa0UtI^KOB7oOVwsh>S8;qN^9 z?azPPYj@7|pZ3s$zw+nb`NzJ5zA^U|Aa1|wfxrGk|Lni`cfa|r3y30x-Z+3|#zCBJ zpO-wsuCje?-T?+J*${W4&e9@ts8nGvDp<_!{87mp0YrwgM^Nb&v*YJEDvq|6usJZH z%ho69a_+h==akH{B{elOO+w&FG1;o};5vWWepggbs-VeqR(Qc^T9hAmGeiu;z>%YvkVEkN(B>Ai@dY>a~A{W@*W?1fM>rRdq?@=uUr1WfBX+m z{mGYcemG=!YI^dMCG^MN@O=K4{`sGN=nMbr@BZcw{QBF@on}i}Ejbdhu?*svrL(Sh zV#_H|1es>LX$hkF#p^tgrZ6-VGjo$XG4mW#B@}b4%-_+I&of~OGfYvX%hq+dGg)+9 z&N=6tQqs*J+6;{dZEH(Ono>j(8B3|cQqzbJWvazpXpW24yW%b$ssT}yr}pAE?&M#+ zoHGZU-{-aimPNEi76}tkLPJq;q6XDNq$;DvB(q2>Ig84Wu!;z7nBaL1Vet)eBpOh9 zo_UihR6NMRp!A!^VhEG5-9p=h$y!<@s+eFx3bLX>?Pj@U7`7&4I64W`lH> z`S9I5v%>?I`HW}S2Xhv|l<8osI9O&^8C%uUSlj|fo)}wS(VkOOU;dtMiLBZ&JapBxExA#cEA#=3Cqvh63vRvl;~8mYG>MIOKM%p z$x<>?HB(a)kr1_FB9KBBHL8f^ZuGK>db(IC(^PAXsKH1KQA@Z+2!g5$)mSU*L=|%3 zu2ov%kr1N`LByq{LLrTot>HqrBN!qPkDO%+&aSw7pDMUfGp=kolcLZGTTQy86k%Bj zV=x97Dmu-Ar=~fR4uM=yrLVKTdX7p2H5ToH1vLhP4r_HyqI8-HsgU6~iKIZH_4B(0 z6A%?{-8Ka!)47;)y(iV#!!#PXwGv7}JgTWACwU~I>TF{)=&@>H z_?;tP^fd3jgOn&e5$vo%xZX)DqFXbqagd2q1xlpW5#(tSoAEN)va`k=X{w2r1 z@{#Yj^{c-9ufB;tf;gT(xWxAxn)27YT3%kU1v^Xhh@Aza&Vy5Pj+a>B^e&f$vn#A; zG|EI)wAFadD&avbn=(%zuzVBmhI!4HZI)YiiJa*=mVN4zF0yn^XYh}NP z5Pqs#3ak^A=!UvZh{sUtTBAQIW0{7jlPd6=d!; zMxPyjt(|TYvIO3c02^6ia~gMr$r#d{Ahiu0tnyq`OZz%E&yW&EsCJd)STJQ-9uEn=PizsiFZ9ooCFZ#9-vM;8iVIq{(q>wdzGZBxa;hjpD^& zUfqs1lv*M};xNm#KD=;#e@;R#!Fn?rn+j#59~pwRQk1l2a;ma&j)SSAA4vl^(_*jQ z{n>y2NB`v?BPr$YV@LdGx%l@U`hllE?~3)e|H)T<{(FDpL%W;QzeX^V%++nKU*J8; z_1oCnClzQ;Kqo*eU14o_z=p$q0|(c*im_>{I-N}vSy^NjiA83atxwu#$tEdj$|>iR zOjAxX`ls<%OsY4qt6aEM5Rf<$P920287fa{rCk2rtG*);+yfsH#4nKvsfaEXwpz4|$S z;w%1WxtHV*GCu0(#y5ZUhrU#ow)c)(uYSuz-}yD4dF`R!$-9w{+0FDFQ{|aksb91E zI~NY7L;m!?N{wAJxK={9TLZ1kBf(`8xqId_sf zHIuH(UDtPgXKE>@n~DX@auB&9$RIw|GB&n#_2N~k6H-bYO0Bhu6G*AmBdE1Tj7FLw z1&zWv;;yak+<1K(KuAzTB4$wvNR6mbB34iYbG9a2IIc`F`+ll0L22h%=lJsFiv7$7 zE^z%8?s%4auCW~HCzch2G0j)>l)w-nIsHUQT;JmA4(p!#uFzMqve*D?VBN#qUtRR1>@u~Rko^&2VhVBtU}lYl5V$xIR%6nW31mZx zZ%$OylBVKb=NGNJS9b>|s{81aN_DE>K^Zs&Z#m7QFX!1)+UU?x4r3yfAFvV%?EF1eaK>) zW9I`$2M^r|Tl}ea%Kj=k<)~qLCI;nb!MS7HHeK$`<}^L|8P(ZrWSf0&&YF^1vXmqx zZ5X;6KU8kU%t$E{Fg8Zq82eU>SFe@o?x7y;b@E!<080d)>u&<_sstuQ2$i-0QID41 zQb08vrWu+e+D@{&ASwjWR~93y#Bt`z77{EDnP9(Xcbt_TjhE0W+YEhWop|INS8tO~ zeIynY35v27@R1A}1 zGAVg-5qb2i{?<t6AX-tbs^Nd1w}OaH+g;DFaJUhiM@v48LX_>TYKFTF7WBu zb~o!K{f0x3d@_8N8J=O9lI1QXOF3mr)+Sds9_k`^^)aDNF4C>)BG!er2Z zn<6A_8*Hs+F0B>R8Jd2*P;PMJ{h~Wsl?C zHs~f2Yex&&K^cQO5|zBJ-SN}k_}c9^ymNgbX!v9a{ak&=gyi4TnfZ(V;)nmM|KvN* zZxg#bv&%zgc->`gIU+05l67!e=!DX-1a*?kN3^}q+NMNGn9#KjFw=G9#G=za=Pui# z&$-j(vhVv&RC3N{rm9{&Jc^fEDiK;3vQA!dWqTcK>gc`>AH}=LMPwpI{J4xvo?I-9 zE>9A5s^MNV+P0&~A!GAzz$459aFr@r-NjvsTd5(02qCG)QN(c~U^%hQj0N`@=_pPq zv!=gg8cxO}6b-XfY6}Od76DkfSOR z#H56@C(CnQlkQ$moMl*8ajLs}cnDC6*HXfxdYQbqdskT*wW0&7;IQ!IIX--vahK0{ zKYQ0H6NgT%lmg1CB9-G2pwC=fiJZOXAOGCL5%R)shUEnkp?q{4+gk#B-79iV<W_9-eY4eLz1yfyV@PER}$hA#JQLN03ZNKL_t)-B#~W$wm_rva#cMVzgOB>_#NgCfHGhP9%#$ zghoqi7UafSXvp-HEo{sx!Sz0B=FTHreK4w!HKjO_7eA+jZl0^oszi%)$O!9cuUj6D zYqt9JG%Pgv&1uFFeiO4Jxwz9X2`)kvukI04_gcqsnChgJVoHckaoxS~v`TPPkwH60 ziXRLd$Gjq*9G?IxT-j%D$wA@CY59hC9KY}fTIq%GK}5v%mJs0#Kl6d<%U-5eQarSH zgg=u%Vp-gLb6%SYox7Q`4b3A*Patw?qP=*dcgPFZx)b*UZTuCp%LqHmY3si?@w z>!XxXYn}gbpK9qvSx+hHA!W>12?7^s`%w?qpcf;XIvNrYNkmmc&4?s!==m+$-!6@wt_LJGH1k;#1!;p=Fyq3bmmLUSwf^aHr3ii z*X$5B8!AJoHV+H>pevG{A<>oSOsff^E+K-7sH$wR8=|)3p@Jse5?v?u0xE|)oIBRA zSSK-(-DKBOQA>6L?n|m>hCAXCCfaRhrdZ~#Alfgrx=Wm3uA{|RZpJ7ydU(uP${HqW z9%3qPNoAbupbU-XX{uQxi3BoCbr5RrQZZNzxFW&W@TMKzifLdskw?xf*e}DsdgK58 z#b5rlpX`DE4aFX;fkLsmgKYF%f%b?JMjVv2oEo~^0 z?OWT;z^#->N-x^0_I>WV)MxFx)OWc{w&=UAYkLiIuhKAdP^#DBwboL6^5T8oPmDv& zDiOYHR2LPOP!%sub)C~pb*^K?EmT{wiG=_m63IZ6LR7Cs!bDQ2Hk`eqqH2UUo|n)d z9~*CjLt+jkvY35Klg)E1S4L@DWxC4FG|QD*tK3yOTAe#bQb@2Y)I7_ufi6&?nyfQh z6RP4SZmJQq(`%}#ZC7V#QniR7GxV}MkQ5=zH84xQY=#FjyNUVv{2N$QMMKCT<`F7W zlKo7VI$PK5s9Jdr<~Hh0cVHJd?!}8rh=jE7p3fTcj+CsceVu9{j>Q-2D58W)#2_jx}*YGIMF zm^ZY*)QosaYJkf`P9z}(3t`!jvn3^GmPwXv}Jzf8)~E+NmbD$Q5DERhUOZjZ=Gqv=&8ccor=YRf33A1)>FN z3ZliUM3_`OxmKen3vb66M`-T9I&WduI_pW?_MuQ=3<1Swkn^6Y$Y+r*|a z+v}6l1+sDZwr(OT(M8b^!$JhnKt)Y0H-J5jUni<`>M10dNJc|6#7u8AqKby4=0Fmt zCP}nOVl7bhl%!agKybL^cW?235Fec?fA(1a-15bXcn&c|Lc~wLx@r=TtEd-43sNP< za1jw#_Xs7qxJoCwZC1=FAthA8!irC0iO`Kn6AQHN*>LhjWiicDmEIN0sF>dKY z`S`lfuUVg!@mcTCNjb{&MMjZOmHCov1D{sK&3AGY`q8^Ct!k;IbecnCk_Zu>G3YXy zSChW_pt3eu6ttpXDk1ZTxy_k#u;WZVU-g3_!J-n)Z{%oGP0BnyUMs0Jx6Se6)W!gn zShVWENnRj3zae zbw_SG$B0g~SM5a;+F8oh=6rGkTW>0=Vyclbk=`Xt744XnlA&UVnM%?v%?*TaZOCte zNUAO#200{qxQM{k+V4$U_Y}G3FkXMK{c27j(IL8FmJjeM33Dujh_(1(FHyZO>`Zvp zNu{kW7lk1Kq&Uf)m;(oc5Dv|s+LfQXYklie(`R4LuQ=|m%GA3iB$~4?LV|Gj;#Fl* z3DD8;XxHgl+-p7Ao(Q#W!kmI!DKX=3G$_WTn6oI9G~43aeyHK&%n7B3MV(o&YKCAU zi?GQkUQc@(JBc7vNSfP|sbTK5b-tnEqDY!Y%nfz46jilVZj&}a#g#twBT*%ilSEa- zBiti9HAx?0;Rl@vvB0WuxRQN4=>BKIqw=bh)Yrl%n$RnRwgc z`rEhH-*xGhS6)kJM!hy32h!Y`rN&eX?zPr&oTe$F#;A-0g*tL1j806fCm6Z3;E^p} zc9G*_x{g|T>WJ;k+8K8*{F9&i*bA_begTxwQ6KZCpSbdYxBo9cb!EL0n+Z=j&DgZ& z;5AO774*4zT=Qp=F_V;1i|OtAZpUW~yjYiAW6s zPbIuJkr2gAbBIpWX$`p)i3%a1jw?y*?6qmE zMLyaY-}9blUwY=Q8fwK5QBaSF;vu4@K#K6{wI+{7R&<%8r&@lRS8iFeJV2doqfCUc zy$dURjquD@eAc;Nc-N&*_Q217yDFDn`%AB}<<&cm^F5jKjtYmtErzkVfm^w#^9`&G z$O_t^5VC=`i;A|IO$UuB2@$>?16`X(4Xu^xV}xo_#BYp+!XY$THyhJ59UmRi2wPhskv;ba^XTC8|e4J-NH9ctV$G z2WvRYTwKKOoUQj?;mmrD4k6~z-Hn%Wdqx~*K6D$ubqBA0nwMW;?6-QHv+uOwPIohfv?UldIQaZ^Hwxd8;R(RgilnSJ-D7_+e=w?{WwjMI;b9^ zx!Y1XIygpCiEy7&#RZ{yZPR7bomDha_CZY zQB1?DX!Hz9#fyhWEL`URRKW!e(Zth>`qTH;`=6?dHG@Z(db0~ZcW%8gViN$7$tSR0-s8G942xWXMP zS&HTmk<=caBBZ1u2%;eZorkI(bi99e_`7%Szi;iYxx^c%yT)F3(_%FZ(=w-&Q4vDn zmazRY)TE1UvArH8>#9yUC8lCIi-TH&d%2;KnkwUFk zMBi&(B-^hOp(=#CnQUxJk&?Eg%a{m!rLk4!1gQ?ToI#m7tYgcGBlg!ZLO-IJDHFjR zr_$g05B}`0J{C3HL_ayinqQH7Y}NC}Z>_&}X1lxf4fV9JyTvflCk$*8hGI8$zcU8i zW|u-c=ZjuUq|4Ujq^8=NgkRgsPi)uA3)l73X&T4T-KTNt+)Ay*Yea!RARy@{;(X-L!6>&t^={ZigvrMd~a{D6v)UW;Y*M9wXUt-#tdkFbseFL}8|8(58 zx4&3C!|hv`vYy!Lp8q>f#*#9Jl|BD5cXxHK=G9&4T#u*quf5oRvOHT3;`_Gmx_ey~ zNxjB)?pNz$LyOFnss`a9A{uy5g%si&tw>Rl;E;l@P&HD!jpi$~K@gd*WE3I^$Lb@I zT(Y+oSEylNF3x;1SBd1lESxI5;tGx7boSH-s z*Jjpjt6)hXUC5Z?&NW~82`d`xAIrV#?Q1inwkL+r5>u`H zS35)zITS_QU&78e4gH7qFTM02Pby?vNlAUmwYZipoQRqj>Y(eW+ufB@wm!di?=|TM zcfq<;-Aj1GMEi#M9Qyw-aiuEap+T;=&bw}cX2}Fh1ymx!B}77nz>%V&E(>Q;^(14* zuinS!JX|jvFm$swMgpCfjI<%(6~~1Ao+mpV-{IvCmuDV*_ox16J0JecQ{Q*%fB3h* z=x_d^@0MN=d*E+A^{qem!GFB>yU*4GhI8~XlNN8T8%0i{K$R2V`$-^h3m?y1W0 zwZs1WnRlIDy=f;FhrH|EPi)({J7lp;sjP;iX{tUiV_KY11S;w>8b^H;<(8E+WMmWl zGn4CLWswjQiOqtCx$Qz#Q(#0U4wHzJ2o)I;6Cp)XK|~^|MC2MnBDtu~;N?x1DN$Wp zLD2LfH#J=WcN}o*5iaZvG*9#dLqa`VHO#EVi-mMfF1S&fnZaeS2C0rh)Fd4`2@99| zj%~|TgHfWL+lErU>4u3W!nDUCc#X|AZNCR zar)R+`K9IlJJy%Jaaw+MzU>yz*@IForFM@^N>?#wRQ~TG1Vb>NVY;W+jK*Y)hE&X> zXj|_|vuB5diX;@FinxRu7Tyff4_uXBxg(}dc1j#3`f;Wswp;-VBvFD|IBPrs7eCz| z`%Lb7<1#;Ts81c8dCB%qzwclCZT`j|`eyhKzmyN+4sQE_v;Tg5!(rKCT(U}(G|O>> zlRV2-Nb6>rrJHNH4Y9n^gpgB>(SyGKzu0@zU~99hJnUI(?ft&z+~J#hR;vdIsT;Kr z2q8oSNKil$45R?N!k8kYQY2NWgpdjjF>&HbNSp){C*^^JvRw`oCdLUB&;5pWeppf$om;27`VM{1x$k-R-fKPU8CFN- z6o-6(Wt##$sYW#4VrHHnlgHwY&1>6bteEuZRWDr3{@Bh-zwj`BZann2>dj#%{s zx`luNZFLU2w|f%gjG4ZGq(})Om^@;=KB~H-JA=#D|GvEI)B^+MA&s39QOl%o2okI^ zyDWet7D;W)Qhi1LwR={7`~Jge!s>#2#r56C*Ta6XA%G!;(ttsjGR0K0$ zRh!hFSS6B_&Kh+HV*pVnNex5@v)Tfe+0(#fuu>)*`MCiv6OYL9ybO}eK}{)|UH#U1 zLJyJSg>lp9)kVa^oFz-BtSt&q!Naiy6{7;eXmaxqgf{IaX~K}2L>&l}jJLiVcdt>i zsIq9c8BYWZgMe$0C<#8{!ZqCU36#S)b2xCd+P{piF@D+dtG8a;=d6%^wQ!-wj7jma zyKwnW>N_rw)5?f+=akiWiyyx}e$6wCmr<%dVhcD%0L_@uo|MfBK$>Kl1$5uhX3~HMvk4Oc>O; zKG)8P;YJTEkU(q$(cXFrlR~)l0&1j+sS7+@Z0=_OF_nlm8c@(Ojc%f%*n>QD7&!zP zE^eeHI6=Ya-L<0>lpRQ5Pw?P#_|o@9{@8Tt26hCi7x63q;Ag+;J>T@XUA?cO1?&^J z#7k#!<|r$o;u0+&dUKhZ zQb1H4gm#N&FU3U`VUHc;uev%sJx*VFZT!3`FGszW86pNMn;)`>N`_i-Bz<`|e(C(- zj}I5`S>ez6sk===MJg$vu+_ay6r>PDSH`qBZvr$Vf*vp$kwa6E#3WlFfUsd~U8|(g z>~%*+&8_3}(uS@kHDkASu7ya;D2jHrMO(i>2&*v#Qh*{N(Fa-)Ti#&oQBJcZr4kfr zWX5)?Az<2)9hIcbUQz&+Y%v!A2|J6^;@iv8dpBK-c36f<>0j8 z={-FAC35|q;ohhGk>9Lx2nWJ2jmUGJz0UcAn@t-{1gm7w!%1UwyV<5FY9}L9LV}7m zt~8Y*SXO?)rF8bjE@J0Rv_(n+*_|lXqzF)AotOZptBJ1aA3P$zBo{xik54Rcl(m*B zWEP!{VYr(=s=^%xgunt4(7@@n1Q>$JAQiX0Gn(~RcA=swmV$v;D<;8Ckrw0NwWlx2 z%rfr_U|t8a{E+}g2N4um8&WW&TYugG1E$UrEQm1r{Jhn>P1->P+panbw90B7i|k(Z zknH+ucN~g$a+z$nsGAz~w1y(N%Duo+mgM{tqd zpm&abhZwwK&2D?*BN8xHhiRaY8CaG~1iGP=lqP=-{pG}d4XYRI)kS4S1PqQakeKE3ymyMFKL zOYe9|?mJ)|OJo~vw|KjeqGGLZ_8V?lPzCFR<%FY*DhPt90W#Za5815YFsNu}J12bn zE`53QFMo=~U?I6uvYDN@rA%$B&*V^GU=`%1Lo|d6+DJw_Srxn$1MX35V*Mp;(X&88 zt&dOJpOzeppyW}2>UCXFgOlVxRJ!^ zVwmuCH%_0+>CKuR9_u&^M~oJkD2@8j@oKha&H?clAlC8(GYWVu$+OFPXc5E&mN8qJ3nK1gNEhQSvHG zL>m93o8So`LXloP_y4z(x#t9h?toz%&uFR$gQ93VPj~;JOmLwtHF2~m^jbIP)w~6C z;$%;psC6^D;w=TW24r4-EtZ4I~c9o)FmiV6gwh+yjnTD2e|P?{kD z!|YG&3D-wF^%mK=BIAc*@o6w&b%5OghgSf_*&Rf|fAD?Z@_|3#=TPMEYFNMmT)lLv z>UF5%)QDxvpSgDz2*RDNPSt^b5=>WMM-Dv#P2D z+{hrKSCids(TPk!^E4-1P*g51@QV+~Q(V3EhP|!iF{^}Z7BDG5Y}iP~aIiVE{?`5c zwY!)rp55gaUP88vlvEWP_EIB?ZUAjS3Evx@OXbt2;)%;?pH|6c)**`2W@G5BWAkxW zQ*2zk8mk5r)ntjmKm=R185p3jf>l@rnV10cNYMpSP<4;Z8jfgz%Et%n_NUvgdck{E?R=Y?QS}_VrVCN8D z`V7*iaJP56Hg%{W7FZ`(!RVWw=Y+vK*fG;~RuoAQfNg8gn0Awc&L=!18{)efG)6FVN~QbPTjL}V|7@r$H1pveBs%I5Tp9K#Wyr~~#ZsJM0tFP(+#;_L*whBF1RHBAqh z-*DY(7zSclX0@$% zv&dFD;?4xwX4Ww&LXaJZw%W0TJz8XWop0CW`9t{aWDJ<~j|UkNk^?UIhL7=W&)Hh> z=z;FSpz3=2XP=#!C4?8XeC%}mqx0qKKbjt?ZrYVYiCcuQTV6T4&Ph6}lcZP7dj&}< z5-~VJ(V`)gFvlt*fX3v!eGN8O=B-4SyI8cisff8^2Zla2cr$lgaL%20%E9voq})uL#zX~LUQB*4vafEx`MRCwe`{DSf0Yg z!OqrZ61_(g9%^+Xo?EZ5SX`vpvJk9+_6H~gpn>-YYJv->!+!jWTt!0rSrdyEYN zk#K2&k3NKd@;Ki1GWMUvB95uiIxm~H?^of`h%=Rq4k4g6GgN_r6l!D42X`vmV1b2N z2o4vpKn|>x)ub83K`^?c&5+YT6*}GI#?TMm>eww$ zyzKs49}lBEaFwYsvdQgpn_IBV5Zaba0xqm@zAA}PWC<-M#h7DTPTQ-{<0@J@mh;Sr zBQQyGk>$*k=-SL%K`hRrspgE)@+(vtM^2!fJ5dblBzaCD7VVKv0#t2jC@0IMwz+Jw zQz*e6O-R7!)W%kVHPvwlilC}oH-DN3A1z0xm-&lvcITzT<<8D+Ju3PyoDH`q3=sxn zzPb%SnWTF&D6pu=6IdXQWVoT&saO}(+$IRDiXfH?{HY)N-gDo5?u+x+{q1l3TdzeX zmI#3T2S4wE+skPM6No`-*UGy?6G$8_@$}vJ-8bRhz!RUsNJz$@J;Ddt^uU10(a4XV zjW4(?`;_oVmIUI{q$$YxA}c6Z&i7L&0@#RUi*-1Z z(GQ$9aE`ep;>ikHpcYIeKon)D0Aj)A<)imjI~r9fIhoJ=h7(whNbf<}mx>ZD7^~*M zl-0o!;erfqED8YRMyJ{;S zoM`Q7!w(duxz`_!p2P2BqZF;h|5*QtOih!7@c4TMUl_P$yWg3JzsxnUK^ z04pq^6fC^AFgD@J0rt=0+70A{Hzj;puKd=e-+1kgk;wSAAN-NO`RBf`8jdFDh}D3x zVnHktCSae~uh==lc!XgMt7v17#mtBZX0O_WFL~kgK#8n1wI6E7wRRKfmeEAvb~7KGvVQb*{L?Gf-p0F3(v4|l%TdT^ zg%TrM9&2naS$Ifv&pmUH5^)SW2N(`UDCd!_JHuPodfp%e)U7}h&J@vfC=l#C`_ zL<&sG=F{W4)uP=Fvgb|k>`YvkzsH-vTh1@0Nw`!ML?Tk{FRcN;ZE9pD?4u*z1O>N6 zca(AY@L*?0FW)F*N;VA52*GM6KRdi>$ztZc%?q~m4Z67?GyR5aB}1cjkN~DR%RvKG zz&`hhaJXU(xFL%S&O|ggfG0;08yOe-BngAVL#omR$>9PahXQr1XP8a{6X1$P#m*Wx zfQas`oe5u{Ud6PF4?M1K{v^vqrWN)omTNrrJ9YfMm+;_cdj;v|ync491hQs5dMvGg zN?1WYLUy=zTq*ip7qLvM7kbON3Vp3Vi2`pgg zQ8isUHUB=j`EvA4;UsBNgfk_!H1`ZZ(M7)$r0C{&5|qk_aIyqT3$Y0X>A^dZ4XXCm zraf&ntPE3XeLG#CkVHU{fq}?GYHr$%p`}2HUlDK)vk^)L?R|Q%C_Fbm6323pv{)#qmMU@E|xTLaFQtrk57Dvc$mvSA?Po zh9wCS20#RpAvF_4g(@T5WqkJBtacJ4sGILgMgm2sEpLlyR2`$x;Km# zQkm484vvthPP0b3hlmS}XkmB?u@;0;PfZ^9dqtxDrs27&-CFY#RF za>XyWf#jsXl`bHmf>6|iRG>fsv?Uv793JAF^zjHj&Ti5r~(#*ARM9?4CW{+WW!J)3P;zk6$MSQ*+MLZ zq$%8VE%c0q%hE*{DT@Wx9wFV*NZN8HN956QDj~=0qX@gxtoKojOq>^*`wm3Up79!!{TVtq**y1NKu#GXdAJ7vE*6WEyt;ax| z0<&ajOFGcx`we<^j}UKOjNPP0W!u9osd+4jqb%Y$oYrC*MDhk=K{si@^NPKxo{#8E z3rb^gaKjXppg5h;{IM(GiiilZ5))}^DGeTh>fQ=xzX^FB)C3^+$nw%@y8VyuBG z(K_nE@uAnzi@)QOG*v=YDhHXUC<+FWLltF$CHRDtkSg{P-f#(D_ZQ2B8yF@O;OY`u zkyg0N@Z10JXRiM6e|_ro+0WWJ{A$Suo-zK_o4#o0O$m=>ls%lSunY{yhHTg)_5_2% z7f6ahkqwd|1DX}pLR6D#PG**AyV8W7I%wJvOCcdxWu%;|(S#$Vnk=YF*#7d5meX&a zDbN4zGvEBsn_qVYcU-^(L1#EZC}V{MqefwMlb5ewTkh_ySF6oi7#`C!ZB~#gOjVsi zR6}xvB6O2|1^}$FP42I?ZoL2r;->20%w`eZyo0Ej zZU08qk*dVC1%`?gP}7xlE?Tq0c!e&4AzmXor?p?$@aRcvfmCB9VL?g@OqJGD3$YVA znYgt>o1^8JN-~omXAmw_ND0~8QD}8K9ST`9jwGC1R=JVmgJ{94uD~W8;V1K6-NO&4zGhruZ<7=%&&gpv&O7^wTzJjLSTi1 z@B7Q&`*;8RUq8GIQ&jCLlYx-A5_7{kbyffkYDfZOqeH?3G|=D*2M7sZ-CZO*4`G0;^bOtN~V}3Mh&J3vi0d7oP<1SyMN>deR9oAwG%KPdxd*Yl2TW zlK|@x^;tWbSryf=@!<$<<3WfD1KN_T1B?b63YApQuvUaoLw3BQzRmr7Xs-MMQx((m zRYgStOs{nL8=l#)kgB}AluyKwJahS-_rD&8YoAt~^>a&C+B&}=YJ@YcEam!u-GVb~ zME6f@hirJbY_)|^W8>c9aWmGsO&la(H=oa25E`MnIbgf6S7-A80nDhP!I1d`wv=v$ z#Gok<6)Y`LjSEL@85baFVtO+Bb-R>dpX;=xOY{ht`89=tD&GDi5k&}56xT*Pdq!?> zeddU-Tcfgz(D(}9{tNV5`X zC!dpaTuXT01OBOfe(|$-&$YB3r3L>I6iUQ~Lel0ym?`;;Ky}C+mMCy?6m&}=(Zah= za67u=BoIhRRG8*0)5gL)jyh=~JkW`#bVpH$SgKg3zTz$9c}mtS)wpFa{!;-O8dZNcOHfux8B*HHyv zP|2WJKwLsp0z(@}sZcOhNg*DRgbqvs7ig0-6VLDC4%-Ki3iWaZ|Z{)d4|LT+P{;PMVyK#6OnlK4k zs?q%ksUii$QHi|h{tsG^0@)&~IUp3Jr4Ut!vxnpfD%@P8(RAp>CO#*dPqJ#QMFLPs zDYwJnwlxsvfphf$=%55L#6oHa-KmIZLJ4~90~50I2^)4c!NqxLEPv^7tcdy|zBF5j zS_P>hQ2?b_^=Jr|s0E^dSJ5Xf(y-g@Ve940ZLAQt5|Rn^*|^=p+e$#kga9}H%Pd40y1tomeB{`4l|=jQw|E4ZL#v5 zib2y$dT=G%l3*Z!~n z`Q!iEM>d3|KjsSnbnJ;40kXh zO7*#1Y(aZ;K)@nWGmGKI0!IiJS^xoa5fPt9qtU!$9BolVwB8=!ZQyjf1&osm_#~zk zj*}fU{FXgRY9}YRj|_0`g2kJ;FO?Ajx+Ig<%d`$&$(c?)a~b0pbkpNXhPO)klpF?DOjHiRUr2!L_ISAQHY zk9fmXutZfdkSeN+3LKq}84)7V7BGo6UkWg`j^CSV=97|mNZicb3B7$=Fn0!=MYQt( zSr}yorA17j$6QNNw9cD=0M?E1`v0ysBU*B+6|2mSTWolhY{DWbkg8-Y_fF|co}Io} z_8&OPuV0&qsHurIifTyI5C|pcf^`yeESyPcjuL3ZyJow4>-#TTG*zAP_Z)pk$lm7X z2t^Hqv{8R&p0|yzsa#HHJp3Z={YV{eTt~%J{HgVmD@+S~rWWw4WsG#Pppw7)H-F@R z`=5UJ(%W?R$#|=S2}3|e3_C#J9*2*R5m}K9gJCe3NROgUnwq7IFqm(b0Jds(W?sg}+EHu&G{IV0Vp(sEV}-3%%C<^rX9YM}m+5!=naE>HeJ{ z!m+9-O@3Tah)PsPshAud$DbBQ4V1u|C>8Ht;J^I)f8z(<_k-{M+FyO+{u^H-CG=*8 z=EaPEa5wx<;%h#<^q)W4TcISpw7||TF1(BzyQsiUv*C_j=O6=$B(R|Bme=7e^QtD{ zuhl8+tPI7*MO{K*eHq_r_1>1<@ycWFCQEmZ2#P>MhKl>E6a&c;aAKV>0fWFICLY(! z5~y?C+fHC^is!h^2^_DufbABouWVf=!JM2JH?c43<^UH&=;ok;KthjkY(%rrpDq-xB$8)Btn2JIrVyH41 zo;oA{=R^L)bA0eBcPH^=R{eJ4Jy7UvFxkv>&__*eu-9O|#_qW7glrStj0|02&++D? z^lhiU=i9#JyTQ+F2Abm4_#xl>oqy*13%GI_hrTKSI1=&yJdEFdr&UE%BttYqVFxQb zFa@WmHL6!UE5hMzd-C~H@1L#g^-^ z#C0}8Az>{JLbQ#EO;buB$2=6Tt+*OQieV*AL55@kfZ}ga!mPlrADk0MLk@u_PiYq=*Q+VUxCwRkU$~PPm!gWHx>$$;-T)JNVpm zsNU9byLk|elc1o$oP8AqJo>ILz|;hioBBMkO^&R@RPQ)9YR+=9eK_bHd-cuA*#KFM z@LUQ|`l;Ed{F^dcfr+fjEfBkGzAHqsG!t|B&m`Xpicod&Uh0UXtvj&!6%<9%4Kzp- zv1B4z8Di#S6(-d%P(q@4cStiBY-FDbx*|*zVkS>7d2yr?SXBzBgI1m&<1EWKUatS>t2&x3xiTp&(>%Py|V;Ara9=d8(qeq&U55ux)S&De#6%{Em;0SEhJmJq&@W zTI4n-Mtfzs0fa>I?pWB!p}|ht!Li!<_IqIp>;PBRNH1YW^u@g!e{|`S51xLFE#S=+ zPC0$+zxQwc;!plWR@fe1TH(PLaN!1y4zZ)K0V0TuN@zvWp0@4@E>Q$E6p92=l$17E zkq$+-vC+o9ya#zV(?xEagn27o$y<0u+r~L^vNvfB`Yg{sjv0glm@rhV6;~6+ie-f; zR*E1}fJEJz)HdeE_6dBOR_-JUb*IbPMDtrr=RShJsf*rT#_clj*q;m4PwWvQVBLE5 zgcB|9#?zp$TZ#S#Y=`?1O~s**X3;JY0z!s3){3TpQ9`sDLNFk^kRq9u0&9y&p@CGe z7Fgh}1AOgMlDU`H4%1Z<_{pa|`PPP@C?F`U1Zci|GYVY;@rwI)HC%VZh%dR0U;Xi; zcYWt$?|%2&Khq)LuhJ9Px&9;h{lB;PM~8Re++}FSLUCGvtAN54W>`ByVS=d@jSk7U zxWpjXVFP673s=`q`D*_r}U-~+;uS9t6)-gD!25LhW{!YbkEGkE44E-o7M(ow1!QTx9+MNARl;kA}< zctp^v`&89q%~~1>ne>>kmWX*2#Qvx*?G^zFFacBlk*<-GzREg`R^3ajrNp|TR73#- zPIw>{prR5|yBkeoo*~eMu>m@2pavpP2&p~$!=ry!X7hs<6y9uT+V0V~B^J;?NO!#< zxA&ON?uuiY7bfdFh(z~iZdM`ljJqRSn^D&Brw#?8vN}t!Mn7zWoWO&Gr%&Uh!4b_x zZe9#36j4Z`3~HlUGE17Wra?5RC9`Zci20x}&CtQy5H!euL?oDM5>r)4rXp%0S>1F} zdXiAYdNP3~r6~Xn2}l(bMUj|-6)k=VsR9+vJ-`TWH&}x(q;Iv&f>(hu;MbnOGq1NB z10>)Rvf{pi_x<@FU#*T_qYHQ!r{Ma+I=%Vr7Y+l%e%JA`KoLxeb;2s) zaKN<@pW4SWr#8J`gG##!)ftsh&>_;w@CawPhXF%DwBf4<+lrm-oFbxn15#_0N_V=m zI;%(ZsZ2*@Dqe#Okx&6Ey1`2{D`F=LP+JUqAlDYrf+NF`Vom6TBVyGa()m*Ep!dk1 z5-5%}TOfTma56_~_SN(1&9h9ZEjCxEAQ2`|L7HvAn%t*7nh_l53`Hl3plK--v!@K^ zyHjUm8ET@b#kVR#g~FyJ6|)45q%<)CU=lS#t7n1G+xwG3Mi&UDglZCsPwnH= zXHf=(Av;p(+mi-daFkjmPOr2F!KfG%Dp)V@{%^r^|Kj5Cf@Q~Pg9XwWXXX^g*CL3O zD9?Sx3;)h9|G9U*E&lKWKYjJ;6*X`Vp`Za7I|*94@5$bTOhkeOo_G--d=9IdgC001BWNkl98ZMZsI1#ls`K!rQsj@;MANurW2k z0~2HHh}tBFV;nB@1zElB5Idrr>cOQer}p+YJzpC-2!$stW-jXgQ?XW>1qY=_2$vo%^2y!uq5bvIxp>dj#pBcRu2>!&=VA#XbQ2$f zO-e{#NTE>2qR#V={||p%QIr-z0!%;w=vW1s(|>!Ep3l||igm`d+kEo!E)I6^$V*r& zWC;;i%w`4(07LZGmWeQkAqfUYG2~kkfKQyLL{cCf>o>DwY}K_juElLm;1eL0oPhjV z58R}Ar;44cIyy63NLl}xZs@5U5TbUk!e+C+34-R`D7Ao)l16iRn z`{%h+_~?VUcn(%^;Rt7z@D)lzo{;ASwnMBE0+5Q50Kpg-90(Lc+)A$8dU?4S549u& z!O+~y6_a7`+l-5ECUQ!j6zDvtQ#RK)G3VLP0b0e2pa@Nn11Ydh^AMX@SuYl-I0ANn z>eCti=-M~H|3P>OfU-m4fPvfow zj5GPU0;X`qT2K^2H2c9mTWTRPHL!Gi`BhxKgi=uU@EhOZb->+!xY&IyFW@G;?C*}? z++A<^(|_&1`%B;d@85S9UU2LyRtYg-F#-D^kaw^;f)6ktC#WL)SP4J^2?n^k&Pv)k zVwJhhDTzFKUH^@v@q31aB*}$``GhT-K%{GLyQOnD>v{sP27cwi`02e%zdPV7FX8*H z>^{0mBW#KYZ!|sk=mBgICKADbmRkd`qeTm7eov6eXdciJJ*hJr~{%^Gc|y046m z&`JUiUPBeE0z&LoBtbP)!(qY2eZ0JjBzWLi>{XB`L=ZZ`h(iIcV@FUF>x!YGwi)MV z0m6DUpTO9jO_J~a6W{;Po4)vmzxgZAodXpwW}G^Oy&EXRkTA8JB!GemD1o6_=V&j- zo07A+Nwjo2sleLHC?S%ojB6ZLU8qobB*;}aFGaOE_i&T!u3a;Vs(AD=-sx$P>bqXT zy>-}4d0MY=Q)Kyjhc-sqj*E1c6dW9a?4SRuNKtig>hMWL_16pq$ z0{3LOt<9jCW{3qvL{&@;W^6(Lmztm^sxqpHs3c_|0vQn1YFe@wyB}pyWLTCIr6x^B zb*hG?$bMePs;nZ4s!@z^<5&g2(V+0iA#ONI!Wv478l^5=3Z$aJ(HtuGNlD2 zL255jY>i~NG~knW$vDW_Pe(3r_N3cMQ3N3J1Sce8FYw?`;0xY+?&)8+oOeDOUGS@P z0pGNi8xil!-H+AtI~VIAuj2RKo_6>3&A&0-zVE+=5A`=V8f>^gnEFprod<^%kG;4j}$+BOtvjeGMLKeWF z1_}mtOp7D_-~oL20XcJo)4+S4r}@nsz?(rVzyLRdU`qJSb8`Q6IW-}5yR8!vR8`rg zWvVKy4R|O*HIX(ef+!)TSQrFGYP7kRnrR4>Osbh;6JXsf(1;uiFE@7VsdMhxnO7FU@FZW!TSWZwlcf5;*KgX;7gvumA`W7e!*HdyqM3RL*=V~0UzJ>d#BI+ z)Ia{%{qK3;TZpF@@rKVor7v8nABQ9;FbOncBv3)%|7Gu8gKf*M`o7;7bFQ`aKKq<= z-?wkS)GxhXEeTNqB#@y9P`pCq*px#kOj6i@gDHP-ioA>}DJPXw1uMiQKKFF1TLy$a2=%I6=icsn&bjCA zz2{tGj`9Eh{?=eq&iwR$G=Wd=Z_(-$lXYC{0oJd6AGfF$rHd{eK>j_Qhl-rg(vtqaB%)q?*=AV6tJx+4gAP1I6 zDfYW~48xD@tu7?UhG(R3?ttb=VrJbyQz0HPVW<_- zK%b^=OV?u8*oMP3y;J0=j@zPc|6iCZz4M*#{32Pv;^L35BH~#8952W&#pWo{j zCAXJ-=A^{oNaw7CL&X}Z#cYhJ$9oj{S(QVjrm+|)QgU&ZLF6Dijz?APB`2aPIi?fr zL?jQ7T%xhaCBmcEFtcIZd!s&>h?u(w^=l`BThuSFUOcWR9yr5&H*?`>wh!rpYl;+T zxbFm4U$tmyyQliDkSkZs@Qf;*q6u$k8fKYJ$k4QeA_-5lFo#ySzKtdtp@qBmINS5a zC;8fkX#&xyh^;mwshn!9|3^eblPp|Q)(0=hFTGwqbaOnh!@_yRRV)8}XlP9$VjC|N^TWq)Ws{`_6*S6tJbK|p$;Es-q3V#&~?krv%5StN=|5%qDMa?Z8s8RkyPNspBMF1C?GixVjuo`wh{%#-No zV?mRM?#eg*;rVXxyniR zURaErHBM@ZwyjRz;~Q&A`RH4jxVO^w#S&C2?^u@0!?tM`^Q{mqr7XHmBLr3%CuWiw z7ir0$=3A+0CA=m-2I{g(MQF~H{MxJee~t66KfOI(9;Fc_QjpP_MO;$kq+z$&_H|HNiA6Xb4d^bp#tdfz^Z^6(u64N*X@m zK#8b^6wq)#vJKb-(_&+nJ2qR5a6@+?MH6Q>uu^O006XW7e;NY1)p zVyr76Q6mT?q=QIen-SD1OAZ*udEqtf?8GSB>(G#*Q=Y1B$R9PRrRH7K5}KejA0Uft z7kSyCEUGH64#x5%cPkF#w7gs?Oez#nvQ5c#>Qha(3VI<0U67f8^0S!g@T+UO9S-(5 zxWM>oIdv}H^?m>Q7ZmaSMYDj9u*5&`y8i;;CHB6b{muP{?wg;(3*Ivtv-613uPBLF zFfxUq8PkZhY^tNqK`Rv&DIi!AYc+AyBKwp|A1SDPN!!1 z<3xjcU@@s}!zRU8t?*|9?Ze&`qJ>QNKGy?SU3r4mxvQc=lcW(9bwE@W zu3A4Ug$SymsyCo>$EHA0lcGR^&bf8u^fSEqDsz|CMYcrq)UEv-Dp6OOlp^UPC*r*) z4`pp%S@O%S=kuOY%2_G}I!2LhI=K1`RrWE3wm2rNGus1=i3H~lq77+W;)u%^h)+Q- zrjy~j3PQ4z95u*l;X6gY<%#(j!{o-@aHz48g$|NW{duqI?u;jzL)KtID5_Y< zCbU>h4k($vFyAJY%oCUQS$DJ7pWphKpL+L?{@}Y_T);O*vYTo;{n)R3{3rjrzxv@{ z{)gRJTH}JQy|QdDAr)qZz>w(^B~bTNq3~uI(8=8&l@n0Y zRss=q_ncHF->_`ZARDjzAL}?U*?OX;6Z#D;u^RIOqGJGe)HX4~O617|lWOMWv0HA1 zW@ti%oO0Tb<3yy={3NKcNixyztJ|@*ozjHR07+WX^oI(a&}OZgMG4s+!)2vXeGP^u-XnhlZ=(?$?=)0$g+&81oAC6o{vXI3WAXy7p+OJY^soC>lv2uCx1SJ|lv2gaRsLyG)pX+yoN~@7v3V0GRSl^+WS}MbYOP0IVzrg# zuIkS@-3&)nq7rRrCvm-#!&!LHJ2#DT!Dp#%SWVKwS`$;+GG$ZMp}GbdqQ;mb#pLgA zeM(ENy`@N0-;(;qo&ix2xU$h>ZhVqO=(8i5!__$HZEw-Lje*G|a?aJ)1-|J&H--yR z5N9vpMpsDAQn3ad&=H&-#A>!RGEx91JGN#WoL!MVdcA?}jp+-z626GK(9g4sBjP{$ z&L6yb?Gu0a@%y&6h!HUa4hQfzp_F^A!E=e zGi6rsBMD8kRn@HIOiJWT%H%xlM>$DunkMC369|=*R8><-DW_Cp%T(%$O*y5cDb;Yo zTtADPYF#KI2}vO;LZgkQMw2G>CbekR8&@6krp+Zel}5)#tih zl{lkCafwiu(Ml1FF!WH}!?V%#;TC%xpV{J(Q|v4!qnM3oAr-V1N(IYch)su@N-4-e zW2i9pG?~(}UZW%JNH`a-Ex+S0f8Q7G7@YZHzUuSx{r~NIo_ywyKJ@;NywoU4S<-B= zeThZLO2+P186J)*tIP(*S|);-JA)A$asOD?Um+z%o!%mvfzVV*8X>8A@5J557*#?i zC2<@N(Ul}}qnO^DieLEXI8m35bX$T3$;gQe5m-WmRL=$V0IbMVLQ5%;^G2#A(}x-( zTGy3MyAI$vB{`OFrfIU}Qb)u>5l_B3UJ9zI$`Pu6Jt_JeD($go785ivG?AQFUE>Xx zw^p6*j1t0-X-kNxtBffsAwZ$H_xN~b`y;c}l^D(+#G4Pd8bm~jnQv%X5>o_L6goEeNu#;G|H*vR3EM?d=E&&?SBl3u{IttG)b|K6|M z`(3x$eO%b(Kv}fBX2rMs^H>dRX$--&OzX@>6s~8QpmlP=9ZMKkB^nyJHfAerFhNvT zQ-lN|7EV~sdX&Htek4td&pN9gS388L9gSX}`>$|9r=EE@jge~2yfF{f!=5GjL}$l3 z+O;2$&@}PrrMA~}+TiMaGU<_FK}2gHN&Ok?*GxM{oaR3p^P!Kd;^sqk&7HL-5kJ?_!&E*{t^@8t1M?ATXz{Ey4?_psfH`Y>wF z2}x0f8Ph>jHYawJ2wXOMN&sYIOw$Q@F|eBY+ceH*RbwzShG0y1!)RRGQ<*(L%-K8Ogwrd1!Z{4W}aTgiB2@^m>CLH1H(kDd0fl*#NxHMpnwPHT}Fs(Ku3i)s*c` zKc*Es#DR|g$U%rrnlpoLBtT4_8?s!gB9s^cH8iZN%q>8@NoxOht* zW_Ep>-|ZQ@q)X4EH%+d0=~_sy8fImxXKr+%A=`}}co8B`hM{Q|#dOMTHce&`a+yrO z4y6VW2dzy)F0ImS!7?6G9CQdn!GaWYh3hRA_#s=F>j`nj84vIB_^rJ0LAI}P`H-!F z?HRxK{@-}9iS@ZIquHC4t2_MRBL3D7{?Jc+=BL(g_ zs#wD+lVMiqTb|nGzE_enH$Tl?mpFaET$n-UOejOa!%@LJi--!T`nKh>`d80Ixr&Nv zQVmtF=hf$y&^EPdh^Zo|j1Eg&Jjs(6xOv4Ld)$6V=hMViHg8{vNJ*1S52?#mMHEp% zrOF#O8;B>BqY_%=SUlJi$SYa{1?!kJ;gM8nYX@E_dVItUYyy6twWOmdWYdg}74a=LYF+Cns`XPimy6OGXteX0nK%F5Z) z6F#N?ic3s=4Vf3j855SKma@Z;=z3O(KCwMwiWu1t!-Ko7@l69?`3QGB!^s00uRXAd zPc}KR6Y3;qEgWQYWRW@SdF0KDbDut3!lSqGsc+-Ue|=l!mR8v9zJRVI{o-H0 zu@ke8e$7X}`pQ>-^ZYyi&e#9=1Aq0guPNVr<$CwvGJAuR+@$PPhi*Ar|83G_4Yqnk_KZj4l#7 z{eFV_*Yd1}syx&22Y2U3wxAO6f)3Wg6Swg3H*nu;c;W)u5>mnRC==$Y$|7r?LG4r} z5l@r3KZ#5y>WD^!q?$FQsr{rPNl8_}tXgBsB0)lhq=<@D=|$zO>oi6_-o{GD9$7S3 zrd1NAqCk^ql|x2SZ8YcH<~+-}$vNfRY|Ykvf7j*s#wVH|ZSVXqmp7j%Dor{Zhokhk@OW0cOrttbh8a z-o+QjT=?hU3@lmXbe#B|ul=1`i=LKK|Lp62?Xj1C@*iCOzQ;fG+tTAJ4rc7Mq(m<) zM%P3(^2pfH3whv{Yvhmf_<7&)ln*QqXu*QDBF0v|?Kq*#BrvOGZ_cR+sWdB*AZtcd zG(u`vYN`w=i_qc{l~FErF{Kmr{pT4H;bD=CQk*!>7kP z_OQ=D!pBJ`G?nF3#>Zy65C|{gqS{DqRS2&>fg~}iOh~MwTyI1rM5Ic@L{W{1qX?z& zP02o~M=M(USyIYlZln;#QiDhcT4UX?K-F0{bV3SOB7}9KDo@3Hma_(LST^O*BhnBy zVC3ZeAc%}sYILh3MI(#l39{)`Ay;{k5rkxmrbJXXl}Civ{*@3`%1X>b#APL+E*^}8 ztGkhmfHAS_Se;D-4U7p7bR-uP#=tTo(}Da-aQPOwdb`VmosQgb`+V_fw@3Q6Aqra! z%}9&~yImi_7ov^zMSlmDXIHOB&(UqSzVw|x{#$q5`yKi8nNg6$5UhIgK$D{zXxE5i zL0*$ae4kq%;vK)vo8E6HuAtZHjYHwkDTTveKNt$_z+TS#Nd}N1d<-9bB*soHr4-Uw zq_Gq$l@zaRxA>yv)Vjkw^Q7NDkQ4*>X4sxAq{E=9u zrk?z%J*v-H(a-xCUh@^IkyB8-9BWxg9pB0%GnsNekq|K=L_WvBP2jMhDN#ioU6Daz zoPxp0CL)YNNEI_nOkr3A;z|iZNIngc>vk8!s5C+{I-jy#D)Le)FtkjRvSGu4sL!e^ zMbJcdh1-9xuq=zjoohlEmn0!Kth))#w{xQTYya`v-u=t(dvO8(iPc)+&F_9He&MJ8 z?#CYcwcq*Z@7{FT?+y-^Xp6VZMv5SU8J@A8S%a)OR2DW_Oeex?_4+{g*aZ<)4R7sQ z<~=!xqAqHxo~IJ}7(0CvW0#PTEF3f(2zekWDL8AK{{_z7-97f^O~+Hd*quky8?4XiS=x7d)4I?!_%H{B@v7>RY@KQsvQa zbX`JtNX5^|CS0X%2!_{`_RSw2e<5%Dz9_1atWrX!c6|vMQ%IU-OHNcGnkm^rj6pDo zRAK`3)?Gwy%;<|w=%o^r%xF`1_vlzf9c3;rI8`PCLtWt|5aCpC@+vVv!#Lt2vl(Xe z!63{stF!C!jsO5407*naR3#_6ad_ent~~R^>2o)~sKojcFXIS#?Hj*l_7x}Iv-M9- zo|*Z>WxP()axl`YL78vU4}_r|!d3E9JPq+{ShZPrFgj1od3cBIefl7Rw8yPiNcY>_bBd8*CS)Pkn@PvKMU1XR zoiygmikM_RU(Dmz82ni%2Ng8lGa!5#p z8H%fnUZl7hlVoc&Em0zhNHbc!iPocWMBwfoHPGAY>aEd`jvJ{axq>UJ1s}Vcb9=P? zGy%?z3bU5+klZtyv39y01Xjl3KyDCcw-8GfExBJW?t1-Quez5%Q$UCE=l`1CZ~xN= z|KTUj@xeF6xBm}+`zd38&C(eP%bqKT><44Rr4_4|Lj_~6=g=8~VT}iUfrhnHR+NDe z)>@-gq8a?eDz^JbL2_wB!kw5zL}}CX5s{?i#2nTgPu|S+m!k`GsLo{0&gl<{K%9MH zEs$tB%(-r?GlMfYV>wo3#v~^>y40CV^UrP_KFT_8&-Y_ksV}k7ij3B*T|6x6;nCb{ z>f4lHT%A=n{>%G$Fjvg-3hpo$4N?GRUqe!uKu-&E4dXv$tn2c7;qFC4whHq?8z2mhXRZsMp7Xx1*!HtsG!yumLK^S`x=fz49*|(@kzk%V zy~UZ1-8Eg|EyAO7_p|HW7F z-jgf~ha;B`xqQI2YZM_3oCwOuDzn#f*fY$yY^+v9j|6E&KjR=XIERGQi3Kr;d&DR) zXlMl=*8JgVwyG& z9=bnUe?}lfDH4$&d#HtP)E(-Zd1_37O2^m#6md3nt6ty-K(u%?dNB{5I76?D;Vbbq zj@jt$?u$W^NmWRDFh(phBbEj`#A6KW&CRWtd5jkBqJ|pJw0!y$pE}L5AqB00(Hd5G z{0tA>#$8XFuE#3s%r>c@+ha11sx5JyUZzp;UF-y9PIO4eZ~pRoo>%Ad;u-iqtkw#N zm&BR>_igVvz_v;E^|#&j?Kj`H*T_$NX1Q`3*Z_iWdi?ofSg&Z5Vt>St^dj0tnHP5?SZq4Q~l)+1FF4Vtk zG9Jit#A!_V+EwfvFPObMrML>I_@fK|{YC7TC%@ItZ8gm}j8$YhYG_Du?bY3NB2*XC zl!jms)SXfTr!c~U!A&C~P~+k*_uWJv{O2E{=#(-iiCj3j;+`vPKep%B7O5y z#>6VCr)X2Gwm%cn5Q+5xDYFRfxWIRR>q~$3S3mjU9r#bPhHK|_n-uUDE_}nU{KfbF z)9?Mj*3Qn(Ic~W#J-Or)XSvdF1@;ol0&8Z<;Tb;sW$}r($|vVo%d%(8lmQ5Pt!jKR9*9u0_-9EnMg9P%GnT!WYM zCBN+HxO>_5jf6*b9O;w95g1H}KWk}02q}c+nqgdPT%E_$J2L8xBpTm@n@YsyA=DG_ zR5shBEw5uKlw*-p6{&w|&Vy+s1Fea8gh&cqX9+}tQDsauVhIy0BuBckr4OF)t&toN zM9~-pCrVACaWr`NBoCb9$t`rif&ofki8=c(I*y#ZbTB^kdR(DQ1+E|nlc%ZvlwWd?SaKWYf}lmF&;geKfPH+-lVIP)jW?) z8i!Im29;GS`>j-_wMRrqOf4mwG8jGlvvrYw67j)=RyI-1&n)8KF81GJk6mkH)zYVM zB~vAyDuKk&2)hO&PT--65~yYi36T)lq$dV42=g_s>)Us%&u{hgt3h1EP?M%0E{Ez- zEcf0F5yg5NR+;-xjPE};v_qun2Nc)1k#r@Pa?o;S$u~SeTWCg70U<*Yk}wvo&nWeP z14{*&yd@@#i6mqwg;vKCuR{3_q}uetQza#vxEiwU=TwN4Ql4E#+tJM+0({V>{l~Yg|K9jSf0~Ey-}`}`JMUVxO^M}t?MQ0!luzs$ z^&~wxe!>ZJZ$!hwgS8A0L6XSD=Og6& z8)w%aIyqqeq<9t`6h{g1VVpT;zNhRZ-u5Y$!uEP{_Or?537hER8M=`%VZvesf(5Nk zkke+sM%rzXGb&w61hWO}L>_4aS#W*DttWW!{lD^O*vk6ST)>;HH~8^iz5hRbMc&!Q z_IXo-29Mv&Q*YwQU4G-%s~5f}-SS82u75P#lxcS`7z-dCc$iAVT{5Eb(p!_qfeYY#bLn<&4?$gkOqbZeDY?f z;68nHcX4$KNt99&qAns)*ESvs8sdTnn$#^sE3^Qs^b4(a+Au%9Ts8cL*+*2<<~(xT z{~Y0N4isT*^pxgxE7xa8$9$1BH6|(=IX4y|UftPjTv$x!$4zYzgn6ms+EFNO5s?D1 z2$!uQAU0`15t)X3wY*6P5o5xpJTy7Gme*a@Jc?%>N{l630*gvBvjAsi+`VFiC6oQA zHpb1cGC8r6Pw1r@D_KEV?o&gnl6^6XAT3)RbD491;?d`rds6WuEe7E!w8p zDr!zrRhPQkZM|}mWD7WZZ;9N=yS{ln;Ri%PbURQ6<-#n2*{doRf~a% zpa5}%%aa@bz|QCclW8&6Xy_pkf+dEALAbrATcJTTG)_unMc`_~%xD`f?{OmYuD|h) z_xxde{tgHI(q6y<#1=!IbQ)!_^E=&_FWaB^;MsS5YU`UWl^_1@SFe)p!Cr7+6yfBE zCDuXe@sTyGgtc%T)?=(jTb6;*w8A@T=#HQw8lfKIE@4@`!IK0DGSZ-laR!;|a~^y9 zPRxiNfsvpA(#Icoo zG@(5bYt%2Zuu*V{iD*B$5;nn&by2lB#*ypG6KnGIRj1kAhgSAG_Oq|?DR)ZX*cds& zJ)#JQIf`)7(_-ybY@KD*&}Twe43R7_b9b{aJEkzP&99{lR;(27N9#*5TMKJqbQklZ zzb}Q<6)rWYtVX(yli|#3*UTnnXpsp_X?bcdcgCp|+XG^(6A|pq`SfWXc{#B=#h`B` znwF+TTBPBWFf;lCZaqgDdF>ti@H<{EVlPT5Ki{j1iw}SBBd1SrV!@e^#dhABf;V#8BB0`G0MbtgZCzAFgV2N7ayBl@v99HpJ z2BTT=Uc)UWp-71Pv@z9@ui5ksN1+5|gAJ`Sq56B%sl~Nb84Oi%+0hw~o#CO^@u@G{ z+Rd@E=UW9b?Togi6*?g$W{IxBUBofiAJ?yW>$mToJpcLNvcAMiXno@U1szB!(#N#vWzm*T%$(0#vC4v)cw)$uW$J{q>t}%0E&V8rj-`&i^J2cgsTG<*6nrRbR z*>UdK<76gQJSF_ztKm-<;&Ytmv?1;{s)iyZ3|Np=5;dd2%;#|;UaYPN*0-44Zva4DX9*U^f5AKr)e zc#UeE`rY9cK8)i~#^PRk1mQuk`lM9_i^gXDr8t$({d_~}y1!JfMa6A|5*x!1`i%qQ0?=n4DBBv-V6!I$4(w2JAts+{F z$NO(QxcQzF@_4)c#NP6}rVlLaWnaJDK5+QThaxAgj$Av(M{nb!cW~0U_i1K5E0H{f z_0)I1261WT_n(Ykc{z_R__~L9<_!GypE)jJWl?Vw@C>4Yv6rsXy zgYYTfv^J!UG}YoF5ix><=a7IN>&1(^1leS2GuDSpBC47eTTA77Bu+7Hk=aqEzqfk1 z`<~ozUD>~b2VTN%*0+AZ*GpdaXb<}IPu~8`f9b^q{HMIuMNBD%4P+-Z*ul8Bek!9}mB@?!pihq++@Ys0@1}LO zr+0YdZe9`dS3kPk7FN!D$%X5jIpCn>u))T(!gGqO<6Vy>h8AnE6*IVSjphM+?7r^JpNX43Im*b@o_UR8m>lU-p0(H8loJXPT3bq0 zW