Files
EgtNesting/AutoNester.cpp
SaraP 918481ee67 EgtNesting 2.7i2 :
- aggiunte funzioni per calcolo shear sequence.
2025-09-12 10:16:47 +02:00

632 lines
22 KiB
C++

//----------------------------------------------------------------------------
// EgalTech 2019-2023
//----------------------------------------------------------------------------
// File : AutoNester.cpp Data : 17.07.23 Versione : 2.5g2
// Contenuto : Implementazione della classe AutoNester.
//
//
//
// Modifiche : 28.11.19 DS Creazione modulo.
// 17.07.23 PS Compilato con Optalog ver. 9.0.
//
//----------------------------------------------------------------------------
//--------------------------- Include ----------------------------------------
#include "stdafx.h"
#include "AutoNester.h"
#include "DllMain.h"
#include "/EgtDev/Include/ENsDllMain.h"
#include "/EgtDev/Include/EGkPolyArc.h"
#include "/EgtDev/Extern/Optalog/Include/cns.h"
#include "/EgtDev/Extern/Optalog/Include/cns_tooling.h"
#include "/EgtDev/Extern/Optalog/Include/cns_restricted_zone.h"
#include "/EgtDev/Extern/Optalog/Include/cns_textile.h"
#include "/EgtDev/Extern/Optalog/Include/cns_shear_sequence.h"
#include "/EgtDev/Extern/Optalog/Include/cns_egaltech.h"
#include "/EgtDev/Include/SELkLockId.h"
using namespace std ;
//----------------------------------------------------------------------------
IAutoNester*
CreateAutoNester( void)
{
// verifico la chiave e le opzioni
if ( ! TestKeyForENs( GetENsKey(), 0, GetENsLogger()))
return nullptr ;
// creo il NestMgr
return static_cast<IAutoNester*> ( new(nothrow) AutoNester) ;
}
//----------------------------------------------------------------------------
static void
GetCNSPath( const PolyArc& Outline, double dOffsX, double dOffsY, vector<CNS_Element>& vElem)
{
// converto nel formato Optalog
int nElem = Outline.GetPointNbr() ;
if ( Outline.IsClosed())
-- nElem ;
vElem.reserve( nElem) ;
Point3d ptIni, ptFin ; double dBulge ;
bool bNext = Outline.GetFirstArc( ptIni, ptFin, dBulge) ;
while ( bNext) {
double dLeftDeflection = - dBulge * DistXY( ptIni, ptFin) / 2 ;
vElem.push_back( { dOffsX + ptFin.x, dOffsY + ptFin.y, dLeftDeflection}) ;
bNext = Outline.GetNextArc( ptIni, ptFin, dBulge) ;
}
}
//----------------------------------------------------------------------------
// AutoNester
//----------------------------------------------------------------------------
AutoNester::AutoNester( void)
: m_pOrder( nullptr), m_pComp( nullptr), m_dInterpartGap( 0), m_dShearGap( 0), m_bGuillotine( false)
{
}
//----------------------------------------------------------------------------
AutoNester::~AutoNester( void)
{
Clear() ;
}
//----------------------------------------------------------------------------
bool
AutoNester::Clear( void)
{
if ( m_pOrder != nullptr) {
if ( m_pComp != nullptr) {
CNS_ComputationStatus status = CNS_GetComputationStatus( m_pComp) ;
if ( status != CNS_Ok && status != CNS_CancelledComputation)
CNS_CancelComputation( m_pComp) ;
}
CNS_DeleteLaunchingOrder( m_pOrder) ;
m_pOrder = nullptr ;
m_pComp = nullptr ;
}
m_IdSheetInfo.clear() ;
m_IdRzCstrPtr.clear() ;
m_IdPartPtr.clear() ;
m_dInterpartGap = 0 ;
m_dShearGap = 0 ;
m_bGuillotine = false ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::Start( void)
{
Clear() ;
m_pOrder = CNS_NewLaunchingOrder() ;
return ( m_pOrder != nullptr) ;
}
//----------------------------------------------------------------------------
bool
AutoNester::SetGuillotineMode( void)
{
if ( m_pOrder == nullptr)
return false ;
CNS_SetShearMode( m_pOrder, 1) ;
m_bGuillotine = true ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::SetStartCorner( int nCorner)
{
if ( m_pOrder == nullptr)
return false ;
CNS_Origin cnsOrig ;
switch ( nCorner) {
case NST_CORNER_BL : cnsOrig = CNS_BottomLeft ; break ;
case NST_CORNER_TL : cnsOrig = CNS_TopLeft ; break ;
case NST_CORNER_BR : cnsOrig = CNS_BottomRight ; break ;
case NST_CORNER_TR : cnsOrig = CNS_TopRight ; break ;
default : return false ;
}
CNS_SetOrigin( m_pOrder, cnsOrig) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::AddSheet( int nSheetId, const PolyArc& Outline, double dKerf, int nPriority, int nCount, bool* pbIsRect)
{
if ( m_pOrder == nullptr)
return false ;
// verifico outline
if ( Outline.GetArcNbr() == 0 || ! Outline.IsClosed())
return false ;
CNS_SheetPtr pSheet = nullptr ;
double dOffsX = 0 ;
double dOffsY = 0 ;
// se rettangolo
BBox3d b3Rect ;
if ( Outline.IsRectangleXY( b3Rect)) {
// imposto il rettangolo
Point3d ptMin ; double dDimX, dDimY, dDimZ ;
b3Rect.GetMinDim( ptMin, dDimX, dDimY, dDimZ) ;
pSheet = CNS_AddSheet( m_pOrder, nCount, dDimX, dDimY) ;
if ( pSheet == nullptr)
return false ;
// salvo offset per origine reale del rettangolo
dOffsX = ptMin.x ;
dOffsY = ptMin.y ;
// imposto altri dati
CNS_SetSheetGaps( pSheet, dKerf, dKerf, dKerf, dKerf) ;
// se richiesto, ritorno che è un rettangolo
if ( pbIsRect != nullptr)
*pbIsRect = true ;
}
// altrimenti forma generica
else {
// aggiungo contorno
vector<CNS_Element> vElem ;
GetCNSPath( Outline, 0, 0, vElem) ;
pSheet = CNS_AddNonRectangularSheet( m_pOrder, nCount, int( vElem.size()), vElem.data()) ;
if ( pSheet == nullptr)
return false ;
// imposto altri dati
CNS_SetNonRectangularSheetGaps( pSheet, dKerf, dKerf, dKerf, dKerf, dKerf) ;
// se richiesto, ritorno che non è un rettangolo
if ( pbIsRect != nullptr)
*pbIsRect = false ;
}
// imposto altri dati
CNS_SetSheetUserString( pSheet, ToString( nSheetId).c_str()) ;
CNS_SetSheetPriority( pSheet, nPriority) ;
// lo inserisco nel map
m_IdSheetInfo.emplace( nSheetId, SheetInfo( pSheet, dOffsX, dOffsY, dKerf)) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::AddDefectToSheet( int nSheetId, const PolyArc& Outline)
{
if ( m_pOrder == nullptr)
return false ;
// verifico outline
if ( Outline.GetArcNbr() == 0 || ! Outline.IsClosed())
return false ;
// cerco lo sheet
const auto Iter = m_IdSheetInfo.find( nSheetId) ;
if ( Iter == m_IdSheetInfo.end())
return false ;
CNS_SheetPtr pSheet = Iter->second.pSheet ;
double dOffsX = Iter->second.dOffsX ;
double dOffsY = Iter->second.dOffsY ;
// aggiungo difetto tramite suo contorno
vector<CNS_Element> vElem ;
GetCNSPath( Outline, -dOffsX, -dOffsY, vElem) ;
CNS_AddDefectToSheet( pSheet, int( vElem.size()), vElem.data()) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::AddRestrictedZoneToSheet( int nRzConstrId, int nSheetId, const PolyArc& Outline)
{
if ( m_pOrder == nullptr)
return false ;
// verifico outline
if ( Outline.GetArcNbr() == 0 || ! Outline.IsClosed())
return false ;
// cerco lo sheet
const auto IterS = m_IdSheetInfo.find( nSheetId) ;
if ( IterS == m_IdSheetInfo.end())
return false ;
CNS_SheetPtr pSheet = IterS->second.pSheet ;
double dOffsX = IterS->second.dOffsX ;
double dOffsY = IterS->second.dOffsY ;
// cerco il vincolo
CNS_RestrictedZoneConstraintPtr pRzCstr = nullptr ;
const auto IterC = m_IdRzCstrPtr.find( nRzConstrId) ;
if ( IterC == m_IdRzCstrPtr.end()) {
// creo vincolo per zona ristretta
pRzCstr = CNS_CreateRestrictedZoneConstraint( m_pOrder) ;
if ( pRzCstr == nullptr)
return false ;
m_IdRzCstrPtr.emplace( nRzConstrId, pRzCstr) ;
}
else
pRzCstr = IterC->second ;
// aggiungo zona ristretta tramite suo contorno
BBox3d b3Rect ;
if ( Outline.IsRectangleXY( b3Rect)) {
// imposto il rettangolo
Point3d ptMin, ptMax ;
b3Rect.GetMinMax( ptMin, ptMax) ;
CNS_Point CNSPT_Min, CNSPT_Max ;
CNSPT_Min.x = ptMin.x - dOffsX ;
CNSPT_Min.y = ptMin.y - dOffsY ;
CNSPT_Max.x = ptMax.x - dOffsX ;
CNSPT_Max.y = ptMax.y - dOffsY ;
ptMin += Vector3d( -dOffsX, -dOffsY, 0) ;
ptMax += Vector3d( -dOffsX, -dOffsY, 0) ;
CNS_SheetAddBoxRestrictedZone( pRzCstr, pSheet, CNSPT_Min, CNSPT_Max) ;
}
else {
vector<CNS_Element> vElem ;
GetCNSPath( Outline, -dOffsX, -dOffsY, vElem) ;
CNS_SheetAddRestrictedZone( pRzCstr, pSheet, int( vElem.size()), vElem.data()) ;
}
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::AddPart( int nPartId, const PolyArc& Outline, bool bCanFlip, bool bCanRotate, double dRotStep, int nPriority, int nCount)
{
if ( m_pOrder == nullptr)
return false ;
// verifico outline
if ( Outline.GetArcNbr() == 0 || ! Outline.IsClosed())
return false ;
// aggiungo contorno
vector<CNS_Element> vElem ;
GetCNSPath( Outline, 0, 0, vElem) ;
if ( vElem.size() <= 1)
return false ;
else if ( vElem.size() == 2) {
if ( abs( vElem[0].left_deflection) < 10 * EPS_SMALL &&
abs( vElem[1].left_deflection) < 10 * EPS_SMALL)
return false ;
}
CNS_PartPtr pPart = CNS_AddPart( m_pOrder, nCount, int( vElem.size()), vElem.data()) ;
if ( pPart == nullptr)
return false ;
// imposto altri dati
CNS_SetPartUserString( pPart, ToString( nPartId).c_str()) ;
CNS_SetPartAuthorizations( pPart, ( bCanFlip ? 1 : 0), ( bCanRotate ? 1 : 0), dRotStep) ;
CNS_SetPartPriority( pPart, nPriority) ;
// lo inserisco nel map
m_IdPartPtr.emplace( nPartId, pPart) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::AddHoleToPart( int nPartId, const PolyArc& Hole)
{
if ( m_pOrder == nullptr)
return false ;
// verifico buco
if ( Hole.GetArcNbr() == 0 || ! Hole.IsClosed())
return false ;
// cerco il pezzo
const auto Iter = m_IdPartPtr.find( nPartId) ;
if ( Iter == m_IdPartPtr.end())
return false ;
CNS_PartPtr pPart = Iter->second ;
// aggiungo buco al pezzo
vector<CNS_Element> vElem ;
GetCNSPath( Hole, 0, 0, vElem) ;
CNS_AddHoleToPart( pPart, int( vElem.size()), vElem.data()) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::AddAnotherOutlineToPart( int nPartId, const PolyArc& AnotherOutline)
{
if ( m_pOrder == nullptr)
return false ;
// verifico outline
if ( AnotherOutline.GetArcNbr() == 0 || ! AnotherOutline.IsClosed())
return false ;
// cerco il pezzo
const auto Iter = m_IdPartPtr.find( nPartId) ;
if ( Iter == m_IdPartPtr.end())
return false ;
CNS_PartPtr pPart = Iter->second ;
// aggiungo contorno aggiuntivo al pezzo
vector<CNS_Element> vElem ;
GetCNSPath( AnotherOutline, 0, 0, vElem) ;
CNS_AddExternalBoundaryToPart( pPart, int( vElem.size()), vElem.data()) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::AddToolOutlineToPart( int nPartId, const PolyArc& ToolOutline)
{
if ( m_pOrder == nullptr)
return false ;
// verifico outline
if ( ToolOutline.GetArcNbr() == 0 || ! ToolOutline.IsClosed())
return false ;
// cerco il pezzo
const auto Iter = m_IdPartPtr.find( nPartId) ;
if ( Iter == m_IdPartPtr.end())
return false ;
CNS_PartPtr pPart = Iter->second ;
// aggiungo contorno di lavorazione
vector<CNS_Element> vElem ;
GetCNSPath( ToolOutline, 0, 0, vElem) ;
CNS_AddToolPathToPart( pPart, int( vElem.size()), vElem.data()) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::SetRestrictedZoneToPart( int nPartId, int nRzConstrId)
{
if ( m_pOrder == nullptr)
return false ;
// cerco il pezzo
const auto Iter = m_IdPartPtr.find( nPartId) ;
if ( Iter == m_IdPartPtr.end())
return false ;
CNS_PartPtr pPart = Iter->second ;
// recupero il vincolo
const auto IterC = m_IdRzCstrPtr.find( nRzConstrId) ;
if ( IterC == m_IdRzCstrPtr.end())
return false ;
CNS_RestrictedZoneConstraintPtr pRzCstr = IterC->second ;
// imposto il vincolo
CNS_SetZoneRestrictedPart( pRzCstr, pPart) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::SetStripYconstraintToPart( int nPartId, const Point3d& ptRef, double dStripStart, double dStripRepeat)
{
if ( m_pOrder == nullptr)
return false ;
// cerco il pezzo
const auto Iter = m_IdPartPtr.find( nPartId) ;
if ( Iter == m_IdPartPtr.end())
return false ;
CNS_PartPtr pPart = Iter->second ;
// imposto il vincolo
CNS_Point CNSPT_Ref ;
CNSPT_Ref.x = ptRef.x ;
CNSPT_Ref.y = ptRef.y ;
CNS_SetPartStripYconstraint( pPart, CNSPT_Ref, dStripStart, dStripRepeat) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::SetStripXconstraintToPart( int nPartId, const Point3d& ptRef, double dStripStart, double dStripRepeat)
{
if ( m_pOrder == nullptr)
return false ;
// cerco il pezzo
const auto Iter = m_IdPartPtr.find( nPartId) ;
if ( Iter == m_IdPartPtr.end())
return false ;
CNS_PartPtr pPart = Iter->second ;
// imposto il vincolo
CNS_Point CNSPT_Ref ;
CNSPT_Ref.x = ptRef.x ;
CNSPT_Ref.y = ptRef.y ;
CNS_SetPartStripXconstraint( pPart, CNSPT_Ref, dStripStart, dStripRepeat) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::SetInterpartGap( double dGap)
{
if ( m_pOrder == nullptr)
return false ;
m_dInterpartGap = max( 0., dGap) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::SetShearGap( double dShearGap)
{
if ( m_pOrder == nullptr)
return false ;
m_dShearGap = max( 0., dShearGap) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::SetReportFile( const string& sReportFile)
{
m_sReportFile = sReportFile ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::Compute( bool bMinimizeOnXvsY, int nMaxTime)
{
if ( m_pOrder == nullptr)
return false ;
// se necessario, imposto i gap ( di default sono a 0)
if ( m_dInterpartGap > 10 * EPS_SMALL) {
// gap tra i pezzi
CNS_SetInterpartGap( m_pOrder, m_dInterpartGap) ;
// gap sui difetti
for ( auto Iter = m_IdSheetInfo.begin() ; Iter != m_IdSheetInfo.end() ; ++ Iter)
CNS_SetDefectGap( Iter->second.pSheet, m_dInterpartGap) ;
}
// shear gap
if ( m_dShearGap > 10 * EPS_SMALL)
CNS_SetShearGap( m_pOrder, m_dShearGap) ;
// per evitare pezzi posti inutilmente di sghembo su ultimo pannello
CNS_SetObjective( m_pOrder, ( bMinimizeOnXvsY ? CNS_IntelligentMinimizeX : CNS_IntelligentMinimizeY)) ;
// recupero i codici di sblocco del nesting
string sKeyId, sKeySign ;
SplitFirst( GetENsKey2(), "-", sKeyId, sKeySign) ;
// verifico si riferiscano alla chiave di protezione in uso
int nKeySN, nKeyId ;
if ( ! GetLockSN( nKeySN) || ! FromString( sKeyId, nKeyId) || nKeySN != nKeyId)
return false ;
// passo i codici al nester
CNS_UnLockLaunchingOrderOxy( m_pOrder, sKeyId.c_str(), sKeySign.c_str()) ;
// se richiesto debug
if ( ! IsEmptyOrSpaces( m_sReportFile))
CNS_GenerateLaunchingOrderProblem( m_pOrder, m_sReportFile.c_str()) ;
// lancio l'esecuzione
m_pComp = CNS_LaunchLocalComputation( m_pOrder, nMaxTime) ;
return ( m_pComp != nullptr) ;
}
//----------------------------------------------------------------------------
bool
AutoNester::CancelComputation( void)
{
if ( m_pOrder == nullptr || m_pComp == nullptr)
return false ;
CNS_ComputationStatus status = CNS_CancelComputation( m_pComp) ;
return ( status == CNS_CancelledComputation || status == CNS_Ok) ;
}
//----------------------------------------------------------------------------
bool
AutoNester::GetComputationStatus( int& nStatus)
{
if ( m_pOrder == nullptr || m_pComp == nullptr)
return false ;
CNS_ComputationStatus status = CNS_GetComputationStatus( m_pComp) ;
if ( status == CNS_CancelledComputation)
nStatus = 0 ;
else if ( status == CNS_PendingComputation)
nStatus = 1 ;
else if ( status == CNS_IntermediateResult)
nStatus = 2 ;
else if ( status == CNS_Ok)
nStatus = 3 ;
else
nStatus = - 1 ;
return ( nStatus >= 0) ;
}
//----------------------------------------------------------------------------
bool
AutoNester::GetResults( double& dTotFillRatio, ANIVECT& vANI)
{
// verifico validità calcolo
if ( m_pComp == nullptr)
return false ;
// recupero la soluzione
CNS_SolutionPtr pSol = CNS_GetSolution( m_pComp) ;
if ( pSol == nullptr)
return false ;
// percentuale di riempimento complessivo
dTotFillRatio = CNS_GetFillRatio( pSol) ;
// recupero i dati di nesting
vANI.clear() ;
int nNestCount = CNS_GetNumberOfNestings( pSol) ;
for ( int i = 0 ; i < nNestCount ; ++ i) {
// riferimento allo i-esimo nesting
CNS_NestingPtr pNest = CNS_GetNesting( pSol, i) ;
// dati dello sheet
CNS_SheetPtr pSheet = CNS_GetSheet( pNest) ;
const char* szSheetId = CNS_GetSheetUserString( pSheet) ;
int nSheetId = 999 ;
if ( szSheetId != nullptr)
FromString( szSheetId, nSheetId) ;
int nMult = CNS_GetMultiplicity( pNest) ;
int nPartCount = CNS_GetNumberOfNestedParts( pNest) ;
double dFillRatio = CNS_GetNestingFillRatio( pNest) ;
vANI.push_back( { nMult, nSheetId, nPartCount, dFillRatio, 0, 0}) ;
// offset per origine sheet rettangolari
const auto Iter = m_IdSheetInfo.find( nSheetId) ;
double dOffsX = ( Iter != m_IdSheetInfo.end() ? Iter->second.dOffsX : 0) ;
double dOffsY = ( Iter != m_IdSheetInfo.end() ? Iter->second.dOffsY : 0) ;
// ciclo sui pezzi nestati
for ( int j = 0; j < nPartCount ; ++ j) {
// dati del pezzo
CNS_PartPtr pPart ;
double dX, dY, dAngRot ;
int nFlip ;
CNS_GetNestedPart( pNest, j, &pPart, &dX, &dY, &nFlip, &dAngRot) ;
const char* szPartId = CNS_GetPartUserString( pPart) ;
int nPartId = 999 ;
if ( szPartId != nullptr)
FromString( szPartId, nPartId) ;
vANI.push_back( { 0, nPartId, nFlip, dOffsX + dX, dOffsY + dY, dAngRot}) ;
}
}
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::CalcShearSequence( int nNesting, PNTVECTOR& vPtStart, PNTVECTOR& vPtEnd)
{
vPtStart.clear() ;
vPtEnd.clear() ;
// verifico se impostata modalità ghigliottina
if ( ! m_bGuillotine)
return false ;
// verifico vincolo con intergap tra i pezzi
if ( m_dShearGap < m_dInterpartGap - EPS_SMALL)
return false ;
// verifico validità calcolo
if ( m_pComp == nullptr)
return false ;
// recupero la soluzione
CNS_SolutionPtr pSol = CNS_GetSolution( m_pComp) ;
if ( pSol == nullptr)
return false ;
// recupero il nesting richiesto
int nNestCount = CNS_GetNumberOfNestings( pSol) ;
if ( nNesting < 0 || nNesting > nNestCount - 1)
return false ;
CNS_NestingPtr pNest = CNS_GetNesting( pSol, nNesting) ;
// recupero pannello
CNS_SheetPtr pSheet = CNS_GetSheet( pNest) ;
const char* szSheetId = CNS_GetSheetUserString( pSheet) ;
int nSheetId = 999 ;
if ( szSheetId != nullptr)
FromString( szSheetId, nSheetId) ;
const auto Iter = m_IdSheetInfo.find( nSheetId) ;
// verifico vincolo con kerf del pannello
double dSheetKerf = ( Iter != m_IdSheetInfo.end() ? Iter->second.dKerf : 0) ;
if ( dSheetKerf > EPS_SMALL && m_dShearGap > dSheetKerf + EPS_SMALL)
return false ;
// offset per origine pannello
double dOffsX = ( Iter != m_IdSheetInfo.end() ? Iter->second.dOffsX : 0) ;
double dOffsY = ( Iter != m_IdSheetInfo.end() ? Iter->second.dOffsY : 0) ;
// calcolo shear sequence
CNS_ShearSequencePtr pShearSeq = CNS_ComputeShearSequence( pNest) ;
int nCnt = CNS_GetNumberOfElementsInShearSequence( pShearSeq) ;
vPtStart.reserve( nCnt) ;
vPtEnd.reserve( nCnt) ;
for ( int j = 0 ; j < nCnt ; j ++) {
double dStartX, dEndX, dStartY, dEndY ;
CNS_GetElementInShearSequence( pShearSeq, j, &dStartX, &dStartY, &dEndX, &dEndY) ;
vPtStart.emplace_back( dStartX + dOffsX, dStartY + dOffsY, 0) ;
vPtEnd.emplace_back( dEndX + dOffsX, dEndY + dOffsY, 0) ;
}
CNS_DeleteShearSequence( pShearSeq) ;
return true ;
}
//----------------------------------------------------------------------------
bool
AutoNester::PrintResults( const string& sHtmlFile)
{
// verifico validità calcolo
if ( m_pComp == nullptr)
return false ;
// recupero la soluzione
CNS_SolutionPtr pSol = CNS_GetSolution( m_pComp) ;
if ( pSol == nullptr)
return false ;
// se richiesto risultato in html
if ( ! IsEmptyOrSpaces( sHtmlFile))
CNS_GenerateHtmlSolutionReport( pSol, sHtmlFile.c_str()) ;
return true ;
}