diff --git a/BatchProcessNew.lua b/BatchProcessNew.lua index afdbaf2..7595fdb 100644 --- a/BatchProcessNew.lua +++ b/BatchProcessNew.lua @@ -572,6 +572,7 @@ if bToProcess then PARTS[i].nIndexInParts = i PARTS[i].CombinationList = BeamExec.GetAvailableCombinations( PARTS[i]) PARTS[i].SplittingPoints = BeamLib.GetPartSplittingPoints( PARTS[i]) + PARTS[i].NotClampableLength = { STD = { dHead = 0, dTail = 0}, SIDE = { dHead = 0, dTail = 0}, DOWN = { dHead = 0, dTail = 0}} -- sovramateriale in testa al pezzo local dDeltaS = max( PARTS[i].dPosX - ( dBarLen - dLen), 0) diff --git a/NestProcess.lua b/NestProcess.lua new file mode 100644 index 0000000..d3fd807 --- /dev/null +++ b/NestProcess.lua @@ -0,0 +1,479 @@ +-- 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')