//---------------------------------------------------------------------------- // EgalTech 2013-2013 //---------------------------------------------------------------------------- // File : GdbExecutor.cpp Data : 25.11.13 Versione : 1.3a1 // Contenuto : Implementazione della classe GdbExecutor. // // // // Modifiche : 27.03.13 DS Creazione modulo. // // //---------------------------------------------------------------------------- //--------------------------- Include ---------------------------------------- #include "stdafx.h" #include #include #include "/EgtDev/Include/EgnStringUtils.h" #include "/EgtDev/Include/EgnStringConverter.h" #include "/EgtDev/Include/EgkGeoPoint3d.h" #include "/EgtDev/Include/EgkGeoVector3d.h" #include "/EgtDev/Include/EgkCurveLine.h" #include "/EgtDev/Include/EgkCurveArc.h" #include "/EgtDev/Include/EgkCurveBezier.h" #include "/EgtDev/Include/EgkCurveComposite.h" #include "/EgtDev/Include/EgtReleasePointer.h" #include "GdbExecutor.h" using namespace std ; //---------------------------------------------------------------------------- IGdbExecutor* CreateGdbExecutor( void) { return static_cast ( new GdbExecutor) ; } //---------------------------------------------------------------------------- GdbExecutor::GdbExecutor( void) { } //---------------------------------------------------------------------------- GdbExecutor::~GdbExecutor( void) { } //---------------------------------------------------------------------------- bool GdbExecutor::Set( IGeomDB* pGdb) { m_pGDB = pGdb ; return ( m_pGDB != nullptr) ; } //---------------------------------------------------------------------------- bool GdbExecutor::Load( const wstring& sFile) { bool bOk ; ifstream ifLoad ; ifLoad.open( sFile) ; if ( ifLoad.good()) { bOk = m_pGDB->Load( ifLoad) ; ifLoad.close() ; } else bOk = false ; return bOk ; } //---------------------------------------------------------------------------- bool GdbExecutor::Save( const wstring& sFile) { bool bOk ; ofstream ofSave ; ofSave.open( sFile) ; if ( ofSave.good()) { bOk = m_pGDB->Save( ofSave) ; ofSave.close() ; } else bOk = false ; return bOk ; } //---------------------------------------------------------------------------- bool GdbExecutor::Execute( const string& sCmd1, const string& sCmd2, const STRVECTOR& vsParams) { STRVECTOR::const_iterator theConstIter ; // output di debug cout << "Cmd = [" << sCmd1 << "/" << sCmd2 << "]" ; cout << " Par = [" ; for ( theConstIter = vsParams.begin() ; theConstIter != vsParams.end() ; theConstIter ++) { if ( theConstIter != vsParams.begin()) cout << "/" ; cout << *theConstIter ; } cout << "]" << endl ; // esecuzione comando if ( sCmd1 == "ALIAS") return ExecuteAlias( sCmd2, vsParams) ; else if ( sCmd1 == "P" || sCmd1 == "POINT") return ExecutePoint( sCmd2, vsParams) ; else if ( sCmd1 == "V" || sCmd1 == "VECTOR") return ExecuteVector( sCmd2, vsParams) ; else if ( sCmd1 == "CL" || sCmd1 == "CURVELINE") return ExecuteCurveLine( sCmd2, vsParams) ; else if ( sCmd1 == "CA" || sCmd1 == "CURVEARC") return ExecuteCurveArc( sCmd2, vsParams) ; else if ( sCmd1 == "CB" || sCmd1 == "CURVEBEZIER") return ExecuteCurveBez( sCmd2, vsParams) ; else if ( sCmd1 == "CC" || sCmd1 == "CURVECOMPO") return ExecuteCurveCompo( sCmd2, vsParams) ; else if ( sCmd1 == "COPY") return ExecuteCopy( vsParams) ; else if ( sCmd1 == "ERASE") return ExecuteErase( vsParams) ; else if ( sCmd1 == "MOVE" || sCmd1 == "TRANSLATE") return ExecuteTranslate( vsParams) ; else if ( sCmd1 == "ROTATE") return ExecuteRotate( vsParams) ; else if ( sCmd1 == "SCALE") return ExecuteScale( vsParams) ; else if ( sCmd1 == "MIRROR") return ExecuteMirror( vsParams) ; else if ( sCmd1 == "LOAD") return ExecuteLoad( vsParams) ; else if ( sCmd1 == "SAVE") return ExecuteSave( vsParams) ; else if ( sCmd1 == "OUTSCL") return ExecuteOutScl( sCmd2, vsParams) ; return false ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteAlias( const string& sCmd2, const STRVECTOR& vsParams) { // analisi ed esecuzione dei comandi if ( sCmd2 == "") { int nId ; // 2 parametri : stringa alias e Id if ( vsParams.size() != 2) return false ; // recupero Id if ( ! FromString( vsParams[1], nId)) return false ; // inserisco le stringhe in coda alla lista return m_AliasMap.insert( pair< string, int>( vsParams[0], nId)).second ; } return false ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecutePoint( const string& sCmd2, const STRVECTOR& vsParams) { // analisi ed esecuzione dei comandi if ( sCmd2 == "MAKE" || sCmd2 == "") { Point3d Pnt ; // 4 parametri : un nome e 3 coordinate if ( vsParams.size() != 4) return false ; // recupero le tre coordinate if ( ! FromString( vsParams[1], Pnt.x) || ! FromString( vsParams[2], Pnt.y) || ! FromString( vsParams[3], Pnt.z)) return false ; // creo il punto IGeoPoint3d* pGPnt = CreateGeoPoint3d() ; if ( pGPnt == nullptr) return false ; pGPnt->Set( Pnt) ; return m_pGDB->AddGeoObj( GetIdParam( vsParams[0]), pGPnt) ; } return false ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteVector( const string& sCmd2, const STRVECTOR& vsParams) { // analisi ed esecuzione dei comandi if ( sCmd2 == "MAKE" || sCmd2 == "") { Vector3d Vect ; // 4 parametri if ( vsParams.size() != 4) return false ; // recupero i tre componenti if ( ! FromString( vsParams[1], Vect.x) || ! FromString( vsParams[2], Vect.y) || ! FromString( vsParams[3], Vect.z)) return false ; // creo il vettore IGeoVector3d* pGVect = CreateGeoVector3d() ; if ( pGVect == nullptr) return false ; pGVect->Set( Vect) ; return m_pGDB->AddGeoObj( GetIdParam( vsParams[0]), pGVect) ; } return false ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteCurveLine( const string& sCmd2, const STRVECTOR& vsParams) { // analisi ed esecuzione dei comandi if ( sCmd2 == "MAKE" || sCmd2 == "") { Point3d ptStart ; Point3d ptEnd ; // 3 parametri if ( vsParams.size() != 3) return false ; // recupero e setto punti iniziale e finale if ( ! GetPointParam( vsParams[1], ptStart) || ! GetPointParam( vsParams[2], ptEnd)) return false ; // creo la linea ReleasePtr pCrvLine( CreateCurveLine()) ; if ( ! IsValid( pCrvLine) || ! pCrvLine->Set( ptStart, ptEnd)) return false ; return m_pGDB->AddGeoObj( GetIdParam( vsParams[0]), Release( pCrvLine)) ; } return false ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteCurveArc( const string& sCmd2, const STRVECTOR& vsParams) { // creo l'arco ReleasePtr pCrvArc( CreateCurveArc()) ; if ( ! IsValid( pCrvArc)) return false ; // arco generico if ( sCmd2 == "MAKE" || sCmd2 == "") { Point3d ptCen ; Vector3d vtN ; double dRad ; Vector3d vtS ; double dAngCenDeg ; double dDeltaZ ; // 7 parametri if ( vsParams.size() != 7) return false ; // centro if ( ! GetPointParam( vsParams[1], ptCen)) return false ; // versore ortogonale al piano della circonferenza if ( ! GetVectorParam( vsParams[2], vtN)) return false ; // raggio if ( ! FromString( vsParams[3], dRad)) return false ; // versore iniziale if ( ! GetVectorParam( vsParams[4], vtS)) return false ; // angolo al centro if ( ! FromString( vsParams[5], dAngCenDeg)) return false ; // deltaZ if ( ! FromString( vsParams[6], dDeltaZ)) return false ; // setto l'arco if ( ! pCrvArc->Set( ptCen, vtN, dRad, vtS, dAngCenDeg, dDeltaZ)) return false ; } // arco nel piano XY else if ( sCmd2 == "PLANEXY" || sCmd2 == "XY") { Point3d ptCen ; double dRad ; double dAngIniDeg ; double dAngCenDeg ; double dDeltaZ ; // 6 parametri if ( vsParams.size() != 6) return false ; // centro if ( ! GetPointParam( vsParams[1], ptCen)) return false ; // raggio if ( ! FromString( vsParams[2], dRad)) return false ; // angolo iniziale if ( ! FromString( vsParams[3], dAngIniDeg)) return false ; // angolo al centro if ( ! FromString( vsParams[4], dAngCenDeg)) return false ; // deltaZ if ( ! FromString( vsParams[5], dDeltaZ)) return false ; // setto l'arco if ( ! pCrvArc->Set( ptCen, dRad, dAngIniDeg, dAngCenDeg, dDeltaZ)) return false ; } // altrimenti errore else return false ; // inserisco l'arco nel DB return m_pGDB->AddGeoObj( GetIdParam( vsParams[0]), Release( pCrvArc)) ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteCurveBez( const string& sCmd2, const STRVECTOR& vsParams) { // creo la curva di Bezier ReleasePtr pCrvBez( CreateCurveBezier()) ; if ( ! IsValid( pCrvBez)) return false ; // curva di Bezier intera (non razionale) if ( sCmd2 == "INTEG" || sCmd2 == "I") { int i ; int nDeg ; Point3d ptP ; // almeno 2 parametri if ( vsParams.size() < 2) return false ; // recupero il grado if ( ! FromString( vsParams[1], nDeg)) return false ; // inizializzo la curva di Bezier if ( ! pCrvBez->Init( nDeg, false)) return false ; // recupero e setto punti di controllo for ( i = 0 ; i <= nDeg ; i ++) { if ( ! GetPointParam( vsParams[i+2], ptP) || ! pCrvBez->SetControlPoint( i, ptP)) return false ; } } // curva di Bezier razionale else if ( sCmd2 == "RATIO" || sCmd2 == "R") { int i ; int nDeg ; double dW ; Point3d ptP ; // almeno 2 parametri if ( vsParams.size() < 2) return false ; // recupero il grado if ( ! FromString( vsParams[1], nDeg)) return false ; // inizializzo la curva di Bezier if ( ! pCrvBez->Init( nDeg, true)) return false ; // recupero e setto punti di controllo for ( i = 0 ; i <= nDeg ; i ++) { if ( ! GetPointWeParam( vsParams[i+2], ptP, dW) || ! pCrvBez->SetControlPoint( i, ptP, dW)) return false ; } } // altrimenti errore else return false ; // inserisco la curva di Bezier nel DB return m_pGDB->AddGeoObj( GetIdParam( vsParams[0]), Release( pCrvBez)) ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteCurveCompo( const string& sCmd2, const STRVECTOR& vsParams) { // curva composita generica if ( sCmd2 == "MAKE" || sCmd2 == "") { bool bErase ; STRVECTOR vsNames ; STRVECTOR::iterator Iter ; const ICurve* pCrv ; ReleasePtr pCrvCompo( CreateCurveComposite()) ; if ( ! IsValid( pCrvCompo)) return false ; // 2 o 3 parametri switch ( vsParams.size()) { case 2 : bErase = false ; break ; case 3 : bErase = ( vsParams[2] != "0") ; break ; default : return false ; break ; } // recupero lista nomi if ( ! GetNamesParam( vsParams[1], vsNames)) return false ; // esecuzione for ( Iter = vsNames.begin() ; Iter != vsNames.end() ; Iter++) { // recupero la curva pCrv = GetCurve( m_pGDB->GetGeoObj( GetIdParam( *Iter))) ; if ( pCrv == nullptr) return false ; // aggiungo questa curva if ( ! pCrvCompo->AddCurve( *pCrv)) return false ; } // inserisco la curva composita nel DB if ( m_pGDB->AddGeoObj( GetIdParam( vsParams[0]), Release( pCrvCompo))) { // se richiesto, cancello le curve originali if ( bErase) { for ( Iter = vsNames.begin() ; Iter != vsNames.end() ; Iter++) { if ( ! m_pGDB->Erase( GetIdParam(*Iter))) return false ; } } return true ; } else return false ; } return false ; } //---------------------------------------------------------------------------- int GdbExecutor::GetIdParam( const std::string& sParam) { int nVal ; AliasMap::iterator Iter ; // sostituisco eventuali Alias Iter = m_AliasMap.find( sParam) ; if ( Iter != m_AliasMap.end()) return Iter->second ; // converto in identificatore numerico else if ( FromString( sParam, nVal)) return nVal ; // altrimenti errore else return 0 ; } //---------------------------------------------------------------------------- bool GdbExecutor::GetNamesParam( const std::string& sParam, STRVECTOR& vsNames) { STRVECTOR::iterator Iter ; // divido in parti Tokenize( sParam, ",", vsNames) ; for ( Iter = vsNames.begin() ; Iter != vsNames.end() ; Iter++) Trim( (*Iter), " \t\r\n()") ; return true ; } //---------------------------------------------------------------------------- bool GdbExecutor::GetVectorParam( const std::string& sParam, Vector3d& vtV) { // se insieme di tre componenti if ( sParam[0] == '(') { STRVECTOR vsParams ; STRVECTOR::iterator Iter ; // divido in parti Tokenize( sParam, ",", vsParams) ; for ( Iter = vsParams.begin() ; Iter != vsParams.end() ; Iter++) Trim( (*Iter), " \t\r\n()") ; // verifico siano 3 parti e le converto if ( vsParams.size() != 3) return false ; return ( FromString( vsParams[0], vtV.x) && FromString( vsParams[1], vtV.y) && FromString( vsParams[2], vtV.z)) ; } // altrimenti nome di vettore già nel DB else { const IGeoVector3d* pV ; if ( ( pV = GetGeoVector3d( m_pGDB->GetGeoObj( GetIdParam( sParam)))) == nullptr) return false ; vtV = pV->GetVector() ; return true ; } } //---------------------------------------------------------------------------- bool GdbExecutor::GetPointParam( const std::string& sParam, Point3d& ptP) { // se insieme di tre coordinate if ( sParam[0] == '(') { STRVECTOR vsParams ; STRVECTOR::iterator Iter ; // divido in parti Tokenize( sParam, ",", vsParams) ; for ( Iter = vsParams.begin() ; Iter != vsParams.end() ; Iter++) Trim( (*Iter), " \t\r\n()") ; // verifico siano 3 parti e le converto if ( vsParams.size() != 3) return false ; return ( FromString( vsParams[0], ptP.x) && FromString( vsParams[1], ptP.y) && FromString( vsParams[2], ptP.z)) ; } // altrimenti nome di punto già nel DB else { const IGeoPoint3d* pPt ; if ( ( pPt = GetGeoPoint3d( m_pGDB->GetGeoObj( GetIdParam( sParam)))) == nullptr) return false ; ptP = pPt->GetPoint() ; return true ; } } //---------------------------------------------------------------------------- bool GdbExecutor::GetPointWeParam( const std::string& sParam, Point3d& ptP, double& dW) { STRVECTOR vsParams ; STRVECTOR::iterator Iter ; // deve essere insieme di dati if ( sParam[0] != '(') return false ; // divido in parti Tokenize( sParam, ",", vsParams) ; for ( Iter = vsParams.begin() ; Iter != vsParams.end() ; Iter++) Trim( (*Iter), " \t\r\n()") ; // se 4 parti, sono 3 coordinate e un peso if ( vsParams.size() == 4) { return ( FromString( vsParams[0], ptP.x) && FromString( vsParams[1], ptP.y) && FromString( vsParams[2], ptP.z) && FromString( vsParams[3], dW)) ; } // se 2 parti, nome di punto già nel DB e un peso else if ( vsParams.size() == 2) { const IGeoPoint3d* pPt ; // recupero il punto if ( ( pPt = GetGeoPoint3d( m_pGDB->GetGeoObj( GetIdParam( vsParams[0])))) == nullptr) return false ; ptP = pPt->GetPoint() ; // recupero il peso return FromString( vsParams[1], dW) ; } // altrimenti errore else return false ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteCopy( const STRVECTOR& vsParams) { // 2 parametri ( NomeSou, NomeDest) if ( vsParams.size() != 2) return false ; // esecuzione copia return m_pGDB->Copy( GetIdParam( vsParams[0]), GetIdParam( vsParams[1])) ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteErase( const STRVECTOR& vsParams) { STRVECTOR vsNames ; STRVECTOR::iterator Iter ; // 1 parametro ( Nome/i) if ( vsParams.size() != 1) return false ; // recupero lista nomi if ( ! GetNamesParam( vsParams[0], vsNames)) return false ; // esecuzione cancellazioni for ( Iter = vsNames.begin() ; Iter != vsNames.end() ; Iter++) { if ( ! m_pGDB->Erase( GetIdParam( *Iter))) return false ; } return true ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteTranslate( const STRVECTOR& vsParams) { STRVECTOR vsNames ; STRVECTOR::iterator Iter ; Vector3d vtVN ; // 2 parametri ( Nome/i, Vettore) if ( vsParams.size() != 2) return false ; // recupero lista nomi if ( ! GetNamesParam( vsParams[0], vsNames)) return false ; // recupero il vettore if ( ! GetVectorParam( vsParams[1], vtVN)) return false ; // esecuzione traslazioni for ( Iter = vsNames.begin() ; Iter != vsNames.end() ; Iter++) { if ( ! m_pGDB->Translate( GetIdParam( *Iter), vtVN)) return false ; } return true ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteRotate( const STRVECTOR& vsParams) { STRVECTOR vsNames ; STRVECTOR::iterator Iter ; Point3d ptPC ; Vector3d vtVN ; double dAngDeg ; // 4 parametri ( Nome, PtoAsse, VtAsse, AngDeg) if ( vsParams.size() != 4) return false ; // recupero lista nomi if ( ! GetNamesParam( vsParams[0], vsNames)) return false ; // recupero il punto if ( ! GetPointParam( vsParams[1], ptPC)) return false ; // recupero il vettore if ( ! GetVectorParam( vsParams[2], vtVN)) return false ; // recupero i coefficienti if ( ! FromString( vsParams[3], dAngDeg)) return false ; // esecuzione rotazioni for ( Iter = vsNames.begin() ; Iter != vsNames.end() ; Iter++) { if ( ! m_pGDB->Rotate( GetIdParam( *Iter), ptPC, vtVN, dAngDeg)) return false ; } return true ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteScale( const STRVECTOR& vsParams) { STRVECTOR vsNames ; STRVECTOR::iterator Iter ; Point3d ptPC ; double dCoeffX ; double dCoeffY ; double dCoeffZ ; // 5 parametri ( Nome, Punto, CoeffX, CoeffY, CoeffZ) if ( vsParams.size() != 5) return false ; // recupero lista nomi if ( ! GetNamesParam( vsParams[0], vsNames)) return false ; // recupero il punto if ( ! GetPointParam( vsParams[1], ptPC)) return false ; // recupero i coefficienti if ( ! FromString( vsParams[2], dCoeffX) || ! FromString( vsParams[3], dCoeffY) || ! FromString( vsParams[4], dCoeffZ)) return false ; // esecuzione scalature for ( Iter = vsNames.begin() ; Iter != vsNames.end() ; Iter++) { if ( ! m_pGDB->Scale( GetIdParam( *Iter), ptPC, dCoeffX, dCoeffY, dCoeffZ)) return false ; } return true ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteMirror( const STRVECTOR& vsParams) { STRVECTOR vsNames ; STRVECTOR::iterator Iter ; Point3d ptPC ; Vector3d vtVN ; // 3 parametri ( Nome, Punto, Vettore) if ( vsParams.size() != 3) return false ; // recupero lista nomi if ( ! GetNamesParam( vsParams[0], vsNames)) return false ; // recupero il punto if ( ! GetPointParam( vsParams[1], ptPC)) return false ; // recupero il vettore if ( ! GetVectorParam( vsParams[2], vtVN)) return false ; // esecuzione specchiature for ( Iter = vsNames.begin() ; Iter != vsNames.end() ; Iter++) { if ( ! m_pGDB->Mirror( GetIdParam( *Iter), ptPC, vtVN)) return false ; } return true ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteLoad( const STRVECTOR& vsParams) { // 1 parametro ( NomeFile) if ( vsParams.size() != 1) return false ; // pulizia e reinizializzazione del DB geometrico m_pGDB->Clear() ; m_pGDB->Init() ; // esecuzione caricamento return Load( stringtoW( vsParams[0])) ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteSave( const STRVECTOR& vsParams) { // 1 parametro ( NomeFile) if ( vsParams.size() != 1) return false ; // esecuzione salvataggio return Save( stringtoW( vsParams[0])) ; } //---------------------------------------------------------------------------- bool GdbExecutor::ExecuteOutScl( const string& sCmd2, const STRVECTOR& vsParams) { // apro il file di uscita Scl if ( sCmd2 == "OPEN") { // 1 parametro if ( vsParams.size() != 1) return false ; // apro il file e scrivo intestazione return m_OutScl.Open( stringtoW( vsParams[0])) ; } // chiudo il file di uscita Scl else if ( sCmd2 == "CLOSE") { // nessun parametro if ( vsParams.size() != 0) return false ; // scrivo terminazioni e chiudo il file return m_OutScl.Close() ; } // imposto materiale corrente else if ( sCmd2 == "SETRGB") { double dRed, dGreen, dBlue ; // 3 parametri if ( vsParams.size() != 3) return false ; // recupero i 3 colori if ( ! FromString( vsParams[0], dRed) || ! FromString( vsParams[1], dGreen) || ! FromString( vsParams[2], dBlue)) return false ; // definisco il materiale opportuno return m_OutScl.SetMaterial( dRed, dGreen, dBlue) ; } // imposto pezzo e layer correnti else if ( sCmd2 == "SETPL") { // 2 parametri if ( vsParams.size() != 2) return false ; // eseguo return m_OutScl.SetPartLay( vsParams[0], vsParams[1]) ; } // emetto entità else if ( sCmd2 == "PUT") { bool bCrvVsPolyg ; int nId ; STRVECTOR vsNames ; STRVECTOR::iterator Iter ; // almeno un parametro if ( vsParams.size() < 1) return false ; // se esiste il secondo if ( vsParams.size() == 2) bCrvVsPolyg = ( vsParams[1] != "CPS") ; else bCrvVsPolyg = TRUE ; // recupero lista nomi if ( ! GetNamesParam( vsParams[0], vsNames)) return false ; // esecuzione for ( Iter = vsNames.begin() ; Iter != vsNames.end() ; Iter++) { // recupero l'oggetto ed eseguo l'output nId = GetIdParam( *Iter) ; switch ( m_pGDB->GetObjType( nId)) { case CRV_LINE : if ( ! m_OutScl.PutCurveLine( *GetCurveLine( m_pGDB->GetGeoObj( nId)))) return false ; break ; case CRV_ARC : if ( ! m_OutScl.PutCurveArc( *GetCurveArc( m_pGDB->GetGeoObj( nId)))) return false ; break ; case CRV_BEZ : if ( bCrvVsPolyg) { if ( ! m_OutScl.PutCurveBez( *GetCurveBezier( m_pGDB->GetGeoObj( nId)))) return false ; } else { if ( ! m_OutScl.PutPolygBez( *GetCurveBezier( m_pGDB->GetGeoObj( nId)))) return false ; } break ; case CRV_COMPO : if ( ! m_OutScl.PutCurveCompo( *GetCurveComposite( m_pGDB->GetGeoObj( nId)))) return false ; break ; default : return false ; } } return true ; } return false ; }