From 478182de8e9b70fd4d683ee7a714290cdad3f9f8 Mon Sep 17 00:00:00 2001 From: vsr Date: Thu, 5 Mar 2015 12:23:43 +0300 Subject: [PATCH] Remove unnecessary dependency on VTK from GEOM engine --- src/GEOMImpl/CMakeLists.txt | 3 +- src/GEOMImpl/GEOMImpl_IMeasureOperations.cxx | 6 +- src/GEOMUtils/GEOMUtils.cxx | 43 ++++ src/GEOMUtils/GEOMUtils.hxx | 16 ++ src/OBJECT/GEOM_Actor.cxx | 52 ++--- src/OBJECT/GEOM_Actor.h | 10 +- src/OCC2VTK/OCC2VTK_Tools.cxx | 196 +++++++------------ src/OCC2VTK/OCC2VTK_Tools.h | 46 +++-- src/VTKPlugin/VTKPlugin_ExportDriver.cxx | 2 +- 9 files changed, 189 insertions(+), 185 deletions(-) diff --git a/src/GEOMImpl/CMakeLists.txt b/src/GEOMImpl/CMakeLists.txt index 5b3534e14..5cc90b4ed 100755 --- a/src/GEOMImpl/CMakeLists.txt +++ b/src/GEOMImpl/CMakeLists.txt @@ -30,7 +30,6 @@ INCLUDE_DIRECTORIES( ${PROJECT_SOURCE_DIR}/src/BlockFix ${PROJECT_SOURCE_DIR}/src/GEOMAlgo ${PROJECT_SOURCE_DIR}/src/GEOMUtils - ${PROJECT_SOURCE_DIR}/src/OCC2VTK ${PROJECT_SOURCE_DIR}/src/SKETCHER ${PROJECT_SOURCE_DIR}/src/ARCHIMEDE ${PROJECT_SOURCE_DIR}/src/XAO @@ -49,7 +48,7 @@ SET(_link_LIBRARIES ${CAS_TKFeat} ${CAS_TKFillet} ${PYTHON_LIBRARIES} - ShHealOper GEOMbasic BlockFix GEOMAlgo GEOMUtils GEOMSketcher GEOMArchimede XAO OCC2VTK + ShHealOper GEOMbasic BlockFix GEOMAlgo GEOMUtils GEOMSketcher GEOMArchimede XAO ${KERNEL_SALOMELocalTrace} ) diff --git a/src/GEOMImpl/GEOMImpl_IMeasureOperations.cxx b/src/GEOMImpl/GEOMImpl_IMeasureOperations.cxx index d39abfa54..d1d23d51c 100644 --- a/src/GEOMImpl/GEOMImpl_IMeasureOperations.cxx +++ b/src/GEOMImpl/GEOMImpl_IMeasureOperations.cxx @@ -32,7 +32,6 @@ #include #include #include -#include #include @@ -1628,9 +1627,8 @@ bool GEOMImpl_IMeasureOperations::FastIntersect (Handle(GEOM_Object) theShape1, GEOMAlgo_AlgoTools::CopyShape(aShape1, aScopy1); GEOMAlgo_AlgoTools::CopyShape(aShape2, aScopy2); - float aDeflection = (theDeflection <= 0.) ? 0.001 : theDeflection; - GEOM::MeshShape(aScopy1, aDeflection); - GEOM::MeshShape(aScopy2, aDeflection); + GEOMUtils::MeshShape(aScopy1, theDeflection); + GEOMUtils::MeshShape(aScopy2, theDeflection); // // Map sub-shapes and their indices TopTools_IndexedMapOfShape anIndices1, anIndices2; diff --git a/src/GEOMUtils/GEOMUtils.cxx b/src/GEOMUtils/GEOMUtils.cxx index 6d9643f0c..9b7ca2d97 100644 --- a/src/GEOMUtils/GEOMUtils.cxx +++ b/src/GEOMUtils/GEOMUtils.cxx @@ -99,6 +99,9 @@ #include #include // CAREFUL ! position of this file is critic : see Lucien PIGNOLONI / OCC +#define MAX2(X, Y) (Abs(X) > Abs(Y) ? Abs(X) : Abs(Y)) +#define MAX3(X, Y, Z) (MAX2(MAX2(X,Y), Z)) + #define STD_SORT_ALGO 1 // When the following macro is defined, ShapeFix_ShapeTolerance function is used to set max tolerance of curve @@ -1193,3 +1196,43 @@ TopoDS_Shape GEOMUtils::ReduceCompound( const TopoDS_Shape& shape ) return result; } + +void GEOMUtils::MeshShape( const TopoDS_Shape shape, + double deflection, bool theForced ) +{ + Standard_Real aDeflection = ( deflection <= 0 ) ? DefaultDeflection() : deflection; + + // Is shape triangulated? + Standard_Boolean alreadyMeshed = true; + TopExp_Explorer ex; + TopLoc_Location aLoc; + for ( ex.Init( shape, TopAbs_FACE ); ex.More() && alreadyMeshed; ex.Next() ) { + const TopoDS_Face& aFace = TopoDS::Face( ex.Current() ); + Handle(Poly_Triangulation) aPoly = BRep_Tool::Triangulation( aFace, aLoc ); + alreadyMeshed = !aPoly.IsNull(); + } + + if ( !alreadyMeshed || theForced ) { + // Compute bounding box + Bnd_Box B; + BRepBndLib::Add( shape, B ); + if ( B.IsVoid() ) + return; // NPAL15983 (Bug when displaying empty groups) + Standard_Real aXmin, aYmin, aZmin, aXmax, aYmax, aZmax; + B.Get( aXmin, aYmin, aZmin, aXmax, aYmax, aZmax ); + + // This magic line comes from Prs3d_ShadedShape.gxx in OCCT + aDeflection = MAX3(aXmax-aXmin, aYmax-aYmin, aZmax-aZmin) * aDeflection * 4; + + // Clean triangulation before compute incremental mesh + BRepTools::Clean( shape ); + + // Compute triangulation + BRepMesh_IncrementalMesh mesh( shape, aDeflection ); + } +} + +double GEOMUtils::DefaultDeflection() +{ + return 0.001; +} diff --git a/src/GEOMUtils/GEOMUtils.hxx b/src/GEOMUtils/GEOMUtils.hxx index b673ea807..d8a839940 100644 --- a/src/GEOMUtils/GEOMUtils.hxx +++ b/src/GEOMUtils/GEOMUtils.hxx @@ -312,6 +312,22 @@ namespace GEOMUtils * \retval TopoDS_Shape resulting shape */ Standard_EXPORT TopoDS_Shape ReduceCompound( const TopoDS_Shape& shape ); + + /*! + * \brief Generate triangulation for the shape. + * + * \param shape shape being meshed + * \param deflection deflection coefficient to be used + * \param forced if \c true, causes generation of mesh regardless it is already present in the shape + */ + Standard_EXPORT void MeshShape( const TopoDS_Shape shape, + double deflection, bool forced = true ); + + /*! + * \brief Get default deflection coefficient used for triangulation + * \return default deflection value + */ + Standard_EXPORT double DefaultDeflection(); }; #endif diff --git a/src/OBJECT/GEOM_Actor.cxx b/src/OBJECT/GEOM_Actor.cxx index db90f7108..b261ec85a 100644 --- a/src/OBJECT/GEOM_Actor.cxx +++ b/src/OBJECT/GEOM_Actor.cxx @@ -37,6 +37,7 @@ #include "GEOM_WireframeFace.h" #include "GEOM_ShadingFace.h" #include "GEOM_PainterPolyDataMapper.h" +#include "GEOMUtils.hxx" #include "SVTK_Actor.h" #include @@ -430,20 +431,19 @@ GEOM_Actor void GEOM_Actor:: -SetDeflection(float theDeflection) +SetDeflection(double theDeflection) { - if( myDeflection == theDeflection ) - return; - - myDeflection = theDeflection; - - GEOM::MeshShape(myShape,myDeflection); - - SetModified(); + double aDeflection = ( theDeflection <= 0 ) ? GEOMUtils::DefaultDeflection() : theDeflection; + + if ( myDeflection != aDeflection ) { + myDeflection = aDeflection; + GEOMUtils::MeshShape( myShape, myDeflection ); + SetModified(); + } } void GEOM_Actor::SetShape (const TopoDS_Shape& theShape, - float theDeflection, + double theDeflection, bool theIsVector) { myShape = theShape; @@ -469,13 +469,13 @@ void GEOM_Actor::SetShape (const TopoDS_Shape& theShape, TopTools_IndexedDataMapOfShapeListOfShape anEdgeMap; TopExp::MapShapesAndAncestors(theShape,TopAbs_EDGE,TopAbs_FACE,anEdgeMap); - GEOM::SetShape(theShape,anEdgeMap,theIsVector, - myStandaloneVertexSource.Get(), - myIsolatedEdgeSource.Get(), - myOneFaceEdgeSource.Get(), - mySharedEdgeSource.Get(), - myWireframeFaceSource.Get(), - myShadingFaceSource.Get()); + GEOM::ShapeToVTK(theShape,anEdgeMap,theIsVector, + myStandaloneVertexSource.Get(), + myIsolatedEdgeSource.Get(), + myOneFaceEdgeSource.Get(), + mySharedEdgeSource.Get(), + myWireframeFaceSource.Get(), + myShadingFaceSource.Get()); isOnlyVertex = myIsolatedEdgeSource->IsEmpty() && myOneFaceEdgeSource->IsEmpty() && @@ -494,15 +494,6 @@ void GEOM_Actor::SetShape (const TopoDS_Shape& theShape, myAppendFilter->Update(); } -// OLD METHODS -void GEOM_Actor::setDeflection(double adef) { -#ifdef MYDEBUG - MESSAGE ( "GEOM_Actor::setDeflection" ); -#endif - SetDeflection((float)adef); -} - - // warning! must be checked! // SetHighlightProperty // SetWireframeProperty @@ -689,15 +680,6 @@ void GEOM_Actor::setInputShape(const TopoDS_Shape& ashape, double adef1, #endif } -double GEOM_Actor::getDeflection() -{ -#ifdef MYDEBUG - MESSAGE ( "GEOM_Actor::getDeflection" ); -#endif - return (double) GetDeflection(); -} - - double GEOM_Actor::isVector() { #ifdef MYDEBUG diff --git a/src/OBJECT/GEOM_Actor.h b/src/OBJECT/GEOM_Actor.h index 2439babeb..471d9f061 100644 --- a/src/OBJECT/GEOM_Actor.h +++ b/src/OBJECT/GEOM_Actor.h @@ -62,11 +62,11 @@ public: static GEOM_Actor* New(); void SetShape(const TopoDS_Shape& theShape, - float theDeflection, + double theDeflection, bool theIsVector = false); - void SetDeflection(float theDeflection); - float GetDeflection() const{ return myDeflection;} + void SetDeflection(double theDeflection); + double GetDeflection() const{ return myDeflection;} void AddToRender(vtkRenderer* theRenderer); void RemoveFromRender(vtkRenderer* theRenderer); @@ -93,7 +93,6 @@ public: vtkProperty* GetSharedEdgeProperty(); vtkProperty* GetFaceEdgeProperty(); - void setDeflection(double adef); virtual void setDisplayMode(int thenewmode); // Description: @@ -108,7 +107,6 @@ public: const TopoDS_Shape& getTopo(); void setInputShape(const TopoDS_Shape& ashape, double adef1, int imode, bool isVector = false); - double getDeflection(); double isVector(); // SubShape @@ -224,7 +222,7 @@ private: TopoDS_Shape myShape; bool isOnlyVertex; - float myDeflection; + double myDeflection; bool myIsForced; // EDisplayMode myDisplayMode; diff --git a/src/OCC2VTK/OCC2VTK_Tools.cxx b/src/OCC2VTK/OCC2VTK_Tools.cxx index a11b19715..b67dc829d 100755 --- a/src/OCC2VTK/OCC2VTK_Tools.cxx +++ b/src/OCC2VTK/OCC2VTK_Tools.cxx @@ -23,6 +23,7 @@ #include "GEOM_EdgeSource.h" #include "GEOM_WireframeFace.h" #include "GEOM_ShadingFace.h" +#include "GEOMUtils.hxx" #include #include @@ -41,76 +42,28 @@ #include #include -#define MAX2(X, Y) (Abs(X) > Abs(Y) ? Abs(X) : Abs(Y)) -#define MAX3(X, Y, Z) (MAX2(MAX2(X,Y), Z)) - - -#define DEFAULT_DEFLECTION 0.001 - namespace GEOM { - void MeshShape(const TopoDS_Shape theShape, - float& theDeflection, - bool theForced ) { - - Standard_Real aDeflection = theDeflection <= 0 ? DEFAULT_DEFLECTION : theDeflection; - - //If deflection <= 0, than return default deflection - if(theDeflection <= 0) - theDeflection = aDeflection; - - // Is shape triangulated? - Standard_Boolean alreadymeshed = Standard_True; - TopExp_Explorer ex; - TopLoc_Location aLoc; - for (ex.Init(theShape, TopAbs_FACE); ex.More(); ex.Next()) { - const TopoDS_Face& aFace = TopoDS::Face(ex.Current()); - Handle(Poly_Triangulation) aPoly = BRep_Tool::Triangulation(aFace,aLoc); - if(aPoly.IsNull()) { - alreadymeshed = Standard_False; - break; - } - } - - if(!alreadymeshed || theForced) { - Bnd_Box B; - BRepBndLib::Add(theShape, B); - if ( B.IsVoid() ) - return; // NPAL15983 (Bug when displaying empty groups) - Standard_Real aXmin, aYmin, aZmin, aXmax, aYmax, aZmax; - B.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax); - - // This magic line comes from Prs3d_ShadedShape.gxx in OCCT - aDeflection = MAX3(aXmax-aXmin, aYmax-aYmin, aZmax-aZmin) * aDeflection * 4; - - //Clean triangulation before compute incremental mesh - BRepTools::Clean(theShape); - - //Compute triangulation - BRepMesh_IncrementalMesh MESH(theShape,aDeflection); - } - } - - void SetShape(const TopoDS_Shape& theShape, - const TopTools_IndexedDataMapOfShapeListOfShape& theEdgeMap, - bool theIsVector, - GEOM_VertexSource* theStandaloneVertexSource, - GEOM_EdgeSource* theIsolatedEdgeSource, - GEOM_EdgeSource* theOneFaceEdgeSource, - GEOM_EdgeSource* theSharedEdgeSource, - GEOM_WireframeFace* theWireframeFaceSource, - GEOM_ShadingFace* theShadingFaceSource) + void ShapeToVTK( const TopoDS_Shape& theShape, + const TopTools_IndexedDataMapOfShapeListOfShape& theEdgeMap, + bool theIsVector, + GEOM_VertexSource* theStandaloneVertexSource, + GEOM_EdgeSource* theIsolatedEdgeSource, + GEOM_EdgeSource* theOneFaceEdgeSource, + GEOM_EdgeSource* theSharedEdgeSource, + GEOM_WireframeFace* theWireframeFaceSource, + GEOM_ShadingFace* theShadingFaceSource ) { if (theShape.ShapeType() == TopAbs_COMPOUND) { TopoDS_Iterator anItr(theShape); for (; anItr.More(); anItr.Next()) { - SetShape(anItr.Value(),theEdgeMap,theIsVector, - theStandaloneVertexSource, - theIsolatedEdgeSource, - theOneFaceEdgeSource, - theSharedEdgeSource, - theWireframeFaceSource, - theShadingFaceSource); + ShapeToVTK( anItr.Value(),theEdgeMap,theIsVector, + theStandaloneVertexSource, + theIsolatedEdgeSource, + theOneFaceEdgeSource, + theSharedEdgeSource, + theWireframeFaceSource, + theShadingFaceSource ); } } @@ -166,67 +119,68 @@ namespace GEOM } } - vtkPolyData* GetData(const TopoDS_Shape& theShape, float theDeflection) { + vtkPolyData* GetVTKData( const TopoDS_Shape& theShape, float theDeflection ) + { + vtkPolyData* ret = 0; + BRepBuilderAPI_Copy aCopy(theShape); - if(!aCopy.IsDone()) { - return 0; - } + if (aCopy.IsDone() ) { - TopoDS_Shape aShape = aCopy.Shape(); - - try { - GEOM_VertexSource* myVertexSource = GEOM_VertexSource::New(); - GEOM_EdgeSource* myIsolatedEdgeSource = GEOM_EdgeSource::New(); - GEOM_EdgeSource* myOneFaceEdgeSource = GEOM_EdgeSource::New(); - GEOM_EdgeSource* mySharedEdgeSource = GEOM_EdgeSource::New(); - GEOM_WireframeFace* myWireframeFaceSource = GEOM_WireframeFace::New(); - GEOM_ShadingFace* myShadingFaceSource = GEOM_ShadingFace::New(); - - vtkAppendPolyData* myAppendFilter = vtkAppendPolyData::New(); - myAppendFilter->AddInputConnection( myVertexSource->GetOutputPort() ); - myAppendFilter->AddInputConnection( myIsolatedEdgeSource->GetOutputPort() ); - myAppendFilter->AddInputConnection( myOneFaceEdgeSource->GetOutputPort() ); - myAppendFilter->AddInputConnection( mySharedEdgeSource->GetOutputPort() ); - myAppendFilter->AddInputConnection( myShadingFaceSource->GetOutputPort() ); + TopoDS_Shape aShape = aCopy.Shape(); - bool anIsVector = false; + try { + GEOM_VertexSource* myVertexSource = GEOM_VertexSource::New(); + GEOM_EdgeSource* myIsolatedEdgeSource = GEOM_EdgeSource::New(); + GEOM_EdgeSource* myOneFaceEdgeSource = GEOM_EdgeSource::New(); + GEOM_EdgeSource* mySharedEdgeSource = GEOM_EdgeSource::New(); + GEOM_WireframeFace* myWireframeFaceSource = GEOM_WireframeFace::New(); + GEOM_ShadingFace* myShadingFaceSource = GEOM_ShadingFace::New(); + + vtkAppendPolyData* myAppendFilter = vtkAppendPolyData::New(); + myAppendFilter->AddInputConnection( myVertexSource->GetOutputPort() ); + myAppendFilter->AddInputConnection( myIsolatedEdgeSource->GetOutputPort() ); + myAppendFilter->AddInputConnection( myOneFaceEdgeSource->GetOutputPort() ); + myAppendFilter->AddInputConnection( mySharedEdgeSource->GetOutputPort() ); + myAppendFilter->AddInputConnection( myShadingFaceSource->GetOutputPort() ); + + bool anIsVector = false; + + GEOMUtils::MeshShape( aShape, theDeflection ); + TopExp_Explorer aVertexExp( aShape, TopAbs_VERTEX ); + for( ; aVertexExp.More(); aVertexExp.Next() ) { + const TopoDS_Vertex& aVertex = TopoDS::Vertex( aVertexExp.Current() ); + myVertexSource->AddVertex( aVertex ); + } - GEOM::MeshShape( aShape, theDeflection ); - TopExp_Explorer aVertexExp( aShape, TopAbs_VERTEX ); - for( ; aVertexExp.More(); aVertexExp.Next() ) { - const TopoDS_Vertex& aVertex = TopoDS::Vertex( aVertexExp.Current() ); - myVertexSource->AddVertex( aVertex ); + TopTools_IndexedDataMapOfShapeListOfShape anEdgeMap; + TopExp::MapShapesAndAncestors( aShape, TopAbs_EDGE, TopAbs_FACE, anEdgeMap ); + + ShapeToVTK( aShape, + anEdgeMap, + anIsVector, + 0, + myIsolatedEdgeSource, + myOneFaceEdgeSource, + mySharedEdgeSource, + myWireframeFaceSource, + myShadingFaceSource ); + + myAppendFilter->Update(); + + myVertexSource->Delete(); + myIsolatedEdgeSource->Delete(); + myOneFaceEdgeSource->Delete(); + mySharedEdgeSource->Delete(); + myWireframeFaceSource->Delete(); + myShadingFaceSource->Delete(); + + ret = vtkPolyData::New(); + ret->ShallowCopy(myAppendFilter->GetOutput()); + myAppendFilter->Delete(); + } + catch(Standard_Failure) { } - - TopTools_IndexedDataMapOfShapeListOfShape anEdgeMap; - TopExp::MapShapesAndAncestors( aShape, TopAbs_EDGE, TopAbs_FACE, anEdgeMap ); - - GEOM::SetShape( aShape, - anEdgeMap, - anIsVector, - 0, - myIsolatedEdgeSource, - myOneFaceEdgeSource, - mySharedEdgeSource, - myWireframeFaceSource, - myShadingFaceSource ); - - myAppendFilter->Update(); - - myVertexSource->Delete(); - myIsolatedEdgeSource->Delete(); - myOneFaceEdgeSource->Delete(); - mySharedEdgeSource->Delete(); - myWireframeFaceSource->Delete(); - myShadingFaceSource->Delete(); - - vtkPolyData* ret = vtkPolyData::New(); - ret->ShallowCopy(myAppendFilter->GetOutput()); - myAppendFilter->Delete(); - return ret; - } - catch(Standard_Failure) { - return 0; } - } + return ret; + } } diff --git a/src/OCC2VTK/OCC2VTK_Tools.h b/src/OCC2VTK/OCC2VTK_Tools.h index ec7155652..1364cf491 100755 --- a/src/OCC2VTK/OCC2VTK_Tools.h +++ b/src/OCC2VTK/OCC2VTK_Tools.h @@ -30,25 +30,39 @@ class GEOM_EdgeSource; class GEOM_WireframeFace; class GEOM_ShadingFace; class vtkPolyData; + namespace GEOM { - // moved from GEOM_AssemblyBuilder - OCC2VTK_EXPORT void MeshShape(const TopoDS_Shape theShape, - float& theDeflection, - bool theForced = true); - - // moved from GEOM_Actor - OCC2VTK_EXPORT void SetShape(const TopoDS_Shape& theShape, - const TopTools_IndexedDataMapOfShapeListOfShape& theEdgeMap, - bool theIsVector, - GEOM_VertexSource* theStandaloneVertexSource, - GEOM_EdgeSource* theIsolatedEdgeSource, - GEOM_EdgeSource* theOneFaceEdgeSource, - GEOM_EdgeSource* theSharedEdgeSource, - GEOM_WireframeFace* theWireframeFaceSource, - GEOM_ShadingFace* theShadingFaceSource); + /*! + * \brief Convert shape to the VTK data sources + * \param theShape shape + * \param theEdgeMape map that stores face-to-edge relations + * \param theIsVector boolen flag, when \c true causes generating additional + * dataset for edges orientation vectors + * \param theStandaloneVertexSource output standalone vertices data source + * \param theIsolatedEdgeSource output standalone edges data source + * \param theOneFaceEdgeSource output face edges data source + * \param theSharedEdgeSource output face shared edges data source + * \param theWireframeFaceSource output wireframe mode faces data source + * \param theShadingFaceSource output shading mode faces data source + */ + OCC2VTK_EXPORT void ShapeToVTK( const TopoDS_Shape& theShape, + const TopTools_IndexedDataMapOfShapeListOfShape& theEdgeMap, + bool theIsVector, + GEOM_VertexSource* theStandaloneVertexSource, + GEOM_EdgeSource* theIsolatedEdgeSource, + GEOM_EdgeSource* theOneFaceEdgeSource, + GEOM_EdgeSource* theSharedEdgeSource, + GEOM_WireframeFace* theWireframeFaceSource, + GEOM_ShadingFace* theShadingFaceSource ); - OCC2VTK_EXPORT vtkPolyData* GetData(const TopoDS_Shape& theShape, float theDeflection); + /*! + * \brief Get VTK mesh data from the shape + * \param theShape shape + * \param theDeflection requested deflection coefficient + * \return VTK data set + */ + OCC2VTK_EXPORT vtkPolyData* GetVTKData( const TopoDS_Shape& theShape, float theDeflection ); } #endif // OCC2VTK_TOOLS_H diff --git a/src/VTKPlugin/VTKPlugin_ExportDriver.cxx b/src/VTKPlugin/VTKPlugin_ExportDriver.cxx index 5e73a4143..e6cc92780 100644 --- a/src/VTKPlugin/VTKPlugin_ExportDriver.cxx +++ b/src/VTKPlugin/VTKPlugin_ExportDriver.cxx @@ -94,7 +94,7 @@ Standard_Integer VTKPlugin_ExportDriver::Execute( TFunction_Logbook& log ) const // Set "C" numeric locale to save numbers correctly Kernel_Utils::Localizer loc; - vtkPolyData* pd = GEOM::GetData( aShape, aDeflection ); + vtkPolyData* pd = GEOM::GetVTKData( aShape, aDeflection ); vtkPolyDataWriter* aWriter = vtkPolyDataWriter::New(); aWriter->SetInputData( pd ); aWriter->SetFileName( aFileName.ToCString() ); -- 2.39.2