Files
EgtGeomKernel/SurfFlatRegionBooleans.cpp
T
Riccardo Elitropi 07d5b3a110 EgtGeomKernel :
- in SurfFlatRegion aggiunta funzione di Cut con un piano.
2026-07-23 14:37:30 +02:00

597 lines
21 KiB
C++

//----------------------------------------------------------------------------
// EgalTech 2015-2015
//----------------------------------------------------------------------------
// File : SurfFlatRegionBooleans.cpp Data : 18.08.15 Versione : 1.6h4
// Contenuto : Implementazione delle funzioni booleane per SurfFlatRegion.
//
//
//
// Modifiche : 18.08.15 DS Creazione modulo.
//
//
//----------------------------------------------------------------------------
//--------------------------- Include ----------------------------------------
#include "stdafx.h"
#include "SurfFlatRegion.h"
#include "CurveComposite.h"
#include "GeoConst.h"
#include "/EgtDev/Include/EGkChainCurves.h"
#include "/EgtDev/Include/EGkIntervals.h"
#include "/EgtDev/Include/EgtPointerOwner.h"
#define SAVELOOPS 0
#define DEBUG_PLANE_CUT 0
#if SAVELOOPS || DEBUG_PLANE_CUT
#include "/EgtDev/Include/EGkGeoObjSave.h"
#include "/EgtDev/Include/EGkColor.h"
#include "/EgtDev/Include/EGkGeoPoint3d.h"
#include "/EgtDev/Include/EGkGeoVector3d.h"
#include "CurveArc.h"
std::vector<IGeoObj*> vGeo ;
std::vector<Color> vCol ;
#endif
using namespace std ;
//----------------------------------------------------------------------------
bool
SurfFlatRegion::Cut( const Plane3d& plPlane)
{
// verifico che la superficie corrente e il piano di taglio siano validi
if ( ! IsValid() || ! plPlane.IsValid())
return false ;
// !<-- Il taglio avviene nel verso della normale
// recupero le normali del piano e della superficie
const Vector3d vtPlane = plPlane.GetVersN() ;
const Vector3d vtN = GetNormVersor() ;
// recupero il centroide e la distanza tra esso e il piano
Point3d ptCentroid ; GetCentroid( ptCentroid) ;
double dDist = DistPointPlane( ptCentroid, plPlane) ;
#if DEBUG_PLANE_CUT
vGeo.clear() ; vCol.clear() ;
CurveArc crvPlane ; crvPlane.Set( plPlane.GetPoint(), plPlane.GetVersN(), 1e5) ;
SurfFlatRegion sfrPlane ; sfrPlane.AddExtLoop( crvPlane) ;
vGeo.emplace_back( sfrPlane.Clone()) ; vCol.emplace_back( BLUE) ;
vGeo.push_back( this->Clone()) ; vCol.emplace_back( LIME) ;
#endif
// --- se le normali sono tra di loro parallele
if ( AreSameOrOppositeVectorApprox( vtPlane, vtN)) {
// la normale della superficie non ha importanza, basta solo verificare se la superficie si trova sopra, sotto o nel piano
// --- se la superficie giace nel piano, allora la svuoto
if ( abs( dDist) < EPS_SMALL)
Clear() ;
// --- se la superficie si trova sopra al piano, allora la svuoto
else if ( dDist > 0.)
Clear() ;
// --- se la superficie si trova sotto allora non devo fare nulla
return true ;
}
// --- essendo la superficie e il piano non paralleli, mettendomi nel sistema di riferimento della superficie posso considerare il
// piano come una linea e classificare i Loop in base al Side. Tutto ciò che si trova nel Side Left viene trimmato
Vector3d vtLineDir = vtPlane ^ vtN ;
double dSqSin = vtLineDir.SqLen() ;
if ( dSqSin < SQ_EPS_SMALL)
return false ;
Vector3d vtNProj = OrthoCompo( vtPlane, vtN) ;
Point3d ptLine = ptCentroid + vtNProj * ( - dDist / dSqSin) ; // !<-- dDist è negativa
if ( ! vtLineDir.Normalize())
return false ;
// definisco la linea di classificazione ( interna al frame della superficie)
BBox3d BBox2d ;
if ( ! GetLocalBBox( BBox2d))
return false ;
double dBoxDiam ; BBox2d.GetDiameter( dBoxDiam) ;
const double dDepth = Dist( ptLine, ptCentroid) + 2. * dBoxDiam ;
Point3d ptLineS = ptLine - dDepth * vtLineDir ;
Point3d ptLineE = ptLine + dDepth * vtLineDir ;
// definisco la regione di influenza del piano
CurveComposite CompoRect ;
CompoRect.AddPoint( ptLineS) ;
CompoRect.AddLine( ptLineS + GetRotate( vtLineDir, m_frF.VersZ(), ANG_RIGHT) * dDepth) ;
CompoRect.AddLine( ptLineE + GetRotate( vtLineDir, m_frF.VersZ(), ANG_RIGHT) * dDepth) ;
CompoRect.AddLine( ptLineE) ;
CompoRect.Close() ;
SurfFlatRegion SfrRect ;
if ( ! SfrRect.AddExtLoop( CompoRect))
return false ;
if ( AreOppositeVectorApprox( SfrRect.GetNormVersor(), m_frF.VersZ()))
SfrRect.Invert() ;
#if DEBUG_PLANE_CUT
vGeo.push_back( SfrRect.Clone()) ; vCol.emplace_back( RED) ;
SaveGeoObj( vGeo, vCol, "C:\\Temp\\SfrPlaneCut.nge") ;
#endif
// Classifico le curve della regione
return ( Subtract( SfrRect)) ;
}
//----------------------------------------------------------------------------
bool
SurfFlatRegion::Add( const ISurfFlatRegion& Other)
{
// converto l'altra regione nell'oggetto base
const SurfFlatRegion* pSfrOther = GetBasicSurfFlatRegion( &Other) ;
if ( pSfrOther == nullptr)
return false ;
const SurfFlatRegion& SfrOther = *pSfrOther ;
// verifico che le due regioni giacciano in piani paralleli
if ( ! AreSameVectorApprox( m_frF.VersZ(), SfrOther.m_frF.VersZ()))
return false ;
// gestione eventuale copia dell'altra per trasformare il riferimento
const SurfFlatRegion* pOther = &SfrOther ;
PtrOwner<SurfFlatRegion> pCopyOth ;
// se riferimenti intrinseci diversi, la trasformo
if ( ! AreSameFrame( m_frF, SfrOther.m_frF)) {
// creo la copia
pCopyOth.Set( SfrOther.Clone()) ;
if ( IsNull( pCopyOth))
return false ;
// calcolo riferimento di traformazione
Frame3d frTransf = pCopyOth->m_frF ;
frTransf.ToLoc( m_frF) ;
// trasformo le curve
for ( auto& pLoop : pCopyOth->m_vpLoop)
pLoop->ToGlob( frTransf) ;
// calcolo vettore di movimento per portarle nel piano XY (Z=0)
Vector3d vtMove( 0, 0, - frTransf.Orig().z) ;
if ( ! vtMove.IsSmall()) {
for ( auto& pLoop : pCopyOth->m_vpLoop)
pLoop->Translate( vtMove) ;
}
// assegno il nuovo riferimento
pCopyOth->m_frF = m_frF ;
// aggiorno puntatore della copia in lavoro
pOther = pCopyOth ;
}
// vettore con curve utili da entrambe le regioni
PCRV_DEQUE vpCurve ;
if ( ! MySelectCurves( m_vpLoop, *pOther, CRVC_OUT, false, CRVC_ON_P, false, vpCurve) ||
! MySelectCurves( pOther->m_vpLoop, *this, CRVC_OUT, false, CRVC_NULL, false, vpCurve)) {
MyTestAndDelete( vpCurve) ;
return false ;
}
// vettore con curve concatenate che sono i contorni del risultato
PCRV_DEQUE vpLoop ;
// concateno e verifico di aver usato tutte le curve
if ( ! MyChainCurves( vpCurve, vpLoop) || ! MyTestAndDelete( vpCurve)) {
MyTestAndDelete( vpCurve) ;
MyTestAndDelete( vpLoop) ;
return false ;
}
// creo una nuova regione a partire da questi loop
PtrOwner<SurfFlatRegion> pSfr( MyNewSurfFromLoops( vpLoop)) ;
if ( IsNull( pSfr)) {
MyTestAndDelete( vpCurve) ;
MyTestAndDelete( vpLoop) ;
return false ;
}
// pulisco la superficie corrente
for ( auto& pLoop : m_vpLoop)
delete pLoop ;
m_vpLoop.clear() ;
// sposto i dati della nuova superficie in quella corrente
m_vExtInd = pSfr->m_vExtInd ;
m_vpLoop = pSfr->m_vpLoop ;
for ( auto& pLoop : pSfr->m_vpLoop)
pLoop = nullptr ;
m_nStatus = pSfr->m_nStatus ;
// imposto ricalcolo della grafica
ResetAuxSurf() ;
m_OGrMgr.Reset() ;
// imposto ricalcolo Voronoi
ResetVoronoiObject() ;
return true ;
}
//----------------------------------------------------------------------------
bool
SurfFlatRegion::Subtract( const ISurfFlatRegion& Other)
{
// converto l'altra regione nell'oggetto base
const SurfFlatRegion* pSfrOther = GetBasicSurfFlatRegion( &Other) ;
if ( pSfrOther == nullptr)
return false ;
const SurfFlatRegion& SfrOther = *pSfrOther ;
// verifico che le due regioni giacciano in piani paralleli
if ( ! AreSameVectorApprox( m_frF.VersZ(), SfrOther.m_frF.VersZ()))
return false ;
// gestione eventuale copia dell'altra per trasformare il riferimento
const SurfFlatRegion* pOther = &SfrOther ;
PtrOwner<SurfFlatRegion> pCopyOth ;
// se riferimenti intrinseci diversi, la trasformo
if ( ! AreSameFrame( m_frF, SfrOther.m_frF)) {
// creo la copia
pCopyOth.Set( SfrOther.Clone()) ;
if ( IsNull( pCopyOth))
return false ;
// calcolo riferimento di traformazione
Frame3d frTransf = pCopyOth->m_frF ;
frTransf.ToLoc( m_frF) ;
// trasformo le curve
for ( auto& pLoop : pCopyOth->m_vpLoop)
pLoop->ToGlob( frTransf) ;
// calcolo vettore di movimento per portarle nel piano XY (Z=0)
Vector3d vtMove( 0, 0, - frTransf.Orig().z) ;
if ( ! vtMove.IsSmall()) {
for ( auto& pLoop : pCopyOth->m_vpLoop)
pLoop->Translate( vtMove) ;
}
// assegno il nuovo riferimento
pCopyOth->m_frF = m_frF ;
// aggiorno puntatore della copia in lavoro
pOther = pCopyOth ;
}
// vettore con curve utili da entrambe le regioni
PCRV_DEQUE vpCurve ;
if ( ! MySelectCurves( m_vpLoop, *pOther, CRVC_OUT, false, CRVC_ON_M, false, vpCurve) ||
! MySelectCurves( pOther->m_vpLoop, *this, CRVC_IN, true, CRVC_NULL, false, vpCurve)) {
MyTestAndDelete( vpCurve) ;
return false ;
}
// vettore con curve concatenate che sono i contorni del risultato
PCRV_DEQUE vpLoop ;
// concateno e verifico di aver usato tutte le curve
if ( ! MyChainCurves( vpCurve, vpLoop) || ! MyTestAndDelete( vpCurve)) {
MyTestAndDelete( vpCurve) ;
MyTestAndDelete( vpLoop) ;
return false ;
}
// creo una nuova regione a partire da questi loop
PtrOwner<SurfFlatRegion> pSfr ;
if ( vpLoop.empty())
pSfr.Set( new( nothrow) SurfFlatRegion) ;
else
pSfr.Set( MyNewSurfFromLoops( vpLoop)) ;
#if SAVELOOPS
for (int i = 0 ; i < ssize( vpLoop) ; ++i)
vGeo.push_back( vpLoop[i]) ;
SaveGeoObj( vGeo, "D:\\Temp\\inters\\CrvCrvInters\\NewLoops.nge") ;
#endif
if ( IsNull( pSfr)) {
MyTestAndDelete( vpCurve) ;
MyTestAndDelete( vpLoop) ;
return false ;
}
// pulisco la superficie corrente
for ( auto& pLoop : m_vpLoop)
delete pLoop ;
m_vpLoop.clear() ;
// sposto i dati della nuova superficie in quella corrente
m_vExtInd = pSfr->m_vExtInd ;
m_vpLoop = pSfr->m_vpLoop ;
for ( auto& pLoop : pSfr->m_vpLoop)
pLoop = nullptr ;
m_nStatus = pSfr->m_nStatus ;
// imposto ricalcolo della grafica
ResetAuxSurf() ;
m_OGrMgr.Reset() ;
// imposto ricalcolo Voronoi
ResetVoronoiObject() ;
return true ;
}
//----------------------------------------------------------------------------
bool
SurfFlatRegion::Intersect( const ISurfFlatRegion& Other)
{
// converto l'altra regione nell'oggetto base
const SurfFlatRegion* pSfrOther = GetBasicSurfFlatRegion( &Other) ;
if ( pSfrOther == nullptr)
return false ;
const SurfFlatRegion& SfrOther = *pSfrOther ;
// verifico che le due regioni giacciano in piani paralleli
if ( ! AreSameVectorApprox( m_frF.VersZ(), SfrOther.m_frF.VersZ()))
return false ;
// gestione eventuale copia dell'altra per trasformare il riferimento
const SurfFlatRegion* pOther = &SfrOther ;
PtrOwner<SurfFlatRegion> pCopyOth ;
// se riferimenti intrinseci diversi, la trasformo
if ( ! AreSameFrame( m_frF, SfrOther.m_frF)) {
// creo la copia
pCopyOth.Set( SfrOther.Clone()) ;
if ( IsNull( pCopyOth))
return false ;
// calcolo riferimento di traformazione
Frame3d frTransf = pCopyOth->m_frF ;
frTransf.ToLoc( m_frF) ;
// trasformo le curve
for ( auto& pLoop : pCopyOth->m_vpLoop)
pLoop->ToGlob( frTransf) ;
// calcolo vettore di movimento per portarle nel piano XY (Z=0)
Vector3d vtMove( 0, 0, - frTransf.Orig().z) ;
if ( ! vtMove.IsSmall()) {
for ( auto& pLoop : pCopyOth->m_vpLoop)
pLoop->Translate( vtMove) ;
}
// assegno il nuovo riferimento
pCopyOth->m_frF = m_frF ;
// aggiorno puntatore della copia in lavoro
pOther = pCopyOth ;
}
// vettore con curve utili da entrambe le regioni
PCRV_DEQUE vpCurve ;
if ( ! MySelectCurves( m_vpLoop, *pOther, CRVC_IN, false, CRVC_ON_P, false, vpCurve) ||
! MySelectCurves( pOther->m_vpLoop, *this, CRVC_IN, false, CRVC_NULL, false, vpCurve)) {
MyTestAndDelete( vpCurve) ;
return false ;
}
// vettore con curve concatenate che sono i contorni del risultato
PCRV_DEQUE vpLoop ;
// concateno e verifico di aver usato tutte le curve
if ( ! MyChainCurves( vpCurve, vpLoop) || ! MyTestAndDelete( vpCurve)) {
MyTestAndDelete( vpCurve) ;
MyTestAndDelete( vpLoop) ;
return false ;
}
// creo una nuova regione a partire da questi loop
PtrOwner<SurfFlatRegion> pSfr ;
if ( vpLoop.empty())
pSfr.Set( new( nothrow) SurfFlatRegion) ;
else
pSfr.Set( MyNewSurfFromLoops( vpLoop)) ;
if ( IsNull( pSfr)) {
MyTestAndDelete( vpCurve) ;
MyTestAndDelete( vpLoop) ;
return false ;
}
// pulisco la superficie corrente
for ( auto& pLoop : m_vpLoop)
delete pLoop ;
m_vpLoop.clear() ;
// sposto i dati della nuova superficie in quella corrente
m_vExtInd = pSfr->m_vExtInd ;
m_vpLoop = pSfr->m_vpLoop ;
for ( auto& pLoop : pSfr->m_vpLoop)
pLoop = nullptr ;
m_nStatus = pSfr->m_nStatus ;
// imposto ricalcolo della grafica
ResetAuxSurf() ;
m_OGrMgr.Reset() ;
// imposto ricalcolo Voronoi
ResetVoronoiObject() ;
return true ;
}
//----------------------------------------------------------------------------
bool
SurfFlatRegion::MySelectCurves( const PCRV_DEQUE& vpLoop, const SurfFlatRegion& Other,
int nType1, bool bInvert1, int nType2, bool bInvert2,
PCRV_DEQUE& vpCurve)
{
// classifico le curve rispetto alla regione altra e copio le parti utili
for ( auto& pLoop : vpLoop) {
// eseguo classificazione
CRVCVECTOR ccClass ;
if ( ! Other.MyGetCurveClassification( *pLoop, EPS_SMALL, ccClass))
return false ;
// creo intervalli validi, tenendo classificazioni ricevute
Intervals inOk1( EPS_PARAM), inOk2( EPS_PARAM) ;
for ( auto& ccOne : ccClass) {
if ( nType1 != CRVC_NULL && ccOne.nClass == nType1)
inOk1.Add( ccOne.dParE, ccOne.dParS) ;
else if ( nType2 != CRVC_NULL && ccOne.nClass == nType2)
inOk2.Add( ccOne.dParE, ccOne.dParS) ;
}
// copio queste parti
double dParS, dParE ;
bool bFound = inOk1.GetFirst( dParS, dParE) ;
while ( bFound) {
ICurve* pCrv = pLoop->CopyParamRange( dParS, dParE) ;
if ( pCrv != nullptr) {
if ( bInvert1)
pCrv->Invert() ;
vpCurve.push_back( pCrv) ;
}
bFound = inOk1.GetNext( dParS, dParE) ;
}
bFound = inOk2.GetFirst( dParS, dParE) ;
while ( bFound) {
ICurve* pCrv = pLoop->CopyParamRange( dParS, dParE) ;
if ( pCrv != nullptr) {
if ( bInvert2)
pCrv->Invert() ;
vpCurve.push_back( pCrv) ;
}
bFound = inOk2.GetNext( dParS, dParE) ;
}
}
return true ;
}
//----------------------------------------------------------------------------
bool
SurfFlatRegion::MyChainCurves( PCRV_DEQUE& vpCurve, PCRV_DEQUE& vpLoop)
{
// concateno le curve
double dToler = 5 * EPS_SMALL ;
ChainCurves chainC ;
chainC.Init( false, dToler, int( vpCurve.size())) ;
for ( int i = 0 ; i < int( vpCurve.size()) ; ++i) {
// recupero i dati della curva necessari al concatenamento e li assegno
Point3d ptStart, ptEnd ;
Vector3d vtStart, vtEnd ;
if ( ! vpCurve[i]->GetStartPoint( ptStart) || ! vpCurve[i]->GetStartDir( vtStart) ||
! vpCurve[i]->GetEndPoint( ptEnd) || ! vpCurve[i]->GetEndDir( vtEnd))
return false ;
if ( ! chainC.AddCurve( i + 1, ptStart, vtStart, ptEnd, vtEnd))
return false ;
}
// recupero i percorsi concatenati
INTVECTOR vIds ;
Point3d ptNearStart ;
while ( chainC.GetChainFromNear( ptNearStart, false, vIds)) {
// creo una curva composita
PtrOwner<CurveComposite> pCrvCompo( CreateBasicCurveComposite()) ;
if ( IsNull( pCrvCompo))
return false ;
// recupero le curve e le inserisco nella nuova curva composita
for ( auto i : vIds) {
// la aggiungo alla curva composta
bool bOk = pCrvCompo->AddCurve( vpCurve[i-1], true, dToler) ;
vpCurve[i-1] = nullptr ;
if ( ! bOk)
return false ;
}
// pulisco
pCrvCompo->RemoveSmallParts( 2 * EPS_SMALL, EPS_ANG_SMALL) ;
// aggiorno il nuovo punto vicino
if ( pCrvCompo->GetCurveCount() > 0)
pCrvCompo->GetEndPoint( ptNearStart) ;
// se lunghezza curva inferiore a 2 volte la tolleranza, la salto
double dCrvLen ;
if ( ! pCrvCompo->GetLength( dCrvLen) || dCrvLen < 2. * dToler)
continue ;
// se curva chiusa entro 3 volte la tolleranza ma considerata aperta, la chiudo bene
Point3d ptStart, ptEnd ;
if ( pCrvCompo->GetStartPoint( ptStart) &&
pCrvCompo->GetEndPoint( ptEnd) &&
AreSamePointEpsilon( ptStart, ptEnd, 3 * dToler) &&
! AreSamePointApprox( ptStart, ptEnd)) {
// porto i punti iniziale e finale a coincidere esattamente
Point3d ptNew = Media( ptStart, ptEnd) ;
pCrvCompo->ModifyStart( ptNew) ;
pCrvCompo->ModifyEnd( ptNew) ;
}
// compatto la nuova curva
pCrvCompo->MergeCurves( LIN_TOL_MIN, ANG_TOL_STD_DEG) ;
// inserisco la curva composita nel gruppo destinazione
if ( pCrvCompo->GetCurveCount() > 0) {
vpLoop.push_back( ::Release( pCrvCompo)) ;
}
}
return true ;
}
//----------------------------------------------------------------------------
SurfFlatRegion*
SurfFlatRegion::MyNewSurfFromLoops( PCRV_DEQUE& vpLoop)
{
// verifico che le curve siano chiuse e ne determino l'area
typedef pair<int,double> INDAREA ; // coppia indice, area
typedef vector<INDAREA> INDAREAVECTOR ; // vettore di coppie indice, area
INDAREAVECTOR vArea ;
vArea.reserve( vpLoop.size()) ;
for ( int i = 0 ; i < int( vpLoop.size()) ;) {
double dArea ;
if ( ! vpLoop[i]->GetAreaXY( dArea))
return nullptr ;
if ( abs( dArea) > SQ_EPS_SMALL) {
vArea.emplace_back( i, dArea) ;
++ i ;
}
else {
delete vpLoop[i] ;
vpLoop.erase( vpLoop.begin() + i) ;
}
}
// ordino in senso decrescente sull'area
sort( vArea.begin(), vArea.end(),
[]( const INDAREA& a, const INDAREA& b) { return ( a.second > b.second) ; }) ;
// determino quanti sono i nuovi cicli esterni (area positiva)
int nLoopCount = int( vArea.size()) ;
int nExtCount = 0 ;
for ( int i = 0 ; i < nLoopCount ; ++ i) {
if ( vArea[i].second > 0)
++ nExtCount ;
}
// creo una nuova regione
PtrOwner<SurfFlatRegion> pSfr( CreateBasicSurfFlatRegion()) ;
// ciclo sui loop esterni, all'indietro dal più piccolo al più grande
for ( int i = nExtCount - 1 ; i >= 0 ; -- i) {
// inserisco il nuovo esterno
int j = vArea[i].first ;
ICurve* pExtLoop = vpLoop[j] ;
double dExtArea = vArea[i].second ;
if ( pSfr->MyAddExtLoop( vpLoop[j])) {
vpLoop[j] = nullptr ;
vArea[i].first = - 1 ;
}
else
continue ;
// provo ad inserire i loop interni
for ( int k = nExtCount ; k < nLoopCount ; ++ k) {
// verifico non sia già stato inserito
if ( vArea[k].first < 0)
continue ;
// salto gli interni con area maggiore dell'esterno corrente
if ( abs( vArea[k].second) > dExtArea)
continue ;
int l = vArea[k].first ;
// verifico che sia interno all'esterno corrente
CRVCVECTOR ccClass ;
IntersCurveCurve ccInt( *vpLoop[l], *pExtLoop) ;
if ( ccInt.GetCrossOrOverlapIntersCount() > 0 ||
! ccInt.GetCurveClassification( 0, EPS_SMALL, ccClass) ||
ccClass.empty() || ccClass[0].nClass != CRVC_IN)
continue ;
// lo inserisco
if ( pSfr->MyAddIntLoop( vpLoop[l], -1)) {
vpLoop[l] = nullptr ;
vArea[k].first = - 1 ;
}
}
}
// se non valida, errore
if ( ! pSfr->IsValid())
return nullptr ;
// verifico non siano rimasti loop inutilizzati
if ( ! MyTestAndDelete( vpLoop))
return nullptr ;
// tutto bene, ritorno la superficie appena creata
return Release( pSfr) ;
}
//----------------------------------------------------------------------------
bool
SurfFlatRegion::MyTestAndDelete( PCRV_DEQUE& vpCrv)
{
bool bOk = true ;
for ( auto& pCrv : vpCrv) {
if ( pCrv != nullptr) {
delete pCrv ;
pCrv = nullptr ;
bOk = false ;
}
}
return bOk ;
}