From 586e0f3d89e48652e2ec3587f5e5584e13109b4a Mon Sep 17 00:00:00 2001 From: azv Date: Wed, 20 Apr 2016 12:12:03 +0300 Subject: [PATCH] Projection of outer edge onto the sketch plane (improvement #1297) Not visible in GUI now --- src/GeomAPI/GeomAPI_Circ.cpp | 8 + src/GeomAPI/GeomAPI_Circ.h | 3 + src/GeomAPI/GeomAPI_Pln.cpp | 12 + src/GeomAPI/GeomAPI_Pln.h | 6 +- src/SketchPlugin/CMakeLists.txt | 2 + src/SketchPlugin/SketchPlugin_Plugin.cpp | 6 + src/SketchPlugin/SketchPlugin_Projection.cpp | 221 +++++++++++++++++++ src/SketchPlugin/SketchPlugin_Projection.h | 73 ++++++ src/SketchPlugin/SketchPlugin_Validators.cpp | 59 +++++ src/SketchPlugin/SketchPlugin_Validators.h | 16 ++ src/SketchPlugin/icons/projection.png | Bin 0 -> 668 bytes src/SketchPlugin/plugin-Sketch.xml | 21 +- 12 files changed, 424 insertions(+), 3 deletions(-) create mode 100644 src/SketchPlugin/SketchPlugin_Projection.cpp create mode 100644 src/SketchPlugin/SketchPlugin_Projection.h create mode 100644 src/SketchPlugin/icons/projection.png diff --git a/src/GeomAPI/GeomAPI_Circ.cpp b/src/GeomAPI/GeomAPI_Circ.cpp index dca089797..c7c4573f0 100644 --- a/src/GeomAPI/GeomAPI_Circ.cpp +++ b/src/GeomAPI/GeomAPI_Circ.cpp @@ -92,3 +92,11 @@ const bool GeomAPI_Circ::parameter(const std::shared_ptr thePoint, Handle(Geom_Circle) aCurve = new Geom_Circle(*MY_CIRC); return GeomLib_Tool::Parameter(aCurve, thePoint->impl(), theTolerance, theParameter) == Standard_True; } + +//================================================================================================= +std::shared_ptr GeomAPI_Circ::normal() const +{ + const gp_Ax1& anAxis = MY_CIRC->Axis(); + const gp_Dir& aDir = anAxis.Direction(); + return std::shared_ptr(new GeomAPI_Dir(aDir.X(), aDir.Y(), aDir.Z())); +} diff --git a/src/GeomAPI/GeomAPI_Circ.h b/src/GeomAPI/GeomAPI_Circ.h index 32cac8968..625ca3895 100644 --- a/src/GeomAPI/GeomAPI_Circ.h +++ b/src/GeomAPI/GeomAPI_Circ.h @@ -42,6 +42,9 @@ class GeomAPI_Circ : public GeomAPI_Interface /// Return radius of the circle GEOMAPI_EXPORT double radius() const; + /// Return orthogonal direction to the circle's plane + GEOMAPI_EXPORT std::shared_ptr normal() const; + /// Project point on circle GEOMAPI_EXPORT const std::shared_ptr project( const std::shared_ptr& thePoint) const; diff --git a/src/GeomAPI/GeomAPI_Pln.cpp b/src/GeomAPI/GeomAPI_Pln.cpp index 4ba715898..347e994f4 100644 --- a/src/GeomAPI/GeomAPI_Pln.cpp +++ b/src/GeomAPI/GeomAPI_Pln.cpp @@ -74,3 +74,15 @@ std::shared_ptr GeomAPI_Pln::intersect(const std::shared_ptrdot(aLocation->decreased(aLineLoc)) / aDot; return std::shared_ptr(new GeomAPI_Pnt(aLineLoc->added(aLineDir->multiplied(aParam)))); } + +std::shared_ptr GeomAPI_Pln::project(const std::shared_ptr& thePoint) const +{ + std::shared_ptr aNormal = direction()->xyz(); + std::shared_ptr aLocation = location()->xyz(); + + std::shared_ptr aVec = thePoint->xyz()->decreased(aLocation); + double aDot = aNormal->dot(aVec); + std::shared_ptr aProjection = + aLocation->added(aVec->decreased(aNormal->multiplied(aDot))); + return std::shared_ptr(new GeomAPI_Pnt(aProjection)); +} diff --git a/src/GeomAPI/GeomAPI_Pln.h b/src/GeomAPI/GeomAPI_Pln.h index 81bc519a0..3c319b789 100644 --- a/src/GeomAPI/GeomAPI_Pln.h +++ b/src/GeomAPI/GeomAPI_Pln.h @@ -54,7 +54,11 @@ class GeomAPI_Pln : public GeomAPI_Interface /// Returns intersection point or empty if no intersections GEOMAPI_EXPORT - std::shared_ptr intersect(const std::shared_ptr& theLine) const; + std::shared_ptr intersect(const std::shared_ptr& theLine) const; + + /// Returns projection of the given point onto the plane + GEOMAPI_EXPORT + std::shared_ptr project(const std::shared_ptr& thePoint) const; }; #endif diff --git a/src/SketchPlugin/CMakeLists.txt b/src/SketchPlugin/CMakeLists.txt index ce8173d6c..c2d0224e2 100644 --- a/src/SketchPlugin/CMakeLists.txt +++ b/src/SketchPlugin/CMakeLists.txt @@ -37,6 +37,7 @@ SET(PROJECT_HEADERS SketchPlugin_ExternalValidator.h SketchPlugin_Validators.h SketchPlugin_Tools.h + SketchPlugin_Projection.h ) SET(PROJECT_SOURCES @@ -72,6 +73,7 @@ SET(PROJECT_SOURCES SketchPlugin_ExternalValidator.cpp SketchPlugin_Validators.cpp SketchPlugin_Tools.cpp + SketchPlugin_Projection.cpp ) SET(PROJECT_LIBRARIES diff --git a/src/SketchPlugin/SketchPlugin_Plugin.cpp b/src/SketchPlugin/SketchPlugin_Plugin.cpp index b0f84cc79..635583b0c 100644 --- a/src/SketchPlugin/SketchPlugin_Plugin.cpp +++ b/src/SketchPlugin/SketchPlugin_Plugin.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -81,6 +82,8 @@ SketchPlugin_Plugin::SketchPlugin_Plugin() new SketchPlugin_ArcTangentPointValidator); aFactory->registerValidator("SketchPlugin_IntersectionValidator", new SketchPlugin_IntersectionValidator); + aFactory->registerValidator("SketchPlugin_ProjectionValidator", + new SketchPlugin_ProjectionValidator); // register this plugin ModelAPI_Session::get()->registerPlugin(this); @@ -126,6 +129,8 @@ FeaturePtr SketchPlugin_Plugin::createFeature(string theFeatureID) return FeaturePtr(new SketchPlugin_Circle); } else if (theFeatureID == SketchPlugin_Arc::ID()) { return FeaturePtr(new SketchPlugin_Arc); + } else if (theFeatureID == SketchPlugin_Projection::ID()) { + return FeaturePtr(new SketchPlugin_Projection); } else if (theFeatureID == SketchPlugin_ConstraintCoincidence::ID()) { return FeaturePtr(new SketchPlugin_ConstraintCoincidence); } else if (theFeatureID == SketchPlugin_ConstraintCollinear::ID()) { @@ -203,6 +208,7 @@ std::shared_ptr SketchPlugin_Plugin aMsg->setState(SketchPlugin_Line::ID(), aHasSketchPlane); aMsg->setState(SketchPlugin_Circle::ID(), aHasSketchPlane); aMsg->setState(SketchPlugin_Arc::ID(), aHasSketchPlane); + aMsg->setState(SketchPlugin_Projection::ID(), aHasSketchPlane); aMsg->setState(SketchPlugin_ConstraintCoincidence::ID(), aHasSketchPlane); aMsg->setState(SketchPlugin_ConstraintCollinear::ID(), aHasSketchPlane); aMsg->setState(SketchPlugin_ConstraintDistance::ID(), aHasSketchPlane); diff --git a/src/SketchPlugin/SketchPlugin_Projection.cpp b/src/SketchPlugin/SketchPlugin_Projection.cpp new file mode 100644 index 000000000..de39ac781 --- /dev/null +++ b/src/SketchPlugin/SketchPlugin_Projection.cpp @@ -0,0 +1,221 @@ +// Copyright (C) 2014-20xx CEA/DEN, EDF R&D --> + +// File: SketchPlugin_Projection.cpp +// Created: 07 May 2014 +// Author: Artem ZHIDKOV + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +static const double tolerance = 1.e-7; + +SketchPlugin_Projection::SketchPlugin_Projection() + : SketchPlugin_SketchEntity(), + myIsComputing(false) +{ +} + +void SketchPlugin_Projection::initDerivedClassAttributes() +{ + data()->addAttribute(EXTERNAL_FEATURE_ID(), ModelAPI_AttributeSelection::typeId()); + data()->addAttribute(PROJECTED_FEATURE_ID(), ModelAPI_AttributeRefAttr::typeId()); + data()->attribute(PROJECTED_FEATURE_ID())->setIsArgument(false); + + data()->addAttribute(EXTERNAL_ID(), ModelAPI_AttributeSelection::typeId()); + ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), EXTERNAL_ID()); +} + +void SketchPlugin_Projection::execute() +{ + AttributeRefAttrPtr aRefAttr = data()->refattr(PROJECTED_FEATURE_ID()); + if (!aRefAttr || !aRefAttr->isInitialized()) + return; + FeaturePtr aProjection = ModelAPI_Feature::feature(aRefAttr->object()); + + bool hasResult = lastResult(); + + std::shared_ptr anEdge; + if (aProjection->getKind() == SketchPlugin_Line::ID()) { + std::shared_ptr aStartPnt = std::dynamic_pointer_cast( + aProjection->attribute(SketchPlugin_Line::START_ID())); + std::shared_ptr aEndPnt = std::dynamic_pointer_cast( + aProjection->attribute(SketchPlugin_Line::END_ID())); + + // edge to store result + std::shared_ptr aFirst = sketch()->to3D(aStartPnt->x(), aStartPnt->y()); + std::shared_ptr aLast = sketch()->to3D(aEndPnt->x(), aEndPnt->y()); + anEdge = GeomAlgoAPI_EdgeBuilder::line(aFirst, aLast); + } + else { + std::shared_ptr aSketchPlane = sketch()->plane(); + std::shared_ptr aNormal = aSketchPlane->direction(); + + if (aProjection->getKind() == SketchPlugin_Circle::ID()) { + std::shared_ptr aCenterPnt = std::dynamic_pointer_cast( + aProjection->attribute(SketchPlugin_Circle::CENTER_ID())); + + // edge to store result + std::shared_ptr aCenter = sketch()->to3D(aCenterPnt->x(), aCenterPnt->y()); + double aRadius = aProjection->real(SketchPlugin_Circle::RADIUS_ID())->value(); + anEdge = GeomAlgoAPI_EdgeBuilder::lineCircle(aCenter, aNormal, aRadius); + } + else if (aProjection->getKind() == SketchPlugin_Arc::ID()) { + std::shared_ptr aCenterPnt = std::dynamic_pointer_cast( + aProjection->attribute(SketchPlugin_Arc::CENTER_ID())); + std::shared_ptr aStartPnt = std::dynamic_pointer_cast( + aProjection->attribute(SketchPlugin_Arc::START_ID())); + std::shared_ptr aEndPnt = std::dynamic_pointer_cast( + aProjection->attribute(SketchPlugin_Arc::END_ID())); + + // edge to store result + std::shared_ptr aCenter = sketch()->to3D(aCenterPnt->x(), aCenterPnt->y()); + std::shared_ptr aFirst = sketch()->to3D(aStartPnt->x(), aStartPnt->y()); + std::shared_ptr aLast = sketch()->to3D(aEndPnt->x(), aEndPnt->y()); + anEdge = GeomAlgoAPI_EdgeBuilder::lineCircleArc(aCenter, aFirst, aLast, aNormal); + } + } + + std::shared_ptr aConstr = document()->createConstruction(data()); + aConstr->setShape(anEdge); + aConstr->setIsInHistory(false); + setResult(aConstr); + + if (!hasResult) + aProjection->selection(EXTERNAL_ID())->setValue(lastResult(), lastResult()->shape()); +} + +void SketchPlugin_Projection::move(double theDeltaX, double theDeltaY) +{ + // feature cannot be moved +} + +void SketchPlugin_Projection::attributeChanged(const std::string& theID) +{ + if ((theID == EXTERNAL_FEATURE_ID() || theID == EXTERNAL_ID()) && !myIsComputing) { + myIsComputing = true; + computeProjection(theID); + myIsComputing = false; + } +} + +void SketchPlugin_Projection::computeProjection(const std::string& theID) +{ + AttributeSelectionPtr aExtFeature = + std::dynamic_pointer_cast(attribute(EXTERNAL_FEATURE_ID())); + + std::shared_ptr anEdge; + if (aExtFeature && aExtFeature->value() && aExtFeature->value()->isEdge()) { + anEdge = std::shared_ptr(new GeomAPI_Edge(aExtFeature->value())); + } else if (aExtFeature->context() && aExtFeature->context()->shape() && aExtFeature->context()->shape()->isEdge()) { + anEdge = std::shared_ptr(new GeomAPI_Edge(aExtFeature->context()->shape())); + } + if (!anEdge.get()) + return; + + AttributeRefAttrPtr aRefAttr = data()->refattr(PROJECTED_FEATURE_ID()); + FeaturePtr aProjection; + if (aRefAttr && aRefAttr->isInitialized()) + aProjection = ModelAPI_Feature::feature(aRefAttr->object()); + + // if the type of feature differs with already selected, remove it and create once again + if (aProjection) { + if ((anEdge->isLine() && aProjection->getKind() != SketchPlugin_Line::ID()) || + (anEdge->isCircle() && aProjection->getKind() != SketchPlugin_Circle::ID()) || + (anEdge->isArc() && aProjection->getKind() != SketchPlugin_Arc::ID())) { + DocumentPtr aDoc = sketch()->document(); + aDoc->removeFeature(aProjection); + aProjection = FeaturePtr(); + aRefAttr->setObject(aProjection); + } + } + + std::shared_ptr aSketchPlane = sketch()->plane(); + + if (anEdge->isLine()) { + std::shared_ptr aFirst = aSketchPlane->project(anEdge->firstPoint()); + std::shared_ptr aLast = aSketchPlane->project(anEdge->lastPoint()); + + std::shared_ptr aFirstInSketch = sketch()->to2D(aFirst); + std::shared_ptr aLastInSketch = sketch()->to2D(aLast); + if (aFirstInSketch->distance(aLastInSketch) < tolerance) + return; // line is semi-orthogonal to the sketch plane + + if (!aProjection) + aProjection = sketch()->addFeature(SketchPlugin_Line::ID()); + + // update attributes of projection + std::shared_ptr aStartPnt = std::dynamic_pointer_cast( + aProjection->attribute(SketchPlugin_Line::START_ID())); + std::shared_ptr aEndPnt = std::dynamic_pointer_cast( + aProjection->attribute(SketchPlugin_Line::END_ID())); + aStartPnt->setValue(aFirstInSketch); + aEndPnt->setValue(aLastInSketch); + } + else if (anEdge->isCircle()) { + std::shared_ptr aCircle = anEdge->circle(); + double aRadius = aCircle->radius(); + + std::shared_ptr aCenter = aSketchPlane->project(aCircle->center()); + std::shared_ptr aCenterInSketch = sketch()->to2D(aCenter); + + if (!aProjection) + aProjection = sketch()->addFeature(SketchPlugin_Circle::ID()); + + // update attributes of projection + std::shared_ptr aCenterPnt = std::dynamic_pointer_cast( + aProjection->attribute(SketchPlugin_Circle::CENTER_ID())); + aCenterPnt->setValue(aCenterInSketch); + aProjection->real(SketchPlugin_Circle::RADIUS_ID())->setValue(aRadius); + } + else if (anEdge->isArc()) { + std::shared_ptr aFirst = aSketchPlane->project(anEdge->firstPoint()); + std::shared_ptr aLast = aSketchPlane->project(anEdge->lastPoint()); + std::shared_ptr aFirstInSketch = sketch()->to2D(aFirst); + std::shared_ptr aLastInSketch = sketch()->to2D(aLast); + + std::shared_ptr aCircle = anEdge->circle(); + std::shared_ptr aCenter = aSketchPlane->project(aCircle->center()); + std::shared_ptr aCenterInSketch = sketch()->to2D(aCenter); + + if (!aProjection) + aProjection = sketch()->addFeature(SketchPlugin_Arc::ID()); + + // update attributes of projection + std::shared_ptr aCenterPnt = std::dynamic_pointer_cast( + aProjection->attribute(SketchPlugin_Arc::CENTER_ID())); + std::shared_ptr aStartPnt = std::dynamic_pointer_cast( + aProjection->attribute(SketchPlugin_Arc::START_ID())); + std::shared_ptr aEndPnt = std::dynamic_pointer_cast( + aProjection->attribute(SketchPlugin_Arc::END_ID())); + aStartPnt->setValue(aFirstInSketch); + aEndPnt->setValue(aLastInSketch); + aCenterPnt->setValue(aCenterInSketch); + } + + aProjection->execute(); + aRefAttr->setObject(aProjection); + + if (theID == EXTERNAL_FEATURE_ID()) + selection(EXTERNAL_ID())->setValue(aExtFeature->context(), aExtFeature->context()->shape()); +} diff --git a/src/SketchPlugin/SketchPlugin_Projection.h b/src/SketchPlugin/SketchPlugin_Projection.h new file mode 100644 index 000000000..f71b899ca --- /dev/null +++ b/src/SketchPlugin/SketchPlugin_Projection.h @@ -0,0 +1,73 @@ +// Copyright (C) 2014-20xx CEA/DEN, EDF R&D --> + +// File: SketchPlugin_Projection.h +// Created: 20 April 2016 +// Author: Artem ZHIDKOV + +#ifndef SketchPlugin_Projection_H_ +#define SketchPlugin_Projection_H_ + +#include "SketchPlugin_SketchEntity.h" + +/** \class SketchPlugin_Projection + * \ingroup Plugins + * \brief Feature for creation of external feature as a projection onto the sketch plane. + */ +class SketchPlugin_Projection : public SketchPlugin_SketchEntity +{ +public: + /// Projection feature kind + inline static const std::string& ID() + { + static const std::string MY_PROJECTION_ID("SketchProjection"); + return MY_PROJECTION_ID; + } + /// Returns the kind of a feature + virtual const std::string& getKind() + { + static std::string MY_KIND = SketchPlugin_Projection::ID(); + return MY_KIND; + } + + static const std::string& EXTERNAL_FEATURE_ID() + { + static std::string MY_EXT_FEATURE_ID("ExternalFeature"); + return MY_EXT_FEATURE_ID; + } + + static const std::string& PROJECTED_FEATURE_ID() + { + static std::string MY_PROJ_FEATURE_ID("ProjectedFeature"); + return MY_PROJ_FEATURE_ID; + } + + /// Returns true because projected feature is always external + virtual bool isFixed() + { return true; } + + /// Creates a new part document if needed + SKETCHPLUGIN_EXPORT virtual void execute(); + + /// Moves the feature + /// \param theDeltaX the delta for X coordinate is moved + /// \param theDeltaY the delta for Y coordinate is moved + SKETCHPLUGIN_EXPORT virtual void move(const double theDeltaX, const double theDeltaY); + + /// Called on change of any argument-attribute of this object: for external point + SKETCHPLUGIN_EXPORT virtual void attributeChanged(const std::string& theID); + + /// Use plugin manager for features creation + SketchPlugin_Projection(); + +protected: + /// \brief Initializes attributes of derived class. + virtual void initDerivedClassAttributes(); + +private: + /// \brief Find projection of a feature onto sketch plane + void computeProjection(const std::string& theID); + + bool myIsComputing; +}; + +#endif diff --git a/src/SketchPlugin/SketchPlugin_Validators.cpp b/src/SketchPlugin/SketchPlugin_Validators.cpp index 6b4b55c92..6f66dc5c7 100755 --- a/src/SketchPlugin/SketchPlugin_Validators.cpp +++ b/src/SketchPlugin/SketchPlugin_Validators.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -813,3 +814,61 @@ bool SketchPlugin_IntersectionValidator::isValid(const AttributePtr& theAttribut std::shared_ptr aNormal = aPlane->direction(); return fabs(aNormal->dot(aLineDir)) > tolerance * tolerance; } + +bool SketchPlugin_ProjectionValidator::isValid(const AttributePtr& theAttribute, + const std::list& theArguments, + std::string& theError) const +{ + if (theAttribute->attributeType() != ModelAPI_AttributeSelection::typeId()) { + theError = "The attribute with the " + theAttribute->attributeType() + " type is not processed"; + return false; + } + + AttributeSelectionPtr aFeatureAttr = + std::dynamic_pointer_cast(theAttribute); + std::shared_ptr anEdge; + if(aFeatureAttr && aFeatureAttr->value() && aFeatureAttr->value()->isEdge()) { + anEdge = std::shared_ptr(new GeomAPI_Edge(aFeatureAttr->value())); + } else if(aFeatureAttr->context() && aFeatureAttr->context()->shape() && + aFeatureAttr->context()->shape()->isEdge()) { + anEdge = std::shared_ptr(new GeomAPI_Edge(aFeatureAttr->context()->shape())); + } + + if (!anEdge) { + theError = "The attribute " + theAttribute->id() + " should be an edge"; + return false; + } + + // find a sketch + std::shared_ptr aSketch; + std::set aRefs = theAttribute->owner()->data()->refsToMe(); + std::set::const_iterator anIt = aRefs.begin(); + for (; anIt != aRefs.end(); ++anIt) { + CompositeFeaturePtr aComp = + std::dynamic_pointer_cast((*anIt)->owner()); + if (aComp && aComp->getKind() == SketchPlugin_Sketch::ID()) { + aSketch = std::dynamic_pointer_cast(aComp); + break; + } + } + if (!aSketch) { + theError = "There is no sketch referring to the current feature"; + return false; + } + + std::shared_ptr aPlane = aSketch->plane(); + std::shared_ptr aNormal = aPlane->direction(); + + if (anEdge->isLine()) { + std::shared_ptr aLineDir = anEdge->line()->direction(); + double aDot = aNormal->dot(aLineDir); + return aDot > -1.0 + tolerance && aDot < 1.0 - tolerance; + } + else if (anEdge->isCircle() || anEdge->isArc()) { + std::shared_ptr aCircNormal = anEdge->circle()->normal(); + double aDot = fabs(aNormal->dot(aCircNormal)); + return fabs(aDot - 1.0) < tolerance * tolerance; + } + + return false; +} diff --git a/src/SketchPlugin/SketchPlugin_Validators.h b/src/SketchPlugin/SketchPlugin_Validators.h index 49854af5e..f201e1b3a 100644 --- a/src/SketchPlugin/SketchPlugin_Validators.h +++ b/src/SketchPlugin/SketchPlugin_Validators.h @@ -234,4 +234,20 @@ class SketchPlugin_IntersectionValidator : public ModelAPI_AttributeValidator std::string& theError) const; }; +/**\class SketchPlugin_ProjectionValidator + * \ingroup Validators + * \brief Validator for the attribute to be projected onto the sketch plane. + */ +class SketchPlugin_ProjectionValidator : public ModelAPI_AttributeValidator +{ + public: + //! returns true if attribute is valid + //! \param theAttribute the checked attribute + //! \param theArguments arguments of the attribute + //! \param theError error message + virtual bool isValid(const AttributePtr& theAttribute, + const std::list& theArguments, + std::string& theError) const; +}; + #endif diff --git a/src/SketchPlugin/icons/projection.png b/src/SketchPlugin/icons/projection.png new file mode 100644 index 0000000000000000000000000000000000000000..38ec567e5c4d9fdac82495660b34c39364acf409 GIT binary patch literal 668 zcmV;N0%QG&P)Tu8H!#&^gy+T!6y0h(~ zk^?PUl_PVb`6>QECM*Qbg6;x#3wo7~6OsP{puGAG6C%l`nfFOAdp^;7Xxl~rCoGme zPP~b?v^gsB&RT9-<%uISpn5Id(z>@{N89w$r^(`<05|DnV>5-J<;o-UC6*q5sb>!_ z7NfE8gB|-$8rO}BvFB1=Heu20h^SZ&i^yFU*bGzvA|)}JA`k%5E6eR-%z#?E7(hR& zY1fgAD#sB-(ftOv6F$eTni6VK0L5r*e9^e^!t1f4)XXWa1~LfDS5FoVK+?-T1(^Sy zC<&SZ@H)E&8mzyRURiFhb6wOoLQa%n2iarl9C>@|r$D2MHg4p6pkyS%>FGlLLmeR5 zoxP%vcE#KSnv2ocIKbL5L^Nr@e0?QmcaQ+J zTtX=W(?KAm^#_nXU)&mCrZ99MG;(${pR=pw7GObPA>0Ol>#*H5+z@o`}pIFy} zQ1c@r3xV}}Ks{IVwj*3X^|A`b9BW#?j1A9)cYgsgd)nA)yG7Rk0000 - + + + + -- 2.39.2