-- BeamNestProcess.lua by Egaltech s.r.l. 2023/01/15 -- Gestione nesting automatico travi -- 2022/10/05 Piccole modifiche per far funzionare correttamente i compilati. -- 2022/10/06 Corretto bug che moltiplicava i pezzi se erano presenti più grezzi della stessa sezione. -- 2023/01/15 Piccole correzioni. -- Intestazioni require( 'EgtBase') _ENV = EgtProtectGlobal() EgtEnableDebug( false) -- Per test --NEST = {} --NEST.FILE = 'c:\\TechnoEssetre7\\EgtData\\Prods\\0010\\Bar_10_1.btl' --NEST.MACHINE = 'Essetre-90480019_MW' --NEST.FLAG = 3 local sLog = ' +++ BeamNestProcess : ' .. NEST.FILE .. ', ' .. NEST.MACHINE .. ', ' .. LEN[1] EgtOutLog( sLog) -- flag per abilitare statistiche in log local bLogStat = false -- Cancello file di log specifico local sLogFile = EgtChangePathExtension( NEST.FILE, '.txt') EgtEraseFile( sLogFile) -- Funzioni per scrittura su file di log specifico local function WriteErrToLogFile( nErr, sMsg, nRot, nCutId, nTaskId) local hFile = io.open( sLogFile, 'a') hFile:write( 'ERR=' .. tostring( nErr) .. '\n') hFile:write( sMsg .. '\n') hFile:write( 'ROT=' .. tostring( nRot or 0) .. '\n') hFile:write( 'CUTID=' .. tostring( nCutId or 0) .. '\n') hFile:write( 'TASKID=' .. tostring( nTaskId or 0) .. '\n') hFile:close() end local function WriteTimeToLogFile( dTime) local hFile = io.open( sLogFile, 'a') hFile:write( 'TIME=' .. EgtNumToString( dTime) .. '\n') hFile:close() end -- Funzione per gestire visualizzazione dopo errore local function PostErrView( nErr, sMsg) if nErr ~= 0 and ( NEST.FLAG == 1 or NEST.FLAG == 2 or NEST.FLAG == 5) then EgtSetView( SCE_VD.ISO_SW, false) EgtZoom( SCE_ZM.ALL) EgtOutBox( sMsg, 'BatchProcess (err=' .. tostring( nErr) .. ')', 'ERRORS') end end -- Funzione per gestire visualizzazione dopo warning local function PostWarnView( nWarn, sMsg) if nWarn ~= 0 and ( NEST.FLAG == 1 or NEST.FLAG == 2 or NEST.FLAG == 5) then EgtSetView( SCE_VD.ISO_SW, false) EgtZoom( SCE_ZM.ALL) EgtOutBox( sMsg, 'BatchProcess (wrn=' .. tostring( nWarn) .. ')', 'WARNINGS') end end -- Funzione per aggiornare dati ausiliari local function UpdateAuxData( sAuxFile) local bModif = false -- Se definito LOAD90, aggiorno local sLoad90 = EgtGetStringFromIni( 'AuxData', 'LOAD90', '', sAuxFile) if sLoad90 ~= '' then local BtlInfoId = EgtGetFirstNameInGroup( GDB_ID.ROOT, 'BtlInfo') or GDB_ID.NULL EgtSetInfo( BtlInfoId, 'LOAD90', sLoad90) bModif = true end return bModif end local function PartsToFill( Parts) local nToFill = 0 for i = 1, #Parts do if Parts[i].Cnt > 0 then nToFill = nToFill + Parts[i].Cnt end end return nToFill end local function ExecMaximumFilling( Raw, Parts) -- Inizializzo maximum filler EgtMaxFillerStart() -- Inserisco i pezzi for i = 1, #Parts do EgtMaxFillerAddPart( i, Parts[i].Len, Parts[i].DispLen or Parts[i].Len, Parts[i].Cnt or 1) end -- Eseguo l'ottimizzazione --EgtStartCounter() EgtMaxFillerCompute( Raw.LenToFill, Raw.StartGap, Raw.MidGap, Raw.EndGap, Raw.SortType) --local dTime = EgtStopCounter() -- Recupero i risultati local nFilledParts, nDiffParts, dTotFillRatio = EgtMaxFillerGetResults() local OneRes = {} for i = 0, nDiffParts - 1 do local nPartId, nCount = EgtMaxFillerGetOneResult( i) table.insert( OneRes, { Id=nPartId, Count=nCount}) end --return { FilledParts=nFilledParts, DiffParts=nDiffParts, FillRatio=dTotFillRatio, Time=dTime, Data=OneRes} return { FilledParts=nFilledParts, DiffParts=nDiffParts, FillRatio=dTotFillRatio, Data=OneRes} end -- Funzione per trovare nome MachGroup local function NewMachGroupName() local nMachGroupId = EgtGetFirstMachGroup() if not nMachGroupId then return 1 end local nMaxMachGroup = 0 while nMachGroupId do sMachGroupName = EgtGetMachGroupName(nMachGroupId) local nMachGroupName = tonumber(sMachGroupName) if nMachGroupName > nMaxMachGroup then nMaxMachGroup = nMachGroupName end nMachGroupId = EgtGetNextMachGroup(nMachGroupId) end return nMaxMachGroup + 1 end local function TotRawCount(Raws) local nTotRaws = 0 for RawIndex = 1, #Raws do nTotRaws = nTotRaws + Raws[RawIndex].Count end return nTotRaws end local function TotPartLen(Parts) local nTotPartLen = 0 for PartIndex = 1, #Parts do nTotPartLen = nTotPartLen + ( Parts[PartIndex].Len * Parts[PartIndex].Cnt) end return nTotPartLen end -- Imposto direttorio libreria specializzata per Travi EgtAddToPackagePath( NEST.BASEDIR .. '\\LuaLibs\\?.lua') -- Imposto la macchina corrente e verifico sia abilitata per la lavorazione delle Travi EgtSetCurrMachine( NEST.MACHINE) local sMachDir = EgtGetCurrMachineDir() if not EgtExistsFile( sMachDir .. '\\Beam\\BeamData.lua') then NEST.ERR = 12 NEST.MSG = 'Error not configured for beam machine : ' .. sMachine WriteErrToLogFile( NEST.ERR, NEST.MSG) PostErrView( NEST.ERR, NEST.MSG) return end -- Elimino direttori altre macchine e imposto direttorio macchina corrente per ricerca librerie EgtRemoveBaseMachineDirFromPackagePath() EgtAddToPackagePath( sMachDir .. '\\Beam\\?.lua') -- Inizializzo contatori errori e avvisi local nErrCnt = 0 local nWarnCnt = 0 -- Grezzi local Raws = {} -- creo tabella dei grezzi for nIndex, nLen in pairs( LEN) do Raws[tonumber(nIndex)] = {LenToFill = nLen, StartGap = NEST.STARTOFFSET, MidGap = NEST.OFFSET, EndGap = 0, SortType = -1} end for nIndex, nQty in pairs( QTY) do Raws[tonumber(nIndex)].Count = nQty end -- cerco il grezzo con la lunghezza maggiore, epurata dello start gap local maxRawLenToFillNoStartGap = 0 for RawIndex = 1, #Raws do if Raws[RawIndex].Count > 0 then maxRawLenToFillNoStartGap = max( maxRawLenToFillNoStartGap, Raws[RawIndex].LenToFill - Raws[RawIndex].StartGap) end end -- Pezzi local Parts = {} -- ciclo su pezzi per aggiungerli al nesting local dTotLen = 0 for nPartId, nCount in pairs( PART) do -- recupero lunghezza pezzo local Len = EgtGetInfo( nPartId, "L", 'd') local DispLen = EgtIf( Len <= 1000, 2000, 0) --EgtIf( Len <= 2000, max( 2000, 6000 - Len), 0) -- aggiungo il pezzo solo se ci sta nel grezzo più lungo a disposizione if Len < maxRawLenToFillNoStartGap then for nCntIndex = 1 , nCount do table.insert( Parts, {Id = nPartId, Len = Len, DispLen = DispLen, Cnt = 1}) dTotLen = dTotLen + Len end end end -- lunghezza totale pezzi local dTotPartLen = TotPartLen( Parts) -- calcolo media delle barre necessarie local NeededRawsForType = {} for RawIndex = 1, #Raws do NeededRawsForType[RawIndex] = min( ceil( dTotPartLen / Raws[RawIndex].LenToFill), Raws[RawIndex].Count) end local RawQtySum = 0 for NeededRawIndex = 1, #NeededRawsForType do RawQtySum = RawQtySum + NeededRawsForType[NeededRawIndex] end local MediumRawQty = ceil( RawQtySum / #NeededRawsForType) if MediumRawQty > 1 then MediumRawQty = MediumRawQty - 1 end -- lista dei risultati local ResultList = {} local BestResult = nil local BestResultIndex = nil -- riordino lista pezzi per lunghezza table.sort( Parts, function( B1, B2) return B1.Len < B2.Len end) local function NestSolutionByIndex( Index) -- creo copia lista raw local TempRaws = {} for TempRawIndex = 1, #Raws do table.insert(TempRaws, {LenToFill = Raws[TempRawIndex].LenToFill, StartGap = Raws[TempRawIndex].StartGap, MidGap = Raws[TempRawIndex].MidGap, EndGap = Raws[TempRawIndex].EndGap, SortType = Raws[TempRawIndex].SortType, Count = Raws[TempRawIndex].Count}) end -- recupero pezzi corti local ShortList = {} local LongList = {} for PartIndex = 1, #Parts do if PartIndex <= Index then table.insert( ShortList, Parts[PartIndex]) else table.insert( LongList, Parts[PartIndex]) end Parts[PartIndex].Cnt = 1 end -- numero di pezzi piccoli per barra local ShortCount = Index local ShortForRaw = floor( ShortCount / MediumRawQty) local ExtraShortForRaw = 0 if MediumRawQty > 0 then ExtraShortForRaw = fmod( ShortCount, MediumRawQty) end -- creo lista pezzi corti singoli local SingleShortList = {} for ShortIndex = 1, #ShortList do for ShortCount = 1, ShortList[ShortIndex].Cnt do table.insert( SingleShortList, {Id = ShortList[ShortIndex].Id, Len = ShortList[ShortIndex].Len, DispLen = ShortList[ShortIndex].DispLen, Cnt = 1}) end end -- li divido per le barre previste local RawsShortList = {} local RawIndex = 0 local ShortRawIndex = 0 for ShortIndex = 1, #SingleShortList do if ShortRawIndex > 0 then table.insert( RawsShortList[RawIndex], SingleShortList[ShortIndex]) ShortRawIndex = ShortRawIndex - 1 else table.insert( RawsShortList, {SingleShortList[ShortIndex]}) RawIndex = RawIndex + 1 ShortRawIndex = ShortForRaw + EgtIf( RawIndex <= ExtraShortForRaw, 1, 0) - 1 end end -- Ciclo fino ad esaurimento pezzi o barre local dTotPartInRawLen = 0 local nRawTot = 0 local dRawTotLen = 0 local dTime = 0 local nCycle = 1 local CurrResult = {} while TotRawCount( TempRaws) > 0 and PartsToFill( Parts) > 0 do -- creo lista con pezzi lunghi e pezzi corti di questo Cycle local PartsToNest = {} for PartIndex = 1, #LongList do table.insert( PartsToNest, LongList[PartIndex]) end for CycleIndex = 1, #RawsShortList do if CycleIndex <= nCycle then for PartIndex = 1, #RawsShortList[CycleIndex] do table.insert( PartsToNest, RawsShortList[CycleIndex][PartIndex]) end end end -- se non ci sono pezzi da nestare, esco if PartsToFill( PartsToNest) <= 0 then break end -- Eseguo ottimizzazione per ogni lunghezza di barra local Results = {} for RawIndex = 1, #TempRaws do if TempRaws[RawIndex].Count > 0 then Results[RawIndex] = ExecMaximumFilling( TempRaws[RawIndex], PartsToNest) else Results[RawIndex] = { FillRatio = 0.001, LenToFill = 1000, DiffParts = 0} end end -- verifico quale e' quella con meno scarto local nMinWasteRawIndex = GDB_ID.NULL local dMinWaste = 100000 for ResultIndex = 1, #Results do if Results[ResultIndex] then local dWaste = (1 - Results[ResultIndex].FillRatio) * TempRaws[ResultIndex].LenToFill if Results[ResultIndex].DiffParts > 0 and dWaste < dMinWaste then dMinWaste = dWaste nMinWasteRawIndex = ResultIndex end end end -- verifico se ci sono pezzi if nMinWasteRawIndex > 0 and Results[nMinWasteRawIndex] and Results[nMinWasteRawIndex].DiffParts > 0 then -- riporto barra e pezzi nel risultato corrente local CurrBar = { BarLen = TempRaws[nMinWasteRawIndex].LenToFill, Parts = {}} local CurrX = TempRaws[nMinWasteRawIndex].StartGap local nInfoIndex = 1 for i = 1, Results[nMinWasteRawIndex].DiffParts do local PartIndex = Results[nMinWasteRawIndex].Data[i].Id local PartId = PartsToNest[PartIndex].Id local dLen = PartsToNest[PartIndex].Len for j = 1, Results[nMinWasteRawIndex].Data[i].Count do -- creo pezzo copia CurrPart = { Index = nInfoIndex, PartId = PartId, PosX = CurrX} table.insert( CurrBar.Parts, CurrPart) CurrX = CurrX + dLen + TempRaws[nMinWasteRawIndex].MidGap nInfoIndex = nInfoIndex + 1 end end table.insert( CurrResult, CurrBar) dTotPartInRawLen = dTotPartInRawLen + ( Results[nMinWasteRawIndex].FillRatio * TempRaws[nMinWasteRawIndex].LenToFill) nRawTot = nRawTot + 1 dRawTotLen = dRawTotLen + TempRaws[nMinWasteRawIndex].LenToFill -- Aggiorno per prossima iterazione TempRaws[nMinWasteRawIndex].Count = TempRaws[nMinWasteRawIndex].Count - 1 for i = 1, Results[nMinWasteRawIndex].DiffParts do local PartId = Results[nMinWasteRawIndex].Data[i].Id PartsToNest[PartId].Cnt = PartsToNest[PartId].Cnt - Results[nMinWasteRawIndex].Data[i].Count end else -- se non sono riuscito ad inserire alcun pezzo esco dal ciclo perche' non ci sono pezzi inseribili break end nCycle = nCycle + 1 end -- riporto risultato in lista ResultList[Index] = dTotPartInRawLen if not BestResult or not BestResultIndex or ( dTotPartInRawLen > ResultList[BestResultIndex] + 0.02 or ( abs( dTotPartInRawLen - ResultList[BestResultIndex]) < 0.02 and dRawTotLen < BestResult.RawTotLen - 0.02)) then BestResult = CurrResult BestResult.RawTotLen = dRawTotLen BestResultIndex = Index end end local CycleCount = 0 local MinTime = 10 + pow( 3, ceil( log10( #Parts)) - 1) if bLogStat then EgtOutLog('MinTime: ' .. MinTime ) end local MaxTime = 30 + pow( 7, ceil( log10( #Parts)) - 1) if bLogStat then EgtOutLog('MaxTime: ' .. MaxTime ) end local TargetRatio = 0.98 local dTargetRatioLen = TargetRatio * dTotLen if bLogStat then EgtOutLog('TargetRatioLen: ' .. dTargetRatioLen ) end local CurrTime = 0 local function NestSolutionFromSP( StartingPoint, OscillationStep) -- ciclo sulle possibilita' da un punto di origine con uno step fisso local CurrResultIndex = StartingPoint NestSolutionByIndex( StartingPoint) if OscillationStep == 0 then return end local CycleIndex = 1 local nOutOfBoundary = 0 while nOutOfBoundary ~= 3 do CurrTime = EgtStopCounter() / 1000 if bLogStat then EgtOutLog('CurrTime: ' .. CurrTime ) end if bLogStat then EgtOutLog('BestRatio: ' .. dTotLen / BestResult.RawTotLen ) end -- se e' passato il tempo massimo, o e' passato il tempo minimo, ha inserito tutti i pezzi e la percentuale di utilizzo del materiale e' maggiore della soglia if CurrTime > MaxTime or ( CurrTime > MinTime and ResultList[BestResultIndex] > dTotLen - 0.1 and ( dTotLen / BestResult.RawTotLen ) >= TargetRatio) then if bLogStat then EgtOutLog('Brake') end break end local bCurrOutOfBoundary = false if CurrResultIndex < 0 then bCurrOutOfBoundary = true if nOutOfBoundary == 2 then nOutOfBoundary = 3 else nOutOfBoundary = 1 end end if CurrResultIndex > #Parts then bCurrOutOfBoundary = true if nOutOfBoundary == 1 then nOutOfBoundary = 3 else nOutOfBoundary = 2 end end if not bCurrOutOfBoundary and not ResultList[CurrResultIndex] then NestSolutionByIndex( CurrResultIndex) if bLogStat then EgtOutLog('CurrResultIndex: ' .. CurrResultIndex ) end if bLogStat then EgtOutLog('Result: ' .. ResultList[CurrResultIndex]) end CycleCount = CycleCount + 1 end CurrResultIndex = StartingPoint + EgtIf( CycleIndex % 2 == 0, (CycleIndex / 2) * OscillationStep, -( ( CycleIndex + 1) / 2) * OscillationStep ) CycleIndex = CycleIndex + 1 end end -- lancio calcolo EgtStartCounter() local StartingResult = floor( #Parts * 0.3) if bLogStat then EgtOutLog('StartingResult: ' .. StartingResult ) end --local Step = floor( #Parts / 10) * floor( log10( #Parts)) local nDividendo = pow( 10, floor( log10( #Parts)) - 1) nDividendo = EgtIf( nDividendo ~= 1, nDividendo, 10) local Step = floor( #Parts / nDividendo) * floor( log10( #Parts)) if bLogStat then EgtOutLog('Step: ' .. Step ) end NestSolutionFromSP( StartingResult, Step) if Step > 1 then NestSolutionFromSP( StartingResult, 1) end -- creo gruppi di lavorazione per risultato for MachGroupIndex = 1, #BestResult do local CurrMachGroup = BestResult[ MachGroupIndex] -- creo gruppo di lavorazione local MachGroupName = NewMachGroupName() nMachGroup = EgtAddMachGroup( MachGroupName) EgtSetInfo( nMachGroup, "BARLEN", CurrMachGroup.BarLen) EgtSetInfo( nMachGroup, "MATERIAL", NEST.MATERIAL) EgtSetInfo( nMachGroup, "AUTONEST", 1) -- scrivo dati per variabili P di comunicazione con la macchina in gruppo di lavorazione EgtSetInfo( nMachGroup, "PRODID", NEST.PRODID) EgtSetInfo( nMachGroup, "PATTID", nMachGroup) -- Disegno i pezzi for i = 1, #CurrMachGroup.Parts do local CurrPart = CurrMachGroup.Parts[ i] -- creo pezzo copia local nPartDuploId = EgtDuploNew( CurrPart.PartId) EgtSetInfo( nMachGroup, "PART" .. CurrPart.Index, nPartDuploId .. "," .. CurrPart.PosX) end end -- creo grezzi per ogni gruppo di lavorazione local nRawCnt = 0 local nRawTot = ResultList[BestResultIndex] _G.BEAM = {} BEAM.FILE = NEST.FILE BEAM.MACHINE = NEST.MACHINE BEAM.FLAG = 6 -- CREATE_PANEL BEAM.BASEDIR = NEST.BASEDIR nMachGroup = EgtGetFirstMachGroup() while nMachGroup do local nNextMachGroup = EgtGetNextMachGroup( nMachGroup) EgtSetCurrMachGroup( nMachGroup) if EgtGetInfo( nMachGroup, "AUTONEST",'i') == 1 then EgtRemoveInfo( nMachGroup, "AUTONEST") EgtSetInfo( nMachGroup, "UPDATEUI", 1) local bOk, sErr = pcall( dofile, BEAM.BASEDIR .. "\\BatchProcessNew.lua") if not bOk then EgtOutLog( 'Error in BatchProcessNew.lua call (' .. ( sErr or '') ..')') end nRawCnt = nRawCnt + 1 -- aggiorno interfaccia EgtProcessEvents( 200 + ( nRawCnt / nRawTot * 100), 0) end nMachGroup = nNextMachGroup end EgtResetCurrMachGroup() NEST.ERR = 0 EgtOutLog( ' +++ BeamNestProcess completed')