//---------------------------------------------------------------------------- // EgalTech 2025-2025 //---------------------------------------------------------------------------- // File : MachOptimization.cpp Data : 31.03.25 Versione : 2.7a1 // Contenuto : Classe per calcolo ottimizzato per le lavorazioni. // // // Modifiche : 31.03.2025 RE Creazione modulo. // // //---------------------------------------------------------------------------- //--------------------------- Include ---------------------------------------- #include "stdafx.h" #include "DllMain.h" #include "MachOptimization.h" #include "/EgtDev/Include/EGnStringUtils.h" #include "/EgtDev/Include/EGkGeoConst.h" #include "/EgtDev/Include/ENkShortestPath.h" #include "/EgtDev/Include/EgtPointerOwner.h" #include #include /* Nomenclatura : - TC = Tool Change ( cambio utensile ) */ #define _DEBUG_OPT 0 #define _TIME_DEBUG 0 #if _TIME_DEBUG #include "/EgtDev/Include/EgtPerfCounter.h" #endif using namespace std ; // Tempo massimo di lavorazione static const int MAXTIME = 1073741823U ; // Dato che tutte le distanze sono salvate come intere ( se ho 4 lavorazioni disposte sui vertici di un // quadrato unitario, la distanza tra i vertici di uno stesso lato e tra i vertici delle diagonali // risulta sempre 1, quindi moltiplico per COEFF in modo da avere una maggiore precisione. // ( vertici su un lato -> dist = 1000, vertici sulla diagonale -> dist = 1414) // invece che ( vertici su un lato -> dist = int( 1) = 1, vertici sulla diagonale -> dist = int( sqrt( 2)) = 1) static const int COEFF = 1000 ; //---------------------------------------------------------------------------- IMachOptimization* CreateMachOptimization( void) { return static_cast ( new( nothrow) MachOptimization) ; } //---------------------------------------------------------------------------- MachOptimization::MachOptimization( void) { m_vMachOptm.clear() ; m_vToolOptm.clear() ; m_vDependences.clear() ; m_vSuggDependences.clear() ; m_dFeedL = 1. ; m_dFeedA = 1. ; m_bAllMandatory = false ; m_bOptInGroup = false ; m_vOpenBounds.clear() ; } //---------------------------------------------------------------------------- MachOptimization::~MachOptimization( void) { } //---------------------------------------------------------------------------- /* Funzione per inserire un Utensile */ bool MachOptimization::InsertTool( int nId, double dTCX, double dTCY, double dTCZ, double dTCA, double dTCB, double dTCC, bool bX, bool bY, bool bZ, bool bA, bool bB, bool bC, double dTLoad, double dTUnL) { // Controllo se Id già presente for ( const ToolOptm& currTool : m_vToolOptm) { if ( currTool.nId == nId) { LOG_ERROR( GetENkLogger(), ( "Duplicated Tool id : " + ToString( nId)).c_str()) ; return false ; } } // Tempi sempre positivi if ( dTLoad < 0. || dTUnL < 0.) { LOG_ERROR( GetENkLogger(), string( ( "Time must be positive :")).c_str()) ; return false ; } // Inserisco il nuovo record m_vToolOptm.emplace_back( nId, dTCX, dTCY, dTCZ, dTCA, dTCB, dTCC, bX, bY, bZ, bA, bB, bC, dTLoad, dTUnL) ; return true ; } //---------------------------------------------------------------------------- /* Funzione per inserire una Lavorazione */ bool MachOptimization::InsertMachining( int nId, int nToolId, int nGroup, double dX_Start, double dY_Start, double dZ_Start, double dA_Start, double dB_Start, double dC_Start, double dX_End, double dY_End, double dZ_End, double dA_End, double dB_End, double dC_End) { // Controllo se Id già presente for ( const MachOptm& currMach : m_vMachOptm) { if ( currMach.nId == nId) { LOG_ERROR( GetENkLogger(), ( "Duplicated Machining id : " + ToString( nId)).c_str()) ; return false ; } } // Controllo che l'utensile sia stato precedentemente inserito bool bOk = false ; for ( const ToolOptm& currTool : m_vToolOptm) { bOk = currTool.nId == nToolId ; if ( bOk) break ; } if ( ! bOk) { LOG_ERROR( GetENkLogger(), ( "Tool not found : " + ToString( nToolId)).c_str()) ; return false ; } // Inserisco il nuovo record m_vMachOptm.emplace_back( nId, nToolId, nGroup, dX_Start, dY_Start, dZ_Start, dA_Start, dB_Start,dC_Start, dX_End, dY_End, dZ_End, dA_End, dB_End, dC_End) ; return true ; } //---------------------------------------------------------------------------- /* Funzione per l'inserimento delle dipendenze di ordine tra le lavorazioni [devono essere rispettate] ( nIdPrev deve essere eseguita prima di nIdNext ) */ bool MachOptimization::InsertDependence( int nIdPrev, int nIdNext) { // Check della dipendenza con quelle inserite bool bExist = false ; bool bConflict = false ; if ( ! CheckDependences( nIdPrev, nIdNext, m_vDependences, false, bExist, bConflict)) return false ; // Se esiste di già, non faccio nulla if ( bExist) return true ; // Se in conflitto, allora errore if ( bConflict) return false ; // Inserisco la nuova dipendenza m_vDependences.emplace_back( make_pair( nIdPrev, nIdNext)) ; return true ; } //---------------------------------------------------------------------------- /* Funzione per l'inserimento delle dipendenze di ordine tra le lavorazioni [si cerca di rispettarle] ( nIdPrev dovrebbe essere eseguita prima di nIdNext ) */ bool MachOptimization::InsertSuggestedDependences( int nIdPrev, int nIdNext) { // Check della dipendenza con quelle inserite bool bExist = false ; bool bConflict = false ; if ( ! CheckDependences( nIdPrev, nIdNext, m_vSuggDependences, false, bExist, bConflict)) return false ; // Se già presente non faccio nulla if ( bExist) return true ; // Inserisco la nuova dipendenza m_vSuggDependences.emplace_back( make_pair( nIdPrev, nIdNext)) ; return true ; } //---------------------------------------------------------------------------- /* Funzione per impostare la prima Lavorazione */ bool MachOptimization::SetFirstMachining( int nId) { // Controllo che l'Id sia presente if ( find_if( m_vMachOptm.begin(), m_vMachOptm.end(), [&]( const MachOptm& Lav) { return Lav.nId == nId ; }) == m_vMachOptm.end()) { LOG_ERROR( GetENkLogger(), ( "Machining id not found : " + ToString( nId)).c_str()) ; return false ; } // Assegno la prima lavorazione for ( int i = 0 ; i < int( m_vMachOptm.size()) ; ++ i) { if ( m_vMachOptm[i].nId == nId) continue ; if ( ! InsertDependence( nId, m_vMachOptm[i].nId)) return false ; } return true ; } //---------------------------------------------------------------------------- /* Funzione per impostare l'ultima Lavorazione */ bool MachOptimization::SetLastMachining( int nId) { // Controllo che l'Id sia presente if ( find_if( m_vMachOptm.begin(), m_vMachOptm.end(), [&]( const MachOptm& Lav) { return Lav.nId == nId ; }) == m_vMachOptm.end()) { LOG_ERROR( GetENkLogger(), ( "Machining id not found : " + ToString( nId)).c_str()) ; return false ; } // Assegno l'ultima lavorazione for ( int i = 0 ; i < int( m_vMachOptm.size()) ; ++ i) { if ( m_vMachOptm[i].nId == nId) continue ; if ( ! InsertDependence( m_vMachOptm[i].nId, nId)) return false ; } return true ; } //---------------------------------------------------------------------------- /* Funzione per impostare la Feed Lineare e Angolare */ bool MachOptimization::SetFeeds( double dFeedL, double dFeedA) { // Controllo dei valori if ( dFeedL < EPS_ZERO || dFeedA < EPS_ZERO) { LOG_ERROR( GetENkLogger(), string( " Feeds must be positive").c_str()) ; return false ; } // Assegno le Feed m_dFeedL = dFeedL ; m_dFeedA = dFeedA ; return true ; } //---------------------------------------------------------------------------- /* Funzione per impostare ogni dipendenza da Gruppo come vincolo */ bool MachOptimization::SetAllGroupsAsMandatory( bool bAllMandatory) { m_bAllMandatory = bAllMandatory ; return true ; } //---------------------------------------------------------------------------- bool MachOptimization::SetOptimizationForGroups( bool bOptForGroups) { m_bOptInGroup = bOptForGroups ; return true ; } //----------------------------------------------------------------------------- bool MachOptimization::SetOpenBound( bool bStartVsEnd, int nFlag, double dX, double dY, double dZ) { // Se parametro Start o End già impostato, lo sovrascrivo for ( int i = 0 ; i < int( m_vOpenBounds.size()) ; ++ i) { if ( m_vOpenBounds[i].bStartVsEnd == bStartVsEnd) { m_vOpenBounds[i].nFlag = nFlag ; m_vOpenBounds[i].dX = dX ; m_vOpenBounds[i].dY = dY ; m_vOpenBounds[i].dZ = dZ ; return true ; } } // altrimenti lo inserisco m_vOpenBounds.emplace_back( -1, bStartVsEnd, nFlag, dX, dY, dZ) ; return true ; } //---------------------------------------------------------------------------- bool MachOptimization::SetOpenBoundForGroups( int nGroup, bool bStartVsEnd, int nFlag, double dX, double dY, double dZ) { // Se parametro Start o End del Gruppo scelto già impostato, lo sovrascrivo for ( int i = 0 ; i < int( m_vOpenBounds.size()) ; ++ i) { if ( m_vOpenBounds[i].nGroup == nGroup && m_vOpenBounds[i].bStartVsEnd == bStartVsEnd) { m_vOpenBounds[i].nFlag = nFlag ; m_vOpenBounds[i].dX = dX ; m_vOpenBounds[i].dY = dY ; m_vOpenBounds[i].dZ = dZ ; return true ; } } // altrimenti lo inserisco m_vOpenBounds.emplace_back( nGroup, bStartVsEnd, nFlag, dX, dY, dZ) ; return true ; } //---------------------------------------------------------------------------- /* Funzione che assegna le dipendenze suggerite in base ai Gruppi */ bool MachOptimization::SetGroupDependences( void) { // Se ho meno di due lavorazioni, non devo fare nulla if ( int( m_vMachOptm.size()) < 2) return true ; // Recupero i diversi gruppi ( ordinati per priorità, quindi ordine decrescente) set> setGroup ; for ( const MachOptm& currLav : m_vMachOptm) setGroup.insert( currLav.nGroup) ; // Se presente un solo gruppo, non devo fare nulla if ( int( setGroup.size()) == 1) return true ; // Scorro i gruppi ordinati per priorità e assegno le dipendenze suggerite for ( auto it = setGroup.begin() ; it != setGroup.end() ; ++ it) { // Cerco tutti gli indici delle lavorazioni con quella priorità e quelli con priorità inferiore INTVECTOR vIndSamePriorityLav ; vIndSamePriorityLav.reserve( m_vMachOptm.size()) ; INTVECTOR vIndLowerPriorityLav ; vIndLowerPriorityLav.reserve( m_vMachOptm.size()) ; for ( int i = 0 ; i < int( m_vMachOptm.size()) ; ++ i) { if ( m_vMachOptm[i].nGroup == *it) vIndSamePriorityLav.emplace_back( i) ; else if ( m_vMachOptm[i].nGroup < *it) vIndLowerPriorityLav.emplace_back( i) ; } // Scorro le Lavorazioni che hanno la priorità massima corrente for ( const int& nInd : vIndSamePriorityLav) { // Scorro le Lavorazioni con priorità inferiore for ( const int& nInd_LowerPriority : vIndLowerPriorityLav) { // Assegno la dipendenze if ( m_bAllMandatory) { if ( ! InsertDependence( m_vMachOptm[nInd].nId, m_vMachOptm[nInd_LowerPriority].nId)) return false ; } else { if ( ! InsertSuggestedDependences( m_vMachOptm[nInd].nId, m_vMachOptm[nInd_LowerPriority].nId)) return false ; } } } } return true ; } //---------------------------------------------------------------------------- /* Funzione per il controllo delle dipendenze inserite : se già presenti o se in conflitto */ bool MachOptimization::CheckDependences( int nIdPrev, int nIdNext, const INTINTVECTOR& vDep, bool bBlockMessages, bool& bExist, bool& bConflict) const { bExist = false ; bConflict = false ; // controllo che tali Id siano entrambi presenti tra le lavorazioni, in caso opposto errore bool bOkIdPrev = false ; bool bOkIdNext = false ; for ( const MachOptm& currLav : m_vMachOptm) { if ( ! bOkIdPrev && currLav.nId == nIdPrev) bOkIdPrev = true ; if ( ! bOkIdNext && currLav.nId == nIdNext) bOkIdNext = true ; if ( bOkIdPrev && bOkIdNext) break ; } if ( ! bOkIdPrev || ! bOkIdNext) { if ( ! bBlockMessages) LOG_ERROR( GetENkLogger(), ( "Discharged Dependence, Id not found (" + ToString( nIdPrev) + "," + ToString( nIdNext) + ")").c_str()) ; return false ; } // controllo se la dipendenza è già stata inserita e che non sia presente una dipendeza inversa (conflitto) // (... (Id_i, Id_j) , ... , ( Id_j, Id_i), ...) for ( const auto& currDep : vDep) { if ( currDep.first == nIdPrev && currDep.second == nIdNext) { bExist = true ; if ( ! bBlockMessages) LOG_INFO( GetENkLogger(), ( "Already found Dependence (" + ToString( nIdPrev) + "," + ToString( nIdNext) + ")").c_str()) ; return true ; } if ( currDep.second == nIdPrev && currDep.first == nIdNext) { bConflict = true ; if ( ! bBlockMessages) LOG_ERROR( GetENkLogger(), ( "Conflict Dependence (" + ToString( nIdPrev) + "," + ToString( nIdNext) + ")").c_str()) ; return true ; } } return true ; } //---------------------------------------------------------------------------- /* Funzione per il calcolo della distanza euclidea tra assi Lineari o Angolari di due lavorazioni Considerando lo stesso utensile */ double MachOptimization::CalcTimeLavLav( const MachOptm& Lav1, const MachOptm& Lav2, bool bLin) const { double dDist = 0. ; double dFeed = EPS_SMALL ; if ( bLin) { /* Lav1 -- Feed Lineare --> Lav2 */ dDist = sqrt( ( Lav2.dX_Start - Lav1.dX_End) * ( Lav2.dX_Start - Lav1.dX_End) + ( Lav2.dY_Start - Lav1.dY_End) * ( Lav2.dY_Start - Lav1.dY_End) + ( Lav2.dZ_Start - Lav1.dZ_End) * ( Lav2.dZ_Start - Lav1.dZ_End)) ; dFeed = m_dFeedL ; } else { /* Lav1 -- Feed Angolare --> Lav2 */ dDist = sqrt( ( Lav2.dA_Start - Lav1.dA_End) * ( Lav2.dA_Start - Lav1.dA_End) + ( Lav2.dB_Start - Lav1.dB_End) * ( Lav2.dB_Start - Lav1.dB_End) + ( Lav2.dC_Start - Lav1.dC_End) * ( Lav2.dC_Start - Lav1.dC_End)) ; dFeed = m_dFeedA ; } return COEFF * ( dDist / dFeed) ; } //---------------------------------------------------------------------------- /* Funzione per il calcolo della distanza euclidea tra assi Lineari o Angolari tra una lavorazione e il suo TC */ bool MachOptimization::CalcTimeLavLavTC( const MachOptm& Lav1, const MachOptm& Lav2, bool bLin, double& dTimeLav1TC1, double& dTimeTC1TC2, double& dTimeTC2Lav2, double& dTUnload1, double& dTLoad2) const { /* dTimeLav1TC1 : Tempo tra EndPoint di Lav1 e Posizione di TC per Utensile di Lav1 dTimeTC1TC2 : Tempo di cambio utensile tra Tool di Lav1 e Lav2 dTimeTC2Lav2 : Tempo tra TC per utensile Lav2 e StartPoint di Lav2 */ dTimeLav1TC1 = 0. ; dTimeTC1TC2 = 0. ; dTimeTC2Lav2 = 0. ; dTUnload1 = 0. ; dTLoad2 = 0. ; // Recupero Utensile Lav1 e Lav2 int nIndToolLav1 = -1 ; int nIndToolLav2 = -1 ; bool bFoundToolLav1 = false ; bool bFoundToolLav2 = false ; for ( int i = 0 ; i < int( m_vToolOptm.size()) ; ++ i) { bFoundToolLav1 = ( m_vToolOptm[i].nId == Lav1.nIdTool) ; if ( bFoundToolLav1) nIndToolLav1 = i ; bFoundToolLav2 = ( m_vToolOptm[i].nId == Lav2.nIdTool) ; if ( bFoundToolLav2) nIndToolLav2 = i ; if ( nIndToolLav1 != -1 && nIndToolLav2 != -1) break ; } if ( nIndToolLav1 == -1) LOG_INFO( GetENkLogger(), ( "Tool Not Found :" + ToString( Lav1.nIdTool)).c_str()) ; if ( nIndToolLav2 == -1) LOG_INFO( GetENkLogger(), ( "Tool Not Found :" + ToString( Lav2.nIdTool)).c_str()) ; if ( nIndToolLav1 == -1 || nIndToolLav2 == -1) return false ; const ToolOptm& ToolLav1 = m_vToolOptm[nIndToolLav1] ; const ToolOptm& ToolLav2 = m_vToolOptm[nIndToolLav2] ; // Imposto i tempi di carico e scarico degli utensili dTUnload1 = COEFF * ToolLav1.dTUnLoad ; dTLoad2 = COEFF * ToolLav2.dTLoad ; // Se distanza lineare if ( bLin) { // Come primo tempo considero quello tra la fine di Lav1 e TC1 double dXi = Lav1.dX_End ; double dXe = ( ToolLav1.bTCX ? ToolLav1.dTCX : dXi) ; double dYi = Lav1.dY_End ; double dYe = ( ToolLav1.bTCY ? ToolLav1.dTCY : dYi) ; double dZi = Lav1.dZ_End ; double dZe = ( ToolLav1.bTCZ ? ToolLav1.dTCZ : dZi) ; double dDist = sqrt( ( dXe - dXi) * ( dXe - dXi) + ( dYe - dYi) * ( dYe - dYi) + ( dZe - dZi) * ( dZe - dZi)) ; dTimeLav1TC1 = COEFF * ( dDist / m_dFeedL) ; // Come secondo tempo considero quello tra TC1 e TC2 dXi = dXe ; dXe = ( ToolLav2.bTCX ? ToolLav2.dTCX : dXi) ; dYi = dYe ; dYe = ( ToolLav2.bTCY ? ToolLav2.dTCY : dYi) ; dZi = dZe ; dZe = ( ToolLav2.bTCZ ? ToolLav2.dTCZ : dZi) ; dDist = sqrt( ( dXe - dXi) * ( dXe - dXi) + ( dYe - dYi) * ( dYe - dYi) + ( dZe - dZi) * ( dZe - dZi)) ; dTimeTC1TC2 = COEFF * ( dDist / m_dFeedL) ; // Come terzo tempo considero quello tra TC2 e Lav2 dXi = dXe ; dYe = Lav2.dX_Start ; dYi = dYe ; dYe = Lav2.dY_Start ; dZi = dZe ; dZe = Lav2.dZ_Start ; dDist = sqrt( ( dXe - dXi) * ( dXe - dXi) + ( dYe - dYi) * ( dYe - dYi) + ( dZe - dZi) * ( dZe - dZi)) ; dTimeTC2Lav2 = COEFF * ( dDist / m_dFeedL) ; } // Se distanza angolare else { // Come primo tempo considero quello tra la fine di Lav1 e TC1 double dAi = Lav1.dA_Start ; double dAe = ( ToolLav1.dTCA ? ToolLav1.dTCA : dAi) ; double dBi = Lav1.dB_Start ; double dBe = ( ToolLav1.dTCB ? ToolLav1.dTCB : dBi) ; double dCi = Lav1.dC_Start ; double dCe = ( ToolLav1.dTCC ? ToolLav1.dTCC : dCi) ; double dDist = sqrt( ( dAe - dAi) * ( dAe - dAi) + ( dBe - dBi) * ( dBe - dBi) + ( dCe - dCi) * ( dCe - dCi)) ; dTimeLav1TC1 = COEFF * ( dDist / m_dFeedA) ; // Come secondo tempo considero quello tra TC1 e TC2 dAi = dAe ; dAe = ( ToolLav2.bTCA ? ToolLav2.dTCA : dAi) ; dBi = dBe ; dBe = ( ToolLav2.bTCB ? ToolLav2.dTCB : dBi) ; dCi = dCe ; dCe = ( ToolLav2.bTCC ? ToolLav2.dTCC : dCi) ; dDist = sqrt( ( dAe - dAi) * ( dAe - dAi) + ( dBe - dBi) * ( dBe - dBi) + ( dCe - dCi) * ( dCe - dCi)) ; dTimeTC1TC2 = COEFF * ( dDist / m_dFeedA) ; // Come terzo tempo considero quello tra TC2 e Lav2 dAi = dAe ; dAe = Lav2.dA_Start ; dBi = dBe ; dBe = Lav2.dB_Start ; dCi = dCe ; dCe = Lav2.dC_Start ; dDist = sqrt( ( dAe - dAi) * ( dAe - dAi) + ( dBe - dBi) * ( dBe - dBi) + ( dCe - dCi) * ( dCe - dCi)) ; dTimeTC2Lav2 = COEFF * ( dDist / m_dFeedA) ; } return true ; } //---------------------------------------------------------------------------- /* Funzione per calcolare il tempo nel passare dalla lavorazione nInd1-esima alla lavorazione nInd2-esima */ bool MachOptimization::CalcMachTime( int nInd1, int nInd2, int& nTime) const { // Se Indice fuori dal range, errore nTime = 0 ; if ( nInd1 < 0 || nInd1 >= int( m_vMachOptm.size()) || nInd2 < 0 || nInd2 >= int( m_vMachOptm.size())) return false ; // Recupero riferimento delle due lavorazioni const MachOptm& Lav1 = m_vMachOptm[nInd1] ; const MachOptm& Lav2 = m_vMachOptm[nInd2] ; return ( CalcMachTime( Lav1, Lav2, nTime)) ; } //---------------------------------------------------------------------------- /* Funzione per calcolare il tempo nel passare da una lavorazione all'altra */ bool MachOptimization::CalcMachTime( const MachOptm& Lav1, const MachOptm& Lav2, int& nTime) const { // Se l'utensile è lo stesso if ( Lav1.nIdTool == Lav2.nIdTool) nTime = int( max( CalcTimeLavLav( Lav1, Lav2, true), CalcTimeLavLav( Lav1, Lav2, false))) ; else { double dLTime1, dLTime2, dLTime3 ; double dATime1, dATime2, dATime3 ; double dTUnload1, dTLoad2 ; if ( ! CalcTimeLavLavTC( Lav1, Lav2, true, dLTime1, dLTime2, dLTime3, dTUnload1, dTLoad2) || ! CalcTimeLavLavTC( Lav1, Lav2, false, dATime1, dATime2, dATime3, dTUnload1, dTLoad2)) { LOG_ERROR( GetENkLogger(), "Error in computing time") ; return false ; } nTime += int( max( dLTime1, dATime1) + max( dLTime2, dATime2) + max( dLTime3, dATime3)) ; // Sommo i tempi di carico e scarico dei due utensili nTime += int( dTUnload1) + int( dTLoad2) ; } return true ; } //---------------------------------------------------------------------------- /* Funzione per il calcolo della Matrice dei tempi (NxN), dove N = #Lavorazioni */ bool MachOptimization::ComputeTimeMatrix( INTMATRIX& mTimes) const { // Creo una copia dei vettori delle dipendenze ( le cancello di volta in volta che le assegno) const int N = int( m_vMachOptm.size()) ; // Matrice NxN ( con Flag per cella già calcolata) mTimes.clear() ; mTimes.resize( N) ; for ( auto& Row : mTimes) Row.resize( N) ; // Calcolo i tempi tra tutte le lavorazioni for ( int i = 0 ; i < N ; ++ i) { for ( int j = 0 ; j < N ; ++ j) { // Se sono sulla diagonale, allora il tempo è infinito if ( i == j) { mTimes[i][j] = MAXTIME ; continue ; } // Se sono nella cella ( i, j), calcolo il tempo necessario if ( ! CalcMachTime( i, j, mTimes[i][j])) return false ; } } #if _DEBUG_OPT string sRowSplit = "" ; for ( int i = 0 ; i < N ; ++ i) sRowSplit += "----------" ; for ( int i = -1 ; i < N ; ++ i) { // Intestazione if ( i == -1) { LOG_INFO( GetENkLogger(), sRowSplit.c_str()) ; string sCell = "**********" ; for ( int j = 0 ; j < N ; ++ j) { int nId = m_vMachOptm[j].nId ; string sId = ToString( nId) ; int nLen = sId.length() ; for ( int k = 0 ; k < 10 - nLen ; ++ k) sCell += " " ; sCell += sId ; } LOG_INFO( GetENkLogger(), sCell.c_str()) ; continue ; } // Prima riga della colonna string sRow = "|" ; int nId = m_vMachOptm[i].nId ; string sId = ToString( nId) ; int nLen = sId.length() ; for ( int k = 0 ; k < 8 - nLen ; ++ k) sRow += " " ; sRow += sId ; sRow += "|" ; for ( int j = 0 ; j < N ; ++ j) { double dTime = mTimes[i][j] ; string sTime = ( dTime > MAXTIME - 1 ? "inf" : ToString( dTime, 1)) ; int nLen = sTime.length() ; for ( int k = 0 ; k < 9 - nLen ; ++ k) sRow += " " ; sRow += sTime ; sRow += "|" ; } LOG_INFO( GetENkLogger(), sRow.c_str()) ; } LOG_INFO( GetENkLogger(), sRowSplit.c_str()) ; #endif return true ; } //---------------------------------------------------------------------------- /* Funzione che restituisce la posizione della lavorazione di Id pari a nId nel vettore delle lavorazioni */ int MachOptimization::GetIndById( int nId) const { // Scorro le lavorazioni for ( int i = 0 ; i < int( m_vMachOptm.size()) ; ++ i) { if ( m_vMachOptm[i].nId == nId) return i ; } return -1 ; } //---------------------------------------------------------------------------- /* Funzione per separare le lavorazioni e le dipendenze a seconda dei gruppi */ bool MachOptimization::SplitMachOptsInGroups( MACHOPTMMATRIX& matMachOpt, vector& matDep, vector& matSuggDep) { // Se non ho lavorazioni, non faccio nulla if ( m_vMachOptm.empty()) return true ; // Se non devo ottimizzare per gruppi, errore if ( ! m_bOptInGroup) return false ; // Ordino le lavorazioni in base al gruppo sort( m_vMachOptm.begin(), m_vMachOptm.end(), []( const MachOptm& MachA, const MachOptm& MachB) { return ( MachA.nGroup > MachB.nGroup) ; }) ; // Separo le lavorazioni int nHighestPriority = m_vMachOptm[0].nGroup ; matMachOpt.emplace_back( MACHOPTMVECTOR{}) ; matMachOpt.back().emplace_back( m_vMachOptm[0]) ; for ( int i = 1 ; i < int( m_vMachOptm.size()) ; ++ i) { if ( m_vMachOptm[i].nGroup < nHighestPriority) { matMachOpt.emplace_back( MACHOPTMVECTOR{}) ; nHighestPriority = m_vMachOptm[i].nGroup ; } matMachOpt.back().emplace_back( m_vMachOptm[i]) ; } // Recupero le Dipendeze Obbligatorie e Suggerite tra i vari gruppi BOOLVECTOR vbDep( m_vDependences.size(), false) ; BOOLVECTOR vbSuggDep( m_vSuggDependences.size(), false) ; for ( const MACHOPTMVECTOR& vMachOpt : matMachOpt) { // Recupero gli Id delle lavorazioni correnti INTVECTOR vId ; vId.reserve( vMachOpt.size()) ; for ( const MachOptm& Lav : vMachOpt) vId.push_back( Lav.nId) ; // Inizializzo i vettori delle dipendenze matDep.emplace_back( INTINTVECTOR{}) ; matSuggDep.emplace_back( INTINTVECTOR{}) ; // Recupero le dipendenze associate a questo gruppo // NB. Devono essere solamente dipendenze interne al gruppo ( le altre vengono scartate a priori) for ( int i = 0 ; i < int( m_vDependences.size()) ; ++ i) { if ( ! vbDep[i] && find( vId.begin(), vId.end(), m_vDependences[i].first) != vId.end() && find( vId.begin(), vId.end(), m_vDependences[i].second) != vId.end()) { matDep.back().emplace_back( m_vDependences[i]) ; vbDep[i] = true ; } } for ( int i = 0 ; i < int( m_vSuggDependences.size()) ; ++ i) { if ( ! vbSuggDep[i] && find( vId.begin(), vId.end(), m_vSuggDependences[i].first) != vId.end() && find( vId.begin(), vId.end(), m_vSuggDependences[i].second) != vId.end()){ matSuggDep.back().emplace_back( m_vSuggDependences[i]) ; vbSuggDep[i] = true ; } } } return true ; } //---------------------------------------------------------------------------- bool MachOptimization::GetGlobalResult( INTVECTOR& vIds) { // Inserisco le lavorazioni per ordine di inserimento for ( const MachOptm& myLav : m_vMachOptm) vIds.push_back( myLav.nId) ; // Inizializzo le dipendenze suggerite dai gruppi di lavorazione if ( ! SetGroupDependences()) { LOG_ERROR( GetENkLogger(), "Error in defining Group depedences") ; return false ; } // Calcolo la matrice dei tempi INTMATRIX mTimes ; if ( ! ComputeTimeMatrix( mTimes)) { LOG_ERROR( GetENkLogger(), "Error in Computing Time Matrix") ; return false ; } // Istanzio il Problema ShortesPath con vincoli PtrOwner mySP( CreateShortestPath()) ; if ( IsNull( mySP)) return false ; // Per ogni lavorazione inserisco gli estremi for ( const MachOptm& Lav : m_vMachOptm) { if ( ! mySP->AddPoint( Lav.dX_Start, Lav.dY_Start, Lav.dZ_End, 0., 0., Lav.dX_End, Lav.dY_End, Lav.dZ_End, 0., 0.)) { LOG_ERROR( GetENkLogger(), "Error adding Machining Bounds") ; return false ; } } // Assegno la matrice delle distanze calcolata in precedenza if ( ! mySP->SetDistMatrix( mTimes)) { LOG_ERROR( GetENkLogger(), "Error setting distances matrix") ; return false ; } // Per ogni dipendeza obbligatoria assegno un vincolo for ( const INTINT& nnDep : m_vDependences) { if ( ! mySP->SetConstraintOrder( GetIndById( nnDep.first), GetIndById( nnDep.second))) { LOG_ERROR( GetENkLogger(), "Error in adding Constraints") ; return false ; } } // Aggiungo le dipendenza suggerite for ( const INTINT& nnSuggDep : m_vSuggDependences) { if ( ! mySP->SetSuggestedOrder( GetIndById( nnSuggDep.first), GetIndById( nnSuggDep.second))) { LOG_ERROR( GetENkLogger(), "Error in adding Suggested Dependence") ; return false ; } } // Se presenti dei incoli di OpenBound, li assegno if ( ! m_vOpenBounds.empty()) { for ( const OpenBoundOptm& OB : m_vOpenBounds) { if ( OB.nGroup == -1) { if ( ! mySP->SetOpenBound( OB.bStartVsEnd, OB.nFlag, OB.dX, OB.dY, OB.dZ, 0., 0.)) { LOG_ERROR( GetENkLogger(), "Error in adding OpenBound") ; return false ; } } } } // Calcolo la soluzione #if _TIME_DEBUG PerformanceCounter PC ; PC.Start() ; #endif if ( ! mySP->Calculate( IShortestPath::SP_OPEN)) { LOG_ERROR( GetENkLogger(), "Error in computing solution") ; return false ; } #if _TIME_DEBUG string sTime = string( to_string( m_vDependences.size()) + string( "Time : ") + ToString( PC.Stop())) ; LOG_INFO( GetENkLogger(), sTime.c_str()) ; #endif // Recupero l'ordine INTVECTOR U ; if ( ! mySP->GetOrder( U)) { LOG_ERROR( GetENkLogger(), "Error in ordering") ; return false ; } // Restituisco gli Id ordinati vIds.resize( m_vMachOptm.size()) ; for ( int nInd = 0 ; nInd < int( U.size()) ; ++ nInd) vIds[nInd] = m_vMachOptm[U[nInd]].nId ; return true ; } //---------------------------------------------------------------------------- bool MachOptimization::GetGroupResult( INTVECTOR& vIds) { // Se Flag ottimizzazione di gruppo non attivo, errore if ( ! m_bOptInGroup) return false ; // Recupero tutti i gruppi e le dipendenze separatamente ed ordinate per priorità di gruppo MACHOPTMMATRIX matMachOpt ; vector matDep, matSuggDep ; if ( ! SplitMachOptsInGroups( matMachOpt, matDep, matSuggDep)) return false ; // Per ognuna di esse eseguo l'algoritmo di ShortestPath MachOptm LastMach ; for ( int i = 0 ; i < int( matMachOpt.size()) ; ++ i) { m_vMachOptm.clear() ; m_vDependences.clear() ; m_vSuggDependences.clear() ; if ( matMachOpt.empty()) continue ; m_vMachOptm = matMachOpt[i] ; m_vDependences = matDep[i] ; m_vSuggDependences = matSuggDep[i] ; // Calcolo la matrice dei tempi INTMATRIX mTimes ; if ( ! ComputeTimeMatrix( mTimes)) { LOG_ERROR( GetENkLogger(), "Error in Computing Time Matrix") ; return false ; } // Istanzio il Problema ShortesPath con vincoli PtrOwner mySP( CreateShortestPath()) ; if ( IsNull( mySP)) return false ; // Per ogni lavorazione inserisco gli estremi for ( const MachOptm& Lav : m_vMachOptm) { if ( ! mySP->AddPoint( Lav.dX_Start, Lav.dY_Start, Lav.dZ_End, 0., 0., Lav.dX_End, Lav.dY_End, Lav.dZ_End, 0., 0.)) { LOG_ERROR( GetENkLogger(), "Error adding Machining Bounds") ; return false ; } } // Assegno la matrice delle distanze calcolata in precedenza if ( ! mySP->SetDistMatrix( mTimes)) { LOG_ERROR( GetENkLogger(), "Error setting distances matrix") ; return false ; } // Se presente un vincolo OpenBound lo assegno bool bForStart = false ; for ( const OpenBoundOptm& OB : m_vOpenBounds) { if ( OB.nGroup == m_vMachOptm[0].nGroup) { bForStart = ( bForStart || OB.bStartVsEnd) ; if ( ! mySP->SetOpenBound( OB.bStartVsEnd, OB.nFlag, OB.dX, OB.dY, OB.dZ, 0., 0.)) { LOG_ERROR( GetENkLogger(), "Error in adding OpenBound") ; return false ; } } } // Se ho una lavorazione impostata in precedenza e nessun vincolo OpenBound if ( ! bForStart && LastMach.nId != -1) { // Cerco le lavorazioni che non presentano precedenze MACHOPTMVECTOR vLav ; vLav.reserve( m_vMachOptm.size()) ; if ( m_vDependences.empty()) vLav = m_vMachOptm ; else { INTSET setSecondDep ; for ( const INTINT& Dep : m_vDependences) setSecondDep.insert( Dep.second) ; for ( int i = 0 ; i < int( m_vMachOptm.size()) ; ++ i) { if ( setSecondDep.find( m_vMachOptm[i].nId) == setSecondDep.end()) vLav.emplace_back( m_vMachOptm[i]) ; } } // Tra quelle valide cerco la migliore da cui partire int nBestLavId = 0 ; int nBestTime = MAXTIME ; for ( int i = 0 ; i < int( vLav.size()) ; ++ i) { int nCurrTime = MAXTIME ; CalcMachTime( LastMach, vLav[i], nCurrTime) ; if ( nCurrTime < nBestTime) { nBestTime = nCurrTime ; nBestLavId = vLav[i].nId ; } } // Imposto la prima lavorazione if ( ! SetFirstMachining( nBestLavId)) return false ; } // Per ogni dipendeza obbligatoria assegno un vincolo for ( const INTINT& nnDep : m_vDependences) { if ( ! mySP->SetConstraintOrder( GetIndById( nnDep.first), GetIndById( nnDep.second))) { LOG_ERROR( GetENkLogger(), "Error in adding Constraints") ; return false ; } } // Aggiungo le dipendenza suggerite for ( const INTINT& nnSuggDep : m_vSuggDependences) { if ( ! mySP->SetSuggestedOrder( GetIndById( nnSuggDep.first), GetIndById( nnSuggDep.second))) { LOG_ERROR( GetENkLogger(), "Error in adding Suggested Dependence") ; return false ; } } // Calcolo la soluzione #if _TIME_DEBUG PerformanceCounter PC ; PC.Start() ; #endif if ( ! mySP->Calculate( IShortestPath::SP_OPEN)) { LOG_ERROR( GetENkLogger(), "Error in computing solution") ; return false ; } // Recupero l'ordine INTVECTOR U ; if ( ! mySP->GetOrder( U)) { LOG_ERROR( GetENkLogger(), "Error in ordering") ; return false ; } // Restituisco gli Id ordinati for ( int nInd = 0 ; nInd < int( U.size()) ; ++ nInd) vIds.emplace_back( m_vMachOptm[U[nInd]].nId) ; // Memorizzo l'ultima lavorazione per il gruppo successivo LastMach = ( U.empty() ? MachOptm() : m_vMachOptm[U.back()]) ; } return true ; } //---------------------------------------------------------------------------- bool MachOptimization::Calculate( INTVECTOR& vIds) { // Se non ho lavorazioni, allora non faccio nulla vIds.clear() ; if ( m_vMachOptm.empty()) return true ; // Se ho una sola lavorazione allora ritorno se stessa if ( int( m_vMachOptm.size()) == 1) { vIds.push_back( m_vMachOptm[0].nId) ; return true ; } // --- Se richiesta ottimizzazione solo interna per i gruppi if ( m_bOptInGroup) return ( GetGroupResult( vIds)) ; // --- Se invece ottimizzazione globale return ( GetGlobalResult( vIds)) ; }