659 lines
23 KiB
Lua
659 lines
23 KiB
Lua
-- BeamNestProcess.lua by Egalware s.r.l. 2026/05/11
|
|
-- Gestione nesting automatico travi anche oblique
|
|
|
|
-- Intestazioni
|
|
require( 'EgtBase')
|
|
_ENV = EgtProtectGlobal()
|
|
EgtEnableDebug( false)
|
|
|
|
-- Include
|
|
local BeamData = require( 'BeamDataNew')
|
|
local BeamLib = require( 'BeamLib')
|
|
|
|
-- 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 : machine' .. sMachine .. 'note configured for Beam'
|
|
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')
|
|
|
|
|
|
----------------------------------------------------------------------------------------------------------
|
|
-- Parametri di configurazione Nesting
|
|
local CONFIG = {
|
|
-- Strategia di ricerca
|
|
NUM_LONGEST_CANDIDATES = 3,
|
|
|
|
-- Punteggi
|
|
BONUS_PERFECT_FIT = 500, -- Se lo scarto è quasi zero (es: < 5mm)
|
|
BONUS_SHARED_CUT = 100, -- Se le inclinazioni combaciano perfettamente
|
|
PENALTY_TOO_SHORT = 100, -- Se lo scarto è troppo piccolo per essere utile ma non zero
|
|
PENALTY_BAD_FIT_END_PHASE = 1000, -- Se siamo alla fine (>80%) e si utilizza un pezzo corto con fit mediocre
|
|
|
|
-- Macchina
|
|
MIN_USABLE_REMNANT = BeamData.MINRAW_S, -- Sotto questa misura lo scarto è considerato "sfrido"
|
|
MAX_REMNANT_PERFECT_FIT = 20,
|
|
BLADE_THICKNESS = 5.4, -- TODO questo deve arrivare da interfaccia o da automatismo!!!!!
|
|
MIN_FILLER_LIMIT = ( BeamData.MINRAW_S + BeamData.MINRAW_L) / 2
|
|
}
|
|
|
|
----------------------------------------------------------------------------------------------------------
|
|
-- variabili per tutto il modulo
|
|
local dMaxJobLength = 0
|
|
local dMaxFillerLength = 0
|
|
local dGlobalProgress = 0
|
|
local t_insert = table.insert -- si mette via il riferimento locale per evitare continui lookup
|
|
local JobPool = {}
|
|
|
|
----------------------------------------------------------------------------------------------------------
|
|
-- inventario grezzi
|
|
local RawInventory = {
|
|
Stock = {},
|
|
ActiveBeams = {}
|
|
}
|
|
|
|
function RawInventory:GetNewBeam( dLength)
|
|
local NewBeam = {
|
|
dTotalLength = dLength,
|
|
dResidualLength = dLength - ( NEST.STARTOFFSET or 0),
|
|
LastOffsetX = { 0, 0, 0, 0},
|
|
LastVtN = Vector3d( 1, 0, 0),
|
|
vtNXabs = 1,
|
|
dLastHeadRecess = 0,
|
|
NestedParts = {}
|
|
}
|
|
|
|
return NewBeam
|
|
end
|
|
|
|
function RawInventory:BuildStock()
|
|
if #LEN ~= #QTY then
|
|
error( 'NestProcess: invalid stock data')
|
|
end
|
|
|
|
for i = 1, #LEN do
|
|
self.Stock[#self.Stock + 1] = {
|
|
dLength = LEN[i],
|
|
nCount = QTY[i]
|
|
}
|
|
end
|
|
|
|
return RawInventory
|
|
end
|
|
|
|
-- aggiunge una nuova barra attiva rimuovendo la corrispondente dalla lista stock disponibili
|
|
function RawInventory:AddActiveBeam( nStockIndex)
|
|
local CurrentStock = self.Stock[nStockIndex]
|
|
-- se barra disponibile posso aggiungerla a quelle attive
|
|
if CurrentStock and CurrentStock.nCount > 0 then
|
|
|
|
-- update quantità a stock
|
|
CurrentStock.nCount = CurrentStock.nCount - 1
|
|
|
|
-- aggiungo una nuova barra attiva
|
|
local NewBeam = self:GetNewBeam( CurrentStock.dLength)
|
|
|
|
t_insert( self.ActiveBeams, NewBeam)
|
|
return NewBeam
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-- auditor della bontà del nesting calcolato. Scrive nel log i risultati.
|
|
function RawInventory:PrintDiagnosticReport()
|
|
local nBeamsUsed = #self.ActiveBeams
|
|
local dTotalStockLength = 0
|
|
local dTotalPartLength = 0
|
|
local dTotalScrap = 0
|
|
local dTotalUsableRemnants = 0
|
|
|
|
-- si contano i pezzi davvero nestati
|
|
local nActualNestedParts = 0
|
|
for i = 1, #JobPool do
|
|
if JobPool[i].bNested then
|
|
nActualNestedParts = nActualNestedParts + 1
|
|
end
|
|
end
|
|
local nUnnestedParts = #JobPool - nActualNestedParts
|
|
|
|
for i = 1, nBeamsUsed do
|
|
local Beam = self.ActiveBeams[i]
|
|
dTotalStockLength = dTotalStockLength + Beam.dTotalLength
|
|
|
|
-- Classificazione del residuo rimasto in fondo alla barra
|
|
if Beam.dResidualLength >= CONFIG.MIN_USABLE_REMNANT then
|
|
dTotalUsableRemnants = dTotalUsableRemnants + Beam.dResidualLength
|
|
else
|
|
dTotalScrap = dTotalScrap + Beam.dResidualLength
|
|
end
|
|
|
|
-- Somma delle lunghezze dei pezzi reali inseriti
|
|
for j = 1, #Beam.NestedParts do
|
|
local Part = Beam.NestedParts[j]
|
|
dTotalPartLength = dTotalPartLength + Part.dLength
|
|
end
|
|
end
|
|
|
|
-- Calcolo efficienze percentuali con protezione per divisione per zero
|
|
local dGrossEfficiency = 0
|
|
if dTotalStockLength > 0 then
|
|
dGrossEfficiency = (dTotalPartLength / dTotalStockLength) * 100
|
|
end
|
|
|
|
local dNetEfficiency = 0
|
|
local dNetDenominator = dTotalStockLength - dTotalUsableRemnants
|
|
if dNetDenominator > 0 then
|
|
dNetEfficiency = (dTotalPartLength / dNetDenominator) * 100
|
|
end
|
|
|
|
-- Stampa tramite il sistema di logging proprietario EgtLog
|
|
EgtOutLog("==========================================================")
|
|
EgtOutLog(" EGALWARE NESTING ENGINE DIAGNOSTIC ")
|
|
EgtOutLog("==========================================================")
|
|
EgtOutLog(string.format("Total Parts Demanded : %d", #JobPool))
|
|
EgtOutLog(string.format("Parts Successfully Nested: %d", nActualNestedParts))
|
|
|
|
-- Se ci sono pezzi rimasti fuori, spariamo un alert visibile nel log
|
|
if nUnnestedParts > 0 then
|
|
EgtOutLog(string.format("WARNING: Unnested Parts : %d <<<<<< MATERIAL SHORTAGE!", nUnnestedParts))
|
|
else
|
|
EgtOutLog("All Parts Nested : YES (Commessa completata)")
|
|
end
|
|
|
|
EgtOutLog(string.format("Total Bars Used : %d", nBeamsUsed))
|
|
EgtOutLog(string.format("Total Material Cut : %.2f mm", dTotalPartLength))
|
|
EgtOutLog("----------------------------------------------------------")
|
|
EgtOutLog(string.format("Gross Yield (Rendimento): %.2f%%", dGrossEfficiency))
|
|
EgtOutLog(string.format("Net Yield (Riutilizzato): %.2f%%", dNetEfficiency))
|
|
EgtOutLog("----------------------------------------------------------")
|
|
EgtOutLog(string.format("Total Usable Remnants : %.2f mm (Safe for Clamps)", dTotalUsableRemnants))
|
|
EgtOutLog(string.format("Total Pure Scrap (Sfrido): %.2f mm (Trash Zone)", dTotalScrap))
|
|
EgtOutLog("==========================================================")
|
|
end
|
|
|
|
----------------------------------------------------------------------------------------------------------
|
|
-- PartTemplates (informazioni geometriche pezzi univoci)
|
|
-- JobPool (lista di tutti i singoli pezzi, multipli compresi, da nestare)
|
|
local PartTemplates = {}
|
|
|
|
function PartTemplates:GetInfoFromPart( id, sStateWithSide)
|
|
local OffsetX = {}
|
|
local vtN
|
|
|
|
local sInfo = EgtGetInfo( id, sStateWithSide)
|
|
if not sInfo then
|
|
return
|
|
end
|
|
|
|
local Info = EgtSplitString( sInfo, ';')
|
|
OffsetX = EgtSplitString( Info[1], ',')
|
|
vtN = VectorFromString( Info[2])
|
|
|
|
for i = 1, #OffsetX do
|
|
OffsetX[i] = tonumber( OffsetX[i]) or 0
|
|
end
|
|
|
|
return OffsetX, vtN
|
|
end
|
|
|
|
function PartTemplates:AddPart( id)
|
|
self[id] = {}
|
|
self[id].dLength = EgtGetInfo( id, 'L', 'd')
|
|
self[id].States = {}
|
|
self[id].dMaxGlobalTailRecess = 0
|
|
|
|
-- si tiene via la lunghezza del pezzo massimo
|
|
if self[id].dLength > dMaxJobLength + 10 * GEO.EPS_SMALL then
|
|
dMaxJobLength = self[id].dLength
|
|
end
|
|
|
|
local States = { '1000', '0010', '1000_INV', '0010_INV' }
|
|
|
|
for _, sState in ipairs(States) do
|
|
local OffsetXHead, vtNHead = self:GetInfoFromPart( id, 'ALT' .. sState .. '_H')
|
|
local OffsetXTail, vtNTail = self:GetInfoFromPart( id, 'ALT' .. sState .. '_T')
|
|
|
|
if OffsetXHead or OffsetXTail then
|
|
|
|
if not OffsetXHead then
|
|
OffsetXHead = { 0, 0, 0, 0}
|
|
vtNHead = Vector3d( 1, 0, 0)
|
|
end
|
|
if not OffsetXTail then
|
|
OffsetXTail = { 0, 0, 0, 0}
|
|
vtNTail = Vector3d( -1, 0, 0)
|
|
end
|
|
|
|
self[id].States[sState] = {}
|
|
local State = self[id].States[sState]
|
|
|
|
State.Head = {
|
|
OffsetX = OffsetXHead,
|
|
vtN = vtNHead,
|
|
vtNXabs = abs( vtNHead:getX())
|
|
}
|
|
State.Tail = {
|
|
OffsetX = OffsetXTail,
|
|
vtN = vtNTail,
|
|
vtNXabs = abs( vtNTail:getX())
|
|
}
|
|
|
|
-- overlap massimi in testa e in coda per questo pezzo
|
|
local dMaxHeadRecess = 0
|
|
for i = 1, 4 do
|
|
if State.Head.OffsetX[i] > dMaxHeadRecess + 10 * GEO.EPS_SMALL then
|
|
dMaxHeadRecess = State.Head.OffsetX[i]
|
|
end
|
|
end
|
|
State.dMaxHeadRecess = dMaxHeadRecess
|
|
|
|
local dMaxTailRecess = 0
|
|
for i = 1, 4 do
|
|
if abs( State.Tail.OffsetX[i]) > dMaxTailRecess + 10 * GEO.EPS_SMALL then
|
|
dMaxTailRecess = abs( State.Tail.OffsetX[i])
|
|
end
|
|
end
|
|
State.dMaxTailRecess = dMaxTailRecess
|
|
if dMaxTailRecess > self[id].dMaxGlobalTailRecess + 10 * GEO.EPS_SMALL then
|
|
self[id].dMaxGlobalTailRecess = dMaxTailRecess
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ordinamento JobPool per lunghezza decrescente dei pezzi
|
|
function JobPool:Sort()
|
|
local function SortByLengthDescending( Part1, Part2)
|
|
local dLength1 = PartTemplates[Part1.id].dLength
|
|
local dLength2 = PartTemplates[Part2.id].dLength
|
|
|
|
if dLength1 > dLength2 + 10 * GEO.EPS_SMALL then
|
|
return true
|
|
elseif dLength2 > dLength1 + 10 * GEO.EPS_SMALL then
|
|
return false
|
|
end
|
|
|
|
-- tie breaker
|
|
return Part1.id < Part2.id
|
|
end
|
|
|
|
table.sort( self, SortByLengthDescending)
|
|
end
|
|
|
|
-- creazione combinata (si cicla una sola volta) di entrambe le tabelle
|
|
local function BuildPartTemplatesAndJobPool()
|
|
for id, nCount in pairs( PART) do
|
|
PartTemplates:AddPart( id)
|
|
for _ = 1, nCount do
|
|
t_insert( JobPool, { id = id, bNested = false})
|
|
end
|
|
end
|
|
|
|
return PartTemplates, JobPool
|
|
end
|
|
|
|
----------------------------------------------------------------------------------------------------------
|
|
-- calcolo singola Move, ossia combinazione barra-pezzo-stato, con relativo punteggio
|
|
local function CalculateMove( Beam, dPartLength, sState, State)
|
|
local Move = {}
|
|
|
|
-- calcolo overlap pezzi (riduzione di lunghezza occupata)
|
|
local dSafeOverlap = GEO.INFINITO
|
|
for i = 1, 4 do
|
|
local dCornerDistance = Beam.LastOffsetX[i] - State.Tail.OffsetX[i]
|
|
if dCornerDistance < dSafeOverlap then
|
|
dSafeOverlap = dCornerDistance
|
|
end
|
|
end
|
|
|
|
-- il massimo overlap va ridotto dello spessore lama
|
|
local dProjectedBlade = CONFIG.BLADE_THICKNESS / min( Beam.vtNXabs, State.Tail.vtNXabs)
|
|
dSafeOverlap = dSafeOverlap - dProjectedBlade
|
|
|
|
-- lunghezza barra rimasta (se negativo non ci sta)
|
|
local dFutureResidualLength = Beam.dResidualLength + dSafeOverlap - dPartLength
|
|
if dFutureResidualLength < -10 * GEO.EPS_SMALL then
|
|
return nil
|
|
end
|
|
|
|
-- Calcolo punteggio
|
|
-- All'inizio (Ratio=1) si dà vantaggio ai pezzi più lunghi. Alla fine (Ratio=0) si dà vantaggio ai best fit.
|
|
local dEfficiency
|
|
-- barra nuova, si valuta l'efficienza prospettica
|
|
if #Beam.NestedParts == 0 then
|
|
-- È una barra vergine dallo stock! Valutiamo la sua efficienza PROSPETTICA
|
|
-- Quanti pezzi di questa lunghezza ci starebbero al massimo?
|
|
local nMaxPieces = math.floor(Beam.dResidualLength / dPartLength)
|
|
if nMaxPieces == 0 then nMaxPieces = 1 end
|
|
|
|
local dUltimateUsed = nMaxPieces * dPartLength
|
|
dEfficiency = dUltimateUsed / Beam.dTotalLength
|
|
-- barra avviata: efficienza reale
|
|
else
|
|
dEfficiency = dPartLength / ( dPartLength - dSafeOverlap)
|
|
end
|
|
local dNormalizedLength = dPartLength / dMaxJobLength
|
|
local dLengthScore = dNormalizedLength * ( 1 - dGlobalProgress) * 1000
|
|
local dEfficiencyScore = dEfficiency * dGlobalProgress * 1000
|
|
|
|
local dScore = dLengthScore + dEfficiencyScore
|
|
|
|
-- Bonus Perfect Fit
|
|
if dFutureResidualLength < CONFIG.MAX_REMNANT_PERFECT_FIT then
|
|
dScore = dScore + CONFIG.BONUS_PERFECT_FIT
|
|
|
|
-- Penalità Sliver (si evitano sfridi non riutilizzabili)
|
|
elseif dFutureResidualLength < CONFIG.MIN_USABLE_REMNANT then
|
|
dScore = dScore - CONFIG.PENALTY_TOO_SHORT
|
|
end
|
|
|
|
-- Bonus Shared Cut: se le normali sono opposte, si risparmia un taglio/posizionamento
|
|
if AreOppositeVectorApprox( Beam.LastVtN, State.Tail.vtN) then
|
|
dScore = dScore + CONFIG.BONUS_SHARED_CUT
|
|
end
|
|
|
|
-- Protezione finale pezzi corti: se siamo alla fine e il pezzo è corto, penalizziamo se non è un fit quasi perfetto
|
|
if ( dGlobalProgress >= 0.8) and ( dPartLength < dMaxFillerLength) and ( dFutureResidualLength > CONFIG.MAX_REMNANT_PERFECT_FIT) then
|
|
dScore = dScore - CONFIG.PENALTY_BAD_FIT_END_PHASE
|
|
end
|
|
|
|
Move = {
|
|
sState = sState,
|
|
dScore = dScore,
|
|
dSafeOverlap = dSafeOverlap,
|
|
dFutureResidualLength = dFutureResidualLength
|
|
}
|
|
return Move
|
|
end
|
|
|
|
----------------------------------------------------------------------------------------------------------
|
|
-- trova i migliori pezzi da inserire nella trave (N pezzi più lunghi e 2 pezzi di lunghezza più adeguata al restante della barra) e i migliori stati in cui metterli
|
|
local function FindBestPartForBeam( Beam)
|
|
local nLongestParts = 0
|
|
local Candidates = {}
|
|
local JobsAlreadyInCandidates = {}
|
|
|
|
local BestFitJob1 = { Job = nil, dGap = GEO.INFINITO }
|
|
local BestFitJob2 = { Job = nil, dGap = GEO.INFINITO }
|
|
|
|
-- 1 Scelta candidati --
|
|
for i = 1, #JobPool do
|
|
local Job = JobPool[i]
|
|
if not Job.bNested then
|
|
local PartTemplate = PartTemplates[Job.id]
|
|
|
|
local dGap = Beam.dResidualLength + Beam.dLastHeadRecess + PartTemplate.dMaxGlobalTailRecess - PartTemplate.dLength
|
|
|
|
if dGap > - 10 * GEO.EPS_SMALL then
|
|
|
|
-- JobPool è già ordinata: i primi N pezzi che entrano sono i più lunghi
|
|
if nLongestParts < CONFIG.NUM_LONGEST_CANDIDATES then
|
|
t_insert( Candidates, Job)
|
|
JobsAlreadyInCandidates[Job] = true
|
|
nLongestParts = nLongestParts + 1
|
|
end
|
|
|
|
-- si cercano i due pezzi con il best fit nella barra restante
|
|
if dGap < BestFitJob1.dGap then
|
|
-- il bestfit1 è il nuovo bestfit 2
|
|
BestFitJob2.Job = BestFitJob1.Job
|
|
BestFitJob2.dGap = BestFitJob1.dGap
|
|
-- la job corrente è la nuova bestfit1
|
|
BestFitJob1.dGap = dGap
|
|
BestFitJob1.Job = Job
|
|
elseif dGap < BestFitJob2.dGap then
|
|
BestFitJob2.Job = Job
|
|
BestFitJob2.dGap = dGap
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- i pezzi bestfit si aggiungono solo se non corrispondono a pezzi già inseriti
|
|
if BestFitJob1.Job and not JobsAlreadyInCandidates[BestFitJob1.Job] then
|
|
table.insert( Candidates, BestFitJob1.Job)
|
|
JobsAlreadyInCandidates[BestFitJob1.Job] = true
|
|
end
|
|
|
|
if BestFitJob2.Job and not JobsAlreadyInCandidates[BestFitJob2.Job] then
|
|
table.insert( Candidates, BestFitJob2.Job)
|
|
JobsAlreadyInCandidates[BestFitJob2.Job] = true
|
|
end
|
|
|
|
-- 2 Scelta miglior candidato --
|
|
local dHighestScore = -GEO.INFINITO
|
|
local BestMove
|
|
for i = 1, #Candidates do
|
|
local Candidate = Candidates[i]
|
|
local Template = PartTemplates[Candidate.id]
|
|
local dPartLength = Template.dLength
|
|
local dHighestCandidateScore = -GEO.INFINITO
|
|
local BestCandidateMove
|
|
|
|
-- si trova la Move migliore del singolo candidato (a CalculateMove si passano gli argomenti precalcolati per evitare di rallentare il calcolo)
|
|
for sState, State in pairs( Template.States) do
|
|
local Move = CalculateMove( Beam, dPartLength, sState, State)
|
|
if Move and Move.dScore > dHighestCandidateScore + 10 * GEO.EPS_SMALL then
|
|
BestCandidateMove = Move
|
|
dHighestCandidateScore = Move.dScore
|
|
BestCandidateMove.Job = Candidate
|
|
end
|
|
end
|
|
|
|
-- si trova la Move migliore in assoluto
|
|
if dHighestCandidateScore > dHighestScore + 10 * GEO.EPS_SMALL then
|
|
BestMove = BestCandidateMove
|
|
dHighestScore = dHighestCandidateScore
|
|
end
|
|
end
|
|
|
|
return BestMove
|
|
end
|
|
|
|
----------------------------------------------------------------------------------------------------------
|
|
-- Esegue la mossa scelta: aggiorna lo stato della trave e segna il pezzo come nestato
|
|
local function CommitBestMove( BestMove)
|
|
local Beam
|
|
|
|
-- recupero o creazione della barra
|
|
if BestMove.nActiveBeamIndex then
|
|
Beam = RawInventory.ActiveBeams[BestMove.nActiveBeamIndex]
|
|
elseif BestMove.nStockIndex then
|
|
Beam = RawInventory:AddActiveBeam( BestMove.nStockIndex)
|
|
end
|
|
|
|
if not Beam then return end
|
|
|
|
-- recupero dati pezzo e stato
|
|
local Job = BestMove.Job
|
|
local Template = PartTemplates[Job.id]
|
|
local State = Template.States[BestMove.sState]
|
|
|
|
-- update geometria barra
|
|
-- la nuova faccia della barra è ora la testa (Head) del pezzo appena inserito
|
|
Beam.dResidualLength = BestMove.dFutureResidualLength
|
|
Beam.LastOffsetX = State.Head.OffsetX
|
|
Beam.LastVtN = State.Head.vtN
|
|
Beam.vtNXabs = abs( State.Head.vtN:getX())
|
|
Beam.dLastHeadRecess = State.dMaxHeadRecess
|
|
|
|
-- registrazione pezzo
|
|
t_insert( Beam.NestedParts, {
|
|
id = Job.id,
|
|
sState = BestMove.sState,
|
|
dSafeOverlap = BestMove.dSafeOverlap,
|
|
dLength = Template.dLength,
|
|
dPosX = BestMove.dFutureResidualLength
|
|
})
|
|
|
|
-- chiusura job
|
|
Job.bNested = true
|
|
end
|
|
|
|
----------------------------------------------------------------------------------------------------------
|
|
-- script principale
|
|
|
|
-- preparazione tabelle lista grezzi (RawInventory), lista pezzi univoci (PartTemplates) e lista singoli pezzi da nestare (JobPool)
|
|
RawInventory:BuildStock()
|
|
BuildPartTemplatesAndJobPool()
|
|
JobPool:Sort()
|
|
|
|
-- calcolo lunghezza massima pezzi "filler"
|
|
local nTotalParts = #JobPool
|
|
local nFillerIndex = floor( nTotalParts * 0.8) + 1
|
|
if nFillerIndex > nTotalParts then nFillerIndex = nTotalParts end
|
|
dMaxFillerLength = PartTemplates[JobPool[nFillerIndex].id].dLength
|
|
dMaxFillerLength = EgtClamp( dMaxFillerLength, CONFIG.MIN_FILLER_LIMIT, BeamData.LEN_SHORT_PART)
|
|
|
|
-- loop principale: scorre le barre, sia già attive che a stock, e le riempie nel modo migliore possibile
|
|
-- per ogni giro sceglie la soluzione con punteggio più alto
|
|
local nDoneParts = 0
|
|
local VirtualBeam = RawInventory:GetNewBeam( 0)
|
|
while true do
|
|
local BestMove
|
|
local dHighestScore = -GEO.INFINITO
|
|
|
|
-- progresso calcolo
|
|
if nTotalParts > 0 then
|
|
dGlobalProgress = nDoneParts / nTotalParts
|
|
-- se non ci sono pezzi il calcolo è già finito
|
|
else
|
|
dGlobalProgress = 1
|
|
end
|
|
|
|
-- 1 Si provano le barre già attive
|
|
for i = 1, #RawInventory.ActiveBeams do
|
|
local CurrentMove = FindBestPartForBeam( RawInventory.ActiveBeams[i])
|
|
if CurrentMove and CurrentMove.dScore > dHighestScore then
|
|
dHighestScore = CurrentMove.dScore
|
|
BestMove = CurrentMove
|
|
BestMove.nActiveBeamIndex = i
|
|
end
|
|
end
|
|
|
|
-- 2 Si provano le barre ancora a stock SOLO SE NON TROVATA SOLUZIONE CON BARRE ATTIVE
|
|
-- VirtualBeam si resetta invece di crearne una nuova per velocizzare il calcolo
|
|
if not BestMove then
|
|
for i = 1, #RawInventory.Stock do
|
|
if RawInventory.Stock[i].nCount > 0 then
|
|
VirtualBeam.dTotalLength = RawInventory.Stock[i].dLength
|
|
VirtualBeam.dResidualLength = RawInventory.Stock[i].dLength - ( NEST.STARTOFFSET or 0)
|
|
VirtualBeam.LastOffsetX = { 0, 0, 0, 0}
|
|
VirtualBeam.LastVtN = Vector3d( 1, 0, 0)
|
|
VirtualBeam.vtNXabs = 1
|
|
VirtualBeam.dLastHeadRecess = 0
|
|
VirtualBeam.NestedParts = {}
|
|
local CurrentMove = FindBestPartForBeam( VirtualBeam)
|
|
if CurrentMove and CurrentMove.dScore > dHighestScore then
|
|
dHighestScore = CurrentMove.dScore
|
|
BestMove = CurrentMove
|
|
BestMove.nStockIndex = i
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 3 Se BestMove trovata si aggiornano lista pezzi e barre
|
|
if BestMove then
|
|
CommitBestMove( BestMove)
|
|
nDoneParts = nDoneParts + 1
|
|
-- se non c'è più niente di compatibile si esce
|
|
else
|
|
break
|
|
end
|
|
end
|
|
|
|
-- creazione MachGroup e Duplo
|
|
for i = 1, #RawInventory.ActiveBeams do
|
|
local dStartOffset = NEST.STARTOFFSET or 0
|
|
local Beam = RawInventory.ActiveBeams[i]
|
|
|
|
-- creazione MachGroup
|
|
local sMachGroup = EgtGetMachGroupNewName( i)
|
|
local idMachGroup = EgtAddMachGroup( sMachGroup)
|
|
|
|
-- assegnazione info
|
|
EgtSetInfo( idMachGroup, "BARLEN", Beam.dTotalLength)
|
|
EgtSetInfo( idMachGroup, "MATERIAL", NEST.MATERIAL)
|
|
EgtSetInfo( idMachGroup, "AUTONEST", 1)
|
|
EgtSetInfo( idMachGroup, "PRODID", NEST.PRODID)
|
|
EgtSetInfo( idMachGroup, "PATTID", idMachGroup)
|
|
|
|
-- Spostamento pezzi verso la testa della barra e aggiunta duplo
|
|
local nIndex = 1
|
|
for j = #Beam.NestedParts, 1, -1 do
|
|
local Part = Beam.NestedParts[j]
|
|
local nInitialPosition = EgtGetInfo( Part.id, 'INITIALPOSITION', 'i')
|
|
|
|
-- spostamento verso la testa della barra
|
|
local dPosX = Part.dPosX - Beam.dResidualLength + dStartOffset
|
|
|
|
-- copia del pezzo (aggiunta duplo)
|
|
local idDuplo = EgtDuploNew( Part.id)
|
|
local Duplo = { id = idDuplo, idRaw = EgtGetRawPartFromPart( idDuplo)}
|
|
|
|
-- eventuale inversione
|
|
if EgtEndsWith( Part.sState, 'INV') then
|
|
BeamLib.InvertRawPart( Duplo, 2)
|
|
end
|
|
|
|
-- eventuale rotazione
|
|
if ( EgtStartsWith( Part.sState, '0010') and nInitialPosition == 1)
|
|
or ( EgtStartsWith( Part.sState, '1000') and nInitialPosition == 3) then
|
|
BeamLib.RotateRawPart( Duplo, 2)
|
|
end
|
|
|
|
-- assegnazione info
|
|
EgtSetInfo( idMachGroup, "PART" .. nIndex, idDuplo .. "," .. dPosX)
|
|
|
|
nIndex = nIndex + 1
|
|
end
|
|
end
|
|
|
|
-- creazione grezzi per ogni MachGroup (si chiama la BatchProcess in modalità creazione barra)
|
|
local nRawCounter = 0
|
|
local nTotalRaws = #RawInventory.ActiveBeams
|
|
|
|
-- variabili per BatchProcess
|
|
_G.BEAM = {}
|
|
BEAM.FILE = NEST.FILE
|
|
BEAM.MACHINE = NEST.MACHINE
|
|
BEAM.FLAG = 6 -- CREATE_PANEL
|
|
BEAM.BASEDIR = NEST.BASEDIR
|
|
|
|
-- per ogni MachGroup si chiama la BatchProcess pe creare il grezzo
|
|
local idMachGroup = EgtGetFirstMachGroup()
|
|
while idMachGroup do
|
|
local nNextMachGroup = EgtGetNextMachGroup( idMachGroup)
|
|
EgtSetCurrMachGroup( idMachGroup)
|
|
if EgtGetInfo( idMachGroup, "AUTONEST",'i') == 1 then
|
|
EgtRemoveInfo( idMachGroup, "AUTONEST")
|
|
EgtSetInfo( idMachGroup, "UPDATEUI", 1)
|
|
local bOk, sErr = pcall( dofile, BEAM.BASEDIR .. "\\BatchProcessNew.lua")
|
|
if not bOk then
|
|
EgtOutLog( 'Error in BatchProcessNew.lua call (' .. ( sErr or '') ..')')
|
|
end
|
|
nRawCounter = nRawCounter + 1
|
|
-- aggiorno interfaccia
|
|
EgtProcessEvents( 200 + ( nRawCounter / nTotalRaws * 100), 0)
|
|
end
|
|
idMachGroup = nNextMachGroup
|
|
end
|
|
|
|
EgtResetCurrMachGroup()
|
|
NEST.ERR = 0
|
|
|
|
-- calcolo bontà soluzione
|
|
RawInventory:PrintDiagnosticReport() |