From e623f2d78948218addaf2052f9b684e2dffb07b0 Mon Sep 17 00:00:00 2001 From: "andrea.villa" Date: Thu, 19 Feb 2026 09:33:25 +0100 Subject: [PATCH] =?UTF-8?q?Nuova=20versione=20BatchProcess:=20-=20gestione?= =?UTF-8?q?=20WIN.FLAG=20(=20per=20ora=20valori=20ammessi=200,=201=20e=202?= =?UTF-8?q?)=20-=20Cancellato=20funzioni=20non=20pi=C3=B9=20utilizzate=20-?= =?UTF-8?q?=20Aggiunta=20nuove=20funzioni=20scrittura=20file=20di=20log=20?= =?UTF-8?q?-=20Aggiunta=20stima=20tempi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CAMAuto/BatchProcessWin.lua | 352 ++++++++++++++++++------------------ CAMAuto/LuaLibs/WinExec.lua | 18 +- 2 files changed, 192 insertions(+), 178 deletions(-) diff --git a/CAMAuto/BatchProcessWin.lua b/CAMAuto/BatchProcessWin.lua index 92abeb5..e9ae11b 100644 --- a/CAMAuto/BatchProcessWin.lua +++ b/CAMAuto/BatchProcessWin.lua @@ -5,35 +5,54 @@ require( 'EgtBase') _ENV = EgtProtectGlobal() EgtEnableDebug( false) +-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- TODO DA CANCELLARE!!!! quando verrà passato automaticamente da programma --local WIN = {} --WIN.BASEDIR = 'C:\\EgtData\\Window\\CAMAuto' ---WIN.FILE = 'C:\\Temp\\Window.nge' +--WIN.FILE = 'C:\\Temp\\TestSerramenti\\4.bwe' +--WIN.MACHINE = 'Saomad-Just3500' +--WIN.FLAG = 2 +-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- Imposto direttorio libreria specializzata per serramenti EgtAddToPackagePath( WIN.BASEDIR .. '\\LuaLibs\\?.lua') -- Imposto direttorio strategie. N.B. Le strategie dovranno essere caricate con il nome del direttorio padre EgtAddToPackagePath( WIN.BASEDIR .. '\\Strategies\\?.lua') -local sTxtLogFile = EgtChangePathExtension( WIN.FILE, '.txt') +-- carico il progetto EgtOpenFile( WIN.FILE) --- Verifico che la macchina corrente sia abilitata per la lavorazione delle Travi local nMachGroupId = EgtGetFirstMachGroup() if not nMachGroupId then EgtOutLog( 'Errore! Nessun gruppo di lavorazione presente!') + return end EgtSetCurrMachGroup( nMachGroupId) + +-- controlli preliminari local sMachDir = EgtGetCurrMachineDir() +local sMachine = WIN.MACHINE EgtResetCurrMachGroup() if not sMachDir then - EgtOutBox( 'Errore nel caricamento della macchina corrente', 'Lavora Serramenti', 'ERROR') + WIN.ERR = 11 + WIN.MSG = 'Errore nel caricamento della macchina corrente' + WriteErrToLogFile( WIN.ERR, WIN.MSG) return end if not EgtExistsFile( sMachDir .. '\\Window\\WinData.lua') then - EgtOutBox( 'La macchina corrente non è configurata per lavorare serramenti', 'Lavora Serramenti', 'ERROR') + WIN.ERR = 11 + WIN.MSG = 'La macchina corrente non è configurata per lavorare serramenti' + WriteErrToLogFile( WIN.ERR, WIN.MSG) return end +if not EgtSetCurrMachine( sMachine) then + WIN.ERR = 11 + WIN.MSG = 'Error selecting machine : ' .. sMachine + WriteErrToLogFile( WIN.ERR, WIN.MSG) + return +end + +local sTxtLogFile = EgtChangePathExtension( WIN.FILE, '.txt') -- Elimino direttori altre macchine e imposto direttorio macchina corrente per ricerca librerie EgtRemoveBaseMachineDirFromPackagePath() @@ -50,6 +69,8 @@ _G.package.loaded.FeatureData = nil _G.package.loaded.MachiningLib = nil local WinExec = require( 'WinExec') +local WinLib = require( 'WinLib') +local JSON = require( 'JSON') -- Carico i dati globali local WinData = require( 'WinData') @@ -57,9 +78,6 @@ local WinData = require( 'WinData') -- Variabili globali PARTS = {} -- tabella contenente tutte le informazioni di ogni pezzo --- Colore del grezzo -local ColA = Color3d( 255, 165, 0, 30) - ------------------------------------------------------------------------------------------------------------- local function GetDataConfig() -- recupero utensili dal magazzino @@ -69,6 +87,25 @@ local function GetDataConfig() return true end +------------------------------------------------------------------------------------------------------------- +-- scrittura JSON risultati +-- TODO da migliorare/completare +local function WriteResultToJson( RESULT) + local sData = JSON:encode_pretty( RESULT) + + -- Salvataggio e visualizzazione tabella + local sOutFile = EgtChangePathExtension( WIN.FILE, '.json') + local DestFh = io.open( sOutFile, 'w+') + if not DestFh then + EgtOutBox( 'Error opening ' .. sOutFile, 'TestJson', 'ERROR') + return false + end + DestFh:write( sData) + DestFh:close() + + return true +end + ------------------------------------------------------------------------------------------------------------- local function WriteErrToLogFile( nErr, sMsg, nRot, idCut, idTask) local hFile = io.open( sTxtLogFile, 'a') @@ -80,20 +117,23 @@ local function WriteErrToLogFile( nErr, sMsg, nRot, idCut, idTask) hFile:close() end +local function WriteTimeToLogFile( dTime) + local hFile = io.open( sTxtLogFile, 'a') + hFile:write( 'TIME=' .. EgtNumToString( dTime) .. '\n') + hFile:close() +end + ------------------------------------------------------------------------------------------------------------- -- *** Recupero i pezzi da processare *** ------------------------------------------------------------------------------------------------------------- local function MyProcessInputData() - -- Recupero le travi selezionate - local nId = EgtGetFirstInGroup( GDB_ID.ROOT) + + local nId = EgtGetFirstPart() while nId do - -- Per capire se è un pezzo controllo una nota, servirebbe info dedicata, oppure essere sicuri che vengano passati solo pezzi - if EgtGetInfo( nId, 'OFFY_1', 'd') then + -- TODO caricare il file di costanti + -- si processano solo BOTTOMRAIL (WIN_PART_TYPES.BOTTOMRAIL = 2) o STANDARD (WIN_PART_TYPES.STD = 3) + if EgtGetInfo( nId, 'PART_TYPE', 'd') == 2 or EgtGetInfo( nId, 'PART_TYPE', 'd') == 3 then table.insert( PARTS, { id = nId, sName = ( EgtGetName( nId) or ( 'Id=' .. tonumber( nId)))}) - -- TODO RIMUOVERE! Bisogna sempre prendere tuttti i pezzi all'interno del file. Devono arrivare solo i pezzi da mettere nella macchinata - if #PARTS == 2 then - break - end end nId = EgtGetNext( nId) end @@ -139,30 +179,6 @@ local function GetDispOffsetFromNotes( nPieceIndex) return bAllOffsetsAreOk end -------------------------------------------------------------------------------------------------------------- -local function GetDispOffsetFromInput( nPieceIndex) - -- assegno alle stringhe i valori letti, in modo che vengano proposti quelli nel dialogo - local sOffYPh1 = EgtIf( PARTS[nPieceIndex].DispOffsets.Phase1.dOffsetY, tostring( PARTS[nPieceIndex].DispOffsets.Phase1.dOffsetY), tostring( PARTS[nPieceIndex].dPartWidth / 2)) - local sOffZPh1 = EgtIf( PARTS[nPieceIndex].DispOffsets.Phase1.dOffsetZ, tostring( PARTS[nPieceIndex].DispOffsets.Phase1.dOffsetZ), '0') - local sOffYPh2 = EgtIf( PARTS[nPieceIndex].DispOffsets.Phase2.dOffsetY, tostring( PARTS[nPieceIndex].DispOffsets.Phase2.dOffsetY), tostring( PARTS[nPieceIndex].dPartWidth / 2)) - local sOffZPh2 = EgtIf( PARTS[nPieceIndex].DispOffsets.Phase2.dOffsetZ, tostring( PARTS[nPieceIndex].DispOffsets.Phase2.dOffsetZ), '0') - - local vInp = EgtDialogBox( 'Dati di disposizione pezzo: ' .. PARTS[nPieceIndex].sName, - {'Sporgenza laterale FASE1', sOffYPh1}, {'Posizione Z FASE1', sOffZPh1}, - {'Sporgenza laterale FASE2', sOffYPh2}, {'Posizione Z FASE2', sOffZPh2}) - if not vInp or #vInp == 0 then - return false - end - - -- salvo input nei valori che utilizzerò dopo - PARTS[nPieceIndex].DispOffsets.Phase1.dOffsetY = tonumber( vInp[1]) - PARTS[nPieceIndex].DispOffsets.Phase1.dOffsetZ = tonumber( vInp[2]) - PARTS[nPieceIndex].DispOffsets.Phase2.dOffsetY = tonumber( vInp[3]) - PARTS[nPieceIndex].DispOffsets.Phase2.dOffsetZ = tonumber( vInp[4]) - - return true -end - --------------------------------------------------------------------- -- Crea il grezzo che verrà messo in macchina --------------------------------------------------------------------- @@ -200,83 +216,18 @@ local function AddOverMaterialToRaw( PARTS) end end ---------------------------------------------------------------------- --- Crea il grezzo che verrà messo in macchina ---------------------------------------------------------------------- -local function CreateRaws( PARTS) - for i = 1, #PARTS do - -- si crea un grezzo "finto" (un cubo da 100mm) nell'origine del pezzo, verrà poi dimensionato uan volta adeguato con i vari sovramateriali - PARTS[i].idRaw = EgtAddRawPart( PARTS[i].frame:getOrigin(), 100, 100, 100, ColA) - EgtAddPartToRawPart( PARTS[i].id, ORIG(), PARTS[i].idRaw) - end -end - ---------------------------------------------------------------------- --- Allinea i pezzi in base a come devono essere disposti sulla tavola ---------------------------------------------------------------------- --- TODO in questa fase bisogna già sapere qual è la prima fase e posizionare il pezzo di conseguenza --- TODO (bassa priorità) adesso allinea sempre in X, ma bisognerebbe farlo in base ad un parametro in WINDATA che dice come si dispongono in macchina -local function AlignRawsToTable( PARTS) - for i = 1, #PARTS do - -- allineo il pezzo all'interno del grezzo - local dRotX, dRotY, dRotZ = GetFixedAxesRotABCFromFrame( PARTS[i].frame) - -- se devo ruotare - if abs( dRotZ) > GEO.EPS_ANG_SMALL then - EgtRotatePartInRawPart( PARTS[i].id, Z_AX(), -dRotZ) - end - -- sposto punto in basso a sinistra del pezzo sul punto in basso a sinistra del grezzo - local b3Part = EgtGetBBoxGlob( PARTS[i].id, GDB_BB.ONLY_VISIBLE) - local dPartPosX = b3Part:getMin():getX() - local dPartPosY = b3Part:getMin():getY() - local dPartPosZ = b3Part:getMin():getZ() - local b3Raw = EgtGetRawPartBBox( PARTS[i].idRaw) - local dRawPosX = b3Raw:getMin():getX() - local dRawPosY = b3Raw:getMin():getY() - local dRawPosZ = b3Raw:getMin():getZ() - local vtMove = Vector3d( dRawPosX - dPartPosX, dRawPosY - dPartPosY, dRawPosZ - dPartPosZ) - EgtMovePartInRawPart( PARTS[i].id, vtMove) - end - return true -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 - local sMachGroupName = EgtGetMachGroupName( nMachGroupId) - local nMachGroupName = tonumber( sMachGroupName) - if nMachGroupName > nMaxMachGroup then - nMaxMachGroup = nMachGroupName - end - nMachGroupId = EgtGetNextMachGroup( nMachGroupId) - end - return tostring( nMaxMachGroup + 1) -end - ------------------------------------------------------------------------------------------------------------- -- *** Inserimento delle travi nel grezzo *** ------------------------------------------------------------------------------------------------------------- local function MyProcessPieces() - -- creo macchinata - --local MachGroupName = NewMachGroupName() - --local nMachGroup = EgtAddMachGroup( MachGroupName) + -- recupero macchinata (è sempre la prima e ce n'è solo una) local nMachGroup = EgtGetFirstMachGroup() EgtSetCurrMachGroup( nMachGroup) - + for i = 1, #PARTS do PARTS[i].idRaw = EgtGetRawPartFromPart( PARTS[i].id) end - - ---- si crea il grezzo - --CreateRaws( PARTS) - - -- allineo i pezzi come orientamento richiesto dalla macchina - --AlignRawsToTable( PARTS) -- aggiungo sovramateriale ai grezzi AddOverMaterialToRaw( PARTS) @@ -288,10 +239,6 @@ local function MyProcessPieces() PARTS[i].DispOffsets.Phase2 = {} local bInsertedAllOffs = GetDispOffsetFromNotes( i) - -- se non sono settati nelle note, li chiedo - if not bInsertedAllOffs then - bInsertedAllOffs = GetDispOffsetFromInput( i) - end -- se non sono stati inseriti o c'è stato un errore esco subito if not bInsertedAllOffs then return false @@ -317,91 +264,126 @@ local function MyProcessPieces() return true end -------------------------------------------------------------------------------------------------------------- --- *** Inserimento delle lavorazioni *** -------------------------------------------------------------------------------------------------------------- -local function MyProcessFeatures() - local bOk = WinExec.ProcessFeatures( PARTS) - - return true -end - ------------------------------------------------------------------------------------------------------------- -- *** Esecuzione *** ------------------------------------------------------------------------------------------------------------- +-- WIN.FLAG : +-- 0 = generazione e stima, esegue solo un aggiornamento (eventuale) delle lavorazioni +-- 1 = da processare completamente, applicazione lavorazioni, simulazione, generazioen e stima +-- 2 = da processare in preverifica, applicazione lavorazioni e stima --- script principale +-- In generale va completamente riprocessato +local bToProcess = true +local bToRecalc = false +local bCheckNoSim = false -local sFlag = '' -if WIN.FLAG == 0 then - sFlag = 'CHECK+GENERATE' +-- se il progetto deve essere completamente riprocessato +if WIN.FLAG == 1 then + bToProcess = true +-- se deve essere fatta la preverifica, senza simulazione +elseif WIN.FLAG == 2 then + bToProcess = true + bCheckNoSim = true +-- altrimenti solo aggiornato (se necessario) else - sFlag = 'FLAG='..tostring( WIN.FLAG) + bToProcess = false end -local sLog = 'BatchProcess : ' .. WIN.FILE .. ', ' .. ( WIN.MACHINE or EgtGetCurrMachineName()) .. ', ' .. sFlag + +local sLog = 'BatchProcess : ' .. WIN.FILE .. ', ' .. ( WIN.MACHINE or EgtGetCurrMachineName()) .. ', ' .. 'FLAG='..tostring( WIN.FLAG) EgtOutLog( sLog) --- Se da elaborare -EgtOutLog( ' +++ Processing Parts >>>') +-- se bisogna processare +if bToProcess then + -- Se da elaborare + EgtOutLog( ' +++ Processing Parts >>>') --- nascondo geometrie varie -local vAuxId = { EgtGetFirstNameInGroup( GDB_ID.ROOT, 'Profile'), EgtGetFirstNameInGroup( GDB_ID.ROOT, 'Area*'), EgtGetFirstNameInGroup( GDB_ID.ROOT, 'Aux')} -EgtSetStatus( vAuxId, GDB_ST.OFF) + -- nascondo geometrie varie + local vAuxId = { EgtGetFirstNameInGroup( GDB_ID.ROOT, 'Profile'), EgtGetFirstNameInGroup( GDB_ID.ROOT, 'Area*'), EgtGetFirstNameInGroup( GDB_ID.ROOT, 'Aux')} + EgtSetStatus( vAuxId, GDB_ST.OFF) -if not MyProcessInputData() then return end + if not MyProcessInputData() then return end -if not MyProcessPieces() then return end + if not MyProcessPieces() then return end -if not GetDataConfig() then return end + if not GetDataConfig() then return end --- Abilito Vmill -EgtSetInfo( EgtGetCurrMachGroup(), 'Vm', '1') + -- Abilito Vmill + EgtSetInfo( EgtGetCurrMachGroup(), 'Vm', '1') -if not MyProcessFeatures() then return end + WinExec.ProcessFeatures( PARTS) + -- Imposto Nome file CN + local _, sName, _ = EgtSplitPath( WIN.FILE) + EgtSetInfo( EgtGetCurrMachGroup(), 'NcName', sName .. '.cnc') --- Imposto Nome file CN -local _, sName, _ = EgtSplitPath( WIN.FILE) -EgtSetInfo( EgtGetCurrMachGroup(), 'NcName', sName .. '.cnc') + -- Salvo il progetto + EgtSaveFile( WIN.FILE) - --- Salvo il progetto -EgtSaveFile( WIN.FILE) - - --- *** Eseguo simulazione con verifica collisione in cieco *** -local bSimOk, nErr, sErr = EgtSimulate() -if not bSimOk then - if nErr == MCH_SHE.INIT then - WIN.ERR = 19 - WIN.MSG = 'Error starting simulation' - elseif nErr == MCH_SHE.COLLISION then - WIN.ERR = 22 - WIN.MSG = 'Head-part collision' - elseif nErr == MCH_SHE.OUTSTROKE then - WIN.ERR = 23 - WIN.MSG = 'Axis outstroke ' .. sErr - elseif nErr == MCH_SHE.SPECIAL then - WIN.ERR = 24 - WIN.MSG = 'Special error ' .. sErr - else - WIN.ERR = 25 - WIN.MSG = 'General failure (contact supplier)' +-- altrimenti è solo da ricalcolare (eventualmente) +else + -- se cambiata configurazione macchina da ultima elaborazione, devo aggiornare + if EgtCompareFilesLastWriteTime( WIN.FILE, sMachDir .. '\\Tools\\Tools.data') == -1 or + EgtCompareFilesLastWriteTime( WIN.FILE, sMachDir .. '\\' .. sMachine ..'.mlde') == -1 then + bToRecalc = true end - WIN.ROT = 0 - WIN.CUTID = 0 - WIN.TASKID = 0 - local vItem = EgtSplitString( sErr, ';') or {} - for i = 1, #vItem do - vItem[i] = EgtTrim( vItem[i] or '') + EgtOutLog( ' +++ Loading Project already processed >>>') + -- Passo in modalità lavora + EgtSetCurrMachGroup( EgtGetLastMachGroup()) + -- Se necessario eseguo aggiornamento con setup corrente e ricalcolo delle lavorazioni + if bToRecalc then + EgtOutLog( ' +++ Recalculating all dispositions and machinings >>>') + EgtImportSetup() + EgtApplyAllMachinings() + -- Salvo il progetto + EgtSaveFile( WIN.FILE) + end +end + +-- se è da riprocessare o da ricalcolare, si lancia simulazione e calcolo stima tempi +if bToProcess or bToRecalc then + -- simulazione e generazione eseguiti se non è la pre-verifica per stima tempi + if not bCheckNoSim then + -- *** Eseguo simulazione con verifica collisione in cieco *** + local bSimOk, nErr, sErr = EgtSimulate() + if not bSimOk then + if nErr == MCH_SHE.INIT then + WIN.ERR = 19 + WIN.MSG = 'Error starting simulation' + elseif nErr == MCH_SHE.COLLISION then + WIN.ERR = 22 + WIN.MSG = 'Head-part collision' + elseif nErr == MCH_SHE.OUTSTROKE then + WIN.ERR = 23 + WIN.MSG = 'Axis outstroke ' .. sErr + elseif nErr == MCH_SHE.SPECIAL then + WIN.ERR = 24 + WIN.MSG = 'Special error ' .. sErr + else + WIN.ERR = 25 + WIN.MSG = 'General failure (contact supplier)' + end + WIN.ROT = 0 + WIN.CUTID = 0 + WIN.TASKID = 0 + local vItem = EgtSplitString( sErr, ';') or {} + for i = 1, #vItem do + vItem[i] = EgtTrim( vItem[i] or '') + if string.find( vItem[i], 'CUTID', 1, true) then + WIN.CUTID = EgtGetVal( vItem[i], 'CUTID', 'i') or 0 + elseif string.find( vItem[i], 'TASKID', 1, true) then + WIN.TASKID = EgtGetVal( vItem[i], 'TASKID', 'i') or 0 + end + end + WriteErrToLogFile( WIN.ERR, WIN.MSG, WIN.ROT, WIN.CUTID, WIN.TASKID) + -- TODO gestire collisione su feature specifica!!!!!!!!!!!!!!!!!!!!!!!!!!!! + WinExec.AddApplyResultToGlobalList( WIN.ERR, WIN.CUTID, WIN.MSG) + WIN.RESULT = WinLib.TableCopyDeep( RESULT) + WriteResultToJson( RESULT) + return + end + end - WriteErrToLogFile( WIN.ERR, WIN.MSG, WIN.ROT, WIN.CUTID, WIN.TASKID) - -- TODO gestire collisione su feature specifica!!!!!!!!!!!!!!!!!!!!!!!!!!!! - BeamExec.AddApplyResultToGlobalList( WIN.ERR, WIN.CUTID, WIN.MSG) - WIN.RESULT = WinLib.TableCopyDeep( RESULT) - WriteResultToJson( RESULT) - return end -- *** Genero programma CN *** ( se richiesto) @@ -414,8 +396,26 @@ end -- *** Eseguo stima tempi *** EgtOutLog( ' +++ Estimating T&L >>>') if not EgtEstimate( '', 'EgtCAM5 - ' .. WIN.FILE) then - -- TODO gestire errori in stima + WIN.ERR = 21 + WIN.MSG = 'Error estimating production time : ' .. WIN.FILE + WriteErrToLogFile( WIN.ERR, WIN.MSG) + WinExec.AddApplyResultToGlobalList( WIN.ERR, 0, WIN.MSG) + WIN.RESULT = WinLib.TableCopyDeep( RESULT) + WriteResultToJson( RESULT) + return end +local Ttot = EgtGetInfo( EgtGetCurrMachGroup(), 'Ttot', 'd') +local sTime = 'Total Time = ' .. EgtNumToString( Ttot, 1) +EgtOutLog( sTime) + +-- Scrittura tempo totale stimato di lavorazione +WriteTimeToLogFile( Ttot) +RESULT[#RESULT+1] = { dTime = Ttot, sType = 'Time'} + +-- Riporto risultati in tabella globale WIN +WIN.RESULT = WinLib.TableCopyDeep( RESULT) +-- Scrittura json risultati +WriteResultToJson( RESULT) EgtOutLog( ' +++ BatchProcess completed') diff --git a/CAMAuto/LuaLibs/WinExec.lua b/CAMAuto/LuaLibs/WinExec.lua index e07a6e0..0244880 100644 --- a/CAMAuto/LuaLibs/WinExec.lua +++ b/CAMAuto/LuaLibs/WinExec.lua @@ -33,8 +33,9 @@ EgtMdbSave() ------------------------------------------------------------------------------------------------------------- -- *** variabili globali *** ------------------------------------------------------------------------------------------------------------- -TOOLS = nil -MACHININGS = nil +TOOLS = {} +MACHININGS = {} +RESULT = {} -- tabella contenente il risultato, feature per feature, dell'applicazione della strategia scelta e il resoconto dell'analisi fatta ------------------------------------------------------------------------------------------------------------- -- *** COSTANTI *** TODO -> DA SPOSTARE IN WINDATA??? @@ -192,6 +193,17 @@ function WinExec.GetToolsFromDB() end +------------------------------------------------------------------------------------------------------------- +function WinExec.AddApplyResultToGlobalList( nErr, idCut, sMsg) + RESULT[#RESULT+1] = { + sType = 'Part', + idCut = idCut, + idTask = 0, + nErr = nErr, + sMsg = sMsg + } +end + ------------------------------------------------------------------------------------------------------------- -- *** Inserimento delle lavorazioni nelle travi *** ------------------------------------------------------------------------------------------------------------- @@ -218,6 +230,8 @@ local function CollectFeatures( vProc, Part, nIndexPart) Proc.sType = sType Proc.b3Box = EgtGetBBoxGlob( ProcId, GDB_BB.STANDARD) Proc.nPhase = EgtGetInfo( ProcId, 'PHASE', 'i') or nil + Proc.idCut = EgtGetInfo( EgtGetParent( EgtGetParent( ProcId)), 'CUTID', 'i') or 0 + Proc.idTask = EgtGetInfo( ProcId, 'TASKID', 'i') or 0 -- se esiste la geometria if Proc.b3Box and not Proc.b3Box:isEmpty() then